天天看點

網絡資料包收發流程(四):協定棧之packet_type

進入函數netif_receive_skb()後,skb正式開始協定棧之旅。

先上圖,協定棧大緻過程如下所示:

網絡資料包收發流程(四):協定棧之packet_type

跟OSI七層模型不同,linux根據包結構對網絡進行分層。

比如,arp頭和ip頭都是緊跟在以太網頭後面的,是以在linux協定棧中arp和ip地位相同(如上圖)

但是在OSI七層模型中,arp屬于鍊路層,ip屬于網絡層.....

這裡就不死摳概念,我們就說arp,ip都屬于第二層。下面是網絡第二層的處理流程

<b>一、相關資料結構</b>

核心處理網絡第二層,有下面2個重要list_head變量 (檔案linux_2_6_24/net/core/dev.c)

list_head 連結清單上挂了很多packet_type資料結構

static struct list_head <b>ptype_base</b>[16] __read_mostly;   /* 16 way hashed list */

static struct list_head <b>ptype_all</b> __read_mostly;        /* Taps */

struct packet_type {

    __be16 <b>type</b>;                /* This is really htons(ether_type).*/

    struct net_device   *dev;   /* NULL is wildcarded here       */

    int     (*<b>func</b>) (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;

};

<b>type</b> 成員儲存了二層協定類型,ETH_P_IP、ETH_P_ARP等等

<b>func </b>成員就是鈎子函數了,如 ip_rcv()、arp_rcv()等等

<b>二、操作packet_type的API</b>

//把packet_type結構挂在與type對應的list_head上面

void <b>dev_add_pack</b>(struct packet_type *pt){

    int hash;

    spin_lock_bh(&amp;ptype_lock);

    if (pt-&gt;type == htons(ETH_P_ALL))        //type為ETH_P_ALL時,挂在ptype_all上面

        list_add_rcu(&amp;pt-&gt;list, &amp;ptype_all);

    else {

        hash = ntohs(pt-&gt;type) &amp; 15;         //否則,挂在ptype_base[type&amp;15]上面

        list_add_rcu(&amp;pt-&gt;list, &amp;ptype_base[hash]);

    }

    spin_unlock_bh(&amp;ptype_lock);

}

//把packet_type從list_head上删除

void <b>dev_remove_pack</b>(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;

    if (pt-&gt;type == htons(ETH_P_ALL))

        head = &amp;ptype_all;                        //找到連結清單頭

    else

        head = &amp;ptype_base[ntohs(pt-&gt;type) &amp; 15]; //

    list_for_each_entry(pt1, head, list) {

        if (pt == pt1) {

            list_del_rcu(&amp;pt-&gt;list);

            goto out;

        }

    printk(KERN_WARNING "dev_remove_pack: %p not found.\n", pt);

out:

<b>三、進入二層協定處理函數</b>

int netif_receive_skb(struct sk_buff *skb)

{

   //略去一些代碼

    rcu_read_lock();

    //第一步:先處理 ptype_all 上所有的 packet_type-&gt;func()           

    //所有包都會調func,對性能影響嚴重!核心預設沒挂任何鈎子函數

    list_for_each_entry_rcu(ptype, <b>&amp;ptype_all</b>, list) {  //周遊ptye_all連結清單

        if (!ptype-&gt;dev || ptype-&gt;dev == skb-&gt;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-&gt;pkt_type 為 PACKET_HOST、PACKET_OTHERHOST

    skb = <b>handle_bridge</b>(skb, &amp;pt_prev, &amp;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-&gt;pkt_type 為 PACKET_BROADCAST、PACKET_MULTICAST、PACKET_HOST

    skb = <b>handle_macvlan</b>(skb, &amp;pt_prev, &amp;ret, orig_dev);

    if (!skb)  goto out;

    //第四步:最後 type = skb-&gt;protocol; &amp;ptype_base[ntohs(type)&amp;15]

    //處理ptype_base[ntohs(type)&amp;15]上的所有的 packet_type-&gt;func()

    //根據第二層不同協定來進入不同的鈎子函數,重要的有:<b>ip_rcv() arp_rcv()</b>

    type = skb-&gt;protocol;

    list_for_each_entry_rcu(ptype, <b>&amp;ptype_base[ntohs(type)&amp;15]</b>, list) {

        if (ptype-&gt;type == type &amp;&amp;                      //周遊包type所對應的連結清單

            (!ptype-&gt;dev || ptype-&gt;dev == skb-&gt;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-&gt;func(skb, skb-&gt;dev, pt_prev, orig_dev);

    } else {               //下面就是資料包從協定棧傳回來了

        kfree_skb(skb);    //注意這句,若skb沒進入socket的接收隊列,則在這裡被釋放

        ret = NET_RX_DROP; //若skb進入接收隊列,則系統調用取包時skb釋放,這裡skb引用數減一而已

    rcu_read_unlock();

    return ret;

int deliver_skb(struct sk_buff *skb,struct packet_type *pt_prev, struct net_device *orig_dev){

    <b>atomic_inc(&amp;skb-&gt;users);</b> <b>//這句不容忽視,與後面流程的kfree_skb()相呼應</b>

    return <b>pt_prev-&gt;func(skb, skb-&gt;dev, pt_prev, orig_dev)</b>;//調函數ip_rcv() arp_rcv()等

這裡隻是将大緻流程,arp_rcv(), ip_rcv() 什麼的具體流程,以後再寫。

<b>四、網絡抓包tcpdump</b>

tcpdump也是在二層抓包的,用的是libpcap庫,它的基本原理是

1.先建立socket,核心dev_add_packet()挂上自己的鈎子函數

2.然後在鈎子函數中,把skb放到自己的接收隊列中,

3.接着系統調用recv取出skb來,把資料包skb-&gt;data拷貝到使用者空間

4.最後關閉socket,核心dev_remove_packet()删除自己的鈎子函數

下面是一些重要的資料結構,用到的鈎子函數都在這裡初始化好了

static const struct proto_ops packet_ops = {

    .family =    PF_PACKET,

    .owner =    THIS_MODULE,

    .release =    <b>packet_release</b>,    //關閉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 =    <b>packet_recvmsg,   </b>//socket收包的時候調這個

    .mmap =        packet_mmap,

    .sendpage =    sock_no_sendpage,

static struct net_proto_family packet_family_ops = {

    .create =    <b>packet_create,</b>     //建立socket的時候調這個

    .owner    =    THIS_MODULE,

至于系統調用 socket、recv、close是如何調到這些核心鈎子函數的,以後再講。這裡隻關注packet_type

<b></b>

4.1 系統調用socket

libpcap系統調用socket,核心最終調用 packet_create

static int <b>packet_create</b>(struct net *net, struct socket *sock, int protocol){

    po-&gt;prot_hook.func = <b>packet_rcv;</b>   //初始化鈎子函數指針

    po-&gt;prot_hook.af_packet_priv = sk;

    if (protocol) {

        po-&gt;prot_hook.type = protocol;  //類型是系統調用socket形參指定的

       <b> dev_add_pack(&amp;po-&gt;prot_hook);</b>//關鍵!!

        sock_hold(sk);

        po-&gt;running = 1;

    return(0);

<b>4.2 鈎子函數 packet_rcv 将skb放入到接收隊列</b>

檔案 linux_2_6_24/net/packet/af_packet.c

簡單來說,packet_rcv中,skb越過了整個協定棧,直接進入隊列

<b>4.3 系統調用recv</b>

系統調用recv、read、recvmsg,核心最終會調用<b>packet_recvmsg</b>

從接收隊列中取出skb,将資料包内容skb-&gt;data拷貝到使用者空間

<b>4.4 系統調用close</b>

核心最終會調用packet_release

static int <b>packet_release</b>(struct socket *sock){

    struct sock *sk = sock-&gt;sk;

    struct packet_sock *po;

    if (!sk)  return 0;

    po = pkt_sk(sk);

    write_lock_bh(&amp;packet_sklist_lock);

    sk_del_node_init(sk);

    write_unlock_bh(&amp;packet_sklist_lock);

    // Unhook packet receive handler.

    if (po-&gt;running) {

        <b>dev_remove_pack(&amp;po-&gt;prot_hook)</b>;   //就是這句!!把packet_type從連結清單中删除

        po-&gt;running = 0;

        po-&gt;num = 0;

        __sock_put(sk);

    packet_flush_mclist(sk);

     // Now the socket is dead. No more input will appear.

    sock_orphan(sk);

    sock-&gt;sk = NULL;

    /* Purge queues */

    skb_queue_purge(&amp;sk-&gt;sk_receive_queue);

    sk_refcnt_debug_release(sk);

    sock_put(sk);

    return 0;

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

搜一下核心源代碼,二層協定還真是多。。。

drivers/net/wan/hdlc.c: dev_add_pack(&amp;hdlc_packet_type);  //ETH_P_HDLC    hdlc_rcv

drivers/net/wan/lapbether.c:

            dev_add_pack(&amp;lapbeth_packet_type);         //ETH_P_DEC       lapbeth_rcv

drivers/net/wan/syncppp.c:

            dev_add_pack(&amp;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(&amp;pppoes_ptype);       //ETH_P_PPP_SES   pppoe_rcv

drivers/net/pppoe.c: dev_add_pack(&amp;pppoed_ptype);       //ETH_P_PPP_DISC  pppoe_disc_rcv

drivers/net/hamradio/bpqether.c:

                    dev_add_pack(&amp;bpq_packet_type);     //ETH_P_BPQ       bpq_rcv

net/ipv4/af_inet.c:  dev_add_pack(&amp;ip_packet_type);     //<b>ETH_P_IP</b>       <b>ip_rcv</b>

net/ipv4/arp.c:    dev_add_pack(&amp;arp_packet_type);      //ETH_P_ARP       arp_rcv

net/ipv4/ipconfig.c:  dev_add_pack(&amp;rarp_packet_type);  //ETH_P_RARP      ic_rarp_recv

net/ipv4/ipconfig.c:  dev_add_pack(&amp;bootp_packet_type); //ETH_P_IP        ic_bootp_recv

net/llc/llc_core.c: dev_add_pack(&amp;llc_packet_type);     //ETH_P_802_2     llc_rcv

net/llc/llc_core.c: dev_add_pack(&amp;llc_tr_packet_type);  //ETH_P_TR_802_2  llc_rcv

net/x25/af_x25.c:  dev_add_pack(&amp;x25_packet_type);    //ETH_P_X25      x25_lapb_receive_frame

net/8021q/vlan.c:  dev_add_pack(&amp;vlan_packet_type);     //ETH_P_8021Q     vlan_skb_recv

這些不同協定的packet_type,有些是linux系統啟動時挂上去的

比如處理ip協定的pakcet_type,就是在 inet_init()時挂上去的

還有些驅動子產品加載的時候才加上去的。

繼續閱讀