NETDEV 協議 四

內核版本:2.6.34

NetFilter在2.4.x內核中引入,成爲linux平臺下進行網絡應用的主要擴展,不僅包括防火牆的實現,還包括報文的處理(如報文加密、報文分類統計等)等。

NetFilter數據結構        勾子struct nf_hook_ops[net\filter\core.c]
 
struct nf_hook_ops {
	struct list_head list;
	/* User fills in from here down. */
	nf_hookfn *hook;
	struct module *owner;
	u_int8_t pf;
	unsigned int hooknum;
	/* Hooks are ordered in ascending priority. */
	int priority;
};
        成員list用於鏈入全局勾子數組nf_hooks中,它一定在第一位,保證&nf_hook_ops->list的值與&nf_hook_ops相同,稍後在使用時會用到這一技巧;
成員hook即用戶定義的勾子函數;owner表示註冊這個勾子函數的模塊,因爲netfilter是內核空間的,所以一般爲模塊來完成勾子函數註冊;pf與hooknum一起索引到特定協議特定編號的勾子函數隊列,用於索引nf_hooks;
priority決定在同一隊列(pf與hooknum相同)的順序,priority越小則排列越靠前。
        struct nf_hook_ops只是存儲勾子的數據結構,而真正存儲這些勾子供協議棧調用的是nf_hooks,從定義可以看出,它其實就是二維數組的鏈表。
        struct list_head nf_hooks[NFPROTO_NUMPROTO][NF_MAX_HOOKS]; [net\filter\core.c]
        其中NFPROTO_NUMPROTO表示勾子關聯的協議,可取值:
enum {
	NFPROTO_UNSPEC =  0,
	NFPROTO_IPV4   =  2,
	NFPROTO_ARP    =  3,
	NFPROTO_BRIDGE =  7,
	NFPROTO_IPV6   = 10,
	NFPROTO_DECNET = 12,
	NFPROTO_NUMPROTO,
};
        NF_MAX_HOOKS表示勾子應用的位置,可選值在每個協議模塊內部定義,這些值代表了勾子函數在協議流程中應用的位置(稍後會以bridge爲例詳細說明),大致上都有以下值:
  
	NF_XXX_PRE_ROUTING,
	NF_XXX_LOCAL_IN,
	NF_XXX_FORWARD,
	NF_XXX_LOCAL_OUT,
	NF_XXX_POST_ROUTING,
	NF_XXX_NUMHOOKS

NetFilter註冊
        在瞭解了nf_hook_ops和nf_hooks後,來看下如何操作nf_hooks中的元素。
        nf_register_hook()將nf_hook_ops註冊到nf_hooks中:
int nf_register_hook(struct nf_hook_ops *reg)
{
	struct nf_hook_ops *elem;
	int err;


	err = mutex_lock_interruptible(&nf_hook_mutex);
	if (err < 0)
		return err;
	list_for_each_entry(elem, &nf_hooks[reg->pf][reg->hooknum], list) {
		if (reg->priority < elem->priority)
			break;
	}
	list_add_rcu(®->list, elem->list.prev);
	mutex_unlock(&nf_hook_mutex);
	return 0;
}
        這個函數很簡單,從指定pf&hooknum的nf_hooks隊列遍歷,按priority從小到大順序,將reg插入相應位置,完成勾子函數的註冊。

        nf_unregister_hook()將nf_hook_ops從nf_hooks中註銷掉:
  
void nf_unregister_hook(struct nf_hook_ops *reg)
{
	mutex_lock(&nf_hook_mutex);
	list_del_rcu(®->list);
	mutex_unlock(&nf_hook_mutex);
	synchronize_net();
}
        這個函數更簡單,從nf_hooks中刪除reg。
        內核同時還提供了nf_register_hooks()和nf_unregister_hooks(),將reg重複註冊n次或將reg從nf_hooks中註銷n次。當勾子函數註冊完成後,nf_hooks的結構如圖所示:
 

NetFilter調用
        在報文在內核協議棧傳遞時,會調用NetFilter模塊對報文進行特定的進濾,這樣的過濾在代碼中隨處可見。
        以上一篇講過的網橋爲例,對於要進行網橋處理的報文,handle_bridge()->br_handle_frame(),如果端口處理於LEARNING或FORWARDING狀態,且報文目的地址正確,則會調用br_handle_frame()進行後續處理,而這個函數調用就是:
