Merge branch 'main' into feature/relay

This commit is contained in:
Zoltán Papp 2024-06-06 13:38:40 +02:00
commit 1e115e3893
58 changed files with 1598 additions and 1194 deletions

View File

@ -130,3 +130,10 @@ issues:
- path: mock\.go - path: mock\.go
linters: linters:
- nilnil - nilnil
# Exclude specific deprecation warnings for grpc methods
- linters:
- staticcheck
text: "grpc.DialContext is deprecated"
- linters:
- staticcheck
text: "grpc.WithBlock is deprecated"

View File

@ -5,7 +5,7 @@
We as members, contributors, and leaders pledge to make participation in our We as members, contributors, and leaders pledge to make participation in our
community a harassment-free experience for everyone, regardless of age, body community a harassment-free experience for everyone, regardless of age, body
size, visible or invisible disability, ethnicity, sex characteristics, gender size, visible or invisible disability, ethnicity, sex characteristics, gender
identity and expression, level of experience, education, socio-economic status, identity and expression, level of experience, education, socioeconomic status,
nationality, personal appearance, race, caste, color, religion, or sexual nationality, personal appearance, race, caste, color, religion, or sexual
identity and orientation. identity and orientation.

View File

@ -57,15 +57,17 @@ type Client struct {
ctxCancel context.CancelFunc ctxCancel context.CancelFunc
ctxCancelLock *sync.Mutex ctxCancelLock *sync.Mutex
deviceName string deviceName string
uiVersion string
networkChangeListener listener.NetworkChangeListener networkChangeListener listener.NetworkChangeListener
} }
// NewClient instantiate a new Client // NewClient instantiate a new Client
func NewClient(cfgFile, deviceName string, tunAdapter TunAdapter, iFaceDiscover IFaceDiscover, networkChangeListener NetworkChangeListener) *Client { func NewClient(cfgFile, deviceName string, uiVersion string, tunAdapter TunAdapter, iFaceDiscover IFaceDiscover, networkChangeListener NetworkChangeListener) *Client {
net.SetAndroidProtectSocketFn(tunAdapter.ProtectSocket) net.SetAndroidProtectSocketFn(tunAdapter.ProtectSocket)
return &Client{ return &Client{
cfgFile: cfgFile, cfgFile: cfgFile,
deviceName: deviceName, deviceName: deviceName,
uiVersion: uiVersion,
tunAdapter: tunAdapter, tunAdapter: tunAdapter,
iFaceDiscover: iFaceDiscover, iFaceDiscover: iFaceDiscover,
recorder: peer.NewRecorder(""), recorder: peer.NewRecorder(""),
@ -88,6 +90,9 @@ func (c *Client) Run(urlOpener URLOpener, dns *DNSList, dnsReadyListener DnsRead
var ctx context.Context var ctx context.Context
//nolint //nolint
ctxWithValues := context.WithValue(context.Background(), system.DeviceNameCtxKey, c.deviceName) ctxWithValues := context.WithValue(context.Background(), system.DeviceNameCtxKey, c.deviceName)
//nolint
ctxWithValues = context.WithValue(ctxWithValues, system.UiVersionCtxKey, c.uiVersion)
c.ctxCancelLock.Lock() c.ctxCancelLock.Lock()
ctx, c.ctxCancel = context.WithCancel(ctxWithValues) ctx, c.ctxCancel = context.WithCancel(ctxWithValues)
defer c.ctxCancel() defer c.ctxCancel()

View File

@ -3,13 +3,14 @@ package cmd
import ( import (
"context" "context"
"fmt" "fmt"
"strings"
"time" "time"
"github.com/spf13/cobra" "github.com/spf13/cobra"
"google.golang.org/grpc/status" "google.golang.org/grpc/status"
"github.com/netbirdio/netbird/client/internal"
"github.com/netbirdio/netbird/client/proto" "github.com/netbirdio/netbird/client/proto"
"github.com/netbirdio/netbird/client/server"
) )
var debugCmd = &cobra.Command{ var debugCmd = &cobra.Command{
@ -58,7 +59,7 @@ var forCmd = &cobra.Command{
} }
func debugBundle(cmd *cobra.Command, _ []string) error { func debugBundle(cmd *cobra.Command, _ []string) error {
conn, err := getClient(cmd.Context()) conn, err := getClient(cmd)
if err != nil { if err != nil {
return err return err
} }
@ -79,14 +80,14 @@ func debugBundle(cmd *cobra.Command, _ []string) error {
} }
func setLogLevel(cmd *cobra.Command, args []string) error { func setLogLevel(cmd *cobra.Command, args []string) error {
conn, err := getClient(cmd.Context()) conn, err := getClient(cmd)
if err != nil { if err != nil {
return err return err
} }
defer conn.Close() defer conn.Close()
client := proto.NewDaemonServiceClient(conn) client := proto.NewDaemonServiceClient(conn)
level := parseLogLevel(args[0]) level := server.ParseLogLevel(args[0])
if level == proto.LogLevel_UNKNOWN { if level == proto.LogLevel_UNKNOWN {
return fmt.Errorf("unknown log level: %s. Available levels are: panic, fatal, error, warn, info, debug, trace\n", args[0]) return fmt.Errorf("unknown log level: %s. Available levels are: panic, fatal, error, warn, info, debug, trace\n", args[0])
} }
@ -102,34 +103,13 @@ func setLogLevel(cmd *cobra.Command, args []string) error {
return nil return nil
} }
func parseLogLevel(level string) proto.LogLevel {
switch strings.ToLower(level) {
case "panic":
return proto.LogLevel_PANIC
case "fatal":
return proto.LogLevel_FATAL
case "error":
return proto.LogLevel_ERROR
case "warn":
return proto.LogLevel_WARN
case "info":
return proto.LogLevel_INFO
case "debug":
return proto.LogLevel_DEBUG
case "trace":
return proto.LogLevel_TRACE
default:
return proto.LogLevel_UNKNOWN
}
}
func runForDuration(cmd *cobra.Command, args []string) error { func runForDuration(cmd *cobra.Command, args []string) error {
duration, err := time.ParseDuration(args[0]) duration, err := time.ParseDuration(args[0])
if err != nil { if err != nil {
return fmt.Errorf("invalid duration format: %v", err) return fmt.Errorf("invalid duration format: %v", err)
} }
conn, err := getClient(cmd.Context()) conn, err := getClient(cmd)
if err != nil { if err != nil {
return err return err
} }
@ -137,18 +117,33 @@ func runForDuration(cmd *cobra.Command, args []string) error {
client := proto.NewDaemonServiceClient(conn) client := proto.NewDaemonServiceClient(conn)
stat, err := client.Status(cmd.Context(), &proto.StatusRequest{})
if err != nil {
return fmt.Errorf("failed to get status: %v", status.Convert(err).Message())
}
restoreUp := stat.Status == string(internal.StatusConnected) || stat.Status == string(internal.StatusConnecting)
initialLogLevel, err := client.GetLogLevel(cmd.Context(), &proto.GetLogLevelRequest{})
if err != nil {
return fmt.Errorf("failed to get log level: %v", status.Convert(err).Message())
}
if _, err := client.Down(cmd.Context(), &proto.DownRequest{}); err != nil { if _, err := client.Down(cmd.Context(), &proto.DownRequest{}); err != nil {
return fmt.Errorf("failed to down: %v", status.Convert(err).Message()) return fmt.Errorf("failed to down: %v", status.Convert(err).Message())
} }
cmd.Println("Netbird down") cmd.Println("Netbird down")
initialLevelTrace := initialLogLevel.GetLevel() >= proto.LogLevel_TRACE
if !initialLevelTrace {
_, err = client.SetLogLevel(cmd.Context(), &proto.SetLogLevelRequest{ _, err = client.SetLogLevel(cmd.Context(), &proto.SetLogLevelRequest{
Level: proto.LogLevel_TRACE, Level: proto.LogLevel_TRACE,
}) })
if err != nil { if err != nil {
return fmt.Errorf("failed to set log level to trace: %v", status.Convert(err).Message()) return fmt.Errorf("failed to set log level to TRACE: %v", status.Convert(err).Message())
} }
cmd.Println("Log level set to trace.") cmd.Println("Log level set to trace.")
}
time.Sleep(1 * time.Second) time.Sleep(1 * time.Second)
@ -175,10 +170,22 @@ func runForDuration(cmd *cobra.Command, args []string) error {
} }
cmd.Println("Netbird down") cmd.Println("Netbird down")
// TODO reset log level
time.Sleep(1 * time.Second) time.Sleep(1 * time.Second)
if restoreUp {
if _, err := client.Up(cmd.Context(), &proto.UpRequest{}); err != nil {
return fmt.Errorf("failed to up: %v", status.Convert(err).Message())
}
cmd.Println("Netbird up")
}
if !initialLevelTrace {
if _, err := client.SetLogLevel(cmd.Context(), &proto.SetLogLevelRequest{Level: initialLogLevel.GetLevel()}); err != nil {
return fmt.Errorf("failed to restore log level: %v", status.Convert(err).Message())
}
cmd.Println("Log level restored to", initialLogLevel.GetLevel())
}
cmd.Println("Creating debug bundle...") cmd.Println("Creating debug bundle...")
resp, err := client.DebugBundle(cmd.Context(), &proto.DebugBundleRequest{ resp, err := client.DebugBundle(cmd.Context(), &proto.DebugBundleRequest{

View File

@ -2,9 +2,10 @@ package cmd
import ( import (
"context" "context"
"github.com/netbirdio/netbird/util"
"time" "time"
"github.com/netbirdio/netbird/util"
log "github.com/sirupsen/logrus" log "github.com/sirupsen/logrus"
"github.com/spf13/cobra" "github.com/spf13/cobra"

View File

@ -353,8 +353,11 @@ func migrateToNetbird(oldPath, newPath string) bool {
return true return true
} }
func getClient(ctx context.Context) (*grpc.ClientConn, error) { func getClient(cmd *cobra.Command) (*grpc.ClientConn, error) {
conn, err := DialClientGRPCServer(ctx, daemonAddr) SetFlagsFromEnvVars(rootCmd)
cmd.SetOut(cmd.OutOrStdout())
conn, err := DialClientGRPCServer(cmd.Context(), daemonAddr)
if err != nil { if err != nil {
return nil, fmt.Errorf("failed to connect to daemon error: %v\n"+ return nil, fmt.Errorf("failed to connect to daemon error: %v\n"+
"If the daemon is not running please run: "+ "If the daemon is not running please run: "+

View File

@ -49,7 +49,7 @@ func init() {
} }
func routesList(cmd *cobra.Command, _ []string) error { func routesList(cmd *cobra.Command, _ []string) error {
conn, err := getClient(cmd.Context()) conn, err := getClient(cmd)
if err != nil { if err != nil {
return err return err
} }
@ -79,7 +79,7 @@ func routesList(cmd *cobra.Command, _ []string) error {
} }
func routesSelect(cmd *cobra.Command, args []string) error { func routesSelect(cmd *cobra.Command, args []string) error {
conn, err := getClient(cmd.Context()) conn, err := getClient(cmd)
if err != nil { if err != nil {
return err return err
} }
@ -106,7 +106,7 @@ func routesSelect(cmd *cobra.Command, args []string) error {
} }
func routesDeselect(cmd *cobra.Command, args []string) error { func routesDeselect(cmd *cobra.Command, args []string) error {
conn, err := getClient(cmd.Context()) conn, err := getClient(cmd)
if err != nil { if err != nil {
return err return err
} }

View File

@ -42,20 +42,20 @@ func NewFirewall(context context.Context, iface IFaceMapper) (firewall.Manager,
switch check() { switch check() {
case IPTABLES: case IPTABLES:
log.Debug("creating an iptables firewall manager") log.Info("creating an iptables firewall manager")
fm, errFw = nbiptables.Create(context, iface) fm, errFw = nbiptables.Create(context, iface)
if errFw != nil { if errFw != nil {
log.Errorf("failed to create iptables manager: %s", errFw) log.Errorf("failed to create iptables manager: %s", errFw)
} }
case NFTABLES: case NFTABLES:
log.Debug("creating an nftables firewall manager") log.Info("creating an nftables firewall manager")
fm, errFw = nbnftables.Create(context, iface) fm, errFw = nbnftables.Create(context, iface)
if errFw != nil { if errFw != nil {
log.Errorf("failed to create nftables manager: %s", errFw) log.Errorf("failed to create nftables manager: %s", errFw)
} }
default: default:
errFw = fmt.Errorf("no firewall manager found") errFw = fmt.Errorf("no firewall manager found")
log.Debug("no firewall manager found, try to use userspace packet filtering firewall") log.Info("no firewall manager found, trying to use userspace packet filtering firewall")
} }
if iface.IsUserspaceBind() { if iface.IsUserspaceBind() {
@ -85,16 +85,58 @@ func NewFirewall(context context.Context, iface IFaceMapper) (firewall.Manager,
// check returns the firewall type based on common lib checks. It returns UNKNOWN if no firewall is found. // check returns the firewall type based on common lib checks. It returns UNKNOWN if no firewall is found.
func check() FWType { func check() FWType {
useIPTABLES := false
var iptablesChains []string
ip, err := iptables.NewWithProtocol(iptables.ProtocolIPv4)
if err == nil && isIptablesClientAvailable(ip) {
major, minor, _ := ip.GetIptablesVersion()
// use iptables when its version is lower than 1.8.0 which doesn't work well with our nftables manager
if major < 1 || (major == 1 && minor < 8) {
return IPTABLES
}
useIPTABLES = true
iptablesChains, err = ip.ListChains("filter")
if err != nil {
log.Errorf("failed to list iptables chains: %s", err)
useIPTABLES = false
}
}
nf := nftables.Conn{} nf := nftables.Conn{}
if _, err := nf.ListChains(); err == nil && os.Getenv(SKIP_NFTABLES_ENV) != "true" { if chains, err := nf.ListChains(); err == nil && os.Getenv(SKIP_NFTABLES_ENV) != "true" {
if !useIPTABLES {
return NFTABLES return NFTABLES
} }
ip, err := iptables.NewWithProtocol(iptables.ProtocolIPv4) // search for chains where table is filter
if err != nil { // if we find one, we assume that nftables manager can be used with iptables
return UNKNOWN for _, chain := range chains {
if chain.Table.Name == "filter" {
return NFTABLES
} }
if isIptablesClientAvailable(ip) { }
// check tables for the following constraints:
// 1. there is no chain in nftables for the filter table and there is at least one chain in iptables, we assume that nftables manager can not be used
// 2. there is no tables or more than one table, we assume that nftables manager can be used
// 3. there is only one table and its name is filter, we assume that nftables manager can not be used, since there was no chain in it
// 4. if we find an error we log and continue with iptables check
nbTablesList, err := nf.ListTables()
switch {
case err == nil && len(iptablesChains) > 0:
return IPTABLES
case err == nil && len(nbTablesList) != 1:
return NFTABLES
case err == nil && len(nbTablesList) == 1 && nbTablesList[0].Name == "filter":
return IPTABLES
case err != nil:
log.Errorf("failed to list nftables tables on fw manager discovery: %s", err)
}
}
if useIPTABLES {
return IPTABLES return IPTABLES
} }

View File

@ -4,6 +4,7 @@ import (
"context" "context"
"errors" "errors"
"fmt" "fmt"
"net"
"runtime" "runtime"
"runtime/debug" "runtime/debug"
"strings" "strings"
@ -330,6 +331,15 @@ func createEngineConfig(key wgtypes.Key, config *Config, peerConfig *mgmProto.Pe
engineConf.PreSharedKey = &preSharedKey engineConf.PreSharedKey = &preSharedKey
} }
port, err := freePort(config.WgPort)
if err != nil {
return nil, err
}
if port != config.WgPort {
log.Infof("using %d as wireguard port: %d is in use", port, config.WgPort)
}
engineConf.WgPort = port
return engineConf, nil return engineConf, nil
} }
@ -379,3 +389,20 @@ func statusRecorderToSignalConnStateNotifier(statusRecorder *peer.Status) signal
notifier, _ := sri.(signal.ConnStateNotifier) notifier, _ := sri.(signal.ConnStateNotifier)
return notifier return notifier
} }
func freePort(start int) (int, error) {
addr := net.UDPAddr{}
if start == 0 {
start = iface.DefaultWgPort
}
for x := start; x <= 65535; x++ {
addr.Port = x
conn, err := net.ListenUDP("udp", &addr)
if err != nil {
continue
}
conn.Close()
return x, nil
}
return 0, errors.New("no free ports")
}

View File

@ -0,0 +1,57 @@
package internal
import (
"net"
"testing"
)
func Test_freePort(t *testing.T) {
tests := []struct {
name string
port int
want int
wantErr bool
}{
{
name: "available",
port: 51820,
want: 51820,
wantErr: false,
},
{
name: "notavailable",
port: 51830,
want: 51831,
wantErr: false,
},
{
name: "noports",
port: 65535,
want: 0,
wantErr: true,
},
}
for _, tt := range tests {
c1, err := net.ListenUDP("udp", &net.UDPAddr{Port: 51830})
if err != nil {
t.Errorf("freePort error = %v", err)
}
c2, err := net.ListenUDP("udp", &net.UDPAddr{Port: 65535})
if err != nil {
t.Errorf("freePort error = %v", err)
}
t.Run(tt.name, func(t *testing.T) {
got, err := freePort(tt.port)
if (err != nil) != tt.wantErr {
t.Errorf("freePort() error = %v, wantErr %v", err, tt.wantErr)
return
}
if got != tt.want {
t.Errorf("freePort() = %v, want %v", got, tt.want)
}
})
c1.Close()
c2.Close()
}
}

View File

@ -4,6 +4,7 @@ import (
"context" "context"
"errors" "errors"
"fmt" "fmt"
"maps"
"math/rand" "math/rand"
"net" "net"
"net/netip" "net/netip"
@ -118,6 +119,7 @@ type Engine struct {
// clientRoutes is the most recent list of clientRoutes received from the Management Service // clientRoutes is the most recent list of clientRoutes received from the Management Service
clientRoutes route.HAMap clientRoutes route.HAMap
clientRoutesMu sync.RWMutex
clientCtx context.Context clientCtx context.Context
clientCancel context.CancelFunc clientCancel context.CancelFunc
@ -150,6 +152,8 @@ type Engine struct {
signalProbe *Probe signalProbe *Probe
relayProbe *Probe relayProbe *Probe
wgProbe *Probe wgProbe *Probe
wgConnWorker sync.WaitGroup
} }
// Peer is an instance of the Connection Peer // Peer is an instance of the Connection Peer
@ -238,13 +242,16 @@ func (e *Engine) Stop() error {
return err return err
} }
e.clientRoutesMu.Lock()
e.clientRoutes = nil e.clientRoutes = nil
e.clientRoutesMu.Unlock()
// very ugly but we want to remove peers from the WireGuard interface first before removing interface. // very ugly but we want to remove peers from the WireGuard interface first before removing interface.
// Removing peers happens in the conn.Close() asynchronously // Removing peers happens in the conn.Close() asynchronously
time.Sleep(500 * time.Millisecond) time.Sleep(500 * time.Millisecond)
e.close() e.close()
e.wgConnWorker.Wait()
log.Infof("stopped Netbird Engine") log.Infof("stopped Netbird Engine")
return nil return nil
} }
@ -735,7 +742,9 @@ func (e *Engine) updateNetworkMap(networkMap *mgmProto.NetworkMap) error {
log.Errorf("failed to update clientRoutes, err: %v", err) log.Errorf("failed to update clientRoutes, err: %v", err)
} }
e.clientRoutesMu.Lock()
e.clientRoutes = clientRoutes e.clientRoutes = clientRoutes
e.clientRoutesMu.Unlock()
protoDNSConfig := networkMap.GetDNSConfig() protoDNSConfig := networkMap.GetDNSConfig()
if protoDNSConfig == nil { if protoDNSConfig == nil {
@ -869,18 +878,25 @@ func (e *Engine) addNewPeer(peerConfig *mgmProto.RemotePeerConfig) error {
log.Warnf("error adding peer %s to status recorder, got error: %v", peerKey, err) log.Warnf("error adding peer %s to status recorder, got error: %v", peerKey, err)
} }
e.wgConnWorker.Add(1)
go e.connWorker(conn, peerKey) go e.connWorker(conn, peerKey)
} }
return nil return nil
} }
func (e *Engine) connWorker(conn *peer.Conn, peerKey string) { func (e *Engine) connWorker(conn *peer.Conn, peerKey string) {
defer e.wgConnWorker.Done()
for { for {
// randomize starting time a bit // randomize starting time a bit
min := 500 min := 500
max := 2000 max := 2000
time.Sleep(time.Duration(rand.Intn(max-min)+min) * time.Millisecond) duration := time.Duration(rand.Intn(max-min)+min) * time.Millisecond
select {
case <-e.ctx.Done():
return
case <-time.After(duration):
}
// if peer has been removed -> give up // if peer has been removed -> give up
if !e.peerExists(peerKey) { if !e.peerExists(peerKey) {
@ -967,7 +983,6 @@ func (e *Engine) createPeerConn(pubKey string, allowedIPs string) (*peer.Conn, e
WgConfig: wgConfig, WgConfig: wgConfig,
LocalWgPort: e.config.WgPort, LocalWgPort: e.config.WgPort,
NATExternalIPs: e.parseNATExternalIPMappings(), NATExternalIPs: e.parseNATExternalIPMappings(),
UserspaceBind: e.wgInterface.IsUserspaceBind(),
RosenpassPubKey: e.getRosenpassPubKey(), RosenpassPubKey: e.getRosenpassPubKey(),
RosenpassAddr: e.getRosenpassAddr(), RosenpassAddr: e.getRosenpassAddr(),
} }
@ -1030,8 +1045,6 @@ func (e *Engine) receiveSignalEvents() {
return err return err
} }
conn.RegisterProtoSupportMeta(msg.Body.GetFeaturesSupported())
var rosenpassPubKey []byte var rosenpassPubKey []byte
rosenpassAddr := "" rosenpassAddr := ""
if msg.GetBody().GetRosenpassConfig() != nil { if msg.GetBody().GetRosenpassConfig() != nil {
@ -1054,8 +1067,6 @@ func (e *Engine) receiveSignalEvents() {
return err return err
} }
conn.RegisterProtoSupportMeta(msg.GetBody().GetFeaturesSupported())
var rosenpassPubKey []byte var rosenpassPubKey []byte
rosenpassAddr := "" rosenpassAddr := ""
if msg.GetBody().GetRosenpassConfig() != nil { if msg.GetBody().GetRosenpassConfig() != nil {
@ -1078,7 +1089,8 @@ func (e *Engine) receiveSignalEvents() {
log.Errorf("failed on parsing remote candidate %s -> %s", candidate, err) log.Errorf("failed on parsing remote candidate %s -> %s", candidate, err)
return err return err
} }
conn.OnRemoteCandidate(candidate)
conn.OnRemoteCandidate(candidate, e.GetClientRoutes())
case sProto.Body_MODE: case sProto.Body_MODE:
} }
@ -1272,11 +1284,17 @@ func (e *Engine) newDnsServer() ([]*route.Route, dns.Server, error) {
// GetClientRoutes returns the current routes from the route map // GetClientRoutes returns the current routes from the route map
func (e *Engine) GetClientRoutes() route.HAMap { func (e *Engine) GetClientRoutes() route.HAMap {
return e.clientRoutes e.clientRoutesMu.RLock()
defer e.clientRoutesMu.RUnlock()
return maps.Clone(e.clientRoutes)
} }
// GetClientRoutesWithNetID returns the current routes from the route map, but the keys consist of the network ID only // GetClientRoutesWithNetID returns the current routes from the route map, but the keys consist of the network ID only
func (e *Engine) GetClientRoutesWithNetID() map[route.NetID][]*route.Route { func (e *Engine) GetClientRoutesWithNetID() map[route.NetID][]*route.Route {
e.clientRoutesMu.RLock()
defer e.clientRoutesMu.RUnlock()
routes := make(map[route.NetID][]*route.Route, len(e.clientRoutes)) routes := make(map[route.NetID][]*route.Route, len(e.clientRoutes))
for id, v := range e.clientRoutes { for id, v := range e.clientRoutes {
routes[id.NetID()] = v routes[id.NetID()] = v

View File

@ -229,6 +229,7 @@ func TestEngine_UpdateNetworkMap(t *testing.T) {
t.Fatal(err) t.Fatal(err)
} }
engine.udpMux = bind.NewUniversalUDPMuxDefault(bind.UniversalUDPMuxParams{UDPConn: conn}) engine.udpMux = bind.NewUniversalUDPMuxDefault(bind.UniversalUDPMuxParams{UDPConn: conn})
engine.ctx = ctx
type testCase struct { type testCase struct {
name string name string
@ -408,6 +409,7 @@ func TestEngine_Sync(t *testing.T) {
WgPrivateKey: key, WgPrivateKey: key,
WgPort: 33100, WgPort: 33100,
}, MobileDependency{}, peer.NewRecorder("https://mgm")) }, MobileDependency{}, peer.NewRecorder("https://mgm"))
engine.ctx = ctx
engine.dnsServer = &dns.MockServer{ engine.dnsServer = &dns.MockServer{
UpdateDNSServerFunc: func(serial uint64, update nbdns.Config) error { return nil }, UpdateDNSServerFunc: func(serial uint64, update nbdns.Config) error { return nil },
@ -566,6 +568,7 @@ func TestEngine_UpdateNetworkMapWithRoutes(t *testing.T) {
WgPrivateKey: key, WgPrivateKey: key,
WgPort: 33100, WgPort: 33100,
}, MobileDependency{}, peer.NewRecorder("https://mgm")) }, MobileDependency{}, peer.NewRecorder("https://mgm"))
engine.ctx = ctx
newNet, err := stdnet.NewNet() newNet, err := stdnet.NewNet()
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
@ -735,6 +738,8 @@ func TestEngine_UpdateNetworkMapWithDNSUpdate(t *testing.T) {
WgPrivateKey: key, WgPrivateKey: key,
WgPort: 33100, WgPort: 33100,
}, MobileDependency{}, peer.NewRecorder("https://mgm")) }, MobileDependency{}, peer.NewRecorder("https://mgm"))
engine.ctx = ctx
newNet, err := stdnet.NewNet() newNet, err := stdnet.NewNet()
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
@ -1003,7 +1008,9 @@ func createEngine(ctx context.Context, cancel context.CancelFunc, setupKey strin
WgPort: wgPort, WgPort: wgPort,
} }
return NewEngine(ctx, cancel, signalClient, mgmtClient, conf, MobileDependency{}, peer.NewRecorder("https://mgm")), nil e, err := NewEngine(ctx, cancel, signalClient, mgmtClient, conf, MobileDependency{}, peer.NewRecorder("https://mgm")), nil
e.ctx = ctx
return e, err
} }
func startSignal() (*grpc.Server, string, error) { func startSignal() (*grpc.Server, string, error) {

View File

@ -4,6 +4,7 @@ import (
"context" "context"
"fmt" "fmt"
"net" "net"
"net/netip"
"runtime" "runtime"
"strings" "strings"
"sync" "sync"
@ -18,7 +19,7 @@ import (
"github.com/netbirdio/netbird/client/internal/wgproxy" "github.com/netbirdio/netbird/client/internal/wgproxy"
"github.com/netbirdio/netbird/iface" "github.com/netbirdio/netbird/iface"
"github.com/netbirdio/netbird/iface/bind" "github.com/netbirdio/netbird/iface/bind"
signal "github.com/netbirdio/netbird/signal/client" "github.com/netbirdio/netbird/route"
sProto "github.com/netbirdio/netbird/signal/proto" sProto "github.com/netbirdio/netbird/signal/proto"
nbnet "github.com/netbirdio/netbird/util/net" nbnet "github.com/netbirdio/netbird/util/net"
"github.com/netbirdio/netbird/version" "github.com/netbirdio/netbird/version"
@ -68,9 +69,6 @@ type ConnConfig struct {
NATExternalIPs []string NATExternalIPs []string
// UsesBind indicates whether the WireGuard interface is userspace and uses bind.ICEBind
UserspaceBind bool
// RosenpassPubKey is this peer's Rosenpass public key // RosenpassPubKey is this peer's Rosenpass public key
RosenpassPubKey []byte RosenpassPubKey []byte
// RosenpassPubKey is this peer's RosenpassAddr server address (IP:port) // RosenpassPubKey is this peer's RosenpassAddr server address (IP:port)
@ -133,32 +131,15 @@ type Conn struct {
wgProxyFactory *wgproxy.Factory wgProxyFactory *wgproxy.Factory
wgProxy wgproxy.Proxy wgProxy wgproxy.Proxy
remoteModeCh chan ModeMessage
meta meta
adapter iface.TunAdapter adapter iface.TunAdapter
iFaceDiscover stdnet.ExternalIFaceDiscover iFaceDiscover stdnet.ExternalIFaceDiscover
sentExtraSrflx bool sentExtraSrflx bool
remoteEndpoint *net.UDPAddr
remoteConn *ice.Conn
connID nbnet.ConnectionID connID nbnet.ConnectionID
beforeAddPeerHooks []BeforeAddPeerHookFunc beforeAddPeerHooks []BeforeAddPeerHookFunc
afterRemovePeerHooks []AfterRemovePeerHookFunc afterRemovePeerHooks []AfterRemovePeerHookFunc
} }
// meta holds meta information about a connection
type meta struct {
protoSupport signal.FeaturesSupport
}
// ModeMessage represents a connection mode chosen by the peer
type ModeMessage struct {
// Direct indicates that it decided to use a direct connection
Direct bool
}
// GetConf returns the connection config // GetConf returns the connection config
func (conn *Conn) GetConf() ConnConfig { func (conn *Conn) GetConf() ConnConfig {
return conn.config return conn.config
@ -185,7 +166,6 @@ func NewConn(config ConnConfig, statusRecorder *Status, wgProxyFactory *wgproxy.
remoteOffersCh: make(chan OfferAnswer), remoteOffersCh: make(chan OfferAnswer),
remoteAnswerCh: make(chan OfferAnswer), remoteAnswerCh: make(chan OfferAnswer),
statusRecorder: statusRecorder, statusRecorder: statusRecorder,
remoteModeCh: make(chan ModeMessage, 1),
wgProxyFactory: wgProxyFactory, wgProxyFactory: wgProxyFactory,
adapter: adapter, adapter: adapter,
iFaceDiscover: iFaceDiscover, iFaceDiscover: iFaceDiscover,
@ -353,7 +333,7 @@ func (conn *Conn) Open(ctx context.Context) error {
err = conn.agent.GatherCandidates() err = conn.agent.GatherCandidates()
if err != nil { if err != nil {
return err return fmt.Errorf("gather candidates: %v", err)
} }
// will block until connection succeeded // will block until connection succeeded
@ -370,14 +350,12 @@ func (conn *Conn) Open(ctx context.Context) error {
return err return err
} }
// dynamically set remote WireGuard port is other side specified a different one from the default one // dynamically set remote WireGuard port if other side specified a different one from the default one
remoteWgPort := iface.DefaultWgPort remoteWgPort := iface.DefaultWgPort
if remoteOfferAnswer.WgListenPort != 0 { if remoteOfferAnswer.WgListenPort != 0 {
remoteWgPort = remoteOfferAnswer.WgListenPort remoteWgPort = remoteOfferAnswer.WgListenPort
} }
conn.remoteConn = remoteConn
// the ice connection has been established successfully so we are ready to start the proxy // the ice connection has been established successfully so we are ready to start the proxy
remoteAddr, err := conn.configureConnection(remoteConn, remoteWgPort, remoteOfferAnswer.RosenpassPubKey, remoteAddr, err := conn.configureConnection(remoteConn, remoteWgPort, remoteOfferAnswer.RosenpassPubKey,
remoteOfferAnswer.RosenpassAddr) remoteOfferAnswer.RosenpassAddr)
@ -435,7 +413,6 @@ func (conn *Conn) configureConnection(remoteConn net.Conn, remoteWgPort int, rem
} }
endpointUdpAddr, _ := net.ResolveUDPAddr(endpoint.Network(), endpoint.String()) endpointUdpAddr, _ := net.ResolveUDPAddr(endpoint.Network(), endpoint.String())
conn.remoteEndpoint = endpointUdpAddr
log.Debugf("Conn resolved IP for %s: %s", endpoint, endpointUdpAddr.IP) log.Debugf("Conn resolved IP for %s: %s", endpoint, endpointUdpAddr.IP)
conn.connID = nbnet.GenerateConnID() conn.connID = nbnet.GenerateConnID()
@ -621,7 +598,11 @@ func (conn *Conn) SetSendSignalMessage(handler func(message *sProto.Message) err
// onICECandidate is a callback attached to an ICE Agent to receive new local connection candidates // onICECandidate is a callback attached to an ICE Agent to receive new local connection candidates
// and then signals them to the remote peer // and then signals them to the remote peer
func (conn *Conn) onICECandidate(candidate ice.Candidate) { func (conn *Conn) onICECandidate(candidate ice.Candidate) {
if candidate != nil { // nil means candidate gathering has been ended
if candidate == nil {
return
}
// TODO: reported port is incorrect for CandidateTypeHost, makes understanding ICE use via logs confusing as port is ignored // TODO: reported port is incorrect for CandidateTypeHost, makes understanding ICE use via logs confusing as port is ignored
log.Debugf("discovered local candidate %s", candidate.String()) log.Debugf("discovered local candidate %s", candidate.String())
go func() { go func() {
@ -629,33 +610,28 @@ func (conn *Conn) onICECandidate(candidate ice.Candidate) {
if err != nil { if err != nil {
log.Errorf("failed signaling candidate to the remote peer %s %s", conn.config.Key, err) log.Errorf("failed signaling candidate to the remote peer %s %s", conn.config.Key, err)
} }
}()
if !conn.shouldSendExtraSrflxCandidate(candidate) {
return
}
// sends an extra server reflexive candidate to the remote peer with our related port (usually the wireguard port) // sends an extra server reflexive candidate to the remote peer with our related port (usually the wireguard port)
// this is useful when network has an existing port forwarding rule for the wireguard port and this peer // this is useful when network has an existing port forwarding rule for the wireguard port and this peer
if !conn.sentExtraSrflx && candidate.Type() == ice.CandidateTypeServerReflexive && candidate.Port() != candidate.RelatedAddress().Port { extraSrflx, err := extraSrflxCandidate(candidate)
relatedAdd := candidate.RelatedAddress()
extraSrflx, err := ice.NewCandidateServerReflexive(&ice.CandidateServerReflexiveConfig{
Network: candidate.NetworkType().String(),
Address: candidate.Address(),
Port: relatedAdd.Port,
Component: candidate.Component(),
RelAddr: relatedAdd.Address,
RelPort: relatedAdd.Port,
})
if err != nil { if err != nil {
log.Errorf("failed creating extra server reflexive candidate %s", err) log.Errorf("failed creating extra server reflexive candidate %s", err)
return return
} }
conn.sentExtraSrflx = true
go func() {
err = conn.signalCandidate(extraSrflx) err = conn.signalCandidate(extraSrflx)
if err != nil { if err != nil {
log.Errorf("failed signaling the extra server reflexive candidate to the remote peer %s: %s", conn.config.Key, err) log.Errorf("failed signaling the extra server reflexive candidate to the remote peer %s: %s", conn.config.Key, err)
return
}
conn.sentExtraSrflx = true
} }
}() }()
} }
}
func (conn *Conn) onICESelectedCandidatePair(c1 ice.Candidate, c2 ice.Candidate) { func (conn *Conn) onICESelectedCandidatePair(c1 ice.Candidate, c2 ice.Candidate) {
log.Debugf("selected candidate pair [local <-> remote] -> [%s <-> %s], peer %s", c1.String(), c2.String(), log.Debugf("selected candidate pair [local <-> remote] -> [%s <-> %s], peer %s", c1.String(), c2.String(),
@ -779,7 +755,7 @@ func (conn *Conn) OnRemoteAnswer(answer OfferAnswer) bool {
} }
// OnRemoteCandidate Handles ICE connection Candidate provided by the remote peer. // OnRemoteCandidate Handles ICE connection Candidate provided by the remote peer.
func (conn *Conn) OnRemoteCandidate(candidate ice.Candidate) { func (conn *Conn) OnRemoteCandidate(candidate ice.Candidate, haRoutes route.HAMap) {
log.Debugf("OnRemoteCandidate from peer %s -> %s", conn.config.Key, candidate.String()) log.Debugf("OnRemoteCandidate from peer %s -> %s", conn.config.Key, candidate.String())
go func() { go func() {
conn.mu.Lock() conn.mu.Lock()
@ -789,6 +765,10 @@ func (conn *Conn) OnRemoteCandidate(candidate ice.Candidate) {
return return
} }
if candidateViaRoutes(candidate, haRoutes) {
return
}
err := conn.agent.AddRemoteCandidate(candidate) err := conn.agent.AddRemoteCandidate(candidate)
if err != nil { if err != nil {
log.Errorf("error while handling remote candidate from peer %s", conn.config.Key) log.Errorf("error while handling remote candidate from peer %s", conn.config.Key)
@ -801,8 +781,49 @@ func (conn *Conn) GetKey() string {
return conn.config.Key return conn.config.Key
} }
// RegisterProtoSupportMeta register supported proto message in the connection metadata func (conn *Conn) shouldSendExtraSrflxCandidate(candidate ice.Candidate) bool {
func (conn *Conn) RegisterProtoSupportMeta(support []uint32) { if !conn.sentExtraSrflx && candidate.Type() == ice.CandidateTypeServerReflexive && candidate.Port() != candidate.RelatedAddress().Port {
protoSupport := signal.ParseFeaturesSupported(support) return true
conn.meta.protoSupport = protoSupport }
return false
}
func extraSrflxCandidate(candidate ice.Candidate) (*ice.CandidateServerReflexive, error) {
relatedAdd := candidate.RelatedAddress()
return ice.NewCandidateServerReflexive(&ice.CandidateServerReflexiveConfig{
Network: candidate.NetworkType().String(),
Address: candidate.Address(),
Port: relatedAdd.Port,
Component: candidate.Component(),
RelAddr: relatedAdd.Address,
RelPort: relatedAdd.Port,
})
}
func candidateViaRoutes(candidate ice.Candidate, clientRoutes route.HAMap) bool {
var routePrefixes []netip.Prefix
for _, routes := range clientRoutes {
if len(routes) > 0 && routes[0] != nil {
routePrefixes = append(routePrefixes, routes[0].Network)
}
}
addr, err := netip.ParseAddr(candidate.Address())
if err != nil {
log.Errorf("Failed to parse IP address %s: %v", candidate.Address(), err)
return false
}
for _, prefix := range routePrefixes {
// default route is
if prefix.Bits() == 0 {
continue
}
if prefix.Contains(addr) {
log.Debugf("Ignoring candidate [%s], its address is part of routed network %s", candidate.String(), prefix)
return true
}
}
return false
} }

View File

@ -170,7 +170,7 @@ func ProbeAll(
var wg sync.WaitGroup var wg sync.WaitGroup
for i, uri := range relays { for i, uri := range relays {
ctx, cancel := context.WithTimeout(ctx, 1*time.Second) ctx, cancel := context.WithTimeout(ctx, 2*time.Second)
defer cancel() defer cancel()
wg.Add(1) wg.Add(1)

View File

@ -43,11 +43,6 @@ func routeCmd(action string, prefix netip.Prefix, nexthop netip.Addr, intf *net.
} }
if prefix.Addr().Is6() { if prefix.Addr().Is6() {
inet = "-inet6" inet = "-inet6"
// Special case for IPv6 split default route, pointing to the wg interface fails
// TODO: Remove once we have IPv6 support on the interface
if prefix.Bits() == 1 {
intf = &net.Interface{Name: "lo0"}
}
} }
args := []string{"-n", action, inet, network} args := []string{"-n", action, inet, network}

View File

@ -1,7 +1,7 @@
<!DOCTYPE html> <!DOCTYPE html>
<html> <html lang="en">
<head> <head>
<meta name="viewport" content="width=device-width, initial-scale=1"/> <meta content="width=device-width, initial-scale=1" name="viewport"/>
<style> <style>
body { body {
display: flex; display: flex;
@ -50,16 +50,17 @@
color: black; color: black;
} }
</style> </style>
<title>NetBird Login Successful</title>
</head> </head>
<body> <body>
<div class="container"> <div class="container">
<div class="logo"> <div class="logo">
<img src="https://img.mailinblue.com/6211297/images/content_library/original/64bd4ce82e1ea753e439b6a2.png"> <img alt="netbird_logo" src="https://img.mailinblue.com/6211297/images/content_library/original/64bd4ce82e1ea753e439b6a2.png">
</div> </div>
<br> <br>
{{ if .Error }} {{ if .Error }}
<svg xmlns="http://www.w3.org/2000/svg" height="50" viewBox="0 0 100 100"> <svg height="50" viewBox="0 0 100 100" xmlns="http://www.w3.org/2000/svg">
<circle cx="50" cy="50" r="45" fill="none" stroke="red" stroke-width="3"/> <circle cx="50" cy="50" fill="none" r="45" stroke="red" stroke-width="3"/>
<path d="M30 30 L70 70 M30 70 L70 30" fill="none" stroke="red" stroke-width="3"/> <path d="M30 30 L70 70 M30 70 L70 30" fill="none" stroke="red" stroke-width="3"/>
</svg> </svg>
<div class="content"> <div class="content">
@ -69,8 +70,8 @@
{{ .Error }}. {{ .Error }}.
</div> </div>
{{ else }} {{ else }}
<svg xmlns="http://www.w3.org/2000/svg" height="50" viewBox="0 0 100 100"> <svg height="50" viewBox="0 0 100 100" xmlns="http://www.w3.org/2000/svg">
<circle cx="50" cy="50" r="45" fill="none" stroke="#5cb85c" stroke-width="3"/> <circle cx="50" cy="50" fill="none" r="45" stroke="#5cb85c" stroke-width="3"/>
<path d="M30 50 L45 65 L70 35" fill="none" stroke="#5cb85c" stroke-width="5"/> <path d="M30 50 L45 65 L70 35" fill="none" stroke="#5cb85c" stroke-width="5"/>
</svg> </svg>
<div class="content"> <div class="content">

View File

@ -1806,6 +1806,91 @@ func (x *DebugBundleResponse) GetPath() string {
return "" return ""
} }
type GetLogLevelRequest struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
}
func (x *GetLogLevelRequest) Reset() {
*x = GetLogLevelRequest{}
if protoimpl.UnsafeEnabled {
mi := &file_daemon_proto_msgTypes[26]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *GetLogLevelRequest) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*GetLogLevelRequest) ProtoMessage() {}
func (x *GetLogLevelRequest) ProtoReflect() protoreflect.Message {
mi := &file_daemon_proto_msgTypes[26]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use GetLogLevelRequest.ProtoReflect.Descriptor instead.
func (*GetLogLevelRequest) Descriptor() ([]byte, []int) {
return file_daemon_proto_rawDescGZIP(), []int{26}
}
type GetLogLevelResponse struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
Level LogLevel `protobuf:"varint,1,opt,name=level,proto3,enum=daemon.LogLevel" json:"level,omitempty"`
}
func (x *GetLogLevelResponse) Reset() {
*x = GetLogLevelResponse{}
if protoimpl.UnsafeEnabled {
mi := &file_daemon_proto_msgTypes[27]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *GetLogLevelResponse) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*GetLogLevelResponse) ProtoMessage() {}
func (x *GetLogLevelResponse) ProtoReflect() protoreflect.Message {
mi := &file_daemon_proto_msgTypes[27]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use GetLogLevelResponse.ProtoReflect.Descriptor instead.
func (*GetLogLevelResponse) Descriptor() ([]byte, []int) {
return file_daemon_proto_rawDescGZIP(), []int{27}
}
func (x *GetLogLevelResponse) GetLevel() LogLevel {
if x != nil {
return x.Level
}
return LogLevel_UNKNOWN
}
type SetLogLevelRequest struct { type SetLogLevelRequest struct {
state protoimpl.MessageState state protoimpl.MessageState
sizeCache protoimpl.SizeCache sizeCache protoimpl.SizeCache
@ -1817,7 +1902,7 @@ type SetLogLevelRequest struct {
func (x *SetLogLevelRequest) Reset() { func (x *SetLogLevelRequest) Reset() {
*x = SetLogLevelRequest{} *x = SetLogLevelRequest{}
if protoimpl.UnsafeEnabled { if protoimpl.UnsafeEnabled {
mi := &file_daemon_proto_msgTypes[26] mi := &file_daemon_proto_msgTypes[28]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi) ms.StoreMessageInfo(mi)
} }
@ -1830,7 +1915,7 @@ func (x *SetLogLevelRequest) String() string {
func (*SetLogLevelRequest) ProtoMessage() {} func (*SetLogLevelRequest) ProtoMessage() {}
func (x *SetLogLevelRequest) ProtoReflect() protoreflect.Message { func (x *SetLogLevelRequest) ProtoReflect() protoreflect.Message {
mi := &file_daemon_proto_msgTypes[26] mi := &file_daemon_proto_msgTypes[28]
if protoimpl.UnsafeEnabled && x != nil { if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil { if ms.LoadMessageInfo() == nil {
@ -1843,7 +1928,7 @@ func (x *SetLogLevelRequest) ProtoReflect() protoreflect.Message {
// Deprecated: Use SetLogLevelRequest.ProtoReflect.Descriptor instead. // Deprecated: Use SetLogLevelRequest.ProtoReflect.Descriptor instead.
func (*SetLogLevelRequest) Descriptor() ([]byte, []int) { func (*SetLogLevelRequest) Descriptor() ([]byte, []int) {
return file_daemon_proto_rawDescGZIP(), []int{26} return file_daemon_proto_rawDescGZIP(), []int{28}
} }
func (x *SetLogLevelRequest) GetLevel() LogLevel { func (x *SetLogLevelRequest) GetLevel() LogLevel {
@ -1862,7 +1947,7 @@ type SetLogLevelResponse struct {
func (x *SetLogLevelResponse) Reset() { func (x *SetLogLevelResponse) Reset() {
*x = SetLogLevelResponse{} *x = SetLogLevelResponse{}
if protoimpl.UnsafeEnabled { if protoimpl.UnsafeEnabled {
mi := &file_daemon_proto_msgTypes[27] mi := &file_daemon_proto_msgTypes[29]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi) ms.StoreMessageInfo(mi)
} }
@ -1875,7 +1960,7 @@ func (x *SetLogLevelResponse) String() string {
func (*SetLogLevelResponse) ProtoMessage() {} func (*SetLogLevelResponse) ProtoMessage() {}
func (x *SetLogLevelResponse) ProtoReflect() protoreflect.Message { func (x *SetLogLevelResponse) ProtoReflect() protoreflect.Message {
mi := &file_daemon_proto_msgTypes[27] mi := &file_daemon_proto_msgTypes[29]
if protoimpl.UnsafeEnabled && x != nil { if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil { if ms.LoadMessageInfo() == nil {
@ -1888,7 +1973,7 @@ func (x *SetLogLevelResponse) ProtoReflect() protoreflect.Message {
// Deprecated: Use SetLogLevelResponse.ProtoReflect.Descriptor instead. // Deprecated: Use SetLogLevelResponse.ProtoReflect.Descriptor instead.
func (*SetLogLevelResponse) Descriptor() ([]byte, []int) { func (*SetLogLevelResponse) Descriptor() ([]byte, []int) {
return file_daemon_proto_rawDescGZIP(), []int{27} return file_daemon_proto_rawDescGZIP(), []int{29}
} }
var File_daemon_proto protoreflect.FileDescriptor var File_daemon_proto protoreflect.FileDescriptor
@ -2138,67 +2223,77 @@ var file_daemon_proto_rawDesc = []byte{
0x73, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x22, 0x73, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x22,
0x29, 0x0a, 0x13, 0x44, 0x65, 0x62, 0x75, 0x67, 0x42, 0x75, 0x6e, 0x64, 0x6c, 0x65, 0x52, 0x65, 0x29, 0x0a, 0x13, 0x44, 0x65, 0x62, 0x75, 0x67, 0x42, 0x75, 0x6e, 0x64, 0x6c, 0x65, 0x52, 0x65,
0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x12, 0x0a, 0x04, 0x70, 0x61, 0x74, 0x68, 0x18, 0x01, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x12, 0x0a, 0x04, 0x70, 0x61, 0x74, 0x68, 0x18, 0x01,
0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x70, 0x61, 0x74, 0x68, 0x22, 0x3c, 0x0a, 0x12, 0x53, 0x65, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x70, 0x61, 0x74, 0x68, 0x22, 0x14, 0x0a, 0x12, 0x47, 0x65,
0x74, 0x4c, 0x6f, 0x67, 0x4c, 0x65, 0x76, 0x65, 0x6c, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x74, 0x4c, 0x6f, 0x67, 0x4c, 0x65, 0x76, 0x65, 0x6c, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74,
0x12, 0x26, 0x0a, 0x05, 0x6c, 0x65, 0x76, 0x65, 0x6c, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x22, 0x3d, 0x0a, 0x13, 0x47, 0x65, 0x74, 0x4c, 0x6f, 0x67, 0x4c, 0x65, 0x76, 0x65, 0x6c, 0x52,
0x10, 0x2e, 0x64, 0x61, 0x65, 0x6d, 0x6f, 0x6e, 0x2e, 0x4c, 0x6f, 0x67, 0x4c, 0x65, 0x76, 0x65, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x26, 0x0a, 0x05, 0x6c, 0x65, 0x76, 0x65, 0x6c,
0x6c, 0x52, 0x05, 0x6c, 0x65, 0x76, 0x65, 0x6c, 0x22, 0x15, 0x0a, 0x13, 0x53, 0x65, 0x74, 0x4c, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x10, 0x2e, 0x64, 0x61, 0x65, 0x6d, 0x6f, 0x6e, 0x2e,
0x6f, 0x67, 0x4c, 0x65, 0x76, 0x65, 0x6c, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x2a, 0x4c, 0x6f, 0x67, 0x4c, 0x65, 0x76, 0x65, 0x6c, 0x52, 0x05, 0x6c, 0x65, 0x76, 0x65, 0x6c, 0x22,
0x62, 0x0a, 0x08, 0x4c, 0x6f, 0x67, 0x4c, 0x65, 0x76, 0x65, 0x6c, 0x12, 0x0b, 0x0a, 0x07, 0x55, 0x3c, 0x0a, 0x12, 0x53, 0x65, 0x74, 0x4c, 0x6f, 0x67, 0x4c, 0x65, 0x76, 0x65, 0x6c, 0x52, 0x65,
0x4e, 0x4b, 0x4e, 0x4f, 0x57, 0x4e, 0x10, 0x00, 0x12, 0x09, 0x0a, 0x05, 0x50, 0x41, 0x4e, 0x49, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x26, 0x0a, 0x05, 0x6c, 0x65, 0x76, 0x65, 0x6c, 0x18, 0x01,
0x43, 0x10, 0x01, 0x12, 0x09, 0x0a, 0x05, 0x46, 0x41, 0x54, 0x41, 0x4c, 0x10, 0x02, 0x12, 0x09, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x10, 0x2e, 0x64, 0x61, 0x65, 0x6d, 0x6f, 0x6e, 0x2e, 0x4c, 0x6f,
0x0a, 0x05, 0x45, 0x52, 0x52, 0x4f, 0x52, 0x10, 0x03, 0x12, 0x08, 0x0a, 0x04, 0x57, 0x41, 0x52, 0x67, 0x4c, 0x65, 0x76, 0x65, 0x6c, 0x52, 0x05, 0x6c, 0x65, 0x76, 0x65, 0x6c, 0x22, 0x15, 0x0a,
0x4e, 0x10, 0x04, 0x12, 0x08, 0x0a, 0x04, 0x49, 0x4e, 0x46, 0x4f, 0x10, 0x05, 0x12, 0x09, 0x0a, 0x13, 0x53, 0x65, 0x74, 0x4c, 0x6f, 0x67, 0x4c, 0x65, 0x76, 0x65, 0x6c, 0x52, 0x65, 0x73, 0x70,
0x05, 0x44, 0x45, 0x42, 0x55, 0x47, 0x10, 0x06, 0x12, 0x09, 0x0a, 0x05, 0x54, 0x52, 0x41, 0x43, 0x6f, 0x6e, 0x73, 0x65, 0x2a, 0x62, 0x0a, 0x08, 0x4c, 0x6f, 0x67, 0x4c, 0x65, 0x76, 0x65, 0x6c,
0x45, 0x10, 0x07, 0x32, 0xee, 0x05, 0x0a, 0x0d, 0x44, 0x61, 0x65, 0x6d, 0x6f, 0x6e, 0x53, 0x65, 0x12, 0x0b, 0x0a, 0x07, 0x55, 0x4e, 0x4b, 0x4e, 0x4f, 0x57, 0x4e, 0x10, 0x00, 0x12, 0x09, 0x0a,
0x72, 0x76, 0x69, 0x63, 0x65, 0x12, 0x36, 0x0a, 0x05, 0x4c, 0x6f, 0x67, 0x69, 0x6e, 0x12, 0x14, 0x05, 0x50, 0x41, 0x4e, 0x49, 0x43, 0x10, 0x01, 0x12, 0x09, 0x0a, 0x05, 0x46, 0x41, 0x54, 0x41,
0x2e, 0x64, 0x61, 0x65, 0x6d, 0x6f, 0x6e, 0x2e, 0x4c, 0x6f, 0x67, 0x69, 0x6e, 0x52, 0x65, 0x71, 0x4c, 0x10, 0x02, 0x12, 0x09, 0x0a, 0x05, 0x45, 0x52, 0x52, 0x4f, 0x52, 0x10, 0x03, 0x12, 0x08,
0x75, 0x65, 0x73, 0x74, 0x1a, 0x15, 0x2e, 0x64, 0x61, 0x65, 0x6d, 0x6f, 0x6e, 0x2e, 0x4c, 0x6f, 0x0a, 0x04, 0x57, 0x41, 0x52, 0x4e, 0x10, 0x04, 0x12, 0x08, 0x0a, 0x04, 0x49, 0x4e, 0x46, 0x4f,
0x67, 0x69, 0x6e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, 0x4b, 0x0a, 0x10, 0x05, 0x12, 0x09, 0x0a, 0x05, 0x44, 0x45, 0x42, 0x55, 0x47, 0x10, 0x06, 0x12, 0x09, 0x0a,
0x0c, 0x57, 0x61, 0x69, 0x74, 0x53, 0x53, 0x4f, 0x4c, 0x6f, 0x67, 0x69, 0x6e, 0x12, 0x1b, 0x2e, 0x05, 0x54, 0x52, 0x41, 0x43, 0x45, 0x10, 0x07, 0x32, 0xb8, 0x06, 0x0a, 0x0d, 0x44, 0x61, 0x65,
0x64, 0x61, 0x65, 0x6d, 0x6f, 0x6e, 0x2e, 0x57, 0x61, 0x69, 0x74, 0x53, 0x53, 0x4f, 0x4c, 0x6f, 0x6d, 0x6f, 0x6e, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x12, 0x36, 0x0a, 0x05, 0x4c, 0x6f,
0x67, 0x69, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1c, 0x2e, 0x64, 0x61, 0x65, 0x67, 0x69, 0x6e, 0x12, 0x14, 0x2e, 0x64, 0x61, 0x65, 0x6d, 0x6f, 0x6e, 0x2e, 0x4c, 0x6f, 0x67,
0x6d, 0x6f, 0x6e, 0x2e, 0x57, 0x61, 0x69, 0x74, 0x53, 0x53, 0x4f, 0x4c, 0x6f, 0x67, 0x69, 0x6e, 0x69, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x15, 0x2e, 0x64, 0x61, 0x65, 0x6d,
0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, 0x2d, 0x0a, 0x02, 0x55, 0x70, 0x6f, 0x6e, 0x2e, 0x4c, 0x6f, 0x67, 0x69, 0x6e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65,
0x12, 0x11, 0x2e, 0x64, 0x61, 0x65, 0x6d, 0x6f, 0x6e, 0x2e, 0x55, 0x70, 0x52, 0x65, 0x71, 0x75, 0x22, 0x00, 0x12, 0x4b, 0x0a, 0x0c, 0x57, 0x61, 0x69, 0x74, 0x53, 0x53, 0x4f, 0x4c, 0x6f, 0x67,
0x65, 0x73, 0x74, 0x1a, 0x12, 0x2e, 0x64, 0x61, 0x65, 0x6d, 0x6f, 0x6e, 0x2e, 0x55, 0x70, 0x52, 0x69, 0x6e, 0x12, 0x1b, 0x2e, 0x64, 0x61, 0x65, 0x6d, 0x6f, 0x6e, 0x2e, 0x57, 0x61, 0x69, 0x74,
0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, 0x39, 0x0a, 0x06, 0x53, 0x74, 0x61, 0x53, 0x53, 0x4f, 0x4c, 0x6f, 0x67, 0x69, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a,
0x74, 0x75, 0x73, 0x12, 0x15, 0x2e, 0x64, 0x61, 0x65, 0x6d, 0x6f, 0x6e, 0x2e, 0x53, 0x74, 0x61, 0x1c, 0x2e, 0x64, 0x61, 0x65, 0x6d, 0x6f, 0x6e, 0x2e, 0x57, 0x61, 0x69, 0x74, 0x53, 0x53, 0x4f,
0x74, 0x75, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x16, 0x2e, 0x64, 0x61, 0x65, 0x4c, 0x6f, 0x67, 0x69, 0x6e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12,
0x6d, 0x6f, 0x6e, 0x2e, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x2d, 0x0a, 0x02, 0x55, 0x70, 0x12, 0x11, 0x2e, 0x64, 0x61, 0x65, 0x6d, 0x6f, 0x6e, 0x2e, 0x55,
0x73, 0x65, 0x22, 0x00, 0x12, 0x33, 0x0a, 0x04, 0x44, 0x6f, 0x77, 0x6e, 0x12, 0x13, 0x2e, 0x64, 0x70, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x12, 0x2e, 0x64, 0x61, 0x65, 0x6d, 0x6f,
0x61, 0x65, 0x6d, 0x6f, 0x6e, 0x2e, 0x44, 0x6f, 0x77, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x6e, 0x2e, 0x55, 0x70, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, 0x39,
0x74, 0x1a, 0x14, 0x2e, 0x64, 0x61, 0x65, 0x6d, 0x6f, 0x6e, 0x2e, 0x44, 0x6f, 0x77, 0x6e, 0x52, 0x0a, 0x06, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, 0x15, 0x2e, 0x64, 0x61, 0x65, 0x6d, 0x6f,
0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, 0x42, 0x0a, 0x09, 0x47, 0x65, 0x74, 0x6e, 0x2e, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a,
0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x18, 0x2e, 0x64, 0x61, 0x65, 0x6d, 0x6f, 0x6e, 0x2e, 0x16, 0x2e, 0x64, 0x61, 0x65, 0x6d, 0x6f, 0x6e, 0x2e, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x52,
0x47, 0x65, 0x74, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, 0x33, 0x0a, 0x04, 0x44, 0x6f, 0x77,
0x1a, 0x19, 0x2e, 0x64, 0x61, 0x65, 0x6d, 0x6f, 0x6e, 0x2e, 0x47, 0x65, 0x74, 0x43, 0x6f, 0x6e, 0x6e, 0x12, 0x13, 0x2e, 0x64, 0x61, 0x65, 0x6d, 0x6f, 0x6e, 0x2e, 0x44, 0x6f, 0x77, 0x6e, 0x52,
0x66, 0x69, 0x67, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, 0x45, 0x0a, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x14, 0x2e, 0x64, 0x61, 0x65, 0x6d, 0x6f, 0x6e, 0x2e,
0x0a, 0x4c, 0x69, 0x73, 0x74, 0x52, 0x6f, 0x75, 0x74, 0x65, 0x73, 0x12, 0x19, 0x2e, 0x64, 0x61, 0x44, 0x6f, 0x77, 0x6e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, 0x42,
0x0a, 0x09, 0x47, 0x65, 0x74, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x18, 0x2e, 0x64, 0x61,
0x65, 0x6d, 0x6f, 0x6e, 0x2e, 0x47, 0x65, 0x74, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x52, 0x65,
0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x19, 0x2e, 0x64, 0x61, 0x65, 0x6d, 0x6f, 0x6e, 0x2e, 0x47,
0x65, 0x74, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65,
0x22, 0x00, 0x12, 0x45, 0x0a, 0x0a, 0x4c, 0x69, 0x73, 0x74, 0x52, 0x6f, 0x75, 0x74, 0x65, 0x73,
0x12, 0x19, 0x2e, 0x64, 0x61, 0x65, 0x6d, 0x6f, 0x6e, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x52, 0x6f,
0x75, 0x74, 0x65, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1a, 0x2e, 0x64, 0x61,
0x65, 0x6d, 0x6f, 0x6e, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x52, 0x6f, 0x75, 0x74, 0x65, 0x73, 0x52, 0x65, 0x6d, 0x6f, 0x6e, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x52, 0x6f, 0x75, 0x74, 0x65, 0x73, 0x52,
0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1a, 0x2e, 0x64, 0x61, 0x65, 0x6d, 0x6f, 0x6e, 0x2e, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, 0x4b, 0x0a, 0x0c, 0x53, 0x65, 0x6c,
0x4c, 0x69, 0x73, 0x74, 0x52, 0x6f, 0x75, 0x74, 0x65, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x65, 0x63, 0x74, 0x52, 0x6f, 0x75, 0x74, 0x65, 0x73, 0x12, 0x1b, 0x2e, 0x64, 0x61, 0x65, 0x6d,
0x73, 0x65, 0x22, 0x00, 0x12, 0x4b, 0x0a, 0x0c, 0x53, 0x65, 0x6c, 0x65, 0x63, 0x74, 0x52, 0x6f, 0x6f, 0x6e, 0x2e, 0x53, 0x65, 0x6c, 0x65, 0x63, 0x74, 0x52, 0x6f, 0x75, 0x74, 0x65, 0x73, 0x52,
0x75, 0x74, 0x65, 0x73, 0x12, 0x1b, 0x2e, 0x64, 0x61, 0x65, 0x6d, 0x6f, 0x6e, 0x2e, 0x53, 0x65, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1c, 0x2e, 0x64, 0x61, 0x65, 0x6d, 0x6f, 0x6e, 0x2e,
0x6c, 0x65, 0x63, 0x74, 0x52, 0x6f, 0x75, 0x74, 0x65, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x53, 0x65, 0x6c, 0x65, 0x63, 0x74, 0x52, 0x6f, 0x75, 0x74, 0x65, 0x73, 0x52, 0x65, 0x73, 0x70,
0x74, 0x1a, 0x1c, 0x2e, 0x64, 0x61, 0x65, 0x6d, 0x6f, 0x6e, 0x2e, 0x53, 0x65, 0x6c, 0x65, 0x63, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, 0x4d, 0x0a, 0x0e, 0x44, 0x65, 0x73, 0x65, 0x6c, 0x65,
0x74, 0x52, 0x6f, 0x75, 0x74, 0x65, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x63, 0x74, 0x52, 0x6f, 0x75, 0x74, 0x65, 0x73, 0x12, 0x1b, 0x2e, 0x64, 0x61, 0x65, 0x6d, 0x6f,
0x00, 0x12, 0x4d, 0x0a, 0x0e, 0x44, 0x65, 0x73, 0x65, 0x6c, 0x65, 0x63, 0x74, 0x52, 0x6f, 0x75, 0x6e, 0x2e, 0x53, 0x65, 0x6c, 0x65, 0x63, 0x74, 0x52, 0x6f, 0x75, 0x74, 0x65, 0x73, 0x52, 0x65,
0x74, 0x65, 0x73, 0x12, 0x1b, 0x2e, 0x64, 0x61, 0x65, 0x6d, 0x6f, 0x6e, 0x2e, 0x53, 0x65, 0x6c, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1c, 0x2e, 0x64, 0x61, 0x65, 0x6d, 0x6f, 0x6e, 0x2e, 0x53,
0x65, 0x63, 0x74, 0x52, 0x6f, 0x75, 0x74, 0x65, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x65, 0x6c, 0x65, 0x63, 0x74, 0x52, 0x6f, 0x75, 0x74, 0x65, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f,
0x1a, 0x1c, 0x2e, 0x64, 0x61, 0x65, 0x6d, 0x6f, 0x6e, 0x2e, 0x53, 0x65, 0x6c, 0x65, 0x63, 0x74, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, 0x48, 0x0a, 0x0b, 0x44, 0x65, 0x62, 0x75, 0x67, 0x42, 0x75,
0x52, 0x6f, 0x75, 0x74, 0x65, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x6e, 0x64, 0x6c, 0x65, 0x12, 0x1a, 0x2e, 0x64, 0x61, 0x65, 0x6d, 0x6f, 0x6e, 0x2e, 0x44, 0x65,
0x12, 0x48, 0x0a, 0x0b, 0x44, 0x65, 0x62, 0x75, 0x67, 0x42, 0x75, 0x6e, 0x64, 0x6c, 0x65, 0x12, 0x62, 0x75, 0x67, 0x42, 0x75, 0x6e, 0x64, 0x6c, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74,
0x1a, 0x2e, 0x64, 0x61, 0x65, 0x6d, 0x6f, 0x6e, 0x2e, 0x44, 0x65, 0x62, 0x75, 0x67, 0x42, 0x75, 0x1a, 0x1b, 0x2e, 0x64, 0x61, 0x65, 0x6d, 0x6f, 0x6e, 0x2e, 0x44, 0x65, 0x62, 0x75, 0x67, 0x42,
0x6e, 0x64, 0x6c, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1b, 0x2e, 0x64, 0x61, 0x75, 0x6e, 0x64, 0x6c, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12,
0x65, 0x6d, 0x6f, 0x6e, 0x2e, 0x44, 0x65, 0x62, 0x75, 0x67, 0x42, 0x75, 0x6e, 0x64, 0x6c, 0x65, 0x48, 0x0a, 0x0b, 0x47, 0x65, 0x74, 0x4c, 0x6f, 0x67, 0x4c, 0x65, 0x76, 0x65, 0x6c, 0x12, 0x1a,
0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, 0x48, 0x0a, 0x0b, 0x53, 0x65, 0x2e, 0x64, 0x61, 0x65, 0x6d, 0x6f, 0x6e, 0x2e, 0x47, 0x65, 0x74, 0x4c, 0x6f, 0x67, 0x4c, 0x65,
0x74, 0x4c, 0x6f, 0x67, 0x4c, 0x65, 0x76, 0x65, 0x6c, 0x12, 0x1a, 0x2e, 0x64, 0x61, 0x65, 0x6d, 0x76, 0x65, 0x6c, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1b, 0x2e, 0x64, 0x61, 0x65,
0x6f, 0x6e, 0x2e, 0x53, 0x65, 0x74, 0x4c, 0x6f, 0x67, 0x4c, 0x65, 0x76, 0x65, 0x6c, 0x52, 0x65, 0x6d, 0x6f, 0x6e, 0x2e, 0x47, 0x65, 0x74, 0x4c, 0x6f, 0x67, 0x4c, 0x65, 0x76, 0x65, 0x6c, 0x52,
0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1b, 0x2e, 0x64, 0x61, 0x65, 0x6d, 0x6f, 0x6e, 0x2e, 0x53, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, 0x48, 0x0a, 0x0b, 0x53, 0x65, 0x74,
0x65, 0x74, 0x4c, 0x6f, 0x67, 0x4c, 0x65, 0x76, 0x65, 0x6c, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x4c, 0x6f, 0x67, 0x4c, 0x65, 0x76, 0x65, 0x6c, 0x12, 0x1a, 0x2e, 0x64, 0x61, 0x65, 0x6d, 0x6f,
0x73, 0x65, 0x22, 0x00, 0x42, 0x08, 0x5a, 0x06, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x06, 0x6e, 0x2e, 0x53, 0x65, 0x74, 0x4c, 0x6f, 0x67, 0x4c, 0x65, 0x76, 0x65, 0x6c, 0x52, 0x65, 0x71,
0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1b, 0x2e, 0x64, 0x61, 0x65, 0x6d, 0x6f, 0x6e, 0x2e, 0x53, 0x65,
0x74, 0x4c, 0x6f, 0x67, 0x4c, 0x65, 0x76, 0x65, 0x6c, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73,
0x65, 0x22, 0x00, 0x42, 0x08, 0x5a, 0x06, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x06, 0x70,
0x72, 0x6f, 0x74, 0x6f, 0x33,
} }
var ( var (
@ -2214,7 +2309,7 @@ func file_daemon_proto_rawDescGZIP() []byte {
} }
var file_daemon_proto_enumTypes = make([]protoimpl.EnumInfo, 1) var file_daemon_proto_enumTypes = make([]protoimpl.EnumInfo, 1)
var file_daemon_proto_msgTypes = make([]protoimpl.MessageInfo, 28) var file_daemon_proto_msgTypes = make([]protoimpl.MessageInfo, 30)
var file_daemon_proto_goTypes = []interface{}{ var file_daemon_proto_goTypes = []interface{}{
(LogLevel)(0), // 0: daemon.LogLevel (LogLevel)(0), // 0: daemon.LogLevel
(*LoginRequest)(nil), // 1: daemon.LoginRequest (*LoginRequest)(nil), // 1: daemon.LoginRequest
@ -2243,16 +2338,18 @@ var file_daemon_proto_goTypes = []interface{}{
(*Route)(nil), // 24: daemon.Route (*Route)(nil), // 24: daemon.Route
(*DebugBundleRequest)(nil), // 25: daemon.DebugBundleRequest (*DebugBundleRequest)(nil), // 25: daemon.DebugBundleRequest
(*DebugBundleResponse)(nil), // 26: daemon.DebugBundleResponse (*DebugBundleResponse)(nil), // 26: daemon.DebugBundleResponse
(*SetLogLevelRequest)(nil), // 27: daemon.SetLogLevelRequest (*GetLogLevelRequest)(nil), // 27: daemon.GetLogLevelRequest
(*SetLogLevelResponse)(nil), // 28: daemon.SetLogLevelResponse (*GetLogLevelResponse)(nil), // 28: daemon.GetLogLevelResponse
(*timestamp.Timestamp)(nil), // 29: google.protobuf.Timestamp (*SetLogLevelRequest)(nil), // 29: daemon.SetLogLevelRequest
(*duration.Duration)(nil), // 30: google.protobuf.Duration (*SetLogLevelResponse)(nil), // 30: daemon.SetLogLevelResponse
(*timestamp.Timestamp)(nil), // 31: google.protobuf.Timestamp
(*duration.Duration)(nil), // 32: google.protobuf.Duration
} }
var file_daemon_proto_depIdxs = []int32{ var file_daemon_proto_depIdxs = []int32{
19, // 0: daemon.StatusResponse.fullStatus:type_name -> daemon.FullStatus 19, // 0: daemon.StatusResponse.fullStatus:type_name -> daemon.FullStatus
29, // 1: daemon.PeerState.connStatusUpdate:type_name -> google.protobuf.Timestamp 31, // 1: daemon.PeerState.connStatusUpdate:type_name -> google.protobuf.Timestamp
29, // 2: daemon.PeerState.lastWireguardHandshake:type_name -> google.protobuf.Timestamp 31, // 2: daemon.PeerState.lastWireguardHandshake:type_name -> google.protobuf.Timestamp
30, // 3: daemon.PeerState.latency:type_name -> google.protobuf.Duration 32, // 3: daemon.PeerState.latency:type_name -> google.protobuf.Duration
16, // 4: daemon.FullStatus.managementState:type_name -> daemon.ManagementState 16, // 4: daemon.FullStatus.managementState:type_name -> daemon.ManagementState
15, // 5: daemon.FullStatus.signalState:type_name -> daemon.SignalState 15, // 5: daemon.FullStatus.signalState:type_name -> daemon.SignalState
14, // 6: daemon.FullStatus.localPeerState:type_name -> daemon.LocalPeerState 14, // 6: daemon.FullStatus.localPeerState:type_name -> daemon.LocalPeerState
@ -2260,34 +2357,37 @@ var file_daemon_proto_depIdxs = []int32{
17, // 8: daemon.FullStatus.relays:type_name -> daemon.RelayState 17, // 8: daemon.FullStatus.relays:type_name -> daemon.RelayState
18, // 9: daemon.FullStatus.dns_servers:type_name -> daemon.NSGroupState 18, // 9: daemon.FullStatus.dns_servers:type_name -> daemon.NSGroupState
24, // 10: daemon.ListRoutesResponse.routes:type_name -> daemon.Route 24, // 10: daemon.ListRoutesResponse.routes:type_name -> daemon.Route
0, // 11: daemon.SetLogLevelRequest.level:type_name -> daemon.LogLevel 0, // 11: daemon.GetLogLevelResponse.level:type_name -> daemon.LogLevel
1, // 12: daemon.DaemonService.Login:input_type -> daemon.LoginRequest 0, // 12: daemon.SetLogLevelRequest.level:type_name -> daemon.LogLevel
3, // 13: daemon.DaemonService.WaitSSOLogin:input_type -> daemon.WaitSSOLoginRequest 1, // 13: daemon.DaemonService.Login:input_type -> daemon.LoginRequest
5, // 14: daemon.DaemonService.Up:input_type -> daemon.UpRequest 3, // 14: daemon.DaemonService.WaitSSOLogin:input_type -> daemon.WaitSSOLoginRequest
7, // 15: daemon.DaemonService.Status:input_type -> daemon.StatusRequest 5, // 15: daemon.DaemonService.Up:input_type -> daemon.UpRequest
9, // 16: daemon.DaemonService.Down:input_type -> daemon.DownRequest 7, // 16: daemon.DaemonService.Status:input_type -> daemon.StatusRequest
11, // 17: daemon.DaemonService.GetConfig:input_type -> daemon.GetConfigRequest 9, // 17: daemon.DaemonService.Down:input_type -> daemon.DownRequest
20, // 18: daemon.DaemonService.ListRoutes:input_type -> daemon.ListRoutesRequest 11, // 18: daemon.DaemonService.GetConfig:input_type -> daemon.GetConfigRequest
22, // 19: daemon.DaemonService.SelectRoutes:input_type -> daemon.SelectRoutesRequest 20, // 19: daemon.DaemonService.ListRoutes:input_type -> daemon.ListRoutesRequest
22, // 20: daemon.DaemonService.DeselectRoutes:input_type -> daemon.SelectRoutesRequest 22, // 20: daemon.DaemonService.SelectRoutes:input_type -> daemon.SelectRoutesRequest
25, // 21: daemon.DaemonService.DebugBundle:input_type -> daemon.DebugBundleRequest 22, // 21: daemon.DaemonService.DeselectRoutes:input_type -> daemon.SelectRoutesRequest
27, // 22: daemon.DaemonService.SetLogLevel:input_type -> daemon.SetLogLevelRequest 25, // 22: daemon.DaemonService.DebugBundle:input_type -> daemon.DebugBundleRequest
2, // 23: daemon.DaemonService.Login:output_type -> daemon.LoginResponse 27, // 23: daemon.DaemonService.GetLogLevel:input_type -> daemon.GetLogLevelRequest
4, // 24: daemon.DaemonService.WaitSSOLogin:output_type -> daemon.WaitSSOLoginResponse 29, // 24: daemon.DaemonService.SetLogLevel:input_type -> daemon.SetLogLevelRequest
6, // 25: daemon.DaemonService.Up:output_type -> daemon.UpResponse 2, // 25: daemon.DaemonService.Login:output_type -> daemon.LoginResponse
8, // 26: daemon.DaemonService.Status:output_type -> daemon.StatusResponse 4, // 26: daemon.DaemonService.WaitSSOLogin:output_type -> daemon.WaitSSOLoginResponse
10, // 27: daemon.DaemonService.Down:output_type -> daemon.DownResponse 6, // 27: daemon.DaemonService.Up:output_type -> daemon.UpResponse
12, // 28: daemon.DaemonService.GetConfig:output_type -> daemon.GetConfigResponse 8, // 28: daemon.DaemonService.Status:output_type -> daemon.StatusResponse
21, // 29: daemon.DaemonService.ListRoutes:output_type -> daemon.ListRoutesResponse 10, // 29: daemon.DaemonService.Down:output_type -> daemon.DownResponse
23, // 30: daemon.DaemonService.SelectRoutes:output_type -> daemon.SelectRoutesResponse 12, // 30: daemon.DaemonService.GetConfig:output_type -> daemon.GetConfigResponse
23, // 31: daemon.DaemonService.DeselectRoutes:output_type -> daemon.SelectRoutesResponse 21, // 31: daemon.DaemonService.ListRoutes:output_type -> daemon.ListRoutesResponse
26, // 32: daemon.DaemonService.DebugBundle:output_type -> daemon.DebugBundleResponse 23, // 32: daemon.DaemonService.SelectRoutes:output_type -> daemon.SelectRoutesResponse
28, // 33: daemon.DaemonService.SetLogLevel:output_type -> daemon.SetLogLevelResponse 23, // 33: daemon.DaemonService.DeselectRoutes:output_type -> daemon.SelectRoutesResponse
23, // [23:34] is the sub-list for method output_type 26, // 34: daemon.DaemonService.DebugBundle:output_type -> daemon.DebugBundleResponse
12, // [12:23] is the sub-list for method input_type 28, // 35: daemon.DaemonService.GetLogLevel:output_type -> daemon.GetLogLevelResponse
12, // [12:12] is the sub-list for extension type_name 30, // 36: daemon.DaemonService.SetLogLevel:output_type -> daemon.SetLogLevelResponse
12, // [12:12] is the sub-list for extension extendee 25, // [25:37] is the sub-list for method output_type
0, // [0:12] is the sub-list for field type_name 13, // [13:25] is the sub-list for method input_type
13, // [13:13] is the sub-list for extension type_name
13, // [13:13] is the sub-list for extension extendee
0, // [0:13] is the sub-list for field type_name
} }
func init() { file_daemon_proto_init() } func init() { file_daemon_proto_init() }
@ -2609,7 +2709,7 @@ func file_daemon_proto_init() {
} }
} }
file_daemon_proto_msgTypes[26].Exporter = func(v interface{}, i int) interface{} { file_daemon_proto_msgTypes[26].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*SetLogLevelRequest); i { switch v := v.(*GetLogLevelRequest); i {
case 0: case 0:
return &v.state return &v.state
case 1: case 1:
@ -2621,6 +2721,30 @@ func file_daemon_proto_init() {
} }
} }
file_daemon_proto_msgTypes[27].Exporter = func(v interface{}, i int) interface{} { file_daemon_proto_msgTypes[27].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*GetLogLevelResponse); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
file_daemon_proto_msgTypes[28].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*SetLogLevelRequest); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
file_daemon_proto_msgTypes[29].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*SetLogLevelResponse); i { switch v := v.(*SetLogLevelResponse); i {
case 0: case 0:
return &v.state return &v.state
@ -2640,7 +2764,7 @@ func file_daemon_proto_init() {
GoPackagePath: reflect.TypeOf(x{}).PkgPath(), GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
RawDescriptor: file_daemon_proto_rawDesc, RawDescriptor: file_daemon_proto_rawDesc,
NumEnums: 1, NumEnums: 1,
NumMessages: 28, NumMessages: 30,
NumExtensions: 0, NumExtensions: 0,
NumServices: 1, NumServices: 1,
}, },

View File

@ -40,6 +40,9 @@ service DaemonService {
// DebugBundle creates a debug bundle // DebugBundle creates a debug bundle
rpc DebugBundle(DebugBundleRequest) returns (DebugBundleResponse) {} rpc DebugBundle(DebugBundleRequest) returns (DebugBundleResponse) {}
// GetLogLevel gets the log level of the daemon
rpc GetLogLevel(GetLogLevelRequest) returns (GetLogLevelResponse) {}
// SetLogLevel sets the log level of the daemon // SetLogLevel sets the log level of the daemon
rpc SetLogLevel(SetLogLevelRequest) returns (SetLogLevelResponse) {} rpc SetLogLevel(SetLogLevelRequest) returns (SetLogLevelResponse) {}
}; };
@ -256,6 +259,13 @@ enum LogLevel {
TRACE = 7; TRACE = 7;
} }
message GetLogLevelRequest {
}
message GetLogLevelResponse {
LogLevel level = 1;
}
message SetLogLevelRequest { message SetLogLevelRequest {
LogLevel level = 1; LogLevel level = 1;
} }

View File

@ -39,6 +39,8 @@ type DaemonServiceClient interface {
DeselectRoutes(ctx context.Context, in *SelectRoutesRequest, opts ...grpc.CallOption) (*SelectRoutesResponse, error) DeselectRoutes(ctx context.Context, in *SelectRoutesRequest, opts ...grpc.CallOption) (*SelectRoutesResponse, error)
// DebugBundle creates a debug bundle // DebugBundle creates a debug bundle
DebugBundle(ctx context.Context, in *DebugBundleRequest, opts ...grpc.CallOption) (*DebugBundleResponse, error) DebugBundle(ctx context.Context, in *DebugBundleRequest, opts ...grpc.CallOption) (*DebugBundleResponse, error)
// GetLogLevel gets the log level of the daemon
GetLogLevel(ctx context.Context, in *GetLogLevelRequest, opts ...grpc.CallOption) (*GetLogLevelResponse, error)
// SetLogLevel sets the log level of the daemon // SetLogLevel sets the log level of the daemon
SetLogLevel(ctx context.Context, in *SetLogLevelRequest, opts ...grpc.CallOption) (*SetLogLevelResponse, error) SetLogLevel(ctx context.Context, in *SetLogLevelRequest, opts ...grpc.CallOption) (*SetLogLevelResponse, error)
} }
@ -141,6 +143,15 @@ func (c *daemonServiceClient) DebugBundle(ctx context.Context, in *DebugBundleRe
return out, nil return out, nil
} }
func (c *daemonServiceClient) GetLogLevel(ctx context.Context, in *GetLogLevelRequest, opts ...grpc.CallOption) (*GetLogLevelResponse, error) {
out := new(GetLogLevelResponse)
err := c.cc.Invoke(ctx, "/daemon.DaemonService/GetLogLevel", in, out, opts...)
if err != nil {
return nil, err
}
return out, nil
}
func (c *daemonServiceClient) SetLogLevel(ctx context.Context, in *SetLogLevelRequest, opts ...grpc.CallOption) (*SetLogLevelResponse, error) { func (c *daemonServiceClient) SetLogLevel(ctx context.Context, in *SetLogLevelRequest, opts ...grpc.CallOption) (*SetLogLevelResponse, error) {
out := new(SetLogLevelResponse) out := new(SetLogLevelResponse)
err := c.cc.Invoke(ctx, "/daemon.DaemonService/SetLogLevel", in, out, opts...) err := c.cc.Invoke(ctx, "/daemon.DaemonService/SetLogLevel", in, out, opts...)
@ -175,6 +186,8 @@ type DaemonServiceServer interface {
DeselectRoutes(context.Context, *SelectRoutesRequest) (*SelectRoutesResponse, error) DeselectRoutes(context.Context, *SelectRoutesRequest) (*SelectRoutesResponse, error)
// DebugBundle creates a debug bundle // DebugBundle creates a debug bundle
DebugBundle(context.Context, *DebugBundleRequest) (*DebugBundleResponse, error) DebugBundle(context.Context, *DebugBundleRequest) (*DebugBundleResponse, error)
// GetLogLevel gets the log level of the daemon
GetLogLevel(context.Context, *GetLogLevelRequest) (*GetLogLevelResponse, error)
// SetLogLevel sets the log level of the daemon // SetLogLevel sets the log level of the daemon
SetLogLevel(context.Context, *SetLogLevelRequest) (*SetLogLevelResponse, error) SetLogLevel(context.Context, *SetLogLevelRequest) (*SetLogLevelResponse, error)
mustEmbedUnimplementedDaemonServiceServer() mustEmbedUnimplementedDaemonServiceServer()
@ -214,6 +227,9 @@ func (UnimplementedDaemonServiceServer) DeselectRoutes(context.Context, *SelectR
func (UnimplementedDaemonServiceServer) DebugBundle(context.Context, *DebugBundleRequest) (*DebugBundleResponse, error) { func (UnimplementedDaemonServiceServer) DebugBundle(context.Context, *DebugBundleRequest) (*DebugBundleResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method DebugBundle not implemented") return nil, status.Errorf(codes.Unimplemented, "method DebugBundle not implemented")
} }
func (UnimplementedDaemonServiceServer) GetLogLevel(context.Context, *GetLogLevelRequest) (*GetLogLevelResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method GetLogLevel not implemented")
}
func (UnimplementedDaemonServiceServer) SetLogLevel(context.Context, *SetLogLevelRequest) (*SetLogLevelResponse, error) { func (UnimplementedDaemonServiceServer) SetLogLevel(context.Context, *SetLogLevelRequest) (*SetLogLevelResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method SetLogLevel not implemented") return nil, status.Errorf(codes.Unimplemented, "method SetLogLevel not implemented")
} }
@ -410,6 +426,24 @@ func _DaemonService_DebugBundle_Handler(srv interface{}, ctx context.Context, de
return interceptor(ctx, in, info, handler) return interceptor(ctx, in, info, handler)
} }
func _DaemonService_GetLogLevel_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
in := new(GetLogLevelRequest)
if err := dec(in); err != nil {
return nil, err
}
if interceptor == nil {
return srv.(DaemonServiceServer).GetLogLevel(ctx, in)
}
info := &grpc.UnaryServerInfo{
Server: srv,
FullMethod: "/daemon.DaemonService/GetLogLevel",
}
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(DaemonServiceServer).GetLogLevel(ctx, req.(*GetLogLevelRequest))
}
return interceptor(ctx, in, info, handler)
}
func _DaemonService_SetLogLevel_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { func _DaemonService_SetLogLevel_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
in := new(SetLogLevelRequest) in := new(SetLogLevelRequest)
if err := dec(in); err != nil { if err := dec(in); err != nil {
@ -475,6 +509,10 @@ var DaemonService_ServiceDesc = grpc.ServiceDesc{
MethodName: "DebugBundle", MethodName: "DebugBundle",
Handler: _DaemonService_DebugBundle_Handler, Handler: _DaemonService_DebugBundle_Handler,
}, },
{
MethodName: "GetLogLevel",
Handler: _DaemonService_GetLogLevel_Handler,
},
{ {
MethodName: "SetLogLevel", MethodName: "SetLogLevel",
Handler: _DaemonService_SetLogLevel_Handler, Handler: _DaemonService_SetLogLevel_Handler,

View File

@ -121,6 +121,12 @@ func (s *Server) anonymize(reader io.Reader, writer io.WriteCloser, errChan chan
} }
} }
// GetLogLevel gets the current logging level for the server.
func (s *Server) GetLogLevel(_ context.Context, _ *proto.GetLogLevelRequest) (*proto.GetLogLevelResponse, error) {
level := ParseLogLevel(log.GetLevel().String())
return &proto.GetLogLevelResponse{Level: level}, nil
}
// SetLogLevel sets the logging level for the server. // SetLogLevel sets the logging level for the server.
func (s *Server) SetLogLevel(_ context.Context, req *proto.SetLogLevelRequest) (*proto.SetLogLevelResponse, error) { func (s *Server) SetLogLevel(_ context.Context, req *proto.SetLogLevelRequest) (*proto.SetLogLevelResponse, error) {
level, err := log.ParseLevel(req.Level.String()) level, err := log.ParseLevel(req.Level.String())

28
client/server/log.go Normal file
View File

@ -0,0 +1,28 @@
package server
import (
"strings"
"github.com/netbirdio/netbird/client/proto"
)
func ParseLogLevel(level string) proto.LogLevel {
switch strings.ToLower(level) {
case "panic":
return proto.LogLevel_PANIC
case "fatal":
return proto.LogLevel_FATAL
case "error":
return proto.LogLevel_ERROR
case "warn":
return proto.LogLevel_WARN
case "info":
return proto.LogLevel_INFO
case "debug":
return proto.LogLevel_DEBUG
case "trace":
return proto.LogLevel_TRACE
default:
return proto.LogLevel_UNKNOWN
}
}

View File

@ -36,7 +36,7 @@ const (
maxRetryIntervalVar = "NB_CONN_MAX_RETRY_INTERVAL_TIME" maxRetryIntervalVar = "NB_CONN_MAX_RETRY_INTERVAL_TIME"
maxRetryTimeVar = "NB_CONN_MAX_RETRY_TIME_TIME" maxRetryTimeVar = "NB_CONN_MAX_RETRY_TIME_TIME"
retryMultiplierVar = "NB_CONN_RETRY_MULTIPLIER" retryMultiplierVar = "NB_CONN_RETRY_MULTIPLIER"
defaultInitialRetryTime = 14 * 24 * time.Hour defaultInitialRetryTime = 30 * time.Minute
defaultMaxRetryInterval = 60 * time.Minute defaultMaxRetryInterval = 60 * time.Minute
defaultMaxRetryTime = 14 * 24 * time.Hour defaultMaxRetryTime = 14 * 24 * time.Hour
defaultRetryMultiplier = 1.7 defaultRetryMultiplier = 1.7

View File

@ -20,6 +20,9 @@ const OsVersionCtxKey = "OsVersion"
// OsNameCtxKey context key for operating system name // OsNameCtxKey context key for operating system name
const OsNameCtxKey = "OsName" const OsNameCtxKey = "OsName"
// UiVersionCtxKey context key for user UI version
const UiVersionCtxKey = "user-agent"
type NetworkAddress struct { type NetworkAddress struct {
NetIP netip.Prefix NetIP netip.Prefix
Mac string Mac string

View File

@ -28,10 +28,18 @@ func GetInfo(ctx context.Context) *Info {
kernelVersion = osInfo[2] kernelVersion = osInfo[2]
} }
gio := &Info{Kernel: kernel, Platform: "unknown", OS: "android", OSVersion: osVersion(), GoOS: runtime.GOOS, CPUs: runtime.NumCPU(), KernelVersion: kernelVersion} gio := &Info{
gio.Hostname = extractDeviceName(ctx, "android") GoOS: runtime.GOOS,
gio.WiretrusteeVersion = version.NetbirdVersion() Kernel: kernel,
gio.UIVersion = extractUserAgent(ctx) Platform: "unknown",
OS: "android",
OSVersion: osVersion(),
Hostname: extractDeviceName(ctx, "android"),
CPUs: runtime.NumCPU(),
WiretrusteeVersion: version.NetbirdVersion(),
UIVersion: extractUIVersion(ctx),
KernelVersion: kernelVersion,
}
return gio return gio
} }
@ -45,6 +53,14 @@ func osVersion() string {
return run("/system/bin/getprop", "ro.build.version.release") return run("/system/bin/getprop", "ro.build.version.release")
} }
func extractUIVersion(ctx context.Context) string {
v, ok := ctx.Value(UiVersionCtxKey).(string)
if !ok {
return ""
}
return v
}
func run(name string, arg ...string) string { func run(name string, arg ...string) string {
cmd := exec.Command(name, arg...) cmd := exec.Command(name, arg...)
cmd.Stdin = strings.NewReader("some") cmd.Stdin = strings.NewReader("some")

View File

@ -399,6 +399,7 @@ func (s *serviceClient) updateStatus() error {
status, err := conn.Status(s.ctx, &proto.StatusRequest{}) status, err := conn.Status(s.ctx, &proto.StatusRequest{})
if err != nil { if err != nil {
log.Errorf("get service status: %v", err) log.Errorf("get service status: %v", err)
s.setDisconnectedStatus()
return err return err
} }
@ -426,17 +427,7 @@ func (s *serviceClient) updateStatus() error {
s.mRoutes.Enable() s.mRoutes.Enable()
systrayIconState = true systrayIconState = true
} else if status.Status != string(internal.StatusConnected) && s.mUp.Disabled() { } else if status.Status != string(internal.StatusConnected) && s.mUp.Disabled() {
s.connected = false s.setDisconnectedStatus()
if s.isUpdateIconActive {
systray.SetIcon(s.icUpdateDisconnected)
} else {
systray.SetIcon(s.icDisconnected)
}
systray.SetTooltip("NetBird (Disconnected)")
s.mStatus.SetTitle("Disconnected")
s.mDown.Disable()
s.mUp.Enable()
s.mRoutes.Disable()
systrayIconState = false systrayIconState = false
} }
@ -481,6 +472,20 @@ func (s *serviceClient) updateStatus() error {
return nil return nil
} }
func (s *serviceClient) setDisconnectedStatus() {
s.connected = false
if s.isUpdateIconActive {
systray.SetIcon(s.icUpdateDisconnected)
} else {
systray.SetIcon(s.icDisconnected)
}
systray.SetTooltip("NetBird (Disconnected)")
s.mStatus.SetTitle("Disconnected")
s.mDown.Disable()
s.mUp.Enable()
s.mRoutes.Disable()
}
func (s *serviceClient) onTrayReady() { func (s *serviceClient) onTrayReady() {
systray.SetIcon(s.icDisconnected) systray.SetIcon(s.icDisconnected)
systray.SetTooltip("NetBird") systray.SetTooltip("NetBird")

120
go.mod
View File

@ -1,15 +1,13 @@
module github.com/netbirdio/netbird module github.com/netbirdio/netbird
go 1.21 go 1.21.0
toolchain go1.21.0
require ( require (
cunicu.li/go-rosenpass v0.4.0 cunicu.li/go-rosenpass v0.4.0
github.com/cenkalti/backoff/v4 v4.2.0 github.com/cenkalti/backoff/v4 v4.3.0
github.com/cloudflare/circl v1.3.3 // indirect github.com/cloudflare/circl v1.3.3 // indirect
github.com/golang-jwt/jwt v3.2.2+incompatible github.com/golang-jwt/jwt v3.2.2+incompatible
github.com/golang/protobuf v1.5.3 github.com/golang/protobuf v1.5.4
github.com/google/uuid v1.6.0 github.com/google/uuid v1.6.0
github.com/gorilla/mux v1.8.0 github.com/gorilla/mux v1.8.0
github.com/kardianos/service v1.2.1-0.20210728001519-a323c3813bc7 github.com/kardianos/service v1.2.1-0.20210728001519-a323c3813bc7
@ -17,17 +15,17 @@ require (
github.com/onsi/gomega v1.27.6 github.com/onsi/gomega v1.27.6
github.com/pion/ice/v3 v3.0.2 github.com/pion/ice/v3 v3.0.2
github.com/rs/cors v1.8.0 github.com/rs/cors v1.8.0
github.com/sirupsen/logrus v1.9.0 github.com/sirupsen/logrus v1.9.3
github.com/spf13/cobra v1.7.0 github.com/spf13/cobra v1.7.0
github.com/spf13/pflag v1.0.5 github.com/spf13/pflag v1.0.5
github.com/vishvananda/netlink v1.1.1-0.20211118161826-650dca95af54 github.com/vishvananda/netlink v1.2.1-beta.2
golang.org/x/crypto v0.23.0 golang.org/x/crypto v0.23.0
golang.org/x/sys v0.20.0 golang.org/x/sys v0.20.0
golang.zx2c4.com/wireguard v0.0.0-20230704135630-469159ecf7d1 golang.zx2c4.com/wireguard v0.0.0-20230704135630-469159ecf7d1
golang.zx2c4.com/wireguard/wgctrl v0.0.0-20230429144221-925a1e7659e6 golang.zx2c4.com/wireguard/wgctrl v0.0.0-20230429144221-925a1e7659e6
golang.zx2c4.com/wireguard/windows v0.5.3 golang.zx2c4.com/wireguard/windows v0.5.3
google.golang.org/grpc v1.56.3 google.golang.org/grpc v1.64.0
google.golang.org/protobuf v1.31.0 google.golang.org/protobuf v1.34.1
gopkg.in/natefinch/lumberjack.v2 v2.0.0 gopkg.in/natefinch/lumberjack.v2 v2.0.0
) )
@ -35,7 +33,7 @@ require (
fyne.io/fyne/v2 v2.1.4 fyne.io/fyne/v2 v2.1.4
github.com/TheJumpCloud/jcapi-go v3.0.0+incompatible github.com/TheJumpCloud/jcapi-go v3.0.0+incompatible
github.com/c-robinson/iplib v1.0.3 github.com/c-robinson/iplib v1.0.3
github.com/cilium/ebpf v0.11.0 github.com/cilium/ebpf v0.15.0
github.com/coreos/go-iptables v0.7.0 github.com/coreos/go-iptables v0.7.0
github.com/creack/pty v1.1.18 github.com/creack/pty v1.1.18
github.com/eko/gocache/v3 v3.1.1 github.com/eko/gocache/v3 v3.1.1
@ -61,7 +59,7 @@ require (
github.com/miekg/dns v1.1.43 github.com/miekg/dns v1.1.43
github.com/mitchellh/hashstructure/v2 v2.0.2 github.com/mitchellh/hashstructure/v2 v2.0.2
github.com/nadoo/ipset v0.5.0 github.com/nadoo/ipset v0.5.0
github.com/netbirdio/management-integrations/integrations v0.0.0-20240415094251-369eb33c9b01 github.com/netbirdio/management-integrations/integrations v0.0.0-20240524104853-69c6d89826cd
github.com/okta/okta-sdk-golang/v2 v2.18.0 github.com/okta/okta-sdk-golang/v2 v2.18.0
github.com/oschwald/maxminddb-golang v1.12.0 github.com/oschwald/maxminddb-golang v1.12.0
github.com/patrickmn/go-cache v2.1.0+incompatible github.com/patrickmn/go-cache v2.1.0+incompatible
@ -69,28 +67,28 @@ require (
github.com/pion/stun/v2 v2.0.0 github.com/pion/stun/v2 v2.0.0
github.com/pion/transport/v3 v3.0.1 github.com/pion/transport/v3 v3.0.1
github.com/pion/turn/v3 v3.0.1 github.com/pion/turn/v3 v3.0.1
github.com/prometheus/client_golang v1.14.0 github.com/prometheus/client_golang v1.19.1
github.com/quic-go/quic-go v0.44.0 github.com/quic-go/quic-go v0.45.0
github.com/rs/xid v1.3.0 github.com/rs/xid v1.3.0
github.com/skratchdot/open-golang v0.0.0-20200116055534-eef842397966 github.com/skratchdot/open-golang v0.0.0-20200116055534-eef842397966
github.com/stretchr/testify v1.8.4 github.com/stretchr/testify v1.9.0
github.com/testcontainers/testcontainers-go v0.20.0 github.com/testcontainers/testcontainers-go v0.31.0
github.com/testcontainers/testcontainers-go/modules/postgres v0.20.0 github.com/testcontainers/testcontainers-go/modules/postgres v0.31.0
github.com/things-go/go-socks5 v0.0.4 github.com/things-go/go-socks5 v0.0.4
github.com/yusufpapurcu/wmi v1.2.3 github.com/yusufpapurcu/wmi v1.2.4
github.com/zcalusic/sysinfo v1.0.2 github.com/zcalusic/sysinfo v1.0.2
go.opentelemetry.io/otel v1.11.1 go.opentelemetry.io/otel v1.26.0
go.opentelemetry.io/otel/exporters/prometheus v0.33.0 go.opentelemetry.io/otel/exporters/prometheus v0.48.0
go.opentelemetry.io/otel/metric v0.33.0 go.opentelemetry.io/otel/metric v1.26.0
go.opentelemetry.io/otel/sdk/metric v0.33.0 go.opentelemetry.io/otel/sdk/metric v1.26.0
goauthentik.io/api/v3 v3.2023051.3 goauthentik.io/api/v3 v3.2023051.3
golang.org/x/exp v0.0.0-20240506185415-9bf2ced13842 golang.org/x/exp v0.0.0-20240506185415-9bf2ced13842
golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028 golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028
golang.org/x/net v0.25.0 golang.org/x/net v0.25.0
golang.org/x/oauth2 v0.8.0 golang.org/x/oauth2 v0.19.0
golang.org/x/sync v0.7.0 golang.org/x/sync v0.7.0
golang.org/x/term v0.20.0 golang.org/x/term v0.20.0
google.golang.org/api v0.126.0 google.golang.org/api v0.177.0
gopkg.in/yaml.v3 v3.0.1 gopkg.in/yaml.v3 v3.0.1
gorm.io/driver/postgres v1.5.7 gorm.io/driver/postgres v1.5.7
gorm.io/driver/sqlite v1.5.3 gorm.io/driver/sqlite v1.5.3
@ -99,25 +97,30 @@ require (
) )
require ( require (
cloud.google.com/go/compute v1.19.3 // indirect cloud.google.com/go/auth v0.3.0 // indirect
cloud.google.com/go/compute/metadata v0.2.3 // indirect cloud.google.com/go/auth/oauth2adapt v0.2.2 // indirect
github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1 // indirect cloud.google.com/go/compute/metadata v0.3.0 // indirect
github.com/BurntSushi/toml v1.2.1 // indirect dario.cat/mergo v1.0.0 // indirect
github.com/Microsoft/go-winio v0.6.0 // indirect github.com/Azure/go-ansiterm v0.0.0-20230124172434-306776ec8161 // indirect
github.com/BurntSushi/toml v1.3.2 // indirect
github.com/Microsoft/go-winio v0.6.2 // indirect
github.com/Microsoft/hcsshim v0.12.3 // indirect
github.com/XiaoMi/pegasus-go-client v0.0.0-20210427083443-f3b6b08bc4c2 // indirect github.com/XiaoMi/pegasus-go-client v0.0.0-20210427083443-f3b6b08bc4c2 // indirect
github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be // indirect github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be // indirect
github.com/beorn7/perks v1.0.1 // indirect github.com/beorn7/perks v1.0.1 // indirect
github.com/bradfitz/gomemcache v0.0.0-20220106215444-fb4bf637b56d // indirect github.com/bradfitz/gomemcache v0.0.0-20220106215444-fb4bf637b56d // indirect
github.com/cespare/xxhash/v2 v2.2.0 // indirect github.com/cespare/xxhash/v2 v2.3.0 // indirect
github.com/containerd/containerd v1.6.19 // indirect github.com/containerd/containerd v1.7.16 // indirect
github.com/containerd/log v0.1.0 // indirect
github.com/cpuguy83/dockercfg v0.3.1 // indirect github.com/cpuguy83/dockercfg v0.3.1 // indirect
github.com/davecgh/go-spew v1.1.1 // indirect github.com/davecgh/go-spew v1.1.1 // indirect
github.com/dgraph-io/ristretto v0.1.1 // indirect github.com/dgraph-io/ristretto v0.1.1 // indirect
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect
github.com/docker/distribution v2.8.1+incompatible // indirect github.com/distribution/reference v0.6.0 // indirect
github.com/docker/docker v23.0.5+incompatible // indirect github.com/docker/docker v26.1.3+incompatible // indirect
github.com/docker/go-connections v0.4.0 // indirect github.com/docker/go-connections v0.5.0 // indirect
github.com/docker/go-units v0.5.0 // indirect github.com/docker/go-units v0.5.0 // indirect
github.com/felixge/httpsnoop v1.0.4 // indirect
github.com/fredbi/uri v0.0.0-20181227131451-3dcfdacbaaf3 // indirect github.com/fredbi/uri v0.0.0-20181227131451-3dcfdacbaaf3 // indirect
github.com/getlantern/context v0.0.0-20190109183933-c447772a6520 // indirect github.com/getlantern/context v0.0.0-20190109183933-c447772a6520 // indirect
github.com/getlantern/errors v0.0.0-20190325191628-abdb3e3e36f7 // indirect github.com/getlantern/errors v0.0.0-20190325191628-abdb3e3e36f7 // indirect
@ -127,9 +130,9 @@ require (
github.com/getlantern/ops v0.0.0-20190325191751-d70cb0d6f85f // indirect github.com/getlantern/ops v0.0.0-20190325191751-d70cb0d6f85f // indirect
github.com/go-gl/gl v0.0.0-20210813123233-e4099ee2221f // indirect github.com/go-gl/gl v0.0.0-20210813123233-e4099ee2221f // indirect
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20211024062804-40e447a793be // indirect github.com/go-gl/glfw/v3.3/glfw v0.0.0-20211024062804-40e447a793be // indirect
github.com/go-logr/logr v1.2.4 // indirect github.com/go-logr/logr v1.4.1 // indirect
github.com/go-logr/stdr v1.2.2 // indirect github.com/go-logr/stdr v1.2.2 // indirect
github.com/go-ole/go-ole v1.2.6 // indirect github.com/go-ole/go-ole v1.3.0 // indirect
github.com/go-redis/redis/v8 v8.11.5 // indirect github.com/go-redis/redis/v8 v8.11.5 // indirect
github.com/go-stack/stack v1.8.0 // indirect github.com/go-stack/stack v1.8.0 // indirect
github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 // indirect github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 // indirect
@ -138,33 +141,34 @@ require (
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect
github.com/google/btree v1.0.1 // indirect github.com/google/btree v1.0.1 // indirect
github.com/google/pprof v0.0.0-20210407192527-94a9f03dee38 // indirect github.com/google/pprof v0.0.0-20210407192527-94a9f03dee38 // indirect
github.com/google/s2a-go v0.1.4 // indirect github.com/google/s2a-go v0.1.7 // indirect
github.com/googleapis/enterprise-certificate-proxy v0.2.3 // indirect github.com/googleapis/enterprise-certificate-proxy v0.3.2 // indirect
github.com/googleapis/gax-go/v2 v2.10.0 // indirect github.com/googleapis/gax-go/v2 v2.12.3 // indirect
github.com/hashicorp/errwrap v1.1.0 // indirect github.com/hashicorp/errwrap v1.1.0 // indirect
github.com/hashicorp/go-uuid v1.0.2 // indirect github.com/hashicorp/go-uuid v1.0.2 // indirect
github.com/imdario/mergo v0.3.12 // indirect
github.com/inconshreveable/mousetrap v1.1.0 // indirect github.com/inconshreveable/mousetrap v1.1.0 // indirect
github.com/jackc/pgpassfile v1.0.0 // indirect github.com/jackc/pgpassfile v1.0.0 // indirect
github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a // indirect github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a // indirect
github.com/jackc/pgx/v5 v5.4.3 // indirect github.com/jackc/pgx/v5 v5.5.5 // indirect
github.com/jackc/puddle/v2 v2.2.1 // indirect
github.com/jinzhu/inflection v1.0.0 // indirect github.com/jinzhu/inflection v1.0.0 // indirect
github.com/jinzhu/now v1.1.5 // indirect github.com/jinzhu/now v1.1.5 // indirect
github.com/josharian/native v1.1.0 // indirect github.com/josharian/native v1.1.0 // indirect
github.com/kelseyhightower/envconfig v1.4.0 // indirect github.com/kelseyhightower/envconfig v1.4.0 // indirect
github.com/klauspost/compress v1.15.9 // indirect github.com/klauspost/compress v1.17.8 // indirect
github.com/matttproud/golang_protobuf_extensions v1.0.4 // indirect github.com/lufia/plan9stats v0.0.0-20240513124658-fba389f38bae // indirect
github.com/mdlayher/genetlink v1.3.2 // indirect github.com/mdlayher/genetlink v1.3.2 // indirect
github.com/mdlayher/netlink v1.7.2 // indirect github.com/mdlayher/netlink v1.7.2 // indirect
github.com/moby/patternmatcher v0.5.0 // indirect github.com/moby/docker-image-spec v1.3.1 // indirect
github.com/moby/patternmatcher v0.6.0 // indirect
github.com/moby/sys/sequential v0.5.0 // indirect github.com/moby/sys/sequential v0.5.0 // indirect
github.com/moby/term v0.0.0-20221128092401-c43b287e0e0f // indirect github.com/moby/sys/user v0.1.0 // indirect
github.com/moby/term v0.5.0 // indirect
github.com/morikuni/aec v1.0.0 // indirect github.com/morikuni/aec v1.0.0 // indirect
github.com/nxadm/tail v1.4.8 // indirect github.com/nxadm/tail v1.4.8 // indirect
github.com/onsi/ginkgo/v2 v2.9.5 // indirect github.com/onsi/ginkgo/v2 v2.9.5 // indirect
github.com/opencontainers/go-digest v1.0.0 // indirect github.com/opencontainers/go-digest v1.0.0 // indirect
github.com/opencontainers/image-spec v1.1.0-rc2 // indirect github.com/opencontainers/image-spec v1.1.0 // indirect
github.com/opencontainers/runc v1.1.5 // indirect
github.com/oxtoacart/bpool v0.0.0-20190530202638-03653db5a59c // indirect github.com/oxtoacart/bpool v0.0.0-20190530202638-03653db5a59c // indirect
github.com/pegasus-kv/thrift v0.13.0 // indirect github.com/pegasus-kv/thrift v0.13.0 // indirect
github.com/pion/dtls/v2 v2.2.10 // indirect github.com/pion/dtls/v2 v2.2.10 // indirect
@ -173,17 +177,23 @@ require (
github.com/pion/transport/v2 v2.2.4 // indirect github.com/pion/transport/v2 v2.2.4 // indirect
github.com/pkg/errors v0.9.1 // indirect github.com/pkg/errors v0.9.1 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect
github.com/prometheus/client_model v0.3.0 // indirect github.com/power-devops/perfstat v0.0.0-20240221224432-82ca36839d55 // indirect
github.com/prometheus/common v0.37.0 // indirect github.com/prometheus/client_model v0.6.1 // indirect
github.com/prometheus/procfs v0.8.0 // indirect github.com/prometheus/common v0.53.0 // indirect
github.com/prometheus/procfs v0.15.0 // indirect
github.com/shirou/gopsutil/v3 v3.24.4 // indirect
github.com/shoenig/go-m1cpu v0.1.6 // indirect
github.com/spf13/cast v1.5.0 // indirect github.com/spf13/cast v1.5.0 // indirect
github.com/srwiley/oksvg v0.0.0-20200311192757-870daf9aa564 // indirect github.com/srwiley/oksvg v0.0.0-20200311192757-870daf9aa564 // indirect
github.com/srwiley/rasterx v0.0.0-20200120212402-85cb7272f5e9 // indirect github.com/srwiley/rasterx v0.0.0-20200120212402-85cb7272f5e9 // indirect
github.com/vishvananda/netns v0.0.0-20211101163701-50045581ed74 // indirect github.com/tklauser/go-sysconf v0.3.14 // indirect
github.com/tklauser/numcpus v0.8.0 // indirect
github.com/vishvananda/netns v0.0.4 // indirect
github.com/yuin/goldmark v1.4.13 // indirect github.com/yuin/goldmark v1.4.13 // indirect
go.opencensus.io v0.24.0 // indirect go.opencensus.io v0.24.0 // indirect
go.opentelemetry.io/otel/sdk v1.11.1 // indirect go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.51.0 // indirect
go.opentelemetry.io/otel/trace v1.11.1 // indirect go.opentelemetry.io/otel/sdk v1.26.0 // indirect
go.opentelemetry.io/otel/trace v1.26.0 // indirect
go.uber.org/mock v0.4.0 // indirect go.uber.org/mock v0.4.0 // indirect
golang.org/x/image v0.10.0 // indirect golang.org/x/image v0.10.0 // indirect
golang.org/x/mod v0.17.0 // indirect golang.org/x/mod v0.17.0 // indirect
@ -191,13 +201,13 @@ require (
golang.org/x/time v0.5.0 // indirect golang.org/x/time v0.5.0 // indirect
golang.org/x/tools v0.21.0 // indirect golang.org/x/tools v0.21.0 // indirect
golang.zx2c4.com/wintun v0.0.0-20230126152724-0fa3db229ce2 // indirect golang.zx2c4.com/wintun v0.0.0-20230126152724-0fa3db229ce2 // indirect
google.golang.org/appengine v1.6.7 // indirect google.golang.org/genproto/googleapis/api v0.0.0-20240509183442-62759503f434 // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20230530153820-e85fd2cbaebc // indirect google.golang.org/genproto/googleapis/rpc v0.0.0-20240515191416-fc5f0ca64291 // indirect
gopkg.in/square/go-jose.v2 v2.6.0 // indirect gopkg.in/square/go-jose.v2 v2.6.0 // indirect
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 // indirect gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 // indirect
gopkg.in/tomb.v2 v2.0.0-20161208151619-d5d1b5820637 // indirect gopkg.in/tomb.v2 v2.0.0-20161208151619-d5d1b5820637 // indirect
gvisor.dev/gvisor v0.0.0-20230927004350-cbd86285d259 // indirect gvisor.dev/gvisor v0.0.0-20230927004350-cbd86285d259 // indirect
k8s.io/apimachinery v0.23.16 // indirect k8s.io/apimachinery v0.26.2 // indirect
) )
replace github.com/kardianos/service => github.com/netbirdio/service v0.0.0-20230215170314-b923b89432b0 replace github.com/kardianos/service => github.com/netbirdio/service v0.0.0-20230215170314-b923b89432b0

634
go.sum

File diff suppressed because it is too large Load Diff

View File

@ -3,6 +3,7 @@ package iface
import ( import (
"fmt" "fmt"
"net" "net"
"net/netip"
"testing" "testing"
"time" "time"
@ -79,8 +80,19 @@ func TestWGIface_UpdateAddr(t *testing.T) {
t.Error(err) t.Error(err)
} }
assert.Equal(t, addr, addrs[0].String()) var found bool
for _, a := range addrs {
prefix, err := netip.ParsePrefix(a.String())
assert.NoError(t, err)
if prefix.Addr().Is4() {
found = true
assert.Equal(t, addr, prefix.String())
}
}
if !found {
t.Fatal("v4 address not found")
}
} }
func getIfaceAddrs(ifaceName string) ([]net.Addr, error) { func getIfaceAddrs(ifaceName string) ([]net.Addr, error) {

View File

@ -1,5 +1,4 @@
//go:build !ios //go:build !ios
// +build !ios
package iface package iface
@ -121,13 +120,19 @@ func (t *tunDevice) Wrapper() *DeviceWrapper {
func (t *tunDevice) assignAddr() error { func (t *tunDevice) assignAddr() error {
cmd := exec.Command("ifconfig", t.name, "inet", t.address.IP.String(), t.address.IP.String()) cmd := exec.Command("ifconfig", t.name, "inet", t.address.IP.String(), t.address.IP.String())
if out, err := cmd.CombinedOutput(); err != nil { if out, err := cmd.CombinedOutput(); err != nil {
log.Infof(`adding address command "%v" failed with output %s and error: `, cmd.String(), out) log.Errorf("adding address command '%v' failed with output: %s", cmd.String(), out)
return err return err
} }
// dummy ipv6 so routing works
cmd = exec.Command("ifconfig", t.name, "inet6", "fe80::/64")
if out, err := cmd.CombinedOutput(); err != nil {
log.Debugf("adding address command '%v' failed with output: %s", cmd.String(), out)
}
routeCmd := exec.Command("route", "add", "-net", t.address.Network.String(), "-interface", t.name) routeCmd := exec.Command("route", "add", "-net", t.address.Network.String(), "-interface", t.name)
if out, err := routeCmd.CombinedOutput(); err != nil { if out, err := routeCmd.CombinedOutput(); err != nil {
log.Printf(`adding route command "%v" failed with output %s and error: `, routeCmd.String(), out) log.Errorf("adding route command '%v' failed with output: %s", routeCmd.String(), out)
return err return err
} }
return nil return nil

View File

@ -132,6 +132,7 @@ type AccountManager interface {
GetValidatedPeers(account *Account) (map[string]struct{}, error) GetValidatedPeers(account *Account) (map[string]struct{}, error)
SyncAndMarkPeer(peerPubKey string, realIP net.IP) (*nbpeer.Peer, *NetworkMap, error) SyncAndMarkPeer(peerPubKey string, realIP net.IP) (*nbpeer.Peer, *NetworkMap, error)
CancelPeerRoutines(peer *nbpeer.Peer) error CancelPeerRoutines(peer *nbpeer.Peer) error
FindExistingPostureCheck(accountID string, checks *posture.ChecksDefinition) (*posture.Checks, error)
} }
type DefaultAccountManager struct { type DefaultAccountManager struct {
@ -241,6 +242,11 @@ type Account struct {
Settings *Settings `gorm:"embedded;embeddedPrefix:settings_"` Settings *Settings `gorm:"embedded;embeddedPrefix:settings_"`
} }
// Subclass used in gorm to only load settings and not whole account
type AccountSettings struct {
Settings *Settings `gorm:"embedded;embeddedPrefix:settings_"`
}
type UserPermissions struct { type UserPermissions struct {
DashboardView string `json:"dashboard_view"` DashboardView string `json:"dashboard_view"`
} }
@ -1768,6 +1774,8 @@ func (am *DefaultAccountManager) GetAccountFromToken(claims jwtclaims.Authorizat
// //
// Existing user + Existing account + Existing domain reclassified Domain as private -> Nothing changes (index domain) // Existing user + Existing account + Existing domain reclassified Domain as private -> Nothing changes (index domain)
func (am *DefaultAccountManager) getAccountWithAuthorizationClaims(claims jwtclaims.AuthorizationClaims) (*Account, error) { func (am *DefaultAccountManager) getAccountWithAuthorizationClaims(claims jwtclaims.AuthorizationClaims) (*Account, error) {
log.Tracef("getting account with authorization claims. User ID: \"%s\", Account ID: \"%s\", Domain: \"%s\", Domain Category: \"%s\"",
claims.UserId, claims.AccountId, claims.Domain, claims.DomainCategory)
if claims.UserId == "" { if claims.UserId == "" {
return nil, fmt.Errorf("user ID is empty") return nil, fmt.Errorf("user ID is empty")
} }
@ -1788,8 +1796,10 @@ func (am *DefaultAccountManager) getAccountWithAuthorizationClaims(claims jwtcla
} }
} }
start := time.Now()
unlock := am.Store.AcquireGlobalLock() unlock := am.Store.AcquireGlobalLock()
defer unlock() defer unlock()
log.Debugf("Acquired global lock in %s for user %s", time.Since(start), claims.UserId)
// We checked if the domain has a primary account already // We checked if the domain has a primary account already
domainAccount, err := am.Store.GetAccountByPrivateDomain(claims.Domain) domainAccount, err := am.Store.GetAccountByPrivateDomain(claims.Domain)
@ -1840,6 +1850,9 @@ func (am *DefaultAccountManager) getAccountWithAuthorizationClaims(claims jwtcla
func (am *DefaultAccountManager) SyncAndMarkPeer(peerPubKey string, realIP net.IP) (*nbpeer.Peer, *NetworkMap, error) { func (am *DefaultAccountManager) SyncAndMarkPeer(peerPubKey string, realIP net.IP) (*nbpeer.Peer, *NetworkMap, error) {
accountID, err := am.Store.GetAccountIDByPeerPubKey(peerPubKey) accountID, err := am.Store.GetAccountIDByPeerPubKey(peerPubKey)
if err != nil { if err != nil {
if errStatus, ok := status.FromError(err); ok && errStatus.Type() == status.NotFound {
return nil, nil, status.Errorf(status.Unauthenticated, "peer not registered")
}
return nil, nil, err return nil, nil, err
} }
@ -1853,7 +1866,7 @@ func (am *DefaultAccountManager) SyncAndMarkPeer(peerPubKey string, realIP net.I
peer, netMap, err := am.SyncPeer(PeerSync{WireGuardPubKey: peerPubKey}, account) peer, netMap, err := am.SyncPeer(PeerSync{WireGuardPubKey: peerPubKey}, account)
if err != nil { if err != nil {
return nil, nil, mapError(err) return nil, nil, err
} }
err = am.MarkPeerConnected(peerPubKey, true, realIP, account) err = am.MarkPeerConnected(peerPubKey, true, realIP, account)
@ -1867,6 +1880,9 @@ func (am *DefaultAccountManager) SyncAndMarkPeer(peerPubKey string, realIP net.I
func (am *DefaultAccountManager) CancelPeerRoutines(peer *nbpeer.Peer) error { func (am *DefaultAccountManager) CancelPeerRoutines(peer *nbpeer.Peer) error {
accountID, err := am.Store.GetAccountIDByPeerPubKey(peer.Key) accountID, err := am.Store.GetAccountIDByPeerPubKey(peer.Key)
if err != nil { if err != nil {
if errStatus, ok := status.FromError(err); ok && errStatus.Type() == status.NotFound {
return status.Errorf(status.Unauthenticated, "peer not registered")
}
return err return err
} }
@ -1951,6 +1967,10 @@ func (am *DefaultAccountManager) onPeersInvalidated(accountID string) {
am.updateAccountPeers(updatedAccount) am.updateAccountPeers(updatedAccount)
} }
func (am *DefaultAccountManager) FindExistingPostureCheck(accountID string, checks *posture.ChecksDefinition) (*posture.Checks, error) {
return am.Store.GetPostureCheckByChecksDefinition(accountID, checks)
}
// addAllGroup to account object if it doesn't exist // addAllGroup to account object if it doesn't exist
func addAllGroup(account *Account) error { func addAllGroup(account *Account) error {
if len(account.Groups) == 0 { if len(account.Groups) == 0 {

View File

@ -48,8 +48,8 @@ func (MocIntegratedValidator) PreparePeer(accountID string, peer *nbpeer.Peer, p
return peer return peer
} }
func (MocIntegratedValidator) IsNotValidPeer(accountID string, peer *nbpeer.Peer, peersGroup []string, extraSettings *account.ExtraSettings) (bool, bool) { func (MocIntegratedValidator) IsNotValidPeer(accountID string, peer *nbpeer.Peer, peersGroup []string, extraSettings *account.ExtraSettings) (bool, bool, error) {
return false, false return false, false, nil
} }
func (MocIntegratedValidator) PeerDeleted(_, _ string) error { func (MocIntegratedValidator) PeerDeleted(_, _ string) error {

View File

@ -12,6 +12,7 @@ import (
nbgroup "github.com/netbirdio/netbird/management/server/group" nbgroup "github.com/netbirdio/netbird/management/server/group"
nbpeer "github.com/netbirdio/netbird/management/server/peer" nbpeer "github.com/netbirdio/netbird/management/server/peer"
"github.com/netbirdio/netbird/management/server/posture"
"github.com/netbirdio/netbird/management/server/status" "github.com/netbirdio/netbird/management/server/status"
"github.com/netbirdio/netbird/management/server/telemetry" "github.com/netbirdio/netbird/management/server/telemetry"
@ -508,7 +509,7 @@ func (s *FileStore) GetAccountByUser(userID string) (*Account, error) {
accountID, ok := s.UserID2AccountID[userID] accountID, ok := s.UserID2AccountID[userID]
if !ok { if !ok {
return nil, status.Errorf(status.NotFound, "account not found") return nil, status.NewUserNotFoundError(userID)
} }
account, err := s.getAccount(accountID) account, err := s.getAccount(accountID)
@ -539,7 +540,7 @@ func (s *FileStore) GetAccountByPeerID(peerID string) (*Account, error) {
if _, ok := account.Peers[peerID]; !ok { if _, ok := account.Peers[peerID]; !ok {
delete(s.PeerID2AccountID, peerID) delete(s.PeerID2AccountID, peerID)
log.Warnf("removed stale peerID %s to accountID %s index", peerID, accountID) log.Warnf("removed stale peerID %s to accountID %s index", peerID, accountID)
return nil, status.Errorf(status.NotFound, "provided peer doesn't exists %s", peerID) return nil, status.NewPeerNotFoundError(peerID)
} }
return account.Copy(), nil return account.Copy(), nil
@ -552,7 +553,7 @@ func (s *FileStore) GetAccountByPeerPubKey(peerKey string) (*Account, error) {
accountID, ok := s.PeerKeyID2AccountID[peerKey] accountID, ok := s.PeerKeyID2AccountID[peerKey]
if !ok { if !ok {
return nil, status.Errorf(status.NotFound, "provided peer key doesn't exists %s", peerKey) return nil, status.NewPeerNotFoundError(peerKey)
} }
account, err := s.getAccount(accountID) account, err := s.getAccount(accountID)
@ -572,7 +573,7 @@ func (s *FileStore) GetAccountByPeerPubKey(peerKey string) (*Account, error) {
if stale { if stale {
delete(s.PeerKeyID2AccountID, peerKey) delete(s.PeerKeyID2AccountID, peerKey)
log.Warnf("removed stale peerKey %s to accountID %s index", peerKey, accountID) log.Warnf("removed stale peerKey %s to accountID %s index", peerKey, accountID)
return nil, status.Errorf(status.NotFound, "provided peer doesn't exists %s", peerKey) return nil, status.NewPeerNotFoundError(peerKey)
} }
return account.Copy(), nil return account.Copy(), nil
@ -584,12 +585,71 @@ func (s *FileStore) GetAccountIDByPeerPubKey(peerKey string) (string, error) {
accountID, ok := s.PeerKeyID2AccountID[peerKey] accountID, ok := s.PeerKeyID2AccountID[peerKey]
if !ok { if !ok {
return "", status.Errorf(status.NotFound, "provided peer key doesn't exists %s", peerKey) return "", status.NewPeerNotFoundError(peerKey)
} }
return accountID, nil return accountID, nil
} }
func (s *FileStore) GetAccountIDByUserID(userID string) (string, error) {
s.mux.Lock()
defer s.mux.Unlock()
accountID, ok := s.UserID2AccountID[userID]
if !ok {
return "", status.NewUserNotFoundError(userID)
}
return accountID, nil
}
func (s *FileStore) GetAccountIDBySetupKey(setupKey string) (string, error) {
s.mux.Lock()
defer s.mux.Unlock()
accountID, ok := s.SetupKeyID2AccountID[strings.ToUpper(setupKey)]
if !ok {
return "", status.Errorf(status.NotFound, "account not found: provided setup key doesn't exists")
}
return accountID, nil
}
func (s *FileStore) GetPeerByPeerPubKey(peerKey string) (*nbpeer.Peer, error) {
s.mux.Lock()
defer s.mux.Unlock()
accountID, ok := s.PeerKeyID2AccountID[peerKey]
if !ok {
return nil, status.NewPeerNotFoundError(peerKey)
}
account, err := s.getAccount(accountID)
if err != nil {
return nil, err
}
for _, peer := range account.Peers {
if peer.Key == peerKey {
return peer.Copy(), nil
}
}
return nil, status.NewPeerNotFoundError(peerKey)
}
func (s *FileStore) GetAccountSettings(accountID string) (*Settings, error) {
s.mux.Lock()
defer s.mux.Unlock()
account, err := s.getAccount(accountID)
if err != nil {
return nil, err
}
return account.Settings.Copy(), nil
}
// GetInstallationID returns the installation ID from the store // GetInstallationID returns the installation ID from the store
func (s *FileStore) GetInstallationID() string { func (s *FileStore) GetInstallationID() string {
return s.InstallationID return s.InstallationID
@ -667,6 +727,10 @@ func (s *FileStore) SaveUserLastLogin(accountID, userID string, lastLogin time.T
return nil return nil
} }
func (s *FileStore) GetPostureCheckByChecksDefinition(accountID string, checks *posture.ChecksDefinition) (*posture.Checks, error) {
return nil, status.Errorf(status.Internal, "GetPostureCheckByChecksDefinition is not implemented")
}
// Close the FileStore persisting data to disk // Close the FileStore persisting data to disk
func (s *FileStore) Close() error { func (s *FileStore) Close() error {
s.mux.Lock() s.mux.Lock()

View File

@ -245,6 +245,11 @@ func (am *DefaultAccountManager) DeleteGroup(accountId, userId, groupID string)
return &GroupLinkError{"route", string(r.NetID)} return &GroupLinkError{"route", string(r.NetID)}
} }
} }
for _, g := range r.PeerGroups {
if g == groupID {
return &GroupLinkError{"route", string(r.NetID)}
}
}
} }
// check DNS links // check DNS links

View File

@ -70,6 +70,11 @@ func TestDefaultAccountManager_DeleteGroup(t *testing.T) {
"grp-for-route", "grp-for-route",
"route", "route",
}, },
{
"route with peer groups",
"grp-for-route2",
"route",
},
{ {
"name server groups", "name server groups",
"grp-for-name-server-grp", "grp-for-name-server-grp",
@ -138,6 +143,14 @@ func initTestGroupAccount(am *DefaultAccountManager) (*Account, error) {
Peers: make([]string, 0), Peers: make([]string, 0),
} }
groupForRoute2 := &nbgroup.Group{
ID: "grp-for-route2",
AccountID: "account-id",
Name: "Group for route",
Issued: nbgroup.GroupIssuedAPI,
Peers: make([]string, 0),
}
groupForNameServerGroups := &nbgroup.Group{ groupForNameServerGroups := &nbgroup.Group{
ID: "grp-for-name-server-grp", ID: "grp-for-name-server-grp",
AccountID: "account-id", AccountID: "account-id",
@ -183,6 +196,11 @@ func initTestGroupAccount(am *DefaultAccountManager) (*Account, error) {
Groups: []string{groupForRoute.ID}, Groups: []string{groupForRoute.ID},
} }
routePeerGroupResource := &route.Route{
ID: "example route with peer groups",
PeerGroups: []string{groupForRoute2.ID},
}
nameServerGroup := &nbdns.NameServerGroup{ nameServerGroup := &nbdns.NameServerGroup{
ID: "example name server group", ID: "example name server group",
Groups: []string{groupForNameServerGroups.ID}, Groups: []string{groupForNameServerGroups.ID},
@ -209,6 +227,7 @@ func initTestGroupAccount(am *DefaultAccountManager) (*Account, error) {
} }
account := newAccountWithId(accountID, groupAdminUserID, domain) account := newAccountWithId(accountID, groupAdminUserID, domain)
account.Routes[routeResource.ID] = routeResource account.Routes[routeResource.ID] = routeResource
account.Routes[routePeerGroupResource.ID] = routePeerGroupResource
account.NameServerGroups[nameServerGroup.ID] = nameServerGroup account.NameServerGroups[nameServerGroup.ID] = nameServerGroup
account.Policies = append(account.Policies, policy) account.Policies = append(account.Policies, policy)
account.SetupKeys[setupKey.Id] = setupKey account.SetupKeys[setupKey.Id] = setupKey
@ -220,6 +239,7 @@ func initTestGroupAccount(am *DefaultAccountManager) (*Account, error) {
} }
_ = am.SaveGroup(accountID, groupAdminUserID, groupForRoute) _ = am.SaveGroup(accountID, groupAdminUserID, groupForRoute)
_ = am.SaveGroup(accountID, groupAdminUserID, groupForRoute2)
_ = am.SaveGroup(accountID, groupAdminUserID, groupForNameServerGroups) _ = am.SaveGroup(accountID, groupAdminUserID, groupForNameServerGroups)
_ = am.SaveGroup(accountID, groupAdminUserID, groupForPolicies) _ = am.SaveGroup(accountID, groupAdminUserID, groupForPolicies)
_ = am.SaveGroup(accountID, groupAdminUserID, groupForSetupKeys) _ = am.SaveGroup(accountID, groupAdminUserID, groupForSetupKeys)

View File

@ -136,7 +136,7 @@ func (s *GRPCServer) Sync(req *proto.EncryptedMessage, srv proto.ManagementServi
peer, netMap, err := s.accountManager.SyncAndMarkPeer(peerKey.String(), realIP) peer, netMap, err := s.accountManager.SyncAndMarkPeer(peerKey.String(), realIP)
if err != nil { if err != nil {
return err return mapError(err)
} }
err = s.sendInitialSync(peerKey, peer, netMap, srv) err = s.sendInitialSync(peerKey, peer, netMap, srv)
@ -368,7 +368,7 @@ func (s *GRPCServer) Login(ctx context.Context, req *proto.EncryptedMessage) (*p
}) })
if err != nil { if err != nil {
log.Warnf("failed logging in peer %s", peerKey) log.Warnf("failed logging in peer %s: %s", peerKey, err)
return nil, mapError(err) return nil, mapError(err)
} }

View File

@ -3,12 +3,10 @@ package http
import ( import (
"encoding/json" "encoding/json"
"net/http" "net/http"
"net/netip"
"regexp" "regexp"
"slices" "slices"
"github.com/gorilla/mux" "github.com/gorilla/mux"
"github.com/rs/xid"
"github.com/netbirdio/netbird/management/server" "github.com/netbirdio/netbird/management/server"
"github.com/netbirdio/netbird/management/server/geolocation" "github.com/netbirdio/netbird/management/server/geolocation"
@ -59,7 +57,7 @@ func (p *PostureChecksHandler) GetAllPostureChecks(w http.ResponseWriter, r *htt
postureChecks := []*api.PostureCheck{} postureChecks := []*api.PostureCheck{}
for _, postureCheck := range accountPostureChecks { for _, postureCheck := range accountPostureChecks {
postureChecks = append(postureChecks, toPostureChecksResponse(postureCheck)) postureChecks = append(postureChecks, postureCheck.ToAPIResponse())
} }
util.WriteJSONObject(w, postureChecks) util.WriteJSONObject(w, postureChecks)
@ -130,7 +128,7 @@ func (p *PostureChecksHandler) GetPostureCheck(w http.ResponseWriter, r *http.Re
return return
} }
util.WriteJSONObject(w, toPostureChecksResponse(postureChecks)) util.WriteJSONObject(w, postureChecks.ToAPIResponse())
} }
// DeletePostureCheck handles posture check deletion request // DeletePostureCheck handles posture check deletion request
@ -178,55 +176,26 @@ func (p *PostureChecksHandler) savePostureChecks(
return return
} }
if postureChecksID == "" {
postureChecksID = xid.New().String()
}
postureChecks := posture.Checks{
ID: postureChecksID,
Name: req.Name,
Description: req.Description,
}
if nbVersionCheck := req.Checks.NbVersionCheck; nbVersionCheck != nil {
postureChecks.Checks.NBVersionCheck = &posture.NBVersionCheck{
MinVersion: nbVersionCheck.MinVersion,
}
}
if osVersionCheck := req.Checks.OsVersionCheck; osVersionCheck != nil {
postureChecks.Checks.OSVersionCheck = &posture.OSVersionCheck{
Android: (*posture.MinVersionCheck)(osVersionCheck.Android),
Darwin: (*posture.MinVersionCheck)(osVersionCheck.Darwin),
Ios: (*posture.MinVersionCheck)(osVersionCheck.Ios),
Linux: (*posture.MinKernelVersionCheck)(osVersionCheck.Linux),
Windows: (*posture.MinKernelVersionCheck)(osVersionCheck.Windows),
}
}
if geoLocationCheck := req.Checks.GeoLocationCheck; geoLocationCheck != nil { if geoLocationCheck := req.Checks.GeoLocationCheck; geoLocationCheck != nil {
if p.geolocationManager == nil { if p.geolocationManager == nil {
// TODO: update error message to include geo db self hosted doc link when ready // TODO: update error message to include geo db self hosted doc link when ready
util.WriteError(status.Errorf(status.PreconditionFailed, "Geo location database is not initialized"), w) util.WriteError(status.Errorf(status.PreconditionFailed, "Geo location database is not initialized"), w)
return return
} }
postureChecks.Checks.GeoLocationCheck = toPostureGeoLocationCheck(geoLocationCheck)
} }
if peerNetworkRangeCheck := req.Checks.PeerNetworkRangeCheck; peerNetworkRangeCheck != nil { postureChecks, err := posture.NewChecksFromAPIPostureCheckUpdate(req, postureChecksID)
postureChecks.Checks.PeerNetworkRangeCheck, err = toPeerNetworkRangeCheck(peerNetworkRangeCheck)
if err != nil { if err != nil {
util.WriteError(status.Errorf(status.InvalidArgument, "invalid network prefix"), w)
return
}
}
if err := p.accountManager.SavePostureChecks(account.Id, user.Id, &postureChecks); err != nil {
util.WriteError(err, w) util.WriteError(err, w)
return return
} }
util.WriteJSONObject(w, toPostureChecksResponse(&postureChecks)) if err := p.accountManager.SavePostureChecks(account.Id, user.Id, postureChecks); err != nil {
util.WriteError(err, w)
return
}
util.WriteJSONObject(w, postureChecks.ToAPIResponse())
} }
func validatePostureChecksUpdate(req api.PostureCheckUpdate) error { func validatePostureChecksUpdate(req api.PostureCheckUpdate) error {
@ -294,105 +263,3 @@ func validatePostureChecksUpdate(req api.PostureCheckUpdate) error {
return nil return nil
} }
func toPostureChecksResponse(postureChecks *posture.Checks) *api.PostureCheck {
var checks api.Checks
if postureChecks.Checks.NBVersionCheck != nil {
checks.NbVersionCheck = &api.NBVersionCheck{
MinVersion: postureChecks.Checks.NBVersionCheck.MinVersion,
}
}
if postureChecks.Checks.OSVersionCheck != nil {
checks.OsVersionCheck = &api.OSVersionCheck{
Android: (*api.MinVersionCheck)(postureChecks.Checks.OSVersionCheck.Android),
Darwin: (*api.MinVersionCheck)(postureChecks.Checks.OSVersionCheck.Darwin),
Ios: (*api.MinVersionCheck)(postureChecks.Checks.OSVersionCheck.Ios),
Linux: (*api.MinKernelVersionCheck)(postureChecks.Checks.OSVersionCheck.Linux),
Windows: (*api.MinKernelVersionCheck)(postureChecks.Checks.OSVersionCheck.Windows),
}
}
if postureChecks.Checks.GeoLocationCheck != nil {
checks.GeoLocationCheck = toGeoLocationCheckResponse(postureChecks.Checks.GeoLocationCheck)
}
if postureChecks.Checks.PeerNetworkRangeCheck != nil {
checks.PeerNetworkRangeCheck = toPeerNetworkRangeCheckResponse(postureChecks.Checks.PeerNetworkRangeCheck)
}
return &api.PostureCheck{
Id: postureChecks.ID,
Name: postureChecks.Name,
Description: &postureChecks.Description,
Checks: checks,
}
}
func toGeoLocationCheckResponse(geoLocationCheck *posture.GeoLocationCheck) *api.GeoLocationCheck {
locations := make([]api.Location, 0, len(geoLocationCheck.Locations))
for _, loc := range geoLocationCheck.Locations {
l := loc // make G601 happy
var cityName *string
if loc.CityName != "" {
cityName = &l.CityName
}
locations = append(locations, api.Location{
CityName: cityName,
CountryCode: loc.CountryCode,
})
}
return &api.GeoLocationCheck{
Action: api.GeoLocationCheckAction(geoLocationCheck.Action),
Locations: locations,
}
}
func toPostureGeoLocationCheck(apiGeoLocationCheck *api.GeoLocationCheck) *posture.GeoLocationCheck {
locations := make([]posture.Location, 0, len(apiGeoLocationCheck.Locations))
for _, loc := range apiGeoLocationCheck.Locations {
cityName := ""
if loc.CityName != nil {
cityName = *loc.CityName
}
locations = append(locations, posture.Location{
CountryCode: loc.CountryCode,
CityName: cityName,
})
}
return &posture.GeoLocationCheck{
Action: string(apiGeoLocationCheck.Action),
Locations: locations,
}
}
func toPeerNetworkRangeCheckResponse(check *posture.PeerNetworkRangeCheck) *api.PeerNetworkRangeCheck {
netPrefixes := make([]string, 0, len(check.Ranges))
for _, netPrefix := range check.Ranges {
netPrefixes = append(netPrefixes, netPrefix.String())
}
return &api.PeerNetworkRangeCheck{
Ranges: netPrefixes,
Action: api.PeerNetworkRangeCheckAction(check.Action),
}
}
func toPeerNetworkRangeCheck(check *api.PeerNetworkRangeCheck) (*posture.PeerNetworkRangeCheck, error) {
prefixes := make([]netip.Prefix, 0)
for _, prefix := range check.Ranges {
parsedPrefix, err := netip.ParsePrefix(prefix)
if err != nil {
return nil, err
}
prefixes = append(prefixes, parsedPrefix)
}
return &posture.PeerNetworkRangeCheck{
Ranges: prefixes,
Action: string(check.Action),
}, nil
}

View File

@ -154,7 +154,7 @@ func (zc *ZitadelCredentials) requestJWTToken() (*http.Response, error) {
data.Set("client_id", zc.clientConfig.ClientID) data.Set("client_id", zc.clientConfig.ClientID)
data.Set("client_secret", zc.clientConfig.ClientSecret) data.Set("client_secret", zc.clientConfig.ClientSecret)
data.Set("grant_type", zc.clientConfig.GrantType) data.Set("grant_type", zc.clientConfig.GrantType)
data.Set("scope", "urn:zitadel:iam:org:project:id:zitadel:aud") data.Set("scope", "openid urn:zitadel:iam:org:project:id:zitadel:aud")
payload := strings.NewReader(data.Encode()) payload := strings.NewReader(data.Encode())
req, err := http.NewRequest(http.MethodPost, zc.clientConfig.TokenEndpoint, payload) req, err := http.NewRequest(http.MethodPost, zc.clientConfig.TokenEndpoint, payload)

View File

@ -11,7 +11,7 @@ type IntegratedValidator interface {
ValidateExtraSettings(newExtraSettings *account.ExtraSettings, oldExtraSettings *account.ExtraSettings, peers map[string]*nbpeer.Peer, userID string, accountID string) error ValidateExtraSettings(newExtraSettings *account.ExtraSettings, oldExtraSettings *account.ExtraSettings, peers map[string]*nbpeer.Peer, userID string, accountID string) error
ValidatePeer(update *nbpeer.Peer, peer *nbpeer.Peer, userID string, accountID string, dnsDomain string, peersGroup []string, extraSettings *account.ExtraSettings) (*nbpeer.Peer, error) ValidatePeer(update *nbpeer.Peer, peer *nbpeer.Peer, userID string, accountID string, dnsDomain string, peersGroup []string, extraSettings *account.ExtraSettings) (*nbpeer.Peer, error)
PreparePeer(accountID string, peer *nbpeer.Peer, peersGroup []string, extraSettings *account.ExtraSettings) *nbpeer.Peer PreparePeer(accountID string, peer *nbpeer.Peer, peersGroup []string, extraSettings *account.ExtraSettings) *nbpeer.Peer
IsNotValidPeer(accountID string, peer *nbpeer.Peer, peersGroup []string, extraSettings *account.ExtraSettings) (bool, bool) IsNotValidPeer(accountID string, peer *nbpeer.Peer, peersGroup []string, extraSettings *account.ExtraSettings) (bool, bool, error)
GetValidatedPeers(accountID string, groups map[string]*nbgroup.Group, peers map[string]*nbpeer.Peer, extraSettings *account.ExtraSettings) (map[string]struct{}, error) GetValidatedPeers(accountID string, groups map[string]*nbgroup.Group, peers map[string]*nbpeer.Peer, extraSettings *account.ExtraSettings) (map[string]struct{}, error)
PeerDeleted(accountID, peerID string) error PeerDeleted(accountID, peerID string) error
SetPeerInvalidationListener(fn func(accountID string)) SetPeerInvalidationListener(fn func(accountID string))

View File

@ -469,8 +469,8 @@ func (MocIntegratedValidator) PreparePeer(accountID string, peer *nbpeer.Peer, p
return peer return peer
} }
func (MocIntegratedValidator) IsNotValidPeer(accountID string, peer *nbpeer.Peer, peersGroup []string, extraSettings *account.ExtraSettings) (bool, bool) { func (MocIntegratedValidator) IsNotValidPeer(accountID string, peer *nbpeer.Peer, peersGroup []string, extraSettings *account.ExtraSettings) (bool, bool, error) {
return false, false return false, false, nil
} }
func (MocIntegratedValidator) PeerDeleted(_, _ string) error { func (MocIntegratedValidator) PeerDeleted(_, _ string) error {

View File

@ -26,7 +26,7 @@ const (
// defaultPushInterval default interval to push metrics // defaultPushInterval default interval to push metrics
defaultPushInterval = 24 * time.Hour defaultPushInterval = 24 * time.Hour
// requestTimeout http request timeout // requestTimeout http request timeout
requestTimeout = 30 * time.Second requestTimeout = 45 * time.Second
) )
type getTokenResponse struct { type getTokenResponse struct {
@ -98,10 +98,7 @@ func (w *Worker) Run() {
} }
func (w *Worker) sendMetrics() error { func (w *Worker) sendMetrics() error {
ctx, cancel := context.WithTimeout(w.ctx, requestTimeout) apiKey, err := getAPIKey(w.ctx)
defer cancel()
apiKey, err := getAPIKey(ctx)
if err != nil { if err != nil {
return err return err
} }
@ -115,7 +112,7 @@ func (w *Worker) sendMetrics() error {
httpClient := http.Client{} httpClient := http.Client{}
exportJobReq, err := createPostRequest(ctx, payloadEndpoint+"/capture/", payloadString) exportJobReq, err := createPostRequest(w.ctx, payloadEndpoint+"/capture/", payloadString)
if err != nil { if err != nil {
return fmt.Errorf("unable to create metrics post request %v", err) return fmt.Errorf("unable to create metrics post request %v", err)
} }
@ -328,6 +325,8 @@ func (w *Worker) generateProperties() properties {
} }
func getAPIKey(ctx context.Context) (string, error) { func getAPIKey(ctx context.Context) (string, error) {
ctx, cancel := context.WithTimeout(ctx, requestTimeout)
defer cancel()
httpClient := http.Client{} httpClient := http.Client{}
@ -375,6 +374,8 @@ func buildMetricsPayload(payload pushPayload) (string, error) {
} }
func createPostRequest(ctx context.Context, endpoint string, payloadStr string) (*http.Request, error) { func createPostRequest(ctx context.Context, endpoint string, payloadStr string) (*http.Request, error) {
ctx, cancel := context.WithTimeout(ctx, requestTimeout)
defer cancel()
reqURL := endpoint reqURL := endpoint
payload := strings.NewReader(payloadStr) payload := strings.NewReader(payloadStr)

View File

@ -95,6 +95,7 @@ type MockAccountManager struct {
GetIdpManagerFunc func() idp.Manager GetIdpManagerFunc func() idp.Manager
UpdateIntegratedValidatorGroupsFunc func(accountID string, userID string, groups []string) error UpdateIntegratedValidatorGroupsFunc func(accountID string, userID string, groups []string) error
GroupValidationFunc func(accountId string, groups []string) (bool, error) GroupValidationFunc func(accountId string, groups []string) (bool, error)
FindExistingPostureCheckFunc func(accountID string, checks *posture.ChecksDefinition) (*posture.Checks, error)
} }
func (am *MockAccountManager) SyncAndMarkPeer(peerPubKey string, realIP net.IP) (*nbpeer.Peer, *server.NetworkMap, error) { func (am *MockAccountManager) SyncAndMarkPeer(peerPubKey string, realIP net.IP) (*nbpeer.Peer, *server.NetworkMap, error) {
@ -734,3 +735,11 @@ func (am *MockAccountManager) GroupValidation(accountId string, groups []string)
} }
return false, status.Errorf(codes.Unimplemented, "method GroupValidation is not implemented") return false, status.Errorf(codes.Unimplemented, "method GroupValidation is not implemented")
} }
// FindExistingPostureCheck mocks FindExistingPostureCheck of the AccountManager interface
func (am *MockAccountManager) FindExistingPostureCheck(accountID string, checks *posture.ChecksDefinition) (*posture.Checks, error) {
if am.FindExistingPostureCheckFunc != nil {
return am.FindExistingPostureCheckFunc(accountID, checks)
}
return nil, status.Errorf(codes.Unimplemented, "method FindExistingPostureCheck is not implemented")
}

View File

@ -335,24 +335,29 @@ func (am *DefaultAccountManager) AddPeer(setupKey, userID string, peer *nbpeer.P
} }
upperKey := strings.ToUpper(setupKey) upperKey := strings.ToUpper(setupKey)
var account *Account var accountID string
var err error var err error
addedByUser := false addedByUser := false
if len(userID) > 0 { if len(userID) > 0 {
addedByUser = true addedByUser = true
account, err = am.Store.GetAccountByUser(userID) accountID, err = am.Store.GetAccountIDByUserID(userID)
} else { } else {
account, err = am.Store.GetAccountBySetupKey(setupKey) accountID, err = am.Store.GetAccountIDBySetupKey(setupKey)
} }
if err != nil { if err != nil {
return nil, nil, status.Errorf(status.NotFound, "failed adding new peer: account not found") return nil, nil, status.Errorf(status.NotFound, "failed adding new peer: account not found")
} }
unlock := am.Store.AcquireAccountWriteLock(account.Id) unlock := am.Store.AcquireAccountWriteLock(accountID)
defer unlock() defer func() {
if unlock != nil {
unlock()
}
}()
var account *Account
// ensure that we consider modification happened meanwhile (because we were outside the account lock when we fetched the account) // ensure that we consider modification happened meanwhile (because we were outside the account lock when we fetched the account)
account, err = am.Store.GetAccount(account.Id) account, err = am.Store.GetAccount(accountID)
if err != nil { if err != nil {
return nil, nil, err return nil, nil, err
} }
@ -485,6 +490,10 @@ func (am *DefaultAccountManager) AddPeer(setupKey, userID string, peer *nbpeer.P
return nil, nil, err return nil, nil, err
} }
// Account is saved, we can release the lock
unlock()
unlock = nil
opEvent.TargetID = newPeer.ID opEvent.TargetID = newPeer.ID
opEvent.Meta = newPeer.EventMeta(am.GetDNSDomain()) opEvent.Meta = newPeer.EventMeta(am.GetDNSDomain())
if !addedByUser { if !addedByUser {
@ -507,7 +516,7 @@ func (am *DefaultAccountManager) AddPeer(setupKey, userID string, peer *nbpeer.P
func (am *DefaultAccountManager) SyncPeer(sync PeerSync, account *Account) (*nbpeer.Peer, *NetworkMap, error) { func (am *DefaultAccountManager) SyncPeer(sync PeerSync, account *Account) (*nbpeer.Peer, *NetworkMap, error) {
peer, err := account.FindPeerByPubKey(sync.WireGuardPubKey) peer, err := account.FindPeerByPubKey(sync.WireGuardPubKey)
if err != nil { if err != nil {
return nil, nil, status.Errorf(status.Unauthenticated, "peer is not registered") return nil, nil, status.NewPeerNotRegisteredError()
} }
err = checkIfPeerOwnerIsBlocked(peer, account) err = checkIfPeerOwnerIsBlocked(peer, account)
@ -515,11 +524,15 @@ func (am *DefaultAccountManager) SyncPeer(sync PeerSync, account *Account) (*nbp
return nil, nil, err return nil, nil, err
} }
if peerLoginExpired(peer, account) { if peerLoginExpired(peer, account.Settings) {
return nil, nil, status.Errorf(status.PermissionDenied, "peer login has expired, please log in once more") return nil, nil, status.Errorf(status.PermissionDenied, "peer login has expired, please log in once more")
} }
peerNotValid, isStatusChanged := am.integratedPeerValidator.IsNotValidPeer(account.Id, peer, account.GetPeerGroupsList(peer.ID), account.Settings.Extra) peerNotValid, isStatusChanged, err := am.integratedPeerValidator.IsNotValidPeer(account.Id, peer, account.GetPeerGroupsList(peer.ID), account.Settings.Extra)
if err != nil {
return nil, nil, err
}
if peerNotValid { if peerNotValid {
emptyMap := &NetworkMap{ emptyMap := &NetworkMap{
Network: account.Network.Copy(), Network: account.Network.Copy(),
@ -541,7 +554,7 @@ func (am *DefaultAccountManager) SyncPeer(sync PeerSync, account *Account) (*nbp
// LoginPeer logs in or registers a peer. // LoginPeer logs in or registers a peer.
// If peer doesn't exist the function checks whether a setup key or a user is present and registers a new peer if so. // If peer doesn't exist the function checks whether a setup key or a user is present and registers a new peer if so.
func (am *DefaultAccountManager) LoginPeer(login PeerLogin) (*nbpeer.Peer, *NetworkMap, error) { func (am *DefaultAccountManager) LoginPeer(login PeerLogin) (*nbpeer.Peer, *NetworkMap, error) {
account, err := am.Store.GetAccountByPeerPubKey(login.WireGuardPubKey) accountID, err := am.Store.GetAccountIDByPeerPubKey(login.WireGuardPubKey)
if err != nil { if err != nil {
if errStatus, ok := status.FromError(err); ok && errStatus.Type() == status.NotFound { if errStatus, ok := status.FromError(err); ok && errStatus.Type() == status.NotFound {
// we couldn't find this peer by its public key which can mean that peer hasn't been registered yet. // we couldn't find this peer by its public key which can mean that peer hasn't been registered yet.
@ -570,19 +583,59 @@ func (am *DefaultAccountManager) LoginPeer(login PeerLogin) (*nbpeer.Peer, *Netw
return nil, nil, status.Errorf(status.Internal, "failed while logging in peer") return nil, nil, status.Errorf(status.Internal, "failed while logging in peer")
} }
// we found the peer, and we follow a normal login flow peer, err := am.Store.GetPeerByPeerPubKey(login.WireGuardPubKey)
unlock := am.Store.AcquireAccountWriteLock(account.Id) if err != nil {
defer unlock() return nil, nil, status.NewPeerNotRegisteredError()
}
accSettings, err := am.Store.GetAccountSettings(accountID)
if err != nil {
return nil, nil, status.Errorf(status.Internal, "failed to get account settings: %s", err)
}
var isWriteLock bool
// duplicated logic from after the lock to have an early exit
expired := peerLoginExpired(peer, accSettings)
switch {
case expired:
if err := checkAuth(login.UserID, peer); err != nil {
return nil, nil, err
}
isWriteLock = true
log.Debugf("peer login expired, acquiring write lock")
case peer.UpdateMetaIfNew(login.Meta):
isWriteLock = true
log.Debugf("peer changed meta, acquiring write lock")
default:
isWriteLock = false
log.Debugf("peer meta is the same, acquiring read lock")
}
var unlock func()
if isWriteLock {
unlock = am.Store.AcquireAccountWriteLock(accountID)
} else {
unlock = am.Store.AcquireAccountReadLock(accountID)
}
defer func() {
if unlock != nil {
unlock()
}
}()
// fetch the account from the store once more after acquiring lock to avoid concurrent updates inconsistencies // fetch the account from the store once more after acquiring lock to avoid concurrent updates inconsistencies
account, err = am.Store.GetAccount(account.Id) account, err := am.Store.GetAccount(accountID)
if err != nil { if err != nil {
return nil, nil, err return nil, nil, err
} }
peer, err := account.FindPeerByPubKey(login.WireGuardPubKey) peer, err = account.FindPeerByPubKey(login.WireGuardPubKey)
if err != nil { if err != nil {
return nil, nil, status.Errorf(status.Unauthenticated, "peer is not registered") return nil, nil, status.NewPeerNotRegisteredError()
} }
err = checkIfPeerOwnerIsBlocked(peer, account) err = checkIfPeerOwnerIsBlocked(peer, account)
@ -593,7 +646,7 @@ func (am *DefaultAccountManager) LoginPeer(login PeerLogin) (*nbpeer.Peer, *Netw
// this flag prevents unnecessary calls to the persistent store. // this flag prevents unnecessary calls to the persistent store.
shouldStoreAccount := false shouldStoreAccount := false
updateRemotePeers := false updateRemotePeers := false
if peerLoginExpired(peer, account) { if peerLoginExpired(peer, account.Settings) {
err = checkAuth(login.UserID, peer) err = checkAuth(login.UserID, peer)
if err != nil { if err != nil {
return nil, nil, err return nil, nil, err
@ -614,7 +667,10 @@ func (am *DefaultAccountManager) LoginPeer(login PeerLogin) (*nbpeer.Peer, *Netw
am.StoreEvent(login.UserID, peer.ID, account.Id, activity.UserLoggedInPeer, peer.EventMeta(am.GetDNSDomain())) am.StoreEvent(login.UserID, peer.ID, account.Id, activity.UserLoggedInPeer, peer.EventMeta(am.GetDNSDomain()))
} }
isRequiresApproval, isStatusChanged := am.integratedPeerValidator.IsNotValidPeer(account.Id, peer, account.GetPeerGroupsList(peer.ID), account.Settings.Extra) isRequiresApproval, isStatusChanged, err := am.integratedPeerValidator.IsNotValidPeer(account.Id, peer, account.GetPeerGroupsList(peer.ID), account.Settings.Extra)
if err != nil {
return nil, nil, err
}
peer, updated := updatePeerMeta(peer, login.Meta, account) peer, updated := updatePeerMeta(peer, login.Meta, account)
if updated { if updated {
shouldStoreAccount = true shouldStoreAccount = true
@ -626,11 +682,17 @@ func (am *DefaultAccountManager) LoginPeer(login PeerLogin) (*nbpeer.Peer, *Netw
} }
if shouldStoreAccount { if shouldStoreAccount {
if !isWriteLock {
log.Errorf("account %s should be stored but is not write locked", accountID)
return nil, nil, status.Errorf(status.Internal, "account should be stored but is not write locked")
}
err = am.Store.SaveAccount(account) err = am.Store.SaveAccount(account)
if err != nil { if err != nil {
return nil, nil, err return nil, nil, err
} }
} }
unlock()
unlock = nil
if updateRemotePeers || isStatusChanged { if updateRemotePeers || isStatusChanged {
am.updateAccountPeers(account) am.updateAccountPeers(account)
@ -676,9 +738,9 @@ func checkAuth(loginUserID string, peer *nbpeer.Peer) error {
return nil return nil
} }
func peerLoginExpired(peer *nbpeer.Peer, account *Account) bool { func peerLoginExpired(peer *nbpeer.Peer, settings *Settings) bool {
expired, expiresIn := peer.LoginExpired(account.Settings.PeerLoginExpiration) expired, expiresIn := peer.LoginExpired(settings.PeerLoginExpiration)
expired = account.Settings.PeerLoginExpirationEnabled && expired expired = settings.PeerLoginExpirationEnabled && expired
if expired || peer.Status.LoginExpired { if expired || peer.Status.LoginExpired {
log.Debugf("peer's %s login expired %v ago", peer.ID, expiresIn) log.Debugf("peer's %s login expired %v ago", peer.ID, expiresIn)
return true return true

View File

@ -216,7 +216,9 @@ func (p *Peer) FQDN(dnsDomain string) string {
// EventMeta returns activity event meta related to the peer // EventMeta returns activity event meta related to the peer
func (p *Peer) EventMeta(dnsDomain string) map[string]any { func (p *Peer) EventMeta(dnsDomain string) map[string]any {
return map[string]any{"name": p.Name, "fqdn": p.FQDN(dnsDomain), "ip": p.IP, "created_at": p.CreatedAt} return map[string]any{"name": p.Name, "fqdn": p.FQDN(dnsDomain), "ip": p.IP, "created_at": p.CreatedAt,
"location_city_name": p.Location.CityName, "location_country_code": p.Location.CountryCode,
"location_geo_name_id": p.Location.GeoNameID, "location_connection_ip": p.Location.ConnectionIP}
} }
// Copy PeerStatus // Copy PeerStatus

View File

@ -5,8 +5,11 @@ import (
"net/netip" "net/netip"
"github.com/hashicorp/go-version" "github.com/hashicorp/go-version"
"github.com/rs/xid"
"github.com/netbirdio/netbird/management/server/http/api"
nbpeer "github.com/netbirdio/netbird/management/server/peer" nbpeer "github.com/netbirdio/netbird/management/server/peer"
"github.com/netbirdio/netbird/management/server/status"
) )
const ( const (
@ -136,6 +139,96 @@ func (pc *Checks) GetChecks() []Check {
return checks return checks
} }
func NewChecksFromAPIPostureCheck(source api.PostureCheck) (*Checks, error) {
description := ""
if source.Description != nil {
description = *source.Description
}
return buildPostureCheck(source.Id, source.Name, description, source.Checks)
}
func NewChecksFromAPIPostureCheckUpdate(source api.PostureCheckUpdate, postureChecksID string) (*Checks, error) {
return buildPostureCheck(postureChecksID, source.Name, source.Description, *source.Checks)
}
func buildPostureCheck(postureChecksID string, name string, description string, checks api.Checks) (*Checks, error) {
if postureChecksID == "" {
postureChecksID = xid.New().String()
}
postureChecks := Checks{
ID: postureChecksID,
Name: name,
Description: description,
}
if nbVersionCheck := checks.NbVersionCheck; nbVersionCheck != nil {
postureChecks.Checks.NBVersionCheck = &NBVersionCheck{
MinVersion: nbVersionCheck.MinVersion,
}
}
if osVersionCheck := checks.OsVersionCheck; osVersionCheck != nil {
postureChecks.Checks.OSVersionCheck = &OSVersionCheck{
Android: (*MinVersionCheck)(osVersionCheck.Android),
Darwin: (*MinVersionCheck)(osVersionCheck.Darwin),
Ios: (*MinVersionCheck)(osVersionCheck.Ios),
Linux: (*MinKernelVersionCheck)(osVersionCheck.Linux),
Windows: (*MinKernelVersionCheck)(osVersionCheck.Windows),
}
}
if geoLocationCheck := checks.GeoLocationCheck; geoLocationCheck != nil {
postureChecks.Checks.GeoLocationCheck = toPostureGeoLocationCheck(geoLocationCheck)
}
var err error
if peerNetworkRangeCheck := checks.PeerNetworkRangeCheck; peerNetworkRangeCheck != nil {
postureChecks.Checks.PeerNetworkRangeCheck, err = toPeerNetworkRangeCheck(peerNetworkRangeCheck)
if err != nil {
return nil, status.Errorf(status.InvalidArgument, "invalid network prefix")
}
}
return &postureChecks, nil
}
func (pc *Checks) ToAPIResponse() *api.PostureCheck {
var checks api.Checks
if pc.Checks.NBVersionCheck != nil {
checks.NbVersionCheck = &api.NBVersionCheck{
MinVersion: pc.Checks.NBVersionCheck.MinVersion,
}
}
if pc.Checks.OSVersionCheck != nil {
checks.OsVersionCheck = &api.OSVersionCheck{
Android: (*api.MinVersionCheck)(pc.Checks.OSVersionCheck.Android),
Darwin: (*api.MinVersionCheck)(pc.Checks.OSVersionCheck.Darwin),
Ios: (*api.MinVersionCheck)(pc.Checks.OSVersionCheck.Ios),
Linux: (*api.MinKernelVersionCheck)(pc.Checks.OSVersionCheck.Linux),
Windows: (*api.MinKernelVersionCheck)(pc.Checks.OSVersionCheck.Windows),
}
}
if pc.Checks.GeoLocationCheck != nil {
checks.GeoLocationCheck = toGeoLocationCheckResponse(pc.Checks.GeoLocationCheck)
}
if pc.Checks.PeerNetworkRangeCheck != nil {
checks.PeerNetworkRangeCheck = toPeerNetworkRangeCheckResponse(pc.Checks.PeerNetworkRangeCheck)
}
return &api.PostureCheck{
Id: pc.ID,
Name: pc.Name,
Description: &pc.Description,
Checks: checks,
}
}
func (pc *Checks) Validate() error { func (pc *Checks) Validate() error {
if check := pc.Checks.NBVersionCheck; check != nil { if check := pc.Checks.NBVersionCheck; check != nil {
if !isVersionValid(check.MinVersion) { if !isVersionValid(check.MinVersion) {
@ -192,3 +285,70 @@ func isVersionValid(ver string) bool {
return false return false
} }
func toGeoLocationCheckResponse(geoLocationCheck *GeoLocationCheck) *api.GeoLocationCheck {
locations := make([]api.Location, 0, len(geoLocationCheck.Locations))
for _, loc := range geoLocationCheck.Locations {
l := loc // make G601 happy
var cityName *string
if loc.CityName != "" {
cityName = &l.CityName
}
locations = append(locations, api.Location{
CityName: cityName,
CountryCode: loc.CountryCode,
})
}
return &api.GeoLocationCheck{
Action: api.GeoLocationCheckAction(geoLocationCheck.Action),
Locations: locations,
}
}
func toPostureGeoLocationCheck(apiGeoLocationCheck *api.GeoLocationCheck) *GeoLocationCheck {
locations := make([]Location, 0, len(apiGeoLocationCheck.Locations))
for _, loc := range apiGeoLocationCheck.Locations {
cityName := ""
if loc.CityName != nil {
cityName = *loc.CityName
}
locations = append(locations, Location{
CountryCode: loc.CountryCode,
CityName: cityName,
})
}
return &GeoLocationCheck{
Action: string(apiGeoLocationCheck.Action),
Locations: locations,
}
}
func toPeerNetworkRangeCheckResponse(check *PeerNetworkRangeCheck) *api.PeerNetworkRangeCheck {
netPrefixes := make([]string, 0, len(check.Ranges))
for _, netPrefix := range check.Ranges {
netPrefixes = append(netPrefixes, netPrefix.String())
}
return &api.PeerNetworkRangeCheck{
Ranges: netPrefixes,
Action: api.PeerNetworkRangeCheckAction(check.Action),
}
}
func toPeerNetworkRangeCheck(check *api.PeerNetworkRangeCheck) (*PeerNetworkRangeCheck, error) {
prefixes := make([]netip.Prefix, 0)
for _, prefix := range check.Ranges {
parsedPrefix, err := netip.ParsePrefix(prefix)
if err != nil {
return nil, err
}
prefixes = append(prefixes, parsedPrefix)
}
return &PeerNetworkRangeCheck{
Ranges: prefixes,
Action: string(check.Action),
}, nil
}

View File

@ -1,6 +1,7 @@
package server package server
import ( import (
"encoding/json"
"errors" "errors"
"fmt" "fmt"
"path/filepath" "path/filepath"
@ -390,7 +391,7 @@ func (s *SqlStore) GetAccount(accountID string) (*Account, error) {
Preload(clause.Associations). Preload(clause.Associations).
First(&account, "id = ?", accountID) First(&account, "id = ?", accountID)
if result.Error != nil { if result.Error != nil {
log.Errorf("error when getting account from the store: %s", result.Error) log.Errorf("error when getting account %s from the store: %s", accountID, result.Error)
if errors.Is(result.Error, gorm.ErrRecordNotFound) { if errors.Is(result.Error, gorm.ErrRecordNotFound) {
return nil, status.Errorf(status.NotFound, "account not found") return nil, status.Errorf(status.NotFound, "account not found")
} }
@ -457,7 +458,6 @@ func (s *SqlStore) GetAccountByUser(userID string) (*Account, error) {
if errors.Is(result.Error, gorm.ErrRecordNotFound) { if errors.Is(result.Error, gorm.ErrRecordNotFound) {
return nil, status.Errorf(status.NotFound, "account not found: index lookup failed") return nil, status.Errorf(status.NotFound, "account not found: index lookup failed")
} }
log.Errorf("error when getting user from the store: %s", result.Error)
return nil, status.Errorf(status.Internal, "issue getting account from store") return nil, status.Errorf(status.Internal, "issue getting account from store")
} }
@ -520,6 +520,61 @@ func (s *SqlStore) GetAccountIDByPeerPubKey(peerKey string) (string, error) {
return accountID, nil return accountID, nil
} }
func (s *SqlStore) GetAccountIDByUserID(userID string) (string, error) {
var user User
var accountID string
result := s.db.Model(&user).Select("account_id").Where("id = ?", userID).First(&accountID)
if result.Error != nil {
if errors.Is(result.Error, gorm.ErrRecordNotFound) {
return "", status.Errorf(status.NotFound, "account not found: index lookup failed")
}
return "", status.Errorf(status.Internal, "issue getting account from store")
}
return accountID, nil
}
func (s *SqlStore) GetAccountIDBySetupKey(setupKey string) (string, error) {
var key SetupKey
var accountID string
result := s.db.Model(&key).Select("account_id").Where("key = ?", strings.ToUpper(setupKey)).First(&accountID)
if result.Error != nil {
if errors.Is(result.Error, gorm.ErrRecordNotFound) {
return "", status.Errorf(status.NotFound, "account not found: index lookup failed")
}
log.Errorf("error when getting setup key from the store: %s", result.Error)
return "", status.Errorf(status.Internal, "issue getting setup key from store")
}
return accountID, nil
}
func (s *SqlStore) GetPeerByPeerPubKey(peerKey string) (*nbpeer.Peer, error) {
var peer nbpeer.Peer
result := s.db.First(&peer, "key = ?", peerKey)
if result.Error != nil {
if errors.Is(result.Error, gorm.ErrRecordNotFound) {
return nil, status.Errorf(status.NotFound, "peer not found")
}
log.Errorf("error when getting peer from the store: %s", result.Error)
return nil, status.Errorf(status.Internal, "issue getting peer from store")
}
return &peer, nil
}
func (s *SqlStore) GetAccountSettings(accountID string) (*Settings, error) {
var accountSettings AccountSettings
if err := s.db.Model(&Account{}).Where("id = ?", accountID).First(&accountSettings).Error; err != nil {
if errors.Is(err, gorm.ErrRecordNotFound) {
return nil, status.Errorf(status.NotFound, "settings not found")
}
log.Errorf("error when getting settings from the store: %s", err)
return nil, status.Errorf(status.Internal, "issue getting settings from store")
}
return accountSettings.Settings, nil
}
// SaveUserLastLogin stores the last login time for a user in DB. // SaveUserLastLogin stores the last login time for a user in DB.
func (s *SqlStore) SaveUserLastLogin(accountID, userID string, lastLogin time.Time) error { func (s *SqlStore) SaveUserLastLogin(accountID, userID string, lastLogin time.Time) error {
var user User var user User
@ -529,7 +584,6 @@ func (s *SqlStore) SaveUserLastLogin(accountID, userID string, lastLogin time.Ti
if errors.Is(result.Error, gorm.ErrRecordNotFound) { if errors.Is(result.Error, gorm.ErrRecordNotFound) {
return status.Errorf(status.NotFound, "user %s not found", userID) return status.Errorf(status.NotFound, "user %s not found", userID)
} }
log.Errorf("error when getting user from the store: %s", result.Error)
return status.Errorf(status.Internal, "issue getting user from store") return status.Errorf(status.Internal, "issue getting user from store")
} }
@ -538,6 +592,21 @@ func (s *SqlStore) SaveUserLastLogin(accountID, userID string, lastLogin time.Ti
return s.db.Save(user).Error return s.db.Save(user).Error
} }
func (s *SqlStore) GetPostureCheckByChecksDefinition(accountID string, checks *posture.ChecksDefinition) (*posture.Checks, error) {
definitionJSON, err := json.Marshal(checks)
if err != nil {
return nil, err
}
var postureCheck posture.Checks
err = s.db.Where("account_id = ? AND checks = ?", accountID, string(definitionJSON)).First(&postureCheck).Error
if err != nil {
return nil, err
}
return &postureCheck, nil
}
// Close closes the underlying DB connection // Close closes the underlying DB connection
func (s *SqlStore) Close() error { func (s *SqlStore) Close() error {
sql, err := s.db.DB() sql, err := s.db.DB()

View File

@ -13,6 +13,7 @@ import (
nbdns "github.com/netbirdio/netbird/dns" nbdns "github.com/netbirdio/netbird/dns"
nbgroup "github.com/netbirdio/netbird/management/server/group" nbgroup "github.com/netbirdio/netbird/management/server/group"
"github.com/netbirdio/netbird/management/server/testutil"
"github.com/google/uuid" "github.com/google/uuid"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
@ -618,7 +619,7 @@ func newAccount(store Store, id int) error {
func newPostgresqlStore(t *testing.T) *SqlStore { func newPostgresqlStore(t *testing.T) *SqlStore {
t.Helper() t.Helper()
cleanUp, err := createPGDB() cleanUp, err := testutil.CreatePGDB()
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
@ -649,7 +650,7 @@ func newPostgresqlStoreFromFile(t *testing.T, filename string) *SqlStore {
fStore, err := NewFileStore(storeDir, nil) fStore, err := NewFileStore(storeDir, nil)
require.NoError(t, err) require.NoError(t, err)
cleanUp, err := createPGDB() cleanUp, err := testutil.CreatePGDB()
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }

View File

@ -75,3 +75,23 @@ func FromError(err error) (s *Error, ok bool) {
} }
return nil, false return nil, false
} }
// NewPeerNotFoundError creates a new Error with NotFound type for a missing peer
func NewPeerNotFoundError(peerKey string) error {
return Errorf(NotFound, "peer not found: %s", peerKey)
}
// NewAccountNotFoundError creates a new Error with NotFound type for a missing account
func NewAccountNotFoundError(accountKey string) error {
return Errorf(NotFound, "account not found: %s", accountKey)
}
// NewUserNotFoundError creates a new Error with NotFound type for a missing user
func NewUserNotFoundError(userKey string) error {
return Errorf(NotFound, "user not found: %s", userKey)
}
// NewPeerNotRegisteredError creates a new Error with NotFound type for a missing peer
func NewPeerNotRegisteredError() error {
return Errorf(Unauthenticated, "peer is not registered")
}

View File

@ -1,7 +1,6 @@
package server package server
import ( import (
"context"
"fmt" "fmt"
"net" "net"
"net/netip" "net/netip"
@ -11,14 +10,13 @@ import (
"time" "time"
log "github.com/sirupsen/logrus" log "github.com/sirupsen/logrus"
"github.com/testcontainers/testcontainers-go"
"github.com/testcontainers/testcontainers-go/modules/postgres"
"github.com/testcontainers/testcontainers-go/wait"
"gorm.io/gorm" "gorm.io/gorm"
"github.com/netbirdio/netbird/management/server/migration" "github.com/netbirdio/netbird/management/server/migration"
nbpeer "github.com/netbirdio/netbird/management/server/peer" nbpeer "github.com/netbirdio/netbird/management/server/peer"
"github.com/netbirdio/netbird/management/server/posture"
"github.com/netbirdio/netbird/management/server/telemetry" "github.com/netbirdio/netbird/management/server/telemetry"
"github.com/netbirdio/netbird/management/server/testutil"
"github.com/netbirdio/netbird/route" "github.com/netbirdio/netbird/route"
) )
@ -29,11 +27,14 @@ type Store interface {
GetAccountByUser(userID string) (*Account, error) GetAccountByUser(userID string) (*Account, error)
GetAccountByPeerPubKey(peerKey string) (*Account, error) GetAccountByPeerPubKey(peerKey string) (*Account, error)
GetAccountIDByPeerPubKey(peerKey string) (string, error) GetAccountIDByPeerPubKey(peerKey string) (string, error)
GetAccountIDByUserID(peerKey string) (string, error)
GetAccountIDBySetupKey(peerKey string) (string, error)
GetAccountByPeerID(peerID string) (*Account, error) GetAccountByPeerID(peerID string) (*Account, error)
GetAccountBySetupKey(setupKey string) (*Account, error) // todo use key hash later GetAccountBySetupKey(setupKey string) (*Account, error) // todo use key hash later
GetAccountByPrivateDomain(domain string) (*Account, error) GetAccountByPrivateDomain(domain string) (*Account, error)
GetTokenIDByHashedToken(secret string) (string, error) GetTokenIDByHashedToken(secret string) (string, error)
GetUserByTokenID(tokenID string) (*User, error) GetUserByTokenID(tokenID string) (*User, error)
GetPostureCheckByChecksDefinition(accountID string, checks *posture.ChecksDefinition) (*posture.Checks, error)
SaveAccount(account *Account) error SaveAccount(account *Account) error
DeleteHashedPAT2TokenIDIndex(hashedToken string) error DeleteHashedPAT2TokenIDIndex(hashedToken string) error
DeleteTokenID2UserIDIndex(tokenID string) error DeleteTokenID2UserIDIndex(tokenID string) error
@ -53,6 +54,8 @@ type Store interface {
// GetStoreEngine should return StoreEngine of the current store implementation. // GetStoreEngine should return StoreEngine of the current store implementation.
// This is also a method of metrics.DataSource interface. // This is also a method of metrics.DataSource interface.
GetStoreEngine() StoreEngine GetStoreEngine() StoreEngine
GetPeerByPeerPubKey(peerKey string) (*nbpeer.Peer, error)
GetAccountSettings(accountID string) (*Settings, error)
} }
type StoreEngine string type StoreEngine string
@ -176,7 +179,7 @@ func NewTestStoreFromJson(dataDir string) (Store, func(), error) {
} }
return store, cleanUp, nil return store, cleanUp, nil
case PostgresStoreEngine: case PostgresStoreEngine:
cleanUp, err = createPGDB() cleanUp, err = testutil.CreatePGDB()
if err != nil { if err != nil {
return nil, nil, err return nil, nil, err
} }
@ -199,33 +202,3 @@ func NewTestStoreFromJson(dataDir string) (Store, func(), error) {
return store, cleanUp, nil return store, cleanUp, nil
} }
} }
func createPGDB() (func(), error) {
ctx := context.Background()
c, err := postgres.RunContainer(ctx,
testcontainers.WithImage("postgres:alpine"),
postgres.WithDatabase("test"),
postgres.WithUsername("postgres"),
postgres.WithPassword("postgres"),
testcontainers.WithWaitStrategy(
wait.ForLog("database system is ready to accept connections").
WithOccurrence(2).WithStartupTimeout(15*time.Second)),
)
if err != nil {
return nil, err
}
cleanup := func() {
timeout := 10 * time.Second
err = c.Stop(ctx, &timeout)
if err != nil {
log.Warnf("failed to stop container: %s", err)
}
}
talksConn, err := c.ConnectionString(ctx)
if err != nil {
return cleanup, err
}
return cleanup, os.Setenv("NETBIRD_STORE_ENGINE_POSTGRES_DSN", talksConn)
}

View File

@ -5,50 +5,49 @@ import (
"time" "time"
"go.opentelemetry.io/otel/metric" "go.opentelemetry.io/otel/metric"
"go.opentelemetry.io/otel/metric/instrument"
"go.opentelemetry.io/otel/metric/instrument/asyncint64"
"go.opentelemetry.io/otel/metric/instrument/syncint64"
) )
// GRPCMetrics are gRPC server metrics // GRPCMetrics are gRPC server metrics
type GRPCMetrics struct { type GRPCMetrics struct {
meter metric.Meter meter metric.Meter
syncRequestsCounter syncint64.Counter syncRequestsCounter metric.Int64Counter
loginRequestsCounter syncint64.Counter loginRequestsCounter metric.Int64Counter
getKeyRequestsCounter syncint64.Counter getKeyRequestsCounter metric.Int64Counter
activeStreamsGauge asyncint64.Gauge activeStreamsGauge metric.Int64ObservableGauge
syncRequestDuration syncint64.Histogram syncRequestDuration metric.Int64Histogram
loginRequestDuration syncint64.Histogram loginRequestDuration metric.Int64Histogram
channelQueueLength syncint64.Histogram channelQueueLength metric.Int64Histogram
ctx context.Context ctx context.Context
} }
// NewGRPCMetrics creates new GRPCMetrics struct and registers common metrics of the gRPC server // NewGRPCMetrics creates new GRPCMetrics struct and registers common metrics of the gRPC server
func NewGRPCMetrics(ctx context.Context, meter metric.Meter) (*GRPCMetrics, error) { func NewGRPCMetrics(ctx context.Context, meter metric.Meter) (*GRPCMetrics, error) {
syncRequestsCounter, err := meter.SyncInt64().Counter("management.grpc.sync.request.counter", instrument.WithUnit("1")) syncRequestsCounter, err := meter.Int64Counter("management.grpc.sync.request.counter", metric.WithUnit("1"))
if err != nil {
return nil, err
}
loginRequestsCounter, err := meter.SyncInt64().Counter("management.grpc.login.request.counter", instrument.WithUnit("1"))
if err != nil {
return nil, err
}
getKeyRequestsCounter, err := meter.SyncInt64().Counter("management.grpc.key.request.counter", instrument.WithUnit("1"))
if err != nil { if err != nil {
return nil, err return nil, err
} }
activeStreamsGauge, err := meter.AsyncInt64().Gauge("management.grpc.connected.streams", instrument.WithUnit("1")) loginRequestsCounter, err := meter.Int64Counter("management.grpc.login.request.counter", metric.WithUnit("1"))
if err != nil { if err != nil {
return nil, err return nil, err
} }
syncRequestDuration, err := meter.SyncInt64().Histogram("management.grpc.sync.request.duration.ms", instrument.WithUnit("milliseconds")) getKeyRequestsCounter, err := meter.Int64Counter("management.grpc.key.request.counter", metric.WithUnit("1"))
if err != nil { if err != nil {
return nil, err return nil, err
} }
loginRequestDuration, err := meter.SyncInt64().Histogram("management.grpc.login.request.duration.ms", instrument.WithUnit("milliseconds")) activeStreamsGauge, err := meter.Int64ObservableGauge("management.grpc.connected.streams", metric.WithUnit("1"))
if err != nil {
return nil, err
}
syncRequestDuration, err := meter.Int64Histogram("management.grpc.sync.request.duration.ms", metric.WithUnit("milliseconds"))
if err != nil {
return nil, err
}
loginRequestDuration, err := meter.Int64Histogram("management.grpc.login.request.duration.ms", metric.WithUnit("milliseconds"))
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -56,10 +55,10 @@ func NewGRPCMetrics(ctx context.Context, meter metric.Meter) (*GRPCMetrics, erro
// We use histogram here as we have multiple channel at the same time and we want to see a slice at any given time // We use histogram here as we have multiple channel at the same time and we want to see a slice at any given time
// Then we should be able to extract min, manx, mean and the percentiles. // Then we should be able to extract min, manx, mean and the percentiles.
// TODO(yury): This needs custom bucketing as we are interested in the values from 0 to server.channelBufferSize (100) // TODO(yury): This needs custom bucketing as we are interested in the values from 0 to server.channelBufferSize (100)
channelQueue, err := meter.SyncInt64().Histogram( channelQueue, err := meter.Int64Histogram(
"management.grpc.updatechannel.queue", "management.grpc.updatechannel.queue",
instrument.WithDescription("Number of update messages in the channel queue"), metric.WithDescription("Number of update messages in the channel queue"),
instrument.WithUnit("length"), metric.WithUnit("length"),
) )
if err != nil { if err != nil {
return nil, err return nil, err
@ -105,14 +104,14 @@ func (grpcMetrics *GRPCMetrics) CountSyncRequestDuration(duration time.Duration)
// RegisterConnectedStreams registers a function that collects number of active streams and feeds it to the metrics gauge. // RegisterConnectedStreams registers a function that collects number of active streams and feeds it to the metrics gauge.
func (grpcMetrics *GRPCMetrics) RegisterConnectedStreams(producer func() int64) error { func (grpcMetrics *GRPCMetrics) RegisterConnectedStreams(producer func() int64) error {
return grpcMetrics.meter.RegisterCallback( _, err := grpcMetrics.meter.RegisterCallback(
[]instrument.Asynchronous{ func(ctx context.Context, observer metric.Observer) error {
observer.ObserveInt64(grpcMetrics.activeStreamsGauge, producer())
return nil
},
grpcMetrics.activeStreamsGauge, grpcMetrics.activeStreamsGauge,
},
func(ctx context.Context) {
grpcMetrics.activeStreamsGauge.Observe(ctx, producer())
},
) )
return err
} }
// UpdateChannelQueueLength update the histogram that keep distribution of the update messages channel queue // UpdateChannelQueueLength update the histogram that keep distribution of the update messages channel queue

View File

@ -6,13 +6,11 @@ import (
"hash/fnv" "hash/fnv"
"net/http" "net/http"
"strings" "strings"
time "time" "time"
log "github.com/sirupsen/logrus" log "github.com/sirupsen/logrus"
"go.opentelemetry.io/otel/attribute" "go.opentelemetry.io/otel/attribute"
"go.opentelemetry.io/otel/metric" "go.opentelemetry.io/otel/metric"
"go.opentelemetry.io/otel/metric/instrument"
"go.opentelemetry.io/otel/metric/instrument/syncint64"
) )
const ( const (
@ -56,51 +54,44 @@ type HTTPMiddleware struct {
meter metric.Meter meter metric.Meter
ctx context.Context ctx context.Context
// all HTTP requests by endpoint & method // all HTTP requests by endpoint & method
httpRequestCounters map[string]syncint64.Counter httpRequestCounters map[string]metric.Int64Counter
// all HTTP responses by endpoint & method & status code // all HTTP responses by endpoint & method & status code
httpResponseCounters map[string]syncint64.Counter httpResponseCounters map[string]metric.Int64Counter
// all HTTP requests // all HTTP requests
totalHTTPRequestsCounter syncint64.Counter totalHTTPRequestsCounter metric.Int64Counter
// all HTTP responses // all HTTP responses
totalHTTPResponseCounter syncint64.Counter totalHTTPResponseCounter metric.Int64Counter
// all HTTP responses by status code // all HTTP responses by status code
totalHTTPResponseCodeCounters map[int]syncint64.Counter totalHTTPResponseCodeCounters map[int]metric.Int64Counter
// all HTTP requests durations by endpoint and method // all HTTP requests durations by endpoint and method
httpRequestDurations map[string]syncint64.Histogram httpRequestDurations map[string]metric.Int64Histogram
// all HTTP requests durations // all HTTP requests durations
totalHTTPRequestDuration syncint64.Histogram totalHTTPRequestDuration metric.Int64Histogram
} }
// NewMetricsMiddleware creates a new HTTPMiddleware // NewMetricsMiddleware creates a new HTTPMiddleware
func NewMetricsMiddleware(ctx context.Context, meter metric.Meter) (*HTTPMiddleware, error) { func NewMetricsMiddleware(ctx context.Context, meter metric.Meter) (*HTTPMiddleware, error) {
totalHTTPRequestsCounter, err := meter.Int64Counter(fmt.Sprintf("%s_total", httpRequestCounterPrefix), metric.WithUnit("1"))
totalHTTPRequestsCounter, err := meter.SyncInt64().Counter(
fmt.Sprintf("%s_total", httpRequestCounterPrefix),
instrument.WithUnit("1"))
if err != nil {
return nil, err
}
totalHTTPResponseCounter, err := meter.SyncInt64().Counter(
fmt.Sprintf("%s_total", httpResponseCounterPrefix),
instrument.WithUnit("1"))
if err != nil { if err != nil {
return nil, err return nil, err
} }
totalHTTPRequestDuration, err := meter.SyncInt64().Histogram( totalHTTPResponseCounter, err := meter.Int64Counter(fmt.Sprintf("%s_total", httpResponseCounterPrefix), metric.WithUnit("1"))
fmt.Sprintf("%s_total", httpRequestDurationPrefix), if err != nil {
instrument.WithUnit("milliseconds")) return nil, err
}
totalHTTPRequestDuration, err := meter.Int64Histogram(fmt.Sprintf("%s_total", httpRequestDurationPrefix), metric.WithUnit("milliseconds"))
if err != nil { if err != nil {
return nil, err return nil, err
} }
return &HTTPMiddleware{ return &HTTPMiddleware{
ctx: ctx, ctx: ctx,
httpRequestCounters: map[string]syncint64.Counter{}, httpRequestCounters: map[string]metric.Int64Counter{},
httpResponseCounters: map[string]syncint64.Counter{}, httpResponseCounters: map[string]metric.Int64Counter{},
httpRequestDurations: map[string]syncint64.Histogram{}, httpRequestDurations: map[string]metric.Int64Histogram{},
totalHTTPResponseCodeCounters: map[int]syncint64.Counter{}, totalHTTPResponseCodeCounters: map[int]metric.Int64Counter{},
meter: meter, meter: meter,
totalHTTPRequestsCounter: totalHTTPRequestsCounter, totalHTTPRequestsCounter: totalHTTPRequestsCounter,
totalHTTPResponseCounter: totalHTTPResponseCounter, totalHTTPResponseCounter: totalHTTPResponseCounter,
@ -113,28 +104,30 @@ func NewMetricsMiddleware(ctx context.Context, meter metric.Meter) (*HTTPMiddlew
// Creates one request counter and multiple response counters (one per http response status code). // Creates one request counter and multiple response counters (one per http response status code).
func (m *HTTPMiddleware) AddHTTPRequestResponseCounter(endpoint string, method string) error { func (m *HTTPMiddleware) AddHTTPRequestResponseCounter(endpoint string, method string) error {
meterKey := getRequestCounterKey(endpoint, method) meterKey := getRequestCounterKey(endpoint, method)
httpReqCounter, err := m.meter.SyncInt64().Counter(meterKey, instrument.WithUnit("1")) httpReqCounter, err := m.meter.Int64Counter(meterKey, metric.WithUnit("1"))
if err != nil { if err != nil {
return err return err
} }
m.httpRequestCounters[meterKey] = httpReqCounter m.httpRequestCounters[meterKey] = httpReqCounter
durationKey := getRequestDurationKey(endpoint, method) durationKey := getRequestDurationKey(endpoint, method)
requestDuration, err := m.meter.SyncInt64().Histogram(durationKey, instrument.WithUnit("milliseconds")) requestDuration, err := m.meter.Int64Histogram(durationKey, metric.WithUnit("milliseconds"))
if err != nil { if err != nil {
return err return err
} }
m.httpRequestDurations[durationKey] = requestDuration m.httpRequestDurations[durationKey] = requestDuration
respCodes := []int{200, 204, 400, 401, 403, 404, 500, 502, 503} respCodes := []int{200, 204, 400, 401, 403, 404, 500, 502, 503}
for _, code := range respCodes { for _, code := range respCodes {
meterKey = getResponseCounterKey(endpoint, method, code) meterKey = getResponseCounterKey(endpoint, method, code)
httpRespCounter, err := m.meter.SyncInt64().Counter(meterKey, instrument.WithUnit("1")) httpRespCounter, err := m.meter.Int64Counter(meterKey, metric.WithUnit("1"))
if err != nil { if err != nil {
return err return err
} }
m.httpResponseCounters[meterKey] = httpRespCounter m.httpResponseCounters[meterKey] = httpRespCounter
meterKey = fmt.Sprintf("%s_%d_total", httpResponseCounterPrefix, code) meterKey = fmt.Sprintf("%s_%d_total", httpResponseCounterPrefix, code)
totalHTTPResponseCodeCounter, err := m.meter.SyncInt64().Counter(meterKey, instrument.WithUnit("1")) totalHTTPResponseCodeCounter, err := m.meter.Int64Counter(meterKey, metric.WithUnit("1"))
if err != nil { if err != nil {
return err return err
} }
@ -144,19 +137,26 @@ func (m *HTTPMiddleware) AddHTTPRequestResponseCounter(endpoint string, method s
return nil return nil
} }
func replaceEndpointChars(endpoint string) string {
endpoint = strings.ReplaceAll(endpoint, "/", "_")
endpoint = strings.ReplaceAll(endpoint, "{", "")
endpoint = strings.ReplaceAll(endpoint, "}", "")
return endpoint
}
func getRequestCounterKey(endpoint, method string) string { func getRequestCounterKey(endpoint, method string) string {
return fmt.Sprintf("%s%s_%s", httpRequestCounterPrefix, endpoint = replaceEndpointChars(endpoint)
strings.ReplaceAll(endpoint, "/", "_"), method) return fmt.Sprintf("%s%s_%s", httpRequestCounterPrefix, endpoint, method)
} }
func getRequestDurationKey(endpoint, method string) string { func getRequestDurationKey(endpoint, method string) string {
return fmt.Sprintf("%s%s_%s", httpRequestDurationPrefix, endpoint = replaceEndpointChars(endpoint)
strings.ReplaceAll(endpoint, "/", "_"), method) return fmt.Sprintf("%s%s_%s", httpRequestDurationPrefix, endpoint, method)
} }
func getResponseCounterKey(endpoint, method string, status int) string { func getResponseCounterKey(endpoint, method string, status int) string {
return fmt.Sprintf("%s%s_%s_%d", httpResponseCounterPrefix, endpoint = replaceEndpointChars(endpoint)
strings.ReplaceAll(endpoint, "/", "_"), method, status) return fmt.Sprintf("%s%s_%s_%d", httpResponseCounterPrefix, endpoint, method, status)
} }
// Handler logs every request and response and adds the, to metrics. // Handler logs every request and response and adds the, to metrics.
@ -201,9 +201,11 @@ func (m *HTTPMiddleware) Handler(h http.Handler) http.Handler {
log.Debugf("request %s %s took %d ms and finished with status %d", r.Method, r.URL.Path, reqTook.Milliseconds(), w.Status()) log.Debugf("request %s %s took %d ms and finished with status %d", r.Method, r.URL.Path, reqTook.Milliseconds(), w.Status())
if w.Status() == 200 && (r.Method == http.MethodPut || r.Method == http.MethodPost || r.Method == http.MethodDelete) { if w.Status() == 200 && (r.Method == http.MethodPut || r.Method == http.MethodPost || r.Method == http.MethodDelete) {
m.totalHTTPRequestDuration.Record(m.ctx, reqTook.Milliseconds(), attribute.String("type", "write")) opts := metric.WithAttributeSet(attribute.NewSet(attribute.String("type", "write")))
m.totalHTTPRequestDuration.Record(m.ctx, reqTook.Milliseconds(), opts)
} else { } else {
m.totalHTTPRequestDuration.Record(m.ctx, reqTook.Milliseconds(), attribute.String("type", "read")) opts := metric.WithAttributeSet(attribute.NewSet(attribute.String("type", "read")))
m.totalHTTPRequestDuration.Record(m.ctx, reqTook.Milliseconds(), opts)
} }
} }

View File

@ -4,64 +4,62 @@ import (
"context" "context"
"go.opentelemetry.io/otel/metric" "go.opentelemetry.io/otel/metric"
"go.opentelemetry.io/otel/metric/instrument"
"go.opentelemetry.io/otel/metric/instrument/syncint64"
) )
// IDPMetrics is common IdP metrics // IDPMetrics is common IdP metrics
type IDPMetrics struct { type IDPMetrics struct {
metaUpdateCounter syncint64.Counter metaUpdateCounter metric.Int64Counter
getUserByEmailCounter syncint64.Counter getUserByEmailCounter metric.Int64Counter
getAllAccountsCounter syncint64.Counter getAllAccountsCounter metric.Int64Counter
createUserCounter syncint64.Counter createUserCounter metric.Int64Counter
deleteUserCounter syncint64.Counter deleteUserCounter metric.Int64Counter
getAccountCounter syncint64.Counter getAccountCounter metric.Int64Counter
getUserByIDCounter syncint64.Counter getUserByIDCounter metric.Int64Counter
authenticateRequestCounter syncint64.Counter authenticateRequestCounter metric.Int64Counter
requestErrorCounter syncint64.Counter requestErrorCounter metric.Int64Counter
requestStatusErrorCounter syncint64.Counter requestStatusErrorCounter metric.Int64Counter
ctx context.Context ctx context.Context
} }
// NewIDPMetrics creates new IDPMetrics struct and registers common // NewIDPMetrics creates new IDPMetrics struct and registers common
func NewIDPMetrics(ctx context.Context, meter metric.Meter) (*IDPMetrics, error) { func NewIDPMetrics(ctx context.Context, meter metric.Meter) (*IDPMetrics, error) {
metaUpdateCounter, err := meter.SyncInt64().Counter("management.idp.update.user.meta.counter", instrument.WithUnit("1")) metaUpdateCounter, err := meter.Int64Counter("management.idp.update.user.meta.counter", metric.WithUnit("1"))
if err != nil { if err != nil {
return nil, err return nil, err
} }
getUserByEmailCounter, err := meter.SyncInt64().Counter("management.idp.get.user.by.email.counter", instrument.WithUnit("1")) getUserByEmailCounter, err := meter.Int64Counter("management.idp.get.user.by.email.counter", metric.WithUnit("1"))
if err != nil { if err != nil {
return nil, err return nil, err
} }
getAllAccountsCounter, err := meter.SyncInt64().Counter("management.idp.get.accounts.counter", instrument.WithUnit("1")) getAllAccountsCounter, err := meter.Int64Counter("management.idp.get.accounts.counter", metric.WithUnit("1"))
if err != nil { if err != nil {
return nil, err return nil, err
} }
createUserCounter, err := meter.SyncInt64().Counter("management.idp.create.user.counter", instrument.WithUnit("1")) createUserCounter, err := meter.Int64Counter("management.idp.create.user.counter", metric.WithUnit("1"))
if err != nil { if err != nil {
return nil, err return nil, err
} }
deleteUserCounter, err := meter.SyncInt64().Counter("management.idp.delete.user.counter", instrument.WithUnit("1")) deleteUserCounter, err := meter.Int64Counter("management.idp.delete.user.counter", metric.WithUnit("1"))
if err != nil { if err != nil {
return nil, err return nil, err
} }
getAccountCounter, err := meter.SyncInt64().Counter("management.idp.get.account.counter", instrument.WithUnit("1")) getAccountCounter, err := meter.Int64Counter("management.idp.get.account.counter", metric.WithUnit("1"))
if err != nil { if err != nil {
return nil, err return nil, err
} }
getUserByIDCounter, err := meter.SyncInt64().Counter("management.idp.get.user.by.id.counter", instrument.WithUnit("1")) getUserByIDCounter, err := meter.Int64Counter("management.idp.get.user.by.id.counter", metric.WithUnit("1"))
if err != nil { if err != nil {
return nil, err return nil, err
} }
authenticateRequestCounter, err := meter.SyncInt64().Counter("management.idp.authenticate.request.counter", instrument.WithUnit("1")) authenticateRequestCounter, err := meter.Int64Counter("management.idp.authenticate.request.counter", metric.WithUnit("1"))
if err != nil { if err != nil {
return nil, err return nil, err
} }
requestErrorCounter, err := meter.SyncInt64().Counter("management.idp.request.error.counter", instrument.WithUnit("1")) requestErrorCounter, err := meter.Int64Counter("management.idp.request.error.counter", metric.WithUnit("1"))
if err != nil { if err != nil {
return nil, err return nil, err
} }
requestStatusErrorCounter, err := meter.SyncInt64().Counter("management.idp.request.status.error.counter", instrument.WithUnit("1")) requestStatusErrorCounter, err := meter.Int64Counter("management.idp.request.status.error.counter", metric.WithUnit("1"))
if err != nil { if err != nil {
return nil, err return nil, err
} }

View File

@ -5,39 +5,37 @@ import (
"time" "time"
"go.opentelemetry.io/otel/metric" "go.opentelemetry.io/otel/metric"
"go.opentelemetry.io/otel/metric/instrument"
"go.opentelemetry.io/otel/metric/instrument/syncint64"
) )
// StoreMetrics represents all metrics related to the Store // StoreMetrics represents all metrics related to the Store
type StoreMetrics struct { type StoreMetrics struct {
globalLockAcquisitionDurationMicro syncint64.Histogram globalLockAcquisitionDurationMicro metric.Int64Histogram
globalLockAcquisitionDurationMs syncint64.Histogram globalLockAcquisitionDurationMs metric.Int64Histogram
persistenceDurationMicro syncint64.Histogram persistenceDurationMicro metric.Int64Histogram
persistenceDurationMs syncint64.Histogram persistenceDurationMs metric.Int64Histogram
ctx context.Context ctx context.Context
} }
// NewStoreMetrics creates an instance of StoreMetrics // NewStoreMetrics creates an instance of StoreMetrics
func NewStoreMetrics(ctx context.Context, meter metric.Meter) (*StoreMetrics, error) { func NewStoreMetrics(ctx context.Context, meter metric.Meter) (*StoreMetrics, error) {
globalLockAcquisitionDurationMicro, err := meter.SyncInt64().Histogram("management.store.global.lock.acquisition.duration.micro", globalLockAcquisitionDurationMicro, err := meter.Int64Histogram("management.store.global.lock.acquisition.duration.micro",
instrument.WithUnit("microseconds")) metric.WithUnit("microseconds"))
if err != nil { if err != nil {
return nil, err return nil, err
} }
globalLockAcquisitionDurationMs, err := meter.SyncInt64().Histogram("management.store.global.lock.acquisition.duration.ms") globalLockAcquisitionDurationMs, err := meter.Int64Histogram("management.store.global.lock.acquisition.duration.ms")
if err != nil { if err != nil {
return nil, err return nil, err
} }
persistenceDurationMicro, err := meter.SyncInt64().Histogram("management.store.persistence.duration.micro", persistenceDurationMicro, err := meter.Int64Histogram("management.store.persistence.duration.micro",
instrument.WithUnit("microseconds")) metric.WithUnit("microseconds"))
if err != nil { if err != nil {
return nil, err return nil, err
} }
persistenceDurationMs, err := meter.SyncInt64().Histogram("management.store.persistence.duration.ms") persistenceDurationMs, err := meter.Int64Histogram("management.store.persistence.duration.ms")
if err != nil { if err != nil {
return nil, err return nil, err
} }

View File

@ -6,60 +6,59 @@ import (
"go.opentelemetry.io/otel/attribute" "go.opentelemetry.io/otel/attribute"
"go.opentelemetry.io/otel/metric" "go.opentelemetry.io/otel/metric"
"go.opentelemetry.io/otel/metric/instrument/syncint64"
) )
// UpdateChannelMetrics represents all metrics related to the UpdateChannel // UpdateChannelMetrics represents all metrics related to the UpdateChannel
type UpdateChannelMetrics struct { type UpdateChannelMetrics struct {
createChannelDurationMicro syncint64.Histogram createChannelDurationMicro metric.Int64Histogram
closeChannelDurationMicro syncint64.Histogram closeChannelDurationMicro metric.Int64Histogram
closeChannelsDurationMicro syncint64.Histogram closeChannelsDurationMicro metric.Int64Histogram
closeChannels syncint64.Histogram closeChannels metric.Int64Histogram
sendUpdateDurationMicro syncint64.Histogram sendUpdateDurationMicro metric.Int64Histogram
getAllConnectedPeersDurationMicro syncint64.Histogram getAllConnectedPeersDurationMicro metric.Int64Histogram
getAllConnectedPeers syncint64.Histogram getAllConnectedPeers metric.Int64Histogram
hasChannelDurationMicro syncint64.Histogram hasChannelDurationMicro metric.Int64Histogram
ctx context.Context ctx context.Context
} }
// NewUpdateChannelMetrics creates an instance of UpdateChannel // NewUpdateChannelMetrics creates an instance of UpdateChannel
func NewUpdateChannelMetrics(ctx context.Context, meter metric.Meter) (*UpdateChannelMetrics, error) { func NewUpdateChannelMetrics(ctx context.Context, meter metric.Meter) (*UpdateChannelMetrics, error) {
createChannelDurationMicro, err := meter.SyncInt64().Histogram("management.updatechannel.create.duration.micro") createChannelDurationMicro, err := meter.Int64Histogram("management.updatechannel.create.duration.micro")
if err != nil { if err != nil {
return nil, err return nil, err
} }
closeChannelDurationMicro, err := meter.SyncInt64().Histogram("management.updatechannel.close.one.duration.micro") closeChannelDurationMicro, err := meter.Int64Histogram("management.updatechannel.close.one.duration.micro")
if err != nil { if err != nil {
return nil, err return nil, err
} }
closeChannelsDurationMicro, err := meter.SyncInt64().Histogram("management.updatechannel.close.multiple.duration.micro") closeChannelsDurationMicro, err := meter.Int64Histogram("management.updatechannel.close.multiple.duration.micro")
if err != nil { if err != nil {
return nil, err return nil, err
} }
closeChannels, err := meter.SyncInt64().Histogram("management.updatechannel.close.multiple.channels") closeChannels, err := meter.Int64Histogram("management.updatechannel.close.multiple.channels")
if err != nil { if err != nil {
return nil, err return nil, err
} }
sendUpdateDurationMicro, err := meter.SyncInt64().Histogram("management.updatechannel.send.duration.micro") sendUpdateDurationMicro, err := meter.Int64Histogram("management.updatechannel.send.duration.micro")
if err != nil { if err != nil {
return nil, err return nil, err
} }
getAllConnectedPeersDurationMicro, err := meter.SyncInt64().Histogram("management.updatechannel.get.all.duration.micro") getAllConnectedPeersDurationMicro, err := meter.Int64Histogram("management.updatechannel.get.all.duration.micro")
if err != nil { if err != nil {
return nil, err return nil, err
} }
getAllConnectedPeers, err := meter.SyncInt64().Histogram("management.updatechannel.get.all.peers") getAllConnectedPeers, err := meter.Int64Histogram("management.updatechannel.get.all.peers")
if err != nil { if err != nil {
return nil, err return nil, err
} }
hasChannelDurationMicro, err := meter.SyncInt64().Histogram("management.updatechannel.haschannel.duration.micro") hasChannelDurationMicro, err := meter.Int64Histogram("management.updatechannel.haschannel.duration.micro")
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -80,7 +79,8 @@ func NewUpdateChannelMetrics(ctx context.Context, meter metric.Meter) (*UpdateCh
// CountCreateChannelDuration counts the duration of the CreateChannel method, // CountCreateChannelDuration counts the duration of the CreateChannel method,
// closed indicates if existing channel was closed before creation of a new one // closed indicates if existing channel was closed before creation of a new one
func (metrics *UpdateChannelMetrics) CountCreateChannelDuration(duration time.Duration, closed bool) { func (metrics *UpdateChannelMetrics) CountCreateChannelDuration(duration time.Duration, closed bool) {
metrics.createChannelDurationMicro.Record(metrics.ctx, duration.Microseconds(), attribute.Bool("closed", closed)) opts := metric.WithAttributeSet(attribute.NewSet(attribute.Bool("closed", closed)))
metrics.createChannelDurationMicro.Record(metrics.ctx, duration.Microseconds(), opts)
} }
// CountCloseChannelDuration counts the duration of the CloseChannel method // CountCloseChannelDuration counts the duration of the CloseChannel method
@ -97,8 +97,8 @@ func (metrics *UpdateChannelMetrics) CountCloseChannelsDuration(duration time.Du
// CountSendUpdateDuration counts the duration of the SendUpdate method // CountSendUpdateDuration counts the duration of the SendUpdate method
// found indicates if peer had channel, dropped indicates if the message was dropped due channel buffer overload // found indicates if peer had channel, dropped indicates if the message was dropped due channel buffer overload
func (metrics *UpdateChannelMetrics) CountSendUpdateDuration(duration time.Duration, found, dropped bool) { func (metrics *UpdateChannelMetrics) CountSendUpdateDuration(duration time.Duration, found, dropped bool) {
attrs := []attribute.KeyValue{attribute.Bool("found", found), attribute.Bool("dropped", dropped)} opts := metric.WithAttributeSet(attribute.NewSet(attribute.Bool("found", found), attribute.Bool("dropped", dropped)))
metrics.sendUpdateDurationMicro.Record(metrics.ctx, duration.Microseconds(), attrs...) metrics.sendUpdateDurationMicro.Record(metrics.ctx, duration.Microseconds(), opts)
} }
// CountGetAllConnectedPeersDuration counts the duration of the GetAllConnectedPeers method and the number of peers have been returned // CountGetAllConnectedPeersDuration counts the duration of the GetAllConnectedPeers method and the number of peers have been returned

View File

@ -0,0 +1,45 @@
//go:build !ios
// +build !ios
package testutil
import (
"context"
"os"
"time"
log "github.com/sirupsen/logrus"
"github.com/testcontainers/testcontainers-go"
"github.com/testcontainers/testcontainers-go/modules/postgres"
"github.com/testcontainers/testcontainers-go/wait"
)
func CreatePGDB() (func(), error) {
ctx := context.Background()
c, err := postgres.RunContainer(ctx,
testcontainers.WithImage("postgres:alpine"),
postgres.WithDatabase("test"),
postgres.WithUsername("postgres"),
postgres.WithPassword("postgres"),
testcontainers.WithWaitStrategy(
wait.ForLog("database system is ready to accept connections").
WithOccurrence(2).WithStartupTimeout(15*time.Second)),
)
if err != nil {
return nil, err
}
cleanup := func() {
timeout := 10 * time.Second
err = c.Stop(ctx, &timeout)
if err != nil {
log.Warnf("failed to stop container: %s", err)
}
}
talksConn, err := c.ConnectionString(ctx)
if err != nil {
return cleanup, err
}
return cleanup, os.Setenv("NETBIRD_STORE_ENGINE_POSTGRES_DSN", talksConn)
}

View File

@ -0,0 +1,6 @@
//go:build ios
// +build ios
package testutil
func CreatePGDB() (func(), error) { return func() {}, nil }

View File

@ -910,8 +910,10 @@ func (am *DefaultAccountManager) SaveOrAddUser(accountID, initiatorUserID string
// GetOrCreateAccountByUser returns an existing account for a given user id or creates a new one if doesn't exist // GetOrCreateAccountByUser returns an existing account for a given user id or creates a new one if doesn't exist
func (am *DefaultAccountManager) GetOrCreateAccountByUser(userID, domain string) (*Account, error) { func (am *DefaultAccountManager) GetOrCreateAccountByUser(userID, domain string) (*Account, error) {
start := time.Now()
unlock := am.Store.AcquireGlobalLock() unlock := am.Store.AcquireGlobalLock()
defer unlock() defer unlock()
log.Debugf("Acquired global lock in %s for user %s", time.Since(start), userID)
lowerDomain := strings.ToLower(domain) lowerDomain := strings.ToLower(domain)

View File

@ -25,11 +25,6 @@ const (
DirectCheck uint32 = 1 DirectCheck uint32 = 1
) )
// FeaturesSupport register protocol supported features
type FeaturesSupport struct {
DirectCheck bool
}
type Client interface { type Client interface {
io.Closer io.Closer
StreamConnected() bool StreamConnected() bool
@ -79,15 +74,3 @@ type Credential struct {
UFrag string UFrag string
Pwd string Pwd string
} }
// ParseFeaturesSupported parses a slice of supported features into FeaturesSupport
func ParseFeaturesSupported(featuresMessage []uint32) FeaturesSupport {
var protoSupport FeaturesSupport
for _, feature := range featuresMessage {
if feature == DirectCheck {
protoSupport.DirectCheck = true
return protoSupport
}
}
return protoSupport
}

View File

@ -4,7 +4,6 @@ import (
"context" "context"
"net" "net"
"sync" "sync"
"testing"
"time" "time"
. "github.com/onsi/ginkgo" . "github.com/onsi/ginkgo"
@ -168,41 +167,6 @@ var _ = Describe("GrpcClient", func() {
}) })
func TestParseFeaturesSupported(t *testing.T) {
expectedOnEmptyOrUnsupported := FeaturesSupport{DirectCheck: false}
expectedWithDirectCheck := FeaturesSupport{DirectCheck: true}
testCases := []struct {
name string
input []uint32
expected FeaturesSupport
}{
{
name: "Should Return DirectCheck Supported",
input: []uint32{DirectCheck},
expected: expectedWithDirectCheck,
},
{
name: "Should Return DirectCheck Unsupported When Nil",
input: nil,
expected: expectedOnEmptyOrUnsupported,
},
{
name: "Should Return DirectCheck Unsupported When Not Known Feature",
input: []uint32{9999},
expected: expectedOnEmptyOrUnsupported,
},
}
for _, testCase := range testCases {
t.Run(testCase.name, func(t *testing.T) {
result := ParseFeaturesSupported(testCase.input)
if result.DirectCheck != testCase.expected.DirectCheck {
t.Errorf("Direct check feature should match: Expected: %t, Got: %t", testCase.expected.DirectCheck, result.DirectCheck)
}
})
}
}
func createSignalClient(addr string, key wgtypes.Key) *GrpcClient { func createSignalClient(addr string, key wgtypes.Key) *GrpcClient {
var sigTLSEnabled = false var sigTLSEnabled = false
client, err := NewClient(context.Background(), addr, key, sigTLSEnabled) client, err := NewClient(context.Background(), addr, key, sigTLSEnabled)