2021-08-15 16:56:26 +02:00
package cmd
import (
2022-03-25 13:21:04 +01:00
"context"
2022-05-12 11:17:24 +02:00
"fmt"
2023-03-02 13:28:14 +01:00
"net"
"net/netip"
2024-01-15 15:53:23 +01:00
"runtime"
2023-03-02 13:28:14 +01:00
"strings"
2024-06-13 13:24:24 +02:00
"time"
2023-03-02 13:28:14 +01:00
2022-05-27 19:16:58 +02:00
log "github.com/sirupsen/logrus"
2022-05-12 11:17:24 +02:00
"github.com/spf13/cobra"
"google.golang.org/grpc/codes"
gstatus "google.golang.org/grpc/status"
2024-06-13 13:24:24 +02:00
"google.golang.org/protobuf/types/known/durationpb"
2023-03-02 13:28:14 +01:00
2024-10-02 18:24:22 +02:00
"github.com/netbirdio/netbird/client/iface"
2023-03-02 13:28:14 +01:00
"github.com/netbirdio/netbird/client/internal"
2023-03-03 19:49:18 +01:00
"github.com/netbirdio/netbird/client/internal/peer"
2023-03-02 13:28:14 +01:00
"github.com/netbirdio/netbird/client/proto"
2023-04-20 16:00:22 +02:00
"github.com/netbirdio/netbird/client/system"
2023-03-02 13:28:14 +01:00
"github.com/netbirdio/netbird/util"
2023-01-17 19:16:50 +01:00
)
const (
invalidInputType int = iota
ipInputType
interfaceInputType
2021-08-15 16:56:26 +02:00
)
2023-01-16 18:12:51 +01:00
var (
foregroundMode bool
upCmd = & cobra . Command {
Use : "up" ,
Short : "install, login and start Netbird client" ,
RunE : upFunc ,
}
)
2022-03-10 18:14:07 +01:00
2023-01-16 18:12:51 +01:00
func init ( ) {
upCmd . PersistentFlags ( ) . BoolVarP ( & foregroundMode , "foreground-mode" , "F" , false , "start service in foreground" )
2024-01-15 15:53:23 +01:00
upCmd . PersistentFlags ( ) . StringVar ( & interfaceName , interfaceNameFlag , iface . WgInterfaceDefault , "Wireguard interface name" )
upCmd . PersistentFlags ( ) . Uint16Var ( & wireguardPort , wireguardPortFlag , iface . DefaultWgPort , "Wireguard interface listening port" )
2024-06-13 17:47:25 +02:00
upCmd . PersistentFlags ( ) . BoolVarP ( & networkMonitor , networkMonitorFlag , "N" , networkMonitor ,
` Manage network monitoring. Defaults to true on Windows and macOS, false on Linux. ` +
` E.g. --network-monitor=false to disable or --network-monitor=true to enable. ` ,
)
2024-03-28 09:56:41 +01:00
upCmd . PersistentFlags ( ) . StringSliceVar ( & extraIFaceBlackList , extraIFaceBlackListFlag , nil , "Extra list of default interfaces to ignore for listening" )
2024-06-13 13:24:24 +02:00
upCmd . PersistentFlags ( ) . DurationVar ( & dnsRouteInterval , dnsRouteIntervalFlag , time . Minute , "DNS route update interval" )
2025-01-15 17:39:47 +01:00
upCmd . PersistentFlags ( ) . BoolVar ( & blockLANAccess , blockLANAccessFlag , false , "Block access to local networks (LAN) when using this peer as a router or exit node" )
2023-01-16 18:12:51 +01:00
}
2022-05-22 18:53:47 +02:00
2023-01-16 18:12:51 +01:00
func upFunc ( cmd * cobra . Command , args [ ] string ) error {
2023-01-17 19:16:50 +01:00
SetFlagsFromEnvVars ( rootCmd )
SetFlagsFromEnvVars ( cmd )
2022-03-10 18:14:07 +01:00
2023-01-16 18:12:51 +01:00
cmd . SetOut ( cmd . OutOrStdout ( ) )
2021-10-17 21:34:07 +02:00
2023-01-16 18:12:51 +01:00
err := util . InitLog ( logLevel , "console" )
if err != nil {
return fmt . Errorf ( "failed initializing log %v" , err )
}
2021-10-17 21:34:07 +02:00
2023-01-17 19:16:50 +01:00
err = validateNATExternalIPs ( natExternalIPs )
if err != nil {
return err
}
2023-01-16 18:12:51 +01:00
ctx := internal . CtxInitState ( cmd . Context ( ) )
2021-10-17 21:34:07 +02:00
2023-04-20 16:00:22 +02:00
if hostName != "" {
// nolint
ctx = context . WithValue ( ctx , system . DeviceNameCtxKey , hostName )
}
2023-01-16 18:12:51 +01:00
if foregroundMode {
return runInForegroundMode ( ctx , cmd )
}
return runInDaemonMode ( ctx , cmd )
}
2021-10-17 21:34:07 +02:00
2023-01-16 18:12:51 +01:00
func runInForegroundMode ( ctx context . Context , cmd * cobra . Command ) error {
err := handleRebrand ( cmd )
if err != nil {
return err
}
2023-01-17 19:16:50 +01:00
customDNSAddressConverted , err := parseCustomDNSAddress ( cmd . Flag ( dnsResolverAddress ) . Changed )
if err != nil {
return err
}
2023-05-11 18:09:06 +02:00
ic := internal . ConfigInput {
2024-03-28 09:56:41 +01:00
ManagementURL : managementURL ,
AdminURL : adminURL ,
ConfigPath : configPath ,
NATExternalIPs : natExternalIPs ,
CustomDNSAddress : customDNSAddressConverted ,
ExtraIFaceBlackList : extraIFaceBlackList ,
2023-05-11 18:09:06 +02:00
}
2023-12-14 11:48:12 +01:00
2024-01-15 15:53:23 +01:00
if cmd . Flag ( enableRosenpassFlag ) . Changed {
2024-01-08 12:25:35 +01:00
ic . RosenpassEnabled = & rosenpassEnabled
}
2024-02-21 17:23:17 +01:00
if cmd . Flag ( rosenpassPermissiveFlag ) . Changed {
ic . RosenpassPermissive = & rosenpassPermissive
}
2024-02-20 11:13:27 +01:00
if cmd . Flag ( serverSSHAllowedFlag ) . Changed {
ic . ServerSSHAllowed = & serverSSHAllowed
}
2024-01-15 15:53:23 +01:00
if cmd . Flag ( interfaceNameFlag ) . Changed {
if err := parseInterfaceName ( interfaceName ) ; err != nil {
return err
}
ic . InterfaceName = & interfaceName
}
if cmd . Flag ( wireguardPortFlag ) . Changed {
p := int ( wireguardPort )
ic . WireguardPort = & p
}
2024-05-07 18:50:34 +02:00
if cmd . Flag ( networkMonitorFlag ) . Changed {
ic . NetworkMonitor = & networkMonitor
}
2023-12-14 11:48:12 +01:00
if rootCmd . PersistentFlags ( ) . Changed ( preSharedKeyFlag ) {
2023-05-11 18:09:06 +02:00
ic . PreSharedKey = & preSharedKey
}
2024-02-20 10:10:05 +01:00
if cmd . Flag ( disableAutoConnectFlag ) . Changed {
ic . DisableAutoConnect = & autoConnectDisabled
if autoConnectDisabled {
cmd . Println ( "Autoconnect has been disabled. The client won't connect automatically when the service starts." )
}
if ! autoConnectDisabled {
cmd . Println ( "Autoconnect has been enabled. The client will connect automatically when the service starts." )
}
}
2024-06-13 13:24:24 +02:00
if cmd . Flag ( dnsRouteIntervalFlag ) . Changed {
ic . DNSRouteInterval = & dnsRouteInterval
}
2025-01-07 20:38:18 +01:00
if cmd . Flag ( disableClientRoutesFlag ) . Changed {
ic . DisableClientRoutes = & disableClientRoutes
}
if cmd . Flag ( disableServerRoutesFlag ) . Changed {
ic . DisableServerRoutes = & disableServerRoutes
}
if cmd . Flag ( disableDNSFlag ) . Changed {
ic . DisableDNS = & disableDNS
}
if cmd . Flag ( disableFirewallFlag ) . Changed {
ic . DisableFirewall = & disableFirewall
}
2025-01-15 17:39:47 +01:00
if cmd . Flag ( blockLANAccessFlag ) . Changed {
ic . BlockLANAccess = & blockLANAccess
}
2024-08-09 20:38:58 +02:00
providedSetupKey , err := getSetupKey ( )
if err != nil {
return err
}
2023-05-11 18:09:06 +02:00
config , err := internal . UpdateOrCreateConfig ( ic )
2023-01-16 18:12:51 +01:00
if err != nil {
return fmt . Errorf ( "get config file: %v" , err )
}
2023-12-27 20:56:04 +01:00
config , _ = internal . UpdateOldManagementURL ( ctx , config , configPath )
2023-01-16 18:12:51 +01:00
2024-08-09 20:38:58 +02:00
err = foregroundLogin ( ctx , cmd , config , providedSetupKey )
2023-01-16 18:12:51 +01:00
if err != nil {
return fmt . Errorf ( "foreground login failed: %v" , err )
}
var cancel context . CancelFunc
ctx , cancel = context . WithCancel ( ctx )
SetupCloseHandler ( ctx , cancel )
2024-05-10 10:47:16 +02:00
2024-09-08 12:06:14 +02:00
r := peer . NewRecorder ( config . ManagementURL . String ( ) )
r . GetFullStatus ( )
connectClient := internal . NewConnectClient ( ctx , config , r )
2025-01-28 12:25:45 +01:00
return connectClient . Run ( nil )
2023-01-16 18:12:51 +01:00
}
2021-10-17 21:34:07 +02:00
2023-01-16 18:12:51 +01:00
func runInDaemonMode ( ctx context . Context , cmd * cobra . Command ) error {
2023-01-17 19:16:50 +01:00
customDNSAddressConverted , err := parseCustomDNSAddress ( cmd . Flag ( dnsResolverAddress ) . Changed )
if err != nil {
return err
}
2023-01-16 18:12:51 +01:00
conn , err := DialClientGRPCServer ( ctx , daemonAddr )
if err != nil {
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 )
}
defer func ( ) {
err := conn . Close ( )
2022-05-27 19:16:58 +02:00
if err != nil {
2023-11-01 17:11:16 +01:00
log . Warnf ( "failed closing daemon gRPC client connection %v" , err )
2023-01-16 18:12:51 +01:00
return
2022-05-27 19:16:58 +02:00
}
2023-01-16 18:12:51 +01:00
} ( )
2022-05-12 11:17:24 +02:00
2023-01-16 18:12:51 +01:00
client := proto . NewDaemonServiceClient ( conn )
status , err := client . Status ( ctx , & proto . StatusRequest { } )
if err != nil {
return fmt . Errorf ( "unable to get daemon status: %v" , err )
}
if status . Status == string ( internal . StatusConnected ) {
cmd . Println ( "Already connected" )
return nil
}
2024-08-09 20:38:58 +02:00
providedSetupKey , err := getSetupKey ( )
if err != nil {
return err
}
2023-01-16 18:12:51 +01:00
loginRequest := proto . LoginRequest {
2024-08-09 20:38:58 +02:00
SetupKey : providedSetupKey ,
2023-09-28 14:02:37 +02:00
ManagementUrl : managementURL ,
AdminURL : adminURL ,
NatExternalIPs : natExternalIPs ,
CleanNATExternalIPs : natExternalIPs != nil && len ( natExternalIPs ) == 0 ,
CustomDNSAddress : customDNSAddressConverted ,
IsLinuxDesktopClient : isLinuxRunningDesktop ( ) ,
2023-11-29 15:01:27 +01:00
Hostname : hostName ,
2024-03-28 09:56:41 +01:00
ExtraIFaceBlacklist : extraIFaceBlackList ,
2023-01-16 18:12:51 +01:00
}
2024-01-19 10:30:41 +01:00
if rootCmd . PersistentFlags ( ) . Changed ( preSharedKeyFlag ) {
loginRequest . OptionalPreSharedKey = & preSharedKey
}
2024-01-08 12:25:35 +01:00
if cmd . Flag ( enableRosenpassFlag ) . Changed {
loginRequest . RosenpassEnabled = & rosenpassEnabled
}
2024-02-21 17:23:17 +01:00
if cmd . Flag ( rosenpassPermissiveFlag ) . Changed {
loginRequest . RosenpassPermissive = & rosenpassPermissive
}
2024-02-20 11:13:27 +01:00
if cmd . Flag ( serverSSHAllowedFlag ) . Changed {
loginRequest . ServerSSHAllowed = & serverSSHAllowed
}
2024-02-20 10:10:05 +01:00
if cmd . Flag ( disableAutoConnectFlag ) . Changed {
loginRequest . DisableAutoConnect = & autoConnectDisabled
}
2024-01-15 15:53:23 +01:00
if cmd . Flag ( interfaceNameFlag ) . Changed {
if err := parseInterfaceName ( interfaceName ) ; err != nil {
return err
}
loginRequest . InterfaceName = & interfaceName
}
if cmd . Flag ( wireguardPortFlag ) . Changed {
wp := int64 ( wireguardPort )
loginRequest . WireguardPort = & wp
}
2024-05-07 18:50:34 +02:00
if cmd . Flag ( networkMonitorFlag ) . Changed {
loginRequest . NetworkMonitor = & networkMonitor
}
2024-06-13 13:24:24 +02:00
if cmd . Flag ( dnsRouteIntervalFlag ) . Changed {
loginRequest . DnsRouteInterval = durationpb . New ( dnsRouteInterval )
}
2025-01-07 20:38:18 +01:00
if cmd . Flag ( disableClientRoutesFlag ) . Changed {
loginRequest . DisableClientRoutes = & disableClientRoutes
}
if cmd . Flag ( disableServerRoutesFlag ) . Changed {
loginRequest . DisableServerRoutes = & disableServerRoutes
}
if cmd . Flag ( disableDNSFlag ) . Changed {
loginRequest . DisableDns = & disableDNS
}
if cmd . Flag ( disableFirewallFlag ) . Changed {
loginRequest . DisableFirewall = & disableFirewall
}
2025-01-15 17:39:47 +01:00
if cmd . Flag ( blockLANAccessFlag ) . Changed {
loginRequest . BlockLanAccess = & blockLANAccess
}
2023-01-16 18:12:51 +01:00
var loginErr error
var loginResp * proto . LoginResponse
err = WithBackOff ( func ( ) error {
var backOffErr error
loginResp , backOffErr = client . Login ( ctx , & loginRequest )
if s , ok := gstatus . FromError ( backOffErr ) ; ok && ( s . Code ( ) == codes . InvalidArgument ||
s . Code ( ) == codes . PermissionDenied ||
s . Code ( ) == codes . NotFound ||
s . Code ( ) == codes . Unimplemented ) {
loginErr = backOffErr
return nil
2022-05-27 19:16:58 +02:00
}
2023-01-16 18:12:51 +01:00
return backOffErr
} )
if err != nil {
return fmt . Errorf ( "login backoff cycle failed: %v" , err )
}
2022-05-26 15:26:14 +02:00
2023-01-16 18:12:51 +01:00
if loginErr != nil {
return fmt . Errorf ( "login failed: %v" , loginErr )
}
2022-05-26 15:26:14 +02:00
2023-01-16 18:12:51 +01:00
if loginResp . NeedsSSOLogin {
2022-05-27 19:16:58 +02:00
2023-05-05 12:43:04 +02:00
openURL ( cmd , loginResp . VerificationURIComplete , loginResp . UserCode )
2021-10-17 21:34:07 +02:00
2023-11-29 15:01:27 +01:00
_ , err = client . WaitSSOLogin ( ctx , & proto . WaitSSOLoginRequest { UserCode : loginResp . UserCode , Hostname : hostName } )
2023-01-16 18:12:51 +01:00
if err != nil {
return fmt . Errorf ( "waiting sso login failed with: %v" , err )
2021-11-01 09:34:06 +01:00
}
2023-01-16 18:12:51 +01:00
}
if _ , err := client . Up ( ctx , & proto . UpRequest { } ) ; err != nil {
return fmt . Errorf ( "call service up method: %v" , err )
}
cmd . Println ( "Connected" )
return nil
2021-10-17 21:34:07 +02:00
}
2023-01-17 19:16:50 +01:00
func validateNATExternalIPs ( list [ ] string ) error {
for _ , element := range list {
if element == "" {
return fmt . Errorf ( "empty string is not a valid input for %s" , externalIPMapFlag )
}
subElements := strings . Split ( element , "/" )
if len ( subElements ) > 2 {
2023-11-01 17:11:16 +01:00
return fmt . Errorf ( "%s is not a valid input for %s. it should be formatted as \"String\" or \"String/String\"" , element , externalIPMapFlag )
2023-01-17 19:16:50 +01:00
}
if len ( subElements ) == 1 && ! isValidIP ( subElements [ 0 ] ) {
2023-11-01 17:11:16 +01:00
return fmt . Errorf ( "%s is not a valid input for %s. it should be formatted as \"IP\" or \"IP/IP\", or \"IP/Interface Name\"" , element , externalIPMapFlag )
2023-01-17 19:16:50 +01:00
}
last := 0
for _ , singleElement := range subElements {
inputType , err := validateElement ( singleElement )
if err != nil {
return fmt . Errorf ( "%s is not a valid input for %s. it should be an IP string or a network name" , singleElement , externalIPMapFlag )
}
if last == interfaceInputType && inputType == interfaceInputType {
return fmt . Errorf ( "%s is not a valid input for %s. it should not contain two interface names" , element , externalIPMapFlag )
}
last = inputType
}
}
return nil
}
2024-01-15 15:53:23 +01:00
func parseInterfaceName ( name string ) error {
if runtime . GOOS != "darwin" {
return nil
}
if strings . HasPrefix ( name , "utun" ) {
return nil
}
return fmt . Errorf ( "invalid interface name %s. Please use the prefix utun followed by a number on MacOS. e.g., utun1 or utun199" , name )
}
2023-01-17 19:16:50 +01:00
func validateElement ( element string ) ( int , error ) {
if isValidIP ( element ) {
return ipInputType , nil
}
validIface , err := isValidInterface ( element )
if err != nil {
return invalidInputType , fmt . Errorf ( "unable to validate the network interface name, error: %s" , err )
}
if validIface {
return interfaceInputType , nil
}
return interfaceInputType , fmt . Errorf ( "invalid IP or network interface name not found" )
}
func isValidIP ( ip string ) bool {
return net . ParseIP ( ip ) != nil
}
func isValidInterface ( name string ) ( bool , error ) {
netInterfaces , err := net . Interfaces ( )
if err != nil {
return false , err
}
for _ , iface := range netInterfaces {
if iface . Name == name {
return true , nil
}
}
return false , nil
}
func parseCustomDNSAddress ( modified bool ) ( [ ] byte , error ) {
var parsed [ ] byte
if modified {
if ! isValidAddrPort ( customDNSAddress ) {
2023-11-01 17:11:16 +01:00
return nil , fmt . Errorf ( "%s is invalid, it should be formatted as IP:Port string or as an empty string like \"\"" , customDNSAddress )
2023-01-17 19:16:50 +01:00
}
if customDNSAddress == "" && logFile != "console" {
parsed = [ ] byte ( "empty" )
} else {
parsed = [ ] byte ( customDNSAddress )
}
}
return parsed , nil
}
func isValidAddrPort ( input string ) bool {
if input == "" {
return true
}
_ , err := netip . ParseAddrPort ( input )
return err == nil
}