Skip to content

Commit 389a7a5

Browse files
committed
Make sure ping works while the VPN is active
1 parent 7dcb15c commit 389a7a5

File tree

9 files changed

+222
-40
lines changed

9 files changed

+222
-40
lines changed

app/src/main/java/tech/httptoolkit/android/ProxyVpnRunnable.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ import tech.httptoolkit.android.vpn.ClientPacketWriter
77
import tech.httptoolkit.android.vpn.SessionHandler
88
import tech.httptoolkit.android.vpn.SessionManager
99
import tech.httptoolkit.android.vpn.socket.SocketNIODataService
10-
import tech.httptoolkit.android.vpn.transport.tcp.PacketHeaderException
10+
import tech.httptoolkit.android.vpn.transport.PacketHeaderException
1111
import io.sentry.Sentry
1212
import java.io.FileInputStream
1313
import java.io.FileOutputStream

app/src/main/java/tech/httptoolkit/android/vpn/SessionHandler.java

Lines changed: 84 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -17,16 +17,20 @@
1717
package tech.httptoolkit.android.vpn;
1818

1919
import java.io.IOException;
20+
import java.net.InetAddress;
2021
import java.nio.ByteBuffer;
2122
import java.nio.channels.SelectionKey;
23+
import java.util.concurrent.ExecutorService;
24+
import java.util.concurrent.Executors;
2225

