1 points par GN⁺ 2024-03-14 | 1 commentaires | Partager sur WhatsApp
  • Fly.io a modifié son fonctionnement pour réduire la charge d’état des passerelles WireGuard tout en conservant une communication directe entre flyctl et les Fly Machines, en ajoutant les pairs au noyau au moment de la connexion plutôt qu’en les préinstallant
  • Le flux existant reposait sur une API GraphQL qui transmettait la configuration des pairs via NATS RPC, puis wggwd les enregistrait dans SQLite et dans WireGuard du noyau Linux avant que flyctl ne se connecte
  • La perte de messages NATS combinée à la création ponctuelle de pairs par les jobs CI a conduit à l’accumulation de centaines de milliers de pairs jamais réutilisés sur les passerelles, ralentissant les opérations du noyau et le chargement après redémarrage
  • La nouvelle approche intercepte les paquets handshake initiation via un filtre BPF ou dans le chemin de réception WebSockets, déchiffre une partie du handshake Noise pour identifier la clé publique, puis récupère via une API HTTP interne uniquement les pairs nécessaires
  • Après quelques semaines en production, le nombre d’anciens pairs a quasiment disparu, et les passerelles peuvent gérer des configurations de pairs et des redémarrages plus rapides avec moins d’état

Comment Fly.io utilise WireGuard

  • Fly.io exécute des conteneurs dans des VM basées sur Firecracker et utilise WireGuard à plusieurs endroits, presque comme une partie de son API client
  • À l’exécution, flyctl crée sa propre pile TCP/IP avec une adresse IPv6 dédiée et communique directement avec les Fly Machines du réseau Fly.io
  • Cette approche facilite l’exposition de fonctionnalités comme un builder Docker distant comme si elles se trouvaient sur le même LAN, mais elle est plus difficile à exploiter de manière fiable sur la durée
  • Fly.io a fini par faire de WireGuard-over-WebSockets son chemin par défaut

Ancien flux de provisioning des passerelles

  • Fly.io relie les connexions WireGuard entrantes à des réseaux privés appropriés via plusieurs serveurs passerelles répartis dans le monde
  • Quand flyctl doit communiquer avec une Fly Machine pour construire un conteneur, ouvrir une console SSH, copier des fichiers ou proxifier un service, il lance ou contacte un processus agent en arrière-plan
  • Lors de son premier lancement, l’agent crée une nouvelle configuration de pair WireGuard via l’API GraphQL
    • La configuration du pair se compose d’une clé publique et d’adresses à connecter
  • L’API transmet ensuite cette configuration à la passerelle appropriée via un RPC du système de messagerie NATS
  • Sur la passerelle, wggwd reçoit la configuration, l’enregistre dans SQLite, l’ajoute au noyau via la bibliothèque WireGuard Go, puis répond à l’API que l’installation est terminée
  • Quand l’API renvoie la configuration à la requête GraphQL, flyctl se connecte alors au pair WireGuard déjà installé sur la passerelle

Pourquoi l’ancienne architecture est devenue lente

  • NATS est rapide, mais ne garantit pas la livraison, ce qui le rend difficile à utiliser comme base fiable pour une API
    • Fly.io a réduit son usage interne de NATS et, par exemple, son API interne flyd est passée d’une base NATS à une base HTTP
    • Même avec cette réduction, l’amélioration des passerelles WireGuard restait insuffisante
  • Les pairs WireGuard créés après la fermeture de flyctl restaient sur les passerelles, sans processus de nettoyage des anciens pairs
    • Ils avaient choisi de ne pas supprimer les pairs, car une nouvelle tentative de déploiement ou de débogage avec fly ssh console le lendemain restait possible
    • Mais la plupart des pairs étaient créés par des jobs CI sans stockage persistant, qui ne pouvaient pas se reconnecter avec le même pair à l’exécution suivante, créant donc un nouveau pair à chaque fois
  • Résultat : les passerelles ont accumulé des centaines de milliers de pairs potentiellement jamais réutilisés
    • Avec l’augmentation du nombre d’anciens pairs, les opérations WireGuard du noyau sont devenues très lentes
    • Le rechargement de tous les pairs dans le noyau après le redémarrage d’un serveur passerelle était particulièrement lent
    • Quelques kernel panics sont même survenus

