Το πακέτο του container δεν φτάνει ποτέ στο tun0, και πρόσθετες δυνατότητες στο container δεν θα το αλλάξουν. Ανάμεσα στον guest και τη σήραγγα στέκονται δύο αναζητήσεις διαδρομής: μία μέσα στον χώρο ονομάτων δικτύου του guest, μία στον host αφού το πλαίσιο προσγειωθεί στη γέφυρα, και η δεύτερη συμβουλεύεται άλλο πίνακα με άλλους κανόνες αντιστοίχισης. Η διάταξη αναφοράς μου είναι ένας host Linux που τρέχει OpenVPN client που κρατά 10.8.0.10 στο tun0, μια γέφυρα Docker στο 172.18.0.0/16, και μια ιδιωτική διεύθυνση που μόνο η πλευρά της σήραγγας μπορεί να φτάσει. Ο στόχος μένει στενός: αυτό το υποδίκτυο guest φεύγει μέσα από τη σήραγγα ενώ ο host κρατά την κανονική του ζεύξη.

Το να τρέχεις τον client VPN μέσα στο container είναι άλλος σχεδιασμός, όχι σύντομος δρόμος. Τότε το container κατέχει τη σήραγγα και τα διαπιστευτήριά του, και η δουλειά μετακομίζει σε Dockerfile αντί να μένει στο iptables.

Η προώθηση επιλέγει διαδρομή με βάση τον προορισμό και μετά το NAT ξαναγράφει την πηγή

Με net.ipv4.ip_forward=1 ορισμένο, ένα προωθημένο πακέτο δρομολογείται μόνο με βάση τον προορισμό, στον κύριο πίνακα. Η πηγή δεν παίζει ρόλο σε αυτή την απόφαση. Μόνο αφού επιλεγεί η διαδρομή, ο πυρήνας εκτελεί POSTROUTING, και εκεί βρίσκεται ο κανόνας του ίδιου του Docker: -s 172.18.0.0/16 ! -o br-appnet -j MASQUERADE. Ό,τι φεύγει από τη γέφυρα προς άλλη διεπαφή μεταφράζεται, και η διεύθυνση που παίρνει εξαρτάται από τη διεπαφή στην οποία το έστειλε η διαδρομή.

Αυτός ο κανόνας εξηγεί τα περισσότερα συμπτώματα για τα οποία με ρωτούν. Αν ο κύριος πίνακας δεν έχει διαδρομή για τον ιδιωτικό προορισμό, το πακέτο βγαίνει από τη ζεύξη κουβαλώντας τη φυσική διεύθυνση του host. Ο πάροχός σας απορρίπτει τον προορισμό, ή ο απομακρυσμένος τόπος απορρίπτει μια πηγή RFC 1918 που δεν περίμενε ποτέ, και το container λήγει σε timeout ενώ η σήραγγα μένει σηκωμένη και υγιής. Μια καταγραφή στο tun0 δεν δείχνει τίποτα, και αυτό είναι το σημάδι. Αν η διαδρομή δείχνει όντως στο tun0, το MASQUERADE ξαναγράφει την πηγή στο 10.8.0.10 και το ίδιο container δουλεύει χωρίς να αγγίξετε τίποτα.

Ζητήστε την αναζήτηση που ονομάζει την πηγή του guest. Χωρίς from και iif ο host απαντά για τη δική του υποδοχή και αναφέρει μια διαδρομή που το προωθημένο πακέτο δεν θα πάρει ποτέ:

ip route get 192.168.11.222 from 172.18.0.5 iif br-appnet

Αν αυτή η απάντηση ονομάζει τη ζεύξη, η σήραγγα δεν είναι στην εικόνα και καμία ρύθμιση στην πλευρά της σήραγγας δεν θα βοηθήσει.

Μεταφέρετε τον guest στη διεύθυνση της ίδιας της σήραγγας

