Πέρασα το μεγαλύτερο μέρος ενός πρωινού φέτος την άνοιξη κυνηγώντας ένα κοντέινερ που δεν μπορούσε να επιλύσει ούτε ένα εσωτερικό όνομα, ενώ το πρόγραμμα περιήγησης δίπλα του άνοιγε το wiki χωρίς παράπονο. Το τούνελ ήταν ενεργό, το πρόγραμμα περιήγησης το αποδείκνυε, και το κοντέινερ καθόταν σε διαφορετικό χώρο ονομάτων δικτύου με διαφορετικό πίνακα δρομολόγησης και διαφορετικό resolver. Αυτό που ακολουθεί είναι η σειρά που ελέγχω τώρα: πρώτα οι πίνακες δρομολόγησης, δεύτερη η επίλυση, τρίτη η προώθηση και τελευταία η πλατφόρμα του host. Σταματήστε στο πρώτο βήμα που αποτυγχάνει ήδη, γιατί η προσθήκη κανόνων πιο κάτω στη στοίβα κρύβει το όριο αντί να το μετακινεί.

Συγκρίνετε τους τρεις πίνακες δρομολόγησης πριν αλλάξετε κανόνα

Ένα κοντέινερ στην προεπιλεγμένη γέφυρα του Docker έχει μία προεπιλεγμένη διαδρομή προς την πύλη docker0 και καμία ιδέα ότι υπάρχει το tun0. Ο host έχει tun0, τις διαδρομές που έσπρωξε το VPN και ίσως έναν κανόνα πολιτικής που στέλνει δοκιμαστική κίνηση από διαφορετική διεπαφή από αυτή που περιμένετε. Το WSL προσθέτει έναν τρίτο πίνακα πίσω από τον δικό του εικονικό προσαρμογέα. Εκτυπώστε και τους τρεις πριν επεξεργαστείτε οποιονδήποτε από αυτούς.

ContextCommandWhat a wrong answer looks like
Hostip route get 10.8.0.20Leaves a physical interface instead of tun0
Containerip routeOnly 172.17.0.0/16 via 172.17.0.1
Container DNScat /etc/resolv.confA public resolver, or an upstream the tunnel never pushed
WSLip addr; ip routeOnly the WSL subnet, no Windows VPN adapter

Αν η μόνη διαδρομή του κοντέινερ είναι η γέφυρα και το tun0 δεν εμφανίζεται ποτέ στο χώρο ονομάτων του, τίποτα παρακάτω δεν θα βοηθήσει: καμία αλλαγή διακομιστή ονομάτων δεν δημιουργεί διαδρομή και καμία διαδρομή δεν δημιουργεί ταυτότητα τούνελ που θα δεχτεί ο peer. Η σειρά έχει σημασία γιατί μια αποτυχία DNS και μια αποτυχία διαδρομής φαίνονται ίδιες μέσα από ένα shell.

Στρέψτε τον resolver στο τούνελ αντί σε δημόσιο

Στον host, resolvectl status απαριθμεί διακομιστές DNS ανά ζεύξη, και readlink -f /etc/resolv.conf σας λέει αν το αρχείο είναι το stub του systemd, αρχείο NetworkManager ή κάτι που ξανάγραψε ο πελάτης ενός προμηθευτή. Μέσα σε κοντέινερ σε δίκτυο ορισμένο από τον χρήστη, /etc/resolv.conf ονομάζει 127.0.0.11, τον ενσωματωμένο resolver του Docker, που προωθεί στο upstream με το οποίο είχε ρυθμιστεί ο daemon. Ένα κοντέινερ στην προεπιλεγμένη γέφυρα αντίθετα πήρε αντίγραφο του αρχείου του host όταν δημιουργήθηκε και το κρατά.

Αυτό το αντίγραφο είναι ο λόγος που ένα τούνελ που σηκώνεται αφού ξεκινήσει το κοντέινερ αφήνει το κοντέινερ να ρωτά το παλιό upstream. Είτε περάστε τον resolver του VPN ρητά με docker run --dns 10.8.0.53, είτε ορίστε "dns": ["10.8.0.53"] στο /etc/docker/daemon.json και επανεκκινήστε τον daemon, είτε δημιουργήστε ξανά το κοντέινερ όταν το τούνελ είναι ενεργό. Η επαναδημιουργία είναι συνήθως φθηνότερη από την αποσφαλμάτωση ενός resolver που ήταν σωστός πριν από μία ώρα.

Η αντικατάσταση με δημόσιο resolver είναι η επισκευή που διατηρεί το σύμπτωμα. Επιλύει το διαδίκτυο και αποτυγχάνει σε κάθε ιδιωτική ζώνη, και μπορεί να παρακάμψει ήσυχα το φιλτράρισμα που υπάρχει για να επιβάλλει το τούνελ. Αν προσπαθήσετε να γράψετε μέσα στο /etc/resolv.conf ενός κοντέινερ που τρέχει, περιμένετε Device or resource busy: το Docker κάνει bind-mount αυτό το αρχείο, οπότε η επεξεργασία χρειάζεται πρώτα αποπροσάρτηση. Το να ορίσετε --dns κατά τη δημιουργία είναι η αλληλουχία που επιβιώνει από επανεκκίνηση.

