Una historia sobre paquetes DNS faltantes del soporte técnico de Google Cloud

Del editor de blogs de Google: ¿Alguna vez te has preguntado cómo los ingenieros de Google Cloud Technical Solutions (TSE) manejan tus solicitudes de soporte? Los ingenieros de soporte técnico de TSE son responsables de identificar y corregir las fuentes de problemas reportadas por los usuarios. Algunos de estos problemas son bastante simples, pero a veces te encuentras con un ticket que requiere la atención de varios ingenieros a la vez. En este artículo, uno de los empleados de TSE nos contará sobre un problema muy complicado de su práctica reciente: caso de paquetes DNS faltantes. En esta historia veremos cómo los ingenieros lograron resolver la situación y qué cosas nuevas aprendieron mientras solucionaban el error. Esperamos que esta historia no solo le informe sobre un error profundamente arraigado, sino que también le brinde información sobre los procesos necesarios para presentar un ticket de soporte técnico en Google Cloud.

Una historia sobre paquetes DNS faltantes del soporte técnico de Google Cloud

La resolución de problemas es tanto una ciencia como un arte. Todo comienza con la formulación de una hipótesis sobre el motivo del comportamiento no estándar del sistema, después de lo cual se prueba su resistencia. Sin embargo, antes de formular una hipótesis, debemos definir claramente y formular con precisión el problema. Si la pregunta suena demasiado vaga, tendrás que analizarlo todo detenidamente; Este es el "arte" de solucionar problemas.

Bajo Google Cloud, estos procesos se vuelven exponencialmente más complejos, ya que Google Cloud hace todo lo posible para garantizar la privacidad de sus usuarios. Debido a esto, los ingenieros de TSE no tienen acceso para editar sus sistemas ni la capacidad de ver configuraciones tan ampliamente como lo hacen los usuarios. Por lo tanto, para probar cualquiera de nuestras hipótesis, nosotros (los ingenieros) no podemos modificar rápidamente el sistema.

Algunos usuarios creen que arreglaremos todo como mecánicos en un servicio de automóviles y simplemente nos enviaremos la identificación de una máquina virtual, mientras que en realidad el proceso se desarrolla en un formato conversacional: recopilando información, formando y confirmando (o refutando) hipótesis, y, al final, los problemas de decisión se basan en la comunicación con el cliente.

problema en cuestión

Hoy tenemos una historia con un buen final. Una de las razones para la resolución exitosa del caso propuesto es una descripción muy detallada y precisa del problema. A continuación puede ver una copia del primer ticket (editado para ocultar información confidencial):
Una historia sobre paquetes DNS faltantes del soporte técnico de Google Cloud
Este mensaje contiene mucha información útil para nosotros:

  • VM específica especificada
  • Se indica el problema en sí: DNS no funciona
  • Se indica dónde se manifiesta el problema: VM y contenedor.
  • Se indican los pasos que siguió el usuario para identificar el problema.

La solicitud fue registrada como “P1: Impacto Crítico - Servicio Inutilizable en producción”, lo que significa un monitoreo constante de la situación 24 horas al día, 7 días a la semana según el esquema “Follow the Sun” (puedes leer más sobre prioridades de las solicitudes de los usuarios), con su transferencia de un equipo de soporte técnico a otro con cada cambio de huso horario. De hecho, cuando el problema llegó a nuestro equipo en Zúrich, ya había dado la vuelta al mundo. En ese momento, el usuario había tomado medidas de mitigación, pero temía que la situación se repitiera en producción, ya que aún no se había descubierto la causa raíz.

Cuando el billete llegó a Zurich, ya teníamos a mano la siguiente información:

  • contenido /etc/hosts
  • contenido /etc/resolv.conf
  • conclusión iptables-save
  • Montado por el equipo ngrep archivo pcap

Con estos datos, estábamos listos para comenzar la fase de “investigación” y resolución de problemas.

Nuestros primeros pasos

