refactor: WireGuard tunnels are read in their own module (#32)
driver / lint (pull_request) Successful in 13s
driver / standard (pull_request) Successful in 14s
driver / test (3.10) (pull_request) Failing after 6s
driver / types (pull_request) Successful in 1m2s
driver / test (3.11) (pull_request) Successful in 1m5s
driver / test (3.12) (pull_request) Successful in 1m5s
driver / lowest (pull_request) Successful in 33s
driver / test (3.13) (pull_request) Successful in 59s
driver / build (pull_request) Successful in 27s
driver / security (pull_request) Successful in 1m1s
CI / driver (pull_request) Failing after 2m25s
driver / publish (pull_request) Skipped
driver / lint (pull_request) Successful in 13s
driver / standard (pull_request) Successful in 14s
driver / test (3.10) (pull_request) Failing after 6s
driver / types (pull_request) Successful in 1m2s
driver / test (3.11) (pull_request) Successful in 1m5s
driver / test (3.12) (pull_request) Successful in 1m5s
driver / lowest (pull_request) Successful in 33s
driver / test (3.13) (pull_request) Successful in 59s
driver / build (pull_request) Successful in 27s
driver / security (pull_request) Successful in 1m1s
CI / driver (pull_request) Failing after 2m25s
driver / publish (pull_request) Skipped
get_vpn_tunnels() grew past the quality baseline with the key fix. The WireGuard part is pure parsing of /api/wireguard/service/show, like the listen ports in listening.py, so it moves to wireguard.py and the driver calls it. opnsense.py shrinks from 3125 to 3082 lines.
This commit is contained in:
1 parent
9f45952605
commit
eaafa540e5
3 files changed
+78
-70
No files matched your search
@@ -69,6 +69,7 @@ from napalm_opnsense.ping_mixin import OPNsensePingMixin
|
|||||||
from napalm_opnsense.port_forwards import alias_index, port_forwards, wan_interfaces
|
from napalm_opnsense.port_forwards import alias_index, port_forwards, wan_interfaces
|
||||||
from napalm_opnsense.resolver_mixin import OPNsenseResolverMixin
|
from napalm_opnsense.resolver_mixin import OPNsenseResolverMixin
|
||||||
from napalm_opnsense.virtual_ips import carp_addresses
|
from napalm_opnsense.virtual_ips import carp_addresses
|
||||||
|
from napalm_opnsense.wireguard import wireguard_tunnels
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
@@ -93,28 +94,6 @@ def _carp_master(entry: dict[str, Any]) -> bool:
|
|||||||
return str(entry.get("status") or "").strip().upper() == "MASTER"
|
return str(entry.get("status") or "").strip().upper() == "MASTER"
|
||||||
|
|
||||||
|
|
||||||
#: How much of a public key names a peer, the same length as napalm-linux uses.
|
|
||||||
_WG_KEY_PREFIX = 8
|
|
||||||
|
|
||||||
|
|
||||||
def _wireguard_key(row: dict[str, Any], position: int, taken: dict[str, Any]) -> str:
|
|
||||||
"""A WireGuard peer's tunnel key: its interface and the start of its public key.
|
|
||||||
|
|
||||||
Not its position: a peer added ahead of another would renumber it, and
|
|
||||||
whatever a consumer keeps per key would move to a different peer (#32). The
|
|
||||||
base64 is made URL-safe because the key is used as a path segment. Two peers
|
|
||||||
sharing a prefix get the whole key; one without a key keeps its position.
|
|
||||||
"""
|
|
||||||
public_key = (row.get("public-key") or "").strip()
|
|
||||||
if not public_key:
|
|
||||||
return f"wireguard-{position}"
|
|
||||||
safe = public_key.rstrip("=").replace("+", "-").replace("/", "_")
|
|
||||||
key = f"wireguard-{row.get('if') or 'wg'}-{safe[:_WG_KEY_PREFIX]}"
|
|
||||||
if key in taken:
|
|
||||||
key = f"wireguard-{row.get('if') or 'wg'}-{safe}"
|
|
||||||
return key
|
|
||||||
|
|
||||||
|
|
||||||
class OPNsenseDriver(
|
class OPNsenseDriver(
|
||||||
OPNsensePingMixin,
|
OPNsensePingMixin,
|
||||||
ConfigSyncMixin,
|
ConfigSyncMixin,
|
||||||
@@ -2189,51 +2168,7 @@ class OPNsenseDriver(
|
|||||||
|
|
||||||
# ── WireGuard ──────────────────────────────────────────────────────
|
# ── WireGuard ──────────────────────────────────────────────────────
|
||||||
try:
|
try:
|
||||||
# /api/wireguard/service/show returns structured JSON:
|
tunnels.update(wireguard_tunnels(self._get("/api/wireguard/service/show")))
|
||||||
# {"total": N, "rows": [
|
|
||||||
# {"if":"wg0", "type":"interface", ...},
|
|
||||||
# {"if":"wg0", "type":"peer", "public-key":"...",
|
|
||||||
# "endpoint":"1.2.3.4:51820", "transfer-rx":N, "transfer-tx":N,
|
|
||||||
# "name":"office", "peer-status":"online",
|
|
||||||
# "latest-handshake-age":45, "ifname":"office"}, ...
|
|
||||||
# ]}
|
|
||||||
data = self._get("/api/wireguard/service/show")
|
|
||||||
rows = data.get("rows", []) if isinstance(data, dict) else []
|
|
||||||
|
|
||||||
wg_idx = 0
|
|
||||||
for row in rows:
|
|
||||||
if row.get("type") != "peer":
|
|
||||||
continue
|
|
||||||
wg_idx += 1
|
|
||||||
|
|
||||||
endpoint = (row.get("endpoint") or "").strip()
|
|
||||||
if endpoint and endpoint != "(none)":
|
|
||||||
remote_ip = endpoint.rsplit(":", 1)[0].strip("[]")
|
|
||||||
else:
|
|
||||||
remote_ip = ""
|
|
||||||
|
|
||||||
is_up = row.get("peer-status") == "online"
|
|
||||||
bytes_in = int(row.get("transfer-rx") or 0)
|
|
||||||
bytes_out = int(row.get("transfer-tx") or 0)
|
|
||||||
hs_age = row.get("latest-handshake-age")
|
|
||||||
uptime = int(hs_age) if hs_age else 0
|
|
||||||
|
|
||||||
description = (
|
|
||||||
(row.get("name") or "").strip()
|
|
||||||
or (row.get("ifname") or "").strip()
|
|
||||||
or f"wg-peer-{wg_idx}"
|
|
||||||
)
|
|
||||||
|
|
||||||
tunnels[_wireguard_key(row, wg_idx, tunnels)] = {
|
|
||||||
"type": "WireGuard",
|
|
||||||
"local_endpoint": "",
|
|
||||||
"remote_endpoint": remote_ip,
|
|
||||||
"is_up": is_up,
|
|
||||||
"uptime": uptime,
|
|
||||||
"bytes_in": bytes_in,
|
|
||||||
"bytes_out": bytes_out,
|
|
||||||
"description": description,
|
|
||||||
}
|
|
||||||
except Exception as exc:
|
except Exception as exc:
|
||||||
logger.debug("WireGuard status fetch failed (plugin not installed?): %s", exc)
|
logger.debug("WireGuard status fetch failed (plugin not installed?): %s", exc)
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,73 @@
|
|||||||
|
"""WireGuard peers as VPN tunnels. Pure: no I/O.
|
||||||
|
|
||||||
|
``/api/wireguard/service/show`` lists every WireGuard interface and peer::
|
||||||
|
|
||||||
|
{"total": N, "rows": [
|
||||||
|
{"if": "wg0", "type": "interface", ...},
|
||||||
|
{"if": "wg0", "type": "peer", "public-key": "...",
|
||||||
|
"endpoint": "1.2.3.4:51820", "transfer-rx": N, "transfer-tx": N,
|
||||||
|
"name": "office", "peer-status": "online",
|
||||||
|
"latest-handshake-age": 45, "ifname": "office"}, ...
|
||||||
|
]}
|
||||||
|
|
||||||
|
**The key is the peer, not its place.** It used to be a running counter, so a
|
||||||
|
peer added ahead of another renumbered it, and whatever a consumer keeps per
|
||||||
|
key (netOrk's site link, its tunnel check) moved to a different peer (#32).
|
||||||
|
Now it is the interface and the start of the public key, as napalm-linux does
|
||||||
|
it, with the base64 made URL-safe because the key is used as a path segment.
|
||||||
|
Two peers sharing the prefix get the whole key; a row without a public key
|
||||||
|
keeps its position.
|
||||||
|
"""
|
||||||
|
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
from typing import Any
|
||||||
|
|
||||||
|
#: How much of a public key names a peer, the same length as napalm-linux uses.
|
||||||
|
_KEY_PREFIX = 8
|
||||||
|
|
||||||
|
|
||||||
|
def _key(row: dict[str, Any], position: int, taken: dict[str, Any]) -> str:
|
||||||
|
public_key = (row.get("public-key") or "").strip()
|
||||||
|
if not public_key:
|
||||||
|
return f"wireguard-{position}"
|
||||||
|
safe = public_key.rstrip("=").replace("+", "-").replace("/", "_")
|
||||||
|
key = f"wireguard-{row.get('if') or 'wg'}-{safe[:_KEY_PREFIX]}"
|
||||||
|
if key in taken:
|
||||||
|
key = f"wireguard-{row.get('if') or 'wg'}-{safe}"
|
||||||
|
return key
|
||||||
|
|
||||||
|
|
||||||
|
def _remote_ip(row: dict[str, Any]) -> str:
|
||||||
|
endpoint = (row.get("endpoint") or "").strip()
|
||||||
|
if not endpoint or endpoint == "(none)":
|
||||||
|
return ""
|
||||||
|
return endpoint.rsplit(":", 1)[0].strip("[]")
|
||||||
|
|
||||||
|
|
||||||
|
def _tunnel(row: dict[str, Any], position: int) -> dict[str, Any]:
|
||||||
|
handshake_age = row.get("latest-handshake-age")
|
||||||
|
return {
|
||||||
|
"type": "WireGuard",
|
||||||
|
"local_endpoint": "",
|
||||||
|
"remote_endpoint": _remote_ip(row),
|
||||||
|
"is_up": row.get("peer-status") == "online",
|
||||||
|
"uptime": int(handshake_age) if handshake_age else 0,
|
||||||
|
"bytes_in": int(row.get("transfer-rx") or 0),
|
||||||
|
"bytes_out": int(row.get("transfer-tx") or 0),
|
||||||
|
"description": (
|
||||||
|
(row.get("name") or "").strip()
|
||||||
|
or (row.get("ifname") or "").strip()
|
||||||
|
or f"wg-peer-{position}"
|
||||||
|
),
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def wireguard_tunnels(show: Any) -> dict[str, dict[str, Any]]:
|
||||||
|
"""One ``VPNTunnelDict`` per peer in ``/api/wireguard/service/show``, by tunnel key."""
|
||||||
|
rows = show.get("rows", []) if isinstance(show, dict) else []
|
||||||
|
tunnels: dict[str, dict[str, Any]] = {}
|
||||||
|
peers = [row for row in rows if isinstance(row, dict) and row.get("type") == "peer"]
|
||||||
|
for position, row in enumerate(peers, start=1):
|
||||||
|
tunnels[_key(row, position, tunnels)] = _tunnel(row, position)
|
||||||
|
return tunnels
|
||||||
@@ -24,7 +24,7 @@
|
|||||||
"napalm_opnsense/opnsense.py::OPNsenseDriver.get_lldp_neighbors_detail": 12,
|
"napalm_opnsense/opnsense.py::OPNsenseDriver.get_lldp_neighbors_detail": 12,
|
||||||
"napalm_opnsense/opnsense.py::OPNsenseDriver.get_networks": 25,
|
"napalm_opnsense/opnsense.py::OPNsenseDriver.get_networks": 25,
|
||||||
"napalm_opnsense/opnsense.py::OPNsenseDriver.get_route_to": 23,
|
"napalm_opnsense/opnsense.py::OPNsenseDriver.get_route_to": 23,
|
||||||
"napalm_opnsense/opnsense.py::OPNsenseDriver.get_vpn_tunnels": 60,
|
"napalm_opnsense/opnsense.py::OPNsenseDriver.get_vpn_tunnels": 47,
|
||||||
"napalm_opnsense/opnsense.py::OPNsenseDriver.load_merge_candidate": 13,
|
"napalm_opnsense/opnsense.py::OPNsenseDriver.load_merge_candidate": 13,
|
||||||
"napalm_opnsense/opnsense.py::OPNsenseDriver.sync_dns_zone": 20,
|
"napalm_opnsense/opnsense.py::OPNsenseDriver.sync_dns_zone": 20,
|
||||||
"napalm_opnsense/port_forwards.py::_forwards_of": 12,
|
"napalm_opnsense/port_forwards.py::_forwards_of": 12,
|
||||||
@@ -43,7 +43,7 @@
|
|||||||
"napalm_opnsense/opnsense.py::OPNsenseDriver.get_interfaces_ip": 68,
|
"napalm_opnsense/opnsense.py::OPNsenseDriver.get_interfaces_ip": 68,
|
||||||
"napalm_opnsense/opnsense.py::OPNsenseDriver.get_networks": 85,
|
"napalm_opnsense/opnsense.py::OPNsenseDriver.get_networks": 85,
|
||||||
"napalm_opnsense/opnsense.py::OPNsenseDriver.get_route_to": 85,
|
"napalm_opnsense/opnsense.py::OPNsenseDriver.get_route_to": 85,
|
||||||
"napalm_opnsense/opnsense.py::OPNsenseDriver.get_vpn_tunnels": 189,
|
"napalm_opnsense/opnsense.py::OPNsenseDriver.get_vpn_tunnels": 145,
|
||||||
"napalm_opnsense/opnsense.py::OPNsenseDriver.load_merge_candidate": 56,
|
"napalm_opnsense/opnsense.py::OPNsenseDriver.load_merge_candidate": 56,
|
||||||
"napalm_opnsense/opnsense.py::OPNsenseDriver.sync_dns_zone": 112
|
"napalm_opnsense/opnsense.py::OPNsenseDriver.sync_dns_zone": 112
|
||||||
},
|
},
|
||||||
@@ -57,7 +57,7 @@
|
|||||||
"napalm_opnsense.resolver_mixin"
|
"napalm_opnsense.resolver_mixin"
|
||||||
],
|
],
|
||||||
"python_file_lines": {
|
"python_file_lines": {
|
||||||
"napalm_opnsense/opnsense.py": 3125
|
"napalm_opnsense/opnsense.py": 3082
|
||||||
},
|
},
|
||||||
"untyped_public": [
|
"untyped_public": [
|
||||||
"napalm_opnsense/opnsense.py::OPNsenseDriver.get_snmp_config"
|
"napalm_opnsense/opnsense.py::OPNsenseDriver.get_snmp_config"
|
||||||
|
|||||||
Reference in new issue
Block a user