You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
When the kanonproxy server and client run on the same Linux host with traffic
routed into the kanon TUN device (e.g. via ip route add <target> dev kanon),
the server's own outbound TCP/UDP socket — opened in AnonymousTcpSession / UdpSession against the same destination — gets routed
right back into kanon. The client picks it up, tunnels it to the server, the
server tries to connect again, and the cycle repeats until curl times out.
This is the Linux equivalent of what VpnService.protect() solves on Android.
Today there is no Linux protector implementation, so kanonproxy itself has no
loop protection on Linux.
The local Linux demo added in #244 sidesteps the issue at the caller by
pinning curl with --interface kanon (SO_BINDTODEVICE), so curl's packets
go through the TUN but the server's outbound packets don't. That's a usable
demo workaround but not a real fix — any caller that doesn't bind to kanon
will still loop.
Options
Option 3 (preferred): LinuxProtector in kanonproxy
Implement a Linux-specific VpnProtector that pins every outbound SocketChannel / DatagramChannel opened by Session / KAnonProxy to the
host's real egress interface via SO_BINDTODEVICE (or equivalent). Wire it
into ProxyServer.main (and into LinuxProxyClient if it grows the same
need).
Pros:
Loop becomes structurally impossible on Linux regardless of how callers
route traffic into the TUN.
Makes the Linux story symmetric with Android (IcmpAndroid + VpnService.protect() already do the same thing on the icmp side).
No special instructions for users — the obvious curl http://1.1.1.1/
through a dev kanon route would Just Work.
Cons:
Requires a code change in core (a new VpnProtector impl) plus a small
hook so the protector sees every newly-opened socket. The Java NIO API
doesn't expose SO_BINDTODEVICE directly, so the impl needs to grab the
underlying file descriptor (likely via reflection or a small JNI/JNA
helper, similar to how TunTapDevice already uses JNA / jnr-enxio).
The JVM running the proxy needs CAP_NET_RAW (or root) for SO_BINDTODEVICE to succeed.
Run the server as a dedicated user; add ip rule add uidrange <srv-uid>-<srv-uid> lookup main priority 50 so the server's own packets bypass any kanon route.
Then ip route add <target> dev kanon works for everyone else without
caller-side flags.
Pros:
No code change.
Any client on the host (curl, wget, browser) goes through the proxy.
Cons:
Requires a second user account and an ip rule setup outside the kanonproxy
process — easy to misconfigure / forget to tear down.
Run the server inside a netns connected to the host by a veth pair with
SNAT/MASQUERADE. The server's routing table is independent of the host's,
so the kanon route doesn't exist inside the netns and the loop is
structurally impossible.
Pros:
Cleanest isolation; closest to a realistic deployment model.
Cons:
Most setup of the three (netns + veth + NAT + cross-namespace UDP between
the client on the host and the server in the netns).
Still no help for any caller actually running on the host.
Recommendation
Implement Option 3 as the real fix. It belongs in the library (matches
the Android model), removes a footgun for any future Linux user of
kanonproxy, and makes Options 1 and 2 unnecessary.
Options 1 and 2 are documented here only for reference / as fallback
workarounds in the meantime.
core/src/main/kotlin/com/jasonernst/kanonproxy/VpnProtector.kt —
current VpnProtector interface; DummyProtector is the default on Linux.
android/src/main/kotlin/com/jasonernst/kanonproxy/KAnonVpnService.kt —
Android wires its own protect() via the VpnService; that's the model
to mirror on Linux.
Background
When the kanonproxy server and client run on the same Linux host with traffic
routed into the
kanonTUN device (e.g. viaip route add <target> dev kanon),the server's own outbound TCP/UDP socket — opened in
AnonymousTcpSession/UdpSessionagainst the same destination — gets routedright back into
kanon. The client picks it up, tunnels it to the server, theserver tries to connect again, and the cycle repeats until curl times out.
This is the Linux equivalent of what
VpnService.protect()solves on Android.Today there is no Linux protector implementation, so kanonproxy itself has no
loop protection on Linux.
The local Linux demo added in #244 sidesteps the issue at the caller by
pinning curl with
--interface kanon(SO_BINDTODEVICE), so curl's packetsgo through the TUN but the server's outbound packets don't. That's a usable
demo workaround but not a real fix — any caller that doesn't bind to
kanonwill still loop.
Options
Option 3 (preferred):
LinuxProtectorin kanonproxyImplement a Linux-specific
VpnProtectorthat pins every outboundSocketChannel/DatagramChannelopened bySession/KAnonProxyto thehost's real egress interface via
SO_BINDTODEVICE(or equivalent). Wire itinto
ProxyServer.main(and intoLinuxProxyClientif it grows the sameneed).
Pros:
route traffic into the TUN.
IcmpAndroid+VpnService.protect()already do the same thing on the icmp side).curl http://1.1.1.1/through a
dev kanonroute would Just Work.Cons:
core(a newVpnProtectorimpl) plus a smallhook so the protector sees every newly-opened socket. The Java NIO API
doesn't expose
SO_BINDTODEVICEdirectly, so the impl needs to grab theunderlying file descriptor (likely via reflection or a small JNI/JNA
helper, similar to how
TunTapDevicealready uses JNA /jnr-enxio).CAP_NET_RAW(or root) forSO_BINDTODEVICEto succeed.Option 1: UID-based policy routing (demo / ops workaround)
Run the server as a dedicated user; add
ip rule add uidrange <srv-uid>-<srv-uid> lookup main priority 50so the server's own packets bypass any kanon route.Then
ip route add <target> dev kanonworks for everyone else withoutcaller-side flags.
Pros:
Cons:
ip rulesetup outside the kanonproxyprocess — easy to misconfigure / forget to tear down.
Option 2: Network namespace (demo / ops workaround)
Run the server inside a netns connected to the host by a veth pair with
SNAT/MASQUERADE. The server's routing table is independent of the host's,
so the kanon route doesn't exist inside the netns and the loop is
structurally impossible.
Pros:
Cons:
the client on the host and the server in the netns).
Recommendation
Implement Option 3 as the real fix. It belongs in the library (matches
the Android model), removes a footgun for any future Linux user of
kanonproxy, and makes Options 1 and 2 unnecessary.
Options 1 and 2 are documented here only for reference / as fallback
workarounds in the meantime.
Related
--interface kanonon the curl side as a demo-only workaround.
core/src/main/kotlin/com/jasonernst/kanonproxy/VpnProtector.kt—current
VpnProtectorinterface;DummyProtectoris the default on Linux.android/src/main/kotlin/com/jasonernst/kanonproxy/KAnonVpnService.kt—Android wires its own
protect()via theVpnService; that's the modelto mirror on Linux.