#web-api #tunnel #api-key #dashboard #authentication #daemon #udp #web-server #gout

app goutd

Goutd — the Gout server daemon. Manages tunnels and API keys, serves the web dashboard.

12 releases (6 breaking)

0.7.0 Jul 20, 2026
0.6.0 Jul 13, 2026
0.5.0 Jul 13, 2026
0.4.1 Jul 13, 2026
0.1.0 Jul 12, 2026

#2461 in HTTP server

MIT license

92KB
2K SLoC

Gout

crates.io crates.io crates.io MIT

受 frp 和 rathole 启发的轻量级内网穿透工具。一行命令将本地服务暴露到公网。

特点

  • 零配置客户端gout login server:8080 keygout tcp 4000,无需手写配置文件
  • Web 管理面板:在浏览器中管理 API key、查看活跃隧道、添加备注
  • REST 控制面:隧道通过 HTTP API 创建和销毁,不依赖配置文件热加载
  • 多协议支持:TCP、UDP 和 HTTP(v0.1 中 HTTP 是 TCP 别名)
  • 信号通道架构:每个隧道一条轻量控制连接,通知客户端有新的外部连接;每条连接使用独立的数据通道
  • Token 认证:每个隧道分配一个随机 64 位 token,用于数据通道身份验证
  • 端口池管理:基于空闲池的端口分配,无碎片问题
  • 自动清理:客户端 30 秒内未完成数据通道握手时隧道自动过期;控制连接断开时全部清理

架构

gout (CLI)  ──REST──►  goutd :8080 (HTTP API + Web 面板)
     │                      │
     └──数据通道──────►  goutd :8081 (原始 TCP)

项目是一个 Cargo workspace,包含三个 crate:

  • gout-api — Rust SDK:协议类型、GoutClient(隧道操作)、GoutAdminClient(管理操作)、data_channel(握手/pipe)
  • gout — CLI 客户端:login/tcp/udp/http 子命令;读取 ~/.goutrc
  • goutd — 服务端守护进程:axum HTTP 服务器 + tokio 数据通道 TCP 服务器

安装

cargo install goutd    # 服务端
cargo install gout     # 客户端

或从源码编译:

git clone https://github.com/fb0sh/Gout.git
cd Gout
cargo build -p goutd -p gout

Rust SDK(在 Cargo.toml 中添加):

gout-api = "0.1"

快速开始

服务端

# 在公网 VPS 上运行
goutd

# 输出:
# ──────────────────────────────────────────
#   Initial admin key: sk-xxxxxxxxxxxx
#   Save this key! It won't be shown again.
# ──────────────────────────────────────────
# HTTP server listening on http://127.0.0.1:8080
# Data server listening on 0.0.0.0:8081

Web 面板默认只监听 127.0.0.1,通过 SSH 端口转发访问:

ssh -L 8080:localhost:8080 your-server
# 浏览器打开 http://localhost:8080

客户端

# 通过 Web 面板创建普通 API key,然后:
gout login server.example.com:8080 sk-xxxxxxxxxxxx
gout tcp 4000     # 将本地 localhost:4000 暴露到公网

REST API 参考

所有请求和响应均为 JSON 格式。

认证

两种 key 类型:

Header 类型 用途
X-Admin-Key admin 管理 API key(增删查)
X-Api-Key tunnel 创建/删除隧道

首次启动时自动生成 admin key,打印到 stdout。 通过 Web 面板或管理 API 创建普通 tunnel key。

管理 API

创建 API key

POST /api/v1/keys
X-Admin-Key: <admin-key>
Content-Type: application/json

{"name": "我的笔记本"}
{"success": true, "data": {"key": "sk-xxx...", "name": "我的笔记本"}}

列出所有 key

GET /api/v1/keys
X-Admin-Key: <admin-key>
{"success": true, "data": [{"key": "sk-xxx...", "name": "我的笔记本"}]}

删除 key

