Το VpnService του Android δεν έχει διαδρομή εξαίρεσης, και καμία ποσότητα ανάγνωσης του API δεν θα παράγει μία. Το Builder.addRoute() προσθέτει πάντα μόνο προορισμούς στη σήραγγα, οπότε το να κρατήσετε ένα τμήμα LAN τοπικό σημαίνει ότι αυτό το τμήμα δεν μπαίνει ποτέ στη λίστα διαδρομών. Αυτό που προσθέτετε αντ' αυτού είναι το συμπλήρωμα του τμήματος, εκφρασμένο ως συνηθισμένα προθέματα. Η δουλειά είναι αριθμητική, και αποτυγχάνει σε μια χούφτα προβλέψιμα σημεία.

Δουλέψτε τους ελέγχους παρακάτω με τη σειρά. Ο καθένας προϋποθέτει ότι ο προηγούμενος κράτησε, οπότε σταματήστε στην πρώτη αποτυχία αντί να μπαλώνετε συμπτώματα πιο κάτω.

Ξεκινήστε από αυτό που διεκδικεί η σήραγγα

Η μετάδοση του 0.0.0.0/0 στο addRoute() λέει στο Android ότι κάθε προορισμός IPv4 ανήκει στη σήραγγα. Γι' αυτό τόσοι πελάτες φαίνονται ανίκανοι για διαχωρισμένη σήραγγα: διεκδικούν ολόκληρο τον χώρο διευθύνσεων και μετά προσπαθούν να αφαιρέσουν από αυτόν. Τίποτα στον κατασκευαστή δεν αφαιρεί. Μια πιο ειδική διαδρομή για το δίκτυο που θέλατε να κρατήσετε τοπικό κάνει το αντίθετο από το αναμενόμενο, γιατί το μακρύτερο ταιριαστό πρόθεμα κερδίζει και αυτό το δίκτυο προσγειώνεται μέσα στη σήραγγα.

Η λίστα διαδρομών είναι όλη η πολιτική. Δεν υπάρχει βέτο ανά προορισμό πίσω της, και η αναφορά Builder δεν απαριθμεί καμία μέθοδο που θα το παρείχε.

Παράγετε το συμπλήρωμα από αριθμητική προθεμάτων

Το συμπλήρωμα ενός εξαιρεθέντος προθέματος είναι το σύνολο των αδελφών του στη διαδρομή προς τη ρίζα του δέντρου διευθύνσεων. Για κάθε μήκος από 1 έως το εξαιρεθέν μήκος, πάρτε το μπλοκ αυτού του μήκους που περιέχει το πρόθεμά σας, αναποδογυρίστε το bit ακριβώς κάτω από το πρόθεμα και εκπέμψτε το αποτέλεσμα. Ακούγεται αφηρημένο μέχρι να το τρέξετε.

Η εξαίρεση του 10.0.0.0/8 δίνει οκτώ διαδρομές: 128.0.0.0/1, 64.0.0.0/2, 32.0.0.0/3, 16.0.0.0/4, 0.0.0.0/5, 12.0.0.0/6, 8.0.0.0/7, 11.0.0.0/8. Καμία τους δεν περιέχει το 10.0.0.0/8, και μαζί καλύπτουν όλα τα υπόλοιπα.

Οκτώ γραμμές παράγουν αυτή τη λίστα για οποιοδήποτε πρόθεμα:

import ipaddress
def complement(addr, length):
    base = int(ipaddress.IPv4Address(addr))
    out = []
    for k in range(1, length + 1):
        block = base & (0xFFFFFFFF << (32 - k))
        sibling = block ^ (1 << (32 - k))
        out.append(f"{ipaddress.IPv4Address(sibling)}/{k}")
    return out
print(complement("10.0.0.0", 8))

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

Ελέγξτε την οικογένεια διευθύνσεων και το εύρος της σήραγγας

Οι διαδρομές IPv4 δεν κάνουν τίποτα για το IPv6. Αν η σήραγγα διεκδικεί διεύθυνση IPv6, ένα τοπικό πρόθεμα IPv6 χρειάζεται το δικό του συμπλήρωμα, παραγόμενο με την ίδια αριθμητική πάνω από 128 bit. Μια συνηθισμένη αιτία να δουλεύει η εξαίρεση στα χαρτιά ενώ η τοπική συσκευή μένει απρόσιτη είναι αυτή: ο κεντρικός υπολογιστής προσεγγίστηκε μέσω IPv6, και μόνο το IPv4 είχε αποκοπεί. Ελέγξτε και τις δύο οικογένειες πριν συμπεράνετε ότι τα μαθηματικά είναι λάθος.

Το εύρος είναι το άλλο μισό του ελέγχου. Το Android παρακολουθεί τη δρομολόγηση ανά δίκτυο και ανά εφαρμογή, οπότε το ίδιο πρόθεμα μπορεί να περνά από τη σήραγγα για μια εφαρμογή και απευθείας για άλλη, ανάλογα με το πώς δηλώθηκε το VPN.

Βάλτε τους κανόνες DNS και ανά εφαρμογή εκεί που ανήκουν

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

Το addDisallowedApplication() κρατά μια εφαρμογή έξω από τη σήραγγα· δεν κρατά έναν προορισμό έξω για τις εφαρμογές που μένουν μέσα. Το protect() αφήνει ένα socket που ανοίγετε να παρακάμψει τη σήραγγα, και έτσι μια σήραγγα αποφεύγει να συλλάβει τη δική της μεταφορά. Κανένα από τα δύο δεν αντικαθιστά το συμπλήρωμα.

Επαληθεύστε με αναζήτηση, όχι με τη δική σας λίστα

Όταν η σήραγγα σηκωθεί, ρωτήστε τη συσκευή πού πηγαίνει πραγματικά μια διεύθυνση αντί να ξαναδιαβάσετε τη λίστα που φτιάξατε. Σε μια έκδοση που φέρνει το iproute2, το adb shell ip route get 10.1.2.3 ονομάζει τη διεπαφή που επέλεξε ο πυρήνας. Τρέξτε την ίδια εντολή για μια δημόσια διεύθυνση και επιβεβαιώστε ότι ονομάζει τη σήραγγα.

Δύο ακόμη έλεγχοι κλείνουν τον κύκλο. Επιλύστε πρώτα ένα τοπικό όνομα, μετά τρέξτε το adb shell ip route get στη διεύθυνση που επιλύθηκε και απαιτήστε διεπαφή που δεν είναι η σήραγγα, γιατί ένα επιτυχές adb shell ping -c 1 host.lan αποδεικνύει προσβασιμότητα και όχι τη διαδρομή. Επαναλάβετε το τεστ οικογένειας διευθύνσεων πάνω από IPv6 αν η σήραγγα διεκδικεί μία. Δοκιμάστε στο επίπεδο API που διανέμετε, γιατί ο τρόπος που τα δίκτυα VPN εκτίθενται στον πίνακα δρομολόγησης έχει αλλάξει μεταξύ εκδόσεων. Μια λίστα διαδρομών που φαίνεται σωστή στα χαρτιά και μια αναζήτηση που διαλέγει λάθος διεπαφή είναι διαφορετικά σφάλματα, και μόνο το δεύτερο φτάνει στους χρήστες.

Αν η λίστα εξαιρέσεων συνεχίσει να μεγαλώνει, σταματήστε να παράγετε διαδρομές και αναριθμήστε το απομακρυσμένο δίκτυο ή στενέψτε τη σήραγγα αντ' αυτού. Η γεννήτρια είναι μια παράκαμψη για το API που λείπει, όχι εργαλείο σχεδιασμού.

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