Transparent Tor Proxy
TTP is a Linux CLI tool that intercepts
all outgoing network traffic
and forces it through the Tor network using
nftables, without requiring
per-application configuration. It uses modern Linux primitives,
loads firewall rules atomically, and is designed to always restore
your network, even after a crash or power outage.
Installation
Requirements
| Requirement | Notes |
|---|---|
| Linux with systemd | Tested on Debian 13 Trixie and Fedora 41+ |
| Python 3.10+ | Required for union type hints and modern stdlib features |
| nftables | Pre-installed on Debian 12+, Fedora 33+, Arch. Replaces iptables as firewall backend. |
| Root privileges |
Required for firewall and DNS modifications, and all
commands must be run with sudo
|
System-wide deployment (Recommended)
For most users, we recommend using the system-wide installer. This
places TTP in /opt/ttp, creates a
symbolic link in /usr/local/bin, and
sets up all necessary permissions.
Alternative: Local pip installation
ttp command is
registered system-wide via the
pyproject.toml entry point
ttp = "ttp.cli:app". Tor itself does
not need to be pre-installed, as TTP detects its absence and
installs it automatically using the system's package manager.
Python dependencies
| Package | Source | Purpose |
|---|---|---|
| stem | PyPI |
Official Python library from the Tor Project. Used to
connect to Tor's Control Port/Socket, monitor bootstrap
progress, and send
NEWNYM signals.
|
| typer | PyPI |
Modern CLI framework. Provides argument parsing,
--help generation, and clean
command routing.
|
| rich | PyPI | Terminal output formatting: panels, progress bars, colored text. |
Architecture
TTP is structured as a set of independent Python modules, each
with a single responsibility. No module imports from
cli.py. UI logic (rich, typer) is confined exclusively to
cli.py while all other modules return
plain data.
Read-only module. Checks if Tor is installed (shutil.which), running (systemctl is-active +
pgrep), and correctly configured
(TransPort 9040,
DNSPort 9053,
ControlPort 9051 in torrc).
Dynamically detects the Tor system user and SELinux
enforcement state. Returns a structured dictionary, but never
modifies anything.
Invoked only when
tor_detect reports Tor is missing
or misconfigured. Detects the system package manager (apt-get, pacman,
dnf,
zypper) and installs Tor. Performs
SELinux optimization on Fedora/RHEL, sanitizes
/etc/tor/torrc, validates with
tor --verify-config, then restarts
the correct systemd unit.
Implements a multi-chain, "Safe-Release" architecture.
Generates a stateless ruleset with three chains:
prerouting,
output (NAT), and
filter_out (Filter). The latter
acts as a dedicated Kill-Switch. Applied
atomically via nft -f.
Manages DNS redirection to
127.0.0.1.
Mode 1 uses
resolvectl to set DNS and the
global routing domain (~.) when
active. Mode 2 overwrites
/etc/resolv.conf. Uses a
Hard-Reset strategy (revert → restart → flush
caches) for robust restoration.
Writes and reads a JSON lock file at
/var/lib/ttp/ttp.lock containing:
pid,
timestamp,
dns_backup, and
dns_mode. On startup, checks if
the lock's PID is still alive. A dead PID means an orphaned
session, exposing
attempt_recovery() to
auto-restore.
Encapsulates all Stem library usage. Connects to Tor's control
interface, monitors bootstrap progress via
status/bootstrap-phase, sends
NEWNYM to rotate circuits, and
verifies actual Tor routing via
check.torproject.org/api/ip.
Pure data-gathering module used by
ttp diagnose. Collects OS info,
Tor service status, active torrc settings, nftables ruleset,
DNS state, and Tor control interface status. Returns a flat
dict[str, str]. Every subprocess
call is wrapped in a try/except so a single failure never
aborts the full diagnostic.
Defines a custom exception hierarchy for the project. Includes
TTPError (base) and specialized
classes: FirewallError,
DNSError,
StateError, and
TorError. Used to distinguish
between fatal issues and recoverable states.
Execution Flow
ttp start
1. Root check, Signal registration & SELinux
Verifies os.geteuid() == 0.
Registers SIGINT and
SIGTERM handlers. On Fedora/RHEL,
ensures the TTP SELinux policy module is installed to allow
Tor port binding.
2. Orphan detection
Reads /var/lib/ttp/ttp.lock. If
the lock exists and its PID is dead, the session is orphaned.
state.attempt_recovery() is called
automatically to restore firewall and DNS.
3. Tor detection & auto-install
tor_install.ensure_tor_ready()
orchestrates detection → install → torrc configuration →
service restart. The torrc is always validated before
restarting.
4. Stateless multi-chain firewall application
A stateless ruleset is written to
/var/lib/ttp/ttp.rules and loaded
atomically via nft -f. This
includes NAT redirection and a filter-based
Kill-Switch to prevent leaks during bootstrap
or unexpected process failure.
5. DNS redirection
The active network interface is auto-detected. DNS is
redirected to 127.0.0.1 using
resolvectl or by overwriting
/etc/resolv.conf.
6. Lock file write
A JSON lock file is written to
/var/lib/ttp/ttp.lock containing
DNS backup info and PID. This is the only persistent record of
the active session.
7. Bootstrap wait & verification
Connects to Tor's control interface and polls
status/bootstrap-phase. Once at
100%, waits 2 seconds for circuits to settle, then verifies
routing against multiple endpoints (e.g.,
check.torproject.org).
ttp stop
Restore firewall
Removes the TTP rules by destroying the table:
nft destroy table inet ttp.
Restore DNS
Reads DNS mode and backup from lock. Performs a Hard-Reset
(resolvectl revert, restart
service, flush caches) or rewrites
/etc/resolv.conf.
Delete lock
Removes /var/lib/ttp/ttp.lock.
Session is terminated.
Firewall Rules
TTP generates a single nftables table. The ruleset is written to a
temporary file and loaded with a single
nft -f call, meaning all rules apply
atomically, or none do.
udp dport 53 dnat ip to 127.0.0.1:9053) must appear before the loopback accept rule in
the output chain. When
resolv.conf points to
127.0.0.1, DNS queries are addressed
to the loopback interface. If the loopback accept rule fires
first, the packet is accepted without redirection, causing silent
DNS leak.
filter_out chain that acts as a global
Kill-Switch. It explicitly allows Tor, root, and
local traffic while rejecting everything else. This ensures that
no cleartext traffic can bypass the NAT redirection, covering edge
cases like pre-existing connections or processes failing to be
intercepted.
DNS Handling
DNS leak prevention is critical for anonymity. TTP redirects all
DNS (UDP port 53) to Tor's DNSPort at
127.0.0.1:9053 via the nftables NAT
rule, and additionally redirects the system resolver to
127.0.0.1.
Used when systemd-resolved is
active. Runs:
resolvectl dns <iface> 127.0.0.1. Integrates cleanly with the resolver daemon, no file
overwrite.
Used when systemd-resolved is not
active. Saves the original content of
/etc/resolv.conf, then overwrites
it. Never used on systems where
systemd-resolved is running.
CLI Reference
All commands require sudo. Global
flags --verbose /
-v and
--quiet /
-q are available on all commands.
Starts the transparent proxy session. Orchestrates the full startup sequence.
Stops the session and restores the network to its original state.
Requests a new Tor circuit without stopping the session. Traffic continues to flow through Tor uninterrupted while the circuit rotates.
Shows the current session status. Reads the lock file and fetches the current exit IP.
Runs a comprehensive system diagnostic and prints a detailed report. Checks Tor service status, torrc configuration, active nftables rules, and DNS settings.
Performs a deep cleanup of the TTP state. Stops any active
session, removes the SELinux policy module, and deletes log
files. Note: This does not remove the
ttp binary itself; for that, use
uninstall.sh.
Crash Recovery
TTP is designed around the assumption that any process can die at
any time. The lock file at
/var/lib/ttp/ttp.lock contains all
information needed to undo changes.
Reads lock → restores firewall via backup file → restores DNS via saved content → deletes lock. Clean exit.
Signal handlers registered at startup intercept the signal
and call the same cleanup routine as
ttp stop before exiting.
The lock file survives. On the next
ttp start, if the process is
dead, the lock is orphaned and
state.attempt_recovery() is
called automatically to restore firewall and DNS.
Or use the nuclear option:
sudo ./restore-network.sh
Distro Support
TTP handles the main differences between distributions automatically.
| Distribution | Service Unit | Tor User | Package Manager | Status |
|---|---|---|---|---|
| Debian 13 | tor@default |
debian-tor |
apt-get |
Tested (VM) |
| Ubuntu 22.04+ | tor@default |
debian-tor |
apt-get |
Tested (VM) |
| Fedora 41+ | tor |
toranon |
dnf |
Tested (Docker) |
| Arch Linux | tor |
tor |
pacman |
Tested (Docker) |
| openSUSE | tor |
_tor |
zypper |
Experimental |
tor.service is a
multi-instance master that reports active (exited) even
when no daemon is actually running. The real daemon lives in
tor@default.service. TTP detects this
safely.
Development
Running unit tests
Unit tests are fully mocked, no root, no network, no system modifications.
VM integration tests
Integration tests require root and a real Tor daemon. They run inside a QEMU VM (Debian 13) or Docker containers.
Project structure
Known Limitations
TransPort only supports TCP.
ttp status reflects the circuit used
to reach check.torproject.org, which
may differ from the circuit used by other applications.
/etc/tor/torrc to add required
directives. The original file is backed up to
torrc.bak
before any changes. Custom configurations are preserved where
possible.
Uninstallation
TTP provides two levels of removal depending on how you installed it.
Use this to clean up the system state while keeping the TTP binary. It stops any active session, removes the SELinux policy module, and clears log files.
A complete system removal. If you used
install.sh, this script will
remove /opt/ttp, the symlink in
/usr/local/bin, and perform all
the cleanups of the CLI command.