ILD

netlink dump
作者:Yuan Jianpeng 邮箱:yuanjianpeng@xiaomi.com
发布时间:2022-10-17 站点:Inside Linux Development

最近在实现netlink的dump接口的时候,发现它调用了2次,用户程序使用libnl,但是用户程序的cb只执行了一次,内核调用栈如下:


第一次:

[240030.960400] CPU: 2 PID: 6021 Comm: ipaccount Not tainted 4.4.198 #0

[240030.967242] Stack : 00000000 00000000 81c969ea 00000037 00000000 00000000 81880000 81cb0000

          8ff0033c 81879263 8175d440 00000002 00001785 81c93690 00000014 00000008

          00000000 8106b1ac 00000000 815be0dc 81870000 8bdbfb6c 817622dc 8bdbfb3c

          00000000 81069128 00000001 00000000 00000001 81768414 020112c0 00dbfb3c

          00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000

          ...

[240031.002982] Call Trace:

[240031.005532] [<810177b4>] show_stack+0x70/0x8c

[240031.009999] [<811da0c8>] dump_stack+0x94/0xc8

[240031.014451] [<80520310>] ip_acc_genl_dump_stat+0x28/0x128 [ip_account]

[240031.021080] [<815c1d1c>] genl_lock_dumpit+0x34/0x58

[240031.026049] [<815bf2cc>] netlink_dump+0xe0/0x2a4

[240031.030754] [<815bf8d8>] __netlink_dump_start+0x100/0x1c0

[240031.036244] [<815c2714>] genl_rcv_msg+0x164/0x338

[240031.041035] [<815c19b4>] netlink_rcv_skb+0x98/0xfc

[240031.045918] [<815c2598>] genl_rcv+0x30/0x48

[240031.050188] [<815c1214>] netlink_unicast+0x17c/0x248

[240031.055240] [<815c1788>] netlink_sendmsg+0x3a4/0x3bc

[240031.060297] [<815769b8>] sock_sendmsg+0x18/0x30

[240031.064917] [<81577f44>] ___sys_sendmsg+0x1bc/0x21c

[240031.069888] [<81578e5c>] __sys_sendmsg+0x48/0x7c

[240031.074603] [<810078d8>] syscall_common+0x30/0x54



第二次:

[240031.083011] CPU: 2 PID: 6021 Comm: ipaccount Not tainted 4.4.198 #0

[240031.089382] Stack : 00000000 00000000 81c969ea 00000037 00000000 00000000 81880000 81cb0000

          8ff0033c 81879263 8175d440 00000002 00001785 81c93690 8bdbfd8c 8fb7a9a0

          00000000 8106b1ac 80ea5c00 8fb7a9a0 00000000 81005420 817622dc 8bdbfc74

          00000000 81069128 00000001 00000000 00000001 81768414 020112c0 00dbfc74

          00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000

          ...

[240031.125132] Call Trace:

[240031.127682] [<810177b4>] show_stack+0x70/0x8c

[240031.132149] [<811da0c8>] dump_stack+0x94/0xc8

[240031.136605] [<80520310>] ip_acc_genl_dump_stat+0x28/0x128 [ip_account]

[240031.143231] [<815c1d1c>] genl_lock_dumpit+0x34/0x58

[240031.148199] [<815bf2cc>] netlink_dump+0xe0/0x2a4

[240031.152904] [<815bf65c>] netlink_recvmsg+0x1cc/0x348

[240031.157965] [<81578060>] ___sys_recvmsg+0xbc/0x16c

[240031.162847] [<81579034>] __sys_recvmsg+0x48/0x7c

[240031.167564] [<810078d8>] syscall_common+0x30/0x54

[240031.172351]


查看内核源代码,是用户程序发送dump请求的时候调用了一次dump,在用户程序接收数据的时候又dump了一次。


查看内核代码:net/netlink/af_netlink.c,在recv的时候,会调用dump:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
static int netlink_recvmsg(struct socket *sock, struct msghdr *msg, size_t len,
                           int flags)
{
        if (nlk->cb_running &&
            atomic_read(&sk->sk_rmem_alloc) <= sk->sk_rcvbuf / 2) {
                ret = netlink_dump(sk);
                if (ret) {
                        sk->sk_err = -ret;
                        sk_error_report(sk);
                }
        }
}
  
static int netlink_dump(struct sock *sk)
{
 
        if (nlk->dump_done_errno > 0) {
                cb->extack = &extack;
                nlk->dump_done_errno = cb->dump(skb, cb);
                cb->extack = NULL;
        }
        if (nlk->dump_done_errno > 0 ||
            skb_tailroom(skb) < nlmsg_total_size(sizeof(nlk->dump_done_errno))) {
                mutex_unlock(nlk->cb_mutex);
                if (sk_filter(sk, skb))
                        kfree_skb(skb);
                else
                        __netlink_sendskb(sk, skb);
                return 0;
        }
        if (netlink_dump_done(nlk, skb, cb, &extack))
                goto errout_skb;
                 
}


