内核与用户层通信之netlink

内核与用户空间通信有很多种通信方式,netlink是其中一种,其余的还有/proc、ioctl、sockopt、共享内存等等。netlink的特点是异步全双工

netlink使用32位端口寻址,称为pid(与进程号没有关系),其中内核的pid地址为0,。netlink主要特性如下:

  • 支持全双工、异步通信(当然同步也支持)
  • 用户空间可使用标准的BSD socket接口(但netlink并没有屏蔽掉协议包的构造与解析过程,推荐使用libnl等第三方库)
  • 在内核空间使用专用的内核API接口
  • 支持多播(因此支持“总线”式通信,可实现消息订阅)
  • 在内核端可用于进程上下文与中断上下文

基本数据结构:


struct msghdr {
    void         *msg_name;       /* optional address */
    socklen_t     msg_namelen;    /* size of address */
    struct iovec *msg_iov;        /* scatter/gather array */
    size_t        msg_iovlen;     /* # elements in msg_iov */
    void         *msg_control;    /* ancillary data, see below */
    size_t        msg_controllen; /* ancillary data buffer len */
    int           msg_flags;      /* flags (unused) */
};

struct sockaddr_nl
{
    sa_family_t nl_family; /*该字段总是为AF_NETLINK */
    unsigned short nl_pad; /* 目前未用到,填充为0*/
    __u32 nl_pid; /* process pid */
    __u32 nl_groups; /* multicast groups mask */
};
struct nlmsghdr
{
    __u32 nlmsg_len; /* Length of message including header */
    __u16 nlmsg_type; /* Message content */
    __u16 nlmsg_flags; /* Additional flags */
    __u32 nlmsg_seq; /* Sequence number */
    __u32 nlmsg_pid; /* Sending process PID */
};

struct sockaddr_nl是netlink通信地址,和通常socket编程中的sockaddr_in作用一样。pid表示通信端口,groups表示组,注意这里为希望加入多播组号的掩码,也就是说最多只支持32个组。
Netlink报文的数据区由消息头和消息体构成,struct nlmsghdr即为消息头,消息体接在消息头后。

操作实例:

用户层pid设置为100,应用层发送一条信息到内核,内核回复同样的信息;
内核层:

#include <linux/init.h>
#include <linux/module.h>
#include <linux/stat.h>
#include <linux/kdev_t.h>
#include <linux/fs.h>
#include <linux/device.h>
#include <linux/cdev.h>
#include <asm/uaccess.h>

#include <net/netlink.h>
#include <net/sock.h>

#define NETLINK_TEST (25)

static dev_t devId;
static struct class *cls = NULL;
struct sock *nl_sk = NULL;

static void
hello_cleanup(void)
{
        netlink_kernel_release(nl_sk);
        device_destroy(cls, devId);
        class_destroy(cls);
        unregister_chrdev_region(devId, 1);
}

static void
netlink_send(int pid, uint8_t *message, int len)
{
        struct sk_buff *skb_1;
        struct nlmsghdr *nlh;

        if(!message || !nl_sk) {
                return;
        }

        skb_1 = alloc_skb(NLMSG_SPACE(len), GFP_KERNEL);
        if( !skb_1 ) {
                printk(KERN_ERR "alloc_skb error!\n");
        }

        nlh = nlmsg_put(skb_1, 0, 0, 0, len, 0);
        NETLINK_CB(skb_1).portid = 0;
        NETLINK_CB(skb_1).dst_group = 0;
        memcpy(NLMSG_DATA(nlh), message, len);
        netlink_unicast(nl_sk, skb_1, pid, MSG_DONTWAIT);
}

static void
netlink_input(struct sk_buff *__skb)
{
        struct sk_buff *skb;
        char str[100];
        struct nlmsghdr *nlh;

        if( !__skb ) {
                return;
        }

        skb = skb_get(__skb);
        if( skb->len < NLMSG_SPACE(0)) {
                return;
        }

        nlh = nlmsg_hdr(skb);
        memset(str, 0, sizeof(str));
        memcpy(str, NLMSG_DATA(nlh), sizeof(str));
        printk(KERN_INFO "receive message (pid:%d):%s\n", nlh->nlmsg_pid, str);
        printk(KERN_INFO "space:%d\n", NLMSG_SPACE(0));
        printk(KERN_INFO "size:%d\n", nlh->nlmsg_len);
        netlink_send(nlh->nlmsg_pid, NLMSG_DATA(nlh), nlh->nlmsg_len - NLMSG_SPACE(0));

        return;
}