Η μικρότερη αλλαγή που δουλεύει είναι μια ρητή επανεγγραφή πηγής στην έξοδο. Εισαγάγετέ την αντί να την προσθέτετε: το Docker κάθεται ήδη στην κεφαλή του POSTROUTING, άρα ένας κανόνας που προστίθεται με -A αξιολογείται πίσω από του Docker και δεν ενεργοποιείται ποτέ.

iptables -t nat -I POSTROUTING 1 -s 172.18.0.0/16 -o tun0 -j SNAT --to-source 10.8.0.10

Δύο συνθήκες κρίνουν αν αυτό θα κρατήσει. Η διεύθυνση πρέπει να είναι μία που ο client κρατά πραγματικά, οπότε καρφώστε την με μια εγγραφή ανά client που περιέχει ifconfig-push 10.8.0.10 255.255.255.0; ένας κανόνας που ονομάζει διεύθυνση που δεν ανατέθηκε ποτέ παράγει σιωπηλές απορρίψεις, όχι σφάλμα. Και η απομακρυσμένη πλευρά πρέπει να δεχτεί αυτή την πηγή και να δρομολογήσει τις απαντήσεις πίσω, γι' αυτό μια λίστα επιτρεπομένων εκεί ονομάζει τη διεύθυνση της σήραγγας και όχι το υποδίκτυο guest. Όταν η διεύθυνση του client είναι δυναμική, -j MASQUERADE με -o tun0 ακολουθεί ό,τι κρατά η διεπαφή, με κόστος μια απομακρυσμένη πολιτική κατά της οποίας δεν μπορείτε να γράψετε σταθερή τιμή.

Το φιλτράρισμα είναι το άλλο μισό. Το Docker θέτει την πολιτική FORWARD σε drop και καθαρίζει τις αλυσίδες που κατέχει σε κάθε επανεκκίνηση του daemon, οπότε βάλτε τοπική πολιτική στο DOCKER-USER, που επιβιώνει:

iptables -I DOCKER-USER -i br-appnet -o tun0 -j ACCEPT
iptables -I DOCKER-USER -i tun0 -o br-appnet -m conntrack --ctstate ESTABLISHED,RELATED -j ACCEPT

Ο κανόνας επιστροφής έχει σημασία: μια απάντηση που φτάνει στο tun0 για σύνδεση που άνοιξε η γέφυρα δεν γίνεται δεκτή μόνο επειδή η κατεύθυνση εξόδου ήταν.

Κρατήστε τον host άμεσο με κανόνα πηγής

Όταν ολόκληρη η γέφυρα πρέπει να χρησιμοποιεί τη σήραγγα αλλά τίποτα άλλο στον host δεν πρέπει, παραλείψτε το NAT και δώστε σε αυτή την πηγή τον δικό της πίνακα. Οι διευθύνσεις guest μένουν τότε ορατές από άκρη σε άκρη.

echo "200 vpnbox" >> /etc/iproute2/rt_tables
ip rule add from 172.18.0.0/16 lookup vpnbox pref 1000
ip route add default dev tun0 table vpnbox

Οι κανόνες εξετάζονται πριν από τον κύριο πίνακα, και from ταιριάζει με την πηγή. Τα εξωτερικά πακέτα της ίδιας της σήραγγας φέρουν τη φυσική πηγή του host, άρα δεν ταιριάζουν ποτέ με from 172.18.0.0/16 και η σήραγγα δεν μπορεί να δρομολογήσει τον εαυτό της πίσω στον εαυτό της. Αυτός ο αυτοαποκλεισμός κάνει τη διάταξη σταθερή.

Η διαδρομή επιστροφής χρειάζεται τώρα βοήθεια. Η απομακρυσμένη πλευρά βλέπει μια αμετάφραστη 172.18.0.x και δεν έχει λόγο να ξέρει πού μένει. Στον server, ένα αρχείο ανά client χρειάζεται iroute 172.18.0.0 255.255.0.0, ώστε ο εσωτερικός πίνακας του server να δίνει αυτό το πρόθεμα σε αυτόν τον client, και ένα route 172.18.0.0 255.255.0.0 που ωθείται ώστε άλλοι clients να ξέρουν ότι υπάρχει.

