使用iptables转发,同时妥善保留源IP

Tui*_*lak 5 iptables port-forwarding forwarding docker

我有一台运行 Wireguard 的服务器(因此需要masquerade)和一个在端口 2525 上运行的容器。

我有以下iptables规则:

iptables -t nat -A PREROUTING -i eth0 -p tcp --dport 25 -j DNAT --to-destination 172.18.0.1:2525
iptables -t nat -A POSTROUTING -o eth0 -j MASQUERADE
Run Code Online (Sandbox Code Playgroud)

直接连接时server:2525,Docker 容器能够看到我的真实 IP 地址 ( 1.2.3.4)。当连接到 port 时server:25,Docker 容器会看到由以下内容提供的本地 IP docker network

Apr 07 12:45:46 mx postfix/smtpd[87]: lost connection after CONNECT from unknown[172.18.0.1]
Apr 07 12:45:46 mx postfix/smtpd[87]: disconnect from unknown[172.18.0.1] commands=0/0
Run Code Online (Sandbox Code Playgroud)

如何确保 Docker 容器在连接到端口 25 时(而不仅仅是在连接到端口 2525 时)正确地看到公共 IP 地址。

谢谢

# iptables -L -n -v -t nat
Chain PREROUTING (policy ACCEPT 0 packets, 0 bytes)
 pkts bytes target     prot opt in     out     source               destination
52300 3131K DNAT       tcp  --  eth0   *       0.0.0.0/0            0.0.0.0/0            tcp dpt:25 to:172.18.0.1:2525
 150K 8524K DOCKER     all  --  *      *       0.0.0.0/0            0.0.0.0/0            ADDRTYPE match dst-type LOCAL

Chain INPUT (policy ACCEPT 0 packets, 0 bytes)
 pkts bytes target     prot opt in     out     source               destination

Chain OUTPUT (policy ACCEPT 0 packets, 0 bytes)
 pkts bytes target     prot opt in     out     source               destination
    2   120 DOCKER     all  --  *      *       0.0.0.0/0           !127.0.0.0/8          ADDRTYPE match dst-type LOCAL

Chain POSTROUTING (policy ACCEPT 0 packets, 0 bytes)
 pkts bytes target     prot opt in     out     source               destination
 3385  256K MASQUERADE  all  --  *      !docker0  172.17.0.0/16        0.0.0.0/0
1733K  104M MASQUERADE  all  --  *      !br-b147ffdbc9f3  172.18.0.0/16        0.0.0.0/0
    0     0 MASQUERADE  tcp  --  *      *       172.17.0.2           172.17.0.2           tcp dpt:53
    0     0 MASQUERADE  udp  --  *      *       172.17.0.2           172.17.0.2           udp dpt:53
    0     0 MASQUERADE  tcp  --  *      *       172.18.0.2           172.18.0.2           tcp dpt:25

Chain DOCKER (2 references)
 pkts bytes target     prot opt in     out     source               destination
   12  1419 RETURN     all  --  docker0 *       0.0.0.0/0            0.0.0.0/0
    0     0 RETURN     all  --  br-b147ffdbc9f3 *       0.0.0.0/0            0.0.0.0/0
   56  3192 DNAT       tcp  --  !docker0 *       0.0.0.0/0            0.0.0.0/0            tcp dpt:5354 to:172.17.0.2:53
    0     0 DNAT       udp  --  !docker0 *       0.0.0.0/0            0.0.0.0/0            udp dpt:5354 to:172.17.0.2:53
  107  6020 DNAT       tcp  --  !br-b147ffdbc9f3 *       0.0.0.0/0            0.0.0.0/0            tcp dpt:2525 to:172.18.0.2:25
Run Code Online (Sandbox Code Playgroud)
# ip addr
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN group default qlen 1000
    link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
    inet 127.0.0.1/8 scope host lo
       valid_lft forever preferred_lft forever
    inet6 ::1/128 scope host
       valid_lft forever preferred_lft forever
