PortRelay 是一个 Go 语言编写的轻量级网络工具, 支持:
- UDP-over-TCP 隧道 — 将 UDP 流量封装在 TCP 连接中传输, 适用于仅开放 TCP 的受限网络(如部分云服务器、企业内网)
- TCP-over-UDP 隧道 — 将 TCP 流量封装在 UDP 中传输, 借助 KCP 协议降低延迟, 适用于对延迟敏感的场景(如游戏、实时通信)
- IPv4 ↔ IPv6 桥接 — 在两个地址族之间做固定端口转发, 适用于纯 IPv6 机器访问 IPv4 服务, 或 IPv6-only 环境部署需要 IPV4 传输数据的程序等场景
- 灵活部署 — 客户端与服务端分离部署, 客户端也可脱离服务端独立运行
从 Releases 下载对应平台的二进制, 直接运行:
# 服务端
./portrelay --config-path server.json
# 客户端
./portrelay --config-path client.json
# 内联配置(无需文件)
./portrelay --config-base64 <base64编码的JSON>PortRelay 支持三种配置传入方式(优先级从高到低):
| 优先级 | CLI 参数 | 适用场景 |
|---|---|---|
| 1 | --config-base64 <base64> |
脚本调用、容器部署、无文件环境, 使用 标准 Base64 编码(非 URL-safe) |
| 2 | --config-path <path> |
指定配置文件路径 |
| 3 | (无参数) | 自动读取程序同目录下的 config.json |
Warning
如需复制配置文件, 请删除注释
{
"name": "游戏服务中继", // 配置名称, 仅用于日志
"mode": "server", // 运行模式: server
"admin_passwd": "", // 全局通用密码, 空字符串 = 禁用
"check_update": true, // 启动时检查新版本, 不填或 false 则不检查
"log_level": "info", // 日志级别: "debug" / "info" / "warn" / "error" / "fatal"
"listen_port": "9000", // 统一入口端口, TCP 和 UDP 共用
"listen_protocol": "all", // 监听的传输协议: "tcp" / "udp" / "all"
"proxies": [
{
"name": "cs2-tunnel", // 隧道名称, 客户端需匹配
"type": "tunnel", // 固定值
"service_target": "127.0.0.1:23450", // 最终转发目标 ip:port
"allow_protocol": "udp", // 允许的内层协议: "tcp" / "udp" / "all"
"passwd": "my-secret-key" // 隧道专属密码, 不可为空
}
]
}| 字段 | 类型 | 必填 | 说明 |
|---|---|---|---|
name |
string | 是 | 配置名称, 仅用于日志 |
mode |
string | 是 | "server" |
admin_passwd |
string | 否 | 全局通用密码, 空字符串 = 禁用 |
check_update |
bool | 否 | 启动时检查是否有新版本可用, 默认 false |
log_level |
string | 否 | 日志级别: "debug" / "info" / "warn" / "error" / "fatal", 默认 "info", "debug" 时显示文件名和行号 |
listen_port |
string | 是 | 统一入口端口, 同时监听 TCP 和 UDP |
listen_protocol |
string | 是 | 服务端监听的传输协议: "tcp" / "udp" / "all" |
max_frame_size |
int | 否 | 最大帧载荷大小(字节), 默认 32768(32KB), 范围: 1024-65535, 服务端和客户端必须使用相同的值 |
Note
max_frame_size 同样适用于 UDP 数据报:超过该限制的单个 UDP 数据报会被丢弃 (UDP 数据报无法拆分), TCP 流会被透明地拆分为多个帧
| max_connections | int | 否 | 最大并发连接数 (仅服务端), 默认 1000, 包括 TCP、UDP 和 KCP 连接, 空闲连接会在 10 分钟后自动清理 |
| proxies[].name | string | 是 | 隧道名称 (客户端需匹配) |
| proxies[].type | string | 是 | "tunnel" |
| proxies[].service_target | string | 是 | 转发到的目标 ip:port |
| proxies[].allow_protocol | string | 是 | 内层协议: "tcp" / "udp" / "all" |
| proxies[].passwd | string | 是 | 隧道专属密码(不可为空) |
认证顺序: 先比对隧道密码 → 再比对全局 admin_passwd
行为说明:
listen_port是服务端统一入口, TCP 和 UDP 共用此端口listen_protocol决定物理层监听哪些传输协议, 是第一层过滤: 客户端使用服务端未监听的传输协议时, 无法成功连接allow_protocol是第二层过滤: 认证通过后, 数据帧的内层协议(TCP/UDP)必须匹配此字段, 不匹配则丢弃流量- 客户端发来的数据帧 → 解封内层载荷 → 转发至
service_target→ 收到的响应封装后沿隧道回传 - 同一隧道可被多个客户端同时连接, 通过 SessionID 区分不同数据流
Warning
如需复制配置文件, 请删除注释
{
"name": "我的客户端", // 配置名称, 仅用于日志
"mode": "client", // 运行模式: client
"check_update": true, // 启动时检查新版本, 不填或 false 则不检查
"log_level": "info", // 日志级别: "debug" / "info" / "warn" / "error" / "fatal"
"proxies": [
{
"name": "cs2-tunnel", // 隧道名称, 必须与服务端一致
"type": "tunnel", // 固定值
"listen_protocol": "udp", // 本地监听协议: "tcp" / "udp" / "all"
"listen_local": "0.0.0.0:12345", // 本地监听地址, 发往此端口的数据将进入隧道
"server_ip": "[2001:db8::1]:9000", // 服务端地址 [ip]:port
"server_passwd": "my-secret-key", // 认证密码
"transport": "auto" // 传输协议: "tcp" / "udp" / "auto"
}
]
}| 字段 | 类型 | 必填 | 说明 |
|---|---|---|---|
check_update |
bool | 否 | 启动时检查是否有新版本可用, 默认 false |
log_level |
string | 否 | 日志级别: "debug" / "info" / "warn" / "error" / "fatal", 默认 "info", "debug" 时显示文件名和行号 |
max_frame_size |
int | 否 | 最大帧载荷大小(字节), 默认 32768(32KB), 必须与服务端配置一致 |
name |
string | 是 | 必须与服务端隧道名称一致 |
type |
string | 是 | "tunnel" |
listen_protocol |
string | 是 | 本地监听协议: "tcp" / "udp" / "all" |
listen_local |
string | 是 | 本地监听地址 ip:port |
server_ip |
string | 是 | 服务端地址 [ipv6]:port 或 ip:port |
server_passwd |
string | 是 | 发往服务端的认证密码 |
transport |
string | 是 | 隧道传输协议: "tcp" / "udp" / "auto" |
transport 协商行为:
| 值 | 行为 |
|---|---|
"tcp" |
强制 TCP, 服务端不支持时自动 fallback(打印 warning) |
"udp" |
强制 UDP, 同上 fallback 逻辑 |
"auto" |
总是先尝试 TCP,TCP 连接失败时回退到 UDP。与 listen_protocol 无关。 |
连接始终建立, 不会因协议不匹配而退出
行为说明:
- 客户端在
listen_local上按listen_protocol启动本地监听:"tcp"仅 TCP,"udp"仅 UDP,"all"同时监听 TCP+UDP - 启动时立即连接服务端并发送握手帧(隧道名 + 密码 + 期望传输协议), 认证通过后开始数据中继
transport决定客户端与服务端之间的传输协议:"tcp"走 TCP 流,"udp"走 UDP 数据报,"auto"自动协商- 每条独立的本地数据流(不同源 IP/端口)分配一个 SessionID, 封装进数据帧发往服务端; 回传数据按 SessionID 路由回正确的来源
- 当
transport为"tcp"且连接断开时, 客户端无限次自动重连, 无需人工干预 transport: "tcp"且服务端仅支持 UDP 时, 自动通过 KCP 建立 TCP-in-UDP 隧道
Warning
如需复制配置文件, 请删除注释
无需服务端, 用于固定目标的 IPv4 ↔ IPv6 端口转发
{
"name": "单转发模式客户端",
"mode": "client",
"check_update": true, // 启动时检查新版本, 不填或 false 则不检查
"log_level": "info", // 日志级别: "debug" / "info" / "warn" / "error" / "fatal"
"proxies": [
{
"name": "local-v4-to-remote-v6-tcp", // 你的配置文件命名
"type": "direct", // 直接转发模式
"protocol": "tcp", // 要转发的端口的协议
"listen": "0.0.0.0:8080", // 本地监听端口, 发往此端口的数据将全部转发至 Target
"target": "[2001:db8::2]:80" // 远端目标地址
}
]
}| 字段 | 类型 | 必填 | 说明 |
|---|---|---|---|
check_update |
bool | 否 | 启动时检查是否有新版本可用, 默认 false |
log_level |
string | 否 | 日志级别: "debug" / "info" / "warn" / "error" / "fatal", 默认 "info", "debug" 时显示文件名和行号 |
name |
string | 是 | 规则名称 |
type |
string | 是 | "direct" |
protocol |
string | 是 | "tcp" / "udp" / "all"(同时转发 TCP+UDP) |
listen |
string | 是 | 本地监听地址 ip:port |
target |
string | 是 | 远端目标 [ipv6]:port 或 ip:port |
行为说明:
- 在
listen地址上启动监听, 所有流量直接转发至target, 回传数据原路返回 protocol: "tcp"— 每个新 TCP 连接独立处理, 双向透明转发protocol: "udp"— 按来源地址区分不同客户端, 各自维持独立会话protocol: "all"— 同一端口同时转发 TCP+UDP, 两端互不干扰- 此模式无认证、无协议封装, 不依赖服务端, 客户端可单独运行
Warning
如需复制配置文件, 请删除注释
同一客户端可同时包含隧道和独立模式规则, 各规则独立运行互不干扰:
{
"name": "多模式客户端",
"mode": "client",
"check_update": true, // 启动时检查新版本, 不填或 false 则不检查
"log_level": "info", // 日志级别: "debug" / "info" / "warn" / "error" / "fatal"
"proxies": [
{
"name": "cs2-tunnel", // 隧道规则, 连接服务端
"type": "tunnel",
"listen_protocol": "udp",
"listen_local": "0.0.0.0:12345",
"server_ip": "[2001:db8::1]:9000",
"server_passwd": "my-secret-key",
"transport": "auto"
},
{
"name": "dns-forward", // 独立规则, 无需服务端
"type": "direct",
"protocol": "udp",
"listen": "0.0.0.0:5353",
"target": "[2001:db8::2]:53"
}
]
}行为说明:
- 每条规则独立启动各自的监听和服务, 互不影响
- 隧道规则按隧道模式运行(需服务端), 独立规则按直接转发模式运行(无需服务端)
| 场景 | 格式 | 示例 |
|---|---|---|
| IPv4 | ip:port |
127.0.0.1:9000 |
| IPv6 | [ipv6]:port |
[2001:db8::1]:9000 |
| 监听所有接口 (IPv4) | 0.0.0.0:port |
0.0.0.0:12345 |
| 监听所有接口 (IPv6) | [::]:port |
[::]:9000 |
四种传输组合, 对应不同的可靠性保证:
| 模式 | 内层 | 外层 | 可靠性 | 实现方式 |
|---|---|---|---|---|
| UDP in UDP | UDP | UDP | 尽力转发 | 原生 UDP socket |
| UDP in TCP | UDP | TCP | 尽力转发(模拟无连接) | TCP 流 + 断线自动重连 |
| TCP in TCP | TCP | TCP | 原生 TCP 保证 | TCP socket |
| TCP in UDP | TCP | UDP | Stop-and-Wait ARQ | kcp-go |
UDP in TCP 断线重连: TCP 隧道断开后客户端自动重连, 无限次重试, 服务端将每次重连视为新连接
KCP 模式会在以下情况自动触发:
- 客户端配置
transport: "tcp"或"auto" - 服务端仅监听 UDP (
listen_protocol: "udp") - 客户端 TCP 连接尝试失败
- 客户端转而发送 UDP 握手
- 服务端在握手 ACK 中返回
AcceptedProto = TransportTCP - 客户端通过 UDP 建立 KCP 连接
结果:在 UDP 传输层上获得 TCP 类似的可靠性,使用 kcp-go 协议进行重传和拥塞控制
在配置文件中设置 "check_update": true 后, 每次启动时程序会通过 GitHub API 获取最新 Release 版本号并与当前版本对比, 若有新版本将在终端打印提示:
[INFO] 2026/05/28 20:14:22 New version available: 1.0.0
[INFO] 2026/05/28 20:14:22 Download: https://github.com/MarchSnow-1/PortRelay/releases
默认不启用, 不填写或设为 false 时启动不会发起任何网络请求
| 依赖 | 说明 |
|---|---|
| Go | ≥ 1.24 |
git clone https://github.com/MarchSnow-1/PortRelay.git
cd PortRelay/src
go build -o ../portrelay .Apache 2.0 — 详见 LICENSE