邻居子系统之ARP概述

本文深入解析ARP协议,介绍了ARP邻居协议对象arp_tbl、ARP报文格式和处理函数,以及网络设备状态事件处理和邻居项初始化。讨论了如何根据设备能力设置邻居项操作集,并探讨了特殊地址映射,特别是arp_mc_map()函数在多播地址映射中的作用。
摘要由CSDN通过智能技术生成


这篇笔记记录了ARP协议的初始化部分,涉及主要文件如下:

文件路径描述
include/linux/arp.harp协议头文件
net/ipv4/arp.carp协议实现文件

数据结构

ARP邻居协议对象: arp_tbl

每个邻居协议,都需要提供一个struct neigh_table对象,然后向系统注册自己,arp协议的struct neigh_table对象为arp_table

struct neigh_table arp_tbl = {
	.family = AF_INET, // ARP协议只用于IPv4协议族
	// 邻居项大小之所以要加4,是因为邻居项的末尾要保存key,对于arp就是要映射的IPv4地址,所以是4
	.entry_size = sizeof(struct neighbour) + 4,
	.key_len = 4, // arp协议的key为IPv4地址,所以是4字节
	.hash =	arp_hash, // 邻居项哈希函数
	.constructor = arp_constructor, // ARP邻居项初始化函数
	.proxy_redo = parp_redo,
	.id = "arp_cache", // 邻居协议名字
	.parms = {
		.tbl =	&arp_tbl,
		.base_reachable_time =	30 * HZ,
		.retrans_time =	1 * HZ, // solicitations请求报文重试间隔为1s
		.gc_staletime =	60 * HZ,
		.reachable_time = 30 * HZ, // 可达性维持时间为30s
		.delay_probe_time =	5 * HZ, // 延迟可达性确认时间为5s
		.queue_len = 3, // 最多缓存3个报文
		.ucast_probes =	3, // solicitations单播请求报文重试次数为3次
		.mcast_probes =	3, // solicitations多播或广播请求此时为3次
		.anycast_delay = 1 * HZ,
		.proxy_delay = (8 * HZ) / 10, // 代理请求最大延迟处理事件为0.8s
		.proxy_qlen = 64, // 最多缓存64个代理请求
		.locktime =	1 * HZ,
	},
	.gc_interval =	30 * HZ,
	.gc_thresh1 =	128,
	.gc_thresh2 =	512,
	.gc_thresh3 =	1024,
};

ARP报文格式

struct arphdr
{
	__be16 ar_hrd; // L2层帧类型,取值ARPHRD_ETHER等
	__be16 ar_pro; // L3层地址类型,取值如ETH_P_IP
	unsigned char ar_hln; // L2层地址长度
	unsigned char ar_pln; // L3层地址长度
	__be16 ar_op; // ARP操作码,如ARPOP_REQUEST等
#if 0
	/*
	 *	 Ethernet looks like this : This bit is variable sized however...
	 */
	unsigned char		ar_sha[ETH_ALEN];	/* sender hardware address	*/
	unsigned char		ar_sip[4];		/* sender IP address		*/
	unsigned char		ar_tha[ETH_ALEN];	/* target hardware address	*/
	unsigned char		ar_tip[4];		/* target IP address		*/
#endif
};
```c

ARP协议报文的前8字节是固定的,后面部分根据协议地址长度以及L2层地址长度不同有所变化,可见在设计的时候,ARP不仅仅是为IPv4服务的,只不过实际中只有IPv4使用它而已。

# 初始化: arp_init()

该函数在IPv4协议族初始化函数inet_init()中被调用。

```cpp
void __init arp_init(void)
{
	// 向系统注册arp邻居协议,注册过程中也会对arp_tbl中的邻居协议公共字段进行初始化
	neigh_table_init(&arp_tbl);
	// 向网络设备接口层注册数据包接收回调,这样当网卡收到arp类型的报文时,将交给本协议处理
	dev_add_pack(&arp_packet_type);
	// 创建/proc/net/arp文件节点,该节点展示了当前arp缓存内容
	arp_proc_init();
#ifdef CONFIG_SYSCTL
	// 创建/proc/sys/net/ipv4/neigh目录,并且在其中创建neight_params对应的参数文件
	neigh_sysctl_register(NULL, &arp_tbl.parms, NET_IPV4, NET_IPV4_NEIGH, "ipv4", NULL, NULL);
#endif
	// 监听网络设备状态变化
	register_netdevice_notifier(&arp_netdev_notifier);
}

arp协议数据包接收处理器

初始化过程中通过dev_add_pack()向设备接口层注册了arp数据包接收函数arp_packet_type,内容如下:

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

设备接口层收到ETH_P_ARP类型的数据包后,将交给arp_rcv()继续处理。

网络设备状态事件处理: arp_netdev_event()

static int arp_netdev_event(struct notifier_block *this, unsigned long event, void *ptr)
{
	struct net_device *dev = ptr;

	switch (event) {
	case NETDEV_CHANGEADDR: // 关注mac地址变化事件
		neigh_changeaddr(&arp_tbl, dev);
		// 刷新路由缓存
		rt_cache_flush(dev_net(dev), 0);
		break;
	default:
		break;
	}
	return NOTIFY_DONE;
}

