mirror of
https://github.com/netbirdio/netbird.git
synced 2026-04-20 01:06:45 +00:00
Compare commits
13 Commits
fix/includ
...
send-ssh-r
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
2d350b2522 | ||
|
|
d18d2db9ee | ||
|
|
c3a1e1ca2c | ||
|
|
c9acd2f880 | ||
|
|
4a1aee1ae0 | ||
|
|
ba33572ec9 | ||
|
|
9d213e0b54 | ||
|
|
5dde044fa5 | ||
|
|
5a3d9e401f | ||
|
|
fde1a2196c | ||
|
|
0aeb87742a | ||
|
|
6d747b2f83 | ||
|
|
199bf73103 |
@@ -11,7 +11,7 @@ concurrency:
|
|||||||
cancel-in-progress: true
|
cancel-in-progress: true
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
andrloid_build:
|
android_build:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout repository
|
- name: Checkout repository
|
||||||
@@ -41,7 +41,7 @@ jobs:
|
|||||||
run: go install golang.org/x/mobile/cmd/gomobile@v0.0.0-20230531173138-3c911d8e3eda
|
run: go install golang.org/x/mobile/cmd/gomobile@v0.0.0-20230531173138-3c911d8e3eda
|
||||||
- name: gomobile init
|
- name: gomobile init
|
||||||
run: gomobile init
|
run: gomobile init
|
||||||
- name: build android nebtird lib
|
- name: build android netbird lib
|
||||||
run: PATH=$PATH:$(go env GOPATH) gomobile bind -o $GITHUB_WORKSPACE/netbird.aar -javapkg=io.netbird.gomobile -ldflags="-X golang.zx2c4.com/wireguard/ipc.socketDirectory=/data/data/io.netbird.client/cache/wireguard -X github.com/netbirdio/netbird/version.version=buildtest" $GITHUB_WORKSPACE/client/android
|
run: PATH=$PATH:$(go env GOPATH) gomobile bind -o $GITHUB_WORKSPACE/netbird.aar -javapkg=io.netbird.gomobile -ldflags="-X golang.zx2c4.com/wireguard/ipc.socketDirectory=/data/data/io.netbird.client/cache/wireguard -X github.com/netbirdio/netbird/version.version=buildtest" $GITHUB_WORKSPACE/client/android
|
||||||
env:
|
env:
|
||||||
CGO_ENABLED: 0
|
CGO_ENABLED: 0
|
||||||
@@ -59,7 +59,7 @@ jobs:
|
|||||||
run: go install golang.org/x/mobile/cmd/gomobile@v0.0.0-20230531173138-3c911d8e3eda
|
run: go install golang.org/x/mobile/cmd/gomobile@v0.0.0-20230531173138-3c911d8e3eda
|
||||||
- name: gomobile init
|
- name: gomobile init
|
||||||
run: gomobile init
|
run: gomobile init
|
||||||
- name: build iOS nebtird lib
|
- name: build iOS netbird lib
|
||||||
run: PATH=$PATH:$(go env GOPATH) gomobile bind -target=ios -bundleid=io.netbird.framework -ldflags="-X github.com/netbirdio/netbird/version.version=buildtest" -o $GITHUB_WORKSPACE/NetBirdSDK.xcframework $GITHUB_WORKSPACE/client/ios/NetBirdSDK
|
run: PATH=$PATH:$(go env GOPATH) gomobile bind -target=ios -bundleid=io.netbird.framework -ldflags="-X github.com/netbirdio/netbird/version.version=buildtest" -o $GITHUB_WORKSPACE/NetBirdSDK.xcframework $GITHUB_WORKSPACE/client/ios/NetBirdSDK
|
||||||
env:
|
env:
|
||||||
CGO_ENABLED: 0
|
CGO_ENABLED: 0
|
||||||
@@ -106,8 +106,8 @@ export NETBIRD_DOMAIN=netbird.example.com; curl -fsSL https://github.com/netbird
|
|||||||
See a complete [architecture overview](https://docs.netbird.io/about-netbird/how-netbird-works#architecture) for details.
|
See a complete [architecture overview](https://docs.netbird.io/about-netbird/how-netbird-works#architecture) for details.
|
||||||
|
|
||||||
### Community projects
|
### Community projects
|
||||||
- [NetBird on OpenWRT](https://github.com/messense/openwrt-netbird)
|
|
||||||
- [NetBird installer script](https://github.com/physk/netbird-installer)
|
- [NetBird installer script](https://github.com/physk/netbird-installer)
|
||||||
|
- [NetBird ansible collection by Dominion Solutions](https://galaxy.ansible.com/ui/repo/published/dominion_solutions/netbird/)
|
||||||
|
|
||||||
**Note**: The `main` branch may be in an *unstable or even broken state* during development.
|
**Note**: The `main` branch may be in an *unstable or even broken state* during development.
|
||||||
For stable versions, see [releases](https://github.com/netbirdio/netbird/releases).
|
For stable versions, see [releases](https://github.com/netbirdio/netbird/releases).
|
||||||
|
|||||||
@@ -61,6 +61,7 @@ var (
|
|||||||
serverSSHAllowed bool
|
serverSSHAllowed bool
|
||||||
interfaceName string
|
interfaceName string
|
||||||
wireguardPort uint16
|
wireguardPort uint16
|
||||||
|
serviceName string
|
||||||
autoConnectDisabled bool
|
autoConnectDisabled bool
|
||||||
rootCmd = &cobra.Command{
|
rootCmd = &cobra.Command{
|
||||||
Use: "netbird",
|
Use: "netbird",
|
||||||
@@ -100,9 +101,16 @@ func init() {
|
|||||||
if runtime.GOOS == "windows" {
|
if runtime.GOOS == "windows" {
|
||||||
defaultDaemonAddr = "tcp://127.0.0.1:41731"
|
defaultDaemonAddr = "tcp://127.0.0.1:41731"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
defaultServiceName := "netbird"
|
||||||
|
if runtime.GOOS == "windows" {
|
||||||
|
defaultServiceName = "Netbird"
|
||||||
|
}
|
||||||
|
|
||||||
rootCmd.PersistentFlags().StringVar(&daemonAddr, "daemon-addr", defaultDaemonAddr, "Daemon service address to serve CLI requests [unix|tcp]://[path|host:port]")
|
rootCmd.PersistentFlags().StringVar(&daemonAddr, "daemon-addr", defaultDaemonAddr, "Daemon service address to serve CLI requests [unix|tcp]://[path|host:port]")
|
||||||
rootCmd.PersistentFlags().StringVarP(&managementURL, "management-url", "m", "", fmt.Sprintf("Management Service URL [http|https]://[host]:[port] (default \"%s\")", internal.DefaultManagementURL))
|
rootCmd.PersistentFlags().StringVarP(&managementURL, "management-url", "m", "", fmt.Sprintf("Management Service URL [http|https]://[host]:[port] (default \"%s\")", internal.DefaultManagementURL))
|
||||||
rootCmd.PersistentFlags().StringVar(&adminURL, "admin-url", "", fmt.Sprintf("Admin Panel URL [http|https]://[host]:[port] (default \"%s\")", internal.DefaultAdminURL))
|
rootCmd.PersistentFlags().StringVar(&adminURL, "admin-url", "", fmt.Sprintf("Admin Panel URL [http|https]://[host]:[port] (default \"%s\")", internal.DefaultAdminURL))
|
||||||
|
rootCmd.PersistentFlags().StringVarP(&serviceName, "service", "s", defaultServiceName, "Netbird system service name")
|
||||||
rootCmd.PersistentFlags().StringVarP(&configPath, "config", "c", defaultConfigPath, "Netbird config file location")
|
rootCmd.PersistentFlags().StringVarP(&configPath, "config", "c", defaultConfigPath, "Netbird config file location")
|
||||||
rootCmd.PersistentFlags().StringVarP(&logLevel, "log-level", "l", "info", "sets Netbird log level")
|
rootCmd.PersistentFlags().StringVarP(&logLevel, "log-level", "l", "info", "sets Netbird log level")
|
||||||
rootCmd.PersistentFlags().StringVar(&logFile, "log-file", defaultLogFile, "sets Netbird log path. If console is specified the log will be output to stdout")
|
rootCmd.PersistentFlags().StringVar(&logFile, "log-file", defaultLogFile, "sets Netbird log path. If console is specified the log will be output to stdout")
|
||||||
|
|||||||
@@ -2,8 +2,6 @@ package cmd
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"runtime"
|
|
||||||
|
|
||||||
"github.com/kardianos/service"
|
"github.com/kardianos/service"
|
||||||
log "github.com/sirupsen/logrus"
|
log "github.com/sirupsen/logrus"
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
@@ -24,12 +22,8 @@ func newProgram(ctx context.Context, cancel context.CancelFunc) *program {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func newSVCConfig() *service.Config {
|
func newSVCConfig() *service.Config {
|
||||||
name := "netbird"
|
|
||||||
if runtime.GOOS == "windows" {
|
|
||||||
name = "Netbird"
|
|
||||||
}
|
|
||||||
return &service.Config{
|
return &service.Config{
|
||||||
Name: name,
|
Name: serviceName,
|
||||||
DisplayName: "Netbird",
|
DisplayName: "Netbird",
|
||||||
Description: "A WireGuard-based mesh network that connects your devices into a single private network.",
|
Description: "A WireGuard-based mesh network that connects your devices into a single private network.",
|
||||||
Option: make(service.KeyValue),
|
Option: make(service.KeyValue),
|
||||||
|
|||||||
@@ -35,6 +35,7 @@ type peerStateDetailOutput struct {
|
|||||||
TransferReceived int64 `json:"transferReceived" yaml:"transferReceived"`
|
TransferReceived int64 `json:"transferReceived" yaml:"transferReceived"`
|
||||||
TransferSent int64 `json:"transferSent" yaml:"transferSent"`
|
TransferSent int64 `json:"transferSent" yaml:"transferSent"`
|
||||||
RosenpassEnabled bool `json:"quantumResistance" yaml:"quantumResistance"`
|
RosenpassEnabled bool `json:"quantumResistance" yaml:"quantumResistance"`
|
||||||
|
Routes []string `json:"routes" yaml:"routes"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type peersStateOutput struct {
|
type peersStateOutput struct {
|
||||||
@@ -72,19 +73,28 @@ type iceCandidateType struct {
|
|||||||
Remote string `json:"remote" yaml:"remote"`
|
Remote string `json:"remote" yaml:"remote"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type nsServerGroupStateOutput struct {
|
||||||
|
Servers []string `json:"servers" yaml:"servers"`
|
||||||
|
Domains []string `json:"domains" yaml:"domains"`
|
||||||
|
Enabled bool `json:"enabled" yaml:"enabled"`
|
||||||
|
Error string `json:"error" yaml:"error"`
|
||||||
|
}
|
||||||
|
|
||||||
type statusOutputOverview struct {
|
type statusOutputOverview struct {
|
||||||
Peers peersStateOutput `json:"peers" yaml:"peers"`
|
Peers peersStateOutput `json:"peers" yaml:"peers"`
|
||||||
CliVersion string `json:"cliVersion" yaml:"cliVersion"`
|
CliVersion string `json:"cliVersion" yaml:"cliVersion"`
|
||||||
DaemonVersion string `json:"daemonVersion" yaml:"daemonVersion"`
|
DaemonVersion string `json:"daemonVersion" yaml:"daemonVersion"`
|
||||||
ManagementState managementStateOutput `json:"management" yaml:"management"`
|
ManagementState managementStateOutput `json:"management" yaml:"management"`
|
||||||
SignalState signalStateOutput `json:"signal" yaml:"signal"`
|
SignalState signalStateOutput `json:"signal" yaml:"signal"`
|
||||||
Relays relayStateOutput `json:"relays" yaml:"relays"`
|
Relays relayStateOutput `json:"relays" yaml:"relays"`
|
||||||
IP string `json:"netbirdIp" yaml:"netbirdIp"`
|
IP string `json:"netbirdIp" yaml:"netbirdIp"`
|
||||||
PubKey string `json:"publicKey" yaml:"publicKey"`
|
PubKey string `json:"publicKey" yaml:"publicKey"`
|
||||||
KernelInterface bool `json:"usesKernelInterface" yaml:"usesKernelInterface"`
|
KernelInterface bool `json:"usesKernelInterface" yaml:"usesKernelInterface"`
|
||||||
FQDN string `json:"fqdn" yaml:"fqdn"`
|
FQDN string `json:"fqdn" yaml:"fqdn"`
|
||||||
RosenpassEnabled bool `json:"quantumResistance" yaml:"quantumResistance"`
|
RosenpassEnabled bool `json:"quantumResistance" yaml:"quantumResistance"`
|
||||||
RosenpassPermissive bool `json:"quantumResistancePermissive" yaml:"quantumResistancePermissive"`
|
RosenpassPermissive bool `json:"quantumResistancePermissive" yaml:"quantumResistancePermissive"`
|
||||||
|
Routes []string `json:"routes" yaml:"routes"`
|
||||||
|
NSServerGroups []nsServerGroupStateOutput `json:"dnsServers" yaml:"dnsServers"`
|
||||||
}
|
}
|
||||||
|
|
||||||
var (
|
var (
|
||||||
@@ -168,7 +178,7 @@ func statusFunc(cmd *cobra.Command, args []string) error {
|
|||||||
case yamlFlag:
|
case yamlFlag:
|
||||||
statusOutputString, err = parseToYAML(outputInformationHolder)
|
statusOutputString, err = parseToYAML(outputInformationHolder)
|
||||||
default:
|
default:
|
||||||
statusOutputString = parseGeneralSummary(outputInformationHolder, false, false)
|
statusOutputString = parseGeneralSummary(outputInformationHolder, false, false, false)
|
||||||
}
|
}
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -268,6 +278,8 @@ func convertToStatusOutputOverview(resp *proto.StatusResponse) statusOutputOverv
|
|||||||
FQDN: pbFullStatus.GetLocalPeerState().GetFqdn(),
|
FQDN: pbFullStatus.GetLocalPeerState().GetFqdn(),
|
||||||
RosenpassEnabled: pbFullStatus.GetLocalPeerState().GetRosenpassEnabled(),
|
RosenpassEnabled: pbFullStatus.GetLocalPeerState().GetRosenpassEnabled(),
|
||||||
RosenpassPermissive: pbFullStatus.GetLocalPeerState().GetRosenpassPermissive(),
|
RosenpassPermissive: pbFullStatus.GetLocalPeerState().GetRosenpassPermissive(),
|
||||||
|
Routes: pbFullStatus.GetLocalPeerState().GetRoutes(),
|
||||||
|
NSServerGroups: mapNSGroups(pbFullStatus.GetDnsServers()),
|
||||||
}
|
}
|
||||||
|
|
||||||
return overview
|
return overview
|
||||||
@@ -299,6 +311,19 @@ func mapRelays(relays []*proto.RelayState) relayStateOutput {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func mapNSGroups(servers []*proto.NSGroupState) []nsServerGroupStateOutput {
|
||||||
|
mappedNSGroups := make([]nsServerGroupStateOutput, 0, len(servers))
|
||||||
|
for _, pbNsGroupServer := range servers {
|
||||||
|
mappedNSGroups = append(mappedNSGroups, nsServerGroupStateOutput{
|
||||||
|
Servers: pbNsGroupServer.GetServers(),
|
||||||
|
Domains: pbNsGroupServer.GetDomains(),
|
||||||
|
Enabled: pbNsGroupServer.GetEnabled(),
|
||||||
|
Error: pbNsGroupServer.GetError(),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
return mappedNSGroups
|
||||||
|
}
|
||||||
|
|
||||||
func mapPeers(peers []*proto.PeerState) peersStateOutput {
|
func mapPeers(peers []*proto.PeerState) peersStateOutput {
|
||||||
var peersStateDetail []peerStateDetailOutput
|
var peersStateDetail []peerStateDetailOutput
|
||||||
localICE := ""
|
localICE := ""
|
||||||
@@ -352,6 +377,7 @@ func mapPeers(peers []*proto.PeerState) peersStateOutput {
|
|||||||
TransferReceived: transferReceived,
|
TransferReceived: transferReceived,
|
||||||
TransferSent: transferSent,
|
TransferSent: transferSent,
|
||||||
RosenpassEnabled: pbPeerState.GetRosenpassEnabled(),
|
RosenpassEnabled: pbPeerState.GetRosenpassEnabled(),
|
||||||
|
Routes: pbPeerState.GetRoutes(),
|
||||||
}
|
}
|
||||||
|
|
||||||
peersStateDetail = append(peersStateDetail, peerState)
|
peersStateDetail = append(peersStateDetail, peerState)
|
||||||
@@ -401,8 +427,7 @@ func parseToYAML(overview statusOutputOverview) (string, error) {
|
|||||||
return string(yamlBytes), nil
|
return string(yamlBytes), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func parseGeneralSummary(overview statusOutputOverview, showURL bool, showRelays bool) string {
|
func parseGeneralSummary(overview statusOutputOverview, showURL bool, showRelays bool, showNameServers bool) string {
|
||||||
|
|
||||||
var managementConnString string
|
var managementConnString string
|
||||||
if overview.ManagementState.Connected {
|
if overview.ManagementState.Connected {
|
||||||
managementConnString = "Connected"
|
managementConnString = "Connected"
|
||||||
@@ -438,7 +463,7 @@ func parseGeneralSummary(overview statusOutputOverview, showURL bool, showRelays
|
|||||||
interfaceIP = "N/A"
|
interfaceIP = "N/A"
|
||||||
}
|
}
|
||||||
|
|
||||||
var relayAvailableString string
|
var relaysString string
|
||||||
if showRelays {
|
if showRelays {
|
||||||
for _, relay := range overview.Relays.Details {
|
for _, relay := range overview.Relays.Details {
|
||||||
available := "Available"
|
available := "Available"
|
||||||
@@ -447,15 +472,46 @@ func parseGeneralSummary(overview statusOutputOverview, showURL bool, showRelays
|
|||||||
available = "Unavailable"
|
available = "Unavailable"
|
||||||
reason = fmt.Sprintf(", reason: %s", relay.Error)
|
reason = fmt.Sprintf(", reason: %s", relay.Error)
|
||||||
}
|
}
|
||||||
relayAvailableString += fmt.Sprintf("\n [%s] is %s%s", relay.URI, available, reason)
|
relaysString += fmt.Sprintf("\n [%s] is %s%s", relay.URI, available, reason)
|
||||||
|
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
relaysString = fmt.Sprintf("%d/%d Available", overview.Relays.Available, overview.Relays.Total)
|
||||||
relayAvailableString = fmt.Sprintf("%d/%d Available", overview.Relays.Available, overview.Relays.Total)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
peersCountString := fmt.Sprintf("%d/%d Connected", overview.Peers.Connected, overview.Peers.Total)
|
routes := "-"
|
||||||
|
if len(overview.Routes) > 0 {
|
||||||
|
sort.Strings(overview.Routes)
|
||||||
|
routes = strings.Join(overview.Routes, ", ")
|
||||||
|
}
|
||||||
|
|
||||||
|
var dnsServersString string
|
||||||
|
if showNameServers {
|
||||||
|
for _, nsServerGroup := range overview.NSServerGroups {
|
||||||
|
enabled := "Available"
|
||||||
|
if !nsServerGroup.Enabled {
|
||||||
|
enabled = "Unavailable"
|
||||||
|
}
|
||||||
|
errorString := ""
|
||||||
|
if nsServerGroup.Error != "" {
|
||||||
|
errorString = fmt.Sprintf(", reason: %s", nsServerGroup.Error)
|
||||||
|
errorString = strings.TrimSpace(errorString)
|
||||||
|
}
|
||||||
|
|
||||||
|
domainsString := strings.Join(nsServerGroup.Domains, ", ")
|
||||||
|
if domainsString == "" {
|
||||||
|
domainsString = "." // Show "." for the default zone
|
||||||
|
}
|
||||||
|
dnsServersString += fmt.Sprintf(
|
||||||
|
"\n [%s] for [%s] is %s%s",
|
||||||
|
strings.Join(nsServerGroup.Servers, ", "),
|
||||||
|
domainsString,
|
||||||
|
enabled,
|
||||||
|
errorString,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
dnsServersString = fmt.Sprintf("%d/%d Available", countEnabled(overview.NSServerGroups), len(overview.NSServerGroups))
|
||||||
|
}
|
||||||
|
|
||||||
rosenpassEnabledStatus := "false"
|
rosenpassEnabledStatus := "false"
|
||||||
if overview.RosenpassEnabled {
|
if overview.RosenpassEnabled {
|
||||||
@@ -465,26 +521,32 @@ func parseGeneralSummary(overview statusOutputOverview, showURL bool, showRelays
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
peersCountString := fmt.Sprintf("%d/%d Connected", overview.Peers.Connected, overview.Peers.Total)
|
||||||
|
|
||||||
summary := fmt.Sprintf(
|
summary := fmt.Sprintf(
|
||||||
"Daemon version: %s\n"+
|
"Daemon version: %s\n"+
|
||||||
"CLI version: %s\n"+
|
"CLI version: %s\n"+
|
||||||
"Management: %s\n"+
|
"Management: %s\n"+
|
||||||
"Signal: %s\n"+
|
"Signal: %s\n"+
|
||||||
"Relays: %s\n"+
|
"Relays: %s\n"+
|
||||||
|
"Nameservers: %s\n"+
|
||||||
"FQDN: %s\n"+
|
"FQDN: %s\n"+
|
||||||
"NetBird IP: %s\n"+
|
"NetBird IP: %s\n"+
|
||||||
"Interface type: %s\n"+
|
"Interface type: %s\n"+
|
||||||
"Quantum resistance: %s\n"+
|
"Quantum resistance: %s\n"+
|
||||||
|
"Routes: %s\n"+
|
||||||
"Peers count: %s\n",
|
"Peers count: %s\n",
|
||||||
overview.DaemonVersion,
|
overview.DaemonVersion,
|
||||||
version.NetbirdVersion(),
|
version.NetbirdVersion(),
|
||||||
managementConnString,
|
managementConnString,
|
||||||
signalConnString,
|
signalConnString,
|
||||||
relayAvailableString,
|
relaysString,
|
||||||
|
dnsServersString,
|
||||||
overview.FQDN,
|
overview.FQDN,
|
||||||
interfaceIP,
|
interfaceIP,
|
||||||
interfaceTypeString,
|
interfaceTypeString,
|
||||||
rosenpassEnabledStatus,
|
rosenpassEnabledStatus,
|
||||||
|
routes,
|
||||||
peersCountString,
|
peersCountString,
|
||||||
)
|
)
|
||||||
return summary
|
return summary
|
||||||
@@ -492,7 +554,7 @@ func parseGeneralSummary(overview statusOutputOverview, showURL bool, showRelays
|
|||||||
|
|
||||||
func parseToFullDetailSummary(overview statusOutputOverview) string {
|
func parseToFullDetailSummary(overview statusOutputOverview) string {
|
||||||
parsedPeersString := parsePeers(overview.Peers, overview.RosenpassEnabled, overview.RosenpassPermissive)
|
parsedPeersString := parsePeers(overview.Peers, overview.RosenpassEnabled, overview.RosenpassPermissive)
|
||||||
summary := parseGeneralSummary(overview, true, true)
|
summary := parseGeneralSummary(overview, true, true, true)
|
||||||
|
|
||||||
return fmt.Sprintf(
|
return fmt.Sprintf(
|
||||||
"Peers detail:"+
|
"Peers detail:"+
|
||||||
@@ -556,6 +618,12 @@ func parsePeers(peers peersStateOutput, rosenpassEnabled, rosenpassPermissive bo
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
routes := "-"
|
||||||
|
if len(peerState.Routes) > 0 {
|
||||||
|
sort.Strings(peerState.Routes)
|
||||||
|
routes = strings.Join(peerState.Routes, ", ")
|
||||||
|
}
|
||||||
|
|
||||||
peerString := fmt.Sprintf(
|
peerString := fmt.Sprintf(
|
||||||
"\n %s:\n"+
|
"\n %s:\n"+
|
||||||
" NetBird IP: %s\n"+
|
" NetBird IP: %s\n"+
|
||||||
@@ -569,7 +637,8 @@ func parsePeers(peers peersStateOutput, rosenpassEnabled, rosenpassPermissive bo
|
|||||||
" Last connection update: %s\n"+
|
" Last connection update: %s\n"+
|
||||||
" Last WireGuard handshake: %s\n"+
|
" Last WireGuard handshake: %s\n"+
|
||||||
" Transfer status (received/sent) %s/%s\n"+
|
" Transfer status (received/sent) %s/%s\n"+
|
||||||
" Quantum resistance: %s\n",
|
" Quantum resistance: %s\n"+
|
||||||
|
" Routes: %s\n",
|
||||||
peerState.FQDN,
|
peerState.FQDN,
|
||||||
peerState.IP,
|
peerState.IP,
|
||||||
peerState.PubKey,
|
peerState.PubKey,
|
||||||
@@ -585,6 +654,7 @@ func parsePeers(peers peersStateOutput, rosenpassEnabled, rosenpassPermissive bo
|
|||||||
toIEC(peerState.TransferReceived),
|
toIEC(peerState.TransferReceived),
|
||||||
toIEC(peerState.TransferSent),
|
toIEC(peerState.TransferSent),
|
||||||
rosenpassEnabledStatus,
|
rosenpassEnabledStatus,
|
||||||
|
routes,
|
||||||
)
|
)
|
||||||
|
|
||||||
peersString += peerString
|
peersString += peerString
|
||||||
@@ -638,3 +708,13 @@ func toIEC(b int64) string {
|
|||||||
return fmt.Sprintf("%.1f %ciB",
|
return fmt.Sprintf("%.1f %ciB",
|
||||||
float64(b)/float64(div), "KMGTPE"[exp])
|
float64(b)/float64(div), "KMGTPE"[exp])
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func countEnabled(dnsServers []nsServerGroupStateOutput) int {
|
||||||
|
count := 0
|
||||||
|
for _, server := range dnsServers {
|
||||||
|
if server.Enabled {
|
||||||
|
count++
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return count
|
||||||
|
}
|
||||||
|
|||||||
@@ -42,6 +42,9 @@ var resp = &proto.StatusResponse{
|
|||||||
LastWireguardHandshake: timestamppb.New(time.Date(2001, time.Month(1), 1, 1, 1, 2, 0, time.UTC)),
|
LastWireguardHandshake: timestamppb.New(time.Date(2001, time.Month(1), 1, 1, 1, 2, 0, time.UTC)),
|
||||||
BytesRx: 200,
|
BytesRx: 200,
|
||||||
BytesTx: 100,
|
BytesTx: 100,
|
||||||
|
Routes: []string{
|
||||||
|
"10.1.0.0/24",
|
||||||
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
IP: "192.168.178.102",
|
IP: "192.168.178.102",
|
||||||
@@ -87,6 +90,31 @@ var resp = &proto.StatusResponse{
|
|||||||
PubKey: "Some-Pub-Key",
|
PubKey: "Some-Pub-Key",
|
||||||
KernelInterface: true,
|
KernelInterface: true,
|
||||||
Fqdn: "some-localhost.awesome-domain.com",
|
Fqdn: "some-localhost.awesome-domain.com",
|
||||||
|
Routes: []string{
|
||||||
|
"10.10.0.0/24",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
DnsServers: []*proto.NSGroupState{
|
||||||
|
{
|
||||||
|
Servers: []string{
|
||||||
|
"8.8.8.8:53",
|
||||||
|
},
|
||||||
|
Domains: nil,
|
||||||
|
Enabled: true,
|
||||||
|
Error: "",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Servers: []string{
|
||||||
|
"1.1.1.1:53",
|
||||||
|
"2.2.2.2:53",
|
||||||
|
},
|
||||||
|
Domains: []string{
|
||||||
|
"example.com",
|
||||||
|
"example.net",
|
||||||
|
},
|
||||||
|
Enabled: false,
|
||||||
|
Error: "timeout",
|
||||||
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
DaemonVersion: "0.14.1",
|
DaemonVersion: "0.14.1",
|
||||||
@@ -116,6 +144,9 @@ var overview = statusOutputOverview{
|
|||||||
LastWireguardHandshake: time.Date(2001, 1, 1, 1, 1, 2, 0, time.UTC),
|
LastWireguardHandshake: time.Date(2001, 1, 1, 1, 1, 2, 0, time.UTC),
|
||||||
TransferReceived: 200,
|
TransferReceived: 200,
|
||||||
TransferSent: 100,
|
TransferSent: 100,
|
||||||
|
Routes: []string{
|
||||||
|
"10.1.0.0/24",
|
||||||
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
IP: "192.168.178.102",
|
IP: "192.168.178.102",
|
||||||
@@ -171,6 +202,31 @@ var overview = statusOutputOverview{
|
|||||||
PubKey: "Some-Pub-Key",
|
PubKey: "Some-Pub-Key",
|
||||||
KernelInterface: true,
|
KernelInterface: true,
|
||||||
FQDN: "some-localhost.awesome-domain.com",
|
FQDN: "some-localhost.awesome-domain.com",
|
||||||
|
NSServerGroups: []nsServerGroupStateOutput{
|
||||||
|
{
|
||||||
|
Servers: []string{
|
||||||
|
"8.8.8.8:53",
|
||||||
|
},
|
||||||
|
Domains: nil,
|
||||||
|
Enabled: true,
|
||||||
|
Error: "",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Servers: []string{
|
||||||
|
"1.1.1.1:53",
|
||||||
|
"2.2.2.2:53",
|
||||||
|
},
|
||||||
|
Domains: []string{
|
||||||
|
"example.com",
|
||||||
|
"example.net",
|
||||||
|
},
|
||||||
|
Enabled: false,
|
||||||
|
Error: "timeout",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Routes: []string{
|
||||||
|
"10.10.0.0/24",
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestConversionFromFullStatusToOutputOverview(t *testing.T) {
|
func TestConversionFromFullStatusToOutputOverview(t *testing.T) {
|
||||||
@@ -232,7 +288,10 @@ func TestParsingToJSON(t *testing.T) {
|
|||||||
"lastWireguardHandshake": "2001-01-01T01:01:02Z",
|
"lastWireguardHandshake": "2001-01-01T01:01:02Z",
|
||||||
"transferReceived": 200,
|
"transferReceived": 200,
|
||||||
"transferSent": 100,
|
"transferSent": 100,
|
||||||
"quantumResistance":false
|
"quantumResistance": false,
|
||||||
|
"routes": [
|
||||||
|
"10.1.0.0/24"
|
||||||
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"fqdn": "peer-2.awesome-domain.com",
|
"fqdn": "peer-2.awesome-domain.com",
|
||||||
@@ -253,7 +312,8 @@ func TestParsingToJSON(t *testing.T) {
|
|||||||
"lastWireguardHandshake": "2002-02-02T02:02:03Z",
|
"lastWireguardHandshake": "2002-02-02T02:02:03Z",
|
||||||
"transferReceived": 2000,
|
"transferReceived": 2000,
|
||||||
"transferSent": 1000,
|
"transferSent": 1000,
|
||||||
"quantumResistance":false
|
"quantumResistance": false,
|
||||||
|
"routes": null
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
@@ -289,8 +349,33 @@ func TestParsingToJSON(t *testing.T) {
|
|||||||
"publicKey": "Some-Pub-Key",
|
"publicKey": "Some-Pub-Key",
|
||||||
"usesKernelInterface": true,
|
"usesKernelInterface": true,
|
||||||
"fqdn": "some-localhost.awesome-domain.com",
|
"fqdn": "some-localhost.awesome-domain.com",
|
||||||
"quantumResistance":false,
|
"quantumResistance": false,
|
||||||
"quantumResistancePermissive":false
|
"quantumResistancePermissive": false,
|
||||||
|
"routes": [
|
||||||
|
"10.10.0.0/24"
|
||||||
|
],
|
||||||
|
"dnsServers": [
|
||||||
|
{
|
||||||
|
"servers": [
|
||||||
|
"8.8.8.8:53"
|
||||||
|
],
|
||||||
|
"domains": null,
|
||||||
|
"enabled": true,
|
||||||
|
"error": ""
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"servers": [
|
||||||
|
"1.1.1.1:53",
|
||||||
|
"2.2.2.2:53"
|
||||||
|
],
|
||||||
|
"domains": [
|
||||||
|
"example.com",
|
||||||
|
"example.net"
|
||||||
|
],
|
||||||
|
"enabled": false,
|
||||||
|
"error": "timeout"
|
||||||
|
}
|
||||||
|
]
|
||||||
}`
|
}`
|
||||||
// @formatter:on
|
// @formatter:on
|
||||||
|
|
||||||
@@ -325,6 +410,8 @@ func TestParsingToYAML(t *testing.T) {
|
|||||||
transferReceived: 200
|
transferReceived: 200
|
||||||
transferSent: 100
|
transferSent: 100
|
||||||
quantumResistance: false
|
quantumResistance: false
|
||||||
|
routes:
|
||||||
|
- 10.1.0.0/24
|
||||||
- fqdn: peer-2.awesome-domain.com
|
- fqdn: peer-2.awesome-domain.com
|
||||||
netbirdIp: 192.168.178.102
|
netbirdIp: 192.168.178.102
|
||||||
publicKey: Pubkey2
|
publicKey: Pubkey2
|
||||||
@@ -342,6 +429,7 @@ func TestParsingToYAML(t *testing.T) {
|
|||||||
transferReceived: 2000
|
transferReceived: 2000
|
||||||
transferSent: 1000
|
transferSent: 1000
|
||||||
quantumResistance: false
|
quantumResistance: false
|
||||||
|
routes: []
|
||||||
cliVersion: development
|
cliVersion: development
|
||||||
daemonVersion: 0.14.1
|
daemonVersion: 0.14.1
|
||||||
management:
|
management:
|
||||||
@@ -368,6 +456,22 @@ usesKernelInterface: true
|
|||||||
fqdn: some-localhost.awesome-domain.com
|
fqdn: some-localhost.awesome-domain.com
|
||||||
quantumResistance: false
|
quantumResistance: false
|
||||||
quantumResistancePermissive: false
|
quantumResistancePermissive: false
|
||||||
|
routes:
|
||||||
|
- 10.10.0.0/24
|
||||||
|
dnsServers:
|
||||||
|
- servers:
|
||||||
|
- 8.8.8.8:53
|
||||||
|
domains: []
|
||||||
|
enabled: true
|
||||||
|
error: ""
|
||||||
|
- servers:
|
||||||
|
- 1.1.1.1:53
|
||||||
|
- 2.2.2.2:53
|
||||||
|
domains:
|
||||||
|
- example.com
|
||||||
|
- example.net
|
||||||
|
enabled: false
|
||||||
|
error: timeout
|
||||||
`
|
`
|
||||||
|
|
||||||
assert.Equal(t, expectedYAML, yaml)
|
assert.Equal(t, expectedYAML, yaml)
|
||||||
@@ -391,6 +495,7 @@ func TestParsingToDetail(t *testing.T) {
|
|||||||
Last WireGuard handshake: 2001-01-01 01:01:02
|
Last WireGuard handshake: 2001-01-01 01:01:02
|
||||||
Transfer status (received/sent) 200 B/100 B
|
Transfer status (received/sent) 200 B/100 B
|
||||||
Quantum resistance: false
|
Quantum resistance: false
|
||||||
|
Routes: 10.1.0.0/24
|
||||||
|
|
||||||
peer-2.awesome-domain.com:
|
peer-2.awesome-domain.com:
|
||||||
NetBird IP: 192.168.178.102
|
NetBird IP: 192.168.178.102
|
||||||
@@ -405,6 +510,7 @@ func TestParsingToDetail(t *testing.T) {
|
|||||||
Last WireGuard handshake: 2002-02-02 02:02:03
|
Last WireGuard handshake: 2002-02-02 02:02:03
|
||||||
Transfer status (received/sent) 2.0 KiB/1000 B
|
Transfer status (received/sent) 2.0 KiB/1000 B
|
||||||
Quantum resistance: false
|
Quantum resistance: false
|
||||||
|
Routes: -
|
||||||
|
|
||||||
Daemon version: 0.14.1
|
Daemon version: 0.14.1
|
||||||
CLI version: development
|
CLI version: development
|
||||||
@@ -413,10 +519,14 @@ Signal: Connected to my-awesome-signal.com:443
|
|||||||
Relays:
|
Relays:
|
||||||
[stun:my-awesome-stun.com:3478] is Available
|
[stun:my-awesome-stun.com:3478] is Available
|
||||||
[turns:my-awesome-turn.com:443?transport=tcp] is Unavailable, reason: context: deadline exceeded
|
[turns:my-awesome-turn.com:443?transport=tcp] is Unavailable, reason: context: deadline exceeded
|
||||||
|
Nameservers:
|
||||||
|
[8.8.8.8:53] for [.] is Available
|
||||||
|
[1.1.1.1:53, 2.2.2.2:53] for [example.com, example.net] is Unavailable, reason: timeout
|
||||||
FQDN: some-localhost.awesome-domain.com
|
FQDN: some-localhost.awesome-domain.com
|
||||||
NetBird IP: 192.168.178.100/16
|
NetBird IP: 192.168.178.100/16
|
||||||
Interface type: Kernel
|
Interface type: Kernel
|
||||||
Quantum resistance: false
|
Quantum resistance: false
|
||||||
|
Routes: 10.10.0.0/24
|
||||||
Peers count: 2/2 Connected
|
Peers count: 2/2 Connected
|
||||||
`
|
`
|
||||||
|
|
||||||
@@ -424,7 +534,7 @@ Peers count: 2/2 Connected
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestParsingToShortVersion(t *testing.T) {
|
func TestParsingToShortVersion(t *testing.T) {
|
||||||
shortVersion := parseGeneralSummary(overview, false, false)
|
shortVersion := parseGeneralSummary(overview, false, false, false)
|
||||||
|
|
||||||
expectedString :=
|
expectedString :=
|
||||||
`Daemon version: 0.14.1
|
`Daemon version: 0.14.1
|
||||||
@@ -432,10 +542,12 @@ CLI version: development
|
|||||||
Management: Connected
|
Management: Connected
|
||||||
Signal: Connected
|
Signal: Connected
|
||||||
Relays: 1/2 Available
|
Relays: 1/2 Available
|
||||||
|
Nameservers: 1/2 Available
|
||||||
FQDN: some-localhost.awesome-domain.com
|
FQDN: some-localhost.awesome-domain.com
|
||||||
NetBird IP: 192.168.178.100/16
|
NetBird IP: 192.168.178.100/16
|
||||||
Interface type: Kernel
|
Interface type: Kernel
|
||||||
Quantum resistance: false
|
Quantum resistance: false
|
||||||
|
Routes: 10.10.0.0/24
|
||||||
Peers count: 2/2 Connected
|
Peers count: 2/2 Connected
|
||||||
`
|
`
|
||||||
|
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import (
|
|||||||
"context"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
"net/netip"
|
"net/netip"
|
||||||
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
|
|
||||||
"github.com/miekg/dns"
|
"github.com/miekg/dns"
|
||||||
@@ -11,6 +12,7 @@ import (
|
|||||||
log "github.com/sirupsen/logrus"
|
log "github.com/sirupsen/logrus"
|
||||||
|
|
||||||
"github.com/netbirdio/netbird/client/internal/listener"
|
"github.com/netbirdio/netbird/client/internal/listener"
|
||||||
|
"github.com/netbirdio/netbird/client/internal/peer"
|
||||||
nbdns "github.com/netbirdio/netbird/dns"
|
nbdns "github.com/netbirdio/netbird/dns"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -59,6 +61,8 @@ type DefaultServer struct {
|
|||||||
// make sense on mobile only
|
// make sense on mobile only
|
||||||
searchDomainNotifier *notifier
|
searchDomainNotifier *notifier
|
||||||
iosDnsManager IosDnsManager
|
iosDnsManager IosDnsManager
|
||||||
|
|
||||||
|
statusRecorder *peer.Status
|
||||||
}
|
}
|
||||||
|
|
||||||
type handlerWithStop interface {
|
type handlerWithStop interface {
|
||||||
@@ -73,7 +77,12 @@ type muxUpdate struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// NewDefaultServer returns a new dns server
|
// NewDefaultServer returns a new dns server
|
||||||
func NewDefaultServer(ctx context.Context, wgInterface WGIface, customAddress string) (*DefaultServer, error) {
|
func NewDefaultServer(
|
||||||
|
ctx context.Context,
|
||||||
|
wgInterface WGIface,
|
||||||
|
customAddress string,
|
||||||
|
statusRecorder *peer.Status,
|
||||||
|
) (*DefaultServer, error) {
|
||||||
var addrPort *netip.AddrPort
|
var addrPort *netip.AddrPort
|
||||||
if customAddress != "" {
|
if customAddress != "" {
|
||||||
parsedAddrPort, err := netip.ParseAddrPort(customAddress)
|
parsedAddrPort, err := netip.ParseAddrPort(customAddress)
|
||||||
@@ -90,13 +99,20 @@ func NewDefaultServer(ctx context.Context, wgInterface WGIface, customAddress st
|
|||||||
dnsService = newServiceViaListener(wgInterface, addrPort)
|
dnsService = newServiceViaListener(wgInterface, addrPort)
|
||||||
}
|
}
|
||||||
|
|
||||||
return newDefaultServer(ctx, wgInterface, dnsService), nil
|
return newDefaultServer(ctx, wgInterface, dnsService, statusRecorder), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewDefaultServerPermanentUpstream returns a new dns server. It optimized for mobile systems
|
// NewDefaultServerPermanentUpstream returns a new dns server. It optimized for mobile systems
|
||||||
func NewDefaultServerPermanentUpstream(ctx context.Context, wgInterface WGIface, hostsDnsList []string, config nbdns.Config, listener listener.NetworkChangeListener) *DefaultServer {
|
func NewDefaultServerPermanentUpstream(
|
||||||
|
ctx context.Context,
|
||||||
|
wgInterface WGIface,
|
||||||
|
hostsDnsList []string,
|
||||||
|
config nbdns.Config,
|
||||||
|
listener listener.NetworkChangeListener,
|
||||||
|
statusRecorder *peer.Status,
|
||||||
|
) *DefaultServer {
|
||||||
log.Debugf("host dns address list is: %v", hostsDnsList)
|
log.Debugf("host dns address list is: %v", hostsDnsList)
|
||||||
ds := newDefaultServer(ctx, wgInterface, newServiceViaMemory(wgInterface))
|
ds := newDefaultServer(ctx, wgInterface, newServiceViaMemory(wgInterface), statusRecorder)
|
||||||
ds.permanent = true
|
ds.permanent = true
|
||||||
ds.hostsDnsList = hostsDnsList
|
ds.hostsDnsList = hostsDnsList
|
||||||
ds.addHostRootZone()
|
ds.addHostRootZone()
|
||||||
@@ -108,13 +124,18 @@ func NewDefaultServerPermanentUpstream(ctx context.Context, wgInterface WGIface,
|
|||||||
}
|
}
|
||||||
|
|
||||||
// NewDefaultServerIos returns a new dns server. It optimized for ios
|
// NewDefaultServerIos returns a new dns server. It optimized for ios
|
||||||
func NewDefaultServerIos(ctx context.Context, wgInterface WGIface, iosDnsManager IosDnsManager) *DefaultServer {
|
func NewDefaultServerIos(
|
||||||
ds := newDefaultServer(ctx, wgInterface, newServiceViaMemory(wgInterface))
|
ctx context.Context,
|
||||||
|
wgInterface WGIface,
|
||||||
|
iosDnsManager IosDnsManager,
|
||||||
|
statusRecorder *peer.Status,
|
||||||
|
) *DefaultServer {
|
||||||
|
ds := newDefaultServer(ctx, wgInterface, newServiceViaMemory(wgInterface), statusRecorder)
|
||||||
ds.iosDnsManager = iosDnsManager
|
ds.iosDnsManager = iosDnsManager
|
||||||
return ds
|
return ds
|
||||||
}
|
}
|
||||||
|
|
||||||
func newDefaultServer(ctx context.Context, wgInterface WGIface, dnsService service) *DefaultServer {
|
func newDefaultServer(ctx context.Context, wgInterface WGIface, dnsService service, statusRecorder *peer.Status) *DefaultServer {
|
||||||
ctx, stop := context.WithCancel(ctx)
|
ctx, stop := context.WithCancel(ctx)
|
||||||
defaultServer := &DefaultServer{
|
defaultServer := &DefaultServer{
|
||||||
ctx: ctx,
|
ctx: ctx,
|
||||||
@@ -124,7 +145,8 @@ func newDefaultServer(ctx context.Context, wgInterface WGIface, dnsService servi
|
|||||||
localResolver: &localResolver{
|
localResolver: &localResolver{
|
||||||
registeredMap: make(registrationMap),
|
registeredMap: make(registrationMap),
|
||||||
},
|
},
|
||||||
wgInterface: wgInterface,
|
wgInterface: wgInterface,
|
||||||
|
statusRecorder: statusRecorder,
|
||||||
}
|
}
|
||||||
|
|
||||||
return defaultServer
|
return defaultServer
|
||||||
@@ -299,6 +321,8 @@ func (s *DefaultServer) applyConfiguration(update nbdns.Config) error {
|
|||||||
s.searchDomainNotifier.onNewSearchDomains(s.SearchDomains())
|
s.searchDomainNotifier.onNewSearchDomains(s.SearchDomains())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
s.updateNSGroupStates(update.NameServerGroups)
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -338,7 +362,13 @@ func (s *DefaultServer) buildUpstreamHandlerUpdate(nameServerGroups []*nbdns.Nam
|
|||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
handler, err := newUpstreamResolver(s.ctx, s.wgInterface.Name(), s.wgInterface.Address().IP, s.wgInterface.Address().Network)
|
handler, err := newUpstreamResolver(
|
||||||
|
s.ctx,
|
||||||
|
s.wgInterface.Name(),
|
||||||
|
s.wgInterface.Address().IP,
|
||||||
|
s.wgInterface.Address().Network,
|
||||||
|
s.statusRecorder,
|
||||||
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("unable to create a new upstream resolver, error: %v", err)
|
return nil, fmt.Errorf("unable to create a new upstream resolver, error: %v", err)
|
||||||
}
|
}
|
||||||
@@ -460,14 +490,14 @@ func getNSHostPort(ns nbdns.NameServer) string {
|
|||||||
func (s *DefaultServer) upstreamCallbacks(
|
func (s *DefaultServer) upstreamCallbacks(
|
||||||
nsGroup *nbdns.NameServerGroup,
|
nsGroup *nbdns.NameServerGroup,
|
||||||
handler dns.Handler,
|
handler dns.Handler,
|
||||||
) (deactivate func(), reactivate func()) {
|
) (deactivate func(error), reactivate func()) {
|
||||||
var removeIndex map[string]int
|
var removeIndex map[string]int
|
||||||
deactivate = func() {
|
deactivate = func(err error) {
|
||||||
s.mux.Lock()
|
s.mux.Lock()
|
||||||
defer s.mux.Unlock()
|
defer s.mux.Unlock()
|
||||||
|
|
||||||
l := log.WithField("nameservers", nsGroup.NameServers)
|
l := log.WithField("nameservers", nsGroup.NameServers)
|
||||||
l.Info("temporary deactivate nameservers group due timeout")
|
l.Info("Temporarily deactivating nameservers group due to timeout")
|
||||||
|
|
||||||
removeIndex = make(map[string]int)
|
removeIndex = make(map[string]int)
|
||||||
for _, domain := range nsGroup.Domains {
|
for _, domain := range nsGroup.Domains {
|
||||||
@@ -486,8 +516,11 @@ func (s *DefaultServer) upstreamCallbacks(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
if err := s.hostManager.applyDNSConfig(s.currentConfig); err != nil {
|
if err := s.hostManager.applyDNSConfig(s.currentConfig); err != nil {
|
||||||
l.WithError(err).Error("fail to apply nameserver deactivation on the host")
|
l.Errorf("Failed to apply nameserver deactivation on the host: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
s.updateNSState(nsGroup, err, false)
|
||||||
|
|
||||||
}
|
}
|
||||||
reactivate = func() {
|
reactivate = func() {
|
||||||
s.mux.Lock()
|
s.mux.Lock()
|
||||||
@@ -510,12 +543,20 @@ func (s *DefaultServer) upstreamCallbacks(
|
|||||||
if err := s.hostManager.applyDNSConfig(s.currentConfig); err != nil {
|
if err := s.hostManager.applyDNSConfig(s.currentConfig); err != nil {
|
||||||
l.WithError(err).Error("reactivate temporary disabled nameserver group, DNS update apply")
|
l.WithError(err).Error("reactivate temporary disabled nameserver group, DNS update apply")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
s.updateNSState(nsGroup, nil, true)
|
||||||
}
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *DefaultServer) addHostRootZone() {
|
func (s *DefaultServer) addHostRootZone() {
|
||||||
handler, err := newUpstreamResolver(s.ctx, s.wgInterface.Name(), s.wgInterface.Address().IP, s.wgInterface.Address().Network)
|
handler, err := newUpstreamResolver(
|
||||||
|
s.ctx,
|
||||||
|
s.wgInterface.Name(),
|
||||||
|
s.wgInterface.Address().IP,
|
||||||
|
s.wgInterface.Address().Network,
|
||||||
|
s.statusRecorder,
|
||||||
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Errorf("unable to create a new upstream resolver, error: %v", err)
|
log.Errorf("unable to create a new upstream resolver, error: %v", err)
|
||||||
return
|
return
|
||||||
@@ -535,7 +576,50 @@ func (s *DefaultServer) addHostRootZone() {
|
|||||||
|
|
||||||
handler.upstreamServers[n] = fmt.Sprintf("%s:53", ipString)
|
handler.upstreamServers[n] = fmt.Sprintf("%s:53", ipString)
|
||||||
}
|
}
|
||||||
handler.deactivate = func() {}
|
handler.deactivate = func(error) {}
|
||||||
handler.reactivate = func() {}
|
handler.reactivate = func() {}
|
||||||
s.service.RegisterMux(nbdns.RootZone, handler)
|
s.service.RegisterMux(nbdns.RootZone, handler)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (s *DefaultServer) updateNSGroupStates(groups []*nbdns.NameServerGroup) {
|
||||||
|
var states []peer.NSGroupState
|
||||||
|
|
||||||
|
for _, group := range groups {
|
||||||
|
var servers []string
|
||||||
|
for _, ns := range group.NameServers {
|
||||||
|
servers = append(servers, fmt.Sprintf("%s:%d", ns.IP, ns.Port))
|
||||||
|
}
|
||||||
|
|
||||||
|
state := peer.NSGroupState{
|
||||||
|
ID: generateGroupKey(group),
|
||||||
|
Servers: servers,
|
||||||
|
Domains: group.Domains,
|
||||||
|
// The probe will determine the state, default enabled
|
||||||
|
Enabled: true,
|
||||||
|
Error: nil,
|
||||||
|
}
|
||||||
|
states = append(states, state)
|
||||||
|
}
|
||||||
|
s.statusRecorder.UpdateDNSStates(states)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *DefaultServer) updateNSState(nsGroup *nbdns.NameServerGroup, err error, enabled bool) {
|
||||||
|
states := s.statusRecorder.GetDNSStates()
|
||||||
|
id := generateGroupKey(nsGroup)
|
||||||
|
for i, state := range states {
|
||||||
|
if state.ID == id {
|
||||||
|
states[i].Enabled = enabled
|
||||||
|
states[i].Error = err
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
s.statusRecorder.UpdateDNSStates(states)
|
||||||
|
}
|
||||||
|
|
||||||
|
func generateGroupKey(nsGroup *nbdns.NameServerGroup) string {
|
||||||
|
var servers []string
|
||||||
|
for _, ns := range nsGroup.NameServers {
|
||||||
|
servers = append(servers, fmt.Sprintf("%s:%d", ns.IP, ns.Port))
|
||||||
|
}
|
||||||
|
return fmt.Sprintf("%s_%s_%s", nsGroup.ID, nsGroup.Name, strings.Join(servers, ","))
|
||||||
|
}
|
||||||
|
|||||||
@@ -15,6 +15,7 @@ import (
|
|||||||
"golang.zx2c4.com/wireguard/wgctrl/wgtypes"
|
"golang.zx2c4.com/wireguard/wgctrl/wgtypes"
|
||||||
|
|
||||||
"github.com/netbirdio/netbird/client/firewall/uspfilter"
|
"github.com/netbirdio/netbird/client/firewall/uspfilter"
|
||||||
|
"github.com/netbirdio/netbird/client/internal/peer"
|
||||||
"github.com/netbirdio/netbird/client/internal/stdnet"
|
"github.com/netbirdio/netbird/client/internal/stdnet"
|
||||||
nbdns "github.com/netbirdio/netbird/dns"
|
nbdns "github.com/netbirdio/netbird/dns"
|
||||||
"github.com/netbirdio/netbird/formatter"
|
"github.com/netbirdio/netbird/formatter"
|
||||||
@@ -274,7 +275,7 @@ func TestUpdateDNSServer(t *testing.T) {
|
|||||||
t.Log(err)
|
t.Log(err)
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
dnsServer, err := NewDefaultServer(context.Background(), wgIface, "")
|
dnsServer, err := NewDefaultServer(context.Background(), wgIface, "", &peer.Status{})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
@@ -375,7 +376,7 @@ func TestDNSFakeResolverHandleUpdates(t *testing.T) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
dnsServer, err := NewDefaultServer(context.Background(), wgIface, "")
|
dnsServer, err := NewDefaultServer(context.Background(), wgIface, "", &peer.Status{})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Errorf("create DNS server: %v", err)
|
t.Errorf("create DNS server: %v", err)
|
||||||
return
|
return
|
||||||
@@ -470,7 +471,7 @@ func TestDNSServerStartStop(t *testing.T) {
|
|||||||
|
|
||||||
for _, testCase := range testCases {
|
for _, testCase := range testCases {
|
||||||
t.Run(testCase.name, func(t *testing.T) {
|
t.Run(testCase.name, func(t *testing.T) {
|
||||||
dnsServer, err := NewDefaultServer(context.Background(), &mocWGIface{}, testCase.addrPort)
|
dnsServer, err := NewDefaultServer(context.Background(), &mocWGIface{}, testCase.addrPort, &peer.Status{})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("%v", err)
|
t.Fatalf("%v", err)
|
||||||
}
|
}
|
||||||
@@ -541,6 +542,7 @@ func TestDNSServerUpstreamDeactivateCallback(t *testing.T) {
|
|||||||
{false, "domain2", false},
|
{false, "domain2", false},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
statusRecorder: &peer.Status{},
|
||||||
}
|
}
|
||||||
|
|
||||||
var domainsUpdate string
|
var domainsUpdate string
|
||||||
@@ -563,7 +565,7 @@ func TestDNSServerUpstreamDeactivateCallback(t *testing.T) {
|
|||||||
},
|
},
|
||||||
}, nil)
|
}, nil)
|
||||||
|
|
||||||
deactivate()
|
deactivate(nil)
|
||||||
expected := "domain0,domain2"
|
expected := "domain0,domain2"
|
||||||
domains := []string{}
|
domains := []string{}
|
||||||
for _, item := range server.currentConfig.Domains {
|
for _, item := range server.currentConfig.Domains {
|
||||||
@@ -601,7 +603,7 @@ func TestDNSPermanent_updateHostDNS_emptyUpstream(t *testing.T) {
|
|||||||
|
|
||||||
var dnsList []string
|
var dnsList []string
|
||||||
dnsConfig := nbdns.Config{}
|
dnsConfig := nbdns.Config{}
|
||||||
dnsServer := NewDefaultServerPermanentUpstream(context.Background(), wgIFace, dnsList, dnsConfig, nil)
|
dnsServer := NewDefaultServerPermanentUpstream(context.Background(), wgIFace, dnsList, dnsConfig, nil, &peer.Status{})
|
||||||
err = dnsServer.Initialize()
|
err = dnsServer.Initialize()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Errorf("failed to initialize DNS server: %v", err)
|
t.Errorf("failed to initialize DNS server: %v", err)
|
||||||
@@ -625,7 +627,7 @@ func TestDNSPermanent_updateUpstream(t *testing.T) {
|
|||||||
}
|
}
|
||||||
defer wgIFace.Close()
|
defer wgIFace.Close()
|
||||||
dnsConfig := nbdns.Config{}
|
dnsConfig := nbdns.Config{}
|
||||||
dnsServer := NewDefaultServerPermanentUpstream(context.Background(), wgIFace, []string{"8.8.8.8"}, dnsConfig, nil)
|
dnsServer := NewDefaultServerPermanentUpstream(context.Background(), wgIFace, []string{"8.8.8.8"}, dnsConfig, nil, &peer.Status{})
|
||||||
err = dnsServer.Initialize()
|
err = dnsServer.Initialize()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Errorf("failed to initialize DNS server: %v", err)
|
t.Errorf("failed to initialize DNS server: %v", err)
|
||||||
@@ -717,7 +719,7 @@ func TestDNSPermanent_matchOnly(t *testing.T) {
|
|||||||
}
|
}
|
||||||
defer wgIFace.Close()
|
defer wgIFace.Close()
|
||||||
dnsConfig := nbdns.Config{}
|
dnsConfig := nbdns.Config{}
|
||||||
dnsServer := NewDefaultServerPermanentUpstream(context.Background(), wgIFace, []string{"8.8.8.8"}, dnsConfig, nil)
|
dnsServer := NewDefaultServerPermanentUpstream(context.Background(), wgIFace, []string{"8.8.8.8"}, dnsConfig, nil, &peer.Status{})
|
||||||
err = dnsServer.Initialize()
|
err = dnsServer.Initialize()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Errorf("failed to initialize DNS server: %v", err)
|
t.Errorf("failed to initialize DNS server: %v", err)
|
||||||
|
|||||||
@@ -11,8 +11,11 @@ import (
|
|||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/cenkalti/backoff/v4"
|
"github.com/cenkalti/backoff/v4"
|
||||||
|
"github.com/hashicorp/go-multierror"
|
||||||
"github.com/miekg/dns"
|
"github.com/miekg/dns"
|
||||||
log "github.com/sirupsen/logrus"
|
log "github.com/sirupsen/logrus"
|
||||||
|
|
||||||
|
"github.com/netbirdio/netbird/client/internal/peer"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
@@ -45,12 +48,13 @@ type upstreamResolverBase struct {
|
|||||||
reactivatePeriod time.Duration
|
reactivatePeriod time.Duration
|
||||||
upstreamTimeout time.Duration
|
upstreamTimeout time.Duration
|
||||||
|
|
||||||
deactivate func()
|
deactivate func(error)
|
||||||
reactivate func()
|
reactivate func()
|
||||||
|
statusRecorder *peer.Status
|
||||||
}
|
}
|
||||||
|
|
||||||
func newUpstreamResolverBase(parentCTX context.Context) *upstreamResolverBase {
|
func newUpstreamResolverBase(ctx context.Context, statusRecorder *peer.Status) *upstreamResolverBase {
|
||||||
ctx, cancel := context.WithCancel(parentCTX)
|
ctx, cancel := context.WithCancel(ctx)
|
||||||
|
|
||||||
return &upstreamResolverBase{
|
return &upstreamResolverBase{
|
||||||
ctx: ctx,
|
ctx: ctx,
|
||||||
@@ -58,6 +62,7 @@ func newUpstreamResolverBase(parentCTX context.Context) *upstreamResolverBase {
|
|||||||
upstreamTimeout: upstreamTimeout,
|
upstreamTimeout: upstreamTimeout,
|
||||||
reactivatePeriod: reactivatePeriod,
|
reactivatePeriod: reactivatePeriod,
|
||||||
failsTillDeact: failsTillDeact,
|
failsTillDeact: failsTillDeact,
|
||||||
|
statusRecorder: statusRecorder,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -68,7 +73,10 @@ func (u *upstreamResolverBase) stop() {
|
|||||||
|
|
||||||
// ServeDNS handles a DNS request
|
// ServeDNS handles a DNS request
|
||||||
func (u *upstreamResolverBase) ServeDNS(w dns.ResponseWriter, r *dns.Msg) {
|
func (u *upstreamResolverBase) ServeDNS(w dns.ResponseWriter, r *dns.Msg) {
|
||||||
defer u.checkUpstreamFails()
|
var err error
|
||||||
|
defer func() {
|
||||||
|
u.checkUpstreamFails(err)
|
||||||
|
}()
|
||||||
|
|
||||||
log.WithField("question", r.Question[0]).Trace("received an upstream question")
|
log.WithField("question", r.Question[0]).Trace("received an upstream question")
|
||||||
|
|
||||||
@@ -81,7 +89,6 @@ func (u *upstreamResolverBase) ServeDNS(w dns.ResponseWriter, r *dns.Msg) {
|
|||||||
for _, upstream := range u.upstreamServers {
|
for _, upstream := range u.upstreamServers {
|
||||||
var rm *dns.Msg
|
var rm *dns.Msg
|
||||||
var t time.Duration
|
var t time.Duration
|
||||||
var err error
|
|
||||||
|
|
||||||
func() {
|
func() {
|
||||||
ctx, cancel := context.WithTimeout(u.ctx, u.upstreamTimeout)
|
ctx, cancel := context.WithTimeout(u.ctx, u.upstreamTimeout)
|
||||||
@@ -132,7 +139,7 @@ func (u *upstreamResolverBase) ServeDNS(w dns.ResponseWriter, r *dns.Msg) {
|
|||||||
// If fails count is greater that failsTillDeact, upstream resolving
|
// If fails count is greater that failsTillDeact, upstream resolving
|
||||||
// will be disabled for reactivatePeriod, after that time period fails counter
|
// will be disabled for reactivatePeriod, after that time period fails counter
|
||||||
// will be reset and upstream will be reactivated.
|
// will be reset and upstream will be reactivated.
|
||||||
func (u *upstreamResolverBase) checkUpstreamFails() {
|
func (u *upstreamResolverBase) checkUpstreamFails(err error) {
|
||||||
u.mutex.Lock()
|
u.mutex.Lock()
|
||||||
defer u.mutex.Unlock()
|
defer u.mutex.Unlock()
|
||||||
|
|
||||||
@@ -146,7 +153,7 @@ func (u *upstreamResolverBase) checkUpstreamFails() {
|
|||||||
default:
|
default:
|
||||||
}
|
}
|
||||||
|
|
||||||
u.disable()
|
u.disable(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// probeAvailability tests all upstream servers simultaneously and
|
// probeAvailability tests all upstream servers simultaneously and
|
||||||
@@ -165,13 +172,16 @@ func (u *upstreamResolverBase) probeAvailability() {
|
|||||||
var mu sync.Mutex
|
var mu sync.Mutex
|
||||||
var wg sync.WaitGroup
|
var wg sync.WaitGroup
|
||||||
|
|
||||||
|
var errors *multierror.Error
|
||||||
for _, upstream := range u.upstreamServers {
|
for _, upstream := range u.upstreamServers {
|
||||||
upstream := upstream
|
upstream := upstream
|
||||||
|
|
||||||
wg.Add(1)
|
wg.Add(1)
|
||||||
go func() {
|
go func() {
|
||||||
defer wg.Done()
|
defer wg.Done()
|
||||||
if err := u.testNameserver(upstream); err != nil {
|
err := u.testNameserver(upstream)
|
||||||
|
if err != nil {
|
||||||
|
errors = multierror.Append(errors, err)
|
||||||
log.Warnf("probing upstream nameserver %s: %s", upstream, err)
|
log.Warnf("probing upstream nameserver %s: %s", upstream, err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -186,7 +196,7 @@ func (u *upstreamResolverBase) probeAvailability() {
|
|||||||
|
|
||||||
// didn't find a working upstream server, let's disable and try later
|
// didn't find a working upstream server, let's disable and try later
|
||||||
if !success {
|
if !success {
|
||||||
u.disable()
|
u.disable(errors.ErrorOrNil())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -245,15 +255,15 @@ func isTimeout(err error) bool {
|
|||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
func (u *upstreamResolverBase) disable() {
|
func (u *upstreamResolverBase) disable(err error) {
|
||||||
if u.disabled {
|
if u.disabled {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// todo test the deactivation logic, it seems to affect the client
|
// todo test the deactivation logic, it seems to affect the client
|
||||||
if runtime.GOOS != "ios" {
|
if runtime.GOOS != "ios" {
|
||||||
log.Warnf("upstream resolving is Disabled for %v", reactivatePeriod)
|
log.Warnf("Upstream resolving is Disabled for %v", reactivatePeriod)
|
||||||
u.deactivate()
|
u.deactivate(err)
|
||||||
u.disabled = true
|
u.disabled = true
|
||||||
go u.waitUntilResponse()
|
go u.waitUntilResponse()
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -11,6 +11,8 @@ import (
|
|||||||
"github.com/miekg/dns"
|
"github.com/miekg/dns"
|
||||||
log "github.com/sirupsen/logrus"
|
log "github.com/sirupsen/logrus"
|
||||||
"golang.org/x/sys/unix"
|
"golang.org/x/sys/unix"
|
||||||
|
|
||||||
|
"github.com/netbirdio/netbird/client/internal/peer"
|
||||||
)
|
)
|
||||||
|
|
||||||
type upstreamResolverIOS struct {
|
type upstreamResolverIOS struct {
|
||||||
@@ -20,8 +22,14 @@ type upstreamResolverIOS struct {
|
|||||||
iIndex int
|
iIndex int
|
||||||
}
|
}
|
||||||
|
|
||||||
func newUpstreamResolver(parentCTX context.Context, interfaceName string, ip net.IP, net *net.IPNet) (*upstreamResolverIOS, error) {
|
func newUpstreamResolver(
|
||||||
upstreamResolverBase := newUpstreamResolverBase(parentCTX)
|
ctx context.Context,
|
||||||
|
interfaceName string,
|
||||||
|
ip net.IP,
|
||||||
|
net *net.IPNet,
|
||||||
|
statusRecorder *peer.Status,
|
||||||
|
) (*upstreamResolverIOS, error) {
|
||||||
|
upstreamResolverBase := newUpstreamResolverBase(ctx, statusRecorder)
|
||||||
|
|
||||||
index, err := getInterfaceIndex(interfaceName)
|
index, err := getInterfaceIndex(interfaceName)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|||||||
@@ -8,14 +8,22 @@ import (
|
|||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/miekg/dns"
|
"github.com/miekg/dns"
|
||||||
|
|
||||||
|
"github.com/netbirdio/netbird/client/internal/peer"
|
||||||
)
|
)
|
||||||
|
|
||||||
type upstreamResolverNonIOS struct {
|
type upstreamResolverNonIOS struct {
|
||||||
*upstreamResolverBase
|
*upstreamResolverBase
|
||||||
}
|
}
|
||||||
|
|
||||||
func newUpstreamResolver(parentCTX context.Context, interfaceName string, ip net.IP, net *net.IPNet) (*upstreamResolverNonIOS, error) {
|
func newUpstreamResolver(
|
||||||
upstreamResolverBase := newUpstreamResolverBase(parentCTX)
|
ctx context.Context,
|
||||||
|
_ string,
|
||||||
|
_ net.IP,
|
||||||
|
_ *net.IPNet,
|
||||||
|
statusRecorder *peer.Status,
|
||||||
|
) (*upstreamResolverNonIOS, error) {
|
||||||
|
upstreamResolverBase := newUpstreamResolverBase(ctx, statusRecorder)
|
||||||
nonIOS := &upstreamResolverNonIOS{
|
nonIOS := &upstreamResolverNonIOS{
|
||||||
upstreamResolverBase: upstreamResolverBase,
|
upstreamResolverBase: upstreamResolverBase,
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -58,7 +58,7 @@ func TestUpstreamResolver_ServeDNS(t *testing.T) {
|
|||||||
for _, testCase := range testCases {
|
for _, testCase := range testCases {
|
||||||
t.Run(testCase.name, func(t *testing.T) {
|
t.Run(testCase.name, func(t *testing.T) {
|
||||||
ctx, cancel := context.WithCancel(context.TODO())
|
ctx, cancel := context.WithCancel(context.TODO())
|
||||||
resolver, _ := newUpstreamResolver(ctx, "", net.IP{}, &net.IPNet{})
|
resolver, _ := newUpstreamResolver(ctx, "", net.IP{}, &net.IPNet{}, nil)
|
||||||
resolver.upstreamServers = testCase.InputServers
|
resolver.upstreamServers = testCase.InputServers
|
||||||
resolver.upstreamTimeout = testCase.timeout
|
resolver.upstreamTimeout = testCase.timeout
|
||||||
if testCase.cancelCTX {
|
if testCase.cancelCTX {
|
||||||
@@ -131,7 +131,7 @@ func TestUpstreamResolver_DeactivationReactivation(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
failed := false
|
failed := false
|
||||||
resolver.deactivate = func() {
|
resolver.deactivate = func(error) {
|
||||||
failed = true
|
failed = true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1188,14 +1188,21 @@ func (e *Engine) newDnsServer() ([]*route.Route, dns.Server, error) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, nil, err
|
return nil, nil, err
|
||||||
}
|
}
|
||||||
dnsServer := dns.NewDefaultServerPermanentUpstream(e.ctx, e.wgInterface, e.mobileDep.HostDNSAddresses, *dnsConfig, e.mobileDep.NetworkChangeListener)
|
dnsServer := dns.NewDefaultServerPermanentUpstream(
|
||||||
|
e.ctx,
|
||||||
|
e.wgInterface,
|
||||||
|
e.mobileDep.HostDNSAddresses,
|
||||||
|
*dnsConfig,
|
||||||
|
e.mobileDep.NetworkChangeListener,
|
||||||
|
e.statusRecorder,
|
||||||
|
)
|
||||||
go e.mobileDep.DnsReadyListener.OnReady()
|
go e.mobileDep.DnsReadyListener.OnReady()
|
||||||
return routes, dnsServer, nil
|
return routes, dnsServer, nil
|
||||||
case "ios":
|
case "ios":
|
||||||
dnsServer := dns.NewDefaultServerIos(e.ctx, e.wgInterface, e.mobileDep.DnsManager)
|
dnsServer := dns.NewDefaultServerIos(e.ctx, e.wgInterface, e.mobileDep.DnsManager, e.statusRecorder)
|
||||||
return nil, dnsServer, nil
|
return nil, dnsServer, nil
|
||||||
default:
|
default:
|
||||||
dnsServer, err := dns.NewDefaultServer(e.ctx, e.wgInterface, e.config.CustomDNSAddress)
|
dnsServer, err := dns.NewDefaultServer(e.ctx, e.wgInterface, e.config.CustomDNSAddress, e.statusRecorder)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, nil, err
|
return nil, nil, err
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -38,7 +38,7 @@ func IsLoginRequired(ctx context.Context, privateKey string, mgmURL *url.URL, ss
|
|||||||
return false, err
|
return false, err
|
||||||
}
|
}
|
||||||
|
|
||||||
_, err = doMgmLogin(ctx, mgmClient, pubSSHKey)
|
_, err = doMgmLogin(ctx, mgmClient, pubSSHKey, &Config{})
|
||||||
if isLoginNeeded(err) {
|
if isLoginNeeded(err) {
|
||||||
return true, nil
|
return true, nil
|
||||||
}
|
}
|
||||||
@@ -67,7 +67,7 @@ func Login(ctx context.Context, config *Config, setupKey string, jwtToken string
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
serverKey, err := doMgmLogin(ctx, mgmClient, pubSSHKey)
|
serverKey, err := doMgmLogin(ctx, mgmClient, pubSSHKey, config)
|
||||||
if isRegistrationNeeded(err) {
|
if isRegistrationNeeded(err) {
|
||||||
log.Debugf("peer registration required")
|
log.Debugf("peer registration required")
|
||||||
_, err = registerPeer(ctx, *serverKey, mgmClient, setupKey, jwtToken, pubSSHKey)
|
_, err = registerPeer(ctx, *serverKey, mgmClient, setupKey, jwtToken, pubSSHKey)
|
||||||
@@ -99,14 +99,14 @@ func getMgmClient(ctx context.Context, privateKey string, mgmURL *url.URL) (*mgm
|
|||||||
return mgmClient, err
|
return mgmClient, err
|
||||||
}
|
}
|
||||||
|
|
||||||
func doMgmLogin(ctx context.Context, mgmClient *mgm.GrpcClient, pubSSHKey []byte) (*wgtypes.Key, error) {
|
func doMgmLogin(ctx context.Context, mgmClient *mgm.GrpcClient, pubSSHKey []byte, config *Config) (*wgtypes.Key, error) {
|
||||||
serverKey, err := mgmClient.GetServerPublicKey()
|
serverKey, err := mgmClient.GetServerPublicKey()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Errorf("failed while getting Management Service public key: %v", err)
|
log.Errorf("failed while getting Management Service public key: %v", err)
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
sysInfo := system.GetInfo(ctx)
|
sysInfo := system.GetInfo(ctx, *config)
|
||||||
_, err = mgmClient.Login(*serverKey, sysInfo, pubSSHKey)
|
_, err = mgmClient.Login(*serverKey, sysInfo, pubSSHKey)
|
||||||
return serverKey, err
|
return serverKey, err
|
||||||
}
|
}
|
||||||
@@ -120,7 +120,7 @@ func registerPeer(ctx context.Context, serverPublicKey wgtypes.Key, client *mgm.
|
|||||||
}
|
}
|
||||||
|
|
||||||
log.Debugf("sending peer registration request to Management Service")
|
log.Debugf("sending peer registration request to Management Service")
|
||||||
info := system.GetInfo(ctx)
|
info := system.GetInfo(ctx, Config{})
|
||||||
loginResp, err := client.Register(serverPublicKey, validSetupKey.String(), jwtToken, info, pubSSHKey)
|
loginResp, err := client.Register(serverPublicKey, validSetupKey.String(), jwtToken, info, pubSSHKey)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Errorf("failed registering peer %v,%s", err, validSetupKey.String())
|
log.Errorf("failed registering peer %v,%s", err, validSetupKey.String())
|
||||||
|
|||||||
@@ -5,6 +5,9 @@ import (
|
|||||||
"sync"
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"google.golang.org/grpc/codes"
|
||||||
|
gstatus "google.golang.org/grpc/status"
|
||||||
|
|
||||||
"github.com/netbirdio/netbird/client/internal/relay"
|
"github.com/netbirdio/netbird/client/internal/relay"
|
||||||
"github.com/netbirdio/netbird/iface"
|
"github.com/netbirdio/netbird/iface"
|
||||||
)
|
)
|
||||||
@@ -26,6 +29,7 @@ type State struct {
|
|||||||
BytesTx int64
|
BytesTx int64
|
||||||
BytesRx int64
|
BytesRx int64
|
||||||
RosenpassEnabled bool
|
RosenpassEnabled bool
|
||||||
|
Routes map[string]struct{}
|
||||||
}
|
}
|
||||||
|
|
||||||
// LocalPeerState contains the latest state of the local peer
|
// LocalPeerState contains the latest state of the local peer
|
||||||
@@ -34,6 +38,7 @@ type LocalPeerState struct {
|
|||||||
PubKey string
|
PubKey string
|
||||||
KernelInterface bool
|
KernelInterface bool
|
||||||
FQDN string
|
FQDN string
|
||||||
|
Routes map[string]struct{}
|
||||||
}
|
}
|
||||||
|
|
||||||
// SignalState contains the latest state of a signal connection
|
// SignalState contains the latest state of a signal connection
|
||||||
@@ -56,6 +61,16 @@ type RosenpassState struct {
|
|||||||
Permissive bool
|
Permissive bool
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// NSGroupState represents the status of a DNS server group, including associated domains,
|
||||||
|
// whether it's enabled, and the last error message encountered during probing.
|
||||||
|
type NSGroupState struct {
|
||||||
|
ID string
|
||||||
|
Servers []string
|
||||||
|
Domains []string
|
||||||
|
Enabled bool
|
||||||
|
Error error
|
||||||
|
}
|
||||||
|
|
||||||
// FullStatus contains the full state held by the Status instance
|
// FullStatus contains the full state held by the Status instance
|
||||||
type FullStatus struct {
|
type FullStatus struct {
|
||||||
Peers []State
|
Peers []State
|
||||||
@@ -64,6 +79,7 @@ type FullStatus struct {
|
|||||||
LocalPeerState LocalPeerState
|
LocalPeerState LocalPeerState
|
||||||
RosenpassState RosenpassState
|
RosenpassState RosenpassState
|
||||||
Relays []relay.ProbeResult
|
Relays []relay.ProbeResult
|
||||||
|
NSGroupStates []NSGroupState
|
||||||
}
|
}
|
||||||
|
|
||||||
// Status holds a state of peers, signal, management connections and relays
|
// Status holds a state of peers, signal, management connections and relays
|
||||||
@@ -83,6 +99,7 @@ type Status struct {
|
|||||||
notifier *notifier
|
notifier *notifier
|
||||||
rosenpassEnabled bool
|
rosenpassEnabled bool
|
||||||
rosenpassPermissive bool
|
rosenpassPermissive bool
|
||||||
|
nsGroupStates []NSGroupState
|
||||||
|
|
||||||
// To reduce the number of notification invocation this bool will be true when need to call the notification
|
// To reduce the number of notification invocation this bool will be true when need to call the notification
|
||||||
// Some Peer actions mostly used by in a batch when the network map has been synchronized. In these type of events
|
// Some Peer actions mostly used by in a batch when the network map has been synchronized. In these type of events
|
||||||
@@ -171,6 +188,10 @@ func (d *Status) UpdatePeerState(receivedState State) error {
|
|||||||
peerState.IP = receivedState.IP
|
peerState.IP = receivedState.IP
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if receivedState.Routes != nil {
|
||||||
|
peerState.Routes = receivedState.Routes
|
||||||
|
}
|
||||||
|
|
||||||
skipNotification := shouldSkipNotify(receivedState, peerState)
|
skipNotification := shouldSkipNotify(receivedState, peerState)
|
||||||
|
|
||||||
if receivedState.ConnStatus != peerState.ConnStatus {
|
if receivedState.ConnStatus != peerState.ConnStatus {
|
||||||
@@ -275,6 +296,13 @@ func (d *Status) GetPeerStateChangeNotifier(peer string) <-chan struct{} {
|
|||||||
return ch
|
return ch
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// GetLocalPeerState returns the local peer state
|
||||||
|
func (d *Status) GetLocalPeerState() LocalPeerState {
|
||||||
|
d.mux.Lock()
|
||||||
|
defer d.mux.Unlock()
|
||||||
|
return d.localPeer
|
||||||
|
}
|
||||||
|
|
||||||
// UpdateLocalPeerState updates local peer status
|
// UpdateLocalPeerState updates local peer status
|
||||||
func (d *Status) UpdateLocalPeerState(localPeerState LocalPeerState) {
|
func (d *Status) UpdateLocalPeerState(localPeerState LocalPeerState) {
|
||||||
d.mux.Lock()
|
d.mux.Lock()
|
||||||
@@ -361,6 +389,12 @@ func (d *Status) UpdateRelayStates(relayResults []relay.ProbeResult) {
|
|||||||
d.relayStates = relayResults
|
d.relayStates = relayResults
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (d *Status) UpdateDNSStates(dnsStates []NSGroupState) {
|
||||||
|
d.mux.Lock()
|
||||||
|
defer d.mux.Unlock()
|
||||||
|
d.nsGroupStates = dnsStates
|
||||||
|
}
|
||||||
|
|
||||||
func (d *Status) GetRosenpassState() RosenpassState {
|
func (d *Status) GetRosenpassState() RosenpassState {
|
||||||
return RosenpassState{
|
return RosenpassState{
|
||||||
d.rosenpassEnabled,
|
d.rosenpassEnabled,
|
||||||
@@ -376,6 +410,24 @@ func (d *Status) GetManagementState() ManagementState {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// IsLoginRequired determines if a peer's login has expired.
|
||||||
|
func (d *Status) IsLoginRequired() bool {
|
||||||
|
d.mux.Lock()
|
||||||
|
defer d.mux.Unlock()
|
||||||
|
|
||||||
|
// if peer is connected to the management then login is not expired
|
||||||
|
if d.managementState {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
s, ok := gstatus.FromError(d.managementError)
|
||||||
|
if ok && (s.Code() == codes.InvalidArgument || s.Code() == codes.PermissionDenied) {
|
||||||
|
return true
|
||||||
|
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
func (d *Status) GetSignalState() SignalState {
|
func (d *Status) GetSignalState() SignalState {
|
||||||
return SignalState{
|
return SignalState{
|
||||||
d.signalAddress,
|
d.signalAddress,
|
||||||
@@ -388,6 +440,10 @@ func (d *Status) GetRelayStates() []relay.ProbeResult {
|
|||||||
return d.relayStates
|
return d.relayStates
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (d *Status) GetDNSStates() []NSGroupState {
|
||||||
|
return d.nsGroupStates
|
||||||
|
}
|
||||||
|
|
||||||
// GetFullStatus gets full status
|
// GetFullStatus gets full status
|
||||||
func (d *Status) GetFullStatus() FullStatus {
|
func (d *Status) GetFullStatus() FullStatus {
|
||||||
d.mux.Lock()
|
d.mux.Lock()
|
||||||
@@ -399,6 +455,7 @@ func (d *Status) GetFullStatus() FullStatus {
|
|||||||
LocalPeerState: d.localPeer,
|
LocalPeerState: d.localPeer,
|
||||||
Relays: d.GetRelayStates(),
|
Relays: d.GetRelayStates(),
|
||||||
RosenpassState: d.GetRosenpassState(),
|
RosenpassState: d.GetRosenpassState(),
|
||||||
|
NSGroupStates: d.GetDNSStates(),
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, status := range d.peers {
|
for _, status := range d.peers {
|
||||||
|
|||||||
@@ -160,6 +160,12 @@ func (c *clientNetwork) removeRouteFromWireguardPeer(peerKey string) error {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
delete(state.Routes, c.network.String())
|
||||||
|
if err := c.statusRecorder.UpdatePeerState(state); err != nil {
|
||||||
|
log.Warnf("Failed to update peer state: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
if state.ConnStatus != peer.StatusConnected {
|
if state.ConnStatus != peer.StatusConnected {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
@@ -225,6 +231,20 @@ func (c *clientNetwork) recalculateRouteAndUpdatePeerAndSystem() error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
c.chosenRoute = c.routes[chosen]
|
c.chosenRoute = c.routes[chosen]
|
||||||
|
|
||||||
|
state, err := c.statusRecorder.GetPeer(c.chosenRoute.Peer)
|
||||||
|
if err != nil {
|
||||||
|
log.Errorf("Failed to get peer state: %v", err)
|
||||||
|
} else {
|
||||||
|
if state.Routes == nil {
|
||||||
|
state.Routes = map[string]struct{}{}
|
||||||
|
}
|
||||||
|
state.Routes[c.network.String()] = struct{}{}
|
||||||
|
if err := c.statusRecorder.UpdatePeerState(state); err != nil {
|
||||||
|
log.Warnf("Failed to update peer state: %v", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
err = c.wgInterface.AddAllowedIP(c.chosenRoute.Peer, c.network.String())
|
err = c.wgInterface.AddAllowedIP(c.chosenRoute.Peer, c.network.String())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Errorf("couldn't add allowed IP %s added for peer %s, err: %v",
|
log.Errorf("couldn't add allowed IP %s added for peer %s, err: %v",
|
||||||
|
|||||||
@@ -58,7 +58,7 @@ func NewManager(ctx context.Context, pubKey string, wgInterface *iface.WGIface,
|
|||||||
|
|
||||||
func (m *DefaultManager) EnableServerRouter(firewall firewall.Manager) error {
|
func (m *DefaultManager) EnableServerRouter(firewall firewall.Manager) error {
|
||||||
var err error
|
var err error
|
||||||
m.serverRouter, err = newServerRouter(m.ctx, m.wgInterface, firewall)
|
m.serverRouter, err = newServerRouter(m.ctx, m.wgInterface, firewall, m.statusRecorder)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,9 +7,10 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
|
|
||||||
firewall "github.com/netbirdio/netbird/client/firewall/manager"
|
firewall "github.com/netbirdio/netbird/client/firewall/manager"
|
||||||
|
"github.com/netbirdio/netbird/client/internal/peer"
|
||||||
"github.com/netbirdio/netbird/iface"
|
"github.com/netbirdio/netbird/iface"
|
||||||
)
|
)
|
||||||
|
|
||||||
func newServerRouter(context.Context, *iface.WGIface, firewall.Manager) (serverRouter, error) {
|
func newServerRouter(context.Context, *iface.WGIface, firewall.Manager, *peer.Status) (serverRouter, error) {
|
||||||
return nil, fmt.Errorf("server route not supported on this os")
|
return nil, fmt.Errorf("server route not supported on this os")
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,24 +10,27 @@ import (
|
|||||||
log "github.com/sirupsen/logrus"
|
log "github.com/sirupsen/logrus"
|
||||||
|
|
||||||
firewall "github.com/netbirdio/netbird/client/firewall/manager"
|
firewall "github.com/netbirdio/netbird/client/firewall/manager"
|
||||||
|
"github.com/netbirdio/netbird/client/internal/peer"
|
||||||
"github.com/netbirdio/netbird/iface"
|
"github.com/netbirdio/netbird/iface"
|
||||||
"github.com/netbirdio/netbird/route"
|
"github.com/netbirdio/netbird/route"
|
||||||
)
|
)
|
||||||
|
|
||||||
type defaultServerRouter struct {
|
type defaultServerRouter struct {
|
||||||
mux sync.Mutex
|
mux sync.Mutex
|
||||||
ctx context.Context
|
ctx context.Context
|
||||||
routes map[string]*route.Route
|
routes map[string]*route.Route
|
||||||
firewall firewall.Manager
|
firewall firewall.Manager
|
||||||
wgInterface *iface.WGIface
|
wgInterface *iface.WGIface
|
||||||
|
statusRecorder *peer.Status
|
||||||
}
|
}
|
||||||
|
|
||||||
func newServerRouter(ctx context.Context, wgInterface *iface.WGIface, firewall firewall.Manager) (serverRouter, error) {
|
func newServerRouter(ctx context.Context, wgInterface *iface.WGIface, firewall firewall.Manager, statusRecorder *peer.Status) (serverRouter, error) {
|
||||||
return &defaultServerRouter{
|
return &defaultServerRouter{
|
||||||
ctx: ctx,
|
ctx: ctx,
|
||||||
routes: make(map[string]*route.Route),
|
routes: make(map[string]*route.Route),
|
||||||
firewall: firewall,
|
firewall: firewall,
|
||||||
wgInterface: wgInterface,
|
wgInterface: wgInterface,
|
||||||
|
statusRecorder: statusRecorder,
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -88,6 +91,11 @@ func (m *defaultServerRouter) removeFromServerNetwork(route *route.Route) error
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
delete(m.routes, route.ID)
|
delete(m.routes, route.ID)
|
||||||
|
|
||||||
|
state := m.statusRecorder.GetLocalPeerState()
|
||||||
|
delete(state.Routes, route.Network.String())
|
||||||
|
m.statusRecorder.UpdateLocalPeerState(state)
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -105,6 +113,14 @@ func (m *defaultServerRouter) addToServerNetwork(route *route.Route) error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
m.routes[route.ID] = route
|
m.routes[route.ID] = route
|
||||||
|
|
||||||
|
state := m.statusRecorder.GetLocalPeerState()
|
||||||
|
if state.Routes == nil {
|
||||||
|
state.Routes = map[string]struct{}{}
|
||||||
|
}
|
||||||
|
state.Routes[route.Network.String()] = struct{}{}
|
||||||
|
m.statusRecorder.UpdateLocalPeerState(state)
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -117,6 +133,10 @@ func (m *defaultServerRouter) cleanUp() {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
log.Warnf("failed to remove clean up route: %s", r.ID)
|
log.Warnf("failed to remove clean up route: %s", r.ID)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
state := m.statusRecorder.GetLocalPeerState()
|
||||||
|
state.Routes = nil
|
||||||
|
m.statusRecorder.UpdateLocalPeerState(state)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
82
client/internal/session.go
Normal file
82
client/internal/session.go
Normal file
@@ -0,0 +1,82 @@
|
|||||||
|
package internal
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"os/exec"
|
||||||
|
"strings"
|
||||||
|
"sync"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/netbirdio/netbird/client/internal/peer"
|
||||||
|
)
|
||||||
|
|
||||||
|
type SessionWatcher struct {
|
||||||
|
ctx context.Context
|
||||||
|
mutex sync.Mutex
|
||||||
|
|
||||||
|
peerStatusRecorder *peer.Status
|
||||||
|
watchTicker *time.Ticker
|
||||||
|
|
||||||
|
sendNotification bool
|
||||||
|
onExpireListener func()
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewSessionWatcher creates a new instance of SessionWatcher.
|
||||||
|
func NewSessionWatcher(ctx context.Context, peerStatusRecorder *peer.Status) *SessionWatcher {
|
||||||
|
s := &SessionWatcher{
|
||||||
|
ctx: ctx,
|
||||||
|
peerStatusRecorder: peerStatusRecorder,
|
||||||
|
watchTicker: time.NewTicker(2 * time.Second),
|
||||||
|
}
|
||||||
|
go s.startWatcher()
|
||||||
|
return s
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetOnExpireListener sets the callback func to be called when the session expires.
|
||||||
|
func (s *SessionWatcher) SetOnExpireListener(onExpire func()) {
|
||||||
|
s.mutex.Lock()
|
||||||
|
defer s.mutex.Unlock()
|
||||||
|
s.onExpireListener = onExpire
|
||||||
|
}
|
||||||
|
|
||||||
|
// startWatcher continuously checks if the session requires login and
|
||||||
|
// calls the onExpireListener if login is required.
|
||||||
|
func (s *SessionWatcher) startWatcher() {
|
||||||
|
for {
|
||||||
|
select {
|
||||||
|
case <-s.ctx.Done():
|
||||||
|
s.watchTicker.Stop()
|
||||||
|
return
|
||||||
|
case <-s.watchTicker.C:
|
||||||
|
managementState := s.peerStatusRecorder.GetManagementState()
|
||||||
|
if managementState.Connected {
|
||||||
|
s.sendNotification = true
|
||||||
|
}
|
||||||
|
|
||||||
|
isLoginRequired := s.peerStatusRecorder.IsLoginRequired()
|
||||||
|
if isLoginRequired && s.sendNotification && s.onExpireListener != nil {
|
||||||
|
s.mutex.Lock()
|
||||||
|
s.onExpireListener()
|
||||||
|
s.sendNotification = false
|
||||||
|
s.mutex.Unlock()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// CheckUIApp checks whether UI application is running.
|
||||||
|
func CheckUIApp() bool {
|
||||||
|
cmd := exec.Command("ps", "-ef")
|
||||||
|
output, err := cmd.Output()
|
||||||
|
if err != nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
lines := strings.Split(string(output), "\n")
|
||||||
|
for _, line := range lines {
|
||||||
|
if strings.Contains(line, "netbird-ui") && !strings.Contains(line, "grep") {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
@@ -772,6 +772,7 @@ type PeerState struct {
|
|||||||
BytesRx int64 `protobuf:"varint,13,opt,name=bytesRx,proto3" json:"bytesRx,omitempty"`
|
BytesRx int64 `protobuf:"varint,13,opt,name=bytesRx,proto3" json:"bytesRx,omitempty"`
|
||||||
BytesTx int64 `protobuf:"varint,14,opt,name=bytesTx,proto3" json:"bytesTx,omitempty"`
|
BytesTx int64 `protobuf:"varint,14,opt,name=bytesTx,proto3" json:"bytesTx,omitempty"`
|
||||||
RosenpassEnabled bool `protobuf:"varint,15,opt,name=rosenpassEnabled,proto3" json:"rosenpassEnabled,omitempty"`
|
RosenpassEnabled bool `protobuf:"varint,15,opt,name=rosenpassEnabled,proto3" json:"rosenpassEnabled,omitempty"`
|
||||||
|
Routes []string `protobuf:"bytes,16,rep,name=routes,proto3" json:"routes,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func (x *PeerState) Reset() {
|
func (x *PeerState) Reset() {
|
||||||
@@ -911,18 +912,26 @@ func (x *PeerState) GetRosenpassEnabled() bool {
|
|||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (x *PeerState) GetRoutes() []string {
|
||||||
|
if x != nil {
|
||||||
|
return x.Routes
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
// LocalPeerState contains the latest state of the local peer
|
// LocalPeerState contains the latest state of the local peer
|
||||||
type LocalPeerState struct {
|
type LocalPeerState struct {
|
||||||
state protoimpl.MessageState
|
state protoimpl.MessageState
|
||||||
sizeCache protoimpl.SizeCache
|
sizeCache protoimpl.SizeCache
|
||||||
unknownFields protoimpl.UnknownFields
|
unknownFields protoimpl.UnknownFields
|
||||||
|
|
||||||
IP string `protobuf:"bytes,1,opt,name=IP,proto3" json:"IP,omitempty"`
|
IP string `protobuf:"bytes,1,opt,name=IP,proto3" json:"IP,omitempty"`
|
||||||
PubKey string `protobuf:"bytes,2,opt,name=pubKey,proto3" json:"pubKey,omitempty"`
|
PubKey string `protobuf:"bytes,2,opt,name=pubKey,proto3" json:"pubKey,omitempty"`
|
||||||
KernelInterface bool `protobuf:"varint,3,opt,name=kernelInterface,proto3" json:"kernelInterface,omitempty"`
|
KernelInterface bool `protobuf:"varint,3,opt,name=kernelInterface,proto3" json:"kernelInterface,omitempty"`
|
||||||
Fqdn string `protobuf:"bytes,4,opt,name=fqdn,proto3" json:"fqdn,omitempty"`
|
Fqdn string `protobuf:"bytes,4,opt,name=fqdn,proto3" json:"fqdn,omitempty"`
|
||||||
RosenpassEnabled bool `protobuf:"varint,5,opt,name=rosenpassEnabled,proto3" json:"rosenpassEnabled,omitempty"`
|
RosenpassEnabled bool `protobuf:"varint,5,opt,name=rosenpassEnabled,proto3" json:"rosenpassEnabled,omitempty"`
|
||||||
RosenpassPermissive bool `protobuf:"varint,6,opt,name=rosenpassPermissive,proto3" json:"rosenpassPermissive,omitempty"`
|
RosenpassPermissive bool `protobuf:"varint,6,opt,name=rosenpassPermissive,proto3" json:"rosenpassPermissive,omitempty"`
|
||||||
|
Routes []string `protobuf:"bytes,7,rep,name=routes,proto3" json:"routes,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func (x *LocalPeerState) Reset() {
|
func (x *LocalPeerState) Reset() {
|
||||||
@@ -999,6 +1008,13 @@ func (x *LocalPeerState) GetRosenpassPermissive() bool {
|
|||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (x *LocalPeerState) GetRoutes() []string {
|
||||||
|
if x != nil {
|
||||||
|
return x.Routes
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
// SignalState contains the latest state of a signal connection
|
// SignalState contains the latest state of a signal connection
|
||||||
type SignalState struct {
|
type SignalState struct {
|
||||||
state protoimpl.MessageState
|
state protoimpl.MessageState
|
||||||
@@ -1191,6 +1207,77 @@ func (x *RelayState) GetError() string {
|
|||||||
return ""
|
return ""
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type NSGroupState struct {
|
||||||
|
state protoimpl.MessageState
|
||||||
|
sizeCache protoimpl.SizeCache
|
||||||
|
unknownFields protoimpl.UnknownFields
|
||||||
|
|
||||||
|
Servers []string `protobuf:"bytes,1,rep,name=servers,proto3" json:"servers,omitempty"`
|
||||||
|
Domains []string `protobuf:"bytes,2,rep,name=domains,proto3" json:"domains,omitempty"`
|
||||||
|
Enabled bool `protobuf:"varint,3,opt,name=enabled,proto3" json:"enabled,omitempty"`
|
||||||
|
Error string `protobuf:"bytes,4,opt,name=error,proto3" json:"error,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *NSGroupState) Reset() {
|
||||||
|
*x = NSGroupState{}
|
||||||
|
if protoimpl.UnsafeEnabled {
|
||||||
|
mi := &file_daemon_proto_msgTypes[17]
|
||||||
|
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||||
|
ms.StoreMessageInfo(mi)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *NSGroupState) String() string {
|
||||||
|
return protoimpl.X.MessageStringOf(x)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (*NSGroupState) ProtoMessage() {}
|
||||||
|
|
||||||
|
func (x *NSGroupState) ProtoReflect() protoreflect.Message {
|
||||||
|
mi := &file_daemon_proto_msgTypes[17]
|
||||||
|
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 NSGroupState.ProtoReflect.Descriptor instead.
|
||||||
|
func (*NSGroupState) Descriptor() ([]byte, []int) {
|
||||||
|
return file_daemon_proto_rawDescGZIP(), []int{17}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *NSGroupState) GetServers() []string {
|
||||||
|
if x != nil {
|
||||||
|
return x.Servers
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *NSGroupState) GetDomains() []string {
|
||||||
|
if x != nil {
|
||||||
|
return x.Domains
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *NSGroupState) GetEnabled() bool {
|
||||||
|
if x != nil {
|
||||||
|
return x.Enabled
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *NSGroupState) GetError() string {
|
||||||
|
if x != nil {
|
||||||
|
return x.Error
|
||||||
|
}
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
// FullStatus contains the full state held by the Status instance
|
// FullStatus contains the full state held by the Status instance
|
||||||
type FullStatus struct {
|
type FullStatus struct {
|
||||||
state protoimpl.MessageState
|
state protoimpl.MessageState
|
||||||
@@ -1202,12 +1289,13 @@ type FullStatus struct {
|
|||||||
LocalPeerState *LocalPeerState `protobuf:"bytes,3,opt,name=localPeerState,proto3" json:"localPeerState,omitempty"`
|
LocalPeerState *LocalPeerState `protobuf:"bytes,3,opt,name=localPeerState,proto3" json:"localPeerState,omitempty"`
|
||||||
Peers []*PeerState `protobuf:"bytes,4,rep,name=peers,proto3" json:"peers,omitempty"`
|
Peers []*PeerState `protobuf:"bytes,4,rep,name=peers,proto3" json:"peers,omitempty"`
|
||||||
Relays []*RelayState `protobuf:"bytes,5,rep,name=relays,proto3" json:"relays,omitempty"`
|
Relays []*RelayState `protobuf:"bytes,5,rep,name=relays,proto3" json:"relays,omitempty"`
|
||||||
|
DnsServers []*NSGroupState `protobuf:"bytes,6,rep,name=dns_servers,json=dnsServers,proto3" json:"dns_servers,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func (x *FullStatus) Reset() {
|
func (x *FullStatus) Reset() {
|
||||||
*x = FullStatus{}
|
*x = FullStatus{}
|
||||||
if protoimpl.UnsafeEnabled {
|
if protoimpl.UnsafeEnabled {
|
||||||
mi := &file_daemon_proto_msgTypes[17]
|
mi := &file_daemon_proto_msgTypes[18]
|
||||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||||
ms.StoreMessageInfo(mi)
|
ms.StoreMessageInfo(mi)
|
||||||
}
|
}
|
||||||
@@ -1220,7 +1308,7 @@ func (x *FullStatus) String() string {
|
|||||||
func (*FullStatus) ProtoMessage() {}
|
func (*FullStatus) ProtoMessage() {}
|
||||||
|
|
||||||
func (x *FullStatus) ProtoReflect() protoreflect.Message {
|
func (x *FullStatus) ProtoReflect() protoreflect.Message {
|
||||||
mi := &file_daemon_proto_msgTypes[17]
|
mi := &file_daemon_proto_msgTypes[18]
|
||||||
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 {
|
||||||
@@ -1233,7 +1321,7 @@ func (x *FullStatus) ProtoReflect() protoreflect.Message {
|
|||||||
|
|
||||||
// Deprecated: Use FullStatus.ProtoReflect.Descriptor instead.
|
// Deprecated: Use FullStatus.ProtoReflect.Descriptor instead.
|
||||||
func (*FullStatus) Descriptor() ([]byte, []int) {
|
func (*FullStatus) Descriptor() ([]byte, []int) {
|
||||||
return file_daemon_proto_rawDescGZIP(), []int{17}
|
return file_daemon_proto_rawDescGZIP(), []int{18}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (x *FullStatus) GetManagementState() *ManagementState {
|
func (x *FullStatus) GetManagementState() *ManagementState {
|
||||||
@@ -1271,6 +1359,13 @@ func (x *FullStatus) GetRelays() []*RelayState {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (x *FullStatus) GetDnsServers() []*NSGroupState {
|
||||||
|
if x != nil {
|
||||||
|
return x.DnsServers
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
var File_daemon_proto protoreflect.FileDescriptor
|
var File_daemon_proto protoreflect.FileDescriptor
|
||||||
|
|
||||||
var file_daemon_proto_rawDesc = []byte{
|
var file_daemon_proto_rawDesc = []byte{
|
||||||
@@ -1380,7 +1475,7 @@ var file_daemon_proto_rawDesc = []byte{
|
|||||||
0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0c, 0x70, 0x72, 0x65, 0x53, 0x68, 0x61, 0x72, 0x65,
|
0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0c, 0x70, 0x72, 0x65, 0x53, 0x68, 0x61, 0x72, 0x65,
|
||||||
0x64, 0x4b, 0x65, 0x79, 0x12, 0x1a, 0x0a, 0x08, 0x61, 0x64, 0x6d, 0x69, 0x6e, 0x55, 0x52, 0x4c,
|
0x64, 0x4b, 0x65, 0x79, 0x12, 0x1a, 0x0a, 0x08, 0x61, 0x64, 0x6d, 0x69, 0x6e, 0x55, 0x52, 0x4c,
|
||||||
0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x61, 0x64, 0x6d, 0x69, 0x6e, 0x55, 0x52, 0x4c,
|
0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x61, 0x64, 0x6d, 0x69, 0x6e, 0x55, 0x52, 0x4c,
|
||||||
0x22, 0x81, 0x05, 0x0a, 0x09, 0x50, 0x65, 0x65, 0x72, 0x53, 0x74, 0x61, 0x74, 0x65, 0x12, 0x0e,
|
0x22, 0x99, 0x05, 0x0a, 0x09, 0x50, 0x65, 0x65, 0x72, 0x53, 0x74, 0x61, 0x74, 0x65, 0x12, 0x0e,
|
||||||
0x0a, 0x02, 0x49, 0x50, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x02, 0x49, 0x50, 0x12, 0x16,
|
0x0a, 0x02, 0x49, 0x50, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x02, 0x49, 0x50, 0x12, 0x16,
|
||||||
0x0a, 0x06, 0x70, 0x75, 0x62, 0x4b, 0x65, 0x79, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06,
|
0x0a, 0x06, 0x70, 0x75, 0x62, 0x4b, 0x65, 0x79, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06,
|
||||||
0x70, 0x75, 0x62, 0x4b, 0x65, 0x79, 0x12, 0x1e, 0x0a, 0x0a, 0x63, 0x6f, 0x6e, 0x6e, 0x53, 0x74,
|
0x70, 0x75, 0x62, 0x4b, 0x65, 0x79, 0x12, 0x1e, 0x0a, 0x0a, 0x63, 0x6f, 0x6e, 0x6e, 0x53, 0x74,
|
||||||
@@ -1420,20 +1515,23 @@ var file_daemon_proto_rawDesc = []byte{
|
|||||||
0x07, 0x62, 0x79, 0x74, 0x65, 0x73, 0x54, 0x78, 0x12, 0x2a, 0x0a, 0x10, 0x72, 0x6f, 0x73, 0x65,
|
0x07, 0x62, 0x79, 0x74, 0x65, 0x73, 0x54, 0x78, 0x12, 0x2a, 0x0a, 0x10, 0x72, 0x6f, 0x73, 0x65,
|
||||||
0x6e, 0x70, 0x61, 0x73, 0x73, 0x45, 0x6e, 0x61, 0x62, 0x6c, 0x65, 0x64, 0x18, 0x0f, 0x20, 0x01,
|
0x6e, 0x70, 0x61, 0x73, 0x73, 0x45, 0x6e, 0x61, 0x62, 0x6c, 0x65, 0x64, 0x18, 0x0f, 0x20, 0x01,
|
||||||
0x28, 0x08, 0x52, 0x10, 0x72, 0x6f, 0x73, 0x65, 0x6e, 0x70, 0x61, 0x73, 0x73, 0x45, 0x6e, 0x61,
|
0x28, 0x08, 0x52, 0x10, 0x72, 0x6f, 0x73, 0x65, 0x6e, 0x70, 0x61, 0x73, 0x73, 0x45, 0x6e, 0x61,
|
||||||
0x62, 0x6c, 0x65, 0x64, 0x22, 0xd4, 0x01, 0x0a, 0x0e, 0x4c, 0x6f, 0x63, 0x61, 0x6c, 0x50, 0x65,
|
0x62, 0x6c, 0x65, 0x64, 0x12, 0x16, 0x0a, 0x06, 0x72, 0x6f, 0x75, 0x74, 0x65, 0x73, 0x18, 0x10,
|
||||||
0x65, 0x72, 0x53, 0x74, 0x61, 0x74, 0x65, 0x12, 0x0e, 0x0a, 0x02, 0x49, 0x50, 0x18, 0x01, 0x20,
|
0x20, 0x03, 0x28, 0x09, 0x52, 0x06, 0x72, 0x6f, 0x75, 0x74, 0x65, 0x73, 0x22, 0xec, 0x01, 0x0a,
|
||||||
0x01, 0x28, 0x09, 0x52, 0x02, 0x49, 0x50, 0x12, 0x16, 0x0a, 0x06, 0x70, 0x75, 0x62, 0x4b, 0x65,
|
0x0e, 0x4c, 0x6f, 0x63, 0x61, 0x6c, 0x50, 0x65, 0x65, 0x72, 0x53, 0x74, 0x61, 0x74, 0x65, 0x12,
|
||||||
0x79, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x70, 0x75, 0x62, 0x4b, 0x65, 0x79, 0x12,
|
0x0e, 0x0a, 0x02, 0x49, 0x50, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x02, 0x49, 0x50, 0x12,
|
||||||
0x28, 0x0a, 0x0f, 0x6b, 0x65, 0x72, 0x6e, 0x65, 0x6c, 0x49, 0x6e, 0x74, 0x65, 0x72, 0x66, 0x61,
|
0x16, 0x0a, 0x06, 0x70, 0x75, 0x62, 0x4b, 0x65, 0x79, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52,
|
||||||
0x63, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0f, 0x6b, 0x65, 0x72, 0x6e, 0x65, 0x6c,
|
0x06, 0x70, 0x75, 0x62, 0x4b, 0x65, 0x79, 0x12, 0x28, 0x0a, 0x0f, 0x6b, 0x65, 0x72, 0x6e, 0x65,
|
||||||
0x49, 0x6e, 0x74, 0x65, 0x72, 0x66, 0x61, 0x63, 0x65, 0x12, 0x12, 0x0a, 0x04, 0x66, 0x71, 0x64,
|
0x6c, 0x49, 0x6e, 0x74, 0x65, 0x72, 0x66, 0x61, 0x63, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x08,
|
||||||
0x6e, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x66, 0x71, 0x64, 0x6e, 0x12, 0x2a, 0x0a,
|
0x52, 0x0f, 0x6b, 0x65, 0x72, 0x6e, 0x65, 0x6c, 0x49, 0x6e, 0x74, 0x65, 0x72, 0x66, 0x61, 0x63,
|
||||||
|
0x65, 0x12, 0x12, 0x0a, 0x04, 0x66, 0x71, 0x64, 0x6e, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52,
|
||||||
|
0x04, 0x66, 0x71, 0x64, 0x6e, 0x12, 0x2a, 0x0a, 0x10, 0x72, 0x6f, 0x73, 0x65, 0x6e, 0x70, 0x61,
|
||||||
|
0x73, 0x73, 0x45, 0x6e, 0x61, 0x62, 0x6c, 0x65, 0x64, 0x18, 0x05, 0x20, 0x01, 0x28, 0x08, 0x52,
|
||||||
0x10, 0x72, 0x6f, 0x73, 0x65, 0x6e, 0x70, 0x61, 0x73, 0x73, 0x45, 0x6e, 0x61, 0x62, 0x6c, 0x65,
|
0x10, 0x72, 0x6f, 0x73, 0x65, 0x6e, 0x70, 0x61, 0x73, 0x73, 0x45, 0x6e, 0x61, 0x62, 0x6c, 0x65,
|
||||||
0x64, 0x18, 0x05, 0x20, 0x01, 0x28, 0x08, 0x52, 0x10, 0x72, 0x6f, 0x73, 0x65, 0x6e, 0x70, 0x61,
|
0x64, 0x12, 0x30, 0x0a, 0x13, 0x72, 0x6f, 0x73, 0x65, 0x6e, 0x70, 0x61, 0x73, 0x73, 0x50, 0x65,
|
||||||
0x73, 0x73, 0x45, 0x6e, 0x61, 0x62, 0x6c, 0x65, 0x64, 0x12, 0x30, 0x0a, 0x13, 0x72, 0x6f, 0x73,
|
0x72, 0x6d, 0x69, 0x73, 0x73, 0x69, 0x76, 0x65, 0x18, 0x06, 0x20, 0x01, 0x28, 0x08, 0x52, 0x13,
|
||||||
0x65, 0x6e, 0x70, 0x61, 0x73, 0x73, 0x50, 0x65, 0x72, 0x6d, 0x69, 0x73, 0x73, 0x69, 0x76, 0x65,
|
0x72, 0x6f, 0x73, 0x65, 0x6e, 0x70, 0x61, 0x73, 0x73, 0x50, 0x65, 0x72, 0x6d, 0x69, 0x73, 0x73,
|
||||||
0x18, 0x06, 0x20, 0x01, 0x28, 0x08, 0x52, 0x13, 0x72, 0x6f, 0x73, 0x65, 0x6e, 0x70, 0x61, 0x73,
|
0x69, 0x76, 0x65, 0x12, 0x16, 0x0a, 0x06, 0x72, 0x6f, 0x75, 0x74, 0x65, 0x73, 0x18, 0x07, 0x20,
|
||||||
0x73, 0x50, 0x65, 0x72, 0x6d, 0x69, 0x73, 0x73, 0x69, 0x76, 0x65, 0x22, 0x53, 0x0a, 0x0b, 0x53,
|
0x03, 0x28, 0x09, 0x52, 0x06, 0x72, 0x6f, 0x75, 0x74, 0x65, 0x73, 0x22, 0x53, 0x0a, 0x0b, 0x53,
|
||||||
0x69, 0x67, 0x6e, 0x61, 0x6c, 0x53, 0x74, 0x61, 0x74, 0x65, 0x12, 0x10, 0x0a, 0x03, 0x55, 0x52,
|
0x69, 0x67, 0x6e, 0x61, 0x6c, 0x53, 0x74, 0x61, 0x74, 0x65, 0x12, 0x10, 0x0a, 0x03, 0x55, 0x52,
|
||||||
0x4c, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x55, 0x52, 0x4c, 0x12, 0x1c, 0x0a, 0x09,
|
0x4c, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x55, 0x52, 0x4c, 0x12, 0x1c, 0x0a, 0x09,
|
||||||
0x63, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x65, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x08, 0x52,
|
0x63, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x65, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x08, 0x52,
|
||||||
@@ -1449,50 +1547,61 @@ var file_daemon_proto_rawDesc = []byte{
|
|||||||
0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x55, 0x52, 0x49, 0x12, 0x1c, 0x0a, 0x09, 0x61, 0x76, 0x61,
|
0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x55, 0x52, 0x49, 0x12, 0x1c, 0x0a, 0x09, 0x61, 0x76, 0x61,
|
||||||
0x69, 0x6c, 0x61, 0x62, 0x6c, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x08, 0x52, 0x09, 0x61, 0x76,
|
0x69, 0x6c, 0x61, 0x62, 0x6c, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x08, 0x52, 0x09, 0x61, 0x76,
|
||||||
0x61, 0x69, 0x6c, 0x61, 0x62, 0x6c, 0x65, 0x12, 0x14, 0x0a, 0x05, 0x65, 0x72, 0x72, 0x6f, 0x72,
|
0x61, 0x69, 0x6c, 0x61, 0x62, 0x6c, 0x65, 0x12, 0x14, 0x0a, 0x05, 0x65, 0x72, 0x72, 0x6f, 0x72,
|
||||||
0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x22, 0x9b, 0x02,
|
0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x22, 0x72, 0x0a,
|
||||||
0x0a, 0x0a, 0x46, 0x75, 0x6c, 0x6c, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, 0x41, 0x0a, 0x0f,
|
0x0c, 0x4e, 0x53, 0x47, 0x72, 0x6f, 0x75, 0x70, 0x53, 0x74, 0x61, 0x74, 0x65, 0x12, 0x18, 0x0a,
|
||||||
0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x53, 0x74, 0x61, 0x74, 0x65, 0x18,
|
0x07, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x09, 0x52, 0x07,
|
||||||
0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x17, 0x2e, 0x64, 0x61, 0x65, 0x6d, 0x6f, 0x6e, 0x2e, 0x4d,
|
0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x73, 0x12, 0x18, 0x0a, 0x07, 0x64, 0x6f, 0x6d, 0x61, 0x69,
|
||||||
0x61, 0x6e, 0x61, 0x67, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x53, 0x74, 0x61, 0x74, 0x65, 0x52, 0x0f,
|
0x6e, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x09, 0x52, 0x07, 0x64, 0x6f, 0x6d, 0x61, 0x69, 0x6e,
|
||||||
0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x53, 0x74, 0x61, 0x74, 0x65, 0x12,
|
0x73, 0x12, 0x18, 0x0a, 0x07, 0x65, 0x6e, 0x61, 0x62, 0x6c, 0x65, 0x64, 0x18, 0x03, 0x20, 0x01,
|
||||||
0x35, 0x0a, 0x0b, 0x73, 0x69, 0x67, 0x6e, 0x61, 0x6c, 0x53, 0x74, 0x61, 0x74, 0x65, 0x18, 0x02,
|
0x28, 0x08, 0x52, 0x07, 0x65, 0x6e, 0x61, 0x62, 0x6c, 0x65, 0x64, 0x12, 0x14, 0x0a, 0x05, 0x65,
|
||||||
0x20, 0x01, 0x28, 0x0b, 0x32, 0x13, 0x2e, 0x64, 0x61, 0x65, 0x6d, 0x6f, 0x6e, 0x2e, 0x53, 0x69,
|
0x72, 0x72, 0x6f, 0x72, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x65, 0x72, 0x72, 0x6f,
|
||||||
0x67, 0x6e, 0x61, 0x6c, 0x53, 0x74, 0x61, 0x74, 0x65, 0x52, 0x0b, 0x73, 0x69, 0x67, 0x6e, 0x61,
|
0x72, 0x22, 0xd2, 0x02, 0x0a, 0x0a, 0x46, 0x75, 0x6c, 0x6c, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73,
|
||||||
0x6c, 0x53, 0x74, 0x61, 0x74, 0x65, 0x12, 0x3e, 0x0a, 0x0e, 0x6c, 0x6f, 0x63, 0x61, 0x6c, 0x50,
|
0x12, 0x41, 0x0a, 0x0f, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x53, 0x74,
|
||||||
0x65, 0x65, 0x72, 0x53, 0x74, 0x61, 0x74, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x16,
|
0x61, 0x74, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x17, 0x2e, 0x64, 0x61, 0x65, 0x6d,
|
||||||
0x2e, 0x64, 0x61, 0x65, 0x6d, 0x6f, 0x6e, 0x2e, 0x4c, 0x6f, 0x63, 0x61, 0x6c, 0x50, 0x65, 0x65,
|
0x6f, 0x6e, 0x2e, 0x4d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x53, 0x74, 0x61,
|
||||||
0x72, 0x53, 0x74, 0x61, 0x74, 0x65, 0x52, 0x0e, 0x6c, 0x6f, 0x63, 0x61, 0x6c, 0x50, 0x65, 0x65,
|
0x74, 0x65, 0x52, 0x0f, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x53, 0x74,
|
||||||
0x72, 0x53, 0x74, 0x61, 0x74, 0x65, 0x12, 0x27, 0x0a, 0x05, 0x70, 0x65, 0x65, 0x72, 0x73, 0x18,
|
0x61, 0x74, 0x65, 0x12, 0x35, 0x0a, 0x0b, 0x73, 0x69, 0x67, 0x6e, 0x61, 0x6c, 0x53, 0x74, 0x61,
|
||||||
0x04, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x11, 0x2e, 0x64, 0x61, 0x65, 0x6d, 0x6f, 0x6e, 0x2e, 0x50,
|
0x74, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x13, 0x2e, 0x64, 0x61, 0x65, 0x6d, 0x6f,
|
||||||
0x65, 0x65, 0x72, 0x53, 0x74, 0x61, 0x74, 0x65, 0x52, 0x05, 0x70, 0x65, 0x65, 0x72, 0x73, 0x12,
|
0x6e, 0x2e, 0x53, 0x69, 0x67, 0x6e, 0x61, 0x6c, 0x53, 0x74, 0x61, 0x74, 0x65, 0x52, 0x0b, 0x73,
|
||||||
0x2a, 0x0a, 0x06, 0x72, 0x65, 0x6c, 0x61, 0x79, 0x73, 0x18, 0x05, 0x20, 0x03, 0x28, 0x0b, 0x32,
|
0x69, 0x67, 0x6e, 0x61, 0x6c, 0x53, 0x74, 0x61, 0x74, 0x65, 0x12, 0x3e, 0x0a, 0x0e, 0x6c, 0x6f,
|
||||||
0x12, 0x2e, 0x64, 0x61, 0x65, 0x6d, 0x6f, 0x6e, 0x2e, 0x52, 0x65, 0x6c, 0x61, 0x79, 0x53, 0x74,
|
0x63, 0x61, 0x6c, 0x50, 0x65, 0x65, 0x72, 0x53, 0x74, 0x61, 0x74, 0x65, 0x18, 0x03, 0x20, 0x01,
|
||||||
0x61, 0x74, 0x65, 0x52, 0x06, 0x72, 0x65, 0x6c, 0x61, 0x79, 0x73, 0x32, 0xf7, 0x02, 0x0a, 0x0d,
|
0x28, 0x0b, 0x32, 0x16, 0x2e, 0x64, 0x61, 0x65, 0x6d, 0x6f, 0x6e, 0x2e, 0x4c, 0x6f, 0x63, 0x61,
|
||||||
0x44, 0x61, 0x65, 0x6d, 0x6f, 0x6e, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x12, 0x36, 0x0a,
|
0x6c, 0x50, 0x65, 0x65, 0x72, 0x53, 0x74, 0x61, 0x74, 0x65, 0x52, 0x0e, 0x6c, 0x6f, 0x63, 0x61,
|
||||||
0x05, 0x4c, 0x6f, 0x67, 0x69, 0x6e, 0x12, 0x14, 0x2e, 0x64, 0x61, 0x65, 0x6d, 0x6f, 0x6e, 0x2e,
|
0x6c, 0x50, 0x65, 0x65, 0x72, 0x53, 0x74, 0x61, 0x74, 0x65, 0x12, 0x27, 0x0a, 0x05, 0x70, 0x65,
|
||||||
0x4c, 0x6f, 0x67, 0x69, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x15, 0x2e, 0x64,
|
0x65, 0x72, 0x73, 0x18, 0x04, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x11, 0x2e, 0x64, 0x61, 0x65, 0x6d,
|
||||||
0x61, 0x65, 0x6d, 0x6f, 0x6e, 0x2e, 0x4c, 0x6f, 0x67, 0x69, 0x6e, 0x52, 0x65, 0x73, 0x70, 0x6f,
|
0x6f, 0x6e, 0x2e, 0x50, 0x65, 0x65, 0x72, 0x53, 0x74, 0x61, 0x74, 0x65, 0x52, 0x05, 0x70, 0x65,
|
||||||
0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, 0x4b, 0x0a, 0x0c, 0x57, 0x61, 0x69, 0x74, 0x53, 0x53, 0x4f,
|
0x65, 0x72, 0x73, 0x12, 0x2a, 0x0a, 0x06, 0x72, 0x65, 0x6c, 0x61, 0x79, 0x73, 0x18, 0x05, 0x20,
|
||||||
0x4c, 0x6f, 0x67, 0x69, 0x6e, 0x12, 0x1b, 0x2e, 0x64, 0x61, 0x65, 0x6d, 0x6f, 0x6e, 0x2e, 0x57,
|
0x03, 0x28, 0x0b, 0x32, 0x12, 0x2e, 0x64, 0x61, 0x65, 0x6d, 0x6f, 0x6e, 0x2e, 0x52, 0x65, 0x6c,
|
||||||
0x61, 0x69, 0x74, 0x53, 0x53, 0x4f, 0x4c, 0x6f, 0x67, 0x69, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65,
|
0x61, 0x79, 0x53, 0x74, 0x61, 0x74, 0x65, 0x52, 0x06, 0x72, 0x65, 0x6c, 0x61, 0x79, 0x73, 0x12,
|
||||||
0x73, 0x74, 0x1a, 0x1c, 0x2e, 0x64, 0x61, 0x65, 0x6d, 0x6f, 0x6e, 0x2e, 0x57, 0x61, 0x69, 0x74,
|
0x35, 0x0a, 0x0b, 0x64, 0x6e, 0x73, 0x5f, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x73, 0x18, 0x06,
|
||||||
0x53, 0x53, 0x4f, 0x4c, 0x6f, 0x67, 0x69, 0x6e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65,
|
0x20, 0x03, 0x28, 0x0b, 0x32, 0x14, 0x2e, 0x64, 0x61, 0x65, 0x6d, 0x6f, 0x6e, 0x2e, 0x4e, 0x53,
|
||||||
0x22, 0x00, 0x12, 0x2d, 0x0a, 0x02, 0x55, 0x70, 0x12, 0x11, 0x2e, 0x64, 0x61, 0x65, 0x6d, 0x6f,
|
0x47, 0x72, 0x6f, 0x75, 0x70, 0x53, 0x74, 0x61, 0x74, 0x65, 0x52, 0x0a, 0x64, 0x6e, 0x73, 0x53,
|
||||||
0x6e, 0x2e, 0x55, 0x70, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x12, 0x2e, 0x64, 0x61,
|
0x65, 0x72, 0x76, 0x65, 0x72, 0x73, 0x32, 0xf7, 0x02, 0x0a, 0x0d, 0x44, 0x61, 0x65, 0x6d, 0x6f,
|
||||||
0x65, 0x6d, 0x6f, 0x6e, 0x2e, 0x55, 0x70, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22,
|
0x6e, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x12, 0x36, 0x0a, 0x05, 0x4c, 0x6f, 0x67, 0x69,
|
||||||
0x00, 0x12, 0x39, 0x0a, 0x06, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, 0x15, 0x2e, 0x64, 0x61,
|
0x6e, 0x12, 0x14, 0x2e, 0x64, 0x61, 0x65, 0x6d, 0x6f, 0x6e, 0x2e, 0x4c, 0x6f, 0x67, 0x69, 0x6e,
|
||||||
0x65, 0x6d, 0x6f, 0x6e, 0x2e, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65,
|
0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x15, 0x2e, 0x64, 0x61, 0x65, 0x6d, 0x6f, 0x6e,
|
||||||
0x73, 0x74, 0x1a, 0x16, 0x2e, 0x64, 0x61, 0x65, 0x6d, 0x6f, 0x6e, 0x2e, 0x53, 0x74, 0x61, 0x74,
|
0x2e, 0x4c, 0x6f, 0x67, 0x69, 0x6e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00,
|
||||||
0x75, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, 0x33, 0x0a, 0x04,
|
0x12, 0x4b, 0x0a, 0x0c, 0x57, 0x61, 0x69, 0x74, 0x53, 0x53, 0x4f, 0x4c, 0x6f, 0x67, 0x69, 0x6e,
|
||||||
0x44, 0x6f, 0x77, 0x6e, 0x12, 0x13, 0x2e, 0x64, 0x61, 0x65, 0x6d, 0x6f, 0x6e, 0x2e, 0x44, 0x6f,
|
0x12, 0x1b, 0x2e, 0x64, 0x61, 0x65, 0x6d, 0x6f, 0x6e, 0x2e, 0x57, 0x61, 0x69, 0x74, 0x53, 0x53,
|
||||||
0x77, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x14, 0x2e, 0x64, 0x61, 0x65, 0x6d,
|
0x4f, 0x4c, 0x6f, 0x67, 0x69, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1c, 0x2e,
|
||||||
0x6f, 0x6e, 0x2e, 0x44, 0x6f, 0x77, 0x6e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22,
|
0x64, 0x61, 0x65, 0x6d, 0x6f, 0x6e, 0x2e, 0x57, 0x61, 0x69, 0x74, 0x53, 0x53, 0x4f, 0x4c, 0x6f,
|
||||||
0x00, 0x12, 0x42, 0x0a, 0x09, 0x47, 0x65, 0x74, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x18,
|
0x67, 0x69, 0x6e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, 0x2d, 0x0a,
|
||||||
0x2e, 0x64, 0x61, 0x65, 0x6d, 0x6f, 0x6e, 0x2e, 0x47, 0x65, 0x74, 0x43, 0x6f, 0x6e, 0x66, 0x69,
|
0x02, 0x55, 0x70, 0x12, 0x11, 0x2e, 0x64, 0x61, 0x65, 0x6d, 0x6f, 0x6e, 0x2e, 0x55, 0x70, 0x52,
|
||||||
0x67, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x19, 0x2e, 0x64, 0x61, 0x65, 0x6d, 0x6f,
|
0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x12, 0x2e, 0x64, 0x61, 0x65, 0x6d, 0x6f, 0x6e, 0x2e,
|
||||||
0x6e, 0x2e, 0x47, 0x65, 0x74, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x52, 0x65, 0x73, 0x70, 0x6f,
|
0x55, 0x70, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, 0x39, 0x0a, 0x06,
|
||||||
0x6e, 0x73, 0x65, 0x22, 0x00, 0x42, 0x08, 0x5a, 0x06, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62,
|
0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, 0x15, 0x2e, 0x64, 0x61, 0x65, 0x6d, 0x6f, 0x6e, 0x2e,
|
||||||
0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33,
|
0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x16, 0x2e,
|
||||||
|
0x64, 0x61, 0x65, 0x6d, 0x6f, 0x6e, 0x2e, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x52, 0x65, 0x73,
|
||||||
|
0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, 0x33, 0x0a, 0x04, 0x44, 0x6f, 0x77, 0x6e, 0x12,
|
||||||
|
0x13, 0x2e, 0x64, 0x61, 0x65, 0x6d, 0x6f, 0x6e, 0x2e, 0x44, 0x6f, 0x77, 0x6e, 0x52, 0x65, 0x71,
|
||||||
|
0x75, 0x65, 0x73, 0x74, 0x1a, 0x14, 0x2e, 0x64, 0x61, 0x65, 0x6d, 0x6f, 0x6e, 0x2e, 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,
|
||||||
|
0x42, 0x08, 0x5a, 0x06, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74,
|
||||||
|
0x6f, 0x33,
|
||||||
}
|
}
|
||||||
|
|
||||||
var (
|
var (
|
||||||
@@ -1507,7 +1616,7 @@ func file_daemon_proto_rawDescGZIP() []byte {
|
|||||||
return file_daemon_proto_rawDescData
|
return file_daemon_proto_rawDescData
|
||||||
}
|
}
|
||||||
|
|
||||||
var file_daemon_proto_msgTypes = make([]protoimpl.MessageInfo, 18)
|
var file_daemon_proto_msgTypes = make([]protoimpl.MessageInfo, 19)
|
||||||
var file_daemon_proto_goTypes = []interface{}{
|
var file_daemon_proto_goTypes = []interface{}{
|
||||||
(*LoginRequest)(nil), // 0: daemon.LoginRequest
|
(*LoginRequest)(nil), // 0: daemon.LoginRequest
|
||||||
(*LoginResponse)(nil), // 1: daemon.LoginResponse
|
(*LoginResponse)(nil), // 1: daemon.LoginResponse
|
||||||
@@ -1526,35 +1635,37 @@ var file_daemon_proto_goTypes = []interface{}{
|
|||||||
(*SignalState)(nil), // 14: daemon.SignalState
|
(*SignalState)(nil), // 14: daemon.SignalState
|
||||||
(*ManagementState)(nil), // 15: daemon.ManagementState
|
(*ManagementState)(nil), // 15: daemon.ManagementState
|
||||||
(*RelayState)(nil), // 16: daemon.RelayState
|
(*RelayState)(nil), // 16: daemon.RelayState
|
||||||
(*FullStatus)(nil), // 17: daemon.FullStatus
|
(*NSGroupState)(nil), // 17: daemon.NSGroupState
|
||||||
(*timestamp.Timestamp)(nil), // 18: google.protobuf.Timestamp
|
(*FullStatus)(nil), // 18: daemon.FullStatus
|
||||||
|
(*timestamp.Timestamp)(nil), // 19: google.protobuf.Timestamp
|
||||||
}
|
}
|
||||||
var file_daemon_proto_depIdxs = []int32{
|
var file_daemon_proto_depIdxs = []int32{
|
||||||
17, // 0: daemon.StatusResponse.fullStatus:type_name -> daemon.FullStatus
|
18, // 0: daemon.StatusResponse.fullStatus:type_name -> daemon.FullStatus
|
||||||
18, // 1: daemon.PeerState.connStatusUpdate:type_name -> google.protobuf.Timestamp
|
19, // 1: daemon.PeerState.connStatusUpdate:type_name -> google.protobuf.Timestamp
|
||||||
18, // 2: daemon.PeerState.lastWireguardHandshake:type_name -> google.protobuf.Timestamp
|
19, // 2: daemon.PeerState.lastWireguardHandshake:type_name -> google.protobuf.Timestamp
|
||||||
15, // 3: daemon.FullStatus.managementState:type_name -> daemon.ManagementState
|
15, // 3: daemon.FullStatus.managementState:type_name -> daemon.ManagementState
|
||||||
14, // 4: daemon.FullStatus.signalState:type_name -> daemon.SignalState
|
14, // 4: daemon.FullStatus.signalState:type_name -> daemon.SignalState
|
||||||
13, // 5: daemon.FullStatus.localPeerState:type_name -> daemon.LocalPeerState
|
13, // 5: daemon.FullStatus.localPeerState:type_name -> daemon.LocalPeerState
|
||||||
12, // 6: daemon.FullStatus.peers:type_name -> daemon.PeerState
|
12, // 6: daemon.FullStatus.peers:type_name -> daemon.PeerState
|
||||||
16, // 7: daemon.FullStatus.relays:type_name -> daemon.RelayState
|
16, // 7: daemon.FullStatus.relays:type_name -> daemon.RelayState
|
||||||
0, // 8: daemon.DaemonService.Login:input_type -> daemon.LoginRequest
|
17, // 8: daemon.FullStatus.dns_servers:type_name -> daemon.NSGroupState
|
||||||
2, // 9: daemon.DaemonService.WaitSSOLogin:input_type -> daemon.WaitSSOLoginRequest
|
0, // 9: daemon.DaemonService.Login:input_type -> daemon.LoginRequest
|
||||||
4, // 10: daemon.DaemonService.Up:input_type -> daemon.UpRequest
|
2, // 10: daemon.DaemonService.WaitSSOLogin:input_type -> daemon.WaitSSOLoginRequest
|
||||||
6, // 11: daemon.DaemonService.Status:input_type -> daemon.StatusRequest
|
4, // 11: daemon.DaemonService.Up:input_type -> daemon.UpRequest
|
||||||
8, // 12: daemon.DaemonService.Down:input_type -> daemon.DownRequest
|
6, // 12: daemon.DaemonService.Status:input_type -> daemon.StatusRequest
|
||||||
10, // 13: daemon.DaemonService.GetConfig:input_type -> daemon.GetConfigRequest
|
8, // 13: daemon.DaemonService.Down:input_type -> daemon.DownRequest
|
||||||
1, // 14: daemon.DaemonService.Login:output_type -> daemon.LoginResponse
|
10, // 14: daemon.DaemonService.GetConfig:input_type -> daemon.GetConfigRequest
|
||||||
3, // 15: daemon.DaemonService.WaitSSOLogin:output_type -> daemon.WaitSSOLoginResponse
|
1, // 15: daemon.DaemonService.Login:output_type -> daemon.LoginResponse
|
||||||
5, // 16: daemon.DaemonService.Up:output_type -> daemon.UpResponse
|
3, // 16: daemon.DaemonService.WaitSSOLogin:output_type -> daemon.WaitSSOLoginResponse
|
||||||
7, // 17: daemon.DaemonService.Status:output_type -> daemon.StatusResponse
|
5, // 17: daemon.DaemonService.Up:output_type -> daemon.UpResponse
|
||||||
9, // 18: daemon.DaemonService.Down:output_type -> daemon.DownResponse
|
7, // 18: daemon.DaemonService.Status:output_type -> daemon.StatusResponse
|
||||||
11, // 19: daemon.DaemonService.GetConfig:output_type -> daemon.GetConfigResponse
|
9, // 19: daemon.DaemonService.Down:output_type -> daemon.DownResponse
|
||||||
14, // [14:20] is the sub-list for method output_type
|
11, // 20: daemon.DaemonService.GetConfig:output_type -> daemon.GetConfigResponse
|
||||||
8, // [8:14] is the sub-list for method input_type
|
15, // [15:21] is the sub-list for method output_type
|
||||||
8, // [8:8] is the sub-list for extension type_name
|
9, // [9:15] is the sub-list for method input_type
|
||||||
8, // [8:8] is the sub-list for extension extendee
|
9, // [9:9] is the sub-list for extension type_name
|
||||||
0, // [0:8] is the sub-list for field type_name
|
9, // [9:9] is the sub-list for extension extendee
|
||||||
|
0, // [0:9] is the sub-list for field type_name
|
||||||
}
|
}
|
||||||
|
|
||||||
func init() { file_daemon_proto_init() }
|
func init() { file_daemon_proto_init() }
|
||||||
@@ -1768,6 +1879,18 @@ func file_daemon_proto_init() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
file_daemon_proto_msgTypes[17].Exporter = func(v interface{}, i int) interface{} {
|
file_daemon_proto_msgTypes[17].Exporter = func(v interface{}, i int) interface{} {
|
||||||
|
switch v := v.(*NSGroupState); i {
|
||||||
|
case 0:
|
||||||
|
return &v.state
|
||||||
|
case 1:
|
||||||
|
return &v.sizeCache
|
||||||
|
case 2:
|
||||||
|
return &v.unknownFields
|
||||||
|
default:
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
file_daemon_proto_msgTypes[18].Exporter = func(v interface{}, i int) interface{} {
|
||||||
switch v := v.(*FullStatus); i {
|
switch v := v.(*FullStatus); i {
|
||||||
case 0:
|
case 0:
|
||||||
return &v.state
|
return &v.state
|
||||||
@@ -1787,7 +1910,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: 0,
|
NumEnums: 0,
|
||||||
NumMessages: 18,
|
NumMessages: 19,
|
||||||
NumExtensions: 0,
|
NumExtensions: 0,
|
||||||
NumServices: 1,
|
NumServices: 1,
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -141,6 +141,7 @@ message PeerState {
|
|||||||
int64 bytesRx = 13;
|
int64 bytesRx = 13;
|
||||||
int64 bytesTx = 14;
|
int64 bytesTx = 14;
|
||||||
bool rosenpassEnabled = 15;
|
bool rosenpassEnabled = 15;
|
||||||
|
repeated string routes = 16;
|
||||||
}
|
}
|
||||||
|
|
||||||
// LocalPeerState contains the latest state of the local peer
|
// LocalPeerState contains the latest state of the local peer
|
||||||
@@ -151,6 +152,7 @@ message LocalPeerState {
|
|||||||
string fqdn = 4;
|
string fqdn = 4;
|
||||||
bool rosenpassEnabled = 5;
|
bool rosenpassEnabled = 5;
|
||||||
bool rosenpassPermissive = 6;
|
bool rosenpassPermissive = 6;
|
||||||
|
repeated string routes = 7;
|
||||||
}
|
}
|
||||||
|
|
||||||
// SignalState contains the latest state of a signal connection
|
// SignalState contains the latest state of a signal connection
|
||||||
@@ -174,6 +176,13 @@ message RelayState {
|
|||||||
string error = 3;
|
string error = 3;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
message NSGroupState {
|
||||||
|
repeated string servers = 1;
|
||||||
|
repeated string domains = 2;
|
||||||
|
bool enabled = 3;
|
||||||
|
string error = 4;
|
||||||
|
}
|
||||||
|
|
||||||
// FullStatus contains the full state held by the Status instance
|
// FullStatus contains the full state held by the Status instance
|
||||||
message FullStatus {
|
message FullStatus {
|
||||||
ManagementState managementState = 1;
|
ManagementState managementState = 1;
|
||||||
@@ -181,4 +190,5 @@ message FullStatus {
|
|||||||
LocalPeerState localPeerState = 3;
|
LocalPeerState localPeerState = 3;
|
||||||
repeated PeerState peers = 4;
|
repeated PeerState peers = 4;
|
||||||
repeated RelayState relays = 5;
|
repeated RelayState relays = 5;
|
||||||
|
repeated NSGroupState dns_servers = 6;
|
||||||
}
|
}
|
||||||
@@ -3,9 +3,16 @@ package server
|
|||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"os"
|
||||||
|
"os/exec"
|
||||||
|
"runtime"
|
||||||
|
"strconv"
|
||||||
"sync"
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/cenkalti/backoff/v4"
|
||||||
|
"golang.org/x/exp/maps"
|
||||||
|
|
||||||
"github.com/netbirdio/netbird/client/internal/auth"
|
"github.com/netbirdio/netbird/client/internal/auth"
|
||||||
"github.com/netbirdio/netbird/client/system"
|
"github.com/netbirdio/netbird/client/system"
|
||||||
|
|
||||||
@@ -21,7 +28,17 @@ import (
|
|||||||
"github.com/netbirdio/netbird/version"
|
"github.com/netbirdio/netbird/version"
|
||||||
)
|
)
|
||||||
|
|
||||||
const probeThreshold = time.Second * 5
|
const (
|
||||||
|
probeThreshold = time.Second * 5
|
||||||
|
retryInitialIntervalVar = "NB_CONN_RETRY_INTERVAL_TIME"
|
||||||
|
maxRetryIntervalVar = "NB_CONN_MAX_RETRY_INTERVAL_TIME"
|
||||||
|
maxRetryTimeVar = "NB_CONN_MAX_RETRY_TIME_TIME"
|
||||||
|
retryMultiplierVar = "NB_CONN_RETRY_MULTIPLIER"
|
||||||
|
defaultInitialRetryTime = 14 * 24 * time.Hour
|
||||||
|
defaultMaxRetryInterval = 60 * time.Minute
|
||||||
|
defaultMaxRetryTime = 14 * 24 * time.Hour
|
||||||
|
defaultRetryMultiplier = 1.7
|
||||||
|
)
|
||||||
|
|
||||||
// Server for service control.
|
// Server for service control.
|
||||||
type Server struct {
|
type Server struct {
|
||||||
@@ -39,6 +56,7 @@ type Server struct {
|
|||||||
proto.UnimplementedDaemonServiceServer
|
proto.UnimplementedDaemonServiceServer
|
||||||
|
|
||||||
statusRecorder *peer.Status
|
statusRecorder *peer.Status
|
||||||
|
sessionWatcher *internal.SessionWatcher
|
||||||
|
|
||||||
mgmProbe *internal.Probe
|
mgmProbe *internal.Probe
|
||||||
signalProbe *internal.Probe
|
signalProbe *internal.Probe
|
||||||
@@ -116,17 +134,116 @@ func (s *Server) Start() error {
|
|||||||
s.statusRecorder.UpdateManagementAddress(config.ManagementURL.String())
|
s.statusRecorder.UpdateManagementAddress(config.ManagementURL.String())
|
||||||
s.statusRecorder.UpdateRosenpass(config.RosenpassEnabled, config.RosenpassPermissive)
|
s.statusRecorder.UpdateRosenpass(config.RosenpassEnabled, config.RosenpassPermissive)
|
||||||
|
|
||||||
|
if s.sessionWatcher == nil {
|
||||||
|
s.sessionWatcher = internal.NewSessionWatcher(s.rootCtx, s.statusRecorder)
|
||||||
|
s.sessionWatcher.SetOnExpireListener(s.onSessionExpire)
|
||||||
|
}
|
||||||
|
|
||||||
if !config.DisableAutoConnect {
|
if !config.DisableAutoConnect {
|
||||||
go func() {
|
go s.connectWithRetryRuns(ctx, config, s.statusRecorder, s.mgmProbe, s.signalProbe, s.relayProbe, s.wgProbe)
|
||||||
if err := internal.RunClientWithProbes(ctx, config, s.statusRecorder, s.mgmProbe, s.signalProbe, s.relayProbe, s.wgProbe); err != nil {
|
|
||||||
log.Errorf("init connections: %v", err)
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// connectWithRetryRuns runs the client connection with a backoff strategy where we retry the operation as additional
|
||||||
|
// mechanism to keep the client connected even when the connection is lost.
|
||||||
|
// we cancel retry if the client receive a stop or down command, or if disable auto connect is configured.
|
||||||
|
func (s *Server) connectWithRetryRuns(ctx context.Context, config *internal.Config, statusRecorder *peer.Status,
|
||||||
|
mgmProbe *internal.Probe, signalProbe *internal.Probe, relayProbe *internal.Probe, wgProbe *internal.Probe) {
|
||||||
|
backOff := getConnectWithBackoff(ctx)
|
||||||
|
retryStarted := false
|
||||||
|
|
||||||
|
go func() {
|
||||||
|
t := time.NewTicker(24 * time.Hour)
|
||||||
|
for {
|
||||||
|
select {
|
||||||
|
case <-ctx.Done():
|
||||||
|
t.Stop()
|
||||||
|
return
|
||||||
|
case <-t.C:
|
||||||
|
if retryStarted {
|
||||||
|
|
||||||
|
mgmtState := statusRecorder.GetManagementState()
|
||||||
|
signalState := statusRecorder.GetSignalState()
|
||||||
|
if mgmtState.Connected && signalState.Connected {
|
||||||
|
log.Tracef("resetting status")
|
||||||
|
retryStarted = false
|
||||||
|
} else {
|
||||||
|
log.Tracef("not resetting status: mgmt: %v, signal: %v", mgmtState.Connected, signalState.Connected)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
runOperation := func() error {
|
||||||
|
log.Tracef("running client connection")
|
||||||
|
err := internal.RunClientWithProbes(ctx, config, statusRecorder, mgmProbe, signalProbe, relayProbe, wgProbe)
|
||||||
|
if err != nil {
|
||||||
|
log.Debugf("run client connection exited with error: %v. Will retry in the background", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if config.DisableAutoConnect {
|
||||||
|
return backoff.Permanent(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if !retryStarted {
|
||||||
|
retryStarted = true
|
||||||
|
backOff.Reset()
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Tracef("client connection exited")
|
||||||
|
return fmt.Errorf("client connection exited")
|
||||||
|
}
|
||||||
|
|
||||||
|
err := backoff.Retry(runOperation, backOff)
|
||||||
|
if s, ok := gstatus.FromError(err); ok && s.Code() != codes.Canceled {
|
||||||
|
log.Errorf("received an error when trying to connect: %v", err)
|
||||||
|
} else {
|
||||||
|
log.Tracef("retry canceled")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// getConnectWithBackoff returns a backoff with exponential backoff strategy for connection retries
|
||||||
|
func getConnectWithBackoff(ctx context.Context) backoff.BackOff {
|
||||||
|
initialInterval := parseEnvDuration(retryInitialIntervalVar, defaultInitialRetryTime)
|
||||||
|
maxInterval := parseEnvDuration(maxRetryIntervalVar, defaultMaxRetryInterval)
|
||||||
|
maxElapsedTime := parseEnvDuration(maxRetryTimeVar, defaultMaxRetryTime)
|
||||||
|
multiplier := defaultRetryMultiplier
|
||||||
|
|
||||||
|
if envValue := os.Getenv(retryMultiplierVar); envValue != "" {
|
||||||
|
// parse the multiplier from the environment variable string value to float64
|
||||||
|
value, err := strconv.ParseFloat(envValue, 64)
|
||||||
|
if err != nil {
|
||||||
|
log.Warnf("unable to parse environment variable %s: %s. using default: %f", retryMultiplierVar, envValue, multiplier)
|
||||||
|
} else {
|
||||||
|
multiplier = value
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return backoff.WithContext(&backoff.ExponentialBackOff{
|
||||||
|
InitialInterval: initialInterval,
|
||||||
|
RandomizationFactor: 1,
|
||||||
|
Multiplier: multiplier,
|
||||||
|
MaxInterval: maxInterval,
|
||||||
|
MaxElapsedTime: maxElapsedTime, // 14 days
|
||||||
|
Stop: backoff.Stop,
|
||||||
|
Clock: backoff.SystemClock,
|
||||||
|
}, ctx)
|
||||||
|
}
|
||||||
|
|
||||||
|
// parseEnvDuration parses the environment variable and returns the duration
|
||||||
|
func parseEnvDuration(envVar string, defaultDuration time.Duration) time.Duration {
|
||||||
|
if envValue := os.Getenv(envVar); envValue != "" {
|
||||||
|
if duration, err := time.ParseDuration(envValue); err == nil {
|
||||||
|
return duration
|
||||||
|
}
|
||||||
|
log.Warnf("unable to parse environment variable %s: %s. using default: %s", envVar, envValue, defaultDuration)
|
||||||
|
}
|
||||||
|
return defaultDuration
|
||||||
|
}
|
||||||
|
|
||||||
// loginAttempt attempts to login using the provided information. it returns a status in case something fails
|
// loginAttempt attempts to login using the provided information. it returns a status in case something fails
|
||||||
func (s *Server) loginAttempt(ctx context.Context, setupKey, jwtToken string) (internal.StatusType, error) {
|
func (s *Server) loginAttempt(ctx context.Context, setupKey, jwtToken string) (internal.StatusType, error) {
|
||||||
var status internal.StatusType
|
var status internal.StatusType
|
||||||
@@ -437,12 +554,7 @@ func (s *Server) Up(callerCtx context.Context, _ *proto.UpRequest) (*proto.UpRes
|
|||||||
s.statusRecorder.UpdateManagementAddress(s.config.ManagementURL.String())
|
s.statusRecorder.UpdateManagementAddress(s.config.ManagementURL.String())
|
||||||
s.statusRecorder.UpdateRosenpass(s.config.RosenpassEnabled, s.config.RosenpassPermissive)
|
s.statusRecorder.UpdateRosenpass(s.config.RosenpassEnabled, s.config.RosenpassPermissive)
|
||||||
|
|
||||||
go func() {
|
go s.connectWithRetryRuns(ctx, s.config, s.statusRecorder, s.mgmProbe, s.signalProbe, s.relayProbe, s.wgProbe)
|
||||||
if err := internal.RunClientWithProbes(ctx, s.config, s.statusRecorder, s.mgmProbe, s.signalProbe, s.relayProbe, s.wgProbe); err != nil {
|
|
||||||
log.Errorf("run client connection: %v", err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
|
|
||||||
return &proto.UpResponse{}, nil
|
return &proto.UpResponse{}, nil
|
||||||
}
|
}
|
||||||
@@ -542,13 +654,23 @@ func (s *Server) GetConfig(_ context.Context, _ *proto.GetConfigRequest) (*proto
|
|||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (s *Server) onSessionExpire() {
|
||||||
|
if runtime.GOOS != "windows" {
|
||||||
|
isUIActive := internal.CheckUIApp()
|
||||||
|
if !isUIActive {
|
||||||
|
if err := sendTerminalNotification(); err != nil {
|
||||||
|
log.Errorf("send session expire terminal notification: %v", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func toProtoFullStatus(fullStatus peer.FullStatus) *proto.FullStatus {
|
func toProtoFullStatus(fullStatus peer.FullStatus) *proto.FullStatus {
|
||||||
pbFullStatus := proto.FullStatus{
|
pbFullStatus := proto.FullStatus{
|
||||||
ManagementState: &proto.ManagementState{},
|
ManagementState: &proto.ManagementState{},
|
||||||
SignalState: &proto.SignalState{},
|
SignalState: &proto.SignalState{},
|
||||||
LocalPeerState: &proto.LocalPeerState{},
|
LocalPeerState: &proto.LocalPeerState{},
|
||||||
Peers: []*proto.PeerState{},
|
Peers: []*proto.PeerState{},
|
||||||
Relays: []*proto.RelayState{},
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pbFullStatus.ManagementState.URL = fullStatus.ManagementState.URL
|
pbFullStatus.ManagementState.URL = fullStatus.ManagementState.URL
|
||||||
@@ -569,6 +691,7 @@ func toProtoFullStatus(fullStatus peer.FullStatus) *proto.FullStatus {
|
|||||||
pbFullStatus.LocalPeerState.Fqdn = fullStatus.LocalPeerState.FQDN
|
pbFullStatus.LocalPeerState.Fqdn = fullStatus.LocalPeerState.FQDN
|
||||||
pbFullStatus.LocalPeerState.RosenpassPermissive = fullStatus.RosenpassState.Permissive
|
pbFullStatus.LocalPeerState.RosenpassPermissive = fullStatus.RosenpassState.Permissive
|
||||||
pbFullStatus.LocalPeerState.RosenpassEnabled = fullStatus.RosenpassState.Enabled
|
pbFullStatus.LocalPeerState.RosenpassEnabled = fullStatus.RosenpassState.Enabled
|
||||||
|
pbFullStatus.LocalPeerState.Routes = maps.Keys(fullStatus.LocalPeerState.Routes)
|
||||||
|
|
||||||
for _, peerState := range fullStatus.Peers {
|
for _, peerState := range fullStatus.Peers {
|
||||||
pbPeerState := &proto.PeerState{
|
pbPeerState := &proto.PeerState{
|
||||||
@@ -587,6 +710,7 @@ func toProtoFullStatus(fullStatus peer.FullStatus) *proto.FullStatus {
|
|||||||
BytesRx: peerState.BytesRx,
|
BytesRx: peerState.BytesRx,
|
||||||
BytesTx: peerState.BytesTx,
|
BytesTx: peerState.BytesTx,
|
||||||
RosenpassEnabled: peerState.RosenpassEnabled,
|
RosenpassEnabled: peerState.RosenpassEnabled,
|
||||||
|
Routes: maps.Keys(peerState.Routes),
|
||||||
}
|
}
|
||||||
pbFullStatus.Peers = append(pbFullStatus.Peers, pbPeerState)
|
pbFullStatus.Peers = append(pbFullStatus.Peers, pbPeerState)
|
||||||
}
|
}
|
||||||
@@ -602,5 +726,47 @@ func toProtoFullStatus(fullStatus peer.FullStatus) *proto.FullStatus {
|
|||||||
pbFullStatus.Relays = append(pbFullStatus.Relays, pbRelayState)
|
pbFullStatus.Relays = append(pbFullStatus.Relays, pbRelayState)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
for _, dnsState := range fullStatus.NSGroupStates {
|
||||||
|
var err string
|
||||||
|
if dnsState.Error != nil {
|
||||||
|
err = dnsState.Error.Error()
|
||||||
|
}
|
||||||
|
pbDnsState := &proto.NSGroupState{
|
||||||
|
Servers: dnsState.Servers,
|
||||||
|
Domains: dnsState.Domains,
|
||||||
|
Enabled: dnsState.Enabled,
|
||||||
|
Error: err,
|
||||||
|
}
|
||||||
|
pbFullStatus.DnsServers = append(pbFullStatus.DnsServers, pbDnsState)
|
||||||
|
}
|
||||||
|
|
||||||
return &pbFullStatus
|
return &pbFullStatus
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// sendTerminalNotification sends a terminal notification message
|
||||||
|
// to inform the user that the NetBird connection session has expired.
|
||||||
|
func sendTerminalNotification() error {
|
||||||
|
message := "NetBird connection session expired\n\nPlease re-authenticate to connect to the network."
|
||||||
|
echoCmd := exec.Command("echo", message)
|
||||||
|
wallCmd := exec.Command("sudo", "wall")
|
||||||
|
|
||||||
|
echoCmdStdout, err := echoCmd.StdoutPipe()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
wallCmd.Stdin = echoCmdStdout
|
||||||
|
|
||||||
|
if err := echoCmd.Start(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := wallCmd.Start(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := echoCmd.Wait(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return wallCmd.Wait()
|
||||||
|
}
|
||||||
|
|||||||
157
client/server/server_test.go
Normal file
157
client/server/server_test.go
Normal file
@@ -0,0 +1,157 @@
|
|||||||
|
package server
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"net"
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
log "github.com/sirupsen/logrus"
|
||||||
|
"google.golang.org/grpc"
|
||||||
|
"google.golang.org/grpc/keepalive"
|
||||||
|
|
||||||
|
"github.com/netbirdio/netbird/client/internal"
|
||||||
|
"github.com/netbirdio/netbird/client/internal/peer"
|
||||||
|
mgmtProto "github.com/netbirdio/netbird/management/proto"
|
||||||
|
"github.com/netbirdio/netbird/management/server"
|
||||||
|
"github.com/netbirdio/netbird/management/server/activity"
|
||||||
|
"github.com/netbirdio/netbird/signal/proto"
|
||||||
|
signalServer "github.com/netbirdio/netbird/signal/server"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
kaep = keepalive.EnforcementPolicy{
|
||||||
|
MinTime: 15 * time.Second,
|
||||||
|
PermitWithoutStream: true,
|
||||||
|
}
|
||||||
|
|
||||||
|
kasp = keepalive.ServerParameters{
|
||||||
|
MaxConnectionIdle: 15 * time.Second,
|
||||||
|
MaxConnectionAgeGrace: 5 * time.Second,
|
||||||
|
Time: 5 * time.Second,
|
||||||
|
Timeout: 2 * time.Second,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
// TestConnectWithRetryRuns checks that the connectWithRetry function runs and runs the retries according to the times specified via environment variables
|
||||||
|
// we will use a management server started via to simulate the server and capture the number of retries
|
||||||
|
func TestConnectWithRetryRuns(t *testing.T) {
|
||||||
|
// start the signal server
|
||||||
|
_, signalAddr, err := startSignal()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("failed to start signal server: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
counter := 0
|
||||||
|
// start the management server
|
||||||
|
_, mgmtAddr, err := startManagement(t, signalAddr, &counter)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("failed to start management server: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx := internal.CtxInitState(context.Background())
|
||||||
|
|
||||||
|
ctx, cancel := context.WithDeadline(ctx, time.Now().Add(30*time.Second))
|
||||||
|
defer cancel()
|
||||||
|
// create new server
|
||||||
|
s := New(ctx, t.TempDir()+"/config.json", "debug")
|
||||||
|
s.latestConfigInput.ManagementURL = "http://" + mgmtAddr
|
||||||
|
config, err := internal.UpdateOrCreateConfig(s.latestConfigInput)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("failed to create config: %v", err)
|
||||||
|
}
|
||||||
|
s.config = config
|
||||||
|
|
||||||
|
s.statusRecorder = peer.NewRecorder(config.ManagementURL.String())
|
||||||
|
t.Setenv(retryInitialIntervalVar, "1s")
|
||||||
|
t.Setenv(maxRetryIntervalVar, "2s")
|
||||||
|
t.Setenv(maxRetryTimeVar, "5s")
|
||||||
|
t.Setenv(retryMultiplierVar, "1")
|
||||||
|
|
||||||
|
s.connectWithRetryRuns(ctx, config, s.statusRecorder, s.mgmProbe, s.signalProbe, s.relayProbe, s.wgProbe)
|
||||||
|
if counter < 3 {
|
||||||
|
t.Fatalf("expected counter > 2, got %d", counter)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type mockServer struct {
|
||||||
|
mgmtProto.ManagementServiceServer
|
||||||
|
counter *int
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *mockServer) Login(ctx context.Context, req *mgmtProto.EncryptedMessage) (*mgmtProto.EncryptedMessage, error) {
|
||||||
|
*m.counter++
|
||||||
|
return m.ManagementServiceServer.Login(ctx, req)
|
||||||
|
}
|
||||||
|
|
||||||
|
func startManagement(t *testing.T, signalAddr string, counter *int) (*grpc.Server, string, error) {
|
||||||
|
t.Helper()
|
||||||
|
dataDir := t.TempDir()
|
||||||
|
|
||||||
|
config := &server.Config{
|
||||||
|
Stuns: []*server.Host{},
|
||||||
|
TURNConfig: &server.TURNConfig{},
|
||||||
|
Signal: &server.Host{
|
||||||
|
Proto: "http",
|
||||||
|
URI: signalAddr,
|
||||||
|
},
|
||||||
|
Datadir: dataDir,
|
||||||
|
HttpConfig: nil,
|
||||||
|
}
|
||||||
|
|
||||||
|
lis, err := net.Listen("tcp", "localhost:0")
|
||||||
|
if err != nil {
|
||||||
|
return nil, "", err
|
||||||
|
}
|
||||||
|
s := grpc.NewServer(grpc.KeepaliveEnforcementPolicy(kaep), grpc.KeepaliveParams(kasp))
|
||||||
|
store, err := server.NewStoreFromJson(config.Datadir, nil)
|
||||||
|
if err != nil {
|
||||||
|
return nil, "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
peersUpdateManager := server.NewPeersUpdateManager(nil)
|
||||||
|
eventStore := &activity.InMemoryEventStore{}
|
||||||
|
if err != nil {
|
||||||
|
return nil, "", err
|
||||||
|
}
|
||||||
|
accountManager, err := server.BuildManager(store, peersUpdateManager, nil, "", "", eventStore, nil, false)
|
||||||
|
if err != nil {
|
||||||
|
return nil, "", err
|
||||||
|
}
|
||||||
|
turnManager := server.NewTimeBasedAuthSecretsManager(peersUpdateManager, config.TURNConfig)
|
||||||
|
mgmtServer, err := server.NewServer(config, accountManager, peersUpdateManager, turnManager, nil, nil)
|
||||||
|
if err != nil {
|
||||||
|
return nil, "", err
|
||||||
|
}
|
||||||
|
mock := &mockServer{
|
||||||
|
ManagementServiceServer: mgmtServer,
|
||||||
|
counter: counter,
|
||||||
|
}
|
||||||
|
mgmtProto.RegisterManagementServiceServer(s, mock)
|
||||||
|
go func() {
|
||||||
|
if err = s.Serve(lis); err != nil {
|
||||||
|
log.Fatalf("failed to serve: %v", err)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
return s, lis.Addr().String(), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func startSignal() (*grpc.Server, string, error) {
|
||||||
|
s := grpc.NewServer(grpc.KeepaliveEnforcementPolicy(kaep), grpc.KeepaliveParams(kasp))
|
||||||
|
|
||||||
|
lis, err := net.Listen("tcp", "localhost:0")
|
||||||
|
if err != nil {
|
||||||
|
log.Fatalf("failed to listen: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
proto.RegisterSignalExchangeServer(s, signalServer.NewServer())
|
||||||
|
|
||||||
|
go func() {
|
||||||
|
if err = s.Serve(lis); err != nil {
|
||||||
|
log.Fatalf("failed to serve: %v", err)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
return s, lis.Addr().String(), nil
|
||||||
|
}
|
||||||
@@ -30,6 +30,12 @@ type Environment struct {
|
|||||||
Platform string
|
Platform string
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type Config struct {
|
||||||
|
RosenpassEnabled bool
|
||||||
|
RosenpassPermissive bool
|
||||||
|
ServerSSHAllowed bool
|
||||||
|
}
|
||||||
|
|
||||||
// Info is an object that contains machine information
|
// Info is an object that contains machine information
|
||||||
// Most of the code is taken from https://github.com/matishsiao/goInfo
|
// Most of the code is taken from https://github.com/matishsiao/goInfo
|
||||||
type Info struct {
|
type Info struct {
|
||||||
@@ -48,6 +54,14 @@ type Info struct {
|
|||||||
SystemProductName string
|
SystemProductName string
|
||||||
SystemManufacturer string
|
SystemManufacturer string
|
||||||
Environment Environment
|
Environment Environment
|
||||||
|
Config Config
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetInfo retrieves and parses the system information
|
||||||
|
func GetInfo(ctx context.Context, config Config) *Info {
|
||||||
|
info := getInfo(ctx)
|
||||||
|
info.Config = config
|
||||||
|
return info
|
||||||
}
|
}
|
||||||
|
|
||||||
// extractUserAgent extracts Netbird's agent (client) name and version from the outgoing context
|
// extractUserAgent extracts Netbird's agent (client) name and version from the outgoing context
|
||||||
|
|||||||
@@ -15,8 +15,7 @@ import (
|
|||||||
"github.com/netbirdio/netbird/version"
|
"github.com/netbirdio/netbird/version"
|
||||||
)
|
)
|
||||||
|
|
||||||
// GetInfo retrieves and parses the system information
|
func getInfo(ctx context.Context) *Info {
|
||||||
func GetInfo(ctx context.Context) *Info {
|
|
||||||
kernel := "android"
|
kernel := "android"
|
||||||
osInfo := uname()
|
osInfo := uname()
|
||||||
if len(osInfo) == 2 {
|
if len(osInfo) == 2 {
|
||||||
@@ -28,7 +27,16 @@ 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{
|
||||||
|
Kernel: kernel,
|
||||||
|
Platform: "unknown",
|
||||||
|
OS: "android",
|
||||||
|
OSVersion: osVersion(),
|
||||||
|
GoOS: runtime.GOOS,
|
||||||
|
CPUs: runtime.NumCPU(),
|
||||||
|
KernelVersion: kernelVersion,
|
||||||
|
}
|
||||||
|
|
||||||
gio.Hostname = extractDeviceName(ctx, "android")
|
gio.Hostname = extractDeviceName(ctx, "android")
|
||||||
gio.WiretrusteeVersion = version.NetbirdVersion()
|
gio.WiretrusteeVersion = version.NetbirdVersion()
|
||||||
gio.UIVersion = extractUserAgent(ctx)
|
gio.UIVersion = extractUserAgent(ctx)
|
||||||
|
|||||||
@@ -20,8 +20,7 @@ import (
|
|||||||
"github.com/netbirdio/netbird/version"
|
"github.com/netbirdio/netbird/version"
|
||||||
)
|
)
|
||||||
|
|
||||||
// GetInfo retrieves and parses the system information
|
func getInfo(ctx context.Context) *Info {
|
||||||
func GetInfo(ctx context.Context) *Info {
|
|
||||||
utsname := unix.Utsname{}
|
utsname := unix.Utsname{}
|
||||||
err := unix.Uname(&utsname)
|
err := unix.Uname(&utsname)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|||||||
@@ -15,8 +15,7 @@ import (
|
|||||||
"github.com/netbirdio/netbird/version"
|
"github.com/netbirdio/netbird/version"
|
||||||
)
|
)
|
||||||
|
|
||||||
// GetInfo retrieves and parses the system information
|
func getInfo(ctx context.Context) *Info {
|
||||||
func GetInfo(ctx context.Context) *Info {
|
|
||||||
out := _getInfo()
|
out := _getInfo()
|
||||||
for strings.Contains(out, "broken pipe") {
|
for strings.Contains(out, "broken pipe") {
|
||||||
out = _getInfo()
|
out = _getInfo()
|
||||||
@@ -31,7 +30,15 @@ func GetInfo(ctx context.Context) *Info {
|
|||||||
Platform: detect_platform.Detect(ctx),
|
Platform: detect_platform.Detect(ctx),
|
||||||
}
|
}
|
||||||
|
|
||||||
gio := &Info{Kernel: osInfo[0], Platform: runtime.GOARCH, OS: osInfo[2], GoOS: runtime.GOOS, CPUs: runtime.NumCPU(), KernelVersion: osInfo[1], Environment: env}
|
gio := &Info{
|
||||||
|
Kernel: osInfo[0],
|
||||||
|
Platform: runtime.GOARCH,
|
||||||
|
OS: osInfo[2],
|
||||||
|
GoOS: runtime.GOOS,
|
||||||
|
CPUs: runtime.NumCPU(),
|
||||||
|
KernelVersion: osInfo[1],
|
||||||
|
Environment: env,
|
||||||
|
}
|
||||||
|
|
||||||
systemHostname, _ := os.Hostname()
|
systemHostname, _ := os.Hostname()
|
||||||
gio.Hostname = extractDeviceName(ctx, systemHostname)
|
gio.Hostname = extractDeviceName(ctx, systemHostname)
|
||||||
|
|||||||
@@ -10,14 +10,21 @@ import (
|
|||||||
"github.com/netbirdio/netbird/version"
|
"github.com/netbirdio/netbird/version"
|
||||||
)
|
)
|
||||||
|
|
||||||
// GetInfo retrieves and parses the system information
|
func getInfo(ctx context.Context) *Info {
|
||||||
func GetInfo(ctx context.Context) *Info {
|
|
||||||
|
|
||||||
// Convert fixed-size byte arrays to Go strings
|
// Convert fixed-size byte arrays to Go strings
|
||||||
sysName := extractOsName(ctx, "sysName")
|
sysName := extractOsName(ctx, "sysName")
|
||||||
swVersion := extractOsVersion(ctx, "swVersion")
|
swVersion := extractOsVersion(ctx, "swVersion")
|
||||||
|
|
||||||
gio := &Info{Kernel: sysName, OSVersion: swVersion, Platform: "unknown", OS: sysName, GoOS: runtime.GOOS, CPUs: runtime.NumCPU(), KernelVersion: swVersion}
|
gio := &Info{
|
||||||
|
Kernel: sysName,
|
||||||
|
OSVersion: swVersion,
|
||||||
|
Platform: "unknown",
|
||||||
|
OS: sysName,
|
||||||
|
GoOS: runtime.GOOS,
|
||||||
|
CPUs: runtime.NumCPU(),
|
||||||
|
KernelVersion: swVersion,
|
||||||
|
}
|
||||||
gio.Hostname = extractDeviceName(ctx, "hostname")
|
gio.Hostname = extractDeviceName(ctx, "hostname")
|
||||||
gio.WiretrusteeVersion = version.NetbirdVersion()
|
gio.WiretrusteeVersion = version.NetbirdVersion()
|
||||||
gio.UIVersion = extractUserAgent(ctx)
|
gio.UIVersion = extractUserAgent(ctx)
|
||||||
|
|||||||
@@ -20,8 +20,7 @@ import (
|
|||||||
"github.com/netbirdio/netbird/version"
|
"github.com/netbirdio/netbird/version"
|
||||||
)
|
)
|
||||||
|
|
||||||
// GetInfo retrieves and parses the system information
|
func getInfo(ctx context.Context) *Info {
|
||||||
func GetInfo(ctx context.Context) *Info {
|
|
||||||
info := _getInfo()
|
info := _getInfo()
|
||||||
for strings.Contains(info, "broken pipe") {
|
for strings.Contains(info, "broken pipe") {
|
||||||
info = _getInfo()
|
info = _getInfo()
|
||||||
|
|||||||
@@ -8,7 +8,6 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
log "github.com/sirupsen/logrus"
|
log "github.com/sirupsen/logrus"
|
||||||
"github.com/yusufpapurcu/wmi"
|
|
||||||
"golang.org/x/sys/windows/registry"
|
"golang.org/x/sys/windows/registry"
|
||||||
|
|
||||||
"github.com/netbirdio/netbird/client/system/detect_cloud"
|
"github.com/netbirdio/netbird/client/system/detect_cloud"
|
||||||
@@ -32,8 +31,7 @@ type Win32_BIOS struct {
|
|||||||
SerialNumber string
|
SerialNumber string
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetInfo retrieves and parses the system information
|
func getInfo(ctx context.Context) *Info {
|
||||||
func GetInfo(ctx context.Context) *Info {
|
|
||||||
osName, osVersion := getOSNameAndVersion()
|
osName, osVersion := getOSNameAndVersion()
|
||||||
buildVersion := getBuildVersion()
|
buildVersion := getBuildVersion()
|
||||||
|
|
||||||
@@ -165,6 +163,10 @@ func sysProductName() (string, error) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
|
// `ComputerSystemProduct` could be empty on some virtualized systems
|
||||||
|
if len(dst) < 1 {
|
||||||
|
return "unknown", nil
|
||||||
|
}
|
||||||
return dst[0].Name, nil
|
return dst[0].Name, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
2
go.mod
2
go.mod
@@ -48,6 +48,7 @@ require (
|
|||||||
github.com/google/gopacket v1.1.19
|
github.com/google/gopacket v1.1.19
|
||||||
github.com/google/nftables v0.0.0-20220808154552-2eca00135732
|
github.com/google/nftables v0.0.0-20220808154552-2eca00135732
|
||||||
github.com/grpc-ecosystem/go-grpc-middleware/v2 v2.0.2-0.20240212192251-757544f21357
|
github.com/grpc-ecosystem/go-grpc-middleware/v2 v2.0.2-0.20240212192251-757544f21357
|
||||||
|
github.com/hashicorp/go-multierror v1.1.0
|
||||||
github.com/hashicorp/go-secure-stdlib/base62 v0.1.2
|
github.com/hashicorp/go-secure-stdlib/base62 v0.1.2
|
||||||
github.com/hashicorp/go-version v1.6.0
|
github.com/hashicorp/go-version v1.6.0
|
||||||
github.com/libp2p/go-netroute v0.2.0
|
github.com/libp2p/go-netroute v0.2.0
|
||||||
@@ -123,6 +124,7 @@ require (
|
|||||||
github.com/googleapis/enterprise-certificate-proxy v0.2.3 // indirect
|
github.com/googleapis/enterprise-certificate-proxy v0.2.3 // indirect
|
||||||
github.com/googleapis/gax-go/v2 v2.10.0 // indirect
|
github.com/googleapis/gax-go/v2 v2.10.0 // indirect
|
||||||
github.com/gopacket/gopacket v1.1.1 // indirect
|
github.com/gopacket/gopacket v1.1.1 // indirect
|
||||||
|
github.com/hashicorp/errwrap v1.0.0 // indirect
|
||||||
github.com/hashicorp/go-uuid v1.0.2 // indirect
|
github.com/hashicorp/go-uuid v1.0.2 // indirect
|
||||||
github.com/inconshreveable/mousetrap v1.1.0 // indirect
|
github.com/inconshreveable/mousetrap v1.1.0 // indirect
|
||||||
github.com/jinzhu/inflection v1.0.0 // indirect
|
github.com/jinzhu/inflection v1.0.0 // indirect
|
||||||
|
|||||||
4
go.sum
4
go.sum
@@ -289,6 +289,10 @@ github.com/gorilla/mux v1.8.0/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB7
|
|||||||
github.com/grpc-ecosystem/go-grpc-middleware/v2 v2.0.2-0.20240212192251-757544f21357 h1:Fkzd8ktnpOR9h47SXHe2AYPwelXLH2GjGsjlAloiWfo=
|
github.com/grpc-ecosystem/go-grpc-middleware/v2 v2.0.2-0.20240212192251-757544f21357 h1:Fkzd8ktnpOR9h47SXHe2AYPwelXLH2GjGsjlAloiWfo=
|
||||||
github.com/grpc-ecosystem/go-grpc-middleware/v2 v2.0.2-0.20240212192251-757544f21357/go.mod h1:w9Y7gY31krpLmrVU5ZPG9H7l9fZuRu5/3R3S3FMtVQ4=
|
github.com/grpc-ecosystem/go-grpc-middleware/v2 v2.0.2-0.20240212192251-757544f21357/go.mod h1:w9Y7gY31krpLmrVU5ZPG9H7l9fZuRu5/3R3S3FMtVQ4=
|
||||||
github.com/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFbcEtjT1g+wF4CSlocrBnw=
|
github.com/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFbcEtjT1g+wF4CSlocrBnw=
|
||||||
|
github.com/hashicorp/errwrap v1.0.0 h1:hLrqtEDnRye3+sgx6z4qVLNuviH3MR5aQ0ykNJa/UYA=
|
||||||
|
github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=
|
||||||
|
github.com/hashicorp/go-multierror v1.1.0 h1:B9UzwGQJehnUY1yNrnwREHc3fGbC2xefo8g4TbElacI=
|
||||||
|
github.com/hashicorp/go-multierror v1.1.0/go.mod h1:spPvp8C1qA32ftKqdAHm4hHTbPw+vmowP0z+KUhOZdA=
|
||||||
github.com/hashicorp/go-secure-stdlib/base62 v0.1.2 h1:ET4pqyjiGmY09R5y+rSd70J2w45CtbWDNvGqWp/R3Ng=
|
github.com/hashicorp/go-secure-stdlib/base62 v0.1.2 h1:ET4pqyjiGmY09R5y+rSd70J2w45CtbWDNvGqWp/R3Ng=
|
||||||
github.com/hashicorp/go-secure-stdlib/base62 v0.1.2/go.mod h1:EdWO6czbmthiwZ3/PUsDV+UD1D5IRU4ActiaWGwt0Yw=
|
github.com/hashicorp/go-secure-stdlib/base62 v0.1.2/go.mod h1:EdWO6czbmthiwZ3/PUsDV+UD1D5IRU4ActiaWGwt0Yw=
|
||||||
github.com/hashicorp/go-uuid v1.0.2 h1:cfejS+Tpcp13yd5nYHWDI6qVCny6wyX2Mt5SGur2IGE=
|
github.com/hashicorp/go-uuid v1.0.2 h1:cfejS+Tpcp13yd5nYHWDI6qVCny6wyX2Mt5SGur2IGE=
|
||||||
|
|||||||
@@ -163,7 +163,7 @@ func TestClient_LoginUnregistered_ShouldThrow_401(t *testing.T) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
sysInfo := system.GetInfo(context.TODO())
|
sysInfo := &system.Info{Hostname: "test"}
|
||||||
_, err = client.Login(*key, sysInfo, nil)
|
_, err = client.Login(*key, sysInfo, nil)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
t.Error("expecting err on unregistered login, got nil")
|
t.Error("expecting err on unregistered login, got nil")
|
||||||
@@ -191,7 +191,7 @@ func TestClient_LoginRegistered(t *testing.T) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
t.Error(err)
|
t.Error(err)
|
||||||
}
|
}
|
||||||
info := system.GetInfo(context.TODO())
|
info := &system.Info{Hostname: "test"}
|
||||||
resp, err := client.Register(*key, ValidKey, "", info, nil)
|
resp, err := client.Register(*key, ValidKey, "", info, nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Error(err)
|
t.Error(err)
|
||||||
@@ -221,7 +221,7 @@ func TestClient_Sync(t *testing.T) {
|
|||||||
t.Error(err)
|
t.Error(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
info := system.GetInfo(context.TODO())
|
info := &system.Info{Hostname: "test"}
|
||||||
_, err = client.Register(*serverKey, ValidKey, "", info, nil)
|
_, err = client.Register(*serverKey, ValidKey, "", info, nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Error(err)
|
t.Error(err)
|
||||||
@@ -237,7 +237,6 @@ func TestClient_Sync(t *testing.T) {
|
|||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
info = system.GetInfo(context.TODO())
|
|
||||||
_, err = remoteClient.Register(*serverKey, ValidKey, "", info, nil)
|
_, err = remoteClient.Register(*serverKey, ValidKey, "", info, nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
@@ -335,7 +334,7 @@ func Test_SystemMetaDataFromClient(t *testing.T) {
|
|||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
info := system.GetInfo(context.TODO())
|
info := &system.Info{Hostname: "test"}
|
||||||
_, err = testClient.Register(*key, ValidKey, "", info, nil)
|
_, err = testClient.Register(*key, ValidKey, "", info, nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Errorf("error while trying to register client: %v", err)
|
t.Errorf("error while trying to register client: %v", err)
|
||||||
|
|||||||
@@ -480,5 +480,10 @@ func infoToMetaData(info *system.Info) *proto.PeerSystemMeta {
|
|||||||
Cloud: info.Environment.Cloud,
|
Cloud: info.Environment.Cloud,
|
||||||
Platform: info.Environment.Platform,
|
Platform: info.Environment.Platform,
|
||||||
},
|
},
|
||||||
|
Config: &proto.Config{
|
||||||
|
RosenpassEnabled: info.Config.RosenpassEnabled,
|
||||||
|
RosenpassPermissive: info.Config.RosenpassPermissive,
|
||||||
|
ServerSSHAllowed: info.Config.ServerSSHAllowed,
|
||||||
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -100,6 +100,13 @@ message Environment {
|
|||||||
string platform = 2;
|
string platform = 2;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Config is a message with local configuration settings of the peer
|
||||||
|
message Config {
|
||||||
|
bool rosenpassEnabled = 1;
|
||||||
|
bool rosenpassPermissive = 2;
|
||||||
|
bool serverSSHAllowed = 3;
|
||||||
|
}
|
||||||
|
|
||||||
// PeerSystemMeta is machine meta data like OS and version.
|
// PeerSystemMeta is machine meta data like OS and version.
|
||||||
message PeerSystemMeta {
|
message PeerSystemMeta {
|
||||||
string hostname = 1;
|
string hostname = 1;
|
||||||
@@ -117,6 +124,7 @@ message PeerSystemMeta {
|
|||||||
string sysProductName = 13;
|
string sysProductName = 13;
|
||||||
string sysManufacturer = 14;
|
string sysManufacturer = 14;
|
||||||
Environment environment = 15;
|
Environment environment = 15;
|
||||||
|
Config config = 16;
|
||||||
}
|
}
|
||||||
|
|
||||||
message LoginResponse {
|
message LoginResponse {
|
||||||
|
|||||||
@@ -72,7 +72,6 @@ type AccountManager interface {
|
|||||||
CheckUserAccessByJWTGroups(claims jwtclaims.AuthorizationClaims) error
|
CheckUserAccessByJWTGroups(claims jwtclaims.AuthorizationClaims) error
|
||||||
GetAccountFromPAT(pat string) (*Account, *User, *PersonalAccessToken, error)
|
GetAccountFromPAT(pat string) (*Account, *User, *PersonalAccessToken, error)
|
||||||
DeleteAccount(accountID, userID string) error
|
DeleteAccount(accountID, userID string) error
|
||||||
GetUsage(ctx context.Context, accountID string, start time.Time, end time.Time) (*AccountUsageStats, error)
|
|
||||||
MarkPATUsed(tokenID string) error
|
MarkPATUsed(tokenID string) error
|
||||||
GetUser(claims jwtclaims.AuthorizationClaims) (*User, error)
|
GetUser(claims jwtclaims.AuthorizationClaims) (*User, error)
|
||||||
ListUsers(accountID string) ([]*User, error)
|
ListUsers(accountID string) ([]*User, error)
|
||||||
@@ -233,14 +232,6 @@ type Account struct {
|
|||||||
RulesG []Rule `json:"-" gorm:"-"`
|
RulesG []Rule `json:"-" gorm:"-"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// AccountUsageStats represents the current usage statistics for an account
|
|
||||||
type AccountUsageStats struct {
|
|
||||||
ActiveUsers int64 `json:"active_users"`
|
|
||||||
TotalUsers int64 `json:"total_users"`
|
|
||||||
ActivePeers int64 `json:"active_peers"`
|
|
||||||
TotalPeers int64 `json:"total_peers"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type UserInfo struct {
|
type UserInfo struct {
|
||||||
ID string `json:"id"`
|
ID string `json:"id"`
|
||||||
Email string `json:"email"`
|
Email string `json:"email"`
|
||||||
@@ -464,6 +455,11 @@ func (a *Account) GetNextPeerExpiration() (time.Duration, bool) {
|
|||||||
}
|
}
|
||||||
_, duration := peer.LoginExpired(a.Settings.PeerLoginExpiration)
|
_, duration := peer.LoginExpired(a.Settings.PeerLoginExpiration)
|
||||||
if nextExpiry == nil || duration < *nextExpiry {
|
if nextExpiry == nil || duration < *nextExpiry {
|
||||||
|
// if expiration is below 1s return 1s duration
|
||||||
|
// this avoids issues with ticker that can't be set to < 0
|
||||||
|
if duration < time.Second {
|
||||||
|
return time.Second, true
|
||||||
|
}
|
||||||
nextExpiry = &duration
|
nextExpiry = &duration
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1121,17 +1117,6 @@ func (am *DefaultAccountManager) DeleteAccount(accountID, userID string) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetUsage returns the usage stats for the given account.
|
|
||||||
// This cannot be used to calculate usage stats for a period in the past as it relies on peers' last seen time.
|
|
||||||
func (am *DefaultAccountManager) GetUsage(ctx context.Context, accountID string, start time.Time, end time.Time) (*AccountUsageStats, error) {
|
|
||||||
usageStats, err := am.Store.CalculateUsageStats(ctx, accountID, start, end)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("failed to calculate usage stats: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
return usageStats, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetAccountByUserOrAccountID looks for an account by user or accountID, if no account is provided and
|
// GetAccountByUserOrAccountID looks for an account by user or accountID, if no account is provided and
|
||||||
// userID doesn't have an account associated with it, one account is created
|
// userID doesn't have an account associated with it, one account is created
|
||||||
// domain is used to create a new account if no account is found
|
// domain is used to create a new account if no account is found
|
||||||
|
|||||||
@@ -1,8 +1,6 @@
|
|||||||
package server
|
package server
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
|
||||||
"fmt"
|
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"strings"
|
"strings"
|
||||||
@@ -664,40 +662,3 @@ func (s *FileStore) Close() error {
|
|||||||
func (s *FileStore) GetStoreEngine() StoreEngine {
|
func (s *FileStore) GetStoreEngine() StoreEngine {
|
||||||
return FileStoreEngine
|
return FileStoreEngine
|
||||||
}
|
}
|
||||||
|
|
||||||
// CalculateUsageStats returns the usage stats for an account
|
|
||||||
// start and end are inclusive.
|
|
||||||
func (s *FileStore) CalculateUsageStats(_ context.Context, accountID string, start time.Time, end time.Time) (*AccountUsageStats, error) {
|
|
||||||
s.mux.Lock()
|
|
||||||
defer s.mux.Unlock()
|
|
||||||
|
|
||||||
account, exists := s.Accounts[accountID]
|
|
||||||
if !exists {
|
|
||||||
return nil, fmt.Errorf("account not found")
|
|
||||||
}
|
|
||||||
|
|
||||||
stats := &AccountUsageStats{
|
|
||||||
TotalUsers: 0,
|
|
||||||
TotalPeers: int64(len(account.Peers)),
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, user := range account.Users {
|
|
||||||
if !user.IsServiceUser {
|
|
||||||
stats.TotalUsers++
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
activeUsers := make(map[string]bool)
|
|
||||||
for _, peer := range account.Peers {
|
|
||||||
lastSeen := peer.Status.LastSeen
|
|
||||||
if lastSeen.Compare(start) >= 0 && lastSeen.Compare(end) <= 0 {
|
|
||||||
if _, exists := account.Users[peer.UserID]; exists && !activeUsers[peer.UserID] {
|
|
||||||
activeUsers[peer.UserID] = true
|
|
||||||
stats.ActiveUsers++
|
|
||||||
}
|
|
||||||
stats.ActivePeers++
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return stats, nil
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
package server
|
package server
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
|
||||||
"crypto/sha256"
|
"crypto/sha256"
|
||||||
"net"
|
"net"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
@@ -658,32 +657,3 @@ func newStore(t *testing.T) *FileStore {
|
|||||||
|
|
||||||
return store
|
return store
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestFileStore_CalculateUsageStats(t *testing.T) {
|
|
||||||
storeDir := t.TempDir()
|
|
||||||
|
|
||||||
err := util.CopyFileContents("testdata/store_stats.json", filepath.Join(storeDir, "store.json"))
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
store, err := NewFileStore(storeDir, nil)
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
startDate := time.Date(2024, time.February, 1, 0, 0, 0, 0, time.UTC)
|
|
||||||
endDate := startDate.AddDate(0, 1, 0).Add(-time.Nanosecond)
|
|
||||||
|
|
||||||
stats1, err := store.CalculateUsageStats(context.TODO(), "account-1", startDate, endDate)
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
assert.Equal(t, int64(2), stats1.ActiveUsers)
|
|
||||||
assert.Equal(t, int64(4), stats1.TotalUsers)
|
|
||||||
assert.Equal(t, int64(3), stats1.ActivePeers)
|
|
||||||
assert.Equal(t, int64(7), stats1.TotalPeers)
|
|
||||||
|
|
||||||
stats2, err := store.CalculateUsageStats(context.TODO(), "account-2", startDate, endDate)
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
assert.Equal(t, int64(1), stats2.ActiveUsers)
|
|
||||||
assert.Equal(t, int64(2), stats2.TotalUsers)
|
|
||||||
assert.Equal(t, int64(1), stats2.ActivePeers)
|
|
||||||
assert.Equal(t, int64(2), stats2.TotalPeers)
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -292,6 +292,9 @@ func extractPeerMeta(loginReq *proto.LoginRequest) nbpeer.PeerSystemMeta {
|
|||||||
Cloud: loginReq.GetMeta().GetEnvironment().GetCloud(),
|
Cloud: loginReq.GetMeta().GetEnvironment().GetCloud(),
|
||||||
Platform: loginReq.GetMeta().GetEnvironment().GetPlatform(),
|
Platform: loginReq.GetMeta().GetEnvironment().GetPlatform(),
|
||||||
},
|
},
|
||||||
|
RosenpassEnabled: loginReq.GetMeta().GetRosenpassEnabled(),
|
||||||
|
RosenpassPermissive: loginReq.GetMeta().GetRosenpassPermissive(),
|
||||||
|
ServerSSHAllowed: loginReq.GetMeta().GetServerSSHAllowed(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
package mock_server
|
package mock_server
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
|
||||||
"net"
|
"net"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
@@ -93,7 +92,6 @@ type MockAccountManager struct {
|
|||||||
SavePostureChecksFunc func(accountID, userID string, postureChecks *posture.Checks) error
|
SavePostureChecksFunc func(accountID, userID string, postureChecks *posture.Checks) error
|
||||||
DeletePostureChecksFunc func(accountID, postureChecksID, userID string) error
|
DeletePostureChecksFunc func(accountID, postureChecksID, userID string) error
|
||||||
ListPostureChecksFunc func(accountID, userID string) ([]*posture.Checks, error)
|
ListPostureChecksFunc func(accountID, userID string) ([]*posture.Checks, error)
|
||||||
GetUsageFunc func(ctx context.Context, accountID string, start, end time.Time) (*server.AccountUsageStats, error)
|
|
||||||
GetIdpManagerFunc func() idp.Manager
|
GetIdpManagerFunc func() idp.Manager
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -707,14 +705,6 @@ func (am *MockAccountManager) ListPostureChecks(accountID, userID string) ([]*po
|
|||||||
return nil, status.Errorf(codes.Unimplemented, "method ListPostureChecks is not implemented")
|
return nil, status.Errorf(codes.Unimplemented, "method ListPostureChecks is not implemented")
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetUsage mocks GetUsage of the AccountManager interface
|
|
||||||
func (am *MockAccountManager) GetUsage(ctx context.Context, accountID string, start time.Time, end time.Time) (*server.AccountUsageStats, error) {
|
|
||||||
if am.GetUsageFunc != nil {
|
|
||||||
return am.GetUsageFunc(ctx, accountID, start, end)
|
|
||||||
}
|
|
||||||
return nil, status.Errorf(codes.Unimplemented, "method GetUsage is not implemented")
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetIdpManager mocks GetIdpManager of the AccountManager interface
|
// GetIdpManager mocks GetIdpManager of the AccountManager interface
|
||||||
func (am *MockAccountManager) GetIdpManager() idp.Manager {
|
func (am *MockAccountManager) GetIdpManager() idp.Manager {
|
||||||
if am.GetIdpManagerFunc != nil {
|
if am.GetIdpManagerFunc != nil {
|
||||||
|
|||||||
@@ -81,21 +81,24 @@ type Environment struct {
|
|||||||
|
|
||||||
// PeerSystemMeta is a metadata of a Peer machine system
|
// PeerSystemMeta is a metadata of a Peer machine system
|
||||||
type PeerSystemMeta struct { //nolint:revive
|
type PeerSystemMeta struct { //nolint:revive
|
||||||
Hostname string
|
Hostname string
|
||||||
GoOS string
|
GoOS string
|
||||||
Kernel string
|
Kernel string
|
||||||
Core string
|
Core string
|
||||||
Platform string
|
Platform string
|
||||||
OS string
|
OS string
|
||||||
OSVersion string
|
OSVersion string
|
||||||
WtVersion string
|
WtVersion string
|
||||||
UIVersion string
|
UIVersion string
|
||||||
KernelVersion string
|
KernelVersion string
|
||||||
NetworkAddresses []NetworkAddress `gorm:"serializer:json"`
|
NetworkAddresses []NetworkAddress `gorm:"serializer:json"`
|
||||||
SystemSerialNumber string
|
SystemSerialNumber string
|
||||||
SystemProductName string
|
SystemProductName string
|
||||||
SystemManufacturer string
|
SystemManufacturer string
|
||||||
Environment Environment `gorm:"serializer:json"`
|
Environment Environment `gorm:"serializer:json"`
|
||||||
|
RosenpassEnabled bool
|
||||||
|
RosenpassPermissive bool
|
||||||
|
ServerSSHAllowed bool
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p PeerSystemMeta) isEqual(other PeerSystemMeta) bool {
|
func (p PeerSystemMeta) isEqual(other PeerSystemMeta) bool {
|
||||||
@@ -130,7 +133,10 @@ func (p PeerSystemMeta) isEqual(other PeerSystemMeta) bool {
|
|||||||
p.SystemProductName == other.SystemProductName &&
|
p.SystemProductName == other.SystemProductName &&
|
||||||
p.SystemManufacturer == other.SystemManufacturer &&
|
p.SystemManufacturer == other.SystemManufacturer &&
|
||||||
p.Environment.Cloud == other.Environment.Cloud &&
|
p.Environment.Cloud == other.Environment.Cloud &&
|
||||||
p.Environment.Platform == other.Environment.Platform
|
p.Environment.Platform == other.Environment.Platform &&
|
||||||
|
p.RosenpassEnabled == other.RosenpassEnabled &&
|
||||||
|
p.RosenpassPermissive == other.RosenpassPermissive &&
|
||||||
|
p.ServerSSHAllowed == other.ServerSSHAllowed
|
||||||
}
|
}
|
||||||
|
|
||||||
// AddedWithSSOLogin indicates whether this peer has been added with an SSO login by a user.
|
// AddedWithSSOLogin indicates whether this peer has been added with an SSO login by a user.
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
package server
|
package server
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"runtime"
|
"runtime"
|
||||||
@@ -256,7 +256,11 @@ func (s *SqliteStore) SavePeerStatus(accountID, peerID string, peerStatus nbpeer
|
|||||||
|
|
||||||
result := s.db.First(&peer, "account_id = ? and id = ?", accountID, peerID)
|
result := s.db.First(&peer, "account_id = ? and id = ?", accountID, peerID)
|
||||||
if result.Error != nil {
|
if result.Error != nil {
|
||||||
return status.Errorf(status.NotFound, "peer %s not found", peerID)
|
if errors.Is(result.Error, gorm.ErrRecordNotFound) {
|
||||||
|
return status.Errorf(status.NotFound, "peer %s not found", peerID)
|
||||||
|
}
|
||||||
|
log.Errorf("error when getting peer from the store: %s", result.Error)
|
||||||
|
return status.Errorf(status.Internal, "issue getting peer from store")
|
||||||
}
|
}
|
||||||
|
|
||||||
peer.Status = &peerStatus
|
peer.Status = &peerStatus
|
||||||
@@ -268,7 +272,11 @@ func (s *SqliteStore) SavePeerLocation(accountID string, peerWithLocation *nbpee
|
|||||||
var peer nbpeer.Peer
|
var peer nbpeer.Peer
|
||||||
result := s.db.First(&peer, "account_id = ? and id = ?", accountID, peerWithLocation.ID)
|
result := s.db.First(&peer, "account_id = ? and id = ?", accountID, peerWithLocation.ID)
|
||||||
if result.Error != nil {
|
if result.Error != nil {
|
||||||
return status.Errorf(status.NotFound, "peer %s not found", peer.ID)
|
if errors.Is(result.Error, gorm.ErrRecordNotFound) {
|
||||||
|
return status.Errorf(status.NotFound, "peer %s not found", peer.ID)
|
||||||
|
}
|
||||||
|
log.Errorf("error when getting peer from the store: %s", result.Error)
|
||||||
|
return status.Errorf(status.Internal, "issue getting peer from store")
|
||||||
}
|
}
|
||||||
|
|
||||||
peer.Location = peerWithLocation.Location
|
peer.Location = peerWithLocation.Location
|
||||||
@@ -292,7 +300,11 @@ func (s *SqliteStore) GetAccountByPrivateDomain(domain string) (*Account, error)
|
|||||||
result := s.db.First(&account, "domain = ? and is_domain_primary_account = ? and domain_category = ?",
|
result := s.db.First(&account, "domain = ? and is_domain_primary_account = ? and domain_category = ?",
|
||||||
strings.ToLower(domain), true, PrivateCategory)
|
strings.ToLower(domain), true, PrivateCategory)
|
||||||
if result.Error != nil {
|
if result.Error != nil {
|
||||||
return nil, status.Errorf(status.NotFound, "account not found: provided domain is not registered or is not private")
|
if errors.Is(result.Error, gorm.ErrRecordNotFound) {
|
||||||
|
return nil, status.Errorf(status.NotFound, "account not found: provided domain is not registered or is not private")
|
||||||
|
}
|
||||||
|
log.Errorf("error when getting account from the store: %s", result.Error)
|
||||||
|
return nil, status.Errorf(status.Internal, "issue getting account from store")
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: rework to not call GetAccount
|
// TODO: rework to not call GetAccount
|
||||||
@@ -303,7 +315,11 @@ func (s *SqliteStore) GetAccountBySetupKey(setupKey string) (*Account, error) {
|
|||||||
var key SetupKey
|
var key SetupKey
|
||||||
result := s.db.Select("account_id").First(&key, "key = ?", strings.ToUpper(setupKey))
|
result := s.db.Select("account_id").First(&key, "key = ?", strings.ToUpper(setupKey))
|
||||||
if result.Error != nil {
|
if result.Error != nil {
|
||||||
return nil, status.Errorf(status.NotFound, "account not found: index lookup failed")
|
if errors.Is(result.Error, gorm.ErrRecordNotFound) {
|
||||||
|
return nil, status.Errorf(status.NotFound, "account not found: index lookup failed")
|
||||||
|
}
|
||||||
|
log.Errorf("error when getting setup key from the store: %s", result.Error)
|
||||||
|
return nil, status.Errorf(status.Internal, "issue getting setup key from store")
|
||||||
}
|
}
|
||||||
|
|
||||||
if key.AccountID == "" {
|
if key.AccountID == "" {
|
||||||
@@ -317,7 +333,11 @@ func (s *SqliteStore) GetTokenIDByHashedToken(hashedToken string) (string, error
|
|||||||
var token PersonalAccessToken
|
var token PersonalAccessToken
|
||||||
result := s.db.First(&token, "hashed_token = ?", hashedToken)
|
result := s.db.First(&token, "hashed_token = ?", hashedToken)
|
||||||
if result.Error != nil {
|
if result.Error != nil {
|
||||||
return "", status.Errorf(status.NotFound, "account not found: index lookup failed")
|
if errors.Is(result.Error, gorm.ErrRecordNotFound) {
|
||||||
|
return "", status.Errorf(status.NotFound, "account not found: index lookup failed")
|
||||||
|
}
|
||||||
|
log.Errorf("error when getting token from the store: %s", result.Error)
|
||||||
|
return "", status.Errorf(status.Internal, "issue getting account from store")
|
||||||
}
|
}
|
||||||
|
|
||||||
return token.ID, nil
|
return token.ID, nil
|
||||||
@@ -327,7 +347,11 @@ func (s *SqliteStore) GetUserByTokenID(tokenID string) (*User, error) {
|
|||||||
var token PersonalAccessToken
|
var token PersonalAccessToken
|
||||||
result := s.db.First(&token, "id = ?", tokenID)
|
result := s.db.First(&token, "id = ?", tokenID)
|
||||||
if result.Error != nil {
|
if result.Error != nil {
|
||||||
return nil, status.Errorf(status.NotFound, "account not found: index lookup failed")
|
if errors.Is(result.Error, gorm.ErrRecordNotFound) {
|
||||||
|
return nil, status.Errorf(status.NotFound, "account not found: index lookup failed")
|
||||||
|
}
|
||||||
|
log.Errorf("error when getting token from the store: %s", result.Error)
|
||||||
|
return nil, status.Errorf(status.Internal, "issue getting account from store")
|
||||||
}
|
}
|
||||||
|
|
||||||
if token.UserID == "" {
|
if token.UserID == "" {
|
||||||
@@ -371,8 +395,11 @@ func (s *SqliteStore) 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("when getting account from the store: %s", result.Error)
|
log.Errorf("error when getting account from the store: %s", result.Error)
|
||||||
return nil, status.Errorf(status.NotFound, "account not found")
|
if errors.Is(result.Error, gorm.ErrRecordNotFound) {
|
||||||
|
return nil, status.Errorf(status.NotFound, "account not found")
|
||||||
|
}
|
||||||
|
return nil, status.Errorf(status.Internal, "issue getting account from store")
|
||||||
}
|
}
|
||||||
|
|
||||||
// we have to manually preload policy rules as it seems that gorm preloading doesn't do it for us
|
// we have to manually preload policy rules as it seems that gorm preloading doesn't do it for us
|
||||||
@@ -432,7 +459,11 @@ func (s *SqliteStore) GetAccountByUser(userID string) (*Account, error) {
|
|||||||
var user User
|
var user User
|
||||||
result := s.db.Select("account_id").First(&user, "id = ?", userID)
|
result := s.db.Select("account_id").First(&user, "id = ?", userID)
|
||||||
if result.Error != nil {
|
if result.Error != nil {
|
||||||
return nil, status.Errorf(status.NotFound, "account not found: index lookup failed")
|
if errors.Is(result.Error, gorm.ErrRecordNotFound) {
|
||||||
|
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")
|
||||||
}
|
}
|
||||||
|
|
||||||
if user.AccountID == "" {
|
if user.AccountID == "" {
|
||||||
@@ -446,7 +477,11 @@ func (s *SqliteStore) GetAccountByPeerID(peerID string) (*Account, error) {
|
|||||||
var peer nbpeer.Peer
|
var peer nbpeer.Peer
|
||||||
result := s.db.Select("account_id").First(&peer, "id = ?", peerID)
|
result := s.db.Select("account_id").First(&peer, "id = ?", peerID)
|
||||||
if result.Error != nil {
|
if result.Error != nil {
|
||||||
return nil, status.Errorf(status.NotFound, "account not found: index lookup failed")
|
if errors.Is(result.Error, gorm.ErrRecordNotFound) {
|
||||||
|
return nil, status.Errorf(status.NotFound, "account not found: index lookup failed")
|
||||||
|
}
|
||||||
|
log.Errorf("error when getting peer from the store: %s", result.Error)
|
||||||
|
return nil, status.Errorf(status.Internal, "issue getting account from store")
|
||||||
}
|
}
|
||||||
|
|
||||||
if peer.AccountID == "" {
|
if peer.AccountID == "" {
|
||||||
@@ -461,7 +496,11 @@ func (s *SqliteStore) GetAccountByPeerPubKey(peerKey string) (*Account, error) {
|
|||||||
|
|
||||||
result := s.db.Select("account_id").First(&peer, "key = ?", peerKey)
|
result := s.db.Select("account_id").First(&peer, "key = ?", peerKey)
|
||||||
if result.Error != nil {
|
if result.Error != nil {
|
||||||
return nil, status.Errorf(status.NotFound, "account not found: index lookup failed")
|
if errors.Is(result.Error, gorm.ErrRecordNotFound) {
|
||||||
|
return nil, status.Errorf(status.NotFound, "account not found: index lookup failed")
|
||||||
|
}
|
||||||
|
log.Errorf("error when getting peer from the store: %s", result.Error)
|
||||||
|
return nil, status.Errorf(status.Internal, "issue getting account from store")
|
||||||
}
|
}
|
||||||
|
|
||||||
if peer.AccountID == "" {
|
if peer.AccountID == "" {
|
||||||
@@ -477,7 +516,11 @@ func (s *SqliteStore) SaveUserLastLogin(accountID, userID string, lastLogin time
|
|||||||
|
|
||||||
result := s.db.First(&user, "account_id = ? and id = ?", accountID, userID)
|
result := s.db.First(&user, "account_id = ? and id = ?", accountID, userID)
|
||||||
if result.Error != nil {
|
if result.Error != nil {
|
||||||
return status.Errorf(status.NotFound, "user %s not found", userID)
|
if errors.Is(result.Error, gorm.ErrRecordNotFound) {
|
||||||
|
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")
|
||||||
}
|
}
|
||||||
|
|
||||||
user.LastLogin = lastLogin
|
user.LastLogin = lastLogin
|
||||||
@@ -498,48 +541,3 @@ func (s *SqliteStore) Close() error {
|
|||||||
func (s *SqliteStore) GetStoreEngine() StoreEngine {
|
func (s *SqliteStore) GetStoreEngine() StoreEngine {
|
||||||
return SqliteStoreEngine
|
return SqliteStoreEngine
|
||||||
}
|
}
|
||||||
|
|
||||||
// CalculateUsageStats returns the usage stats for an account
|
|
||||||
// start and end are inclusive.
|
|
||||||
func (s *SqliteStore) CalculateUsageStats(ctx context.Context, accountID string, start time.Time, end time.Time) (*AccountUsageStats, error) {
|
|
||||||
stats := &AccountUsageStats{}
|
|
||||||
|
|
||||||
err := s.db.WithContext(ctx).Transaction(func(tx *gorm.DB) error {
|
|
||||||
err := tx.Model(&nbpeer.Peer{}).
|
|
||||||
Where("account_id = ? AND peer_status_last_seen BETWEEN ? AND ?", accountID, start, end).
|
|
||||||
Distinct("user_id").
|
|
||||||
Count(&stats.ActiveUsers).Error
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("get active users: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
err = tx.Model(&User{}).
|
|
||||||
Where("account_id = ? AND is_service_user = ?", accountID, false).
|
|
||||||
Count(&stats.TotalUsers).Error
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("get total users: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
err = tx.Model(&nbpeer.Peer{}).
|
|
||||||
Where("account_id = ? AND peer_status_last_seen BETWEEN ? AND ?", accountID, start, end).
|
|
||||||
Count(&stats.ActivePeers).Error
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("get active peers: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
err = tx.Model(&nbpeer.Peer{}).
|
|
||||||
Where("account_id = ?", accountID).
|
|
||||||
Count(&stats.TotalPeers).Error
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("get total peers: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
})
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("transaction: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
return stats, nil
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
package server
|
package server
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
"net"
|
"net"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
@@ -13,6 +12,8 @@ import (
|
|||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
|
|
||||||
|
"github.com/netbirdio/netbird/management/server/status"
|
||||||
|
|
||||||
nbpeer "github.com/netbirdio/netbird/management/server/peer"
|
nbpeer "github.com/netbirdio/netbird/management/server/peer"
|
||||||
"github.com/netbirdio/netbird/util"
|
"github.com/netbirdio/netbird/util"
|
||||||
)
|
)
|
||||||
@@ -175,6 +176,26 @@ func TestSqlite_DeleteAccount(t *testing.T) {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestSqlite_GetAccount(t *testing.T) {
|
||||||
|
if runtime.GOOS == "windows" {
|
||||||
|
t.Skip("The SQLite store is not properly supported by Windows yet")
|
||||||
|
}
|
||||||
|
|
||||||
|
store := newSqliteStoreFromFile(t, "testdata/store.json")
|
||||||
|
|
||||||
|
id := "bf1c8084-ba50-4ce7-9439-34653001fc3b"
|
||||||
|
|
||||||
|
account, err := store.GetAccount(id)
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.Equal(t, id, account.Id, "account id should match")
|
||||||
|
|
||||||
|
_, err = store.GetAccount("non-existing-account")
|
||||||
|
assert.Error(t, err)
|
||||||
|
parsedErr, ok := status.FromError(err)
|
||||||
|
require.True(t, ok)
|
||||||
|
require.Equal(t, status.NotFound, parsedErr.Type(), "should return not found error")
|
||||||
|
}
|
||||||
|
|
||||||
func TestSqlite_SavePeerStatus(t *testing.T) {
|
func TestSqlite_SavePeerStatus(t *testing.T) {
|
||||||
if runtime.GOOS == "windows" {
|
if runtime.GOOS == "windows" {
|
||||||
t.Skip("The SQLite store is not properly supported by Windows yet")
|
t.Skip("The SQLite store is not properly supported by Windows yet")
|
||||||
@@ -189,6 +210,9 @@ func TestSqlite_SavePeerStatus(t *testing.T) {
|
|||||||
newStatus := nbpeer.PeerStatus{Connected: true, LastSeen: time.Now().UTC()}
|
newStatus := nbpeer.PeerStatus{Connected: true, LastSeen: time.Now().UTC()}
|
||||||
err = store.SavePeerStatus(account.Id, "non-existing-peer", newStatus)
|
err = store.SavePeerStatus(account.Id, "non-existing-peer", newStatus)
|
||||||
assert.Error(t, err)
|
assert.Error(t, err)
|
||||||
|
parsedErr, ok := status.FromError(err)
|
||||||
|
require.True(t, ok)
|
||||||
|
require.Equal(t, status.NotFound, parsedErr.Type(), "should return not found error")
|
||||||
|
|
||||||
// save new status of existing peer
|
// save new status of existing peer
|
||||||
account.Peers["testpeer"] = &nbpeer.Peer{
|
account.Peers["testpeer"] = &nbpeer.Peer{
|
||||||
@@ -255,6 +279,13 @@ func TestSqlite_SavePeerLocation(t *testing.T) {
|
|||||||
|
|
||||||
actual := account.Peers[peer.ID].Location
|
actual := account.Peers[peer.ID].Location
|
||||||
assert.Equal(t, peer.Location, actual)
|
assert.Equal(t, peer.Location, actual)
|
||||||
|
|
||||||
|
peer.ID = "non-existing-peer"
|
||||||
|
err = store.SavePeerLocation(account.Id, peer)
|
||||||
|
assert.Error(t, err)
|
||||||
|
parsedErr, ok := status.FromError(err)
|
||||||
|
require.True(t, ok)
|
||||||
|
require.Equal(t, status.NotFound, parsedErr.Type(), "should return not found error")
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestSqlite_TestGetAccountByPrivateDomain(t *testing.T) {
|
func TestSqlite_TestGetAccountByPrivateDomain(t *testing.T) {
|
||||||
@@ -272,6 +303,9 @@ func TestSqlite_TestGetAccountByPrivateDomain(t *testing.T) {
|
|||||||
|
|
||||||
_, err = store.GetAccountByPrivateDomain("missing-domain.com")
|
_, err = store.GetAccountByPrivateDomain("missing-domain.com")
|
||||||
require.Error(t, err, "should return error on domain lookup")
|
require.Error(t, err, "should return error on domain lookup")
|
||||||
|
parsedErr, ok := status.FromError(err)
|
||||||
|
require.True(t, ok)
|
||||||
|
require.Equal(t, status.NotFound, parsedErr.Type(), "should return not found error")
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestSqlite_GetTokenIDByHashedToken(t *testing.T) {
|
func TestSqlite_GetTokenIDByHashedToken(t *testing.T) {
|
||||||
@@ -287,6 +321,12 @@ func TestSqlite_GetTokenIDByHashedToken(t *testing.T) {
|
|||||||
token, err := store.GetTokenIDByHashedToken(hashed)
|
token, err := store.GetTokenIDByHashedToken(hashed)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
require.Equal(t, id, token)
|
require.Equal(t, id, token)
|
||||||
|
|
||||||
|
_, err = store.GetTokenIDByHashedToken("non-existing-hash")
|
||||||
|
require.Error(t, err)
|
||||||
|
parsedErr, ok := status.FromError(err)
|
||||||
|
require.True(t, ok)
|
||||||
|
require.Equal(t, status.NotFound, parsedErr.Type(), "should return not found error")
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestSqlite_GetUserByTokenID(t *testing.T) {
|
func TestSqlite_GetUserByTokenID(t *testing.T) {
|
||||||
@@ -301,6 +341,12 @@ func TestSqlite_GetUserByTokenID(t *testing.T) {
|
|||||||
user, err := store.GetUserByTokenID(id)
|
user, err := store.GetUserByTokenID(id)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
require.Equal(t, id, user.PATs[id].ID)
|
require.Equal(t, id, user.PATs[id].ID)
|
||||||
|
|
||||||
|
_, err = store.GetUserByTokenID("non-existing-id")
|
||||||
|
require.Error(t, err)
|
||||||
|
parsedErr, ok := status.FromError(err)
|
||||||
|
require.True(t, ok)
|
||||||
|
require.Equal(t, status.NotFound, parsedErr.Type(), "should return not found error")
|
||||||
}
|
}
|
||||||
|
|
||||||
func newSqliteStore(t *testing.T) *SqliteStore {
|
func newSqliteStore(t *testing.T) *SqliteStore {
|
||||||
@@ -347,29 +393,3 @@ func newAccount(store Store, id int) error {
|
|||||||
|
|
||||||
return store.SaveAccount(account)
|
return store.SaveAccount(account)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestSqliteStore_CalculateUsageStats(t *testing.T) {
|
|
||||||
store := newSqliteStoreFromFile(t, "testdata/store_stats.json")
|
|
||||||
t.Cleanup(func() {
|
|
||||||
require.NoError(t, store.Close())
|
|
||||||
})
|
|
||||||
|
|
||||||
startDate := time.Date(2024, time.February, 1, 0, 0, 0, 0, time.UTC)
|
|
||||||
endDate := startDate.AddDate(0, 1, 0).Add(-time.Nanosecond)
|
|
||||||
|
|
||||||
stats1, err := store.CalculateUsageStats(context.TODO(), "account-1", startDate, endDate)
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
assert.Equal(t, int64(2), stats1.ActiveUsers)
|
|
||||||
assert.Equal(t, int64(4), stats1.TotalUsers)
|
|
||||||
assert.Equal(t, int64(3), stats1.ActivePeers)
|
|
||||||
assert.Equal(t, int64(7), stats1.TotalPeers)
|
|
||||||
|
|
||||||
stats2, err := store.CalculateUsageStats(context.TODO(), "account-2", startDate, endDate)
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
assert.Equal(t, int64(1), stats2.ActiveUsers)
|
|
||||||
assert.Equal(t, int64(2), stats2.TotalUsers)
|
|
||||||
assert.Equal(t, int64(1), stats2.ActivePeers)
|
|
||||||
assert.Equal(t, int64(2), stats2.TotalPeers)
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
package server
|
package server
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
@@ -42,7 +41,6 @@ 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
|
||||||
CalculateUsageStats(ctx context.Context, accountID string, start time.Time, end time.Time) (*AccountUsageStats, error)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type StoreEngine string
|
type StoreEngine string
|
||||||
|
|||||||
161
management/server/testdata/store_stats.json
vendored
161
management/server/testdata/store_stats.json
vendored
@@ -1,161 +0,0 @@
|
|||||||
{
|
|
||||||
"Accounts": {
|
|
||||||
"account-1": {
|
|
||||||
"Id": "account-1",
|
|
||||||
"Domain": "example.com",
|
|
||||||
"Network": {
|
|
||||||
"Id": "af1c8024-ha40-4ce2-9418-34653101fc3c",
|
|
||||||
"Net": {
|
|
||||||
"IP": "100.64.0.0",
|
|
||||||
"Mask": "//8AAA=="
|
|
||||||
},
|
|
||||||
"Dns": null
|
|
||||||
},
|
|
||||||
"Users": {
|
|
||||||
"user-1-account-1": {
|
|
||||||
"Id": "user-1-account-1"
|
|
||||||
},
|
|
||||||
"user-2-account-1": {
|
|
||||||
"Id": "user-2-account-1"
|
|
||||||
},
|
|
||||||
"user-3-account-1": {
|
|
||||||
"Id": "user-3-account-1"
|
|
||||||
},
|
|
||||||
"user-4-account-1": {
|
|
||||||
"Id": "user-4-account-1"
|
|
||||||
},
|
|
||||||
"user-5-account-1": {
|
|
||||||
"Id": "user-5-account-1",
|
|
||||||
"IsServiceUser": true
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"Peers": {
|
|
||||||
"peer-1-account-1": {
|
|
||||||
"ID": "peer-1-account-1",
|
|
||||||
"UserID": "user-1-account-1",
|
|
||||||
"Status": {
|
|
||||||
"LastSeen": "2024-01-01T00:00:00Z"
|
|
||||||
},
|
|
||||||
"Name": "Peer One",
|
|
||||||
"Meta": {
|
|
||||||
"Hostname": "peer1-host"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"peer-2-account-1": {
|
|
||||||
"ID": "peer-2-account-1",
|
|
||||||
"UserID": "user-2-account-1",
|
|
||||||
"Status": {
|
|
||||||
"LastSeen": "2024-02-29T23:59:59Z"
|
|
||||||
},
|
|
||||||
"Name": "Peer Two",
|
|
||||||
"Meta": {
|
|
||||||
"Hostname": "peer2-host"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"peer-3-account-1": {
|
|
||||||
"ID": "peer-3-account-1",
|
|
||||||
"UserID": "user-2-account-1",
|
|
||||||
"Status": {
|
|
||||||
"LastSeen": "2024-02-01T12:00:00Z"
|
|
||||||
},
|
|
||||||
"Name": "Peer Three",
|
|
||||||
"Meta": {
|
|
||||||
"Hostname": "peer3-host"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"peer-4-account-1": {
|
|
||||||
"ID": "peer-4-account-1",
|
|
||||||
"UserID": "user-3-account-1",
|
|
||||||
"Status": {
|
|
||||||
"LastSeen": "2024-02-08T12:00:00Z"
|
|
||||||
},
|
|
||||||
"Name": "Peer Four",
|
|
||||||
"Meta": {
|
|
||||||
"Hostname": "peer4-host"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"peer-5-account-1": {
|
|
||||||
"ID": "peer-5-account-1",
|
|
||||||
"UserID": "user-3-account-1",
|
|
||||||
"Status": {
|
|
||||||
"LastSeen": "2023-06-01T12:00:00Z"
|
|
||||||
},
|
|
||||||
"Name": "Peer Five",
|
|
||||||
"Meta": {
|
|
||||||
"Hostname": "peer5-host"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"peer-6-account-1": {
|
|
||||||
"ID": "peer-6-account-1",
|
|
||||||
"UserID": "user-4-account-1",
|
|
||||||
"Status": {
|
|
||||||
"LastSeen": "2024-01-31T23:59:59Z"
|
|
||||||
},
|
|
||||||
"Name": "Peer Six",
|
|
||||||
"Meta": {
|
|
||||||
"Hostname": "peer6-host"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"peer-7-account-1": {
|
|
||||||
"ID": "peer-7-account-1",
|
|
||||||
"UserID": "user-4-account-1",
|
|
||||||
"Status": {
|
|
||||||
"LastSeen": "2024-03-01T00:00:00Z"
|
|
||||||
},
|
|
||||||
"Name": "Peer Seven",
|
|
||||||
"Meta": {
|
|
||||||
"Hostname": "peer7-host"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"account-2": {
|
|
||||||
"Id": "account-2",
|
|
||||||
"Domain": "example.org",
|
|
||||||
"Network": {
|
|
||||||
"Id": "af1c8024-ha40-4ce2-9418-34653101fc3c",
|
|
||||||
"Net": {
|
|
||||||
"IP": "100.64.0.0",
|
|
||||||
"Mask": "//8AAA=="
|
|
||||||
},
|
|
||||||
"Dns": null
|
|
||||||
},
|
|
||||||
"Users": {
|
|
||||||
"user-1-account-2": {
|
|
||||||
"Id": "user-1-account-2"
|
|
||||||
},
|
|
||||||
"user-2-account-2": {
|
|
||||||
"Id": "user-1-account-2"
|
|
||||||
},
|
|
||||||
"user-3-account-2": {
|
|
||||||
"Id": "user-3-account-2",
|
|
||||||
"IsServiceUser": true
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"Peers": {
|
|
||||||
"peer-1-account-2": {
|
|
||||||
"ID": "peer-1-account-2",
|
|
||||||
"UserID": "user-1-account-2",
|
|
||||||
"Status": {
|
|
||||||
"LastSeen": "2023-08-30T12:00:00Z"
|
|
||||||
},
|
|
||||||
"Name": "Peer One",
|
|
||||||
"Meta": {
|
|
||||||
"Hostname": "peer1-host"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"peer-2-account-2": {
|
|
||||||
"ID": "peer-2-account-2",
|
|
||||||
"UserID": "user-1-account-2",
|
|
||||||
"Status": {
|
|
||||||
"LastSeen": "2024-02-08T12:00:00Z"
|
|
||||||
},
|
|
||||||
"Name": "Peer Two",
|
|
||||||
"Meta": {
|
|
||||||
"Hostname": "peer2-host"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Reference in New Issue
Block a user