NF_HOOK(PF_BRIDGE, NF_BR_PRE_ROUTING, skb, skb->dev, NULL,
br_handle_frame_finish);
NF_HOOK()->NF_HOOK_THRESH()->nf_hook_thresh()->nf_hook_slow():
int nf_hook_slow(u_int8_t pf, unsigned int hook, struct sk_buff *skb,
		 struct net_device *indev,
		 struct net_device *outdev,
		 int (*okfn)(struct sk_buff *),
		 int hook_thresh)
{
	struct list_head *elem;
	unsigned int verdict;
	int ret = 0;


	/* We may already have this, but read-locks nest anyway */
	rcu_read_lock();


	elem = &nf_hooks[pf][hook];
next_hook:
	verdict = nf_iterate(&nf_hooks[pf][hook], skb, hook, indev,
			     outdev, &elem, okfn, hook_thresh);
	if (verdict == NF_ACCEPT || verdict == NF_STOP) {
		ret = 1;
	} else if (verdict == NF_DROP) {
		kfree_skb(skb);
		ret = -EPERM;
	} else if ((verdict & NF_VERDICT_MASK) == NF_QUEUE) {
		if (!nf_queue(skb, elem, pf, hook, indev, outdev, okfn,
			      verdict >> NF_VERDICT_BITS))
			goto next_hook;
	}
	rcu_read_unlock();
	return ret;
}
        nf_hook_slow()從nf_hooks中找出到執行的勾子隊列,依次執行,然後根據返回值決定是否繼續(由nf_iterate()完成)。參數中的pf和hook代表了註冊勾子函數時給的參數PF和HOOKNUM,它們共同決定勾子函數要插入的nf_hook的哪個隊列中。 
        作爲過濾報文的勾子函數的返回值是值得注意的地方,可取值如下:
#define NF_DROP 0
#define NF_ACCEPT 1
#define NF_STOLEN 2
#define NF_QUEUE 3
#define NF_REPEAT 4
#define NF_STOP 5
        先以nf_iterate()函數爲例,elem->hook()表示執行勾子函數,執行結構爲verdict;
unsigned int nf_iterate(……)
{
	unsigned int verdict;


	list_for_each_continue_rcu(*i, head) {
		struct nf_hook_ops *elem = (struct nf_hook_ops *)*i;
		if (hook_thresh > elem->priority)
			continue;
		verdict = elem->hook(hook, skb, indev, outdev, okfn);
		if (verdict != NF_ACCEPT) {
			if (verdict != NF_REPEAT)
				return verdict;
			*i = (*i)->prev;
		}
	}
	return NF_ACCEPT;
}
        根據nf_iterate()返回,會有以下情況:
              1.如果結果爲NF_ACCEPT,表示勾子函數允許報文繼續向下處理,此時應該繼續執行隊列上的下一個勾子函數,因爲這些勾子函數都是對同一類報文在相同位置的過濾,前一個通後,並不能返回,而要所有函數都執行完,結果仍爲NF_ACCEPT時,則可返回它;
              2.如果結果爲NF_REPEAT,表示要重複執行勾子函數一次;所以勾子函數要編寫得當,否則報文會一直執行一個返回NF_REPEAET的勾子函數,當返回值爲NF_REPEAT時,不會返回;
              3.如果爲其它結果,則不必再執行隊列上的其它函數,直接返回它;如NF_STOP表示停止執行隊列上的勾子函數,直接返回;NF_DROP表示丟棄掉報文;NF_STOLEN表示報文不再往上傳遞,與NF_DROP不同的是,它沒有調用kfree_skb()釋放掉skb;NF_QUEUE檢查給定協議(pf)是否有隊列處理函數,有則進行處理,否則丟掉。
        瞭解了這些值再來看nf_hook_slow()中對於nf_iterate()返回值的處理就明瞭了:
if (verdict == NF_ACCEPT || verdict == NF_STOP) {
	ret = 1;
} else if (verdict == NF_DROP) {
	kfree_skb(skb);
	ret = -EPERM;
} else if ((verdict & NF_VERDICT_MASK) == NF_QUEUE) {
	if (!nf_queue(skb, elem, pf, hook, indev, outdev, okfn,
			 verdict >> NF_VERDICT_BITS))
		goto next_hook;
}
        最後還是以bridge來說明下hooks參數的意義,上面已經講過,它決定了在協議流程的何處調用勾子函數;因爲使用NetFilter的目的是在內核態處理報文,而哪些地方可以處理報文只能是內核已經定義好的。一般來說,內核會在報文發送和接收的關鍵位置添加勾子函數處理,查找代碼中NF_HOOK即可知。下面以bridge,爲例,來看下在哪些地方用到了,以及這些值的含義:
  
        NetFilter的存在使得在內核空間對報文進行用戶定義的要求處理變得可能、簡單。一般來說,編寫好struct nf_hook_ops,其中hook/pf/ hook是必給的參數,然後使用nf_register_hook進行註冊就可以了。整個過濾文件可以寫了一個內核模塊,用insmod進行動態加載。
