IPv6 邻居创建和发现流程

来源:互联网 发布:算法优化技巧 编辑:程序博客网 时间:2024/04/28 10:29


在nieghbour.h文件中的__neigh_lookup函数,其实会在检查邻居表项中是否已经有了,该邻居。如果没有找到相应的邻居表项,就会调用neigh_create()的函数创建一个。

点击(此处)折叠或打开

  1. static inline struct neighbour *
  2. __neigh_lookup(struct neigh_table *tbl, const void*pkey, struct net_device*dev, int creat)
  3. {
  4.     struct neighbour *n = neigh_lookup(tbl, pkey, dev);

  5.     if (n|| !creat)
  6.         return n;

  7.     n = neigh_create(tbl, pkey, dev);
  8.     return IS_ERR(n)? NULL : n;
  9. }

而neigh_create(tbl,pkey, dev);函数也只有在__neigh_lookup函数中会被调用,别的函数中不会直接调用neigh_create,也就是说如果想创建一个邻居表项的话,必须首先调用__neigh_lookup函数进行查找。

下面是neigh_create函数的具体实现:

点击(此处)折叠或打开

  1. struct neighbour *neigh_create(struct neigh_table*tbl, const void *pkey,
  2.              struct net_device *dev)
  3. {
  4.     u32 hash_val;
  5.     int key_len = tbl->key_len;
  6.     int error;
  7.     struct neighbour *n1,*rc, *n = neigh_alloc(tbl);

  8.     if (!n){
  9.         rc = ERR_PTR(-ENOBUFS);
  10.         goto out;
  11.     }

  12.     memcpy(n->primary_key, pkey, key_len);
  13.     n->dev= dev;
  14.     dev_hold(dev);

  15.     /* Protocol specific setup.*/
  16.     if (tbl->constructor&&    (error= tbl->constructor(n))< 0) {
  17.         rc = ERR_PTR(error);
  18.         goto out_neigh_release;
  19.     }

  20.     /* Device specific setup.*/
  21.     if (n->parms->neigh_setup&&
  22.      (error= n->parms->neigh_setup(n))< 0) {
  23.         rc = ERR_PTR(error);
  24.         goto out_neigh_release;
  25.     }

  26.     n->confirmed= jiffies -(n->parms->base_reachable_time<< 1);

  27.     write_lock_bh(&tbl->lock);

  28.     if (atomic_read(&tbl->entries)> (tbl->hash_mask+ 1))
  29.         neigh_hash_grow(tbl,(tbl->hash_mask+ 1) << 1);

  30.     hash_val = tbl->hash(pkey, dev)& tbl->hash_mask;

  31.     if (n->parms->dead){
  32.         rc = ERR_PTR(-EINVAL);
  33.         goto out_tbl_unlock;
  34.     }

  35.     for (n1= tbl->hash_buckets[hash_val]; n1; n1 = n1->next){
  36.         if (dev== n1->dev&& !memcmp(n1->primary_key, pkey, key_len)){
  37.             neigh_hold(n1);
  38.             rc = n1;
  39.             goto out_tbl_unlock;
  40.         }
  41.     }

  42.     n->next= tbl->hash_buckets[hash_val];
  43.     tbl->hash_buckets[hash_val]= n;
  44.     n->dead= 0;
  45.     neigh_hold(n);
  46.     write_unlock_bh(&tbl->lock);
  47.     NEIGH_PRINTK2("neigh %p is created.\n", n);
  48.     rc = n;
  49. out:
  50.     return rc;
  51. out_tbl_unlock:
  52.     write_unlock_bh(&tbl->lock);
  53. out_neigh_release:
  54.     neigh_release(n);
  55.     goto out;
  56. }

首先,会调用neigh_alloc函数分配一个邻居表项的空间。

下面是neigh_alloc函数的具体实现

