linux网络协议栈源码分析 - 链路层ARP地址解析协议

Posted arm7star

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了linux网络协议栈源码分析 - 链路层ARP地址解析协议相关的知识,希望对你有一定的参考价值。

1、ARP报文格式

 

1.1、ARP报文格式

ARP报文的格式如下:

(具体各字段的含义参考《TCP/IP详解卷 1:协议》第4章 ARP:地址解析协议)

1.2、内核定义

op定义如下:

#define	ARPOP_REQUEST	1		/* ARP request			*/
#define	ARPOP_REPLY	2		/* ARP reply			*/

协议类型定义如下:

#define ETH_P_IP	0x0800		/* Internet Protocol packet	*/
#define ETH_P_ARP	0x0806		/* Address Resolution packet	*/


 

2、ARP报文的创建(arp_create)

2.1、ARP首部结构体

struct arphdr 
	__be16		ar_hrd;		/* format of hardware address	*/
	__be16		ar_pro;		/* format of protocol address	*/
	unsigned char	ar_hln;		/* length of hardware address	*/
	unsigned char	ar_pln;		/* length of protocol address	*/
	__be16		ar_op;		/* ARP opcode (command)		*/
;

2.2、ARP报文创建(arp_create)

        ARP报文的创建主要就是为ARP报文分配内存并设置以太网首部、28字节ARP请求/应答的各个字段。arp_create函数代码如下:

struct sk_buff *arp_create(int type, int ptype, __be32 dest_ip, // type:  op(ARPOP_REQUEST/ARPOP_REPLY), ptype: 帧类型(ETH_P_IP/ETH_P_ARP), dest_ip: 目的IP地址
			   struct net_device *dev, __be32 src_ip, // dev: 输出网卡设备, src_ip: 发送端IP地址
			   const unsigned char *dest_hw, // dest_hw: 以太网目的地址(以太网首部)
			   const unsigned char *src_hw, // 发送端以太网地址
			   const unsigned char *target_hw) // target_hw: 目的以太网地址(ARP应答报文设置, ARP请求为空)

	struct sk_buff *skb;
	struct arphdr *arp;
	unsigned char *arp_ptr;
	int hlen = LL_RESERVED_SPACE(dev);
	int tlen = dev->needed_tailroom;

	/*
	 *	Allocate a buffer
	 */

	skb = alloc_skb(arp_hdr_len(dev) + hlen + tlen, GFP_ATOMIC); // 分配ARP报文内存(以太网首部+28字节ARP请求/应答)
	if (!skb)
		return NULL;

	skb_reserve(skb, hlen); // 预留hlen长度的内存(以太网首部)
	skb_reset_network_header(skb);
	arp = (struct arphdr *) skb_put(skb, arp_hdr_len(dev)); // 预留ARP首部的内存空间(获取skb内存地址,skb内存地址后移ARP首部大小)
	skb->dev = dev;
	skb->protocol = htons(ETH_P_ARP); // 帧类型(ETH_P_ARP, ARP请求或应答)
	if (!src_hw)
		src_hw = dev->dev_addr;
	if (!dest_hw) // 目的以太网地址(ARP请求没有目的以太网地址,设置为广播地址FF:FF:FF:FF:FF:FF)
		dest_hw = dev->broadcast; // 广播地址FF:FF:FF:FF:FF:FF

	/*
	 *	Fill the device header for the ARP frame
	 */
	if (dev_hard_header(skb, dev, ptype, dest_hw, src_hw, skb->len) < 0) // 调用eth_header填充以太网首部(ptype: 帧类型, dest_hw: 以太网目的地址, src_hw: 以太网源地址)
		goto out;

	/*
	 * Fill out the arp protocol part.
	 *
	 * The arp 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.25 PID value not the
	 *	DIX code for the protocol. Make these device structure fields.
	 */
	switch (dev->type) 
	default:
		arp->ar_hrd = htons(dev->type); // 设置ARP首部硬件类型(ARPHRD_ETHER)
		arp->ar_pro = htons(ETH_P_IP); // 设置ARP首部协议类型(ETH_P_IP)
		break;

#if IS_ENABLED(CONFIG_AX25)
	case ARPHRD_AX25:
		arp->ar_hrd = htons(ARPHRD_AX25);
		arp->ar_pro = htons(AX25_P_IP);
		break;

