给定一个相当常见的 nftables/iptables 防火墙设置(OUTPUT 接受、INPUT/FORWARD 接受已建立+相关、默认丢弃):
table ip nat {
chain DOCKER {
iifname "docker0" return
iifname != "docker0" meta l4proto tcp ip daddr 172.17.0.1 tcp dport 5000 dnat to 172.17.0.2:5000
iifname != "docker0" meta l4proto tcp ip daddr 127.0.0.1 tcp dport 5000 dnat to 172.17.0.2:5000
}
chain POSTROUTING {
type nat hook postrouting priority srcnat; policy accept;
oifname != "docker0" ip saddr 172.17.0.0/16 masquerade
meta l4proto tcp ip saddr 172.17.0.2 ip daddr 172.17.0.2 tcp dport 5000 masquerade
}
chain PREROUTING {
type nat hook prerouting priority dstnat; policy accept;
fib daddr type local jump DOCKER
}
chain OUTPUT {
type nat hook output priority -100; policy accept;
ip daddr != 127.0.0.0/8 fib daddr type local jump DOCKER
}
}
table ip filter {
chain INPUT {
type filter hook input priority filter; policy drop;
iif "lo" accept comment "Allow loopback"
ct state invalid drop
ct state established,related accept
iifname "bond0-data" tcp dport 22 accept
udp dport 3052 accept
iifname "bond0-data" tcp dport 9090 accept
log drop
}
chain FORWARD {
type filter hook forward priority filter; policy drop;
jump DOCKER-USER
jump DOCKER-ISOLATION-STAGE-1
oifname "docker0" ct state related,established accept
oifname "docker0" jump DOCKER
iifname "docker0" oifname != "docker0" accept
iifname "docker0" oifname "docker0" accept
log drop
}
chain OUTPUT {
type filter hook output priority filter; policy accept;
}
chain FORWARD-OVERRIDE {
type filter hook forward priority filter + 10; policy accept;
ct state invalid drop
ct state established,related accept comment "Accept established, related"
iifname "bond1-control" drop comment "Drop new connections from bond1-control"
}
chain DOCKER {
iifname != "docker0" oifname "docker0" meta l4proto tcp ip daddr 172.17.0.2 tcp dport 5000 accept
}
chain DOCKER-ISOLATION-STAGE-1 {
iifname "docker0" oifname != "docker0" jump DOCKER-ISOLATION-STAGE-2
return
}
chain DOCKER-ISOLATION-STAGE-2 {
oifname "docker0" drop
return
}
chain DOCKER-USER {
return
}
}
请注意,容器 172.17.0.2:5000 使用 docker 的端口转发映射到端口 5000。
如果我尝试从容器 172.17.0.3 直接连接到 172.17.0.2:5000 那么这有效。如果我尝试通过主机 IP (172.17.0.1:5000) 从容器 172.17.0.3 连接,那么这不起作用,除非我添加iffname "docker0" accept
到 INPUT 链。
我想了解为什么。
尝试连接时 kern.log 显示以下内容:
IN=docker0 OUT= PHYSIN=vethbdc197b MAC=xxxxxxx SRC=172.17.0.3 DST=172.17.0.1 LEN=44 TOS=0x00 PREC=0x00 TTL=58 ID=37344 PROTO=TCP SPT=52535 DPT=5000 WINDOW=1024 RES=0x00 SYN URGP=0
为什么允许直接连接时该连接被阻止?目前我最好的猜测是,输出接受以某种方式允许直接连接,然后输入将其视为已建立的、相关的。但目前尚不清楚在进行 docker 端口映射时情况有何不同。也许 DNAT 会导致一个单独的 INPUT 连接,然后被阻止?
任何人都可以从 iptables/nftables 的角度阐明它是如何工作的吗?
答案1
事实证明,这个问题的答案非常简单。我的印象是容器只是被主机网络堆栈视为“额外”IP。这不是真的——容器有自己的网络堆栈,与主机分离(技术上容器也可以有自己的 iptables/nftables 规则)。
这意味着容器到容器的直接连接不会经过主机的 OUTPUT 或 INPUT 链,因为涉及的 IP 不是主机网络堆栈上的本地 IP。方向连接如下所示:
- 容器 A 输出(可能为空)
- 主机转发
- 容器 B 输入(可能为空)
事实上,这在主机上通过 FORWARD 有点令人惊讶 - 我已经测试过,情况确实如此。这似乎是由于桥接的工作方式所致。
在这种情况下,主机允许连接,因为 FORWARD 链具有iifname "docker0" oifname "docker0" accept
.
另一方面,如果容器 A 尝试连接到主机上的转发端口,那么它确实会进入主机的 INPUT 链,并且根据上述规则,它会被阻止。