上面一节分析了arp协议的初始化过程。本节主要是arp数据包的处理流程,在arp初始化时,通过调用dev_add_pack将arp协议的接收处理函数添加到了三层协议数据包处理函数相关的hash链表ptype_base中(关于三层协议数据包处理函数相关的hash链表,请参考文档http://blog.csdn.net/lickylin/article/details/22900401)。当底层接收到属于本机的arp数据包时,就会调用arp_rcv进行后续处理。

    下面我们就分析arp_rcv以及与其相关的函数.

 

arp_rcv的定义如下, 该函数主要就是arp_process的封装,相比arp_process主要是增加了arp数据包的合理性检查,以及增加防火墙的hook函数。对于满足要求的数据包才会调用arp_process。

 

功能:对接收到的arp数据包的处理函数

1、首先对arp数据包进行合理性检查

2、调用NF_HOOK,判断是否需要对arp进行进一步的处理,对于需要

       进一步处理的数据包,则调用arp_process进行后续处理。

static intarp_rcv(struct sk_buff *skb, struct net_device *dev,

         struct packet_type *pt, struct net_device *orig_dev)

{

    struct arphdr *arp;

 

    /* ARP header, plus 2 device addresses, plus2 IP addresses.  */

    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(structneighbour_cb));

 

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

 

freeskb:

    kfree_skb(skb);

out_of_mem:

    return 0;

}

 

 

下面我们分析arp_process

功能:处理一个arp请求

对于arp_process,主要是考虑如下几个方面:

1、arp数据包的格式是否正确,是否是属于系统支持的邻居项协议

2、是否需要丢弃接收到的arp数据包

3、处理符合条件的arp数据包。

下面是处理arp包的几个条件:

丢弃数据包的标准:

1、arp_process只处理request、reply的arp数据包,丢弃其他类型的数据包

    a)对于类型为request的数据包,丢弃目的地址是组播或者loopback的arp数据。

 

对于需要处理的数据包,大致可以分为几个方面:

1、对本机发送的arp 请求的应答数据包的处理

2、arp 请求数据包

       a)目的地址是本地地址,且源地址不为0的arp 请求数据包

       b)目的地址是本地地址,且源地址为0的重复地址检测的arp请求数据包

3、非本地发送的arp请求的应答数据包

 

static intarp_process(struct sk_buff *skb)

{

    struct net_device *dev = skb->dev;

    struct in_device *in_dev = in_dev_get(dev);

    struct arphdr *arp;

    unsigned char *arp_ptr;

    struct rtable *rt;

    unsigned char *sha;

    __be32 sip, tip;

    u16 dev_type = dev->type;

    int addr_type;

    struct neighbour *n;

    struct net *net = dev_net(dev);

 

    /* arp_rcv below verifies the ARP header andverifies the device

     * isARP'able.

     */

 

    if (in_dev == NULL)

       goto out;

 

    /*调用arp_hdr获取skb数据中arp头的开始指针*/

    arp = arp_hdr(skb);

 

    /*判断设备的类型与数据包中的硬件类型是否相符*/

    switch (dev_type) {

    default:

       if (arp->ar_pro != htons(ETH_P_IP) ||

          htons(dev_type) != arp->ar_hrd)

           goto out;

       break;

    case ARPHRD_ETHER:

    case ARPHRD_IEEE802_TR:

    case ARPHRD_FDDI:

    case ARPHRD_IEEE802:

       /*

        *ETHERNET, Token Ring and Fibre Channel (which are IEEE 802

        *devices, according to RFC 2625) devices will accept ARP

        *hardware types of either 1 (Ethernet) or 6 (IEEE 802.2).

        *This is the case also of FDDI, where the RFC 1390 says that

        *FDDI devices should accept ARP hardware of (1) Ethernet,

        *however, to be more robust, we'll accept both 1 (Ethernet)

        *or 6 (IEEE 802.2)

        */

       if ((arp->ar_hrd !=htons(ARPHRD_ETHER) &&

           arp->ar_hrd != htons(ARPHRD_IEEE802)) ||

          arp->ar_pro != htons(ETH_P_IP))

           goto out;

       break;

    case ARPHRD_AX25:

       if (arp->ar_pro != htons(AX25_P_IP) ||

          arp->ar_hrd != htons(ARPHRD_AX25))

           goto out;

       break;

    case ARPHRD_NETROM:

       if (arp->ar_pro != htons(AX25_P_IP) ||

          arp->ar_hrd != htons(ARPHRD_NETROM))

           goto out;

       break;

    }

 

    /* Understand only these message types */

 

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

       arp->ar_op != htons(ARPOP_REQUEST))

       goto out;

 

