From 3a6d0c3f0ba3e46b120c32837710705a8100babf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Zolt=C3=A1n=20Papp?= Date: Tue, 23 Jun 2026 01:07:36 +0200 Subject: [PATCH] [client] Exclude own overlay address from reported network addresses The peer's own WireGuard overlay address (v4 and v6) was reported in network_addresses. As the interface comes and goes during reconnects it churned the peer meta on the management server. Drop it in GetInfoWithChecks, matching the IP regardless of prefix length since the engine knows the overlay address with the network mask while the interface reports it as a host address. --- client/internal/engine.go | 18 +++++++++++++++-- client/system/info.go | 23 +++++++++++++++++++++- client/system/info_test.go | 40 ++++++++++++++++++++++++++++++++++++++ 3 files changed, 78 insertions(+), 3 deletions(-) diff --git a/client/internal/engine.go b/client/internal/engine.go index 452075da8..e7f1c0501 100644 --- a/client/internal/engine.go +++ b/client/internal/engine.go @@ -1066,7 +1066,7 @@ func (e *Engine) updateChecksIfNew(checks []*mgmProto.Checks) error { } e.checks = checks - info, err := system.GetInfoWithChecks(e.ctx, checks) + info, err := system.GetInfoWithChecks(e.ctx, checks, e.overlayAddresses()...) if err != nil { log.Warnf("failed to get system info with checks: %v", err) info = system.GetInfo(e.ctx) @@ -1097,6 +1097,20 @@ func (e *Engine) updateChecksIfNew(checks []*mgmProto.Checks) error { return nil } +// overlayAddresses returns our own WireGuard overlay address (v4 and v6) so it +// can be excluded from the reported network addresses; the interface coming and +// going otherwise churns the peer meta on the management server. +func (e *Engine) overlayAddresses() []netip.Addr { + var ips []netip.Addr + if e.config.WgAddr.IP.IsValid() { + ips = append(ips, e.config.WgAddr.IP) + } + if e.config.WgAddr.HasIPv6() { + ips = append(ips, e.config.WgAddr.IPv6) + } + return ips +} + func (e *Engine) updateConfig(conf *mgmProto.PeerConfig) error { if e.wgInterface == nil { return errors.New("wireguard interface is not initialized") @@ -1240,7 +1254,7 @@ func (e *Engine) receiveManagementEvents() { e.shutdownWg.Add(1) go func() { defer e.shutdownWg.Done() - info, err := system.GetInfoWithChecks(e.ctx, e.checks) + info, err := system.GetInfoWithChecks(e.ctx, e.checks, e.overlayAddresses()...) if err != nil { log.Warnf("failed to get system info with checks: %v", err) info = system.GetInfo(e.ctx) diff --git a/client/system/info.go b/client/system/info.go index 477d5162b..27588859e 100644 --- a/client/system/info.go +++ b/client/system/info.go @@ -3,6 +3,7 @@ package system import ( "context" "net/netip" + "slices" "strings" log "github.com/sirupsen/logrus" @@ -121,6 +122,23 @@ func (i *Info) SetFlags( } } +// removeAddresses drops network addresses whose IP matches any of the given +// addresses, regardless of prefix length. Used to exclude the NetBird overlay +// address, which otherwise churns the meta as the interface comes and goes. +func (i *Info) removeAddresses(ips ...netip.Addr) { + if len(ips) == 0 { + return + } + filtered := i.NetworkAddresses[:0] + for _, addr := range i.NetworkAddresses { + if slices.Contains(ips, addr.NetIP.Addr()) { + continue + } + filtered = append(filtered, addr) + } + i.NetworkAddresses = filtered +} + // extractUserAgent extracts Netbird's agent (client) name and version from the outgoing context func extractUserAgent(ctx context.Context) string { md, hasMeta := metadata.FromOutgoingContext(ctx) @@ -147,7 +165,9 @@ func extractDeviceName(ctx context.Context, defaultName string) string { } // GetInfoWithChecks retrieves and parses the system information with applied checks. -func GetInfoWithChecks(ctx context.Context, checks []*proto.Checks) (*Info, error) { +// excludeIPs are dropped from the reported network addresses (e.g. our own +// WireGuard overlay address, which otherwise churns the peer meta). +func GetInfoWithChecks(ctx context.Context, checks []*proto.Checks, excludeIPs ...netip.Addr) (*Info, error) { log.Debugf("gathering system information with checks: %d", len(checks)) processCheckPaths := make([]string, 0) for _, check := range checks { @@ -162,6 +182,7 @@ func GetInfoWithChecks(ctx context.Context, checks []*proto.Checks) (*Info, erro info := GetInfo(ctx) info.Files = files + info.removeAddresses(excludeIPs...) log.Debugf("all system information gathered successfully") return info, nil diff --git a/client/system/info_test.go b/client/system/info_test.go index 27821f3c5..dcda18e61 100644 --- a/client/system/info_test.go +++ b/client/system/info_test.go @@ -2,6 +2,7 @@ package system import ( "context" + "net/netip" "testing" "github.com/stretchr/testify/assert" @@ -43,3 +44,42 @@ func Test_NetAddresses(t *testing.T) { t.Errorf("no network addresses found") } } + +func TestInfo_RemoveAddresses(t *testing.T) { + addr := func(cidr string) NetworkAddress { + return NetworkAddress{NetIP: netip.MustParsePrefix(cidr)} + } + + info := &Info{ + NetworkAddresses: []NetworkAddress{ + addr("192.168.1.7/24"), + addr("100.76.70.97/32"), // overlay v4 (host mask /32) + addr("2001:818:c51b:4800:845:a65d:ae6f:623f/64"), // real global v6 + addr("fd00:1234::1/64"), // overlay v6 + }, + } + + // Overlay addresses as the engine knows them, with a different mask (/16, /64). + info.removeAddresses( + netip.MustParseAddr("100.76.70.97"), + netip.MustParseAddr("fd00:1234::1"), + ) + + want := []string{"192.168.1.7/24", "2001:818:c51b:4800:845:a65d:ae6f:623f/64"} + if len(info.NetworkAddresses) != len(want) { + t.Fatalf("got %d addresses, want %d: %v", len(info.NetworkAddresses), len(want), info.NetworkAddresses) + } + for i, w := range want { + if got := info.NetworkAddresses[i].NetIP.String(); got != w { + t.Errorf("address[%d] = %s, want %s", i, got, w) + } + } +} + +func TestInfo_RemoveAddresses_NoOp(t *testing.T) { + info := &Info{NetworkAddresses: []NetworkAddress{{NetIP: netip.MustParsePrefix("10.0.0.1/24")}}} + info.removeAddresses() + if len(info.NetworkAddresses) != 1 { + t.Errorf("expected no change with empty input, got %v", info.NetworkAddresses) + } +}