Linux网络子系统中协议栈的入口处理

网络驱动接收到报文后,会初始化skb->protocol 字段。链路层的接收函数netif_receive_skb会根据该字段来确定把报文送给那个协议模块进一步处理。

以太网的设备调用 eth_type_trans()来给skb->protocol赋值。
__be16 eth_type_trans(struct sk_buff *skb,struct net_device *dev)
{
    struct ethhdr *eth;
    unsigned char *rawp;net
    /*把接收net_device 赋给skb*/
    skb->dev = dev;
    /*给skb 的 mac 头指针赋值*/
    skb_reset_mac_header(skb);
    /*把 skb->data 下移,跳过以太头*/
    skb_pull(skb, ETH_HLEN);
    eth = eth_hdr(skb);
    if (unlikely(is_multicast_ether_addr(eth->h_dest)))
    {
        /*判断是否是广播和组播报文,是就给skb->pkt_type赋值*/
        if (!compare_ether_addr_64bits(eth->h_dest,dev->broadcast))
            skb->pkt_type = PACKET_BROADCAST;
        else
            skb->pkt_type = PACKET_MULTICAST;
    }
               
    /*如果报文的目的MAC不是到接收设备的MAC,设置skb->pkt_type*/
    else if (1 /*dev->flags&IFF_PROMISC */ )
    {
        if (unlikely(compare_ether_addr_64bits(eth->h_dest,
                                               dev->dev_addr)))
            skb->pkt_type = PACKET_OTHERHOST;
    }
    /*Marvell 交换芯片dsa 头*/
    if (netdev_uses_dsa_tags(dev))
        return htons(ETH_P_DSA);
    if (netdev_uses_trailer_tags(dev))
        return htons(ETH_P_TRAILER);
               
    /*以太网头在此返回*/
    if (ntohs(eth->h_proto) >= 1536)
        return eth->h_proto;
               
    /*以下处理非以太网的报文,不讨论*/
    rawp = skb->data;
    if (*(unsigned short *)rawp == 0xFFFF)
        return htons(ETH_P_802_3);
    /* Real 802.2 LLC*/
    return htons(ETH_P_802_2);
}
网络设备驱动在调用netif_receive_skb()或netif_rx()前调用 eth_type_trans():
    skb->protocol = eth_type_trans(skb,dev);

每个网络层协议都会初始化一个接收报文的函数。Linux内核中使用数据结构 struct packet_type 来描述单个网络层协议。
struct packet_type
{
    /*协议类型,比如ip(0x0800),vlan(0x8100)*/
    /* This is really htons(ether_type). */
    __be16  type;  
  
    /*指定接收的网络设备,如果为空,就从所有网络设备中接收,
      如果指定,就只接收指定设备上的数据*/
    struct net_device  *dev; /* NULL is wildcarded here      */

    /*协议接收函数*/
    int  (*func) (struct sk_buff *,
                  struct net_device *,
                  struct packet_type *,
                  struct net_device *);
  
    /*下面是 gso 功能使用,每个协议要用gso功能就自己实现自己的函数*/
    struct sk_buff  *(*gso_segment)(struct sk_buff *skb,int features);
    int  (*gso_send_check)(struct sk_buff *skb);
                                                             
    /*下面是 gro 功能使用,每个协议要用gro功能就自己实现自己的函数*/ 
    struct sk_buff   **(*gro_receive)(struct sk_buff **head,struct sk_buff *skb);
    int  (*gro_complete)(struct sk_buff *skb);
  
    /*指向协议使用的私有数据指针,一般没有使用*/
    void  *af_packet_priv;                                        
    /*把该结构体连接到相应的hash 链表上*/
    struct list_head    list;
};
linux 内核中定义了一张hash 表 ptype_base,hash key 是struct packet_type 中的type字段。表中的每个元素都指向一个struct packet_type 的链表。

同时还定义了一个struct packet_type 的链表ptype_all。这个链表上的协议处理程序接收所以协议的报文,主要用于网络工具和网络嗅探器接收报文。比如tcpdump 抓包程序和原始套接字使用这种类型的packet_type结构。

/*      0800    IP
 *      8100    802.1Q VLAN
 *      0001    802.3
 *      0002    AX.25
 *      0004    802.2
 *      8035    RARP
 *      0005    SNAP
 *      0805    X.25
 *      0806    ARP
 *      8137    IPX
 *      0009    Localtalk
 *      86DD    IPv6
 */
#define PTYPE_HASH_SIZE (16)
#define PTYPE_HASH_MASK (PTYPE_HASH_SIZE - 1)
static DEFINE_SPINLOCK(ptype_lock);
static struct list_head ptype_base[PTYPE_HASH_SIZE] __read_mostly;
static struct list_head ptype_all __read_mostly;    /* Taps */
这一张hash 表,协议栈根据报文类型找到相应的处理函数。

linux 内核中使用如下函数进行协议的添加和删除
添加协议:
void dev_add_pack(struct packet_type *pt)
{
    int hash;                                                                      
    /*ptype_all 链表使用ptype_lock 自旋锁来保护。
     ptype_bash  hash 表中写的时候用该自旋锁来保护,
    读的时候用rcu 锁来保护,实质就是读的时候禁止抢占*/
    spin_lock_bh(&ptype_lock);
    if (pt->type == htons(ETH_P_ALL))
        list_add_rcu(&pt->list, &ptype_all);
    else {
        hash = ntohs(pt->type) & PTYPE_HASH_MASK;
        list_add_rcu(&pt->list, &ptype_base[hash]);
    }
    spin_unlock_bh(&ptype_lock);
}

