diff --git a/daemon/helper_test.go b/daemon/helper_test.go index 9cd8749c..02ce145e 100644 --- a/daemon/helper_test.go +++ b/daemon/helper_test.go @@ -276,6 +276,17 @@ func serversList() core.Servers { }, } + dipGroups := core.Groups{ + core.Group{ + ID: config.DedicatedIP, + Title: "Dedicated IP", + }, + core.Group{ + ID: config.StandardVPNServers, + Title: "Standard VPN Servers", + }, + } + virtualServer := []core.Specification{ { Identifier: core.VirtualLocation, @@ -322,8 +333,7 @@ func serversList() core.Servers { }, }, }, - Groups: groups, - Specifications: virtualServer, + Groups: groups, }, core.Server{ ID: 3, @@ -345,8 +355,7 @@ func serversList() core.Servers { }, }, }, - Specifications: virtualServer, - Groups: groups, + Groups: groups, }, core.Server{ ID: 4, @@ -384,18 +393,86 @@ func serversList() core.Servers { Groups: groups, }, core.Server{ - ID: 6, - Name: "Canada #944", - Hostname: "ca944.nordvpn.com", + ID: 7, + Hostname: "lt7.nordvpn.com", + Status: core.Online, + Technologies: technologies, + CreatedAt: "2006-01-02 15:04:05", + Station: "127.0.0.1", + Locations: core.Locations{ + core.Location{ + Country: core.Country{Name: "Lithuania", + Code: "LT", + City: core.City{Name: "Vilnius"}, + }, + }, + }, + Groups: dipGroups, + }, + core.Server{ + ID: 8, + Hostname: "lt8.nordvpn.com", + Status: core.Online, + Technologies: technologies, + CreatedAt: "2006-01-02 15:04:05", + Station: "127.0.0.1", + Locations: core.Locations{ + core.Location{ + Country: core.Country{Name: "Lithuania", + Code: "LT", + City: core.City{Name: "Kaunas"}, + }, + }, + }, + Groups: dipGroups, + }, + core.Server{ + ID: 9, + Hostname: "lt9.nordvpn.com", Status: core.Offline, + Technologies: technologies, + CreatedAt: "2006-01-02 15:04:05", + Station: "127.0.0.1", + Locations: core.Locations{ + core.Location{ + Country: core.Country{Name: "Lithuania", + Code: "LT", + City: core.City{Name: "Kaunas"}, + }, + }, + }, + Groups: dipGroups, + }, + core.Server{ + ID: 10, + Hostname: "dz1.nordvpn.com", + Status: core.Online, + Technologies: technologies, + CreatedAt: "2006-01-02 15:04:05", + Station: "127.0.0.1", + Locations: core.Locations{ + core.Location{ + Country: core.Country{Name: "Algeria", + Code: "DZ", + City: core.City{Name: "Algiers"}, + }, + }, + }, + Specifications: virtualServer, + Groups: groups, + }, + core.Server{ + ID: 11, + Hostname: "dz2.nordvpn.com", + Status: core.Online, Technologies: obfuscatedTechnologies, CreatedAt: "2006-01-02 15:04:05", Station: "127.0.0.1", Locations: core.Locations{ core.Location{ - Country: core.Country{Name: "Canada", - Code: "CA", - City: core.City{Name: "Toronto"}, + Country: core.Country{Name: "Algeria", + Code: "DZ", + City: core.City{Name: "Algiers"}, }, }, }, @@ -450,5 +527,12 @@ func countriesList() core.Countries { {Name: "Berlin"}, }, }, + { + Name: "Algeria", + Code: "DZ", + Cities: []core.City{ + {Name: "Algiers"}, + }, + }, } } diff --git a/daemon/job_servers.go b/daemon/job_servers.go index 149317dc..420ac44b 100644 --- a/daemon/job_servers.go +++ b/daemon/job_servers.go @@ -150,8 +150,8 @@ func generateKeys(server core.Server) []string { return append([]string{ loweredCountryName, loweredCountryCode, - loweredCountryName + loweredCityName, - loweredCountryCode + loweredCityName, + loweredCountryName + " " + loweredCityName, + loweredCountryCode + " " + loweredCityName, loweredCityName, loweredHostnameID, }, loweredGroupTitles...) diff --git a/daemon/job_servers_test.go b/daemon/job_servers_test.go index 05d89ceb..663a669b 100644 --- a/daemon/job_servers_test.go +++ b/daemon/job_servers_test.go @@ -26,6 +26,10 @@ func (mockServersAPI) Servers() (core.Servers, http.Header, error) { } func (mockServersAPI) RecommendedServers(filter core.ServersFilter, _ float64, _ float64) (core.Servers, http.Header, error) { + if filter.Group == config.DedicatedIP { + return nil, nil, fmt.Errorf("API must not be called for Dedicated IP") + } + var servers core.Servers for _, server := range serversList() { if server.Status != core.Online || isDedicatedIP(server) { @@ -35,30 +39,6 @@ func (mockServersAPI) RecommendedServers(filter core.ServersFilter, _ float64, _ servers = append(servers, server) } - if filter.Group == config.DedicatedIP { - return core.Servers{{ - Name: "dedicated-ip", - Status: core.Online, - Station: "127.0.0.1", - CreatedAt: "2006-01-02 15:04:05", - Locations: core.Locations{ - { - Country: core.Country{Name: "Romania"}, - }, - }, - Technologies: core.Technologies{ - {ID: core.WireguardTech, Pivot: core.Pivot{Status: core.Online}}, - {ID: core.OpenVPNUDP, Pivot: core.Pivot{Status: core.Online}}, - }, - IPRecords: []core.ServerIPRecord{ - { - ServerIP: core.ServerIP{IP: "127.0.0.1", Version: 4}, - Type: "some type", - }, - }, - Groups: core.Groups{core.Group{ID: config.DedicatedIP, Title: "DedicatedIP"}}, - }}, nil, nil - } return servers, nil, nil } diff --git a/daemon/rpc_cities_test.go b/daemon/rpc_cities_test.go index 0c3a943b..c1d0a911 100644 --- a/daemon/rpc_cities_test.go +++ b/daemon/rpc_cities_test.go @@ -52,29 +52,27 @@ func TestRPCCities(t *testing.T) { name: "results for country name with virtual servers only", cm: newMockConfigManager(), servers: serversList(), - countryName: "liThuania", + countryName: "alGeria", statusCode: internal.CodeSuccess, expected: []*pb.ServerGroup{ - {Name: "Kaunas", VirtualLocation: true}, - {Name: "Vilnius", VirtualLocation: true}, + {Name: "Algiers", VirtualLocation: true}, }, }, { name: "results for country code with virtual servers only", cm: newMockConfigManager(), servers: serversList(), - countryName: "lT", + countryName: "dz", statusCode: internal.CodeSuccess, expected: []*pb.ServerGroup{ - {Name: "Kaunas", VirtualLocation: true}, - {Name: "Vilnius", VirtualLocation: true}, + {Name: "Algiers", VirtualLocation: true}, }, }, { name: "no servers for country with virtual servers only and virtual location is off", cm: newMockConfigManager(), servers: serversList(), - countryName: "lT", + countryName: "dZ", disableVirtualServers: true, statusCode: internal.CodeSuccess, expected: []*pb.ServerGroup{}, diff --git a/daemon/rpc_connect_test.go b/daemon/rpc_connect_test.go index 17f48956..d8176cdb 100644 --- a/daemon/rpc_connect_test.go +++ b/daemon/rpc_connect_test.go @@ -2,7 +2,6 @@ package daemon import ( "errors" - "fmt" "net/http" "net/netip" "testing" @@ -96,20 +95,21 @@ type workingLoginChecker struct { vpnErr error isDedicatedIPExpired bool dedicatedIPErr error + dedicatedIPService []auth.DedicatedIPService } func (*workingLoginChecker) IsLoggedIn() bool { return true } func (c *workingLoginChecker) IsVPNExpired() (bool, error) { return c.isVPNExpired, c.vpnErr } func (c *workingLoginChecker) GetDedicatedIPServices() ([]auth.DedicatedIPService, error) { if c.isDedicatedIPExpired { - return nil, fmt.Errorf("dedicated IP is expired") + return nil, nil } if c.dedicatedIPErr != nil { return nil, c.dedicatedIPErr } - return []auth.DedicatedIPService{{ExpiresAt: "", ServerID: 1111}}, nil + return c.dedicatedIPService, nil } type mockAnalytics struct{} @@ -118,21 +118,22 @@ func (*mockAnalytics) Enable() error { return nil } func (*mockAnalytics) Disable() error { return nil } func TestRpcConnect(t *testing.T) { - category.Set(t, category.Route) + category.Set(t, category.Unit) defer testsCleanup() tests := []struct { - name string - serverGroup string - factory FactoryFunc - netw networker.Networker - fw firewall.Service - checker auth.Checker - resp int64 + name string + serverGroup string + serverTag string + factory FactoryFunc + netw networker.Networker + fw firewall.Service + checker auth.Checker + resp int64 + expectedError error }{ { - name: "successful connect", - serverGroup: "", + name: "Quick connect works", factory: func(config.Technology) (vpn.VPN, error) { return &mock.WorkingVPN{}, nil }, @@ -142,8 +143,7 @@ func TestRpcConnect(t *testing.T) { resp: internal.CodeConnected, }, { - name: "failed connect", - serverGroup: "", + name: "Fail for broken Networker and VPN", factory: func(config.Technology) (vpn.VPN, error) { return &mock.FailingVPN{}, nil }, @@ -153,8 +153,7 @@ func TestRpcConnect(t *testing.T) { resp: internal.CodeFailure, }, { - name: "VPN expired", - serverGroup: "", + name: "fFail when VPN subscription is expired", factory: func(config.Technology) (vpn.VPN, error) { return &mock.WorkingVPN{}, nil }, @@ -164,8 +163,7 @@ func TestRpcConnect(t *testing.T) { resp: internal.CodeAccountExpired, }, { - name: "VPN expiration check fails", - serverGroup: "", + name: "Fail when VPN subscription API calls fails", factory: func(config.Technology) (vpn.VPN, error) { return &mock.WorkingVPN{}, nil }, @@ -174,51 +172,194 @@ func TestRpcConnect(t *testing.T) { checker: &workingLoginChecker{vpnErr: errors.New("test error")}, resp: internal.CodeTokenRenewError, }, + { + name: "Connects using country name", + serverTag: "germany", + factory: func(config.Technology) (vpn.VPN, error) { + return &mock.WorkingVPN{}, nil + }, + netw: &testnetworker.Mock{}, + fw: &workingFirewall{}, + checker: &workingLoginChecker{}, + resp: internal.CodeConnected, + }, + { + name: "Connects using country name + city name", + serverTag: "germany berlin", + factory: func(config.Technology) (vpn.VPN, error) { + return &mock.WorkingVPN{}, nil + }, + netw: &testnetworker.Mock{}, + fw: &workingFirewall{}, + checker: &workingLoginChecker{}, + resp: internal.CodeConnected, + }, + { + name: "Connects for city name", + serverTag: "berlin", + factory: func(config.Technology) (vpn.VPN, error) { + return &mock.WorkingVPN{}, nil + }, + netw: &testnetworker.Mock{}, + fw: &workingFirewall{}, + checker: &workingLoginChecker{}, + resp: internal.CodeConnected, + }, + { + name: "Connects using country code + city name", + serverTag: "de berlin", + factory: func(config.Technology) (vpn.VPN, error) { + return &mock.WorkingVPN{}, nil + }, + netw: &testnetworker.Mock{}, + fw: &workingFirewall{}, + checker: &workingLoginChecker{}, + resp: internal.CodeConnected, + }, + { + name: "Connects using country code", + serverTag: "de", + factory: func(config.Technology) (vpn.VPN, error) { + return &mock.WorkingVPN{}, nil + }, + netw: &testnetworker.Mock{}, + fw: &workingFirewall{}, + checker: &workingLoginChecker{}, + resp: internal.CodeConnected, + }, + { + name: "Dedicated IP group connect works", + serverGroup: "Dedicated_IP", + factory: func(config.Technology) (vpn.VPN, error) { + return &mock.WorkingVPN{}, nil + }, + netw: &testnetworker.Mock{}, + fw: &workingFirewall{}, + checker: &workingLoginChecker{ + isDedicatedIPExpired: false, + dedicatedIPService: []auth.DedicatedIPService{{ExpiresAt: "", ServerID: 7}}, + }, + resp: internal.CodeConnected, + }, + { + name: "Dedicated IP with server name works", + serverTag: "lt7", + factory: func(config.Technology) (vpn.VPN, error) { + return &mock.WorkingVPN{}, nil + }, + netw: &testnetworker.Mock{}, + fw: &workingFirewall{}, + checker: &workingLoginChecker{ + isDedicatedIPExpired: false, + dedicatedIPService: []auth.DedicatedIPService{{ExpiresAt: "", ServerID: 7}}, + }, + resp: internal.CodeConnected, + }, + { + name: "fails when Dedicated IP subscription is expired", + serverTag: "lt7", + factory: func(config.Technology) (vpn.VPN, error) { + return &mock.WorkingVPN{}, nil + }, + netw: &testnetworker.Mock{}, + fw: &workingFirewall{}, + checker: &workingLoginChecker{isDedicatedIPExpired: true}, + resp: internal.CodeDedicatedIPRenewError, + }, + { + name: "fails for Dedicated IP when API fails", + serverTag: "lt7", + factory: func(config.Technology) (vpn.VPN, error) { + return &mock.WorkingVPN{}, nil + }, + netw: &testnetworker.Mock{}, + fw: &workingFirewall{}, + checker: &workingLoginChecker{isDedicatedIPExpired: true}, + resp: internal.CodeDedicatedIPRenewError, + }, + { + name: "fails when server not into Dedicated IP servers list", + serverTag: "lt8", + factory: func(config.Technology) (vpn.VPN, error) { + return &mock.WorkingVPN{}, nil + }, + netw: &testnetworker.Mock{}, + fw: &workingFirewall{}, + checker: &workingLoginChecker{ + isDedicatedIPExpired: false, + dedicatedIPService: []auth.DedicatedIPService{{ExpiresAt: "", ServerID: 7}}, + }, + resp: internal.CodeDedicatedIPNoServer, + }, + { + name: "fails because Dedicated IP servers list is empty", + serverTag: "lt7", + factory: func(config.Technology) (vpn.VPN, error) { + return &mock.WorkingVPN{}, nil + }, + netw: &testnetworker.Mock{}, + fw: &workingFirewall{}, + checker: &workingLoginChecker{ + isDedicatedIPExpired: false, + dedicatedIPService: []auth.DedicatedIPService{{ExpiresAt: "", ServerID: auth.NoServerSelected}}, + }, + resp: internal.CodeDedicatedIPServiceButNoServers, + }, } for _, test := range tests { - t.Run(test.name, func(t *testing.T) { - cm := newMockConfigManager() - tokenData := cm.c.TokensData[cm.c.AutoConnectData.ID] - tokenData.TokenExpiry = time.Now().Add(time.Hour * 1).Format(internal.ServerDateFormat) - tokenData.ServiceExpiry = time.Now().Add(time.Hour * 1).Format(internal.ServerDateFormat) - cm.c.TokensData[cm.c.AutoConnectData.ID] = tokenData - dm := testNewDataManager() - api := core.NewDefaultAPI( - "", - "", - http.DefaultClient, - response.NoopValidator{}, - ) - rpc := NewRPC( - internal.Development, - test.checker, - cm, - dm, - api, - &mockServersAPI{}, - &validCredentialsAPI{}, - testNewCDNAPI(), - testNewRepoAPI(), - &mockAuthenticationAPI{}, - "1.0.0", - test.fw, - daemonevents.NewEventsEmpty(), - test.factory, - newEndpointResolverMock(netip.MustParseAddr("127.0.0.1")), - test.netw, - &subs.Subject[string]{}, - &mock.DNSGetter{Names: []string{"1.1.1.1"}}, - nil, - &mockAnalytics{}, - &testnorduser.MockNorduserCombinedService{}, - &RegistryMock{}, - ) - server := &mockRPCServer{} - err := rpc.Connect(&pb.ConnectRequest{ServerGroup: test.serverGroup}, server) - assert.NoError(t, err) - assert.Equal(t, server.msg.Type, test.resp) - }) + // run each test using working API for servers list and using local cached servers list + servers := map[string]core.ServersAPI{ + "Remote": mockServersAPI{}, + "Local": mockFailingServersAPI{}, + } + for key, serversAPI := range servers { + t.Run(test.name+" "+key, func(t *testing.T) { + cm := newMockConfigManager() + tokenData := cm.c.TokensData[cm.c.AutoConnectData.ID] + tokenData.TokenExpiry = time.Now().Add(time.Hour * 1).Format(internal.ServerDateFormat) + tokenData.ServiceExpiry = time.Now().Add(time.Hour * 1).Format(internal.ServerDateFormat) + cm.c.TokensData[cm.c.AutoConnectData.ID] = tokenData + dm := testNewDataManager() + dm.SetServersData(time.Now(), serversList(), "") + api := core.NewDefaultAPI( + "", + "", + http.DefaultClient, + response.NoopValidator{}, + ) + rpc := NewRPC( + internal.Development, + test.checker, + cm, + dm, + api, + serversAPI, + &validCredentialsAPI{}, + testNewCDNAPI(), + testNewRepoAPI(), + &mockAuthenticationAPI{}, + "1.0.0", + test.fw, + daemonevents.NewEventsEmpty(), + test.factory, + newEndpointResolverMock(netip.MustParseAddr("127.0.0.1")), + test.netw, + &subs.Subject[string]{}, + &mock.DNSGetter{Names: []string{"1.1.1.1"}}, + nil, + &mockAnalytics{}, + &testnorduser.MockNorduserCombinedService{}, + &RegistryMock{}, + ) + server := &mockRPCServer{} + err := rpc.Connect(&pb.ConnectRequest{ServerGroup: test.serverGroup, ServerTag: test.serverTag}, server) + assert.Equal(t, test.expectedError, err) + if err == nil { + assert.Equal(t, test.resp, server.msg.Type) + } + }) + } } } diff --git a/daemon/rpc_countries_test.go b/daemon/rpc_countries_test.go index c9dd5709..8103205a 100644 --- a/daemon/rpc_countries_test.go +++ b/daemon/rpc_countries_test.go @@ -44,9 +44,10 @@ func TestRPCCountries(t *testing.T) { servers: serversList(), statusCode: internal.CodeSuccess, expected: []*pb.ServerGroup{ + {Name: "Algeria", VirtualLocation: true}, {Name: "France", VirtualLocation: false}, - {Name: "Germany", VirtualLocation: true}, - {Name: "Lithuania", VirtualLocation: true}, + {Name: "Germany", VirtualLocation: false}, + {Name: "Lithuania", VirtualLocation: false}, }, }, { @@ -57,6 +58,8 @@ func TestRPCCountries(t *testing.T) { statusCode: internal.CodeSuccess, expected: []*pb.ServerGroup{ {Name: "France", VirtualLocation: false}, + {Name: "Germany", VirtualLocation: false}, + {Name: "Lithuania", VirtualLocation: false}, }, }, } diff --git a/daemon/rpc_groups_test.go b/daemon/rpc_groups_test.go index a619cacc..33043bb6 100644 --- a/daemon/rpc_groups_test.go +++ b/daemon/rpc_groups_test.go @@ -87,6 +87,7 @@ func TestRPCGroups_Successful(t *testing.T) { servers: serversList(), statusCode: internal.CodeSuccess, expected: []*pb.ServerGroup{ + {Name: "Dedicated_IP", VirtualLocation: false}, {Name: "Double_VPN", VirtualLocation: false}, {Name: "P2P", VirtualLocation: false}, {Name: "Standard_VPN_Servers", VirtualLocation: false}, @@ -99,6 +100,7 @@ func TestRPCGroups_Successful(t *testing.T) { disableVirtualServers: true, statusCode: internal.CodeSuccess, expected: []*pb.ServerGroup{ + {Name: "Dedicated_IP", VirtualLocation: false}, {Name: "Double_VPN", VirtualLocation: false}, {Name: "P2P", VirtualLocation: false}, {Name: "Standard_VPN_Servers", VirtualLocation: false}, diff --git a/daemon/servers.go b/daemon/servers.go index 71b3df96..58fe560f 100644 --- a/daemon/servers.go +++ b/daemon/servers.go @@ -126,6 +126,11 @@ func getServers( serverGroup, obfuscated, ) + + // remove all DIP servers from the list if the search wasn't made for a server name and there is more than 1 server found + if err == nil && serverTag.Action != core.ServerByName && len(ret) > 1 { + ret = slices.DeleteFunc(ret, func(s core.Server) bool { return isDedicatedIP(s) }) + } } if err != nil { @@ -246,7 +251,7 @@ func filterServers( ) ([]core.Server, error) { ret := internal.Filter(servers, canConnect(tech, protocol, serverTag, group, obfuscated)) if len(ret) == 0 { - log.Println(internal.DebugPrefix, "no servers found for:", tech, protocol, serverTag, group, obfuscated) + log.Println(internal.ErrorPrefix, "no servers found locally for:", tech, protocol, serverTag, group, obfuscated) return nil, internal.ErrServerIsUnavailable } return ret, nil diff --git a/daemon/servers_test.go b/daemon/servers_test.go index 8ffc1026..7b6a2297 100644 --- a/daemon/servers_test.go +++ b/daemon/servers_test.go @@ -714,13 +714,27 @@ func TestPickServer(t *testing.T) { expectedServerName: "Germany #3", }, { - name: "find server when virtual locations are disabled", + name: "find server using country + city name", api: mockFailingServersAPI{}, servers: serversList(), tech: config.Technology_NORDLYNX, - onlyPhysicServers: true, - tag: "", - expectedServerName: "France #1", + tag: "germany berlin", + expectedServerName: "Germany #3", + }, + { + name: "find server using country code + city name", + api: mockFailingServersAPI{}, + servers: serversList(), + tech: config.Technology_NORDLYNX, + tag: "de berlin", + expectedServerName: "Germany #3", + }, + { + name: "find server when virtual locations are disabled", + api: mockFailingServersAPI{}, + servers: serversList(), + tech: config.Technology_NORDLYNX, + onlyPhysicServers: true, }, } @@ -731,7 +745,9 @@ func TestPickServer(t *testing.T) { assert.Equal(t, test.expectedError, err) assert.Equal(t, test.expectedRemoteServer, remote) - assert.Equal(t, test.expectedServerName, server.Name) + if len(test.expectedServerName) > 0 { + assert.Equal(t, test.expectedServerName, server.Name) + } }) } } diff --git a/test/qa/test_meshnet_routing.py b/test/qa/test_meshnet_routing.py index bb538d24..ec0c3fef 100644 --- a/test/qa/test_meshnet_routing.py +++ b/test/qa/test_meshnet_routing.py @@ -93,8 +93,6 @@ def test_killswitch_exitnode(lan_discovery: bool, local: bool): assert network.is_available() -@pytest.mark.flaky(reruns=2, reruns_delay=90) -@timeout_decorator.timeout(130) def test_route_traffic_to_each_other(): peer_list = meshnet.PeerList.from_str(sh.nordvpn.mesh.peer.list()) peer_hostname = peer_list.get_external_peer().hostname