Files
netbird/client/system/info.go
Zoltan Papp fd96b8c12f [client] Improve network addresses filter (#6515)
* [client] Filter link-local and multicast from network addresses

Skip IPv6 link-local and multicast addresses when building the peer
network_addresses list on non-iOS platforms, matching the existing iOS
behavior. A flapping NIC's link-local address otherwise churns the peer
meta on every interface up/down.

* [client] Skip engine restart when default route is unchanged

After the network monitor's debounce window, re-check the default next
hop before triggering a client restart. A flapping NIC that returns to
the same default route no longer forces a restart, avoiding redundant
sync stream reconnects and peer meta churn.

* [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] Treat missing default route per protocol in next-hop check

A failed GetNextHop lookup is now treated as an absent route (zero
Nexthop) and compared per protocol, instead of forcing a restart. In a
single-stack network the missing IPv6 default route no longer counts as
a change on every debounce, which previously defeated the unchanged-route
check.

* [client] Make next-hop check injectable for network monitor tests

Move the next-hop comparison behind a NetworkMonitor field set by New(),
so tests can supply a stub instead of hitting the host's real default
route. Fixes the Event/MultiEvent tests hanging after the unchanged-route
check was added.

* Revert "[client] Make next-hop check injectable for network monitor tests"

This reverts commit 88a9d96e8f.

* Revert "[client] Treat missing default route per protocol in next-hop check"

This reverts commit 0fb531e4bc.

* Revert "[client] Skip engine restart when default route is unchanged"

This reverts commit a071b55f35.
2026-06-28 12:44:40 +02:00

190 lines
5.1 KiB
Go

package system
import (
"context"
"net/netip"
"slices"
"strings"
log "github.com/sirupsen/logrus"
"google.golang.org/grpc/metadata"
"github.com/netbirdio/netbird/shared/management/proto"
)
// DeviceNameCtxKey context key for device name
const DeviceNameCtxKey = "deviceName"
// OsVersionCtxKey context key for operating system version
const OsVersionCtxKey = "OsVersion"
// OsNameCtxKey context key for operating system name
const OsNameCtxKey = "OsName"
// UiVersionCtxKey context key for user UI version
const UiVersionCtxKey = "user-agent"
type NetworkAddress struct {
NetIP netip.Prefix
Mac string
}
type Environment struct {
Cloud string
Platform string
}
type File struct {
Path string
Exist bool
ProcessIsRunning bool
}
// Info is an object that contains machine information
// Most of the code is taken from https://github.com/matishsiao/goInfo
type Info struct {
GoOS string
Kernel string
Platform string
OS string
OSVersion string
Hostname string
CPUs int
NetbirdVersion string
UIVersion string
KernelVersion string
NetworkAddresses []NetworkAddress
SystemSerialNumber string
SystemProductName string
SystemManufacturer string
Environment Environment
Files []File // for posture checks
RosenpassEnabled bool
RosenpassPermissive bool
ServerSSHAllowed bool
DisableClientRoutes bool
DisableServerRoutes bool
DisableDNS bool
DisableFirewall bool
BlockLANAccess bool
BlockInbound bool
DisableIPv6 bool
LazyConnectionEnabled bool
EnableSSHRoot bool
EnableSSHSFTP bool
EnableSSHLocalPortForwarding bool
EnableSSHRemotePortForwarding bool
DisableSSHAuth bool
}
func (i *Info) SetFlags(
rosenpassEnabled, rosenpassPermissive bool,
serverSSHAllowed *bool,
disableClientRoutes, disableServerRoutes,
disableDNS, disableFirewall, blockLANAccess, blockInbound, disableIPv6, lazyConnectionEnabled bool,
enableSSHRoot, enableSSHSFTP, enableSSHLocalPortForwarding, enableSSHRemotePortForwarding *bool,
disableSSHAuth *bool,
) {
i.RosenpassEnabled = rosenpassEnabled
i.RosenpassPermissive = rosenpassPermissive
if serverSSHAllowed != nil {
i.ServerSSHAllowed = *serverSSHAllowed
}
i.DisableClientRoutes = disableClientRoutes
i.DisableServerRoutes = disableServerRoutes
i.DisableDNS = disableDNS
i.DisableFirewall = disableFirewall
i.BlockLANAccess = blockLANAccess
i.BlockInbound = blockInbound
i.DisableIPv6 = disableIPv6
i.LazyConnectionEnabled = lazyConnectionEnabled
if enableSSHRoot != nil {
i.EnableSSHRoot = *enableSSHRoot
}
if enableSSHSFTP != nil {
i.EnableSSHSFTP = *enableSSHSFTP
}
if enableSSHLocalPortForwarding != nil {
i.EnableSSHLocalPortForwarding = *enableSSHLocalPortForwarding
}
if enableSSHRemotePortForwarding != nil {
i.EnableSSHRemotePortForwarding = *enableSSHRemotePortForwarding
}
if disableSSHAuth != nil {
i.DisableSSHAuth = *disableSSHAuth
}
}
// 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)
if hasMeta {
agent, ok := md["user-agent"]
if ok {
nbAgent := strings.Split(agent[0], " ")[0]
if strings.HasPrefix(nbAgent, "netbird") {
return nbAgent
}
return ""
}
}
return ""
}
// extractDeviceName extracts device name from context or returns the default system name
func extractDeviceName(ctx context.Context, defaultName string) string {
v, ok := ctx.Value(DeviceNameCtxKey).(string)
if !ok {
return defaultName
}
return v
}
// GetInfoWithChecks retrieves and parses the system information with applied checks.
// 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 {
processCheckPaths = append(processCheckPaths, check.GetFiles()...)
}
files, err := checkFileAndProcess(processCheckPaths)
if err != nil {
return nil, err
}
log.Debugf("gathering process check information completed")
info := GetInfo(ctx)
info.Files = files
info.removeAddresses(excludeIPs...)
log.Debugf("all system information gathered successfully")
return info, nil
}