2026-09-21 16:00:26 +08:00
11
2026-09-21 16:00:26 +08:00
2026-09-21 15:59:34 +08:00

mysql-tunnel

Windows 本机 MySQL 协议代理,给 影刀 RPA(以及其它只认 MySQL 协议的客户端)用。

影刀连的是本机 127.0.0.1:13306。本进程把这条连接翻译成 Navicat HTTP Tunnel 协议,再 POST 到服务器上的 ntunnel_mysql.php,由 PHP 去连远程 MySQL。

影刀 RPA / mysql 客户端
        │  MySQL 协议(握手、认证、COM_QUERY)
        ▼
  127.0.0.1:13306
        │
  mysql-tunnel.exe          ← 本仓库编译出的 Windows 程序
        │  HTTP POST  application/x-www-form-urlencoded
        │  URL 带 ?token=...
        ▼
  ntunnel_mysql.php         ← 部署在能访问远程 MySQL 的 Web 主机上
        │  mysqli_connect + mysqli_real_query
        ▼
  远程 MySQL:3306

这 不是 TCP-over-HTTP 端口转发,也 不是 业务 REST API。每一条 SQL 都走一次独立的 HTTP 请求。

协议细节见 docs/PROTOCOL.md。


当前状态

可用(v0.5.0): 本机 MySQL Server(默认 127.0.0.1:13306)已接通 Navicat HTTP 隧道。Handshake、mysql_native_password、COM_QUERY / COM_PING / COM_QUIT / COM_INIT_DB 可用。Windows 双击 exe 打开配置窗口(保存、测试、启动/停止、托盘)。可选安装为 Windows 服务。


工作原理

仓库里的 ntunnel_mysql.php 是 Navicat 16 官方隧道脚本(script version 206),外加 GET token 门禁。

每次 HTTP POST,PHP 都会:

  1. 校验 URL 上的 ?token=
  2. mysqli_connect(host, login, password, …)
  3. mysqli_set_charset(..., 'UTF8')
  4. 可选 mysqli_select_db
  5. actn=C 返回服务器信息,或 actn=Q 执行 q[] 里的 SQL
  6. 脚本结束,远程 MySQL 连接被丢掉

因此:

  • 无持久会话。 START TRANSACTION / COMMIT 不能跨两次 HTTP 请求。
  • SET 会话变量、last_insert_id() 只在同一次 POST 的 q[] 里有效。
  • 影刀侧的 USE db 必须由 mysql-tunnel 记住,并在后续请求里带上 db=。
  • 大结果集会整包进 PHP 内存再作为 HTTP 响应返回。

config.yaml 里的 charset 不是 Navicat POST 字段。PHP 固定 UTF8;本客户端在 actn=Q 时会把 SET NAMES utf8mb4(若配置了非 utf8 字符集)作为第一条 SQL 一并发送。


你需要准备什么

角色 是什么 谁提供
本机程序 mysql-tunnel.exe 本仓库编译,或已有的 dist\mysql-tunnel.exe
配置文件 config.yaml 从 config.example.yaml 复制后填写
隧道脚本 ntunnel_mysql.php 上传到能访问目标 MySQL 的 PHP 主机
访问令牌 URL ?token= 与 PHP $accessToken 必须一致 你自己设定
远程 MySQL PHP 主机能连上的库(常是 127.0.0.1 / Docker 名 mysql) 运维 / 现有环境
影刀 把数据源指到本机监听地址 本机 mysql-tunnel.exe 保持运行

环境要求:

  • Windows(目标运行环境)。Linux/macOS 可编译、跑单测,但产品面向影刀本机。
  • Go 1.24+(仅构建需要;运行 mysql-tunnel.exe 不需要装 Go)。
  • PHP 主机需启用 mysqli,并能访问目标 MySQL。
  • 本机到隧道 URL 的 HTTP/HTTPS 网络畅通(公司代理走系统环境变量)。

第三方依赖:gopkg.in/yaml.v3(配置)、github.com/lxn/walk(Windows 配置窗口,无 CGO)。github.com/go-sql-driver/mysql 仅用于兼容性测试,不会链进 mysql-tunnel.exe。Windows 服务使用 golang.org/x/sys/windows/svc。


快速开始

1. 编译

Go 已加入 PATH 后,在仓库根目录:

go mod tidy
go test ./...
build.bat

产物:dist\mysql-tunnel.exe。build.bat 会先跑完全部测试,再以 CGO_ENABLED=0 生成精简二进制。

等价命令:

go test ./...
go build -trimpath -ldflags="-s -w" -o dist\mysql-tunnel.exe .\cmd\tunnel

