轉載至:http://www.aichengxu.com/linux/7237757.htm
Netlink 是一種特殊的 socket,它是 Linux 所特有的,類似於 BSD 中的AF_ROUTE 但又遠比它的功能強大,目前在最新的 Linux 內核(2.6.14)中使用netlink 進行應用與內核通信的應用很多,包括:路由 daemon(NETLINK_ROUTE),1-wire 子系統(NETLINK_W1),用戶態 socket 協議(NETLINK_USERSOCK),防火牆(NETLINK_FIREWALL),socket 監視(NETLINK_INET_DIAG),netfilter
日誌(NETLINK_NFLOG),ipsec 安全策略(NETLINK_XFRM),SELinux 事件通知(NETLINK_SELINUX),iSCSI 子系統(NETLINK_ISCSI),進程審計(NETLINK_AUDIT),轉發信息表查詢 (NETLINK_FIB_LOOKUP),netlink connector(NETLINK_CONNECTOR),netfilter 子系統(NETLINK_NETFILTER),IPv6 防火牆(NETLINK_IP6_FW),DECnet 路由信息(NETLINK_DNRTMSG),內核事件向用戶態通知(NETLINK_KOBJECT_UEVENT),通用netlink(NETLINK_GENERIC)。
Netlink 是一種在內核與用戶應用間進行雙向數據傳輸的非常好的方式,用戶態應用使用標準的 socket API 就可以使用 netlink 提供的強大功能,內核態需要使用專門的內核 API 來使用 netlink。
Netlink 相對於系統調用,ioctl 以及 /proc 文件系統而言具有以下優點:
1,爲了使用 netlink,用戶僅需要在 include/linux/netlink.h 中增加一個新類型的 netlink 協議定義即可, 如 #define NETLINK_MYTEST 17 然後,內核和用戶態應用就可以立即通過 socket API 使用該 netlink 協議類型進行數據交換。但系統調用需要增加新的系統調用,ioctl 則需要增加設備或文件, 那需要不少代碼,proc 文件系統則需要在 /proc 下添加新的文件或目錄,那將使本來就混亂的
/proc 更加混亂。
2. netlink是一種異步通信機制,在內核與用戶態應用之間傳遞的消息保存在socket緩存隊列中,發送消息只是把消息保存在接收者的socket的接 收隊列,而不需要等待接收者收到消息,但系統調用與 ioctl 則是同步通信機制,如果傳遞的數據太長,將影響調度粒度。
3.使用 netlink 的內核部分可以採用模塊的方式實現,使用 netlink 的應用部分和內核部分沒有編譯時依賴,但系統調用就有依賴,而且新的系統調用的實現必須靜態地連接到內核中,它無法在模塊中實現,使用新系統調用的應用在編譯時需要依賴內核。
4.netlink 支持多播,內核模塊或應用可以把消息多播給一個netlink組,屬於該neilink 組的任何內核模塊或應用都能接收到該消息,內核事件向用戶態的通知機制就使用了這一特性,任何對內核事件感興趣的應用都能收到該子系統發送的內核事件,在 後面的文章中將介紹這一機制的使用。
5.內核可以使用 netlink 首先發起會話,但系統調用和 ioctl 只能由用戶應用發起調用。
6.netlink 使用標準的 socket API,因此很容易使用,但系統調用和 ioctl則需要專門的培訓才能使用。
用戶態使用 netlink
用戶態應用使用標準的socket APIs, socket(), bind(), sendmsg(), recvmsg() 和 close() 就能很容易地使用 netlink socket,查詢手冊頁可以瞭解這些函數的使用細節,本文只是講解使用 netlink 的用戶應該如何使用這些函數。注意,使用 netlink 的應用必須包含頭文件 linux/netlink.h。當然 socket 需要的頭文件也必不可少,sys/socket.h。
爲了創建一個 netlink socket,用戶需要使用如下參數調用 socket():
socket(AF_NETLINK, SOCK_RAW, netlink_type) |
#define NETLINK_ROUTE 0 /* Routing/device hook */ #define NETLINK_W1 1 /* 1-wire subsystem */ #define NETLINK_USERSOCK 2 /* Reserved for user mode socket protocols */ #define NETLINK_FIREWALL 3 /* Firewalling hook */ #define NETLINK_INET_DIAG 4 /* INET 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 |
函數 bind() 用於把一個打開的 netlink socket 與 netlink 源 socket 地址綁定在一起。netlink socket 的地址結構如下:
struct sockaddr_nl { sa_family_t nl_family; unsigned short nl_pad; __u32 nl_pid; __u32 nl_groups; }; |
傳遞給 bind 函數的地址的 nl_pid 字段應當設置爲本進程的進程 ID,這相當於 netlink socket 的本地地址。但是,對於一個進程的多個線程使用 netlink socket 的情況,字段 nl_pid 則可以設置爲其它的值,如:
pthread_self() << 16 | getpid(); |
bind(fd, (struct sockaddr*)&nladdr, sizeof(struct sockaddr_nl)); |
sendmsg 發送 netlink 消息時還需要引用結構 struct msghdr、struct nlmsghdr 和 struct iovec,結構 struct msghdr 需如下設置:
struct msghdr msg; memset(&msg, 0, sizeof(msg)); msg.msg_name = (void *)&(nladdr); msg.msg_namelen = sizeof(nladdr); |
struct nlmsghdr 爲 netlink socket 自己的消息頭,這用於多路複用和多路分解 netlink 定義的所有協議類型以及其它一些控制,netlink 的內核實現將利用這個消息頭來多路複用和多路分解已經其它的一些控制,因此它也被稱爲netlink 控制塊。因此,應用在發送 netlink 消息時必須提供該消息頭。
struct nlmsghdr { __u32 nlmsg_len; /* Length of message */ __u16 nlmsg_type; /* Message type*/ __u16 nlmsg_flags; /* Additional flags */ __u32 nlmsg_seq; /* Sequence number */ __u32 nlmsg_pid; /* Sending process PID */ }; |
/* 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 */ /* 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 */ |
標誌NLM_F_MULTI 用於指示該消息是一個多部分消息的一部分,後續的消息可以通過宏NLMSG_NEXT來獲得。
宏NLM_F_ACK表示該消息是前一個請求消息的響應,順序號與進程ID可以把請求與響應關聯起來。
標誌NLM_F_ECHO表示該消息是相關的一個包的回傳。
標誌NLM_F_ROOT 被許多 netlink 協議的各種數據獲取操作使用,該標誌指示被請求的數據表應當整體返回用戶應用,而不是一個條目一個條目地返回。有該標誌的請求通常導致響應消息設置 NLM_F_MULTI標誌。注意,當設置了該標誌時,請求是協議特定的,因此,需要在字段 nlmsg_type 中指定協議類型。
標誌 NLM_F_MATCH 表示該協議特定的請求只需要一個數據子集,數據子集由指定的協議特定的過濾器來匹配。
標誌 NLM_F_ATOMIC 指示請求返回的數據應當原子地收集,這預防數據在獲取期間被修改。
標誌 NLM_F_DUMP 未實現。
標誌 NLM_F_REPLACE 用於取代在數據表中的現有條目。
標誌 NLM_F_EXCL_ 用於和 CREATE 和 APPEND 配合使用,如果條目已經存在,將失敗。
標誌 NLM_F_CREATE 指示應當在指定的表中創建一個條目。
標誌 NLM_F_APPEND 指示在表末尾添加新的條目。
內核需要讀取和修改這些標誌,對於一般的使用,用戶把它設置爲 0 就可以,只是一些高級應用(如 netfilter 和路由 daemon 需要它進行一些複雜的操作),字段 nlmsg_seq 和 nlmsg_pid 用於應用追蹤消息,前者表示順序號,後者爲消息來源進程 ID。下面是一個示例:
#define MAX_MSGSIZE 1024 char buffer[] = "An example message"; struct nlmsghdr nlhdr; nlhdr = (struct nlmsghdr *)malloc(NLMSG_SPACE(MAX_MSGSIZE)); strcpy(NLMSG_DATA(nlhdr),buffer); nlhdr->nlmsg_len = NLMSG_LENGTH(strlen(buffer)); nlhdr->nlmsg_pid = getpid(); /* self pid */ nlhdr->nlmsg_flags = 0; |
struct iovec iov; iov.iov_base = (void *)nlhdr; iov.iov_len = nlh->nlmsg_len; msg.msg_iov = &iov; msg.msg_iovlen = 1; |
sendmsg(fd, &msg, 0); |
#define MAX_NL_MSG_LEN 1024 struct sockaddr_nl nladdr; struct msghdr msg; struct iovec iov; struct nlmsghdr * nlhdr; nlhdr = (struct nlmsghdr *)malloc(MAX_NL_MSG_LEN); iov.iov_base = (void *)nlhdr; iov.iov_len = MAX_NL_MSG_LEN; msg.msg_name = (void *)&(nladdr); msg.msg_namelen = sizeof(nladdr); msg.msg_iov = &iov; msg.msg_iovlen = 1; recvmsg(fd, &msg, 0); |
在消息接收後,nlhdr指向接收到的消息的消息頭,nladdr保存了接收到的消息的目標地址,宏NLMSG_DATA(nlhdr)返回指向消息的數據部分的指針。
在linux/netlink.h中定義了一些方便對消息進行處理的宏,這些宏包括:
#define NLMSG_ALIGNTO 4 #define NLMSG_ALIGN(len) ( ((len)+NLMSG_ALIGNTO-1) & ~(NLMSG_ALIGNTO-1) ) |
#define NLMSG_LENGTH(len) ((len)+NLMSG_ALIGN(sizeof(struct nlmsghdr))) |
#define NLMSG_SPACE(len) NLMSG_ALIGN(NLMSG_LENGTH(len)) |
#define NLMSG_DATA(nlh) ((void*)(((char*)nlh) + NLMSG_LENGTH(0))) |
#define NLMSG_NEXT(nlh,len) ((len) -= NLMSG_ALIGN((nlh)->nlmsg_len), \ (struct nlmsghdr*)(((char*)(nlh)) + NLMSG_ALIGN((nlh)->nlmsg_len))) |
#define NLMSG_OK(nlh,len) ((len) >= (int)sizeof(struct nlmsghdr) && \ (nlh)->nlmsg_len >= sizeof(struct nlmsghdr) && \ (nlh)->nlmsg_len <= (len)) |
#define NLMSG_PAYLOAD(nlh,len) ((nlh)->nlmsg_len - NLMSG_SPACE((len))) |
函數close用於關閉打開的netlink socket。
netlink內核API
netlink的內核實現在.c文件net/core/af_netlink.c中,內核模塊要想使用netlink,也必須包含頭文件linux /netlink.h。內核使用netlink需要專門的API,這完全不同於用戶態應用對netlink的使用。如果用戶需要增加新的netlink協 議類型,必須通過修改linux/netlink.h來實現,當然,目前的netlink實現已經包含了一個通用的協議類型 NETLINK_GENERIC以方便用戶使用,用戶可以直接使用它而不必增加新的協議類型。前面講到,爲了增加新的netlink協議類型,用戶僅需增
加如下定義到linux/netlink.h就可以:
#define NETLINK_MYTEST 17 |
在內核中,爲了創建一個netlink socket用戶需要調用如下函數:
struct sock * netlink_kernel_create(int unit, void (*input)(struct sock *sk, int len)); |
void input (struct sock *sk, int len) { struct sk_buff *skb; struct nlmsghdr *nlh = NULL; u8 *data = NULL; while ((skb = skb_dequeue(&sk->receive_queue)) != NULL) { /* process netlink message pointed by skb->data */ nlh = (struct nlmsghdr *)skb->data; data = NLMSG_DATA(nlh); /* process netlink message with header pointed by * nlh and data pointed by data */ } } |
函數skb = skb_dequeue(&sk->receive_queue)用於取得socket sk的接收隊列上的消息,返回爲一個struct sk_buff的結構,skb->data指向實際的netlink消息。
函數skb_recv_datagram(nl_sk)也用於在netlink socket nl_sk上接收消息,與skb_dequeue的不同指出是,如果socket的接收隊列上沒有消息,它將導致調用進程睡眠在等待隊列 nl_sk->sk_sleep,因此它必須在進程上下文使用,剛纔講的內核線程就可以採用這種方式來接收消息。
下面的函數input就是這種使用的示例:
void input (struct sock *sk, int len) { wake_up_interruptible(sk->sk_sleep); } |
#define NETLINK_CB(skb) (*(struct netlink_skb_parms*)&((skb)->cb)) |
NETLINK_CB(skb).pid = 0; NETLINK_CB(skb).dst_pid = 0; NETLINK_CB(skb).dst_group = 1; |
在內核中,模塊調用函數 netlink_unicast 來發送單播消息:
int netlink_unicast(struct sock *sk, struct sk_buff *skb, u32 pid, int nonblock); |
內核模塊或子系統也可以使用函數netlink_broadcast來發送廣播消息:
void netlink_broadcast(struct sock *sk, struct sk_buff *skb, u32 pid, u32 group, int allocation); |
在內核中使用函數sock_release來釋放函數netlink_kernel_create()創建的netlink socket:
void sock_release(struct socket * sock); |
sock_release(sk->sk_socket); |
在源代碼包中 給出了一個使用 netlink 的示例,它包括一個內核模塊
netlink-exam-kern.c 和兩個應用程序 netlink-exam-user-recv.c, netlink-exam-user-send.c。內核模塊必須先插入到內核,然後在一個終端上運行用戶態接收程序,在另一個終端上運行用戶態發送程 序,發送程序讀取參數指定的文本文件並把它作爲 netlink 消息的內容發送給內核模塊,內核模塊接受該消息保存到內核緩存中,它也通過proc接口出口到 procfs,因此用戶也能夠通過 /proc/netlink_exam_buffer 看到全部的內容,同時內核也把該消息發送給用戶態接收程序,用戶態接收程序將把接收到的內容輸出到屏幕上