Skip to content

About

Dual-WAN status & connectivity checker for the TP-Link Omada ER605 — drives its interactive Dropbear CLI over SSH with expect.

Topics

Resources

Stars

1 star

Watchers

0 watching

Forks

Latest commit

 

History

31 Commits

Folders and files

Repository files navigation

ER605 Dual-WAN Connectivity Checker

Bash scripts that log into a TP-Link Omada ER605 router over SSH and report the live status of both WAN links plus their connectivity — one checker plus two CLI-wrangling dev tools.

Tested on ER605 v2.0, firmware 2.3.0 Build 20250428.


Compatibility

Verified only on the hardware above — everything below is informed inference, not tested. The project has two layers, with very different portability:

The driver & techniques (broadly reusable). The SSH workarounds aren't really ER605-specific; they apply to a whole class of devices:

  • Legacy ssh-rsa host key → re-enabled via HostKeyAlgorithms=+ssh-rsa — applies to almost any Dropbear-based device (embedded routers, switches, NAS, IoT).
  • No SSH "exec" mode → PTY + expect prompt-detection — applies to most interactive-CLI gear (Cisco IOS, MikroTik, HPE/Aruba, many embedded CLIs).
  • enable → privileged # prompt, non-zero exit codes, no clean EOF on logout (close instead of expect eof) — all common Cisco-style-CLI traits.
  • probe_cli.sh as a "discover an unknown CLI's grammar" tool is device-agnostic.

The commands & parsers (Omada-gateway-specific). show interface switchport <1-5>, the Field.....Value parsing, and the WAN/gateway logic are tied to the Omada gateway firmware (and the 2.3.0 text format).

Device class Expectation
ER605 (v2, fw 2.3.0) ✅ Tested — works.
Other Omada gateways/routers — ER7206, ER7212PC, ER8411 🟡 Likely works with minor tweaks (same firmware lineage). Different port counts (adjust WAN*_PORT and the 1-5 switchport range); field labels may differ slightly — re-check with probe_cli.sh.
Other ER605 firmware versions 🟡 Driver fine; parsers may need adjusting if labels changed.
Omada switches (TL-SG…) ❌ Different, more Cisco-like CLI and command set — parsers won't fit (driver techniques still do).
Omada EAPs (access points) ❌ Different CLI again.
Older non-Omada SafeStream routers (TL-ER6020/6120, R600VPN) ❌ Different/older firmware; may not expose this CLI at all.

Bottom line: on another Omada gateway expect most of it to work after small config tweaks; on switches/APs reuse the driver, rewrite the commands.


Configuration

Config comes from three sources, highest precedence first:

  1. CLI flags / positional args — --host <ip>, <password>, --trace
  2. Inline environment variables — ROUTER_IP=… ROUTER_PASS=… ./er605-watch
  3. .env file next to the scripts — git-ignored, holds your site-specific values

No router IP or password is stored in the repo. Set up your local .env once:

cp .env.example .env
chmod 600 .env            # keep the password readable only by you
# edit .env: set ROUTER_IP and ROUTER_PASS (and optionally WAN1_GW/WAN2_GW, etc.)

.env (note: .env.example is the committed template; .env itself is git-ignored — an exact match in .gitignore, not .env*, so the example stays tracked):

ROUTER_IP=192.168.0.1
ROUTER_PASS=yourpassword
# optional: ROUTER_USER, ROUTER_PORT, WAN1_PORT, WAN2_PORT, PING_PUBLIC, WAN1_GW, WAN2_GW
# optional ISP labels: WAN1_ISP, WAN2_ISP  (shown as e.g. "Airtel Fiber (WAN1)")

With .env in place you can just run ./er605-watch. Override ad-hoc without touching the file:

./er605-watch --host 10.0.0.1 'pass'      # different router, this run only
ROUTER_IP=10.0.0.1 ./er605-watch          # via env var

The same .env is shared by probe_cli.sh and debug_expect.sh.


Why this is harder than it looks — the ER605 CLI limitations

The ER605 does not expose a normal Linux/SSH environment. Its SSH service is Dropbear fronting a small, custom, menu-style CLI. Several things that work on a normal host fail here, and the scripts are built specifically to work around them:

