mirror of
https://github.com/netbirdio/netbird.git
synced 2026-04-01 15:14:03 -04:00
Compare commits
12 Commits
debug-and-
...
dns-exit-n
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
2db23a42dc | ||
|
|
c2822eebb0 | ||
|
|
5b246e0a08 | ||
|
|
7aef0f67df | ||
|
|
dba7ef667d | ||
|
|
69d87343d2 | ||
|
|
5113c70943 | ||
|
|
ad8fcda67b | ||
|
|
d33f88df82 | ||
|
|
786ca6fc79 | ||
|
|
dfebdf1444 | ||
|
|
a8dcff69c2 |
@@ -4,6 +4,7 @@ package android
|
||||
|
||||
import (
|
||||
"context"
|
||||
"os"
|
||||
"slices"
|
||||
"sync"
|
||||
|
||||
@@ -83,7 +84,8 @@ func NewClient(cfgFile string, androidSDKVersion int, deviceName string, uiVersi
|
||||
}
|
||||
|
||||
// Run start the internal client. It is a blocker function
|
||||
func (c *Client) Run(urlOpener URLOpener, dns *DNSList, dnsReadyListener DnsReadyListener) error {
|
||||
func (c *Client) Run(urlOpener URLOpener, dns *DNSList, dnsReadyListener DnsReadyListener, envList *EnvList) error {
|
||||
exportEnvList(envList)
|
||||
cfg, err := profilemanager.UpdateOrCreateConfig(profilemanager.ConfigInput{
|
||||
ConfigPath: c.cfgFile,
|
||||
})
|
||||
@@ -118,7 +120,8 @@ func (c *Client) Run(urlOpener URLOpener, dns *DNSList, dnsReadyListener DnsRead
|
||||
|
||||
// RunWithoutLogin we apply this type of run function when the backed has been started without UI (i.e. after reboot).
|
||||
// In this case make no sense handle registration steps.
|
||||
func (c *Client) RunWithoutLogin(dns *DNSList, dnsReadyListener DnsReadyListener) error {
|
||||
func (c *Client) RunWithoutLogin(dns *DNSList, dnsReadyListener DnsReadyListener, envList *EnvList) error {
|
||||
exportEnvList(envList)
|
||||
cfg, err := profilemanager.UpdateOrCreateConfig(profilemanager.ConfigInput{
|
||||
ConfigPath: c.cfgFile,
|
||||
})
|
||||
@@ -249,3 +252,14 @@ func (c *Client) SetConnectionListener(listener ConnectionListener) {
|
||||
func (c *Client) RemoveConnectionListener() {
|
||||
c.recorder.RemoveConnectionListener()
|
||||
}
|
||||
|
||||
func exportEnvList(list *EnvList) {
|
||||
if list == nil {
|
||||
return
|
||||
}
|
||||
for k, v := range list.AllItems() {
|
||||
if err := os.Setenv(k, v); err != nil {
|
||||
log.Errorf("could not set env variable %s: %v", k, err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
32
client/android/env_list.go
Normal file
32
client/android/env_list.go
Normal file
@@ -0,0 +1,32 @@
|
||||
package android
|
||||
|
||||
import "github.com/netbirdio/netbird/client/internal/peer"
|
||||
|
||||
var (
|
||||
// EnvKeyNBForceRelay Exported for Android java client
|
||||
EnvKeyNBForceRelay = peer.EnvKeyNBForceRelay
|
||||
)
|
||||
|
||||
// EnvList wraps a Go map for export to Java
|
||||
type EnvList struct {
|
||||
data map[string]string
|
||||
}
|
||||
|
||||
// NewEnvList creates a new EnvList
|
||||
func NewEnvList() *EnvList {
|
||||
return &EnvList{data: make(map[string]string)}
|
||||
}
|
||||
|
||||
// Put adds a key-value pair
|
||||
func (el *EnvList) Put(key, value string) {
|
||||
el.data[key] = value
|
||||
}
|
||||
|
||||
// Get retrieves a value by key
|
||||
func (el *EnvList) Get(key string) string {
|
||||
return el.data[key]
|
||||
}
|
||||
|
||||
func (el *EnvList) AllItems() map[string]string {
|
||||
return el.data
|
||||
}
|
||||
@@ -388,12 +388,12 @@ func generateDebugBundle(config *profilemanager.Config, recorder *peer.Status, c
|
||||
}
|
||||
|
||||
func init() {
|
||||
debugBundleCmd.Flags().Uint32VarP(&logFileCount, "log-file-count", "C", 1, "Number of rotated log files to include in debug bundle")
|
||||
debugBundleCmd.Flags().Uint32VarP(&logFileCount, "log-file-count", "C", 10, "Number of rotated log files to include in debug bundle")
|
||||
debugBundleCmd.Flags().BoolVarP(&systemInfoFlag, "system-info", "S", true, "Adds system information to the debug bundle")
|
||||
debugBundleCmd.Flags().BoolVarP(&uploadBundleFlag, "upload-bundle", "U", false, "Uploads the debug bundle to a server")
|
||||
debugBundleCmd.Flags().StringVar(&uploadBundleURLFlag, "upload-bundle-url", types.DefaultBundleURL, "Service URL to get an URL to upload the debug bundle")
|
||||
|
||||
forCmd.Flags().Uint32VarP(&logFileCount, "log-file-count", "C", 1, "Number of rotated log files to include in debug bundle")
|
||||
forCmd.Flags().Uint32VarP(&logFileCount, "log-file-count", "C", 10, "Number of rotated log files to include in debug bundle")
|
||||
forCmd.Flags().BoolVarP(&systemInfoFlag, "system-info", "S", true, "Adds system information to the debug bundle")
|
||||
forCmd.Flags().BoolVarP(&uploadBundleFlag, "upload-bundle", "U", false, "Uploads the debug bundle to a server")
|
||||
forCmd.Flags().StringVar(&uploadBundleURLFlag, "upload-bundle-url", types.DefaultBundleURL, "Service URL to get an URL to upload the debug bundle")
|
||||
|
||||
@@ -227,7 +227,7 @@ func doForegroundLogin(ctx context.Context, cmd *cobra.Command, setupKey string,
|
||||
}
|
||||
|
||||
// update host's static platform and system information
|
||||
system.UpdateStaticInfo()
|
||||
system.UpdateStaticInfoAsync()
|
||||
|
||||
configFilePath, err := activeProf.FilePath()
|
||||
if err != nil {
|
||||
|
||||
@@ -27,7 +27,7 @@ func (p *program) Start(svc service.Service) error {
|
||||
log.Info("starting NetBird service") //nolint
|
||||
|
||||
// Collect static system and platform information
|
||||
system.UpdateStaticInfo()
|
||||
system.UpdateStaticInfoAsync()
|
||||
|
||||
// in any case, even if configuration does not exists we run daemon to serve CLI gRPC API.
|
||||
p.serv = grpc.NewServer()
|
||||
|
||||
@@ -9,29 +9,26 @@ import (
|
||||
"github.com/golang/mock/gomock"
|
||||
"github.com/stretchr/testify/require"
|
||||
"go.opentelemetry.io/otel"
|
||||
"google.golang.org/grpc"
|
||||
|
||||
"github.com/netbirdio/management-integrations/integrations"
|
||||
clientProto "github.com/netbirdio/netbird/client/proto"
|
||||
client "github.com/netbirdio/netbird/client/server"
|
||||
"github.com/netbirdio/netbird/management/internals/server/config"
|
||||
mgmt "github.com/netbirdio/netbird/management/server"
|
||||
"github.com/netbirdio/netbird/management/server/activity"
|
||||
"github.com/netbirdio/netbird/management/server/groups"
|
||||
"github.com/netbirdio/netbird/management/server/integrations/port_forwarding"
|
||||
"github.com/netbirdio/netbird/management/server/peers"
|
||||
"github.com/netbirdio/netbird/management/server/permissions"
|
||||
"github.com/netbirdio/netbird/management/server/settings"
|
||||
"github.com/netbirdio/netbird/management/server/store"
|
||||
"github.com/netbirdio/netbird/management/server/telemetry"
|
||||
"github.com/netbirdio/netbird/management/server/types"
|
||||
|
||||
"github.com/netbirdio/netbird/util"
|
||||
|
||||
"google.golang.org/grpc"
|
||||
|
||||
"github.com/netbirdio/management-integrations/integrations"
|
||||
|
||||
clientProto "github.com/netbirdio/netbird/client/proto"
|
||||
client "github.com/netbirdio/netbird/client/server"
|
||||
mgmt "github.com/netbirdio/netbird/management/server"
|
||||
mgmtProto "github.com/netbirdio/netbird/shared/management/proto"
|
||||
sigProto "github.com/netbirdio/netbird/shared/signal/proto"
|
||||
sig "github.com/netbirdio/netbird/signal/server"
|
||||
"github.com/netbirdio/netbird/util"
|
||||
)
|
||||
|
||||
func startTestingServices(t *testing.T) string {
|
||||
@@ -90,15 +87,20 @@ func startManagement(t *testing.T, config *config.Config, testFile string) (*grp
|
||||
if err != nil {
|
||||
return nil, nil
|
||||
}
|
||||
iv, _ := integrations.NewIntegratedValidator(context.Background(), eventStore)
|
||||
|
||||
metrics, err := telemetry.NewDefaultAppMetrics(context.Background())
|
||||
require.NoError(t, err)
|
||||
ctrl := gomock.NewController(t)
|
||||
t.Cleanup(ctrl.Finish)
|
||||
|
||||
settingsMockManager := settings.NewMockManager(ctrl)
|
||||
permissionsManagerMock := permissions.NewMockManager(ctrl)
|
||||
peersmanager := peers.NewManager(store, permissionsManagerMock)
|
||||
settingsManagerMock := settings.NewMockManager(ctrl)
|
||||
|
||||
iv, _ := integrations.NewIntegratedValidator(context.Background(), peersmanager, settingsManagerMock, eventStore)
|
||||
|
||||
metrics, err := telemetry.NewDefaultAppMetrics(context.Background())
|
||||
require.NoError(t, err)
|
||||
|
||||
settingsMockManager := settings.NewMockManager(ctrl)
|
||||
groupsManager := groups.NewManagerMock()
|
||||
|
||||
settingsMockManager.EXPECT().
|
||||
|
||||
@@ -394,6 +394,13 @@ func toLastHandshake(stringVar string) (time.Time, error) {
|
||||
if err != nil {
|
||||
return time.Time{}, fmt.Errorf("parse handshake sec: %w", err)
|
||||
}
|
||||
|
||||
// If sec is 0 (Unix epoch), return zero time instead
|
||||
// This indicates no handshake has occurred
|
||||
if sec == 0 {
|
||||
return time.Time{}, nil
|
||||
}
|
||||
|
||||
return time.Unix(sec, 0), nil
|
||||
}
|
||||
|
||||
|
||||
@@ -315,6 +315,10 @@ func (g *BundleGenerator) createArchive() error {
|
||||
return fmt.Errorf("add sync response: %w", err)
|
||||
}
|
||||
|
||||
if err := g.addDNSConfig(); err != nil {
|
||||
log.Errorf("failed to add DNS config to debug bundle: %v", err)
|
||||
}
|
||||
|
||||
if err := g.addStateFile(); err != nil {
|
||||
log.Errorf("failed to add state file to debug bundle: %v", err)
|
||||
}
|
||||
@@ -341,6 +345,50 @@ func (g *BundleGenerator) createArchive() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// addDNSConfig writes a dns_config.json snapshot with routed domains and NS group status
|
||||
func (g *BundleGenerator) addDNSConfig() error {
|
||||
type nsGroup struct {
|
||||
ID string `json:"id"`
|
||||
Servers []string `json:"servers"`
|
||||
Domains []string `json:"domains"`
|
||||
Enabled bool `json:"enabled"`
|
||||
Error string `json:"error,omitempty"`
|
||||
}
|
||||
type dnsConfig struct {
|
||||
Groups []nsGroup `json:"name_server_groups"`
|
||||
}
|
||||
|
||||
if g.statusRecorder == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
states := g.statusRecorder.GetDNSStates()
|
||||
cfg := dnsConfig{Groups: make([]nsGroup, 0, len(states))}
|
||||
for _, st := range states {
|
||||
var servers []string
|
||||
for _, ap := range st.Servers {
|
||||
servers = append(servers, ap.String())
|
||||
}
|
||||
var errStr string
|
||||
if st.Error != nil {
|
||||
errStr = st.Error.Error()
|
||||
}
|
||||
cfg.Groups = append(cfg.Groups, nsGroup{
|
||||
ID: st.ID,
|
||||
Servers: servers,
|
||||
Domains: st.Domains,
|
||||
Enabled: st.Enabled,
|
||||
Error: errStr,
|
||||
})
|
||||
}
|
||||
|
||||
bs, err := json.MarshalIndent(cfg, "", " ")
|
||||
if err != nil {
|
||||
return fmt.Errorf("marshal dns config: %w", err)
|
||||
}
|
||||
return g.addFileToZip(bytes.NewReader(bs), "dns_config.json")
|
||||
}
|
||||
|
||||
func (g *BundleGenerator) addSystemInfo() {
|
||||
if err := g.addRoutes(); err != nil {
|
||||
log.Errorf("failed to add routes to debug bundle: %v", err)
|
||||
|
||||
@@ -46,6 +46,18 @@ type DNSForwarder struct {
|
||||
fwdEntries []*ForwarderEntry
|
||||
firewall firewaller
|
||||
resolver resolver
|
||||
|
||||
// failure rate tracking for routed domains
|
||||
failureMu sync.Mutex
|
||||
failureCounts map[string]int
|
||||
failureWindow time.Duration
|
||||
lastLogPerHost map[string]time.Time
|
||||
|
||||
// per-domain rolling stats and windows
|
||||
statsMu sync.Mutex
|
||||
stats map[string]*domainStats
|
||||
winSize time.Duration
|
||||
slowT time.Duration
|
||||
}
|
||||
|
||||
func NewDNSForwarder(listenAddress string, ttl uint32, firewall firewaller, statusRecorder *peer.Status) *DNSForwarder {
|
||||
@@ -56,9 +68,25 @@ func NewDNSForwarder(listenAddress string, ttl uint32, firewall firewaller, stat
|
||||
firewall: firewall,
|
||||
statusRecorder: statusRecorder,
|
||||
resolver: net.DefaultResolver,
|
||||
failureCounts: make(map[string]int),
|
||||
failureWindow: 10 * time.Second,
|
||||
lastLogPerHost: make(map[string]time.Time),
|
||||
stats: make(map[string]*domainStats),
|
||||
winSize: 10 * time.Second,
|
||||
slowT: 300 * time.Millisecond,
|
||||
}
|
||||
}
|
||||
|
||||
type domainStats struct {
|
||||
total int
|
||||
success int
|
||||
timeouts int
|
||||
notfound int
|
||||
failures int // other failures (incl. SERVFAIL-like)
|
||||
slow int
|
||||
lastLog time.Time
|
||||
}
|
||||
|
||||
func (f *DNSForwarder) Listen(entries []*ForwarderEntry) error {
|
||||
log.Infof("starting DNS forwarder on address=%s", f.listenAddress)
|
||||
|
||||
@@ -163,12 +191,19 @@ func (f *DNSForwarder) handleDNSQuery(w dns.ResponseWriter, query *dns.Msg) *dns
|
||||
|
||||
ctx, cancel := context.WithTimeout(context.Background(), upstreamTimeout)
|
||||
defer cancel()
|
||||
start := time.Now()
|
||||
ips, err := f.resolver.LookupNetIP(ctx, network, domain)
|
||||
elapsed := time.Since(start)
|
||||
if err != nil {
|
||||
f.handleDNSError(ctx, w, question, resp, domain, err)
|
||||
// record error stats for routed domains
|
||||
f.recordErrorStats(strings.TrimSuffix(domain, "."), err)
|
||||
return nil
|
||||
}
|
||||
|
||||
// record success timing
|
||||
f.recordSuccessStats(strings.TrimSuffix(domain, "."), elapsed)
|
||||
|
||||
f.updateInternalState(ips, mostSpecificResId, matchingEntries)
|
||||
f.addIPsToResponse(resp, domain, ips)
|
||||
|
||||
@@ -306,6 +341,91 @@ func (f *DNSForwarder) handleDNSError(ctx context.Context, w dns.ResponseWriter,
|
||||
if err := w.WriteMsg(resp); err != nil {
|
||||
log.Errorf("failed to write failure DNS response: %v", err)
|
||||
}
|
||||
|
||||
// Track failure rate for routed domains only
|
||||
if resID, _ := f.getMatchingEntries(strings.TrimSuffix(domain, ".")); resID != "" {
|
||||
f.recordDomainFailure(strings.TrimSuffix(domain, "."))
|
||||
}
|
||||
}
|
||||
|
||||
// recordErrorStats updates per-domain counters and emits rate-limited logs
|
||||
func (f *DNSForwarder) recordErrorStats(domain string, err error) {
|
||||
domain = strings.ToLower(domain)
|
||||
f.statsMu.Lock()
|
||||
s := f.ensureStats(domain)
|
||||
s.total++
|
||||
|
||||
var dnsErr *net.DNSError
|
||||
if errors.As(err, &dnsErr) {
|
||||
if dnsErr.IsNotFound {
|
||||
s.notfound++
|
||||
} else if dnsErr.Timeout() {
|
||||
s.timeouts++
|
||||
} else {
|
||||
s.failures++
|
||||
}
|
||||
} else {
|
||||
s.failures++
|
||||
}
|
||||
|
||||
f.maybeLogDomainStats(domain, s)
|
||||
f.statsMu.Unlock()
|
||||
}
|
||||
|
||||
// recordSuccessStats updates per-domain latency stats and slow counters, logs if needed (rate-limited)
|
||||
func (f *DNSForwarder) recordSuccessStats(domain string, elapsed time.Duration) {
|
||||
domain = strings.ToLower(domain)
|
||||
f.statsMu.Lock()
|
||||
s := f.ensureStats(domain)
|
||||
s.total++
|
||||
s.success++
|
||||
if elapsed >= f.slowT {
|
||||
s.slow++
|
||||
}
|
||||
f.maybeLogDomainStats(domain, s)
|
||||
f.statsMu.Unlock()
|
||||
}
|
||||
|
||||
func (f *DNSForwarder) ensureStats(domain string) *domainStats {
|
||||
if ds, ok := f.stats[domain]; ok {
|
||||
return ds
|
||||
}
|
||||
ds := &domainStats{}
|
||||
f.stats[domain] = ds
|
||||
return ds
|
||||
}
|
||||
|
||||
// maybeLogDomainStats logs a compact summary per routed domain at most once per window
|
||||
func (f *DNSForwarder) maybeLogDomainStats(domain string, s *domainStats) {
|
||||
now := time.Now()
|
||||
if !s.lastLog.IsZero() && now.Sub(s.lastLog) < f.winSize {
|
||||
return
|
||||
}
|
||||
|
||||
// check if routed (avoid logging for non-routed domains)
|
||||
if resID, _ := f.getMatchingEntries(domain); resID == "" {
|
||||
return
|
||||
}
|
||||
|
||||
// only log if something noteworthy happened in the window
|
||||
noteworthy := s.timeouts > 0 || s.notfound > 0 || s.failures > 0 || s.slow > 0
|
||||
if !noteworthy {
|
||||
s.lastLog = now
|
||||
return
|
||||
}
|
||||
|
||||
// warn on persistent problems, info otherwise
|
||||
levelWarn := s.timeouts >= 3 || s.failures >= 3
|
||||
if levelWarn {
|
||||
log.Warnf("[d] DNS stats: domain=%s total=%d ok=%d timeout=%d nxdomain=%d fail=%d slow=%d(>=%s)",
|
||||
domain, s.total, s.success, s.timeouts, s.notfound, s.failures, s.slow, f.slowT)
|
||||
} else {
|
||||
log.Infof("[d] DNS stats: domain=%s total=%d ok=%d timeout=%d nxdomain=%d fail=%d slow=%d(>=%s)",
|
||||
domain, s.total, s.success, s.timeouts, s.notfound, s.failures, s.slow, f.slowT)
|
||||
}
|
||||
|
||||
// reset counters for next window
|
||||
*s = domainStats{lastLog: now}
|
||||
}
|
||||
|
||||
// addIPsToResponse adds IP addresses to the DNS response as appropriate A or AAAA records
|
||||
@@ -341,6 +461,27 @@ func (f *DNSForwarder) addIPsToResponse(resp *dns.Msg, domain string, ips []neti
|
||||
}
|
||||
}
|
||||
|
||||
// recordDomainFailure increments failure count for the domain and logs at info/warn with throttling.
|
||||
func (f *DNSForwarder) recordDomainFailure(domain string) {
|
||||
domain = strings.ToLower(domain)
|
||||
|
||||
f.failureMu.Lock()
|
||||
defer f.failureMu.Unlock()
|
||||
|
||||
f.failureCounts[domain]++
|
||||
count := f.failureCounts[domain]
|
||||
|
||||
now := time.Now()
|
||||
last, ok := f.lastLogPerHost[domain]
|
||||
if ok && now.Sub(last) < f.failureWindow {
|
||||
return
|
||||
}
|
||||
f.lastLogPerHost[domain] = now
|
||||
|
||||
log.Warnf("[d] DNS failures observed for routed domain: domain=%s failures=%d/%s", domain, count, f.failureWindow)
|
||||
|
||||
}
|
||||
|
||||
// getMatchingEntries retrieves the resource IDs for a given domain.
|
||||
// It returns the most specific match and all matching resource IDs.
|
||||
func (f *DNSForwarder) getMatchingEntries(domain string) (route.ResID, []*ForwarderEntry) {
|
||||
|
||||
@@ -949,7 +949,6 @@ func (e *Engine) receiveManagementEvents() {
|
||||
e.config.LazyConnectionEnabled,
|
||||
)
|
||||
|
||||
// err = e.mgmClient.Sync(info, e.handleSync)
|
||||
err = e.mgmClient.Sync(e.ctx, info, e.handleSync)
|
||||
if err != nil {
|
||||
// happens if management is unavailable for a long time.
|
||||
@@ -960,7 +959,7 @@ func (e *Engine) receiveManagementEvents() {
|
||||
}
|
||||
log.Debugf("stopped receiving updates from Management Service")
|
||||
}()
|
||||
log.Debugf("connecting to Management Service updates stream")
|
||||
log.Infof("connecting to Management Service updates stream")
|
||||
}
|
||||
|
||||
func (e *Engine) updateSTUNs(stuns []*mgmProto.HostConfig) error {
|
||||
|
||||
@@ -19,17 +19,13 @@ import (
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
"go.opentelemetry.io/otel"
|
||||
wgdevice "golang.zx2c4.com/wireguard/device"
|
||||
"golang.zx2c4.com/wireguard/tun/netstack"
|
||||
"golang.zx2c4.com/wireguard/wgctrl/wgtypes"
|
||||
"google.golang.org/grpc"
|
||||
"google.golang.org/grpc/keepalive"
|
||||
|
||||
wgdevice "golang.zx2c4.com/wireguard/device"
|
||||
"golang.zx2c4.com/wireguard/tun/netstack"
|
||||
|
||||
"github.com/netbirdio/management-integrations/integrations"
|
||||
"github.com/netbirdio/netbird/management/internals/server/config"
|
||||
"github.com/netbirdio/netbird/management/server/groups"
|
||||
|
||||
"github.com/netbirdio/netbird/client/iface"
|
||||
"github.com/netbirdio/netbird/client/iface/bind"
|
||||
"github.com/netbirdio/netbird/client/iface/configurer"
|
||||
@@ -45,9 +41,12 @@ import (
|
||||
"github.com/netbirdio/netbird/client/ssh"
|
||||
"github.com/netbirdio/netbird/client/system"
|
||||
nbdns "github.com/netbirdio/netbird/dns"
|
||||
"github.com/netbirdio/netbird/management/internals/server/config"
|
||||
"github.com/netbirdio/netbird/management/server"
|
||||
"github.com/netbirdio/netbird/management/server/activity"
|
||||
"github.com/netbirdio/netbird/management/server/groups"
|
||||
"github.com/netbirdio/netbird/management/server/integrations/port_forwarding"
|
||||
"github.com/netbirdio/netbird/management/server/peers"
|
||||
"github.com/netbirdio/netbird/management/server/permissions"
|
||||
"github.com/netbirdio/netbird/management/server/settings"
|
||||
"github.com/netbirdio/netbird/management/server/store"
|
||||
@@ -1555,7 +1554,11 @@ func startManagement(t *testing.T, dataDir, testFile string) (*grpc.Server, stri
|
||||
if err != nil {
|
||||
return nil, "", err
|
||||
}
|
||||
ia, _ := integrations.NewIntegratedValidator(context.Background(), eventStore)
|
||||
|
||||
permissionsManager := permissions.NewManager(store)
|
||||
peersManager := peers.NewManager(store, permissionsManager)
|
||||
|
||||
ia, _ := integrations.NewIntegratedValidator(context.Background(), peersManager, nil, eventStore)
|
||||
|
||||
metrics, err := telemetry.NewDefaultAppMetrics(context.Background())
|
||||
require.NoError(t, err)
|
||||
@@ -1572,7 +1575,6 @@ func startManagement(t *testing.T, dataDir, testFile string) (*grpc.Server, stri
|
||||
Return(&types.ExtraSettings{}, nil).
|
||||
AnyTimes()
|
||||
|
||||
permissionsManager := permissions.NewManager(store)
|
||||
groupsManager := groups.NewManagerMock()
|
||||
|
||||
accountManager, err := server.BuildManager(context.Background(), store, peersUpdateManager, nil, "", "netbird.selfhosted", eventStore, nil, false, ia, metrics, port_forwarding.NewControllerMock(), settingsMockManager, permissionsManager, false)
|
||||
|
||||
@@ -6,7 +6,6 @@ import (
|
||||
"math/rand"
|
||||
"net"
|
||||
"net/netip"
|
||||
"os"
|
||||
"runtime"
|
||||
"sync"
|
||||
"time"
|
||||
@@ -174,7 +173,7 @@ func (conn *Conn) Open(engineCtx context.Context) error {
|
||||
conn.handshaker = NewHandshaker(conn.Log, conn.config, conn.signaler, conn.workerICE, conn.workerRelay)
|
||||
|
||||
conn.handshaker.AddOnNewOfferListener(conn.workerRelay.OnNewOffer)
|
||||
if os.Getenv("NB_FORCE_RELAY") != "true" {
|
||||
if !isForceRelayed() {
|
||||
conn.handshaker.AddOnNewOfferListener(conn.workerICE.OnNewOffer)
|
||||
}
|
||||
|
||||
|
||||
14
client/internal/peer/env.go
Normal file
14
client/internal/peer/env.go
Normal file
@@ -0,0 +1,14 @@
|
||||
package peer
|
||||
|
||||
import (
|
||||
"os"
|
||||
"strings"
|
||||
)
|
||||
|
||||
const (
|
||||
EnvKeyNBForceRelay = "NB_FORCE_RELAY"
|
||||
)
|
||||
|
||||
func isForceRelayed() bool {
|
||||
return strings.EqualFold(os.Getenv(EnvKeyNBForceRelay), "true")
|
||||
}
|
||||
@@ -43,13 +43,6 @@ type OfferAnswer struct {
|
||||
SessionID *ICESessionID
|
||||
}
|
||||
|
||||
func (oa *OfferAnswer) SessionIDString() string {
|
||||
if oa.SessionID == nil {
|
||||
return "unknown"
|
||||
}
|
||||
return oa.SessionID.String()
|
||||
}
|
||||
|
||||
type Handshaker struct {
|
||||
mu sync.Mutex
|
||||
log *log.Entry
|
||||
@@ -57,7 +50,7 @@ type Handshaker struct {
|
||||
signaler *Signaler
|
||||
ice *WorkerICE
|
||||
relay *WorkerRelay
|
||||
onNewOfferListeners []func(*OfferAnswer)
|
||||
onNewOfferListeners []*OfferListener
|
||||
|
||||
// remoteOffersCh is a channel used to wait for remote credentials to proceed with the connection
|
||||
remoteOffersCh chan OfferAnswer
|
||||
@@ -78,7 +71,8 @@ func NewHandshaker(log *log.Entry, config ConnConfig, signaler *Signaler, ice *W
|
||||
}
|
||||
|
||||
func (h *Handshaker) AddOnNewOfferListener(offer func(remoteOfferAnswer *OfferAnswer)) {
|
||||
h.onNewOfferListeners = append(h.onNewOfferListeners, offer)
|
||||
l := NewOfferListener(offer)
|
||||
h.onNewOfferListeners = append(h.onNewOfferListeners, l)
|
||||
}
|
||||
|
||||
func (h *Handshaker) Listen(ctx context.Context) {
|
||||
@@ -91,13 +85,13 @@ func (h *Handshaker) Listen(ctx context.Context) {
|
||||
continue
|
||||
}
|
||||
for _, listener := range h.onNewOfferListeners {
|
||||
listener(&remoteOfferAnswer)
|
||||
listener.Notify(&remoteOfferAnswer)
|
||||
}
|
||||
h.log.Infof("received offer, running version %s, remote WireGuard listen port %d, session id: %s", remoteOfferAnswer.Version, remoteOfferAnswer.WgListenPort, remoteOfferAnswer.SessionIDString())
|
||||
case remoteOfferAnswer := <-h.remoteAnswerCh:
|
||||
h.log.Infof("received answer, running version %s, remote WireGuard listen port %d, session id: %s", remoteOfferAnswer.Version, remoteOfferAnswer.WgListenPort, remoteOfferAnswer.SessionIDString())
|
||||
for _, listener := range h.onNewOfferListeners {
|
||||
listener(&remoteOfferAnswer)
|
||||
listener.Notify(&remoteOfferAnswer)
|
||||
}
|
||||
case <-ctx.Done():
|
||||
h.log.Infof("stop listening for remote offers and answers")
|
||||
|
||||
62
client/internal/peer/handshaker_listener.go
Normal file
62
client/internal/peer/handshaker_listener.go
Normal file
@@ -0,0 +1,62 @@
|
||||
package peer
|
||||
|
||||
import (
|
||||
"sync"
|
||||
)
|
||||
|
||||
type callbackFunc func(remoteOfferAnswer *OfferAnswer)
|
||||
|
||||
func (oa *OfferAnswer) SessionIDString() string {
|
||||
if oa.SessionID == nil {
|
||||
return "unknown"
|
||||
}
|
||||
return oa.SessionID.String()
|
||||
}
|
||||
|
||||
type OfferListener struct {
|
||||
fn callbackFunc
|
||||
running bool
|
||||
latest *OfferAnswer
|
||||
mu sync.Mutex
|
||||
}
|
||||
|
||||
func NewOfferListener(fn callbackFunc) *OfferListener {
|
||||
return &OfferListener{
|
||||
fn: fn,
|
||||
}
|
||||
}
|
||||
|
||||
func (o *OfferListener) Notify(remoteOfferAnswer *OfferAnswer) {
|
||||
o.mu.Lock()
|
||||
defer o.mu.Unlock()
|
||||
|
||||
// Store the latest offer
|
||||
o.latest = remoteOfferAnswer
|
||||
|
||||
// If already running, the running goroutine will pick up this latest value
|
||||
if o.running {
|
||||
return
|
||||
}
|
||||
|
||||
// Start processing
|
||||
o.running = true
|
||||
|
||||
// Process in a goroutine to avoid blocking the caller
|
||||
go func(remoteOfferAnswer *OfferAnswer) {
|
||||
for {
|
||||
o.fn(remoteOfferAnswer)
|
||||
|
||||
o.mu.Lock()
|
||||
if o.latest == nil {
|
||||
// No more work to do
|
||||
o.running = false
|
||||
o.mu.Unlock()
|
||||
return
|
||||
}
|
||||
remoteOfferAnswer = o.latest
|
||||
// Clear the latest to mark it as being processed
|
||||
o.latest = nil
|
||||
o.mu.Unlock()
|
||||
}
|
||||
}(remoteOfferAnswer)
|
||||
}
|
||||
39
client/internal/peer/handshaker_listener_test.go
Normal file
39
client/internal/peer/handshaker_listener_test.go
Normal file
@@ -0,0 +1,39 @@
|
||||
package peer
|
||||
|
||||
import (
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
func Test_newOfferListener(t *testing.T) {
|
||||
dummyOfferAnswer := &OfferAnswer{}
|
||||
runChan := make(chan struct{}, 10)
|
||||
|
||||
longRunningFn := func(remoteOfferAnswer *OfferAnswer) {
|
||||
time.Sleep(1 * time.Second)
|
||||
runChan <- struct{}{}
|
||||
}
|
||||
|
||||
hl := NewOfferListener(longRunningFn)
|
||||
|
||||
hl.Notify(dummyOfferAnswer)
|
||||
hl.Notify(dummyOfferAnswer)
|
||||
hl.Notify(dummyOfferAnswer)
|
||||
|
||||
// Wait for exactly 2 callbacks
|
||||
for i := 0; i < 2; i++ {
|
||||
select {
|
||||
case <-runChan:
|
||||
case <-time.After(3 * time.Second):
|
||||
t.Fatal("Timeout waiting for callback")
|
||||
}
|
||||
}
|
||||
|
||||
// Verify no additional callbacks happen
|
||||
select {
|
||||
case <-runChan:
|
||||
t.Fatal("Unexpected additional callback")
|
||||
case <-time.After(100 * time.Millisecond):
|
||||
t.Log("Correctly received exactly 2 callbacks")
|
||||
}
|
||||
}
|
||||
@@ -21,9 +21,9 @@ import (
|
||||
"github.com/netbirdio/netbird/client/internal/ingressgw"
|
||||
"github.com/netbirdio/netbird/client/internal/relay"
|
||||
"github.com/netbirdio/netbird/client/proto"
|
||||
"github.com/netbirdio/netbird/route"
|
||||
"github.com/netbirdio/netbird/shared/management/domain"
|
||||
relayClient "github.com/netbirdio/netbird/shared/relay/client"
|
||||
"github.com/netbirdio/netbird/route"
|
||||
)
|
||||
|
||||
const eventQueueSize = 10
|
||||
@@ -201,6 +201,8 @@ type Status struct {
|
||||
resolvedDomainsStates map[domain.Domain]ResolvedDomainInfo
|
||||
lazyConnectionEnabled bool
|
||||
|
||||
lastDisconnectLog map[string]time.Time
|
||||
|
||||
// To reduce the number of notification invocation this bool will be true when need to call the notification
|
||||
// Some Peer actions mostly used by in a batch when the network map has been synchronized. In these type of events
|
||||
// set to true this variable and at the end of the processing we will reset it by the FinishPeerListModifications()
|
||||
@@ -229,6 +231,7 @@ func NewRecorder(mgmAddress string) *Status {
|
||||
notifier: newNotifier(),
|
||||
mgmAddress: mgmAddress,
|
||||
resolvedDomainsStates: map[domain.Domain]ResolvedDomainInfo{},
|
||||
lastDisconnectLog: make(map[string]time.Time),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -487,6 +490,9 @@ func (d *Status) UpdatePeerRelayedStateToDisconnected(receivedState State) error
|
||||
|
||||
d.peers[receivedState.PubKey] = peerState
|
||||
|
||||
// info log about disconnect with impacted routes (throttled)
|
||||
d.logPeerDisconnectIfNeeded(receivedState.PubKey, peerState)
|
||||
|
||||
if hasConnStatusChanged(oldState, receivedState.ConnStatus) {
|
||||
d.notifyPeerListChanged()
|
||||
}
|
||||
@@ -519,6 +525,9 @@ func (d *Status) UpdatePeerICEStateToDisconnected(receivedState State) error {
|
||||
|
||||
d.peers[receivedState.PubKey] = peerState
|
||||
|
||||
// info log about disconnect with impacted routes (throttled)
|
||||
d.logPeerDisconnectIfNeeded(receivedState.PubKey, peerState)
|
||||
|
||||
if hasConnStatusChanged(oldState, receivedState.ConnStatus) {
|
||||
d.notifyPeerListChanged()
|
||||
}
|
||||
@@ -529,6 +538,49 @@ func (d *Status) UpdatePeerICEStateToDisconnected(receivedState State) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// logPeerDisconnectIfNeeded logs an info message when a routing peer transitions to disconnected
|
||||
// with the number of impacted routes. Throttled to once per peer per 30 seconds.
|
||||
func (d *Status) logPeerDisconnectIfNeeded(pubKey string, state State) {
|
||||
if state.ConnStatus != StatusIdle {
|
||||
return
|
||||
}
|
||||
|
||||
now := time.Now()
|
||||
last, ok := d.lastDisconnectLog[pubKey]
|
||||
if ok && now.Sub(last) < 10*time.Second {
|
||||
return
|
||||
}
|
||||
d.lastDisconnectLog[pubKey] = now
|
||||
|
||||
routes := state.GetRoutes()
|
||||
numRoutes := len(routes)
|
||||
|
||||
fqdn := state.FQDN
|
||||
if fqdn == "" {
|
||||
fqdn = pubKey
|
||||
}
|
||||
|
||||
// prepare a bounded list of impacted routes to avoid huge log lines
|
||||
maxList := 20
|
||||
list := make([]string, 0, maxList)
|
||||
for r := range routes {
|
||||
if len(list) >= maxList {
|
||||
break
|
||||
}
|
||||
list = append(list, r)
|
||||
}
|
||||
more := ""
|
||||
if numRoutes > len(list) {
|
||||
more = ", more=" + fmt.Sprintf("%d", numRoutes-len(list))
|
||||
}
|
||||
|
||||
if len(list) > 0 {
|
||||
log.Warnf("[d] Routing peer disconnected: peer=%s impacted_routes=%d routes=%v%s", fqdn, numRoutes, list, more)
|
||||
} else {
|
||||
log.Warnf("[d] Routing peer disconnected: peer=%s impacted_routes=%d", fqdn, numRoutes)
|
||||
}
|
||||
}
|
||||
|
||||
// UpdateWireGuardPeerState updates the WireGuard bits of the peer state
|
||||
func (d *Status) UpdateWireGuardPeerState(pubKey string, wgStats configurer.WGStats) error {
|
||||
d.mux.Lock()
|
||||
|
||||
@@ -30,9 +30,10 @@ type WGWatcher struct {
|
||||
peerKey string
|
||||
stateDump *stateDump
|
||||
|
||||
ctx context.Context
|
||||
ctxCancel context.CancelFunc
|
||||
ctxLock sync.Mutex
|
||||
ctx context.Context
|
||||
ctxCancel context.CancelFunc
|
||||
ctxLock sync.Mutex
|
||||
enabledTime time.Time
|
||||
}
|
||||
|
||||
func NewWGWatcher(log *log.Entry, wgIfaceStater WGInterfaceStater, peerKey string, stateDump *stateDump) *WGWatcher {
|
||||
@@ -48,6 +49,7 @@ func NewWGWatcher(log *log.Entry, wgIfaceStater WGInterfaceStater, peerKey strin
|
||||
func (w *WGWatcher) EnableWgWatcher(parentCtx context.Context, onDisconnectedFn func()) {
|
||||
w.log.Debugf("enable WireGuard watcher")
|
||||
w.ctxLock.Lock()
|
||||
w.enabledTime = time.Now()
|
||||
|
||||
if w.ctx != nil && w.ctx.Err() == nil {
|
||||
w.log.Errorf("WireGuard watcher already enabled")
|
||||
@@ -101,6 +103,11 @@ func (w *WGWatcher) periodicHandshakeCheck(ctx context.Context, ctxCancel contex
|
||||
onDisconnectedFn()
|
||||
return
|
||||
}
|
||||
if lastHandshake.IsZero() {
|
||||
elapsed := handshake.Sub(w.enabledTime).Seconds()
|
||||
w.log.Infof("first wg handshake detected within: %.2fsec, (%s)", elapsed, handshake)
|
||||
}
|
||||
|
||||
lastHandshake = *handshake
|
||||
|
||||
resetTime := time.Until(handshake.Add(checkPeriod))
|
||||
|
||||
@@ -122,7 +122,6 @@ func (w *WorkerICE) OnNewOffer(remoteOfferAnswer *OfferAnswer) {
|
||||
w.log.Warnf("failed to close ICE agent: %s", err)
|
||||
}
|
||||
w.agent = nil
|
||||
// todo consider to switch to Relay connection while establishing a new ICE connection
|
||||
}
|
||||
|
||||
var preferredCandidateTypes []ice.CandidateType
|
||||
@@ -410,7 +409,10 @@ func (w *WorkerICE) onConnectionStateChange(agent *icemaker.ThreadSafeAgent, dia
|
||||
case ice.ConnectionStateConnected:
|
||||
w.lastKnownState = ice.ConnectionStateConnected
|
||||
return
|
||||
case ice.ConnectionStateFailed, ice.ConnectionStateDisconnected:
|
||||
case ice.ConnectionStateFailed, ice.ConnectionStateDisconnected, ice.ConnectionStateClosed:
|
||||
// ice.ConnectionStateClosed happens when we recreate the agent. For the P2P to TURN switch important to
|
||||
// notify the conn.onICEStateDisconnected changes to update the current used priority
|
||||
|
||||
if w.lastKnownState == ice.ConnectionStateConnected {
|
||||
w.lastKnownState = ice.ConnectionStateDisconnected
|
||||
w.conn.onICEStateDisconnected()
|
||||
|
||||
@@ -40,7 +40,7 @@ func NewNetWithDiscover(iFaceDiscover ExternalIFaceDiscover, disallowList []stri
|
||||
if netstack.IsEnabled() {
|
||||
n.iFaceDiscover = pionDiscover{}
|
||||
} else {
|
||||
newMobileIFaceDiscover(iFaceDiscover)
|
||||
n.iFaceDiscover = newMobileIFaceDiscover(iFaceDiscover)
|
||||
}
|
||||
return n, n.UpdateInterfaces()
|
||||
}
|
||||
|
||||
@@ -10,25 +10,24 @@ import (
|
||||
"time"
|
||||
|
||||
"github.com/golang/mock/gomock"
|
||||
"github.com/stretchr/testify/require"
|
||||
"go.opentelemetry.io/otel"
|
||||
|
||||
"github.com/netbirdio/management-integrations/integrations"
|
||||
"github.com/netbirdio/netbird/management/internals/server/config"
|
||||
"github.com/netbirdio/netbird/management/server/groups"
|
||||
|
||||
log "github.com/sirupsen/logrus"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
"go.opentelemetry.io/otel"
|
||||
"google.golang.org/grpc"
|
||||
"google.golang.org/grpc/keepalive"
|
||||
|
||||
"github.com/netbirdio/management-integrations/integrations"
|
||||
"github.com/netbirdio/netbird/client/internal"
|
||||
"github.com/netbirdio/netbird/client/internal/peer"
|
||||
"github.com/netbirdio/netbird/client/internal/profilemanager"
|
||||
daemonProto "github.com/netbirdio/netbird/client/proto"
|
||||
"github.com/netbirdio/netbird/management/internals/server/config"
|
||||
"github.com/netbirdio/netbird/management/server"
|
||||
"github.com/netbirdio/netbird/management/server/activity"
|
||||
"github.com/netbirdio/netbird/management/server/groups"
|
||||
"github.com/netbirdio/netbird/management/server/integrations/port_forwarding"
|
||||
"github.com/netbirdio/netbird/management/server/peers"
|
||||
"github.com/netbirdio/netbird/management/server/permissions"
|
||||
"github.com/netbirdio/netbird/management/server/settings"
|
||||
"github.com/netbirdio/netbird/management/server/store"
|
||||
@@ -294,15 +293,20 @@ func startManagement(t *testing.T, signalAddr string, counter *int) (*grpc.Serve
|
||||
if err != nil {
|
||||
return nil, "", err
|
||||
}
|
||||
ia, _ := integrations.NewIntegratedValidator(context.Background(), eventStore)
|
||||
|
||||
ctrl := gomock.NewController(t)
|
||||
t.Cleanup(ctrl.Finish)
|
||||
|
||||
permissionsManagerMock := permissions.NewMockManager(ctrl)
|
||||
peersManager := peers.NewManager(store, permissionsManagerMock)
|
||||
settingsManagerMock := settings.NewMockManager(ctrl)
|
||||
|
||||
ia, _ := integrations.NewIntegratedValidator(context.Background(), peersManager, settingsManagerMock, eventStore)
|
||||
|
||||
metrics, err := telemetry.NewDefaultAppMetrics(context.Background())
|
||||
require.NoError(t, err)
|
||||
|
||||
ctrl := gomock.NewController(t)
|
||||
t.Cleanup(ctrl.Finish)
|
||||
settingsMockManager := settings.NewMockManager(ctrl)
|
||||
permissionsManagerMock := permissions.NewMockManager(ctrl)
|
||||
groupsManager := groups.NewManagerMock()
|
||||
|
||||
accountManager, err := server.BuildManager(context.Background(), store, peersUpdateManager, nil, "", "netbird.selfhosted", eventStore, nil, false, ia, metrics, port_forwarding.NewControllerMock(), settingsMockManager, permissionsManagerMock, false)
|
||||
|
||||
@@ -6,6 +6,7 @@ import (
|
||||
"net/netip"
|
||||
"strings"
|
||||
|
||||
log "github.com/sirupsen/logrus"
|
||||
"google.golang.org/grpc/metadata"
|
||||
|
||||
"github.com/netbirdio/netbird/shared/management/proto"
|
||||
@@ -95,14 +96,6 @@ func (i *Info) SetFlags(
|
||||
i.LazyConnectionEnabled = lazyConnectionEnabled
|
||||
}
|
||||
|
||||
// StaticInfo is an object that contains machine information that does not change
|
||||
type StaticInfo struct {
|
||||
SystemSerialNumber string
|
||||
SystemProductName string
|
||||
SystemManufacturer string
|
||||
Environment Environment
|
||||
}
|
||||
|
||||
// extractUserAgent extracts Netbird's agent (client) name and version from the outgoing context
|
||||
func extractUserAgent(ctx context.Context) string {
|
||||
md, hasMeta := metadata.FromOutgoingContext(ctx)
|
||||
@@ -180,6 +173,7 @@ func isDuplicated(addresses []NetworkAddress, addr NetworkAddress) bool {
|
||||
|
||||
// GetInfoWithChecks retrieves and parses the system information with applied checks.
|
||||
func GetInfoWithChecks(ctx context.Context, checks []*proto.Checks) (*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()...)
|
||||
@@ -189,16 +183,11 @@ func GetInfoWithChecks(ctx context.Context, checks []*proto.Checks) (*Info, erro
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
log.Debugf("gathering process check information completed")
|
||||
|
||||
info := GetInfo(ctx)
|
||||
info.Files = files
|
||||
|
||||
log.Debugf("all system information gathered successfully")
|
||||
return info, nil
|
||||
}
|
||||
|
||||
// UpdateStaticInfo asynchronously updates static system and platform information
|
||||
func UpdateStaticInfo() {
|
||||
go func() {
|
||||
_ = updateStaticInfo()
|
||||
}()
|
||||
}
|
||||
|
||||
@@ -15,6 +15,11 @@ import (
|
||||
"github.com/netbirdio/netbird/version"
|
||||
)
|
||||
|
||||
// UpdateStaticInfoAsync is a no-op on Android as there is no static info to update
|
||||
func UpdateStaticInfoAsync() {
|
||||
// do nothing
|
||||
}
|
||||
|
||||
// GetInfo retrieves and parses the system information
|
||||
func GetInfo(ctx context.Context) *Info {
|
||||
kernel := "android"
|
||||
|
||||
@@ -19,6 +19,10 @@ import (
|
||||
"github.com/netbirdio/netbird/version"
|
||||
)
|
||||
|
||||
func UpdateStaticInfoAsync() {
|
||||
go updateStaticInfo()
|
||||
}
|
||||
|
||||
// GetInfo retrieves and parses the system information
|
||||
func GetInfo(ctx context.Context) *Info {
|
||||
utsname := unix.Utsname{}
|
||||
@@ -41,7 +45,7 @@ func GetInfo(ctx context.Context) *Info {
|
||||
}
|
||||
|
||||
start := time.Now()
|
||||
si := updateStaticInfo()
|
||||
si := getStaticInfo()
|
||||
if time.Since(start) > 1*time.Second {
|
||||
log.Warnf("updateStaticInfo took %s", time.Since(start))
|
||||
}
|
||||
|
||||
@@ -18,6 +18,11 @@ import (
|
||||
"github.com/netbirdio/netbird/version"
|
||||
)
|
||||
|
||||
// UpdateStaticInfoAsync is a no-op on Android as there is no static info to update
|
||||
func UpdateStaticInfoAsync() {
|
||||
// do nothing
|
||||
}
|
||||
|
||||
// GetInfo retrieves and parses the system information
|
||||
func GetInfo(ctx context.Context) *Info {
|
||||
out := _getInfo()
|
||||
|
||||
@@ -10,6 +10,11 @@ import (
|
||||
"github.com/netbirdio/netbird/version"
|
||||
)
|
||||
|
||||
// UpdateStaticInfoAsync is a no-op on Android as there is no static info to update
|
||||
func UpdateStaticInfoAsync() {
|
||||
// do nothing
|
||||
}
|
||||
|
||||
// GetInfo retrieves and parses the system information
|
||||
func GetInfo(ctx context.Context) *Info {
|
||||
|
||||
|
||||
@@ -23,6 +23,10 @@ var (
|
||||
getSystemInfo = defaultSysInfoImplementation
|
||||
)
|
||||
|
||||
func UpdateStaticInfoAsync() {
|
||||
go updateStaticInfo()
|
||||
}
|
||||
|
||||
// GetInfo retrieves and parses the system information
|
||||
func GetInfo(ctx context.Context) *Info {
|
||||
info := _getInfo()
|
||||
@@ -48,7 +52,7 @@ func GetInfo(ctx context.Context) *Info {
|
||||
}
|
||||
|
||||
start := time.Now()
|
||||
si := updateStaticInfo()
|
||||
si := getStaticInfo()
|
||||
if time.Since(start) > 1*time.Second {
|
||||
log.Warnf("updateStaticInfo took %s", time.Since(start))
|
||||
}
|
||||
|
||||
@@ -2,187 +2,51 @@ package system
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"os"
|
||||
"runtime"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
log "github.com/sirupsen/logrus"
|
||||
"github.com/yusufpapurcu/wmi"
|
||||
"golang.org/x/sys/windows/registry"
|
||||
|
||||
"github.com/netbirdio/netbird/version"
|
||||
)
|
||||
|
||||
type Win32_OperatingSystem struct {
|
||||
Caption string
|
||||
}
|
||||
|
||||
type Win32_ComputerSystem struct {
|
||||
Manufacturer string
|
||||
}
|
||||
|
||||
type Win32_ComputerSystemProduct struct {
|
||||
Name string
|
||||
}
|
||||
|
||||
type Win32_BIOS struct {
|
||||
SerialNumber string
|
||||
func UpdateStaticInfoAsync() {
|
||||
go updateStaticInfo()
|
||||
}
|
||||
|
||||
// GetInfo retrieves and parses the system information
|
||||
func GetInfo(ctx context.Context) *Info {
|
||||
osName, osVersion := getOSNameAndVersion()
|
||||
buildVersion := getBuildVersion()
|
||||
|
||||
addrs, err := networkAddresses()
|
||||
if err != nil {
|
||||
log.Warnf("failed to discover network addresses: %s", err)
|
||||
}
|
||||
|
||||
start := time.Now()
|
||||
si := updateStaticInfo()
|
||||
si := getStaticInfo()
|
||||
if time.Since(start) > 1*time.Second {
|
||||
log.Warnf("updateStaticInfo took %s", time.Since(start))
|
||||
}
|
||||
|
||||
gio := &Info{
|
||||
Kernel: "windows",
|
||||
OSVersion: osVersion,
|
||||
OSVersion: si.OSVersion,
|
||||
Platform: "unknown",
|
||||
OS: osName,
|
||||
OS: si.OSName,
|
||||
GoOS: runtime.GOOS,
|
||||
CPUs: runtime.NumCPU(),
|
||||
KernelVersion: buildVersion,
|
||||
NetworkAddresses: addrs,
|
||||
KernelVersion: si.BuildVersion,
|
||||
SystemSerialNumber: si.SystemSerialNumber,
|
||||
SystemProductName: si.SystemProductName,
|
||||
SystemManufacturer: si.SystemManufacturer,
|
||||
Environment: si.Environment,
|
||||
}
|
||||
|
||||
addrs, err := networkAddresses()
|
||||
if err != nil {
|
||||
log.Warnf("failed to discover network addresses: %s", err)
|
||||
} else {
|
||||
gio.NetworkAddresses = addrs
|
||||
}
|
||||
|
||||
systemHostname, _ := os.Hostname()
|
||||
gio.Hostname = extractDeviceName(ctx, systemHostname)
|
||||
gio.NetbirdVersion = version.NetbirdVersion()
|
||||
gio.UIVersion = extractUserAgent(ctx)
|
||||
|
||||
return gio
|
||||
}
|
||||
|
||||
func sysInfo() (serialNumber string, productName string, manufacturer string) {
|
||||
var err error
|
||||
serialNumber, err = sysNumber()
|
||||
if err != nil {
|
||||
log.Warnf("failed to get system serial number: %s", err)
|
||||
}
|
||||
|
||||
productName, err = sysProductName()
|
||||
if err != nil {
|
||||
log.Warnf("failed to get system product name: %s", err)
|
||||
}
|
||||
|
||||
manufacturer, err = sysManufacturer()
|
||||
if err != nil {
|
||||
log.Warnf("failed to get system manufacturer: %s", err)
|
||||
}
|
||||
|
||||
return serialNumber, productName, manufacturer
|
||||
}
|
||||
|
||||
func getOSNameAndVersion() (string, string) {
|
||||
var dst []Win32_OperatingSystem
|
||||
query := wmi.CreateQuery(&dst, "")
|
||||
err := wmi.Query(query, &dst)
|
||||
if err != nil {
|
||||
log.Error(err)
|
||||
return "Windows", getBuildVersion()
|
||||
}
|
||||
|
||||
if len(dst) == 0 {
|
||||
return "Windows", getBuildVersion()
|
||||
}
|
||||
|
||||
split := strings.Split(dst[0].Caption, " ")
|
||||
|
||||
if len(split) <= 3 {
|
||||
return "Windows", getBuildVersion()
|
||||
}
|
||||
|
||||
name := split[1]
|
||||
version := split[2]
|
||||
if split[2] == "Server" {
|
||||
name = fmt.Sprintf("%s %s", split[1], split[2])
|
||||
version = split[3]
|
||||
}
|
||||
|
||||
return name, version
|
||||
}
|
||||
|
||||
func getBuildVersion() string {
|
||||
k, err := registry.OpenKey(registry.LOCAL_MACHINE, `SOFTWARE\Microsoft\Windows NT\CurrentVersion`, registry.QUERY_VALUE)
|
||||
if err != nil {
|
||||
log.Error(err)
|
||||
return "0.0.0.0"
|
||||
}
|
||||
defer func() {
|
||||
deferErr := k.Close()
|
||||
if deferErr != nil {
|
||||
log.Error(deferErr)
|
||||
}
|
||||
}()
|
||||
|
||||
major, _, err := k.GetIntegerValue("CurrentMajorVersionNumber")
|
||||
if err != nil {
|
||||
log.Error(err)
|
||||
}
|
||||
minor, _, err := k.GetIntegerValue("CurrentMinorVersionNumber")
|
||||
if err != nil {
|
||||
log.Error(err)
|
||||
}
|
||||
build, _, err := k.GetStringValue("CurrentBuildNumber")
|
||||
if err != nil {
|
||||
log.Error(err)
|
||||
}
|
||||
// Update Build Revision
|
||||
ubr, _, err := k.GetIntegerValue("UBR")
|
||||
if err != nil {
|
||||
log.Error(err)
|
||||
}
|
||||
ver := fmt.Sprintf("%d.%d.%s.%d", major, minor, build, ubr)
|
||||
return ver
|
||||
}
|
||||
|
||||
func sysNumber() (string, error) {
|
||||
var dst []Win32_BIOS
|
||||
query := wmi.CreateQuery(&dst, "")
|
||||
err := wmi.Query(query, &dst)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return dst[0].SerialNumber, nil
|
||||
}
|
||||
|
||||
func sysProductName() (string, error) {
|
||||
var dst []Win32_ComputerSystemProduct
|
||||
query := wmi.CreateQuery(&dst, "")
|
||||
err := wmi.Query(query, &dst)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
// `ComputerSystemProduct` could be empty on some virtualized systems
|
||||
if len(dst) < 1 {
|
||||
return "unknown", nil
|
||||
}
|
||||
return dst[0].Name, nil
|
||||
}
|
||||
|
||||
func sysManufacturer() (string, error) {
|
||||
var dst []Win32_ComputerSystem
|
||||
query := wmi.CreateQuery(&dst, "")
|
||||
err := wmi.Query(query, &dst)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return dst[0].Manufacturer, nil
|
||||
}
|
||||
|
||||
@@ -3,12 +3,7 @@
|
||||
package system
|
||||
|
||||
import (
|
||||
"context"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/netbirdio/netbird/client/system/detect_cloud"
|
||||
"github.com/netbirdio/netbird/client/system/detect_platform"
|
||||
)
|
||||
|
||||
var (
|
||||
@@ -16,25 +11,26 @@ var (
|
||||
once sync.Once
|
||||
)
|
||||
|
||||
func updateStaticInfo() StaticInfo {
|
||||
// StaticInfo is an object that contains machine information that does not change
|
||||
type StaticInfo struct {
|
||||
SystemSerialNumber string
|
||||
SystemProductName string
|
||||
SystemManufacturer string
|
||||
Environment Environment
|
||||
|
||||
// Windows specific fields
|
||||
OSName string
|
||||
OSVersion string
|
||||
BuildVersion string
|
||||
}
|
||||
|
||||
func updateStaticInfo() {
|
||||
once.Do(func() {
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
|
||||
defer cancel()
|
||||
wg := sync.WaitGroup{}
|
||||
wg.Add(3)
|
||||
go func() {
|
||||
staticInfo.SystemSerialNumber, staticInfo.SystemProductName, staticInfo.SystemManufacturer = sysInfo()
|
||||
wg.Done()
|
||||
}()
|
||||
go func() {
|
||||
staticInfo.Environment.Cloud = detect_cloud.Detect(ctx)
|
||||
wg.Done()
|
||||
}()
|
||||
go func() {
|
||||
staticInfo.Environment.Platform = detect_platform.Detect(ctx)
|
||||
wg.Done()
|
||||
}()
|
||||
wg.Wait()
|
||||
staticInfo = newStaticInfo()
|
||||
})
|
||||
}
|
||||
|
||||
func getStaticInfo() StaticInfo {
|
||||
updateStaticInfo()
|
||||
return staticInfo
|
||||
}
|
||||
|
||||
@@ -1,8 +0,0 @@
|
||||
//go:build android || freebsd || ios
|
||||
|
||||
package system
|
||||
|
||||
// updateStaticInfo returns an empty implementation for unsupported platforms
|
||||
func updateStaticInfo() StaticInfo {
|
||||
return StaticInfo{}
|
||||
}
|
||||
35
client/system/static_info_update.go
Normal file
35
client/system/static_info_update.go
Normal file
@@ -0,0 +1,35 @@
|
||||
//go:build (linux && !android) || (darwin && !ios)
|
||||
|
||||
package system
|
||||
|
||||
import (
|
||||
"context"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/netbirdio/netbird/client/system/detect_cloud"
|
||||
"github.com/netbirdio/netbird/client/system/detect_platform"
|
||||
)
|
||||
|
||||
func newStaticInfo() StaticInfo {
|
||||
si := StaticInfo{}
|
||||
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
|
||||
defer cancel()
|
||||
wg := sync.WaitGroup{}
|
||||
wg.Add(3)
|
||||
go func() {
|
||||
si.SystemSerialNumber, si.SystemProductName, si.SystemManufacturer = sysInfo()
|
||||
wg.Done()
|
||||
}()
|
||||
go func() {
|
||||
si.Environment.Cloud = detect_cloud.Detect(ctx)
|
||||
wg.Done()
|
||||
}()
|
||||
go func() {
|
||||
si.Environment.Platform = detect_platform.Detect(ctx)
|
||||
wg.Done()
|
||||
}()
|
||||
wg.Wait()
|
||||
return si
|
||||
}
|
||||
184
client/system/static_info_update_windows.go
Normal file
184
client/system/static_info_update_windows.go
Normal file
@@ -0,0 +1,184 @@
|
||||
package system
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
log "github.com/sirupsen/logrus"
|
||||
"github.com/yusufpapurcu/wmi"
|
||||
"golang.org/x/sys/windows/registry"
|
||||
|
||||
"github.com/netbirdio/netbird/client/system/detect_cloud"
|
||||
"github.com/netbirdio/netbird/client/system/detect_platform"
|
||||
)
|
||||
|
||||
type Win32_OperatingSystem struct {
|
||||
Caption string
|
||||
}
|
||||
|
||||
type Win32_ComputerSystem struct {
|
||||
Manufacturer string
|
||||
}
|
||||
|
||||
type Win32_ComputerSystemProduct struct {
|
||||
Name string
|
||||
}
|
||||
|
||||
type Win32_BIOS struct {
|
||||
SerialNumber string
|
||||
}
|
||||
|
||||
func newStaticInfo() StaticInfo {
|
||||
si := StaticInfo{}
|
||||
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
|
||||
defer cancel()
|
||||
wg := sync.WaitGroup{}
|
||||
wg.Add(1)
|
||||
go func() {
|
||||
si.SystemSerialNumber, si.SystemProductName, si.SystemManufacturer = sysInfo()
|
||||
wg.Done()
|
||||
}()
|
||||
wg.Add(1)
|
||||
go func() {
|
||||
si.Environment.Cloud = detect_cloud.Detect(ctx)
|
||||
wg.Done()
|
||||
}()
|
||||
wg.Add(1)
|
||||
go func() {
|
||||
si.Environment.Platform = detect_platform.Detect(ctx)
|
||||
wg.Done()
|
||||
}()
|
||||
wg.Add(1)
|
||||
go func() {
|
||||
si.OSName, si.OSVersion = getOSNameAndVersion()
|
||||
wg.Done()
|
||||
}()
|
||||
wg.Add(1)
|
||||
go func() {
|
||||
si.BuildVersion = getBuildVersion()
|
||||
wg.Done()
|
||||
}()
|
||||
wg.Wait()
|
||||
return si
|
||||
}
|
||||
|
||||
func sysInfo() (serialNumber string, productName string, manufacturer string) {
|
||||
var err error
|
||||
serialNumber, err = sysNumber()
|
||||
if err != nil {
|
||||
log.Warnf("failed to get system serial number: %s", err)
|
||||
}
|
||||
|
||||
productName, err = sysProductName()
|
||||
if err != nil {
|
||||
log.Warnf("failed to get system product name: %s", err)
|
||||
}
|
||||
|
||||
manufacturer, err = sysManufacturer()
|
||||
if err != nil {
|
||||
log.Warnf("failed to get system manufacturer: %s", err)
|
||||
}
|
||||
|
||||
return serialNumber, productName, manufacturer
|
||||
}
|
||||
|
||||
func sysNumber() (string, error) {
|
||||
var dst []Win32_BIOS
|
||||
query := wmi.CreateQuery(&dst, "")
|
||||
err := wmi.Query(query, &dst)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return dst[0].SerialNumber, nil
|
||||
}
|
||||
|
||||
func sysProductName() (string, error) {
|
||||
var dst []Win32_ComputerSystemProduct
|
||||
query := wmi.CreateQuery(&dst, "")
|
||||
err := wmi.Query(query, &dst)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
// `ComputerSystemProduct` could be empty on some virtualized systems
|
||||
if len(dst) < 1 {
|
||||
return "unknown", nil
|
||||
}
|
||||
return dst[0].Name, nil
|
||||
}
|
||||
|
||||
func sysManufacturer() (string, error) {
|
||||
var dst []Win32_ComputerSystem
|
||||
query := wmi.CreateQuery(&dst, "")
|
||||
err := wmi.Query(query, &dst)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return dst[0].Manufacturer, nil
|
||||
}
|
||||
|
||||
func getOSNameAndVersion() (string, string) {
|
||||
var dst []Win32_OperatingSystem
|
||||
query := wmi.CreateQuery(&dst, "")
|
||||
err := wmi.Query(query, &dst)
|
||||
if err != nil {
|
||||
log.Error(err)
|
||||
return "Windows", getBuildVersion()
|
||||
}
|
||||
|
||||
if len(dst) == 0 {
|
||||
return "Windows", getBuildVersion()
|
||||
}
|
||||
|
||||
split := strings.Split(dst[0].Caption, " ")
|
||||
|
||||
if len(split) <= 3 {
|
||||
return "Windows", getBuildVersion()
|
||||
}
|
||||
|
||||
name := split[1]
|
||||
version := split[2]
|
||||
if split[2] == "Server" {
|
||||
name = fmt.Sprintf("%s %s", split[1], split[2])
|
||||
version = split[3]
|
||||
}
|
||||
|
||||
return name, version
|
||||
}
|
||||
|
||||
func getBuildVersion() string {
|
||||
k, err := registry.OpenKey(registry.LOCAL_MACHINE, `SOFTWARE\Microsoft\Windows NT\CurrentVersion`, registry.QUERY_VALUE)
|
||||
if err != nil {
|
||||
log.Error(err)
|
||||
return "0.0.0.0"
|
||||
}
|
||||
defer func() {
|
||||
deferErr := k.Close()
|
||||
if deferErr != nil {
|
||||
log.Error(deferErr)
|
||||
}
|
||||
}()
|
||||
|
||||
major, _, err := k.GetIntegerValue("CurrentMajorVersionNumber")
|
||||
if err != nil {
|
||||
log.Error(err)
|
||||
}
|
||||
minor, _, err := k.GetIntegerValue("CurrentMinorVersionNumber")
|
||||
if err != nil {
|
||||
log.Error(err)
|
||||
}
|
||||
build, _, err := k.GetStringValue("CurrentBuildNumber")
|
||||
if err != nil {
|
||||
log.Error(err)
|
||||
}
|
||||
// Update Build Revision
|
||||
ubr, _, err := k.GetIntegerValue("UBR")
|
||||
if err != nil {
|
||||
log.Error(err)
|
||||
}
|
||||
ver := fmt.Sprintf("%d.%d.%s.%d", major, minor, build, ubr)
|
||||
return ver
|
||||
}
|
||||
2
go.mod
2
go.mod
@@ -62,7 +62,7 @@ require (
|
||||
github.com/miekg/dns v1.1.59
|
||||
github.com/mitchellh/hashstructure/v2 v2.0.2
|
||||
github.com/nadoo/ipset v0.5.0
|
||||
github.com/netbirdio/management-integrations/integrations v0.0.0-20250820151658-9ee1b34f4190
|
||||
github.com/netbirdio/management-integrations/integrations v0.0.0-20250906095204-f87a07690ba0
|
||||
github.com/netbirdio/signal-dispatcher/dispatcher v0.0.0-20250805121659-6b4ac470ca45
|
||||
github.com/okta/okta-sdk-golang/v2 v2.18.0
|
||||
github.com/oschwald/maxminddb-golang v1.12.0
|
||||
|
||||
4
go.sum
4
go.sum
@@ -503,8 +503,8 @@ github.com/netbirdio/go-netroute v0.0.0-20240611143515-f59b0e1d3944 h1:TDtJKmM6S
|
||||
github.com/netbirdio/go-netroute v0.0.0-20240611143515-f59b0e1d3944/go.mod h1:sHA6TRxjQ6RLbnI+3R4DZo2Eseg/iKiPRfNmcuNySVQ=
|
||||
github.com/netbirdio/ice/v4 v4.0.0-20250827161942-426799a23107 h1:ZJwhKexMlK15B/Ld+1T8VYE2Mt1lk1kf2DlXr46EHcw=
|
||||
github.com/netbirdio/ice/v4 v4.0.0-20250827161942-426799a23107/go.mod h1:ZSIbPdBn5hePO8CpF1PekH2SfpTxg1PDhEwtbqZS7R8=
|
||||
github.com/netbirdio/management-integrations/integrations v0.0.0-20250820151658-9ee1b34f4190 h1:/ZbExdcDwRq6XgTpTf5I1DPqnC3eInEf0fcmkqR8eSg=
|
||||
github.com/netbirdio/management-integrations/integrations v0.0.0-20250820151658-9ee1b34f4190/go.mod h1:v0nUbbHbuQnqR7yKIYnKzsLBCswLtp2JctmKYmGgVhc=
|
||||
github.com/netbirdio/management-integrations/integrations v0.0.0-20250906095204-f87a07690ba0 h1:9BUqQHPVOGr0edk8EifUBUfTr2Ob0ypAPxtasUApBxQ=
|
||||
github.com/netbirdio/management-integrations/integrations v0.0.0-20250906095204-f87a07690ba0/go.mod h1:v0nUbbHbuQnqR7yKIYnKzsLBCswLtp2JctmKYmGgVhc=
|
||||
github.com/netbirdio/service v0.0.0-20240911161631-f62744f42502 h1:3tHlFmhTdX9axERMVN63dqyFqnvuD+EMJHzM7mNGON8=
|
||||
github.com/netbirdio/service v0.0.0-20240911161631-f62744f42502/go.mod h1:CIMRFEJVL+0DS1a3Nx06NaMn4Dz63Ng6O7dl0qH0zVM=
|
||||
github.com/netbirdio/signal-dispatcher/dispatcher v0.0.0-20250805121659-6b4ac470ca45 h1:ujgviVYmx243Ksy7NdSwrdGPSRNE3pb8kEDSpH0QuAQ=
|
||||
|
||||
@@ -20,7 +20,11 @@ func (s *BaseServer) PeersUpdateManager() *server.PeersUpdateManager {
|
||||
|
||||
func (s *BaseServer) IntegratedValidator() integrated_validator.IntegratedValidator {
|
||||
return Create(s, func() integrated_validator.IntegratedValidator {
|
||||
integratedPeerValidator, err := integrations.NewIntegratedValidator(context.Background(), s.EventStore())
|
||||
integratedPeerValidator, err := integrations.NewIntegratedValidator(
|
||||
context.Background(),
|
||||
s.PeersManager(),
|
||||
s.SettingsManager(),
|
||||
s.EventStore())
|
||||
if err != nil {
|
||||
log.Errorf("failed to create integrated peer validator: %v", err)
|
||||
}
|
||||
|
||||
@@ -1714,7 +1714,9 @@ func (am *DefaultAccountManager) onPeersInvalidated(ctx context.Context, account
|
||||
log.WithContext(ctx).Errorf("failed to get invalidated peer %s for account %s: %v", peerID, accountID, err)
|
||||
continue
|
||||
}
|
||||
peers = append(peers, peer)
|
||||
if peer.UserID != "" {
|
||||
peers = append(peers, peer)
|
||||
}
|
||||
}
|
||||
if len(peers) > 0 {
|
||||
err := am.expireAndUpdatePeers(ctx, accountID, peers)
|
||||
|
||||
@@ -18,6 +18,7 @@ type Manager interface {
|
||||
GetPeer(ctx context.Context, accountID, userID, peerID string) (*peer.Peer, error)
|
||||
GetPeerAccountID(ctx context.Context, peerID string) (string, error)
|
||||
GetAllPeers(ctx context.Context, accountID, userID string) ([]*peer.Peer, error)
|
||||
GetPeersByGroupIDs(ctx context.Context, accountID string, groupsIDs []string) ([]*peer.Peer, error)
|
||||
}
|
||||
|
||||
type managerImpl struct {
|
||||
@@ -61,3 +62,7 @@ func (m *managerImpl) GetAllPeers(ctx context.Context, accountID, userID string)
|
||||
func (m *managerImpl) GetPeerAccountID(ctx context.Context, peerID string) (string, error) {
|
||||
return m.store.GetAccountIDByPeerID(ctx, store.LockingStrengthNone, peerID)
|
||||
}
|
||||
|
||||
func (m *managerImpl) GetPeersByGroupIDs(ctx context.Context, accountID string, groupsIDs []string) ([]*peer.Peer, error) {
|
||||
return m.store.GetPeersByGroupIDs(ctx, accountID, groupsIDs)
|
||||
}
|
||||
|
||||
@@ -79,3 +79,18 @@ func (mr *MockManagerMockRecorder) GetPeerAccountID(ctx, peerID interface{}) *go
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetPeerAccountID", reflect.TypeOf((*MockManager)(nil).GetPeerAccountID), ctx, peerID)
|
||||
}
|
||||
|
||||
// GetPeersByGroupIDs mocks base method.
|
||||
func (m *MockManager) GetPeersByGroupIDs(ctx context.Context, accountID string, groupsIDs []string) ([]*peer.Peer, error) {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "GetPeersByGroupIDs", ctx, accountID, groupsIDs)
|
||||
ret0, _ := ret[0].([]*peer.Peer)
|
||||
ret1, _ := ret[1].(error)
|
||||
return ret0, ret1
|
||||
}
|
||||
|
||||
// GetPeersByGroupIDs indicates an expected call of GetPeersByGroupIDs.
|
||||
func (mr *MockManagerMockRecorder) GetPeersByGroupIDs(ctx, accountID, groupsIDs interface{}) *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetPeersByGroupIDs", reflect.TypeOf((*MockManager)(nil).GetPeersByGroupIDs), ctx, accountID, groupsIDs)
|
||||
}
|
||||
|
||||
@@ -2847,3 +2847,22 @@ func (s *SqlStore) UpdateAccountNetwork(ctx context.Context, accountID string, i
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *SqlStore) GetPeersByGroupIDs(ctx context.Context, accountID string, groupIDs []string) ([]*nbpeer.Peer, error) {
|
||||
if len(groupIDs) == 0 {
|
||||
return []*nbpeer.Peer{}, nil
|
||||
}
|
||||
|
||||
var peers []*nbpeer.Peer
|
||||
peerIDsSubquery := s.db.Model(&types.GroupPeer{}).
|
||||
Select("DISTINCT peer_id").
|
||||
Where("account_id = ? AND group_id IN ?", accountID, groupIDs)
|
||||
|
||||
result := s.db.Where("id IN (?)", peerIDsSubquery).Find(&peers)
|
||||
if result.Error != nil {
|
||||
log.WithContext(ctx).Errorf("failed to get peers by group IDs: %s", result.Error)
|
||||
return nil, status.Errorf(status.Internal, "failed to get peers by group IDs")
|
||||
}
|
||||
|
||||
return peers, nil
|
||||
}
|
||||
|
||||
@@ -3607,3 +3607,113 @@ func intToIPv4(n uint32) net.IP {
|
||||
binary.BigEndian.PutUint32(ip, n)
|
||||
return ip
|
||||
}
|
||||
|
||||
func TestSqlStore_GetPeersByGroupIDs(t *testing.T) {
|
||||
accountID := "bf1c8084-ba50-4ce7-9439-34653001fc3b"
|
||||
|
||||
group1ID := "test-group-1"
|
||||
group2ID := "test-group-2"
|
||||
emptyGroupID := "empty-group"
|
||||
|
||||
peer1 := "cfefqs706sqkneg59g4g"
|
||||
peer2 := "cfeg6sf06sqkneg59g50"
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
groupIDs []string
|
||||
expectedPeers []string
|
||||
expectedCount int
|
||||
}{
|
||||
{
|
||||
name: "retrieve peers from single group with multiple peers",
|
||||
groupIDs: []string{group1ID},
|
||||
expectedPeers: []string{peer1, peer2},
|
||||
expectedCount: 2,
|
||||
},
|
||||
{
|
||||
name: "retrieve peers from single group with one peer",
|
||||
groupIDs: []string{group2ID},
|
||||
expectedPeers: []string{peer1},
|
||||
expectedCount: 1,
|
||||
},
|
||||
{
|
||||
name: "retrieve peers from multiple groups (with overlap)",
|
||||
groupIDs: []string{group1ID, group2ID},
|
||||
expectedPeers: []string{peer1, peer2}, // should deduplicate
|
||||
expectedCount: 2,
|
||||
},
|
||||
{
|
||||
name: "retrieve peers from existing 'All' group",
|
||||
groupIDs: []string{"cfefqs706sqkneg59g3g"}, // All group from test data
|
||||
expectedPeers: []string{peer1, peer2},
|
||||
expectedCount: 2,
|
||||
},
|
||||
{
|
||||
name: "retrieve peers from empty group",
|
||||
groupIDs: []string{emptyGroupID},
|
||||
expectedPeers: []string{},
|
||||
expectedCount: 0,
|
||||
},
|
||||
{
|
||||
name: "retrieve peers from non-existing group",
|
||||
groupIDs: []string{"non-existing-group"},
|
||||
expectedPeers: []string{},
|
||||
expectedCount: 0,
|
||||
},
|
||||
{
|
||||
name: "empty group IDs list",
|
||||
groupIDs: []string{},
|
||||
expectedPeers: []string{},
|
||||
expectedCount: 0,
|
||||
},
|
||||
{
|
||||
name: "mix of existing and non-existing groups",
|
||||
groupIDs: []string{group1ID, "non-existing-group"},
|
||||
expectedPeers: []string{peer1, peer2},
|
||||
expectedCount: 2,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
store, cleanup, err := NewTestStoreFromSQL(context.Background(), "../testdata/store_policy_migrate.sql", t.TempDir())
|
||||
t.Cleanup(cleanup)
|
||||
require.NoError(t, err)
|
||||
|
||||
ctx := context.Background()
|
||||
|
||||
groups := []*types.Group{
|
||||
{
|
||||
ID: group1ID,
|
||||
AccountID: accountID,
|
||||
},
|
||||
{
|
||||
ID: group2ID,
|
||||
AccountID: accountID,
|
||||
},
|
||||
}
|
||||
require.NoError(t, store.CreateGroups(ctx, accountID, groups))
|
||||
|
||||
require.NoError(t, store.AddPeerToGroup(ctx, accountID, peer1, group1ID))
|
||||
require.NoError(t, store.AddPeerToGroup(ctx, accountID, peer2, group1ID))
|
||||
require.NoError(t, store.AddPeerToGroup(ctx, accountID, peer1, group2ID))
|
||||
|
||||
peers, err := store.GetPeersByGroupIDs(ctx, accountID, tt.groupIDs)
|
||||
require.NoError(t, err)
|
||||
require.Len(t, peers, tt.expectedCount)
|
||||
|
||||
if tt.expectedCount > 0 {
|
||||
actualPeerIDs := make([]string, len(peers))
|
||||
for i, peer := range peers {
|
||||
actualPeerIDs[i] = peer.ID
|
||||
}
|
||||
assert.ElementsMatch(t, tt.expectedPeers, actualPeerIDs)
|
||||
|
||||
// Verify all returned peers belong to the correct account
|
||||
for _, peer := range peers {
|
||||
assert.Equal(t, accountID, peer.AccountID)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -136,6 +136,7 @@ type Store interface {
|
||||
GetUserPeers(ctx context.Context, lockStrength LockingStrength, accountID, userID string) ([]*nbpeer.Peer, error)
|
||||
GetPeerByID(ctx context.Context, lockStrength LockingStrength, accountID string, peerID string) (*nbpeer.Peer, error)
|
||||
GetPeersByIDs(ctx context.Context, lockStrength LockingStrength, accountID string, peerIDs []string) (map[string]*nbpeer.Peer, error)
|
||||
GetPeersByGroupIDs(ctx context.Context, accountID string, groupIDs []string) ([]*nbpeer.Peer, error)
|
||||
GetAccountPeersWithExpiration(ctx context.Context, lockStrength LockingStrength, accountID string) ([]*nbpeer.Peer, error)
|
||||
GetAccountPeersWithInactivity(ctx context.Context, lockStrength LockingStrength, accountID string) ([]*nbpeer.Peer, error)
|
||||
GetAllEphemeralPeers(ctx context.Context, lockStrength LockingStrength) ([]*nbpeer.Peer, error)
|
||||
|
||||
@@ -942,6 +942,11 @@ func (am *DefaultAccountManager) expireAndUpdatePeers(ctx context.Context, accou
|
||||
// nolint:staticcheck
|
||||
ctx = context.WithValue(ctx, nbContext.PeerIDKey, peer.Key)
|
||||
|
||||
if peer.UserID == "" {
|
||||
// we do not want to expire peers that are added via setup key
|
||||
continue
|
||||
}
|
||||
|
||||
if peer.Status.LoginExpired {
|
||||
continue
|
||||
}
|
||||
|
||||
@@ -130,36 +130,6 @@ repo_gpgcheck=1
|
||||
EOF
|
||||
}
|
||||
|
||||
install_aur_package() {
|
||||
INSTALL_PKGS="git base-devel go"
|
||||
REMOVE_PKGS=""
|
||||
|
||||
# Check if dependencies are installed
|
||||
for PKG in $INSTALL_PKGS; do
|
||||
if ! pacman -Q "$PKG" > /dev/null 2>&1; then
|
||||
# Install missing package(s)
|
||||
${SUDO} pacman -S "$PKG" --noconfirm
|
||||
|
||||
# Add installed package for clean up later
|
||||
REMOVE_PKGS="$REMOVE_PKGS $PKG"
|
||||
fi
|
||||
done
|
||||
|
||||
# Build package from AUR
|
||||
cd /tmp && git clone https://aur.archlinux.org/netbird.git
|
||||
cd netbird && makepkg -sri --noconfirm
|
||||
|
||||
if ! $SKIP_UI_APP; then
|
||||
cd /tmp && git clone https://aur.archlinux.org/netbird-ui.git
|
||||
cd netbird-ui && makepkg -sri --noconfirm
|
||||
fi
|
||||
|
||||
if [ -n "$REMOVE_PKGS" ]; then
|
||||
# Clean up the installed packages
|
||||
${SUDO} pacman -Rs "$REMOVE_PKGS" --noconfirm
|
||||
fi
|
||||
}
|
||||
|
||||
prepare_tun_module() {
|
||||
# Create the necessary file structure for /dev/net/tun
|
||||
if [ ! -c /dev/net/tun ]; then
|
||||
@@ -276,12 +246,9 @@ install_netbird() {
|
||||
if ! $SKIP_UI_APP; then
|
||||
${SUDO} rpm-ostree -y install netbird-ui
|
||||
fi
|
||||
;;
|
||||
pacman)
|
||||
${SUDO} pacman -Syy
|
||||
install_aur_package
|
||||
# in-line with the docs at https://wiki.archlinux.org/title/Netbird
|
||||
${SUDO} systemctl enable --now netbird@main.service
|
||||
# ensure the service is started after install
|
||||
${SUDO} netbird service install || true
|
||||
${SUDO} netbird service start || true
|
||||
;;
|
||||
pkg)
|
||||
# Check if the package is already installed
|
||||
@@ -458,11 +425,7 @@ if type uname >/dev/null 2>&1; then
|
||||
elif [ -x "$(command -v yum)" ]; then
|
||||
PACKAGE_MANAGER="yum"
|
||||
echo "The installation will be performed using yum package manager"
|
||||
elif [ -x "$(command -v pacman)" ]; then
|
||||
PACKAGE_MANAGER="pacman"
|
||||
echo "The installation will be performed using pacman package manager"
|
||||
fi
|
||||
|
||||
else
|
||||
echo "Unable to determine OS type from /etc/os-release"
|
||||
exit 1
|
||||
|
||||
@@ -9,34 +9,30 @@ import (
|
||||
"time"
|
||||
|
||||
"github.com/golang/mock/gomock"
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"github.com/netbirdio/netbird/client/system"
|
||||
"github.com/netbirdio/netbird/management/internals/server/config"
|
||||
"github.com/netbirdio/netbird/management/server/activity"
|
||||
"github.com/netbirdio/netbird/management/server/groups"
|
||||
"github.com/netbirdio/netbird/management/server/integrations/port_forwarding"
|
||||
"github.com/netbirdio/netbird/management/server/permissions"
|
||||
"github.com/netbirdio/netbird/management/server/settings"
|
||||
"github.com/netbirdio/netbird/management/server/store"
|
||||
"github.com/netbirdio/netbird/management/server/telemetry"
|
||||
"github.com/netbirdio/netbird/management/server/types"
|
||||
|
||||
log "github.com/sirupsen/logrus"
|
||||
"github.com/stretchr/testify/assert"
|
||||
|
||||
"github.com/netbirdio/management-integrations/integrations"
|
||||
|
||||
"github.com/netbirdio/netbird/encryption"
|
||||
mgmt "github.com/netbirdio/netbird/management/server"
|
||||
"github.com/netbirdio/netbird/management/server/mock_server"
|
||||
mgmtProto "github.com/netbirdio/netbird/shared/management/proto"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
"golang.zx2c4.com/wireguard/wgctrl/wgtypes"
|
||||
"google.golang.org/grpc"
|
||||
"google.golang.org/grpc/codes"
|
||||
"google.golang.org/grpc/status"
|
||||
|
||||
"github.com/netbirdio/management-integrations/integrations"
|
||||
"github.com/netbirdio/netbird/client/system"
|
||||
"github.com/netbirdio/netbird/encryption"
|
||||
"github.com/netbirdio/netbird/management/internals/server/config"
|
||||
mgmt "github.com/netbirdio/netbird/management/server"
|
||||
"github.com/netbirdio/netbird/management/server/activity"
|
||||
"github.com/netbirdio/netbird/management/server/groups"
|
||||
"github.com/netbirdio/netbird/management/server/integrations/port_forwarding"
|
||||
"github.com/netbirdio/netbird/management/server/mock_server"
|
||||
"github.com/netbirdio/netbird/management/server/peers"
|
||||
"github.com/netbirdio/netbird/management/server/permissions"
|
||||
"github.com/netbirdio/netbird/management/server/settings"
|
||||
"github.com/netbirdio/netbird/management/server/store"
|
||||
"github.com/netbirdio/netbird/management/server/telemetry"
|
||||
"github.com/netbirdio/netbird/management/server/types"
|
||||
mgmtProto "github.com/netbirdio/netbird/shared/management/proto"
|
||||
"github.com/netbirdio/netbird/util"
|
||||
)
|
||||
|
||||
@@ -72,13 +68,31 @@ func startManagement(t *testing.T) (*grpc.Server, net.Listener) {
|
||||
|
||||
peersUpdateManager := mgmt.NewPeersUpdateManager(nil)
|
||||
eventStore := &activity.InMemoryEventStore{}
|
||||
ia, _ := integrations.NewIntegratedValidator(context.Background(), eventStore)
|
||||
|
||||
ctrl := gomock.NewController(t)
|
||||
t.Cleanup(ctrl.Finish)
|
||||
|
||||
permissionsManagerMock := permissions.NewMockManager(ctrl)
|
||||
permissionsManagerMock.
|
||||
EXPECT().
|
||||
ValidateUserPermissions(
|
||||
gomock.Any(),
|
||||
gomock.Any(),
|
||||
gomock.Any(),
|
||||
gomock.Any(),
|
||||
gomock.Any(),
|
||||
).
|
||||
Return(true, nil).
|
||||
AnyTimes()
|
||||
|
||||
peersManger := peers.NewManager(store, permissionsManagerMock)
|
||||
settingsManagerMock := settings.NewMockManager(ctrl)
|
||||
|
||||
ia, _ := integrations.NewIntegratedValidator(context.Background(), peersManger, settingsManagerMock, eventStore)
|
||||
|
||||
metrics, err := telemetry.NewDefaultAppMetrics(context.Background())
|
||||
require.NoError(t, err)
|
||||
|
||||
ctrl := gomock.NewController(t)
|
||||
t.Cleanup(ctrl.Finish)
|
||||
settingsMockManager := settings.NewMockManager(ctrl)
|
||||
settingsMockManager.
|
||||
EXPECT().
|
||||
@@ -95,19 +109,6 @@ func startManagement(t *testing.T) (*grpc.Server, net.Listener) {
|
||||
Return(&types.ExtraSettings{}, nil).
|
||||
AnyTimes()
|
||||
|
||||
permissionsManagerMock := permissions.NewMockManager(ctrl)
|
||||
permissionsManagerMock.
|
||||
EXPECT().
|
||||
ValidateUserPermissions(
|
||||
gomock.Any(),
|
||||
gomock.Any(),
|
||||
gomock.Any(),
|
||||
gomock.Any(),
|
||||
gomock.Any(),
|
||||
).
|
||||
Return(true, nil).
|
||||
AnyTimes()
|
||||
|
||||
accountManager, err := mgmt.BuildManager(context.Background(), store, peersUpdateManager, nil, "", "netbird.selfhosted", eventStore, nil, false, ia, metrics, port_forwarding.NewControllerMock(), settingsMockManager, permissionsManagerMock, false)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
|
||||
@@ -14,7 +14,7 @@ import (
|
||||
"github.com/netbirdio/netbird/formatter"
|
||||
)
|
||||
|
||||
const defaultLogSize = 15
|
||||
const defaultLogSize = 100
|
||||
|
||||
const (
|
||||
LogConsole = "console"
|
||||
|
||||
Reference in New Issue
Block a user