#if IS_ENABLED(CONFIG_NETROM)
	case ARPHRD_NETROM:
		arp->ar_hrd = htons(ARPHRD_NETROM);
		arp->ar_pro = htons(AX25_P_IP);
		break;
#endif
#endif

#if IS_ENABLED(CONFIG_FDDI)
	case ARPHRD_FDDI:
		arp->ar_hrd = htons(ARPHRD_ETHER);
		arp->ar_pro = htons(ETH_P_IP);
		break;
#endif
	

	arp->ar_hln = dev->addr_len; // 设置ARP首部硬件地址长度(6, 物理地址长度)
	arp->ar_pln = 4; // 设置ARP首部协议地址长度(4, IP地址长度)
	arp->ar_op = htons(type); // op(ARPOP_REQUEST/ARPOP_REPLY)

	arp_ptr = (unsigned char *)(arp + 1); // arp_ptr指针指向发送端以太网地址

	memcpy(arp_ptr, src_hw, dev->addr_len); // 拷贝发送端以太网地址到ARP报文发送端以太网地址里面
	arp_ptr += dev->addr_len; // 发送端以太网地址指针+地址长度(6),arp_ptr指针指向发送端IP地址
	memcpy(arp_ptr, &src_ip, 4); // 拷贝发送端IP地址到ARP报文发送端地址里面
	arp_ptr += 4; // 发送端IP地址指针+4,arp_ptr指针指向目的以太网地址

	switch (dev->type) 
#if IS_ENABLED(CONFIG_FIREWIRE_NET)
	case ARPHRD_IEEE1394:
		break;
#endif
	default:
		if (target_hw) // 目的以太网地址不为空(ARP应答报文)
			memcpy(arp_ptr, target_hw, dev->addr_len); // 拷贝目的以太网地址到ARP的目的以太网地址里面
		else // 目的以太网地址为空(ARP请求报文)
			memset(arp_ptr, 0, dev->addr_len); // ARP请求报文,设置目的以太网地址为00:00:00:00:00:00
		arp_ptr += dev->addr_len; // 目的以太网地址指针+地址长度(6),arp_ptr指针指向目的IP地址
	
	memcpy(arp_ptr, &dest_ip, 4); // 拷贝目的IP地址到ARP报文的目的IP地址里面

	return skb;

out:
	kfree_skb(skb);
	return NULL;

3、ARP报文的发送

3.1、ARP请求报文的发送

        arp_send_dst调用arp_create创建ARP报文后,调用arp_xmit发送ARP报文,最后调用__dev_queue_xmit将报文发送到网卡的发送队列里面。

         ARP报文最后通过调用网卡设备驱动函数smsc911x_hard_start_xmit发送到网卡里面,smsc911x_hard_start_xmit调用栈如下:

       

 3.2、ARP应答报文的发送

        首先网卡收到报文触发硬件中断,调用网卡的中断处理函数smsc911x_irqhandler(smsc911x_drv_probe注册的中断处理函数),smsc911x_irqhandler最终调用____napi_schedule触发NET_RX_SOFTIRQ软件中断,在硬件中断返回时,调用软件中断处理函数。

        __do_softirq软件中断调用栈如下:

        smsc911x_poll从网卡读取报文并设置帧类型skb->protocol,__netif_receive_skb_core调用deliver_ptype_list_skb找到对应协议的packet_type,packet_type报文对应协议的输入函数,对于ARP协议的packet_type就是arp_packet_type,处理函数就是arp_rcv,arp_rcv最终调用arp_process处理ARP报文,对于ARP请求,调用arp_send_dst发送ARP应答报文,调用代码如下:

         ARP请求输入到ARP应答再到发送到网卡的调用栈如下:

以上是关于linux网络协议栈源码分析 - 链路层ARP地址解析协议的主要内容,如果未能解决你的问题,请参考以下文章

LWIP协议栈:ARP协议

Linux 网络基础--网络层+数据链路层

Linux网络编程----网络接口层(数据链路层+物理层)

Linux网络编程----网络接口层(数据链路层+物理层)

Linux网络编程(数据链路层)

Linux从青铜到王者第二十一篇:Linux网络基础第三篇之数据链路层