# Limitation Consequence Workaround used
1 Legacy host key only. Dropbear offers only the ssh-rsa (SHA-1) host key, which modern OpenSSH disables by default. ssh fails with "no matching host key type found. Their offer: ssh-rsa" and the script dies before doing anything. Connect with -o HostKeyAlgorithms=+ssh-rsa -o PubkeyAcceptedAlgorithms=+ssh-rsa.
2 No exec mode. Running ssh router "show arp" does not execute the command — the CLI ignores it and just prints a Match mac success banner, then closes. One-shot commands silently return garbage. Allocate a real PTY (ssh -tt) and feed commands interactively.
3 Non-zero exit codes on success. The CLI returns a non-zero exit status even when a command succeeds. Scripts that check $? wrongly conclude "SSH failed". Judge success by the SSH transport / by whether a prompt came back, not by the remote command's exit code.
4 No readiness signal / no flow control. The CLI gives no prompt-ready marker, and input sent too early is discarded. Piping all commands at once truncates or drops output. Drive the session with expect: send a command, then wait for the # prompt to return before sending the next. No blind sleeps — it runs as fast as the router responds.
5 Privileged commands gated behind enable. The base prompt (>) only offers help/exit/enable/disable. show ... / ping don't exist until you elevate. Send enable first to reach the # prompt before running anything useful.
6 Password auth only (in practice). Key auth is often not usable; login is via password. Cannot script ssh non-interactively without help. Let expect type the password at the prompt.
7 No clean logout / EOF. After exit, the router does not promptly send EOF; expect eof blocks for the full timeout (~30s per session). A naive driver is dozens of seconds slower than the actual work. Once all output is captured, close the connection from our side instead of waiting for EOF.
8 ping/tracert cannot be bound to a source interface. Both take only an IP (ping <ip> / tracert <ip>; extra args give "Too many parameters") and follow the routing table — there is no "ping via WAN2" option. You can't directly test "internet over WAN2"; a traceroute only shows the active path. Ping each WAN's own default gateway (which egresses that specific link) for a true per-WAN health check; ping a public IP separately for overall internet. tracert's hop 1 reveals which WAN the route used.
9 Limited command set. show interface switchport <1-5> and show interface vlan <id> require parameters; commands like show ip route are not registered. Generic networking commands don't exist. Use only the verified command grammar (see probe_cli.sh).

Because of #2 and #4, every interaction is essentially screen-scraping an interactive terminal session — not a clean request/response API. The scripts use expect to make that reliable and fast.


Scripts

er605-watch — the dual-WAN status report

./er605-watch                                  # all from .env  (~13s)
./er605-watch '<router-password>'              # password as arg, rest from .env
./er605-watch --host <ip> '<router-password>'  # override the router IP
./er605-watch --fast                           # link status only, skip pings (~3s)
./er605-watch --json | jq .                    # machine-readable output (needs jq)
./er605-watch --trace                          # also run a traceroute (slow)

Flags: --fast/-f (skip all pings/traceroute — instant link-status check, good for frequent polling), --json/-j (emit one JSON object on stdout, progress on stderr), --trace/-t (full check then a traceroute), --trace-only (a pure traceroute — skips the WAN/ARP/ping queries entirely), --host/-H <ip>.

Exit codes (for cron/alerting): 0 all WANs up · 1 one WAN down · 2 both WANs down · 3 router unreachable · 4 usage/config error. So a "both WANs down" alert is just er605-watch --fast >/dev/null 2>&1 || [ $? -eq 2 ] && notify….

JSON shape (--json): {timestamp, router, mode, overall, wans:[{port,name,isp, type,status,proto,ip,gateway,up,ping:{target,loss_pct,rtt_ms,state,online}}], internet, arp:[{interface,ip,mac,type}], traceroute}. In --fast mode ping/internet are null and up comes from the switchport link status. In --trace-only mode the WAN/ARP/ping queries are skipped entirely, so each WAN is state:"skipped"/up:false, arp is [], and internet is null — only traceroute is populated. isp is the friendly WAN*_ISP label (falls back to WAN1/WAN2). mode is full / fast / trace-only. This feeds a status-bar module, tray apps, Home Assistant, Prometheus, etc.

