Android VpnService yhdistää, mutta paketit eivät saavuta verkkoa
Tunneli on pystyssä ja sivut epäonnistuvat silti: tarkista lukutila, socketin suojaus, TCP-tuki ja reittiluettelo ennen kuin syytät VPN-palvelinta.
Tunneli nousee, avainkuvake ilmestyy ja jokainen selaimen pyyntö aikakatkaistaan edelleen. VpnService-sovelluksessa tämä yhdistelmä tarkoittaa yleensä sitä, että lukusilmukka elää ja lähtöpuolisko on rikki, ei sitä että palvelin hylkäsi kättelyn. Android on jo luonut rajapinnan, siirtänyt sovelluksen liikenteen siihen ja antanut prosessillesi tiedostokuvaajan. Kaikki tuon kuvaajan jälkeen on sinun koodiasi, ja yksi puuttuva kutsu riittää nielaisemaan koko virran.
Mitä järjestelmä antaa ja mitä ei
establish() palauttaa ParcelFileDescriptorin tun-rajapinnalle. Järjestelmä kirjoittaa lähtevät IP-paketit siihen ja odottaa prosessisi lukevan ne, päättävän minne ne menevät ja lähettävän ne tavallisen socketin kautta. Alla ei ole ytimen välitystä, joten kun kukaan ei lue, paketit pudotetaan eivätkä rajapinnan laskurit koskaan liiku.
Aloita laskemalla arvaamisen sijaan. Kasvata laskuria jokaisella onnistuneella lukemisella ja kirjaa ensimmäisten pakettien kohde ja protokolla. Jos laskuri pysyy nollassa liikennettä syntyessä, vika on välityskoodisi yläpuolella, mikä rajaa haun builderin asetukseen ja lukutilaan. Jos laskuri liikkuu ja selain epäonnistuu silti, vika on lähetyspolulla tai paluupolulla.
Estävä lukeminen ratkaisee, liikkuuko silmukka
setBlocking(true) asettaa kuvaajan estävään tilaan ja on ollut olemassa API 21:stä lähtien. Vanhemmissa julkaisuissa kiertotie on pieni JNI-apuri, joka tyhjentää O_NONBLOCKin fcntl:llä raakakuvaajasta. Tila on tärkeä, koska se päättää, mitä lukeminen ilman odottavaa pakettia saa tehdä.
Ei-estävässä tilassa lukeminen palaa heti negatiivisella arvolla, ja tuloksen jättävä silmukka pyörittää ydintä siirtämättä lainkaan liikennettä. Estävässä tilassa lukeminen odottaa, mitä välityssäie haluaa, mutta säiettä ei silloin voi enää keskeyttää. Kuvaajan sulkeminen on tuettu tapa saada lukeminen epäonnistumaan, jotta silmukka voi poistua. Prosessin tappaminen tai säikeen keskeyttäminen voi jättää seuraavan käynnistyksen kuvaajaan, joka ei enää vastaa rajapintaa.
Lue yksi paketti kutsua kohden puskuriin, joka on rajapinnan MTU:n kokoinen, ja kohtele nollaa tai negatiivista paluuarvoa virheenä, älä tyhjänä pakettina.
protect() ennen ensimmäistä lähetystä, ei sen jälkeen
Jokainen socket, joka kuljettaa kapseloitua pakettia, on suojattava ensin. Suojaamaton socket noudattaa samoja reitityssääntöjä, jotka juuri asensit, joten sen paketit palaavat tunneliin ja oma lukusilmukkasi poimii ne uudelleen. Silloin tunneli välittää omaa lähtöään loputtomasti, ja ulkopuolelta se näyttää yhteydeltä, joka avautuu eikä koskaan vastaa.
Järjestys on protect, sitten connect tai bind, sitten send. protectin kutsuminen connectin jälkeen voi jättää aiemman reittipäätöksen voimaan, joten socket käyttää edelleen väärää polkua vaikka kutsu palautti true. Tarkista palautettu boolean ja epäonnistu äänekkäästi, kun se on false, sen sijaan että astuisit lähetykseen, joka ei voi toimia.
Käyttöoikeusvirhe sendto-kutsusta socketilla, joka ei koskaan koskenut protect():iin, on tämän virheen tavallinen allekirjoitus.
UDP-esimerkki ei kanna HTTPS:ää
Useimpien opetusohjelmien kopioima välitysesimerkki käsittelee UDP:tä eikä muuta. TCP tarvitsee kättelyn, järjestysnumerot, kuittaukset ja uudelleenlähetyksen, eikä silmukka, joka kopioi hyötykuormaa tun-kuvaajan ja socketin välillä, tarjoa niistä yhtäkään. Siksi UDP-DNS-kysely voi mennä läpi samalla kun jokainen HTTPS-pyyntö jumittuu. Joko aja TCP-pino käyttäjätilassa tai anna yhteydet paikalliselle välityspalvelimelle, joka päättää TCP:n laitteella ja välittää virran.
Testaa jako ennen kuin kirjoitat mitään uudelleen: lähetä yksi UDP-kysely ja avaa yksi TCP-yhteys samaan isäntään. Jos UDP-polku toimii ja TCP-polku jumittuu ensimmäiseen SYN:iin, vika on välityskoodissa eikä palvelimessa. Tämä yksi vertailu säästää paljon aikaa, joka kuluu palvelimen lokeja lukiessa, joissa ei ole mitään hyödyllistä.
Reittiluettelo ja sovelluskohtaiset suodattimet rajaavat mitä näet
addAddress, addRoute ja addDns määrittävät, mitä järjestelmä lähettää tunneliin. Reittiluettelon ulkopuolinen kohde ei koskaan saavuta kuvaajaasi, joten mikään määrä välityslogiikkaa ei korjaa sitä. Kirjaa builder-kutsut ja vertaa niitä osoitteisiin, joihin sovellus todella yrittää päästä.
Sovelluskohtaisella valinnalla on oma alarajansa: addAllowedApplication ja addDisallowedApplication vaativat API 21:n, ja vanhemmissa julkaisuissa menetelmät yksinkertaisesti puuttuvat. Paketin mukaan suodattava suunnittelu ei voi toimia siellä lainkaan.
Uudelleenperustaminen jättää vanhentuneita kuvaajia
establish()-kutsu uudelleen palauttaa uuden kuvaajan ja sulkee edellisen rajapinnan. Vanha kuvaaja on nyt virheellinen, joten siihen yhä estynyt säie saa virheen, ja vanhalle polulle luodut socketit on ehkä korvattava. onRevoke on takaisinkutsu tunnelille, jonka käyttäjä tai järjestelmä on ottanut pois. Elinkaaren pitäminen yhdellä omistajalla, joka pysäyttää lukijan ennen uutta perustamista, estää useimmat yhdistetty-mutta-kuollut-tilat.
Lue jokainen virhe sen vaiheen mukaan, johon se kuuluu. recvfromin yhteysnollaus tarkoittaa, että etäpää sulkeutui koodisi vielä lukiessa. Huonon osoitteen virhe viittaa otsakkeen jäsennykseen tai siirtymävirheeseen, ei verkkoon.
Työjärjestys: estävä tila päälle, yksi paketti lukua kohden, otsake jäsennetty, socket suojattu, hyötykuorma lähetetty, siisti sulkeminen luovutuksessa ja reittiluettelo, joka kattaa kohteen.