Ο έλεγχος που χρησιμοποιούν οι περισσότερες εφαρμογές Android για να εντοπίσουν VPN είναι ένα όνομα διεπαφής, και ποτέ δεν ήταν έλεγχος VPN. Αναφέρει ότι υπάρχει κάποια διεπαφή με μια συμβολοσειρά που περίμενε ο προγραμματιστής, το οποίο σε μια συσκευή σημαίνει OpenVPN και σε άλλη σημαίνει κάτι εντελώς άσχετο. Το ping είναι το ίδιο είδος απόδειξης: αποδεικνύει ότι ένας προορισμός απάντησε, όχι ότι μια σήραγγα μετέφερε το πακέτο. Αυτό που εκθέτει πραγματικά η πλατφόρμα είναι ένα σύνολο δικτύων και τα transports που δηλώνει καθένα από αυτά, και αυτό είναι άλλη ερώτηση με άλλη απάντηση.

Μύθος: αν υπάρχει tun0, το VPN είναι ενεργό

Οι εφαρμογές VPN για Android είναι υλοποιήσεις VpnService που παραδίδουν στο σύστημα έναν περιγραφέα αρχείου tun. Το όνομα της διεπαφής προέρχεται από όποιον τη δημιούργησε: το OpenVPN συνήθως παράγει tun0, τα PPTP και L2TP παράγουν ppp0, το WireGuard ονομάζει τη διεπαφή του από τη σήραγγα, και μια σήραγγα IPsec μπορεί να μην παρουσιάζει καθόλου συμβατικό όνομα. Επομένως η ανάγνωση του /proc/net/dev ή η απαρίθμηση αντικειμένων NetworkInterface αναζητά σύμβαση, όχι γεγονός.

Και οι δύο κατευθύνσεις σφάλματος είναι ζωντανές. Μια συσκευή μπορεί να έχει tun0 για τοπική σήραγγα που δεν έχει καμία σχέση με το VPN του χρήστη, και μια απολύτως υγιής σήραγγα WireGuard μπορεί να είναι αόρατη σε έλεγχο που γνωρίζει μόνο τη συμβολοσειρά tun0. Τα ψευδώς θετικά και τα ψευδώς αρνητικά στην ίδια συνάρτηση την καθιστούν άχρηστη ως διαγνωστικό, γιατί ένα δελτίο υποστήριξης που λέει «δεν εντοπίστηκε VPN» δεν μπορεί να είναι αξιόπιστο προς καμία κατεύθυνση.

Η πρόσβαση επίσης έχει στενέψει. Νεότερες εκδόσεις Android περιορίζουν τι μπορεί να διαβάσει μια συνηθισμένη εφαρμογή από το /proc, οπότε ένας έλεγχος βάσει ονόματος που λειτουργούσε στο Android 8 μπορεί να επιστρέψει κενή λίστα σε μεταγενέστερη συσκευή χωρίς καμία αλλαγή κώδικα. Όταν ο έλεγχος αποτυγχάνει σιωπηλά προς την ανοιχτή πλευρά, η εφαρμογή συμπεραίνει ότι δεν υπάρχει VPN σε υλικό όπου η σήραγγα είναι εντάξει.

Μύθος: ένα επιτυχές ping σημαίνει ότι η σήραγγα μεταφέρει κίνηση

Ένα ping προς εσωτερικό host πετυχαίνει όποτε υπάρχει διαδρομή προς αυτόν τον host. Με split tunnelling ο host μπορεί να είναι προσβάσιμος μέσω Wi-Fi ενώ η σήραγγα είναι ενεργή και κάνει κάτι εντελώς διαφορετικό. Με VPN ανά εφαρμογή η εφαρμογή που κάνει το ping μπορεί να εξαιρείται από τη σήραγγα, οπότε η ηχώ ICMP βγαίνει έξω από αυτήν και επιστρέφει, κάτι που αποδεικνύει το αντίθετο από ό,τι υποθέτει ο έλεγχος.

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

Τι αναφέρει πραγματικά η πλατφόρμα

