不推荐直接用 iptables -m geoip 封禁,因其模块已移除、不支持 ipv6、数据库更新僵硬;应改用 ipset + maxmind geolite2 csv 实现动态国家封禁,支持热更新、ipv4/ipv6 混合及原子切换,且需过滤私有地址段避免误杀。

为什么直接用 iptables -m geoip 封禁不推荐
因为 iptables 自带的 geoip 模块早已被移除(自 kernel 4.15+ 和 xtables-addons 3.12+ 起默认不编译),现在强行启用要自己打补丁、重编译内核模块,极易导致规则加载失败或重启后失效。更关键的是,它不支持 IPv6,且数据库更新机制僵硬——你改了 /usr/share/xt_geoip/ 下的二进制库,iptables 不会自动 reload,得手动 iptables-restore 全量刷入,运维成本高。
用 ipset + MaxMind GeoLite2 实现动态国家封禁
核心思路是:用 geoiplookup 或 mmdblookup 解析 IP 归属国 → 抽出目标国家的 CIDR 列表 → 批量导入 ipset 集合 → 通过 iptables / nftables 引用该集合做匹配。优势在于:集合可热更新(ipset flush + ipset restore)、支持 IPv4/IPv6 混合、规则轻量(一条 -m set --match-set 即可)、与现有防火墙策略无冲突。
- 必须用
MaxMind GeoLite2 CountryCSV(非旧版 GeoLite Legacy),因为只有它提供按国家导出的完整 CIDR 列表;免费版需注册账号获取license key - 下载后解压得到
GeoLite2-Country-Blocks-IPv4.csv和GeoLite2-Country-Blocks-IPv6.csv,再用GeoLite2-Country-Locations-en.csv查找国家对应的country_iso_code(比如中国是CN) - 写个简短脚本抽 CIDR:
awk -F, '$1 ~ /^([0-9]{1,3}\.){3}[0-9]{1,3}\/[0-9]+$/ && $2 == "CN" {print $1}' GeoLite2-Country-Blocks-IPv4.csv | sort -u > cn-ipv4.txt,IPv6 同理(注意字段位置可能不同,查 CSV 头确认) - 创建 ipset 集合:
ipset create cn-block hash:net family inet hashsize 4096 maxelem 65536(IPv6 用family inet6) - 批量导入:
while read net; do ipset add cn-block "$net"; done
iptables 规则怎么写才不丢包、不误杀
封禁动作必须放在 INPUT 和 FORWARD 链的靠前位置(建议在 ESTABLISHED,RELATED 规则之后、其他业务规则之前),否则已建立的连接可能被意外中断;同时要排除本地回环和内部网段,避免管理通道被锁死。
- 基础封禁规则:
iptables -I INPUT 2 -m set --match-set cn-block src -j DROP(插入到第 2 行,确保在状态跟踪规则之后) - 务必加白名单保护 SSH:
iptables -I INPUT 2 -s 192.168.1.100 -j ACCEPT(把你的运维 IP 提前放行) - 如果服务器跑 Web 服务,检查是否启用了
conntrack模块,否则ESTABLISHED匹配可能失效,导致新连接建连即断 - IPv6 规则不能省略:
ip6tables -I INPUT 2 -m set --match-set cn-block src -j DROP,且集合名要一致(ipset默认 IPv4/IPv6 集合隔离)
自动化更新脚本的关键陷阱
GeoLite2 数据库每月更新一次,但直接覆盖 CSV 再重跑脚本会导致封禁窗口期(集合清空→重新填充期间流量放行)。真正可靠的方案是双集合原子切换 + 延迟刷新。
- 维护两个集合:
cn-block-new和cn-block,每次更新先建cn-block-new,填完再用ipset swap cn-block-new cn-block原子切换,旧集合自动销毁 - 不要在 crontab 直接调用
ipset flush,那是“先删后建”,有毫秒级风险;swap是内核级原子操作,无间隙 -
mmdblookup比geoiplookup更准,但需要libmaxminddb库;若用 CSV 解析,注意某些 CIDR 在 CSV 中重复出现(同一 IP 段归属多国?实际极少),用sort -u去重 - 首次运行前检查
ipset list输出中集合的family是否匹配(inetvsinet6),错配会导致规则不生效且无报错
最易被忽略的是:GeoLite2 的 CN CIDR 包含大量私有地址段(如 10.0.0.0/8)和保留地址,必须在抽数据时过滤掉——否则内网服务全挂。加一行 awk 条件:$1 !~ /^((10|127|169\.254|172\.(1[6-9]|2[0-9]|3[0-1])|192\.168)\.)/。











