master
go 145 lines 3.66 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 inferFDBPairwiseBridgeLinks(
11 attachments []Attachment,
12 ifaceByDeviceIndex map[string]Interface,
13 reporterAliases map[string][]string,
14 ) []bridgeBridgeLinkRecord {
15 if len(attachments) == 0 || len(reporterAliases) == 0 {
16 return nil
17 }
18
19 aliasOwnerIDs := buildFDBAliasOwnerMap(reporterAliases)
20 if len(aliasOwnerIDs) == 0 {
21 return nil
22 }
23
24 // reporterA -> reporterB -> unique reporter ports where A learns aliases of B.
25 pairs := make(map[string]map[string]map[string]bridgePortRef)
26 for _, attachment := range attachments {
27 if !strings.EqualFold(strings.TrimSpace(attachment.Method), "fdb") {
28 continue
29 }
30 reporterID := strings.TrimSpace(attachment.DeviceID)
31 if reporterID == "" {
32 continue
33 }
34 endpointID := normalizeFDBEndpointID(attachment.EndpointID)
35 if endpointID == "" {
36 continue
37 }
38 owners := aliasOwnerIDs[endpointID]
39 if len(owners) == 0 {
40 continue
41 }
42 port := bridgePortFromAttachment(attachment, ifaceByDeviceIndex)
43 portKey := bridgePortObservationKey(port)
44 if portKey == "" {
45 continue
46 }
47 for ownerID := range owners {
48 ownerID = strings.TrimSpace(ownerID)
49 if ownerID == "" || strings.EqualFold(ownerID, reporterID) {
50 continue
51 }
52 byPeer := pairs[reporterID]
53 if byPeer == nil {
54 byPeer = make(map[string]map[string]bridgePortRef)
55 pairs[reporterID] = byPeer
56 }
57 ports := byPeer[ownerID]
58 if ports == nil {
59 ports = make(map[string]bridgePortRef)
60 byPeer[ownerID] = ports
61 }
62 ports[portKey] = port
63 }
64 }
65 if len(pairs) == 0 {
66 return nil
67 }
68
69 records := make([]bridgeBridgeLinkRecord, 0)
70 seen := make(map[string]struct{})
71 leftIDs := make([]string, 0, len(pairs))
72 for leftID := range pairs {
73 leftIDs = append(leftIDs, leftID)
74 }
75 sort.Strings(leftIDs)
76 for _, leftID := range leftIDs {
77 neighbors := pairs[leftID]
78 if len(neighbors) == 0 {
79 continue
80 }
81 rightIDs := make([]string, 0, len(neighbors))
82 for rightID := range neighbors {
83 rightIDs = append(rightIDs, rightID)
84 }
85 sort.Strings(rightIDs)
86 for _, rightID := range rightIDs {
87 if leftID >= rightID {
88 continue
89 }
90 leftPorts := pairs[leftID][rightID]
91 rightPorts := pairs[rightID][leftID]
92 if len(leftPorts) != 1 || len(rightPorts) != 1 {
93 // Conservative rule: infer direct bridge link only when each side reports
94 // exactly one reciprocal managed-alias learning port.
95 continue
96 }
97 leftPort := firstSortedBridgePort(leftPorts)
98 rightPort := firstSortedBridgePort(rightPorts)
99 if bridgePortObservationKey(leftPort) == "" || bridgePortObservationKey(rightPort) == "" {
100 continue
101 }
102 key := bridgePairKey(leftPort, rightPort)
103 if key == "" {
104 continue
105 }
106 if _, ok := seen[key]; ok {
107 continue
108 }
109 seen[key] = struct{}{}
110
111 designated := leftPort
112 other := rightPort
113 if bridgePortRefSortKey(leftPort) > bridgePortRefSortKey(rightPort) {
114 designated = rightPort
115 other = leftPort
116 }
117 records = append(records, bridgeBridgeLinkRecord{
118 port: other,
119 designatedPort: designated,
120 method: "fdb_pairwise",
121 })
122 }
123 }
124 if len(records) == 0 {
125 return nil
126 }
127 sort.SliceStable(records, func(i, j int) bool {
128 li := portSortKey(records[i].designatedPort) + keySep + portSortKey(records[i].port)
129 lj := portSortKey(records[j].designatedPort) + keySep + portSortKey(records[j].port)
130 return li < lj
131 })
132 return records
133 }
134
135 func firstSortedBridgePort(ports map[string]bridgePortRef) bridgePortRef {
136 if len(ports) == 0 {
137 return bridgePortRef{}
138 }
139 keys := make([]string, 0, len(ports))
140 for key := range ports {
141 keys = append(keys, key)
142 }
143 sort.Strings(keys)
144 return ports[keys[0]]
145 }