Une histoire sur les paquets DNS manquants du support technique de Google Cloud

Depuis l'éditeur de blog Google : Vous êtes-vous déjà demandé comment les ingénieurs de Google Cloud Technical Solutions (TSE) traitent vos demandes d'assistance ? Les ingénieurs du support technique de TSE sont chargés d'identifier et de corriger les sources de problèmes signalées par les utilisateurs. Certains de ces problèmes sont assez simples, mais parfois vous rencontrez un ticket qui nécessite l'attention de plusieurs ingénieurs à la fois. Dans cet article, l'un des employés de TSE nous parlera d'un problème très délicat de sa récente pratique : cas de paquets DNS manquants. Dans cette histoire, nous verrons comment les ingénieurs ont réussi à résoudre la situation et quelles nouvelles choses ils ont appris en corrigeant l'erreur. Nous espérons que cette histoire vous informera non seulement sur un bug profondément enraciné, mais vous donnera également un aperçu des processus nécessaires au dépôt d'un ticket d'assistance auprès de Google Cloud.

Une histoire sur les paquets DNS manquants du support technique de Google Cloud

Le dépannage est à la fois une science et un art. Tout commence par l'élaboration d'une hypothèse sur la raison du comportement non standard du système, après quoi sa résistance est testée. Cependant, avant de formuler une hypothèse, il faut définir clairement et formuler précisément le problème. Si la question vous semble trop vague, vous devrez alors tout analyser attentivement ; C’est « l’art » du dépannage.

Sous Google Cloud, ces processus deviennent exponentiellement plus complexes, car Google Cloud fait de son mieux pour garantir la confidentialité de ses utilisateurs. Pour cette raison, les ingénieurs TSE n'ont pas accès pour modifier vos systèmes, ni la possibilité de visualiser les configurations aussi largement que les utilisateurs. Par conséquent, pour tester l’une de nos hypothèses, nous (les ingénieurs) ne pouvons pas modifier rapidement le système.

Certains utilisateurs pensent que nous allons tout réparer comme la mécanique d'un service automobile, et nous envoyer simplement l'identifiant d'une machine virtuelle, alors qu'en réalité le processus se déroule sous un format conversationnel : collecter des informations, former et confirmer (ou réfuter) des hypothèses, et, en fin de compte, les problèmes de décision reposent sur la communication avec le client.

Problème en question

Aujourd'hui, nous avons une histoire avec une bonne fin. L'une des raisons de la résolution réussie du cas proposé est une description très détaillée et précise du problème. Ci-dessous vous pouvez voir une copie du premier ticket (modifié pour masquer les informations confidentielles) :
Une histoire sur les paquets DNS manquants du support technique de Google Cloud
Ce message contient de nombreuses informations utiles pour nous :

  • VM spécifique spécifiée
  • Le problème lui-même est indiqué - DNS ne fonctionne pas
  • Il est indiqué où le problème se manifeste - VM et conteneur
  • Les étapes suivies par l'utilisateur pour identifier le problème sont indiquées.

La demande a été enregistrée comme « P1 : Impact critique - Service inutilisable en production », ce qui signifie une surveillance constante de la situation 24h/7 et XNUMXj/XNUMX selon le schéma « Follow the Sun » (vous pouvez en savoir plus sur priorités des demandes des utilisateurs), avec son transfert d'une équipe de support technique à une autre à chaque changement de fuseau horaire. En fait, lorsque le problème a atteint notre équipe à Zurich, il avait déjà fait le tour du monde. À ce moment-là, l'utilisateur avait pris des mesures d'atténuation, mais craignait une répétition de la situation en production, car la cause profonde n'avait pas encore été découverte.

Au moment où le billet arrive à Zurich, nous disposons déjà des informations suivantes :

  • teneur /etc/hosts
  • teneur /etc/resolv.conf
  • conclusion iptables-save
  • Assemblé par l'équipe ngrep fichier PCAP

Avec ces données, nous étions prêts à commencer la phase « d’enquête » et de dépannage.

Nos premiers pas

