我有一个 bash 脚本,它加载一个大的 iptables 规则文件。所有这些 iptable 规则都是为了阻止所有美国 IP 地址。该文件的大小约为 9MB。当我尝试执行它时,大约需要 40 分钟才能执行。
有没有办法加快这个脚本的执行速度:
# head -n 4 iptables_block_usa.sh
iptables -A INPUT -s 216.187.112.96/27 -j DROP
iptables -A INPUT -s 216.187.112.128/27 -j DROP
iptables -A INPUT -s 216.187.112.160/31 -j DROP
iptables -A INPUT -s 216.187.112.163/32 -j DROP
......
答案1
iptables 由于其概念而存在局限性。什么时候iptables改变规则,这就是真正发生的事情:
- iptables询问内核所有的规则集
- iptables更改(在用户空间中)规则集,通常是添加一入口
- iptables返回给内核所有的规则集
因此,您必须避免这种情况发生,因为当您向 100000 条规则的规则集中添加一条规则时,会浪费大量 CPU。
另外,由于您的规则是线性编写的,因此每次数据包(如果使用有状态规则来帮助解决此问题,则可能是处于新状态的数据包)不在此列表中,全部遍历规则以找出没有匹配的规则并接受它。所以你的数据包查找与列表的大小成正比,这是不好的。
不该做什么:
- 不要尝试并行运行多个
iptables
,没有内核锁定,您最终将丢失规则。使用iptables --wait
(用户空间)锁定,但它不再是并行的。
您可以执行以下任一操作来解决您的问题:
使用
iptables-restore
一次性加载整个规则集。运行一次 bash 脚本(并等待)并使用 转储最终结果
iptables-save
,准备好与 重用iptables-restore
,或者使用进行编辑sed等你的脚本有一个准备好的iptables-restore
格式文件。如果它是默认的过滤表,它应该具有类似于以下的格式:*filter :INPUT ACCEPT [0:0] :FORWARD ACCEPT [0:0] :OUTPUT ACCEPT [0:0] -A INPUT -s 216.187.112.96/27 -j DROP -A INPUT -s 216.187.112.128/27 -j DROP -A INPUT -s 216.187.112.160/31 -j DROP -A INPUT -s 216.187.112.163/32 -j DROP [...] -A INPUT -s 192.0.2.0/24 -j DROP COMMIT
否则,不要直接使用iptables要加载地址列表,请使用其同伴IP集它经过优化来处理这个问题,通过使用哈希表并且仅将要添加的条目推送到内核,而不是像 iptables 那样首先询问其列表:
如果你想
- 存储多个IP地址或端口号,并与iptables的集合一次性匹配;
- 针对 IP 地址或端口动态更新 iptables 规则没有性能损失;
- 使用单个 iptables 规则表达复杂的 IP 地址和基于端口的规则集,并受益于 IP 集的速度
那么 ipset 可能是适合您的工具。
您必须定义最大元素数(默认为“仅”65536)。哈希大小由 ipset 动态调整大小,因此不需要(或
hashsize 65536
至少考虑添加)。对于简单的情况(仅源 IP 网络),您可以执行以下操作:ipset create usa_ips hash:net maxelem 300000 hashsize 65536
现在您将使用 循环遍历您的列表
ipset
。该列表可以是usa_ips.txt
这样的文件:216.187.112.96/27 216.187.112.128/27 216.187.112.160/31 216.187.112.163/32 [...]
这样循环可能需要 10mn,如下所示:
while read net; do ipset add usa_ips $net done < usa_ips.txt
在最后或结束之前,您可以看到列表有多大,例如:
# ipset -t list usa_ips Name: usa_ips Type: hash:net Revision: 6 Header: family inet hashsize 65536 maxelem 300000 Size in memory: 3847384 References: 0 Number of entries: 131076
现在您可以删除与此单曲匹配的所有内容iptables规则:
iptables -A INPUT -m set --match-set usa_ips src -j DROP
这将设置参考上面的条目为 1,因为 iptables 正在使用它。
ipset save usa_ips > ipset_usa_ips.txt
可以使用和保存并重新加载上面所做的当前设置ipset restore usa_ips < ipset_usa_ips.txt
。当您看到输出格式时,您会知道您还可以像上面一样为其准备一个一次性文件,这要快得多:加载约 130000 个条目只需 < 1 秒。格式是这样的:更新:实际上你会得到一个错误,因为该集现在正在使用中,这就是为什么有一个
swap
命令可用IP集。您必须将该列表加载到备用集并将其与前一个集交换。create usa_ips_new hash:net family inet hashsize 65536 maxelem 300000 add usa_ips_new 216.187.112.96/27 add usa_ips_new 216.187.112.128/27 add usa_ips_new 216.187.112.160/31 add usa_ips_new 216.187.112.163/32 [...]
它可以将之前创建的集合替换为:
# ipset restore < usa_ips_new.txt # ipset swap usa_ips usa_ips_new # ipset destroy usa_ips_new
切换到nftables或者至少是 iptables-over-nftables API(Debian 10 和 RHEL 8/CentOS 8 上默认)
nftables已经做出来解决许多错误iptables。添加规则时,仅将此增量传输到内核:不需要询问整个规则集/编辑/发送回来完成iptables。 iptables-over-nftables 兼容性层也在做同样的事情,因为它是用 nftables 实现的(以及主要用于 iptables 的特殊匹配和目标的兼容性层,IP集其中,无法翻译)。
我就讲到这里,把练习留给你。只知道最近nftables在最近的内核上使用,有本机集支持,当与
flags interval
(并且可能auto-merge
避免重复)一起使用时,可以完全取代使用IP集对于你的情况。
答案2
您可以指定多个源,用分隔符分隔,
[!] -s, --源地址[/掩码][,...] 来源规范。地址可以是网络名称、主机名、网络 IP 地址(带有 /mask)或普通 IP 地址。在将规则提交给内核之前,主机名只会解析一次。请注意,指定要使用远程查询(例如 DNS)解析的任何名称是一个非常糟糕的主意。掩码可以是 ipv4 网络掩码(对于 iptables)或普通数字,指定网络掩码左侧 1 的数量。因此,iptables 掩码 24 相当于 255.255.255.0。 A ”!”地址规范之前的参数反转地址的含义。标志 --src 是此选项的别名。可以指定多个地址,但这会扩展为多个规则(使用 -A 添加时),或者会导致多个规则被删除(使用 -D 时)。
然后你会受到 bash 命令行最大长度的限制,该长度可能因操作系统而异。找到你的极限跑
getconf ARG_MAX
这样做将节省您多次执行 iptables 命令。
问题的例子是:
iptables -A INPUT -s 216.187.112.96/27,216.187.112.128/27,216.187.112.160/31,216.187.112.163/32 -j DROP
为了使其更具可读性,您应该能够像这样分割行:
iptables -A INPUT -s\
216.187.112.96/27,\
216.187.112.128/27,\
216.187.112.160/31,\
216.187.112.163/32 \
-j DROP
让我们算一下:
- 您的文件有 9MB。行数约为 48B,即大约
9 * 1024^2 / 48 ≈ between 190000 and 200000
行数(1 行 = 1 次 iptables 调用)。 - 在 Ubuntu 上有 ARG_MAX 2097152。
9 * 1024^2 / 2097152 ≈ 5
行