static int ip_rcv_finish(struct net *net, struct sock *sk, struct sk_buff *skb)
{
struct net_device *dev = skb->dev;
int ret;
/*
* if ingress device is enslaved to an L3 master device pass the
* skb to its handler for processing
*/
// 未开启CONFIG_NET_L3_MASTER_DEV时
// l3mdev_ip_rcv do nothing
skb = l3mdev_ip_rcv(skb);
if (!skb)
return NET_RX_SUCCESS;
// 查找路由表
ret = ip_rcv_finish_core(net, sk, skb, dev, NULL);
if (ret != NET_RX_DROP)
ret = dst_input(skb);
return ret;
}
static inline int fib_lookup(struct net *net, struct flowi4 *flp,
struct fib_result *res, unsigned int flags)
{
struct fib_table *tb;
int err = -ENETUNREACH;
flags |= FIB_LOOKUP_NOREF;
if (net->ipv4.fib_has_custom_rules)
// 如果有策略路由(Policy-based routing (PBR))
// 进行PBR匹配
// 下面的fib_table_lookup查找的是 ip route show 展示的路由
// 也就是PBR的main routing table
// 所以不需要再调用fib_table_lookup
// ip rule list
// Priority: 32766, Selector: match anything, Action: lookup
// routing table main (ID 254). The main table is the normal
// routing table containing all non-policy routes
return __fib_lookup(net, flp, res, flags);
rcu_read_lock();
res->tclassid = 0;
// 默认三张表
// local main default
// 这里只查了 main 和 default表,没有 local
// 如果开启策略路由,在上面调用 __fib_lookup 并返回
// 否则 local 合并到 main table 查询
// 好处:代码尽量复用,如果用户不开启自定义rule,速度会有提升
// 开启 RPDB ,kernel调用 fib_trie_unmerge(),将 local 从 main 剥离
// https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/commit/?id=0ddcf43d5d4a03ded1ee3f6b3b72a0cbed4e90b1
tb = rcu_dereference_rtnl(net->ipv4.fib_main);
if (tb)
err = fib_table_lookup(tb, flp, res, flags);
if (!err)
goto out;
// 查default表
tb = rcu_dereference_rtnl(net->ipv4.fib_default);
if (tb)
err = fib_table_lookup(tb, flp, res, flags);
out:
if (err == -EAGAIN)
err = -ENETUNREACH;
rcu_read_unlock();
return err;
}
使用 ip rule 列出/添加/删除 PBR
```txt
$ ip rule list
# PRIORITY SELECTOR ACTION
0: from 127.0.0.1 iif lo ipproto tcp lookup 127
0: from 127.0.0.1 iif lo ipproto udp lookup 127
0: from all iif lo ipproto tcp lookup 128
0: from all iif lo ipproto udp lookup 128
0: from all lookup local
32766: from all lookup main
32767: from all lookup default
// 路由查找算法
// default via 10.0.2.2 dev enp0s3
// 10.0.2.0/24 dev enp0s3 proto kernel scope link src 10.0.2.15
// 最长前缀匹配
int fib_table_lookup(struct fib_table *tb, const struct flowi4 *flp,
struct fib_result *res, int fib_flags)
{
/* Step 1: Travel to the longest prefix match in the trie */
for (;;) {
}
/* Step 2: Sort out leaves and begin backtracing for longest prefix */
for (;;) {
}
found:
/* Step 3: Process the leaf, if that fails fall back to backtracing */
// ...
}
驱动层
驱动向内核注册 softirq,里面包含回调函数。驱动收到数据触发中断,kernel读取
NAPI
基于已有thread_struct封装的新的任务调度库
kernel thread通过thread_struct调度,napi本身封装了thread_struct,内部有kernel thread
驱动初始化代码里创建napi结构,kernel会创建对应的调度上下文,napi 被 kernel调度执行,最后回调驱动代码
为什么NAPI收到第一个包需要关闭中断?
内核网络层
receive_skb*
有list和非list版本。list可以做GRO合并,根据条件合并多个skb,减少重复处理次数。以list版本继续分析调用次序
netif_receive_skb_list
__netif_receive_skb_list_core
__netif_receive_skb_core
deliver_skb
回调 packet_type 指向的handlerIP 层
ipv4 的handler在init阶段注册inet_init
deliver_skb -> ip_rcv,继续ipv4 流程处理
ip_rcv 逻辑很少,在计数,包检查后交给netfilter hook处理,处理完调用
ip_rcv_finish
skb刚进入IP层第一时间调用
PRE_ROUTING
hookPRE_ROUTING
hooknetfilter hook 执行过程后面会详细讲解
ip_rcv_finish
ip_rcv_finish_core
: 查路由表,详细路由查询在 路由选择ip_route_input_slow
:路由查询入口函数单就路由而言,路由是根据目的地IP进行选择的过程。而 PBR(policy-based routing)的引入使得路由结果还受 source ip address, skb->mark, tcp/udp source/destination port 等变量影响。
struct flowi4
包含了上述可以被参考的字段值。struct flowi4
fib_lookup
/fib_table_lookup
需struct flowi4
和struct fib_result
参数。 给接下来的路由选择准备所需参数。struct flowi4
:给PBR提供除 destination ip 之外的参考信息struct fib_result
:fib_lookup 的 in_out 返回结果。fib_lookup
返回的struct fib_result
构造route cachestruct dst_entry
字段调用
fib_lookup
开始路由选择路由选择
路由选择总览
fib(forwarding information base) kernel kbuild有IP_MULTIPLE_TABLES选项。关闭则只有一张main表
使用如下 fib_lookup 函数
开启则引入多路由表,代码如下
fib_lookup
out: if (err == -EAGAIN) err = -ENETUNREACH;
}
每个规则包含三部分,优先级,选择器,执行结果。优先级从小到大是规则被匹配的顺序。选择器匹配包信息,ACTION 执行具体的操作。
from all
:任意source ip数据包都命中,执行actionlookup local
: 到local表继续查找lookup main
: 到main表继续查找lookup default
: 到default表继续查找 local, main, default 表的查找顺序也对应了fib_lookup
的表查找顺序如果 ip rule 过程有一条规则命中,查找马上停止,并使用此规则作为最后的路由规则。
fib_lookup =>
__fib_lookup
:策略路由查找 => fib_table_lookup: 路由查找算法实现struct rtable
代表一条路由规则,路由结束绑定到
skb#_skb_refdst
struct dst_entry dst
下面详细介绍_u16 rt_type
本路由类型
RTN_UNICAST
unicast
skb#_skb_refdst
是此值,数据需要被转发RTN_LOCAL
local
RNT_BROADCAST
RTN_MULTICAST
RNT_UNREACHABLE
unreachable
__u8 rt_uses_gateway
via 10.0.0.1
的格式),那么rt_gw4
包含网关IP地址。u8 rt_gw_family
rt_uses_gateway
是0,那rt_gw_family
是0。如果网关地址是IPV4,=AF_INET。IPV6, =AF_INET6union {__be32 rt_gw4; struct in6_addr rt_gw6;}
rt_gw4
或rt_gw6
字段struct dst_entry
struct net_device *dev
struct xfrm_state *xfrm
int (_input)(struct sk_buff_skb)
unreachable
,数据包丢弃,返回ICMP host unreachableint (_output)(struct net_net, struct sock _sk, struct sk_buff_skb);
尝试重组skb。如果此skb分片包的最后一个那么
ip_defrag
返回0,获得完成的ip数据包。ip_local_deliver call stack
dst_input
是很薄的中间函数展开
INDIRECT_CALL_INET
如果 input 不等于
ip_local_deliver
,调用skb_dst(skb)->input(skb);
,否则调用ip_local_deliver
这样就包含多条路径,这些路径的 input 都在路由选择阶段赋值。 如果 input ==
ip_forward
说明数据包需要转发到本机外ip_forward
ip forward 调用栈
ip_output
ip_output
: 利用IP协议发送xfrm4_output
: IPsec送达ip_mc_output
: multicast packets执行 Netfilter POSTROUTING HOOK
ip_finish_output
__ip_finish_output
ip_finish_output2
进入
netghbouring subsystem
,查找ARP cache,找到链路层 MACip_local_deliver
ip_local_deliver 在创建
dst_entry
时初始化。如果ip packet目的地为本机,继续传递到上层协议栈假设packet送往本机,分析 ip_local_deliver
继续调用
ip_local_deliver_finish
ip_protocol_deliver_rcu
resubmit: raw = raw_local_deliver(skb, protocol); // CC-NET 根据ip协议字段找到handler处理 ipprot = rcu_dereference(inet_protos[protocol]); if (ipprot) { if (!ipprot->no_policy) { if (!xfrm4_policy_check(NULL, XFRM_POLICY_IN, skb)) { kfree_skb_reason(skb, SKB_DROP_REASON_XFRM_POLICY); return; } nf_reset_ct(skb); } // call 入传输层 ret = INDIRECT_CALL_2(ipprot->handler, tcp_v4_rcv, udp_rcv, skb); if (ret < 0) { protocol = -ret; goto resubmit; } IP_INC_STATS(net, IPSTATS_MIB_INDELIVERS); } else { if (!raw) { if (xfrm4_policy_check(NULL, XFRM_POLICY_IN, skb)) { IP_INC_STATS(net, IPSTATS_MIB_INUNKNOWNPROTOS); icmp_send(skb, ICMP_DEST_UNREACH, ICMP_PROT_UNREACH, 0); } kfree_skb_reason(skb, SKB_DROP_REASON_IP_NOPROTO); } else { __IP_INC_STATS(net, IPSTATS_MIB_INDELIVERS); consume_skb(skb); } } }
注册 tcp 回调,IP层根据protocol type回调传输层handler
udp send
udp receive
UDP sock receive
tcp send
tcp connect
tcp sendmsg
tcp receive
tcp rev + tcp syn send
netfilter hook
入口点
数据包流
通常在协议栈有很多的决策点,根据不同的条件调用C的各种函数回调,将包送到不同的路径。
绿色部分是和路由相关的两个分流点。
skb_dst(skb)->output
不同值,决定如何调用三层协议发送下图还针对收包展示了两个分流点。
deliver_skb
: 根据链路层Type字段区分三层协议,IPv4, IPv6, ARP等等。iphdr->protocol
字段区分传输层协议TCP,UDP 回调handler从图的左下角看起
数据包可以一个个处理
也可以作为lists一同处理
两种处理方式最后都回调上层 non-list 或者 list 的 handler
ipv4 的 handler 在init阶段注册inet_init
协议分发利用EtherType - Wikipedia字段,通过hashtable查找handle,ipv4的两个回调函数分别为
ip_rcv
和ip_list_rcv
。一到达网络层,首先进行 Netfilter Prerouting,如果没有被丢弃,继续调用
ip_rcv_finish/ip_list_rcv_finish
ip_rcv_finish_core
没有list版本,每个skb单独调用。开始routing参考