点击(此处)折叠或打开

  1. static struct neighbour *neigh_alloc(struct neigh_table*tbl)
  2. {
  3.     struct neighbour *n = NULL;
  4.     unsigned long now = jiffies;
  5.     int entries;

  6.     entries = atomic_inc_return(&tbl->entries)- 1;
  7.     if (entries>= tbl->gc_thresh3||
  8.      (entries >= tbl->gc_thresh2&&
  9.      time_after(now, tbl->last_flush+ 5 * HZ))){
  10.         if (!neigh_forced_gc(tbl)&&
  11.          entries >= tbl->gc_thresh3)
  12.             goto out_entries;
  13.     }

  14.     n = kmem_cache_alloc(tbl->kmem_cachep, SLAB_ATOMIC);
  15.     if (!n)
  16.         goto out_entries;

  17.     memset(n, 0, tbl->entry_size);

  18.     skb_queue_head_init(&n->arp_queue);
  19.     rwlock_init(&n->lock);
  20.     n->updated    = n->used= now;
  21.     n->nud_state    = NUD_NONE;-------------------(1)
  22.     n->output    = neigh_blackhole;----------------(2)
  23.     n->parms    = neigh_parms_clone(&tbl->parms);
  24.     init_timer(&n->timer);------------------------------(3)
  25.     n->timer.function= neigh_timer_handler;----------------(4)
  26.     n->timer.data    = (unsigned long)n;-------------------------(5)

  27.     NEIGH_CACHE_STAT_INC(tbl, allocs);
  28.     n->tbl        = tbl;
  29.     atomic_set(&n->refcnt, 1);
  30.     n->dead        = 1;
  31. out:
  32.     return n;

  33. out_entries:
  34.     atomic_dec(&tbl->entries);
  35.     goto out;
  36. }

在该函数中比较重要的代码,是用红色标识出来的。

(1)      是设置邻居表项的最开始的状态

(2)      设置发送的函数

(3)      初始化一个定时器,在邻居发现过程中,有五个状态需要相互的切换,这其中就需要定时器,这里也只是对定时器进行了初始化,并没有启动定时器

(4)      定时器的处理函数,neigh_timer_handler

(5)      定时器处理函数所需要的参数

在neigh_create函数中有下面一段代码.

点击(此处)折叠或打开

  1. /* Protocol specific setup.*/
  2.     if (tbl->constructor&&    (error= tbl->constructor(n))< 0) {
  3.         rc = ERR_PTR(error);
  4.         goto out_neigh_release;
  5.     }

该段代码会调用ndisc_constructor()函数,对于IPv6来说,如果是IPv4的话,调用arp_constructor()

点击(此处)折叠或打开

  1. static int ndisc_constructor(struct neighbour*neigh)
  2. {
  3.     struct in6_addr *addr = (struct in6_addr*)&neigh->primary_key;
  4.     struct net_device *dev = neigh->dev;
  5.     struct inet6_dev *in6_dev;
  6.     struct neigh_parms *parms;
  7.     int is_multicast = ipv6_addr_is_multicast(addr);

  8.     rcu_read_lock();
  9.     in6_dev = in6_dev_get(dev);
  10.     if (in6_dev== NULL) {
  11.         rcu_read_unlock();
  12.         return -EINVAL;
  13.     }

  14.     parms = in6_dev->nd_parms;
  15.     __neigh_parms_put(neigh->parms);
  16.     neigh->parms= neigh_parms_clone(parms);
  17.     rcu_read_unlock();

  18.     neigh->type= is_multicast ? RTN_MULTICAST: RTN_UNICAST;
  19.     if (dev->hard_header== NULL) {
  20. //不需要ARP时,例如PPPOE
  21.         neigh->nud_state= NUD_NOARP;
  22.         neigh->ops= &ndisc_direct_ops;
  23.         neigh->output= neigh->ops->queue_xmit;
  24.     } else{
  25.         if (is_multicast){
  26.             neigh->nud_state= NUD_NOARP;
  27.             ndisc_mc_map(addr, neigh->ha, dev, 1);
  28.         } elseif (dev->flags&(IFF_NOARP|IFF_LOOPBACK)){
  29.             neigh->nud_state= NUD_NOARP;
  30.             memcpy(neigh->ha, dev->dev_addr, dev->addr_len);
  31.             if (dev->flags&IFF_LOOPBACK)
  32.                 neigh->type= RTN_LOCAL;
  33.         } elseif (dev->flags&IFF_POINTOPOINT){
  34.             neigh->nud_state= NUD_NOARP;
  35.             memcpy(neigh->ha, dev->broadcast, dev->addr_len);
  36.         }
  37.         if (dev->hard_header_cache)
  38.             neigh->ops= &ndisc_hh_ops;
  39.         else
  40.             neigh->ops= &ndisc_generic_ops;
  41.         if (neigh->nud_state&NUD_VALID)
  42.             neigh->output= neigh->ops->connected_output;
  43.         else
  44.             neigh->output= neigh->ops->output;
  45.     }
  46.     in6_dev_put(in6_dev);
  47.     return 0;
  48. }

