• 【博客440】Linux netlink:用户态进程与内核态进程通信


    Linux netlink:用户态进程与内核态进程通信

    Netlink套接字

    实现用户进程与内核进程通信的一种特殊的进程间通信(IPC) ,也是网络应用程序与内核通信的最常用的接口。

    在Linux 内核中,使用netlink 进行应用与内核通信的应用有很多,如

    路由 daemon(NETLINK_ROUTE)
    用户态 socket 协议(NETLINK_USERSOCK)
    防火墙(NETLINK_FIREWALL)
    netfilter 子系统(NETLINK_NETFILTER)
    内核事件向用户态通知(NETLINK_KOBJECT_UEVENT)
    通用netlink(NETLINK_GENERIC)
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    Netlink 是一种在内核与用户应用间进行双向数据传输的非常好的方式,用户态应用使用标准的 socket API 就可以使用 netlink 提供的强大功能,内核态需要使用专门的内核 API 来使用 netlink。

    一般来说用户空间和内核空间的通信方式有三种:/proc、ioctl、Netlink。而前两种都是单向的,而Netlink可以实现双工通信。

    Netlink 相对于系统调用,ioctl 以及 /proc文件系统而言,具有以下优点:

    1、netlink使用简单
    
    只需要在include/linux/netlink.h中增加一个新类型的 netlink 协议定义即可,
    (如 #define NETLINK_TEST 20 然后,内核和用户态应用就可以立即通过 socket API 使用该
     netlink 协议类型进行数据交换)
    
    2、netlink是一种异步通信机制,在内核与用户态应用之间传递的消息保存在socket缓存队列中,
    发送消息只是把消息保存在接收者的socket的接收队列,而不需要等待接收者收到消息
    
    3、netlink 的内核部分可采用模块的方式实现,使用 netlink 的应用部分和内核部分没有编译时依赖
    
    4、netlink 支持多播
    
    内核模块或应用可以把消息多播给一个netlink组,属于该neilink 组的任何内核模块或应用都能接收到该消息,
    内核事件向用户态的通知机制就使用了这一特性
    
    5、内核可以使用 netlink 首先发起会话
    
    Netlink协议基于BSD socket和AF_NETLINK地址簇,使用32位的端口号寻址,
    每个Netlink协议通常与一个或一组内核服务/组件相关联,
    如NETLINK_ROUTE用于获取和设置路由与链路信息、NETLINK_KOBJECT_UEVENT用于内核向用户空间的udev进程发送通知等。
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21

    用户态数据结构

    用户态应用使用标准的 socket API有sendto(),recvfrom(), sendmsg(), recvmsg()。

    Netlink通信跟常用UDP Socket通信类似,struct sockaddr_nl是netlink通信地址,跟普通socket struct sockaddr_in类似。

    1、struct sockaddr_nl结构:

    struct sockaddr_nl {
         __kernel_sa_family_t    nl_family;  /* AF_NETLINK (跟AF_INET对应)*/
         unsigned short  nl_pad;     /* zero */
         __u32       nl_pid;     /* port ID  (通信端口号)*/
         __u32       nl_groups;  /* multicast groups mask */
    };
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    2、struct nlmsghd 结构:

    /* struct nlmsghd 是netlink消息头*/
    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 port ID */
    };
    
    nlmsg_type:消息状态,内核在include/uapi/linux/netlink.h中定义了以下4种通用的消息类型,它们分别是:
    #define NLMSG_NOOP      0x1 /* Nothing.     */
    #define NLMSG_ERROR     0x2 /* Error        */
    #define NLMSG_DONE      0x3 /* End of a dump    */
    #define NLMSG_OVERRUN       0x4 /* Data lost        */
    #define NLMSG_MIN_TYPE      0x10    /* < 0x10: reserved control messages */
    nlmsg_flags:消息标记,它们用以表示消息的类型,如下
    /* Flags values */
    #define NLM_F_REQUEST       1   /* It is request message.   */
    #define NLM_F_MULTI     2   /* Multipart message, terminated by NLMSG_DONE */
    #define NLM_F_ACK       4   /* Reply with ack, with zero or error code */
    #define NLM_F_ECHO      8   /* Echo this request        */
    #define NLM_F_DUMP_INTR     16  /* Dump was inconsistent due to sequence change */
    
    /* Modifiers to GET request */
    #define NLM_F_ROOT  0x100   /* specify tree root    */
    #define NLM_F_MATCH 0x200   /* return all matching  */
    #define NLM_F_ATOMIC    0x400   /* atomic GET       */
    #define NLM_F_DUMP  (NLM_F_ROOT|NLM_F_MATCH)
    
    /* Modifiers to NEW request */
    #define NLM_F_REPLACE   0x100   /* Override existing        */
    #define NLM_F_EXCL  0x200   /* Do not touch, if it exists   */
    #define NLM_F_CREATE    0x400   /* Create, if it does not exist */
    #define NLM_F_APPEND    0x800   /* Add to end of list       */
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34

    3、struct msghdr 结构体

    struct iovec {                    /* Scatter/gather array items */
         void  *iov_base;              /* Starting address */
         size_t iov_len;               /* Number of bytes to transfer */
     };
      /* iov_base: iov_base指向数据包缓冲区,即参数buff,iov_len是buff的长度。msghdr中允许一次传递多个buff,以数组的形式组织在 msg_iov中,msg_iovlen就记录数组的长度 (即有多少个buff)  */
     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 on received message */
     };
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14

    netlink 内核数据结构

    1、netlink消息类型:

    #define NETLINK_ROUTE       0   /* Routing/device hook              */
    #define NETLINK_UNUSED      1   /* Unused number                */
    #define NETLINK_USERSOCK    2   /* Reserved for user mode socket protocols  */
    #define NETLINK_FIREWALL    3   /* Unused number, formerly ip_queue     */
    #define NETLINK_SOCK_DIAG   4   /* socket monitoring                */
    #define NETLINK_NFLOG       5   /* netfilter/iptables ULOG */
    #define NETLINK_XFRM        6   /* ipsec */
    #define NETLINK_SELINUX     7   /* SELinux event notifications */
    #define NETLINK_ISCSI       8   /* Open-iSCSI */
    #define NETLINK_AUDIT       9   /* auditing */
    #define NETLINK_FIB_LOOKUP  10  
    #define NETLINK_CONNECTOR   11
    #define NETLINK_NETFILTER   12  /* netfilter subsystem */
    #define NETLINK_IP6_FW      13
    #define NETLINK_DNRTMSG     14  /* DECnet routing messages */
    #define NETLINK_KOBJECT_UEVENT  15  /* Kernel messages to userspace */
    #define NETLINK_GENERIC     16
    /* leave room for NETLINK_DM (DM Events) */
    #define NETLINK_SCSITRANSPORT   18  /* SCSI Transports */
    #define NETLINK_ECRYPTFS    19
    #define NETLINK_RDMA        20
    #define NETLINK_CRYPTO      21  /* Crypto layer */
    
    #define NETLINK_INET_DIAG   NETLINK_SOCK_DIAG
    
    #define MAX_LINKS 32
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26

    2、netlink常用宏:

    #define NLMSG_ALIGNTO   4U
    /* 宏NLMSG_ALIGN(len)用于得到不小于len且字节对齐的最小数值 */
    #define NLMSG_ALIGN(len) ( ((len)+NLMSG_ALIGNTO-1) & ~(NLMSG_ALIGNTO-1) )
    
    /* Netlink 头部长度 */
    #define NLMSG_HDRLEN     ((int) NLMSG_ALIGN(sizeof(struct nlmsghdr)))
    
    /* 计算消息数据len的真实消息长度(消息体 + 消息头)*/
    #define NLMSG_LENGTH(len) ((len) + NLMSG_HDRLEN)
    
    /* 宏NLMSG_SPACE(len)返回不小于NLMSG_LENGTH(len)且字节对齐的最小数值 */
    #define NLMSG_SPACE(len) NLMSG_ALIGN(NLMSG_LENGTH(len))
    
    /* 宏NLMSG_DATA(nlh)用于取得消息的数据部分的首地址,设置和读取消息数据部分时需要使用该宏 */
    #define NLMSG_DATA(nlh)  ((void*)(((char*)nlh) + NLMSG_LENGTH(0)))
    
    /* 宏NLMSG_NEXT(nlh,len)用于得到下一个消息的首地址, 同时len 变为剩余消息的长度 */
    #define NLMSG_NEXT(nlh,len)  ((len) -= NLMSG_ALIGN((nlh)->nlmsg_len), \
                      (struct nlmsghdr*)(((char*)(nlh)) + NLMSG_ALIGN((nlh)->nlmsg_len)))
    
    /* 判断消息是否 >len */
    #define NLMSG_OK(nlh,len) ((len) >= (int)sizeof(struct nlmsghdr) && \
                   (nlh)->nlmsg_len >= sizeof(struct nlmsghdr) && \
                   (nlh)->nlmsg_len <= (len))
    
    /* NLMSG_PAYLOAD(nlh,len) 用于返回payload的长度*/
    #define NLMSG_PAYLOAD(nlh,len) ((nlh)->nlmsg_len - NLMSG_SPACE((len)))
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27

    3、netlink 内核常用函数

    netlink_kernel_create内核函数用于创建内核socket与用户态通信
    
    static inline struct sock *
    netlink_kernel_create(struct net *net, int unit, struct netlink_kernel_cfg *cfg)
    /* net: net指向所在的网络命名空间, 一般默认传入的是&init_net(不需要定义);  定义在net_namespace.c(extern struct net init_net);
       unit:netlink协议类型
       cfg: cfg存放的是netlink内核配置参数(如下)
    */
    
    /* optional Netlink kernel configuration parameters */
    struct netlink_kernel_cfg {
        unsigned int    groups;  
        unsigned int    flags;  
        void        (*input)(struct sk_buff *skb); /* input 回调函数 */
        struct mutex    *cb_mutex; 
        void        (*bind)(int group); 
        bool        (*compare)(struct net *net, struct sock *sk);
    };
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18

    4、单播netlink_unicast() 和 多播netlink_broadcast()

    /* 发送单播消息 */
    extern int netlink_unicast(struct sock *ssk, struct sk_buff *skb, __u32 portid, int nonblock);
    /*
     ssk: netlink socket 
     skb: skb buff 指针
     portid: 通信的端口号
     nonblock:表示该函数是否为非阻塞,如果为1,该函数将在没有接收缓存可利用时立即返回,而如果为0,该函数在没有接收缓存可利用定时睡眠
    */
    
    /* 发送多播消息 */
    extern int netlink_broadcast(struct sock *ssk, struct sk_buff *skb, __u32 portid,
                     __u32 group, gfp_t allocation);
    /* 
       ssk: 同上(对应netlink_kernel_create 返回值)、
       skb: 内核skb buff
       portid: 端口id
       group: 是所有目标多播组对应掩码的"OR"操作的合值。
       allocation: 指定内核内存分配方式,通常GFP_ATOMIC用于中断上下文,而GFP_KERNEL用于其他场合。这个参数的存在是因为该API可能需要分配一个或多个缓冲区来对多播消息进行clone
    */
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19

    netlink example

    用户态程序: (sendto(), recvfrom())

    #include 
    #include 
    #include 
    #include 
    #include 
    #include 
    #include 
    #include 
    
    #define NETLINK_TEST    30
    #define MSG_LEN            125
    #define MAX_PLOAD        125
    
    typedef struct _user_msg_info
    {
        struct nlmsghdr hdr;
        char  msg[MSG_LEN];
    } user_msg_info;
    
    int main(int argc, char **argv)
    {
        int skfd;
        int ret;
        user_msg_info u_info;
        socklen_t len;
        struct nlmsghdr *nlh = NULL;
        struct sockaddr_nl saddr, daddr;
        char *umsg = "hello netlink!!";
    
        /* 创建NETLINK socket */
        skfd = socket(AF_NETLINK, SOCK_RAW, NETLINK_TEST);
        if(skfd == -1)
        {
            perror("create socket error\n");
            return -1;
        }
    
        memset(&saddr, 0, sizeof(saddr));
        saddr.nl_family = AF_NETLINK; //AF_NETLINK
        saddr.nl_pid = 100;  //端口号(port ID) 
        saddr.nl_groups = 0;
        if(bind(skfd, (struct sockaddr *)&saddr, sizeof(saddr)) != 0)
        {
            perror("bind() error\n");
            close(skfd);
            return -1;
        }
    
        memset(&daddr, 0, sizeof(daddr));
        daddr.nl_family = AF_NETLINK;
        daddr.nl_pid = 0; // to kernel 
        daddr.nl_groups = 0;
    
        nlh = (struct nlmsghdr *)malloc(NLMSG_SPACE(MAX_PLOAD));
        memset(nlh, 0, sizeof(struct nlmsghdr));
        nlh->nlmsg_len = NLMSG_SPACE(MAX_PLOAD);
        nlh->nlmsg_flags = 0;
        nlh->nlmsg_type = 0;
        nlh->nlmsg_seq = 0;
        nlh->nlmsg_pid = saddr.nl_pid; //self port
    
        memcpy(NLMSG_DATA(nlh), umsg, strlen(umsg));
        ret = sendto(skfd, nlh, nlh->nlmsg_len, 0, (struct sockaddr *)&daddr, sizeof(struct sockaddr_nl));
        if(!ret)
        {
            perror("sendto error\n");
            close(skfd);
            exit(-1);
        }
        printf("send kernel:%s\n", umsg);
    
        memset(&u_info, 0, sizeof(u_info));
        len = sizeof(struct sockaddr_nl);
        ret = recvfrom(skfd, &u_info, sizeof(user_msg_info), 0, (struct sockaddr *)&daddr, &len);
        if(!ret)
        {
            perror("recv form kernel error\n");
            close(skfd);
            exit(-1);
        }
    
        printf("from kernel:%s\n", u_info.msg);
        close(skfd);
    
        free((void *)nlh);
        return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87

    Netlink 内核模块代码

    下面代码中这里的这里是关键,注册了回调函数来响应消息
    struct netlink_kernel_cfg cfg = {
    .input = netlink_rcv_msg, /* set recv callback */
    };

    #include 
    #include 
    #include 
    #include 
    #include 
    
    #define NETLINK_TEST     30
    #define MSG_LEN            125
    #define USER_PORT        100
    
    MODULE_LICENSE("GPL");
    MODULE_AUTHOR("zhangwj");
    MODULE_DESCRIPTION("netlink example");
    
    struct sock *nlsk = NULL;
    extern struct net init_net;
    
    int send_usrmsg(char *pbuf, uint16_t len)
    {
        struct sk_buff *nl_skb;
        struct nlmsghdr *nlh;
    
        int ret;
    
        /* 创建sk_buff 空间 */
        nl_skb = nlmsg_new(len, GFP_ATOMIC);
        if(!nl_skb)
        {
            printk("netlink alloc failure\n");
            return -1;
        }
    
        /* 设置netlink消息头部 */
        nlh = nlmsg_put(nl_skb, 0, 0, NETLINK_TEST, len, 0);
        if(nlh == NULL)
        {
            printk("nlmsg_put failaure \n");
            nlmsg_free(nl_skb);
            return -1;
        }
    
        /* 拷贝数据发送 */
        memcpy(nlmsg_data(nlh), pbuf, len);
        ret = netlink_unicast(nlsk, nl_skb, USER_PORT, MSG_DONTWAIT);
    
        return ret;
    }
    
    static void netlink_rcv_msg(struct sk_buff *skb)
    {
        struct nlmsghdr *nlh = NULL;
        char *umsg = NULL;
        char *kmsg = "hello users!!!";
    
        if(skb->len >= nlmsg_total_size(0))
        {
            nlh = nlmsg_hdr(skb);
            umsg = NLMSG_DATA(nlh);
            if(umsg)
            {
                printk("kernel recv from user: %s\n", umsg);
                send_usrmsg(kmsg, strlen(kmsg));
            }
        }
    }
    
    struct netlink_kernel_cfg cfg = { 
            .input  = netlink_rcv_msg, /* set recv callback */
    };  
    
    int test_netlink_init(void)
    {
        /* create netlink socket */
        nlsk = (struct sock *)netlink_kernel_create(&init_net, NETLINK_TEST, &cfg);
        if(nlsk == NULL)
        {   
            printk("netlink_kernel_create error !\n");
            return -1; 
        }   
        printk("test_netlink_init\n");
        
        return 0;
    }
    
    void test_netlink_exit(void)
    {
        if (nlsk){
            netlink_kernel_release(nlsk); /* release ..*/
            nlsk = NULL;
        }   
        printk("test_netlink_exit!\n");
    }
    
    module_init(test_netlink_init);
    module_exit(test_netlink_exit);
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95

    Makeflie

    #
    #Desgin of Netlink
    #
    
    MODULE_NAME :=netlink_test
    obj-m :=$(MODULE_NAME).o
    
    KERNELDIR ?= /lib/modules/$(shell uname -r)/build
    PWD := $(shell pwd)
    
    all:
        $(MAKE) -C $(KERNELDIR) M=$(PWD)
    
    clean:
        $(MAKE) -C $(KERNELDIR) M=$(PWD) clean
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15

    运行结果:

    记得首先将编译出来的Netlink内核模块插入到系统当中(insmod netlink_test.ko),然后运行应用程序,可以看到如下输出:

    # 应用程序打印
    send kernel:hello netlink!!
    from kernel:hello users!!!
    # 内核打印 
    [25024.276345] test_netlink_init
    [25117.548350] kernel recv from user: hello netlink!!
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
  • 相关阅读:
    lwIP 开发指南(下)
    .NET Worker Service 作为 Windows 服务运行及优雅退出改进
    【深度学习】attention机制
    【题解】洛谷 P9658 Laser Trap
    数字化转型专题汇总
    DevOps 装配线如何加速流水线移动
    新版校园跑腿外卖独立版+APP+小程序前端外卖配送平台源码
    进程与线程的区别
    Oracle 客户体验 (Oracle CX) 怎么样?
    【04】Istio的pilot流量分发机制
  • 原文地址:https://blog.csdn.net/qq_43684922/article/details/126316902