Cuando el desarrollo de software se complica

Esta historia aun no ha terminado pero vengo acá sólo para desahogarme. El desarrollo de software a veces puede ser muy cabrón, sobre todo cuando hay que interactuar con componentes sobre los que no se tiene control, y protocolos diseñados por enajenados mentales.

RetroX soporta conectarse a unidades de red (Windows/NAS) para cargar juegos, el protocolo (SMB/CIFS) es muy antiguo y tiene todo tipo de problemas, sin embargo hay bibliotecas como jCIFS que ayudan a implementarlo en Android y aplicaciones Java.

Un usuario reportó un problema de conexión hacia su Shield y efectivamente RetroX arroja error porque el servidor retorna un valor vacío en vez de la lista de elementos compartidos. El error lo reporta la biblioteca y es el típico Null Pointer Exception cuando trata de obtener una lista de recursos compartidos. Parece un easy fix.

Para no alargar la historia, acá un resumen de lo que ha significado diagnosticar el problema, encontrar la causa y aún así, no tener la solución:

  1. Comprobar que el uso de jCIFS esté correcto
  2. Revisar si es o no un bug conocido y corregido en jCIFS
  3. Descartar un problema de seguridad
  4. Descartar un problema de protocolo
  5. Revisar el código y encontrar el punto exacto de la caída para ver si es simple de corregir. Y no lo es.
  6. Asumir que fue corregido y actualizar la biblioteca,pero
    1. Hay 3 forks divergentes y se debe escoger uno
    2. Hay que revisar la historia de cada uno y por qué existe
    3. Compruebo dolorosamente que no todos funcionarán con Android
  7. Escogido uno, hacer lo necesario para que se pueda usar en Android
  8. Una vez actualizado jCIFS, aparece problema de protocolo. Ahora soportan SMB2 pero la Shield no.
  9. Solucionado el problema de protocolo, volvemos al error original!!! Viene un valor vacío desde la Shield.
  10. Consigo un Windows para ver si es un problema exclusivo de la Shield y efectivamente, Windows si se conecta (*)
  11. Usando Samba compruebo que sí me puedo conectar a la Shield y a Windows, mientras que jCIFS solo a Windows.

