Files
LinkPort/docs/tcp-port-mapping.md
T
曾志威 d3edfea03f feat: add independent TCP forwarding and gateway operations improvements
Support target-free SOCKS5 port mappings with reference protection and UI workflows.

Include connection-stage diagnostics, SSH fingerprint confirmation, asset impact checks, listener traffic visibility, SQLite snapshots, account mapping improvements, and MySQL discovery fixes.

Update feature rationale and validation docs; Go checks, frontend build and 121 browser tests passed.
2026-09-22 15:42:58 +08:00

14 KiB
Raw Blame History

TCP 端口映射

独立纯端口转发(2026-09-22)

只转发端口时,不需要创建目标服务,也不会自动生成目标记录。 在「网关服务」选择 TCP 类型,再选择「纯端口转发」,直接填写监听地址/端口、已有 SOCKS5 代理以及目的地址/端口。目标服务用于需要目标关联、协议处理或账号映射的配置,不是独立转发的前置条件。原有「关联已有 TCP 目标」模式保持不变;本文后续原有配置步骤、运行中目标切换和目标定时检查说明适用于该关联模式。

配置两个独立映射

先在代理管理中保存可用的 SOCKS5 代理,然后新建两条 TCP「纯端口转发」网关,共用该代理:

监听地址 转发代理 目的地址 目的端口
127.0.0.1:1081 已有 SOCKS5 代理 192.168.11.16 8080
127.0.0.1:1082 同一个已有 SOCKS5 代理 192.168.11.16 8082

即 127.0.0.1:1081 → 已有 SOCKS5 → 192.168.11.16:8080,以及 127.0.0.1:1082 → 已有 SOCKS5 → 192.168.11.16:8082。这里 192.168.11.16:8080/8082 是经代理访问的目的服务,不是 SOCKS5 代理自身的监听地址。每条网关保存、手动测试后分别启动;客户端连接本机的 1081 或 1082。

每条网关对应一组监听与目的端口。现有批量创建只递增监听端口,不递增目的端口;以上两条映射应分别创建,或复制后显式把第二条目的端口改为 8082,不要仅依赖批量递增。

默认监听仍为 0.0.0.0;以上例子显式使用 127.0.0.1,仅供本机客户端。需要向其他机器开放时,应另行设置监听地址、来源 IP/CIDR 白名单和系统防火墙,不把全网卡监听视为认证保护。

数据与安全边界

  • 网关只透传 TCP 原始字节,不解析或转换业务协议,不替换业务账号密码,不终止 TLS,也不提供网关应用层认证;业务认证和 TLS 校验仍由客户端与目的服务处理。不支持 UDP。
  • SOCKS5 连接需要认证时,复用所选代理已保存的凭据;独立网关只保存代理引用,不额外保存业务凭据,也不创建目标或目标账号。
  • 独立模式只选择已有 SOCKS5 代理,不在此模式内配置 SSH/Kubernetes 出口。旧目标关联模式及其出口能力保持不变。
  • 被独立网关引用的代理禁止删除或改成其他类型;相关网关运行时禁止编辑该代理,网关自身也需停止后才能编辑。停止所有相关网关后可以更改代理的 SOCKS5 配置或网关映射,但引用仍存在时仍不能删除代理或改类型;须先解除引用。
  • 独立模式不使用运行中目标切换。只有手动连接测试和运行指标,没有目标周期健康检查;手动测试只证明 TCP 链路可建立,不证明业务认证、TLS 或应用功能正常。
  • 「拖拽连线」图视图中,纯端口转发网关不显示“未关联目标”提示,也不允许拖到目标进行绑定;仍保留手动测试按钮。

API、备份与回退

沿用现有网关创建/更新接口,网关配置的关键字段如下(名称等其余必填字段按现有接口填写,创建后再启动):

{
  "type": "tcp",
  "listenHost": "127.0.0.1",
  "listenPort": 1081,
  "targetId": "",
  "targetAccountId": "",
  "gatewayAccounts": [],
  "tcpForward": {
    "host": "192.168.11.16",
    "port": 8080,
    "outboundId": "existing-socks5-profile-id"
  }
}

outboundId 必须引用已有 SOCKS5 代理。tcpForward 仅适用于 type: "tcp",与目标服务引用及账号映射互斥。停止网关后提交 tcpForward: null 可清除独立模式,再按原有流程关联 TCP 目标;清除字段不会自动生成或绑定目标。

配置保存在既有网关数据中,无需 SQLite schema 迁移。备份导出/导入与 WebDAV 同步沿用 bundle 校验,检查代理引用存在且为 SOCKS5,不能把缺少代理的配置当作可用映射恢复。旧版程序不支持新的 tcpForward 字段,即使旧版已支持 tcp 类型也不能据此认定可用;回退前应备份数据、核对版本兼容性并处理独立转发配置,避免直接用旧程序覆盖或导入新配置。

本轮实际验证

