master
go 222 lines 5.92 KB
Raw
1 // SPDX-License-Identifier: GPL-3.0-or-later
2
3 package l2topology
4
5 import (
6 "fmt"
7 "net/netip"
8 "strconv"
9 "strings"
10 )
11
12 func (s *l2BuildState) registerObservations(observations []L2Observation) error {
13 for _, obs := range observations {
14 if err := s.registerObservation(obs); err != nil {
15 return err
16 }
17 }
18 return nil
19 }
20
21 func (s *l2BuildState) registerObservation(obs L2Observation) error {
22 deviceID := strings.TrimSpace(obs.DeviceID)
23 if deviceID == "" {
24 return fmt.Errorf("observation with empty device id")
25 }
26
27 device := Device{
28 ID: deviceID,
29 Hostname: strings.TrimSpace(obs.Hostname),
30 SysObject: strings.TrimSpace(obs.SysObjectID),
31 ChassisID: strings.TrimSpace(obs.ChassisID),
32 }
33 if primaryMAC := primaryL2MACIdentity(obs.ChassisID, obs.BaseBridgeAddress); primaryMAC != "" {
34 device.ChassisID = primaryMAC
35 }
36 if !obs.Inferred {
37 s.managedObservationByDeviceID[deviceID] = true
38 }
39 if device.Hostname == "" {
40 device.Hostname = device.ID
41 }
42 if addr := parseAddr(obs.ManagementIP); addr.IsValid() {
43 device.Addresses = []netip.Addr{addr}
44 }
45 if len(device.Labels) == 0 {
46 device.Labels = make(map[string]string)
47 }
48 observedProtocols := observationProtocolsUsed(obs)
49 if existing, ok := s.devices[device.ID]; ok {
50 device = mergeObservedDevice(existing, device)
51 if device.Labels == nil {
52 device.Labels = make(map[string]string)
53 }
54 for protocol := range csvToTopologySet(existing.Labels["protocols_observed"]) {
55 observedProtocols[protocol] = struct{}{}
56 }
57 }
58 if len(observedProtocols) > 0 {
59 device.Labels["protocols_observed"] = setToCSV(observedProtocols)
60 }
61 s.devices[device.ID] = device
62
63 if host := canonicalHost(device.Hostname); host != "" {
64 s.hostToID[host] = device.ID
65 }
66 if ip := canonicalIP(obs.ManagementIP); ip != "" {
67 s.ipToID[ip] = device.ID
68 }
69 if mac := primaryL2MACIdentity(device.ChassisID, ""); mac != "" {
70 if _, exists := s.macToID[mac]; !exists {
71 s.macToID[mac] = device.ID
72 }
73 s.chassisToID[canonicalToken(mac)] = device.ID
74 } else if chassis := canonicalToken(device.ChassisID); chassis != "" {
75 s.chassisToID[chassis] = device.ID
76 }
77 if bridgeAddr := canonicalBridgeAddr(obs.BaseBridgeAddress, device.ChassisID); bridgeAddr != "" {
78 if _, exists := s.bridgeAddrToID[bridgeAddr]; !exists {
79 s.bridgeAddrToID[bridgeAddr] = device.ID
80 }
81 }
82
83 for _, iface := range obs.Interfaces {
84 if iface.IfIndex <= 0 {
85 continue
86 }
87 ifName := strings.TrimSpace(iface.IfName)
88 ifDescr := strings.TrimSpace(iface.IfDescr)
89 if ifName == "" {
90 ifName = ifDescr
91 }
92 if ifDescr == "" {
93 ifDescr = ifName
94 }
95 if ifName == "" {
96 continue
97 }
98 engIface := Interface{
99 DeviceID: device.ID,
100 IfIndex: iface.IfIndex,
101 IfName: ifName,
102 IfDescr: ifDescr,
103 MAC: normalizeMAC(iface.MAC),
104 }
105 if ifType := strings.TrimSpace(iface.InterfaceType); ifType != "" {
106 if engIface.Labels == nil {
107 engIface.Labels = make(map[string]string)
108 }
109 engIface.Labels["if_type"] = ifType
110 }
111 if admin := strings.TrimSpace(iface.AdminStatus); admin != "" {
112 if engIface.Labels == nil {
113 engIface.Labels = make(map[string]string)
114 }
115 engIface.Labels["admin_status"] = admin
116 }
117 if oper := strings.TrimSpace(iface.OperStatus); oper != "" {
118 if engIface.Labels == nil {
119 engIface.Labels = make(map[string]string)
120 }
121 engIface.Labels["oper_status"] = oper
122 }
123 if ifAlias := strings.TrimSpace(iface.IfAlias); ifAlias != "" {
124 if engIface.Labels == nil {
125 engIface.Labels = make(map[string]string)
126 }
127 engIface.Labels["if_alias"] = ifAlias
128 }
129 if iface.SpeedBps > 0 {
130 if engIface.Labels == nil {
131 engIface.Labels = make(map[string]string)
132 }
133 engIface.Labels["speed_bps"] = strconv.FormatInt(iface.SpeedBps, 10)
134 }
135 if iface.LastChange > 0 {
136 if engIface.Labels == nil {
137 engIface.Labels = make(map[string]string)
138 }
139 engIface.Labels["last_change"] = strconv.FormatInt(iface.LastChange, 10)
140 }
141 if duplex := strings.TrimSpace(iface.Duplex); duplex != "" {
142 if engIface.Labels == nil {
143 engIface.Labels = make(map[string]string)
144 }
145 engIface.Labels["duplex"] = duplex
146 }
147 if engIface.MAC != "" {
148 if engIface.Labels == nil {
149 engIface.Labels = make(map[string]string)
150 }
151 engIface.Labels["mac"] = engIface.MAC
152 }
153 s.interfaces[ifaceKey(engIface)] = engIface
154 s.ifNameByDeviceIfIndex[deviceIfIndexKey(device.ID, iface.IfIndex)] = ifName
155 }
156
157 return nil
158 }
159
160 func mergeObservedDevice(existing, incoming Device) Device {
161 out := existing
162 if strings.TrimSpace(out.ID) == "" {
163 out.ID = incoming.ID
164 }
165 if strings.TrimSpace(incoming.Hostname) != "" && (strings.TrimSpace(out.Hostname) == "" || out.Hostname == out.ID) {
166 out.Hostname = incoming.Hostname
167 }
168 if strings.TrimSpace(out.SysObject) == "" {
169 out.SysObject = incoming.SysObject
170 }
171 if strings.TrimSpace(out.ChassisID) == "" {
172 out.ChassisID = incoming.ChassisID
173 }
174 out.Addresses = mergeObservedDeviceAddresses(existing.Addresses, incoming.Addresses)
175 out.Labels = mergeObservedDeviceLabels(existing.Labels, incoming.Labels)
176 if strings.TrimSpace(out.Hostname) == "" {
177 out.Hostname = out.ID
178 }
179 return out
180 }
181
182 func mergeObservedDeviceAddresses(existing, incoming []netip.Addr) []netip.Addr {
183 if len(existing) == 0 && len(incoming) == 0 {
184 return nil
185 }
186 merged := make(map[string]netip.Addr, len(existing)+len(incoming))
187 for _, addr := range existing {
188 if addr.IsValid() {
189 merged[addr.String()] = addr
190 }
191 }
192 for _, addr := range incoming {
193 if addr.IsValid() {
194 merged[addr.String()] = addr
195 }
196 }
197 return sortedAddrValues(merged)
198 }
199
200 func mergeObservedDeviceLabels(existing, incoming map[string]string) map[string]string {
201 if len(existing) == 0 && len(incoming) == 0 {
202 return nil
203 }
204 out := make(map[string]string, len(existing)+len(incoming))
205 for key, value := range existing {
206 if value != "" {
207 out[key] = value
208 }
209 }
210 for key, value := range incoming {
211 if value == "" {
212 continue
213 }
214 if strings.TrimSpace(out[key]) == "" {
215 out[key] = value
216 }
217 }
218 if len(out) == 0 {
219 return nil
220 }
221 return out
222 }