Nada en VpnService intercepta tráfico por si só. Pide ao sistema unha interface tun, entrégalle a esa interface un conxunto de enderezos e rutas e, desde ese momento, o núcleo copia cada paquete que coincide con esas rutas nun descritor de ficheiro que a aplicación pode ler. O código da aplicación nunca ve os paquetes que as rutas non seleccionaron, así que o ámbito dun túnel é unha cuestión de táboa de enrutamento antes que unha cuestión de programación. Por iso a mesma implementación do servizo pode tragar cada fluxo nun teléfono e só unha parte noutro, e por iso engadir unha ruta ás veces repara un túnel que parecía roto. O que obtés é o que declaraches, menos todo o que o construtor rexeitou en silencio.

As rutas deciden que paquetes chegan

Builder.addRoute(prefix, prefixLength) instala un prefixo na táboa de enrutamento da rede VPN. O sistema trata esa rede como predeterminada para as aplicacións do ámbito, así que as súas entradas consúltanse antes que a ruta predeterminada da rede física e un prefixo coincidente abonda para desviar un fluxo. Por iso unha ruta para 0.0.0.0/0 toma IPv4 enteiro, incluídos a impresora, o destino de cast e a páxina de administración do router.

Os clientes que queren esa cobertura sen borrar a entrada predeterminada física usan en cambio o par 0.0.0.0/1 e 128.0.0.0/1. Dúas rutas máis específicas gañan a unha ruta predeterminada, xuntas cobren todo o espazo de enderezos e a entrada predeterminada do propio teléfono queda na táboa para os sockets que seguen ligados á rede física.

Os enderezos e as entradas DNS veñen do mesmo construtor e responden a outra pregunta. addAddress dálle á interface un enderezo cunha lonxitude de prefixo, e addDnsServer instala un resolvedor que as aplicacións do ámbito consultarán a través da interface. Ambas as familias poden convivir nunha interface, así que un conxunto de rutas só IPv4 xunto a un enderezo IPv6 é unha configuración que se le correctamente e aínda así filtra. Se enrutas unha familia na que a interface non ten enderezo, o núcleo non ten enderezo de orixe para eses paquetes e nunca aparecen no descritor.

Que devolve establish()

establish() devolve un ParcelFileDescriptor, ou null cando a aplicación non está autorizada. Null é un estado de consentimento e non un erro de rede: VpnService.prepare() xa debe ter devolto null, ou o usuario debe ter aprobado o diálogo do sistema antes da chamada. O consentimento pódese revogar mentres o servizo se executa, e a interface péchase baixo os teus pés sen ningunha chamada de retorno ao teu bucle de lectura.

O descritor non bloquea salvo que digas o contrario, así que unha lectura volve de inmediato sen datos en lugar de aparcar o fío. Un bucle escrito para un socket bloqueante queima un núcleo, e un bucle que trata unha lectura baleira como fatal rompe o túnel nun segundo inactivo. Builder.setBlocking(true) cambia ese comportamento, e o prezo é un fío que espera dentro da lectura.

Os argumentos de ruta e enderezo valídanse como valores LinkAddress, e os destinos de multicast como 224.0.0.0/4 rexéitanse de plano. Unha interface tun punto a punto tampouco ten dominio de broadcast, así que o descubrimento de multicast a través dela necesita outro deseño en lugar dunha ruta máis. A referencia de VpnService.Builder é a lista que hai que revisar antes de concluír que unha chamada se ignorou.

O ámbito por aplicación estreita o conxunto, non o túnel

Desde a API 21 o construtor acepta addAllowedApplication e addDisallowedApplication, e as dúas listas son mutuamente excluíntes dentro dun mesmo construtor. Unha lista de permitidos fai que o túnel exista para eses paquetes mentres todo o demais conserva a rede física; unha lista de denegados captura todo agás os paquetes nomeados, que é o que un cliente ofrece como túnel dividido. Antes da API 21 non hai ningún mecanismo de plataforma, e o comportamento por aplicación viña de clientes de terceiros que filtraban por UID dentro do seu propio reenviador. A distinción importa ao depurar, porque un túnel por aplicación cunha lista dun só paquete apenas produce tráfico e iso é un comportamento correcto, non unha ruta rota.

Sexa cal sexa o ámbito, o socket que a túa aplicación abre cara ao servidor VPN non debe viaxar polo túnel que está a construír. VpnService.protect(socket) liga ese socket á rede subxacente para que os seus paquetes salten o dispositivo tun. Se saltas a chamada, o aperto de mans é tragado pola interface que debería levantar o aperto de mans.

VpnService.setUnderlyingNetworks pecha o círculo dicíndolle ao sistema que rede física transporta o túnel. Ese valor determina como informa a VPN da conectividade ás demais aplicacións, e un valor null ou desactualizado é unha causa habitual dun túnel conectado que o resto do teléfono trata como fóra de liña. Actualízao cando cambie a rede subxacente, non só ao arrincar.

Sumas de verificación e MTU: onde unha ruta correcta segue fallando

Unha vez que os paquetes chegan, dous detalles a nivel de paquete deciden se algo funciona de extremo a extremo. O primeiro é o enderezo de orixe: con varias direccións de interface o núcleo pode emitir paquetes que levan a primeira, así que un reenviador que reescribe cabeceiras debe corrixir o enderezo e a suma de verificación que o cobre.

O segundo é o ámbito da suma de verificación. Cando a aplicación constrúe ela mesma paquetes TCP ou UDP, a suma cobre unha pseudocabeceira co enderezo de orixe, o enderezo de destino, o número de protocolo e a lonxitude de transporte, e omitir calquera deles produce paquetes que chegan e se descartan. Ese fallo parece un tempo de espera tras unha resposta que nunca chega, o que devolve a maioría da xente ás rutas. setMtu fixa a MTU da interface, e cada lectura devolve exactamente un paquete IP completo, así que un búfer escrito para un fluxo de bytes fragmentará a túa análise, non o paquete.

Unha pasada de verificación que separa as capas

Para atopar onde se torceu o ámbito, proba as capas en orde. Pregunta ao dispositivo que rutas mantén realmente a rede VPN, despois comproba se un fluxo cara a un enderezo coñecido aparece como unha lectura no descritor, e despois comproba se o paquete que emite o teu reenviador está ben formado. Un problema de ruta non mostra nada no descritor, mentres que un problema de suma de verificación mostra un paquete que chega e desaparece.

Se nada chega ao descritor, o sospeitoso é o conxunto de rutas e non o lector: confirma os prefixos, confirma que hai un enderezo na mesma familia e confirma que a aplicación está dentro do ámbito que fixaches. Se os paquetes chegan ao descritor pero non volve nada, inspecciona protect() e o valor da rede subxacente antes de tocar outra vez unha ruta. Se o lado remoto informa de paquetes mal formados, a suma de verificación e a MTU son os sitios onde mirar. Unha sonda por capa aforra unha tarde de conxecturas.

Artigos relacionados