如何通过 netlink 发送和接收结构?
How to send and receive a struct through netlink?
我正在尝试使用 netlink 从 user-space 向我在内核 space 中的模块发送一个结构,我在 user-space 中的结构是:
struct test{
unsigned int length;
char name[MAX_NAME_LENGTH];
};
在内核中 space 是:
struct test{
__u32 length;
char name[MAX_NAME_LENGTH];
};
其中 MAX_NAME_LENGTH
是定义为等于 50 的宏。
在 user-space 中,我有函数 main,它使用以下代码将我的结构发送到内核:
int main(){
struct iovec iov[2];
int sock_fd;
struct sockaddr_nl src_add;
struct sockaddr_nl dest_add;
struct nlmsghdr * nl_hdr = NULL;
struct msghdr msg;
struct test message;
memset(&message, 0, sizeof(struct test));
message.length = 18;
strcpy(message.name, "Just a test[=12=]");
sock_fd = socket(PF_NETLINK, SOCK_RAW, NETLINK_USER);
if (sock_fd < 0){
printf("Netlink socket creation failed\n");
return -1;
}
memset(&src_add, 0, sizeof(src_add));
src_add.nl_family = AF_NETLINK;
src_add.nl_pid = getpid();
memset(&dest_add, 0, sizeof(dest_add));
dest_add.nl_family = AF_NETLINK;
dest_add.nl_pid = 0; // Send to linux kernel
dest_add.nl_groups = 0; // Unicast
bind(sock_fd,(struct sockaddr *)&src_add,sizeof(src_add));
nl_hdr = (struct nlmsghdr *) malloc(NLMSG_SPACE(sizeof(struct test)));
memset(nl_hdr, 0, NLMSG_SPACE(sizeof (struct test)));
nl_hdr->nlmsg_len = NLMSG_SPACE(sizeof(struct test));
nl_hdr->nlmsg_pid = getpid();
nl_hdr->nlmsg_flags = 0;
iov[0].iov_base = (void *)nl_hdr;
iov[0].iov_len = nl_hdr->nlmsg_len;
iov[1].iov_base = &message;
iov[1].iov_len = sizeof(struct test);
memset(&msg,0, sizeof(msg));
msg.msg_name = (void *)&dest_add;
msg.msg_namelen = sizeof(dest_add);
msg.msg_iov = &iov[0];
msg.msg_iovlen = 2;
sendmsg(sock_fd,&msg,0);
close(sock_fd);
return 0;
}
并且在内核端我已经注册了一个叫做回调的函数,每次收到消息时都会被调用,这就是回调函数:
static void callback(struct sk_buff *skb){
struct nlmsghdr *nl_hdr;
struct test * msg_rcv;
nl_hdr = (struct nlmsghdr*)skb->data;
msg_rcv = (struct test*) nlmsg_data(nl_hdr);
printk(KERN_INFO "Priting the length and name in the struct:%u, %s\n",msg_rcv->length, msg_rcv->name);
}
当我 运行 这些代码并看到 dmesg 输出时,我收到以下消息:Priting the length and name in the struct:0,
,那么为什么在 user-space 侧填充的结构的字段不是正在发送到内核?
顺便说一句,NETLINK_USER
定义为 31。
不要那样做。您的代码在设计上存在错误。
我将首先解释阻止您的代码执行您想要的操作的一个多余问题,然后解释为什么您想要的是一个坏主意,然后解释正确的解决方案。
1。随心所欲
您“想要”发送一个由网络链接 header 和后跟结构组成的数据包。换句话说,这个:
+-----------------+-------------+
| struct nlmsghdr | struct test |
| (16 bytes) | (54 bytes) |
+-----------------+-------------+
问题是这不是您告诉 iovec 的内容。根据您的 iovec 代码,数据包如下所示:
+-----------------+--------------+-------------+
| struct nlmsghdr | struct test | struct test |
| (16 bytes) | (54 bytes) | (54 bytes) |
| (data) | (all zeroes) | (data) |
+-----------------+--------------+-------------+
这一行:
iov[0].iov_len = nl_hdr->nlmsg_len;
应该是这样的:
iov[0].iov_len = NLMSG_HDRLEN;
因为你的第一个 iovec 插槽就是 Netlink header;不是整包。
2。为什么你想要的东西不好
C 有一个陷阱,叫做“数据结构填充”。不要跳过这个讲座;我认为任何使用 C 语言的人都必须尽快阅读它:http://www.catb.org/esr/structure-packing/
其要点是允许 C 编译器在任何结构的成员之间引入垃圾。因此,当您声明时:
struct test {
unsigned int length;
char name[MAX_NAME_LENGTH];
};
技术上允许编译器在实现过程中将其突变为类似
的东西
struct test {
unsigned int length;
unsigned char garbage[4];
char name[MAX_NAME_LENGTH];
};
看到问题了吗?如果您的内核模块和您的用户空间客户端是由不同的编译器生成的,或者由相同的编译器生成但标志略有不同,或者甚至由相同编译器的略有不同的版本,结构可能不同并且无论您的代码看起来多么正确,内核都会收到垃圾。
更新:有人让我详细说明一下,所以这里是:
假设您有以下结构:
struct example {
__u8 value8;
__u16 value16;
};
在用户空间中,编译器决定保持原样。但是,在内核空间中,编译器“随机”决定将其转换为:
struct example {
__u8 value8;
__u8 garbage;
__u16 value16;
};
然后在您的用户空间客户端中编写以下代码:
struct example x;
x.value8 = 0x01;
x.value16 = 0x0203;
在内存中,结构将如下所示:
01 <- value8
02 <- First byte of value16
03 <- Second byte of value16
当你把它发送给内核时,内核当然会收到同样的东西:
01
02
03
但它会以不同的方式解释它:
01 <- value8
02 <- garbage
03 <- First byte of value16
junk <- Second byte of value16
(更新结束)
在您的情况下,由于您在用户空间中将 test.length
定义为 unsigned int
,但由于某种原因您在内核空间中将其更改为 __u32
,这一事实使问题更加严重。您的代码甚至在结构填充之前就有问题;如果您的用户空间将基本整数定义为 64 位,则该错误也将不可避免地触发。
还有另一个问题:“顺便说一句,NETLINK_USER
被定义为 31”告诉我您正在关注早已过时或由不知道自己在做什么的人编写的教程或代码示例。你知道 31 是从哪里来的吗?它是您的“Netlink 家族”的标识符。他们将其定义为 31,因为这是 它可以具有的最高可能值 (0-31),因此,它最不可能与内核定义的其他 Netlink 系列发生冲突。 (因为它们是 numbered monotonically。)但是大多数粗心的 Netlink 用户都在按照教程进行操作,因此他们的大多数 Netlink 系列都标识为 31。因此,您的内核模块将无法与它们共存。 netlink_kernel_create()
会把你踢出去,因为 31 号已经被占用了。
您可能会想,“好吧,该死。只有 32 个可用插槽,23 of them are already taken by the kernel 并且有未知但可能有大量其他人想要注册不同的 Netlink 系列。我该怎么办? !"
3。正确的方法
现在是 2020 年。我们不再使用 Netlink。我们使用 better-Netlink:通用网络链接。
Generic Netlink 使用字符串和动态整数作为家族标识符,并促使您默认使用 Netlink 的“属性”框架。 (后者鼓励你以可移植的方式序列化和反序列化结构,这才是真正解决你原来问题的方法。)
此代码需要对您的用户空间客户端和内核模块可见:
#define SAMPLE_FAMILY "Sample Family"
enum sample_operations {
SO_TEST, /* from your "struct test" */
/* List more here for different request types. */
};
enum sample_attribute_ids {
/* Numbering must start from 1 */
SAI_LENGTH = 1, /* From your test.length */
SAI_NAME, /* From your test.name */
/* This is a special one; don't list any more after this. */
SAI_COUNT,
#define SAI_MAX (SAI_COUNT - 1)
};
这是内核模块:
#include <linux/module.h>
#include <linux/version.h>
#include <net/genetlink.h>
#include "../include/protocol.h"
/*
* A "policy" is a bunch of rules. The kernel will validate the request's fields
* match these data types (and other defined constraints) for us.
*/
struct nla_policy const sample_policy[SAI_COUNT] = {
[SAI_LENGTH] = { .type = NLA_U32 },
[SAI_NAME] = { .type = NLA_STRING },
};
/*
* This is the function the kernel calls whenever the client sends SO_TEST
* requests.
*/
static int handle_test_operation(struct sk_buff *skb, struct genl_info *info)
{
if (!info->attrs[SAI_LENGTH]) {
pr_err("Invalid request: Missing length attribute.\n");
return -EINVAL;
}
if (!info->attrs[SAI_NAME]) {
pr_err("Invalid request: Missing name attribute.\n");
return -EINVAL;
}
pr_info("Printing the length and name: %u, '%s'\n",
nla_get_u32(info->attrs[SAI_LENGTH]),
(unsigned char *)nla_data(info->attrs[SAI_NAME]));
return 0;
}
static const struct genl_ops ops[] = {
/*
* This is what tells the kernel to use the function above whenever
* userspace sends SO_TEST requests.
* Add more array entries if you define more sample_operations.
*/
{
.cmd = SO_TEST,
.doit = handle_test_operation,
#if LINUX_VERSION_CODE < KERNEL_VERSION(5, 2, 0)
/* Before kernel 5.2, each op had its own policy. */
.policy = sample_policy,
#endif
},
};
/* Descriptor of our Generic Netlink family */
static struct genl_family sample_family = {
.name = SAMPLE_FAMILY,
.version = 1,
.maxattr = SAI_MAX,
#if LINUX_VERSION_CODE >= KERNEL_VERSION(5, 2, 0)
/* Since kernel 5.2, the policy is family-wide. */
.policy = sample_policy,
#endif
.module = THIS_MODULE,
.ops = ops,
.n_ops = ARRAY_SIZE(ops),
};
/* Called by the kernel when the kernel module is inserted */
static int test_init(void)
{
return genl_register_family(&sample_family);
}
/* Called by the kernel when the kernel module is removed */
static void test_exit(void)
{
genl_unregister_family(&sample_family);
}
module_init(test_init);
module_exit(test_exit);
这是用户空间客户端(您需要在 Debian/Ubuntu 上安装 libnl-genl-3 --sudo apt install libnl-genl-3-dev
):
#include <errno.h>
#include <netlink/genl/ctrl.h>
#include <netlink/genl/genl.h>
#include "../include/protocol.h"
static struct nl_sock *sk;
static int genl_family;
static void prepare_socket(void)
{
sk = nl_socket_alloc();
genl_connect(sk);
genl_family = genl_ctrl_resolve(sk, SAMPLE_FAMILY);
}
static struct nl_msg *prepare_message(void)
{
struct nl_msg *msg;
msg = nlmsg_alloc();
genlmsg_put(msg, NL_AUTO_PORT, NL_AUTO_SEQ, genl_family, 0, 0, SO_TEST, 1);
/*
* The nla_put* functions ensure that your data will be stored in a
* portable way.
*/
nla_put_u32(msg, SAI_LENGTH, 18);
nla_put_string(msg, SAI_NAME, "Just a test");
return msg;
}
int main(int argc, char **argv)
{
struct nl_msg *msg;
prepare_socket();
msg = prepare_message();
nl_send_auto(sk, msg); /* Send message */
nlmsg_free(msg);
nl_socket_free(sk);
return 0;
}
此代码应从内核 4.10 开始运行。 (我在 4.15 测试过它。)内核 API 之前有些不同。
我在我的 Dropbox 中留下了我的测试环境的袖珍版(带有 makefile 和正确的错误处理等等),因此您可以 运行 轻松地使用它。
我正在尝试使用 netlink 从 user-space 向我在内核 space 中的模块发送一个结构,我在 user-space 中的结构是:
struct test{
unsigned int length;
char name[MAX_NAME_LENGTH];
};
在内核中 space 是:
struct test{
__u32 length;
char name[MAX_NAME_LENGTH];
};
其中 MAX_NAME_LENGTH
是定义为等于 50 的宏。
在 user-space 中,我有函数 main,它使用以下代码将我的结构发送到内核:
int main(){
struct iovec iov[2];
int sock_fd;
struct sockaddr_nl src_add;
struct sockaddr_nl dest_add;
struct nlmsghdr * nl_hdr = NULL;
struct msghdr msg;
struct test message;
memset(&message, 0, sizeof(struct test));
message.length = 18;
strcpy(message.name, "Just a test[=12=]");
sock_fd = socket(PF_NETLINK, SOCK_RAW, NETLINK_USER);
if (sock_fd < 0){
printf("Netlink socket creation failed\n");
return -1;
}
memset(&src_add, 0, sizeof(src_add));
src_add.nl_family = AF_NETLINK;
src_add.nl_pid = getpid();
memset(&dest_add, 0, sizeof(dest_add));
dest_add.nl_family = AF_NETLINK;
dest_add.nl_pid = 0; // Send to linux kernel
dest_add.nl_groups = 0; // Unicast
bind(sock_fd,(struct sockaddr *)&src_add,sizeof(src_add));
nl_hdr = (struct nlmsghdr *) malloc(NLMSG_SPACE(sizeof(struct test)));
memset(nl_hdr, 0, NLMSG_SPACE(sizeof (struct test)));
nl_hdr->nlmsg_len = NLMSG_SPACE(sizeof(struct test));
nl_hdr->nlmsg_pid = getpid();
nl_hdr->nlmsg_flags = 0;
iov[0].iov_base = (void *)nl_hdr;
iov[0].iov_len = nl_hdr->nlmsg_len;
iov[1].iov_base = &message;
iov[1].iov_len = sizeof(struct test);
memset(&msg,0, sizeof(msg));
msg.msg_name = (void *)&dest_add;
msg.msg_namelen = sizeof(dest_add);
msg.msg_iov = &iov[0];
msg.msg_iovlen = 2;
sendmsg(sock_fd,&msg,0);
close(sock_fd);
return 0;
}
并且在内核端我已经注册了一个叫做回调的函数,每次收到消息时都会被调用,这就是回调函数:
static void callback(struct sk_buff *skb){
struct nlmsghdr *nl_hdr;
struct test * msg_rcv;
nl_hdr = (struct nlmsghdr*)skb->data;
msg_rcv = (struct test*) nlmsg_data(nl_hdr);
printk(KERN_INFO "Priting the length and name in the struct:%u, %s\n",msg_rcv->length, msg_rcv->name);
}
当我 运行 这些代码并看到 dmesg 输出时,我收到以下消息:Priting the length and name in the struct:0,
,那么为什么在 user-space 侧填充的结构的字段不是正在发送到内核?
顺便说一句,NETLINK_USER
定义为 31。
不要那样做。您的代码在设计上存在错误。
我将首先解释阻止您的代码执行您想要的操作的一个多余问题,然后解释为什么您想要的是一个坏主意,然后解释正确的解决方案。
1。随心所欲
您“想要”发送一个由网络链接 header 和后跟结构组成的数据包。换句话说,这个:
+-----------------+-------------+
| struct nlmsghdr | struct test |
| (16 bytes) | (54 bytes) |
+-----------------+-------------+
问题是这不是您告诉 iovec 的内容。根据您的 iovec 代码,数据包如下所示:
+-----------------+--------------+-------------+
| struct nlmsghdr | struct test | struct test |
| (16 bytes) | (54 bytes) | (54 bytes) |
| (data) | (all zeroes) | (data) |
+-----------------+--------------+-------------+
这一行:
iov[0].iov_len = nl_hdr->nlmsg_len;
应该是这样的:
iov[0].iov_len = NLMSG_HDRLEN;
因为你的第一个 iovec 插槽就是 Netlink header;不是整包。
2。为什么你想要的东西不好
C 有一个陷阱,叫做“数据结构填充”。不要跳过这个讲座;我认为任何使用 C 语言的人都必须尽快阅读它:http://www.catb.org/esr/structure-packing/
其要点是允许 C 编译器在任何结构的成员之间引入垃圾。因此,当您声明时:
struct test {
unsigned int length;
char name[MAX_NAME_LENGTH];
};
技术上允许编译器在实现过程中将其突变为类似
的东西struct test {
unsigned int length;
unsigned char garbage[4];
char name[MAX_NAME_LENGTH];
};
看到问题了吗?如果您的内核模块和您的用户空间客户端是由不同的编译器生成的,或者由相同的编译器生成但标志略有不同,或者甚至由相同编译器的略有不同的版本,结构可能不同并且无论您的代码看起来多么正确,内核都会收到垃圾。
更新:有人让我详细说明一下,所以这里是:
假设您有以下结构:
struct example {
__u8 value8;
__u16 value16;
};
在用户空间中,编译器决定保持原样。但是,在内核空间中,编译器“随机”决定将其转换为:
struct example {
__u8 value8;
__u8 garbage;
__u16 value16;
};
然后在您的用户空间客户端中编写以下代码:
struct example x;
x.value8 = 0x01;
x.value16 = 0x0203;
在内存中,结构将如下所示:
01 <- value8
02 <- First byte of value16
03 <- Second byte of value16
当你把它发送给内核时,内核当然会收到同样的东西:
01
02
03
但它会以不同的方式解释它:
01 <- value8
02 <- garbage
03 <- First byte of value16
junk <- Second byte of value16
(更新结束)
在您的情况下,由于您在用户空间中将 test.length
定义为 unsigned int
,但由于某种原因您在内核空间中将其更改为 __u32
,这一事实使问题更加严重。您的代码甚至在结构填充之前就有问题;如果您的用户空间将基本整数定义为 64 位,则该错误也将不可避免地触发。
还有另一个问题:“顺便说一句,NETLINK_USER
被定义为 31”告诉我您正在关注早已过时或由不知道自己在做什么的人编写的教程或代码示例。你知道 31 是从哪里来的吗?它是您的“Netlink 家族”的标识符。他们将其定义为 31,因为这是 它可以具有的最高可能值 (0-31),因此,它最不可能与内核定义的其他 Netlink 系列发生冲突。 (因为它们是 numbered monotonically。)但是大多数粗心的 Netlink 用户都在按照教程进行操作,因此他们的大多数 Netlink 系列都标识为 31。因此,您的内核模块将无法与它们共存。 netlink_kernel_create()
会把你踢出去,因为 31 号已经被占用了。
您可能会想,“好吧,该死。只有 32 个可用插槽,23 of them are already taken by the kernel 并且有未知但可能有大量其他人想要注册不同的 Netlink 系列。我该怎么办? !"
3。正确的方法
现在是 2020 年。我们不再使用 Netlink。我们使用 better-Netlink:通用网络链接。
Generic Netlink 使用字符串和动态整数作为家族标识符,并促使您默认使用 Netlink 的“属性”框架。 (后者鼓励你以可移植的方式序列化和反序列化结构,这才是真正解决你原来问题的方法。)
此代码需要对您的用户空间客户端和内核模块可见:
#define SAMPLE_FAMILY "Sample Family"
enum sample_operations {
SO_TEST, /* from your "struct test" */
/* List more here for different request types. */
};
enum sample_attribute_ids {
/* Numbering must start from 1 */
SAI_LENGTH = 1, /* From your test.length */
SAI_NAME, /* From your test.name */
/* This is a special one; don't list any more after this. */
SAI_COUNT,
#define SAI_MAX (SAI_COUNT - 1)
};
这是内核模块:
#include <linux/module.h>
#include <linux/version.h>
#include <net/genetlink.h>
#include "../include/protocol.h"
/*
* A "policy" is a bunch of rules. The kernel will validate the request's fields
* match these data types (and other defined constraints) for us.
*/
struct nla_policy const sample_policy[SAI_COUNT] = {
[SAI_LENGTH] = { .type = NLA_U32 },
[SAI_NAME] = { .type = NLA_STRING },
};
/*
* This is the function the kernel calls whenever the client sends SO_TEST
* requests.
*/
static int handle_test_operation(struct sk_buff *skb, struct genl_info *info)
{
if (!info->attrs[SAI_LENGTH]) {
pr_err("Invalid request: Missing length attribute.\n");
return -EINVAL;
}
if (!info->attrs[SAI_NAME]) {
pr_err("Invalid request: Missing name attribute.\n");
return -EINVAL;
}
pr_info("Printing the length and name: %u, '%s'\n",
nla_get_u32(info->attrs[SAI_LENGTH]),
(unsigned char *)nla_data(info->attrs[SAI_NAME]));
return 0;
}
static const struct genl_ops ops[] = {
/*
* This is what tells the kernel to use the function above whenever
* userspace sends SO_TEST requests.
* Add more array entries if you define more sample_operations.
*/
{
.cmd = SO_TEST,
.doit = handle_test_operation,
#if LINUX_VERSION_CODE < KERNEL_VERSION(5, 2, 0)
/* Before kernel 5.2, each op had its own policy. */
.policy = sample_policy,
#endif
},
};
/* Descriptor of our Generic Netlink family */
static struct genl_family sample_family = {
.name = SAMPLE_FAMILY,
.version = 1,
.maxattr = SAI_MAX,
#if LINUX_VERSION_CODE >= KERNEL_VERSION(5, 2, 0)
/* Since kernel 5.2, the policy is family-wide. */
.policy = sample_policy,
#endif
.module = THIS_MODULE,
.ops = ops,
.n_ops = ARRAY_SIZE(ops),
};
/* Called by the kernel when the kernel module is inserted */
static int test_init(void)
{
return genl_register_family(&sample_family);
}
/* Called by the kernel when the kernel module is removed */
static void test_exit(void)
{
genl_unregister_family(&sample_family);
}
module_init(test_init);
module_exit(test_exit);
这是用户空间客户端(您需要在 Debian/Ubuntu 上安装 libnl-genl-3 --sudo apt install libnl-genl-3-dev
):
#include <errno.h>
#include <netlink/genl/ctrl.h>
#include <netlink/genl/genl.h>
#include "../include/protocol.h"
static struct nl_sock *sk;
static int genl_family;
static void prepare_socket(void)
{
sk = nl_socket_alloc();
genl_connect(sk);
genl_family = genl_ctrl_resolve(sk, SAMPLE_FAMILY);
}
static struct nl_msg *prepare_message(void)
{
struct nl_msg *msg;
msg = nlmsg_alloc();
genlmsg_put(msg, NL_AUTO_PORT, NL_AUTO_SEQ, genl_family, 0, 0, SO_TEST, 1);
/*
* The nla_put* functions ensure that your data will be stored in a
* portable way.
*/
nla_put_u32(msg, SAI_LENGTH, 18);
nla_put_string(msg, SAI_NAME, "Just a test");
return msg;
}
int main(int argc, char **argv)
{
struct nl_msg *msg;
prepare_socket();
msg = prepare_message();
nl_send_auto(sk, msg); /* Send message */
nlmsg_free(msg);
nl_socket_free(sk);
return 0;
}
此代码应从内核 4.10 开始运行。 (我在 4.15 测试过它。)内核 API 之前有些不同。
我在我的 Dropbox 中留下了我的测试环境的袖珍版(带有 makefile 和正确的错误处理等等),因此您可以 运行 轻松地使用它。