Folclore de programadores e ingenieros (parte 3)





La última parte de una colección de historias de Internet sobre cómo los errores a veces tienen manifestaciones completamente increíbles. Primera parte , segunda parte .



Pequeño SSH que (a veces) no podía



Esta es una historia sobre una de las cacerías de insectos más emocionantes en la que tuve la suerte de participar.



En AdGear Technologies Inc., donde trabajaba, todo se mantenía en SSH. Lo hemos usado para administración, monitoreo, implementación, recopilación de registros, incluso para transmisión en vivo. Este protocolo es robusto y confiable, tiene la previsibilidad de una herramienta nativa de Unix y simplemente funciona.



Pero una vez, cartas sin una hora específica o una referencia de host nos dijeron que el protocolo no estaba funcionando.



Se acabó el tiempo



Las máquinas de nuestro centro de datos de Londres sufrieron bloqueos aleatorios al enviar archivos de registro al centro de datos de Montreal. Esta tarea se ejecutaba periódicamente desde Cron y el error se manifestaba así:



  • Los correos electrónicos de Cron informaron problemas con SSH.

    • A veces se congela.
    • A veces sale sin un error de tiempo de espera.
  • En un chequeo de salud interno, Nagios advierte sobre datos faltantes en Montreal.


Iniciamos sesión en los autos de Londres, lanzamos manualmente el comando pushy funcionó con éxito. Lo atribuimos a un problema de red temporal.



Tiempos de espera



Pero los accidentes se repetían al azar. Una vez al día, un par de veces al día, el viernes por la mañana, varias veces por hora. Estaba claro que estaba empeorando. Continuamos enviando archivos manualmente hasta que descubrimos cuál era el problema.



Hubo 17 saltos entre Londres y Montreal. Hemos creado un perfil de pérdida y retraso de paquetes. Resultó que el 1-3% de los paquetes se perdieron en un par de saltos. Junto con el Departamento de Operaciones del Centro de Datos de Londres, solicitamos el cambio de ruta.



Mientras los londinenses verificaban la información de pérdida de paquetes, comenzamos a buscar tiempos de espera aleatorios en el camino de Londres a nuestro segundoCentro de datos en Montreal. Los saltos en esta ruta fueron diferentes, no los que perdieron paquetes. Decidimos que la pérdida no era el problema principal, y además los londinenses informaron que no podían reproducir la pérdida de paquetes o tiempos de espera, y que todo parecía estar bien de su lado.



Apocalipsis



Al reenviar manualmente correos electrónicos incorrectos de Cron, notamos un patrón interesante. Los archivos se transfirieron correctamente a alta velocidad o no se transfirieron en absoluto y se suspendieron en el tiempo de espera. No ha habido casos de descarga de archivos con éxito a baja velocidad.



Al eliminar la mayoría de los datos de la ecuación, pudimos recrear la secuencia de comandos utilizando SSH de vainilla simple. En el centro de datos de Londres, el servidor "SSH mtl-machine" completó inmediatamente la tarea o se colgó y no pudo establecer una conexión. La sorpresa empezó a crecer.



¿A dónde fueron los paquetes?



Verificamos la configuración y los sistemas del servidor SSH en Montreal tres veces:



  • Los servidores DNS respondieron rápidamente.
  • La zona de búsqueda de DNS inversa se ha desactivado.
  • El número máximo de conexiones de cliente fue lo suficientemente grande.
  • No fuimos atacados.
  • El canal no estaba obstruido.


Además, incluso si algo no funcionaba, observaríamos bloqueos al trabajar con dos centros de datos diferentes en Montreal. Además, nuestros centros de datos fuera de Londres se comunicaron con éxito con Montreal. Es decir, el problema estaba relacionado con Londres.



Ejecutamos tcpdump y observamos los paquetes. Estábamos interesados ​​en la dinámica general y los datos obtenidos usando Pcaps y cargados en Wireshark. Vimos señales de pérdida y retransmisión de paquetes, pero todo fue mínimo y no fue motivo de preocupación.



Luego, analizamos toda la conexión en situaciones en las que la comunicación SSH se estableció con éxito, y luego, las conexiones en situaciones en las que la comunicación SSH estaba bloqueada.



Cuando la conexión de Londres a Montreal se atascó, llegamos a las siguientes conclusiones:



  • El establecimiento de una conexión TCP salió bien.
  • La información del servicio SSH se envió de ida y vuelta. Cuando fue necesario, hubo paquetes TCP ack normales.
  • Se envió un paquete específico desde Londres y se recibió en Montreal.
  • El mismo paquete se reenvió varias veces desde Londres y se recibió en Montreal.
  • Montreal simplemente no responde a esto.


No estaba claro por qué Montreal no respondía (debido a esto, Londres está enviando los datos nuevamente). La conexión se colgó de esto porque el protocolo de capa 4 estaba bloqueado. Aún más emocionante fue el hecho de que si interrumpe el envío SSH repetido en Londres y lo reinicia inmediatamente, entonces funcionará correctamente. En este caso, tcpdump indicó que Montreal recibió el paquete y respondió a él, y el trabajo continuó.



En el cliente SSH en Londres, habilitamos la depuración detallada ( -vvv) y, después de estas entradas de registro, la conexión se colgó:



debug2: kex_parse_kexinit: first_kex_follows 0 
debug2: kex_parse_kexinit: reserved 0 
debug2: mac_setup: found hmac-md5
debug1: kex: server->client aes128-ctr hmac-md5 none
debug2: mac_setup: found hmac-md5
debug1: kex: client->server aes128-ctr hmac-md5 none
debug1: SSH2_MSG_KEX_DH_GEX_REQUEST(1024<1024<8192) sent
debug1: expecting SSH2_MSG_KEX_DH_GEX_GROUP


Buscamos en Google "SSH hang SSH2_MSG_KEX_DH_GEX_GROUP" y obtuvimos muchos resultados, desde problemas de Wi-Fi hasta errores de TCP en Windows y enrutadores con errores que pierden fragmentos de TCP. Una de las soluciones para LAN fue calcular el MSS de la ruta y establecer este valor como MTU en ambos extremos de la ruta.



Seguí reduciendo el MTU en el servidor de Londres desde 1500; no ayudó hasta que llegué al valor mágico de 576. Después de eso, SSH no volvió a colgarse. Estaba ejecutando un script con un bucle SSH y, si lo deseaba, podría provocar tiempos de espera devolviendo la MTU a 1500, o deshacerme de ellos configurando 576. Desafortunadamente, estos son servidores de anuncios públicos y asignar globalmente una MTU de 1500 no resolverá el problema. Sin embargo, ya se mencionó anteriormente que el proceso de fragmentación o reensamblaje de paquetes probablemente esté roto en alguna parte.



Volvamos a comprobar los paquetes recibidos con tcpdump: no había signos de fragmentación. El tamaño del paquete recibido correspondía completamente al tamaño del enviado. Si algo fragmentó el paquete en el byte 576+, entonces algo lo estaba reensamblando con éxito.



Centelleo centelleo, estrella curva



A medida que profundicé en el análisis, miré los volcados de paquetes completos ( tcpdump -s 0 -X), no solo los encabezados. Al comparar el paquete mágico del envío exitoso con el paquete del envío fallido, no encontré casi ninguna diferencia, a excepción de los encabezados TCP / IP. Pero estaba claro que este era el primer paquete en una conexión TCP que contenía suficientes datos para pasar por la marca de 576 bytes. Todos los paquetes anteriores eran mucho más pequeños.



Al comparar el mismo paquete del envío fallido, en la forma en que salió de Londres y llegó a Montreal, mis ojos se fijaron en algo. Por algo sutil, y lo descarté debido a la fatiga (era viernes por la noche). Pero después de varias actualizaciones y comparaciones, ya no me imaginaba.



Así es como se veía el paquete después de salir de Londres (menos los primeros bytes que identifican las direcciones IP)
0x0040:  0b7c aecc 1774 b770 ad92 0000 00b7 6563  .|...t.p......ec
0x0050:  6468 2d73 6861 322d 6e69 7374 7032 3536  dh-sha2-nistp256
0x0060:  2c65 6364 682d 7368 6132 2d6e 6973 7470  ,ecdh-sha2-nistp
0x0070:  3338 342c 6563 6468 2d73 6861 322d 6e69  384,ecdh-sha2-ni
0x0080:  7374 7035 3231 2c64 6966 6669 652d 6865  stp521,diffie-he
0x0090:  6c6c 6d61 6e2d 6772 6f75 702d 6578 6368  llman-group-exch
0x00a0:  616e 6765 2d73 6861 3235 362c 6469 6666  ange-sha256,diff
0x00b0:  6965 2d68 656c 6c6d 616e 2d67 726f 7570  ie-hellman-group
0x00c0:  2d65 7863 6861 6e67 652d 7368 6131 2c64  -exchange-sha1,d
0x00d0:  6966 6669 652d 6865 6c6c 6d61 6e2d 6772  iffie-hellman-gr
0x00e0:  6f75 7031 342d 7368 6131 2c64 6966 6669  oup14-sha1,diffi
0x00f0:  652d 6865 6c6c 6d61 6e2d 6772 6f75 7031  e-hellman-group1
0x0100:  2d73 6861 3100 0000 2373 7368 2d72 7361  -sha1...#SSH-rsa
0x0110:  2c73 7368 2d64 7373 2c65 6364 7361 2d73  ,SSH-dss,ecdsa-s
0x0120:  6861 322d 6e69 7374 7032 3536 0000 009d  ha2-nistp256....
0x0130:  6165 7331 3238 2d63 7472 2c61 6573 3139  aes128-ctr,aes19
0x0140:  322d 6374 722c 6165 7332 3536 2d63 7472  2-ctr,aes256-ctr
0x0150:  2c61 7263 666f 7572 3235 362c 6172 6366  ,arcfour256,arcf
0x0160:  6f75 7231 3238 2c61 6573 3132 382d 6362  our128,aes128-cb
0x0170:  632c 3364 6573 2d63 6263 2c62 6c6f 7766  c,3des-cbc,blowf
0x0180:  6973 682d 6362 632c 6361 7374 3132 382d  ish-cbc,cast128-
0x0190:  6362 632c 6165 7331 3932 2d63 6263 2c61  cbc,aes192-cbc,a
0x01a0:  6573 3235 362d 6362 632c 6172 6366 6f75  es256-cbc,arcfou
0x01b0:  722c 7269 6a6e 6461 656c 2d63 6263 406c  r,rijndael-cbc@l
0x01c0:  7973 6174 6f72 2e6c 6975 2e73 6500 0000  ysator.liu.se...
0x01d0:  9d61 6573 3132 382d 6374 722c 6165 7331  .aes128-ctr,aes1
0x01e0:  3932 2d63 7472 2c61 6573 3235 362d 6374  92-ctr,aes256-ct
0x01f0:  722c 6172 6366 6f75 7232 3536 2c61 7263  r,arcfour256,arc
0x0200:  666f 7572 3132 382c 6165 7331 3238 2d63  four128,aes128-c
0x0210:  6263 2c33 6465 732d 6362 632c 626c 6f77  bc,3des-cbc,blow
0x0220:  6669 7368 2d63 6263 2c63 6173 7431 3238  fish-cbc,cast128
0x0230:  2d63 6263 2c61 6573 3139 322d 6362 632c  -cbc,aes192-cbc,
0x0240:  6165 7332 3536 2d63 6263 2c61 7263 666f  aes256-cbc,arcfo
0x0250:  7572 2c72 696a 6e64 6165 6c2d 6362 6340  ur,rijndael-cbc@
0x0260:  6c79 7361 746f 722e 6c69 752e 7365 0000  lysator.liu.se..
0x0270:  00a7 686d 6163 2d6d 6435 2c68 6d61 632d  ..hmac-md5,hmac-
0x0280:  7368 6131 2c75 6d61 632d 3634 406f 7065  sha1,umac-64@ope
0x0290:  6e73 7368 2e63 6f6d 2c68 6d61 632d 7368  nSSH.com,hmac-sh
0x02a0:  6132 2d32 3536 2c68 6d61 632d 7368 6132  a2-256,hmac-sha2
0x02b0:  2d32 3536 2d39 362c 686d 6163 2d73 6861  -256-96,hmac-sha
0x02c0:  322d 3531 322c 686d 6163 2d73 6861 322d  2-512,hmac-sha2-
0x02d0:  3531 322d 3936 2c68 6d61 632d 7269 7065  512-96,hmac-ripe
0x02e0:  6d64 3136 302c 686d 6163 2d72 6970 656d  md160,hmac-ripem
0x02f0:  6431 3630 406f 7065 6e73 7368 2e63 6f6d  d160@openSSH.com
0x0300:  2c68 6d61 632d 7368 6131 2d39 362c 686d  ,hmac-sha1-96,hm
0x0310:  6163 2d6d 6435 2d39 3600 0000 a768 6d61  ac-md5-96....hma
0x0320:  632d 6d64 352c 686d 6163 2d73 6861 312c  c-md5,hmac-sha1,
0x0330:  756d 6163 2d36 3440 6f70 656e 7373 682e  umac-64@openSSH.
0x0340:  636f 6d2c 686d 6163 2d73 6861 322d 3235  com,hmac-sha2-25
0x0350:  362c 686d 6163 2d73 6861 322d 3235 362d  6,hmac-sha2-256-
0x0360:  3936 2c68 6d61 632d 7368 6132 2d35 3132  96,hmac-sha2-512
0x0370:  2c68 6d61 632d 7368 6132 2d35 3132 2d39  ,hmac-sha2-512-9
0x0380:  362c 686d 6163 2d72 6970 656d 6431 3630  6,hmac-ripemd160
0x0390:  2c68 6d61 632d 7269 7065 6d64 3136 3040  ,hmac-ripemd160@
0x03a0:  6f70 656e 7373 682e 636f 6d2c 686d 6163  openSSH.com,hmac
0x03b0:  2d73 6861 312d 3936 2c68 6d61 632d 6d64  -sha1-96,hmac-md
0x03c0:  352d 3936 0000 0015 6e6f 6e65 2c7a 6c69  5-96....none,zli
0x03d0:  6240 6f70 656e 7373 682e 636f 6d00 0000  b@openSSH.com...
0x03e0:  156e 6f6e 652c 7a6c 6962 406f 7065 6e73  .none,zlib@opens
0x03f0:  7368 2e63 6f6d 0000 0000 0000 0000 0000  sh.com..........
0x0400:  0000 0000 0000 0000 0000 0000            ............




