Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Extend netbird status command to include health information #1471

Merged
merged 16 commits into from
Jan 22, 2024
Merged
Show file tree
Hide file tree
Changes from 13 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
2 changes: 2 additions & 0 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -274,6 +274,8 @@ go test -exec sudo ./...
```
> On Windows use a powershell with administrator privileges

> Non-GTK environments will need the `libayatana-appindicator3-dev` (debian/ubuntu) package installed

## Checklist before submitting a PR
As a critical network service and open-source project, we must enforce a few things before submitting the pull-requests:
- Keep functions as simple as possible, with a single purpose
Expand Down
168 changes: 152 additions & 16 deletions client/cmd/status.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,14 +22,18 @@ import (
)

type peerStateDetailOutput struct {
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"`
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"`
}

type peersStateOutput struct {
Expand All @@ -41,11 +45,25 @@ type peersStateOutput struct {
type signalStateOutput struct {
URL string `json:"url" yaml:"url"`
Connected bool `json:"connected" yaml:"connected"`
Error string `json:"error" yaml:"error"`
}

type managementStateOutput struct {
URL string `json:"url" yaml:"url"`
Connected bool `json:"connected" yaml:"connected"`
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"`
}

type iceCandidateType struct {
Expand All @@ -59,6 +77,7 @@ type statusOutputOverview struct {
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"`
Expand Down Expand Up @@ -146,7 +165,7 @@ func statusFunc(cmd *cobra.Command, args []string) error {
case yamlFlag:
statusOutputString, err = parseToYAML(outputInformationHolder)
default:
statusOutputString = parseGeneralSummary(outputInformationHolder, false)
statusOutputString = parseGeneralSummary(outputInformationHolder, false, false)
}

if err != nil {
Expand Down Expand Up @@ -220,14 +239,17 @@ func convertToStatusOutputOverview(resp *proto.StatusResponse) statusOutputOverv
managementOverview := managementStateOutput{
URL: managementState.GetURL(),
Connected: managementState.GetConnected(),
Error: managementState.Error,
}

signalState := pbFullStatus.GetSignalState()
signalOverview := signalStateOutput{
URL: signalState.GetURL(),
Connected: signalState.GetConnected(),
Error: signalState.Error,
}

relayOverview := mapRelays(pbFullStatus.GetRelays())
peersOverview := mapPeers(resp.GetFullStatus().GetPeers())

overview := statusOutputOverview{
Expand All @@ -236,6 +258,7 @@ func convertToStatusOutputOverview(resp *proto.StatusResponse) statusOutputOverv
DaemonVersion: resp.GetDaemonVersion(),
ManagementState: managementOverview,
SignalState: signalOverview,
Relays: relayOverview,
IP: pbFullStatus.GetLocalPeerState().GetIP(),
PubKey: pbFullStatus.GetLocalPeerState().GetPubKey(),
KernelInterface: pbFullStatus.GetLocalPeerState().GetKernelInterface(),
Expand All @@ -245,12 +268,43 @@ func convertToStatusOutputOverview(resp *proto.StatusResponse) statusOutputOverv
return overview
}

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,
}
}

func mapPeers(peers []*proto.PeerState) peersStateOutput {
var peersStateDetail []peerStateDetailOutput
localICE := ""
remoteICE := ""
localICEEndpoint := ""
remoteICEEndpoint := ""
connType := ""
peersConnected := 0
lastHandshake := time.Time{}
transferReceived := int64(0)
transferSent := int64(0)
for _, pbPeerState := range peers {
isPeerConnected := pbPeerState.ConnStatus == peer.StatusConnected.String()
if skipDetailByFilters(pbPeerState, isPeerConnected) {
Expand All @@ -261,10 +315,15 @@ func mapPeers(peers []*proto.PeerState) peersStateOutput {

localICE = pbPeerState.GetLocalIceCandidateType()
remoteICE = pbPeerState.GetRemoteIceCandidateType()
localICEEndpoint = pbPeerState.GetLocalIceCandidateEndpoint()
remoteICEEndpoint = pbPeerState.GetRemoteIceCandidateEndpoint()
connType = "P2P"
if pbPeerState.Relayed {
connType = "Relayed"
}
lastHandshake = pbPeerState.GetLastWireguardHandshake().AsTime().Local()
transferReceived = pbPeerState.GetBytesRx()
transferSent = pbPeerState.GetBytesTx()
}

timeLocal := pbPeerState.GetConnStatusUpdate().AsTime().Local()
Expand All @@ -279,7 +338,14 @@ func mapPeers(peers []*proto.PeerState) peersStateOutput {
Local: localICE,
Remote: remoteICE,
},
FQDN: pbPeerState.GetFqdn(),
IceCandidateEndpoint: iceCandidateType{
Local: localICEEndpoint,
Remote: remoteICEEndpoint,
},
FQDN: pbPeerState.GetFqdn(),
LastWireguardHandshake: lastHandshake,
TransferReceived: transferReceived,
TransferSent: transferSent,
}

peersStateDetail = append(peersStateDetail, peerState)
Expand Down Expand Up @@ -329,22 +395,32 @@ func parseToYAML(overview statusOutputOverview) (string, error) {
return string(yamlBytes), nil
}

func parseGeneralSummary(overview statusOutputOverview, showURL bool) string {
func parseGeneralSummary(overview statusOutputOverview, showURL bool, showRelays bool) string {

managementConnString := "Disconnected"
var managementConnString string
if overview.ManagementState.Connected {
managementConnString = "Connected"
if showURL {
managementConnString = fmt.Sprintf("%s to %s", managementConnString, overview.ManagementState.URL)
}
} else {
managementConnString = "Disconnected"
if overview.ManagementState.Error != "" {
managementConnString = fmt.Sprintf("%s, reason: %s", managementConnString, overview.ManagementState.Error)
}
}

signalConnString := "Disconnected"
var signalConnString string
if overview.SignalState.Connected {
signalConnString = "Connected"
if showURL {
signalConnString = fmt.Sprintf("%s to %s", signalConnString, overview.SignalState.URL)
}
} else {
signalConnString = "Disconnected"
if overview.SignalState.Error != "" {
signalConnString = fmt.Sprintf("%s, reason: %s", signalConnString, overview.SignalState.Error)
}
}

interfaceTypeString := "Userspace"
Expand All @@ -356,13 +432,31 @@ func parseGeneralSummary(overview statusOutputOverview, showURL bool) string {
interfaceIP = "N/A"
}

var relayAvailableString string
if showRelays {
for _, relay := range overview.Relays.Details {
available := "Available"
reason := ""
if !relay.Available {
available = "Unavailable"
reason = fmt.Sprintf(", reason: %s", relay.Error)
}
relayAvailableString += fmt.Sprintf("\n [%s] is %s%s", relay.URI, available, reason)

}
} else {

relayAvailableString = fmt.Sprintf("%d/%d Available", overview.Relays.Available, overview.Relays.Total)
}

peersCountString := fmt.Sprintf("%d/%d Connected", overview.Peers.Connected, overview.Peers.Total)

summary := fmt.Sprintf(
"Daemon version: %s\n"+
"CLI version: %s\n"+
"Management: %s\n"+
"Signal: %s\n"+
"Relays: %s\n"+
"FQDN: %s\n"+
"NetBird IP: %s\n"+
"Interface type: %s\n"+
Expand All @@ -371,6 +465,7 @@ func parseGeneralSummary(overview statusOutputOverview, showURL bool) string {
version.NetbirdVersion(),
managementConnString,
signalConnString,
relayAvailableString,
overview.FQDN,
interfaceIP,
interfaceTypeString,
Expand All @@ -381,7 +476,7 @@ func parseGeneralSummary(overview statusOutputOverview, showURL bool) string {

func parseToFullDetailSummary(overview statusOutputOverview) string {
parsedPeersString := parsePeers(overview.Peers)
summary := parseGeneralSummary(overview, true)
summary := parseGeneralSummary(overview, true, true)

return fmt.Sprintf(
"Peers detail:"+
Expand Down Expand Up @@ -409,6 +504,25 @@ func parsePeers(peers peersStateOutput) string {
remoteICE = peerState.IceCandidateType.Remote
}

localICEEndpoint := "-"
if peerState.IceCandidateEndpoint.Local != "" {
localICEEndpoint = peerState.IceCandidateEndpoint.Local
}

remoteICEEndpoint := "-"
if peerState.IceCandidateEndpoint.Remote != "" {
remoteICEEndpoint = peerState.IceCandidateEndpoint.Remote
}
lastStatusUpdate := "-"
if !peerState.LastStatusUpdate.IsZero() {
lastStatusUpdate = peerState.LastStatusUpdate.Format("2006-01-02 15:04:05")
}

lastWireguardHandshake := "-"
if !peerState.LastWireguardHandshake.IsZero() {
lastWireguardHandshake = peerState.LastWireguardHandshake.Format("2006-01-02 15:04:05")
}

peerString := fmt.Sprintf(
"\n %s:\n"+
" NetBird IP: %s\n"+
Expand All @@ -418,7 +532,10 @@ func parsePeers(peers peersStateOutput) string {
" Connection type: %s\n"+
" Direct: %t\n"+
" ICE candidate (Local/Remote): %s/%s\n"+
" Last connection update: %s\n",
" ICE candidate endpoints (Local/Remote): %s/%s\n"+
" Last connection update: %s\n"+
" Last Wireguard handshake: %s\n"+
" Transfer status (received/sent) %s/%s\n",
peerState.FQDN,
peerState.IP,
peerState.PubKey,
Expand All @@ -427,7 +544,12 @@ func parsePeers(peers peersStateOutput) string {
peerState.Direct,
localICE,
remoteICE,
peerState.LastStatusUpdate.Format("2006-01-02 15:04:05"),
localICEEndpoint,
remoteICEEndpoint,
lastStatusUpdate,
lastWireguardHandshake,
toIEC(peerState.TransferReceived),
toIEC(peerState.TransferSent),
)

peersString += peerString
Expand Down Expand Up @@ -467,3 +589,17 @@ func skipDetailByFilters(peerState *proto.PeerState, isConnected bool) bool {

return statusEval || ipEval || nameEval
}

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])
}