master
go 347 lines 8.06 KB
Raw
1 // SPDX-License-Identifier: GPL-3.0-or-later
2
3 package l2topology
4
5 import (
6 "fmt"
7 "sort"
8 "strings"
9 )
10
11 type topologyMatchLookup struct {
12 canonical string
13 identityKeys []string
14 }
15
16 type topologyActorSortEntry struct {
17 actor Actor
18 key string
19 }
20
21 type topologyLinkSortEntry struct {
22 link Link
23 key string
24 }
25
26 func canonicalTopologyMatchKey(match Match) string {
27 if key := canonicalTopologyPrimaryMACKey(match); key != "" {
28 return "mac:" + key
29 }
30 if key := canonicalTopologyHardwareKey(match.ChassisIDs); key != "" {
31 return "chassis:" + key
32 }
33 if key := canonicalTopologyIPListKey(match.IPAddresses); key != "" {
34 return "ip:" + key
35 }
36 if key := canonicalTopologyStringListKey(match.Hostnames); key != "" {
37 return "hostname:" + key
38 }
39 if key := canonicalTopologyStringListKey(match.DNSNames); key != "" {
40 return "dns:" + key
41 }
42 if sysName := strings.ToLower(strings.TrimSpace(match.SysName)); sysName != "" {
43 return "sysname:" + sysName
44 }
45 if match.SysObjectID != "" {
46 return "sysobjectid:" + match.SysObjectID
47 }
48 return ""
49 }
50
51 func assignTopologyActorIDsAndLinkEndpoints(actors []Actor, links []Link) {
52 if len(actors) == 0 {
53 return
54 }
55
56 usedActorIDs := make(map[string]int, len(actors))
57 actorIDByCanonicalMatch := make(map[string]string, len(actors))
58 actorIDByIdentityKey := make(map[string]string, len(actors)*4)
59 actorLookups := make([]topologyMatchLookup, len(actors))
60
61 for i := range actors {
62 actorLookups[i] = newTopologyMatchLookup(actors[i].Match)
63
64 baseID := actorLookups[i].canonical
65 if baseID == "" {
66 actorType := strings.ToLower(strings.TrimSpace(actors[i].ActorType))
67 if actorType == "" {
68 actorType = "actor"
69 }
70 baseID = "generated:" + actorType
71 }
72
73 actorID := responseScopedActorID(baseID, usedActorIDs)
74 actors[i].ActorID = actorID
75
76 if actorLookups[i].canonical != "" {
77 if _, exists := actorIDByCanonicalMatch[actorLookups[i].canonical]; !exists {
78 actorIDByCanonicalMatch[actorLookups[i].canonical] = actorID
79 }
80 }
81 for _, key := range actorLookups[i].identityKeys {
82 if _, exists := actorIDByIdentityKey[key]; !exists {
83 actorIDByIdentityKey[key] = actorID
84 }
85 }
86 }
87
88 for i := range links {
89 srcLookup := newTopologyMatchLookup(links[i].Src.Match)
90 dstLookup := newTopologyMatchLookup(links[i].Dst.Match)
91 links[i].SrcActorID = resolveTopologyEndpointActorID(srcLookup, actorIDByCanonicalMatch, actorIDByIdentityKey)
92 links[i].DstActorID = resolveTopologyEndpointActorID(dstLookup, actorIDByCanonicalMatch, actorIDByIdentityKey)
93 }
94 }
95
96 func newTopologyMatchLookup(match Match) topologyMatchLookup {
97 return topologyMatchLookup{
98 canonical: canonicalTopologyMatchKey(match),
99 identityKeys: topologyMatchIdentityKeys(match),
100 }
101 }
102
103 func responseScopedActorID(base string, used map[string]int) string {
104 base = strings.ToLower(strings.TrimSpace(base))
105 if base == "" {
106 base = "generated:actor"
107 }
108
109 count := used[base]
110 count++
111 used[base] = count
112 if count == 1 {
113 return base
114 }
115 return fmt.Sprintf("%s#%d", base, count)
116 }
117
118 func resolveTopologyEndpointActorID(lookup topologyMatchLookup, byCanonicalMatch map[string]string, byIdentityKey map[string]string) string {
119 if lookup.canonical != "" {
120 if actorID := strings.TrimSpace(byCanonicalMatch[lookup.canonical]); actorID != "" {
121 return actorID
122 }
123 }
124 for _, key := range lookup.identityKeys {
125 if actorID := strings.TrimSpace(byIdentityKey[key]); actorID != "" {
126 return actorID
127 }
128 }
129 return ""
130 }
131
132 func enrichTopologyPortTablesWithLinkCounts(actors []Actor, links []Link) {
133 type actorPort struct {
134 actorID string
135 portName string
136 }
137 counts := make(map[actorPort]int, len(links)*2)
138
139 for _, link := range links {
140 if link.SrcActorID != "" {
141 if ifName, ok := link.Src.Attributes["if_name"]; ok {
142 name := strings.TrimSpace(fmt.Sprintf("%v", ifName))
143 if name != "" {
144 counts[actorPort{link.SrcActorID, name}]++
145 }
146 }
147 }
148 if link.DstActorID != "" {
149 if ifName, ok := link.Dst.Attributes["if_name"]; ok {
150 name := strings.TrimSpace(fmt.Sprintf("%v", ifName))
151 if name != "" {
152 counts[actorPort{link.DstActorID, name}]++
153 }
154 }
155 }
156 }
157
158 for i := range actors {
159 portRows := actors[i].Tables["ports"]
160 if len(portRows) == 0 {
161 continue
162 }
163 for j := range portRows {
164 name := strings.TrimSpace(fmt.Sprintf("%v", portRows[j]["name"]))
165 if name == "" {
166 continue
167 }
168 if c := counts[actorPort{actors[i].ActorID, name}]; c > 0 {
169 portRows[j]["link_count"] = c
170 }
171 }
172 }
173 }
174
175 func canonicalTopologyPrimaryMACKey(match Match) string {
176 set := make(map[string]struct{}, len(match.MacAddresses)+len(match.ChassisIDs))
177 for _, value := range match.MacAddresses {
178 if mac := normalizeMAC(value); mac != "" {
179 set[mac] = struct{}{}
180 }
181 }
182 for _, value := range match.ChassisIDs {
183 if mac := normalizeMAC(value); mac != "" {
184 set[mac] = struct{}{}
185 }
186 }
187 if len(set) == 0 {
188 return ""
189 }
190 keys := sortedTopologySet(set)
191 if len(keys) == 0 {
192 return ""
193 }
194 return strings.Join(keys, ",")
195 }
196
197 func canonicalTopologyHardwareKey(values []string) string {
198 if len(values) == 0 {
199 return ""
200 }
201 out := make([]string, 0, len(values))
202 for _, value := range values {
203 value = strings.TrimSpace(value)
204 if value == "" {
205 continue
206 }
207 if mac := normalizeMAC(value); mac != "" {
208 out = append(out, mac)
209 continue
210 }
211 if ip := normalizeTopologyIP(value); ip != "" {
212 out = append(out, ip)
213 continue
214 }
215 out = append(out, strings.ToLower(value))
216 }
217 if len(out) == 0 {
218 return ""
219 }
220 sort.Strings(out)
221 out = uniqueTopologyStrings(out)
222 return strings.Join(out, ",")
223 }
224
225 func canonicalTopologyIPListKey(values []string) string {
226 if len(values) == 0 {
227 return ""
228 }
229 out := make([]string, 0, len(values))
230 for _, value := range values {
231 value = strings.TrimSpace(value)
232 if value == "" {
233 continue
234 }
235 if ip := normalizeTopologyIP(value); ip != "" {
236 out = append(out, ip)
237 continue
238 }
239 out = append(out, strings.ToLower(value))
240 }
241 if len(out) == 0 {
242 return ""
243 }
244 sort.Strings(out)
245 out = uniqueTopologyStrings(out)
246 return strings.Join(out, ",")
247 }
248
249 func canonicalTopologyStringListKey(values []string) string {
250 if len(values) == 0 {
251 return ""
252 }
253 out := make([]string, 0, len(values))
254 for _, value := range values {
255 value = strings.ToLower(strings.TrimSpace(value))
256 if value == "" {
257 continue
258 }
259 out = append(out, value)
260 }
261 if len(out) == 0 {
262 return ""
263 }
264 sort.Strings(out)
265 out = uniqueTopologyStrings(out)
266 return strings.Join(out, ",")
267 }
268
269 func topologyLinkSortKey(link Link) string {
270 return strings.Join([]string{
271 link.Protocol,
272 link.Direction,
273 canonicalTopologyMatchKey(link.Src.Match),
274 canonicalTopologyMatchKey(link.Dst.Match),
275 topologyAttrKey(link.Src.Attributes, "if_index"),
276 topologyAttrKey(link.Src.Attributes, "if_name"),
277 topologyAttrKey(link.Src.Attributes, "port_id"),
278 topologyAttrKey(link.Dst.Attributes, "if_index"),
279 topologyAttrKey(link.Dst.Attributes, "if_name"),
280 topologyAttrKey(link.Dst.Attributes, "port_id"),
281 link.State,
282 }, keySep)
283 }
284
285 func topologyActorSortKey(actor Actor) string {
286 return strings.Join([]string{
287 actor.ActorType,
288 canonicalTopologyMatchKey(actor.Match),
289 actor.Source,
290 actor.Layer,
291 }, keySep)
292 }
293
294 func topologyAttrKey(attrs map[string]any, key string) string {
295 if len(attrs) == 0 {
296 return ""
297 }
298 value, ok := attrs[key]
299 if !ok || value == nil {
300 return ""
301 }
302 return fmt.Sprint(value)
303 }
304
305 func sortTopologyActors(actors []Actor) {
306 if len(actors) < 2 {
307 return
308 }
309
310 entries := make([]topologyActorSortEntry, len(actors))
311 for i := range actors {
312 entries[i] = topologyActorSortEntry{
313 actor: actors[i],
314 key: topologyActorSortKey(actors[i]),
315 }
316 }
317
318 sort.SliceStable(entries, func(i, j int) bool {
319 return entries[i].key < entries[j].key
320 })
321
322 for i := range entries {
323 actors[i] = entries[i].actor
324 }
325 }
326
327 func sortTopologyLinks(links []Link) {
328 if len(links) < 2 {
329 return
330 }
331
332 entries := make([]topologyLinkSortEntry, len(links))
333 for i := range links {
334 entries[i] = topologyLinkSortEntry{
335 link: links[i],
336 key: topologyLinkSortKey(links[i]),
337 }
338 }
339
340 sort.SliceStable(entries, func(i, j int) bool {
341 return entries[i].key < entries[j].key
342 })
343
344 for i := range entries {
345 links[i] = entries[i].link
346 }
347 }