Linux内核和用户空间通信之netlink

1. netlink

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

Netlink 是一种特殊的 socket,它是 Linux 所特有的,类似于 BSD 中的AF_ROUTE 但又远比它的功能强大,目前在Linux 内核中使用netlink 进行应用与内核通信的应用很多; 包括:路由 daemon(NETLINK_ROUTE),用户态 socket 协议(NETLINK_USERSOCK),

防火墙(NETLINK_FIREWALL),netfilter 子系统(NETLINK_NETFILTER),内核事件向用户态通知(NETLINK_KOBJECT_UEVENT),通用 netlink(NETLINK_GENERIC)等。

Netlink 是一种在内核与用户应用间进行双向数据传输的非常好的方式,用户态应用使用标准的 socket API 就可以使用 netlink 提供的强大功能,内核态需要使用专门的内核 API 来使用 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 首先发起会话;

 

2.1 netlink相关数据结构

struct net网络设备命名空间指针。

struct sock是套接口在网络层的表示。

struct sk_buff结构是Linux网络代码中重要的数据结构,它管理和控制接收或发送数据包的信息。

struct sockaddr_nl是netlink通信地址。

struct netlink_kernel_cfg netlink的配置结构体。

struct nlmsghdr是netlink提供的协议头,netlink协议是面向消息的,需要定义自己的协议。自定义协议按照协议头格式填充协议头内容,并定义自己的payload,通常自定义的协议体包含自定义协议头与额外的属性。

复制代码

struct net {
    refcount_t        passive;    /* To decided when the network
                         * namespace should be freed.
                         */
    atomic_t        count;        /* To decided when the network
                         *  namespace should be shut down.
                         */
    spinlock_t        rules_mod_lock;

    atomic64_t        cookie_gen;
...
} __randomize_layout;

struct sock {
    /*
     * Now struct inet_timewait_sock also uses sock_common, so please just
     * don't add nothing before this first member (__sk_common) --acme
     */
    struct sock_common    __sk_common;
    socket_lock_t        sk_lock;
    atomic_t        sk_drops;
    int            sk_rcvlowat;
    struct sk_buff_head    sk_error_queue;
    struct sk_buff_head    sk_receive_queue;
...
};

struct sk_buff {
...
    struct sock        *sk;
...
    /* private: */
    __u32            headers_end[0];
    /* public: */

    /* These elements must be at the end, see alloc_skb() for details.  */
    sk_buff_data_t        tail;
    sk_buff_data_t        end;
    unsigned char        *head,
                *data;
    unsigned int        truesize;
    refcount_t        users;
};

struct nlmsghdr {
    __u32        nlmsg_len;    /* Length of message including header */----------整个netlink消息的长度,包含消息头。
    __u16        nlmsg_type;    /* Message content */----------------------------消息状态,内核在include/uapi/linux/netlink.h中定义以下4种通用的消息类型。
    __u16        nlmsg_flags;    /* Additional flags */--------------------------消息标记,它们泳衣表示消息的类型。
    __u32        nlmsg_seq;    /* Sequence number */-----------------------------消息序列号,用以将消息排队有些类似TCP协议中的序号(不完全一样),但是netlink的这个字段是可选的,不强制使用。
    __u32        nlmsg_pid;    /* Sending process port ID */---------------------发送端口的ID号,对于内核来说该值就是0,对于用户进程来说就是其socket所绑定的ID号。
};

struct netlink_kernel_cfg {
    unsigned int    groups;
    unsigned int    flags;
    void        (*input)(struct sk_buff *skb);-----------------------------------input回调函数
    struct mutex    *cb_mutex;
    int        (*bind)(struct net *net, int group);
    void        (*unbind)(struct net *net, int group);
    bool        (*compare)(struct net *net, struct sock *sk);
};

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

复制代码

下面是nlmsg_type和nlmsg_flags宏对应解释:

复制代码

nlmsg_type:

#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        0x01    /* It is request message.     */
#define NLM_F_MULTI        0x02    /* Multipart message, terminated by NLMSG_DONE */
#define NLM_F_ACK        0x04    /* Reply with ack, with zero or error code */
#define NLM_F_ECHO        0x08    /* Echo this request         */
#define NLM_F_DUMP_INTR        0x10    /* Dump was inconsistent due to sequence change */
#define NLM_F_DUMP_FILTERED    0x20    /* Dump was filtered as requested */

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

复制代码

系统预定义了一系列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_SMC        22    /* SMC monitoring */

#define NETLINK_INET_DIAG    NETLINK_SOCK_DIAG

#define MAX_LINKS 32        

复制代码

netlink如下些常用宏:

复制代码

