原文出处:

http://blog.csdn.net/qy532846454/article/details/6627536

http://blog.csdn.net/qy532846454/article/details/6646122

http://blog.csdn.net/qy532846454/article/details/6657511

本文只是一个内核网络协议的实践的例子,先说明添加的目的。

内核版本:2.6.34;在支持802.1主机上,报文的一般格式:

0_1311388829ls02.gif

现在需要支持一种新的协议[二层] – BRCM协议,与IP等协议不同,它位于2层,拥有6字节的头部和4字节的尾部,添加的层次决定了比起添加其它协议要复杂一些,新的报文格式如下,而我们的目的就是要网络协议栈能正常处理这样的报文: 

0_1311388845hryo.gif

实际上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协议后的层次图: 

0_1311388869Z3b6.gif

eth1代表实际的B4401物理网卡;eth1.X/brcm0.x代表VLAN创建的虚拟网卡,后面的数字X是vlan号;brcm0代表BRCM创建的虚拟网卡,数字0表示测试用;从图中可以看到,brcm协议的添加是通过添加brcmX虚拟网卡接口实现的。

如果BRCM协议添加正确,那么最终的结果应该是:

0_1311388895htRi.gif

在接收报文时,通过虚拟设备BRCM会脱去brcm的6字节的头部与4字节尾部,当然,协议做的远远不只这些,但这是核心。 

0_131138890905nC.gif

在发送报文时,通过虚拟设备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
数据结构是核心,每一步也是一个网络协议模块的核心。

