Πώς να ελέγξετε στον κώδικα αν είναι συνδεδεμένο ένα VPN στο Android
Το Android δεν έχει getter για συνδεδεμένο VPN. Ρωτήστε τις δυνατότητες δικτύου, παρατηρήστε τις αλλαγές μεταφοράς και κρατήστε χωριστά τη συγκατάθεση της εφαρμογής, την κατάσταση της συσκευής και την απόδειξη του διακομιστή.
Δεν μπορείτε να ρωτήσετε το Android αν είναι συνδεδεμένο ένα VPN. Το VpnService δεν εκθέτει getter σύνδεσης, και το εικονίδιο του κλειδιού στη γραμμή κατάστασης το σχεδιάζει το σύστημα, όχι κάτι που μπορεί να διαβάσει η εφαρμογή σας. Αυτό που μπορείτε να κάνετε είναι να θέσετε στη στοίβα συνδεσιμότητας μια στενότερη ερώτηση: ποιο δίκτυο χρησιμοποιεί η κίνηση αυτής της εφαρμογής και μεταφέρει αυτό το δίκτυο τη μεταφορά VPN. Αυτό το εγχειρίδιο περνά τους ελέγχους με τη σειρά που αποφεύγει τα ψευδώς θετικά, από μια εντολή shell στη συσκευή έως το callback που αφήνετε να τρέχει στην εφαρμογή.
Ελέγξτε τη συσκευή από ένα shell πριν αγγίξετε τον κώδικα της εφαρμογής
Πάρτε πρώτα μια βάση αναφοράς. Το adb shell dumpsys connectivity εκτυπώνει κάθε δίκτυο που γνωρίζει το σύστημα, και κάθε μπλοκ απαριθμεί τις μεταφορές του, οπότε ένα δίκτυο VPN εμφανίζεται με VPN σε αυτή τη λίστα. Διαβάστε το μπλοκ του προεπιλεγμένου δικτύου πριν από οτιδήποτε άλλο και μετά αναζητήστε άλλα δίκτυα που μεταφέρουν την ίδια μεταφορά.
Δύο έξοδοι απαντούν σε διαφορετικές ερωτήσεις. Το αν υπάρχει δίκτυο VPN δεν είναι το ίδιο με το αν η εφαρμογή σας το χρησιμοποιεί, και σε συσκευή με προφίλ εργασίας ή δεύτερο χρήστη και τα δύο μπορούν να ισχύουν ταυτόχρονα, ενώ η διεργασία σας μιλά απευθείας στο διαδίκτυο. Η γραμμή του ενεργού δικτύου είναι αυτή που καθορίζει τι κάνουν οι υποδοχές σας.
Ζητήστε το δίκτυο που χρησιμοποιεί η εφαρμογή
Στον κώδικα ο έλεγχος δυνατοτήτων είναι τέσσερις γραμμές. Ζητήστε το ACCESS_NETWORK_STATE στο manifest και μετά συγκρίνετε τις δυνατότητες του ενεργού δικτύου με τη μεταφορά VPN:
ConnectivityManager cm = (ConnectivityManager)
context.getSystemService(Context.CONNECTIVITY_SERVICE);
Network active = cm.getActiveNetwork();
NetworkCapabilities caps = cm.getNetworkCapabilities(active);
boolean vpnInUse = caps != null
&& caps.hasTransport(NetworkCapabilities.TRANSPORT_VPN);
Η απάντηση είναι σκόπιμα στενή: είναι το προεπιλεγμένο δίκτυο της εφαρμογής VPN αυτή τη στιγμή. Δεν λέει τίποτα για το ποιο VPN είναι, αν σας ανήκει ή αν η δίοδος φτάνει στο μακρινό της άκρο. Αντιμετωπίστε το true ως «η κίνηση φεύγει μέσα από κάποια δίοδο» και κρατήστε τη δική σας λογιστική χωριστά. Ένα boolean που σιωπηλά σημαίνει τρία πράγματα ταυτόχρονα είναι ο λόγος που μια οθόνη ρυθμίσεων καταλήγει να αναφέρει το VPN ως κλειστό ενώ ο χρήστης περιηγείται μέσα από αυτό.
Όταν αντίθετα χρειάζεται να ξέρετε αν υπάρχει οποιοδήποτε δίκτυο VPN, επαναλάβετε το cm.getAllNetworks() και δοκιμάστε κάθε σύνολο δυνατοτήτων. Οι δύο απαντήσεις αποκλίνουν σε συσκευή που τρέχει VPN ανά εφαρμογή, το οποίο καλύπτει άλλες εφαρμογές αλλά όχι τη δική σας, και αυτή η απόκλιση είναι η πηγή των περισσότερων αναφορών σφαλμάτων που λένε ότι η εφαρμογή δεν βλέπει ένα VPN που είναι προφανώς ανοιχτό.
Παρατηρήστε αλλαγές αντί να κάνετε polling
Το polling με χρονόμετρο είναι το λάθος σχήμα για αυτή την ερώτηση, επειδή οι αλλαγές κατάστασης φτάνουν ως συμβάντα. Καταχωρίστε ένα αίτημα που ταιριάζει με τη μεταφορά VPN και αφήστε το framework να σας πει πότε ένα ταιριαστό δίκτυο εμφανίζεται, εξαφανίζεται ή αλλάζει:
NetworkRequest request = new NetworkRequest.Builder()
.addTransportType(NetworkCapabilities.TRANSPORT_VPN)
.removeCapability(NetworkCapabilities.NET_CAPABILITY_NOT_VPN)
.build();
cm.registerNetworkCallback(request, callback);
Ένα δίκτυο VPN δεν μεταφέρει ποτέ το NET_CAPABILITY_NOT_VPN, οπότε η ρητή αφαίρεση τεκμηριώνει την πρόθεση. Χωρίς αυτήν το αίτημα εξακολουθεί να ταιριάζει, αλλά ο επόμενος που θα διαβάσει τον κώδικα πρέπει να το συμπεράνει μόνος του.
Η καταχώριση ανήκει στο πεδίο που χρειάζεται την παρατήρηση, συνήθως μια οθόνη ή μια υπηρεσία foreground. Ακυρώστε την καταχώριση στο onStop ή στο onDestroy και μην αφήνετε callback προσκολλημένο σε activity που έχει ήδη φύγει. Μια εφαρμογή χωρίς ορατό συστατικό είναι κακός οικοδεσπότης για μόνιμο παρατηρητή, και τα callbacks που ξεχνάτε είναι αυτά που καταρρέουν μετά από αλλαγή διαμόρφωσης.
Κρατήστε τρεις ερωτήσεις χωριστά
Η περισσότερη σύγχυση σε αυτόν τον τομέα προέρχεται από το να αντιμετωπίζονται τρεις διαφορετικοί έλεγχοι ως ένας. Χρησιμοποιούν διαφορετικά API, και καθένας μπορεί να είναι αληθής ενώ οι άλλοι είναι ψευδείς.
| Question | How you answer it | What a yes means |
|---|---|---|
| Did the user grant VPN consent to my app? | VpnService.prepare() returns null | Consent exists; the tunnel may still be down |
| Is the device sending default traffic through a VPN? | TRANSPORT_VPN on the active network | Something is tunneling, not necessarily your app |
| Did this request arrive through the VPN? | Source address seen by the server | Proof for that connection, nothing wider |
Η τρίτη γραμμή είναι αυτή που οι άνθρωποι προσπαθούν να απαντήσουν στον πελάτη, και εκεί δεν απαντιέται. Δώστε στους πελάτες VPN το δικό τους σύνολο διευθύνσεων και διαβάστε τη διεύθυνση προέλευσης στον διακομιστή· αυτή είναι η μόνη απόδειξη που επιβιώνει όταν ένας χρήστης αλλάζει δίοδο στη μέση της συνεδρίας.
Σειρά επαλήθευσης που αποκλείει τα ψευδώς θετικά
Παλαιότερο δείγμα κώδικα που κυκλοφορεί ακόμη καλεί το getNetworkInfo(ConnectivityManager.TYPE_VPN). Αυτή η κλήση είναι παρωχημένη και σε αρκετές εκδόσεις επέστρεφε null ακόμη και με συνδεδεμένο VPN, γι' αυτό τόσα πολλά αντιγραμμένα αποσπάσματα φαίνονται σπασμένα σε συσκευή που δουλεύει. Οι έλεγχοι δυνατοτήτων αποφεύγουν την παγίδα και συμπεριφέρονται το ίδιο από το Android 5.0 και μετά. Αν συντηρείτε κώδικα που εξακολουθεί να την καλεί, αντικαταστήστε την κλήση αντί να την τυλίξετε σε εφεδρική διαδρομή, γιατί η εφεδρική διαδρομή ήταν αυτή που παρήγαγε τη λάθος απάντηση εξαρχής.
Μια συντόμευση αξίζει μια προειδοποίηση. Η σύγκριση της δημόσιας διεύθυνσης που αναφέρει μια εξωτερική υπηρεσία με τη διεύθυνση εξόδου του VPN αποδεικνύει κάτι για τη διαδρομή, αλλά προσθέτει αίτημα τρίτου μέρους σε κάθε έλεγχο, εξαρτάται από το ότι η υπηρεσία μένει ζωντανή και μπορεί να αποθηκευτεί προσωρινά ή να μπλοκαριστεί. Κρατήστε το ως χειροκίνητη διάγνωση, ποτέ ως έλεγχο κατάστασης μέσα στην εφαρμογή.
Εκτελέστε τους ελέγχους με αυτή τη σειρά και σταματήστε στον πρώτο που διαφωνεί με την αναφορά του χρήστη: λίστα μεταφορών στο shell, δυνατότητες ενεργού δικτύου, συμβάντα callback καθώς εναλλάσσετε τη δίοδο, μετά η διεύθυνση προέλευσης στην πλευρά του διακομιστή. Αν και τα τέσσερα συμφωνούν και η εφαρμογή εξακολουθεί να συμπεριφέρεται λάθος, το σφάλμα είναι στη δική σας μηχανή καταστάσεων και όχι στην εικόνα του Android για το δίκτυο.