Async Rust client and CLI for AT&T residential gateway routers — the BGW210, BGW320, 5268AC, NVG589/599 and similar devices that ship with fiber and fixed-wireless AT&T service. Talks to the gateway's web UI over plain HTTP on the LAN; no cloud API involvement.
Primary development target: Humax BGW320-500 running firmware 6.34.x.
Should work on most gateways that render the classic /cgi-bin/*.ha web
UI. If you hit a model that doesn't parse, patches welcome.
Read-only (no authentication required):
- Discover the gateway on the LAN
- System information (model, firmware, MAC, uptime)
- Broadband (WAN) status, IP addresses, traffic counters
- LAN / DHCP status and interface summary
- Full list of devices known to the gateway (wired and Wi-Fi)
Mutating (requires the 12-character device access code printed on the label under the router):
- Reboot the gateway
- Restart the broadband connection
- Restart a Wi-Fi radio (2.4 GHz or 5 GHz)
- Clear and rescan the device list
make help # every target with a one-line description
make build # cargo build --workspace
make test # fmt-check + clippy + unit + simulator
make test-live LIVE_ROUTER=192.168.1.254use attrouter::Client;
#[tokio::main]
async fn main() -> attrouter::Result<()> {
// Auto-detect the gateway on the local network
let client = Client::discover().await?;
println!("gateway at {}", client.ip());
let info = client.sysinfo().await?;
println!("{:?} firmware {:?}", info.model_number, info.software_version);
for d in client.devices().await? {
println!("{:?} {:?}", d.mac_address, d.name);
}
// Mutating actions need authentication
client.login("ABCD1234EFGH").await?;
client.restart_broadband().await?;
Ok(())
}cargo install attrctl # from crates.io
cargo install --path attrctl # from a local checkout
attrctl discover
attrctl info
attrctl broadband
attrctl devices --online
attrctl status -o yaml
attrctl --access-code ABCD1234EFGH reboot
attrctl --access-code ABCD1234EFGH restart-broadband
attrctl --access-code ABCD1234EFGH restart-wifi 5
Environment variables: ATTROUTER_IP, ATTROUTER_ACCESS_CODE, LOG_LEVEL.
Three layers, all wired into the Makefile:
-
Inline unit tests (
#[cfg(test)] mod testsinattrouter/src/*.rs) cover HTML parsing, MD5 hashing, the vendor/model heuristic, URL parsing, and login-page detection. Fast, no network. Uses sanitized fixtures fromattrouter/src/testdata/.make test-unit
-
Simulator integration tests (
attrouter/tests/sim_integration.rs) spin upFakeGateway, an in-memory axum server that mimics a BGW320 well enough to exercise everyClientmethod end-to-end — including the full login nonce dance, reboot, broadband restart, Wi-Fi restart, and clear-and-rescan. Safe to run anywhere.make test-sim
make testruns both layers 1 and 2. -
Live integration tests (
attrouter/tests/live_integration.rs) drive the sameClientagainst a real AT&T gateway viaProtectiveProxy, an axum forwarding proxy. By default the proxy runs inDestructiveMode::Block: any POST to/cgi-bin/restart.ha,/crestart.ha,/wrestart.ha,/reset.ha, or/devices.ha(clear and rescan) is intercepted and answered with a canned success page. The real router is never rebooted.LIVE_ROUTER=192.168.1.254 \ LIVE_ROUTER_ACCESS_CODE=ABCD1234EFGH \ make test-live
To actually let destructive operations through (e.g. for acceptance testing on a router you are willing to reboot), use
test-live-destructive, which flips the proxy toTransparentand runs tests serially:LIVE_ROUTER=192.168.1.254 \ LIVE_ROUTER_ACCESS_CODE=ABCD1234EFGH \ make test-live-destructive
Warning:
test-live-destructivewill reboot the gateway. Do not run it against a router you depend on for connectivity — you will lose your network connection mid-test and the run will fail.LIVE_ROUTERis optional — the test harness auto-discovers the AT&T gateway by probing the OS default route, just likeattrctl discover. On a non-AT&T network, live tests skip silently.make test(unit + sim) never touches the network.
The AT&T gateway UI is classic XHTML with a <tr><th>label</th><td>value</td></tr>
layout on every status page, form POSTs protected by a per-page nonce, and
MD5-hashed logins that match the router's login.js:
hashpassword = md5(access_code + nonce)
attrouter scrapes the tables with scraper, manages the SessionID
cookie via reqwest's cookie jar, and computes the login hash with
md-5. There's no JSON API to call — everything is pulled from the
rendered web pages.
AGPL-3.0-or-later