
Moby Bridge 网络端口映射 iptables 规则详解禁用 Userland Proxy 的 Hairpin NAT 场景【免费下载链接】mobyThe Moby Project - a collaborative project for the container ecosystem to assemble container-based systems项目地址: https://gitcode.com/GitHub_Trending/mo/moby本篇技术文章基于 MobyDocker Engine 上游项目仓库中integration/network/bridge/iptablesdoc/templates/usernet-portmap-noproxy.md文档模板及其自动生成的规则快照完整解析「用户自定义 bridge 网络 发布端口 禁用 userland proxy」场景下 Docker 在 iptables 中生成的全部规则。读完后你将能够独立读懂并验证 Docker Engine 在禁用用户态代理后如何通过 hairpin发夹式NAT 规则让宿主机直接访问容器发布端口并理解每条规则对应的源码实现位置。文档背景这是一套自动生成的 iptables 快照该模板位于仓库integration/network/bridge/iptablesdoc/目录属于 Docker Engine 的 iptables 使用文档套件的一部分。根据 index.md 的说明这套文档由测试用例TestBridgeIptablesDoc自动生成测试会真实启动一个 dockerd 守护进程按场景创建网络和容器然后抓取 iptables 输出再把抓取结果与templates/目录下的文本模板text/template合并最后与generated/目录中的产物做 diff——如果生成的规则与仓库中的快照不一致测试就会失败。原文档同时给出两点重要前提这里必须继承仅供开发参考不是稳定接口Docker 的 iptables以及 ip6tables规则结构会在版本之间变化不属于稳定接口不应依赖具体规则布局编写防火墙脚本。ip6tables 规则与 iptables 模式一致文档只展示 IPv4 规则。规则顺序不保证稳定bridge 驱动在初始化时会删除自建链并在网络恢复时重建规则见 bridge_linux.go 的configure而 filter 表的 FORWARD 链不会被清空且网络重建顺序与创建顺序不同因此守护进程重启后规则排列可能变化。firewalld 重载会清空 iptables守护进程通过 dbus 监听其重载事件并重建规则。filter 表的 INPUT/OUTPUT 链不被 Docker 使用来自宿主机物理网络或宿主机自身的包会被路由进 bridge 网络后命中 filter 表的 FORWARD 链。当前仓库中本场景的模板 usernet-portmap-noproxy.md 与生成产物 usernet-portmap-noproxy.md 一一对应生成产物内嵌了真实的 iptables 表格本文据此展开。仓库中还有同族场景文档可对照阅读带 userland proxy 的 usernet-portmap.md、发布到 loopback 的 usernet-portmap-lo.md、禁用容器间通信的 usernet-portmap-noicc.md 等。场景搭建禁用 userland proxy 后发布端口文档定义的操作步骤等价于以下命令dockerd --userland-proxyfalse docker network create \ -o com.docker.network.bridge.namebridge1 \ --subnet 192.0.2.0/24 --gateway 192.0.2.1 bridge1 docker run --network bridge1 -p 8080:80 --name c1 busybox要点说明--userland-proxyfalse禁用 userland proxy。启用时Docker 会为每个发布端口启动一个用户态代理进程docker-proxy由它监听宿主机端口并把连接转发进容器禁用后端口发布完全依赖内核的 DNAThairpin NAT路径宿主机访问192.0.2.2:80或宿主机 IP 的8080端口的包直接由 iptables 完成地址转换。-o com.docker.network.bridge.namebridge1把自定义网络绑定到名为bridge1的网桥设备便于规则中直观看到网桥名。--subnet 192.0.2.0/24 --gateway 192.0.2.1使用文档约定的 192.0.2.0/24 测试网段TEST-NET-1容器c1得到地址192.0.2.2。端口映射-p 8080:80宿主机 8080 → 容器 80tcp。在源码层面「是否启用 userland proxy」直接影响 iptables 的配置结构。从 bridge_linux.go 第 199 行可以看到防火墙配置中的Hairpin字段就是由 proxy 开关取反得到的Hairpin: !config.EnableProxy,而 firewaller.go 第 2728 行的注释明确解释了二者的等价关系// Hairpin means the userland proxy will not be running. Hairpin bool也就是说禁用 userland proxy 等价于开启 hairpin 模式发布端口的 DNAT/MASQUERADE 规则需要让「宿主机自己发出的包」也能被转换和转发这正是下文 nat 表差异的根源。filter 表与启用 userland proxy 时完全相同原文档结论filter 表与启用 userland proxy 时一致——用户态代理只影响 nat 表以及一个 proxy 进程本身不改变过滤规则。生成产物中捕获到的完整 filter 表如下保留原文全貌Chain INPUT (policy ACCEPT 0 packets, 0 bytes) num pkts bytes target prot opt in out source destination Chain FORWARD (policy ACCEPT 0 packets, 0 bytes) num pkts bytes target prot opt in out source destination 1 0 0 DOCKER-USER all -- any any anywhere anywhere 2 0 0 DOCKER-FORWARD all -- any any anywhere anywhere Chain OUTPUT (policy ACCEPT 0 packets, 0 bytes) num pkts bytes target prot opt in out source destination Chain DOCKER (2 references) num pkts bytes target prot opt in out source destination 1 0 0 ACCEPT tcp -- !bridge1 bridge1 anywhere 192.0.2.2 tcp dpt:http 2 0 0 DROP all -- !docker0 docker0 anywhere anywhere 3 0 0 DROP all -- !bridge1 bridge1 anywhere anywhere Chain DOCKER-BRIDGE (1 references) num pkts bytes target prot opt in out source destination 1 0 0 DOCKER all -- any docker0 anywhere anywhere 2 0 0 DOCKER all -- any bridge1 anywhere anywhere Chain DOCKER-CT (1 references) num pkts bytes target prot opt in out source destination 1 0 0 ACCEPT all -- any docker0 anywhere anywhere ctstate RELATED,ESTABLISHED 2 0 0 ACCEPT all -- any bridge1 anywhere anywhere ctstate RELATED,ESTABLISHED Chain DOCKER-FORWARD (1 references) num pkts bytes target prot opt in out source destination 1 0 0 DOCKER-CT all -- any any anywhere anywhere 2 0 0 DOCKER-INTERNAL all -- any any anywhere anywhere 3 0 0 DOCKER-BRIDGE all -- any any anywhere anywhere 4 0 0 ACCEPT all -- docker0 any anywhere anywhere 5 0 0 ACCEPT all -- bridge1 any anywhere anywhere Chain DOCKER-INTERNAL (1 references) num pkts bytes target prot opt in out source destination Chain DOCKER-USER (1 references) num pkts bytes target prot opt in out source destination -P INPUT ACCEPT -P FORWARD ACCEPT -P OUTPUT ACCEPT -N DOCKER -N DOCKER-BRIDGE -N DOCKER-CT -N DOCKER-FORWARD -N DOCKER-INTERNAL -N DOCKER-USER -A FORWARD -j DOCKER-USER -A FORWARD -j DOCKER-FORWARD -A DOCKER -d 192.0.2.2/32 ! -i bridge1 -o bridge1 -p tcp -m tcp --dport 80 -j ACCEPT -A DOCKER ! -i docker0 -o docker0 -j DROP -A DOCKER ! -i bridge1 -o bridge1 -j DROP -A DOCKER-BRIDGE -o docker0 -j DOCKER -A DOCKER-BRIDGE -o bridge1 -j DOCKER -A DOCKER-CT -o docker0 -m conntrack --ctstate RELATED,ESTABLISHED -j ACCEPT -A DOCKER-CT -o bridge1 -m conntrack --ctstate RELATED,ESTABLISHED -j ACCEPT -A DOCKER-FORWARD -j DOCKER-CT -A DOCKER-FORWARD -j DOCKER-INTERNAL -A DOCKER-FORWARD -j DOCKER-BRIDGE -A DOCKER-FORWARD -i docker0 -j ACCEPT -A DOCKER-FORWARD -i bridge1 -j ACCEPT对应规则的含义与源码 port.go 中setPerPortForwarding的构造逻辑一致DOCKER链第 1 条-A DOCKER -d 192.0.2.2/32 ! -i bridge1 -o bridge1 -p tcp -m tcp --dport 80 -j ACCEPT—— 以容器 IP192.0.2.2为目的地址、从网桥bridge1出去的 80 端口包放行实现发布端口的「开放端口」访问DOCKER链第 2、3 条DROP进入docker0/bridge1的包若未从该网桥进入则丢弃防止绕过上述放行规则直接访问网桥DOCKER-CT对已建立/相关连接RELATED,ESTABLISHED出网桥方向直接放行加速返回路径DOCKER-FORWARD以DOCKER-CT → DOCKER-INTERNAL → DOCKER-BRIDGE的固定顺序串联各子链最后无条件放行从docker0、bridge1进入的包。nat 表hairpin 模式下的完整规则这是本场景的核心。原文档给出的完整 nat 表如下Chain PREROUTING (policy ACCEPT 0 packets, 0 bytes) num pkts bytes target prot opt in out source destination 1 0 0 DOCKER all -- any any anywhere anywhere ADDRTYPE match dst-type LOCAL Chain INPUT (policy ACCEPT 0 packets, 0 bytes) num pkts bytes target prot opt in out source destination Chain OUTPUT (policy ACCEPT 0 packets, 0 bytes) num pkts bytes target prot opt in out source destination 1 0 0 DOCKER all -- any any anywhere anywhere ADDRTYPE match dst-type LOCAL Chain POSTROUTING (policy ACCEPT 0 packets, 0 bytes) num pkts bytes target prot opt in out source destination 1 0 0 MASQUERADE all -- any bridge1 anywhere anywhere ADDRTYPE match src-type LOCAL 2 0 0 MASQUERADE all -- any !bridge1 192.0.2.0/24 anywhere 3 0 0 MASQUERADE all -- any docker0 anywhere anywhere ADDRTYPE match src-type LOCAL 4 0 0 MASQUERADE all -- any !docker0 172.17.0.0/16 anywhere 5 0 0 MASQUERADE tcp -- any any 192.0.2.2 192.0.2.2 tcp dpt:http Chain DOCKER (2 references) num pkts bytes target prot opt in out source destination 1 0 0 DNAT tcp -- any any anywhere anywhere tcp dpt:http-alt to:192.0.2.2:80等价的 iptables 命令行原文档以折叠块形式提供此处完整保留-P PREROUTING ACCEPT -P INPUT ACCEPT -P OUTPUT ACCEPT -P POSTROUTING ACCEPT -N DOCKER -A PREROUTING -m addrtype --dst-type LOCAL -j DOCKER -A OUTPUT -m addrtype --dst-type LOCAL -j DOCKER -A POSTROUTING -o bridge1 -m addrtype --src-type LOCAL -j MASQUERADE -A POSTROUTING -s 192.0.2.0/24 ! -o bridge1 -j MASQUERADE -A POSTROUTING -o docker0 -m addrtype --src-type LOCAL -j MASQUERADE -A POSTROUTING -s 172.17.0.0/16 ! -o docker0 -j MASQUERADE -A POSTROUTING -s 192.0.2.2/32 -d 192.0.2.2/32 -p tcp -m tcp --dport 80 -j MASQUERADE -A DOCKER -p tcp -m tcp --dport 8080 -j DNAT --to-destination 192.0.2.2:80结合源码可以逐条定位这些规则的来源PREROUTING/OUTPUT → DOCKER 的跳转规则由 iptabler.go 中的addNATJumpRules编程约第 194 行是否覆盖 loopback 正是由Hairpin即无 proxy开关决定的见下文差异一。POSTROUTING 的 LOCAL 源地址 MASQUERADE-A POSTROUTING -o bridge1 -m addrtype --src-type LOCAL -j MASQUERADE由 network.go 第 306 行写入标记为MASQ LOCAL HOST且仅在Hairpin为真时生效enable n.ipt.config.Hairpin——这就是原文档所称的setupIPTablesInternal旧代码路径现已重构进internal/iptabler包行为。每端口 DNAT hairpin MASQUERADE由 port.go 的setPerPortNAT第 79121 行生成对应差异三与差异四。与启用 userland proxy 时的四项差异原文档的核心结论部分列出了与 usernet-portmap 场景userland proxy 启用的 4 点差异。下面逐条继承并结合当前仓库源码展开。差异一OUTPUT 链对 loopback 地址也会跳转到 DOCKER 链原文The jump from the OUTPUT chain to DOCKER happens even for loopback addresses.nat 表中的-A OUTPUT -m addrtype --dst-type LOCAL -j DOCKER意味着宿主机进程访问本机任意本地地址包括127.0.0.1:8080这种 loopback 目标时包在 OUTPUT 阶段就进入DOCKER链做 DNAT。启用 proxy 时127.0.0.1:8080上的监听者是 proxy 进程本身内核不需要为 loopback 目标做 DNAT因此该跳转被限制禁用 proxy 后没有任何用户态监听者只有靠内核 DNAT 才能让curl 127.0.0.1:8080到达容器所以规则放开到 loopback。差异二容器访问自己发布端口时的 MASQUERADE原文A MASQUERADE rule is added for packets sent from the container to one of its own published ports on the host.对应 POSTROUTING 第 5 条-A POSTROUTING -s 192.0.2.2/32 -d 192.0.2.2/32 -p tcp -m tcp --dport 80 -j MASQUERADE这是典型的 hairpin NAT 需求容器c1192.0.2.2访问宿主机映射的8080端口时包经 DNAT 后源/目的都是192.0.2.2:80若不把源地址伪装掉容器网络栈无法正确完成回环路径。该规则在 port.go 第 109118 行构造且明确以n.ipt.config.Hairpin enable为条件——只有禁用 userland proxy 时才会被写入与原文档结论完全吻合。差异三POSTROUTING 中包含 LOCAL 源地址的 MASQUERADE原文A MASQUERADE rule for packets from a LOCAL source address is included in POSTROUTING.对应 POSTROUTING 第 1 条针对bridge1另有docker0的同类规则第 3 条-A POSTROUTING -o bridge1 -m addrtype --src-type LOCAL -j MASQUERADE它的作用当宿主机自身发出的包源地址属于宿主机 LOCAL 地址被 DNAT 进容器后出网桥时再做一次 MASQUERADE把源地址改成网关192.0.2.1容器回包才能被路由回来。启用 proxy 时宿主机流量由 proxy 进程发起并被单独处理此规则不必要hairpin 模式下则是必需。其源码位置见 network.go 第 306 行MASQ LOCAL HOST标记同样受Hairpin门控。差异四DOCKER 链的 DNAT 规则不再限定目的网桥原文In the DOCKER chains DNAT rule, theres no destination bridge.对比两种场景的最终 DNAT 规则有 proxy-A DOCKER ! -i bridge1 -p tcp --dport 8080 -j DNAT --to-destination 192.0.2.2:80带! -i bridge1排除从bridge1进入的包无 proxy-A DOCKER -p tcp --dport 8080 -j DNAT --to-destination 192.0.2.2:80没有接口条件。源码依据在 port.go 第 98100 行if !n.ipt.config.Hairpin { args append(args, !, -i, n.config.IfName) }即Hairpinfalse有 proxy时才追加! -i 网桥名排除条件。去掉该条件后从bridge1进来的包例如容器访问宿主机映射端口的 hairpin 流量同样会命中 DNAT这正是差异二、三共同支撑的「无代理自访问」路径的入口。小结与验证方式本场景的关键在于理解一个源码级事实在 Moby 的 bridge 驱动内部「禁用 userland proxy」与「开启 Hairpin」是同一个布尔量的两面bridge_linux.go 第 199 行Hairpin: !config.EnableProxyfirewaller.go 第 27 行注释。由此派生出 nat 表的全部差异差异点有 userland proxy无 userland proxy本文场景OUTPUT → DOCKER 跳转不覆盖 loopback覆盖 loopback容器访问自身发布端口无对应规则增加源/目的均为容器 IP 的 MASQUERADEPOSTROUTING LOCAL 源 MASQUERADE无每个网桥一条-m addrtype --src-type LOCALDOCKER 链 DNAT带! -i 网桥条件无接口条件hairpin 流量也命中 DNATfilter 表相同相同验证方式与文档生成机制一致在具备 iptables 权限的 Linux 环境按「场景搭建」一节操作后执行iptables-save -t filter与iptables-save -t nat与 生成产物 中的表格逐条比对仓库内的TestBridgeIptablesDoc测试入口见 iptablesdoc_linux_test.go即以此机制在 CI 中守护规则快照。单元测试层面的规则编程行为含hairpin开关的对照结果可参考 iptabler_test.go 中按hairpin%v生成的 golden 文件。最后重申原文档的两条边界该规则结构是开发参考而非稳定接口跨版本不可依赖ip6tables 规则模式与 iptables 相同本文只展示 IPv4。【免费下载链接】mobyThe Moby Project - a collaborative project for the container ecosystem to assemble container-based systems项目地址: https://gitcode.com/GitHub_Trending/mo/moby创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考