Η εφαρμογή σας καλεί το VpnService.prepare, παίρνει null, χτίζει σήραγγα, και ο περιγραφέας φαίνεται έγκυρος, αλλά τίποτα δεν φτάνει στον διακομιστή. Ή το prepare επιστρέφει Intent, ο χρήστης το εγκρίνει, και το establish αποτυγχάνει ακόμα. Και τα δύο αποτελέσματα συνήθως σημαίνουν το ίδιο: μια άλλη εφαρμογή κατέχει ήδη τη διεπαφή VPN για αυτόν τον χρήστη, και οι κλήσεις protect σας σημαίνουν υποδοχές που η σήραγγά σας δεν μπορεί να χρησιμοποιήσει. Περάστε τους παρακάτω ελέγχους με σειρά και σταματήστε στον πρώτο που αποτυγχάνει, γιατί ένα μεταγενέστερο βήμα δεν μπορεί να διορθώσει ένα προηγούμενο.

Ελέγξτε τη θέση VPN πριν αγγίξετε το protect

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

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

Βήμα 1: διαβάστε τι επιστρέφει το prepare

Το VpnService.prepare απαντά σε ένα ερώτημα: επιτρέπεται σε αυτή την εφαρμογή να γίνει το VPN; Επιστρέφει null όταν υπάρχει ήδη συγκατάθεση, και Intent όταν ο χρήστης πρέπει ακόμη να το εγκρίνει, το οποίο πρέπει να εκκινήσετε με startActivityForResult. Αντιμετωπίστε την επιστροφή null ως άδεια να συνεχίσετε, όχι ως απόδειξη ότι το establish θα πετύχει.

Intent intent = VpnService.prepare(context);
if (intent != null) {
    startActivityForResult(intent, REQUEST_VPN);
}

Αν η διαδρομή Intent επιστρέψει και ο χρήστης αρνήθηκε, το prepare θα συνεχίσει να επιστρέφει Intent στην επόμενη προσπάθεια. Καταγράψτε τον κωδικό αποτελέσματος αντί να επαναλαμβάνετε σε βρόχο, γιατί μια αρνηθείσα συγκατάθεση είναι απόφαση χρήστη, όχι παροδικό σφάλμα.

Βήμα 2: επιβεβαιώστε ότι η διεπαφή ανήκει στην εφαρμογή σας

Μετά το establish, το ParcelFileDescriptor που λαμβάνετε είναι η απόδειξη ιδιοκτησίας σας, και αξίζει να το ελέγξετε πριν από οποιαδήποτε εργασία με υποδοχές. Ένας περιγραφέας που είναι null ή έχει ήδη κλείσει σημαίνει ότι η διεπαφή δεν δημιουργήθηκε ποτέ, ακόμα και αν δεν εμφανίστηκε εξαίρεση. Συγκρίνετε το όνομα της διεπαφής από τον builder με αυτό που αναφέρει το σύστημα για το ενεργό VPN, χρησιμοποιώντας dumpsys σε συσκευή δοκιμής. Αν διαφέρουν, η σήραγγα που χτίσατε δεν είναι η σήραγγα που μεταφέρει κίνηση.

adb shell dumpsys connectivity

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

Βήμα 3: καλέστε το protect πριν από το connect

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

Καλέστε το protect πριν από το connect ή το bind, ποτέ μετά. Η προστασία πρέπει να υπάρχει πριν συνδεθεί ή δεθεί η υποδοχή, γιατί μια συνδεδεμένη υποδοχή έχει ήδη επιλέξει τη διαδρομή της και ένα μεταγενέστερο protect δεν τη μετακινεί αξιόπιστα. Αν το protect επιστρέψει false, σταματήστε και ελέγξτε ξανά την ιδιοκτησία: αποτυγχάνει όταν η διεπαφή δεν ανήκει στην εφαρμογή σας, που είναι ακριβώς η κατάσταση που αφήνει ένα άλλο VPN. Μια επιστροφή false είναι σήμα να επιστρέψετε στο Βήμα 1, όχι να ξαναδοκιμάσετε την ίδια υποδοχή.

DatagramSocket socket = new DatagramSocket();
if (!vpnService.protect(socket)) {
    return;
}
socket.connect(serverAddress, serverPort);

Βήμα 4: δεσμεύστε μια διεργασία όταν μια υποδοχή είναι άβολη

Μερικές μεταφορές δεν είναι μία υποδοχή: μια βιβλιοθήκη μπορεί να ανοίξει πολλές, ή μπορεί να θέλετε κάθε σύνδεση σε μια βοηθητική διεργασία να χρησιμοποιεί την ίδια έξοδο. Το ConnectivityManager.bindProcessToNetwork δεσμεύει τη διεργασία σε επιλεγμένο δίκτυο, και το παλαιότερο setProcessDefaultNetwork έκανε την ίδια δουλειά πριν καταργηθεί. Ζητήστε την υποκείμενη σύνδεση με NET_CAPABILITY_NOT_VPN ώστε η δέσμευση να επιλέξει το δίκτυο κάτω από τη σήραγγά σας και όχι την ίδια τη σήραγγα.

NetworkRequest request = new NetworkRequest.Builder()
    .addCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET)
    .addCapability(NetworkCapabilities.NET_CAPABILITY_NOT_VPN)
    .build();

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

Διορθώστε τη συσκευή του χρήστη, όχι τη διαδρομή του κώδικα

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

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

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

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