Από το API 21 και μετά, το ConnectivityManager.getAllNetworks() επιστρέφει κάθε δίκτυο που γνωρίζει το σύστημα, και το getNetworkCapabilities(network) αναφέρει τι δηλώνει αυτό το δίκτυο. Ένα δίκτυο VPN φέρει TRANSPORT_VPN. Η επανάληψη σε όλα τα δίκτυα απαντά σε μία ερώτηση: υπάρχει αυτή τη στιγμή ενεργό VPN σε αυτή τη συσκευή;

Μια δεύτερη, στενότερη ερώτηση απαιτεί διαφορετική κλήση. Το getActiveNetwork() επιστρέφει το δίκτυο που θα χρησιμοποιούσε τώρα η κίνηση της εφαρμογής, και ο έλεγχος της ίδιας δυνατότητας σε αυτό απαντά: θα περνούσε η κίνησή μου μέσα από VPN? Τα δύο μπορεί να διαφωνούν, και η διαφωνία είναι κατατοπιστική. Ένα VPN που είναι ενεργό αλλά όχι τρέχον παραπέμπει σε δρομολόγηση ανά εφαρμογή ή σε ρύθμιση split tunnel, όχι σε σήραγγα που λείπει.

Network active = cm.getActiveNetwork();
NetworkCapabilities caps = cm.getNetworkCapabilities(active);
boolean viaVpn = caps != null
    && caps.hasTransport(NetworkCapabilities.TRANSPORT_VPN);

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

Το NetworkInfo ήταν ο ίδιος μύθος με API

Το ConnectivityManager.TYPE_VPN με το NetworkInfo είναι η δεύτερη γενιά αυτού του ελέγχου, και είναι παρωχημένο για έναν λόγο. Στις σύγχρονες εκδόσεις το αντικείμενο που επιστρέφεται για VPN μπορεί να είναι null ενώ η σήραγγα είναι συνδεδεμένη, οπότε κώδικας που αντιμετωπίζει το null ως «δεν υπάρχει VPN» αναφέρει σιωπηλά ψευδώς αρνητικό αποτέλεσμα στις συσκευές όπου η απάντηση μετράει περισσότερο.

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

Αντιδράστε σε callbacks αντί να ερευνάτε περιοδικά

Καταχωρίστε ένα NetworkRequest που απαιτεί TRANSPORT_VPN και αφαιρεί το NET_CAPABILITY_NOT_VPN, και μετά καλέστε registerNetworkCallback με αυτό το αίτημα. Τα onAvailable και onLost σας λένε τότε πότε αλλάζει η δήλωση, και αυτό είναι το γεγονός που πραγματικά σας ενδιαφέρει. Ακυρώστε την καταχώριση του callback όταν σταματήσει το component, αλλιώς η εφαρμογή κρατά αναφορά σε νεκρή οθόνη και το callback συνεχίζει να ενεργοποιείται.

NetworkRequest req = new NetworkRequest.Builder()
    .addTransportType(NetworkCapabilities.TRANSPORT_VPN)
    .removeCapability(NetworkCapabilities.NET_CAPABILITY_NOT_VPN)
    .build();
cm.registerNetworkCallback(req, callback);

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

Τι δεν μπορεί ακόμη να σας πει η απάντηση

Κανένα από αυτά τα API δεν αναφέρει αν η σήραγγα είναι υγιής, σε ποιον διακομιστή τερματίζει ή αν η κίνηση μιας συγκεκριμένης εφαρμογής είναι μέσα σε αυτήν. Ένα δίκτυο VPN μπορεί να υπάρχει χωρίς χρησιμοποιήσιμες διαδρομές, και μια σήραγγα ανά εφαρμογή μπορεί να εξαιρεί την εφαρμογή που κάνει την ερώτηση.

Άρα καταγράψτε τι είδε ο έλεγχος, όχι μόνο το boolean. Καταγράψτε το αναγνωριστικό δικτύου, τα δηλωμένα transports και αν αυτό το δίκτυο ήταν ενεργό. Μια αναφορά που λέει «δεν εντοπίστηκε VPN» είναι άχρηστη, ενώ μία που λέει ότι ένα δίκτυο δήλωσε TRANSPORT_VPN αλλά το getActiveNetwork() επέστρεψε Wi-Fi δείχνει απευθείας σε δρομολόγηση ή σε λίστα εξαιρέσεων, και αυτό είναι διόρθωση πάνω στην οποία μπορεί κάποιος να δουλέψει.

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