Files
Christian Manivong f934cc0cfa
CI / test (3.10) (push) Failing after 19s
CI / test (3.11) (push) Failing after 8s
CI / test (3.12) (push) Failing after 11s
CI / test (3.9) (push) Failing after 8s
feat(ping): implement ping and a batched ping_sweep over the diagnostics API
OPNsense has no synchronous ping endpoint. /api/diagnostics/ping is a job API —
create, start, read statistics, stop, remove — so a single ping costs five
requests and roughly a second of waiting, which makes the generic per-host
sweep from napalm-device-types unusable for a whole subnet.

The override exploits what the job API does offer instead: jobs are
independent and run on the firewall in parallel, and search_jobs reports all of
them in one response. A batch (32 by default) is created and started, waited
for once, harvested with a single request and then cleaned up, so waiting time
per batch is constant rather than linear in hosts. PING_SWEEP_MAX_TARGETS
bounds the sweep as a whole — this runs on production firewalls.

Two details the API forces: results are polled, because search_jobs signals the
running ping with SIGINFO and then parses whatever it has written so far, so
the first read of a healthy host can still show zero probes; and the model's
root node is read from /get rather than hardcoded, so a rename in a future
OPNsense release cannot silently break job creation.

Tested against mocked API responses only — no live device is reachable at the
moment, so the endpoint shapes come from the OPNsense sources (PingController,
scripts/interfaces/ping.py).
2026-08-13 16:51:05 +07:00

7.2 KiB
Raw Permalink Blame History

napalm-opnsense

NAPALM community driver for OPNsense firewalls (read-only, via REST API).

Tested devices

Model OPNsense Version Tested
OPNsense (virtual/bare metal) 24.7 ✅

Additional OPNsense versions should work — contributions welcome.

Requirements

Dependency Minimum version
Python 3.9
NAPALM 4.0
requests 2.28

Installation

pip install napalm-opnsense

Or from source:

git clone https://github.com/napalm-automation-community/napalm-opnsense
cd napalm-opnsense
pip install -e .

Quick start

from napalm import get_network_driver

driver = get_network_driver("opnsense")
with driver(
    "192.168.1.1",
    "my_api_key",
    "my_api_secret",
    optional_args={"verify": False},
) as device:
    facts = device.get_facts()
    print(facts)

Authentication

OPNsense uses API key/secret pairs instead of username/password. Generate a key pair in the OPNsense GUI under System → Access → Users → edit user → API keys.

Pass the credentials via optional_args:

optional_args={
    "api_key": "your-api-key",
    "api_secret": "your-api-secret",
    "verify": False,  # set to a CA bundle path or True in production
}

Alternatively, pass the key/secret as the positional username/password arguments.

Implemented getters

Getter Status OPNsense API Endpoint
get_facts ✅ GET /api/core/system/status
get_interfaces ✅ GET /api/interfaces/overview/export
get_interfaces_ip ✅ GET /api/interfaces/addresses/export
get_interfaces_counters ✅ GET /api/diagnostics/interface/get_interface_statistics
get_arp_table ✅ GET /api/diagnostics/interface/get_arp
get_ipv6_neighbors_table ✅ GET /api/diagnostics/interface/get_ndp
get_route_to ✅ GET /api/diagnostics/interface/get_routes
get_environment ✅ GET /api/diagnostics/system/system_resources + system_temperature
get_lldp_neighbors ✅ ¹ GET /api/lldpd/service/neighbor
get_lldp_neighbors_detail ✅ ¹ GET /api/lldpd/service/neighbor
get_ntp_servers ✅ GET /api/ntpd/service/status
get_config ✅ GET /api/core/backup/download/this (XML)
is_alive ✅ TCP socket check
get_bgp_neighbors ✅ ² GET /api/quagga/bgp/get + GET /api/quagga/diagnostics/bgpneighbors
get_vlans ✅ GET /api/interfaces/vlan_settings/search_item
get_mac_address_table ❌ Not applicable (firewall, no L2 switching)
ping ✅ POST /api/diagnostics/ping/set + start + search_jobs + stop/remove
ping_sweep ✅ ³ same endpoints, one batch of parallel jobs at a time

