From 40fdeda838101b1bdc644026ecc66d9fbca9adb4 Mon Sep 17 00:00:00 2001 From: Ali Amer <76897266+aliamerj@users.noreply.github.com> Date: Mon, 21 Jul 2025 12:55:17 +0300 Subject: [PATCH] [client] add new filter-by-connection-type flag (#4010) introduces a new flag --filter-by-connection-type to the status command. It allows users to filter peers by connection type (P2P or Relayed) in both JSON and detailed views. Input validation is added in parseFilters() to ensure proper usage, and --detail is auto-enabled if no output format is specified (consistent with other filters). --- client/cmd/debug.go | 2 +- client/cmd/status.go | 13 ++++++++++++- client/proto/daemon.pb.go | 7 +++++++ client/status/status.go | 20 +++++++++++--------- client/status/status_test.go | 2 +- client/ui/debug.go | 6 +++--- 6 files changed, 35 insertions(+), 15 deletions(-) diff --git a/client/cmd/debug.go b/client/cmd/debug.go index 4036bb8f6..3f13a0c3a 100644 --- a/client/cmd/debug.go +++ b/client/cmd/debug.go @@ -307,7 +307,7 @@ func getStatusOutput(cmd *cobra.Command, anon bool) string { cmd.PrintErrf("Failed to get status: %v\n", err) } else { statusOutputString = nbstatus.ParseToFullDetailSummary( - nbstatus.ConvertToStatusOutputOverview(statusResp, anon, "", nil, nil, nil), + nbstatus.ConvertToStatusOutputOverview(statusResp, anon, "", nil, nil, nil, ""), ) } return statusOutputString diff --git a/client/cmd/status.go b/client/cmd/status.go index b108ca57a..2d6e41bc2 100644 --- a/client/cmd/status.go +++ b/client/cmd/status.go @@ -26,6 +26,7 @@ var ( statusFilter string ipsFilterMap map[string]struct{} prefixNamesFilterMap map[string]struct{} + connectionTypeFilter string ) var statusCmd = &cobra.Command{ @@ -45,6 +46,7 @@ func init() { 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().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") statusCmd.PersistentFlags().StringVar(&statusFilter, "filter-by-status", "", "filters the detailed output by connection status(idle|connecting|connected), e.g., --filter-by-status connected") + statusCmd.PersistentFlags().StringVar(&connectionTypeFilter, "filter-by-connection-type", "", "filters the detailed output by connection type (P2P|Relayed), e.g., --filter-by-connection-type P2P") } func statusFunc(cmd *cobra.Command, args []string) error { @@ -89,7 +91,7 @@ func statusFunc(cmd *cobra.Command, args []string) error { return nil } - var outputInformationHolder = nbstatus.ConvertToStatusOutputOverview(resp, anonymizeFlag, statusFilter, prefixNamesFilter, prefixNamesFilterMap, ipsFilterMap) + var outputInformationHolder = nbstatus.ConvertToStatusOutputOverview(resp, anonymizeFlag, statusFilter, prefixNamesFilter, prefixNamesFilterMap, ipsFilterMap, connectionTypeFilter) var statusOutputString string switch { case detailFlag: @@ -156,6 +158,15 @@ func parseFilters() error { enableDetailFlagWhenFilterFlag() } + switch strings.ToLower(connectionTypeFilter) { + case "", "p2p", "relayed": + if strings.ToLower(connectionTypeFilter) != "" { + enableDetailFlagWhenFilterFlag() + } + default: + return fmt.Errorf("wrong connection-type filter, should be one of P2P|Relayed, got: %s", connectionTypeFilter) + } + return nil } diff --git a/client/proto/daemon.pb.go b/client/proto/daemon.pb.go index 26e58d183..753aa62d1 100644 --- a/client/proto/daemon.pb.go +++ b/client/proto/daemon.pb.go @@ -1330,6 +1330,13 @@ func (x *PeerState) GetRelayAddress() string { return "" } +func (x *PeerState) GetConnectionType() string { + if x.Relayed { + return "Relayed" + } + return "P2P" +} + // LocalPeerState contains the latest state of the local peer type LocalPeerState struct { state protoimpl.MessageState `protogen:"open.v1"` diff --git a/client/status/status.go b/client/status/status.go index 18056e363..507c7ea80 100644 --- a/client/status/status.go +++ b/client/status/status.go @@ -100,7 +100,7 @@ type OutputOverview struct { LazyConnectionEnabled bool `json:"lazyConnectionEnabled" yaml:"lazyConnectionEnabled"` } -func ConvertToStatusOutputOverview(resp *proto.StatusResponse, anon bool, statusFilter string, prefixNamesFilter []string, prefixNamesFilterMap map[string]struct{}, ipsFilter map[string]struct{}) OutputOverview { +func ConvertToStatusOutputOverview(resp *proto.StatusResponse, anon bool, statusFilter string, prefixNamesFilter []string, prefixNamesFilterMap map[string]struct{}, ipsFilter map[string]struct{}, connectionTypeFilter string) OutputOverview { pbFullStatus := resp.GetFullStatus() managementState := pbFullStatus.GetManagementState() @@ -118,7 +118,7 @@ func ConvertToStatusOutputOverview(resp *proto.StatusResponse, anon bool, status } relayOverview := mapRelays(pbFullStatus.GetRelays()) - peersOverview := mapPeers(resp.GetFullStatus().GetPeers(), statusFilter, prefixNamesFilter, prefixNamesFilterMap, ipsFilter) + peersOverview := mapPeers(resp.GetFullStatus().GetPeers(), statusFilter, prefixNamesFilter, prefixNamesFilterMap, ipsFilter, connectionTypeFilter) overview := OutputOverview{ Peers: peersOverview, @@ -193,6 +193,7 @@ func mapPeers( prefixNamesFilter []string, prefixNamesFilterMap map[string]struct{}, ipsFilter map[string]struct{}, + connectionTypeFilter string, ) PeersStateOutput { var peersStateDetail []PeerStateDetailOutput peersConnected := 0 @@ -208,7 +209,7 @@ func mapPeers( transferSent := int64(0) isPeerConnected := pbPeerState.ConnStatus == peer.StatusConnected.String() - if skipDetailByFilters(pbPeerState, pbPeerState.ConnStatus, statusFilter, prefixNamesFilter, prefixNamesFilterMap, ipsFilter) { + if skipDetailByFilters(pbPeerState, pbPeerState.ConnStatus, statusFilter, prefixNamesFilter, prefixNamesFilterMap, ipsFilter, connectionTypeFilter) { continue } if isPeerConnected { @@ -218,10 +219,7 @@ func mapPeers( remoteICE = pbPeerState.GetRemoteIceCandidateType() localICEEndpoint = pbPeerState.GetLocalIceCandidateEndpoint() remoteICEEndpoint = pbPeerState.GetRemoteIceCandidateEndpoint() - connType = "P2P" - if pbPeerState.Relayed { - connType = "Relayed" - } + connType = pbPeerState.GetConnectionType() relayServerAddress = pbPeerState.GetRelayAddress() lastHandshake = pbPeerState.GetLastWireguardHandshake().AsTime().Local() transferReceived = pbPeerState.GetBytesRx() @@ -542,10 +540,11 @@ func parsePeers(peers PeersStateOutput, rosenpassEnabled, rosenpassPermissive bo return peersString } -func skipDetailByFilters(peerState *proto.PeerState, peerStatus string, statusFilter string, prefixNamesFilter []string, prefixNamesFilterMap map[string]struct{}, ipsFilter map[string]struct{}) bool { +func skipDetailByFilters(peerState *proto.PeerState, peerStatus string, statusFilter string, prefixNamesFilter []string, prefixNamesFilterMap map[string]struct{}, ipsFilter map[string]struct{}, connectionTypeFilter string) bool { statusEval := false ipEval := false nameEval := true + connectionTypeEval := false if statusFilter != "" { if !strings.EqualFold(peerStatus, statusFilter) { @@ -570,8 +569,11 @@ func skipDetailByFilters(peerState *proto.PeerState, peerStatus string, statusFi } else { nameEval = false } + if connectionTypeFilter != "" && !strings.EqualFold(peerState.GetConnectionType(), connectionTypeFilter) { + connectionTypeEval = true + } - return statusEval || ipEval || nameEval + return statusEval || ipEval || nameEval || connectionTypeEval } func toIEC(b int64) string { diff --git a/client/status/status_test.go b/client/status/status_test.go index 33eda4b9e..5b5d23efd 100644 --- a/client/status/status_test.go +++ b/client/status/status_test.go @@ -234,7 +234,7 @@ var overview = OutputOverview{ } func TestConversionFromFullStatusToOutputOverview(t *testing.T) { - convertedResult := ConvertToStatusOutputOverview(resp, false, "", nil, nil, nil) + convertedResult := ConvertToStatusOutputOverview(resp, false, "", nil, nil, nil, "") assert.Equal(t, overview, convertedResult) } diff --git a/client/ui/debug.go b/client/ui/debug.go index ab7dba37a..55829de1e 100644 --- a/client/ui/debug.go +++ b/client/ui/debug.go @@ -433,7 +433,7 @@ func (s *serviceClient) collectDebugData( var postUpStatusOutput string if postUpStatus != nil { - overview := nbstatus.ConvertToStatusOutputOverview(postUpStatus, params.anonymize, "", nil, nil, nil) + overview := nbstatus.ConvertToStatusOutputOverview(postUpStatus, params.anonymize, "", nil, nil, nil, "") postUpStatusOutput = nbstatus.ParseToFullDetailSummary(overview) } headerPostUp := fmt.Sprintf("----- NetBird post-up - Timestamp: %s", time.Now().Format(time.RFC3339)) @@ -450,7 +450,7 @@ func (s *serviceClient) collectDebugData( var preDownStatusOutput string if preDownStatus != nil { - overview := nbstatus.ConvertToStatusOutputOverview(preDownStatus, params.anonymize, "", nil, nil, nil) + overview := nbstatus.ConvertToStatusOutputOverview(preDownStatus, params.anonymize, "", nil, nil, nil, "") preDownStatusOutput = nbstatus.ParseToFullDetailSummary(overview) } headerPreDown := fmt.Sprintf("----- NetBird pre-down - Timestamp: %s - Duration: %s", @@ -581,7 +581,7 @@ func (s *serviceClient) createDebugBundle(anonymize bool, systemInfo bool, uploa var statusOutput string if statusResp != nil { - overview := nbstatus.ConvertToStatusOutputOverview(statusResp, anonymize, "", nil, nil, nil) + overview := nbstatus.ConvertToStatusOutputOverview(statusResp, anonymize, "", nil, nil, nil, "") statusOutput = nbstatus.ParseToFullDetailSummary(overview) }