Concevoir une installation des pairs dans le noyau uniquement quand c’est nécessaire

  • Stocker tout l’historique des pairs WireGuard dans une seule base SQLite n’est pas difficile, mais conserver tous les pairs dans le noyau Linux devient le vrai goulot d’étranglement
  • Au lieu de pousser la configuration vers les passerelles, Fly.io a choisi de laisser les passerelles récupérer à la demande les pairs nécessaires via l’API
  • En ajoutant un pair au noyau uniquement quand un client tente de se connecter, les anciens pairs peuvent être supprimés du noyau à tout moment
  • Même si un pair supprimé est requis plus tard, il suffit de le récupérer et de l’installer à nouveau lors de la prochaine connexion, ce qui réduit le besoin pour la passerelle de conserver un état durable
  • En revanche, le noyau Linux de WireGuard ne fournit pas d’API permettant de s’abonner à un événement de « tentative de connexion entrante »

Mise en œuvre des pairs WireGuard JIT

  • L’interface de configuration de WireGuard dans le noyau Linux repose sur Netlink, et la bibliothèque de contrôle WireGuard Go utilise wgctrl-go
  • Fly.io exploite le fait qu’une requête de connexion WireGuard est un paquet identifiable pour créer directement des événements avec un filtre BPF et un packet socket
  • Sur le chemin WireGuard via WebSockets, il est encore plus facile d’obtenir les paquets WireGuard bruts
    • Ce chemin échange avec l’interface de la passerelle des paquets UDP encapsulés dans une connexion WebSockets non authentifiée
    • Comme Fly.io possède le code de ce démon, il peut accrocher un hook à la fonction de réception des paquets
  • WireGuard ne distingue pas « client » et « serveur » ; c’est un protocole point à point où les pairs se connectent quand ils ont du trafic à envoyer
    • Celui qui initie la connexion est l’initiator, l’autre est le responder
    • Chez Fly.io, c’est généralement flyctl qui joue l’initiator, et la passerelle le responder
  • Le premier paquet UDP est, selon l’article WireGuard, un handshake initiation, et le type de paquet est enregistré sur 1 octet en clair
    • Fly.io capture les connexions entrantes avec le filtre BPF udp and dst port 51820 and udp[8] = 1

Identifier un pair dans le handshake Noise

  • WireGuard est basé sur le Noise Protocol Framework, et Noise masque les identifiants pendant le handshake via identity hiding
  • Il n’est donc pas possible de lire directement une valeur comme un nom d’utilisateur dans le paquet pour retrouver la configuration correspondante
  • Pour identifier les requêtes entrantes, Fly.io exécute une partie du chiffrement Noise afin de déchiffrer l’identité
    • Ce code est délicat, mais il tient en environ 200 lignes
    • L’interface Netlink du noyau peut fournir à un processus privilégié la clé privée de l’interface, ce qui permet d’obtenir les secrets nécessaires
    • Le code associé est publié dans ce gist
  • Ce processus permet d’obtenir un flux d’événements des clés publiques des utilisateurs qui tentent une connexion WireGuard vers la passerelle

Optimisations d’installation, de cache et de retry

  • La passerelle maintient dans SQLite un cache avec limitation de débit et, lorsqu’elle découvre un nouveau pair, récupère et installe les informations correspondantes via une requête HTTP à une API interne
  • Cette logique s’intègre bien dans le petit démon qui gérait déjà WireGuard sur la passerelle
  • Les anciens pairs peuvent désormais être supprimés activement par une tâche cron
  • Une requête API pour un nouveau pair peut ne pas être assez rapide pour répondre immédiatement au premier message handshake initiation
    • WireGuard réessaie rapidement, donc cela ne pose pas de problème de fonctionnement
  • Fly.io s’appuie sur une fonctionnalité Netlink de Linux WireGuard signalée par Jason Donenfeld pour établir les connexions plus vite
    • Le message d’initiation entrant permet d’obtenir le 4-tuple d’adresses, y compris le port source éphémère de flyctl
    • La passerelle installe alors le pair comme si elle était l’initiator et flyctl le responder
    • Le noyau Linux démarre la connexion WireGuard vers flyctl, et le protocole ne dépend pas fortement d’un rôle serveur ou client
    • Les nouvelles connexions s’établissent ainsi à une vitesse proche du temps d’installation

