Skip to content

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:

{ "data": { ... } }

On error:

{ "error": { "code": "string", "message": "string" } }

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

{ "ip": "string (required)", "reason": "string (optional)", "duration": "string (optional)" }
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

{ "data": { "id": 1, "success": true } }

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_add event.

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

{ "data": { "success": true } }

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_remove event.

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

{ "data": { "success": true } }

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.conf and reloads).
  • Logs a config_update event.

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

{ "data": { "id": 1, "success": true } }

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_add event.

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

{ "data": { "success": true } }

Errors

Status Code Meaning
500 db_error Database delete failed.

Side effects

  • Deletes from bgp_flowspec_rules, regenerates FRR config, logs a flowspec_remove event.

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

{ "data": { "success": true } }

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 a flowspec_update event.

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

{ "data": { "supported": true } }

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

{ "prefix": "string (required)", "description": "string (optional)" }
Field Type Required Description
prefix string yes CIDR prefix to announce.
description string no Free-text note.

Response - 201 Created

{ "data": { "id": 1, "success": true } }

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 a route_add event.

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

{ "data": { "success": true } }

Errors

Status Code Meaning
500 db_error Database delete failed.

Side effects

  • Deletes from bgp_networks, regenerates FRR config, logs a route_remove event.

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_change in bgp_peers when 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

{ "data": { "id": 1, "success": true } }

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 a peer_add event.

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

{ "data": { "success": true } }

Errors

Status Code Meaning
500 db_error Database delete failed.

Side effects

  • Deletes from bgp_peers, regenerates FRR config, logs a peer_remove event.

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

{ "data": { "success": true } }

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 a peer_update event.

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

curl https://panel.example.com:2087/api/advanced-network/export \
  -H "Authorization: Bearer $TOKEN"

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

{ "data": { "success": true } }

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_config row 1.
  • Regenerates FRR config and logs a config_import event.

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).

{ "data": [ { "interface": "eth0", "ip": "203.0.113.10", "cidr": "203.0.113.10/24" } ] }

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.

{ "data": ["lo", "eth0", "eth1"] }

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

{ "target": "string (required)", "interface": "string (required)" }
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

{ "data": { "success": true } }

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_install event.

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

{ "data": { "success": true } }

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

{ "data": { "id": 1, "success": true } }

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 a vrrp_add event.

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

{ "data": { "success": true } }

Errors

Status Code Meaning
500 db_error Database delete failed.

Side effects

  • Deletes from keepalived_vrrp, regenerates keepalived config, logs a vrrp_remove event.

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

{ "data": { "success": true } }

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 a vrrp_update event.

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

{ "ip_address": "string (required)", "interface": "string (optional)" }
Field Type Required Description
ip_address string yes Virtual IP to add.
interface string no Interface the VIP binds to.

Response - 201 Created

{ "data": { "id": 1, "success": true } }

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

{ "data": { "success": true } }

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

{ "data": { "id": 1, "success": true } }

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 a vs_add event.

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

{ "data": { "success": true } }

Errors

Status Code Meaning
500 db_error Database delete failed.

Side effects

  • Deletes from keepalived_virtual_servers, regenerates keepalived config, logs a vs_remove event.

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

{ "data": { "success": true } }

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 a vs_update event.

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

{ "data": { "id": 1, "success": true } }

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 an rs_add event.

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

{ "data": { "success": true } }

Errors

Status Code Meaning
500 db_error Database delete failed.

Side effects

  • Deletes from keepalived_real_servers, regenerates keepalived config, logs an rs_remove event.

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

{ "data": { "success": true } }

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

{ "ip": "string (required)", "description": "string (optional)", "iface": "string (optional)" }
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

{ "data": { "id": 1, "success": true } }

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-lo interfaces, sets net.ipv4.conf.all.rp_filter=0 and per-interface rp_filter=0 via sysctl.
  • Logs a loopback_add event.

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

{ "data": { "success": true } }

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 a loopback_delete event.

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

