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:

captura de pantalla

Espero que os sirva y os haya gustado la entrada… y como diría el grandísimo mago Juan Tamariz: Tiararaaaaa raaaaaaaaaaaa….

 

2 comentarios

Añadir un comentario

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *

Este sitio usa Akismet para reducir el spam. Aprende cómo se procesan los datos de tus comentarios.