Todo apunta a un problema de cliente, y como Samba se conecta, ahora es cosa de comparar jhacer pruebas con jCIFS y con Samba para ver cuál es la diferencia entre ambos clientes.

  1. Usando Wireshark reviso toda la comunicación y sólo aparece una diferencia. El server dice File Not Found
  2. Me enfoco en identificar problemas relacionados con nombres. Pero todos los nombres son iguales en ambos casos
  3. Verifico las peticiones antes del error via Wireshark y ambas son idénticas también!!!!!
  4. Desde la Shield no puedo ver nada, excepto que el server instalado ahí dice «Alfresco»
  5. No hay rastros de un server SMB publicado por Alfresco hasta que doy con un comunicado de prensa y se llama JLAN… con un link al código fuente
  6. Instalo el servidor de Alfresco que está con código código fuente y lo puedo intervenir. Esto es mejor que probar con la Shield al menos!
  7. El error se replica! Comienzo a buscar reportes de este problema
  8. Sólo encuentro preguntas sin respuestas o links rotos.
  9. Mostrando la interpretación del protocolo desde Alfresco logro encontrar una diferencia!!! Veo que jCIFS envía un «0x10000» cuando debe enviar «1»
  10. Modifico Alfresco para que siempre crea que viene el valor como «1» y ahora funciona!!!
  11. Me voy a dormir pensando en que solo basta con corregir jCIFS para que envíe el valor correcto.
  12. Dia siguiente. En ninguna parte jCIFS envía ese valor, pero sí envía un «1». Al parecer sería un problema de interpretación de Alfresco o de codificación de jCIFS
  13. Modificando Alfresco encuentro que lee la secuencia «00 00 01 00» en vez de leer «01 00 00 00». Hay dos bytes corridos
  14. Determino dos causas probables:
    1. El string que viene antes jCIFS lo envía con «00 00» al final y Alfresco no espera eso
    2. El byte alignment entre Alfresco y jCIFS no es el mismo y por eso se corre 2 bytes
  15. Busco que dice la especificación de SMB y ohhh my god
    1. No hay una especificación de SMB sino múltiples
    2. La especificación está basada en protocolos heredados (NetBios y hasta X.)
    3. Leer leyes es más fácil que entender esta especificación!! Hay referencias cruzadas para todos lados y no llegas a ninguna parte que realmente sirva para este caso.
    4. Me encuentro con joyas del tipo : «Los strings a veces van con 00 00 al final, y a veces NO»
    5. Y otras como, a veces el alignment es relativo al paquete y otras veces relativo al mensaje
  16. Busco otras referencias / análisis de SMB y encuentro
    1. Un libro sobre CIFS muy bueno pero que no cubre esa parte
    2. Análisis de SMB pero de v2. Gra-cias.
  17. No importa, el código es la primera referencia. Sabemos que Samba lo hace bien, revisemos
  18. Horror! El código de Samba…
    1. Son 3082 archivos en C
    2. Ninguno hace referencia a la llamada que busco, tampoco se ve la parte de codificación del mensaje tras varios minutos de búsqueda
    3. Tiene directorios llamados source2, source3 en donde el contenido está duplicado, y dentro de él duplicado nuevamente. También aparecen carpetas como rpc_client y rpcclient. Cual es cual? No se sabe.  Para buscar es un infierno
  19. Probemos a fuerza bruta entonces y vamos descartando. Compruebo que el string tanto en Alfresco, Samba y jCIFS termina con «00 00» y por lo tanto es un problema de alignment y no de strings terminados en 0.
  20. En Alfresco el alignment es de 4 bytes. Hago el cambio en jCIFS y… tampoco funciona
  21. El problema del alignment es que depende del nombre del host, entonces a veces puede funcionar y a veces no, dependiendo del largo de ese nombre. * Es una posible explicación a por qué para jCIFS o Alfresco este problema ha sido reportado pero no lo han logrado replicar, y por qué algunos usuarios si se pueden conectar a la Shield desde RetroX y otros no.
  22. Reviso el código del alignment en jCIFS y veo que es relativo al mensaje, pero para Alfresco es relativo al contenedor del mensaje. Cuando Alfresco hace alignment al valor, jCIFS no, y vice versa.
  23. Podría pensar que Alfresco tiene el error, pero si Samba logra conectarse entonces se puede codificar el mensaje en forma correcta.
  24. Podría modificar Alfresco, pero ese código corre dentro de la Shield y no es modificable ahí. Puedo implementar quizás un workaround desde el punto de vista del cliente pero…
  25. Si corrijo jCIFS se rompe todo el resto de sus llamadas, porque asume alignment relativo la mensaje, sin embargo esa versión funcionaría con la Shield….

Mientras tanto, los usuarios me siguen preguntando si podrán conectarse a la Shield via SMB, ya que desde otras aplicaciones si pueden… claro, son aplicaciones cuyo único propósito es conectarse a la Shield y probablemente están usando Samba en forma nativa, un terreno al que no me gustaría entrar para algo tan simple. Mejor hago un servidor propio!

Como comentario final, es impresionante el nivel de complejidad de protocolos «corporativos antiguos» como SMB para algo que es relativamente simple. Dentro del protocolo metieron RPC, Named Pipes y lo que tuvieran a mano, pensar que con simple HTTP se puede hacer lo mismo y más.


Actualización 1: Dejaré de luchar un rato, reporté el bug a ver si hay alguna ayuda: https://github.com/AgNO3/jcifs-ng/issues/211

Actualización 2: Logré un workaround que debería servir por mientras (20 de Marzo)

