| 1 | // SPDX-License-Identifier: GPL-3.0-or-later |
| 2 | |
| 3 | package l2topology |
| 4 | |
| 5 | import ( |
| 6 | "strconv" |
| 7 | "strings" |
| 8 | ) |
| 9 | |
| 10 | func (b *deviceInterfaceSummaryBuilder) collectInterfaces() { |
| 11 | for _, iface := range b.interfaces { |
| 12 | deviceID := strings.TrimSpace(iface.DeviceID) |
| 13 | if deviceID == "" || iface.IfIndex <= 0 { |
| 14 | continue |
| 15 | } |
| 16 | col := b.collectors[deviceID] |
| 17 | if col == nil { |
| 18 | col = &deviceInterfaceCollector{ |
| 19 | ifIndexes: make(map[string]struct{}), |
| 20 | ifNames: make(map[string]struct{}), |
| 21 | ifTypes: make(map[string]int), |
| 22 | adminCounts: make(map[string]int), |
| 23 | operCounts: make(map[string]int), |
| 24 | portEvidence: make(map[int]*topologyDevicePortEvidence), |
| 25 | } |
| 26 | b.collectors[deviceID] = col |
| 27 | } |
| 28 | |
| 29 | ifIndex := strconv.Itoa(iface.IfIndex) |
| 30 | col.ifIndexes[ifIndex] = struct{}{} |
| 31 | if ifName := strings.TrimSpace(iface.IfName); ifName != "" { |
| 32 | col.ifNames[ifName] = struct{}{} |
| 33 | } |
| 34 | |
| 35 | admin := strings.TrimSpace(iface.Labels["admin_status"]) |
| 36 | oper := strings.TrimSpace(iface.Labels["oper_status"]) |
| 37 | ifType := strings.TrimSpace(iface.Labels["if_type"]) |
| 38 | ifAlias := strings.TrimSpace(iface.Labels["if_alias"]) |
| 39 | ifDescr := strings.TrimSpace(iface.IfDescr) |
| 40 | if ifDescr == "" { |
| 41 | ifDescr = strings.TrimSpace(iface.IfName) |
| 42 | } |
| 43 | speedBps := parseTopologyLabelInt64(iface.Labels["speed_bps"]) |
| 44 | lastChange := parseTopologyLabelInt64(iface.Labels["last_change"]) |
| 45 | duplex := normalizeTopologyDuplex(iface.Labels["duplex"]) |
| 46 | mac := normalizeMAC(iface.MAC) |
| 47 | if mac == "" { |
| 48 | mac = normalizeMAC(iface.Labels["mac"]) |
| 49 | } |
| 50 | if ifType != "" { |
| 51 | col.ifTypes[ifType]++ |
| 52 | } |
| 53 | if admin != "" { |
| 54 | col.adminCounts[admin]++ |
| 55 | } |
| 56 | if oper != "" { |
| 57 | col.operCounts[oper]++ |
| 58 | } |
| 59 | |
| 60 | col.portStatuses = append(col.portStatuses, topologyDevicePortStatus{ |
| 61 | IfIndex: iface.IfIndex, |
| 62 | IfName: strings.TrimSpace(iface.IfName), |
| 63 | IfDescr: ifDescr, |
| 64 | IfAlias: ifAlias, |
| 65 | MAC: mac, |
| 66 | SpeedBps: speedBps, |
| 67 | LastChange: lastChange, |
| 68 | Duplex: duplex, |
| 69 | InterfaceType: ifType, |
| 70 | AdminStatus: admin, |
| 71 | OperStatus: oper, |
| 72 | LinkMode: "unknown", |
| 73 | ModeConfidence: "low", |
| 74 | TopologyRole: "unknown", |
| 75 | RoleConfidence: "low", |
| 76 | }) |
| 77 | |
| 78 | if isTopologyLAGInterfaceType(ifType) { |
| 79 | evidence := ensureTopologyPortEvidence(col.portEvidence, iface.IfIndex) |
| 80 | evidence.isLAG = true |
| 81 | } |
| 82 | } |
| 83 | } |
| 84 | |
| 85 | func (b *deviceInterfaceSummaryBuilder) collectFDBAttachments() { |
| 86 | for _, attachment := range b.attachments { |
| 87 | deviceID := strings.TrimSpace(attachment.DeviceID) |
| 88 | if deviceID == "" || attachment.IfIndex <= 0 { |
| 89 | continue |
| 90 | } |
| 91 | col := b.collectors[deviceID] |
| 92 | if col == nil { |
| 93 | continue |
| 94 | } |
| 95 | if !strings.EqualFold(strings.TrimSpace(attachment.Method), "fdb") { |
| 96 | continue |
| 97 | } |
| 98 | fdbStatus := strings.ToLower(strings.TrimSpace(attachment.Labels["fdb_status"])) |
| 99 | if fdbStatus == "ignored" { |
| 100 | continue |
| 101 | } |
| 102 | |
| 103 | evidence := ensureTopologyPortEvidence(col.portEvidence, attachment.IfIndex) |
| 104 | evidence.hasFDB = true |
| 105 | endpointID := normalizeFDBEndpointID(attachment.EndpointID) |
| 106 | if endpointID == "" { |
| 107 | endpointID = strings.TrimSpace(attachment.EndpointID) |
| 108 | } |
| 109 | if endpointID != "" { |
| 110 | evidence.fdbEndpointIDs[endpointID] = struct{}{} |
| 111 | if aliasOwners, ok := b.managedAliasOwners[endpointID]; ok { |
| 112 | for aliasOwnerID := range aliasOwners { |
| 113 | if !strings.EqualFold(strings.TrimSpace(aliasOwnerID), deviceID) { |
| 114 | evidence.hasFDBManagedAlias = true |
| 115 | break |
| 116 | } |
| 117 | } |
| 118 | } |
| 119 | } |
| 120 | vlanID := normalizeTopologyVLANID(firstNonEmpty(attachment.Labels["vlan_id"], attachment.Labels["vlan"])) |
| 121 | if vlanID != "" { |
| 122 | evidence.vlanIDs[vlanID] = struct{}{} |
| 123 | if vlanName := strings.TrimSpace(attachment.Labels["vlan_name"]); vlanName != "" { |
| 124 | if _, exists := evidence.vlanNames[vlanID]; !exists { |
| 125 | evidence.vlanNames[vlanID] = vlanName |
| 126 | } |
| 127 | } |
| 128 | } |
| 129 | } |
| 130 | } |
| 131 | |
| 132 | func (b *deviceInterfaceSummaryBuilder) collectAdjacencyEvidence() { |
| 133 | for _, adj := range b.adjacencies { |
| 134 | deviceID := strings.TrimSpace(adj.SourceID) |
| 135 | if deviceID == "" { |
| 136 | continue |
| 137 | } |
| 138 | col := b.collectors[deviceID] |
| 139 | if col == nil { |
| 140 | continue |
| 141 | } |
| 142 | |
| 143 | ifIndex := resolveAdjacencySourceIfIndex(adj, b.ifIndexByDeviceName) |
| 144 | if ifIndex <= 0 { |
| 145 | continue |
| 146 | } |
| 147 | evidence := ensureTopologyPortEvidence(col.portEvidence, ifIndex) |
| 148 | protocol := strings.ToLower(strings.TrimSpace(adj.Protocol)) |
| 149 | switch protocol { |
| 150 | case "stp": |
| 151 | evidence.hasSTP = true |
| 152 | vlanID := normalizeTopologyVLANID(firstNonEmpty(adj.Labels["vlan_id"], adj.Labels["vlan"])) |
| 153 | if vlanID != "" { |
| 154 | evidence.vlanIDs[vlanID] = struct{}{} |
| 155 | if vlanName := strings.TrimSpace(adj.Labels["vlan_name"]); vlanName != "" { |
| 156 | if _, exists := evidence.vlanNames[vlanID]; !exists { |
| 157 | evidence.vlanNames[vlanID] = vlanName |
| 158 | } |
| 159 | } |
| 160 | } |
| 161 | if state := normalizeTopologySTPState(adj.Labels["stp_state"]); state != "" { |
| 162 | evidence.stpStates[state] = struct{}{} |
| 163 | } |
| 164 | case "lldp", "cdp": |
| 165 | evidence.hasPeer = true |
| 166 | neighbor := buildTopologyPortNeighborStatus(protocol, adj, b.deviceByID) |
| 167 | if key := topologyPortNeighborStatusKey(neighbor); key != "" { |
| 168 | evidence.neighbors[key] = neighbor |
| 169 | } |
| 170 | } |
| 171 | } |
| 172 | } |
| 173 | |
| 174 | func (b *deviceInterfaceSummaryBuilder) collectBridgeLinkEvidence() { |
| 175 | for _, link := range b.bridgeLinks { |
| 176 | for _, port := range []bridgePortRef{link.designatedPort, link.port} { |
| 177 | deviceID := strings.TrimSpace(port.deviceID) |
| 178 | if deviceID == "" || port.ifIndex <= 0 { |
| 179 | continue |
| 180 | } |
| 181 | col := b.collectors[deviceID] |
| 182 | if col == nil { |
| 183 | continue |
| 184 | } |
| 185 | evidence := ensureTopologyPortEvidence(col.portEvidence, port.ifIndex) |
| 186 | evidence.hasBridgeLink = true |
| 187 | } |
| 188 | } |
| 189 | } |