赞
踩
目录
1 协议栈入口 __netif_receive_skb_core()
网络收包流程从网卡驱动开始,一直往上,涉及NAPI、GRO、RPS等特性,通常是经过硬件中断后在经由软中断处理,在内核软中断的最后一步就是调用 netif_receive_skb 开始报文协议栈处理。
高版本内核(本文基于 linux-3.10.0)都会对 netif_receive_skb 函数进行了封装,一般最后都会调用 __netif_receive_skb_core 函数,主要逻辑如下:
- static int __netif_receive_skb_core(struct sk_buff *skb, bool pfmemalloc)
- {
- struct packet_type *ptype, *pt_prev;
- rx_handler_func_t *rx_handler;
- struct net_device *orig_dev;
- struct net_device *null_or_dev;
- bool deliver_exact = false;
- int ret = NET_RX_DROP;
- __be16 type;
-
- //记录收包时间,netdev_tstamp_prequeue为0,表示可能有包延迟
- net_timestamp_check(!netdev_tstamp_prequeue, skb);
-
- trace_netif_receive_skb(skb);
-
- /* if we've gotten here through NAPI, check netpoll */
- if (netpoll_receive_skb(skb))
- goto out;
-
- orig_dev = skb->dev;//记录原始收包网络设备
-
- //此时data指针是指向IP层头部的(没有vlan的情况下)
- //设置network_header指针 skb->network_header = skb->data - skb->head;
- skb_reset_network_header(skb);
- if (!skb_transport_header_was_set(skb))
- skb_reset_transport_header(skb);//设置transport_header指针,这里也是指向IP层
-
- /*设置mac_len的值为以太网报文头部长度,一般为mac_len = 14
- *skb->mac_len = skb->network_header - skb->mac_header;
- */
- skb_reset_mac_len(skb);
-
- /*指向前一个packet_type 的指针为NULL,设置此指针的目的是为了提高效率
- *这样相当于最后一个pt_prev 指向的函数未被执行,最后一次向上层传递时,
- *不需要在inc引用,回调中会free,这样相当于少调用了一次free
- */
- pt_prev = NULL;
-
- rcu_read_lock();
-
- another_round:
- //设置skb的skb_iif,记录数据包收包网络设备的索引号
- skb->skb_iif = skb->dev->ifindex;
-
- //将处理此数据包cpu的softnet_data结构统计已处理数据的字段processed加1
- __this_cpu_inc(softnet_data.processed);
-
- //如果报文为带vlan报文,在eth_type_trans中设置的skb->protocol 为 ETH_P_8021Q 或 ETH_P_8021AD
- if (skb->protocol == cpu_to_be16(ETH_P_8021Q) ||
- skb->protocol == cpu_to_be16(ETH_P_8021AD)) {
- skb = vlan_untag(skb);//剥离vlan标签
- if (unlikely(!skb))
- goto unlock;
- }
-
- #ifdef CONFIG_NET_CLS_ACT
- if (skb->tc_verd & TC_NCLS) {
- skb->tc_verd = CLR_TC_NCLS(skb->tc_verd);
- goto ncls;
- }
- #endif
-
- if (pfmemalloc)
- goto skip_taps;
-
- //如抓包程序未指定设备,遍历ptype_all链表,输入一份报文到ptype_all链表中的协议族,处理ETH_P_ALL类型的数据包
- list_for_each_entry_rcu(ptype, &ptype_all, list) {
- if (!ptype->dev || ptype->dev == skb->dev) {
- if (pt_prev)
- ret = deliver_skb(skb, pt_prev, orig_dev);
- pt_prev = ptype;
- }
- }
-
- skip_taps:
- #ifdef CONFIG_NET_CLS_ACT
- skb = handle_ing(skb, &pt_prev, &ret, orig_dev);
- if (!skb)
- goto unlock;
- ncls:
- #endif
-
- if (pfmemalloc && !skb_pfmemalloc_protocol(skb))
- goto drop;
-
- //判断是否为vlan报文,并且vlan_tci 的VLAN_TAG_PRESENT位为1(skb_vlan_untag中进行过设置)
- if (vlan_tx_tag_present(skb)) {
- /*若pt_prev 不为空,则表示进行过ETH_P_ALL 协议类型处理,
- *执行刚刚链表的最后一个协议处理函数,并将pt_prev 置为NULL
- */
- if (pt_prev) {
- ret = deliver_skb(skb, pt_prev, orig_dev);
- pt_prev = NULL;
- }
- if (vlan_do_receive(&skb))//vlan处理函数
- /*完成vlan处理后,改变了skb->dev,跳转到another_round重新执行
- *此时有一个问题:是否会重复执行ETH_P_ALL 协议处理函数,
- *答案:不会。因为一般会判断orig_dev和skb->dev一否一致,此时已经不一致了
- */
- goto another_round;
- else if (unlikely(!skb))
- goto unlock;
- }
-
- //若rx_handler 不为NULL,则进入桥处理,rx_handler 在br_add_if中注册的这个函数
- rx_handler = rcu_dereference(skb->dev->rx_handler);
- if (rx_handler) {
- /*若pt_prev 不为空,则表示进行过ETH_P_ALL 协议类型处理,
- *执行刚刚链表的最后一个协议处理函数,并将pt_prev 置为NULL
- */
- if (pt_prev) {
- ret = deliver_skb(skb, pt_prev, orig_dev);
- pt_prev = NULL;
- }
- /*执行rx_handler 函数,为br_handle_frame 函数,在br_add_if中注册的这个函数
- *下面根据桥处理的返回值进行下一步处理
- */
- switch (rx_handler(&skb)) {
- //桥已经处理该数据包,该数据包会以其他的方式传送
- case RX_HANDLER_CONSUMED:
- ret = NET_RX_SUCCESS;
- goto unlock;
-
- //桥改变的数据包的skb->dev,需要another_round进行再一次的处理
- case RX_HANDLER_ANOTHER:
- goto another_round;
-
- //数据包只会传送到注册为具体网络设备(ptype->dev == skb->dev)的协议处理例程
- case RX_HANDLER_EXACT:
- deliver_exact = true;
-
- //正常传送
- case RX_HANDLER_PASS:
- break;
- default:
- BUG();
- }
- }
-
- if (vlan_tx_nonzero_tag_present(skb))
- skb->pkt_type = PACKET_OTHERHOST;
-
- /* deliver only exact match when indicated */
- null_or_dev = deliver_exact ? skb->dev : NULL;
-
- //记录三层的协议类型(ip、arp等)
- type = skb->protocol;
-
- //获取三层协议钩子函数,向上层继续处理报文
- list_for_each_entry_rcu(ptype,
- &ptype_base[ntohs(type) & PTYPE_HASH_MASK], list) {
- if (ptype->type == type &&
- (ptype->dev == null_or_dev || ptype->dev == skb->dev ||
- ptype->dev == orig_dev)) {
- if (pt_prev)
- ret = deliver_skb(skb, pt_prev, orig_dev);
- pt_prev = ptype;
- }
- }
- /*如果pt_prev 不为空,表明上面链表处理过程中还留下最后一个协议处理函数还没有执行
- *此时就将这个协议处理函数传出到外层函数__netif_receive_skb_one_core调用pt_prev->func进行处理
- *外层函数处理时就不需要deliver_skb来增加skb->users,减少了一次skb的释放
- */
- if (pt_prev) {
- if (unlikely(skb_orphan_frags(skb, GFP_ATOMIC)))
- goto drop;
- else
- ret = pt_prev->func(skb, skb->dev, pt_prev, orig_dev);
- } else {
- drop:
- atomic_long_inc(&skb->dev->rx_dropped);
- kfree_skb(skb);
- /* Jamal, now you will not able to escape explaining
- * me how you were going to use this. :-)
- */
- ret = NET_RX_DROP;
- }
-
- unlock:
- rcu_read_unlock();
- out:
- return ret;
- }
- /*
- * struct vlan_hdr - vlan header
- * @h_vlan_TCI: priority and VLAN ID
- * @h_vlan_encapsulated_proto: packet type ID or len
- */
- struct vlan_hdr {
- __be16 h_vlan_TCI; //vlan 控制信息(2字节)
- __be16 h_vlan_encapsulated_proto;//报文实际协议类型(2字节)
- };
前两个字节为标签协议标识TPID(Tag Protocol Identifier),值为0x8100,后
后两个字节为标签控制信息TCI(Tag Control Information),前三位Priority表明帧的优先级,接下来的一位cfi用于以太网与FDDI和令牌环网交换数据时的帧格式,最后12位VLAN ID,一共4096个
报文信息如下:
- struct sk_buff *vlan_untag(struct sk_buff *skb)
- {
- struct vlan_hdr *vhdr;
- u16 vlan_tci;
-
- if (unlikely(vlan_tx_tag_present(skb))) {
- /* vlan_tci is already set-up so leave this for another time */
- return skb;
- }
-
- skb = skb_share_check(skb, GFP_ATOMIC);
- if (unlikely(!skb))
- goto err_free;
-
- if (unlikely(!pskb_may_pull(skb, VLAN_HLEN)))
- goto err_free;
-
- vhdr = (struct vlan_hdr *) skb->data;
- vlan_tci = ntohs(vhdr->h_vlan_TCI);
- __vlan_hwaccel_put_tag(skb, skb->protocol, vlan_tci);
-
- skb_pull_rcsum(skb, VLAN_HLEN);
- vlan_set_encap_proto(skb, vhdr);
-
- skb = vlan_reorder_header(skb);
- if (unlikely(!skb))
- goto err_free;
-
- skb_reset_network_header(skb);
- skb_reset_transport_header(skb);
- skb_reset_mac_len(skb);
-
- return skb;
-
- err_free:
- kfree_skb(skb);
- return NULL;
- }
eth_type_trans()
在eth_type_trans
函数中,将data
指针下移14字节(skb_pull_inline(skb, ETH_HLEN);
),如果此时报文带vlan,vlan信息4个字节,前两个字节为标签协议标识TPID(Tag Protocol Identifier),值为0x8100,后两个字节为标签控制信息TCI(Tag Control Information),那么此时data就指向的是TCI控制信息,因为以太网源和目的mac地址12字节,加上vlan标签协议标识2字节正好14字节vhdr = (struct vlan_hdr *)skb->data;
这个函数将data
后的四字节数据赋给vlan_hdr
,那么h_vlan_TCI
就为vlan标签控制信息,h_vlan_encapsulated_proto
即为真正的以太网协议类型。后续函数vlan_set_encap_proto
会设置 skb->protocol = vhdr->h_vlan_encapsulated_proto
packet_type
结构作为网络层的输入接口,系统支持多种协议族,因此每个协议族都会实现一个报文处理例程,此结构的功能时在链路层和网络层之间起到了桥梁的作用,在以太网上,以太网帧到达主机后,会根据协议族的报文类型调用相应的网络层接受处理函数。结构体信息如下:
- struct packet_type {
-
- //网络层数据包协议类型
- __be16 type; /* This is really htons(ether_type). */
-
- //接受从指定网络设备输入的数据包,若为NULL,则表示接受全部网络设备的数据包
- struct net_device *dev; /* NULL is wildcarded here */
-
- //协议入口处理函数
- int (*func) (struct sk_buff *,
- struct net_device *,
- struct packet_type *,
- struct net_device *);
- bool (*id_match)(struct packet_type *ptype,
- struct sock *sk);
- void *af_packet_priv;//存储各协议族私有数据
- struct list_head list;//链接不同协议族报文接受例程的指针
- };
为向上层协议递交设备驱动收到的数据包,内核提供了表结构 ptype_base
和 ptype_all
,它们都是struct packet_type类型,ptype_base
负责把不同类型(协议)的数据包递交给对应的上层协议模块,ptype_all
表不区分包的协议类型,负责把所有数据包递交给某个注册的上层模块
参见《linux内核协议栈 三 / 四层协议接收数据处理函数以及相关的全局 hash表 / 数组》
br_handle_frame
这个函数的初始注册地点是在桥添加接口的时候,注册在桥某一个接口上
- int br_add_if(struct net_bridge *br, struct net_device *dev, struct netlink_ext_ack *extack)
- {
- struct net_bridge_port *p;
- int err = 0;
- unsigned br_hr, dev_hr;
- bool changed_addr;
- ...
- //创建一个新的桥接口 p->br = br; p->dev = dev;
- p = new_nbp(br, dev);
- ...
-
- //register receive handler,将br_handle_frame 函数注册到此桥接口上
- err = netdev_rx_handler_register(dev, br_handle_frame, p);
- ...
- }
-
- int netdev_rx_handler_register(struct net_device *dev,
- rx_handler_func_t *rx_handler,
- void *rx_handler_data)
- {
- ...
- //将net_bridge_port 赋给dev网络设备的rx_handler_data
- rcu_assign_pointer(dev->rx_handler_data, rx_handler_data);
-
- //将br_handle_frame 赋给dev网络设备的rx_handler
- rcu_assign_pointer(dev->rx_handler, rx_handler);
- ...
- }
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。