2022-03-08 14:47:55 +01:00
package cmd
import (
"context"
2022-05-12 11:17:24 +02:00
"fmt"
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"
nbStatus "github.com/netbirdio/netbird/client/status"
2022-03-26 12:08:54 +01:00
"github.com/netbirdio/netbird/util"
2022-03-08 14:47:55 +01:00
"github.com/spf13/cobra"
"google.golang.org/grpc/status"
2022-07-05 19:47:50 +02:00
"net/netip"
"sort"
"strings"
)
2022-03-08 14:47:55 +01:00
2022-07-05 19:47:50 +02:00
var (
detailFlag bool
ipsFilter [ ] string
statusFilter string
ipsFilterMap 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" ,
2022-03-08 14:47:55 +01:00
RunE : func ( cmd * cobra . Command , args [ ] string ) error {
SetFlagsFromEnvVars ( )
2022-03-10 18:14:07 +01:00
2022-05-25 19:41:03 +02:00
cmd . SetOut ( cmd . OutOrStdout ( ) )
2022-05-22 18:53:47 +02:00
2022-07-05 19:47:50 +02:00
err := parseFilters ( )
if err != nil {
return err
}
err = util . InitLog ( logLevel , "console" )
2022-03-10 18:14:07 +01:00
if err != nil {
2022-05-12 11:17:24 +02:00
return fmt . Errorf ( "failed initializing log %v" , err )
2022-03-10 18:14:07 +01:00
}
2022-03-08 14:47:55 +01:00
ctx := internal . CtxInitState ( context . Background ( ) )
conn , err := DialClientGRPCServer ( ctx , daemonAddr )
if err != nil {
2022-05-12 11:17:24 +02:00
return 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 )
2022-03-08 14:47:55 +01:00
}
defer conn . Close ( )
2022-07-05 19:47:50 +02:00
resp , err := proto . NewDaemonServiceClient ( conn ) . Status ( cmd . Context ( ) , & proto . StatusRequest { GetFullPeerStatus : true } )
2022-03-08 14:47:55 +01:00
if err != nil {
2022-05-12 11:17:24 +02:00
return fmt . Errorf ( "status failed: %v" , status . Convert ( err ) . Message ( ) )
}
2022-07-05 19:47:50 +02:00
daemonStatus := fmt . Sprintf ( "Daemon status: %s\n" , resp . GetStatus ( ) )
2022-05-12 11:17:24 +02:00
if resp . GetStatus ( ) == string ( internal . StatusNeedsLogin ) || resp . GetStatus ( ) == string ( internal . StatusLoginFailed ) {
2022-05-12 21:57:31 +02:00
2022-07-05 19:47:50 +02:00
cmd . Printf ( "%s\n" +
"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" +
"More info: https://www.netbird.io/docs/overview/setup-keys\n\n" ,
daemonStatus ,
)
return nil
2022-03-08 14:47:55 +01:00
}
2022-07-05 19:47:50 +02:00
pbFullStatus := resp . GetFullStatus ( )
fullStatus := fromProtoFullStatus ( pbFullStatus )
cmd . Print ( parseFullStatus ( fullStatus , detailFlag , daemonStatus ) )
2022-03-08 14:47:55 +01:00
return nil
} ,
}
2022-07-05 19:47:50 +02:00
func init ( ) {
ipsFilterMap = make ( map [ string ] struct { } )
statusCmd . PersistentFlags ( ) . BoolVarP ( & detailFlag , "detail" , "d" , false , "display detailed status information" )
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" )
statusCmd . PersistentFlags ( ) . StringVar ( & statusFilter , "filter-by-status" , "" , "filters the detailed output by connection status(connected|disconnected), e.g. --filter-by-status connected" )
}
func parseFilters ( ) error {
switch strings . ToLower ( statusFilter ) {
case "" , "disconnected" , "connected" :
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 { } { }
}
}
return nil
}
func fromProtoFullStatus ( pbFullStatus * proto . FullStatus ) nbStatus . FullStatus {
var fullStatus nbStatus . FullStatus
2022-07-07 13:54:47 +02:00
managementState := pbFullStatus . GetManagementState ( )
fullStatus . ManagementState . URL = managementState . GetURL ( )
fullStatus . ManagementState . Connected = managementState . GetConnected ( )
2022-07-05 19:47:50 +02:00
2022-07-07 13:54:47 +02:00
signalState := pbFullStatus . GetSignalState ( )
fullStatus . SignalState . URL = signalState . GetURL ( )
fullStatus . SignalState . Connected = signalState . GetConnected ( )
2022-07-05 19:47:50 +02:00
2022-07-07 13:54:47 +02:00
localPeerState := pbFullStatus . GetLocalPeerState ( )
fullStatus . LocalPeerState . IP = localPeerState . GetIP ( )
fullStatus . LocalPeerState . PubKey = localPeerState . GetPubKey ( )
fullStatus . LocalPeerState . KernelInterface = localPeerState . GetKernelInterface ( )
2022-07-05 19:47:50 +02:00
var peersState [ ] nbStatus . PeerState
2022-07-07 13:54:47 +02:00
for _ , pbPeerState := range pbFullStatus . GetPeers ( ) {
timeLocal := pbPeerState . GetConnStatusUpdate ( ) . AsTime ( ) . Local ( )
2022-07-05 19:47:50 +02:00
peerState := nbStatus . PeerState {
2022-07-07 13:54:47 +02:00
IP : pbPeerState . GetIP ( ) ,
PubKey : pbPeerState . GetPubKey ( ) ,
ConnStatus : pbPeerState . GetConnStatus ( ) ,
2022-07-05 19:47:50 +02:00
ConnStatusUpdate : timeLocal ,
2022-07-07 13:54:47 +02:00
Relayed : pbPeerState . GetRelayed ( ) ,
Direct : pbPeerState . GetDirect ( ) ,
LocalIceCandidateType : pbPeerState . GetLocalIceCandidateType ( ) ,
RemoteIceCandidateType : pbPeerState . GetRemoteIceCandidateType ( ) ,
2022-07-05 19:47:50 +02:00
}
peersState = append ( peersState , peerState )
}
fullStatus . Peers = peersState
return fullStatus
}
func parseFullStatus ( fullStatus nbStatus . FullStatus , printDetail bool , daemonStatus string ) string {
var (
managementStatusURL = ""
signalStatusURL = ""
managementConnString = "Disconnected"
signalConnString = "Disconnected"
interfaceTypeString = "Userspace"
)
if printDetail {
managementStatusURL = fmt . Sprintf ( " to %s" , fullStatus . ManagementState . URL )
signalStatusURL = fmt . Sprintf ( " to %s" , fullStatus . SignalState . URL )
}
if fullStatus . ManagementState . Connected {
managementConnString = "Connected"
}
if fullStatus . SignalState . Connected {
signalConnString = "Connected"
}
interfaceIP := fullStatus . LocalPeerState . IP
if fullStatus . LocalPeerState . KernelInterface {
interfaceTypeString = "Kernel"
} else if fullStatus . LocalPeerState . IP == "" {
interfaceTypeString = "N/A"
interfaceIP = "N/A"
}
parsedPeersString , peersConnected := parsePeers ( fullStatus . Peers , printDetail )
peersCountString := fmt . Sprintf ( "%d/%d Connected" , peersConnected , len ( fullStatus . Peers ) )
summary := fmt . Sprintf (
"%s" + // daemon status
"Management: %s%s\n" +
"Signal: %s%s\n" +
"NetBird IP: %s\n" +
"Interface type: %s\n" +
"Peers count: %s\n" ,
daemonStatus ,
managementConnString ,
managementStatusURL ,
signalConnString ,
signalStatusURL ,
interfaceIP ,
interfaceTypeString ,
peersCountString ,
)
if printDetail {
return fmt . Sprintf (
"Peers detail:" +
"%s\n" +
"%s" ,
parsedPeersString ,
summary ,
)
}
return summary
}
func parsePeers ( peers [ ] nbStatus . PeerState , printDetail bool ) ( string , int ) {
var (
peersString = ""
peersConnected = 0
)
if len ( peers ) > 0 {
sort . SliceStable ( peers , func ( i , j int ) bool {
iAddr , _ := netip . ParseAddr ( peers [ i ] . IP )
jAddr , _ := netip . ParseAddr ( peers [ j ] . IP )
return iAddr . Compare ( jAddr ) == - 1
} )
}
connectedStatusString := peer . StatusConnected . String ( )
for _ , peerState := range peers {
peerConnectionStatus := false
if peerState . ConnStatus == connectedStatusString {
peersConnected = peersConnected + 1
peerConnectionStatus = true
}
if printDetail {
if skipDetailByFilters ( peerState , peerConnectionStatus ) {
continue
}
localICE := "-"
remoteICE := "-"
connType := "-"
if peerConnectionStatus {
localICE = peerState . LocalIceCandidateType
remoteICE = peerState . RemoteIceCandidateType
connType = "P2P"
if peerState . Relayed {
connType = "Relayed"
}
}
peerString := fmt . Sprintf (
"\n Peer:\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" +
" Last connection update: %s\n" ,
peerState . IP ,
peerState . PubKey ,
peerState . ConnStatus ,
connType ,
peerState . Direct ,
localICE ,
remoteICE ,
peerState . ConnStatusUpdate . Format ( "2006-01-02 15:04:05" ) ,
)
peersString = peersString + peerString
}
}
return peersString , peersConnected
}
func skipDetailByFilters ( peerState nbStatus . PeerState , isConnected bool ) bool {
statusEval := false
ipEval := false
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
}
}
return statusEval || ipEval
}