Skip to content

Protection API

Endpoints that protect and expose the DTVSOL Super Router itself: who may reach the management ports, who fail2ban has banned, which public ports are forwarded to subscribers, the per-MAC forwarding rules, DHCP option 82 (relay circuit / remote ID) and the router’s own SNMP agent.

Authentication, error format and status codes are described in the API overview.

The API port (8880 by default) and the OLT monitor port (8881 by default) sit behind one allow-list. While the list is empty both ports are open to any source ("status": "open (no restrictions)"); as soon as it holds one network, only new connections from the listed networks are accepted on those ports and every other new connection is dropped. The rules are saved so they survive a reboot.

The allowed networks.

Terminal window
curl -H "X-API-Key: YOUR_API_KEY" http://ROUTER-IP:8880/protect
{
"port": 8880,
"count": 1,
"networks": [
{ "network": "XXX.XXX.XXX.0/24", "comment": "NOC", "added": "2026-09-27 10:00:00" }
],
"status": "protected"
}
Name In Type Notes
network body string An IPv4 address or address/0..32. A bare address is stored as /32. Required.
comment body string Free text.
Terminal window
curl -X POST http://ROUTER-IP:8880/protect \
-H "X-API-Key: YOUR_API_KEY" -H "Content-Type: application/json" \
-d '{"network": "XXX.XXX.XXX.0/24", "comment": "NOC"}'
{
"ok": true,
"code": 201,
"message": "Network XXX.XXX.XXX.0/24 allowed",
"networks": [ { "network": "XXX.XXX.XXX.0/24", "comment": "NOC", "added": "2026-09-27 10:00:00" } ]
}

Errors: 400 Invalid network: …, 409 Network … already allowed. GET /protect/add?network=…&comment=… does the same with query parameters.

Name In Type Notes
network body string As it was added; a bare address means /32.
Terminal window
curl -X DELETE http://ROUTER-IP:8880/protect \
-H "X-API-Key: YOUR_API_KEY" -H "Content-Type: application/json" \
-d '{"network": "XXX.XXX.XXX.0/24"}'
{ "ok": true, "code": 200, "message": "Network XXX.XXX.XXX.0/24 removed", "networks": [] }

404 Network … not found when it is not on the list. GET /protect/delete?network=… does the same with query parameters.

fail2ban bans sources that repeatedly fail authentication (the API logs every rejected key).

Whether fail2ban runs, and each jail’s counters and banned addresses.

Terminal window
curl -H "X-API-Key: YOUR_API_KEY" http://ROUTER-IP:8880/fail2ban
{
"running": true,
"jails": [
{ "jail": "sshd", "currently_banned": 2, "total_banned": 7, "banned_ips": ["XXX.XXX.XXX.1", "XXX.XXX.XXX.7"] }
]
}

When fail2ban is not active: {"running": false, "jails": []}.

Name In Type Notes
unban body string The IPv4 or IPv6 address to unban.
Terminal window
curl -X POST http://ROUTER-IP:8880/fail2ban \
-H "X-API-Key: YOUR_API_KEY" -H "Content-Type: application/json" \
-d '{"unban": "XXX.XXX.XXX.1"}'
{ "ok": true, "code": 200, "message": "Unbanned XXX.XXX.XXX.1", "detail": "" }

400 Invalid IP; 500 Unban failed (with detail) when fail2ban refuses.

Inbound port forwards (DNAT) from a public address and port to a subscriber’s address and port. /portforward is an alias of /pf with exactly the same behaviour. A forward is identified by protocol + public address + public port. Forwards whose client address sits on a switched-off VLAN are kept but not applied until the VLAN is switched on again.

The saved forwards and the live NAT rules that carry them.

Terminal window
curl -H "X-API-Key: YOUR_API_KEY" http://ROUTER-IP:8880/pf
{
"count": 1,
"forwards": [
{ "proto": "tcp", "public_ip": "XXX.XXX.XXX.10", "public_port": 8080, "client_ip": "100.64.0.2", "client_port": 80, "comment": "camera", "created": "2026-09-27 10:00:00" }
],
"live": ["-A PREROUTING -d XXX.XXX.XXX.10/32 -p tcp -m tcp --dport 8080 … -j DNAT --to-destination 100.64.0.2:80"]
}

Alias of GET /pf.

Terminal window
curl -H "X-API-Key: YOUR_API_KEY" http://ROUTER-IP:8880/portforward
Name In Type Notes
proto body string tcp (default) or udp.
public_ip body string Public IPv4 address; empty means any address on the router.
public_port body integer 1–65535. Required.
client_ip body string The subscriber’s IPv4 address. Required.
client_port body integer 1–65535. Required.
comment body string Free text.
Terminal window
curl -X POST http://ROUTER-IP:8880/pf \
-H "X-API-Key: YOUR_API_KEY" -H "Content-Type: application/json" \
-d '{"proto": "tcp", "public_ip": "XXX.XXX.XXX.10", "public_port": 8080, "client_ip": "100.64.0.2", "client_port": 80, "comment": "camera"}'
{
"ok": true,
"code": 201,
"message": "Port forward added",
"forward": { "proto": "tcp", "public_ip": "XXX.XXX.XXX.10", "public_port": 8080, "client_ip": "100.64.0.2", "client_port": 80, "comment": "camera", "created": "2026-09-27 10:00:00" }
}