本文只是一個內核網絡協議的實踐的例子,先說明添加的目的,下篇開始具體的實現。

        內核版本:2.6.34;在支持802.1主機上,報文的一般格式: 

        現在需要支持一種新的協議[二層] – BRCM協議,與IP等協議不同,它位於2層,擁有6字節的頭部和4字節的尾部,添加的層次決定了比起添加其它協議要複雜一些,新的報文格式如下,而我們的目的就是要網絡協議棧能正常處理這樣的報文: 

        實際上BRCM是一種交換機的內部協議,用處是讓交換機管理端口能通過BRCM獲取報文來自於交換機的哪個端口,或者指定報文從交換機哪個端口出去;當然,這不是我們關心的內容,我們只需要爲它挑選一個協議號0x8744,其餘內容置0就可以了。因此,brcm頭部會填寫成 88 74 00 00 00 00,brcm尾部會填寫成 00 00。一個新協議的報文內容用wireshark等捕包工具查看的形式如下:
                 [源/目的mac]02 03 04 05 06 07 10 11 12 13 14 15
                 [BRCM報頭]88 74 00 00 00 00
                 [Vlan報頭]81 00 00 01
                 [報文內容]……..

        先從設備的概念來看下添加BRCM協議後的層次圖: 

        eth1代表實際的B4401物理網卡;eth1.X/brcm0.x代表VLAN創建的虛擬網卡,後面的數字X是vlan號;brcm0代表BRCM創建的虛擬網卡,數字0表示測試用;從圖中可以看到,brcm協議的添加是通過添加brcmX虛擬網卡接口實現的。

        如果BRCM協議添加正確,那麼最終的結果應該是:

         在接收報文時,通過虛擬設備BRCM會脫去brcm的6字節的頭部與4字節尾部,當然,協議做的遠遠不只這些,但這是核心。 

        在發送報文時,通過虛擬設備BRCM會添加6字節的頭部與4字節的尾部,當然,協議做的遠遠不只這些,但這是核心。

        要做的工作大致是以下幾項:
               1. 確定brcm_dev的存儲數據結構
               2. 編寫struct ptype_base brcm_packet_type
               3. 添加ioctl調用供用戶空間調用,至少包括brcm_dev的創建和刪除
               4. 添加notifier機制、netlink機制、proc機制
               5. 添加編譯用的Kconfig, Makefile等,並修改Menuconfig
        數據結構是核心,每一步也是一個網絡協議模塊的核心。下一篇開始brcm協議的添加實現。

內核版本:2.6.34

實現思路:
      報文在網絡協議棧中的流動,對於接收來講,是對報文的脫殼的過程,由於報文是已知的輸入,只要逐個解析協議號;對於發送來講,是各層發送函數的嵌套調用,由於沒有已知的輸入,只能按事先設計好的協議進行層層構造。但無論報文怎樣的流動,核心是報文所在設備(skb->dev)的變化,相當於各層之間傳遞的交接棒。
      按照上述思路,brcm協議接收的處理作爲模塊brcm_packet_type加入到ptype_base中就可以了;brcm協議發送的處理則複雜一點,發送的嵌套調用完全是依賴於設備來推動的,因此要有一種新創建的設備X,插入到vlan設備和網卡設備之間。
因此,至少要有brcm_packet_type來加入ptype_base和register_brcm_dev()來向系統註冊設備X。進一步考慮,設備X在全局量init_net中有存儲,但我們還需要知道設備X與vlan設備以及網卡設備是何種組織關係,所以在這裏設計了brcm_group_hash來存儲這種關係。爲了對設備感興趣的事件作出響應,添加自己的notifier到netdev_chain中。另外,爲了用戶空間具有一定控制能力(如創建、刪除),還需要添加brcm相關的ioctl調用。爲了讓它看起來更完整,一種新的設備在proc中也應有對應項,用來調試和查看設備。
 

從最簡單開始
      要讓網絡協議棧能夠接收一種新協議是很簡單的,由於已經有報文作爲輸入,我們要做的僅僅是編寫好brcm_packet_type,然後在註冊模塊時只用做一件事:dev_add_pack。

static int __init brcm_proto_init(void)
{
 dev_add_pack(&brcm_packet_type);
}

static struct packet_type brcm_packet_type __read_mostly = {
 .type = cpu_to_be16(ETH_P_BRCM),
 .func = brcm_skb_recv, /* BRCM receive method */
};

int brcm_skb_recv(struct sk_buff *skb, struct net_device *dev,
    struct packet_type *ptype, struct net_device *orig_dev)
{
 struct brcm_hdr *bhdr;
 struct brcm_rx_stats *rx_stats;

 skb = skb_share_check(skb, GFP_ATOMIC);
 if(!skb)
  goto err_free;
 bhdr = (struct brcm_hdr *)skb->data;

