A TR-069/CWMP CPE simulator and test client for goacs-go, built to exercise the ACS end-to-end without needing real hardware. It speaks the exact wire dialect goacs-go expects (session cookies, envelope framing, RPC set), presents editable TR-098/TR-106, TR-181, VoIP (TR-104) and STB/TV (TR-135) parameter trees, includes TR-143 diagnostics, and can simulate anywhere from one device to a fleet of hundreds — each with its own serial number — connecting concurrently.
go build -o goacs-client ./cmd/goacs-clientRequires Go 1.23+. No external services needed to build; profiles are embedded in the binary.
Run a single device in the foreground with full request/response XML logged, useful for debugging one exchange:
./goacs-client device --acs-url http://localhost:8085/acs --profile tr098-router --serial SIM-DEBUG-0001 --verboseRun a fleet of 300 simulated devices, mixing profiles, ramping up gradually:
./goacs-client run --acs-url http://localhost:8085/acs --count 300 --ramp 20 --profile tr098-routerOr drive a fleet from a config file (mixed profiles, weighted, connection-request enabled):
./goacs-client run --config configs/example-fleet.yamlCtrl+C stops a fleet run; each device keeps re-Informing on its own periodic interval (and reacting to Connection Requests / Reboots) until then.
A profile is a YAML file describing a device's identity and its full parameter tree (dotted TR-069 paths, values, xsd types, writable flags). Profiles can include composable modules, so VoIP/STB/diagnostics content is written once and shared across both the TR-098 and TR-181 root conventions.
Built-in profiles:
| Profile | Root | Description |
|---|---|---|
tr098-router |
InternetGatewayDevice |
Plain TR-098/TR-106 router/CPE |
tr181-router |
Device |
Plain TR-181 router/CPE |
voip-gateway |
InternetGatewayDevice |
TR-098 router + VoIP gateway (TR-104 VoiceService) |
combined-ont |
Device |
Router + VoIP + STB triple-play ONT/CPE (TR-181) |
Inspect and edit them without hand-writing YAML:
./goacs-client profile list
./goacs-client profile show tr098-router LANDevice.1.Hosts
./goacs-client profile get tr098-router LANDevice.1.WLANConfiguration.1.SSID
./goacs-client profile set tr098-router LANDevice.1.WLANConfiguration.1.SSID "MySSID" --writable
./goacs-client profile add-instance tr098-router LANDevice.1.Hosts.Host.
./goacs-client profile validate combined-ontset/add-instance write a copy of the profile into --profiles-dir (default ./profiles), which is always consulted before the embedded defaults — the embedded originals are never modified.
Profile YAML schema (see internal/profiles/data/*.yaml for real examples):
name: my-profile
root: Device # set on a selectable top-level profile; omit on a composable module
description: "..."
device:
manufacturer: "..."
oui: "001A2B"
product_class: "..."
includes: [voip_tr104] # other profiles/modules to merge in first (paths are root-relative)
parameters:
- path: DeviceInfo.SoftwareVersion
value: "1.0.0"
type: xsd:string
writable: false
objects: # addable/deletable multi-instance containers
- path: LANDevice.1.Hosts.Host.
instances: [1] # seeded instance numbers
template: # leaf shape materialized for every instance
- suffix: HostName
value: ""
type: xsd:string
writable: falseAdd a new profile or module by dropping a .yaml file next to the existing ones under internal/profiles/data/ (or in your --profiles-dir); give it a root to make it independently selectable, or omit root to make it an includable module.
Diagnostic objects (IPPingDiagnostics, TraceRouteDiagnostics, Download/UploadDiagnostics, UDPEchoConfig, NSLookupDiagnostics — flat under TR-098, nested under IP.Diagnostics.*/DNS.Diagnostics.* for TR-181) are part of every router profile. Setting DiagnosticsState=Requested via SetParameterValues (exactly how a real ACS drives TR-143) triggers a short simulated run; polling DiagnosticsState afterward via GetParameterValues reports Complete plus plausible result parameters, or an occasional Error_Other for realism.
Pass --conn-request (on by default for run, opt-in for device) to have each simulated device run a small HTTP server answering Digest-authenticated Connection Requests — mirroring exactly how goacs-go's own acs/http/acsrequest.go calls a real CPE. The device reports the listener's URL as ManagementServer.ConnectionRequestURL and, when kicked, opens a fresh session with event 6 CONNECTION REQUEST.
See configs/example-fleet.yaml for the full schema: acs_url, auth (Basic/Digest credentials the simulated CPEs answer the ACS's challenge with), count, ramp_per_second, serial (sequential or random scheme), connection_request, weighted groups of profiles, and periodic_inform_override_seconds.
cmd/goacs-client cobra CLI (run, device, profile, version)
internal/cwmp CWMP wire protocol: envelope, RPC builders/parsers
internal/datamodel generic parameter tree + TR-143 diagnostics state machine
internal/profiles YAML profile loader/composer (embedded defaults + user overrides)
internal/device device identity + serial number schemes
internal/session one CWMP session: auth transport, request/response dispatch loop
internal/connreq Connection Request listener (Digest server)
internal/fleet concurrent multi-device orchestration, ramp-up, stats
internal/config fleet run YAML schema
go test ./...Every package has unit tests; internal/session and internal/fleet include full integration tests against an in-process fake ACS (httptest.Server) covering the whole RPC lifecycle (Inform → GetParameterNames/Values → SetParameterValues → AddObject/DeleteObject → Download/TransferComplete → Reboot).