如果协议类型定义为 ETH_P_ALL,就是接收所有类型的报文。就把该packet_type加入到ptyte_all 链表上。

每个协议要自己定义自己的paket_type变量,初始化自己的数据。然后自协议模块初始化时调用
dev_add_packet把自己的packet_type 加入到 packet_base hash表中。
 

协议栈的真正入口函数 netif_receive_skb()
int netif_receive_skb(struct sk_buff *skb)
{
    struct packet_type *ptype, *pt_prev;
    struct net_device *orig_dev;
    struct net_device *null_or_orig;
    int ret = NET_RX_DROP;
    __be16 type;
                                                  
    /*给skb赋值接收时间戳*/
    if (!skb->tstamp.tv64)
        net_timestamp(skb);
                                                  
    /*如果带vlan tag,报文走到这里一般是网卡硬件支持解析vlan,驱动已经把解析出来的
      vlan tag 赋值给了sbk,判断是否是到本机vlan 三层口的报文,
      如果是,把接收dev 赋成vlan dev*/
    if (skb->vlan_tci && vlan_hwaccel_do_receive(skb))
        return NET_RX_SUCCESS;
                                                  
    /*netpoll是用于在网络设备及I/O还没初始化好时
      也能进行报文的收发处理的虚拟网卡的一种实现,
      主要用于远程调试及网络控制终端*/
    /* if we've gotten here through NAPI, check netpoll */
    if (netpoll_receive_skb(skb))
        return NET_RX_DROP;
    if (!skb->iif)
        skb->iif = skb->dev->ifindex;
                                                  
    /*处理链路聚合,如果接收dev 加入了聚合组,把接收dev 替换成聚合口*/
    null_or_orig = NULL;
    orig_dev = skb->dev;
    if (orig_dev->master)
    {
        if (skb_bond_should_drop(skb))
            null_or_orig = orig_dev; /* deliver only exact match */
        else
            skb->dev = orig_dev->master;
    }
                                                 
     /*增加收包统计计数*/
    __get_cpu_var(netdev_rx_stat).total++;
                                                  
    /*初始化skb 中以下指针*/
    skb_reset_network_header(skb);
    skb_reset_transport_header(skb);
    skb->mac_len = skb->network_header - skb->mac_header;
    pt_prev = NULL;
                                                 
    /*在ptype_base 和 ptype_base中查找,要使用rcu 读锁保护临界区*/
    rcu_read_lock();
  
                                                   
    /*如果内核注册了协议嗅探器,把skb 拷贝一份传给它进行处理。
      注意
      经过这轮循环,最后一个协议嗅探器的执行函数是没有被调用的,
      放在下面进行调用,因为报文处理的最后一个处理函数被调用时
      不需要进行skb  user 引用计数的加 1,所以,下一个处理函数会把
      最后一个ptype 传递进去,如果该函数要处理掉该skb时,应该先执行
      该ptype 处理函数后再执行自己的处理程序*/
    list_for_each_entry_rcu(ptype, &ptype_all, list)
    {
        if (ptype->dev == null_or_orig ||
            ptype->dev == skb->dev ||
            ptype->dev == orig_dev)
        {
            if (pt_prev)
                ret = deliver_skb(skb, pt_prev, orig_dev);
            pt_prev = ptype;
        }
    }
                                                 
    /*进入桥进行二层处理,如果返回skb == NULL,说明skb 被直接二层
      转发走了,不用再送网络层了,函数直接返回*/
    skb = handle_bridge(skb, &pt_prev, &ret, orig_dev);
    if (!skb)
        goto out;
                                                  
    /*处理vlan*/
    skb = handle_macvlan(skb, &pt_prev, &ret, orig_dev);
    if (!skb)
        goto out;
                                                 
    /*经过以上处理,报文没被消化掉,就在 ptype_base hash 表中
      找到该报文的协议接收函数,送给相应协议处理*/
    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_orig
            || ptype->dev == skb->dev
            || ptype->dev == orig_dev))
        {
            if (pt_prev)
                ret = deliver_skb(skb, pt_prev, orig_dev);
            pt_prev = ptype;
        }
    }
    if (pt_prev)
    {
        ret = pt_prev->func(skb, skb->dev, pt_prev, orig_dev);
    }
    else
    {
        kfree_skb(skb);
       /* Jamal, now you will not able to escape explaining
        * me how you were going to use this. :-)
        */
         ret = NET_RX_DROP;
    }
out:
    rcu_read_unlock();
    return ret;
}
EXPORT_SYMBOL(netif_receive_skb);

static inline int deliver_skb(struct sk_buff *skb,
                  struct packet_type *pt_prev,
                  struct net_device *orig_dev)
{
  /*送入相应协议接收函数时要对skb 的引用计数加一,
    防止正在使用时被释放。因为一个报文可以被多个协议
    的接收函数处理。但最后一个协议接收函数不需要对skb
    的引用计数加一,因为最后一个协议接收函数负责释放该
    报文所占的内存*/
    atomic_inc(&skb->users);
    return pt_prev->func(skb, skb->dev, pt_prev, orig_dev);
}

Linux网络子系统中协议栈的入口处理Linux网络子系统中协议栈的入口处理 alpha_2017 发布了64 篇原创文章 · 获赞 171 · 访问量 22万+ 私信 关注
上一篇:Linux网桥的实现分析与使用


下一篇:长瘦管道的MSS对TCP性能的影响