Linux核心ARP的處理函數分析(arp_rcv, arp_send)
int arp_rcv(struct sk_buff *skb, struct net_device *dev,
struct packet_type *pt)
當系統的網路驅動程式收到一個arp包的時候,調用這個函數處理。簡單來說,arp_rev
發回本機器或者它代理的其他機器的網卡硬體地址(mac address),並且將寄件者的網卡硬體地址放在自己的緩衝(arp cache)中。
實現過程:
* 檢查硬體地址長度(一般為6 位元組)和協議地址長度(4 位元組)是否正確。
* 調用skb_linearize 來初始化skb,這樣我們就可以用skb->nh.iph來訪問IP header.
if (in_dev == NULL ||
arp->ar_hln != dev->addr_len ||
dev->flags & IFF_NOARP ||
skb->pkt_type == PACKET_OTHERHOST ||
skb->pkt_type == PACKET_LOOPBACK ||
arp->ar_pln != 4)
goto out;
if ((skb = skb_share_check(skb, GFP_ATOMIC)) == NULL)
goto out_of_mem;
if (skb_is_nonlinear(skb)) {
if (skb_linearize(skb, GFP_ATOMIC) != 0)
goto freeskb;
arp = skb->nh.arph;
arp_ptr= (unsigned char *)(arp+1);
}
* 判斷硬體地址類型(ethernet :1)和協議地址的類型(ip :0x800)。
* 判斷arp包的類型,arp請求為1,arp應答為2。
if (arp->ar_hrd != __constant_htons(ARPHRD_ETHER) &&
arp->ar_hrd != __constant_htons(ARPHRD_IEEE802))
goto out;
if (arp->ar_pro != __constant_htons(ETH_P_IP))
goto out;
... ...
if (arp->ar_op != __constant_htons(ARPOP_REPLY) &&
arp->ar_op != __constant_htons(ARPOP_REQUEST))
goto out;
* 取得arp包各項值,包括源ethernet地址(sha)、源ip地址(sip)、目的ethernet地址(tha) 和目的ip地址(tip)。
* 如果目的地址是loopback或者是muticast地址,將這個arp包扔掉。
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);
if (LOOPBACK(tip) || MULTICAST(tip))
goto out;
* 如果源地址為0的話,這個arp包使用來檢測IP地址衝突的,則以原生ip 地址為源地址和 目的地址發回一個arp應答。
/* Special case: IPv4 duplicate address detection packet (RFC2131) */
if (sip == 0) {
if (arp->ar_op == __constant_htons(ARPOP_REQUEST) &&
inet_addr_type(tip) == RTN_LOCAL)
arp_send(ARPOP_REPLY,ETH_P_ARP,tip,dev,tip,sha,dev->dev_addr,dev->dev_addr);
goto out;
}
* 如果是arp請求,調用ip_route_input找到目的地址(tip)的路由。
(a)如果tip是本機地址,則調用neigh_event_ns來更新arp 緩衝(arp_tbl),記下對方
的ip和硬體地址。然後調用arp_send發回應答。
既然對方發給我arp請求,很可能他會和我通訊,存下他的地址會縮短下次處理的時間,
這是保留髮送者的ip和硬體地址的原因。
(b)如果tip是本機需要forward的ip,也進行類似的操作,但是如果proxy_delay 不等於 0
的話,不馬上發回應答,而是將這個arp包先放在arp_tbl中的proxy_queue 中,設定定時
器,以後再發送。另外,調用pneigh_lookup判斷tip的硬體地址是否在常駐緩衝,也要此時發回應答。
if (arp->ar_op == __constant_htons(ARPOP_REQUEST) &&
ip_route_input(skb, tip, sip, 0, dev) == 0) {
rt = (struct rtable*)skb->dst;
addr_type = rt->rt_type;
if (addr_type == RTN_LOCAL) {
n = neigh_event_ns(&arp_tbl, sha, &sip, dev);
if (n) {
int dont_send = 0;
if (IN_DEV_ARPFILTER(in_dev))
dont_send |= arp_filter(sip,tip,dev);
if (!dont_send)
arp_send(ARPOP_REPLY,ETH_P_ARP,sip,dev,tip,sha,dev->dev_addr,sha);
neigh_release(n);
}
goto out;
} else if (IN_DEV_FORWARD(in_dev)) {
if ((rt->rt_flags&RTCF_DNAT) ||
(addr_type == RTN_UNICAST && rt->u.dst.dev != dev &&
(IN_DEV_PROXY_ARP(in_dev) || pneigh_lookup(&arp_tbl, &tip, dev, 0)))) {
n = neigh_event_ns(&arp_tbl, sha, &sip, dev);
if (n)
neigh_release(n);
if (skb->stamp.tv_sec == 0 ||
skb->pkt_type == PACKET_HOST ||
in_dev->arp_parms->proxy_delay == 0) {
arp_send(ARPOP_REPLY,ETH_P_ARP,sip,dev,tip,sha,dev->dev_addr,sha);
} else {
pneigh_enqueue(&arp_tbl, in_dev->arp_parms, skb);
in_dev_put(in_dev);
return 0;
}
goto out;
}
}
}
* 處理完arp請求後,arp_rcv不管收到的是請求還是應答,根據sip調用__neigh_lookup來查
找arp_tbl,找不到則插入一個新的entry,存入對方的ip和硬體地址,最後調用neigh_update 來更新這個entry的狀態。
n = __neigh_lookup(&arp_tbl, &sip, dev, 0);
if (n) {
int state = NUD_REACHABLE;
int override = 0;
/* If several different ARP replies follows back-to-back,
use the FIRST one. It is possible, if several proxy
agents are active. Taking the first reply prevents
arp trashing and chooses the fastest router.
*/
if (jiffies - n->updated >= n->parms->locktime)
override = 1;
/* Broadcast replies and request packets
do not assert neighbour reachability.
*/
if (arp->ar_op != __constant_htons(ARPOP_REPLY) ||
skb->pkt_type != PACKET_HOST)
state = NUD_STALE;
neigh_update(n, sha, state, override, 1);
neigh_release(n);
}
void arp_send(int type, int ptype, u32 dest_ip,
struct net_device *dev, u32 src_ip,
unsigned char *dest_hw, unsigned char *src_hw,
unsigned char *target_hw)
當系統的網絡驅動程式收到一個arp包的時候,調用這個函數處理。簡單來說,arp_rev
發回本機器或者它代理的其他機器的網卡硬體地址(mac address),並且將發送者的網卡硬體地址放在自己的緩存(arp cache)中。
實現過程:
(1) 分配一個sk_buff,用來構造ARP包,除了必要的空間以外,還預留了15字節作備用。
調用skb_reserve空出前面的15字節和網絡包頭部。
skb = alloc_skb(sizeof(struct arphdr)+ 2*(dev->addr_len+4)
+ dev->hard_header_len + 15, GFP_ATOMIC);
if (skb == NULL)
return;
skb_reserve(skb, (dev->hard_header_len+15)&~15);
skb->nh.raw = skb->data;
arp = (struct arphdr *) skb_put(skb,sizeof(struct arphdr) + 2*(dev->addr_len+4));
skb->dev = dev;
skb->protocol = __constant_htons (ETH_P_ARP);
+ 如果從參數傳進來的源硬體地址為空的話,則設為設備的硬體地址;
如果從參數傳進來的目的硬體地址為空的話,則設為廣播地址。
if (src_hw == NULL)
src_hw = dev->dev_addr;
if (dest_hw == NULL)
dest_hw = dev->broadcast;
+ 調用網卡設備本身的MAC頭部構造函數,填好這個ARP包的ethernet目的硬體地址和源硬體地址。
ethernet的預設構造函數為eth_header,請參閱net_init.c中的ether_setup函數。
if (dev->hard_header &&
dev->hard_header(skb,dev,ptype,dest_hw,src_hw,skb->len) ar_hrd = htons(dev->type);
arp->ar_pro = __constant_htons(ETH_P_IP);
+ 填寫硬體地址長度(一般為6 字節)和協議地址長度(4 字節);
填寫arp包的類型,arp請求為1,arp應答為2。
arp->ar_hln = dev->addr_len;
arp->ar_pln = 4;
arp->ar_op = htons(type);
填寫arp包的數據,包括源ethernet地址(sha)、源ip地址(sip)、目的ethernet地址(tha) 和目的ip地址(tip)。
arp_ptr=(unsigned char *)(arp+1);
memcpy(arp_ptr, src_hw, dev->addr_len);
arp_ptr+=dev->addr_len;
memcpy(arp_ptr, &src_ip,4);
arp_ptr+=4;
if (target_hw != NULL)
memcpy(arp_ptr, target_hw, dev->addr_len);
else
memset(arp_ptr, 0, dev->addr_len);
arp_ptr+=dev->addr_len;
memcpy(arp_ptr, &dest_ip, 4);
+ 最後,調用dev_queue_xmit將這個ARP包放在發送隊列中準備發送。
dev_queue_xmit(skb);