179 lines
4.4 KiB
Go
179 lines
4.4 KiB
Go
package collector
|
|
|
|
import (
|
|
"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 {
|
|
ifaces := parseIfconfig()
|
|
addTrafficStats(ifaces)
|
|
return ifaces
|
|
}
|
|
|
|
// parseIfconfig parst ifconfig -a Output
|
|
func parseIfconfig() []NetworkInterface {
|
|
out, err := exec.Command("ifconfig", "-a").Output()
|
|
if err != nil {
|
|
return nil
|
|
}
|
|
|
|
var ifaces []NetworkInterface
|
|
var current *NetworkInterface
|
|
|
|
for _, line := range strings.Split(string(out), "\n") {
|
|
// Neues Interface: "igb0: flags=..."
|
|
if len(line) > 0 && line[0] != '\t' && line[0] != ' ' && strings.Contains(line, ": flags=") {
|
|
if current != nil {
|
|
ifaces = append(ifaces, *current)
|
|
}
|
|
name := strings.Split(line, ":")[0]
|
|
|
|
// Loopback und virtuelle Interfaces ueberspringen
|
|
if name == "lo0" || strings.HasPrefix(name, "pflog") || strings.HasPrefix(name, "pfsync") || strings.HasPrefix(name, "enc") {
|
|
current = nil
|
|
continue
|
|
}
|
|
|
|
status := "down"
|
|
if strings.Contains(line, "UP") {
|
|
status = "up"
|
|
}
|
|
|
|
current = &NetworkInterface{
|
|
Name: name,
|
|
Status: status,
|
|
Role: guessRole(name),
|
|
}
|
|
continue
|
|
}
|
|
|
|
if current == nil {
|
|
continue
|
|
}
|
|
|
|
trimmed := strings.TrimSpace(line)
|
|
|
|
// IPv4 Adresse
|
|
if strings.HasPrefix(trimmed, "inet ") && !strings.HasPrefix(trimmed, "inet6") {
|
|
fields := strings.Fields(trimmed)
|
|
if len(fields) >= 2 {
|
|
current.IP = fields[1]
|
|
}
|
|
}
|
|
|
|
// MAC Adresse
|
|
if strings.HasPrefix(trimmed, "ether ") {
|
|
fields := strings.Fields(trimmed)
|
|
if len(fields) >= 2 {
|
|
current.MAC = fields[1]
|
|
}
|
|
}
|
|
}
|
|
|
|
if current != nil {
|
|
ifaces = append(ifaces, *current)
|
|
}
|
|
|
|
return ifaces
|
|
}
|
|
|
|
// addTrafficStats fuegt RX/TX Bytes via netstat -ib hinzu
|
|
func addTrafficStats(ifaces []NetworkInterface) {
|
|
out, err := exec.Command("/usr/bin/netstat", "-ib").Output()
|
|
if err != nil {
|
|
return
|
|
}
|
|
|
|
// Nur <Link#> Zeilen haben die totalen Byte-Zaehler
|
|
// Format variiert, aber die letzten 4 Felder sind immer: Opkts Oerrs Obytes Coll
|
|
// und davor: Ipkts Ierrs Idrop Ibytes
|
|
lines := strings.Split(string(out), "\n")
|
|
for _, line := range lines[1:] {
|
|
if !strings.Contains(line, "<Link#") {
|
|
continue
|
|
}
|
|
fields := strings.Fields(line)
|
|
if len(fields) < 11 {
|
|
continue
|
|
}
|
|
name := fields[0]
|
|
// Von hinten zaehlen: Coll=last, Obytes=last-1, Oerrs=last-2, Opkts=last-3
|
|
// Ibytes=last-4 von Opkts aus: Ibytes ist 4 vor Opkts
|
|
n := len(fields)
|
|
obytes, err1 := strconv.ParseInt(fields[n-2], 10, 64)
|
|
// Ibytes: 7 von hinten (Coll, Obytes, Oerrs, Opkts, Ibytes, Idrop, Ierrs)
|
|
ibytes, err2 := strconv.ParseInt(fields[n-5], 10, 64)
|
|
if err1 != nil || err2 != nil {
|
|
continue
|
|
}
|
|
|
|
for i := range ifaces {
|
|
if ifaces[i].Name == name {
|
|
ifaces[i].RxBytes = ibytes
|
|
ifaces[i].TxBytes = obytes
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// guessRole versucht die Rolle anhand des Interface-Namens zu erraten
|
|
func guessRole(name string) string {
|
|
// OPNsense benutzt typischerweise config.xml fuer Zuordnungen
|
|
// Hier: Heuristik basierend auf gaengigen Konventionen
|
|
lower := strings.ToLower(name)
|
|
|
|
// Versuche OPNsense-Config zu lesen
|
|
role := getOPNsenseIfRole(name)
|
|
if role != "" {
|
|
return role
|
|
}
|
|
|
|
// Fallback-Heuristik
|
|
if strings.Contains(lower, "wan") {
|
|
return "WAN"
|
|
}
|
|
if strings.Contains(lower, "lan") {
|
|
return "LAN"
|
|
}
|
|
if strings.HasPrefix(lower, "igb0") || strings.HasPrefix(lower, "em0") || strings.HasPrefix(lower, "ix0") {
|
|
return "WAN"
|
|
}
|
|
if strings.HasPrefix(lower, "igb1") || strings.HasPrefix(lower, "em1") || strings.HasPrefix(lower, "ix1") {
|
|
return "LAN"
|
|
}
|
|
|
|
return "OPT"
|
|
}
|
|
|
|
// getOPNsenseIfRole liest die Interface-Rolle aus der OPNsense config.xml
|
|
func getOPNsenseIfRole(ifName string) string {
|
|
// config.xml Struktur: <interfaces><wan><if>vtnet1</if>...
|
|
// Wir suchen nach <if>IFNAME</if> und schauen welcher Block (wan/lan/opt) es enthaelt
|
|
out, err := exec.Command("grep", "-B5", "<if>"+ifName+"</if>", "/conf/config.xml").Output()
|
|
if err != nil {
|
|
return ""
|
|
}
|
|
|
|
content := string(out)
|
|
// Suche nach dem oeffnenden Tag des Parent-Elements
|
|
for _, role := range []string{"wan", "lan", "opt1", "opt2", "opt3", "opt4", "opt5", "opt6"} {
|
|
if strings.Contains(content, "<"+role+">") {
|
|
return strings.ToUpper(role)
|
|
}
|
|
}
|
|
|
|
return ""
|
|
}
|