All links of one day
in a single page.
<Previous day - Next day>

rss_feedDaily RSS Feed
floral_left The Daily Shaarli floral_right
——————————— Saturday 10, April 2021 ———————————

Proxmox : bridge + politique de filtrage REJECT côté hyperviseur + VM à faible trafic = « connection refused »

Résumé : si tu utilises le pare-feu proposé par la solution de virtualisation Proxmox (c'est juste une surcouche à Netfilter / iptables) avec une politique "REJECT" (jeter les paquets non désirés en prévenant leur émetteur), que tes VMs accèdent au réseau via un bridge GNU/Linux et que, par intermittence, tes tentatives de connexion (SSH, par exemple) à une machine virtuelle à très faible trafic se terminent sans raison apparente en « connection refused », il est possible que le filtrage Proxmox activé sur une autre VM rejette la connexion à cause d'un bridge qui n'a plus la VM légitime dans sa table de commutation (le trafic est alors dupliqué, par le bridge, à l'ensemble des VMs, et le pare-feu côté hyperviseur rejette illégitimement la connexion entrante).

C'est l'occasion de réviser les bases : table ARP, table de commutation, durées de rétention des entrées de chaque table (ARP Linux = potentiellement plusieurs heures), comportement d'un switch / bridge quand sa table MAC est incomplète, rappel que toute machine peut clôturer une connexion qui ne lui est pas destinée mais qui lui parvient, etc.

