master
go 289 lines 9.32 KB
Raw
1 // SPDX-License-Identifier: GPL-3.0-or-later
2
3 package l2topology
4
5 import (
6 "sort"
7 "strings"
8 )
9
10 func (b *segmentProjectionBuilder) emitLinks() {
11 sort.Strings(b.segmentIDs)
12 probableOnlyAnchorPortIDBySegment := b.buildProbableOnlyAnchorPortIDBySegment()
13 segmentsWithAnyLinks := make(map[string]struct{})
14
15 for _, segmentID := range b.segmentIDs {
16 segment := b.segmentByID[segmentID]
17 if segment == nil {
18 continue
19 }
20 segmentEndpoint := LinkEndpoint{
21 Match: b.segmentMatchByID[segmentID],
22 Attributes: map[string]any{
23 "segment_id": segmentID,
24 },
25 }
26
27 portIDs := make([]string, 0, len(segment.ports))
28 for portID := range segment.ports {
29 portIDs = append(portIDs, portID)
30 }
31 sort.Strings(portIDs)
32 probableOnlyAnchorPortID := probableOnlyAnchorPortIDBySegment[segmentID]
33 for _, portID := range portIDs {
34 if probableOnlyAnchorPortID != "" && portID != probableOnlyAnchorPortID {
35 continue
36 }
37 port := segment.ports[portID]
38 device, ok := b.deviceByID[port.deviceID]
39 if !ok {
40 continue
41 }
42 localPort := bridgePortDisplay(port)
43 if localPort == "" {
44 continue
45 }
46 edgeKey := segmentID + keySep + portID
47 if _, seen := b.deviceSegmentEdgeSeen[edgeKey]; seen {
48 continue
49 }
50 b.deviceSegmentEdgeSeen[edgeKey] = struct{}{}
51
52 metrics := map[string]any{
53 "bridge_domain": segmentID,
54 }
55 if segment.portIdentityKey(port) == segment.portIdentityKey(segment.designatedPort) {
56 metrics["designated"] = true
57 }
58 b.out.links = append(b.out.links, Link{
59 Layer: b.layer,
60 Protocol: "bridge",
61 LinkType: "bridge",
62 Direction: "bidirectional",
63 Src: adjacencySideToEndpoint(device, localPort, b.ifIndexByDeviceName, b.ifaceByDeviceIndex),
64 Dst: segmentEndpoint,
65 DiscoveredAt: topologyTimePtr(b.collectedAt),
66 LastSeen: topologyTimePtr(b.collectedAt),
67 Metrics: metrics,
68 })
69 b.out.linksFdb++
70 b.out.bidirectionalCount++
71 segmentsWithAnyLinks[segmentID] = struct{}{}
72 }
73
74 allowedEndpoints := b.allowedEndpointBySegment[segmentID]
75 if len(allowedEndpoints) == 0 {
76 continue
77 }
78 endpointSet := make(map[string]struct{}, len(segment.endpointIDs)+len(allowedEndpoints))
79 for endpointID := range segment.endpointIDs {
80 endpointSet[endpointID] = struct{}{}
81 }
82 for endpointID := range allowedEndpoints {
83 endpointSet[endpointID] = struct{}{}
84 }
85 endpointIDs := sortedTopologySet(endpointSet)
86 for _, endpointID := range endpointIDs {
87 if _, ok := allowedEndpoints[endpointID]; !ok {
88 continue
89 }
90
91 endpointMatch, ok := b.endpointMatchByID[endpointID]
92 if !ok {
93 endpointMatch = endpointMatchFromID(endpointID)
94 if len(topologyMatchIdentityKeys(endpointMatch)) == 0 {
95 continue
96 }
97 }
98 overlappingDeviceIDs := endpointMatchOverlappingKnownDeviceIDs(endpointMatch, b.deviceIdentityByID)
99 if len(overlappingDeviceIDs) > 0 {
100 matchedManagedDeviceIDs := make([]string, 0, len(overlappingDeviceIDs))
101 for _, overlapID := range overlappingDeviceIDs {
102 if _, ok := b.deviceByID[overlapID]; ok {
103 matchedManagedDeviceIDs = append(matchedManagedDeviceIDs, overlapID)
104 }
105 }
106 if len(matchedManagedDeviceIDs) > 0 {
107 if len(matchedManagedDeviceIDs) == 1 {
108 matchedDeviceID := matchedManagedDeviceIDs[0]
109 if segmentContainsDevice(segment, matchedDeviceID) {
110 if b.out.suppressedManagedOverlapIDs == nil {
111 b.out.suppressedManagedOverlapIDs = make(map[string]struct{})
112 }
113 b.out.suppressedManagedOverlapIDs[normalizeFDBEndpointID(endpointID)] = struct{}{}
114 b.out.endpointLinksSuppressed++
115 continue
116 }
117 if matchedDevice, ok := b.deviceByID[matchedDeviceID]; ok {
118 edgeKey := segmentID + "|managed-device|" + matchedDeviceID
119 if _, seen := b.endpointSegmentEdgeSeen[edgeKey]; !seen {
120 b.endpointSegmentEdgeSeen[edgeKey] = struct{}{}
121 b.out.links = append(b.out.links, Link{
122 Layer: b.layer,
123 Protocol: "fdb",
124 LinkType: "fdb",
125 Direction: "bidirectional",
126 Src: segmentEndpoint,
127 Dst: adjacencySideToEndpoint(matchedDevice, "", b.ifIndexByDeviceName, b.ifaceByDeviceIndex),
128 DiscoveredAt: topologyTimePtr(b.collectedAt),
129 LastSeen: topologyTimePtr(b.collectedAt),
130 Metrics: map[string]any{
131 "bridge_domain": segmentID,
132 "attachment_mode": "managed_device_overlap",
133 },
134 })
135 b.out.linksFdb++
136 b.out.bidirectionalCount++
137 b.out.endpointLinksEmitted++
138 segmentsWithAnyLinks[segmentID] = struct{}{}
139 }
140 if b.out.suppressedManagedOverlapIDs == nil {
141 b.out.suppressedManagedOverlapIDs = make(map[string]struct{})
142 }
143 b.out.suppressedManagedOverlapIDs[normalizeFDBEndpointID(endpointID)] = struct{}{}
144 continue
145 }
146 }
147 if b.out.suppressedManagedOverlapIDs == nil {
148 b.out.suppressedManagedOverlapIDs = make(map[string]struct{})
149 }
150 b.out.suppressedManagedOverlapIDs[normalizeFDBEndpointID(endpointID)] = struct{}{}
151 b.out.endpointLinksSuppressed++
152 continue
153 }
154 if !b.probabilisticConnectivity {
155 b.out.endpointLinksSuppressed++
156 continue
157 }
158 b.allowEndpoint(segmentID, endpointID, true, "probable_segment")
159 }
160
161 if owner, hasOwner := b.out.endpointDirectOwners[endpointID]; hasOwner &&
162 strings.EqualFold(strings.TrimSpace(owner.source), "single_port_mac") {
163 device, ok := b.deviceByID[owner.port.deviceID]
164 if ok {
165 localPort := bridgePortDisplay(owner.port)
166 if localPort != "" {
167 edgeKey := "direct" + keySep + bridgePortObservationVLANKey(owner.port) + keySep + endpointID
168 if _, seen := b.endpointSegmentEdgeSeen[edgeKey]; !seen {
169 b.endpointSegmentEdgeSeen[edgeKey] = struct{}{}
170 metrics := map[string]any{
171 "attachment_mode": "direct",
172 }
173 linkState := ""
174 if probableSet := b.probableEndpointBySegment[segmentID]; len(probableSet) > 0 {
175 if _, isProbable := probableSet[endpointID]; isProbable {
176 metrics["attachment_mode"] = "probable_direct"
177 metrics["inference"] = "probable"
178 metrics["confidence"] = "low"
179 linkState = "probable"
180 }
181 }
182 b.out.links = append(b.out.links, Link{
183 Layer: b.layer,
184 Protocol: "fdb",
185 LinkType: "fdb",
186 Direction: "bidirectional",
187 Src: adjacencySideToEndpoint(device, localPort, b.ifIndexByDeviceName, b.ifaceByDeviceIndex),
188 Dst: LinkEndpoint{Match: endpointMatch},
189 DiscoveredAt: topologyTimePtr(b.collectedAt),
190 LastSeen: topologyTimePtr(b.collectedAt),
191 State: linkState,
192 Metrics: metrics,
193 })
194 b.out.linksFdb++
195 b.out.bidirectionalCount++
196 b.out.endpointLinksEmitted++
197 continue
198 }
199 }
200 }
201 }
202
203 edgeKey := segmentID + keySep + endpointID
204 if _, seen := b.endpointSegmentEdgeSeen[edgeKey]; seen {
205 continue
206 }
207 b.endpointSegmentEdgeSeen[edgeKey] = struct{}{}
208
209 metrics := map[string]any{
210 "bridge_domain": segmentID,
211 }
212 linkState := ""
213 if probableSet := b.probableEndpointBySegment[segmentID]; len(probableSet) > 0 {
214 if _, isProbable := probableSet[endpointID]; isProbable {
215 probableMode := ""
216 if modes := b.probableAttachmentModes[segmentID]; len(modes) > 0 {
217 probableMode = strings.TrimSpace(modes[endpointID])
218 }
219 if probableMode == "" {
220 probableMode = "probable_segment"
221 }
222 metrics["attachment_mode"] = probableMode
223 metrics["inference"] = "probable"
224 metrics["confidence"] = "low"
225 linkState = "probable"
226 }
227 }
228
229 b.out.links = append(b.out.links, Link{
230 Layer: b.layer,
231 Protocol: "fdb",
232 LinkType: "fdb",
233 Direction: "bidirectional",
234 Src: segmentEndpoint,
235 Dst: LinkEndpoint{Match: endpointMatch},
236 DiscoveredAt: topologyTimePtr(b.collectedAt),
237 LastSeen: topologyTimePtr(b.collectedAt),
238 State: linkState,
239 Metrics: metrics,
240 })
241 b.out.linksFdb++
242 b.out.bidirectionalCount++
243 b.out.endpointLinksEmitted++
244 segmentsWithAnyLinks[segmentID] = struct{}{}
245 }
246 }
247
248 b.pruneSegmentsWithoutLinks(segmentsWithAnyLinks)
249 }
250
251 func (b *segmentProjectionBuilder) pruneSegmentsWithoutLinks(segmentsWithAnyLinks map[string]struct{}) {
252 if len(segmentsWithAnyLinks) >= len(b.segmentIDs) {
253 return
254 }
255
256 filteredActors := make([]Actor, 0, len(b.out.actors))
257 for _, actor := range b.out.actors {
258 segmentID := topologyAttrString(actor.Attributes, "segment_id")
259 if segmentID == "" {
260 continue
261 }
262 if _, ok := segmentsWithAnyLinks[segmentID]; ok {
263 filteredActors = append(filteredActors, actor)
264 }
265 }
266 b.out.actors = filteredActors
267
268 filteredLinks := make([]Link, 0, len(b.out.links))
269 b.out.linksFdb = 0
270 b.out.bidirectionalCount = 0
271 b.out.endpointLinksEmitted = 0
272 for _, link := range b.out.links {
273 segmentID := topologyMetricString(link.Metrics, "bridge_domain")
274 if segmentID != "" {
275 if _, ok := segmentsWithAnyLinks[segmentID]; !ok {
276 continue
277 }
278 }
279 filteredLinks = append(filteredLinks, link)
280 b.out.linksFdb++
281 if strings.EqualFold(strings.TrimSpace(link.Direction), "bidirectional") {
282 b.out.bidirectionalCount++
283 }
284 if strings.EqualFold(strings.TrimSpace(link.Protocol), "fdb") {
285 b.out.endpointLinksEmitted++
286 }
287 }
288 b.out.links = filteredLinks
289 }