/*

 *  Extractfields

 */

    /*

    获取arp数据包中源mac地址、源ip地址、目的mac地址、目的ip地址

    */

    arp_ptr= (unsigned char *)(arp+1);

    sha =arp_ptr;

    arp_ptr += dev->addr_len;

    memcpy(&sip, arp_ptr, 4);

    arp_ptr += 4;

    arp_ptr += dev->addr_len;

    memcpy(&tip, arp_ptr, 4);

/*

 *  Checkfor bad requests for 127.x.x.x and requests for multicast

 *  addresses.  If this is one such, delete it.

 */

    /*丢弃目的地址是组播或者loopback的arp数据(对于组播地址和loopback地址是不需要arp)*/

    if (ipv4_is_loopback(tip) ||ipv4_is_multicast(tip))

       goto out;

 

/*

 *    Special case: We must set Frame Relay source Q.922 address

 */

    if (dev_type == ARPHRD_DLCI)

       sha = dev->broadcast;

 

/*

 * Process entry.  The idea here iswe want to send a reply if it is a

 * request for us or if it is a request for someone else that we hold

 *  aproxy for.  We want to add an entry toour cache if it is a reply

 *  to usor if it is a request for our address.

 *  (Theassumption for this last is that if someone is requesting our

 * address, they are probably intending to talk to us, so it saves time

 *  if wecache their address.  Their address isalso probably not in

 *  ourcache, since ours is not in their cache.)

 *

 * Putting this another way, we only care about replies if they are to

 *  us,in which case we add them to the cache. For requests, we care

 *  aboutthose for us and those for our proxies. We reply to both,

 *  andin the case of requests for us we add the requester to the arp

 * cache.

 */

 

    /* Special case: IPv4 duplicate addressdetection packet (RFC2131) */

 

    /*对于源ip地址是0的arp请求,一般用于重复地址检测,

        此时如果arp类型为request,且目的ip地址是本地地址,且可以进行arp应答时,

        则调用arp_send发送arp reply数据包。*/

 

    /*

        对于源地址为0的数据包,在发送arp 应答报文时,为什么没有先查找路由表呢?

      在我们建立路由表时,都会建立一个全零的默认路由,所以对于目的ip为0的

      数据包,其路由是一直存在的。所以在处理时不用查找路由表,直接生成

      arpreply数据包,并发送出去。

        */

    if (sip == 0) {

       if (arp->ar_op == htons(ARPOP_REQUEST)&&

          inet_addr_type(net, tip) == RTN_LOCAL &&

          !arp_ignore(in_dev, sip, tip))

           arp_send(ARPOP_REPLY, ETH_P_ARP, sip,dev, tip, sha,

               dev->dev_addr, sha);

       goto out;

    }

    /*

    对于arp 类型为request的数据包,且能找到到目的地址tip的路由,则执行下面的代码

    */

    if (arp->ar_op == htons(ARPOP_REQUEST)&&

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

       /*获取tip对应的路由缓存*/

       rt = skb_rtable(skb);

       addr_type = rt->rt_type;

 

       /*1、如果路由缓存对应的ip地址类型为local,则调用neigh_event_ns,查找符合条件的邻居项

              a)如果找到符合条件的邻居项,则调用arp_send发送对该arp request包的reply包,并返回

              b)直接返回。

         2、如果路由缓存对应的ip地址类型不是local,则进行arp proxy的处理,完成后

                直接返回

       */

       if (addr_type == RTN_LOCAL) {

           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 = neigh_event_ns(&arp_tbl,sha, &sip, dev);

              if (n) {

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

                  neigh_release(n);

              }

           }

           goto out;

       } else if (IN_DEV_FORWARD(in_dev)) {

           if (addr_type == RTN_UNICAST  &&

              (arp_fwd_proxy(in_dev, dev, rt) ||

               arp_fwd_pvlan(in_dev, dev, rt, sip, tip) ||

               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;

           }

       }

    }

 

    /* Update our ARP tables */

 

 

    /*

    1、对于arp reply数据包,进入下面的处理流程

    2、对于arp request数据包,且没有找到tip ip对应的路由缓存

    */

 

    /*调用__neigh_lookup,查找arp_tbl的neighbour hash bucket,查找sip对应的邻居项*/

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

 

 

    /*对于系统允许非arp请求的arp reply,则进行如下处理*/

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

       /* Unsolicited ARP is not accepted bydefault.

         It is possible, that this option should be enabled for some

         devices (strip is candidate)

        */

 

       /*1、对于非由arp请求的arp reply,且没有相应的neighbour,则强制创建新的neighbour

         2、对于sip与tip相等的arp request,也强制创建新的neighbour  ??

       */

       if (n == NULL &&

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

           (arp->ar_op == htons(ARPOP_REQUEST) && tip == sip))&&

          inet_addr_type(net, sip) == RTN_UNICAST)

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

    }

 

 

    /*如果查找到符合条件的neighbour,则执行如下代码

    1、对于发给给本机的arp reply报文,则将邻居项设置为reach状态

    2、对于发给给本机的arp request报文,则将邻居项状态设置为stale状态

 

    最后调用neigh_update,更新neighbour的状态

    */

    if (n) {

       int state = NUD_REACHABLE;

       int override;

 

       /* If several different ARP repliesfollows back-to-back,

         use the FIRST one. It is possible, if several proxy

         agents are active. Taking the first reply prevents

         arp trashing and chooses the fastest router.

        */

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

 

       /* Broadcast replies and request packets

         do not assert neighbour reachability.

        */

       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);

    }

 

