Advanced Network¶
BGP/FRR, GRE/VXLAN tunnels, IPSec, self IPs, static routes, virtual servers, Keepalived (VRRP).
All endpoints below are mounted under /api. Authentication: Authorization: Bearer <token> unless noted as public.
Endpoint count: 88
Endpoints¶
| Method | Path | Handler | Guards |
|---|---|---|---|
POST |
/api/advanced-network/bgp/auto-detect |
advNetAutoDetect |
RequireSuperuser(), RequirePlugin(db, "advanced-network") |
GET |
/api/advanced-network/bgp/blackholes |
listAdvNetBlackholes |
RequireSuperuser(), RequirePlugin(db, "advanced-network") |
POST |
/api/advanced-network/bgp/blackholes |
addAdvNetBlackhole |
RequireSuperuser(), RequirePlugin(db, "advanced-network") |
DELETE |
/api/advanced-network/bgp/blackholes/:id |
deleteAdvNetBlackhole |
RequireSuperuser(), RequirePlugin(db, "advanced-network") |
GET |
/api/advanced-network/bgp/config |
getAdvNetBGPConfig |
RequireSuperuser(), RequirePlugin(db, "advanced-network") |
POST |
/api/advanced-network/bgp/config |
saveAdvNetBGPConfig |
RequireSuperuser(), RequirePlugin(db, "advanced-network") |
GET |
/api/advanced-network/bgp/events |
getAdvNetBGPEvents |
RequireSuperuser(), RequirePlugin(db, "advanced-network") |
GET |
/api/advanced-network/bgp/flowspec |
listAdvNetFlowspec |
RequireSuperuser(), RequirePlugin(db, "advanced-network") |
POST |
/api/advanced-network/bgp/flowspec |
addAdvNetFlowspec |
RequireSuperuser(), RequirePlugin(db, "advanced-network") |
DELETE |
/api/advanced-network/bgp/flowspec/:id |
deleteAdvNetFlowspec |
RequireSuperuser(), RequirePlugin(db, "advanced-network") |
PUT |
/api/advanced-network/bgp/flowspec/:id |
updateAdvNetFlowspec |
RequireSuperuser(), RequirePlugin(db, "advanced-network") |
GET |
/api/advanced-network/bgp/flowspec/supported |
getAdvNetFlowspecSupported |
RequireSuperuser(), RequirePlugin(db, "advanced-network") |
GET |
/api/advanced-network/bgp/flowspec/templates |
getAdvNetFlowspecTemplates |
RequireSuperuser(), RequirePlugin(db, "advanced-network") |
GET |
/api/advanced-network/bgp/networks |
listAdvNetBGPNetworks |
RequireSuperuser(), RequirePlugin(db, "advanced-network") |
POST |
/api/advanced-network/bgp/networks |
addAdvNetBGPNetwork |
RequireSuperuser(), RequirePlugin(db, "advanced-network") |
DELETE |
/api/advanced-network/bgp/networks/:id |
deleteAdvNetBGPNetwork |
RequireSuperuser(), RequirePlugin(db, "advanced-network") |
GET |
/api/advanced-network/bgp/peers |
listAdvNetBGPPeers |
RequireSuperuser(), RequirePlugin(db, "advanced-network") |
POST |
/api/advanced-network/bgp/peers |
addAdvNetBGPPeer |
RequireSuperuser(), RequirePlugin(db, "advanced-network") |
DELETE |
/api/advanced-network/bgp/peers/:id |
deleteAdvNetBGPPeer |
RequireSuperuser(), RequirePlugin(db, "advanced-network") |
PUT |
/api/advanced-network/bgp/peers/:id |
updateAdvNetBGPPeer |
RequireSuperuser(), RequirePlugin(db, "advanced-network") |
GET |
/api/advanced-network/bgp/routes |
getAdvNetBGPRoutes |
RequireSuperuser(), RequirePlugin(db, "advanced-network") |
GET |
/api/advanced-network/bgp/routes-detail |
getAdvNetBGPRoutesDetail |
RequireSuperuser(), RequirePlugin(db, "advanced-network") |
GET |
/api/advanced-network/bgp/status |
getAdvNetBGPStatus |
RequireSuperuser(), RequirePlugin(db, "advanced-network") |
GET |
/api/advanced-network/bgp/summary |
getAdvNetBGPSummary |
RequireSuperuser(), RequirePlugin(db, "advanced-network") |
GET |
/api/advanced-network/export |
exportAdvNetConfig |
RequireSuperuser(), RequirePlugin(db, "advanced-network") |
POST |
/api/advanced-network/import |
importAdvNetConfig |
RequireSuperuser(), RequirePlugin(db, "advanced-network") |
GET |
/api/advanced-network/interface-ips |
listAdvNetInterfaceIPs |
RequireSuperuser(), RequirePlugin(db, "advanced-network") |
GET |
/api/advanced-network/interfaces |
listAdvNetInterfaces |
RequireSuperuser(), RequirePlugin(db, "advanced-network") |
POST |
/api/advanced-network/keepalived/check-l2 |
checkAdvNetL2Connectivity |
RequireSuperuser(), RequirePlugin(db, "advanced-network") |
POST |
/api/advanced-network/keepalived/install |
installAdvNetKeepalived |
RequireSuperuser(), RequirePlugin(db, "advanced-network") |
POST |
/api/advanced-network/keepalived/restart |
restartAdvNetKeepalived |
RequireSuperuser(), RequirePlugin(db, "advanced-network") |
GET |
/api/advanced-network/keepalived/status |
getAdvNetKeepalivedStatus |
RequireSuperuser(), RequirePlugin(db, "advanced-network") |
GET |
/api/advanced-network/keepalived/vrrp |
listAdvNetVRRP |
RequireSuperuser(), RequirePlugin(db, "advanced-network") |
POST |
/api/advanced-network/keepalived/vrrp |
createAdvNetVRRP |
RequireSuperuser(), RequirePlugin(db, "advanced-network") |
DELETE |
/api/advanced-network/keepalived/vrrp/:id |
deleteAdvNetVRRP |
RequireSuperuser(), RequirePlugin(db, "advanced-network") |
PUT |
/api/advanced-network/keepalived/vrrp/:id |
updateAdvNetVRRP |
RequireSuperuser(), RequirePlugin(db, "advanced-network") |
POST |
/api/advanced-network/keepalived/vrrp/:id/vips |
addAdvNetVRRPVIP |
RequireSuperuser(), RequirePlugin(db, "advanced-network") |
DELETE |
/api/advanced-network/keepalived/vrrp/:id/vips/:vid |
deleteAdvNetVRRPVIP |
RequireSuperuser(), RequirePlugin(db, "advanced-network") |
GET |
/api/advanced-network/keepalived/vs |
listAdvNetVirtualServers |
RequireSuperuser(), RequirePlugin(db, "advanced-network") |
POST |
/api/advanced-network/keepalived/vs |
createAdvNetVirtualServer |
RequireSuperuser(), RequirePlugin(db, "advanced-network") |
DELETE |
/api/advanced-network/keepalived/vs/:id |
deleteAdvNetVirtualServer |
RequireSuperuser(), RequirePlugin(db, "advanced-network") |
PUT |
/api/advanced-network/keepalived/vs/:id |
updateAdvNetVirtualServer |
RequireSuperuser(), RequirePlugin(db, "advanced-network") |
GET |
/api/advanced-network/keepalived/vs/:id/rs |
listAdvNetRealServers |
RequireSuperuser(), RequirePlugin(db, "advanced-network") |
POST |
/api/advanced-network/keepalived/vs/:id/rs |
addAdvNetRealServer |
RequireSuperuser(), RequirePlugin(db, "advanced-network") |
DELETE |
/api/advanced-network/keepalived/vs/:id/rs/:rid |
deleteAdvNetRealServer |
RequireSuperuser(), RequirePlugin(db, "advanced-network") |
PUT |
/api/advanced-network/keepalived/vs/:id/rs/:rid |
updateAdvNetRealServer |
RequireSuperuser(), RequirePlugin(db, "advanced-network") |
GET |
/api/advanced-network/loopbacks |
listAdvNetLoopbacks |
RequireSuperuser(), RequirePlugin(db, "advanced-network") |
POST |
/api/advanced-network/loopbacks |
addAdvNetLoopback |
RequireSuperuser(), RequirePlugin(db, "advanced-network") |
DELETE |
/api/advanced-network/loopbacks/:id |
deleteAdvNetLoopback |
RequireSuperuser(), RequirePlugin(db, "advanced-network") |
POST |
/api/advanced-network/loopbacks/:id/toggle |
toggleAdvNetLoopback |
RequireSuperuser(), RequirePlugin(db, "advanced-network") |
GET |
/api/advanced-network/prefix-lists |
listAdvNetPrefixLists |
RequireSuperuser(), RequirePlugin(db, "advanced-network") |
POST |
/api/advanced-network/prefix-lists |
createAdvNetPrefixList |
RequireSuperuser(), RequirePlugin(db, "advanced-network") |
DELETE |
/api/advanced-network/prefix-lists/:id |
deleteAdvNetPrefixList |
RequireSuperuser(), RequirePlugin(db, "advanced-network") |
PUT |
/api/advanced-network/prefix-lists/:id |
updateAdvNetPrefixList |
RequireSuperuser(), RequirePlugin(db, "advanced-network") |
GET |
/api/advanced-network/prefix-lists/:id/entries |
listAdvNetPrefixListEntries |
RequireSuperuser(), RequirePlugin(db, "advanced-network") |
POST |
/api/advanced-network/prefix-lists/:id/entries |
addAdvNetPrefixListEntry |
RequireSuperuser(), RequirePlugin(db, "advanced-network") |
DELETE |
/api/advanced-network/prefix-lists/:id/entries/:eid |
deleteAdvNetPrefixListEntry |
RequireSuperuser(), RequirePlugin(db, "advanced-network") |
PUT |
/api/advanced-network/prefix-lists/:id/entries/:eid |
updateAdvNetPrefixListEntry |
RequireSuperuser(), RequirePlugin(db, "advanced-network") |
POST |
/api/advanced-network/prefix-lists/sync-bgp |
syncAdvNetPrefixListsToBGP |
RequireSuperuser(), RequirePlugin(db, "advanced-network") |
GET |
/api/advanced-network/public-subnets |
middleware |
- |
POST |
/api/advanced-network/service/install |
installAdvNetFRR |
RequireSuperuser(), RequirePlugin(db, "advanced-network") |
POST |
/api/advanced-network/service/restart |
advNetFRRAction |
RequireSuperuser(), RequirePlugin(db, "advanced-network") |
POST |
/api/advanced-network/service/start |
advNetFRRAction |
RequireSuperuser(), RequirePlugin(db, "advanced-network") |
GET |
/api/advanced-network/service/status |
getAdvNetFRRServiceStatus |
RequireSuperuser(), RequirePlugin(db, "advanced-network") |
POST |
/api/advanced-network/service/stop |
advNetFRRAction |
RequireSuperuser(), RequirePlugin(db, "advanced-network") |
GET |
/api/advanced-network/static-routes |
listAdvNetStaticRoutes |
RequireSuperuser(), RequirePlugin(db, "advanced-network") |
POST |
/api/advanced-network/static-routes |
addAdvNetStaticRoute |
RequireSuperuser(), RequirePlugin(db, "advanced-network") |
DELETE |
/api/advanced-network/static-routes/:id |
deleteAdvNetStaticRoute |
RequireSuperuser(), RequirePlugin(db, "advanced-network") |
PUT |
/api/advanced-network/static-routes/:id |
updateAdvNetStaticRoute |
RequireSuperuser(), RequirePlugin(db, "advanced-network") |
POST |
/api/advanced-network/static-routes/:id/toggle |
toggleAdvNetStaticRoute |
RequireSuperuser(), RequirePlugin(db, "advanced-network") |
POST |
/api/advanced-network/tunnel-pair |
middleware |
- |
GET |
/api/advanced-network/tunnels |
listAdvNetTunnels |
RequireSuperuser(), RequirePlugin(db, "advanced-network") |
POST |
/api/advanced-network/tunnels |
createAdvNetTunnel |
RequireSuperuser(), RequirePlugin(db, "advanced-network") |
DELETE |
/api/advanced-network/tunnels/:id |
deleteAdvNetTunnel |
RequireSuperuser(), RequirePlugin(db, "advanced-network") |
PUT |
/api/advanced-network/tunnels/:id |
updateAdvNetTunnel |
RequireSuperuser(), RequirePlugin(db, "advanced-network") |
GET |
/api/advanced-network/tunnels/:id/mac-table |
getAdvNetBridgeMACTable |
RequireSuperuser(), RequirePlugin(db, "advanced-network") |
GET |
/api/advanced-network/tunnels/:id/peers |
listAdvNetTunnelPeers |
RequireSuperuser(), RequirePlugin(db, "advanced-network") |
POST |
/api/advanced-network/tunnels/:id/peers |
addAdvNetTunnelPeer |
RequireSuperuser(), RequirePlugin(db, "advanced-network") |
DELETE |
/api/advanced-network/tunnels/:id/peers/:pid |
deleteAdvNetTunnelPeer |
RequireSuperuser(), RequirePlugin(db, "advanced-network") |
PUT |
/api/advanced-network/tunnels/:id/peers/:pid |
updateAdvNetTunnelPeer |
RequireSuperuser(), RequirePlugin(db, "advanced-network") |
GET |
/api/advanced-network/tunnels/:id/peers/:pid/config |
getAdvNetTunnelPeerConfig |
RequireSuperuser(), RequirePlugin(db, "advanced-network") |
GET |
/api/advanced-network/tunnels/:id/peers/:pid/qr |
getAdvNetTunnelPeerQR |
RequireSuperuser(), RequirePlugin(db, "advanced-network") |
POST |
/api/advanced-network/tunnels/:id/retry-pair |
retryTunnelPair |
RequireSuperuser(), RequirePlugin(db, "advanced-network") |
POST |
/api/advanced-network/tunnels/:id/toggle |
toggleAdvNetTunnel |
RequireSuperuser(), RequirePlugin(db, "advanced-network") |
GET |
/api/advanced-network/tunnels/check-wireguard |
checkWireGuardInstalled |
RequireSuperuser(), RequirePlugin(db, "advanced-network") |
POST |
/api/advanced-network/tunnels/install-wireguard |
installWireGuard |
RequireSuperuser(), RequirePlugin(db, "advanced-network") |
GET |
/api/advanced-network/tunnels/remote-subnets/:ip |
getRemoteSubnets |
RequireSuperuser(), RequirePlugin(db, "advanced-network") |
GET |
/api/advanced-network/tunnels/status |
getTunnelStatus |
RequireSuperuser(), RequirePlugin(db, "advanced-network") |
Source¶
Handlers for this section live in api/routes/handlers_*.go in the StarkOne source. Each handler binds its request body with gin.ShouldBindJSON(&req) and responds with the standard envelope:
On error:
Reference¶
POST /api/advanced-network/bgp/auto-detect¶
Probes the cloud metadata API (http://169.254.169.254/v1.json, Vultr format) and returns any BGP settings (ASN, local/peer addresses, router ID) it can extract.
Guards: RequireSuperuser(), RequirePlugin(db, "advanced-network")
Response - 200 OK
{ "data": { "detected": true, "provider": "vultr", "asn": "64500", "local_ip": "203.0.113.10", "peer_asn": "64501", "peer_ip": "203.0.113.1", "router_id": "203.0.113.10", "ipv6_peer_address": "2001:db8::1", "ipv6_local_ip": "2001:db8::10" } }
When the metadata API is unreachable or has no BGP block, the response is still 200 OK with { "data": { "detected": false, "error": "..." } }.
Side effects None (outbound HTTP GET to the metadata endpoint only).
Example
curl -X POST https://panel.example.com:2087/api/advanced-network/bgp/auto-detect \
-H "Authorization: Bearer $TOKEN"
GET /api/advanced-network/bgp/blackholes¶
Lists configured blackhole routes, first deleting any rows whose expires_at is in the past.
Guards: RequireSuperuser(), RequirePlugin(db, "advanced-network")
Response - 200 OK
{ "data": { "blackholes": [ { "id": 1, "ip": "203.0.113.5", "reason": "DDoS source", "expires_at": "2026-06-17 12:00:00", "created_at": "2026-06-17 10:00:00" } ] } }
Errors
| Status | Code | Meaning |
|---|---|---|
500 |
db_error |
Database query failed. |
Side effects
- Deletes expired blackhole rows from
bgp_blackholes.
Example
curl https://panel.example.com:2087/api/advanced-network/bgp/blackholes \
-H "Authorization: Bearer $TOKEN"
POST /api/advanced-network/bgp/blackholes¶
Adds a blackhole for an IP, installs a kernel blackhole route immediately, and regenerates the FRR config.
Guards: RequireSuperuser(), RequirePlugin(db, "advanced-network")
Request body
| Field | Type | Required | Description |
|---|---|---|---|
ip |
string | yes | IP/prefix to blackhole. |
reason |
string | no | Free-text note stored with the entry. |
duration |
string | no | Go duration (e.g. 15m, 1h, 24h); empty means permanent. |
Response - 201 Created
Errors
| Status | Code | Meaning |
|---|---|---|
400 |
bad_request |
Invalid body, missing ip, or invalid duration format. |
500 |
db_error |
Database insert failed. |
Side effects
- Inserts into
bgp_blackholes. - Runs
ip route add blackhole <ip>. - Regenerates FRR config (
generateFRRConfig). - Logs a
blackhole_addevent.
Example
curl -X POST https://panel.example.com:2087/api/advanced-network/bgp/blackholes \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{"ip": "203.0.113.5", "reason": "DDoS source", "duration": "1h"}'
DELETE /api/advanced-network/bgp/blackholes/:id¶
Removes a blackhole entry and deletes the corresponding kernel blackhole route.
Guards: RequireSuperuser(), RequirePlugin(db, "advanced-network")
Path parameters
| Name | Type | Description |
|---|---|---|
id |
integer | Blackhole row id. |
Response - 200 OK
Errors
| Status | Code | Meaning |
|---|---|---|
500 |
db_error |
Database delete failed. |
Side effects
- Runs
ip route del blackhole <ip>. - Deletes the row from
bgp_blackholes. - Regenerates FRR config and logs a
blackhole_removeevent.
Example
curl -X DELETE https://panel.example.com:2087/api/advanced-network/bgp/blackholes/1 \
-H "Authorization: Bearer $TOKEN"
GET /api/advanced-network/bgp/config¶
Returns the singleton BGP configuration row (ASN, router ID, enabled flag, auto-detect provider).
Guards: RequireSuperuser(), RequirePlugin(db, "advanced-network")
Response - 200 OK
{ "data": { "asn": "64500", "router_id": "203.0.113.10", "enabled": true, "auto_detect_provider": "vultr", "created_at": "2026-06-17 10:00:00", "updated_at": "2026-06-17 10:05:00" } }
If no config row exists, returns 200 OK with empty defaults: { "data": { "asn": "", "router_id": "", "enabled": false, "auto_detect_provider": "" } }.
Example
curl https://panel.example.com:2087/api/advanced-network/bgp/config \
-H "Authorization: Bearer $TOKEN"
POST /api/advanced-network/bgp/config¶
Upserts the singleton BGP config row; if enabled is true it also regenerates the FRR config and reloads.
Guards: RequireSuperuser(), RequirePlugin(db, "advanced-network")
Request body
{ "asn": "string (optional)", "router_id": "string (optional)", "enabled": "bool (optional)", "auto_detect_provider": "string (optional)" }
| Field | Type | Required | Description |
|---|---|---|---|
asn |
string | no | Local autonomous system number. |
router_id |
string | no | BGP router ID. |
enabled |
bool | no | Whether BGP is enabled; when true triggers FRR regeneration. |
auto_detect_provider |
string | no | Provider tag (e.g. vultr). |
Response - 200 OK
If enabled is true but the FRR reload fails, the response is still 200 OK with { "data": { "success": true, "warning": "Config saved but FRR reload failed: ..." } }.
Errors
| Status | Code | Meaning |
|---|---|---|
400 |
bad_request |
Invalid request body. |
500 |
db_error |
Database upsert failed. |
Side effects
- Upserts row id 1 in
bgp_config. - When
enabled, regenerates FRR config (writes/etc/frr/frr.confand reloads). - Logs a
config_updateevent.
Example
curl -X POST https://panel.example.com:2087/api/advanced-network/bgp/config \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{"asn": "64500", "router_id": "203.0.113.10", "enabled": true}'
GET /api/advanced-network/bgp/events¶
Returns recent advanced-network event-log entries (config changes, peer/route adds, etc.), newest first.
Guards: RequireSuperuser(), RequirePlugin(db, "advanced-network")
Query parameters
| Name | Type | Optional | Description |
|---|---|---|---|
limit |
integer | yes | Maximum number of events to return; defaults to 100. |
Response - 200 OK
{ "data": { "events": [ { "id": 1, "event_type": "peer_add", "details": "Added peer edge1 (203.0.113.1) AS64501", "created_at": "2026-06-17 10:00:00" } ] } }
Errors
| Status | Code | Meaning |
|---|---|---|
500 |
db_error |
Database query failed. |
Example
curl "https://panel.example.com:2087/api/advanced-network/bgp/events?limit=50" \
-H "Authorization: Bearer $TOKEN"
GET /api/advanced-network/bgp/flowspec¶
Lists all configured BGP FlowSpec rules.
Guards: RequireSuperuser(), RequirePlugin(db, "advanced-network")
Response - 200 OK
{ "data": { "rules": [ { "id": 1, "name": "Block UDP flood", "source": "", "destination": "203.0.113.0/24", "dest_port": "", "source_port": "123", "protocol": "udp", "action": "drop", "rate_limit": "", "enabled": true, "expires_at": "", "created_at": "2026-06-17 10:00:00" } ] } }
Errors
| Status | Code | Meaning |
|---|---|---|
500 |
db_error |
Database query failed. |
Example
curl https://panel.example.com:2087/api/advanced-network/bgp/flowspec \
-H "Authorization: Bearer $TOKEN"
POST /api/advanced-network/bgp/flowspec¶
Creates a FlowSpec rule and regenerates the FRR config.
Guards: RequireSuperuser(), RequirePlugin(db, "advanced-network")
Request body
{ "name": "string (required)", "source": "string (optional)", "destination": "string (optional)", "dest_port": "string (optional)", "source_port": "string (optional)", "protocol": "string (optional)", "action": "string (optional)", "rate_limit": "string (optional)", "expires_at": "string (optional)" }
| Field | Type | Required | Description |
|---|---|---|---|
name |
string | yes | Rule label. |
source |
string | no | Source prefix match. |
destination |
string | no | Destination prefix match. |
dest_port |
string | no | Destination port match. |
source_port |
string | no | Source port match. |
protocol |
string | no | Protocol match (e.g. udp, tcp, icmp). |
action |
string | no | Action; defaults to drop when empty. |
rate_limit |
string | no | Rate limit value (for rate-limit action). |
expires_at |
string | no | Expiry timestamp; stored NULL when empty. |
Response - 201 Created
Errors
| Status | Code | Meaning |
|---|---|---|
400 |
bad_request |
Invalid body or missing name. |
500 |
db_error |
Database insert failed. |
Side effects
- Inserts into
bgp_flowspec_rules. - Regenerates FRR config and logs a
flowspec_addevent.
Example
curl -X POST https://panel.example.com:2087/api/advanced-network/bgp/flowspec \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{"name": "Block NTP", "protocol": "udp", "source_port": "123", "action": "drop"}'
DELETE /api/advanced-network/bgp/flowspec/:id¶
Deletes a FlowSpec rule and regenerates the FRR config.
Guards: RequireSuperuser(), RequirePlugin(db, "advanced-network")
Path parameters
| Name | Type | Description |
|---|---|---|
id |
integer | FlowSpec rule id. |
Response - 200 OK
Errors
| Status | Code | Meaning |
|---|---|---|
500 |
db_error |
Database delete failed. |
Side effects
- Deletes from
bgp_flowspec_rules, regenerates FRR config, logs aflowspec_removeevent.
Example
curl -X DELETE https://panel.example.com:2087/api/advanced-network/bgp/flowspec/1 \
-H "Authorization: Bearer $TOKEN"
PUT /api/advanced-network/bgp/flowspec/:id¶
Updates an existing FlowSpec rule and regenerates the FRR config.
Guards: RequireSuperuser(), RequirePlugin(db, "advanced-network")
Path parameters
| Name | Type | Description |
|---|---|---|
id |
integer | FlowSpec rule id. |
Request body
{ "name": "string", "source": "string", "destination": "string", "dest_port": "string", "source_port": "string", "protocol": "string", "action": "string", "rate_limit": "string", "enabled": "bool (optional)", "expires_at": "string" }
| Field | Type | Required | Description |
|---|---|---|---|
name |
string | no | Rule label. |
source |
string | no | Source prefix match. |
destination |
string | no | Destination prefix match. |
dest_port |
string | no | Destination port match. |
source_port |
string | no | Source port match. |
protocol |
string | no | Protocol match. |
action |
string | no | Action. |
rate_limit |
string | no | Rate limit value. |
enabled |
bool | no | Enable flag; defaults to enabled unless explicitly false. |
expires_at |
string | no | Expiry timestamp; stored NULL when empty. |
Response - 200 OK
Errors
| Status | Code | Meaning |
|---|---|---|
400 |
bad_request |
Invalid request body. |
500 |
db_error |
Database update failed. |
Side effects
- Updates
bgp_flowspec_rules, regenerates FRR config, logs aflowspec_updateevent.
Example
curl -X PUT https://panel.example.com:2087/api/advanced-network/bgp/flowspec/1 \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{"name": "Block NTP", "enabled": false}'
GET /api/advanced-network/bgp/flowspec/supported¶
Reports whether any enabled BGP peer advertises FlowSpec capability, by inspecting vtysh show bgp neighbor output.
Guards: RequireSuperuser(), RequirePlugin(db, "advanced-network")
Response - 200 OK
Side effects None (runs vtysh read commands only).
Example
curl https://panel.example.com:2087/api/advanced-network/bgp/flowspec/supported \
-H "Authorization: Bearer $TOKEN"
GET /api/advanced-network/bgp/flowspec/templates¶
Returns a static list of preset FlowSpec rule templates the UI can offer.
Guards: RequireSuperuser(), RequirePlugin(db, "advanced-network")
Response - 200 OK
{ "data": { "templates": [ { "name": "Block UDP flood", "protocol": "udp", "action": "drop", "source": "", "destination": "", "dest_port": "", "source_port": "" } ] } }
Example
curl https://panel.example.com:2087/api/advanced-network/bgp/flowspec/templates \
-H "Authorization: Bearer $TOKEN"
GET /api/advanced-network/bgp/networks¶
Lists prefixes that BGP announces (the network statements).
Guards: RequireSuperuser(), RequirePlugin(db, "advanced-network")
Response - 200 OK
{ "data": { "networks": [ { "id": 1, "prefix": "203.0.113.0/24", "description": "Customer block", "active": true, "created_at": "2026-06-17 10:00:00" } ] } }
Errors
| Status | Code | Meaning |
|---|---|---|
500 |
db_error |
Database query failed. |
Example
curl https://panel.example.com:2087/api/advanced-network/bgp/networks \
-H "Authorization: Bearer $TOKEN"
POST /api/advanced-network/bgp/networks¶
Adds a prefix to advertise via BGP and regenerates the FRR config.
Guards: RequireSuperuser(), RequirePlugin(db, "advanced-network")
Request body
| Field | Type | Required | Description |
|---|---|---|---|
prefix |
string | yes | CIDR prefix to announce. |
description |
string | no | Free-text note. |
Response - 201 Created
Errors
| Status | Code | Meaning |
|---|---|---|
400 |
bad_request |
Invalid body or missing prefix. |
500 |
db_error |
Database insert failed. |
Side effects
- Inserts into
bgp_networks, regenerates FRR config, logs aroute_addevent.
Example
curl -X POST https://panel.example.com:2087/api/advanced-network/bgp/networks \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{"prefix": "203.0.113.0/24", "description": "Customer block"}'
DELETE /api/advanced-network/bgp/networks/:id¶
Removes an advertised prefix and regenerates the FRR config.
Guards: RequireSuperuser(), RequirePlugin(db, "advanced-network")
Path parameters
| Name | Type | Description |
|---|---|---|
id |
integer | Network row id. |
Response - 200 OK
Errors
| Status | Code | Meaning |
|---|---|---|
500 |
db_error |
Database delete failed. |
Side effects
- Deletes from
bgp_networks, regenerates FRR config, logs aroute_removeevent.
Example
curl -X DELETE https://panel.example.com:2087/api/advanced-network/bgp/networks/1 \
-H "Authorization: Bearer $TOKEN"
GET /api/advanced-network/bgp/peers¶
Lists configured BGP peers, refreshing each peer's live session state from vtysh show bgp neighbor.
Guards: RequireSuperuser(), RequirePlugin(db, "advanced-network")
Response - 200 OK
{ "data": { "peers": [ { "id": 1, "name": "edge1", "peer_ip": "203.0.113.1", "peer_asn": "64501", "password": "", "ebgp_multihop": 2, "update_source": "", "ipv6_peer_address": "", "enabled": true, "status": "established", "last_status_change": "2026-06-17 10:00:00", "created_at": "2026-06-17 09:00:00", "filter_in_list_id": 0, "filter_out_list_id": 0 } ] } }
Errors
| Status | Code | Meaning |
|---|---|---|
500 |
db_error |
Database query failed. |
Side effects
- May update
status/last_status_changeinbgp_peerswhen live state differs from the stored value.
Example
curl https://panel.example.com:2087/api/advanced-network/bgp/peers \
-H "Authorization: Bearer $TOKEN"
POST /api/advanced-network/bgp/peers¶
Adds a BGP peer and regenerates the FRR config.
Guards: RequireSuperuser(), RequirePlugin(db, "advanced-network")
Request body
{ "name": "string (optional)", "peer_ip": "string (required)", "peer_asn": "string (required)", "password": "string (optional)", "ebgp_multihop": "int (optional)", "update_source": "string (optional)", "ipv6_peer_address": "string (optional)", "filter_in_list_id": "int (optional)", "filter_out_list_id": "int (optional)" }
| Field | Type | Required | Description |
|---|---|---|---|
name |
string | no | Peer label. |
peer_ip |
string | yes | Neighbor IPv4 address. |
peer_asn |
string | yes | Neighbor ASN. |
password |
string | no | MD5 session password. |
ebgp_multihop |
int | no | Multihop TTL; defaults to 2 when 0. |
update_source |
string | no | Source interface/address for the session. |
ipv6_peer_address |
string | no | Optional IPv6 neighbor address. |
filter_in_list_id |
int | no | Inbound prefix-list id. |
filter_out_list_id |
int | no | Outbound prefix-list id. |
Response - 201 Created
Errors
| Status | Code | Meaning |
|---|---|---|
400 |
bad_request |
Invalid body or missing peer_ip/peer_asn. |
500 |
db_error |
Database insert failed. |
Side effects
- Inserts into
bgp_peers, regenerates FRR config, logs apeer_addevent.
Example
curl -X POST https://panel.example.com:2087/api/advanced-network/bgp/peers \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{"name": "edge1", "peer_ip": "203.0.113.1", "peer_asn": "64501"}'
DELETE /api/advanced-network/bgp/peers/:id¶
Deletes a BGP peer and regenerates the FRR config.
Guards: RequireSuperuser(), RequirePlugin(db, "advanced-network")
Path parameters
| Name | Type | Description |
|---|---|---|
id |
integer | Peer row id. |
Response - 200 OK
Errors
| Status | Code | Meaning |
|---|---|---|
500 |
db_error |
Database delete failed. |
Side effects
- Deletes from
bgp_peers, regenerates FRR config, logs apeer_removeevent.
Example
curl -X DELETE https://panel.example.com:2087/api/advanced-network/bgp/peers/1 \
-H "Authorization: Bearer $TOKEN"
PUT /api/advanced-network/bgp/peers/:id¶
Updates a BGP peer and regenerates the FRR config.
Guards: RequireSuperuser(), RequirePlugin(db, "advanced-network")
Path parameters
| Name | Type | Description |
|---|---|---|
id |
integer | Peer row id. |
Request body
{ "name": "string", "peer_ip": "string", "peer_asn": "string", "password": "string", "ebgp_multihop": "int", "update_source": "string", "ipv6_peer_address": "string", "enabled": "bool (optional)", "filter_in_list_id": "int", "filter_out_list_id": "int" }
| Field | Type | Required | Description |
|---|---|---|---|
name |
string | no | Peer label. |
peer_ip |
string | no | Neighbor IPv4 address. |
peer_asn |
string | no | Neighbor ASN. |
password |
string | no | MD5 session password. |
ebgp_multihop |
int | no | Multihop TTL. |
update_source |
string | no | Source interface/address. |
ipv6_peer_address |
string | no | IPv6 neighbor address. |
enabled |
bool | no | Enable flag; defaults to enabled unless explicitly false. |
filter_in_list_id |
int | no | Inbound prefix-list id. |
filter_out_list_id |
int | no | Outbound prefix-list id. |
Response - 200 OK
Errors
| Status | Code | Meaning |
|---|---|---|
400 |
bad_request |
Invalid request body. |
500 |
db_error |
Database update failed. |
Side effects
- Updates
bgp_peers, regenerates FRR config, logs apeer_updateevent.
Example
curl -X PUT https://panel.example.com:2087/api/advanced-network/bgp/peers/1 \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{"name": "edge1", "peer_ip": "203.0.113.1", "peer_asn": "64501", "enabled": true}'
GET /api/advanced-network/bgp/routes¶
Returns the parsed and raw output of vtysh show ip bgp (the BGP RIB).
Guards: RequireSuperuser(), RequirePlugin(db, "advanced-network")
Response - 200 OK
{ "data": { "raw": "BGP table version ...", "routes": [ { "raw": "*> 203.0.113.0/24 0.0.0.0", "active": true, "network": "203.0.113.0/24", "next_hop": "0.0.0.0" } ] } }
If vtysh fails, returns 200 OK with empty raw and routes.
Side effects None (runs vtysh read command only).
Example
curl https://panel.example.com:2087/api/advanced-network/bgp/routes \
-H "Authorization: Bearer $TOKEN"
GET /api/advanced-network/bgp/routes-detail¶
Returns advertised routes plus per-peer received/advertised route counts, gathered from several vtysh commands.
Guards: RequireSuperuser(), RequirePlugin(db, "advanced-network")
Response - 200 OK
{ "data": { "routes": [ { "network": "203.0.113.0/24", "next_hop": "0.0.0.0", "origin": "local", "raw": "*> 203.0.113.0/24 0.0.0.0" } ], "peers": [ { "peer": "203.0.113.1", "received": [ { "network": "198.51.100.0/24", "next_hop": "203.0.113.1" } ], "received_count": 1, "advertised_count": 3 } ] } }
Side effects None (runs vtysh read commands only).
Example
curl https://panel.example.com:2087/api/advanced-network/bgp/routes-detail \
-H "Authorization: Bearer $TOKEN"
GET /api/advanced-network/bgp/status¶
Returns an overall BGP status snapshot: FRR install/run state, version, and counts of peers, networks, blackholes, and FlowSpec rules.
Guards: RequireSuperuser(), RequirePlugin(db, "advanced-network")
Response - 200 OK
{ "data": { "installed": true, "running": true, "version": "FRRouting 8.4 ...", "bgp_enabled": true, "total_peers": 2, "established_peers": 1, "total_networks": 3, "total_blackholes": 0, "total_flowspec": 1 } }
Side effects None (reads filesystem, systemctl, vtysh, and DB counts).
Example
curl https://panel.example.com:2087/api/advanced-network/bgp/status \
-H "Authorization: Bearer $TOKEN"
GET /api/advanced-network/bgp/summary¶
Returns the parsed and raw output of vtysh show bgp summary.
Guards: RequireSuperuser(), RequirePlugin(db, "advanced-network")
Response - 200 OK
{ "data": { "raw": "IPv4 Unicast Summary ...", "peers": [ { "neighbor": "203.0.113.1", "version": "4", "as": "64501", "msg_rcvd": "120", "msg_sent": "118", "up_down": "01:02:03", "state_pfx": "3" } ] } }
If vtysh fails, returns 200 OK with empty raw and peers.
Side effects None (runs vtysh read command only).
Example
curl https://panel.example.com:2087/api/advanced-network/bgp/summary \
-H "Authorization: Bearer $TOKEN"
GET /api/advanced-network/export¶
Exports the entire advanced-network configuration (BGP config/peers/networks, prefix lists, static routes, loopbacks, FlowSpec, blackholes, tunnels and peers, Keepalived VRRP and virtual servers) as a single JSON object.
Guards: RequireSuperuser(), RequirePlugin(db, "advanced-network")
Response - 200 OK
{ "data": { "bgp_config": { "asn": "64500", "router_id": "203.0.113.10", "enabled": 1, "auto_detect_provider": "" }, "bgp_peers": [], "bgp_networks": [], "prefix_lists": [], "static_routes": [], "loopback_ips": [], "flowspec_rules": [], "blackholes": [], "tunnels": [], "keepalived_vrrp": [], "keepalived_virtual_servers": [], "exported_at": "2026-06-17T10:00:00Z", "version": "2" } }
Side effects None (read-only DB queries).
Example
POST /api/advanced-network/import¶
Imports a previously exported config: deletes existing prefix lists/entries, BGP networks/peers, static routes and loopbacks, then re-inserts from the supplied JSON and regenerates the FRR config.
Guards: RequireSuperuser(), RequirePlugin(db, "advanced-network")
Request body
The full export object (see GET /api/advanced-network/export). Recognized top-level keys: bgp_config, bgp_peers, bgp_networks, prefix_lists (each with nested entries), static_routes, loopback_ips. Other keys present in an export (tunnels, keepalived, flowspec, blackholes) are not re-imported by this handler.
{ "bgp_config": { "asn": "64500", "router_id": "203.0.113.10", "enabled": 1 }, "bgp_peers": [], "bgp_networks": [], "prefix_lists": [], "static_routes": [], "loopback_ips": [] }
Response - 200 OK
Errors
| Status | Code | Meaning |
|---|---|---|
400 |
bad_request |
Body is not valid JSON. |
Side effects
- Deletes all rows from
prefix_list_entries,prefix_lists,bgp_networks,bgp_peers,static_routes,loopback_ips. - Re-inserts imported records, updates
bgp_configrow 1. - Regenerates FRR config and logs a
config_importevent.
Example
curl -X POST https://panel.example.com:2087/api/advanced-network/import \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d @advnet-export.json
GET /api/advanced-network/interface-ips¶
Lists IPv4 addresses bound to each network interface (excluding 127.0.0.1), parsed from ip -o -4 addr show.
Guards: RequireSuperuser(), RequirePlugin(db, "advanced-network")
Response - 200 OK
This handler returns a flat array under data (not an object).
Side effects None (runs ip read command only).
Example
curl https://panel.example.com:2087/api/advanced-network/interface-ips \
-H "Authorization: Bearer $TOKEN"
GET /api/advanced-network/interfaces¶
Lists network interface names parsed from ip -o link show.
Guards: RequireSuperuser(), RequirePlugin(db, "advanced-network")
Response - 200 OK
This handler returns a flat array of strings under data. Falls back to ["lo"] if the command fails.
Side effects None (runs ip read command only).
Example
curl https://panel.example.com:2087/api/advanced-network/interfaces \
-H "Authorization: Bearer $TOKEN"
POST /api/advanced-network/keepalived/check-l2¶
Runs arping from a chosen interface to a target and reports whether layer-2 reachability exists (needed for LVS DR mode).
Guards: RequireSuperuser(), RequirePlugin(db, "advanced-network")
Request body
| Field | Type | Required | Description |
|---|---|---|---|
target |
string | yes | IP to arping. |
interface |
string | yes | Interface to send the arping from. |
Response - 200 OK
{ "data": { "l2": true, "message": "L2 connectivity to 203.0.113.20 via eth1 confirmed.", "raw": "ARPING ..." } }
Errors
| Status | Code | Meaning |
|---|---|---|
400 |
bad_request |
Invalid body or missing target/interface. |
Side effects None persistent (runs arping).
Example
curl -X POST https://panel.example.com:2087/api/advanced-network/keepalived/check-l2 \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{"target": "203.0.113.20", "interface": "eth1"}'
POST /api/advanced-network/keepalived/install¶
Installs keepalived and ipvsadm via the detected package manager, then enables and starts the service.
Guards: RequireSuperuser(), RequirePlugin(db, "advanced-network")
Response - 200 OK
Errors
| Status | Code | Meaning |
|---|---|---|
500 |
(no code; message only) |
No supported package manager, or install failed (message contains command output). |
Side effects
- Runs
apt-get/dnf/yum install -y keepalived ipvsadm. - Runs
systemctl enable --now keepalived. - Logs a
keepalived_installevent.
Example
curl -X POST https://panel.example.com:2087/api/advanced-network/keepalived/install \
-H "Authorization: Bearer $TOKEN"
POST /api/advanced-network/keepalived/restart¶
Restarts the keepalived systemd service.
Guards: RequireSuperuser(), RequirePlugin(db, "advanced-network")
Response - 200 OK
Errors
| Status | Code | Meaning |
|---|---|---|
500 |
(no code; message only) |
systemctl restart keepalived failed. |
Side effects
- Runs
systemctl restart keepalived.
Example
curl -X POST https://panel.example.com:2087/api/advanced-network/keepalived/restart \
-H "Authorization: Bearer $TOKEN"
GET /api/advanced-network/keepalived/status¶
Reports whether keepalived is installed/running plus raw ipvsadm -Ln and ip -brief addr output.
Guards: RequireSuperuser(), RequirePlugin(db, "advanced-network")
Response - 200 OK
{ "data": { "installed": true, "running": true, "ipvsadm": "IP Virtual Server ...", "vrrp_state": "eth0 UP 203.0.113.10/24 ..." } }
Side effects None (reads systemctl, ipvsadm, ip).
Example
curl https://panel.example.com:2087/api/advanced-network/keepalived/status \
-H "Authorization: Bearer $TOKEN"
GET /api/advanced-network/keepalived/vrrp¶
Lists VRRP instances with their virtual IPs.
Guards: RequireSuperuser(), RequirePlugin(db, "advanced-network")
Response - 200 OK
{ "data": { "vrrp_instances": [ { "id": 1, "name": "vrrp1", "state": "MASTER", "interface": "eth0", "virtual_router_id": 51, "priority": 100, "advert_int": 1, "auth_type": "PASS", "auth_pass": "secret", "enabled": true, "vips": [ { "id": 1, "ip_address": "203.0.113.100", "interface": "eth0" } ], "created_at": "2026-06-17 10:00:00" } ] } }
Errors
| Status | Code | Meaning |
|---|---|---|
500 |
db_error |
Database query failed. |
Example
curl https://panel.example.com:2087/api/advanced-network/keepalived/vrrp \
-H "Authorization: Bearer $TOKEN"
POST /api/advanced-network/keepalived/vrrp¶
Creates a VRRP instance and regenerates the keepalived config.
Guards: RequireSuperuser(), RequirePlugin(db, "advanced-network")
Request body
{ "name": "string (required)", "state": "string (optional)", "interface": "string (required)", "virtual_router_id": "int (required)", "priority": "int (optional)", "advert_int": "int (optional)", "auth_type": "string (optional)", "auth_pass": "string (optional)" }
| Field | Type | Required | Description |
|---|---|---|---|
name |
string | yes | Instance name. |
state |
string | no | MASTER/BACKUP; defaults to BACKUP. |
interface |
string | yes | Interface the VRRP runs on. |
virtual_router_id |
int | yes | VRID (must be non-zero). |
priority |
int | no | Election priority; defaults to 100. |
advert_int |
int | no | Advertisement interval; defaults to 1. |
auth_type |
string | no | Defaults to PASS. |
auth_pass |
string | no | Authentication password. |
Response - 201 Created
Errors
| Status | Code | Meaning |
|---|---|---|
400 |
bad_request |
Invalid body, or missing name/interface/virtual_router_id. |
500 |
db_error |
Database insert failed. |
Side effects
- Inserts into
keepalived_vrrp, regenerates keepalived config, logs avrrp_addevent.
Example
curl -X POST https://panel.example.com:2087/api/advanced-network/keepalived/vrrp \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{"name": "vrrp1", "interface": "eth0", "virtual_router_id": 51, "state": "MASTER"}'
DELETE /api/advanced-network/keepalived/vrrp/:id¶
Deletes a VRRP instance and regenerates the keepalived config.
Guards: RequireSuperuser(), RequirePlugin(db, "advanced-network")
Path parameters
| Name | Type | Description |
|---|---|---|
id |
integer | VRRP instance id. |
Response - 200 OK
Errors
| Status | Code | Meaning |
|---|---|---|
500 |
db_error |
Database delete failed. |
Side effects
- Deletes from
keepalived_vrrp, regenerates keepalived config, logs avrrp_removeevent.
Example
curl -X DELETE https://panel.example.com:2087/api/advanced-network/keepalived/vrrp/1 \
-H "Authorization: Bearer $TOKEN"
PUT /api/advanced-network/keepalived/vrrp/:id¶
Updates a VRRP instance and regenerates the keepalived config.
Guards: RequireSuperuser(), RequirePlugin(db, "advanced-network")
Path parameters
| Name | Type | Description |
|---|---|---|
id |
integer | VRRP instance id. |
Request body
{ "name": "string", "state": "string", "interface": "string", "virtual_router_id": "int", "priority": "int", "advert_int": "int", "auth_type": "string", "auth_pass": "string", "enabled": "bool (optional)" }
| Field | Type | Required | Description |
|---|---|---|---|
name |
string | no | Instance name. |
state |
string | no | MASTER/BACKUP. |
interface |
string | no | Interface. |
virtual_router_id |
int | no | VRID. |
priority |
int | no | Election priority. |
advert_int |
int | no | Advertisement interval. |
auth_type |
string | no | Auth type. |
auth_pass |
string | no | Auth password. |
enabled |
bool | no | Enable flag; defaults to enabled unless explicitly false. |
Response - 200 OK
Errors
| Status | Code | Meaning |
|---|---|---|
400 |
bad_request |
Invalid request body. |
500 |
db_error |
Database update failed. |
Side effects
- Updates
keepalived_vrrp, regenerates keepalived config, logs avrrp_updateevent.
Example
curl -X PUT https://panel.example.com:2087/api/advanced-network/keepalived/vrrp/1 \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{"name": "vrrp1", "priority": 150, "enabled": true}'
POST /api/advanced-network/keepalived/vrrp/:id/vips¶
Adds a virtual IP to a VRRP instance and regenerates the keepalived config.
Guards: RequireSuperuser(), RequirePlugin(db, "advanced-network")
Path parameters
| Name | Type | Description |
|---|---|---|
id |
integer | Parent VRRP instance id. |
Request body
| Field | Type | Required | Description |
|---|---|---|---|
ip_address |
string | yes | Virtual IP to add. |
interface |
string | no | Interface the VIP binds to. |
Response - 201 Created
Errors
| Status | Code | Meaning |
|---|---|---|
400 |
bad_request |
Invalid body or missing ip_address. |
500 |
db_error |
Database insert failed. |
Side effects
- Inserts into
keepalived_vrrp_vips, regenerates keepalived config.
Example
curl -X POST https://panel.example.com:2087/api/advanced-network/keepalived/vrrp/1/vips \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{"ip_address": "203.0.113.100", "interface": "eth0"}'
DELETE /api/advanced-network/keepalived/vrrp/:id/vips/:vid¶
Removes a virtual IP from a VRRP instance and regenerates the keepalived config.
Guards: RequireSuperuser(), RequirePlugin(db, "advanced-network")
Path parameters
| Name | Type | Description |
|---|---|---|
id |
integer | Parent VRRP instance id. |
vid |
integer | VIP row id. |
Response - 200 OK
Errors
| Status | Code | Meaning |
|---|---|---|
500 |
db_error |
Database delete failed. |
Side effects
- Deletes from
keepalived_vrrp_vips, regenerates keepalived config.
Example
curl -X DELETE https://panel.example.com:2087/api/advanced-network/keepalived/vrrp/1/vips/1 \
-H "Authorization: Bearer $TOKEN"
GET /api/advanced-network/keepalived/vs¶
Lists LVS virtual servers, each with a count of associated real servers.
Guards: RequireSuperuser(), RequirePlugin(db, "advanced-network")
Response - 200 OK
{ "data": { "virtual_servers": [ { "id": 1, "vip": "203.0.113.100", "port": 80, "protocol": "TCP", "lb_method": "NAT", "scheduler": "wlc", "delay_loop": 5, "enabled": true, "real_server_count": 2, "created_at": "2026-06-17 10:00:00" } ] } }
Errors
| Status | Code | Meaning |
|---|---|---|
500 |
db_error |
Database query failed. |
Example
curl https://panel.example.com:2087/api/advanced-network/keepalived/vs \
-H "Authorization: Bearer $TOKEN"
POST /api/advanced-network/keepalived/vs¶
Creates an LVS virtual server and regenerates the keepalived config.
Guards: RequireSuperuser(), RequirePlugin(db, "advanced-network")
Request body
{ "vip": "string (required)", "port": "int (required)", "protocol": "string (optional)", "lb_method": "string (optional)", "scheduler": "string (optional)", "delay_loop": "int (optional)" }
| Field | Type | Required | Description |
|---|---|---|---|
vip |
string | yes | Virtual IP. |
port |
int | yes | Virtual service port (must be non-zero). |
protocol |
string | no | Defaults to TCP. |
lb_method |
string | no | Forwarding method; defaults to NAT. |
scheduler |
string | no | IPVS scheduler; defaults to wlc. |
delay_loop |
int | no | Health-check loop interval; defaults to 5. |
Response - 201 Created
Errors
| Status | Code | Meaning |
|---|---|---|
400 |
bad_request |
Invalid body, or missing vip/port. |
500 |
db_error |
Database insert failed. |
Side effects
- Inserts into
keepalived_virtual_servers, regenerates keepalived config, logs avs_addevent.
Example
curl -X POST https://panel.example.com:2087/api/advanced-network/keepalived/vs \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{"vip": "203.0.113.100", "port": 80, "protocol": "TCP"}'
DELETE /api/advanced-network/keepalived/vs/:id¶
Deletes an LVS virtual server and regenerates the keepalived config.
Guards: RequireSuperuser(), RequirePlugin(db, "advanced-network")
Path parameters
| Name | Type | Description |
|---|---|---|
id |
integer | Virtual server id. |
Response - 200 OK
Errors
| Status | Code | Meaning |
|---|---|---|
500 |
db_error |
Database delete failed. |
Side effects
- Deletes from
keepalived_virtual_servers, regenerates keepalived config, logs avs_removeevent.
Example
curl -X DELETE https://panel.example.com:2087/api/advanced-network/keepalived/vs/1 \
-H "Authorization: Bearer $TOKEN"
PUT /api/advanced-network/keepalived/vs/:id¶
Updates an LVS virtual server and regenerates the keepalived config.
Guards: RequireSuperuser(), RequirePlugin(db, "advanced-network")
Path parameters
| Name | Type | Description |
|---|---|---|
id |
integer | Virtual server id. |
Request body
{ "vip": "string", "port": "int", "protocol": "string", "lb_method": "string", "scheduler": "string", "delay_loop": "int", "enabled": "bool (optional)" }
| Field | Type | Required | Description |
|---|---|---|---|
vip |
string | no | Virtual IP. |
port |
int | no | Virtual service port. |
protocol |
string | no | Protocol. |
lb_method |
string | no | Forwarding method. |
scheduler |
string | no | IPVS scheduler. |
delay_loop |
int | no | Health-check loop interval. |
enabled |
bool | no | Enable flag; defaults to enabled unless explicitly false. |
Response - 200 OK
Errors
| Status | Code | Meaning |
|---|---|---|
400 |
bad_request |
Invalid request body. |
500 |
db_error |
Database update failed. |
Side effects
- Updates
keepalived_virtual_servers, regenerates keepalived config, logs avs_updateevent.
Example
curl -X PUT https://panel.example.com:2087/api/advanced-network/keepalived/vs/1 \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{"vip": "203.0.113.100", "port": 80, "enabled": true}'
GET /api/advanced-network/keepalived/vs/:id/rs¶
Lists the real servers attached to a virtual server.
Guards: RequireSuperuser(), RequirePlugin(db, "advanced-network")
Path parameters
| Name | Type | Description |
|---|---|---|
id |
integer | Parent virtual server id. |
Response - 200 OK
{ "data": { "real_servers": [ { "id": 1, "virtual_server_id": 1, "ip_address": "203.0.113.21", "port": 80, "weight": 1, "check_type": "TCP", "check_url": "", "check_script": "", "check_timeout": 3, "check_interval": 5, "enabled": true, "status": "up" } ] } }
Errors
| Status | Code | Meaning |
|---|---|---|
500 |
db_error |
Database query failed. |
Example
curl https://panel.example.com:2087/api/advanced-network/keepalived/vs/1/rs \
-H "Authorization: Bearer $TOKEN"
POST /api/advanced-network/keepalived/vs/:id/rs¶
Adds a real server to a virtual server and regenerates the keepalived config.
Guards: RequireSuperuser(), RequirePlugin(db, "advanced-network")
Path parameters
| Name | Type | Description |
|---|---|---|
id |
integer | Parent virtual server id. |
Request body
{ "ip_address": "string (required)", "port": "int (required)", "weight": "int (optional)", "check_type": "string (optional)", "check_url": "string (optional)", "check_script": "string (optional)", "check_timeout": "int (optional)", "check_interval": "int (optional)" }
| Field | Type | Required | Description |
|---|---|---|---|
ip_address |
string | yes | Backend server IP. |
port |
int | yes | Backend port (must be non-zero). |
weight |
int | no | Scheduling weight; defaults to 1. |
check_type |
string | no | Health-check type; defaults to TCP. |
check_url |
string | no | URL for HTTP checks. |
check_script |
string | no | Custom check script. |
check_timeout |
int | no | Check timeout; defaults to 3. |
check_interval |
int | no | Check interval; defaults to 5. |
Response - 201 Created
Errors
| Status | Code | Meaning |
|---|---|---|
400 |
bad_request |
Invalid body, or missing ip_address/port. |
500 |
db_error |
Database insert failed. |
Side effects
- Inserts into
keepalived_real_servers, regenerates keepalived config, logs anrs_addevent.
Example
curl -X POST https://panel.example.com:2087/api/advanced-network/keepalived/vs/1/rs \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{"ip_address": "203.0.113.21", "port": 80}'
DELETE /api/advanced-network/keepalived/vs/:id/rs/:rid¶
Removes a real server from a virtual server and regenerates the keepalived config.
Guards: RequireSuperuser(), RequirePlugin(db, "advanced-network")
Path parameters
| Name | Type | Description |
|---|---|---|
id |
integer | Parent virtual server id. |
rid |
integer | Real server id. |
Response - 200 OK
Errors
| Status | Code | Meaning |
|---|---|---|
500 |
db_error |
Database delete failed. |
Side effects
- Deletes from
keepalived_real_servers, regenerates keepalived config, logs anrs_removeevent.
Example
curl -X DELETE https://panel.example.com:2087/api/advanced-network/keepalived/vs/1/rs/1 \
-H "Authorization: Bearer $TOKEN"
PUT /api/advanced-network/keepalived/vs/:id/rs/:rid¶
Updates a real server and regenerates the keepalived config.
Guards: RequireSuperuser(), RequirePlugin(db, "advanced-network")
Path parameters
| Name | Type | Description |
|---|---|---|
id |
integer | Parent virtual server id. |
rid |
integer | Real server id. |
Request body
{ "ip_address": "string", "port": "int", "weight": "int", "check_type": "string", "check_url": "string", "check_script": "string", "check_timeout": "int", "check_interval": "int", "enabled": "bool (optional)" }
| Field | Type | Required | Description |
|---|---|---|---|
ip_address |
string | no | Backend server IP. |
port |
int | no | Backend port. |
weight |
int | no | Scheduling weight. |
check_type |
string | no | Health-check type. |
check_url |
string | no | URL for HTTP checks. |
check_script |
string | no | Custom check script. |
check_timeout |
int | no | Check timeout. |
check_interval |
int | no | Check interval. |
enabled |
bool | no | Enable flag; defaults to enabled unless explicitly false. |
Response - 200 OK
Errors
| Status | Code | Meaning |
|---|---|---|
400 |
bad_request |
Invalid request body. |
500 |
db_error |
Database update failed. |
Side effects
- Updates
keepalived_real_servers, regenerates keepalived config.
Example
curl -X PUT https://panel.example.com:2087/api/advanced-network/keepalived/vs/1/rs/1 \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{"ip_address": "203.0.113.21", "port": 80, "weight": 2, "enabled": true}'
GET /api/advanced-network/loopbacks¶
Lists managed loopback/self IPs (from the DB) merged with IPs actually present on lo, flagging which are live and which are unmanaged.
Guards: RequireSuperuser(), RequirePlugin(db, "advanced-network")
Response - 200 OK
This handler returns a flat array under data.
{ "data": [ { "id": 1, "ip": "203.0.113.50", "description": "Self IP", "iface": "lo", "active": true, "persistent": false, "managed": true, "created_at": "2026-06-17 10:00:00", "live": true } ] }
Unmanaged live IPs appear with id: 0, managed: false, and description: "(not managed)".
Side effects None (reads DB and ip addr show dev lo).
Example
curl https://panel.example.com:2087/api/advanced-network/loopbacks \
-H "Authorization: Bearer $TOKEN"
POST /api/advanced-network/loopbacks¶
Adds a loopback/self IP to the DB and binds it to the chosen interface (default lo); relaxes rp_filter when binding to a non-loopback interface.
Guards: RequireSuperuser(), RequirePlugin(db, "advanced-network")
Request body
| Field | Type | Required | Description |
|---|---|---|---|
ip |
string | yes | IP, optionally with a CIDR mask; defaults to /32 (IPv4) or /128 (IPv6). |
description |
string | no | Free-text note. |
iface |
string | no | Interface to bind to; defaults to lo. |
Response - 201 Created
Errors
| Status | Code | Meaning |
|---|---|---|
400 |
bad_request |
Invalid body or missing ip. |
500 |
db_error |
Database insert failed. |
Side effects
- Inserts into
loopback_ips. - Runs
ip addr add <ip/mask> dev <iface>. - For non-
lointerfaces, setsnet.ipv4.conf.all.rp_filter=0and per-interfacerp_filter=0viasysctl. - Logs a
loopback_addevent.
Example
curl -X POST https://panel.example.com:2087/api/advanced-network/loopbacks \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{"ip": "203.0.113.50", "description": "Self IP"}'
DELETE /api/advanced-network/loopbacks/:id¶
Deletes a managed loopback IP from the DB and removes it from lo.
Guards: RequireSuperuser(), RequirePlugin(db, "advanced-network")
Path parameters
| Name | Type | Description |
|---|---|---|
id |
integer | Loopback IP row id. |
Response - 200 OK
Errors
| Status | Code | Meaning |
|---|---|---|
404 |
not_found |
No loopback IP with that id. |
Side effects
- Runs
ip addr del <ip/mask> dev lo. - Deletes from
loopback_ips, logs aloopback_deleteevent.
Example
curl -X DELETE https://panel.example.com:2087/api/advanced-network/loopbacks/1 \
-H "Authorization: Bearer $TOKEN"
POST /api/advanced-network/loopbacks/:id/toggle¶
Toggles a loopback IP active/inactive, adding or removing it from lo accordingly.
Guards: RequireSuperuser(), RequirePlugin(db, "advanced-network")
Path parameters
| Name | Type | Description |
|---|---|---|
id |
integer | Loopback IP row id. |
Response - 200 OK
Errors
| Status | Code | Meaning |
|---|---|---|
404 |
not_found |
No loopback IP with that id. |
Side effects
- Runs
ip addr add/ip addr del <ip/mask> dev lodepending on new state. - Updates
activeinloopback_ips, logs aloopback_toggleevent.
Example
curl -X POST https://panel.example.com:2087/api/advanced-network/loopbacks/1/toggle \
-H "Authorization: Bearer $TOKEN"
GET /api/advanced-network/prefix-lists¶
Lists BGP prefix-lists with their entry counts.
Guards: RequireSuperuser(), RequirePlugin(db, "advanced-network")
Response - 200 OK
{ "data": { "lists": [ { "id": 1, "name": "customers-in", "type": "ipv4", "description": "Accepted from customers", "entry_count": 3, "created_at": "2026-06-17 10:00:00" } ] } }
Errors
| Status | Code | Meaning |
|---|---|---|
500 |
db_error |
Database query failed. |
Example
curl https://panel.example.com:2087/api/advanced-network/prefix-lists \
-H "Authorization: Bearer $TOKEN"
POST /api/advanced-network/prefix-lists¶
Creates a prefix-list.
Guards: RequireSuperuser(), RequirePlugin(db, "advanced-network")
Request body
| Field | Type | Required | Description |
|---|---|---|---|
name |
string | yes | Prefix-list name. |
type |
string | yes | Must be ipv4 or ipv6. |
description |
string | no | Free-text note. |
Response - 201 Created
Errors
| Status | Code | Meaning |
|---|---|---|
400 |
bad_request |
Invalid body, missing name, or type not ipv4/ipv6. |
500 |
db_error |
Database insert failed. |
Side effects
- Inserts into
prefix_lists, logs aprefix_list_createevent. Does not regenerate FRR config (use the sync endpoint).
Example
curl -X POST https://panel.example.com:2087/api/advanced-network/prefix-lists \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{"name": "customers-in", "type": "ipv4"}'
DELETE /api/advanced-network/prefix-lists/:id¶
Deletes a prefix-list, its entries, and clears any BGP peer references to it.
Guards: RequireSuperuser(), RequirePlugin(db, "advanced-network")
Path parameters
| Name | Type | Description |
|---|---|---|
id |
integer | Prefix-list id. |
Response - 200 OK
Errors
| Status | Code | Meaning |
|---|---|---|
500 |
db_error |
Database delete failed. |
Side effects
- Clears
filter_in_list_id/filter_out_list_idonbgp_peersrows referencing this list. - Deletes from
prefix_list_entriesandprefix_lists, logs aprefix_list_deleteevent.
Example
curl -X DELETE https://panel.example.com:2087/api/advanced-network/prefix-lists/1 \
-H "Authorization: Bearer $TOKEN"
PUT /api/advanced-network/prefix-lists/:id¶
Updates a prefix-list's name and description (type is immutable here).
Guards: RequireSuperuser(), RequirePlugin(db, "advanced-network")
Path parameters
| Name | Type | Description |
|---|---|---|
id |
integer | Prefix-list id. |
Request body
| Field | Type | Required | Description |
|---|---|---|---|
name |
string | yes | New name. |
description |
string | no | New description. |
Response - 200 OK
Errors
| Status | Code | Meaning |
|---|---|---|
400 |
bad_request |
Invalid body or missing name. |
500 |
db_error |
Database update failed. |
Side effects
- Updates
prefix_lists, logs aprefix_list_updateevent.
Example
curl -X PUT https://panel.example.com:2087/api/advanced-network/prefix-lists/1 \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{"name": "customers-in", "description": "Updated"}'
GET /api/advanced-network/prefix-lists/:id/entries¶
Lists entries of a prefix-list, ordered by sequence.
Guards: RequireSuperuser(), RequirePlugin(db, "advanced-network")
Path parameters
| Name | Type | Description |
|---|---|---|
id |
integer | Prefix-list id. |
Response - 200 OK
{ "data": { "entries": [ { "id": 1, "list_id": 1, "prefix": "203.0.113.0/24", "le": 32, "ge": 24, "action": "permit", "seq": 10, "description": "", "created_at": "2026-06-17 10:00:00" } ] } }
Errors
| Status | Code | Meaning |
|---|---|---|
500 |
db_error |
Database query failed. |
Example
curl https://panel.example.com:2087/api/advanced-network/prefix-lists/1/entries \
-H "Authorization: Bearer $TOKEN"
POST /api/advanced-network/prefix-lists/:id/entries¶
Adds an entry to a prefix-list, auto-assigning the next sequence number (max + 10) when seq is omitted.
Guards: RequireSuperuser(), RequirePlugin(db, "advanced-network")
Path parameters
| Name | Type | Description |
|---|---|---|
id |
integer | Prefix-list id. |
Request body
{ "prefix": "string (required)", "le": "int (optional)", "ge": "int (optional)", "action": "string (optional)", "seq": "int (optional)", "description": "string (optional)" }
| Field | Type | Required | Description |
|---|---|---|---|
prefix |
string | yes | CIDR prefix. |
le |
int | no | Less-than-or-equal mask length. |
ge |
int | no | Greater-than-or-equal mask length. |
action |
string | no | permit or deny; defaults to permit. |
seq |
int | no | Sequence number; auto-assigned (max + 10) when <= 0. |
description |
string | no | Free-text note. |
Response - 201 Created
Errors
| Status | Code | Meaning |
|---|---|---|
400 |
bad_request |
Invalid body, missing prefix, or action not permit/deny. |
500 |
db_error |
Database insert failed. |
Side effects
- Inserts into
prefix_list_entries, logs aprefix_entry_addevent.
Example
curl -X POST https://panel.example.com:2087/api/advanced-network/prefix-lists/1/entries \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{"prefix": "203.0.113.0/24", "action": "permit", "le": 32, "ge": 24}'
DELETE /api/advanced-network/prefix-lists/:id/entries/:eid¶
Deletes a single prefix-list entry.
Guards: RequireSuperuser(), RequirePlugin(db, "advanced-network")
Path parameters
| Name | Type | Description |
|---|---|---|
id |
integer | Prefix-list id (path only; not used in the delete query). |
eid |
integer | Entry id to delete. |
Response - 200 OK
Errors
| Status | Code | Meaning |
|---|---|---|
500 |
db_error |
Database delete failed. |
Side effects
- Deletes from
prefix_list_entries, logs aprefix_entry_deleteevent.
Example
curl -X DELETE https://panel.example.com:2087/api/advanced-network/prefix-lists/1/entries/1 \
-H "Authorization: Bearer $TOKEN"
PUT /api/advanced-network/prefix-lists/:id/entries/:eid¶
Updates a prefix-list entry.
Guards: RequireSuperuser(), RequirePlugin(db, "advanced-network")
Path parameters
| Name | Type | Description |
|---|---|---|
id |
integer | Prefix-list id (path only; not used in the update query). |
eid |
integer | Entry id to update. |
Request body
{ "prefix": "string", "le": "int", "ge": "int", "action": "string", "seq": "int", "description": "string" }
| Field | Type | Required | Description |
|---|---|---|---|
prefix |
string | no | CIDR prefix. |
le |
int | no | Less-than-or-equal mask length. |
ge |
int | no | Greater-than-or-equal mask length. |
action |
string | no | permit or deny (validated only when non-empty). |
seq |
int | no | Sequence number. |
description |
string | no | Free-text note. |
Response - 200 OK
Errors
| Status | Code | Meaning |
|---|---|---|
400 |
bad_request |
Invalid body, or action set but not permit/deny. |
500 |
db_error |
Database update failed. |
Side effects
- Updates
prefix_list_entries, logs aprefix_entry_updateevent.
Example
curl -X PUT https://panel.example.com:2087/api/advanced-network/prefix-lists/1/entries/1 \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{"prefix": "203.0.113.0/24", "action": "deny", "seq": 20}'
POST /api/advanced-network/prefix-lists/sync-bgp¶
Regenerates the FRR configuration so prefix-list changes take effect in BGP.
Guards: RequireSuperuser(), RequirePlugin(db, "advanced-network")
Response - 200 OK
Errors
| Status | Code | Meaning |
|---|---|---|
500 |
sync_error |
FRR config generation/reload failed. |
Side effects
- Regenerates FRR config (writes
/etc/frr/frr.confand reloads), logs aprefix_lists_syncevent.
Example
curl -X POST https://panel.example.com:2087/api/advanced-network/prefix-lists/sync-bgp \
-H "Authorization: Bearer $TOKEN"
GET /api/advanced-network/public-subnets¶
Returns the distinct IPv4 subnets configured on this host's interfaces (excluding loopback and the 10.255. auto-pairing range). Used by remote panels during tunnel auto-pairing.
This route is mounted with only RequirePlugin(db, "advanced-network") and no token authentication, so a remote peer can query it.
Response - 200 OK
This handler returns a flat array of subnet strings under data.
Side effects None (runs ip -o -4 addr show).
Example
POST /api/advanced-network/service/install¶
Installs FRR via the detected package manager, enables bgpd in /etc/frr/daemons, and starts the service.
Guards: RequireSuperuser(), RequirePlugin(db, "advanced-network")
Response - 200 OK
Errors
| Status | Code | Meaning |
|---|---|---|
500 |
(no code; message only) |
No supported package manager, or install failed (message contains output). |
Side effects
- Runs
apt-get/dnf/yum install -y frr [frr-pythontools]. - Edits
/etc/frr/daemonsto setbgpd=yes. - Runs
systemctl enable --now frr. - Logs a
frr_installevent.
Example
curl -X POST https://panel.example.com:2087/api/advanced-network/service/install \
-H "Authorization: Bearer $TOKEN"
POST /api/advanced-network/service/restart¶
Restarts the FRR systemd service.
Guards: RequireSuperuser(), RequirePlugin(db, "advanced-network")
Response - 200 OK
Errors
| Status | Code | Meaning |
|---|---|---|
500 |
(no code; message only) |
systemctl restart frr failed. |
Side effects
- Runs
systemctl restart frr.
Example
curl -X POST https://panel.example.com:2087/api/advanced-network/service/restart \
-H "Authorization: Bearer $TOKEN"
POST /api/advanced-network/service/start¶
Starts the FRR systemd service.
Guards: RequireSuperuser(), RequirePlugin(db, "advanced-network")
Response - 200 OK
Errors
| Status | Code | Meaning |
|---|---|---|
500 |
(no code; message only) |
systemctl start frr failed. |
Side effects
- Runs
systemctl start frr.
Example
curl -X POST https://panel.example.com:2087/api/advanced-network/service/start \
-H "Authorization: Bearer $TOKEN"
GET /api/advanced-network/service/status¶
Returns FRR install/run/enabled state, version, and (when installed but not running) the last 15 journal lines.
Guards: RequireSuperuser(), RequirePlugin(db, "advanced-network")
Response - 200 OK
{ "data": { "installed": true, "running": true, "enabled": true, "version": "FRRouting 8.4 ...", "logs": "" } }
Side effects None (reads vtysh, systemctl, journalctl).
Example
curl https://panel.example.com:2087/api/advanced-network/service/status \
-H "Authorization: Bearer $TOKEN"
POST /api/advanced-network/service/stop¶
Stops the FRR systemd service.
Guards: RequireSuperuser(), RequirePlugin(db, "advanced-network")
Response - 200 OK
Errors
| Status | Code | Meaning |
|---|---|---|
500 |
(no code; message only) |
systemctl stop frr failed. |
Side effects
- Runs
systemctl stop frr.
Example
curl -X POST https://panel.example.com:2087/api/advanced-network/service/stop \
-H "Authorization: Bearer $TOKEN"
GET /api/advanced-network/static-routes¶
Lists configured static routes.
Guards: RequireSuperuser(), RequirePlugin(db, "advanced-network")
Response - 200 OK
{ "data": { "routes": [ { "id": 1, "destination": "198.51.100.0/24", "gateway": "203.0.113.1", "interface": "eth0", "metric": 0, "description": "", "enabled": true, "persistent": true, "created_at": "2026-06-17 10:00:00" } ] } }
Errors
| Status | Code | Meaning |
|---|---|---|
500 |
db_error |
Database query failed. |
Example
curl https://panel.example.com:2087/api/advanced-network/static-routes \
-H "Authorization: Bearer $TOKEN"
POST /api/advanced-network/static-routes¶
Adds a static route, applies it to the kernel immediately, and auto-configures IP forwarding/NAT toward the destination.
Guards: RequireSuperuser(), RequirePlugin(db, "advanced-network")
Request body
{ "destination": "string (required)", "gateway": "string (required)", "interface": "string (optional)", "metric": "int (optional)", "description": "string (optional)", "persistent": "bool (optional)" }
| Field | Type | Required | Description |
|---|---|---|---|
destination |
string | yes | Destination prefix. |
gateway |
string | yes | Next-hop gateway. |
interface |
string | no | Outbound interface. |
metric |
int | no | Route metric. |
description |
string | no | Free-text note. |
persistent |
bool | no | Persist across reboots; defaults to true unless explicitly false. |
Response - 201 Created
{ "data": { "id": 1, "success": true, "forwarding_setup": ["IP forwarding enabled", "iptables FORWARD rules added for 198.51.100.0/24"] } }
Errors
| Status | Code | Meaning |
|---|---|---|
400 |
bad_request |
Invalid body, or missing destination/gateway. |
500 |
db_error |
Database insert failed. |
Side effects
- Inserts into
static_routes. - Runs
ip route add <dest> via <gw> [dev …] [metric …]. ensureForwarding: may enablenet.ipv4.ip_forward(writes/etc/sysctl.d/99-ip-forward.conf), add iptables FORWARD/MASQUERADE rules, and configure CSF if present.- When persistent, regenerates FRR config. Logs a
static_route_addevent.
Example
curl -X POST https://panel.example.com:2087/api/advanced-network/static-routes \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{"destination": "198.51.100.0/24", "gateway": "203.0.113.1"}'
DELETE /api/advanced-network/static-routes/:id¶
Removes a static route from the kernel and the database.
Guards: RequireSuperuser(), RequirePlugin(db, "advanced-network")
Path parameters
| Name | Type | Description |
|---|---|---|
id |
integer | Static route id. |
Response - 200 OK
Errors
| Status | Code | Meaning |
|---|---|---|
500 |
db_error |
Database delete failed. |
Side effects
- Runs
ip route del <dest> via <gw>. - Deletes from
static_routes, regenerates FRR config, logs astatic_route_removeevent.
Example
curl -X DELETE https://panel.example.com:2087/api/advanced-network/static-routes/1 \
-H "Authorization: Bearer $TOKEN"
PUT /api/advanced-network/static-routes/:id¶
Updates a static route's database record and regenerates the FRR config.
Guards: RequireSuperuser(), RequirePlugin(db, "advanced-network")
Path parameters
| Name | Type | Description |
|---|---|---|
id |
integer | Static route id. |
Request body
{ "destination": "string", "gateway": "string", "interface": "string", "metric": "int", "description": "string", "enabled": "bool (optional)", "persistent": "bool (optional)" }
| Field | Type | Required | Description |
|---|---|---|---|
destination |
string | no | Destination prefix. |
gateway |
string | no | Next-hop gateway. |
interface |
string | no | Outbound interface. |
metric |
int | no | Route metric. |
description |
string | no | Free-text note. |
enabled |
bool | no | Enable flag; defaults to enabled unless explicitly false. |
persistent |
bool | no | Persist flag; defaults to true unless explicitly false. |
Response - 200 OK
Errors
| Status | Code | Meaning |
|---|---|---|
400 |
bad_request |
Invalid request body. |
500 |
db_error |
Database update failed. |
Side effects
- Updates
static_routes, regenerates FRR config, logs astatic_route_updateevent.
Example
curl -X PUT https://panel.example.com:2087/api/advanced-network/static-routes/1 \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{"destination": "198.51.100.0/24", "gateway": "203.0.113.1", "enabled": true}'
POST /api/advanced-network/static-routes/:id/toggle¶
Toggles a static route enabled/disabled, adding or removing it from the kernel accordingly.
Guards: RequireSuperuser(), RequirePlugin(db, "advanced-network")
Path parameters
| Name | Type | Description |
|---|---|---|
id |
integer | Static route id. |
Response - 200 OK
Errors
| Status | Code | Meaning |
|---|---|---|
500 |
db_error |
Database update failed. |
Side effects
- Runs
ip route add/ip route del <dest> via <gw>depending on new state. - Updates
static_routes, regenerates FRR config.
Example
curl -X POST https://panel.example.com:2087/api/advanced-network/static-routes/1/toggle \
-H "Authorization: Bearer $TOKEN"
POST /api/advanced-network/tunnel-pair¶
Auto-pairing endpoint called by a remote panel: negotiates a /30 subnet, assigns tunnel IPs to both sides, configures the local tunnel, and (for WireGuard) records the peer. The route is mounted with only RequirePlugin(db, "advanced-network") and no token authentication; the handler instead validates a /tmp/tunnel-pair-*.token file or a matching local tunnel before pairing.
Request body
{ "from_ip": "string (required)", "type": "string (required)", "tunnel_id": "int (optional)", "proposed_subnet": "string (optional)", "public_key": "string (optional)", "listen_port": "int (optional)" }
| Field | Type | Required | Description |
|---|---|---|---|
from_ip |
string | yes | Requester's public IP (must be a valid IP). |
type |
string | yes | gre, vxlan, or wireguard. |
tunnel_id |
int | no | Requester's tunnel id (informational). |
proposed_subnet |
string | no | Requester's proposed /30 subnet. |
public_key |
string | no | WireGuard: requester's public key. |
listen_port |
int | no | WireGuard: requester's listen port. |
Response - 200 OK
This handler returns a flat (non-enveloped) body.
{ "matched": true, "my_tunnel_ip": "10.255.0.2", "your_tunnel_ip": "10.255.0.1", "subnet": "10.255.0.0/30", "my_interface": "wg0", "public_key": "abc...=", "listen_port": 51821 }
Errors
All errors are flat bodies with { "matched": false, "error": "..." }.
| Status | Code | Meaning |
|---|---|---|
400 |
(flat error) |
Invalid body, invalid from_ip, bad type, or invalid subnet. |
404 |
(flat error) |
No valid token and no matching local tunnel. |
409 |
(flat error) |
No available pairing subnets. |
200 |
(flat error) |
matched: false when no matching tunnel row is found. |
Side effects
- Updates
tunnels(assignstunnel_ip, setspaired,pair_subnet). - Inserts/updates
tunnel_peers(WireGuard: stores peer public key/endpoint). - Applies the tunnel interface (
applyTunnelConfig) - brings up the WireGuard/GRE/VXLAN interface. - Removes the pairing token file, logs a
tunnel_pair_acceptevent.
Example
curl -X POST https://panel.example.com:2087/api/advanced-network/tunnel-pair \
-H "Content-Type: application/json" \
-d '{"from_ip": "203.0.113.10", "type": "wireguard", "proposed_subnet": "10.255.0.0/30", "public_key": "abc...=", "listen_port": 51820}'
GET /api/advanced-network/tunnels¶
Lists all tunnels (WireGuard/GRE/VXLAN) with peer counts, pairing state, and bridge settings.
Guards: RequireSuperuser(), RequirePlugin(db, "advanced-network")
Response - 200 OK
{ "data": { "tunnels": [ { "id": 1, "name": "site-a", "type": "wireguard", "interface_name": "wg0", "local_ip": "203.0.113.10", "tunnel_ip": "10.255.0.1/30", "listen_port": 51820, "public_key": "abc...=", "vxlan_id": 0, "mtu": 1420, "enabled": true, "persistent": true, "peer_count": 1, "created_at": "2026-06-17 10:00:00", "paired": true, "pair_subnet": "10.255.0.0/30", "remote_ip": "203.0.113.20", "bridge_enabled": false, "bridge_name": "", "bridge_iface": "", "bridge_ip": "" } ] } }
Errors
| Status | Code | Meaning |
|---|---|---|
500 |
db_error |
Database query failed. |
Example
curl https://panel.example.com:2087/api/advanced-network/tunnels \
-H "Authorization: Bearer $TOKEN"
POST /api/advanced-network/tunnels¶
Creates a tunnel (generating a WireGuard keypair and listen port as needed), pre-allocates a /30 subnet, optionally auto-pairs with a remote panel, and applies the interface.
Guards: RequireSuperuser(), RequirePlugin(db, "advanced-network")
Request body
{ "name": "string (required)", "type": "string (required)", "interface_name": "string (required)", "local_ip": "string (optional)", "tunnel_ip": "string (optional)", "listen_port": "int (optional)", "vxlan_id": "int (optional)", "mtu": "int (optional)", "remote_ip": "string (optional)" }
| Field | Type | Required | Description |
|---|---|---|---|
name |
string | yes | Tunnel name. |
type |
string | yes | wireguard, gre, or vxlan. |
interface_name |
string | yes | Network interface to create. |
local_ip |
string | no | Local underlay IP. |
tunnel_ip |
string | no | Tunnel overlay IP; auto-allocated when empty. |
listen_port |
int | no | WireGuard port is always auto-assigned; others default to 51820. |
vxlan_id |
int | no | VXLAN VNI. |
mtu |
int | no | Interface MTU. |
remote_ip |
string | no | Remote panel public IP to auto-pair with. |
Response - 201 Created
{ "data": { "id": 1, "public_key": "abc...=", "success": true, "pair_status": "paired", "pair_error": "" } }
pair_status is paired, failed, or empty (manual mode).
Errors
| Status | Code | Meaning |
|---|---|---|
400 |
bad_request |
Invalid body, missing name/type/interface_name, or unsupported type. |
500 |
keygen_error |
WireGuard key generation failed. |
500 |
db_error |
Database insert failed. |
Side effects
- Generates WireGuard keys via
wg genkey/wg pubkey. - Inserts into
tunnels(and atunnel_peersrow whenremote_ipis set). - When
remote_ipset, writes a/tmp/tunnel-pair-*.tokenfile and calls the remote panel'stunnel-pairendpoint. - Applies the tunnel interface (brings up WireGuard/GRE/VXLAN), logs
tunnel_addand pairing events.
Example
curl -X POST https://panel.example.com:2087/api/advanced-network/tunnels \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{"name": "site-a", "type": "wireguard", "interface_name": "wg0", "remote_ip": "203.0.113.20"}'
DELETE /api/advanced-network/tunnels/:id¶
Deletes a tunnel, tears down its interface (and any VXLAN bridge), and removes WireGuard config.
Guards: RequireSuperuser(), RequirePlugin(db, "advanced-network")
Path parameters
| Name | Type | Description |
|---|---|---|
id |
integer | Tunnel id. |
Response - 200 OK
Errors
| Status | Code | Meaning |
|---|---|---|
500 |
db_error |
Database delete failed. |
Side effects
- For VXLAN: cleans up the associated bridge.
- WireGuard:
wg-quick down,systemctl disable wg-quick@<if>, removes/etc/wireguard/<if>.conf. - GRE/VXLAN:
ip link set <if> downandip link del <if>. - Deletes from
tunnels, logs atunnel_removeevent.
Example
curl -X DELETE https://panel.example.com:2087/api/advanced-network/tunnels/1 \
-H "Authorization: Bearer $TOKEN"
PUT /api/advanced-network/tunnels/:id¶
Updates a tunnel's settings (including bridge options) and re-applies the interface.
Guards: RequireSuperuser(), RequirePlugin(db, "advanced-network")
Path parameters
| Name | Type | Description |
|---|---|---|
id |
integer | Tunnel id. |
Request body
{ "name": "string", "interface_name": "string", "local_ip": "string", "tunnel_ip": "string", "listen_port": "int", "vxlan_id": "int", "mtu": "int", "ttl": "int", "bridge_enabled": "bool", "bridge_name": "string", "bridge_iface": "string", "bridge_ip": "string" }
| Field | Type | Required | Description |
|---|---|---|---|
name |
string | no | Tunnel name. |
interface_name |
string | no | Interface name. |
local_ip |
string | no | Local underlay IP. |
tunnel_ip |
string | no | Tunnel overlay IP. |
listen_port |
int | no | Listen port. |
vxlan_id |
int | no | VXLAN VNI. |
mtu |
int | no | Interface MTU. |
ttl |
int | no | Tunnel TTL. |
bridge_enabled |
bool | no | Enable VXLAN bridging. |
bridge_name |
string | no | Bridge name. |
bridge_iface |
string | no | Interface to enslave to the bridge. |
bridge_ip |
string | no | Bridge IP address. |
Response - 200 OK
Errors
| Status | Code | Meaning |
|---|---|---|
400 |
bad_request |
Invalid request body. |
500 |
db_error |
Database update failed. |
Side effects
- Updates
tunnels, re-applies the tunnel interface, logs atunnel_updateevent.
Example
curl -X PUT https://panel.example.com:2087/api/advanced-network/tunnels/1 \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{"name": "site-a", "interface_name": "wg0", "mtu": 1420}'
GET /api/advanced-network/tunnels/:id/mac-table¶
Returns the bridge forwarding (MAC) table for a bridge-enabled VXLAN tunnel, parsed from bridge fdb show.
Guards: RequireSuperuser(), RequirePlugin(db, "advanced-network")
Path parameters
| Name | Type | Description |
|---|---|---|
id |
integer | Tunnel id (must have bridge_enabled = 1). |
Response - 200 OK
This handler returns a flat array under data. Returns an empty array if the tunnel has no bridge or the command fails.
Side effects None (runs bridge fdb show).
Example
curl https://panel.example.com:2087/api/advanced-network/tunnels/1/mac-table \
-H "Authorization: Bearer $TOKEN"
GET /api/advanced-network/tunnels/:id/peers¶
Lists peers of a tunnel.
Guards: RequireSuperuser(), RequirePlugin(db, "advanced-network")
Path parameters
| Name | Type | Description |
|---|---|---|
id |
integer | Tunnel id. |
Response - 200 OK
preshared_key is reported as a boolean (whether one is set), not the value itself.
{ "data": { "peers": [ { "id": 1, "tunnel_id": 1, "name": "laptop", "endpoint": "203.0.113.20:51820", "public_key": "abc...=", "preshared_key": true, "allowed_ips": "10.255.0.2/32", "tunnel_ip": "10.255.0.2", "persistent_keepalive": 25, "remote_ip": "203.0.113.20", "enabled": true, "last_handshake": "", "transfer_rx": 0, "transfer_tx": 0, "created_at": "2026-06-17 10:00:00" } ] } }
Errors
| Status | Code | Meaning |
|---|---|---|
500 |
db_error |
Database query failed. |
Example
curl https://panel.example.com:2087/api/advanced-network/tunnels/1/peers \
-H "Authorization: Bearer $TOKEN"
POST /api/advanced-network/tunnels/:id/peers¶
Adds a peer to a tunnel; for WireGuard tunnels it generates the peer keypair (and optional preshared key) and re-applies the interface.
Guards: RequireSuperuser(), RequirePlugin(db, "advanced-network")
Path parameters
| Name | Type | Description |
|---|---|---|
id |
integer | Tunnel id. |
Request body
{ "name": "string (optional)", "endpoint": "string (optional)", "allowed_ips": "string (optional)", "tunnel_ip": "string (optional)", "persistent_keepalive": "int (optional)", "remote_ip": "string (optional)", "preshared_key": "bool (optional)" }
| Field | Type | Required | Description |
|---|---|---|---|
name |
string | no | Peer label. |
endpoint |
string | no | Peer endpoint host:port. |
allowed_ips |
string | no | Allowed IPs for the peer. |
tunnel_ip |
string | no | Peer's tunnel IP. |
persistent_keepalive |
int | no | Keepalive seconds; defaults to 25. |
remote_ip |
string | no | Peer's public IP. |
preshared_key |
bool | no | WireGuard: generate a preshared key. |
Response - 201 Created
{ "data": { "id": 1, "public_key": "abc...=", "private_key": "xyz...=", "preshared_key": "psk...=", "success": true } }
Errors
| Status | Code | Meaning |
|---|---|---|
400 |
bad_request |
Invalid request body. |
500 |
keygen_error |
WireGuard key generation failed. |
500 |
db_error |
Database insert failed. |
Side effects
- Generates WireGuard keys (
wg genkey/wg pubkey/wg genpsk) for WireGuard tunnels. - Inserts into
tunnel_peers, re-applies the tunnel interface, logs atunnel_peer_addevent.
Example
curl -X POST https://panel.example.com:2087/api/advanced-network/tunnels/1/peers \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{"name": "laptop", "allowed_ips": "10.255.0.2/32", "preshared_key": true}'
DELETE /api/advanced-network/tunnels/:id/peers/:pid¶
Removes a peer from a tunnel and re-applies the interface.
Guards: RequireSuperuser(), RequirePlugin(db, "advanced-network")
Path parameters
| Name | Type | Description |
|---|---|---|
id |
integer | Tunnel id. |
pid |
integer | Peer id. |
Response - 200 OK
Errors
| Status | Code | Meaning |
|---|---|---|
500 |
db_error |
Database delete failed. |
Side effects
- Deletes from
tunnel_peers, re-applies the tunnel interface, logs atunnel_peer_removeevent.
Example
curl -X DELETE https://panel.example.com:2087/api/advanced-network/tunnels/1/peers/1 \
-H "Authorization: Bearer $TOKEN"
PUT /api/advanced-network/tunnels/:id/peers/:pid¶
Updates a tunnel peer and re-applies the interface.
Guards: RequireSuperuser(), RequirePlugin(db, "advanced-network")
Path parameters
| Name | Type | Description |
|---|---|---|
id |
integer | Tunnel id. |
pid |
integer | Peer id. |
Request body
{ "name": "string", "endpoint": "string", "allowed_ips": "string", "tunnel_ip": "string", "persistent_keepalive": "int", "remote_ip": "string", "enabled": "bool (optional)" }
| Field | Type | Required | Description |
|---|---|---|---|
name |
string | no | Peer label. |
endpoint |
string | no | Peer endpoint. |
allowed_ips |
string | no | Allowed IPs. |
tunnel_ip |
string | no | Peer tunnel IP. |
persistent_keepalive |
int | no | Keepalive seconds. |
remote_ip |
string | no | Peer public IP. |
enabled |
bool | no | Enable flag; defaults to enabled unless explicitly false. |
Response - 200 OK
Errors
| Status | Code | Meaning |
|---|---|---|
400 |
bad_request |
Invalid request body. |
500 |
db_error |
Database update failed. |
Side effects
- Updates
tunnel_peers, re-applies the tunnel interface.
Example
curl -X PUT https://panel.example.com:2087/api/advanced-network/tunnels/1/peers/1 \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{"name": "laptop", "allowed_ips": "10.255.0.2/32", "enabled": true}'
GET /api/advanced-network/tunnels/:id/peers/:pid/config¶
Returns a ready-to-use WireGuard client configuration for the peer; with ?format=file it streams the config as a downloadable .conf file.
Guards: RequireSuperuser(), RequirePlugin(db, "advanced-network")
Path parameters
| Name | Type | Description |
|---|---|---|
id |
integer | Tunnel id. |
pid |
integer | Peer id. |
Query parameters
| Name | Type | Optional | Description |
|---|---|---|---|
format |
string | yes | When file, streams a text/plain attachment (wg-client.conf) instead of JSON. |
Response - 200 OK
{ "data": { "config": "[Interface]\nPrivateKey = ...\nAddress = 10.255.0.2/24\nDNS = 1.1.1.1\n\n[Peer]\nPublicKey = ...\nEndpoint = 203.0.113.10:51820\nAllowedIPs = 10.255.0.0/24\n" } }
With ?format=file the body is the raw config file (Content-Disposition: attachment; filename=wg-client.conf).
Errors
| Status | Code | Meaning |
|---|---|---|
400 |
no_key |
The peer has no stored private key (cannot build a client config). |
Side effects None (may run hostname -I to detect the server endpoint).
Example
curl "https://panel.example.com:2087/api/advanced-network/tunnels/1/peers/1/config?format=file" \
-H "Authorization: Bearer $TOKEN" -o wg-client.conf
GET /api/advanced-network/tunnels/:id/peers/:pid/qr¶
Returns the peer's WireGuard client config plus a PNG QR code (base64 data URL) generated with qrencode.
Guards: RequireSuperuser(), RequirePlugin(db, "advanced-network")
Path parameters
| Name | Type | Description |
|---|---|---|
id |
integer | Tunnel id. |
pid |
integer | Peer id. |
Response - 200 OK
{ "data": { "qr_png": "data:image/png;base64,iVBOR...", "config": "[Interface]\n...", "qr_available": true } }
If qrencode is unavailable, returns 200 OK with { "data": { "config": "...", "qr_available": false } }.
Errors
| Status | Code | Meaning |
|---|---|---|
400 |
no_key |
The peer has no stored private key. |
Side effects None (runs qrencode and optionally hostname -I).
Example
curl https://panel.example.com:2087/api/advanced-network/tunnels/1/peers/1/qr \
-H "Authorization: Bearer $TOKEN"
POST /api/advanced-network/tunnels/:id/retry-pair¶
Retries auto-pairing for an unpaired tunnel using its first enabled peer's remote IP.
Guards: RequireSuperuser(), RequirePlugin(db, "advanced-network")
Path parameters
| Name | Type | Description |
|---|---|---|
id |
integer | Tunnel id. |
Response - 200 OK
On a failed (but non-error) attempt: { "data": { "paired": false, "error": "..." } }.
Errors
| Status | Code | Meaning |
|---|---|---|
404 |
not_found |
Tunnel not found. |
400 |
already_paired |
Tunnel is already paired. |
400 |
bad_type |
Type is not GRE/VXLAN/WireGuard. |
400 |
no_peer |
No enabled peer with a remote IP configured. |
Side effects
- Writes a
/tmp/tunnel-pair-*.tokenfile and calls the remote panel'stunnel-pairendpoint. - On success, updates
tunnels/tunnel_peersand applies the interface; logs pairing events.
Example
curl -X POST https://panel.example.com:2087/api/advanced-network/tunnels/1/retry-pair \
-H "Authorization: Bearer $TOKEN"
POST /api/advanced-network/tunnels/:id/toggle¶
Toggles a tunnel enabled/disabled, bringing its interface up or down.
Guards: RequireSuperuser(), RequirePlugin(db, "advanced-network")
Path parameters
| Name | Type | Description |
|---|---|---|
id |
integer | Tunnel id. |
Response - 200 OK
Errors
| Status | Code | Meaning |
|---|---|---|
500 |
db_error |
Database update failed. |
Side effects
- Updates
tunnels. - When enabling, re-applies the tunnel interface; when disabling, runs
wg-quick down <if>(WireGuard) orip link set <if> down.
Example
curl -X POST https://panel.example.com:2087/api/advanced-network/tunnels/1/toggle \
-H "Authorization: Bearer $TOKEN"
GET /api/advanced-network/tunnels/check-wireguard¶
Reports whether the wg binary is present on the system.
Guards: RequireSuperuser(), RequirePlugin(db, "advanced-network")
Response - 200 OK
Side effects None.
Example
curl https://panel.example.com:2087/api/advanced-network/tunnels/check-wireguard \
-H "Authorization: Bearer $TOKEN"
POST /api/advanced-network/tunnels/install-wireguard¶
Installs wireguard-tools via apt (Debian) or dnf (other distros).
Guards: RequireSuperuser(), RequirePlugin(db, "advanced-network")
Response - 200 OK
Errors
| Status | Code | Meaning |
|---|---|---|
500 |
install_error |
Package install failed (message contains command output). |
Side effects
- Runs
apt install -y wireguard-toolsordnf install -y wireguard-tools.
Example
curl -X POST https://panel.example.com:2087/api/advanced-network/tunnels/install-wireguard \
-H "Authorization: Bearer $TOKEN"
GET /api/advanced-network/tunnels/remote-subnets/:ip¶
Queries a remote panel's public-subnets endpoint and returns the deduplicated list of network subnets behind it (excluding the 10.255. auto-pairing range), for use in tunnel allowed-IPs.
Guards: RequireSuperuser(), RequirePlugin(db, "advanced-network")
Path parameters
| Name | Type | Description |
|---|---|---|
ip |
string | Remote panel IP to query (must be a valid IP). |
Response - 200 OK
This handler returns a flat array of subnet strings under data. Returns an empty array if the remote panel is unreachable.
Errors
| Status | Code | Meaning |
|---|---|---|
400 |
(flat data: []) |
ip is not a valid IP address. |
Side effects None (outbound HTTPS GET to https://<ip>:2087/api/advanced-network/public-subnets, TLS verification disabled).
Example
curl https://panel.example.com:2087/api/advanced-network/tunnels/remote-subnets/203.0.113.20 \
-H "Authorization: Bearer $TOKEN"
GET /api/advanced-network/tunnels/status¶
Returns the live up/down state of all enabled tunnels, plus parsed wg show peer details for WireGuard tunnels.
Guards: RequireSuperuser(), RequirePlugin(db, "advanced-network")
Response - 200 OK
{ "data": { "tunnels": [ { "id": 1, "interface": "wg0", "type": "wireguard", "up": true, "peers": [] } ] } }
Side effects None (runs ip link show and wg show).
Example