Προωθήστε και μεταφράστε όταν τα κοντέινερ δανείζονται το τούνελ

Με την προεπιλεγμένη γέφυρα, ο host είναι δρομολογητής μεταξύ docker0 και tun0, και από προεπιλογή δεν κάνει καμία από τις δύο δουλειές. Ενεργοποιήστε την προώθηση και μεταφράστε τη διεύθυνση πηγής ώστε ο peer να βλέπει διεύθυνση τούνελ του host αντί για μία στην οποία δεν έχει διαδρομή επιστροφής (αντικαταστήστε το tun0 με ό,τι ονομάζει ο πελάτης σας τη διεπαφή):

sysctl -w net.ipv4.ip_forward=1
iptables -t nat -A POSTROUTING -o tun0 -j MASQUERADE
iptables -A FORWARD -i docker0 -o tun0 -j ACCEPT
iptables -A FORWARD -i tun0 -o docker0 -m conntrack --ctstate ESTABLISHED,RELATED -j ACCEPT

Οι κανόνες FORWARD έχουν σημασία σε hosts των οποίων η πολιτική προώθησης είναι DROP. Το Docker εισάγει δικές του αλυσίδες, γι' αυτό δοκιμάστε με πακέτο αντί να διαβάζετε iptables -S: από το κοντέινερ, ip route get 10.8.0.20 πρέπει να ονομάζει την πύλη της γέφυρας, και nc -vz 10.8.0.20 443 πρέπει να συνδέεται ή να λήγει στο τούνελ αντί στον resolver.

Χωρίς MASQUERADE ο peer χρειάζεται διαδρομή επιστροφής προς 172.17.0.0/16, την οποία τα περισσότερα VPN απομακρυσμένης πρόσβασης δεν θα σπρώξουν. Εδώ επίσης χαλάει το σχέδιο με πελάτη βασισμένο σε πολιτικές: αν συλλαμβάνει μόνο κίνηση από συγκεκριμένα UID ή cgroups, οι κανόνες προώθησης δεν βλέπουν ποτέ το πακέτο μέσα στο τούνελ, και η διόρθωση ανήκει στην πολιτική δρομολόγησης του πελάτη και όχι στο iptables. Κανόνες που προστίθενται με το χέρι εξαφανίζονται στην επανεκκίνηση, οπότε βάλτε τους σε μονάδα systemd ή πίνακα netfilter-persistent και δοκιμάστε ξανά μετά από επανεκκίνηση, αφού ένα τούνελ που σηκώνεται αργά μπορεί να τους σαρώσει.

Βάλτε το τούνελ μέσα στο κοντέινερ όταν ο στόχος είναι η απομόνωση

Αν ένα κοντέινερ πρέπει να βγει μέσω συγκεκριμένου κόμβου, εκτελέστε τον πελάτη σε αυτό το κοντέινερ. Χρειάζεται --cap-add NET_ADMIN και --device /dev/net/tun, και τότε κατέχει το δικό του tun0, διαδρομές και τείχος προστασίας. Αν άλλα κοντέινερ περνούν από αυτό, χρειάζεται επίσης προώθηση και κανόνα MASQUERADE μέσα στο κοντέινερ, και αυτοί οι peers χρειάζονται ρητή διαδρομή ή proxy που δείχνει σε αυτό· η προεπιλεγμένη γέφυρα του Docker δεν προσθέτει αυτή τη διαδρομή για εσάς.

Δικτύωση host, docker run --network host, είναι η φθηνή εκδοχή: το κοντέινερ ενώνεται με το χώρο ονομάτων του host και βλέπει τις διαδρομές του τούνελ άμεσα. Επίσης ρίχνει την απομόνωση δικτύου, προσκαλεί συγκρούσεις θυρών και στην πράξη είναι επιλογή του Linux. Μια δημοσιευμένη θύρα δεν είναι υποκατάστατο. -p 8080:80 κάνει μια υπηρεσία προσβάσιμη από τον host· δεν κάνει τις εσωτερικές διευθύνσεις 10.x προσβάσιμες από μέσα στο κοντέινερ, και το να συγχέετε τα δύο είναι ο τρόπος που ένα πρόβλημα δρομολόγησης γίνεται εκτεθειμένη θύρα βάσης δεδομένων.

Ξεχωρίστε μια αποτυχία ονόματος από ένα επικαλυπτόμενο υποδίκτυο

