Linux网络协议源码分析(二):arp协议

前沿

linux版本 1.2.13

往片回顾

在上一篇中,主要主主体上学习了ISO 7层模型,并分析了网络协议栈初始化的相关代码;但其实在老版本linux中,上三层之间功能划分并不清晰,在sock层(会话层)掌握这每个sock资源的状态

  • 每个struct sock结构体对应一个套接字资源,它注册在对应传输层协议的描述结构体struct proto中,同时sock->prot指向该套接字所属的传输层协议;待发送报文组装也是在传输层开始的

arp协议

  • 在以太网上传输IP数据包时,以太设备并不能识别32位IP地址,而是以48位mac地址传输;因此IP数据包要封装成以太网帧

  • arp协议是网络层协议,用于发现局域网内设备;协议栈通过arp协议获取到网络上邻居主机的IP地址与MAC地址的对应关系,并保存在全局arp table中;

  • arp table是个链式hash table

  • 下面是在以太网中传输的arp报文结构

目的MAC 源MAC 帧类型 硬件类型 上层协议类型 MAC地址长度 IP地址长度 op smac sip tmac tip
6bytes 6bytes 2 2 2 1 1 1 6 4 6 4
  • 前3段为以太头;中5段位arp头;最后4段为arp数据

本篇主要内容

  • arp相关知识

代码分析

arp相关函数

arp_init

/* 初始化地址协议层:arp
 * 1. 将arp消息处理结构体挂到 packet_type队列上,指示kernel要接收处理arp报文,根据报文type=ETH_P_ARP(0X0806)
 * 2. 增加arp定时器,检查过期arp entry
 * 3. 将网络设备状态变更事件注册到内核通知链上
 */
void arp_init (void)
{
	/* 注册ARP报文到kernel接受数据包列表中 */
	arp_packet_type.type=htons(ETH_P_ARP);
	/* 将apr报文处理结构体挂到全局 packet_type 队列上,结构体中定义了arp packet handle函数 */
	dev_add_pack(&arp_packet_type);
	/* 增加arp定时器,定期检查arp表项是否过期;注册频率为60HZ. */
	add_timer(&arp_timer);
	/* arp事件注册到内核通知链中,通知链相关知识见参考文献 
	 * 内核关于网络的通知链有两条:
	 * 1. inetaddr_chain: 发送有关本地接口上的ipv4地址插入、删除、变更的通知信息
	 * 2. netdev_chain: 发送有关网络设备注册状态的通知信息
	 * 注册了一个网络设备状态变更处理函数 arp_device_event ,传入参数:
	 * 1. 事件类型(down,up,restart)
	 * 2. struct device 指针,指向发生变更的设备
	 * 只处理NOTIFY_DOWN事件,将设备表项T出arp table以及停尸表项定时器
	 */
	register_netdevice_notifier(&arp_dev_notifier);
}

arp_check_expire

/* arp定时器回调函数:检测ARP表项是否过期,过期则清除
 * 如果ATF_PERM置位,则永久保存
 */
static void arp_check_expire(unsigned long dummy)
{
	int i;
	unsigned long now = jiffies;
	unsigned long flags;
	save_flags(flags);
	cli();

	for (i = 0; i < FULL_ARP_TABLE_SIZE; i++)
	{
		struct arp_table *entry;
		struct arp_table **pentry = &arp_tables[i];

		while ((entry = *pentry) != NULL)
		{
		/* 对每个表项上次使用时间标志进行检查,如果超过ARP_TIMEOUT,则表示已过期 */
			if ((now - entry->last_used) > ARP_TIMEOUT
				&& !(entry->flags & ATF_PERM))/* 置为ATF_PERM,则永久保存 */
			{
				*pentry = entry->next;	/* remove from list */
				del_timer(&entry->timer);	/* 停止该表项设置的定时器 */
				kfree_s(entry, sizeof(struct arp_table));
			}
			else
				pentry = &entry->next;	/* go to next entry */
		}
	}
	restore_flags(flags);

	/*
	 * Set the timer again.
	 * kernel tcp 协议栈重置的操作一般都是先清楚现有的,然后新建一个再插入到链表中
	 * 直接修改现有的,会造成内核状态不一致
	 */
	
	del_timer(&arp_timer);
	arp_timer.expires = ARP_CHECK_INTERVAL;
	add_timer(&arp_timer);
}

