以下三個庫都基於其核心庫libnl:
libnl-route:用於和Kernel中的Routing子系統交互。
libnl-nf:用於和Kernel中的Netfilter子系統交互。
libnl-genl:用於和Kernel中的Generic Netlink模塊交互。
本節介紹libnl中的一些常用API。詳細內容還請讀者參考其官方網站中的文檔,地址為
http://www.infradead.org/~tgr/libnl/doc/api/group__cb.html
1 結構體
libnl以面向對象的方式重新封裝了netlink原有的API。
( 1)netlink message消息結構體
struct nl_msg{
int nm_protocol;
int nm_flags;
struct sockaddr_nl nm_src;
struct sockaddr_nl nm_dst;
struct ucred nm_creds;
struct nlmsghdr * nm_nlh;
size_t nm_size;
int nm_refcnt;
};
(2)netlink socket結構體
netlink socket以及其包含具體文件描述符的相關屬性通過結構體struct nl_sock表示
struct nl_sock{
struct sockaddr_nl s_local;
struct sockaddr_nl s_peer;
int s_fd;
int s_proto;
unsigned int s_seq_next;
unsigned int s_seq_expect;
int s_flags;
struct nl_cb * s_cb;
};
1)使用前首先要分配nl_sock:
struct nl_sock *nl_socket_alloc(void); //分配新的netlink socket,返回新分配的nl_sock地址或者NULL
struct nl_sock *nl_socket_alloc_cb(struct nl_cb *cb); //根據給定的回調函數nl_cb分配新的nl_sock,返回新分配的nl_sock地址或者NULL
2) 使用后需釋放:
void nl_socket_free(struct nl_sock *sk);
3)連接:
int nl_connect(struct nl_sock *sk, int protocol); //nl_connet將通過bind函數將netlink socket和protocol對應的模塊進行綁定:
4)關閉:
void nl_close(struct nl_sock *sk);
5)發送:
int nl_sendto(struct nl_sock *sk, void *buf, size_t size); //發送buffer數據到nl_sock
int nl_sendmsg(struct nl_sock *sk, struct nl_msg *msg, struct msghdr *hdr); //發送nl_msg數據,指定msghdr
int nl_send(struct nl_sock *sk, struct nl_msg *msg);
(3) 回調處理
linbl還可為每個nl_sock設置消息處理回調函數,當該socket上收到消息后,就會回調此函數進行處理。 回調函數及參數封裝在結構體struct nl_cb中
struct nl_cb{
nl_recvmsg_msg_cb_t cb_set[NL_CB_TYPE_MAX+1];
void * cb_args[NL_CB_TYPE_MAX+1];
nl_recvmsg_err_cb_t cb_err;
void *cb_err_arg;
/** May be used to replace nl_recvmsgs with your own implementation in all internal calls to nl_recvmsgs. */
int (*cb_recvmsgs_ow)(struct nl_sock *, struct nl_cb *);
/** Overwrite internal calls to nl_recv, must return the number of octets read and allocate a buffer for the received data. */
int (*cb_recv_ow)(struct nl_sock *, struct sockaddr_nl *, unsigned char **, struct ucred **);
/** Overwrites internal calls to nl_send, must send the netlink message. */
int (*cb_send_ow)(struct nl_sock *, struct nl_msg *);
int cb_refcnt;
};
設置回調:void nl_socket_set_cb(struct nl_sock *sk, struct nl_cb *cb);
獲取該nl_sock設置的回調函數信息:struct nl_cb *nl_socket_get_cb(const struct nl_sock *sk);
注意,libnl庫對nl_cb函數進行細分歸類
/*
1)回調函數的返回值包括以下:
enum nl_cb_action {
NL_OK, //表示處理正常。
NL_SKIP, //表示停止當前netlink消息分析,轉而去分析接收buffer中下一條netlink消息(消息分 片的情況)。
NL_STOP, //表示停止此次接收buffer中的消息分析。
};
2)消息回調函數的類型:
enum nl_cb_kind {
NL_CB_DEFAULT, //默認回調處理函數
NL_CB_VERBOSE, //默認處理函數,打印錯誤信息
NL_CB_DEBUG, //debug 處理函數
NL_CB_CUSTOM, //用戶自定義處理函數
__NL_CB_KIND_MAX
};
3)type類型
可用於處理底層不同netlink消息的情況。 例如,當收到的netlink消息無效時,將調用NL_CB_INVALIDE設置的回調函數進行處理。
enum nl_cb_type {
NL_CB_VALID, //有效消息
NL_CB_FINISH, //multipart消息結尾
NL_CB_OVERRUN, //數據丟失報告
NL_CB_SKIPPED, //跳過處理消息
NL_CB_ACK, //確認消息
NL_CB_MSG_IN, //所有接收到的消息
NL_CB_MSG_OUT, //所有發出的消息,除nl_sendto()外
NL_CB_INVALID, //無效消息
NL_CB_SEQ_CHECK,
NL_CB_SEND_ACK,
NL_CB_DUMP_INTR,
__NL_CB_TYPE_MAX
};
#include <netlink/handlers.h> // 必須包含此頭文件
1)初始化
struct nl_cb *nl_cb_alloc(enum nl_cb_kind kind);//分配新的nl_cb,返回新nl_cb地址或者NULL
struct nl_cb *nl_cb_clone(struct nl_cb *orig) ; //根據給定nl_cb復制新的nl_cb
2)設置
int nl_cb_set(struct nl_cb *cb, enum nl_cb_type type, enum nl_cb_kind kind, nl_recvmsg_msg_cb_t func, void *arg); //設置給定kind_type的callback
int nl_cb_err(struct nl_cb *cb, enum nl_cb_kind kind, nl_recvmsg_err_cb_t func, void * arg); //設置錯誤callback
3)修改給定nl_sock的callbacks:
int nl_socket_modify_cb(struct nl_sock *sk, enum nl_cb_type type, enum nl_cb_kind kind, nl_recvmsg_msg_cb_t func, void *arg); //
2 libnl中的消息處理
libnl定義了自己的消息結構體struct nl_msg。不過它也提供API直接處理netlink的消息。常用的API如下。
#include <netlink/msg.h> // 必須包含這個頭文件
// 計算netlink消息體中對應部分的長度
int nlmsg_size(int payloadlen); // 請參考圖來理解這兩個函數返回值的意義
int nlmsg_total_size(int payloadlen);
其他可直接處理netlink消息的API如下。
struct nlmsghdr *nlmsg_next(struct nlmsghdr *hdr, int *remaining);
int nlmsg_ok(const struct nlmsghdr *hdr, int remaining);
/*定義一個消息處理的for循環宏,其值等於
for (int rem = len, pos = head; nlmsg_ok(pos, rem);\
pos = nlmsg_next(pos, &rem))
*/
#define nlmsg_for_each(pos,head,en)
開發者也可以通過libnl定義的消息結構體nl_msg進行相關操作,和nl_msg有關的API如下。
struct nl_msg *nlmsg_alloc(void);
void nlmsg_free(struct nl_msg *msg);
// nl_msg內部肯定會指向一個netlink消息頭實例,下面這個函數用於填充netlink消息頭
struct nlmsghdr *nlmsg_put(struct nl_msg *msg, uint32_t port, uint32_t seqnr, int nlmsg_type, int payload, int nlmsg_flags);
(3)libnl中的消息發送和接收
netlink直接利用系統調用(如send、recv、sendmsg、recvmsg等)進行數據收發,而libnl封裝了自己特有的數據收發API。其中和發送有關的幾個主要API如下。
// 直接發送netlink消息
int nl_sendto (struct nl_sock *sk, void *buf, size_t size)
// 發送nl_msg消息
int nl_send (struct nl_sock *sk, struct nl_msg *msg)
int nl_send_simple(struct nl_sock *sk, int type, int flags,void *buf, size_t size);
常用的數據接收API如下。
// 核心接收函數。nla參數用於存儲發送端的地址信息。creds用於存儲權限相關的信息
int nl_recv(struct nl_sock *sk, struct sockaddr_nl *nla, unsigned char **buf, struct ucred **creds)
// 內部通過nl_recv接收消息,然后通過cb回調結構體中的回調函數傳給接收者
int nl_recvmsgs (struct nl_sock *sk, struct nl_cb *cb)
(4)libnl-genl API介紹
libnl-genl封裝了對generic netlink模塊的處理,它基於libnl。Linux中關於generic netlink的說明幾乎沒有,建議大家參考libnl中的說明。
其中,genlmsghdr的原型如下。
struct genlmsghdr {
__u8 cmd; // cmd和version都和具體的案例有關
__u8 version;
__u16 reserved; // 保留
};
genl常用的API如下。
// 和libnl的nl_connect類型,只不過協議類型為GENERIC_NETLINK
int genl_connect (struct nl_sock *sk)
// genlmsg_put用於填充圖中的nlmsghdr、genlmsghder和用戶自定義的消息頭。詳細內容見下文
void* genlmsg_put (struct nl_msg *msg, uint32_t port, uint32_t seq, int family, int hdrlen, int flags, uint8_t cmd, uint8_t version)
// 用於獲取genl消息中攜帶的nlattr內容
struct nlattr* genlmsg_attrdata(const struct genlmsghdr *gnlh,int hdrlen)
另外,genl還有幾個比較重要的API,它們和genl機制的內核實現有關,這里僅簡單介紹其中幾點內容。為實現genl機制,內核創建了一個虛擬的Generic Netlink Bus。所有genl的使用者(包含內核模塊或用戶空間進程)都會注冊到此Bus上。這些使用者注冊時,都需要填充一個名為genl_family的數據結構,該結構是一種身份標示。所以某一方只要設置好genlmsg_put中的family參數,數據就能傳遞到對應的模塊。
family是一個整型,可讀性較差,所以genl使用者往往會指定一個字符串作為family name。而family name和family的對應關系則由genl中另外一個重要模塊去處理。這個模塊就是genl中的Controller,它也是Generic Bus使用者。其family name為“nlctrl”,只不過它的family是固定的,目前取值為16(一般為它定義一個NETLINK_GENERIC宏)。Controller的一個重要作用就是為其他注冊者建立family name和family之間關系,也就是動態為其他注冊者分配family編號。另外,Controller也支持查詢,即返回當前Kernel中注冊的所有genl模塊的family name和family的值。
對用戶空間程序來說,只要知道family的值,就可和指定模塊進行通信了。libnl-genl封裝了上述操作,並提供了幾個常用的API。
// 根據family name字符串去查詢family,該函數內部實現將發送查詢消息給Controller
int genl_ctrl_resolve (struct nl_sock *sk, const char *name)
/*
如果每次都向Controller去查詢family編號將嚴重影響效率,所以libnl-genl會把查詢到的信息 緩存起來。
下面這個函數將分配一個nl_cache列表,其內容存儲了當前注冊到Generic Netlink Bus上所有注冊者的信息。
*/
int genl_ctrl_alloc_cache (struct nl_sock *sk, struct nl_cache **result)
// 根據family name從緩存中獲取對應的genl_family信息
struct genl_family * genl_ctrl_search_by_name (struct nl_cache *cache, const char *name)
提示 相比直接使用netlink API,libnl對開發者更加友好,即使libnl封裝得再好,netlink編程依然不是一件輕松的事情。目前為止,筆者還沒有找到一篇文檔能全面描述netlink中的protocol及對應的多播組、genl中Controller模塊所支持的命令等至關重要的知識點。當年在Windows平台做開發時,微軟為開發者提供的編程文檔中不僅有原理性說明,還有很多編程技巧。這些內容對開發者而言都是無價之寶。不過,指望Linux重新修訂、增補文檔無疑是一件異想天開的事情。在此筆者也只能希望讀者們在學習過程中注意收集資料並和大家一起分享了。
