Files
LibreQoS/prd.json
T

109 lines
8.0 KiB
JSON

{
"product": "LibreQoS",
"branchName": "radius-accounting-dynamic-circuits",
"summary": "Extend the opt-in RADIUS accounting listener from session tracking to dynamic circuit application, with fallback speeds and MAC-based ShapedDevices matching for operator-managed account plans.",
"stories": [
{
"id": "RADIUS-010",
"title": "Gate RADIUS dynamic circuit application",
"description": "As a LibreQoS operator, I want RADIUS dynamic circuit application to have its own explicit activation settings, so that I can receive accounting packets without changing shaping until I choose to enable it.",
"priority": 1,
"passes": true,
"acceptanceCriteria": [
"RADIUS accounting continues to listen and track sessions when dynamic circuit application is disabled.",
"RADIUS-created dynamic circuits are not applied unless both RADIUS application settings and the existing dynamic_circuits feature are enabled.",
"Invalid activation settings are rejected during configuration validation before listener startup.",
"Config round-trip tests cover disabled application, enabled application, missing dynamic_circuits dependency, and valid activation settings."
]
},
{
"id": "RADIUS-011",
"title": "Configure fallback RADIUS speeds",
"description": "As a LibreQoS operator, I want an optional default speed profile for RADIUS-created circuits, so that sessions without packet-provided or lookup-provided rates can still be shaped at a safe default plan.",
"priority": 1,
"passes": true,
"acceptanceCriteria": [
"RADIUS accounting configuration can define download_min_mbps, upload_min_mbps, download_max_mbps, and upload_max_mbps as an optional fallback profile.",
"Validation rejects non-finite rates, rates less than or equal to zero, and minimum rates greater than maximum rates.",
"When no packet or lookup rate exists and no fallback profile is configured, the session remains pending with a missing-rate reason.",
"When a fallback profile is configured, it is used only after higher-priority rate sources fail to resolve a rate."
]
},
{
"id": "RADIUS-012",
"title": "Match ShapedDevices by RADIUS MAC",
"description": "As a LibreQoS operator, I want RADIUS accounting sessions to optionally match ShapedDevices entries by MAC address, so that my existing account-plan sync can provide circuit metadata while RADIUS supplies the active IP addresses.",
"priority": 1,
"passes": true,
"acceptanceCriteria": [
"A configuration option enables or disables matching RADIUS Calling-Station-Id values against ShapedDevices MAC values.",
"MAC matching normalizes common colon, hyphen, dot, plain-hex, and mixed-case formats before comparison.",
"A unique MAC match supplies circuit ID, circuit name, device ID, device name, parent fields, speed fields, and SQM override metadata for the dynamic circuit.",
"RADIUS-provided IPv4 and IPv6 address data replaces the matched ShapedDevices address fields for the active dynamic circuit.",
"No match or more than one matching ShapedDevices row leaves the session pending and emits no dynamic circuit."
]
},
{
"id": "RADIUS-013",
"title": "Resolve RADIUS sessions into shaped devices",
"description": "As a LibreQoS operator, I want shapeable RADIUS sessions converted into valid dynamic circuit device definitions, so that active subscriber addresses can be attached to the correct circuit and plan.",
"priority": 1,
"passes": true,
"acceptanceCriteria": [
"A session becomes shapeable only when it has a stable circuit identity, device identity, at least one IP address or prefix, parent attachment metadata, and resolved rates.",
"When no ShapedDevices MAC match supplies identity, the default circuit ID is derived from NAS identity and Acct-Session-Id, and display names prefer User-Name and Calling-Station-Id when present.",
"Rate resolution uses decoded RADIUS rate data before ShapedDevices MAC-match rates, and uses the configured fallback profile only when both are missing.",
"Missing IP, missing parent, missing rate, and ambiguous MAC match cases remain pending with distinct reasons.",
"Unit tests cover Start without IP, Interim activation with IP, matched ShapedDevices metadata with RADIUS IP replacement, and fallback identity generation."
]
},
{
"id": "RADIUS-014",
"title": "Apply RADIUS dynamic circuit upserts",
"description": "As a LibreQoS operator, I want accepted RADIUS Start and Interim-Update packets to create or update dynamic circuits through the existing daemon path, so that active sessions are shaped without editing ShapedDevices.csv.",
"priority": 1,
"passes": true,
"acceptanceCriteria": [
"Shapeable Start and Interim-Update events submit CreateDynamicCircuit requests with the resolved ShapedDevice payload.",
"RADIUS dynamic circuit application uses the existing lqosd dynamic circuit path and never writes dynamic_circuits.json directly from lqos_radius.",
"The UDP Accounting-Response path does not wait for dynamic circuit persistence or bakery overlay work to finish.",
"Create or update failures are logged with circuit and session identity and do not stop the RADIUS listener.",
"Tests use fake command or bus sinks and do not require live shaping, bakery, TC, XDP, or root privileges."
]
},
{
"id": "RADIUS-015",
"title": "Expire RADIUS dynamic circuits",
"description": "As a LibreQoS operator, I want RADIUS-created dynamic circuits removed when accounting sessions stop or expire, so that stale subscriber IPs do not remain shaped after the session ends.",
"priority": 1,
"passes": true,
"acceptanceCriteria": [
"Stop events submit RemoveDynamicCircuit for the active circuit ID, and duplicate Stop events are harmless.",
"Sessions that exceed default_ttl_seconds without refresh remove any active RADIUS-created dynamic circuit.",
"Accounting-On and Accounting-Off events mark matching NAS sessions stale and remove active circuits no later than stale_grace_seconds unless refreshed by Start or Interim-Update.",
"Removal uses the same circuit ID that was used for creation, including IDs supplied by ShapedDevices MAC matching.",
"Fake-time tests cover Stop removal, TTL expiry removal, stale NAS reset expiry, and refresh before expiry."
]
},
{
"id": "RADIUS-016",
"title": "Report RADIUS activation outcomes",
"description": "As a LibreQoS operator, I want clear RADIUS dynamic circuit diagnostics, so that I can see why a session was shaped, pending, expired, or rejected without exposing shared secrets.",
"priority": 2,
"passes": true,
"acceptanceCriteria": [
"Diagnostics distinguish active, pending, stopped, stale, expired, and apply-failed RADIUS session states.",
"Pending diagnostics include missing IP, missing parent, missing rate, missing session identity, no MAC match, and ambiguous MAC match reasons.",
"Create, update, remove, and expiry counters are tracked separately from packet accept and reject counters.",
"Logs and diagnostics include circuit/session identifiers needed for troubleshooting but never include shared secret contents."
]
}
],
"notes": [
"This PRD starts after the completed listener, packet parsing, session tracking, and deferred command-intent work from RADIUS-001 through RADIUS-009.",
"RADIUS-provided IP and prefix data is authoritative for active dynamic circuit addresses; ShapedDevices MAC matches supply account-plan and topology metadata only.",
"Dynamic circuit application must use the existing lqosd dynamic circuit path and must continue to respect the existing dynamic_circuits.enabled safety gate.",
"Automated tests for this PRD should use fake sinks, fake time, and loopback-only packet handling; they must not require root privileges or live TC/XDP shaping state."
]
}