文档说明
本文档详细解析VPP中的DHCP插件实现,从DHCP协议基础到VPP的具体实现和增强特性,系统性地梳理整个DHCP插件的架构和源码细节。此篇文章主要讲解第六部分:DHCP Proxy实现详解,内容较多分为上下篇。
目录大纲
-
第一部分:DHCP协议基础知识 - 系统介绍DHCP协议的基本概念、DORA交互流程、DHCPv4/v6协议差异以及协议在网络中的角色定位,为理解VPP实现奠定理论基础。
-
第二部分:VPP DHCP插件功能概述 - 全面介绍VPP DHCP插件的功能特性、架构定位、API/CLI支持能力以及多线程处理机制,帮助读者建立对插件的整体认知。
-
第三部分:源码目录结构与模块划分 - 详细解析DHCP插件的源码文件组织方式、各模块职责划分、控制平面与数据平面分离设计,以及模块间的依赖关系。
-
第四部分:DHCPv4客户端实现详解 - 深入剖析DHCPv4客户端的核心数据结构、状态机实现、报文构造与解析、地址安装与路由更新等关键实现细节。
-
第五部分:DHCPv6客户端实现详解 - 全面解析DHCPv6客户端的CP/DP分离架构、IA_NA和IA_PD两种模式的实现机制、前缀委派功能以及DUID管理。
-
第六部分:DHCP Proxy实现详解 - 深入讲解DHCP代理/中继的架构设计、多服务器支持策略、VRF隔离机制、Option 82插入以及Cookie去重机制。
-
第七部分:API接口与CLI命令详解 - 系统说明所有DHCP相关的API接口定义、参数说明、使用示例以及CLI命令的完整语法和配置方法。
-
第八部分:调试与故障排查 - 详细介绍日志系统使用、统计计数器查看、调试命令操作、常见问题诊断方法以及数据包捕获分析技巧。
-
第九部分:扩展与定制 - 讲解如何基于源码添加新的DHCP选项支持、定制客户端和Proxy行为、与外部系统集成以及插件开发最佳实践。
-
第十部分:应用场景与配置实践 - 基于实际CLI命令和配置方法,详细说明边缘路由器、企业网络、多租户等典型应用场景的具体配置步骤和实现效果。
-
第十一部分:安全考虑 - 分析DHCP协议面临的安全威胁(欺骗攻击、DoS攻击等)、VPP提供的安全特性以及实际部署中的安全加固建议。
-
第十二部分:当前限制与未来发展方向 - 总结当前实现的限制(如不支持DHCP服务器、选项支持有限等)以及基于协议标准和实际需求的改进方向。
第六部分:DHCP Proxy实现详解
6.1 DHCP Proxy架构设计
DHCP Proxy(DHCP代理/中继)是VPP DHCP插件的核心功能之一,用于在不同网络段之间转发DHCP报文。
Proxy功能支持DHCPv4和DHCPv6两种协议,可以同时处理多个VRF(Virtual Routing and Forwarding)的DHCP请求,支持多服务器配置和VSS(Vendor-Specific Subnet Selection)功能。
这一节详细讲解DHCP Proxy的架构设计,包括功能概述、核心数据结构、设计理念等。
6.1.1 Proxy功能概述
DHCP Proxy在DHCP协议中扮演"中继代理"的角色,负责在不同网络段之间转发DHCP报文。
当客户端和服务器不在同一个广播域时,Proxy接收客户端的DHCP请求,将其转发给远程的DHCP服务器,并将服务器的回复转发回客户端。
这一节详细讲解DHCP Proxy的角色、多服务器支持策略、VRF隔离支持等核心功能。
6.1.1.1 DHCP Proxy的角色
功能说明:
DHCP Proxy在DHCP协议中扮演中继代理的角色,用于在不同网络段之间转发DHCP报文。Proxy位于客户端和服务器之间,负责接收、修改和转发DHCP报文。
DHCP Proxy的基本工作流程:
DHCP Proxy基本工作流程
│
├─→ 1. 客户端发送DHCP请求
│ └─→ 客户端在本地网络广播DHCP请求
│
├─→ 2. Proxy接收请求
│ └─→ Proxy在客户端网络接口上接收DHCP请求
│
├─→ 3. Proxy修改请求
│ ├─→ 插入Option 82(DHCPv4)或Interface-ID选项(DHCPv6)
│ ├─→ 插入VSS选项(如果配置)
│ ├─→ 修改源地址和目标地址
│ └─→ 封装为Relay消息(DHCPv6)
│
├─→ 4. Proxy转发请求
│ └─→ 将修改后的请求转发给远程DHCP服务器
│
├─→ 5. 服务器回复
│ └─→ DHCP服务器处理请求并回复
│
├─→ 6. Proxy接收回复
│ └─→ Proxy在服务器网络接口上接收DHCP回复
│
├─→ 7. Proxy修改回复
│ ├─→ 删除Option 82或Interface-ID选项
│ ├─→ 修改源地址和目标地址
│ └─→ 解封装Relay消息(DHCPv6)
│
└─→ 8. Proxy转发回复
└─→ 将修改后的回复转发给客户端
DHCP Proxy的关键功能:
-
报文转发:
- 接收客户端的DHCP请求并转发给服务器
- 接收服务器的DHCP回复并转发给客户端
- 支持单播和广播转发
-
报文修改:
- 插入Option 82(DHCPv4)或Interface-ID选项(DHCPv6)
- 插入VSS选项用于多租户支持
- 修改源地址和目标地址
- 封装/解封装Relay消息(DHCPv6)
-
多服务器支持:
- 支持配置多个DHCP服务器
- 将DISCOVER/SOLICIT消息转发给所有服务器
- 使用Cookie机制确保只转发一个回复给客户端
-
VRF隔离:
- 支持不同VRF的DHCP请求
- 每个VRF可以配置不同的服务器
- 通过FIB索引实现VRF隔离
6.1.1.2 多服务器支持策略
功能说明:
DHCP Proxy支持配置多个DHCP服务器,当配置了多个服务器时,Proxy会将客户端的DISCOVER/SOLICIT消息转发给所有服务器,但只转发第一个收到的回复给客户端。
多服务器支持的工作流程:
多服务器支持工作流程
│
├─→ 1. 客户端发送DISCOVER/SOLICIT消息
│ └─→ Proxy接收消息
│
├─→ 2. Proxy生成Cookie
│ └─→ 为请求生成唯一的Cookie(基于客户端MAC地址)
│
├─→ 3. Proxy转发给所有服务器
│ ├─→ 复制报文
│ ├─→ 为每个服务器设置目标地址
│ └─→ 转发给服务器1、服务器2、...、服务器N
│
├─→ 4. 服务器回复
│ ├─→ 服务器1回复OFFER/ADVERTISE消息
│ ├─→ 服务器2回复OFFER/ADVERTISE消息
│ └─→ ...(可能有多个回复)
│
├─→ 5. Proxy接收第一个回复
│ └─→ Proxy接收第一个到达的回复
│
├─→ 6. Proxy验证Cookie
│ └─→ 验证回复中的Cookie是否匹配
│
├─→ 7. Proxy转发回复给客户端
│ └─→ 转发第一个匹配的回复
│
└─→ 8. Proxy丢弃后续回复
└─→ 丢弃其他服务器的回复(避免重复)
多服务器支持的关键机制:
-
Cookie机制:
- 为每个请求生成唯一的Cookie
- Cookie基于客户端MAC地址生成
- 用于匹配请求和回复
-
待处理请求跟踪:
- 使用哈希表跟踪待处理的请求
- 键:客户端MAC地址
- 值:Cookie和其他状态信息
-
回复去重:
- 只转发第一个匹配的回复
- 丢弃其他服务器的回复
- 避免客户端收到重复的配置
多服务器支持的源码位置:
//多服务器转发逻辑(dhcp4_proxy_node.c和dhcp6_proxy_node.c)
if (is_solicit && vec_len (proxy->dhcp_servers) > 1) /* 如果是SOLICIT消息且有多个服务器 */
{
u32 ii;
for (ii = 1; ii < vec_len (proxy->dhcp_servers); ii++) /* 遍历所有服务器 */
{
vlib_buffer_t *c0; /* 复制缓冲区 */
u32 ci0;
c0 = vlib_buffer_copy (vm, b0); /* 复制报文 */
/* 说明:为每个服务器复制一份报文,确保每个服务器都收到请求。 */
server = &proxy->dhcp_servers[ii]; /* 获取服务器信息 */
/* 设置目标地址为服务器地址 */
copy_ip_address (&ip1->dst_address, &server->dhcp_server);
/* 转发给服务器 */
vlib_validate_buffer_enqueue_x1 (vm, node, next_index,
to_next, n_left_to_next,
ci0, next0);
}
}
/* 说明:多服务器转发逻辑确保所有服务器都收到DISCOVER/SOLICIT消息。
* 每个服务器都会回复,但Proxy只转发第一个匹配的回复给客户端。 */
6.1.1.3 VRF隔离支持
功能说明:
DHCP Proxy支持VRF(Virtual Routing and Forwarding)隔离,允许不同VRF的客户端使用不同的DHCP服务器配置。每个VRF可以配置独立的服务器列表和VSS信息。
VRF隔离的工作原理:
VRF隔离工作原理
│
├─→ 1. 客户端发送DHCP请求
│ └─→ 请求到达Proxy的接口(属于某个VRF)
│
├─→ 2. Proxy识别VRF
│ └─→ 通过接收接口的FIB索引识别VRF
│
├─→ 3. Proxy查找VRF配置
│ └─→ 根据FIB索引查找对应的Proxy配置
│ ├─→ 查找服务器列表
│ └─→ 查找VSS信息
│
├─→ 4. Proxy应用VRF配置
│ ├─→ 使用VRF特定的服务器列表
│ ├─→ 插入VRF特定的VSS选项
│ └─→ 使用VRF特定的源地址
│
└─→ 完成(VRF隔离已实现)
VRF隔离的关键数据结构:
//VRF到Proxy配置的映射(dhcp_proxy.h)
u32 *dhcp_server_index_by_rx_fib_index[DHCP_N_PROTOS]; /* FIB索引到Proxy索引的映射 */
/* 说明:dhcp_server_index_by_rx_fib_index是一个向量数组,每个元素对应一个FIB索引。
*
* 数据结构:
* - 索引:FIB索引(rx_fib_index)
* - 值:Proxy配置的索引(在dhcp_servers池中的索引)
* - 如果值为~0,表示该VRF没有配置Proxy
*
* 使用方式:
* - 通过FIB索引查找对应的Proxy配置索引
* - 使用Proxy配置索引从池中获取Proxy配置
* - 如果索引为~0,表示该VRF没有配置Proxy,丢弃报文 */
VRF隔离的查找逻辑:
//Proxy配置查找(dhcp_proxy.h)
static inline dhcp_proxy_t *
dhcp_get_proxy (dhcp_proxy_main_t * dm,
u32 rx_fib_index, fib_protocol_t proto)
{
dhcp_proxy_t *s = NULL; /* Proxy配置指针 */
/* 说明:s用于存储查找到的Proxy配置指针。 */
if (vec_len (dm->dhcp_server_index_by_rx_fib_index[proto]) > rx_fib_index &&
dm->dhcp_server_index_by_rx_fib_index[proto][rx_fib_index] != ~0)
/* 说明:检查FIB索引是否在有效范围内,且该VRF已配置Proxy。
*
* 检查条件:
* 1. vec_len > rx_fib_index:确保FIB索引在向量范围内
* 2. 值 != ~0:确保该VRF已配置Proxy(~0表示未配置)
*
* 为什么需要这两个检查?
* - 第一个检查防止数组越界
* - 第二个检查确保VRF已配置Proxy */
{
s = pool_elt_at_index (dm->dhcp_servers[proto],
dm->dhcp_server_index_by_rx_fib_index[proto]
[rx_fib_index]);
/* 说明:从Proxy池中获取Proxy配置。
*
* 函数参数:
* - dm->dhcp_servers[proto]:Proxy配置池(IPv4或IPv6)
* - dm->dhcp_server_index_by_rx_fib_index[proto][rx_fib_index]:Proxy配置索引
*
* 返回值:
* - s:Proxy配置指针,如果找到返回配置指针,否则返回NULL */
}
return (s); /* 返回Proxy配置指针 */
/* 说明:返回查找到的Proxy配置指针。
* 如果VRF未配置Proxy,返回NULL,调用者需要处理这种情况(通常丢弃报文)。 */
}
VRF隔离的优势:
-
网络隔离:
- 不同VRF的客户端完全隔离
- 每个VRF可以配置不同的服务器
- 支持多租户场景
-
灵活配置:
- 每个VRF可以独立配置
- 支持不同的VSS信息
- 支持不同的源地址
-
安全性:
- VRF之间完全隔离
- 防止跨VRF的DHCP请求泄露
- 支持细粒度的访问控制
6.1.2 核心数据结构
DHCP Proxy的核心数据结构定义了Proxy配置、服务器信息、VSS信息等关键数据。
这些数据结构是Proxy功能的基础,用于存储配置信息、跟踪请求状态、管理服务器列表等。
这一节详细讲解所有核心数据结构的定义、字段含义、使用场景等。
6.1.2.1 dhcp_server_t - 服务器信息结构
功能说明:
dhcp_server_t结构用于表示单个DHCP服务器的信息,包括服务器地址和服务器所在的FIB索引。每个Proxy配置可以包含多个服务器。
结构体定义:
//79:95:src/plugins/dhcp/dhcp_proxy.h
/**
* @brief A representation of a single DHCP Server within a given VRF config
*/
/* 注释说明:表示给定VRF配置中的单个DHCP服务器。 */
typedef struct dhcp_server_t_
{
/**
* @brief The address of the DHCP server to which to relay the client's
* messages
*/
ip46_address_t dhcp_server; /* DHCP服务器地址 */
/* 说明:dhcp_server字段存储DHCP服务器的IP地址。
*
* 数据类型:
* - ip46_address_t:IPv4和IPv6地址的联合体
* - 可以存储IPv4地址(4字节)或IPv6地址(16字节)
* - 根据协议类型(IPv4或IPv6)使用不同的地址格式
*
* 使用场景:
* - 在转发客户端请求时,将目标地址设置为服务器地址
* - 在接收服务器回复时,验证源地址是否为配置的服务器地址
* - 支持单播和广播地址(DHCPv4支持广播)
*
* 地址类型:
* - IPv4:可以是单播地址或255.255.255.255(广播)
* - IPv6:通常是单播地址(IPv6不支持广播) */
/**
* @brief The FIB index (not the external Table-ID) in which the server
* is reachable.
*/
u32 server_fib_index; /* 服务器所在的FIB索引 */
/* 说明:server_fib_index字段存储服务器所在的FIB索引。
*
* FIB索引:
* - FIB(Forwarding Information Base)索引是VPP内部的路由表索引
* - 不是外部的Table-ID,而是VPP内部的索引值
* - 用于查找服务器所在的路由表
*
* 使用场景:
* - 在转发请求时,使用server_fib_index查找路由
* - 确定报文的转发路径和出口接口
* - 支持服务器在不同的VRF中
*
* 与rx_fib_index的区别:
* - rx_fib_index:客户端所在的FIB索引(接收接口的FIB)
* - server_fib_index:服务器所在的FIB索引(服务器接口的FIB)
* - 两者可能不同,支持跨VRF转发 */
} dhcp_server_t; /* DHCP服务器信息结构类型定义 */
/* 说明:dhcp_server_t是单个DHCP服务器的信息结构。
* 每个Proxy配置可以包含多个dhcp_server_t结构,存储在dhcp_proxy_t的dhcp_servers向量中。 */
结构体字段总结:
| 字段名 | 类型 | 说明 | 用途 |
|---|---|---|---|
dhcp_server | ip46_address_t | DHCP服务器地址 | 转发请求的目标地址 |
server_fib_index | u32 | 服务器FIB索引 | 查找服务器路由表 |
使用场景:
-
服务器列表管理:
- 每个Proxy配置包含一个
dhcp_server_t向量 - 支持配置多个服务器
- 服务器列表在Proxy配置时添加/删除
- 每个Proxy配置包含一个
-
请求转发:
- 根据服务器地址设置目标地址
- 根据server_fib_index查找路由
- 支持向多个服务器转发请求
-
回复验证:
- 验证回复的源地址是否为配置的服务器地址
- 验证回复的FIB索引是否匹配
- 防止伪造的服务器回复
6.1.2.2 dhcp_proxy_t - Proxy配置结构
功能说明:
dhcp_proxy_t结构是DHCP Proxy的核心配置结构,用于存储每个VRF的Proxy配置信息,包括服务器列表、待处理请求哈希表、源地址等。
结构体定义:
//97:132:src/plugins/dhcp/dhcp_proxy.h
/**
* @brief A DHCP proxy representation fpr per-client VRF config
*/
/* 注释说明:用于每个客户端VRF配置的DHCP Proxy表示。 */
typedef struct dhcp_proxy_t_
{
/**
* @brief The set of DHCP servers to which messages are relayed.
* If multiple servers are configured then discover/solict messages
* are relayed to each. A cookie is maintained for the relay, and only
* one message is replayed to the client, based on the presence of the
* cookie.
* The expectation is there are only 1 or 2 servers, hence no fancy DB.
*/
dhcp_server_t *dhcp_servers; /* DHCP服务器列表 */
/* 说明:dhcp_servers字段存储DHCP服务器列表,是一个向量。
*
* 数据结构:
* - 类型:dhcp_server_t *(向量指针)
* - 每个元素是一个dhcp_server_t结构
* - 支持动态添加/删除服务器
*
* 多服务器支持:
* - 如果配置了多个服务器,DISCOVER/SOLICIT消息会转发给所有服务器
* - 使用Cookie机制确保只转发一个回复给客户端
* - 预期只有1-2个服务器,所以不使用复杂的数据库
*
* 使用场景:
* - 在转发请求时,遍历所有服务器并转发
* - 在接收回复时,验证服务器是否在列表中
* - 在配置时,添加/删除服务器 */
/**
* @brief Hash table of pending requets key'd on the clients MAC address
*/
uword *dhcp_pending; /* 待处理请求哈希表 */
/* 说明:dhcp_pending字段存储待处理请求的哈希表。
*
* 数据结构:
* - 类型:uword *(VPP的哈希表类型)
* - 键:客户端MAC地址(6字节)
* - 值:Cookie或其他状态信息
*
* 用途:
* - 跟踪已转发但尚未收到回复的请求
* - 用于Cookie生成和验证
* - 用于请求去重和超时处理
*
* 工作流程:
* 1. 客户端发送DISCOVER/SOLICIT消息
* 2. Proxy生成Cookie并插入哈希表(键:MAC地址)
* 3. Proxy转发请求给服务器
* 4. 服务器回复时,Proxy查找哈希表验证Cookie
* 5. 转发回复后,从哈希表中删除条目
*
* 锁保护:
* - 使用lock字段保护哈希表的并发访问
* - 在访问哈希表前加锁,访问后解锁 */
/**
* @brief A lock for the pending request DB.
*/
int lock; /* 待处理请求数据库的锁 */
/* 说明:lock字段用于保护dhcp_pending哈希表的并发访问。
*
* 锁类型:
* - int类型,实际上是一个互斥锁或自旋锁
* - 用于多线程/多工作线程环境下的并发控制
*
* 使用方式:
* - 在访问dhcp_pending哈希表前调用dhcp_proxy_lock()
* - 在访问完成后调用dhcp_proxy_unlock()
* - 确保同一时间只有一个线程访问哈希表
*
* 为什么需要锁?
* - VPP支持多工作线程,可能同时处理多个DHCP请求
* - 哈希表的插入/删除/查找操作需要原子性
* - 防止竞态条件导致的数据不一致 */
/**
* @brief The source address to use in relayed messaes
*/
ip46_address_t dhcp_src_address; /* 中继消息中使用的源地址 */
/* 说明:dhcp_src_address字段存储中继消息中使用的源地址。
*
* 数据类型:
* - ip46_address_t:IPv4和IPv6地址的联合体
* - 根据协议类型使用IPv4或IPv6地址
*
* 使用场景:
* - 在转发客户端请求时,将源地址设置为dhcp_src_address
* - 服务器回复时,目标地址为dhcp_src_address
* - 如果dhcp_src_address为0,使用接口地址作为源地址
*
* 配置方式:
* - 在配置Proxy时指定源地址
* - 如果不指定,使用接口的全局地址
* - 支持手动指定源地址(用于特殊场景) */
/**
* @brief The FIB index (not the external Table-ID) in which the client
* is resides.
*/
u32 rx_fib_index; /* 客户端所在的FIB索引 */
/* 说明:rx_fib_index字段存储客户端所在的FIB索引。
*
* FIB索引:
* - 客户端接收接口的FIB索引
* - 用于标识客户端所在的VRF
* - 不是外部的Table-ID,而是VPP内部的索引值
*
* 使用场景:
* - 在接收客户端请求时,通过rx_fib_index查找Proxy配置
* - 用于VRF隔离,不同VRF使用不同的Proxy配置
* - 在转发回复时,使用rx_fib_index确定目标VRF
*
* 与server_fib_index的区别:
* - rx_fib_index:客户端所在的FIB(接收接口的FIB)
* - server_fib_index:服务器所在的FIB(服务器接口的FIB)
* - 两者可能不同,支持跨VRF转发 */
} dhcp_proxy_t; /* DHCP Proxy配置结构类型定义 */
/* 说明:dhcp_proxy_t是每个VRF的Proxy配置结构。
* 每个VRF可以有一个dhcp_proxy_t配置,存储在dhcp_proxy_main_t的dhcp_servers池中。 */
结构体字段总结:
| 字段名 | 类型 | 说明 | 用途 |
|---|---|---|---|
dhcp_servers | dhcp_server_t * | DHCP服务器列表 | 存储多个服务器配置 |
dhcp_pending | uword * | 待处理请求哈希表 | 跟踪待处理的请求 |
lock | int | 锁 | 保护哈希表的并发访问 |
dhcp_src_address | ip46_address_t | 源地址 | 中继消息的源地址 |
rx_fib_index | u32 | 客户端FIB索引 | 标识客户端所在的VRF |
使用场景:
-
Proxy配置管理:
- 每个VRF对应一个
dhcp_proxy_t配置 - 配置存储在
dhcp_proxy_main_t的池中 - 通过
rx_fib_index查找配置
- 每个VRF对应一个
-
请求处理:
- 接收客户端请求时,查找对应的Proxy配置
- 使用服务器列表转发请求
- 使用哈希表跟踪请求状态
-
回复处理:
- 接收服务器回复时,查找对应的Proxy配置
- 验证Cookie并转发回复
- 从哈希表中删除已处理的请求
6.1.2.3 dhcp_vss_t - VSS配置结构
功能说明:
dhcp_vss_t结构用于存储VSS(Vendor-Specific Subnet Selection)信息。VSS是Cisco扩展的DHCP选项,用于在多租户环境中标识不同的VPN或子网。每个VRF可以配置一个VSS信息。
结构体定义:
//53:77:src/plugins/dhcp/dhcp_proxy.h
/**
* @brief The Virtual Sub-net Selection information for a given RX FIB
*/
/* 注释说明:给定RX FIB的虚拟子网选择信息。 */
typedef struct dhcp_vss_t_
{
/**
* @brief VSS type as defined in RFC 6607:
* 0 for NVT ASCII VPN Identifier
* 1 for RFC 2685 VPN-ID of 7 octects - 3 bytes OUI & 4 bytes VPN index
* 255 for global default VPN
*/
u8 vss_type; /* VSS类型 */
/* 说明:vss_type字段存储VSS类型,占用1字节。
*
* VSS类型定义(RFC 6607):
* - VSS_TYPE_ASCII (0):NVT ASCII格式的VPN标识符
* * 使用ASCII字符串标识VPN
* * 存储在vpn_ascii_id字段中
* * 最大长度由实现决定
*
* - VSS_TYPE_VPN_ID (1):RFC 2685格式的VPN-ID
* * 7字节格式:3字节OUI(Organizationally Unique Identifier)+ 4字节VPN索引
* * 存储在vpn_id字段中
* * OUI由IEEE分配,VPN索引由组织分配
*
* - VSS_TYPE_DEFAULT (255):全局默认VPN
* * 表示使用默认VPN配置
* * 不包含具体的VPN标识符
*
* - VSS_TYPE_INVALID (123):无效类型
* * 用于表示无效的VSS配置
* * 通常用于错误处理
*
* 使用场景:
* - 在多租户环境中标识不同的VPN
* - 服务器根据VSS信息分配不同的地址池
* - 支持网络虚拟化和多租户隔离 */
#define VSS_TYPE_ASCII 0 /* ASCII格式的VPN标识符 */
#define VSS_TYPE_VPN_ID 1 /* VPN-ID格式的VPN标识符 */
#define VSS_TYPE_INVALID 123 /* 无效类型 */
#define VSS_TYPE_DEFAULT 255 /* 默认VPN */
/* 说明:VSS类型常量定义,用于标识不同的VSS类型。 */
/**
* @brief Type 1 VPN-ID
*/
u8 vpn_id[7]; /* 类型1的VPN-ID(7字节) */
/* 说明:vpn_id字段存储类型1的VPN-ID,占用7字节。
*
* VPN-ID格式(RFC 2685):
* - 前3字节:OUI(Organizationally Unique Identifier)
* * 由IEEE分配的企业标识符
* * 例如:Cisco的OUI是0x00000C
*
* - 后4字节:VPN索引
* * 由组织分配的唯一标识符
* * 用于在同一OUI下区分不同的VPN
*
* 使用场景:
* - 当vss_type为VSS_TYPE_VPN_ID时使用
* - 服务器根据VPN-ID分配对应的地址池
* - 支持企业级的多VPN场景
*
* 示例:
* - OUI: 0x00000C(Cisco)
* - VPN索引: 0x00000001
* - VPN-ID: {0x00, 0x00, 0x0C, 0x00, 0x00, 0x00, 0x01} */
/**
* @brief Type 0 ASCII VPN Identifier
*/
u8 *vpn_ascii_id; /* 类型0的ASCII VPN标识符 */
/* 说明:vpn_ascii_id字段存储类型0的ASCII VPN标识符,是一个字符串向量。
*
* 数据类型:
* - u8 *:字符串向量指针
* - 存储ASCII字符串,以NULL结尾
* - 长度可变,由实际配置决定
*
* 使用场景:
* - 当vss_type为VSS_TYPE_ASCII时使用
* - 服务器根据ASCII标识符分配对应的地址池
* - 支持更灵活的VPN命名方式
*
* 示例:
* - "vpn1"、"tenant-1"、"customer-A"等
* - 可以是任何ASCII字符串
* - 长度通常不超过64字节
*
* 内存管理:
* - 使用vec_alloc分配内存
* - 使用vec_free释放内存
* - 在配置VSS时分配,在删除VSS时释放 */
} dhcp_vss_t; /* VSS配置结构类型定义 */
/* 说明:dhcp_vss_t是VSS配置结构,用于在多租户环境中标识不同的VPN。
* 每个VRF可以配置一个VSS信息,存储在dhcp_proxy_main_t的vss池中。 */
结构体字段总结:
| 字段名 | 类型 | 说明 | 用途 |
|---|---|---|---|
vss_type | u8 | VSS类型 | 标识VSS格式(ASCII或VPN-ID) |
vpn_id | u8[7] | VPN-ID | 类型1的VPN标识符(7字节) |
vpn_ascii_id | u8 * | ASCII VPN标识符 | 类型0的VPN标识符(字符串) |
VSS类型对比:
| VSS类型 | 值 | 标识符格式 | 使用场景 |
|---|---|---|---|
| ASCII | 0 | ASCII字符串 | 灵活的VPN命名 |
| VPN-ID | 1 | 7字节(OUI+索引) | 标准化的VPN标识 |
| 默认 | 255 | 无 | 使用默认VPN |
| 无效 | 123 | 无 | 错误处理 |
使用场景:
-
多租户环境:
- 不同租户使用不同的VSS标识符
- 服务器根据VSS信息分配不同的地址池
- 实现网络虚拟化和隔离
-
VPN标识:
- 使用ASCII字符串或VPN-ID标识VPN
- 支持灵活的VPN命名方式
- 支持标准化的VPN标识格式
-
地址池选择:
- 服务器根据VSS信息选择对应的地址池
- 不同VSS使用不同的地址范围
- 实现细粒度的地址管理
6.1.2.4 dhcp_proxy_main_t - Proxy全局管理结构
功能说明:
dhcp_proxy_main_t结构是DHCP Proxy的全局管理器,用于存储所有Proxy配置、VSS配置、索引映射等全局信息。这是Proxy模块的单例管理器。
结构体定义:
//134:161:src/plugins/dhcp/dhcp_proxy.h
#define DHCP_N_PROTOS (FIB_PROTOCOL_IP6 + 1)
/* 说明:DHCP_N_PROTOS定义支持的协议数量。
*
* 协议数量:
* - FIB_PROTOCOL_IP4:IPv4协议
* - FIB_PROTOCOL_IP6:IPv6协议
* - DHCP_N_PROTOS = FIB_PROTOCOL_IP6 + 1 = 2
*
* 用途:
* - 用于数组大小定义
* - 支持IPv4和IPv6两种协议
* - 数组索引:0=IPv4,1=IPv6 */
/**
* @brief Collection of global DHCP proxy data
*/
/* 注释说明:全局DHCP Proxy数据集合。 */
typedef struct
{
/* Pool of DHCP servers */
dhcp_proxy_t *dhcp_servers[DHCP_N_PROTOS]; /* DHCP服务器配置池 */
/* 说明:dhcp_servers字段存储Proxy配置池,是一个数组,每个协议一个池。
*
* 数据结构:
* - 类型:dhcp_proxy_t *(池指针)
* - 数组大小:DHCP_N_PROTOS(2个元素)
* - 索引0:IPv4协议的Proxy配置池
* - 索引1:IPv6协议的Proxy配置池
*
* 使用方式:
* - 使用pool_get从池中分配Proxy配置
* - 使用pool_put将Proxy配置归还到池中
* - 使用pool_elt_at_index通过索引访问配置
*
* 存储内容:
* - 每个VRF的Proxy配置(dhcp_proxy_t)
* - 配置包括服务器列表、哈希表、源地址等 */
/* Pool of selected DHCP server. Zero is the default server */
u32 *dhcp_server_index_by_rx_fib_index[DHCP_N_PROTOS]; /* FIB索引到Proxy索引的映射 */
/* 说明:dhcp_server_index_by_rx_fib_index字段存储FIB索引到Proxy配置索引的映射。
*
* 数据结构:
* - 类型:u32 *(向量指针)
* - 数组大小:DHCP_N_PROTOS(2个元素)
* - 索引:FIB索引(rx_fib_index)
* - 值:Proxy配置在池中的索引
*
* 使用方式:
* - 通过FIB索引查找对应的Proxy配置索引
* - 如果值为~0,表示该VRF没有配置Proxy
* - 使用配置索引从池中获取Proxy配置
*
* 查找流程:
* 1. 接收客户端请求,获取接收接口的FIB索引
* 2. 使用FIB索引查找dhcp_server_index_by_rx_fib_index
* 3. 如果值不为~0,使用值作为索引从池中获取Proxy配置
* 4. 如果值为~0,表示该VRF没有配置Proxy,丢弃报文 */
/* to drop pkts in server-to-client direction */
u32 error_drop_node_index; /* 错误丢弃节点索引 */
/* 说明:error_drop_node_index字段存储错误丢弃节点的索引。
*
* 用途:
* - 在服务器到客户端方向出现错误时,将报文发送到错误丢弃节点
* - 错误丢弃节点会丢弃报文并更新统计计数
* - 用于错误处理和统计
*
* 使用场景:
* - 报文格式错误
* - 找不到对应的Proxy配置
* - Cookie验证失败
* - 其他处理错误 */
dhcp_vss_t *vss[DHCP_N_PROTOS]; /* VSS配置池 */
/* 说明:vss字段存储VSS配置池,是一个数组,每个协议一个池。
*
* 数据结构:
* - 类型:dhcp_vss_t *(池指针)
* - 数组大小:DHCP_N_PROTOS(2个元素)
* - 索引0:IPv4协议的VSS配置池
* - 索引1:IPv6协议的VSS配置池
*
* 使用方式:
* - 使用pool_get从池中分配VSS配置
* - 使用pool_put将VSS配置归还到池中
* - 使用pool_elt_at_index通过索引访问配置
*
* 存储内容:
* - 每个VRF的VSS配置(dhcp_vss_t)
* - 配置包括VSS类型、VPN-ID或ASCII标识符 */
/* hash lookup specific vrf_id -> option 82 vss suboption */
u32 *vss_index_by_rx_fib_index[DHCP_N_PROTOS]; /* FIB索引到VSS索引的映射 */
/* 说明:vss_index_by_rx_fib_index字段存储FIB索引到VSS配置索引的映射。
*
* 数据结构:
* - 类型:u32 *(向量指针)
* - 数组大小:DHCP_N_PROTOS(2个元素)
* - 索引:FIB索引(rx_fib_index)
* - 值:VSS配置在池中的索引
*
* 使用方式:
* - 通过FIB索引查找对应的VSS配置索引
* - 如果值为~0,表示该VRF没有配置VSS
* - 使用配置索引从池中获取VSS配置
*
* 查找流程:
* 1. 接收客户端请求,获取接收接口的FIB索引
* 2. 使用FIB索引查找vss_index_by_rx_fib_index
* 3. 如果值不为~0,使用值作为索引从池中获取VSS配置
* 4. 在转发请求时,插入VSS选项 */
/* flags to indicate which udp ports have been registered */
int udp_ports_registered; /* UDP端口注册标志 */
/* 说明:udp_ports_registered字段存储UDP端口注册标志。
*
* 标志值:
* - DHCP_PORT_REG_CLIENT (0x1):客户端端口已注册
* - DHCP_PORT_REG_SERVER (0x2):服务器端口已注册
* - 可以组合使用(如0x3表示两个端口都已注册)
*
* 使用场景:
* - 在配置Proxy时,检查端口是否已注册
* - 如果未注册,注册UDP端口
* - 在删除Proxy时,检查是否还有其他Proxy使用端口
* - 如果没有,注销UDP端口
*
* 端口号:
* - DHCPv4客户端端口:68
* - DHCPv4服务器端口:67
* - DHCPv6客户端端口:546
* - DHCPv6服务器端口:547 */
/* convenience */
vlib_main_t *vlib_main; /* VLIB主线程指针 */
/* 说明:vlib_main字段存储VLIB主线程指针,用于访问VPP的核心功能。
*
* 用途:
* - 访问节点索引
* - 访问缓冲区池
* - 访问时间函数
* - 其他VPP核心功能 */
} dhcp_proxy_main_t; /* DHCP Proxy全局管理结构类型定义 */
/* 说明:dhcp_proxy_main_t是DHCP Proxy的全局管理器,单例模式。
* 全局变量名为dhcp_proxy_main,在dhcp_proxy.c中定义。 */
结构体字段总结:
| 字段名 | 类型 | 说明 | 用途 |
|---|---|---|---|
dhcp_servers | dhcp_proxy_t *[2] | Proxy配置池 | 存储所有VRF的Proxy配置 |
dhcp_server_index_by_rx_fib_index | u32 *[2] | FIB到Proxy索引映射 | 通过FIB索引查找Proxy配置 |
error_drop_node_index | u32 | 错误丢弃节点索引 | 错误报文的丢弃节点 |
vss | dhcp_vss_t *[2] | VSS配置池 | 存储所有VRF的VSS配置 |
vss_index_by_rx_fib_index | u32 *[2] | FIB到VSS索引映射 | 通过FIB索引查找VSS配置 |
udp_ports_registered | int | UDP端口注册标志 | 跟踪UDP端口注册状态 |
vlib_main | vlib_main_t * | VLIB主线程指针 | 访问VPP核心功能 |
全局管理器的工作流程:
全局管理器工作流程
│
├─→ 1. Proxy配置添加
│ ├─→ 从dhcp_servers池中分配Proxy配置
│ ├─→ 在dhcp_server_index_by_rx_fib_index中建立映射
│ └─→ 注册UDP端口(如果未注册)
│
├─→ 2. 请求处理
│ ├─→ 通过FIB索引查找Proxy配置索引
│ ├─→ 从池中获取Proxy配置
│ ├─→ 查找VSS配置(如果配置)
│ └─→ 处理请求
│
├─→ 3. Proxy配置删除
│ ├─→ 从dhcp_server_index_by_rx_fib_index中删除映射
│ ├─→ 将Proxy配置归还到池中
│ └─→ 注销UDP端口(如果没有其他Proxy使用)
│
└─→ 完成(全局管理器维护所有配置)
关键设计理念:
-
协议分离:
- IPv4和IPv6使用独立的配置池
- 支持同时配置IPv4和IPv6 Proxy
- 协议之间完全隔离
-
索引映射:
- 使用FIB索引作为键查找配置
- 支持快速查找(O(1)时间复杂度)
- 使用~0表示未配置
-
资源管理:
- 使用内存池管理配置
- 支持动态添加/删除配置
- 自动管理UDP端口注册
6.1.2节总结:
本节详细讲解了DHCP Proxy的核心数据结构,包括:
-
dhcp_server_t:单个DHCP服务器的信息- 服务器地址和FIB索引
- 用于服务器列表管理
-
dhcp_proxy_t:Proxy配置结构- 服务器列表、待处理请求哈希表、锁、源地址、FIB索引
- 每个VRF一个配置
-
dhcp_vss_t:VSS配置结构- VSS类型、VPN-ID、ASCII标识符
- 用于多租户支持
-
dhcp_proxy_main_t:全局管理器- Proxy配置池、VSS配置池、索引映射、UDP端口注册
- 单例模式,管理所有配置
数据结构关系图:
dhcp_proxy_main_t(全局管理器)
│
├─→ dhcp_servers[IPv4/IPv6](Proxy配置池)
│ └─→ dhcp_proxy_t(每个VRF一个配置)
│ ├─→ dhcp_servers(服务器列表向量)
│ │ └─→ dhcp_server_t(每个服务器一个结构)
│ ├─→ dhcp_pending(待处理请求哈希表)
│ ├─→ lock(锁)
│ ├─→ dhcp_src_address(源地址)
│ └─→ rx_fib_index(客户端FIB索引)
│
├─→ dhcp_server_index_by_rx_fib_index[IPv4/IPv6](索引映射)
│ └─→ 通过FIB索引查找Proxy配置索引
│
├─→ vss[IPv4/IPv6](VSS配置池)
│ └─→ dhcp_vss_t(每个VRF一个VSS配置)
│
└─→ vss_index_by_rx_fib_index[IPv4/IPv6](索引映射)
└─→ 通过FIB索引查找VSS配置索引
6.1节总结:
本节详细讲解了DHCP Proxy的架构设计,包括:
- Proxy功能概述:
- Proxy的角色和基本工作流程
- 多服务器支持策略
- VRF隔离支持
- 核心数据结构:
- 服务器信息结构(
dhcp_server_t) - Proxy配置结构(
dhcp_proxy_t) - VSS配置结构(
dhcp_vss_t) - 全局管理结构(
dhcp_proxy_main_t)
- 服务器信息结构(
关键设计特点:
- 协议分离:IPv4和IPv6使用独立的配置池和索引映射
- VRF隔离:通过FIB索引实现VRF隔离,每个VRF独立配置
- 多服务器支持:支持配置多个服务器,使用Cookie机制去重
- 多租户支持:通过VSS选项支持多租户场景
- 资源管理:使用内存池和索引映射实现高效的资源管理
至此,6.1节(DHCP Proxy架构设计)的详细讲解已完成。
6.2 dhcp_proxy.h 头文件分析
dhcp_proxy.h头文件定义了DHCP Proxy模块的所有公共接口、数据结构和辅助函数。
本节详细分析头文件中的各个组成部分,包括错误代码定义、端口注册标志、VSS类型定义、函数接口声明和内联辅助函数。
注意:6.1节已经详细讲解过的核心数据结构(dhcp_server_t、dhcp_proxy_t、dhcp_vss_t、dhcp_proxy_main_t)在本节中不再重复讲解。
6.2.1 错误代码定义
功能说明:
错误代码定义用于标识DHCP Proxy处理过程中可能出现的各种错误情况。VPP使用宏定义机制自动生成错误代码枚举和错误消息字符串,支持IPv4和IPv6两种协议。
DHCPv4 Proxy错误代码定义:
//30:36:src/plugins/dhcp/dhcp_proxy.h
typedef enum
{
#define dhcp_proxy_error(n,s) DHCP_PROXY_ERROR_##n, /* 宏展开:定义错误代码枚举值 */
/* 说明:dhcp_proxy_error是一个宏定义,用于定义错误代码。
* 宏参数:
* - n:错误代码名称(如NONE、NO_SERVER等)
* - s:错误消息字符串(如"no error"、"no dhcp server configured"等)
*
* 宏展开过程:
* - #define dhcp_proxy_error(n,s) DHCP_PROXY_ERROR_##n
* - 例如:dhcp_proxy_error(NONE, "no error")
* 展开为:DHCP_PROXY_ERROR_NONE
*
* 这个宏在dhcp4_proxy_error.def文件中使用,定义所有DHCPv4 Proxy的错误代码。 */
#include <dhcp/dhcp4_proxy_error.def> /* 包含错误定义文件 */
/* 说明:包含dhcp4_proxy_error.def文件,该文件使用dhcp_proxy_error宏定义所有错误代码。
*
* dhcp4_proxy_error.def文件内容示例:
* dhcp_proxy_error (NONE, "no error")
* dhcp_proxy_error (NO_SERVER, "no dhcp server configured")
* dhcp_proxy_error (RELAY_TO_SERVER, "DHCP packets relayed to the server")
* ...
*
* 包含后,这些宏调用会被展开为枚举值:
* DHCP_PROXY_ERROR_NONE
* DHCP_PROXY_ERROR_NO_SERVER
* DHCP_PROXY_ERROR_RELAY_TO_SERVER
* ... */
#undef dhcp_proxy_error /* 取消宏定义 */
/* 说明:取消dhcp_proxy_error宏定义,避免影响后续代码。
* 这是C语言中常见的宏使用模式:定义宏 → 使用宏 → 取消宏定义。 */
DHCP_PROXY_N_ERROR, /* 错误代码总数 */
/* 说明:DHCP_PROXY_N_ERROR是最后一个枚举值,表示错误代码的总数。
*
* 用途:
* - 用于错误代码数组的大小定义
* - 用于错误代码的边界检查
* - 自动计算错误代码数量,无需手动维护
*
* 值:
* - 等于前面所有错误代码的数量
* - 例如:如果有15个错误代码,DHCP_PROXY_N_ERROR = 15 */
} dhcp_proxy_error_t; /* DHCPv4 Proxy错误代码枚举类型 */
/* 说明:dhcp_proxy_error_t是DHCPv4 Proxy错误代码的枚举类型。
*
* 使用方式:
* - 函数返回dhcp_proxy_error_t类型的错误代码
* - 使用错误代码查找对应的错误消息字符串
* - 用于错误统计和日志记录 */
DHCPv4 Proxy错误代码列表(来自dhcp4_proxy_error.def):
| 错误代码 | 枚举值 | 说明 | 使用场景 |
|---|---|---|---|
NONE | DHCP_PROXY_ERROR_NONE | 无错误 | 正常处理完成 |
NO_SERVER | DHCP_PROXY_ERROR_NO_SERVER | 未配置DHCP服务器 | 找不到Proxy配置 |
RELAY_TO_SERVER | DHCP_PROXY_ERROR_RELAY_TO_SERVER | 报文已中继到服务器 | 统计计数 |
RELAY_TO_CLIENT | DHCP_PROXY_ERROR_RELAY_TO_CLIENT | 报文已中继到客户端 | 统计计数 |
OPTION_82_ERROR | DHCP_PROXY_ERROR_OPTION_82_ERROR | Option 82插入失败 | Option 82处理错误 |
NO_OPTION_82 | DHCP_PROXY_ERROR_NO_OPTION_82 | 缺少Option 82 | 服务器到客户端方向验证 |
BAD_OPTION_82_ITF | DHCP_PROXY_ERROR_BAD_OPTION_82_ITF | Option 82接口值错误 | Option 82解析错误 |
BAD_OPTION_82_ADDR | DHCP_PROXY_ERROR_BAD_OPTION_82_ADDR | Option 82地址值错误 | Option 82解析错误 |
BAD_FIB_ID | DHCP_PROXY_ERROR_BAD_FIB_ID | FIB-ID映射失败 | FIB索引转换错误 |
NO_INTERFACE_ADDRESS | DHCP_PROXY_ERROR_NO_INTERFACE_ADDRESS | 接口无地址 | 源地址选择失败 |
OPTION_82_VSS_NOT_PROCESSED | DHCP_PROXY_ERROR_OPTION_82_VSS_NOT_PROCESSED | VSS未处理 | 服务器不支持VSS |
BAD_YIADDR | DHCP_PROXY_ERROR_BAD_YIADDR | Your IP地址字段错误 | 报文验证失败 |
BAD_SVR_FIB_OR_ADDRESS | DHCP_PROXY_ERROR_BAD_SVR_FIB_OR_ADDRESS | 服务器FIB或地址错误 | 服务器验证失败 |
PKT_TOO_BIG | DHCP_PROXY_ERROR_PKT_TOO_BIG | 报文过大 | 报文大小检查失败 |
FOR_US | DHCP_PROXY_ERROR_FOR_US | 本地客户端报文 | 报文应发送给本地客户端 |
ALLOC_FAIL | DHCP_PROXY_ERROR_ALLOC_FAIL | 缓冲区分配失败 | 内存分配错误 |
DHCPv6 Proxy错误代码定义:
//38:44:src/plugins/dhcp/dhcp_proxy.h
typedef enum
{
#define dhcpv6_proxy_error(n,s) DHCPV6_PROXY_ERROR_##n, /* 宏展开:定义DHCPv6错误代码枚举值 */
/* 说明:dhcpv6_proxy_error是一个宏定义,用于定义DHCPv6 Proxy错误代码。
* 与dhcp_proxy_error类似,但用于DHCPv6协议。
*
* 宏展开过程:
* - #define dhcpv6_proxy_error(n,s) DHCPV6_PROXY_ERROR_##n
* - 例如:dhcpv6_proxy_error(NONE, "no error")
* 展开为:DHCPV6_PROXY_ERROR_NONE */
#include <dhcp/dhcp6_proxy_error.def> /* 包含DHCPv6错误定义文件 */
/* 说明:包含dhcp6_proxy_error.def文件,该文件使用dhcpv6_proxy_error宏定义所有DHCPv6错误代码。
*
* dhcp6_proxy_error.def文件内容示例:
* dhcpv6_proxy_error (NONE, "no error")
* dhcpv6_proxy_error (NO_SERVER, "no dhcpv6 server configured")
* dhcpv6_proxy_error (RELAY_TO_SERVER, "DHCPV6 packets relayed to the server")
* ... */
#undef dhcpv6_proxy_error /* 取消宏定义 */
DHCPV6_PROXY_N_ERROR, /* DHCPv6错误代码总数 */
/* 说明:DHCPV6_PROXY_N_ERROR是最后一个枚举值,表示DHCPv6错误代码的总数。 */
} dhcpv6_proxy_error_t; /* DHCPv6 Proxy错误代码枚举类型 */
/* 说明:dhcpv6_proxy_error_t是DHCPv6 Proxy错误代码的枚举类型。 */
DHCPv6 Proxy错误代码列表(来自dhcp6_proxy_error.def):
| 错误代码 | 枚举值 | 说明 | 使用场景 |
|---|---|---|---|
NONE | DHCPV6_PROXY_ERROR_NONE | 无错误 | 正常处理完成 |
NO_SERVER | DHCPV6_PROXY_ERROR_NO_SERVER | 未配置DHCPv6服务器 | 找不到Proxy配置 |
RELAY_TO_SERVER | DHCPV6_PROXY_ERROR_RELAY_TO_SERVER | 报文已中继到服务器 | 统计计数 |
RELAY_TO_CLIENT | DHCPV6_PROXY_ERROR_RELAY_TO_CLIENT | 报文已中继到客户端 | 统计计数 |
NO_INTERFACE_ADDRESS | DHCPV6_PROXY_ERROR_NO_INTERFACE_ADDRESS | 接口无地址 | 源地址选择失败 |
WRONG_MESSAGE_TYPE | DHCPV6_PROXY_ERROR_WRONG_MESSAGE_TYPE | 消息类型错误 | 报文类型验证失败 |
NO_SRC_ADDRESS | DHCPV6_PROXY_ERROR_NO_SRC_ADDRESS | 未配置源IPv6地址 | 源地址配置缺失 |
NO_CIRCUIT_ID_OPTION | DHCPV6_PROXY_ERROR_NO_CIRCUIT_ID_OPTION | 缺少Circuit ID选项 | Relay-Reply验证失败 |
NO_RELAY_MESSAGE_OPTION | DHCPV6_PROXY_ERROR_NO_RELAY_MESSAGE_OPTION | 缺少Relay消息选项 | Relay-Reply验证失败 |
BAD_SVR_FIB_OR_ADDRESS | DHCPV6_PROXY_ERROR_BAD_SVR_FIB_OR_ADDRESS | 服务器FIB或地址错误 | 服务器验证失败 |
PKT_TOO_BIG | DHCPV6_PROXY_ERROR_PKT_TOO_BIG | 报文过大 | 报文大小检查失败 |
WRONG_INTERFACE_ID_OPTION | DHCPV6_PROXY_ERROR_WRONG_INTERFACE_ID_OPTION | Interface ID选项错误 | Interface ID验证失败 |
ALLOC_FAIL | DHCPV6_PROXY_ERROR_ALLOC_FAIL | 缓冲区分配失败 | 内存分配错误 |
错误代码定义机制总结:
错误代码定义流程
│
├─→ 1. 定义宏
│ └─→ #define dhcp_proxy_error(n,s) DHCP_PROXY_ERROR_##n
│
├─→ 2. 包含定义文件
│ └─→ #include <dhcp/dhcp4_proxy_error.def>
│ └─→ 文件中使用宏定义所有错误代码
│
├─→ 3. 宏展开
│ └─→ dhcp_proxy_error(NONE, "no error")
│ └─→ 展开为:DHCP_PROXY_ERROR_NONE
│
├─→ 4. 取消宏定义
│ └─→ #undef dhcp_proxy_error
│
└─→ 5. 添加总数枚举值
└─→ DHCP_PROXY_N_ERROR
关键设计特点:
- 自动生成:使用宏定义机制自动生成枚举值和错误消息,减少手动维护
- 协议分离:IPv4和IPv6使用独立的错误代码枚举,避免混淆
- 扩展性:添加新错误只需在
.def文件中添加一行,无需修改头文件 - 类型安全:使用枚举类型提供类型检查,避免使用魔法数字
6.2.2 端口注册标志
功能说明:
端口注册标志用于跟踪UDP端口的注册状态。DHCP Proxy需要注册客户端端口和服务器端口,以便接收DHCP报文。标志使用位掩码方式,支持组合使用。
端口注册标志定义:
//46:51:src/plugins/dhcp/dhcp_proxy.h
/* flags to indicate which DHCP ports should be or have been registered */
/* 注释说明:标志用于指示哪些DHCP端口应该被注册或已经被注册。 */
typedef enum
{
DHCP_PORT_REG_CLIENT = 0x1, /* 客户端端口注册标志(位0) */
/* 说明:DHCP_PORT_REG_CLIENT标志表示客户端端口已注册。
*
* 值:0x1(二进制:0001)
* - 位0设置为1,表示客户端端口已注册
* - 用于IPv4和IPv6的客户端端口
*
* 端口号:
* - DHCPv4客户端端口:68
* - DHCPv6客户端端口:546
*
* 使用场景:
* - 在配置Proxy时,检查客户端端口是否已注册
* - 如果未注册,注册客户端端口
* - 在删除Proxy时,检查是否还有其他Proxy使用客户端端口
* - 如果没有,注销客户端端口 */
DHCP_PORT_REG_SERVER = 0x2, /* 服务器端口注册标志(位1) */
/* 说明:DHCP_PORT_REG_SERVER标志表示服务器端口已注册。
*
* 值:0x2(二进制:0010)
* - 位1设置为1,表示服务器端口已注册
* - 用于IPv4和IPv6的服务器端口
*
* 端口号:
* - DHCPv4服务器端口:67
* - DHCPv6服务器端口:547
*
* 使用场景:
* - 在配置Proxy时,检查服务器端口是否已注册
* - 如果未注册,注册服务器端口
* - 在删除Proxy时,检查是否还有其他Proxy使用服务器端口
* - 如果没有,注销服务器端口 */
} dhcp_port_reg_flags_t; /* 端口注册标志枚举类型 */
/* 说明:dhcp_port_reg_flags_t是端口注册标志的枚举类型。
*
* 使用方式:
* - 可以单独使用:DHCP_PORT_REG_CLIENT或DHCP_PORT_REG_SERVER
* - 可以组合使用:DHCP_PORT_REG_CLIENT | DHCP_PORT_REG_SERVER(值为0x3)
* - 存储在dhcp_proxy_main_t的udp_ports_registered字段中
*
* 位操作:
* - 检查客户端端口:flags & DHCP_PORT_REG_CLIENT
* - 检查服务器端口:flags & DHCP_PORT_REG_SERVER
* - 设置客户端端口:flags |= DHCP_PORT_REG_CLIENT
* - 设置服务器端口:flags |= DHCP_PORT_REG_SERVER
* - 清除客户端端口:flags &= ~DHCP_PORT_REG_CLIENT
* - 清除服务器端口:flags &= ~DHCP_PORT_REG_SERVER */
端口注册标志组合:
| 标志值 | 二进制 | 说明 |
|---|---|---|
0x0 | 0000 | 无端口注册 |
0x1 | 0001 | 仅客户端端口注册 |
0x2 | 0010 | 仅服务器端口注册 |
0x3 | 0011 | 客户端和服务器端口都已注册 |
端口注册标志使用流程:
端口注册标志使用流程
│
├─→ 1. 初始化
│ └─→ udp_ports_registered = 0(无端口注册)
│
├─→ 2. 配置Proxy时
│ ├─→ 检查客户端端口:if (!(flags & DHCP_PORT_REG_CLIENT))
│ │ └─→ 注册客户端端口
│ │ └─→ flags |= DHCP_PORT_REG_CLIENT
│ └─→ 检查服务器端口:if (!(flags & DHCP_PORT_REG_SERVER))
│ └─→ 注册服务器端口
│ └─→ flags |= DHCP_PORT_REG_SERVER
│
├─→ 3. 删除Proxy时
│ ├─→ 检查是否还有其他Proxy使用客户端端口
│ ├─→ 如果没有:注销客户端端口,flags &= ~DHCP_PORT_REG_CLIENT
│ ├─→ 检查是否还有其他Proxy使用服务器端口
│ └─→ 如果没有:注销服务器端口,flags &= ~DHCP_PORT_REG_SERVER
│
└─→ 完成(标志反映当前端口注册状态)
关键设计特点:
- 位掩码设计:使用位掩码方式,支持组合使用,节省存储空间
- 状态跟踪:准确跟踪端口注册状态,避免重复注册或遗漏注销
- 协议无关:标志值适用于IPv4和IPv6两种协议
- 原子操作:使用位操作实现标志的检查和设置,高效且线程安全
6.2.3 VSS类型定义
功能说明:
VSS(Virtual Sub-net Selection)类型定义用于标识不同的VSS类型。VSS是DHCP Option 82的一个子选项,用于在多租户环境中标识VPN。VPP支持三种VSS类型:ASCII标识符、VPN-ID和默认VPN。
VSS类型定义(在dhcp_vss_t结构体中):
//64:68:src/plugins/dhcp/dhcp_proxy.h
u8 vss_type; /* VSS类型字段 */
/* 说明:vss_type字段存储VSS类型,使用u8类型(1字节)。
*
* 类型值:
* - 0:ASCII VPN标识符(NVT ASCII VPN Identifier)
* - 1:VPN-ID(RFC 2685定义的7字节VPN标识符)
* - 255:全局默认VPN
* - 123:无效类型(用于错误处理)
*
* 存储位置:
* - 在dhcp_vss_t结构体中
* - 每个VRF可以配置一个VSS类型
* - 用于在转发DHCP请求时插入VSS选项 */
#define VSS_TYPE_ASCII 0 /* ASCII VPN标识符类型 */
/* 说明:VSS_TYPE_ASCII定义ASCII VPN标识符类型。
*
* 值:0
* 含义:使用ASCII字符串作为VPN标识符
*
* 使用方式:
* - 在dhcp_vss_t结构体中,如果vss_type = VSS_TYPE_ASCII
* - 则使用vpn_ascii_id字段存储ASCII字符串
* - vpn_id字段不使用
*
* 特点:
* - 灵活的VPN命名方式
* - 支持任意长度的ASCII字符串
* - 适合用户自定义的VPN名称 */
#define VSS_TYPE_VPN_ID 1 /* VPN-ID类型 */
/* 说明:VSS_TYPE_VPN_ID定义VPN-ID类型。
*
* 值:1
* 含义:使用RFC 2685定义的VPN-ID格式
*
* VPN-ID格式:
* - 总长度:7字节
* - 前3字节:OUI(Organizationally Unique Identifier,组织唯一标识符)
* - 后4字节:VPN索引(VPN Index)
*
* 使用方式:
* - 在dhcp_vss_t结构体中,如果vss_type = VSS_TYPE_VPN_ID
* - 则使用vpn_id字段存储7字节VPN-ID
* - vpn_ascii_id字段不使用
*
* 特点:
* - 标准化的VPN标识格式
* - 固定长度,便于处理
* - 适合标准化的VPN管理 */
#define VSS_TYPE_INVALID 123 /* 无效类型 */
/* 说明:VSS_TYPE_INVALID定义无效VSS类型。
*
* 值:123
* 含义:表示VSS类型无效或未配置
*
* 使用场景:
* - 初始化VSS配置时,设置为无效类型
* - 删除VSS配置时,标记为无效
* - 错误处理时,检查是否为无效类型
*
* 注意:
* - 123是一个特殊值,不会与有效类型(0、1、255)冲突
* - 用于区分"未配置"和"配置了默认VPN"两种情况 */
#define VSS_TYPE_DEFAULT 255 /* 全局默认VPN类型 */
/* 说明:VSS_TYPE_DEFAULT定义全局默认VPN类型。
*
* 值:255
* 含义:使用全局默认VPN,不插入VSS选项
*
* 使用方式:
* - 在dhcp_vss_t结构体中,如果vss_type = VSS_TYPE_DEFAULT
* - 则不插入VSS选项到DHCP请求中
* - vpn_id和vpn_ascii_id字段都不使用
*
* 特点:
* - 表示使用默认VPN配置
* - 不插入VSS选项,服务器使用默认地址池
* - 适合单租户环境或不需要VSS的场景 */
VSS类型对比表:
| 类型 | 值 | 标识符字段 | 长度 | 使用场景 | RFC标准 |
|---|---|---|---|---|---|
| ASCII | 0 | vpn_ascii_id | 可变 | 灵活的VPN命名 | RFC 6607 |
| VPN-ID | 1 | vpn_id[7] | 7字节 | 标准化VPN标识 | RFC 2685 |
| 默认 | 255 | 无 | 0 | 默认VPN,不插入VSS | RFC 6607 |
| 无效 | 123 | 无 | 0 | 错误处理 | 内部使用 |
VSS类型使用流程:
VSS类型使用流程
│
├─→ 1. 配置VSS
│ ├─→ 用户指定VSS类型(ASCII、VPN-ID或默认)
│ ├─→ 根据类型设置vss_type字段
│ └─→ 根据类型设置对应的标识符字段
│
├─→ 2. 转发请求时
│ ├─→ 检查vss_type
│ ├─→ 如果为VSS_TYPE_ASCII:插入ASCII VSS选项
│ ├─→ 如果为VSS_TYPE_VPN_ID:插入VPN-ID VSS选项
│ └─→ 如果为VSS_TYPE_DEFAULT:不插入VSS选项
│
└─→ 完成(VSS选项已插入或跳过)
关键设计特点:
- 类型明确:使用枚举值明确区分不同的VSS类型,避免混淆
- 灵活支持:支持ASCII和VPN-ID两种标准格式,满足不同需求
- 默认处理:支持默认VPN类型,兼容不需要VSS的场景
- 错误处理:使用特殊值(123)标识无效类型,便于错误检测
6.2.1-6.2.3节总结:
本节详细讲解了dhcp_proxy.h头文件中的基础定义部分,包括:
-
错误代码定义:
- DHCPv4和DHCPv6使用独立的错误代码枚举
- 使用宏定义机制自动生成枚举值和错误消息
- 支持扩展,添加新错误只需修改
.def文件
-
端口注册标志:
- 使用位掩码方式跟踪UDP端口注册状态
- 支持客户端端口和服务器端口的独立跟踪
- 适用于IPv4和IPv6两种协议
-
VSS类型定义:
- 支持ASCII、VPN-ID和默认三种VSS类型
- 符合RFC 6607和RFC 2685标准
- 提供灵活的VPN标识方式
这些基础定义为后续的函数接口和实现提供了类型安全和错误处理支持。
6.2.4 函数接口声明
功能说明:
函数接口声明定义了DHCP Proxy模块对外提供的所有公共函数。这些函数包括配置管理、UDP端口注册、配置查询、锁机制等。本节按照功能分类讲解各个函数接口的声明和用途。
6.2.4.1 UDP端口注册函数
//165:168:src/plugins/dhcp/dhcp_proxy.h
/**
* @brief Register the dhcp client and/or server ports, if not already done
*/
/* 注释说明:注册DHCP客户端和/或服务器端口,如果尚未注册。 */
void dhcp_maybe_register_udp_ports (dhcp_port_reg_flags_t ports); /* UDP端口注册函数 */
/* 说明:dhcp_maybe_register_udp_ports函数用于注册DHCP客户端和/或服务器UDP端口。
*
* 函数签名:
* - 返回类型:void(无返回值)
* - 参数:ports(dhcp_port_reg_flags_t类型)
* - 端口注册标志,指定要注册的端口
* - DHCP_PORT_REG_CLIENT:注册客户端端口
* - DHCP_PORT_REG_SERVER:注册服务器端口
* - 可以组合使用:DHCP_PORT_REG_CLIENT | DHCP_PORT_REG_SERVER
*
* 功能:
* - 检查端口是否已注册(通过dhcp_proxy_main.udp_ports_registered)
* - 如果未注册,注册指定的UDP端口
* - 如果已注册,跳过注册(幂等操作)
*
* 端口号:
* - DHCPv4客户端端口:68
* - DHCPv4服务器端口:67
* - DHCPv6客户端端口:546
* - DHCPv6服务器端口:547
*
* 使用场景:
* - 在配置Proxy时调用,确保UDP端口已注册
* - 在模块初始化时调用,注册必要的端口
*
* 实现位置:
* - 在dhcp_proxy.c中实现
* - 使用VPP的UDP本地端口注册机制 */
6.2.4.2 Proxy配置管理函数
//194:202:src/plugins/dhcp/dhcp_proxy.h
/**
* @brief Add a new DHCP proxy server configuration.
* @return 1 is the config is new,
* 0 otherwise (implying a modify of an existing)
*/
/* 注释说明:添加新的DHCP Proxy服务器配置。
* 返回值:1表示配置是新的,0表示修改了现有配置。 */
int dhcp_proxy_server_add (fib_protocol_t proto, /* 协议类型(IPv4或IPv6) */
/* 说明:proto参数指定协议类型。
*
* 类型:fib_protocol_t枚举
* - FIB_PROTOCOL_IP4:IPv4协议
* - FIB_PROTOCOL_IP6:IPv6协议
*
* 用途:
* - 确定使用哪个协议的配置池
* - IPv4和IPv6使用独立的配置池和索引映射 */
ip46_address_t * addr, /* 服务器地址指针 */
/* 说明:addr参数指定DHCP服务器地址。
*
* 类型:ip46_address_t *(IPv4/IPv6地址联合体指针)
* - 支持IPv4和IPv6地址
* - 地址格式由proto参数确定
*
* 用途:
* - 添加到服务器列表中
* - 用于转发DHCP请求到服务器 */
ip46_address_t * src_address, /* 源地址指针 */
/* 说明:src_address参数指定Proxy使用的源地址。
*
* 类型:ip46_address_t *(IPv4/IPv6地址联合体指针)
* - 支持IPv4和IPv6地址
* - 用于在转发请求时设置源地址
*
* 用途:
* - 设置dhcp_proxy_t的dhcp_src_address字段
* - 服务器回复时使用此地址作为目标地址 */
u32 rx_fib_iindex, /* 接收FIB索引 */
/* 说明:rx_fib_iindex参数指定客户端所在的FIB索引。
*
* 类型:u32(32位无符号整数)
* - FIB索引,不是外部Table-ID
* - 用于标识客户端所在的VRF
*
* 用途:
* - 在dhcp_server_index_by_rx_fib_index中建立映射
* - 用于查找Proxy配置
* - 每个VRF可以有独立的Proxy配置 */
u32 server_table_id); /* 服务器Table-ID */
/* 说明:server_table_id参数指定服务器所在的Table-ID。
*
* 类型:u32(32位无符号整数)
* - 外部Table-ID,不是FIB索引
* - 需要转换为FIB索引
*
* 用途:
* - 转换为FIB索引后,设置dhcp_server_t的server_fib_index字段
* - 用于转发请求到服务器时的路由查找 */
/* 说明:dhcp_proxy_server_add函数用于添加或修改DHCP Proxy服务器配置。
*
* 返回值:
* - 1:配置是新的(新创建的Proxy配置)
* - 0:修改了现有配置(在现有Proxy配置中添加了服务器)
*
* 功能:
* - 根据rx_fib_index查找或创建Proxy配置
* - 在服务器列表中添加或更新服务器信息
* - 建立FIB索引到Proxy配置索引的映射
* - 注册UDP端口(如果未注册)
*
* 使用场景:
* - 通过CLI或API配置Proxy时调用
* - 支持为同一VRF配置多个服务器
*
* 实现位置:
* - 在dhcp_proxy.c中实现 */
//204:210:src/plugins/dhcp/dhcp_proxy.h
/**
* @brief Delete a DHCP proxy config
* @return 1 if the proxy is deleted, 0 otherwise
*/
/* 注释说明:删除DHCP Proxy配置。
* 返回值:1表示Proxy被删除,0表示未删除。 */
int dhcp_proxy_server_del (fib_protocol_t proto, /* 协议类型 */
/* 说明:proto参数指定协议类型,用于确定使用哪个协议的配置池。 */
u32 rx_fib_index, /* 接收FIB索引 */
/* 说明:rx_fib_index参数指定要删除的Proxy配置的FIB索引。 */
ip46_address_t * addr, /* 服务器地址指针 */
/* 说明:addr参数指定要删除的服务器地址。
*
* 用途:
* - 从服务器列表中删除指定的服务器
* - 如果删除后服务器列表为空,删除整个Proxy配置 */
u32 server_table_id); /* 服务器Table-ID */
/* 说明:server_table_id参数指定服务器的Table-ID,用于匹配服务器。 */
/* 说明:dhcp_proxy_server_del函数用于删除DHCP Proxy服务器配置。
*
* 返回值:
* - 1:Proxy配置被删除(服务器列表为空)
* - 0:仅删除了服务器,Proxy配置仍存在
*
* 功能:
* - 从服务器列表中删除指定的服务器
* - 如果服务器列表为空,删除整个Proxy配置
* - 从索引映射中删除映射关系
* - 注销UDP端口(如果没有其他Proxy使用)
*
* 使用场景:
* - 通过CLI或API删除Proxy配置时调用
* - 支持删除部分服务器或整个Proxy配置 */
6.2.4.3 VSS配置函数
//181:187:src/plugins/dhcp/dhcp_proxy.h
/**
* @brief Configure/set a new VSS info
*/
/* 注释说明:配置/设置新的VSS信息。 */
int dhcp_proxy_set_vss (fib_protocol_t proto, /* 协议类型 */
/* 说明:proto参数指定协议类型。 */
u32 tbl_id, /* Table-ID */
/* 说明:tbl_id参数指定VRF的Table-ID,需要转换为FIB索引。 */
u8 vss_type, /* VSS类型 */
/* 说明:vss_type参数指定VSS类型。
* - VSS_TYPE_ASCII:ASCII VPN标识符
* - VSS_TYPE_VPN_ID:VPN-ID
* - VSS_TYPE_DEFAULT:默认VPN */
u8 * vpn_ascii_id, /* ASCII VPN标识符指针 */
/* 说明:vpn_ascii_id参数指定ASCII VPN标识符字符串。
* - 仅在vss_type = VSS_TYPE_ASCII时使用
* - 可以为NULL(如果使用VPN-ID或默认类型) */
u32 oui, /* OUI(组织唯一标识符) */
/* 说明:oui参数指定VPN-ID的前3字节OUI。
* - 仅在vss_type = VSS_TYPE_VPN_ID时使用
* - 24位值,存储在vpn_id[0-2]中 */
u32 vpn_index, /* VPN索引 */
/* 说明:vpn_index参数指定VPN-ID的后4字节VPN索引。
* - 仅在vss_type = VSS_TYPE_VPN_ID时使用
* - 32位值,存储在vpn_id[3-6]中 */
u8 is_del); /* 删除标志 */
/* 说明:is_del参数指定是删除还是添加VSS配置。
* - 1:删除VSS配置
* - 0:添加或修改VSS配置 */
/* 说明:dhcp_proxy_set_vss函数用于配置或删除VSS信息。
*
* 返回值:
* - 0:成功
* - 非0:失败(错误代码)
*
* 功能:
* - 根据tbl_id查找或创建VSS配置
* - 根据vss_type设置VSS类型和标识符
* - 建立FIB索引到VSS配置索引的映射
* - 支持删除VSS配置
*
* 使用场景:
* - 通过CLI或API配置VSS时调用
* - 支持多租户环境中的VPN标识 */
6.2.4.4 配置查询和遍历函数
//189:192:src/plugins/dhcp/dhcp_proxy.h
/**
* @brief Dump the proxy configs to the API
*/
/* 注释说明:将Proxy配置转储到API。 */
void dhcp_proxy_dump (fib_protocol_t proto, /* 协议类型 */
/* 说明:proto参数指定协议类型,用于确定转储哪个协议的配置。 */
void *opaque, /* 不透明指针 */
/* 说明:opaque参数是用户提供的不透明指针,用于传递上下文信息。
* - 通常用于API响应结构
* - 在回调函数中传递给dhcp_send_details */
u32 context); /* 上下文标识符 */
/* 说明:context参数是上下文标识符,用于关联请求和响应。
* - 在API调用中,用于匹配请求和响应
* - 在回调函数中传递给dhcp_send_details */
/* 说明:dhcp_proxy_dump函数用于将所有Proxy配置转储到API。
*
* 功能:
* - 遍历所有Proxy配置
* - 对每个配置调用dhcp_send_details发送详细信息
* - 用于API的配置查询功能
*
* 使用场景:
* - API客户端请求获取所有Proxy配置时调用
* - 用于配置管理和监控 */
//170:174:src/plugins/dhcp/dhcp_proxy.h
/**
* @brief Send the details of a proxy session to the API client during a dump
*/
/* 注释说明:在转储期间将Proxy会话的详细信息发送给API客户端。 */
void dhcp_send_details (fib_protocol_t proto, /* 协议类型 */
/* 说明:proto参数指定协议类型。 */
void *opaque, /* 不透明指针 */
/* 说明:opaque参数是用户提供的不透明指针,用于传递上下文信息。 */
u32 context, /* 上下文标识符 */
/* 说明:context参数是上下文标识符。 */
dhcp_proxy_t * proxy); /* Proxy配置指针 */
/* 说明:proxy参数指定要发送详细信息的Proxy配置。
*
* 包含的信息:
* - 服务器列表
* - 源地址
* - FIB索引
* - 其他配置信息 */
/* 说明:dhcp_send_details函数用于发送Proxy配置的详细信息到API客户端。
*
* 功能:
* - 将Proxy配置信息序列化为API消息格式
* - 通过API机制发送给客户端
* - 用于配置查询和监控
*
* 使用场景:
* - 在dhcp_proxy_dump中调用,发送每个配置的详细信息
* - 在API响应中使用 */
//214:224:src/plugins/dhcp/dhcp_proxy.h
/**
* @brief Callback function invoked for each DHCP proxy entry
* return 0 to break the walk, non-zero otherwise.
*/
/* 注释说明:为每个DHCP Proxy条目调用的回调函数。
* 返回0以中断遍历,否则返回非0值。 */
typedef int (*dhcp_proxy_walk_fn_t) (dhcp_proxy_t * server, /* Proxy配置指针 */
/* 说明:server参数是当前遍历到的Proxy配置指针。 */
void *ctx); /* 上下文指针 */
/* 说明:ctx参数是用户提供的上下文指针,用于传递额外信息。 */
/* 说明:dhcp_proxy_walk_fn_t是Proxy配置遍历的回调函数类型。
*
* 返回值:
* - 0:中断遍历
* - 非0:继续遍历
*
* 用途:
* - 在dhcp_proxy_walk中使用
* - 用于遍历所有Proxy配置并执行自定义操作 */
/**
* @brief Walk/Visit each DHCP proxy server
*/
/* 注释说明:遍历/访问每个DHCP Proxy服务器。 */
void dhcp_proxy_walk (fib_protocol_t proto, /* 协议类型 */
/* 说明:proto参数指定协议类型。 */
dhcp_proxy_walk_fn_t fn, /* 回调函数指针 */
/* 说明:fn参数是回调函数指针,对每个Proxy配置调用。 */
void *ctx); /* 上下文指针 */
/* 说明:ctx参数是用户提供的上下文指针,传递给回调函数。 */
/* 说明:dhcp_proxy_walk函数用于遍历所有Proxy配置。
*
* 功能:
* - 遍历指定协议的所有Proxy配置
* - 对每个配置调用用户提供的回调函数
* - 支持通过回调函数返回值中断遍历
*
* 使用场景:
* - 配置查询和统计
* - 配置验证和清理
* - 自定义配置处理 */
//226:236:src/plugins/dhcp/dhcp_proxy.h
/**
* @brief Callback function invoked for each DHCP VSS entry
* return 0 to break the walk, non-zero otherwise.
*/
/* 注释说明:为每个DHCP VSS条目调用的回调函数。
* 返回0以中断遍历,否则返回非0值。 */
typedef int (*dhcp_vss_walk_fn_t) (dhcp_vss_t * server, /* VSS配置指针 */
/* 说明:server参数是当前遍历到的VSS配置指针。 */
u32 rx_table_id, /* 接收Table-ID */
/* 说明:rx_table_id参数是VSS配置对应的Table-ID。 */
void *ctx); /* 上下文指针 */
/* 说明:ctx参数是用户提供的上下文指针。 */
/* 说明:dhcp_vss_walk_fn_t是VSS配置遍历的回调函数类型。
*
* 返回值:
* - 0:中断遍历
* - 非0:继续遍历 */
/**
* @brief Walk/Visit each DHCP proxy VSS
*/
/* 注释说明:遍历/访问每个DHCP Proxy VSS。 */
void dhcp_vss_walk (fib_protocol_t proto, /* 协议类型 */
/* 说明:proto参数指定协议类型。 */
dhcp_vss_walk_fn_t fn, /* 回调函数指针 */
/* 说明:fn参数是回调函数指针,对每个VSS配置调用。 */
void *ctx); /* 上下文指针 */
/* 说明:ctx参数是用户提供的上下文指针。 */
/* 说明:dhcp_vss_walk函数用于遍历所有VSS配置。
*
* 功能:
* - 遍历指定协议的所有VSS配置
* - 对每个配置调用用户提供的回调函数
* - 支持通过回调函数返回值中断遍历
*
* 使用场景:
* - VSS配置查询和统计
* - VSS配置验证和清理 */
6.2.4.5 锁机制函数
//238:248:src/plugins/dhcp/dhcp_proxy.h
/**
* @brief Lock a proxy object to prevent simultaneous access of its
* pending store
*/
/* 注释说明:锁定Proxy对象,防止同时访问其待处理存储。 */
void dhcp_proxy_lock (dhcp_proxy_t * server); /* Proxy配置指针 */
/* 说明:server参数指定要锁定的Proxy配置。
*
* 功能:
* - 获取Proxy配置的锁
* - 防止多线程同时访问dhcp_pending哈希表
* - 使用自旋锁或互斥锁实现
*
* 使用场景:
* - 在访问或修改dhcp_pending哈希表前调用
* - 确保线程安全 */
/**
* @brief Lock a proxy object to prevent simultaneous access of its
* pending store
*/
/* 注释说明:解锁Proxy对象。 */
void dhcp_proxy_unlock (dhcp_proxy_t * server); /* Proxy配置指针 */
/* 说明:server参数指定要解锁的Proxy配置。
*
* 功能:
* - 释放Proxy配置的锁
* - 允许其他线程访问dhcp_pending哈希表
*
* 使用场景:
* - 在访问或修改dhcp_pending哈希表后调用
* - 与dhcp_proxy_lock配对使用 */
/* 说明:dhcp_proxy_lock和dhcp_proxy_unlock函数用于保护Proxy配置的并发访问。
*
* 锁的作用:
* - 保护dhcp_pending哈希表的并发访问
* - 防止多线程同时修改待处理请求
* - 确保Cookie机制的正确性
*
* 使用模式:
* - 必须在访问dhcp_pending前调用dhcp_proxy_lock
* - 必须在访问完成后调用dhcp_proxy_unlock
* - 必须配对使用,避免死锁 */
6.2.4.6 辅助查询函数
//211:212:src/plugins/dhcp/dhcp_proxy.h
u32 dhcp_proxy_rx_table_get_table_id (fib_protocol_t proto, u32 fib_index); /* 获取Table-ID函数 */
/* 说明:dhcp_proxy_rx_table_get_table_id函数用于将FIB索引转换为Table-ID。
*
* 参数:
* - proto:协议类型(IPv4或IPv6)
* - fib_index:FIB索引
*
* 返回值:
* - Table-ID(外部Table-ID)
*
* 功能:
* - 将内部FIB索引转换为外部Table-ID
* - 用于API响应和CLI显示
*
* 使用场景:
* - 在API响应中返回Table-ID
* - 在CLI显示中使用Table-ID */
//176:179:src/plugins/dhcp/dhcp_proxy.h
/**
* @brief Show (on CLI) a VSS config during a show walk
*/
/* 注释说明:在显示遍历期间在CLI上显示VSS配置。 */
int dhcp_vss_show_walk (dhcp_vss_t * vss, /* VSS配置指针 */
/* 说明:vss参数指定要显示的VSS配置。 */
u32 rx_table_id, /* 接收Table-ID */
/* 说明:rx_table_id参数是VSS配置对应的Table-ID。 */
void *ctx); /* 上下文指针 */
/* 说明:ctx参数是用户提供的上下文指针,用于CLI输出。 */
/* 说明:dhcp_vss_show_walk函数用于在CLI上显示VSS配置。
*
* 返回值:
* - 0:成功
* - 非0:失败
*
* 功能:
* - 格式化VSS配置信息
* - 输出到CLI
* - 用于"show dhcp proxy"命令
*
* 使用场景:
* - 在CLI命令中调用,显示VSS配置信息 */
6.2.4.7 协议特定函数
//289:295:src/plugins/dhcp/dhcp_proxy.h
int dhcp6_proxy_set_server (ip46_address_t * addr, /* 服务器地址 */
/* 说明:addr参数指定DHCPv6服务器地址。 */
ip46_address_t * src_addr, /* 源地址 */
/* 说明:src_addr参数指定Proxy使用的源IPv6地址。 */
u32 rx_table_id, /* 接收Table-ID */
/* 说明:rx_table_id参数指定客户端所在的Table-ID。 */
u32 server_table_id, /* 服务器Table-ID */
/* 说明:server_table_id参数指定服务器所在的Table-ID。 */
int is_del); /* 删除标志 */
/* 说明:is_del参数指定是删除还是添加配置(1=删除,0=添加)。 */
/* 说明:dhcp6_proxy_set_server函数是DHCPv6 Proxy的配置接口。
*
* 功能:
* - 调用dhcp_proxy_server_add或dhcp_proxy_server_del
* - 提供DHCPv6特定的配置接口
* - 用于CLI和API
*
* 使用场景:
* - DHCPv6 Proxy配置命令
* - DHCPv6 Proxy API调用 */
int dhcp4_proxy_set_server (ip46_address_t * addr, /* 服务器地址 */
/* 说明:addr参数指定DHCPv4服务器地址。 */
ip46_address_t * src_addr, /* 源地址 */
/* 说明:src_addr参数指定Proxy使用的源IPv4地址。 */
u32 rx_table_id, /* 接收Table-ID */
/* 说明:rx_table_id参数指定客户端所在的Table-ID。 */
u32 server_table_id, /* 服务器Table-ID */
/* 说明:server_table_id参数指定服务器所在的Table-ID。 */
int is_del); /* 删除标志 */
/* 说明:is_del参数指定是删除还是添加配置(1=删除,0=添加)。 */
/* 说明:dhcp4_proxy_set_server函数是DHCPv4 Proxy的配置接口。
*
* 功能:
* - 调用dhcp_proxy_server_add或dhcp_proxy_server_del
* - 提供DHCPv4特定的配置接口
* - 用于CLI和API
*
* 使用场景:
* - DHCPv4 Proxy配置命令
* - DHCPv4 Proxy API调用 */
函数接口分类总结:
| 功能分类 | 函数名 | 说明 |
|---|---|---|
| 端口注册 | dhcp_maybe_register_udp_ports | 注册UDP端口 |
| 配置管理 | dhcp_proxy_server_add | 添加Proxy配置 |
dhcp_proxy_server_del | 删除Proxy配置 | |
dhcp_proxy_set_vss | 配置VSS信息 | |
| 配置查询 | dhcp_proxy_dump | 转储所有配置 |
dhcp_send_details | 发送配置详情 | |
dhcp_proxy_walk | 遍历Proxy配置 | |
dhcp_vss_walk | 遍历VSS配置 | |
| 锁机制 | dhcp_proxy_lock | 锁定Proxy配置 |
dhcp_proxy_unlock | 解锁Proxy配置 | |
| 辅助函数 | dhcp_proxy_rx_table_get_table_id | 获取Table-ID |
dhcp_vss_show_walk | 显示VSS配置 | |
| 协议特定 | dhcp4_proxy_set_server | DHCPv4配置接口 |
dhcp6_proxy_set_server | DHCPv6配置接口 |
6.2.5 内联辅助函数
功能说明:
内联辅助函数提供了快速查找Proxy配置和VSS配置的功能。这些函数使用内联方式实现,避免函数调用开销,提高性能。函数通过FIB索引查找对应的配置,使用索引映射实现O(1)时间复杂度的查找。
6.2.5.1 dhcp_get_vss_info() - 获取VSS配置
功能说明:
dhcp_get_vss_info()函数用于根据FIB索引查找对应的VSS配置。函数通过索引映射快速定位VSS配置,如果未配置则返回NULL。
函数输入:
dm:DHCP Proxy全局管理器指针rx_fib_index:接收FIB索引(客户端所在的FIB索引)proto:协议类型(IPv4或IPv6)
函数输出:
- 返回VSS配置指针,如果未配置则返回NULL
源码详解:
//252:268:src/plugins/dhcp/dhcp_proxy.h
/**
* @brief Get the VSS data for the FIB index
*/
/* 注释说明:获取FIB索引对应的VSS数据。 */
static inline dhcp_vss_t * /* 返回类型:VSS配置指针,内联函数 */
/* 说明:static inline表示这是一个静态内联函数。
*
* static:
* - 函数仅在当前文件内可见
* - 避免与其他文件的同名函数冲突
*
* inline:
* - 建议编译器将函数内联展开
* - 避免函数调用开销
* - 提高性能,适合频繁调用的小函数 */
dhcp_get_vss_info (dhcp_proxy_main_t * dm, /* 全局管理器指针 */
/* 说明:dm参数是DHCP Proxy全局管理器指针。
*
* 用途:
* - 访问vss配置池
* - 访问vss_index_by_rx_fib_index索引映射
* - 全局管理器是单例,在dhcp_proxy.c中定义 */
u32 rx_fib_index, /* 接收FIB索引 */
/* 说明:rx_fib_index参数是客户端所在的FIB索引。
*
* 用途:
* - 作为索引映射的键
* - 查找对应的VSS配置索引
* - 每个VRF可以有独立的VSS配置 */
fib_protocol_t proto) /* 协议类型 */
/* 说明:proto参数指定协议类型。
*
* 用途:
* - 确定使用哪个协议的配置池
* - IPv4和IPv6使用独立的VSS配置池 */
{
dhcp_vss_t *v = NULL; /* 初始化返回值为NULL */
/* 说明:v变量用于存储找到的VSS配置指针,初始化为NULL。
*
* 初始化原因:
* - 如果查找失败,返回NULL
* - 如果未配置VSS,返回NULL
* - NULL表示未找到或未配置 */
if (vec_len (dm->vss_index_by_rx_fib_index[proto]) > rx_fib_index && /* 检查索引映射向量长度 */
/* 说明:第一个条件检查索引映射向量的长度是否大于rx_fib_index。
*
* vec_len:
* - 返回向量的长度(元素个数)
* - 如果rx_fib_index >= vec_len,表示索引超出范围
*
* 检查目的:
* - 防止数组越界访问
* - 确保索引在有效范围内
* - 如果向量未扩展到此索引,表示未配置 */
dm->vss_index_by_rx_fib_index[proto][rx_fib_index] != ~0) /* 检查索引值是否为~0 */
/* 说明:第二个条件检查索引映射的值是否为~0。
*
* ~0:
* - 表示所有位都为1(0xFFFFFFFF)
* - 用作"未配置"的标记值
* - 如果值为~0,表示该VRF未配置VSS
*
* 检查目的:
* - 区分"未配置"和"已配置"
* - 只有值不为~0时,才表示已配置VSS */
{
v = pool_elt_at_index (dm->vss[proto], /* 从VSS配置池中获取配置 */
/* 说明:pool_elt_at_index函数从内存池中通过索引获取元素。
*
* 参数1:dm->vss[proto]
* - VSS配置池指针
* - proto确定使用IPv4还是IPv6的配置池
*
* 功能:
* - 通过索引从池中获取VSS配置
* - 返回配置指针
* - 如果索引无效,返回NULL或崩溃(取决于实现) */
dm->vss_index_by_rx_fib_index[proto] /* 索引映射向量 */
/* 说明:参数2是索引映射向量,用于获取VSS配置在池中的索引。
*
* 访问方式:
* - dm->vss_index_by_rx_fib_index[proto][rx_fib_index]
* - 通过rx_fib_index作为数组索引
* - 获取的值是VSS配置在池中的索引 */
[rx_fib_index]); /* FIB索引作为数组索引 */
/* 说明:使用rx_fib_index作为数组索引,获取VSS配置在池中的索引。
*
* 查找流程:
* 1. 使用rx_fib_index访问索引映射数组
* 2. 获取的值是VSS配置在池中的索引
* 3. 使用此索引从池中获取VSS配置
* 4. 返回配置指针 */
}
return (v); /* 返回VSS配置指针或NULL */
/* 说明:返回找到的VSS配置指针。
*
* 返回值:
* - 如果找到配置:返回VSS配置指针
* - 如果未找到或未配置:返回NULL
*
* 调用者需要检查返回值是否为NULL */
}
函数流程总结:
dhcp_get_vss_info() 函数流程
│
├─→ 1. 初始化返回值
│ └─→ v = NULL
│
├─→ 2. 边界检查
│ ├─→ 检查索引映射向量长度 > rx_fib_index
│ └─→ 检查索引值 != ~0
│
├─→ 3. 查找VSS配置
│ ├─→ 使用rx_fib_index访问索引映射
│ ├─→ 获取VSS配置在池中的索引
│ └─→ 从池中获取VSS配置指针
│
└─→ 4. 返回结果
└─→ 返回VSS配置指针或NULL
关键设计特点:
- 内联实现:使用
static inline避免函数调用开销,提高性能 - 边界检查:检查向量长度和索引值,防止数组越界
- 快速查找:使用索引映射实现O(1)时间复杂度的查找
- NULL安全:未找到时返回NULL,调用者需要检查
使用示例:
dhcp_vss_t *vss = dhcp_get_vss_info(&dhcp_proxy_main, rx_fib_index, FIB_PROTOCOL_IP4);
if (vss != NULL) {
// 使用VSS配置
if (vss->vss_type == VSS_TYPE_ASCII) {
// 处理ASCII VSS
}
} else {
// 未配置VSS,使用默认处理
}
6.2.5.2 dhcp_get_proxy() - 获取Proxy配置
功能说明:
dhcp_get_proxy()函数用于根据FIB索引查找对应的Proxy配置。函数通过索引映射快速定位Proxy配置,如果未配置则返回NULL。这是Proxy处理中最常用的查找函数。
函数输入:
dm:DHCP Proxy全局管理器指针rx_fib_index:接收FIB索引(客户端所在的FIB索引)proto:协议类型(IPv4或IPv6)
函数输出:
- 返回Proxy配置指针,如果未配置则返回NULL
源码详解:
//270:288:src/plugins/dhcp/dhcp_proxy.h
/**
* @brief Get the DHCP proxy server data for the FIB index
*/
/* 注释说明:获取FIB索引对应的DHCP Proxy服务器数据。 */
static inline dhcp_proxy_t * /* 返回类型:Proxy配置指针,内联函数 */
/* 说明:static inline表示这是一个静态内联函数。
*
* 特点:
* - 函数仅在当前文件内可见
* - 建议编译器内联展开
* - 避免函数调用开销 */
dhcp_get_proxy (dhcp_proxy_main_t * dm, /* 全局管理器指针 */
/* 说明:dm参数是DHCP Proxy全局管理器指针。
*
* 用途:
* - 访问dhcp_servers配置池
* - 访问dhcp_server_index_by_rx_fib_index索引映射 */
u32 rx_fib_index, /* 接收FIB索引 */
/* 说明:rx_fib_index参数是客户端所在的FIB索引。
*
* 用途:
* - 作为索引映射的键
* - 查找对应的Proxy配置索引 */
fib_protocol_t proto) /* 协议类型 */
/* 说明:proto参数指定协议类型。
*
* 用途:
* - 确定使用哪个协议的配置池
* - IPv4和IPv6使用独立的Proxy配置池 */
{
dhcp_proxy_t *s = NULL; /* 初始化返回值为NULL */
/* 说明:s变量用于存储找到的Proxy配置指针,初始化为NULL。
*
* 初始化原因:
* - 如果查找失败,返回NULL
* - 如果未配置Proxy,返回NULL
* - NULL表示未找到或未配置 */
if (vec_len (dm->dhcp_server_index_by_rx_fib_index[proto]) > rx_fib_index && /* 检查索引映射向量长度 */
/* 说明:第一个条件检查索引映射向量的长度是否大于rx_fib_index。
*
* 检查目的:
* - 防止数组越界访问
* - 确保索引在有效范围内
* - 如果向量未扩展到此索引,表示未配置Proxy */
dm->dhcp_server_index_by_rx_fib_index[proto][rx_fib_index] != ~0) /* 检查索引值是否为~0 */
/* 说明:第二个条件检查索引映射的值是否为~0。
*
* ~0:
* - 表示所有位都为1(0xFFFFFFFF)
* - 用作"未配置"的标记值
* - 如果值为~0,表示该VRF未配置Proxy
*
* 检查目的:
* - 区分"未配置"和"已配置"
* - 只有值不为~0时,才表示已配置Proxy */
{
s = pool_elt_at_index (dm->dhcp_servers[proto], /* 从Proxy配置池中获取配置 */
/* 说明:pool_elt_at_index函数从内存池中通过索引获取元素。
*
* 参数1:dm->dhcp_servers[proto]
* - Proxy配置池指针
* - proto确定使用IPv4还是IPv6的配置池
*
* 功能:
* - 通过索引从池中获取Proxy配置
* - 返回配置指针 */
dm->dhcp_server_index_by_rx_fib_index[proto] /* 索引映射向量 */
/* 说明:参数2是索引映射向量,用于获取Proxy配置在池中的索引。
*
* 访问方式:
* - dm->dhcp_server_index_by_rx_fib_index[proto][rx_fib_index]
* - 通过rx_fib_index作为数组索引
* - 获取的值是Proxy配置在池中的索引 */
[rx_fib_index]); /* FIB索引作为数组索引 */
/* 说明:使用rx_fib_index作为数组索引,获取Proxy配置在池中的索引。
*
* 查找流程:
* 1. 使用rx_fib_index访问索引映射数组
* 2. 获取的值是Proxy配置在池中的索引
* 3. 使用此索引从池中获取Proxy配置
* 4. 返回配置指针 */
}
return (s); /* 返回Proxy配置指针或NULL */
/* 说明:返回找到的Proxy配置指针。
*
* 返回值:
* - 如果找到配置:返回Proxy配置指针
* - 如果未找到或未配置:返回NULL
*
* 调用者需要检查返回值是否为NULL */
}
函数流程总结:
dhcp_get_proxy() 函数流程
│
├─→ 1. 初始化返回值
│ └─→ s = NULL
│
├─→ 2. 边界检查
│ ├─→ 检查索引映射向量长度 > rx_fib_index
│ └─→ 检查索引值 != ~0
│
├─→ 3. 查找Proxy配置
│ ├─→ 使用rx_fib_index访问索引映射
│ ├─→ 获取Proxy配置在池中的索引
│ └─→ 从池中获取Proxy配置指针
│
└─→ 4. 返回结果
└─→ 返回Proxy配置指针或NULL
关键设计特点:
- 内联实现:使用
static inline避免函数调用开销,提高性能 - 边界检查:检查向量长度和索引值,防止数组越界
- 快速查找:使用索引映射实现O(1)时间复杂度的查找
- NULL安全:未找到时返回NULL,调用者需要检查
使用示例:
dhcp_proxy_t *proxy = dhcp_get_proxy(&dhcp_proxy_main, rx_fib_index, FIB_PROTOCOL_IP4);
if (proxy != NULL) {
// 使用Proxy配置
// 访问服务器列表、源地址等
dhcp_proxy_lock(proxy);
// 访问dhcp_pending哈希表
dhcp_proxy_unlock(proxy);
} else {
// 未配置Proxy,丢弃报文或返回错误
}
两个内联函数的对比:
| 特性 | dhcp_get_vss_info() | dhcp_get_proxy() |
|---|---|---|
| 返回类型 | dhcp_vss_t * | dhcp_proxy_t * |
| 配置池 | dm->vss[proto] | dm->dhcp_servers[proto] |
| 索引映射 | dm->vss_index_by_rx_fib_index[proto] | dm->dhcp_server_index_by_rx_fib_index[proto] |
| 使用频率 | 较低(仅在需要VSS时) | 很高(每个报文都需要) |
| 用途 | 查找VSS配置 | 查找Proxy配置 |
6.2.4-6.2.5节总结:
本节详细讲解了dhcp_proxy.h头文件中的函数接口声明和内联辅助函数,包括:
-
函数接口声明:
- UDP端口注册函数
- Proxy配置管理函数(添加、删除、VSS配置)
- 配置查询和遍历函数
- 锁机制函数
- 辅助查询函数
- 协议特定函数
-
内联辅助函数:
dhcp_get_vss_info():快速查找VSS配置dhcp_get_proxy():快速查找Proxy配置- 使用索引映射实现O(1)时间复杂度
- 内联实现避免函数调用开销
这些函数接口为DHCP Proxy模块提供了完整的配置管理和查询功能,内联辅助函数提供了高效的配置查找能力。
6.2节总结:
本节详细分析了dhcp_proxy.h头文件的所有组成部分,包括:
-
错误代码定义(6.2.1):
- DHCPv4和DHCPv6使用独立的错误代码枚举
- 使用宏定义机制自动生成
-
端口注册标志(6.2.2):
- 使用位掩码跟踪UDP端口注册状态
- 支持客户端和服务器端口的独立跟踪
-
VSS类型定义(6.2.3):
- 支持ASCII、VPN-ID和默认三种VSS类型
- 符合RFC标准
-
函数接口声明(6.2.4):
- 完整的配置管理接口
- 配置查询和遍历接口
- 锁机制接口
-
内联辅助函数(6.2.5):
- 高效的配置查找函数
- 使用索引映射实现快速查找
头文件定义了DHCP Proxy模块的完整接口规范,为后续的源码实现提供了清晰的接口定义。
6.3 dhcp_proxy.c 源码实现分析
dhcp_proxy.c文件实现了DHCP Proxy的核心功能,包括配置管理、VSS配置、配置查询等。
本节详细分析各个函数的实现原理,包括初始化、配置管理、核心逻辑和辅助功能。
所有讲解都基于实际源码,逐行注释,确保读者能够深入理解实现细节。
6.3.1 Proxy初始化
功能说明:
Proxy初始化在VPP插件加载时完成,负责初始化全局管理器、获取错误丢弃节点索引、设置VLIB主线程指针等。初始化分为两个部分:全局变量的静态初始化和模块初始化函数。
6.3.1.1 全局变量初始化
功能说明:
全局变量dhcp_proxy_main在编译时自动初始化为零值,这是C语言的特性。所有指针字段初始化为NULL,整数字段初始化为0。
全局变量定义:
//22:25:src/plugins/dhcp/dhcp_proxy.c
/**
* @brief Shard 4/6 instance of DHCP main
*/
/* 注释说明:DHCP主结构的IPv4/IPv6共享实例。 */
dhcp_proxy_main_t dhcp_proxy_main; /* 全局DHCP Proxy管理器实例 */
/* 说明:dhcp_proxy_main是全局变量,在编译时自动初始化为零值。
*
* 初始化状态:
* - 所有指针字段(dhcp_servers、vss等)初始化为NULL
* - 所有整数字段(udp_ports_registered、error_drop_node_index等)初始化为0
* - 所有向量字段(dhcp_server_index_by_rx_fib_index等)初始化为空向量
*
* 特点:
* - 单例模式,整个系统只有一个实例
* - 在模块初始化时设置必要的字段
* - 在运行时动态分配配置池和索引映射 */
全局变量初始化状态:
| 字段 | 初始值 | 说明 |
|---|---|---|
dhcp_servers[2] | NULL | Proxy配置池指针数组,未分配 |
dhcp_server_index_by_rx_fib_index[2] | 空向量 | FIB索引到Proxy索引的映射向量 |
error_drop_node_index | 0 | 错误丢弃节点索引,在初始化函数中设置 |
vss[2] | NULL | VSS配置池指针数组,未分配 |
vss_index_by_rx_fib_index[2] | 空向量 | FIB索引到VSS索引的映射向量 |
udp_ports_registered | 0 | UDP端口注册标志,未注册任何端口 |
vlib_main | NULL | VLIB主线程指针,在初始化函数中设置 |
6.3.1.2 模块初始化函数
功能说明:
dhcp4_proxy_init()函数在VPP插件加载时被调用,负责初始化DHCPv4 Proxy模块。函数获取错误丢弃节点索引并设置VLIB主线程指针。这是模块初始化的关键步骤。
函数输入:
vm:VLIB主线程指针
函数输出:
- 返回
clib_error_t *类型,成功返回0(NULL),失败返回错误指针
源码详解:
//826:840:src/plugins/dhcp/dhcp4_proxy_node.c
static clib_error_t * /* 返回类型:错误指针,成功返回NULL */
/* 说明:clib_error_t *是VPP的错误类型。
* - 如果返回NULL,表示初始化成功
* - 如果返回非NULL,表示初始化失败,包含错误信息 */
dhcp4_proxy_init (vlib_main_t * vm) /* 函数名:DHCPv4 Proxy初始化函数 */
/* 说明:dhcp4_proxy_init是DHCPv4 Proxy模块的初始化函数。
*
* 调用时机:
* - 在VPP插件加载时自动调用
* - 通过VLIB_INIT_FUNCTION宏注册
* - 在主线程中执行,保证线程安全
*
* 参数:
* - vm:VLIB主线程指针
* - 用于访问VPP核心功能
* - 用于获取节点索引
* - 用于访问缓冲区池等 */
{
dhcp_proxy_main_t *dm = &dhcp_proxy_main; /* 获取全局管理器指针 */
/* 说明:dm变量指向全局的dhcp_proxy_main实例。
*
* 使用方式:
* - 通过指针访问全局管理器
* - 设置管理器的各个字段
* - 后续所有函数都通过这个指针访问全局管理器 */
vlib_node_t *error_drop_node; /* 错误丢弃节点指针 */
/* 说明:error_drop_node变量用于存储错误丢弃节点的指针。
*
* 错误丢弃节点:
* - 是VPP的一个标准节点
* - 用于丢弃报文并更新错误统计
* - 在服务器到客户端方向出现错误时使用 */
error_drop_node = vlib_get_node_by_name (vm, (u8 *) "error-drop"); /* 通过节点名获取节点指针 */
/* 说明:vlib_get_node_by_name函数通过节点名获取节点指针。
*
* 参数1:vm
* - VLIB主线程指针
* - 用于访问节点表
*
* 参数2:(u8 *) "error-drop"
* - 节点名称字符串
* - "error-drop"是VPP的标准错误丢弃节点
* - 转换为u8 *类型(VPP使用的字符串类型)
*
* 返回值:
* - 返回节点指针(vlib_node_t *)
* - 如果节点不存在,可能返回NULL或崩溃(取决于实现)
*
* 节点用途:
* - 在服务器到客户端方向出现错误时
* - 将报文发送到此节点
* - 节点会丢弃报文并更新错误统计计数 */
dm->error_drop_node_index = error_drop_node->index; /* 保存节点索引 */
/* 说明:将错误丢弃节点的索引保存到全局管理器中。
*
* error_drop_node->index:
* - 节点的索引值(u32类型)
* - 用于在数据平面中引用节点
* - 索引是节点的唯一标识符
*
* 保存位置:
* - dm->error_drop_node_index字段
* - 在后续处理中,通过此索引将报文发送到错误丢弃节点
*
* 使用场景:
* - 在dhcp4_proxy_to_client节点中
* - 当检测到错误时(如Cookie验证失败、找不到Proxy配置等)
* - 使用此索引将报文发送到错误丢弃节点 */
dm->vlib_main = vm; /* 保存VLIB主线程指针 */
/* 说明:将VLIB主线程指针保存到全局管理器中。
*
* 保存原因:
* - 后续函数需要访问VLIB主线程
* - 例如:UDP端口注册、节点访问等
* - 避免每次调用都传递vm参数
*
* 使用场景:
* - 在dhcp_maybe_register_udp_ports中
* - 在配置查询和遍历函数中
* - 在其他需要访问VPP核心功能的函数中 */
return 0; /* 返回成功(NULL) */
/* 说明:返回0表示初始化成功。
*
* 返回值:
* - 0(NULL)表示初始化成功
* - 非NULL表示初始化失败,包含错误信息
*
* 初始化完成后的状态:
* - error_drop_node_index已设置
* - vlib_main已设置
* - 其他字段保持初始值(NULL或0)
* - 配置池和索引映射在首次配置时动态分配 */
}
/* 说明:函数执行完成后,DHCPv4 Proxy模块初始化完成。
*
* 初始化完成标志:
* - error_drop_node_index已设置,可以处理错误报文
* - vlib_main已设置,可以访问VPP核心功能
* - 全局管理器已准备好接收配置
*
* 后续操作:
* - 等待用户配置Proxy服务器
* - 在首次配置时,动态分配配置池和索引映射
* - UDP端口在首次配置时注册 */
模块注册:
//840:840:src/plugins/dhcp/dhcp4_proxy_node.c
VLIB_INIT_FUNCTION (dhcp4_proxy_init); /* 注册初始化函数 */
/* 说明:VLIB_INIT_FUNCTION宏用于注册模块初始化函数。
*
* 宏功能:
* - 将dhcp4_proxy_init函数注册到VPP的初始化函数列表
* - 在插件加载时自动调用
* - 按照注册顺序执行初始化
*
* 执行时机:
* - 在VPP启动时,加载DHCP插件后
* - 在主线程中执行
* - 在所有节点注册之前执行
*
* 执行顺序:
* 1. 插件加载
* 2. 初始化函数执行(dhcp4_proxy_init)
* 3. 节点注册
* 4. 插件就绪,可以接收配置 */
初始化流程总结:
DHCP Proxy初始化流程
│
├─→ 1. 编译时初始化
│ └─→ dhcp_proxy_main全局变量自动初始化为零值
│
├─→ 2. 插件加载
│ └─→ VPP加载DHCP插件
│
├─→ 3. 调用初始化函数
│ └─→ VLIB_INIT_FUNCTION调用dhcp4_proxy_init()
│
├─→ 4. 获取错误丢弃节点
│ ├─→ vlib_get_node_by_name("error-drop")
│ └─→ 保存节点索引到dm->error_drop_node_index
│
├─→ 5. 保存VLIB主线程指针
│ └─→ 保存vm到dm->vlib_main
│
└─→ 6. 初始化完成
└─→ 返回0,模块就绪
关键设计特点:
- 延迟分配:配置池和索引映射在首次配置时动态分配,节省内存
- 单例模式:全局只有一个
dhcp_proxy_main实例,简化管理 - 错误处理:初始化失败时返回错误指针,VPP会记录错误信息
- 线程安全:初始化在主线程中执行,保证线程安全
初始化后的状态:
- ✅
error_drop_node_index已设置,可以处理错误报文 - ✅
vlib_main已设置,可以访问VPP核心功能 - ⏳ 配置池未分配,等待首次配置
- ⏳ 索引映射未分配,等待首次配置
- ⏳ UDP端口未注册,等待首次配置
DHCPv6 Proxy初始化:
DHCPv6 Proxy也有类似的初始化函数dhcp6_proxy_init(),实现方式与DHCPv4相同,只是处理IPv6协议。两个初始化函数独立执行,互不影响。
6.3.1节总结:
本节详细讲解了DHCP Proxy的初始化过程,包括:
-
全局变量初始化:
dhcp_proxy_main在编译时自动初始化为零值- 所有字段初始化为NULL或0
-
模块初始化函数:
dhcp4_proxy_init()在插件加载时调用- 获取错误丢弃节点索引
- 保存VLIB主线程指针
-
初始化特点:
- 延迟分配:配置池在首次配置时分配
- 单例模式:全局只有一个管理器实例
- 线程安全:在主线程中执行初始化
初始化完成后,模块已准备好接收配置,但配置池和索引映射需要等到首次配置时才分配。
6.3.2 Proxy配置管理
Proxy配置管理是DHCP Proxy的核心功能,包括添加、删除Proxy配置和VSS配置。
本节详细分析各个配置管理函数的实现原理,包括FIB索引处理、服务器列表管理、资源清理等。
6.3.2.1 添加Proxy配置 - dhcp_proxy_server_add()
功能说明:
dhcp_proxy_server_add()函数用于添加或修改DHCP Proxy服务器配置。如果指定VRF的Proxy配置不存在,则创建新配置;如果已存在,则在现有配置中添加新的服务器。函数支持为同一VRF配置多个服务器。
函数输入:
proto:协议类型(IPv4或IPv6)addr:DHCP服务器地址指针src_address:Proxy使用的源地址指针rx_fib_index:接收FIB索引(客户端所在的FIB索引)server_table_id:服务器Table-ID(外部Table-ID,需要转换为FIB索引)
函数输出:
- 返回
int类型:1表示配置是新的(新创建的Proxy配置),0表示修改了现有配置(在现有Proxy配置中添加了服务器)
源码详解:
//183:228:src/plugins/dhcp/dhcp_proxy.c
int /* 返回类型:整数,1表示新配置,0表示修改现有配置 */
dhcp_proxy_server_add (fib_protocol_t proto, /* 协议类型(IPv4或IPv6) */
/* 说明:proto参数指定协议类型。
*
* 类型:fib_protocol_t枚举
* - FIB_PROTOCOL_IP4:IPv4协议
* - FIB_PROTOCOL_IP6:IPv6协议
*
* 用途:
* - 确定使用哪个协议的配置池
* - IPv4和IPv6使用独立的配置池和索引映射 */
ip46_address_t * addr, /* 服务器地址指针 */
/* 说明:addr参数指定DHCP服务器地址。
*
* 类型:ip46_address_t *(IPv4/IPv6地址联合体指针)
* - 支持IPv4和IPv6地址
* - 地址格式由proto参数确定
*
* 用途:
* - 添加到服务器列表中
* - 用于转发DHCP请求到服务器 */
ip46_address_t * src_address, /* 源地址指针 */
/* 说明:src_address参数指定Proxy使用的源地址。
*
* 类型:ip46_address_t *(IPv4/IPv6地址联合体指针)
* - 支持IPv4和IPv6地址
* - 用于在转发请求时设置源地址
*
* 用途:
* - 设置dhcp_proxy_t的dhcp_src_address字段
* - 服务器回复时使用此地址作为目标地址 */
u32 rx_fib_index, /* 接收FIB索引 */
/* 说明:rx_fib_index参数指定客户端所在的FIB索引。
*
* 类型:u32(32位无符号整数)
* - FIB索引,不是外部Table-ID
* - 用于标识客户端所在的VRF
*
* 用途:
* - 在dhcp_server_index_by_rx_fib_index中建立映射
* - 用于查找Proxy配置
* - 每个VRF可以有独立的Proxy配置 */
u32 server_table_id) /* 服务器Table-ID */
/* 说明:server_table_id参数指定服务器所在的Table-ID。
*
* 类型:u32(32位无符号整数)
* - 外部Table-ID,不是FIB索引
* - 需要转换为FIB索引
*
* 用途:
* - 转换为FIB索引后,设置dhcp_server_t的server_fib_index字段
* - 用于转发请求到服务器时的路由查找 */
{
dhcp_proxy_main_t *dpm = &dhcp_proxy_main; /* 获取全局管理器指针 */
/* 说明:dpm变量指向全局的dhcp_proxy_main实例。
*
* 使用方式:
* - 访问配置池
* - 访问索引映射
* - 修改全局状态 */
dhcp_proxy_t *proxy = 0; /* Proxy配置指针,初始化为NULL */
/* 说明:proxy变量用于存储找到或创建的Proxy配置指针。
*
* 初始值:0(NULL)
* - 如果找到现有配置,指向现有配置
* - 如果未找到,需要创建新配置 */
int new = 0; /* 新配置标志,初始化为0(修改现有配置) */
/* 说明:new变量用于标识配置是否为新创建的。
*
* 值:
* - 0:修改现有配置(在现有Proxy配置中添加服务器)
* - 1:新创建的配置(创建了新的Proxy配置)
*
* 用途:
* - 返回值,告知调用者配置是新创建还是修改现有配置
* - 调用者可以根据返回值决定是否需要额外的初始化操作 */
proxy = dhcp_get_proxy (dpm, rx_fib_index, proto); /* 查找Proxy配置 */
/* 说明:调用内联函数查找指定FIB索引的Proxy配置。
*
* 查找过程:
* 1. 检查索引映射向量长度是否大于rx_fib_index
* 2. 检查索引值是否为~0(未配置)
* 3. 如果已配置,从池中获取Proxy配置指针
*
* 返回值:
* - 如果找到:返回Proxy配置指针
* - 如果未找到:返回NULL
*
* 使用场景:
* - 如果返回NULL,需要创建新配置
* - 如果返回非NULL,在现有配置中添加服务器 */
if (NULL == proxy) /* 如果Proxy配置不存在 */
/* 说明:检查是否找到Proxy配置。
*
* 条件:
* - NULL == proxy:未找到Proxy配置,需要创建新配置
* - 这种情况发生在首次为某个VRF配置Proxy时 */
{
vec_validate_init_empty (dpm->dhcp_server_index_by_rx_fib_index[proto], /* 扩展索引映射向量 */
/* 说明:vec_validate_init_empty函数用于扩展向量并初始化新元素。
*
* 参数1:dpm->dhcp_server_index_by_rx_fib_index[proto]
* - 索引映射向量指针
* - proto确定使用IPv4还是IPv6的映射
*
* 功能:
* - 如果向量长度 <= rx_fib_index,扩展向量到rx_fib_index+1
* - 新扩展的元素初始化为指定的初始值(~0)
* - 如果向量长度 > rx_fib_index,不执行任何操作
*
* 目的:
* - 确保索引映射向量足够大,可以存储rx_fib_index索引
* - 新元素初始化为~0,表示未配置 */
rx_fib_index, /* 目标索引 */
/* 说明:rx_fib_index参数指定要扩展到的索引位置。
*
* 扩展规则:
* - 如果向量长度 <= rx_fib_index,扩展向量到rx_fib_index+1
* - 例如:如果rx_fib_index=5,向量长度为3,则扩展到6
* - 新元素索引为3、4、5,都初始化为~0 */
~0); /* 初始值:~0表示未配置 */
/* 说明:~0是初始值,表示未配置Proxy。
*
* ~0的含义:
* - 所有位都为1(0xFFFFFFFF)
* - 用作"未配置"的标记值
* - 在查找时,如果值为~0,表示未配置
*
* 初始化目的:
* - 新扩展的元素初始化为~0
* - 表示这些VRF尚未配置Proxy
* - 后续配置时,会更新为实际的配置索引 */
pool_get (dpm->dhcp_servers[proto], proxy); /* 从池中分配Proxy配置 */
/* 说明:pool_get函数从内存池中分配一个新的Proxy配置。
*
* 参数1:dpm->dhcp_servers[proto]
* - Proxy配置池指针
* - proto确定使用IPv4还是IPv6的配置池
*
* 参数2:proxy(输出参数)
* - 用于接收分配的配置指针
* - 分配后,proxy指向新分配的配置
*
* 功能:
* - 从池中分配一个dhcp_proxy_t结构体
* - 如果池为空,自动扩展池
* - 返回指向新分配配置的指针
*
* 分配后的状态:
* - 配置结构体的内容是未初始化的(随机值)
* - 需要手动清零和初始化 */
clib_memset (proxy, 0, sizeof (*proxy)); /* 清零Proxy配置 */
/* 说明:clib_memset函数将Proxy配置结构体清零。
*
* 参数1:proxy
* - 要清零的内存区域指针
*
* 参数2:0
* - 填充值(字节值)
*
* 参数3:sizeof (*proxy)
* - 要清零的字节数(Proxy配置结构体的大小)
*
* 功能:
* - 将proxy指向的结构体所有字节设置为0
* - 确保所有字段初始化为0或NULL
*
* 清零后的状态:
* - dhcp_servers = NULL(空向量)
* - dhcp_pending = NULL(空哈希表)
* - lock = 0(锁未锁定)
* - dhcp_src_address = 0(地址为零)
* - rx_fib_index = 0(未设置) */
new = 1; /* 标记为新配置 */
/* 说明:设置new标志为1,表示这是新创建的配置。
*
* 用途:
* - 返回值,告知调用者配置是新创建的
* - 调用者可以根据此标志决定是否需要额外的初始化操作
* - 例如:注册UDP端口、添加特殊路由等 */
dpm->dhcp_server_index_by_rx_fib_index[proto][rx_fib_index] = /* 建立索引映射 */
/* 说明:将Proxy配置在池中的索引保存到索引映射中。
*
* 索引计算:
* - proxy - dpm->dhcp_servers[proto]
* - 计算proxy指针与池起始地址的偏移
* - 偏移除以元素大小,得到索引
*
* 例如:
* - 如果池起始地址为0x1000,proxy地址为0x1050
* - 元素大小为sizeof(dhcp_proxy_t),假设为64字节
* - 索引 = (0x1050 - 0x1000) / 64 = 1
*
* 保存位置:
* - dpm->dhcp_server_index_by_rx_fib_index[proto][rx_fib_index]
* - 使用rx_fib_index作为数组索引
* - 存储的值是Proxy配置在池中的索引 */
proxy - dpm->dhcp_servers[proto]; /* 计算配置在池中的索引 */
/* 说明:计算Proxy配置在池中的索引。
*
* 计算方法:
* - 指针相减:proxy - dpm->dhcp_servers[proto]
* - 结果是指针之间的元素个数(不是字节数)
* - C语言自动处理指针算术,除以元素大小
*
* 索引用途:
* - 用于从池中获取配置:pool_elt_at_index(pool, index)
* - 用于标识配置在池中的位置
* - 用于在删除配置时归还到池中 */
proxy->dhcp_src_address = *src_address; /* 设置源地址 */
/* 说明:将源地址复制到Proxy配置中。
*
* 复制方式:
* - *src_address:解引用指针,获取地址值
* - proxy->dhcp_src_address:结构体字段
* - 结构体赋值,复制所有字段
*
* 源地址用途:
* - 在转发请求到服务器时,使用此地址作为IP源地址
* - 服务器回复时,使用此地址作为IP目标地址
* - 确保服务器回复能够正确路由回Proxy */
proxy->rx_fib_index = rx_fib_index; /* 设置接收FIB索引 */
/* 说明:将接收FIB索引保存到Proxy配置中。
*
* 保存目的:
* - 标识此Proxy配置对应的VRF
* - 用于后续查找和验证
* - 用于日志和调试
*
* 使用场景:
* - 在转发请求时,验证FIB索引是否匹配
* - 在删除配置时,验证FIB索引是否匹配 */
}
else /* 如果Proxy配置已存在 */
/* 说明:检查是否找到现有Proxy配置。
*
* 条件:
* - proxy != NULL:找到现有Proxy配置
* - 这种情况发生在为已配置的VRF添加新服务器时 */
{
if (~0 != dhcp_proxy_server_find (proxy, proto, addr, server_table_id)) /* 检查服务器是否已存在 */
/* 说明:调用dhcp_proxy_server_find函数检查服务器是否已存在。
*
* 函数功能:
* - 在proxy->dhcp_servers向量中查找指定的服务器
* - 比较服务器地址和FIB索引
*
* 返回值:
* - 如果找到:返回服务器在向量中的索引(>= 0)
* - 如果未找到:返回~0
*
* 检查目的:
* - 避免重复添加相同的服务器
* - 如果服务器已存在,直接返回,不执行任何操作 */
{
return (new); /* 服务器已存在,直接返回 */
/* 说明:如果服务器已存在,直接返回new标志。
*
* 返回值:
* - new = 0(因为这是修改现有配置,不是新创建)
* - 表示未执行任何操作(服务器已存在)
*
* 使用场景:
* - 用户重复添加相同的服务器配置
* - 函数幂等性:多次调用相同参数,结果相同 */
}
}
dhcp_server_t server = { /* 创建服务器结构体 */
/* 说明:创建新的服务器结构体,使用初始化列表。
*
* 结构体初始化:
* - 使用C99的指定初始化器语法
* - 只初始化指定的字段
* - 其他字段自动初始化为0 */
.dhcp_server = *addr, /* 服务器地址 */
/* 说明:设置服务器地址字段。
*
* 复制方式:
* - *addr:解引用指针,获取地址值
* - .dhcp_server:结构体字段
* - 结构体赋值,复制所有字段(IPv4和IPv6地址) */
.server_fib_index = fib_table_find_or_create_and_lock (proto, /* 服务器FIB索引 */
/* 说明:调用fib_table_find_or_create_and_lock函数查找或创建FIB表。
*
* 函数功能:
* - 根据Table-ID查找对应的FIB索引
* - 如果FIB表不存在,创建新的FIB表
* - 锁定FIB表,防止被删除
*
* 参数1:proto
* - 协议类型(IPv4或IPv6)
*
* 参数2:server_table_id
* - 外部Table-ID
* - 需要转换为FIB索引
*
* 参数3:FIB_SOURCE_DHCP
* - FIB来源标识符
* - 用于标识是DHCP模块创建的FIB表
* - 在删除配置时,用于释放FIB表锁
*
* 返回值:
* - FIB索引(u32类型)
* - 用于路由查找和报文转发 */
server_table_id, /* 服务器Table-ID */
FIB_SOURCE_DHCP), /* FIB来源 */
/* 说明:FIB_SOURCE_DHCP是FIB来源标识符。
*
* 用途:
* - 标识FIB表的创建者
* - 在删除配置时,用于释放FIB表锁
* - 用于FIB表的管理和统计 */
};
vec_add1 (proxy->dhcp_servers, server); /* 将服务器添加到列表 */
/* 说明:vec_add1函数将服务器添加到服务器列表向量的末尾。
*
* 参数1:proxy->dhcp_servers
* - 服务器列表向量指针
* - 如果向量为NULL,自动创建新向量
*
* 参数2:server
* - 要添加的服务器结构体
* - 通过值传递,复制结构体内容
*
* 功能:
* - 在向量末尾添加一个新元素
* - 如果向量容量不足,自动扩展
* - 复制server结构体到新元素
*
* 添加后的状态:
* - 服务器列表包含新添加的服务器
* - 向量长度增加1
* - 新服务器在列表末尾 */
return (new); /* 返回新配置标志 */
/* 说明:返回new标志,告知调用者配置是否为新创建的。
*
* 返回值:
* - 1:新创建的Proxy配置
* - 0:修改了现有配置(添加了服务器)
*
* 调用者用途:
* - 如果返回1,可能需要额外的初始化操作
* - 例如:注册UDP端口、添加特殊路由等
* - 如果返回0,只需要更新服务器列表 */
}
函数流程总结:
dhcp_proxy_server_add() 函数流程
│
├─→ 1. 初始化变量
│ ├─→ dpm = &dhcp_proxy_main
│ ├─→ proxy = NULL
│ └─→ new = 0
│
├─→ 2. 查找Proxy配置
│ └─→ proxy = dhcp_get_proxy(dpm, rx_fib_index, proto)
│
├─→ 3. 如果Proxy配置不存在(proxy == NULL)
│ ├─→ 扩展索引映射向量
│ │ └─→ vec_validate_init_empty(..., rx_fib_index, ~0)
│ ├─→ 从池中分配Proxy配置
│ │ └─→ pool_get(dpm->dhcp_servers[proto], proxy)
│ ├─→ 清零Proxy配置
│ │ └─→ clib_memset(proxy, 0, sizeof(*proxy))
│ ├─→ 设置新配置标志
│ │ └─→ new = 1
│ ├─→ 建立索引映射
│ │ └─→ dpm->dhcp_server_index_by_rx_fib_index[proto][rx_fib_index] = index
│ ├─→ 设置源地址
│ │ └─→ proxy->dhcp_src_address = *src_address
│ └─→ 设置接收FIB索引
│ └─→ proxy->rx_fib_index = rx_fib_index
│
├─→ 4. 如果Proxy配置已存在(proxy != NULL)
│ └─→ 检查服务器是否已存在
│ └─→ if (dhcp_proxy_server_find(...) != ~0)
│ └─→ return new(服务器已存在,直接返回)
│
├─→ 5. 创建服务器结构体
│ ├─→ server.dhcp_server = *addr
│ └─→ server.server_fib_index = fib_table_find_or_create_and_lock(...)
│
├─→ 6. 添加服务器到列表
│ └─→ vec_add1(proxy->dhcp_servers, server)
│
└─→ 7. 返回新配置标志
└─→ return new
关键设计特点:
- 幂等性:多次添加相同的服务器配置,结果相同,不会重复添加
- 延迟分配:索引映射向量在首次配置时动态扩展,节省内存
- 多服务器支持:支持为同一VRF配置多个服务器,使用向量存储
- FIB表管理:自动查找或创建FIB表,并锁定防止被删除
使用示例:
// 为VRF 0添加DHCP服务器
ip46_address_t server_addr = { .ip4.as_u32 = 0x0a000001 }; // 10.0.0.1
ip46_address_t src_addr = { .ip4.as_u32 = 0x0a0000fe }; // 10.0.0.254
u32 rx_fib_index = 0; // VRF 0的FIB索引
u32 server_table_id = 0; // 服务器在VRF 0中
int is_new = dhcp_proxy_server_add(FIB_PROTOCOL_IP4,
&server_addr,
&src_addr,
rx_fib_index,
server_table_id);
if (is_new) {
// 新创建的Proxy配置,可能需要额外的初始化
}
6.3.2.2 删除Proxy配置 - dhcp_proxy_server_del()
功能说明:
dhcp_proxy_server_del()函数用于删除DHCP Proxy服务器配置。如果删除后服务器列表为空,则删除整个Proxy配置;如果还有其他服务器,则只删除指定的服务器。函数会自动释放FIB表锁和内存资源。
函数输入:
proto:协议类型(IPv4或IPv6)rx_fib_index:接收FIB索引(客户端所在的FIB索引)addr:要删除的DHCP服务器地址指针server_table_id:服务器Table-ID(用于匹配服务器)
函数输出:
- 返回
int类型:1表示Proxy配置被删除(服务器列表为空),0表示仅删除了服务器(Proxy配置仍存在)
辅助函数:dhcp_proxy_server_find()
在讲解删除函数之前,先了解用于查找服务器的辅助函数:
//120:141:src/plugins/dhcp/dhcp_proxy.c
static u32 /* 返回类型:服务器在向量中的索引,未找到返回~0 */
dhcp_proxy_server_find (dhcp_proxy_t * proxy, /* Proxy配置指针 */
/* 说明:proxy参数指定要查找的Proxy配置。
*
* 用途:
* - 在proxy->dhcp_servers向量中查找指定的服务器
* - 比较服务器地址和FIB索引 */
fib_protocol_t proto, /* 协议类型 */
/* 说明:proto参数指定协议类型,用于FIB表查找。 */
ip46_address_t * addr, /* 服务器地址指针 */
/* 说明:addr参数指定要查找的服务器地址。 */
u32 server_table_id) /* 服务器Table-ID */
/* 说明:server_table_id参数指定服务器的Table-ID,用于匹配FIB索引。 */
{
dhcp_server_t *server; /* 服务器指针 */
/* 说明:server变量用于存储当前遍历到的服务器指针。 */
u32 ii, fib_index; /* 循环索引和FIB索引 */
/* 说明:ii变量用于循环遍历服务器列表。
* fib_index变量用于存储查找的FIB索引。 */
vec_foreach_index (ii, proxy->dhcp_servers) /* 遍历服务器列表 */
/* 说明:vec_foreach_index宏用于遍历向量的所有索引。
*
* 语法:
* - vec_foreach_index(index, vector)
* - 对向量的每个索引执行循环体
*
* 遍历过程:
* - ii从0开始,递增到vec_len(proxy->dhcp_servers)-1
* - 每次循环,ii是当前元素的索引 */
{
server = &proxy->dhcp_servers[ii]; /* 获取当前服务器指针 */
/* 说明:通过索引获取服务器结构体的指针。
*
* 获取方式:
* - &proxy->dhcp_servers[ii]:取地址操作
* - 获取向量中第ii个元素的地址
*
* 使用方式:
* - 通过指针访问服务器结构体的字段
* - 比较服务器地址和FIB索引 */
fib_index = fib_table_find (proto, server_table_id); /* 查找FIB索引 */
/* 说明:调用fib_table_find函数根据Table-ID查找FIB索引。
*
* 函数功能:
* - 根据Table-ID查找对应的FIB索引
* - 如果FIB表不存在,返回~0
*
* 参数1:proto
* - 协议类型(IPv4或IPv6)
*
* 参数2:server_table_id
* - 外部Table-ID
*
* 返回值:
* - FIB索引(u32类型)
* - 如果FIB表不存在,返回~0 */
if (ip46_address_is_equal (&server->dhcp_server, /* 比较服务器地址 */
/* 说明:调用ip46_address_is_equal函数比较服务器地址。
*
* 函数功能:
* - 比较两个IPv4/IPv6地址是否相等
* - 支持IPv4和IPv6地址比较
*
* 参数1:&server->dhcp_server
* - 当前服务器的地址
*
* 参数2:addr
* - 要查找的服务器地址
*
* 返回值:
* - 1:地址相等
* - 0:地址不相等 */
addr) &&
/* 说明:逻辑与操作,两个条件都必须满足。
*
* 条件1:地址相等
* - ip46_address_is_equal返回1
*
* 条件2:FIB索引相等(在下一行检查) */
(server->server_fib_index == fib_index)) /* 比较FIB索引 */
/* 说明:比较服务器的FIB索引是否匹配。
*
* 比较方式:
* - server->server_fib_index:当前服务器的FIB索引
* - fib_index:根据Table-ID查找的FIB索引
*
* 匹配条件:
* - 地址相等 AND FIB索引相等
* - 两个条件都满足,表示找到匹配的服务器 */
{
return (ii); /* 返回服务器在向量中的索引 */
/* 说明:如果找到匹配的服务器,返回其在向量中的索引。
*
* 返回值:
* - ii:服务器在proxy->dhcp_servers向量中的索引(>= 0)
* - 调用者可以使用此索引删除服务器
*
* 使用场景:
* - 在删除服务器时,使用此索引定位服务器
* - 在修改服务器配置时,使用此索引访问服务器 */
}
}
return (~0); /* 未找到,返回~0 */
/* 说明:如果遍历完所有服务器都未找到匹配的,返回~0。
*
* ~0的含义:
* - 所有位都为1(0xFFFFFFFF)
* - 用作"未找到"的标记值
*
* 返回值检查:
* - 调用者需要检查返回值是否为~0
* - 如果为~0,表示未找到匹配的服务器 */
}
删除函数源码详解:
//143:181:src/plugins/dhcp/dhcp_proxy.c
int /* 返回类型:整数,1表示Proxy配置被删除,0表示仅删除服务器 */
dhcp_proxy_server_del (fib_protocol_t proto, /* 协议类型(IPv4或IPv6) */
/* 说明:proto参数指定协议类型,用于确定使用哪个协议的配置池。 */
u32 rx_fib_index, /* 接收FIB索引 */
/* 说明:rx_fib_index参数指定要删除的Proxy配置的FIB索引。
*
* 用途:
* - 用于查找Proxy配置
* - 用于删除索引映射 */
ip46_address_t * addr, /* 服务器地址指针 */
/* 说明:addr参数指定要删除的服务器地址。
*
* 用途:
* - 用于在服务器列表中查找匹配的服务器
* - 与server_table_id一起用于匹配服务器 */
u32 server_table_id) /* 服务器Table-ID */
/* 说明:server_table_id参数指定服务器的Table-ID,用于匹配服务器。
*
* 用途:
* - 转换为FIB索引后,与服务器结构体中的FIB索引比较
* - 用于精确匹配服务器 */
{
dhcp_proxy_main_t *dpm = &dhcp_proxy_main; /* 获取全局管理器指针 */
/* 说明:dpm变量指向全局的dhcp_proxy_main实例。
*
* 使用方式:
* - 访问配置池
* - 访问索引映射
* - 修改全局状态 */
dhcp_proxy_t *proxy = 0; /* Proxy配置指针,初始化为NULL */
/* 说明:proxy变量用于存储找到的Proxy配置指针。
*
* 初始值:0(NULL)
* - 如果找到Proxy配置,指向配置
* - 如果未找到,保持NULL */
proxy = dhcp_get_proxy (dpm, rx_fib_index, proto); /* 查找Proxy配置 */
/* 说明:调用内联函数查找指定FIB索引的Proxy配置。
*
* 查找过程:
* 1. 检查索引映射向量长度是否大于rx_fib_index
* 2. 检查索引值是否为~0(未配置)
* 3. 如果已配置,从池中获取Proxy配置指针
*
* 返回值:
* - 如果找到:返回Proxy配置指针
* - 如果未找到:返回NULL */
if (NULL != proxy) /* 如果Proxy配置存在 */
/* 说明:检查是否找到Proxy配置。
*
* 条件:
* - NULL != proxy:找到Proxy配置
* - 如果为NULL,表示该VRF未配置Proxy,直接返回0 */
{
dhcp_server_t *server; /* 服务器指针 */
/* 说明:server变量用于存储要删除的服务器指针。 */
u32 index; /* 服务器在向量中的索引 */
/* 说明:index变量用于存储服务器在向量中的索引。
*
* 用途:
* - 用于定位要删除的服务器
* - 用于从向量中删除服务器 */
index = dhcp_proxy_server_find (proxy, proto, addr, server_table_id); /* 查找服务器 */
/* 说明:调用辅助函数在服务器列表中查找指定的服务器。
*
* 查找过程:
* - 遍历proxy->dhcp_servers向量
* - 比较服务器地址和FIB索引
* - 如果找到匹配的服务器,返回其在向量中的索引
*
* 返回值:
* - 如果找到:返回服务器索引(>= 0)
* - 如果未找到:返回~0 */
if (~0 != index) /* 如果找到服务器 */
/* 说明:检查是否找到匹配的服务器。
*
* 条件:
* - ~0 != index:找到匹配的服务器
* - 如果为~0,表示服务器不存在,直接返回0 */
{
server = &proxy->dhcp_servers[index]; /* 获取服务器指针 */
/* 说明:通过索引获取服务器结构体的指针。
*
* 获取方式:
* - &proxy->dhcp_servers[index]:取地址操作
* - 获取向量中第index个元素的地址
*
* 使用方式:
* - 通过指针访问服务器结构体的字段
* - 用于释放FIB表锁 */
fib_table_unlock (server->server_fib_index, proto, FIB_SOURCE_DHCP); /* 释放FIB表锁 */
/* 说明:调用fib_table_unlock函数释放FIB表锁。
*
* 函数功能:
* - 释放之前通过fib_table_find_or_create_and_lock获取的FIB表锁
* - 允许FIB表被删除(如果没有其他引用)
*
* 参数1:server->server_fib_index
* - 服务器的FIB索引
* - 在添加服务器时通过fib_table_find_or_create_and_lock获取
*
* 参数2:proto
* - 协议类型(IPv4或IPv6)
*
* 参数3:FIB_SOURCE_DHCP
* - FIB来源标识符
* - 必须与添加时使用的来源标识符一致
*
* 释放锁的目的:
* - 允许FIB表被删除
* - 释放FIB表资源
* - 防止资源泄漏 */
vec_del1 (proxy->dhcp_servers, index); /* 从向量中删除服务器 */
/* 说明:vec_del1函数从向量中删除指定索引的元素。
*
* 参数1:proxy->dhcp_servers
* - 服务器列表向量指针
*
* 参数2:index
* - 要删除的元素的索引
*
* 功能:
* - 删除向量中第index个元素
* - 将后面的元素前移,填补空缺
* - 向量长度减1
*
* 删除后的状态:
* - 服务器列表不再包含被删除的服务器
* - 向量长度减少1
* - 其他服务器的索引可能发生变化(如果删除的不是最后一个元素) */
if (0 == vec_len (proxy->dhcp_servers)) /* 如果服务器列表为空 */
/* 说明:检查服务器列表是否为空。
*
* vec_len函数:
* - 返回向量的长度(元素个数)
*
* 条件:
* - 0 == vec_len:向量为空,没有服务器了
* - 这种情况需要删除整个Proxy配置 */
{
/* no servers left, delete the proxy config */
/* 注释说明:没有服务器了,删除Proxy配置。 */
dpm->dhcp_server_index_by_rx_fib_index[proto][rx_fib_index] = /* 清除索引映射 */
/* 说明:将索引映射设置为~0,表示未配置Proxy。
*
* 清除方式:
* - 设置为~0(所有位都为1)
* - 表示该VRF未配置Proxy
*
* 清除目的:
* - 后续查找时,dhcp_get_proxy会返回NULL
* - 表示Proxy配置已被删除 */
~0; /* 设置为~0,表示未配置 */
/* 说明:~0是"未配置"的标记值。
*
* 使用场景:
* - 在查找Proxy配置时,如果索引值为~0,返回NULL
* - 在遍历配置时,如果索引值为~0,跳过该配置 */
vec_free (proxy->dhcp_servers); /* 释放服务器列表向量 */
/* 说明:vec_free函数释放向量的内存。
*
* 释放对象:
* - proxy->dhcp_servers向量
* - 向量中存储的所有服务器结构体
*
* 释放目的:
* - 回收内存资源
* - 防止内存泄漏
*
* 注意:
* - 向量必须是通过vec_*函数分配的
* - 释放后,向量指针变为无效,不应再使用 */
pool_put (dpm->dhcp_servers[proto], proxy); /* 将Proxy配置归还到池中 */
/* 说明:pool_put函数将Proxy配置归还到内存池中。
*
* 参数1:dpm->dhcp_servers[proto]
* - Proxy配置池指针
* - proto确定使用IPv4还是IPv6的配置池
*
* 参数2:proxy
* - 要归还的Proxy配置指针
* - 必须是通过pool_get分配的
*
* 功能:
* - 将配置结构体归还到池中
* - 池可以重用此内存,分配给新的配置
* - 提高内存使用效率
*
* 归还后的状态:
* - Proxy配置结构体的内容变为未定义
* - 不应再访问proxy指针
* - 内存可以被重用 */
return (1); /* 返回1,表示Proxy配置被删除 */
/* 说明:返回1表示Proxy配置被完全删除。
*
* 返回值含义:
* - 1:Proxy配置被删除(服务器列表为空)
* - 调用者可以根据此返回值决定是否需要额外的清理操作
* - 例如:注销UDP端口、删除特殊路由等 */
}
}
}
/* the proxy still exists */
/* 注释说明:Proxy配置仍然存在。 */
return (0); /* 返回0,表示仅删除了服务器 */
/* 说明:返回0表示仅删除了服务器,Proxy配置仍然存在。
*
* 返回值含义:
* - 0:仅删除了服务器(Proxy配置仍存在,还有其他服务器)
* - 或者:未找到Proxy配置或服务器
*
* 使用场景:
* - 调用者可以根据此返回值决定是否需要额外的清理操作
* - 如果还有其他服务器,不需要清理Proxy配置 */
}
函数流程总结:
dhcp_proxy_server_del() 函数流程
│
├─→ 1. 初始化变量
│ ├─→ dpm = &dhcp_proxy_main
│ └─→ proxy = NULL
│
├─→ 2. 查找Proxy配置
│ └─→ proxy = dhcp_get_proxy(dpm, rx_fib_index, proto)
│
├─→ 3. 如果Proxy配置不存在(proxy == NULL)
│ └─→ return 0(未找到配置)
│
├─→ 4. 如果Proxy配置存在(proxy != NULL)
│ ├─→ 查找服务器
│ │ └─→ index = dhcp_proxy_server_find(...)
│ │
│ ├─→ 如果服务器不存在(index == ~0)
│ │ └─→ return 0(未找到服务器)
│ │
│ └─→ 如果服务器存在(index != ~0)
│ ├─→ 释放FIB表锁
│ │ └─→ fib_table_unlock(...)
│ ├─→ 从向量中删除服务器
│ │ └─→ vec_del1(proxy->dhcp_servers, index)
│ │
│ └─→ 如果服务器列表为空(vec_len == 0)
│ ├─→ 清除索引映射
│ │ └─→ dpm->dhcp_server_index_by_rx_fib_index[proto][rx_fib_index] = ~0
│ ├─→ 释放服务器列表向量
│ │ └─→ vec_free(proxy->dhcp_servers)
│ ├─→ 归还Proxy配置到池中
│ │ └─→ pool_put(dpm->dhcp_servers[proto], proxy)
│ └─→ return 1(Proxy配置被删除)
│
└─→ 5. 返回0(Proxy配置仍存在)
└─→ return 0
关键设计特点:
- 资源清理:自动释放FIB表锁和内存资源,防止资源泄漏
- 条件删除:只有在服务器列表为空时才删除整个Proxy配置
- 幂等性:多次删除不存在的服务器,结果相同,不会出错
- 内存管理:正确释放向量和池内存,避免内存泄漏
使用示例:
// 删除VRF 0的DHCP服务器
ip46_address_t server_addr = { .ip4.as_u32 = 0x0a000001 }; // 10.0.0.1
u32 rx_fib_index = 0; // VRF 0的FIB索引
u32 server_table_id = 0; // 服务器在VRF 0中
int proxy_deleted = dhcp_proxy_server_del(FIB_PROTOCOL_IP4,
rx_fib_index,
&server_addr,
server_table_id);
if (proxy_deleted) {
// Proxy配置被完全删除,可能需要额外的清理操作
// 例如:注销UDP端口、删除特殊路由等
}
6.3.2.3 VSS配置 - dhcp_proxy_set_vss()
功能说明:
dhcp_proxy_set_vss()函数用于配置或删除VSS(Virtual Sub-net Selection)信息。VSS是DHCP Option 82的一个子选项,用于在多租户环境中标识VPN。函数支持ASCII标识符、VPN-ID和默认VPN三种类型。
函数输入:
proto:协议类型(IPv4或IPv6)tbl_id:Table-ID(需要转换为FIB索引)vss_type:VSS类型(ASCII、VPN-ID或默认)vpn_ascii_id:ASCII VPN标识符指针(仅在vss_type=ASCII时使用)oui:OUI(组织唯一标识符,仅在vss_type=VPN_ID时使用)vpn_index:VPN索引(仅在vss_type=VPN_ID时使用)is_del:删除标志(1=删除,0=添加或修改)
函数输出:
- 返回
int类型:0表示成功,非0表示失败(错误代码)
辅助函数:update_vss()
在讲解VSS配置函数之前,先了解用于更新VSS信息的辅助函数:
//281:306:src/plugins/dhcp/dhcp_proxy.c
void /* 返回类型:无返回值 */
update_vss (dhcp_vss_t * v, /* VSS配置指针 */
/* 说明:v参数指定要更新的VSS配置。
*
* 用途:
* - 更新VSS类型和标识符
* - 释放旧的标识符内存(如果存在) */
u8 vss_type, /* VSS类型 */
/* 说明:vss_type参数指定VSS类型。
* - VSS_TYPE_ASCII:ASCII VPN标识符
* - VSS_TYPE_VPN_ID:VPN-ID
* - VSS_TYPE_DEFAULT:默认VPN */
u8 * vpn_ascii_id, /* ASCII VPN标识符指针 */
/* 说明:vpn_ascii_id参数指定ASCII VPN标识符字符串。
* - 仅在vss_type = VSS_TYPE_ASCII时使用
* - 可以为NULL(如果使用VPN-ID或默认类型) */
u32 oui, /* OUI(组织唯一标识符) */
/* 说明:oui参数指定VPN-ID的前3字节OUI。
* - 仅在vss_type = VSS_TYPE_VPN_ID时使用
* - 24位值,存储在vpn_id[0-2]中 */
u32 vpn_index) /* VPN索引 */
/* 说明:vpn_index参数指定VPN-ID的后4字节VPN索引。
* - 仅在vss_type = VSS_TYPE_VPN_ID时使用
* - 32位值,存储在vpn_id[3-6]中 */
{
v->vss_type = vss_type; /* 设置VSS类型 */
/* 说明:将VSS类型保存到配置中。
*
* 保存目的:
* - 标识VSS配置的类型
* - 用于后续处理时确定使用哪个标识符字段 */
if (v->vpn_ascii_id) /* 如果存在旧的ASCII标识符 */
/* 说明:检查是否存在旧的ASCII标识符。
*
* 检查目的:
* - 如果存在旧的标识符,需要先释放内存
* - 防止内存泄漏 */
{
if (v->vpn_ascii_id == (u8 *) ~ 0) /* 检查是否为特殊标记值 */
/* 说明:检查ASCII标识符是否为特殊标记值。
*
* 特殊标记值:
* - (u8 *) ~ 0:所有位都为1的指针值
* - 用作"未初始化"的标记
*
* 检查目的:
* - 区分"未初始化"和"已分配内存"
* - 如果为特殊标记值,只需要清零,不需要释放 */
v->vpn_ascii_id = 0; /* 清零特殊标记值 */
/* 说明:将特殊标记值清零。
*
* 清零目的:
* - 清除特殊标记值
* - 设置为NULL,表示未使用 */
else
vec_free (v->vpn_ascii_id); /* 释放旧的ASCII标识符内存 */
/* 说明:vec_free函数释放ASCII标识符向量的内存。
*
* 释放对象:
* - v->vpn_ascii_id向量
* - 向量中存储的字符串数据
*
* 释放目的:
* - 回收内存资源
* - 防止内存泄漏
*
* 注意:
* - 向量必须是通过vec_*函数分配的
* - 释放后,向量指针变为无效,不应再使用 */
}
if (vss_type == VSS_TYPE_ASCII) /* 如果VSS类型为ASCII */
/* 说明:检查VSS类型是否为ASCII。
*
* ASCII类型:
* - 使用ASCII字符串作为VPN标识符
* - 存储在vpn_ascii_id字段中 */
v->vpn_ascii_id = vpn_ascii_id; /* 设置ASCII标识符 */
/* 说明:将ASCII标识符指针保存到配置中。
*
* 保存方式:
* - 直接保存指针,不复制内容
* - 调用者负责管理字符串内存
*
* 使用场景:
* - 在转发请求时,使用此标识符插入VSS选项 */
else if (vss_type == VSS_TYPE_VPN_ID) /* 如果VSS类型为VPN-ID */
/* 说明:检查VSS类型是否为VPN-ID。
*
* VPN-ID类型:
* - 使用RFC 2685定义的VPN-ID格式
* - 7字节:3字节OUI + 4字节VPN索引
* - 存储在vpn_id[7]数组中 */
{
v->vpn_id[0] = (oui >> 16) & 0xff; /* OUI字节0(最高字节) */
/* 说明:提取OUI的最高字节(第23-16位)。
*
* 提取方式:
* - oui >> 16:右移16位,将最高字节移到最低8位
* - & 0xff:掩码操作,只保留最低8位
*
* 存储位置:
* - vpn_id[0]:VPN-ID的第0字节 */
v->vpn_id[1] = (oui >> 8) & 0xff; /* OUI字节1(中间字节) */
/* 说明:提取OUI的中间字节(第15-8位)。
*
* 提取方式:
* - oui >> 8:右移8位,将中间字节移到最低8位
* - & 0xff:掩码操作,只保留最低8位
*
* 存储位置:
* - vpn_id[1]:VPN-ID的第1字节 */
v->vpn_id[2] = (oui >> 0) & 0xff; /* OUI字节2(最低字节) */
/* 说明:提取OUI的最低字节(第7-0位)。
*
* 提取方式:
* - oui >> 0:不移位(可以省略,但为了清晰保留)
* - & 0xff:掩码操作,只保留最低8位
*
* 存储位置:
* - vpn_id[2]:VPN-ID的第2字节 */
v->vpn_id[3] = (vpn_index >> 24) & 0xff; /* VPN索引字节0(最高字节) */
/* 说明:提取VPN索引的最高字节(第31-24位)。
*
* 提取方式:
* - vpn_index >> 24:右移24位,将最高字节移到最低8位
* - & 0xff:掩码操作,只保留最低8位
*
* 存储位置:
* - vpn_id[3]:VPN-ID的第3字节 */
v->vpn_id[4] = (vpn_index >> 16) & 0xff; /* VPN索引字节1 */
/* 说明:提取VPN索引的第2字节(第23-16位)。
*
* 存储位置:
* - vpn_id[4]:VPN-ID的第4字节 */
v->vpn_id[5] = (vpn_index >> 8) & 0xff; /* VPN索引字节2 */
/* 说明:提取VPN索引的第3字节(第15-8位)。
*
* 存储位置:
* - vpn_id[5]:VPN-ID的第5字节 */
v->vpn_id[6] = (vpn_index >> 0) & 0xff; /* VPN索引字节3(最低字节) */
/* 说明:提取VPN索引的最低字节(第7-0位)。
*
* 存储位置:
* - vpn_id[6]:VPN-ID的第6字节
*
* VPN-ID格式:
* - 总共7字节:vpn_id[0-6]
* - 前3字节:OUI(vpn_id[0-2])
* - 后4字节:VPN索引(vpn_id[3-6]) */
}
}
VSS配置函数源码详解:
//308:367:src/plugins/dhcp/dhcp_proxy.c
int /* 返回类型:整数,0表示成功,非0表示失败 */
dhcp_proxy_set_vss (fib_protocol_t proto, /* 协议类型(IPv4或IPv6) */
/* 说明:proto参数指定协议类型,用于确定使用哪个协议的配置池。 */
u32 tbl_id, /* Table-ID */
/* 说明:tbl_id参数指定VRF的Table-ID,需要转换为FIB索引。
*
* 用途:
* - 转换为FIB索引后,用于查找或创建VSS配置
* - 用于建立索引映射 */
u8 vss_type, /* VSS类型 */
/* 说明:vss_type参数指定VSS类型。
* - VSS_TYPE_ASCII:ASCII VPN标识符
* - VSS_TYPE_VPN_ID:VPN-ID
* - VSS_TYPE_DEFAULT:默认VPN */
u8 * vpn_ascii_id, /* ASCII VPN标识符指针 */
/* 说明:vpn_ascii_id参数指定ASCII VPN标识符字符串。
* - 仅在vss_type = VSS_TYPE_ASCII时使用
* - 可以为NULL(如果使用VPN-ID或默认类型) */
u32 oui, /* OUI(组织唯一标识符) */
/* 说明:oui参数指定VPN-ID的前3字节OUI。
* - 仅在vss_type = VSS_TYPE_VPN_ID时使用
* - 24位值,存储在vpn_id[0-2]中 */
u32 vpn_index, /* VPN索引 */
/* 说明:vpn_index参数指定VPN-ID的后4字节VPN索引。
* - 仅在vss_type = VSS_TYPE_VPN_ID时使用
* - 32位值,存储在vpn_id[3-6]中 */
u8 is_del) /* 删除标志 */
/* 说明:is_del参数指定是删除还是添加VSS配置。
* - 1:删除VSS配置
* - 0:添加或修改VSS配置 */
{
dhcp_proxy_main_t *dm = &dhcp_proxy_main; /* 获取全局管理器指针 */
/* 说明:dm变量指向全局的dhcp_proxy_main实例。 */
dhcp_vss_t *v = NULL; /* VSS配置指针,初始化为NULL */
/* 说明:v变量用于存储找到或创建的VSS配置指针。 */
u32 rx_fib_index; /* 接收FIB索引 */
/* 说明:rx_fib_index变量用于存储转换后的FIB索引。 */
int rc = 0; /* 返回值,初始化为0(成功) */
/* 说明:rc变量用于存储函数返回值。
*
* 初始值:0(成功)
* - 如果操作成功,保持0
* - 如果操作失败,设置为错误代码 */
if (proto == FIB_PROTOCOL_IP4) /* 如果协议为IPv4 */
/* 说明:检查协议类型是否为IPv4。
*
* IPv4处理:
* - 使用fib_table_find_or_create_and_lock
* - 使用FIB_SOURCE_DHCP作为来源标识符 */
rx_fib_index = fib_table_find_or_create_and_lock (proto, tbl_id, /* 查找或创建IPv4 FIB表 */
/* 说明:调用fib_table_find_or_create_and_lock函数查找或创建IPv4 FIB表。
*
* 函数功能:
* - 根据Table-ID查找对应的FIB索引
* - 如果FIB表不存在,创建新的FIB表
* - 锁定FIB表,防止被删除
*
* 返回值:
* - FIB索引(u32类型) */
FIB_SOURCE_DHCP); /* FIB来源 */
/* 说明:FIB_SOURCE_DHCP是FIB来源标识符。 */
else /* 如果协议为IPv6 */
/* 说明:检查协议类型是否为IPv6。
*
* IPv6处理:
* - 使用mfib_table_find_or_create_and_lock
* - 使用MFIB_SOURCE_DHCP作为来源标识符 */
rx_fib_index = mfib_table_find_or_create_and_lock (proto, tbl_id, /* 查找或创建IPv6 FIB表 */
/* 说明:调用mfib_table_find_or_create_and_lock函数查找或创建IPv6 FIB表。
*
* 函数功能:
* - 根据Table-ID查找对应的多播FIB索引
* - 如果FIB表不存在,创建新的FIB表
* - 锁定FIB表,防止被删除
*
* 返回值:
* - FIB索引(u32类型) */
MFIB_SOURCE_DHCP); /* MFIB来源 */
/* 说明:MFIB_SOURCE_DHCP是多播FIB来源标识符。 */
v = dhcp_get_vss_info (dm, rx_fib_index, proto); /* 查找VSS配置 */
/* 说明:调用内联函数查找指定FIB索引的VSS配置。
*
* 查找过程:
* 1. 检查索引映射向量长度是否大于rx_fib_index
* 2. 检查索引值是否为~0(未配置)
* 3. 如果已配置,从池中获取VSS配置指针
*
* 返回值:
* - 如果找到:返回VSS配置指针
* - 如果未找到:返回NULL */
if (NULL != v) /* 如果VSS配置存在 */
/* 说明:检查是否找到VSS配置。
*
* 条件:
* - NULL != v:找到VSS配置
* - 这种情况发生在修改或删除现有VSS配置时 */
{
if (is_del) /* 如果是删除操作 */
/* 说明:检查是否为删除操作。
*
* 删除操作:
* - is_del = 1:删除VSS配置
* - 需要释放FIB表锁和内存资源 */
{
/* release the lock held on the table when the VSS
* info was created */
/* 注释说明:释放创建VSS信息时持有的表锁。 */
dhcp_proxy_rx_table_unlock (proto, rx_fib_index); /* 释放FIB表锁 */
/* 说明:调用辅助函数释放FIB表锁。
*
* 释放目的:
* - 允许FIB表被删除(如果没有其他引用)
* - 释放FIB表资源
* - 防止资源泄漏 */
vec_free (v->vpn_ascii_id); /* 释放ASCII标识符内存 */
/* 说明:如果存在ASCII标识符,释放其内存。
*
* 释放对象:
* - v->vpn_ascii_id向量(如果存在)
* - 向量中存储的字符串数据
*
* 释放目的:
* - 回收内存资源
* - 防止内存泄漏 */
pool_put (dm->vss[proto], v); /* 将VSS配置归还到池中 */
/* 说明:pool_put函数将VSS配置归还到内存池中。
*
* 归还目的:
* - 回收内存资源
* - 池可以重用此内存,分配给新的配置 */
dm->vss_index_by_rx_fib_index[proto][rx_fib_index] = ~0; /* 清除索引映射 */
/* 说明:将索引映射设置为~0,表示未配置VSS。
*
* 清除目的:
* - 后续查找时,dhcp_get_vss_info会返回NULL
* - 表示VSS配置已被删除 */
}
else /* 如果是添加或修改操作 */
/* 说明:检查是否为添加或修改操作。
*
* 修改操作:
* - is_del = 0:添加或修改VSS配置
* - 调用update_vss函数更新配置 */
{
update_vss (v, vss_type, vpn_ascii_id, oui, vpn_index); /* 更新VSS配置 */
/* 说明:调用辅助函数更新VSS配置。
*
* 更新内容:
* - VSS类型
* - VPN标识符(ASCII或VPN-ID)
* - 释放旧的标识符内存(如果存在) */
}
}
else /* 如果VSS配置不存在 */
/* 说明:检查是否找到VSS配置。
*
* 条件:
* - NULL == v:未找到VSS配置
* - 这种情况发生在首次配置VSS或删除不存在的VSS时 */
{
if (is_del) /* 如果是删除操作 */
/* 说明:检查是否为删除操作。
*
* 删除不存在的配置:
* - 返回错误代码
* - 表示配置不存在,无法删除 */
rc = VNET_API_ERROR_NO_SUCH_ENTRY; /* 返回错误:配置不存在 */
/* 说明:设置错误代码,表示配置不存在。
*
* 错误代码:
* - VNET_API_ERROR_NO_SUCH_ENTRY:条目不存在
* - 用于API错误响应 */
else /* 如果是添加操作 */
/* 说明:检查是否为添加操作。
*
* 添加新配置:
* - is_del = 0:添加新的VSS配置
* - 需要创建新的VSS配置并建立索引映射 */
{
/* create a new entry */
/* 注释说明:创建新条目。 */
vec_validate_init_empty (dm->vss_index_by_rx_fib_index[proto], /* 扩展索引映射向量 */
/* 说明:vec_validate_init_empty函数用于扩展向量并初始化新元素。
*
* 功能:
* - 如果向量长度 <= rx_fib_index,扩展向量到rx_fib_index+1
* - 新扩展的元素初始化为~0
*
* 目的:
* - 确保索引映射向量足够大,可以存储rx_fib_index索引 */
rx_fib_index, /* 目标索引 */
~0); /* 初始值:~0表示未配置 */
/* 说明:新扩展的元素初始化为~0。 */
/* hold a lock on the table whilst the VSS info exist */
/* 注释说明:在VSS信息存在期间持有表锁。 */
pool_get (dm->vss[proto], v); /* 从池中分配VSS配置 */
/* 说明:pool_get函数从内存池中分配一个新的VSS配置。
*
* 分配后的状态:
* - VSS配置结构体的内容是未初始化的
* - 需要调用update_vss初始化 */
update_vss (v, vss_type, vpn_ascii_id, oui, vpn_index); /* 初始化VSS配置 */
/* 说明:调用辅助函数初始化VSS配置。
*
* 初始化内容:
* - VSS类型
* - VPN标识符(ASCII或VPN-ID) */
dm->vss_index_by_rx_fib_index[proto][rx_fib_index] = /* 建立索引映射 */
/* 说明:将VSS配置在池中的索引保存到索引映射中。
*
* 索引计算:
* - v - dm->vss[proto]
* - 计算v指针与池起始地址的偏移
* - 偏移除以元素大小,得到索引 */
v - dm->vss[proto]; /* 计算配置在池中的索引 */
/* 说明:计算VSS配置在池中的索引。 */
dhcp_proxy_rx_table_lock (proto, rx_fib_index); /* 锁定FIB表 */
/* 说明:调用辅助函数锁定FIB表。
*
* 锁定目的:
* - 防止FIB表被删除
* - 确保VSS配置有效期间,FIB表一直存在
* - 在删除VSS配置时,释放此锁 */
}
}
/* Release the lock taken during the create_or_lock at the start */
/* 注释说明:释放开始时通过create_or_lock获取的锁。 */
dhcp_proxy_rx_table_unlock (proto, rx_fib_index); /* 释放FIB表锁 */
/* 说明:释放开始时通过fib_table_find_or_create_and_lock获取的FIB表锁。
*
* 释放原因:
* - 开始时获取的锁是临时的,用于查找或创建FIB表
* - 如果创建了新的VSS配置,会再次锁定FIB表(通过dhcp_proxy_rx_table_lock)
* - 这里释放的是临时锁,不是VSS配置持有的锁
*
* 锁的管理:
* - 临时锁:在函数开始时获取,在函数结束时释放
* - VSS配置锁:在创建VSS配置时获取,在删除VSS配置时释放 */
return (rc); /* 返回结果 */
/* 说明:返回函数执行结果。
*
* 返回值:
* - 0:成功
* - 非0:失败(错误代码)
*
* 错误情况:
* - VNET_API_ERROR_NO_SUCH_ENTRY:尝试删除不存在的VSS配置 */
}
函数流程总结:
dhcp_proxy_set_vss() 函数流程
│
├─→ 1. 初始化变量
│ ├─→ dm = &dhcp_proxy_main
│ ├─→ v = NULL
│ └─→ rc = 0
│
├─→ 2. 转换Table-ID为FIB索引
│ ├─→ 如果IPv4:fib_table_find_or_create_and_lock(...)
│ └─→ 如果IPv6:mfib_table_find_or_create_and_lock(...)
│
├─→ 3. 查找VSS配置
│ └─→ v = dhcp_get_vss_info(dm, rx_fib_index, proto)
│
├─→ 4. 如果VSS配置存在(v != NULL)
│ ├─→ 如果是删除操作(is_del == 1)
│ │ ├─→ 释放FIB表锁
│ │ │ └─→ dhcp_proxy_rx_table_unlock(...)
│ │ ├─→ 释放ASCII标识符内存
│ │ │ └─→ vec_free(v->vpn_ascii_id)
│ │ ├─→ 归还VSS配置到池中
│ │ │ └─→ pool_put(dm->vss[proto], v)
│ │ └─→ 清除索引映射
│ │ └─→ dm->vss_index_by_rx_fib_index[proto][rx_fib_index] = ~0
│ │
│ └─→ 如果是修改操作(is_del == 0)
│ └─→ 更新VSS配置
│ └─→ update_vss(v, vss_type, vpn_ascii_id, oui, vpn_index)
│
├─→ 5. 如果VSS配置不存在(v == NULL)
│ ├─→ 如果是删除操作(is_del == 1)
│ │ └─→ 返回错误:rc = VNET_API_ERROR_NO_SUCH_ENTRY
│ │
│ └─→ 如果是添加操作(is_del == 0)
│ ├─→ 扩展索引映射向量
│ │ └─→ vec_validate_init_empty(...)
│ ├─→ 从池中分配VSS配置
│ │ └─→ pool_get(dm->vss[proto], v)
│ ├─→ 初始化VSS配置
│ │ └─→ update_vss(v, vss_type, vpn_ascii_id, oui, vpn_index)
│ ├─→ 建立索引映射
│ │ └─→ dm->vss_index_by_rx_fib_index[proto][rx_fib_index] = index
│ └─→ 锁定FIB表
│ └─→ dhcp_proxy_rx_table_lock(proto, rx_fib_index)
│
├─→ 6. 释放临时FIB表锁
│ └─→ dhcp_proxy_rx_table_unlock(proto, rx_fib_index)
│
└─→ 7. 返回结果
└─→ return rc
关键设计特点:
- FIB表管理:自动查找或创建FIB表,并正确管理锁
- 内存管理:正确释放ASCII标识符内存,避免内存泄漏
- 类型支持:支持ASCII、VPN-ID和默认三种VSS类型
- 错误处理:删除不存在的配置时返回错误代码
使用示例:
// 配置ASCII VSS
u8 *vpn_id = (u8 *)"my-vpn";
int rc = dhcp_proxy_set_vss(FIB_PROTOCOL_IP4,
0, // Table-ID
VSS_TYPE_ASCII,
vpn_id,
0, // OUI(不使用)
0, // VPN索引(不使用)
0); // is_del = 0(添加)
// 配置VPN-ID VSS
rc = dhcp_proxy_set_vss(FIB_PROTOCOL_IP4,
0, // Table-ID
VSS_TYPE_VPN_ID,
NULL, // ASCII ID(不使用)
0x123456, // OUI
0x7890ABCD, // VPN索引
0); // is_del = 0(添加)
// 删除VSS配置
rc = dhcp_proxy_set_vss(FIB_PROTOCOL_IP4,
0, // Table-ID
VSS_TYPE_ASCII, // 类型(删除时忽略)
NULL,
0,
0,
1); // is_del = 1(删除)
6.3.2节总结:
本节详细讲解了DHCP Proxy的配置管理功能,包括:
-
添加Proxy配置(
dhcp_proxy_server_add):- 支持创建新配置或添加服务器到现有配置
- 自动管理FIB表锁和索引映射
- 支持多服务器配置
-
删除Proxy配置(
dhcp_proxy_server_del):- 支持删除单个服务器或整个Proxy配置
- 自动释放FIB表锁和内存资源
- 条件删除:只有在服务器列表为空时才删除整个配置
-
VSS配置(
dhcp_proxy_set_vss):- 支持ASCII、VPN-ID和默认三种VSS类型
- 自动管理FIB表锁和内存资源
- 支持添加、修改和删除操作
所有配置管理函数都正确管理资源,避免内存泄漏和资源泄漏。
6.3.3 Proxy核心逻辑
Proxy核心逻辑包括待处理请求管理和锁机制。待处理请求管理用于跟踪客户端请求,确保多个服务器回复时只转发一个回复给客户端。锁机制用于保护共享数据结构,确保多线程环境下的数据一致性。
6.3.3.1 待处理请求管理
功能说明:
DHCP Proxy在转发客户端请求到服务器时,需要跟踪待处理的请求。当配置了多个服务器时,Proxy会将同一个DISCOVER请求转发到所有服务器,但只应该将第一个收到的OFFER回复转发给客户端。待处理请求管理通过哈希表实现,使用客户端的MAC地址作为键。
数据结构:
在dhcp_proxy_t结构体中,有以下字段用于待处理请求管理:
//100:132:src/plugins/dhcp/dhcp_proxy.h
typedef struct dhcp_proxy_t_
{
/**
* @brief The set of DHCP servers to which messages are relayed.
* If multiple servers are configured then discover/solict messages
* are relayed to each. A cookie is maintained for the relay, and only
* one message is replayed to the client, based on the presence of the
* cookie.
* The expectation is there are only 1 or 2 servers, hence no fancy DB.
*/
dhcp_server_t *dhcp_servers; /* DHCP服务器列表向量 */
/* 说明:dhcp_servers字段存储DHCP服务器列表。
*
* 类型:dhcp_server_t *(向量指针)
* - 使用VPP的向量(vec)数据结构
* - 支持动态扩展,可以添加多个服务器
*
* 用途:
* - 存储配置的DHCP服务器地址和FIB索引
* - 在转发请求时,遍历此列表
* - 如果配置了多个服务器,DISCOVER请求会转发到所有服务器
*
* 多服务器处理:
* - 当收到DISCOVER请求时,会复制报文并转发到每个服务器
* - 每个服务器可能回复OFFER,但只转发第一个OFFER给客户端
* - 使用Cookie机制确保只转发一个回复 */
/**
* @brief Hash table of pending requets key'd on the clients MAC address
*/
uword *dhcp_pending; /* 待处理请求哈希表 */
/* 说明:dhcp_pending字段存储待处理请求的哈希表。
*
* 类型:uword *(VPP哈希表指针)
* - 使用VPP的哈希表(hash)数据结构
* - 键:客户端的MAC地址(6字节)
* - 值:待处理请求的相关信息(具体实现可能因版本而异)
*
* 用途:
* - 跟踪已转发到服务器的客户端请求
* - 当收到服务器回复时,检查此哈希表
* - 如果找到匹配的请求,转发回复给客户端
* - 如果未找到,可能是重复回复或无效回复,丢弃
*
* Cookie机制:
* - 在转发请求时,将客户端MAC地址插入哈希表
* - 在收到回复时,检查回复中的Cookie或MAC地址
* - 如果匹配,转发回复并删除哈希表项
* - 如果未匹配,丢弃回复(可能是重复回复)
*
* 超时处理:
* - 如果长时间未收到回复,需要清理哈希表项
* - 避免哈希表无限增长
* - 当前实现可能依赖超时机制或定期清理 */
/**
* @brief A lock for the pending request DB.
*/
int lock; /* 待处理请求数据库的锁 */
/* 说明:lock字段用于保护待处理请求哈希表的并发访问。
*
* 类型:int(锁变量)
* - 在VPP中,通常使用自旋锁或互斥锁
* - 具体实现取决于VPP的锁机制
*
* 用途:
* - 保护dhcp_pending哈希表的并发访问
* - 防止多线程同时修改哈希表导致数据损坏
* - 确保哈希表操作的原子性
*
* 使用场景:
* - 在添加待处理请求时,需要先加锁
* - 在查找和删除待处理请求时,也需要加锁
* - 在超时清理时,需要加锁
*
* 锁的实现:
* - 通过dhcp_proxy_lock()函数加锁
* - 通过dhcp_proxy_unlock()函数解锁
* - 锁的实现可能使用vppinfra的锁机制 */
/**
* @brief The source address to use in relayed messaes
*/
ip46_address_t dhcp_src_address; /* 转发报文使用的源地址 */
/* 说明:dhcp_src_address字段存储Proxy使用的源地址。
*
* 类型:ip46_address_t(IPv4/IPv6地址联合体)
* - 支持IPv4和IPv6地址
* - 在配置时设置
*
* 用途:
* - 在转发请求到服务器时,使用此地址作为IP源地址
* - 服务器回复时,使用此地址作为IP目标地址
* - 确保服务器回复能够正确路由回Proxy
*
* 地址选择:
* - 通常选择客户端所在接口的IP地址
* - 或者选择Proxy配置的特定地址
* - 必须确保服务器能够路由到此地址 */
/**
* @brief The FIB index (not the external Table-ID) in which the client
* is resides.
*/
u32 rx_fib_index; /* 客户端所在的FIB索引 */
/* 说明:rx_fib_index字段存储客户端所在的FIB索引。
*
* 类型:u32(32位无符号整数)
* - FIB索引,不是外部Table-ID
* - 用于标识客户端所在的VRF
*
* 用途:
* - 标识此Proxy配置对应的VRF
* - 用于查找和验证
* - 用于路由查找和转发
*
* 索引映射:
* - 通过dhcp_server_index_by_rx_fib_index建立映射
* - 使用rx_fib_index作为键,查找Proxy配置 */
} dhcp_proxy_t;
Cookie机制说明:
根据代码注释和结构体说明,DHCP Proxy使用Cookie机制来确保多个服务器回复时只转发一个回复给客户端。虽然当前代码中dhcp_pending哈希表的具体实现细节可能因版本而异,但基本机制如下:
-
请求转发时:
- 当收到客户端的DISCOVER请求时,Proxy会检查是否配置了多个服务器
- 如果配置了多个服务器,会复制报文并转发到每个服务器
- 将客户端MAC地址插入
dhcp_pending哈希表,标记为待处理请求
-
回复接收时:
- 当收到服务器的OFFER回复时,检查回复中的客户端MAC地址
- 在
dhcp_pending哈希表中查找匹配的请求 - 如果找到匹配的请求,转发回复给客户端,并删除哈希表项
- 如果未找到匹配的请求,可能是重复回复或无效回复,丢弃
-
超时处理:
- 如果长时间未收到回复,需要清理哈希表项
- 避免哈希表无限增长
- 当前实现可能依赖超时机制或定期清理
注意:当前VPP代码中,dhcp_pending哈希表的具体使用可能因版本而异。在某些版本中,可能使用Option 82中的信息来匹配请求和回复,而不是直接使用MAC地址。具体实现细节需要查看实际运行的代码版本。
6.3.3.2 锁机制
功能说明:
锁机制用于保护dhcp_pending哈希表的并发访问,确保多线程环境下的数据一致性。当多个线程同时访问哈希表时,需要加锁来防止数据竞争。
锁函数声明:
//238:248:src/plugins/dhcp/dhcp_proxy.h
/**
* @brief Lock a proxy object to prevent simultaneous access of its
* pending store
*/
void dhcp_proxy_lock (dhcp_proxy_t * server); /* 锁定Proxy配置 */
/* 说明:dhcp_proxy_lock函数用于锁定Proxy配置的待处理请求哈希表。
*
* 功能:
* - 获取Proxy配置的锁
* - 保护dhcp_pending哈希表的并发访问
* - 防止多线程同时修改哈希表导致数据损坏
*
* 参数:
* - server:Proxy配置指针
* - 指向要锁定的dhcp_proxy_t结构体
* - 通过server->lock字段实现锁机制
*
* 使用场景:
* - 在添加待处理请求时调用
* - 在查找待处理请求时调用
* - 在删除待处理请求时调用
* - 在超时清理时调用
*
* 锁的实现:
* - 可能使用vppinfra的锁机制(如clib_spinlock_lock)
* - 或者使用简单的互斥锁
* - 具体实现取决于VPP版本
*
* 注意事项:
* - 必须与dhcp_proxy_unlock配对使用
* - 避免死锁,不要在持有锁时调用可能阻塞的函数
* - 锁的粒度要合适,既要保证数据安全,又要避免性能问题 */
/**
* @brief Lock a proxy object to prevent simultaneous access of its
* pending store
*/
void dhcp_proxy_unlock (dhcp_proxy_t * server); /* 解锁Proxy配置 */
/* 说明:dhcp_proxy_unlock函数用于解锁Proxy配置的待处理请求哈希表。
*
* 功能:
* - 释放Proxy配置的锁
* - 允许其他线程访问哈希表
* - 与dhcp_proxy_lock配对使用
*
* 参数:
* - server:Proxy配置指针
* - 指向要解锁的dhcp_proxy_t结构体
* - 通过server->lock字段实现锁机制
*
* 使用场景:
* - 在完成哈希表操作后调用
* - 必须与dhcp_proxy_lock配对使用
* - 在函数返回前必须解锁
*
* 注意事项:
* - 必须与dhcp_proxy_lock配对使用
* - 确保在所有返回路径上都解锁
* - 避免在未加锁的情况下调用此函数 */
锁的使用模式:
虽然当前代码中dhcp_proxy_lock和dhcp_proxy_unlock的具体实现可能因版本而异,但典型的使用模式如下:
/* 典型使用模式 */
dhcp_proxy_t *proxy = ...; /* 获取Proxy配置指针 */
/* 加锁 */
dhcp_proxy_lock(proxy);
/* 访问哈希表 */
uword *p = hash_get(proxy->dhcp_pending, client_mac);
if (p) {
/* 找到待处理请求,处理回复 */
...
hash_unset(proxy->dhcp_pending, client_mac);
} else {
/* 未找到待处理请求,可能是重复回复,丢弃 */
...
}
/* 解锁 */
dhcp_proxy_unlock(proxy);
并发控制说明:
-
锁的粒度:
- 锁保护的是整个
dhcp_pending哈希表 - 粒度较粗,但实现简单,适合VPP的高性能要求
- 如果性能成为瓶颈,可以考虑使用更细粒度的锁
- 锁保护的是整个
-
锁的类型:
- 在VPP中,通常使用自旋锁(spinlock)
- 自旋锁适合短时间持有的锁
- 如果锁持有时间较长,可能需要使用互斥锁
-
死锁预防:
- 避免在持有锁时调用可能阻塞的函数
- 避免嵌套加锁
- 确保在所有返回路径上都解锁
-
性能考虑:
- 尽量减少锁的持有时间
- 在加锁前完成所有不需要锁的操作
- 考虑使用无锁数据结构(如果适用)
锁的实现推测:
虽然当前代码中锁函数的具体实现可能因版本而异,但根据VPP的常见模式,可能的实现如下:
/* 可能的实现方式(仅供参考,实际实现可能不同) */
void
dhcp_proxy_lock (dhcp_proxy_t * proxy)
{
/* 使用vppinfra的锁机制 */
/* 具体实现取决于VPP版本和锁类型 */
/* 可能是:clib_spinlock_lock(&proxy->lock) */
/* 或者:pthread_mutex_lock(&proxy->lock) */
}
void
dhcp_proxy_unlock (dhcp_proxy_t * proxy)
{
/* 释放锁 */
/* 可能是:clib_spinlock_unlock(&proxy->lock) */
/* 或者:pthread_mutex_unlock(&proxy->lock) */
}
注意:由于当前代码中dhcp_proxy_lock和dhcp_proxy_unlock的具体实现可能因版本而异,或者可能还没有实现(函数声明存在但实现可能为空),实际使用时需要查看具体版本的代码实现。
6.3.3节总结:
本节详细讲解了DHCP Proxy的核心逻辑,包括:
-
待处理请求管理:
- 使用
dhcp_pending哈希表跟踪待处理请求 - 使用客户端MAC地址作为键
- 支持多服务器配置时的Cookie机制
- 确保多个服务器回复时只转发一个回复给客户端
- 使用
-
锁机制:
- 使用
lock字段保护哈希表的并发访问 - 通过
dhcp_proxy_lock和dhcp_proxy_unlock函数加锁和解锁 - 确保多线程环境下的数据一致性
- 使用
-
设计特点:
- 简单高效:使用哈希表实现O(1)时间复杂度的查找
- 线程安全:使用锁机制保护共享数据结构
- 可扩展:支持动态添加和删除待处理请求
6.3.4 Proxy辅助功能
Proxy辅助功能包括UDP端口注册、配置查询和遍历、API支持等。这些功能为Proxy提供了完整的配置管理和查询能力。
6.3.4.1 UDP端口注册
功能说明:
dhcp_maybe_register_udp_ports()函数用于注册DHCP客户端和服务器UDP端口。函数会检查端口是否已注册,如果未注册则注册,避免重复注册。这是Proxy功能正常工作的前提,因为VPP需要通过UDP端口将DHCP报文路由到Proxy节点。
函数输入:
ports:端口注册标志(dhcp_port_reg_flags_t类型)DHCP_PORT_REG_CLIENT:注册客户端端口(68)DHCP_PORT_REG_SERVER:注册服务器端口(67)- 可以同时设置两个标志(使用位或操作)
函数输出:
- 无返回值(
void类型) - 函数执行后,指定的UDP端口已注册到VPP的UDP处理模块
端口注册标志枚举:
//46:51:src/plugins/dhcp/dhcp_proxy.h
/* flags to indicate which DHCP ports should be or have been registered */
typedef enum
{
DHCP_PORT_REG_CLIENT = 0x1, /* 注册客户端端口标志 */
/* 说明:DHCP_PORT_REG_CLIENT标志用于指示注册客户端端口。
*
* 值:0x1(二进制:0001)
* - 位0设置为1
* - 用于标识需要注册客户端端口(UDP 68)
*
* 用途:
* - 在dhcp_maybe_register_udp_ports函数中
* - 检查是否需要注册客户端端口
* - 客户端端口用于接收服务器到客户端的DHCP回复 */
DHCP_PORT_REG_SERVER = 0x2, /* 注册服务器端口标志 */
/* 说明:DHCP_PORT_REG_SERVER标志用于指示注册服务器端口。
*
* 值:0x2(二进制:0010)
* - 位1设置为1
* - 用于标识需要注册服务器端口(UDP 67)
*
* 用途:
* - 在dhcp_maybe_register_udp_ports函数中
* - 检查是否需要注册服务器端口
* - 服务器端口用于接收客户端到服务器的DHCP请求 */
} dhcp_port_reg_flags_t; /* 端口注册标志类型 */
/* 说明:dhcp_port_reg_flags_t是端口注册标志的枚举类型。
*
* 使用方式:
* - 可以单独使用:DHCP_PORT_REG_CLIENT或DHCP_PORT_REG_SERVER
* - 可以组合使用:DHCP_PORT_REG_CLIENT | DHCP_PORT_REG_SERVER
* - 使用位或操作组合多个标志 */
全局管理器中的端口注册状态:
//139:161:src/plugins/dhcp/dhcp_proxy.h
typedef struct
{
/* Pool of DHCP servers */
dhcp_proxy_t *dhcp_servers[DHCP_N_PROTOS]; /* DHCP服务器配置池数组 */
/* 说明:dhcp_servers字段存储DHCP服务器配置池。
*
* 类型:dhcp_proxy_t *数组
* - 数组大小为DHCP_N_PROTOS(2,IPv4和IPv6)
* - 每个协议有独立的配置池
*
* 用途:
* - 存储所有Proxy配置
* - 通过内存池管理,支持动态分配和释放 */
/* Pool of selected DHCP server. Zero is the default server */
u32 *dhcp_server_index_by_rx_fib_index[DHCP_N_PROTOS]; /* 索引映射向量数组 */
/* 说明:dhcp_server_index_by_rx_fib_index字段存储索引映射向量。
*
* 类型:u32 *数组
* - 数组大小为DHCP_N_PROTOS(2)
* - 每个协议有独立的索引映射
*
* 用途:
* - 通过FIB索引快速查找Proxy配置
* - 使用向量实现O(1)时间复杂度的查找 */
/* to drop pkts in server-to-client direction */
u32 error_drop_node_index; /* 错误丢弃节点索引 */
/* 说明:error_drop_node_index字段存储错误丢弃节点索引。
*
* 用途:
* - 在服务器到客户端方向出现错误时
* - 将报文发送到此节点
* - 节点会丢弃报文并更新错误统计 */
dhcp_vss_t *vss[DHCP_N_PROTOS]; /* VSS配置池数组 */
/* 说明:vss字段存储VSS配置池。
*
* 类型:dhcp_vss_t *数组
* - 数组大小为DHCP_N_PROTOS(2)
* - 每个协议有独立的VSS配置池
*
* 用途:
* - 存储VSS配置信息
* - 用于在多租户环境中标识VPN */
/* hash lookup specific vrf_id -> option 82 vss suboption */
u32 *vss_index_by_rx_fib_index[DHCP_N_PROTOS]; /* VSS索引映射向量数组 */
/* 说明:vss_index_by_rx_fib_index字段存储VSS索引映射向量。
*
* 类型:u32 *数组
* - 数组大小为DHCP_N_PROTOS(2)
* - 每个协议有独立的索引映射
*
* 用途:
* - 通过FIB索引快速查找VSS配置
* - 使用向量实现O(1)时间复杂度的查找 */
/* flags to indicate which udp ports have been registered */
int udp_ports_registered; /* UDP端口注册状态标志 */
/* 说明:udp_ports_registered字段存储已注册的UDP端口标志。
*
* 类型:int(整数类型,实际用作位标志)
* - 使用位标志存储端口注册状态
* - 位0:客户端端口(68)是否已注册
* - 位1:服务器端口(67)是否已注册
*
* 用途:
* - 避免重复注册端口
* - 在dhcp_maybe_register_udp_ports函数中
* - 检查端口是否已注册
* - 如果已注册,跳过注册操作
*
* 初始值:
* - 0:未注册任何端口
* - 在首次注册时设置相应的位标志 */
/* convenience */
vlib_main_t *vlib_main; /* VLIB主线程指针 */
/* 说明:vlib_main字段存储VLIB主线程指针。
*
* 用途:
* - 用于访问VPP核心功能
* - 用于UDP端口注册
* - 用于节点访问等 */
} dhcp_proxy_main_t; /* DHCP Proxy全局管理器类型 */
/* 说明:dhcp_proxy_main_t是DHCP Proxy的全局管理器结构体。
*
* 用途:
* - 存储所有Proxy相关的全局数据
* - 包括配置池、索引映射、端口注册状态等
* - 全局只有一个实例:dhcp_proxy_main */
UDP端口注册函数源码详解:
//805:824:src/plugins/dhcp/dhcp4_proxy_node.c
void /* 返回类型:无返回值 */
dhcp_maybe_register_udp_ports (dhcp_port_reg_flags_t ports) /* 函数名:可能注册UDP端口 */
/* 说明:dhcp_maybe_register_udp_ports函数用于注册DHCP客户端和服务器UDP端口。
*
* 功能:
* - 检查端口是否已注册
* - 如果未注册,则注册指定的UDP端口
* - 避免重复注册端口
*
* 参数:
* - ports:端口注册标志
* - DHCP_PORT_REG_CLIENT:注册客户端端口(68)
* - DHCP_PORT_REG_SERVER:注册服务器端口(67)
* - 可以组合使用(使用位或操作)
*
* 调用时机:
* - 在首次配置Proxy服务器时调用
* - 在dhcp4_proxy_set_server函数中调用
* - 确保端口在使用前已注册 */
{
dhcp_proxy_main_t *dm = &dhcp_proxy_main; /* 获取全局管理器指针 */
/* 说明:dm变量指向全局的dhcp_proxy_main实例。
*
* 使用方式:
* - 访问udp_ports_registered字段
* - 访问vlib_main字段
* - 修改端口注册状态 */
vlib_main_t *vm = dm->vlib_main; /* 获取VLIB主线程指针 */
/* 说明:vm变量存储VLIB主线程指针。
*
* 获取方式:
* - 从全局管理器的vlib_main字段获取
* - 在初始化时设置(dhcp4_proxy_init函数)
*
* 用途:
* - 传递给udp_register_dst_port函数
* - 用于UDP端口注册 */
int port_regs_diff = dm->udp_ports_registered ^ ports; /* 计算端口注册差异 */
/* 说明:port_regs_diff变量存储端口注册状态的差异。
*
* 计算方法:
* - 使用异或操作(^)计算差异
* - dm->udp_ports_registered:已注册的端口标志
* - ports:需要注册的端口标志
* - 异或结果:需要注册但未注册的端口
*
* 示例:
* - 如果已注册客户端端口(0x1),需要注册服务器端口(0x2)
* - port_regs_diff = 0x1 ^ 0x2 = 0x3
* - 但实际需要注册的是服务器端口(0x2)
* - 需要进一步检查 */
if (!port_regs_diff) /* 如果端口已全部注册 */
/* 说明:检查端口注册差异。
*
* 条件:
* - port_regs_diff == 0:所有需要注册的端口都已注册
* - 不需要执行任何操作,直接返回
*
* 优化:
* - 避免重复注册端口
* - 提高性能,减少不必要的操作 */
return; /* 直接返回,无需注册 */
/* 说明:如果所有端口都已注册,直接返回。
*
* 返回原因:
* - 避免重复注册端口
* - 提高性能
* - 简化逻辑 */
if ((port_regs_diff & DHCP_PORT_REG_CLIENT) & ports) /* 如果需要注册客户端端口 */
/* 说明:检查是否需要注册客户端端口。
*
* 条件分解:
* - port_regs_diff & DHCP_PORT_REG_CLIENT:差异中包含客户端端口
* - & ports:并且需要注册客户端端口
* - 两个条件都满足,说明需要注册客户端端口
*
* 逻辑:
* - 如果已注册客户端端口,port_regs_diff & DHCP_PORT_REG_CLIENT = 0
* - 如果未注册客户端端口,port_regs_diff & DHCP_PORT_REG_CLIENT = 0x1
* - 如果ports包含客户端端口,ports & DHCP_PORT_REG_CLIENT = 0x1
* - 两个条件都满足,说明需要注册 */
udp_register_dst_port (vm, UDP_DST_PORT_dhcp_to_client, /* 注册客户端端口 */
/* 说明:udp_register_dst_port函数用于注册UDP目标端口。
*
* 参数1:vm
* - VLIB主线程指针
* - 用于访问UDP处理模块
*
* 参数2:UDP_DST_PORT_dhcp_to_client
* - UDP目标端口常量
* - 值为68(DHCP客户端端口)
* - 定义在udp_local.h中
*
* 功能:
* - 将UDP端口68注册到VPP的UDP处理模块
* - 当收到目标端口为68的UDP报文时
* - 路由到dhcp_proxy_to_client节点
*
* 注册结果:
* - UDP报文的目标端口为68时
* - 会被路由到dhcp_proxy_to_client节点
* - 节点处理服务器到客户端的DHCP回复 */
dhcp_proxy_to_client_node.index, /* 目标节点索引 */
/* 说明:dhcp_proxy_to_client_node.index是目标节点索引。
*
* 节点:
* - dhcp_proxy_to_client_node是服务器到客户端方向的节点
* - 处理服务器回复的DHCP报文
* - 节点索引在节点注册时自动分配
*
* 用途:
* - 指定UDP端口68的目标节点
* - 当收到目标端口为68的UDP报文时
* - 路由到此节点处理 */
1 /* is_ip4 */ ); /* IPv4协议标志 */
/* 说明:is_ip4参数指定是否为IPv4协议。
*
* 值:1
* - 表示这是IPv4协议的UDP端口注册
* - DHCPv4使用IPv4协议
*
* 用途:
* - 区分IPv4和IPv6的UDP端口注册
* - IPv4和IPv6使用独立的端口映射表 */
if ((port_regs_diff & DHCP_PORT_REG_SERVER) & ports) /* 如果需要注册服务器端口 */
/* 说明:检查是否需要注册服务器端口。
*
* 条件分解:
* - port_regs_diff & DHCP_PORT_REG_SERVER:差异中包含服务器端口
* - & ports:并且需要注册服务器端口
* - 两个条件都满足,说明需要注册服务器端口
*
* 逻辑:
* - 如果已注册服务器端口,port_regs_diff & DHCP_PORT_REG_SERVER = 0
* - 如果未注册服务器端口,port_regs_diff & DHCP_PORT_REG_SERVER = 0x2
* - 如果ports包含服务器端口,ports & DHCP_PORT_REG_SERVER = 0x2
* - 两个条件都满足,说明需要注册 */
udp_register_dst_port (vm, UDP_DST_PORT_dhcp_to_server, /* 注册服务器端口 */
/* 说明:udp_register_dst_port函数用于注册UDP目标端口。
*
* 参数1:vm
* - VLIB主线程指针
* - 用于访问UDP处理模块
*
* 参数2:UDP_DST_PORT_dhcp_to_server
* - UDP目标端口常量
* - 值为67(DHCP服务器端口)
* - 定义在udp_local.h中
*
* 功能:
* - 将UDP端口67注册到VPP的UDP处理模块
* - 当收到目标端口为67的UDP报文时
* - 路由到dhcp_proxy_to_server节点
*
* 注册结果:
* - UDP报文的目标端口为67时
* - 会被路由到dhcp_proxy_to_server节点
* - 节点处理客户端到服务器的DHCP请求 */
dhcp_proxy_to_server_node.index, /* 目标节点索引 */
/* 说明:dhcp_proxy_to_server_node.index是目标节点索引。
*
* 节点:
* - dhcp_proxy_to_server_node是客户端到服务器方向的节点
* - 处理客户端请求的DHCP报文
* - 节点索引在节点注册时自动分配
*
* 用途:
* - 指定UDP端口67的目标节点
* - 当收到目标端口为67的UDP报文时
* - 路由到此节点处理 */
1 /* is_ip4 */ ); /* IPv4协议标志 */
/* 说明:is_ip4参数指定是否为IPv4协议。
*
* 值:1
* - 表示这是IPv4协议的UDP端口注册
* - DHCPv4使用IPv4协议
*
* 用途:
* - 区分IPv4和IPv6的UDP端口注册
* - IPv4和IPv6使用独立的端口映射表 */
dm->udp_ports_registered |= ports; /* 更新端口注册状态 */
/* 说明:更新全局管理器的端口注册状态标志。
*
* 操作:
* - 使用位或操作(|)更新标志
* - 将新注册的端口标志添加到已注册标志中
* - 例如:如果已注册客户端端口(0x1),新注册服务器端口(0x2)
* - 结果:0x1 | 0x2 = 0x3(两个端口都已注册)
*
* 更新目的:
* - 记录已注册的端口
* - 避免重复注册
* - 在下次调用时,检查此标志决定是否需要注册 */
}
/* 说明:函数执行完成后,指定的UDP端口已注册到VPP的UDP处理模块。
*
* 注册完成后的状态:
* - UDP端口67和/或68已注册
* - 相应的DHCP报文会路由到Proxy节点
* - udp_ports_registered标志已更新
*
* 后续操作:
* - 当收到目标端口为67或68的UDP报文时
* - VPP会自动路由到相应的Proxy节点
* - 节点处理DHCP报文 */
函数流程总结:
dhcp_maybe_register_udp_ports() 函数流程
│
├─→ 1. 获取全局管理器和VLIB主线程指针
│ ├─→ dm = &dhcp_proxy_main
│ └─→ vm = dm->vlib_main
│
├─→ 2. 计算端口注册差异
│ └─→ port_regs_diff = dm->udp_ports_registered ^ ports
│
├─→ 3. 检查是否需要注册
│ └─→ if (!port_regs_diff) return(已全部注册,直接返回)
│
├─→ 4. 注册客户端端口(如果需要)
│ ├─→ 检查:port_regs_diff & DHCP_PORT_REG_CLIENT & ports
│ └─→ 如果满足:udp_register_dst_port(vm, UDP_DST_PORT_dhcp_to_client, ...)
│
├─→ 5. 注册服务器端口(如果需要)
│ ├─→ 检查:port_regs_diff & DHCP_PORT_REG_SERVER & ports
│ └─→ 如果满足:udp_register_dst_port(vm, UDP_DST_PORT_dhcp_to_server, ...)
│
└─→ 6. 更新端口注册状态
└─→ dm->udp_ports_registered |= ports
关键设计特点:
- 幂等性:函数可以多次调用,不会重复注册端口
- 按需注册:只注册需要的端口,避免不必要的操作
- 状态跟踪:使用位标志跟踪已注册的端口
- 性能优化:快速检查端口是否已注册,避免重复操作
使用示例:
// 在dhcp4_proxy_set_server函数中的使用
dhcp_maybe_register_udp_ports (DHCP_PORT_REG_CLIENT | DHCP_PORT_REG_SERVER);
/* 说明:同时注册客户端和服务器端口。
*
* 调用时机:
* - 在首次配置Proxy服务器时
* - 确保端口在使用前已注册
*
* 注册结果:
* - UDP端口67(服务器)已注册
* - UDP端口68(客户端)已注册
* - 相应的DHCP报文会路由到Proxy节点 */
6.3.4.2 配置查询和遍历
功能说明:
配置查询和遍历功能用于遍历所有Proxy配置和VSS配置,支持回调函数处理每个配置。这些函数主要用于CLI显示和API转储功能。
6.3.4.2.1 Proxy配置遍历 - dhcp_proxy_walk()
功能说明:
dhcp_proxy_walk()函数用于遍历指定协议的所有Proxy配置,对每个配置调用回调函数。函数通过索引映射向量遍历所有已配置的Proxy,跳过未配置的项(索引为~0)。
函数输入:
proto:协议类型(IPv4或IPv6)fn:回调函数指针(dhcp_proxy_walk_fn_t类型)ctx:回调函数上下文指针(void *类型)
函数输出:
- 无返回值(
void类型) - 对每个Proxy配置调用回调函数,直到回调函数返回0(停止遍历)
回调函数类型定义:
//214:218:src/plugins/dhcp/dhcp_proxy.h
/**
* @brief Callback function invoked for each DHCP proxy entry
* return 0 to break the walk, non-zero otherwise.
*/
typedef int (*dhcp_proxy_walk_fn_t) (dhcp_proxy_t * server, void *ctx); /* Proxy配置遍历回调函数类型 */
/* 说明:dhcp_proxy_walk_fn_t是Proxy配置遍历回调函数的类型定义。
*
* 函数签名:
* - 参数1:server - Proxy配置指针
* - 参数2:ctx - 回调函数上下文指针
* - 返回值:int - 0表示停止遍历,非0表示继续遍历
*
* 使用场景:
* - 在dhcp_proxy_walk函数中调用
* - 对每个Proxy配置执行特定操作
* - 例如:显示配置、转储到API等
*
* 返回值:
* - 返回0:停止遍历,不再处理后续配置
* - 返回非0:继续遍历下一个配置
*
* 典型实现:
* - 处理Proxy配置(显示、转储等)
* - 返回1继续遍历,返回0停止遍历 */
Proxy配置遍历函数源码详解:
//66:84:src/plugins/dhcp/dhcp_proxy.c
void /* 返回类型:无返回值 */
dhcp_proxy_walk (fib_protocol_t proto, /* 协议类型(IPv4或IPv6) */
/* 说明:proto参数指定协议类型。
*
* 用途:
* - 确定使用哪个协议的配置池和索引映射
* - IPv4和IPv6使用独立的配置池 */
dhcp_proxy_walk_fn_t fn, /* 回调函数指针 */
/* 说明:fn参数指定回调函数指针。
*
* 回调函数:
* - 对每个Proxy配置调用此函数
* - 函数接收Proxy配置指针和上下文指针
* - 返回0停止遍历,返回非0继续遍历 */
void *ctx) /* 回调函数上下文指针 */
/* 说明:ctx参数是回调函数的上下文指针。
*
* 用途:
* - 传递给回调函数
* - 可以包含遍历相关的状态信息
* - 例如:输出缓冲区、计数器等 */
{
dhcp_proxy_main_t *dpm = &dhcp_proxy_main; /* 获取全局管理器指针 */
/* 说明:dpm变量指向全局的dhcp_proxy_main实例。
*
* 使用方式:
* - 访问索引映射向量
* - 访问配置池 */
dhcp_proxy_t *server; /* Proxy配置指针 */
/* 说明:server变量用于存储当前遍历的Proxy配置指针。 */
u32 server_index, i; /* 服务器索引和循环变量 */
/* 说明:server_index变量存储Proxy配置在池中的索引。
* i变量用于循环遍历索引映射向量。 */
vec_foreach_index (i, dpm->dhcp_server_index_by_rx_fib_index[proto]) /* 遍历索引映射向量 */
/* 说明:vec_foreach_index宏用于遍历向量的索引。
*
* 参数1:i
* - 循环变量,从0开始递增
* - 每次循环递增1,直到向量长度
*
* 参数2:dpm->dhcp_server_index_by_rx_fib_index[proto]
* - 索引映射向量指针
* - proto确定使用IPv4还是IPv6的映射
*
* 功能:
* - 遍历索引映射向量的所有索引
* - i从0到vec_len-1
* - 对于每个索引i,执行循环体
*
* 遍历范围:
* - 包括所有已分配的元素
* - 包括未配置的元素(值为~0)
* - 需要检查值是否为~0来跳过未配置的项 */
{
server_index = dpm->dhcp_server_index_by_rx_fib_index[proto][i]; /* 获取Proxy配置索引 */
/* 说明:从索引映射向量中获取Proxy配置在池中的索引。
*
* 访问方式:
* - dpm->dhcp_server_index_by_rx_fib_index[proto][i]
* - 使用i作为数组索引
* - 获取的值是Proxy配置在池中的索引
*
* 索引值:
* - 如果已配置:索引值 >= 0
* - 如果未配置:索引值为~0(0xFFFFFFFF) */
if (~0 == server_index) /* 如果索引为~0,跳过未配置的项 */
/* 说明:检查索引值是否为~0。
*
* ~0的含义:
* - 所有位都为1(0xFFFFFFFF)
* - 用作"未配置"的标记值
*
* 检查目的:
* - 跳过未配置的VRF
* - 只处理已配置的Proxy配置
* - 提高遍历效率 */
continue; /* 跳过未配置的项 */
/* 说明:continue语句跳过当前循环,继续下一个循环。
*
* 跳过原因:
* - 索引值为~0表示该VRF未配置Proxy
* - 不需要处理未配置的项
* - 继续遍历下一个索引 */
server = pool_elt_at_index (dpm->dhcp_servers[proto], server_index); /* 从池中获取Proxy配置 */
/* 说明:pool_elt_at_index函数从内存池中通过索引获取元素。
*
* 参数1:dpm->dhcp_servers[proto]
* - Proxy配置池指针
* - proto确定使用IPv4还是IPv6的配置池
*
* 参数2:server_index
* - Proxy配置在池中的索引
* - 从索引映射向量中获取
*
* 返回值:
* - 返回Proxy配置指针
* - 如果索引无效,可能返回NULL或崩溃(取决于实现) */
if (!fn (server, ctx)) /* 调用回调函数 */
/* 说明:调用回调函数处理Proxy配置。
*
* 参数1:server
* - Proxy配置指针
* - 传递给回调函数
*
* 参数2:ctx
* - 回调函数上下文指针
* - 传递给回调函数
*
* 返回值:
* - 回调函数返回0:停止遍历
* - 回调函数返回非0:继续遍历
*
* 检查:
* - !fn(server, ctx):如果回调函数返回0(假)
* - 执行break语句,停止遍历 */
break; /* 如果回调函数返回0,停止遍历 */
/* 说明:break语句跳出循环,停止遍历。
*
* 停止原因:
* - 回调函数返回0,表示不需要继续遍历
* - 例如:已找到目标配置、发生错误等
*
* 停止后的状态:
* - 循环结束
* - 函数返回
* - 后续配置不会被处理 */
}
}
/* 说明:函数执行完成后,所有Proxy配置都已遍历。
*
* 遍历完成后的状态:
* - 所有已配置的Proxy配置都已处理
* - 回调函数可能已停止遍历(返回0)
* - 或者所有配置都已处理(回调函数一直返回非0) */
函数流程总结:
dhcp_proxy_walk() 函数流程
│
├─→ 1. 初始化变量
│ ├─→ dpm = &dhcp_proxy_main
│ ├─→ server = NULL
│ └─→ server_index, i = 0
│
├─→ 2. 遍历索引映射向量
│ └─→ vec_foreach_index(i, dpm->dhcp_server_index_by_rx_fib_index[proto])
│
├─→ 3. 获取Proxy配置索引
│ └─→ server_index = dpm->dhcp_server_index_by_rx_fib_index[proto][i]
│
├─→ 4. 检查是否已配置
│ ├─→ if (~0 == server_index) continue(跳过未配置的项)
│ └─→ 否则继续处理
│
├─→ 5. 从池中获取Proxy配置
│ └─→ server = pool_elt_at_index(dpm->dhcp_servers[proto], server_index)
│
├─→ 6. 调用回调函数
│ ├─→ if (!fn(server, ctx)) break(如果返回0,停止遍历)
│ └─→ 否则继续遍历下一个配置
│
└─→ 7. 遍历完成
└─→ 所有已配置的Proxy配置都已处理
6.3.4.2.2 VSS配置遍历 - dhcp_vss_walk()
功能说明:
dhcp_vss_walk()函数用于遍历指定协议的所有VSS配置,对每个配置调用回调函数。函数通过索引映射向量遍历所有已配置的VSS,跳过未配置的项(索引为~0)。
函数输入:
proto:协议类型(IPv4或IPv6)fn:回调函数指针(dhcp_vss_walk_fn_t类型)ctx:回调函数上下文指针(void *类型)
函数输出:
- 无返回值(
void类型) - 对每个VSS配置调用回调函数,直到回调函数返回0(停止遍历)
回调函数类型定义:
//226:231:src/plugins/dhcp/dhcp_proxy.h
/**
* @brief Callback function invoked for each DHCP VSS entry
* return 0 to break the walk, non-zero otherwise.
*/
typedef int (*dhcp_vss_walk_fn_t) (dhcp_vss_t * server, /* VSS配置指针 */
/* 说明:dhcp_vss_walk_fn_t是VSS配置遍历回调函数的类型定义。
*
* 函数签名:
* - 参数1:server - VSS配置指针(注意:参数名是server,但实际是VSS配置)
* - 参数2:rx_table_id - 接收Table-ID(外部Table-ID)
* - 参数3:ctx - 回调函数上下文指针
* - 返回值:int - 0表示停止遍历,非0表示继续遍历
*
* 使用场景:
* - 在dhcp_vss_walk函数中调用
* - 对每个VSS配置执行特定操作
* - 例如:显示配置、转储到API等
*
* 返回值:
* - 返回0:停止遍历,不再处理后续配置
* - 返回非0:继续遍历下一个配置 */
u32 rx_table_id, /* 接收Table-ID */
/* 说明:rx_table_id参数是接收Table-ID(外部Table-ID)。
*
* 用途:
* - 标识VSS配置对应的VRF
* - 用于显示和转储
* - 从FIB表中获取 */
void *ctx); /* 回调函数上下文指针 */
/* 说明:ctx参数是回调函数的上下文指针。
*
* 用途:
* - 传递给回调函数
* - 可以包含遍历相关的状态信息 */
VSS配置遍历函数源码详解:
//86:118:src/plugins/dhcp/dhcp_proxy.c
void /* 返回类型:无返回值 */
dhcp_vss_walk (fib_protocol_t proto, /* 协议类型(IPv4或IPv6) */
/* 说明:proto参数指定协议类型。
*
* 用途:
* - 确定使用哪个协议的VSS配置池和索引映射
* - IPv4和IPv6使用独立的配置池 */
dhcp_vss_walk_fn_t fn, /* 回调函数指针 */
/* 说明:fn参数指定回调函数指针。
*
* 回调函数:
* - 对每个VSS配置调用此函数
* - 函数接收VSS配置指针、Table-ID和上下文指针
* - 返回0停止遍历,返回非0继续遍历 */
void *ctx) /* 回调函数上下文指针 */
/* 说明:ctx参数是回调函数的上下文指针。
*
* 用途:
* - 传递给回调函数
* - 可以包含遍历相关的状态信息 */
{
dhcp_proxy_main_t *dpm = &dhcp_proxy_main; /* 获取全局管理器指针 */
/* 说明:dpm变量指向全局的dhcp_proxy_main实例。
*
* 使用方式:
* - 访问VSS索引映射向量
* - 访问VSS配置池 */
mfib_table_t *mfib; /* 多播FIB表指针(IPv6使用) */
/* 说明:mfib变量用于存储IPv6协议的FIB表指针。
*
* 用途:
* - 在IPv6协议时,获取Table-ID */
dhcp_vss_t *vss; /* VSS配置指针 */
/* 说明:vss变量用于存储当前遍历的VSS配置指针。 */
u32 vss_index, i; /* VSS索引和循环变量 */
/* 说明:vss_index变量存储VSS配置在池中的索引。
* i变量用于循环遍历索引映射向量。 */
fib_table_t *fib; /* FIB表指针(IPv4使用) */
/* 说明:fib变量用于存储IPv4协议的FIB表指针。
*
* 用途:
* - 在IPv4协议时,获取Table-ID */
vec_foreach_index (i, dpm->vss_index_by_rx_fib_index[proto]) /* 遍历VSS索引映射向量 */
/* 说明:vec_foreach_index宏用于遍历向量的索引。
*
* 参数1:i
* - 循环变量,从0开始递增
* - 每次循环递增1,直到向量长度
*
* 参数2:dpm->vss_index_by_rx_fib_index[proto]
* - VSS索引映射向量指针
* - proto确定使用IPv4还是IPv6的映射
*
* 功能:
* - 遍历索引映射向量的所有索引
* - i从0到vec_len-1
* - 对于每个索引i,执行循环体 */
{
vss_index = dpm->vss_index_by_rx_fib_index[proto][i]; /* 获取VSS配置索引 */
/* 说明:从索引映射向量中获取VSS配置在池中的索引。
*
* 访问方式:
* - dpm->vss_index_by_rx_fib_index[proto][i]
* - 使用i作为数组索引
* - 获取的值是VSS配置在池中的索引 */
if (~0 == vss_index) /* 如果索引为~0,跳过未配置的项 */
/* 说明:检查索引值是否为~0。
*
* ~0的含义:
* - 所有位都为1(0xFFFFFFFF)
* - 用作"未配置"的标记值
*
* 检查目的:
* - 跳过未配置的VRF
* - 只处理已配置的VSS配置 */
continue; /* 跳过未配置的项 */
/* 说明:continue语句跳过当前循环,继续下一个循环。 */
vss = pool_elt_at_index (dpm->vss[proto], vss_index); /* 从池中获取VSS配置 */
/* 说明:pool_elt_at_index函数从内存池中通过索引获取元素。
*
* 参数1:dpm->vss[proto]
* - VSS配置池指针
* - proto确定使用IPv4还是IPv6的配置池
*
* 参数2:vss_index
* - VSS配置在池中的索引
* - 从索引映射向量中获取
*
* 返回值:
* - 返回VSS配置指针 */
if (FIB_PROTOCOL_IP4 == proto) /* 如果是IPv4协议 */
/* 说明:检查协议类型是否为IPv4。
*
* IPv4处理:
* - 使用fib_table_get获取FIB表
* - 从FIB表中获取Table-ID */
{
fib = fib_table_get (i, proto); /* 获取FIB表指针 */
/* 说明:fib_table_get函数通过FIB索引获取FIB表指针。
*
* 参数1:i
* - FIB索引(与循环变量i相同)
* - 在索引映射向量中,i既是向量索引,也是FIB索引
*
* 参数2:proto
* - 协议类型(IPv4)
*
* 返回值:
* - 返回FIB表指针
* - 用于获取Table-ID */
if (!fn (vss, fib->ft_table_id, ctx)) /* 调用回调函数 */
/* 说明:调用回调函数处理VSS配置。
*
* 参数1:vss
* - VSS配置指针
*
* 参数2:fib->ft_table_id
* - FIB表的Table-ID(外部Table-ID)
* - 用于标识VSS配置对应的VRF
*
* 参数3:ctx
* - 回调函数上下文指针
*
* 返回值:
* - 回调函数返回0:停止遍历
* - 回调函数返回非0:继续遍历 */
break; /* 如果回调函数返回0,停止遍历 */
/* 说明:break语句跳出循环,停止遍历。 */
}
else /* 如果是IPv6协议 */
/* 说明:检查协议类型是否为IPv6。
*
* IPv6处理:
* - 使用mfib_table_get获取多播FIB表
* - 从多播FIB表中获取Table-ID */
{
mfib = mfib_table_get (i, proto); /* 获取多播FIB表指针 */
/* 说明:mfib_table_get函数通过FIB索引获取多播FIB表指针。
*
* 参数1:i
* - FIB索引(与循环变量i相同)
*
* 参数2:proto
* - 协议类型(IPv6)
*
* 返回值:
* - 返回多播FIB表指针
* - 用于获取Table-ID */
if (!fn (vss, mfib->mft_table_id, ctx)) /* 调用回调函数 */
/* 说明:调用回调函数处理VSS配置。
*
* 参数1:vss
* - VSS配置指针
*
* 参数2:mfib->mft_table_id
* - 多播FIB表的Table-ID(外部Table-ID)
* - 用于标识VSS配置对应的VRF
*
* 参数3:ctx
* - 回调函数上下文指针
*
* 返回值:
* - 回调函数返回0:停止遍历
* - 回调函数返回非0:继续遍历 */
break; /* 如果回调函数返回0,停止遍历 */
/* 说明:break语句跳出循环,停止遍历。 */
}
}
}
/* 说明:函数执行完成后,所有VSS配置都已遍历。
*
* 遍历完成后的状态:
* - 所有已配置的VSS配置都已处理
* - 回调函数可能已停止遍历(返回0)
* - 或者所有配置都已处理(回调函数一直返回非0) */
函数流程总结:
dhcp_vss_walk() 函数流程
│
├─→ 1. 初始化变量
│ ├─→ dpm = &dhcp_proxy_main
│ ├─→ vss = NULL
│ └─→ vss_index, i = 0
│
├─→ 2. 遍历VSS索引映射向量
│ └─→ vec_foreach_index(i, dpm->vss_index_by_rx_fib_index[proto])
│
├─→ 3. 获取VSS配置索引
│ └─→ vss_index = dpm->vss_index_by_rx_fib_index[proto][i]
│
├─→ 4. 检查是否已配置
│ ├─→ if (~0 == vss_index) continue(跳过未配置的项)
│ └─→ 否则继续处理
│
├─→ 5. 从池中获取VSS配置
│ └─→ vss = pool_elt_at_index(dpm->vss[proto], vss_index)
│
├─→ 6. 根据协议类型获取Table-ID
│ ├─→ 如果是IPv4:fib = fib_table_get(i, proto), table_id = fib->ft_table_id
│ └─→ 如果是IPv6:mfib = mfib_table_get(i, proto), table_id = mfib->mft_table_id
│
├─→ 7. 调用回调函数
│ ├─→ if (!fn(vss, table_id, ctx)) break(如果返回0,停止遍历)
│ └─→ 否则继续遍历下一个配置
│
└─→ 8. 遍历完成
└─→ 所有已配置的VSS配置都已处理
关键设计特点:
- 统一接口:Proxy配置和VSS配置使用相同的遍历模式
- 灵活回调:支持自定义回调函数,可以用于显示、转储、统计等
- 早期退出:回调函数可以返回0提前停止遍历
- 协议区分:IPv4和IPv6使用独立的配置池和索引映射
6.3.4.3 API支持
功能说明:
API支持功能用于将Proxy配置转储到API客户端。当API客户端请求转储Proxy配置时,VPP会遍历所有配置并通过API消息发送给客户端。这些函数主要用于VPP的API接口,支持外部程序查询Proxy配置。
6.3.4.3.1 Proxy配置转储 - dhcp_proxy_dump()
功能说明:
dhcp_proxy_dump()函数用于转储指定协议的所有Proxy配置到API客户端。函数遍历所有Proxy配置,对每个配置调用dhcp_send_details()函数发送配置详情。
函数输入:
proto:协议类型(IPv4或IPv6)opaque:API注册指针(void *类型,实际是vl_api_registration_t *)context:API上下文(u32类型,用于匹配请求和回复)
函数输出:
- 无返回值(
void类型) - 通过API消息发送所有Proxy配置详情
转储上下文结构体:
//230:235:src/plugins/dhcp/dhcp_proxy.c
typedef struct dhcp4_proxy_dump_walk_ctx_t_
{
fib_protocol_t proto; /* 协议类型 */
/* 说明:proto字段存储协议类型。
*
* 用途:
* - 传递给dhcp_send_details函数
* - 用于确定协议相关的处理 */
void *opaque; /* API注册指针 */
/* 说明:opaque字段存储API注册指针。
*
* 类型:void *(实际是vl_api_registration_t *)
* - 用于发送API消息
* - 标识API客户端 */
u32 context; /* API上下文 */
/* 说明:context字段存储API上下文。
*
* 用途:
* - 用于匹配请求和回复
* - 客户端在请求中设置,在回复中返回
* - 允许客户端关联多个请求和回复 */
} dhcp_proxy_dump_walk_cxt_t; /* Proxy转储遍历上下文类型 */
/* 说明:dhcp_proxy_dump_walk_cxt_t是Proxy转储遍历上下文的结构体类型。
*
* 用途:
* - 在dhcp_proxy_dump函数中创建
* - 传递给dhcp_proxy_walk函数
* - 在回调函数中使用,传递给dhcp_send_details函数 */
转储遍历回调函数:
//237:245:src/plugins/dhcp/dhcp_proxy.c
static int /* 返回类型:整数,0表示停止遍历,非0表示继续遍历 */
dhcp_proxy_dump_walk (dhcp_proxy_t * proxy, /* Proxy配置指针 */
/* 说明:dhcp_proxy_dump_walk函数是Proxy转储遍历的回调函数。
*
* 功能:
* - 对每个Proxy配置调用dhcp_send_details函数
* - 发送配置详情到API客户端
*
* 参数:
* - proxy:Proxy配置指针
* - arg:转储上下文指针(实际是dhcp_proxy_dump_walk_cxt_t *)
*
* 返回值:
* - 返回1,继续遍历下一个配置
* - 如果需要停止遍历,可以返回0 */
void *arg) /* 转储上下文指针 */
/* 说明:arg参数是转储上下文指针。
*
* 类型:void *(实际是dhcp_proxy_dump_walk_cxt_t *)
* - 包含协议类型、API注册指针和上下文 */
{
dhcp_proxy_dump_walk_cxt_t *ctx = arg; /* 转换上下文指针类型 */
/* 说明:将void *指针转换为dhcp_proxy_dump_walk_cxt_t *类型。
*
* 转换原因:
* - 回调函数使用void *类型,提高通用性
* - 需要转换为具体类型才能访问字段 */
dhcp_send_details (ctx->proto, ctx->opaque, ctx->context, proxy); /* 发送配置详情 */
/* 说明:调用dhcp_send_details函数发送Proxy配置详情。
*
* 参数1:ctx->proto
* - 协议类型(IPv4或IPv6)
*
* 参数2:ctx->opaque
* - API注册指针
*
* 参数3:ctx->context
* - API上下文
*
* 参数4:proxy
* - Proxy配置指针
*
* 功能:
* - 构造API消息
* - 填充配置信息
* - 发送消息到API客户端 */
return (1); /* 返回1,继续遍历 */
/* 说明:返回1表示继续遍历下一个配置。
*
* 返回值:
* - 1:继续遍历
* - 0:停止遍历(当前实现总是返回1,遍历所有配置) */
}
Proxy配置转储函数源码详解:
//247:256:src/plugins/dhcp/dhcp_proxy.c
void /* 返回类型:无返回值 */
dhcp_proxy_dump (fib_protocol_t proto, /* 协议类型(IPv4或IPv6) */
/* 说明:dhcp_proxy_dump函数用于转储指定协议的所有Proxy配置。
*
* 功能:
* - 遍历所有Proxy配置
* - 对每个配置发送详情到API客户端
*
* 参数:
* - proto:协议类型
* - opaque:API注册指针
* - context:API上下文 */
void *opaque, /* API注册指针 */
/* 说明:opaque参数是API注册指针。
*
* 类型:void *(实际是vl_api_registration_t *)
* - 用于发送API消息
* - 标识API客户端 */
u32 context) /* API上下文 */
/* 说明:context参数是API上下文。
*
* 用途:
* - 用于匹配请求和回复
* - 客户端在请求中设置,在回复中返回 */
{
dhcp_proxy_dump_walk_cxt_t ctx = { /* 创建转储上下文 */
/* 说明:创建转储上下文结构体。
*
* 初始化方式:
* - 使用结构体初始化语法
* - 按顺序初始化各个字段 */
.proto = proto, /* 设置协议类型 */
/* 说明:将协议类型保存到上下文中。
*
* 用途:
* - 传递给回调函数
* - 用于确定协议相关的处理 */
.opaque = opaque, /* 设置API注册指针 */
/* 说明:将API注册指针保存到上下文中。
*
* 用途:
* - 传递给回调函数
* - 用于发送API消息 */
.context = context, /* 设置API上下文 */
/* 说明:将API上下文保存到上下文中。
*
* 用途:
* - 传递给回调函数
* - 用于匹配请求和回复 */
};
dhcp_proxy_walk (proto, dhcp_proxy_dump_walk, &ctx); /* 遍历Proxy配置 */
/* 说明:调用dhcp_proxy_walk函数遍历所有Proxy配置。
*
* 参数1:proto
* - 协议类型(IPv4或IPv6)
*
* 参数2:dhcp_proxy_dump_walk
* - 回调函数指针
* - 对每个Proxy配置调用此函数
*
* 参数3:&ctx
* - 转储上下文指针
* - 传递给回调函数
*
* 功能:
* - 遍历所有Proxy配置
* - 对每个配置调用dhcp_proxy_dump_walk函数
* - 发送配置详情到API客户端 */
}
/* 说明:函数执行完成后,所有Proxy配置都已转储到API客户端。
*
* 转储完成后的状态:
* - 所有Proxy配置都已发送
* - API客户端收到所有配置详情
* - 每个配置对应一个API消息 */
函数流程总结:
dhcp_proxy_dump() 函数流程
│
├─→ 1. 创建转储上下文
│ ├─→ ctx.proto = proto
│ ├─→ ctx.opaque = opaque
│ └─→ ctx.context = context
│
├─→ 2. 遍历Proxy配置
│ └─→ dhcp_proxy_walk(proto, dhcp_proxy_dump_walk, &ctx)
│
├─→ 3. 对每个配置调用回调函数
│ └─→ dhcp_proxy_dump_walk(proxy, &ctx)
│
├─→ 4. 发送配置详情
│ └─→ dhcp_send_details(ctx->proto, ctx->opaque, ctx->context, proxy)
│
└─→ 5. 转储完成
└─→ 所有Proxy配置都已发送到API客户端
6.3.4.3.2 发送配置详情 - dhcp_send_details()
功能说明:
dhcp_send_details()函数用于构造并发送单个Proxy配置的详情到API客户端。函数分配API消息缓冲区,填充配置信息(包括服务器列表、VSS配置、源地址等),然后发送消息。
函数输入:
proto:协议类型(IPv4或IPv6)opaque:API注册指针(void *类型,实际是vl_api_registration_t *)context:API上下文(u32类型)proxy:Proxy配置指针(dhcp_proxy_t *类型)
函数输出:
- 无返回值(
void类型) - 通过API消息发送Proxy配置详情
发送配置详情函数源码详解:
//173:254:src/plugins/dhcp/dhcp_api.c
void /* 返回类型:无返回值 */
dhcp_send_details (fib_protocol_t proto, /* 协议类型(IPv4或IPv6) */
/* 说明:dhcp_send_details函数用于发送单个Proxy配置的详情到API客户端。
*
* 功能:
* - 分配API消息缓冲区
* - 填充Proxy配置信息
* - 发送消息到API客户端
*
* 参数:
* - proto:协议类型
* - opaque:API注册指针
* - context:API上下文
* - proxy:Proxy配置指针 */
void *opaque, /* API注册指针 */
/* 说明:opaque参数是API注册指针。
*
* 类型:void *(实际是vl_api_registration_t *)
* - 用于发送API消息
* - 标识API客户端 */
u32 context, /* API上下文 */
/* 说明:context参数是API上下文。
*
* 用途:
* - 用于匹配请求和回复
* - 客户端在请求中设置,在回复中返回 */
dhcp_proxy_t * proxy) /* Proxy配置指针 */
/* 说明:proxy参数是Proxy配置指针。
*
* 用途:
* - 包含要发送的配置信息
* - 包括服务器列表、VSS配置、源地址等 */
{
vl_api_dhcp_proxy_details_t *mp; /* API消息指针 */
/* 说明:mp变量用于存储API消息指针。
*
* 类型:vl_api_dhcp_proxy_details_t *
* - DHCP Proxy详情API消息类型
* - 定义在dhcp.api中 */
vl_api_registration_t *reg = opaque; /* 转换API注册指针类型 */
/* 说明:将void *指针转换为vl_api_registration_t *类型。
*
* 转换原因:
* - 函数参数使用void *类型,提高通用性
* - 需要转换为具体类型才能使用 */
vl_api_dhcp_server_t *v_server; /* API服务器结构体指针 */
/* 说明:v_server变量用于存储API服务器结构体指针。
*
* 用途:
* - 在消息中填充服务器信息
* - 每个服务器对应一个vl_api_dhcp_server_t结构体 */
dhcp_server_t *server; /* 内部服务器结构体指针 */
/* 说明:server变量用于存储内部服务器结构体指针。
*
* 用途:
* - 从Proxy配置中获取服务器信息
* - 复制到API消息中 */
fib_table_t *s_fib; /* 服务器FIB表指针 */
/* 说明:s_fib变量用于存储服务器FIB表指针。
*
* 用途:
* - 获取服务器的Table-ID
* - 填充到API消息中 */
dhcp_vss_t *vss; /* VSS配置指针 */
/* 说明:vss变量用于存储VSS配置指针。
*
* 用途:
* - 获取VSS配置信息
* - 填充到API消息中 */
u32 count; /* 服务器数量 */
/* 说明:count变量用于存储服务器数量。
*
* 用途:
* - 计算消息大小
* - 遍历服务器列表 */
size_t n; /* 消息大小 */
/* 说明:n变量用于存储消息大小(字节数)。
*
* 用途:
* - 分配消息缓冲区
* - 包括消息头和服务器列表 */
count = vec_len (proxy->dhcp_servers); /* 获取服务器数量 */
/* 说明:vec_len函数返回向量的长度(元素个数)。
*
* 参数:proxy->dhcp_servers
* - DHCP服务器列表向量
*
* 返回值:
* - 服务器数量(u32类型)
*
* 用途:
* - 计算消息大小
* - 消息大小 = 消息头大小 + 服务器数量 * 服务器结构体大小 */
n = sizeof (*mp) + (count * sizeof (vl_api_dhcp_server_t)); /* 计算消息大小 */
/* 说明:计算API消息的总大小。
*
* 计算方式:
* - sizeof(*mp):消息头大小
* - count * sizeof(vl_api_dhcp_server_t):服务器列表大小
* - 总大小 = 消息头大小 + 服务器列表大小
*
* 消息结构:
* - 消息头:包含协议类型、VRF ID、VSS信息、源地址等
* - 服务器列表:可变长度,每个服务器一个结构体 */
mp = vl_msg_api_alloc (n); /* 分配API消息缓冲区 */
/* 说明:vl_msg_api_alloc函数分配API消息缓冲区。
*
* 参数:n
* - 消息大小(字节数)
*
* 返回值:
* - 返回消息缓冲区指针
* - 如果分配失败,返回NULL
*
* 分配方式:
* - 从API消息池中分配
* - 内存由VPP管理,使用后自动释放 */
if (!mp) /* 如果分配失败 */
/* 说明:检查消息缓冲区是否分配成功。
*
* 失败原因:
* - 内存不足
* - API消息池耗尽 */
return; /* 直接返回,不发送消息 */
/* 说明:如果分配失败,直接返回。
*
* 返回原因:
* - 无法发送消息
* - 避免后续操作访问空指针 */
clib_memset (mp, 0, n); /* 清零消息缓冲区 */
/* 说明:clib_memset函数将消息缓冲区清零。
*
* 参数1:mp
* - 消息缓冲区指针
*
* 参数2:0
* - 填充值(字节值)
*
* 参数3:n
* - 要清零的字节数
*
* 功能:
* - 将消息缓冲区所有字节设置为0
* - 确保未使用的字段为0
* - 避免未初始化的数据 */
mp->_vl_msg_id = ntohs (VL_API_DHCP_PROXY_DETAILS + REPLY_MSG_ID_BASE); /* 设置消息ID */
/* 说明:设置API消息的消息ID。
*
* 消息ID:
* - VL_API_DHCP_PROXY_DETAILS:基础消息ID
* - REPLY_MSG_ID_BASE:回复消息ID基址
* - 使用ntohs转换为网络字节序
*
* 用途:
* - 标识消息类型
* - API客户端根据消息ID处理消息 */
mp->context = context; /* 设置API上下文 */
/* 说明:将API上下文保存到消息中。
*
* 用途:
* - 客户端用于匹配请求和回复
* - 客户端在请求中设置,在回复中返回 */
mp->count = count; /* 设置服务器数量 */
/* 说明:将服务器数量保存到消息中。
*
* 用途:
* - 客户端知道有多少个服务器
* - 用于解析服务器列表 */
mp->is_ipv6 = (proto == FIB_PROTOCOL_IP6); /* 设置协议类型标志 */
/* 说明:设置协议类型标志。
*
* 值:
* - 如果proto == FIB_PROTOCOL_IP6:is_ipv6 = 1(IPv6)
* - 否则:is_ipv6 = 0(IPv4)
*
* 用途:
* - 客户端知道是IPv4还是IPv6配置
* - 用于解析地址字段 */
mp->rx_vrf_id = /* 设置接收VRF ID */
/* 说明:设置接收VRF ID(外部Table-ID)。
*
* 获取方式:
* - 通过dhcp_proxy_rx_table_get_table_id函数
* - 将FIB索引转换为Table-ID
*
* 转换:
* - 使用htonl转换为网络字节序 */
htonl (dhcp_proxy_rx_table_get_table_id (proto, proxy->rx_fib_index)); /* 获取并转换Table-ID */
/* 说明:dhcp_proxy_rx_table_get_table_id函数获取Table-ID。
*
* 参数1:proto
* - 协议类型
*
* 参数2:proxy->rx_fib_index
* - 接收FIB索引
*
* 返回值:
* - Table-ID(外部Table-ID)
*
* 转换:
* - 使用htonl转换为网络字节序 */
vss = dhcp_get_vss_info (&dhcp_proxy_main, proxy->rx_fib_index, proto); /* 获取VSS配置 */
/* 说明:调用dhcp_get_vss_info函数获取VSS配置。
*
* 参数1:&dhcp_proxy_main
* - 全局管理器指针
*
* 参数2:proxy->rx_fib_index
* - 接收FIB索引
*
* 参数3:proto
* - 协议类型
*
* 返回值:
* - VSS配置指针,如果未配置则返回NULL */
if (vss) /* 如果VSS配置存在 */
/* 说明:检查是否配置了VSS。
*
* 条件:
* - vss != NULL:已配置VSS
* - vss == NULL:未配置VSS */
{
mp->vss_type = ntohl (vss->vss_type); /* 设置VSS类型 */
/* 说明:将VSS类型保存到消息中。
*
* 类型:
* - VSS_TYPE_ASCII:ASCII VPN标识符
* - VSS_TYPE_VPN_ID:VPN-ID
* - VSS_TYPE_DEFAULT:默认VPN
*
* 转换:
* - 使用ntohl转换为网络字节序 */
if (vss->vss_type == VSS_TYPE_ASCII) /* 如果是ASCII类型 */
/* 说明:检查VSS类型是否为ASCII。
*
* ASCII类型:
* - 使用ASCII字符串作为VPN标识符
* - 存储在vpn_ascii_id字段中 */
{
u32 id_len = vec_len (vss->vpn_ascii_id); /* 获取ASCII标识符长度 */
/* 说明:获取ASCII标识符的长度。
*
* 长度:
* - 不包括结束符
* - 用于确定复制多少字节 */
clib_memcpy (mp->vss_vpn_ascii_id, vss->vpn_ascii_id, id_len); /* 复制ASCII标识符 */
/* 说明:将ASCII标识符复制到消息中。
*
* 参数1:mp->vss_vpn_ascii_id
* - 目标缓冲区(消息中的字段)
*
* 参数2:vss->vpn_ascii_id
* - 源缓冲区(VSS配置中的字段)
*
* 参数3:id_len
* - 复制的字节数
*
* 功能:
* - 将ASCII标识符复制到消息中
* - 客户端可以读取VPN标识符 */
}
else if (vss->vss_type == VSS_TYPE_VPN_ID) /* 如果是VPN-ID类型 */
/* 说明:检查VSS类型是否为VPN-ID。
*
* VPN-ID类型:
* - 使用RFC 2685定义的VPN-ID格式
* - 7字节:3字节OUI + 4字节VPN索引 */
{
u32 oui = ((u32) vss->vpn_id[0] << 16) + ((u32) vss->vpn_id[1] << 8) /* 提取OUI */
/* 说明:从VPN-ID中提取OUI(组织唯一标识符)。
*
* 提取方式:
* - vpn_id[0]:OUI字节0(最高字节)
* - vpn_id[1]:OUI字节1(中间字节)
* - vpn_id[2]:OUI字节2(最低字节)
*
* 组合:
* - oui = (vpn_id[0] << 16) | (vpn_id[1] << 8) | vpn_id[2]
* - 将3个字节组合成24位值 */
+ ((u32) vss->vpn_id[2]);
u32 fib_id = ((u32) vss->vpn_id[3] << 24) + ((u32) vss->vpn_id[4] << 16) /* 提取VPN索引 */
/* 说明:从VPN-ID中提取VPN索引。
*
* 提取方式:
* - vpn_id[3]:VPN索引字节0(最高字节)
* - vpn_id[4]:VPN索引字节1
* - vpn_id[5]:VPN索引字节2
* - vpn_id[6]:VPN索引字节3(最低字节)
*
* 组合:
* - fib_id = (vpn_id[3] << 24) | (vpn_id[4] << 16) | (vpn_id[5] << 8) | vpn_id[6]
* - 将4个字节组合成32位值 */
+ ((u32) vss->vpn_id[5] << 8) + ((u32) vss->vpn_id[6]);
mp->vss_oui = htonl (oui); /* 设置OUI */
/* 说明:将OUI保存到消息中。
*
* 转换:
* - 使用htonl转换为网络字节序 */
mp->vss_fib_id = htonl (fib_id); /* 设置VPN索引 */
/* 说明:将VPN索引保存到消息中。
*
* 转换:
* - 使用htonl转换为网络字节序 */
}
}
else /* 如果VSS配置不存在 */
/* 说明:检查是否未配置VSS。
*
* 条件:
* - vss == NULL:未配置VSS */
mp->vss_type = VSS_TYPE_INVALID; /* 设置VSS类型为无效 */
/* 说明:将VSS类型设置为无效。
*
* 值:VSS_TYPE_INVALID
* - 表示未配置VSS
*
* 用途:
* - 客户端知道未配置VSS
* - 不需要处理VSS信息 */
vec_foreach_index (count, proxy->dhcp_servers) /* 遍历服务器列表 */
/* 说明:遍历Proxy配置中的服务器列表。
*
* 参数1:count
* - 循环变量,从0开始递增
*
* 参数2:proxy->dhcp_servers
* - 服务器列表向量
*
* 功能:
* - 对每个服务器填充API消息
* - count从0到vec_len-1 */
{
server = &proxy->dhcp_servers[count]; /* 获取服务器配置 */
/* 说明:从向量中获取服务器配置。
*
* 访问方式:
* - proxy->dhcp_servers[count]
* - 使用count作为数组索引
*
* 返回值:
* - 服务器配置指针(dhcp_server_t *) */
v_server = &mp->servers[count]; /* 获取API服务器结构体 */
/* 说明:从消息中获取API服务器结构体。
*
* 访问方式:
* - mp->servers[count]
* - 使用count作为数组索引
*
* 用途:
* - 填充服务器信息
* - 每个服务器对应一个结构体 */
s_fib = fib_table_get (server->server_fib_index, proto); /* 获取服务器FIB表 */
/* 说明:通过FIB索引获取FIB表指针。
*
* 参数1:server->server_fib_index
* - 服务器FIB索引
*
* 参数2:proto
* - 协议类型
*
* 返回值:
* - FIB表指针
* - 用于获取Table-ID */
v_server->server_vrf_id = htonl (s_fib->ft_table_id); /* 设置服务器VRF ID */
/* 说明:将服务器VRF ID保存到消息中。
*
* 获取方式:
* - 从FIB表中获取Table-ID
* - 使用htonl转换为网络字节序 */
if (mp->is_ipv6) /* 如果是IPv6协议 */
/* 说明:检查协议类型是否为IPv6。
*
* IPv6处理:
* - 地址长度为16字节
* - 使用memcpy复制地址 */
{
memcpy (&v_server->dhcp_server.un, &server->dhcp_server.ip6, 16); /* 复制IPv6地址 */
/* 说明:将IPv6地址复制到消息中。
*
* 参数1:&v_server->dhcp_server.un
* - 目标缓冲区(消息中的联合体字段)
*
* 参数2:&server->dhcp_server.ip6
* - 源缓冲区(服务器配置中的IPv6地址)
*
* 参数3:16
* - 复制的字节数(IPv6地址长度)
*
* 功能:
* - 将IPv6地址复制到消息中 */
}
else /* 如果是IPv4协议 */
/* 说明:检查协议类型是否为IPv4。
*
* IPv4处理:
* - 地址长度为4字节
* - 使用memcpy复制地址 */
{
/* put the address in the first bytes */
memcpy (&v_server->dhcp_server.un, &server->dhcp_server.ip4, 4); /* 复制IPv4地址 */
/* 说明:将IPv4地址复制到消息中。
*
* 参数1:&v_server->dhcp_server.un
* - 目标缓冲区(消息中的联合体字段)
*
* 参数2:&server->dhcp_server.ip4
* - 源缓冲区(服务器配置中的IPv4地址)
*
* 参数3:4
* - 复制的字节数(IPv4地址长度)
*
* 功能:
* - 将IPv4地址复制到消息中
* - 地址存储在联合体的前4个字节中 */
}
}
if (mp->is_ipv6) /* 如果是IPv6协议 */
/* 说明:检查协议类型是否为IPv6。
*
* IPv6处理:
* - 源地址长度为16字节
* - 使用memcpy复制地址 */
{
memcpy (&mp->dhcp_src_address.un, &proxy->dhcp_src_address.ip6, 16); /* 复制IPv6源地址 */
/* 说明:将IPv6源地址复制到消息中。
*
* 参数1:&mp->dhcp_src_address.un
* - 目标缓冲区(消息中的联合体字段)
*
* 参数2:&proxy->dhcp_src_address.ip6
* - 源缓冲区(Proxy配置中的IPv6源地址)
*
* 参数3:16
* - 复制的字节数(IPv6地址长度) */
}
else /* 如果是IPv4协议 */
/* 说明:检查协议类型是否为IPv4。
*
* IPv4处理:
* - 源地址长度为4字节
* - 使用memcpy复制地址 */
{
/* put the address in the first bytes */
memcpy (&mp->dhcp_src_address.un, &proxy->dhcp_src_address.ip4, 4); /* 复制IPv4源地址 */
/* 说明:将IPv4源地址复制到消息中。
*
* 参数1:&mp->dhcp_src_address.un
* - 目标缓冲区(消息中的联合体字段)
*
* 参数2:&proxy->dhcp_src_address.ip4
* - 源缓冲区(Proxy配置中的IPv4源地址)
*
* 参数3:4
* - 复制的字节数(IPv4地址长度) */
}
vl_api_send_msg (reg, (u8 *) mp); /* 发送API消息 */
/* 说明:vl_api_send_msg函数发送API消息到客户端。
*
* 参数1:reg
* - API注册指针
* - 标识API客户端
*
* 参数2:(u8 *) mp
* - 消息缓冲区指针
* - 转换为u8 *类型
*
* 功能:
* - 将消息发送到API客户端
* - 消息由VPP管理,使用后自动释放 */
}
/* 说明:函数执行完成后,Proxy配置详情已发送到API客户端。
*
* 发送完成后的状态:
* - API消息已构造并发送
* - 客户端收到配置详情
* - 消息缓冲区由VPP管理,使用后自动释放 */
函数流程总结:
dhcp_send_details() 函数流程
│
├─→ 1. 初始化变量
│ ├─→ reg = opaque(转换API注册指针类型)
│ ├─→ count = vec_len(proxy->dhcp_servers)
│ └─→ n = sizeof(*mp) + count * sizeof(vl_api_dhcp_server_t)
│
├─→ 2. 分配消息缓冲区
│ ├─→ mp = vl_msg_api_alloc(n)
│ └─→ if (!mp) return(分配失败,直接返回)
│
├─→ 3. 初始化消息
│ ├─→ clib_memset(mp, 0, n)
│ ├─→ mp->_vl_msg_id = ntohs(VL_API_DHCP_PROXY_DETAILS + REPLY_MSG_ID_BASE)
│ ├─→ mp->context = context
│ ├─→ mp->count = count
│ └─→ mp->is_ipv6 = (proto == FIB_PROTOCOL_IP6)
│
├─→ 4. 填充基本配置信息
│ ├─→ mp->rx_vrf_id = htonl(dhcp_proxy_rx_table_get_table_id(...))
│ └─→ vss = dhcp_get_vss_info(...)
│
├─→ 5. 填充VSS配置信息
│ ├─→ if (vss)
│ │ ├─→ mp->vss_type = ntohl(vss->vss_type)
│ │ ├─→ 如果是ASCII:复制vpn_ascii_id
│ │ └─→ 如果是VPN-ID:提取oui和fib_id
│ └─→ else:mp->vss_type = VSS_TYPE_INVALID
│
├─→ 6. 填充服务器列表
│ └─→ vec_foreach_index(count, proxy->dhcp_servers)
│ ├─→ server = &proxy->dhcp_servers[count]
│ ├─→ v_server = &mp->servers[count]
│ ├─→ s_fib = fib_table_get(server->server_fib_index, proto)
│ ├─→ v_server->server_vrf_id = htonl(s_fib->ft_table_id)
│ └─→ 复制服务器地址(IPv4或IPv6)
│
├─→ 7. 填充源地址
│ └─→ 复制源地址(IPv4或IPv6)
│
└─→ 8. 发送消息
└─→ vl_api_send_msg(reg, (u8 *) mp)
关键设计特点:
- 动态消息大小:根据服务器数量动态计算消息大小
- 协议区分:IPv4和IPv6使用不同的地址处理方式
- 完整信息:包含所有配置信息(服务器列表、VSS、源地址等)
- 网络字节序:所有多字节字段都转换为网络字节序
6.3.4节总结:
本节详细讲解了DHCP Proxy的辅助功能,包括:
-
UDP端口注册(
dhcp_maybe_register_udp_ports):- 注册DHCP客户端和服务器UDP端口
- 避免重复注册,提高性能
- 确保Proxy功能正常工作
-
配置查询和遍历:
dhcp_proxy_walk:遍历所有Proxy配置dhcp_vss_walk:遍历所有VSS配置- 支持自定义回调函数,灵活处理配置
-
API支持:
dhcp_proxy_dump:转储所有Proxy配置到API客户端dhcp_send_details:发送单个Proxy配置详情- 支持IPv4和IPv6协议
所有辅助功能都正确管理资源,支持IPv4和IPv6协议,为Proxy提供了完整的配置管理和查询能力。
206

被折叠的 条评论
为什么被折叠?



