zoukankan      html  css  js  c++  java
  • 网络数据包收发流程(四):协议栈之packet_type

    进入函数netif_receive_skb()后,skb正式开始协议栈之旅。
    先上图,协议栈大致过程如下所示:

    跟OSI七层模型不同,linux根据包结构对网络进行分层。
    比如,arp头和ip头都是紧跟在以太网头后面的,所以在linux协议栈中arp和ip地位相同(如上图)
    但是在OSI七层模型中,arp属于链路层,ip属于网络层.....
    这里就不死抠概念,我们就说arp,ip都属于第二层。下面是网络第二层的处理流程

    一、相关数据结构
    内核处理网络第二层,有下面2个重要list_head变量 (文件linux_2_6_24/net/core/dev.c)
    list_head 链表上挂了很多packet_type数据结构

    static struct list_head ptype_base[16] __read_mostly;   /* 16 way hashed list */
    static struct list_head ptype_all __read_mostly;        /* Taps */

    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);
        void   *af_packet_priv;
        struct list_head    list;
    };

    type 成员保存了二层协议类型,ETH_P_IP、ETH_P_ARP等等
    func 成员就是钩子函数了,如 ip_rcv()、arp_rcv()等等

    二、操作packet_type的API
    //把packet_type结构挂在与type对应的list_head上面
    void dev_add_pack(struct packet_type *pt){
        int hash;
        spin_lock_bh(&ptype_lock);
        if (pt->type == htons(ETH_P_ALL))        //type为ETH_P_ALL时,挂在ptype_all上面
            list_add_rcu(&pt->list, &ptype_all);
        else {
            hash = ntohs(pt->type) & 15;         //否则,挂在ptype_base[type&15]上面
            list_add_rcu(&pt->list, &ptype_base[hash]);
        }
        spin_unlock_bh(&ptype_lock);
    }

    //把packet_type从list_head上删除
    void dev_remove_pack(struct packet_type *pt){
        __dev_remove_pack(pt);
        synchronize_net();
    }
    void __dev_remove_pack(struct packet_type *pt){
        struct list_head *head;
        struct packet_type *pt1;
        spin_lock_bh(&ptype_lock);
        if (pt->type == htons(ETH_P_ALL))
            head = &ptype_all;                        //找到链表头
        else
            head = &ptype_base[ntohs(pt->type) & 15]; //

        list_for_each_entry(pt1, head, list) {
            if (pt == pt1) {
                list_del_rcu(&pt->list);
                goto out;
            }
        }
        printk(KERN_WARNING "dev_remove_pack: %p not found. ", pt);
    out:
        spin_unlock_bh(&ptype_lock);
    }

    三、进入二层协议处理函数
    int netif_receive_skb(struct sk_buff *skb)
    {
       //略去一些代码
        rcu_read_lock();
        //第一步:先处理 ptype_all 上所有的 packet_type->func()           
        //所有包都会调func,对性能影响严重!内核默认没挂任何钩子函数

        list_for_each_entry_rcu(ptype, &ptype_all, list) {  //遍历ptye_all链表
            if (!ptype->dev || ptype->dev == skb->dev) {    //上面的paket_type.type 为 ETH_P_ALL
                if (pt_prev)                                //对所有包调用paket_type.func()
                    ret = deliver_skb(skb, pt_prev, orig_dev); //此函数最终调用paket_type.func()
                pt_prev = ptype;
            }
        }
        //第二步:若编译内核时选上BRIDGE,下面会执行网桥模块
        //调用函数指针 br_handle_frame_hook(skb), 在动态模块 linux_2_6_24/net/bridge/br.c中
        //br_handle_frame_hook = br_handle_frame;
        //所以实际函数 br_handle_frame。
        //注意:在此网桥模块里初始化 skb->pkt_type 为 PACKET_HOST、PACKET_OTHERHOST

        skb = handle_bridge(skb, &pt_prev, &ret, orig_dev);
        if (!skb) goto out;

        //第三步:编译内核时选上MAC_VLAN模块,下面才会执行
        //调用 macvlan_handle_frame_hook(skb), 在动态模块linux_2_6_24/drivers/net/macvlan.c中
        //macvlan_handle_frame_hook = macvlan_handle_frame;
        //所以实际函数为 macvlan_handle_frame。
        //注意:此函数里会初始化 skb->pkt_type 为 PACKET_BROADCAST、PACKET_MULTICAST、PACKET_HOST

        skb = handle_macvlan(skb, &pt_prev, &ret, orig_dev);
        if (!skb)  goto out;

        //第四步:最后 type = skb->protocol; &ptype_base[ntohs(type)&15]
        //处理ptype_base[
    ntohs(type)&15]上的所有的 packet_type->func()
        //根据第二层不同协议来进入不同的钩子函数,重要的有:ip_rcv() arp_rcv()
        type = skb->protocol;
        list_for_each_entry_rcu(ptype, &ptype_base[ntohs(type)&15], list) {
            if (ptype->type == type &&                      //遍历包type所对应的链表
                (!ptype->dev || ptype->dev == skb->dev)) {  //调用链表上所有pakcet_type.func()
                if (pt_prev)
                    ret = deliver_skb(skb, pt_prev, orig_dev); //就这里!arp包会调arp_rcv()
                pt_prev = ptype;                               //        ip包会调ip_rcv()
            }
        }
        if (pt_prev) {
            ret = pt_prev->func(skb, skb->dev, pt_prev, orig_dev);
        } else {               //下面就是数据包从协议栈返回来了
            kfree_skb(skb);    //注意这句,若skb没进入socket的接收队列,则在这里被释放
            ret = NET_RX_DROP; //若skb进入接收队列,则系统调用取包时skb释放,这里skb引用数减一而已
        }
    out:
        rcu_read_unlock();
        return ret;
    }

    int deliver_skb(struct sk_buff *skb,struct packet_type *pt_prev, struct net_device *orig_dev){
       atomic_inc(&skb->users); //这句不容忽视,与后面流程的kfree_skb()相呼应
        return pt_prev->func(skb, skb->dev, pt_prev, orig_dev);//调函数ip_rcv() arp_rcv()等
    }

    这里只是将大致流程,arp_rcv(), ip_rcv() 什么的具体流程,以后再写。

    四、网络抓包tcpdump
    tcpdump也是在二层抓包的,用的是libpcap库,它的基本原理是
    1.先创建socket,内核dev_add_packet()挂上自己的钩子函数
    2.然后在钩子函数中,把skb放到自己的接收队列中,
    3.接着系统调用recv取出skb来,把数据包skb->data拷贝到用户空间
    4.最后关闭socket,内核dev_remove_packet()删除自己的钩子函数

    下面是一些重要的数据结构,用到的钩子函数都在这里初始化好了
    static const struct proto_ops packet_ops = {
        .family =    PF_PACKET,
        .owner =    THIS_MODULE,
        .release =    packet_release,    //关闭socket的时候调这个
        .bind =        packet_bind,
        .connect =    sock_no_connect,
        .socketpair =    sock_no_socketpair,
        .accept =    sock_no_accept,
        .getname =    packet_getname,
        .poll =        packet_poll,
        .ioctl =    packet_ioctl,
        .listen =    sock_no_listen,
        .shutdown =    sock_no_shutdown,
        .setsockopt =    packet_setsockopt,
        .getsockopt =    packet_getsockopt,
        .sendmsg =    packet_sendmsg,
        .recvmsg =    packet_recvmsg,   //socket收包的时候调这个
        .mmap =        packet_mmap,
        .sendpage =    sock_no_sendpage,
    };

    static struct net_proto_family packet_family_ops = {
        .family =    PF_PACKET,
        .create =    packet_create,     //创建socket的时候调这个
        .owner    =    THIS_MODULE,
    };

    至于系统调用 socket、recv、close是如何调到这些内核钩子函数的,以后再讲。这里只关注packet_type

    4.1 系统调用socket

    libpcap系统调用socket,内核最终调用 packet_create
    static int packet_create(struct net *net, struct socket *sock, int protocol){
        po->prot_hook.func = packet_rcv;   //初始化钩子函数指针
        po->prot_hook.af_packet_priv = sk;
        if (protocol) {
            po->prot_hook.type = protocol;  //类型是系统调用socket形参指定的
            dev_add_pack(&po->prot_hook);//关键!!
            sock_hold(sk);
            po->running = 1;
        }
        return(0);
    }

    4.2 钩子函数 packet_rcv 将skb放入到接收队列
    文件 linux_2_6_24/net/packet/af_packet.c
    简单来说,packet_rcv中,skb越过了整个协议栈,直接进入队列

    4.3 系统调用recv
    系统调用recv、read、recvmsg,内核最终会调用packet_recvmsg
    从接收队列中取出skb,将数据包内容skb->data拷贝到用户空间

    4.4 系统调用close
    内核最终会调用packet_release
    static int packet_release(struct socket *sock){
        struct sock *sk = sock->sk;
        struct packet_sock *po;
        if (!sk)  return 0;
        po = pkt_sk(sk);
        write_lock_bh(&packet_sklist_lock);
        sk_del_node_init(sk);
        write_unlock_bh(&packet_sklist_lock);
        // Unhook packet receive handler.
        if (po->running) {
            dev_remove_pack(&po->prot_hook);   //就是这句!!把packet_type从链表中删除
            po->running = 0;
            po->num = 0;
            __sock_put(sk);
        }
        packet_flush_mclist(sk);
         // Now the socket is dead. No more input will appear.
        sock_orphan(sk);
        sock->sk = NULL;
        /* Purge queues */
        skb_queue_purge(&sk->sk_receive_queue);
        sk_refcnt_debug_release(sk);
        sock_put(sk);
        return 0;
    }

    ----------------------------------------------------------------------------------------------


    搜一下内核源代码,二层协议还真是多。。。
    drivers/net/wan/hdlc.c: dev_add_pack(&hdlc_packet_type);  //ETH_P_HDLC    hdlc_rcv
    drivers/net/wan/lapbether.c:
                dev_add_pack(&lapbeth_packet_type);         //ETH_P_DEC       lapbeth_rcv
    drivers/net/wan/syncppp.c:
                dev_add_pack(&sppp_packet_type);            //ETH_P_WAN_PPP   sppp_rcv
    drivers/net/bonding/bond_alb.c:  dev_add_pack(pk_type); //ETH_P_ARP       rlb_arp_recv
    drivers/net/bonding/bond_main.c:dev_add_pack(pk_type);  //PKT_TYPE_LACPDU bond_3ad_lacpdu_recv
    drivers/net/bonding/bond_main.c:dev_add_pack(pt);       //ETH_P_ARP       bond_arp_rcv
    drivers/net/pppoe.c: dev_add_pack(&pppoes_ptype);       //ETH_P_PPP_SES   pppoe_rcv
    drivers/net/pppoe.c: dev_add_pack(&pppoed_ptype);       //ETH_P_PPP_DISC  pppoe_disc_rcv
    drivers/net/hamradio/bpqether.c:
                        dev_add_pack(&bpq_packet_type);     //ETH_P_BPQ       bpq_rcv
    net/ipv4/af_inet.c:  dev_add_pack(&ip_packet_type);     //ETH_P_IP       ip_rcv
    net/ipv4/arp.c:    dev_add_pack(&arp_packet_type);      //ETH_P_ARP       arp_rcv
    net/ipv4/ipconfig.c:  dev_add_pack(&rarp_packet_type);  //ETH_P_RARP      ic_rarp_recv
    net/ipv4/ipconfig.c:  dev_add_pack(&bootp_packet_type); //ETH_P_IP        ic_bootp_recv
    net/llc/llc_core.c: dev_add_pack(&llc_packet_type);     //ETH_P_802_2     llc_rcv
    net/llc/llc_core.c: dev_add_pack(&llc_tr_packet_type);  //ETH_P_TR_802_2  llc_rcv
    net/x25/af_x25.c:  dev_add_pack(&x25_packet_type);    //ETH_P_X25      x25_lapb_receive_frame
    net/8021q/vlan.c:  dev_add_pack(&vlan_packet_type);     //ETH_P_8021Q     vlan_skb_recv

    这些不同协议的packet_type,有些是linux系统启动时挂上去的
    比如处理ip协议的pakcet_type,就是在 inet_init()时挂上去的
    还有些驱动模块加载的时候才加上去的。

    转载自http://blog.chinaunix.net/uid-24148050-id-1994898.html

  • 相关阅读:
    day01--计算机硬件基础笔记
    22 Jun 18 Django,ORM
    21 Jun 18 Django,ORM
    20 Jun 18 复习, mysql
    20 Jun 18 Django,ORM
    19 Jun 18 复习, 正则表达式
    19 Jun 18 Django
    15 Jun 18 复习, shutil模块
    15 Jun 18 Django
    14 Jun 18 复习, form表单
  • 原文地址:https://www.cnblogs.com/CasonChan/p/5166250.html
Copyright © 2011-2022 走看看