DELETE /api/v1/keys/sk-xxx...
X-Admin-Key: <admin-key>
{"success": true, "data": null}

隧道 API

创建隧道

POST /api/v1/tunnels
X-Api-Key: <tunnel-key>
Content-Type: application/json

{"type": "tcp", "local_port": 4000}
{
  "success": true,
  "data": {
    "token": 15735302723313469543,
    "public_port": 10000,
    "data_port": 8081,
    "tunnel_type": "tcp"
  }
}

创建后客户端需要连接数据端口(data_port)完成握手,详见"数据通道协议"。

删除隧道

DELETE /api/v1/tunnels/15735302723313469543
X-Api-Key: <tunnel-key>
{"success": true, "data": null}

隧道类型

type 说明
tcp TCP 隧道,每个外部连接一条独立数据通道
udp UDP 隧道,一条持久数据通道承载数据报帧
http v0.1 等价于 tcp

数据通道协议

创建隧道后的数据通道握手流程:

客户端                           服务端
  │                                │
  │── [token: u64 BE][type: u8] ──►│  握手(9 字节)
  │◄──── [status: u8] ─────────────│  0x01=OK, 0x00=Error
  │                                │
  │  (TCP:信号通道循环)           │
  │◄──── [0x02] ───────────────────│  新外部连接通知
  │                                │
  │  客户端另开连接:                │
  │── [token][type] ──────────────►│  数据通道握手
  │◄──── [0x01] ──────────────────│  OK
  │══════ raw bytes ══════════════│  双向 pipe

UDP 隧道使用帧格式:

[len: u16 BE][data: len bytes]
len = 0 表示关闭信号

Rust SDK (gout-api)

gout-api 是 Rust crate,提供协议类型和客户端。

添加依赖

[dependencies]
gout-api = { git = "https://github.com/fb0sh/Gout" }

GoutClient(隧道操作)

use gout_api::client::GoutClient;
use gout_api::TunnelType;

let gout = GoutClient::new("server.example.com:8080", "sk-xxxx...");

// 创建隧道
let tunnel = gout.create_tunnel(TunnelType::Tcp, 4000).await?;
println!("公网端口: {}", tunnel.public_port);

// 连接数据端口(握手由 data_channel 模块处理)
let mut stream = tokio::net::TcpStream::connect(
    format!("server.example.com:{}", tunnel.data_port)
).await?;

gout_api::data_channel::client_handshake(
    &mut stream, tunnel.token, TunnelType::Tcp
).await?;

// 连接本地服务
let local = tokio::net::TcpStream::connect("127.0.0.1:4000").await?;

// 双向 pipe
gout_api::data_channel::pipe_bidirectional(stream, local).await;

// 删除隧道
gout.delete_tunnel(tunnel.token).await?;

GoutAdminClient(管理操作)

use gout_api::admin::GoutAdminClient;

let admin = GoutAdminClient::new("server.example.com:8080", "admin-key-xxx...");

// 创建 tunnel key
let key = admin.create_key("我的笔记本").await?;
println!("新 key: {}", key.key);

// 列出所有 key
let keys = admin.list_keys().await?;
for k in keys {
    println!("{} ({})", k.name, k.key);
}

// 删除 key
admin.delete_key("sk-xxx...").await?;

数据通道协议(底层)

use gout_api::data_channel;

// 客户端握手
data_channel::client_handshake(&mut stream, token, TunnelType::Tcp).await?;

// 服务端接收握手
let (token, tt) = data_channel::server_receive_handshake(&mut stream).await?;

// 服务端确认/拒绝
data_channel::server_accept(&mut stream).await?;
data_channel::server_reject(&mut stream, "reason").await?;

// 双向 pipe
data_channel::pipe_bidirectional(a, b).await;

开发

cargo build          # 编译全部
cargo test           # 运行所有测试
cargo run -p goutd   # 启动服务端
cargo run -p gout    # CLI 客户端

License

MIT

Dependencies

~18–28MB
~423K SLoC