master
go 223 lines 5.35 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 func buildBridgeSegmentActor(segmentID string, segment *bridgeDomainSegment, layer string, source string) (Match, Actor) {
12 parentDevices := make(map[string]struct{})
13 ifNames := make(map[string]struct{})
14 ifIndexes := make(map[string]struct{})
15 bridgePorts := make(map[string]struct{})
16 vlanIDs := make(map[string]struct{})
17 if segment != nil {
18 for _, port := range segment.ports {
19 if strings.TrimSpace(port.deviceID) != "" {
20 parentDevices[port.deviceID] = struct{}{}
21 }
22 if strings.TrimSpace(port.ifName) != "" {
23 ifNames[port.ifName] = struct{}{}
24 }
25 if port.ifIndex > 0 {
26 ifIndexes[strconv.Itoa(port.ifIndex)] = struct{}{}
27 }
28 if strings.TrimSpace(port.bridgePort) != "" {
29 bridgePorts[port.bridgePort] = struct{}{}
30 }
31 if strings.TrimSpace(port.vlanID) != "" {
32 vlanIDs[port.vlanID] = struct{}{}
33 }
34 }
35 }
36
37 match := Match{
38 Hostnames: []string{"segment:" + segmentID},
39 }
40
41 attrs := map[string]any{
42 "segment_id": segmentID,
43 "segment_type": "broadcast_domain",
44 "parent_devices": sortedTopologySet(parentDevices),
45 "if_names": sortedTopologySet(ifNames),
46 "if_indexes": sortedTopologySet(ifIndexes),
47 "bridge_ports": sortedTopologySet(bridgePorts),
48 "vlan_ids": sortedTopologySet(vlanIDs),
49 "ports_total": 0,
50 "endpoints_total": 0,
51 }
52 if segment != nil {
53 attrs["learned_sources"] = sortedTopologySet(segment.methods)
54 attrs["ports_total"] = len(segment.ports)
55 attrs["endpoints_total"] = len(segment.endpointIDs)
56 if bridgePortRefKey(segment.designatedPort, false, false) != "" {
57 attrs["designated_port"] = bridgePortRefSortKey(segment.designatedPort)
58 }
59 }
60
61 actor := Actor{
62 ActorType: "segment",
63 Layer: layer,
64 Source: source,
65 Match: match,
66 Attributes: pruneTopologyAttributes(attrs),
67 Labels: map[string]string{
68 "segment_kind": "broadcast_domain",
69 },
70 }
71
72 return match, actor
73 }
74
75 func endpointMatchFromID(endpointID string) Match {
76 kind, value, ok := strings.Cut(strings.TrimSpace(endpointID), ":")
77 if !ok {
78 return Match{}
79 }
80 switch strings.ToLower(strings.TrimSpace(kind)) {
81 case "mac":
82 mac := normalizeMAC(value)
83 if mac == "" {
84 return Match{}
85 }
86 return Match{
87 ChassisIDs: []string{mac},
88 MacAddresses: []string{mac},
89 }
90 case "ip":
91 addr := normalizeTopologyIP(value)
92 if addr == "" {
93 return Match{}
94 }
95 return Match{
96 IPAddresses: []string{addr},
97 }
98 }
99 return Match{}
100 }
101
102 func annotateEndpointActorsWithDirectOwners(
103 actors []Actor,
104 endpointMatchByID map[string]Match,
105 owners map[string]fdbEndpointOwner,
106 deviceByID map[string]Device,
107 ) {
108 if len(actors) == 0 || len(owners) == 0 {
109 return
110 }
111
112 ownerByMatchKey := make(map[string]fdbEndpointOwner, len(owners))
113 endpointIDs := make([]string, 0, len(owners))
114 for endpointID := range owners {
115 endpointIDs = append(endpointIDs, endpointID)
116 }
117 sort.Strings(endpointIDs)
118
119 for _, endpointID := range endpointIDs {
120 owner := owners[endpointID]
121 if !strings.EqualFold(strings.TrimSpace(owner.source), "single_port_mac") {
122 continue
123 }
124 match, ok := endpointMatchByID[endpointID]
125 if !ok {
126 match = endpointMatchFromID(endpointID)
127 }
128 key := canonicalTopologyMatchKey(match)
129 if key == "" {
130 continue
131 }
132 ownerByMatchKey[key] = owner
133 }
134
135 if len(ownerByMatchKey) == 0 {
136 return
137 }
138
139 for i := range actors {
140 actor := &actors[i]
141 if !strings.EqualFold(strings.TrimSpace(actor.ActorType), "endpoint") {
142 continue
143 }
144 key := canonicalTopologyMatchKey(actor.Match)
145 if key == "" {
146 continue
147 }
148 owner, ok := ownerByMatchKey[key]
149 if !ok {
150 continue
151 }
152
153 attrs := cloneAnyMap(actor.Attributes)
154 if attrs == nil {
155 attrs = make(map[string]any)
156 }
157 labels := cloneStringMap(actor.Labels)
158 if labels == nil {
159 labels = make(map[string]string)
160 }
161
162 deviceID := strings.TrimSpace(owner.port.deviceID)
163 port := bridgePortDisplay(owner.port)
164 ifName := strings.TrimSpace(owner.port.ifName)
165 bridgePort := strings.TrimSpace(owner.port.bridgePort)
166 vlanID := strings.TrimSpace(owner.port.vlanID)
167
168 attrs["attachment_source"] = "single_port_mac"
169 if deviceID != "" {
170 attrs["attached_device_id"] = deviceID
171 labels["attached_device_id"] = deviceID
172 }
173 if port != "" {
174 attrs["attached_port"] = port
175 labels["attached_port"] = port
176 }
177 if ifName != "" {
178 attrs["attached_if_name"] = ifName
179 }
180 if owner.port.ifIndex > 0 {
181 attrs["attached_if_index"] = owner.port.ifIndex
182 }
183 if bridgePort != "" {
184 attrs["attached_bridge_port"] = bridgePort
185 }
186 if vlanID != "" {
187 attrs["attached_vlan"] = vlanID
188 attrs["attached_vlan_id"] = vlanID
189 }
190 if device, ok := deviceByID[deviceID]; ok {
191 display := strings.TrimSpace(device.Hostname)
192 if display == "" {
193 display = deviceID
194 }
195 if display != "" {
196 attrs["attached_device"] = display
197 labels["attached_device"] = display
198 }
199 }
200 labels["attached_by"] = "single_port_mac"
201
202 actor.Attributes = pruneTopologyAttributes(attrs)
203 if len(labels) > 0 {
204 actor.Labels = labels
205 }
206 }
207 }
208
209 func segmentContainsDevice(segment *bridgeDomainSegment, deviceID string) bool {
210 if segment == nil {
211 return false
212 }
213 deviceID = strings.TrimSpace(deviceID)
214 if deviceID == "" {
215 return false
216 }
217 for _, port := range segment.ports {
218 if strings.EqualFold(strings.TrimSpace(port.deviceID), deviceID) {
219 return true
220 }
221 }
222 return false
223 }