gpt4 book ai didi

c - 通过 C 中的 Netlink 从内核到用户空间的多播

转载 作者:太空狗 更新时间:2023-10-29 15:58:15 26 4
gpt4 key购买 nike

我正在尝试使用 Netlink 编写一个在内核空间和用户空间之间进行通信的简单程序。基本上这就是我想要实现的目标:

  1. 用户空间程序开始绑定(bind)到用户定义的多播组。
  2. 插入内核模块
  3. 内核模块向这个多播组发送消息
  4. 用户空间程序收到消息

这是我的代码:

======用户空间程序======

#include<stdio.h>
#include<string.h>
#include<stdlib.h>
#include<sys/socket.h>
#include<linux/netlink.h>
#include<sys/types.h>
#include<unistd.h>

#define MYPROTO NETLINK_USERSOCK
#define MYMGRP 0x21 //User defined group, consistent in both kernel prog and user prog

int open_netlink()
{
int sock = socket(AF_NETLINK,SOCK_RAW,MYPROTO);
struct sockaddr_nl addr;

memset((void *)&addr, 0, sizeof(addr));

if (sock<0)
return sock;
addr.nl_family = AF_NETLINK;
addr.nl_pid = getpid();
addr.nl_groups = MYMGRP;
if (bind(sock,(struct sockaddr *)&addr,sizeof(addr))<0)
return -1;
return sock;
}

int read_event(int sock)
{
struct sockaddr_nl nladdr;
struct msghdr msg;
struct iovec iov[2];
struct nlmsghdr nlh;
char buffer[65536];
int ret;
iov[0].iov_base = (void *)&nlh;
iov[0].iov_len = sizeof(nlh);
iov[1].iov_base = (void *)buffer;
iov[1].iov_len = sizeof(buffer);
msg.msg_name = (void *)&(nladdr);
msg.msg_namelen = sizeof(nladdr);
msg.msg_iov = iov;
msg.msg_iovlen = sizeof(iov)/sizeof(iov[0]);
ret=recvmsg(sock, &msg, 0);
if (ret<0) {
return ret;
}
printf("Received message payload: %s\n", NLMSG_DATA(&nlh));
}

int main(int argc, char *argv[])
{
int nls = open_netlink();
if (nls<0) {
err(1,"netlink");
}

while (1)
read_event(nls);
return 0;
}

======内核模块======

#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/init.h>
#include <net/sock.h>
#include <linux/socket.h>
#include <linux/net.h>
#include <asm/types.h>
#include <linux/netlink.h>
#include <linux/rtnetlink.h>
#include <linux/skbuff.h>
#include <linux/delay.h>

#define NETLINK_USER 31
#define MYGRP 0x21 //User defined group, consistent in both kernel prog and user prog

struct sock *nl_sk = NULL;

static void send_to_user() {
struct sk_buff *skb_out;
struct nlmsghdr *nlh;
int msg_size;
char *msg = "Hello from kernel";
int res;

printk(KERN_INFO "Entering: %s\n", __FUNCTION__);
msg_size = strlen(msg);
skb_out = nlmsg_new(msg_size, 0);

if (!skb_out) {
printk(KERN_ERR "Failed to allocate new skb\n");
return;
}
nlh = nlmsg_put(skb_out, 0, 1, NLMSG_DONE, msg_size, 0);
//NETLINK_CB(skb_out).dst_group = 1; /* Multicast to group 1, 1<<0 */
strncpy(nlmsg_data(nlh), msg, msg_size);

res = nlmsg_multicast(nl_sk, skb_out, 0, MYGRP, 0);
if (res < 0) {
printk(KERN_INFO "Error while sending bak to user, err id: %d\n", res);
}
}

static int __init
hello_init(void) {

struct netlink_kernel_cfg cfg = {
.groups = MYGRP,
};
printk("Entering: %s\n", __FUNCTION__);
nl_sk = netlink_kernel_create(&init_net, NETLINK_USER, &cfg);
if (!nl_sk) {
printk(KERN_ALERT "Error creating socket.\n");
return -10;
}

send_to_user();

return 0;
}

static void __exit
hello_exit(void) {

printk(KERN_INFO "exiting hello module\n");
netlink_kernel_release(nl_sk);
}

module_init(hello_init);
module_exit(hello_exit);

由于内核模块在初始化时只会发送一次消息,所以我先运行监听程序,然后再插入模块,虽然我总是得到这个错误:

Error while sending bak to user, err id: -3

当追踪到err id时,它反射(reflect)在netlink/af_netlink.c中的这段代码中:

if (info.delivery_failure) {
kfree_skb(info.skb2);
return -ENOBUFS;
}
consume_skb(info.skb2);

if (info.delivered) {
if (info.congested && (allocation & __GFP_WAIT))
yield();
return 0;
}
return -ESRCH;

我认为这不是 delivery_failure 但由于某些原因仍未交付。

我指的是这个 example其中作者的程序不断监听路由变化。尽管我想使用用户定义的多播组。

有什么想法吗?提前致谢!

最佳答案

