My application implements VpnService to intercept network traffic and provide tailored responses. The goal is to handle traffic to specific addresses, and discard other requests.
Presently I'm successful in parsing incoming requests and constructing and sending responses. The problem, however, is that these responses do not arrive as the actual response to the original request; testing with a socket connection simply times out.
In order to make this distinction, I'm presently parsing the raw IP packets from the VpnService's input stream as follows:
VpnService.Builder b = new VpnService.Builder();
b.addAddress("10.2.3.4", 28);
b.addRoute("0.0.0.0", 0);
b.setMtu(1500);
...
ParcelFileDescriptor vpnInterface = b.establish();
final FileInputStream in = new FileInputStream(
vpnInterface.getFileDescriptor());
final FileOutputStream out = new FileOutputStream(
vpnInterface.getFileDescriptor());
// Allocate the buffer for a single packet.
ByteBuffer packet = ByteBuffer.allocate(32767);
// We keep forwarding packets till something goes wrong.
try {
while (vpnInterface != null && vpnInterface.getFileDescriptor() != null
&& vpnInterface.getFileDescriptor().valid()) {
packet.clear();
SystemClock.sleep(10);
// Read the outgoing packet from the input stream.
final byte[] data = packet.array();
int length = in.read(data);
if (length > 0) {
packet.limit(length);
/*
1. Parse the TCP/UDP header
2. Create an own socket with the same src/dest port/ip
3. Use protect() on this socket so it not routed over tun0
4. Send the packet body (excluding the header)
5. Obtain the response
6. Add the TCP header to the response and forward it
*/
final IpDatagram ip = IpDatagram.create(packet);
...
}
}
IpDatagram is a class through which create()
parses the byte array into a representation of the IP packet, containing the IP header, options and body. I proceed to parse the byte array of the body according to the protocol type. In this case, I'm only interested in IPv4 with a TCP payload—here too I create a representation of the TCP header, options and body.
After obtaining an instance of IpDatagram, I can determine the source and destination IP (from the IP header) and port (from the TCP header). I also acknowledge the request TCP's flags (such as SYN, ACK and PSH) and sequence number. In the app:
Subsequently I construct a new IpDatagram as a response, where:
I convert the resulting IpDatagram to a byte array and write it to the VpnServer's output stream:
TcpDatagram tcp = new TcpDatagram(tcpHeader, tcpOptions, tcpBody);
IpDatagram ip = new Ip4Datagram(ipHeader, ipOptions, tcp);
out.write(ip.toBytes());
My application displays the outgoing datagram as it should be, but nevertheless, all connections are still timing out.
Here's a sample incoming TCP/IP packet in hexadecimal:
4500003c7de04000400605f10a0203044faa5a3bb9240050858bc52b00000000a00239089a570000020405b40402080a00bfb8cb0000000001030306
And the resulting outgoing TCP/IP packet in hexadecimal:
450000bb30394000800613194faa5a3b0a0203040050b92400a00000858bc52b501820001fab0000485454502f312e3120323030204f4b0a446174653a205475652c203139204e6f7620323031332031323a32333a303320474d540a436f6e74656e742d547970653a20746578742f68746d6c0a436f6e74656e742d4c656e6774683a2031320a457870697265733a205475652c203139204e6f7620323031332031323a32333a303320474d540a0a48656c6c6f20776f726c6421
However, a simple test simply times out; I creata a new socket and connect it to the IP above, yet the response provided above never arrives.
What could be going wrong? Is there any way to troubleshoot why my response isn't arriving?
This TCP/IP response doesn't contain a valid TCP header checksum:
450000bb30394000800613194faa5a3b0a0203040050b92400a00000858bc52b501820001fab0000485454502f312e3120323030204f4b0a446174653a205475652c203139204e6f7620323031332031323a32333a303320474d540a436f6e74656e742d547970653a20746578742f68746d6c0a436f6e74656e742d4c656e6774683a2031320a457870697265733a205475652c203139204e6f7620323031332031323a32333a303320474d540a0a48656c6c6f20776f726c6421
More generally, the request and response mechanism is very picky. This is of course the case due to the very nature of networking, and as the kernel takes care of ensuring that responses are good and to which port a response should be sent, anything that doesn't compute will simply be discarded as a bad packet. This also holds true when responding from the VpnService's output stream, as you're operating on the network layer.
To return to the specific case above: the IP packet is correct (including the checksum) but the TCP packet was not. You need to compute the TCP header checksum over not just the TCP packet, but prefixed by the pseudo header as follows:
(source: tcpipguide.com)
It should be then be computed over the following bytes:
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