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

Οι διαδρομές καθορίζουν ποια πακέτα φτάνουν

Το Builder.addRoute(prefix, prefixLength) εγκαθιστά ένα πρόθεμα στον πίνακα δρομολόγησης του δικτύου VPN. Το σύστημα αντιμετωπίζει αυτό το δίκτυο ως προεπιλογή για τις εφαρμογές στο εύρος, άρα οι εγγραφές του εξετάζονται πριν από την προεπιλεγμένη διαδρομή του φυσικού δικτύου και ένα ταιριαστό πρόθεμα αρκεί για να εκτραπεί μια ροή. Έτσι μια διαδρομή για το 0.0.0.0/0 παίρνει το IPv4 ολόκληρο, μαζί με τον εκτυπωτή, τον στόχο cast και τη σελίδα διαχείρισης του δρομολογητή.

Οι πελάτες που θέλουν αυτή την κάλυψη χωρίς να διαγράψουν τη φυσική προεπιλεγμένη εγγραφή χρησιμοποιούν αντ' αυτού το ζεύγος 0.0.0.0/1 και το 128.0.0.0/1. Δύο πιο ειδικές διαδρομές κερδίζουν μια προεπιλεγμένη διαδρομή, μαζί καλύπτουν όλο τον χώρο διευθύνσεων, και η προεπιλεγμένη εγγραφή του ίδιου του τηλεφώνου μένει στον πίνακα για τις υποδοχές που είναι ακόμη δεσμευμένες στο φυσικό δίκτυο.

Οι διευθύνσεις και οι εγγραφές DNS προέρχονται από τον ίδιο κατασκευαστή και απαντούν σε άλλο ερώτημα. Το addAddress δίνει στη διεπαφή μια διεύθυνση με μήκος προθέματος, και το addDnsServer εγκαθιστά έναν επιλυτή που οι εφαρμογές στο εύρος θα ρωτούν μέσω της διεπαφής. Και οι δύο οικογένειες μπορούν να βρίσκονται σε μία διεπαφή, άρα ένα σύνολο διαδρομών μόνο IPv4 δίπλα σε διεύθυνση IPv6 είναι ρύθμιση που διαβάζεται σωστά και παρόλα αυτά διαρρέει. Αν δρομολογήσετε μια οικογένεια στην οποία η διεπαφή δεν έχει διεύθυνση, ο πυρήνας δεν έχει διεύθυνση πηγής για αυτά τα πακέτα και δεν εμφανίζονται ποτέ στον περιγραφέα.

Τι επιστρέφει το establish()

Το establish() επιστρέφει έναν ParcelFileDescriptor, ή null όταν η εφαρμογή δεν είναι εξουσιοδοτημένη. Το null είναι κατάσταση συναίνεσης και όχι σφάλμα δικτύου: το VpnService.prepare() πρέπει να έχει ήδη επιστρέψει null, ή ο χρήστης πρέπει να έχει εγκρίνει τον διάλογο του συστήματος πριν την κλήση. Η συναίνεση μπορεί να ανακληθεί όσο εκτελείται η υπηρεσία, και η διεπαφή κλείνει κάτω από τα πόδια σας χωρίς ανάκληση στον βρόχο ανάγνωσης.

Ο περιγραφέας δεν μπλοκάρει εκτός αν πείτε το αντίθετο, άρα μια ανάγνωση επιστρέφει αμέσως χωρίς δεδομένα αντί να παρκάρει το νήμα. Ένας βρόχος γραμμένος για μπλοκάροντας υποδοχή καίει έναν πυρήνα, και ένας βρόχος που θεωρεί μια κενή ανάγνωση μοιραία σκίζει τη σήραγγα σε ένα δευτερόλεπτο αδράνειας. Το Builder.setBlocking(true) αλλάζει αυτή τη συμπεριφορά, και το τίμημα είναι ένα νήμα που περιμένει μέσα στην ανάγνωση.

Τα ορίσματα διαδρομής και διεύθυνσης επικυρώνονται ως τιμές LinkAddress, και προορισμοί multicast όπως το 224.0.0.0/4 απορρίπτονται ευθέως. Μια διεπαφή tun σημείου προς σημείο δεν έχει ούτε τομέα εκπομπής, άρα η ανακάλυψη multicast μέσω αυτής χρειάζεται άλλο σχεδιασμό αντί για άλλη μια διαδρομή. Η αναφορά VpnService.Builder είναι η λίστα που πρέπει να ελέγξετε πριν συμπεράνετε ότι μια κλήση αγνοήθηκε.

Το εύρος ανά εφαρμογή στενεύει το σύνολο, όχι τη σήραγγα