这是我在您的代码中发现的两个关键问题:

  1. 内核程序和用户程序中的协议(protocol)族和多播组都需要保持一致。您的协议(protocol)系列在用户空间中为 NETLINK_USERSOCK (2),在内核空间中为 NETLINK_USER (31)。
  2. addr.nl_groups = MYMGRP; 出于某种原因无法正常工作。不过,这样做是可行的:setsockopt(sock, 270, NETLINK_ADD_MEMBERSHIP, &group, sizeof(group))

不致命:

  1. 在这种情况下,模块不监听组消息,因此您不需要在 netlink_kernel_create() 参数中包含多播组。

此外,与 netlink 无关,但无论如何都很有用:

  1. strlen() 不包括空字符。在消息分配期间,您可能应该添加一个字节来弥补这一点。
  2. 在这种情况下,NLMSG_DATA(&nlh) 是未定义的行为。这是因为您的 header 和数据位于单独的内存块中,不能保证粘合在一起,并且宏所做的只是在 nlh 之后访问内存块:

#define NLMSG_DATA(nlh) ((void*)(((char*)nlh) + NLMSG_LENGTH(0)))

这是我的代码版本:

用户空间程序:

#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <sys/socket.h>
#include <linux/netlink.h>
#include <unistd.h>

/* Protocol family, consistent in both kernel prog and user prog. */
#define MYPROTO NETLINK_USERSOCK
/* Multicast group, consistent in both kernel prog and user prog. */
#define MYMGRP 21

int open_netlink(void)
{
int sock;
struct sockaddr_nl addr;
int group = MYMGRP;

sock = socket(AF_NETLINK, SOCK_RAW, MYPROTO);
if (sock < 0) {
printf("sock < 0.\n");
return sock;
}

memset((void *) &addr, 0, sizeof(addr));
addr.nl_family = AF_NETLINK;
addr.nl_pid = getpid();
/* This doesn't work for some reason. See the setsockopt() below. */
/* addr.nl_groups = MYMGRP; */

if (bind(sock, (struct sockaddr *) &addr, sizeof(addr)) < 0) {
printf("bind < 0.\n");
return -1;
}

/*
* 270 is SOL_NETLINK. See
* http://lxr.free-electrons.com/source/include/linux/socket.h?v=4.1#L314
* and
* http://stackoverflow.com/questions/17732044/
*/
if (setsockopt(sock, 270, NETLINK_ADD_MEMBERSHIP, &group, sizeof(group)) < 0) {
printf("setsockopt < 0\n");
return -1;
}

return sock;
}

void read_event(int sock)
{
struct sockaddr_nl nladdr;
struct msghdr msg;
struct iovec iov;
char buffer[65536];
int ret;

iov.iov_base = (void *) buffer;
iov.iov_len = sizeof(buffer);
msg.msg_name = (void *) &(nladdr);
msg.msg_namelen = sizeof(nladdr);
msg.msg_iov = &iov;
msg.msg_iovlen = 1;

printf("Ok, listening.\n");
ret = recvmsg(sock, &msg, 0);
if (ret < 0)
printf("ret < 0.\n");
else
printf("Received message payload: %s\n", NLMSG_DATA((struct nlmsghdr *) &buffer));
}

int main(int argc, char *argv[])
{
int nls;

nls = open_netlink();
if (nls < 0)
return nls;

while (1)
read_event(nls);

return 0;
}

这是内核模块:

#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/netlink.h>
#include <net/netlink.h>
#include <net/net_namespace.h>

/* Protocol family, consistent in both kernel prog and user prog. */
#define MYPROTO NETLINK_USERSOCK
/* Multicast group, consistent in both kernel prog and user prog. */
#define MYGRP 21

static struct sock *nl_sk = NULL;

static void send_to_user(void)
{
struct sk_buff *skb;
struct nlmsghdr *nlh;
char *msg = "Hello from kernel";
int msg_size = strlen(msg) + 1;
int res;

pr_info("Creating skb.\n");
skb = nlmsg_new(NLMSG_ALIGN(msg_size + 1), GFP_KERNEL);
if (!skb) {
pr_err("Allocation failure.\n");
return;
}

nlh = nlmsg_put(skb, 0, 1, NLMSG_DONE, msg_size + 1, 0);
strcpy(nlmsg_data(nlh), msg);

pr_info("Sending skb.\n");
res = nlmsg_multicast(nl_sk, skb, 0, MYGRP, GFP_KERNEL);
if (res < 0)
pr_info("nlmsg_multicast() error: %d\n", res);
else
pr_info("Success.\n");
}

static int __init hello_init(void)
{
pr_info("Inserting hello module.\n");

nl_sk = netlink_kernel_create(&init_net, MYPROTO, NULL);
if (!nl_sk) {
pr_err("Error creating socket.\n");
return -10;
}

send_to_user();

netlink_kernel_release(nl_sk);
return 0;
}

static void __exit hello_exit(void)
{
pr_info("Exiting hello module.\n");
}

module_init(hello_init);
module_exit(hello_exit);

MODULE_LICENSE("GPL");

在内核 3.13 中测试。

(我可以建议人们使用 libnl-3 而不是用户空间程序的原始套接字。它的多播 Netlink 文档是 actually decent 。)

关于c - 通过 C 中的 Netlink 从内核到用户空间的多播,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/22691305/

26 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com