 rcu_read_lock();
 skb_pull_rcsum(skb, BRCM_HLEN);
 // set protocol
 skb->protocol = bhdr->brcm_encapsulated_proto;
 // reorder skb
 skb = brcm_check_reorder_header(skb);
 if (!skb) 
  goto err_unlock;
 
 netif_rx(skb);
 rcu_read_unlock();
 return NET_RX_SUCCESS;

err_unlock:
 rcu_read_unlock();

err_free:
 kfree_skb(skb);
 return NET_RX_DROP;
}

      註冊這個模塊後,協議棧就能正常接收帶brcm報頭的報文的,代碼中ETH_P_BRCM是brcm的協議號,BRCM_HLEN是brcm的報頭長度。正是由於有報文作爲輸入,接收變得十分簡單。
      但這僅僅是能接收而已,發送的報文還是不帶brcm報頭的,而且接收的這段代碼也很粗略,沒有變更skb的設備,沒有記錄流量,沒有對brcm報頭作有意義的處理,下面逐一進行添加。

設備的相關定義
      一種設備就是net_device類型,而每種設備都有自己的私有變量,它存儲在net_device末尾,定義如下,其中real_dev指向下層設備,這是最基本屬性,其餘可以視需要自己設定,brcm_rx_stats則是該設備接收流量統計:

struct brcm_dev_info{
 struct net_device  *real_dev;
 u16 brcm_port;
 unsigned char  real_dev_addr[ETH_ALEN];
 struct proc_dir_entry *dent;
 struct brcm_rx_stats __percpu  *brcm_rx_stats;
};
struct brcm_rx_stats {
 unsigned long rx_packets;
 unsigned long rx_bytes;
 unsigned long multicast;
 unsigned long rx_errors;
};

 設備間的關係問題
      如果brcm僅僅是隻有一個設備,則無需數據結構來存儲這種關係,一個全局全變的brcm_dev就可以了。這裏的設計考慮的是複雜的情況,可以存在多個下層設備,多個brcm設備,之間沒有固定的關係。所以需要一種數據結構來存儲這種關係- brcm_group_hash。下面是一個簡單的圖示: 

      各個數據結構定義如下:

 
static struct hlist_head brcm_group_hash[BRCM_GRP_HASH_SIZE];
struct brcm_group {
 struct hlist_node hlist;
 struct net_device *real_dev;
 int nr_ports;
 int killall;
 struct net_device *brcm_devices_array[BRCM_GROUP_ARRAY_LEN];
 struct rcu_head  rcu;
};

      brcm_group_hash作爲全局變量存在,以hash表形式組織,brcm_group被插入到brcm_group_hash中,brcm_group存儲了它與下層設備的關係(eth與brcm),real_dev指向e下層設備,而brcm設備則存儲在brcm_devices_array數組中。
     下面完成由下層設備轉換成brcm設備的函數,brcm_port是報頭中的值,可以自己設定它的含義,這裏設定它表示報文來自於哪個端口。

struct net_device *find_brcm_dev(struct net_device *real_dev, u16 brcm_port)
{
 struct brcm_group *grp = brcm_find_group(real_dev);
 if (grp) 
  brcm_dev = grp->brcm_devices_array[brcm_port];
 return NULL;
}

     因爲在接收報文時,報文到達brcm層開始處理時,skb->dev指向的仍是下層設備,這時通過skb->dev查到brcm_group->real_dev相匹配的hash項,然後通過報文brcm報頭的信息,確定brcm_group->brcm_devices_array中哪個brcm設備作爲skb的新設備;
     而在發送報文時,報文到達brcm層開始處理時,skb->dev指向的是brcm設備,爲了繼續向下傳遞,需要變更爲它的下層設備,在設備數據net_device的私有數據部分,一般會存儲一個指針,指向它的下層設備,因此skb->dev只要變更爲brcm_dev_info(dev)->real_dev。
 

流量統計
      在數據結構中,brcm設備的私有數據brcm_dev_info中brcm_rx_stats記錄接收的流量信息;而dev->_tx[index]則會記錄發送的流量信息。
      在接收函數brcm_skb_rcv()中對於成功接收的報文會增加流量統計:

rx_stats = per_cpu_ptr(brcm_dev_info(skb->dev)->brcm_rx_stats,
  smp_processor_id());
rx_stats->rx_packets++;
rx_stats->rx_bytes += skb->len;

      在發送函數brcm_dev_hard_start_xmit()中對於發送的報文會增加相應流量統計:

if (likely(ret == NET_XMIT_SUCCESS)) {
txq->tx_packets++;
txq->tx_bytes += len;
} else
 txq->tx_dropped++;

      而brcm_netdev_ops->ndo_get_stats()即brcm_dev_get_stats()函數,則會將brcm網卡設備中記錄的發送和接收流量信息彙總成通用的格式net_device_stats,像ifconfig等命令使用的就是net_device_stats轉換後的結果。 

