1. 程式人生 > 其它 >linux下netlink的使用簡介

linux下netlink的使用簡介

一、什麼是netlink

Netlink套接字是用以實現使用者程序核心程序通訊的一種特殊的程序間通訊(IPC) ,也是網路應用程式與核心通訊的最常用的介面。

在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。

一般來說使用者空間和核心空間的通訊方式有三種:/proc、ioctl、Netlink。而前兩種都是單向的,而Netlink可以實現雙工通訊。

Netlink 相對於系統呼叫,ioctl 以及 /proc檔案系統而言,具有以下優點:

  • netlink使用簡單,只需要在include/linux/netlink.h中增加一個新型別的 netlink 協議定義即可,(如#define NETLINK_TEST 20然後,核心和使用者態應用就可以立即通過 socket API 使用該 netlink 協議型別進行資料交換)
  • netlink是一種非同步通訊機制,在核心與使用者態應用之間傳遞的訊息儲存在socket快取佇列中,傳送訊息只是把訊息儲存在接收者的socket的接收佇列,而不需要等待接收者收到訊息
  • 使用 netlink 的核心部分可以採用模組的方式實現,使用 netlink 的應用部分和核心部分沒有編譯時依賴
  • netlink 支援多播,核心模組或應用可以把訊息多播給一個netlink組,屬於該neilink 組的任何核心模組或應用都能接收到該訊息,核心事件向用戶態的通知機制就使用了這一特性
  • 核心可以使用 netlink 首先發起會話

Netlink協議基於BSD socket和AF_NETLINK地址簇,使用32位的埠號定址,每個Netlink協議通常與一個或一組核心服務/元件相關聯,如NETLINK_ROUTE用於獲取和設定路由與鏈路資訊、NETLINK_KOBJECT_UEVENT

用於核心向用戶空間的udev程序傳送通知等。


二、使用者態資料結構

使用者態應用使用標準的 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 */
};

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

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

三、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

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

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

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

四、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 核心模組程式碼

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

4. 執行結果

首先將編譯出來的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!!