¹ Requires the os-lldpd plugin. Returns empty dict if the plugin is not installed. ² Requires the os-frr (FRR/Quagga) plugin. Returns empty dict if the plugin is not installed or FRR is not running. ³ Overrides the generic per-host loop from napalm-device-types.

Ping

OPNsense has no synchronous ping endpoint: /api/diagnostics/ping is a job API — create, start, read statistics, stop, remove. A single ping therefore costs five requests and about a second of waiting, which makes the generic sequential sweep from napalm-device-types unusable for a whole subnet.

ping_sweep exploits what the job API does offer instead: jobs are independent and run on the firewall in parallel, and search_jobs reports all of them in one response. It creates and starts a batch (PING_SWEEP_BATCH_SIZE, default 32), waits once, reads every result with a single request, then cleans the batch up — waiting time per batch is constant rather than linear in hosts. PING_SWEEP_MAX_TARGETS (default 512) bounds the sweep as a whole; both are deliberately conservative, since this runs on production firewalls.

Results are polled rather than read once: search_jobs signals the running ping with SIGINFO and parses whatever it has written so far, so the statistics line lands in the log slightly after the request that triggered it.

ttl and vrf are accepted for NAPALM compatibility and ignored — the API has no equivalent.

Config management

OPNsense does not expose a single generic "push config" endpoint. Config is managed per-module via separate API controllers. This driver implements config management for static routes via /api/routes/routes/.

Why routes? Routes are the most common network-automation target on a firewall, and the OPNsense routes API provides full CRUD operations.

Supported methods

Method OPNsense API
load_merge_candidate(config=...) stages routes in memory
compare_config() diffs against GET /api/routes/routes/searchroute
commit_config() snapshots backup → POST /api/routes/routes/addroute × n → reconfigure
discard_config() clears staged candidate
rollback() POST /api/core/backup/revert_backup/{id} — restores the config.xml snapshot taken before the last commit

Config format

The load_merge_candidate config parameter must be a JSON array of route objects, each with network and gateway keys. gateway must be the name of an existing OPNsense gateway (as configured under System → Gateways → Configuration), not an IP address.

[
  {
    "network": "10.0.0.0/8",
    "gateway": "WAN_GW",
    "descr": "Corporate internal",
    "disabled": "0"
  },
  {
    "network": "0.0.0.0/0",
    "gateway": "WAN_GW",
    "descr": "Default route"
  }
]

Example

import json

driver = get_network_driver("opnsense")
d = driver("192.168.1.1", "user", "pass", optional_args={"api_key": "k", "api_secret": "s"})
d.open()

routes = json.dumps([{"network": "10.0.0.0/8", "gateway": "WAN_GW"}])
d.load_merge_candidate(config=routes)

print(d.compare_config())   # unified diff
d.commit_config()           # applies routes and calls reconfigure
d.rollback()                # removes the routes just added
d.close()

Limitations

  • load_replace_candidate is not supported (no XML upload endpoint in the JSON API).
  • Non-route config (firewall rules, DHCP, DNS, interfaces, …) must be managed via OPNsense module-specific controllers — outside the scope of this driver.
  • rollback() reverts the entire config.xml to the pre-commit state (not just the routes). OPNsense's backup API has no partial-restore capability.
  • Rollback uses the backup snapshot taken at commit_config() time. If no commit was made in the current session, the most recent available backup is used as a fallback.

Optional arguments

Argument Default Description
api_key username OPNsense API key
api_secret password OPNsense API secret
base_url https://<hostname> Override the base URL
verify True TLS certificate verification (path or bool)

Development

pip install -e ".[dev]"
pytest tests/

CI

This project includes a GitHub Actions workflow that:

  • Runs unit tests across Python 3.9–3.12
  • Builds sdist and wheel
  • Uploads build artifacts

See .github/workflows/ci.yml.

License

Apache 2.0 — see LICENSE.