Solutions possibles : politique de filtrage "DROP" (par défaut dans Proxmox) ou harmoniser la durée de rétention des tables ARP et MAC (si on le peut, car un switch physique n'est pas forcément paramétrable) ou fournir aux VMs un accès au réseau via un routage IP / niveau 3 (plus de bridge / niveau 2) ou ajouter un filtrage niveau 2 (ebtables) complexe.



Soit un serveur dans une machine virtuelle sur un hyperviseur Proxmox. Depuis une machine distante, on supervise son port SSH avec Picomon, un outil de supervision léger qui utilise lui-même check_ssh de nagios/monitoring-plugins, une valeur sûre. Une vérification tous les quarts d'heure.

Surprise : parfois, la vérification retourne une erreur « connection refused ». Lancé sur la machine de supervision, tcpdump voit un paquet TCP avec le drapeau RST en réponse au SYN initial. Ça ne devrait pas puisqu'il n'y a aucun changement côté serveur (sshd est toujours lancé) ni aucun filtrage temporaire de type fail2ban ni cookies TCP (activés par Linux quand il y a trop de paquets TCP avec le drapeau SYN qui entrent sur la machine, car c'est le signe d'une potentielle attaque par saturation des états TCP) ni…

Même s'il y a une grande part d'aléatoire (en apparence…), le problème est reproductible avec le client OpenSSH (ssh sous GNU/Linux et *BSD) et telnet. Depuis plusieurs points du réseau, tous chez des Fournisseurs d'Accès à Internet différents. Le problème est donc du côté de la destination, du serveur.

Le problème est toujours le même : parfois, un telnet / ssh termine en « connection refused » puis toutes les tentatives suivantes sont fructueuses… jusqu'à temps que le problème revienne. Nous avons même réussi à enchaîner 6 « connection refused » avant d'établir une connexion. Mais c'est arrivé une seule fois, donc il ne faut pas trop en tenir compte.

Côté machine virtuelle, un tcpdump capture un SYN entrant sur la VM et des SYN+ACK sortants. Ce n'est donc pas la VM légitime qui émet le TCP RST reçu par le client (Picomon).


Le NAT ?

L'hyperviseur se trouvant derrière un accès à Internet de particulier, la première piste est un NAT / pare-feu trop agressif qui ferme prématurément les états liés à la connexion. Nous n'y croyons pas, car on parle de connexions pas encore établies. Même incrédulité concernant une éventuelle utilisation du port de NAT entrant par d'autres usages : il y a très très très très peu de trafic sur cet accès à Internet donc plein de ports libres pour NAT-er les connexions sortantes (SNAT, Source NAT). Même pas de torrent au moment des faits !

De plus, la configuration du DNAT (Destination NAT, NAT entrant) est correcte : une seule règle concernant ce port + règle qui ne varie pas dans le temps + une seule adresse IP définie comme destination finale / IP à substituer à l'IP de destination.


Le filtrage Proxmox ?

Proxmox propose des fonctions de filtrage réseau. Ce filtrage est effectué sur l'hyperviseur (les interfaces TAP de la VM filtrée sont ajoutées à un nouveau bridge, qui est lui-même connecté au bridge principal des VM de l'hyperviseur via une paire d'interfaces de type veth). Le filtrage est appliqué sur la veth côté bridge principal. Il y a aucun règle de filtrage définies pour cette machine virtuelle, mais nous choisissons de désactiver le filtrage côté Proxmox et de redémarrer la VM. Le problème persiste.


Qu'est-ce qui est susceptible de bagoter (flapper) ?

Le routage IP ? Il n'y a pas de routage à l'intérieur du réseau local : le routeur du réseau est dans le même réseau que la machine virtuelle. Dans le même segment réseau.



La résolution ARP (IP vers MAC) ? Le routeur du réseau est un OpenWRT. L'observation de la sortie d'un arp -an ne montre rien de suspect. Enfin, si : elle montre plusieurs adresses IPs pour une même adresse MAC, ce qui fera douter l'un de nous. Mais plusieurs adresses IP associées à une même adresse MAC, donc à une même machine, c'est parfaitement normal. Ce qui a semé le doute, c'est que l'IP supplémentaire n'est pas censée être affectée à qui que ce soit, et qu'elle est passée d'une adresse MAC à une autre au cours de nos observations. Probablement une personne disposant d'une machine virtuelle sur l'hyperviseur qui jouait pendant qu'on observait.

Cette obversation de la table ARP du routeur permet également d'invalider notre deuxième hypothèse : un conflit d'adresse IP ou d'adresse MAC. Deux VMs utilisant la même IP / MAC, l'une sans démon SSH en cours d'exécution, l'autre avec, provoquerait aussi ce comportement.



La table de commutation du bridge ? Sur l'hyperviseur, les machines virtuelles accèdent à Internet via un bridge. Un bridge GNU/Linux, c'est un commutateur virtuel minimaliste. Il a donc une table de commutation qui dit sur quel port (donc à quelle VM) il doit envoyer le trafic destiné à telle adresse MAC. Pour la visualiser : brctl showmacs <NOM_BRIDGE>.

Hum… À chaque fois que le problème se produit, la table MACs ne contient pas l'adresse MAC de la VM de destination.

À chaque fois que l'on vide la table de commutation du bridge à la mano, on reproduit le problème.

Cool, on passe d'un problème pseudo-aléatoire à un problème reproductible à la demande, c'est un pas essentiel. \o/


Politique de filtrage

Mais… D'où l'absence d'une correspondance MAC / port sur un switch génère un paquet TCP RST ?! Cette histoire devrait terminer en timeout (+ réémission des SYN / SYN+ACK).

Sauf que… Que fait un switch quand il n'a pas d'association entre une MAC et un port ? Il émet le paquet sur tous ses ports. Dans le cas présent, toutes les machines virtuelles de l'hyperviseur reçoivent le TCP SYN initial.

Oui, et alors ? Puisqu'il n'y a pas leur adresse MAC (ni celle de broadcast ni celles normalisées pour le multicast) dans le champ destination de la trame ethernet, la carte réseau de chaque machine virtuelle va ignorer le paquet, sauf la machine légitime, qui va y répondre et re-peupler ainsi la table de commutation. Ce n'est pas aussi simple que cela, nous y reviendrons à la fin, mais cette subtilité n'est pas le sujet pour l'instant.

Si le pare-feu """"Proxmox"""" est désactivé pour la VM problématique, il ne l'est pas pour les autres VMs, dont, pour rappel du paragraphe précédent, leur interface réseau côté hyperviseur reçoit le trafic émis par le bridge sur tous ses ports. Nous regardons la liste des règles de filtrage : si aucune n'est déclenchée, une règle jette les paquets en prévenant l'émetteur (« REJECT» dans la terminologie Netfilter / iptables). Cette politique est notre choix (Proxmox propose toutes celles de Netfilter / iptables).

Après vérification, nous avons une seule VM filtrée sur cet hyperviseur. tcpdump sur l'une de ses interfaces veth nous confirme que c'est bien la règle de filtrage sur cette interface qui génère un paquet TCP RST. \o/

Nous remplaçons « REJECT» par « DROP » (jeter les paquets sans prévenir l'émetteur) dans la politique de filtrage. Plus moyen d'avoir l'erreur à la main, et plus d'alerte Picomon durant quelques cycles. Puis, à nouveau, « connection refused ».

Mince. On n'a donc pas identifié l'origine de la panne, pense l'un de nous. L'autre dit "nan mais c'est normal. Y'a un switch physique entre le routeur du réseau et les hyperviseurs, donc lui aussi il diffuse sur tous ses ports une fois l'association périmée à son niveau, et, sur l'autre hyperviseur, il y a aussi un bridge et des VMs avec des règles de filtrage similaires". C'est donc le même problème. On change la politique de sécurité (REJECT -> DROP) sur TOUTES les autres VM filtrées du cluster Proxmox. On attend 24 h : pas d'alertes Picomon et impossible de reproduire le problème à la main. \o/


Et du coup, y'a quoi comme solutions ?

Changer la politique de filtrage Proxmox de « REJECT » à « DROP ».

Harmoniser la durée de rétention d'une entrée dans la table MAC des switchs avec celle d'une entrée dans la table ARP du (ou des) routeur. Dans notre cas, cela signifie augmenter la durée de rétention d'une entrée dans la table de commutation d'un bridge GNU/Linux. Cela se fait dans /sys/class/net/<NOM_BRIDGE>/bridge/ageing_time + configuration sysctl pour rendre le changement permanent.

Fournir aux VMs un accès au réseau via du routage IP / niveau 3. Plus de bridge / de niveau 2.

Filtrer ce qui entre sur la VM au niveau 2 (ebtables) afin que le paquet envoyé à toutes les VMs par le switch atteigne jamais iptables. Mais c'est pénible : "si l'adresse MAC de destination n'est ni celle de broadcast ni celle de la VM, alors filtrer", cela fait deux règles par VM sans compter ce qu'on n'a pas identifié, c'est relou.


Explications complémentaires

Forcément, on se pose des questions.



"Nan mais normalement, quand le routeur du réseau reçoit le premier paquet SYN, il effectue une résolution ARP. La réponse de la VM met à jour la table de commutation des switchs sur le chemin donc notre problème ne devrait pas arriver".

Oui… Sauf si le routeur a encore la réponse, datant d'un précédent échange, dans sa table ARP.

D'un côté, la durée de vie par défaut d'une entrée dans la table MAC d'un bridge GNU/Linux est de 5 minutes. C'est le manuel qui le dit et cela se vérifie avec cat /sys/class/net/<NOM_BRIDGE>/bridge/ageing_time. Nous n'avons pas trouvé l'info dans la datasheet du switch physique, mais nos observations laissent supposer une durée de vie de 5 à 10 minutes.

D'un autre côté, la durée de vie d'une entrée dans la table ARP de Linux est… un vrai bordel à déterminer. Après un délai pseudo-aléatoire (borné par les calculs /proc/sys/net/ipv4/neigh/default/base_reachable_time / 2 et 3 * (/proc/sys/net/ipv4/neigh/default/base_reachable_time /2), l'état de l'entrée passe de « REACHABLE » à « STALE ». Une entrée ARP dans l'état « STALE » est toujours utilisable (Linux pourra confirmer l'association en effectuant une résolution ARP après-coup ou en se contentant d'avoir reçu du trafic provenant de la machine). Pour sortir de cet état « STALE » (donc pour disparaître), l'entrée doit remplir quatre conditions cumulatives :

  • 1) ne pas être utilisée dans la table de routage et que le garbage collector de ladite table soit passé (sa fréquence est définie dans /proc/sys/net/ipv4/route/gc_timeout) ;

  • 2) que le nombre d'entrées de la table dépasse un seuil (/proc/sys/net/ipv4/neigh/default/gc_thresh1). Sur un tout petit réseau, ce seuil, de 128 entrées par défaut, est jamais atteint ;

  • 3) que le timeout de l'entrée ait expiré (/proc/sys/net/ipv4/neigh/default/gc_stale_time) ;

  • 4) que le délai avant passage du garbage collector soit écoulé (fréquence définie dans /proc/sys/net/ipv4/neigh/default/gc_interval).

