usbip: add extended control protocol

This commit is contained in:
世界
2026-04-24 07:12:56 +08:00
parent e94a99d5b9
commit 0135eaef7c
10 changed files with 1705 additions and 101 deletions
+237 -3
View File
@@ -1226,6 +1226,13 @@ func TestServerDispatchConnHandlesControlPingAndChanged(t *testing.T) {
require.Equal(t, controlCapabilities, ack.Capabilities)
require.Zero(t, ack.Sequence)
snapshotMessage, err := readControlMessage(conn)
require.NoError(t, err)
require.Equal(t, controlFrameDeviceSnapshot, snapshotMessage.Frame.Type)
var snapshot controlDeviceSnapshot
require.NoError(t, unmarshalControlPayload(snapshotMessage.Payload, &snapshot))
require.Empty(t, snapshot.Devices)
require.NoError(t, WriteControlPing(conn))
pong, err := ReadControlFrame(conn)
require.NoError(t, err)
@@ -1233,10 +1240,108 @@ func TestServerDispatchConnHandlesControlPingAndChanged(t *testing.T) {
require.Equal(t, controlProtocolVersion, pong.Version)
server.broadcastChanged()
changed, err := ReadControlFrame(conn)
changed, err := readControlMessage(conn)
require.NoError(t, err)
require.Equal(t, controlFrameChanged, changed.Type)
require.Equal(t, uint64(1), changed.Sequence)
require.Equal(t, controlFrameDeviceDelta, changed.Frame.Type)
require.Equal(t, uint64(1), changed.Frame.Sequence)
var delta controlDeviceDelta
require.NoError(t, unmarshalControlPayload(changed.Payload, &delta))
require.Equal(t, uint64(1), delta.Sequence)
}
func TestServerControlLeaseEnablesImportExt(t *testing.T) {
t.Parallel()
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
device := newTestDevice("1-1", 0x1d6b, 0x0002, "serial-1", SpeedHigh)
store := newTestDeviceStore(device)
store.setStatus("1-1", usbipStatusAvailable)
serverOps := newTestUSBIPOps(t)
serverOps.readUsbipStatus = store.readUsbipStatus
serverOps.readSysfsDevice = store.readSysfsDevice
serverOps.writeUsbipSockfd = store.writeUsbipSockfd
server := &ServerService{
ctx: ctx,
cancel: cancel,
logger: newTestLogger(),
exports: map[string]serverExport{"1-1": {busid: "1-1"}},
controlSubs: make(map[uint64]*serverControlConn),
controlState: make(map[string]DeviceInfoV2),
leases: make(map[uint64]serverImportLease),
leaseByBusID: make(map[string]uint64),
ops: serverOps,
}
server.refreshControlState()
serverAddr, closeServer := startDispatchServer(t, server)
defer closeServer()
controlConn, err := net.Dial("tcp", serverAddr.String())
require.NoError(t, err)
defer controlConn.Close()
require.NoError(t, WriteControlPreface(controlConn))
require.NoError(t, WriteControlHello(controlConn))
ack, err := ReadControlFrame(controlConn)
require.NoError(t, err)
require.Equal(t, controlCapabilities, ack.Capabilities)
_, err = readControlMessage(controlConn)
require.NoError(t, err)
require.NoError(t, writeControlMessage(controlConn, controlFrame{
Type: controlFrameLeaseRequest,
Version: controlProtocolVersion,
}, controlLeaseRequest{BusID: "1-1", ClientNonce: 42}))
leaseMessage, err := readControlMessage(controlConn)
require.NoError(t, err)
require.Equal(t, controlFrameLeaseResponse, leaseMessage.Frame.Type)
var lease controlLeaseResponse
require.NoError(t, unmarshalControlPayload(leaseMessage.Payload, &lease))
require.Empty(t, lease.ErrorCode)
require.Equal(t, uint64(42), lease.ClientNonce)
require.NotZero(t, lease.LeaseID)
require.NoError(t, writeControlMessage(controlConn, controlFrame{
Type: controlFrameLeaseRequest,
Version: controlProtocolVersion,
}, controlLeaseRequest{BusID: "1-1", ClientNonce: 43}))
busyMessage, err := readControlMessage(controlConn)
require.NoError(t, err)
var busy controlLeaseResponse
require.NoError(t, unmarshalControlPayload(busyMessage.Payload, &busy))
require.Equal(t, "busy", busy.ErrorCode)
importConn, err := net.Dial("tcp", serverAddr.String())
require.NoError(t, err)
require.NoError(t, WriteOpReqImportExt(importConn, ImportExtRequest{
BusID: "1-1",
LeaseID: lease.LeaseID,
ClientNonce: lease.ClientNonce,
}))
header, err := ReadOpHeader(importConn)
require.NoError(t, err)
require.Equal(t, OpRepImportExt, header.Code)
require.Equal(t, OpStatusOK, header.Status)
info, err := ReadOpRepImportBody(importConn)
require.NoError(t, err)
require.Equal(t, "1-1", info.BusIDString())
require.NoError(t, importConn.Close())
require.Positive(t, store.lastSockfd("1-1"))
reuseConn, err := net.Dial("tcp", serverAddr.String())
require.NoError(t, err)
defer reuseConn.Close()
require.NoError(t, WriteOpReqImportExt(reuseConn, ImportExtRequest{
BusID: "1-1",
LeaseID: lease.LeaseID,
ClientNonce: lease.ClientNonce,
}))
header, err = ReadOpHeader(reuseConn)
require.NoError(t, err)
require.Equal(t, OpRepImportExt, header.Code)
require.Equal(t, OpStatusError, header.Status)
}
func TestClientAttemptAttachUsesImportReplyAndVHCIAttach(t *testing.T) {
@@ -1301,6 +1406,135 @@ func TestClientAttemptAttachUsesImportReplyAndVHCIAttach(t *testing.T) {
require.Positive(t, store.lastSockfd("1-1"))
}
func TestClientAttemptAttachUsesImportExtLease(t *testing.T) {
t.Parallel()
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
controlClient, controlServer := net.Pipe()
defer controlClient.Close()
defer controlServer.Close()
controlSession := newClientControlSession(controlClient, controlCapabilities)
controlErrCh := make(chan error, 1)
go func() {
message, err := readControlMessage(controlServer)
if err != nil {
controlErrCh <- err
return
}
if message.Frame.Type != controlFrameLeaseRequest {
controlErrCh <- fmt.Errorf("unexpected control frame %d", message.Frame.Type)
return
}
var request controlLeaseRequest
if err := unmarshalControlPayload(message.Payload, &request); err != nil {
controlErrCh <- err
return
}
if request.BusID != "1-1" {
controlErrCh <- fmt.Errorf("unexpected lease busid %s", request.BusID)
return
}
controlErrCh <- writeControlMessage(controlServer, controlFrame{
Type: controlFrameLeaseResponse,
Version: controlProtocolVersion,
}, controlLeaseResponse{
BusID: request.BusID,
LeaseID: 55,
ClientNonce: request.ClientNonce,
Generation: 2,
TTLMillis: int64(importLeaseTTL / time.Millisecond),
})
}()
deliverErrCh := make(chan error, 1)
go func() {
message, err := readControlMessage(controlClient)
if err != nil {
deliverErrCh <- err
return
}
if message.Frame.Type != controlFrameLeaseResponse {
deliverErrCh <- fmt.Errorf("unexpected control response %d", message.Frame.Type)
return
}
var response controlLeaseResponse
if err := unmarshalControlPayload(message.Payload, &response); err != nil {
deliverErrCh <- err
return
}
controlSession.deliverLeaseResponse(response)
deliverErrCh <- nil
}()
listener, err := net.Listen("tcp", "127.0.0.1:0")
require.NoError(t, err)
defer listener.Close()
device := newTestDevice("1-1", 0x1d6b, 0x0002, "serial-1", SpeedHigh)
serverErrCh := make(chan error, 1)
go func() {
conn, acceptErr := listener.Accept()
if acceptErr != nil {
serverErrCh <- acceptErr
return
}
defer conn.Close()
header, readErr := ReadOpHeader(conn)
if readErr != nil {
serverErrCh <- readErr
return
}
if header.Code != OpReqImportExt {
serverErrCh <- fmt.Errorf("unexpected request code 0x%s", hex16(header.Code))
return
}
request, readErr := ReadOpReqImportExtBody(conn)
if readErr != nil {
serverErrCh <- readErr
return
}
if request.BusID != "1-1" || request.LeaseID != 55 || request.ClientNonce != 1 {
serverErrCh <- fmt.Errorf("unexpected import-ext request %+v", request)
return
}
info := device.toProtocol()
serverErrCh <- WriteOpRepImportExt(conn, OpStatusOK, &info)
}()
ops := newTestUSBIPOps(t)
ops.vhciPickFreePort = func(speed uint32) (int, error) {
require.Equal(t, SpeedHigh, speed)
return 4, nil
}
ops.vhciAttach = func(port int, _ uintptr, devid uint32, speed uint32) error {
require.Equal(t, 4, port)
info := device.toProtocol()
require.Equal(t, info.DevID(), devid)
require.Equal(t, SpeedHigh, speed)
return nil
}
client := &ClientService{
ctx: ctx,
cancel: cancel,
logger: newTestLogger(),
dialer: testDialer{},
serverAddr: M.SocksaddrFromNet(listener.Addr()),
ops: ops,
}
client.setControlSession(controlSession)
defer client.clearControlSession(controlSession, errClientControlSessionClosed)
port, err := client.attemptAttach(ctx, "1-1")
require.NoError(t, err)
require.Equal(t, 4, port)
require.NoError(t, <-controlErrCh)
require.NoError(t, <-deliverErrCh)
require.NoError(t, <-serverErrCh)
}
func TestClientAttemptAttachWithOpaqueConnRelay(t *testing.T) {
t.Parallel()