Tout d’abord, nous avons vérifié les journaux et l’état du serveur de métadonnées et nous sommes assurés qu’il fonctionnait correctement. Le serveur de métadonnées répond à l'adresse IP 169.254.169.254 et est notamment responsable du contrôle des noms de domaine. Nous avons également vérifié que le pare-feu fonctionne correctement avec la VM et ne bloque pas les paquets.

C'était une sorte de problème étrange : la vérification nmap a réfuté notre hypothèse principale sur la perte de paquets UDP, nous avons donc mentalement proposé plusieurs autres options et façons de les vérifier :

  • Les paquets sont-ils abandonnés de manière sélective ? => Vérifiez les règles iptables
  • N'est-il pas trop petit ? MTU? => Vérifier la sortie ip a show
  • Le problème affecte-t-il uniquement les paquets UDP ou TCP ? => Partez dig +tcp
  • Les paquets générés par la fouille sont-ils renvoyés ? => Partez tcpdump
  • Est-ce que libdns fonctionne correctement ? => Partez strace pour vérifier la transmission des paquets dans les deux sens

Ici, nous décidons d'appeler l'utilisateur pour résoudre les problèmes en direct.

Lors de l'appel nous pouvons vérifier plusieurs choses :

  • Après plusieurs vérifications nous excluons les règles iptables de la liste des raisons
  • Nous vérifions les interfaces réseau et les tables de routage, et vérifions que le MTU est correct
  • On découvre que dig +tcp google.com (TCP) fonctionne comme il se doit, mais dig google.com (UDP) ne fonctionne pas
  • Ayant chassé tcpdump en travaillant dig, nous constatons que les paquets UDP sont renvoyés
  • Nous partons strace dig google.com et nous voyons comment creuser correctement les appels sendmsg() и recvms(), cependant le second est interrompu par un timeout

Malheureusement, la fin du quart de travail arrive et nous sommes obligés de faire remonter le problème au fuseau horaire suivant. La demande a cependant suscité l'intérêt de notre équipe et un collègue suggère de créer le package DNS initial à l'aide du module Scrapy 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())

Ce fragment crée un paquet DNS et envoie la requête au serveur de métadonnées.

L'utilisateur exécute le code, la réponse DNS est renvoyée et l'application la reçoit, confirmant qu'il n'y a pas de problème au niveau du réseau.

Après un autre « tour du monde », la demande revient à notre équipe, et je la transfère entièrement sur moi-même, pensant qu'il sera plus pratique pour l'utilisateur si la demande cesse de tourner d'un endroit à l'autre.

En attendant, l'utilisateur s'engage à fournir un instantané de l'image système. C'est une très bonne nouvelle : la possibilité de tester le système moi-même rend le dépannage beaucoup plus rapide, car je n'ai plus besoin de demander à l'utilisateur d'exécuter des commandes, de m'envoyer les résultats et de les analyser, je peux tout faire moi-même !

Mes collègues commencent à m'envier un peu. Pendant le déjeuner, nous discutons de la conversion, mais personne n'a la moindre idée de ce qui se passe. Heureusement, l'utilisateur lui-même a déjà pris des mesures pour atténuer les conséquences et n'est pas pressé, nous avons donc le temps de décortiquer le problème. Et puisque nous avons une image, nous pouvons faire tous les tests qui nous intéressent. Super!

Prendre du recul

L'une des questions d'entretien les plus populaires pour les postes d'ingénieur système est : "Que se passe-t-il lorsque vous envoyez un ping à www.google.com? La question est importante, puisque le candidat doit tout décrire, du shell à l'espace utilisateur, en passant par le noyau du système puis le réseau. Je souris : parfois les questions d'entretien s'avèrent utiles dans la vraie vie...

