Android VpnService konektiĝas sed neniuj pakaĵoj atingas la reton
Tunelo supren kaj paĝoj ankoraŭ malsukcesas: kontrolu la legreĝimon, ŝirmon de la ingo, subtenon de TCP kaj la itinerliston antaŭ ol kulpigi la VPN-servilon.
La tunelo supreniras, la ŝlosila piktogramo aperas, kaj ĉiu peto en la retumilo ankoraŭ finiĝas per tempolimo. En VpnService-a aplikaĵo tiu kombinaĵo kutime signifas, ke la legbuklo vivas kaj la eliga duono estas rompita, ne ke la servilo rifuzis la manpremon. Android jam kreis la interfacon, movis la aplikaĵan trafikon en ĝin kaj transdonis al via procezo dosierpriskribilon. Ĉio post tiu priskribilo estas via kodo, kaj unu mankanta voko sufiĉas por engluti la tutan fluon.
Kion la sistemo donas al vi, kaj kion ne
establish() redonas ParcelFileDescriptor por tun-interfaco. La sistemo skribas elirantajn IP-pakaĵojn en ĝin kaj atendas, ke via procezo legu ilin, decidu kien ili iras kaj sendu ilin tra ordinara ingo. Sube ne estas kerno-plusendo, do kiam neniu legas, la pakaĵoj estas forĵetataj kaj la fulmmoj de la interfaco neniam moviĝas.
Komencu per nombrado anstataŭ divenado. Pliigu nombrilon ĉe ĉiu sukcesa legado kaj registru la celon kaj protokolon de la unuaj pakaĵoj. Se la nombrilo restas nulo dum trafiko estas generata, la fuŝo sidas supre de via plusenda kodo, kio mallarĝigas la serĉon al la konstruila agordo kaj la legreĝimo. Se la nombrilo moviĝas kaj la retumilo ankoraŭ malsukcesas, la fuŝo estas en la senda vojo aŭ en la revena vojo.
Blokanta legado decidas ĉu la buklo moviĝas
setBlocking(true) metas la priskribilon en blokantan reĝimon kaj ekzistas ekde API 21. Sur pli malnovaj eldonoj la solvo estas malgranda JNI-helpanto, kiu forigas O_NONBLOCK per fcntl sur la kruda priskribilo. La reĝimo gravas ĉar ĝi decidas, kion rajtas fari legado sen atendantaj pakaĵoj.
En neblokanta reĝimo tiu legado tuj revenas kun negativa valoro, kaj buklo, kiu ignoras la rezulton, turnas kernon sen movi ajnan trafikon. En blokanta reĝimo la legado atendas, kion plusenda fadeno volas, sed la fadeno tiam ne plu povas esti interrompita. Fermi la priskribilon estas la subtenata maniero igi la legadon malsukcesi, por ke la buklo povu eliri. Mortigi la procezon aŭ interrompi la fadenon povas lasi la sekvan starton kun priskribilo, kiu ne plu kongruas kun la interfaco.
Legu unu pakaĵon po voko en bufron grandan laŭ la MTU de la interfaco, kaj traktu nulon aŭ negativan revenon kiel eraron, ne kiel malplenan pakaĵon.
protect() antaŭ la unua sendo, ne post ĝi
Ĉiu ingo, kiu portas enkapsuligitan pakaĵon, unue devas esti ŝirmita. Neŝirmita ingo obeas la samajn itinerajn regulojn, kiujn vi ĵus starigis, do ĝiaj pakaĵoj reiras en la tunelon kaj via propra legbuklo rekaptas ilin. La tunelo tiam plusendas sian propran eligon senfine, kaj de ekstere ĝi aspektas kiel konekto, kiu malfermiĝas kaj neniam respondas.
La ordo estas protect, poste connect aŭ bind, poste send. Voki protect post connect povas lasi la pli fruan itinerdecidon, do la ingo daŭre uzas la malĝustan vojon eĉ kiam la voko redonis true. Kontrolu la boolean, kiun ĝi redonas, kaj laŭte malsukcesu kiam ĝi estas false, anstataŭ eniri sendon, kiu ne povas funkcii.
Permesa eraro de sendto sur ingo, kiu neniam tuŝis protect(), estas la kutima signo de ĉi tiu eraro.
La UDP-ekzemplo ne portas HTTPS
La plusenda ekzemplo, kiun kopias plej multaj lerniloj, traktas UDP kaj nenion alian. TCP bezonas manpremon, sinsekvajn numerojn, konfirmojn kaj resendon, kaj buklo, kiu kopias ŝarĝojn inter tun-priskribilo kaj ingo, donas neniun el tiuj. Tial UDP-DNS-demando povas trapasi dum ĉiu HTTPS-peto haltas. Aŭ funkciigu TCP-stakon en uzanta spaco aŭ donu la konektojn al loka prokurilo, kiu finas TCP sur la aparato kaj plusendas la fluon.
Testu la dividon antaŭ ol reskribi ion: sendu unu UDP-demandon kaj malfermu unu TCP-konekton al la sama gastiganto. Se la UDP-vojo funkcias kaj la TCP-vojo pendas ĉe la unua SYN, la plusenda kodo estas la problemo kaj ne la servilo. Tiu unu komparo ŝparas multan tempon pasigitan legante servilajn protokolojn, kiuj enhavas nenion utilan.
Itinerlisto kaj po-aplikaĵaj filtriloj limigas, kion vi vidas
addAddress, addRoute kaj addDns difinas, kion la sistemo sendas en la tunelon. Celo ekster la itinerlisto neniam atingas vian priskribilon, do neniu kvanto da plusenda logiko riparos ĝin. Registru la konstruilajn vokojn, kiujn vi faras, kaj komparu ilin kun la adresoj, kiujn la aplikaĵo efektive provas atingi.
Po-aplikaĵa elekto havas sian propran minimumon: addAllowedApplication kaj addDisallowedApplication bezonas API 21, kaj sur pli malnovaj eldonoj la metodoj simple mankas. Dezajno, kiu filtras laŭ pakaĵo, tute ne povas funkcii tie.
Reestablo lasas malnovajn priskribilojn
Denova voko al establish() redonas novan priskribilon kaj fermas la antaŭan interfacon. La malnova priskribilo nun estas malvalida, do fadeno ankoraŭ blokita sur ĝi ricevas eraron, kaj ingoj kreitaj por la malnova vojo eble devas esti anstataŭigitaj. onRevoke estas la revoko por tunelo, kiun la uzanto aŭ la sistemo forprenis. Teni la vivciklon ĉe unu posedanto, kiu haltigas la leganton antaŭ ol reestabli, malhelpas plej multajn statojn konektita-sed-morta.
Legu ĉiun eraron laŭ la paŝo, al kiu ĝi apartenas. Restarigo de konekto de recvfrom signifas, ke la fora fino fermis dum via kodo ankoraŭ legis. Eraro pri malbona adreso montras al analizado de kaplinio aŭ cimo en ofseto, ne al la reto.
Ordo de laboro: blokanta reĝimo ŝaltita, unu pakaĵo po legado, kaplinio analizita, ingo ŝirmita, ŝarĝo sendita, pura fermo ĉe transdono kaj itinerlisto, kiu kovras la celon.