| 1 | // SPDX-License-Identifier: GPL-3.0-or-later |
| 2 | |
| 3 | package l2topology |
| 4 | |
| 5 | import ( |
| 6 | "sort" |
| 7 | "strings" |
| 8 | "time" |
| 9 | ) |
| 10 | |
| 11 | func projectSegmentTopology( |
| 12 | attachments []Attachment, |
| 13 | adjacencies []Adjacency, |
| 14 | layer string, |
| 15 | source string, |
| 16 | collectedAt time.Time, |
| 17 | deviceByID map[string]Device, |
| 18 | ifaceByDeviceIndex map[string]Interface, |
| 19 | ifIndexByDeviceName map[string]int, |
| 20 | bridgeLinks []bridgeBridgeLinkRecord, |
| 21 | reporterAliases map[string][]string, |
| 22 | endpointMatchByID map[string]Match, |
| 23 | endpointLabelsByID map[string]map[string]string, |
| 24 | actorIndex map[string]struct{}, |
| 25 | probabilisticConnectivity bool, |
| 26 | strategyConfig topologyInferenceStrategyConfig, |
| 27 | ) projectedSegments { |
| 28 | return newSegmentProjectionBuilder( |
| 29 | attachments, |
| 30 | adjacencies, |
| 31 | layer, |
| 32 | source, |
| 33 | collectedAt, |
| 34 | deviceByID, |
| 35 | ifaceByDeviceIndex, |
| 36 | ifIndexByDeviceName, |
| 37 | bridgeLinks, |
| 38 | reporterAliases, |
| 39 | endpointMatchByID, |
| 40 | endpointLabelsByID, |
| 41 | actorIndex, |
| 42 | probabilisticConnectivity, |
| 43 | strategyConfig, |
| 44 | ).build() |
| 45 | } |
| 46 | |
| 47 | func pickProbableSegmentAnchorPortID( |
| 48 | segment *bridgeDomainSegment, |
| 49 | probableEndpoints map[string]struct{}, |
| 50 | fdbOwners map[string]fdbEndpointOwner, |
| 51 | managedDeviceIDs map[string]struct{}, |
| 52 | ) string { |
| 53 | if segment == nil || len(segment.ports) == 0 { |
| 54 | return "" |
| 55 | } |
| 56 | |
| 57 | portIDs := make([]string, 0, len(segment.ports)) |
| 58 | portIDByObservation := make(map[string]string, len(segment.ports)*2) |
| 59 | designatedPortID := "" |
| 60 | managedPortIDs := make(map[string]struct{}) |
| 61 | for portID, port := range segment.ports { |
| 62 | portIDs = append(portIDs, portID) |
| 63 | if key := bridgePortObservationKey(port); key != "" { |
| 64 | portIDByObservation[key] = portID |
| 65 | } |
| 66 | if key := bridgePortObservationVLANKey(port); key != "" { |
| 67 | portIDByObservation[key] = portID |
| 68 | } |
| 69 | if segment.portIdentityKey(port) == segment.portIdentityKey(segment.designatedPort) { |
| 70 | designatedPortID = portID |
| 71 | } |
| 72 | if len(managedDeviceIDs) == 0 { |
| 73 | managedPortIDs[portID] = struct{}{} |
| 74 | continue |
| 75 | } |
| 76 | if _, ok := managedDeviceIDs[strings.TrimSpace(port.deviceID)]; ok { |
| 77 | managedPortIDs[portID] = struct{}{} |
| 78 | } |
| 79 | } |
| 80 | sort.Strings(portIDs) |
| 81 | preferManaged := len(managedPortIDs) > 0 |
| 82 | allowPortID := func(portID string) bool { |
| 83 | if !preferManaged { |
| 84 | return true |
| 85 | } |
| 86 | _, ok := managedPortIDs[portID] |
| 87 | return ok |
| 88 | } |
| 89 | |
| 90 | endpointIDs := sortedTopologySet(probableEndpoints) |
| 91 | for _, endpointID := range endpointIDs { |
| 92 | owner, ok := fdbOwners[endpointID] |
| 93 | if !ok { |
| 94 | continue |
| 95 | } |
| 96 | if portID, ok := portIDByObservation[owner.portVLANKey]; ok { |
| 97 | if allowPortID(portID) { |
| 98 | return portID |
| 99 | } |
| 100 | } |
| 101 | if portID, ok := portIDByObservation[owner.portKey]; ok { |
| 102 | if allowPortID(portID) { |
| 103 | return portID |
| 104 | } |
| 105 | } |
| 106 | } |
| 107 | |
| 108 | if designatedPortID != "" && allowPortID(designatedPortID) { |
| 109 | return designatedPortID |
| 110 | } |
| 111 | if preferManaged { |
| 112 | managedPortIDList := make([]string, 0, len(managedPortIDs)) |
| 113 | for portID := range managedPortIDs { |
| 114 | managedPortIDList = append(managedPortIDList, portID) |
| 115 | } |
| 116 | sort.Strings(managedPortIDList) |
| 117 | if len(managedPortIDList) > 0 { |
| 118 | return managedPortIDList[0] |
| 119 | } |
| 120 | } |
| 121 | if designatedPortID != "" { |
| 122 | return designatedPortID |
| 123 | } |
| 124 | return portIDs[0] |
| 125 | } |
| 126 | |
| 127 | func segmentHasManagedPort(segment *bridgeDomainSegment, managedDeviceIDs map[string]struct{}) bool { |
| 128 | if segment == nil || len(segment.ports) == 0 { |
| 129 | return false |
| 130 | } |
| 131 | if len(managedDeviceIDs) == 0 { |
| 132 | return true |
| 133 | } |
| 134 | for _, port := range segment.ports { |
| 135 | if _, ok := managedDeviceIDs[strings.TrimSpace(port.deviceID)]; ok { |
| 136 | return true |
| 137 | } |
| 138 | } |
| 139 | return false |
| 140 | } |
| 141 | |
| 142 | func pickMostProbableSegment( |
| 143 | candidates []string, |
| 144 | endpointLabels map[string]string, |
| 145 | segmentIfIndexes map[string]map[string]struct{}, |
| 146 | segmentIfNames map[string]map[string]struct{}, |
| 147 | ) string { |
| 148 | if len(candidates) == 0 { |
| 149 | return "" |
| 150 | } |
| 151 | if len(candidates) == 1 { |
| 152 | return candidates[0] |
| 153 | } |
| 154 | |
| 155 | ifIndexes := make(map[string]struct{}) |
| 156 | for _, ifIndex := range labelsCSVToSlice(endpointLabels, "learned_if_indexes") { |
| 157 | ifIndex = strings.TrimSpace(ifIndex) |
| 158 | if ifIndex == "" { |
| 159 | continue |
| 160 | } |
| 161 | ifIndexes[ifIndex] = struct{}{} |
| 162 | } |
| 163 | ifNames := make(map[string]struct{}) |
| 164 | for _, ifName := range labelsCSVToSlice(endpointLabels, "learned_if_names") { |
| 165 | ifName = strings.ToLower(strings.TrimSpace(ifName)) |
| 166 | if ifName == "" { |
| 167 | continue |
| 168 | } |
| 169 | ifNames[ifName] = struct{}{} |
| 170 | } |
| 171 | |
| 172 | bestID := "" |
| 173 | bestScore := -1 |
| 174 | for _, segmentID := range candidates { |
| 175 | score := 0 |
| 176 | for ifIndex := range ifIndexes { |
| 177 | if indexes := segmentIfIndexes[segmentID]; indexes != nil { |
| 178 | if _, ok := indexes[ifIndex]; ok { |
| 179 | score += 2 |
| 180 | } |
| 181 | } |
| 182 | } |
| 183 | for ifName := range ifNames { |
| 184 | if names := segmentIfNames[segmentID]; names != nil { |
| 185 | if _, ok := names[ifName]; ok { |
| 186 | score++ |
| 187 | } |
| 188 | } |
| 189 | } |
| 190 | if score > bestScore { |
| 191 | bestScore = score |
| 192 | bestID = segmentID |
| 193 | continue |
| 194 | } |
| 195 | if score == bestScore && (bestID == "" || segmentID < bestID) { |
| 196 | bestID = segmentID |
| 197 | } |
| 198 | } |
| 199 | if bestID != "" { |
| 200 | return bestID |
| 201 | } |
| 202 | return candidates[0] |
| 203 | } |