play - Un firewall para Android con VpnService. Las respuestas se entregan, pero se lanza una SocketTimeoutException
firewalls para android (3)
Desde recvfrom () llame a documentos:
[EAGAIN] o [EWOULDBLOCK] El descriptor de archivo del socket está marcado como O_NONBLOCK y no hay datos esperando ser recibidos ; o MSG_OOB está configurado y no hay datos fuera de banda disponibles y el descriptor de archivo del socket está marcado como O_NONBLOCK o el socket no admite el bloqueo para esperar datos fuera de banda.
explicación simplificada
Si no hay mensajes disponibles en el socket, las llamadas de recepción esperan a que llegue un mensaje, a menos que el socket sea no bloqueante (vea fcntl (2)), en cuyo caso se devuelve el valor -1 y la variable externa errno se establece en EAGAIN
El DatagramSocket no está en modo de bloqueo y parece que estás tratando de leer desde el socket y no hay datos para leer, ¿estás seguro de que realmente estás recibiendo los datos? intente limpiar el buffer para cada paquete recibido.
Estoy implementando un firewall simple para Android usando VpnService . Mi aplicación es similar a ToyVpnService , pero no envía paquetes IP sin procesar a un servidor VPN remoto que los reenviará a sus destinos.
Mi implementación está aquí: https://bitbucket.org/MaksimDmitriev/norootfirewall/src/006f7c33cd1cd4055f372ed3a88664fe2a4be3dd/src/com/norootfw/NoRootFwService.java?at=unix
¿Puedo hacer toda esta rutina de reenvío localmente? Eso es lo que estoy tratando de implementar.
Inicializo un dispositivo TUN y sus descriptores de archivo:
mInterface = new Builder().setSession(getString(R.string.app_name))
.addAddress("10.0.2.1", 24)
.addRoute("0.0.0.0", 1)
.addRoute("128.0.0.0", 1)
.establish();
in = new FileInputStream(mInterface.getFileDescriptor());
out = new FileOutputStream(mInterface.getFileDescriptor());
Asigno 0.0.0.0/1 y 128.0.0.0/1 al dispositivo TUN para hacerlo más preferible que la ruta predeterminada con 0.0.0.0/0. Usé 0.0.0.0/0 y encontré la misma excepción que está debajo.
Y aquí hay una solicitud UDP de muestra.
1). Leí un paquete IP del dispositivo TUN.
05-06 00:46:52.749: D/UDPChecksum(31077): Sent == [69, 0, 0, 36, 0, 0, 64, 0, 64, 17, 108, 91, 10, 0, 2, 1, -64, -88, 1, -59, -53, 1, -50, -87, 0, 16, 89, -114, 85, 68, 80, 95, 68, 65, 84, 65]
Considere la estructura del paquete IPv4 here. Por ejemplo, el primer número 69 (0100 0101 en binario) significa que la versión del protocolo IP es 4 (4 bits de orden superior). Y los 4 bits de bajo orden representan la longitud del encabezado de Internet (IHL) en palabras de 32 bits.
2). Luego, cree un DatagramSocket
protected y envíe los datos ( sin sus encabezados IP y UDP) a la dirección de destino que leí del paquete IP capturado.
3). Recibo una respuesta de la máquina remota y quiero enviarla a la aplicación que inició la solicitud.
4). Intercambié las direcciones IP de origen y destino y los números de puerto en el paquete IP, calculo la suma de comprobación del encabezado IPv4 y la suma de comprobación UDP (habiendo construido un pseudo encabezado IPv4).
05-06 00:46:52.889: D/UDPChecksum(31077): mIpv4PseudoHeader == [-64, -88, 1, -59, 10, 0, 2, 1, 0, 17, 0, 14]
5). Después, establezco las sumas de comprobación calculadas en los índices correspondientes del paquete IP y escribo el paquete IP para que out
, el flujo de salida del dispositivo TUN.
05-06 00:46:52.889: D/UDPChecksum(31077): To TUN == [69, 0, 0, 34, 0, 0, 64, 0, 64, 17, 108, 93, -64, -88, 1, -59, 10, 0, 2, 1, -50, -87, -53, 1, 0, 14, -105, -72, 85, 68, 80, 95, 79, 75]
La respuesta llega a mi aplicación. El DatagramSocket
que ha sido bloqueado después de llamar a su método receive () llena el buffer que proporciono.
byte[] responseBuffer = new byte[RESPONSE_SIZE];
try {
mDatagramSocket.send(mDatagramPacket);
final DatagramPacket response = new DatagramPacket(responseBuffer, responseBuffer.length);
mDatagramSocket.receive(response);
} catch (IOException e) {
Log.e("NoRootFwService", "error: " + Arrays.toString(responseBuffer)); // I can see the correct response here.
logException(e);
}
Pero su socket arroja una excepción cuando se excede el tiempo de espera.
05-05 23:46:58.389: E/CLIENT(20553): java.net.SocketTimeoutException
05-05 23:46:58.389: E/CLIENT(20553): at libcore.io.IoBridge.maybeThrowAfterRecvfrom(IoBridge.java:551)
05-05 23:46:58.389: E/CLIENT(20553): at libcore.io.IoBridge.recvfrom(IoBridge.java:509)
05-05 23:46:58.389: E/CLIENT(20553): at java.net.PlainDatagramSocketImpl.doRecv(PlainDatagramSocketImpl.java:161)
05-05 23:46:58.389: E/CLIENT(20553): at java.net.PlainDatagramSocketImpl.receive(PlainDatagramSocketImpl.java:169)
05-05 23:46:58.389: E/CLIENT(20553): at java.net.DatagramSocket.receive(DatagramSocket.java:250)
05-05 23:46:58.389: E/CLIENT(20553): at socket.client.MainActivity$UdpThread.run(MainActivity.java:195)
05-05 23:46:58.389: E/CLIENT(20553): Caused by: libcore.io.ErrnoException: recvfrom failed: EAGAIN (Try again)
05-05 23:46:58.389: E/CLIENT(20553): at libcore.io.Posix.recvfromBytes(Native Method)
05-05 23:46:58.389: E/CLIENT(20553): at libcore.io.Posix.recvfrom(Posix.java:141)
05-05 23:46:58.389: E/CLIENT(20553): at libcore.io.BlockGuardOs.recvfrom(BlockGuardOs.java:164)
05-05 23:46:58.389: E/CLIENT(20553): at libcore.io.IoBridge.recvfrom(IoBridge.java:506)
05-05 23:46:58.389: E/CLIENT(20553): ... 4 more
Todo funciona correctamente sin el firewall. Leí estas discusiones, pero no fueron útiles:
https://stackoverflow.com/a/17820461/1065835
Firewall de Android con VpnService
El punto fue que utilicé un pseudo encabezado de IPv4 incorrecto para calcular la suma de comprobación. No contenía el encabezado UDP del paquete ni los datos transmitidos. Dado que incluí el encabezado UDP y los datos, no he visto la excepción de la pregunta original.
Intente agregar el controlador de excepción SocketTimeoutException que falta
byte[] responseBuffer = new byte[RESPONSE_SIZE];
try {
mDatagramSocket.send(mDatagramPacket);
final DatagramPacket response = new DatagramPacket(responseBuffer, responseBuffer.length);
mDatagramSocket.receive(response);
} catch (SocketTimeoutException e) {
// ignore
; // continue;
} catch (IOException e) {
Log.e("NoRootFwService", "error: " + Arrays.toString(responseBuffer)); // I can see the correct response here.
logException(e);
}