zoukankan      html  css  js  c++  java
  • ptype_base和ptype_all学习笔记

     1 "linux-2.6.32/include/linux/netdevice.h"
     2 struct packet_type {
     3         __be16                  type;   /* This is really htons(ether_type). */
     4         struct net_device       *dev;   /* NULL is wildcarded here           */
     5         int                     (*func) (struct sk_buff *,
     6                                          struct net_device *,
     7                                          struct packet_type *,
     8                                          struct net_device *);
     9         struct sk_buff          *(*gso_segment)(struct sk_buff *skb,
    10                                                 int features);
    11         int                     (*gso_send_check)(struct sk_buff *skb);
    12         struct sk_buff          **(*gro_receive)(struct sk_buff **head,
    13                                                struct sk_buff *skb);
    14         int                     (*gro_complete)(struct sk_buff *skb);
    15         void                    *af_packet_priv;
    16         struct list_head        list;
    17 };

    "net/core/dev.c" 5753

    static struct list_head ptype_base[PTYPE_HASH_SIZE] __read_mostly;

    static struct list_head ptype_all __read_mostly; /* Taps */

     1 //当网络设备收到网络数据包时,最终会在软件中断环境里调用此函数    
     2 int netif_receive_skb(struct sk_buff *skb)   
     3 {   
     4    //ptype_all 用于sniffer这样的程序
     5    // 发送一份拷贝给这些注册的sniffer程序
     6    list_for_each_entry_rcu(ptype, &ptype_all, list) {   
     7      if (!ptype->dev || ptype->dev == skb->dev) {   
     8        if (pt_prev)   
     9          ret = deliver_skb(skb, pt_prev, orig_dev);   
    10        pt_prev = ptype;   
    11      }    
    12    }   
    13    // 内核编译开Bridge_config,则将该数据包让网桥函数来处理,否则handle_bridge定义为空操作,
    14    // 返回skb,让协议栈来处理上层协议。   
    15    skb = handle_bridge(skb, &pt_prev, &ret, orig_dev);   
    16    if (!skb)   
    17      goto out;  
    18            
    19    skb = handle_macvlan(skb, &pt_prev, &ret, orig_dev);   
    20    if (!skb)   
    21      goto out;   
    22    //对该数据包转达到其他L3协议的处理函数   
    23    type = skb->protocol;   
    24    list_for_each_entry_rcu(ptype, &ptype_base[ntohs(type)&15], list) {   
    25      if (ptype->type == type &&   
    26          (!ptype->dev || ptype->dev == skb->dev)) {   
    27        if (pt_prev)   
    28          ret = deliver_skb(skb, pt_prev, orig_dev);   
    29        pt_prev = ptype;   
    30      }   
    31    }   
    32 }

    netif_receive_skb()的主要作用体现在两个遍历链表的操作中,其中之一为遍历ptype_all 链,这些为注册到内核的一些 sniffer,将上传给这些sniffer,另一个就是遍历 ptype_base,这个就是具体的协议类型。当 eth1 接收到一个IP数据包时,它首先分别发送一份副本给每个 ptype_all 链表中的 packet_type,它们都由 package_rcv 处理,然后再根据HASH 值,在遍历另一个HASH 表时,发送一份给类型为 ETH_P_IP 的类型,它由 ip_rcv处理。如果这个链中还注册有其它 IP层的协议,它也会同时发送一个副本给它。 

    在数据包接收过程的那篇笔记中可以知道,在数据包的处理函数netif_receive_skb中,会先看ptype_all中是否有注册的协议,如果有,则调用相应的处理函数,然后再到ptype_base中,找到合适的协议,将skb发送到相关协议的处理函数.比如ip协议(ip_rcv)或者arp(arp_rcv)等等.此篇笔记讲的是有关ptype_all和ptype_base的相关知识点.

    ptype_base和ptype_all在内核中存储的情况如下图:



    可以看到,ptype_base为一个hash表,而ptype_all为一个双向链表.每一个里面注册的协议都用一个struct packet_type表示.

    struct packet_type 
    {
        unsigned short        type;    /*协议类型*/
        struct net_device     *dev;   
        int            (*func) (struct sk_buff *, struct net_device *,
                         struct packet_type *);
        void            *data;    /* Private to the packet type        */
        struct packet_type    *next;
    };
    其中需要注意的是dev参数,此参数表明了协议只处理来自dev指向device的数据,当dev=NULL时,表示该协议处理来自所有device的数据.这样,当注册自己的协议时,就可以指定自己想要监听或者接收的device.
    其中注册和注销协议的函数为:
    dev_add_pack(...)和dev_remove_pack(...)
    这两个函数很简单,分别如下:
    void dev_add_pack(struct packet_type *pt)
    {
        int hash;
        br_write_lock_bh(BR_NETPROTO_LOCK);
    #ifdef CONFIG_NET_FASTROUTE
        /* Hack to detect packet socket */
        if ((pt->data) && ((int)(pt->data)!=1)) {
            netdev_fastroute_obstacles++;
            dev_clear_fastroute(pt->dev);
        }
    #endif
        if (pt->type == htons(ETH_P_ALL)) {
            netdev_nit++;
            pt->next=ptype_all;
            ptype_all=pt;
        } else {
            hash=ntohs(pt->type)&15;
            pt->next = ptype_base[hash];
            ptype_base[hash] = pt;
        }
        br_write_unlock_bh(BR_NETPROTO_LOCK);
    }
    此函数判断协议类型,然后加到ptype_base或者ptype_all中.
    void dev_remove_pack(struct packet_type *pt)
    {
        struct packet_type **pt1;
        br_write_lock_bh(BR_NETPROTO_LOCK);
        if (pt->type == htons(ETH_P_ALL)) {
            netdev_nit--;
            pt1=&ptype_all;
        } else {
            pt1=&ptype_base[ntohs(pt->type)&15];
        }
        for (; (*pt1) != NULL; pt1 = &((*pt1)->next)) {
            if (pt == (*pt1)) {
                *pt1 = pt->next;
    #ifdef CONFIG_NET_FASTROUTE
                if (pt->data)
                    netdev_fastroute_obstacles--;
    #endif
                br_write_unlock_bh(BR_NETPROTO_LOCK);
                return;
            }
        }
        br_write_unlock_bh(BR_NETPROTO_LOCK);
        printk(KERN_WARNING "dev_remove_pack: %p not found. ", pt);
    }
    此函数也很简单,只是把协议从相关的链表中移除.
    下面以ip协议为例子来看看相关的实现:
    ip协议结构体的定义如下:
    static struct packet_type ip_packet_type =
    {
        __constant_htons(ETH_P_IP),
        NULL,    /* All devices */
        ip_rcv,
        (void*)1,
        NULL,
    };
    当ipv4协议栈初始化时,会调用ip_init.之后,所有协议类型为ETH_P_IP的包都会交由ip_rcv处理.代码如下:
    void __init ip_init(void)
    {
        dev_add_pack(&ip_packet_type);
        ip_rt_init();
        inet_initpeers();
    #ifdef CONFIG_IP_MULTICAST
        proc_net_create("igmp", 0, ip_mc_procinfo);
    #endif
    }
    这样在系统启动之后,ip协议便被注册到ptype_base链表中,相应的处理函数为ip_rcv.
    arp协议和其他类型的协议(在ptype_base或者ptype_all中的)的执行过程同理.
    本人初学网络,水平很菜,如有错误,希望看到的朋友们及时指出,不胜感激.
    ps1:记得刚来实验室的时候,做的截包模块的第一种方法是用的netfilter,第二种方法主要就是用到的这块知识.现在总结起来,觉得还算简单,当初却用了很长时间,想想,真是难者不会,会者不难啊.今天看书的时候,好像又发现了另外一种方法可以实现我的要求,记录在ps2上.
    ps2:在数据链路层截包的另一种方法:用PF_PACKET socket type.linux可以用此类型套节字直接从链路层截获或者注入数据.发送数据时,直接发送到dev_queue_xmit.而接收函数时,可以在数据包通过路由之前截获到.如tcpdump和Ethereal都是用到了此套接字.那么总结起来可以看出,截获数据包至少可以有三种方法实现,第一种的netfilter是在协议栈中截获数据包,而利用ptype_all或者ptype_base和后面这种套节字的方法是在链路层截获数据包.

  • 相关阅读:
    HashCode和equal方法的区别和联系 [转]
    Linux makefile 教程 [转]
    gcc: multiple definition of [转]
    conda虚拟环默认路径
    terrasolid修改背景颜色
    台式机无法开机可能的原因
    TensorFlow2.1中计算平方函数的tf.square()的用法
    Terrasolid 安装教程
    如何解决Pytorch的GPU driver is too old的问题?
    使用 TensorBoard 可视化进行监督
  • 原文地址:https://www.cnblogs.com/shudai/p/3234358.html
Copyright © 2011-2022 走看看