了解 docker 端口转发和 iptables/nftables

了解 docker 端口转发和 iptables/nftables

给定一个相当常见的 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 链,并且根据上述规则,它会被阻止。

相关内容