rinetd 端口转发工具技术原理
Refer :
deepwiki.com@liulilittle/rinetd
github.com@liulilittle/rinetd
一、工具概述
rinetd 是一款基于 C++ 开发的高性能端口转发工具,其核心设计理念围绕简洁性 和高效性展开。工具的主要功能是在不同网络端点之间透明地转发 TCP 和 UDP 流量,实现网络连接的桥接与重定向。
1.1 设计原则
rinetd设计原则
单进程架构
事件驱动模型
零拷贝转发
资源最小化
无锁并发
减少上下文切换
简化进程管理
异步I/O
回调机制
内存重用
缓冲区共享
固定内存分配
静态缓冲区
单线程处理
无竞争访问
二、系统组件
2.1 整体视图
rinetd 采用分层架构设计,各组件职责明确,耦合度低:
支持层
网络层
核心层
应用层
主程序入口
配置加载器
信号处理器
规则解析器
TCP转发服务
UDP转发服务
连接管理器
隧道管理器
Boost.Asio封装
原生Socket操作
日志系统
内存分配器
工具函数集
2.2 数据结构
2.2.1 网络地址表示
cpp
typedef struct {
bool bv6; // IPv6标识
uint32_t in4; // IPv4地址(网络字节序)
uint8_t in6[16]; // IPv6地址
} ip_address;
这种设计实现了IPv4/IPv6的统一表示 ,通过bv6字段区分地址类型,in4和in6共用内存空间,体现了C语言联合体的设计思想但更安全。
2.2.2 转发规则配置
cpp
typedef struct {
bool tcp_or_udp; // 协议类型:true=TCP, false=UDP
ip_address local_host; // 本地监听地址
uint16_t local_port; // 本地监听端口
ip_address remote_host; // 远程目标地址
uint16_t remote_port; // 远程目标端口
} listen_port;
typedef struct {
std::vector<listen_port> listen_ports; // 转发规则列表
std::string log_var; // 日志文件路径
} rinetd_config;
配置结构体现了声明式编程思想,用户只需声明"什么"而不需关心"如何",系统自动处理转发逻辑。
三、配置系统实现原理
3.1 配置加载流程
配置存储 规则解析器 文件系统 路径解析器 命令行参数 配置存储 规则解析器 文件系统 路径解析器 命令行参数 阶段一:配置文件定位 alt [指定配置文件] [未指定配置文件] 阶段二:配置解析 alt [TCP转发规则] [UDP转发规则] [日志配置] 阶段三:配置验证与存储 传递argc, argv 解析-c/--conf-file参数 读取指定文件 生成默认路径 读取默认配置文件 提供配置文件内容 逐行解析 移除注释和空白 解析四元组:本地地址 端口/tcp 远程地址 端口/tcp 解析四元组:本地地址 端口/udp 远程地址 端口/udp 解析logfile路径 验证端口范围(1-65535) 验证IP地址有效性 存储有效规则 存储日志路径
3.2 配置解析实现
cpp
static bool parse_config(std::vector<listen_port>& out_, std::string& log_path_,
const std::string& config_str) noexcept {
// 第一阶段:文本预处理
std::vector<std::string> lines_;
Tokenize<std::string>(config_str, lines_, "\r\n");
for (size_t i = 0; i < lines_.size(); i++) {
std::string& line_ = lines_[i];
// 注释处理策略
size_t comment_pos = line_.find_first_of('#');
if (comment_pos != std::string::npos) {
if (comment_pos == 0) continue; // 整行注释
line_ = line_.substr(0, comment_pos); // 行内注释
}
// 空白字符规范化
line_ = RTrim(LTrim(line_));
if (line_.empty()) continue;
// 第二阶段:模式匹配
char local_host[128], remote_host[128];
uint32_t local_port = 0, remote_port = 0;
bool tcp_or_udp = false;
// 协议类型识别算法
line_ = ToLower(line_);
if (sscanf(line_.data(), "%s %u/tcp %s %u/tcp",
local_host, &local_port, remote_host, &remote_port) >= 4) {
tcp_or_udp = true; // TCP协议
}
else if (sscanf(line_.data(), "%s %u/udp %s %u/udp",
local_host, &local_port, remote_host, &remote_port) >= 4) {
tcp_or_udp = false; // UDP协议
}
else if (sscanf(line_.data(), "logfile %s", local_host) >= 1) {
// 日志配置处理
log_path_ = std::string(local_host, strlen(local_host));
continue;
}
else {
continue; // 未知格式
}
// 第三阶段:语义验证
if ((local_port == 0 || local_port > 65535) ||
(remote_port == 0 || remote_port > 65535)) {
continue; // 端口范围无效
}
// 地址解析与转换
listen_port rule_;
if (!parse_address(rule_.local_host, local_host) ||
!parse_address(rule_.remote_host, remote_host)) {
continue; // 地址格式无效
}
// 规则组装
rule_.tcp_or_udp = tcp_or_udp;
rule_.local_port = local_port;
rule_.remote_port = remote_port;
out_.push_back(std::move(rule_));
}
return !out_.empty(); // 至少有一条有效规则
}
四、TCP 转发
4.1 TCP 连接管理
创建
引用
tcp_forward
-context_: io_context&
-config_: rinetd_config&
-forward_: listen_port&
-server_: ip::tcp::acceptor
-log_: stream_descriptor_ptr
+run() : bool
-accept_socket() : bool
tcp_connection
-forward_: tcp_forward_ptr
-timeout_: deadline_timer
-local_socket_: tcp::socket_ptr
-remote_socket_: tcp::socket
-local_buf[65536]: char
-remote_buf[65536]: char
+run() : bool
-socket_to_destination() : bool
-wirte_log(int) : void
-abort() : void
4.2 TCP 连接生命周期
4.2.1 连接建立过程
cpp
// tcp_forward::accept_socket() - 监听循环
bool accept_socket() noexcept {
std::shared_ptr<boost::asio::ip::tcp::socket> socket =
make_shared_object<boost::asio::ip::tcp::socket>(context_);
server_.async_accept(*socket.get(),
[self, this, socket](boost::system::error_code ec_) noexcept {
// 错误处理策略
if (ec_ == boost::system::errc::operation_canceled) {
return; // 正常取消操作
}
if (ec_) {
close_socket(*socket.get());
} else {
// 连接处理器创建
std::shared_ptr<tcp_connection> connection_ =
make_shared_object<tcp_connection>(self, socket);
if (!connection_->run()) {
connection_->abort();
}
}
// 继续监听新连接
accept_socket();
});
return true;
}
4.2.2 数据双向转发
cpp
// tcp_connection::socket_to_destination() - 零拷贝转发
bool socket_to_destination(
boost::asio::ip::tcp::socket* socket,
boost::asio::ip::tcp::socket* to,
char* buf) noexcept {
// 异步接收数据
socket->async_receive(boost::asio::buffer(buf, RINETD_BUFFER_SIZE),
[self, this, socket, to, buf](const boost::system::error_code& ec, uint32_t sz) noexcept {
// 传输错误检测
int by = std::max<int>(-1, ec ? -1 : sz);
if (by < 1) {
abort(); // 连接异常终止
return;
}
// 异步发送数据
boost::asio::async_write(*to, boost::asio::buffer(buf, sz),
[self, this, socket, to, buf](const boost::system::error_code& ec, uint32_t sz) noexcept {
if (ec) {
abort(); // 发送失败
} else {
// 继续下一轮数据接收
socket_to_destination(socket, to, buf);
}
});
});
return true;
}
4.3 连接超时控制机制
cpp
// tcp_connection::run() - 超时控制实现
bool run() noexcept {
// 连接超时定时器设置
timeout_.expires_from_now(boost::posix_time::seconds(RINETD_TCP_CONNECT_TIMEOUT));
timeout_.async_wait([self, this](const boost::system::error_code& ec) noexcept {
if (ec != boost::system::errc::operation_canceled) {
abort(); // 超时强制终止
}
});
// 异步连接远程端点
remote_socket_.async_connect(connectEP,
[self, this](boost::system::error_code ec_) noexcept {
if (ec_) {
abort(); // 连接失败
return;
}
// 连接成功,取消超时定时器
try {
boost::system::error_code ec;
timeout_.cancel(ec);
} catch (std::exception&) {}
// 启动双向数据转发
socket_to_destination(local_socket_.get(), &remote_socket_, local_socket_buf);
socket_to_destination(&remote_socket_, local_socket_.get(), remote_socket_buf);
// 记录连接日志
if (forward_->log_ || !forward_->config_.log_var.empty()) {
wirte_log(2); // 连接建立日志
}
});
return true;
}
五、UDP 转发
5.1 UDP 处理模型
UDP 转发采用单线程线性处理模型,这一设计的根本原因在于UDP协议的特性和内存访问模式:
5.1.1 内存访问模式
cpp
// UDP转发内存布局
class udp_forward {
private:
char* buf_; // 关键设计:单缓冲区
// ...
public:
inline udp_forward(...) noexcept : ... {
static char s_buf_[UINT16_MAX]; // 静态全局缓冲区
buf_ = s_buf_; // 所有实例共享同一缓冲区
}
};
设计原理:
- 内存局部性优化:单个缓冲区确保所有UDP数据包在缓存中连续存储
- 零内存竞争:单线程访问消除缓存行伪共享问题
- 确定性内存访问:可预测的内存访问模式提高CPU流水线效率
5.1.2 协议特性适配
UDP协议的无连接特性决定了其适合单线程处理:
线性处理优势
新会话
现有会话
UDP数据包
接收队列
包处理决策
创建隧道
复用隧道
隧道映射表
发送队列
目标服务器
无锁操作
顺序处理
状态一致
5.2 UDP 隧道管理机制
5.2.1 隧道数据结构
cpp
class udp_tunnel {
private:
std::shared_ptr<udp_forward> owner_;
boost::asio::ip::udp::socket socket_; // 独立出站socket
boost::asio::ip::udp::endpoint local_ep_; // 客户端端点标识
uint64_t last_ts_; // 最后活动时间戳
};
设计特点:
- 端点隔离:每个客户端有独立的出站socket
- 状态保持:维护客户端到目标的映射关系
- 时间跟踪:支持会话超时清理
5.2.2 隧道生命周期管理
收到首包
隧道初始化
收发数据包
更新活动时间
定期检查
超时(72秒)
未超时
空闲状态
创建隧道
活动状态
数据交换
超时检测
清理隧道
5.3 UDP 线性处理
5.3.1 主接收循环
cpp
// udp_forward::accept_socket() - 单线程事件循环
void accept_socket() noexcept {
socket_.async_receive_from(
boost::asio::buffer(buf_, UINT16_MAX), // 使用共享缓冲区
udp_ep_, // 发送方端点信息
[self, this](const boost::system::error_code& ec, uint32_t sz) noexcept {
// 错误处理
int by = std::max<int>(-1, ec ? -1 : sz);
if (by < 1) {
// 错误处理但不中断循环
accept_socket(); // 继续处理下一个包
return;
}
// 隧道查找或创建
std::shared_ptr<udp_tunnel> tunnel_ = get_or_add_tunnel(udp_ep_);
if (tunnel_) {
// 通过隧道转发数据
tunnel_->send_to(buf_, by);
}
// 立即继续处理下一个数据包
accept_socket();
});
}
5.3.2 隧道查找算法
cpp
std::shared_ptr<udp_tunnel> get_or_add_tunnel(
boost::asio::ip::udp::endpoint& endpoint_) noexcept {
// 生成会话键:端点地址:端口
std::string key = to_address(endpoint_);
// 哈希表查找(O(1)复杂度)
udp_tunnel_map::iterator it = tunnel_map_.find(key);
if (it != tunnel_map_.end()) {
return it->second; // 返回现有隧道
}
// 创建新隧道
std::shared_ptr<udp_tunnel> tunnel_ =
make_shared_object<udp_tunnel>(self_, endpoint_);
if (!tunnel_->run()) {
return NULL; // 隧道初始化失败
}
// 插入隧道映射表
tunnel_map_.insert(std::make_pair(key, tunnel_));
return tunnel_;
}
5.4 UDP 超时清理机制
5.4.1 定时检查算法
cpp
// udp_forward::check_timer() - 定期清理
void check_timer() noexcept {
// 设置10秒检查间隔
check_timer_.expires_from_now(boost::posix_time::seconds(10));
check_timer_.async_wait([self, this](const boost::system::error_code& ec) noexcept {
// 执行清理操作
next_tick(GetTickCount(false));
// 重新设置定时器
check_timer();
});
}
// 隧道老化检测算法
inline bool is_port_aging(uint64_t now) noexcept {
// 时间回滚保护
if (last_ts_ > now || !socket_.is_open()) {
return true;
}
// 计算空闲时间(秒)
uint64_t delta_ts_ = (now - last_ts_) / 1000;
// 72秒超时阈值
return delta_ts_ >= RINETD_DEFAULT_UDP_TIMEOUT;
}
5.4.2 批量清理策略
cpp
void next_tick(uint64_t now) noexcept {
std::vector<std::string> releases;
// 第一阶段:识别待清理隧道
udp_tunnel_map::iterator tail = tunnel_map_.begin();
udp_tunnel_map::iterator endl = tunnel_map_.end();
for (; tail != endl; tail++) {
std::shared_ptr<udp_tunnel> tunnel_ = tail->second;
if (!tunnel_ || tunnel_->is_port_aging(now)) {
releases.push_back(tail->first);
}
}
// 第二阶段:批量清理
for (size_t i = 0, l = releases.size(); i < l; i++) {
tail = tunnel_map_.find(std::move(releases[i]));
if (tail != endl) {
std::shared_ptr<udp_tunnel> tunnel_ = std::move(tail->second);
if (tunnel_) {
tunnel_->abort(); // 释放资源
}
tunnel_map_.erase(tail); // 移除映射
}
}
}
六、网络栈优化
6.1 Socket 选项配置
cpp
void syssocket_setsockopt(int sockfd, bool v4_or_v6) noexcept {
if (sockfd != -1) {
// 服务质量标记:AF31(保证转发类)
uint8_t tos = 0x68;
if (v4_or_v6) {
::setsockopt(sockfd, SOL_IP, IP_TOS, (char*)&tos, sizeof(tos));
// 路径MTU发现配置
#ifdef _WIN32
int dont_frag = 0;
::setsockopt(sockfd, IPPROTO_IP, IP_DONTFRAGMENT,
(char*)&dont_frag, sizeof(dont_frag));
#elif IP_MTU_DISCOVER
int dont_frag = IP_PMTUDISC_WANT; // 希望但不强制
::setsockopt(sockfd, IPPROTO_IP, IP_MTU_DISCOVER,
&dont_frag, sizeof(dont_frag));
#endif
} else {
// IPv6类似配置
::setsockopt(sockfd, SOL_IPV6, IP_TOS, (char*)&tos, sizeof(tos));
#ifdef _WIN32
int dont_frag = 0;
::setsockopt(sockfd, IPPROTO_IPV6, IP_DONTFRAGMENT,
(char*)&dont_frag, sizeof(dont_frag));
#elif IPV6_MTU_DISCOVER
int dont_frag = IPV6_PMTUDISC_WANT;
::setsockopt(sockfd, IPPROTO_IPV6, IPV6_MTU_DISCOVER,
&dont_frag, sizeof(dont_frag));
#endif
}
// 信号管道抑制(Unix-like系统)
#ifdef SO_NOSIGPIPE
int no_sigpipe = 1;
::setsockopt(sockfd, SOL_SOCKET, SO_NOSIGPIPE,
&no_sigpipe, sizeof(no_sigpipe));
#endif
}
}
6.2 TCP快速打开优化
cpp
// TCP_FASTOPEN选项启用
boost::system::error_code ec;
remote_socket_.set_option(
boost::asio::detail::socket_option::boolean<IPPROTO_TCP, TCP_FASTOPEN>(true),
ec);
七、内存管理
7.1 自定义内存分配器
cpp
// 内存分配策略选择
#ifdef JEMALLOC
return (void*)::je_malloc(size_); // jemalloc内存分配器
#else
return (void*)::malloc(size_); // 系统默认分配器
#endif
7.2 智能内存包装器
cpp
template<typename T>
inline std::shared_ptr<T> make_shared_alloc(int length) noexcept {
static_assert(sizeof(T) > 0, "不能创建不完整类型的指针");
if (length < 1) {
return NULL;
}
// 自定义分配内存
T* p = (T*)Malloc(length * sizeof(T));
// 使用自定义释放器
return std::shared_ptr<T>(p, Mfree);
}
template<typename T, typename... A>
inline std::shared_ptr<T> make_shared_object(A&&... args) noexcept {
static_assert(sizeof(T) > 0, "不能创建不完整类型的指针");
// 分配原始内存
T* p = (T*)Malloc(sizeof(T));
// 原位构造对象
return std::shared_ptr<T>(
new (p) T(std::forward<A&&>(args)...), // 完美转发参数
[](T* p) noexcept {
p->~T(); // 显式析构
Mfree(p); // 自定义释放
});
}
八、日志实现
8.1 日志记录
文件路径存在
流描述符有效
日志请求
日志目标选择
文件日志记录器
异步流日志记录器
打开日志文件
追加时间戳
写入消息内容
刷新缓冲区
构建消息字符串
异步写入操作
自动内存管理
8.2 日志写入
cpp
// 文件日志写入器
bool write_log(const std::string& path_, const std::string& msg_) noexcept {
if (path_.empty() || msg_.empty()) {
return false;
}
// 打开日志文件(追加模式)
FILE* f = fopen(path_.data(), "ab+");
if (!f) {
return false;
}
// 构建完整日志行
std::string line_("[" + GetCurrentTimeText<std::string>() + "]" + msg_ + "\r\n");
// 原子写入操作
fwrite(line_.data(), 1, line_.size(), f);
fflush(f); // 强制刷新缓冲区
fclose(f);
return true;
}
// 异步流日志写入器
bool write_log(boost::asio::posix::stream_descriptor& log_,
const std::string& msg_) noexcept {
if (!log_.is_open() || msg_.empty()) {
return false;
}
// 构建日志消息(智能指针管理生命周期)
std::shared_ptr<std::string> line_ =
make_shared_object<std::string>("[" + GetCurrentTimeText<std::string>() + "]" + msg_ + "\r\n");
// 异步写入操作
log_.async_write_some(
boost::asio::buffer(line_->data(), line_->size()),
[line_](const boost::system::error_code& ec, std::size_t sz) noexcept {
// 回调函数持有line_引用,确保写入期间内存有效
});
return true;
}
九、跨平台适配
9.1 平台抽象层
cpp
// 平台相关类型定义
#ifdef _WIN32
namespace boost {
namespace asio {
namespace posix {
// Windows平台使用stream_handle模拟stream_descriptor
typedef boost::asio::windows::stream_handle stream_descriptor;
}
}
}
#include <WinSock2.h>
#else
namespace boost {
namespace asio {
// Unix-like平台使用标准io_context
typedef io_service io_context;
}
}
#endif
9.2 文件句柄
cpp
std::shared_ptr<boost::asio::posix::stream_descriptor>
open_log(boost::asio::io_context& context_, const std::string& path_) noexcept {
if (path_.empty()) {
return NULL;
}
#ifdef _WIN32
// Windows平台实现
HANDLE handle_ = CreateFileA(path_.data(),
GENERIC_READ | GENERIC_WRITE,
FILE_SHARE_READ | FILE_SHARE_WRITE,
NULL,
OPEN_ALWAYS, // 总是打开(不存在则创建)
FILE_FLAG_OVERLAPPED, // 重叠I/O(异步)
NULL);
if (handle_ == INVALID_HANDLE_VALUE) {
return NULL;
}
// 定位到文件末尾
LARGE_INTEGER li_ = { 0 };
SetFilePointer(handle_, li_.LowPart, &li_.HighPart, FILE_END);
return make_shared_object<boost::asio::posix::stream_descriptor>(
context_, handle_);
#else
// Unix-like平台实现
int handle_ = open(path_.data(), O_RDWR | O_CREAT, S_IRWXU);
if (handle_ == -1) {
return NULL;
}
// 定位到文件末尾
lseek64(handle_, 0, SEEK_END);
return make_shared_object<boost::asio::posix::stream_descriptor>(
context_, handle_);
#endif
}
十、工具函数库
10.1 字符串处理工具集
cpp
// 安全字符串比较函数
inline int strncasecmp_(const void* x, const void* y, size_t length) noexcept {
if (x == y || length == 0) {
return 0; // 相同指针或零长度
}
char* px = (char*)x;
char* py = (char*)y;
for (size_t i = 0; i < length; i++) {
int xch = toupper(*px++);
int ych = toupper(*py++);
if (xch != ych) {
return xch > ych ? 1 : -1; // 差异比较
}
}
return 0; // 完全相等
}
10.2 时间处理工具
cpp
// 跨平台时间戳获取
inline uint64_t GetTickCount(bool microseconds) noexcept {
#ifdef _WIN32
static LARGE_INTEGER ticksPerSecond;
LARGE_INTEGER ticks;
if (!ticksPerSecond.QuadPart) {
QueryPerformanceFrequency(&ticksPerSecond);
}
QueryPerformanceCounter(&ticks);
if (microseconds) {
double cpufreq = (double)(ticksPerSecond.QuadPart / 1000000);
return (unsigned long long)(ticks.QuadPart / cpufreq);
} else {
unsigned long long seconds = ticks.QuadPart / ticksPerSecond.QuadPart;
unsigned long long milliseconds = 1000 *
(ticks.QuadPart - (ticksPerSecond.QuadPart * seconds)) /
ticksPerSecond.QuadPart;
return seconds * 1000 + milliseconds;
}
#else
struct timeval tv;
gettimeofday(&tv, NULL);
if (microseconds) {
return ((unsigned long long)tv.tv_sec * 1000000) + tv.tv_usec;
}
return ((unsigned long long)tv.tv_usec / 1000) +
((unsigned long long)tv.tv_sec * 1000);
#endif
}
总结
rinetd 工具的实现体现了多个重要的系统编程原则:
- 协议特性适配:根据TCP和UDP的不同特性,采用差异化的处理模型
- 资源最小化:通过单缓冲区和共享内存减少内存占用
- 无锁并发:单线程线性处理消除同步开销
- 零拷贝优化:减少不必要的数据移动
- 平台抽象:统一的接口适配不同操作系统
特别值得注意的是UDP处理的单线程线性模型,这一设计选择并非简单的技术妥协,而是基于对UDP协议特性、内存访问模式和系统调用开销的分析后做出的理性决策。它展示了在特定约束条件下,简单直接的解决方案往往比复杂的并发模型更有效。