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:
- The source and destination IP are reversed from the incoming request;
- The source and destination ports are reversed from the incoming request;
- The TCP acknowledgement number is set to the incoming request's sequence number;
- A dummy HTTP/1.1 payload is provided as the TCP's body.
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?