D'après nos observations, une entrée ARP non utilisée existe encore une heure après son ajout automatique. Soit bien au-delà des 5 à 10 minutes de la purge de la table MAC de nos switchs (physiques ou virtuels).

Sources de tout ce paragraphe : 1, 2 (cette source parle du cache de la table de routage IPv4 de Linux… qui n'existe plus mais la structure de données perdure donc la réflexion semble rester pertinente).

Donc, si, c'est parfaitement crédible que le routeur n'effectue pas une résolution ARP préalable.

Les switchs sur le chemin qui "oublient" l'existence de la VM est tout aussi crédible : 1) il s'agit d'un serveur (donc absence de trafic parasite permanent que l'on retrouve sur une station de travail type mDNS, SMB, etc.) ; 2) il s'agit d'un bastion SSH, ce qui occasionne très très peu de trafic, d'autant qu'il est accessible sur le net via un port non-standard et qu'un fail2ban rode.



"Nan mais la VM légitime répond TCP SYN+ACK donc le TCP RST illégitime ne devrait pas être pris en compte par le client SSH.

Oui, mais non. Si le SYN+ACK parvient au client avant le RST, l'implémentation TCP va incrémenter ses compteurs internes et générer le dernier ACK de la [poignée de main TCP](https://fr.wikipedia.org/wiki/Acquittement_(informatique). Le RST reçu après sera invalide car son numéro de séquence ne correspondra pas au compteur interne, donc, en effet, il sera ignoré et la connexion ne sera pas interrompue.

