从外部访问Docker桥接网络容器路径分析
source link: http://just4coding.com/2023/08/24/docker-bridge/
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
从外部访问Docker桥接网络容器路径分析
2023-08-242023-08-25 Kernel
Docker
默认的网络模式是bridge
模式, 在宿主机上创建一个Linux bridge
:docker0
,并分配一个网段给该网桥使用。该模式下启动的容器,会分配一个该网段的IP
, 并通过veth-pair
接入网桥。为了能够从宿主机外部访问容器,需要在创建容器时指定-p
参数,在宿主机上将某个宿主机的端口映射到容器的端口。
如:
docker run --rm -itd -p 80:80 nginx
本文来简要分析一下从宿主机外访问bridge
网络模式下docker
容器的数据包路径。
整体的网络架构如图所示:
容器网络大量应用了netfilter
和iptables
。netfilter
是内核协议栈的包过滤框架,iptables
是建立在netfilter
框架之上的基于规则的过滤防火墙,它将规则组织成表
和链
结构对数据包进行操作和过滤。之前写过一篇简单的介绍<<IPTABLES机制分析>>。
netfilter
和iptables
整体处理路径如图:
netfilter
有5个挂载点:
NF_INET_PRE_ROUTING
:
在网卡驱动收到数据包之后会调用ip_rcv()
函数进行协议栈处理过程。它会调用NF_INET_PRE_ROUTING
挂载点上注册的函数。NF_INET_LOCAL_IN
:
在处理完上一阶段的函数后,会针对数据包进行路由选择,如果数据包是发送给本机的,则调用ip_local_deliver()
函数处理。它会调用NF_INET_LOCAL_IN
挂载点上注册的函数。NF_INET_FORWARD
:
如果上述的数据包不是发送给本机的网络地址的,则会调用ip_forward()
进行处理,它会调用NF_INET_FORWARD
挂载点上注册的函数。NF_INET_POST_ROUTING
:
对于上述来自转发的数据包处理完挂载点函数后会调用ip_output
函数将数据进行发送。这个函数会调用NF_INET_POST_ROUTING
挂载点上注册的函数。
对于来自主机上应用程序的数据包在经过下边的NF_INET_LOCAL_OUT
挂载点后,也会调用到ip_output
进行数据包发送。NF_INET_LOCAL_OUT
:
当前主机产生的TCP/UDP
数据包经过路由选择后调用ip_local_out
, 它会调用NF_INET_LOCAL_OUT
挂载点上的函数。之后调用到ip_output
函数进而流经上述的NF_INET_POST_ROUTING
阶段。
从这里来看,NF_INET_POST_ROUTING
阶段并不是直接位于路由过程之后,而是路由 -> FORWARD -> POST_ROUTING
和路由 -> LOCAL_OUT -> POST_ROUTING
这样的路径中,名称叫做POST_ROUTING
不是特别准确。
创建完容器后,网络接口如下:
[root@default ~]# ip a
......
3: eth1: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc pfifo_fast state UP group default qlen 1000
link/ether 08:00:27:d0:e3:37 brd ff:ff:ff:ff:ff:ff
inet 192.168.0.11/24 brd 192.168.0.255 scope global noprefixroute eth1
valid_lft forever preferred_lft forever
inet6 fe80::a00:27ff:fed0:e337/64 scope link
valid_lft forever preferred_lft forever
......
5: docker0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP group default
link/ether 02:42:e4:0f:0f:26 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:e4ff:fe0f:f26/64 scope link
valid_lft forever preferred_lft forever
11: vethfd5436d@if10: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue master docker0 state UP group default
link/ether 86:81:2a:a8:5a:c8 brd ff:ff:ff:ff:ff:ff link-netnsid 0
inet6 fe80::8481:2aff:fea8:5ac8/64 scope link
valid_lft forever preferred_lft forever
查看iptables
规则:
[root@default ~]# iptables-save
# Generated by iptables-save v1.4.21 on Thu Aug 24 01:52:06 2023
*filter
:INPUT ACCEPT [169:13126]
:FORWARD DROP [0:0]
:OUTPUT ACCEPT [111:11866]
:DOCKER - [0:0]
:DOCKER-ISOLATION-STAGE-1 - [0:0]
:DOCKER-ISOLATION-STAGE-2 - [0:0]
:DOCKER-USER - [0:0]
-A FORWARD -j DOCKER-USER
-A FORWARD -j DOCKER-ISOLATION-STAGE-1
-A FORWARD -o docker0 -m conntrack --ctstate RELATED,ESTABLISHED -j ACCEPT
-A FORWARD -o docker0 -j DOCKER
-A FORWARD -i docker0 ! -o docker0 -j ACCEPT
-A FORWARD -i docker0 -o docker0 -j ACCEPT
-A DOCKER -d 172.17.0.2/32 ! -i docker0 -o docker0 -p tcp -m tcp --dport 80 -j ACCEPT
-A DOCKER-ISOLATION-STAGE-1 -i docker0 ! -o docker0 -j DOCKER-ISOLATION-STAGE-2
-A DOCKER-ISOLATION-STAGE-1 -j RETURN
-A DOCKER-ISOLATION-STAGE-2 -o docker0 -j DROP
-A DOCKER-ISOLATION-STAGE-2 -j RETURN
-A DOCKER-USER -j RETURN
COMMIT
# Completed on Thu Aug 24 01:52:06 2023
# Generated by iptables-save v1.4.21 on Thu Aug 24 01:52:06 2023
*nat
:PREROUTING ACCEPT [0:0]
:INPUT ACCEPT [0:0]
:OUTPUT ACCEPT [8:544]
:POSTROUTING ACCEPT [8:544]
:DOCKER - [0:0]
-A PREROUTING -m addrtype --dst-type LOCAL -j DOCKER
-A OUTPUT ! -d 127.0.0.0/8 -m addrtype --dst-type LOCAL -j DOCKER
-A POSTROUTING -s 172.17.0.0/16 ! -o docker0 -j MASQUERADE
-A POSTROUTING -s 172.17.0.2/32 -d 172.17.0.2/32 -p tcp -m tcp --dport 80 -j MASQUERADE
-A DOCKER -i docker0 -j RETURN
-A DOCKER ! -i docker0 -p tcp -m tcp --dport 80 -j DNAT --to-destination 172.17.0.2:80
COMMIT
# Completed on Thu Aug 24 01:52:06 2023
当从图中节点node2
上访问192.168.0.11:80
的数据包到达网卡eth1
之后,网卡驱动收包之后进入PRE_ROUTING
挂载点。此时,会以raw
,mangle
,nat
的表顺序进行规则匹配。
由于目的IP: 192.168.0.11
是本机IP
地址,因而会匹配到:
-A PREROUTING -m addrtype --dst-type LOCAL -j DOCKER
而在DOCKER
链中会匹配到:
-A DOCKER ! -i docker0 -p tcp -m tcp --dport 80 -j DNAT --to-destination 172.17.0.2:80
从而进行DNAT
转换,数据包变为:
192.168.0.12:44444 -> 172.17.0.2:80
然后进行路由,根据本地路由表,确实目标设备为docker0
:
172.17.0.0/16 dev docker0 proto kernel scope link src 172.17.0.1
接下来进入FORWARD
挂载点,开始匹配filter
表中的FORWARD
链。FORWARD
链会直接跳转至DOCKER-USER
链,该链中只有一条RETURN
的规则。因而继续匹配DOCKER-ISOLATION-STAGE-1
和DOCKER-ISOLATION-STAGE-2
。这两个链用于保证不同docker
网络之间的隔离。我们这里都匹配不上,最终进入DOCKER
链,匹配到下面规则,进行放行数据包:
-A FORWARD -o docker0 -j DOCKER
-A DOCKER -d 172.17.0.2/32 ! -i docker0 -o docker0 -p tcp -m tcp --dport 80 -j ACCEPT
接下来进入到POST_ROUTING
挂载点,匹配nat
表的POSTROUTING
链。不能匹配任意规则,于是继续放行,从而最终调用到docker0
的网卡驱动将数据包发出。docker0
为Linux bridge
设备,bridge
的实现将数据包送到容器的namespace
中。后续可以写文章分析bridge
发送数据包的具体实现。
以上是外部数据包访问容器的转发路径,整体过程如图:
当容器内的回包通过veth-pair
设备到达docker0
后,与上述的入包一样,首先进入PRE_ROUTING
挂载点。此时不会匹配到任何规则,继续放行进行路由。根据目的IP: 192.168.0.12
查找路由条目, 确定目标设备为eth1
:
192.168.0.0/24 dev eth1 proto kernel scope link src 192.168.0.11 metric 101
接下来进入到FORWARD
挂载点。与入包一样,跳过DOCKER-USER
,DOCKER-ISOLATION-STAGE-1
, DOCKER-ISOLATION-STAGE-2
。DOCKER
链中也匹配不到规则,最终命中:
-A FORWARD -i docker0 ! -o docker0 -j ACCEPT
接着进入POST_ROUTING
挂载点。匹配到规则:
-A POSTROUTING -s 172.17.0.0/16 ! -o docker0 -j MASQUERADE
进行SNAT
操作,将数据包转换为:
192.168.0.11:80 -> 192.168.0.12:44444
之后将数据包由eth1
网卡发到物理网络,完成一次网络数据包交互流程。
回包的整体路径如下图:
本文介绍了从外部访问端口映射到宿主机的容器的数据包路径。而对于相同网桥下的不同容器之间的网络访问路径,和参数net.bridge.bridge-nf-call-iptables
有关,后续可以再写文章分析一下。
Recommend
About Joyk
Aggregate valuable and interesting links.
Joyk means Joy of geeK