【问题标题】:How to send multipart messages using libnl and generic netlink?如何使用 libnl 和通用 netlink 发送多部分消息?
【发布时间】:2020-06-23 03:02:35
【问题描述】:

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

  • 通用 netlink 和 libnl nla_put_string 函数支持的最大字符串大小是多少?
  • 如何使用通用 netlink 的多部分机制将这个字符串分解成更小的部分,以便在内核端发送和重组它?

如果有地方可以学习这样的主题,我很感激。

【问题讨论】:

  • 默认的 Netlink 数据包分配函数会保留大小为 PAGE_SIZE 的数据包。然后,您必须从中减去 Netlink 和 Generic Netlink 标头。要了解更多细节,您必须说明您的字符串在哪里以及您要发送到哪里。用户空间到内核空间?内核空间到用户空间?内核空间到内核空间?如果涉及用户空间,您是否使用 libnl-genl-3?
  • 我的字符串在用户空间,我正在尝试发送到内核。是的,我正在使用 libnl-genl-3,我试图找到一些使用这种机制的示例,但我没有找到任何示例,您是否知道或有一些示例,用户空间应用程序使用这些示例将多部分消息发送到内核通用网络链接?
  • 据我所知,Netlink 将碎片化推迟到应用程序。 (这是 IMO 的好设计;人类不希望再次出现 IPv4 碎片崩溃。)但是它有什么问题呢?只需将字符串切片并以不同的数据包发送即可。
  • 平。答案已更新。

标签: linux-kernel kernel netlink


【解决方案1】:

如何使用通用netlink的多部分机制将这个字符串分解成更小的部分,以便在内核端发送和重组?

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

实际上,在我看来,Multipart 是一个定义不明确的噱头。问题是内核实际上并没有以任何通用能力处理它。如果您查看所有NLMSG_DONE usage instances,您会注意到它不仅很少被读取(其中大多数是写入),而且,它不是Netlink 代码,而是一些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,而不是在“标志”字段中。这让我感到莫名其妙,因为通用 Netlink stores the family identifier in type,所以看起来没有办法同时告诉 Netlink 该消息属于您,并且它应该结束一些数据流。除非我遗漏了一些重要的东西,否则 Multipart 和 Generic Netlink 彼此不兼容。

因此,我建议在必要时实施您自己的消息控制,并忘记 Multipart。

通用 netlink 和 libnl nla_put_string 函数支持的最大字符串大小是多少?

这不是一个常数。 nlmsg_alloc()储备 getpagesize() 每个数据包的字节数 by default。您可以使用nlmsg_set_default_size() 调整此默认值,或者更多地使用nlmsg_alloc_size() 覆盖它。

然后您必须查询actual allocated size(因为不能保证它是您要求的)并从那里构建。要获得可用的有效负载,您必须减去要添加的任何属性的 Netlink header 长度、Generic Header 长度和 Attribute Header 长度。还有user header length,如果你有的话。您还必须对齐所有这些组件,因为它们的 sizeof 不一定是它们的实际大小 (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 中测试。)

【讨论】:

    猜你喜欢
    • 2015-08-17
    • 2023-04-08
    • 2012-02-04
    • 1970-01-01
    • 1970-01-01
    • 2011-07-25
    • 2012-03-25
    • 2014-04-30
    • 1970-01-01
    相关资源
    最近更新 更多