Merge branch 'main' into fix/pkg-loss

This commit is contained in:
Zoltán Papp
2025-02-24 10:44:32 +01:00
51 changed files with 2269 additions and 1738 deletions

View File

@@ -99,7 +99,7 @@ type Config struct {
BlockLANAccess bool
DisableNotifications bool
DisableNotifications *bool
DNSLabels domain.List
@@ -479,13 +479,20 @@ func (config *Config) apply(input ConfigInput) (updated bool, err error) {
updated = true
}
if input.DisableNotifications != nil && *input.DisableNotifications != config.DisableNotifications {
if input.DisableNotifications != nil && input.DisableNotifications != config.DisableNotifications {
if *input.DisableNotifications {
log.Infof("disabling notifications")
} else {
log.Infof("enabling notifications")
}
config.DisableNotifications = *input.DisableNotifications
config.DisableNotifications = input.DisableNotifications
updated = true
}
if config.DisableNotifications == nil {
disabled := true
config.DisableNotifications = &disabled
log.Infof("setting notifications to disabled by default")
updated = true
}

View File

@@ -23,6 +23,7 @@ import (
"github.com/netbirdio/netbird/client/internal/listener"
"github.com/netbirdio/netbird/client/internal/peer"
"github.com/netbirdio/netbird/client/internal/stdnet"
cProto "github.com/netbirdio/netbird/client/proto"
"github.com/netbirdio/netbird/client/ssh"
"github.com/netbirdio/netbird/client/system"
mgm "github.com/netbirdio/netbird/management/client"
@@ -104,6 +105,16 @@ func (c *ConnectClient) RunOniOS(
func (c *ConnectClient) run(mobileDependency MobileDependency, runningChan chan error) error {
defer func() {
if r := recover(); r != nil {
rec := c.statusRecorder
if rec != nil {
rec.PublishEvent(
cProto.SystemEvent_CRITICAL, cProto.SystemEvent_SYSTEM,
"panic occurred",
"The Netbird service panicked. Please restart the service and submit a bug report with the client logs.",
nil,
)
}
log.Panicf("Panic occurred: %v, stack trace: %s", r, string(debug.Stack()))
}
}()

View File

@@ -58,7 +58,7 @@ func (f *fileConfigurator) applyDNSConfig(config HostDNSConfig, stateManager *st
return fmt.Errorf("restoring the original resolv.conf file return err: %w", err)
}
}
return fmt.Errorf("unable to configure DNS for this peer using file manager without a nameserver group with all domains configured")
return ErrRouteAllWithoutNameserverGroup
}
if !backupFileExist {
@@ -121,6 +121,10 @@ func (f *fileConfigurator) restoreHostDNS() error {
return f.restore()
}
func (f *fileConfigurator) string() string {
return "file"
}
func (f *fileConfigurator) backup() error {
stats, err := os.Stat(defaultResolvConfPath)
if err != nil {

View File

@@ -9,6 +9,8 @@ import (
nbdns "github.com/netbirdio/netbird/dns"
)
var ErrRouteAllWithoutNameserverGroup = fmt.Errorf("unable to configure DNS for this peer using file manager without a nameserver group with all domains configured")
const (
ipv4ReverseZone = ".in-addr.arpa"
ipv6ReverseZone = ".ip6.arpa"
@@ -18,6 +20,7 @@ type hostManager interface {
applyDNSConfig(config HostDNSConfig, stateManager *statemanager.Manager) error
restoreHostDNS() error
supportCustomPort() bool
string() string
}
type SystemDNSSettings struct {
@@ -44,6 +47,7 @@ type mockHostConfigurator struct {
restoreHostDNSFunc func() error
supportCustomPortFunc func() bool
restoreUncleanShutdownDNSFunc func(*netip.Addr) error
stringFunc func() string
}
func (m *mockHostConfigurator) applyDNSConfig(config HostDNSConfig, stateManager *statemanager.Manager) error {
@@ -67,6 +71,13 @@ func (m *mockHostConfigurator) supportCustomPort() bool {
return false
}
func (m *mockHostConfigurator) string() string {
if m.stringFunc != nil {
return m.stringFunc()
}
return "mock"
}
func newNoopHostMocker() hostManager {
return &mockHostConfigurator{
applyDNSConfigFunc: func(config HostDNSConfig, stateManager *statemanager.Manager) error { return nil },
@@ -122,3 +133,7 @@ func (n noopHostConfigurator) restoreHostDNS() error {
func (n noopHostConfigurator) supportCustomPort() bool {
return true
}
func (n noopHostConfigurator) string() string {
return "noop"
}

View File

@@ -22,3 +22,7 @@ func (a androidHostManager) restoreHostDNS() error {
func (a androidHostManager) supportCustomPort() bool {
return false
}
func (a androidHostManager) string() string {
return "none"
}

View File

@@ -114,6 +114,10 @@ func (s *systemConfigurator) applyDNSConfig(config HostDNSConfig, stateManager *
return nil
}
func (s *systemConfigurator) string() string {
return "scutil"
}
func (s *systemConfigurator) restoreHostDNS() error {
keys := s.getRemovableKeysWithDefaults()
for _, key := range keys {

View File

@@ -38,3 +38,7 @@ func (a iosHostManager) restoreHostDNS() error {
func (a iosHostManager) supportCustomPort() bool {
return false
}
func (a iosHostManager) string() string {
return "none"
}

View File

@@ -184,6 +184,10 @@ func (r *registryConfigurator) configureDNSPolicy(policyPath string, domains []s
return nil
}
func (r *registryConfigurator) string() string {
return "registry"
}
func (r *registryConfigurator) updateSearchDomains(domains []string) error {
if err := r.setInterfaceRegistryKeyStringValue(interfaceConfigSearchListKey, strings.Join(domains, ",")); err != nil {
return fmt.Errorf("update search domains: %w", err)

View File

@@ -179,6 +179,10 @@ func (n *networkManagerDbusConfigurator) restoreHostDNS() error {
return nil
}
func (n *networkManagerDbusConfigurator) string() string {
return "network-manager"
}
func (n *networkManagerDbusConfigurator) getAppliedConnectionSettings() (networkManagerConnSettings, networkManagerConfigVersion, error) {
obj, closeConn, err := getDbusObject(networkManagerDest, n.dbusLinkObject)
if err != nil {

View File

@@ -91,7 +91,7 @@ func (r *resolvconf) applyDNSConfig(config HostDNSConfig, stateManager *stateman
if err != nil {
log.Errorf("restore host dns: %s", err)
}
return fmt.Errorf("unable to configure DNS for this peer using resolvconf manager without a nameserver group with all domains configured")
return ErrRouteAllWithoutNameserverGroup
}
searchDomainList := searchDomains(config)
@@ -139,6 +139,10 @@ func (r *resolvconf) restoreHostDNS() error {
return nil
}
func (r *resolvconf) string() string {
return fmt.Sprintf("resolvconf (%s)", r.implType)
}
func (r *resolvconf) applyConfig(content bytes.Buffer) error {
var cmd *exec.Cmd

View File

@@ -2,6 +2,7 @@ package dns
import (
"context"
"errors"
"fmt"
"net/netip"
"runtime"
@@ -15,6 +16,7 @@ import (
"github.com/netbirdio/netbird/client/internal/listener"
"github.com/netbirdio/netbird/client/internal/peer"
"github.com/netbirdio/netbird/client/internal/statemanager"
cProto "github.com/netbirdio/netbird/client/proto"
nbdns "github.com/netbirdio/netbird/dns"
)
@@ -420,6 +422,7 @@ func (s *DefaultServer) applyConfiguration(update nbdns.Config) error {
if err = s.hostManager.applyDNSConfig(hostUpdate, s.stateManager); err != nil {
log.Error(err)
s.handleErrNoGroupaAll(err)
}
go func() {
@@ -438,10 +441,26 @@ func (s *DefaultServer) applyConfiguration(update nbdns.Config) error {
return nil
}
func (s *DefaultServer) handleErrNoGroupaAll(err error) {
if !errors.Is(ErrRouteAllWithoutNameserverGroup, err) {
return
}
if s.statusRecorder == nil {
return
}
s.statusRecorder.PublishEvent(
cProto.SystemEvent_WARNING, cProto.SystemEvent_DNS,
"The host dns manager does not support match domains",
"The host dns manager does not support match domains without a catch-all nameserver group.",
map[string]string{"manager": s.hostManager.string()},
)
}
func (s *DefaultServer) buildLocalHandlerUpdate(
customZones []nbdns.CustomZone,
) ([]handlerWrapper, map[string][]nbdns.SimpleRecord, error) {
var muxUpdates []handlerWrapper
localRecords := make(map[string][]nbdns.SimpleRecord)
@@ -672,6 +691,7 @@ func (s *DefaultServer) upstreamCallbacks(
}
if err := s.hostManager.applyDNSConfig(s.currentConfig, s.stateManager); err != nil {
s.handleErrNoGroupaAll(err)
l.Errorf("Failed to apply nameserver deactivation on the host: %v", err)
}
@@ -710,6 +730,7 @@ func (s *DefaultServer) upstreamCallbacks(
if s.hostManager != nil {
if err := s.hostManager.applyDNSConfig(s.currentConfig, s.stateManager); err != nil {
s.handleErrNoGroupaAll(err)
l.WithError(err).Error("reactivate temporary disabled nameserver group, DNS update apply")
}
}

View File

@@ -356,7 +356,7 @@ func TestUpdateDNSServer(t *testing.T) {
t.Log(err)
}
}()
dnsServer, err := NewDefaultServer(context.Background(), wgIface, "", &peer.Status{}, nil, false)
dnsServer, err := NewDefaultServer(context.Background(), wgIface, "", peer.NewRecorder("mgm"), nil, false)
if err != nil {
t.Fatal(err)
}
@@ -413,7 +413,7 @@ func TestDNSFakeResolverHandleUpdates(t *testing.T) {
defer t.Setenv("NB_WG_KERNEL_DISABLED", ov)
t.Setenv("NB_WG_KERNEL_DISABLED", "true")
newNet, err := stdnet.NewNet(nil)
newNet, err := stdnet.NewNet([]string{"utun2301"})
if err != nil {
t.Errorf("create stdnet: %v", err)
return
@@ -465,7 +465,7 @@ func TestDNSFakeResolverHandleUpdates(t *testing.T) {
return
}
dnsServer, err := NewDefaultServer(context.Background(), wgIface, "", &peer.Status{}, nil, false)
dnsServer, err := NewDefaultServer(context.Background(), wgIface, "", peer.NewRecorder("mgm"), nil, false)
if err != nil {
t.Errorf("create DNS server: %v", err)
return
@@ -566,7 +566,7 @@ func TestDNSServerStartStop(t *testing.T) {
for _, testCase := range testCases {
t.Run(testCase.name, func(t *testing.T) {
dnsServer, err := NewDefaultServer(context.Background(), &mocWGIface{}, testCase.addrPort, &peer.Status{}, nil, false)
dnsServer, err := NewDefaultServer(context.Background(), &mocWGIface{}, testCase.addrPort, peer.NewRecorder("mgm"), nil, false)
if err != nil {
t.Fatalf("%v", err)
}
@@ -639,7 +639,7 @@ func TestDNSServerUpstreamDeactivateCallback(t *testing.T) {
{false, "domain2", false},
},
},
statusRecorder: &peer.Status{},
statusRecorder: peer.NewRecorder("mgm"),
}
var domainsUpdate string
@@ -700,7 +700,7 @@ func TestDNSPermanent_updateHostDNS_emptyUpstream(t *testing.T) {
var dnsList []string
dnsConfig := nbdns.Config{}
dnsServer := NewDefaultServerPermanentUpstream(context.Background(), wgIFace, dnsList, dnsConfig, nil, &peer.Status{}, false)
dnsServer := NewDefaultServerPermanentUpstream(context.Background(), wgIFace, dnsList, dnsConfig, nil, peer.NewRecorder("mgm"), false)
err = dnsServer.Initialize()
if err != nil {
t.Errorf("failed to initialize DNS server: %v", err)
@@ -724,7 +724,7 @@ func TestDNSPermanent_updateUpstream(t *testing.T) {
}
defer wgIFace.Close()
dnsConfig := nbdns.Config{}
dnsServer := NewDefaultServerPermanentUpstream(context.Background(), wgIFace, []string{"8.8.8.8"}, dnsConfig, nil, &peer.Status{}, false)
dnsServer := NewDefaultServerPermanentUpstream(context.Background(), wgIFace, []string{"8.8.8.8"}, dnsConfig, nil, peer.NewRecorder("mgm"), false)
err = dnsServer.Initialize()
if err != nil {
t.Errorf("failed to initialize DNS server: %v", err)
@@ -816,7 +816,7 @@ func TestDNSPermanent_matchOnly(t *testing.T) {
}
defer wgIFace.Close()
dnsConfig := nbdns.Config{}
dnsServer := NewDefaultServerPermanentUpstream(context.Background(), wgIFace, []string{"8.8.8.8"}, dnsConfig, nil, &peer.Status{}, false)
dnsServer := NewDefaultServerPermanentUpstream(context.Background(), wgIFace, []string{"8.8.8.8"}, dnsConfig, nil, peer.NewRecorder("mgm"), false)
err = dnsServer.Initialize()
if err != nil {
t.Errorf("failed to initialize DNS server: %v", err)
@@ -887,7 +887,7 @@ func createWgInterfaceWithBind(t *testing.T) (*iface.WGIface, error) {
defer t.Setenv("NB_WG_KERNEL_DISABLED", ov)
t.Setenv("NB_WG_KERNEL_DISABLED", "true")
newNet, err := stdnet.NewNet(nil)
newNet, err := stdnet.NewNet([]string{"utun2301"})
if err != nil {
t.Fatalf("create stdnet: %v", err)
return nil, err

View File

@@ -154,6 +154,10 @@ func (s *systemdDbusConfigurator) applyDNSConfig(config HostDNSConfig, stateMana
return nil
}
func (s *systemdDbusConfigurator) string() string {
return "dbus"
}
func (s *systemdDbusConfigurator) setDomainsForInterface(domainsInput []systemdDbusLinkDomainsInput) error {
err := s.callLinkMethod(systemdDbusSetDomainsMethodSuffix, domainsInput)
if err != nil {

View File

@@ -183,6 +183,19 @@ func (u *upstreamResolverBase) checkUpstreamFails(err error) {
}
u.disable(err)
if u.statusRecorder == nil {
return
}
u.statusRecorder.PublishEvent(
proto.SystemEvent_WARNING,
proto.SystemEvent_DNS,
"All upstream servers failed (fail count exceeded)",
"Unable to reach one or more DNS servers. This might affect your ability to connect to some services.",
map[string]string{"upstreams": strings.Join(u.upstreamServers, ", ")},
// TODO add domain meta
)
}
// probeAvailability tests all upstream servers simultaneously and
@@ -232,10 +245,14 @@ func (u *upstreamResolverBase) probeAvailability() {
if !success {
u.disable(errors.ErrorOrNil())
if u.statusRecorder == nil {
return
}
u.statusRecorder.PublishEvent(
proto.SystemEvent_WARNING,
proto.SystemEvent_DNS,
"All upstream servers failed",
"All upstream servers failed (probe failed)",
"Unable to reach one or more DNS servers. This might affect your ability to connect to some services.",
map[string]string{"upstreams": strings.Join(u.upstreamServers, ", ")},
)

View File

@@ -43,6 +43,7 @@ import (
"github.com/netbirdio/netbird/client/internal/routemanager"
"github.com/netbirdio/netbird/client/internal/routemanager/systemops"
"github.com/netbirdio/netbird/client/internal/statemanager"
cProto "github.com/netbirdio/netbird/client/proto"
"github.com/netbirdio/netbird/management/domain"
semaphoregroup "github.com/netbirdio/netbird/util/semaphore-group"
@@ -673,6 +674,8 @@ func (e *Engine) handleSync(update *mgmProto.SyncResponse) error {
return err
}
e.statusRecorder.PublishEvent(cProto.SystemEvent_INFO, cProto.SystemEvent_SYSTEM, "Network map updated", "", nil)
return nil
}

View File

@@ -306,11 +306,13 @@ func (c *clientNetwork) recalculateRouteAndUpdatePeerAndSystem(rsn reason) error
return nil
}
var isNew bool
if c.currentChosen == nil {
// If they were not previously assigned to another peer, add routes to the system first
if err := c.handler.AddRoute(c.ctx); err != nil {
return fmt.Errorf("add route: %w", err)
}
isNew = true
} else {
// Otherwise, remove the allowed IPs from the previous peer first
if err := c.removeRouteFromWireGuardPeer(); err != nil {
@@ -324,6 +326,10 @@ func (c *clientNetwork) recalculateRouteAndUpdatePeerAndSystem(rsn reason) error
return fmt.Errorf("add allowed IPs for peer %s: %w", c.currentChosen.Peer, err)
}
if isNew {
c.connectEvent()
}
err := c.statusRecorder.AddPeerStateRoute(c.currentChosen.Peer, c.handler.String())
if err != nil {
return fmt.Errorf("add peer state route: %w", err)
@@ -331,6 +337,35 @@ func (c *clientNetwork) recalculateRouteAndUpdatePeerAndSystem(rsn reason) error
return nil
}
func (c *clientNetwork) connectEvent() {
var defaultRoute bool
for _, r := range c.routes {
if r.Network.Bits() == 0 {
defaultRoute = true
break
}
}
if !defaultRoute {
return
}
meta := map[string]string{
"network": c.handler.String(),
}
if c.currentChosen != nil {
meta["id"] = string(c.currentChosen.NetID)
meta["peer"] = c.currentChosen.Peer
}
c.statusRecorder.PublishEvent(
proto.SystemEvent_INFO,
proto.SystemEvent_NETWORK,
"Default route added",
"Exit node connected.",
meta,
)
}
func (c *clientNetwork) disconnectEvent(rsn reason) {
var defaultRoute bool
for _, r := range c.routes {
@@ -349,29 +384,27 @@ func (c *clientNetwork) disconnectEvent(rsn reason) {
var userMessage string
meta := make(map[string]string)
if c.currentChosen != nil {
meta["id"] = string(c.currentChosen.NetID)
meta["peer"] = c.currentChosen.Peer
}
meta["network"] = c.handler.String()
switch rsn {
case reasonShutdown:
severity = proto.SystemEvent_INFO
message = "Default route removed"
userMessage = "Exit node disconnected."
meta["network"] = c.handler.String()
case reasonRouteUpdate:
severity = proto.SystemEvent_INFO
message = "Default route updated due to configuration change"
meta["network"] = c.handler.String()
case reasonPeerUpdate:
severity = proto.SystemEvent_WARNING
message = "Default route disconnected due to peer unreachability"
userMessage = "Exit node connection lost. Your internet access might be affected."
if c.currentChosen != nil {
meta["peer"] = c.currentChosen.Peer
meta["network"] = c.handler.String()
}
default:
severity = proto.SystemEvent_ERROR
message = "Default route disconnected for unknown reason"
message = "Default route disconnected for unknown reasons"
userMessage = "Exit node disconnected for unknown reasons."
meta["network"] = c.handler.String()
}
c.statusRecorder.PublishEvent(

View File

@@ -21,7 +21,6 @@ func InterfaceFilter(disallowList []string) func(string) bool {
for _, s := range disallowList {
if strings.HasPrefix(iFace, s) && runtime.GOOS != "ios" {
log.Tracef("ignoring interface %s - it is not allowed", iFace)
return false
}
}

View File

@@ -5,11 +5,16 @@ package stdnet
import (
"fmt"
"slices"
"sync"
"time"
"github.com/pion/transport/v3"
"github.com/pion/transport/v3/stdnet"
)
const updateInterval = 30 * time.Second
// Net is an implementation of the net.Net interface
// based on functions of the standard net package.
type Net struct {
@@ -18,6 +23,10 @@ type Net struct {
iFaceDiscover iFaceDiscover
// interfaceFilter should return true if the given interfaceName is allowed
interfaceFilter func(interfaceName string) bool
lastUpdate time.Time
// mu is shared between interfaces and lastUpdate
mu sync.Mutex
}
// NewNetWithDiscover creates a new StdNet instance.
@@ -43,18 +52,40 @@ func NewNet(disallowList []string) (*Net, error) {
// The interfaces are discovered by an external iFaceDiscover function or by a default discoverer if the external one
// wasn't specified.
func (n *Net) UpdateInterfaces() (err error) {
n.mu.Lock()
defer n.mu.Unlock()
return n.updateInterfaces()
}
func (n *Net) updateInterfaces() (err error) {
allIfaces, err := n.iFaceDiscover.iFaces()
if err != nil {
return err
}
n.interfaces = n.filterInterfaces(allIfaces)
n.lastUpdate = time.Now()
return nil
}
// Interfaces returns a slice of interfaces which are available on the
// system
func (n *Net) Interfaces() ([]*transport.Interface, error) {
return n.interfaces, nil
n.mu.Lock()
defer n.mu.Unlock()
if time.Since(n.lastUpdate) < updateInterval {
return slices.Clone(n.interfaces), nil
}
if err := n.updateInterfaces(); err != nil {
return nil, fmt.Errorf("update interfaces: %w", err)
}
return slices.Clone(n.interfaces), nil
}
// InterfaceByIndex returns the interface specified by index.
@@ -63,6 +94,8 @@ func (n *Net) Interfaces() ([]*transport.Interface, error) {
// sharing the logical data link; for more precision use
// InterfaceByName.
func (n *Net) InterfaceByIndex(index int) (*transport.Interface, error) {
n.mu.Lock()
defer n.mu.Unlock()
for _, ifc := range n.interfaces {
if ifc.Index == index {
return ifc, nil
@@ -74,6 +107,8 @@ func (n *Net) InterfaceByIndex(index int) (*transport.Interface, error) {
// InterfaceByName returns the interface specified by name.
func (n *Net) InterfaceByName(name string) (*transport.Interface, error) {
n.mu.Lock()
defer n.mu.Unlock()
for _, ifc := range n.interfaces {
if ifc.Name == name {
return ifc, nil
@@ -87,7 +122,7 @@ func (n *Net) filterInterfaces(interfaces []*transport.Interface) []*transport.I
if n.interfaceFilter == nil {
return interfaces
}
result := []*transport.Interface{}
var result []*transport.Interface
for _, iface := range interfaces {
if n.interfaceFilter(iface.Name) {
result = append(result, iface)