master
go 319 lines 7.09 KB
Raw
1 // SPDX-License-Identifier: GPL-3.0-or-later
2
3 package l2topology
4
5 import (
6 "sort"
7 "strconv"
8 "strings"
9 )
10
11 var interfaceNameLookupSanitizer = strings.NewReplacer(
12 " ", "",
13 "-", "",
14 "_", "",
15 ".", "",
16 "\t", "",
17 "\n", "",
18 "\r", "",
19 )
20
21 func deviceIfNameKey(deviceID, ifName string) string {
22 return strings.TrimSpace(deviceID) + keySep + strings.ToLower(strings.TrimSpace(ifName))
23 }
24
25 func interfaceNameLookupAliases(values ...string) []string {
26 set := make(map[string]struct{}, len(values)*2)
27 for _, value := range values {
28 trimmed := strings.TrimSpace(value)
29 if trimmed == "" {
30 continue
31 }
32 set[trimmed] = struct{}{}
33 if normalized := normalizeInterfaceNameForLookup(trimmed); normalized != "" && normalized != strings.ToLower(trimmed) {
34 set[normalized] = struct{}{}
35 }
36 }
37 if len(set) == 0 {
38 return nil
39 }
40 out := make([]string, 0, len(set))
41 for value := range set {
42 out = append(out, value)
43 }
44 sort.Strings(out)
45 return out
46 }
47
48 func normalizeInterfaceNameForLookup(value string) string {
49 value = strings.TrimSpace(strings.ToLower(value))
50 if value == "" {
51 return ""
52 }
53 return interfaceNameLookupSanitizer.Replace(value)
54 }
55
56 func resolveIfIndexByPortName(deviceID, port string, ifIndexByDeviceName map[string]int) int {
57 deviceID = strings.TrimSpace(deviceID)
58 port = strings.TrimSpace(port)
59 if deviceID == "" || port == "" {
60 return 0
61 }
62 if idx, ok := ifIndexByDeviceName[deviceIfNameKey(deviceID, port)]; ok && idx > 0 {
63 return idx
64 }
65 if normalized := normalizeInterfaceNameForLookup(port); normalized != "" {
66 if idx, ok := ifIndexByDeviceName[deviceIfNameKey(deviceID, normalized)]; ok && idx > 0 {
67 return idx
68 }
69 }
70 if parsed, err := strconv.Atoi(port); err == nil && parsed > 0 {
71 return parsed
72 }
73 return 0
74 }
75
76 func topologyIdentityIndexOverlaps(index map[string]struct{}, keys []string) bool {
77 if len(index) == 0 || len(keys) == 0 {
78 return false
79 }
80 for _, key := range keys {
81 if _, ok := index[key]; ok {
82 return true
83 }
84 }
85 return false
86 }
87
88 func addTopologyIdentityKeys(index map[string]struct{}, keys []string) {
89 if index == nil || len(keys) == 0 {
90 return
91 }
92 for _, key := range keys {
93 index[key] = struct{}{}
94 }
95 }
96
97 func buildDeviceIdentityKeySetByID(
98 deviceByID map[string]Device,
99 adjacencies []Adjacency,
100 ifaceByDeviceIndex map[string]Interface,
101 ) map[string]topologyIdentityKeySet {
102 if len(deviceByID) == 0 {
103 return nil
104 }
105 out := make(map[string]topologyIdentityKeySet, len(deviceByID))
106 for _, device := range deviceByID {
107 deviceID := strings.TrimSpace(device.ID)
108 if deviceID == "" {
109 continue
110 }
111 keys := topologyMatchIdentityKeys(
112 deviceToTopologyActor(device, "", "", "", topologyDeviceInterfaceSummary{}, nil).Match,
113 )
114 if len(keys) == 0 {
115 continue
116 }
117 set := make(topologyIdentityKeySet, len(keys))
118 for _, key := range keys {
119 key = strings.TrimSpace(key)
120 if key == "" {
121 continue
122 }
123 set[key] = struct{}{}
124 }
125 if len(set) == 0 {
126 continue
127 }
128 out[deviceID] = set
129 }
130 for _, adjacency := range adjacencies {
131 protocol := strings.ToLower(strings.TrimSpace(adjacency.Protocol))
132 if protocol != "lldp" && protocol != "cdp" {
133 continue
134 }
135 if mac := normalizeMAC(adjacency.SourcePort); mac != "" {
136 deviceID := strings.TrimSpace(adjacency.SourceID)
137 if deviceID != "" {
138 if out[deviceID] == nil {
139 out[deviceID] = make(topologyIdentityKeySet)
140 }
141 out[deviceID]["hw:"+mac] = struct{}{}
142 }
143 }
144 if mac := normalizeMAC(adjacency.TargetPort); mac != "" {
145 deviceID := strings.TrimSpace(adjacency.TargetID)
146 if deviceID != "" {
147 if out[deviceID] == nil {
148 out[deviceID] = make(topologyIdentityKeySet)
149 }
150 out[deviceID]["hw:"+mac] = struct{}{}
151 }
152 }
153 }
154 for _, iface := range ifaceByDeviceIndex {
155 deviceID := strings.TrimSpace(iface.DeviceID)
156 if deviceID == "" {
157 continue
158 }
159 ifaceMAC := normalizeMAC(iface.MAC)
160 if ifaceMAC == "" {
161 continue
162 }
163 if out[deviceID] == nil {
164 out[deviceID] = make(topologyIdentityKeySet)
165 }
166 out[deviceID]["hw:"+ifaceMAC] = struct{}{}
167 }
168 if len(out) == 0 {
169 return nil
170 }
171 return out
172 }
173
174 func topologyMatchIdentityKeys(match Match) []string {
175 seen := make(map[string]struct{}, 8)
176 add := func(kind, value string) {
177 value = strings.TrimSpace(value)
178 if value == "" {
179 return
180 }
181 key := kind + ":" + value
182 seen[key] = struct{}{}
183 }
184
185 for _, value := range match.ChassisIDs {
186 value = strings.TrimSpace(value)
187 if value == "" {
188 continue
189 }
190 if mac := normalizeMAC(value); mac != "" {
191 add("hw", mac)
192 continue
193 }
194 if ip := normalizeTopologyIP(value); ip != "" {
195 add("ip", ip)
196 continue
197 }
198 add("chassis", strings.ToLower(value))
199 }
200
201 for _, value := range match.MacAddresses {
202 if mac := normalizeMAC(value); mac != "" {
203 add("hw", mac)
204 }
205 }
206 for _, value := range match.IPAddresses {
207 if ip := normalizeTopologyIP(value); ip != "" {
208 add("ip", ip)
209 continue
210 }
211 add("ipraw", strings.ToLower(strings.TrimSpace(value)))
212 }
213 for _, value := range match.Hostnames {
214 add("hostname", strings.ToLower(strings.TrimSpace(value)))
215 }
216 for _, value := range match.DNSNames {
217 add("dns", strings.ToLower(strings.TrimSpace(value)))
218 }
219 if sysName := strings.TrimSpace(match.SysName); sysName != "" {
220 add("sysname", strings.ToLower(sysName))
221 }
222
223 if len(seen) == 0 {
224 return nil
225 }
226
227 keys := make([]string, 0, len(seen))
228 for key := range seen {
229 keys = append(keys, key)
230 }
231 sort.Strings(keys)
232 return keys
233 }
234
235 func topologyMatchHardwareIdentityKeys(match Match) []string {
236 seen := make(map[string]struct{}, len(match.MacAddresses)+len(match.ChassisIDs))
237 add := func(value string) {
238 if mac := normalizeMAC(value); mac != "" {
239 seen["hw:"+mac] = struct{}{}
240 }
241 }
242
243 for _, value := range match.MacAddresses {
244 add(value)
245 }
246 for _, value := range match.ChassisIDs {
247 add(value)
248 }
249
250 if len(seen) == 0 {
251 return nil
252 }
253 keys := make([]string, 0, len(seen))
254 for key := range seen {
255 keys = append(keys, key)
256 }
257 sort.Strings(keys)
258 return keys
259 }
260
261 func endpointMatchOverlappingKnownDeviceIDs(
262 endpointMatch Match,
263 deviceIdentityByID map[string]topologyIdentityKeySet,
264 ) []string {
265 if len(deviceIdentityByID) == 0 {
266 return nil
267 }
268
269 endpointKeys := topologyMatchHardwareIdentityKeys(endpointMatch)
270 if len(endpointKeys) == 0 {
271 endpointKeys = topologyMatchIdentityKeys(endpointMatch)
272 }
273 if len(endpointKeys) == 0 {
274 return nil
275 }
276
277 deviceIDs := make([]string, 0, len(deviceIdentityByID))
278 for deviceID := range deviceIdentityByID {
279 deviceID = strings.TrimSpace(deviceID)
280 if deviceID == "" {
281 continue
282 }
283 deviceIDs = append(deviceIDs, deviceID)
284 }
285 sort.Strings(deviceIDs)
286 if len(deviceIDs) == 0 {
287 return nil
288 }
289
290 matches := make([]string, 0, 2)
291 for _, deviceID := range deviceIDs {
292 deviceKeys := deviceIdentityByID[deviceID]
293 if len(deviceKeys) == 0 {
294 continue
295 }
296 for _, endpointKey := range endpointKeys {
297 if _, ok := deviceKeys[endpointKey]; ok {
298 matches = append(matches, deviceID)
299 break
300 }
301 }
302 }
303 if len(matches) == 0 {
304 return nil
305 }
306 return matches
307 }
308
309 func normalizeTopologyIP(value string) string {
310 value = strings.TrimSpace(value)
311 if value == "" {
312 return ""
313 }
314 addr := parseAddr(value)
315 if !addr.IsValid() {
316 return ""
317 }
318 return addr.Unmap().String()
319 }