Η προεπιλεγμένη γέφυρα του Docker είναι 172.17.0.0/16, και πολλές εταιρικές δεξαμενές VPN βρίσκονται μέσα στο 172.16.0.0/12. Μόλις το τούνελ κατέχει αυτό το εύρος, η κίνηση που προορίζεται για κοντέινερ μπορεί να δρομολογηθεί μέσα στο τούνελ. Τα ονόματα αποτυγχάνουν, το σφάλμα μοιάζει με DNS και το πρόβλημα είναι οι διευθύνσεις. Ελέγξτε τον προορισμό, όχι τον resolver: ip route get 172.17.0.5 στον host, και μετά συγκρίνετε το πρόθεμα της γέφυρας με τη δεξαμενή του VPN και με docker network inspect bridge --format '{{range .IPAM.Config}}{{.Subnet}}{{end}}'.

Η επισκευή είναι να μετακινήσετε ένα από τα εύρη: είτε διαφορετική προεπιλεγμένη δεξαμενή διευθύνσεων για το Docker στο daemon.json είτε διαφορετική δεξαμενή στον συγκεντρωτή. Η προσθήκη άλλου διακομιστή ονομάτων δεν κάνει τίποτα εδώ, ούτε κάνει ssh -b: η σύνδεση του πελάτη σε διεπαφή αλλάζει τη διεύθυνση πηγής, όχι μια διαδρομή προορισμού που δείχνει μέσα στο τούνελ. Γρήγορη κατανομή εργασίας: ένα όνομα που επιλύεται στον host αλλά όχι στο κοντέινερ είναι πρόβλημα resolver, ενώ μια άμεση σύνδεση IP που κολλάει στο κοντέινερ αλλά λειτουργεί στον host είναι πρόβλημα διαδρομής ή αλυσίδας προώθησης.

Το WSL και το Docker Desktop δεν ακολουθούν τους κανόνες του Linux

Στην προεπιλεγμένη λειτουργία NAT, το WSL κάθεται πίσω από τον δικό του εικονικό προσαρμογέα και δεν λαμβάνει τις εσωτερικές διαδρομές ή τα καταλήγματα DNS του VPN των Windows, γι' αυτό το πρόγραμμα περιήγησης λειτουργεί και ping μέσα στη διανομή όχι. Η κατοπτρική δικτύωση, διαθέσιμη σε πρόσφατες εκδόσεις των Windows 11, μοιράζεται αντίθετα τις διεπαφές του host, αλλά πρέπει να ενεργοποιηθεί στο .wslconfig και η διανομή να επανεκκινήσει με wsl --shutdown. Επιβεβαιώστε την ενεργή λειτουργία με wslinfo --networking-mode πριν εφαρμόσετε διόρθωση, γιατί η απάντηση του NAT είναι λάθος για την κατοπτρική λειτουργία και αντίστροφα.

Το Docker Desktop προσθέτει άλλο ένα επίπεδο. Τα κοντέινερ Linux στα Windows κάθονται πίσω από υλοποίηση NAT που ιστορικά ονομάζεται vpnkit, και η διαδρομή τους διαφέρει από ενός εγγενούς host Linux. Τα κοντέινερ Windows συνδέονται μέσω εικονικού διακόπτη Hyper-V, και ένας εταιρικός πελάτης μπορεί να μπλοκάρει την κίνηση από αυτόν τον διακόπτη, οπότε κοντέινερ Linux στο ίδιο μηχάνημα μπορεί να φτάνουν στο δίκτυο ενώ τα κοντέινερ Windows δεν έχουν τίποτα. Ένας εξωτερικός διακόπτης δεσμευμένος στον φυσικό προσαρμογέα είναι η συνήθης λύση, και αλλάζει την ταυτότητα δικτύου του κοντέινερ με τρόπους που η πολιτική μπορεί να μην επιτρέπει.

Πριν γράψετε άλλον κανόνα, διαβείτε τα τέσσερα επίπεδα με σειρά και σταματήστε όπου ένα από αυτά είναι λάθος:

  • Μέσα στο κοντέινερ: φτάνει το ip route στο ιδιωτικό πρόθεμα, και ονομάζει το /etc/resolv.conf ένα upstream που το επιλύει;
  • Στον host: είναι ενεργή η προώθηση, είναι το NAT δεσμευμένο στη διεπαφή του τούνελ, και δέχεται η αλυσίδα προώθησης γέφυρα προς τούνελ;
  • Στον peer: γνωρίζει διαδρομή επιστροφής, ή βασίζεστε στο NAT για να κρύψει το υποδίκτυο του κοντέινερ;
  • Στην πλατφόρμα: είναι το WSL σε λειτουργία NAT ή κατοπτρική, και το κοντέινερ είναι Linux ή Windows;

Εκτελέστε ξανά κάθε δοκιμή σε νέο κοντέινερ ή shell. Μια μακροχρόνια διεργασία με αποθηκευμένη απάντηση ή εδραιωμένη σύνδεση θα συνεχίσει να αποτυγχάνει αφού η διαμόρφωση είναι σωστή, και αυτή η αποτυχία μοιάζει ακριβώς με επισκευή που δεν λειτούργησε.

Σχετικά άρθρα