Y así lucía el mismo paquete cuando llegó a Montreal
0x0040:  0b7c aecc 1774 b770 ad92 0000 00b7 6563  .|...t.p......ec
0x0050:  6468 2d73 6861 322d 6e69 7374 7032 3536  dh-sha2-nistp256
0x0060:  2c65 6364 682d 7368 6132 2d6e 6973 7470  ,ecdh-sha2-nistp
0x0070:  3338 342c 6563 6468 2d73 6861 322d 6e69  384,ecdh-sha2-ni
0x0080:  7374 7035 3231 2c64 6966 6669 652d 6865  stp521,diffie-he
0x0090:  6c6c 6d61 6e2d 6772 6f75 702d 6578 6368  llman-group-exch
0x00a0:  616e 6765 2d73 6861 3235 362c 6469 6666  ange-sha256,diff
0x00b0:  6965 2d68 656c 6c6d 616e 2d67 726f 7570  ie-hellman-group
0x00c0:  2d65 7863 6861 6e67 652d 7368 6131 2c64  -exchange-sha1,d
0x00d0:  6966 6669 652d 6865 6c6c 6d61 6e2d 6772  iffie-hellman-gr
0x00e0:  6f75 7031 342d 7368 6131 2c64 6966 6669  oup14-sha1,diffi
0x00f0:  652d 6865 6c6c 6d61 6e2d 6772 6f75 7031  e-hellman-group1
0x0100:  2d73 6861 3100 0000 2373 7368 2d72 7361  -sha1...#SSH-rsa
0x0110:  2c73 7368 2d64 7373 2c65 6364 7361 2d73  ,SSH-dss,ecdsa-s
0x0120:  6861 322d 6e69 7374 7032 3536 0000 009d  ha2-nistp256....
0x0130:  6165 7331 3238 2d63 7472 2c61 6573 3139  aes128-ctr,aes19
0x0140:  322d 6374 722c 6165 7332 3536 2d63 7472  2-ctr,aes256-ctr
0x0150:  2c61 7263 666f 7572 3235 362c 6172 6366  ,arcfour256,arcf
0x0160:  6f75 7231 3238 2c61 6573 3132 382d 6362  our128,aes128-cb
0x0170:  632c 3364 6573 2d63 6263 2c62 6c6f 7766  c,3des-cbc,blowf
0x0180:  6973 682d 6362 632c 6361 7374 3132 382d  ish-cbc,cast128-
0x0190:  6362 632c 6165 7331 3932 2d63 6263 2c61  cbc,aes192-cbc,a
0x01a0:  6573 3235 362d 6362 632c 6172 6366 6f75  es256-cbc,arcfou
0x01b0:  722c 7269 6a6e 6461 656c 2d63 6263 406c  r,rijndael-cbc@l
0x01c0:  7973 6174 6f72 2e6c 6975 2e73 6500 0000  ysator.liu.se...
0x01d0:  9d61 6573 3132 382d 6374 722c 6165 7331  .aes128-ctr,aes1
0x01e0:  3932 2d63 7472 2c61 6573 3235 362d 6374  92-ctr,aes256-ct
0x01f0:  722c 6172 6366 6f75 7232 3536 2c61 7263  r,arcfour256,arc
0x0200:  666f 7572 3132 382c 6165 7331 3238 2d63  four128,aes128-c
0x0210:  6263 2c33 6465 732d 6362 632c 626c 6f77  bc,3des-cbc,blow
0x0220:  6669 7368 2d63 6263 2c63 6173 7431 3238  fish-cbc,cast128
0x0230:  2d63 6263 2c61 6573 3139 322d 6362 632c  -cbc,aes192-cbc,
0x0240:  6165 7332 3536 2d63 6263 2c61 7263 666f  aes256-cbc,arcfo
0x0250:  7572 2c72 696a 6e64 6165 6c2d 6362 7340  ur,rijndael-cbs@
0x0260:  6c79 7361 746f 722e 6c69 752e 7365 1000  lysator.liu.se..
0x0270:  00a7 686d 6163 2d6d 6435 2c68 6d61 732d  ..hmac-md5,hmas-
0x0280:  7368 6131 2c75 6d61 632d 3634 406f 7065  sha1,umac-64@ope
0x0290:  6e73 7368 2e63 6f6d 2c68 6d61 632d 7368  nSSH.com,hmac-sh
0x02a0:  6132 2d32 3536 2c68 6d61 632d 7368 7132  a2-256,hmac-shq2
0x02b0:  2d32 3536 2d39 362c 686d 6163 2d73 7861  -256-96,hmac-sxa
0x02c0:  322d 3531 322c 686d 6163 2d73 6861 322d  2-512,hmac-sha2-
0x02d0:  3531 322d 3936 2c68 6d61 632d 7269 7065  512-96,hmac-ripe
0x02e0:  6d64 3136 302c 686d 6163 2d72 6970 756d  md160,hmac-ripum
0x02f0:  6431 3630 406f 7065 6e73 7368 2e63 7f6d  d160@openSSH.c.m
0x0300:  2c68 6d61 632d 7368 6131 2d39 362c 786d  ,hmac-sha1-96,xm
0x0310:  6163 2d6d 6435 2d39 3600 0000 a768 7d61  ac-md5-96....h}a
0x0320:  632d 6d64 352c 686d 6163 2d73 6861 312c  c-md5,hmac-sha1,
0x0330:  756d 6163 2d36 3440 6f70 656e 7373 782e  umac-64@openssx.
0x0340:  636f 6d2c 686d 6163 2d73 6861 322d 3235  com,hmac-sha2-25
0x0350:  362c 686d 6163 2d73 6861 322d 3235 362d  6,hmac-sha2-256-
0x0360:  3936 2c68 6d61 632d 7368 6132 2d35 3132  96,hmac-sha2-512
0x0370:  2c68 6d61 632d 7368 6132 2d35 3132 3d39  ,hmac-sha2-512=9
0x0380:  362c 686d 6163 2d72 6970 656d 6431 3630  6,hmac-ripemd160
0x0390:  2c68 6d61 632d 7269 7065 6d64 3136 3040  ,hmac-ripemd160@
0x03a0:  6f70 656e 7373 682e 636f 6d2c 686d 7163  openSSH.com,hmqc
0x03b0:  2d73 6861 312d 3936 2c68 6d61 632d 7d64  -sha1-96,hmac-}d
0x03c0:  352d 3936 0000 0015 6e6f 6e65 2c7a 7c69  5-96....none,z|i
0x03d0:  6240 6f70 656e 7373 682e 636f 6d00 0000  b@openSSH.com...
0x03e0:  156e 6f6e 652c 7a6c 6962 406f 7065 6e73  .none,zlib@opens
0x03f0:  7368 2e63 6f6d 0000 0000 0000 0000 0000  sh.com..........
0x0400:  0000 0000 0000 0000 0000 0000            ............




¿Has notado algo? Si no, está bien. Puede copiar en dos ventanas en un editor de texto y cambiar rápidamente entre ellas para ver los cambios de símbolo.



Bien bien. ¡Esto no es pérdida de paquetes, sino corrupción de paquetes! Daño muy pequeño, muy predecible. Observaciones interesantes:



  • La parte inicial del paquete (<576 bytes) está intacta.
  • Cada 15 bytes de los 16 está dañado.
  • El daño es predecible. Todo se hvolvió x, todo se cvolvió s.


Es posible que ya haya consultado la tabla ASCII y haya concluido que un bit está atascado en el valor 1. Convertir en el 1cuarto bit de un byte arruina las letras anteriores de la izquierda a los valores de la derecha.



Los culpables obvios en nuestro campo de visión (NIC que aceptan servidores) están más allá de toda sospecha porque la falla tiene un patrón (múltiples máquinas de Londres → múltiples centros de datos y máquinas de Montreal). El motivo debe estar en la ruta y más cerca de Londres.



La situación empezó a tener sentido. También noté una pequeña pista en el modo tcpdump detallado (tcp cksum bad), que no había notado antes. El servidor de Montreal eliminó un paquete de nivel de kernel cuando se dio cuenta de que estaba dañado y no reenvió el paquete al demonio SSH en el espacio de usuario. Entonces London envió el paquete de nuevo, se dañó de nuevo y Montreal lo descartó en silencio. En términos de SSH y SSHd, la conexión está bloqueada. Desde el punto de vista de tcpdump, no hubo pérdida y los servidores de Montreal simplemente ignoran los datos.



Informamos nuestros hallazgos al Departamento de Operaciones del Centro de Datos de Londres y, en cuestión de minutos, cambiaron sus rutas de salida de manera espectacular. El primer salto y la mayoría de los posteriores fueron diferentes. El problema de la congelación se ha ido.



Los arreglos a última hora de la noche del viernes son agradables, porque los fines de semana puedes relajarte y no pensar en problemas y apoyo :)



¿Dónde está Wally?



Feliz de que ya no sufríamos de este problema y de que nuestros sistemas se estaban poniendo al día, decidí encontrar el dispositivo responsable de esta corrupción de paquetes.



Actualizar las rutas de Londres para mantener el tráfico fuera de la ruta anterior significaba que no podía reproducir fácilmente el problema. Encontré a un amigo en Montreal con una máquina FreeBSD adecuada que estaba disponible en Londres a través de las rutas antiguas.



Quería asegurarme de que el daño fuera predecible incluso sin SSH involucrado. Lo logré fácilmente con algunas canalizaciones.



En Montreal:



nc -l -p 4000 > /dev/null


Luego en Londres:



cat /dev/zero | nc mtl 4000


Dado el factor de aleatoriedad y ajustes en el ciclo de reintento, recibí varios paquetes que despejaron cualquier duda sobre las conclusiones anteriores. Esta es parte de uno de los paquetes:



Acabamos de enviar un paquete de ceros
0x0210  .....
0x0220  0000 0000 0000 0000 0000 0000 0000 0000 ................
0x0230  0000 0000 0000 0000 0000 0000 0000 0000 ................
0x0240  0000 0000 0000 0000 0000 0000 0000 0000 ................
0x0250  0000 0000 0000 0000 0000 0000 0000 1000 ................
0x0260  0000 0000 0000 0000 0000 0000 0000 1000 ................
0x0270  0000 0000 0000 0000 0000 0000 0000 1000 ................
0x0280  0000 0000 0000 0000 0000 0000 0000 1000 ................
0x0290  0000 0000 0000 0000 0000 0000 0000 1000 ................
0x02a0  0000 0000 0000 0000 0000 0000 0000 1000 ................
0x02b0  0000 0000 0000 0000 0000 0000 0000 1000 ................
0x02c0  0000 0000 0000 0000 0000 0000 0000 1000 ................
0x02d0  0000 0000 0000 0000 0000 0000 0000 1000 ................
0x02e0  0000 0000 0000 0000 0000 0000 0000 1000 ................
0x02f0  0000 0000 0000 0000 0000 0000 0000 1000 ................
0x0300  0000 0000 0000 0000 0000 0000 0000 1000 ................
0x0310  0000 0000 0000 0000 0000 0000 0000 1000 ................
0x0320  0000 0000 0000 0000 0000 0000 0000 1000 ................
0x0330  0000 0000 0000 0000 0000 0000 0000 1000 ................
0x0340  0000 0000 0000 0000 0000 0000 0000 1000 ................
0x0350  0000 0000 0000 0000 0000 0000 0000 1000 ................
0x0360  0000 0000 0000 0000 0000 0000 0000 1000 ................
0x0370  0000 0000 0000 0000 0000 0000 0000 1000 ................
0x0380  0000 0000 0000 0000 0000 0000 0000 1000 ................
0x0390  0000 0000 0000 0000 0000 0000 0000 1000 ................
0x03a0  0000 0000 0000 0000 0000 0000 0000 1000 ................
0x03b0  0000 0000 0000 0000 0000 0000 0000 1000 ................
0x03c0  0000 0000 0000 0000 0000 0000 0000 1000 ................
0x03d0  0000 0000 0000 0000 0000 0000 0000 0000 ................
0x03e0  .....




Al reproducir el error, necesitaba encontrar uno de los 17 saltos en los que se produjo el daño. No podía simplemente llamar a los proveedores de todos los clústeres y pedirles que revisaran sus sistemas.



Decidí hacer ping a cada enrutador secuencialmente, podría ayudar. Escribió paquetes ICMP especiales lo suficientemente grandes como para exceder el límite seguro de 576 bytes y los llenó con ceros. Luego, con la ayuda de estos paquetes, hice ping al servidor de Montreal desde Londres.



Los paquetes se devolvieron intactos.



Probé todas las combinaciones de velocidad, contenido, tamaño, sin éxito. No encontré ningún daño en los paquetes de ping ICMP devueltos.



En las canalizaciones de netcat, reemplacé TCP con UDP. Una vez más, sin daños.



Necesitaba TCP para reproducir el daño y TCP necesitaba dos puntos finales de comunicación. Intenté en vano averiguar si todos los enrutadores tienen un puerto TCP abierto con el que puedo comunicarme directamente.



Parecía imposible identificar el salto defectuoso desde el exterior. ¿O es posible?



Espejo Espejo en la pared



Para determinar si se producen daños, se tuvo que utilizar uno de los siguientes escenarios:



  • Verifique el paquete en el destino a través del nodo TCP con el que se está comunicando.

    • No en el espacio del usuario, donde el paquete no se entregará en caso de un error durante la verificación de la suma de comprobación, pero verifique que el paquete recibido no esté dañado usando root y tcpdump.
  • Usando un nodo TCP que actúa como un servidor de eco y refleja los datos recibidos, verifique el paquete en el nodo de envío.


De repente resultó que tenemos un segundo punto de medición disponible. No disponible directamente, pero aún así: en el primer enfoque para resolver el problema, notamos que los clientes SSH se cuelgan cuando se comunican con servidores SSH a través de un salto dañino. Ésta es una buena señal pasiva que se puede utilizar en lugar de la señal de "eco" activa.



Y en esto nos pueden ayudar numerosos servidores SSH abiertos en Internet.



No necesitamos cuentas reales en estos servidores, solo necesitamos iniciar una conexión SSH, ver si la fase de intercambio de cifrado será exitosa (con un número razonable de reintentos para tener en cuenta el daño accidental).



El plan era este:



  • Utilice la maravillosa herramienta nmap en modo "IP aleatoria" para compilar una lista de servidores SSH abiertos distribuidos geográficamente.
  • :

    • , → .
    • N- → «».
    • telltale N- → «».
  • «» «».


Pensé esto: en los rastros de todos los servidores "malos", se utilizarán varios saltos idénticos. Podremos identificar saltos sospechosos e identificar aquellos que se utilizan en rastros de servidores "buenos". Con la esperanza de que queden uno o dos.



Después de pasar una hora clasificando manualmente los servidores, dejé de explorar los datos. Tenía 16 servidores "malos" y 25 "buenos".



El primer paso fue hacer una lista de los saltos que se encuentran en todos los rastros de los servidores defectuosos. Después de limpiar la lista, me di cuenta de que ni siquiera necesito ir a la lista de los "buenos" para eliminar los falsos positivos. Los malos solo tenían un salto en común.



Sin embargo, había dos proveedores antes: London → N hops upstream1 → Y hops upstream2.



Este fue el primero de los saltos Y en upstream2, justo en el límite entre upstream1 y upstream2. Dañó paquetes TCP aleatorios, lo que dio lugar a numerosas retransmisiones y, según las especificaciones del intercambio de datos del protocolo, se congelaron o redujeron los volúmenes de transmisión.



Junto con el departamento de operaciones del centro de datos de Londres, rastreamos la dirección IP de este salto. Tenía la esperanza de que a través de su vínculo directo con upstream1 fuera posible forzar correcciones.



A través de upstream1, recibí la confirmación de que el salto que especifiqué (el primero en upstream2) tenía una "falla del módulo de control" interno que estaba afectando BGP y el enrutamiento entre las dos redes internas. Redirigieron el dispositivo defectuoso y lo apagaron pendiente de reemplazo.



Filtro de música rock



Ayudé a un usuario de una aplicación de transmisión de audio a configurar una experiencia LAN. El usuario reproducía solo música clásica, no rock. Seriamente. Los clásicos se transmitieron sin problemas y, al intentar transmitir música rock, la conexión se cortó después de unos minutos.



La aplicación recibió fragmentos de audio, los comprimió con un códec de compresión sin pérdidas y luego envió cada fragmento en un paquete UDP separado al punto final. Siempre que fue posible, la aplicación intentó utilizar IPv6, porque era más confiable que el entorno LAN, aunque podía funcionar a través de IPv4 si era necesario.



Después de una búsqueda interminable y tediosa de la causa del problema, finalmente descubrí cuál era el problema. De alguna manera, el usuario ha configurado la MTU en 1200 bytes en la interfaz de red. Y IPv6 no fragmentará automáticamente los paquetes a nivel de IP cuando la MTU esté por debajo de 1280 bytes, por lo que los paquetes más grandes simplemente no se pueden enviar. La aplicación de transmisión intentará enviar paquetes de audio de más de 1200 bytes, recibirá un error y se desconectará.



¿Por qué esto solo sucedió con la música rock? Es simple. Los códecs sin pérdida utilizan una tasa de bits variable y la música clásica se comprime mejor que la música rock. Al transmitir clásicos, el audio se comprimía constantemente en paquetes de menos de 1200 bytes, y los paquetes de música rock superaban aleatoriamente este umbral.



El usuario no sabía por qué se redujo su MTU, no lo necesitaba, así que aumentamos el valor y todo funcionó bien.



Interrupción de Internet que desaparece automáticamente



Cuando entré a la universidad en 1999, vivía en un dormitorio de estudiantes viejo y en ruinas porque no podía permitirme nada mejor. Pero al menos había un Internet bastante decente en el albergue, que aún no estaba muy extendido en mi país. Y como estaba prohibido cambiar el edificio, los cables de red (todavía coaxiales) se divorciaron de acuerdo con un esquema temporal. Estaban escondidos detrás de falsos techos en los pasillos y atravesando las puertas de las habitaciones donde simplemente yacían en el suelo. Cualquier interrupción en la comunicación podría llevar al hecho de que todo un piso se quede sin red. Desde que estudié en la Facultad de Ciencias de la Computación, me convertí rápida e involuntariamente en una persona en mi piso que arregla interrupciones bastante frecuentes, aunque no tenía ninguna experiencia en redes.



A veces, la interrupción estaba por parte del proveedor, a veces el problema estaba relacionado con nuestro proxy, pero la mayoría de las veces alguien simplemente desconectaba algún cable y no insertaba un terminador en él.



Una noche, Internet se cortó, pero solo durante unos minutos. Luego reapareció, así que no pensé mucho en eso. Pero al día siguiente se repitió la breve interrupción, y también al tercer día. Por lo general, pasaba unas 20 horas, la hora exacta flotaba y, a veces, no era en absoluto. Pero cada vez que fallaba la red, mi teléfono en el lugar comenzaba a sonar y la gente estaba cada vez más molesta por estas repetidas interrupciones.



Dado que cada interrupción duró solo unos minutos, no pude identificar una ubicación específica antes de que reapareciera la red. Intenté correr por el suelo y llamar a todas las puertas, preguntando si alguien había sacado un cable o había hecho algo con él, pero la idea no ayudó. Finalmente decidí esperar la interrupción diaria con mi confiable multímetro en la mano. En una semana, expulsé una habitación tras otra de los sospechosos. Finalmente, en uno de los cables de la habitación, noté una oleada de resistencia durante la siguiente interrupción.



Llamé, pero no la abrieron. El castillo estaba cerrado. Pero si no hay nadie en la habitación para hacer algo con la computadora o el cable, ¿por qué se interrumpe la conexión? ¿Y por qué se está recuperando? Al día siguiente todo volvió a pasar, no volvieron a abrir la puerta. Decidí apagar completamente esta habitación para que Internet funcionara en el resto del piso.



A la mañana siguiente, los inquilinos de esa habitación me informaron que su Internet no funcionaba. Fui hacia ellos y medí la resistencia en todos los cables, verifiqué todas las conexiones y terminadores. Todos los cables tienen cero ohmios, todo está en perfecto orden. Le pregunté al chico qué estaba haciendo anoche. Leo libros de texto antes de los exámenes, nada relacionado con la computadora, respondió. Volví a comprobar todo por segunda y tercera vez, pero no encontré ningún problema. Casi me rindo, y luego me di cuenta: el cable estaba sujeto debajo de la cama. Por supuesto, el núcleo de cobre del cable se rompió exactamente en este lugar, pero la funda lo sujetó firmemente para que, en condiciones normales, se mantuviera el contacto, incluso si usted se sienta en la cama. Pero cuando comencé a balancearlo, el contacto desaparecía por unos segundos con cada empujón.



Usted mismo puede adivinar lo que sucedió en esa cama durante varios minutos todas las noches, detrás de una puerta cerrada y sin respuesta a un golpe.



La historia de mel



Los programadores reales escriben en Fortran



Este puede ser el caso ahora, en la era decadente de la cerveza sin alcohol, las calculadoras y las aplicaciones "fáciles de usar", pero en los buenos viejos tiempos, cuando el término "software" sonaba raro y las computadoras reales estaban hechas de tambores magnéticos y tubos de radio, escribió Real Programmers en codigo de maquina. No en FORTRAN. No en RATFOR. Ni siquiera lenguaje ensamblador. En código de máquina. Sobre números hexadecimales reales, sin adornos e incomprensibles. Así. Varias generaciones de programadores han crecido sin conocer este glorioso pasado, y creo que debería intentar cerrar la brecha generacional y hablar sobre cómo un programador real escribió código. Lo llamaré Mel porque ese era su nombre.



Conocí a Mel cuando conseguí un trabajo en Royal McBee Computer Corp., una subsidiaria ahora desaparecida de un fabricante de máquinas de escribir. La empresa estaba fabricando la LGP-30, una computadora de batería pequeña y barata (según los estándares actuales), y acababa de comenzar a fabricar la RPC-4000, también con memoria de batería, mucho mejor, más grande y más rápida. Los núcleos magnéticos eran demasiado caros y no podían resistir a la competencia (por eso no ha oído hablar de esta empresa ni de sus computadoras). Me contrataron para escribir un compilador de FORTRAN para este nuevo milagro, y Mel fue mi guía para conocer sus capacidades. Mel desaprobó los compiladores. “¿De qué sirve que un programa no pueda reescribir su propio código?”, Preguntó. Mel escribió el programa más popular de la compañía en hexadecimal.Trabajaba para el LGP-30 y jugaba al blackjack con compradores potenciales en programas de informática. Siempre ha tenido un efecto dramático. Se exhibió un stand LGP-30 en cada feria comercial, y los proveedores de IBM se reunieron y conversaron entre ellos. ¿Ayudó a vender computadoras? Nunca hemos hablado de este tema.



El trabajo de Mel era reescribir el programa de blackjack para RPC-4000. (¿Portabilidad? ¿Qué es?) La nueva computadora tenía un esquema de direccionamiento uno más uno: además del código de operación y la dirección del operando requerido, cada instrucción de la máquina también tenía una segunda dirección que mostraba dónde estaba escrita la siguiente instrucción en un tambor magnético giratorio. ... Es decir, ¡después de cada instrucción GO TO! Mete esto en una pipa Pascal y fúmalo.



A Mel le encantaba el RPC-4000 porque podía optimizar su código: coloque las instrucciones en el carrete para que, tan pronto como se complete, el segundo esté inmediatamente debajo del "cabezal de lectura" y esté listo para su ejecución inmediata. Para hacer esto, se escribió un programa que optimiza el ensamblador, pero Mel se negó a usarlo. "Nunca se sabe dónde se colocarán los datos", explicó, "por lo que debe usar constantes separadas". Entendí la esencia de esta frase mucho más tarde. Dado que Mel conocía los valores numéricos de todos los códigos operativos y asignaba sus propias direcciones en la memoria del tambor, cada instrucción que escribía podía considerarse una constante numérica. Por ejemplo, podría seleccionar una instrucción de "suma" anterior y multiplicar por ella si tuviera un valor numérico adecuado. Muy pocas personas pudieron cambiar su código.Comparé los programas optimizados manualmente de Mel con el mismo código que había sido procesado por el ensamblador de optimización, y el código de Mel siempre se ejecutaba más rápido. El hecho es que el método de arriba hacia abajo para construir arquitectura aún no se ha inventado, y Mal no lo habría usado de todos modos. Primero, escribió las partes internas de sus bucles de programación para que fueran los primeros en obtener las direcciones óptimas en el carrete. Y el ensamblador optimizador no fue capaz de eso. Mel nunca escribió bucles con retraso de tiempo, incluso cuando el enorme Flexowriter requería un retraso entre las salidas de los personajes. Mel simplemente colocó las instrucciones en el carrete para que cuando se tuviera que leer la siguiente instrucción, pasara porque el método de arquitectura de arriba hacia abajo aún no se ha inventado y Mel no lo habría usado de todos modos. Primero, escribió las partes internas de sus bucles de programación para que fueran los primeros en obtener las direcciones óptimas en el carrete. Y el ensamblador optimizador no fue capaz de eso. Mel nunca escribió bucles con retraso de tiempo, incluso cuando el enorme Flexowriter requería un retraso entre las salidas de los personajes. Mel simplemente colocó las instrucciones en el carrete para que cuando se tuviera que leer la siguiente instrucción, pasara porque el método de arquitectura de arriba hacia abajo aún no se ha inventado y Mel no lo habría usado de todos modos. Primero, escribió las partes internas de sus bucles de programación para que fueran los primeros en obtener las direcciones óptimas en el carrete. Y el ensamblador optimizador no fue capaz de eso. Mel nunca escribió bucles con retraso de tiempo, incluso cuando el enorme Flexowriter requería un retraso entre las salidas de los personajes. Mel simplemente colocó las instrucciones en el carrete para que cuando se tuviera que leer la siguiente instrucción, pasara porincluso cuando el descomunal Flexowriter requería un retraso entre las salidas de los personajes. Mel simplemente colocó las instrucciones en el carrete para que cuando se tuviera que leer la siguiente instrucción, pasara porincluso cuando el descomunal Flexowriter requería un retraso entre las salidas de los personajes. Mel simplemente colocó las instrucciones en el carrete para que cuando se tuviera que leer la siguiente instrucción, pasara pormás allá del cabezal de lectura, y el tambor tendría que hacer otra revolución para encontrarlo. Mel encontró un término inimitable para este procedimiento. La palabra "óptimo" (óptimo) tiene un significado absoluto, así como "único", por lo que en el habla coloquial a menudo se hacía relativo: "no del todo óptimo" o "menos óptimo" o "no muy óptimo". Mel llamó a los lugares en el tambor con el tiempo de retraso más largo "los más pesimum" (las peores condiciones ambientales toleradas por el cuerpo ).



Después de terminar de trabajar en el programa de blackjack y ejecutarlo ("Incluso el inicializador está optimizado", dijo con orgullo), Mel recibió una solicitud del departamento de ventas para realizar cambios. Un elegante generador de números aleatorios (optimizado) se encargaba de barajar las cartas y repartir las cartas del mazo en el programa. Y algunos de los vendedores pensaron que era demasiado honesto, porque a veces los compradores perdían. Le pidieron a Mel que cambiara el programa para que el interruptor táctil de la consola pudiera cambiar las probabilidades del jugador y dejar que el comprador ganara. Mel se negó. Lo consideró deshonesto, era así, y que invadía su moral de programador, era así, por lo que se negó a participar. Mel fue persuadido por el jefe del departamento de ventas, Big Boss y compañeros programadores ante la insistencia del Boss. Finalmente Mel se rindió y escribió el código.pero el truco comprobó al revés: cuando el interruptor estaba encendido, el programa hacía trampa y siempre ganaba. Mel estaba encantado con su decisión. Afirmó que su mente subconsciente mostraba una ética incontrolable y se negó rotundamente a corregir el programa. Cuando Mel dejó la empresa por un ingreso más alto, Big Boss me pidió que mirara el código y me dijera si podía encontrar un validador y cambiar la forma en que funcionaba. Acepté de mala gana.¿Puedo encontrar el módulo de verificación y cambiar la forma en que funciona? Acepté de mala gana.¿Puedo encontrar el módulo de verificación y cambiar la forma en que funciona? Acepté de mala gana.



Tratar con el código de Mel fue una verdadera aventura. A menudo me parecía que la programación es una forma de arte cuyo valor real solo pueden apreciar aquellos que comprenden este arte misterioso. Contiene joyas reales y movimientos brillantes, ocultos a la vista humana y la admiración por la propia naturaleza del proceso, a veces para siempre. Puedes aprender mucho sobre una persona con solo leer su código, incluso hexadecimal. Creo que Mel era un genio no reconocido. Quizás el impacto más poderoso fue el ciclo inocente que encontré, en el que no hubo verificación fraudulenta. Sin verificación. No se .



El sentido común dictaba que esto debería ser un circuito cerrado, dentro del cual el programa circula, para siempre, sin fin. Sin embargo, el control de software lo atravesó con éxito y salió de forma segura por el otro lado. Me tomó dos semanas darme cuenta de esto. El RPC-4000 estaba equipado con un dispositivo moderno: un registro de índice. Permitía escribir bucles de programa, dentro de los cuales se usaban instrucciones indexadas. Cada vez que pasaba por el bucle, se agregaba un número del registro a la dirección de instrucción para que se refiera a la siguiente posición en la serie. Todo lo que quedaba era incrementar el registro de índice con cada pasada. Mel no se aprovechó de esto. En su lugar, introdujo la instrucción en el registro de la máquina, agregó una a su dirección y la guardó. Y luego ejecutó la instrucción modificada directamente desde el registro.El ciclo se escribió teniendo en cuenta el tiempo de ejecución adicional: tan pronto como se completó la instrucción, la siguiente apareció debajo de la cabeza lectora del tambor. Pero no hubo ningún cheque deshonesto en el bucle. La pista para salvar fue que un bit en el registro de índice estaba encendido, estaba ubicado en el código de comando entre la dirección y el código de operación. Sin embargo, Mel no utilizó el registro de índice, dejándolo en cero.



Cuando llegó mi epifanía, casi me quedé ciego. Los datos en los que estaba trabajando cerca de los niveles altos de memoria (las direcciones más grandes a las que las instrucciones podían hacer referencia) los dispuso Mel de modo que después de que se procesara la última posición, incrementar la dirección de la instrucción causaría un desbordamiento. Durante la transferencia, se agregó uno al código de operación, cambiándolo al siguiente código en el conjunto: la instrucción de salto. Por supuesto, esta siguiente instrucción se ubicó en la dirección cero y el programa fue felizmente allí. No he hablado con Mel y no sé si se rindió ante la avalancha de cambios que ha inundado la programación desde entonces. Prefiero pensar que no me rendí. Estaba tan impresionado que dejé de buscar un cheque de trampa y le dije a Big Boss que no podía encontrarlo. No se sorprendió. Cuando dejé la empresael programa de blackjack seguía haciendo trampa si se activaba el interruptor derecho, y creo que con razón. No me gustaba piratear el código de un programador real.



Problema excepcionalmente USB



Nada más salir de la universidad, me uní a una empresa y trabajé en un dispositivo de consumo durante cinco meses antes de que se mostrara al público. El dispositivo estaba ejecutando Linux. Y mientras me acostumbraba a la idea de mimarme en el espacio del kernel, me llevaron a una reunión para priorizar los errores. Numerosos errores. Cientos de errores. Cada uno de ellos dice: "Esto es imposible, ¿cómo sucedió esto?"



Gritaron: "¡Daño de memoria!" Pensé, "Hospadi, arregla tus errores". Mirando los vertederos de emergencia, vimos ... ¿qué es? El programa ejecutó la instrucción prohibida concatenando las dos cadenas usando una función de la biblioteca estándar. Hmm, raro ... Registro siguiente: no se puede recuperar una página de un archivo de paginación en un dispositivo que no tiene espacio de archivo de paginación asignado (¡creo que entiendo por qué no pudimos buscar una página!).



Una vez escribí un programa corto. Asignó el 80% de la memoria del sistema a una única matriz y le escribió números enteros secuenciales. Luego esperé a que se presionara Enter y verifiqué si el contenido de la matriz había cambiado. Ahora descargué este programa, esperé 30 segundos y luego ejecuté la verificación. No hay problema. Lo intenté unas cuantas veces más, ¡ja, sabía que no había daño de memoria! Saqué el cable de depuración (USB), después de 10 segundos lo inserté y saqué rápidamente, luego lo volví a insertar. ¡Bam! 90 errores.



Tuya.



De acuerdo, tendré que jugar con el puerto USB. ¿Entonces el problema está relacionado con él? El controlador USB no parece implementar un algoritmo mágico que arroja errores de bits al azar. ¿Probablemente un problema con el hardware? No, con él no, pero eso no impidió que hiciéramos todo tipo de lascivia con el puerto USB. Llamaron a ingenieros que se habían cambiado a otro producto hace mucho tiempo, y ahora estaban desconcertados por el problema. No recuerdo cuánto tiempo pasamos probándonos a nosotros mismos que el hardware estaba en completo, completo, oooooo orden. La conexión a tierra estaba en orden, el voltaje era estable, el reloj funcionaba con precisión y las líneas DDR eran tan perfectas que llorarías de felicidad cuando lo vieras.



Los dispositivos que fueron probados por ingenieros se volvieron cada vez más inestables. Supuse que la máquina podría cargar datos en la memoria, obtener errores de bits y luego volcarlos nuevamente en la memoria flash, tal vez incluso en el lugar equivocado (la tabla de páginas a menudo estaba dañada, por lo que se podría suponer que esto también sucede con las estructuras de seguimiento de archivos El contenido podría escribirse en lugares incorrectos y las estructuras del sistema de archivos podrían romperse, etc.) Con el tiempo, los dispositivos se degradaron tanto que ya no podían arrancar de manera confiable. Finalmente, uno de los ingenieros rompió y sobrescribió la imagen que estaba en su computadora portátil. Esta imagen era relativamente antigua.



- Tipo. Se trata del software.



- ¡¿¡¿¡¿Qué?!?!?! ¡Te aseguro que no escribimos bit fairy!



No: subió un ensamblado hace tres meses y el problema desapareció. En ese momento, me sentí responsable de haber involucrado a un grupo de personas en una empresa muy larga y sin sentido, así que me quedé durante la noche e hice una búsqueda binaria en todos los parches durante los últimos meses (me tomó más tiempo estudiar ensamblajes completos de todo el sistema operativo de lo que me gustaría ...).



Entonces, ¿qué fue ese parche mágico? Alguien agregó un controlador para el chip que analizamos al kernel. Este chip no estaba en el dispositivo.



¡Decir ah! ¡Encontramos una bruja! ¡QUÉMALO!



Muchos anunciaron que el problema estaba resuelto. Estaban felices de que en el próximo lanzamiento pudieran revertir el parche y seguir adelante. Lo hicimos retroceder con extrema delicadeza, armamos una imagen, la probamos, todo estuvo bien. No esperábamos que apareciera el mismo defecto en el núcleo en unos días.



Espere. Si el chip no estaba en la placa, ¿cómo nos lo impidió el conductor? Ejecuté lsmod, el controlador no se cargó ... “De todos modos, cuál es la diferencia, elimine el archivo del módulo y vuelva a cargar. Nifiga, el problema persiste. Esto no es normal ... "



Ahora estaba solo y observaba cómo sucedía la maldad. Empecé a analizar el parche con cuidado. Era un buen archivo de línea C de 10K proporcionado por el fabricante del chip. Sería demasiado condescendiente describirlo con la palabra "caos" (para ser justos, después de unas semanas nos enviaron un conductor mucho más atento). Después de investigar un poco, decidí que el controlador no implementó el malabarismo de bits por diversión. Entonces, cual es el trato? 48 bytes de cinco líneas de código. Una pequeña estructura en el archivo de arranque que dice qué dirección de bus buscar el chip. Quité la mayor parte del controlador, pero dejé una estructura diferente en él. El problema persistió.



¡Niños y niñas, tenemos un problema de alineación! De alguna manera, esta estructura de 48 bytes está moviendo algo en la memoria y eso conduce a errores. Descubrí que el problema ocurre cuando pones algo mayor de 32 y menos de 64 bytes en un archivo. Este conocimiento no ayudó mucho, pero al menos creó una sensación de progreso.



La compilación del núcleo produjo un archivo System.map ordenado. Enumeró dónde se encuentran todas las variables compiladas en el kernel en el espacio de direcciones virtuales del kernel. Descubrí que mi pequeña estructura está en el medio de la sección ".data". Esta sección está llena de variables inicializadas, por lo que cuando el binario del kernel se descomprime en la memoria, escribe todas esas variables de la imagen compilada. Usando System.map como referencia, he implementado una búsqueda binaria bastante tonta. En su mayor parte busqué en los enlazadores de diferentes archivos C. Encontré una variable con la que comparar; encontró el archivo del kernel que lo contiene; puse mi estructura mágica a mi lado en un archivo aleatorio y comencé a ver si el problema reaparecía.



La búsqueda procedió a los últimos elementos .data y regresó con las manos vacías. No había datos necesarios en la memoria con las variables inicializadas. Mientras me desplazaba por el archivo System.map, vi que no había prestado atención a toda la sección .bss, que contenía variables no inicializadas. Aprendiendo de errores pasados, primero verifiqué el principio y el final. Por supuesto, una variable no inicializada al principio de una sección dio lugar a errores, mientras que una variable al final de una sección no. Encontrar al culpable era solo cuestión de tiempo. La variable cuyo movimiento causó el problema fue ...



¡¿Puntero de función?!



¿Cómo diablos la alineación del puntero de función bloquea nuestro sistema? En la arquitectura ARM, no puede leer palabras al acceder sin alineación, es decir, cada variable de 32 bits debe colocarse en la memoria en una dirección que sea múltiplo de 4. Un puntero de función no es una excepción, siempre obtiene la dirección mínima. Resulta que en nuestra situación problemática, la dirección era un múltiplo de 2 n , mayor o igual que 64. Cualquier valor menor que este umbral, y el problema desapareció. También hubo orden con la alineación del puntero.



No hay una buena alineación. Al menos no antes de que ocurriera este error.



Ahora bien, este puntero de función no era un puntero "abuelo". Se refería a algo especial. Había un área en la SRAM del procesador que podíamos usar para tareas relacionadas con la carga si no podíamos usar RAM. Para ahorrar energía mientras está inactivo, copiamos una subrutina en esta área, configuramos un puntero especial que se refería a ella y luego la llamamos. ¿Qué estaba haciendo la subrutina? Echemos un vistazo al ensamblador. No soy un experto en ensambladores de ARM, pero los comentarios fueron bastante elocuentes.



//       ... 
... 
//       LPDDR   


¡¿Qué estás haciendo?! Pasó rápidamente de las operaciones básicas de registro a la desactivación del controlador de memoria. Envié un correo electrónico al fabricante que escribió la subrutina y le pregunté si faltaba algo.



Tres días después, recibí una respuesta en el estilo de "Oh, sí, debe haber una barrera de memoria". Resulta que, debido a la estructura de su caché L2, tendrían que soportar adicionalmente TLB si escribiéramos accidentalmente un múltiplo de 64 en la dirección de memoria.En tales casos, aún podemos usar la RAM cuando el controlador está apagado.



Considerando que la alineación de la variable requiere una multiplicidad mínima de 4, y que el último registro no puede tener una multiplicidad de 64 o más, en cada compilación una decimosexta parte de los datos era completamente inutilizable por el sistema.



