Go ReadMsgUDP 超时返回 n=-1,随后切片 panic
Linux 的 ReadMsgUDP 路径在错误时可能透传负字节数。先处理非nil错误,再校验长度并取切片,不能用 n==0 判断是否读失败,也不要丢失控制信息。
Linux 上 UDP 读取超时后,程序没有进入错误分支,反而在 buf[:n] 报切片边界 panic。先检查判断条件是否写成了 n == 0 && err != nil,并保留 ReadMsgUDP 实际返回的 n 和 error。
受影响的 Go 路径可能返回 n=-1 与非 nil error。Go 1.27.3 的 Unix ReadMsg 实现仍可能透传 recvmsg 的失败计数;超时或另一 goroutine 关闭 socket 都可能经过这条路径。不能假定每个网络读取函数、每个操作系统都在错误时统一返回零。
在构造任何切片之前处理错误
对于这一数据报读取入口,先按应用原来的超时、取消和关闭策略处理 error;只有本次读取成功,再检查有效长度。以下是接收循环中的片段,conn、buf、oob 均由原调用方管理:
n, oobn, flags, peer, err := conn.ReadMsgUDP(buf, oob)
if err != nil {
return err
}
if n < 0 || n > len(buf) || oobn < 0 || oobn > len(oob) {
return fmt.Errorf("invalid datagram lengths: %d, %d", n, oobn)
}
payload := buf[:n]
control := oob[:oobn]
片段需要 fmt,并位于可返回 error 的函数内。payload、control、flags 和 peer 继续交给原协议处理;如果数据或控制信息被截断,应按项目既有 flags 检查拒绝,不能只校验 n 后就认为完整。
零长度 UDP 数据报是合法的。不要把所有 n==0 都当成连接关闭,也不要把 n<0 改成零后照常提交一条“空包”;前者会丢合法消息,后者会把错误伪装成业务输入。
不丢掉原有的 socket 生命周期
超时需要重试、继续等待还是结束,由业务决定。若继续读取,按原方案更新 deadline,避免在已经过期的 deadline 上忙循环。主动关闭和真正网络故障也要分开识别,不能为了让循环继续而吞掉所有 error。
应用若需要 ancillary data,不要随意改用不读取 oob 的 API。它可能携带接口、目标地址或其他必要信息。无需这些信息的应用可以另评估更简单的读取入口,但仍须遵守错误和长度检查。
回归在目标 Linux 环境覆盖正常数据报、空数据报、读超时、主动关闭和截断。确认错误路径没有访问任何负长度切片,也没有提交伪造空包,再检查原来的取消与资源释放。Windows 上返回零的对照,不能替代 Linux 的验收。
即便后续版本统一了错误计数,先处理非 nil error、再使用成功读取的长度,仍应保留在应用边界。


