Sovelluksesi kutsuu VpnService.prepare, saa nullin, rakentaa tunnelin ja kuvaaja näyttää kelvolliselta, mutta mikään ei saavuta palvelinta. Tai prepare palauttaa Intentin, käyttäjä hyväksyy sen, ja establish epäonnistuu silti. Molemmat tulokset tarkoittavat yleensä samaa: toinen sovellus omistaa jo tämän käyttäjän VPN-liittymän, ja protect-kutsusi merkitsevät socketteja, joita tunnelisi ei voi käyttää. Käy alla olevat tarkistukset järjestyksessä läpi ja pysähdy ensimmäiseen, joka epäonnistuu, koska myöhempi vaihe ei voi korjata aiempaa.

Tarkista VPN-paikka ennen kuin kosket protectiin

Android antaa jokaiselle käyttäjälle täsmälleen yhden aktiivisen VPN-liittymän. Se on alustan sääntö, ei käyttöoikeus, jota sovelluksesi voi pyytää, joten kaksi asiakasta, jotka molemmat haluavat olla tunneli, eivät voi olla olemassa yhtä aikaa.

Toiselle sovellukselle asetettu aina päällä oleva VPN on vaikein tapaus, koska järjestelmä pitää sen sovelluksen yhdistettynä ja hylkää haltuunoton. Liikennesuodatin kuten AdGuard, yritysasiakas tai kuluttaja-VPN voi pitää paikan, vaikka käyttäjä on unohtanut sen olevan asennettuna. Pyydä käyttäjää avaamaan Asetukset ja lukemaan VPN-kohdassa lueteltu merkintä; siellä oleva nimi on omistaja, jonka kanssa kilpailet. Kunnes merkintä on katkaistu tai aina päällä -asetus poistettu, VpnService ei voi tulla liittymäksi.

Vaihe 1: lue, mitä prepare palauttaa

VpnService.prepare vastaa yhteen kysymykseen: saako tämä sovellus tulla VPN:ksi? Se palauttaa nullin, kun suostumus on jo olemassa, ja Intentin, kun käyttäjän on vielä hyväksyttävä se, minkä sinun on käynnistettävä startActivityForResultilla. Kohtele nullin palautusta lupana jatkaa, ei todisteena siitä, että establish onnistuu.

Intent intent = VpnService.prepare(context);
if (intent != null) {
    startActivityForResult(intent, REQUEST_VPN);
}

Jos Intent-polku palaa ja käyttäjä on kieltäytynyt, prepare palauttaa edelleen Intentin seuraavalla yrityksellä. Kirjaa tuloskoodi sen sijaan, että yrittäisit uudelleen silmukassa, koska kieltäytynyt suostumus on käyttäjän päätös, ei tilapäinen virhe.

Vaihe 2: varmista, että liittymä kuuluu sovelluksellesi

establishin jälkeen saamasi ParcelFileDescriptor on omistustodistuksesi, ja se kannattaa tarkistaa ennen mitään socket-työtä. Kuvaaja, joka on null tai jo suljettu, tarkoittaa, että liittymää ei koskaan luotu, vaikka poikkeusta ei ilmennyt. Vertaa rakentajan liittymänimeä siihen, jonka järjestelmä ilmoittaa aktiiviselle VPN:lle, käyttämällä dumpsysia testilaitteella. Jos ne eroavat, rakentamasi tunneli ei ole se tunneli, joka kuljettaa liikennettä.

adb shell dumpsys connectivity

Sama ristiriita selittää yhdistetyn kuvakkeen, jonka liikenne on kuollutta. Järjestelmä näyttää toisen sovelluksen tunnelin, kun taas koodisi pitää kuvaajaa liittymälle, jota se ei omista.

Vaihe 3: kutsu protect ennen connectia

