- agent-linux/: Kompletter Linux/Proxmox RMM Agent - Collectors: CPU, Memory, Disk, Network, Services, Updates, Proxmox (VMs, Container, Storage, ZFS), Backups - Backup Collector: Jobs nach Node gefiltert (Cluster-aware) - WebSocket: Tunnel-Support, writeMux fuer stabile Verbindungen - Systemd Service Template - updater/: Plattform-unabhaengiger Updater (FreeBSD + Linux) - runtime.GOOS erkennt Plattform automatisch - FreeBSD: /usr/local/rmm/, service rmm_agent - Linux: /usr/local/bin/, /etc/rmm/, systemctl rmm-agent - Firmware-Download mit ?platform= Parameter - frontend/: Proxmox-Bereich - ProxmoxServers.jsx: Eigene Seite fuer PVE Server mit VM/CT Counts - ProxmoxPanel.jsx: Detail-Panel mit Uebersicht, VMs, Container, Storage, ZFS, Tunnel, Dienste, Updates, Backups - Agents.jsx: Filtert Linux-Agents raus (nur OPNsense) - Sidebar: Proxmox Navigationspunkt - Installationsanleitung mit rmm-agent-linux + rmm-updater-linux - backend: Platform-Feld fuer Agents, Firmware multi-platform
185 lines
4.0 KiB
Go
185 lines
4.0 KiB
Go
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
|
|
} |