arp_rcv

/*
 * 该函数为arp_packet_type结构体中注册的回调函数,结构体挂在kernel的 packet_type 网络报文处理队列上
 * 每当有arp数据包报文到达,会触发本函数
 * skb:arp数据包;dev:接受数据包的网络设备;pt:指向arp协议本身的 packet_type 
 */
int arp_rcv(struct sk_buff *skb, struct device *dev, struct packet_type *pt)
{

	struct arphdr *arp = (struct arphdr *)skb->h.raw;	/* arp首部. */
	unsigned char *arp_ptr= (unsigned char *)(arp+1);/* arp报文中的地址位置. */
	struct arp_table *entry;
	struct arp_table *proxy_entry;
	int addr_hint,hlen,htype;
	unsigned long hash;
	unsigned char ha[MAX_ADDR_LEN];	/* So we can enable ints again. */
	long sip,tip;
	unsigned char *sha,*tha;

	/* 健壮性check:地址检查、硬件类型检查、标志位检查 */  
	if (arp->ar_hln != dev->addr_len    || 
     		dev->type != ntohs(arp->ar_hrd) || 
		dev->flags & IFF_NOARP          ||
		arp->ar_pln != 4)
	{
		kfree_skb(skb, FREE_READ);
		return 0;
	}

	/* arp报文来源的硬件形态 */
  	switch(dev->type)
  	{
#ifdef CONFIG_AX25
		case ARPHRD_AX25://AX.25网络
			if(arp->ar_pro != htons(AX25_P_IP))
			{
				kfree_skb(skb, FREE_READ);
				return 0;
			}
			break;
#endif
		case ARPHRD_ETHER://以太网络
		case ARPHRD_ARCNET://令牌总线网络
			if(arp->ar_pro != htons(ETH_P_IP))
			{
				kfree_skb(skb, FREE_READ);
				return 0;
			}
			break;

		default:
			printk("ARP: dev->type mangled!\n");
			kfree_skb(skb, FREE_READ);
			return 0;
	}

	hlen  = dev->addr_len;
	htype = dev->type;
	/* 从报文中提取sip(源地址), tip(目的地址) */
	sha=arp_ptr;
	arp_ptr+=hlen;
	memcpy(&sip,arp_ptr,4);
	arp_ptr+=4;
	tha=arp_ptr;
	arp_ptr+=hlen;
	memcpy(&tip,arp_ptr,4);
  
	/* 
	 *	如果为环回地址,则不处理
	 */
	if(tip == INADDR_LOOPBACK)
	{
		kfree_skb(skb, FREE_READ);
		return 0;
	}

/*
 *  Process entry.  The idea here is we want to send a reply if it is a
 *  request for us or if it is a request for someone else that we hold
 *  a proxy for.  We want to add an entry to our cache if it is a reply
 *  to us or if it is a request for our address.  
 *  (The assumption for this last is that if someone is requesting our 
 *  address, they are probably intending to talk to us, so it saves time 
 *  if we cache their address.  Their address is also probably not in 
 *  our cache, 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
 *  about those for us and those for our proxies.  We reply to both,
 *  and in the case of requests for us we add the requester to the arp 
 *  cache.
 */
	/* 检查地址是否为本地接口地址/环回地址/广播地址/多播IP地址
	 * 对于广播报文,tip为目的IP地址,MAC=0xff-ff-ff
	 */
	addr_hint = ip_chk_addr(tip);
	
	if(arp->ar_op == htons(ARPOP_REPLY))
	{
		/* 如果为arp响应报文,且目的地址不是本机,则不处理 */
		if(addr_hint!=IS_MYADDR)
		{
			kfree_skb(skb, FREE_READ);
			return 0;
		}
	}
	else
	{ 
		/* 
		 * 请求报文:
		 * 1. 发送给本机
		 * 2. 发送给由本机代理的主机
		 */
		if(tip!=dev->pa_addr)
		{
		/* 目的地址为由本机代理的主机 */
			cli();
		/* 遍历arp缓存表中代理表项(固定为最后一个索引元素指向) */
			for(proxy_entry=arp_tables[PROXY_HASH];
			    proxy_entry;
			    proxy_entry = proxy_entry->next)
			{
			  /* ip地址、网络设备、硬件地址类型同时进行精确匹配 
			   * (proxy_entry->ip^tip)&proxy_entry->mask == 0,则匹配
			   */
			  if (proxy_entry->dev != dev && proxy_entry->htype == htype &&
			      !((proxy_entry->ip^tip)&proxy_entry->mask))
			    break;

			}
			if (proxy_entry)
			{
				/* 找到代理表项符合arp报文地址 */
				memcpy(ha, proxy_entry->ha, hlen);
				sti();
				/* 根据表项中硬件地址响应arp报文;
				 * 该地址为代理主机对应网段网络设备的硬件地址 
				 */
				arp_send(ARPOP_REPLY,ETH_P_ARP,sip,dev,tip,sha,ha);
				kfree_skb(skb, FREE_READ);
				return 0;
			}
			else
			{
				sti();
				kfree_skb(skb, FREE_READ);
				return 0;
			}
		}
		else
		{
			/* 发往本机的直接做应答;应答后并为结束,还要刷新arp table */
			arp_send(ARPOP_REPLY,ETH_P_ARP,sip,dev,tip,sha,dev->dev_addr);
		}
	}
	
	/* 能走到这有两种情况
	 * 1. arp->ar_op == htons(ARPOP_REPLY),且目标为本机
	 * 2. arp->ar_op == htons(ARPOP_REQUEST),且目标为本机
	 */
	/* 由于arp table为链式hash表;
	 * 1. 先由source ip地址算出hash值,找到对应的hash bulk 
	 * 2. 在遍历该hash bulk中entry
	 */
	hash = HASH(sip);
	cli();
	for(entry=arp_tables[hash];entry;entry=entry->next)
		if(entry->ip==sip && entry->htype==htype)
			break;

	if(entry)
	{
		memcpy(entry->ha, sha, hlen);/* 刷新对应的硬件地址 */
		entry->hlen = hlen;
		entry->last_used = jiffies;
		if (!(entry->flags & ATF_COM))
		{
			/* 是未完成表项,删除定时器,标志为完成状态 */
			del_timer(&entry->timer);
			entry->flags |= ATF_COM;
			sti();
		/* 滞留的数据包重发:
		 * 1. arp请求的主动发起是在发送普通数据包时无法建立链路层首部的情况下进行的(不知道对端硬件地址)
		 * 2. 内核在进行arp地址解析过程中,会将这个数据包暂存在新建的arp表项的相关队列中
		 * 3. 等到新创建的表项完成所有字段的初始化(硬件地址),才把滞留的数据包发送出去(arp_send_q)
		 */
			arp_send_q(entry, sha);
		}
		else
		{
			sti();//是已经完成的表项,那么数据包自然已经发送出去了,不作处理  
		}
	}
	else
	{
	/* 没有找到,则需要创建一个新的表项插入; hash bulk 首部插入 */
		entry = (struct arp_table *)kmalloc(sizeof(struct arp_table),GFP_ATOMIC);
		if(entry == NULL)
		{
			sti();
			printk("ARP: no memory for new arp entry\n");

			kfree_skb(skb, FREE_READ);
			return 0;
		}

		entry->mask = DEF_ARP_NETMASK;
		entry->ip = sip;
		entry->hlen = hlen;
		entry->htype = htype;
		entry->flags = ATF_COM;
		init_timer(&entry->timer);
		memcpy(entry->ha, sha, hlen);
		entry->last_used = jiffies;
		entry->dev = skb->dev;
		skb_queue_head_init(&entry->skb);
		entry->next = arp_tables[hash];
		arp_tables[hash] = entry;
		sti();
	}

	/* arp报文功成身就,寿终正寝 */
	kfree_skb(skb, FREE_READ);
	return 0;
}