static __init int netlink_init(void)
{
        int result;
        struct netlink_kernel_cfg nkc;

        printk(KERN_WARNING "netlink init start!\n");

        //动态注册设备号
        if(( result = alloc_chrdev_region(&devId, 0, 1, "stone-alloc-dev") ) != 0) {
                printk(KERN_WARNING "register dev id error:%d\n", result);
                goto err;
        } else {
                printk(KERN_WARNING "register dev id success!\n");
        }
        //动态创建设备节点
        cls = class_create(THIS_MODULE, "stone-class");
        if(IS_ERR(cls)) {
                printk(KERN_WARNING "create class error!\n");
                goto err;
        }
        if(device_create(cls, NULL, devId, "", "hello%d", 0) == NULL) {
                printk(KERN_WARNING "create device error!\n");
                goto err;
        }

        //初始化netlink
        nkc.groups = 0;
        nkc.flags = 0;
        nkc.input = netlink_input;
        nkc.cb_mutex = NULL;
        nkc.bind = NULL;
        nkc.unbind = NULL;
        nkc.compare = NULL;
        nl_sk = netlink_kernel_create(&init_net, NETLINK_TEST, &nkc);
        if( !nl_sk ) {
                printk(KERN_ERR "[netlink] create netlink socket error!\n");
                goto err;
        }

        printk(KERN_ALERT "netlink init success!\n");
        return 0;
err:
        hello_cleanup();
        return -1;
}

static __exit void netlink_exit(void)
{
        hello_cleanup();
        printk(KERN_WARNING "netlink exit!\n");
}

module_init(netlink_init);
module_exit(netlink_exit);

MODULE_LICENSE("GPL");
MODULE_AUTHOR("Stone");

应用层使用sendmsg、recvmsg :

#include <sys/stat.h>  
#include <unistd.h>  
#include <stdio.h>  
#include <stdlib.h>  
#include <sys/socket.h>  
#include <string.h>  
#include <asm/types.h>  
#include <linux/netlink.h>  
#include <linux/socket.h>  
#include <errno.h>  
  
#define NETLINK_TEST    (25)  
#define MAX_PAYLOAD     (1024)  
#define TEST_PID        (100)  
  
int netlink_create_socket(void)  
{  
        //create a socket  
        return socket(AF_NETLINK, SOCK_RAW, NETLINK_TEST);  
}  
  
int netlink_bind(int sock_fd)  
{  
        struct sockaddr_nl addr;  
  
        memset(&addr, 0, sizeof(struct sockaddr_nl));  
        addr.nl_family = AF_NETLINK;  
        addr.nl_pid = TEST_PID;  
        addr.nl_groups = 0;  
  
        return bind(sock_fd, (struct sockaddr *)&addr, sizeof(struct sockaddr_nl));  
}  
  
int  
netlink_send_message(int sock_fd, const unsigned char *message, int len,  
                                        unsigned int pid, unsigned int group)  
{  
        struct nlmsghdr *nlh = NULL;  
        struct sockaddr_nl dest_addr;  
        struct iovec iov;  
        struct msghdr msg;  
  
        if( !message ) {  
                return -1;  
        }  
  
        //create message  
        nlh = (struct nlmsghdr *)malloc(NLMSG_SPACE(len));  
        if( !nlh ) {  
                perror("malloc");  
                return -2;  
        }  
        nlh->nlmsg_len = NLMSG_SPACE(len);  
        nlh->nlmsg_pid = TEST_PID;  
        nlh->nlmsg_flags = 0;  
        memcpy(NLMSG_DATA(nlh), message, len);  
  
        iov.iov_base = (void *)nlh;  
        iov.iov_len = nlh->nlmsg_len;  
        memset(&dest_addr, 0, sizeof(struct sockaddr_nl));  
        dest_addr.nl_family = AF_NETLINK;  
        dest_addr.nl_pid = pid;  
        dest_addr.nl_groups = group;  
  
        memset(&msg, 0, sizeof(struct msghdr));  
        msg.msg_name = (void *)&dest_addr;  
        msg.msg_namelen = sizeof(struct sockaddr_nl);  
        msg.msg_iov = &iov;  
        msg.msg_iovlen = 1;  
  
        //send message  
        if( sendmsg(sock_fd, &msg, 0) < 0 )  
        {  
                printf("send error!\n");  
                free(nlh);  
                return -3;  
        }  
  
        free(nlh);  
        return 0;  
}  
  
