天天看点

linux网络协议栈分析笔记9-arp邻居子系统2

上回说到流量输出会走到 neigh_resolve_output    我们来看看邻居这玩意能玩点什么花样,传说的arp学习在哪里 int   neigh_resolve_output(struct sk_buff *skb) {

     struct dst_entry *dst = skb_dst(skb);

     struct neighbour *neigh;

     int rc = 0;

     if (!dst || !(neigh = dst->neighbour))        异常退出

          goto discard;

     __skb_pull(skb, skb_network_offset(skb));

      if (!neigh_event_send(neigh, skb)) {            判断邻居项是否有可用状态,如果可用,则把数据包发送出去

           int err;

          struct net_device *dev = neigh->dev;

          if (dev->header_ops->cache && !dst->hh) {

               write_lock_bh(&neigh->lock);

               if (!dst->hh)

                    neigh_hh_init(neigh, dst, dst->ops->protocol);

               err = dev_hard_header(skb, dev, ntohs(skb->protocol),

                               neigh->ha, NULL, skb->len);

               write_unlock_bh(&neigh->lock);

          } else {

               read_lock_bh(&neigh->lock);

               err = dev_hard_header(skb, dev, ntohs(skb->protocol),

                               neigh->ha, NULL, skb->len);

               read_unlock_bh(&neigh->lock);

          }

          if (err >= 0)

               rc = neigh->ops->queue_xmit(skb);

          else

               goto out_kfree_skb;

     }

out:

     return rc;

discard:

     NEIGH_PRINTK1("neigh_resolve_output: dst=%p neigh=%p\n",

                dst, dst ? dst->neighbour : NULL);

out_kfree_skb:

     rc = -EINVAL;

     kfree_skb(skb);

     goto out;

} 这里叫邻居事件发送,自己瞎叫的

static inline int   neigh_event_send(struct neighbour *neigh, struct sk_buff *skb)  

{

     neigh->used = jiffies;

     if (!(neigh->nud_state& (NUD_CONNECTED|NUD_DELAY|NUD_PROBE)))              假设带着NUD_NONE进来的

          return   __neigh_event_send(neigh, skb);

     return 0;

} 这里开始涉及状态的东西了   先对NUD状态描述下 #define   NUD_INCOMPLETE      0x01         一个请求已发送出去,但还没收到应答,在这个状态不使用任何硬件地址

#define   NUD_REACHABLE      0x02           邻居的地址被放入缓存,并且知道该邻居是可到达的

#define NUD_STALE     0x04                

#define NUD_DELAY     0x08

#define NUD_PROBE     0x10             这三个用于状态转换阶段,当本地主机确定邻居是否可到达时,状态会发生改变    

#define NUD_FAILED     0x20              由于请求失败,将邻居标记为不可达

#define NUD_NOARP     0x40                   用于标记不要任何协议进行L3到L2的地址映射的邻居

#define NUD_PERMANENT     0x80           邻居的L2地址是静态配置,因此不需要邻居协议进行地址解析

#define NUD_NONE     0x00                    邻居项刚被创建,还没有状态可用

派生状态 NUD_IN_TIMER :                当某一邻居项的状态不是很清晰时,邻居子系统就为其运行一个定时器 #define NUD_IN_TIMER     (NUD_INCOMPLETE|NUD_REACHABLE|NUD_DELAY|NUD_PROBE)    

#define NUD_VALID     (NUD_PERMANENT|NUD_NOARP|NUD_REACHABLE|NUD_PROBE|NUD_STALE|NUD_DELAY) NUD_CONNECTED  :       这个状态为NUD_VALID的子状态,没有未决的确认要处理 #define NUD_CONNECTED     (NUD_PERMANENT|NUD_NOARP|NUD_REACHABLE) int   __neigh_event_send(struct neighbour *neigh, struct sk_buff *skb)