arp_send

void arp_send(int type, int ptype, unsigned long dest_ip, 
	      struct device *dev, unsigned long src_ip, 
	      unsigned char *dest_hw, unsigned char *src_hw)
{
	struct sk_buff *skb;
	struct arphdr *arp;
	unsigned char *arp_ptr;

	/*
	 *	No arp on this interface.
	 */
	
	if(dev->flags&IFF_NOARP)//非ARP协议
		return;

	/*
	 *	大小:以太网帧头+arp报头+源端、目的端对应MAC,IP
	 */
	skb = alloc_skb(sizeof(struct arphdr)+ 2*(dev->addr_len+4)
				+ dev->hard_header_len, GFP_ATOMIC);
	if (skb == NULL)
	{
		printk("ARP: no memory to send an arp packet\n");
		return;
	}
	skb->len = sizeof(struct arphdr) + dev->hard_header_len + 2*(dev->addr_len+4);
	skb->arp = 1;  //表示已完成MAC首部的创建  
	skb->dev = dev;//绑定设备  
	skb->free = 1; //数据包发送后立即释放  

	/* Fill the device header for the ARP frame
	 * 如果dest_hw==NULL,则用广播地址填入
	 * 如果src_hw==NULL,则用NULL填入
	 */
	dev->hard_header(skb->data,dev,ptype,dest_hw?dest_hw:dev->broadcast,src_hw?src_hw:NULL,skb->len,skb);

	/* 得到arp报头。arp报文内存布局:以太网帧头 | arp报头 | 地址类   */
	arp = (struct arphdr *) (skb->data + dev->hard_header_len);
	arp->ar_hrd = htons(dev->type);/* 设置arp报文头中硬件类型 */
#ifdef CONFIG_AX25
	arp->ar_pro = (dev->type != ARPHRD_AX25)? htons(ETH_P_IP) : htons(AX25_P_IP);
#else
	arp->ar_pro = htons(ETH_P_IP);
#endif
	arp->ar_hln = dev->addr_len;
	arp->ar_pln = 4;
	arp->ar_op = htons(type);/* 设置arp报文头中报文类型 */

	arp_ptr=(unsigned char *)(arp+1);/* arp指向apr报文数据部分 */
	/* 拷入数据部分MAC,IP地址 */
	memcpy(arp_ptr, src_hw, dev->addr_len);
	arp_ptr+=dev->addr_len;
	memcpy(arp_ptr, &src_ip,4);
	arp_ptr+=4;
	if (dest_hw != NULL)
		memcpy(arp_ptr, dest_hw, dev->addr_len);
	else
		memset(arp_ptr, 0, dev->addr_len);
	arp_ptr+=dev->addr_len;
	memcpy(arp_ptr, &dest_ip, 4);
	/* 将该数据包传递给驱动程序,由驱动程序最终将数据发送到物理介质上 
	 * 1. dev_queue_xmit完成其本层处理后,调用发送设备device结构之hard_start_xmit指针指向的不同设备具体硬件发送函数
	 * 2. skb_queue_tail: 每个device中默认有DEV_NUMBUFFS个数据包发送环形双向队列;待发送数据包根据不同的优先级加入不同队列的尾部
	 * 3. skb_dequeue:从环形队列头部取出一个包发送;在真正将报文发到硬件前;会遍历 packet_type 全局队列,将报文发到所有对本类型报文感兴趣的钩子上
	 */
	dev_queue_xmit(skb, dev, 0);
}

