美文网首页
初试Netlink之unicast

初试Netlink之unicast

作者: 网路元素 | 来源:发表于2019-10-03 00:46 被阅读0次

           在之前已经了解了好几种内核态与用户态之间数据的交换方法,但那些方法都是单向的(即单工),其关系是一对一的,而Netlink Socket则是一种用于内核与用户空间之间传递信息的特殊IPC,在用户进程中,其以标准的Socket API为内核和用户之间提供了全双工的通信通道,在内核模块中则提供了一类特殊的API。相比TCP/IP socket所使用的AF_INET地址族,Netlink socket则使用AF_NETLINK地址族,每一个Netlink Socket都在Kernel的include/uapi/linux/netlink.h(linux-3.11.0-rc4,关于Netlink部分相关文档,如无注明均以该版本为基准)中定义了相应的protocol type。

            在Linux  Kernel中,以模块形式去创建Netlink Socket时需要涉及到如下相关的API及数据结构:

            1.创建Socket

               在include/linux/netlink.h文件中,有如下的定义:

    /* optional Netlink kernel configuration parameters */
    struct netlink_kernel_cfg {
            unsigned int    groups;
            unsigned int    flags;
            void            (*input)(struct sk_buff *skb);
            struct mutex    *cb_mutex;
            void            (*bind)(int group);
            bool            (*compare)(struct net *net, struct sock *sk);
    };

    extern struct sock *__netlink_kernel_create(struct net *net, int unit,
                    struct module *module,
                    struct netlink_kernel_cfg *cfg);

    static inline struct sock *
    netlink_kernel_create(struct net *net, int unit, struct netlink_kernel_cfg *cfg)
    {
            return __netlink_kernel_create(net, unit, THIS_MODULE, cfg);
    }

               其中,netlink_kernel_create的第一个参数net一般使用&init_net这个全局量;第二个参数unit可使用include/uapi/linux/netlink.h文件中以NETLINK_开头的宏定义,其对应Netlink支持的协议类型,也可以是自定义的类型(可自行添加相应的宏定义,等会的例子里会使用),而cfg对应上面的可选配置参数结构体(等会例子会使用到input,用于接收到数据时的回调处理,其对应的skb为接收到的数据对应的结构体,其他量在后期使用时再说明)。

            2.数据处理

               当上面的socket创建好后,其就会进行监听对应协议的数据,当收到数据时会调用上面的input回调函数,而其参数skb为接收到的struct sk_buff结构体,使用include/linux/netlink.h的如下函数获取struct nlmsghdr结构体指针值:

    static inline struct nlmsghdr *nlmsg_hdr(const struct sk_buff *skb)
    {
            return (struct nlmsghdr *)skb->data;
    }

               而struct nlmsghdr结构体在include/uapi/linux/netlink.h文件中有如下声明:

    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 */
    };

               通过同一文件的如下宏:

    #define NLMSG_ALIGNTO   4U
    #define NLMSG_ALIGN(len) ( ((len)+NLMSG_ALIGNTO-1) & ~(NLMSG_ALIGNTO-1) )
    #define NLMSG_HDRLEN     ((int) NLMSG_ALIGN(sizeof(struct nlmsghdr)))
    #define NLMSG_LENGTH(len) ((len) + NLMSG_HDRLEN)
    #define NLMSG_SPACE(len) NLMSG_ALIGN(NLMSG_LENGTH(len))
    #define NLMSG_DATA(nlh)  ((void*)(((char*)nlh) + NLMSG_LENGTH(0)))
    #define NLMSG_NEXT(nlh,len)      ((len) -= NLMSG_ALIGN((nlh)->nlmsg_len), \
                    (struct nlmsghdr*)(((char*)(nlh)) + NLMSG_ALIGN((nlh)->nlmsg_len)))

    #define NLMSG_OK(nlh,len) ((len) >= (int)sizeof(struct nlmsghdr) && \
                    (nlh)->nlmsg_len >= sizeof(struct nlmsghdr) && \
                    (nlh)->nlmsg_len <= (len))

    #define NLMSG_PAYLOAD(nlh,len) ((nlh)->nlmsg_len - NLMSG_SPACE((len)))

               我们可以按需获得相应的参数数据,通过NLMSG_DATA宏即可获得接收到的数据内容。上述内容我们可以从获取到的sk_buff提取nlmsghdr后利用NLMSG_DATA宏来获取接收的数据内容,接下来我们看下如何构建要发送的数据和操作:

               通过include/net/netlink.h文件中的如下函数:

    static inline struct sk_buff *nlmsg_new(size_t payload, gfp_t flags)
    {
            return alloc_skb(nlmsg_total_size(payload), flags);
    }

    static inline struct nlmsghdr *nlmsg_put(struct sk_buff *skb, u32 portid, u32 seq,
                    int type, int payload, int flags)
    {
            if (unlikely(skb_tailroom(skb) < nlmsg_total_size(payload)))
                    return NULL;

            return __nlmsg_put(skb, portid, seq, type, payload, flags);
    }

               来构建要发送的sk_buff和填充相应的nlmsghdr数据,最终使用include/linux/netlink.h中的如下函数:

               extern int netlink_unicast(struct sock *ssk, struct sk_buff *skb, __u32 portid, int nonblock);

               或include/net/netlink.h中的如下函数:

    static inline int nlmsg_unicast(struct sock *sk, struct sk_buff *skb, u32 portid)
    {
            int err;
            err = netlink_unicast(sk, skb, portid, MSG_DONTWAIT);
            if (err > 0)
                    err = 0;

            return err;
    }

               进行单播发送数据,在接下来的例子我们调用nlmsg_put时会设置portid为0,该处表示发送进程的端口号(因为是内核,默认设置为0,会与应用层处配置对应),而上面的netlink_unicast的portid为接收进程的端口号(下面的实例会使用传进来的pid),注意这两者的区别。

            上面了解了Kernel部分的操作后,下面了解下用户态应用程序的处理所需:

            1.创建及绑定socket

                使用int socket(int domain, int type, int protocol)函数来创建一个socket,其中domain为AF_NETLINK,type只能为SOCK_RAW或SOCK_DGRAM(因为Netlink是基于数据报的传输,类似UDP),protocol为使用到的Netlink Socket协议(可以是自定义的,在下面例子会使用自定义的)。

               在创建好socket后需要将其绑定bind(fd, (struct sockaddr*)&nladdr, sizeof(nladdr),其中fd为上面socket的返回值,而nladdr为struct sockaddr_nl结构体类型数据,其在include/uapi/linux/netlink.h文件中有如下声明:

    struct sockaddr_nl {
            __kernel_sa_family_t    nl_family;      /* AF_NETLINK   */
            unsigned short  nl_pad;         /* zero         */
            __u32           nl_pid;         /* port ID      */
            __u32           nl_groups;      /* multicast groups mask */
    };

               最终会将其转换为sockaddr类型的指针,相应的在include/linux/socket.h文件中有如下声明:

    struct sockaddr {
            sa_family_t     sa_family;      /* address family, AF_xxx       */
            char            sa_data[14];    /* 14 bytes of protocol address */
    };

            2.数据处理

               在上面创建好socket和绑定好本地信息后,接下来要构建发送给接收端的struct msghdr结构体数据,该结构体在include/linux/socket.h有如下声明:

    struct msghdr {
            void    *       msg_name;       /* Socket name                  */
            int             msg_namelen;    /* Length of name               */
            struct iovec *  msg_iov;        /* Data blocks                  */
            __kernel_size_t msg_iovlen;     /* Number of blocks             */
            void    *       msg_control;    /* Per protocol magic (eg BSD file descriptor passing) */
            __kernel_size_t msg_controllen; /* Length of cmsg list */
            unsigned int    msg_flags;
    };

               对于该结构体,我们需要填充msg_name和msg_iov,具体在下面实例中可以看到填充什么数据,其中iovec结构体在include/uapi/linux/uio.h文件中有声明:

    struct iovec
    {
            void __user *iov_base;  /* BSD uses caddr_t (1003.1g requires void *) */
            __kernel_size_t iov_len; /* Must be size_t (1003.1g) */
    };

               在构建好数据后,我们使用如下函数进行数据收发:

               ssize_t sendmsg(int sockfd, const struct msghdr *msg, int flags);
               ssize_t recvmsg(int sockfd, struct msghdr *msg, int flags);

            在上述分析后,接下来我们实例感受下通过Netlink Socket进行内核态与用户态之间进行通信的过程:

            对于Kernel部分netlink_unicast.c的源码如下:

    #include <linux/init.h>
    #include <linux/module.h>
    #include <net/sock.h>
    #include <net/netlink.h>

    #define NETLINK_TEST 25
    #define MAX_PAYLOAD_SIZE 1024

    struct sock *nl_sk = NULL;

    void sendnlmsg(int pid)
    {
        struct sk_buff *skb;
        struct nlmsghdr *nlh;
        char msg[30] = "Say hello from kernel!";

        if(!nl_sk){
            return;
        }

        skb = nlmsg_new(MAX_PAYLOAD_SIZE, GFP_KERNEL);
        if(!skb){
            printk(KERN_ERR "nlmsg_new error!\n");
        }

        nlh = nlmsg_put(skb, 0, 0, 0, MAX_PAYLOAD_SIZE, 0);
        memcpy(NLMSG_DATA(nlh), msg, sizeof(msg));
        printk("Send message '%s'.\n",(char *)NLMSG_DATA(nlh));
        netlink_unicast(nl_sk, skb, pid, MSG_DONTWAIT);
    }

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

        skb = skb_get (__skb);
        if(skb->len >= NLMSG_SPACE(0)){
            nlh = nlmsg_hdr(skb);
            memcpy(str, NLMSG_DATA(nlh), sizeof(str));
            printk("Message received:%s\n",str) ;
            sendnlmsg(nlh->nlmsg_pid);
            kfree_skb(skb);
        }
    }

    static int netlink_unicast_init(void)
    {
        struct netlink_kernel_cfg netlink_kerncfg = {
            .input = nl_data_ready,
        };
        nl_sk = netlink_kernel_create(&init_net, NETLINK_TEST, &netlink_kerncfg);
        if(!nl_sk){
            printk(KERN_ERR "netlink_unicast_init: Create netlink socket error.\n");
            return -1;
        }
        printk("netlink_unicast_init: Create netlink socket ok.\n");
        return 0;
    }

    static void netlink_unicast_exit(void)
    {
        if(nl_sk != NULL){
            sock_release(nl_sk->sk_socket);
        }
        printk("netlink_unicast_exit!\n");
    }

    module_init(netlink_unicast_init);
    module_exit(netlink_unicast_exit);

    MODULE_AUTHOR("X-SLAM XINU");
    MODULE_LICENSE("GPL");

            相应的Makefile内容如下:

    obj-m += netlink_unicast.o

    CUR_PATH:=$(shell pwd)
    LINUX_KERNEL_PATH:=/home/guochongxin/xinu/linux-stable/

    all:
            make -C $(LINUX_KERNEL_PATH) M=$(CUR_PATH) modules

    clean:
            make -C $(LINUX_KERNEL_PATH) M=$(CUR_PATH) clean

            而对于用户层应用程序netlink_unicast_app.c文件的源码如下:

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

    #define NETLINK_TEST 25
    #define MAX_PAYLOAD_SIZE 1024 // maximum payload size

    int main(int argc, char* argv[])
    {
        int state;
        struct sockaddr_nl src_addr, dest_addr;
        struct nlmsghdr *nlh = NULL;
        struct iovec iov;
        struct msghdr msg;

        int sock_fd, retval;
        int state_smg = 0;

        // Create a socket
        sock_fd = socket(AF_NETLINK, SOCK_RAW, NETLINK_TEST);
        if(sock_fd == -1){
            printf("error getting socket: %s", strerror(errno));
            return -1;
        }

        // To prepare binding
        memset(&msg,0,sizeof(msg));
        memset(&src_addr, 0, sizeof(src_addr));
        src_addr.nl_family = AF_NETLINK;
        src_addr.nl_pid = getpid();
        src_addr.nl_groups = 0;

        retval = bind(sock_fd, (struct sockaddr*)&src_addr, sizeof(src_addr));
        if(retval < 0){
            printf("bind failed: %s", strerror(errno));
            close(sock_fd);
            return -1;
        }

        // To prepare recvmsg
        nlh = (struct nlmsghdr *)malloc(NLMSG_SPACE(MAX_PAYLOAD_SIZE));
        if(!nlh){
            printf("malloc nlmsghdr error!\n");
            close(sock_fd);
            return -1;
        }

        memset(&dest_addr,0,sizeof(dest_addr));
        dest_addr.nl_family = AF_NETLINK;
        dest_addr.nl_pid = 0; /* For Linux Kernel */
        dest_addr.nl_groups = 0; /* Unicast */
        nlh->nlmsg_len = NLMSG_SPACE(MAX_PAYLOAD_SIZE);
        nlh->nlmsg_pid = getpid();
        nlh->nlmsg_flags = 0;

        strcpy(NLMSG_DATA(nlh), "Say hello from user application!");
        iov.iov_base = (void *)nlh;
        iov.iov_len = NLMSG_SPACE(MAX_PAYLOAD_SIZE);
        memset(&msg, 0, sizeof(msg));
        msg.msg_name = (void *)&dest_addr;
        msg.msg_namelen = sizeof(dest_addr);
        msg.msg_iov = &iov;
        msg.msg_iovlen = 1;

        state_smg = sendmsg(sock_fd, &msg, 0);
        if(state_smg == -1){
            printf("get error sendmsg = %s\n",strerror(errno));
        }

        memset(nlh, 0, NLMSG_SPACE(MAX_PAYLOAD_SIZE));
        printf("waiting received!\n");

        // Read message from kernel
        state = recvmsg(sock_fd, &msg, 0);
        if(state < 0){
            printf("recvmsg state < 1");
        }

        printf("Received message: %s\n",(char *) NLMSG_DATA(nlh));
        close(sock_fd);

        return 0;
    }

            相应的Makefile文件内容如下:

    all:
        gcc -o netlink_unicast_app netlink_unicast_app.c

    clean:
        rm -rf *.o netlink_unicast_app

            最终的源码文件目录树如下:

    /home/guochongxin/xinu/xinu/linux_kernel_driver_l1/netlink_unicast/
    ├── kernel_module
    │   ├── Makefile
    │   └── netlink_unicast.c
    └── user_application
    ├── Makefile
    └── netlink_unicast_app.c

            当我们先编译加载内核模块sudo insmod netlink_unicast.ko时,dmesg命令有如下输出:

            netlink_unicast_init: Create netlink socket ok.

            当我们运行应用程序./netlink_unicast_app时,Terminal有如下输出:

    waiting received!
    Received message: Say hello from kernel!

            而当我们再使用dmesg命令查看时会有如下输出:

    Message received:Say hello from user application!
    Send message 'Say hello from kernel!'.

            至此,使用Netlink Socket的Unicast方式通信能正常使用了,以后内核态与用户态之间自由交换意见就更容易了,接下来我们会讲到Broadcast等方式的通信,静候更新。

    参考网址:

    http://www.cnblogs.com/D3Hunter/p/3207670.html
    http://blog.csdn.net/wangpengqi/article/details/9969599
    http://www.cnblogs.com/hoys/archive/2011/04/09/2010788.html
    http://binwaheed.blogspot.com/2010/08/after-reading-kernel-source-i-finally.html
    http://ycool.com/post/by6vz7j
    http://blog.csdn.net/vichie2008/article/details/37568065
    http://www.linuxjournal.com/article/7356?page=0,3

    相关文章

      网友评论

          本文标题:初试Netlink之unicast

          本文链接:https://www.haomeiwen.com/subject/lzqryctx.html