2326
import tech.httptoolkit.android.vpn.network.ip.IPPacketFactory;
2427
import tech.httptoolkit.android.vpn.network.ip.IPv4Header;
2528
import tech.httptoolkit.android.vpn.socket.SocketNIODataService;
26-
import tech.httptoolkit.android.vpn.transport.tcp.PacketHeaderException;
29+
import tech.httptoolkit.android.vpn.transport.PacketHeaderException;
30+
import tech.httptoolkit.android.vpn.transport.icmp.ICMPPacket;
31+
import tech.httptoolkit.android.vpn.transport.icmp.ICMPPacketFactory;
2732
import tech.httptoolkit.android.vpn.transport.tcp.TCPHeader;
2833
import tech.httptoolkit.android.vpn.transport.tcp.TCPPacketFactory;
29-
import tech.httptoolkit.android.vpn.transport.ITransportHeader;
3034
import tech.httptoolkit.android.vpn.transport.udp.UDPHeader;
3135
import tech.httptoolkit.android.vpn.transport.udp.UDPPacketFactory;
3236
import tech.httptoolkit.android.vpn.util.PacketUtil;
@@ -48,13 +52,41 @@ public class SessionHandler {
4852
private final SocketNIODataService nioService;
4953
private final ClientPacketWriter writer;
5054

55+
private final ExecutorService pingThreadpool = Executors.newCachedThreadPool();
56+
5157
public SessionHandler(SessionManager manager, SocketNIODataService nioService, ClientPacketWriter writer) {
5258
this.manager = manager;
5359
this.nioService = nioService;
5460
this.writer = writer;
5561
}
5662

57-
private void handleUDPPacket(ByteBuffer clientPacketData, IPv4Header ipHeader, UDPHeader udpheader){
63+
/**
64+
* Handle unknown raw IP packet data
65+
*
66+
* @param stream ByteBuffer to be read
67+
*/
68+
public void handlePacket(@NonNull ByteBuffer stream) throws PacketHeaderException, IOException {
69+
final byte[] rawPacket = new byte[stream.limit()];
70+
stream.get(rawPacket, 0, stream.limit());
71+
stream.rewind();
72+
73+
final IPv4Header ipHeader = IPPacketFactory.createIPv4Header(stream);
74+
75+
if (ipHeader.getProtocol() == 6) {
76+
handleTCPPacket(stream, ipHeader);
77+
} else if (ipHeader.getProtocol() == 17) {
78+
handleUDPPacket(stream, ipHeader);
79+
} else if (ipHeader.getProtocol() == 1) {
80+
handleICMPPacket(stream, ipHeader);
81+
} else {
82+
Log.w(TAG, "Unsupported IP protocol: " + ipHeader.getProtocol());
83+
return;
84+
}
85+
}
86+
87+
private void handleUDPPacket(ByteBuffer clientPacketData, IPv4Header ipHeader) throws PacketHeaderException {
88+
UDPHeader udpheader = UDPPacketFactory.createUDPHeader(clientPacketData);
89+
5890
Session session = manager.getSession(
5991
ipHeader.getDestinationIP(), udpheader.getDestinationPort(),
6092
ipHeader.getSourceIP(), udpheader.getSourcePort()
@@ -77,17 +109,18 @@ private void handleUDPPacket(ByteBuffer clientPacketData, IPv4Header ipHeader, U
77109
int len = manager.addClientData(clientPacketData, session);
78110
session.setDataForSendingReady(true);
79111

112+
Log.i(TAG, "Subscribe UDP to OP_WRITE");
80113
// Ping the NIO thread to write this, when the session is next writable
81114
session.subscribeKey(SelectionKey.OP_WRITE);
82115
nioService.refreshSelect(session);
83-
84-
Log.d(TAG,"Added " + len + " bytes of UDP data, ready to send");
116+
Log.d(TAG,"added UDP data for bg worker to send: "+len);
85117
}
86118

87119
manager.keepSessionAlive(session);
88120
}
89121

90-
private void handleTCPPacket(ByteBuffer clientPacketData, IPv4Header ipHeader, TCPHeader tcpheader){
122+
private void handleTCPPacket(ByteBuffer clientPacketData, IPv4Header ipHeader) throws PacketHeaderException {
123+
TCPHeader tcpheader = TCPPacketFactory.createTCPHeader(clientPacketData);
91124
int dataLength = clientPacketData.limit() - clientPacketData.position();
92125
int sourceIP = ipHeader.getSourceIP();
93126
int destinationIP = ipHeader.getDestinationIP();
@@ -176,34 +209,6 @@ private void handleTCPPacket(ByteBuffer clientPacketData, IPv4Header ipHeader, T
176209
}
177210
}
178211

179-
/**
180-
* handle each packet from each vpn client
181-
* @param stream ByteBuffer to be read
182-
*/
183-
public void handlePacket(@NonNull ByteBuffer stream) throws PacketHeaderException {
184-
final byte[] rawPacket = new byte[stream.limit()];
185-
stream.get(rawPacket, 0, stream.limit());
186-
stream.rewind();
187-
188-
final IPv4Header ipHeader = IPPacketFactory.createIPv4Header(stream);
189-
190-
final ITransportHeader transportHeader;
191-
if(ipHeader.getProtocol() == 6) {
192-
transportHeader = TCPPacketFactory.createTCPHeader(stream);
193-
} else if(ipHeader.getProtocol() == 17) {
194-
transportHeader = UDPPacketFactory.createUDPHeader(stream);
195-
} else {
196-
Log.e(TAG, "******===> Unsupported protocol: " + ipHeader.getProtocol());
197-
return;
198-
}
199-
200-
if (transportHeader instanceof TCPHeader) {
201-
handleTCPPacket(stream, ipHeader, (TCPHeader) transportHeader);
202-
} else if (ipHeader.getProtocol() == 17){
203-
handleUDPPacket(stream, ipHeader, (UDPHeader) transportHeader);
204-
}
205-
}
206-
207212
private void sendRstPacket(IPv4Header ip, TCPHeader tcp, int dataLength){
208213
byte[] data = TCPPacketFactory.createRstData(ip, tcp, dataLength);
209214

@@ -384,4 +389,48 @@ private void replySynAck(IPv4Header ip, TCPHeader tcp){
384389
Log.d(TAG,"Send SYN-ACK to client");
385390
}
386391
}
387-
}//end class
392+
393+
private void handleICMPPacket(
394+
ByteBuffer clientPacketData,
395+
final IPv4Header ipHeader
396+
) throws PacketHeaderException {
397+
final ICMPPacket requestPacket = ICMPPacketFactory.parseICMPPacket(clientPacketData);
398+
399+
pingThreadpool.execute(new Runnable() {
400+
@Override
401+
public void run() {
402+
try {
403+
if (!isReachable(PacketUtil.intToIPAddress(ipHeader.getDestinationIP()))) {
404+
Log.d(TAG, "Failed ping, ignoring");
405+
return;
406+
}
407+
408+
ICMPPacket response = ICMPPacketFactory.buildSuccessPacket(requestPacket);
409+
410+
// Flip the address
411+
int destination = ipHeader.getDestinationIP();
412+
int source = ipHeader.getSourceIP();
413+
ipHeader.setSourceIP(destination);
414+
ipHeader.setDestinationIP(source);
415+
416+
byte[] responseData = ICMPPacketFactory.packetToBuffer(ipHeader, response);
417+
418+
Log.d(TAG, "Successful ping response");
419+
writer.write(responseData);
420+
} catch (PacketHeaderException e) {
421+
Log.w(TAG, "Handling ICMP failed with " + e.getMessage());
422+
return;
423+
}
424+
}
425+
426+
private boolean isReachable(String ipAddress) {
427+
try {
428+
InetAddress destAddr = InetAddress.getByName(ipAddress);
429+
return destAddr.isReachable(10000);
430+
} catch (IOException e) {
431+
return false;
432+
}
433+
}
434+
});
435+
}
436+
}

app/src/main/java/tech/httptoolkit/android/vpn/network/ip/IPPacketFactory.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@
1818

1919
import androidx.annotation.NonNull;
2020

21-
import tech.httptoolkit.android.vpn.transport.tcp.PacketHeaderException;
21+
import tech.httptoolkit.android.vpn.transport.PacketHeaderException;
2222

2323
import java.nio.ByteBuffer;
2424
import java.nio.ByteOrder;

app/src/main/java/tech/httptoolkit/android/vpn/socket/SocketChannelReader.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
import tech.httptoolkit.android.vpn.Session;
88
import tech.httptoolkit.android.vpn.network.ip.IPPacketFactory;
99
import tech.httptoolkit.android.vpn.network.ip.IPv4Header;
10-
import tech.httptoolkit.android.vpn.transport.tcp.PacketHeaderException;
10+
import tech.httptoolkit.android.vpn.transport.PacketHeaderException;
1111
import tech.httptoolkit.android.vpn.transport.tcp.TCPHeader;
1212
import tech.httptoolkit.android.vpn.transport.tcp.TCPPacketFactory;
1313
import tech.httptoolkit.android.vpn.transport.udp.UDPHeader;

app/src/main/java/tech/httptoolkit/android/vpn/transport/tcp/PacketHeaderException.java renamed to app/src/main/java/tech/httptoolkit/android/vpn/transport/PacketHeaderException.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@
1414
* limitations under the License.
1515
*/
1616

17-
package tech.httptoolkit.android.vpn.transport.tcp;
17+
package tech.httptoolkit.android.vpn.transport;
1818
/**
1919
*
2020
* @author Borey Sao
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
package tech.httptoolkit.android.vpn.transport.icmp;
2+
3+
import java.nio.ByteBuffer;
4+
5+
import tech.httptoolkit.android.vpn.transport.PacketHeaderException;
6+
7+
public class ICMPPacket {
8+
public static final byte ECHO_REQUEST_TYPE = 8;
9+
public static final byte ECHO_SUCCESS_TYPE = 0;
10+
11+
final byte type;
12+
final byte code; // 0 for request, 0 for success, 0 - 15 for error subtypes
13+
14+
final int checksum;
15+
final int identifier;
16+
final int sequenceNumber;
17+
18+
final byte[] data;
19+
20+
ICMPPacket(
21+
int type,
22+
int code,
23+
int checksum,
24+
int identifier,
25+
int sequenceNumber,
26+
byte[] data
27+
) throws PacketHeaderException {
28+
if (type != ECHO_REQUEST_TYPE && type != ECHO_SUCCESS_TYPE) {
29+
throw new PacketHeaderException("ICMP packet with id must be request or response");
30+
}
31+
32+
this.type = (byte) type;
33+
this.code = (byte) code;
34+
this.checksum = checksum;
35+
this.identifier = identifier;
36+
this.sequenceNumber = sequenceNumber;
37+
this.data = data;
38+
}
39+
40+
public String toString() {
41+
return "ICMP packet type " + type + "/" + code + " id:" + identifier +
42+
" seq:" + sequenceNumber + " and " + data.length + " bytes of data";
43+
}
44+
}
Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,88 @@
1+
package tech.httptoolkit.android.vpn.transport.icmp;
2+
3+
import android.util.Log;
4+
5+
import androidx.annotation.NonNull;
6+
7+
import java.io.ByteArrayOutputStream;
8+
import java.nio.ByteBuffer;
9+
10+
import tech.httptoolkit.android.TagKt;
11+
import tech.httptoolkit.android.vpn.network.ip.IPPacketFactory;
12+
import tech.httptoolkit.android.vpn.network.ip.IPv4Header;
13+
import tech.httptoolkit.android.vpn.transport.PacketHeaderException;
14+
15+
import static tech.httptoolkit.android.vpn.util.PacketUtil.calculateChecksum;
16+
17+
public class ICMPPacketFactory {
18+
19+
public static ICMPPacket parseICMPPacket(@NonNull ByteBuffer stream) throws PacketHeaderException {
20+
final byte type = stream.get();
21+
final byte code = stream.get();
22+
final int checksum = stream.getShort();
23+
24+
final int identifier = stream.getShort();
25+
final int sequenceNumber = stream.getShort();
26+
27+
final byte[] data = new byte[stream.remaining()];
28+
stream.get(data);
29+
30+
if (type == 8) {
31+
return new ICMPPacket(type, code, checksum, identifier, sequenceNumber, data);
32+
} else {
33+
throw new PacketHeaderException("For ICMP, only echo requests are supported");
34+
}
35+
}
36+
37+
public static ICMPPacket buildSuccessPacket(ICMPPacket requestPacket) throws PacketHeaderException {
38+
return new ICMPPacket(
39+
0,
40+
0,
41+
0,
42+
requestPacket.identifier,
43+
requestPacket.sequenceNumber,
44+
requestPacket.data
45+
);
46+
}
47+
48+
public static byte[] packetToBuffer(IPv4Header ipHeader, ICMPPacket packet) throws PacketHeaderException {
49+
byte[] ipData = IPPacketFactory.createIPv4HeaderData(ipHeader);
50+
51+
ByteArrayOutputStream icmpDataBuffer = new ByteArrayOutputStream();
52+
icmpDataBuffer.write(packet.type);
53+
icmpDataBuffer.write(packet.code);
54+
55+
icmpDataBuffer.write(asShortBytes(0 /* checksum placeholder */), 0, 2);
56+
57+
if (packet.type == ICMPPacket.ECHO_REQUEST_TYPE || packet.type == ICMPPacket.ECHO_SUCCESS_TYPE) {
58+
icmpDataBuffer.write(asShortBytes(packet.identifier), 0, 2);
59+
icmpDataBuffer.write(asShortBytes(packet.sequenceNumber), 0, 2);
60+
61+
byte[] extraData = packet.data;
62+
icmpDataBuffer.write(extraData, 0, extraData.length);
63+
} else {
64+
throw new PacketHeaderException("Can't serialize unrecognized ICMP packet type");
65+
}
66+
67+
byte[] icmpPacketData = icmpDataBuffer.toByteArray();
68+
byte[] checksum = calculateChecksum(icmpPacketData, 0, icmpPacketData.length);
69+
70+
ByteBuffer resultBuffer = ByteBuffer.allocate(ipData.length + icmpPacketData.length);
71+
resultBuffer.put(ipData);
72+
resultBuffer.put(icmpPacketData);
73+
74+
// Replace the checksum placeholder
75+
resultBuffer.position(ipData.length + 2);
76+
resultBuffer.put(checksum);
77+
resultBuffer.position(0);
78+
79+
byte[] result = new byte[resultBuffer.remaining()];
80+
resultBuffer.get(result);
81+
return result;
82+
}
83+
84+
private static byte[] asShortBytes(int value) {
85+
return ByteBuffer.allocate(2).putShort((short) value).array();
86+
}
87+
88+
}

app/src/main/java/tech/httptoolkit/android/vpn/transport/tcp/TCPPacketFactory.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@
2323
import tech.httptoolkit.android.vpn.Packet;
2424
import tech.httptoolkit.android.vpn.network.ip.IPPacketFactory;
2525
import tech.httptoolkit.android.vpn.network.ip.IPv4Header;
26+
import tech.httptoolkit.android.vpn.transport.PacketHeaderException;
2627
import tech.httptoolkit.android.vpn.util.PacketUtil;
2728

2829
import java.nio.ByteBuffer;

app/src/main/java/tech/httptoolkit/android/vpn/transport/udp/UDPPacketFactory.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44

55
import tech.httptoolkit.android.vpn.network.ip.IPPacketFactory;
66
import tech.httptoolkit.android.vpn.network.ip.IPv4Header;
7-
import tech.httptoolkit.android.vpn.transport.tcp.PacketHeaderException;
7+
import tech.httptoolkit.android.vpn.transport.PacketHeaderException;
88
import tech.httptoolkit.android.vpn.util.PacketUtil;
99

1010
import java.nio.ByteBuffer;

0 commit comments

Comments
 (0)