diff --git a/cli/cli_connect.go b/cli/cli_connect.go index e4649ca2e..0873476a0 100644 --- a/cli/cli_connect.go +++ b/cli/cli_connect.go @@ -141,6 +141,8 @@ func (c *cmd) Connect(ctx *cli.Context) error { rpcErr = errors.New(internal.ServerUnavailableErrorMessage) case internal.CodeDoubleGroupError: rpcErr = errors.New(internal.DoubleGroupErrorMessage) + case internal.CodeTechnologyDisabled: + rpcErr = errors.New(TechnologyDisabledMessage) case internal.CodeVPNRunning: color.Yellow(client.ConnectConnected) case internal.CodeNothingToDo: diff --git a/cli/messages.go b/cli/messages.go index 72ddd08c8..24d7c45fa 100644 --- a/cli/messages.go +++ b/cli/messages.go @@ -123,6 +123,7 @@ Example: nordvpn set %s on` NoPreferredDedicatedIPLocationSelected = "It looks like you haven’t selected the preferred server location for your dedicated IP. Please head over to Nord Account and set up your dedicated IP server." NoSuchCommand = "Command '%s' doesn't exist." MsgListIsEmpty = "We couldn’t load the list of %s. Please try again later." + TechnologyDisabledMessage = "The technology you have configured is not available right now. Please switch to a different technology using nordvpn set technology command and try again." // Meshnet MsgSetMeshnetUsage = "Enables or disables Meshnet on this device." diff --git a/cmd/daemon/main.go b/cmd/daemon/main.go index 47714aa6b..e9e99f961 100644 --- a/cmd/daemon/main.go +++ b/cmd/daemon/main.go @@ -49,7 +49,6 @@ import ( "github.com/NordSecurity/nordvpn-linux/events/meshunsetter" "github.com/NordSecurity/nordvpn-linux/events/refresher" "github.com/NordSecurity/nordvpn-linux/events/subs" - "github.com/NordSecurity/nordvpn-linux/features" grpcmiddleware "github.com/NordSecurity/nordvpn-linux/grpc_middleware" "github.com/NordSecurity/nordvpn-linux/internal" "github.com/NordSecurity/nordvpn-linux/ipv6" @@ -147,21 +146,6 @@ func main() { } rcConfig := getRemoteConfigGetter(fsystem) - nordWhisperEnabled, err := rcConfig.GetNordWhisperEnabled(Version) - if err != nil { - log.Println("failed to determine if NordWhisper is enabled:", err) - } - - // fallback to Nordlynx if NordWhisper was enabled in previous installation and is disabled now - if (features.NordWhisperEnabled || !nordWhisperEnabled) && cfg.Technology == config.Technology_NORDWHISPER { - err := fsystem.SaveWith(func(c config.Config) config.Config { - c.Technology = config.Technology_NORDLYNX - return c - }) - if err != nil { - log.Println(internal.ErrorPrefix, "failed to fallback to Nordlynx tech:", err) - } - } // Events @@ -209,6 +193,7 @@ func main() { ) // API + var err error var validator response.Validator if !internal.IsProdEnv(Environment) && os.Getenv(EnvIgnoreHeaderValidation) == "1" { validator = response.NoopValidator{} @@ -323,7 +308,13 @@ func main() { vpn, err := vpnFactory(cfg.Technology) if err != nil { - log.Fatalln(err) + // if NordWhiser was disabled we'll fall back automatically to NordLynx if autoconnect is enabled or tell user + // to switch to a different tech + if !errors.Is(err, ErrNordWhisperDisabled) { + log.Fatalln(err) + } else { + log.Println(internal.ErrorPrefix, "failed to build NordWhisper VPN, it was disabled during compilation") + } } devices, err := device.ListPhysical() diff --git a/cmd/daemon/vpn_factory.go b/cmd/daemon/vpn_factory.go index 5db5781e1..bc7f7f31e 100644 --- a/cmd/daemon/vpn_factory.go +++ b/cmd/daemon/vpn_factory.go @@ -11,23 +11,31 @@ import ( "github.com/NordSecurity/nordvpn-linux/internal" ) +var ErrNordWhisperDisabled = errors.New("NordWhisper technology was disabled in compile time") + func getVpnFactory(eventsDbPath string, fwmark uint32, envIsDev bool, cfg vpn.LibConfigGetter, appVersion string, eventsPublisher *vpn.Events, ) daemon.FactoryFunc { - nordlynxVPN, err := getNordlynxVPN(envIsDev, eventsDbPath, fwmark, cfg, appVersion, eventsPublisher) - if err != nil { + nordlynxVPN, nordLynxErr := getNordlynxVPN(envIsDev, eventsDbPath, fwmark, cfg, appVersion, eventsPublisher) + if nordLynxErr != nil { // don't exit with `err` here in case the factory will be called with // technology different than `config.Technology_NORDLYNX` - log.Println(internal.ErrorPrefix, "getting NordLynx vpn:", err) + log.Println(internal.ErrorPrefix, "getting NordLynx vpn:", nordLynxErr) + } + + nordWhisperVPN, nordWhisperErr := getNordWhisperVPN(fwmark) + if nordWhisperErr != nil { + log.Println(internal.ErrorPrefix, "getting NordWhisper vpn:", nordWhisperErr) } + return func(tech config.Technology) (vpn.VPN, error) { switch tech { case config.Technology_NORDLYNX: - return nordlynxVPN, err + return nordlynxVPN, nordLynxErr case config.Technology_OPENVPN: return openvpn.New(fwmark, eventsPublisher), nil case config.Technology_NORDWHISPER: - return getNordWhisperVPN(fwmark) + return nordWhisperVPN, nordWhisperErr case config.Technology_UNKNOWN_TECHNOLOGY: fallthrough default: diff --git a/cmd/daemon/vpn_no_quench.go b/cmd/daemon/vpn_no_quench.go index e2537e929..2b93095ba 100644 --- a/cmd/daemon/vpn_no_quench.go +++ b/cmd/daemon/vpn_no_quench.go @@ -3,11 +3,26 @@ package main import ( - "fmt" + "context" "github.com/NordSecurity/nordvpn-linux/daemon/vpn" + "github.com/NordSecurity/nordvpn-linux/tunnel" ) func getNordWhisperVPN(fwmark uint32) (vpn.VPN, error) { - return nil, fmt.Errorf("NordWhisper is not enabled") + return noopNordWhisper{}, ErrNordWhisperDisabled +} + +// noopMesh is a noop implementation of meshnet. It is used when telio +// is not available and should be used only for development purposes +type noopNordWhisper struct{} + +func (noopNordWhisper) Start(context.Context, vpn.Credentials, vpn.ServerData) error { return nil } +func (noopNordWhisper) Stop() error { return nil } +func (noopNordWhisper) State() vpn.State { return "" } +func (noopNordWhisper) IsActive() bool { return false } +func (noopNordWhisper) Tun() tunnel.T { return &tunnel.Tunnel{} } +func (noopNordWhisper) NetworkChanged() error { return nil } +func (noopNordWhisper) GetConnectionParameters() (vpn.ServerData, bool) { + return vpn.ServerData{}, false } diff --git a/config/remote/firebase.go b/config/remote/firebase.go index 69af40a0c..11fdd61d7 100644 --- a/config/remote/firebase.go +++ b/config/remote/firebase.go @@ -72,19 +72,19 @@ func (rc *RConfig) fetchRemoteConfigIfTime() error { remoteConfig := []byte(cfg.RemoteConfig) // don't fetch the remote config too often even if there is nothing cached - if time.Now().After(cfg.RCLastUpdate.Add(rc.updatePeriod)) { - if fetchedConfig, err := rc.fetchAndSaveRemoteConfig(); err != nil { - // if there no is cached config return error - // otherwise use the cached data - if len(remoteConfig) == 0 { - return err - } else { - log.Println(internal.ErrorPrefix, "use cached config because fetch failed:", err) - } + // if time.Now().After(cfg.RCLastUpdate.Add(rc.updatePeriod)) { + if fetchedConfig, err := rc.fetchAndSaveRemoteConfig(); err != nil { + // if there no is cached config return error + // otherwise use the cached data + if len(remoteConfig) == 0 { + return err } else { - remoteConfig = fetchedConfig + log.Println(internal.ErrorPrefix, "use cached config because fetch failed:", err) } + } else { + remoteConfig = fetchedConfig } + // } if err := json.Unmarshal(remoteConfig, rc.config); err != nil { return fmt.Errorf("parsing remote config from JSON: %w", err) @@ -172,6 +172,8 @@ func (rc *RConfig) GetNordWhisperEnabled(stringVersion string) (bool, error) { return false, fmt.Errorf("fetching the telio config: %w", err) } + log.Println("DEBUG: ", enabledStr) + enabled, err := strconv.ParseBool(enabledStr) if err != nil { return false, fmt.Errorf("parsing firebase parameter: %w", err) diff --git a/daemon/jobs.go b/daemon/jobs.go index fd5762147..1257ccdaf 100644 --- a/daemon/jobs.go +++ b/daemon/jobs.go @@ -15,6 +15,7 @@ import ( "github.com/NordSecurity/nordvpn-linux/daemon/pb" "github.com/NordSecurity/nordvpn-linux/daemon/state" "github.com/NordSecurity/nordvpn-linux/events" + "github.com/NordSecurity/nordvpn-linux/features" "github.com/NordSecurity/nordvpn-linux/internal" "github.com/NordSecurity/nordvpn-linux/meshnet" @@ -233,6 +234,24 @@ func (r *RPC) StartAutoConnect(timeoutFn GetTimeoutFunc) error { return err } + if cfg.Technology == config.Technology_NORDWHISPER { + nordWhisperEnabled, err := r.remoteConfigGetter.GetNordWhisperEnabled(r.version) + if err != nil { + log.Println("failed to determine if NordWhisper is enabled:", err) + } + + // fallback to Nordlynx if NordWhisper was enabled in previous installation and is disabled now + if features.NordWhisperEnabled || !nordWhisperEnabled { + err := r.cm.SaveWith(func(c config.Config) config.Config { + c.Technology = config.Technology_NORDLYNX + return c + }) + if err != nil { + log.Println(internal.ErrorPrefix, "failed to fallback to Nordlynx tech:", err) + } + } + } + server := autoconnectServer{} err = r.Connect(&pb.ConnectRequest{ServerTag: cfg.AutoConnectData.ServerTag}, &server) if connectErrorCheck(err) && server.err == nil { diff --git a/daemon/rpc_connect.go b/daemon/rpc_connect.go index 22c68795d..aa6346cc3 100644 --- a/daemon/rpc_connect.go +++ b/daemon/rpc_connect.go @@ -95,6 +95,23 @@ func (r *RPC) connect( if err := r.cm.Load(&cfg); err != nil { log.Println(internal.ErrorPrefix, err) } + + if cfg.Technology == config.Technology_NORDWHISPER { + if !features.NordWhisperEnabled { + return srv.Send(&pb.Payload{Type: internal.CodeTechnologyDisabled}) + } + + nordWhisperEnabled, err := r.remoteConfigGetter.GetNordWhisperEnabled(r.version) + if err != nil { + log.Println(internal.ErrorPrefix, "failed to retrieve remote config for NordWhisper:", err) + return srv.Send(&pb.Payload{Type: internal.CodeTechnologyDisabled}) + } + + if !nordWhisperEnabled { + return srv.Send(&pb.Payload{Type: internal.CodeTechnologyDisabled}) + } + } + cfg = r.nordWhisperConfigFallback(cfg) insights := r.dm.GetInsightsData().Insights diff --git a/internal/codes.go b/internal/codes.go index 4a3198b3c..6d128da31 100644 --- a/internal/codes.go +++ b/internal/codes.go @@ -62,6 +62,7 @@ const ( CodePqAndMeshnetSimultaneously int64 = 3048 CodePqWithoutNordlynx int64 = 3049 CodeFeatureHidden int64 = 3050 + CodeTechnologyDisabled int64 = 3051 ) type ErrorWithCode struct {