Tunelul urcă, pictograma cheii apare, iar fiecare cerere din browser expiră în continuare. Într-o aplicație VpnService această combinație înseamnă de obicei că bucla de citire este vie și jumătatea de ieșire este stricată, nu că serverul a respins handshake-ul. Android a creat deja interfața, a mutat traficul aplicației în ea și a dat procesului dumneavoastră un descriptor de fișier. Tot ce urmează după acel descriptor este codul dumneavoastră, iar un singur apel lipsă este suficient pentru a înghiți tot fluxul.

Ce vă dă sistemul și ce nu

establish() returnează un ParcelFileDescriptor pentru o interfață tun. Sistemul scrie pachetele IP de ieșire în ea și se așteaptă ca procesul dumneavoastră să le citească, să decidă unde merg și să le trimită printr-un socket obișnuit. Dedesubt nu există redirecționare în kernel, așa că atunci când nimeni nu citește pachetele sunt aruncate și contoarele interfeței nu se mișcă niciodată.

Începeți cu numărarea în loc de presupuneri. Incrementați un contor la fiecare citire reușită și înregistrați destinația și protocolul primelor pachete. Dacă contorul rămâne la zero în timp ce se generează trafic, defectul este deasupra codului de redirecționare, ceea ce restrânge căutarea la configurația builder-ului și la modul de citire. Dacă contorul se mișcă și browserul eșuează în continuare, defectul este pe calea de trimitere sau pe cea de întoarcere.

Citirea blocantă decide dacă bucla se mișcă

setBlocking(true) pune descriptorul în modul blocant și există din API 21. În versiunile vechi soluția este un mic ajutor JNI care șterge O_NONBLOCK cu fcntl pe descriptorul brut. Modul contează pentru că decide ce are voie să facă o citire fără niciun pachet în așteptare.

În modul neblocant acea citire se întoarce imediat cu o valoare negativă, iar o buclă care ignoră rezultatul învârte un nucleu fără să miște niciun trafic. În modul blocant citirea așteaptă, ceea ce vrea un fir de redirecționare, dar firul nu mai poate fi întrerupt. Închiderea descriptorului este modalitatea acceptată de a face citirea să eșueze ca bucla să poată ieși. Oprirea procesului sau întreruperea firului poate lăsa pornirea următoare cu un descriptor care nu mai corespunde interfeței.

Citiți un pachet pe apel într-un buffer dimensionat pentru MTU-ul interfeței, și tratați zero sau un rezultat negativ ca eroare, nu ca pachet gol.

protect() înainte de prima trimitere, nu după

Fiecare socket care transportă un pachet încapsulat trebuie protejat mai întâi. Un socket neprotejat respectă aceleași reguli de rutare pe care tocmai le-ați instalat, așa că pachetele lui se întorc în tunel și propria buclă de citire le ridică din nou. Tunelul redirecționează apoi propria ieșire la nesfârșit, iar din exterior arată ca o conexiune care se deschide și nu răspunde niciodată.

Ordinea este protect, apoi connect sau bind, apoi send. Apelarea protect după connect poate lăsa în picioare decizia anterioară de rută, așa că socketul continuă să folosească calea greșită chiar dacă apelul a returnat true. Verificați booleanul returnat și eșuați zgomotos când este false, în loc să intrați într-o trimitere care nu poate funcționa.

O eroare de permisiune de la sendto pe un socket care nu a atins niciodată protect() este semnătura obișnuită a acestei greșeli.

Exemplul UDP nu transportă HTTPS

Exemplul de redirecționare pe care îl copiază majoritatea tutorialelor tratează UDP și nimic altceva. TCP are nevoie de handshake, numere de secvență, confirmări și retransmisie, iar o buclă care copiază încărcătura între un descriptor tun și un socket nu oferă niciuna dintre ele. De aceea o interogare DNS prin UDP poate trece în timp ce fiecare cerere HTTPS se blochează. Fie rulați un stivă TCP în spațiul utilizatorului, fie dați conexiunile unui proxy local care termină TCP pe dispozitiv și redirecționează fluxul.

Testați separarea înainte de a rescrie ceva: trimiteți o interogare UDP și deschideți o conexiune TCP către același host. Dacă calea UDP funcționează și calea TCP se blochează la primul SYN, problema este codul de redirecționare, nu serverul. Acea singură comparație economisește mult timp petrecut citind jurnale de server care nu conțin nimic util.

Lista rutelor și filtrele pe aplicație limitează ce vedeți

addAddress, addRoute și addDns definesc ce trimite sistemul în tunel. O destinație din afara listei de rute nu ajunge niciodată la descriptorul dumneavoastră, așa că nicio cantitate de logică de redirecționare nu o va repara. Înregistrați apelurile builder pe care le faceți și comparați-le cu adresele pe care aplicația încearcă cu adevărat să le atingă.

Selecția pe aplicație are propriul prag: addAllowedApplication și addDisallowedApplication necesită API 21, iar în versiunile vechi metodele pur și simplu lipsesc. Un design care filtrează după pachet nu poate rula deloc acolo.

Restabilirea lasă descriptori învechiți

Un nou apel la establish() returnează un descriptor nou și închide interfața anterioară. Descriptorul vechi este acum invalid, așa că un fir încă blocat pe el primește o eroare, iar socketurile create pentru calea veche pot trebui înlocuite. onRevoke este apelul invers pentru un tunel pe care utilizatorul sau sistemul l-au retras. Păstrarea ciclului de viață la un singur proprietar, care oprește cititorul înainte de restabilire, evită majoritatea stărilor conectat-dar-mort.

Citiți fiecare eroare după pasul căruia îi aparține. O resetare a conexiunii de la recvfrom înseamnă că capătul îndepărtat s-a închis în timp ce codul dumneavoastră încă citea. O eroare de adresă greșită indică parsarea antetului sau un bug de offset, nu rețeaua.

Ordinea lucrului: mod blocant pornit, un pachet pe citire, antet analizat, socket protejat, încărcătură trimisă, închidere curată la predare și o listă de rute care acoperă destinația.

Articole similare