用C从内核中通过网络链路的多播到用户空间 [英] Multicast from kernel to user space via Netlink in C

查看:706
本文介绍了用C从内核中通过网络链路的多播到用户空间的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我试图写一个简单的程序内核,并使用网络链路用户空间之间的通信。基本上这里就是我想实现:

I was trying to write a simple program communicating between kernel and user space using Netlink. Basically here's what I wanted to achieve:


  1. 用户空间程序开始结合定义多播组的用户。

  2. 插入内核模块

  3. 内核模块将消息发送到该组播组

  4. 用户空间程序接收到的消息

下面是我的code:

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

======User space program======

#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;
}

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

======Kernel module======

#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);

由于初始化期间的内核模块将只发送消息一次,因此我跑听节目然后再插入模块,虽然我总是得到这个错误:

Since the kernel module will only send the message once during initialization, thus I run listening program first and then insert module, although I always got this error:

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

当追查到犯错的ID,它体现在这片网络链路/ af_netlink.c code的:

When track down to err id, it's reflected in this piece of code in 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;

我presume它不是delivery_failure但仍不交付某些原因。

I presume it's not delivery_failure but still not delivered for some reasons.

我指的是这个 的例子中,笔者的计划保持侦听路线发生改变。虽然我想用一个用户自定义多播组。

I was referring to this example in which author's program keeps listening routes change. Although I would like to use a user defined multicast group.

任何想法?在此先感谢!

Any ideas? Thanks in advance!

推荐答案

这是我在你的code发现了两个关键问题:

These are the two key problems I found in your code:


  1. 双方协议族和组播组需要在两个内核中前卫和用户PROG一致。你的协议族是 NETLINK_USERSOCK 在用户空间(2)和 NETLINK_USER (31)在内核空间。

  2. addr.nl_groups = MYMGRP; 不出于某种原因。这样做,虽然:的setsockopt(袜子,270,NETLINK_ADD_MEMBERSHIP,&安培;组的sizeof(集团))

  1. Both protocol family and multicast group need to be consistent in both kernel prog and user prog. Your protocol family is NETLINK_USERSOCK (2) in userspace and NETLINK_USER (31) in kernelspace.
  2. addr.nl_groups = MYMGRP; doesn't work for some reason. This does, though: setsockopt(sock, 270, NETLINK_ADD_MEMBERSHIP, &group, sizeof(group)).

不是致命的:


  1. 在这种情况下,模块不听组消息,因此,你不必包含在 netlink_kernel_create() PARAMS组播组。

  1. In this case, the module is not listening to the group messages, therefore you don't need to include the multicast group in the netlink_kernel_create() params.

此外,没有真正的netlink有关,但无论如何有用的:

Also, Not really netlink-related but useful anyway:


  1. 的strlen()不包括空恰拉。在消息的分配,你应该增加一个字节来弥补这一点。

  2. 在这种情况下, NLMSG_DATA(安培;北大屿山医院)是未定义的行为。这是因为你的头和数据在单独的内存块,但不保证粘,所有的宏将是继北大屿山医院访问的内存块:

  1. strlen() does not include the null chara. During message allocations, you should probably add a byte to make up for this.
  2. In this case, NLMSG_DATA(&nlh) is undefined behaviour. This is because your header and data are in separate memory chunks, not guaranteed to be glued, and all the macro does is access the chunk of memory after nlh:

的#define NLMSG_DATA(北大屿山医院)((无效*)(((字符*)北大屿山医院)+ NLMSG_LENGTH(0)))

这是我对你的code版本:

This is my version of your code:

用户空间程序:

#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。

Tested in kernel 3.13.

(我建议人们使用 libnl-3 代替了原始套接字用户空间程序。它的多播网络链路文档实际上体面)。

(May I suggest that people uses libnl-3 instead of raw sockets for the userspace program. Its multicast Netlink documentation is actually decent.)

这篇关于用C从内核中通过网络链路的多播到用户空间的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