Kun liittymä on olemassa, sovelluksesi avaamat socketit ohjataan oletuksena tunneliin. Ulompi kuljetus ei voi noudattaa tätä sääntöä, muuten kapseloitu paketti palaisi liittymään, siksi VpnService protect merkitsee socketin ohittamaan VPN:n ja käyttämään alla olevaa verkkoa. Kutsu palauttaa booleanin, ja sen huomiotta jättäminen on tapa päätyä socketiin, joka näyttää terveeltä eikä kanna mitään.

Kutsu protect ennen connectia tai bindia, ei koskaan jälkeen. Suojauksen on oltava paikallaan ennen kuin socket yhdistetään tai sidotaan, koska yhdistetty socket on jo valinnut reittinsä eikä myöhempi protect siirrä sitä luotettavasti. Jos protect palauttaa falsen, pysähdy ja tarkista omistus uudelleen: se epäonnistuu, kun liittymä ei kuulu sovelluksellesi, ja juuri sellaisen tilan toinen VPN jättää. Falsen palautus on merkki palata vaiheeseen 1, ei yrittää samaa socketia uudelleen.

DatagramSocket socket = new DatagramSocket();
if (!vpnService.protect(socket)) {
    return;
}
socket.connect(serverAddress, serverPort);

Vaihe 4: sido prosessi, kun socket on hankala

Jotkin kuljetukset eivät ole yksi socket: kirjasto voi avata useita, tai saatat haluta, että jokainen apuprosessin yhteys käyttää samaa uloskäyntiä. ConnectivityManager.bindProcessToNetwork sitoo prosessin valittuun verkkoon, ja vanhempi setProcessDefaultNetwork teki saman ennen vanhentumistaan. Pyydä alla oleva yhteys NET_CAPABILITY_NOT_VPN:llä, jotta sidonta valitsee verkon tunnelisi alta eikä tunnelia itseään.

NetworkRequest request = new NetworkRequest.Builder()
    .addCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET)
    .addCapability(NetworkCapabilities.NET_CAPABILITY_NOT_VPN)
    .build();

Prosessin sitominen ei ole toinen VPN. Se valitsee uloskäynnin sille prosessille, mikä on hyödyllistä kuljetuksellesi ja hyödytöntä kahden tunnelin ajamiseen yhtä aikaa.

Korjaa käyttäjän laite, älä koodipolkua

Ei ole API:a, joka sallisi kahden sovelluksen jakaa VPN-paikan, joten korjaus kuuluu laitteen tilaan. Tarjoa käyttäjälle kaksi konkreettista polkua: katkaise tai poista toisen sovelluksen aina päällä -asetus, jotta prepare voi ottaa haltuunsa, tai avaa sen sovelluksen sovelluskohtaiset asetukset ja sulje sovelluksesi sen tunnelin ulkopuolelle. Poissulkeminen pitää suodattimen käynnissä kaikelle muulle ja antaa sovelluksesi käyttää tavallista yhteyttä, mikä on usein tulos, jota käyttäjä todella haluaa.

Kirjoita molemmat polut tukidokumentaatioosi, koska käyttäjät harvoin tietävät, mikä sovellus omistaa VPN:n. Pyydä heitä lähettämään aktiivisen VPN:n nimi Asetuksista ja preparen, establishin ja protectin tulokset lokeistasi, niin voit minuutissa sanoa, mikä vaihe epäonnistui. Vahvista sitten oikealla liikenteellä: nimihaku, TCP-yhteys ja yksi pyyntö tunnelin läpi, ei vain yhdistetty kuvake. Kelvollinen kuvaaja ja vihreä ilmaisin todistavat korkeintaan omistuksen, eivät koskaan sitä, että kuljetus selvisi reitityssäännöistä.

Jos kaikki neljä tarkistusta menevät läpi ja liikenne kuolee silti, omistus ei ole enää ongelmasi. Siirry reititykseen ja palvelimen tavoitettavuuteen ja testaa samat tunnukset laitteelta, johon ei ole asennettu VPN-sovellusta.

Aiheeseen liittyvät artikkelit