VpnService protect() ή εξαίρεση εφαρμογής: τι διορθώνει τη δρομολόγηση
Προστατέψτε το socket που μεταφέρει το τούνελ ή εξαιρέστε ολόκληρη την εφαρμογή: οι τρεις συνθήκες που καθορίζουν ποιο εύρος χρειάζεται το Android VPN σας.
Έχετε δύο τρόπους να κρατήσετε τα πακέτα του ίδιου του τούνελ έξω από το τούνελ: να προστατέψετε το socket μεταφοράς ή να βγάλετε ολόκληρη την εφαρμογή από το VPN. Λύνουν διαφορετικά προβλήματα, και το να διαλέγετε το ευρύτερο επειδή συνδέεται ευκολότερα είναι ακριβώς ο τρόπος με τον οποίο οι εφαρμογές χάνουν την πρόσβαση στο τοπικό δίκτυο και το διαχωρισμένο DNS χωρίς να το καταλάβουν. Ξεκινήστε από ό,τι πρέπει να μείνει έξω από το τούνελ και μετά διαλέξτε το μικρότερο εύρος που το πετυχαίνει.
Τι αφαιρεί το protect() από το τούνελ
VpnService.protect() είναι κλήση ανά socket. Του δίνετε το socket ή τον περιγραφέα που μεταφέρει το τούνελ, και το Android σημαδεύει αυτό το socket ώστε η κίνησή του να χρησιμοποιεί το υποκείμενο δίκτυο αντί για τη διεπαφή του VPN. Ό,τι άλλο στέλνει η εφαρμογή εξακολουθεί να ακολουθεί την κανονική διαδρομή του VPN.
Ακριβώς αυτό το στενό εύρος είναι το νόημα. Ένα προστατευμένο socket δεν αλλάζει το πώς ο HTTP πελάτης σας επιλύει ονόματα, ποια διεπαφή χρησιμοποιούν οι κλήσεις αναλυτικών σας ή τι συμβαίνει με μια δεύτερη σύνδεση που ανοίγει αργότερα.
Τρεις συνθήκες που καθορίζουν το εύρος
Η πρώτη συνθήκη είναι αν πρέπει να διαφύγει μόνο η μεταφορά του τούνελ. Αν η υπόλοιπη κίνηση της εφαρμογής πρέπει να περνά ακόμη από το VPN, η προστασία του socket είναι το σωστό εύρος. Η δεύτερη συνθήκη είναι αν ελέγχετε τη δημιουργία του socket. Μια βιβλιοθήκη που ανοίγει το δικό της socket βαθιά μέσα δεν σας αφήνει σημείο για να καλέσετε protect(), εκτός αν εκθέτει hook ή δέχεται έτοιμο socket. Η τρίτη συνθήκη είναι αν η εφαρμογή πρέπει να φτάνει απευθείας στο τοπικό δίκτυο ή σε captive portal, που είναι άλλη απαίτηση από το να κρατάτε το τούνελ ζωντανό.
Το να εξαιρέσετε την εφαρμογή με addDisallowedApplication() είναι το χοντρό εργαλείο. Αφαιρεί από το VPN κάθε σύνδεση που κάνει η εφαρμογή, μαζί με εκείνες που θέλατε να επιθεωρήσετε, και σε μερικές συσκευές αλλάζει επίσης το πώς συμπεριφέρεται η τοπική ανακάλυψη.
Μια τρίτη επιλογή είναι να δεσμεύσετε τη μεταφορά σε επιλεγμένο δίκτυο με bindProcessToNetwork() ή με λαβή δικτύου σε επίπεδο socket. Αυτό είναι χρήσιμο όταν η συσκευή έχει Wi-Fi και κινητό ταυτόχρονα και θέλετε το τούνελ να πηγαίνει από συγκεκριμένο κανάλι ανόδου.
Καλέστε το όσο δημιουργείται η μεταφορά
Καλέστε protect() στο ίδιο μπλοκ που δημιουργεί τη μεταφορά, πριν από connect() ή send(). Αν η κλήση επιστρέψει false, κλείστε το socket και αποτύχετε τη ρύθμιση· αν συνεχίσετε, μένετε με μεταφορά της οποίας τα πακέτα μπορεί να ξαναμπούν στη δική σας διεπαφή.
Το σχήμα είναι αρκετά μικρό ώστε να μείνει σε ένα σημείο:
DatagramSocket transport = new DatagramSocket();
if (!protect(transport)) {
transport.close();
throw new IOException("transport not protected");
}
transport.connect(serverAddress);Τρεις λεπτομέρειες δαγκώνουν εδώ. Πρώτον,το boolean είναι το μόνο σήμα που παίρνετε, οπότε καταγράψτε την αποτυχία με αρκετό πλαίσιο ώστε να ξεχωρίζετε μια ανακλημένη συγκατάθεση από μια απορριφθείσα κλήση. Δεύτερον,protect() καλύπτει το socket, όχι τον αριθμό του περιγραφέα: αν κλείσετε το socket και αργότερα επαναχρησιμοποιήσετε τον ίδιο ακέραιο για άλλο socket, το νέο socket δεν είναι προστατευμένο. Τρίτον, όταν μια native βιβλιοθήκη ανοίγει τη μεταφορά, προστατέψτε τον περιγραφέα πριν συνδεθεί ο native κώδικας, αλλιώς η διαδρομή μπορεί να έχει ήδη επιλεγεί.Η αναφορά του VpnService είναι το σημείο όπου επιβεβαιώνετε ποιες υπερφορτώσεις παρέχει πραγματικά το επίπεδο API-στόχος σας.
Δοκιμάστε τη μπλοκαρισμένη διαδρομή, όχι μόνο την ευτυχισμένη
Επαληθεύστε με το τούνελ σηκωμένο, γιατί είναι η μόνη κατάσταση στην οποία οι δύο διαδρομές μπορούν να διαφέρουν. Στείλτε ένα αίτημα μέσα από την προστατευμένη μεταφορά και ένα από διαδρομή κώδικα που δεν κάλεσε ποτέ protect(), και μετά συγκρίνετε τι βλέπει το απομακρυσμένο άκρο: η προστατευμένη σύνδεση πρέπει να φτάνει με το υποκείμενο δίκτυο ως πηγή, ενώ η μη προστατευμένη δείχνει τη διεύθυνση του ίδιου του τούνελ και κινεί τους μετρητές byte του τούνελ. Στη συσκευή, το ss ή το netstat δείχνει τον ίδιο διαχωρισμό πριν φύγει οτιδήποτε από το ραδιόφωνο.
Δοκιμάστε την ανάκληση χωριστά, γιατί μια αλλαγή συγκατάθεσης γκρεμίζει τη διεπαφή χωρίς να αγγίξει τον κώδικα του socket σας. Ανακαλέστε τη συγκατάθεση VPN ή σταματήστε την υπηρεσία, ξαναχτίστε τη μεταφορά και επιβεβαιώστε ότι το protect() επιστρέφει ακόμη true και ότι η μεταφορά επανασυνδέεται μόλις δοθεί ξανά η συγκατάθεση.
Αν το προστατευμένο αίτημα ακολουθεί παρ' όλα αυτά το τούνελ, η κλήση προστασίας πιθανώς γίνεται αφού το socket έχει ήδη δρομολογηθεί. Μετακινήστε την νωρίτερα και δοκιμάστε ξανά.
Κρατήστε την απόφαση γραμμένη δίπλα στη ρύθμιση της μεταφοράς: ποια sockets είναι προστατευμένα, ποια κίνηση της εφαρμογής εξαιρείται και τι κάνει η εφαρμογή όταν η προστασία αποτυγχάνει. Αυτή η γραμμή είναι ό,τι εμποδίζει μια μεταγενέστερη αναδιάρθρωση να επεκτείνει την εξαίρεση σε ολόκληρη την εφαρμογή επειδή ένα μόνο test αποτύγχανε. Δίνει επίσης στο επόμενο άτομο τρόπο να ελέγξει την πρόθεση χωρίς αντίστροφη μηχανική του manifest.