Qindel Group, empresa desarrolladora de QVD, sigue fiel a su filosofía Open Source, así lo demuestran sus contribuciones al kernel de Linux.
Esta entrada es la tercera parte de una serie de artículos que hacen un recorrido por los pasos previos antes de realizar una contribución en el kernel de Linux [Cómo contribuir en el kernel de Linux, Identificando bugs en el kernel de Linux]. Para finalizar la serie, explicaré a continuación los detalles técnicos de las contribuciones que en Qindel Group hicimos al kernel de Linux.
Cómo empezamos
Cuando se necesita alterar una funcionalidad, lo primero es actualizar nuestro kernel a la última versión disponible. Las distribuciones Linux no suelen traer el último kernel en desarrollo, traen a lo sumo el último kernel estable. Así que se debe descargar la última versión en desarrollo y probarla (probablemente debamos compilarla nosotros mismos del código fuente). Si nuestro problema sigue existiendo en la última versión, entonces se echa mano del código fuente.
Afortunadamente, esto es muy sencillo en el kernel de Linux, puesto que su código está disponible para todo el mundo: https://github.com/torvalds/linux
Una vez tenemos el código en nuestras manos, hay que localizar dónde se encuentra nuestro problema. Para nuestro caso, linux/drivers/usb/usbip.
Y entonces, se estudia cómo funciona el código. De lo aprendido, se busca una estrategia de mejora, y se programa un cambio.
Primera prueba
Para las necesidades de QVD, revisamos que el número de puertos que tenía un hub virtual de usbip, así como el número de hubs que cargaba el driver USB/IP era configurable hasta unos ciertos máximos. Así que la primera prueba fue elevar estos números y compilar el driver. Hasta aquí sin problemas. Después comenzamos a probar todo lo que necesitábamos:
Paso 1: Cargamos el driver. Todo bien, parece que el kernel ha cargado correctamente el número de hubs que hemos configurado y que cada hub tiene el número de puertos especificado.
Paso 2: Empezamos a conectar dispositivos remotos. Todo parece correcto… hasta que terminamos el primer hub.
Más allá del primer hub, el comando se queda colgado intentando acceder al segundo… y no lo consigue. Parece que no sabe utilizar hubs más allá del primero. Esto… ¡es un BUG en la herramienta!
Por lo menos, vamos a comprobar que el primer hub funciona correctamente. Así que empezamos a conectar distintos tipos de dispositivos de manera remota y… ¡no nos funciona el lector de huellas! ¡Pero si antes funcionaba! Lo que es peor… cuando se intenta utilizar suelta una traza en el log de sistema: Kernel BUG.
En este punto, comprobamos si este último asunto podía tener relación con nuestro cambio, y no, no era así. Cargamos la versión del módulo que venía con el último kernel (sin modificaciones por nuestra parte) y el resultado era el mismo: no funcionaba y línea de bug en el log. No habíamos encontrado un bug… ¡habíamos encontrado DOS!
Además, que un dispositivo deje de funcionar en una versión superior del kernel, es lo que se conoce como una regresión. Es de los más graves, porque significa que algo que funcionaba bien, se ha roto en alguno de los últimos cambios.
Había llegado el momento de reportar problemas.
Reportando un problema a la lista USB del kernel
La lista USB del kernel es el sistema que gestiona las listas de correo para desarrolladores del kernel. Es posible suscribirse a ellas y recibir información, así como escribir a ellas para reportar errores. Existe además una serie de normas de uso que se pueden buscar en el enlace http://vger.kernel.org/.
Por si el lector se plantea en algún momento seguir los pasos del autor, decir que estas reglas son bastante estrictas, y no seguirlas puede llevarnos a recibir un rechazo de nuestro correo, o incluso a ser bloqueados en las listas durante un tiempo.
Para nuestro caso, se reportó a la lista USB. En el correo escrito, se reportó ambos problemas y se dieron detalles de la implementación de las pruebas realizadas, entornos de test, etc… A este correo, respondió un desarrollador que a partir de ahora llamaremos “Desarrollador 1”, responsable de la parte concreta de USB/IP. La persona en cuestión pidió algunas aclaraciones sobre los problemas, y sobre todo… logs, más logs. Los desarrolladores del kernel opinan (y según parece es una opinión generalizada), que nunca se envían suficientes logs de los problemas. Así que hubo que remitir hasta el último log que pudiera tener la más mínima relación con el asunto, y detallar aún más el entorno de pruebas y la implementación de estas.
Después de todos estos detalles, el Desarrollador 1 solicitó ayuda de un segundo desarrollador, en adelante el Desarrollador 2, que fue quien hizo los últimos cambios sobre el subsistema USB/IP. Este respondió que cuando hizo las pruebas en su momento todo funcionaba, pero que tenía que probar con otros tipos de dispositivos USB, dado que en QVD habíamos probado que distintos tipos de dispositivos podían fallar o no.
Los siguientes correos no tuvieron precio. En resumen, el Desarrollador 2 no podía sino disculparse de que semejante problema se le hubiera colado, mientras que el Desarrollador 1 no podía creer que aquel código hubiese pasado los test. El Desarrollador 1 descubrió de hecho, que el bug que se había colado no solo era una regresión para ciertos tipos de dispositivos, sino que además dejaba el kernel en un estado inestable que a la larga ¡terminaba colgando la máquina completa!
Mientras tanto, el equipo de QVD no se quedó a la espera. Mientras los desarrolladores del kernel verificaban que el problema era tal, en Qindel estuvimos haciendo un proceso conocido como bisect. Con él, se empieza a probar sobre distintos cambios en el código de un producto, organizados en una secuencia lógica con un propósito: localizar el cambio exacto en el que se rompió el software.
Cómo realizar un bisect del kernel es un tema interesante, y hay literatura al respecto en internet, por ejemplo: http://blog.oddbit.com/2014/07/21/tracking-down-a-kernel-bug-wit/
Esta es la metodología que seguimos en QVD. Y con ella, dimos con el momento exacto en la línea de cambios en el código del kernel donde nacieron nuestros problemas (nuestros y de otros, claro). Y así se lo enviamos a la lista para ayudar a que encontrasen su problema. A este respecto, indicar que los desarrolladores con los que tratamos fueron muy agradecidos, y creo que es la tónica general en el entorno del kernel. La ayuda bien intencionada es bien recibida, y si es útil más aún.
Un par de días más tarde, nos mandaron una solución y nos pidieron que probáramos su parche. Todo volvió a funcionar como la seda. Nuestro lector de huellas se podía compartir por USB/IP y usarse correctamente. Además, la herramienta en línea de comando sabía utilizar más de un hub. Probamos con todos los dispositivos que teníamos y funcionaban.
Asunto arreglado.
La contribución de Qindel Group
En este momento, el lector se habrá dado cuenta de que lo único que habíamos hecho era reportar un problema y ayudar en su resolución, pero el parche lo hicieron otros. Tranquilo, ahora vamos con el nuestro.
Una vez estaba todo arreglado, y los parches enviados entraron a la rama master del kernel, el resto de ramas en las que todo estaba roto empezaron a “importar” el cambio, para tenerlo también arreglado. Nosotros por nuestra parte, con el asunto arreglado, continuamos con nuestro desarrollo para conseguir que QVD pueda usar USB/IP con contenedores LXC.
Nuestro desarrollo incluía estudiar a fondo el funcionamiento del driver, y la experiencia de haber contribuido en el bug anterior fue muy valiosa. Fue durante este estudio que el autor, mientras sacaba trazas del funcionamiento de la herramienta en línea de comandos, se encontró con que en su relación con el driver había un pequeño defecto: uno de los comandos que pasaba internamente la herramienta al driver escribía una maraña sin sentido detrás del comando.
Nuevamente, echamos mano del código fuente. Efectivamente, ahí había un error. Una variable donde se preparaban los datos del comando que debía pasarse al driver estaba sobredimensionada, y la orden de escritura se pasaba de escribir, resultando en una escritura aleatoria y sin sentido. Nadie se había dado cuenta porque el driver ignoraba todo detrás del comando, pero este tipo de escrituras sin control sobre la información que viene detrás puede llevar a inseguridades. Un programador con malas intenciones podría intentar escribir esa zona de memoria con algo de su conveniencia y alterar el comportamiento del software.
Esta vez, el parche lo escribimos en Qindel Group. Hubo que echar mano de la nada corta guía para enviar parches al kernel:
Enviamos el parche, y respondió nuestro viejo amigo, el Desarrollador 1. Veía la necesidad, y nos hizo alguna sugerencia de implementación. Los reorganizamos de manera apropiada y lo enviamos de nuevo. Y se aceptó.
El parche entró a la rama máster, y empezó a portarse a ramas estables, puesto que tenía implicaciones de seguridad. Ayer mismo recibimos notificación de que se ha portado a ramas más antiguas que todavía están en mantenimiento. Y esto, resulta gratificante.
Así es, gratificante. El kernel de Linux es un software que lleva en pie muchos años, y que sigue avanzando y expandiéndose. Y todo eso, es gracias a colaboraciones. Y hoy podemos decir que en Qindel Group, hemos puesto nuestro granito de arena. Nos sentimos orgullosos de ello, y lo volveremos hacer si se presenta la ocasión.
Gracias por su interés, y esperamos le haya gustado esta serie de artículos.