Résultats en production

  • Cette approche fonctionne en production depuis plusieurs semaines
  • Le nombre d’anciens pairs WireGuard, qui allait de quelques milliers à plusieurs centaines de milliers par passerelle, est retombé quasiment à 0
  • La quantité d’état que les passerelles doivent conserver a diminué
  • La configuration des pairs est devenue plus rapide
  • Au redémarrage, il y a moins besoin de recharger dans le noyau des pairs inutilisés

1 commentaires

 
GN⁺ 2024-03-14
Avis de Hacker News
  • Je ne comprends pas bien l’affirmation selon laquelle WireGuard dans le noyau Linux n’aurait pas de fonction permettant d’installer des pairs à la demande. Il semble possible d’ajouter des pairs à l’exécution : https://serverfault.com/questions/1101002/wireguard-client-a...
    Si j’ai bien compris, cette étape arrive déjà trop tard, et ils semblent vouloir authentifier avant d’ajouter le pair, afin d’éviter que d’anciennes entrées ne restent dans l’interface.
    La structure semble donc être la suivante : placer un filtre eBPF devant l’interface, vérifier directement s’il s’agit d’un correspondant autorisé sur la base du routage par clé cryptographique, puis, si c’est validé, ajouter le pair à l’interface et le supprimer après expiration du délai.

    • Au fond, ce qu’ils veulent, c’est une API Netlink grâce à laquelle WireGuard dans le noyau ferait remonter la liste des clés publiques vues dans les messages d’initiation. À moyen terme, Jason semble aussi vouloir proposer ce genre de fonctionnalité ; avec ce flux, il ne serait plus nécessaire d’installer à l’avance le moindre pair WireGuard.
      Les pairs pourraient tous se trouver dans quelque chose comme SQLite, puis être installés à la demande lorsqu’un client tente de se connecter.
      Du point de vue d’un fournisseur de VPN, l’API actuelle est un peu rudimentaire. Il y a certes le fait qu’à un instant donné seule une partie des pairs est réellement utilisée, mais quand leur nombre passe de centaines de milliers à des dizaines de millions, les stocker tous dans une seule instance du noyau devient tout simplement impossible.
      Si les pairs doivent être installés à l’avance, on finit par les lier à une machine serveur précise.
      Comme le dit l’article, il est déjà possible aujourd’hui de créer quelque chose qui ressemble à l’interface nécessaire avec une simple capture de paquets, et grâce à la bonne conception de l’API par Jason, il est très facile d’inverser le sens d’initiation entre serveur et client. Même si le noyau a jeté le premier message d’initiation, l’utilisateur aura l’impression que la connexion s’établit sans accroc.
      Jann Horn disait qu’on aurait même pu aller plus loin : conserver le paquet d’initiation capturé, puis le réinjecter dans le noyau après l’installation du pair. C’est aussi une assez bonne idée.
      Je ne pense pas que cet article change une vie ; c’est plutôt une série de petites astuces propres que les gens gagneraient à connaître.
      L’étape suivante consisterait à s’appuyer là-dessus pour créer des floating peers, afin de découpler complètement les pairs d’une région. Ainsi, l’utilisateur n’aurait pas à se soucier de la région dans laquelle son pair est configuré, et cela semble offrir de vrais avantages produit, au-delà du simple amusement de geek.
    • On dirait qu’ils ont fait cela pour éviter l’alternative consistant à exécuter WireGuard hors du noyau. Le noyau Linux ne dispose pas d’une fonctionnalité permettant de router d’abord par adresse cryptographique, mais ils ne voulaient pas quitter le noyau ; ils l’ont donc ajoutée par un hack, en quelque sorte.
      L’expression JIT WireGuard me paraît un peu étrange. Ma première réaction a été : « pourquoi ? Le goulot d’étranglement en performance, c’est le chiffrement, et un JIT par client n’aidera pas là-dessus. »
      À leur place, je serais simplement passé en espace utilisateur. On peut obtenir de bonnes performances avec des choses comme tokio-uring ou glommio.
      S’ils continuent à pousser dans le noyau, ils vont constamment buter sur des limites, car Linux n’a pas été conçu pour gérer des millions de tunnels actifs. Même des millions de connexions TCP dans un seul noyau, c’est parfois délicat.
      Chaque limite nécessite un hack, et chaque hack apporte des paramètres système à appliquer et à gérer. La chaîne d’outils de provisionnement de serveurs physiques Linux est très en retard par rapport aux outils de développement applicatif, de services et de gestion de configuration.
      À moins que je sois idiot et que j’aie mal compris quelque chose ?
  • Si vous voulez créer des pairs WireGuard en espace utilisateur dans une appli Go, vous pouvez jeter un œil à ce projet expérimental récent : https://github.com/dpeckett/noisysockets
    Il s’appuie sur l’excellent travail de wireguard-go, mais vise à être plus simple à utiliser comme bibliothèque et plus idiomatique en Go.
    Ce serait intéressant d’en faire un service mesh. Il serait sans doute difficile de prendre en charge plusieurs langages, mais on pourrait peut-être implémenter une API de sockets.
    Cela dit, je n’ai pas encore vu d’accélération matérielle pour le chiffrement WireGuard, donc côté performances il pourrait être difficile de rivaliser avec mTLS.
    Au passage, je cherche actuellement des missions freelance ; si vous avez besoin d’un freelance Golang dans le réseau haute performance et sécurisé, vous pouvez me contacter.

    • Je rêve de prendre un projet WireGuard en espace utilisateur, d’échanger les clés WireGuard via PAKE sur un relais en frontal, puis d’établir ensuite un tunnel direct par hole punching.
      Ce serait une sorte de Magic Wormhole pour tunnels arbitraires, avec l’espoir d’améliorer fortement le problème des transferts de fichiers qui s’effondrent à 20–30 MB/s sur des réseaux longs et à forte bande passante.
    • Je me demande si Noisy Transport ressemble, dans une certaine mesure, à Nebula de Slack [0], ou si je mélange tout.
      0 - https://github.com/slackhq/nebula
  • Je suis globalement d’accord pour dire que, pour des messages point à point uniques, des requêtes HTTP directes peuvent être plus fiables que de passer par une file de messages, mais je suis un peu surpris que NATS ait perdu autant de messages au point d’avoir un impact important sur le service.
    Quand des messages sont perdus, NATS ne les retransmet-il pas jusqu’au succès ? Je me demande si quelqu’un sait pourquoi ils ont subi une instabilité perceptible à ce point.

    • J’aimerais beaucoup avoir plus de détails. Les mainteneurs de NATS aussi, j’imagine.
      L’architecture de NATS est intuitive et séduisante, donc je serais curieux de savoir où ça a déraillé. JetStream offre beaucoup de paramètres ajustables.
      Par exemple, on peut configurer un stream en mémoire avec une fenêtre de déduplication temporelle, un mode push/pull, ainsi que les politiques de retransmission et d’accusé de réception.
      Cela dit, ce n’est peut-être pas adapté à des connexions ponctuelles à message unique. Dans tous les cas, des détails plus précis seraient très utiles.
    • Je ne cherche pas à dénigrer NATS. Il est très possible que nous l’utilisions mal.
      Mais au final, nous n’en avions pas besoin. Plutôt que d’apporter de l’expressivité, la couche de messages rendait surtout les tests et le monitoring plus difficiles.
    • Si vous utilisez core NATS, et non JetStream, je crois qu’il n’y a tout simplement pas d’option de retransmission.
  • Le passage disant « on installe le pair comme si nous étions l’initiator, et on laisse flyctl comme responder. Le noyau Linux relance la connexion WireGuard vers flyctl » ajoute-t-il en pratique une demi-latence aller-retour au handshake ?
    Par exemple, je me demande si le flux ressemble à : 1) flyctl envoie une Initiation, 2) le pair est ajouté via netlink et une nouvelle Initiation est envoyée, 3) flyctl envoie une Response

    • D’après ma lecture, les deux pairs « pensent » tous les deux avoir initié, mais en réalité ça ne semble pas important
      Autrement dit, je pense qu’il n’y a pas d’étape 3, ou pas besoin de l’attendre, et que ce serait clairement le cas si l’on empêchait la nouvelle initiation de l’étape 2
    • C’est globalement ça. Si l’on imagine que « Bob » a pour règle de ne pouvoir appeler que les numéros présents dans son carnet d’adresses, on peut voir les choses ainsi
      1. Alice appelle Bob
        1.a) Bob ne répond pas, mais ajoute le numéro de l’appelant à son carnet d’adresses
      2. Bob rappelle ce numéro, c’est-à-dire Alice
      3. Alice décroche et ils discutent joyeusement
  • Je ne comprends pas ce que signifie « à chaque exécution de flyctl, notre adorable et énorme CLI crée une pile TCP/IP à partir de rien, dispose de sa propre adresse IPv6 et communique directement avec les Fly Machines qui tournent sur notre réseau »

    • En gros, cela veut dire qu’ils utilisent WireGuard en espace utilisateur, comme l’implémentation en Go. C’est l’approche opposée à WireGuard dans le noyau
      Ils disent « crée une pile TCP/IP à partir de rien » parce que, d’habitude, le système d’exploitation fournit la pile TCP/IP comme une partie du noyau
      Avec wireguard-go, la pile TCP/IP s’exécute en espace utilisateur, donc elle peut être créée dans un processus utilisateur ordinaire comme l’interface en ligne de commande flyctl
      Pour quelqu’un qui administre des systèmes depuis longtemps, ça peut paraître assez magique. Les piles TCP/IP en espace utilisateur, intégrées au processus et réellement utilisables, sont relativement récentes et originales
    • J’ai écrit un article complet à ce sujet : https://fly.io/blog/our-user-mode-wireguard-year/
    • Ça veut dire qu’ils utilisent WireGuard
    • J’ai du mal à imaginer ce qu’est une énorme CLI adorable
  • Je me demande ce qui empêche de réinjecter dans la pile réseau le tout premier paquet de handshake. Comme ça, il n’y aurait apparemment pas de perte de paquet
    Je me demande aussi à quoi sert de vérifier udp[8] = 1 dans le filtre eBPF

    • Rien ne l’empêche. C’est une bonne idée
      Comme indiqué dans le commentaire voisin, le filtre BPF ne capture que les paquets d’initiation, et c’est bien le comportement recherché. C’est la version WireGuard du fait de sniffer un SYN pour observer le début d’une connexion TCP
    • udp[8] = 1 ne filtre que les paquets de handshake. Sans ça, les paquets de données seraient aussi envoyés au démon en espace utilisateur
      Je ne suis pas sûr qu’on puisse rejouer le premier handshake, mais comme WireGuard ignore les clients inconnus, c’est peut-être possible
    • Ça ressemble à un helper NFQUEUE qui libère le paquet après avoir ajouté la clé
  • Je trouve intéressant que, par défaut, WireGuard soit tunnelé au-dessus de WebSocket. Ce n’est pas idéal pour les performances, mais pour les tâches à tendance DevOps où flyctl est utilisé, ça semble acceptable
    Je me posais aussi ce genre de question en réfléchissant à l’avenir de QUIC/HTTP3. Il n’est pas impossible que des opérateurs réseau bloquent tout simplement le port UDP 443 plutôt que de le gérer correctement

    • On peut évidemment aussi utiliser WireGuard en natif, et flyctl propose une option de configuration pour ça
      Quand UDP ne fonctionne pas, il ne fonctionne tout simplement pas et c’est difficile à déboguer ; nous avons donc choisi par défaut la solution dont nous savons qu’elle fonctionne à coup sûr
      Ça me laisse un goût amer d’avoir perdu le débat sur le choix de la valeur par défaut
  • Ma startup a utilisé Fly pendant presque un an. La fonctionnalité centrale qui transforme du code en code déployé en moins d’une minute est vraiment belle
    Monter et démonter de nouveaux nœuds pour du backfill ne prend aussi que quelques secondes
    Mais l’entreprise elle-même m’a paru un peu immature. Une fois, notre serveur API est resté inaccessible sur Fly pendant 48 heures, et je n’étais pas certain que ce soit une erreur de configuration de ma part ou encore une autre panne « silencieuse »
    Ils ont un produit « db », mais présenté comme « pas du Postgres managé », et nous y avons aussi connu des interruptions régulières
    J’ai trouvé étrange d’ajouter Postgres comme nom de premier niveau dans la CLI tout en limitant l’étendue des fonctionnalités prises en charge
    L’accès à l’API du service principal tombait aussi souvent, ce qui nous obligeait à attendre pour déployer des modifications sur de nouveaux services
    L’expérience de déploiement me manque, mais honnêtement je suis aujourd’hui plus satisfait de Cloud Run de GCP. Il y a beaucoup moins de « surprises » et la documentation est bien plus aboutie

    • L’expérience de déploiement est excellente, mais pour moi les killer features de Fly.io sont le réseau Anycast et des fonctionnalités comme FLY_REPLAY ou LiteFS. Elles rendent le clustering très facile
      Je trouve étonnant que les fournisseurs de VPS fassent si peu pour aider à réduire la latence des services backend pour les utilisateurs. Aucun ne prend en charge l’Anycast, et les options GeoDNS sont très rares
      Cela dit, GeoDNS ajoute une complexité supplémentaire
      J’aimerais que les coûts de transfert de données de Fly.io soient plus bas. Pour l’instant, pour un service ressemblant à ngrok sur lequel je travaille, je me retrouve à réimplémenter maladroitement une bonne partie des fonctionnalités de Fly.io
      [0]: https://lastlogin.io
      [1]: Le code spécifique à Fly nécessaire pour exécuter LastLogin de manière distribuée dans le monde entier tient à peu près à ça : https://github.com/lastlogin-io/obligator/blob/37f75cc861f1b...
    • Fly a l’air bien, mais je n’ai pas eu l’occasion de l’essayer moi-même. En revanche, Cloud Run de GCP fait partie de mes trois outils d’infrastructure et de déploiement préférés, donc la barre est placée assez haut
    • J’ai eu une expérience presque identique. Nous avons utilisé Fly pendant un an, puis nous sommes passés à GCP il y a un ou deux mois ; dans notre cas, pour certaines raisons, nous avons choisi GKE
      Quand ça marchait bien, c’était vraiment fluide, mais ce n’était pas assez souvent le cas
  • Je voudrais profiter de l’occasion pour présenter Netmaker[0]
    Je n’y suis pas affilié ; je suis simplement quelqu’un qui l’utilise avec satisfaction parce que j’ai besoin d’accéder à des VPC AWS privés répartis sur plusieurs comptes. J’aimerais qu’il soit plus largement adopté
    [0] https://www.netmaker.io/

    • Netmaker, c’est un truc comme Tailscale ? Rien qu’en regardant le site, je ne vois pas bien quel est le facteur différenciant
    • Il semble que Netmaker, ou des outils similaires, gère les clés à votre place, ce qui devrait rendre l’administration beaucoup plus simple
      Dans mon précédent emploi, on configurait et gérait wg avec Ansible sur quelques machines Windows et Linux ; ça marchait correctement, mais à la fin c’était devenu un peu brouillon
    • Est-ce qu’on ne pourrait pas faire ça de façon native AWS avec private link ou du VPC peering ? Je ne connais pas bien ce domaine, donc je ne comprends pas l’intérêt de Netmaker
    • C’est une plateforme VPN classique ? Je me demande si c’est comparable à quelque chose comme Tailscale
      Le site est beaucoup trop vague
  • « Une passerelle avec des centaines de milliers de pairs, dont certains ne seront plus jamais utilisés » : c’est exactement ce qui m’est venu à l’esprit en lisant les premiers paragraphes
    J’aime aussi l’idée suivante : « Il n’existe pas d’appel API permettant de s’abonner aux événements de tentative de connexion entrante. Pas grave. Nous allons créer nous-mêmes ces événements. Une requête de connexion WireGuard est un paquet, et elle est facile à identifier ; on peut donc l’attraper efficacement avec un filtre BPF et un socket paquet »
    Quand on reçoit un message d’initiation entrant, on obtient l’adresse 4-tuple de la connexion voulue, y compris le port source temporaire utilisé par flyctl, puis on installe le pair comme si nous étions l’initiateur et flyctl le répondeur ; je me demande si cela fonctionne aussi derrière un NAT

    • Oui, ça fonctionne. Parce qu’un NAT UDP ne connaît que le 4-tuple. Par exemple, quelque chose comme {wggwd.fly.io, 12345, clientIP, 23456}
      Qu’il s’agisse d’un nouveau paquet UDP « initiateur » ou d’une réponse à un message d’initiation sortant, pour le NAT UDP sur le chemin, cela paraît exactement identique
      La décision ne repose que sur le 4-tuple, et ce 4-tuple est le même
    • Si le paquet revient vers la même IP/le même port et provient de la même IP/du même port, ça fonctionne en traversant le NAT