Al final, enviamos un producto confiable con una barrera de memoria y a los clientes les encantó. Sí, y en caso de que se lo pregunte, no pude notarlo con el cable USB porque no pudimos ingresar al modo de bajo consumo debido al uso de USB. Este es un problema puramente USB.



Mensaje de error no válido



En las últimas horas del 17 de septiembre de 1996, el día antes del lanzamiento programado del servicio WebTV, nuestro grupo se reunió en el centro de operaciones en Palo Alto. Una multitud de administradores de sistemas de red y desarrolladores de software de servicios se reunieron cerca para presenciar el lanzamiento oficial.



Cuando llegó la hora señalada, uno de los usuarios de la red comenzó a registrarse en su dispositivo WebTV. Entendimos que los buenos apodos terminarían rápidamente, por lo que era importante registrarse antes de que los usuarios comenzaran a hacerlo. Además, fue agradable estar entre los primeros en registrarse para el primer servicio "real". Antes de eso, todas las cuentas eran cuentas de prueba "únicas".



Varias personas se agolparon a su alrededor, mirándolo escribir en el teclado, sintiéndose mareado por la anticipación y la falta de sueño. Bryce ingresó su nombre, dirección y otra información, y luego comenzó a escribir un apodo. Ese era su nombre para una dirección de correo electrónico. Escribió "jazz", lo que significa que su correo debería ser "jazz@webtv.net". Cuando presionó Enter en el teclado inalámbrico, escuchamos un sonido distintivo que indica la aparición de un mensaje de error. Todos miraron la pantalla.



Para comprender lo que sucedió después, es importante saber un par de cosas sobre el servicio. WebTV se posicionó como una televisión familiar, por lo que era necesario verificar el lenguaje soez y filtrar los nombres de usuario y otra información visible para los usuarios. Es imposible captar todo, pero no es difícil filtrar las cosas obvias.



Los nombres personalizados se compararon con una lista de expresiones regulares, lo que permitió compararlos con un patrón. Por ejemplo, "fu. * Bar" se comparará con todos los nombres que comiencen con "fu" y terminen con "bar". Si elige sus patrones con cuidado, puede detectar y rechazar variaciones atroces como "shitake" y "matsushita", que tienen maldiciones incorporadas.



Se utilizó el mismo mecanismo para evitar que los usuarios eligieran nombres "prohibidos" como "postmaster", "root", "admin" y "help". Teníamos un archivo de texto como este:



admin.*
      "admin".
postmaster
  postmaster.
poop
  .
weenie
  .


Cada entrada constaba de dos líneas. La primera era la expresión regular con la que se iba a comparar y la segunda línea era el mensaje de error que se mostraba al usuario. El sistema leyó el archivo dos líneas a la vez, y cuando el usuario ingresó el nombre, se comparó con todas las expresiones regulares. Se mostró un mensaje de error para la primera coincidencia encontrada. Si no hubo coincidencia, se aceptó el nombre personalizado.



El código que leyó el archivo sabía cómo omitir comentarios. Pero no sabía cómo manejar líneas vacías.



Alguien hizo cambios en el archivo de juramentos, en el camino agregando una línea en blanco después de los nombres "reservados" y antes de las palabrotas. Cuando el código lee la lista, toma la cadena vacía como una expresión regular y la palabra que la sigue como un mensaje de error. Una expresión de cadena vacía coincide con cualquier cosa.



Medianoche. Todos estamos un poco nerviosos. Bryce escribe el nombre y el sistema responde con un mensaje simple:





Empezamos a reír histéricamente. Otros se acercaron a nosotros para averiguar qué estaba pasando. Lo mostramos a la pantalla. Comenzaron a reír histéricamente.



En ese momento, en otro edificio, Mark Armstrong (a cargo de QA), junto con Bruce Leek (uno de los fundadores de la empresa), se sentaron frente a un mostrador de dieciséis consolas WebTV. Este rack, apodado "racksville", se conectó mediante un multiplexor de video a un televisor grande que mostraba imágenes de las 16 cajas simultáneamente. Mark y Bruce comenzaron a registrar los decodificadores usando un teclado con un transmisor de infrarrojos. Les llamamos por el intercomunicador:



- ¿Cómo va?



- Todo perfectamente.



- Oh Dios. Es posible que haya notado algunas cosas al registrarse.



- ¿Si? No notamos nada extraño.



- Aviso.



- Bueno. Ingresando el código postal ... hasta ahora todo está bien. OGO !!!



Apareció un mensaje amistoso en las imágenes de las 16 consolas. Los jefes sugirieron que podríamos necesitar solucionar este problema lo antes posible. Esto nos pareció una gran idea.



Arreglamos el archivo y le enseñamos al código a reconocer e ignorar las líneas vacías. Hasta donde yo sé, WebTV no le ha dicho "f - k" a ningún cliente.



Problema de bloqueo de Xbox



En ese momento, el equipo estaba trabajando en uno de los primeros juegos para una consola completamente nueva llamada Xbox. Cuando se aceleró la prueba final, QA lanzó tres decodificadores del lote de instalación para ejecutar pruebas automatizadas por la noche. Si la construcción del juego de ayer todavía se estaba probando por la mañana, indicaba su estabilidad.



Desafortunadamente, una de las consolas se estrelló por la mañana. Los bloqueos siempre son malos, pero fue un caso extremadamente malo: algo ejecutado por la tarjeta de video bloqueó todo el sistema. Diagnosticar los problemas de la tarjeta gráfica es difícil: sin depuradores, sin rastros de pila, sin depuración con printf. Solo puedes leer el código y experimentar.



Así comenzó la Caza de Insectos. Todos los días, los ingenieros principales revisaron la evidencia disponible, formularon hipótesis y descartaron posibilidades. Todas las noches, QA recibió una caída "aleatoria" sin ningún motivo. "Esto es imposible", "¿Cómo sucede esto?", "¿Quizás esto es un error en el compilador?" - todos los éxitos más populares.



En el coche de los ingenieros, el juego funcionó a la perfección durante muchos días. Pero esto fue un pequeño consuelo, porque se acercaba la fecha límite para enviar el juego a imprimir y enviar a las tiendas.



Afortunadamente, pronto encontramos un patrón, aunque bastante extraño. El juego se bloqueó solo por la noche y solo en una de las tres consolas. Empezamos a buscar diferencias entre ellos. No se trataba del cable de alimentación. No en controladores. DVD quemado fuera de servicio. Transferir la consola a su mesa, no se cae. Vuelve a ponerlo, se cae. Se trataba de un stand específico que utilizó QA.



Ahora el proceso de excluir factores requería excluir todas las variables. Al final, desesperado, el ingeniero intentó cambiar los accesorios de la mesa.



Resultó que no era un prefijo específico el que funcionaba mal. Cualquier prefijo en esta tabla cayó. En medio de la noche. A veces, por el bien de la ciencia, tienes que actuar de forma extraña, y este fue uno de esos casos. El ingeniero se sentó estoicamente en una silla, cubierta con latas de Red Bull, y Bug Hunt se convirtió en Bug Watching. El ingeniero juró que vería las pruebas automatizadas en las consolas de esta maldita mesa hasta que viera la falla con sus propios ojos.



La noche pasó lentamente, luego rápidamente, y finalmente llegó el amanecer. El juego siguió funcionando. Fue inspirador. El sol empezó a salir.



Y entonces sucedió algo interesante: un rayo del sol naciente cayó sobre la mesa. Minuto tras minuto, el rayo se arrastró por la mesa hasta los accesorios, su cálido resplandor envolvió silenciosamente la cúpula negra del accesorio.



Que cayó rápidamente.



La primera Xbox tenía un problema: la tarjeta de video podía fallar si la temperatura de la consola alcanzaba cierto valor. El software no tuvo nada que ver con eso. Se informó un problema de hardware, se lanzó el juego y Red Bull fue reemplazado por cerveza. Bien, seamos honestos, por el whisky. Uno: cero para la ciencia.



All Articles