TCP/IP学习(33)——ARP数据包的处理流程分析

1050阅读 0评论2013-11-08 491357604
分类:LINUX

本文的copyleft归gfree.wind@gmail.com所有,使用GPL发布,可以自由拷贝,转载。但转载请保持文档的完整性,注明原作者及原链接,严禁用于任何商业用途。
作者:gfree.wind@gmail.com
博客:linuxfocus.blog.chinaunix.net
    

在IPv4中,ARP是Linux TCP/IP中的neighbour system的实现,今天主要是学习一下Linux kernel的ARP代码。

ARP模块的初始化函数arp_init是由inet_init()调用。
  1. void __init arp_init(void)
  2. {
  3.     /* 初始化ARP neighbor table */
  4.     neigh_table_init(&arp_tbl);
  5.     /* 注册ARP数据包类型到L2 */
  6.     dev_add_pack(&arp_packet_type);
  7.     /* 
  8.     初始化ARP对应的/proc/net/arp
  9.     */
  10.     arp_proc_init();
  11. #ifdef CONFIG_SYSCTL
  12.     neigh_sysctl_register(NULL, &arp_tbl.parms, "ipv4", NULL);
  13. #endif
  14.     /* 
  15.     加入到netdevice的通知链。
  16.     因为ARP module需要handle网络设备的事件
  17.     */
  18.     register_netdevice_notifier(&arp_netdev_notifier);
  19. }
下面看ARP数据包的接收处理函数
  1. static int arp_rcv(struct sk_buff *skb, struct net_device *dev,
  2.          struct packet_type *pt, struct net_device *orig_dev)
  3. {
  4.     struct arphdr *arp;

  5.     /* ARP header, plus 2 device addresses, plus 2 IP addresses. */
  6.     /* 
  7.     确保buffer可以容纳ARP包。
  8.     这里arp_hdr_len从名字上看,似乎是arp的报文头长。
  9.     但是该函数返回的是return sizeof(struct arphdr) + (dev->addr_len + sizeof(u32)) * 2;
  10.     也就是整个儿ARP报文的长度。与函数名字不符。
  11.     另外,其它协议的接收处理函数,都是确保有首部的长度,而这里确实验证完整的ARP包长。
  12.     我认为是因为ARP本身就没有首部和数据部得概念的区分。
  13.     */
  14.     if (!pskb_may_pull(skb, arp_hdr_len(dev)))
  15.         goto freeskb;
     /* 拿到arp报文的指针 */ 
  1.     arp = arp_hdr(skb);
     /* 
     sanity check:
     1. arp中的l2地址长度与接收包的dev的l2地址长度不符;
     2. 接收包的device不支持ARP;
     3. 该数据包的l2地址是发送到别的主机的;
     4. 该数据包是发送到回环口的;
     5. arp中的l3地址长度不是4字节;
     */
  1.     if (arp->ar_hln != dev->addr_len ||
  2.      dev->flags & IFF_NOARP ||
  3.      skb->pkt_type == PACKET_OTHERHOST ||
  4.      skb->pkt_type == PACKET_LOOPBACK ||
  5.      arp->ar_pln != 4)
  6.         goto freeskb;
     /* 做skb的share check,因为后面的代码会修改skb,所以需要获得唯一的skb */
  1.     if ((skb = skb_share_check(skb, GFP_ATOMIC)) == NULL)
  2.         goto out_of_mem;
     /* 到此,已确定该数据包由ARP处理,所以初始化skb中的cb字段,用于arp后面的处理 */
  1.     memset(NEIGH_CB(skb), 0, sizeof(struct neighbour_cb));
     /* 通过netfilter的检查点 */
  1.     return NF_HOOK(NFPROTO_ARP, NF_ARP_IN, skb, dev, NULL, arp_process);

  2. freeskb:
  3.     kfree_skb(skb);
  4. out_of_mem:
  5.     return 0;
  6. }
