Chinaunix首页 | 论坛 | 博客
  • 博客访问: 1879287
  • 博文数量: 376
  • 博客积分: 2147
  • 博客等级: 大尉
  • 技术积分: 3642
  • 用 户 组: 普通用户
  • 注册时间: 2012-02-06 10:47
文章分类

全部博文(376)

文章存档

2019年(3)

2017年(28)

2016年(15)

2015年(17)

2014年(182)

2013年(16)

2012年(115)

我的朋友

分类: 嵌入式

2014-01-06 16:39:44

内核版本: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. static int __init brcm_proto_init(void)  
  2. {  
  3.  dev_add_pack(&brcm_packet_type);  
  4. }  
  5.   
  6. static struct packet_type brcm_packet_type __read_mostly = {  
  7.  .type = cpu_to_be16(ETH_P_BRCM),  
  8.  .func = brcm_skb_recv, /* BRCM receive method */  
  9. };  
  10.   
  11. int brcm_skb_recv(struct sk_buff *skb, struct net_device *dev,  
  12.     struct packet_type *ptype, struct net_device *orig_dev)  
  13. {  
  14.  struct brcm_hdr *bhdr;  
  15.  struct brcm_rx_stats *rx_stats;  
  16.   
  17.  skb = skb_share_check(skb, GFP_ATOMIC);  
  18.  if(!skb)  
  19.   goto err_free;  
  20.  bhdr = (struct brcm_hdr *)skb->data;  
  21.   
  22.  rcu_read_lock();  
  23.  skb_pull_rcsum(skb, BRCM_HLEN);  
  24.  // set protocol  
  25.  skb->protocol = bhdr->brcm_encapsulated_proto;  
  26.  // reorder skb  
  27.  skb = brcm_check_reorder_header(skb);  
  28.  if (!skb)   
  29.   goto err_unlock;  
  30.    
  31.  netif_rx(skb);  
  32.  rcu_read_unlock();  
  33.  return NET_RX_SUCCESS;  
  34.   
  35. err_unlock:  
  36.  rcu_read_unlock();  
  37.   
  38. err_free:  
  39.  kfree_skb(skb);  
  40.  return NET_RX_DROP;  
  41. }  

      注册这个模块后,协议栈就能正常接收带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. };  

 设备间的关系问题
      如果brcm仅仅是只有一个设备,则无需数据结构来存储这种关系,一个全局全变的brcm_dev就可以了。这里的设计考虑的是复杂的情况,可以存在多个 下层设备,多个brcm设备,之间没有固定的关系。所以需要一种数据结构来存储这种关系- brcm_group_hash。下面是一个简单的图示: 

      各个数据结构定义如下:

  1. static struct 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. };  

      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. {  
  3.  struct brcm_group *grp = brcm_find_group(real_dev);  
  4.  if (grp)   
  5.   brcm_dev = grp->brcm_devices_array[brcm_port];  
  6.  return NULL;  
  7. }  

     因为在接收报文时,报文到达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;  

      在发送函数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++;  

      而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. {  
  4.  struct brcm_hdr *bhdr;  
  5.  struct brcm_rx_stats *rx_stats;  
  6.  int op, brcm_port;  
  7.   
  8.  skb = skb_share_check(skb, GFP_ATOMIC);  
  9.  if(!skb)  
  10.   goto err_free;  
  11.  bhdr = (struct brcm_hdr *)skb->data;  
  12.  op = bhdr->brcm_tag.brcm_53242_op;  
  13.  brcm_port = bhdr->brcm_tag.brcm_53242_src_portid- 23;  
  14.   
  15.  rcu_read_lock();  
  16.   
  17.  // drop wrong brcm tag packet  
  18.  if (op != BRCM_RCV_OP || brcm_port < 1   
  19.   || brcm_port > 27)   
  20.   goto err_unlock;  
  21.   
  22.  skb->dev = find_brcm_dev(dev, brcm_port);  
  23.  if (!skb->dev) {  
  24.   goto err_unlock;  
  25.  }  
  26.   
  27.  rx_stats = per_cpu_ptr(brcm_dev_info(skb->dev)->brcm_rx_stats,  
  28.           smp_processor_id());  
  29.  rx_stats->rx_packets++;  
  30.  rx_stats->rx_bytes += skb->len;  
  31.  skb_pull_rcsum(skb, BRCM_HLEN);  
  32.   
  33.  switch (skb->pkt_type) {  
  34.  case PACKET_BROADCAST: /* Yeah, stats collect these together.. */  
  35.   /* stats->broadcast ++; // no such counter :-( */  
  36.   break;  
  37.   
  38.  case PACKET_MULTICAST:  
  39.   rx_stats->multicast++;  
  40.   break;  
  41.   
  42.  case PACKET_OTHERHOST:  
  43.   /* Our lower layer thinks this is not local, let's make sure. 
  44.    * This allows the VLAN to have a different MAC than the 
  45.    * underlying device, and still route correctly. 
  46.    */  
  47.   if (!compare_ether_addr(eth_hdr(skb)->h_dest,  
  48.      skb->dev->dev_addr))  
  49.    skb->pkt_type = PACKET_HOST;  
  50.   break;  
  51.  default:  
  52.   break;  
  53.  }  
  54.    
  55.  // set protocol  
  56.  skb->protocol = bhdr->brcm_encapsulated_proto;  
  57.   
  58.  // reorder skb  
  59.  skb = brcm_check_reorder_header(skb);  
  60.  if (!skb) {  
  61.   rx_stats->rx_errors++;  
  62.   goto err_unlock;  
  63.  }  
  64.    
  65.  netif_rx(skb);  
  66.  rcu_read_unlock();  
  67.  return NET_RX_SUCCESS;  
  68.   
  69. err_unlock:  
  70.  rcu_read_unlock();  
  71.   
  72. err_free:  
  73.  kfree_skb(skb);  
  74.  return NET_RX_DROP;  
  75. }  

      同时,发送函数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. {  
  4.  int i = skb_get_queue_mapping(skb);  
  5.  struct netdev_queue *txq = netdev_get_tx_queue(dev, i);  
  6.  struct brcm_ethhdr *beth = (struct brcm_ethhdr *)(skb->data);  
  7.  unsigned int len;  
  8.  u16 brcm_port;  
  9.  int ret;  
  10.   
  11.  /* Handle non-VLAN frames if they are sent to us, for example by DHCP. 
  12.   * 
  13.   * NOTE: THIS ASSUMES DIX ETHERNET, SPECIFICALLY NOT SUPPORTING 
  14.   * OTHER THINGS LIKE FDDI/TokenRing/802.3 SNAPs... 
  15.   */  
  16.  if (beth->h_brcm_proto != htons(ETH_P_BRCM)){  
  17.   //unsigned int orig_headroom = skb_headroom(skb);  
  18.   brcm_t brcm_tag;  
  19.   brcm_port = brcm_dev_info(dev)->brcm_port;  
  20.   if (brcm_port == BRCM_ANY_PORT) {  
  21.    brcm_tag.brcm_op_53242 = 0;  
  22.    brcm_tag.brcm_tq_53242 = 0;  
  23.    brcm_tag.brcm_te_53242 = 0;  
  24.    brcm_tag.brcm_dst_53242 = 0;  
  25.   }else {  
  26.    brcm_tag.brcm_op_53242 = BRCM_SND_OP;  
  27.    brcm_tag.brcm_tq_53242 = 0;  
  28.    brcm_tag.brcm_te_53242 = 0;  
  29.    brcm_tag.brcm_dst_53242 = brcm_port + 23;  
  30.   }  
  31.   
  32.   skb = brcm_put_tag(skb, *(u32 *)(&brcm_tag));  
  33.   if (!skb) {  
  34.    txq->tx_dropped++;  
  35.    return NETDEV_TX_OK;  
  36.   }  
  37.  }  
  38.   
  39.  skb_set_dev(skb, brcm_dev_info(dev)->real_dev);  
  40.  len = skb->len;  
  41.  ret = dev_queue_xmit(skb);  
  42.   
  43.  if (likely(ret == NET_XMIT_SUCCESS)) {  
  44.   txq->tx_packets++;  
  45.   txq->tx_bytes += len;  
  46.  } else  
  47.   txq->tx_dropped++;  
  48.   
  49.  return ret;  
  50. }  

