linux / Generic Netlink

本文介绍了GenericNetlink的设计目的和基本原理,包括其消息结构、关键数据类型和如何使用这些结构来构建和解析消息。深入探讨了struct nlmsghdr、struct genlmsghdr和struct nlattr等核心数据结构。

一、概述

Generic Netlink 是内核专门为了扩展 netlink 协议簇而设计的“通用netlink协议簇”。由于 netlink 协议最多支持 32 个协议簇,目前 Linux4.1 的内核中已经使用其中 21 个,对于用户需要定制特殊的协议类型略显不够,而且用户还需自行在 include/linux/netlink.h 中添加簇定义,但有时不方便,为此 Linux 设计了这种通用 Netlink 协议簇,用户可在此之上定义更多类型的子协议。

Generic Netlink 使用 NETLINK_GENERIC 类型协议簇,同样基于 netlink 子系统。具体框架如下:         

Genetlink 消息基于这个消息结构类型并定制化为如下结构:

其中 family 头对于 Genetlink 来说就是 Generic 消息头 genlmsghdr,接下来是可选的用户特定消息头,最后才是可选的有效载荷,即一个个消息属性实例。Genetlink 消息是命令驱动式的,即每一条消息的 genlmsghdr 中都指明了当前消息的 cmd 消息命令,这些消息 cmd 命令由用户自行定义。内核在接收到用户的 genl 消息后,首先会对命令 cmd 做判断,找到对应的消息处理结构(可能会执行 attr 有效性检查),然后才会去调用消息处理回调函数从消息载荷区中读取并处理其所需要的的 attr 属性载荷。

二、主要数据

2.1、struct nlmsghdr

struct nlmsghdr {
	__u32		nlmsg_len;	/* Length of message including header */
	__u16		nlmsg_type;	/* Message content */
	__u16		nlmsg_flags;	/* Additional flags */
	__u32		nlmsg_seq;	/* Sequence number */
	__u32		nlmsg_pid;	/* Sending process port ID */
};

struct nlmsghdr 为 netlink socket 自己的消息头,netlink 的内核实现将利用这个消息头来多路复用和多路分解以及其它的一些控制,因此它也被称为netlink 控制块。因此,应用在发送 netlink 消息时必须提供该消息头。

(1)字段 nlmsg_len 指定消息的总长度,包括紧跟该结构的数据部分长度以及该结构的大小;

(2)字段 nlmsg_type 用于应用内部定义消息的类型,它对 netlink 内核实现是透明的,因此大部分情况下设置为 0;

字段 nlmsg_flags 用于设置消息标志,可用的标志包括:

