I am implementing a simple firewall for Android using VpnService. My app is similar to ToyVpnService, but it doesn't send raw IP packets to a remote VPN server which would forward them to their destinations.
My implementation is here: https://bitbucket.org/MaksimDmitriev/norootfirewall/src/006f7c33cd1cd4055f372ed3a88664fe2a4be3dd/src/com/norootfw/NoRootFwService.java?at=unix
Can I do all this forwarding routine locally? That's what I'm trying to implement.
I initialize a TUN device and its file descriptors:
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());
I assign 0.0.0.0/1 and 128.0.0.0/1 to the TUN device to make it more preferable than the default route with 0.0.0.0/0. I used 0.0.0.0/0 and ran into the same exception which is below.
And here is a sample UDP request.
1). I read an IP packet from the TUN device.
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]
Please consider the IPv4 packet structure here. For example, the first number 69 (0100 0101 in binary) means that the version of the IP protocol is 4 (4 high-order bits). And the 4 low-order bits stand for the Internet Header Length (IHL) in 32-bit words.
2). Then a create a protected DatagramSocket
and send the data (without its IP and UDP headers) to the destination address I'd read from the captured IP packet.
3). I receive a response from the remote machine and want to send it back to the app which initialized the request.
4). I swap the source and destination IP addresses and port numbers in the IP packet, calculate the IPv4 header checksum and the UDP checksum (having constructed an IPv4 pseudo header).
05-06 00:46:52.889: D/UDPChecksum(31077): mIpv4PseudoHeader == [-64, -88, 1, -59, 10, 0, 2, 1, 0, 17, 0, 14]
5). Afterwards I set the calculated checksums to the corresponding indexes of the IP packet and write the IP packet to out
, the output stream of the TUN device.
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]
The response reaches my app. The DatagramSocket
which has been blocked after calling its receive() method fills the buffer I provide.
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); }
But its socket throws an exception when the timeout is exceeded.
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
Everything works properly without the firewall. I read these discussions, but they weren't helpful:
https://stackoverflow.com/a/17820461/1065835
Android firewall with VpnService
Try adding the missing SocketTimeoutException exception handler
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); }
source
From recvfrom() call docs:
[EAGAIN] or [EWOULDBLOCK] The socket's file descriptor is marked O_NONBLOCK and no data is waiting to be received; or MSG_OOB is set and no out-of-band data is available and either the socket's file descriptor is marked O_NONBLOCK or the socket does not support blocking to await out-of-band data.
simplified explanation
If no messages are available at the socket, the receive calls wait for a message to arrive, unless the socket is nonblocking (see fcntl(2)), in which case the value -1 is returned and the external variable errno set to EAGAIN
The DatagramSocket is not in blocking mode and seems like you are trying to read from the socket and there is no data to read, are you sure you are really receiving the data? try cleaning the buffer for each received packet.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With