Skip to content
Snigdha OSDecentralized

Network Troubleshooting Guide

Diagnosing captive portals, disconnected interfaces, and relay blocks.

4 min readUpdated: 2026-11-12

Snigdha NetGuard — Automated Testing & Verification Guide

1. Verification Strategy

Static verification of configuration files is insufficient to prove network security invariants. Snigdha NetGuard employs isolated Linux network namespaces (ip netns and unprivileged unshare -n -r) to inject live packets, execute network redirection, test drop policies, and simulate component failure states.


2. Test Suites Overview

The automated test infrastructure is organized into four distinct test layers:

netguard/tests/
├── unit/
│   ├── test_firewall_unit.sh       # nftables syntax, table atomicity, table isolation
│   ├── test_tor_config.sh          # torrc invariant audits & tor --verify-config
│   └── test_bridge_validator.py    # Strict parser tests, injection rejection, redaction
│
├── integration/
│   └── test_netns_firewall.sh      # Simulated egress interface, TCP & DNS redirection
│
├── failure/
│   └── test_failure_injection.sh   # Tor crash, disconnect, invalid reload resilience
│
└── runner: tests/test-netguard.sh   # Master test harness

3. Running the Test Suites

Execute the comprehensive NetGuard test suite via the top-level test runner:

./tests/test-netguard.sh

Or execute via the master build automation target:

make test

4. Test Details and Assertions

4.1 Unit Test Layer

test_firewall_unit.sh

  • Assertion 1: nft -c -f netguard/firewall/snigdha.nft evaluates with exit code 0.
  • Assertion 2: Ruleset creates table inet snigdha atomically in an isolated netns without requiring root privileges.
  • Assertion 3: Applying snigdha.nft over an existing unrelated table (table inet unrelated_table) leaves the unrelated table completely intact, confirming zero reliance on destructive flush ruleset.
  • Assertion 4: Repeated sequential execution of the ruleset is idempotent and generates zero errors.

test_tor_config.sh

  • Assertion 1: Verifies presence of ClientOnly 1.
  • Assertion 2: Verifies mandatory DisableNetwork 1 default.
  • Assertion 3: Verifies dual-stack transparent proxy listeners: TransPort 127.0.0.1:9040 and [::1]:9040.
  • Assertion 4: Verifies dual-stack DNS proxy listeners: DNSPort 127.0.0.1:5353 and [::1]:5353.
  • Assertion 5: Verifies SocksPort 0 (unneeded local SOCKS listener disabled).
  • Assertion 6: Verifies CookieAuthentication 1 and restricted ControlSocket /run/tor/control.
  • Assertion 7: Verifies volatile data directory DataDirectory /run/tor/data.
  • Assertion 8: Executes tor -f netguard/tor/torrc --verify-config using the distribution Tor binary.

test_bridge_validator.py

  • Assertion 1: Successfully parses vanilla IPv4 bridges (192.0.2.1:9001) and fingerprints.
  • Assertion 2: Successfully parses bracketed IPv6 bridges ([2001:db8::1]:9001).
  • Assertion 3: Successfully parses pluggable transport bridges (obfs4, meek, snowflake, webtunnel).
  • Assertion 4: Rejects shell command injection vectors (;, &&, |, `, $()).
  • Assertion 5: Rejects invalid IP ranges, ports outside 1–65535, and empty input.
  • Assertion 6: Verifies that sanitize_bridge_line() masks all IP addresses and fingerprints.

4.2 Integration Test Layer (test_netns_firewall.sh)

The integration test creates an isolated network namespace with a virtual ethernet pair (eth0 <-> veth1) and sets a default route to simulate a real hardware interface:

  1. Loopback Permitted: Sockets communicating over 127.0.0.1 are accepted cleanly.
  2. TCP Transparent Redirection: An application initiating an outbound TCP socket to an external address (93.184.216.34:80) is intercepted by nat_output and redirected to 127.0.0.1:9040, where a simulated Tor TransPort listener accepts the connection.
  3. DNS Redirection: An application issuing UDP DNS queries to 8.8.8.8:53 is intercepted and redirected to 127.0.0.1:5353, where a simulated Tor DNSPort listener receives the payload.
  4. Arbitrary UDP Drop: UDP packets destined for port 123 (NTP) or port 443 (QUIC) are dropped by the firewall filter chain.
  5. Tor Bootstrap DNS Isolation: Populates @tor_bootstrap_dns_v4 with a DHCP server IP. Verifies that only the Tor process can reach the resolver, while non-Tor entities are dropped.
  6. Private Network Drop: Applications attempting direct TCP connections to RFC 1918 private subnets (192.168.1.1:80) bypass redirection and are dropped fail-closed.
  7. Direct TCP DNS Drop: Direct TCP port 53 connections are dropped fail-closed.

4.3 Failure Injection Test Layer (test_failure_injection.sh)

  1. Tor Stopped: With no process listening on port 9040, connection attempts receive ECONNREFUSED locally. Packets never escape to clearnet.
  2. Tor Abrupt Crash (SIGKILL): The simulated Tor listener is killed abruptly. The firewall ruleset remains active in kernel netfilter; subsequent connections fail-closed.
  3. Invalid Firewall Reload: Injects a syntactically malformed ruleset. The atomic loader rejects the replacement, preserving the active ruleset without interruption.
  4. Interface Flap: Interface disconnection triggers a flush of the bootstrap DNS allowlist. Stale DNS addresses are eliminated immediately.

5. QEMU ISO Verification

To verify full network behavior on the bootable ISO artifact within QEMU:

make run

Inside the booted live desktop:

  1. Observe Polybar status: displays NETWORK BLOCKED — TOR DISCONNECTED.
  2. Launch a terminal: attempt curl https://check.torproject.org -> fails immediately with connection error.
  3. Attempt direct DNS query: drill archlinux.org @8.8.8.8 -> connection refused/timeout.
  4. Open NetGuard menu via $mod+Shift+n or Polybar click: select "Connect to Tor".
  5. Observe Polybar: transitions to TOR CONNECTING -> TOR BOOTSTRAPPING -> TOR CONNECTED — TRAFFIC ROUTED THROUGH TOR.
  6. Open browser or curl: traffic now resolves and transits Tor exits.