Je décide d'appliquer cette question RH à une problématique actuelle. En gros, lorsque vous essayez de déterminer un nom DNS, les événements suivants se produisent :

  1. L'application appelle une bibliothèque système telle que libdns
  2. libdns vérifie la configuration du système vers quel serveur DNS il doit contacter (dans le schéma il s'agit de 169.254.169.254, serveur de métadonnées)
  3. libdns utilise des appels système pour créer un socket UDP (SOKET_DGRAM) et envoyer des paquets UDP avec une requête DNS dans les deux sens
  4. Grâce à l'interface sysctl, vous pouvez configurer la pile UDP au niveau du noyau
  5. Le noyau interagit avec le matériel pour transmettre des paquets sur le réseau via l'interface réseau
  6. L'hyperviseur capte et transmet le paquet au serveur de métadonnées lors de son contact
  7. Le serveur de métadonnées, par sa magie, détermine le nom DNS et renvoie une réponse en utilisant la même méthode

Une histoire sur les paquets DNS manquants du support technique de Google Cloud
Permettez-moi de vous rappeler quelles hypothèses nous avons déjà envisagées :

Hypothèse : bibliothèques brisées

  • Test 1 : exécutez strace dans le système, vérifiez que dig appelle les bons appels système
  • Résultat : les appels système corrects sont appelés
  • Test 2 : utiliser srapy pour vérifier si l'on peut déterminer des noms en contournant les bibliothèques système
  • Résultat : on peut
  • Test 3 : exécutez rpm –V sur le package libdns et les fichiers de la bibliothèque md5sum
  • Résultat : le code de la bibliothèque est totalement identique au code du système d'exploitation opérationnel
  • Test 4 : montez l'image système racine de l'utilisateur sur une VM sans ce comportement, exécutez chroot, voyez si DNS fonctionne
  • Résultat : DNS fonctionne correctement

Conclusion basée sur des tests : le problème ne vient pas des bibliothèques

Hypothèse : Il y a une erreur dans les paramètres DNS

  • Test 1 : vérifiez tcpdump et voyez si les paquets DNS sont envoyés et renvoyés correctement après avoir exécuté dig
  • Résultat : les paquets sont transmis correctement
  • Test 2 : revérification sur le serveur /etc/nsswitch.conf и /etc/resolv.conf
  • Résultat : tout est correct

Conclusion basée sur des tests : le problème ne vient pas de la configuration DNS

Hypothèse : noyau endommagé

  • Test : installer le nouveau noyau, vérifier la signature, redémarrer
  • Résultat : comportement similaire

Conclusion basée sur des tests : le noyau n'est pas endommagé

Hypothèse : comportement incorrect du réseau utilisateur (ou interface réseau hyperviseur)

  • Test 1 : Vérifiez les paramètres de votre pare-feu
  • Résultat : le pare-feu transmet les paquets DNS à la fois sur l'hôte et sur GCP
  • Test 2 : intercepter le trafic et surveiller l'exactitude de la transmission et du retour des requêtes DNS
  • Résultat : tcpdump confirme que l'hôte a reçu les paquets de retour

Conclusion basée sur des tests : le problème ne vient pas du réseau

Hypothèse : le serveur de métadonnées ne fonctionne pas

  • Test 1 : vérifier les journaux du serveur de métadonnées pour détecter les anomalies
  • Résultat : il n'y a aucune anomalie dans les logs
  • Test 2 : Contourner le serveur de métadonnées via dig @8.8.8.8
  • Résultat : la résolution est interrompue même sans utiliser de serveur de métadonnées

Conclusion basée sur des tests : le problème ne vient pas du serveur de métadonnées

The bottom line: nous avons testé tous les sous-systèmes sauf paramètres d'exécution !

Plonger dans les paramètres d'exécution du noyau

Pour configurer l'environnement d'exécution du noyau, vous pouvez utiliser les options de ligne de commande (grub) ou l'interface sysctl. j'ai regardé dedans /etc/sysctl.conf et réfléchissez-y, j'ai découvert plusieurs paramètres personnalisés. Ayant l'impression d'avoir saisi quelque chose, j'ai abandonné tous les paramètres non-réseau ou non-TCP, restant avec les paramètres de montagne. net.core. Ensuite, je suis allé là où se trouvaient les autorisations de l'hôte dans la VM et j'ai commencé à appliquer les paramètres un par un, l'un après l'autre, avec la VM cassée, jusqu'à ce que je trouve le coupable :

net.core.rmem_default = 2147483647

La voici, une configuration qui casse le DNS ! J'ai trouvé l'arme du crime. Mais pourquoi cela se produit-il ? Il me fallait encore un motif.

La taille de base du tampon du paquet DNS est configurée via net.core.rmem_default. Une valeur typique se situe autour de 200 Ko, mais si votre serveur reçoit beaucoup de paquets DNS, vous souhaiterez peut-être augmenter la taille du tampon. Si le tampon est plein lorsqu'un nouveau paquet arrive, par exemple parce que l'application ne le traite pas assez rapidement, vous commencerez à perdre des paquets. Notre client a correctement augmenté la taille du tampon car il craignait la perte de données, car il utilisait une application pour collecter des métriques via des paquets DNS. La valeur qu'il a définie était la valeur maximale possible : 231-1 (si elle est définie sur 231, le noyau renverra "ARGUMENT INVALIDE").

Soudain, j'ai compris pourquoi nmap et scapy fonctionnaient correctement : ils utilisaient des sockets bruts ! Les sockets brutes sont différentes des sockets normales : elles contournent iptables et ne sont pas mises en mémoire tampon !

Mais pourquoi un « tampon trop grand » pose-t-il des problèmes ? Cela ne fonctionne clairement pas comme prévu.

À ce stade, je pourrais reproduire le problème sur plusieurs noyaux et plusieurs distributions. Le problème est déjà apparu sur le noyau 3.x et maintenant il apparaît également sur le noyau 5.x.

En effet, au démarrage

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

Le DNS a cessé de fonctionner.

J'ai commencé à chercher des valeurs de travail via un simple algorithme de recherche binaire et j'ai découvert que le système fonctionnait avec 2147481343, mais ce nombre était pour moi un ensemble de nombres dénué de sens. J'ai suggéré au client d'essayer ce numéro, et il a répondu que le système fonctionnait avec google.com, mais donnait toujours une erreur avec d'autres domaines, j'ai donc poursuivi mon enquête.

J'ai installé montre de baisse, un outil qui aurait dû être utilisé plus tôt : il montre exactement où aboutit un paquet dans le noyau. Le coupable était la fonction udp_queue_rcv_skb. J'ai téléchargé les sources du noyau et en ai ajouté quelques-unes fonctions printk pour savoir où aboutit exactement le paquet. J'ai rapidement trouvé la bonne condition if, et je l'ai simplement regardé pendant un moment, car c'est à ce moment-là que tout s'est finalement réuni en un tout : 231-1, un nombre dénué de sens, un domaine qui ne fonctionne pas... C'était un morceau de code dans __udp_enqueue_schedule_skb:

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

S'il vous plaît noter:

  • rmem est de type int
  • size est de type u16 (entier seize bits non signé) et stocke la taille du paquet
  • sk->sk_rcybuf est de type int et stocke la taille du tampon qui, par définition, est égale à la valeur dans net.core.rmem_default

Quand sk_rcvbuf approche 231, la somme de la taille du paquet peut entraîner débordement d'entier. Et comme c'est un int, sa valeur devient négative, donc la condition devient vraie alors qu'elle devrait être fausse (vous pouvez en savoir plus à ce sujet sur lien).

L'erreur peut être corrigée de manière triviale : en lançant unsigned int. J'ai appliqué le correctif et redémarré le système et DNS a de nouveau fonctionné.

Goût de la victoire

J'ai transmis mes conclusions au client et envoyé LKML correctif du noyau. Je suis content : chaque pièce du puzzle s'emboîte, je peux expliquer exactement pourquoi nous avons observé ce que nous avons observé, et surtout, nous avons pu trouver une solution au problème grâce à notre travail d'équipe !

Il convient de reconnaître que le cas s'est avéré rare et, heureusement, nous recevons rarement des demandes aussi complexes de la part des utilisateurs.

Une histoire sur les paquets DNS manquants du support technique de Google Cloud


Source: habr.com

Ajouter un commentaire