Skip to content

Commit

Permalink
Enhance status command (#382)
Browse files Browse the repository at this point in the history
Print peer status from the package

Added --detail flag for detailed status output
  • Loading branch information
mlsmaycon authored Jul 5, 2022
1 parent 3bdfa3c commit 49e9113
Show file tree
Hide file tree
Showing 10 changed files with 978 additions and 128 deletions.
260 changes: 249 additions & 11 deletions client/cmd/status.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,23 @@ package cmd
import (
"context"
"fmt"
"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"
"github.com/netbirdio/netbird/util"

"github.com/spf13/cobra"
"google.golang.org/grpc/status"
"net/netip"
"sort"
"strings"
)

"github.com/netbirdio/netbird/client/internal"
"github.com/netbirdio/netbird/client/proto"
var (
detailFlag bool
ipsFilter []string
statusFilter string
ipsFilterMap map[string]struct{}
)

var statusCmd = &cobra.Command{
Expand All @@ -20,7 +30,12 @@ var statusCmd = &cobra.Command{

cmd.SetOut(cmd.OutOrStdout())

err := util.InitLog(logLevel, "console")
err := parseFilters()
if err != nil {
return err
}

err = util.InitLog(logLevel, "console")
if err != nil {
return fmt.Errorf("failed initializing log %v", err)
}
Expand All @@ -35,21 +50,244 @@ var statusCmd = &cobra.Command{
}
defer conn.Close()

resp, err := proto.NewDaemonServiceClient(conn).Status(cmd.Context(), &proto.StatusRequest{})
resp, err := proto.NewDaemonServiceClient(conn).Status(cmd.Context(), &proto.StatusRequest{GetFullPeerStatus: true})
if err != nil {
return fmt.Errorf("status failed: %v", status.Convert(err).Message())
}

cmd.Printf("Status: %s\n\n", resp.GetStatus())
daemonStatus := fmt.Sprintf("Daemon status: %s\n", resp.GetStatus())
if resp.GetStatus() == string(internal.StatusNeedsLogin) || resp.GetStatus() == string(internal.StatusLoginFailed) {

cmd.Printf("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")
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
}

pbFullStatus := resp.GetFullStatus()
fullStatus := fromProtoFullStatus(pbFullStatus)

cmd.Print(parseFullStatus(fullStatus, detailFlag, daemonStatus))

return nil
},
}

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
fullStatus.ManagementState.URL = pbFullStatus.ManagementState.URL
fullStatus.ManagementState.Connected = pbFullStatus.ManagementState.Connected

fullStatus.SignalState.URL = pbFullStatus.SignalState.URL
fullStatus.SignalState.Connected = pbFullStatus.SignalState.Connected

fullStatus.LocalPeerState.IP = pbFullStatus.LocalPeerState.IP
fullStatus.LocalPeerState.PubKey = pbFullStatus.LocalPeerState.PubKey
fullStatus.LocalPeerState.KernelInterface = pbFullStatus.LocalPeerState.KernelInterface

var peersState []nbStatus.PeerState

for _, pbPeerState := range pbFullStatus.Peers {
timeLocal := pbPeerState.ConnStatusUpdate.AsTime().Local()
peerState := nbStatus.PeerState{
IP: pbPeerState.IP,
PubKey: pbPeerState.PubKey,
ConnStatus: pbPeerState.ConnStatus,
ConnStatusUpdate: timeLocal,
Relayed: pbPeerState.Relayed,
Direct: pbPeerState.Direct,
LocalIceCandidateType: pbPeerState.LocalIceCandidateType,
RemoteIceCandidateType: pbPeerState.RemoteIceCandidateType,
}
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
}
1 change: 1 addition & 0 deletions client/internal/connect.go
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,7 @@ func RunClient(ctx context.Context, config *Config, statusRecorder *nbStatus.Sta
engineCtx, cancel := context.WithCancel(ctx)
defer func() {
statusRecorder.MarkManagementDisconnected(managementURL)
statusRecorder.CleanLocalPeerState()
cancel()
}()

Expand Down
10 changes: 6 additions & 4 deletions client/internal/engine.go
Original file line number Diff line number Diff line change
Expand Up @@ -305,15 +305,17 @@ func (e *Engine) removePeer(peerKey string) error {
e.sshServer.RemoveAuthorizedKey(peerKey)
}

conn, exists := e.peerConns[peerKey]
if exists {
defer func() {
err := e.statusRecorder.RemovePeer(peerKey)
if err != nil {
log.Warn("received error when removing peer from status recorder: ", err)
log.Warnf("received error when removing peer %s from status recorder: %v", peerKey, err)
}
}()

conn, exists := e.peerConns[peerKey]
if exists {
delete(e.peerConns, peerKey)
err = conn.Close()
err := conn.Close()
if err != nil {
switch err.(type) {
case *peer.ConnectionAlreadyClosedError:
Expand Down
8 changes: 4 additions & 4 deletions client/internal/peer/status.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,19 +7,19 @@ type ConnStatus int
func (s ConnStatus) String() string {
switch s {
case StatusConnecting:
return "StatusConnecting"
return "Connecting"
case StatusConnected:
return "StatusConnected"
return "Connected"
case StatusDisconnected:
return "StatusDisconnected"
return "Disconnected"
default:
log.Errorf("unknown status: %d", s)
return "INVALID_PEER_CONNECTION_STATUS"
}
}

const (
StatusConnected = iota
StatusConnected ConnStatus = iota
StatusConnecting
StatusDisconnected
)
6 changes: 3 additions & 3 deletions client/internal/peer/status_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,9 @@ func TestConnStatus_String(t *testing.T) {
status ConnStatus
want string
}{
{"StatusConnected", StatusConnected, "StatusConnected"},
{"StatusDisconnected", StatusDisconnected, "StatusDisconnected"},
{"StatusConnecting", StatusConnecting, "StatusConnecting"},
{"StatusConnected", StatusConnected, "Connected"},
{"StatusDisconnected", StatusDisconnected, "Disconnected"},
{"StatusConnecting", StatusConnecting, "Connecting"},
}

for _, table := range tables {
Expand Down
Loading

0 comments on commit 49e9113

Please sign in to comment.