void neigh_changeaddr(struct neigh_table *tbl, struct net_device *dev)
{
    // 刷新arp缓存,见笔记“邻居子系统值邻居项的回收”
	write_lock_bh(&tbl->lock);
	neigh_flush_dev(tbl, dev);
	write_unlock_bh(&tbl->lock);
}

arp邻居项初始化: arp_constructor()

当新建一个ARP邻居项时,会回调该函数。

static int arp_constructor(struct neighbour *neigh)
{
	// key值就是邻居项要映射的IPv4地址
	__be32 addr = *(__be32*)neigh->primary_key;
	struct net_device *dev = neigh->dev;
	struct in_device *in_dev;
	struct neigh_parms *parms;

	rcu_read_lock();
	// 找到出口IP地址对应的IP地址配置结构
	in_dev = __in_dev_get_rcu(dev);
	if (in_dev == NULL) {
		rcu_read_unlock();
		return -EINVAL;
	}
	// 根据邻居项的IP地址类型设置邻居项类型
	neigh->type = inet_addr_type(&init_net, addr);

	// arp使用的是网络设备上的邻居协议参数
	parms = in_dev->arp_parms;
	__neigh_parms_put(neigh->parms);
	neigh->parms = neigh_parms_clone(parms);
	rcu_read_unlock();

	if (!dev->header_ops) {
		// 对于未指定首部操作的设备,表示其不需要L2帧头,这种情况下
		// 邻居子系统对数据包透传即可
		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 (neigh->type == RTN_MULTICAST) {
			// L3多播地址有固定的映射规则,所以设置邻居项状态为NUD_NOARP,并且用arp_mc_map()完成映射
			neigh->nud_state = NUD_NOARP;
			arp_mc_map(addr, neigh->ha, dev, 1);
		} else if (dev->flags&(IFF_NOARP|IFF_LOOPBACK)) {
			// 对于环回设备或者设备指示无需ARP的设备,使用它们提供了L2地址即可,
			// 直接将网卡设备指明的地址拷贝到ha中作为映射结果
			neigh->nud_state = NUD_NOARP;
			memcpy(neigh->ha, dev->dev_addr, dev->addr_len);
		} else if (neigh->type == RTN_BROADCAST || dev->flags&IFF_POINTOPOINT) {
			// 广播地址、P2P设备也不需要进行映射,直接使用L2设备的广播地址即可
			neigh->nud_state = NUD_NOARP;
			memcpy(neigh->ha, dev->broadcast, dev->addr_len);
		}
        // 根据网络设备是否支持L2帧头缓存,选择不同的邻居项操作集
		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;
	}
	return 0;
}

设置邻居项操作集

邻居项的操作集是在创建是确定的,之后就不会再改变。从上面可以看出,确定它的依据就是网络设备本身的能力:

  1. 如果网络没有提供header_ops操作集,说明其无需邻居子系统参与L2帧首部的构建,这种情况下ARP协议会透传数据包(arp_direct_ops);
  2. 如果网络设备没有提供header_ops->cache()回调,说明其不支持L2帧首部缓存,或者其发往同一个邻居的L2帧首部是也是会频繁变化的,缓存没有意义,这种情况下ARP协议会使用最通用的邻居项操作集(arp_generic_ops);
  3. 和情况2相反,ARP协议会使用能够处理L2帧首部缓存的操作集(arp_hh_ops);

关于这3种操作集的实现分析见邻居子系统数据发送流程

特殊地址映射

如上,一些特殊的IP地址是可以直接转换出对应的L2地址的,无需进行ARP协议流程,转换规则如下:

  1. 多播地址有固定的映射规则,见下方arp_mc_map();
  2. 网络设备无需ARP,或者是loopback设备,直接填充本地网络设备的L2地址即可;
  3. 广播地址或者点对点网络设备直接使用L2设备的广播地址即可;

arp_mc_map()

int arp_mc_map(__be32 addr, u8 *haddr, struct net_device *dev, int dir)
{
	switch (dev->type) { // 根据网络设备类型做不同方式的映射
	case ARPHRD_ETHER:
	case ARPHRD_FDDI:
	case ARPHRD_IEEE802:
		ip_eth_mc_map(addr, haddr);
		return 0;
...
	default:
		if (dir) {
			memcpy(haddr, dev->broadcast, dev->addr_len);
			return 0;
		}
	}
	return -EINVAL;
}

static inline void ip_eth_mc_map(__be32 naddr, char *buf)
{
    // IP地址为4字节,mac地址为6字节。前3字节为固定值,IP地址的后3字节映射到mac地址的后3字节
	__u32 addr=ntohl(naddr);
	buf[0]=0x01;
	buf[1]=0x00;
	buf[2]=0x5e;
	buf[5]=addr&0xFF;
	addr>>=8;
	buf[4]=addr&0xFF;
	addr>>=8;
	buf[3]=addr&0x7F;
}
  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值