ARP报文的格式如下:
(具体各字段的含义参考《TCP/IP详解卷 1:协议》第4章 ARP:地址解析协议)
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 */
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) */
};
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;
}
arp_send_dst调用arp_create创建ARP报文后,调用arp_xmit发送ARP报文,最后调用__dev_queue_xmit将报文发送到网卡的发送队列里面。
ARP报文最后通过调用网卡设备驱动函数smsc911x_hard_start_xmit发送到网卡里面,smsc911x_hard_start_xmit调用栈如下:
首先网卡收到报文触发硬件中断,调用网卡的中断处理函数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应答再到发送到网卡的调用栈如下: