master
go 445 lines 12.3 KB
Raw
1 // SPDX-License-Identifier: GPL-3.0-or-later
2
3 package l2topology
4
5 import "strings"
6
7 const (
8 lldpMatchPassDefault = "default"
9 lldpMatchPassPortDesc = "port_description"
10 lldpMatchPassSysName = "sysname"
11 lldpMatchPassChassisPort = "chassis_port_id_subtype"
12 lldpMatchPassChassisDescr = "chassis_port_descr"
13 lldpMatchPassChassis = "chassis"
14 )
15
16 type lldpMatchLink struct {
17 index int
18
19 sourceDeviceID string
20 localChassisID string
21 localSysName string
22 localMatchID string
23
24 localPortID string
25 localPortIDSubtype string
26 localPortDescr string
27
28 remoteChassisID string
29 remoteSysName string
30 remoteMatchID string
31 remotePortID string
32 remotePortIDSubtype string
33 remotePortDescr string
34
35 sourcePort string
36 targetPort string
37 remoteManagement string
38 remoteFallbackID string
39 }
40
41 type lldpMatchedPair struct {
42 sourceIndex int
43 targetIndex int
44 pass string
45 }
46
47 func buildLLDPMatchLinks(observations []L2Observation) []lldpMatchLink {
48 links := make([]lldpMatchLink, 0)
49 for _, obs := range observations {
50 sourceID := strings.TrimSpace(obs.DeviceID)
51 if sourceID == "" {
52 continue
53 }
54 localChassisID := strings.TrimSpace(obs.ChassisID)
55 localSysName := strings.TrimSpace(obs.Hostname)
56
57 remotes := sortedLLDPRemotes(obs.LLDPRemotes)
58 for _, remote := range remotes {
59 localPortID := strings.TrimSpace(remote.LocalPortID)
60 localPortDescr := strings.TrimSpace(remote.LocalPortDesc)
61 sourcePort := localPortID
62 if sourcePort == "" {
63 sourcePort = localPortDescr
64 }
65 if sourcePort == "" {
66 sourcePort = strings.TrimSpace(remote.LocalPortNum)
67 }
68
69 targetPort := strings.TrimSpace(remote.PortID)
70 if targetPort == "" {
71 targetPort = strings.TrimSpace(remote.PortDesc)
72 }
73
74 links = append(links, lldpMatchLink{
75 index: len(links),
76
77 sourceDeviceID: sourceID,
78 localChassisID: localChassisID,
79 localSysName: localSysName,
80 localMatchID: sourceID,
81
82 localPortID: localPortID,
83 localPortIDSubtype: strings.TrimSpace(remote.LocalPortIDSubtype),
84 localPortDescr: localPortDescr,
85
86 remoteChassisID: strings.TrimSpace(remote.ChassisID),
87 remoteSysName: strings.TrimSpace(remote.SysName),
88 remoteMatchID: "",
89 remotePortID: strings.TrimSpace(remote.PortID),
90 remotePortIDSubtype: strings.TrimSpace(remote.PortIDSubtype),
91 remotePortDescr: strings.TrimSpace(remote.PortDesc),
92
93 sourcePort: sourcePort,
94 targetPort: targetPort,
95 remoteManagement: strings.TrimSpace(remote.ManagementIP),
96 remoteFallbackID: strings.TrimSpace(remote.SysName),
97 })
98 }
99 }
100 return links
101 }
102
103 func annotateLLDPLinkMatchIdentities(
104 links []lldpMatchLink,
105 hostToID map[string]string,
106 chassisToID map[string]string,
107 ipToID map[string]string,
108 ) {
109 for i := range links {
110 link := &links[i]
111 if strings.TrimSpace(link.localMatchID) == "" {
112 link.localMatchID = strings.TrimSpace(link.sourceDeviceID)
113 }
114 if strings.TrimSpace(link.remoteMatchID) != "" {
115 continue
116 }
117 link.remoteMatchID = resolveKnownDeviceID(hostToID, chassisToID, ipToID, link.remoteSysName, link.remoteChassisID, link.remoteManagement)
118 }
119 }
120
121 func resolveKnownDeviceID(
122 hostToID map[string]string,
123 chassisToID map[string]string,
124 ipToID map[string]string,
125 hostname, chassisID, managementIP string,
126 ) string {
127 if id := hostToID[canonicalHost(hostname)]; strings.TrimSpace(id) != "" {
128 return strings.TrimSpace(id)
129 }
130 if id := chassisToID[canonicalToken(chassisID)]; strings.TrimSpace(id) != "" {
131 return strings.TrimSpace(id)
132 }
133 if id := ipToID[canonicalIP(managementIP)]; strings.TrimSpace(id) != "" {
134 return strings.TrimSpace(id)
135 }
136 return ""
137 }
138
139 func lldpIdentityTokenForMatch(matchID, chassisID string) string {
140 matchID = strings.TrimSpace(matchID)
141 if matchID != "" {
142 return "device:" + matchID
143 }
144 return normalizeLLDPChassisForMatch(chassisID)
145 }
146
147 func buildLLDPLookupMap(links []lldpMatchLink) map[string]int {
148 lookup := make(map[string]int, len(links)*6)
149 for _, link := range links {
150 defaultKey := lldpCompositeKey(
151 lldpIdentityTokenForMatch(link.remoteMatchID, link.remoteChassisID),
152 lldpIdentityTokenForMatch(link.localMatchID, link.localChassisID),
153 normalizeLLDPPortIDForMatch(link.localPortID, link.localPortIDSubtype),
154 normalizeLLDPPortSubtypeForMatch(link.localPortIDSubtype),
155 normalizeLLDPPortIDForMatch(link.remotePortID, link.remotePortIDSubtype),
156 normalizeLLDPPortSubtypeForMatch(link.remotePortIDSubtype),
157 )
158 lookup[defaultKey] = link.index
159
160 descrKey := lldpCompositeKey(
161 lldpIdentityTokenForMatch(link.remoteMatchID, link.remoteChassisID),
162 lldpIdentityTokenForMatch(link.localMatchID, link.localChassisID),
163 link.localPortDescr,
164 link.remotePortDescr,
165 )
166 lookup[descrKey] = link.index
167
168 sysNameKey := lldpCompositeKey(
169 link.remoteSysName,
170 link.localSysName,
171 normalizeLLDPPortIDForMatch(link.localPortID, link.localPortIDSubtype),
172 normalizeLLDPPortSubtypeForMatch(link.localPortIDSubtype),
173 normalizeLLDPPortIDForMatch(link.remotePortID, link.remotePortIDSubtype),
174 normalizeLLDPPortSubtypeForMatch(link.remotePortIDSubtype),
175 )
176 lookup[sysNameKey] = link.index
177
178 elementaryAKey := lldpCompositeKey(
179 lldpIdentityTokenForMatch(link.remoteMatchID, link.remoteChassisID),
180 lldpIdentityTokenForMatch(link.localMatchID, link.localChassisID),
181 normalizeLLDPPortIDForMatch(link.remotePortID, link.remotePortIDSubtype),
182 normalizeLLDPPortSubtypeForMatch(link.remotePortIDSubtype),
183 )
184 lookup[elementaryAKey] = link.index
185
186 elementaryBKey := lldpCompositeKey(
187 lldpIdentityTokenForMatch(link.remoteMatchID, link.remoteChassisID),
188 lldpIdentityTokenForMatch(link.localMatchID, link.localChassisID),
189 link.remotePortDescr,
190 )
191 lookup[elementaryBKey] = link.index
192
193 elementaryCKey := lldpCompositeKey(
194 lldpIdentityTokenForMatch(link.remoteMatchID, link.remoteChassisID),
195 lldpIdentityTokenForMatch(link.localMatchID, link.localChassisID),
196 )
197 lookup[elementaryCKey] = link.index
198 }
199 return lookup
200 }
201
202 func lldpCompositeKey(parts ...string) string {
203 return topologyMatchCompositeKey(parts...)
204 }
205
206 func matchLLDPLinksEnlinkdPassOrder(links []lldpMatchLink) []lldpMatchedPair {
207 if len(links) == 0 {
208 return nil
209 }
210
211 lookup := buildLLDPLookupMap(links)
212 parsed := make(map[int]struct{}, len(links))
213 pairs := make([]lldpMatchedPair, 0, len(links)/2)
214
215 addPair := func(sourceIndex, targetIndex int, pass string) {
216 parsed[sourceIndex] = struct{}{}
217 parsed[targetIndex] = struct{}{}
218 pairs = append(pairs, lldpMatchedPair{
219 sourceIndex: sourceIndex,
220 targetIndex: targetIndex,
221 pass: pass,
222 })
223 }
224
225 for _, source := range links {
226 if _, ok := parsed[source.index]; ok {
227 continue
228 }
229 if lldpIdentityTokenForMatch(source.localMatchID, source.localChassisID) == lldpIdentityTokenForMatch(source.remoteMatchID, source.remoteChassisID) ||
230 (source.localSysName != "" && source.localSysName == source.remoteSysName) {
231 parsed[source.index] = struct{}{}
232 continue
233 }
234
235 key := lldpCompositeKey(
236 lldpIdentityTokenForMatch(source.localMatchID, source.localChassisID),
237 lldpIdentityTokenForMatch(source.remoteMatchID, source.remoteChassisID),
238 normalizeLLDPPortIDForMatch(source.remotePortID, source.remotePortIDSubtype),
239 normalizeLLDPPortSubtypeForMatch(source.remotePortIDSubtype),
240 normalizeLLDPPortIDForMatch(source.localPortID, source.localPortIDSubtype),
241 normalizeLLDPPortSubtypeForMatch(source.localPortIDSubtype),
242 )
243 targetIndex, ok := lookup[key]
244 if !ok {
245 continue
246 }
247 if source.index == targetIndex {
248 continue
249 }
250 if _, targetParsed := parsed[targetIndex]; targetParsed {
251 continue
252 }
253 addPair(source.index, targetIndex, lldpMatchPassDefault)
254 }
255
256 for _, source := range links {
257 if _, ok := parsed[source.index]; ok {
258 continue
259 }
260 if strings.TrimSpace(source.remotePortDescr) == "" || strings.TrimSpace(source.localPortDescr) == "" {
261 continue
262 }
263 key := lldpCompositeKey(
264 lldpIdentityTokenForMatch(source.localMatchID, source.localChassisID),
265 lldpIdentityTokenForMatch(source.remoteMatchID, source.remoteChassisID),
266 source.remotePortDescr,
267 source.localPortDescr,
268 )
269 targetIndex, ok := lookup[key]
270 if !ok {
271 continue
272 }
273 if source.index == targetIndex {
274 continue
275 }
276 if _, targetParsed := parsed[targetIndex]; targetParsed {
277 continue
278 }
279 addPair(source.index, targetIndex, lldpMatchPassPortDesc)
280 }
281
282 for _, source := range links {
283 if _, ok := parsed[source.index]; ok {
284 continue
285 }
286 key := lldpCompositeKey(
287 source.localSysName,
288 source.remoteSysName,
289 normalizeLLDPPortIDForMatch(source.remotePortID, source.remotePortIDSubtype),
290 normalizeLLDPPortSubtypeForMatch(source.remotePortIDSubtype),
291 normalizeLLDPPortIDForMatch(source.localPortID, source.localPortIDSubtype),
292 normalizeLLDPPortSubtypeForMatch(source.localPortIDSubtype),
293 )
294 targetIndex, ok := lookup[key]
295 if !ok {
296 continue
297 }
298 if source.index == targetIndex {
299 continue
300 }
301 if _, targetParsed := parsed[targetIndex]; targetParsed {
302 continue
303 }
304 addPair(source.index, targetIndex, lldpMatchPassSysName)
305 }
306
307 for _, source := range links {
308 if _, ok := parsed[source.index]; ok {
309 continue
310 }
311 key := lldpCompositeKey(
312 lldpIdentityTokenForMatch(source.localMatchID, source.localChassisID),
313 lldpIdentityTokenForMatch(source.remoteMatchID, source.remoteChassisID),
314 normalizeLLDPPortIDForMatch(source.localPortID, source.localPortIDSubtype),
315 normalizeLLDPPortSubtypeForMatch(source.localPortIDSubtype),
316 )
317 targetIndex, ok := lookup[key]
318 if !ok {
319 continue
320 }
321 if source.index == targetIndex {
322 continue
323 }
324 if _, targetParsed := parsed[targetIndex]; targetParsed {
325 continue
326 }
327 addPair(source.index, targetIndex, lldpMatchPassChassisPort)
328 }
329
330 for _, source := range links {
331 if _, ok := parsed[source.index]; ok {
332 continue
333 }
334 key := lldpCompositeKey(
335 lldpIdentityTokenForMatch(source.localMatchID, source.localChassisID),
336 lldpIdentityTokenForMatch(source.remoteMatchID, source.remoteChassisID),
337 source.localPortDescr,
338 )
339 targetIndex, ok := lookup[key]
340 if !ok {
341 continue
342 }
343 if source.index == targetIndex {
344 continue
345 }
346 if _, targetParsed := parsed[targetIndex]; targetParsed {
347 continue
348 }
349 addPair(source.index, targetIndex, lldpMatchPassChassisDescr)
350 }
351
352 for _, source := range links {
353 if _, ok := parsed[source.index]; ok {
354 continue
355 }
356 key := lldpCompositeKey(
357 lldpIdentityTokenForMatch(source.localMatchID, source.localChassisID),
358 lldpIdentityTokenForMatch(source.remoteMatchID, source.remoteChassisID),
359 )
360 targetIndex, ok := lookup[key]
361 if !ok {
362 continue
363 }
364 if source.index == targetIndex {
365 continue
366 }
367 if _, targetParsed := parsed[targetIndex]; targetParsed {
368 continue
369 }
370 addPair(source.index, targetIndex, lldpMatchPassChassis)
371 }
372
373 return pairs
374 }
375
376 func buildLLDPTargetOverrides(links []lldpMatchLink, pairs []lldpMatchedPair) map[int]string {
377 if len(pairs) == 0 {
378 return nil
379 }
380
381 indexToLink := make(map[int]lldpMatchLink, len(links))
382 for _, link := range links {
383 indexToLink[link.index] = link
384 }
385
386 overrides := make(map[int]string, len(pairs)*2)
387 for _, pair := range pairs {
388 source, sourceOK := indexToLink[pair.sourceIndex]
389 target, targetOK := indexToLink[pair.targetIndex]
390 if !sourceOK || !targetOK {
391 continue
392 }
393
394 if _, exists := overrides[source.index]; !exists {
395 overrides[source.index] = target.sourceDeviceID
396 }
397 if _, exists := overrides[target.index]; !exists {
398 overrides[target.index] = source.sourceDeviceID
399 }
400 }
401
402 return overrides
403 }
404
405 func buildLLDPPairMetadata(links []lldpMatchLink, pairs []lldpMatchedPair) map[int]matchedPairMetadata {
406 if len(pairs) == 0 {
407 return nil
408 }
409
410 indexToLink := make(map[int]lldpMatchLink, len(links))
411 for _, link := range links {
412 indexToLink[link.index] = link
413 }
414
415 metadata := make(map[int]matchedPairMetadata, len(pairs)*2)
416 for _, pair := range pairs {
417 sourceLink, sourceOK := indexToLink[pair.sourceIndex]
418 targetLink, targetOK := indexToLink[pair.targetIndex]
419 if !sourceOK || !targetOK {
420 continue
421 }
422
423 pairID := canonicalAdjacencyPairID(
424 "lldp",
425 sourceLink.sourceDeviceID,
426 sourceLink.sourcePort,
427 targetLink.sourceDeviceID,
428 targetLink.sourcePort,
429 )
430 if pairID == "" {
431 continue
432 }
433
434 metadata[sourceLink.index] = matchedPairMetadata{
435 id: pairID,
436 pass: pair.pass,
437 }
438 metadata[targetLink.index] = matchedPairMetadata{
439 id: pairID,
440 pass: pair.pass,
441 }
442 }
443
444 return metadata
445 }