O túnel levanta, aparece a icona da chave e cada petición do navegador segue esgotando o tempo. Nunha aplicación VpnService esa combinación adoita significar que o bucle de lectura está vivo e a metade de saída está rota, non que o servidor rexeitase o aperto de mans. Android xa creou a interface, moveu o tráfico da aplicación dentro dela e entregou ao seu proceso un descritor de ficheiro. Todo o que vén despois dese descritor é código seu, e unha chamada que falte abonda para tragar o fluxo enteiro.

Que lle dá o sistema e que non

establish() devolve un ParcelFileDescriptor para unha interface tun. O sistema escribe paquetes IP saíntes nela e espera que o seu proceso os lea, decida a onde van e os envíe por un socket corrente. Debaixo non hai reenvío do núcleo, así que cando ninguén le, os paquetes descártanse e os contadores da interface nunca se moven.

Comece contando en lugar de adiviñar. Incremente un contador en cada lectura correcta e rexistre o destino e o protocolo dos primeiros paquetes. Se o contador queda en cero mentres se xera tráfico, o fallo está por riba do código de reenvío, o que reduce a busca á configuración do builder e ao modo de lectura. Se o contador se move e o navegador segue fallando, o fallo está na ruta de envío ou na de volta.

A lectura bloqueante decide se o bucle avanza

setBlocking(true) pon o descritor en modo bloqueante e existe desde API 21. Nas versións anteriores a solución é un pequeno axudante JNI que borra O_NONBLOCK con fcntl sobre o descritor sen procesar. O modo importa porque decide que pode facer unha lectura sen ningún paquete pendente.

En modo non bloqueante esa lectura volve de inmediato cun valor negativo, e un bucle que ignora o resultado xira un núcleo sen mover tráfico ningún. En modo bloqueante a lectura espera, que é o que quere un fío de reenvío, pero entón o fío xa non se pode interromper. Pechar o descritor é a forma soportada de facer fallar a lectura para que o bucle poida saír. Matar o proceso ou interromper o fío pode deixar o seguinte arranque cun descritor que xa non corresponde á interface.

Lea un paquete por chamada nun búfer dimensionado para a MTU da interface, e trate o cero ou un retorno negativo como erro en lugar de como paquete baleiro.

protect() antes do primeiro envío, non despois

Todo socket que transporte un paquete encapsulado debe protexerse primeiro. Un socket sen protexer obedece as mesmas regras de encamiñamento que acaba de instalar, así que os seus paquetes volven ao túnel e o seu propio bucle de lectura recólleos outra vez. Entón o túnel reenvía a súa propia saída sen fin, e desde fóra parece unha conexión que se abre e nunca responde.

A orde é protect, logo connect ou bind, logo send. Chamar protect despois de connect pode deixar en pé a decisión de ruta anterior, así que o socket segue usando o camiño equivocado aínda que a chamada devolvese true. Comprobe o booleano que devolve e falle ruidosamente cando sexa false en vez de entrar nun envío que non pode funcionar.

Un erro de permisos de sendto nun socket que nunca tocou protect() é a sinatura habitual deste fallo.

O exemplo UDP non transporta HTTPS

O exemplo de reenvío que copian case todos os titoriais manexa UDP e nada máis. TCP necesita un aperto de mans, números de secuencia, confirmacións e retransmisión, e un bucle que copia cargas entre un descritor tun e un socket non achega nada diso. Por iso unha consulta DNS por UDP pode pasar mentres cada petición HTTPS se atasca. Ou executa unha pila TCP en espazo de usuario ou entrega as conexións a un proxy local que termina TCP no dispositivo e reenvía o fluxo.

Probe a división antes de reescribir nada: envía unha consulta UDP e abra unha conexión TCP ao mesmo host. Se o camiño UDP funciona e o camiño TCP se colga no primeiro SYN, o problema é o código de reenvío e non o servidor. Esa única comparación aforra moito tempo lendo rexistros do servidor que non conteñen nada útil.

A lista de rutas e os filtros por aplicación limitan o que ve

addAddress, addRoute e addDns definen que envía o sistema ao túnel. Un destino fóra da lista de rutas nunca chega ao seu descritor, así que ningunha cantidade de lóxica de reenvío o arranxará. Rexitre as chamadas ao builder que fai e compáreas coas direccións ás que a aplicación intenta chegar de verdade.

A selección por aplicación ten o seu propio mínimo: addAllowedApplication e addDisallowedApplication necesitan API 21, e nas versións anteriores os métodos simplemente non existen. Un deseño que filtra por paquete non pode executarse alí en absoluto.

Restablecer deixa descritores obsoletos

Volver chamar a establish() devolve un descritor novo e pecha a interface anterior. O descritor vello xa non é válido, así que un fío que siga bloqueado nel recibe un erro, e os sockets creados para o camiño vello quizais deban substituírse. onRevoke é a chamada de retorno para un túnel que o usuario ou o sistema retiraron. Manter o ciclo de vida nun só dono, que detén o lector antes de restablecer, evita a maioría dos estados conectado-pero-morto.

Lea cada erro segundo o paso ao que pertence. Un reinicio de conexión de recvfrom significa que o extremo remoto pechou mentres o seu código aínda lía. Un erro de dirección incorrecta apunta á análise de cabeceiras ou a un fallo de desprazamento, non á rede.

Orde de traballo: modo bloqueante activado, un paquete por lectura, cabeceira analizada, socket protexido, carga enviada, peche limpo no traspaso e unha lista de rutas que cubra o destino.

Artigos relacionados