What it does (expect-driven — waits on the # prompt, no blind sleeps; one SSH login if WAN*_GW are set, otherwise two — see the gateway note below):

  1. Connects with the host-key workaround + PTY, types the password, and runs enable to reach privileged mode.
  2. show interface switchport 1/2 + show arp — parses each WAN's:
    • Port name, VLAN type, Routing Interface Status (UP/DOWN)
    • Protocol (dhcp / pppoe / static)
    • IP address, Default Gateway, Primary DNS
    • The full ARP table is printed as-is.
  3. Pings (same session), parsed for packet loss and average RTT:
    • Each WAN's default gateway — a genuine per-link health check, since gateway traffic can only egress that interface.
    • A public IP (8.8.8.8 by default) for overall internet reachability (this follows the active route / load-balance / failover).
  4. Prints a colour-coded summary (ONLINE / DEGRADED / OFFLINE).
  5. --trace / -t (optional) — appends a tracert to the public target. Useful, but slow: traceroute waits out a timeout on every unresponsive (* * *) hop, so a single trace can take ~30–60s. Hop 1 reveals which WAN the active route used. In a terminal (non---json) the hops stream live as the router emits them. --trace-only runs just the trace — it skips the switchport/ARP/ping queries entirely (handy for the panel Traceroute action).

Configurable via .env / env vars (see Configuration): ROUTER_IP, ROUTER_USER, ROUTER_PORT, WAN1_PORT, WAN2_PORT, PING_PUBLIC, WAN1_GW / WAN2_GW, and WAN1_ISP / WAN2_ISP (friendly ISP labels shown in the output, the isp JSON field, and the integrations — e.g. "Airtel Fiber (WAN1)").

On the gateways (dynamic vs. speed): by default WAN1_GW / WAN2_GW are blank, so the script auto-discovers each WAN's gateway live from show interface switchport — nothing site-specific is needed, at the cost of a second SSH login. Optionally set both (in .env) to your real gateways to run everything in a single login (~1s faster); the script then still reads the live gateway and prints a ⚠ configured ≠ live warning if your ISP changes one.

probe_cli.sh — the CLI discovery / debug tool

A helper used to reverse-engineer the CLI. It opens a paced, privileged session and dumps the raw output of whatever commands you pass, so you can inspect the real text format before writing parsers.

# reads ROUTER_IP from .env; password via RPASS (or ROUTER_PASS in .env)
export RPASS='<router-password>'
./probe_cli.sh                                   # runs a default set of show commands
./probe_cli.sh "show arp" "show system-info"     # probe specific commands
DELAY=12 ./probe_cli.sh "ping 8.8.8.8"           # bump per-command wait for slow commands

probe_cli.sh uses the older sleep-paced driver (and sshpass) — it predates the expect rewrite, which is fine for a discovery tool where you read the raw dump anyway.

debug_expect.sh — per-step timing probe

Diagnostic for the expect driver: runs a full session (login → enable → a couple of shows → a ping → logout) and prints the milliseconds spent on each step, so you can spot which expect is stalling. This is the tool that pinned the ~30s-per-session expect eof stall down to the logout step.

ROUTER_IP=... RPASS='<router-password>' ./debug_expect.sh   # or set them in .env

Integrations

Optional front-ends that surface er605-watch's status elsewhere. Each is self-contained with its own README; both consume the --json output (the contract documented under er605-watch).

  • integrations/home-assistant/ — push dual-WAN status to Home Assistant over MQTT for phone alerts, dashboards, history, and automations. A publisher box runs er605-watch --json on a timer and publishes to a Mosquitto broker; HA auto-creates entities via MQTT Discovery. Router credentials stay on the publisher box.
  • integrations/ubuntu-panel/ — a GNOME top-panel tray indicator with a state-tinted icon and a per-WAN dropdown. Runs er605-watch directly on your desktop (no broker needed). Menu actions: Refresh, Full check, and Traceroute (opens a terminal running --trace-only).
  • integrations/ubuntu-panel-mqtt/ — the same panel indicator, but fed from MQTT (subscribes to the publisher's er605/status) instead of driving the router. No router creds on the desktop; needs a broker.
  • integrations/ubuntu-panel-hybrid/ — display from MQTT (like the MQTT panel) plus an on-demand Traceroute that runs er605-watch --trace-only in a terminal (like the direct panel). Status needs only broker read access; the Traceroute action also needs er605-watch + router creds on this box.

Run only one ER605 panel indicator (direct, MQTT, or hybrid) — each adds its own panel icon.

Per-WAN ISP labels: set WAN1_ISP / WAN2_ISP in .env and the integrations (and the CLI) show e.g. "Airtel Fiber (WAN1)" in place of "WAN1".


Requirements

  • bash
  • expect — sudo apt-get install expect (drives the interactive CLI; er605-watch types the password itself, so sshpass is not required)
  • jq — sudo apt-get install jq (only for --json)
  • ssh (OpenSSH client)
  • SSH enabled on the router (Omada: enable CLI/SSH access)

Note: probe_cli.sh is the older sleep-paced helper and still uses sshpass — only needed if you use that debug tool.

Notes & caveats

  • Password handling. Passing the password as a CLI argument leaves it in your shell history and process list (ps). Prefer putting ROUTER_PASS in a chmod 600 .env file, or an inline ROUTER_PASS=… env var.
  • Speed. A full run takes ~14s. Because er605-watch waits on the # prompt (via expect) instead of fixed sleeps, the time is essentially just the SSH logins plus the actual ping durations — there is no wasted blind waiting.
  • Firmware-specific. Output parsing is matched to firmware 2.3.0's exact text format. A different firmware version may change field labels; use probe_cli.sh to re-check and adjust the parsers.

Secret-scanning git hooks

This repo's hard rule is no real IPs, passwords, gateways, MACs, usernames, or hostnames in tracked files — only placeholders. To enforce it locally, hooks/ ships dependency-free pre-commit / pre-push hooks that scan the diff for private/site IPs, MACs, hardcoded credentials, and private keys, and block the commit/push if any slip in.

./hooks/install.sh        # enable (sets core.hooksPath; run once per clone)

Bypass a false positive with --no-verify, or allowlist a literal in hooks/secret-allow.txt. These are local hooks (skippable, per-clone) — for an unbypassable layer, also enable GitHub Push Protection / a CI scanner. Full details: hooks/README.md.

License

MIT © Vivek K (ivivek)

About

Dual-WAN status & connectivity checker for the TP-Link Omada ER605 — drives its interactive Dropbear CLI over SSH with expect.

Topics

Resources

Stars

1 star

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages