NetLink内核套接字案例分析

server/2025/3/15 6:00:13/

一、基础知识

        Netlink 是 Linux 系统中一种内核与用户空间通信的高效机制,而 Netlink 消息是这种通信的核心载体。它允许用户态程序(如网络配置工具、监控工具)与内核子系统(如网络协议栈、设备驱动)交换数据,例如获取网络接口信息、配置路由表、接收内核事件通知等。


Netlink 消息的组成

一个完整的 Netlink 消息由两部分构成:

  1. 消息头(struct nlmsghdr
    定义消息的元信息,例如消息类型、长度、序列号等。

    struct nlmsghdr {__u32 nlmsg_len;    // 消息总长度(头部 + 数据)__u16 nlmsg_type;   // 消息类型(如请求、响应、错误)__u16 nlmsg_flags;  // 标志位(如请求标志、多部分消息标志)__u32 nlmsg_seq;    // 序列号(用于匹配请求和响应)__u32 nlmsg_pid;    // 发送方端口ID(通常为进程ID)
    };
  2. 消息体(Payload)
    具体的数据内容,格式由消息类型决定。例如:

    • 路由消息:struct rtgenmsg(指定地址族)

    • 接口信息:struct ifinfomsg(接口索引、状态等)

    • 属性列表:动态附加的属性(如接口名称、MAC地址等)。


Netlink 消息的作用

Netlink 消息的核心功能是双向通信

1. 用户空间 → 内核

用户程序通过发送 Netlink 消息向内核发起操作请求。例如:

  • 查询信息RTM_GETLINK(获取网络接口列表)、RTM_GETROUTE(获取路由表)。

  • 配置内核RTM_NEWLINK(创建新接口)、RTM_SETLINK(修改接口属性)。

2. 内核 → 用户空间

内核通过 Netlink 消息主动通知用户程序事件。例如:

  • 接口状态变化网络接口启用/禁用。

  • 新设备插入:USB 设备连接、Wi-Fi 网络扫描结果。

  • 路由表更新:路由条目添加或删除。


为什么用 Netlink?

与其他内核通信方式相比,Netlink 的优势在于:

机制特点适用场景
Netlink双向、异步、支持多播、结构化数据、可扩展动态配置和实时事件通知
Sysfs通过文件系统操作(/sys),读写简单但效率低静态配置(如设置参数)
Procfs通过文件系统(/proc),主要用于状态查询读取系统信息(如进程状态)
ioctl通过设备文件操作,接口不统一,扩展性差设备驱动特定操作
Netlink 的独特优势
  1. 结构化数据
    消息通过二进制格式传递,避免了文本解析(如 procfs/sysfs)的开销。

  2. 异步通信
    支持非阻塞通信,用户程序无需等待内核响应。

  3. 多播支持
    内核可以向多个用户进程广播事件(如接口状态变化)。

  4. 可扩展性
    通过消息类型(nlmsg_type)和属性(struct rtattr)灵活扩展功能。


Netlink 消息的工作流程

以获取网络接口列表为例:

  1. 用户程序构造请求消息

    • 设置 nlmsghdrnlmsg_type = RTM_GETLINKnlmsg_flags = NLM_F_DUMP

    • 设置 rtgenmsgrtgen_family = AF_UNSPEC(获取所有接口)。

  2. 发送消息到内核
    通过 sendmsg 系统调用发送 Netlink 消息。

  3. 内核处理请求
    路由子系统解析消息,收集所有网络接口信息,封装为多个 Netlink 消息(可能分片)。

  4. 用户程序接收响应
    通过 recvmsg 读取消息,解析 nlmsghdr 和消息体,提取接口名称、状态等数据。


典型应用场景

  1. 网络配置工具
    iproute2 工具集(如 ip linkip route)底层使用 Netlink 配置网络

  1. 设备监控
    监听内核事件,如接口状态变化、新设备连接。

  2. 防火墙和策略路由
    配置 netfilter(iptables/nftables)规则或复杂路由策略。

  3. 容器网络
    容器运行时(如 Docker)通过 Netlink 管理虚拟网络设备。

 

1.nlinterfaces.c

// 程序功能:应用Netlink套接字从Linux内核打印输出所有网络接口名称
#include <bits/types/struct_iovec.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <unistd.h>
#include <sys/socket.h>
#include <linux/netlink.h>  //Netlink协议相关定义
#include <linux/rtnetlink.h> // 路由相关的Netlink消息定义#define BUFSIZE 10240//定义一个自定义结构体ln_request_s,它包含一个 Netlink 消息头nlmsghdr和一个路由通用消息结构体rtgenmsg
struct In_request_s{//Netlink消息头struct nlmsghdr hdr;//路由消息通用结构,指定地址族struct rtgenmsg gen;
};//功能:解析并打印网络接口信息
void rtnl_print_link(struct nlmsghdr *h){//struct ifinfomsg *iface:指向 Netlink 消息中包含的网络接口信息结构体struct ifinfomsg *iface;//struct rtattr *attr:指向路由属性结构体struct  rtattr *attr;int len = 0;//获取 Netlink 消息中实际的数据部分,计算方式:消息头地址 + 头部大小iface = NLMSG_DATA(h);//获取 Netlink 消息中有效负载的长度len = RTM_PAYLOAD(h);//遍历路由属性for(attr = IFLA_RTA(iface);RTA_OK(attr,len);attr = RTA_NEXT(attr,len)){switch (attr->rta_type){//如果属性是接口名称就打印case IFLA_IFNAME:printf("接口名称%d : %s\n", iface->ifi_index, (char *)RTA_DATA(attr));break;default:break;}}
}int main(int argc,char *argv[]){//Netlink地址结构,用于绑定套接字struct sockaddr_nl nkernel;//消息头结构,用于 sendmsg 和 recvmsgstruct msghdr msg;//分散、聚集I/O结构,用于消息传输,主要跟readv、writev等缓冲区合并有关,用于一次I/O操作处理多个缓冲区struct iovec io;//自定义请求结构struct In_request_s req;//s为套接字描述符,end为循环结束标志int s = -1, end = 0, ret;//接收缓冲区char buf[BUFSIZE];//初始化Netlink地址结构memset(&nkernel,0,sizeof(nkernel));nkernel.nl_family = AF_NETLINK;    //这里与内核通信所以不使用AF_INETnkernel.nl_groups = 0; // 不加入任何组播组//创建套接字/*AF_NETLINK: 使用 Netlink 协议族。SOCK_RAW: 原始套接字类型,允许直接操作 Netlink 消息。NETLINK_ROUTE: 路由子系统,用于获取网络接口和路由信息。*/if ((s = socket(AF_NETLINK, SOCK_RAW, NETLINK_ROUTE)) < 0){printf("创建Netlink套接字失败.\n");exit(EXIT_FAILURE);}//构造Netlink请求消息memset(&req, 0, sizeof(req));//#define NLMSG_LENGTH(len) ((len) + NLMSG_ALIGN(sizeof(struct nlmsghdr)))//nlmsg_len: 消息总长度(头部 + rtgenmsg 结构体),通过 NLMSG_LENGTH 计算对齐后的长度req.hdr.nlmsg_len = NLMSG_LENGTH(sizeof(struct rtgenmsg));//请求获取网络接口信息req.hdr.nlmsg_type = RTM_GETLINK;//标志为请求消息,并要求返回所有条目req.hdr.nlmsg_flags = NLM_F_REQUEST | NLM_F_DUMP;//序列号,用于匹配请求和响应req.hdr.nlmsg_seq = 1;//发送方进程 IDreq.hdr.nlmsg_pid = getpid();//指定地址族为 IPv4(可改为 AF_UNSPEC 获取所有接口)req.gen.rtgen_family = AF_INET;//设置I/O向量和消息头memset(&io, 0, sizeof(io));io.iov_base = &req;io.iov_len = req.hdr.nlmsg_len;memset(&msg, 0, sizeof(msg));msg.msg_iov = &io;          // 指向 I/O 向量msg.msg_iovlen = 1;         // 向量数量为 1msg.msg_name = &nkernel;    // 目标地址(内核)msg.msg_namelen = sizeof(nkernel);//发送请求消息if ((ret = sendmsg(s, &msg, 0)) < 0) {perror("发送消息失败");close(s);exit(EXIT_FAILURE);}//接收并解析内核响应,,当接收到 NLMSG_DONE 消息时,end 会被置为 1,从而退出循环while(!end){//定义一个指向 nlmsghdr 结构体的指针 msg_ptr,用于遍历接收到的 Netlink 消息struct nlmsghdr *msg_ptr;//用于记录还未处理的消息长度int remaining_len;memset(buf, 0, BUFSIZE);io.iov_base = buf;io.iov_len = BUFSIZE;if ((ret = recvmsg(s, &msg, 0)) < 0) {if (errno == EINTR) continue;  // 处理中断perror("接收消息失败");close(s);exit(EXIT_FAILURE);}// 处理消息分片(NLMSG_TRUNC标志)if (msg.msg_flags & MSG_TRUNC) {fprintf(stderr, "警告:消息被截断,考虑增大缓冲区\n");}//将 msg_ptr 指针指向接收缓冲区 buf 的起始位置,将其视为第一个 Netlink 消息的头部msg_ptr = (struct nlmsghdr *)buf;//将 remaining_len 初始化为接收到的消息总长度 retremaining_len = ret;for (; NLMSG_OK(msg_ptr, remaining_len);   //NLMSG_OK(msg_ptr, remaining_len):这是一个宏,用于检查 msg_ptr 指向的 Netlink 消息是否有效,即消息长度是否足够且未超出剩余未处理的消息长度msg_ptr = NLMSG_NEXT(msg_ptr, remaining_len)) {  //将 msg_ptr 指针移动到下一个 Netlink 消息的头部,并更新 remaining_len 的值//内核在回复单播请求时,会将 nlmsg_pid 设置为用户进程的 PID(即 self_pid)if (msg_ptr->nlmsg_pid != getpid()) {fprintf(stderr, "收到非本进程的消息,已忽略 (PID: %u)\n", msg_ptr->nlmsg_pid);continue;}   switch (msg_ptr->nlmsg_type) {case NLMSG_ERROR: {  //如果消息类型为 NLMSG_ERROR,表示内核返回了错误信息struct nlmsgerr *err = NLMSG_DATA(msg_ptr);  //使用 NLMSG_DATA 宏获取消息中的错误信息结构体 nlmsgerr 的指针if (err->error != 0) {fprintf(stderr, "内核返回错误: %s\n", strerror(-err->error));close(s);exit(EXIT_FAILURE);}break;}case NLMSG_DONE: //如果消息类型为 NLMSG_DONE,表示内核已经发送完所有请求的信息,将 end 标志置为 1,退出循环end = 1;break;case RTM_NEWLINK: //如果消息类型为 RTM_NEWLINK,表示接收到了新的网络接口信息。调用 rtnl_print_link 函数处理该消息,打印网络接口的相关信息rtnl_print_link(msg_ptr);break;default:   //如果消息类型不是上述几种情况,输出忽略消息的信息,包含消息类型和消息长度printf("忽略消息:type=%d, len=%d\n",msg_ptr->nlmsg_type, msg_ptr->nlmsg_len);break;}}// 处理未对齐的剩余数据if (remaining_len > 0) {fprintf(stderr, "剩余%d字节未处理数据\n", remaining_len);}}close(s);  // 确保关闭套接字return 0;
}

编译运行:

二、ipaddress.c

// 显示IPv4,应用Netlink套接字从Linux内核中获取所有网络接口的IP地址
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <linux/netlink.h>
#include <linux/rtnetlink.h>
#include <errno.h>#define BUFFERSIZE 10240// 定义一个自定义结构体 netlink_reqest_s,它包含一个 Netlink 消息头 nlmsghdr 和一个路由通用消息结构体 rtgenmsg
struct netlink_reqest_s {// Netlink 消息头struct nlmsghdr hdr;// 路由消息通用结构,指定地址族struct rtgenmsg gen;
};// 功能:解析并打印网络接口的 IP 地址信息
void rtnetlink_disp_address(struct nlmsghdr *h) {// struct ifaddrmsg *addr:指向 Netlink 消息中包含的网络地址信息结构体struct ifaddrmsg *addr;// struct rtattr *attr:指向路由属性结构体struct rtattr *attr;// 用于记录 Netlink 消息中有效负载的长度int len;// 获取 Netlink 消息中实际的数据部分,计算方式:消息头地址 + 头部大小addr = NLMSG_DATA(h);// 获取 Netlink 消息中有效负载的长度len = RTM_PAYLOAD(h);/* 循环输出 Netlink 所有属性消息:网络接口名称及 IP 地址 */for (attr = IFA_RTA(addr); RTA_OK(attr, len); attr = RTA_NEXT(attr, len)) {switch (attr->rta_type) {// 如果属性是接口名称就打印case IFA_LABEL:printf("网络接口名称 : %s\n", (char *)RTA_DATA(attr));break;// 如果属性是本地 IP 地址就打印case IFA_LOCAL: {// 获取 IP 地址的二进制表示int ip = *(int *)RTA_DATA(attr);// 用于存储 IP 地址的四个字节unsigned char bytes[4];// 提取 IP 地址的四个字节bytes[0] = ip & 0xFF;bytes[1] = (ip >> 8) & 0xFF;bytes[2] = (ip >> 16) & 0xFF;bytes[3] = (ip >> 24) & 0xFF;// 打印网络 IP 地址printf("网络 IP 地址为 : %d.%d.%d.%d\n\n", bytes[0], bytes[1], bytes[2], bytes[3]);break;}default:break;}}
}int main(void) {// Netlink 地址结构,用于绑定套接字struct sockaddr_nl kerl;// 套接字描述符int s;// 循环结束标志int end = 0;// 接收到的消息长度int len;// 消息头结构,用于 sendmsg 和 recvmsgstruct msghdr msg;// 自定义请求结构struct netlink_reqest_s req;// 分散、聚集 I/O 结构,用于消息传输struct iovec io;// 接收缓冲区char buffer[BUFFERSIZE];// 初始化 Netlink 地址结构memset(&kerl, 0, sizeof(kerl));// 使用 Netlink 协议族kerl.nl_family = AF_NETLINK;// 不加入任何组播组kerl.nl_groups = 0;// 创建套接字/*AF_NETLINK: 使用 Netlink 协议族。SOCK_RAW: 原始套接字类型,允许直接操作 Netlink 消息。NETLINK_ROUTE: 路由子系统,用于获取网络接口和路由信息。*/if ((s = socket(AF_NETLINK, SOCK_RAW, NETLINK_ROUTE)) < 0) {perror("创建 Netlink 套接字失败");exit(EXIT_FAILURE);}// 构造 Netlink 请求消息memset(&req, 0, sizeof(req));// 消息总长度(头部 + rtgenmsg 结构体),通过 NLMSG_LENGTH 计算对齐后的长度req.hdr.nlmsg_len = NLMSG_LENGTH(sizeof(struct rtgenmsg));// 请求获取网络接口地址信息req.hdr.nlmsg_type = RTM_GETADDR;// 标志为请求消息,并要求返回所有条目req.hdr.nlmsg_flags = NLM_F_REQUEST | NLM_F_DUMP;// 序列号,用于匹配请求和响应req.hdr.nlmsg_seq = 1;// 发送方进程 IDreq.hdr.nlmsg_pid = getpid();// 指定地址族为 IPv4req.gen.rtgen_family = AF_INET;// 设置 I/O 向量和消息头memset(&io, 0, sizeof(io));io.iov_base = &req;io.iov_len = req.hdr.nlmsg_len;memset(&msg, 0, sizeof(msg));msg.msg_iov = &io;          // 指向 I/O 向量msg.msg_iovlen = 1;         // 向量数量为 1msg.msg_name = &kerl;       // 目标地址(内核)msg.msg_namelen = sizeof(kerl);// 发送请求消息if (sendmsg(s, &msg, 0) < 0) {perror("发送消息失败");close(s);exit(EXIT_FAILURE);}// 接收并解析内核响应,当接收到 NLMSG_DONE 消息时,end 会被置为 1,从而退出循环while (!end) {// 定义一个指向 nlmsghdr 结构体的指针 msg_ptr,用于遍历接收到的 Netlink 消息struct nlmsghdr *msg_ptr;// 用于记录还未处理的消息长度int remaining_len;// 清空接收缓冲区memset(buffer, 0, BUFFERSIZE);io.iov_base = buffer;io.iov_len = BUFFERSIZE;// 接收消息if ((len = recvmsg(s, &msg, 0)) < 0) {if (errno == EINTR) continue;  // 处理中断perror("接收消息失败");close(s);exit(EXIT_FAILURE);}// 处理消息分片(NLMSG_TRUNC 标志)if (msg.msg_flags & MSG_TRUNC) {fprintf(stderr, "警告:消息被截断,考虑增大缓冲区\n");}// 将 msg_ptr 指针指向接收缓冲区 buffer 的起始位置,将其视为第一个 Netlink 消息的头部msg_ptr = (struct nlmsghdr *)buffer;// 将 remaining_len 初始化为接收到的消息总长度 lenremaining_len = len;for (; NLMSG_OK(msg_ptr, remaining_len);  // NLMSG_OK(msg_ptr, remaining_len):这是一个宏,用于检查 msg_ptr 指向的 Netlink 消息是否有效,即消息长度是否足够且未超出剩余未处理的消息长度msg_ptr = NLMSG_NEXT(msg_ptr, remaining_len)) {  // 将 msg_ptr 指针移动到下一个 Netlink 消息的头部,并更新 remaining_len 的值// 内核在回复单播请求时,会将 nlmsg_pid 设置为用户进程的 PID(即 self_pid)if (msg_ptr->nlmsg_pid != getpid()) {fprintf(stderr, "收到非本进程的消息,已忽略 (PID: %u)\n", msg_ptr->nlmsg_pid);continue;}switch (msg_ptr->nlmsg_type) {// 如果消息类型为 NLMSG_ERROR,表示内核返回了错误信息case NLMSG_ERROR: {// 使用 NLMSG_DATA 宏获取消息中的错误信息结构体 nlmsgerr 的指针struct nlmsgerr *err = NLMSG_DATA(msg_ptr);if (err->error != 0) {fprintf(stderr, "内核返回错误: %s\n", strerror(-err->error));close(s);exit(EXIT_FAILURE);}break;}// 如果消息类型为 NLMSG_DONE,表示内核已经发送完所有请求的信息,将 end 标志置为 1,退出循环case NLMSG_DONE:end = 1;break;// 如果消息类型为 RTM_NEWADDR,表示接收到了新的网络接口地址信息。调用 rtnetlink_disp_address 函数处理该消息,打印网络接口的相关信息case RTM_NEWADDR:rtnetlink_disp_address(msg_ptr);break;// 如果消息类型不是上述几种情况,输出忽略消息的信息,包含消息类型和消息长度default:printf("忽略消息:type=%d, len=%d\n", msg_ptr->nlmsg_type, msg_ptr->nlmsg_len);break;}}// 处理未对齐的剩余数据if (remaining_len > 0) {fprintf(stderr, "剩余 %d 字节未处理数据\n", remaining_len);}}// 确保关闭套接字close(s);return 0;
}    

编译运行:


http://www.ppmy.cn/server/175075.html

相关文章

vue 仿deepseek前端开发一个对话界面

后端&#xff1a;调用deepseek的api&#xff0c;所以返回数据格式和deepseek相同 {"model": "DeepSeek-R1-Distill-Qwen-1.5B", "choices": [{"index": 0, "delta": {"role": "assistant", "cont…

HTML5拼图游戏开发经验分享

HTML5拼图游戏开发经验分享 这里写目录标题 HTML5拼图游戏开发经验分享前言项目架构1. 文件结构2. 核心功能模块 技术要点解析1. 响应式布局2. 图片处理3. 拖拽交互4. 动画效果 性能优化开发心得项目亮点总结源码分享写在最后 前言 在Web前端开发领域&#xff0c;通过实战项目…

带宽管理配置实验

一、实验拓扑 配置流程&#xff1a; 1、带宽通道&#xff1a;整体带宽、每个用户带宽、连接数、优先级信息 2、带宽策略 3、策略通道&#xff0c;引用 4、配置接口出入带宽 二、实验需求和配置 1、基础配置 接口配置 [dianxin]interface GigabitEthernet 0/0/0 [dianxin-G…

设计:用创意灵感勾勒界面灵魂,引领用户情感共鸣

在当今数字化飞速发展的时代&#xff0c;UI 设计已不再仅仅局限于构建美观的界面&#xff0c;它更是一门用创意灵感勾勒界面灵魂&#xff0c;从而引领用户情感共鸣的艺术。一个成功的 UI 设计能够在用户与产品之间搭建起一座无形的桥梁&#xff0c;使两者之间产生深度的情感连接…

《Python全栈开发》第8课:Python后端入门 - Flask框架基础

🌟 课程目标 掌握Flask开发环境搭建理解路由与视图函数工作原理学会处理请求与返回JSON响应完成第一个后端API开发一、Flask是什么?(餐厅服务员比喻) 1.1 前后端分工 #mermaid-svg-oHBE8WyDrAoZPoBY {font-family:"trebuchet ms",verdana,arial,sans-serif;fon…

HarmonyOS Next~HarmonyOS 应用开发利器:ArkData 深度解析

HarmonyOS 应用开发利器&#xff1a;ArkData 深度解析 ​ HarmonyOS 作为一款面向全场景的分布式操作系统&#xff0c;其应用开发工具链也在不断完善。ArkData 作为 HarmonyOS 应用开发中的重要一环&#xff0c;为开发者提供了高效、便捷的数据管理解决方案。本文将深入解析 A…

sqldef:一款免费的数据库变更管理工具

应用程序的升级通常伴随着数据库表结构的变更&#xff0c;为了维护各种环境的数据库变更&#xff0c;我们通常需要引入 Liquibase 或者 Flyaway 这样的数据库版本控制工具。不过&#xff0c;这类工具通常需要绑定某种编程语言&#xff0c;例如 Java&#xff1b;这次我们介绍一个…

kotlin与MVVM的结合使用总结(二)

在 MVVM&#xff08;Model - View - ViewModel&#xff09;架构中&#xff0c;M 层即 Model 层&#xff0c;主要负责数据的管理、存储和获取&#xff0c;它与业务逻辑和数据处理相关。在 Kotlin 中实现 MVVM 的 M 层&#xff0c;通常会涉及数据类的定义、数据的本地存储与远程获…