内核版本: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。

  1. staticint __init brcm_proto_init(void

  2. dev_add_pack(&brcm_packet_type); 

  3. staticstruct packet_type brcm_packet_type __read_mostly = { 

  4. .type = cpu_to_be16(ETH_P_BRCM), 

  5. .func = brcm_skb_recv, /* BRCM receive method */

  6. }; 

  7. int brcm_skb_recv(struct sk_buff *skb, struct net_device *dev, 

  8. struct packet_type *ptype, struct net_device *orig_dev) 

  9. struct brcm_hdr *bhdr; 

  10. struct brcm_rx_stats *rx_stats; 

  11. skb = skb_share_check(skb, GFP_ATOMIC); 

  12. if(!skb) 

  13. goto err_free; 

  14. bhdr = (struct brcm_hdr *)skb->data; 

  15. rcu_read_lock(); 

  16. skb_pull_rcsum(skb, BRCM_HLEN); 

  17. // set protocol

  18. skb->protocol = bhdr->brcm_encapsulated_proto; 

  19. // reorder skb

  20. skb = brcm_check_reorder_header(skb); 

  21. if (!skb) 

  22. goto err_unlock; 

  23. netif_rx(skb); 

  24. rcu_read_unlock(); 

  25. return NET_RX_SUCCESS; 

  26. err_unlock: 

  27. rcu_read_unlock(); 

  28. err_free: 

  29. kfree_skb(skb); 

  30. return NET_RX_DROP; 

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则是该设备接收流量统计:

  1. struct brcm_dev_info{ 

  2. struct net_device *real_dev; 

  3. u16 brcm_port; 

  4. unsigned char real_dev_addr[ETH_ALEN]; 

  5. struct proc_dir_entry *dent; 

  6. struct brcm_rx_stats __percpu *brcm_rx_stats; 

  7. }; 

  8. struct brcm_rx_stats { 

  9. unsigned long rx_packets; 

  10. unsigned long rx_bytes; 

  11. unsigned long multicast; 

  12. unsigned long rx_errors; 

  13. }; 

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。下面是一个简单的图示: 

0_1311992667Lv8J.gif

各个数据结构定义如下:

  1. staticstruct hlist_head brcm_group_hash[BRCM_GRP_HASH_SIZE]; 

  2. struct brcm_group { 

  3. struct hlist_node hlist; 

  4. struct net_device *real_dev; 

  5. int nr_ports; 

  6. int killall; 

  7. struct net_device *brcm_devices_array[BRCM_GROUP_ARRAY_LEN]; 

  8. struct rcu_head rcu; 

  9. }; 

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是报头中的值,可以自己设定它的含义,这里设定它表示报文来自于哪个端口。

  1. struct net_device *find_brcm_dev(struct net_device *real_dev, u16 brcm_port) 

  2. struct brcm_group *grp = brcm_find_group(real_dev); 

  3. if (grp) 

  4. brcm_dev = grp->brcm_devices_array[brcm_port]; 

  5. return NULL; 

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()中对于成功接收的报文会增加流量统计:

  1. rx_stats = per_cpu_ptr(brcm_dev_info(skb->dev)->brcm_rx_stats, 

  2. smp_processor_id()); 

  3. rx_stats->rx_packets++; 

  4. rx_stats->rx_bytes += skb->len; 

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()中对于发送的报文会增加相应流量统计:

  1. if (likely(ret == NET_XMIT_SUCCESS)) { 

  2. txq->tx_packets++; 

  3. txq->tx_bytes += len; 

  4. else

  5. txq->tx_dropped++; 

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的处理可以略过,由于是空想的协议,含义是可以自己设定的:

  1. int brcm_skb_recv(struct sk_buff *skb, struct net_device *dev, 

  2. struct packet_type *ptype, struct net_device *orig_dev) 

  3. struct brcm_hdr *bhdr; 

  4. struct brcm_rx_stats *rx_stats; 

  5. int op, brcm_port; 

  6. skb = skb_share_check(skb, GFP_ATOMIC); 

  7. if(!skb) 

  8. goto err_free; 

  9. bhdr = (struct brcm_hdr *)skb->data; 

  10. op = bhdr->brcm_tag.brcm_53242_op; 

  11. brcm_port = bhdr->brcm_tag.brcm_53242_src_portid- 23; 

  12. rcu_read_lock(); 

  13. // drop wrong brcm tag packet

  14. if (op != BRCM_RCV_OP || brcm_port < 1 

  15. || brcm_port > 27) 

  16. goto err_unlock; 

  17. skb->dev = find_brcm_dev(dev, brcm_port); 

  18. if (!skb->dev) { 

  19. goto err_unlock; 

  20. rx_stats = per_cpu_ptr(brcm_dev_info(skb->dev)->brcm_rx_stats, 

  21. smp_processor_id()); 

  22. rx_stats->rx_packets++; 

  23. rx_stats->rx_bytes += skb->len; 

  24. skb_pull_rcsum(skb, BRCM_HLEN); 

  25. switch (skb->pkt_type) { 

  26. case PACKET_BROADCAST: /* Yeah, stats collect these together.. */

  27. /* stats->broadcast ++; // no such counter :-( */

  28. break

  29. case PACKET_MULTICAST: 

  30. rx_stats->multicast++; 

  31. break

  32. case PACKET_OTHERHOST: 

  33. /* Our lower layer thinks this is not local, let's make sure.

  34. * This allows the VLAN to have a different MAC than the

  35. * underlying device, and still route correctly.

  36. */

  37. if (!compare_ether_addr(eth_hdr(skb)->h_dest, 

  38. skb->dev->dev_addr)) 

  39. skb->pkt_type = PACKET_HOST; 

  40. break

  41. default

  42. break

  43. // set protocol

  44. skb->protocol = bhdr->brcm_encapsulated_proto; 

  45. // reorder skb

  46. skb = brcm_check_reorder_header(skb); 

  47. if (!skb) { 

  48. rx_stats->rx_errors++; 

  49. goto err_unlock; 

  50. netif_rx(skb); 

  51. rcu_read_unlock(); 

  52. return NET_RX_SUCCESS; 

  53. err_unlock: 

  54. rcu_read_unlock(); 

  55. err_free: 

  56. kfree_skb(skb); 

  57. return NET_RX_DROP; 

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的处理可以略过:

  1. static netdev_tx_t brcm_dev_hard_start_xmit(struct sk_buff *skb, 

  2. struct net_device *dev) 

  3. int i = skb_get_queue_mapping(skb); 

  4. struct netdev_queue *txq = netdev_get_tx_queue(dev, i); 

  5. struct brcm_ethhdr *beth = (struct brcm_ethhdr *)(skb->data); 

  6. unsigned int len; 

  7. u16 brcm_port; 

  8. int ret; 

  9. /* Handle non-VLAN frames if they are sent to us, for example by DHCP.

  10. *

  11. * NOTE: THIS ASSUMES DIX ETHERNET, SPECIFICALLY NOT SUPPORTING

  12. * OTHER THINGS LIKE FDDI/TokenRing/802.3 SNAPs...

  13. */

  14. if (beth->h_brcm_proto != htons(ETH_P_BRCM)){ 

  15. //unsigned int orig_headroom = skb_headroom(skb);

  16. brcm_t brcm_tag; 

  17. brcm_port = brcm_dev_info(dev)->brcm_port; 

  18. if (brcm_port == BRCM_ANY_PORT) { 

  19. brcm_tag.brcm_op_53242 = 0; 

  20. brcm_tag.brcm_tq_53242 = 0; 

  21. brcm_tag.brcm_te_53242 = 0; 

  22. brcm_tag.brcm_dst_53242 = 0; 

  23. }else { 

  24. brcm_tag.brcm_op_53242 = BRCM_SND_OP; 

  25. brcm_tag.brcm_tq_53242 = 0; 

  26. brcm_tag.brcm_te_53242 = 0; 

  27. brcm_tag.brcm_dst_53242 = brcm_port + 23; 

  28. skb = brcm_put_tag(skb, *(u32 *)(&brcm_tag)); 

  29. if (!skb) { 

  30. txq->tx_dropped++; 

  31. return NETDEV_TX_OK; 

  32. skb_set_dev(skb, brcm_dev_info(dev)->real_dev); 

  33. len = skb->len; 

  34. ret = dev_queue_xmit(skb); 

  35. if (likely(ret == NET_XMIT_SUCCESS)) { 

  36. txq->tx_packets++; 

  37. txq->tx_bytes += len; 

  38. else

  39. txq->tx_dropped++; 

  40. return ret; 

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():

  1. void brcm_setup(struct net_device *dev) 

  2. ether_setup(dev); 

  3. dev->priv_flags |= IFF_BRCM_TAG; 

  4. dev->priv_flags &= ~IFF_XMIT_DST_RELEASE; 

  5. dev->tx_queue_len = 0; 

  6. dev->netdev_ops = &brcm_netdev_ops; 

  7. dev->destructor = free_netdev; 

  8. dev->ethtool_ops = &brcm_ethtool_ops; 

  9. memset(dev->broadcast, 0, ETH_ALEN); 

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()。

  1. staticconststruct net_device_ops brcm_netdev_ops = { 

  2. .ndo_change_mtu = brcm_dev_change_mtu, 

  3. .ndo_init = brcm_dev_init, 

  4. .ndo_uninit = brcm_dev_uninit, 

  5. .ndo_open = brcm_dev_open, 

  6. .ndo_stop = brcm_dev_stop, 

  7. .ndo_start_xmit = brcm_dev_hard_start_xmit, 

  8. .ndo_validate_addr = eth_validate_addr, 

  9. .ndo_set_mac_address = brcm_dev_set_mac_address, 

  10. .ndo_set_rx_mode = brcm_dev_set_rx_mode, 

  11. .ndo_set_multicast_list = brcm_dev_set_rx_mode, 

  12. .ndo_change_rx_flags = brcm_dev_change_rx_flags, 

  13. //.ndo_do_ioctl = brcm_dev_ioctl,

  14. .ndo_neigh_setup = brcm_dev_neigh_setup, 

  15. .ndo_get_stats = brcm_dev_get_stats, 

  16. }; 

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()。 

  1. staticint register_brcm_dev(struct net_device *real_dev, u16 brcm_port) 

  2. struct net_device *new_dev; 

  3. struct net *net = dev_net(real_dev); 

  4. struct brcm_group *grp; 

  5. char name[IFNAMSIZ]; 

  6. int err; 

  7. if(brcm_port >= BRCM_PORT_MASK) 

  8. return -ERANGE; 

  9. // exist yet

  10. if (find_brcm_dev(real_dev, brcm_port) != NULL) 

  11. return -EEXIST; 

  12. snprintf(name, IFNAMSIZ, "brcm%i", brcm_port); 

  13. new_dev = alloc_netdev_mq(sizeof(struct brcm_dev_info), name, 

  14. brcm_setup, 1); 

  15. if (new_dev == NULL) 

  16. return -ENOBUFS; 

  17. new_dev->real_num_tx_queues = real_dev->real_num_tx_queues; 

  18. dev_net_set(new_dev, net); 

  19. new_dev->mtu = real_dev->mtu; 

  20. brcm_dev_info(new_dev)->brcm_port = brcm_port; 

  21. brcm_dev_info(new_dev)->real_dev = real_dev; 

  22. brcm_dev_info(new_dev)->dent = NULL; 

  23. //new_dev->rtnl_link_ops = &brcm_link_ops;

  24. grp = brcm_find_group(real_dev); 

  25. if (!grp) 

  26. grp = brcm_group_alloc(real_dev); 

  27. err = register_netdevice(new_dev); 

  28. if (err < 0) 

  29. goto out_free_newdev; 

  30. /* Account for reference in struct vlan_dev_info */

  31. dev_hold(real_dev); 

  32. brcm_group_set_device(grp, brcm_port, new_dev); 

  33. return 0; 

  34. out_free_newdev: 

  35. free_netdev(new_dev); 

  36. return err; 

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()。

  1. staticint brcm_ioctl_handler(struct net *net, void __user *arg) 

  2. int err; 

  3. struct brcm_ioctl_args args; 

  4. struct net_device *dev = NULL; 

  5. if (copy_from_user(&args, arg, sizeof(struct brcm_ioctl_args))) 

  6. return -EFAULT; 

  7. /* Null terminate this sucker, just in case. */

  8. args.device1[23] = 0; 

  9. args.u.device2[23] = 0; 

  10. rtnl_lock(); 

  11. switch (args.cmd) { 

  12. case ADD_BRCM_CMD: 

  13. case DEL_BRCM_CMD: 

  14. err = -ENODEV; 

  15. dev = __dev_get_by_name(net, args.device1); 

  16. if (!dev) 

  17. goto out; 

  18. err = -EINVAL; 

  19. if (args.cmd != ADD_BRCM_CMD && !is_brcm_dev(dev)) 

  20. goto out; 

  21. switch (args.cmd) { 

  22. case ADD_BRCM_CMD: 

  23. err = -EPERM; 

  24. if (!capable(CAP_NET_ADMIN)) 

  25. break

  26. err = register_brcm_dev(dev, args.u.port); 

  27. break

  28. case DEL_BRCM_CMD: 

  29. err = -EPERM; 

  30. if (!capable(CAP_NET_ADMIN)) 

  31. break

  32. unregister_brcm_dev(dev, NULL); 

  33. err = 0; 

  34. break

  35. default

  36. err = -EOPNOTSUPP; 

  37. break

  38. out: 

  39. rtnl_unlock(); 

  40. return err; 

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的编写,当然还有协议的测试。

为了用户方便查看brcm设备的工作状态,使用proc文件系统是很好的方式。一个网络协议模块可以注册到网络空间中register_pernet_subsys(),这个函数会为子空间分配一个id号,通过id可以在网络空间中找到分配给该子空间的内存:init_net->gen->ptr[id - 1]。而我们正是利用这块内存去存储proc中的相关信息:struct brcm_net,它记录了brcm设备在proc文件系统中的位置。

  1. struct brcm_net { 

  2. /* /proc/net/brcm */

  3. struct proc_dir_entry *proc_brcm_dir; 

  4. /* /proc/net/brcm/config */

  5. struct proc_dir_entry *proc_brcm_conf; 

  6. }; 

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了。

  1. err = register_pernet_subsys(&brcm_net_ops); 

  2. staticstruct pernet_operations brcm_net_ops = { 

  3. .init = brcm_init_net, 

  4. .exit = brcm_exit_net, 

  5. .id = &brcm_net_id, 

  6. .size = sizeof(struct brcm_net), 

  7. }; 

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设备对所关心的事件作出的反应。

  1. err = register_netdevice_notifier(&brcm_notifier_block); 

  2. staticstruct notifier_block brcm_notifier_block __read_mostly = { 

  3. .notifier_call = brcm_device_event, 

  4. }; 

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() 处理。 

  1. if (is_brcm_dev(dev)) 

  2. __brcm_device_event(dev, event); 

  3. staticvoid __brcm_device_event(struct net_device *dev, unsigned long event) 

  4. switch (event) { 

  5. case NETDEV_CHANGENAME: 

  6. brcm_proc_rem_dev(dev); 

  7. if (brcm_proc_add_dev(dev) < 0) 

  8. pr_warning("BRCM: failed to change proc name for %s\n"

  9. dev->name); 

  10. break

  11. case NETDEV_REGISTER: 

  12. if (brcm_proc_add_dev(dev) < 0) 

  13. pr_warning("BRCM: failed to add proc entry for %s\n"

  14. dev->name); 

  15. break

  16. case NETDEV_UNREGISTER: 

  17. brcm_proc_rem_dev(dev); 

  18. break

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设备进行操作:

  1. switch (event) { 

  2. case NETDEV_CHANGE: 

  3. /* Propagate real device state to vlan devices */

  4. for (i = 0; i < BRCM_GROUP_ARRAY_LEN; i++) { 

  5. brcmdev = brcm_group_get_device(grp, i); 

  6. if (!brcmdev) 

  7. continue

  8. netif_stacked_transfer_operstate(dev, brcmdev); 

  9. break

  10. case NETDEV_CHANGEADDR: 

  11. /* Adjust unicast filters on underlying device */

  12. for (i = 0; i < BRCM_GROUP_ARRAY_LEN; i++) { 

  13. brcmdev = brcm_group_get_device(grp, i); 

  14. if (!brcmdev) 

  15. continue

  16. flgs = brcmdev->flags; 

  17. if (!(flgs & IFF_UP)) 

  18. continue

  19. brcm_sync_address(dev, brcmdev); 

  20. break

  21. case NETDEV_CHANGEMTU: 

  22. for (i = 0; i < BRCM_GROUP_ARRAY_LEN; i++) { 

  23. brcmdev = brcm_group_get_device(grp, i); 

  24. if (!brcmdev) 

  25. continue

  26. if (brcmdev->mtu <= dev->mtu) 

  27. continue

  28. dev_set_mtu(brcmdev, dev->mtu); 

  29. break

  30. case NETDEV_DOWN: 

  31. /* Put all VLANs for this dev in the down state too. */

  32. for (i = 0; i < BRCM_GROUP_ARRAY_LEN; i++) { 

  33. brcmdev = brcm_group_get_device(grp, i); 

  34. if (!brcmdev) 

  35. continue

  36. flgs = brcmdev->flags; 

  37. if (!(flgs & IFF_UP)) 

  38. continue

  39. brcm = brcm_dev_info(brcmdev); 

  40. dev_change_flags(brcmdev, flgs & ~IFF_UP); 

  41. netif_stacked_transfer_operstate(dev, brcmdev); 

  42. break

  43. case NETDEV_UP: 

  44. /* Put all VLANs for this dev in the up state too. */

  45. for (i = 0; i < BRCM_GROUP_ARRAY_LEN; i++) { 

  46. brcmdev = brcm_group_get_device(grp, i); 

  47. if (!brcmdev) 

  48. continue

  49. flgs = brcmdev->flags; 

  50. if (flgs & IFF_UP) 

  51. continue

  52. brcm = brcm_dev_info(brcmdev); 

  53. dev_change_flags(brcmdev, flgs | IFF_UP); 

  54. netif_stacked_transfer_operstate(dev, brcmdev); 

  55. break

  56. case NETDEV_UNREGISTER: 

  57. /* Delete all BRCMs for this dev. */

  58. grp->killall = 1; 

  59. for (i = 0; i < BRCM_GROUP_ARRAY_LEN; i++) { 

  60. brcmdev = brcm_group_get_device(grp, i); 

  61. if (!brcmdev) 

  62. continue

  63. /* unregistration of last brcm destroys group, abort

  64. * afterwards */

  65. if (grp->nr_ports == 1) 

  66. i = BRCM_GROUP_ARRAY_LEN; 

  67. unregister_brcm_dev(brcmdev, &list); 

  68. unregister_netdevice_many(&list); 

  69. break

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 

0_13123666737L77.gif

同时,需要一个简单的用户空间工具来配置我们的brcm设备,就像vconfig用来配置vlan设备一样;编写的简单的bconfig工具,命令格式:
"Usage: add [interface-name] [brcm_port]\n"
" rem [dev-name]";

内核编译完成后就该进行测试了,如果开启了内核调试信息,启动内核就看到以下信息:

0_13123667911nqi.gif

然后启用网卡,可以查看到添加了brcm设备后的状态:

0_1312366835Ho6d.gif

可以使用原生套接字自己打上brcm头后发送报文让协议栈接收,或者用wireshark等捕获协议栈发出的报文,下图即是捕获到的报文:

0_1312366888sZ8A.gif

这是主机发出的arp报文,可以看到,在源mac后接的不是vlan报头,而是我们添加的brcm报文,协议号是8744。
查看proc中信息:

0_13123668993Z45.gif

:patch补丁 && 重要的源文件 && bconfig工具源码

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

 

  以上是原作者上传的code,但CSDN下载需要1积分,CSDN的积分实在没有了,我托人下来的,并在51CTO上分享了一下,这里的下载豆很好弄到。spacer.gif 

http://down.51cto.com/data/848538