FN-Knock 的高性能 Go 网关数据面
统一承载 HTTP(S)、WebSocket、TCP 与 UDP 流量,提供认证接入、动态路由、WAF、流量治理和本机 gRPC 控制面。
Important
本仓库是 FN-Knock 的网关进程,不是独立的管理后台或认证服务。启动后会等待 Rust 后端建立 AuthBridge gRPC 长连接;两端必须使用相同的 FN_KNOCK_INTERNAL_RPC_TOKEN。
Go Reauth Proxy 部署在应用与客户端之间,将多个本地、内网或远程服务收口到统一入口。数据面负责高并发转发和安全策略执行,Rust 后端通过仅监听回环地址的 gRPC 控制面管理配置、承载认证决策并对接前端管理界面。
| 能力 | 说明 |
|---|---|
| 多协议代理 | HTTP/HTTPS、WebSocket、TCP、UDP |
| 灵活路由 | Host、路径前缀、Host Location、默认路由与 TCP/UDP 端口规则 |
| 统一认证 | AuthBridge 联合鉴权、预检、登录跳转、认证缓存与高级策略 |
| TLS 入口 | HTTP/HTTPS 同端口、证书热更新、单证书与 Multi-SNI |
| 安全防护 | Coraza WAF、全局黑名单、访问范围、爬虫拦截、限流与 Linux iptables |
| 可观测性 | 访问日志、流量统计、活跃 IP、诊断端点与 Deep Monitor |
| 动态控制 | 配置热更新、原子持久化、本机 gRPC 控制面与健康检查 |
flowchart LR
Client["客户端"] -->|"HTTP(S) / WebSocket<br/>默认 :7999"| Gateway["Go Reauth Proxy<br/>路由 · WAF · 限流 · TLS"]
Client -->|"TCP / UDP<br/>规则监听端口"| Streams["Stream Manager"]
Gateway -->|"Host / Path / Location"| Upstream["上游服务"]
Streams -->|"TCP / UDP"| StreamUpstream["流式上游"]
Gateway <-->|"AuthBridge stream<br/>AuthorizeHttp / VerifyStreamAuth"| Rust["FN-Knock Rust Backend<br/>认证 · 管理 API · Web UI"]
Rust -->|"gRPC 控制面<br/>127.0.0.1 / ::1 :7996"| Control["Gateway Services"]
Control --> Gateway
默认端口:
| 端口 | 角色 | 暴露范围 |
|---|---|---|
7999 |
HTTP/HTTPS/WebSocket 代理入口 | 由 gateway_listener.scope 控制 |
7996 |
内部 gRPC 控制面 | 固定为 127.0.0.1 / ::1 |
7997 |
Rust 认证 HTTP 服务 | 由 auth_config.auth_port 指定 |
代理入口使用 cmux 在同一端口识别明文 HTTP 与 TLS。部署证书后,符合条件的 HTTP 请求会以 307 Temporary Redirect 跳转到 HTTPS。
- Host 精确匹配、路径最长前缀匹配和默认 Host/路径路由
- Host 规则支持独立 Location、可用时段、HTTP/1.1 / HTTP/2 策略和 Host 保留
- 路径规则支持
StripPath、HTML 绝对路径重写和根路径模式 - WebSocket 自动升级与双向转发
- TCP/UDP 监听规则热更新,可按规则启用认证
- 内置应用选择页、悬浮工具栏、图标与多语言响应页面
- Rust AuthBridge 长连接承载 HTTP 和 TCP/UDP 鉴权
AuthorizeHttp联合鉴权,并兼容旧后端的VerifyAuth + PreflightAuth流程- 登录跳转、短时鉴权缓存、Host 高级认证策略与临时授权
- 全局、Host 级访问范围及可信客户端 IP 绕过策略
- 阿里云 ESA、腾讯 EdgeOne 与标准转发头来源 IP 识别
- Coraza WAF:
off、detection、blocking三种模式 - 基于客户端 IP 的令牌桶限流与超限临时阻断
- 全局黑名单、爬虫拦截和常见路径例外策略
- Linux iptables/ip6tables 白名单、黑名单、SSH 防火墙与端口重定向
- 请求日志、流量计数、活跃连接/IP、5xx 统计
- Deep Monitor 按 Host 捕获 HTTP 交换与 WebSocket 帧,支持查询和归档导出
- Go
1.26.10+;go.mod要求包含 2026 年 10 月 HTTP/TLS、模板及证书信任安全修复的工具链 - 可选:Task 用于统一执行构建与测试命令
- 配套的 FN-Knock Rust 后端,用于建立 AuthBridge 和管理网关
- 使用防火墙能力时需要 Linux、
iptables/ip6tables及相应权限
CI 和 task security:vuln 会使用 govulncheck 检查实际可达的 Go/依赖漏洞;若升级依赖或工具链后重新引入已知漏洞,构建会直接失败。
安全更新需要同时更新工具链与模块依赖:当前使用 golang.org/x/net v0.60.0 和 Coraza v3.8.1,覆盖 HTTP/2 及 WAF 请求解析的安全修复。更新后必须重新构建并替换、重启已部署的网关;仅更新主机上的 Go 安装不会修复已有二进制。上游 TLS 保留既有的跳过证书验证设计。
首次启动会在配置路径创建默认 config.json。开发环境也可以从最小配置开始:
{
"rules": [],
"host_rules": [],
"stream_rules": [],
"default_route": "/__select__",
"auth_config": {
"auth_port": 7997,
"auth_url": "/api/auth/verify",
"login_url": "/login",
"logout_url": "/api/auth/logout",
"preflight_url": "/api/auth/preflight",
"auth_cache_ttl_seconds": 1,
"auth_cache_unauthorized_ttl_seconds": 1
},
"admin_port": 7996,
"gateway_listener": {
"scope": "all"
},
"reverse_proxy_throttle": {
"enabled": true,
"requests_per_second": 500,
"burst": 1000,
"block_seconds": 30
}
}先确保 Rust 后端会使用同一个内部 RPC token 连接网关,然后运行:
export FN_KNOCK_INTERNAL_RPC_TOKEN='replace-with-a-long-random-token'
task run -- -proxy-port 7999 -admin-port 7996 -c ./config.json不使用 Task:
FN_KNOCK_INTERNAL_RPC_TOKEN='replace-with-a-long-random-token' \
go run ./cmd/server -proxy-port 7999 -admin-port 7996 -c ./config.jsonNote
网关最多等待 AuthBridge 就绪 60 秒,成功后才开放代理监听端口。没有配套 Rust 后端时,进程会因等待超时退出。
| 参数 | 默认值 | 说明 |
|---|---|---|
-proxy-port |
7999 |
HTTP/HTTPS 反向代理端口 |
-admin-port |
7996 |
内部 gRPC 端口;传 0 时使用配置值 |
-c |
可执行文件目录下的 config.json |
配置文件或配置目录路径 |
-logs-dir |
配置目录下的默认日志目录 | 网关访问日志目录,必须为绝对路径 |
-waf-dir |
配置目录下的 waf |
WAF 规则与状态目录,必须为绝对路径 |
三类规则都通过内部 gRPC 控制面全量更新,并原子写回 config.json。
适合为不同域名映射独立应用:
{
"host": "app.example.com",
"target": "http://127.0.0.1:8080",
"protocol_mode": "auto",
"use_auth": true,
"access_mode": "login_first",
"preserve_host": true,
"visibility": {
"mode": "inherit"
},
"title": "Example App",
"locations": [
{
"path": "/api",
"match": "prefix",
"action": "proxy",
"target": "http://127.0.0.1:9000",
"strip_path": false
}
]
}host为精确域名,不接受路径或通配符protocol_mode可为auto、http1、http2locations可将 Host 下的路径转发到不同上游,或直接返回固定响应disabled、availability、visibility可控制规则启用状态、时段和来源范围basic_auth用于向可信上游注入 Basic Auth,不是面向访客的登录机制
Host 目标中可附带入口路径。例如 http://127.0.0.1:19122/p 仅把公开根请求 / 映射到 /p;/assets/app.js 等非根路径仍按原路径转发。
Host 也可以由网关直接映射单个文件或只读目录:
{
"host": "docs.example.com",
"target_type": "directory",
"static_serve": {
"path": "/srv/fn-knock-static/docs",
"index_files": ["index.html", "index.htm"],
"directory_listing": {
"enabled": true,
"render_readme": true
}
},
"use_auth": true
}target_type为proxy、file或directory;旧配置缺省为proxy。file只响应GET/HEAD /;directory依序寻找默认文档,未找到时才按配置显示目录列表与当前目录的README.md。- 目录映射省略
index_files时,管理服务会将其补齐为index.html、index.htm;显式配置[]会关闭默认文档查找。由于 proto3 的 repeated 字段没有 presence,Go 数据面始终按收到的空列表原样执行,不会再次补默认值。 static_serve.path是网关进程或容器内的绝对路径,不是浏览器所在电脑的路径。容器部署应使用只读挂载,例如-v /host/docs:/srv/fn-knock-static/docs:ro。- 静态服务使用根目录约束打开,不接受点文件、编码分隔符、路径穿越、特殊设备或网关
/__…内部命名空间。根内可见符号链接必须仍解析到同一根内;配置/数据/密钥、系统设备与敏感目录及其祖先/后代不能映射。 - 文件响应支持
HEAD、Range、Last-Modified 与弱 ETag。需要认证的映射使用private, no-store;目录列表和净化后的 README 还会启用严格 CSP。 - 当前版本仅只读响应,不提供上传、删除、重命名、SPA fallback、动态压缩或静态目标自定义响应头;点目录一律拒绝,因此不支持
.well-known。
适合在同一个 Host 下按前缀挂载服务:
{
"path": "/grafana",
"target": "http://127.0.0.1:3000",
"use_auth": true,
"strip_path": true,
"rewrite_html": true,
"use_root_mode": false
}- 多条规则按最长路径前缀匹配
- 非根规则收到
/grafana时会重定向到/grafana/ strip_path删除匹配前缀后再转发rewrite_html为 HTML 中的绝对href、src、action与<base href>补上挂载前缀use_root_mode将命中路径写入__proxy_pathCookie 后跳转到/
根路径没有直接命中时,网关依次考虑默认 Host、__proxy_path、default_route,最后进入 /__select__ 或返回未匹配页面。该行为也可以通过 unmatched_route 调整为直接重置连接。
portal.select_page_enabled 控制内置映射选择页,默认为 true(旧配置缺失时同样开启)。设为 false 后,直接访问 /__select__ 和原本跳转选择页的根路径请求按 unmatched_route.behavior 返回无映射导航的错误页或重置连接。已有映射、默认 Host、自定义默认路由与无映射欢迎页保持原有行为;传送门工具栏仍由 portal.enabled 独立控制。
{
"protocol": "tcp",
"listen_port": 3307,
"target": "127.0.0.1:3306",
"use_auth": true
}protocol 支持 tcp 和 udp。监听端口不能与网关内部端口冲突,也不能转发回相同的本地监听地址。开启 use_auth 后,连接或会话建立前会通过 AuthBridge 执行 VerifyStreamAuth。
- Rust 后端以客户端身份连接
127.0.0.1:7996,建立长生命周期 AuthBridge stream。 - 请求命中
use_auth=true的规则后,Go 将 Cookie、Authorization、来源 IP 和路由上下文发送给 Rust。 - Rust 返回允许、拒绝或重定向决策;未登录用户会进入
/__auth__/login?redirect_uri=...。 /__auth__/*由网关转发到auth_config.auth_port指定的 Rust HTTP 认证服务。
联合鉴权协议通过 authorize_http_v1 capability 协商。滚动升级时应先发布 Rust 后端,再发布 Go 网关;旧 Rust 后端会自动继续使用双调用流程。
HTTP/2 上游连接连续 30 秒未收到帧时,网关发送 PING;10 秒内未收到回应则淘汰该连接,后续请求重新建连。这不是请求总时长限制:能回应 PING 的慢请求、下载和静默 SSE 保持可用。保留 GODEBUG=http2client=0 禁用 HTTP/2 的运维开关。HTTP/1.1 不受影响,网关不增加请求重试层,受影响的在途请求仍可能失败。
探活失败通过运行诊断日志记录 component=proxy、event=http2_upstream_healthcheck_failed、reason_code=conn_close_lost_ping,使用现有日志的重复事件聚合机制。部署新版本后新建的 Transport 默认启用,无需配置迁移。此机制处理连接失去响应,不能解决仍能回应 PING 的上游应用卡顿。
三类反向代理路由失败时默认向 runtime/logs/gateway.jsonl 写入 event=upstream_failure,不需要开启控制台或调试日志。reason_code 区分连接不可用、DNS、超时、EOF 和重置;fields 包含目标 origin(协议、主机和端口)、服务端生成的 trace_id、路由类型、连接阶段,以及可用时的实际 TCP 地址、系统 errno 和规范化错误类别。未知错误不记录原文。取消请求不产生此告警,错误响应和重试策略保持不变。
这是一项专用的故障诊断例外:本地受限权限日志保留上游地址,便于排查容器端口、DNS 和连接问题,但不记录用户信息、URL 路径、查询参数、请求头、请求体或任意错误文本。分享日志前应按需遮蔽内部地址。相同 origin、路由、类别、阶段和错误类型在 60 秒内合并,下一条记录的 count 包含期间抑制的重复次数;沿用有界异步队列、重复键数量上限及日志轮转。队列过载可能丢弃记录,告警次数不能当作请求失败总数。可使用 trace_id 关联已启用的访问日志;未记录本机访问时仍保留此故障诊断。
测试机可显式运行只读对照:FN_KNOCK_TEST_UPSTREAM=https://127.0.0.1:19123/ go test ./pkg/proxy -run '^TestUpstreamLiveProtocolComparison$' -v -count=1。仅接受 loopback HTTPS 根地址,分别验证 HTTP/1.1、HTTP/2 的首次访问、45 秒空闲后的访问和 8 路并发,输出请求失败、连接复用和 PING 失败数量。默认测试不访问现场上游;慢响应、静默 SSE 和断连恢复由 TestTransportFault* 故障注入测试覆盖。
single_active:部署一个活动证书multi_sni:按 SNI 选择多个证书,支持精确域名和合法的单级通配符- 最低 TLS 版本为 TLS 1.2
- Host 规则可按 SNI 独立选择 HTTP/1.1、HTTP/2 或自动协商
- 证书或站点协议热更新后,HTTP/1.1 连接在响应完成后淘汰;HTTP/2 保留进行中的响应,在后续请求上通过 GOAWAY 排空连接,无后续请求时按空闲超时回收。协议不匹配的请求仍返回
421,促使客户端重新协商
如果网关位于 NAT、端口映射或边缘代理之后,可通过公开端口和认证地址修正跳转 URL:
{
"auth_config": {
"public_http_port": 80,
"public_https_port": 443,
"public_auth_base_url": "https://auth.example.com"
}
}WAF 基于 Coraza,从 waf.rules_dir 加载规则包:
| 模式 | 行为 |
|---|---|
off |
不执行 WAF |
detection |
记录命中事件,但不阻断请求 |
blocking |
按规则与异常阈值阻断请求 |
配置支持 paranoia level、入站/出站异常阈值、请求体限制,以及按 Host 或路径前缀停用 WAF。WAF 默认未启用。
reverse_proxy_throttle 对命中的 Host/Path 规则和 /__auth__ 代理路径按客户端 IP 限流,不影响内部 gRPC、应用选择页和未命中页面。超限后连接会在 block_seconds 内被直接拒绝。
Linux 构建提供 firewall.iptables capability,可管理 iptables/ip6tables、自定义链、白名单、黑名单、SSH 防火墙和 TCP 重定向。非 Linux 平台仍注册 FirewallService,但方法统一返回 gRPC Unimplemented。
防火墙链初始化的基础顺序为:
- 放行 loopback
- 放行
ESTABLISHED,RELATED - 放行配置的本地网段、ICMP 和例外端口
- 应用白名单/黑名单规则
- 默认拒绝其余流量
默认自定义链名为 REAUTH_FW,父链为 INPUT 与 DOCKER-USER。
白名单与大规模 SSH 名单优先使用 nft interval set 分类。网关会先在未挂接流量的临时链中探测各地址族的 MARK --set-xmark 和 -m mark 能力;任一地址族明确缺少扩展时,两类策略统一使用逐来源的 iptables/ip6tables 规则。无 nft 时也使用逐来源规则。回退保留访问控制语义,但大规模地域名单的规则数量和匹配成本会增加。
能力结果在 Manager 生命周期内复用,恢复扩展后重启网关即可重新探测。回退会记录 iptables / classifier_fallback / mark_unavailable 诊断事件,包含失败地址族和底层错误。权限、锁竞争、提交或清理失败仍返回错误,不会被当作兼容回退或就绪成功。
防火墙测试:
go test ./pkg/iptables ./pkg/admin
go test -race ./pkg/iptables ./pkg/adminLinux 实际流量回归测试需要 ip、unshare、nft、iptables、ip6tables 及对应 restore 工具,以及 CAP_SYS_ADMIN、CAP_NET_ADMIN。测试启动后先创建独立的网络和挂载命名空间,再建立 veth 客户端;缺失扩展通过命令执行器注入,其余规则和 IPv4/IPv6 TCP 流量由真实内核处理:
go test -c -o /tmp/fn-knock-firewall.test ./pkg/iptables
sudo env FN_KNOCK_FIREWALL_INTEGRATION=1 /tmp/fn-knock-firewall.test \
-test.run='^TestClassifierFirewallIntegration$' -test.v该测试覆盖正常 nft、IPv4/IPv6 MARK 缺失、mark 匹配缺失、无 nft、旧分类规则迁移、名单撤销和清空、SSH 默认动作及其他应用规则保留;不能替代 fnOS 1.2.0800 的安装和启动实机验证。
控制面固定绑定 127.0.0.1 与 ::1,所有 unary、stream 和标准 health 请求都要求携带 FN_KNOCK_INTERNAL_RPC_TOKEN 对应的 metadata。这里没有 HTTP Admin API,浏览器管理后台应访问 Rust 后端提供的 HTTP API。
| 服务 | 职责 |
|---|---|
GatewayControlService |
运行信息、路由、认证、监听范围及网关策略 |
GatewayLogsService |
日志配置、日期查询、分页读取与删除 |
DeepMonitorService |
监控会话、事件流、Payload 与归档 |
SecurityService |
全局黑名单与安全状态 |
TrafficService |
流量统计、Host 活跃 IP |
WafService |
WAF 状态、配置、规则验证与事件流 |
SslService |
证书部署、查询与清除 |
FirewallService |
Linux 防火墙与端口重定向 |
AuthBridgeService |
HTTP/TCP/UDP 鉴权双向流 |
健康检查服务名:
fnknock.gateway.processfnknock.gateway.dataplanefnknock.gateway.auth_bridge
控制协议的唯一来源是相邻 FN-Knock 仓库中的 packages/grpc-contracts/proto/fnknock/v1/gateway.proto。同步生成代码:
# 在 fn-knock 根目录执行
npm run fn-knock:grpc:sync-go
npm run fn-knock:grpc:check-go配置由 gRPC 控制面热更新并原子持久化。运行中的网关应优先通过 Rust 后端管理,避免手工编辑与控制面更新互相覆盖。
| 顶层字段 | 用途 |
|---|---|
rules |
路径前缀规则 |
host_rules |
Host 与 Host Location 规则 |
stream_rules |
TCP/UDP 监听规则 |
default_route |
根路径默认路由,默认为 /__select__ |
auth_config |
认证服务、缓存、公开 URL 与边缘来源 IP 设置 |
admin_port |
内部 gRPC 端口;仅在 -admin-port=0 时作为回退 |
gateway_listener.scope |
all 或 loopback;当前默认 all |
proxy_protocol_force |
强制 PROXY protocol 场景;启用时代理只监听 loopback |
reverse_proxy_throttle |
请求速率、突发容量与阻断时长 |
visibility / visibility_policies |
全局与规则级编译 IP 集策略 |
forwarded_headers / preserve_host |
转发头和原始 Host 策略 |
crawler_blocker / general_blacklist |
爬虫和 IP 黑名单 |
portal / unmatched_route |
应用选择页与未命中行为 |
logging |
访问日志开关、本机请求记录与保留天数 |
waf |
Coraza WAF 模式、规则包和阈值 |
ssl |
single_active / multi_sni 证书部署 |
locale |
内置页面默认语言 |
iptables_chain_name |
Linux 自定义防火墙链名 |
边缘来源 IP 识别由 auth_config.edge_client_ip_enabled 总开关控制。阿里云 ESA 与腾讯 EdgeOne 模式互斥;同时配置时会保留腾讯 EdgeOne。未启用受信边缘模式时,不应信任来自公网客户端自行提交的厂商来源头。
fn-knock 托管的 Cloudflare Tunnel 使用专用 loopback 入口作为请求头信任边界。若 Cloudflare 的 Pseudo IPv4 设置为 Overwrite Headers,网关会在 CF-Connecting-IP 属于 Class E 240.0.0.0/4 时校验并恢复 CF-Connecting-IPv6 中的真实公网 IPv6;头部缺失或异常时保留 Pseudo IPv4 并继续按现有安全策略处理。非托管入口不会信任 CF-Connecting-IPv6,手工配置 Cloudflare 回源时建议将 Pseudo IPv4 设置为 Off 或 Add Header。
| 环境变量 | 说明 |
|---|---|
FN_KNOCK_INTERNAL_RPC_TOKEN |
必填,内部 gRPC 共享 token |
GO_REPROXY_PORT |
-proxy-port 的环境变量默认值 |
FN_KNOCK_GATEWAY_LOGS_DIR |
访问日志绝对目录 |
FN_KNOCK_GATEWAY_WAF_DIR |
WAF 规则与状态绝对目录 |
FN_KNOCK_AUTH_BRIDGE_MAX_IN_FLIGHT |
AuthBridge 排队及等待回复的总调用上限,默认 1024;超出时快速返回现有的队列满错误 |
FN_KNOCK_UDP_BUFFER_BUDGET_BYTES |
UDP 全进程转发报文缓冲预算,包括监听器读区、上游读区、队列及正在写入的报文;默认 0 保留现有会话和队列限制,可按设备内存主动设置 |
FN_KNOCK_UDP_SESSION_IDLE_SECONDS |
UDP 会话空闲期限,默认 120 秒,可设 1–86400 秒;每 10 秒回收过期会话 |
GO_REPROXY_LOG=1 |
开启常规控制台日志 |
GO_REPROXY_DEBUG_LOG=1 |
开启结构化调试日志 |
GO_REPROXY_DEBUG_LOG_DIR |
调试日志目录 |
GO_REPROXY_DIAGNOSTICS_ADDR |
开启 loopback 诊断监听,如 127.0.0.1:6060 |
FN_KNOCK_DISABLE_IPTABLES=1 |
禁用 iptables 能力 |
FN_KNOCK_IPTABLES_USE_SUDO |
控制 iptables 命令是否通过 sudo 执行 |
访问日志由 logging.enabled 控制,默认关闭。TrafficService 提供总入站/出站字节、活跃身份、5xx、Host 活跃 IP 等运行统计。
上述资源环境变量需在进程启动前设置。Linux/macOS 的 UDP 上游使用 Go netpoll 等待就绪,空闲会话不持有 64 KiB 接收区;其他平台保留完整接收区,均不会通过缩小接收区截断大报文。UDP 字节预算不包含 socket 内核内存、会话元数据和已归还的池缓存。启用预算后,空间不足会丢弃新报文;无法取得上游接收区时结束相应会话;连监听器读区都无法预留时监听器创建返回错误。单监听器仍最多 8192 个会话、64 MiB 队列。诊断增加 auth.bridge_in_flight、auth.bridge_in_flight_peak、auth.bridge_in_flight_drops 和 udp.buffer_budget_drops。
普通访问日志查询最多同时执行 4 个扫描,并响应请求取消;页码模式保留精确总数,游标模式按需倒序读取。每次查询的分页尾部缓存最多 8 MiB,超出后改为有界扫描;返回结果的估算预算为 16 MiB,超限会明确报错,可减小 limit 或收窄筛选条件。单行读取上限为 8 MiB。
HTTP 的 SSE、小响应和普通未知长度响应使用 32 KiB 复制缓冲,大下载保留 256 KiB。合并写入的中、大档活跃缓冲共用 64 MiB 预算,不足时继续使用较小档位;HTML 路径重写另有 4 MiB 输出预算,超出时跳过路径重写。Deep Monitor 的在途捕获与写队列共用 64 MiB 预算,捕获容量不足时保留前缀和已观察字节的哈希;写队列或事件元数据预算不足会停止该监控会话并拒绝事件,HTTP 转发继续。活动会话摘要缓存最多每会话 1024 条 / 约 1 MiB,历史详情按需读盘。这些预算不等于进程 RSS 上限,已归还的池缓存、Go 运行时和内核 socket 内存需另行观察。
Deep Monitor 重启恢复时,事件数按日志中连续可读取的完整事件重建;已存储字节按 events.pb 与会话目录内载荷文件的实际大小重建,包含崩溃遗留的尾帧或未提交载荷,不含索引和会话元数据。原活动会话仍以中止状态保留;因 I/O 错误无法确定的统计保留原值,避免将读取失败误报为零。
HTML 206 Partial Content 响应保持原始表示,不进行解码、路径重写或工具栏注入。工具栏仅在可确认的 HTML 上下文注入;SVG/MathML 跟踪超过固定深度或标签名长度、混合 HTML 命名空间或出现在 select 等特殊结构中,以及 HTML 正文中的 CDATA 标记、未知声明等情况,会保守跳过本响应的工具栏。仅含纯文本或标准 CDATA 的 SVG title、desc 不受此限制。
WAF 事件存储的估算总预算为 16 MiB,单事件预算为 256 KiB;超大事件保留安全决定,精简规则与匹配详情并标记 event_details_truncated,总量超限时淘汰最旧事件。
Host 活跃 IP 和登录活跃身份属于有界近似统计,达到容量后优先淘汰较旧记录并批量腾出空间;高基数时统计可能少于实际身份数,不会因此关闭转发连接。
诊断监听提供 /debug/pprof/* 与 /debug/metrics,只接受 loopback 地址,并要求请求携带:
x-fn-knock-internal-rpc-token: <FN_KNOCK_INTERNAL_RPC_TOKEN>
也可使用同值的 Bearer token。未设置 GO_REPROXY_DIAGNOSTICS_ADDR 时不会启动诊断服务。
/debug/metrics 的 runtime 还提供 heap_idle_bytes、heap_released_bytes、stack_inuse_bytes、heap_objects、next_gc_bytes 和 total_alloc_bytes,用于区分堆对象、空闲页及分配速率。读取指标不会触发 GC。
coalescing_buffers 按 capacity_bytes 区分 256 KiB 和 2 MiB 档位,提供 active_bytes、累计 reuse_hits 和 allocations。当前 cache_policy 为 gc_managed,保留原有 sync.Pool 复用行为;由于 GC 可以静默清空池,idle_bytes 和 discards 返回 null,表示无法精确统计,并非零。计数器为独立读取的进程累计值。独立进程的内存与性能对比方法见 memorycheck。
task build # 构建全部支持的平台
task build:mac # macOS ARM64
task build:linux # Linux AMD64
task build:linux-arm64 # Linux ARM64
task build:linux-arm # Linux ARMv7
task build:windows # Windows AMD64
task run # 本地运行
task test # 全量测试
task test:race # Race Detector
task security:vuln # govulncheck 可达漏洞扫描
task security:docker-ro-smoke # Linux 只读挂载 HTTP 冒烟
task bench:hot # 热路径基准测试
task bench:all # 全量基准测试静态路径安全语料可额外用原生 Go fuzzing 复验:
go test ./pkg/staticserve -fuzz=FuzzStaticDirectoryNeverEscapesRoot -fuzztime=30s
go test ./pkg/staticserve -fuzz=FuzzRequestRootNameIsAlwaysLocalAndVisible -fuzztime=30s每个 PR 会在同一 CI runner 上将热路径 benchmark 与目标分支基线进行比较。基线和当前版本会先各执行一轮不计入结果的完整预热,再以交替顺序运行 6 轮独立样本,避免进程启动、CPU 初始调频、固定的先后顺序、热漂移或前序 benchmark 的自适应工作量系统性影响其中一方。每轮采样至少 500 ms。比较器取每个 benchmark 的样本中位数:ns/op 的容差为 10%,B/op 为 15%,allocs/op 为 5%;字节数与分配次数各允许 1 个报告单位的绝对舍入余量。至少有 6 个样本时,延迟还使用固定随机种子的 10,000 次 bootstrap 重采样估计 95% 区间,仅当区间下界也超过 10% 时阻塞,以避免共享 runner 上网络时序波动造成误报。少于 6 个样本时仍严格比较中位数。缺失的既有 benchmark 同样会使检查失败。这个门禁用于识别相对回退,实际绝对性能仍应以发布前的目标设备测量为准。
项目结构:
cmd/server/ 进程入口、HTTP/TLS 监听与内部 gRPC Server
pkg/proxy/ HTTP(S)、WebSocket、认证和策略执行核心
pkg/staticserve/ 根目录约束文件响应、目录分页与 README 安全渲染
pkg/stream/ TCP/UDP 监听、会话与转发
pkg/admin/ gRPC 控制面服务实现
pkg/rpcbridge/ AuthBridge 与内部 token 校验
pkg/waf/ Coraza 规则加载、运行时与事件
pkg/deepmonitor/ 深度监控会话、存储与归档
pkg/gatewaylog/ 网关访问日志
pkg/iptables/ Linux iptables/ip6tables 管理
pkg/config/ 配置默认值、加载与原子持久化
pkg/grpc/pb/ 生成的 Protobuf / gRPC Go 代码
pkg/response/ 内置页面、工具栏与错误响应
pkg/i18n/ 内置页面多语言资源
- 日志统计的域名、路径等维度键拥有独立字符串存储,避免短键连带保留整条长 URL;已有键更新也遵守此约束。
- 统计缓存同时受最多 30 天和 8 MiB 记账预算约束,优先淘汰最近最少使用的日期;每项空闲 5 分钟后由定时器释放,无需下一次查询触发。超过预算的单日结果仍正常返回,但不缓存。预算采用字符串长度和保守的容器开销估算,不是整个进程的 RSS 硬上限;查询临时对象和仍被读取的快照另计。淘汰后查询会重新扫描日志。
- UA 解析器在首次遇到非空 User-Agent 的统计查询时初始化,随后以紧凑的 radix trie 常驻;无结果的单分支路径合并为边标签,保留原有逐字符跳过规则、优先级和版本解析。
- WAF 关键词匹配的 NFA 在构建后转换为连续、无指针的状态和转移数组,以有界的 32 位索引替代分散对象;沿用原有匹配算法、失败转移和捕获顺序。DFA 构建不变,超出索引范围的输入保留原始表示。
匹配库的固定上游版本、局部改动和等价测试见 UA 解析器说明 与 WAF 自动机说明。task test、task test:race 和 CI 包含这两个子模块的测试。
Linux 网关在服务初始化前默认通过 PR_SET_THP_DISABLE 禁用本进程的透明大页(THP),减少小堆在激进大页合并策略下的 RSS 膨胀。无需增加启动参数;不修改主机全局 THP 设置、GC 参数或 Go 内存软上限。此进程策略也会由网关启动的子进程继承。非 Linux 平台不受影响;内核不支持或限制该调用时,记录警告并继续启动。
需要保留原有大页行为进行性能对照时,可在启动环境设置 GODEBUG=disablethp=0;若已有其他 GODEBUG 配置,追加此项即可,例如 GODEBUG=http2client=0,disablethp=0。重复的 disablethp 项以最后一个有效整数为准;0 仅跳过网关的禁用操作,不解除父进程继承的禁用策略。环境变量本身不会被网关改写。
部署后可查看 /proc/<PID>/smaps_rollup 的 Rss、Anonymous 和 AnonHugePages,结合控制面的存活堆、运行时管理内存及同负载下的延迟比较效果。启动前已经形成的大页不会被此调用主动拆分;不要将全部大页占用视为可节省内存。GOMEMLIMIT 是 Go 运行时软上限,不是进程 RSS 硬上限。
- 内部 gRPC 端口不是 HTTP 服务,不能使用
curl http://127.0.0.1:7996/...访问。 SetRules、SetHostRules、SetStreamRules都是全量替换,不是增量追加。- 反向代理目标可以是外部地址;安全校验只会阻止目标直接指向本机管理端口。请自行限制不可信的管理权限。
- 当前 HTTPS 上游连接会跳过上游证书校验,只应连接受信网络中的上游服务。
- 证书私钥、上游 Basic Auth 和其他敏感配置会持久化到
config.json,请严格控制文件权限与备份范围。 gateway_listener.scope=all会对外监听;不需要局域网访问时应改为loopback。- WAF、黑名单和 iptables 都可能中断真实流量,生产启用前应先使用检测模式或在受控环境验证。
本项目基于 MIT License 开源。