diff --git a/client/iface/configurer/kernel_unix.go b/client/iface/configurer/kernel_unix.go index fd5e3e626..87076fea8 100644 --- a/client/iface/configurer/kernel_unix.go +++ b/client/iface/configurer/kernel_unix.go @@ -201,19 +201,7 @@ func (c *KernelConfigurer) configure(config wgtypes.Config) error { func (c *KernelConfigurer) Close() { } -func (c *KernelConfigurer) GetStats(peerKey string) (WGStats, error) { - peer, err := c.getPeer(c.deviceName, peerKey) - if err != nil { - return WGStats{}, fmt.Errorf("get wireguard stats: %w", err) - } - return WGStats{ - LastHandshake: peer.LastHandshakeTime, - TxBytes: peer.TransmitBytes, - RxBytes: peer.ReceiveBytes, - }, nil -} - -func (c *KernelConfigurer) Transfers() (map[string]WGStats, error) { +func (c *KernelConfigurer) GetStats() (map[string]WGStats, error) { stats := make(map[string]WGStats) wg, err := wgctrl.New() if err != nil { @@ -230,6 +218,7 @@ func (c *KernelConfigurer) Transfers() (map[string]WGStats, error) { if err != nil { return nil, fmt.Errorf("get device %s: %w", c.deviceName, err) } + for _, peer := range wgDevice.Peers { stats[peer.PublicKey.String()] = WGStats{ LastHandshake: peer.LastHandshakeTime, diff --git a/client/iface/configurer/usp.go b/client/iface/configurer/usp.go index 6600c6f16..2731d7a16 100644 --- a/client/iface/configurer/usp.go +++ b/client/iface/configurer/usp.go @@ -225,45 +225,7 @@ func (t *WGUSPConfigurer) Close() { } } -func (t *WGUSPConfigurer) GetStats(peerKey string) (WGStats, error) { - ipc, err := t.device.IpcGet() - if err != nil { - return WGStats{}, fmt.Errorf("ipc get: %w", err) - } - - stats, err := findPeerInfo(ipc, peerKey, []string{ - ipcKeyLastHandshakeTimeSec, - ipcKeyLastHandshakeTimeNsec, - ipcKeyTxBytes, - ipcKeyRxBytes, - }) - if err != nil { - return WGStats{}, fmt.Errorf("find peer info: %w", err) - } - - lastHandshake, err := toLastHandshake(stats[ipcKeyLastHandshakeTimeSec]) - if err != nil { - return WGStats{}, err - } - - txBytes, err := toTxBytes(stats[ipcKeyTxBytes]) - if err != nil { - return WGStats{}, err - } - - rxBytes, err := toRxBytes(stats[ipcKeyRxBytes]) - if err != nil { - return WGStats{}, err - } - - return WGStats{ - LastHandshake: lastHandshake, - TxBytes: txBytes, - RxBytes: rxBytes, - }, nil -} - -func (t *WGUSPConfigurer) Transfers() (map[string]WGStats, error) { +func (t *WGUSPConfigurer) GetStats() (map[string]WGStats, error) { ipc, err := t.device.IpcGet() if err != nil { return nil, fmt.Errorf("ipc get: %w", err) @@ -315,18 +277,18 @@ func parseTransfers(ipc string) (map[string]WGStats, error) { currentStats.LastHandshake = hs stats[currentKey] = currentStats case ipcKeyRxBytes: - rxBytes, err := strconv.ParseInt(key[1], 10, 64) + rxBytes, err := toBytes(key[1]) if err != nil { return nil, fmt.Errorf("parse rx_bytes: %w", err) } currentStats.RxBytes = rxBytes stats[currentKey] = currentStats case ipcKeyTxBytes: - txBytes, err := strconv.ParseInt(key[1], 10, 64) + TxBytes, err := toBytes(key[1]) if err != nil { return nil, fmt.Errorf("parse tx_bytes: %w", err) } - currentStats.TxBytes = txBytes + currentStats.TxBytes = TxBytes stats[currentKey] = currentStats } } @@ -334,53 +296,6 @@ func parseTransfers(ipc string) (map[string]WGStats, error) { return stats, nil } -func findPeerInfo(ipcInput string, peerKey string, searchConfigKeys []string) (map[string]string, error) { - peerKeyParsed, err := wgtypes.ParseKey(peerKey) - if err != nil { - return nil, fmt.Errorf("parse key: %w", err) - } - - hexKey := hex.EncodeToString(peerKeyParsed[:]) - - lines := strings.Split(ipcInput, "\n") - - configFound := map[string]string{} - foundPeer := false - for _, line := range lines { - line = strings.TrimSpace(line) - - // If we're within the details of the found peer and encounter another public key, - // this means we're starting another peer's details. So, stop. - if strings.HasPrefix(line, "public_key=") && foundPeer { - break - } - - // Identify the peer with the specific public key - if line == fmt.Sprintf("public_key=%s", hexKey) { - foundPeer = true - } - - for _, key := range searchConfigKeys { - if foundPeer && strings.HasPrefix(line, key+"=") { - v := strings.SplitN(line, "=", 2) - configFound[v[0]] = v[1] - } - } - } - - // todo: use multierr - for _, key := range searchConfigKeys { - if _, ok := configFound[key]; !ok { - return configFound, fmt.Errorf("config key not found: %s", key) - } - } - if !foundPeer { - return nil, fmt.Errorf("%w: %s", ErrPeerNotFound, peerKey) - } - - return configFound, nil -} - func toWgUserspaceString(wgCfg wgtypes.Config) string { var sb strings.Builder if wgCfg.PrivateKey != nil { @@ -444,22 +359,6 @@ func toLastHandshake(stringVar string) (time.Time, error) { return time.Unix(sec, nsec), nil } -func toRxBytes(s string) (int64, error) { - b, err := toBytes(s) - if err != nil { - return 0, fmt.Errorf("parse rx_bytes: %w", err) - } - return b, nil -} - -func toTxBytes(s string) (int64, error) { - b, err := toBytes(s) - if err != nil { - return 0, fmt.Errorf("parse tx_bytes: %w", err) - } - return b, nil -} - func toBytes(s string) (int64, error) { return strconv.ParseInt(s, 10, 64) } diff --git a/client/iface/configurer/usp_test.go b/client/iface/configurer/usp_test.go index 8e43cb359..e32491c54 100644 --- a/client/iface/configurer/usp_test.go +++ b/client/iface/configurer/usp_test.go @@ -2,10 +2,8 @@ package configurer import ( "encoding/hex" - "fmt" "testing" - "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "golang.zx2c4.com/wireguard/wgctrl/wgtypes" ) @@ -34,69 +32,35 @@ errno=0 ` -func Test_Transfer(t *testing.T) { - stats, err := parseTransfers(ipcFixture) - if err != nil { - t.Fatal(err) - } - - if len(stats) != 3 { - t.Fatalf("expected 2 stats, got %d", len(stats)) - } -} - -func Test_findPeerInfo(t *testing.T) { +func Test_parseTransfers(t *testing.T) { tests := []struct { - name string - peerKey string - searchKeys []string - want map[string]string - wantErr bool + name string + peerKey string + want WGStats }{ { - name: "single", - peerKey: "58402e695ba1772b1cc9309755f043251ea77fdcf10fbe63989ceb7e19321376", - searchKeys: []string{"tx_bytes"}, - want: map[string]string{ - "tx_bytes": "38333", + name: "single", + peerKey: "b85996fecc9c7f1fc6d2572a76eda11d59bcd20be8e543b15ce4bd85a8e75a33", + want: WGStats{ + TxBytes: 0, + RxBytes: 0, }, - wantErr: false, }, { - name: "multiple", - peerKey: "58402e695ba1772b1cc9309755f043251ea77fdcf10fbe63989ceb7e19321376", - searchKeys: []string{"tx_bytes", "rx_bytes"}, - want: map[string]string{ - "tx_bytes": "38333", - "rx_bytes": "2224", + name: "multiple", + peerKey: "58402e695ba1772b1cc9309755f043251ea77fdcf10fbe63989ceb7e19321376", + want: WGStats{ + TxBytes: 38333, + RxBytes: 2224, }, - wantErr: false, }, { - name: "lastpeer", - peerKey: "662e14fd594556f522604703340351258903b64f35553763f19426ab2a515c58", - searchKeys: []string{"tx_bytes", "rx_bytes"}, - want: map[string]string{ - "tx_bytes": "1212111", - "rx_bytes": "1929999999", + name: "lastpeer", + peerKey: "662e14fd594556f522604703340351258903b64f35553763f19426ab2a515c58", + want: WGStats{ + TxBytes: 1212111, + RxBytes: 1929999999, }, - wantErr: false, - }, - { - name: "peer not found", - peerKey: "1111111111111111111111111111111111111111111111111111111111111111", - searchKeys: nil, - want: nil, - wantErr: true, - }, - { - name: "key not found", - peerKey: "662e14fd594556f522604703340351258903b64f35553763f19426ab2a515c58", - searchKeys: []string{"tx_bytes", "unknown_key"}, - want: map[string]string{ - "tx_bytes": "1212111", - }, - wantErr: true, }, } for _, tt := range tests { @@ -107,9 +71,19 @@ func Test_findPeerInfo(t *testing.T) { key, err := wgtypes.NewKey(res) require.NoError(t, err) - got, err := findPeerInfo(ipcFixture, key.String(), tt.searchKeys) - assert.Equalf(t, tt.wantErr, err != nil, fmt.Sprintf("findPeerInfo(%v, %v, %v)", ipcFixture, key.String(), tt.searchKeys)) - assert.Equalf(t, tt.want, got, "findPeerInfo(%v, %v, %v)", ipcFixture, key.String(), tt.searchKeys) + stats, err := parseTransfers(ipcFixture) + if err != nil { + require.NoError(t, err) + return + } + + stat, ok := stats[key.String()] + if !ok { + require.True(t, ok) + return + } + + require.Equal(t, tt.want, stat) }) } } diff --git a/client/iface/device/interface.go b/client/iface/device/interface.go index 2a368f6e6..a1d44a150 100644 --- a/client/iface/device/interface.go +++ b/client/iface/device/interface.go @@ -16,6 +16,5 @@ type WGConfigurer interface { AddAllowedIP(peerKey string, allowedIP string) error RemoveAllowedIP(peerKey string, allowedIP string) error Close() - GetStats(peerKey string) (configurer.WGStats, error) - Transfers() (map[string]configurer.WGStats, error) + GetStats() (map[string]configurer.WGStats, error) } diff --git a/client/iface/iface.go b/client/iface/iface.go index d952524d9..3118e5e3f 100644 --- a/client/iface/iface.go +++ b/client/iface/iface.go @@ -213,13 +213,9 @@ func (w *WGIface) GetWGDevice() *wgdevice.Device { return w.tun.Device() } -// GetStats returns the last handshake time, rx and tx bytes for the given peer -func (w *WGIface) GetStats(peerKey string) (configurer.WGStats, error) { - return w.configurer.GetStats(peerKey) -} - -func (w *WGIface) Transfers() (map[string]configurer.WGStats, error) { - return w.configurer.Transfers() +// GetStats returns the last handshake time, rx and tx bytes +func (w *WGIface) GetStats() (map[string]configurer.WGStats, error) { + return w.configurer.GetStats() } func (w *WGIface) waitUntilRemoved() error { diff --git a/client/internal/dns/wgiface.go b/client/internal/dns/wgiface.go index 69bc83659..3ee8313b6 100644 --- a/client/internal/dns/wgiface.go +++ b/client/internal/dns/wgiface.go @@ -6,7 +6,6 @@ import ( "net" "github.com/netbirdio/netbird/client/iface" - "github.com/netbirdio/netbird/client/iface/configurer" "github.com/netbirdio/netbird/client/iface/device" ) @@ -18,5 +17,4 @@ type WGIface interface { IsUserspaceBind() bool GetFilter() device.PacketFilter GetDevice() *device.FilteredDevice - GetStats(peerKey string) (configurer.WGStats, error) } diff --git a/client/internal/dns/wgiface_windows.go b/client/internal/dns/wgiface_windows.go index 765132fdb..7800d5e1a 100644 --- a/client/internal/dns/wgiface_windows.go +++ b/client/internal/dns/wgiface_windows.go @@ -2,7 +2,6 @@ package dns import ( "github.com/netbirdio/netbird/client/iface" - "github.com/netbirdio/netbird/client/iface/configurer" "github.com/netbirdio/netbird/client/iface/device" ) @@ -13,6 +12,5 @@ type WGIface interface { IsUserspaceBind() bool GetFilter() device.PacketFilter GetDevice() *device.FilteredDevice - GetStats(peerKey string) (configurer.WGStats, error) GetInterfaceGUIDString() (string, error) } diff --git a/client/internal/engine.go b/client/internal/engine.go index 9fa721dcb..3f17d99be 100644 --- a/client/internal/engine.go +++ b/client/internal/engine.go @@ -1542,13 +1542,17 @@ func (e *Engine) RunHealthProbes() bool { } log.Debugf("relay health check: healthy=%t", relayHealthy) + stats, err := e.wgInterface.GetStats() + if err != nil { + log.Warnf("failed to get wireguard stats: %v", err) + return false + } for _, key := range e.peerStore.PeersPubKey() { - wgStats, err := e.wgInterface.GetStats(key) - if err != nil { - log.Debugf("failed to get wg stats for peer %s: %s", key, err) + // wgStats could be zero value, in which case we just reset the stats + wgStats, ok := stats[key] + if !ok { continue } - // wgStats could be zero value, in which case we just reset the stats if err := e.statusRecorder.UpdateWireGuardPeerState(key, wgStats); err != nil { log.Debugf("failed to update wg stats for peer %s: %s", key, err) } diff --git a/client/internal/engine_test.go b/client/internal/engine_test.go index 65581bef8..b089b19c3 100644 --- a/client/internal/engine_test.go +++ b/client/internal/engine_test.go @@ -87,8 +87,7 @@ type MockWGIface struct { GetFilterFunc func() device.PacketFilter GetDeviceFunc func() *device.FilteredDevice GetWGDeviceFunc func() *wgdevice.Device - GetStatsFunc func(peerKey string) (configurer.WGStats, error) - TransfersMoc func() (map[string]configurer.WGStats, error) + GetStatsFunc func() (map[string]configurer.WGStats, error) GetInterfaceGUIDStringFunc func() (string, error) GetProxyFunc func() wgproxy.Proxy GetNetFunc func() *netstack.Net @@ -166,12 +165,8 @@ func (m *MockWGIface) GetWGDevice() *wgdevice.Device { return m.GetWGDeviceFunc() } -func (m *MockWGIface) GetStats(peerKey string) (configurer.WGStats, error) { - return m.GetStatsFunc(peerKey) -} - -func (m *MockWGIface) Transfers() (map[string]configurer.WGStats, error) { - return m.TransfersMoc() +func (m *MockWGIface) GetStats() (map[string]configurer.WGStats, error) { + return m.GetStatsFunc() } func (m *MockWGIface) GetProxy() wgproxy.Proxy { diff --git a/client/internal/iface_common.go b/client/internal/iface_common.go index 7e45759c6..675748c95 100644 --- a/client/internal/iface_common.go +++ b/client/internal/iface_common.go @@ -34,7 +34,6 @@ type wgIfaceBase interface { GetFilter() device.PacketFilter GetDevice() *device.FilteredDevice GetWGDevice() *wgdevice.Device - GetStats(peerKey string) (configurer.WGStats, error) - Transfers() (map[string]configurer.WGStats, error) + GetStats() (map[string]configurer.WGStats, error) GetNet() *netstack.Net } diff --git a/client/internal/lazyconn/watcher/watcher.go b/client/internal/lazyconn/watcher/watcher.go index d5a1e8799..ef20f5cde 100644 --- a/client/internal/lazyconn/watcher/watcher.go +++ b/client/internal/lazyconn/watcher/watcher.go @@ -48,7 +48,7 @@ func (m *Watcher) Watch(ctx context.Context) { case <-ctx.Done(): return case <-timer.C: - stats, err := m.wgIface.Transfers() + stats, err := m.wgIface.GetStats() if err != nil { log.Errorf("failed to get peer stats: %s", err) continue diff --git a/client/internal/lazyconn/wgiface.go b/client/internal/lazyconn/wgiface.go index 4eff7b833..a2f7dc7a6 100644 --- a/client/internal/lazyconn/wgiface.go +++ b/client/internal/lazyconn/wgiface.go @@ -11,7 +11,7 @@ import ( ) type WGIface interface { - Transfers() (map[string]configurer.WGStats, error) RemovePeer(peerKey string) error UpdatePeer(peerKey string, allowedIps []netip.Prefix, keepAlive time.Duration, endpoint *net.UDPAddr, preSharedKey *wgtypes.Key) error + GetStats() (map[string]configurer.WGStats, error) } diff --git a/client/internal/peer/iface.go b/client/internal/peer/iface.go index c7b6de9ea..3ad5e26f8 100644 --- a/client/internal/peer/iface.go +++ b/client/internal/peer/iface.go @@ -14,6 +14,6 @@ import ( type WGIface interface { UpdatePeer(peerKey string, allowedIps []netip.Prefix, keepAlive time.Duration, endpoint *net.UDPAddr, preSharedKey *wgtypes.Key) error RemovePeer(peerKey string) error - GetStats(peerKey string) (configurer.WGStats, error) + GetStats() (map[string]configurer.WGStats, error) GetProxy() wgproxy.Proxy } diff --git a/client/internal/peer/wg_watcher.go b/client/internal/peer/wg_watcher.go index 6670c6517..f7258d3b0 100644 --- a/client/internal/peer/wg_watcher.go +++ b/client/internal/peer/wg_watcher.go @@ -2,6 +2,7 @@ package peer import ( "context" + "fmt" "sync" "time" @@ -20,7 +21,7 @@ var ( ) type WGInterfaceStater interface { - GetStats(key string) (configurer.WGStats, error) + GetStats() (map[string]configurer.WGStats, error) } type WGWatcher struct { @@ -146,9 +147,13 @@ func (w *WGWatcher) handshakeCheck(lastHandshake time.Time) (*time.Time, bool) { } func (w *WGWatcher) wgState() (time.Time, error) { - wgState, err := w.wgIfaceStater.GetStats(w.peerKey) + wgStates, err := w.wgIfaceStater.GetStats() if err != nil { return time.Time{}, err } + wgState, ok := wgStates[w.peerKey] + if !ok { + return time.Time{}, fmt.Errorf("peer %s not found in WireGuard endpoints", w.peerKey) + } return wgState.LastHandshake, nil } diff --git a/client/internal/peer/wg_watcher_test.go b/client/internal/peer/wg_watcher_test.go index a5b9026ad..a3e70aaca 100644 --- a/client/internal/peer/wg_watcher_test.go +++ b/client/internal/peer/wg_watcher_test.go @@ -11,26 +11,11 @@ import ( ) type MocWgIface struct { - initial bool - lastHandshake time.Time - stop bool + stop bool } -func (m *MocWgIface) GetStats(key string) (configurer.WGStats, error) { - if !m.initial { - m.initial = true - return configurer.WGStats{}, nil - } - - if !m.stop { - m.lastHandshake = time.Now() - } - - stats := configurer.WGStats{ - LastHandshake: m.lastHandshake, - } - - return stats, nil +func (m *MocWgIface) GetStats() (map[string]configurer.WGStats, error) { + return map[string]configurer.WGStats{}, nil } func (m *MocWgIface) disconnect() { diff --git a/client/internal/routemanager/iface/iface_common.go b/client/internal/routemanager/iface/iface_common.go index 8b2dc9714..7ae9f3cda 100644 --- a/client/internal/routemanager/iface/iface_common.go +++ b/client/internal/routemanager/iface/iface_common.go @@ -4,7 +4,6 @@ import ( "net" "github.com/netbirdio/netbird/client/iface" - "github.com/netbirdio/netbird/client/iface/configurer" "github.com/netbirdio/netbird/client/iface/device" ) @@ -18,5 +17,4 @@ type wgIfaceBase interface { IsUserspaceBind() bool GetFilter() device.PacketFilter GetDevice() *device.FilteredDevice - GetStats(peerKey string) (configurer.WGStats, error) }