内核 FIB Trie 路由效果演示
通过动画展示下内核路由树(FIB trie) 插入路由时,对应节点的变化。
Q1:ping 本机eth1口的ip地址时,对应的ICMP报文会被发送到eth1口上吗?

通过tcpdump抓包命令,我们发现报文被发送到lo上,而不是对应的物理口 eth1。
虽然ip 地址被配在本机的物理口上,但是报文并没有被发送到物理口。为了理解这个问题,我们需要先看下下面这个问题。
Q2: 当一个IP地址被添加到一个网口后,会引起哪些路由变化呢?
为一个网口增加一个 IP 地址是个很常见的网络操作。
具体添加方式有多种,包括命令行手动添加、通过配置文件在系统启动时候添加,还是通过dhcp自动获取并添加等。
无论哪种场景,添加完IP地址后,系统路由有什么变化。
场景:在本机物理口 eth1上配置192.168.8.8/24,网口状态正常(UP).
fib_unmerge 是内核路由子系统中一个精巧的按需优化设计:
fib_lookup 只需查一次表,性能最优fib_unmerge 将 local 路由从 main 表中切割出来,放入独立的 local 表,确保策略路由能正确匹配fib_trie_unmerge 检测到已分离,直接返回本文将从调用点入手,逐步分析 fib_unmerge 的实现细节。
接上篇文章 ping本机网口的IP地址,tcpdump在lo口才能抓到对应报文,我们知道在网口eth1上增加一个IP地址192.168.8.8/24后,内核会生成3条路由:
RTN_LOCAL)RTN_UNICAST)其中主机路由类型是RTN_LOCAL,网段路由类型是RTN_UNICAST。
那如果把IP地址配到lo口上,会有什么不同呢?
tcpdump/iproute2 是很多网络问题下的调试工具,大家也都很熟他的一些特性。比如,
-p参数, 网口会进入PROMISC状态。PROMISC标志。ip link结果,对应网口的状态标志位没有PROMISC标志位,也没有其他变化。
ip link set dev eth0 promisc on命令,设置网口混杂模式。这时,网口的PROMISC标志位能正常显示出来。
PROMISC标志位只受 ip link命令控制,与在网口上是否运行tcpdump无关。看到这里,不免就有疑问了
PROMISC混杂模式呢?PROMISC状态了。 因为对应时间段里,dmesg内核日志有明确的记录。1 | [5828194.373058] virtio_net virtio0 eth0: entered promiscuous mode |
注意,ip link 命令设置 on 的时候,网口也会进入PROMISC混杂模式。
promisc off, 网卡会退出混杂模式,会不会对tcdpump抓包造成影响?promisc off 命令只会让 ip link 显示的网口的状态标志位里的PROMISC被清除掉。PROMISC。内核有引用计数机制,保证最后一个 tcpdump 退出时候,网口也跟着关闭混杂模式。1 | 91 enum tcp_tw_status |
1 | 1142 |
1 | 1160 static inline bool tcp_paws_reject(const struct tcp_options_received *rx_opt, |
网上大部分文档这部分内容讲的比较多,简单来说,可以把半链接队列的逻辑概括如下:
在v6.14内核里,三次握手的处理流程如下:
socket指针指向child。 
在处理TCP-SYN首包时候, tcp_conn_request函数里, 会有三个不同条件的长度检查。
inet_csk_reqsk_queue_is_full 半链接的个数超过sk_max_ack_backlog, 则丢包。sk_acceptq_is_full: accept 队列长度超过sk_max_ack_backlog,则丢包。sysctl_tcp_syncookies禁用(值为0)时, sysctl_max_syn_backlog 与inet_csk_reqsk_queue_len: 队列长度如果超过sysctl_max_syn_backlog的3/4则丢包其中,
sysctl_max_syn_backlog: 初始化时,最小 128。如果ehash_entries/128比128大,取最大值。ehash_entries是 tcp 的 hash 桶个数。sysctl_tcp_syncookies: 初始值为 1虽然不再维护半链接队列了, 但是每次创建req socket后,这个统计值都是在增加的。
因此如果半链接个数超过了最大值sk_max_ack_backlog,则启用cookie(sysctl_tcp_syncookies为1或2),如果不支持cookie,则丢弃。
1 | 278 static inline int inet_csk_reqsk_queue_len(const struct sock *sk) |
当前内核默认启用syncookie机制(sysctl_tcp_syncookies为1),队列溢出会触发synccookie 机制。
只有关闭了tcpcookie(0)后,才会在队列溢出时候丢弃syn报文。
2006年内核引入operstate特性,在当时协议栈的维护者中也是颇有争议的!!!
引入operstat特性的patch

IFF_UPIFF_UPip link set eth0 up:
eth口在内核里有对应的struct net_device。netdev设备里有一个上的flags用来存放标志位ip link set eth0 up 设置 eth0口对应的IFF_UP标志。ip link set eth0 down清除对应的IFF_UP标记。ip link set eth0 up命令实现:
IFF_UP标志位设置到 flags 上,SIOCSIFFLAGS下发会内核。具体实现在函数do_set和do_chflags中。
do_set: 解析命令,转换为需要设置的标志位。1 | 1370 static int do_set(int argc, char **argv) |
do_chflags: 借助ioctl函数接口,与内核交互。netdev->flags,1 | static int do_chflags(const char *dev, __u32 flags, __u32 mask) |
*** 注意: *** ioctol命令参数里,获取和设置的命令名字, 只有一个字母G和S的差别。
ioctl在内核的对应实现比较复杂, 避免歪楼,单拉一篇去介绍实现吧。
内核如何维护网卡设备的RUNNING 状态
主要几个部分:
netif_carrier_on和netif_carrier_off。这个函数会netdev->state上的__LINK_STATE_NOCARRIER标志位。ndo_tx_timeout, 做一些应急补救,比如对网卡队列复位等操作。这里的看门狗跟网卡驱动里的看门狗还不是同一个看门狗。具体差别待研究。linkwatch_do_dev(struct net_device *dev)函数进行处理。 该函数更新netdev->operate标志位。同时调用通用的dev_activate或者dev_deactivate对网卡做网卡队列进行处理。 我们这里重点关注跟网卡状态位有管的部分,忽略跟网卡队列的处理。rfc2863_policy和default_operstate 后面我们重点介绍。netif_carrier_on和netif_carrier_off: 内核里的两个通用的处理函数,功能基本对称
总结:
dev->state下的__LINK_STATE_NOCARRIER是 carrier是否OK 的唯一判断标准。
我们在平常定位问题,经常会通过ip link或ifconfig查看网络环境和配置。其中网口的标志位是其中很重要的一个检查环节。在输出结果里,会有两组标志位信息
<BROADCAST,MULTICAST,UP,LOWER_UP\>,有很多标志位的组合。state UP。还有其他状态如DOWN, LOWERLAYERDOWN等。
NO-CARRIER
NO-CARRIER标志,但是状态里一个UP,一个DOWN,why? :(