完整收發函數
      有了這些後接收函數brcm_skb_recv()就可以完整了,其中關於報頭brcm_hdr的處理可以略過,由於是空想的協議,含義是可以自己設定的:

int brcm_skb_recv(struct sk_buff *skb, struct net_device *dev,
    struct packet_type *ptype, struct net_device *orig_dev)
{
 struct brcm_hdr *bhdr;
 struct brcm_rx_stats *rx_stats;
 int op, brcm_port;

 skb = skb_share_check(skb, GFP_ATOMIC);
 if(!skb)
  goto err_free;
 bhdr = (struct brcm_hdr *)skb->data;
 op = bhdr->brcm_tag.brcm_53242_op;
 brcm_port = bhdr->brcm_tag.brcm_53242_src_portid- 23;

 rcu_read_lock();

 // drop wrong brcm tag packet
 if (op != BRCM_RCV_OP || brcm_port < 1 
  || brcm_port > 27) 
  goto err_unlock;

 skb->dev = find_brcm_dev(dev, brcm_port);
 if (!skb->dev) {
  goto err_unlock;
 }

 rx_stats = per_cpu_ptr(brcm_dev_info(skb->dev)->brcm_rx_stats,
          smp_processor_id());
 rx_stats->rx_packets++;
 rx_stats->rx_bytes += skb->len;
 skb_pull_rcsum(skb, BRCM_HLEN);

 switch (skb->pkt_type) {
 case PACKET_BROADCAST: /* Yeah, stats collect these together.. */
  /* stats->broadcast ++; // no such counter :-( */
  break;

 case PACKET_MULTICAST:
  rx_stats->multicast++;
  break;

 case PACKET_OTHERHOST:
  /* Our lower layer thinks this is not local, let's make sure.
   * This allows the VLAN to have a different MAC than the
   * underlying device, and still route correctly.
   */
  if (!compare_ether_addr(eth_hdr(skb)->h_dest,
     skb->dev->dev_addr))
   skb->pkt_type = PACKET_HOST;
  break;
 default:
  break;
 }
 
 // set protocol
 skb->protocol = bhdr->brcm_encapsulated_proto;

 // reorder skb
 skb = brcm_check_reorder_header(skb);
 if (!skb) {
  rx_stats->rx_errors++;
  goto err_unlock;
 }
 
 netif_rx(skb);
 rcu_read_unlock();
 return NET_RX_SUCCESS;

err_unlock:
 rcu_read_unlock();

err_free:
 kfree_skb(skb);
 return NET_RX_DROP;
}

      同時,發送函數brcm_dev_hard_start_xmit()可以完整了,同樣,其中關於brcm_hdr的處理可以略過:

static netdev_tx_t brcm_dev_hard_start_xmit(struct sk_buff *skb,
         struct net_device *dev)
{
 int i = skb_get_queue_mapping(skb);
 struct netdev_queue *txq = netdev_get_tx_queue(dev, i);
 struct brcm_ethhdr *beth = (struct brcm_ethhdr *)(skb->data);
 unsigned int len;
 u16 brcm_port;
 int ret;

 /* Handle non-VLAN frames if they are sent to us, for example by DHCP.
  *
  * NOTE: THIS ASSUMES DIX ETHERNET, SPECIFICALLY NOT SUPPORTING
  * OTHER THINGS LIKE FDDI/TokenRing/802.3 SNAPs...
  */
 if (beth->h_brcm_proto != htons(ETH_P_BRCM)){
  //unsigned int orig_headroom = skb_headroom(skb);
  brcm_t brcm_tag;
  brcm_port = brcm_dev_info(dev)->brcm_port;
  if (brcm_port == BRCM_ANY_PORT) {
   brcm_tag.brcm_op_53242 = 0;
   brcm_tag.brcm_tq_53242 = 0;
   brcm_tag.brcm_te_53242 = 0;
   brcm_tag.brcm_dst_53242 = 0;
  }else {
   brcm_tag.brcm_op_53242 = BRCM_SND_OP;
   brcm_tag.brcm_tq_53242 = 0;
   brcm_tag.brcm_te_53242 = 0;
   brcm_tag.brcm_dst_53242 = brcm_port + 23;
  }

  skb = brcm_put_tag(skb, *(u32 *)(&brcm_tag));
  if (!skb) {
   txq->tx_dropped++;
   return NETDEV_TX_OK;
  }
 }

 skb_set_dev(skb, brcm_dev_info(dev)->real_dev);
 len = skb->len;
 ret = dev_queue_xmit(skb);

 if (likely(ret == NET_XMIT_SUCCESS)) {
  txq->tx_packets++;
  txq->tx_bytes += len;
 } else
  txq->tx_dropped++;

 return ret;
}

