下载地址《http://download.youkuaiyun.com/detail/shichaog/8620701》
4.1 主机到网络层的过渡
从netif_receive_skb(struct sk_buff *skb)函数开始,网卡收到数据包后产生中断通知CPU有数据到达,在中断服务函数中触发接收软中断,等待内核在适当的时间调度NAPI方式的接收函数完成数据的接收,并非所有网卡或者MAC控制器都是支持NAPI方法(需要硬件能支持)的,NAPI服务函数最重要的工作就是调用netif_receive_skb将数据从主机到网络层送到网络层,其函数参数在第一章叙述过,收到的数据最终能不能送到应用层,是和拥塞控制、路由、协议层如何处理该数据包相关的,由于该函数是在软中断中调用的,所以该函数执行时硬件中断是开启的,这就意味着可能前一次MAC接收到的数据还没有传递到网络层时,MAC又接收到数据又产生新中断,而新的数据需要存放在一个通常被称为DMA缓冲区的地方,这也意味着要支持NAPI方式就需要多个缓冲区,这也是硬件为支持NAPI方式必须支持的一个特性。
int netif_receive_skb(struct sk_buff *skb)
{
//接收到的数据包的时间戳,netdev_tstamp_prequeue在dev.c文件里被初始化成了1,作为在SKB加入队列前是否打上时间戳的标志
net_timestamp_check(netdev_tstamp_prequeue, skb);
if (skb_defer_rx_timestamp(skb))
return NET_RX_SUCCESS;
//RPS是Receive Packet Steering的缩写,也许你还看到过GSO,TSO,RFS等等,这些特性后面专门有个讲,这里就略过了。
#ifdef CONFIG_RPS
if (static_key_false(&rps_needed)) {
struct rps_dev_flow voidflow, *rflow = &voidflow;
int cpu, ret;
rcu_read_lock();
cpu = get_rps_cpu(skb->dev, skb, &rflow); //RPS见十五章
if (cpu >= 0) {
ret = enqueue_to_backlog(skb, cpu, &rflow->last_qtail);
rcu_read_unlock();
return ret;
}
rcu_read_unlock();
}
#endif
return __netif_receive_skb(skb);
}
经过几层调用后__netif_receive_skb会调用 __netif_receive_skb_core函数。
3419 static int __netif_receive_skb_core(struct sk_buff *skb, bool pfmemalloc)
3420 {
3421 struct packet_type *ptype, *pt_prev;// packet_type在第一章就见过了
3422 rx_handler_func_t *rx_handler;
3423 struct net_device *orig_dev;
3424 struct net_device *null_or_dev;
3425 bool deliver_exact = false;
3426 int ret = NET_RX_DROP;
3448 another_round:
3449 skb->skb_iif = skb->dev->ifindex; //这是在sk_buff中提到的接口索引
3450
3451 __this_cpu_inc(softnet_data.processed); //这也是第二章中提到的per-CPU变量,增加正在处理标志计数器。
//这里是sniffer嗅探器相关的代码,ptype_all包括了所以协议类型,通过wireshark或者tcpdump工具将网卡至于混杂模式下//抓取数据,会执行这段代码
3470 list_for_each_entry_rcu(ptype, &ptype_all, list) {
3471 if (!ptype->dev || ptype->dev == skb->dev) {
3472 if (pt_prev) //分片相关
3473 ret = deliver_skb(skb, pt_prev, orig_dev);
3474 pt_prev = ptype;
3475 }
3476 }
//解引用设备接收处理函数,如何存在会向上层发送接收到数据包
3500 rx_handler = rcu_dereference(skb->dev->rx_handler);
3501 if (rx_handler) {
3502 if (pt_prev) {
3503 ret = deliver_skb(skb, pt_prev, orig_dev);
3504 pt_prev = NULL;
3505 }
//根据处理结果,判断接下来对数据包如何进一步处理。
3506 switch (rx_handler(&skb)) {
//数据包已成功接收,不需要再处理
3507 case RX_HANDLER_CONSUMED:
3508 ret = NET_RX_SUCCESS;
3509 goto unlock;
//当rx_handler改变过skb->dev时,在接收回路中再一次处理。
3510 case RX_HANDLER_ANOTHER:
3511 goto another_round;
//不使用匹配的方式,精确传递。
3512 case RX_HANDLER_EXACT:
3513 deliver_exact = true;
//忽略rx_handler的影响。
3514 case RX_HANDLER_PASS:
3515 break;
3516 default:
3517 BUG();
3518 }
3519 }
//如果前面判段是精确发送方式,那么把nulll_or_dev设置成精确传送的设备。
3532 null_or_dev = deliver_exact ? skb->dev : NULL;
3533
3534 type = skb->protocol;
//根据套接字类型处理,802.3的type值是0001。
3535 list_for_each_entry_rcu(ptype,
3536 &ptype_base[ntohs(type) & PTYPE_HASH_MASK], list) {
3537 if (ptype->type == type &&
3538 (ptype->dev == null_or_dev || ptype->dev == skb->dev ||
3539 ptype->dev == orig_dev)) {
3540 if (pt_prev)
3541 ret = deliver_skb(skb, pt_prev, orig_dev);
3542 pt_prev = ptype;
3543 }
3544 }
3565 }
由上面可以看出,各种接收数据包情况下最后的函数均指向了deliver_skb函数,到这里,代码不再在/net/core/dev.c中了,转到了net/ipv4/ip_input.c了,这里真真切切的将代码转到了IP(网络)层了。
net/core/dev.c
1679 static inline int deliver_skb(struct sk_buff *skb,
1680 struct packet_type *pt_prev,
1681 struct net_device *orig_dev)
1682 {
//在接收端要将分片的数据包重组,这里判断是否因缺少分片包而导致一个skb成为一个孤儿skb。显然是不太可能是孤儿进
//程的,所以这里使用了unlikely知道gcc编译器优化程序,以减少指令流水被打断的概率。
1683 if (unlikely(skb_orphan_frags(skb, GFP_ATOMIC)))
1684 return -ENOMEM;
1685 atomic_inc(&skb->users); //增加skb的使用者计数器
1686 return pt_prev->func(skb, skb->dev, pt_prev, orig_dev);
1687 }
<span style="font-family: Arial, Helvetica, sans-serif; font-size: 12px; color: rgb(51, 51, 51); background-color: rgb(255, 255, 255);"><span style="color: rgb(51, 51, 51);"></span></span><pre name="code" class="cpp"><span style="font-family: Arial, Helvetica, sans-serif; font-size: 12px; color: rgb(51, 51, 51); background-color: rgb(255, 255, 255);"></span>
1686行,对于ip数据包,这里的func是ip_packet_type结构体中的指针。为了弄清楚这个函数到底是怎么指向ip_rcv的需要向下接着看。以太网初始化函数inet_init调用了dev_add_pack(&ip_packet_type);
static int __init inet_init(void)
{
/*注册了四个种协议类型*/
rc = proto_register(&tcp_prot, 1);
rc = proto_register(&udp_prot, 1);
rc = proto_register(&raw_prot, 1);
rc = proto_register(&ping_prot, 1);
//对应协议初始化,ping隶属icmp协议,在以前版本中没有独立出来,不过由于其重要性,所以默认各个系统都支持了。
arp_init();
ip_init();
tcp_v4_init();
tcp_init();
udp_init();
ping_init();
ipv4_proc_init();
dev_add_pack(&ip_packet_type);
}
dev_add_pack函数原型和参数如下:
void dev_add_pack(struct packet_type *pt)
{
struct list_head *head = ptype_head(pt);
spin_lock(&ptype_lock);
list_add_rcu(&pt->list, head);
spin_unlock(&ptype_lock);
}
/参数/ af_inet.c
static struct packet_type ip_packet_type __read_mostly = {
.type = cpu_to_be16(ETH_P_IP),
.func = ip_rcv,
};
ptype_head函数定义如下:
net/core/dev.c
struct list_head ptype_base[PTYPE_HASH_SIZE] __read_mostly;
static inline struct list_head *ptype_head(const struct packet_type *pt)
{
if (pt->type == htons(ETH_P_ALL))
return &ptype_all;
else
return &ptype_base[ntohs(pt->type) & PTYPE_HASH_MASK];
}
4.2 进入网络层
到这里已经获得了ip_rcv的来历,并且知道ip_rcv已经被调用来处理接收到的frame了。
net/ipv4/ip_input.c
375 int ip_rcv(struct sk_buff *skb, struct net_device *dev, struct packet_type *pt, struct net_device *orig_dev)
376 {
/* 如果packet的目的地址是其它主机,简单丢弃该数据包而不做处理 */
383 if (skb->pkt_type == PACKET_OTHERHOST)
384 goto drop;
/*该宏跟新包相关统计信息,如丢弃、接收、过载等 */
387 IP_UPD_PO_STATS_BH(dev_net(dev), IPSTATS_MIB_IN, skb->len);
//检查该skb是否共享的,如果共享则会复制该数据包,在遇到内存申请失败时,会更新discard计数器。
389 if ((skb = skb_share_check(skb, GFP_ATOMIC)) == NULL) {
390 IP_INC_STATS_BH(dev_net(dev), IPSTATS_MIB_INDISCARDS);
391 goto out;
392 }
393 /* 对接收的数据包IP头进行判断,如果IP头小于最短长度,即IP头有错,那么执行395行。
394 if (!pskb_may_pull(skb, sizeof(struct iphdr)))
395 goto inhdr_error;
397 iph = ip_hdr(skb);
409 /*头长和版本号判断。*/
410 if (iph->ihl < 5 || iph->version != 4)
411 goto inhdr_error;
412 /*处理头可选项字段*/
413 if (!pskb_may_pull(skb, iph->ihl*4))
414 goto inhdr_error;
416 iph = ip_hdr(skb);
417 /*Ip校验*/
418 if (unlikely(ip_fast_csum((u8 *)iph, iph->ihl)))
419 goto csum_error;
/*防火墙钩子函数,防火墙规则验证通过后对调用最后一个参数对应的函数,即 ip_rcv_finish */
445 return NF_HOOK(NFPROTO_IPV4, NF_INET_PRE_ROUTING, skb, dev, NULL,
446 ip_rcv_finish);
456 }
这个函数的处理逻辑思路非常清楚,首先是对IP数据包的正确性进行相关的验证,包括协议头长度、协议的版本号、IP校验和等。然后调用传送数据的核心函数进行相应的函数(防火墙钩子函数)进行数据处理。
445行,NF_HOOK不是宏,而是一个函数,通常称为钩子函数,NF是netfilter的缩写,netfilter在Linux内核中被称为包过滤防火墙,netfilter的内容见十一章。
在编译内核时没有配置netfilter时,NF_HOOK最后一个参数被调用,此例中即执行ip_forward_finish函数;否则进入HOOK点,执行通过nf_register_hook()登记的功能(这句话表达意义的可能比较含糊,实际是进入nf_hook_slow()函数,再由它执行登记的函数)。
钩子函数不是重点,重点是ip_rcv_finish函数,该函数完成两个功能,其一是根据数据包目的地址和路由配置将数据包送给本地(local)主机或者发送出去(forward),处理IP头字段中的可选字段。这里涉及到一个路由的概念,路由就是为packet找到合适的归宿,这个归宿可能是数据包被传送到本机协议栈上层,也可能通过网卡发送出去,关于Linux内核的trie路由内容见十二章。
311 static int ip_rcv_finish(struct sk_buff *skb)
312 {
313 const struct iphdr *iph = ip_hdr(skb); //获得ip头,20字节内容
314 struct rtable *rt; //rtable是路由缓存
/*判断skb的路由缓存是否已有路由项,对于发往本机的数据(回环)该套接字的会包含路由项,如果没有路由项会调用ip_route_input_noref为skb选择一个合适的路由项,并将该信息存储在skb指向的路由选项指针*/
332 if (!skb_dst(skb)) {
333 int err = ip_route_input_noref(skb, iph->daddr, iph->saddr,
334 iph->tos, skb->dev);
335 if (unlikely(err)) {//找不到路由项,则进行出错处理
336 if (err == -EXDEV)
337 NET_INC_STATS_BH(dev_net(skb->dev),
338 LINUX_MIB_IPRPFILTER);
339 goto drop;
340 }
341 }
//这里获得上面skb指向的路由项入口,_skb_refdst是套接字的一个成员,如果refcount没有使用,则最低一个bit是1,其它
//比特用于指示路由项的入口地址,根据skb类型,对多播和广播的统计信息如下,
357 rt = skb_rtable(skb);
358 if (rt->rt_type == RTN_MULTICAST) {
359 IP_UPD_PO_STATS_BH(dev_net(rt->dst.dev), IPSTATS_MIB_INMCAST,
360 skb->len);
361 } else if (rt->rt_type == RTN_BROADCAST)
362 IP_UPD_PO_STATS_BH(dev_net(rt->dst.dev), IPSTATS_MIB_INBCAST,
363 skb->len);
365 return dst_input(skb);// 对数据包的实际处理函数。
367 drop:
368 kfree_skb(skb);
369 return NET_RX_DROP; //如果packet被丢弃了,没有进行forward或者local deliver操作则返回NET_RX_DROP。
370 }
到这里,从ip_rcv和ip_rcv_finish函数的注释中可以看出,ip层首先检查IP头的正确性,然后根据skb找到对应的路由信息,找到路由信息后就可以调用365行的函数dst_input完成实际的接收工作了。关于网络层的路由查找见第十二章,365行函数的原型如下:
static inline int dst_input(struct sk_buff *skb)
{
return skb_dst(skb)->input(skb);
}
input指针已经在333行建立路由项时进行赋值了,对于localdeliver,函数就是ip_local_deliver,forward就是ip_forward。
244 int ip_local_deliver(struct sk_buff *skb)
245 {
//对于分片的packet就逆向合并成一个packet,有些网卡硬件有offload特性,这个特性可以将分片和解分的过程在网卡处实现
//而不需要在ip层实现。此外,现在一般网络上的路由器都能支持1500的以太网数据包,有些还支持巨形包,分片的情况现在
//已经很少了。
250 if (ip_is_fragment(ip_hdr(skb))) {
251 if (ip_defrag(skb, IP_DEFRAG_LOCAL_DELIVER))
252 return 0;
253 }
254 //再一次看到NF_HOOK函数,这是钩子函数起作用的第二个地方,ip_local_deliver_finish函数,完成实际的处理工作。
255 return NF_HOOK(NFPROTO_IPV4, NF_INET_LOCAL_IN, skb, skb->dev, NULL,
256 ip_local_deliver_finish);
257 }
ip_local_deliver_finish()和ip_rcv()在同一个文件里。
189 static int ip_local_deliver_finish(struct sk_buff *skb)
190 {
191 struct net *net = dev_net(skb->dev);
//移动skb->data指针跳过ip头,获得tcp层起始地址。
193 __skb_pull(skb, skb_network_header_len(skb));
195 rcu_read_lock();
196 {
197 int protocol = ip_hdr(skb)->protocol; //获得ip层的协议,V4协议,传输层。
198 const struct net_protocol *ipprot; //对于v4版本该结构体的初始定义如下。
/**********************************
这篇文章是按照TCP-IP来跟踪网络协议栈的, tcp对应的 net_protocol对应结构体
static const struct net_protocol tcp_protocol = {
.early_demux = tcp_v4_early_demux,
.handler = tcp_v4_rcv, //对应接收函数的指针。
.err_handler = tcp_v4_err,
.no_policy = 1,
.netns_ok = 1,
};
**********************************/
201 resubmit:
202 raw = raw_local_deliver(skb, protocol); // raw socket的deliver的方式。
204 ipprot = rcu_dereference(inet_protos[protocol]); //根据protocol获得net_protocol对应函数的指针。
205 if (ipprot != NULL) { //如果找到,对于tcp协议其将是tcp_protocol,执行的是这段代码
206 int ret;
208 if (!ipprot->no_policy) {//为使用策略
209 if (!xfrm4_policy_check(NULL, XFRM_POLICY_IN, skb)) {
210 kfree_skb(skb);
211 goto out;
212 }
213 nf_reset(skb);
214 }
215 ret = ipprot->handler(skb);
216 if (ret < 0) {
217 protocol = -ret;
218 goto resubmit;
219 }
220 IP_INC_STATS_BH(net, IPSTATS_MIB_INDELIVERS);
221 } else { //对于raw socket的操作。
222 if (!raw) {
223 if (xfrm4_policy_check(NULL, XFRM_POLICY_IN, skb)) {
224 IP_INC_STATS_BH(net, IPSTATS_MIB_INUNKNOWNPROTOS);
//icmp协议的packet的deliver方式,该协议还是挺重要的,iP获取,路由表信息的建立等会用到该协议skb,这就验证了前面
//说的,这个sk_buff贯串整个协议栈,只有在必要时才会赋值该sk_buff的一个副本。至此ip到tcp的接收流程已经梳理完毕。
225 icmp_send(skb, ICMP_DEST_UNREACH,
226 ICMP_PROT_UNREACH, 0);
227 }
228 kfree_skb(skb);
229 } else {
230 IP_INC_STATS_BH(net, IPSTATS_MIB_INDELIVERS);
231 consume_skb(skb); //非上面的情况,意味着packet应当被释放掉,该函数即完成该功能。
232 }
233 }
234 }
235 out:
236 rcu_read_unlock(); //rcu锁就是读写锁,支持并发读,而不支持并发写,为了防止写的starvation,有写请求时会阻塞读,可见写的优先级高于读,但是写不会抢占读。
239 }
215行, 结合198行上面的 .handler=tcp_v4_rcv,可知,调用了tcp_v4_rcv其参数是需要接收的skb套接字。
图4.1网络层接收函数调用流程