ping: settings are posted one level above where the API expects them, so every ping fails #3

Closed
opened 2026-08-22 08:40:45 +00:00 by christianmanivong · 0 comments
Owner

Symptom

Every ping() and every ping_sweep() probe fails against a live OPNsense:

ping job creation failed for 10.30.0.1:
{'result': 'failed', 'validations': {'ping.settings.hostname': 'A value is required.'}}

Found 2026-08-22 through netOrk, where a LAN scan from a firewall reported 254 hosts scanned and 0 alive, with no error at all.

Root cause

_ping_model_node() reads the model from GET /api/diagnostics/ping/get and takes the single dict-valued key as the node to post under. The real response nests two levels:

{"ping": {"settings": {"hostname": "", "fam": {"ip": {...}, "ip6": {...}},
                       "source_address": "", "packetsize": "", "disable_frag": "0",
                       "interval": "", "description": ""}}}

So the helper returned "ping" and _ping_job_create posted {"ping": {hostname: …}}, putting the fields where OPNsense expects the settings node. The hostname never arrived, and the API said so on every single call.

Why the tests passed

FakePingAPI in tests/unit/test_ping.py answered /get with a one-level model, {node: {"hostname": "", "fam": "ip"}}, and read the posted payload back through the same assumption. The fake agreed with the code about a shape neither shares with the device.

Fix

_ping_model_path() returns the whole chain of single-dict wrappers (("ping", "settings")), stopping at the first level with more than one key — i.e. the field level, where fam is a dict too and descending further would land inside a form field. _wrap_in_model() nests the settings accordingly. Both the two-level and a hypothetical one-level model work, and the default when /get cannot be read is what current firmware ships.

Verified against a live OPNsense: job accepted ({"result": "ok", "uuid": …}), 10.30.0.1 answered 3 of 3 at 0.116 ms.

## Symptom Every `ping()` and every `ping_sweep()` probe fails against a live OPNsense: ``` ping job creation failed for 10.30.0.1: {'result': 'failed', 'validations': {'ping.settings.hostname': 'A value is required.'}} ``` Found 2026-08-22 through netOrk, where a LAN scan from a firewall reported 254 hosts scanned and 0 alive, with no error at all. ## Root cause `_ping_model_node()` reads the model from `GET /api/diagnostics/ping/get` and takes the single dict-valued key as the node to post under. The real response nests **two** levels: ```json {"ping": {"settings": {"hostname": "", "fam": {"ip": {...}, "ip6": {...}}, "source_address": "", "packetsize": "", "disable_frag": "0", "interval": "", "description": ""}}} ``` So the helper returned `"ping"` and `_ping_job_create` posted `{"ping": {hostname: …}}`, putting the fields where OPNsense expects the `settings` node. The hostname never arrived, and the API said so on every single call. ## Why the tests passed `FakePingAPI` in `tests/unit/test_ping.py` answered `/get` with a **one-level** model, `{node: {"hostname": "", "fam": "ip"}}`, and read the posted payload back through the same assumption. The fake agreed with the code about a shape neither shares with the device. ## Fix `_ping_model_path()` returns the whole chain of single-dict wrappers (`("ping", "settings")`), stopping at the first level with more than one key — i.e. the field level, where `fam` is a dict too and descending further would land inside a form field. `_wrap_in_model()` nests the settings accordingly. Both the two-level and a hypothetical one-level model work, and the default when `/get` cannot be read is what current firmware ships. Verified against a live OPNsense: job accepted (`{"result": "ok", "uuid": …}`), `10.30.0.1` answered 3 of 3 at 0.116 ms.
Sign in to join this conversation.
No labels
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: NAPALM/napalm-opnsense#3