有 Make 时也可以 make test / make build(后者交叉编译 Windows amd64)。

2. 部署 PHP 隧道

把 ntunnel_mysql.php 放到 Web 根目录或由反向代理转到该文件。编辑文件顶部:

$accessToken = '换成足够长的随机串';
$allowTestMenu = false;

浏览器访问(应看到 403 文本,说明门禁在工作):

https://你的域名/ntunnel_mysql.php

带正确 token、无 POST 时,脚本返回二进制错误包(errno 202),而不是 HTML 测试页。

URL 路径可以重写。客户端只要 POST 到配置里的完整 URL(必须带 ?token=)。例如:

https://example.com/ntunnel_mysql.php?token=YOUR_SECRET
https://ntunnel.example.com/?token=YOUR_SECRET

Token 只 放在 query string,不要放 POST body,也不要用 Authorization 头。PHP 只读 $_GET['token']。

3. 填写配置

双击 dist\mysql-tunnel.exe 会打开配置窗口(无参数时默认如此)。把隧道 URL、远程库账号等填进表单,点 保存(写入 exe 旁的 config.yaml)。窗口可最小化到任务栏;点关闭会藏到托盘,监听可继续。托盘图标双击还原,右键退出。

也可以复制模板后用编辑器改:

copy config.example.yaml config.yaml

不要把 config.yaml 提交进 Git(已在 .gitignore)。密钥只放在 yaml 里,不会写进 exe。

4. 验证隧道

在配置窗口点 测试连接,或在仓库根目录(或把 config.yaml 拷到 exe 同级目录):

dist\mysql-tunnel.exe --test

成功时应类似:

mysql-tunnel --test
Tunnel URL: https://example.com/ntunnel_mysql.php?token=***
Tunnel: OK
Remote MySQL: OK
Query SELECT 1: OK

失败时先看「常见问题」。日志默认写到 logs\tunnel.log(密码和 token 会被打码)。

5. 日常启动

推荐: 双击 mysql-tunnel.exe,在窗口里点 启动。可最小化;关窗口进托盘,进程还在。

不要窗口、前台监听(关控制台即退出):

dist\mysql-tunnel.exe --console
dist\mysql-tunnel.exe --config config.yaml

Windows 服务(开机自启、后台运行): 先填好 config.yaml,再以管理员运行:

install-service.bat

或:

dist\mysql-tunnel.exe --install --config config.yaml

安装时会把 config.yaml 复制到 exe 同目录(dist\config.yaml),服务始终读这份配置,不依赖当前工作目录。改配置后执行:

dist\mysql-tunnel.exe --restart

其它:

dist\mysql-tunnel.exe --stop
dist\mysql-tunnel.exe --start
dist\mysql-tunnel.exe --uninstall

卸载也可用 uninstall-service.bat(需管理员)。请先 --test 全绿再安装服务。


配置说明

完整示例见 config.example.yaml。

listen:
  host: 127.0.0.1
  port: 13306

local_auth:
  username: root
  password: "123456"

tunnel:
  url: "https://example.com/ntunnel_mysql.php?token=YOUR_TOKEN"
  encode_base64: false
  tls_insecure_skip_verify: false

remote:
  host: "mysql"
  port: 3306
  username: "root"
  password: "YOUR_PASSWORD"
  database: "your_database"
  charset: "utf8mb4"

options:
  request_timeout: 30
  max_connections: 5
  log_level: info
  log_sql: false
  log_file: logs/tunnel.log

listen — 本机给影刀听的地址

字段 默认 说明
host 127.0.0.1 只应绑回环。0.0.0.0 / :: 会被拒绝。非回环地址会打启动警告。
port 13306 影刀里填的端口。可改成未被占用的端口(如 3309)。

CLI --host / --port 会覆盖这两项。

local_auth — 影刀连本机时用的账号

这是 mysql-tunnel 自己校验 的用户名/密码,不必 和远程 MySQL 相同。远程凭据只写在 remote.*。

影刀数据源填这里的用户名和密码(与远程 MySQL 账号可以不同)。

tunnel — HTTP 隧道

字段 说明
url 完整 URL,必须含 ?token=...,且与 PHP $accessToken 一致。支持 http 和 https。
encode_base64 true 时给每条 SQL 做 Base64 并带 encodeBase64=1,用于部分 WAF。默认 false。
tls_insecure_skip_verify true 时跳过 HTTPS 证书校验。仅排障用,启动会打 SECURITY 警告。生产保持 false。

remote — PHP 主机眼里的 MySQL