Go 1.25.0 的全量 go test ./... -count=1 -timeout=120s 和 go vet ./... 已通过。首次 scripts/build.ps1 -SkipTests 在 Node 22.10 下构建成功,但触发 Vite 版本要求警告;这不表示该 Node 版本满足 Vite 支持要求。补充图视图调整后,使用机器已有 Node 22.22.3 重新执行同一构建脚本,完成 npm build 和 Go 1.25.0 编译,生成最终程序 artifacts/tcp-independent/serviceport.exe;此次无 Node 版本警告,仅保留 chunk 大小警告。

最终重建前,Playwright tcp-independent.spec.ts、tcp-forwarding.spec.ts 共 2 项通过。新增 API 测试 TestIndependentTCPForwardTwoRoutes 使用本地带密码 SOCKS5 fixture,将两条映射转发到本地 TCP fixtures 并校验二进制字节;不是对用户真实 192.168.11.16 网络或业务服务的验收。新增 tcp_independent_validation_test.go 的 5 项边界测试(21 项子测试)通过,覆盖无效代理/字段、账号冲突、部分更新、模式切换、引用重绑定、代理改类型及影响范围响应脱敏。

最终使用 Node 22.22.3,设置 SERVICEPORT_E2E_BINARY=artifacts/tcp-independent/serviceport.exe,在隔离端口 19281/19282 完整运行 Playwright,121/121 通过(6.6 分钟),包含最终图视图修复;日志为 artifacts/tcp-independent-playwright-final.log。此前 Node 22.10 因 node:sqlite 不可用导致测试发现失败;仅在当前 shell 通过 fnm use 22.22.3 切换机器已有版本后,一次运行在 59 项时被工具 200 秒时限中断,该中断不代表用例失败。最终延长时限后的完整重跑已通过;详细记录见 testing.md。

用途和边界

服务类型 tcp 将一个监听端口映射到一个 TCP 目标端口,不解析应用协议,不替换账号,不终止 TLS。可以传输 MongoDB、HTTP 或其他 TCP 服务的原始字节;客户端直接使用目标服务自己的认证方式和凭据。此版本不支持 UDP。

默认监听 0.0.0.0,即所有 IPv4 网卡;网关不提供应用层认证。请配置来源 IP/CIDR 白名单及系统防火墙,只允许可信客户端访问。白名单留空意味着不限制来源。需要仅本机使用时,将监听地址改为 127.0.0.1。应用层认证、权限和 TLS 证书校验由客户端与目标负责。

原目标关联模式的配置步骤

  1. 在「目标服务」新建「TCP 端口映射」目标,填写主机和目标端口;不配置目标账号或 TLS 终止。
  2. 连接方式可选直连、SSH/SOCKS5 连接资产,或 K8s 资源 port-forward。K8s 目标配置 Kubeconfig、命名空间和 StatefulSet 等资源以及容器端口,不维护 Pod IP。
  3. 在「网关服务」新建相同类型,填写监听端口、来源白名单和最大连接数。默认监听地址为 0.0.0.0,不需要网关账号。
  4. 选择 TCP 目标,或在「拖拽连线」将网关卡片直接拖到 TCP 目标卡片。TCP 映射不使用账号行,也不绑定其他协议类型的目标;已有数据库目标保持原来的账号映射语义。
  5. 测试连接后启动。客户端连接网关的地址和监听端口,由目标服务完成认证。运行中切换目标仅影响新连接,已有连接保留原目标;停止网关会断开现有连接。

例如:0.0.0.0:27018 → K8s StatefulSet:27017。客户端的 MongoDB authSource、认证机制和密码直接交给 MongoDB,此模式不执行 ServicePort 的 MongoDB SCRAM 认证。

测试、监控与审计

  • 「测试连接」只建立并关闭 TCP 连接,不发送应用探测数据;定时健康检查仅针对目标关联模式,并采用相同的 TCP 可达性检查,独立纯端口转发模式不提供目标周期健康检查。成功仅表示端口可达,不证明 TLS、账号密码或业务服务正常。
  • 保留连接生命周期记录、来源白名单、并发连接数限制、启停和运行中目标切换;不提供命令内容审计、只读控制、账号级选路或应用层认证成功计数。
  • 支持双向转发和底层连接支持的写半关闭,停止或取消时清理两端连接;SSH/K8s 包装连接透出底层半关闭能力,最终语义仍受远端转发实现影响。
  • 每条配置映射一对端口;可创建多条网关配置,也可使用现有批量创建流程。只支持 TCP,不提供 NAT/路由器规则管理。

兼容性

监听预检与流量可观测性(2026-09-22)

