linux下netlink的使用簡介
阿新 • • 發佈:2021-12-20
一、什麼是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
二、使用者態資料結構
使用者態應用使用標準的 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