2: eth0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc pfifo_fast state UP group default qlen 1000
    link/ether 32:d0:56:15:0a:64 brd ff:ff:ff:ff:ff:ff
    altname enp0s3
    altname ens3
    inet 159.223.80.86/20 brd 159.223.95.255 scope global eth0
       valid_lft forever preferred_lft forever
    inet 10.15.0.19/16 brd 10.15.255.255 scope global eth0:1
       valid_lft forever preferred_lft forever
    inet6 2400:6180:0:d0::f57:6001/64 scope global
       valid_lft forever preferred_lft forever
    inet6 fe80::30d0:56ff:fe15:a64/64 scope link
       valid_lft forever preferred_lft forever
3: eth1: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc pfifo_fast state UP group default qlen 1000
    link/ether 32:dc:4a:e4:27:be brd ff:ff:ff:ff:ff:ff
    altname enp0s4
    altname ens4
    inet 10.130.244.15/16 brd 10.130.255.255 scope global eth1
       valid_lft forever preferred_lft forever
    inet6 fe80::30dc:4aff:fee4:27be/64 scope link
       valid_lft forever preferred_lft forever
4: wg0: <POINTOPOINT,NOARP,UP,LOWER_UP> mtu 1420 qdisc noqueue state UNKNOWN group default qlen 1000
    link/none
    inet 10.200.200.52/24 scope global wg0
       valid_lft forever preferred_lft forever
5: wg1: <POINTOPOINT,NOARP,UP,LOWER_UP> mtu 1420 qdisc noqueue state UNKNOWN group default qlen 1000
    link/none
    inet 10.222.111.1/24 scope global wg1
       valid_lft forever preferred_lft forever
6: br-b147ffdbc9f3: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP group default
    link/ether 02:42:46:21:70:c0 brd ff:ff:ff:ff:ff:ff
    inet 172.18.0.1/16 brd 172.18.255.255 scope global br-b147ffdbc9f3
       valid_lft forever preferred_lft forever
    inet6 fe80::42:46ff:fe21:70c0/64 scope link
       valid_lft forever preferred_lft forever
7: docker0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP group default
    link/ether 02:42:66:22:41:91 brd ff:ff:ff:ff:ff:ff
    inet 172.17.0.1/16 brd 172.17.255.255 scope global docker0
       valid_lft forever preferred_lft forever
    inet6 fe80::42:66ff:fe22:4191/64 scope link
       valid_lft forever preferred_lft forever
9: veth31eff9d@if8: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue master docker0 state UP group default
    link/ether e6:fb:80:5d:c7:a3 brd ff:ff:ff:ff:ff:ff link-netnsid 0
    inet6 fe80::e4fb:80ff:fe5d:c7a3/64 scope link
       valid_lft forever preferred_lft forever
19: veth01269f5@if18: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue master br-b147ffdbc9f3 state UP group default
    link/ether 36:f4:e7:43:5f:da brd ff:ff:ff:ff:ff:ff link-netnsid 2
    inet6 fe80::34f4:e7ff:fe43:5fda/64 scope link
       valid_lft forever preferred_lft forever
Run Code Online (Sandbox Code Playgroud)

A.B*_*A.B 2

只需让 Docker 处理重定向,该重定向是动态的,并且可能会在添加、删除或重新启动容器时发生变化。但请参阅下面的更新

此重定向不应该重定向到 172.18.0.1,它是主机而不是容器。当主机收到此类连接时,它会被docker-proxy代理到容器进行处理,在此过程中会丢失源 IP 地址。

docker-proxyDocker 已经在规则集的最后一条规则中通过DNAT + 正确路由此端口(除了主机本身,在主机本身中扮演此角色),将其路由到具有 172.18.0.2 地址的正在运行的容器。只不过它配置为使用端口 2525 而不是端口 25。

  107  6020 DNAT       tcp  --  !br-b147ffdbc9f3 *       0.0.0.0/0            0.0.0.0/0            tcp dpt:2525 to:172.18.0.2:25
Run Code Online (Sandbox Code Playgroud)

这个问题应该通过 Docker 设置来修复,而不是手动iptables规则,因为容器布局发生变化时就无法更改。由于 25 端口是特权端口,如果 Docker 无根运行,则需要进行额外设置,请查看有关暴露特权端口的文档


