问题概述
UpdateUsers 把 数组下标(int)写进了连接的 ctx 作为用户标识。下标在热更新(添加/删除用户)后会指向不同的人,导致:
- 流量归因错位:原本绑定到下标 3 的长连接,更新后被记到新的下标 3 用户头上。
- 越界 panic:新列表更短时,h.userNameList[userID] 直接越界,goroutine panic。由于 sing-quic/hysteria2.Service 的 accept 循环没有 recover(),整个 sing-box 进程会崩溃。
只影响 cedar2025 fork(fork 自行加的 UpdateUsers 接口),上游 sagernet/sing-box 没有这段代码。同分支的 anytls 用法是对的,可以作为参考。
受影响协议
┌───────────┬─────────────────┬─────────────┐
│ 协议 │ ctx 类型 │ 是否有 bug │
├───────────┼─────────────────┼─────────────┤
│ hysteria2 │ int (数组下标) │ ❌ │
├───────────┼─────────────────┼─────────────┤
│ tuic │ int (数组下标) │ ❌ │
└───────────┴─────────────────┴─────────────┘
根因代码
protocol/hysteria2/user.go:
for index, user := range users {
userList = append(userList, index) // ← 把下标当 ID
userNameList = append(userNameList, user.Name)
userPasswordList = append(userPasswordList, user.Password)
}
h.service.UpdateUsers(userList, userPasswordList)
h.userNameList = userNameList
protocol/hysteria2/inbound.go:158-164:
userID, _ := auth.UserFromContextint // 数组下标
if userName := h.userNameList[userID]; userName != "" { // ← 越界点
metadata.User = userName
}
protocol/tuic/user.go 同模式。
复现路径
- inbound 启动时有 5 个用户,user[3]=Alice 建立长连接,ctx 里存 userID=3。
- 面板下发新用户列表(删掉前面某个用户),现在 user[3]=Bob。
- Alice 的已有连接继续传输数据,但 userNameList[3] 返回 Bob——流量计到 Bob 头上。
- 若新列表只剩 3 个用户,userNameList[3] 越界 → panic → 进程退出。
修复方向
参考 protocol/anytls:把 ctx 里存的标识换成稳定的字符串(用户名/UUID),而不是数组下标。
- 方案 A(推荐):UpdateUsers 与 auth.UserFromContext 都改用 string,下游用 map 查找用户名 → 密码/配置。
- 方案 B:连接建立时立即把用户名"钉"在连接对象上,后续不再回查数组。
问题概述
UpdateUsers 把 数组下标(int)写进了连接的 ctx 作为用户标识。下标在热更新(添加/删除用户)后会指向不同的人,导致:
只影响 cedar2025 fork(fork 自行加的 UpdateUsers 接口),上游 sagernet/sing-box 没有这段代码。同分支的 anytls 用法是对的,可以作为参考。
受影响协议
┌───────────┬─────────────────┬─────────────┐
│ 协议 │ ctx 类型 │ 是否有 bug │
├───────────┼─────────────────┼─────────────┤
│ hysteria2 │ int (数组下标) │ ❌ │
├───────────┼─────────────────┼─────────────┤
│ tuic │ int (数组下标) │ ❌ │
└───────────┴─────────────────┴─────────────┘
根因代码
protocol/hysteria2/user.go:
for index, user := range users {
userList = append(userList, index) // ← 把下标当 ID
userNameList = append(userNameList, user.Name)
userPasswordList = append(userPasswordList, user.Password)
}
h.service.UpdateUsers(userList, userPasswordList)
h.userNameList = userNameList
protocol/hysteria2/inbound.go:158-164:
userID, _ := auth.UserFromContextint // 数组下标
if userName := h.userNameList[userID]; userName != "" { // ← 越界点
metadata.User = userName
}
protocol/tuic/user.go 同模式。
复现路径
修复方向
参考 protocol/anytls:把 ctx 里存的标识换成稳定的字符串(用户名/UUID),而不是数组下标。