我有一个 iptables 特定问题。我的机器上定义了以下网络接口:
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
2: eno1: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc pfifo_fast state UP group default qlen 1000
link/ether f8:ca:b8:5c:59:b5 brd ff:ff:ff:ff:ff:ff
inet 172.16.214.45/24 brd 172.16.214.255 scope global dynamic eno1
valid_lft 773635sec preferred_lft 773635sec
3: wlp3s0: <BROADCAST,MULTICAST> mtu 1500 qdisc noop state DOWN group default qlen 1000
link/ether 80:00:0b:d7:a8:c5 brd ff:ff:ff:ff:ff:ff
4: docker0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP group default
link/ether 02:42:bf:b2:fa:86 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
我有一个活动的 docker 容器正在监听 ip 172.17.0.2(附加到 docker0 接口)
我想做两件事:
- 将我机器上端口 8443 上的所有传入数据包转发到其端口 8443 上的 docker 容器 ip 172.17.0.2
- 将lo接口上的所有环回数据包转发到端口8443上的docker容器ip 172.17.0.2
我已经这样做了,但是在环回接口上测试时它不起作用
iptables -t nat -I PREROUTING -i lo -d 127.0.0.1 -p tcp --dport 8443 -j DNAT --to-destination 172.17.0.2:8443
$ curl https://localhost:8443
curl: (7) Failed to connect to localhost port 8443: Connection refused
$ curl -k https://172.17.0.2:8443
{
"paths": [
"/api"
]
}
有经验的 iptables 人员有任何迹象表明我做错了什么吗?
答案1
有两个问题(实际上是非询问的第三个问题,我将用一个简单的(如果不是最好的)解决方案来解决,以防万一,彻底):
本地发起的数据包不会被转发/路由
本地发起的数据包不会被转发(路由)。所以这些数据包永远不会看到nat/PREROUTING
链。看一眼Netfilter 和通用网络中的数据包流了解内核中数据包的生命周期中发生的情况。本地数据包来自“本地进程”。
因此,除了nat/PREROUTING
执行DNAT
来自“外部”的数据包的规则之外,还应如下所示:
iptables -t nat -I PREROUTING -i eno1 -p tcp --dport 8443 -j DNAT --to-destination 172.17.0.2:8443
您还必须使用nat/OUTPUT
链条。由于它的输出,它的语法仅允许传出接口,因此它的更改如下:
iptables -t nat -I OUTPUT -o lo -p tcp --dport 8443 -j DNAT --to-destination 172.17.0.2:8443
初始数据包和随后的流实际上将被重新路由到其他接口(我怀疑上一个链接示意图中的“重新路由检查”可能没有正确放置)。
这适用于属于主机的任何 IP(即:172.16.214.45 和 172.17.0.1),除了...
lo
禁止在接口之外看到 IP 范围 127.0.0.0/8
Linux 内核有特定的设置,防止 127.0.0.0/8 范围内的任何 IP 路由到接口以外的任何地方lo
,并且如果“尝试”使用其他接口,则会丢弃任何此类数据包,如火星源,正确的是:远程系统(即使它是一个容器)不会接受源地址为 127.0.0.1 且目标地址为 172.17.0.2 的传入数据包,至少是因为它不知道在哪里回复它。
因此,除了 之外,还必须对数据包进行一个SNAT
(或简单的) ,这次是在遍历的链中(参见前面的原理图):MASQUERADE
DNAT
nat/POSTROUTING
iptables -t nat -I POSTROUTING -s 127.0.0.1 -d 172.17.0.2 -j MASQUERADE
这仍然不够:顾名思义,nat/POSTROUTING
发生了后路由(实际上是在之后发生的重新路由检查DNAT
),并且数据包已经作为火星源被丢弃。
对于特殊情况,例如本例,可以使用每个接口切换来覆盖本地网络限制route_localnet
:
echo 1 > /proc/sys/net/ipv4/conf/docker0/route_localnet
现在,路由堆栈允许源为 127.0.0.1 的数据包通过,并且在通过虚拟线路发送到容器之前,它们的源将根据之前的规则更正为 172.17.0.1:它有效。
你确实应该避免任何需要第二种情况的事情,因为它是不必要的复杂性:使用属于主机的 IP 而不是 127.0.0.1 对于任何测试来说应该足够了。此外,如果docker0
要删除并重新创建界面,route_localnet
设置将会丢失,并且将其设置为默认值也是不明智的。
发夹
没有询问,但如果您在同一 LAN 中添加第二个系统(此处为容器),则 lan-to-host-to-same-lan 重定向会出现问题(除非 Docker 已经在网络级别处理此问题)。
nat/PREROUTING
我在答案开头编写的规则仅处理接口eno1
。我添加此限制是有原因的-i eno1
:如果没有它,如果 172.17.0.0/16 网络中的其他容器尝试连接到例如 172.16.214.45:8443(或 172.17.0.1:8443),数据包将被重定向至 172.17.0.2。 172.17.0.2 将会回复直接地到源:另一个容器,并完全绕过主机及其 NAT 规则。该容器将看到来自它不知道的源的回复数据包并拒绝它(使用TCP RST
)。所以根本不处理它比处理不好更好。 Docker 可能提供了特定的方法来直接将服务解析为其他容器的 IP/端口,而无需涉及主机。
如果无论如何需要,有几种方法可以克服这个问题,通常需要权衡,从简单的 NAT(丢失源 IP 或必须将其转换为虚构网络,以用于记录目的)到能够拦截的复杂网桥和/或路由器设置局域网通信。
这是一个简单的解决方案,其中源是 SNAT 的,使用NETMAP
,到虚构的网络 10.17.0.0/16。一个简单的先决条件:10.17.0.0/16 必须在主机上路由(即使没有真正使用),无论是在默认路由(可能是这种情况)、特定路由上还是在该虚拟网络中具有 IP 的主机上这个目的。具有此 IP 的数据包仅存在于docker0
网络内部。
-i eno1
从上面的规则中删除 后PREROUTING
,添加以下新规则:
iptables -t nat -I POSTROUTING -s 172.17.0.0/16 -d 172.17.0.0/16 -j NETMAP --to 10.17.0.0/16
现在,从 LAN 到同一 LAN 的重定向将起作用,目标容器的日志显示 10.17.0.0/16 范围内的源 IP。
当然,发夹的情况也应该避免。