Files
bee/audit/internal/collector/nic_mellanox.go
Michael Chus 7d2e904d14 Bring codebase into compliance with bible contracts (A–E)
A (hardware-ingest-json v2.8-2.9): remove sensor location fields from schema
and collector; tag HardwareMemory.Location as json:"-"; add PlatformConfig to
HardwareSnapshot.

B (no-hardcoded-vendors): consolidate PCI vendor IDs into collector/pci_vendors.go;
replace all vendor-name string checks in isGPUDevice, isNVIDIADevice, isMellanoxDevice,
isAMDGPUDevice, matchesGPUVendor (sat_overlay), and validateIsVendorGPU (page_validate)
with numeric vendor_id comparisons.

C (module-structure): split app/app.go (1413 lines) into app.go + app_format.go,
app_network.go, app_services.go, app_packs.go, app_install.go — no logic changes.

D (go-code-style): wrap bare return err in interfaceAdminState and
interfaceIPv4Addrs (platform/network.go) with fmt.Errorf context including
the interface name.

E (go-project-bible): add bible-local/architecture/data-model.md and
bible-local/architecture/api-surface.md.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-13 14:32:08 +03:00

172 lines
3.6 KiB
Go

package collector
import (
"bee/audit/internal/schema"
"context"
"log/slog"
"os"
"os/exec"
"path/filepath"
"strings"
"time"
)
const nicProbeTimeout = 2 * time.Second
var (
mstflintQuery = func(bdf string) (string, error) {
out, err := commandOutputWithTimeout(nicProbeTimeout, "mstflint", "-d", bdf, "q")
if err != nil {
return "", err
}
return string(out), nil
}
ethtoolInfoQuery = func(iface string) (string, error) {
out, err := commandOutputWithTimeout(nicProbeTimeout, "ethtool", "-i", iface)
if err != nil {
return "", err
}
return string(out), nil
}
netIfacesByBDF = listNetIfacesByBDF
readNetCarrierFile = func(iface string) (string, error) {
path := filepath.Join("/sys/class/net", iface, "carrier")
raw, err := os.ReadFile(path)
if err != nil {
return "", err
}
return strings.TrimSpace(string(raw)), nil
}
)
// enrichPCIeWithMellanox enriches Mellanox/NVIDIA Networking devices with
// firmware/serial information from mstflint, with ethtool fallback for firmware.
func enrichPCIeWithMellanox(devs []schema.HardwarePCIeDevice) []schema.HardwarePCIeDevice {
enriched := 0
for i := range devs {
if !isMellanoxDevice(devs[i]) {
continue
}
bdf := ""
if devs[i].BDF != nil {
bdf = normalizePCIeBDF(*devs[i].BDF)
}
if bdf == "" {
continue
}
fw, serial := queryMellanoxFromMstflint(bdf)
if fw == "" {
fw = queryFirmwareFromEthtool(bdf)
}
if fw != "" {
devs[i].Firmware = &fw
}
if serial != "" {
devs[i].SerialNumber = &serial
}
if fw != "" || serial != "" {
enriched++
}
}
slog.Info("mellanox: enriched", "count", enriched)
return devs
}
func isMellanoxDevice(dev schema.HardwarePCIeDevice) bool {
return dev.VendorID != nil && *dev.VendorID == MellanoxVendorID
}
func queryMellanoxFromMstflint(bdf string) (firmware, serial string) {
out, err := mstflintQuery(bdf)
if err != nil {
return "", ""
}
return parseMstflintQuery(out)
}
func parseMstflintQuery(raw string) (firmware, serial string) {
for _, line := range strings.Split(raw, "\n") {
line = strings.TrimSpace(line)
if line == "" {
continue
}
idx := strings.Index(line, ":")
if idx < 0 {
continue
}
key := strings.ToLower(strings.TrimSpace(line[:idx]))
val := strings.TrimSpace(line[idx+1:])
switch key {
case "fw version":
if val != "" {
firmware = val
}
case "board serial number":
if val != "" {
serial = val
}
}
}
return firmware, serial
}
func queryFirmwareFromEthtool(bdf string) string {
for _, iface := range netIfacesByBDF(bdf) {
out, err := ethtoolInfoQuery(iface)
if err != nil {
continue
}
if fw := parseEthtoolFirmwareInfo(out); fw != "" {
return fw
}
}
return ""
}
func parseEthtoolFirmwareInfo(raw string) string {
for _, line := range strings.Split(raw, "\n") {
line = strings.TrimSpace(line)
if line == "" {
continue
}
idx := strings.Index(line, ":")
if idx < 0 {
continue
}
key := strings.ToLower(strings.TrimSpace(line[:idx]))
val := strings.TrimSpace(line[idx+1:])
if key == "firmware-version" && val != "" {
return val
}
}
return ""
}
func listNetIfacesByBDF(bdf string) []string {
path := filepath.Join("/sys/bus/pci/devices", bdf, "net")
entries, err := os.ReadDir(path)
if err != nil {
return nil
}
ifaces := make([]string, 0, len(entries))
for _, e := range entries {
if e.Name() == "" {
continue
}
ifaces = append(ifaces, e.Name())
}
return ifaces
}
func commandOutputWithTimeout(timeout time.Duration, name string, args ...string) ([]byte, error) {
ctx, cancel := context.WithTimeout(context.Background(), timeout)
defer cancel()
return exec.CommandContext(ctx, name, args...).Output()
}