在维护CentOS服务器的日常中,你会发现来自某些特定国家的IP地址,会持续对你的SSH端口、数据库端口或Web后台进行暴力破解和恶意扫描。如果你的业务仅面向国内用户,或者明确不希望某些地区的访客访问特定服务,那么直接使用Firewalld的富规则(Rich Rules)来按国家维度封锁IP段,是一种成本极低且高效的防护手段。这不需要额外安装复杂的IPS系统,也无需对应用层代码做任何修改,直接在系统防火墙层面就能完成精准拦截。
为什么选择Firewalld富规则而不是直接使用iptablesCentOS 7及之后的版本默认使用Firewalld作为防火墙管理工具。虽然底层依然是调用内核的netfilter,但Firewalld引入了区域(Zone)和富规则的概念,使得复杂策略的管理变得极其灵活。直接写iptables规则去匹配成千上万个IP段,不仅会让规则文件变得臃肿不堪,还会严重影响数据包匹配效率。而Firewalld的富规则配合ipset功能,可以将大量的IP地址段打包成一个集合,防火墙只需要做一次集合匹配,性能损耗极低,管理起来也像操作一个“黑名单”一样简单直观。
核心思路:将国家IP段转化为ipset集合要实现按国家封锁,不能指望Firewalld自带地理信息库。我们需要借助互联网上公开的国家级IP分配数据,将这些CIDR网段提取出来,注入到Linux内核的ipset中,最后通过Firewalld的富规则调用这个ipset集合。整个过程分为三步:获取IP列表、创建并填充ipset、编写永久生效的富规则。
第一步:安装必要工具并获取国家IP段首先确保系统安装了ipset和必要的文本处理工具。在CentOS上执行以下命令:
yum install ipset curl wget -y
接下来我们需要获取可靠的IP段数据。目前互联网上有多个组织维护着按国家分类的IP分配列表,例如IPdeny、Country IP Blocks等。以IPdeny为例,我们可以直接下载指定国家的聚合IP段文件。假设我们要封锁来自A国的所有IP访问,使用以下命令获取其IP段:
# 注意:实际操作中请将文件名中的国家代码替换为目标国家的两位ISO代码 wget -O /tmp/blocked_country.zone http://www.ipdeny.com/ipblocks/data/countries/xx.zone
下载下来的文件每一行都是一个CIDR格式的网段,例如“1.2.3.0/24”。我们需要对这些数据进行简单清洗,确保没有空行和非法字符。
第二步:创建并配置ipset集合ipset是Linux内核提供的一个IP集合框架,可以存储大量的IP地址、网段、端口等信息。我们需要创建一个类型为hash:net的集合,这种类型专门用于存储网段,匹配速度极快。执行以下命令创建集合:
ipset create blocked_country hash:net maxelem 100000
这里maxelem参数指定了集合能容纳的最大条目数,根据实际下载到的IP段数量进行调整,一般一个中型国家的IP段数量在几千到几万条不等,设置10万足够应对绝大多数场景。接下来编写一个简单的脚本来批量导入IP段:
#!/bin/bash
# 清空集合中的旧数据,确保每次导入都是干净的状态
ipset flush blocked_country
# 逐行读取IP段文件并添加到ipset中
while read -r line; do
# 跳过空行和注释行
[[ -z "$line" || "$line" =~ ^# ]] && continue
ipset add blocked_country "$line"
done < /tmp/blocked_country.zone
将上述脚本保存为/usr/local/bin/update_ipset.sh并赋予执行权限,运行一次即可将数万条IP段在几秒内全部导入内核级集合中。你可以通过ipset list blocked_country命令查看当前集合中的条目数量,确认导入成功。
第三步:编写Firewalld富规则调用ipset有了ipset集合后,Firewalld的富规则可以通过source ipset=语法直接引用。假设我们要封锁这些IP段对服务器所有服务的访问,可以在默认区域(通常为public)中添加一条拒绝规则:
firewall-cmd --permanent --add-rich-rule='rule family="ipv4" source ipset="blocked_country" reject'
如果你只想封锁特定端口,比如SSH的22端口,可以更精细地控制:
firewall-cmd --permanent --add-rich-rule='rule family="ipv4" source ipset="blocked_country" port port="22" protocol="tcp" reject'
这里的reject动作会向请求方返回一个ICMP不可达的响应,让对方明确知道连接被拒绝。如果你希望彻底静默处理,让对方感觉不到服务器的存在,可以将reject替换为drop,这样数据包会被直接丢弃,不产生任何回应。重载防火墙使规则生效:
firewall-cmd --reload
验证规则是否生效,可以使用firewall-cmd --list-rich-rules查看当前所有富规则。此时,来自目标国家的任何IP尝试连接你的服务器,都会在防火墙层面被直接拦截,根本不会到达应用层。
处理IPv6地址的必要补充很多管理员只关注IPv4而忽略了IPv6,这是一个严重的安全盲区。如果你的服务器启用了IPv6,攻击者完全可以通过IPv6绕过你的IPv4封锁规则。IPdeny同样提供按国家分类的IPv6地址段,下载地址格式类似:
wget -O /tmp/blocked_country_v6.zone http://www.ipdeny.com/ipblocks/data/countries/xx.zone
注意IPv6的zone文件与IPv4是同一个URL,但内容格式不同。你需要创建一个类型为hash:net family inet6的ipset集合:
ipset create blocked_country_v6 hash:net family inet6 maxelem 50000
然后同样编写脚本将IPv6网段导入,并在Firewalld中添加对应的IPv6富规则:
firewall-cmd --permanent --add-rich-rule='rule family="ipv6" source ipset="blocked_country_v6" reject'
重载防火墙后,双栈防护才算完整。
实现IP段数据的自动更新国家的IP分配并非一成不变,互联网号码分配机构会不定期调整各地区的IP资源。如果长期不更新IP段列表,可能会出现漏网之鱼或者误封新分配的IP。因此我们需要建立一个定期更新机制。创建一个自动更新脚本/usr/local/bin/update_firewall_country.sh:
#!/bin/bash
# 定义国家代码,根据实际需要修改
COUNTRY_CODE="xx"
# 备份当前IP段文件
cp /tmp/blocked_country.zone /tmp/blocked_country.zone.bak 2>/dev/null
# 下载最新的IPv4 IP段
wget -q -O /tmp/blocked_country.zone http://www.ipdeny.com/ipblocks/data/countries/${COUNTRY_CODE}.zone
# 检查下载是否成功
if [ $? -ne 0 ]; then
echo "下载IP段文件失败,恢复备份"
cp /tmp/blocked_country.zone.bak /tmp/blocked_country.zone
exit 1
fi
# 清空并重新填充ipset
ipset flush blocked_country
while read -r line; do
[[ -z "$line" || "$line" =~ ^# ]] && continue
ipset add blocked_country "$line" 2>/dev/null
done < /tmp/blocked_country.zone
# 记录更新时间
echo "IPset updated at $(date)" >> /var/log/ipset_update.log
将该脚本加入crontab,建议每周执行一次,避开业务高峰期:
0 3 * * 0 /usr/local/bin/update_firewall_country.sh
这样每周日凌晨3点系统会自动拉取最新IP段并更新ipset,无需重启防火墙或中断现有连接,因为ipset的flush和add操作是原子性的,不会造成规则中断。
确保重启后ipset和规则不丢失ipset集合存储在内存中,系统重启后会丢失。Firewalld的富规则虽然设置了--permanent,但它引用的ipset集合必须在Firewalld启动之前就存在,否则规则加载会失败。解决这个问题需要将ipset的持久化配置写入系统服务。CentOS下ipset的配置文件通常位于/etc/sysconfig/ipset,但默认可能不存在。我们需要手动创建并配置保存与恢复机制:
# 保存当前ipset配置到文件 ipset save > /etc/sysconfig/ipset
然后启用ipset服务,让它开机自动加载配置:
systemctl enable ipset systemctl start ipset
确认ipset服务状态正常后,再重新加载Firewalld,确保富规则能正确引用到已加载的集合。如果你在更新脚本中修改了ipset内容,记得在脚本末尾加上ipset save > /etc/sysconfig/ipset,将最新状态持久化到磁盘。
性能考量与误封排查使用ipset做大规模IP段匹配,性能远优于逐条iptables规则。一个包含5万条CIDR网段的hash:net集合,匹配一个数据包的时间复杂度接近O(1),对服务器网络栈的影响微乎其微。但如果你同时封锁数十个国家,集合总条目达到几十万甚至上百万,建议监控一下CPU软中断的占用情况。对于绝大多数场景,封锁一两个主要攻击来源国完全在可承受范围内。
在实际运维中,偶尔会遇到误封的情况。比如某个CDN节点的IP被划入了被封锁国家的IP段,导致部分合法用户无法访问。排查方法是先通过日志确认客户端IP,然后使用ipset test命令检查该IP是否命中了集合:
ipset test blocked_country 客户端IP地址
如果返回“is in set blocked_country”,说明该IP确实被规则拦截了。此时可以考虑在富规则之前添加一条白名单规则,允许特定IP通行:
firewall-cmd --permanent --add-rich-rule='rule family="ipv4" source address="白名单IP" accept'
注意规则的顺序很重要,Firewalld按照添加顺序进行匹配,白名单规则必须放在封锁规则之前才能生效。
将封锁策略与区域绑定实现精细化管理如果你的服务器有多张网卡或者多个服务面向不同用户群,可以利用Firewalld的区域特性进行更精细的控制。比如你可以创建一个名为blocked_zone的自定义区域,将所有封锁规则和ipset绑定到这个区域,然后将特定网卡或服务绑定到该区域。这样既实现了对恶意流量的隔离,又不影响其他区域的正常通信策略。创建自定义区域并添加规则的命令如下:
firewall-cmd --permanent --new-zone=blocked_zone firewall-cmd --permanent --zone=blocked_zone --add-rich-rule='rule family="ipv4" source ipset="blocked_country" drop' firewall-cmd --permanent --zone=blocked_zone --add-interface=eth1 firewall-cmd --reload
这种架构让防火墙策略的层次更加清晰,后续维护和审计都会方便很多。
监控与日志记录建议直接drop数据包虽然高效,但会让攻击行为完全不可见,不利于安全分析。建议在正式使用drop之前,先用reject配合日志记录运行一段时间,观察拦截量和来源分布。可以通过富规则的log前缀功能记录被拦截的请求:
firewall-cmd --permanent --add-rich-rule='rule family="ipv4" source ipset="blocked_country" log prefix="COUNTRY_BLOCK: " level="info" limit value="10/m" reject'
这条规则会在系统日志中每分钟最多记录10条被拦截的信息,既满足了审计需求,又不会让日志文件被洪水般的记录撑爆。通过分析/var/log/messages中的COUNTRY_BLOCK前缀日志,你可以清晰地看到哪些IP在尝试连接、目标端口是什么,从而进一步优化防护策略。
通过上述方法,你可以在CentOS服务器上构建起一套基于国家维度的自动化防火墙拦截体系。这套方案完全基于系统自带工具,不依赖第三方商业软件,稳定性和可控性都极高。只要定期更新IP段数据,它就能持续有效地将绝大多数来自特定地区的恶意流量挡在门外,让你的服务器安全防护水平提升一个台阶。
