鏈路層輸入報文的處理


中斷服務程序接收報文后都交由__netif_receive_skb處理:根據協議將報文向上傳輸;

packet_type 結構為網絡層輸入接口;其支持多種協議,每個協議族都會實現一個接收報文的的實例;此結構在鏈路層和網絡層之間起到了橋梁的作用。

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 *);
    bool            (*id_match)(struct packet_type *ptype,
                        struct sock *sk);
    void            *af_packet_priv;
    struct list_head    list;
};
View Code

其中type為以太網或者其他鏈路層承載的網絡層協議號,dev接收指定的網絡設備輸入報文,為NULL 表接收所有設備的報文;

int  (*func) (struct sk_buff *, struct net_device *, struct packet_type *,struct net_device *);為協議入口的接收函數;第二個參數當前處理該報文的網絡設備,第四個參數為報文的原始輸入網絡設備

note:一般處理報文的設備和報文的原始接收設備是一個,但是在聚合口情況下:輸入設備為物理設備,實際處理報文的為虛擬網絡設備。

static struct packet_type ip_packet_type __read_mostly = {
    .type = cpu_to_be16(ETH_P_IP),
    .func = ip_rcv,
};
static int __init inet_init(void)
{
.........................
dev_add_pack(&ip_packet_type);
..............
}

/*******************************************************************************

        Protocol management and registration routines

*******************************************************************************/

/*
 *    Add a protocol ID to the list. Now that the input handler is
 *    smarter we can dispense with all the messy stuff that used to be
 *    here.
 *
 *    BEWARE!!! Protocol handlers, mangling input packets,
 *    MUST BE last in hash buckets and checking protocol handlers
 *    MUST start from promiscuous ptype_all chain in net_bh.
 *    It is true now, do not change it.
 *    Explanation follows: if protocol handler, mangling packet, will
 *    be the first on list, it is not able to sense, that packet
 *    is cloned and should be copied-on-write, so that it will
 *    change it and subsequent readers will get broken packet.
 *                            --ANK (980803)
 */

static inline struct list_head *ptype_head(const struct packet_type *pt)
{
    if (pt->type == htons(ETH_P_ALL))
        return pt->dev ? &pt->dev->ptype_all : &ptype_all;
    else
        return pt->dev ? &pt->dev->ptype_specific :
                 &ptype_base[ntohs(pt->type) & PTYPE_HASH_MASK];
}
/**
 *    dev_add_pack - add packet handler
 *    @pt: packet type declaration
 *
 *    Add a protocol handler to the networking stack. The passed &packet_type
 *    is linked into kernel lists and may not be freed until it has been
 *    removed from the kernel lists.
 *
 *    This call does not sleep therefore it can not
 *    guarantee all CPU's that are in middle of receiving packets
 *    will see the new packet type (until the next received packet).
 */

void dev_add_pack(struct packet_type *pt)
{
    struct list_head *head = ptype_head(pt);

    spin_lock(&ptype_lock);
    list_add_rcu(&pt->list, head);
    spin_unlock(&ptype_lock);
}
View Code
/*
 *    The list of packet types we will receive (as opposed to discard)
 *    and the routines to invoke.
 *
 *    Why 16. Because with 16 the only overlap we get on a hash of the
 *    low nibble of the protocol value is RARP/SNAP/X.25.
 *
 *      NOTE:  That is no longer true with the addition of VLAN tags.  Not
 *             sure which should go first, but I bet it won't make much
 *             difference if we are running VLANs.  The good news is that
 *             this protocol won't be in the list unless compiled in, so
 *             the average user (w/out VLANs) will not be adversely affected.
 *             --BLG
 *
 *        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)
View Code

 

dev_add_pack()是將一個 協議類型結構鏈入某一個鏈表, 當協議類型為ETH_P_ALL 時,它將被鏈入 ptype_all 鏈表,這個鏈表是用於 sniffer 這樣一些程序的,它接收所有 NIC 收到的包。還有一個是 HASH 鏈表 ptype_base,用於各種協議,它是一個 PTYPE_HASH_SIZE 個元素的數組, dev_add_pack()會根據協議類型將這個 packet_type 鏈入相應的 HASH 鏈表中

 

 

2、__netif_receive_skb_core 分析

static int __netif_receive_skb_core(struct sk_buff *skb, bool pfmemalloc)
{
    struct packet_type *ptype, *pt_prev;
    rx_handler_func_t *rx_handler;
    struct net_device *orig_dev;
    bool deliver_exact = false;
    int ret = NET_RX_DROP;
    __be16 type;

    net_timestamp_check(!netdev_tstamp_prequeue, skb);

    trace_netif_receive_skb(skb);

    orig_dev = skb->dev;

    skb_reset_network_header(skb);
    if (!skb_transport_header_was_set(skb))
        skb_reset_transport_header(skb);
    skb_reset_mac_len(skb);

    pt_prev = NULL;

another_round:
    skb->skb_iif = skb->dev->ifindex;

    __this_cpu_inc(softnet_data.processed);/* 增加本cpu處理過的數據包個數 */  
     /*解析8021 q 協議*/
    if (skb->protocol == cpu_to_be16(ETH_P_8021Q) ||
        skb->protocol == cpu_to_be16(ETH_P_8021AD)) {
        skb = skb_vlan_untag(skb);
        if (unlikely(!skb))
            goto out;
    }
