ARP 協議負責 IPv4 地址到 MAC 地址的解析。內核通過 packet_type 掛鉤在網絡設備接收路徑,入口函數 arp_rcv() 完成基本校驗後交由 arp_process() 進行核心邏輯處理(請求回應、緩存更新、代理 ARP 等)。本文基於 Linux 2.6.21 源碼完整追蹤接收流程。
static struct packet_type arp_packet_type = {
.type = __constant_htons(ETH_P_ARP), // 0x0806
.func = arp_rcv,
};
dev_add_pack(&arp_packet_type); // arp_init() 中調用
int arp_rcv(struct sk_buff *skb, struct net_device *dev, struct packet_type *pt) {
struct arphdr *arp;
// 1. 線性化並檢查長度:ARP頭 + 2*硬件地址 + 2*協議地址
if (!pskb_may_pull(skb, sizeof(struct arphdr) + 2*dev->addr_len + 2*sizeof(u32)))
goto freeskb;
arp = skb->nh.arph;
// 2. 基本字段校驗
if (arp->ar_hln != dev->addr_len || // 硬件地址長度匹配
dev->flags & IFF_NOARP || // 設備禁用 ARP
skb->pkt_type == PACKET_OTHERHOST || // 非本機單播/廣播
skb->pkt_type == PACKET_LOOPBACK || // 回環
arp->ar_pln != 4) // 協議地址長度必須 4 (IPv4)
goto freeskb;
// 3. 共享檢查(Copy-on-Write)
if ((skb = skb_share_check(skb, GFP_ATOMIC)) == NULL)
goto out_of_mem;
// 4. Netfilter 掛鉤 -> arp_process
return NF_HOOK(NF_ARP, NF_ARP_IN, skb, dev, NULL, arp_process);
freeskb:
kfree_skb(skb);
out_of_mem:
return 0;
}
int arp_process(struct sk_buff *skb) {
struct net_device *dev = skb->dev;
struct in_device *in_dev = in_dev_get(dev); // 獲取 IPv4 設備配置
struct arphdr *arp = skb->nh.arph;
unsigned char *arp_ptr = (unsigned char *)(arp + 1);
struct neighbour *n;
u32 sip, tip; // 發送端 IP、目標 IP
unsigned char *sha, *tha; // 發送端 MAC、目標 MAC
if (!in_dev) goto out; // 設備無 IPv4 配置
// 1. 硬件類型/協議類型校驗(以太網/令牌環/FDDI 等)
switch (dev->type) {
case ARPHRD_ETHER:
case ARPHRD_IEEE802_TR:
case ARPHRD_FDDI:
case ARPHRD_IEEE802:
if ((arp->ar_hrd != htons(ARPHRD_ETHER) &&
arp->ar_hrd != htons(ARPHRD_IEEE802)) ||
arp->ar_pro != htons(ETH_P_IP))
goto out;
break;
// ... AX25, NETROM 等其他類型
}
// 2. 僅處理 REQUEST/REPLY
if (arp->ar_op != htons(ARPOP_REPLY) &&
arp->ar_op != htons(ARPOP_REQUEST))
goto out;
// 3. 解析地址字段
sha = arp_ptr; arp_ptr += dev->addr_len;
memcpy(&sip, arp_ptr, 4); arp_ptr += 4;
tha = arp_ptr; arp_ptr += dev->addr_len;
memcpy(&tip, arp_ptr, 4);
// 4. 過濾回環/多播目標 IP
if (LOOPBACK(tip) || MULTICAST(tip))
goto out;
// 5. 確定目標 IP 類型
int addr_type = inet_addr_type(tip);
// 6. 處理 ARP REQUEST
if (arp->ar_op == htons(ARPOP_REQUEST)) {
// 目標 IP 是本機
if (addr_type == RTN_LOCAL) {
// 構造並發送 REPLY
arp_send(ARPOP_REPLY, ETH_P_ARP, sip, dev, tip, sha, dev->dev_addr, tha);
goto out;
}
// 目標 IP 是本機代理(代理 ARP)
if (arp_fwd_proxy(in_dev, sip, tip) ||
(arp->ar_hrd == htons(ARPHRD_ETHER) && arp_proxy(in_dev, sip, tip))) {
arp_send(ARPOP_REPLY, ETH_P_ARP, sip, dev, tip, sha, dev->dev_addr, tha);
goto out;
}
}
// 7. 處理 ARP REPLY / 通用緩存更新
// 查找或創建鄰居條目
n = __neigh_lookup(&arp_tbl, &sip, dev, 1);
if (n) {
// 更新鄰居條目:狀態→REACHABLE,MAC→sha
neigh_update(n, sha, NUD_REACHABLE,
NEIGH_UPDATE_F_OVERRIDE | NEIGH_UPDATE_F_OVERRIDE_ISROUTER);
neigh_release(n);
}
out:
kfree_skb(skb);
return 0;
}
tip == 本機接口 IP (addr_type == RTN_LOCAL)。sha),直接發送(arp_send)。/proc/sys/net/ipv4/conf/*/proxy_arp 啟用,且 sip 可通過該設備路由到達。proxy_arp_pvlan (Private VLAN) 場景,隔離同 VLAN 端口間通信。__neigh_lookup(&arp_tbl, &sip, dev, 1) 查找/創建鄰居。neigh_update(..., NUD_REACHABLE, OVERRIDE) 強制設置為可達、更新 MAC。arp_queue),立即發送。arp_send()
void arp_send(int op, int type, u32 dest_ip, struct net_device *dev,
u32 src_ip, const unsigned char *dest_hw, const unsigned char *src_hw,
const unsigned char *target_hw) {
struct sk_buff *skb = alloc_skb(ARP_PACKET_SIZE, GFP_ATOMIC);
// 填充以太網頭 + ARP 頭 + 地址字段
// dev_queue_xmit(skb) 發送
}
NF_ARP_IN:arp_rcv → arp_process 之間,可修改/丟棄 ARP 包。NF_ARP_OUT:arp_send 發送前。NF_ARP_FORWARD:轉發 ARP 包(罕見)。