最容易配错的一组。 host 是 PHP 所在机器 访问 MySQL 时用的地址,不是你笔记本上的公网 IP。

常见部署 remote.host 应填
PHP 与 MySQL 同机 127.0.0.1 或 localhost
Docker Compose 服务名 mysql mysql
内网另一台库 那台机在 PHP 所在网段的内网 IP / 主机名
字段 说明
port 远程 MySQL 端口,默认 3306。
username / password 远程 MySQL 账号。密码可为空(若库允许)。
database 默认库;空字符串则 PHP 不 select_db。
charset 本客户端用 SET NAMES 补发。PHP 本身仍先 UTF8。utf8 / utf8mb3 不再额外发 SET。

options

字段 默认 说明
request_timeout 30 单次 HTTP 超时(秒),须 > 0。大查询可加大。
max_connections 5 本机并发连接上限。须 >= 1。
log_level info debug / info / warn / error。
log_sql false true 时记录 SQL 文本(仍会脱敏 token/密码)。排障后再关掉。
log_file logs/tunnel.log 同时写 stdout 和该文件。空路径则只打控制台。

Windows 启动时会提醒:config.yaml 含密钥,请用 NTFS 权限限制为本机账户可读。


命令行

mysql-tunnel.exe
mysql-tunnel.exe --gui
mysql-tunnel.exe --gui --config config.yaml
mysql-tunnel.exe --console
mysql-tunnel.exe --config config.yaml
mysql-tunnel.exe --host 127.0.0.1 --port 13306
mysql-tunnel.exe --test
mysql-tunnel.exe --version
mysql-tunnel.exe -h

Windows 下无参数(双击)打开配置窗口。--config 且未加 --gui 时仍走命令行监听。

参数 说明
--gui 打开配置窗口(编辑 yaml、测试、启动/停止、最小化/托盘)。
--console 前台监听,不打开窗口。
--config 配置文件路径。CLI 默认当前目录 config.yaml;GUI 默认 exe 旁。
--host 覆盖 listen.host。
--port 覆盖 listen.port。
--test POST actn=C,再 POST actn=Q + SELECT 1。不监听端口。
--version 打印版本并退出。
--install / --uninstall / --start / --stop / --restart Windows 服务(需管理员安装)。

退出码:0 成功,1 配置/隧道/查询失败,2 参数解析错误。

--test 分层含义:

输出 含义
Tunnel: OK HTTP 通了,响应是 Navicat 二进制头(magic=1111)。
Remote MySQL: OK PHP 连上了 remote.*。
Query SELECT 1: OK 查询返回一行一列 "1"。
Tunnel: FAIL 网络、403、token、HTML 拦截页、非法二进制。
Remote MySQL: FAIL 隧道层正常,但 MySQL 账号/主机/库不对(如 1045)。

脚本 version 不是 202/206 时会提示并继续(magic 仍须正确)。


给影刀配置

不要 把影刀的 MySQL 主机填成 HTTP 隧道 URL。影刀只会说 MySQL 协议。

影刀字段 填什么
主机 127.0.0.1(与 listen.host 一致)
端口 13306(与 listen.port 一致)
用户名 local_auth.username
密码 local_auth.password
数据库 可选;不填则用配置里的 remote.database

流程:配置窗口「测试连接」全绿(或 --test)→ 点「启动」保持运行(可最小化/托盘)→ 影刀再连本机端口。

本机命令行客户端也可:

mysql -h 127.0.0.1 -P 13306 -u root -p
SELECT 1;

目录结构

cmd/tunnel                 入口:GUI / --console / --test / Windows 服务
internal/app               测试隧道、启动本机监听(CLI 与 GUI 共用)
internal/gui               Windows 配置窗口 + 托盘
internal/config            YAML 加载/保存、校验、URL 脱敏
internal/logger            文件日志,脱敏 token / 密码 / Authorization
internal/tunnel            HTTP 客户端(TLS、超时、keep-alive)
internal/navicat           actn=C / actn=Q、二进制解析、mock 隧道
internal/mysqlserver       本机 MySQL 握手 / 认证 / COM_QUERY / 结果集
internal/winsvc            Windows 服务安装与运行
ntunnel_mysql.php          部署到远端的隧道脚本
config.example.yaml        配置模板
docs/PROTOCOL.md           协议对照 PHP 源码的说明
tests/README.md            单测范围说明
build.bat                  Windows 一键测试 + 编译

自动化测试

go test ./...