En primer lugar, verificamos los registros y el estado del servidor de metadatos y nos aseguramos de que estuviera funcionando correctamente. El servidor de metadatos responde a la dirección IP 169.254.169.254 y, entre otras cosas, es responsable del control de los nombres de dominio. También verificamos dos veces que el firewall funcione correctamente con la VM y no bloquee paquetes.

Fue una especie de problema extraño: la verificación de nmap refutó nuestra hipótesis principal sobre la pérdida de paquetes UDP, por lo que mentalmente se nos ocurrieron varias opciones y formas más de verificarlas:

  • ¿Se descartan los paquetes de forma selectiva? => Verifique las reglas de iptables
  • ¿No es demasiado pequeño? MTU? => Verificar salida ip a show
  • ¿El problema afecta sólo a los paquetes UDP o también a los TCP? => Aléjate dig +tcp
  • ¿Se devuelven los paquetes generados por excavación? => Aléjate tcpdump
  • ¿Libdns funciona correctamente? => Aléjate strace para comprobar la transmisión de paquetes en ambas direcciones

Aquí decidimos llamar al usuario para solucionar problemas en vivo.

Durante la llamada podemos comprobar varias cosas:

  • Después de varias comprobaciones, excluimos las reglas de iptables de la lista de motivos.
  • Comprobamos las interfaces de red y las tablas de enrutamiento, y comprobamos que la MTU sea correcta
  • descubrimos que dig +tcp google.com (TCP) funciona como debería, pero dig google.com (UDP) no funciona
  • Habiéndose alejado tcpdump mientras trabajo dig, encontramos que se están devolviendo paquetes UDP
  • Nos alejamos strace dig google.com y vemos como dig llama correctamente sendmsg() и recvms(), sin embargo el segundo es interrumpido por un tiempo de espera.

Desafortunadamente, llega el final del turno y nos vemos obligados a escalar el problema a la siguiente zona horaria. Sin embargo, la solicitud despertó el interés de nuestro equipo y un colega sugiere crear el paquete DNS inicial utilizando el módulo scrapy de Python.

from scapy.all import *

answer = sr1(IP(dst="169.254.169.254")/UDP(dport=53)/DNS(rd=1,qd=DNSQR(qname="google.com")),verbose=0)
print ("169.254.169.254", answer[DNS].summary())

Este fragmento crea un paquete DNS y envía la solicitud al servidor de metadatos.

El usuario ejecuta el código, se devuelve la respuesta DNS y la aplicación la recibe, confirmando que no hay ningún problema a nivel de red.

Después de otro "viaje alrededor del mundo", la solicitud regresa a nuestro equipo y me la transfiero por completo, pensando que será más conveniente para el usuario si la solicitud deja de dar vueltas de un lugar a otro.

Mientras tanto, el usuario acepta amablemente proporcionar una instantánea de la imagen del sistema. Esta es una muy buena noticia: la capacidad de probar el sistema yo mismo hace que la resolución de problemas sea mucho más rápida, porque ya no tengo que pedirle al usuario que ejecute comandos, me envíe los resultados y los analice, ¡puedo hacerlo todo yo mismo!

Mis compañeros empiezan a envidiarme un poco. Durante el almuerzo discutimos la conversión, pero nadie tiene idea de lo que está pasando. Afortunadamente, el propio usuario ya ha tomado medidas para mitigar las consecuencias y no tiene prisa, por lo que tenemos tiempo de analizar el problema. Y como tenemos imagen, podemos realizar las pruebas que nos interesen. ¡Excelente!

Dando un paso atrás

Una de las preguntas de entrevista más populares para puestos de ingeniero de sistemas es: "¿Qué sucede cuando haces ping?" www.google.com? La pregunta es excelente, ya que el candidato necesita describir todo, desde el shell hasta el espacio del usuario, el núcleo del sistema y luego la red. Sonrío: a veces las preguntas de la entrevista resultan útiles en la vida real...

