get_vlans() liefert stillschweigend synthetische Daten, wenn das bridge-Binary fehlt #1

Closed
opened 2026-08-17 13:00:24 +00:00 by christianmanivong · 2 comments
Owner

Problem

get_vlans() stützt sich primär auf bridge vlan show:

bridge_out = self._send_command("bridge vlan show")

Auf OpenWrt-Geräten, die nur BusyBox mitbringen (ohne die Pakete bridge/ip-full), existiert das Binary nicht. _send_command liefert dann die Shell-Fehlermeldung zurück:

ash: bridge: not found

Der Parser findet darin nichts, wirft aber auch keinen Fehler. Übrig bleibt nur der Fallback über ip link show, der pro VLAN-Sub-Interface einträgt:

# sub-interface itself -> untagged egress
vlans[vlan_id]["untagged"].append(iface)   # br-ap.10
# parent -> tagged trunk
vlans[vlan_id]["tagged"].append(parent)    # br-ap

Auswirkung

Das Ergebnis sieht aus wie echte Port-Membership, ist aber nur eine Umformulierung der Sub-Interface-Topologie. Gemessen auf zwei Sophos AP100 (BusyBox v1.37.0):

"10": { "name": "MGMT", "tagged": ["br-ap"], "untagged": ["br-ap.10"] },
"30": { "name": "USERS", "tagged": ["br-ap"], "untagged": ["br-ap.30"] }

Die tatsächliche Membership auf dem Uplink-Port ist eine völlig andere:

network.apbr_vlan10.ports='eth0:u*'   <-- untagged + PVID
network.apbr_vlan30.ports='eth0:t'

eth0 — der einzige Port, um den es geht — taucht in der Ausgabe überhaupt nicht auf. Die Daten landen so in Device.vlans_snapshot und werden in NetOrk als Port-Membership angezeigt.

Lösungsvorschlag

Zusätzlich uci show network nach bridge-vlan-Sektionen parsen — die Sektionen tragen die Membership explizit und sind ohne bridge-Binary lesbar:

network.<sec>.device='br-ap'
network.<sec>.vlan='10'
network.<sec>.ports='eth0:u*'

Port-Suffixe: :t = tagged, :u = untagged, * = PVID, ohne Suffix = untagged. Die UCI-Daten sollten Vorrang vor dem ip link show-Fallback haben, weil sie den konfigurierten Soll-Zustand beschreiben.

Zusammenhang: NetOrk christianmanivong/netork#76 braucht diese Daten für den Drift-Vergleich des VLAN-Taggings.

## Problem `get_vlans()` stützt sich primär auf `bridge vlan show`: ```python bridge_out = self._send_command("bridge vlan show") ``` Auf OpenWrt-Geräten, die nur BusyBox mitbringen (ohne die Pakete `bridge`/`ip-full`), existiert das Binary nicht. `_send_command` liefert dann die Shell-Fehlermeldung zurück: ``` ash: bridge: not found ``` Der Parser findet darin nichts, wirft aber auch keinen Fehler. Übrig bleibt nur der Fallback über `ip link show`, der pro VLAN-Sub-Interface einträgt: ```python # sub-interface itself -> untagged egress vlans[vlan_id]["untagged"].append(iface) # br-ap.10 # parent -> tagged trunk vlans[vlan_id]["tagged"].append(parent) # br-ap ``` ## Auswirkung Das Ergebnis sieht aus wie echte Port-Membership, ist aber nur eine Umformulierung der Sub-Interface-Topologie. Gemessen auf zwei Sophos AP100 (BusyBox v1.37.0): ```json "10": { "name": "MGMT", "tagged": ["br-ap"], "untagged": ["br-ap.10"] }, "30": { "name": "USERS", "tagged": ["br-ap"], "untagged": ["br-ap.30"] } ``` Die tatsächliche Membership auf dem Uplink-Port ist eine völlig andere: ``` network.apbr_vlan10.ports='eth0:u*' <-- untagged + PVID network.apbr_vlan30.ports='eth0:t' ``` `eth0` — der einzige Port, um den es geht — taucht in der Ausgabe überhaupt nicht auf. Die Daten landen so in `Device.vlans_snapshot` und werden in NetOrk als Port-Membership angezeigt. ## Lösungsvorschlag Zusätzlich `uci show network` nach `bridge-vlan`-Sektionen parsen — die Sektionen tragen die Membership explizit und sind ohne `bridge`-Binary lesbar: ``` network.<sec>.device='br-ap' network.<sec>.vlan='10' network.<sec>.ports='eth0:u*' ``` Port-Suffixe: `:t` = tagged, `:u` = untagged, `*` = PVID, ohne Suffix = untagged. Die UCI-Daten sollten Vorrang vor dem `ip link show`-Fallback haben, weil sie den konfigurierten Soll-Zustand beschreiben. Zusammenhang: NetOrk christianmanivong/netork#76 braucht diese Daten für den Drift-Vergleich des VLAN-Taggings.
Author
Owner

