网络驱动接收到报文后,会初始化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网络子系统中协议栈的入口处理相关推荐

  1. linux 网络 指示灯 亮,Linux网络子系统中GRO的实现

    GRO (generic receive offload) GRO是在协议栈接收报文时进行减负的一种处理方式,该方式在设计上考虑了多种协议报文.主要原理是在接收端通过把多个相关的报文(比如TCP分段报 ...

  2. linux 网络路径中网络协议栈有几种,linux网络路径中网络协议栈有几种

    网络路径有很多种,其中的linux网络路径是最常用的,也是最需要关注的.linux网络路径中网络协议栈有几种?电脑新装系统漏洞应不应该修复?了解网络安全常识,首先就要了解计算机网络安全有哪些基本注意事 ...

  3. Linux协议栈:基于ping流程窥探Linux网络子系统,及常用优化方法

    初识 Linux 网络栈及常用优化方法 RToax 2020年9月 初识 Linux 网络栈及常用优化方法 1. 文章简介 基于 ping 流程窥探 Linux 网络子系统,同时介绍各个模块的优化方法 ...

  4. Linux网络子系统

    今天分享一篇经典Linux协议栈文章,主要讲解Linux网络子系统,看完相信大家对协议栈又会加深不少,不光可以了解协议栈处理流程,方便定位问题,还可以学习一下怎么去设计一个可扩展的子系统,屏蔽不同层次 ...

  5. 一文搞定 | Linux 网络子系统

    今天分享一篇经典Linux协议栈文章,主要讲解Linux网络子系统,看完相信大家对协议栈又会加深不少,不光可以了解协议栈处理流程,方便定位问题,还可以学习一下怎么去设计一个可扩展的子系统,屏蔽不同层次 ...

  6. linux网络子系统研究:数据收发简略流程图

    Linux网络子系统十分庞大复杂,总想着等自己全部弄明白后再动笔写些笔记,但实在太耗时.后来想通了,先从宏观上掌握大体框图,然后再研究细节. 本文先给出一张自己画的网络数据收发简略流程图,每个路径都可 ...

  7. Linux网络服务中,bond网络模式

    Linux网络服务中,bond网络模式 bond网络模式原理 多块网卡虚拟成一块网卡,实现冗余,多张网卡对外显示一张,具有同一个IP,网络配置都会使用Bonding技术做网口硬件层的冗余,防止单个网口 ...

  8. linux给网卡添加一个ip地址,linux网络配置中如何给一块网卡添加多个IP地址

    汤向峰每日一题-2017年3月16日: linux网络配置中如何给一块网卡添加多个IP地址 linux系统给网卡配置VIP的方法常见有两种:别名IP.以及辅助IP ================== ...

  9. Windows 10安装Linux子系统、可视化Linux、子系统中openfoam

    Windows 10安装Linux子系统.可视化Linux.子系统中openfoam安装 一. Windows 10安装Linux子系统 打开Windows PowerShell(管理员) 鼠标右键点 ...

  10. Linux网络编程中的几组类似功能的区别

    1.bzero与memset char buff[1024]; memset(buff,0,sizeof(buff));bzero(buff, sizeof(buff)); struct sockad ...

最新文章

  1. html px转换,pc端px转换为rem针对屏幕分辨率进行页面适配
  2. hadoop 开启防火墙_Hadoop部署一Hadoop安装
  3. mysqladmin flush-hosts 解决方法
  4. 前端学习(1800):前端调试之清除浮动练习1
  5. 微信分身版电脑版_电脑版营销wetool电脑版-网站
  6. 网站提速-缓存技术(4)
  7. keepalived IP漂移技术
  8. 手写Google第一代分布式计算框架 MapReduce
  9. cloud华为云服务登录(华为云服务平台登录入口)
  10. solidworks 2017/2018快捷键
  11. 数据传输过程加密方案
  12. JVM G1 源码分析(七)- Full GC
  13. 在Ubuntu1604中安装ROS Kinetic
  14. 基于遥感及气象数据的土壤侵蚀敏感性评价的解决方案
  15. 谦虚是幸福人生的护身符
  16. 《完美应用Ubuntu》第3版 何晓龙 著
  17. 技术分享连载(八十七)
  18. 2021年全球陀螺测斜仪收入大约6百万美元,预计2028年达到7百万美元
  19. 质量管理体系之测试用例
  20. 近视眼的病因有一定遗传倾向

热门文章

  1. Android【报错】. lang。android.app ClassCastException。SharedPreferencesImpl不能被强制转换为android.content.Shared
  2. HTML5 — 知识总结篇《V》【a元素】
  3. 023-数据结构与算法系列
  4. Bootstrap 模态框(Modal)插件
  5. C++中的模板那点事
  6. 为您的Android,iOS等应用加入声波传输功能
  7. OpenGL纹理-12.5、纹理坐标
  8. 海量数据库解决方案2011050301
  9. 浮动路由与VRRP的概念
  10. HCIE-Security Day33:IPSec:深入学习ipsec ikev2、IKEV1和IKEV2比较