Skip to content

Commit c03a945

Browse files
authored
Merge pull request #241 from compscidr/docs/architecture-knet-icmp
Add onboarding architecture doc for knet/icmp interaction
2 parents c2ff959 + 16f6974 commit c03a945

2 files changed

Lines changed: 198 additions & 15 deletions

File tree

README.md

Lines changed: 31 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -2,25 +2,41 @@
22
[![JVM Tests](https://github.com/compscidr/kanonproxy/actions/workflows/test.yml/badge.svg)](https://github.com/compscidr/kanonproxy/actions/workflows/test.yml) 
33
[![codecov](https://codecov.io/gh/compscidr/kanonproxy/graph/badge.svg?token=yBstrWw9Mm)](https://codecov.io/gh/compscidr/kanonproxy) 
44

5-
An anonymous proxy written in kotlin.
5+
An anonymous proxy written in kotlin.
66

7-
There are four modules for this project.
7+
For a deeper architectural overview of how the modules and external libraries fit together,
8+
see [docs/architecture.md](docs/architecture.md).
89

9-
## Core
10-
The core module is meant to be a library that can run on Android or Linux. It does not
11-
provide the client / server functionality. It is able to process packets which have
12-
been parsed by https://github.com/compscidr/knet, manage sessions and make outgoing
13-
anonymous requests based on the incoming traffic. It also receives the return traffic,
14-
and puts them into a queue. Outgoing ICMP requests are made by https://github.com/compscidr/icmp
10+
## Modules
1511

16-
It is up to consumers of the this library to implement a server or a tun/tap adapter, or a
17-
VPN service on Android to make use of this library.
12+
There are four modules in this project:
1813

19-
There are example implementations of a client and server in each of the respective modules
20-
as well.
14+
### `core`
15+
The core module is meant to be a library that can run on Android or Linux. It does not
16+
provide the client / server functionality. It is able to process packets which have
17+
been parsed by https://github.com/compscidr/knet, manage sessions and make outgoing
18+
anonymous requests based on the incoming traffic. It also receives the return traffic,
19+
and puts them into a queue. Outgoing ICMP requests are made by https://github.com/compscidr/icmp.
2120

22-
Lastly there is a sample Android app which uses a local client and server alongside the
23-
Android VPN functionality to intercept the packets.
21+
It is up to consumers of this library to implement a server, a tun/tap adapter, or a
22+
VPN service on Android to make use of it.
23+
24+
### `server`
25+
A reference UDP-based proxy server (`ProxyServer`) built on top of `core`. It listens on
26+
a `DatagramChannel`, parses inbound packets with knet, dispatches them to `KAnonProxy`,
27+
and returns responses back to the originating client. Run it directly via
28+
`ProxyServer.main` (defaults to port `8080`).
29+
30+
### `client`
31+
A reference client (`ProxyClient`) that reads/writes a TUN adapter and tunnels the
32+
parsed packets over UDP to a `ProxyServer`. `LinuxProxyClient` is the Linux entry point;
33+
helper scripts to set up and tear down the TUN device live in [`client/scripts/`](client/scripts).
34+
See [`client/README.md`](client/README.md) for setup details.
35+
36+
### `android`
37+
A sample Android app (`KAnonVpnService` + `MainActivity`) that uses Android's `VpnService`
38+
to intercept packets. It runs both `ProxyServer` and `AndroidClient` in-process, wired to
39+
`IcmpAndroid` for ICMP support.
2440

2541
## Usage
2642
The examples below show a contrived example where the packets are manually constructed. This
@@ -67,4 +83,4 @@ kAnonProxy.handlePackets(packets)
6783
val response = kanonProxy.takeResponse()
6884
```
6985

70-
There are more examples of usage in the [tests](src/test/kotlin/com/jasonernst/kanonproxy)
86+
There are more examples of usage in the [tests](core/src/test/kotlin/com/jasonernst/kanonproxy).

docs/architecture.md

Lines changed: 167 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,167 @@
1+
# Architecture: how knet, icmp, and packetdumper fit into kanonproxy
2+
3+
This is an onboarding-oriented map of how the three external libraries
4+
[knet](https://github.com/compscidr/knet),
5+
[icmp](https://github.com/compscidr/icmp), and
6+
[packetdumper](https://github.com/compscidr/packetdumper) interact with the
7+
four kanonproxy modules (`core`, `client`, `server`, `android`).
8+
9+
## 1. Repo relationships at a glance
10+
11+
```mermaid
12+
flowchart TD
13+
subgraph kanon["<b>kanonproxy</b>"]
14+
direction LR
15+
core["<b>core</b><br/>proxy logic &amp; sessions"]
16+
client["<b>client</b><br/>TUN/VPN ↔ proxy server"]
17+
server["<b>server</b><br/>UDP listener for clients"]
18+
android["<b>android</b><br/>sample VPN app"]
19+
end
20+
21+
knet["<b>knet</b><br/>parses raw IP bytes<br/>into Packet objects;<br/>serializes them back"]
22+
icmp["<b>icmp</b><br/>sends real ICMP echoes;<br/>platform-specific:<br/>IcmpLinux / IcmpAndroid"]
23+
pd["<b>packetdumper</b><br/>writes pcap-ng / hex-dump<br/>captures to a file or<br/>live TCP server"]
24+
25+
kanon -- "uses: Packet, IpHeader, TcpHeader,<br/>UdpHeader, IcmpFactory, …" --> knet
26+
kanon -- "uses: Icmp.ping(),<br/>IcmpV4/V6EchoPacket,<br/>DestinationUnreachable…" --> icmp
27+
kanon -- "uses: AbstractPacketDumper,<br/>PcapNgTcpServerPacketDumper,<br/>DummyPacketDumper, EtherType" --> pd
28+
29+
classDef lib fill:#4f6df0,stroke:#1a2a8c,stroke-width:2px,color:#ffffff;
30+
classDef mod fill:#2da44e,stroke:#1a6b34,stroke-width:2px,color:#ffffff;
31+
classDef group fill:#1f6feb22,stroke:#58a6ff,stroke-width:2px,color:#c9d1d9;
32+
class knet,icmp,pd lib;
33+
class core,client,server,android mod;
34+
class kanon group;
35+
```
36+
37+
knet, icmp, and packetdumper are independent libraries (separate repos,
38+
separate Maven Central artifacts) that kanonproxy depends on:
39+
40+
- **knet** — "what is this packet?" Parses raw IP bytes into typed `Packet`
41+
objects and serializes them back.
42+
- **icmp** — "actually emit a real ping." Provides the platform-specific raw-
43+
socket implementations (`IcmpLinux`, `IcmpAndroid`).
44+
- **packetdumper** — "see what's flowing through." An optional observability
45+
hook injected into `ProxyServer` / `ProxyClient` / `KAnonVpnService` that
46+
dumps every packet to a pcap-ng stream (live-tailable from Wireshark via
47+
`wireshark -k -i TCP@127.0.0.1:19000`) or to a file. `DummyPacketDumper`
48+
is the no-op default so it costs nothing in production.
49+
50+
kanonproxy stitches these together with its own session management, TCP
51+
state machine, and platform glue.
52+
53+
## 2. Data flow — one packet round-trip
54+
55+
```mermaid
56+
flowchart TB
57+
subgraph coreMod["<b>core/</b> — KAnonProxy (pure logic, no I/O of its own)"]
58+
kHandle["handlePacket(packet)"]
59+
kSess["Session table<br/>(per clientAddress, per 5-tuple)"]
60+
kTcpUdp["AnonymousTcpSession / UdpSession<br/>real SocketChannel / DatagramChannel"]
61+
kIcmp["icmp.ping(destination)<br/>→ Success: IcmpV4/V6EchoPacket reply<br/>→ Failure: DestinationUnreachable"]
62+
kErr["Session.handleExceptionOnRemoteChannel<br/>→ knet:IcmpFactory.createDestinationUnreachable"]
63+
kHandle -- "TransportHeader (knet)" --> kSess --> kTcpUdp
64+
kHandle -- "IcmpNextHeaderWrapper (knet)" --> kIcmp
65+
kTcpUdp -.->|on connect failure| kErr
66+
end
67+
68+
subgraph midRow[" "]
69+
direction LR
70+
subgraph clientMod["<b>client/</b> — LinuxProxyClient · AndroidClient extend ProxyClient"]
71+
cParse["knet: Packet.parseStream"]
72+
cChan["DatagramChannel<br/>(UDP to server)"]
73+
cWrite["tunWrite ← packet.toByteArray()"]
74+
cParse -- "parsed Packets" --> cChan
75+
cChan -- "from server" --> cWrite
76+
end
77+
subgraph serverMod["<b>server/</b> — ProxyServer"]
78+
sRead["readFromClient()<br/>knet: Packet.parseStream"]
79+
sHandle["kAnonProxy.handlePackets()"]
80+
sTake["kAnonProxy.takeResponse()<br/>via ProxySession"]
81+
sOut["enqueueOutgoing(buffer)"]
82+
sRead --> sHandle
83+
sTake --> sOut
84+
end
85+
end
86+
87+
os[("OS / TUN-TAP / Android VPN<br/>raw IP byte stream")]
88+
inet[("public Internet")]
89+
pdLib(["<b>packetdumper</b><br/>dumpBuffer(...)"])
90+
icmpLib(["<b>icmp</b> lib<br/>IcmpLinux / IcmpAndroid"])
91+
92+
%% core ↔ server
93+
kHandle == "Packet (knet)" ==> sHandle
94+
sTake -- "responses" --> kTcpUdp
95+
sTake -- "responses" --> kIcmp
96+
97+
%% server ↔ client (UDP)
98+
sRead == "UDP" ==> cChan
99+
cChan == "UDP" ==> sOut
100+
101+
%% client ↔ OS
102+
cParse -- "bytes in" --> os
103+
os -- "bytes out" --> cWrite
104+
105+
%% core ↔ external services
106+
kTcpUdp <--> inet
107+
kIcmp <-.-> icmpLib
108+
109+
%% Packetdumper hooks (each packet on read/write)
110+
cParse -.->|each packet| pdLib
111+
cWrite -.->|each packet| pdLib
112+
sRead -.->|each packet| pdLib
113+
sOut -.->|each packet| pdLib
114+
115+
classDef ext fill:#d97706,stroke:#7c4a06,stroke-width:2px,color:#ffffff;
116+
classDef io fill:#6b7280,stroke:#1f2937,stroke-width:2px,color:#ffffff;
117+
classDef node fill:#1f6feb,stroke:#0d419d,stroke-width:2px,color:#ffffff;
118+
classDef group fill:#1f6feb22,stroke:#58a6ff,stroke-width:2px,color:#c9d1d9;
119+
classDef invisGroup fill:transparent,stroke:transparent;
120+
class pdLib,icmpLib ext;
121+
class os,inet io;
122+
class cParse,cChan,cWrite,sRead,sHandle,sTake,sOut,kHandle,kSess,kTcpUdp,kIcmp,kErr node;
123+
class clientMod,serverMod,coreMod group;
124+
class midRow invisGroup;
125+
```
126+
127+
Note: arrow directions in this diagram describe **rank/layout intent** (core on top,
128+
server + client below) rather than the actual data direction. Data flows in both
129+
directions on every edge — read the edge labels for what crosses the boundary.
130+
131+
## 3. Library responsibilities — who owns what
132+
133+
| Concern | Library | What it gives kanonproxy |
134+
|---|---|---|
135+
| Parse a raw IP byte stream into objects | **knet** | `Packet.parseStream(ByteBuffer)` |
136+
| IP / TCP / UDP / ICMP header data classes (read + serialize) | **knet** | `Ipv4Header`, `Ipv6Header`, `TcpHeader`, `UdpHeader`, `IcmpNextHeaderWrapper`, `IcmpFactory` |
137+
| Serialize a `Packet` back to bytes | **knet** | `packet.toByteArray()` |
138+
| Actually emit ICMP echo to the real network | **icmp** | `Icmp.ping(InetAddress)``IcmpLinux` (raw socket) on JVM, `IcmpAndroid` on Android |
139+
| ICMP echo / destination-unreachable packet types and codes | **icmp** | `IcmpV4EchoPacket`, `IcmpV6EchoPacket`, `IcmpV4/V6DestinationUnreachablePacket`, `IcmpV4/V6DestinationUnreachableCodes` |
140+
| Capture a copy of every packet for debugging | **packetdumper** | `AbstractPacketDumper.dumpBuffer(ByteBuffer, EtherType)` |
141+
| Live pcap-ng stream consumable by Wireshark | **packetdumper** | `PcapNgTcpServerPacketDumper` (default port 19000) |
142+
| No-op dumper for production / tests | **packetdumper** | `DummyPacketDumper` |
143+
144+
Key point: **knet handles every other protocol end-to-end** (parse + build + serialize).
145+
For TCP and UDP, kanonproxy itself opens the outbound connections using ordinary
146+
`SocketChannel` / `DatagramChannel` instances inside `AnonymousTcpSession` / `UdpSession`.
147+
**ICMP is the exception**: because emitting a real ICMP echo requires privileged raw-socket
148+
access (and differs between Linux and Android), that emission is delegated to the icmp
149+
library via `Icmp.ping(...)`, with `IcmpLinux` / `IcmpAndroid` providing the platform
150+
implementation.
151+
152+
## 4. Where each library is wired in
153+
154+
- **`KAnonProxy(icmp: Icmp, ...)`** — single constructor injection point. Caller passes
155+
`IcmpLinux` (server `main`) or `IcmpAndroid` (`KAnonVpnService` on Android).
156+
- **`ProxyServer`** parses inbound UDP from clients with `Packet.parseStream` (knet) and
157+
forwards into `KAnonProxy`. Also accepts an `AbstractPacketDumper` (packetdumper) and
158+
calls `dumpBuffer(...)` on every packet in both directions; `ProxyServer.main` boots a
159+
`PcapNgTcpServerPacketDumper` on port `DEFAULT_PORT + 1`.
160+
- **`ProxyClient`** parses both directions with knet — TUN→proxy and proxy→TUN — and
161+
dumps each packet through its injected `AbstractPacketDumper`.
162+
- **`KAnonVpnService`** (android) wires a `PcapNgTcpServerPacketDumper` into the
163+
in-process `ProxyServer` / `AndroidClient` so on-device captures can be tailed live
164+
from Wireshark.
165+
- **`Session.handleExceptionOnRemoteChannel`** uses knet's `IcmpFactory` to fabricate a
166+
"host unreachable" reply when a TCP/UDP outbound connect fails — so even non-ICMP
167+
failures synthesize ICMP responses, and that synthesis lives in knet.

0 commit comments

Comments
 (0)