Tunelio pačių paketų laikymui už tunelio ribų yra du būdai: apsaugoti transporto socket arba išimti visą programą iš VPN. Jie sprendžia skirtingas problemas, o platesnio pasirinkimas todėl, kad jį lengviau prijungti, yra būtent tas kelias, kuriuo programos nepastebėdamos praranda prieigą prie vietinio tinklo ir padalintą DNS. Pradėkite nuo to, kas turi likti už tunelio, tada pasirinkite mažiausią apimtį, kuri tai pasiekia.

Ką protect() pašalina iš tunelio

VpnService.protect() yra iškvietimas vienam socket. Jūs perduodate jam tunelį nešantį socket arba deskriptorių, ir Android pažymi tą socket, kad jo srautas naudotų po juo esantį tinklą, o ne VPN sąsają. Visa kita, ką siunčia programa, vis dar eina įprastu VPN maršrutu.

Būtent ši siaura apimtis ir yra esmė. Apsaugotas socket nekeičia, kaip jūsų HTTP klientas sprendžia vardus, kurią sąsają naudoja analitikos iškvietimai ar kas nutinka vėliau atidarytam antrajam ryšiui.

Trys sąlygos, nusakančios apimtį

Pirmoji sąlyga – ar turi išeiti tik tunelio transportas. Jei likęs programos srautas turi ir toliau eiti per VPN, socket apsauga yra tinkama apimtis. Antroji sąlyga – ar jūs valdote socket sukūrimą. Biblioteka, kuri atidaro savo socket giliai viduje, nepalieka jums vietos iškviesti protect(), nebent ji atskleidžia hook arba priima jau sukurtą socket. Trečioji sąlyga – ar programa turi tiesiogiai pasiekti vietinį tinklą ar captive portal, o tai yra kitas reikalavimas nei palaikyti tunelį gyvą.

Programos išskyrimas su addDisallowedApplication() yra grubus įrankis. Jis pašalina iš VPN kiekvieną programos ryšį, įskaitant tuos, kuriuos norėjote stebėti, o kai kuriuose įrenginiuose pakeičia ir vietinio aptikimo elgseną.

Trečias variantas – susieti transportą su pasirinktu tinklu naudojant bindProcessToNetwork() arba tinklo rankenėlę socket lygmenyje. Tai naudinga, kai įrenginys vienu metu turi Wi-Fi ir mobilųjį tinklą, o jūs norite, kad tunelis eitų konkrečiu išėjimo kanalu.

Iškvieskite jį, kai transportas kuriamas

Iškvieskite protect() tame pačiame bloke, kuris kuria transportą, prieš connect() arba send(). Jei iškvietimas grąžina false, uždarykite socket ir nepavykite sąrankos; tęsiant lieka transportas, kurio paketai gali vėl patekti į jūsų pačių sąsają.

Forma pakankamai maža, kad tilptų vienoje vietoje:

DatagramSocket transport = new DatagramSocket();
if (!protect(transport)) {
    transport.close();
    throw new IOException("transport not protected");
}
transport.connect(serverAddress);

Trys detalės čia kandžiojasi. Pirma,būlio reikšmė yra vienintelis signalas, kurį gaunate, todėl registruokite nesėkmę su pakankamai konteksto, kad atskirtumėte atšauktą sutikimą nuo atmesto iškvietimo. Antra,protect() apima socket, o ne deskriptoriaus numerį: jei uždarysite socket ir vėliau tą patį sveikąjį skaičių panaudosite kitam socket, naujasis socket neapsaugotas. Trečia, kai native biblioteka atidaro transportą, apsaugokite deskriptorių prieš native kodui prisijungiant, kitaip maršrutas gali būti jau pasirinktas.VpnService žinynas yra vieta, kur patvirtinate, kurias perkrovas jūsų tikslinis API lygis iš tikrųjų teikia.

Tikrinkite užblokuotą kelią, o ne tik laimingąjį

Tikrinkite su pakeltu tuneliu, nes tai vienintelė būsena, kurioje du keliai gali skirtis. Nusiųskite vieną užklausą apsaugotu transportu, o kitą – kodo keliu, kuris niekada neiškvietė protect(), tada palyginkite, ką mato tolimas galas: apsaugotas ryšys turėtų atvykti su po juo esančiu tinklu kaip šaltiniu, o neapsaugotas rodo paties tunelio adresą ir judina tunelio baitų skaitiklius. Įrenginyje ss arba netstat rodo tą patį atskyrimą dar prieš ką nors išsiunčiant per radiją.

Atšaukimą tikrinkite atskirai, nes sutikimo pokytis sugriauna sąsają neliesdamas jūsų socket kodo. Atšaukite VPN sutikimą arba sustabdykite paslaugą, iš naujo sukurkite transportą ir patvirtinkite, kad protect() vis dar grąžina true ir kad transportas vėl prisijungia, kai sutikimas vėl suteikiamas.

Jei apsaugota užklausa vis tiek eina per tunelį, apsaugos iškvietimas greičiausiai įvyksta po to, kai socket jau maršrutizuotas. Perkelkite jį anksčiau ir tikrinkite dar kartą.

Laikykite sprendimą užrašytą šalia transporto sąrankos: kurie socket apsaugoti, kuris programos srautas išskirtas ir ką programa daro, kai apsauga nepavyksta. Būtent ši eilutė neleidžia vėlesniam pertvarkymui išplėsti išskyrimo visai programai todėl, kad vienas testas nepavykdavo. Ji taip pat duoda kitam žmogui būdą patikrinti ketinimą neatliekant atvirkštinės manifest inžinerijos.

Susiję straipsniai