package collector import ( "os/exec" "strconv" "strings" "time" ) type WireGuardTunnel struct { Interface string `json:"interface"` PublicKey string `json:"public_key"` ListenPort int `json:"listen_port"` Peers []WireGuardPeer `json:"peers"` } type WireGuardPeer struct { PublicKey string `json:"public_key"` Endpoint string `json:"endpoint"` AllowedIPs []string `json:"allowed_ips"` LatestHandshake int64 `json:"latest_handshake_epoch"` HandshakeAge string `json:"handshake_age"` TransferRx int64 `json:"transfer_rx_bytes"` TransferTx int64 `json:"transfer_tx_bytes"` Keepalive int `json:"persistent_keepalive"` Status string `json:"status"` // "active", "inactive", "unknown" } func CollectWireGuard() []WireGuardTunnel { // wg show all dump: maschinenlesbares Format // Voller Pfad, da daemon(8) evtl. reduzierten PATH hat wgPath := "/usr/bin/wg" if _, err := exec.LookPath("wg"); err == nil { wgPath = "wg" } out, err := exec.Command(wgPath, "show", "all", "dump").Output() if err != nil { return nil } tunnels := make(map[string]*WireGuardTunnel) var order []string for _, line := range strings.Split(strings.TrimSpace(string(out)), "\n") { if line == "" { continue } fields := strings.Split(line, "\t") if len(fields) < 5 { continue } iface := fields[0] // Interface-Zeile: iface privkey pubkey listenport fwmark if _, exists := tunnels[iface]; !exists { port := 0 if len(fields) >= 4 { port, _ = strconv.Atoi(fields[3]) } pubkey := "" if len(fields) >= 3 { pubkey = fields[2] } tunnels[iface] = &WireGuardTunnel{ Interface: iface, PublicKey: pubkey, ListenPort: port, } order = append(order, iface) continue } // Peer-Zeile: iface pubkey presharedkey endpoint allowedips latest-handshake transfer-rx transfer-tx keepalive if len(fields) < 9 { continue } peer := WireGuardPeer{ PublicKey: fields[1], Endpoint: fields[3], } // Allowed IPs if fields[4] != "(none)" { peer.AllowedIPs = strings.Split(fields[4], ",") } // Latest handshake (epoch) if ts, err := strconv.ParseInt(fields[5], 10, 64); err == nil && ts > 0 { peer.LatestHandshake = ts age := time.Since(time.Unix(ts, 0)) if age < 3*time.Minute { peer.Status = "active" } else if age < 10*time.Minute { peer.Status = "inactive" } else { peer.Status = "inactive" } // Menschenlesbare Altersangabe if age.Hours() >= 24 { peer.HandshakeAge = strconv.Itoa(int(age.Hours()/24)) + "d " + strconv.Itoa(int(age.Hours())%24) + "h ago" } else if age.Hours() >= 1 { peer.HandshakeAge = strconv.Itoa(int(age.Hours())) + "h " + strconv.Itoa(int(age.Minutes())%60) + "m ago" } else if age.Minutes() >= 1 { peer.HandshakeAge = strconv.Itoa(int(age.Minutes())) + "m " + strconv.Itoa(int(age.Seconds())%60) + "s ago" } else { peer.HandshakeAge = strconv.Itoa(int(age.Seconds())) + "s ago" } } else { peer.Status = "unknown" peer.HandshakeAge = "never" } // Transfer RX/TX peer.TransferRx, _ = strconv.ParseInt(fields[6], 10, 64) peer.TransferTx, _ = strconv.ParseInt(fields[7], 10, 64) // Keepalive if fields[8] != "off" { peer.Keepalive, _ = strconv.Atoi(fields[8]) } tunnels[iface].Peers = append(tunnels[iface].Peers, peer) } // Geordnet zurueckgeben var result []WireGuardTunnel for _, name := range order { result = append(result, *tunnels[name]) } return result }