Skip to content

USB/IP transport

The wire under every device/host call. Everything is local by default; name a transport only when you go remote with USBIP, or use Loopback for in-process tests. use() sets a process-wide default, and Transport is the base class for carrying USB/IP over something else.

transport

Transports - the only place USB/IP addressing (host/port/socket) lives.

The top layer never names these directly: device.plug() and host.open() use a default transport unless you pass via=/transport=.

USBIP(host, port) - real USB/IP over TCP (kernel-compatible) Loopback() - in-process socketpair; same protocol code, no network

USBIP

USBIP(host=None, port=3240)

Bases: Transport

Real USB/IP over TCP. Serving binds a usbipd-compatible listener; connecting attaches like the kernel client / usbip attach does.

Source code in usbip/transport.py
def __init__(self, host=None, port=3240):
    self.host = host
    self.port = port
    self._listener = None

host instance-attribute

host = host

port instance-attribute

port = port

serve

serve(dev)
Source code in usbip/transport.py
def serve(self, dev):
    listener = _listener_for(self.host, self.port)
    self._listener = listener
    if not listener.register(dev):
        return  # already bound; dev just joined it

    srv = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    srv.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
    try:
        srv.bind((self.host or "0.0.0.0", self.port))
        srv.listen(4)
    except OSError:
        srv.close()
        listener.unregister(dev)
        _drop_listener(listener)
        raise
    listener.socks.append(srv)

    def accept_loop():
        while True:
            try:
                conn, _addr = srv.accept()
            except OSError:
                break
            # Tiny request/response messages, and write_ret() sends header+data
            # in separate sendall()s; without TCP_NODELAY, Nagle plus the peer's
            # delayed-ACK stalls each IN reply ~40-250 ms.
            conn.setsockopt(socket.IPPROTO_TCP, socket.TCP_NODELAY, 1)
            # the handshake picks which of these the connection imported
            threading.Thread(
                target=protocol.serve_connection, args=(conn, listener.snapshot()), daemon=True
            ).start()

    threading.Thread(target=accept_loop, daemon=True).start()

connect

connect()
Source code in usbip/transport.py
def connect(self):
    sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    sock.connect((self.host or "127.0.0.1", self.port))
    return sock

stop

stop(dev=None)
Source code in usbip/transport.py
def stop(self, dev=None):
    listener = self._listener
    if listener is None:
        return
    if dev is not None and not listener.unregister(dev):
        return  # other devices still on this listener
    _drop_listener(listener)
    self._listener = None

Loopback

Loopback()

Bases: Transport

Device and host in one process, wired by a socketpair (no kernel/network).

Each connect() mints its own socketpair and serves the listener's devices on it, so several devices - and several sequential opens - behave exactly as they do over TCP.

Source code in usbip/transport.py
def __init__(self):
    self._listener = _Listener()  # private: loopback has no address to share

serve

serve(dev)
Source code in usbip/transport.py
def serve(self, dev):
    self._listener.register(dev)

connect

connect()
Source code in usbip/transport.py
def connect(self):
    dev_sock, host_sock = socket.socketpair()
    # keep only live sockets: an open/close cycle per connect() must not pile up
    self._listener.socks = [sock for sock in self._listener.socks if sock.fileno() != -1]
    self._listener.socks += [dev_sock, host_sock]
    threading.Thread(
        target=protocol.serve_connection,
        args=(dev_sock, self._listener.snapshot()),
        daemon=True,
    ).start()
    return host_sock

stop

stop(dev=None)
Source code in usbip/transport.py
def stop(self, dev=None):
    if dev is not None and not self._listener.unregister(dev):
        return  # other devices still on this listener
    self._listener.close()

Transport

serve

serve(dev)
Source code in usbip/transport.py
def serve(self, dev):  # device side
    raise NotImplementedError

connect

connect()
Source code in usbip/transport.py
def connect(self):  # host side -> connected socket
    raise NotImplementedError

stop

stop(dev=None)
Source code in usbip/transport.py
def stop(self, dev=None):
    pass

use

use(transport)

Set the process-wide default transport.

Source code in usbip/transport.py
def use(transport):
    """Set the process-wide default transport."""
    global _default
    _default = transport