rmm2/agent-bsd/collector/wireguard.go

134 lines
3.5 KiB
Go

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
}