Δύο έλεγχοι πριν το εμπιστευτείτε. Εκτελέστε ip route show default: με redirect-gateway def1 ο πίνακας κρατά 0.0.0.0/1 και 128.0.0.0/1 μέσω tun0, και ο host δεν είναι καθόλου άμεσος, γιατί αυτά τα μισά-προεπιλεγμένα κερδίζουν χωρίς να αντικαθιστούν το default. Κανόνες που προστέθηκαν με ip rule add επίσης εξαφανίζονται στην επανεκκίνηση, οπότε κρατήστε τους σε ένα up script.

Το μέγεθος πακέτου είναι η δεύτερη αποτυχία που συναντάτε

Μια διεπαφή γέφυρας στα 1500 byte δίνει στη σήραγγα περισσότερα απ' όσα μπορεί να κουβαλήσει μόλις προστεθούν οι εξωτερικές κεφαλίδες UDP και IP. Μικρές ανταλλαγές περνούν, άρα το DNS λύνεται και η χειραψία TLS ολοκληρώνεται, μετά ένα μεγαλύτερο upload κολλάει χωρίς σφάλμα πουθενά. Ο αποστολέας επαναλαμβάνει το ίδιο μέγεθος τμήματος γιατί το μήνυμα ICMP για ανάγκη κατάτμησης, που θα του έλεγε να μικρύνει, δεν φτάνει ποτέ.

Διορθώστε το στη διεπαφή, στο MSS ή και στα δύο. Το σφίξιμο δεν χρειάζεται συνεργασία από τον guest:

ip link set dev tun0 mtu 1400
iptables -t mangle -A FORWARD -o tun0 -p tcp --tcp-flags SYN,RST SYN -j TCPMSS --clamp-mss-to-pmtu

Το σφίξιμο ξαναγράφει μόνο TCP. Το UDP, μαζί με το QUIC, χρειάζεται αντίθετα χαμηλότερη διεπαφή guest, με --opt com.docker.network.driver.mtu=1400 στη δημιουργία δικτύου ή το αντίστοιχο κλειδί compose.

DNS και οι έλεγχοι που αξίζει να επαναλαμβάνετε

Η επίλυση ονομάτων ακολουθεί την ίδια διαδρομή. Ο ενσωματωμένος resolver του Docker στο 127.0.0.11 προωθεί στους ρυθμισμένους servers του host, άρα το container κληρονομεί ό,τι χρησιμοποιεί ο host. Όταν η ιδιωτική ζώνη απαντάται μόνο από resolver προσβάσιμο μέσω της σήραγγας, δείξτε στον guest αυτή τη διεύθυνση, και δοκιμάστε με getent hosts μέσα στο container και όχι στον host.

  • Εκτελέστε το αποτυχημένο αίτημα μέσα στον guest· μια δοκιμή στην πλευρά του host χρησιμοποιεί άλλη διεύθυνση πηγής και κρύβει το πρόβλημα.
  • Διαβάστε τους μετρητές στους κανόνες NAT και φιλτραρίσματος που προσθέσατε. Ένας κανόνας με μηδέν αντιστοιχίσεις είναι λάθος δρομολόγησης, όχι πολιτικής.
  • Επιβεβαιώστε ότι η διεύθυνση της σήραγγας στον κανόνα SNAT σας είναι μία που ο client κρατά πραγματικά.
  • Χαμηλώστε το MTU του guest ή σφίξτε το MSS πριν κατηγορήσετε τον server για κόλλημα.

Δουλέψτε τη λίστα και σταματήστε στην πρώτη αποτυχία. Αυτό το επίπεδο είναι αυτό που πρέπει να διορθωθεί· ό,τι προστίθεται πιο κάτω κρύβει μόνο το όριο που σπάει.

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