24

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:

Community
  • 1
  • 1
Maksim Dmitriev
  • 5,985
  • 12
  • 73
  • 138
  • Is it possible your Firewall is transmitting itself as a master ip causing a fake IP log. VPN's dont tend to like multiple IP's from a single client that way. –  May 14 '15 at 22:31
  • @DCdaz, what is a master IP? What do you mean by the fake IP log? – Maksim Dmitriev May 15 '15 at 07:51
  • VPN's don't generally allow more than one IP to come from the same location and sometimes a firewall can behave like another IP which would block your connection from the client because of its terms of use with regards to IP's –  May 15 '15 at 09:03
  • @DCdaz, I don't understand you. The response I expect reaches my app, but the socket listening for it remains blocked. As if it need something which means OK, you can unblock – Maksim Dmitriev May 15 '15 at 12:53
  • @MaksimDmitriev how do you know if response reaches your app while socket is blocked? Doesn't 'SocketTimeoutException' mean, that you don't get any response? – Semyon Danilov May 15 '15 at 15:34
  • @SemyonDanilov, I print the response in the catch block. So it reaches the app. And by the way, http://ru.stackoverflow.com/q/422615/180952 :) – Maksim Dmitriev May 15 '15 at 19:45

3 Answers3

4

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

Pat
  • 2,670
  • 18
  • 27
  • You copied it from my question. – Maksim Dmitriev May 21 '15 at 18:10
  • Anyway, it's not an answer. I need to fix my firewall rather than the client for testing – Maksim Dmitriev May 21 '15 at 18:55
  • 2
    probably there's not a problem in your firewall. Please run a promiscuous Wireshark capture traffic, save it as a pcap file and upload it somewhere for analysis. This way we can easily see what's going on at packet level. – Pat May 21 '15 at 19:26
  • There was a problem with my firewall. I did one srong thing. I found the reason – Maksim Dmitriev May 26 '15 at 19:40
  • 1
    Please for future readers, when you get a minute, tell us what was wrong and how you found the problem. BTW this is not about points; it is just to close your question properly. – Pat May 26 '15 at 21:30
  • Your answer is not really clear and you didn't tell us how you found the error. Probably running a Wireshark capture and seeing there the checksum errors??? – Pat May 27 '15 at 22:06
3

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.

vzamanillo
  • 9,905
  • 1
  • 36
  • 56
0

The point was that I used a wrong IPv4 pseudo header to compute the checksum. It contained neither the packet UDP header nor the transmitted data. Since I included the UDP header and the data, I haven't seen the exception from the original question.

enter image description here

Maksim Dmitriev
  • 5,985
  • 12
  • 73
  • 138
  • Your answer is not really clear and you didn't tell us how you found the error. – Pat May 27 '15 at 22:07
  • I understand what the problem is, bad pseudo-header produces incorrect UDP checksum with mismatching information and no addresing information (good job, it's really hard to find) but I can not identify when you are receiving the error (I am assuming that the error is produced when you are forwarding the response packet to tun) because the stacktrace error is not complete and your bitbucket code is not updated, anyway I am glad if my answer helped you. – vzamanillo May 28 '15 at 07:57
  • I read your code on bitbucket. It seems you haven't implemented TCP, only UDP. Any pointers?? I want to understand the principle behind your code. Any articles I can read maybe?? – sirackh Nov 06 '15 at 12:56
  • @sirackh, you're right. I haven't implemented TCP, but others have. Watch this (the video is in Russian). Can you understand it? http://www.youtube.com/watch?v=pqMqfVlGyWM – Maksim Dmitriev Nov 09 '15 at 19:24
  • @MaksimDmitriev unfortunately I can't speak Russian. Any other link maybe? Or article?? – sirackh Nov 10 '15 at 09:59