ip_queue_xmit函数
对于tcp协议往ip层发送报文时用的接口函数为ip_queue_xmit->__ip_queue_xmit
首先获取struct sock结构体缓存的目的地址路由表sk->sk_dst_cache,如果存在则直接使用该路由表的dst目的地址结构体设置到skb,如果不存在则通过ip_route_output_ports函数查找路由表,找到的路由表会通过sk_setup_caps函数设置到sk->sk_dst_cache缓存。套接字 sk 与路由项 dst 的对应关系并不是固定的,而是动态维护的。当套接字需要发送数据包时,它会根据目标 IP 地址和端口查找相应的路由项。找到合适的路由项后,将其缓存在 sk_dst_cache 中,以便后续的数据包发送可以直接使用缓存的路由项,避免重复的查找操作。
dst_entry路由项状态
DST_OBSOLETE_NONE:表示目的地址dst_entry没有过时状态。
DST_OBSOLETE_DEAD:表示目的地址dst_entry已经过期,不再使用。
DST_OBSOLETE_FORCE_CHK:强制检查目的地址dst_entry是否过期,即使状态不是OBSOLETE_DEAD也需要检查。
DST_OBSOLETE_KILL:直接释放目的地址dst_entry,不再保留。
路由表项初始化时obsolete的状态为DST_OBSOLETE_FORCE_CHK。后续会根据网卡收到的报文改变对应路由项的obsolete的值。
其检测原理即为检查net_device网卡对应net空间的rt_genid是否与初始化时的值不一致。通过rt_cache_flush函数改变net空间的rt_genid值。
路由(route)和路由规则(rule) 的添加删除查找
linux通过fib维护内核路由表其上分别对应添加inet_rtm_newroute,删除inet_rtm_delroute,获取inet_dump_fib的回调函数,应用层通过netlink与内核交互。需要指出的是路由表项是不可变的数据结构,不能直接进行"修改"操作,如果需要修改也是通过"删除-添加"的方式来完成。
fib_inetaddr_notifier对应IPv4地址事件通知回调函数。
fib_netdev_notifier对应网络设备事件通知回调函数。
其中网卡的up和down都会调用rt_cache_flush该net网络空间的rt_genid值。
上述函数则是路由规则的添加删除和获取。在新增rule或删除rule的时候都会调用flush_route_cache函数间接调用rt_cache_flush函数。
sk->sk_dst_cache的有效性判断
在上述判断中dst为NULL表示该路由项被释放,dst->obsolete为真则表示需要进一步判断。dst->ops->check(dst, cookie) == NULL为真则表示网络命名空间发生了改变,如添加/删除路由或up/down网卡。
由于网络环境的变化和路由表的更新,缓存的路由项可能会变得过时或无效(obsolete)。为了确保缓存的路由项仍然有效,内核会定期检查路由项的状态。如果路由项已经过时且不再有效,就会触发路由项的检查(dst->ops->check(dst, cookie))。如果检查结果为 NULL,说明路由项已过时且无效,此时会清除套接字的发送队列,并清除关联的缓存路由项,释放相关资源。
ip_local_out函数
ip_local_out函数中会首先调用__ip_local_out->nf_hook(NFPROTO_IPV4, NF_INET_LOCAL_OUT,net, sk, skb, NULL, skb_dst(skb)->dev,dst_output);该函数即是netfilter框架的入口函数实现iptables中对NF_INET_LOCAL_OUT链的的报文过滤。
nf_hook的具体实现为先获取net->nf.hooks_ipv4[hook]对应的钩子函数头结点然后再调用nf_hook_slow函数对注册到NF_INET_LOCAL_OUT类型的所有钩子函数依次调用并判断其返回值NF_ACCEPT则继续调用下一个钩子函数NF_DROP则表示该报文被钩子过滤不能发送。
例如xt_table 的mangle表即会注册NF_INET_LOCAL_OUT钩子函数。
ip_output函数
最后在ip_output->ip_finish_output函数中进行ip层的报文发送进而发送给数据链路层。需要注意的是在ip_finish_output函数中会检测报文的长度如果大于mtu的长度则会调用ip_fragment进行ip报文的分片。
总结
1.__ip_queue_xmit函数将报文从ip层发送给首先判断sk->sk_dst_cache缓存的路由项是否可用。不可用的情况一般有路由项被释放,网络命名空间发生改变如添加/删除路由或up/down网卡。如果sk->sk_dst_cache缓存的路由项不可用则根据目的地址查找路由项并设置到sk->sk_dst_cache。
2.应用层通过netlink通信进行路由表的添加/删除操作,且路由表的"修改"是通过先删除再添加的方式完成的。同时内核本身也会根据网络设备和IP地址变化等原因,动态更新和重新查找路由表项。
3.然后通过nf_hook调用netfilter框架中的钩子函数进行报文的过滤。
4.在最后报文从ip层发送给数据链路层时会检测报文的长度如果大于mtu的长度则会调用ip_fragment进行ip报文的分片。