out:

    if (in_dev)

       in_dev_put(in_dev);

    consume_skb(skb);

    return 0;

}

 

 

 

在上面的函数里,出现了发送arp数据包的函数arp_send,下面我们分析arp_send与arp_create

 

arp_send就是arp_create的封装函数,相比arp_creare,增加了判断设备是否为NOARP的设备。

 void arp_send(int type, int ptype, __be32dest_ip,

         struct net_device *dev, __be32 src_ip,

         const unsigned char *dest_hw, const unsigned char *src_hw,

         const unsigned char *target_hw)

{

    struct sk_buff *skb;

 

    /*

     *  No arp on this interface.

     */

 

    /*

    如果该设备不需要arp,则直接返回

    */

 

    if (dev->flags&IFF_NOARP)

       return;

 

    skb = arp_create(type, ptype, dest_ip, dev,src_ip,

            dest_hw, src_hw, target_hw);

    if (skb == NULL) {

       return;

    }

 

    arp_xmit(skb);

}

 

下面分析arp_create

 

该函数主要是申请一个缓存,并根据arp协议的格式,创建一个arp数据包。该函数还是比较简单的。

struct sk_buff*arp_create(int type, int ptype, __be32 dest_ip,

             struct net_device *dev, __be32 src_ip,

             const unsigned char *dest_hw,

             const unsigned char *src_hw,

             const unsigned char *target_hw)