该函数主要是对各种状态进行了判断,其中标识红色的代码,设置定时器时间到期时所调用的发送处理函数。这里有对NOARP的一个判断,在PPPOE拨号成功后,在MSG设备上的WAN接口上可以看到“NOARP“的说明信息,说明该接口不需要ARP或者ND协议学习MAC地址。如果是生成另外一个单独的PPPoe接口的话,就只会在新生成的PPP0接口下生成”NOARP”的标识信息。这里如果判断是NOARP时,就是把自身的MAC地址设置为目的MAC地址,也就是发送出来的数据包的,源MAC和目的MAC是相同的。


下面是ndisc_hh_opsndisc_generic_ops结构体的定义

点击(此处)折叠或打开

  1. static struct neigh_ops ndisc_generic_ops ={
  2.     .family =        AF_INET6,
  3.     .solicit =        ndisc_solicit,
  4.     .error_report =        ndisc_error_report,
  5.     .output =        neigh_resolve_output,
  6.     .connected_output =    neigh_connected_output,
  7.     .hh_output =        dev_queue_xmit,
  8.     .queue_xmit =        dev_queue_xmit,
  9. };

  10. static struct neigh_ops ndisc_hh_ops = {
  11.     .family =        AF_INET6,
  12.     .solicit =        ndisc_solicit,
  13.     .error_report =        ndisc_error_report,
  14.     .output =        neigh_resolve_output,
  15.     .connected_output =    neigh_resolve_output,
  16.     .hh_output =        dev_queue_xmit,
  17.     .queue_xmit =        dev_queue_xmit,
  18. };

在上面的两数据结构体中分别,对output函数进行了赋值。

neigh_create函数在创建一个邻居表项成功以后,返回一个struct neighbour *的结构体指针。

在接收的NS数据报文后,也就是下面的函数

static void ndisc_recv_ns(struct sk_buff*skb)

在接收的RA数据报文后,也就是下面的函数

static void ndisc_recv_rs(struct sk_buff*skb)

在路由重定向函数中

static void ndisc_redirect_rcv(structsk_buff *skb)

在路由发现函数中,

static void ndisc_router_discovery(structsk_buff *skb)

都会调用__neigh_lookup函数,创建一个邻居表项。

这里以static void ndisc_recv_ns(struct sk_buff*skb)函数中的代码为例,看看其中的流程

点击(此处)折叠或打开

  1. neigh = __neigh_lookup(&nd_tbl, saddr, dev,
  2.              !inc || lladdr ||!dev->addr_len);
  3.     if (neigh)
  4.         neigh_update(neigh, lladdr, NUD_STALE,
  5.              NEIGH_UPDATE_F_WEAK_OVERRIDE|
  6.              NEIGH_UPDATE_F_OVERRIDE);
  7.     if (neigh|| !dev->hard_header){
  8.         ndisc_send_na(dev, neigh, saddr,&msg->target,
  9.              idev->cnf.forwarding,
  10.              1, (ifp !=NULL && inc), inc);
  11.         if (neigh)
  12.             neigh_release(neigh);
  13.     }

在调用__neigh_lookup函数成功,并返回一个邻居表项的指针时,接着调用了

neigh_update()函数,该函数主要是对邻居表项的一个信息进行了填充。这个函数才是真正的把邻居表项建立,这里“真正”的含义,在对邻居表项的很多信息进行了填充,例如重要的邻居的IP地址,MAC地址,状态,生命期等。