继续看ARP的数据包的处理
  1. static int arp_process(struct sk_buff *skb)
  2. {
  3.     struct net_device *dev = skb->dev;
  4.     struct in_device *in_dev = __in_dev_get_rcu(dev);
  5.     struct arphdr *arp;
  6.     unsigned char *arp_ptr;
  7.     struct rtable *rt;
  8.     unsigned char *sha;
  9.     __be32 sip, tip;
  10.     u16 dev_type = dev->type;
  11.     int addr_type;
  12.     struct neighbour *n;
  13.     struct net *net = dev_net(dev);

  14.     /* arp_rcv below verifies the ARP header and verifies the device
  15.      * is ARP'able.
  16.      */
  17.     /* 该dev没有指定IP */
  18.     if (in_dev == NULL)
  19.         goto out;

  20.     arp = arp_hdr(skb);

  21.     switch (dev_type) {
  22.     default:
  23.         /* arp的l3 协议类型不是IP或者dev的l2类型与arp的l2类型不符 */
  24.         if (arp->ar_pro != htons(ETH_P_IP) ||
  25.          htons(dev_type) != arp->ar_hrd)
  26.             goto out;
  27.         break;
  28.     case ARPHRD_ETHER:
  29.     case ARPHRD_IEEE802_TR:
  30.     case ARPHRD_FDDI:
  31.     case ARPHRD_IEEE802:
  32.         /*
  33.          * ETHERNET, Token Ring and Fibre Channel (which are IEEE 802
  34.          * devices, according to RFC 2625) devices will accept ARP
  35.          * hardware types of either 1 (Ethernet) or 6 (IEEE 802.2).
  36.          * This is the case also of FDDI, where the RFC 1390 says that
  37.          * FDDI devices should accept ARP hardware of (1) Ethernet,
  38.          * however, to be more robust, we'll accept both 1 (Ethernet)
  39.          * or 6 (IEEE 802.2)
  40.          */
  41.         /* 仍然是做sanity check,原因如上面的注释 */
  42.         if ((arp->ar_hrd != htons(ARPHRD_ETHER) &&
  43.          arp->ar_hrd != htons(ARPHRD_IEEE802)) ||
  44.          arp->ar_pro != htons(ETH_P_IP))
  45.             goto out;
  46.         break;
  47.     case ARPHRD_AX25:
  48.         if (arp->ar_pro != htons(AX25_P_IP) ||
  49.          arp->ar_hrd != htons(ARPHRD_AX25))
  50.             goto out;
  51.         break;
  52.     case ARPHRD_NETROM:
  53.         if (arp->ar_pro != htons(AX25_P_IP) ||
  54.          arp->ar_hrd != htons(ARPHRD_NETROM))
  55.             goto out;
  56.         break;
  57.     }

  58.     /* Understand only these message types */
     /* 只支持ARP的正向查询 */
  1.     if (arp->ar_op != htons(ARPOP_REPLY) &&
  2.      arp->ar_op != htons(ARPOP_REQUEST))
  3.         goto out;

  4. /*
  5.  *    Extract fields
  6.  */
  7.     /* 得到ARP中的源l2地址sha,源l3地址sip和目的l3地址dip */
  8.     arp_ptr= (unsigned char *)(arp+1);
  9.     sha    = arp_ptr;
  10.     arp_ptr += dev->addr_len;
  11.     memcpy(&sip, arp_ptr, 4);
  12.     arp_ptr += 4;
  13.     arp_ptr += dev->addr_len;
  14.     memcpy(&tip, arp_ptr, 4);
  15. /*
  16.  *    Check for bad requests for 127.x.x.x and requests for multicast
  17.  *    addresses. If this is one such, delete it.
  18.  */
  19.     /* 如果目的IP为回环或者组播地址,也不处理该ARP包 */
  20.     if (ipv4_is_loopback(tip) || ipv4_is_multicast(tip))
  21.         goto out;

  22. /*
  23.  * Special case: We must set Frame Relay source Q.922 address
  24.  */
  25.     if (dev_type == ARPHRD_DLCI)
  26.         sha = dev->broadcast;

  27. /*
  28.  * Process entry. The idea here is we want to send a reply if it is a
  29.  * request for us or if it is a request for someone else that we hold
  30.  * a proxy for. We want to add an entry to our cache if it is a reply
  31.  * to us or if it is a request for our address.
  32.  * (The assumption for this last is that if someone is requesting our
  33.  * address, they are probably intending to talk to us, so it saves time
  34.  * if we cache their address. Their address is also probably not in
  35.  * our cache, since ours is not in their cache.)
  36.  *
  37.  * Putting this another way, we only care about replies if they are to
  38.  * us, in which case we add them to the cache. For requests, we care
  39.  * about those for us and those for our proxies. We reply to both,
  40.  * and in the case of requests for us we add the requester to the arp
  41.  * cache.
  42.  */

  43.     /* Special case: IPv4 duplicate address detection packet (RFC2131) */
  44.     if (sip == 0) {
  45.         /* 
  46.         如果源ip为0,则是一种特殊的ARP,用于检测重复的IP地址。
  47.         如果为ARP request,且通过检查目的地址,确认该包需要本机处理,
  48.         且不忽略ARP,则回复ARP。
  49.         */
  50.         if (arp->ar_op == htons(ARPOP_REQUEST) &&
  51.          inet_addr_type(net, tip) == RTN_LOCAL &&
  52.          !arp_ignore(in_dev, sip, tip))
  53.             arp_send(ARPOP_REPLY, ETH_P_ARP, sip, dev, tip, sha,
  54.                  dev->dev_addr, sha);
  55.         goto out;
  56.     }

  57.     if (arp->ar_op == htons(ARPOP_REQUEST) &&
  58.      ip_route_input_noref(skb, tip, sip, 0, dev) == 0) {
  59.         /* ARP为request, ip_route_input_noref返回成功。即查找input route成功 */
  60.         rt = skb_rtable(skb);
  61.         addr_type = rt->rt_type;
         /* 路由的类型进行ARP的不同处理 */
  1.         if (addr_type == RTN_LOCAL) {
  2.             /* 该ARP应有本机处理 */
  3.             int dont_send = 0;
             /* 做是否dont send的检查 */
  1.             if (!dont_send)
  2.                 dont_send |= arp_ignore(in_dev,sip,tip);
  3.             if (!dont_send && IN_DEV_ARPFILTER(in_dev))
  4.                 dont_send |= arp_filter(sip,tip,dev);
  5.             if (!dont_send) {
  6.                 /* 
  7.                 查找sip对应的neibour信息,如没有,则将数据包的源l2地址sha和l3地址sip加入到表中                     */
  8.                 n = neigh_event_ns(&arp_tbl, sha, &sip, dev);
  9.                 if (n) {
  10.                     /* 发送ARP reply */
  11.                     arp_send(ARPOP_REPLY,ETH_P_ARP,sip,dev,tip,sha,dev->dev_addr,sha);
  12.                     neigh_release(n);
  13.                 }
  14.             }
  15.             goto out;
  16.         } else if (IN_DEV_FORWARD(in_dev)) {
  17.             /* dev支持转发 */
  18.             if (addr_type == RTN_UNICAST &&
  19.              (arp_fwd_proxy(in_dev, dev, rt) ||
  20.              arp_fwd_pvlan(in_dev, dev, rt, sip, tip) ||
  21.              pneigh_lookup(&arp_tbl, net, &tip, dev, 0)))
  22.             {
  23.                 /* 
  24.                 本机enable了ARP proxy,并且与目的地址tip直连,且有tip对应的neighbour信息。
  25.                 那么,就要做arp proxy的工作了。
  26.                 */
                 
                 /*
                 将sha和sip加入到arp table中。 
                 */
  1.                 n = neigh_event_ns(&arp_tbl, sha, &sip, dev);
  2.                 if (n)
  3.                     neigh_release(n);

  4.                 if (NEIGH_CB(skb)->flags & LOCALLY_ENQUEUED ||
  5.                  skb->pkt_type == PACKET_HOST ||
  6.                  in_dev->arp_parms->proxy_delay == 0) {
  7.                     /* 回复ARP请求 */
  8.                     arp_send(ARPOP_REPLY,ETH_P_ARP,sip,dev,tip,sha,dev->dev_addr,sha);
  9.                 } else {
  10.                     /* 将数据包加入到arp table的队列中,由对应的timer handler处理 */
  11.                     pneigh_enqueue(&arp_tbl, in_dev->arp_parms, skb);
  12.                     return 0;
  13.                 }
  14.                 goto out;
  15.             }
  16.         }
  17.     }

  18.     /* Update our ARP tables */

  19.     n = __neigh_lookup(&arp_tbl, &sip, dev, 0);

  20.     if (IPV4_DEVCONF_ALL(dev_net(dev), ARP_ACCEPT)) {
  21.         /* 如果dev指定接收所有的ARP */
  22.         /* Unsolicited ARP is not accepted by default.
  23.          It is possible, that this option should be enabled for some
  24.          devices (strip is candidate)
  25.          */
         /* 
         默认情况下,只接受回复自己请求的ARP,防止ARP攻击。
         但是有的情况下,linux作为中间设备,需要学习所有经过它的ARP,那么就需要打开这个option。
         当然这就引入了风险。
         */

         /*
         没有找到对应neighbou时且本机与sip直连,那么下面的2种情况下,
         本机会学习非回复请求的ARP,即unsolicited ARP;
         1. ARP是reply;
         2. ARP为gratuitous ARP;
         */
  1.         if (n == NULL &&
  2.          (arp->ar_op == htons(ARPOP_REPLY) ||
  3.          (arp->ar_op == htons(ARPOP_REQUEST) && tip == sip)) &&
  4.          inet_addr_type(net, sip) == RTN_UNICAST)
  5.             n = __neigh_lookup(&arp_tbl, &sip, dev, 1);
  6.     }

  7.     if (n) {
  8.         /* 找到了对应的neighbour信息,那么就更新其时间戳即状态 */
  9.         int state = NUD_REACHABLE;
  10.         int override;

  11.         /* If several different ARP replies follows back-to-back,
  12.          use the FIRST one. It is possible, if several proxy
  13.          agents are active. Taking the first reply prevents
  14.          arp trashing and chooses the fastest router.
  15.          */
  16.         override = time_after(jiffies, n->updated + n->parms->locktime);

  17.         /* Broadcast replies and request packets
  18.          do not assert neighbour reachability.
  19.          */
  20.         if (arp->ar_op != htons(ARPOP_REPLY) ||
  21.          skb->pkt_type != PACKET_HOST)
  22.             state = NUD_STALE;
  23.         neigh_update(n, sha, state, override ? NEIGH_UPDATE_F_OVERRIDE : 0);
  24.         neigh_release(n);
  25.     }

  26. out:
  27.     consume_skb(skb);
  28.     return 0;
  29. }

ARP的接收流程比较简单,但是有一些特殊的ARP数据包,如检测重复IP,如果不看这个源代码,我都有些忘记了。而且从通过阅读代码,彻底搞清楚了,Linux更新ARP表的条件。
1. 如果数据包的L2地址是发送给别的主机或者回环的,则不处理;
2. 如ARP是发给本机且为request,则回复ARP并更新本机的ARP table;
3. 如ARP是需要转发,且本机为arp proxy,则回复并更新本机的ARP table;
4. 如本机指定了接收所有ARP,即接收unsolicited的ARP数据包,则在没有对应的ARP条目,且sip与本机直连:1)ARP为reply;2)ARP为gratuitous ARP时,更新ARP table;

上面的总结,并不是特别严谨,但大致的条件就是如此。


上一篇:Linux如何实现在一个CPU上只能有一个软中断运行实例
下一篇:TCP/IP学习(29)——kernel如何选择socket接收数据