May dalawang paraan para manatiling labas ng tunnel ang sariling mga packet nito: protektahan ang transport socket, o ilabas ang buong app mula sa VPN. Iba-ibang problema ang nilulutas ng mga ito, at ang pagpili sa mas malawak dahil mas madaling ikabit ay siya mismong dahilan kung bakit nawawala nang hindi namamalayan ang access sa lokal na network at split DNS. Magsimula sa kung ano ang dapat manatiling labas ng tunnel, pagkatapos piliin ang pinakamaliit na saklaw na nakakamit nito.

Ano ang inaalis ng protect() sa tunnel

VpnService.protect() ay isang tawag sa bawat socket. Ibibigay mo ang socket o descriptor na nagdadala ng tunnel, at minamarkahan ng Android ang socket na iyon para gamitin ng trapiko nito ang underlying network sa halip na ang VPN interface. Lahat ng iba pang ipinapadala ng app ay sumusunod pa rin sa normal na ruta ng VPN.

Ang makitid na saklaw na iyon ang punto. Hindi binabago ng protektadong socket kung paano nireresolba ng HTTP client mo ang mga pangalan, kung aling interface ang ginagamit ng mga analytics call mo, o ano ang nangyayari sa pangalawang koneksyon na bubuksan mamaya.

Tatlong kondisyon na nagtatakda ng saklaw

Ang unang kondisyon ay kung tanging transport ng tunnel lang ang kailangang makalabas. Kung ang iba pang trapiko ng app ay dapat pa ring dumaan sa VPN, ang proteksyon ng socket ang tamang saklaw. Ang ikalawang kondisyon ay kung ikaw ang may kontrol sa paglikha ng socket. Ang isang library na nagbubukas ng sarili nitong socket nang malalim sa loob ay hindi nagbibigay sa iyo ng lugar para tawagin ang protect() maliban kung naglalantad ito ng hook o tumatanggap ng built na socket. Ang ikatlong kondisyon ay kung kailangang maabot ng app nang direkta ang lokal na network o isang captive portal, na ibang pangangailangan kaysa sa pagpapanatiling buhay ang tunnel.

Ang pagbubukod ng app gamit ang addDisallowedApplication() ang blunt na kasangkapan. Inaalis nito sa VPN ang bawat koneksyong ginagawa ng app, kabilang ang mga gusto mong siyasatin, at sa ilang device binabago rin nito ang pag-uugali ng lokal na pagtuklas.

Ang ikatlong opsyon ay igapos ang transport sa piniling network gamit ang bindProcessToNetwork() o isang network handle sa antas ng socket. Kapaki-pakinabang ito kapag may Wi-Fi at cellular ang device nang sabay at gusto mong dumaan ang tunnel sa isang partikular na uplink.

Tawagin ito habang ginagawa ang transport

Tawagin ang protect() sa parehong block na gumagawa ng transport, bago ang connect() o send(). Kung magbalik ito ng false, isara ang socket at hayaang mabigo ang setup; ang pagpapatuloy ay mag-iiwan sa iyo ng transport na ang mga packet ay maaaring muling pumasok sa sarili mong interface.

Sapat na maliit ang hugis para manatili sa isang lugar:

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

Tatlong detalye ang kumakagat dito. Una, ang boolean ang tanging senyas na nakukuha mo, kaya i-log ang pagkabigo nang may sapat na konteksto para makilala ang binawing pahintulot sa isang tinanggihang tawag. Ikalawa, sinasaklaw ng protect() ang socket, hindi ang numero ng descriptor: kung isasara mo ang socket at gagamitin mamaya ang parehong integer para sa ibang socket, hindi protektado ang bagong socket. Ikatlo, kapag binubuksan ng native library ang transport, protektahan ang descriptor bago magkonekta ang native code, kung hindi ay maaaring napili na ang ruta. Ang VpnService reference ang lugar para kumpirmahin kung aling mga overload ang aktwal na ibinibigay ng target API level mo.

Subukan ang naharang na ruta, hindi lang ang maayos

I-verify habang naka-up ang tunnel, dahil iyon lang ang estado kung saan maaaring magkaiba ang dalawang ruta. Magpadala ng isang request sa protektadong transport at isa mula sa code path na hindi kailanman tumawag ng protect(), pagkatapos ihambing ang nakikita ng dulong dulo: ang protektadong koneksyon ay dapat dumating na ang underlying network ang pinagmulan, samantalang ang hindi protektado ay nagpapakita ng sariling address ng tunnel at gumagalaw sa mga byte counter nito. Sa device, ipinapakita ng ss o netstat ang parehong paghahati bago may lumabas sa radyo.

Subukan nang hiwalay ang pagbawi, dahil binabagsak ng pagbabago ng pahintulot ang interface nang hindi hinahawakan ang code ng socket mo. Bawiin ang pahintulot sa VPN o ihinto ang serbisyo, buuin muli ang transport, at kumpirmahin na nagbabalik pa rin ng true ang protect() at na muling kumokonekta ang transport kapag naibigay muli ang pahintulot.

Kung sinusundan pa rin ng protektadong request ang tunnel, malamang nangyayari ang tawag sa proteksyon pagkatapos nang ma-ruta ang socket. Ilipat ito nang mas maaga at subukan muli.

Itago ang desisyon na nakasulat sa tabi ng setup ng transport: aling mga socket ang protektado, aling trapiko ng app ang ibinukod, at ano ang ginagawa ng app kapag nabigo ang proteksyon. Ang linyang iyon ang pumipigil sa isang susunod na refactor na palawakin ang pagbubukod sa buong app dahil isang pagsubok lang ang nabibigo. Nagbibigay din ito sa susunod na tao ng paraan para suriin ang layunin nang hindi nire-reverse-engineer ang manifest.

Kaugnay na mga artikulo