arp_find

/*
 * 在elp_start_xmit(hard_start_xmit)->rebuild_header函数中被用来重建以太报文头部
 * 根据目的IP地址在ARP缓存中查找匹配的表项从而完成数据帧中链路层首部的创建工作
 * 
 */
int arp_find(unsigned char *haddr, unsigned long paddr, struct device *dev,
	   unsigned long saddr, struct sk_buff *skb)
{
	struct arp_table *entry;
	unsigned long hash;
#ifdef CONFIG_IP_MULTICAST
	unsigned long taddr;
#endif	
	/*
	 * 根据tip判断是什么类型的IP地址
	 * 1. 本机地址
	 * 2. 多播地址
	 * 3. 广播地址
	 */
	switch (ip_chk_addr(paddr))
	{
		case IS_MYADDR:
			printk("ARP: arp called for own IP address\n");
			memcpy(haddr, dev->dev_addr, dev->addr_len);
			skb->arp = 1;
			return 0;
#ifdef CONFIG_IP_MULTICAST
		case IS_MULTICAST:
			if(dev->type==ARPHRD_ETHER || dev->type==ARPHRD_IEEE802)
			{
				haddr[0]=0x01;
				haddr[1]=0x00;
				haddr[2]=0x5e;
				taddr=ntohl(paddr);
				haddr[5]=taddr&0xff;
				taddr=taddr>>8;
				haddr[4]=taddr&0xff;
				taddr=taddr>>8;
				haddr[3]=taddr&0x7f;
				return 0;
			}
		/*
		 *	If a device does not support multicast broadcast the stuff (eg AX.25 for now)
		 */
#endif
		
		case IS_BROADCAST:
			memcpy(haddr, dev->broadcast, dev->addr_len);
			skb->arp = 1;
			return 0;
	}

	hash = HASH(paddr);//找到tid所属hash bulk
	cli();

	/* 从arp table中找到具体表项 */
	entry = arp_lookup(paddr, PROXY_NONE);

	if (entry != NULL) 	/* It exists */
	{
		if (!(entry->flags & ATF_COM))
		{
			/*
			 *	A request was already send, but no reply yet. Thus
			 *	queue the packet with the previous attempt
			 */
			
			if (skb != NULL)
			{
				skb_queue_tail(&entry->skb, skb);
				skb_device_unlock(skb);
			}
			sti();
			return 1;
		}

		/* 更新匹配表项的最近一次使用时间 */
		entry->last_used = jiffies;
		/* 将表项中mac返回给上层使用 */
		memcpy(haddr, entry->ha, dev->addr_len);
		if (skb)
			skb->arp = 1;
		sti();
		return 0;
	}

	/* 如果没有找到对应的表项,说明目前arp table不存在该ip的mac信息,需要新获取. */
	entry = (struct arp_table *) kmalloc(sizeof(struct arp_table),
					GFP_ATOMIC);
	if (entry != NULL)
	{
	/* 初始化arp表项. */
	        entry->mask = DEF_ARP_NETMASK;
		entry->ip = paddr;
		entry->hlen = dev->addr_len;
		entry->htype = dev->type;
		entry->flags = 0;
		memset(entry->ha, 0, dev->addr_len);
		entry->dev = dev;
		entry->last_used = jiffies;
		init_timer(&entry->timer);
		/* 注册定时器任务,该函数会给目标地址发arp报文,三次没响应则删除entry. */
		entry->timer.function = arp_expire_request;
		entry->timer.data = (unsigned long)entry;
		entry->timer.expires = ARP_RES_TIME;
		entry->next = arp_tables[hash];/* 将新entry加入到hash bulk首部. */
		arp_tables[hash] = entry;
		add_timer(&entry->timer);
		entry->retries = ARP_MAX_TRIES;
		skb_queue_head_init(&entry->skb);/* 组装一个arp请求报文. */
		if (skb != NULL)
		{	//待发送数据包的缓存工作  
			skb_queue_tail(&entry->skb, skb);
			skb_device_unlock(skb);
		}
	}
	else
	{
		if (skb != NULL && skb->free)
			kfree_skb(skb, FREE_WRITE);
  	}
	sti();

	/* 对于没有找到arp表项,组装完entry后,发送arp request. */
	arp_send(ARPOP_REQUEST, ETH_P_ARP, paddr, dev, saddr, NULL, 
		 dev->dev_addr);

	return 1;
}