package collector import ( "bee/audit/internal/schema" "log/slog" "os" "path/filepath" "regexp" "sort" "strconv" "strings" ) var ( ethtoolModuleQuery = func(iface string) (string, error) { out, err := commandOutputWithTimeout(nicProbeTimeout, "ethtool", "-m", iface) if err != nil { return "", err } return string(out), nil } readNetAddressFile = func(iface string) (string, error) { path := filepath.Join("/sys/class/net", iface, "address") raw, err := os.ReadFile(path) if err != nil { return "", err } return strings.TrimSpace(string(raw)), nil } ) func enrichPCIeWithNICTelemetry(devs []schema.HardwarePCIeDevice) []schema.HardwarePCIeDevice { enriched := 0 for i := range devs { if !isNICDevice(devs[i]) || devs[i].BDF == nil { continue } bdf := normalizePCIeBDF(*devs[i].BDF) if bdf == "" { continue } ifaces := netIfacesByBDF(bdf) if len(ifaces) == 0 { continue } sort.Strings(ifaces) iface := ifaces[0] devs[i].MacAddresses = collectInterfaceMACs(ifaces) if devs[i].SerialNumber == nil { if serial := queryPCIDeviceSerial(bdf); serial != "" { devs[i].SerialNumber = &serial } } if devs[i].Firmware == nil { if out, err := ethtoolInfoQuery(iface); err == nil { if fw := parseEthtoolFirmwareInfo(out); fw != "" { devs[i].Firmware = &fw } } } for port, portIface := range ifaces { out, err := ethtoolModuleQuery(portIface) if err != nil { continue } legacy := schema.HardwarePCIeDevice{} if !injectSFPDOMTelemetry(&legacy, out) || legacy.SFPIdentifier == nil { continue } devs[i].SFPModules = append(devs[i].SFPModules, sfpModuleFromLegacy(port, legacy)) // Keep the v2.10 scalar representation for old Reanimator installs. if len(devs[i].SFPModules) == 1 { copyLegacySFPFields(&devs[i], legacy) } } if len(devs[i].SFPModules) > 0 { enriched++ continue } if len(devs[i].MacAddresses) > 0 || devs[i].Firmware != nil { enriched++ } } slog.Info("nic: telemetry enriched", "count", enriched) return devs } func sfpModuleFromLegacy(port int, dev schema.HardwarePCIeDevice) schema.HardwareSFPModule { m := schema.HardwareSFPModule{Port: port, Identifier: dev.SFPIdentifier, Connector: dev.SFPConnector, Vendor: dev.SFPVendor, PartNumber: dev.SFPPartNumber, SerialNumber: dev.SFPSerialNumber, Revision: dev.SFPRevision, TransceiverType: dev.SFPTransceiverType, TemperatureC: dev.SFPTemperatureC, TXPowerDBM: dev.SFPTXPowerDBM, RXPowerDBM: dev.SFPRXPowerDBM, VoltageV: dev.SFPVoltageV, BiasMA: dev.SFPBiasMA} if dev.SFPWavelengthNM != nil { v := int(*dev.SFPWavelengthNM + 0.5) m.WavelengthNM = &v } return m } func copyLegacySFPFields(dst *schema.HardwarePCIeDevice, src schema.HardwarePCIeDevice) { dst.SFPPresent, dst.SFPIdentifier, dst.SFPConnector = src.SFPPresent, src.SFPIdentifier, src.SFPConnector dst.SFPVendor, dst.SFPPartNumber, dst.SFPSerialNumber = src.SFPVendor, src.SFPPartNumber, src.SFPSerialNumber dst.SFPRevision, dst.SFPTransceiverType = src.SFPRevision, src.SFPTransceiverType dst.SFPWavelengthNM, dst.SFPTemperatureC, dst.SFPTXPowerDBM, dst.SFPRXPowerDBM = src.SFPWavelengthNM, src.SFPTemperatureC, src.SFPTXPowerDBM, src.SFPRXPowerDBM dst.SFPVoltageV, dst.SFPBiasMA = src.SFPVoltageV, src.SFPBiasMA } func isNICDevice(dev schema.HardwarePCIeDevice) bool { if dev.DeviceClass == nil { return false } c := strings.TrimSpace(*dev.DeviceClass) return isNICClass(c) || strings.EqualFold(c, "FibreChannelController") } func collectInterfaceMACs(ifaces []string) []string { seen := map[string]struct{}{} var out []string for _, iface := range ifaces { mac, err := readNetAddressFile(iface) if err != nil || mac == "" { continue } mac = strings.ToLower(strings.TrimSpace(mac)) if _, ok := seen[mac]; ok { continue } seen[mac] = struct{}{} out = append(out, mac) } return out } var floatRe = regexp.MustCompile(`[-+]?[0-9]*\.?[0-9]+`) func injectSFPDOMTelemetry(dev *schema.HardwarePCIeDevice, raw string) bool { var changed bool for _, line := range strings.Split(raw, "\n") { trimmed := strings.TrimSpace(line) if trimmed == "" { continue } idx := strings.Index(trimmed, ":") if idx < 0 { continue } key := strings.ToLower(strings.TrimSpace(trimmed[:idx])) val := strings.TrimSpace(trimmed[idx+1:]) if val == "" || strings.EqualFold(val, "not supported") || strings.EqualFold(val, "unknown") { continue } switch { case key == "identifier": s := parseSFPIdentifier(val) dev.SFPIdentifier = &s t := true dev.SFPPresent = &t changed = true case key == "connector": s := parseSFPConnector(val) dev.SFPConnector = &s changed = true case key == "vendor name": s := strings.TrimSpace(val) dev.SFPVendor = &s changed = true case key == "vendor pn": s := strings.TrimSpace(val) dev.SFPPartNumber = &s changed = true case key == "vendor sn": s := strings.TrimSpace(val) dev.SFPSerialNumber = &s changed = true case key == "vendor rev": s := strings.TrimSpace(val) dev.SFPRevision = &s changed = true case key == "transceiver type": s := strings.TrimSpace(val) dev.SFPTransceiverType = &s changed = true case strings.Contains(key, "laser wavelength"): if f, ok := firstFloat(val); ok { dev.SFPWavelengthNM = &f changed = true } case strings.Contains(key, "module temperature"): if f, ok := firstFloat(val); ok { dev.SFPTemperatureC = &f changed = true } case strings.Contains(key, "laser output power"): if f, ok := dbmValue(val); ok { dev.SFPTXPowerDBM = &f changed = true } case strings.Contains(key, "receiver signal"): if f, ok := dbmValue(val); ok { dev.SFPRXPowerDBM = &f changed = true } case strings.Contains(key, "module voltage"): if f, ok := firstFloat(val); ok { dev.SFPVoltageV = &f changed = true } case strings.Contains(key, "laser bias current"): if f, ok := firstFloat(val); ok { dev.SFPBiasMA = &f changed = true } } } return changed } // parseSFPIdentifier extracts the human-readable transceiver type from the // raw ethtool identifier line, e.g. "0x03 (SFP)" → "SFP". func parseSFPIdentifier(val string) string { if s := extractParens(val); s != "" { return s } return val } // parseSFPConnector extracts the connector type from the raw ethtool line, // e.g. "0x07 (LC)" → "LC". func parseSFPConnector(val string) string { if s := extractParens(val); s != "" { return s } return val } var parenRe = regexp.MustCompile(`\(([^)]+)\)`) func extractParens(s string) string { m := parenRe.FindStringSubmatch(s) if len(m) < 2 { return "" } return strings.TrimSpace(m[1]) } func parseSFPDOM(raw string) map[string]any { dev := schema.HardwarePCIeDevice{} if !injectSFPDOMTelemetry(&dev, raw) { return map[string]any{} } out := map[string]any{} if dev.SFPPresent != nil { out["sfp_present"] = *dev.SFPPresent } if dev.SFPIdentifier != nil { out["sfp_identifier"] = *dev.SFPIdentifier } if dev.SFPConnector != nil { out["sfp_connector"] = *dev.SFPConnector } if dev.SFPVendor != nil { out["sfp_vendor"] = *dev.SFPVendor } if dev.SFPPartNumber != nil { out["sfp_part_number"] = *dev.SFPPartNumber } if dev.SFPSerialNumber != nil { out["sfp_serial_number"] = *dev.SFPSerialNumber } if dev.SFPWavelengthNM != nil { out["sfp_wavelength_nm"] = *dev.SFPWavelengthNM } if dev.SFPTemperatureC != nil { out["sfp_temperature_c"] = *dev.SFPTemperatureC } if dev.SFPTXPowerDBM != nil { out["sfp_tx_power_dbm"] = *dev.SFPTXPowerDBM } if dev.SFPRXPowerDBM != nil { out["sfp_rx_power_dbm"] = *dev.SFPRXPowerDBM } if dev.SFPVoltageV != nil { out["sfp_voltage_v"] = *dev.SFPVoltageV } if dev.SFPBiasMA != nil { out["sfp_bias_ma"] = *dev.SFPBiasMA } return out } func firstFloat(raw string) (float64, bool) { m := floatRe.FindString(raw) if m == "" { return 0, false } v, err := strconv.ParseFloat(m, 64) if err != nil { return 0, false } return v, true } func dbmValue(raw string) (float64, bool) { parts := strings.Split(strings.ToLower(raw), "dbm") if len(parts) == 0 { return 0, false } for i := len(parts) - 1; i >= 0; i-- { candidate := parts[i] matches := floatRe.FindAllString(candidate, -1) if len(matches) == 0 { continue } v, err := strconv.ParseFloat(matches[len(matches)-1], 64) if err == nil { return v, true } } return 0, false }