如何使用 libnl 和通用 netlink 发送多部分消息?

How to send multipart messages using libnl and generic netlink?

我正在尝试通过 libnl 和通用网络链接发送一个相对较大的字符串 (6Kb),但是,我从函数 nla_put_string 中收到错误 -5 (NL_ENOMEM)这个流程。我做了很多研究,但我没有找到关于这两个问题的任何信息:

如果有学习此类科目的地方,我将不胜感激。

How to use the multipart mechanism of generic netlink to broke this string in smaller parts to send and reassemble it on the Kernel side?

Netlink 的多部分功能“可能”帮助您传输已经分段的字符串,但它不会帮助您进行实际的字符串分段操作。那是你的工作。 Multipart 是一种通过多个数据包传输多个小的相关对象而不是一个大对象的方法。一般来说,Netlink 作为一个整体的设计假设您要发送的任何原子数据都可以放在一个数据包中。我同意这样的观点,即 6Kbs 的字符串有点奇怪。

实际上,在我看来,Multipart 是一个定义不清的噱头。问题是内核实际上并没有以任何通用能力处理它;如果您查看所有 NLMSG_DONE usage instances, you will notice not only that it is very rarely read (most of them are writes), but also, it's not the Netlink code but rather some specific protocol 为某些 static(即私有)操作所做的事情。也就是说,NLMSG_DONE的语义是你给的,不是内核给的。 Linux 如果您选择使用它,将不会为您节省任何工作。

另一方面,libnl-genl-3 似乎确实会使用 Multipart 标志(NLMSG_DONENLM_F_MULTI)执行一些自动操作,但这仅适用于发送内容时从内核空间到用户空间,最重要的是,甚至库本身也承认 it doesn't really work.

此外,NLMSG_DONE 应该是 placed in the "type" Netlink header field, not in the "flags" field. This is baffling to me, because Generic Netlink stores the family identifier in type,所以看起来没有办法同时告诉 Netlink 该消息属于您,并且它应该结束一些数据流。除非我遗漏了一些重要的东西,否则 Multipart 和 Generic Netlink 彼此不兼容。

因此,我建议在必要时实现您自己的消息控件,而忘记 Multipart。

What's the maximum string size supported by generic netlink and libnl nla_put_string function?

这不是一个常数。 nlmsg_alloc() 储备 getpagesize() bytes per packet by default. You can tweak this default with nlmsg_set_default_size(), or more to the point you can override it with nlmsg_alloc_size().

那你就得查询 actual allocated size (because it's not guaranteed to be what you requested) and build from there. To get the available payload you'd have to subtract the Netlink header length, the Generic Header length and the Attribute Header lengths for any attributes you want to add. Also the user header length, if you have one. You would also have to align all these components because their sizeof is not necessarily their actual size (example).

综上所述,内核仍然会拒绝超过页面大小的数据包,因此即使您指定了自定义大小,您仍然需要对字符串进行分段。

真的,忘了以上所有吧。只需将字符串分段为 getpagesize() / 2 之类的内容,然后将其分块发送。

这是总体思路:

static void do_request(struct nl_sock *sk, int fam, char const *string)
{
    struct nl_msg *msg;

    msg = nlmsg_alloc();
    genlmsg_put(msg, NL_AUTO_PORT, NL_AUTO_SEQ, fam,
            0, 0, DOC_EXMPL_C_ECHO, 1);
    nla_put_string(msg, DOC_EXMPL_A_MSG, string);
    nl_send_auto(sk, msg);
    nlmsg_free(msg);
}

int main(int argc, char **argv)
{
    struct nl_sock *sk;
    int fam;

    sk = nl_socket_alloc();
    genl_connect(sk);
    fam = genl_ctrl_resolve(sk, FAMILY_NAME);

    do_request(sk, fam, "I'm sending a string.");
    do_request(sk, fam, "Let's pretend I'm biiiiiig.");
    do_request(sk, fam, "Look at me, I'm so big.");
    do_request(sk, fam, "But I'm already fragmented, so it's ok.");
    
    nl_close(sk);
    nl_socket_free(sk);
    
    return 0;
}

我在 my Dropbox 中留下了一个完整的沙箱。请参阅自述文件。 (在内核 5.4.0-37-generic 中测试。)