Home > OS >  Understanding USBIP bind function
Understanding USBIP bind function

Time:02-02

I am trying to understand the USBIP tool code from Linux (https://github.com/torvalds/linux/tree/master/tools/usb/usbip).

USBIP has a command that attaches a remote USB from an IP to a client system. If you look at the code (https://github.com/torvalds/linux/blob/master/tools/usb/usbip/src/usbip_bind.c#L130) below, which binds the USB, if the function calls close(sockfd); and close the socket, then how the communication is done between client and server for USB data.

From Docs (https://docs.kernel.org/usb/usbip_protocol.html):

Once the client knows the list of exported USB devices it may decide to use one of them. First the client opens a TCP/IP connection to the server and sends an OP_REQ_IMPORT packet. The server replies with OP_REP_IMPORT. If the import was successful the TCP/IP connection remains open and will be used to transfer the URB traffic between the client and the server.

It says connection remains open if import is successful, then what is the purpose of close(sockfd);. I can also see sockfd is sent inside query_import_device(sockfd, busid); but if it is closed how this is used?

static int attach_device(char *host, char *busid)
{
    int sockfd;
    int rc;
    int rhport;

    // creates a TCP connection to the specified host on the specified port.
    sockfd = usbip_net_tcp_connect(host, usbip_port_string);
    if (sockfd < 0) {
        err("tcp connect");
        return -1;
    }

    // sends a query to the connected host to import the device specified by "busid".
    rhport = query_import_device(sockfd, busid);
    if (rhport < 0)
        return -1;

    // closes the previously established TCP connection.
    close(sockfd);

    // records details of the connection, such as the host and port, the busid of the device, and the assigned rhport.
    rc = record_connection(host, usbip_port_string, busid, rhport);
    if (rc < 0) {
        err("record connection");
        return -1;
    }

    return 0;
}

CodePudding user response:

If you look at the source code of usbip helper program, query_import_device(), it establishes the connection and then calls import_device() to finish it.

In turn, import_device() calls usbip_vhci_attach_device(), that calls usbip_vhci_attach_device2()... that eventually writes the sockfd and some extra data to the /sys/*/attach pseudo file for the given device.

To see what happens next we need to go to kernel mode. So, looking at the source code of the usbip, at function attach_store(). This function parses the sockfd back into an integer file descriptor and, most notably, does this:

struct socket *socket;
socket = sockfd_lookup(sockfd, &err);

This converts a file descriptor into a real kernel socket object. And it increases the reference count. Note how if any of the further checks fail, it calls sockfd_put(socket); to decrease that reference count.

But if the function succeeds, the socket is stored into the device. It is just as if an userland program had done a call to dup(): it doesn't matter if the original sockfd is closed, the kernel keeps the actual socket opened as long as it is needed.

You can see that the value of sockfd is also stored in the device, but it isn't actually used for anything other than reading back from /sys, so it doesn't matter if it is no longer a valid file descriptor.

So the close is actually necessary because the kernel dups the socket internally, that keeps the connection opened. And you do not want to keep an extra FD referencing the same socket, if you try to use that socket for anything that could mess up the device connection. The responsible thing to do is just to close it.

  • Related