DEV Community

Cover image for How I fixed network state corruption in my Linux Tor proxy
onyks
onyks

Posted on

How I fixed network state corruption in my Linux Tor proxy

Most transparent proxy scripts share a fatal flaw: if they crash, they take your system's network down with them. Firewall rules are left lingering, DNS is routed to a dead port, and you're forced to manually flush routing tables just to get back online.

When building TTP (Transparent Tor Proxy), I wanted to engineer a resilient system that survives crashes and prevents leaks, especially on modern distros dealing with systemd-resolved and firewalld conflicts.

Here is the engineering approach I used to handle the three biggest network failure points.

1. Atomic Cleanup with Stateless nftables

Modifying the system's default firewall rules requires complex backup and restore logic. It's fragile and prone to race conditions.

Instead of touching existing rules, TTP creates a strictly isolated inet ttp table. I set the output hook to priority -150 to ensure TTP's redirection executes before any standard NAT or firewalld rules.

Because the ruleset is completely isolated, network restoration is a single, atomic operation:

nft destroy table inet ttp
Enter fullscreen mode Exit fullscreen mode

If the proxy stops, the table is nuked. No system state backups are required, meaning zero risk of permanent corruption.

2. Auto-Recovering Orphaned Sessions

If the main process is killed abruptly (e.g., SIGKILL or power loss), the network remains trapped in Tor-routing mode.

To solve this, TTP uses a persistent JSON lock file (/var/lib/ttp/ttp.lock) to track the session state. On startup, the CLI checks for this file. If it exists, it verifies if the recorded PID is actually alive:

def is_orphan() -> bool:
    # ...
    pid = data.get("pid")
    try:
        os.kill(pid, 0) # Sends no signal, just checks for existence
    except OSError:
        return True # Process is dead, session is orphaned
    return False
Enter fullscreen mode Exit fullscreen mode

If an orphaned session is detected, TTP automatically triggers the rollback logic (destroying the nftables table and restoring DNS) before starting a new, clean circuit.

3. The DNS Hard-Reset Strategy

Routing DNS through Tor (127.0.0.1:9053) is straightforward. Restoring it reliably is hard because systemd-resolved aggressively caches states.

A simple revert command often leaves the system unable to resolve domains. To guarantee cleartext connectivity upon exit, the restoration uses a "Hard-Reset" sequence:

  1. Revert interface-specific overrides (resolvectl revert <interface>).
  2. Force-restart the daemon to purge lingering memory states (systemctl restart systemd-resolved).
  3. Wipe the cache entirely (resolvectl flush-caches).

The Result

The result is a proxy that guarantees a safe return to cleartext routing, even after a fatal crash. I've also added an emergency --restore-only flag and active leak audits (ttp check-leak) to verify exit IPs and DNS routes dynamically.

If you're interested in low-level Linux networking, nftables routing, or want to audit the code, you can check the repository here.

I am currently an undergraduate CS student, so any feedback is profoundly appreciated.

Top comments (0)