A tunnel interface for HTTP and SOCKS proxies on Linux, Android, macOS, iOS and Windows.
This fork supports desktop --dns physical: query AliDNS 223.5.5.5, Tencent
DNSPod 119.29.29.29, AliDNS 223.6.6.6, then the physical adapter and configured
resolver in order. DNS UDP and TCP sockets bind to that interface. Explicit
process bypass uses this same resolver path, including cached Fake-IP recovery.
Platform-managed VPN providers retain their existing DNS modes. On Windows,
interface selection reads IP Helper snapshots without gateway MAC/ARP probing.
Embedders can reuse LocalProcessMatcher for local HTTP/SOCKS connections. It
matches only loopback callers and excludes the embedding process's own PID
before ancestry matching, preserving TUN-to-proxy routing. UDP relays preserve
empty and large datagrams and log route, socket endpoints and lifecycle counts.
Additional information can be found in the wiki
- HTTP proxy support (unauthenticated, basic and digest auth)
- SOCKS4 and SOCKS5 support (unauthenticated, username/password auth)
- SOCKS4a and SOCKS5h support (through the virtual DNS feature)
- Minimal configuration setup for routing all traffic
- IPv4 and IPv6 support
- GFW evasion mechanism for certain use cases (see issue #35)
- SOCKS5 UDP support
- Native support for proxying DNS over TCP
- UdpGW (UDP gateway) support for UDP over TCP, see the wiki for more information
- Session info embedding for per-app routing on Android (see below)
TCP forwarding preserves application data received in the same read as the successful HTTP CONNECT, SOCKS4 or SOCKS5 reply. This includes server-first protocols such as SSH, whose greeting must reach the client before it sends application data. The proxy handshake and application wire formats are unchanged.
This fork also offers opt-in --icmp-echo for IPv4/IPv6 ping through
proxy-everything's loopback SOCKS5 extension. It requires an unauthenticated
loopback endpoint implementing private command 0xe0; ordinary HTTP/SOCKS
servers do not provide this command. The extension is disabled by default.
Its 64 flow / four queued packet limits and four-second request deadlines keep
Echo from blocking TCP/UDP. Only matching real remote Echo replies are returned;
arbitrary IP protocols and fragmented Echo are outside its scope. The parent
repository documents the wire format and exit requirements.
DNS encoding requires Hickory 0.26.3 or later, which includes the name-compression CPU exhaustion fix for GHSA-q2qq-hmj6-3wpp. The fork requires Rust 1.95 or later. DNS validation, Fake-IP behavior and IPv6 filtering retain their existing wire behavior with the stable message API.
To enable per-app traffic routing on Android 10+, you can embed session information (protocol, source IP, source port) in the SOCKS5 username field. This allows your proxy server to call getConnectionOwnerUid() to identify which app initiated the connection.
To enable this feature, append +info to your username:
# Without session info (normal mode)
tun2proxy --proxy "socks5://user:pass@127.0.0.1:1080"
# With session info
tun2proxy --proxy "socks5://user+info:pass@127.0.0.1:1080"The proxy server will receive the username in the format:
original_username|protocol|src_ip|src_port
| is a literal field delimiter in this format. The client does not escape | characters in
original_username, so when using +info, the original username must not contain | (including
a | introduced via percent-encoding in the proxy URL), otherwise the value becomes ambiguous for
server-side parsers.
For example, user+info becomes user|tcp|10.0.0.5|54321.
Servers should parse this as exactly four |-separated fields and reject usernames that do not
meet that requirement.
Clone the repository and cd into the project folder. Then run the following:
cargo build --release
To build an XCFramework for macOS and iOS, run the following:
./build-apple.sh
Download the binary from releases and put it in your PATH.
Authenticity Verification
Since v0.2.23 build provenance attestations are supported. These allow you to ensure that the builds have been generated from the code on GitHub through the GitHub CI/CD pipeline. To verify the authenticity of the build files, you can use the GitHub CLI:
gh attestation verify <*.zip file> --owner tun2proxyIf you have rust toolchain installed, this should work:
cargo install tun2proxyNote: In Windows, you need to copy wintun DLL to the same directory as the binary. It's
%USERPROFILE%\.cargo\binby default.
For a local HTTP or SOCKS5 proxy listening on a loopback address, see the local proxy to TUN setup guide (Simplified Chinese).
Using --setup, you can have tun2proxy configure your system to automatically route all traffic through the
specified proxy. This requires running the tool as root and will roughly perform the steps outlined in the section
describing the manual setup, except that a bind mount is used to overlay the /etc/resolv.conf file.
On Windows, the executable detects an unelevated --setup run and relaunches itself through the UAC prompt while
preserving all command-line arguments, the working directory, and the current console. It does not open another console
window, so an administrator terminal does not need to be opened in advance. Linux and macOS users must still start the
command with the required privileges.
Windows auto-elevation is intended for interactive console sessions. The unelevated launcher remains alive while the
elevated child runs, so two tun2proxy-bin.exe processes are expected. The child reconnects its standard streams to the
original console; as a result, output redirection or pipelines may not be preserved. Headless launches, scheduled tasks,
and Windows services should be configured to run with the required privileges directly instead of relying on interactive
UAC. Keep the original terminal open and use Ctrl+C for normal route cleanup; a forced termination can leave setup
changes behind.
Windows --setup preserves every existing physical/VPN default route. It captures IPv4 with two more-specific /1
routes and an additional session-owned 0.0.0.0/0 compatibility row for forwarding consumers such as WSL HNS NAT.
It also snapshots and enables IPv4 forwarding on Wintun and WSL HNS vEthernet interfaces. Wintun uses weak-host
send/receive while capture is active so NAT-translated traffic is accepted even when its address belongs to another
interface. A lightweight monitor applies the same forwarding policy if WSL creates or replaces its interface after TUN
startup. All changes use the IP Helper API; teardown restores only fields and route rows changed by the current session,
together with the prior TUN DNS setting. It never performs an unqualified deletion of 0.0.0.0/0. Bypass routes are
resolved before capture is enabled and retain both the physical interface and next hop, which avoids selecting the TUN
itself. Windows 10 version 2004 (build 19041) or newer is required for transactional DNS setup. See the
local proxy to TUN setup guide for limitations and diagnostics.
You would then run the tool as follows:
sudo ./target/release/tun2proxy-bin --setup --proxy "socks5://1.2.3.4:1080"Apart from SOCKS5, SOCKS4 and HTTP are supported.
Note that if your proxy is a non-global IP address (e.g. because the proxy is provided by some tunneling tool running
locally), you will additionally need to provide the public IP address of the server through which the traffic is
actually tunneled. In such a case, the tool will tell you to specify the address through --bypass <IP/CIDR> if you
wish to make use of the automated setup feature.
A standard setup, which would route all traffic from your system through the tunnel interface, could look as follows:
# The proxy type can be either SOCKS4, SOCKS5 or HTTP.
PROXY_TYPE=SOCKS5
PROXY_IP=1.2.3.4
PROXY_PORT=1080
BYPASS_IP=123.45.67.89
# Create a tunnel interface named tun0 which you can bind to,
# so we don't need to run tun2proxy as root.
sudo ip tuntap add name tun0 mode tun
sudo ip link set tun0 up
# To prevent a routing loop, we add a route to the proxy server that behaves
# like the default route.
sudo ip route add "$BYPASS_IP" $(ip route | grep '^default' | cut -d ' ' -f 2-)
# Route all your traffic through tun0 without interfering with the default route.
sudo ip route add 128.0.0.0/1 dev tun0
sudo ip route add 0.0.0.0/1 dev tun0
# If you wish to also route IPv6 traffic through the proxy, these two commands will do.
sudo ip route add ::/1 dev tun0
sudo ip route add 8000::/1 dev tun0
# Make sure that DNS queries are routed through the tunnel.
sudo sh -c "echo nameserver 198.18.0.1 > /etc/resolv.conf"
./target/release/tun2proxy-bin --tun tun0 --proxy "$PROXY_TYPE://$PROXY_IP:$PROXY_PORT"This tool implements a virtual DNS feature that is used by switch --dns virtual. When a DNS packet to port 53 is detected, an IP
address from 198.18.0.0/15 is chosen and mapped to the query name. Connections destined for an IP address from that
range will supply the proxy with the mapped query name instead of the IP address. Since many proxies do not support UDP,
this enables an out-of-the-box experience in most cases, without relying on third-party resolvers or applications.
Depending on your use case, you may want to disable this feature using --dns direct.
In that case, you might need an additional tool like dnsproxy that is
configured to listen on a local UDP port and communicates with a third-party upstream DNS server via TCP.
When you terminate this program and want to eliminate the impact caused by the above several commands, you can execute the following command. The routes will be automatically deleted with the tunnel device.
sudo ip link del tun0When the upstream proxy is itself a local service (for example another tunnel listening on 127.0.0.1:1080),
a global --setup tunnel creates a routing loop: the proxy's own outbound traffic to the real servers is captured
by the TUN's catch-all route and fed straight back into the proxy, forever. --bypass <IP/CIDR> only helps when
the real destinations are known and fixed; it cannot cover the proxy's dynamic, arbitrary upstream connections.
--bypass-process solves this the way clash/mihomo's PROCESS-NAME rule does. For every new session arriving from
the TUN, tun2proxy looks up the local process that owns the originating socket and, if its executable name matches,
relays that session directly to its destination instead of forwarding it to the proxy — breaking the loop:
# Linux: send everything to a local proxy, but let the proxy's own traffic out directly.
sudo ./target/release/tun2proxy-bin --setup --proxy "socks5://127.0.0.1:1080" --bypass-process my-proxy# Windows (run elevated): same idea, match the proxy's executable name.
tun2proxy-bin.exe --setup --proxy "http://127.0.0.1:8080" --bypass-process my-proxy.exeNotes:
- The match is on the executable file name and is case-insensitive; a trailing
.exeis ignored, so--bypass-process curlmatches bothcurlandcurl.exe. The flag is repeatable. On Windows, a selected launcher/application root also covers its live descendant processes. Process names and parent relationships come from the system ToolHelp snapshot, so protected game and anti-cheat processes do not depend onOpenProcessaccess. - A bypassed session is relayed directly and its outbound socket is pinned to the physical network interface
(
SO_BINDTODEVICEon Linux,IP_UNICAST_IF/IPV6_UNICAST_IFon Windows). This is required: otherwise the relay would be re-captured by the TUN and the loop would merely move one hop. The interface is auto-detected from the default route before--setupinstalls the TUN routes; override it with--bind-interface <name>if detection picks the wrong one. - Process bypass takes precedence over virtual/over-TCP DNS and UdpGW handling. In particular, DNS requests from a bypassed process are sent directly so the process receives real addresses rather than virtual-DNS fake IPs. If an application cached a virtual address before it was selected, tun2proxy resolves the remembered domain again through a physical-interface-bound DNS socket before opening its direct relay.
- Live
ProcessBypassupdates compare the new policy with the owner/ancestor identities captured for each session. They do not repeat OS socket lookups for established sessions or reinterpret a missing/reused socket as another process. Unchanged relays stay open. Changed TCP relays send a reset before releasing their state (including DNS over TCP); changed UDP relays are recreated on the next datagram. An unidentified session stays proxied until it ends, and all new sessions still perform a fresh owner lookup. The TUN device and routes are not recreated. - Implemented on Windows, Linux, and macOS. On other platforms the flag is accepted but ignored (with a warning).
- Not compatible with the Linux
--unsharesocket-transfer path; use it with a normal--setuprun.
Tunnel interface to proxy.
Usage: tun2proxy-bin [OPTIONS] --proxy <URL> [ADMIN_COMMAND]...
Arguments:
[ADMIN_COMMAND]... Specify a command to run with root-like capabilities in the new namespace when using `--unshare`. This could be
useful to start additional daemons, e.g. `openvpn` instance
Options:
-p, --proxy <URL> Proxy URL in the form proto://[username[:password]@]host:port, where proto is one of
socks4, socks5, http. Username and password are encoded in percent encoding. For example:
socks5://myname:pass%40word@127.0.0.1:1080
-t, --tun <name> Name of the tun interface, such as tun0, utun4, etc. If this option is not provided, the
OS will generate a random one
--tun-fd <fd> File descriptor of the tun interface
--close-fd-on-drop <BOOL> Close the received file descriptor on drop (requires --tun-fd) [possible values: true,
false]
--unshare Create a tun interface in a newly created unprivileged namespace while maintaining proxy
connectivity via the global network namespace
--unshare-pidfile <path> Create a pidfile of `unshare` process when using `--unshare`
-6, --ipv6-enabled IPv6 enabled
-s, --setup Routing and system setup, which decides whether to setup the routing and system
configuration. This option requires root-like privileges on every platform. The
Windows binary automatically requests elevation through UAC when necessary. It is
very important on Linux, see `capabilities(7)`
-d, --dns <strategy> DNS handling strategy [default: direct] [possible values: virtual, over-tcp, direct]
--dns-addr <IP> DNS resolver address [default: 8.8.8.8]
--virtual-dns-pool <CIDR> IP address pool to be used by virtual DNS in CIDR notation [default: 198.18.0.0/15]
-b, --bypass <IP/CIDR> IPs used in routing setup which should bypass the tunnel, in the form of IP or IP/CIDR.
Multiple IPs can be specified, e.g. --bypass 3.4.5.0/24 --bypass 5.6.7.8
--bypass-process <PROCESS> Relay sessions originating from the named local process directly to their destination
instead of forwarding them to the proxy. The match is on the executable file name
(case-insensitive), e.g. --bypass-process curl or --bypass-process my-proxy.exe.
Repeatable. Primarily breaks routing loops when the upstream proxy is a local loopback
service. Only implemented on Windows and Linux; ignored elsewhere
--bind-interface <name> Physical network interface used to egress process-bypass direct relays. When omitted, the
default route interface is auto-detected. Only relevant on Windows and Linux
--mtu <bytes> MTU of the TUN device
--tcp-timeout <seconds> TCP timeout in seconds [default: 600]
--tcp-mss <bytes> MSS advertised in the SYN-ACK to the kernel side [default: none]
--udp-timeout <seconds> UDP timeout in seconds [default: 10]
-v, --verbosity <level> Verbosity level [default: info] [possible values: off, error, warn, info, debug, trace]
--daemonize Daemonize for unix family or run as Windows service
--exit-on-fatal-error Exit immediately when fatal error occurs, useful for running as a service
--max-sessions <number> Maximum number of sessions to be handled concurrently [default: 200]
--udpgw-server <IP:PORT> UDP gateway server address, forwards UDP packets via specified TCP server
--udpgw-connections <number> Max connections for the UDP gateway, default value is 5
--udpgw-keepalive <seconds> Keepalive interval in seconds for the UDP gateway, default value is 30
-h, --help Print help
-V, --version Print version
Currently, tun2proxy supports HTTP, SOCKS4/SOCKS4a and SOCKS5. A proxy is supplied to the --proxy argument in the
URL format. For example, an HTTP proxy at 1.2.3.4:3128 with a username of john.doe and a password of secret is
supplied as --proxy http://john.doe:secret@1.2.3.4:3128. This works analogously to curl's --proxy argument.
Tun2proxy can serve as a proxy for other Docker containers. To make use of that feature, first build the image:
docker buildx build -t tun2proxy .This will build an image containing a statically linked tun2proxy binary (based on musl) without OS.
Alternatively, you can build images based on Ubuntu or Alpine as follows:
docker buildx build -t tun2proxy --target tun2proxy-ubuntu .
docker buildx build -t tun2proxy --target tun2proxy-alpine .Next, start a container from the tun2proxy image:
docker run -d \
-v /dev/net/tun:/dev/net/tun \
--sysctl net.ipv6.conf.default.disable_ipv6=0 \
--cap-add NET_ADMIN \
--name tun2proxy \
tun2proxy --proxy proto://[username[:password]@]host:portYou can then provide the running container's network to another worker container by sharing the network namespace (like kubernetes sidecar):
docker run -it \
--network "container:tun2proxy" \
ubuntu:latestCreate a docker-compose.yaml file with the following content:
services:
tun2proxy:
volumes:
- /dev/net/tun:/dev/net/tun
sysctls:
- net.ipv6.conf.default.disable_ipv6=0
cap_add:
- NET_ADMIN
container_name: tun2proxy
image: ghcr.io/tun2proxy/tun2proxy-ubuntu:latest
command: --proxy proto://[username[:password]@]host:port
alpine:
stdin_open: true
tty: true
network_mode: container:tun2proxy
image: alpine:latest
command: apk add curl && curl ifconfig.icu && sleep 10Then run the compose file
docker compose up -d tun2proxy
docker compose up alpineWhen DNS resolution is performed by a service on your machine or through a server in your local network, DNS resolution
will not be performed through the tunnel interface, since the routes to localhost or your local network are more
specific than 0.0.0.0/1 and 128.0.0.0/1.
In this case, it may be advisable to update your /etc/resolv.conf file to use a nameserver address that is routed
through the tunnel interface. When virtual DNS is working correctly, you will see log messages like
DNS query: example.org for hostnames which your machine is connecting to after having resolved them through DNS.
Note that software like the NetworkManager may change the /etc/resolv.conf file automatically at any time, which
will result in DNS leaks. A hacky solution to prevent this consists in making the file immutable as follows:
sudo chattr +i "$(realpath /etc/resolv.conf)".
Some proxy servers might not support IPv6. When using virtual DNS, this is not a problem as DNS names are resolved by
the proxy server. When DNS names are resolved to IPv6 addresses locally, this becomes a problem as the proxy will be
asked to open connections to IPv6 destinations. In such a case, you can disable IPv6 on your machine. This can be done
either through sysctl -w net.ipv6.conf.all.disable_ipv6=1 and sysctl -w net.ipv6.conf.default.disable_ipv6=1
or through ip -6 route del default, which causes the libc resolver (and other software) to not issue DNS AAAA
requests for IPv6 addresses.
Thanks goes to these wonderful people: