proxy_qlen控制邻居代理缓存的报文数量,proxy_delay定义处理需要代理的arp报文的时间间隔。对于arp协议,内核默认的proxy_qlen为64,proxy_delay为80。
通过PROC文件proxy_qlen可查看和修改其值。
$ cat /proc/sys/net/ipv4/neigh/ens33/proxy_qlen
64
$ cat /proc/sys/net/ipv4/neigh/ens33/proxy_delay
80
在arp邻居表arp_tbl中将NEIGH_VAR_PROXY_QLEN索引所对应的表项初始化为64,将NEIGH_VAR_PROXY_DELAY索引所对应的表项初始化为HZ值的8/10,对于内核如果HZ为1000,其值为800;而对于用户层面,HZ为100,其值为80。
struct neigh_table arp_tbl = {
.family = AF_INET,
.key_len = 4,
.protocol = cpu_to_be16(ETH_P_IP),
.hash = arp_hash,
.key_eq = arp_key_eq,
.constructor = arp_constructor,
.proxy_redo = parp_redo,
.id = "arp_cache",
.parms = {
.tbl = &arp_tbl,
.reachable_time = 30 * HZ,
.data = {
[NEIGH_VAR_PROXY_QLEN] = 64,
[NEIGH_VAR_PROXY_DELAY] = (8 * HZ) / 10,
内核中静态变量neigh_sysctl_table定义了proxy_qlen和proxy_delay的PROC文件信息。
static struct neigh_sysctl_table {
struct ctl_table_header *sysctl_header;
struct ctl_table neigh_vars[NEIGH_VAR_MAX + 1];
} neigh_sysctl_template __read_mostly = {
.neigh_vars = {
...
NEIGH_SYSCTL_ZERO_INTMAX_ENTRY(PROXY_QLEN, "proxy_qlen"),
NEIGH_SYSCTL_USERHZ_JIFFIES_ENTRY(PROXY_DELAY, "proxy_delay"),
netlink接口
除了以上的PROC文件外,还可使用ip ntable命令查看和修改设备的邻居表参数proxy_qlen和proxy_delay的值。
# $ ip ntable show dev ens33
inet arp_cache
dev ens33
refcnt 1 reachable 37268 base_reachable 30000 retrans 1000
gc_stale 60000 delay_probe 5000 queue 101
app_probes 0 ucast_probes 3 mcast_probes 3
anycast_delay 1000 proxy_delay 800 proxy_queue 64 locktime 1000
inet6 ndisc_cache
dev ens33
refcnt 1 reachable 31516 base_reachable 30000 retrans 1000
gc_stale 60000 delay_probe 5000 queue 101
app_probes 0 ucast_probes 3 mcast_probes 3
anycast_delay 1000 proxy_delay 800 proxy_queue 64 locktime 0
对于proxy_qlen,与PROC文件不同,这里使用的名称为proxy_queue,其值等于64。这里显示的proxy_delay值为毫秒值。如下将设备ens33的邻居表参数proxy_queue修改为128。将proxy_delay设置为1000,即1秒钟。
# ip ntable change name arp_cache dev ens33 proxy_queue 128
# ip ntable change name arp_cache dev ens33 proxy_delay 1000
内核函数neightbl_set负责以上ip ntable change命令的处理。函数nla_get_u32读取IP命令行设置的proxy_queue的值。对于arp协议,宏NEIGH_VAR_SET将修改全局变量arp_tbl的成员parms的data数组,具体为以NEIGH_VAR_PROXY_QLEN索引所对应的成员的值。
函数nla_get_msecs读取IP命令行设置的proxy_delay的值。对于arp协议,宏NEIGH_VAR_SET将修改全局变量arp_tbl的成员parms的data数组,具体为以NEIGH_VAR_PROXY_DELAY索引所对应的成员的值。
static int neightbl_set(struct sk_buff *skb, struct nlmsghdr *nlh, struct netlink_ext_ack *extack)
{
struct neigh_table *tbl;
struct nlattr *tb[NDTA_MAX+1];
if (tb[NDTA_PARMS]) {
struct neigh_parms *p;
p = lookup_neigh_parms(tbl, net, ifindex);
...
for (i = 1; i <= NDTPA_MAX; i++) {
if (tbp[i] == NULL) continue;
switch (i) {
...
case NDTPA_PROXY_QLEN:
NEIGH_VAR_SET(p, PROXY_QLEN,
nla_get_u32(tbp[i]));
break;
case NDTPA_PROXY_DELAY:
NEIGH_VAR_SET(p, PROXY_DELAY,
nla_get_msecs(tbp[i]));
break;
显示命令ip ntable show由内核中的函数neightbl_fill_parms处理,,负责填充内核中proxy_queue和proxy_delay的参数值,对于proxy_queue的值,由nla_put_u32函数由邻居表参数中取出并进行填充。对于proxy_delay的值,由nla_put_msecs函数由邻居表参数中取出并转换为毫秒值进行填充。
static int neightbl_fill_parms(struct sk_buff *skb, struct neigh_parms *parms)
{
...
if ((parms->dev &&
...
nla_put_u32(skb, NDTPA_PROXY_QLEN, NEIGH_VAR(parms, PROXY_QLEN)) ||
nla_put_msecs(skb, NDTPA_PROXY_DELAY,
NEIGH_VAR(parms, PROXY_DELAY), NDTPA_PAD) ||
proxy_qlen和proxy_delay处理
首先看一下arp代理相关的几个控制开关,默认值都是0,不开启。
$ cat /proc/sys/net/ipv4/conf/ens33/forwarding
0
$ cat /proc/sys/net/ipv4/conf/ens33/proxy_arp
0
$ cat /proc/sys/net/ipv4/conf/ens33/proxy_arp_pvlan
0
$ cat /proc/sys/net/ipv4/conf/ens33/medium_id
0
在arp报文处理函数arp_process中,如果arp报文的目标IP不是本机IP地址,即addr_type不等于RTN_LOCAL,但是,满足以下几个条件:
1) 接收arp报文的网络接口(in_dev)开启了转发功能(以上的forwarding文件值不为0);
2) 目标IP地址为单播(addr_type == RTN_UNICAST);
3) 满足以下任一一个条件:
3a) 接口开启了代理转发功能(以上proxy_arp文件值不为0);
3b) 接口开启了Private-vlan转发功能(以上的proxy_arp_pvlan文件中不为0);
3c) 目标IP地址对应的路由出口设备与接收arp报文的接口设备不同,并且,找到了代理邻居表项。
static int arp_process(struct net *net, struct sock *sk, struct sk_buff *skb)
{
...
if (arp->ar_op == htons(ARPOP_REQUEST) &&
ip_route_input_noref(skb, tip, sip, 0, dev) == 0) {
rt = skb_rtable(skb);
addr_type = rt->rt_type;
if (addr_type == RTN_LOCAL) {
...
} else if (IN_DEV_FORWARD(in_dev)) {
if (addr_type == RTN_UNICAST &&
(arp_fwd_proxy(in_dev, dev, rt) ||
arp_fwd_pvlan(in_dev, dev, rt, sip, tip) ||
(rt->dst.dev != dev &&
pneigh_lookup(&arp_tbl, net, &tip, dev, 0)))) {
n = neigh_event_ns(&arp_tbl, sha, &sip, dev);
if (n) neigh_release(n);
如果此arp报文已经添加到代理邻居队列中(标志LOCALLY_ENQUEUED),或者报文的物理地址就是本机(PACKET_HOST),再或者proxy_delay设置为零的情况,立即回复arp,使用接收设备的硬件地址。否则,执行代理的入队列操作,参见函数pneigh_enqueue。
if (NEIGH_CB(skb)->flags & LOCALLY_ENQUEUED ||
skb->pkt_type == PACKET_HOST ||
NEIGH_VAR(in_dev->arp_parms, PROXY_DELAY) == 0) {
arp_send_dst(ARPOP_REPLY, ETH_P_ARP,
sip, dev, tip, sha,
dev->dev_addr, sha,
reply_dst);
} else {
pneigh_enqueue(&arp_tbl, in_dev->arp_parms, skb);
goto out_free_dst;
}
goto out_consume_skb;
如下函数pneigh_enqueue,如果邻居处理队列长度超过proxy_qlen设定的值,释放当前报文,不再进行处理。否则,将报文添加到proxy_queue队列的尾部,并且,将队列处理定时器的下一次超时时长设置为不超过proxy_delay时长的一个随机值。
代理队列超时处理函数neigh_proxy_process可能会再次调用arp_process处理队列中的报文。
void pneigh_enqueue(struct neigh_table *tbl, struct neigh_parms *p,
struct sk_buff *skb)
{
unsigned long now = jiffies;
unsigned long sched_next = now + (prandom_u32() %
NEIGH_VAR(p, PROXY_DELAY));
if (tbl->proxy_queue.qlen > NEIGH_VAR(p, PROXY_QLEN)) {
kfree_skb(skb);
return;
}
NEIGH_CB(skb)->sched_next = sched_next;
NEIGH_CB(skb)->flags |= LOCALLY_ENQUEUED;
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;
}
skb_dst_drop(skb);
dev_hold(skb->dev);
__skb_queue_tail(&tbl->proxy_queue, skb);
mod_timer(&tbl->proxy_timer, sched_next);
spin_unlock(&tbl->proxy_queue.lock);
}
内核版本 5.0