iptables
表
iptables 有 5 张表:
• filter(默认):决定"放过/丢弃"
• nat:做 NAT,专门改写源/目的 IP 和端口
• mangle:改 TTL、TOS 等
• raw:在 conntrack 之前操作
• security:SELinux 用
iptables 有 5 张表:
• filter(默认):决定"放过/丢弃"
• nat:做 NAT,专门改写源/目的 IP 和端口
• mangle:改 TTL、TOS 等
• raw:在 conntrack 之前操作
• security:SELinux 用
orbstack
vm-1: client
d2: caddy * 2
ipvs: lb
apt install -y ipvsadm conntrack tcpdump iproute2 net-tools curl iptables
sysctl -w net.ipv4.ip_forward=1
sysctl -w net.ipv4.conf.all.rp_filter=0
sysctl -w net.ipv4.conf.eth0.rp_filter=0
modprobe ip_vs
modprobe ip_vs_rr
modprobe ip_vs_wrr
modprobe ip_vs_sh
modprobe ip_vs_lc
ipvsadm -ln
iptables -t nat -S
ip addr show eth0
ipvsadm -C
ipvsadm -A -t 192.168.139.29:80 -s rr
ipvsadm -a -t 192.168.139.29:80 -r 192.168.139.184:80 -m # -m = NAT (Masq)
ipvsadm -a -t 192.168.139.29:80 -r 192.168.139.184:81 -m
ipvsadm -ln
没有任何 iptables 规则,没有开 vs.conntrack。
tcpdump -i eth0 -s0 -X -nn 'port 80' -w ipvs/not-snat/nat-vm-1-eth0.pcap --print

tcpdump -i eth0 -s0 -X -nn 'port 80 or port 81' -w ipvs/not-snat/nat-ipvs-eth0.pcap --print

tcpdump -i eth0 -s0 -X -nn 'port 80 or port 81' -w ipvs/not-snat/nat-d2-eth0.pcap --print

$ ipvsadm -lnc
IPVS connection entries
pro expire state source virtual destination
TCP 00:59 SYN_RECV 192.168.139.68:37564 192.168.139.29:80 192.168.139.184:80
能看到一直卡在 SYN_RECV 所以 curl 会卡住
开启 conntrack
iptables -t nat -A POSTROUTING -d 192.168.139.184 -p tcp --dport 80 -j SNAT --to 192.168.139.29
iptables -t nat -A POSTROUTING -d 192.168.139.184 -p tcp --dport 81 -j SNAT --to 192.168.139.29
sysctl -w net.ipv4.vs.conntrack=1
iptables -t nat -S POSTROUTING
ipvsadm -ln
IP Virtual Server version 1.2.1 (size=4096)
Prot LocalAddress:Port Scheduler Flags
-> RemoteAddress:Port Forward Weight ActiveConn InActConn
TCP 192.168.139.29:80 rr
-> 192.168.139.184:80 Masq 1 0 1
-> 192.168.139.184:81 Masq 1 0 1
tcpdump -i eth0 -s0 -X -nn 'port 80' -w ipvs/snat/nat-vm-1-eth0.pcap --print

tcpdump -i eth0 -s0 -X -nn 'port 80 or port 81' -w ipvs/snat/nat-ipvs-eth0.pcap --print

tcpdump -i eth0 -s0 -X -nn 'port 80 or port 81' -w ipvs/snat/nat-d2-eth0.pcap --print

conntrack -L
tcp 6 84 TIME_WAIT src=192.168.139.68 dst=192.168.139.29 sport=43856 dport=80 src=192.168.139.184 dst=192.168.139.29 sport=80 dport=43856 [ASSURED] mark=0 use=1
tcp 6 46 TIME_WAIT src=192.168.139.68 dst=192.168.139.29 sport=47380 dport=80 src=192.168.139.184 dst=192.168.139.29 sport=81 dport=47380 [ASSURED] mark=0 use=1
conntrack v1.4.8 (conntrack-tools): 2 flow entries have been shown.
加了规则后链路通了,不过会有新的问题 在 vm-1 抓包能看到发起了重试,这很奇怪
叫 ai 分析了下
根因:net.ipv4.vs.conntrack=1 让 IPVS 跟 conntrack 共存,在连接刚建立的瞬间存在状态机竞争窗口,invalid 状态的包被丢
看 ipvs 视角,前 3 个 GET 的入向包都没有对应的出向(没有转发出去):
18:58:32.335745 ipvs 入向收到 GET(第 2 次重传)
← 没有对应的出向!
18:58:32.544XXX ipvs 入向收到 GET(第 3 次重传)
← 也没有出向!
18:58:32.951XXX ipvs 入向收到 GET(第 4 次重传)
18:58:32.951XXX ipvs 出向转给 d2 ← 这次才转发!
ipvs 把前 3 个数据包丢了!
为什么?这是 IPVS + conntrack 的一个经典 race condition
T0: SYN → IPVS 建立连接 → DNAT 到 d2:81 → 转发
SYN-ACK → ipvs → 反向 NAT → 回 vm-1
ACK → ipvs → 转发到 d2
T0+1ms: GET 数据包到达 ipvs
│
├─ IPVS 已经知道这条 conn 转给 d2:81
├─ 但是 conntrack 表里这条连接还在 SYN_RECV 状态?
│ 或者 conntrack 在 GET 这一刻刚好被 d2 的 SYN-ACK 推到 ESTABLISHED,
│ 但 SNAT 状态机短暂混乱
│
└─ 结果:包被 conntrack 标记为 invalid 或丢弃
第 2 次重传 (200ms 后):仍然有问题,丢
第 3 次重传 (400ms 后):仍然有问题,丢
第 4 次重传 (800ms 后):状态稳定了,终于通过!
这是开了 net.ipv4.vs.conntrack=1 之后的已知副作用——IPVS 和 conntrack 两套状态机共存,握手完成的瞬间存在竞争窗口。
NAT 模式
client ─请求─► director ──NAT──► RS
client ◄───响应────── director ◄───响应─── RS
↑
└ 必经之路!双向流量都过 director
所有包:源/目的 IP 都被改写
DR 模式
client ─请求─► director ──仅改MAC──► RS
client ◄────响应──────────────────── RS
└─ 直接回,回程不经过 director!
请求包:IP 不变,只换二层目的 MAC
响应包:直接二层送出(看起来像 RS 自己就是 VIP)