更新(考虑OP的评论):OP当前无法使用-p 25:25,因为docker-proxy与本地主机的SMTP服务器冲突并竞争侦听主机上的端口25。这就是OP 进行初始(错误)iptables重定向的原因。

一个人可以:

  • 通过将属性设置为 falsedocker-proxy运行来全局禁用dockerduserland-proxy

    作为参数 或作为添加到 的--userland-proxy=false属性。"userland-proxy": false/etc/docker/daemon.json

    然后,这将允许使用docker run ... -p 25:25 ...(如文档所述)而不会发生冲突:当到达 localhost 或 $HOSTNAME 时,主机将到达自身,远程系统将到达容器,并且没有“地址已在使用中”将使主机的 SMTP 守护进程或 Docker 的容器失败开始。

  • 或者添加手动重定向(下面有冗长的设置以几乎自动完成)

    每当容器重新启动时,其(内部)IP 地址都存在更改的风险。所以这个必须要计算一下。mx因此,对于使用命名的网络mx和涉及的单个 IP 地址命名的容器,可以按照下面的说明来完成此操作。

    创建一个单独的预路由链(这样就可以刷新它而无需刷新其他任何内容)并首先调用它:

    iptables -t nat -N mynat
    iptables -t nat -I PREROUTING -j mynat
    
    Run Code Online (Sandbox Code Playgroud)

    mx可以通过编程方式检索容器的 IP 地址(对于使用单个地址命名的容器的简单情况):

    containerip=$(docker container inspect --format '{{.NetworkSettings.IPAddress}}' mx)
    
    Run Code Online (Sandbox Code Playgroud)

    (或者可以使用jq:)containerip=$(docker container inspect mx | jq '.[].NetworkSettings.IPAddress'

    查找桥接接口名称更加复杂,或者至少我找不到仅使用docker ... inspect. 因此,在主机上找到其 IP 地址,并使用查询主机ip address来仅查找设置此特定 IP 地址的桥接接口(需要命令jq。)

     bridgeip=$(docker network inspect --format '{{(index .IPAM.Config 0).Gateway}}' mx)
     bridgeinterface=$(ip -json address show to "$bridgeip"/32 | jq -r '.[].ifname')
    
    Run Code Online (Sandbox Code Playgroud)

    mynat每次(重新)启动容器时刷新并重新填充:

    iptables -t nat -F mynat
    iptables -t nat -A mynat ! -i "$bridgeinterface" -p tcp --dport 25 -j DNAT --to-destination "$containerip":25
    
    Run Code Online (Sandbox Code Playgroud)

    对于当前的情况来说:

    iptables -t nat -I mynat !-i br-b147ffdbc9f3 -p tcp --dport 25 -j DNAT --到目的地 172.18.0.2:25

    为了确保 Docker 自己的防火墙规则不会阻止此类流量,请FORWARDDOCKER-USER链中的 filter/ 开始执行类似的操作。

    最初(如果从启动,您可能还必须先创建DOCKER-USER):

    iptables -N myforward
    iptables -I DOCKER-USER 1 -j myforward
    
    Run Code Online (Sandbox Code Playgroud)

    然后每次容器(重新)启动时:

    iptables -F myforward
    iptables -A myforward -p tcp ! -i "$bridgeinterface" -d "$containerip" -p tcp --dport 25 -j ACCEPT
    
    Run Code Online (Sandbox Code Playgroud)

    对于当前的情况来说:

    iptables -A myforward -p tcp ! -i br-b147ffdbc9f3 -d 172.18.0.2 -p tcp --dport 25 -j ACCEPT
    
    Run Code Online (Sandbox Code Playgroud)

笔记:

  • 为了简化上述规则并避免一些计算,容器及其桥接网络可以使用固定IP地址启动。例如,请参阅此 SO Q/A:将静态 IP 分配给 Docker 容器

  • 这里还有一个 UL SE Q/A,其中包含我关于与 Docker 交互时出现的问题的答案(它适用于nftables,但有关DOCKER-USER链或br_netfilter桥交互的某些部分仍然令人感兴趣):nftables 白名单 docker