Makefile and agent start.
This commit is contained in:
194
agent/network/network.go
Normal file
194
agent/network/network.go
Normal file
@@ -0,0 +1,194 @@
|
||||
package network
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"os/exec"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"kattila-agent/models"
|
||||
)
|
||||
|
||||
type ipAddrInfo struct {
|
||||
Local string `json:"local"`
|
||||
PrefixLen int `json:"prefixlen"`
|
||||
}
|
||||
|
||||
type ipInterface struct {
|
||||
Ifname string `json:"ifname"`
|
||||
Address string `json:"address"` // MAC address in newer iproute2
|
||||
AddrInfo []ipAddrInfo `json:"addr_info"`
|
||||
}
|
||||
|
||||
func getInterfaces() ([]models.Interface, error) {
|
||||
cmd := exec.Command("ip", "-j", "a")
|
||||
out, err := cmd.Output()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var parsed []ipInterface
|
||||
if err := json.Unmarshal(out, &parsed); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var results []models.Interface
|
||||
for _, itf := range parsed {
|
||||
mac := itf.Address
|
||||
var addrs []string
|
||||
for _, info := range itf.AddrInfo {
|
||||
addrs = append(addrs, fmt.Sprintf("%s/%d", info.Local, info.PrefixLen))
|
||||
}
|
||||
|
||||
isVirtual := false
|
||||
var vpnType *string
|
||||
if strings.HasPrefix(itf.Ifname, "wg") || strings.HasPrefix(itf.Ifname, "tun") || strings.HasPrefix(itf.Ifname, "parvpn") || strings.HasPrefix(itf.Ifname, "home") || strings.HasPrefix(itf.Ifname, "tailscale") {
|
||||
isVirtual = true
|
||||
}
|
||||
|
||||
results = append(results, models.Interface{
|
||||
Name: itf.Ifname,
|
||||
MAC: mac,
|
||||
Addresses: addrs,
|
||||
IsVirtual: isVirtual,
|
||||
VPNType: vpnType,
|
||||
})
|
||||
}
|
||||
return results, nil
|
||||
}
|
||||
|
||||
type ipRoute struct {
|
||||
Dst string `json:"dst"`
|
||||
Gateway string `json:"gateway"`
|
||||
Via string `json:"via"` // Sometimes present instead of gateway
|
||||
Dev string `json:"dev"`
|
||||
}
|
||||
|
||||
func getRoutes() ([]models.Route, error) {
|
||||
cmd := exec.Command("ip", "-j", "-4", "r")
|
||||
out, err := cmd.Output()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var parsed []ipRoute
|
||||
if err := json.Unmarshal(out, &parsed); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var results []models.Route
|
||||
for _, r := range parsed {
|
||||
via := r.Gateway
|
||||
if via == "" {
|
||||
via = r.Via
|
||||
}
|
||||
results = append(results, models.Route{
|
||||
Dst: r.Dst,
|
||||
Via: via,
|
||||
Dev: r.Dev,
|
||||
})
|
||||
}
|
||||
return results, nil
|
||||
}
|
||||
|
||||
func getWgPeers() ([]models.WGPeer, error) {
|
||||
cmd := exec.Command("wg", "show", "all", "dump")
|
||||
out, err := cmd.Output()
|
||||
if err != nil { // wg might fail or not be installed, ignore and return empty
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
var peers []models.WGPeer
|
||||
lines := bytes.Split(out, []byte("\n"))
|
||||
for _, line := range lines {
|
||||
line = bytes.TrimSpace(line)
|
||||
if len(line) == 0 {
|
||||
continue
|
||||
}
|
||||
|
||||
cols := strings.Split(string(line), "\t")
|
||||
// wg interface line: itf, privkey, pubkey, port, fwmark (5 cols)
|
||||
// wg peer line: itf, pubkey, psk, endpoint, allowed-ips, handshake, rx, tx, keepalive (9 cols)
|
||||
if len(cols) >= 8 {
|
||||
itf := cols[0]
|
||||
pubkey := cols[1]
|
||||
endpoint := cols[3]
|
||||
if endpoint == "(none)" {
|
||||
endpoint = ""
|
||||
}
|
||||
allowedIpsRaw := cols[4]
|
||||
if allowedIpsRaw == "(none)" {
|
||||
allowedIpsRaw = ""
|
||||
}
|
||||
allowedIps := strings.Split(allowedIpsRaw, ",")
|
||||
|
||||
handshake, _ := strconv.ParseInt(cols[5], 10, 64)
|
||||
rx, _ := strconv.ParseInt(cols[6], 10, 64)
|
||||
tx, _ := strconv.ParseInt(cols[7], 10, 64)
|
||||
|
||||
peers = append(peers, models.WGPeer{
|
||||
Interface: itf,
|
||||
PublicKey: pubkey,
|
||||
Endpoint: endpoint,
|
||||
AllowedIPs: allowedIps,
|
||||
LatestHandshake: handshake,
|
||||
TransferRx: rx,
|
||||
TransferTx: tx,
|
||||
})
|
||||
}
|
||||
}
|
||||
return peers, nil
|
||||
}
|
||||
|
||||
func GatherSystemData() (models.SystemData, error) {
|
||||
hostname, _ := exec.Command("hostname").Output()
|
||||
|
||||
// Simplified uptime extraction
|
||||
uptimeBytes, _ := exec.Command("cat", "/proc/uptime").Output()
|
||||
var uptimeSec int64
|
||||
if len(uptimeBytes) > 0 {
|
||||
parts := strings.Fields(string(uptimeBytes))
|
||||
if len(parts) > 0 {
|
||||
uptimeFloat, _ := strconv.ParseFloat(parts[0], 64)
|
||||
uptimeSec = int64(uptimeFloat)
|
||||
}
|
||||
}
|
||||
|
||||
loadavgBytes, _ := exec.Command("cat", "/proc/loadavg").Output()
|
||||
var loadavg []float64
|
||||
if len(loadavgBytes) > 0 {
|
||||
parts := strings.Fields(string(loadavgBytes))
|
||||
if len(parts) >= 3 {
|
||||
l1, _ := strconv.ParseFloat(parts[0], 64)
|
||||
l2, _ := strconv.ParseFloat(parts[1], 64)
|
||||
l3, _ := strconv.ParseFloat(parts[2], 64)
|
||||
loadavg = []float64{l1, l2, l3}
|
||||
}
|
||||
}
|
||||
|
||||
intfs, err := getInterfaces()
|
||||
if err != nil {
|
||||
intfs = []models.Interface{}
|
||||
}
|
||||
|
||||
routes, err := getRoutes()
|
||||
if err != nil {
|
||||
routes = []models.Route{}
|
||||
}
|
||||
|
||||
wgPeers, err := getWgPeers()
|
||||
if err != nil {
|
||||
wgPeers = []models.WGPeer{}
|
||||
}
|
||||
|
||||
return models.SystemData{
|
||||
Hostname: strings.TrimSpace(string(hostname)),
|
||||
UptimeSeconds: uptimeSec,
|
||||
LoadAvg: loadavg,
|
||||
Interfaces: intfs,
|
||||
Routes: routes,
|
||||
WGPeers: wgPeers,
|
||||
}, nil
|
||||
}
|
||||
Reference in New Issue
Block a user