点击(此处)折叠或打开

  1. int neigh_update(struct neighbour*neigh, const u8 *lladdr, u8 new,
  2.          u32 flags)
  3. {
  4.     u8 old;
  5.     int err;
  6. #ifdef CONFIG_ARPD
  7.     int notify = 0;
  8. #endif
  9.     struct net_device *dev;
  10.     int update_isrouter = 0;

  11.     write_lock_bh(&neigh->lock);

  12.     dev = neigh->dev;
  13.     old = neigh->nud_state;
  14.     err =-EPERM;

  15.     if (!(flags& NEIGH_UPDATE_F_ADMIN)&&
  16.      (old &(NUD_NOARP | NUD_PERMANENT)))
  17.         goto out;

  18.     if (!(new& NUD_VALID)){
  19.         neigh_del_timer(neigh);
  20.         if (old& NUD_CONNECTED)
  21.             neigh_suspect(neigh);
  22.         neigh->nud_state= new;
  23.         err = 0;
  24. #ifdef CONFIG_ARPD
  25.         notify = old & NUD_VALID;
  26. #endif
  27.         goto out;
  28.     }

  29.     /* Compare new lladdr with cached one*/
  30.     if (!dev->addr_len){
  31.         /* Firstcase: device needs no address.*/
  32.         lladdr = neigh->ha;
  33.     } elseif (lladdr){
  34.         /* Thesecond case:if something is already cached
  35.          and a new address is proposed:
  36.          - compare new & old
  37.          - if they are different, check override flag
  38.          */
  39.         if ((old& NUD_VALID)&&
  40.          !memcmp(lladdr, neigh->ha, dev->addr_len))
  41.             lladdr = neigh->ha;
  42.     } else{
  43.         /* No addressis supplied;if we know something,
  44.          use it, otherwise discard the request.
  45.          */
  46.         err =-EINVAL;
  47.         if (!(old& NUD_VALID))
  48.             goto out;
  49.         lladdr = neigh->ha;
  50.     }

  51.     if (new& NUD_CONNECTED)
  52.         neigh->confirmed= jiffies;
  53.     neigh->updated= jiffies;

  54.     /* If entry was valid and address is not changed,
  55.      do not change entry state,if new one is STALE.
  56.      */
  57.     err = 0;
  58.     update_isrouter = flags & NEIGH_UPDATE_F_OVERRIDE_ISROUTER;
  59.     if (old& NUD_VALID){
  60.         if (lladdr!= neigh->ha&& !(flags & NEIGH_UPDATE_F_OVERRIDE)){
  61.             update_isrouter = 0;
  62.             if ((flags & NEIGH_UPDATE_F_WEAK_OVERRIDE)&&
  63.              (old & NUD_CONNECTED)){
  64.                 lladdr = neigh->ha;
  65.                 new = NUD_STALE;
  66.             } else
  67.                 goto out;
  68.         } else{
  69.             if (lladdr == neigh->ha&& new == NUD_STALE &&
  70.              ((flags& NEIGH_UPDATE_F_WEAK_OVERRIDE)||
  71.              (old & NUD_CONNECTED))
  72.              )
  73.                 new = old;
  74.         }
  75.     }

  76.     if (new!= old){
  77.         neigh_del_timer(neigh);
  78.         if (new& NUD_IN_TIMER){
  79.             neigh_hold(neigh);
  80.             neigh_add_timer(neigh,(jiffies +
  81.                         ((new& NUD_REACHABLE)?
  82.                          neigh->parms->reachable_time:
  83.                          0)));------------------(1)
  84.         }
  85.         neigh->nud_state= new;
  86.     }

  87.     if (lladdr!= neigh->ha){
  88.         memcpy(&neigh->ha, lladdr, dev->addr_len);
  89.         neigh_update_hhs(neigh);
  90.         if (!(new& NUD_CONNECTED))
  91.             neigh->confirmed= jiffies -
  92.                  (neigh->parms->base_reachable_time<< 1);
  93. #ifdef CONFIG_ARPD
  94.         notify = 1;
  95. #endif
  96.     }
  97.     if (new== old)
  98.         goto out;
  99.     if (new& NUD_CONNECTED)
  100.         neigh_connect(neigh);
  101.     else
  102.         neigh_suspect(neigh);
  103.     if (!(old& NUD_VALID)){
  104.         struct sk_buff *skb;

  105.         /* Again: avoid deadloop if something went wrong*/

  106.         while (neigh->nud_state& NUD_VALID &&
  107.          (skb = __skb_dequeue(&neigh->arp_queue))!= NULL) {
  108.             struct neighbour *n1 = neigh;
  109.             write_unlock_bh(&neigh->lock);
  110.             /*On shaper/eql skb->dst->neighbour!= neigh :( */
  111.             if (skb->dst&& skb->dst->neighbour)
  112.                 n1 = skb->dst->neighbour;
  113.             n1->output(skb);-----------------------------(2)
  114.             write_lock_bh(&neigh->lock);
  115.         }
  116.         skb_queue_purge(&neigh->arp_queue);
  117.     }
  118. out:
  119.     if (update_isrouter){
  120.         neigh->flags= (flags & NEIGH_UPDATE_F_ISROUTER) ?
  121.             (neigh->flags| NTF_ROUTER):
  122.             (neigh->flags& ~NTF_ROUTER);
  123.     }
  124.     write_unlock_bh(&neigh->lock);
  125. #ifdef CONFIG_ARPD
  126.     if (notify&& neigh->parms->app_probes)
  127.         neigh_app_notify(neigh);
  128. #endif
  129.     return err;
  130. }

