跳到主要内容

nftables 透明拦截流量

概述

iptables 和 nftables 都能用于透明代理的流量拦截,区别就是 nftables 是后起之秀,未来取代 iptables。可读性更高、性能更好。

本文介绍如何在路由器利用 nftables 透明拦截流量。

思路

可以利用 Pod 的 postStart 和 preStop 两个生命周期的 hook 来设置和清理 nftables 相关规则:

        lifecycle:
postStart:
exec:
command:
- /scripts/set-rules.sh
preStop:
exec:
command:
- /scripts/clean.sh

脚本和 nftables 通过 configmap 挂载:

        volumeMounts:
- mountPath: /scripts
name: script
- mountPath: /etc/nftables
name: nftables-config

另外,保险起见,可以加个 initContainer,保证在启动的时候也执行下清理规则的脚本(避免代理 Pod 异常挂掉导致 preStop 里的清理规则脚本没执行到):

      initContainers:
- command:
- /scripts/clean.sh
image: your-proxy-image
imagePullPolicy: IfNotPresent
name: clean
securityContext:
privileged: true
volumeMounts:
- mountPath: /scripts
name: script

nftables 规则

nftables.conf
#!/usr/sbin/nft -f

table inet proxy
delete table inet proxy

table inet proxy {

# 保留网段,参考:https://zh.wikipedia.org/zh-sg/%E4%BF%9D%E7%95%99IP%E5%9C%B0%E5%9D%80
set byp4 {
typeof ip daddr
flags interval
elements = {
0.0.0.0/8,
10.0.0.0/8,
100.64.0.0/10,
127.0.0.0/8,
169.254.0.0/16,
172.16.0.0/12,
192.0.0.0/24,
192.0.2.0/24,
192.88.99.0/24,
192.168.0.0/16,
198.18.0.0/15,
198.51.100.0/24,
203.0.113.0/24,
224.0.0.0/4,
240.0.0.0-255.255.255.255
}
}
set byp6 {
typeof ip6 daddr
flags interval
elements = {
::,
::1,
::ffff:0:0:0/96,
100::/64,
64:ff9b::/96,
2001::/32,
2001:10::/28,
2001:20::/28,
2001:db8::/32,
2002::/16,
fc00::/7,
fe80::/10,
ff00::/8
}
}

chain prerouting {
type filter hook prerouting priority filter; policy accept;

meta l4proto != { tcp, udp } counter return

# 避免拦截公网回包(如果没用主路由方案,可以删除)
iifname ppp0 \
ct state new \
ct mark set 11 \
log prefix "Public Inbound:" \
counter \
return \
comment "Mark conntrack for connection from internet, avoid intercept the return packets"

# 不拦截从公网主动进来的包(如果没用主路由方案,可以删除)
iifname ppp0 counter return

# 避免源和目的都是本机公网 IP 时造成代理 UDP 无限回环
fib daddr type local counter return

# 对保留 IP(包括内网) 放行。
ip daddr @byp4 counter return
ip6 daddr @byp6 counter return
goto proxy
}
chain proxy {
# 对其它数据包(其它内网机器访问公网的包)进行拦截,全部转发给代理。
# 踩坑:一定 tproxy 到回环 ip (127.0.0.1和::1),如果 tproxy 到 0.0.0.0和::,运行一段时间后,可能会导致 UDP 疯狂不停回环打满 CPU(本机外部IP到本机外部IP一直在lo网卡打转)
meta l4proto { tcp, udp } tproxy ip to 127.0.0.1:12345 meta mark set 1 counter
meta l4proto { tcp, udp } tproxy ip6 to [::1]:12345 meta mark set 1 counter
}
chain output {
type route hook output priority filter; policy accept;

# 不拦截代理自身发出的包。
meta skgid eq 23333 counter return

# 不拦截公网回包
ct mark 11 counter return

# 对保留 IP(包括内网) 放行。
ip daddr @byp4 counter return
ip6 daddr @byp6 counter return

# 避免源和目的都是本机公网 IP 时造成代理 UDP 无限回环
fib daddr type local counter return

# 主机内进程发出的包打上 mark,让报文走策略路由重新进入 PREROUTING,以便被代理。
meta l4proto { tcp, udp } meta mark set 1 counter
}
}

设置规则的脚本

set-rules.sh
#!/bin/bash

set -x

while :; do
export https_proxy=http://127.0.0.1:10809 # 假设代理会启动 10809 端口的 HTTP 代理
code=$(curl -I -o /dev/null -m 5 -s -w %{http_code} https://www.google.com | xargs) # 通过代理探测目标地址是否正常响应
if [[ "${code}" == "200" ]]; then # 探测成功就退出探测循环,准备设置策略路由
break
fi
echo "bad code:${code};" >/tmp/error.log
sleep 5s
done

echo "proxy is ready, set up rules"
ip rule list | grep "from all fwmark 0x1 lookup 100"
if [ $? -ne 0 ]; then
echo "add rule and route"
ip rule add fwmark 0x1 table 100
ip route add local 0.0.0.0/0 dev lo table 100
ip -6 rule add fwmark 0x1 table 100
ip -6 route add local ::/0 dev lo table 100
fi

nft -f /etc/nftables/nftables.conf # 设置 nftables 规则
exit 0

清理规则的脚本

clean.sh
#!/bin/bash

set -x

nft delete table inet proxy # 关闭时清理 nftables,避免拉不了镜像
ip rule delete fwmark 0x1 table 100
ip route delete local 0.0.0.0/0 dev lo table 100
ip -6 rule delete fwmark 0x1 table 100
ip -6 route delete local ::/0 dev lo table 100

exit 0

定向拦截

如果家里的设备特别多,全局拦截流量的话,对代理的压力可能较大,有时可能会影响网速。

这种情况可以考虑只拦截特定设备的流量,比如只拦截 10.10.10.2~10.10.10.10 这个小网段的流量,该网段的 IP 可以静态分配,在 dnsmasq 的 dhcp 里配一下,示例:

log-queries=extra
no-resolv
no-poll
server=61.139.2.69
strict-order
log-dhcp
cache-size=2000
dhcp-range=10.10.10.11,10.10.10.254,255.255.255.0,12h
dhcp-authoritative
dhcp-leasefile=/var/lib/dnsmasq/dnsmasq.leases
dhcp-option=option:router,10.10.10.2
dhcp-option=option:dns-server,61.139.2.69,218.6.200.139
dhcp-host=a0:78:17:87:0e:51,10.10.10.5 # M1
dhcp-host=fc:18:3c:37:6a:1c,10.10.10.6 # iPhone - roc

dhcp-host 配置项里填入设备的 MAC 地址及其对应固定分配的 IP 地址。

注意

有些设备在内网通信时可能不会使用真实的 MAC 地址,比如 iPhone,可以在无线局域网里点进连上的 WiFi 设置下,关掉 私有无线局域网地址