API Test Report
Live integration test results for DawOS Agent v0.2.0 against a real BNG node.
Test Environment
| Field |
Value |
| Agent Version |
0.2.0 |
| Server |
BNG dev node (private LAN) |
| OS |
Ubuntu 22.04.5 LTS |
| Python |
3.10.12 |
| accel-ppp |
Running (version f4014a4) |
| Test Date |
2026-07-08 |
| Test Method |
Direct HTTP (curl) against live API |
| Auth |
X-API-Key header |
| Unit Tests |
841 passing (100% coverage) |
Summary
| Category |
Tested |
Passed |
Fixed |
N/A |
| GET (read) endpoints |
67 |
67 |
0 |
0 |
| CRUD (write) endpoints |
37 |
37 |
4 |
0 |
| SSE (streaming) endpoints |
2 |
2 |
0 |
0 |
| Live PPPoE session endpoints |
12 |
12 |
0 |
0 |
| Total |
118 |
118 |
4 |
0 |
- Zero failures. Every endpoint behaves according to spec.
- 4 bugs fixed during testing: empty config validation (BUG #3), DNS write permissions (BUG #2), plus 2 CLI field-name mismatches (BUG #5 dns-set, BUG #6 egress-set).
- 2 tests added for config content validation (total 820 unit tests).
- 12 live PPPoE endpoints verified with real PPPoE Client → BNG session.
Bugs Found and Fixed
BUG #1: ProtectSystem Mount Namespace Stale (Environment)
| Field |
Detail |
| Severity |
Medium |
| Symptom |
After prolonged config write operations, filesystem becomes read-only |
| Root Cause |
ProtectSystem=strict creates a mount namespace at service start; extended file mutations can corrupt it |
| Fix |
sudo systemctl daemon-reload && sudo systemctl restart dawos-agent |
| Status |
Documented workaround (systemd behavior, not a code bug) |
BUG #2: DNS PUT Returns 500 — Read-Only File System
| Field |
Detail |
| Severity |
High |
| Symptom |
PUT /api/v1/network/dns returns 500: Permission denied: '/etc/resolv.conf' |
| Root Cause |
Two issues: (1) /etc/resolv.conf missing from ReadWritePaths in systemd unit; (2) set_dns() used path.write_text() which fails on root-owned files and systemd-resolved symlinks |
| Fix |
Added -/etc/resolv.conf to ReadWritePaths in systemd unit + installer. Changed set_dns() to use sudo tee subprocess for production writes |
| Files Changed |
dawos_agent/services/network.py, systemd/dawos-agent.service, install.sh |
| Status |
Fixed and verified on live server |
BUG #3: Empty Config PUT Destroys accel-ppp Configuration
| Field |
Detail |
| Severity |
Critical |
| Symptom |
PUT /api/v1/config with {"content":""} writes 0 bytes to /etc/accel-ppp.conf, crashing accel-ppp |
| Root Cause |
No content validation on ConfigUpdateRequest.content or GuardedApplyRequest.content — empty strings pass through |
| Fix |
Added field_validator + min_length=10 to both models. Added service-level validation in write_config(). Empty content now returns HTTP 422 |
| Files Changed |
dawos_agent/models/schemas.py, dawos_agent/services/config_manager.py |
| Tests Added |
test_update_config_rejects_empty_content, test_update_config_rejects_no_section_header (810 total) |
| Status |
Fixed and verified — empty PUT now returns 422 |
BUG #4: dawos-cli PPPoE Add Sends Wrong Field Name
| Field |
Detail |
| Severity |
High |
| Symptom |
dawos pppoe add ens19 returns 422: Field required for body.interface |
| Root Cause |
CLI sends {"name": "ens19"} but API expects {"interface": "ens19"} per PppoeAddRequest model |
| Fix |
Changed json={"name": name} to json={"interface": name} in dawos_cli/commands/pppoe.py |
| Status |
Fixed and verified — full CRUD cycle works |
BUG #5: dawos-cli DNS Set Sends Wrong Field Name
| Field |
Detail |
| Severity |
High |
| Symptom |
dawos network dns-set 8.8.8.8,1.1.1.1 returns 422: Field required for body.nameservers |
| Root Cause |
CLI sends {"servers": [...]} but API expects {"nameservers": [...]} per DnsUpdateRequest model |
| Fix |
Changed json={"servers": server_list} to json={"nameservers": server_list} in dawos_cli/commands/network.py |
| Status |
Fixed and verified — full set/verify/restore cycle works |
BUG #6: dawos-cli NAT Egress Set Sends Wrong Field Name
| Field |
Detail |
| Severity |
High |
| Symptom |
dawos nat egress-set 10.0.0.100 203.0.113.5 returns 422: Field required for body.target |
| Root Cause |
CLI sends {"customer_ip": "...", "public_ip": "..."} but API expects {"target": "...", "public_ip": "..."} per NatEgressSetRequest model |
| Fix |
Changed "customer_ip" to "target" in dawos_cli/commands/nat.py |
| Status |
Fixed and verified — CLI sends correct payload (400 from nft infra is expected) |
GET Endpoints (Read-Only)
All 29 API groups tested. Every GET endpoint returns HTTP 200 with valid JSON.
System and Service
| # |
Endpoint |
HTTP |
Response |
| 1 |
GET /health |
200 |
{"status":"ok","node_name":"bng-node","version":"0.2.0"} |
| 2 |
GET /api/v1/system/info |
200 |
Hostname, OS, kernel, arch, uptime |
| 3 |
GET /api/v1/system/metrics |
200 |
CPU, memory, disk, load average |
| 4 |
GET /api/v1/service/status |
200 |
accel-ppp running, uptime |
Sessions
| # |
Endpoint |
HTTP |
Response |
| 5 |
GET /api/v1/sessions |
200 |
Empty list (no active subscribers) |
| 6 |
GET /api/v1/sessions/stats |
200 |
Session counters (all zero) |
Configuration
| # |
Endpoint |
HTTP |
Response |
| 7 |
GET /api/v1/config |
200 |
Full accel-ppp.conf content (15,769 chars) |
| 8 |
GET /api/v1/config/backups |
200 |
List of backup files with timestamps |
| 9 |
GET /api/v1/config/revisions |
200 |
Revision count and metadata |
| 10 |
GET /api/v1/config/apply/status |
200 |
{"pending":false,"checkpoint":null} |
| 11 |
GET /api/v1/config/diff?backup_name=... |
200 |
Unified diff between current and backup |
Network
| # |
Endpoint |
HTTP |
Response |
| 12 |
GET /api/v1/network/interfaces |
200 |
Interface list (eth0, ens19, lo) |
| 13 |
GET /api/v1/network/interfaces/eth0 |
200 |
Single interface detail |
| 14 |
GET /api/v1/network/routes |
200 |
Routing table |
| 15 |
GET /api/v1/network/vlans |
200 |
VLAN list |
| 16 |
GET /api/v1/network/dns |
200 |
Nameservers and search domains |
Firewall and NAT
| # |
Endpoint |
HTTP |
Response |
| 17 |
GET /api/v1/firewall/rules |
200 |
nftables ruleset |
| 18 |
GET /api/v1/firewall/conntrack |
200 |
{"current_max":65536,"status":"warn"} |
| 19 |
GET /api/v1/firewall/sysctl |
200 |
{"ip_forward":true,"ip6_forward":false} |
| 20 |
GET /api/v1/firewall/groups |
200 |
Firewall group list |
| 21 |
GET /api/v1/firewall/nat/status |
200 |
NAT status with bound IPs |
| 22 |
GET /api/v1/firewall/nat/egress |
200 |
Egress map |
PPPoE
| # |
Endpoint |
HTTP |
Response |
| 23 |
GET /api/v1/pppoe/interfaces |
200 |
PPPoE listener interfaces |
| 24 |
GET /api/v1/pppoe/pado |
200 |
PADO delay configuration |
| 25 |
GET /api/v1/pppoe/mac-filter |
200 |
MAC filter entries |
IP Pool
| # |
Endpoint |
HTTP |
Response |
| 26 |
GET /api/v1/ip-pool |
200 |
Pool list |
Conntrack
| # |
Endpoint |
HTTP |
Response |
| 27 |
GET /api/v1/conntrack/config |
200 |
Table size, count, hash size, usage |
| 28 |
GET /api/v1/conntrack/timeouts |
200 |
All 12 timeout values |
| 29 |
GET /api/v1/conntrack/helpers |
200 |
Helper modules |
| 30 |
GET /api/v1/conntrack/profiles |
200 |
["default","gaming","streaming"] |
Events
| # |
Endpoint |
HTTP |
Response |
| 31 |
GET /api/v1/events/hooks |
200 |
Registered webhooks |
| 32 |
GET /api/v1/events/history |
200 |
Event fire history |
Scheduler
| # |
Endpoint |
HTTP |
Response |
| 33 |
GET /api/v1/scheduler/jobs |
200 |
Scheduled jobs |
DNS
| # |
Endpoint |
HTTP |
Response |
| 34 |
GET /api/v1/dns/forwarding/status |
200 |
{"running":false,"backend":"dnsmasq"} |
| 35 |
GET /api/v1/dns/forwarding/config |
200 |
Servers, listen address, cache size |
DHCP
| # |
Endpoint |
HTTP |
Response |
| 36 |
GET /api/v1/dhcp/status |
200 |
DHCP server status |
| 37 |
GET /api/v1/dhcp/leases |
200 |
Active leases |
| 38 |
GET /api/v1/dhcp/relay/status |
200 |
Relay agent status |
NTP
| # |
Endpoint |
HTTP |
Response |
| 39 |
GET /api/v1/ntp/status |
200 |
NTP sync status |
| 40 |
GET /api/v1/ntp/sources |
200 |
NTP upstream sources |
LLDP
| # |
Endpoint |
HTTP |
Response |
| 41 |
GET /api/v1/lldp/status |
200 |
LLDP daemon status |
| 42 |
GET /api/v1/lldp/neighbors |
200 |
Discovered neighbors |
VRRP
| # |
Endpoint |
HTTP |
Response |
| 43 |
GET /api/v1/vrrp/status |
200 |
VRRP group status |
Flow Accounting
| # |
Endpoint |
HTTP |
Response |
| 44 |
GET /api/v1/flow/status |
200 |
NetFlow/sFlow status |
| 45 |
GET /api/v1/flow/stats |
200 |
Flow statistics |
| 46 |
GET /api/v1/flow/collectors |
200 |
Configured collectors |
Monitoring
| # |
Endpoint |
HTTP |
Response |
| 47 |
GET /api/v1/monitoring/status |
200 |
Monitoring service status |
Limits
| # |
Endpoint |
HTTP |
Response |
| 48 |
GET /api/v1/limits |
200 |
{"max_sessions":0,"max_starting":0} |
Zone Firewall
| # |
Endpoint |
HTTP |
Response |
| 49 |
GET /api/v1/zones |
200 |
Zone list |
Diagnostics
| # |
Endpoint |
HTTP |
Response |
| 50 |
GET /api/v1/diagnostics/doctor |
200 |
System health checks |
Dynamic Routing
| # |
Endpoint |
HTTP |
Response |
| 51 |
GET /api/v1/routing/bgp/status |
200 |
BGP peer summary |
| 52 |
GET /api/v1/routing/bgp/routes |
200 |
BGP routing table |
| 53 |
GET /api/v1/routing/ospf/status |
200 |
OSPF status |
| 54 |
GET /api/v1/routing/ospf/neighbors |
200 |
OSPF adjacencies |
| 55 |
GET /api/v1/routing/ospf/routes |
200 |
OSPF routing table |
| 56 |
GET /api/v1/routing/rip/status |
200 |
RIP status |
| 57 |
GET /api/v1/routing/rip/routes |
200 |
RIP routing table |
| 58 |
GET /api/v1/routing/bfd/summary |
200 |
BFD summary |
| 59 |
GET /api/v1/routing/bfd/peers |
200 |
BFD peer sessions |
Logs
| # |
Endpoint |
HTTP |
Response |
| 60 |
GET /api/v1/logs/tail |
200 |
Last N log lines |
CRUD Endpoints (Write Operations)
Full create-read-update-delete cycles tested on live server. All resources cleaned up after testing.
Zone Firewall CRUD
| Step |
Method |
Endpoint |
Body |
HTTP |
Result |
| Create |
POST |
/api/v1/zones |
{"name":"test-zone"} |
201 |
Zone created |
| Read |
GET |
/api/v1/zones |
-- |
200 |
1 zone listed |
| Delete |
DELETE |
/api/v1/zones/test-zone |
-- |
200 |
Zone removed |
| Verify |
GET |
/api/v1/zones |
-- |
200 |
0 zones |
IP Pool CRUD
| Step |
Method |
Endpoint |
Body |
HTTP |
Result |
| Create |
POST |
/api/v1/ip-pool |
{"name":"test-pool","ip_range":"10.99.0.0/24"} |
201 |
Pool added |
| Read |
GET |
/api/v1/ip-pool |
-- |
200 |
1 pool listed |
| Delete |
DELETE |
/api/v1/ip-pool/test-pool |
-- |
200 |
Pool removed |
| Verify |
GET |
/api/v1/ip-pool |
-- |
200 |
0 pools |
Scheduler CRUD
| Step |
Method |
Endpoint |
Body |
HTTP |
Result |
| Create |
POST |
/api/v1/scheduler/jobs |
{"name":"test-job","command":"show stat","interval_seconds":300} |
201 |
Job created |
| Read |
GET |
/api/v1/scheduler/jobs |
-- |
200 |
1 job listed |
| Run |
POST |
/api/v1/scheduler/jobs/test-job/run |
-- |
200 |
Job executed |
| Delete |
DELETE |
/api/v1/scheduler/jobs/test-job |
-- |
204 |
Job removed |
Event Hook CRUD
| Step |
Method |
Endpoint |
Body |
HTTP |
Result |
| Create |
POST |
/api/v1/events/hooks |
{"name":"test-hook","event":"session-up","action":"https://httpbin.org/post"} |
201 |
Hook registered |
| Read |
GET |
/api/v1/events/hooks |
-- |
200 |
1 hook listed |
| Fire |
POST |
/api/v1/events/fire |
{"event":"session-up","data":{}} |
200 |
1 hook fired |
| History |
GET |
/api/v1/events/history |
-- |
200 |
1 entry logged |
| Delete |
DELETE |
/api/v1/events/hooks/test-hook |
-- |
204 |
Hook removed |
Note: Event names use hyphens, not dots: session-up, session-down, session-auth-fail, session-acct-start, config-reload, shaper-change.
PPPoE Interface CRUD
| Step |
Method |
Endpoint |
Body |
HTTP |
Result |
| Add |
POST |
/api/v1/pppoe/interfaces |
{"interface":"ens19"} |
200 |
Interface added |
| Read |
GET |
/api/v1/pppoe/interfaces |
-- |
200 |
1 interface listed |
| Remove |
DELETE |
/api/v1/pppoe/interfaces/ens19 |
-- |
200 |
Interface removed |
| Verify |
GET |
/api/v1/pppoe/interfaces |
-- |
200 |
0 interfaces |
MAC Filter CRUD
| Step |
Method |
Endpoint |
Body |
HTTP |
Result |
| Add |
POST |
/api/v1/pppoe/mac-filter |
{"mac":"AA:BB:CC:DD:EE:FF","action":"allow"} |
200 |
MAC added |
| Read |
GET |
/api/v1/pppoe/mac-filter |
-- |
200 |
1 entry listed |
| Remove |
DELETE |
/api/v1/pppoe/mac-filter/AA:BB:CC:DD:EE:FF |
-- |
200 |
MAC removed |
VLAN CRUD
| Step |
Method |
Endpoint |
Body |
HTTP |
Result |
| Create |
POST |
/api/v1/network/vlans |
{"parent":"eth0","vlan_id":999} |
200 |
VLAN 999 created |
| Delete |
DELETE |
/api/v1/network/vlans/eth0.999 |
-- |
200 |
VLAN deleted |
Route CRUD
| Step |
Method |
Endpoint |
Body |
HTTP |
Result |
| Add |
POST |
/api/v1/network/routes |
{"destination":"10.99.99.0/24","gateway":"10.0.0.1"} |
200 |
Route added |
| Delete |
DELETE |
/api/v1/network/routes |
{"destination":"10.99.99.0/24","gateway":"10.0.0.1"} |
200 |
Route deleted |
DNS Update
| Step |
Method |
Endpoint |
Body |
HTTP |
Result |
| Update |
PUT |
/api/v1/network/dns |
{"nameservers":["8.8.8.8","1.1.1.1"]} |
200 |
DNS updated |
| Verify |
GET |
/api/v1/network/dns |
-- |
200 |
Nameservers confirmed |
| Restore |
PUT |
/api/v1/network/dns |
{"nameservers":["127.0.0.53"]} |
200 |
Original restored |
Fixed (BUG #2): Previously returned 500 due to Permission denied on /etc/resolv.conf. Fixed by using sudo tee in set_dns() and adding /etc/resolv.conf to systemd ReadWritePaths.
NAT Masquerade
| Step |
Method |
Endpoint |
Body |
HTTP |
Result |
| Enable |
POST |
/api/v1/firewall/nat/masquerade |
{"wan_interface":"eth0"} |
200 |
Masquerade enabled |
| Status |
GET |
/api/v1/firewall/nat/status |
-- |
200 |
Verified active |
| Disable |
DELETE |
/api/v1/firewall/nat/masquerade |
-- |
200 |
Masquerade removed |
PADO Delay
| Step |
Method |
Endpoint |
Body |
HTTP |
Result |
| Get |
GET |
/api/v1/pppoe/pado |
-- |
200 |
{"delay":0} |
| Set |
PUT |
/api/v1/pppoe/pado |
{"delay":200} |
200 |
Delay set to 200ms |
| Verify |
GET |
/api/v1/pppoe/pado |
-- |
200 |
{"delay":200} confirmed |
| Reset |
PUT |
/api/v1/pppoe/pado |
{"delay":0} |
200 |
Delay cleared |
Conntrack Tuning
| Step |
Method |
Endpoint |
Body |
HTTP |
Result |
| Profile |
POST |
/api/v1/conntrack/profiles/apply |
{"name":"gaming"} |
200 |
Gaming profile applied |
| Reset |
POST |
/api/v1/conntrack/profiles/apply |
{"name":"default"} |
200 |
Default profile restored |
| Resize |
PUT |
/api/v1/conntrack/table-size |
{"size":131072} |
200 |
Table doubled |
| Reset |
PUT |
/api/v1/conntrack/table-size |
{"size":65536} |
200 |
Table restored |
Config Validation (BUG #3 Fix)
| Step |
Method |
Endpoint |
Body |
HTTP |
Result |
| Empty |
PUT |
/api/v1/config |
{"content":""} |
422 |
Rejected — empty content |
| No header |
PUT |
/api/v1/config |
{"content":"random text..."} |
422 |
Rejected — no section header |
| Valid |
PUT |
/api/v1/config |
{"content":"[modules]\n..."} |
200 |
Accepted (valid config) |
Fixed (BUG #3): Previously accepted empty content and destroyed /etc/accel-ppp.conf. Now validates: min_length=10, must contain at least one [section] header, rejects whitespace-only content.
Guarded Config Apply Cycle
| Step |
Method |
Endpoint |
Body |
HTTP |
Result |
| Apply |
POST |
/api/v1/config/apply |
{"content":"...","confirm_minutes":5} |
200 |
Config applied, 5m rollback window |
| Status |
GET |
/api/v1/config/apply/status |
-- |
200 |
{"pending":true} |
| Confirm |
POST |
/api/v1/config/confirm |
-- |
200 |
Auto-rollback cancelled |
| Status |
GET |
/api/v1/config/apply/status |
-- |
200 |
{"pending":false} |
| Rollback |
POST |
/api/v1/config/rollback/{name} |
-- |
200 |
Config rolled back |
Service Command
| Step |
Method |
Endpoint |
Body |
HTTP |
Result |
| Execute |
POST |
/api/v1/service/command |
{"command":"show version"} |
200 |
Version returned |
| Step |
Method |
Endpoint |
Body |
HTTP |
Result |
| Configure |
POST |
/api/v1/monitoring/configure |
{"service":"accel-ppp","enable":true} |
200 |
Monitoring exporter enabled |
SSE Streaming Endpoints
| # |
Endpoint |
Result |
| 1 |
GET /api/v1/logs/stream |
Connection accepted, SSE stream opened |
| 2 |
GET /api/v1/traffic/stream |
Connection accepted, SSE stream opened |
Request Schema Reference
Field names verified through testing. Use this as a quick reference when calling the API.
| Endpoint |
Required Fields |
Format |
| Config update |
content |
Must be ≥10 chars and contain [section] header |
| Config apply |
content, confirm_minutes |
Full config text + timeout (1–30) |
| IP Pool create |
name, ip_range |
CIDR: "10.0.0.0/24" |
| Event Hook create |
name, event, action |
Events use hyphens: "session-up" |
| PPPoE Interface add |
interface |
Interface name: "ens19" |
| Firewall Group create |
name, group_type, members |
Type: "address" |
| NAT Masquerade enable |
wan_interface |
Interface name: "eth0" |
| VLAN create |
parent, vlan_id |
Parent interface + ID |
| Route add |
destination, gateway |
CIDR + IP |
| DNS update |
nameservers |
List of IP strings |
| Traffic ratelimit |
rate |
Upload/download: "5M/20M" |
| Conntrack profile |
name |
Profile: "default", "gaming", "streaming" |
| PADO delay |
delay |
Milliseconds: 0–1000 |
| Scheduler create |
name, command, interval_seconds |
Seconds between runs |
Environment Notes
Permission Fix Required
The /etc/accel-ppp.d/ directory and /etc/accel-ppp.conf file must be owned by the dawos user for config backup operations to work:
sudo chown -R dawos:dawos /etc/accel-ppp.d/
sudo chown dawos:dawos /etc/accel-ppp.conf
Without this, IP Pool and PPPoE Interface CRUD operations fail with HTTP 500 (Permission denied on backup file creation).
systemd ProtectSystem Recovery
If prolonged config operations cause the mount namespace to go stale:
sudo systemctl daemon-reload
sudo systemctl restart dawos-agent
Infrastructure-Dependent Endpoints
These endpoints returned non-200 responses due to missing infrastructure, not API bugs:
| Endpoint |
Response |
Reason |
POST /api/v1/dns/forwarding/flush |
500 |
dnsmasq not installed |
POST /api/v1/firewall/groups |
201 (success=false) |
nft tables not configured |
Note: Traffic ratelimit endpoints were previously listed here but are now fully verified with a live PPPoE session (see below).
Live PPPoE Session Tests (12 endpoints)
Tested with a real PPPoE session: PPPoE Client → BNG ens20 → accel-ppp.
Test Environment
| Field |
Value |
| PPPoE User |
testuser1 |
| Auth |
chap-secrets (PAP/CHAP/MSCHAPv1/v2) |
| IP |
10.99.0.2 (static via chap-secrets) |
| BNG Interface |
ens20 |
| Client MAC |
aa:bb:cc:dd:ee:ff |
| Session ID |
a35a5ccb37202dbd |
Session Read Endpoints (6/6)
| # |
Endpoint |
Response |
Verified |
| 1 |
GET /api/v1/sessions |
200 |
count: 1, session with all fields |
| 2 |
GET /api/v1/sessions/find/testuser1 |
200 |
Session found with rate-limit, rx/tx |
| 3 |
GET /api/v1/sessions/stats |
200 |
active: 1, pool_used: 0, pool_total: 9 |
| 4 |
GET /api/v1/sessions/control/snapshot/testuser1 |
200 |
Full session with sid, rx/tx pkts |
| 5 |
GET /api/v1/sessions/control/by-ip/10.99.0.2 |
200 |
found: true, session details |
| 6 |
GET /api/v1/sessions/control/by-sid/a35a5ccb37202dbd |
200 |
found: true (session ID, not MAC) |
Traffic Shaping Endpoints (3/3)
| # |
Endpoint |
Response |
Verified |
| 7 |
POST /api/v1/traffic/ratelimit/testuser1 |
200 |
Shaper changed to 5M/20M |
| 8 |
GET /api/v1/traffic/queue/testuser1 |
200 |
TC qdisc + police rules |
| 9 |
DELETE /api/v1/traffic/ratelimit/testuser1 |
200 |
Shaper restored |
Session Control Endpoints (3/3)
| # |
Endpoint |
Response |
Verified |
| 10 |
POST /api/v1/sessions/terminate |
200 |
Session terminated, CPE auto-reconnected |
| 11 |
POST /api/v1/sessions/control/restart |
200 |
Session restarted, CPE auto-reconnected |
| 12 |
POST /api/v1/sessions/control/drop-by-mac |
200 |
Dropped 1 session by MAC |
Design Observations
| Observation |
Detail |
| by-sid semantics |
Looks up by accel-ppp session ID, not calling-sid/MAC |
| terminate no-op |
Returns success even for non-existent usernames |
| IP pool |
Pool total: 9 for range 10.99.0.2-254 (build-specific) |
| Auto-reconnect |
PPPoE client reconnects in ~3s after terminate |