作者:gfree.wind@gmail.com
博客:linuxfocus.blog.chinaunix.net
在IPv4中,ARP是Linux TCP/IP中的neighbour system的实现,今天主要是学习一下Linux kernel的ARP代码。
ARP模块的初始化函数arp_init是由inet_init()调用。
- void __init arp_init(void)
-
{
- /* 初始化ARP neighbor table */
-
neigh_table_init(&arp_tbl);
- /* 注册ARP数据包类型到L2 */
-
dev_add_pack(&arp_packet_type);
- /*
- 初始化ARP对应的/proc/net/arp
- */
-
arp_proc_init();
-
#ifdef CONFIG_SYSCTL
-
neigh_sysctl_register(NULL, &arp_tbl.parms, "ipv4", NULL);
-
#endif
- /*
- 加入到netdevice的通知链。
- 因为ARP module需要handle网络设备的事件
- */
-
register_netdevice_notifier(&arp_netdev_notifier);
- }
下面看ARP数据包的接收处理函数
- static int arp_rcv(struct sk_buff *skb, struct net_device *dev,
-
struct packet_type *pt, struct net_device *orig_dev)
-
{
-
struct arphdr *arp;
-
-
/* ARP header, plus 2 device addresses, plus 2 IP addresses. */
- /*
- 确保buffer可以容纳ARP包。
- 这里arp_hdr_len从名字上看,似乎是arp的报文头长。
- 但是该函数返回的是return sizeof(struct arphdr) + (dev->addr_len + sizeof(u32)) * 2;
- 也就是整个儿ARP报文的长度。与函数名字不符。
- 另外,其它协议的接收处理函数,都是确保有首部的长度,而这里确实验证完整的ARP包长。
- 我认为是因为ARP本身就没有首部和数据部得概念的区分。
- */
-
if (!pskb_may_pull(skb, arp_hdr_len(dev)))
-
goto freeskb;
/* 拿到arp报文的指针 */
-
arp = arp_hdr(skb);
/*
sanity check:
1. arp中的l2地址长度与接收包的dev的l2地址长度不符;
2. 接收包的device不支持ARP;
3. 该数据包的l2地址是发送到别的主机的;
4. 该数据包是发送到回环口的;
5. arp中的l3地址长度不是4字节;
*/
-
if (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 freeskb;
/* 做skb的share check,因为后面的代码会修改skb,所以需要获得唯一的skb */
-
if ((skb = skb_share_check(skb, GFP_ATOMIC)) == NULL)
-
goto out_of_mem;
/* 到此,已确定该数据包由ARP处理,所以初始化skb中的cb字段,用于arp后面的处理 */
-
memset(NEIGH_CB(skb), 0, sizeof(struct neighbour_cb));
/* 通过netfilter的检查点 */
-
return NF_HOOK(NFPROTO_ARP, NF_ARP_IN, skb, dev, NULL, arp_process);
-
-
freeskb:
-
kfree_skb(skb);
-
out_of_mem:
-
return 0;
- }
继续看ARP的数据包的处理
- static int arp_process(struct sk_buff *skb)
-
{
-
struct net_device *dev = skb->dev;
-
struct in_device *in_dev = __in_dev_get_rcu(dev);
-
struct arphdr *arp;
-
unsigned char *arp_ptr;
-
struct rtable *rt;
-
unsigned char *sha;
-
__be32 sip, tip;
-
u16 dev_type = dev->type;
-
int addr_type;
-
struct neighbour *n;
-
struct net *net = dev_net(dev);
-
-
/* arp_rcv below verifies the ARP header and verifies the device
-
* is ARP'able.
-
*/
- /* 该dev没有指定IP */
-
if (in_dev == NULL)
-
goto out;
-
-
arp = arp_hdr(skb);
-
-
switch (dev_type) {
-
default:
- /* arp的l3 协议类型不是IP或者dev的l2类型与arp的l2类型不符 */
-
if (arp->ar_pro != htons(ETH_P_IP) ||
-
htons(dev_type) != arp->ar_hrd)
-
goto out;
-
break;
-
case ARPHRD_ETHER:
-
case ARPHRD_IEEE802_TR:
-
case ARPHRD_FDDI:
-
case ARPHRD_IEEE802:
-
/*
-
* ETHERNET, Token Ring and Fibre Channel (which are IEEE 802
-
* devices, according to RFC 2625) devices will accept ARP
-
* hardware types of either 1 (Ethernet) or 6 (IEEE 802.2).
-
* This is the case also of FDDI, where the RFC 1390 says that
-
* FDDI devices should accept ARP hardware of (1) Ethernet,
-
* however, to be more robust, we'll accept both 1 (Ethernet)
-
* or 6 (IEEE 802.2)
-
*/
- /* 仍然是做sanity check,原因如上面的注释 */
-
if ((arp->ar_hrd != htons(ARPHRD_ETHER) &&
-
arp->ar_hrd != htons(ARPHRD_IEEE802)) ||
-
arp->ar_pro != htons(ETH_P_IP))
-
goto out;
-
break;
-
case ARPHRD_AX25:
-
if (arp->ar_pro != htons(AX25_P_IP) ||
-
arp->ar_hrd != htons(ARPHRD_AX25))
-
goto out;
-
break;
-
case ARPHRD_NETROM:
-
if (arp->ar_pro != htons(AX25_P_IP) ||
-
arp->ar_hrd != htons(ARPHRD_NETROM))
-
goto out;
-
break;
-
}
-
-
/* Understand only these message types */
/* 只支持ARP的正向查询 */
-
if (arp->ar_op != htons(ARPOP_REPLY) &&
-
arp->ar_op != htons(ARPOP_REQUEST))
-
goto out;
-
-
/*
-
* Extract fields
-
*/
- /* 得到ARP中的源l2地址sha,源l3地址sip和目的l3地址dip */
-
arp_ptr= (unsigned char *)(arp+1);
-
sha = arp_ptr;
-
arp_ptr += dev->addr_len;
-
memcpy(&sip, arp_ptr, 4);
-
arp_ptr += 4;
-
arp_ptr += dev->addr_len;
-
memcpy(&tip, arp_ptr, 4);
-
/*
-
* Check for bad requests for 127.x.x.x and requests for multicast
-
* addresses. If this is one such, delete it.
-
*/
- /* 如果目的IP为回环或者组播地址,也不处理该ARP包 */
-
if (ipv4_is_loopback(tip) || ipv4_is_multicast(tip))
-
goto out;
-
-
/*
-
* Special case: We must set Frame Relay source Q.922 address
-
*/
-
if (dev_type == ARPHRD_DLCI)
-
sha = dev->broadcast;
-
-
/*
-
* 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.
-
*/
-
-
/* Special case: IPv4 duplicate address detection packet (RFC2131) */
-
if (sip == 0) {
- /*
- 如果源ip为0,则是一种特殊的ARP,用于检测重复的IP地址。
- 如果为ARP request,且通过检查目的地址,确认该包需要本机处理,
- 且不忽略ARP,则回复ARP。
- */
-
if (arp->ar_op == htons(ARPOP_REQUEST) &&
-
inet_addr_type(net, tip) == RTN_LOCAL &&
-
!arp_ignore(in_dev, sip, tip))
-
arp_send(ARPOP_REPLY, ETH_P_ARP, sip, dev, tip, sha,
-
dev->dev_addr, sha);
-
goto out;
-
}
-
-
if (arp->ar_op == htons(ARPOP_REQUEST) &&
-
ip_route_input_noref(skb, tip, sip, 0, dev) == 0) {
- /* ARP为request, ip_route_input_noref返回成功。即查找input route成功 */
-
rt = skb_rtable(skb);
-
addr_type = rt->rt_type;
/* 路由的类型进行ARP的不同处理 */
-
if (addr_type == RTN_LOCAL) {
- /* 该ARP应有本机处理 */
-
int dont_send = 0;
/* 做是否dont send的检查 */
-
if (!dont_send)
-
dont_send |= arp_ignore(in_dev,sip,tip);
-
if (!dont_send && IN_DEV_ARPFILTER(in_dev))
-
dont_send |= arp_filter(sip,tip,dev);
-
if (!dont_send) {
- /*
- 查找sip对应的neibour信息,如没有,则将数据包的源l2地址sha和l3地址sip加入到表中 */
-
n = neigh_event_ns(&arp_tbl, sha, &sip, dev);
-
if (n) {
- /* 发送ARP reply */
-
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)) {
- /* dev支持转发 */
-
if (addr_type == RTN_UNICAST &&
-
(arp_fwd_proxy(in_dev, dev, rt) ||
-
arp_fwd_pvlan(in_dev, dev, rt, sip, tip) ||
-
pneigh_lookup(&arp_tbl, net, &tip, dev, 0)))
-
{
- /*
- 本机enable了ARP proxy,并且与目的地址tip直连,且有tip对应的neighbour信息。
- 那么,就要做arp proxy的工作了。
- */
/*
将sha和sip加入到arp table中。
*/
-
n = neigh_event_ns(&arp_tbl, sha, &sip, dev);
-
if (n)
-
neigh_release(n);
-
-
if (NEIGH_CB(skb)->flags & LOCALLY_ENQUEUED ||
-
skb->pkt_type == PACKET_HOST ||
-
in_dev->arp_parms->proxy_delay == 0) {
- /* 回复ARP请求 */
-
arp_send(ARPOP_REPLY,ETH_P_ARP,sip,dev,tip,sha,dev->dev_addr,sha);
-
} else {
- /* 将数据包加入到arp table的队列中,由对应的timer handler处理 */
-
pneigh_enqueue(&arp_tbl, in_dev->arp_parms, skb);
-
return 0;
-
}
-
goto out;
-
}
-
}
-
}
-
-
/* Update our ARP tables */
-
-
n = __neigh_lookup(&arp_tbl, &sip, dev, 0);
-
-
if (IPV4_DEVCONF_ALL(dev_net(dev), ARP_ACCEPT)) {
- /* 如果dev指定接收所有的ARP */
-
/* Unsolicited ARP is not accepted by default.
-
It is possible, that this option should be enabled for some
-
devices (strip is candidate)
-
*/
/*
默认情况下,只接受回复自己请求的ARP,防止ARP攻击。
但是有的情况下,linux作为中间设备,需要学习所有经过它的ARP,那么就需要打开这个option。
当然这就引入了风险。
*/
/*
没有找到对应neighbou时且本机与sip直连,那么下面的2种情况下,
本机会学习非回复请求的ARP,即unsolicited ARP;
1. ARP是reply;
2. ARP为gratuitous ARP;
*/
-
if (n == NULL &&
-
(arp->ar_op == htons(ARPOP_REPLY) ||
-
(arp->ar_op == htons(ARPOP_REQUEST) && tip == sip)) &&
-
inet_addr_type(net, sip) == RTN_UNICAST)
-
n = __neigh_lookup(&arp_tbl, &sip, dev, 1);
-
}
-
-
if (n) {
- /* 找到了对应的neighbour信息,那么就更新其时间戳即状态 */
-
int state = NUD_REACHABLE;
-
int override;
-
-
/* 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.
-
*/
-
override = time_after(jiffies, n->updated + n->parms->locktime);
-
-
/* Broadcast replies and request packets
-
do not assert neighbour reachability.
-
*/
-
if (arp->ar_op != htons(ARPOP_REPLY) ||
-
skb->pkt_type != PACKET_HOST)
-
state = NUD_STALE;
-
neigh_update(n, sha, state, override ? NEIGH_UPDATE_F_OVERRIDE : 0);
-
neigh_release(n);
-
}
-
-
out:
-
consume_skb(skb);
-
return 0;
- }
ARP的接收流程比较简单,但是有一些特殊的ARP数据包,如检测重复IP,如果不看这个源代码,我都有些忘记了。而且从通过阅读代码,彻底搞清楚了,Linux更新ARP表的条件。
1. 如果数据包的L2地址是发送给别的主机或者回环的,则不处理;
2. 如ARP是发给本机且为request,则回复ARP并更新本机的ARP table;
3. 如ARP是需要转发,且本机为arp proxy,则回复并更新本机的ARP table;
4. 如本机指定了接收所有ARP,即接收unsolicited的ARP数据包,则在没有对应的ARP条目,且sip与本机直连:1)ARP为reply;2)ARP为gratuitous ARP时,更新ARP table;
上面的总结,并不是特别严谨,但大致的条件就是如此。