{ "data": { "success": true } }

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 lo depending on new state.
  • Updates active in loopback_ips, logs a loopback_toggle event.

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

{ "name": "string (required)", "type": "string (required)", "description": "string (optional)" }
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

{ "data": { "id": 1, "success": true } }

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 a prefix_list_create event. 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

{ "data": { "success": true } }

Errors

Status Code Meaning
500 db_error Database delete failed.

Side effects

  • Clears filter_in_list_id/filter_out_list_id on bgp_peers rows referencing this list.
  • Deletes from prefix_list_entries and prefix_lists, logs a prefix_list_delete event.

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

{ "name": "string (required)", "description": "string (optional)" }
Field Type Required Description
name string yes New name.
description string no New description.

Response - 200 OK

{ "data": { "success": true } }

Errors

Status Code Meaning
400 bad_request Invalid body or missing name.
500 db_error Database update failed.

Side effects

  • Updates prefix_lists, logs a prefix_list_update event.

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

{ "data": { "id": 1, "success": true } }

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 a prefix_entry_add event.

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

{ "data": { "success": true } }

Errors

Status Code Meaning
500 db_error Database delete failed.

Side effects

  • Deletes from prefix_list_entries, logs a prefix_entry_delete event.

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

{ "data": { "success": true } }

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 a prefix_entry_update event.

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

{ "data": { "success": true } }

Errors

Status Code Meaning
500 sync_error FRR config generation/reload failed.

Side effects

  • Regenerates FRR config (writes /etc/frr/frr.conf and reloads), logs a prefix_lists_sync event.

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.

{ "data": ["203.0.113.0/24", "198.51.100.0/24"] }

Side effects None (runs ip -o -4 addr show).

Example

curl https://panel.example.com:2087/api/advanced-network/public-subnets

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

{ "data": { "success": true } }

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/daemons to set bgpd=yes.
  • Runs systemctl enable --now frr.
  • Logs a frr_install event.

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

{ "data": { "success": true } }

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

{ "data": { "success": true } }

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

{ "data": { "success": true } }

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 enable net.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_add event.

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

{ "data": { "success": true } }

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 a static_route_remove event.

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

{ "data": { "success": true } }

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 a static_route_update event.

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

{ "data": { "success": true, "enabled": true } }

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 (assigns tunnel_ip, sets paired, 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_accept event.

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 a tunnel_peers row when remote_ip is set).
  • When remote_ip set, writes a /tmp/tunnel-pair-*.token file and calls the remote panel's tunnel-pair endpoint.
  • Applies the tunnel interface (brings up WireGuard/GRE/VXLAN), logs tunnel_add and 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

{ "data": { "success": true } }

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> down and ip link del <if>.
  • Deletes from tunnels, logs a tunnel_remove event.

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

{ "data": { "success": true } }

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 a tunnel_update event.

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.

{ "data": [ { "mac": "52:54:00:11:22:33", "interface": "vxlan0", "vlan": "" } ] }

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 a tunnel_peer_add event.

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

{ "data": { "success": true } }

Errors

Status Code Meaning
500 db_error Database delete failed.

Side effects

  • Deletes from tunnel_peers, re-applies the tunnel interface, logs a tunnel_peer_remove event.

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

{ "data": { "success": true } }

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

{ "data": { "paired": true, "tunnel_ip": "10.255.0.1/30", "subnet": "10.255.0.0/30" } }

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-*.token file and calls the remote panel's tunnel-pair endpoint.
  • On success, updates tunnels/tunnel_peers and 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

{ "data": { "success": true, "enabled": true } }

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) or ip 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

{ "data": { "installed": true } }

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

{ "data": { "success": true } }

Errors

Status Code Meaning
500 install_error Package install failed (message contains command output).

Side effects

  • Runs apt install -y wireguard-tools or dnf 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.

{ "data": ["198.51.100.0/24"] }

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

curl https://panel.example.com:2087/api/advanced-network/tunnels/status \
  -H "Authorization: Bearer $TOKEN"