Linux网络
张彦飞 公众号 #开发内功修炼之网络篇 的读书笔记
1、图解Linux网络包接收过程
在TCP/IP网络分层模型里,整个协议栈被分成了物理层、链路层、网络层,传输层和应用层。物理层对应的是网卡和网线,应用层对应的是我们常见的Nginx,FTP等等各种应用。Linux实现的是链路层、网络层和传输层这三层。
在Linux内核实现中,链路层协议靠网卡驱动来实现,内核协议栈来实现网络层和传输层。内核对更上层的应用层提供socket接口来供用户进程访问。
AF_INET协议栈注册
当启用一个网卡时(例如,通过 ifconfig eth0 up),net_device_ops 中的 igb_open方法会被调用。它通常会做以下事情:
首先当数据帧从网线到达网卡上的时候,第一站是网卡的接收队列。网卡在分配给自己的RingBuffer中寻找可用的内存位置,找到后DMA引擎会把数据DMA到网卡之前关联的内存里,这个时候CPU都是无感的。当DMA操作完成以后,网卡会像CPU发起一个硬中断,通知CPU有数据到达。
注意:当RingBuffer满的时候,新来的数据包将给丢弃。ifconfig查看网卡的时候,可以里面有个overruns,表示因为环形队列满被丢弃的包。如果发现有丢包,可能需要通过ethtool命令来加大环形队列的长度。
Linux在硬中断里只完成简单必要的工作,剩下的大部分的处理都是转交给软中断的。
网卡GRO特性,可以简单理解成把相关的小包合并成一个大包就行,目的是减少传送给网络栈的包数,这有助于减少 CPU 的使用量。 GRO说的仅仅只是包的接收阶段的优化方式,对于发送来说是GSO。
NF_HOOK
是一个钩子函数,当执行完注册的钩子后就会执行到最后一个参数指向的函数ip_rcv_finish
。
socket
数据结构中的const struct proto_ops
对应的是协议的方法集合。每个协议都会实现不同的方法集 。
首先在开始收包之前,Linux要做许多的准备工作:
- 创建ksoftirqd线程,为它设置好它自己的线程函数,后面指望着它来处理软中断呢
- 协议栈注册,linux要实现许多协议,比如arp,icmp,ip,udp,tcp,每一个协议都会将自己的处理函数注册一下,方便包来了迅速找到对应的处理函数
- 网卡驱动初始化,每个驱动都有一个初始化函数,内核会让驱动也初始化一下。在这个初始化过程中,把自己的DMA准备好,把NAPI的poll函数地址告诉内核
- 启动网卡,分配RX,TX队列,注册中断对应的处理函数
以上是内核准备收包之前的重要工作,当上面都ready之后,就可以打开硬中断,等待数据包的到来了。
当数据到来了以后,第一个迎接它的是网卡(我去,这不是废话么):
- 网卡将数据帧DMA到内存的RingBuffer中,然后向CPU发起中断通知
- CPU响应中断请求,调用网卡启动时注册的中断处理函数
- 中断处理函数几乎没干啥,就发起了软中断请求
- 内核线程ksoftirqd线程发现有软中断请求到来,先关闭硬中断
- ksoftirqd线程开始调用驱动的poll函数收包
- poll函数将收到的包送到协议栈注册的ip_rcv函数中
- ip_rcv函数再讲包送到udp_rcv函数中(对于tcp包就送到tcp_rcv)
2、Linux网络包接收过程的监控与调优
Linux内核对网络包的接收过程大致可以分为接收到RingBuffer、硬中断处理、ksoftirqd软中断处理几个过程。其中在ksoftirqd软中断处理中,把数据包从RingBuffer中摘下来,送到协议栈的处理,再之后送到用户进程socket的接收队列中。
Linux下监控网卡时可用的工具
ethtool
ethtool
用来查看和设置网卡参数。这个工具其实本身只是提供几个通用接口,真正的实现是都是在网卡驱动中的。正因为该工具是由驱动直接实现的,所以个人觉得它最重要。
伪文件系统/proc
Linux 内核提供了 /proc 伪文件系统,通过/proc可以查看内核内部数据结构、改变内核设置。我们先跑一下题,看一下这个伪文件系统里都有啥:
/proc/sys
目录可以查看或修改内核参数/proc/cpuinfo
可以查看CPU信息/proc/meminfo
可以查看内存信息/proc/interrupts
统计所有的硬中断/proc/softirqs
统计的所有的软中断信息/proc/slabinfo
统计了内核数据结构的slab内存使用情况/proc/net/dev
可以看到一些网卡统计数据
详细聊下伪文件/proc/net/dev
,通过它可以看到内核中对网卡的一些相关统计。包含了以下信息:
- bytes: 发送或接收的数据的总字节数
- packets: 接口发送或接收的数据包总数
- errs: 由设备驱动程序检测到的发送或接收错误的总数
- drop: 设备驱动程序丢弃的数据包总数
- fifo: FIFO缓冲区错误的数量
- frame: The number of packet framing errors.(分组帧错误的数量)
- colls: 接口上检测到的冲突数
所以,伪文件/proc/net/dev
也可以作为我们查看网卡工作统计数据的工具之一。
伪文件系统sysfs
sysfs和/proc类似,也是一个伪文件系统,但是比proc更新,结构更清晰。其中的/sys/class/net/eth0/statistics/
也包含了网卡的统计信息。
当网线中的数据帧到达网卡后,第一站就是RingBuffer(网卡通过DMA机制将数据帧送到RingBuffer中)。
ethtool查看到的是实际是Rx bd的大小。Rx bd位于网卡中,相当于一个指针。RingBuffer在内存中,Rx bd指向RingBuffer。Rx bd和RingBuffer中的元素是一一对应的关系。在网卡启动的时候,内核会为网卡的Rx bd在内存中分配RingBuffer,并设置好对应关系。 在Linux的整个网络栈中,RingBuffer起到一个任务的收发中转站的角色。对于接收过程来讲,网卡负责往RingBuffer中写入收到的数据帧,ksoftirqd内核线程负责从中取走处理。
现在的主流网卡基本上都是支持多队列的, 每一个队列都有一个中断号,可以独立向某个CPU核心发起硬中断请求,让CPU来poll
包。
硬中断发生在哪一个核上,它发出的软中断就由哪个核来处理。
27、天天讲路由,那 Linux 路由到底咋实现的!?
Linux 中最多可以有 255 张路由表,其中默认情况下有 local 和 main 两张。在默认情况下,Linux 只有 local 和 main 两个路由表。如果内核编译时支持策略路由,那么管理员最多可以配置 255 个独立的路由表。 local 表的优先级要高于 main 表,如果 local 表中找到了规则 ,则路由过程就结束了。
如果你的服务器上创建了多个网络命名空间的话,那么就会存在多套路由表。
查看local表的命令
[root@izj6c262htpobd1pfkqlqgz ~]# ip route list table local
broadcast 127.0.0.0 dev lo proto kernel scope link src 127.0.0.1
local 127.0.0.0/8 dev lo proto kernel scope host src 127.0.0.1
local 127.0.0.1 dev lo proto kernel scope host src 127.0.0.1
broadcast 127.255.255.255 dev lo proto kernel scope link src 127.0.0.1
broadcast 172.17.0.0 dev eth0 proto kernel scope link src 172.17.56.20
local 172.17.56.20 dev eth0 proto kernel scope host src 172.17.56.20
broadcast 172.17.63.255 dev eth0 proto kernel scope link src 172.17.56.20
在默认情况下,Linux 上的转发功能是关闭的,这时候 Linux 发现收到的网络包不属于自己就会将其丢弃。 开启转发功能,以下两条命令都可以
sysctl -w net.ipv4.ip_forward=1
sysctl net.ipv4.conf.all.forwarding=1
开启后,Linux 就能像路由器一样对不属于本机(严格地说是本网络命名空间)的 IP 数据包进行路由转发了。
所有的路由表按照从 0 - 255 进行编号,每个编号都有一个别名。编号和别名的对应关系在 /etc/iproute2/rt_tables 这个文件里可以查到。
cat /etc/iproute2/rt_tables
查看某个路由表的配置,通过使用 ip route list table {表名} 来查看。例如,要查看local表可使用如下命令
ip route list table local
如果是查看 main 路由表,也可以直接使用 route 命令
# route -n
Kernel IP routing table
Destination Gateway Genmask Flags Metric Ref Use Iface
10.0.0.0 10.*.*.254 255.0.0.0 UG 0 0 0 eth0
10.*.*.0 0.0.0.0 255.255.248.0 U 0 0 0 eth0
上面字段中的含义如下
Destination:目的地址,可以是一个具体的 IP,也可以是一个网段,和 Genmask 一起表示。 Gateway:网关地址,如果是 0.0.0.0 表示不需要经过网关。 Flags: U 表示有效,G 表示连接路由,H 这条规则是主机路由,而不是网络路由。 Iface:网卡设备,使用哪个网卡将包送过去。 上述结果中输出的第一条路由规则表示这台机器下,确切地说这个网络环境下,所有目标为 10.0.0.0/8(Genmask 255.0.0.0 表示前 8 位为子网掩码) 网段的网络包都要通过 eth0 设备送到 10…254 这个网关,由它再帮助转发。
第二条路由规则表示,如果目的地址是 10…0/21(Genmask 255.255.248.0 表示前 21 位为子网掩码)则直接通过 eth0 发出即可,不需要经过网关就可通信。
默认的 local 路由表是内核根据当前机器的网卡设备配置自动生成的,不需要手工维护。对于main 的路由表配置我们一般只需要使用 route add
命令就可以了,删除使用 route del
。
修改主机路由
# route add -host 192.168.0.100 dev eth0 //直连不用网关
# route add -host 192.168.1.100 dev eth0 gw 192.168.0.254 //下一跳网关
修改网络路由
# route add -net 192.168.1.0/24 dev eth0 //直连不用网关
# route add -net 192.168.1.0/24 dev eth0 gw 10.162.132.110 //下一跳网关
也可以指定一条默认规则,不命中其它规则的时候会执行到这条。
# route add default gw 192.168.0.1 eth0
对于其它编号的路由表想要修改的话,就需要使用 ip route 命令了。用 main 表举一个例子
# ip route add 192.168.5.0/24 via 10.*.*.110 dev eth0 table main
在配置了一系列路由规则后,为了快速校验是否符合预期,可以通过 ip route get 命令来确认。
# ip route get 192.168.2.25
192.168.2.25 via 10.*.*.110 dev eth0 src 10.*.*.161
cache
在 Linux 内核中,对于发送过程和接收过程都会涉及路由选择,其中接收过程的路由选择是为了判断是该本地接收还是将它转发出去
28、绑定特殊 IP 之 0.0.0.0 的内部工作原理
没有需要记录的笔记