2022-03-08 14:47:55 +01:00
package cmd
import (
"context"
2023-02-23 20:13:19 +01:00
"encoding/json"
2022-05-12 11:17:24 +02:00
"fmt"
2022-12-01 11:48:13 +01:00
"net"
"net/netip"
2024-04-26 17:20:10 +02:00
"os"
"runtime"
2022-12-01 11:48:13 +01:00
"sort"
"strings"
2023-02-24 19:01:54 +01:00
"time"
"github.com/spf13/cobra"
"google.golang.org/grpc/status"
2023-02-24 19:14:22 +01:00
"gopkg.in/yaml.v3"
2022-12-01 11:48:13 +01:00
2024-04-26 17:20:10 +02:00
"github.com/netbirdio/netbird/client/anonymize"
2022-07-05 19:47:50 +02:00
"github.com/netbirdio/netbird/client/internal"
"github.com/netbirdio/netbird/client/internal/peer"
"github.com/netbirdio/netbird/client/proto"
2022-03-26 12:08:54 +01:00
"github.com/netbirdio/netbird/util"
2023-03-15 07:54:51 +01:00
"github.com/netbirdio/netbird/version"
2022-07-05 19:47:50 +02:00
)
2022-03-08 14:47:55 +01:00
2023-02-24 19:01:54 +01:00
type peerStateDetailOutput struct {
2024-01-22 12:20:24 +01:00
FQDN string ` json:"fqdn" yaml:"fqdn" `
IP string ` json:"netbirdIp" yaml:"netbirdIp" `
PubKey string ` json:"publicKey" yaml:"publicKey" `
Status string ` json:"status" yaml:"status" `
LastStatusUpdate time . Time ` json:"lastStatusUpdate" yaml:"lastStatusUpdate" `
ConnType string ` json:"connectionType" yaml:"connectionType" `
Direct bool ` json:"direct" yaml:"direct" `
IceCandidateType iceCandidateType ` json:"iceCandidateType" yaml:"iceCandidateType" `
IceCandidateEndpoint iceCandidateType ` json:"iceCandidateEndpoint" yaml:"iceCandidateEndpoint" `
LastWireguardHandshake time . Time ` json:"lastWireguardHandshake" yaml:"lastWireguardHandshake" `
TransferReceived int64 ` json:"transferReceived" yaml:"transferReceived" `
TransferSent int64 ` json:"transferSent" yaml:"transferSent" `
2024-03-20 11:18:34 +01:00
Latency time . Duration ` json:"latency" yaml:"latency" `
2024-02-24 12:41:13 +01:00
RosenpassEnabled bool ` json:"quantumResistance" yaml:"quantumResistance" `
2024-03-12 19:06:16 +01:00
Routes [ ] string ` json:"routes" yaml:"routes" `
2023-02-24 19:01:54 +01:00
}
type peersStateOutput struct {
Total int ` json:"total" yaml:"total" `
Connected int ` json:"connected" yaml:"connected" `
Details [ ] peerStateDetailOutput ` json:"details" yaml:"details" `
}
type signalStateOutput struct {
URL string ` json:"url" yaml:"url" `
Connected bool ` json:"connected" yaml:"connected" `
2024-01-22 12:20:24 +01:00
Error string ` json:"error" yaml:"error" `
2023-02-24 19:01:54 +01:00
}
type managementStateOutput struct {
URL string ` json:"url" yaml:"url" `
Connected bool ` json:"connected" yaml:"connected" `
2024-01-22 12:20:24 +01:00
Error string ` json:"error" yaml:"error" `
}
type relayStateOutputDetail struct {
URI string ` json:"uri" yaml:"uri" `
Available bool ` json:"available" yaml:"available" `
Error string ` json:"error" yaml:"error" `
}
type relayStateOutput struct {
Total int ` json:"total" yaml:"total" `
Available int ` json:"available" yaml:"available" `
Details [ ] relayStateOutputDetail ` json:"details" yaml:"details" `
2023-02-24 19:01:54 +01:00
}
2023-02-27 17:06:20 +01:00
type iceCandidateType struct {
Local string ` json:"local" yaml:"local" `
Remote string ` json:"remote" yaml:"remote" `
}
2024-03-12 19:06:16 +01:00
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" `
}
2023-02-24 19:01:54 +01:00
type statusOutputOverview struct {
2024-03-12 19:06:16 +01:00
Peers peersStateOutput ` json:"peers" yaml:"peers" `
CliVersion string ` json:"cliVersion" yaml:"cliVersion" `
DaemonVersion string ` json:"daemonVersion" yaml:"daemonVersion" `
ManagementState managementStateOutput ` json:"management" yaml:"management" `
SignalState signalStateOutput ` json:"signal" yaml:"signal" `
Relays relayStateOutput ` json:"relays" yaml:"relays" `
IP string ` json:"netbirdIp" yaml:"netbirdIp" `
PubKey string ` json:"publicKey" yaml:"publicKey" `
KernelInterface bool ` json:"usesKernelInterface" yaml:"usesKernelInterface" `
FQDN string ` json:"fqdn" yaml:"fqdn" `
RosenpassEnabled bool ` json:"quantumResistance" yaml:"quantumResistance" `
RosenpassPermissive bool ` json:"quantumResistancePermissive" yaml:"quantumResistancePermissive" `
Routes [ ] string ` json:"routes" yaml:"routes" `
NSServerGroups [ ] nsServerGroupStateOutput ` json:"dnsServers" yaml:"dnsServers" `
2023-02-24 19:01:54 +01:00
}
2022-07-05 19:47:50 +02:00
var (
2023-12-14 11:18:43 +01:00
detailFlag bool
ipv4Flag bool
jsonFlag bool
yamlFlag bool
ipsFilter [ ] string
prefixNamesFilter [ ] string
statusFilter string
ipsFilterMap map [ string ] struct { }
prefixNamesFilterMap map [ string ] struct { }
2022-03-08 14:47:55 +01:00
)
var statusCmd = & cobra . Command {
Use : "status" ,
2022-05-22 18:53:47 +02:00
Short : "status of the Netbird Service" ,
2023-02-23 20:13:19 +01:00
RunE : statusFunc ,
}
2022-03-10 18:14:07 +01:00
2023-02-23 20:13:19 +01:00
func init ( ) {
ipsFilterMap = make ( map [ string ] struct { } )
2023-12-14 11:18:43 +01:00
prefixNamesFilterMap = make ( map [ string ] struct { } )
2023-02-23 20:13:19 +01:00
statusCmd . PersistentFlags ( ) . BoolVarP ( & detailFlag , "detail" , "d" , false , "display detailed status information in human-readable format" )
statusCmd . PersistentFlags ( ) . BoolVar ( & jsonFlag , "json" , false , "display detailed status information in json format" )
statusCmd . PersistentFlags ( ) . BoolVar ( & yamlFlag , "yaml" , false , "display detailed status information in yaml format" )
statusCmd . PersistentFlags ( ) . BoolVar ( & ipv4Flag , "ipv4" , false , "display only NetBird IPv4 of this peer, e.g., --ipv4 will output 100.64.0.33" )
statusCmd . MarkFlagsMutuallyExclusive ( "detail" , "json" , "yaml" , "ipv4" )
statusCmd . PersistentFlags ( ) . StringSliceVar ( & ipsFilter , "filter-by-ips" , [ ] string { } , "filters the detailed output by a list of one or more IPs, e.g., --filter-by-ips 100.64.0.100,100.64.0.200" )
2023-12-14 11:18:43 +01:00
statusCmd . PersistentFlags ( ) . StringSliceVar ( & prefixNamesFilter , "filter-by-names" , [ ] string { } , "filters the detailed output by a list of one or more peer FQDN or hostnames, e.g., --filter-by-names peer-a,peer-b.netbird.cloud" )
2023-02-23 20:13:19 +01:00
statusCmd . PersistentFlags ( ) . StringVar ( & statusFilter , "filter-by-status" , "" , "filters the detailed output by connection status(connected|disconnected), e.g., --filter-by-status connected" )
}
2022-05-22 18:53:47 +02:00
2023-02-23 20:13:19 +01:00
func statusFunc ( cmd * cobra . Command , args [ ] string ) error {
SetFlagsFromEnvVars ( rootCmd )
2022-07-05 19:47:50 +02:00
2023-02-23 20:13:19 +01:00
cmd . SetOut ( cmd . OutOrStdout ( ) )
2022-03-10 18:14:07 +01:00
2023-02-23 20:13:19 +01:00
err := parseFilters ( )
if err != nil {
return err
}
2022-03-08 14:47:55 +01:00
2023-02-23 20:13:19 +01:00
err = util . InitLog ( logLevel , "console" )
if err != nil {
return fmt . Errorf ( "failed initializing log %v" , err )
}
2022-03-08 14:47:55 +01:00
2024-04-26 17:20:10 +02:00
ctx := internal . CtxInitState ( cmd . Context ( ) )
2022-05-12 11:17:24 +02:00
2024-04-26 17:20:10 +02:00
resp , err := getStatus ( ctx )
2023-02-23 20:13:19 +01:00
if err != nil {
2023-09-04 17:03:44 +02:00
return err
2023-02-23 20:13:19 +01:00
}
2022-07-05 19:47:50 +02:00
2023-02-23 20:13:19 +01:00
if resp . GetStatus ( ) == string ( internal . StatusNeedsLogin ) || resp . GetStatus ( ) == string ( internal . StatusLoginFailed ) {
2023-02-24 19:01:54 +01:00
cmd . Printf ( "Daemon status: %s\n\n" +
2023-02-23 20:13:19 +01:00
"Run UP command to log in with SSO (interactive login):\n\n" +
" netbird up \n\n" +
"If you are running a self-hosted version and no SSO provider has been configured in your Management Server,\n" +
"you can use a setup-key:\n\n netbird up --management-url <YOUR_MANAGEMENT_URL> --setup-key <YOUR_SETUP_KEY>\n\n" +
2023-08-17 12:27:04 +02:00
"More info: https://docs.netbird.io/how-to/register-machines-using-setup-keys\n\n" ,
2023-02-24 19:01:54 +01:00
resp . GetStatus ( ) ,
2023-02-23 20:13:19 +01:00
)
2022-03-08 14:47:55 +01:00
return nil
2023-02-23 20:13:19 +01:00
}
2022-07-05 19:47:50 +02:00
2023-02-24 19:01:54 +01:00
if ipv4Flag {
cmd . Print ( parseInterfaceIP ( resp . GetFullStatus ( ) . GetLocalPeerState ( ) . GetIP ( ) ) )
return nil
}
2023-02-27 17:06:20 +01:00
outputInformationHolder := convertToStatusOutputOverview ( resp )
2023-02-23 20:13:19 +01:00
2023-09-04 17:03:44 +02:00
var statusOutputString string
2023-02-27 17:06:20 +01:00
switch {
case detailFlag :
statusOutputString = parseToFullDetailSummary ( outputInformationHolder )
case jsonFlag :
statusOutputString , err = parseToJSON ( outputInformationHolder )
case yamlFlag :
statusOutputString , err = parseToYAML ( outputInformationHolder )
default :
2024-03-12 19:06:16 +01:00
statusOutputString = parseGeneralSummary ( outputInformationHolder , false , false , false )
2023-02-27 17:06:20 +01:00
}
if err != nil {
return err
2023-02-23 20:13:19 +01:00
}
cmd . Print ( statusOutputString )
return nil
2022-07-05 19:47:50 +02:00
}
2024-04-26 17:20:10 +02:00
func getStatus ( ctx context . Context ) ( * proto . StatusResponse , error ) {
2023-02-24 19:01:54 +01:00
conn , err := DialClientGRPCServer ( ctx , daemonAddr )
if err != nil {
return nil , fmt . Errorf ( "failed to connect to daemon error: %v\n" +
"If the daemon is not running please run: " +
"\nnetbird service install \nnetbird service start\n" , err )
}
defer conn . Close ( )
2024-04-26 17:20:10 +02:00
resp , err := proto . NewDaemonServiceClient ( conn ) . Status ( ctx , & proto . StatusRequest { GetFullPeerStatus : true } )
2023-02-24 19:01:54 +01:00
if err != nil {
return nil , fmt . Errorf ( "status failed: %v" , status . Convert ( err ) . Message ( ) )
}
return resp , nil
}
2022-07-05 19:47:50 +02:00
func parseFilters ( ) error {
2023-12-14 11:18:43 +01:00
2022-07-05 19:47:50 +02:00
switch strings . ToLower ( statusFilter ) {
case "" , "disconnected" , "connected" :
2023-12-14 11:18:43 +01:00
if strings . ToLower ( statusFilter ) != "" {
enableDetailFlagWhenFilterFlag ( )
}
2022-07-05 19:47:50 +02:00
default :
return fmt . Errorf ( "wrong status filter, should be one of connected|disconnected, got: %s" , statusFilter )
}
if len ( ipsFilter ) > 0 {
for _ , addr := range ipsFilter {
_ , err := netip . ParseAddr ( addr )
if err != nil {
return fmt . Errorf ( "got an invalid IP address in the filter: address %s, error %s" , addr , err )
}
ipsFilterMap [ addr ] = struct { } { }
2023-12-14 11:18:43 +01:00
enableDetailFlagWhenFilterFlag ( )
2022-07-05 19:47:50 +02:00
}
}
2023-12-14 11:18:43 +01:00
if len ( prefixNamesFilter ) > 0 {
for _ , name := range prefixNamesFilter {
prefixNamesFilterMap [ strings . ToLower ( name ) ] = struct { } { }
}
enableDetailFlagWhenFilterFlag ( )
}
2022-07-05 19:47:50 +02:00
return nil
}
2023-12-14 11:18:43 +01:00
func enableDetailFlagWhenFilterFlag ( ) {
if ! detailFlag && ! jsonFlag && ! yamlFlag {
detailFlag = true
}
}
2023-02-24 19:01:54 +01:00
func convertToStatusOutputOverview ( resp * proto . StatusResponse ) statusOutputOverview {
pbFullStatus := resp . GetFullStatus ( )
2022-07-07 13:54:47 +02:00
managementState := pbFullStatus . GetManagementState ( )
2023-02-24 19:01:54 +01:00
managementOverview := managementStateOutput {
URL : managementState . GetURL ( ) ,
Connected : managementState . GetConnected ( ) ,
2024-01-22 12:20:24 +01:00
Error : managementState . Error ,
2023-02-24 19:01:54 +01:00
}
2022-07-05 19:47:50 +02:00
2022-07-07 13:54:47 +02:00
signalState := pbFullStatus . GetSignalState ( )
2023-02-24 19:01:54 +01:00
signalOverview := signalStateOutput {
URL : signalState . GetURL ( ) ,
Connected : signalState . GetConnected ( ) ,
2024-01-22 12:20:24 +01:00
Error : signalState . Error ,
2023-02-24 19:01:54 +01:00
}
2022-07-05 19:47:50 +02:00
2024-01-22 12:20:24 +01:00
relayOverview := mapRelays ( pbFullStatus . GetRelays ( ) )
2023-02-24 19:01:54 +01:00
peersOverview := mapPeers ( resp . GetFullStatus ( ) . GetPeers ( ) )
2022-07-05 19:47:50 +02:00
2023-02-24 19:01:54 +01:00
overview := statusOutputOverview {
2024-02-24 12:41:13 +01:00
Peers : peersOverview ,
CliVersion : version . NetbirdVersion ( ) ,
DaemonVersion : resp . GetDaemonVersion ( ) ,
ManagementState : managementOverview ,
SignalState : signalOverview ,
Relays : relayOverview ,
IP : pbFullStatus . GetLocalPeerState ( ) . GetIP ( ) ,
PubKey : pbFullStatus . GetLocalPeerState ( ) . GetPubKey ( ) ,
KernelInterface : pbFullStatus . GetLocalPeerState ( ) . GetKernelInterface ( ) ,
FQDN : pbFullStatus . GetLocalPeerState ( ) . GetFqdn ( ) ,
RosenpassEnabled : pbFullStatus . GetLocalPeerState ( ) . GetRosenpassEnabled ( ) ,
RosenpassPermissive : pbFullStatus . GetLocalPeerState ( ) . GetRosenpassPermissive ( ) ,
2024-03-12 19:06:16 +01:00
Routes : pbFullStatus . GetLocalPeerState ( ) . GetRoutes ( ) ,
NSServerGroups : mapNSGroups ( pbFullStatus . GetDnsServers ( ) ) ,
2023-02-24 19:01:54 +01:00
}
2024-04-26 17:20:10 +02:00
if anonymizeFlag {
anonymizer := anonymize . NewAnonymizer ( anonymize . DefaultAddresses ( ) )
anonymizeOverview ( anonymizer , & overview )
}
2023-02-24 19:01:54 +01:00
return overview
}
2024-01-22 12:20:24 +01:00
func mapRelays ( relays [ ] * proto . RelayState ) relayStateOutput {
var relayStateDetail [ ] relayStateOutputDetail
var relaysAvailable int
for _ , relay := range relays {
available := relay . GetAvailable ( )
relayStateDetail = append ( relayStateDetail ,
relayStateOutputDetail {
URI : relay . URI ,
Available : available ,
Error : relay . GetError ( ) ,
} ,
)
if available {
relaysAvailable ++
}
}
return relayStateOutput {
Total : len ( relays ) ,
Available : relaysAvailable ,
Details : relayStateDetail ,
}
}
2024-03-12 19:06:16 +01:00
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
}
2023-02-24 19:01:54 +01:00
func mapPeers ( peers [ ] * proto . PeerState ) peersStateOutput {
var peersStateDetail [ ] peerStateDetailOutput
2023-02-27 15:14:41 +01:00
localICE := ""
remoteICE := ""
2024-01-22 12:20:24 +01:00
localICEEndpoint := ""
remoteICEEndpoint := ""
2023-02-27 15:14:41 +01:00
connType := ""
2023-02-24 19:01:54 +01:00
peersConnected := 0
2024-01-22 12:20:24 +01:00
lastHandshake := time . Time { }
transferReceived := int64 ( 0 )
transferSent := int64 ( 0 )
2023-02-24 19:01:54 +01:00
for _ , pbPeerState := range peers {
isPeerConnected := pbPeerState . ConnStatus == peer . StatusConnected . String ( )
if skipDetailByFilters ( pbPeerState , isPeerConnected ) {
continue
}
if isPeerConnected {
2023-11-27 16:40:02 +01:00
peersConnected ++
2023-02-24 19:01:54 +01:00
localICE = pbPeerState . GetLocalIceCandidateType ( )
remoteICE = pbPeerState . GetRemoteIceCandidateType ( )
2024-01-22 12:20:24 +01:00
localICEEndpoint = pbPeerState . GetLocalIceCandidateEndpoint ( )
remoteICEEndpoint = pbPeerState . GetRemoteIceCandidateEndpoint ( )
2023-02-24 19:01:54 +01:00
connType = "P2P"
if pbPeerState . Relayed {
connType = "Relayed"
}
2024-01-22 12:20:24 +01:00
lastHandshake = pbPeerState . GetLastWireguardHandshake ( ) . AsTime ( ) . Local ( )
transferReceived = pbPeerState . GetBytesRx ( )
transferSent = pbPeerState . GetBytesTx ( )
2023-02-24 19:01:54 +01:00
}
2022-07-05 19:47:50 +02:00
2022-07-07 13:54:47 +02:00
timeLocal := pbPeerState . GetConnStatusUpdate ( ) . AsTime ( ) . Local ( )
2023-02-24 19:01:54 +01:00
peerState := peerStateDetailOutput {
2023-02-27 17:06:20 +01:00
IP : pbPeerState . GetIP ( ) ,
PubKey : pbPeerState . GetPubKey ( ) ,
Status : pbPeerState . GetConnStatus ( ) ,
2023-04-27 16:02:00 +02:00
LastStatusUpdate : timeLocal ,
2023-02-27 17:06:20 +01:00
ConnType : connType ,
Direct : pbPeerState . GetDirect ( ) ,
IceCandidateType : iceCandidateType {
Local : localICE ,
Remote : remoteICE ,
} ,
2024-01-22 12:20:24 +01:00
IceCandidateEndpoint : iceCandidateType {
Local : localICEEndpoint ,
Remote : remoteICEEndpoint ,
} ,
FQDN : pbPeerState . GetFqdn ( ) ,
LastWireguardHandshake : lastHandshake ,
TransferReceived : transferReceived ,
TransferSent : transferSent ,
2024-03-20 11:18:34 +01:00
Latency : pbPeerState . GetLatency ( ) . AsDuration ( ) ,
2024-02-24 12:41:13 +01:00
RosenpassEnabled : pbPeerState . GetRosenpassEnabled ( ) ,
2024-03-12 19:06:16 +01:00
Routes : pbPeerState . GetRoutes ( ) ,
2022-07-05 19:47:50 +02:00
}
2023-02-24 19:01:54 +01:00
peersStateDetail = append ( peersStateDetail , peerState )
2022-07-05 19:47:50 +02:00
}
2023-02-27 15:34:17 +01:00
sortPeersByIP ( peersStateDetail )
2022-07-05 19:47:50 +02:00
2023-02-24 19:01:54 +01:00
peersOverview := peersStateOutput {
Total : len ( peersStateDetail ) ,
Connected : peersConnected ,
Details : peersStateDetail ,
}
return peersOverview
}
2023-02-27 15:34:17 +01:00
func sortPeersByIP ( peersStateDetail [ ] peerStateDetailOutput ) {
2023-02-24 19:01:54 +01:00
if len ( peersStateDetail ) > 0 {
sort . SliceStable ( peersStateDetail , func ( i , j int ) bool {
iAddr , _ := netip . ParseAddr ( peersStateDetail [ i ] . IP )
jAddr , _ := netip . ParseAddr ( peersStateDetail [ j ] . IP )
return iAddr . Compare ( jAddr ) == - 1
} )
}
2022-07-05 19:47:50 +02:00
}
2023-02-23 20:13:19 +01:00
func parseInterfaceIP ( interfaceIP string ) string {
2022-09-22 09:25:52 +02:00
ip , _ , err := net . ParseCIDR ( interfaceIP )
if err != nil {
return ""
}
2023-02-23 20:13:19 +01:00
return fmt . Sprintf ( "%s\n" , ip )
}
2022-09-22 09:25:52 +02:00
2023-02-27 15:34:17 +01:00
func parseToJSON ( overview statusOutputOverview ) ( string , error ) {
2023-02-24 19:01:54 +01:00
jsonBytes , err := json . Marshal ( overview )
if err != nil {
return "" , fmt . Errorf ( "json marshal failed" )
}
2023-02-23 20:13:19 +01:00
return string ( jsonBytes ) , err
}
2022-09-22 09:25:52 +02:00
2023-02-27 15:34:17 +01:00
func parseToYAML ( overview statusOutputOverview ) ( string , error ) {
2023-02-24 19:01:54 +01:00
yamlBytes , err := yaml . Marshal ( overview )
if err != nil {
return "" , fmt . Errorf ( "yaml marshal failed" )
2022-07-05 19:47:50 +02:00
}
2023-02-24 19:01:54 +01:00
return string ( yamlBytes ) , nil
2023-02-23 20:13:19 +01:00
}
2024-03-12 19:06:16 +01:00
func parseGeneralSummary ( overview statusOutputOverview , showURL bool , showRelays bool , showNameServers bool ) string {
2024-01-22 12:20:24 +01:00
var managementConnString string
2023-02-24 19:01:54 +01:00
if overview . ManagementState . Connected {
2022-07-05 19:47:50 +02:00
managementConnString = "Connected"
2023-02-27 15:34:17 +01:00
if showURL {
2023-02-24 19:01:54 +01:00
managementConnString = fmt . Sprintf ( "%s to %s" , managementConnString , overview . ManagementState . URL )
}
2024-01-22 12:20:24 +01:00
} else {
managementConnString = "Disconnected"
if overview . ManagementState . Error != "" {
managementConnString = fmt . Sprintf ( "%s, reason: %s" , managementConnString , overview . ManagementState . Error )
}
2022-07-05 19:47:50 +02:00
}
2024-01-22 12:20:24 +01:00
var signalConnString string
2023-02-24 19:01:54 +01:00
if overview . SignalState . Connected {
2022-07-05 19:47:50 +02:00
signalConnString = "Connected"
2023-02-27 15:34:17 +01:00
if showURL {
2023-02-24 19:01:54 +01:00
signalConnString = fmt . Sprintf ( "%s to %s" , signalConnString , overview . SignalState . URL )
}
2024-01-22 12:20:24 +01:00
} else {
signalConnString = "Disconnected"
if overview . SignalState . Error != "" {
signalConnString = fmt . Sprintf ( "%s, reason: %s" , signalConnString , overview . SignalState . Error )
}
2022-07-05 19:47:50 +02:00
}
2023-02-27 15:14:41 +01:00
interfaceTypeString := "Userspace"
interfaceIP := overview . IP
if overview . KernelInterface {
interfaceTypeString = "Kernel"
} else if overview . IP == "" {
interfaceTypeString = "N/A"
interfaceIP = "N/A"
}
2024-03-12 19:06:16 +01:00
var relaysString string
2024-01-22 12:20:24 +01:00
if showRelays {
for _ , relay := range overview . Relays . Details {
available := "Available"
reason := ""
if ! relay . Available {
available = "Unavailable"
reason = fmt . Sprintf ( ", reason: %s" , relay . Error )
}
2024-03-12 19:06:16 +01:00
relaysString += fmt . Sprintf ( "\n [%s] is %s%s" , relay . URI , available , reason )
2024-01-22 12:20:24 +01:00
}
} else {
2024-03-12 19:06:16 +01:00
relaysString = fmt . Sprintf ( "%d/%d Available" , overview . Relays . Available , overview . Relays . Total )
}
2024-01-22 12:20:24 +01:00
2024-03-12 19:06:16 +01:00
routes := "-"
if len ( overview . Routes ) > 0 {
sort . Strings ( overview . Routes )
routes = strings . Join ( overview . Routes , ", " )
2024-01-22 12:20:24 +01:00
}
2024-03-12 19:06:16 +01:00
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 ) )
}
2022-07-05 19:47:50 +02:00
2024-02-24 12:41:13 +01:00
rosenpassEnabledStatus := "false"
if overview . RosenpassEnabled {
rosenpassEnabledStatus = "true"
if overview . RosenpassPermissive {
rosenpassEnabledStatus = "true (permissive)" //nolint:gosec
}
}
2024-03-12 19:06:16 +01:00
peersCountString := fmt . Sprintf ( "%d/%d Connected" , overview . Peers . Connected , overview . Peers . Total )
2024-04-26 17:20:10 +02:00
goos := runtime . GOOS
goarch := runtime . GOARCH
goarm := ""
if goarch == "arm" {
goarm = fmt . Sprintf ( " (ARMv%s)" , os . Getenv ( "GOARM" ) )
}
2022-07-05 19:47:50 +02:00
summary := fmt . Sprintf (
2024-04-26 17:20:10 +02:00
"OS: %s\n" +
"Daemon version: %s\n" +
2022-08-01 12:42:45 +02:00
"CLI version: %s\n" +
2023-02-24 19:01:54 +01:00
"Management: %s\n" +
"Signal: %s\n" +
2024-01-22 12:20:24 +01:00
"Relays: %s\n" +
2024-03-12 19:06:16 +01:00
"Nameservers: %s\n" +
2023-02-27 17:06:20 +01:00
"FQDN: %s\n" +
2022-07-05 19:47:50 +02:00
"NetBird IP: %s\n" +
"Interface type: %s\n" +
2024-02-24 12:41:13 +01:00
"Quantum resistance: %s\n" +
2024-03-12 19:06:16 +01:00
"Routes: %s\n" +
2022-07-05 19:47:50 +02:00
"Peers count: %s\n" ,
2024-04-26 17:20:10 +02:00
fmt . Sprintf ( "%s/%s%s" , goos , goarch , goarm ) ,
2023-02-24 19:01:54 +01:00
overview . DaemonVersion ,
2023-03-15 07:54:51 +01:00
version . NetbirdVersion ( ) ,
2022-07-05 19:47:50 +02:00
managementConnString ,
signalConnString ,
2024-03-12 19:06:16 +01:00
relaysString ,
dnsServersString ,
2023-02-24 19:01:54 +01:00
overview . FQDN ,
2023-02-27 15:14:41 +01:00
interfaceIP ,
interfaceTypeString ,
2024-02-24 12:41:13 +01:00
rosenpassEnabledStatus ,
2024-03-12 19:06:16 +01:00
routes ,
2022-07-05 19:47:50 +02:00
peersCountString ,
)
return summary
}
2023-02-24 19:01:54 +01:00
func parseToFullDetailSummary ( overview statusOutputOverview ) string {
2024-02-24 12:41:13 +01:00
parsedPeersString := parsePeers ( overview . Peers , overview . RosenpassEnabled , overview . RosenpassPermissive )
2024-03-12 19:06:16 +01:00
summary := parseGeneralSummary ( overview , true , true , true )
2023-02-23 20:13:19 +01:00
return fmt . Sprintf (
"Peers detail:" +
"%s\n" +
"%s" ,
parsedPeersString ,
summary ,
)
}
2024-02-24 12:41:13 +01:00
func parsePeers ( peers peersStateOutput , rosenpassEnabled , rosenpassPermissive bool ) string {
2022-07-05 19:47:50 +02:00
var (
2023-02-23 20:13:19 +01:00
peersString = ""
2022-07-05 19:47:50 +02:00
)
2023-02-24 19:01:54 +01:00
for _ , peerState := range peers . Details {
2023-02-27 15:14:41 +01:00
localICE := "-"
2023-02-27 17:06:20 +01:00
if peerState . IceCandidateType . Local != "" {
localICE = peerState . IceCandidateType . Local
2023-02-27 15:14:41 +01:00
}
remoteICE := "-"
2023-02-27 17:06:20 +01:00
if peerState . IceCandidateType . Remote != "" {
remoteICE = peerState . IceCandidateType . Remote
2023-02-27 15:14:41 +01:00
}
2024-01-22 12:20:24 +01:00
localICEEndpoint := "-"
if peerState . IceCandidateEndpoint . Local != "" {
localICEEndpoint = peerState . IceCandidateEndpoint . Local
}
remoteICEEndpoint := "-"
if peerState . IceCandidateEndpoint . Remote != "" {
remoteICEEndpoint = peerState . IceCandidateEndpoint . Remote
}
2024-02-24 12:41:13 +01:00
rosenpassEnabledStatus := "false"
if rosenpassEnabled {
if peerState . RosenpassEnabled {
rosenpassEnabledStatus = "true"
} else {
if rosenpassPermissive {
rosenpassEnabledStatus = "false (remote didn't enable quantum resistance)"
} else {
rosenpassEnabledStatus = "false (connection won't work without a permissive mode)"
}
}
} else {
if peerState . RosenpassEnabled {
rosenpassEnabledStatus = "false (connection might not work without a remote permissive mode)"
}
2024-01-22 12:20:24 +01:00
}
2024-03-12 19:06:16 +01:00
routes := "-"
if len ( peerState . Routes ) > 0 {
sort . Strings ( peerState . Routes )
routes = strings . Join ( peerState . Routes , ", " )
}
2023-02-23 20:13:19 +01:00
peerString := fmt . Sprintf (
"\n %s:\n" +
" NetBird IP: %s\n" +
" Public key: %s\n" +
" Status: %s\n" +
" -- detail --\n" +
" Connection type: %s\n" +
" Direct: %t\n" +
" ICE candidate (Local/Remote): %s/%s\n" +
2024-01-22 12:20:24 +01:00
" ICE candidate endpoints (Local/Remote): %s/%s\n" +
" Last connection update: %s\n" +
2024-02-24 12:41:13 +01:00
" Last WireGuard handshake: %s\n" +
" Transfer status (received/sent) %s/%s\n" +
2024-03-12 19:06:16 +01:00
" Quantum resistance: %s\n" +
2024-03-20 11:18:34 +01:00
" Routes: %s\n" +
" Latency: %s\n" ,
2023-02-23 20:13:19 +01:00
peerState . FQDN ,
peerState . IP ,
peerState . PubKey ,
2023-02-27 17:06:20 +01:00
peerState . Status ,
2023-02-24 19:01:54 +01:00
peerState . ConnType ,
2023-02-23 20:13:19 +01:00
peerState . Direct ,
2023-02-27 15:14:41 +01:00
localICE ,
remoteICE ,
2024-01-22 12:20:24 +01:00
localICEEndpoint ,
remoteICEEndpoint ,
2024-04-26 17:20:10 +02:00
timeAgo ( peerState . LastStatusUpdate ) ,
timeAgo ( peerState . LastWireguardHandshake ) ,
2024-01-22 12:20:24 +01:00
toIEC ( peerState . TransferReceived ) ,
toIEC ( peerState . TransferSent ) ,
2024-02-24 12:41:13 +01:00
rosenpassEnabledStatus ,
2024-03-12 19:06:16 +01:00
routes ,
2024-03-20 11:18:34 +01:00
peerState . Latency . String ( ) ,
2023-02-23 20:13:19 +01:00
)
2022-07-05 19:47:50 +02:00
2023-11-27 16:40:02 +01:00
peersString += peerString
2022-07-05 19:47:50 +02:00
}
2023-02-23 20:13:19 +01:00
return peersString
2022-07-05 19:47:50 +02:00
}
2023-02-24 19:01:54 +01:00
func skipDetailByFilters ( peerState * proto . PeerState , isConnected bool ) bool {
2022-07-05 19:47:50 +02:00
statusEval := false
ipEval := false
2023-12-14 11:18:43 +01:00
nameEval := false
2022-07-05 19:47:50 +02:00
if statusFilter != "" {
lowerStatusFilter := strings . ToLower ( statusFilter )
if lowerStatusFilter == "disconnected" && isConnected {
statusEval = true
} else if lowerStatusFilter == "connected" && ! isConnected {
statusEval = true
}
}
if len ( ipsFilter ) > 0 {
_ , ok := ipsFilterMap [ peerState . IP ]
if ! ok {
ipEval = true
}
}
2023-12-14 11:18:43 +01:00
if len ( prefixNamesFilter ) > 0 {
for prefixNameFilter := range prefixNamesFilterMap {
if ! strings . HasPrefix ( peerState . Fqdn , prefixNameFilter ) {
nameEval = true
break
}
}
}
return statusEval || ipEval || nameEval
2022-07-05 19:47:50 +02:00
}
2024-01-22 12:20:24 +01:00
func toIEC ( b int64 ) string {
const unit = 1024
if b < unit {
return fmt . Sprintf ( "%d B" , b )
}
div , exp := int64 ( unit ) , 0
for n := b / unit ; n >= unit ; n /= unit {
div *= unit
exp ++
}
return fmt . Sprintf ( "%.1f %ciB" ,
float64 ( b ) / float64 ( div ) , "KMGTPE" [ exp ] )
}
2024-03-12 19:06:16 +01:00
func countEnabled ( dnsServers [ ] nsServerGroupStateOutput ) int {
count := 0
for _ , server := range dnsServers {
if server . Enabled {
count ++
}
}
return count
}
2024-04-26 17:20:10 +02:00
// timeAgo returns a string representing the duration since the provided time in a human-readable format.
func timeAgo ( t time . Time ) string {
if t . IsZero ( ) || t . Equal ( time . Unix ( 0 , 0 ) ) {
return "-"
}
duration := time . Since ( t )
switch {
case duration < time . Second :
return "Now"
case duration < time . Minute :
seconds := int ( duration . Seconds ( ) )
if seconds == 1 {
return "1 second ago"
}
return fmt . Sprintf ( "%d seconds ago" , seconds )
case duration < time . Hour :
minutes := int ( duration . Minutes ( ) )
seconds := int ( duration . Seconds ( ) ) % 60
if minutes == 1 {
if seconds == 1 {
return "1 minute, 1 second ago"
} else if seconds > 0 {
return fmt . Sprintf ( "1 minute, %d seconds ago" , seconds )
}
return "1 minute ago"
}
if seconds > 0 {
return fmt . Sprintf ( "%d minutes, %d seconds ago" , minutes , seconds )
}
return fmt . Sprintf ( "%d minutes ago" , minutes )
case duration < 24 * time . Hour :
hours := int ( duration . Hours ( ) )
minutes := int ( duration . Minutes ( ) ) % 60
if hours == 1 {
if minutes == 1 {
return "1 hour, 1 minute ago"
} else if minutes > 0 {
return fmt . Sprintf ( "1 hour, %d minutes ago" , minutes )
}
return "1 hour ago"
}
if minutes > 0 {
return fmt . Sprintf ( "%d hours, %d minutes ago" , hours , minutes )
}
return fmt . Sprintf ( "%d hours ago" , hours )
}
days := int ( duration . Hours ( ) ) / 24
hours := int ( duration . Hours ( ) ) % 24
if days == 1 {
if hours == 1 {
return "1 day, 1 hour ago"
} else if hours > 0 {
return fmt . Sprintf ( "1 day, %d hours ago" , hours )
}
return "1 day ago"
}
if hours > 0 {
return fmt . Sprintf ( "%d days, %d hours ago" , days , hours )
}
return fmt . Sprintf ( "%d days ago" , days )
}
func anonymizePeerDetail ( a * anonymize . Anonymizer , peer * peerStateDetailOutput ) {
peer . FQDN = a . AnonymizeDomain ( peer . FQDN )
if localIP , port , err := net . SplitHostPort ( peer . IceCandidateEndpoint . Local ) ; err == nil {
peer . IceCandidateEndpoint . Local = fmt . Sprintf ( "%s:%s" , a . AnonymizeIPString ( localIP ) , port )
}
if remoteIP , port , err := net . SplitHostPort ( peer . IceCandidateEndpoint . Remote ) ; err == nil {
peer . IceCandidateEndpoint . Remote = fmt . Sprintf ( "%s:%s" , a . AnonymizeIPString ( remoteIP ) , port )
}
for i , route := range peer . Routes {
peer . Routes [ i ] = a . AnonymizeIPString ( route )
}
for i , route := range peer . Routes {
prefix , err := netip . ParsePrefix ( route )
if err == nil {
ip := a . AnonymizeIPString ( prefix . Addr ( ) . String ( ) )
peer . Routes [ i ] = fmt . Sprintf ( "%s/%d" , ip , prefix . Bits ( ) )
}
}
}
func anonymizeOverview ( a * anonymize . Anonymizer , overview * statusOutputOverview ) {
for i , peer := range overview . Peers . Details {
peer := peer
anonymizePeerDetail ( a , & peer )
overview . Peers . Details [ i ] = peer
}
overview . ManagementState . URL = a . AnonymizeURI ( overview . ManagementState . URL )
overview . ManagementState . Error = a . AnonymizeString ( overview . ManagementState . Error )
overview . SignalState . URL = a . AnonymizeURI ( overview . SignalState . URL )
overview . SignalState . Error = a . AnonymizeString ( overview . SignalState . Error )
overview . IP = a . AnonymizeIPString ( overview . IP )
for i , detail := range overview . Relays . Details {
detail . URI = a . AnonymizeURI ( detail . URI )
detail . Error = a . AnonymizeString ( detail . Error )
overview . Relays . Details [ i ] = detail
}
for i , nsGroup := range overview . NSServerGroups {
for j , domain := range nsGroup . Domains {
overview . NSServerGroups [ i ] . Domains [ j ] = a . AnonymizeDomain ( domain )
}
for j , ns := range nsGroup . Servers {
host , port , err := net . SplitHostPort ( ns )
if err == nil {
overview . NSServerGroups [ i ] . Servers [ j ] = fmt . Sprintf ( "%s:%s" , a . AnonymizeIPString ( host ) , port )
}
}
}
for i , route := range overview . Routes {
prefix , err := netip . ParsePrefix ( route )
if err == nil {
ip := a . AnonymizeIPString ( prefix . Addr ( ) . String ( ) )
overview . Routes [ i ] = fmt . Sprintf ( "%s/%d" , ip , prefix . Bits ( ) )
}
}
overview . FQDN = a . AnonymizeDomain ( overview . FQDN )
}