linux netlink通信機制

linux netlink通信機制

一、什麼是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 首先發起會話;

二、Netlink常用數據結構及函數

用戶態應用使用標準的 socket API有(sendto()),recvfrom(); sendmsg(), recvmsg())

下面簡單介紹幾種NETLINK用戶態通信的常用數據結構

1、用戶態數據結構

Netlink通信跟常用UDP Socket通信類似:
 struct sockaddr_nl 是netlink通信地址跟普通socket struct sockaddr_in類似
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 */
 };

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

(1)nlmsg_len:整個netlink消息的長度(包含消息頭);
(2)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_NOOP:不執行任何動作,必須將該消息丟棄;
NLMSG_ERROR:消息發生錯誤;
NLMSG_DONE:標識分組消息的末尾;
NLMSG_OVERRUN:緩衝區溢出,表示某些消息已經丟失。
NLMSG_MIN_TYPEK:預留 */

(3)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       */

(4)nlmsg_seq:消息序列號,用以將消息排隊,有些類似TCP協議中的序號(不完全一樣),但是netlink的這個字段是可選的,不強制使用;
(5)nlmsg_pid:發送端口的ID號,對於內核來說該值就是0,對於用戶進程來說就是其socket所綁定的ID號。

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 */
 };
 /* msg_name: 數據的目的地址,網絡包指向sockaddr_in, netlink則指向sockaddr_nl;
    msg_namelen: msg_name 所代表的地址長度
    msg_iov: 指向的是緩衝區數組
    msg_iovlen: 緩衝區數組長度
    msg_control: 輔助數據,控制信息(發送任何的控制信息)
    msg_controllen: 輔助信息長度
    msg_flags: 消息標識
 */

2. netlink 內核數據結構、常用宏及函數:

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

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

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

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

三、netlink實例

1. 用戶態程序 (sendto(), recvfrom())

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

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

2. Netlink 內核模塊代碼:

/****************************************
* Author: zhangwj
* Date: 2017-01-19
* Filename: netlink_test.c
* Descript: netlink of kernel
* Kernel: 3.10.0-327.22.2.el7.x86_64
* Warning:
******************************************/

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

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

3. Makefile

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

運行結果:

首先將編譯出來的Netlink內核模塊拷貝到系統當中(insmod netlink_demo.ko)可以看到如下:

[root@localhost ]# insmod netlink_demo.ko 
[root@localhost ]# dmesg
[25024.276345] test_netlink_init

接着運行應用程序:

[root@localhost ]# ./netlink_demo_app
send kernel:hello netlink!!
from kernel:hello users!!!
[root@localhost ]# dmesg 
[25024.276345] test_netlink_init
[25117.548350] kernel recv from user: hello netlink!!
[root@localhost ]#

特別說明:

此代碼適用3.10以上內核。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章