註冊設備
      接收通過dev_add_pack(),就可以融入協議棧了,前面幾篇的分析已經講過通過ptype_base對報文進行脫殼。現在要融入的發送,函數已經完成了,既然發送是一種嵌套的調用,並且是由dev來推過的,那麼發送函數的融入一定在設備進行註冊時,作爲設備的一種發送方法。
      創建一種設備時,一定會有設備的XXX_setup()初始化,大部分設備都會用ether_setup()來作初始化,再進行適當更改。下面是brcm_setup():

void brcm_setup(struct net_device *dev)
{
 ether_setup(dev);

 dev->priv_flags  |= IFF_BRCM_TAG;
 dev->priv_flags  &= ~IFF_XMIT_DST_RELEASE;
 dev->tx_queue_len = 0;

 dev->netdev_ops  = &brcm_netdev_ops;
 dev->destructor  = free_netdev;
 dev->ethtool_ops = &brcm_ethtool_ops;

 memset(dev->broadcast, 0, ETH_ALEN);
}

      其中發送函數就在brcm_netdev_ops中,每層設備都會這樣調用:dev->netdev_ops->ndo_start_xmit()。     

static const struct net_device_ops brcm_netdev_ops = {
	.ndo_change_mtu		= brcm_dev_change_mtu,
	.ndo_init		= brcm_dev_init,
	.ndo_uninit		= brcm_dev_uninit,
	.ndo_open		= brcm_dev_open,
	.ndo_stop		= brcm_dev_stop,
	.ndo_start_xmit =  brcm_dev_hard_start_xmit,
	.ndo_validate_addr	= eth_validate_addr,
	.ndo_set_mac_address	= brcm_dev_set_mac_address,
	.ndo_set_rx_mode	= brcm_dev_set_rx_mode,
	.ndo_set_multicast_list	= brcm_dev_set_rx_mode,
	.ndo_change_rx_flags	= brcm_dev_change_rx_flags,
	//.ndo_do_ioctl		= brcm_dev_ioctl,
	.ndo_neigh_setup	= brcm_dev_neigh_setup,
	.ndo_get_stats		= brcm_dev_get_stats,
};
       而設備的初始化應該發生在創建設備時,也就是向網絡註冊它時,也就是register_brcm_dev(),註冊一個新設備,需要知道它的下層設備real_dev以及唯一標識brcm設備的brcm_port。首先確定該設備沒有被創建,然後用alloc_netdev_mq創建新設備new_dev,然後設置相關屬性,特別是它的私有屬性brcm_dev_info(new_dev),然後添加它到brcm_group_hash中,最後發生真正的註冊register_netdevice()。

static int register_brcm_dev(struct net_device *real_dev, u16 brcm_port)
{
 struct net_device *new_dev;
 struct net *net = dev_net(real_dev);
 struct brcm_group *grp;
 char name[IFNAMSIZ];
 int err;

 if(brcm_port >= BRCM_PORT_MASK)
  return -ERANGE;

 // exist yet
 if (find_brcm_dev(real_dev, brcm_port) != NULL)
  return -EEXIST;

 snprintf(name, IFNAMSIZ, "brcm%i", brcm_port);
 new_dev = alloc_netdev_mq(sizeof(struct brcm_dev_info), name,
      brcm_setup, 1);
 if (new_dev == NULL)
  return -ENOBUFS;
 new_dev->real_num_tx_queues = real_dev->real_num_tx_queues;
 dev_net_set(new_dev, net);
 new_dev->mtu = real_dev->mtu;

 brcm_dev_info(new_dev)->brcm_port = brcm_port;
 brcm_dev_info(new_dev)->real_dev = real_dev;
 brcm_dev_info(new_dev)->dent = NULL;
 //new_dev->rtnl_link_ops = &brcm_link_ops;

 grp = brcm_find_group(real_dev);
 if (!grp)
  grp = brcm_group_alloc(real_dev);
 
 err = register_netdevice(new_dev);
 if (err < 0)
  goto out_free_newdev;
 
 /* Account for reference in struct vlan_dev_info */
 dev_hold(real_dev);
 brcm_group_set_device(grp, brcm_port, new_dev);

 return 0;

out_free_newdev:
 free_netdev(new_dev);
 return err;
}

ioctl
      由於brcm設備可以存在多個,並且和下層設備不是固定的對應關係,因此它的創建應該可以人爲控制,因此通過ioctl由用戶進行創建。這裏只爲brcm提供了兩種操作-添加與刪除。一種設備添加一定是與下層設備成關係的,因此添加時需要手動指明這種下層設備,然後通過__dev_get_by_name()從網絡空間中找到這種設備,就可以調用register_brcm_dev()來完成註冊了。而設備的刪除則是直接刪除,直接刪除unregister_brcm_dev()。
     

