最佳实践

概述

参考:

让服务器成为路由器

这里假设想作为路由器的服务器的 IP 为 172.38.180.211

一、首先需要保证路由器具有 IP 转发能力,开启 IP 转发

echo 1 > /proc/sys/net/ipv4/ip_forward

二、保证路由器在收到局域网内其他设备的数据包时,将收到的 IP SNAT 为本机 IP。这有两种方法

方法一:手动 SNAT

export LOCAL_IP="172.38.180.211"
export LAN_CIDR="172.38.180.0/24"
iptables -t nat -A POSTROUTING -s ${LAN_CIDR} ! -d ${LAN_CIDR} -j SNAT --to-source ${LOCAL_IP}

如果 LOCAL_IP 直接就是公网 IP 的话,也就是这台服务器本身就有公网 IP 的话,那就更完美了。

方法二:自动 SNAT

iptables -t nat -A POSTROUTING -o eth0 -j MASQUERADE

这条命令表示将 eth0 接口的出站流量进行伪装,i.e. 使用 eth0 接口的 IP 地址作为源地址。这样,内网的主机就可以通过本服务器访问外网了。

三、将内网服务器的网关设为这台服务器即可

我可以 ping 别人,别人不能 ping 我

  • iptables -A INPUT -p icmp –icmp-type 8 -s 0/0 -j DROP # 默认 INPUT 链的策略为 ACCEPT 的时候用
  • iptables -A INPUT -p icmp –icmp-type 0 -s 0/0 -j ACCEPT # 默认 INPUT 链的策略为 DROP 的时候用
  • iptables -A OUTPUT -p icmp –icmp-type 0 -s LOCALIP -j DROP # 默认 OUTPUT 链的策略为 ACCEPT 的时候用,注意把 Localip 改为本机 IP
  • iptables -A OUTPUT -p icmp –icmp-type 8 -s LOCALIP -j ACCEPT # 默认 OUTPUT 链的策略为 DROP 的时候用,注意把 Localip 改为本机 IP

敲门机制 - 通过特定行为放通入站流量

参考: https://www.cnblogs.com/martinzhang/p/5348769.html

敲门机制是指,OS 记录访问者各种请求,达到某些条件(e.g. 在特定时间达到 N 次 TCP、ICMP、etc. 请求)后,允许该访问者访问 OS。

一、敲门(knocking)机制

  • 监听 ICMP echo 请求(ping)
  • 数据包长度必须正好是 1078 字节
  • 将源 IP 记录到 sshKeyList 列表中

[!Tip] 可以在 sshKeyList 文件 cat /proc/net/xt_recent/sshKeyList 中查看敲门成功的 IP

ICMP报文结构:IP头部(20 Bytes) + ICMP头部(8 Bytes) + 数据部分Data。所以 ping 的时候,指定数据大小为 1050 即可

iptables -A INPUT -p icmp -m icmp --icmp-type 8 -m length --length 1078 -m recent --name sshKeyList --set -j ACCEPT

# 使用 TCP 代替 ICMP 实现敲门机制。将对 1000 端口得请求记录到 sshKeyList 中。然后下面只要在某时间内访问了 1000 端口 N 次,即可放通
# **注意**:这个 1000 端口哪怕没有任何程序监听也是可以的!
# iptables -A INPUT -p tcp --dport 1000 -m recent --name sshKeyList --set -j DROP

二、只有满足以下条件才能访问 10443 端口

  • 源 IP 在过去 30 秒内至少敲门 5 次(hitcount 5),但是不能超过 5 次(第一条,达到 6 次及以上就会 DROP)
  • IP 必须在 sshKeyList 中
iptables -A INPUT -p tcp -m tcp --dport 10443 -m recent --name sshKeyList --rcheck --seconds 30 --hitcount 6 -j DROP
iptables -A INPUT -p tcp -m tcp --dport 10443 -m recent --name sshKeyList --rcheck --seconds 30 --hitcount 5 -j ACCEPT

# 不限制放通的端口
# iptables -A INPUT -m recent --name sshKeyList --rcheck --seconds 30 --hitcount 5 -j ACCEPT

[!Tip] –set, –name, –rcheck, –seconds, –hitcount 都是 recent 扩展中可用的选项。详见 iptables CLI

