ARP:地址解析协议实现学习

来源:互联网 发布:兄弟连php课程大纲 编辑:程序博客网 时间:2024/06/14 21:50

 在以太网上传输IP数据报时,以太网设备并不能识别32位IP地址,而是以48位以太网地址传输以太网数据包的。因此,IP数据报在以太网上传输前需要封装为以太网帧,而以太网帧的目的地址正是通过IP数据报的目的IP地址查询得到的。因此IP地址和以太网地址之间存在着映射,通过查看ARP表就可以得到这两地址间的对应关系。地址解析协议(Address Resolution Protocol-ARP)就是用来确定这些对应关系的协议。

ARP协议的处理涉及以下文件:

include/linux/if_arp.h 定义Arp报文等结构、宏和函数原型

net/ipv4/arp.c Arp协议实现

ARP报文格式

参见tcp/ip协议学习笔记(4)ARP&RARP

定义arp报文头的结构

struct arphdr{__be16ar_hrd;/* format of hardware address*/__be16ar_pro;/* format of protocol address*/unsigned charar_hln;/* length of hardware address*/unsigned charar_pln;/* length of protocol address*/__be16ar_op;/* ARP opcode (command)*/#if 0 /*  * Ethernet looks like this : This bit is variable sized however...  */unsigned charar_sha[ETH_ALEN];/* sender hardware address*/unsigned charar_sip[4];/* sender IP address*/unsigned charar_tha[ETH_ALEN];/* target hardware address*/unsigned charar_tip[4];/* target IP address*/#endif};
由于不同网络介质的MAC地址长度是不同的,因此ARP报文的结构不能包括操作码后面的内容。这里只是列举了以太网上的ARP定义。

ARP协议并不仅仅被IPv4使用,在内核的网络模块代码中使用缩写SIP和TIP来代表发送方IP地址和目的IP地址

注册ARP报文类型

ARP报文像IP数据报一样,也是作为数据封装在以太网帧中发送的。ARP报文由arp_rcv()接收处理,ARP模块初始化时需要在协议栈中注册ARP报文的类型

/* *Called once on startup. */static struct packet_type arp_packet_type __read_mostly = {.type =cpu_to_be16(ETH_P_ARP),.func =arp_rcv,};

ARP初始化

ARP模块的初始化是由arp_init()完成的,该函数由IPv4协议栈初始化函数inet_init()调用,首先初始化arp协议的邻居表,然后在协议栈中注册ARP协议,最后建立proc对象,注册事件通知

void __init arp_init(void){neigh_table_init(&arp_tbl);dev_add_pack(&arp_packet_type);arp_proc_init();#ifdef CONFIG_SYSCTLneigh_sysctl_register(NULL, &arp_tbl.parms, NET_IPV4,      NET_IPV4_NEIGH, "ipv4", NULL, NULL);#endifregister_netdevice_notifier(&arp_netdev_notifier);}
ARP的邻居项函数指针表

在ARP中,根据不同的介质,提供了多种邻居项函数指针表的实例,例如通用的arp_generic_ops,支持缓存硬件首部arp_hh_ops,不支持ARP的arp_direct_ops以及支持业余无线电设备等的arp_broken_ops。除了两个输出函数指针output和connected_output,这些邻居项函数指针表实例区别不大

static const struct neigh_ops arp_generic_ops = {.family =AF_INET,.solicit =arp_solicit,.error_report =arp_error_report,.output =neigh_resolve_output,.connected_output =neigh_connected_output,.hh_output =dev_queue_xmit,.queue_xmit =dev_queue_xmit,};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,};static const struct neigh_ops arp_direct_ops = {.family =AF_INET,.output =dev_queue_xmit,.connected_output =dev_queue_xmit,.hh_output =dev_queue_xmit,.queue_xmit =dev_queue_xmit,};const struct neigh_ops arp_broken_ops = {.family =AF_INET,.solicit =arp_solicit,.error_report =arp_error_report,.output =neigh_compat_output,.connected_output =neigh_compat_output,.hh_output =dev_queue_xmit,.queue_xmit =dev_queue_xmit,};
ARP表

ARP的邻居表为arp_tbl,其中与协议特性相关的字段有:地址族为AF_INET;邻居项的大小为neighbour结构+4(IPv4地址的长度);哈希算法为arp_hash();arp初始化函数arp_constructor();延时处理代理ARP报文的例程为parp_redo(),以及调整ARP表特性的参数。

struct neigh_table arp_tbl = {.family =AF_INET,.entry_size =sizeof(struct neighbour) + 4,.key_len =4,.hash =arp_hash,.constructor =arp_constructor,.proxy_redo =parp_redo,.id ="arp_cache",.parms = {.tbl =&arp_tbl,.base_reachable_time =30 * HZ,.retrans_time =1 * HZ,.gc_staletime =60 * HZ,.reachable_time =30 * HZ,.delay_probe_time =5 * HZ,.queue_len =3,.ucast_probes =3,.mcast_probes =3,.anycast_delay =1 * HZ,.proxy_delay =(8 * HZ) / 10,.proxy_qlen =64,.locktime =1 * HZ,},.gc_interval =30 * HZ,.gc_thresh1 =128,.gc_thresh2 =512,.gc_thresh3 =1024,};

IPv4中邻居项的初始化

arp_constructor()是ARP的邻居初始化函数,用来创建新的neighbour结构实例,在邻居表创建函数neigh_create()中被调用。

static int arp_constructor(struct neighbour *neigh){__be32 addr = *(__be32*)neigh->primary_key;struct net_device *dev = neigh->dev;struct in_device *in_dev;struct neigh_parms *parms;rcu_read_lock();in_dev = __in_dev_get_rcu(dev);if (in_dev == NULL) {rcu_read_unlock();return -EINVAL;}neigh->type = inet_addr_type(dev_net(dev), addr);parms = in_dev->arp_parms;__neigh_parms_put(neigh->parms);neigh->parms = neigh_parms_clone(parms);rcu_read_unlock();if (!dev->header_ops) {neigh->nud_state = NUD_NOARP;neigh->ops = &arp_direct_ops;neigh->output = neigh->ops->queue_xmit;} else {/* Good devices (checked by reading texts, but only Ethernet is   tested)   ARPHRD_ETHER: (ethernet, apfddi)   ARPHRD_FDDI: (fddi)   ARPHRD_IEEE802: (tr)   ARPHRD_METRICOM: (strip)   ARPHRD_ARCNET:   etc. etc. etc.   ARPHRD_IPDDP will also work, if author repairs it.   I did not it, because this driver does not work even   in old paradigm. */#if 1/* So... these "amateur" devices are hopeless.   The only thing, that I can say now:   It is very sad that we need to keep ugly obsolete   code to make them happy.   They should be moved to more reasonable state, now   they use rebuild_header INSTEAD OF hard_start_xmit!!!   Besides that, they are sort of out of date   (a lot of redundant clones/copies, useless in 2.1),   I wonder why people believe that they work. */switch (dev->type) {default:break;case ARPHRD_ROSE:#if defined(CONFIG_AX25) || defined(CONFIG_AX25_MODULE)case ARPHRD_AX25:#if defined(CONFIG_NETROM) || defined(CONFIG_NETROM_MODULE)case ARPHRD_NETROM:#endifneigh->ops = &arp_broken_ops;neigh->output = neigh->ops->output;return 0;#endif;}#endifif (neigh->type == RTN_MULTICAST) {neigh->nud_state = NUD_NOARP;arp_mc_map(addr, neigh->ha, dev, 1);} else if (dev->flags&(IFF_NOARP|IFF_LOOPBACK)) {neigh->nud_state = NUD_NOARP;memcpy(neigh->ha, dev->dev_addr, dev->addr_len);} else if (neigh->type == RTN_BROADCAST || dev->flags&IFF_POINTOPOINT) {neigh->nud_state = NUD_NOARP;memcpy(neigh->ha, dev->broadcast, dev->addr_len);}if (dev->header_ops->cache)neigh->ops = &arp_hh_ops;elseneigh->ops = &arp_generic_ops;if (neigh->nud_state&NUD_VALID)neigh->output = neigh->ops->connected_output;elseneigh->output = neigh->ops->output;}return 0;}

ARP的输出

arp_send()先创建一个ARP报文,如果创建成功就将其发送出。该函数的参数与arp_create()相同

/* *Create and send an arp packet. */void arp_send(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;/* *No arp on this interface. */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的输入

arp_rcv()用来从二层接收并处理一个ARP报文

/* *Receive an arp request from the device layer. */static int arp_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, plus 2 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(struct neighbour_cb));return NF_HOOK(NFPROTO_ARP, NF_ARP_IN, skb, dev, NULL, arp_process);freeskb:kfree_skb(skb);out_of_mem:return 0;}

路由表项与邻居项的绑定

在路由模块中,每当添加一条输出路由或是单播转发路由时,会尝试将该路由与该路由目的地址相对应的邻居项绑定。arp_bind_neighbour()实现了路由表项与邻居绑定的功能,在绑定过程中,如果对应的邻居项不存在,则会创建一个邻居项然后将路由项与之绑定。绑定之后,再输出报文时就能通过路由缓存找到输出函数。






0 0
原创粉丝点击