Files
sing-box/service/usbip/server_shared.go
T
世界 c3946c00a3 usbip: fix correctness findings from protocol audit
Windows export now reports the real USB link speed, probed from the
parent hub (IOCTL_USB_GET_NODE_CONNECTION_INFORMATION_EX and _V2 for
SuperSpeedPlus), so SuperSpeed devices route to the correct root-hub
speed domain instead of advertising speed=0.

- protocol: pin DeviceInfoTruncated/DeviceInterface wire sizes with
  two-sided compile-time assertions so a struct change fails the build
  instead of silently mis-bounding the reader
- server: bound inbound connections with a handshake read deadline and a
  per-iteration idle deadline on the control loop, plus write deadlines
  on control writes; clear the deadline before the conn becomes a data
  session
- server: serialize import reservation under reconcileAccess so a reserve
  cannot interleave a reconcile pass that would release a busy device
- data: validate CMD_SUBMIT iso descriptor offset/length against the
  transfer buffer before forwarding to a platform engine
- darwin: make darwinUSBHostDevice.Close idempotent via sync.Once to
  avoid a double close/free under concurrent shutdown
- windows: guard windowsExport.device with a mutex and hand the claimed
  handle to a single closer
2026-06-09 14:38:23 +08:00

88 lines
2.0 KiB
Go

//go:build linux || (darwin && cgo) || windows
package usbip
import (
"bytes"
"io"
"net"
"time"
E "github.com/sagernet/sing/common/exceptions"
)
// serverHandshakeTimeout bounds how long an unauthenticated peer may hold a
// goroutine before sending its preface/hello. The post-handshake control loop
// uses controlReadTimeout, which tolerates the client's controlPingInterval.
const serverHandshakeTimeout = 10 * time.Second
func (s *ServerService) acceptLoop(ln net.Listener) {
for {
conn, err := ln.Accept()
if err != nil {
select {
case <-s.ctx.Done():
return
default:
}
if E.IsClosed(err) {
return
}
//nolint:staticcheck // net.Error.Temporary predates net.ErrClosed; replacement needs a separate audit.
if netError, isNetError := err.(net.Error); isNetError && netError.Temporary() {
s.logger.Error("accept: ", err)
if !sleepCtx(s.ctx, 200*time.Millisecond) {
return
}
continue
}
s.logger.Error("accept: ", err)
return
}
go s.dispatchConn(conn)
}
}
func (s *ServerService) dispatchConn(conn net.Conn) {
cancelClose := closeConnOnContextDone(s.ctx, conn)
defer cancelClose()
_ = conn.SetReadDeadline(time.Now().Add(serverHandshakeTimeout))
var prefix [controlPrefaceSize]byte
_, err := io.ReadFull(conn, prefix[:])
if err != nil {
s.logger.Debug("read connection preface: ", err)
_ = conn.Close()
return
}
if bytes.Equal(prefix[:], controlPreface[:]) {
s.handleControlConn(conn)
return
}
s.handleStandardConn(conn, ParseOpHeader(prefix[:]))
}
func (s *ServerService) readControlConn(sub *exportSubscriber, done chan<- struct{}) {
defer close(done)
var reader controlReader
for {
err := sub.conn.SetReadDeadline(time.Now().Add(controlReadTimeout))
if err != nil {
return
}
message, err := reader.read(sub.conn)
if err != nil {
return
}
frame := message.Frame
switch frame.Type {
case controlFramePing:
s.ledger.enqueueFrame(sub, controlFrame{
Type: controlFramePong,
Version: controlProtocolVersion,
})
default:
return
}
}
}