Skip to content
Snigdha OSDecentralized

Network Security Architecture

nftables transparent redirection, Tor TransPort, and DNS interception.

7 min readUpdated: 2026-11-12

Snigdha NetGuard — System-Wide Network Architecture

1. Executive Summary

Snigdha NetGuard is the dedicated network security and censorship-circumvention subsystem of Snigdha OS Decentralized. Its primary architectural objective is to guarantee at the Linux kernel packet-filtering layer that no user-space application can initiate direct clearnet connections to the Internet.

All outbound application network traffic is either:

  1. Transparently intercepted and routed through the local Tor client overlay network, or
  2. Dropped fail-closed by the kernel firewall.

Network enforcement is implemented through an owned Linux nftables ruleset (table inet snigdha), strict systemd startup dependency chains, dedicated Unix domain socket Tor control IPC, volatile RAM-backed state management, and NetworkManager privacy integration.


2. Subsystem Layout

NetGuard is structured as an independent, modular subsystem within the repository:

netguard/
├── core/
│   ├── tor_controller.py       # Tor ControlSocket client & event engine
│   ├── bridge_validator.py     # Strict syntax validation & IP/fingerprint redactor
│   └── diagnostics.py          # 17-point invariant verification engine
│
├── firewall/
│   ├── snigdha.nft             # Primary nftables ruleset (inet snigdha)
│   └── snigdha-firewall.sh     # Atomic ruleset validator and loader
│
├── tor/
│   └── torrc                   # Hardened client-only volatile Tor configuration
│
├── daemon/
│   ├── snigdha-netguardd       # System state machine & authenticated IPC daemon
│   └── org.snigdha.NetGuard1.conf # D-Bus system bus authorization policy
│
├── cli/
│   └── snigdha-netguard        # User CLI for status, bridges, and diagnostics
│
├── desktop/
│   └── snigdha-netguard-menu   # Rofi graphical control menu
│
├── networkmanager/
│   ├── 10-privacy.conf         # MAC randomization & DHCP hostname suppression
│   ├── 10-netguard-dns.sh      # Dispatcher for Tor bootstrap DNS allowlist
│   ├── NetworkManager.conf     # Keyfile plugin and dns=none enforcement
│   └── 50-snigdha-netguard.conf# route_localnet=1 and ICMP redirect blocking
│
├── systemd/
│   ├── snigdha-firewall.service # Firewall validation and activation
│   ├── snigdha-netguardd.service# Daemon coordinator service
│   ├── NetworkManager.service.d/netguard.conf # Firewall prerequisite drop-in
│   └── tor.service.d/netguard.conf            # RuntimeDirectory & ordering drop-in
│
└── tests/
    ├── unit/                   # Ruleset syntax, torrc invariants, bridge validation
    ├── integration/            # Network namespace packet redirection & drops
    └── failure/                # Tor crash, disconnect, invalid reload resilience

3. High-Level Architectural Flow

flowchart TD
    App["Application / Browser"] --> Route["Kernel Routing Decision"]
    Route --> NAT["nftables nat_output (hook output, priority dstnat)"]

    NAT -->|TCP to External| RedirTCP["Redirect to :9040 (Tor TransPort)"]
    NAT -->|UDP Port 53 DNS| RedirDNS["Redirect to :5353 (Tor DNSPort)"]
    NAT -->|Private Networks RFC1918| RetPriv["Return (No Redirection)"]
    NAT -->|Tor UID 43| RetTor["Return (No Redirection)"]

    RedirTCP --> OutputFilter["nftables output (hook output, priority filter)"]
    RedirDNS --> OutputFilter
    RetPriv --> OutputFilter
    RetTor --> OutputFilter

    OutputFilter -->|Tor UID 43| AcceptTor["ACCEPT (Outbound to Guard/Relay)"]
    OutputFilter -->|Tor UID 43 to @tor_bootstrap_dns| AcceptBootDNS["ACCEPT (Bootstrap DNS)"]
    OutputFilter -->|Redirected to 127.0.0.1:9040| AcceptLocalTrans["ACCEPT (Tor TransPort)"]
    OutputFilter -->|Redirected to 127.0.0.1:5353| AcceptLocalDNS["ACCEPT (Tor DNSPort)"]
    OutputFilter -->|DHCP Client (sport 68 dport 67)| AcceptDHCP["ACCEPT (Local DHCP)"]
    OutputFilter -->|ICMPv6 Discovery| AcceptICMP6["ACCEPT (IPv6 Router/Neighbor)"]
    OutputFilter -->|All other UDP (QUIC, NTP)| DropUDP["DROP (Fail-Closed)"]
    OutputFilter -->|All other Traffic / RFC1918| DropDefault["DROP (Default Policy)"]

4. Firewall Enforcement (inet snigdha)

4.1 Table Ownership and Atomicity

The firewall is encapsulated strictly within the table inet snigdha table.

  • No flush ruleset: NetGuard never executes flush ruleset, ensuring third-party or container packet-filter tables are never destroyed.
  • Atomic updates: Updates use atomic transactions:
    add table inet snigdha
    delete table inet snigdha
    table inet snigdha { ... }
    
  • Pre-flight verification: Rulesets are validated via nft -c -f <file> before activation. If syntax or kernel acceptance fails, the existing active ruleset is preserved intact.

4.2 Transparent TCP Routing

All non-Tor outbound TCP connections are intercepted in chain nat_output and redirected to 127.0.0.1:9040 (and [::1]:9040 for IPv6). The Tor daemon's TransPort reconstructs original destination metadata via Linux SO_ORIGINAL_DST socket options and routes streams over anonymous Tor circuits.

4.3 DNS Interception

UDP port 53 traffic is intercepted in chain nat_output and redirected to 127.0.0.1:5353 (and [::1]:5353). Tor's internal DNSPort resolves A, AAAA, and PTR records anonymously over multi-hop circuits. Direct TCP DNS, DNS-over-TLS (port 853), and DNS-over-QUIC are dropped.

4.4 UDP and Leak Elimination

Because Tor transparent proxying does not route raw arbitrary UDP, the default policy for outbound UDP is drop. This strictly eliminates:

  • QUIC bypass attempts (UDP 443)
  • Direct NTP queries (UDP 123)
  • WebRTC STUN/TURN direct IP leakage
  • Direct external DNS queries

5. Tor Lifecycle & Explicit Activation

5.1 Safe Startup (DisableNetwork 1)

Tor starts automatically at boot with DisableNetwork 1 in /etc/tor/torrc.

  • The daemon binds its local listeners (TransPort 9040, DNSPort 5353, ControlSocket /run/tor/control).
  • It initiates zero network packets and performs zero directory downloads.
  • This ensures users in hostile or heavily monitored jurisdictions are not endangered by immediate Tor handshakes upon connecting to an untrusted Wi-Fi hotspot.

Outbound Tor connectivity is initiated only when the user explicitly triggers "Connect to Tor" via the Rofi desktop menu (snigdha-netguard-menu) or CLI (snigdha-netguard connect).

  • NetGuard verifies that table inet snigdha is healthy and active.
  • NetGuard configures volatile bridges if defined.
  • NetGuard issues SETCONF DisableNetwork=0 via the Tor ControlSocket.
  • Tor bootstraps to 100%, establishes circuits, and updates desktop status.

5.3 Fail-Closed Disconnect and Crash Safety

If the user selects "Disconnect", or if the Tor daemon crashes:

  • NetGuard sets DisableNetwork=1 or detects socket closure.
  • The firewall ruleset remains completely untouched in the kernel.
  • Application traffic hitting the TransPort receives Connection Refused or drops.
  • Under no circumstances is there ever a fallback to clearnet.

6. DNS Bootstrap Isolation

When connecting via bridge hostnames (or CDN-fronted bridges such as meek), the Tor process must resolve hostnames before circuits exist.

  • NetGuard deploys a NetworkManager dispatcher script: /etc/NetworkManager/dispatcher.d/10-netguard-dns.sh.
  • Upon interface activation (up or dhcp4-change), the dispatcher reads the DHCP-supplied DNS resolvers and populates nftables sets:
    • @tor_bootstrap_dns_v4
    • @tor_bootstrap_dns_v6
  • The firewall output chain allows ONLY meta skuid 43 (tor) to query those specific IP addresses on port 53.
  • User applications (UID 1000 / live) are strictly forbidden from contacting bootstrap resolvers and receive no exemption.
  • When the interface disconnects, the allowlist sets are immediately flushed.

7. Volatile Amnesia Storage

All runtime state of the Tor and NetGuard subsystems is backed exclusively by RAM (tmpfs):

  • /run/tor/: Tor runtime data directory, cached descriptors, PID file, and authentication cookie.
  • /run/netguard/: Volatile bridge definitions (bridges.json) and IPC socket (netguard.sock).
  • /run/netguard/bridges.json is set to mode 0600 and destroyed immediately on power loss or shutdown, leaving no persistent traces on physical storage.

8. Application Sandboxing Integration (Phase 5)

Snigdha NetGuard integrates directly with the Phase 5 Snigdha Isolation subsystem (snigdha-sandbox):

  1. host-enforced Confinement Mode: For applications requiring network access (such as Kitty terminal or web clients), the bubblewrap sandbox deliberately shares the host network namespace. All outgoing TCP and DNS queries generated within the sandbox remain subject to NetGuard's nftables redirection rules (table inet snigdha), guaranteeing transparent routing through Tor TransPort and DNSPort.
  2. offline Confinement Mode: For offline utilities, image viewers, and file tools, the sandbox unshares the network namespace (--unshare-net) with loopback only, rendering external network egress physically impossible.
  3. Zero Firewall Bypass: Sandboxed applications possess zero Linux capabilities (CAP_NET_ADMIN dropped) and cannot modify or inspect nftables rulesets. AppArmor profiles strictly block direct access to Tor control sockets or NetGuard D-Bus interfaces.

9. Snigdha Security Center Integration (Phase 7)

Snigdha NetGuard exposes its live operational status and management controls over the D-Bus system bus via interface org.snigdha.NetGuard1 (/org/snigdha/NetGuard1):

  • Live Telemetry: GetStatus() provides firewall state, Tor circuit count, bridge status, and IPv6 enforcement to the unprivileged GTK4 Security Center console (snigdha-security-center).
  • Authenticated Configuration: Actions such as ReloadFirewall() and SetBridges() are gated by Polkit action org.snigdha.netguard.configure.
  • Diagnostics Export: GetDiagnostics() feeds sanitized network metrics into the unified diagnostic report engine without exposing bridge IP addresses or authentication cookies.