netlink介紹
一般來說用戶空間和內核空間的通信方式有很多種,而Netlink可以實現雙工通信。
Netlink套接字是用以實現用戶進程與內核進程通信的一種特殊的進程間通信(IPC) ,也是網絡應用程序與內核通信的最常用的接口。
在Linux 內核中,使用netlink 進行應用與內核通信的應用有很多,如:
- 路由 daemon(NETLINK_ROUTE)
- 用戶態 socket 協議(NETLINK_USERSOCK)
- 防火牆(NETLINK_FIREWALL)
- netfilter 子系統(NETLINK_NETFILTER)
- 內核事件向用戶態通知(NETLINK_KOBJECT_UEVENT)
- 通用netlink(NETLINK_GENERIC)
Netlink 相對於系統調用
,ioctl
以及 /proc文件系統
而言,具有以下優點:
- netlink使用簡單,只需要在
include/linux/netlink.h
中增加一個新類型的 netlink 協議定義即可,(如#define NETLINK_MSG_FOR_SCHIPS 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 就可以使用 netlink 提供的強大功能,內核態需要使用專門的內核 API 來使用 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)))
用戶態數據結構
用戶態應用使用標准的 socket API有sendto()
,recvfrom()
, sendmsg()
, recvmsg()
。
Netlink通信跟常用UDP Socket通信類似,struct sockaddr_nl
是netlink通信地址,跟普通socket struct sockaddr_in
類似。
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 */
};
nlmsghdr
struct nlmsghdr是netlink提供的協議頭,netlink協議是面向消息的,需要定義自己的協議。自定義協議按照協議頭格式填充協議頭內容,並定義自己的payload,通常自定義的協議體包含自定義協議頭與額外的屬性。
/* struct nlmsghd 是netlink消息頭*/
struct nlmsghdr {
__u32 nlmsg_len; /* 整個netlink消息的長度,包含消息頭。 */
__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 */
- nlmsg_seq:消息序列號,用以將消息排隊有些類似TCP協議中的序號(不完全一樣),可選,不強制使用。
- nlmsg_pid:發送端口的ID號,對於內核來說該值就是0,對於用戶進程來說就是其socket所綁定的ID號。
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_kernel_cfg
/* 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消息類型
#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 內核函數
創建內核socket
static inline struct sock *
netlink_kernel_create(struct net *net,
int unit,
struct netlink_kernel_cfg *cfg)
/*
net: 所在的網絡命名空間, 一般默認為 init_net 是內核定義的變量;
init_net 來自 net_namespace.c(extern struct net init_net)
貌似是不建議使用init_net的,但對於測試足夠了
unit:netlink協議類型
cfg: netlink內核配置參數
*/
一般在模塊初始化函數中使用:
struct sock *nlsk = NULL; // 全局變量
static void netlink_rcv_msg(struct sk_buff *skb); // 接收消息時的回調函數
#define NETLINK_MSG_FOR_SCHIPS 30
// 指定接收時的回調函數
struct netlink_kernel_cfg cfg = {
.input = netlink_rcv_msg, /* set recv callback */
};
/* create netlink socket */
nlsk = (struct sock *)netlink_kernel_create(&init_net, NETLINK_MSG_FOR_SCHIPS, &cfg);
if(nlsk == NULL)
{
printk("netlink_kernel_create error !\n");
return -1;
}
// ...
最多可以定義32中類型,若 uint > MAX_LINKS 則該函數返回NULL,源代碼片段如下:
// kernel/net/netlink/af_netlink.c
__netlink_kernel_create(struct net *net, int unit, struct module *module,
struct netlink_kernel_cfg *cfg)
{
struct socket *sock;
struct sock *sk;
struct netlink_sock *nlk;
struct listeners *listeners = NULL;
struct mutex *cb_mutex = cfg ? cfg->cb_mutex : NULL;
unsigned int groups;
BUG_ON(!nl_table);
if (unit < 0 || unit >= MAX_LINKS)
return NULL;
// ...
}
接收消息
作為回調函數被調用的。
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);
}
}
}
單播和多播
單播就是點對點發送的意思,作用相當於用戶函數中的sendto
。
/* 發送單播消息 */
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
*/
一般這么用:
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_MSG_FOR_SCHIPS, 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);
// nlmsg_free(nl_skb); 注意,不需要free(內核會做這件事情)
return ret;
}
netlink 用戶函數
#include <sys/types.h> /* See NOTES */
#include <sys/socket.h>
創建用戶socket
int socket(int domain, int type, int protocol);
在netlink 中,一般使用以下的方式:
#define NETLINK_MSG_FOR_SCHIPS 30 // 創建自己的消息類型
int skfd = socket(AF_NETLINK, SOCK_RAW, NETLINK_MSG_FOR_SCHIPS);
// ...
綁定地址
int bind(int sockfd,
const struct sockaddr *addr,
socklen_t addrlen);
在netlink 中,一般使用以下的方式:
#define USER_PORT 123
struct sockaddr_nl bind_addr;
memset(&bind_addr, 0, sizeof(bind_addr));
bind_addr.nl_family = AF_NETLINK; //AF_NETLINK
bind_addr.nl_pid = USER_PORT; //端口號(port ID)
bind_addr.nl_groups = 0;
bind(skfd, (struct sockaddr *)&bind_addr, sizeof(bind_addr));
// ...
發送數據
ssize_t sendto(int sockfd, const void *buf, size_t len, int flags,
const struct sockaddr *dest_addr, socklen_t addrlen);
#define NLMSG_DATA(nlh) ((void*)(((char*)nlh) + NLMSG_LENGTH(0)))
#define NLMSG_SPACE(len) NLMSG_ALIGN(NLMSG_LENGTH(len))
在netlink 中,一般使用以下的方式:
struct nlmsghdr *nlh = NULL;
struct sockaddr_nl bind_addr; // 在上面已經初始化過了
struct sockaddr_nl dest_addr;
char *umsg = "hello netlink!!";
memset(&dest_addr, 0, sizeof(dest_addr));
dest_addr.nl_family = AF_NETLINK;
dest_addr.nl_pid = 0; // to kernel
dest_addr.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 = bind_addr.nl_pid; //self port
memcpy(NLMSG_DATA(nlh), umsg, strlen(umsg));
sendto(skfd, nlh, nlh->nlmsg_len, 0, (struct sockaddr *)&dest_addr, sizeof(struct sockaddr_nl));
free(nlh); // 只是為了防止內存泄漏
// ...
接收數據
ssize_t recvfrom(int sockfd, void *buf, size_t len, int flags,
struct sockaddr *src_addr, socklen_t *addrlen);
在netlink 中,一般使用以下的方式:
user_msg_info u_info;
socklen_t len;
memset(&u_info, 0, sizeof(u_info));
len = sizeof(struct sockaddr_nl);
ret = recvfrom(skfd, &u_info, sizeof(user_msg_info), 0, (struct sockaddr *)&dest_addr, &len);
總結
使用netlink時需要定義好消息類型,socket的端口id(pid);如果需要使用多播,則還需要指定nl_groups。
實際通信時,需要內核先配置好;應用程序再創建對應的socket。
發送和接收需要使用netlink 的一些宏進行處理。
實例
用戶態程序
#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>
/********* NETLINK 傳輸有關定義*********/
// 消息類型
#define NETLINK_MSG_FOR_SCHIPS 33
// 端口號
#define USER_PORT 123
#define MSG_LEN 125
#define MAX_PLOAD MSG_LEN
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 bind_addr;
struct sockaddr_nl dest_addr;
char *umsg = "hello netlink!!";
/* 創建NETLINK socket */
skfd = socket(AF_NETLINK, SOCK_RAW, NETLINK_MSG_FOR_SCHIPS);
if(skfd == -1)
{
perror("create socket error");
return -1;
}
memset(&bind_addr, 0, sizeof(bind_addr));
bind_addr.nl_family = AF_NETLINK; //AF_NETLINK
bind_addr.nl_pid = USER_PORT; //端口號(port ID)
bind_addr.nl_groups = 0;
if(bind(skfd, (struct sockaddr *)&bind_addr, sizeof(bind_addr)) != 0)
{
perror("bind() error\n");
close(skfd);
return -1;
}
memset(&dest_addr, 0, sizeof(dest_addr));
dest_addr.nl_family = AF_NETLINK;
dest_addr.nl_pid = 0; // to kernel
dest_addr.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 = bind_addr.nl_pid; //self port
memcpy(NLMSG_DATA(nlh), umsg, strlen(umsg));
ret = sendto(skfd, nlh, nlh->nlmsg_len, 0, (struct sockaddr *)&dest_addr, sizeof(struct sockaddr_nl));
if(!ret)
{
perror("sendto error");
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 *)&dest_addr, &len);
if(!ret)
{
perror("recv form kernel error");
close(skfd);
exit(-1);
}
printf("from kernel:%s\n", u_info.msg);
close(skfd);
free((void *)nlh);
return 0;
}
內核模塊程序
#include <linux/init.h>
#include <linux/module.h>
#include <linux/types.h>
#include <net/sock.h>
#include <linux/netlink.h>
// 消息類型
#define NETLINK_MSG_FOR_SCHIPS 33
// 端口號
#define USER_PORT 123
// 消息長度限制
#define MSG_LEN 125
#define MAX_PLOAD MSG_LEN
struct sock *nlsk = NULL;
extern struct net init_net;
static void netlink_rcv_msg(struct sk_buff *skb);
int send_usrmsg(char *pbuf, uint16_t len);
struct netlink_kernel_cfg cfg = {
.input = netlink_rcv_msg, /* set recv callback */
};
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_MSG_FOR_SCHIPS, 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);
// nlmsg_free(nl_skb); 發送的skb不需要內核模塊去釋放,也不能釋放,否則會崩潰。內核會處理skb的釋放,所以不會出現內存泄露問題
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));
}
}
}
int test_netlink_init(void)
{
/* create netlink socket */
nlsk = (struct sock *)netlink_kernel_create(&init_net, NETLINK_MSG_FOR_SCHIPS, &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);
Makeflie
適用於Ubuntu,其他環境沒有測試。
# Makefile for netlink
MODULE_NAME := nl
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 nl.ko)
-
再運行應用程序
可以看到如下輸出:
#./nl_app
create socket error: Protocol not supported
# insmod nl.ko
[25024.276345]test_netlink_init
# ./nl_app
[25117.548350]kernel recv from user: hello netlink!!
send kernel:hello netlink!!
from kernel:hello users!!!
## 卸載以后,app 無法通信(正常)
#rmmod nl
[25124.541352]test_netlink_exit!
# ./nl_app
create socket error: Protocol not supported
ref :