#define NLMSG_ALIGNTO    4U
#define NLMSG_ALIGN(len) ( ((len)+NLMSG_ALIGNTO-1) & ~(NLMSG_ALIGNTO-1) )------------------用于得到不小于len且字节对齐的最小数值
#define NLMSG_HDRLEN     ((int) NLMSG_ALIGN(sizeof(struct nlmsghdr)))----------------------netlink头部长度
#define NLMSG_LENGTH(len) ((len) + NLMSG_HDRLEN)-------------------------------------------计算消息数据len的真实消息长度,消息体+消息头
#define NLMSG_SPACE(len) NLMSG_ALIGN(NLMSG_LENGTH(len))------------------------------------返回不小于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)))------用于得到下一个消息的首地址,同时len变为剩余消息的长度
#define NLMSG_OK(nlh,len) ((len) >= (int)sizeof(struct nlmsghdr) && \
               (nlh)->nlmsg_len >= sizeof(struct nlmsghdr) && \
               (nlh)->nlmsg_len <= (len))--------------------------------------------------判断消息是否>len
#define NLMSG_PAYLOAD(nlh,len) ((nlh)->nlmsg_len - NLMSG_SPACE((len)))---------------------用于返回payload的长度

复制代码

 

2.2 netlink相关API

内核通过netlink_kernel_create()/netlink_kernel_release() 创建/销毁struct sock:

复制代码

static inline struct sock *
netlink_kernel_create(struct net *net, int unit, struct netlink_kernel_cfg *cfg)

net:net指向所在的网络命名空间,一般默认传入的是&init_net,不需要定义;定义在net_namespace.c中。
unit:netlink协议类型。
cfg:cfg存放的是netlink内核配置参数struct netlink_kernel_cfg数据结构。

void netlink_kernel_release(struct sock *sk)
sk:释放netlink_kernel_create()创建的sock。

复制代码

下面是netlink消息常用API: 

复制代码

int netlink_unicast(struct sock *ssk, struct sk_buff *skb,
            u32 portid, int nonblock)-----------------------------------------------用来发送单播信息。
ssk:netlink socket
skb:skb buff指针
portid:通信端口号
nonblock:表示该函数是否为非阻塞,如果为1,该函数将在没有接收缓存可利用时立即返回;如果为0,该函数在没有接收缓存可利用时定是睡眠。

int netlink_broadcast(struct sock *ssk, struct sk_buff *skb, u32 portid,
              u32 group, gfp_t allocation)------------------------------------------用来发送多播信息。
ssk:netlink socket,netlink_kernel_create()返回值
skb:内核skb buff
portid:通信端口号
group:是所有目标多播组对应掩码的OR操作的合值
allocation:指定内核内存分配方式,通常GFP_ATOMIC用于中断上下文,而GFP_KERNEL用于其他场合。这个参数的存在是因为该API可能需要分配一个或多个缓冲区来对多播消息进行clone。

  static inline struct nlmsghdr *nlmsg_hdr(const struct sk_buff *skb)----------------从sk_buff->data获取struct nlmsghdr数据结构。
  {
      return (struct nlmsghdr *)skb->data;
  }

static inline struct sk_buff *nlmsg_new(size_t payload, gfp_t flags)---------------创建len大小的struct sk_buff。
{
    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)----------------------------将一个新的netlink消息加入到skb中。如果skb无法存放消息则返回NULL。

static inline void nlmsg_free(struct sk_buff *skb)--------------------------------释放nlmsg_new()创建的skb。
{
    kfree_skb(skb);
}

static inline void *nlmsg_data(const struct nlmsghdr *nlh)------------------------根据nlmsghdr指针获取对应的payload。
{
    return (unsigned char *) nlh + NLMSG_HDRLEN;
}

static inline struct nlmsghdr *nlmsg_next(const struct nlmsghdr *nlh, int *remaining)---获取消息流中下一个netlink消息。

static inline void nlmsg_end(struct sk_buff *skb, struct nlmsghdr *nlh)

复制代码

用户空间相关socket操作API有: 

复制代码

int socket(int domain, int type, int protocol);------------------该函数用来创建一个套接字,并返回一个描述符,该描述符可以用来访问该套接字。protocol参数设置为0表示使用默认协议。

int bind( int socket, const struct sockaddr *address, size_t address_len);--把通过socket()创建的套接字命名,从而让它可以被其他进程使用。

int sendto(int sockfd, void *buffer, size_t len, int flags, struct sockaddr *to, socklen_t tolen);----把缓冲区buffer中的信息送给制定的IP端口程序,buffer存放将要发送的数据,len是buffer长度,to是要发送数据到的程序IP端口,tolen是to参数长度。

int recvfrom(int sockfd, void *buffer, size_t len,int flags, struct sockaddr *src_from, socklen_t *src_len); --把发送给程序的信息存储在缓冲区buffer中,并记录数据来源的程序IP端口。buffer存放接收的数据,len是buffer长度,src_from是数据来源程序IP端口,src_len是src_from长度。

复制代码

 

2.3 netlink实例 

对netlink的测试分为两部分,一部分是内核提供接收用户空间消息,并响应发送功能;另一部分是用户空间发送netlink到内核,并等待回复。

2.3.1 内核netlink测试代码

新建netlink_kernel.c文件:

复制代码

#include 
#include 
#include 
#include 
#include 