static int brcm_ioctl_handler(struct net *net, void __user *arg)
{
	int err;
	struct brcm_ioctl_args args;
	struct net_device *dev = NULL;

	if (copy_from_user(&args, arg, sizeof(struct brcm_ioctl_args)))
		return -EFAULT;

	/* Null terminate this sucker, just in case. */
	args.device1[23] = 0;
	args.u.device2[23] = 0;

	rtnl_lock();

	switch (args.cmd) {
	case ADD_BRCM_CMD:
	case DEL_BRCM_CMD:
		err = -ENODEV;
		dev = __dev_get_by_name(net, args.device1);
		if (!dev)
			goto out;

		err = -EINVAL;
		if (args.cmd != ADD_BRCM_CMD && !is_brcm_dev(dev))
			goto out;
	}

	switch (args.cmd) {
	case ADD_BRCM_CMD:
		err = -EPERM;
		if (!capable(CAP_NET_ADMIN))
			break;
		err = register_brcm_dev(dev, args.u.port);
		break;

	case DEL_BRCM_CMD:
		err = -EPERM;
		if (!capable(CAP_NET_ADMIN))
			break;
		unregister_brcm_dev(dev, NULL);
		err = 0;
		break;
		
	default:
		err = -EOPNOTSUPP;
		break;
	}
out:
	rtnl_unlock();
	return err;
}

        這些是brcm協議模塊的主體部分了,當然它還不完整,在下篇中繼續完成brcm協議的添加,爲它完善一些細節:proc文件系統, notifier機制等等,以及內核Makefile的編寫,當然還有協議的測試。相關源碼在下篇中打包上傳。

內核版本:2.6.34
接上篇《添加網絡協議》。
        爲了用戶方便查看brcm設備的工作狀態,使用proc文件系統是很好的方式。一個網絡協議模塊可以註冊到網絡空間中register_pernet_subsys(),這個函數會爲子空間分配一個id號,通過id可以在網絡空間中找到分配給該子空間的內存:init_net->gen->ptr[id - 1]。而我們正是利用這塊內存去存儲proc中的相關信息:struct brcm_net,它記錄了brcm設備在proc文件系統中的位置。

 
struct brcm_net {
	/* /proc/net/brcm */
	struct proc_dir_entry *proc_brcm_dir;
	/* /proc/net/brcm/config */
	struct proc_dir_entry *proc_brcm_conf;
};

        在加載brcm模塊時會註冊子空間,brcm_init_net創建在proc中的相關項,並記錄路徑在brcm_net中;brcm_exit_net刪除在proc中的相關項;brcm_net_id記錄分配給子空間的id,這樣通過init_net->gen->ptr[brcm_net_id - 1]就可以操作brcm_net了。

err = register_pernet_subsys(&brcm_net_ops);
static struct pernet_operations brcm_net_ops = {
	.init = brcm_init_net,
	.exit = brcm_exit_net,
	.id = &brcm_net_id,
	.size = sizeof(struct brcm_net),
};

        注意到在brcm_init_net和brcm_exit_net中添加和刪除的僅僅是/proc/net/brcm目錄和config文件,而在使用中brcm設備是可以動態創建的,因此這部分代碼應該發生在添加和刪除brcm設備時,而不是在brcm模塊註冊和刪除時。最簡單的是直接添加在register方法或unregister方法中,但內核提供了更好的機制:事件,將對proc的操作分離出來,因爲proc的操作實際上屬於附加的操作而不是必須的操作。下面就來看event機制。
        前面幾篇已經有描述過event機制,這裏的事件都是關於設備的事件,使用的是register_netdevice_notifier()來,註冊notifier到netdev_chain鏈表上。在加載brcm模塊時註冊notifier,brcm_notifier_block包含了brcm設備對所關心的事件作出的反應。

 
err = register_netdevice_notifier(&brcm_notifier_block);
static struct notifier_block brcm_notifier_block __read_mostly = {
	.notifier_call = brcm_device_event,
};

        設備對哪些事件會感興趣,首先,brcm設備對註冊和註銷是感興趣的,要操作proc文件系統;其次,對於發往brcm下層設備的事件,要考慮這些事件造成的連帶影響(比如eth1被down掉,則其上的brcm設備也應該被down掉),一般是下層設備的事件對其上的所有brcm設備都進行相應操作。
        所以在brcm_device_event有兩類進入的設備dev會進行操作,一類是brcm設備,它僅僅是操作proc文件系統。判斷是否爲brcm設備,是的話則由__brcm_device_event() 處理。 

  
if (is_brcm_dev(dev))
	__brcm_device_event(dev, event);

