trans_proxyA transparent proxy for macOS and Linux that intercepts TCP traffic via pf/nftables and forwards it through an upstream HTTP CONNECT or SOCKS5 proxy.
Uses DIOCNATLOOK ioctl on /dev/pf to recover original destinations from pf's NAT state table — the same technique mitmproxy uses.
Uses SO_ORIGINAL_DST getsockopt to recover original destinations from nftables redirect rules.
Use a SOCKS5 proxy as the upstream via socks5://host:port, with optional username/password auth (RFC 1928/1929).
Peeks at the TLS ClientHello to extract hostnames, sending proper CONNECT host:port instead of raw IPs. No TLS termination or certificate generation needed.
Listens directly on the gateway interface (port 53) for LAN client DNS queries. Builds an IP→domain lookup table. Supports DoH and UDP upstream.
Uses anchor-based pf rules (macOS) or a dedicated nftables table (Linux) so your existing firewall configuration is never touched.
Run as a background process with PID file and log file support. Start with -d, stop with kill.
Install as a system service (launchd on macOS, systemd on Linux) for automatic startup on boot. On Linux, nftables NAT rules are auto-managed. Use --install and --uninstall.
Built on tokio with per-connection task spawning. Handles many concurrent connections efficiently with bidirectional relay.
Full end-to-end test suite exercises the real nftables/pf + proxy pipeline on both Linux and macOS, covering SOCKS5, HTTP CONNECT, DNS forwarding, and port-selective redirect.
DIOCNATLOOK ioctl)# Clone the repository git clone https://github.com/madeye/trans_proxy.git cd trans_proxy # Build release binary cargo build --release # Verify cargo test ./target/release/trans_proxy --help
This example assumes your upstream HTTP proxy runs on 127.0.0.1:1082 and your LAN interface is en0.
Run with DNS on the gateway interface (uses Cloudflare DoH by default):
# Foreground (auto-detects en0 IP, listens on port 53) sudo ./target/release/trans_proxy \ --upstream-proxy 127.0.0.1:1082 \ --dns # Or as a daemon sudo ./target/release/trans_proxy \ --upstream-proxy 127.0.0.1:1082 \ --dns -d
In another terminal, install the pf rules (HTTP/HTTPS only — DNS is handled directly):
sudo scripts/pf_setup.sh en0 8443
The script prints your gateway IP and setup summary.
Set the Mac's IP as the default gateway (and DNS server) on each client device.
sudo scripts/pf_teardown.sh # If running as daemon sudo kill $(cat /var/run/trans_proxy.pid)
| Flag | Default | Description |
|---|---|---|
--listen-addr |
0.0.0.0:8443 | Address and port the proxy listens on |
--upstream-proxy |
required | Upstream proxy: host:port for HTTP CONNECT, socks5://host:port for SOCKS5 |
--log-level |
info | Log verbosity: trace, debug, info, warn, error |
--dns |
off | Enable DNS forwarder on the gateway interface (port 53) |
--interface |
en0 | Network interface for DNS auto-detection |
--dns-listen |
auto | Override DNS listen address (e.g., 192.168.1.42:53) |
--dns-upstream |
https://cloudflare-dns.com/dns-query | Upstream DNS: host:port for UDP, or https:// URL for DoH |
-d / --daemon |
off | Run as a background daemon |
--pid-file |
/var/run/trans_proxy.pid | PID file path (used with --daemon) |
--log-file |
/var/log/trans_proxy.log (daemon) | Log file path (defaults to stderr in foreground) |
--install |
off | Install as a system service (launchd on macOS, systemd on Linux) |
--uninstall |
off | Uninstall the system service |
# Setup: pf_setup.sh <interface> [proxy_port] # DNS no longer needs pf redirection — listens on port 53 directly sudo scripts/pf_setup.sh en0 8443 # Teardown: flushes anchor rules, disables IP forwarding sudo scripts/pf_teardown.sh
example.com:443 (resolved to 93.184.216.34)127.0.0.1:8443 (pf on macOS, nftables on Linux)DIOCNATLOOK (macOS) or SO_ORIGINAL_DST (Linux)example.com)CONNECT example.com:443 to the upstream proxyExtracted from TLS ClientHello. Works for HTTPS (port 443). No TLS termination needed.
IP→domain mapping from DNS responses (via DoH or UDP). Works for HTTP and HTTPS. Requires --dns.
Falls back to the IP address if no hostname can be determined. Always available.
NAT redirect rules rewrite the destination address before the socket layer sees it.
trans_proxy recovers the original destination using platform-specific mechanisms:
DIOCNATLOOK on macOS (queries pf's NAT state table, same as mitmproxy) and
SO_ORIGINAL_DST on Linux (recovers the pre-redirect destination from the kernel's conntrack).
On each device you want to route through the proxy, set the gateway machine's IP as the default gateway and DNS server.
Settings → Wi-Fi → (i) → Configure IP → Manual → Router & DNS: gateway IP
Settings → Network → Wi-Fi → Properties → Edit IP → Manual → Gateway & DNS: gateway IP
sudo ip route replace default via <ip> and update /etc/resolv.conf
Wi-Fi → Long press → Modify → Advanced → Static IP → Gateway & DNS: gateway IP
sudo. The proxy needs root to access /dev/pf.pfctl. macOS doesn't include ALTQ. pf redirection works fine without it.sudo pfctl -a trans_proxy -s rulessudo pfctl -s info | head -1--log-level debug for detailed per-connection logging.sysctl net.inet.ip.forwarding (should be 1).
--dns is set and the DNS forwarder is running.DNS forwarder listening on <ip>:53.dig @<gateway_ip> example.com