master
go 257 lines 5.92 KB
Raw
1 // SPDX-License-Identifier: GPL-3.0-or-later
2
3 package l2topology
4
5 import (
6 "maps"
7 "net/netip"
8 "sort"
9 "strings"
10 )
11
12 type enrichmentAccumulator struct {
13 EndpointID string
14 MAC string
15 IPs map[string]netip.Addr
16 Protocols map[string]struct{}
17 DeviceIDs map[string]struct{}
18 IfIndexes map[string]struct{}
19 IfNames map[string]struct{}
20 States map[string]struct{}
21 AddrTypes map[string]struct{}
22 }
23
24 func ensureEnrichmentAccumulator(enrichments map[string]*enrichmentAccumulator, endpointID string) *enrichmentAccumulator {
25 acc := enrichments[endpointID]
26 if acc != nil {
27 return acc
28 }
29 acc = &enrichmentAccumulator{
30 EndpointID: endpointID,
31 IPs: make(map[string]netip.Addr),
32 Protocols: make(map[string]struct{}),
33 DeviceIDs: make(map[string]struct{}),
34 IfIndexes: make(map[string]struct{}),
35 IfNames: make(map[string]struct{}),
36 States: make(map[string]struct{}),
37 AddrTypes: make(map[string]struct{}),
38 }
39 enrichments[endpointID] = acc
40 return acc
41 }
42
43 func mergeEnrichmentAccumulator(target, source *enrichmentAccumulator) {
44 if target == nil || source == nil || target == source {
45 return
46 }
47 if target.MAC == "" {
48 target.MAC = source.MAC
49 }
50 maps.Copy(target.IPs, source.IPs)
51 for key := range source.Protocols {
52 target.Protocols[key] = struct{}{}
53 }
54 for key := range source.DeviceIDs {
55 target.DeviceIDs[key] = struct{}{}
56 }
57 for key := range source.IfIndexes {
58 target.IfIndexes[key] = struct{}{}
59 }
60 for key := range source.IfNames {
61 target.IfNames[key] = struct{}{}
62 }
63 for key := range source.States {
64 target.States[key] = struct{}{}
65 }
66 for key := range source.AddrTypes {
67 target.AddrTypes[key] = struct{}{}
68 }
69 }
70
71 type identityAliasReconcileStats struct {
72 endpointsMapped int
73 endpointsAmbiguousMAC int
74 ipsMerged int
75 ipsConflictSkipped int
76 }
77
78 func reconcileDeviceIdentityAliases(
79 devices map[string]Device,
80 interfaces map[string]Interface,
81 enrichments map[string]*enrichmentAccumulator,
82 ) identityAliasReconcileStats {
83 stats := identityAliasReconcileStats{}
84 if len(devices) == 0 || len(enrichments) == 0 {
85 return stats
86 }
87
88 uniqueMACToDeviceID, ambiguousMACs := buildUniqueMACToDeviceIndex(devices, interfaces)
89 if len(uniqueMACToDeviceID) == 0 {
90 return stats
91 }
92
93 ipToMACs := make(map[string]map[string]struct{})
94 enrichmentKeys := make([]string, 0, len(enrichments))
95 for endpointID := range enrichments {
96 enrichmentKeys = append(enrichmentKeys, endpointID)
97 }
98 sort.Strings(enrichmentKeys)
99
100 for _, endpointID := range enrichmentKeys {
101 acc := enrichments[endpointID]
102 if acc == nil {
103 continue
104 }
105 mac := normalizeMAC(acc.MAC)
106 if mac == "" {
107 continue
108 }
109 for _, ipKey := range sortedIPKeys(acc.IPs) {
110 addr, ok := acc.IPs[ipKey]
111 if !ok || !isUsableAliasIPAddress(addr) {
112 continue
113 }
114 owners := ipToMACs[ipKey]
115 if owners == nil {
116 owners = make(map[string]struct{})
117 ipToMACs[ipKey] = owners
118 }
119 owners[mac] = struct{}{}
120 }
121 }
122
123 aliasIPsByDevice := make(map[string]map[string]netip.Addr)
124 for _, endpointID := range enrichmentKeys {
125 acc := enrichments[endpointID]
126 if acc == nil {
127 continue
128 }
129 mac := normalizeMAC(acc.MAC)
130 if mac == "" {
131 continue
132 }
133 if _, ambiguous := ambiguousMACs[mac]; ambiguous {
134 stats.endpointsAmbiguousMAC++
135 continue
136 }
137
138 deviceID := strings.TrimSpace(uniqueMACToDeviceID[mac])
139 if deviceID == "" {
140 continue
141 }
142 stats.endpointsMapped++
143
144 if aliasIPsByDevice[deviceID] == nil {
145 aliasIPsByDevice[deviceID] = make(map[string]netip.Addr)
146 }
147 for _, ipKey := range sortedIPKeys(acc.IPs) {
148 addr, ok := acc.IPs[ipKey]
149 if !ok || !isUsableAliasIPAddress(addr) {
150 continue
151 }
152 if len(ipToMACs[ipKey]) > 1 {
153 stats.ipsConflictSkipped++
154 continue
155 }
156 aliasIPsByDevice[deviceID][addr.String()] = addr.Unmap()
157 }
158 }
159
160 for deviceID, aliasIPs := range aliasIPsByDevice {
161 device, ok := devices[deviceID]
162 if !ok || len(aliasIPs) == 0 {
163 continue
164 }
165
166 merged := make(map[string]netip.Addr, len(device.Addresses)+len(aliasIPs))
167 for _, addr := range device.Addresses {
168 if !isUsableAliasIPAddress(addr) {
169 continue
170 }
171 normalized := addr.Unmap()
172 merged[normalized.String()] = normalized
173 }
174 before := len(merged)
175 maps.Copy(merged, aliasIPs)
176 added := len(merged) - before
177 if added <= 0 {
178 continue
179 }
180 stats.ipsMerged += added
181
182 keys := make([]string, 0, len(merged))
183 for key := range merged {
184 keys = append(keys, key)
185 }
186 sort.Strings(keys)
187
188 addresses := make([]netip.Addr, 0, len(keys))
189 for _, key := range keys {
190 addresses = append(addresses, merged[key])
191 }
192 device.Addresses = addresses
193 devices[deviceID] = device
194 }
195
196 return stats
197 }
198
199 func buildUniqueMACToDeviceIndex(
200 devices map[string]Device,
201 interfaces map[string]Interface,
202 ) (map[string]string, map[string]struct{}) {
203 ownersByMAC := make(map[string]map[string]struct{})
204 addOwner := func(mac, deviceID string) {
205 mac = normalizeMAC(mac)
206 deviceID = strings.TrimSpace(deviceID)
207 if mac == "" || deviceID == "" {
208 return
209 }
210 owners := ownersByMAC[mac]
211 if owners == nil {
212 owners = make(map[string]struct{})
213 ownersByMAC[mac] = owners
214 }
215 owners[deviceID] = struct{}{}
216 }
217
218 for _, device := range devices {
219 addOwner(primaryL2MACIdentity(device.ChassisID, ""), device.ID)
220 }
221 for _, iface := range interfaces {
222 addOwner(iface.MAC, iface.DeviceID)
223 }
224
225 unique := make(map[string]string, len(ownersByMAC))
226 ambiguous := make(map[string]struct{})
227 for mac, owners := range ownersByMAC {
228 if len(owners) == 1 {
229 for deviceID := range owners {
230 unique[mac] = deviceID
231 }
232 continue
233 }
234 ambiguous[mac] = struct{}{}
235 }
236 return unique, ambiguous
237 }
238
239 func isUsableAliasIPAddress(addr netip.Addr) bool {
240 addr = addr.Unmap()
241 if !addr.IsValid() {
242 return false
243 }
244 return !addr.IsUnspecified()
245 }
246
247 func sortedIPKeys(in map[string]netip.Addr) []string {
248 if len(in) == 0 {
249 return nil
250 }
251 keys := make([]string, 0, len(in))
252 for key := range in {
253 keys = append(keys, key)
254 }
255 sort.Strings(keys)
256 return keys
257 }