USBIP: Compartiendo dispositivos USB en nuestra red
Estos últimos días he estado pensando en como podría actualizar el firmware de mi RFXCOM, que tengo instalado junto al control de riego, en remoto desde casa. De esta forma no tendría que ir con el portátil, con Windows, con la herramienta para Windows, desconectar el dispositivo de la Orange Pi Zero, conectarlo al portátil, actualizarlo, y volvérselo a poner a la Orange Pi Zero. Es un jaleo hacerlo así, y yo quiero actualizarlo en remoto, desde casa, y cuando me venga bien… 🙂
Existe un proyecto, que hace tiempo se incluyó dentro del kernel de Linux, llamado USBIP. Esta compuesto por tres partes:
- Los módulos del kernel. Hay tres módulo del kernel que tenemos que cargar para hacer uso de USBIP, que nos permiten desasociar el dispositivo de la máquina que lo tenga, y ponerlo a disposición de quien lo quiera usar en la red.
- El demonio usbipd. Es el programa que escucha las peticiones a través de la red y las encamina al módulo del kernel correspondiente para ser atendidas.
- La utilidad usbip. Es la herramienta encargada de administrar los dispositivos compartidos, conectarlos, listarlos, desconectarlos…
Instalación
Los primero que me llama la atención de este sistema es que los módulos del kernel y su software de gestión están dentro del código fuente del kernel. Ni os molestéis en descargar paquetes del sistema con apt-get install usbip, sencillamente no funciona. Lo mejor es bajarse el código fuente de la versión del kernel que queramos (la que tengamos instalada), compilar e instalar el código que viene con él. El software que suelen traer los paquetes del sistema suele estar desactualizado y da bastantes problemas.
Para comprobar si tenéis los módulos disponibles en vuestro sistema podéis hacer lo siguiente:
root@jsensor:~# uname -a Linux jsensor 4.10.3-sun8i #6 SMP Sat Mar 25 02:54:20 CET 2017 armv7l armv7l armv7l GNU/Linux root@jsensor:~# ls /lib/modules/4.10.3-sun8i/kernel/drivers/usb/usbip usbip-core.ko usbip-host.ko vhci-hcd.ko root@jsensor:~#
Primero averiguáis la versión del kernel que está en uso, en mi caso, 4.10.3-sun8i, y vais a la ruta concreta donde deberían estar los tres módulos de USBIP. En caso de que los tengáis, hay que cargarlos manualmente (por defecto no se suelen cargar automáticamente, aunque podéis hacerlo añadiéndolos en la lista de /etc/modules):
root@jsensor:~# modprobe usbip-core root@jsensor:~# modprobe usbip-host root@jsensor:~# modprobe vhci-hcd root@jsensor:~#
Ya tenemos nuestro servidor listo, con los módulos cargados. En el cliente haremos exactamente lo mismo. En mis caso, en el portátil, también tengo cargados los tres módulos.
De una cosa que me he dado cuenta trasteando con USBIP es que la versión de las utilidades usbipd y usbip es importante que no difieran demasiado ya que puede que haya incompatibilidades y no conecte el cliente con el servidor. Al final he usado el mismo software en los dos: la versión que viene con el código fuente del kernel del servidor. Vamos a ver de donde la sacamos, y como se compila e instala.
Como había comentado el código fuente del kernel incluye estas utilidades, pero hay que compilarlas. El código está en ./tools/usb/usbip en la raíz de donde tengáis el código del kernel. Para compilarlo ejecutamos estos comandos:
[09:07] root@titanio:~/devel/opi/sources/linux-sun8i-mainline/orange-pi-4.10# cd tools/usb/usbip/ [09:07] root@titanio:~/devel/opi/sources/linux-sun8i-mainline/orange-pi-4.10/tools/usb/usbip# ./autogen.sh + autoreconf -i -f -v autoreconf: Entering directory `.' autoreconf: configure.ac: not using Gettext autoreconf: running: aclocal --force autoreconf: configure.ac: tracing autoreconf: running: libtoolize --copy --force libtoolize: putting auxiliary files in '.'. libtoolize: copying file './ltmain.sh' libtoolize: Consider adding 'AC_CONFIG_MACRO_DIRS([m4])' to configure.ac, libtoolize: and rerunning libtoolize and aclocal. libtoolize: Consider adding '-I m4' to ACLOCAL_AMFLAGS in Makefile.am. autoreconf: running: /usr/bin/autoconf --force autoreconf: running: /usr/bin/autoheader --force autoreconf: running: automake --add-missing --copy --force-missing configure.ac:16: installing './compile' configure.ac:16: installing './config.guess' configure.ac:16: installing './config.sub' configure.ac:15: installing './install-sh' configure.ac:15: installing './missing' libsrc/Makefile.am: installing './depcomp' autoreconf: Leaving directory `.' [09:07] root@titanio:~/devel/opi/sources/linux-sun8i-mainline/orange-pi-4.10/tools/usb/usbip# ./configure --prefix=/usr checking for a BSD-compatible install... /usr/bin/install -c checking whether build environment is sane... yes checking for a thread-safe mkdir -p... /bin/mkdir -p checking for gawk... gawk checking whether make sets $(MAKE)... yes checking whether make supports nested variables... yes checking build system type... x86_64-pc-linux-gnu checking host system type... x86_64-pc-linux-gnu checking how to print strings... printf checking for style of include used by make... GNU checking for gcc... gcc checking whether the C compiler works... yes checking for C compiler default output file name... a.out checking for suffix of executables... checking whether we are cross compiling... no checking for suffix of object files... o checking whether we are using the GNU C compiler... yes checking whether gcc accepts -g... yes checking for gcc option to accept ISO C89... none needed checking whether gcc understands -c and -o together... yes checking dependency style of gcc... gcc3 checking for a sed that does not truncate output... /bin/sed checking for grep that handles long lines and -e... /bin/grep checking for egrep... /bin/grep -E checking for fgrep... /bin/grep -F checking for ld used by gcc... /usr/bin/ld checking if the linker (/usr/bin/ld) is GNU ld... yes checking for BSD- or MS-compatible name lister (nm)... /usr/bin/nm -B checking the name lister (/usr/bin/nm -B) interface... BSD nm checking whether ln -s works... yes checking the maximum length of command line arguments... 1572864 checking how to convert x86_64-pc-linux-gnu file names to x86_64-pc-linux-gnu format... func_convert_file_noop checking how to convert x86_64-pc-linux-gnu file names to toolchain format... func_convert_file_noop checking for /usr/bin/ld option to reload object files... -r checking for objdump... objdump checking how to recognize dependent libraries... pass_all checking for dlltool... no checking how to associate runtime and link libraries... printf %s\n checking for ar... ar checking for archiver @FILE support... @ checking for strip... strip checking for ranlib... ranlib checking command to parse /usr/bin/nm -B output from gcc object... ok checking for sysroot... no checking for a working dd... /bin/dd checking how to truncate binary pipes... /bin/dd bs=4096 count=1 checking for mt... mt checking if mt is a manifest tool... no checking how to run the C preprocessor... gcc -E checking for ANSI C header files... yes checking for sys/types.h... yes checking for sys/stat.h... yes checking for stdlib.h... yes checking for string.h... yes checking for memory.h... yes checking for strings.h... yes checking for inttypes.h... yes checking for stdint.h... yes checking for unistd.h... yes checking for dlfcn.h... yes checking for objdir... .libs checking if gcc supports -fno-rtti -fno-exceptions... no checking for gcc option to produce PIC... -fPIC -DPIC checking if gcc PIC flag -fPIC -DPIC works... yes checking if gcc static flag -static works... yes checking if gcc supports -c -o file.o... yes checking if gcc supports -c -o file.o... (cached) yes checking whether the gcc linker (/usr/bin/ld -m elf_x86_64) supports shared libraries... yes checking whether -lc should be explicitly linked in... no checking dynamic linker characteristics... GNU/Linux ld.so checking how to hardcode library paths into programs... immediate checking whether stripping libraries is possible... yes checking if libtool supports shared libraries... yes checking whether to build shared libraries... yes checking whether to build static libraries... yes checking whether make supports nested variables... (cached) yes checking for gcc... (cached) gcc checking whether we are using the GNU C compiler... (cached) yes checking whether gcc accepts -g... (cached) yes checking for gcc option to accept ISO C89... (cached) none needed checking whether gcc understands -c and -o together... (cached) yes checking dependency style of gcc... (cached) gcc3 checking whether make sets $(MAKE)... (cached) yes checking for dirent.h that defines DIR... yes checking for library containing opendir... none required checking for ANSI C header files... (cached) yes checking arpa/inet.h usability... yes checking arpa/inet.h presence... yes checking for arpa/inet.h... yes checking fcntl.h usability... yes checking fcntl.h presence... yes checking for fcntl.h... yes checking netdb.h usability... yes checking netdb.h presence... yes checking for netdb.h... yes checking netinet/in.h usability... yes checking netinet/in.h presence... yes checking for netinet/in.h... yes checking for stdint.h... (cached) yes checking for stdlib.h... (cached) yes checking for string.h... (cached) yes checking sys/socket.h usability... yes checking sys/socket.h presence... yes checking for sys/socket.h... yes checking syslog.h usability... yes checking syslog.h presence... yes checking for syslog.h... yes checking for unistd.h... (cached) yes checking for int32_t... yes checking for size_t... yes checking for ssize_t... yes checking for uint16_t... yes checking for uint32_t... yes checking for uint8_t... yes checking for stdlib.h... (cached) yes checking for GNU libc compatible realloc... yes checking for memset... yes checking for mkdir... yes checking for regcomp... yes checking for socket... yes checking for strchr... yes checking for strerror... yes checking for strstr... yes checking for strtoul... yes checking libudev.h usability... yes checking libudev.h presence... yes checking for libudev.h... yes checking for udev_new in -ludev... yes checking whether to use the libwrap (TCP wrappers) library... (default) checking for hosts_access in -lwrap... no checking whether to use fortify... default checking that generated files are newer than configure... done configure: creating ./config.status config.status: creating Makefile config.status: creating libsrc/Makefile config.status: creating src/Makefile config.status: creating config.h config.status: executing depfiles commands config.status: executing libtool commands [09:07] root@titanio:~/devel/opi/sources/linux-sun8i-mainline/orange-pi-4.10/tools/usb/usbip# make make all-recursive make[1]: se entra en el directorio '/home/jose/devel/opi/sources/linux-sun8i-mainline/orange-pi-4.10/tools/usb/usbip' Making all in libsrc make[2]: se entra en el directorio '/home/jose/devel/opi/sources/linux-sun8i-mainline/orange-pi-4.10/tools/usb/usbip/libsrc' CC libusbip_la-names.lo CC libusbip_la-usbip_host_driver.lo CC libusbip_la-usbip_device_driver.lo CC libusbip_la-usbip_common.lo CC libusbip_la-usbip_host_common.lo CC libusbip_la-vhci_driver.lo CC libusbip_la-sysfs_utils.lo CCLD libusbip.la ar: `u' modifier ignored since `D' is the default (see `U') make[2]: se sale del directorio '/home/jose/devel/opi/sources/linux-sun8i-mainline/orange-pi-4.10/tools/usb/usbip/libsrc' Making all in src make[2]: se entra en el directorio '/home/jose/devel/opi/sources/linux-sun8i-mainline/orange-pi-4.10/tools/usb/usbip/src' CC usbip.o CC utils.o CC usbip_network.o CC usbip_attach.o CC usbip_detach.o CC usbip_list.o CC usbip_bind.o CC usbip_unbind.o CC usbip_port.o CCLD usbip CC usbipd.o CCLD usbipd make[2]: se sale del directorio '/home/jose/devel/opi/sources/linux-sun8i-mainline/orange-pi-4.10/tools/usb/usbip/src' make[2]: se entra en el directorio '/home/jose/devel/opi/sources/linux-sun8i-mainline/orange-pi-4.10/tools/usb/usbip' make[2]: se sale del directorio '/home/jose/devel/opi/sources/linux-sun8i-mainline/orange-pi-4.10/tools/usb/usbip' make[1]: se sale del directorio '/home/jose/devel/opi/sources/linux-sun8i-mainline/orange-pi-4.10/tools/usb/usbip' [09:07] root@titanio:~/devel/opi/sources/linux-sun8i-mainline/orange-pi-4.10/tools/usb/usbip# make install Making install in libsrc make[1]: se entra en el directorio '/home/jose/devel/opi/sources/linux-sun8i-mainline/orange-pi-4.10/tools/usb/usbip/libsrc' make[2]: se entra en el directorio '/home/jose/devel/opi/sources/linux-sun8i-mainline/orange-pi-4.10/tools/usb/usbip/libsrc' /bin/mkdir -p '/usr/lib' /bin/bash ../libtool --mode=install /usr/bin/install -c libusbip.la '/usr/lib' libtool: install: /usr/bin/install -c .libs/libusbip.so.0.0.1 /usr/lib/libusbip.so.0.0.1 libtool: install: (cd /usr/lib && { ln -s -f libusbip.so.0.0.1 libusbip.so.0 || { rm -f libusbip.so.0 && ln -s libusbip.so.0.0.1 libusbip.so.0; }; }) libtool: install: (cd /usr/lib && { ln -s -f libusbip.so.0.0.1 libusbip.so || { rm -f libusbip.so && ln -s libusbip.so.0.0.1 libusbip.so; }; }) libtool: install: /usr/bin/install -c .libs/libusbip.lai /usr/lib/libusbip.la libtool: install: /usr/bin/install -c .libs/libusbip.a /usr/lib/libusbip.a libtool: install: chmod 644 /usr/lib/libusbip.a libtool: install: ranlib /usr/lib/libusbip.a libtool: finish: PATH="/home/jose/Espressif/crosstool-NG/builds/xtensa-lx106-elf/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/snap/bin:/home/jose/jdomo/android/sdk/tools:/sbin" ldconfig -n /usr/lib ---------------------------------------------------------------------- Libraries have been installed in: /usr/lib If you ever happen to want to link against installed libraries in a given directory, LIBDIR, you must either use libtool, and specify the full pathname of the library, or use the '-LLIBDIR' flag during linking and do at least one of the following: - add LIBDIR to the 'LD_LIBRARY_PATH' environment variable during execution - add LIBDIR to the 'LD_RUN_PATH' environment variable during linking - use the '-Wl,-rpath -Wl,LIBDIR' linker flag - have your system administrator add LIBDIR to '/etc/ld.so.conf' See any operating system documentation about shared libraries for more information, such as the ld(1) and ld.so(8) manual pages. ---------------------------------------------------------------------- make[2]: No se hace nada para 'install-data-am'. make[2]: se sale del directorio '/home/jose/devel/opi/sources/linux-sun8i-mainline/orange-pi-4.10/tools/usb/usbip/libsrc' make[1]: se sale del directorio '/home/jose/devel/opi/sources/linux-sun8i-mainline/orange-pi-4.10/tools/usb/usbip/libsrc' Making install in src make[1]: se entra en el directorio '/home/jose/devel/opi/sources/linux-sun8i-mainline/orange-pi-4.10/tools/usb/usbip/src' make[2]: se entra en el directorio '/home/jose/devel/opi/sources/linux-sun8i-mainline/orange-pi-4.10/tools/usb/usbip/src' /bin/mkdir -p '/usr/sbin' /bin/bash ../libtool --mode=install /usr/bin/install -c usbip usbipd '/usr/sbin' libtool: install: /usr/bin/install -c .libs/usbip /usr/sbin/usbip libtool: install: /usr/bin/install -c .libs/usbipd /usr/sbin/usbipd make[2]: No se hace nada para 'install-data-am'. make[2]: se sale del directorio '/home/jose/devel/opi/sources/linux-sun8i-mainline/orange-pi-4.10/tools/usb/usbip/src' make[1]: se sale del directorio '/home/jose/devel/opi/sources/linux-sun8i-mainline/orange-pi-4.10/tools/usb/usbip/src' make[1]: se entra en el directorio '/home/jose/devel/opi/sources/linux-sun8i-mainline/orange-pi-4.10/tools/usb/usbip' make[2]: se entra en el directorio '/home/jose/devel/opi/sources/linux-sun8i-mainline/orange-pi-4.10/tools/usb/usbip' make[2]: No se hace nada para 'install-exec-am'. /bin/mkdir -p '/usr/include/usbip' /usr/bin/install -c -m 644 libsrc/usbip_common.h libsrc/vhci_driver.h libsrc/usbip_host_driver.h '/usr/include/usbip' /bin/mkdir -p '/usr/share/man/man8' /usr/bin/install -c -m 644 doc/usbip.8 doc/usbipd.8 '/usr/share/man/man8' make[2]: se sale del directorio '/home/jose/devel/opi/sources/linux-sun8i-mainline/orange-pi-4.10/tools/usb/usbip' make[1]: se sale del directorio '/home/jose/devel/opi/sources/linux-sun8i-mainline/orange-pi-4.10/tools/usb/usbip' [09:08] root@titanio:~/devel/opi/sources/linux-sun8i-mainline/orange-pi-4.10/tools/usb/usbip#
Solo nos queda actualizar la base de datos de dispositivos USB del sistema y ponerla a disposición de USBIP:
root@jsensor:~# update-usbids --2017-04-21 09:21:31-- http://www.linux-usb.org/usb.ids Resolving www.linux-usb.org (www.linux-usb.org)... 216.34.181.97 Connecting to www.linux-usb.org (www.linux-usb.org)|216.34.181.97|:80... connected. HTTP request sent, awaiting response... 200 OK Length: 579005 (565K) Saving to: ‘/var/lib/usbutils/usb.ids.new’ /var/lib/usbutils/usb.ids.new 100%[=======================================================================================================>] 565,43K 439KB/s in 1,3s 2017-04-21 09:21:38 (439 KB/s) - ‘/var/lib/usbutils/usb.ids.new’ saved [579005/579005] Done. root@jsensor:~# mkdir /usr/share/hwdata/ root@jsensor:~# ln -s /var/lib/usbutils/usb.ids /usr/share/hwdata/usb.ids
Si en vuestro sistema ya existe /usr/share/hwdata/usb.ids (como es el caso de mi portátil (titanio)), podéis omitir este último paso. En la Orange Pi Zero (jsensor) no existía.
Una vez hemos hecho esto en el servidor y en el cliente, ya lo tendremos todo instalado. Es posible que al ejecutar el ./configure os falte alguna herramienta o librería, como me pasó a mi en el servidor (jsensor) que necesité instalar libtool y libudev-dev. Se instalan sin mayor problema y se continua.
Compartiendo dispositivos
Ahora es cuando viene la parte importante de USBIP. El proceso es el siguiente: primero cargamos el demonio usbipd tanto en cliente como en el servidor, luego en el servidor listamos los dispositivos disponibles, compartimos el que queramos, finalmente en el cliente listamos los dispositivos que nos comparte el servidor y lo conectamos. Vamos a ello.
En el servidor (jsensor):
root@jsensor:~# usbipd -D root@jsensor:~# usbip list -l - busid 4-1 (0403:6001) Future Technology Devices International, Ltd : FT232 Serial (UART) IC (0403:6001) root@jsensor:~# usbip bind -b 4-1 usbip: info: bind device on busid 4-1: complete root@jsensor:~#
Es importante la notación que usa usbip para hacer referencia a los dispositivos: el busid, 4-1, por eso saco la lista primero, para obtener el busid, y luego hacer el bind del dispositivo correcto. Otro punto a tener muy en cuenta es que en cuanto le pasamos el dispositivo a usbip (hacemos el bind), dejará de estar disponible para su uso normal en el sistema, lo podéis comprobar en la salida del comando dmesg:
... [ 57.899604] usbip_core: USB/IP Core v1.0.0 [ 61.746339] usbcore: registered new device driver usbip-host [ 61.746399] usbip_host: USB/IP Host Driver v1.0.0 [ 66.863208] vhci_hcd vhci_hcd: USB/IP Virtual Host Controller [ 66.863259] vhci_hcd vhci_hcd: new USB bus registered, assigned bus number 6 [ 66.863460] vhci_hcd: created sysfs vhci_hcd [ 66.864526] usb usb6: New USB device found, idVendor=1d6b, idProduct=0002 [ 66.864559] usb usb6: New USB device strings: Mfr=3, Product=2, SerialNumber=1 [ 66.864581] usb usb6: Product: USB/IP Virtual Host Controller [ 66.864604] usb usb6: Manufacturer: Linux 4.10.3-sun8i vhci_hcd [ 66.864625] usb usb6: SerialNumber: vhci_hcd [ 66.867897] hub 6-0:1.0: USB hub found [ 66.868133] hub 6-0:1.0: 8 ports detected [ 66.876645] vhci_hcd: USB/IP 'Virtual' Host Controller (VHCI) Driver v1.0.0 [ 81.864681] ftdi_sio ttyUSB0: FTDI USB Serial Device converter now disconnected from ttyUSB0 [ 81.864777] ftdi_sio 4-1:1.0: device disconnected [ 81.870035] usbip-host 4-1: usbip-host: register new device (bus 4 dev 2)
En el cliente:
[09:42] root@titanio:~# usbipd -D [09:42] root@titanio:~# usbip list -r jsensor Exportable USB devices ====================== - jsensor 4-1: Future Technology Devices International, Ltd : FT232 USB-Serial (UART) IC (0403:6001) : /sys/devices/platform/soc/1c1b400.usb/usb4/4-1 : (Defined at Interface level) (00/00/00) [09:42] root@titanio:~# usbip attach usage: usbip attach <args> -r, --remote=<host> The machine with exported USB devices -b, --busid=<busid> Busid of the device on <host> -d, --device=<devid> Id of the virtual UDC on <host> [09:42] root@titanio:~# usbip attach -r jsensor -b 4-1
De forma similar al servidor, podemos comprobar en la salida del comando dmesg, como nos ha conectado correctamente el dispositivo de nuestro servidor jsensor, en el portátil titanio como si realmente lo hubiéramos conectado físicamente al portátil:
... [51415.571357] vhci_hcd vhci_hcd: rhport(0) sockfd(3) devid(262146) speed(2) speed_str(full-speed) [51415.788148] usb 9-1: new full-speed USB device number 127 using vhci_hcd [51415.948178] usb 9-1: SetAddress Request (127) to port 0 [51417.208110] usb 9-1: New USB device found, idVendor=0403, idProduct=6001 [51417.208122] usb 9-1: New USB device strings: Mfr=1, Product=2, SerialNumber=3 [51417.208130] usb 9-1: Product: RFXtrx433 [51417.208136] usb 9-1: Manufacturer: RFXCOM [51417.208142] usb 9-1: SerialNumber: A1XSULMI [51417.308310] ftdi_sio 9-1:1.0: FTDI USB Serial Device converter detected [51417.308424] usb 9-1: Detected FT232RL [51417.399151] usb 9-1: FTDI USB Serial Device converter now attached to ttyUSB0
A partir de este momento ya podemos usar el dispositivo como si lo tuviéramos en local, en mi caso, queria actualizar el firmware del RFXCOM, por tanto abro mi maquina virtual con Windows XP, donde tengo las utilidades para hacerlo, y le conecto el dispositivo recién ‘traído’ del servidor:
Espero que os sirva y os haya gustado la entrada… y como diría el grandísimo mago Juan Tamariz: Tiararaaaaa raaaaaaaaaaaa….
Está muy buena la explicación, gracias.
Lo que no me queda claro es ¿cómo el cliente encuentra al servidor? no veo que tengas que indicarle la IP del servidor o el puerto..
En lugar de las IPs estoy usando los nombres de los equipos, en este caso jsensor (que tengo en /etc/hosts definido con su direccion IP).
En el ejemplo los equipos son titanio, y jsensor (que se traducen a las IPs por medio de los archivos hosts de los equipos)