124 lines
2.6 KiB
Go
124 lines
2.6 KiB
Go
package collector
|
|
|
|
import (
|
|
"bee/audit/internal/schema"
|
|
"log/slog"
|
|
"os"
|
|
"os/exec"
|
|
"path/filepath"
|
|
"strings"
|
|
)
|
|
|
|
var (
|
|
queryPCILSPCIDetail = func(bdf string) (string, error) {
|
|
out, err := exec.Command("lspci", "-vv", "-s", bdf).Output()
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
return string(out), nil
|
|
}
|
|
readPCIVPDFile = func(bdf string) ([]byte, error) {
|
|
return os.ReadFile(filepath.Join("/sys/bus/pci/devices", bdf, "vpd"))
|
|
}
|
|
)
|
|
|
|
func enrichPCIeWithPCISerials(devs []schema.HardwarePCIeDevice) []schema.HardwarePCIeDevice {
|
|
enriched := 0
|
|
for i := range devs {
|
|
if !shouldProbePCIeSerial(devs[i]) {
|
|
continue
|
|
}
|
|
bdf := normalizePCIeBDF(*devs[i].BDF)
|
|
if bdf == "" {
|
|
continue
|
|
}
|
|
if serial := queryPCIDeviceSerial(bdf); serial != "" {
|
|
devs[i].SerialNumber = &serial
|
|
enriched++
|
|
}
|
|
}
|
|
if enriched > 0 {
|
|
slog.Info("pcie: serials enriched", "count", enriched)
|
|
}
|
|
return devs
|
|
}
|
|
|
|
func shouldProbePCIeSerial(dev schema.HardwarePCIeDevice) bool {
|
|
if dev.BDF == nil || dev.SerialNumber != nil {
|
|
return false
|
|
}
|
|
if dev.DeviceClass == nil {
|
|
return false
|
|
}
|
|
class := strings.TrimSpace(*dev.DeviceClass)
|
|
return isNICClass(class) || isGPUClass(class)
|
|
}
|
|
|
|
func queryPCIDeviceSerial(bdf string) string {
|
|
if out, err := queryPCILSPCIDetail(bdf); err == nil {
|
|
if serial := parseLSPCIDetailSerial(out); serial != "" {
|
|
return serial
|
|
}
|
|
}
|
|
if raw, err := readPCIVPDFile(bdf); err == nil {
|
|
return parsePCIVPDSerial(raw)
|
|
}
|
|
return ""
|
|
}
|
|
|
|
func parseLSPCIDetailSerial(raw string) string {
|
|
for _, line := range strings.Split(raw, "\n") {
|
|
line = strings.TrimSpace(line)
|
|
if line == "" {
|
|
continue
|
|
}
|
|
lower := strings.ToLower(line)
|
|
if !strings.Contains(lower, "serial number:") {
|
|
continue
|
|
}
|
|
idx := strings.Index(line, ":")
|
|
if idx < 0 {
|
|
continue
|
|
}
|
|
if serial := strings.TrimSpace(line[idx+1:]); serial != "" {
|
|
return serial
|
|
}
|
|
}
|
|
return ""
|
|
}
|
|
|
|
func parsePCIVPDSerial(raw []byte) string {
|
|
for i := 0; i+3 < len(raw); i++ {
|
|
if raw[i] != 'S' || raw[i+1] != 'N' {
|
|
continue
|
|
}
|
|
length := int(raw[i+2])
|
|
if length <= 0 || length > 64 || i+3+length > len(raw) {
|
|
continue
|
|
}
|
|
value := strings.TrimSpace(strings.Trim(string(raw[i+3:i+3+length]), "\x00"))
|
|
if !looksLikeSerial(value) {
|
|
continue
|
|
}
|
|
return value
|
|
}
|
|
return ""
|
|
}
|
|
|
|
func looksLikeSerial(value string) bool {
|
|
if len(value) < 4 {
|
|
return false
|
|
}
|
|
hasAlphaNum := false
|
|
for _, r := range value {
|
|
switch {
|
|
case r >= 'a' && r <= 'z', r >= 'A' && r <= 'Z', r >= '0' && r <= '9':
|
|
hasAlphaNum = true
|
|
case strings.ContainsRune(" -_./:", r):
|
|
default:
|
|
return false
|
|
}
|
|
}
|
|
return hasAlphaNum
|
|
}
|