三、允许所有已经建立的连接继续通信

[!Attention] 这条规则只能放在敲门行为之后,如果放在开头将导致开门失效。原因未知

iptables -A INPUT -m state --state ESTABLISHED -j ACCEPT

四、拒绝所有

iptables -A INPUT -j DROP

五、实现效果

使用如下 ping 的方式,即可让设备放通本机

ping -s 1050 -c 5 ${DestIP}

随后在 30 秒内即可访问任意端口,由于第三条的存在,已建立的连接不会中断

常用命令组合

使用 ping -s 1050 -c 5 目标IP 即可放通

iptables -A INPUT -p icmp -m icmp --icmp-type 8 -m length --length 1078 -m recent --name sshKeyList --set -j ACCEPT
iptables -A INPUT -m recent --name sshKeyList --rcheck --seconds 30 --hitcount 6 -j DROP
iptables -A INPUT -m recent --name sshKeyList --rcheck --seconds 30 --hitcount 5 -j ACCEPT
iptables -A INPUT -m state --state RELATED,ESTABLISHED -j ACCEPT
iptables -A INPUT -j DROP

使用 ping -s 1050 -c 5 目标IP 即可放通 10443 端口

iptables -A INPUT -p icmp -m icmp --icmp-type 8 -m length --length 1078 -m recent --name sshKeyList --set -j ACCEPT
iptables -A INPUT -p tcp -m tcp --dport 10443 --syn -m recent --name sshKeyList --rcheck --seconds 30 --hitcount 6 -j DROP
iptables -A INPUT -p tcp -m tcp --dport 10443 --syn -m recent --name sshKeyList --rcheck --seconds 30 --hitcount 5 -j ACCEPT
iptables -A INPUT -m state --state RELATED,ESTABLISHED -j ACCEPT
iptables -A INPUT -j DROP

使用 for i in {1..5}; do nc -w 1 ${DestIP} 1000; done 访问 1000 端口 5 次即可饭桶

iptables -A INPUT -p tcp --dport 1000 -m recent --name sshKeyList --set -j DROP
iptables -A INPUT -p tcp -m recent --name sshKeyList --rcheck --seconds 30 --hitcount 6 -j DROP
iptables -A INPUT -p tcp -m recent --name sshKeyList --rcheck --seconds 30 --hitcount 5 -j ACCEPT
iptables -A INPUT -m state --state RELATED,ESTABLISHED -j ACCEPT
iptables -A INPUT -j DROP

其他

屏蔽 HTTP 服务 Flood×××,有时会有用户在某个服务,例如 HTTP 80 上发起大量连接请求,此时我们可以启用如下规则:

  • iptables -A INPUT -p tcp –dport 80 -m limit –limit 100/minute –limit-burst 200 -j ACCEPT
  • 上述命令会将连接限制到每分钟 100 个,上限设定为 200。

12、屏蔽指定 MAC 地址

使用如下规则可以屏蔽指定的 MAC 地址:

iptables -A INPUT -m mac –mac-source 00:00:00:00:00:00 -j DROP

13、限制并发连接数

如果你不希望来自特定端口的过多并发连接,可以使用如下规则:

iptables -A INPUT -p tcp –syn –dport 22 -m connlimit –connlimit-above 3 -j REJECT

以上规则限制每客户端不超过 3 个连接。

17、允许建立相关连接

随着网络流量的进出分离,要允许建立传入相关连接,可以使用如下规则:

iptables -A INPUT -m conntrack –ctstate ESTABLISHED,RELATED -j ACCEPT

允许建立传出相关连接的规则:

iptables -A OUTPUT -m conntrack –ctstate ESTABLISHED -j ACCEPT

18、丢弃无效数据包

很多网络 ××× 都会尝试用 ××× 自定义的非法数据包进行尝试,我们可以使用如下命令来丢弃无效数据包:

iptables -A INPUT -m conntrack –ctstate INVALID -j DROP

屏蔽邮件发送规则,可以在规则中屏蔽 SMTP 传出端口:

  • iptables -A OUTPUT -p tcp –dports 25,465,587 -j REJECT

使用 iptables 对多租户环境中的 TCP 限速