该函数主要是对邻居表项的状态进行了判断,还有就是对定时器的启动

(1)      当新(new)的状态和老(old)的状态不匹配的时候,就会从新启动相应的定时器

(2)      调用上面的output函数指针进行赋值后的output函数,这里是neigh_resolve_output函数

点击(此处)折叠或打开

  1. int neigh_resolve_output(struct sk_buff*skb)
  2. {
  3.     struct dst_entry *dst = skb->dst;
  4.     struct neighbour *neigh;
  5.     int rc = 0;

  6.     if (!dst|| !(neigh = dst->neighbour))
  7.         goto discard;

  8.     __skb_pull(skb, skb->nh.raw- skb->data);

  9.     if (!neigh_event_send(neigh, skb)){
  10.         int err;
  11.         struct net_device *dev = neigh->dev;
  12.         if (dev->hard_header_cache&& !dst->hh){
  13.             write_lock_bh(&neigh->lock);
  14.             if (!dst->hh)
  15.                 neigh_hh_init(neigh, dst, dst->ops->protocol);
  16.             err = dev->hard_header(skb, dev, ntohs(skb->protocol),
  17.                      neigh->ha,NULL, skb->len);
  18.             write_unlock_bh(&neigh->lock);
  19.         } else{
  20.             read_lock_bh(&neigh->lock);
  21.             err = dev->hard_header(skb, dev, ntohs(skb->protocol),
  22.                      neigh->ha,NULL, skb->len);---------(1)
  23.             read_unlock_bh(&neigh->lock);
  24.         }
  25.         if (err>= 0)
  26.             rc = neigh->ops->queue_xmit(skb);
  27.         else
  28.             goto out_kfree_skb;
  29.     }
  30. out:
  31.     return rc;
  32. discard:
  33.     NEIGH_PRINTK1("neigh_resolve_output: dst=%p neigh=%p\n",
  34.          dst, dst ? dst->neighbour: NULL);
  35. out_kfree_skb:
  36.     rc = -EINVAL;
  37.     kfree_skb(skb);
  38.     goto out;
  39. }

(1)在netdevice.h文件中下面的函数调用

点击(此处)折叠或打开

  1. static inline int dev_hard_header(struct sk_buff*skb, struct net_device*dev,
  2.                  unsigned short type,
  3.                  const void *daddr, const void*saddr,
  4.                  unsigned len)
  5. {
  6. //检测设备是否有头部操作集
  7.   //检测操作集是否有创建操作
  8.     if (!dev->header_ops|| !dev->header_ops->create)
  9.         return 0;

  10.     return dev->header_ops->create(skb, dev, type, daddr, saddr,len);
  11. }