Decido aplicar esta pregunta de recursos humanos a un problema actual. En términos generales, cuando intentas determinar un nombre DNS, sucede lo siguiente:

  1. La aplicación llama a una biblioteca del sistema como libdns.
  2. libdns verifica la configuración del sistema con qué servidor DNS debe contactar (en el diagrama es 169.254.169.254, servidor de metadatos)
  3. libdns utiliza llamadas al sistema para crear un socket UDP (SOKET_DGRAM) y enviar paquetes UDP con una consulta DNS en ambas direcciones
  4. A través de la interfaz sysctl puede configurar la pila UDP a nivel del kernel
  5. El kernel interactúa con el hardware para transmitir paquetes a través de la red a través de la interfaz de red.
  6. El hipervisor captura y transmite el paquete al servidor de metadatos al entrar en contacto con él.
  7. El servidor de metadatos, por su magia, determina el nombre DNS y devuelve una respuesta usando el mismo método.

Una historia sobre paquetes DNS faltantes del soporte técnico de Google Cloud
Permítanme recordarles qué hipótesis ya hemos considerado:

Hipótesis: bibliotecas rotas

  • Prueba 1: ejecute strace en el sistema, verifique que dig llame a las llamadas correctas al sistema
  • Resultado: se realizan llamadas al sistema correctas
  • Prueba 2: uso de sprayy para comprobar si podemos determinar nombres sin pasar por las bibliotecas del sistema
  • Resultado: podemos
  • Prueba 3: ejecute rpm –V en el paquete libdns y los archivos de la biblioteca md5sum
  • Resultado: el código de la biblioteca es completamente idéntico al código del sistema operativo en funcionamiento
  • Prueba 4: monte la imagen del sistema raíz del usuario en una VM sin este comportamiento, ejecute chroot, vea si DNS funciona
  • Resultado: DNS funciona correctamente

Conclusión basada en pruebas: el problema no está en las bibliotecas

Hipótesis: hay un error en la configuración de DNS

  • Prueba 1: verifique tcpdump y vea si los paquetes DNS se envían y devuelven correctamente después de ejecutar dig
  • Resultado: los paquetes se transmiten correctamente
  • Prueba 2: doble verificación en el servidor /etc/nsswitch.conf и /etc/resolv.conf
  • Resultado: todo es correcto

Conclusión basada en pruebas: el problema no está en la configuración DNS

Hipótesis: núcleo dañado

  • Prueba: instale un nuevo kernel, verifique la firma, reinicie
  • Resultado: comportamiento similar

Conclusión basada en pruebas: el núcleo no está dañado

Hipótesis: comportamiento incorrecto de la red del usuario (o interfaz de red del hipervisor)

  • Prueba 1: verifique la configuración de su firewall
  • Resultado: el firewall pasa paquetes DNS tanto en el host como en GCP
  • Prueba 2: interceptar el tráfico y monitorear la exactitud de la transmisión y devolución de solicitudes DNS
  • Resultado: tcpdump confirma que el host ha recibido paquetes de devolución

Conclusión basada en pruebas: el problema no esta en la red

Hipótesis: el servidor de metadatos no funciona

  • Prueba 1: verifique los registros del servidor de metadatos para detectar anomalías
  • Resultado: no hay anomalías en los registros.
  • Prueba 2: omitir el servidor de metadatos a través de dig @8.8.8.8
  • Resultado: la resolución no funciona incluso sin utilizar un servidor de metadatos

Conclusión basada en pruebas: el problema no está en el servidor de metadatos

El resultado final: Probamos todos los subsistemas excepto configuración de tiempo de ejecución!

Profundizando en la configuración del tiempo de ejecución del kernel

Para configurar el entorno de ejecución del kernel, puede utilizar las opciones de la línea de comandos (grub) o la interfaz sysctl. miré dentro /etc/sysctl.conf Y piense, descubrí varias configuraciones personalizadas. Sintiendo como si me hubiera aferrado a algo, descarté todas las configuraciones que no eran de red o de TCP y me quedé con la configuración de montaña. net.core. Luego fui a donde estaban los permisos de host en la VM y comencé a aplicar las configuraciones una por una, una tras otra, con la VM rota, hasta que encontré al culpable:

net.core.rmem_default = 2147483647

¡Aquí está, una configuración que rompe DNS! Encontré el arma homicida. Pero ¿por qué sucede esto? Todavía necesitaba un motivo.

El tamaño básico del búfer de paquetes DNS se configura a través de net.core.rmem_default. Un valor típico es de alrededor de 200 KB, pero si su servidor recibe muchos paquetes DNS, es posible que desee aumentar el tamaño del búfer. Si el búfer está lleno cuando llega un nuevo paquete, por ejemplo porque la aplicación no lo procesa lo suficientemente rápido, comenzará a perder paquetes. Nuestro cliente aumentó correctamente el tamaño del búfer porque tenía miedo de perder datos, ya que estaba usando una aplicación para recopilar métricas a través de paquetes DNS. El valor que estableció fue el máximo posible: 231-1 (si se establece en 231, el núcleo devolverá "ARGUMENTO NO VÁLIDO").

De repente me di cuenta de por qué nmap y scapy funcionaban correctamente: ¡utilizaban sockets sin formato! Los sockets sin formato son diferentes de los sockets normales: ¡pasan por alto iptables y no están almacenados en buffer!

Pero, ¿por qué un "búfer demasiado grande" causa problemas? Claramente no funciona como se esperaba.

En este punto podría reproducir el problema en múltiples núcleos y múltiples distribuciones. El problema ya apareció en el kernel 3.x y ahora también apareció en el kernel 5.x.

De hecho, al iniciar

sysctl -w net.core.rmem_default=$((2**31-1))

DNS dejó de funcionar.

Comencé a buscar valores de trabajo a través de un algoritmo de búsqueda binaria simple y descubrí que el sistema funcionaba con 2147481343, pero este número era un conjunto de números sin sentido para mí. Le sugerí al cliente que probara con este número y respondió que el sistema funcionaba con google.com, pero aún daba error con otros dominios, así que continué mi investigación.

he instalado reloj desplegable, una herramienta que debería haberse utilizado antes: muestra exactamente en qué parte del núcleo termina un paquete. La culpable fue la función. udp_queue_rcv_skb. Descargué las fuentes del kernel y agregué algunas функций printk para rastrear dónde termina exactamente el paquete. Rápidamente encontré la condición adecuada if, y simplemente me quedé mirándolo durante un rato, porque fue entonces cuando finalmente todo se unió en una imagen completa: 231-1, un número sin sentido, un dominio que no funciona... Era un fragmento de código en __udp_enqueue_schedule_skb:

if (rmem > (size + sk->sk_rcvbuf))
		goto uncharge_drop;

Por favor, tenga en cuenta:

  • rmem es de tipo int
  • size es de tipo u16 (int de dieciséis bits sin signo) y almacena el tamaño del paquete
  • sk->sk_rcybuf es de tipo int y almacena el tamaño del buffer que, por definición, es igual al valor en net.core.rmem_default

¿Cuándo sk_rcvbuf se acerca a 231, la suma del tamaño del paquete puede dar como resultado desbordamiento de enteros. Y como es un int, su valor se vuelve negativo, por lo que la condición se vuelve verdadera cuando debería ser falsa (puedes leer más sobre esto en enlace).

El error se puede corregir de una forma trivial: lanzando unsigned int. Apliqué la solución y reinicié el sistema y el DNS volvió a funcionar.

Sabor de la victoria

Envié mis hallazgos al cliente y envié LKML parche del núcleo. Estoy contento: cada pieza del rompecabezas encaja, puedo explicar exactamente por qué observamos lo que observamos y, lo más importante, ¡pudimos encontrar una solución al problema gracias a nuestro trabajo en equipo!

Vale la pena reconocer que el caso resultó ser poco común y, afortunadamente, rara vez recibimos solicitudes tan complejas de los usuarios.

Una historia sobre paquetes DNS faltantes del soporte técnico de Google Cloud


Fuente: habr.com

Añadir un comentario