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