为避免启动失败时只能看到模糊的端口错误,新增 gateway.PreflightListen 和 API handler Server.listenPreflight。预检只短暂绑定本机 TCP socket 后释放,不连接目标、不发送协议数据、不保留端口。它不能消除预检到启动之间的竞争;启动仍执行真实 bind 并返回包含明确监听地址、稳定错误码和操作建议的安全错误,不回显操作系统原始诊断或凭据。错误区分端口占用、地址不可用、权限不足及其他监听失败;Windows 使用 Winsock 错误码,其他系统使用 errno。

  • 预检输入 {listenHost,listenPort,allowCIDRs};监听地址要求显式 IPv4/IPv6 字面量,不解析 DNS。空地址/主机名应改为明确本地 IP;端口范围 0–65535,0 用于测试或临时监听。
  • 返回 available,requestedAddress,boundAddress,clientDestinations,warnings,code,action。boundAddress 是本次临时 socket 的真实绑定结果,不是已运行网关,也不是端口预约。冲突返回 200 + available:false;非法地址返回 400。
  • 0.0.0.0 或 :: 会返回 listen_wildcard_requires_client_address;同时没有来源白名单时增加 listen_wildcard_without_allowlist。警告不阻止监听,操作者应配置白名单、防火墙并向客户端提供实际可达的服务器 IP/DNS。
  • 网关现有 API 的 runtime.boundAddress 来自运行中的 listener;runtime.clientDestinations 对明确非通配绑定地址给出该地址,对通配绑定给出相同实际地址族的 loopback 候选(127.0.0.1:port 或 [::1]:port),绝不宣称 0.0.0.0/:: 可拨号。预检和 runtime 都附带 clientDestinationScope:loopback 候选为 local_only,其他为 reachability_unverified。loopback 仅供网关所在主机使用,仍受来源 ACL 限制;远端客户端必须使用实际可达的服务器接口 IP/DNS,候选不证明远端路由或防火墙放行。不根据浏览器 Host 或枚举网卡猜测公开地址。预检增加 listen_client_destination_local_only 警告,父层需提供对应文案。
  • 现有 GET /api/v1/monitoring 的每网关 metrics 新增 totalConnections,rxBytes,txBytes,recentError,recentErrorAt,继续使用 activeConnections。累计连接数仅计通过来源/并发限制的连接,不等同上游连接或认证成功。RX 是客户端向网关读取的字节,TX 是网关成功写给客户端的字节;不是仅业务载荷,也不证明 RX 已转发成功。现有活动连接数据增加同向的 rxBytes,txBytes。
  • 累计值及最近安全错误在网关停启后保留,进程重启清零,不写入 SQLite。最近错误仅保存安全分类与时间,正常停止/主动断开导致的取消不覆盖它;一次后续成功不会清掉历史错误。计数包装保留 TCP 写半关闭,不读取或记录数据内容。

界面与路由集成

  1. server.go 在认证、权限、CSRF 检查后将 listen-preflight 分派给 s.listenPreflight(w,r);仅 POST,沿用写入权限检查。
  2. App.vue 的监听编辑页通过 GatewayListenCheck.vue 调用预检并渲染 ListenPreflight.vue;监听字段或白名单改变后将旧结果置 stale。已有运行 listener 占用端口时如实报告冲突,不通过客户端提交网关 ID 绕过检查。
  3. 网关表格展开项使用 GatewayRuntimePanel.vue 展示实际地址、客户端候选、复制操作和 GatewayTraffic.vue 的统计。未启动网关可在展开项预检;回环候选明确仅供本机客户端,不表示远端可达。
  4. 展示组件通过 labels 接收文案,当前仓库没有 Shopee i18n API,沿用现有中文界面。完整构建与端到端验证记录见 testing.md。

本次实际验证

使用 GOTOOLCHAIN=go1.25.0,focused 命令通过:

go test ./internal/gateway ./internal/api ./internal/protocol/tcpproxy -run 'TestListenPreflight|TestTCP|TestOpaqueStreamAndHalfClose|TestCancellationClosesBothStreams|TestConnectionProbeDoesNotSendApplicationData' -count=1 -timeout=120s

覆盖临时端口释放/冲突、显式地址校验、通配警告、不泄漏输入秘密、真实 bound 地址、活动/累计连接与 RX/TX、停启保留、既有 TCP 半关闭及取消。全部使用隔离 SQLite 与 loopback 临时 TCP fixture;不是生产服务或真实远端验收,不修改本地真实 data。合并后的全量 Go、前端构建和 Playwright 结果见 testing.md。

新增类型不修改现有数据库网关,无 SQLite 结构迁移。旧版本不识别 tcp 配置,导入或回退到旧程序前应注意版本兼容性。必须运行包含 TCP handler 的新程序才生效,本次开发未替换正在运行的程序或修改实际数据。

验证范围

新增后端测试使用隔离 SQLite、本机 TCP 测试服务和受控 handler,覆盖字节透传、半关闭、取消清理、来源/连接数限制、旧会话保留、新目标切换、无账号创建与测试、各类出口配置和定时健康检查。

Playwright 使用新构建程序,验证界面默认 0.0.0.0,运行测试时显式改为 loopback 并分配临时端口,实际传输包含 NUL 和非 UTF-8 的测试字节。不是生产服务、真实 Kubernetes 或 MongoDB 的连接验收;验证命令与结果见 testing.md。