{

    struct sk_buff *skb;

    struct arphdr *arp;

    unsigned char *arp_ptr;

 

    /*

     *  Allocate a buffer

     */

    /*首先调用alloc_skb,申请缓存空间*/

    skb = alloc_skb(arp_hdr_len(dev) +LL_ALLOCATED_SPACE(dev), GFP_ATOMIC);

    if (skb == NULL)

       return NULL;

    /*留出源、目的mac地址的空间*/

    skb_reserve(skb, LL_RESERVED_SPACE(dev));

    /*设置三层头部指针*/

    skb_reset_network_header(skb);

    /*设置arp头指针*/

    arp = (struct arphdr *) skb_put(skb,arp_hdr_len(dev));

    skb->dev = dev;

    skb->protocol = htons(ETH_P_ARP);

    /*设置源、目的mac地址*/

    if (src_hw == NULL)

       src_hw = dev->dev_addr;

    if (dest_hw == NULL)

       dest_hw = dev->broadcast;

 

    /*

     *  Fill the device header for the ARP frame

     */

     /*通过调用eth_header,填充二层头部*/

    if (dev_hard_header(skb, dev, ptype,dest_hw, src_hw, skb->len) < 0)

       goto out;

 

    /*

     *Fill out the arp protocol part.

     *

     * Thearp hardware type should match the device type, except for FDDI,

     *which (according to RFC 1390) should always equal 1 (Ethernet).

     */

    /*

     *  Exceptions everywhere. AX.25 uses the AX.25PID value not the

     *  DIX code for the protocol. Make these devicestructure fields.

     */

    switch (dev->type) {

    default:

       /*设置硬件协议类型与软件协议类型,对于Ethernet硬件类型为1软件类型为0x0800*/

       arp->ar_hrd = htons(dev->type);

       arp->ar_pro = htons(ETH_P_IP);

       break;

 

#ifdefined(CONFIG_AX25) || defined(CONFIG_AX25_MODULE)

    case ARPHRD_AX25:

       arp->ar_hrd = htons(ARPHRD_AX25);

       arp->ar_pro = htons(AX25_P_IP);

       break;

 

#ifdefined(CONFIG_NETROM) || defined(CONFIG_NETROM_MODULE)

    case ARPHRD_NETROM:

       arp->ar_hrd = htons(ARPHRD_NETROM);

       arp->ar_pro = htons(AX25_P_IP);

       break;

#endif

#endif

 

#ifdefined(CONFIG_FDDI) || defined(CONFIG_FDDI_MODULE)

    case ARPHRD_FDDI:

       arp->ar_hrd = htons(ARPHRD_ETHER);

       arp->ar_pro = htons(ETH_P_IP);

       break;

#endif

#ifdefined(CONFIG_TR) || defined(CONFIG_TR_MODULE)

    case ARPHRD_IEEE802_TR:

       arp->ar_hrd = htons(ARPHRD_IEEE802);

       arp->ar_pro = htons(ETH_P_IP);

       break;

#endif

    }

 

    /*设置硬件协议长度、软件协议长度、arp包类型*/

    arp->ar_hln = dev->addr_len;

    arp->ar_pln = 4;

    arp->ar_op = htons(type);

    /*设置arp的源mac、ip 与目的mac、ip地址*/

    arp_ptr=(unsigned char *)(arp+1);

 

    memcpy(arp_ptr, src_hw, dev->addr_len);

    arp_ptr += dev->addr_len;

    memcpy(arp_ptr, &src_ip, 4);

    arp_ptr += 4;

    if (target_hw != NULL)

       memcpy(arp_ptr, target_hw,dev->addr_len);

    else

       memset(arp_ptr, 0, dev->addr_len);

    arp_ptr += dev->addr_len;

    memcpy(arp_ptr, &dest_ip, 4);

 

    return skb;

 

out:

    kfree_skb(skb);

    return NULL;

}

 

 

 

对于arp_send,既可以发送arp请求数据包,也可以发送arp应答报文,主要是在arp_process中调用。对于应答报文,回复的依据为:

1)对于重复地址检测请求,则发送一个arp reply消息。

2)对于发往本地的arp request,则发送一个arp reply消息,并将邻居项的状态设置为NUD_STALE。

 

 

 

Arp 邻居项的创建以及arp solicit请求发送的流程

 

那对于arp request消息是如何发送的呢?

当本地有数据需要发送时,则会查找路由,在查找到路由且没有路由缓存时,则会创建路由缓存,而在创建路由缓存的过程中,就会调用arp_bind_neighbour,实现路由缓存与arp邻居项的绑定,对于不存在的邻居项,则创建该邻居项,并将邻居项的状态设置为NUD_NONE。

    此时邻居项的状态还是NUD_NONE。接着就会执行ip_output,然后就会调用到ip_finish_output2,接着就会调用到neighbour->output,对于刚创建的邻居项,其output为neigh_resolve_output。在neigh_resolve_output里就会调用到__neigh_event_send判断数据包是否可以直接发送出去,如果此时邻居项的状态为NUD_NONE,则会将邻居项的状态设置为NUD_INCOMPLETE,并将要发送的数据包缓存到邻居项的队列中。而处于NUD_INCOMPLETE状态的邻居项的状态转变会有定时器处理函数来实现。

    由以前的分析,我们知道处于NUD_INCOMPLETE状态的邻居项,就会调用neigh->solicit,发送邻居项请求的数据包,对于arp 来说,其neigh->solicit即为arp_solicit,在分析之前,首先需要理解 arp announce的级别

当发送arp请求的主机对应的ip地址不止一个时,arp announce级别

决定如何选择ip地址

0:任何ip地址都可以

1:尽可能选择和目的ip处于同一个子网的ip地址,否则使用级别2的选择

2:优先使用主地址

static void arp_solicit(struct neighbour *neigh,struct sk_buff *skb)

