package collector import ( "os" "os/exec" "strconv" "strings" ) type NetworkInterface struct { Name string `json:"name"` Role string `json:"role"` IP string `json:"ip"` MAC string `json:"mac"` Status string `json:"status"` RxBytes int64 `json:"rx_bytes"` TxBytes int64 `json:"tx_bytes"` } func CollectNetwork() []NetworkInterface { var interfaces []NetworkInterface // Interface-Statistiken aus /proc/net/dev lesen stats := readNetDevStats() // Interface-Informationen aus /sys/class/net/* sammeln entries, err := os.ReadDir("/sys/class/net") if err != nil { return interfaces } for _, entry := range entries { if !entry.IsDir() { continue } ifName := entry.Name() // Überspringe loopback if ifName == "lo" { continue } iface := NetworkInterface{ Name: ifName, Role: determineInterfaceRole(ifName), } // Status aus operstate lesen if data, err := os.ReadFile("/sys/class/net/" + ifName + "/operstate"); err == nil { state := strings.TrimSpace(string(data)) if state == "up" { iface.Status = "up" } else { iface.Status = "down" } } // MAC-Adresse lesen if data, err := os.ReadFile("/sys/class/net/" + ifName + "/address"); err == nil { iface.MAC = strings.TrimSpace(string(data)) } // Statistiken hinzufügen if stat, exists := stats[ifName]; exists { iface.RxBytes = stat.rxBytes iface.TxBytes = stat.txBytes } // IP-Adresse über ip addr bestimmen iface.IP = getInterfaceIP(ifName) interfaces = append(interfaces, iface) } return interfaces } // Bestimmt die Rolle eines Interfaces basierend auf dem Namen func determineInterfaceRole(ifName string) string { switch { case strings.HasPrefix(ifName, "eth"): return "lan" case strings.HasPrefix(ifName, "ens"): return "lan" case strings.HasPrefix(ifName, "enp"): return "lan" case strings.HasPrefix(ifName, "wlan"): return "wan" case strings.HasPrefix(ifName, "wlp"): return "wan" case strings.HasPrefix(ifName, "ppp"): return "wan" case strings.HasPrefix(ifName, "tun"): return "vpn" case strings.HasPrefix(ifName, "tap"): return "vpn" case strings.HasPrefix(ifName, "br"): return "bridge" case strings.HasPrefix(ifName, "docker"): return "virtual" case strings.HasPrefix(ifName, "veth"): return "virtual" case strings.HasPrefix(ifName, "virbr"): return "virtual" default: return "other" } } // Liest die erste IPv4-Adresse eines Interfaces func getInterfaceIP(ifName string) string { output, err := exec.Command("/usr/sbin/ip", "-4", "addr", "show", ifName).CombinedOutput() if err != nil { return "" } lines := strings.Split(string(output), "\n") for _, line := range lines { line = strings.TrimSpace(line) if strings.HasPrefix(line, "inet ") { fields := strings.Fields(line) if len(fields) >= 2 { // inet 192.168.1.100/24 ... -> 192.168.1.100 ipCidr := fields[1] if parts := strings.SplitN(ipCidr, "/", 2); len(parts) > 0 { return parts[0] } } } } return "" } // Network-Statistiken type netDevStat struct { rxBytes int64 txBytes int64 } // Liest Interface-Statistiken aus /proc/net/dev func readNetDevStats() map[string]netDevStat { stats := make(map[string]netDevStat) data, err := os.ReadFile("/proc/net/dev") if err != nil { return stats } lines := strings.Split(string(data), "\n") if len(lines) < 3 { return stats } // Ersten 2 Zeilen sind Header for _, line := range lines[2:] { line = strings.TrimSpace(line) if line == "" { continue } // Interface: rx_bytes rx_packets ... tx_bytes tx_packets ... parts := strings.SplitN(line, ":", 2) if len(parts) != 2 { continue } ifName := strings.TrimSpace(parts[0]) values := strings.Fields(strings.TrimSpace(parts[1])) if len(values) >= 9 { // rx_bytes ist das erste Feld, tx_bytes das 9te Feld (0-basiert: Index 8) rxBytes, _ := strconv.ParseInt(values[0], 10, 64) txBytes, _ := strconv.ParseInt(values[8], 10, 64) stats[ifName] = netDevStat{ rxBytes: rxBytes, txBytes: txBytes, } } } return stats }