在數(shù)據(jù)包接收過程的那篇筆記中可以知道,在數(shù)據(jù)包的處理函數(shù)netif_receive_skb中,會先看ptype_all中是否有注冊的協(xié)議,如果有,則調(diào)用相應(yīng)的處理函數(shù),然后再到ptype_base中,找到合適的協(xié)議,將skb發(fā)送到相關(guān)協(xié)議的處理函數(shù).比如ip協(xié)議(ip_rcv)或者arp(arp_rcv)等等.此篇筆記講的是有關(guān)ptype_all和ptype_base的相關(guān)知識點.
ptype_base和ptype_all在內(nèi)核中存儲的情況如下圖:

可以看到,ptype_base為一個hash表,而ptype_all為一個雙向鏈表.每一個里面注冊的協(xié)議都用一個struct packet_type表示.
struct packet_type { unsigned short type; /*協(xié)議類型*/ 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參數(shù),此參數(shù)表明了協(xié)議只處理來自dev指向device的數(shù)據(jù),當(dāng)dev=NULL時,表示該協(xié)議處理來自所有device的數(shù)據(jù).這樣,當(dāng)注冊自己的協(xié)議時,就可以指定自己想要監(jiān)聽或者接收的device. 其中注冊和注銷協(xié)議的函數(shù)為: dev_add_pack(...)和dev_remove_pack(...) 這兩個函數(shù)很簡單,分別如下: 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); } 此函數(shù)判斷協(xié)議類型,然后加到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); } 此函數(shù)也很簡單,只是把協(xié)議從相關(guān)的鏈表中移除. 下面以ip協(xié)議為例子來看看相關(guān)的實現(xiàn): ip協(xié)議結(jié)構(gòu)體的定義如下: static struct packet_type ip_packet_type = { __constant_htons(ETH_P_IP), NULL, /* All devices */ ip_rcv, (void*)1, NULL, }; 當(dāng)ipv4協(xié)議棧初始化時,會調(diào)用ip_init.之后,所有協(xié)議類型為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 } 這樣在系統(tǒng)啟動之后,ip協(xié)議便被注冊到ptype_base鏈表中,相應(yīng)的處理函數(shù)為ip_rcv. arp協(xié)議和其他類型的協(xié)議(在ptype_base或者ptype_all中的)的執(zhí)行過程同理. 本人初學(xué)網(wǎng)絡(luò),水平很菜,如有錯誤,希望看到的朋友們及時指出,不勝感激. ps1:記得剛來實驗室的時候,做的截包模塊的第一種方法是用的netfilter,第二種方法主要就是用到的這塊知識.現(xiàn)在總結(jié)起來,覺得還算簡單,當(dāng)初卻用了很長時間,想想,真是難者不會,會者不難啊.今天看書的時候,好像又發(fā)現(xiàn)了另外一種方法可以實現(xiàn)我的要求,記錄在ps2上. ps2:在數(shù)據(jù)鏈路層截包的另一種方法:用PF_PACKET socket type.linux可以用此類型套節(jié)字直接從鏈路層截獲或者注入數(shù)據(jù).發(fā)送數(shù)據(jù)時,直接發(fā)送到dev_queue_xmit.而接收函數(shù)時,可以在數(shù)據(jù)包通過路由之前截獲到.如tcpdump和Ethereal都是用到了此套接字.那么總結(jié)起來可以看出,截獲數(shù)據(jù)包至少可以有三種方法實現(xiàn),第一種的netfilter是在協(xié)議棧中截獲數(shù)據(jù)包,而利用ptype_all或者ptype_base和后面這種套節(jié)字的方法是在鏈路層截獲數(shù)據(jù)包.
//當(dāng)網(wǎng)絡(luò)設(shè)備收到網(wǎng)絡(luò)數(shù)據(jù)包時,最終會在軟件中斷環(huán)境里調(diào)用此函數(shù)
- int netif_receive_skb(struct sk_buff *skb)
- {
-
-
- 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;
- }
- }
-
- 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;
- type = skb->protocol;
- list_for_each_entry_rcu(ptype, &ptype_base[ntohs(type)&15], 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;
- }
- }
- }
int netif_receive_skb(struct sk_buff *skb)
{
//ptype_all 用于sniffer這樣的程序
// 發(fā)送一份拷貝給這些注冊的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;
}
}
// 內(nèi)核編譯開Bridge_config,則將該數(shù)據(jù)包讓網(wǎng)橋函數(shù)來處理,否則handle_bridge定義為空操作,
// 返回skb,讓協(xié)議棧來處理上層協(xié)議。
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;
//對該數(shù)據(jù)包轉(zhuǎn)達(dá)到其他L3協(xié)議的處理函數(shù)
type = skb->protocol;
list_for_each_entry_rcu(ptype, &ptype_base[ntohs(type)&15], 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()的主要作用體現(xiàn)在兩個遍歷鏈表的操作中,其中之一為遍歷ptype_all 鏈,這些為注冊到內(nèi)核的一些 sniffer,將上傳給這些sniffer,另一個就是遍歷 ptype_base,這個就是具體的協(xié)議類型。當(dāng) eth1 接收到一個IP數(shù)據(jù)包時,它首先分別發(fā)送一份副本給每個 ptype_all 鏈表中的 packet_type,它們都由 package_rcv 處理,然后再根據(jù)HASH 值,在遍歷另一個HASH 表時,發(fā)送一份給類型為 ETH_P_IP 的類型,它由 ip_rcv處理。如果這個鏈中還注冊有其它 IP層的協(xié)議,它也會同時發(fā)送一個副本給它。
|