/* 入口流量控制 */  
#ifdef CONFIG_NET_CLS_ACT
    if (skb->tc_verd & TC_NCLS) {
        skb->tc_verd = CLR_TC_NCLS(skb->tc_verd);
        goto ncls;
    }
#endif

    if (pfmemalloc)
        goto skip_taps;
 /*    
    po->prot_hook.func = packet_rcv;
    if (sock->type == SOCK_PACKET)
        po->prot_hook.func = packet_rcv_spkt;
    */
    //在net_dev_init中初始化
        /*注意這里並沒有要求ptype->type == type,所以接收到的包只要有注冊ETH_P_ALL協議
        ,所有的包都會走到deliver_skb
           遍歷嗅探器(ETH_P_ALL)鏈表ptype_all。對於每個注冊的sniffer, 
     * 調用它的處理函數packet_type->func(),例如tcpdump。 
     */  
    list_for_each_entry_rcu(ptype, &ptype_all, list) {
        if (pt_prev)
            ret = deliver_skb(skb, pt_prev, orig_dev);
        pt_prev = ptype;
    }
/* //設備上注冊ptype_all,做相應的處理,更加精細的控制 **/
    list_for_each_entry_rcu(ptype, &skb->dev->ptype_all, list) {
        if (pt_prev)
            ret = deliver_skb(skb, pt_prev, orig_dev);
        pt_prev = ptype;
    }

skip_taps:
#ifdef CONFIG_NET_INGRESS
    if (static_key_false(&ingress_needed)) {
        skb = sch_handle_ingress(skb, &pt_prev, &ret, orig_dev);
        if (!skb)
            goto out;

        if (nf_ingress(skb, &pt_prev, &ret, orig_dev) < 0)
            goto out;
    }
#endif
#ifdef CONFIG_NET_CLS_ACT
    skb->tc_verd = 0;
ncls:
#endif
    if (pfmemalloc && !skb_pfmemalloc_protocol(skb))
        goto drop;
/*處理8021q**/
    if (skb_vlan_tag_present(skb)) {
        if (pt_prev) {
            ret = deliver_skb(skb, pt_prev, orig_dev);
            pt_prev = NULL;
        }
        if (vlan_do_receive(&skb))
            goto another_round;
        else if (unlikely(!skb))
            goto out;
    }
/*內核提供了netdev_rx_handler_register接口函數向接口注冊rx_handler 
比如為網橋下的接口注冊br_handle_frame函數 
為bonding接口注冊bond_handle_frame函數
網橋的處理包括向上層提交和轉發 
發往本地的報文會修改入接口為網橋虛接口如br0 
調用netif_receive_skb重新進入協議棧處理 
對於上層協議棧見到的只有橋虛接口 
需要轉發的報文根據轉發表進行單播或廣播發送 */
//設備rx_handler,加入OVS時會注冊為OVS的入口函數??
    rx_handler = rcu_dereference(skb->dev->rx_handler);
    if (rx_handler) {//執行rx_handler處理,例如進入OVS,OVS不支持報頭中攜帶vlan的報文??
        if (pt_prev) {
            ret = deliver_skb(skb, pt_prev, orig_dev);
            pt_prev = NULL;
        }
        switch (rx_handler(&skb)) {
        case RX_HANDLER_CONSUMED:
            ret = NET_RX_SUCCESS;//數據包已成功接收,不需要再處理
            goto out;
        case RX_HANDLER_ANOTHER://當rx_handler改變過skb->dev時,在接收回路中再一次處理。
            goto another_round;
        case RX_HANDLER_EXACT://不使用匹配的方式,精確傳遞。
            deliver_exact = true;
        case RX_HANDLER_PASS://忽略rx_handler的影響。
            break;
        default:
            BUG();
        }
    }

    if (unlikely(skb_vlan_tag_present(skb))) {
        if (skb_vlan_tag_get_id(skb))
            skb->pkt_type = PACKET_OTHERHOST;
        /* Note: we might in the future use prio bits
         * and set skb->priority like in vlan_do_receive()
         * For the time being, just ignore Priority Code Point
         */
        skb->vlan_tci = 0;
    }

   /*
    最后 type = skb->protocol; &ptype_base[ntohs(type)&15]
        //處理ptype_base[ntohs(type)&15]上的所有的 packet_type->func()
        //根據第二層不同協議來進入不同的鈎子函數,重要的有:ip_rcv() arp_rcv()
        ip_recv見inet_init里面的dev_add_pack(&ip_packet_type);
    */
    type = skb->protocol; //skb->protocol用來表示此SKB包含的數據所支持的L3層協議是什么. 如ox0800代表IP,0x0806代表ARP 在驅動程序中已經獲取了該值

    /* deliver only exact match when indicated */
    if (likely(!deliver_exact)) {//根據全局定義的協議處理報文??//如果前面判段是精確發送方式,那么把nulll_or_dev設置成精確傳送的設備
        deliver_ptype_list_skb(skb, &pt_prev, orig_dev, type,
                       &ptype_base[ntohs(type) &
                           PTYPE_HASH_MASK]);
    }

    deliver_ptype_list_skb(skb, &pt_prev, orig_dev, type,
                   &orig_dev->ptype_specific);?//根據設備上注冊的協議進行處理??

    if (unlikely(skb->dev != orig_dev)) {//如果設備發生變化,那么還需要針對新設備的注冊協議進行處理??
        deliver_ptype_list_skb(skb, &pt_prev, orig_dev, type,
                       &skb->dev->ptype_specific);
    }