注册设备
      接收通过dev_add_pack(),就可以融入协议栈了,前面几篇的分析已经讲过通过ptype_base对报文进行脱壳。现在要融入的发送,函数已 经完成了,既然发送是一种嵌套的调用,并且是由dev来推过的,那么发送函数的融入一定在设备进行注册时,作为设备的一种发送方法。
      创建一种设备时,一定会有设备的XXX_setup()初始化,大部分设备都会用ether_setup()来作初始化,再进行适当更改。下面是brcm_setup():

  1. void brcm_setup(struct net_device *dev)  
  2. {  
  3.  ether_setup(dev);  
  4.   
  5.  dev->priv_flags  |= IFF_BRCM_TAG;  
  6.  dev->priv_flags  &= ~IFF_XMIT_DST_RELEASE;  
  7.  dev->tx_queue_len = 0;  
  8.   
  9.  dev->netdev_ops  = &brcm_netdev_ops;  
  10.  dev->destructor  = free_netdev;  
  11.  dev->ethtool_ops = &brcm_ethtool_ops;  
  12.   
  13.  memset(dev->broadcast, 0, ETH_ALEN);  
  14. }  

      其中发送函数就在brcm_netdev_ops中,每层设备都会这样调用:dev->netdev_ops->ndo_start_xmit()。     

  1. static const struct 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. };  
       而设备的初始化应该发生在创建设备时,也就是向网络注册它时,也就是register_brcm_dev(),注册一个新设备,需要知道它的下层设备 real_dev以及唯一标识brcm设备的brcm_port。首先确定该设备没有被创建,然后用alloc_netdev_mq创建新设备 new_dev,然后设置相关属性,特别是它的私有属性brcm_dev_info(new_dev),然后添加它到brcm_group_hash中, 最后发生真正的注册register_netdevice()。
  1. static int register_brcm_dev(struct net_device *real_dev, u16 brcm_port)  
  2. {  
  3.  struct net_device *new_dev;  
  4.  struct net *net = dev_net(real_dev);  
  5.  struct brcm_group *grp;  
  6.  char name[IFNAMSIZ];  
  7.  int err;  
  8.   
  9.  if(brcm_port >= BRCM_PORT_MASK)  
  10.   return -ERANGE;  
  11.   
  12.  // exist yet  
  13.  if (find_brcm_dev(real_dev, brcm_port) != NULL)  
  14.   return -EEXIST;  
  15.   
  16.  snprintf(name, IFNAMSIZ, "brcm%i", brcm_port);  
  17.  new_dev = alloc_netdev_mq(sizeof(struct brcm_dev_info), name,  
  18.       brcm_setup, 1);  
  19.  if (new_dev == NULL)  
  20.   return -ENOBUFS;  
  21.  new_dev->real_num_tx_queues = real_dev->real_num_tx_queues;  
  22.  dev_net_set(new_dev, net);  
  23.  new_dev->mtu = real_dev->mtu;  
  24.   
  25.  brcm_dev_info(new_dev)->brcm_port = brcm_port;  
  26.  brcm_dev_info(new_dev)->real_dev = real_dev;  
  27.  brcm_dev_info(new_dev)->dent = NULL;  
  28.  //new_dev->rtnl_link_ops = &brcm_link_ops;  
  29.   
  30.  grp = brcm_find_group(real_dev);  
  31.  if (!grp)  
  32.   grp = brcm_group_alloc(real_dev);  
  33.    
  34.  err = register_netdevice(new_dev);  
  35.  if (err < 0)  
  36.   goto out_free_newdev;  
  37.    
  38.  /* Account for reference in struct vlan_dev_info */  
  39.  dev_hold(real_dev);  
  40.  brcm_group_set_device(grp, brcm_port, new_dev);  
  41.   
  42.  return 0;  
  43.   
  44. out_free_newdev:  
  45.  free_netdev(new_dev);  
  46.  return err;  
  47. }  