{

    __be32 saddr= 0;

    u8  *dst_ha = NULL;

    structnet_device *dev = neigh->dev;

    __be32 target= *(__be32*)neigh->primary_key;

    int probes =atomic_read(&neigh->probes);

   

    /* 获取源设备ip层的相关信息*/

    structin_device *in_dev = in_dev_get(dev);

 

 

    if (!in_dev)

       return;

 

    switch(IN_DEV_ARP_ANNOUNCE(in_dev)) {

    default:

    case 0:       /* By default announce any local IP */

 

       /*判断数据包的源地址是否为本地地址。

       若是,则将源地址设置为数据包的源地址;

       若不是,则调用inet_select_addr选择一个源地址*/

       if (skb&& inet_addr_type(dev_net(dev), ip_hdr(skb)->saddr) == RTN_LOCAL)

           saddr= ip_hdr(skb)->saddr;

       break;

    case 1:       /* Restrict announcements of saddr insame subnet */

       /*判断数据包的源地址是否为本地地址。

       若是,则优先选择与目的ip地址在相同子网上的ip地址,否则则调用

       inet_select_addr优先使用主地址*/

       if (!skb)

           break;

       saddr =ip_hdr(skb)->saddr;

       if(inet_addr_type(dev_net(dev), saddr) == RTN_LOCAL) {

           /*saddr should be known to target */

           if(inet_addr_onlink(in_dev, target, saddr))

              break;

       }

       saddr = 0;

       break;

    case 2:       /* Avoid secondary IPs, get aprimary/preferred one */

       /*调用inet_select_addr优先获取一个符合条件的主地址*/

       break;

    }

 

    if (in_dev)

       in_dev_put(in_dev);

 

    /*若此时还没有设置源ip地址,则调用inet_select_addr获取ip地址

    该函数主要实现的功能

    1、对于指定设备dev所关联的ip配置块,查找scope小于等于RT_SCOPE_LINK且与目的地址

            属于同一子网的地址作为源ip地址

    2、对于符合scope条件的ip地址,若没有子网相同的地址,则选择主地址作为源

            ip地址

      3、对于在指定设备dev上找不到满足scope条件的ifaddr结构,则遍历所有dev设备,找到

            符合条件的ifaddr结构,并将其主地址作为源ip地址。

    */

    if (!saddr)

       saddr =inet_select_addr(dev, target, RT_SCOPE_LINK);

 

    /*判断arp请求报文是否到达上限,若到达上限则不发送*/

    if ((probes-= neigh->parms->ucast_probes) < 0) {

       if(!(neigh->nud_state&NUD_VALID))

           printk(KERN_DEBUG"trying to ucast probe in NUD_INVALID\n");

       dst_ha =neigh->ha;

       read_lock_bh(&neigh->lock);

    } else if((probes -= neigh->parms->app_probes) < 0) {

#ifdef CONFIG_ARPD

       neigh_app_ns(neigh);

#endif

       return;

    }

    /*调用arp_send发送arp请求包*/

    arp_send(ARPOP_REQUEST,ETH_P_ARP, target, dev, saddr,

        dst_ha, dev->dev_addr, NULL);

    if (dst_ha)

       read_unlock_bh(&neigh->lock);

}

 

 

 

 

 

刚才我们有提到arp_bind_neighbour,下面分析一下这个函数:

实现arp协议中,neighbour项与路由缓存中的dst_entry表项的绑定

通过下一跳网关地址和net_dev为关键字查找一个neighbour项

1、若查找到,则将dst->neighbour指向该neighbour项

2、若没有查找到,则调用neigh_create创建一个邻居表项并加入到arp_table的邻居表项链表中,并将dst->neighbour指向该neighbour项

int arp_bind_neighbour(struct dst_entry *dst)

{

    structnet_device *dev = dst->dev;

    structneighbour *n = dst->neighbour;

 

    if (dev ==NULL)

       return-EINVAL;

    if (n ==NULL) {

       __be32nexthop = ((struct rtable *)dst)->rt_gateway;

       if(dev->flags&(IFF_LOOPBACK|IFF_POINTOPOINT))

           nexthop= 0;

       n =__neigh_lookup_errno(

#if defined(CONFIG_ATM_CLIP) ||defined(CONFIG_ATM_CLIP_MODULE)

           dev->type == ARPHRD_ATM ? clip_tbl_hook:

#endif

           &arp_tbl, &nexthop, dev);

       if(IS_ERR(n))

           returnPTR_ERR(n);

       dst->neighbour= n;

    }

    return 0;

}

 

 

至此完成arp 协议数据处理分析。

GitHub 加速计划 / li / linux-dash
10.39 K
1.2 K
下载
A beautiful web dashboard for Linux
最近提交(Master分支:2 个月前 )
186a802e added ecosystem file for PM2 4 年前
5def40a3 Add host customization support for the NodeJS version 4 年前
Logo

旨在为数千万中国开发者提供一个无缝且高效的云端环境,以支持学习、使用和贡献开源项目。

更多推荐