int  
netlink_recv_message(int sock_fd, unsigned char *message, int *len)  
{  
        struct nlmsghdr *nlh = NULL;  
        struct sockaddr_nl source_addr;  
        struct iovec iov;  
        struct msghdr msg;  
  
        if( !message || !len ) {  
                return -1;  
        }  
  
        //create message  
        nlh = (struct nlmsghdr *)malloc(NLMSG_SPACE(MAX_PAYLOAD));  
        if( !nlh ) {  
                perror("malloc");  
                return -2;  
        }  
        iov.iov_base = (void *)nlh;  
        iov.iov_len = NLMSG_SPACE(MAX_PAYLOAD);  
        memset(&source_addr, 0, sizeof(struct sockaddr_nl));  
        memset(&msg, 0, sizeof(struct msghdr));  
        msg.msg_name = (void *)&source_addr;  
        msg.msg_namelen = sizeof(struct sockaddr_nl);  
        msg.msg_iov = &iov;  
        msg.msg_iovlen = 1;  
  
        if ( recvmsg(sock_fd, &msg, 0) < 0 ) {  
                printf("recvmsg error!\n");  
                return -3;  
        }  
        *len = nlh->nlmsg_len - NLMSG_SPACE(0);  
        memcpy(message, (unsigned char *)NLMSG_DATA(nlh), *len);  
  
        free(nlh);  
        return 0;  
}  
  
int  
main(int argc, char **argv)  
{  
        int sock_fd;  
        char buf[MAX_PAYLOAD];  
        int len;  
  
        if( argc < 2) {  
                printf("enter message!\n");  
                exit(EXIT_FAILURE);  
        }  
  
        sock_fd = netlink_create_socket();  
        if(sock_fd == -1) {  
                printf("socket error!\n");  
                return -1;  
        }  
  
        if( netlink_bind(sock_fd) < 0 ) {  
                perror("bind");  
                close(sock_fd);  
                exit(EXIT_FAILURE);  
        }  
  
        netlink_send_message(sock_fd, argv[1], strlen(argv[1]) + 1, 0, 0);  
        if( netlink_recv_message(sock_fd, buf, &len) == 0 ) {  
                printf("recv:%s len:%d\n", buf, len);  
        }  
  
        close(sock_fd);  
        return 0;  
}  

应用层程序2 , 使用sendto、recvfrom发送接收数据:


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

#define NETLINK_TEST    (25)
#define MAX_PAYLOAD     (1024)
#define TEST_PID        (100)

int netlink_create_socket(void)
{
        //create a socket
        return socket(AF_NETLINK, SOCK_RAW, NETLINK_TEST);
}

int netlink_bind(int sock_fd)
{
        struct sockaddr_nl addr;

        memset(&addr, 0, sizeof(struct sockaddr_nl));
        addr.nl_family = AF_NETLINK;
        addr.nl_pid = TEST_PID;
        addr.nl_groups = 0;

        return bind(sock_fd, (struct sockaddr *)&addr, sizeof(struct sockaddr_nl));
}