{

     int rc;

     unsigned long now;

     write_lock_bh(&neigh->lock);

     rc = 0;

     if (neigh->nud_state & (NUD_CONNECTED | NUD_DELAY | NUD_PROBE))

          goto out_unlock_bh;

     now = jiffies;

     if (!( neigh->nud_state & (NUD_STALE | NUD_INCOMPLETE))) {

          if (neigh->parms->mcast_probes + neigh->parms->app_probes) {                   .mcast_probes = 3,

               atomic_set(&neigh->probes, neigh->parms->ucast_probes);     重发arp请求的次数  .ucast_probes =  3,

               neigh->nud_state     = NUD_INCOMPLETE;     设状态为NUD_INCOMPLETE,

                neigh->updated = jiffies;

                 neigh_add_timer(neigh, now + 1);                  且设置定时器

          } else {

               neigh->nud_state = NUD_FAILED;

               neigh->updated = jiffies;

               write_unlock_bh(&neigh->lock);

               kfree_skb(skb);

               return 1;

          }

     } else if (neigh->nud_state & NUD_STALE) {

          NEIGH_PRINTK2("neigh %p is delayed.\n", neigh);

          neigh->nud_state = NUD_DELAY;

          neigh->updated = jiffies;

          neigh_add_timer(neigh,

                    jiffies + neigh->parms->delay_probe_time);

     }

     if (neigh->nud_state == NUD_INCOMPLETE) {           如果是NUD_INCOMPLETE状态

          if (skb) {

     if (skb_queue_len(&neigh->arp_queue) >= 检查队列长度,应该是每个邻居项都有一个可供缓存待解析的skb

                   neigh->parms->queue_len) {              .queue_len =          3,

                    struct sk_buff *buff;

                    buff = __skb_dequeue(&neigh->arp_queue);

                    kfree_skb(buff);                               取出一个  释放掉     

                    NEIGH_CACHE_STAT_INC(neigh->tbl, unres_discards);

               }

               __skb_queue_tail(&neigh->arp_queue, skb);       把这个新的放进去

          }

          rc = 1;

     }

out_unlock_bh:

     write_unlock_bh(&neigh->lock);

     return rc;

} 上面这段函数的主要作用将邻居状态置成NUD_INCOMPLETE,并启动定时器,我们看下定时器里干了啥 setup_timer(&n->timer,   neigh_timer_handler, (unsigned long)n); neigh_timer_handler():      大概看了看,都是定时器到期后处理各种状态迁移,找到我们分析的状态      if (neigh->nud_state & (NUD_INCOMPLETE | NUD_PROBE)) {

          struct sk_buff *skb = skb_peek(&neigh->arp_queue);

          if (skb)

               skb = skb_copy(skb, GFP_ATOMIC);

          write_unlock(&neigh->lock);

            neigh->ops->solicit(neigh, skb);               在 arp_constructor()中被置上

          atomic_inc(&neigh->probes);

          kfree_skb(skb); static const struct neigh_ops arp_hh_ops = {

     .family =          AF_INET,

      .solicit =          arp_solicit,

     .error_report =          arp_error_report,

     .output =          neigh_resolve_output,

     .connected_output =     neigh_resolve_output,

     .hh_output =          dev_queue_xmit,

     .queue_xmit =          dev_queue_xmit,

}; ->arp_solicit()           ->arp_send(ARPOP_REQUEST, ETH_P_ARP, target, dev, saddr,dst_ha, dev->dev_addr, NULL); 通过arp_send发送ARP REQUEST报文 发送看累了,看看arp的接收吧 static int arp_rcv(struct sk_buff *skb, struct net_device *dev,

             struct packet_type *pt, struct net_device *orig_dev)

{

     struct arphdr *arp;

     if (!pskb_may_pull(skb, arp_hdr_len(dev)))

          goto freeskb;

     arp = arp_hdr(skb);

     if (arp->ar_hln != dev->addr_len ||

         dev->flags & IFF_NOARP ||

         skb->pkt_type == PACKET_OTHERHOST ||

         skb->pkt_type == PACKET_LOOPBACK ||

         arp->ar_pln != 4)

          goto freeskb;

     if ((skb = skb_share_check(skb, GFP_ATOMIC)) == NULL)

          goto out_of_mem;                                                                       一系列的合法性检查

     memset(NEIGH_CB(skb), 0, sizeof(struct neighbour_cb));   这里把skb的cb区域用作neighbour_cb结构空间

     return NF_HOOK(NFPROTO_ARP, NF_ARP_IN, skb, dev, NULL,   arp_process);

freeskb:

     kfree_skb(skb);

out_of_mem:

     return 0;

}      struct arphdr          arp头结构

{

     __be16          ar_hrd;       硬件地址格式

     __be16          ar_pro;        协议地址格式

     unsigned char     ar_hln;      硬件地址长度

     unsigned char     ar_pln;     协议地址长度

     __be16          ar_op;         命令代码

#if 0

     unsigned char          ar_sha[ETH_ALEN];    

     unsigned char          ar_sip[4];         

     unsigned char          ar_tha[ETH_ALEN];    

     unsigned char          ar_tip[4];         

#endif

}; 进入arp_process()      ->arp = arp_hdr(skb);  获得ARP头

     ->if (arp->ar_op != htons(ARPOP_REPLY) &&arp->ar_op != htons(ARPOP_REQUEST))                        goto out;                          不是reply 也不是 request 那是啥呢?      ->                arp_ptr= (unsigned char *)(arp+1);

          sha     = arp_ptr;                         发送端以太网首址

          arp_ptr += dev->addr_len;

          memcpy(&sip, arp_ptr, 4);             发送端ip地址

          arp_ptr += 4;

          arp_ptr += dev->addr_len;

          memcpy(&tip, arp_ptr, 4);             目的IP地址

     ->      if (ipv4_is_loopback(tip) || ipv4_is_multicast(tip))    环回或多播           goto out;      -> if (arp->ar_op == htons(ARPOP_REQUEST) &&                 收到一个请求包

         ip_route_input(skb, tip, sip, 0, dev) == 0) {

          rt = skb_rtable(skb);  

          addr_type = rt->rt_type;

           if (addr_type == RTN_LOCAL) {                             若是送往本机的arp请求包

               int dont_send = 0;

               if (!dont_send)

                    dont_send |= arp_ignore(in_dev,sip,tip);

               if (!dont_send && IN_DEV_ARPFILTER(in_dev))

                    dont_send |= arp_filter(sip,tip,dev);

               if (!dont_send) {

                    n = n eigh_event_ns(&arp_tbl, sha, &sip, dev);           更新arp邻居表

                    if (n) {

                          arp_send(ARPOP_REPLY,ETH_P_ARP,sip,dev,tip,sha,dev->dev_addr,sha);  发送arp应答

                         neigh_release(n);

                    }

               }

               goto out;

           } else if (IN_DEV_FORWARD(in_dev)) {          代理arp

                   if (addr_type == RTN_UNICAST  && rt->u.dst.dev != dev &&

                    (arp_fwd_proxy(in_dev, rt) || pneigh_lookup(&arp_tbl, net, &tip, dev, 0))) {

                    n = neigh_event_ns(&arp_tbl, sha, &sip, dev);

                    if (n)

                         neigh_release(n);

                    if (NEIGH_CB(skb)->flags & LOCALLY_ENQUEUED ||

                        skb->pkt_type == PACKET_HOST ||

                        in_dev->arp_parms->proxy_delay == 0) {

                         arp_send(ARPOP_REPLY,ETH_P_ARP,sip,dev,tip,sha,dev->dev_addr,sha);

                    } else {

                         pneigh_enqueue(&arp_tbl, in_dev->arp_parms, skb);

                         in_dev_put(in_dev);

                         return 0;

                    }

                    goto out;

               }

          }

     }          n = __neigh_lookup(&arp_tbl, &sip, dev, 0);       对arp应答包的处理          用源ip去邻居表里查是否有邻居项

     if (IPV4_DEVCONF_ALL(dev_net(dev), ARP_ACCEPT)) {

          if (n == NULL &&

              arp->ar_op == htons(ARPOP_REPLY) &&

              inet_addr_type(net, sip) == RTN_UNICAST)

               n = __neigh_lookup(&arp_tbl, &sip, dev, 1);

     }

     if (n) {

          int state = NUD_REACHABLE;             如果存在

          int override;

          override = time_after(jiffies, n->updated + n->parms->locktime);

          if (arp->ar_op != htons(ARPOP_REPLY) ||

              skb->pkt_type != PACKET_HOST)

               state = NUD_STALE;

           neigh_update(n, sha, state, override ? NEIGH_UPDATE_F_OVERRIDE : 0);        更新邻居项状态

          neigh_release(n);

     } 涉及到状态迁移的东西挺复杂的,我们就不深入分析了,这里只是输出下大体流程和主要数据结构的组织,关于arp表项的状态迁移 转网上达人的分析共同学习:

在上图中,我们看到只有arp缓存项的reachable状态对于外发包是可用的,对于stale状态的arp缓存项而言,它实际上是不可用的。如果此时有人要发包,那么需要进行重新解析,对于常规的理解,重新解析意味着要重新发送arp请求,然后事实上却不一定这样,因为Linux为arp增加了一个“事件点”来“不用发送arp请求”而对arp协议生成的缓存维护的优化措施,事实上,这种措施十分有效。这就是arp的“确认”机制,也就是说,如果说从一个邻居主动发来一个数据包到本机,那么就可以确认该包的“上一跳”这个邻居是有效的,然而为何只有到达本机的包才能确认“上一跳”这个邻居的有效性呢?因为Linux并不想为IP 层的处理增加负担,也即不想改变IP 层的原始语义。  

Linux维护一个stale状态其实就是为了保留一个neighbour结构体,在其状态改变时只是个别字段得到修改或者填充。如果按照简单的实现,只保存一个reachable状态即可,其到期则删除arp缓存表项。Linux的做法只是做了很多的优化,但是如果你为这些优化而绞尽脑汁,那就悲剧了... 

linux中是如何维护这个state状态的? 在Linux实现的ARP状态机中,最复杂的就是stale状态了,在此状态中的arp缓存表项面临着生死抉择,抉择者就是本地发出的包,如果本地发出的包使用了这个stale状态的arp缓存表项,那么就将状态机推进到delay状态,如果在“垃圾收集”定时器到期后还没有人使用该邻居,那么就有可能删除这个表项了,到底删除吗?这样看看有木有其它路径使用它,关键是看路由缓存,路由缓存虽然是一个第三层的概念,然而却保留了该路由的下一条的ARP缓存表项,这个意义上,Linux的路由缓存实则一个转发表而不是一个路由表。 

如果有外发包使用了这个表项,那么该表项的ARP状态机将进入delay状态,在delay状态中,只要有“本地”确认的到来(本地接收包的上一跳来自该邻居),linux 还是不会发送ARP请求的,但是如果一直都没有本地确认,那么Linux就将发送真正的ARP请求了,进入probe状态。因此可以看到,从stale状态开始,所有的状态只是为一种优化措施而存在的,stale状态的ARP缓存表项就是一个缓存的缓存,如果Linux只是将过期的reachable状态的arp缓存表项删除,语义是一样的,但是实现看起来以及理解起来会简单得多! 

再次强调,reachable过期进入stale状态而不是直接删除,是为了保留neighbour结构体,优化内存以及CPU利用,实际上进入stale状态的arp缓存表项时不可用的,要想使其可用,要么在delay状态定时器到期前本地给予了确认,比如tcp收到了一个包,要么delay状态到期进入probe状态后arp请求得到了回应。否则还是会被删除。