#define NETLINK_TEST     30
#define USER_PORT        100
int netlink_count = 0;
char netlink_kmsg[30];

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;

    //Create sk_buff using nlmsg_new().
    nl_skb = nlmsg_new(len, GFP_ATOMIC);
    if(!nl_skb)
    {
        printk("netlink alloc failure\n");
        return -1;
    }

    //Set up nlmsghdr.
    nlh = nlmsg_put(nl_skb, 0, 0, NETLINK_TEST, len, 0);
    if(nlh == NULL)
    {
        printk("nlmsg_put failaure \n");
        nlmsg_free(nl_skb);  //If nlmsg_put() failed, nlmsg_free() will free sk_buff.
        return -1;
    }

    //Copy pbuf to nlmsghdr payload.
    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!!!";
    char *kmsg;

    if(skb->len >= nlmsg_total_size(0))
    {
        netlink_count++;
    snprintf(netlink_kmsg, sizeof(netlink_kmsg), "hello users count=%d", netlink_count);
    kmsg = netlink_kmsg;
        nlh = nlmsg_hdr(skb);  //Get nlmsghdr from sk_buff.
        umsg = NLMSG_DATA(nlh); //Get payload from nlmsghdr.
        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 */
};  

__init int netlink_test_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("netlink_test_init\n");
    
    return 0;
}

__exit void netlink_test_exit(void)
{
    if (nlsk){
        netlink_kernel_release(nlsk); /* release ..*/
        nlsk = NULL;
    }   
    printk("netlink_test_exit!\n");
}

module_init(netlink_test_init);
module_exit(netlink_test_exit);

MODULE_LICENSE("GPL");
MODULE_DESCRIPTION("netlink test");

复制代码

 

View Code

Makefile如下:

复制代码

MODULE_NAME :=netlink_kernel
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

复制代码

 

2.3.2 用户空间netlink测试代码

新建netlink_user.c文件:

复制代码

#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 

#define NETLINK_TEST    30
#define USER_PORT    100
#define MAX_PLOAD    125
#define MSG_LEN        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!!";
    int loop_count = 0;

    /*Create netlink socket*/
    skfd = socket(AF_NETLINK, SOCK_RAW, NETLINK_TEST); //Create a socket using user defined protocol NETLINK_TEST.
    if(skfd == -1)
    {
        perror("create socket error\n");
        return -1;
    }

    //Source address.
    memset(&saddr, 0, sizeof(saddr));
    saddr.nl_family = AF_NETLINK; //AF_NETLINK
    saddr.nl_pid = USER_PORT;  //netlink portid, same as kernel.
    saddr.nl_groups = 0;
    if(bind(skfd, (struct sockaddr *)&saddr, sizeof(saddr)) != 0) //bind to skfd with saddr.
    {
        perror("bind() error\n");
        close(skfd);
        return -1;
    }

    //Destination address.
    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));
    while(loop_count < 11) {
        printf("sendto kernel:%s\n", 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);
        }

        //Receive netlink message from kernel.
        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);
    //usleep(1000);
    loop_count++;
    }
    close(skfd);

    free((void *)nlh);
    return 0;
}

复制代码

 

View Code

然后通过gcc netlink_user.c -o netlink_user编译。

2.3.3 测试结果

将编译的内核module通过sudo insmod netlink_kernel.kl加载到内核。

然后执行./netlink_user,得到如下结果。

复制代码

sendto kernel:hello netlink!!
from kernel:hello users count=1
sendto kernel:hello netlink!!
from kernel:hello users count=2
sendto kernel:hello netlink!!
from kernel:hello users count=3
sendto kernel:hello netlink!!
from kernel:hello users count=4
sendto kernel:hello netlink!!
from kernel:hello users count=5
sendto kernel:hello netlink!!
from kernel:hello users count=6
sendto kernel:hello netlink!!
from kernel:hello users count=7
sendto kernel:hello netlink!!
from kernel:hello users count=8
sendto kernel:hello netlink!!
from kernel:hello users count=9
sendto kernel:hello netlink!!
from kernel:hello users count=10
sendto kernel:hello netlink!!
from kernel:hello users count=11

复制代码

通过dmesg获取内核log如下:

复制代码

[16924.072556] netlink_test_init
[16926.909889] kernel recv from user: hello netlink!!
[16926.909919] kernel recv from user: hello netlink!!
[16926.909940] kernel recv from user: hello netlink!!
[16926.909960] kernel recv from user: hello netlink!!
[16926.909980] kernel recv from user: hello netlink!!
[16926.910002] kernel recv from user: hello netlink!!
[16926.910021] kernel recv from user: hello netlink!!
[16926.910048] kernel recv from user: hello netlink!!
[16926.910072] kernel recv from user: hello netlink!!
[16926.910089] kernel recv from user: hello netlink!!
[16926.910114] kernel recv from user: hello netlink!!

复制代码

可以看出从用户发送->内核接收->内核响应->用户接收,整个流程耗时均值在(16926.910114 - 16926.909889) / 10 = 22.5us。

 参考文档:《linux netlink通信机制》、《Linux进程间通信(九):数据报套接字 socket()、bind()、sendto()、recvfrom()、close()》

 

你可能感兴趣的:(c/c++编程基础,linux基础,linux,netlink)