加速 bash 脚本的执行

加速 bash 脚本的执行

我有一个 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

您可以指定多个源,用分隔符分隔,

iptables(8)

[!] -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

相关内容