包内测试(internal/.../*_test.go):

  • config:YAML、监听绑定规则、URL 脱敏
  • logger:token / 密码脱敏
  • tunnel:HTTP 客户端、Navicat magic 头嗅探
  • navicat:GetBlock / 头解析、actn=C/Q、mock ntunnel_mysql.php
  • mysqlserver:握手、native 密码、COM_QUERY/PING/QUIT、ResultSet、与 mock 隧道联调;另用官方 Go MySQL 驱动做兼容性测试

Mock 覆盖:SELECT 1、NULL、BIGINT(文本)、DECIMAL(文本)、utf8mb4 中文、SQL errno 1064、DML OK、错误 token(204)。

--test 才打真实隧道;单测默认不访问外网。


常见问题

现象 原因与处理
config error: ... YAML 缺必填项或非法。对照 config.example.yaml。
HTTP 403 文本 invalid or missing token GET 无 token 或 token 错。检查 URL ?token= 与 $accessToken。
二进制 errno 204 invalid access token POST 带了错误 token(HTTP 往往仍是 200)。
二进制 errno 202 invalid parameters 缺 actn / host / port / login。
MySQL 1045 remote.username / password 被远程拒绝。
Tunnel: FAIL 且提示 HTML URL 指到了登录页、WAF 拦截页或错误的 PHP 路径。用浏览器看该 URL(无 token 应 403 纯文本)。
连得上隧道但连不上库 remote.host 填了公网 IP。应填 PHP 主机访问 MySQL 的地址。
中文乱码 把 remote.charset 设为 utf8mb4(客户端会发 SET NAMES utf8mb4)。
超时 加大 options.request_timeout;大 SELECT 会占满 PHP 内存。
事务丢了 正常现象:每次 HTTP 都是新连接。事务必须写在同一次请求里(本机多语句映射是后续阶段)。
listen.host must not be 0.0.0.0 禁止对全网卡暴露本机 MySQL 口。保持 127.0.0.1。
Go is not on PATH 安装 Go 1.24+ 后重开终端再跑 build.bat。

隧道层 errno(PHP 定义,不是 MySQL):

errno 含义
201 PHP 版本过低
202 参数无效
203 服务器无 MySQL 扩展
204 token 无效
其它 一般为 mysqli 错误号(1045、1049、1146、1064…)

安全

  • 默认只监听 127.0.0.1。绑定非回环等于把远程库暴露给能连上该端口的人(仅靠 local_auth)。
  • 日志不写 MySQL 密码、HTTP token、Authorization。出错信息也会脱敏。
  • 默认校验证书。tls_insecure_skip_verify: true 仅临时排障。
  • ntunnel_mysql.php 里的 $accessToken 视为密钥。仓库里的默认值必须在部署前改掉。 脚本泄露或曾公开过旧 token 时立刻轮换,并同步改 config.yaml。
  • 不要提交 config.yaml。示例文件里只放占位符。本地 config.yaml / logs/ / dist/ 已在 .gitignore。
  • Token 出现在 URL 中,可能进入 Web 访问日志。用 HTTPS,并限制 PHP 主机访问来源。

能力与限制

当前 --test / Navicat 客户端已支持:连接探测、任意 SQL 转发(SHOW / DESC / SELECT / DML 都是 actn=Q)。

已知限制(协议如此,不是漏实现):

  • 无服务端会话;预处理语句(COM_STMT_*)要到更后阶段在本机模拟。
  • insert_id / affected_rows 在 PHP 里是 32 位,超大 AUTO_INCREMENT 会截断。
  • BIGINT / DECIMAL 以 文本 传递,客户端不解析成 Go 数值。
  • 响应无 SQLSTATE;本机侧多数会用 HY000。

开发阶段(已完成)

  1. 协议调研 — 见 docs/PROTOCOL.md
  2. Navicat 客户端 actn=C / actn=Q + 解析
  3. 本机 MySQL 服务(握手、认证、COM_QUERY、PING、QUIT)
  4. 接到隧道客户端 + ResultSet / DML / COM_INIT_DB
  5. Windows GUI / 托盘 / 服务安装
  6. 构建脚本与端到端测试(go test + tests/)

已知后续方向:预处理语句本地模拟、跨 HTTP 事务(协议本身不支持)、多语句拆分策略。

实现约定:只说 Navicat 隧道这一套协议——token 放 query、POST application/x-www-form-urlencoded、解析 16 字节 EchoHeader + GetBlock,不发明 JSON API。

License

MIT — 见 LICENSE。 )

S
Description
No description provided
Readme MIT
415 KiB
Languages
Go 85.9%
Python 12.7%
Batchfile 1.3%
Makefile 0.1%