文章目录
邻居子系统支持代理功能,即主机在收到一个目的地址查询不属于自己地址的ARP请求时,可以代替真正的主机做出响应,这就是邻居代理。
代理条件
当然,主机不能对任何不属于自己的ARP请求都进行代理,使用代理应该遵守如下的条件:
- 请求的地址和收到该请求的接口上配置的地址不能处于同一个子网;因为如果属于同一个子网,那么真正的目的主机也能收到该请求,它会对该请求做出响应,此时代理是没有意义的;
- 代理功能是打开的,即设备的代理功能是否开启应该是可配置的;
通用代理机制
邻居子系统框架为邻居代理提供了一些通用的机制,这些机制需要具体邻居协议配合才能发挥作用。
延时处理请求
为何要设计延时处理机制,实在是没有理解透彻。
代理请求入队列: pneigh_enqueue()
void pneigh_enqueue(struct neigh_table *tbl, struct neigh_parms *p,
struct sk_buff *skb)
{
unsigned long now = jiffies;
// 实际延时的时间为一个随机数,proxy_delay配置的是最大时延
unsigned long sched_next = now + (net_random() % p->proxy_delay);
// 代理队列已经超过了上线,代理失败
if (tbl->proxy_queue.qlen > p->proxy_qlen) {
kfree_skb(skb);
return;
}
// 在skb控制块中记录超时时间,并设置LOCALLY_ENQUEUED
NEIGH_CB(skb)->sched_next = sched_next;
NEIGH_CB(skb)->flags |= LOCALLY_ENQUEUED;
// 重启定时器,并将代理请求放入proxy_queue中等待定时器超时处理
spin_lock(&tbl->proxy_queue.lock);
if (del_timer(&tbl->proxy_timer)) {
if (time_before(tbl->proxy_timer.expires, sched_next))
sched_next = tbl->proxy_timer.expires;
}
dst_release(skb->dst);
skb->dst = NULL;
dev_hold(skb->dev);
__skb_queue_tail(&tbl->proxy_queue, skb);
mod_timer(&tbl->proxy_timer, sched_next);
spin_unlock(&tbl->proxy_queue.lock);
}
代理请求处理定时器: neigh_proxy_process()
static void neigh_proxy_process(unsigned long arg)
{
struct neigh_table *tbl = (struct neigh_table *)arg;
long sched_next = 0;
unsigned long now = jiffies;
struct sk_buff *skb, *n;
spin_lock(&tbl->proxy_queue.lock);
// 处理代理请求队列中所有超时的skb
skb_queue_walk_safe(&tbl->proxy_queue, skb, n) {
long tdif = NEIGH_CB(skb)->sched_next - now;
if (tdif <= 0) {
struct net_device *dev = skb->dev;
__skb_unlink(skb, &tbl->proxy_queue);
// 具体处理方式调用的是邻居协议自己的proxy_redo()回调,ARP协议的实现
// 非常直接,就是调用arp_process()
if (tbl->proxy_redo && netif_running(dev))
tbl->proxy_redo(skb);
else
kfree_skb(skb);
dev_put(dev);
} else if (!sched_next || tdif < sched_next)
sched_next = tdif;
}
// 重启定时器
del_timer(&tbl->proxy_timer);
if (sched_next)
mod_timer(&tbl->proxy_timer, jiffies + sched_next);
spin_unlock(&tbl->proxy_queue.lock);
}
代理类型
邻居子系统从两个维度上来控制是否启动代理:
- 基于设备的代理配置。该设备收到的非本机请求都触发代理;
- 基于目的地址的代理配置。按照请求的目的地址决定是否进行代理;
基于设备的代理配置实现见下方ARP代理介绍。这里重点看框架为了支持基于目的地址的代理配置所提供的机制实现。
目的地址代理配置项: pneigh_entry
每个pneigh_entry代表了一个目的地址配置,所有的pneigh_entry对象被组织到邻居协议phash_buckets哈希表中。
struct pneigh_entry
{
struct pneigh_entry *next;
#ifdef CONFIG_NET_NS
struct net *net;
#endif
struct net_device *dev;
u8 flags;
u8 key[0]; // L3地址
};
目低地址代理配置项查询: pneigh_lookup()
和neigh_lookup()类似,该函数不仅仅可以查询,也可以根据参数在查询失败时进行创建。
struct pneigh_entry * pneigh_lookup(struct neigh_table *tbl,
struct net *net, const void *pkey, struct net_device *dev, int creat)
{
struct pneigh_entry *n;
int key_len = tbl->key_len;
u32 hash_val = pneigh_hash(pkey, key_len); // 计算哈希桶索引
// 单纯的查询,查询失败n为NULL
read_lock_bh(&tbl->lock);
n = __pneigh_lookup_1(tbl->phash_buckets[hash_val], net, pkey, key_len, dev);
read_unlock_bh(&tbl->lock);
if (n || !creat)
goto out;
// 查询失败并且需要创建一个(creat参数)
ASSERT_RTNL();
// 分配pneigh_entry对象,并保存L3地址
n = kmalloc(sizeof(*n) + key_len, GFP_KERNEL);
if (!n)
goto out;
write_pnet(&n->net, hold_net(net));
memcpy(n->key, pkey, key_len);
n->dev = dev;
if (dev)
dev_hold(dev);
// 回调构造函数
if (tbl->pconstructor && tbl->pconstructor(n)) {
if (dev)
dev_put(dev);
release_net(net);
kfree(n);
n = NULL;
goto out;
}
// 将pneigh_entry对象插入哈希表中
write_lock_bh(&tbl->lock);
n->next = tbl->phash_buckets[hash_val];
tbl->phash_buckets[hash_val] = n;
write_unlock_bh(&tbl->lock);
out:
return n;
}
类似的,也有pneigh_delete()函数,这里不再展开。
ARP代理
arp_process()
...
} else if (IN_DEV_FORWARD(in_dev)) { // 设备要开启转发功能
// cond1: 路由结果显示是单播报文;
// cond2: 路由结果显示转发该报文需要通过不同的网络设备出去,意味着不属于同一个子网
// (同时排除了一个网络设备上配置两个子网IP地址的情况);
// cond3: 子条件1表示开启了基于设备的代理,子条件2表示开启了基于目的地址的代理;
if (addr_type == RTN_UNICAST && rt->u.dst.dev != dev &&
(arp_fwd_proxy(in_dev, rt) || pneigh_lookup(&arp_tbl, net, &tip, dev, 0))) {
// 即使是代理,其源IP和源L2地址的映射关系也是可以学习的,也属于被动学习
n = neigh_event_ns(&arp_tbl, sha, &sip, dev);
if (n)
neigh_release(n);
// cond1:设置了LOCALLY_ENQUEUED标记表示请求来自延时处理队列
// cond2: L2层识别到数据包就是发给本机的;
// cond3: 不使用延时处理
if (NEIGH_CB(skb)->flags & LOCALLY_ENQUEUED ||
skb->pkt_type == PACKET_HOST ||
in_dev->arp_parms->proxy_delay == 0) {
// 不使用延迟处理的情况立即响应,注意回复的L2地址为本机入口网络设备的地址
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_fwd_proxy()
/*
* Check if we can use proxy ARP for this path
*/
static inline int arp_fwd_proxy(struct in_device *in_dev, struct rtable *rt)
{
struct in_device *out_dev;
int imi, omi = -1;
// 检查IPv4配置块中的proxy_arp选项是否打开
if (!IN_DEV_PROXY_ARP(in_dev))
return 0;
// 输入设备上如果没有开启medium_id,返回可以代理(默认不开启)
if ((imi = IN_DEV_MEDIUM_ID(in_dev)) == 0)
return 1;
// 输入设备上的medium_id为-1,会关闭代理功能
if (imi == -1)
return 0;
// 根据路由结果,找到输出设备上配置的medium_id
if ((out_dev = in_dev_get(rt->u.dst.dev)) != NULL) {
omi = IN_DEV_MEDIUM_ID(out_dev);
in_dev_put(out_dev);
}
// 输入设备和输出设备上的medium_id必须不同,才能代理
return (omi != imi && omi != -1);
}
medium_id指的时IPv4配置块中的一个配置项,引入它是为了解决复杂场景下arp代理工作异常的情况(启动代理的主机有多个网络设备同时连接到了同一个局域网,配置不当的情况下会工作异常),关于medium_id的使用和介绍,可以参考这里。