Linux内核网络模块概览
Linux内核网络模块涉及OSI 7层模型中的3个层: 数据链路层、网络层、传输层。
在Linux内核实现中,链路层协议靠网卡驱动来实现;内核协议栈来实现网络层和传输层,所以很多资料中将网络层和传输层统称为Linux网络协议栈。
网卡驱动可以分为物理网卡和虚拟网卡。物理网卡是指真正能把报文发出本机的网卡,包括真实物理机的网卡以及VM虚拟机的网卡,而像 tun/tap,vxlan、veth pair 这样的则属于虚拟网卡的范畴。
接收和发送数据包过程
Linux内核网络模块的主要任务:
- 就是将接收到的数据包从数据链路层传递给网络层。接下来,如果数据包目的地为当前设备,就将其传递给传输层;如果数据包需要转发,就将其交还给数据链路层进行传输。
- 将当前主机生成的数据包传递给网络层,再传递给数据链路层发送出去。
总结来说可以分为三种情况: 本机接收数据包,转发数据包,本机发送数据包。
对于每个数据包,无论是接收到的还是发送出去的。都需要在路由子系统中执行一次查找操作。根据查找结果,决定是否对数据包进行转发以及该从哪个网卡接口发送出去。
在数据包的传输过程中有5个位置,Netfilter子系统在其中注册了回调函数,这些回调函数通常称为Netfilter Hook。数据包被这样的回调函数处理后,其在网络栈中的后续旅程取决于回调函数的结果(这种结果被称为verdict)。比如如果verdict为NF_DROP, 则数据包将被丢弃; 如果verdict为NF_ACCEPT,则数据包将继续传输。
注: Netfilter子系统就是我们平时所说的防火墙,通常我们使用管理工具iptables来配置Netfilter Hook。
本机接收数据包,转发数据包,本机发送数据包的过程如下图所示。
蓝色方框为Netfilter Hook。
本机接收数据包和转发数据包
- 数据包从外面的网络进入物理网卡。如果目的地址(mac地址)不是该网卡,且该网卡没有开启混杂模式,该包会被网卡丢弃。
- 当网卡上收到一个数据包,最先经过的是RREROUTING,通常目的地址修改(DNAT)发生在这里。
- 然后是进行路由查找,不管在PREROUTING有没有做过DNAT,内核都会通过查本地路由表决定这个数据包是发送给本地进程还是发送给其他机器,如果是转发给其他机器还会决定从哪个网卡接口发送出去。
- 经过上面的路由决策,如果目的地址是本机地址,内核决定把包发给本地进程,就会经过INPUT,然后交给传输层,最后到达应用程序处理。
- 如果是发送给其他机器(或其他network namespace),就相当于把本机当作路由器,就会经过FORWARD,用户可以在此处设置数据包过滤函数。
- 所有马上要发到协议栈外的包都会经过POSTROUTING,用户可以在这里设置源地址转换(SNAT)或源地址伪装(Masquerade,简称Masq)的函数。
- 最终从网卡驱动发送出去。
本机发送数据包
- 来自用户程序的数据包从传输层传递到网络层
- 数据包先经过OUTPUT
- 然后经过一次路由决策(例如,决定从机器的哪种网卡出去,下一跳地址是(mac地址)多少)
- 最后出协议栈的网络包同样会经过POSTROUTING
参考:
- 《精通Linux内核网络》4.3 小节 接收IPv4数据包
- 《精通Linux内核网络》1.2.2 小节 数据包的收发
- 《Kubernetes网络权威指南:基础、原理与实践》1.5 小节 iptables
将Linux主机看成是路由器
参考:
- 《深入理解Linux网络内幕》16.1 小节 网桥设备抽象
上图中,我们有两个局域网LAN1和LAN2,它们分别是不同的网段,通过路由器,在LAN1和LAN2中的主机可以互相访问。
注: 这里的局域网可以看出是最简单的通过集线器连接的总线型局域网。
我们也可以用有两张物理网卡Linux主机来代替路由器达到相同的功能,一个网卡驱动相当于路由器的一个端口(虚拟网卡也是一样的),示意图如下:
家用路由器
这里有点容易产生混淆,因为我们平时接触的家用路由器,家用路由器通常来说只有一个WAN接口和一些LAN接口,跟上面例子中用的路由器完全不一样,如下图所示。
一般来说,家用路由器的ip为192.168.0.1,WAN口连接的是另外一个网络。
实际上,家用路由器可以看成一个路由器和一个交换机的组合,如下图所示。
注: 路由器的每个端口都需要ip。
veth pair简介
veth是虚拟以太网卡(Virtual Ethernet)的缩写。veth设备总是成对的,因此我们称之为veth pair。
根据这一特性,veth pair常被用于跨network namespace之间的通信,根据这一特性,veth pair常被用于跨network namespace之间的通信,即分别将veth pair的两端放在不同的namespace里,如下图所示。
下面,我们将演示veth pair的创建和使用:
# 创建veth pair,名字分别是veth0和veth1
ip link add veth0 type veth peer name veth1
创建的veth pair在主机上表现为两块网卡,veth pair相当于两张网卡并且使用网线连接起来,我们可以通过ip link命令查看:
ip link list
8: veth1: <BROADCAST,MULTICAST,M-DOWN> mtu 1500 qdisc noop state DOWN mode DEFAULT group default qlen 1000
link/ether fa:6d:ab:a3:42:d9 brd ff:ff:ff:ff:ff:ff
9: veth0: <BROADCAST,MULTICAST,M-DOWN> mtu 1500 qdisc noop state DOWN mode DEFAULT group default qlen 1000
link/ether 32:5f:5d:c9:af:89 brd ff:ff:ff:ff:ff:ff
如上所示,新创建的veth pair设备的默认mtu是1500,设备初始状态是DOWN。我们同样可以使用ip link命令将这两块网卡的状态设置为UP。
ip link set dev veth0 up
ip link set dev veth1 up
为veth pair设备设置IP:
ip addr add 1.2.3.1/24 dev veth0
ip addr add 1.2.3.2/24 dev veth1
可以将veth pair设备放到namespace中。
# 创建network namespace,名字为netns1
ip netns add netns1
# 将veth1放到netns1中
ip link set veth1 netns netns1
需要注意的是,当将veth1设备放到新的network namespace后,它的状态会重置,所以我们需要重新为veth1设置状态为UP,并且设置IP:
ip netns exec netns1 ip link set dev veth1 up
ip netns exec netns1 ip addr add 1.2.3.2/24 dev veth1
经过上面的命令,得到如下结构:
我们直接从veth0 ping veth1,应该是可以ping通的。
ping -c 1 1.2.3.2
PING 1.2.3.2 (1.2.3.2) 56(84) bytes of data.
64 bytes from 1.2.3.2: icmp_seq=1 ttl=64 time=0.098 ms
--- 1.2.3.2 ping statistics ---
1 packets transmitted, 1 received, 0% packet loss, time 0ms
rtt min/avg/max/mdev = 0.098/0.098/0.098/0.000 ms
这里需要说明的的是,当一个network namespace下新加一张网后并设置了ip,那么在该network namespace的路由表中会自动新增规则,如下所示:
route
Kernel IP routing table
Destination Gateway Genmask Flags Metric Ref Use Iface
1.2.3.0 0.0.0.0 255.255.255.0 U 0 0 0 veth0
所以,ping 1.2.3.2
会匹配到这个条目,数据包将从veth0发出。
下面我们进行另一个实验,验证在netns1中能够访问外网吗?
ip netns exec netns1 ping -c 1 39.156.66.14
connect: Network is unreachable
目前并不能够ping通,这是什么原因呢?
我们先来看一下netns1中的路由表:
ip netns exec netns1 route
Kernel IP routing table
Destination Gateway Genmask Flags Metric Ref Use Iface
1.2.3.0 0.0.0.0 255.255.255.0 U 0 0 0 veth1
只有一条记录,只有目的地址属于1.2.3.0/18网段的能够匹配,所以39.156.66.14不能匹配到路由表中条目,系统不知道将数据包发往何处,抛出网络不可达错误。
在netns1中添加默认网关:
ip netns exec netns1 ip route add default via 1.2.3.1
再试一次:
ip netns exec netns1 ping -c 1 39.156.66.14
PING 39.156.66.14 (39.156.66.14) 56(84) bytes of data.
--- 39.156.66.14 ping statistics ---
1 packets transmitted, 0 received, 100% packet loss, time 0ms
还是ping不同,我们还分析一下过程,数据包经veth1发送到veth0,veth0收到到,交给主机的协议栈处理(也就是网络层),目的地址为39.156.66.14,所以需要进行转发,从eth0发出去。在eth0上抓包看看结果。
tcpdump -i ens33 -n host 39.156.66.14
10:51:19.665749 IP 1.2.3.2 > 39.156.66.14: ICMP echo request, id 3616, seq 1, length 64
发现只有ICMP请求数据包,没有响应包,并且数据包的原地址为1.2.3.2;所以数据包成功被发出,只是响应包没有返回。
我们可以将主机看成路由器,那么结构如下所示。
39.156.66.14接收到ICMP请求数据包,发现它的原地址为1.2.3.2,将构造目的地址为1.2.3.2的ICMP响应数据包,这个包肯定是传不回来的,如下图所示。
我们需要在ICMP请求数据包从eth0出去的时候做原地址转换SDAT,这样回包的目的地址才能是正确的,执行如下命令:
iptables -t nat -A POSTROUTING -s 1.2.3.0/24 -o ens33 -j MASQUERADE
协议任意, 目标地址任意, 源地址满足1.2.3.0/24, 这条规则匹配成功, 它执行MASQUERADE。 MASQUERADE的作用是包从哪个网卡发出去, 就把源地址转换成那个网卡的ip.
MASQUERADE操作属于特殊的SDAT。
ICMP请求数据包的原地址会被替换为192.168.1.111,那么回包的目的地址就变成了192.168.1.111,如下图所示。
还有一点需要说明,就是主机收到回包,但是它的目的地址为192.168.1.111,主机怎么知道需要将这个数据包转发给1.2.3.2呢?
在源地址转换的时候,会在名为connection tracking
的表中记录这个信息,那么收到数据包时,根据源地址目的地址进行匹配,如果匹配,则根据信息进行一次目的地址转化。这个过程可以认为发生在PREROUTING期间(具体是不是,我没有验证)。
可以运行如下命令查看connection tracking表, 可能需要安装yum install conntrack:
回包时,匹配到src=39.156.66.14 dst=192.168.1.111
,知道应该修改目的地址为1.2.3.2,最后会转发到1.2.3.2设备。如下图所示。
Linux bridge简介
bridge我们通常称为网桥,网桥的功能和交换机的功能是一样的,它们的区别在于一个是软件,一个时硬件。
Linux bridge其实是一张网卡驱动加一个网桥,这点通常容易让人感到困惑。Linux bridge的抽象如下图所示。
我们先用iproute2软件包里的ip命令创建一个名为br0的Linux bridge:
ip link add name br0 type bridge
如上图所示,br0其实是网卡驱动的名字,可以设置ip,它连接着一个传统意义上的网桥。 这其实跟家用路由器很像,家用路由器可以看出是一个路由器和一个交换机;主机可以看成是一个路由器,Linux bridge就像是路由器上的一个端口连着一个网桥。(其实Linux网络中,一个网卡驱动可以看成路由器上的一个端口)
物理网卡和虚拟网卡都可以连接到网桥上。连接到网桥上的网卡不需要设置,可以这么理解,它们从路由器上的端口变成了网桥上的端口。