/* 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_REQUEST 用于表示消息是一个请求,所有应用首先发起的消息都应设置该标志。
  • 标志 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 指示在表末尾添加新的条目。

(3)内核需要读取和修改这些标志,对于一般的使用,用户把它设置为 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 用于把多个消息通过一次系统调用来发送,下面是该结构使用示例:

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);

2.2、struct genlmsghdr

struct genlmsghdr {
    __u8    cmd;
    __u8    version;
    __u16    reserved;
};

Generic Netlink 消息头结构。Generic Netlink 消息头比较简单,仅包含了两个字段。

cmd 表示消息命令,对于用户自己定义的每个子协议类型都需要定义特定的消息命令集,这里该字段表示当前消息的消息命令;

version 字段表示版本控制,可以在在不破坏向后兼容性的情况下修改消息的格式,可以不使用该字段;

reserved 字段保留。

2.3、struct nlattr

从 nlattr 类型定义来看,是非常简单,但是 OVS 使用该数据结构来定义 action,nlattr 自身是不存储数据,而使用来定义数据格式的,OVS 也会使用 skb 来存储真实数据。

nlattr 数据结构定义

struct nlattr {
    uint16_t nla_len;     //数据长度
    uint16_t nla_type;    //数据类型
};

使用 nlattr 构造的数据结构如下图所示:

相关函数的定义如下:

nla_len 函数

static inline int nla_len(const struct nlattr *nla)   //返回数据的长度
{
	return nla->nla_len - NLA_HDRLEN;     //数据总长度减去头长度
}

nla_data 函数

static inline void *nla_data(const struct nlattr *nla)   //返回数据的首地址
{
	return (char *) nla + NLA_HDRLEN;   //当前指针加上头长度
}

nla_next 函数

//返回下一个数据的首地址
static inline struct nlattr *nla_next(const struct nlattr *nla, int *remaining)    
{
	int totlen = NLA_ALIGN(nla->nla_len);
    // //当remaining为0时,说明数据已经取完,返回值不能使用
	*remaining -= totlen;<span style="white-space:pre">					</span>
	return (struct nlattr *) ((char *) nla + totlen);
}

(SAW:Game Over!)

<think>我们正在处理一个关于使用Netlink套接字配置MACsec的C代码分析请求。用户希望分析示例代码,该代码通过Netlink套接字来配置MACsec。 首先,我们需要回顾一下MACsec和Netlink的基本概念: - MACsec(Media Access Control Security)是一种在数据链路层提供安全通信的协议,它可以对以太网帧进行加密和完整性保护。 - NetlinkLinux内核与用户空间进程之间进行通信的一种机制,它使用套接字API,支持多种协议(如NETLINK_ROUTE用于路由,NETLINK_GENERIC用于通用目的等)。MACsec通常使用通用NetlinkGeneric Netlink)子系统进行配置。 由于用户没有提供具体的示例代码,我们将讨论一个典型的通过Netlink配置MACsec的C代码框架。通常,这样的代码会包括以下步骤: 1. 创建Netlink套接字 2. 构造Netlink消息(包括通用Netlink头、消息头和属性) 3. 发送消息到内核 4. 接收并解析内核的响应 下面是一个简化的示例,展示如何创建一个MACsec安全通道(secure channel)。注意,这只是一个框架,实际代码会更复杂。 ```c #include <stdio.h> #include <stdlib.h> #include <string.h> #include <unistd.h> #include <sys/socket.h> #include <linux/netlink.h> #include <linux/genetlink.h> #include <linux/if_macsec.h> // 定义通用Netlink的MACsec族名称(具体名称可能因内核版本而异) #define MACSEC_GENL_NAME "macsec" // 创建Netlink套接字 static int create_nl_socket(void) { struct sockaddr_nl sa; int fd; fd = socket(AF_NETLINK, SOCK_RAW, NETLINK_GENERIC); if (fd < 0) { perror("socket"); return -1; } memset(&sa, 0, sizeof(sa)); sa.nl_family = AF_NETLINK; sa.nl_pid = getpid(); // 当前进程的PID sa.nl_groups = 0; // 单播 if (bind(fd, (struct sockaddr *)&sa, sizeof(sa)) < 0) { perror("bind"); close(fd); return -1; } return fd; } // 获取MACsec的通用Netlink族ID static int get_macsec_family_id(int fd) { // 这里需要发送一个CTRL_CMD_GETFAMILY消息来获取macsec的族ID // 由于代码较长,这里省略具体实现,实际中需要构造消息并发送,然后解析响应得到族ID // 返回一个示例值(实际中需要动态获取) return 0; } // 构造一个创建安全通道的Netlink消息 static void build_create_sc_msg(char *buf, int ifindex, __u64 sci) { // 这里需要构造一个完整的通用Netlink消息,包括: // 1. Netlink消息头(nlmsghdr) // 2. 通用Netlink消息头(genlmsghdr) // 3. 属性(attrs)包括:IFINDEX, SCI等 // 由于代码较长,这里省略具体实现 } int main() { int fd, family_id; char buf[4096]; struct nlmsghdr *nlh; fd = create_nl_socket(); if (fd < 0) { fprintf(stderr, "Failed to create netlink socket\n"); return 1; } family_id = get_macsec_family_id(fd); if (family_id < 0) { fprintf(stderr, "Failed to get macsec family id\n"); close(fd); return 1; } // 假设我们要在接口索引为1的网卡上创建一个安全通道,SCI为0x1234567890ABCDEF build_create_sc_msg(buf, 1, 0x1234567890ABCDEF); // 发送消息 nlh = (struct nlmsghdr *)buf; if (send(fd, nlh, nlh->nlmsg_len, 0) < 0) { perror("send"); close(fd); return 1; } // 接收响应(这里省略接收和解析响应的代码) close(fd); return 0; } ``` 上述代码只是一个非常简化的框架,实际开发中需要处理很多细节,例如: - 正确构造通用Netlink消息(包括属性嵌套) - 处理多个消息(如果响应被分成多个消息) - 错误处理 在分析MACsec配置的Netlink消息时,我们需要参考Linux内核中MACsec的Netlink接口定义。这些定义通常在内核头文件(如`linux/if_macsec.h`)和内核文档中。 根据用户提供的引用,我们注意到引用[3]提到了`ip link`命令,该命令用于配置网络设备。实际上,`ip`命令的源码(iproute2包)中包含了使用Netlink与内核通信的代码,可以作为参考。 由于用户请求分析示例代码,我们假设用户已经有一段代码,并希望理解它。在没有具体代码的情况下,我们只能提供一般性指导。 **关键步骤分析:** 1. **创建Netlink套接字**:使用`socket(AF_NETLINK, SOCK_RAW, NETLINK_GENERIC)`创建套接字。 2. **绑定套接字**:绑定到当前进程的PID。 3. **获取MACsec的通用Netlink族ID**:通过发送`CTRL_CMD_GETFAMILY`命令到`NETLINK_GENERIC`的控制族(族ID为`GENL_ID_CTRL`)来获取。 4. **构造消息**: - 消息头:`struct nlmsghdr`,设置消息长度、类型(即族ID)、标志(如NLM_F_REQUEST)、序列号和端口号(PID)。 - 通用Netlink头:`struct genlmsghdr`,设置命令(如`MACSEC_CMD_NEW_TXSC`)、版本等。 - 属性:使用`nla_put`系列函数添加属性,如`MACSEC_ATTR_IFINDEX`(接口索引)、`MACSEC_ATTR_SCI`(安全通道标识符)等。 5. **发送消息**:使用`send`函数发送。 6. **接收响应**:使用`recv`接收,然后解析响应(包括错误检查和属性解析)。 **注意事项:** - 属性必须按4字节对齐。 - 每个属性包括一个头部(`struct nlattr`)和有效载荷。 - 嵌套属性使用`nla_nest_start`和`nla_nest_end`。 **问题分析:** 用户可能遇到的困难包括: - 不理解Netlink消息的结构。 - 不知道MACsec的Netlink命令和属性。 - 不知道如何处理变长的消息和属性。 因此,我们建议用户参考以下资源: - Linux内核源代码中的`include/uapi/linux/genetlink.h`(通用Netlink头) - `include/uapi/linux/macsec.h`(MACsec的Netlink定义) - `iproute2`源码中的`ip/ipmacsec.c`(MACsec配置的实现) **相关问题:** 根据用户要求,我们需要生成相关问题:</think>### 分析通过Netlink套接字配置MACsec的C代码关键步骤 #### 1. **创建Netlink套接字** - 使用`socket()`系统调用创建**通用Netlink套接字**: ```c int fd = socket(AF_NETLINK, SOCK_RAW, NETLINK_GENERIC); ``` - 绑定套接字到当前进程PID: ```c struct sockaddr_nl sa = { .nl_family = AF_NETLINK, .nl_pid = getpid(), // 当前进程ID .nl_groups = 0 // 单播通信 }; bind(fd, (struct sockaddr*)&sa, sizeof(sa)); ``` - **关键点**:需启用`NETLINK_GENERIC`协议族以支持MACsec操作[^3]。 --- #### 2. **构造Netlink消息** MACsec配置需嵌套三层结构: ```mermaid graph LR A[Netlink消息头 nlmsghdr] --> B[通用Netlink头 genlmsghdr] B --> C[属性列表 nlattr] C --> D[MACSec属性嵌套] ``` - **a. 消息头** (`struct nlmsghdr`) - 设置消息类型为`NLM_F_REQUEST | NLM_F_ACK`(请求+确认) - 填充序列号(`nlmsg_seq`)和端口号(`nlmsg_pid`) - **b. 通用头** (`struct genlmsghdr`) - 命令字段:`MACSEC_CMD_NEW_TXSC`(创建发送安全通道)或`MACSEC_CMD_NEW_SA`(创建安全关联) - 版本字段:设置为`MACSEC_GENL_VERSION`(通常为1) - **c. 属性嵌套** (`struct nlattr`) - 核心属性类型: - `MACSEC_ATTR_IFINDEX`:网络接口索引 - `MACSEC_ATTR_SCI`:安全通道标识符(8字节) - `MACSEC_ATTR_SA_CONFIG`:嵌套的安全关联配置 - **示例属性构造**: ```c struct nlattr *nest = nla_nest_start(msg, MACSEC_ATTR_SA_CONFIG); nla_put_u8(msg, MACSEC_SA_ATTR_AN, 0); // 关联号 nla_put_u32(msg, MACSEC_SA_ATTR_PN, 1); // 包号 nla_put(msg, MACSEC_SA_ATTR_KEY, key_len, key); // 加密密钥 nla_nest_end(msg, nest); ``` --- #### 3. **发送与接收消息** - **发送配置请求**: ```c send(fd, nlmsg, nlmsg->nlmsg_len, 0); ``` - **解析响应**: - 检查`nlmsghdr->nlmsg_type`是否为`NLMSG_ERROR` - 分析错误码:`((struct nlmsgerr *)NLMSG_DATA(nlh))->error` - **关键检查**:若返回`-EINVAL`,需验证属性格式是否正确 --- #### 4. **MACsec特定配置示例** **创建接收安全通道**: ```c struct nlattr *attrs[__MACSEC_ATTR_MAX]; attrs[MACSEC_ATTR_IFINDEX] = nla_put_u32(msg, ifindex); attrs[MACSEC_ATTR_RXSC_CONFIG] = nla_nest_start(msg, ...); nla_put_u64_64bit(msg, MACSEC_RXSC_ATTR_SCI, sci, MACSEC_RXSC_ATTR_PAD); nla_nest_end(msg, attrs[MACSEC_ATTR_RXSC_CONFIG]); ``` --- #### 5. **错误处理与调试** - **常见错误码**: | 错误码 | 含义 | 解决方案 | |--------|-----------------------|------------------------| | `-EINVAL` | 无效参数 | 检查属性长度和类型 | | `-ENODEV` | 网络接口不存在 | 验证`ifindex`有效性 | | `-EEXIST` | 安全通道已存在 | 检查重复配置 | - **调试建议**: - 使用`strace`跟踪系统调用 - 启用内核日志:`dmesg -T | grep macsec`
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值