The single-localhost-listener architecture (Unbound:53 → ctrld:5354)
fundamentally cannot support per-VLAN ControlD profiles: all queries
arrive at ctrld from Unbound as 127.0.0.1, so ctrld has no way to
distinguish VLANs and routes everything to a single upstream. This
broke Asterisk and IoT isolation — all traffic was hitting the same
ControlD profile regardless of which VLAN it came from.
New architecture when all VLAN profiles have a gateway IP set:
Clients → ctrld on VLAN-gateway-IP:53 → per-VLAN ControlD profile
Unbound stays on 127.0.0.1:53 (loopback only — no port conflict)
ctrld sees real client source IPs → routes correctly per VLAN
ctrld forwards *.lan / *.local → Unbound loopback (local-data)
_build_ctrld_toml changes:
- Detects when all active profiles have a gateway IP
- Generates one [listener.N] per VLAN on its gateway IP:53 instead
of a single [listener.0] on 127.0.0.1:ctrld_port
- Each listener has its own [listener.N.policy] with the correct
upstream.N (that VLAN's ControlD profile)
- Adds upstream.local → 127.0.0.1:53 for .lan/.local split-horizon
- Falls back to single-listener with a clear WARNING comment when
gateways are missing
_ctrld_generate_opnsense_cmd changes:
- Detects which mode was generated and produces matching instructions
- Gateway mode: tells user to restrict Unbound to loopback and
disable Query Forwarding (ctrld is no longer downstream of Unbound)
- Fallback mode: warns that per-VLAN profiles are not working
Required OPNsense change to activate gateway mode:
Services → Unbound DNS → General → Network Interfaces → Loopback only
Services → Unbound DNS → Query Forwarding → disable/remove forward to ctrld
https://claude.ai/code/session_01JR2EMK7rwrZJowpstcaxQ6
Avaya / Extreme ERS Switch Manager
A browser-based management interface for Avaya / Extreme Networks ERS switches. You click buttons. The software figures out the CLI. You never type a switch command.
Compatible with: ERS 5928, ERS 5948, ERS 5952, ERS 5952-PWR+, ERS 59100GTS-PWR+
Requirements
Management computer OS: Linux (Ubuntu / Debian recommended — Raspberry Pi OS works perfectly)
The setup script uses apt, systemd, picocom, and Docker. It will not run on Mac or Windows. The management computer does not need a monitor — a headless Raspberry Pi or thin client is ideal. Once running, the web UI is accessible from any browser on any device on your network.
The switch UI itself (the browser interface) works from any OS — phone, tablet, Mac, Windows, Linux.
Switch OS: BOSS (Baystack Operating System Software) v7.9.6
BOSS is the firmware that runs on Avaya / Extreme ERS switches. This project is written and tested against BOSS v7.9.6 on the ERS 59100GTS-PWR+. It is not Cisco IOS or any other vendor's CLI — commands are different. BOSS firmware is pre-installed on the switch and does not need to be downloaded or installed.
Physical Setup — First Time Out of the Box
Everything here happens before you run any software. You need a console cable and a laptop.
Step 1 — Find the Service Port
The console (service) port is a physical RJ-45 serial port on the switch. It is not a regular ethernet port.
ERS 59100GTS-PWR+: The console port is on the front panel, left side. It is labelled "Console" and looks like a standard ethernet jack but is wired as RS-232 serial. It is located next to the USB port and the out-of-band management port.
ERS 5952 / 5952-PWR+: The console port is on the front panel, far left, labelled "Console".
Step 2 — Get a Console Cable
Search "RJ45 to USB console cable Cisco compatible" — around $8. This is the same cable used for Cisco, Juniper, and most enterprise switches. It has an RJ-45 plug on one end and USB-A on the other.
Do not use a standard ethernet cable — it will not work.
Step 3 — Connect and Open a Terminal
Important: Plug the console cable in — both the RJ-45 end into the switch and the USB end into your laptop — before powering on the switch. If you plug in after power-on, screen and picocom may not detect the device correctly.
Settings: 9600 baud · 8 data bits · No parity · 1 stop bit · No flow control
Open a terminal session:
| OS | Command |
|---|---|
| Linux | sudo picocom -b 9600 /dev/ttyUSB0 |
| Mac | screen /dev/tty.usbserial-* 9600 |
| Windows | PuTTY → Serial → COM port → 9600 baud |
To find your COM/device name if unsure:
- Linux:
ls /dev/ttyUSB*before and after plugging in - Mac:
ls /dev/tty.usb*before and after plugging in - Windows: Device Manager → Ports (COM & LPT)
Console vs SSH: The console cable gives you direct CLI access to type commands on the switch. It is only needed for initial setup. Once SSH is configured, the management script connects over Ethernet (port 22) and you never need the console cable again — unless you lock yourself out.
Step 4 — Power On and Read the Boot Screen
Power on the switch. You will see a banner like this:
Enter Ctrl-Y to begin.
*************************************************************
*** Ethernet Routing Switch 59100GTS-PWR+ ***
*** Copyright (C) 1996-2024 Extreme Networks. ***
*** HW:22 FW:7.5.0.4 SW:v7.9.6.015 ***
*************************************************************
Press Ctrl-Y. The switch is waiting for this keypress to continue booting — it will not proceed without it. After pressing Ctrl-Y the switch runs POST and boots normally in about 60–90 seconds.
When it is ready you will see:
Login:
Step 5 — Log In with Default Credentials
Login: admin
Password: (press Enter — no password by default)
If that fails, try admin / admin. If the switch was previously configured you may need to do a factory reset (see Troubleshooting below).
You will land at the 5952# or 59100GTS-PWR+# prompt in privileged exec mode.
Step 6 — Bootstrap VLAN 99 via Console
This is the only time you need the console cable for normal operation. Enter these commands one at a time, waiting for the prompt before each:
enable
configure terminal
vlan create 99 name "Management" type port
interface vlan 99
ip address 192.168.99.1 255.255.255.0
no shutdown
exit
vlan members add 99 1
vlan pvid 1 99
ip ssh
username admin password YourPassword
end
save config
Replace 1 with the port number your management computer will plug into. Replace YourPassword with a strong password (8–32 characters; allowed special characters: ! @ # $ % ^ & * - _ = + [ ] ; : , . / — no spaces, no quotes).
Port numbering: On both the ERS 5952 and ERS 59100, use bare port numbers (1, 2, 48…). There is no slot prefix.
What about the switch's existing IP? Out of the box the switch has
192.168.1.1on VLAN 1. If your router is also192.168.1.1, they will conflict — two devices with the same IP on the same network causes ARP fights and makes both unreliable.This setup deliberately avoids that by putting management traffic on a separate VLAN (99) with its own subnet (192.168.99.x). Your management computer gets a static IP on that subnet and talks to the switch there. The switch's original VLAN 1 address is irrelevant once VLAN 99 is up.
If you ever need to change the switch's VLAN 1 IP (e.g. to remove the conflict before VLAN 99 is configured):
config terminal interface vlan 1 ip address 192.168.1.2 255.255.255.0 exit ip default-gateway 192.168.1.1 exit save config
After the last command the switch confirms with CP1 [07/04/15 12:00:00.000:INFO]: Operation Success or similar.
Step 7 — Give the Management Computer a Static IP
On the machine that will run the switch manager software:
# Find your interface name first
ip link show
# Set a static IP on the management network interface (adjust eth0 to your interface)
sudo ip addr add 192.168.99.50/24 dev eth0
sudo ip link set eth0 up
Verify: ping 192.168.99.1 — you should get replies from the switch.
Step 8 — Run the Setup Script
python Avaya_5952_setup.py
The script handles everything from here. When it pauses and asks you to load the SSH public key onto the switch, it will show you exactly what to paste into the console.
Installation
Copy Avaya_5952_setup.py to your always-on management computer (Raspberry Pi, HP T620 thin client, old computer or laptop) and run:
python Avaya_5952_setup.py
That is the entire installation process. The script handles everything automatically. The only step requiring human intervention is loading the SSH public key onto the switch via console cable — the script pauses, shows you exactly what to type, and waits for you to confirm before continuing.
What the Setup Script Does
The script runs 21 steps automatically:
- Writes all project files (backend, frontend source, this README)
- Installs system packages (picocom, qrencode, wireguard-tools)
- Adds your user to the dialout group for console cable access
- Installs Python dependencies
- Creates the config directory
/etc/switch-manager/ - Sets a static IP on your management network interface
- Asks whether to deploy with Docker (recommended) or native Python
- Guides you through console cable setup and software links
- Generates an ed25519 SSH keypair
- Shows you exactly what to paste on the switch console
- Tests the SSH connection and retries if it fails
- Pins the switch host key (MITM protection)
- Generates your TOTP authenticator secret with QR code
- Verifies your authenticator app is working before continuing
- Patches the backend config with your switch IP and credentials
- Builds the React frontend (requires Node.js)
- Installs and enables a systemd service (or Docker container)
- Starts the service
- Optionally sets up Docker with Caddy HTTPS
- Optionally configures WireGuard VPN for remote access
- Optionally installs Control D DNS filtering via ctrld
Re-running is safe — completed steps are skipped.
Deployment Options
The script explains both options before asking:
Docker + Caddy (recommended default)
Runs the switch manager in a container with all dependencies baked in. Caddy provides automatic HTTPS at a hostname you choose (default: switch.mgmt.lan). Caddy generates its own internal CA — the setup script installs it into your system trust store automatically so browsers show a clean padlock. Updates with one command: docker compose pull && docker compose up -d.
Native Python (bare metal)
Runs directly as a Python process managed by systemd. Simpler, lower memory, no Docker required. Access via http://IP:8765 directly. Right choice for very low memory machines or users who prefer managing services directly.
Accessing the Interface
http://[management computer IP]:8765
Or with Docker + Caddy:
https://switch.mgmt.lan
You must be on VLAN 99 (management VLAN) or connected via WireGuard VPN to reach this address. This is intentional — the switch enforces isolation at the network layer.
Getting onto VLAN 99 from your laptop
Linux:
sudo ip link add link eth0 name eth0.99 type vlan id 99
sudo ip addr add 192.168.99.50/24 dev eth0.99
sudo ip link set eth0.99 up
Mac: System Settings → Network → Add VLAN interface → VLAN ID 99
Dedicated port: Ask the switch to put one port on VLAN 99 as an access port. Plug in when managing.
VPN: Connect via WireGuard (configured during setup or via the VPN tab).
The Interface — Eight Tabs
Port Map
Visual 48+4 port chassis, colour-coded by VLAN. Each port shows its VLAN assignment, mode (access/trunk/disabled), and a green dot when PoE is active. Click any port to configure it in the right panel.
Port configuration panel:
- Description (e.g. "AP-Corridor-1", "Camera-NE")
- Mode: Access, Trunk, or Disabled
- VLAN assignment (access) or tagged/native VLANs (trunk)
- PoE on/off and wattage limit (copper ports, 1W–30W)
Live status pulls from the switch every 15 seconds while the tab is active, 60 seconds when backgrounded, and pauses when nobody has the page open.
VLANs
Create, rename, and delete VLANs. Shows port count and subnet per VLAN. VLAN 1 cannot be deleted.
ACL Builder
Build Access Control Lists visually. Each rule specifies action (permit/deny), protocol (ip/tcp/udp/icmp), source, destination, and optional port. The ACL is assigned to a VLAN interface with a direction. The tool generates all CLI syntax — you never write it yourself.
Templates: Click "Use Template" to pre-fill rules for common patterns: Staff (full internet, no management), IoT (internet only, no RFC1918), Guest (internet + ctrld DNS enforcement), Camera (NVR only). All rules are editable after applying the template.
Review & Push
Every change across all tabs is translated into the exact CLI commands the switch understands. This tab shows those commands before anything is sent.
You never write CLI commands. The tool generates them. The review step exists so you can inspect what will be sent.
Push mode choice:
- Batch — all commands sent in sequence, results shown when complete
- Step by step — one command at a time, confirm each before the next is sent
Each command is displayed with a plain-English explanation of what it does and what it affects. You can read exactly what is about to happen.
Device Access
Manages which devices can reach the management interface from their normal VLAN without needing VPN. The switch enforces access via an ACL pinhole. TOTP still gates any changes.
MAC address randomization warning: Modern phones and laptops randomize MAC addresses per network. This breaks DHCP reservations. The tab shows per-platform instructions to disable it (iOS, Android, macOS, Windows) before adding a device.
Live DHCP leases pulled from the switch appear as unregistered devices — click Register to add them.
Per device:
- Name, MAC, IP, VLAN
- Reserve static IP (DHCP binding pushed to switch)
- Grant/revoke management access (ACL pinhole pushed to switch)
DHCP
Unified view of all DHCP reservations across switch and OPNsense (if configured).
DHCP server recommendation:
- Use switch DHCP for VLAN 99 (management) — devices get IPs before OPNsense is reachable
- Use OPNsense for all other VLANs — integrates with DNS, firewall rules, lease history
- Never run both for the same VLAN
OPNsense integration (optional): Auto-detects OPNsense at your gateway IP. If found, prompts for API key. Once connected, shows reservations from both switch and OPNsense in one table, colour-coded by source.
Conflict detection: If the same MAC has reservations in both places, a badge appears — red for IP conflicts (same MAC, different IP), yellow for duplicates (same MAC, same IP). Each conflict has a Resolve button with four options: Switch wins, OPNsense wins, Remove from switch, Remove from OPNsense.
DNS Filtering
Configures Control D DNS filtering per VLAN via the ctrld daemon.
How it works: ctrld runs as a local DNS proxy. Devices send normal DNS queries to it. ctrld identifies the source VLAN subnet and routes each query to the correct Control D profile via DoH3. Each VLAN gets different filtering rules. Your ISP sees encrypted HTTPS traffic, not DNS queries.
Three deployment options presented with full explanations:
Option A — ctrld on the management computer (fully automated) Installs ctrld alongside the switch manager. One command downloads and installs it, writes the per-VLAN config, starts it as a system service. The switch DHCP points each VLAN to this machine's IP for DNS.
Option B — ctrld on OPNsense (semi-automated) Generates a single SSH command to paste into OPNsense shell. ctrld installs as a service on OPNsense. OPNsense's IP becomes the DNS server for the network.
Option C — manual / existing setup
Generates the ctrld.toml config and install command. You install wherever you choose.
Per-VLAN Resolver IDs: Each VLAN gets its own Control D profile. Enter the Resolver ID from the Control D dashboard (controld.com → Add Device → Router → Resolver ID). VLANs without a Resolver ID use the first configured profile as fallback.
DNS Enforcement: After installing ctrld, use the "Enforce DNS on Switch" button to generate ACLs that block devices from bypassing ctrld by using 8.8.8.8 directly. See DNS Enforcement ACLs below.
Local Hostnames: Optionally run a dnsmasq container so .lan names resolve for all devices. See Local Hostname Resolution below.
References:
- Control D documentation: https://docs.controld.com/docs/ctrld
- Router setup guide: https://docs.controld.com/docs/routers-platform
VPN
Manages WireGuard VPN for remote access to the management interface from outside the management VLAN.
Add clients: Enter a name (laptop, phone, tablet) and the tool generates a keypair, adds the peer to the server config, reloads WireGuard live, and displays a QR code to scan with the WireGuard app. Also saves a .conf file for desktop import.
Revoke clients: Disconnects the peer immediately and removes it from the server config.
Connected peers: Shows last handshake time and transfer stats for each peer.
SSH tunnel alternative: For power users — one command gives secure access without WireGuard installed:
ssh -L 8765:localhost:8765 user@management-computer-ip
Making Changes — Step by Step
- Configure across any tabs — nothing happens on the switch yet
- Go to Review & Push — read the generated commands with explanations
- Check for dangers — automatic pre-flight runs before authentication
- Authenticate — enter 6-digit TOTP code to unlock a push session
- Choose push mode — batch (faster) or step-by-step (full control)
- Push — commands go one at a time, checked after each
- Review results — each command shows success or failure with the switch's error output
- Auto-lock — session closes when push completes. New changes need a new TOTP code
Authentication and Sessions
Read-only — no authentication required. Anyone on VLAN 99 can view the dashboard.
Push mode — requires TOTP. One code unlocks exactly one push session. The session closes automatically when the push completes or you cancel. The next set of changes requires a new TOTP code.
Why TOTP and not a password: VLAN 99 isolation is the primary barrier. TOTP adds a second factor confirming it is you making a change. A code is useless after 30 seconds and requires physical access to your authenticator app.
How Commands Reach the Switch
Exact sequence on every push:
- Browser sends the command list to the management computer backend
- Backend runs a danger check against known lethal patterns
- Backend validates every command against an allowlist
- Backend opens a fresh SSH connection using the ed25519 key stored on the management computer
- Commands execute one at a time via interactive shell
- After each command the switch's response is checked for error patterns
- On error: push stops immediately, no further commands sent, config not saved
- On full success:
endthencopy running-config nvram:config.cfg— config saved to NVRAM
Blocked Commands
Hard-blocked — refused entirely, must run at the switch console:
| Pattern | Reason |
|---|---|
no vlan 99 |
Deletes management VLAN |
vlan members remove ... 99 |
Removes VLAN 99 from a port — kills management trunk |
no vlan tagging ... 99 |
Removes VLAN 99 tagging — kills management trunk |
no ip ssh |
Disables SSH — permanent lockout |
no ip address |
Removes IP — management computer loses connectivity |
interface vlan 99 |
Modifies management VLAN interface |
boot config flags factory |
Factory reset |
Warning-level — shown for review, push proceeds with confirmation:
| Pattern | Reason |
|---|---|
shutdown |
Shuts down an interface — confirm not your uplink |
default interface |
Resets interface to defaults |
no vlan [id] |
Deletes a VLAN — confirm no active ports depend on it |
spanning-tree ... disable |
Disables spanning tree — loop risk |
SSH Key Security
The ed25519 private key lives at /etc/switch-manager/ers5952_key on the management computer. It never leaves that machine. Your laptop, phone, or tablet never touches it.
The switch host key is pinned after the first connection. If the switch's host key ever changes the backend refuses to connect and reports the mismatch — MITM protection even on the management VLAN.
Console Cable
See Physical Setup — First Time Out of the Box above for the complete walkthrough including where the console port is, what cable to buy, and what to do at the boot screen.
The short version: one-time only, RJ-45 to USB console cable (~$8), 9600 baud, no parity.
Switch password rules: 8–32 characters. Special characters allowed: ! @ # $ % ^ & * - _ = + [ ] ; : , . /. No spaces. No quotes.
Live Polling
The backend polls the switch using a connection pool:
- Pool lifetime: 25 seconds (shorter than switch idle timeout)
- Tab active/visible: polls every 15 seconds
- Tab backgrounded: polls every 60 seconds
- No visitors for 5 minutes: polling pauses completely
- New visitor opens page: immediate poll, resumes normal interval
The switch is never being polled when nobody is looking at the dashboard.
Files Created
| File | Location | Purpose |
|---|---|---|
switch_backend.py |
Project folder | Python API server (34 endpoints) |
ers5952-manager.jsx |
Project folder | React app source (8 tabs) |
README.md |
Project folder | This file |
Dockerfile |
Project folder | Docker image definition |
docker-compose.yml |
Project folder | Caddy + switch manager services |
Caddyfile |
Project folder | HTTPS reverse proxy config |
ctrld.toml |
Project folder | Control D per-VLAN DNS config (if configured) |
frontend/dist/ |
Project folder | Built React app |
ers5952_key |
/etc/switch-manager/ |
SSH private key (chmod 600) |
ers5952_key.pub |
/etc/switch-manager/ |
SSH public key |
known_hosts |
/etc/switch-manager/ |
Pinned switch host key |
totp_secret |
/etc/switch-manager/ |
TOTP seed (chmod 600) — back this up |
devices.json |
/etc/switch-manager/ |
Registered device list |
opnsense.json |
/etc/switch-manager/ |
OPNsense API credentials (chmod 600) |
ctrld.json |
/etc/switch-manager/ |
Control D config (chmod 600) |
wg_server_private |
/etc/switch-manager/ |
WireGuard server private key (chmod 600) |
wg_server_public |
/etc/switch-manager/ |
WireGuard server public key |
clients/ |
/etc/switch-manager/ |
WireGuard client .conf files |
switch-manager.service |
/etc/systemd/system/ |
Systemd service (native mode) |
local-hostnames.json |
/etc/switch-manager/ |
User-defined hostname→IP mappings (optional) |
dnsmasq.conf |
/etc/switch-manager/ |
Generated dnsmasq config (optional) |
Back up /etc/switch-manager/totp_secret — if the management computer fails and you have not backed this up you will need to regenerate the TOTP secret and re-scan it into your authenticator app.
Firmware Upgrade
The switch runs two software components that must both be upgraded:
- Diagnostic image (diag) — upgraded first
- Agent image (BOSS firmware) — upgraded second
Important rules:
- Upgrade one version at a time — cannot skip releases
- Always upgrade the diagnostic image before the agent image
- USB files must not be marked read-only or the transfer fails
- Firmware downloads require an active support contract at the Extreme Networks portal
- Check the ERS Announcements page to find the latest version
Your switch currently runs: BOSS v7.9.6.015 / Diagnostics 7.5.0.4
Method 1 — USB (no network needed, easiest)
- Download the diagnostic
.binand agent.imgfiles from the Extreme portal - Copy both files to a USB stick — ensure they are not read-only
- Insert the USB stick into the front panel USB port on the switch
- Via console or SSH (in enable mode):
download usb diag ers5900diag_7x_x_x_x.bin
Wait for it to complete and confirm, then:
download usb image ers5900_7x_x_x_x.img
The switch will reboot automatically after the agent upgrade.
Alternatively, from the boot menu (option 4 — "Download Agent/Diag") you can trigger a USB download without logging in first.
Method 2 — SFTP over SSH (requires network)
From enable mode on the switch, with an SFTP server running on your management computer:
copy sftp address 192.168.99.50 filename ers5900diag_7x_x_x_x.bin
Wait for completion, then:
copy sftp address 192.168.99.50 filename ers5900_7x_x_x_x.img
The switch reboots after the agent upgrade.
Method 3 — TFTP from a laptop (no dedicated server needed)
Your laptop can act as a temporary TFTP server. The switch has a default IP of 192.168.1.1/24 on VLAN 1 out of the box (or after a factory reset) — so on a fresh switch you can skip console IP configuration entirely. Just plug in Ethernet and go.
Plug both the console cable and an Ethernet cable from the laptop into the switch at the same time.
On the laptop:
sudo apt install tftpd-hpa
sudo cp ers5900diag_*.bin ers5900_*.img /srv/tftp/
sudo ip addr add 192.168.1.50/24 dev eth0 # must be on same /24 as switch default (192.168.1.x)
sudo systemctl start tftpd-hpa
Then on the switch via console (or via SSH to 192.168.1.1 if SSH is already enabled):
enable
copy tftp address 192.168.1.50 filename ers5900diag_7x_x_x_x.bin
Then:
copy tftp address 192.168.1.50 filename ers5900_7x_x_x_x.img
Stop the server when done:
sudo systemctl stop tftpd-hpa
No console needed on a factory switch: The switch answers at
192.168.1.1immediately after boot. As long as your laptop is on192.168.1.x/24, the TFTP transfer works without touching the console at all — useful if you only have an Ethernet cable and no console cable handy.
Method 4 — XMODEM over console cable (last resort, no network required)
If the USB port is broken and you have no Ethernet available at all, boot menu option 4 ("Download Agent/Diag") supports XMODEM file transfer directly over the serial console cable. No network required.
Warning: At 9600 baud, a 10 MB firmware file takes approximately 3 hours. Only use this if nothing else is possible.
In screen, after selecting option 4 from the boot menu, send the file with:
Ctrl-A then :exec !! sx -b /path/to/ers5900_7x_x_x_x.img
(sx is part of the lrzsz package: sudo apt install lrzsz)
Troubleshooting
Connection banner stuck on "Connecting..."
Backend not running or device not on VLAN 99. Check: sudo systemctl status switch-manager or docker compose ps
"Switch unreachable" in status pill
Backend running but cannot reach switch. Check: ping 192.168.99.1 from the management computer.
TOTP code rejected
Ensure time is synchronised on both the management computer and your phone. Backend allows one 30-second window of clock drift. Sync: sudo timedatectl set-ntp true
Command failed — switch error shown Read the error text — the switch says exactly what was wrong. Fix the configuration and push again. Already-succeeded commands do not need to be resent.
Host key rejection after switch reset
Re-pin: ssh-keyscan -H 192.168.99.1 > /etc/switch-manager/known_hosts
Factory reset — wiping a previously configured switch
When to wipe: if the switch has unknown previous configuration, unknown passwords, or you want a guaranteed clean slate. If the switch already responds to
adminwith no password and has no unexpected VLANs or ACLs, a wipe is not required — you can configure over the existing state.
Method 1 — Boot menu (easiest, works even if you don't know the password)
The console cable must be plugged in before power-on. During boot a diagnostic menu appears briefly:
DIAGNOSTIC BREAK MENU
59100 GTS-PWR+ Diagnostics 7.5.0.4
1 - Launch Primary Agent-1 Vers: 7.9.6.015
2 - Launch Secondary Agent-2 Vers: 7.6.2.019
3 - Toggle Primary Agent Selection
4 - Download Agent/ Diag
5 - Reinitialize Agent Configuration Files
6 - Display Error Log
7 - Display System Information
8 - Continue Boot Sequence
9 - Reset
A - Power-Cycle
B - Toggle Do-POST Selection [ ENABLED ]
C - Run POST tests
Select:
Booting Agent in 60 seconds...
Press 5 — "Reinitialize Agent Configuration Files". The switch wipes its config and reboots into factory defaults. You have 60 seconds before it boots automatically.
Other useful options: 1/2 switch between primary (v7.9.6) and secondary (v7.6.2) firmware. 6 shows the error log. 7 shows system info. 8 continues normal boot if you entered the menu by accident. 9 resets (reboots). A power-cycles. B toggles POST (hardware self-test) on/off. C runs POST tests manually.
Method 2 — CLI (requires working login)
enable
boot config flags factory
save config
boot
After either method the switch comes up with blank config, default credentials (admin / no password), and management IP 192.168.1.1 on VLAN 1. Continue from Step 5.
ctrld not filtering DNS
Check DHCP option 6 is set to the ctrld machine's IP on each VLAN pool. Check ctrld is running: ctrld status. Check the switch is handing out the right DNS: from a device, run nslookup example.com and verify the server IP matches.
WireGuard not connecting
Check port 51820 UDP is reachable from outside your network (router port forwarding may be needed for external access). Check: sudo systemctl status wg-quick@wg0
Service logs
# Native
journalctl -u switch-manager -f
# Docker
docker compose logs -f
docker compose ps
Command Reference — Tested Status
Commands the script sends to the switch, with known test status. Use this as a guide when debugging unexpected behaviour — untested commands may behave differently on your firmware version.
Read commands
| Command | Status | Notes |
|---|---|---|
show interfaces |
confirmed | |
show poe-main-status |
confirmed | |
show vlan |
confirmed | |
show sys-info |
confirmed | |
show arp |
confirmed | |
show config |
confirmed | Returns table output |
show ip helper-address |
not tested | |
show ip route default |
not tested | |
show dhcp-server leases |
invalid on this firmware | Script handles gracefully — returns empty |
show dhcp-server static-binding |
invalid on this firmware | Script handles gracefully — returns empty |
show dhcp-server |
invalid on this firmware | Script handles gracefully — returns empty |
Config / push commands
| Command | Status | Notes |
|---|---|---|
enable |
confirmed | |
configure terminal |
confirmed | |
terminal length 0 |
not tested | Disables pagination — sent before reads |
interface FastEthernet {port} |
confirmed | |
vlan members add {vid} {port} |
confirmed | |
vlan pvid {port} {vid} |
not tested | Sets native/untagged VLAN on a port |
vlan tagging {tagged-set} {port} |
not tested | Adds trunk tagging |
vlan create {vid} name "x" type port |
not tested | |
no vlan {vid} |
not tested | |
name "{description}" (under interface) |
not tested | Sets port description |
shutdown (under interface) |
not tested | Disables a port |
poe enable / no poe enable |
not tested | |
poe poe-limit {milliwatts} |
not tested | |
interface vlan {vid} (in config mode) |
not tested | |
ip access-list extended {name} |
not tested | |
ip access-group {name} in/out |
not tested | |
end |
not tested | Returns to enable mode |
save config |
confirmed | Caused a reboot on first run — watch the first time you push this |
save configreboot note: On at least one switch, issuingsave configtriggered a reboot. This may be firmware-version-specific or a one-time behaviour after certain config states. Subsequent saves have not reproduced it. Be aware when pushing config changes in a live environment.
Scrollback in screen (console cable sessions)
By default screen does not let you scroll up through output. Enable it with copy mode:
| Action | Keys |
|---|---|
| Enter scrollback mode | Ctrl-A then [ |
| Scroll up / down | Arrow keys or PgUp / PgDn |
| Exit scrollback mode | Esc or q |
To increase the scrollback buffer for a session (default is only 100 lines):
Ctrl-A then :scrollback 10000
To set it permanently, add this to ~/.screenrc:
defscrollback 10000
Useful when reviewing long show config or show interfaces output during console sessions.
What This Tool Does Not Do
- Does not manage OPNsense, pfSense, or any other device directly (OPNsense integration is read/sync only)
- Does not provide a terminal or shell — there is no way to type arbitrary commands through the main interface (CLI mode in settings is available for advanced users but still runs through the safety pipeline)
- Does not support multiple switches simultaneously
- Does not provide traffic analytics or bandwidth graphs
- Does not automatically discover or adopt new network devices
On the ERS Switch Family
These switches have no REST API. Everything this tool does is via SSH sessions that parse text output and send CLI commands — the same thing a human would do at a terminal, automated and wrapped in a browser interface.
There is an inherent limit to how reliably the tool can detect every possible error condition. The danger blocking system catches the known lethal patterns but cannot anticipate every possible misconfiguration. Use the CLI review step. Read what is about to be sent.
The console cable is always your fallback. Keep it accessible.
DNS Filtering — Port 53 Conflict Resolution
When installing ctrld (Option A — local install), ctrld needs to bind port 53. On Ubuntu and Debian, systemd-resolved holds port 53 via its stub listener.
The tool fixes this automatically during installation. It adds DNSStubListener=no to /etc/systemd/resolved.conf and restarts systemd-resolved. The service itself keeps running — it still manages /etc/resolv.conf and local hostname caching. Only the stub listener is disabled.
If the automatic fix fails (permission issue, non-standard config), fix it manually:
echo "[Resolve]" | sudo tee -a /etc/systemd/resolved.conf
echo "DNSStubListener=no" | sudo tee -a /etc/systemd/resolved.conf
sudo systemctl restart systemd-resolved
On OPNsense (Option B): OPNsense runs Unbound DNS on port 53. The correct approach is not to uninstall Unbound — it handles .lan hostnames and local DNS. Instead:
- In OPNsense UI: Services → Unbound DNS → General → set Listen Port to
5353, Listen Interface toLoopback (lo0). Save + Apply. - Configure ctrld to forward
*.lanand*.localto127.0.0.1:5353(the split-horizon block shown in the DNS tab result panel). - ctrld handles all other queries via DoH3 to Control D.
This keeps local names working while all external DNS is filtered per-VLAN through Control D.
DNS Enforcement ACLs
Without enforcement, a device can ignore DHCP-assigned DNS and use 8.8.8.8 directly, bypassing all ctrld filtering.
The DNS tab has an "Enforce DNS on Switch" button that generates ACLs blocking this. For each VLAN:
ip access-list extended DNS-ENFORCE-VLAN10
1 permit udp 192.168.10.0 0.0.0.255 host [ctrld-ip] eq 53
2 permit tcp 192.168.10.0 0.0.0.255 host [ctrld-ip] eq 53
3 deny udp 192.168.10.0 0.0.0.255 any eq 53
4 deny tcp 192.168.10.0 0.0.0.255 any eq 53
5 deny tcp 192.168.10.0 0.0.0.255 any eq 853
6 permit ip any any
interface vlan 10
ip access-group DNS-ENFORCE-VLAN10 in
Rules 1–2 permit DNS only to ctrld. Rules 3–4 block DNS anywhere else (8.8.8.8, Cloudflare, etc.). Rule 5 blocks DNS-over-TLS (port 853) as another bypass path. Rule 6 permits all other traffic so internet still works.
VLAN 99 (management) is automatically excluded — a broken ACL on the management VLAN would lock you out.
The generated commands are shown for review and pushed through the normal TOTP-gated push mechanism. Nothing is sent to the switch automatically.
Inter-VLAN Routing ACL Templates
The ACL Builder tab has a "Use Template" button that pre-fills common policies:
Staff VLAN — full internet, no management access Permits everything except access to VLAN 99 (192.168.99.0/24). Use on a staff or office VLAN where users need full internet but must not reach the management interface.
IoT VLAN — internet only, no RFC1918 Blocks all RFC1918 private address ranges (192.168.x.x, 10.x.x.x, 172.16-31.x.x). IoT devices get internet but cannot reach any other VLAN, internal servers, or management. Permits internet.
Guest VLAN — internet only, DNS must work first Like IoT but explicitly permits DNS to ctrld first (before the deny rules), ensuring DNS filtering continues to work even after RFC1918 is blocked.
Camera VLAN — NVR only Cameras may only send traffic to one NVR/DVR IP. All other traffic is dropped. Prevents cameras from phoning home, scanning the network, or accessing the internet directly.
All templates are fully editable after applying. The template fills the rule table — you adjust IPs, add rules, or delete rules before pushing.
Local Hostname Resolution (dnsmasq)
The DNS tab has a "Local Hostnames" section. It manages an optional dnsmasq container that resolves .lan hostnames for all devices on the network.
How it works:
- dnsmasq runs in Docker, listening on port 5353 on the management computer.
- ctrld is configured to forward
*.lanand*.localqueries to127.0.0.1:5353(split-horizon rule). - All other queries go through Control D as normal.
switch.mgmt.lanandmanagement.lanalways resolve to the management computer's IP.
Setup:
Add hostname→IP mappings in the DNS tab → Local Hostnames section. Click Save & Generate dnsmasq.conf. The tab shows:
- The generated
dnsmasq.confcontent and path - A docker-compose snippet to add the dnsmasq service
- A ctrld.toml block to enable split-horizon forwarding
Add the docker-compose snippet to docker-compose.yml, add the toml block to ctrld.toml, then:
docker compose up -d dnsmasq
ctrld restart
The dnsmasq.conf is written to /etc/switch-manager/dnsmasq.conf and mounted read-only into the container.
Files added:
| File | Location | Purpose |
|---|---|---|
local-hostnames.json |
/etc/switch-manager/ |
User-defined hostname→IP mappings |
dnsmasq.conf |
/etc/switch-manager/ |
Generated dnsmasq config |