| 1 | // SPDX-License-Identifier: GPL-3.0-or-later |
| 2 | |
| 3 | package wireguard |
| 4 | |
| 5 | import ( |
| 6 | "fmt" |
| 7 | "time" |
| 8 | |
| 9 | "golang.zx2c4.com/wireguard/wgctrl/wgtypes" |
| 10 | ) |
| 11 | |
| 12 | func (c *Collector) collect() (map[string]int64, error) { |
| 13 | if c.client == nil { |
| 14 | client, err := c.newWGClient() |
| 15 | if err != nil { |
| 16 | return nil, fmt.Errorf("creating WireGuard client: %v", err) |
| 17 | } |
| 18 | c.client = client |
| 19 | } |
| 20 | |
| 21 | // TODO: probably we need to get a list of interfaces and query interfaces using client.Device() |
| 22 | // https://github.com/WireGuard/wgctrl-go/blob/3d4a969bb56bb6931f6661af606bc9c4195b4249/internal/wglinux/client_linux.go#L79-L80 |
| 23 | devices, err := c.client.Devices() |
| 24 | if err != nil { |
| 25 | return nil, fmt.Errorf("retrieving WireGuard devices: %v", err) |
| 26 | } |
| 27 | |
| 28 | if len(devices) == 0 { |
| 29 | c.Info("no WireGuard devices found on the host system") |
| 30 | } |
| 31 | |
| 32 | now := time.Now() |
| 33 | if c.cleanupLastTime.IsZero() { |
| 34 | c.cleanupLastTime = now |
| 35 | } |
| 36 | |
| 37 | mx := make(map[string]int64) |
| 38 | |
| 39 | c.collectDevicesPeers(mx, devices, now) |
| 40 | |
| 41 | if now.Sub(c.cleanupLastTime) > c.cleanupEvery { |
| 42 | c.cleanupLastTime = now |
| 43 | c.cleanupDevicesPeers(devices) |
| 44 | } |
| 45 | |
| 46 | return mx, nil |
| 47 | } |
| 48 | |
| 49 | func (c *Collector) collectDevicesPeers(mx map[string]int64, devices []*wgtypes.Device, now time.Time) { |
| 50 | for _, d := range devices { |
| 51 | if !c.devices[d.Name] { |
| 52 | c.devices[d.Name] = true |
| 53 | c.addNewDeviceCharts(d.Name) |
| 54 | } |
| 55 | |
| 56 | mx["device_"+d.Name+"_peers"] = int64(len(d.Peers)) |
| 57 | if len(d.Peers) == 0 { |
| 58 | mx["device_"+d.Name+"_receive"] = 0 |
| 59 | mx["device_"+d.Name+"_transmit"] = 0 |
| 60 | continue |
| 61 | } |
| 62 | |
| 63 | for _, p := range d.Peers { |
| 64 | if p.LastHandshakeTime.IsZero() { |
| 65 | continue |
| 66 | } |
| 67 | |
| 68 | pubKey := p.PublicKey.String() |
| 69 | id := peerID(d.Name, pubKey) |
| 70 | |
| 71 | if !c.peers[id] { |
| 72 | c.peers[id] = true |
| 73 | c.addNewPeerCharts(id, d.Name, pubKey) |
| 74 | } |
| 75 | |
| 76 | mx["device_"+d.Name+"_receive"] += p.ReceiveBytes |
| 77 | mx["device_"+d.Name+"_transmit"] += p.TransmitBytes |
| 78 | mx["peer_"+id+"_receive"] = p.ReceiveBytes |
| 79 | mx["peer_"+id+"_transmit"] = p.TransmitBytes |
| 80 | mx["peer_"+id+"_latest_handshake_ago"] = int64(now.Sub(p.LastHandshakeTime).Seconds()) |
| 81 | } |
| 82 | } |
| 83 | } |
| 84 | |
| 85 | func (c *Collector) cleanupDevicesPeers(devices []*wgtypes.Device) { |
| 86 | seenDevices, seenPeers := make(map[string]bool), make(map[string]bool) |
| 87 | for _, d := range devices { |
| 88 | seenDevices[d.Name] = true |
| 89 | for _, p := range d.Peers { |
| 90 | seenPeers[peerID(d.Name, p.PublicKey.String())] = true |
| 91 | } |
| 92 | } |
| 93 | for d := range c.devices { |
| 94 | if !seenDevices[d] { |
| 95 | delete(c.devices, d) |
| 96 | c.removeDeviceCharts(d) |
| 97 | } |
| 98 | } |
| 99 | for p := range c.peers { |
| 100 | if !seenPeers[p] { |
| 101 | delete(c.peers, p) |
| 102 | c.removePeerCharts(p) |
| 103 | } |
| 104 | } |
| 105 | } |
| 106 | |
| 107 | func peerID(device, peerPublicKey string) string { |
| 108 | return device + "_" + peerPublicKey |
| 109 | } |