Linux之netlink(2)Libnl3使用介紹(1)
Author:Onceday Date:2025年4月26日
漫漫長路,才剛剛開始…
全系列文章可查看專欄: Linux內核知識_Once-Day的博客-CSDN博客
本文翻譯自libnl3官方文檔:Netlink Library (libnl)
參考文檔:
- thom311/libnl: Netlink Library Suite
- libnl - Netlink Protocol Library Suite
- Routing Family Netlink Library (libnl-route)
- Netlink Library (libnl)
- Documentation Overview - libnl Suite
文章目錄
- Linux之netlink(2)Libnl3使用介紹(1)
- 1. libnl介紹
- 2. Netlink協議介紹
- 2.1 Netlink地址
- 2.2 消息格式
- 2.3 消息類型
- 2.4 序列號
- 3. Netlink套接字
- 3.1 Socket實例
- 3.2 序列號
- 3.3 多播組
- 3.4 回調函數配置
- 3.5 套接字屬性
1. libnl介紹
libnl3是Linux平臺上一個功能豐富的網絡編程庫。它為用戶空間程序提供了一組全面的API,用于與Linux內核的網絡組件進行交互。libnl3讓開發者能夠方便地配置和管理各種網絡功能,如鏈路、接口、路由、地址、鄰居、策略路由、流量控制、網絡狀態監控等。
libnl 套件是一組庫的集合,它為基于 Netlink 協議的 Linux 內核接口提供了應用程序編程接口(API)。
Netlink 是一種主要在內核與用戶空間進程之間使用的進程間通信(IPC)機制。它被設計成是 ioctl(輸入輸出控制)更靈活的替代方案,主要用于提供與網絡相關的內核配置和監控接口。
這些接口被劃分到幾個小型庫中,這樣就不會強制應用程序鏈接到一個龐大臃腫的單一庫上。
- libnl,核心庫,實現了使用 Netlink 協議所需的基本功能,比如套接字處理、消息構建與解析,以及數據的發送和接收。這個庫保持小巧且簡約。該套件中的其他庫都依賴于這個庫。
- libnl-route,提供了 NETLINK_ROUTE 系列配置接口的 API,這些接口涉及網絡接口、路由、地址、鄰居節點以及流量控制等方面。
- libnl-genl,提供了通用 Netlink 協議(Netlink 協議的擴展版本)的 API。
- libnl-nf,提供了基于 Netlink 的 netfilter(網絡過濾器)配置和監控接口(連接跟蹤、日志記錄、隊列)的 API。
主要的頭文件是 <netlink/netlink.h>
。根據程序所使用的子系統和組件的不同,可能還需要在源文件中包含其他的頭文件。
#include <netlink/netlink.h>#include <netlink/cache.h>#include <netlink/route/link.h>
如果該庫在編譯時啟用了調試語句,那么當環境變量 NLDBG 的值設置為大于 0 時,它將會把調試信息打印到標準錯誤輸出(stderr)中。
NLDBG=2 ./myprogram
下面是各個NLDBG值對應的調試級別:
調試級別 | 描述 |
---|---|
0 | 調試功能已禁用(默認設置) |
1 | 警告、重要事件及通知信息 |
2 | 或多或少較為重要的調試消息 |
3 | 會產生大量調試消息的重復性事件相關信息 |
4 | 甚至更不太重要的消息 |
查看與其他套接字交換的 Netlink 消息流通常是很有用的。設置環境變量 NLCB=debug 將啟用調試消息處理程序,該程序進而會以人類可讀的格式將交換的 Netlink 消息打印到標準錯誤輸出(stderr)中。
$ NLCB=debug ./myprogram-- Debug: Sent Message:-------------------------- BEGIN NETLINK MESSAGE ---------------------------[HEADER] 16 octets.nlmsg_len = 20.nlmsg_type = 18 <route/link::get>.nlmsg_flags = 773 <REQUEST,ACK,ROOT,MATCH>.nlmsg_seq = 1301410712.nlmsg_pid = 20014[PAYLOAD] 16 octets
.....
2. Netlink協議介紹
2.1 Netlink地址
Netlink 協議是一種基于套接字的進程間通信(IPC)機制,用于用戶空間進程與內核之間,或者用戶空間進程彼此之間的通信。Netlink 協議基于 BSD 套接字,并使用 AF_NETLINK 地址族。每一種 Netlink 協議都有其自身的協議編號(例如,NETLINK_ROUTE、NETLINK_NETFILTER 等)。它的編址模式基于一個 32 位的端口號,以前稱為進程標識符(PID),這個端口號唯一標識每個通信對等端。
Netlink 地址(端口)由一個 32 位整數組成。端口 0(零)是為內核保留的,它指的是每個 Netlink 協議族在內核端的套接字。其他端口號通常指的是用戶空間所擁有的套接字,不過這并非強制規定。
起初,常見的做法是使用進程標識符(PID)作為本地端口號。但隨著支持多線程的 Netlink 應用程序以及需要多個套接字的應用程序的出現,這種做法變得不太實用了。因此,libnl 庫會基于進程標識符生成唯一的端口號,并為其添加一個偏移量,從而允許使用多個套接字。出于向后兼容的原因,初始套接字的端口號仍然會等于進程標識符。
上圖展示了三個應用程序以及內核端所暴露的兩個內核端套接字。它呈現了常見的 Netlink 使用場景:
- 用戶空間到內核。
- 用戶空間到用戶空間。
- 監聽內核的多播通知。
(1)用戶空間到內核:Netlink 最常見的使用形式是用戶空間應用程序向內核發送請求,并處理回復,回復內容要么是錯誤消息,要么是成功通知。
(2)用戶空間到用戶空間:Netlink 也可以用作一種進程間通信機制,以便用戶空間應用程序之間直接進行通信。通信并不局限于兩個對等端,任意數量的對等端都可以相互通信,并且多播功能允許通過一條消息發送給多個對等端。
為了讓各個套接字能夠相互可見,必須為相同的 Netlink 協議族創建這兩個套接字。
(3)用戶空間監聽內核通知:這種形式的 Netlink 通信通常出現在用戶空間守護進程中,這些守護進程需要針對某些內核事件采取行動。此類守護進程通常會維護一個訂閱了某個多播組的 Netlink 套接字,內核使用該多播組向感興趣的用戶空間各方通知特定事件。
由于在任何時候更換用戶空間組件時都無需內核察覺,并且具有靈活性,所以相較于直接編址,多播的使用更為可取。
2.2 消息格式
Netlink 協議通常基于消息,由 Netlink 消息頭部(struct nlmsghdr
)以及附加到它上面的有效載荷組成。有效載荷可以由任意數據構成,但通常包含一個固定大小的特定于協議的頭部,其后跟著一系列屬性。
Netlink 消息頭部(struct nlmsghdr
結構體):
-
總長度(32 位):消息的總長度,以字節為單位,包括 Netlink 消息頭部。
-
消息類型(16 位):消息類型指定了該消息所攜帶的有效載荷的類型。Netlink 協議定義了幾種標準的消息類型。每個協議族可能會定義額外的消息類型。
-
消息標志(16 位):消息標志可用于修改消息類型的行為。
-
序列號(32 位):序列號是可選的,可用于引用先前的消息,例如,一條錯誤消息可以引用導致該錯誤的原始請求。
-
端口號(32 位):端口號指定了該消息應發送到的對等端。如果未指定端口號,該消息將被發送到同一協議族中第一個匹配的內核端套接字。
2.3 消息類型
Netlink 協議區分請求、通知和回復。請求是設置了NLM_F_REQUEST
標志的消息,其目的是向接收方請求執行某個操作。請求通常由用戶空間進程發送到內核。雖然并非嚴格強制要求,但每個發送的請求都應該攜帶一個遞增的序列號。
根據請求的性質,接收方可能會用另一條 Netlink 消息來回復該請求。回復的序列號必須與它所關聯的請求的序列號相匹配。
通知屬于非正規性質的消息,不需要回復,因此序列號通常設置為 0。
消息的類型主要通過消息頭部中設置的 16 位消息類型來識別。定義了以下標準消息類型:
-
NLMSG_NOOP,無操作,該消息必須被丟棄。
-
NLMSG_ERROR,錯誤消息或確認消息(ACK)。
-
NLMSG_DONE,多部分消息序列的結束。
-
NLMSG_OVERRUN,溢出通知(錯誤)。
每個 Netlink 協議都可以自由定義自己的消息類型。請注意,小于 NLMSG_MIN_TYPE(0x10)的消息類型值是保留的,不能使用。
通常的做法是使用自定義的消息類型來實現遠程過程調用(RPC)模式。假設正在實現的 Netlink 協議的目標是允許對特定網絡設備進行配置,因此希望提供對各種配置選項的讀寫訪問權限。實現這一目標的典型 “Netlink 方式” 是定義兩種消息類型:MSG_SETCFG(設置配置消息)和 MSG_GETCFG(獲取配置消息):
#define MSG_SETCFG 0x11
#define MSG_GETCFG 0x12
發送一條MSG_GETCFG
請求消息通常會觸發一條消息類型為MSG_SETCFG
的回復,回復中包含當前的配置信息。用面向對象的術語來說,這可以描述為 “內核在用戶空間中設置配置的本地副本”。
可以通過發送一條 MSG_SETCFG 消息來更改配置,對該消息的回復要么是一條確認消息(ACK),要么是一條錯誤消息。
作為可選操作,內核可以發送配置更改的通知,使用戶空間能夠監聽這些更改,而無需頻繁輪詢。通知通常會重用現有的消息類型,并且依賴于應用程序使用單獨的套接字來區分請求和通知,但你也可以指定一個單獨的消息類型。
盡管從理論上講,一條 Netlink 消息的大小最大可達 4GiB,但套接字緩沖區很可能不夠大,無法容納如此大小的消息。因此,通常的做法是將消息大小限制為一頁的大小(PAGE_SIZE),并使用多部分機制將大塊數據分割成幾條消息。一條多部分消息(Multipart Messages)設置了標志NLM_F_MULTI
,接收方需要持續接收和解析消息,直到接收到特殊的消息類型NLMSG_DONE
為止。
與分片的 IP 數據包不同,多部分消息無需重新組裝,盡管如果協議希望以這種方式工作,重新組裝也是完全可行的。多部分消息常常用于發送對象列表或對象樹,每條多部分消息僅僅攜帶多個對象,從而允許獨立解析每條消息。
錯誤消息可以作為對請求的響應而發送。錯誤消息必須使用標準消息類型 NLMSG_ERROR。其有效載荷由一個錯誤代碼和原始請求的 Netlink 消息頭部組成。
錯誤消息應將序列號設置為導致該錯誤的請求的序列號。
確認消息(ACK),發送方可以通過在請求中設置 NLM_F_ACK 標志,來請求接收方為每條已處理的請求發回一條確認消息。這通常用于讓發送方在請求被接收方處理之前同步后續的處理操作。
確認消息也使用消息類型NLMSG_ERROR
和有效載荷格式,但錯誤代碼設置為 0。
消息標志,定義了以下標準標志:
#define NLM_F_REQUEST 1
#define NLM_F_MULTI 2
#define NLM_F_ACK 4
#define NLM_F_ECHO 8
- NLM_F_REQUEST - 消息是一個請求。
- NLM_F_MULTI - 多部分消息。
- NLM_F_ACK - 請求確認消息。
- NLM_F_ECHO - 請求回顯該請求。
標志 NLM_F_ECHO 與 NLM_F_ACK 標志類似。它可以與 NLM_F_REQUEST 標志結合使用,使得作為請求結果而發送的通知,無論發送方是否已訂閱相應的多播組,都會被發送給發送方。
還定義了一些僅適用于 GET 請求的額外通用消息標志:
#define NLM_F_ROOT 0x100
#define NLM_F_MATCH 0x200
#define NLM_F_ATOMIC 0x400
#define NLM_F_DUMP (NLM_F_ROOT|NLM_F_MATCH)
- NLM_F_ROOT - 基于樹的根節點返回數據。
- NLM_F_MATCH - 返回所有匹配的條目。
- NLM_F_ATOMIC - 已過時,曾經用于請求一個原子操作。
- NLM_F_DUMP - 返回所有對象的列表(NLM_F_ROOT|NLM_F_MATCH)。
這些標志的使用完全是可選的,許多 Netlink 協議僅使用 NLM_F_DUMP 標志,該標志通常請求接收方以多部分消息序列的形式,發送與消息類型相關的所有對象的列表。
還有另一組與 NEW 或 SET 請求相關的標志。這些標志與 GET 請求的標志相互排斥:
#define NLM_F_REPLACE 0x100
#define NLM_F_EXCL 0x200
#define NLM_F_CREATE 0x400
#define NLM_F_APPEND 0x800
- NLM_F_REPLACE - 如果對象已存在,則替換該現有對象。
- NLM_F_EXCL - 如果對象已經存在,則不更新該對象。
- NLM_F_CREATE - 如果對象尚不存在,則創建該對象。
- NLM_F_APPEND - 在列表末尾添加對象。
這些標志的行為在不同的 Netlink 協議之間可能會略有不同。
2.4 序列號
Netlink 允許使用序列號來幫助將回復與請求相關聯。需要注意的是,與 TCP 等協議不同,Netlink 對序列號沒有嚴格的強制要求。序列號的唯一目的是幫助發送方將回復與相應的請求關聯起來。序列號是在每個套接字的基礎上進行管理的。
3. Netlink套接字
3.1 Socket實例
為了使用 Netlink 協議,需要一個 Netlink 套接字。每個套接字都定義了一個獨立的消息發送和接收上下文。一個應用程序可以使用多個套接字,例如,一個套接字用于發送請求并接收回復,另一個套接字訂閱多播組以接收通知。
Netlink 套接字以及所有相關屬性(包括實際的文件描述符)都由struct nl_sock
結構體表示。
#include <netlink/socket.h>struct nl_sock *nl_socket_alloc(void)
void nl_socket_free(struct nl_sock *sk)
應用程序必須為其希望使用的每個 Netlink 套接字分配一個struct nl_sock
結構體的實例。
3.2 序列號
該庫會自動為應用程序處理序列號。在套接字結構中存儲有一個序列號計數器,當需要發送預期會產生回復(如錯誤消息或任何其他需要與原始消息相關聯的消息類型)的消息時,會自動使用并遞增該計數器。
或者,也可以通過函數nl_socket_use_seq()
直接使用該計數器。它將返回計數器的當前值,然后將其遞增 1。
#include <netlink/socket.h>unsigned int nl_socket_use_seq(struct nl_sock *sk);
不過,大多數應用程序并不希望自己處理序列號。當使用nl_send_auto()
函數時,序列號會自動填入,并且在收到回復時會再次進行匹配。
如果所實現的 Netlink 協議不使用請求 / 回復模型,例如當套接字用于接收通知消息時,這種(自動處理序列號的)行為可以并且必須被禁用。
#include <netlink/socket.h>void nl_socket_disable_seq_check(struct nl_sock *sk);
3.3 多播組
每個套接字可以訂閱其所連接的 Netlink 協議的任意數量的多播組。然后,該套接字將接收發送到任何一個多播組的每條消息的副本。多播組通常用于實現事件通知。
在內核版本 2.6.14 之前,組訂閱是使用位掩碼來執行的,這將每個協議族的組數量限制為 32 個。即使不建議在新代碼中使用,仍然可以通過函數nl_join_groups()
訪問這個過時的接口。
#include <netlink/socket.h>void nl_join_groups(struct nl_sock *sk, int bitmask);
從內核版本 2.6.14 開始引入了一種新方法,該方法支持訂閱幾乎無限數量的多播組。
#include <netlink/socket.h>int nl_socket_add_memberships(struct nl_sock *sk, int group, ...);
int nl_socket_drop_memberships(struct nl_sock *sk, int group, ...);
3.4 回調函數配置
每個套接字都被分配一個回調配置,該配置控制套接字的行為。例如,這對于每個套接字擁有一個單獨的消息接收函數是必要的。不過,在套接字之間共享回調配置也是完全可行的。
可以使用以下函數來訪問和設置套接字的回調配置:
#include <netlink/socket.h>struct nl_cb *nl_socket_get_cb(const struct nl_sock *sk);
void nl_socket_set_cb(struct nl_sock *sk, struct nl_cb *cb);
此外,還存在一種快捷方式,可以直接修改分配給套接字的回調配置:
#include <netlink/socket.h>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);
3.5 套接字屬性
本地端口號唯一標識該套接字,并用于對其進行尋址。在分配套接字時,會自動生成一個唯一的本地端口號。它將由進程 ID(22 位)和一個隨機數(10 位)組成,因此每個進程最多允許有 1024 個套接字。
#include <netlink/socket.h>uint32_t nl_socket_get_local_port(const struct nl_sock *sk);
void nl_socket_set_local_port(struct nl_sock *sk, uint32_t port);
注意:可以覆蓋本地端口號,但你必須確保所提供的值是唯一的,并且任何其他應用程序中的其他套接字都沒有使用相同的值。
可以為套接字分配一個對等端口,這將導致通過該套接字發送的所有單播消息都被發送到該對等方。如果未指定對等方,則消息將發送到內核,內核將嘗試自動將該套接字綁定到同一 Netlink 協議族的內核端套接字。通常的做法是不將套接字綁定到對等端口,因為每個 Netlink 協議族通常只存在一個內核端套接字。
#include <netlink/socket.h>uint32_t nl_socket_get_peer_port(const struct nl_sock *sk);
void nl_socket_set_peer_port(struct nl_sock *sk, uint32_t port);
Netlink 使用 BSD 套接字接口,因此每個套接字背后都有一個文件描述符,你可以直接使用它。
#include <netlink/socket.h>int nl_socket_get_fd(const struct nl_sock *sk);
如果一個套接字僅用于接收通知,通常最好將該套接字設置為非阻塞模式,并定期輪詢以獲取新的通知。
#include <netlink/socket.h>int nl_socket_set_nonblocking(const struct nl_sock *sk);
套接字緩沖區用于在發送方和接收方之間對 Netlink 消息進行排隊。這些緩沖區的大小指定了你能夠寫入 Netlink 套接字的最大大小,也就是說,它將間接定義最大消息大小。默認大小是 32KiB。
#include <netlink/socket.h>int nl_socket_set_buffer_size(struct nl_sock *sk, int rx, int tx);
以下函數允許在套接字上啟用 / 禁用自動確認模式。自動確認模式默認是啟用的。
#include <netlink/socket.h>void nl_socket_enable_auto_ack(struct nl_sock *sk);
void nl_socket_disable_auto_ack(struct nl_sock *sk);
啟用/禁用消息窺探(Message Peeking)。如果啟用,消息窺探會使nl_recv函數嘗試使用 MSG_PEEK 來檢索接收到的下一條消息的大小,并分配一個該大小的緩沖區。消息窺探默認是啟用的,但可以使用以下函數將其禁用:
#include <netlink/socket.h>void nl_socket_enable_msg_peek(struct nl_sock *sk);
void nl_socket_disable_msg_peek(struct nl_sock *sk);
啟用 / 禁用接收數據包信息。如果啟用,從內核接收到的每條 Netlink 消息都將在控制消息中包含一個額外的struct nl_pktinfo
結構體。可以使用以下函數來啟用 / 禁用接收數據包信息。
#include <netlink/socket.h>int nl_socket_recv_pktinfo(struct nl_sock *sk, int state);
注意,Netlink Pktinfo的處理功能還沒有實現。