使用Golang实现带有认证机制的TCP私有协议服务

1次阅读

应在net.listener.accept()后立即对conn进行认证拦截,通过setreaddeadline设超时、io.readfull读固定长度认证头、校验hmac与nonce防重放,认证失败即close连接,全程避免bufio.reader和协议解析混用,确保panic或超时时conn必关闭。

使用Golang实现带有认证机制的TCP私有协议服务

go net.Conn 上怎么加认证拦截,不让未授权连接进入业务逻辑

认证必须在 conn.Read 处理业务数据前完成,否则攻击者可直接发恶意 payload 绕过校验。常见错误是把认证逻辑塞进 handler 函数里——那时连接已建立、资源已分配,再拒绝等于白忙。

  • net.Listener.Accept() 后立刻读取固定长度的认证头(比如 16 字节 Token 或 4 字节命令 + 12 字节签名)
  • 设置超时:conn.SetReadDeadline(time.Now().Add(5 * time.Second)),防止客户端不发认证数据卡死 goroutine
  • 认证失败直接 conn.Close(),别 return 错误或继续往下走
  • 避免在认证阶段解析完整协议帧——只读预期字节数,用 io.ReadFull(conn, buf) 而非 conn.Read

如何设计轻量但防重放的 TCP 认证协议(不用 TLS)

私有协议没法直接套 TLS,但裸 token 明文传就是送钥匙。核心矛盾:要快(不能每次连都查 DB),又要防截获重放。

  • 客户端发:timestamp(uint32) + nonce(uint32) + hmac-sha256(key, timestamp|nonce|ip),服务端校验时间戳 ±30 秒 + HMAC 是否匹配 + nonce 是否已用过(用 LRU cache 存最近 1000 个)
  • key 不硬编码在代码里,从环境变量或文件读:os.Getenv("AUTH_KEY"),启动时校验非空
  • 别用 math/rand 生成 nonce——用 crypto/rand.Read(),否则 predictable
  • 时间戳用 uint32 而非 time.Now().unix() 全精度,减少对齐和序列化开销

为什么用 bufio.Reader 做认证后,后续协议解析会丢数据

认证时如果用了 bufio.NewReader(conn),又调了 reader.Peek()reader.Discard(),底层 buffer 里的数据可能被提前消费,导致业务层 conn.Read() 读到残缺帧。

  • 认证阶段坚持用原始 conn.Read()io.ReadFull(),绕过任何缓冲层
  • 认证成功后,再包一层 bufio.NewReader(conn) 交给业务 handler —— 此时连接状态干净
  • 如果协议本身有变长包头(如前 4 字节是 body length),认证完立刻用 binary.Read(conn, binary.BigEndian, &Length) 读长度,别混用 bufio 和 raw conn
  • 调试时打印 len(buf)cap(buf),确认没意外读多

goroutine 泄漏:认证超时没关 conn,panic 没 recover 导致连接

Go 的 net.Conn 不会自动回收,一个没 close 的连接会一直占着 fd 和 goroutine 内存。线上跑几天就 too many open files

立即学习go语言免费学习笔记(深入)”;

  • 所有 Accept() 得到的 conn,必须在 defer 里确保 conn.Close(),哪怕认证中途 panic
  • defer func() { if r := recover(); r != nil { conn.Close() } }() 包住认证逻辑
  • 监听器启动用 http.Server 那套优雅关闭思路不行——TCP server 得自己管,建议封装StructShutdown() 方法,遍历 active conns 关闭
  • lsof -i :port | wc -l 定期核对连接数,和你代码里计数器比对,不一致说明有泄漏

认证逻辑越靠近连接入口越好,但 buffer 管理、超时控制、panic 恢复这三件事,漏掉任何一个,服务就不是“带认证”,而是“带幻觉认证”。

text=ZqhQzanResources