【问题标题】:libnl getting error: Invalid Messagelibnl 出现错误:无效消息
【发布时间】:2016-06-08 07:15:10
【问题描述】:

我正在尝试使用 netlink 套接字和通用消息类型来实现内核用户通信。到目前为止,我能够将消息从用户空间发送到内核,然后将消息发送回用户空间。问题是在我的用户空间程序中,我总是收到一个错误,即收到了无效/格式错误的消息。在用户空间程序中,我使用 libnl 进行 netlink 通信。

相关的netlink内核代码如下:

enum nl_tdisk_attr {
    NL_UNSPEC,
    NL_MY_ATTR,    //My argument
    __NL_MAX
};
#define NL_MAX (__NL_MAX - 1)

enum nl_tdisk_msg_types {
    NL_CMD_READ = 0,
    NL_CMD_MY_CMD    //My command
    NL_CMD_MAX
};

//Family definition
static struct genl_family family = {
    .id = GENL_ID_GENERATE,
    .name = "my-family",
    .hdrsize = 0,
    .version = 0,
    .maxattr = NL_MAX,
};

//Command definition
static struct genl_ops ops[] = {
    {
        .cmd = NL_CMD_MY_CMD,
        .doit = genl_register,
    }
};

//...
//When the module is loaded:
genl_register_family_with_ops(&family, ops);


//Now some data should be sent to user space:
struct sk_buff *msg= nlmsg_new(NLMSG_GOODSIZE, GFP_KERNEL);
void *hdr = genlmsg_put(msg, port/*note1*/, 0, &family, 0/*note2*/, NL_CMD_MY_CMD);
nla_put_u32(msg, NL_MY_ATTR, some_value);
genlmsg_end(msg, hdr);
genlmsg_unicast(&init_net, msg, port/*note1*/); //note3

请注意,我删除了错误检查以减少代码量

一些注意事项:

  • 注意 1:用户空间程序的端口存储在内核模块内部 - 我 100% 确定它是正确的
  • note2:在标志中我也尝试设置 NLM_F_REQUEST 但没有成功
  • 注意3:函数genlmsg_unicast总是返回0,表示消息发送成功。所以我认为内核代码应该没问题。

这里是用户空间代码:

#include <netlink/netlink.h>
#include <netlink/socket.h>
#include <netlink/types.h>
#include <netlink/genl/genl.h>
#include <netlink/genl/ctrl.h>
#include <netlink/genl/mngt.h>

//...
struct nl_sock *socket = nl_socket_alloc();

//I explicitly set those callbacks to get some debug information
nl_socket_modify_cb(socket, NL_CB_MSG_IN, NL_CB_DEBUG, NULL, NULL);
nl_socket_modify_cb(socket, NL_CB_INVALID, NL_CB_DEBUG, NULL, NULL);

//I also tried to Play around with the buffer size:
nl_socket_set_buffer_size(socket, 65536, 65536);

genl_connect(socket);
familyId = genl_ctrl_resolve(socket, "my-family");    //This works and gives me the correct Family id

nl_recvmsgs_default(socket);

内核一发送消息,我就会在用户空间程序中看到调试信息,但遗憾的是它只是错误消息:

-- Debug: Received Message:
--------------------------   BEGIN NETLINK MESSAGE ---------------------------
  [NETLINK HEADER] 16 octets
.nlmsg_len = 308
    .type = 23 <0x17>
    .flags = 0
    .seq = 0
    .port = -1765782228
  [GENERIC NETLINK HEADER] 4 octets
    .cmd = 1
    .version = 1
    .unused = 0
  [PAYLOAD] 4 octets
    08 00 02 00                                     ....
---------------------------  END NETLINK MESSAGE   ---------------------------
-- Error: Invalid message: type=0x17 length=24 flags=0 sequence-nr=0 pid=2529185068

如您所见,在“END NETLINK MESSAGE”行之后是来自回调NL_CB_INVALID 的消息,它告诉我收到了无效消息。

所以实际上通信本身是正常的,因为它应该只是收到一个无效的消息,不知道为什么。有人知道我在哪里可以找到更多信息吗?为什么邮件格式不正确... 甚至更好:有人在我的代码中看到错误吗? 或者有谁知道描述这种场景的非常好的网站?

【问题讨论】:

    标签: c sockets linux-kernel netlink


    【解决方案1】:

    经过长时间的反复试验,我终于找到了某种的解决方案。问题实际上是修改“无效消息”回调:nl_socket_modify_cb(socket, NL_CB_INVALID, NL_CB_DEBUG, NULL, NULL);

    通过修改它,nl_recvmsgs_default(socket); 总是返回 0 表示没有错误。删除该回调后,我意识到nl_recvmsgs_default(socket); 返回了-16,根据文档,这意味着“消息序列号不匹配”。由于某种原因它不接受序列号 0,我不知道为什么......

    为了解决问题,我在用户空间程序中添加了nl_socket_disable_seq_check(socket);。我想这不是最佳解决方案,所以如果您知道更好的解决方案,请告诉我!

    【讨论】:

      【解决方案2】:

      (顺便说一句:如果您没有先阅读@ThomasSparber 自己的答案,这个答案没有意义,它确定了问题的根源和解决方法。)

      您可以在genlmsg_put 期间指定序列号。 libnl 期望响应 seqnum 与请求的相同。

      假设您在genl_register 期间致电genlmsg_put

      int genl_register(struct sk_buff *skb, struct genl_info *info)
      {
          ...
          genlmsg_put(msg, port, info->nlhdr->nlmsg_seq, &family, 0,
                  NL_CMD_MY_CMD);
          ...
      }
      

      应该这样做。禁用 seqnum 分析可能很糟糕,因为您可能会在多线程用户空间客户端等期间混合请求-响应。


      顺便说一句,这也可能很糟糕:

      struct sk_buff *msg= nlmsg_new(NLMSG_GOODSIZE, GFP_KERNEL);
      

      NLMSG_GOODSIZE 对于nlmsg_new 来说不是一个合适的大小;它的大小适合整个数据包。整个数据包是您发送给nlmsg_new 加上at least the netlink header size 的任何内容,并且您不希望它超过PAGE_SIZENLMSG_DEFAULT_SIZE 通常更适合nlmsg_new

      但是,由于您使用的是 Generic Netlink,因此您可能希望完全从头开始处理

      struct sk_buff *msg= genlmsg_new(GENLMSG_DEFAULT_SIZE, GFP_KERNEL);
      

      (很遗憾,GENLMSG_DEFAULT_SIZE 在较旧的内核中不可用。)

      【讨论】:

      • 非常好的答案 - 谢谢! nlmsg_new 听起来很不错——我会试试的!我知道我可以在“回答”请求时设置请求编号。我的问题是内核正在向用户空间发送请求,所以我没有info-&gt;nlhdr-&gt;nlmsg_seq 可用。
      • @Thomas "expected" sequence number 被初始化为 that is not likely zero。如果内核是发出请求的人,那么用户空间完成的序列号检查在您的代码中确实没有意义,至少对于第一个数据包而言。您自己的答案(禁用序列检查)是正确的。