pnetctl is a fast, keyboard-first terminal console manager for Cisco routers, switches, and other virtual lab nodes running inside PNETLab (or EVE-NG).
Instead of manually SSHing into your PNETLab host, running ps -ef | grep ... to discover ports, and repeatedly typing telnet localhost 30043, pnetctl automates the entire workflow directly from your Windows Terminal or PowerShell.
- Overview
- How It Works
- System Requirements
- Installation
- Configuration
- SSH Authentication & Host Key Security
- Usage & Commands
- Keyboard Shortcuts
- Session Management & Detach
- Concrete Lab Example
- Troubleshooting
- Security Considerations
- Limitations
- Development & Automated Testing
In typical PNETLab environments, virtual lab devices (Cisco IOL/IOSv/IOSvL2/Dynamips/QEMU) expose local Telnet consoles bound to 127.0.0.1 on random or dynamic TCP ports (e.g., 30043, 30044).
pnetctl provides:
- Zero Port Forwarding: Connects securely over SSH. Device Telnet sessions stay strictly on the remote server's
127.0.0.1. - Automatic Node Discovery: Interrogates running PNETLab processes dynamically over SSH.
- Interactive Terminal UI: Powered by Textual with instant search, filtering, and status badges.
- ANSI/VT100 Console Emulation: Powered by pyte supporting full Cisco editing shortcuts (
Tab,Ctrl+C,Ctrl+Z,Ctrl+Shift+6). - Multi-Session Keep-Alive: Detach back to the node selector without terminating active router console sessions.
┌─────────────────────────────────────────────────────────────┐
│ Windows Host │
│ │
│ pnetctl (Desktop GUI & Direct Telnet Client) │
└──────────────┬──────────────────────────────┬───────────────┘
│ │
[1] SSH Port 22 (Fast Discovery) │ [2] Direct TCP Telnet Socket
Runs `ps -ef | grep ...` │ (1ms Handshake, Zero Lag!)
Discovers active ports: │ Connects directly to:
SW-LAN -> Port 30043 │ 192.168.2.20:30043
R-GW-LAN -> Port 30044 │ 192.168.2.20:30044
R1 -> Port 30046 │ ...
▼ ▼
┌─────────────────────────────────────────────────────────────┐
│ PNETLab Linux Server │
│ │
│ Virtual Lab Nodes (QEMU / IOL / Dynamips) │
│ Telnet consoles listening on TCP ports 30043, 30044, etc. │
└─────────────────────────────────────────────────────────────┘
Key Advantages:
- ⚡ Ultra-Low Latency (Zero Lag): As soon as ports are discovered, console tabs connect directly via raw TCP socket. Typing feels instantaneous.
- 🛡️ Automatic SSH Fallback: If device ports are firewalled or bound strictly to
127.0.0.1,pnetctlseamlessly falls back to an SSH tunnel PTY channel. - 🚀 1-Click PuTTY / Windows Terminal: Opens external PuTTY directly via
-telnet <host> <port>in milliseconds.
- Local Machine: Windows 10/11 (PowerShell 7 / Windows Terminal recommended). Also works on Linux/macOS.
- Python: Python 3.11, 3.12, or 3.13+.
- Remote Server: Linux-based PNETLab or EVE-NG server with SSH access and
telnetclient installed.
If you use Scoop:
scoop install python pipx
pipx ensurepathInstall pnetctl directly:
pipx install .Or from a git repository:
pipx install git+https://github.com/nxctf/pnetctl.gitNow pnetctl is available everywhere in your terminal:
pnetctl --version# Clone the repository
git clone https://github.com/nxctf/pnetctl.git
cd pnetctl
# Create and activate virtual environment
python -m venv .venv
.venv\Scripts\Activate.ps1
# Install in editable mode with development tools
pip install -e ".[dev]"
# Run (launches Desktop GUI by default!)
pnetctlA Scoop manifest template is provided in packaging/scoop/pnetctl.json.
For team or bucket distribution:
- Tag a release on GitHub (e.g.
v0.1.0). - Generate SHA256 checksum of the release ZIP.
- Add the manifest to your custom Scoop bucket:
scoop bucket add mybucket https://github.com/your-org/scoop-bucket scoop install pnetctl
On Windows, the default configuration file is stored at:
%APPDATA%\pnetctl\config.toml
(e.g., C:\Users\<username>\AppData\Roaming\pnetctl\config.toml).
On Linux/macOS:
~/.config/pnetctl/config.toml
You can override the path using the --config flag or the PNETCTL_CONFIG environment variable:
$env:PNETCTL_CONFIG = "D:\labs\my-pnetlab-config.toml"Run:
pnetctl setupThe wizard will guide you through:
- Remote server hostname or IP.
- SSH port (default 22).
- Username (default root).
- Authentication method (Key, Agent, or Password).
- Private key location.
See config.example.toml for reference:
[connection]
host = "192.168.1.100"
port = 22
username = "root"
auth_method = "key"
private_key = "~/.ssh/id_ed25519"
timeout = 10.0
strict_host_key_check = true
[discovery]
refresh_interval = 10
command_timeout = 10.0
[terminal]
term_type = "xterm-256color"
scrollback = 5000
keep_sessions = true
detach_key = "ctrl+b"- SSH Keys (Recommended): Supports Ed25519, RSA, and ECDSA keys. Expands
~and environment variables. - SSH Agent: Leverages Windows OpenSSH Agent or Pageant.
- Passwords: Never stored in plaintext in
config.toml. Prompted securely without echo at runtime. - Host Key Verification: Rejects unknown or modified server host keys by default. Prompts interactively to verify and add new host keys into your
~/.ssh/known_hostsfile.
Running pnetctl without arguments launches the modern tabbed desktop GUI with mouse navigation, mousewheel scrolling, in-app configuration & password setup, and quick action buttons:
pnetctl # Launch Desktop GUI (reads saved config or prompts setup dialog)
pnetctl 192.168.2.20 # Launch GUI directly targeting server IP
pnetctl root@192.168.2.20:22 # Launch GUI with full target [user@]host[:port]
pnetctl nodes # Fast CLI: List discovered active router/switch nodes & ports
pnetctl nodes 192.168.2.20 # Fast CLI: Scan nodes directly from target server
pnetctl tui # Launch terminal Textual TUI interface
pnetctl doctor # Inspect health & connectivity
pnetctl -v # Display versionNo need to touch the terminal for configuration:
- When you run
pnetctlfor the first time (or click⚙️ Edit Connection / Passwordin the sidebar), a configuration dialog appears. - Enter your PNETLab Server IP, SSH Port, Username, and SSH Password (with show/hide 👁 toggle).
- Check
💾 Save these settings to config.tomland click⚡ Save & Connect! - Discovered routers & switches appear immediately in the left sidebar table. Double-click any node to open an ultra-fast Direct Telnet console tab.
Check your Python environment, configuration file, SSH reachability, and remote server dependencies:
pnetctl doctorOutput:
+-----------------------------------------------------------------------------+
| pnetctl Diagnostic Doctor |
+-----------------------------------------------------------------------------+
| Check | Result | Details |
| Python Version | PASS | 3.13.0 (>= 3.11 required) |
| Config File | PASS | Valid: C:\Users\user\AppData\Roaming\... |
| Private Key | PASS | Exists: C:\Users\user\.ssh\id_ed25519 |
| Known Hosts | PASS | C:\Users\user\.ssh\known_hosts |
| SSH Reachability | PASS | Connected to 192.168.1.100:22 |
| Remote Telnet Tool | PASS | Found at /usr/bin/telnet |
| Discovery Command | PASS | Executed successfully (found 7 active node(s))|
+-----------------------------------------------------------------------------+
Print all discovered nodes in a table and exit:
pnetctl nodesTest your SSH connection credentials:
pnetctl connect| Key | Action |
|---|---|
Up / Down |
Navigate through discovered nodes |
Enter or C |
Connect to selected node console |
/ |
Focus search / filter input |
R |
Refresh discovery over SSH |
D |
Close active session for selected node |
? |
Show interactive help modal |
Q |
Safely disconnect and quit |
| Key | Action | Notes |
|---|---|---|
F10 or Ctrl+B |
Detach to main node menu | Session remains alive in background |
F12 |
Close console channel | Closes remote Telnet process |
Ctrl+Shift+6 |
Cisco Abort (^^) |
Cancels ping / traceroute |
Ctrl+C |
Interrupt signal | Forwarded to Cisco CLI |
Ctrl+Z |
Exit configuration mode | Forwarded to Cisco CLI |
Tab |
Command completion | Forwarded to Cisco CLI |
Up / Down |
CLI history navigation | Forwarded to Cisco CLI |
pnetctl supports seamless multi-session multiplexing over a single persistent SSH connection:
- Reattach: If you detach from
SW-LAN(viaF10orCtrl+B) and connect toR1,SW-LANstays running in the background. SelectingSW-LANagain restores your exact console state immediately. - Close vs Detach:
- Detaching (
F10/Ctrl+B) leaves the Telnet connection active on the server. - Closing (
F12in console, orDin main menu) closes the remote channel cleanly.
- Detaching (
- Graceful Exit: Quitting the application (
Q) terminates all active channels and closes the SSH transport cleanly.
Consider the following running PNETLab processes:
SW-LAN 30043
R-GW-LAN 30044
SW-TRANSIT 30045
R1 30046
R2 30047
R-ISPB 30049
R-GW-SRV 30050
(Note: These ports are dynamically assigned by PNETLab and discovered automatically at runtime).
- When you run
pnetctl, all 7 nodes appear in the table. - Typing
/thenGWfilters the view instantly toR-GW-LAN(30044) andR-GW-SRV(30050). - Press
EnteronSW-LAN:pnetctlexecutestelnet 127.0.0.1 30043over an interactive PTY channel on the PNETLab server. - You configure VLANs in
SW-LAN, pressF10to detach, selectR-GW-LAN(30044), and test default gateway routing. - If a ping gets stuck on a Cisco router, press
Ctrl+Shift+6to abort it instantly.
Run pnetctl setup to generate an initial configuration file, or copy config.example.toml to %APPDATA%\pnetctl\config.toml.
Your PNETLab server may have been reinstalled or changed IP address. Check your ~/.ssh/known_hosts file or run pnetctl connect to accept the new fingerprint.
Ensure your lab nodes are powered on inside the PNETLab web interface. Check running processes on the server using pnetctl doctor.
Windows Terminal handles VT sequences natively. Ensure you use Windows Terminal rather than legacy cmd.exe for optimal ANSI escape code rendering.
- No Remote Port Exposure: Telnet is unencrypted by nature.
pnetctlaccesses127.0.0.1strictly over an encrypted SSH channel; no Telnet ports (30000+) are ever opened to the public network. - Port Validation: All discovered ports are strictly validated as integers (1–65535) before building the
telnet 127.0.0.1 <PORT>command. - Credential Safety: No passwords are saved in plain text.
- Application Lifetime: Sessions kept alive in the background remain alive while
pnetctlis running. Closingpnetctlterminates active Telnet channels. - Telnet Binary Requirement: PNETLab Linux host must have the
telnetclient package installed (default on all PNETLab releases).
Run the automated test suite:
pytest -vAll 41 unit and UI tests run in isolation with mocked SSH transports and test fixtures.