Δύο συμπτώματα εμφανίζονται στον ίδιο βρόχο VpnService και καταλογίζονται στο ίδιο πράγμα: ένας αναγνώστης που δεν επιστρέφει ποτέ, και μια εγγραφή που αποτυγχάνει με ENOBUFS. Δεν είναι παραλλαγές ενός σφάλματος. Μια κολλημένη ανάγνωση είναι διαφωνία για τον έλεγχο ροής με τον peer· μια αποτυχημένη εγγραφή είναι ένα πακέτο που η διεπαφή δεν θα δεχτεί. Αν τα αντιμετωπίσετε ως ένα πρόβλημα, θα περάσετε μια μέρα ρυθμίζοντας buffers που δεν συμμετείχαν ποτέ. Η παρακάτω διαίρεση είναι πώς αποφασίζω ποιο μισό του βρόχου να οργανώσω πρώτο.

Ένας βρόχος, δύο ανεξάρτητα σημεία αποτυχίας

Η υπηρεσία σας διαβάζει ένα πακέτο IP από τον περιγραφέα αρχείου TUN, το εξετάζει και γράφει μια απάντηση πίσω στον ίδιο περιγραφέα. Ό,τι σπάει σε αυτή τη διαδρομή εμφανίζεται σε μία από αυτές τις δύο κλήσεις, γι' αυτό και οι δύο αναφέρονται ως σπασμένη σήραγγα. Οι κλήσεις έχουν διαφορετικά συμβόλαια. Μια ανάγνωση περιμένει να σας δώσει ο πυρήνας ένα πακέτο, και μια εγγραφή δίνει στον πυρήνα ένα πακέτο που έχει το δικαίωμα να απορρίψει.

Οι αναγνώσεις είναι το εύκολο μισό για σκέψη. Μια ανάγνωση που δεν επιστρέφει τίποτα είτε περιμένει κίνηση που δεν φτάνει ποτέ είτε περιμένει επειδή ο peer σταμάτησε να στέλνει.

Μια κολλημένη ανάγνωση είναι διαφωνία ελέγχου ροής

Η ανάγνωση μιας συσκευής TUN μπλοκάρει μέχρι να υπάρξει διαθέσιμο πακέτο. Αυτή είναι σχεδιασμένη συμπεριφορά, και γι' αυτό ένα νήμα παρκαρισμένο μέσα στο read φαίνεται φυσιολογικό σε αδρανή ζεύξη. Γίνεται κόλλημα όταν η δική σας στοίβα TCP συνεχίζει να δέχεται δεδομένα από την τοπική εφαρμογή αλλά δεν διαφημίζει ποτέ αρκετό παράθυρο ώστε ο απομακρυσμένος peer να στείλει περισσότερα. Ο peer γεμίζει το παράθυρο που διαφημίσατε, περιμένει ένα ACK και μια ενημέρωση παραθύρου, και σταματά. Τότε ο αναγνώστης σας δεν έχει τίποτα να επιστρέψει, και η εφαρμογή βλέπει μια σύνδεση ανοιχτή και σιωπηλή.

Οι συνήθεις αιτίες είναι μια στοίβα που γράφει στο socket χωρίς να παρακολουθεί το διαφημιζόμενο παράθυρο, και μια που ξεχνά να στείλει ενημέρωση παραθύρου αφού αδειάσει το buffer της. Απ' έξω φαίνονται ίδιες: βγαίνουν bytes, δεν επιστρέφει τίποτα, δεν σηκώνεται σφάλμα. Το μπλοκάρισμα του νήματος ανάγνωσης σε parsing, επίλυση DNS ή I/O δίσκου παράγει την ίδια μορφή συμπτώματος για άσχετο λόγο, οπότε επιβεβαιώστε ότι το νήμα είναι πράγματι μέσα στο read πριν κατηγορήσετε τον έλεγχο ροής.

Ελέγξτε την ουρά αποστολής του peer και το παράθυρο που είδε τελευταία φορά. Αν ο peer κρατά δεδομένα επειδή το παράθυρό σας είναι μηδέν, η διόρθωση είναι στη διαδρομή ACK σας, όχι στην ανάγνωση TUN.

Το ENOBUFS είναι πρόβλημα μεγέθους, όχι έλλειψη buffer

Μια εγγραφή που επιστρέφει ENOBUFS σε διεπαφή TUN συνήθως απορρίπτει το μήκος του πακέτου. Η διεπαφή έχει MTU, ένα πακέτο μεγαλύτερο δεν μπορεί να μπει στην ουρά, και η επανάληψη του ίδιου πακέτου αποτυγχάνει με τον ίδιο τρόπο κάθε φορά. Αυτός ο ντετερμινισμός είναι το σημάδι. Μια πραγματική έλλειψη buffer καθαρίζει όταν πέσει η πίεση, ενώ μια απόρριψη μεγέθους δεν νοιάζεται πόσο περιμένετε.

Το TCP έχει μηχανισμό ακριβώς γι' αυτό, και είναι το βήμα που οι άνθρωποι παραλείπουν: το MSS clamp. Όταν συνθέτετε τμήματα στον χώρο χρήστη επιλέγετε το μέγεθος του payload, και η επιλογή του από το MTU της τοπικής διεπαφής αντί του MTU της σήραγγας εκπέμπει τμήματα που η σήραγγα δεν μπορεί να μεταφέρει. Περιορίστε στο μικρότερο από τα δύο. Το clamp καλύπτει μόνο TCP, οπότε ένα υπερμέγεθες datagram UDP πρέπει να κατατμηθεί ή να απορριφθεί από τον κώδικά σας.

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

Τρεις μετρήσεις που χωρίζουν τις περιπτώσεις

Δεν χρειάζεστε debugger για καμία από τις δύο αποτυχίες. Τρεις παρατηρήσεις αρκούν για να κατευθυνθείτε στο σωστό τμήμα, και παίρνουν λιγότερο χρόνο από ένα rebuild.

What you seeWhere to look
Reader parked, peer send queue empty, last advertised window zeroWindow updates in your ACK path
Write fails on one length and succeeds on a smaller oneMSS clamp and tunnel MTU
Both directions look fine, app still sees no dataProtected socket and routing loop

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

Διορθώστε την υδραυλική από την οποία εξαρτώνται και οι δύο διαδρομές

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

Builder()
  .addAddress(10.8.0.2, 32)
  .addRoute(0.0.0.0, 0)
  .setMtu(1400)
  .establish()

Ορίστε το MTU στην πραγματική τιμή της σήραγγας αντί να το αφήσετε σε ό,τι χρησιμοποίησε το παράδειγμα. Ό,τι γράψετε αργότερα περιορίζεται από αυτόν τον αριθμό, και η αναντιστοιχία μεταξύ του MTU που ορίζετε και του MSS στο οποίο κάνετε clamp είναι ο πιο συνηθισμένος τρόπος να επαναφέρετε το σφάλμα εγγραφής μετά από μια διόρθωση. Μετά προστατέψτε το socket που μεταφέρει κίνηση στον ίδιο τον διακομιστή VPN. Χωρίς protect, αυτά τα πακέτα δρομολογούνται πίσω στη δική σας σήραγγα και η σύνδεση δεν ολοκληρώνεται ποτέ, κάτι που μοιάζει με κολλημένη ανάγνωση αλλά είναι βρόχος δρομολόγησης.

Η συγκατάθεση δεν παρακάμπτεται. Μια υπηρεσία που ξεκινά χωρίς το prepare να επιστρέφει null δεν παίρνει διεπαφή, και το establish επιστρέφει null αντί να πετάξει, οπότε ελέγξτε τον περιγραφέα πριν ξεκινήσετε τον βρόχο. Η αναφορά VpnService είναι το μέρος για να επιβεβαιώσετε το συμβόλαιο του builder αντί να αντιγράψετε ένα παράδειγμα.

Επαληθεύστε μία κατεύθυνση τη φορά

Στείλτε ένα UDP echo μέσα από τη σήραγγα και παρακολουθήστε τα μήκη των πακέτων πριν αγγίξετε το TCP. Μετά ανοίξτε ένα handshake και επιβεβαιώστε ότι τα τμήματα μένουν κάτω από το MSS που υπολογίσατε. Μετρήστε πακέτα αντί για συνδέσεις, ώστε ένα κόλλημα να εμφανιστεί ως κενό στον μετρητή αντί για κολλημένο socket. Αν οι αναγνώσεις σταματούν μετά από έναν καταιγισμό εγγραφών, το παράθυρο είναι ο ύποπτος· αν οι εγγραφές αποτυγχάνουν αμέσως, είναι το μήκος.

Μετά επανεκκινήστε τη συσκευή και επαναλάβετε μία φορά, γιατί ένα VpnService που λειτουργεί μόνο μέχρι την πρώτη αλλαγή δικτύου είναι πρόβλημα δρομολόγησης και όχι ελέγχου ροής.

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