La continuación de esta historia es más o menos así: Ya abandonado el branch de SMB para RetroX y dando por congelado el tema, aparece en mi ToDo list una tarea que alguna vez anoté y que dice «Sources: SMB2 via SMBJ». Aha!! Otra hay otra implementación para revisar, vamos a ver!

Hago las pruebas respectivas e implementa sólo SMB2 y SMB3, que no me sirve porque la compatibilidad que necesito es con SMB1. Reviso en mayor profundidad y veo que el protocolo es totalmente diferente así que nada más que hacer. Pero… mientras buscaba la versión compilada de SMBJ en el repositorio de Maven aparece un proyecto relacionado llamado SMBJ-RPC, y se trata justamente de la parte del protocolo que está fallando en SMB1. Paso a enumerar para facilitar el relato:

  • Aprendo que el request que lista los recursos compartidos es parte de un servicio llamado MS-SRVS (y no SMB / LanMan como uno pensaría)
  • DCE-RPC que es la parte que estuve revisando en jCIFS es quien permite invocar los servicios de MS-SRVS. En DCE-RPC se define el alignment de los datos y esa parte la documentan los amigos de SMBJ-RPC.
  • Y para mayor goce SMBJ-RPC tiene el código fuente para NetShareEnumRequest, la llamada que me está dando problemas!
  • En el código además está el link hacia la especificacón de Microsoft para MS-SRVS.
  • La spec dice lo siguiente señoras y señores: ServerName: If this parameter is NULL, the local computer is used.  O sea, que si paso un valor nulo en vez del nombre del servidor, se elimina la dependencia del alignment!!!
  • Reviso el código de SMBJ-RPC y justamente, pasan un valor nulo antes del InfoLevel
// <NDR: pointer[struct]> [in, string, unique] SRVSVC_HANDLE ServerName,
packetOut.writeNull();

// [in, out] LPSHARE_ENUM_STRUCT InfoStruct
// <NDR: unsigned long> DWORD Level
// Alignment: 4 - Already aligned
packetOut.writeInt(getShareEnumLevel().getInfoLevel());
  • Ahí además se ve claramente que InfoLevel debe ir alineado, por lo que jlan está correcto, el que falla en esa parte es jCIFS
  • Problem is: Incluso si aplico el alineado en jCIFS lo calcula mal dependiendo del nombre del servidor pero… el nombre puede ir nulo o vacio!
  • Pruebo con NULL y jCIFS se queda esperando respuesta de jlan, tampoco le gustó. Pero qué pasa si pruebo con string vacío?
 if ( this.servername != null ) {
    // _dst.enc_ndr_string(this.servername);
    _dst.enc_ndr_string("");
 }

FUNCIONA!!!!

Este cambio hace que el alignment ya no dependa del nombre del servidor porque tanto el pointer como el string vacío dejan el valor alineado de InfoLevel a 4 bytes.

Ahora, todavía me queda la duda de quién está calculando mal el alignment, si jlan o jCIFS, pero eso lo dejo en manos de ellos en el bug report de jCIFS.

Por otro lado jlan/alfresco dice:

Anyway, there is really no point working with that library any more, since Alfresco does not support its use in a standalone capacity (standalone library hasn’t had a release since ca. 2009) and will remove the support of CIFS / SMB based shared drives in one of the upcoming versions since Microsoft / WIndows no longer supports the old CIFS / SMB 1.0 dialect provided by JLAN.

Pero el problema es que Nvidia Shield está usando esa biblioteca. La cambiarán?

Actualización 3: Finalmente encontraron el bug en jCIFS, siempre estuvo ahí pero se desconocía la causa. Ya lo corrigieron y al menos en mis pruebas funciona bien. La vida puede continuar!

Turns out the SMB_COM_TRANSACTION padding algorithm has been wrong all the time (padding two 2 bytes among other things). So this was caused by missing padding before the NetShareEnum structure.