Skip to content

About

FN-Knock 的 Go 网关数据面:支持 HTTP(S)、WebSocket、TCP/UDP 代理、AuthBridge 认证、WAF、Multi-SNI 与 gRPC 控制面。

Topics

Resources

Stars

6 stars

Watchers

0 watching

Forks

Repository files navigation

Go Reauth Proxy

FN-Knock 的高性能 Go 网关数据面

统一承载 HTTP(S)、WebSocket、TCP 与 UDP 流量,提供认证接入、动态路由、WAF、流量治理和本机 gRPC 控制面。

Go Control Plane Protocols Platforms License

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
Loading

默认端口:

端口 角色 暴露范围
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 保留既有的跳过证书验证设计。

1. 准备配置

首次启动会在配置路径创建默认 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
  }
}

2. 启动网关

先确保 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.json

Note

网关最多等待 AuthBridge 就绪 60 秒,成功后才开放代理监听端口。没有配套 Rust 后端时,进程会因等待超时退出。

3. 启动参数

参数 默认值 说明
-proxy-port 7999 HTTP/HTTPS 反向代理端口
-admin-port 7996 内部 gRPC 端口;传 0 时使用配置值
-c 可执行文件目录下的 config.json 配置文件或配置目录路径
-logs-dir 配置目录下的默认日志目录 网关访问日志目录,必须为绝对路径
-waf-dir 配置目录下的 waf WAF 规则与状态目录,必须为绝对路径

路由配置

三类规则都通过内部 gRPC 控制面全量更新,并原子写回 config.json。

Host 路由

适合为不同域名映射独立应用:

{
  "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、http2
  • locations 可将 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_path Cookie 后跳转到 /

根路径没有直接命中时,网关依次考虑默认 Host、__proxy_path、default_route,最后进入 /__select__ 或返回未匹配页面。该行为也可以通过 unmatched_route 调整为直接重置连接。

portal.select_page_enabled 控制内置映射选择页,默认为 true(旧配置缺失时同样开启)。设为 false 后,直接访问 /__select__ 和原本跳转选择页的根路径请求按 unmatched_route.behavior 返回无映射导航的错误页或重置连接。已有映射、默认 Host、自定义默认路由与无映射欢迎页保持原有行为;传送门工具栏仍由 portal.enabled 独立控制。

TCP / UDP 路由

{
  "protocol": "tcp",
  "listen_port": 3307,
  "target": "127.0.0.1:3306",
  "use_auth": true
}

protocol 支持 tcp 和 udp。监听端口不能与网关内部端口冲突,也不能转发回相同的本地监听地址。开启 use_auth 后,连接或会话建立前会通过 AuthBridge 执行 VerifyStreamAuth。

认证与 TLS

AuthBridge 流程

  1. Rust 后端以客户端身份连接 127.0.0.1:7996,建立长生命周期 AuthBridge stream。
  2. 请求命中 use_auth=true 的规则后,Go 将 Cookie、Authorization、来源 IP 和路由上下文发送给 Rust。
  3. Rust 返回允许、拒绝或重定向决策;未登录用户会进入 /__auth__/login?redirect_uri=...。
  4. /__auth__/* 由网关转发到 auth_config.auth_port 指定的 Rust HTTP 认证服务。

联合鉴权协议通过 authorize_http_v1 capability 协商。滚动升级时应先发布 Rust 后端,再发布 Go 网关;旧 Rust 后端会自动继续使用双调用流程。

上游 HTTP/2 连接探活

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* 故障注入测试覆盖。

TLS 入口

  • 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

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 防火墙

Linux 构建提供 firewall.iptables capability,可管理 iptables/ip6tables、自定义链、白名单、黑名单、SSH 防火墙和 TCP 重定向。非 Linux 平台仍注册 FirewallService,但方法统一返回 gRPC Unimplemented。

防火墙链初始化的基础顺序为:

  1. 放行 loopback
  2. 放行 ESTABLISHED,RELATED
  3. 放行配置的本地网段、ICMP 和例外端口
  4. 应用白名单/黑名单规则
  5. 默认拒绝其余流量

默认自定义链名为 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/admin

Linux 实际流量回归测试需要 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 的安装和启动实机验证。

内部 gRPC 控制面

控制面固定绑定 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.process
  • fnknock.gateway.dataplane
  • fnknock.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 内存与透明大页

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 都可能中断真实流量,生产启用前应先使用检测模式或在受控环境验证。

License

本项目基于 MIT License 开源。

About

FN-Knock 的 Go 网关数据面:支持 HTTP(S)、WebSocket、TCP/UDP 代理、AuthBridge 认证、WAF、Multi-SNI 与 gRPC 控制面。

Topics

Resources

Stars

6 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages