Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Neutron Documentation Wiki

Welcome to the Neutron technical documentation and developer wiki.

Neutron is a high-performance WireGuard manager for Linux written in Rust, leveraging NetworkManager as the system-native networking control plane.


Wiki Contents

1. Usage Guide (TUI & CLI)

Complete interactive guide to the Terminal User Interface (TUI), keybindings, modal navigation, and scriptable CLI command reference.

2. System Architecture & Design Principles

Detailed overview of the decoupled subsystem architecture, module boundaries, data flows, thread model, and comparative analysis of UI frontends (CLI, TUI, GUI, Electron).

3. NetworkManager Integration

How Neutron interfaces with NetworkManager, nmcli command execution, batch error aggregation, profile discovery, interface comment extraction, and connection lifecycle.

4. Security Architecture (Kill Switch & Lockdown)

Comprehensive explanation of the two security layers: Layer 3 NetworkManager policy routing (fwmark, exclusive DNS priorities) and Netfilter/Firewalld always-on OUTPUT filtering.

5. Split Tunneling (IP & Domain Routing)

Architecture of global split tunneling, Include vs. Exclude routing modes, CIDR normalization (/32 & /128), client-side DNS resolution, and NetworkManager route injection.

6. NAT-PMP Port Forwarding Engine

Pure Rust UDP implementation of the NAT-PMP protocol (RFC 6886), tunnel gateway derivation, mapping request framing, lease renewal timers, and clipboard integration.

7. User Configuration & Theming (config.toml)

Specification for the human-readable TOML configuration, managed profile drop directory (profiles/) auto-sync, built-in themes (Osaka Jade, Catppuccin, Nord, Gruvbox, Monochrome), and color customization.

8. Packaging & Universal Distribution

Packaging guides and distribution models for Homebrew tap formulas, Arch Linux AUR, and static musl compilation for headless servers.

9. Implementation Notes

Startup selection, checked activation, configuration persistence, and saved versus effective policy state.

10. Testing & Quality Checks

Formatting, Clippy, host tests, and disposable NetworkManager/firewall sandbox tests.


Core Invariants

  • NetworkManager as Single Source of Truth: All WireGuard profile parameters and secrets remain stored exclusively inside NetworkManager profile storage.
  • Decoupled Business Logic: No domain logic (routing, firewall, config, port forwarding) is coupled to GTK or terminal drawing code.
  • Apply-Before-Persist: Network operations are executed first; persisted application configurations are only updated if the underlying network mutation succeeds.
  • Fail-Safe Security: Kill Switch and Lockdown teardowns are strictly surgical and guaranteed never to lock the user out permanently.

Usage Guide (TUI & CLI)

Neutron provides both an interactive, full-featured Terminal User Interface (TUI) and a comprehensive, scriptable Command-Line Interface (CLI).


1. Interactive Terminal User Interface (TUI)

The TUI is the primary, most user-friendly way to interact with Neutron. It provides real-time telemetry, zero-latency profile browsing, interactive security toggles, and live configuration management.

Starting the TUI

Launch the TUI simply by running:

neutron
# or explicitly:
neutron tui

TUI Screen Layout

  • Header (Top):
    • Status Panel: Live active profile, connection indicator, public IP, ping latency, download/upload throughput rates (/proc/net/dev), and active NAT-PMP forwarded port.
    • Policies Panel: Real-time status pills for Kill Switch, Lockdown Firewall, Split Tunneling, Port Forwarding, and Auto-Connect at login.
  • Main Body:
    • Left (Profile Browser): Lists all NetworkManager WireGuard profiles with active checkmarks (), favorite stars (), and pool exclusion badges ().
    • Right (Details Pane): Full connection diagnostics including remote peer endpoint, allowed IPs, latest handshake age, cumulative RX/TX transfer counters, persistent keepalive, and assigned interface IP.
  • Footer: Quick keybinding shortcuts and status messages.

TUI Keybindings

Every action below is also searchable by name from the Command Palette (Ctrl+P or :).

KeyActionDescription
/ (or p / n)NavigateMove cursor up / down through the profile list
Space / EnterConnect / DisconnectConnect to the selected profile, or disconnect if already active
sSwitch ProfileInstantly switch active tunnel to the selected profile
fToggle FavoriteStar/unstar profile for quick tray indicator access
eToggle Eligibility PoolInclude or exclude selected profile from random boot connection pool
aAuto-Connect at LoginToggle automated random profile connection on login
tSplit TunnelingOpen interactive Split Tunneling manager (Domains & Subnets)
kKill SwitchToggle NetworkManager-native routing kill switch
lLockdown ModeToggle always-on Netfilter firewall (requires pkexec root)
oPort ForwardingToggle NAT-PMP dynamic port leasing and renewal
rSync Drop DirectoryScan ~/.config/neutron/profiles/ and batch-import new .conf files
d / DeleteDelete ProfilePermanently remove selected profile from NetworkManager (with confirmation)
Ctrl+P / :Command PaletteSearchable fuzzy popup for all commands and actions
Ctrl+TTheme PickerSwitch themes live (nord, osaka-jade, catppuccin, gruvbox, monochrome)
? / hHelpDisplay keybindings help modal
q / EscQuitExit the TUI cleanly

Interactive Split Tunneling (t)

Pressing t opens the side-by-side Split Tunneling manager:

  • Mode Selector (Top): Use / to move between [ Disabled ], [ Include ], and [ Exclude ], and Space/Enter to activate.
  • Domains (Left Column): Type a domain (e.g. github.com) into the + Add Domain box and press Enter.
  • Subnets / CIDRs (Right Column): Type an IPv4/IPv6 CIDR (e.g. 10.0.0.0/8 or 192.168.1.0/24) and press Enter.
  • Navigation: Tab cycles between Mode, Domains, and Subnets. Arrow keys (, , , ) navigate between panels and list items.
  • Deletion: Select any entry in the list and press Del or x.
  • Automatic Non-Blocking Apply: All changes apply and persist immediately in the background without freezing the UI.
  • Lockdown Notice: If the Lockdown Firewall is active, a clear banner reminds you that all non-tunnel traffic is dropped when disconnected.

Command Palette (Ctrl+P or :)

Press Ctrl+P or : to open the Command Palette. Type any keyword (e.g. “kill”, “split”, “theme”, “sync”) to filter actions, then press Enter to execute.


Theme Picker (Ctrl+T)

Press Ctrl+T to switch between calibrated color palettes live without restarting the app:


2. Command Line Interface (CLI)

All operations can also be run directly from terminal commands or shell scripts.

Profile Connections & Status

# List all WireGuard profiles with active status and eligibility
neutron list

# Connect to a profile by name or UUID
neutron connect "Home-Server"

# Switch connection directly to another profile
neutron switch "Work-VPN"

# Disconnect active tunnel
neutron disconnect

Profile Ingestion & Drop Directory

# Batch-import new or updated *.conf files from ~/.config/neutron/profiles/
neutron sync

# Manage random-on-boot eligibility pool
neutron eligible list
neutron eligible add "Home-Server"
neutron eligible remove "Test-Server"

Global Split Tunneling

# Check current split tunneling status and active routes
neutron split-tunnel status

# Set routing mode
neutron split-tunnel set-mode include
neutron split-tunnel set-mode exclude
neutron split-tunnel set-mode disabled

# Add destination subnets or domains
neutron split-tunnel add-cidr 10.0.0.0/8
neutron split-tunnel add-cidr 192.168.1.0/24
neutron split-tunnel add-domain internal.corp
neutron split-tunnel add-domain gitlab.company.com

# Remove destinations
neutron split-tunnel remove-cidr 10.0.0.0/8
neutron split-tunnel remove-domain internal.corp

# Clear all split tunnel routes
neutron split-tunnel clear

Security & Firewalls

# Inspect or toggle NetworkManager-native routing kill switch
neutron kill-switch status
neutron kill-switch enable
neutron kill-switch disable

# Inspect or toggle always-on Netfilter lockdown firewall (requires polkit/pkexec)
neutron lockdown status
neutron lockdown enable
neutron lockdown disable

NAT-PMP & qBittorrent Dynamic Port Sync

# Check qBittorrent integration status, WebUI connectivity, and active ports
neutron qbit status

# Test connection to qBittorrent WebUI
neutron qbit test

# Immediately sync active NAT-PMP port to qBittorrent
neutron qbit sync

# Enable / disable automated sync
neutron qbit enable
neutron qbit disable

# Configure WebUI connection parameters
neutron qbit config --url http://127.0.0.1:8080 --bind true

System Tray AppIndicator & Background Daemon

Neutron includes a pure-Rust D-Bus StatusNotifierItem and DBusMenu system tray indicator (src/service/indicator.rs via zbus) that monitors link health and provides quick desktop controls:

# Run persistent D-Bus system tray AppIndicator daemon
neutron indicator

# Terminate running background instances and launch fresh session
neutron restart

# Run one-shot random profile connection (used by boot/login automation)
neutron startup-random

System Architecture & Design Principles

Neutron is designed with a strictly decoupled architecture where all networking, security, and state logic exist in pure Rust modules that can be driven by any UI frontend (CLI, TUI, or GUI).


High-Level Architecture Diagram

┌─────────────────────────────────────────────────────────────────────────────┐
│                            User Interface Layer                             │
│  ┌───────────────────────┐  ┌───────────────────────┐  ┌─────────────────┐  │
│  │     CLI (clap)        │  │     GUI (Adwaita)     │  │   TUI (ratatui) │  │
│  │ (Scripting & Headless)│  │ (GNOME Desktop Window)│  │ (Terminal UI)   │  │
│  └───────────┬───────────┘  └───────────┬───────────┘  └────────┬────────┘  │
└──────────────┼──────────────────────────┼───────────────────────┼───────────┘
               └──────────────────────────┼───────────────────────┘
                                          ▼