Mais, le pare-feu qui émet le RST est sur l'hyperviseur, pas dans une VM. Sa réponse parvient forcément plus rapidement au client que celle que la VM légitime (moins d'empilement de couches, de copies dans des structures de données, de timers, etc.). De plus, le RST ferme les états dans les pare-feux et NAT situés entre le serveur et le client, donc les SYN+ACK émis (et réémis) par la VM légitime arriveront jamais jusqu'au client SSH.



"Nan mais notre diagnostic est incorrect, car, s'il était vrai, cela signifie qu'un pare-feu configuré avec une politique « REJECT » dans une VM chez n'importe quel hébergeur provoquerait ce genre d'effet secondaire sur des clients à faible trafic".

Normalement, le pilote de la carte réseau transmet au système uniquement les trames ethernet dont l'adresse MAC de destination correspond à la sienne. Sauf quand la carte réseau est en mode promiscuous. Donc un pare-feu à l'intérieur d'une VM ne verrait pas les paquets qu'un switch aurait diffusé sur tous ses ports et n'enverrait donc pas de TCP RST en retour.

  • Ça, c'est la théorie. Durant nos tests, un tcpdump -p (-p = désactivation du mode promiscuous, ce que /var/log/kern.log confirme) lancé dans une VM capture les paquets que le switch diffuse à toutes les VMs. En revanche, une règle de filtrage sur ces paquets ne produit aucun effet (compteurs iptables -L -n -v toujours à zéro). D'après notre test, il s'agit du comportement du pilote virtio_net. Le pilote e1000e ne fait pas remonter les paquets tant qu'on n'active pas le mode promiscuous.

Ensuite, la latence causée par la remontée du paquet jusqu'à la VM puis la descente du TCP RST entrerait en compétition ardue avec la réponse légitime. Dans notre cas, la réponse illégitime gagne la course car la compétition se joue entre un pare-feu sur l'hyperviseur et une réponse émise par un noyau Linux dans une VM.

Enfin, il faut remplir plusieurs conditions pour déclencher ce bazar, dont certaines sont peu communes chez les hébergeurs justement conscients des risques : VMs raccordées au réseau via un bridge (très peu courant) + filtrage sur l'hyperviseur + politique de filtrage de type rejet en prévenant l'émetteur (dite « REJECT ») alors que les préconisations sont de jeter silencieusement les paquets venant de l'extérieur afin de donner le moins d'indices possibles à un attaquant (c'est d'ailleurs la politique par défaut proposée par Proxmox) + durée de la rétention ARP supérieure à la durée de rétention de la table de commutation des switchs sur le chemin (alors que les hébergeurs ont plutôt du matos aux durées de rétention harmonisées plutôt qu'un routeur logiciel GNU/Linux) + machine émettant et recevant peu de trafic.



Debug et écriture de ce shaarli en collaboration avec Johndescs.

-