Umgesetzt

get_vlans() wertet jetzt zusätzlich die UCI-bridge-vlan-Sektionen aus.

  • _uci_sections_of_type(uci_out, "bridge-vlan") sammelt Sektionen über die Typ-Deklaration und erkennt dadurch benannte Sektionen (network.apbr_vlan10) genauso wie anonyme (network.@bridge-vlan[0]). Vorher matchte die Regex nur die anonyme Form — auf den betroffenen Geräten wurde die Sektion also nicht einmal für die Namensauflösung gefunden.
  • _parse_port_spec übersetzt die Suffixe: :t → tagged, :u* / :* / ohne Suffix → untagged.
  • _split_uci_list / _uci_scalar trennen Listen-Optionen (ports='lan1:t' 'lan2:t') von Skalaren. Wichtig: die Rohwerte müssen inklusive Quotes durchgereicht werden, sonst zerfällt die Listenstruktur.
  • Präzedenz: bridge vlan show gewinnt, wo es Daten liefert — das ist der Zustand, den der Kernel tatsächlich durchsetzt. UCI füllt nur Ports auf, die dort nicht vorkommen.

Damit erscheint eth0 mit korrektem Tagging in tagged/untagged, was NetOrk christianmanivong/netork#76 für den Drift-Vergleich braucht.

Tests: neue Klasse TestGetVlansWithoutBridgeBinary — untagged/PVID- und tagged-Membership, benannte Sektionen, Multi-Port-Listen, Vorrang der Runtime-Daten, und dass die Shell-Fehlermeldung nicht als Port geparst wird.

## Umgesetzt `get_vlans()` wertet jetzt zusätzlich die UCI-`bridge-vlan`-Sektionen aus. - `_uci_sections_of_type(uci_out, "bridge-vlan")` sammelt Sektionen über die Typ-Deklaration und erkennt dadurch **benannte** Sektionen (`network.apbr_vlan10`) genauso wie anonyme (`network.@bridge-vlan[0]`). Vorher matchte die Regex nur die anonyme Form — auf den betroffenen Geräten wurde die Sektion also nicht einmal für die Namensauflösung gefunden. - `_parse_port_spec` übersetzt die Suffixe: `:t` → tagged, `:u*` / `:*` / ohne Suffix → untagged. - `_split_uci_list` / `_uci_scalar` trennen Listen-Optionen (`ports='lan1:t' 'lan2:t'`) von Skalaren. Wichtig: die Rohwerte müssen inklusive Quotes durchgereicht werden, sonst zerfällt die Listenstruktur. - Präzedenz: `bridge vlan show` gewinnt, wo es Daten liefert — das ist der Zustand, den der Kernel tatsächlich durchsetzt. UCI füllt nur Ports auf, die dort nicht vorkommen. Damit erscheint `eth0` mit korrektem Tagging in `tagged`/`untagged`, was NetOrk christianmanivong/netork#76 für den Drift-Vergleich braucht. Tests: neue Klasse `TestGetVlansWithoutBridgeBinary` — untagged/PVID- und tagged-Membership, benannte Sektionen, Multi-Port-Listen, Vorrang der Runtime-Daten, und dass die Shell-Fehlermeldung nicht als Port geparst wird.
Author
Owner

Behoben in c686fac.

Nach dem Deploy gegen ap-see-1 (Sophos AP100, OpenWrt 25.12.5) verifiziert — eth0 erscheint jetzt mit korrektem Tagging im Snapshot:

"10": { "name": "MGMT",   "tagged": ["br-ap"],           "untagged": ["eth0", "br-ap.10"] },
"30": { "name": "USERS",  "tagged": ["eth0", "br-ap"],   "untagged": ["br-ap.30"] },
"40": { "name": "GUESTS", "tagged": ["eth0", "br-ap"],   "untagged": ["br-ap.40"] }

Deckt sich exakt mit der UCI-Konfiguration des Geräts (eth0:u* für VLAN 10, eth0:t für den Rest).

Behoben in c686fac. Nach dem Deploy gegen ap-see-1 (Sophos AP100, OpenWrt 25.12.5) verifiziert — `eth0` erscheint jetzt mit korrektem Tagging im Snapshot: ```json "10": { "name": "MGMT", "tagged": ["br-ap"], "untagged": ["eth0", "br-ap.10"] }, "30": { "name": "USERS", "tagged": ["eth0", "br-ap"], "untagged": ["br-ap.30"] }, "40": { "name": "GUESTS", "tagged": ["eth0", "br-ap"], "untagged": ["br-ap.40"] } ``` Deckt sich exakt mit der UCI-Konfiguration des Geräts (`eth0:u*` für VLAN 10, `eth0:t` für den Rest).
Sign in to join this conversation.
No labels
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: NAPALM/napalm-openwrt#1