Android VpnService prisijungia, bet paketai nepasiekia tinklo
Tunelis pakeltas, o puslapiai vis dar nepavyksta: patikrinkite skaitymo režimą, socket apsaugą, TCP palaikymą ir maršrutų sąrašą prieš kaltinant VPN serverį.
Tunelis pakyla, atsiranda rakto piktograma, o kiekviena naršyklės užklausa vis tiek baigiasi skirtuoju laiku. VpnService programoje šis derinys paprastai reiškia, kad skaitymo ciklas gyvas, o išvesties pusė sulaužyta, o ne kad serveris atmetė rankos paspaudimą. Android jau sukūrė sąsają, perkėlė į ją programos srautą ir perdavė jūsų procesui failo deskriptorių. Viskas po to deskriptoriaus yra jūsų kodas, ir vieno trūkstamo kvietimo pakanka visam srautui praryti.
Ką sistema duoda, o ko ne
establish() grąžina ParcelFileDescriptor tun sąsajai. Sistema rašo į ją išeinančius IP paketus ir tikisi, kad jūsų procesas juos skaitys, nuspręs, kur jie keliauja, ir išsiųs per įprastą socket. Po apačia nėra branduolio persiuntimo, tad kai niekas neskaito, paketai išmetami, o sąsajos skaitikliai niekada nejuda.
Pradėkite skaičiuoti, o ne spėlioti. Didinkite skaitiklį po kiekvieno sėkmingo skaitymo ir registruokite pirmųjų paketų paskirties vietą bei protokolą. Jei skaitiklis lieka nulyje, kai srautas generuojamas, gedimas yra aukščiau jūsų persiuntimo kodo, o tai susiaurina paiešką iki builder konfigūracijos ir skaitymo režimo. Jei skaitiklis juda, o naršyklė vis tiek nepavyksta, gedimas yra siuntimo arba grįžimo kelyje.
Blokuojantis skaitymas nulemia, ar ciklas juda
setBlocking(true) perkelia deskriptorių į blokuojantį režimą ir egzistuoja nuo API 21. Senesnėse versijose sprendimas yra mažas JNI pagalbininkas, kuris fcntl pašalina O_NONBLOCK iš neapdoroto deskriptoriaus. Režimas svarbus, nes nulemia, ką gali daryti skaitymas be laukiančio paketo.
Neblokuojančiame režime tas skaitymas iškart grįžta su neigiama reikšme, o ciklas, kuris ignoruoja rezultatą, sukasi branduolį nejudindamas jokio srauto. Blokuojančiame režime skaitymas laukia, ko nori persiuntimo gija, bet tada gijos nebegalima pertraukti. Deskriptoriaus uždarymas yra palaikomas būdas priversti skaitymą nepavykti, kad ciklas galėtų išeiti. Proceso žudymas ar gijos pertraukimas gali palikti kitą paleidimą su deskriptoriumi, kuris nebeatitinka sąsajos.
Skaitykite vieną paketą per kvietimą į buferį, kurio dydis atitinka sąsajos MTU, o nulį ar neigiamą grąžinimą laikykite klaida, ne tuščiu paketu.
protect() prieš pirmą siuntimą, ne po jo
Kiekvienas socket, kuris neša inkapsuliuotą paketą, pirmiausia turi būti apsaugotas. Neapsaugotas socket paklūsta toms pačioms maršrutizavimo taisyklėms, kurias ką tik įdiegėte, todėl jo paketai grįžta į tunelį, ir jūsų pačių skaitymo ciklas juos vėl paima. Tada tunelis be galo persiunčia savo išvestį, ir iš išorės tai atrodo kaip ryšys, kuris atsidaro ir niekada neatsako.
Tvarka: protect, tada connect arba bind, tada send. protect po connect gali palikti ankstesnį maršruto sprendimą, todėl socket toliau naudoja neteisingą kelią, net jei kvietimas grąžino true. Patikrinkite grąžinamą boolean ir garsiai nepavykkite, kai jis false, užuot įėję į siuntimą, kuris negali veikti.
Leidimų klaida iš sendto socket, kuris niekada nelietė protect(), yra įprastas šios klaidos požymis.
UDP pavyzdys neperneša HTTPS
Persiuntimo pavyzdys, kurį kopijuoja dauguma mokymų, apdoroja tik UDP. TCP reikia rankos paspaudimo, sekos numerių, patvirtinimų ir pakartotinio siuntimo, o ciklas, kuris kopijuoja naudingąją apkrovą tarp tun deskriptoriaus ir socket, nė vieno to nepateikia. Todėl UDP DNS užklausa gali praeiti, kol kiekviena HTTPS užklausa sustoja. Arba paleiskite TCP steką vartotojo erdvėje, arba perduokite ryšius vietiniam proxy, kuris užbaigia TCP įrenginyje ir persiunčia srautą.
Išbandykite atskyrimą prieš ką nors perrašydami: nusiųskite vieną UDP užklausą ir atidarykite vieną TCP ryšį tam pačiam hostui. Jei UDP kelias veikia, o TCP kelias stringa ties pirmu SYN, problema yra persiuntimo kode, o ne serveryje. Tas vienas palyginimas sutaupo daug laiko, praleisto skaitant serverio žurnalus, kuriuose nėra nieko naudingo.
Maršrutų sąrašas ir filtrai pagal programą riboja, ką matote
addAddress, addRoute ir addDns nustato, ką sistema siunčia į tunelį. Paskirties vieta už maršrutų sąrašo niekada nepasiekia jūsų deskriptoriaus, todėl joks persiuntimo logikos kiekis to nepataisys. Registruokite builder kvietimus ir palyginkite juos su adresais, į kuriuos programa iš tikrųjų bando patekti.
Pasirinkimas pagal programą turi savo ribą: addAllowedApplication ir addDisallowedApplication reikalauja API 21, o senesnėse versijose šių metodų tiesiog nėra. Dizainas, filtruojantis pagal paketą, ten visai negali veikti.
Perkūrimas palieka pasenusius deskriptorius
Pakartotinis establish() kvietimas grąžina naują deskriptorių ir uždaro ankstesnę sąsają. Senas deskriptorius dabar negalioja, todėl gija, kuri jame dar blokuojasi, gauna klaidą, o senam keliui sukurtus socket gali tekti pakeisti. onRevoke yra atgalinis kvietimas tuneliui, kurį paėmė naudotojas ar sistema. Gyvavimo ciklo laikymas pas vieną savininką, kuris sustabdo skaitytuvą prieš perkuriant, išvengia daugumos prisijungta-bet-mirusi būsenų.
Kiekvieną klaidą skaitykite pagal jai priklausantį žingsnį. Ryšio atstatymas iš recvfrom reiškia, kad tolimas galas užsidarė, kol jūsų kodas dar skaitė. Blogo adreso klaida rodo į antraštės analizę ar poslinkio klaidą, ne į tinklą.
Darbo tvarka: blokuojantis režimas įjungtas, vienas paketas per skaitymą, antraštė išanalizuota, socket apsaugotas, apkrova išsiųsta, švarus uždarymas perduodant ir maršrutų sąrašas, apimantis paskirties vietą.