Skip to content

Commit 65765a7

Browse files
committed
fix(agent): parse lldpd 1.0.x keyed-array LLDP neighbors
lldpd 1.0.20 (OpenWrt 25.x) emits each lldp.interface array entry as a single-key object keyed by interface name ({"lan1": {...}}) with no "name" field inside. The parser only understood entries with a "name" field (and its fixture modeled that shape), so every neighbor degraded to {"port": ""} and LLDP evidence was silently unusable. Descend into single-key entries using the key as the interface name, guarding against real field names. Adds a fixture captured from real lldpd 1.0.20 output. References #1370
1 parent f70fa1c commit 65765a7

2 files changed

Lines changed: 88 additions & 0 deletions

File tree

‎agent/probe/lldp.go‎

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -106,6 +106,22 @@ func parseLldpEntry(raw json.RawMessage, fallbackName string) LldpNeighbor {
106106
// Best-effort: un campo de tipo inesperado (chassis como cadena, port
107107
// como número…) no invalida el resto de la entrada.
108108
_ = json.Unmarshal(raw, &e)
109+
// lldpd 1.0.20 (OpenWrt 25.x, #1370): cada entrada del array puede ser
110+
// un objeto de UNA clave cuyo nombre es la interfaz ({"lan1": {...}}),
111+
// sin campo "name" dentro. Descender usando la clave como nombre de
112+
// puerto. Las claves que son campos conocidos no son interfaces.
113+
if e.Name == "" && e.Chassis == nil {
114+
var byName map[string]json.RawMessage
115+
if err := json.Unmarshal(raw, &byName); err == nil && len(byName) == 1 {
116+
for name, inner := range byName {
117+
switch name {
118+
case "name", "chassis", "port", "via", "rid", "age":
119+
default:
120+
return parseLldpEntry(inner, name)
121+
}
122+
}
123+
}
124+
}
109125
n := LldpNeighbor{Port: e.Name}
110126
if n.Port == "" {
111127
n.Port = fallbackName

‎agent/probe/lldp_test.go‎

Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -148,6 +148,78 @@ func TestParseLldpNeighborsDefensivo(t *testing.T) {
148148
}
149149
}
150150

151+
// Fixture real de lldpd 1.0.20 (OpenWrt 25.12, #1370): el array de interface
152+
// lleva objetos de UNA clave con el nombre de la interfaz, sin campo "name".
153+
const lldpcliKeyedArrayFixture = `{
154+
"lldp": {
155+
"interface": [
156+
{
157+
"lan1": {
158+
"via": "LLDP",
159+
"rid": "1",
160+
"age": "0 day, 00:06:34",
161+
"chassis": {
162+
"core-switch": {
163+
"id": {"type": "mac", "value": "78:d8:00:32:31:49"},
164+
"descr": "Managed switch",
165+
"mgmt-ip": ["192.168.8.6"],
166+
"capability": [
167+
{"type": "Bridge", "enabled": true},
168+
{"type": "Router", "enabled": false}
169+
]
170+
}
171+
},
172+
"port": {
173+
"id": {"type": "ifname", "value": "3"},
174+
"ttl": "120"
175+
}
176+
}
177+
},
178+
{
179+
"lan1": {
180+
"via": "LLDP",
181+
"rid": "2",
182+
"age": "0 day, 00:00:59",
183+
"chassis": {
184+
"ap-atico": {
185+
"id": {"type": "mac", "value": "9c:9d:7e:54:ef:b8"},
186+
"capability": [
187+
{"type": "Wlan", "enabled": true}
188+
]
189+
}
190+
},
191+
"port": {
192+
"id": {"type": "mac", "value": "9c:9d:7e:54:ef:b8"},
193+
"descr": "lan1",
194+
"ttl": "120"
195+
}
196+
}
197+
}
198+
]
199+
}
200+
}`
201+
202+
func TestParseLldpNeighborsKeyedArray(t *testing.T) {
203+
nbs, err := ParseLldpNeighbors([]byte(lldpcliKeyedArrayFixture))
204+
if err != nil {
205+
t.Fatalf("parse: %v", err)
206+
}
207+
if len(nbs) != 2 {
208+
t.Fatalf("vecinos: %d", len(nbs))
209+
}
210+
got := nbs[0]
211+
if got.Port != "lan1" || got.Chassis != "core-switch" || got.ChassisMac != "78:D8:00:32:31:49" {
212+
t.Fatalf("n[0]: %+v", got)
213+
}
214+
if got.Mgmt != "192.168.8.6" || !reflect.DeepEqual(got.Caps, []string{"Bridge"}) {
215+
t.Fatalf("n[0] mgmt/caps: %+v", got)
216+
}
217+
got = nbs[1]
218+
if got.Port != "lan1" || got.Chassis != "ap-atico" || got.ChassisMac != "9C:9D:7E:54:EF:B8" || got.PortDesc != "lan1" {
219+
t.Fatalf("n[1]: %+v", got)
220+
}
221+
}
222+
151223
// TestProberLldp: contrato de disponibilidad de la sonda (#489).
152224
func TestProberLldp(t *testing.T) {
153225
ctx := context.Background()

0 commit comments

Comments
 (0)