Skip to content
Merged
46 changes: 31 additions & 15 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,25 +2,41 @@
[![JVM Tests](https://github.com/compscidr/kanonproxy/actions/workflows/test.yml/badge.svg)](https://github.com/compscidr/kanonproxy/actions/workflows/test.yml) 
[![codecov](https://codecov.io/gh/compscidr/kanonproxy/graph/badge.svg?token=yBstrWw9Mm)](https://codecov.io/gh/compscidr/kanonproxy) 

An anonymous proxy written in kotlin.
An anonymous proxy written in kotlin.

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

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

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

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

Lastly there is a sample Android app which uses a local client and server alongside the
Android VPN functionality to intercept the packets.
It is up to consumers of this library to implement a server, a tun/tap adapter, or a
VPN service on Android to make use of it.

### `server`
A reference UDP-based proxy server (`ProxyServer`) built on top of `core`. It listens on
a `DatagramChannel`, parses inbound packets with knet, dispatches them to `KAnonProxy`,
and returns responses back to the originating client. Run it directly via
`ProxyServer.main` (defaults to port `8080`).

### `client`
A reference client (`ProxyClient`) that reads/writes a TUN adapter and tunnels the
parsed packets over UDP to a `ProxyServer`. `LinuxProxyClient` is the Linux entry point;
helper scripts to set up and tear down the TUN device live in [`client/scripts/`](client/scripts).
See [`client/README.md`](client/README.md) for setup details.

### `android`
A sample Android app (`KAnonVpnService` + `MainActivity`) that uses Android's `VpnService`
to intercept packets. It runs both `ProxyServer` and `AndroidClient` in-process, wired to
`IcmpAndroid` for ICMP support.

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

There are more examples of usage in the [tests](src/test/kotlin/com/jasonernst/kanonproxy)
There are more examples of usage in the [tests](core/src/test/kotlin/com/jasonernst/kanonproxy).
167 changes: 167 additions & 0 deletions docs/architecture.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,167 @@
# Architecture: how knet, icmp, and packetdumper fit into kanonproxy

This is an onboarding-oriented map of how the three external libraries
[knet](https://github.com/compscidr/knet),
[icmp](https://github.com/compscidr/icmp), and
[packetdumper](https://github.com/compscidr/packetdumper) interact with the
four kanonproxy modules (`core`, `client`, `server`, `android`).

## 1. Repo relationships at a glance

```mermaid
flowchart TD
subgraph kanon["<b>kanonproxy</b>"]
direction LR
core["<b>core</b><br/>proxy logic &amp; sessions"]
client["<b>client</b><br/>TUN/VPN ↔ proxy server"]
server["<b>server</b><br/>UDP listener for clients"]
android["<b>android</b><br/>sample VPN app"]
end

knet["<b>knet</b><br/>parses raw IP bytes<br/>into Packet objects;<br/>serializes them back"]
icmp["<b>icmp</b><br/>sends real ICMP echoes;<br/>platform-specific:<br/>IcmpLinux / IcmpAndroid"]
pd["<b>packetdumper</b><br/>writes pcap-ng / hex-dump<br/>captures to a file or<br/>live TCP server"]

kanon -- "uses: Packet, IpHeader, TcpHeader,<br/>UdpHeader, IcmpFactory, …" --> knet
kanon -- "uses: Icmp.ping(),<br/>IcmpV4/V6EchoPacket,<br/>DestinationUnreachable…" --> icmp
kanon -- "uses: AbstractPacketDumper,<br/>PcapNgTcpServerPacketDumper,<br/>DummyPacketDumper, EtherType" --> pd

classDef lib fill:#4f6df0,stroke:#1a2a8c,stroke-width:2px,color:#ffffff;
classDef mod fill:#2da44e,stroke:#1a6b34,stroke-width:2px,color:#ffffff;
classDef group fill:#1f6feb22,stroke:#58a6ff,stroke-width:2px,color:#c9d1d9;
class knet,icmp,pd lib;
class core,client,server,android mod;
class kanon group;
```

knet, icmp, and packetdumper are independent libraries (separate repos,
separate Maven Central artifacts) that kanonproxy depends on:

- **knet** — "what is this packet?" Parses raw IP bytes into typed `Packet`
objects and serializes them back.
- **icmp** — "actually emit a real ping." Provides the platform-specific raw-
socket implementations (`IcmpLinux`, `IcmpAndroid`).
- **packetdumper** — "see what's flowing through." An optional observability
hook injected into `ProxyServer` / `ProxyClient` / `KAnonVpnService` that
dumps every packet to a pcap-ng stream (live-tailable from Wireshark via
`wireshark -k -i TCP@127.0.0.1:19000`) or to a file. `DummyPacketDumper`
is the no-op default so it costs nothing in production.

kanonproxy stitches these together with its own session management, TCP
state machine, and platform glue.

## 2. Data flow — one packet round-trip

```mermaid
flowchart TB
subgraph coreMod["<b>core/</b> — KAnonProxy (pure logic, no I/O of its own)"]
kHandle["handlePacket(packet)"]
kSess["Session table<br/>(per clientAddress, per 5-tuple)"]
kTcpUdp["AnonymousTcpSession / UdpSession<br/>real SocketChannel / DatagramChannel"]
kIcmp["icmp.ping(destination)<br/>→ Success: IcmpV4/V6EchoPacket reply<br/>→ Failure: DestinationUnreachable"]
kErr["Session.handleExceptionOnRemoteChannel<br/>→ knet:IcmpFactory.createDestinationUnreachable"]
kHandle -- "TransportHeader (knet)" --> kSess --> kTcpUdp
kHandle -- "IcmpNextHeaderWrapper (knet)" --> kIcmp
kTcpUdp -.->|on connect failure| kErr
end

subgraph midRow[" "]
direction LR
subgraph clientMod["<b>client/</b> — LinuxProxyClient · AndroidClient extend ProxyClient"]
cParse["knet: Packet.parseStream"]
cChan["DatagramChannel<br/>(UDP to server)"]
cWrite["tunWrite ← packet.toByteArray()"]
cParse -- "parsed Packets" --> cChan
cChan -- "from server" --> cWrite
end
subgraph serverMod["<b>server/</b> — ProxyServer"]
sRead["readFromClient()<br/>knet: Packet.parseStream"]
sHandle["kAnonProxy.handlePackets()"]
sTake["kAnonProxy.takeResponse()<br/>via ProxySession"]
sOut["enqueueOutgoing(buffer)"]
sRead --> sHandle
sTake --> sOut
end
end

os[("OS / TUN-TAP / Android VPN<br/>raw IP byte stream")]
inet[("public Internet")]
pdLib(["<b>packetdumper</b><br/>dumpBuffer(...)"])
icmpLib(["<b>icmp</b> lib<br/>IcmpLinux / IcmpAndroid"])

%% core ↔ server
kHandle == "Packet (knet)" ==> sHandle
sTake -- "responses" --> kTcpUdp
sTake -- "responses" --> kIcmp

%% server ↔ client (UDP)
sRead == "UDP" ==> cChan
cChan == "UDP" ==> sOut

%% client ↔ OS
cParse -- "bytes in" --> os
os -- "bytes out" --> cWrite

%% core ↔ external services
kTcpUdp <--> inet
kIcmp <-.-> icmpLib

%% Packetdumper hooks (each packet on read/write)
cParse -.->|each packet| pdLib
cWrite -.->|each packet| pdLib
sRead -.->|each packet| pdLib
sOut -.->|each packet| pdLib

classDef ext fill:#d97706,stroke:#7c4a06,stroke-width:2px,color:#ffffff;
classDef io fill:#6b7280,stroke:#1f2937,stroke-width:2px,color:#ffffff;
classDef node fill:#1f6feb,stroke:#0d419d,stroke-width:2px,color:#ffffff;
classDef group fill:#1f6feb22,stroke:#58a6ff,stroke-width:2px,color:#c9d1d9;
classDef invisGroup fill:transparent,stroke:transparent;
class pdLib,icmpLib ext;
class os,inet io;
class cParse,cChan,cWrite,sRead,sHandle,sTake,sOut,kHandle,kSess,kTcpUdp,kIcmp,kErr node;
class clientMod,serverMod,coreMod group;
class midRow invisGroup;
```

Note: arrow directions in this diagram describe **rank/layout intent** (core on top,
server + client below) rather than the actual data direction. Data flows in both
directions on every edge — read the edge labels for what crosses the boundary.

## 3. Library responsibilities — who owns what

| Concern | Library | What it gives kanonproxy |
|---|---|---|
| Parse a raw IP byte stream into objects | **knet** | `Packet.parseStream(ByteBuffer)` |
Comment thread
compscidr marked this conversation as resolved.
| IP / TCP / UDP / ICMP header data classes (read + serialize) | **knet** | `Ipv4Header`, `Ipv6Header`, `TcpHeader`, `UdpHeader`, `IcmpNextHeaderWrapper`, `IcmpFactory` |
| Serialize a `Packet` back to bytes | **knet** | `packet.toByteArray()` |
| Actually emit ICMP echo to the real network | **icmp** | `Icmp.ping(InetAddress)` — `IcmpLinux` (raw socket) on JVM, `IcmpAndroid` on Android |
| ICMP echo / destination-unreachable packet types and codes | **icmp** | `IcmpV4EchoPacket`, `IcmpV6EchoPacket`, `IcmpV4/V6DestinationUnreachablePacket`, `IcmpV4/V6DestinationUnreachableCodes` |
| Capture a copy of every packet for debugging | **packetdumper** | `AbstractPacketDumper.dumpBuffer(ByteBuffer, EtherType)` |
| Live pcap-ng stream consumable by Wireshark | **packetdumper** | `PcapNgTcpServerPacketDumper` (default port 19000) |
| No-op dumper for production / tests | **packetdumper** | `DummyPacketDumper` |

Key point: **knet handles every other protocol end-to-end** (parse + build + serialize).
For TCP and UDP, kanonproxy itself opens the outbound connections using ordinary
`SocketChannel` / `DatagramChannel` instances inside `AnonymousTcpSession` / `UdpSession`.
**ICMP is the exception**: because emitting a real ICMP echo requires privileged raw-socket
access (and differs between Linux and Android), that emission is delegated to the icmp
library via `Icmp.ping(...)`, with `IcmpLinux` / `IcmpAndroid` providing the platform
implementation.

## 4. Where each library is wired in

- **`KAnonProxy(icmp: Icmp, ...)`** — single constructor injection point. Caller passes
`IcmpLinux` (server `main`) or `IcmpAndroid` (`KAnonVpnService` on Android).
- **`ProxyServer`** parses inbound UDP from clients with `Packet.parseStream` (knet) and
forwards into `KAnonProxy`. Also accepts an `AbstractPacketDumper` (packetdumper) and
calls `dumpBuffer(...)` on every packet in both directions; `ProxyServer.main` boots a
`PcapNgTcpServerPacketDumper` on port `DEFAULT_PORT + 1`.
- **`ProxyClient`** parses both directions with knet — TUN→proxy and proxy→TUN — and
dumps each packet through its injected `AbstractPacketDumper`.
- **`KAnonVpnService`** (android) wires a `PcapNgTcpServerPacketDumper` into the
in-process `ProxyServer` / `AndroidClient` so on-device captures can be tailed live
from Wireshark.
- **`Session.handleExceptionOnRemoteChannel`** uses knet's `IcmpFactory` to fabricate a
"host unreachable" reply when a TCP/UDP outbound connect fails — so even non-ICMP
failures synthesize ICMP responses, and that synthesis lives in knet.