"linux-2.6.32/include/linux/netdevice.h"
struct packet_type {
__be16 type; /* This is really htons(ether_type). */
struct net_device *dev; /* NULL is wildcarded here */
int (*func) (struct sk_buff *,
struct net_device *,
struct packet_type *,
struct net_device *);
struct sk_buff *(*gso_segment)(struct sk_buff *skb,
int features);
int (*gso_send_check)(struct sk_buff *skb);
struct sk_buff **(*gro_receive)(struct sk_buff **head,
struct sk_buff *skb);
int (*gro_complete)(struct sk_buff *skb);
void *af_packet_priv;
struct list_head list;
};

"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 */

 //当网络设备收到网络数据包时,最终会在软件中断环境里调用此函数
int netif_receive_skb(struct sk_buff *skb)
{
//ptype_all 用于sniffer这样的程序
// 发送一份拷贝给这些注册的sniffer程序
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;
}
}
// 内核编译开Bridge_config,则将该数据包让网桥函数来处理,否则handle_bridge定义为空操作,
// 返回skb,让协议栈来处理上层协议。
skb = handle_bridge(skb, &pt_prev, &ret, orig_dev);
if (!skb)
goto out; skb = handle_macvlan(skb, &pt_prev, &ret, orig_dev);
if (!skb)
goto out;
//对该数据包转达到其他L3协议的处理函数
type = skb->protocol;
list_for_each_entry_rcu(ptype, &ptype_base[ntohs(type)&], list) {
if (ptype->type == type &&
(!ptype->dev || ptype->dev == skb->dev)) {
if (pt_prev)
ret = deliver_skb(skb, pt_prev, orig_dev);
pt_prev = ptype;
}
}
}

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.\n", 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和后面这种套节字的方法是在链路层截获数据包.

ptype_base和ptype_all学习笔记的更多相关文章

  1. js学习笔记:webpack基础入门(一)

    之前听说过webpack,今天想正式的接触一下,先跟着webpack的官方用户指南走: 在这里有: 如何安装webpack 如何使用webpack 如何使用loader 如何使用webpack的开发者 ...

  2. PHP-自定义模板-学习笔记

    1.  开始 这几天,看了李炎恢老师的<PHP第二季度视频>中的“章节7:创建TPL自定义模板”,做一个学习笔记,通过绘制架构图.UML类图和思维导图,来对加深理解. 2.  整体架构图 ...

  3. PHP-会员登录与注册例子解析-学习笔记

    1.开始 最近开始学习李炎恢老师的<PHP第二季度视频>中的“章节5:使用OOP注册会员”,做一个学习笔记,通过绘制基本页面流程和UML类图,来对加深理解. 2.基本页面流程 3.通过UM ...

  4. 2014年暑假c#学习笔记目录

    2014年暑假c#学习笔记 一.C#编程基础 1. c#编程基础之枚举 2. c#编程基础之函数可变参数 3. c#编程基础之字符串基础 4. c#编程基础之字符串函数 5.c#编程基础之ref.ou ...

  5. JAVA GUI编程学习笔记目录

    2014年暑假JAVA GUI编程学习笔记目录 1.JAVA之GUI编程概述 2.JAVA之GUI编程布局 3.JAVA之GUI编程Frame窗口 4.JAVA之GUI编程事件监听机制 5.JAVA之 ...

  6. seaJs学习笔记2 – seaJs组建库的使用

    原文地址:seaJs学习笔记2 – seaJs组建库的使用 我觉得学习新东西并不是会使用它就够了的,会使用仅仅代表你看懂了,理解了,二不代表你深入了,彻悟了它的精髓. 所以不断的学习将是源源不断. 最 ...

  7. CSS学习笔记

    CSS学习笔记 2016年12月15日整理 CSS基础 Chapter1 在console输入escape("宋体") ENTER 就会出现unicode编码 显示"%u ...

  8. HTML学习笔记

    HTML学习笔记 2016年12月15日整理 Chapter1 URL(scheme://host.domain:port/path/filename) scheme: 定义因特网服务的类型,常见的为 ...

  9. DirectX Graphics Infrastructure(DXGI):最佳范例 学习笔记

    今天要学习的这篇文章写的算是比较早的了,大概在DX11时代就写好了,当时龙书11版看得很潦草,并没有注意这篇文章,现在看12,觉得是跳不过去的一篇文章,地址如下: https://msdn.micro ...

随机推荐

  1. PHPExcel上传sae遇到: -1:fail to get xml content

    在用PHPExcel1.8.0来处理excel时,本地测试时好使的,但是要把代码部署到SAE,在上传代码的时候就会遇到这个问题. 部署代码中遇到问题: -1:fail to get xml conte ...

  2. DNS笔记 DNS区域集成到 Active Directory

    可以将 DNS 区域集成到 Active Directory 中以提供增强的容错功能和安全性.OpenDNS   Google Public DNS往返时间 (RTT) 远程访问服务 (RAS)域名与 ...

  3. devexpress 控制面板汉化方式 参考信息

    using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.T ...

  4. 【转载】input 中 type='text' 的提交问题

    原文链接:http://www.nowamagic.net/html/html_AboutInputSummit.php 有时候我们希望回车键敲在文本框(input element)里来提交表单(fo ...

  5. 在usercontrol中如何使用验证控件CustomValidator 中的客户端验证

    在用户控件中,为一个文本控件添加CustomValidator验证,然后设置CustomValidator 的ClientValidationFunction 属性为客户端的Validate(sour ...

  6. Mongodb的索引--学习笔记(未完)

    全文索引 建立方法: --在articles集合的key字段上创建全文索引 db.articles.ensureIndex({key:"text"}) --在articles集合的 ...

  7. python学习应用笔记(一)

    之前一直用c++写程序  所以考虑程序一般都比较容易往数据结构的方向想 而自己设计数据结构往往要费很大事  昨天看了一下python  发现脚本语言 真是厉害    用来进行模拟运算确实不错  可以先 ...

  8. Python学习教程(learning Python)--3.3 分支语句的条件表达式详解

    本节主要讨论分支语句的条件表达式问题. 在if或者if-else分支控制语句里由于都用到条件判断(表达式是真还是假),条件判断可以是一种关系运算也可以是布尔表达式. 本节将对if及if-else语句的 ...

  9. WPF数据双向绑定

    设置双向绑定,首先控件要绑定的对象要先继承一个接口: INotifyPropertyChanged 然后对应被绑定的属性增加代码如下: 意思就是当Age这个属性变化时,要通知监听它变化的人. 即:Pr ...

  10. 【转载】MongoDB参数

    我们可以通过mongod --help查看mongod的所有参数说明,以下是各参数的中文解释. 基本配置–quiet# 安静输出 –port arg# 指定服务端口号,默认端口27017 –bind_ ...