Από το API 21 ο κατασκευαστής δέχεται το addAllowedApplication και το addDisallowedApplication, και οι δύο λίστες είναι αμοιβαία αποκλειόμενες μέσα σε έναν κατασκευαστή. Μια λίστα επιτρεπόμενων κάνει τη σήραγγα να υπάρχει για αυτά τα πακέτα ενώ όλα τα άλλα κρατούν το φυσικό δίκτυο· μια λίστα αποκλεισμένων συλλαμβάνει τα πάντα εκτός από τα ονομασμένα πακέτα, που είναι αυτό που ένας πελάτης προσφέρει ως διαχωρισμένη σήραγγα. Πριν από το API 21 δεν υπάρχει καθόλου μηχανισμός πλατφόρμας, και η συμπεριφορά ανά εφαρμογή ερχόταν από πελάτες τρίτων που φιλτράριζαν κατά UID μέσα στον δικό τους προωθητή. Η διάκριση μετράει όταν αποσφαλματώνετε, γιατί μια σήραγγα ανά εφαρμογή με λίστα επιτρεπόμενων ενός πακέτου παράγει σχεδόν καθόλου κίνηση και αυτό είναι σωστή συμπεριφορά, όχι χαλασμένη διαδρομή.

Όποιο κι αν είναι το εύρος, η υποδοχή που ανοίγει η εφαρμογή σας προς τον διακομιστή VPN δεν πρέπει να ταξιδεύει μέσα από τη σήραγγα που χτίζει. Το VpnService.protect(socket) δένει αυτή την υποδοχή στο υποκείμενο δίκτυο ώστε τα πακέτα της να παρακάμπτουν τη συσκευή tun. Αν παραλείψετε την κλήση, η χειραψία καταπίνεται από τη διεπαφή που υποτίθεται ότι θα τη σηκώσει.

Το VpnService.setUnderlyingNetworks κλείνει τον κύκλο λέγοντας στο σύστημα ποιο φυσικό δίκτυο μεταφέρει τη σήραγγα. Αυτή η τιμή καθορίζει πώς το VPN αναφέρει συνδεσιμότητα στις άλλες εφαρμογές, και μια τιμή null ή παρωχημένη είναι συνηθισμένη αιτία συνδεδεμένης σήραγγας που το υπόλοιπο τηλέφωνο θεωρεί εκτός σύνδεσης. Ενημερώστε την όταν αλλάζει το υποκείμενο δίκτυο, όχι μόνο κατά την εκκίνηση.

Αθροίσματα ελέγχου και MTU: όπου μια σωστή διαδρομή αποτυγχάνει ακόμη

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

Η δεύτερη είναι το εύρος του αθροίσματος ελέγχου. Όταν η εφαρμογή χτίζει η ίδια πακέτα TCP ή UDP, το άθροισμα ελέγχου καλύπτει μια ψευδοκεφαλίδα που κρατά τη διεύθυνση πηγής, τη διεύθυνση προορισμού, τον αριθμό πρωτοκόλλου και το μήκος μεταφοράς, και η παράλειψη οποιουδήποτε από αυτά δίνει πακέτα που φτάνουν και απορρίπτονται. Αυτή η αποτυχία μοιάζει με λήξη χρόνου μετά από απάντηση που δεν έρχεται ποτέ, που στέλνει τους περισσότερους πίσω στις διαδρομές. Το setMtu καθορίζει το MTU της διεπαφής, και κάθε ανάγνωση επιστρέφει ακριβώς ένα πλήρες πακέτο IP, άρα ένα buffer γραμμένο για ροή byte θα κατατμήσει την ανάλυσή σας και όχι το πακέτο.

Ένα πέρασμα επαλήθευσης που χωρίζει τα επίπεδα

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

Αν τίποτα δεν φτάνει στον περιγραφέα, το σύνολο διαδρομών είναι ο ύποπτος και όχι ο αναγνώστης: επιβεβαιώστε τα προθέματα, επιβεβαιώστε ότι υπάρχει διεύθυνση στην ίδια οικογένεια, και επιβεβαιώστε ότι η εφαρμογή είναι μέσα στο εύρος που ορίσατε. Αν τα πακέτα φτάνουν στον περιγραφέα αλλά τίποτα δεν επιστρέφει, εξετάστε το protect() και την τιμή του υποκείμενου δικτύου πριν αγγίξετε ξανά μια διαδρομή. Αν η μακρινή πλευρά αναφέρει παραμορφωμένα πακέτα, το άθροισμα ελέγχου και το MTU είναι τα σημεία που κοιτάτε. Ένας έλεγχος ανά επίπεδο γλιτώνει ένα απόγευμα εικασιών.

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