static void __brcm_device_event(struct net_device *dev, unsigned long event)
{
	switch (event) {
	case NETDEV_CHANGENAME:
		brcm_proc_rem_dev(dev);
		if (brcm_proc_add_dev(dev) < 0)
			pr_warning("BRCM: failed to change proc name for %s\n",
					dev->name);
		break;
	case NETDEV_REGISTER:
		if (brcm_proc_add_dev(dev) < 0)
			pr_warning("BRCM: failed to add proc entry for %s\n",
					dev->name);
		break;
	case NETDEV_UNREGISTER:
		brcm_proc_rem_dev(dev);
		break;
	}
}

        如何是brcm的下層設備,如根據brcm_group_hash中的映射關係,對下層設備相關的所有brcm設備進行操作:

switch (event) {
case NETDEV_CHANGE:
	/* Propagate real device state to vlan devices */
	for (i = 0; i < BRCM_GROUP_ARRAY_LEN; i++) {
		brcmdev = brcm_group_get_device(grp, i);
		if (!brcmdev)
			continue;

		netif_stacked_transfer_operstate(dev, brcmdev);
	}
	break;

case NETDEV_CHANGEADDR:
	/* Adjust unicast filters on underlying device */
	for (i = 0; i < BRCM_GROUP_ARRAY_LEN; i++) {
		brcmdev = brcm_group_get_device(grp, i);
		if (!brcmdev)
			continue;

		flgs = brcmdev->flags;
		if (!(flgs & IFF_UP))
			continue;

		brcm_sync_address(dev, brcmdev);
	}
	break;

case NETDEV_CHANGEMTU:
	for (i = 0; i < BRCM_GROUP_ARRAY_LEN; i++) {
		brcmdev = brcm_group_get_device(grp, i);
		if (!brcmdev)
			continue;

		if (brcmdev->mtu <= dev->mtu)
			continue;

		dev_set_mtu(brcmdev, dev->mtu);
	}
	break;

case NETDEV_DOWN:
	/* Put all VLANs for this dev in the down state too.  */
	for (i = 0; i < BRCM_GROUP_ARRAY_LEN; i++) {
		brcmdev = brcm_group_get_device(grp, i);
		if (!brcmdev)
			continue;

		flgs = brcmdev->flags;
		if (!(flgs & IFF_UP))
			continue;

		brcm = brcm_dev_info(brcmdev);
		dev_change_flags(brcmdev, flgs & ~IFF_UP);
		netif_stacked_transfer_operstate(dev, brcmdev);
	}
	break;

case NETDEV_UP:
	/* Put all VLANs for this dev in the up state too.  */
	for (i = 0; i < BRCM_GROUP_ARRAY_LEN; i++) {
		brcmdev = brcm_group_get_device(grp, i);
		if (!brcmdev)
			continue;

		flgs = brcmdev->flags;
		if (flgs & IFF_UP)
			continue;

		brcm = brcm_dev_info(brcmdev);
		dev_change_flags(brcmdev, flgs | IFF_UP);
		netif_stacked_transfer_operstate(dev, brcmdev);
	}
	break;

case NETDEV_UNREGISTER:
	/* Delete all BRCMs for this dev. */
	grp->killall = 1;

	for (i = 0; i < BRCM_GROUP_ARRAY_LEN; i++) {
		brcmdev = brcm_group_get_device(grp, i);
		if (!brcmdev)
			continue;

		/* unregistration of last brcm destroys group, abort
		 * afterwards */
		if (grp->nr_ports == 1)
			i = BRCM_GROUP_ARRAY_LEN;

		unregister_brcm_dev(brcmdev, &list);
	}
	unregister_netdevice_many(&list);
	break;
}

        到這裏,協議的添加就大致完成了,當然還包括一些頭文件的修改,宏變量的添加等就不一一詳述,具體可見最後的附件。
        爲了編譯進內核,還需要修改以下文件:
              $(linux)/net/Kconfig  $(linux)/net/Makefile

        最後,在make menuconfig選擇添加brcm協議
              Networking Support -> Networking options     
  

        同時,需要一個簡單的用戶空間工具來配置我們的brcm設備,就像vconfig用來配置vlan設備一樣;編寫的簡單的bconfig工具,命令格式:
                "Usage: add [interface-name] [brcm_port]\n"
                "       rem [dev-name]";

        內核編譯完成後就該進行測試了,如果開啓了內核調試信息,啓動內核就看到以下信息: 

        然後啓用網卡,可以查看到添加了brcm設備後的狀態: 

        可以使用原生套接字自己打上brcm頭後發送報文讓協議棧接收,或者用wireshark等捕獲協議棧發出的報文,下圖即是捕獲到的報文: 

        這是主機發出的arp報文,可以看到,在源mac後接的不是vlan報頭,而是我們添加的brcm報文,協議號是8744。
        查看proc中信息:
 

        :patch補丁 && 重要的源文件 && bconfig工具源碼

              http://download.csdn.net/source/3548117

發佈了0 篇原創文章 · 獲贊 7 · 訪問量 14萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章