Skip to content

Commit 94a6613

Browse files
committed
Add detailed docs & sprinkle logging in the VPN internals
1 parent 8d40bfe commit 94a6613

File tree

4 files changed

+26
-10
lines changed

4 files changed

+26
-10
lines changed
Lines changed: 17 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,20 @@
1-
# ToyShark VPN packet capture implementation
1+
# Android packet-intercepting VPN
22

3-
This package contains the logic that captures and then forwards packets.
3+
This package contains the logic that captures and then forwards packets, on device, using the Android VPN interface.
44

5-
It's been heavily edited, but is originally based on [ToyShark](https://github.com/LipiLee/ToyShark/tree/0963ad8bda35cd2b2e8e0ea0a47873683b604453) (under the Apache 2 license).
5+
It provides the components (woven together in ProxyVpnRunnable) that read raw IP packets from the VPN interface, parse them, make the corresponding upstream TCP/UDP requests, and proxy data between the two.
66

7-
The full source & license is available from that URL.
7+
The code has been very heavily edited, but was originally based on [ToyShark](https://github.com/LipiLee/ToyShark/tree/0963ad8bda35cd2b2e8e0ea0a47873683b604453), which was in turn based on AT&T's [ARO project](https://github.com/attdevsupport/ARO/), all used under the Apache 2 license. The full original source & license is available from those repo URLs.
8+
9+
## Details
10+
11+
Some quick notes on how this all hangs together:
12+
13+
* The VPN interface gives us a file descriptor, from which we can read & write raw IP packets.
14+
* On Android we can't actually send or receive raw IP packets from upstream, without native code, at least.
15+
* To handle this, we parse the IP packets, work out how to do the equivalent TCP/UDP/ICMP upstream, do it, and proxy between that & the VPN interface/
16+
* SessionHandler handles the VPN packet side of this: it receives IP packets in `handlePacket` from a thread that loops on `vpn.read()`, handles ACKs etc, and makes calls to `SessionManager` to create/close upstream connections when required.
17+
* SessionManager allows opening and closing upstream sessions and their channels (TCP/UDP connections), registering each channel with `SocketNIODataService`.
18+
* SocketNIODataService runs on a single thread, using NIO to write VPN-received data from the session to the upstream channel when the channel is available, and to read data from upstream channels when it's received.
19+
* Data is sent back into the VPN (by both SessionHandler and the NIO thread) via ClientPacketWriter, which runs on its own thread, looping on a blocking queue to do each requested write.
20+
* SessionManager can be configured with traffic redirections, to redirect traffic on certain ports to different destinations (e.g. all outgoing traffic on 80/443 to a transparent proxy server).

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

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -405,6 +405,7 @@ private void handleICMPPacket(
405405
final IPv4Header ipHeader
406406
) throws PacketHeaderException {
407407
final ICMPPacket requestPacket = ICMPPacketFactory.parseICMPPacket(clientPacketData);
408+
Log.d(TAG, "Got an ICMP ping packet, type " + requestPacket.toString());
408409

409410
pingThreadpool.execute(new Runnable() {
410411
@Override
@@ -435,8 +436,7 @@ public void run() {
435436

436437
private boolean isReachable(String ipAddress) {
437438
try {
438-
InetAddress destAddr = InetAddress.getByName(ipAddress);
439-
return destAddr.isReachable(10000);
439+
return InetAddress.getByName(ipAddress).isReachable(10000);
440440
} catch (IOException e) {
441441
return false;
442442
}

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

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -124,11 +124,13 @@ private void readTCP(@NonNull Session session) {
124124
}
125125

126126
private void sendToRequester(ByteBuffer buffer, int dataSize, @NonNull Session session){
127-
//last piece of data is usually smaller than MAX_RECEIVE_BUFFER_SIZE
128-
if(dataSize < DataConst.MAX_RECEIVE_BUFFER_SIZE)
127+
// Last piece of data is usually smaller than MAX_RECEIVE_BUFFER_SIZE. We use this as a
128+
// trigger to set PSH on the resulting TCP packet that goes to the VPN.
129+
if (dataSize < DataConst.MAX_RECEIVE_BUFFER_SIZE) {
129130
session.setHasReceivedLastSegment(true);
130-
else
131+
} else {
131132
session.setHasReceivedLastSegment(false);
133+
}
132134

133135
buffer.limit(dataSize);
134136
buffer.flip();

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

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,8 @@ public void write(@NonNull Session session) {
4343
} else if(channel instanceof DatagramChannel) {
4444
writeUDP(session);
4545
} else {
46-
return;
46+
// We only ever create TCP & UDP channels, so this should never happen
47+
throw new IllegalArgumentException("Unexpected channel type: " + channel);
4748
}
4849

4950
if(session.isAbortingConnection()){

0 commit comments

Comments
 (0)