Close can hang and leak the WireGuard engine and DERP connection: bump tailscale.com past the netstack close fix, and close the backend regardless

未关闭
#151 0 条评论 0 个 reaction 已指派 0 人 在 GitHub 查看

@ryjen 已经在做这个了。

开始于 2026年10月20日。

  • #3 来自 @ryjen —— 未关闭

评估

难度
4/5
预计耗时
3-5 天
新手友好度
22/100
Issue 类型
缺陷
描述清晰度
基本清楚
活跃度
活跃
技术栈
go

调研方向

Start with locoBackend.Close in tailcat.go (around lines 374-392), where the WireGuard engine, network monitor, dialer and event bus only close after ns.Close() returns, and with Client.Close (around lines 2003-2010), which holds startMu across lb.Close. Bumping the tailscale.com version in go.mod is the first step, and the backend-teardown change needs a test that makes netstack close block. Done means a close that hangs in the netstack still releases the remaining resources and does not block other Client use. The TCP option and abortive-close requests are API design and should be scoped separately.

由索引模型根据 Issue 内容生成。

描述

What is the issue?

Tailcat v0.7.0 requires tailscale.com v1.103.0-pre.0.20260916030321-a2263542f260, and main (52fbad3) requires v1.103.0-pre.0.20260929142145-a0e471a35b8f. Both predate tailscale/tailscale#21668 (merged as fb79793 on 2026-10-13), which fixes tailscale/tailscale#21631: wgengine/netstack.(*Impl).Close blocks forever once more TCP endpoints need a reset than its 512-packet outbound queue holds.

We hit that through Tailcat. A Server holding 497 endpoints in TIME-WAIT closed in about 1 ms; one holding 539 never returned. A server that carried a few hundred short connections in the last minute is enough, since gVisor keeps TIME-WAIT for 60 s.

Requests, roughly in order of value:

  1. Bump tailscale.com past fb79793.
  2. Close the rest of the backend even when the netstack's close blocks. locoBackend.Close (tailcat.go:374-392, v0.7.0) closes the WireGuard engine, the network monitor, the dialer and the event bus only after ns.Close() returns. When that blocks, the process keeps the magicsock UDP sockets, the DERP connection (with its keepalive every 60 to 65 s, reconnecting after each relay restart) and their goroutines until it exits. Measured per stuck Client: about 104 goroutines, 7.5 MiB of live heap and one DERP connection. A program that closes a Client whenever a peer goes away leaks one of these per peer change.
  3. Client.Close holds startMu across lb.Close (tailcat.go:2003-2010), so a close stuck in the backend also blocks any concurrent use of that Client.
  4. A way to set the netstack's TCP options, at least tcpip.TCPTimeWaitTimeoutOption. tailscale.com's netstack.Impl already has SetTransportProtocolOption; a Server/Client field for the TIME-WAIT timeout, or the accessor the TODO at tcpipStackOf (tailcat.go:960-963) asks for, would do.
  5. Optionally, an exported abortive close (linger 0) for the conns Tailcat returns. Related to google/gvisor#15013: Server accepts through tcp.Forwarder, and gVisor drops a new SYN on a 4-tuple the server still holds in TIME-WAIT until TIME-WAIT expires. A client that makes one short connection per request, with the server closing first, sees about 60 x R / 49,536 of its dials stall (R connections a second over one client/server pair): we measured 2.4% at 20/s, 9.7% at 80/s and 36% at 300/s, each stalled dial waiting out the caller's timeout. Setting linger 0 on the client's conn before closing it (through gonet's unexported ep, the field Tailcat's own gonetTCPConnInternals reads) removed the stalls and left no TIME-WAIT on either side: 60,000 short connections over one pair in 4.2 s, none failed. An exported option would avoid the reflection.
Environment

Tailcat v0.7.0; tailscale.com v1.103.0-pre.0.20260916030321-a2263542f260; gVisor v0.0.0-20260915211658-a6f909f08a72; Go 1.27.2; Linux (Docker 29.8) and macOS. Measured with Tailcat's Server and Client in one process over an in-process relay, and between containers.

主要语言
Go
星标
8.8k
派生
404
平均合并
3 小时 48 分钟
30 天内合并 PR
1

环境准备

这个项目没有提供开发容器、Dockerfile 或贡献指南,环境需要你自己搭建:先看它的 README,通用步骤见我们的新手贡献指南。

从这里开始

  1. 先读完整个 Issue,再读项目的贡献指南。
  2. 在 Issue 下留言说明你要接手 —— 这能避免两个人做同样的事。
  3. Fork 仓库,在一个分支上完成修改。
  4. 提交 Pull Request,并在描述里引用这个 Issue 编号。

tailscale/tailcat 的其他 Issue

查看 tailscale/tailcat 的全部 Issue

相似的 Issue

更多 Go Issue

把新 issue 发到你的邮箱

精选适合新手参与的 GitHub issue 摘要。