最近整理了一个 Go 库,叫 libknock 。
它解决的问题比较窄: 有些 TCP 服务不太想让随机连接直接打到应用协议解析层,比如内部管理端口、agent/collector 、私有 RPC 、gRPC 服务、自定义 TCP 协议之类。
传统做法一般是:
- 直接暴露 TCP 端口,让 TLS / HTTP / gRPC / 业务协议自己处理
- 前面再套一层 proxy / gateway
- 靠防火墙、VPN 、内网环境隔离
- 或者用 port knocking 临时放行来源 IP
libknock 的思路是把这类能力做成 Go 里的底层网络库,放在 net.Listener / net.Dialer 这一层。
服务端大概是:
ln, _ := net.Listen("tcp", ":9000")
ln = libknock.WrapListener(ln, knockCfg)
for {
conn, err := ln.Accept()
if err != nil {
return err
}
go handleConn(conn)
}
客户端大概是:
d := libknock.Dialer{
Base: &net.Dialer{},
Config: knockCfg,
}
conn, err := d.DialContext(ctx, "tcp", "example.com:9000")
连接建立后,客户端先发一个 binary auth frame 。服务端验证 client secret 、timestamp 、nonce 、replay cache ,通过以后才把一个干净的 net.Conn 交给上层程序。
也就是说,上层协议不用知道 libknock 存在。 如果后面是 TLS ,TLS 看到的还是正常 ClientHello ;如果后面是自定义 TCP 协议,业务 handler 看到的就是自己的业务首包。
除了 TCP 前置认证,也可以组合 port knocking / firewall gate:
client
-> knock
-> temporary allow / session
-> TCP connect
-> TCP pre-auth
-> application protocol
不过它不是传统意义上的“端口敲门工具”。port knocking 只是可选的一层 gate ;核心还是 Go 程序可嵌入的 TCP pre-application authentication SDK 。
主要特性:
- Go 原生
net.Listener/net.Dialer包装 - TCP 建连后、应用协议开始前认证
- binary auth frame
- client secret 校验
- timestamp window
- nonce replay 防护
- 认证失败默认直接关闭连接
- 不解析、不修改上层应用协议
- 可用于普通 TCP 、自定义二进制协议、TLS 、HTTP 、gRPC
- 可选 port knocking / firewall gate / relay gateway
适合的场景大概是:
- 内部管理端口
- agent / collector 长连接
- 私有 RPC / gRPC 服务
- 自定义 TCP 协议
- 数据采集、边缘节点、运维控制面
- 不想让应用协议解析器直接面对随机 TCP 输入的服务
不适合的场景:
- 想要开箱即用保护任意未改造二进制程序,这种更适合 relay/gateway
- 想替代 TLS / mTLS / 业务鉴权
- 想做 VPN 或完整隧道
- 只想要一个传统 knockd 替代品
现在还在早期阶段,主要想看看有没有人对这种“TCP pre-application authentication + optional port knocking”的组合有类似需求。
仓库地址: https://github.com/libknock/libknock
欢迎拍砖,尤其是 Go API 设计、net.Conn 包装方式、port knocking 和 TCP auth 的边界、安全默认值这几块。