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: