Android VpnService es connecta, però cap paquet arriba a la xarxa
Túnel aixecat i pàgines que encara fallen: comproveu el mode de lectura, la protecció del socket, el suport de TCP i la llista de rutes abans de culpar el servidor VPN.
El túnel s'aixeca, apareix la icona del clau, i cada petició del navegador continua esgotant-se. En una aplicació VpnService aquesta combinació normalment significa que el bucle de lectura és viu i la meitat de sortida està trencada, no que el servidor hagi rebutjat l'aplec de mans. Android ja ha creat la interfície, ha mogut el trànsit de l'aplicació cap a ella i ha lliurat un descriptor de fitxer al vostre procés. Tot el que ve després d'aquest descriptor és codi vostre, i una crida que falti n'hi ha prou per engolir tot el flux.
Què us dona el sistema i què no
establish() retorna un ParcelFileDescriptor per a una interfície tun. El sistema hi escriu els paquets IP de sortida i espera que el vostre procés els llegeixi, decideixi on van i els enviï per un socket ordinari. A sota no hi ha cap reenviament del nucli, així que quan ningú no llegeix, els paquets es descarten i els comptadors de la interfície no es mouen mai.
Comenceu comptant en lloc d'endevinar. Incrementeu un comptador a cada lectura correcta i registreu la destinació i el protocol dels primers paquets. Si el comptador es queda a zero mentre es genera trànsit, la fallada és per damunt del codi de reenviament, cosa que estreta la cerca a la configuració del builder i al mode de lectura. Si el comptador es mou i el navegador encara falla, la fallada és al camí d'enviament o al de tornada.
La lectura bloquejant decideix si el bucle avança
setBlocking(true) posa el descriptor en mode bloquejant i existeix des de l'API 21. En versions antigues la solució és un petit ajudant JNI que esborra O_NONBLOCK amb fcntl sobre el descriptor cru. El mode importa perquè decideix què pot fer una lectura sense cap paquet pendent.
En mode no bloquejant aquella lectura torna de seguida amb un valor negatiu, i un bucle que ignora el resultat fa girar un nucli sense moure cap trànsit. En mode bloquejant la lectura espera, que és el que vol un fil de reenviament, però llavors el fil ja no es pot interrompre. Tancar el descriptor és la manera suportada de fer fallar la lectura perquè el bucle pugui sortir. Matar el procés o interrompre el fil pot deixar la propera arrencada amb un descriptor que ja no correspon a la interfície.
Llegiu un paquet per crida en un búfer dimensionat per a l'MTU de la interfície, i tracteu el zero o un retorn negatiu com un error i no com un paquet buit.
protect() abans del primer enviament, no després
Cada socket que transporta un paquet encapsulat s'ha de protegir primer. Un socket no protegit obeeix les mateixes regles d'encaminament que acabeu d'instal·lar, així que els seus paquets tornen al túnel i el vostre propi bucle de lectura els recull de nou. Llavors el túnel reenvia la seva pròpia sortida indefinidament, i des de fora sembla una connexió que s'obre i no respon mai.
L'ordre és protect, després connect o bind, després send. Cridar protect després de connect pot deixar la decisió d'encaminament anterior al seu lloc, així que el socket continua usant el camí equivocat encara que la crida hagi retornat true. Comproveu el booleà que retorna i fallleu sorollosament quan sigui false en lloc d'entrar en un enviament que no pot funcionar.
Un error de permisos de sendto en un socket que no ha tocat mai protect() és la signatura habitual d'aquest error.
L'exemple UDP no porta HTTPS
L'exemple de reenviament que copien la majoria de tutorials gestiona UDP i res més. TCP necessita un aplec de mans, números de seqüència, reconeixements i retransmissió, i un bucle que copia càrregues entre un descriptor tun i un socket no n'ofereix cap. Per això una consulta DNS per UDP pot passar mentre cada petició HTTPS s'encalla. O executeu una pila TCP en espai d'usuari o lliureu les connexions a un proxy local que acaba TCP al dispositiu i reenvia el flux.
Proveu la divisió abans de reescriure res: envieu una consulta UDP i obriu una connexió TCP al mateix host. Si el camí UDP funciona i el camí TCP es penja al primer SYN, el problema és el codi de reenviament i no el servidor. Aquesta única comparació estalvia molt de temps llegint registres del servidor que no contenen res d'útil.
La llista de rutes i els filtres per aplicació limiten què veieu
addAddress, addRoute i addDns defineixen què envia el sistema al túnel. Una destinació fora de la llista de rutes no arriba mai al vostre descriptor, així que cap quantitat de lògica de reenviament ho arreglarà. Registreu les crides al builder que feu i compareu-les amb les adreces que l'aplicació intenta assolir de veritat.
La selecció per aplicació té el seu propi mínim: addAllowedApplication i addDisallowedApplication necessiten l'API 21, i en versions antigues els mètodes simplement no hi són. Un disseny que filtra per paquet no hi pot funcionar de cap manera.
Restablir deixa descriptors obsolets
Tornar a cridar establish() retorna un descriptor nou i tanca la interfície anterior. El descriptor vell ara és invàlid, així que un fil que encara hi està bloquejat rep un error, i els sockets creats per al camí vell potser s'han de substituir. onRevoke és la crida de retorn per a un túnel que l'usuari o el sistema han pres. Mantenir el cicle de vida en un sol propietari, que atura el lector abans de restablir, evita la majoria dels estats connectat-però-mort.
Llegiu cada error segons el pas al qual pertany. Un reinici de connexió de recvfrom significa que l'extrem remot s'ha tancat mentre el vostre codi encara llegia. Un error d'adreça incorrecta apunta a l'anàlisi de capçaleres o a un error de desplaçament, no a la xarxa.
Ordre de treball: mode bloquejant activat, un paquet per lectura, capçalera analitzada, socket protegit, càrrega enviada, tancament net en el relleu i una llista de rutes que cobreixi la destinació.