ioctl
      由于brcm设备可以存在多个,并且和下层设备不是固定的对应关系,因此它的创建应该可以人为控制,因此通过ioctl由用户进行创建。这里只为brcm 提供了两种操作-添加与删除。一种设备添加一定是与下层设备成关系的,因此添加时需要手动指明这种下层设备,然后通过 __dev_get_by_name()从网络空间中找到这种设备,就可以调用register_brcm_dev()来完成注册了。而设备的删除则是直 接删除,直接删除unregister_brcm_dev()。
     

  1. static int brcm_ioctl_handler(struct net *net, void __user *arg)  
  2. {  
  3.     int err;  
  4.     struct brcm_ioctl_args args;  
  5.     struct net_device *dev = NULL;  
  6.   
  7.     if (copy_from_user(&args, arg, sizeof(struct brcm_ioctl_args)))  
  8.         return -EFAULT;  
  9.   
  10.     /* Null terminate this sucker, just in case. */  
  11.     args.device1[23] = 0;  
  12.     args.u.device2[23] = 0;  
  13.   
  14.     rtnl_lock();  
  15.   
  16.     switch (args.cmd) {  
  17.     case ADD_BRCM_CMD:  
  18.     case DEL_BRCM_CMD:  
  19.         err = -ENODEV;  
  20.         dev = __dev_get_by_name(net, args.device1);  
  21.         if (!dev)  
  22.             goto out;  
  23.   
  24.         err = -EINVAL;  
  25.         if (args.cmd != ADD_BRCM_CMD && !is_brcm_dev(dev))  
  26.             goto out;  
  27.     }  
  28.   
  29.     switch (args.cmd) {  
  30.     case ADD_BRCM_CMD:  
  31.         err = -EPERM;  
  32.         if (!capable(CAP_NET_ADMIN))  
  33.             break;  
  34.         err = register_brcm_dev(dev, args.u.port);  
  35.         break;  
  36.   
  37.     case DEL_BRCM_CMD:  
  38.         err = -EPERM;  
  39.         if (!capable(CAP_NET_ADMIN))  
  40.             break;  
  41.         unregister_brcm_dev(dev, NULL);  
  42.         err = 0;  
  43.         break;  
  44.           
  45.     default:  
  46.         err = -EOPNOTSUPP;  
  47.         break;  
  48.     }  
  49. out:  
  50.     rtnl_unlock();  
  51.     return err;  
  52. }  

        这些是brcm协议模块的主体部分了,当然它还不完整,在下篇中继续完成brcm协议的添加,为它完善一些细节:proc文件系统, notifier机制等等,以及内核Makefile的编写,当然还有协议的测试。相关源码在下篇中打包上传。

更多

阅读(1004) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~