由于这里的设备是以太网设备,在以太网设备进行初始化的时候,会有调用下面的函数

点击(此处)折叠或打开

  1. void ether_setup(struct net_device *dev)
  2. {
  3.     dev->header_ops        =&eth_header_ops;
  4.     dev->type        = ARPHRD_ETHER;
  5.     dev->hard_header_len     = ETH_HLEN;
  6.     dev->mtu        = ETH_DATA_LEN;
  7.     dev->addr_len        = ETH_ALEN;
  8.     dev->tx_queue_len    = 1000;    /* Ethernet wants good queues */
  9.     dev->flags        = IFF_BROADCAST|IFF_MULTICAST;

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

  11. }

这样,在调用dev->header_ops->create个函数时,实际上是调用的eth_headr


点击(此处)折叠或打开

  1. const struct header_ops eth_header_ops ____cacheline_aligned= {
  2.     .create        = eth_header,
  3.     .parse        = eth_header_parse,
  4.     .rebuild    = eth_rebuild_header,
  5.     .cache        = eth_header_cache,
  6.     .cache_update    = eth_header_cache_update,
  7. };
  8. /**
  9.  * eth_header - create the Ethernet header
  10.  * @skb:    bufferto alter
  11.  * @dev:    source device
  12.  * @type:    Ethernet type field
  13.  * @daddr: destination address(NULL leave destination address)
  14.  * @saddr: source address(NULL use device source address)
  15.  * @len: packet length(<= skb->len)
  16.  *
  17.  *
  18.  * Set the protocol type.For a packet of type ETH_P_802_3/2 we put the length
  19.  * in here instead.
  20.  */
  21. 创建以太网的头部,
  22. int eth_header(struct sk_buff*skb, struct net_device*dev,
  23.      unsigned short type,
  24.      const void *daddr, const void*saddr, unsignedlen)
  25. {
  26.     struct ethhdr *eth = (struct ethhdr *)skb_push(skb, ETH_HLEN);//加入到SKB的buf中,使用skb_push向上增长

  27.     if (type!= ETH_P_802_3&& type != ETH_P_802_2)
  28.         eth->h_proto= htons(type);
  29.     else
  30.         eth->h_proto= htons(len);

  31.     /*
  32.      * Set the source hardware address.
  33.      */

  34.     if (!saddr)
  35.         saddr = dev->dev_addr;
  36.     memcpy(eth->h_source, saddr, ETH_ALEN);

  37.     if (daddr){
  38.         memcpy(eth->h_dest, daddr, ETH_ALEN);
  39.         return ETH_HLEN;
  40.     }

  41.     /*
  42.      * Anyway, the loopback-device should never use thisfunction...
  43.      */

  44.     if (dev->flags& (IFF_LOOPBACK| IFF_NOARP)){
  45.         memset(eth->h_dest, 0, ETH_ALEN);
  46.         return ETH_HLEN;
  47.     }

  48.     return -ETH_HLEN;
  49. }

(2)这个函数是和三层协议进行交互的接口,在IP层协议发送数据时,也就是在static int ip6_finish_output2(struct sk_buff *skb)函数中的最后会调用下面的两个函数,

   if (dst->hh)

                         return neigh_hh_output(dst->hh, skb);

         else if(dst->neighbour)

            returndst->neighbour->output(skb);

      这两个函数调用,最后可能的调用的就是neigh_resolve_output,加上以太网的报头后,通过dev_queue_xmit函数调用,发送出去。

在该函数中调用neigh_event_send()进行发送ND报文

如果这个表项创建成功后,接下来就调用ndisc_send_na函数,发送NA报文。

ndisc_send_na(dev, neigh, saddr, &msg->target,

                                  idev->cnf.forwarding,

                                  1, (ifp != NULL && inc), inc);

 

 

自此,在IPv6中的邻居表项就创建完成了。
0 0
原创粉丝点击