您的位置:首页 > 运维架构 > Linux

linux内核学习笔记------ARP:地址解析协议

2014-01-07 20:58 986 查看
当发送ARP请求时,发送方填入发送方以太网地址、发送方ip地址以及目标ip地址。目标主机接收到这个ARP广播包时,会在响应报文中填上自己的以太网地址。ARP报文格式如下:

硬件类型协议类型硬件地址长度协议地址长度操作码src硬件地址src ip地址dst硬件地址目标ip地址
1、硬件类型都是以ARPHDR_开头

2、操作码包括:ARPOP_REQUEST,ARPOP_REPLY

ARP报文像ip数据报一样,要是作为数据封装在以太网帧中发送。ARP会在初始化的时候注册ARP报文类型:

static struct packet_type arp_packet_type __read_mostly = {
.type = cpu_to_be16(ETH_P_ARP),
.func = arp_rcv,
};ARP模块的初始化是通过arp_init完成,该函数在ipv4协议栈初始化函数inet_init调用。
void __init arp_init(void)
{
neigh_table_init(&arp_tbl);

dev_add_pack(&arp_packet_type);
arp_proc_init();
#ifdef CONFIG_SYSCTL
neigh_sysctl_register(NULL, &arp_tbl.parms, NET_IPV4,
NET_IPV4_NEIGH, "ipv4", NULL, NULL);
#endif
register_netdevice_notifier(&arp_netdev_notifier);
}该函数首先初始化ARP协议的邻居表,然后在协议栈中注册ARP协议,最后建立proc对象,注册事件通知。ARP协议是通过哈希表组织起来的,除了ARP协议还有ip协议等;
在ARP中,根据不同的介质,提供了多种邻居项函数指针表的实例,例如通用的arp_generic_ops,支持缓存首部硬件首部arp_hh_ops,不支持ARP的arp_direct_ops以及支持业余无线电设备等的arp_broken_ops。在初始化邻居项时,会根据访问邻居项的输出网络设备的特性使用合适的邻居项函数指针表的实例。

在需要获取目标的mac地址时就会调用arp_solicit发送ARP请求:

static void arp_solicit(struct neighbour *neigh, struct sk_buff *skb)
{
......
__be32 target = *(__be32*)neigh->primary_key;
int probes = atomic_read(&neigh->probes);
struct in_device *in_dev = in_dev_get(dev);
......
switch (IN_DEV_ARP_ANNOUNCE(in_dev)) {
default:
case 0: /* By default announce any local IP */
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 in same subnet */
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 a primary/preferred one */
break;
}

if (in_dev)
in_dev_put(in_dev);
if (!saddr)
saddr = inet_select_addr(dev, target, RT_SCOPE_LINK);

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(ARPOP_REQUEST, ETH_P_ARP, target, dev, saddr,
dst_ha, dev->dev_addr, NULL);
......
}

arp会根据arp_announce系统参数来选择源ip地址,检测ARP请求报文重传次数是否达到上限,如果是,则停止发送,否则就会调用arp_send发送请求。

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_send比较简单,如果网络设备无需arp支持,则直接返回,否则就会调用arp_create创建arp请求报文,并调用arp_xmit把请求报文发送出去。arp_create逻辑比较见到那,就不贴出源码了。
在以前学习邻居项的时候,在创建邻居项时也就是neigh_create有这么一句话:

if (tbl->constructor && (error = tbl->constructor(n)) < 0) {
rc = ERR_PTR(error);
goto out_neigh_release;
}tbl_constructor就是arp协议中的arp_constructor,当然如果是别的协议就另说了
static int arp_constructor(struct neighbour *neigh)
{
__be32 addr = *(__be32*)neigh->primary_key;
......
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);
if (!dev->header_ops) {
neigh->nud_state = NUD_NOARP;
neigh->ops = &arp_direct_ops;
neigh->output = neigh->ops->queue_xmit;
} else {
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:
#endif
neigh->ops = &arp_broken_ops;
neigh->output = neigh->ops->output;
return 0;
#endif
;}
#endif
if (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;
else
neigh->ops = &arp_generic_ops;

if (neigh->nud_state&NUD_VALID)
neigh->output = neigh->ops->connected_output;
else
neigh->output = neigh->ops->output;
}
}arp_constructor主要就是初始化neighbor结构,根据不同的硬件接口,启用不同的邻居项的函数操作表。如果是组播类型是不需要arp支持的,如果网络设备不需要arp支持或者回环设备,则设置邻居项状态NUD_NOARP,并将广播地址作为硬件地址存储到邻居项中。然后根据网络设备支持缓存硬件首部接口来设置邻居项函数表,在根据邻居项的状态设置输出接口。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  linux内核 网络