原文链接:使用 iptables 对多租户环境中的 TCP 限速

我们有个服务以类似 SideCar 的方式和应用一起运行,SideCar 和应用通过 Unix Domain Socket 进行通讯。为了方便用户,在开发的时候不必在自己的开发环境中跑一个 SideCar,我用 socat 在一台开发环境的机器上 map UDS 到一个端口。这样用户在开发的时候就可以直接通过这个 TCP 端口测试服务,而不用自己开一个 SideCar 使用 UDS 了。

因为所有人都要用这一个地址做开发,所以就有互相影响的问题。虽然性能还可以,几十万 QPS 不成问题,但是总有憨憨拿来搞压测,把资源跑满,影响别人。我在使用说明文档里用红色大字写了这是开发测试用的,不能压测,还是有一些视力不好的同事会强行压测。隔三差五我就得去解释一番,礼貌地请同事不要再这样做了。

最近实在累了。研究了一下直接给这个端口加上 per IP 的 rate limit,效果还不错。方法是在 Per-IP rate limiting with iptables[1] 学习到的,这个公司是提供一个多租户的 SaaS 服务,也有类似的问题:有一些非正常用户 abuse 他们的服务,由于 abuse 发生在连接建立阶段,还没有进入到业务代码,所以无法从应用的层面进行限速,解决发现就是通过 iptables 实现的。详细的实现方法可以参考这篇文章。

iptables 本身是无状态的,每一个进入的 packet 都单独判断规则。rate limit 显然是一个有状态的规则,所以要用到 module: hashlimit。(原文中还用到了 conntrack,他是想只针对新建连接做限制,已经建立的连接不限制速度了。因为这个应用内部就可以控制了,但是我这里是想对所有的 packet 进行限速,所以就不需要用到这个 module)

完整的命令如下:

$ iptables --new-chain SOCAT-RATE-LIMIT
$ iptables --append SOCAT-RATE-LIMIT \
    --match hashlimit \
    --hashlimit-mode srcip \
    --hashlimit-upto 50/sec \
    --hashlimit-burst 100 \
    --hashlimit-name conn_rate_limit \
    --jump ACCEPT
$ iptables --append SOCAT-RATE-LIMIT --jump DROP
$ iptables -I INPUT -p tcp --dport 1234 --jump SOCAT-RATE-LIMIT

第一行是新建一个 iptables Chain,做 rate limit;

第二行处理如果在 rate limit 限额内,就接受包;否则跳到第三行,直接将包 DROP;

最后将新的 Chain 加入到 INPUT 中,对此端口的流量进行限制。

有关 rate limit 的算法,主要是两个参数:

  1. --hashlimit-upto 其实本质上是 1s 内可以进入多少 packet,50/sec 就是 20ms 一个 packet;
  2. 那如何在 10ms 发来 10 个 packet,后面一直没发送,怎么办?这个在测试情景下也比较常见,不能要求用户一直匀速地发送。所以就要用到 --hashlimit-burst。字面意思是瞬间可以发送多少 packet,但实际上,可以理解这个参数就是可用的 credit。

两个指标配合起来理解,就是每个 ip 刚开始都会有 burst 个 credit,每个 ip 发送来的 packet 都会占用 burst 里面的 credit,用完了之后再发来的包就会被直接 DROP。这个 credit 会以 upto 的速度一直增加,但是最多增加到 burst(初始值),之后就 use it or lost it.

举个例子,假如 --hashlimit-upto 50/sec --hashlimit-burst 20 的话,某个 IP 以匀速每 ms 一个 packet 的速度发送,最终会有多少 packets 被接受?答案是 70. 最初的 20ms,所有的 packet 都会被接受,因为 --hashlimit-burst 是 20,所以最初的 credit 是 20. 这个用完之后就要依赖 --hashlimit--upto 50/sec 来每 20ms 获得一个 packet credit 了。所以每 20ms 可以接受一个。

这是限速之后的效果,非常明显:

原文链接:https://www.kawabangga.com/posts/4594

参考资料

[1]Per-IP rate limiting with iptables: https://making.pusher.com/per-ip-rate-limiting-with-iptables/index.html


最后修改 September 4, 2025: iptables 最佳实践 (7e806e4e)