libknock/libknock
GitHub: libknock/libknock
一款专为 Go 应用设计的可嵌入式 TCP 应用前认证 SDK,在连接建立与应用协议启动之间执行身份验证和准入控制。
Stars: 2 | Forks: 0
# libknock
[](LICENSE)
[](LICENSE)
[](https://pkg.go.dev/github.com/libknock/libknock)
源码基于 BSL-1.1 可用。允许生产环境使用,但在其于 2030-05-15 转换为 Apache-2.0 之前,限制将 libknock 或其衍生作品作为托管或管理服务提供。请参阅 [LICENSE](LICENSE) 和 [许可说明](docs/license.md)。
专为 Go 应用设计的可嵌入式 TCP 应用前认证 SDK。
它负责在 TCP 连接建立之后、应用协议启动之前,对一个紧凑的二进制帧进行身份验证。认证成功后,调用方将获得一个标准的 `net.Conn`,并继续执行其自定义协议栈:纯 TCP、TLS、HTTP、gRPC、自定义 RPC、agent 连接、游戏协议、类数据库协议,或任何基于 `net.Conn` 构建的协议。
`libknock` 不会解析、修改或接管应用载荷。它仅在 TCP 连接边界执行准入步骤,随后将纯净的连接交还给嵌入式应用。
## 依赖模型
libknock 使用 Go modules 作为主要依赖路径。标准源码归档适用于常规的 Go module 用户。配套的 `with-vendor` 归档包含 `vendor/`、`vendor/modules.txt`、`go.work` 和 `go.work.sum`,适用于离线审查、可重现的本地审计、LLM 辅助集成以及受限的 CI 环境。在验证 vendor 归档时,请从工作区根目录使用 `go test -mod=vendor ./...` 和 `go vet -mod=vendor ./...`。请保持启用 `go.work`;在工作区 vendor 模式下不要使用 `GOWORK=off`。
对于编码 agent 和 LLM 辅助工作,请从 [`llms.txt`](llms.txt)、[`docs/llms.md`](docs/llms.md) 和 [`docs/agents/AGENTS.md`](docs/agents/AGENTS.md) 开始。这些文件描述了受支持的集成入口点、禁止使用的捷径、vendored 工作区要求以及特定任务的方案指南。
## 提供的功能
- 用于服务端 TCP 服务的已认证 `net.Listener` 包装器。
- 用于客户端的已认证 `net.Dialer` 包装器。
- 用于自定义连接流水线的独立 `ServerAuth` 和 `ClientAuth` 函数。
- 用于 TCP 认证的核心根包 SDK 入口点。
- 用于高级准入路径的可选 knock、firewall、gate 和 relay 包。
- 防重放保护、时间戳验证、节点元数据、事件钩子和策略钩子。
- 位于独立模块中的可选 Prometheus 适配器。
## 安装
```
go get github.com/libknock/libknock
```
## 最简服务端
```
package main
import (
"log"
"net"
"time"
libknock "github.com/libknock/libknock"
)
func main() {
secret := []byte("0123456789abcdef0123456789abcdef")
ln, err := net.Listen("tcp", ":9000")
if err != nil {
log.Fatal(err)
}
ln, err = libknock.NewListener(ln, libknock.ServerConfig{
ServerPort: 9000,
Secrets: libknock.NewStaticSecretResolver(map[string][]byte{
"client-001": secret,
}),
ReplayCache: libknock.NewMemoryReplayCache(5 * time.Minute),
})
if err != nil {
log.Fatal(err)
}
for {
conn, err := ln.Accept()
if err != nil {
log.Fatal(err)
}
go handleConn(conn)
}
}
```
`NewListener` 会直接返回启动验证错误,并在未提供时创建一个由监听器拥有的重放缓存。`WrapListener` 依然可用,作为便捷的 `net.Listener` 包装器;配置错误会在 `Accept` 时暴露。如果您直接调用底层 `ServerAuth` 函数,请自行提供共享的 `ReplayCache`。
## 最简客户端
```
package main
import (
"context"
"net"
"time"
libknock "github.com/libknock/libknock"
)
func dial(ctx context.Context) (net.Conn, error) {
secret := []byte("0123456789abcdef0123456789abcdef")
d := libknock.Dialer{
Base: &net.Dialer{Timeout: 5 * time.Second},
Config: libknock.ClientConfig{
ClientID: "client-001",
Secret: secret,
ServerPort: 9000,
AuthTimeout: 3 * time.Second,
},
}
return d.DialContext(ctx, "tcp", "127.0.0.1:9000")
}
```
## 根包与高级包
根包刻意保持精简。它提供了常规的 SDK 路径:`NewListener`、`WrapListener`、`WrapListenerE`、`ServerAuth`、`ClientAuth`、`Dialer`、`ServerConfig`、`ClientConfig`、`PeerInfo`、`NewServer`、`NewMemoryReplayCache` 和 `NewStaticSecretResolver`。
高级准入功能位于子包中。使用 `auth` 处理协议选择器和高级认证钩子,使用 `gate` 处理监听器组合模式,使用 `relay` 处理代理式网关,使用 `firewall` 处理平台后端,使用 `knock` 处理 knock 发送器/监听器,使用 `observability` 处理网关事件。
```
import (
libknock "github.com/libknock/libknock"
"github.com/libknock/libknock/gate"
)
ln, err := gate.Listen(ctx, gate.Config{
Mode: gate.AuthOnly,
Auth: libknock.ServerConfig{
ServerPort: 9000,
Secrets: libknock.NewStaticSecretResolver(map[string][]byte{"client-001": secret}),
},
})
```
## TCP 认证协议
`libknock` 支持两种 TCP 应用前认证协议:
| 协议 | 名称 | 简介 |
| --- | --- | --- |
| v1 | `tcp-auth-frame-v1` | 带有 AEAD 密封认证元数据的固定二进制帧。 |
| v2 | `tcp-auth-envelope-v2` | 支持路由提示和固定大小桶填充的密封信封。 |
两种协议都提供客户端密钥验证、时间戳窗口验证、防重放保护、可选的 knock 会话绑定、节点元数据、事件钩子以及可选的服务器证明。
客户端通过 `ClientConfig.Protocol` 选择协议。服务端通过 `ServerConfig.Protocol` 选择首选协议,并通过 `ServerConfig.AcceptProtocols` 选择接受的协议。默认的 TCP 认证协议是 `tcp-auth-envelope-v2`。
## UDP knock 帧
UDP knock 使用带有 AEAD 密封载荷的二进制数据报帧。相同的 UDP 帧系列由以下组件使用:
- `udp`
- `udp-seq`
- `udp-passive`
- `udp-passive-seq`
该帧携带经过身份验证的元数据,例如客户端身份哈希、方法、时间戳、受保护端口、可选的会话 ID、序列字段和扩展。
## Knock 方法支持
| 方法 | 简介 | 绑定 UDP socket | 额外权限 | 扫描行为 |
| --- | --- | --- | --- | --- |
| `tcp-syn` | 单次 TCP SYN 形态的 knock。 | 否。 | 在参与的平台上需要原始数据包或数据包捕获能力。 | 不开放 UDP 端口;TCP SYN 行为取决于具体平台和防火墙。 |
| `tcp-syn-seq` | 多部分 TCP SYN 形态的序列 knock。 | 否。 | 需要原始数据包或数据包捕获能力;在需要多次短窗口尝试时非常有用。 | 同 `tcp-syn`,并带有序列聚合。 |
| `udp` | 通过常规 UDP socket 的单次 UDP knock。 | 是。 | 否。 | 扫描时可能会发现 UDP knock 端口处于可达状态。 |
| `udp-seq` | 多部分 UDP 序列 knock。 | 是。 | 否。 | socket 可见性同 `udp`;具有更强的短窗口准入信号。 |
| `udp-passive` | 在服务端通过数据包捕获读取 UDP knock。 | 否。 | 需要 root、`CAP_NET_RAW` 或 pcap/BPF 权限。 | 启用 `DropUDPKnockPort` 时,扫描应表现为 DROP,同时捕获器仍能观察到流量。 |
| `udp-passive-seq` | 在服务端通过数据包捕获读取多部分 UDP 序列。 | 否。 | 需要 root、`CAP_NET_RAW` 或 pcap/BPF 权限。 | 被动扫描行为,并带有序列聚合。 |
对于大多数部署场景,在考虑被动或原始数据包方法之前,请优先从 UDP knock 开始。
## 功能状态
| 功能 | 状态 |
| --- | --- |
| 已认证的监听器 | stable |
| Dialer | stable |
| TCP auth frame v1 | stable |
| TCP auth envelope v2 | release candidate |
| UDP knock / UDP 序列 | release candidate |
| 仅 Knock 认证门控 | release candidate |
| 防火墙支持的门控 | 特定平台 / 未经完全验证 |
| UDP 被动 knock | experimental / 未经完全验证 |
| TCP SYN knock | experimental / 未经完全验证 |
| Windows 数据包捕获集成 | experimental / 未经完全验证 |
| macOS 被动捕获集成 | experimental / 未经完全验证 |
## 门控模式
| 模式 | 描述 |
| --- | --- |
| `auth-only` | TCP 连接必须在应用程序接收它们之前通过 libknock 的 TCP 身份验证。 |
| `knock-auth-only` | 传输层的 TCP 保持打开,但客户端必须先 knock,然后再通过 TCP 身份验证,应用程序才会接收它们。不更改任何防火墙规则。 |
| `knock-firewall-auth` | 成功的 knock 会打开短暂的防火墙窗口,随后必须通过 TCP 身份验证。 |
| `knock-firewall-only` | 成功的 knock 会打开短暂的防火墙窗口,应用程序随后接收匹配的 TCP 连接。 |
`knock-auth-only` 不是一种端口隐藏模式:SYN 扫描仍会将 TCP 端口报告为打开状态,但未经验证的客户端无法访问应用程序协议。它不需要 root 或 `CAP_NET_ADMIN` 权限,适用于容器、受限的 VPS 实例、Windows/macOS 部署,并在 `auth-only` 的基础上增加了短暂的 knock 会话要求。当需要基于防火墙的端口门控时,它不能替代 `knock-firewall-auth`。
`knock-firewall-auth` 和 `knock-firewall-only` 需要真实的防火墙后端。`auth-only` 和 `knock-auth-only` 可以使用 `firewall.Noop{}`。
重复的有效 knock 会从最近接受的 knock 开始续期防火墙允许窗口;来自早期 knock 的过期定时器不会撤销已续期的租约。
## 中继网关
`relay.Gateway` 是一个可选的 TCP 转发组件。它监听一个地址,执行 libknock 身份验证和可选的 knock/防火墙处理,随后连接到上游 TCP 服务。
```
gw := relay.Gateway{
Listen: ":9000",
Upstream: "127.0.0.1:19000",
Auth: serverAuthConfig,
Firewall: firewall.Noop{},
}
err := gw.Run(ctx)
```
当受保护的上游是独立的 TCP 服务,而不是直接内嵌 `libknock` 的应用时,请使用 relay。
## 文档
- 编码 agent:[docs/agents/AGENTS.md](docs/agents/AGENTS.md),[集成指南](docs/agents/integration-guide.md),[反模式](docs/agents/anti-patterns.md),[方案指南](docs/agents/recipes/)
- 示例:[examples/README.md](examples/README.md)
- [文档索引](docs/README.md)
- [快速开始](docs/getting-started.md)
- [用例](docs/use-cases.md)
- [API 参考](docs/api.md)
- [API 范围与兼容性](docs/api-surface.md)
- [兼容性策略](COMPATIBILITY.md)
- [协议](docs/protocols.md)
- [门控与中继](docs/gate-and-relay.md)
- [Knock 方法](docs/knock-methods.md)
- [防火墙后端](docs/firewall.md)
- [可观测性](docs/observability.md)
- [生产环境部署](docs/production.md)
- [故障排除](docs/troubleshooting.md)
- [已知限制](docs/known-limitations.md)
- [验证矩阵](docs/validation-matrix.md)
- [性能说明](docs/performance.md)
- [路线图](docs/roadmap.md)
- [发行说明](docs/release-notes.md)
- [发布检查清单](docs/release-checklist.md)
- [开发指南](docs/development.md)
## 防火墙说明
`nftables` 和 `ipset-iptables` 支持基于超时的规则。普通的 `iptables` 后端依赖 libknock 的 gate/relay 定时器来撤销 ACCEPT 规则,并在启动/关闭时执行托管链清理,因此如果进程非正常退出,在再次执行清理之前可能会残留临时规则。对于需要内核强制执行过期的生产环境部署,请优先使用 `nftables` 或 `ipset-iptables`。
## 仓库结构
```
protocol/ binary protocol codecs and cryptographic helpers
auth/ server/client authentication, replay cache, secret resolvers
netx/ listener, dialer, buffered connection behavior
knock/ knock senders and listeners
firewall/ firewall backend interfaces and implementations
gate/ SDK listener composition modes
relay/ optional TCP relay gateway
policy/ limiter and ban policy adapters
observability/ event interfaces and metrics adapters
cmd/knock-proxy command entrypoint
examples/ integration examples
```
## 测试
```
scripts/check.sh
```
若需更短的编辑循环:
```
go test ./...
go vet ./...
go test -race ./auth ./firewall ./knock ./netx ./policy ./protocol ./relay
```
Prometheus 和 gRPC 集成检查位于独立的模块中:
```
go -C observability/prometheus test ./...
go -C test/integration/grpc test ./...
```
## 兼容性命令
`cmd/knock-proxy` 是用于简单客户端/服务端代理部署的兼容性调用器。它并非完整的旧版 knock-proxy 产品,也不会托管每一个可选的集成。当嵌入式应用需要自定义生命周期、指标、策略或应用协议处理时,应优先直接使用 SDK 包。
标签:EVTX分析, Go, Ruby工具, TCP连接, 内核驱动, 开发组件, 日志审计, 网络协议, 自定义请求头