┌─────────────────────────────────────────────────────────────────────────────┐
│                            Core Decoupled Engine                            │
│  ┌─────────────────────────┐  ┌─────────────────────────┐  ┌─────────────┐  │
│  │   NetworkManager (nm/)  │  │  Firewall (firewall/)   │  │ NAT-PMP     │  │
│  │ • Profile Discovery     │  │ • Lockdown Netfilter    │  │ (portforward│  │
│  │ • Policy Kill Switch    │  │ • Surgical Teardown     │  │ • UDP Lease │  │
│  │ • Split Tunnel Routes   │  │ • pkexec Orchestration  │  │ • Auto-Renew│  │
│  └─────────────────────────┘  └─────────────────────────┘  └─────────────┘  │
│  ┌─────────────────────────┐  ┌─────────────────────────┐                   │
│  │    Config (config/)     │  │    Service (service/)   │                   │
│  │ • Atomic Persistence    │  │ • Random Boot Selector  │                   │
│  │ • Unix Mode 0600        │  │ • Autostart Unit        │                   │
│  └─────────────────────────┘  └─────────────────────────┘                   │
└─────────────────────────────────────────────────────────────────────────────┘

Subsystem Responsibilities

1. nm/ — NetworkManager Control Plane

  • Trait NmClient provides an abstract interface for listing, connecting, disconnecting, switching profiles, setting kill-switch properties, and applying split-tunneling routes.
  • CliNmClient interacts with NetworkManager via nmcli with a strict 30-second execution deadline.
  • Submodule nm::split_tunnel validates and normalizes CIDRs, resolves domain names to IP addresses, and formats ipv4.routes / ipv6.routes / never-default arguments.
  • Submodule nm::kill_switch configures kernel policy routing (wireguard.ip4-auto-default-route) and negative DNS priorities (-1500).

2. firewall/ — Always-On Lockdown Netfilter Engine

  • Trait FirewallClient manages permanent direct OUTPUT chain rules in firewalld.
  • Uses mangle OUTPUT allow-list rules and a final DROP before filter-table established accepts; see Security & Kill Switch.
  • All rules are tagged with a unique comment (neutron-lockdown) ensuring surgical removal without modifying user-defined firewall rules.
  • Privilege escalation is consolidated into one pkexec shell batch, with permanent fail-closed guards during rebuilds.

3. portforward/ — NAT-PMP Dynamic Port Leasing & App Integrations

  • Implements RFC 6886 NAT-PMP client directly over std::net::UdpSocket.
  • Derives the gateway address from the local tunnel IPv4 address (10.x.x.x / 100.x.x.x).
  • Acquires dynamic UDP/TCP port mappings and schedules automatic lease renewals before expiration.
  • Integrates portforward::qbittorrent Web API bridge to automatically synchronize dynamic listening ports to qBittorrent (native, Flatpak, containerized).

4. config/ — Configuration & State Persistence

  • Manages AppConfig serialized as TOML in ~/.config/neutron/config.toml.
  • Implements atomic file writes (fs::rename with fallback across filesystem boundaries) with strict 0o600 permissions.
  • Stores policy intent, startup eligibility, favorites, theme settings, and integration settings. Narrow updates are serialized by a sidecar file lock.

5. service/ — Boot-Time Automation

  • Implements the one-shot random profile selector for login / boot.
  • Manages XDG desktop autostart entries (~/.config/autostart/io.github.pandabytez.neutron.desktop).
  • Prevents immediate profile repeats and respects user-defined eligibility exclusion sets.

Frontend & Resource Comparison Matrix

MetricGTK4 / Libadwaita (GUI)UNRELEASEDPure Rust TUI (ratatui)Background Daemon / CLIElectron / Web Clients
Binary Size~15–30 MB (or AppImage bundle)~3–5 MB (Static musl binary)~3 MB150–250 MB
Active RAM (RSS)~70 – 110 MB~10 – 15 MB~3 – 6 MB250 – 450 MB
Idle CPU Usage0.1% – 0.5%0.0% (sleeps on epoll)0.0%0.5% – 2.0%
Startup Time~150–300 ms< 10 ms (instantaneous)< 2 ms1.5 – 3.0 seconds
System DependenciesGTK4, Libadwaita, Mesa/WaylandZero (100% static musl)ZeroNode, Chromium, X11/Wayland
Primary EnvironmentsGNOME Desktop WorkstationsServers, SSH, Hyprland, Sway, i3Automation, Cron, SystemdLegacy Cross-Platform
Distribution ChannelsAppImage, Distro PackagesHomebrew, Cargo, AUR, Static MuslHomebrew, System PackageCustom Installers

NetworkManager Integration

Neutron relies on NetworkManager as the source of truth for all WireGuard network configurations.


Why NetworkManager?

Direct usage of wg-quick creates ad-hoc network interfaces and routing tables outside the system networking daemon, often causing conflicts with system DNS (systemd-resolved), connection reconnects, Wi-Fi switching, and desktop status integration.

By integrating directly with NetworkManager:

  1. System Consistency: Profiles integrate cleanly with GNOME Shell, desktop networking indicators, and D-Bus network monitors.
  2. Key Security: Private keys remain stored securely within NetworkManager profile storage (/etc/NetworkManager/system-connections/) rather than an unencrypted app database.
  3. Hardware & Power Management: Sleep, resume, and interface roaming are handled natively by the Linux kernel and NetworkManager daemon.

Technical Details & Command Flow

1. Profile Discovery

Profiles of type wireguard are enumerated via:

nmcli -t -f NAME,UUID,TYPE connection show
nmcli -t -f NAME,UUID,TYPE connection show --active

The output is parsed into WireguardProfile structs with active/inactive states.

2. Timeouts & Concurrency

NetworkManager command helpers use process::run_with_timeout with a 30-second deadline (NMCLI_TIMEOUT). The long-lived nmcli monitor process is managed separately.

  • Standard output and standard error pipes are drained concurrently on separate worker threads to prevent pipe buffer deadlocks.
  • If a command exceeds the deadline, the child process is terminated and an explicit AppError::CommandFailed error is returned. Failed commands surface their exit status.

3. Error Aggregation (apply_to_every_profile)

Autoconnect normalization processes every profile and aggregates failures. Other policy sweeps can stop on a failure and leave mixed profile settings; callers report that possible partial state. See Implementation Notes.

4. WireGuard Comment Ingestion

When importing .conf files via nmcli connection import type wireguard file <path>, comments inside the [Interface] section (often containing provider metadata, server features, or notes) are extracted and saved in profile-info.json beside the application settings, keyed by profile UUID. AppConfig.profile_custom_info remains the in-memory view used by the CLI and TUI.

Security Architecture (Kill Switch & Lockdown)

Neutron combines NetworkManager routing/DNS policy with an optional always-on firewall.


Defense Tiers Overview

┌─────────────────────────────────────────────────────────────────────────────┐
│                             Defense Tier 1:                                 │
│                   Kill Switch (Layer 3 Routing Plane)                       │
│  • Active while WireGuard tunnel is UP                                      │
│  • NetworkManager Policy Routing (`wireguard.ip4-auto-default-route = yes`) │
│  • Dedicated routing table + `fwmark` + `suppress_prefixlength 0`           │
│  • Exclusive DNS priority (`ipv4.dns-priority = -1500`)                     │
│  • Drops traffic if tunnel fails; prevents fallback to physical gateway     │
└──────────────────────────────────────┬──────────────────────────────────────┘
                                       │
                                       ▼
┌─────────────────────────────────────────────────────────────────────────────┐
│                             Defense Tier 2:                                 │
│                   Lockdown Mode (Netfilter Firewall Plane)                  │
│  • Active 24/7 (Even when tunnel is DISCONNECTED or RECONNECTING)           │
│  • Permanent `firewalld` direct mangle OUTPUT rules (IPv4 & IPv6)           │
│  • Allows: Loopback, configured tunnel interfaces, conditional DNS (53)     │
│  • Allows: Peer Handshake Endpoints (Host:Port)                             │
│  • Allows: Private LAN (RFC 1918: 10.0.0.0/8, 192.168.0.0/16, DHCP, mDNS)  │
│  • Blocks: Other outbound traffic via terminal DROP                        │
└─────────────────────────────────────────────────────────────────────────────┘

1. NetworkManager-Native Kill Switch

How It Works

The kill switch operates entirely through NetworkManager connection properties without introducing custom firewall scripts:

nmcli connection modify <uuid> \
    wireguard.ip4-auto-default-route yes \
    wireguard.ip6-auto-default-route yes \
    ipv4.dns-priority -1500 \
    ipv6.dns-priority -1500

Routing Invariants

  1. Dedicated Table Routing: NetworkManager places the tunnel default route into an isolated routing table guarded by an fwmark and a suppress_prefixlength 0 rule.
  2. Active-Connection Scope: Routing protection depends on the active connection’s routes and rules. Lockdown provides protection across disconnection and switching.
  3. DNS Priority: A negative DNS priority (-1500) gives the tunnel’s DNS resolvers exclusive precedence over LAN/DHCP resolvers, eliminating DNS leaks to your local ISP.

The setting is global and applies to saved profiles for their next activation. Automatic default-route handling is pinned independently of the DNS toggle; full-tunnel routing requires a peer allowing 0.0.0.0/0 or ::/0. IPv6 policy is adjusted for profiles without IPv6. See saved versus effective policy.


2. Always-On Lockdown Firewall

Why Lockdown is Needed

The Kill Switch only protects traffic while a tunnel connection is actively established. When disconnected, traffic flows normally over the physical interface.

Lockdown closes that gap by installing permanent firewalld direct rules in mangle OUTPUT via a single batched pkexec invocation. This runs before filter-table established-connection accepts on both firewalld backends.

Ruleset Hierarchy (OUTPUT Chain)

PriorityMatch CriteriaTargetPurpose
0-o loACCEPTAllow local loopback communication
1-p udp/tcp --dport 53ACCEPT / DROPAllow bootstrap DNS when disconnected; block non-tunnel DNS when connected
1-d <LAN_SUBNETS>ACCEPTKeep local LAN devices reachable (Router, Printer, NAS)
0-o <TUNNEL_IFACE>ACCEPTAllow first traffic on configured WireGuard interfaces
1-p udp -d <PEER_HOST> --dport <PEER_PORT>ACCEPTAllow encrypted WireGuard handshake packets out
10All remaining packetsDROPBlock other outbound traffic before filter-table accepts

Activation installs the target interface and connected DNS policy before bringing the tunnel up. DNS drops precede LAN allowances. An ACCEPT here finishes this table; it does not bypass other firewall policy.

Surgical Teardown Guarantee

Every single rule installed by Lockdown carries a marker: -m comment --comment neutron-lockdown

When disabling Lockdown:

  1. Unprivileged reads enumerate permanent and runtime tagged rules in both mangle and legacy filter OUTPUT.
  2. A single privileged batch removes those rules individually, without reloading firewalld.
  3. Foreign runtime/permanent rules, custom chains, and rich rules are left untouched.
  4. Disable requires successful firewall authorization and execution; failures are reported.

Rebuild Recovery

Rebuilds install tagged priority -1 DROP guards for both address families before replacing rules in each of the permanent and runtime configurations. Guards are removed only after the replacement is complete. An interruption therefore remains fail-closed after reload or reboot, but can block all outbound traffic. Retry enabling lockdown or explicitly disable it to remove the guards and recover.

Sandbox tests exercise interrupted rebuilds, migration, teardown, and established IPv4/IPv6 egress on both firewalld backends.

Split Tunneling (IP & Domain Routing)

Neutron provides Global Split Tunneling, allowing users to specify exact subnets, IPs, and domain names that should route through or bypass the WireGuard tunnel.


Routing Modes

1. Include Mode (Route Only Listed Destinations via Tunnel)

  • Behavior: Default internet browsing bypasses the tunnel over your physical interface, while only specified subnets and domain IPs are routed securely through the WireGuard tunnel.
  • NetworkManager Mechanism:
    nmcli connection modify <uuid> \
        ipv4.never-default yes \
        ipv6.never-default yes \
        ipv4.routes "10.0.0.0/8, 192.168.10.0/24" \
        ipv6.routes "2001:db8::/32"
    
  • Use Case: Remote network access where corporate/homelab subnets must route through the WireGuard tunnel while streaming, gaming, and personal browsing remain on high-speed unthrottled physical internet.

2. Exclude Mode (Bypass Tunnel for Listed Destinations)

  • Behavior: All general traffic routes through the encrypted WireGuard tunnel, while specified subnets or domains bypass the tunnel directly to your local physical gateway.

  • NetworkManager Mechanism: never-default = yes with the complement of the listed destinations installed as tunnel routes.

    Adding an excluded range to ipv4.routes would route it into the tunnel — the opposite of excluding it. There is no “bypass route” to install, because every route on a WireGuard connection points at the WireGuard device. So Neutron inverts the selection instead: it computes every CIDR except the listed ones and routes those through the tunnel, leaving the excluded ranges to the physical default route.

    Excluding 10.0.0.0/8 therefore produces:

    nmcli connection modify <uuid> \
        ipv4.never-default yes \
        ipv6.never-default yes \
        ipv4.routes "0.0.0.0/5, 8.0.0.0/7, 11.0.0.0/8, 12.0.0.0/6, ..." \
        ipv6.routes "::/0"
    

    The complement is computed by nm::split_tunnel::complement_routes, which recursively splits the address space into the smallest set of aligned CIDRs that covers everything but the exclusions. Excluding nothing yields a full tunnel (0.0.0.0/0); excluding 0.0.0.0/0 yields no routes at all.

  • Use Case: Privacy browsing with exclusions for local services, banking portals, or gaming servers that block remote tunnel endpoints.

3. Disabled Mode (Standard Full-Tunnel)

  • Restores never-default = no and clears all custom static routes.

Route Normalization & Dynamic DNS

1. CIDR Normalization

Input routes are parsed and normalized into standard CIDR format:

  • Single IPv4 192.168.1.50 $\rightarrow$ 192.168.1.50/32
  • Single IPv6 ::1 $\rightarrow$ ::1/128
  • Subnets 10.0.0.0/8 $\rightarrow$ 10.0.0.0/8
  • Routes are partitioned into ipv4.routes and ipv6.routes automatically.

2. Client-Side Domain Resolution

For domain entries (e.g., internal.corp, service.local):

  1. The domain is resolved via std::net::ToSocketAddrs to all associated IPv4 (/32) and IPv6 (/128) literals.
  2. Resolved IPs are merged with configured CIDRs before applying route arguments to NetworkManager.
  3. Resolution is performed in the background during connection or rule modification.

CLI & GUI Configuration

CLI Commands

# Check status
neutron split-tunnel status

# Set routing mode
neutron split-tunnel set-mode include
neutron split-tunnel set-mode exclude
neutron split-tunnel set-mode disabled

# Manage CIDRs & Domains
neutron split-tunnel add-cidr 10.0.0.0/8
neutron split-tunnel remove-cidr 10.0.0.0/8
neutron split-tunnel add-domain internal.corp
neutron split-tunnel remove-domain internal.corp

# Clear all rules
neutron split-tunnel clear

GUI Dialog

Available in the Settings section of the GUI main window:

  • Clean mode selector dropdown (Disabled, Include, Exclude).
  • Interactive list editors for CIDRs and Domain names with inline syntax validation.
  • Real-time subtitle updates reflecting active rules.

NAT-PMP Dynamic Port Forwarding Engine

Neutron includes a native, pure Rust NAT-PMP (RFC 6886) client designed for WireGuard endpoints and providers that support dynamic port forwarding (such as Proton, Mullvad, and PIA).


Technical Protocol Overview

NAT-PMP (Port Mapping Protocol) allows clients behind a NAT gateway to request dynamic UDP and TCP port mappings.

┌──────────────┐                            ┌──────────────┐
│   Neutron    │                            │Tunnel Gateway│
│   (Client)   │                            │ (NAT Router) │
└──────┬───────┘                            └──────┬───────┘
       │                                           │
       │  1. UDP Request (Opcode 1: Map UDP)       │
       │──────────────────────────────────────────>│
       │     Internal: 0 (Request any WAN port)    │
       │     External: 0                           │
       │     Lifetime: 60 seconds                  │
       │                                           │
       │  2. UDP Response (Opcode 129: Success)    │
       │<──────────────────────────────────────────│
       │     Assigned Port: 51423                  │
       │     Lifetime: 60 seconds                  │
       │                                           │
       │  3. Auto-Renew Loop (Every 45 seconds)    │
       │──────────────────────────────────────────>│
       │     Repeats mapping request before expiry │
       │                                           │

Gateway Derivation Logic

WireGuard profile interfaces obtain private IPv4 addresses (e.g. 10.2.0.2/32 or 100.96.0.4/32).

Neutron derives the default NAT-PMP gateway IP automatically:

  1. Extracts the primary tunnel IPv4 address via nmcli -g ipv4.addresses connection show <uuid>.
  2. Replaces the host octet with .1 (e.g. 10.2.0.2 $\rightarrow$ 10.2.0.1).
  3. Dispatches the NAT-PMP packet to UDP port 5351 at that gateway address.

Auto-Renewal Lifecycle

Port forwarding is off by default — a lease is renewed on a timer against the provider’s gateway, so it is never requested unless asked for. Turn it on with o in the TUI (or via the Command Palette), or set it in ~/.config/neutron/config.toml:

[port_forwarding]
enabled = true
  1. Lease Grant: Upon receiving a success packet, the granted port number is stored in memory and displayed in the UI banner.
  2. Periodic Renewal Timer: A background timer runs at RENEW_INTERVAL (every 45 seconds) to refresh the lease with the gateway.
  3. Profile Switch / Disconnect Cleanup: When switching profiles or disconnecting, the active port is cleared immediately to prevent displaying stale mappings.
  4. Clipboard Integration: A 1-click button in the GUI copies the forwarded port to the system clipboard for easy pasting into BitTorrent or game servers.

qBittorrent Dynamic Port Sync

Neutron can automatically push the dynamic NAT-PMP port to a running qBittorrent instance (native package, Flatpak, Docker/Podman container, or headless server) via its official Web API (/api/v2).

Setup Prerequisite (Required in qBittorrent)

Before enabling synchronization, ensure qBittorrent’s Web User Interface is enabled:

  1. In qBittorrent, open Tools $\rightarrow$ Options $\rightarrow$ Web UI (or Preferences $\rightarrow$ Web UI).
  2. Check “Web User Interface (Remote control)” (default port: 8080).
  3. (Recommended) Under Authentication, check “Bypass authentication for clients on localhost”.
    • If localhost authentication bypass is not enabled, configure your WebUI username and password in Neutron via neutron qbit config --username <user> --password <pass>.

Compatibility (Flatpak, Native, Containers)

  • Flatpak (org.qbittorrent.qBittorrent): Fully compatible. Because Flatpak packages share the host network stack (--share=network), the WebUI is reached at http://127.0.0.1:8080, and updated listening ports apply directly to the host socket.
  • Native Package: Fully compatible.
  • Docker / Podman / Remote WebUI: Fully compatible by configuring the target URL (e.g. neutron qbit config --url http://192.168.1.50:8080).

CLI Management

# Check status and test WebUI connectivity
neutron qbit status

# Test WebUI credentials and fetch current listening port
neutron qbit test

# Immediately forward the active leased port to qBittorrent
neutron qbit sync

# Enable / disable automated background port synchronization
neutron qbit enable
neutron qbit disable

# Configure WebUI parameters & optional interface binding
neutron qbit config --url http://127.0.0.1:8080 --bind true

User Configuration & Theming (config.toml)

Neutron uses a human-readable, self-documenting TOML configuration file located at ~/.config/neutron/config.toml.

Imported profile comments are stored separately in profile-info.json beside the settings file, keyed by NetworkManager profile UUID. They still appear in profile details, but no longer clutter config.toml. Existing inline profile_custom_info entries migrate on the next successful settings save. The notes file uses atomic writes and owner-only permissions; deleting a profile removes its notes.


Configuration File Schema

# ==============================================================================
# Neutron Configuration (~/.config/neutron/config.toml)
# ==============================================================================

[general]
# Directory monitored for WireGuard .conf files.
# Dropping, copying, or git-cloning profiles here automatically imports them to NetworkManager.
profiles_dir = "~/.config/neutron/profiles"

# Automatically import new/updated .conf files from profiles_dir on launch
auto_sync_profiles = true

# Opt in to connecting a random eligible profile at login
autoconnect_at_login = false

# Default interface when launching `neutron` with no arguments: "tui" or "gui"
default_ui = "tui"

# ==============================================================================
# Security & Routing Policies
# ==============================================================================
[security]
# NetworkManager policy routing (drops traffic if tunnel fails; exclusive DNS priority)
kill_switch = false

# Always-on Netfilter firewall via firewalld (blocks non-tunnel traffic even when disconnected)
lockdown = false

# ==============================================================================
# Global Split Tunneling
# ==============================================================================
[split_tunnel]
# Routing mode: "disabled", "include" (route only listed), or "exclude" (bypass listed)
mode = "include"

# Custom IP subnets (CIDRs) or single host IPs
cidrs = [
    "10.0.0.0/8",
    "192.168.10.0/24",
    "172.16.0.0/12",
]

# Domain names resolved dynamically at connection time
domains = [
    "internal.corp",
    "homelab.local",
]

# ==============================================================================
# Startup Random Profile Selection Pool
# ==============================================================================
[startup_pool]
# List of profile names or UUIDs excluded from random selection (opt-out model)
excluded_profiles = [
    "backup-slow-server",
    "emergency-profile",
]

# ==============================================================================
# Terminal User Interface (TUI) & Theming
# ==============================================================================
[tui]
# Telemetry polling rate in milliseconds (handshake, transfer counters)
refresh_interval_ms = 1000

# Built-in theme preset: "nord" (default), "osaka-jade", "catppuccin", "gruvbox", "monochrome"
theme = "nord"

# Optional custom color overrides (accepts hex #rrggbb or standard color names)
[tui.colors]
active_border = "#88c0d0"
status_connected = "#a3be8c"
status_disconnected = "#bf616a"
transfer_rx = "#81a1c1"
transfer_tx = "#ebcb8b"

# ==============================================================================
# Port Forwarding (NAT-PMP)
# ==============================================================================
[port_forwarding]
# Lease an incoming port from the tunnel gateway and keep renewing it.
# Also togglable live from the TUI with `f`. Off by default: the lease is
# renewed on a timer against the provider, so it is only requested on request.
enabled = false

# ==============================================================================
# qBittorrent Dynamic Port Forwarding Sync
# ==============================================================================
[qbittorrent]
# Automatically push NAT-PMP leased ports to qBittorrent WebUI on connect/renew
enabled = false

# WebUI HTTP/HTTPS endpoint URL
url = "http://127.0.0.1:8080"

# Optional authentication (leave empty if localhost auth bypass is enabled in qBittorrent)
# username = "admin"
# password = "your-webui-password"

# Bind qBittorrent network interface to the active WireGuard interface
bind_interface = false

Theming Engine

Neutron features a built-in terminal theme engine with 5 carefully calibrated color palettes that can be toggled live via Ctrl+T:

ThemeDescriptionAccent ColorsBest For
nord (Default)Arctic, north-bluish clean paletteFrost Cyan, Polar Night GrayMinimalist dark setups
osaka-jadeOsaka Jade / Bamboo paletteJade Cyan, Bamboo Green, GoldDark forest green aesthetic
catppuccinSoothing pastel palette (Mocha)Mauve, Sapphire, PeachModern terminal setups
gruvboxRetro groove warm earthy paletteWarm Amber, Forest GreenTiling window managers & vim users
monochromeHigh-compatibility black & whiteHigh-contrast ASCII/ANSIMinimal TTYs, serial consoles, 16-color terms

Managed Profile Drop Directory (profiles/)

Neutron manages a dedicated profile drop directory at ~/.config/neutron/profiles/ (with strict 0700 user-only permissions).

Workflow:

  1. Drop / Copy Profiles: Users can simply copy .conf files into the directory:
    cp ~/Downloads/wireguard_configs/*.conf ~/.config/neutron/profiles/
    
  2. Auto-Sync on Launch: Whenever the TUI or CLI runs, Neutron scans the folder, compares content checksums against NetworkManager, and batch-imports new profiles in milliseconds.
  3. Manual Sync Command: You can trigger an instant sync via CLI or within the TUI:
    neutron sync
    
  4. Git/Dotfiles Automation: The entire ~/.config/neutron/ folder can be tracked in a private Git repository for instant syncing across multiple machines.

Packaging & Universal Distribution

Neutron is designed for easy distribution across all major Linux packaging ecosystems.


Packaging Channels Overview

FormatTarget PlatformDependenciesStandalone?Build Command
HomebrewLinux / LinuxbrewZero (Pure Rust TUI/CLI)Yesbrew tap pandabytez/tap && brew trust pandabytez/tap && brew install neutron
Static MuslHeadless Servers, SSHZero (Static musl binary)Yescargo build --target x86_64-unknown-linux-musl
Arch AURArch Linux, ManjaroSystem dependenciesNativemakepkg -si

1. Homebrew Tap Formula (Formula/neutron.rb)

Sample formula for custom tap (brew tap pandabytez/tap):

class Neutron < Formula
  desc "Fast WireGuard profile manager via NetworkManager"
  homepage "https://github.com/PandaBytez/neutron"
  url "https://github.com/PandaBytez/neutron/archive/refs/tags/v0.1.0.tar.gz"
  sha256 "<checksum>"
  license "GPL-3.0-or-later"

  depends_on "rust" => :build
  depends_on :linux

  def install
    system "cargo", "install", *std_cargo_args
    bin.install_symlink "neutron" => "neutron-vpn"
  end

  test do
    assert_match "Neutron", shell_output("#{bin}/neutron --help")
  end
end

Tap Trust Verification (Modern Homebrew)

Under Homebrew’s tap trust model, non-official taps can be explicitly marked as trusted:

# Trust the tap
brew trust pandabytez/tap

# Or trust specifically the neutron formula
brew trust --formula pandabytez/tap/neutron

2. Static Musl Target (Headless Servers / Homelabs)

Compile a 100% statically-linked executable with no dynamic shared library dependencies:

# Add musl target
rustup target add x86_64-unknown-linux-musl

# Build static binary
cargo build --release --target x86_64-unknown-linux-musl

The resulting binary (target/x86_64-unknown-linux-musl/release/neutron) runs on Alpine Linux, Debian, RHEL, Ubuntu, and any minimal Linux environment.


3. Arch Linux AUR Package (PKGBUILD)

Sample PKGBUILD for Arch Linux:

pkgname=neutron-bin
pkgver=0.1.0
pkgrel=1
pkgdesc="Fast WireGuard manager via NetworkManager"
arch=('x86_64' 'aarch64')
url="https://github.com/PandaBytez/neutron"
license=('GPL-3.0-or-later')
depends=('networkmanager')
source_x86_64=("https://github.com/PandaBytez/neutron/releases/download/v${pkgver}/neutron-linux-amd64.tar.gz")
sha256sums_x86_64=('SKIP')

package() {
    install -Dm755 neutron "${pkgdir}/usr/bin/neutron"
    ln -s neutron "${pkgdir}/usr/bin/neutron-vpn"
}

Implementation Notes

This page collects lifecycle and persistence details. See the existing architecture, NetworkManager integration, and security chapters for subsystem and policy design.

Startup Selection

Auto-connect at login defaults to off. Enable it with a in the TUI to persist the preference and install the desktop autostart entry. Explicit saved settings are preserved when upgrading.

Neutron issues explicit connection requests and disables NetworkManager’s native autoconnect on managed WireGuard profiles. The startup selector leaves a single active, eligible profile untouched. If replacement is needed, it validates the eligible pool before disconnecting existing tunnels; an empty pool returns an error without teardown. Candidate selection avoids immediately repeating the last random profile when alternatives exist.

Configuration Persistence

Application configuration is written atomically and uses owner-only 0o600 permissions on Unix. Production writers update individual settings under a stable sidecar file lock, preserving newer settings written by other Neutron processes. External editors must cooperate with locking to serialize edits.

Imported profile notes live in a separate profile-info.json alongside the settings file. Reading older inline notes is backward-compatible; the next save writes the notes file before removing the inline section. Once present, the notes file is authoritative, including an empty map after deletion. Malformed or unreadable notes produce an error rather than being silently overwritten.

WireGuard private keys remain in NetworkManager storage. Optional qBittorrent WebUI credentials are application settings; they are passed to curl through stdin rather than command-line arguments.

Activation and Import

Activation requires successful configuration loading and routing/DNS policy preparation. Invalid split-tunnel targets, unresolved domains, or rejected NetworkManager modifications stop activation with an error. Imported profiles receive the same checked preparation.

Automatic handshake-failure teardown applies to fresh interfaces whose profiles have an endpoint and nonzero persistent keepalive, when verification is enabled. On-demand profiles remain active while idle so their first packet can initiate the handshake.

Imports use nmcli connection import type wireguard file <path>. The profile UUID comes from that command’s validated C-locale confirmation, rather than guessing from concurrent profile-list changes. The profile inbox consumes source files after successful import; source removal is best-effort, and matching filenames are currently skipped by profile name rather than content comparison. Interface comments are retained as application metadata.

Saved Intent and Effective Policy

The long-lived indicator refreshes domain routes every 30 seconds and reapplies them to active NetworkManager devices. Automatic firewall reconciliation and domain refresh share coordination with explicit policy changes; failed firewall reconciliation is retried. DNS rotation still has a polling interval, not a per-query routing guarantee.

TUI profile refresh and startup sync run in background workers. Transient profile read failures retry with a delay, and public-IP requests coalesce without losing the last request during worker shutdown. Favorite menu IDs remain tied to UUIDs across layout refreshes; clicks on removed items are ignored.

Port leases renew at half the granted lifetime (capped at 45 seconds). Failed qBittorrent synchronization retries after 45 seconds, while changed configuration can trigger an immediate attempt.

Routing/DNS changes to saved NetworkManager profiles require reconnect. Action toasts explain this; the policy panel shows saved settings. DNS details do not claim a verified live priority.

Policy errors distinguish potentially partial application from completed application followed by failed saving. The current TUI session marks affected policies UNKNOWN until a successful retry, even after a config refresh. CLI lockdown status explicitly reports saved intent rather than verified firewall state. Emergency disable attempts firewall removal even when saving is unavailable.

For permanent rebuild guards and recovery, see Lockdown rebuild recovery.

Binary and Verification

NAT-PMP sockets accept replies only from the requested gateway address and port. Replies must match the protocol version, length, opcode and internal port (with the provider’s zero-port allocation convention supported). Failure to bind the requested tunnel address aborts the request instead of falling back to another source.

The executable is named neutron. The musl target produces a statically linked binary; system tools and services such as NetworkManager are still required. Build instructions are in Packaging & Distribution. Commands and test-tier boundaries are in Testing & Quality Checks.

Testing & Quality Checks

Run these commands from the repository root with the Rust toolchain installed.

Host Checks

# Check formatting and run strict Clippy across all features
cargo lint

# Run default-feature unit and integration tests
cargo test

# Include the optional qBittorrent integration tests
cargo test --all-features

Host tests use pure logic, mocks, and isolated local responders. Tests that modify real NetworkManager profiles or firewall rules are ignored on the host.

Disposable System Sandbox

Requires Podman or Docker. The Cargo tasks start NetworkManager and firewalld in a disposable container with its own network namespace. Network and firewall changes stay inside that sandbox.

# Host tests across all features, then containerized system tests
cargo test-all

# Containerized system tests only
cargo test-system

# Select a system tier or rebuild the sandbox image
cargo test-system -- --nm
cargo test-system -- --firewall
cargo test-system -- --rebuild

# Firewall leak regression checks
cargo test-leaks

# Interactive sandbox for investigation
cargo xtask container-shell

Never run cargo test -- --ignored directly on the host or set NEUTRON_TEST_SANDBOX=1 there. The sandbox sets that marker for tests guarded by require_sandbox().

Coverage and Limits

  • NetworkManager tests check profile import, policy properties, and real WireGuard activation, including idle on-demand tunnels.
  • Firewall tests check rule acceptance, legacy-rule migration, surgical teardown, and established IPv4/IPv6 packet egress. The leak_* checks are also included in the firewall tier.
  • The ignored library test interrupted_rebuild_stays_closed_after_reload_and_recovers exercises partial permanent rebuilds and recovery. It runs in the full system suite; --firewall selects only tests/system_firewall.rs.
  • The default sandbox uses firewalld’s iptables backend. Its entrypoint also accepts NEUTRON_TEST_FIREWALL_BACKEND=nftables inside the container for cross-backend checks.
  • Container reload checks do not establish persistence across an actual reboot.

Build the Documentation

cargo docs
cargo xtask docs --serve

mdBook writes the site to public/. See Implementation Notes for the behavior these tests protect.