feat: report what listens, and which OPNsense service it is
CI / test (3.10) (push) Successful in 40s
CI / test (3.11) (push) Successful in 30s
CI / test (3.12) (push) Successful in 31s
CI / test (3.10) (pull_request) Successful in 29s
CI / test (3.11) (pull_request) Successful in 28s
CI / test (3.12) (pull_request) Successful in 30s

get_listening_sockets reads /api/diagnostics/interface/get_socket_statistics:
netstat's sockets with sockstat's user, command and PID, collected by
configd as root, so no SSH is needed. A listening socket is one without a
peer (*:*); "*" is the any-address of the socket's family.

Which service: the socket's unit is a name of the firewall's own service
list, so netOrk can match it to the service. The command (cut to ten
characters by FreeBSD) matches a service name, the start of one, or one
of the daemons whose service is called otherwise (sshd is openssh, the
FRR daemons are frr, kea-ctrl-agent is kea-dhcp). lighttpd is the web UI,
or the captive portal when it runs as www. WireGuard's sockets belong to
the kernel and are told by the listen ports of /api/wireguard/service/show.

The fixture is a real OPNsense 26.7.5 firewall's answer, cut down, with
documentation addresses. For netOrk#673.
This commit is contained in:
Christian Manivong
2026-10-07 07:20:27 +02:00
parent 8ce7ea52e7
commit 2fb1226734
5 changed files with 1233 additions and 0 deletions
+22
View File
@@ -53,8 +53,10 @@ import requests
from requests.exceptions import RequestException
from napalm_device_types import FingerprintRule, FirewallDriver
from napalm_device_types.models import ListeningSocketsDict
from napalm.base.exceptions import ConnectionException, ConnectionClosedException, MergeConfigException
from napalm_opnsense.listening import parse_socket_statistics, wireguard_listen_ports
from napalm_opnsense.ping_mixin import OPNsensePingMixin
from napalm_opnsense.port_forwards import alias_index, port_forwards, wan_interfaces
@@ -1789,6 +1791,26 @@ class OPNsenseDriver(OPNsensePingMixin, FirewallDriver):
})
return sorted(result, key=lambda x: x["name"].lower())
def get_listening_sockets(self) -> ListeningSocketsDict:
"""Every listening TCP and bound UDP socket, with the OPNsense service behind it.
Read from ``/api/diagnostics/interface/get_socket_statistics``, which
configd collects as root, so every socket names its process where it has
one (``attributed``). Which service: :mod:`napalm_opnsense.listening`.
"""
statistics = self._get("/api/diagnostics/interface/get_socket_statistics")
services = [service["name"] for service in self.get_services()]
try:
wireguard = wireguard_listen_ports(self._get("/api/wireguard/service/show"))
except Exception as exc:
# No WireGuard here: its kernel sockets stay nobody's.
logger.debug("WireGuard listen ports not read: %s", exc)
wireguard = set()
return {
"attributed": True,
"sockets": parse_socket_statistics(statistics, services, wireguard),
}
def manage_service(self, name: str, action: str) -> dict[str, Any]:
"""Execute a lifecycle action on an OPNsense service.