邻居表项的unres_qlen_bytes长度

对于每个未解析的邻居地址,变量unres_qlen和unres_qlen_bytes分别控制可换成的报文数量和报文字节数量,其中前者unres_qlen在内核linux-3.3版本已经废弃,应使用后一个变量unres_qlen_bytes,其默认值为SK_WMEM_MAX(即net.core.wmem_default),内核建议此值的设置应能够容纳256个中型长度的报文。

通过PROC文件unres_qlen和unres_qlen_bytes可查看和修改其值。

$ cat /proc/sys/net/ipv4/neigh/ens33/unres_qlen
101
$ cat /proc/sys/net/ipv4/neigh/ens33/unres_qlen_bytes 
212992

在arp邻居表arp_tbl中将NEIGH_VAR_QUEUE_LEN_BYTES索引所对应的表项(unres_qlen_bytes)初始化为SK_WMEM_MAX。

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_QUEUE_LEN_BYTES] = SK_WMEM_MAX,
			[NEIGH_VAR_PROXY_QLEN] = 64,

内核中静态变量neigh_sysctl_table定义了unres_qlen_bytes和unres_qlen的PROC文件信息。注意这里的参数QUEUE_LEN_BYTES,其实际上为NEIGH_VAR_QUEUE_LEN_BYTES,两者使用的是相同的,即指向同一个变量,只是在显示时unres_qlen需要进行转换。

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(QUEUE_LEN_BYTES, "unres_qlen_bytes"),
        ...
        NEIGH_SYSCTL_UNRES_QLEN_REUSED_ENTRY(QUEUE_LEN, QUEUE_LEN_BYTES, "unres_qlen"),

如下unres_qlen的转换函数proc_unres_qlen,对于读操作,需要将unres_qlen_bytes的值除以SKB_TRUESIZE(ETH_FRAME_LEN)的值,以得到unres_qlen的值。而对于写操作需进行相反的操作,将设置的unres_qlen乘以SKB_TRUESIZE(ETH_FRAME_LEN)的值。

unres_qlen_max限定了unres_qlen的最大值。

static int int_max = INT_MAX;
static int unres_qlen_max = INT_MAX / SKB_TRUESIZE(ETH_FRAME_LEN);

static int proc_unres_qlen(struct ctl_table *ctl, int write,
               void __user *buffer, size_t *lenp, loff_t *ppos)
{
    int size, ret;
    struct ctl_table tmp = *ctl;

    tmp.extra1 = &zero;
    tmp.extra2 = &unres_qlen_max;
    tmp.data = &size;

    size = *(int *)ctl->data / SKB_TRUESIZE(ETH_FRAME_LEN);
    ret = proc_dointvec_minmax(&tmp, write, buffer, lenp, ppos);

    if (write && !ret)
        *(int *)ctl->data = size * SKB_TRUESIZE(ETH_FRAME_LEN);
    return ret;

netlink接口

通过netlink设置unres_qlen和unres_qlen_bytes的值由以下函数neightbl_set处理,注意对于NDTPA_QUEUE_LEN,需要先行将其转换为字节数进行设置,两者设置的都是同一个变量,即NEIGH_VAR_QUEUE_LEN_BYTES为索引的数组成员。

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_QUEUE_LEN:
                NEIGH_VAR_SET(p, QUEUE_LEN_BYTES,
                          nla_get_u32(tbp[i]) * SKB_TRUESIZE(ETH_FRAME_LEN));
                break;
            case NDTPA_QUEUE_LENBYTES:
                NEIGH_VAR_SET(p, QUEUE_LEN_BYTES, nla_get_u32(tbp[i]));
                break;

以下函数neightbl_fill_parms读取内核中的unres_qlen和unres_qlen_bytes的值,其中前者为一个近似值。

static int neightbl_fill_parms(struct sk_buff *skb, struct neigh_parms *parms)
{
    struct nlattr *nest;

    nest = nla_nest_start(skb, NDTA_PARMS);
    if (nest == NULL)
        return -ENOBUFS;

    if ((parms->dev &&
        nla_put_u32(skb, NDTPA_QUEUE_LENBYTES,
            NEIGH_VAR(parms, QUEUE_LEN_BYTES)) ||
        /* approximative value for deprecated QUEUE_LEN (in packets) */
        nla_put_u32(skb, NDTPA_QUEUE_LEN,
            NEIGH_VAR(parms, QUEUE_LEN_BYTES) / SKB_TRUESIZE(ETH_FRAME_LEN)) ||

unres_qlen处理

变量unres_qlen的值在数据流程中用到,如下函数__neigh_event_send,如果邻居表项的状态位没有设置NUD_STALE和NUD_INCOMPLETE,并且探测次数不为零,立即开始地址探测(immediate_probe)。并且,将邻居表项的状态位图设置为NUD_INCOMPLETE。

int __neigh_event_send(struct neighbour *neigh, struct sk_buff *skb)
{
    int rc;
    bool immediate_probe = false;

    ...
    if (neigh->nud_state & (NUD_CONNECTED | NUD_DELAY | NUD_PROBE))
        goto out_unlock_bh;
    if (neigh->dead)
        goto out_dead;

    if (!(neigh->nud_state & (NUD_STALE | NUD_INCOMPLETE))) {
        if (NEIGH_VAR(neigh->parms, MCAST_PROBES) +
            NEIGH_VAR(neigh->parms, APP_PROBES)) {
            unsigned long next, now = jiffies;

            atomic_set(&neigh->probes, NEIGH_VAR(neigh->parms, UCAST_PROBES));
            neigh->nud_state     = NUD_INCOMPLETE;
            neigh->updated = now;
            next = now + max(NEIGH_VAR(neigh->parms, RETRANS_TIME), HZ/2);
            neigh_add_timer(neigh, next);
            immediate_probe = true;
        } else {
            neigh->nud_state = NUD_FAILED;
            ...
            kfree_skb(skb);
            return 1;
        }
    } else if (neigh->nud_state & NUD_STALE) {
        ...
    }

如果在发送报文时,邻居地址的状态等于NUD_INCOMPLETE,并且arp_queue队列中的报文总长度arp_queue_len_bytes与当前发送报文的长度(skb->truesize)之和,大于限定的QUEUE_LEN_BYTES值,将arp_queue队列中头部(最老的)报文释放,之后,再次检测总长度是否超限。

最终,将当前报文添加到arp_queue队列的尾部,同时增加缓存报文的总长度arp_queue_len_bytes。

    if (neigh->nud_state == NUD_INCOMPLETE) {
        if (skb) {
            while (neigh->arp_queue_len_bytes + skb->truesize >
                   NEIGH_VAR(neigh->parms, QUEUE_LEN_BYTES)) {
                struct sk_buff *buff;

                buff = __skb_dequeue(&neigh->arp_queue);
                if (!buff)
                    break;
                neigh->arp_queue_len_bytes -= buff->truesize;
                kfree_skb(buff);
                NEIGH_CACHE_STAT_INC(neigh->tbl, unres_discards);
            }
            skb_dst_force(skb);
            __skb_queue_tail(&neigh->arp_queue, skb);
            neigh->arp_queue_len_bytes += skb->truesize;
        }
        rc = 1;
    }
out_unlock_bh:
    if (immediate_probe)
        neigh_probe(neigh);

内核版本 5.0

已标记关键词 清除标记
©️2020 CSDN 皮肤主题: 编程工作室 设计师:CSDN官方博客 返回首页