一个轻量的 TCP/UDP 端口转发小工具 —— 用 Go 实现,无需依赖系统
socat。
portmap 是一个用 Go 实现的通用 TCP/UDP 端口转发小工具,等价于:
sudo socat TCP-LISTEN:22,fork,reuseaddr TCP:127.0.0.1:2222支持两种模式:
go(默认):纯 Go 实现,不依赖系统socat,跨平台,对应TCP-LISTEN/fork/reuseaddr,并扩展了 UDP、并发限流、空闲超时与连接级日志(见下)。socat:直接调用本机的socat命令(可选-sudo),生成等价命令行(支持 TCP/UDP)。
Podman(尤其是 rootless 模式)默认不允许非特权用户监听 1024 以下的低位端口,这与 Docker 的端口映射行为存在明显差异:在 Docker 里可以直接把容器映射到宿主机的 22/53/80 等低位端口,而在 Podman rootless 下同样的映射会因为权限限制而失败。
常见的绕过办法是调整系统设置,例如降低 sysctl net.ipv4.ip_unprivileged_port_start,但这会修改全局内核参数、影响整机安全边界,并不总是可取。
portmap 提供了一条更轻量的路径:让容器以 rootless 方式监听一个高位端口(如 2222),再用本工具在宿主机上把低位端口(如 22/53/80)转发到该高位端口。这样既保留了类 Docker 的端口映射体验,又无需调整 net.ipv4.ip_unprivileged_port_start 等系统级设置。
Podman 示例:rootless 容器监听 2222,用 portmap 把宿主机 22 端口暴露出去(监听 22 需要特权,故用 sudo):
# rootless 容器(示例):将服务映射到高位端口 2222
podman run -d -p 2222:22 your-image
# 用 portmap 把低位端口 22 转发到容器监听的 2222
sudo portmap -listen-port 22 -target 127.0.0.1:2222需要 Go 1.26+(版本以 go.mod 为准)。
go build -o portmap .
# 或使用 Makefile(自动注入版本信息)
make buildmacOS / Linux 可通过 Homebrew Tap 安装:
brew tap soulteary/tap
brew install soulteary/tap/portmap验证:
portmap --version
# portmap 1.1.0 (commit 85fc65e, built 2026-08-08T10:26:23Z)预构建的多架构镜像(linux/amd64 + linux/arm64)发布在 ghcr.io 与 Docker Hub:
# 从 ghcr.io 拉取
docker pull ghcr.io/soulteary/portmap:latest
# 或从 Docker Hub 拉取
docker pull soulteary/portmap:latest容器基于 scratch,仅包含单个静态二进制。运行时需使用 host 网络才能完成端口转发:
# 把宿主机 22 转发到容器监听的 2222(低位端口需特权)
docker run --rm --network host ghcr.io/soulteary/portmap:latest \
-listen-port 22 -target 127.0.0.1:2222portmap [flags]
flags:
-listen-port int 本地监听端口 (默认 22)
-listen-host string 本地监听地址(默认所有网卡)
-target string 转发目标地址 host:port (默认 "127.0.0.1:2222")
-mode string 转发模式:go 或 socat (默认 "go")
-proto string 转发协议:tcp 或 udp (默认 "tcp")
-reuseaddr 启用 SO_REUSEADDR (默认 true)
-sudo socat 模式下以 sudo 运行
-dial-timeout duration 拨号目标超时 (默认 10s)
-max-conns int 最大并发连接数,0 表示不限制(仅 go 模式)
-idle-timeout duration 空闲超时,任一方向空闲超过阈值即回收该方向连接,0 表示不启用(仅 go 模式)
-log-level string 日志级别:info 或 debug(仅 go 模式,默认 "info")
-quiet 安静模式,抑制每连接的常规日志(仅 go 模式)
-config string YAML 配置文件路径
-lang string 界面语言:en/zh/ja/ko/fr/de(默认自动检测系统语言)
-version 打印版本信息后退出
与原命令完全等价(需 root 监听 22 端口):
sudo ./portmap -listen-port 22 -target 127.0.0.1:2222复用系统 socat(生成等价命令行):
./portmap -mode socat -sudo -listen-port 22 -target 127.0.0.1:2222UDP 转发(如 DNS):
./portmap -proto udp -listen-port 53 -target 127.0.0.1:5353高位端口本地测试 + 并发限流 + 空闲超时 + 调试日志:
./portmap -listen-port 13000 -target 127.0.0.1:2222 \
-max-conns 100 -idle-timeout 5m -log-level debug查看版本:
./portmap -version按 Ctrl+C 优雅退出,会等待在途连接处理完成。
帮助文本、日志与错误消息支持多语言:en(英文)、zh(简体中文)、ja(日文)、ko(韩文)、fr(法文)、de(德文),无法识别时回退英文。
默认按系统区域自动检测,检测优先级为:PORTMAP_LANG > LC_ALL > LC_MESSAGES > LANG > LANGUAGE > 系统区域(Windows 平台)。也可显式覆盖:
./portmap -lang zh -version # 命令行显式指定
PORTMAP_LANG=ja ./portmap -version # 环境变量指定除命令行 flag 外,还可通过 -config <path> 从 YAML 文件读取配置:
./portmap -config config.yaml- 优先级:命令行显式设置的 flag > 配置文件 > 内置默认值。 即:只有在配置文件中出现、且命令行未显式设置的字段,才会覆盖默认值。
dial_timeout/idle_timeout在 YAML 中用字符串(如"10s"、"5m"),经time.ParseDuration解析。- 配置文件中出现未知字段会直接报错,便于及早发现拼写错误。
lang字段仅影响运行期消息(日志/错误),不影响--help与 flag 描述的语言:--help在配置文件加载前就已定稿,如需改变其语言请用命令行-lang。- 启动时会打印一行「生效配置」摘要,便于确认合并后的实际参数。
完整示例见 config.example.yaml:
listen_port: 22
listen_host: ""
target: 127.0.0.1:2222
mode: go
proto: tcp
reuseaddr: true
sudo: false
dial_timeout: 10s
max_conns: 0
idle_timeout: 0s
log_level: info
quiet: false
lang: en命令行覆盖配置文件示例(配置文件里 listen_port 为 22,此处显式指定 8022 生效):
./portmap -config config.yaml -listen-port 8022两种模式支持的能力不同,请按需选择:
| 参数 / 能力 | go 模式 |
socat 模式 |
|---|---|---|
-listen-host |
支持(绑定指定地址) | 支持(生成 bind=<host>) |
-proto tcp/udp |
支持 | 支持 |
-reuseaddr |
支持 | 支持(reuseaddr) |
-max-conns |
支持 | 不支持(忽略并提示) |
-idle-timeout |
支持 | 不支持(忽略并提示) |
-log-level |
支持 | 不支持(忽略并提示) |
-quiet |
支持 | 不支持(忽略并提示) |
SIGUSR1 状态打印 |
支持(见下) | 不适用(由 socat 进程接管) |
-listen-host在 socat 下的行为:非空时会在监听地址上追加bind=<host>, 例如-mode socat -listen-host 127.0.0.1 -listen-port 22生成socat TCP-LISTEN:22,bind=127.0.0.1,fork,reuseaddr TCP:127.0.0.1:2222; 不设置时行为与之前完全一致(仅监听端口,等价0.0.0.0)。- 在 socat 模式下显式设置了仅
go模式支持的参数(-idle-timeout/-max-conns/-log-level/-quiet)时,程序会打印一行提示说明这些参数被忽略。
UDP 无连接,go 模式以「客户端地址 → 一条到目标的 UDP 连接」维护会话表,目标的回包按会话转发回对应客户端。以下参数在 UDP 下与 TCP 的差异:
-max-conns:UDP 下限制并发会话数(而非连接数),超限时直接拒绝新客户端并丢弃其首包(UDP 无排队语义),并在-log-level debug下记录。-idle-timeout:UDP 下为0会回退为默认 60s,即回收空闲超过 60s 的会话(TCP 下0表示不启用)。
go 模式下:
- 每条连接在建立与关闭时各打印一行日志,包含连接序号、双方地址、上/下行字节数与连接时长。
- 维护活跃连接的原子计数。
- 在类 Unix 平台可向进程发送
SIGUSR1打印当前连接快照,例如:
kill -USR1 <pid>
# 日志输出:status: active=<当前活跃连接数> total=<累计处理连接数>Windows 无 SIGUSR1,该功能自动跳过(不影响编译与运行)。
-quiet抑制常规日志;-log-level debug输出更详细信息(如pipe层异常)。
仓库自带一个独立、自包含的压测工具 cmd/loadtest,用于评估 portmap 转发的可靠性与吞吐量,并输出当前主机环境信息。
默认自包含模式下,工具会在进程内启动一个 echo 目标服务,再用 forward.New(...) 起一个转发服务,然后由压测客户端对转发端口发压,形成 client -> portmap -> echo 的完整链路,无需任何额外准备即可运行。也可用 -external <addr> 直接压测外部已运行的 portmap(此时需自备目标服务)。
# TCP 吞吐模式(长连接持续收发)
go run ./cmd/loadtest -proto tcp -mode throughput -conns 100 -duration 10s
# TCP 连接速率模式(短连接建立/关闭循环)
go run ./cmd/loadtest -proto tcp -mode connrate -conns 100 -duration 10s
# UDP 吞吐模式
go run ./cmd/loadtest -proto udp -mode throughput -conns 50 -duration 10s -payload 512
# 按每连接请求数而非时长跑;并测试限流/超时路径
go run ./cmd/loadtest -proto tcp -conns 20 -requests 1000 -max-conns 200 -idle-timeout 5m
# 压测外部已运行的 portmap(需自备目标服务)
go run ./cmd/loadtest -external 127.0.0.1:13000 -proto tcp -duration 10s参数说明:
loadtest [flags]
flags:
-proto tcp|udp 转发协议 (默认 tcp)
-conns int 并发连接/会话数 (默认 50)
-duration duration 压测时长,与 -requests 二选一 (默认 10s)
-requests int 每连接请求数,0 表示按 duration 持续跑 (默认 0)
-payload int 单次请求负载字节数 (默认 1024)
-mode throughput|connrate 吞吐模式(长连接持续收发)或连接速率模式(短连接循环) (默认 throughput)
-external string 外部 portmap 地址;为空则自建链路
-max-conns int 内建转发服务最大并发连接数,0 表示不限制
-idle-timeout duration 内建转发服务空闲超时,0 表示不启用
-warmup duration 预热时间,预热期数据不计入统计 (默认 1s)
报告包含三块:主机环境(GOOS/GOARCH、CPU 数、Go 版本、主机名、内存分配)、配置、结果(吞吐 MB/s 与 Gbps、req/s、connrate 模式下的 conns/s、延迟 min/p50/p95/p99/max、错误率与分类计数,以及压测后 ActiveConns() 是否归零的可靠性校验)。所有输出走标准库,不引入新依赖。
样例输出(节选):
==================== portmap loadtest report ====================
[ Host / Runtime ]
hostname : example
os/arch : linux/amd64
num cpu : 8
go version : go1.26.x
...
[ Results ]
throughput : 75.35 MB/s | 0.632 Gbps
req/s : 77159.59
[ Latency (RTT) ]
min : 26µs
p50 : 240µs
p95 : 387µs
p99 : 553µs
max : 19.973ms
[ Reliability ]
errors : 0 (rate 0.0000%)
active conns : 0 (returned to zero) OK
=================================================================
make build # 注入版本信息编译
make test # go test ./... -race
make vet # go vet
make lint # golangci-lint(需已安装)
make release # 交叉编译多平台产物到 dist/
make snapshot # 用 GoReleaser 本地试跑发布流程(不推送、不发布)CI 见 .github/workflows/ci.yml:在 linux/macOS/windows 上运行 go vet 与
go test -race,并独立运行 golangci-lint。
发布见 .github/workflows/release.yml,由 GoReleaser 驱动:
- 触发:推送
v*形式的 tag(如v1.0.0)自动发布,或在 Actions 页手动workflow_dispatch。 - 二进制产物:
linux/darwin/windows×amd64/arm64(windows/arm64 除外) 连同checksums.txt一并上传到 GitHub Release。 - 容器镜像:构建多架构(
linux/amd64+linux/arm64)镜像并推送到ghcr.io/soulteary/portmap与docker.io/soulteary/portmap, 同时打:latest与:<version>标签。
发布前置条件:
- ghcr.io 使用内置
GITHUB_TOKEN(工作流已授予packages: write),无需额外配置。 - Docker Hub 需在仓库 Settings → Secrets 中配置
DOCKERHUB_USERNAME与DOCKERHUB_TOKEN两个 secret。若未配置DOCKERHUB_USERNAME,工作流会自动跳过 Docker Hub 登录步骤(镜像仅推送到 ghcr.io),发布流程不会因此失败。 - 本地可用
make snapshot(等价goreleaser release --snapshot --clean)或goreleaser check校验配置,均不会推送。
.
├── main.go # 命令行入口与参数解析
├── main_test.go # 命令行入口测试
├── config.go # YAML 配置文件加载与合并
├── config_test.go # 配置文件加载/合并测试
├── config.example.yaml # 配置文件示例
├── signals_unix.go # 类 Unix 平台 SIGUSR1 状态打印
├── signals_windows.go # Windows 平台 no-op(无 SIGUSR1)
├── cmd
│ └── loadtest # 独立压测工具(自包含链路 + TCP/UDP 压测)
│ └── main.go
├── Makefile # 构建/测试/发布
├── LICENSE # Apache 2.0 许可证
├── .golangci.yml # golangci-lint 配置
├── .goreleaser.yaml # GoReleaser 发布配置(二进制 + 镜像)
├── Dockerfile # scratch 基础镜像
├── .dockerignore # 容器构建上下文忽略规则
├── .github/workflows/ci.yml # CI 工作流
├── .github/workflows/release.yml # 发布工作流(GoReleaser)
└── internal
├── forward # 纯 Go TCP/UDP 转发器
│ ├── forward.go # TCP 转发、限流、空闲超时、日志
│ ├── forward_test.go # forward 单元测试
│ ├── udp.go # UDP 会话转发
│ ├── reuseaddr_unix.go # 类 Unix 平台 SO_REUSEADDR
│ └── reuseaddr_windows.go # Windows 平台 SO_REUSEADDR
├── socat # 调用系统 socat 的 fallback
├── socat.go # 构造并执行 socat 命令
├── socat_test.go # socat 单元测试
├── socat_cancel_unix.go # 类 Unix 平台 SIGTERM 优雅取消
└── socat_cancel_windows.go # Windows 平台 no-op(无 SIGTERM)
└── i18n # 多语言(i18n)支持
├── i18n.go # 语言检测、解析与查表
├── i18n_test.go # i18n 单元测试
├── keys.go # 消息 key 常量与语言表
├── locale_unix.go # 类 Unix 平台区域探测(no-op)
├── locale_windows.go # Windows 平台区域探测
└── messages_*.go # 各语言消息(en/zh/ja/ko/fr/de)
go test ./...
# 或
make test本项目基于 Apache License 2.0 开源,版权所有 (c) 2026 soulteary。

