邻居表的proxy_qlen长度和proxy_delay延时参数

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

上一篇:android开发布局文件imageview 图片等比例缩放:


下一篇:linux关于tcp协议ack以及乱序报文暂存的实现--立即ack/延迟ack/捎带ack