@@ -94,6 +94,116 @@ val response = kanonProxy.takeResponse()
9494
9595There are more examples of usage in the [ tests] ( core/src/test/kotlin/com/jasonernst/kanonproxy ) .
9696
97+ ## Local Linux demo
98+
99+ An end-to-end demo on a single Linux host: a kanonproxy server, a kanonproxy
100+ client tunneling a TUN device to that server, and a ` curl ` issued through the
101+ proxy.
102+
103+ [ ![ Local Linux demo] ( https://img.youtube.com/vi/_ypo_3PYqTM/maxresdefault.jpg )] ( https://youtu.be/_ypo_3PYqTM )
104+
105+ (Click to watch on YouTube — the steps below produce what the video shows.)
106+
107+ Prerequisites:
108+ - Linux with ` iproute2 ` and ` sudo ` (TUN setup and ` --interface ` both need root)
109+ - JDK 21 (the Gradle wrapper handles Gradle itself)
110+ - ` ./gradlew :server:assemble :client:assemble ` succeeds
111+
112+ ### Path A — scripted (one-shot)
113+
114+ ``` bash
115+ bash client/scripts/demo.sh # uses 1.1.1.1 as the curl target
116+ bash client/scripts/demo.sh 9.9.9.9 # or pick your own HTTP-reachable IP
117+ ```
118+
119+ The script will:
120+ 1 . Run ` client/scripts/tuntap.sh ` to create the persistent ` kanon ` TUN device
121+ (` 10.0.1.1/24 ` , MTU 1024).
122+ 2 . Start the proxy server with ` ./gradlew :server:run --args="8080" ` (UDP
123+ listener on port 8080) — logs to ` build/demo-logs/server.log ` .
124+ 3 . Start the proxy client with ` ./gradlew :client:run --args="127.0.0.1 8080" ` —
125+ logs to ` build/demo-logs/client.log ` .
126+ 4 . Run ` sudo curl -v --interface kanon http://<target>/ ` . ` --interface kanon `
127+ uses ` SO_BINDTODEVICE ` to pin curl's socket to the TUN, so curl's packets
128+ go into the proxy without touching the kernel's main route table. The
129+ server's own outbound TCP socket stays unbound and follows the normal
130+ default route — that's what prevents the server from looping back into
131+ its own VPN.
132+
133+ The server and client stay running after the script finishes, so you can fire
134+ more requests against the same proxy:
135+ ``` bash
136+ sudo curl -v --interface kanon http://example.com/
137+ sudo curl -v --interface kanon http://1.1.1.1/
138+ ```
139+ Each call creates a new session — look for ` New session: ... ` lines in
140+ ` tail -f build/demo-logs/server.log ` .
141+
142+ Tear-down:
143+ ``` bash
144+ bash client/scripts/cleanup.sh
145+ ```
146+ This SIGTERMs the server/client/Gradle workers (then SIGKILLs anything left),
147+ retries ` ip tuntap del ` to handle any fd-release race, and removes the TUN
148+ interface. It's idempotent — safe to rerun.
149+
150+ ### Path B — manual (4 terminals)
151+
152+ Use this if you want to see each piece's output live, or to debug a failure
153+ from path A.
154+
155+ ** Terminal 1 — TUN device:**
156+ ``` bash
157+ bash client/scripts/tuntap.sh " $USER "
158+ ip addr show kanon # expect: kanon, inet 10.0.1.1/24, MTU 1024
159+ ```
160+
161+ ** Terminal 2 — server:**
162+ ``` bash
163+ ./gradlew :server:run --args=" 8080"
164+ # expect log: "Server listening on default port: 8080"
165+ # verify in another shell: ss -lun | grep 8080
166+ ```
167+
168+ ** Terminal 3 — client:**
169+ ``` bash
170+ ./gradlew :client:run --args=" 127.0.0.1 8080"
171+ # expect logs: "Opened TUN/TAP device" and "Created TUN/TAP device"
172+ ```
173+
174+ ** Terminal 4 — curl through the proxy:**
175+ ``` bash
176+ sudo curl -v --max-time 15 --interface kanon http://1.1.1.1/
177+ ```
178+ Success looks like a real HTTP response from ` 1.1.1.1 ` (almost certainly a
179+ ` 301 Moved Permanently ` to HTTPS — that proves the round trip).
180+
181+ Tear-down:
182+ ``` bash
183+ # Ctrl-C terminal 3 (client), then terminal 2 (server)
184+ bash client/scripts/cleanup.sh
185+ ip link show kanon || echo " kanon gone"
186+ ```
187+ If cleanup ever reports ` kanon interface is still present ` , run
188+ ` sudo lsof /dev/net/tun ` to find what's still holding the fd.
189+
190+ ### Watching packets while the demo runs
191+
192+ ` kanon ` is a regular kernel interface, so on Linux the easiest capture is
193+ plain libpcap on the device itself. The in-process pcap-ng dumpers
194+ ([ packetdumper] ( https://github.com/compscidr/packetdumper ) ) are also exposed
195+ in case you want to see the proxy's egress side (and they're what the Android
196+ app uses where you can't ` tcpdump ` the VPN interface from your laptop):
197+
198+ ``` bash
199+ sudo wireshark -k -i kanon # client/TUN leg, native libpcap
200+ wireshark -k -i TCP@127.0.0.1:19000 # client-side in-process dumper (same packets)
201+ wireshark -k -i TCP@127.0.0.1:19001 # server-side dumper (proxy's outbound to public Internet)
202+ ```
203+
204+ See [ Debugging with Wireshark] ( #debugging-with-wireshark ) below for more on
205+ the in-process dumpers.
206+
97207## Debugging with Wireshark
98208
99209Both the reference server/client and the Android sample app embed a
0 commit comments