Δύο διαδρομές μπορούν να βάλουν μια διεπαφή IP μπροστά από μια εφαρμογή Android, και οι δύο καταλήγουν στο ίδιο είδος περιγραφέα αρχείου με πολύ διαφορετικούς ιδιοκτήτες. Η μία περνά από το VpnService και τον builder του, η άλλη από ένα root shell και το /dev/net/tun. Κάθε ανάγνωση σε αυτόν τον περιγραφέα είναι ένα εξερχόμενο πακέτο και κάθε εγγραφή είναι ένα εισερχόμενο. Αυτό που χωρίζει τις διαδρομές είναι ποιος κρατά την πολιτική, ποιος επιβιώνει από μια αναβάθμιση του λειτουργικού και πόσο από τον πίνακα δρομολόγησης επιτρέπεται να αγγίξεις.

Πάρε τη διαδρομή της πλατφόρμας εκτός αν μπορείς να ονομάσεις το συγκεκριμένο πράγμα που αρνείται να κάνει. Αυτή η άρνηση είναι συνήθως TAP, καταγραφή έξω από το δικό σου σύνολο εφαρμογών, ή ένας κανόνας δρομολόγησης που το πλαίσιο δεν εκθέτει ποτέ.

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

Δύο τρόποι να αποκτήσεις διεπαφή IP

Η διαδρομή root είναι η παλαιότερη συνταγή. Ανοίγεις το /dev/net/tun, καλείς το TUNSETIFF με IFF_TUN, ανεβάζεις τη διεπαφή με ip link και προσθέτεις διευθύνσεις και διαδρομές με το χέρι. Κάθε βήμα είναι δικό σου να το πετύχεις, μαζί με όσα θα είχε χειριστεί σιωπηλά το πλαίσιο. Μια διαδρομή που λείπει δίνει στον χρήστη την ίδια αναφορά με έναν νεκρό διακομιστή.

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

Τι επιστρέφει στην πραγματικότητα το establish()

Το Builder.establish() έχει τρεις εκβάσεις και χρειάζονται τρεις διαφορετικές απαντήσεις. Ένας περιγραφέας σημαίνει ότι η διεπαφή έχει ρυθμιστεί και είναι δική σου για χρήση, το null σημαίνει ότι το αίτημα δεν πέρασε, και μια εξαίρεση που ρίχνεται σημαίνει ότι η υπηρεσία συστήματος το απέρριψε. Το να συνενώσεις το null και την επιτυχία σε ένα boolean είναι ο πιο συνηθισμένος τρόπος με τον οποίο μια εφαρμογή αναφέρει ένα λειτουργικό τούνελ που δεν μεταφέρει ποτέ πακέτο. Η αναφορά του Builder αναφέρει την απαίτηση διεύθυνσης, και αξίζει να τη διαβάσεις πριν κατηγορήσεις τον διακομιστή.

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

Το Binder είναι ένα άλμα, το JNI είναι άλλος άξονας

Ένα αίτημα του builder περνά στο σύστημα μέσω Binder, και κάπου από κάτω η πλατφόρμα καλεί εγγενή κώδικα για να ρυθμίσει τη συσκευή. Πρόκειται για δύο διαφορετικούς μηχανισμούς στοιβαγμένους μέσα σε μία κλήση. Το Binder είναι επικοινωνία μεταξύ διεργασιών ανάμεσα στην εφαρμογή σου και σε μια υπηρεσία συστήματος, ενώ το JNI είναι η γέφυρα από Java σε εγγενή κώδικα μέσα σε μια διεργασία. Η ανάγνωση ενός δέντρου AOSP του Android 9 κάνει τη στοίβα να φαίνεται κυριολεκτική, και αξίζει να θυμάσαι ότι τα εσωτερικά ονόματα κλάσεων και οι διαδρομές αρχείων μετακινούνται μεταξύ εκδόσεων.

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

Το TUN μεταφέρει IP, το TAP μεταφέρει Ethernet

Το TUN είναι διεπαφή IP. Το TAP είναι διεπαφή Ethernet, και αυτή η διαφορά αποφασίζει αν η γεφύρωση είναι καν δυνατή. Μια συσκευή TUN δεν έχει διεύθυνση MAC ούτε κεφαλίδα Ethernet για να προωθήσει, άρα ένας πελάτης TUN δεν μπορεί να μπει σε γέφυρα επιπέδου 2, όποια κι αν είναι η γραφή των διαδρομών.

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

Πού κερδίζει ακόμη η διαδρομή root

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

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

Επιλογή ανάμεσά τους

Ξεκίνα από τη διεπαφή που χρειάζεσαι, όχι από τον κώδικα που έχεις. Αν η απάντηση είναι μια συσκευή TUN με διαδρομές περιορισμένες στην εφαρμογή, η διαδρομή της πλατφόρμας είναι συντομότερη και επιβιώνει από αναβαθμίσεις. Αν η απάντηση περιλαμβάνει πλαίσια Ethernet, καταγραφή άλλων εφαρμογών ή κανόνες δρομολόγησης που ο builder απορρίπτει, είσαι σε έδαφος root και πρέπει να το προϋπολογίσεις ως λειτουργικό κόστος.

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

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