Errors: 400 (proto must be tcp or udp, Invalid public_ip, Ports must be 1..65535, Invalid client_ip); 409 A forward for that proto/public_ip:port already exists.

Name In Type Notes
proto body string tcp (default) or udp.
public_ip body string As added; empty for “any address”.
public_port body integer As added.
Terminal window
curl -X DELETE http://ROUTER-IP:8880/pf \
-H "X-API-Key: YOUR_API_KEY" -H "Content-Type: application/json" \
-d '{"proto": "tcp", "public_ip": "XXX.XXX.XXX.10", "public_port": 8080}'
{ "ok": true, "code": 200, "message": "Port forward removed" }

404 No forward matching tcp/XXX.XXX.XXX.10:8080 when none matches.

Read-only views of the per-MAC forwarding rules the router keeps for registered clients.

The MAC rules in the forwarding chain, each with the client it belongs to.

Name In Type Notes
network query string Only clients whose address is in this IPv4 network (a.b.c.d/nn); rules without a known client are then left out.
Terminal window
curl -H "X-API-Key: YOUR_API_KEY" "http://ROUTER-IP:8880/firewall?network=100.64.0.0/22"
{
"count": 1,
"mac_rules": [
{ "mac": "AA:BB:CC:DD:EE:FF", "iface": "vlan100", "client": { "ip": "100.64.0.2", "hostname": "client-aabbccddeeff", "comment": "" } }
]
}

Without a filter, a rule whose MAC is not a registered client has "client": null.

The whole forwarding chain as the kernel lists it (verbose, with counters and line numbers), one string per line.

Terminal window
curl -H "X-API-Key: YOUR_API_KEY" http://ROUTER-IP:8880/firewall/full
{ "forward_chain": ["Chain FORWARD (policy ACCEPT 0 packets, 0 bytes)", "num pkts bytes target prot opt in out source destination", "…"] }

When DHCP requests arrive through a relay (for example an OLT) that adds option 82, the relay’s circuit ID and remote ID identify the subscriber’s port.

Leases that carry option 82 data, whether capture to the log is enabled, and the last captured log lines (up to 50).

Terminal window
curl -H "X-API-Key: YOUR_API_KEY" http://ROUTER-IP:8880/option82
{
"count": 1,
"from_leases": [
{ "ip": "100.64.0.2", "circuit_id": "0:1:2", "remote_id": "\"olt-1\"", "mac": "AA:BB:CC:DD:EE:FF" }
],
"capture_enabled": true,
"recent_log": ["… DTVSOL-OPT82 ip=100.64.0.2 circuit=00:01:02 remote=…"]
}
Name In Type Notes
enable body boolean true to log option 82 on every lease commit, false to stop.
Terminal window
curl -X POST http://ROUTER-IP:8880/option82 \
-H "X-API-Key: YOUR_API_KEY" -H "Content-Type: application/json" \
-d '{"enable": true}'
{ "ok": true, "code": 200, "message": "Option 82 capture enabled (logged to journal + parsed by /option82)" }

If the DHCP server rejects the configuration, the change is reverted and the answer is 400 with detail; reading option 82 from the leases still works. Disabling answers "message": "Option 82 capture disabled".

The router’s own read-only SNMP agent (v2c, UDP 161, IPv4 and IPv6), used by external monitoring systems to graph interface and per-client traffic.

Whether the agent runs and its settings. The community itself is never returned — only whether one is set.

Terminal window
curl -H "X-API-Key: YOUR_API_KEY" http://ROUTER-IP:8880/snmp
{
"running": true,
"enabled": "enabled",
"listen": "udp/161 (IPv4+IPv6)",
"community_set": true,
"sys_location": "POP 1",
"sys_contact": "noc@example.net",
"per_client_count": 120,
"sample": [ "…" ]
}

The answer also contains a few informational text fields describing what the agent publishes.

Fields left out keep their current value.

Name In Type Notes
community body string Read-only community: 1–64 characters of A-Z a-z 0-9 _ . : -.
location body string System location text.
contact body string System contact text.
Terminal window
curl -X POST http://ROUTER-IP:8880/snmp \
-H "X-API-Key: YOUR_API_KEY" -H "Content-Type: application/json" \
-d '{"community": "YOUR_COMMUNITY", "location": "POP 1", "contact": "noc@example.net"}'
{ "ok": true, "code": 200, "message": "SNMP configured", "detail": null }

Errors: 400 Invalid community (A-Z a-z 0-9 _.:- , max 64); 500 when the settings could not be saved or the agent failed to restart (snmpd restart failed, with detail).

The same operations through GET /api?action=…, with every parameter in the query string (see the action API).

Action Query parameters Same as
action=protect-list — GET /protect
action=protect-add network, comment POST /protect
action=protect-delete network DELETE /protect
action=fail2ban-status — GET /fail2ban
action=fail2ban-unban ip POST /fail2ban
action=pf-list — GET /pf
action=pf-add proto, public_ip, public_port, client_ip, client_port, comment POST /pf
action=pf-del proto, public_ip, public_port DELETE /pf
action=firewall network GET /firewall
action=firewall-full — GET /firewall/full
action=option82 — GET /option82
action=snmp-status — GET /snmp
action=snmp-set community, location, contact POST /snmp