Android VpnService ferbynt, mar gjin pakketten berikke it netwurk
Tunnel omheech en siden mislearje noch: kontrolearje de lêsmodus, socket-beskerming, TCP-stipe en de rûtelist foardat jo de VPN-tsjinner de skuld jouwe.
De tunnel giet omheech, it kaai-ikoan ferskynt, en elk fersyk yn de browser rint noch altyd op 'e tiid. Yn in VpnService-app betsjut dy kombinaasje meast dat de lêslus libbet en de útgongshalf stikken is, net dat de tsjinner de hânskodding wegere hat. Android hat de ynterface al makke, it ferkear fan de app deryn ferpleatst en jo proses in triemdeskriptor jûn. Alles nei dy deskriptor is jo koade, en ien ûntbrekkende oprop is genôch om de hiele stream te ferswolgjen.
Wat it systeem jo jout, en wat net
establish() jout in ParcelFileDescriptor foar in tun-ynterface. It systeem skriuwt útgong-IP-pakketten deryn en ferwachtet dat jo proses se lêst, beslút wêr't se hinne geane en se troch in gewoane socket stjoert. Dêrûnder is gjin ferstjoering yn 'e kearn, dus as nimmen lêst, wurde de pakketten fuortsmiten en bewege de tellers fan 'e ynterface har nea.
Begjin mei telle ynstee fan rieden. Fergrutsje in teller by elk slagge lêzen en registrearje it bestimming en protokol fan 'e earste pakketten. As de teller op nul bliuwt wylst ferkear makke wurdt, sit de flater boppe jo ferstjoerkoade, wat it sykjen beheint ta de builder-konfiguraasje en de lêsmodus. As de teller beweecht en de browser mislearret noch, sit de flater yn 'e stjoerrûte of op 'e weromwei.
Blokkerend lêzen beslút oft de lus beweecht
setBlocking(true) set de deskriptor yn blokkerende modus en bestiet sûnt API 21. Op âldere ferzjes is de oplossing in lyts JNI-helper dy't O_NONBLOCK mei fcntl op 'e rauwe deskriptor wisket. De modus is wichtich, want hy beslút wat in lêzen sûnder wachtsjend pakket dwaan mei.
Yn net-blokkerende modus komt dat lêzen daliks werom mei in negative wearde, en in lus dy't it resultaat negearret draait in kearn sûnder hokker ferkear dan ek te ferpleatsen. Yn blokkerende modus wachtet it lêzen, dat is wat in ferstjoerthread wol, mar de thread kin dan net mear ûnderbrutsen wurde. It sluten fan de deskriptor is de stipe manier om it lêzen mislearje te litten, sadat de lus fuort kin. It proses deadzje of de thread ûnderbrekke kin de folgjende start mei in deskriptor efterlitte dy't net mear by de ynterface past.
Lês ien pakket per oprop yn in buffer fan 'e grutte fan 'e MTU fan 'e ynterface, en behannelje nul of in negative weromjefte as in flater, net as in leech pakket.
protect() foar de earste stjoering, net dêrnei
Eltse socket dy't in ynkapsulearre pakket draacht moat earst beskerme wurde. In net-beskerme socket folget deselde rûtelregels dy't jo krekt ynstallearen, dus syn pakketten geane werom yn 'e tunnel en jo eigen lêslus pakt se wer op. Dan ferstjoert de tunnel syn eigen útgong einleas, en fan bûten liket it op in ferbining dy't iepenet en nea antwurdet.
De folchoarder is protect, dan connect of bind, dan send. protect oanroppe nei connect kin it eardere rûtbeslút stean litte, dus de socket bliuwt it ferkearde paad brûken ek al joech de oprop true werom. Kontrolearje de boolean dy't er weromjout en mislearje lûd as er false is, ynstee fan in stjoering yn te gean dy't net wurkje kin.
In tastimmingflater fan sendto op in socket dy't nea protect() oanrekke hat, is it gewoane sinjaal fan dizze flater.
It UDP-foarbyld draacht gjin HTTPS
It ferstjoerfoarbyld dat de measte tutorials kopiearje behannelet UDP en fierders neat. TCP hat in hânskodding, folchnûmers, befêstigingen en werstjoering nedich, en in lus dy't ladingen tusken in tun-deskriptor en in socket kopiearret jout dêr neat fan. Dêrom kin in UDP-DNS-fraach trochkomme wylst elk HTTPS-fersyk fêstrint. Rin in TCP-stapel yn brûkersromte of jou de ferbiningen oan in lokale proxy dy't TCP op it apparaat beëiniget en de stream ferstjoert.
Test de splitsing foardat jo wat dan ek opnij skriuwe: stjoer ien UDP-fraach en iepenje ien TCP-ferbining nei deselde host. As it UDP-paad wurket en it TCP-paad by de earste SYN hinget, is de ferstjoerkoade it probleem en net de tsjinner. Dy iene fergeliking sparret in soad tiid dy't oan tsjinnerlochs bestege wurdt dy't neat nuttich befetsje.
Rûtelist en filters per app beheine wat jo sjogge
addAddress, addRoute en addDns bepale wat it systeem yn 'e tunnel stjoert. In bestimming bûten de rûtelist berikt jo deskriptor nea, dus gjin inkelde hoemannichte ferstjoerlogika sil dat better meitsje. Registrearje de builder-oproppen dy't jo dogge en fergelykje se mei de adressen dy't de app echt besiket te berikken.
Kar per app hat syn eigen flier: addAllowedApplication en addDisallowedApplication hawwe API 21 nedich, en op âldere ferzjes ûntbrekke dy metoaden gewoan. In ûntwerp dat op pakket filteret kin dêr hielendal net rinne.
Weropbou lit ferâldere deskriptors efter
In nije oprop fan establish() jout in nije deskriptor en slút de foarige ynterface. De âlde deskriptor is no ûnjildich, dus in thread dy't dêrop noch blokkearret krijget in flater, en sockets makke foar it âlde paad moatte miskien ferfongen wurde. onRevoke is de callback foar in tunnel dy't de brûker of it systeem fuortnommen hat. It libbenssyklus by ien eigner hâlde, dy't de lêzer stopet foar't wer opboud wurdt, foarkomt de measte ferbûn-mar-dea-tastannen.
Lês elke flater neffens de stap dêr't er by heart. In ferbiningsreset fan recvfrom betsjut dat it fiere ein sletten is wylst jo koade noch lies. In flater oer in ferkeard adres wiist op koptekst-parsing of in offset-bug, net op it netwurk.
Wurkfolchoarder: blokkerende modus oan, ien pakket per lêzen, koptekst parsd, socket beskerme, lading ferstjoerd, skjin sluten by oerdracht en in rûtelist dy't de bestimming dekt.