之前没搞明白,在dump的返回里面返回skb->len,以为dump要返回数据长度。其实是搞错意思了。dump的返回值为正数,表明数据太长,单次dump还没完毕,在用recv的时候,继续进行dump,这样不停的recv,不停的dump,直到dump返回0,表明数据完成了。


测试发现,一个skb的长度,大概是16K,而我的dump函数,有256个条目,大概只能dump 240个条目,需要进行2次dump。


前一次的状态可以保存在cb的args中:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
struct netlink_callback {
        struct sk_buff          *skb;
        const struct nlmsghdr   *nlh;
        int                     (*dump)(struct sk_buff * skb,
                                        struct netlink_callback *cb);
        int                     (*done)(struct netlink_callback *cb);
        void                    *data;
        /* the module that dump function belong to */
        struct module           *module;
        struct netlink_ext_ack  *extack;
        u16                     family;
        u16                     answer_flags;
        u32                     min_dump_alloc;
        unsigned int            prev_seq, seq;
        bool                    strict_check;
        union {
                u8              ctx[48];
                /* args is deprecated. Cast a struct over ctx instead
                 * for proper type safety.
                 */
                long            args[6];
        };
};


在第一次调用的时候,内核将args清0。将上一次的位置保存到cb->args[0],下次继续从cb->args[0]开始dump:


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
static int ip_acc_genl_dump_stat(struct sk_buff *skb, struct netlink_callback *cb)
{
        struct ip_acc_table *table = &ip_acc_table_default;
        void *hdr;
        int i;
         
        hdr = genlmsg_put(skb, NETLINK_CB(cb->skb).portid, cb->nlh->nlmsg_seq,
                        &ip_acc_genl_family, NLM_F_MULTI, IPACC_CMD_GET_STAT);
                         
        spin_lock_bh(&stat_lock);
         
        if (cb->args[0] == 0) {
                nla_put_u32(skb, IPACC_ATTR_NET, table->net);
                nla_put_u32(skb, IPACC_ATTR_CURSOR, table->cursor);
        }
         
        for (i = cb->args[0]; i < MAX_IP_NUM; i++) {
                if (nla_put(skb, IPACC_ATTR_STAT_ENTRY, sizeof(struct ip_acc), &table->ip_acc[i]) < 0)
                        break;
        }
        cb->args[0] = i;
         
        spin_unlock_bh(&stat_lock);
         
        genlmsg_end(skb, hdr);
        return (i < MAX_IP_NUM) ? i : 0;
}


之前的代码有3个问题,1是没有保存dump状态,每次都从0开始dump,2是直接返回skb->len,3是没有使用NLM_F_MULTI标志。


查看libnl的代码 lib/nl.c,只有当有NLM_F_MULTI标志的时候,才进行多次recv(只展示相关代码)。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
static int recvmsgs(struct nl_sock *sk, struct nl_cb *cb)
{
        int n, err = 0, multipart = 0, interrupted = 0, nrecv = 0;
 
continue_reading:
        NL_DBG(3, "Attempting to read from %p\n", sk);
        if (cb->cb_recv_ow)
                n = cb->cb_recv_ow(sk, &nla, &buf, &creds);
        else
                n = nl_recv(sk, &nla, &buf, &creds);
 
        while (nlmsg_ok(hdr, n)) {
         
                if (hdr->nlmsg_flags & NLM_F_MULTI)
                        multipart = 1;
        }
         
        if (multipart) {
                /* Multipart message not yet complete, continue reading */
                goto continue_reading;
        }
}


修改完毕后,用户程序可以完整读取到256个数据。


另外,dump返回负数或0,将结束dump,dump的这个返回值,将保存在DONE message的payload里,所以返回的负数,可以

传递可用户程序,内核代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
static int netlink_dump_done(struct netlink_sock *nlk, struct sk_buff *skb,
                             struct netlink_callback *cb,
                             struct netlink_ext_ack *extack)
{
        struct nlmsghdr *nlh;
        nlh = nlmsg_put_answer(skb, cb, NLMSG_DONE, sizeof(nlk->dump_done_errno),
                               NLM_F_MULTI | cb->answer_flags);
        if (WARN_ON(!nlh))
                return -ENOBUFS;
        nl_dump_check_consistent(cb, nlh);
        memcpy(nlmsg_data(nlh), &nlk->dump_done_errno, sizeof(nlk->dump_done_errno));
        if (extack->_msg && nlk->flags & NETLINK_F_EXT_ACK) {
                nlh->nlmsg_flags |= NLM_F_ACK_TLVS;
                if (!nla_put_string(skb, NLMSGERR_ATTR_MSG, extack->_msg))
                        nlmsg_end(skb, nlh);
        }
        return 0;
}

每个dump消息,总是以NLMSG_DONE结尾,这是内核封装的。


Copyright © linuxdev.cc 2017-2024. Some Rights Reserved.