int
netlink_send_message(int sock_fd, const unsigned char *message, int len,
                                        unsigned int pid, unsigned int group)
{
        struct nlmsghdr *nlh = NULL;
        struct sockaddr_nl dest_addr;

        if( !message ) {
                return -1;
        }

        //create message
        nlh = (struct nlmsghdr *)malloc(NLMSG_SPACE(len));
        if( !nlh ) {
                perror("malloc");
                return -2;
        }
        nlh->nlmsg_len = NLMSG_SPACE(len);
        nlh->nlmsg_pid = TEST_PID;
        nlh->nlmsg_flags = 0;
        memcpy(NLMSG_DATA(nlh), message, len);

        memset(&dest_addr, 0, sizeof(struct sockaddr_nl));
        dest_addr.nl_family = AF_NETLINK;
        dest_addr.nl_pid = pid;
        dest_addr.nl_groups = group;

        //send message
        if( sendto(sock_fd, nlh, nlh->nlmsg_len, 0, (struct sockaddr *)&dest_addr, sizeof(struct sockaddr_nl)) != nlh->nlmsg_len ) {
                printf("send error!\n");
                free(nlh);
                return -3;
        }

        free(nlh);
        return 0;
}

int
netlink_recv_message(int sock_fd, unsigned char *message, int *len)
{
        struct nlmsghdr *nlh = NULL;
        struct sockaddr_nl src_addr;
        socklen_t addrlen = sizeof(struct sockaddr_nl);

        if( !message || !len ) {
                return -1;
        }

        //create message
        nlh = (struct nlmsghdr *)malloc(NLMSG_SPACE(MAX_PAYLOAD));
        if( !nlh ) {
                perror("malloc");
                return -2;
        }
        memset(&src_addr, 0, sizeof(struct sockaddr_nl));
        if( recvfrom(sock_fd, nlh, NLMSG_SPACE(MAX_PAYLOAD), 0, (struct sockaddr *)&src_addr, (socklen_t *)&addrlen) < 0 ) {
                printf("recvmsg error!\n");
                return -3;
        }
        *len = nlh->nlmsg_len - NLMSG_SPACE(0);
        memcpy(message, (unsigned char *)NLMSG_DATA(nlh), *len);

        free(nlh);
        return 0;
}

int
main(int argc, char **argv)
{
        int sock_fd;
        char buf[MAX_PAYLOAD];
        int len;

        if( argc < 2) {
                printf("enter message!\n");
                exit(EXIT_FAILURE);
        }

        sock_fd = netlink_create_socket();
        if(sock_fd == -1) {
                printf("socket error!\n");
                return -1;
        }

        if( netlink_bind(sock_fd) < 0 ) {
                perror("bind");
                close(sock_fd);
                exit(EXIT_FAILURE);
        }

        netlink_send_message(sock_fd, argv[1], strlen(argv[1]) + 1, 0, 0);
        if( netlink_recv_message(sock_fd, buf, &len) == 0 ) {
                printf("recv:%s len:%d\n", buf, len);
        }

        close(sock_fd);
        return 0;
}

执行应用程序

$ ./netlink hello
查看内核信息:


QQ图片20180419145506.png
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 219,869评论 6 508
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 93,716评论 3 396
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 166,223评论 0 357
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 59,047评论 1 295
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 68,089评论 6 395
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,839评论 1 308
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,516评论 3 420
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,410评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,920评论 1 319
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 38,052评论 3 340
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 40,179评论 1 352
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,868评论 5 346
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,522评论 3 331
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 32,070评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 33,186评论 1 272
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 48,487评论 3 375
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 45,162评论 2 356

推荐阅读更多精彩内容

  • 作者: 一字马胡 转载标志 【2018-03-27】 更新日志 日期更新内容备注2018-03-27回顾以前的知...
    一字马胡阅读 496评论 0 3
  • 大纲 一.Socket简介 二.BSD Socket编程准备 1.地址 2.端口 3.网络字节序 4.半相关与全相...
    y角阅读 2,486评论 2 11
  • 有没有发现一个奇怪的现象?越优秀的人,越是多才多艺的,而且每种能力似乎都在平均线之上?优秀这种品质难道也符合马太效...
    遇见未来d我阅读 407评论 1 1
  • Location: Shanghai Weather: Sunny A、Briefing B、英语 阅读:扫雷 ,...
    V窦小安V阅读 151评论 0 0
  • 4月6日 星期五 大风 今天是放假的第二天,二月的天气忽冷忽热,前几天还是阳光明媚,这几天就又像回到了冬天,...
    一米阳光369阅读 131评论 0 1