Nimic din VpnService nu interceptează trafic de unul singur. Cere sistemului o interfață tun, îi dă acelei interfețe un set de adrese și rute, iar din acel moment nucleul copiază fiecare pachet care corespunde acelor rute într-un descriptor de fișier pe care aplicația îl poate citi. Codul aplicației nu vede niciodată pachetele pe care rutele nu le-au selectat, așa că domeniul unui tunel este o întrebare despre tabela de rutare înainte de a fi o întrebare de programare. De aceea aceeași implementare a serviciului poate înghiți fiecare flux pe un telefon și doar o parte pe altul, și de aceea adăugarea unei rute repară uneori un tunel care părea rupt. Primești exact ce ai declarat, minus tot ce constructorul a refuzat în tăcere.

Rutele decid ce pachete ajung

Builder.addRoute(prefix, prefixLength) instalează un prefix în tabela de rutare a rețelei VPN. Sistemul tratează acea rețea ca implicită pentru aplicațiile din domeniu, așa că intrările ei sunt consultate înaintea rutei implicite a rețelei fizice, iar un prefix potrivit este suficient pentru a devia un flux. De aceea o rută pentru 0.0.0.0/0 ia IPv4 în întregime, inclusiv imprimanta, ținta de cast și pagina de administrare a routerului.

Clienții care vor această acoperire fără a șterge intrarea implicită fizică folosesc în schimb perechea 0.0.0.0/1 și 128.0.0.0/1. Două rute mai specifice înving o rută implicită, împreună acoperă tot spațiul de adrese, iar intrarea implicită a telefonului rămâne în tabelă pentru socketurile încă legate de rețeaua fizică.

Adresele și înregistrările DNS vin de la același constructor și răspund la altă întrebare. addAddress dă interfeței o adresă cu o lungime de prefix, iar addDnsServer instalează un resolver pe care aplicațiile din domeniu îl vor întreba prin interfață. Ambele familii pot sta pe o interfață, așa că un set de rute doar IPv4 lângă o adresă IPv6 este o configurație care se citește corect și totuși pierde. Dacă rutați o familie în care interfața nu are adresă, nucleul nu are adresă sursă pentru acele pachete și ele nu apar niciodată la descriptor.

Ce returnează establish()

establish() returnează un ParcelFileDescriptor, sau null când aplicația nu este autorizată. Null este o stare de consimțământ, nu o eroare de rețea: VpnService.prepare() trebuie să fi returnat deja null, sau utilizatorul trebuie să fi aprobat dialogul sistemului înainte de apel. Consimțământul poate fi retras în timp ce serviciul rulează, iar interfața se închide sub picioarele tale fără niciun callback în bucla ta de citire.

Descriptorul nu blochează decât dacă spui altfel, așa că o citire revine imediat fără date în loc să parcheze firul. O buclă scrisă pentru un socket blocant arde un nucleu, iar o buclă care tratează o citire goală ca fatală rupe tunelul într-o secundă inactivă. Builder.setBlocking(true) schimbă acel comportament, iar prețul este un fir care așteaptă în interiorul citirii.

Argumentele de rută și adresă sunt validate ca valori LinkAddress, iar destinațiile multicast precum 224.0.0.0/4 sunt respinse din start. O interfață tun punct-la-punct nu are nici domeniu de broadcast, așa că descoperirea multicast prin ea cere alt design în loc de încă o rută. Referința VpnService.Builder este lista de verificat înainte de a concluziona că un apel a fost ignorat.

Domeniul pe aplicație îngustează setul, nu tunelul

De la API 21 constructorul acceptă addAllowedApplication și addDisallowedApplication, iar cele două liste se exclud reciproc într-un singur constructor. O listă de permise face tunelul să existe pentru acele pachete în timp ce tot restul păstrează rețeaua fizică; o listă de interzise prinde tot cu excepția pachetelor numite, ceea ce un client oferă ca tunelare divizată. Înainte de API 21 nu există niciun mecanism de platformă, iar comportamentul pe aplicație venea de la clienți terți care filtrau după UID în propriul redirector. Diferența contează la depanare, pentru că un tunel pe aplicație cu o listă de permise de un singur pachet aproape nu produce trafic și acesta este comportament corect, nu o rută ruptă.

Orice ar fi domeniul, socketul pe care aplicația ta îl deschide către serverul VPN nu trebuie să treacă prin tunelul pe care îl construiește. VpnService.protect(socket) leagă acel socket de rețeaua de bază, astfel încât pachetele lui să ocolească dispozitivul tun. Dacă sari apelul, strângerea de mână este înghițită de interfața care ar fi trebuit să o ridice.

VpnService.setUnderlyingNetworks închide bucla spunând sistemului ce rețea fizică poartă tunelul. Acea valoare determină cum raportează VPN-ul conectivitatea celorlalte aplicații, iar o valoare null sau învechită este o cauză obișnuită a unui tunel conectat pe care restul telefonului îl tratează ca offline. Actualizeaz-o când se schimbă rețeaua de bază, nu doar la pornire.

Sume de control și MTU: unde o rută corectă tot eșuează

După ce pachetele ajung, două detalii la nivel de pachet decid dacă ceva funcționează de la un capăt la altul. Primul este adresa sursă: cu mai multe adrese de interfață nucleul poate emite pachete care o poartă pe prima, așa că un redirector care rescrie antetele trebuie să corecteze adresa și suma de control care o acoperă.

Al doilea este domeniul sumei de control. Când aplicația construiește ea însăși pachete TCP sau UDP, suma de control acoperă un pseudo-antet care ține adresa sursă, adresa destinație, numărul protocolului și lungimea transportului, iar omiterea oricăruia dintre ele dă pachete care ajung și sunt aruncate. Eșecul arată ca un timeout după un răspuns care nu vine niciodată, ceea ce trimite majoritatea oamenilor înapoi la rute. setMtu fixează MTU-ul interfeței, iar fiecare citire returnează exact un pachet IP complet, așa că un buffer scris pentru un flux de octeți va fragmenta analiza ta, nu pachetul.

O rundă de verificare care separă straturile

Ca să găsești unde a greșit domeniul, testează straturile în ordine. Întreabă dispozitivul ce rute ține într-adevăr rețeaua VPN, apoi verifică dacă un flux către o adresă cunoscută apare ca o citire la descriptor, apoi verifică dacă pachetul pe care redirectorul tău îl emite este bine format. O problemă de rută nu arată nimic la descriptor, în timp ce o problemă de sumă de control arată un pachet care ajunge și dispare.

Dacă nimic nu ajunge la descriptor, suspect este setul de rute, nu cititorul: confirmă prefixele, confirmă că există o adresă în aceeași familie și confirmă că aplicația este în domeniul pe care l-ai stabilit. Dacă pachetele ajung la descriptor dar nimic nu se întoarce, inspectează protect() și valoarea rețelei de bază înainte de a atinge din nou o rută. Dacă partea îndepărtată raportează pachete malformate, suma de control și MTU-ul sunt locurile de privit. O sondă pe strat economisește o după-amiază de presupuneri.

Articole similare