/***'
1、vlan報文的處理,主要是循環把vlan頭剝掉,如果qinqxxxxx場景,兩個vlan都會被剝掉;
2、交給rx_handler處理,例如OVS、linux bridge等;
3、ptype_all處理,例如抓包程序、raw socket等;
4、ptype_base處理,交給協議棧處理,例如ip、arp、rarp等;
*/
    if (pt_prev) {
        if (unlikely(skb_orphan_frags(skb, GFP_ATOMIC)))
            goto drop;
        else
            ret = pt_prev->func(skb, skb->dev, pt_prev, orig_dev);?//調用協議處理ip_rcv()   arp_rcv()
    } else {
drop:
        if (!deliver_exact)
            atomic_long_inc(&skb->dev->rx_dropped);
        else
            atomic_long_inc(&skb->dev->rx_nohandler);
        kfree_skb(skb);
        /* Jamal, now you will not able to escape explaining
         * me how you were going to use this. :-)
         */
        ret = NET_RX_DROP;
    }

/**
內核提供了netdev_rx_handler_register接口函數向接口注冊rx_handler 
比如為網橋下的接口注冊br_handle_frame函數 
為bonding接口注冊bond_handle_frame函數 
這相對於老式的網橋處理更靈活 
有了這個機制也可以在模塊中自行注冊處理函數 
? 
網橋的處理包括向上層提交和轉發 
發往本地的報文會修改入接口為網橋虛接口如br0 
調用netif_receive_skb重新進入協議棧處理 
對於上層協議棧見到的只有橋虛接口 
需要轉發的報文根據轉發表進行單播或廣播發送 
netfilter在網橋的處理路徑中從br_handle_frame到br_dev_queue_push_xmit設置了5個hook點 
根據nf_call_iptables的配置還會經過NFPROTO_IPV4的hook點等 
內核注冊的由br_nf_ops數組中定義 
可在模塊中自行向NFPROTO_BRIDGE族的幾個hook點注冊函數 
ebtables在netfilter框架NFPROTO_BRIDGE中實現了橋二層過濾機制 
配合應用程序ebtables可在橋下自定義相關規則 
? 
處理完接口上的rx_handler后便根據具體的3層協議類型在ptype_base中尋找處理函數 
比如ETH_P_IP則調用ip_rcv,ETH_P_IPV6則調用ipv6_rcv 
這些函數都由dev_add_pack注冊 
可在模塊中自定義協議類型處理函數 
如果重復定義相同協議的處理函數則要注意報文的修改對后續流程的影響 
? 
IP報文進入ip_rcv后進行簡單的檢查便進入路由選擇 
根據路由查找結果調用ip_local_deliver向上層提交或調用ip_forward進行轉發 
向上層提交前會進行IP分片的重組 
在ip_local_deliver_finish中會根據報文中4層協議類型調用對應的處理函數 
處理函數由接口函數inet_add_protocol注冊 
針對TCP或UDP進行不同處理,最后喚醒應用程序接收數據 
向外發送和轉發數據經由ip_output函數 
包括IP的分片,ARP學習,MAC地址的修改或填充等 
netfilter在從ip_rcv到ip_output間設置了5個hook點 
向各個點的鏈表中注冊處理函數或使用iptables工具自定義規則 
實現報文處理的行為控制 
*/
out:
    return ret;
}

 

net/core/dev.c
static inline int deliver_skb(struct sk_buff *skb,
                  struct packet_type *pt_prev,
                   struct net_device *orig_dev)
 {
//在接收端要將分片的數據包重組,這里判斷是否因缺少分片包而導致一個skb成為一個孤兒skb。顯然是不太可能是孤兒進
//程的,所以這里使用了unlikely知道gcc編譯器優化程序,以減少指令流水被打斷的概率。
    if (unlikely(skb_orphan_frags(skb, GFP_ATOMIC)))   
         return -ENOMEM;
    atomic_inc(&skb->users);  //增加skb的使用者計數器
     return pt_prev->func(skb, skb->dev, pt_prev, orig_dev);  
 }

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM