Netlink 是 Linux 內核與用戶態進程雙向通信的標準機制,基於 BSD Socket(AF_NETLINK)。優於 /proc、ioctl 的單向限制,支持全雙工、異步、多播(總線模式)、內核主動推送。
struct sockaddr_nl {
sa_family_t nl_family; // AF_NETLINK
unsigned short nl_pad; // 填充 0
__u32 nl_pid; // 端口 ID(通常為進程 PID)
__u32 nl_groups; // 多播組掩碼(每 bit 代表一組)
};
nl_pid:唯一標識一個 Netlink 通道,內核為 0,用戶態通常用 getpid()。nl_groups:加入多播組掩碼,bind() 時設置。struct nlmsghdr {
__u32 nlmsg_len; // 整個消息長度(含頭)
__u16 nlmsg_type; // 消息類型
__u16 nlmsg_flags; // 標誌位
__u32 nlmsg_seq; // 序列號
__u32 nlmsg_pid; // 發送進程 PID
};
消息體通過 NLMSG_DATA(nlh) 獲取。控制消息類型:NLMSG_NOOP、NLMSG_ERROR、NLMSG_DONE、NLMSG_OVERRUN。
int sock_fd = socket(AF_NETLINK, SOCK_RAW, NETLINK_TEST);
// 綁定本地地址
struct sockaddr_nl src = { .nl_family = AF_NETLINK, .nl_pid = 100 };
bind(sock_fd, (struct sockaddr*)&src, sizeof(src));
// 構造消息
struct nlmsghdr *nlh = malloc(NLMSG_SPACE(MAX_PAYLOAD));
nlh->nlmsg_len = NLMSG_SPACE(MAX_PAYLOAD);
nlh->nlmsg_pid = 100;
nlh->nlmsg_flags = 0;
strcpy(NLMSG_DATA(nlh), "Hello kernel!");
// 目標地址(內核 pid=0)
struct sockaddr_nl dst = { .nl_family = AF_NETLINK, .nl_pid = 0 };
struct iovec iov = { .iov_base = nlh, .iov_len = nlh->nlmsg_len };
struct msghdr msg = { .msg_name = &dst, .msg_namelen = sizeof(dst), .msg_iov = &iov, .msg_iovlen = 1 };
sendmsg(sock_fd, &msg, 0);
// 接收回復
recvmsg(sock_fd, &msg, 0);
printf("Reply: %s\n", (char*)NLMSG_DATA(nlh));
| 位置 | 代碼 | 作用 |
|---|---|---|
| bind 地址 | src.nl_pid = 100 | 註冊本進程接收通道,如信箱門牌號 |
| 目標地址 | dst.nl_pid = 0 | 指定接收方(0=內核),如收信地址 |
| 消息頭 | nlh->nlmsg_pid = 100 | 告知內核回復地址,如信封上的寄件人地址 |
若三者不一致會導致無法收到回復或發送給錯誤進程。
sendto(sock_fd, nlh, nlh->nlmsg_len, 0, (struct sockaddr*)&dst, sizeof(dst)); recvfrom(sock_fd, nlh, MAX_PAYLOAD, 0, NULL, NULL);
仍需 bind() 註冊本地 pid,否則內核無法回復。
// 創建 Netlink socket
struct sock *netlink_kernel_create(struct net *net, int unit, unsigned int groups,
void (*input)(struct sk_buff *skb), struct mutex *cb_mutex, struct module *module);
// 單播發送
int netlink_unicast(struct sock *ssk, struct sk_buff *skb, u32 pid, int nonblock);
// 多播發送
int netlink_broadcast(struct sock *ssk, struct sk_buff *skb, u32 pid, u32 group, gfp_t allocation);
// 釋放
void netlink_kernel_release(struct sock *sk);
void nl_data_ready(struct sk_buff *skb) {
struct nlmsghdr *nlh = nlmsg_hdr(skb);
char *data = NLMSG_DATA(nlh);
u32 pid = nlh->nlmsg_pid; // 獲取發送進程 pid
// 處理數據...
// 回復用戶態
struct sk_buff *skb_out = alloc_skb(NLMSG_SPACE(MAX), GFP_KERNEL);
struct nlmsghdr *nlh_out = nlmsg_put(skb_out, 0, 0, 0, MAX, 0);
NETLINK_CB(skb_out).pid = 0; // 內核 pid
memcpy(NLMSG_DATA(nlh_out), "Reply from kernel", 18);
netlink_unicast(nl_sk, skb_out, pid, MSG_DONTWAIT);
}
sendmsg 會阻塞直到內核回調 input() 返回。回調中耗時操作會卡死用戶進程(D 狀態)。recvmsg 非阻塞讀取。bind 時 pid=0 會與內核衝突,導致死循環掛機。NETLINK_TEST 下 pid 唯一,嘗試雙進程 bind 會報 Address already in use,SO_REUSEADDR 無效。netlink_unicast 即可,無需用戶態先發起。Netlink 為內核與用戶態提供了靈活、高效的雙向通信通道,掌握 sockaddr_nl、nlmsghdr、sendmsg/recvmsg 與內核 netlink_kernel_create、netlink_unicast 核心 API,即可實現路由管理、監控事件訂閱、容器網絡配置等高級場景。