IVPN server data update code and ISP filter (#578)
- Use IVPN's HTTP API instead of their .zip file - Unit tests for API and GetServers - Paves the way for Wireguard - Update server information for IVPN - Add `ISP` filter for IVPN
This commit is contained in:
@@ -24,6 +24,11 @@ func (settings *Provider) readIvpn(r reader) (err error) {
|
||||
return fmt.Errorf("environment variable CITY: %w", err)
|
||||
}
|
||||
|
||||
settings.ServerSelection.ISPs, err = r.env.CSVInside("ISP", constants.IvpnISPChoices())
|
||||
if err != nil {
|
||||
return fmt.Errorf("environment variable ISP: %w", err)
|
||||
}
|
||||
|
||||
settings.ServerSelection.Hostnames, err = r.env.CSVInside("SERVER_HOSTNAME", constants.IvpnHostnameChoices())
|
||||
if err != nil {
|
||||
return fmt.Errorf("environment variable SERVER_HOSTNAME: %w", err)
|
||||
|
||||
@@ -34,6 +34,7 @@ func Test_Provider_readIvpn(t *testing.T) {
|
||||
targetIP singleStringCall
|
||||
countries sliceStringCall
|
||||
cities sliceStringCall
|
||||
isps sliceStringCall
|
||||
hostnames sliceStringCall
|
||||
settings Provider
|
||||
err error
|
||||
@@ -62,10 +63,21 @@ func Test_Provider_readIvpn(t *testing.T) {
|
||||
},
|
||||
err: errors.New("environment variable CITY: dummy test error"),
|
||||
},
|
||||
"isps error": {
|
||||
targetIP: singleStringCall{call: true},
|
||||
countries: sliceStringCall{call: true},
|
||||
cities: sliceStringCall{call: true},
|
||||
isps: sliceStringCall{call: true, err: errDummy},
|
||||
settings: Provider{
|
||||
Name: constants.Ivpn,
|
||||
},
|
||||
err: errors.New("environment variable ISP: dummy test error"),
|
||||
},
|
||||
"hostnames error": {
|
||||
targetIP: singleStringCall{call: true},
|
||||
countries: sliceStringCall{call: true},
|
||||
cities: sliceStringCall{call: true},
|
||||
isps: sliceStringCall{call: true},
|
||||
hostnames: sliceStringCall{call: true, err: errDummy},
|
||||
settings: Provider{
|
||||
Name: constants.Ivpn,
|
||||
@@ -76,6 +88,7 @@ func Test_Provider_readIvpn(t *testing.T) {
|
||||
targetIP: singleStringCall{call: true},
|
||||
countries: sliceStringCall{call: true},
|
||||
cities: sliceStringCall{call: true},
|
||||
isps: sliceStringCall{call: true},
|
||||
hostnames: sliceStringCall{call: true},
|
||||
protocol: singleStringCall{call: true, err: errDummy},
|
||||
settings: Provider{
|
||||
@@ -87,6 +100,7 @@ func Test_Provider_readIvpn(t *testing.T) {
|
||||
targetIP: singleStringCall{call: true},
|
||||
countries: sliceStringCall{call: true},
|
||||
cities: sliceStringCall{call: true},
|
||||
isps: sliceStringCall{call: true},
|
||||
hostnames: sliceStringCall{call: true},
|
||||
protocol: singleStringCall{call: true},
|
||||
settings: Provider{
|
||||
@@ -97,6 +111,7 @@ func Test_Provider_readIvpn(t *testing.T) {
|
||||
targetIP: singleStringCall{call: true, value: "1.2.3.4"},
|
||||
countries: sliceStringCall{call: true, values: []string{"A", "B"}},
|
||||
cities: sliceStringCall{call: true, values: []string{"C", "D"}},
|
||||
isps: sliceStringCall{call: true, values: []string{"ISP 1"}},
|
||||
hostnames: sliceStringCall{call: true, values: []string{"E", "F"}},
|
||||
protocol: singleStringCall{call: true, value: constants.TCP},
|
||||
settings: Provider{
|
||||
@@ -108,6 +123,7 @@ func Test_Provider_readIvpn(t *testing.T) {
|
||||
TargetIP: net.IPv4(1, 2, 3, 4),
|
||||
Countries: []string{"A", "B"},
|
||||
Cities: []string{"C", "D"},
|
||||
ISPs: []string{"ISP 1"},
|
||||
Hostnames: []string{"E", "F"},
|
||||
},
|
||||
},
|
||||
@@ -136,6 +152,10 @@ func Test_Provider_readIvpn(t *testing.T) {
|
||||
env.EXPECT().CSVInside("CITY", constants.IvpnCityChoices()).
|
||||
Return(testCase.cities.values, testCase.cities.err)
|
||||
}
|
||||
if testCase.isps.call {
|
||||
env.EXPECT().CSVInside("ISP", constants.IvpnISPChoices()).
|
||||
Return(testCase.isps.values, testCase.isps.err)
|
||||
}
|
||||
if testCase.hostnames.call {
|
||||
env.EXPECT().CSVInside("SERVER_HOSTNAME", constants.IvpnHostnameChoices()).
|
||||
Return(testCase.hostnames.values, testCase.hostnames.err)
|
||||
|
||||
@@ -28,6 +28,15 @@ func IvpnCityChoices() (choices []string) {
|
||||
return makeUnique(choices)
|
||||
}
|
||||
|
||||
func IvpnISPChoices() (choices []string) {
|
||||
servers := IvpnServers()
|
||||
choices = make([]string, len(servers))
|
||||
for i := range servers {
|
||||
choices[i] = servers[i].ISP
|
||||
}
|
||||
return makeUnique(choices)
|
||||
}
|
||||
|
||||
func IvpnHostnameChoices() (choices []string) {
|
||||
servers := IvpnServers()
|
||||
choices = make([]string, len(servers))
|
||||
|
||||
@@ -25970,13 +25970,14 @@
|
||||
]
|
||||
},
|
||||
"ivpn": {
|
||||
"version": 1,
|
||||
"timestamp": 1629490838,
|
||||
"version": 2,
|
||||
"timestamp": 1629589118,
|
||||
"servers": [
|
||||
{
|
||||
"country": "Australia",
|
||||
"city": "",
|
||||
"hostname": "au-nsw.gw.ivpn.net",
|
||||
"city": "Sydney",
|
||||
"isp": "M247",
|
||||
"hostname": "au-nsw1.gw.ivpn.net",
|
||||
"tcp": false,
|
||||
"udp": true,
|
||||
"ips": [
|
||||
@@ -25985,8 +25986,9 @@
|
||||
},
|
||||
{
|
||||
"country": "Austria",
|
||||
"city": "",
|
||||
"hostname": "at.gw.ivpn.net",
|
||||
"city": "Vienna",
|
||||
"isp": "M247",
|
||||
"hostname": "at1.gw.ivpn.net",
|
||||
"tcp": false,
|
||||
"udp": true,
|
||||
"ips": [
|
||||
@@ -25995,8 +25997,9 @@
|
||||
},
|
||||
{
|
||||
"country": "Belgium",
|
||||
"city": "",
|
||||
"hostname": "be.gw.ivpn.net",
|
||||
"city": "Brussels",
|
||||
"isp": "M247",
|
||||
"hostname": "be1.gw.ivpn.net",
|
||||
"tcp": false,
|
||||
"udp": true,
|
||||
"ips": [
|
||||
@@ -26005,8 +26008,9 @@
|
||||
},
|
||||
{
|
||||
"country": "Brazil",
|
||||
"city": "",
|
||||
"hostname": "br.gw.ivpn.net",
|
||||
"city": "Franca",
|
||||
"isp": "Qnax",
|
||||
"hostname": "br1.gw.ivpn.net",
|
||||
"tcp": false,
|
||||
"udp": true,
|
||||
"ips": [
|
||||
@@ -26015,8 +26019,9 @@
|
||||
},
|
||||
{
|
||||
"country": "Bulgaria",
|
||||
"city": "",
|
||||
"hostname": "bg.gw.ivpn.net",
|
||||
"city": "Sofia",
|
||||
"isp": "M247",
|
||||
"hostname": "bg1.gw.ivpn.net",
|
||||
"tcp": false,
|
||||
"udp": true,
|
||||
"ips": [
|
||||
@@ -26026,7 +26031,8 @@
|
||||
{
|
||||
"country": "Canada",
|
||||
"city": "Montreal",
|
||||
"hostname": "ca-qc.gw.ivpn.net",
|
||||
"isp": "M247",
|
||||
"hostname": "ca-qc1.gw.ivpn.net",
|
||||
"tcp": false,
|
||||
"udp": true,
|
||||
"ips": [
|
||||
@@ -26036,17 +26042,30 @@
|
||||
{
|
||||
"country": "Canada",
|
||||
"city": "Toronto",
|
||||
"hostname": "ca.gw.ivpn.net",
|
||||
"isp": "Amanah",
|
||||
"hostname": "ca1.gw.ivpn.net",
|
||||
"tcp": false,
|
||||
"udp": true,
|
||||
"ips": [
|
||||
"104.254.90.178"
|
||||
]
|
||||
},
|
||||
{
|
||||
"country": "Canada",
|
||||
"city": "Toronto",
|
||||
"isp": "Amanah",
|
||||
"hostname": "ca2.gw.ivpn.net",
|
||||
"tcp": false,
|
||||
"udp": true,
|
||||
"ips": [
|
||||
"172.86.186.170"
|
||||
]
|
||||
},
|
||||
{
|
||||
"country": "Czech Republic",
|
||||
"city": "",
|
||||
"hostname": "cz.gw.ivpn.net",
|
||||
"city": "Prague",
|
||||
"isp": "Datapacket",
|
||||
"hostname": "cz1.gw.ivpn.net",
|
||||
"tcp": false,
|
||||
"udp": true,
|
||||
"ips": [
|
||||
@@ -26055,8 +26074,9 @@
|
||||
},
|
||||
{
|
||||
"country": "Denmark",
|
||||
"city": "",
|
||||
"hostname": "dk.gw.ivpn.net",
|
||||
"city": "Copenhagen",
|
||||
"isp": "M247",
|
||||
"hostname": "dk1.gw.ivpn.net",
|
||||
"tcp": false,
|
||||
"udp": true,
|
||||
"ips": [
|
||||
@@ -26065,8 +26085,9 @@
|
||||
},
|
||||
{
|
||||
"country": "Finland",
|
||||
"city": "",
|
||||
"hostname": "fi.gw.ivpn.net",
|
||||
"city": "Helsinki",
|
||||
"isp": "Creanova",
|
||||
"hostname": "fi1.gw.ivpn.net",
|
||||
"tcp": false,
|
||||
"udp": true,
|
||||
"ips": [
|
||||
@@ -26075,8 +26096,9 @@
|
||||
},
|
||||
{
|
||||
"country": "France",
|
||||
"city": "",
|
||||
"hostname": "fr.gw.ivpn.net",
|
||||
"city": "Paris",
|
||||
"isp": "Datapacket",
|
||||
"hostname": "fr1.gw.ivpn.net",
|
||||
"tcp": false,
|
||||
"udp": true,
|
||||
"ips": [
|
||||
@@ -26085,8 +26107,20 @@
|
||||
},
|
||||
{
|
||||
"country": "Germany",
|
||||
"city": "",
|
||||
"hostname": "de.gw.ivpn.net",
|
||||
"city": "Frankfurt",
|
||||
"isp": "Leaseweb",
|
||||
"hostname": "de1.gw.ivpn.net",
|
||||
"tcp": false,
|
||||
"udp": true,
|
||||
"ips": [
|
||||
"178.162.222.40"
|
||||
]
|
||||
},
|
||||
{
|
||||
"country": "Germany",
|
||||
"city": "Frankfurt",
|
||||
"isp": "Leaseweb",
|
||||
"hostname": "de2.gw.ivpn.net",
|
||||
"tcp": false,
|
||||
"udp": true,
|
||||
"ips": [
|
||||
@@ -26095,8 +26129,20 @@
|
||||
},
|
||||
{
|
||||
"country": "Hong Kong",
|
||||
"city": "",
|
||||
"hostname": "hk.gw.ivpn.net",
|
||||
"city": "Hong Kong",
|
||||
"isp": "Leaseweb",
|
||||
"hostname": "hk1.gw.ivpn.net",
|
||||
"tcp": false,
|
||||
"udp": true,
|
||||
"ips": [
|
||||
"209.58.189.163"
|
||||
]
|
||||
},
|
||||
{
|
||||
"country": "Hong Kong",
|
||||
"city": "Hong Kong",
|
||||
"isp": "Leaseweb",
|
||||
"hostname": "hk2.gw.ivpn.net",
|
||||
"tcp": false,
|
||||
"udp": true,
|
||||
"ips": [
|
||||
@@ -26105,8 +26151,9 @@
|
||||
},
|
||||
{
|
||||
"country": "Hungary",
|
||||
"city": "",
|
||||
"hostname": "hu.gw.ivpn.net",
|
||||
"city": "Budapest",
|
||||
"isp": "M247",
|
||||
"hostname": "hu1.gw.ivpn.net",
|
||||
"tcp": false,
|
||||
"udp": true,
|
||||
"ips": [
|
||||
@@ -26115,8 +26162,9 @@
|
||||
},
|
||||
{
|
||||
"country": "Iceland",
|
||||
"city": "",
|
||||
"hostname": "is.gw.ivpn.net",
|
||||
"city": "Reykjavik",
|
||||
"isp": "Advania",
|
||||
"hostname": "is1.gw.ivpn.net",
|
||||
"tcp": false,
|
||||
"udp": true,
|
||||
"ips": [
|
||||
@@ -26125,8 +26173,9 @@
|
||||
},
|
||||
{
|
||||
"country": "Israel",
|
||||
"city": "",
|
||||
"hostname": "il.gw.ivpn.net",
|
||||
"city": "Holon, Tel Aviv",
|
||||
"isp": "HQServ",
|
||||
"hostname": "il1.gw.ivpn.net",
|
||||
"tcp": false,
|
||||
"udp": true,
|
||||
"ips": [
|
||||
@@ -26135,8 +26184,9 @@
|
||||
},
|
||||
{
|
||||
"country": "Italy",
|
||||
"city": "",
|
||||
"hostname": "it.gw.ivpn.net",
|
||||
"city": "Milan",
|
||||
"isp": "SEFlow",
|
||||
"hostname": "it1.gw.ivpn.net",
|
||||
"tcp": false,
|
||||
"udp": true,
|
||||
"ips": [
|
||||
@@ -26145,8 +26195,9 @@
|
||||
},
|
||||
{
|
||||
"country": "Japan",
|
||||
"city": "",
|
||||
"hostname": "jp.gw.ivpn.net",
|
||||
"city": "Tokyo",
|
||||
"isp": "M247",
|
||||
"hostname": "jp1.gw.ivpn.net",
|
||||
"tcp": false,
|
||||
"udp": true,
|
||||
"ips": [
|
||||
@@ -26155,8 +26206,9 @@
|
||||
},
|
||||
{
|
||||
"country": "Luxembourg",
|
||||
"city": "",
|
||||
"hostname": "lu.gw.ivpn.net",
|
||||
"city": "Luxembourg",
|
||||
"isp": "Evoluso",
|
||||
"hostname": "lu1.gw.ivpn.net",
|
||||
"tcp": false,
|
||||
"udp": true,
|
||||
"ips": [
|
||||
@@ -26165,18 +26217,75 @@
|
||||
},
|
||||
{
|
||||
"country": "Netherlands",
|
||||
"city": "",
|
||||
"hostname": "nl.gw.ivpn.net",
|
||||
"city": "Amsterdam",
|
||||
"isp": "Leaseweb",
|
||||
"hostname": "nl3.gw.ivpn.net",
|
||||
"tcp": false,
|
||||
"udp": true,
|
||||
"ips": [
|
||||
"95.211.172.68"
|
||||
]
|
||||
},
|
||||
{
|
||||
"country": "Netherlands",
|
||||
"city": "Amsterdam",
|
||||
"isp": "Leaseweb",
|
||||
"hostname": "nl4.gw.ivpn.net",
|
||||
"tcp": false,
|
||||
"udp": true,
|
||||
"ips": [
|
||||
"95.211.172.95"
|
||||
]
|
||||
},
|
||||
{
|
||||
"country": "Netherlands",
|
||||
"city": "Amsterdam",
|
||||
"isp": "Leaseweb",
|
||||
"hostname": "nl5.gw.ivpn.net",
|
||||
"tcp": false,
|
||||
"udp": true,
|
||||
"ips": [
|
||||
"95.211.187.222"
|
||||
]
|
||||
},
|
||||
{
|
||||
"country": "Netherlands",
|
||||
"city": "Amsterdam",
|
||||
"isp": "Leaseweb",
|
||||
"hostname": "nl6.gw.ivpn.net",
|
||||
"tcp": false,
|
||||
"udp": true,
|
||||
"ips": [
|
||||
"95.211.187.228"
|
||||
]
|
||||
},
|
||||
{
|
||||
"country": "Netherlands",
|
||||
"city": "Amsterdam",
|
||||
"isp": "Leaseweb",
|
||||
"hostname": "nl7.gw.ivpn.net",
|
||||
"tcp": false,
|
||||
"udp": true,
|
||||
"ips": [
|
||||
"95.211.95.22"
|
||||
]
|
||||
},
|
||||
{
|
||||
"country": "Netherlands",
|
||||
"city": "Amsterdam",
|
||||
"isp": "Leaseweb",
|
||||
"hostname": "nl8.gw.ivpn.net",
|
||||
"tcp": false,
|
||||
"udp": true,
|
||||
"ips": [
|
||||
"95.211.172.18"
|
||||
]
|
||||
},
|
||||
{
|
||||
"country": "Norway",
|
||||
"city": "",
|
||||
"hostname": "no.gw.ivpn.net",
|
||||
"city": "Oslo",
|
||||
"isp": "Servethewrld",
|
||||
"hostname": "no1.gw.ivpn.net",
|
||||
"tcp": false,
|
||||
"udp": true,
|
||||
"ips": [
|
||||
@@ -26185,8 +26294,9 @@
|
||||
},
|
||||
{
|
||||
"country": "Poland",
|
||||
"city": "",
|
||||
"hostname": "pl.gw.ivpn.net",
|
||||
"city": "Warsaw",
|
||||
"isp": "Datapacket",
|
||||
"hostname": "pl1.gw.ivpn.net",
|
||||
"tcp": false,
|
||||
"udp": true,
|
||||
"ips": [
|
||||
@@ -26195,8 +26305,9 @@
|
||||
},
|
||||
{
|
||||
"country": "Portugal",
|
||||
"city": "",
|
||||
"hostname": "pt.gw.ivpn.net",
|
||||
"city": "Lisbon",
|
||||
"isp": "Hostwebis",
|
||||
"hostname": "pt1.gw.ivpn.net",
|
||||
"tcp": false,
|
||||
"udp": true,
|
||||
"ips": [
|
||||
@@ -26205,8 +26316,9 @@
|
||||
},
|
||||
{
|
||||
"country": "Romania",
|
||||
"city": "",
|
||||
"hostname": "ro.gw.ivpn.net",
|
||||
"city": "Bucharest",
|
||||
"isp": "M247",
|
||||
"hostname": "ro1.gw.ivpn.net",
|
||||
"tcp": false,
|
||||
"udp": true,
|
||||
"ips": [
|
||||
@@ -26215,8 +26327,9 @@
|
||||
},
|
||||
{
|
||||
"country": "Serbia",
|
||||
"city": "",
|
||||
"hostname": "rs.gw.ivpn.net",
|
||||
"city": "Belgrade",
|
||||
"isp": "M247",
|
||||
"hostname": "rs1.gw.ivpn.net",
|
||||
"tcp": false,
|
||||
"udp": true,
|
||||
"ips": [
|
||||
@@ -26225,8 +26338,9 @@
|
||||
},
|
||||
{
|
||||
"country": "Singapore",
|
||||
"city": "",
|
||||
"hostname": "sg.gw.ivpn.net",
|
||||
"city": "Singapore",
|
||||
"isp": "M247",
|
||||
"hostname": "sg1.gw.ivpn.net",
|
||||
"tcp": false,
|
||||
"udp": true,
|
||||
"ips": [
|
||||
@@ -26235,8 +26349,9 @@
|
||||
},
|
||||
{
|
||||
"country": "Slovakia",
|
||||
"city": "",
|
||||
"hostname": "sk.gw.ivpn.net",
|
||||
"city": "Bratislava",
|
||||
"isp": "M247",
|
||||
"hostname": "sk1.gw.ivpn.net",
|
||||
"tcp": false,
|
||||
"udp": true,
|
||||
"ips": [
|
||||
@@ -26245,8 +26360,9 @@
|
||||
},
|
||||
{
|
||||
"country": "Spain",
|
||||
"city": "",
|
||||
"hostname": "es.gw.ivpn.net",
|
||||
"city": "Madrid",
|
||||
"isp": "Datapacket",
|
||||
"hostname": "es1.gw.ivpn.net",
|
||||
"tcp": false,
|
||||
"udp": true,
|
||||
"ips": [
|
||||
@@ -26255,8 +26371,9 @@
|
||||
},
|
||||
{
|
||||
"country": "Sweden",
|
||||
"city": "",
|
||||
"hostname": "se.gw.ivpn.net",
|
||||
"city": "Stockholm",
|
||||
"isp": "GleSyS",
|
||||
"hostname": "se1.gw.ivpn.net",
|
||||
"tcp": false,
|
||||
"udp": true,
|
||||
"ips": [
|
||||
@@ -26265,8 +26382,9 @@
|
||||
},
|
||||
{
|
||||
"country": "Switzerland",
|
||||
"city": "",
|
||||
"hostname": "ch.gw.ivpn.net",
|
||||
"city": "Zurich",
|
||||
"isp": "M247",
|
||||
"hostname": "ch1.gw.ivpn.net",
|
||||
"tcp": false,
|
||||
"udp": true,
|
||||
"ips": [
|
||||
@@ -26274,129 +26392,21 @@
|
||||
]
|
||||
},
|
||||
{
|
||||
"country": "USA",
|
||||
"city": "Atlanta",
|
||||
"hostname": "us-ga.gw.ivpn.net",
|
||||
"country": "Switzerland",
|
||||
"city": "Zurich",
|
||||
"isp": "Privatelayer",
|
||||
"hostname": "ch3.gw.ivpn.net",
|
||||
"tcp": false,
|
||||
"udp": true,
|
||||
"ips": [
|
||||
"104.129.24.146"
|
||||
]
|
||||
},
|
||||
{
|
||||
"country": "USA",
|
||||
"city": "Chicago",
|
||||
"hostname": "us-il.gw.ivpn.net",
|
||||
"tcp": false,
|
||||
"udp": true,
|
||||
"ips": [
|
||||
"72.11.137.146"
|
||||
]
|
||||
},
|
||||
{
|
||||
"country": "USA",
|
||||
"city": "Dallas",
|
||||
"hostname": "us-tx.gw.ivpn.net",
|
||||
"tcp": false,
|
||||
"udp": true,
|
||||
"ips": [
|
||||
"96.44.189.194"
|
||||
]
|
||||
},
|
||||
{
|
||||
"country": "USA",
|
||||
"city": "Las Vegas",
|
||||
"hostname": "us-nv.gw.ivpn.net",
|
||||
"tcp": false,
|
||||
"udp": true,
|
||||
"ips": [
|
||||
"185.242.5.34"
|
||||
]
|
||||
},
|
||||
{
|
||||
"country": "USA",
|
||||
"city": "Los Angeles",
|
||||
"hostname": "us-ca.gw.ivpn.net",
|
||||
"tcp": false,
|
||||
"udp": true,
|
||||
"ips": [
|
||||
"69.12.80.146"
|
||||
]
|
||||
},
|
||||
{
|
||||
"country": "USA",
|
||||
"city": "Miami",
|
||||
"hostname": "us-fl.gw.ivpn.net",
|
||||
"tcp": false,
|
||||
"udp": true,
|
||||
"ips": [
|
||||
"173.44.49.90"
|
||||
]
|
||||
},
|
||||
{
|
||||
"country": "USA",
|
||||
"city": "New Jersey",
|
||||
"hostname": "us-nj.gw.ivpn.net",
|
||||
"tcp": false,
|
||||
"udp": true,
|
||||
"ips": [
|
||||
"23.226.128.18"
|
||||
]
|
||||
},
|
||||
{
|
||||
"country": "USA",
|
||||
"city": "New York",
|
||||
"hostname": "us-ny.gw.ivpn.net",
|
||||
"tcp": false,
|
||||
"udp": true,
|
||||
"ips": [
|
||||
"64.120.44.114"
|
||||
]
|
||||
},
|
||||
{
|
||||
"country": "USA",
|
||||
"city": "Phoenix",
|
||||
"hostname": "us-az.gw.ivpn.net",
|
||||
"tcp": false,
|
||||
"udp": true,
|
||||
"ips": [
|
||||
"193.37.254.130"
|
||||
]
|
||||
},
|
||||
{
|
||||
"country": "USA",
|
||||
"city": "Salt Lake City",
|
||||
"hostname": "us-ut.gw.ivpn.net",
|
||||
"tcp": false,
|
||||
"udp": true,
|
||||
"ips": [
|
||||
"198.105.216.28"
|
||||
]
|
||||
},
|
||||
{
|
||||
"country": "USA",
|
||||
"city": "Seattle",
|
||||
"hostname": "us-wa.gw.ivpn.net",
|
||||
"tcp": false,
|
||||
"udp": true,
|
||||
"ips": [
|
||||
"23.19.87.209"
|
||||
]
|
||||
},
|
||||
{
|
||||
"country": "USA",
|
||||
"city": "Washington",
|
||||
"hostname": "us-dc.gw.ivpn.net",
|
||||
"tcp": false,
|
||||
"udp": true,
|
||||
"ips": [
|
||||
"207.244.108.207"
|
||||
"141.255.166.194"
|
||||
]
|
||||
},
|
||||
{
|
||||
"country": "Ukraine",
|
||||
"city": "",
|
||||
"hostname": "ua.gw.ivpn.net",
|
||||
"city": "Kharkiv",
|
||||
"isp": "Xservers",
|
||||
"hostname": "ua1.gw.ivpn.net",
|
||||
"tcp": false,
|
||||
"udp": true,
|
||||
"ips": [
|
||||
@@ -26406,23 +26416,255 @@
|
||||
{
|
||||
"country": "United Kingdom",
|
||||
"city": "London",
|
||||
"hostname": "gb.gw.ivpn.net",
|
||||
"isp": "Datapacket",
|
||||
"hostname": "gb1.gw.ivpn.net",
|
||||
"tcp": false,
|
||||
"udp": true,
|
||||
"ips": [
|
||||
"185.59.221.88",
|
||||
"185.59.221.133"
|
||||
]
|
||||
},
|
||||
{
|
||||
"country": "United Kingdom",
|
||||
"city": "London",
|
||||
"isp": "Datapacket",
|
||||
"hostname": "gb2.gw.ivpn.net",
|
||||
"tcp": false,
|
||||
"udp": true,
|
||||
"ips": [
|
||||
"185.59.221.88"
|
||||
]
|
||||
},
|
||||
{
|
||||
"country": "United Kingdom",
|
||||
"city": "Manchester",
|
||||
"hostname": "gb-man.gw.ivpn.net",
|
||||
"isp": "M247",
|
||||
"hostname": "gb-man1.gw.ivpn.net",
|
||||
"tcp": false,
|
||||
"udp": true,
|
||||
"ips": [
|
||||
"89.238.141.228"
|
||||
]
|
||||
},
|
||||
{
|
||||
"country": "United States",
|
||||
"city": "Atlanta, GA",
|
||||
"isp": "Quadranet",
|
||||
"hostname": "us-ga1.gw.ivpn.net",
|
||||
"tcp": false,
|
||||
"udp": true,
|
||||
"ips": [
|
||||
"104.129.24.146"
|
||||
]
|
||||
},
|
||||
{
|
||||
"country": "United States",
|
||||
"city": "Atlanta, GA",
|
||||
"isp": "Quadranet",
|
||||
"hostname": "us-ga2.gw.ivpn.net",
|
||||
"tcp": false,
|
||||
"udp": true,
|
||||
"ips": [
|
||||
"107.150.22.74"
|
||||
]
|
||||
},
|
||||
{
|
||||
"country": "United States",
|
||||
"city": "Chicago, IL",
|
||||
"isp": "Quadranet",
|
||||
"hostname": "us-il1.gw.ivpn.net",
|
||||
"tcp": false,
|
||||
"udp": true,
|
||||
"ips": [
|
||||
"107.150.28.82"
|
||||
]
|
||||
},
|
||||
{
|
||||
"country": "United States",
|
||||
"city": "Chicago, IL",
|
||||
"isp": "Quadranet",
|
||||
"hostname": "us-il2.gw.ivpn.net",
|
||||
"tcp": false,
|
||||
"udp": true,
|
||||
"ips": [
|
||||
"72.11.137.146"
|
||||
]
|
||||
},
|
||||
{
|
||||
"country": "United States",
|
||||
"city": "Dallas, TX",
|
||||
"isp": "Quadranet",
|
||||
"hostname": "us-tx1.gw.ivpn.net",
|
||||
"tcp": false,
|
||||
"udp": true,
|
||||
"ips": [
|
||||
"96.44.189.194"
|
||||
]
|
||||
},
|
||||
{
|
||||
"country": "United States",
|
||||
"city": "Dallas, TX",
|
||||
"isp": "Quadranet",
|
||||
"hostname": "us-tx2.gw.ivpn.net",
|
||||
"tcp": false,
|
||||
"udp": true,
|
||||
"ips": [
|
||||
"96.44.142.74"
|
||||
]
|
||||
},
|
||||
{
|
||||
"country": "United States",
|
||||
"city": "Las Vegas, NV",
|
||||
"isp": "M247",
|
||||
"hostname": "us-nv1.gw.ivpn.net",
|
||||
"tcp": false,
|
||||
"udp": true,
|
||||
"ips": [
|
||||
"185.242.5.34"
|
||||
]
|
||||
},
|
||||
{
|
||||
"country": "United States",
|
||||
"city": "Los Angeles, CA",
|
||||
"isp": "Quadranet",
|
||||
"hostname": "us-ca1.gw.ivpn.net",
|
||||
"tcp": false,
|
||||
"udp": true,
|
||||
"ips": [
|
||||
"173.254.196.58"
|
||||
]
|
||||
},
|
||||
{
|
||||
"country": "United States",
|
||||
"city": "Los Angeles, CA",
|
||||
"isp": "Quadranet",
|
||||
"hostname": "us-ca2.gw.ivpn.net",
|
||||
"tcp": false,
|
||||
"udp": true,
|
||||
"ips": [
|
||||
"69.12.80.146"
|
||||
]
|
||||
},
|
||||
{
|
||||
"country": "United States",
|
||||
"city": "Los Angeles, CA",
|
||||
"isp": "Leaseweb",
|
||||
"hostname": "us-ca3.gw.ivpn.net",
|
||||
"tcp": false,
|
||||
"udp": true,
|
||||
"ips": [
|
||||
"209.58.130.196"
|
||||
]
|
||||
},
|
||||
{
|
||||
"country": "United States",
|
||||
"city": "Los Angeles, CA",
|
||||
"isp": "Quadranet",
|
||||
"hostname": "us-ca4.gw.ivpn.net",
|
||||
"tcp": false,
|
||||
"udp": true,
|
||||
"ips": [
|
||||
"173.254.204.202"
|
||||
]
|
||||
},
|
||||
{
|
||||
"country": "United States",
|
||||
"city": "Miami, FL",
|
||||
"isp": "Quadranet",
|
||||
"hostname": "us-fl1.gw.ivpn.net",
|
||||
"tcp": false,
|
||||
"udp": true,
|
||||
"ips": [
|
||||
"173.44.49.90"
|
||||
]
|
||||
},
|
||||
{
|
||||
"country": "United States",
|
||||
"city": "New Jersey, NJ",
|
||||
"isp": "Quadranet",
|
||||
"hostname": "us-nj3.gw.ivpn.net",
|
||||
"tcp": false,
|
||||
"udp": true,
|
||||
"ips": [
|
||||
"23.226.128.18"
|
||||
]
|
||||
},
|
||||
{
|
||||
"country": "United States",
|
||||
"city": "New Jersey, NJ",
|
||||
"isp": "M247",
|
||||
"hostname": "us-nj4.gw.ivpn.net",
|
||||
"tcp": false,
|
||||
"udp": true,
|
||||
"ips": [
|
||||
"194.36.111.50"
|
||||
]
|
||||
},
|
||||
{
|
||||
"country": "United States",
|
||||
"city": "New York, NY",
|
||||
"isp": "Leaseweb",
|
||||
"hostname": "us-ny1.gw.ivpn.net",
|
||||
"tcp": false,
|
||||
"udp": true,
|
||||
"ips": [
|
||||
"64.120.44.114"
|
||||
]
|
||||
},
|
||||
{
|
||||
"country": "United States",
|
||||
"city": "New York, NY",
|
||||
"isp": "Leaseweb",
|
||||
"hostname": "us-ny2.gw.ivpn.net",
|
||||
"tcp": false,
|
||||
"udp": true,
|
||||
"ips": [
|
||||
"173.234.153.130"
|
||||
]
|
||||
},
|
||||
{
|
||||
"country": "United States",
|
||||
"city": "Phoenix, AZ",
|
||||
"isp": "M247",
|
||||
"hostname": "us-az1.gw.ivpn.net",
|
||||
"tcp": false,
|
||||
"udp": true,
|
||||
"ips": [
|
||||
"193.37.254.130"
|
||||
]
|
||||
},
|
||||
{
|
||||
"country": "United States",
|
||||
"city": "Salt Lake City, UT",
|
||||
"isp": "100TB",
|
||||
"hostname": "us-ut1.gw.ivpn.net",
|
||||
"tcp": false,
|
||||
"udp": true,
|
||||
"ips": [
|
||||
"198.105.216.28"
|
||||
]
|
||||
},
|
||||
{
|
||||
"country": "United States",
|
||||
"city": "Seattle, WA",
|
||||
"isp": "Leaseweb",
|
||||
"hostname": "us-wa1.gw.ivpn.net",
|
||||
"tcp": false,
|
||||
"udp": true,
|
||||
"ips": [
|
||||
"23.19.87.209"
|
||||
]
|
||||
},
|
||||
{
|
||||
"country": "United States",
|
||||
"city": "Washington, DC",
|
||||
"isp": "Leaseweb",
|
||||
"hostname": "us-dc1.gw.ivpn.net",
|
||||
"tcp": false,
|
||||
"udp": true,
|
||||
"ips": [
|
||||
"207.244.108.207"
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
|
||||
@@ -68,7 +68,7 @@ func Test_versions(t *testing.T) {
|
||||
"Ivpn": {
|
||||
model: models.IvpnServer{},
|
||||
version: allServers.Ivpn.Version,
|
||||
digest: "2eb80d28",
|
||||
digest: "abdc2848",
|
||||
},
|
||||
"Mullvad": {
|
||||
model: models.MullvadServer{},
|
||||
|
||||
@@ -41,6 +41,7 @@ type IpvanishServer struct {
|
||||
type IvpnServer struct {
|
||||
Country string `json:"country"`
|
||||
City string `json:"city"`
|
||||
ISP string `json:"isp"`
|
||||
Hostname string `json:"hostname"`
|
||||
TCP bool `json:"tcp"`
|
||||
UDP bool `json:"udp"`
|
||||
|
||||
@@ -11,6 +11,7 @@ func (i *Ivpn) filterServers(selection configuration.ServerSelection) (
|
||||
for _, server := range i.servers {
|
||||
switch {
|
||||
case
|
||||
utils.FilterByPossibilities(server.ISP, selection.ISPs),
|
||||
utils.FilterByPossibilities(server.Country, selection.Countries),
|
||||
utils.FilterByPossibilities(server.City, selection.Cities),
|
||||
utils.FilterByPossibilities(server.Hostname, selection.Hostnames),
|
||||
|
||||
@@ -110,7 +110,7 @@ func (u *updater) updateIpvanish(ctx context.Context) (err error) {
|
||||
func (u *updater) updateIvpn(ctx context.Context) (err error) {
|
||||
minServers := getMinServers(len(u.servers.Ivpn.Servers))
|
||||
servers, warnings, err := ivpn.GetServers(
|
||||
ctx, u.unzipper, u.presolver, minServers)
|
||||
ctx, u.client, u.presolver, minServers)
|
||||
if u.options.CLI {
|
||||
for _, warning := range warnings {
|
||||
u.logger.Warn("Ivpn: " + warning)
|
||||
|
||||
66
internal/updater/providers/ivpn/api.go
Normal file
66
internal/updater/providers/ivpn/api.go
Normal file
@@ -0,0 +1,66 @@
|
||||
package ivpn
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"net/http"
|
||||
)
|
||||
|
||||
var (
|
||||
errBuildRequest = errors.New("cannot build HTTP request")
|
||||
errDoRequest = errors.New("failed doing HTTP request")
|
||||
errHTTPStatusCodeNotOK = errors.New("HTTP status code not OK")
|
||||
errUnmarshalResponseBody = errors.New("failed unmarshaling response body")
|
||||
errCloseBody = errors.New("failed closing HTTP body")
|
||||
)
|
||||
|
||||
type apiData struct {
|
||||
Servers []apiServer `json:"servers"`
|
||||
}
|
||||
|
||||
type apiServer struct {
|
||||
Hostnames apiHostnames `json:"hostnames"`
|
||||
IsActive bool `json:"is_active"`
|
||||
Country string `json:"country"`
|
||||
City string `json:"city"`
|
||||
ISP string `json:"isp"`
|
||||
}
|
||||
|
||||
type apiHostnames struct {
|
||||
OpenVPN string `json:"openvpn"`
|
||||
}
|
||||
|
||||
func fetchAPI(ctx context.Context, client *http.Client) (
|
||||
data apiData, err error) {
|
||||
const url = "https://api.ivpn.net/v4/servers/stats"
|
||||
|
||||
request, err := http.NewRequestWithContext(ctx, http.MethodGet, url, nil)
|
||||
if err != nil {
|
||||
return data, fmt.Errorf("%w: %s", errBuildRequest, err)
|
||||
}
|
||||
|
||||
response, err := client.Do(request)
|
||||
if err != nil {
|
||||
return data, fmt.Errorf("%w: %s", errDoRequest, err)
|
||||
}
|
||||
|
||||
if response.StatusCode != http.StatusOK {
|
||||
_ = response.Body.Close()
|
||||
return data, fmt.Errorf("%w: %d %s",
|
||||
errHTTPStatusCodeNotOK, response.StatusCode, response.Status)
|
||||
}
|
||||
|
||||
decoder := json.NewDecoder(response.Body)
|
||||
if err := decoder.Decode(&data); err != nil {
|
||||
_ = response.Body.Close()
|
||||
return data, fmt.Errorf("%w: %s", errUnmarshalResponseBody, err)
|
||||
}
|
||||
|
||||
if err := response.Body.Close(); err != nil {
|
||||
return data, fmt.Errorf("%w: %s", errCloseBody, err)
|
||||
}
|
||||
|
||||
return data, nil
|
||||
}
|
||||
96
internal/updater/providers/ivpn/api_test.go
Normal file
96
internal/updater/providers/ivpn/api_test.go
Normal file
@@ -0,0 +1,96 @@
|
||||
package ivpn
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func Test_fetchAPI(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
testCases := map[string]struct {
|
||||
responseStatus int
|
||||
responseBody io.ReadCloser
|
||||
data apiData
|
||||
err error
|
||||
}{
|
||||
"http response status not ok": {
|
||||
responseStatus: http.StatusNoContent,
|
||||
err: errors.New("HTTP status code not OK: 204 No Content"),
|
||||
},
|
||||
"nil body": {
|
||||
responseStatus: http.StatusOK,
|
||||
err: errors.New("failed unmarshaling response body: EOF"),
|
||||
},
|
||||
"no server": {
|
||||
responseStatus: http.StatusOK,
|
||||
responseBody: ioutil.NopCloser(strings.NewReader(`{}`)),
|
||||
},
|
||||
"success": {
|
||||
responseStatus: http.StatusOK,
|
||||
responseBody: ioutil.NopCloser(strings.NewReader(`{"servers":[
|
||||
{"country":"Country1","city":"City A","isp":"xyz","is_active":true,"hostnames":{"openvpn":"hosta"}},
|
||||
{"country":"Country2","city":"City B","isp":"abc","is_active":false,"hostnames":{"openvpn":"hostb"}}
|
||||
]}`)),
|
||||
data: apiData{
|
||||
Servers: []apiServer{
|
||||
{
|
||||
Country: "Country1",
|
||||
City: "City A",
|
||||
IsActive: true,
|
||||
ISP: "xyz",
|
||||
Hostnames: apiHostnames{
|
||||
OpenVPN: "hosta",
|
||||
},
|
||||
},
|
||||
{
|
||||
Country: "Country2",
|
||||
City: "City B",
|
||||
ISP: "abc",
|
||||
Hostnames: apiHostnames{
|
||||
OpenVPN: "hostb",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
for name, testCase := range testCases {
|
||||
testCase := testCase
|
||||
t.Run(name, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
ctx := context.Background()
|
||||
|
||||
client := &http.Client{
|
||||
Transport: roundTripFunc(func(r *http.Request) (*http.Response, error) {
|
||||
assert.Equal(t, http.MethodGet, r.Method)
|
||||
assert.Equal(t, r.URL.String(), "https://api.ivpn.net/v4/servers/stats")
|
||||
return &http.Response{
|
||||
StatusCode: testCase.responseStatus,
|
||||
Status: http.StatusText(testCase.responseStatus),
|
||||
Body: testCase.responseBody,
|
||||
}, nil
|
||||
}),
|
||||
}
|
||||
|
||||
data, err := fetchAPI(ctx, client)
|
||||
|
||||
assert.Equal(t, testCase.data, data)
|
||||
if testCase.err != nil {
|
||||
require.Error(t, err)
|
||||
assert.Equal(t, testCase.err.Error(), err.Error())
|
||||
} else {
|
||||
assert.NoError(t, err)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -1,16 +0,0 @@
|
||||
package ivpn
|
||||
|
||||
import (
|
||||
"strings"
|
||||
)
|
||||
|
||||
func parseFilename(fileName string) (country, city string) {
|
||||
const suffix = ".ovpn"
|
||||
fileName = strings.TrimSuffix(fileName, suffix)
|
||||
parts := strings.Split(fileName, "-")
|
||||
country = strings.ReplaceAll(parts[0], "_", " ")
|
||||
if len(parts) > 1 {
|
||||
city = strings.ReplaceAll(parts[1], "_", " ")
|
||||
}
|
||||
return country, city
|
||||
}
|
||||
@@ -1,41 +0,0 @@
|
||||
package ivpn
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func Test_parseFilename(t *testing.T) {
|
||||
t.Parallel()
|
||||
testCases := map[string]struct {
|
||||
fileName string
|
||||
country string
|
||||
city string
|
||||
}{
|
||||
"empty filename": {},
|
||||
"country only": {
|
||||
fileName: "Country.ovpn",
|
||||
country: "Country",
|
||||
},
|
||||
"country and city": {
|
||||
fileName: "Country-City.ovpn",
|
||||
country: "Country",
|
||||
city: "City",
|
||||
},
|
||||
"composite country and city": {
|
||||
fileName: "Coun_try-Ci_ty.ovpn",
|
||||
country: "Coun try",
|
||||
city: "Ci ty",
|
||||
},
|
||||
}
|
||||
for name, testCase := range testCases {
|
||||
testCase := testCase
|
||||
t.Run(name, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
country, city := parseFilename(testCase.fileName)
|
||||
assert.Equal(t, testCase.country, country)
|
||||
assert.Equal(t, testCase.city, city)
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -1,58 +0,0 @@
|
||||
package ivpn
|
||||
|
||||
import (
|
||||
"net"
|
||||
"sort"
|
||||
|
||||
"github.com/qdm12/gluetun/internal/models"
|
||||
)
|
||||
|
||||
type hostToServer map[string]models.IvpnServer
|
||||
|
||||
func (hts hostToServer) add(host, country, city string, tcp, udp bool) {
|
||||
server, ok := hts[host]
|
||||
if !ok {
|
||||
server.Hostname = host
|
||||
server.Country = country
|
||||
server.City = city
|
||||
}
|
||||
if tcp {
|
||||
server.TCP = tcp
|
||||
}
|
||||
if udp {
|
||||
server.UDP = udp
|
||||
}
|
||||
hts[host] = server
|
||||
}
|
||||
|
||||
func (hts hostToServer) toHostsSlice() (hosts []string) {
|
||||
hosts = make([]string, 0, len(hts))
|
||||
for host := range hts {
|
||||
hosts = append(hosts, host)
|
||||
}
|
||||
sort.Slice(hosts, func(i, j int) bool {
|
||||
return hosts[i] < hosts[j]
|
||||
})
|
||||
return hosts
|
||||
}
|
||||
|
||||
func (hts hostToServer) adaptWithIPs(hostToIPs map[string][]net.IP) {
|
||||
for host, IPs := range hostToIPs {
|
||||
server := hts[host]
|
||||
server.IPs = IPs
|
||||
hts[host] = server
|
||||
}
|
||||
for host, server := range hts {
|
||||
if len(server.IPs) == 0 {
|
||||
delete(hts, host)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (hts hostToServer) toServersSlice() (servers []models.IvpnServer) {
|
||||
servers = make([]models.IvpnServer, 0, len(hts))
|
||||
for _, server := range hts {
|
||||
servers = append(servers, server)
|
||||
}
|
||||
return servers
|
||||
}
|
||||
@@ -1,211 +0,0 @@
|
||||
package ivpn
|
||||
|
||||
import (
|
||||
"net"
|
||||
"testing"
|
||||
|
||||
"github.com/qdm12/gluetun/internal/models"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func Test_hostToServer_add(t *testing.T) {
|
||||
t.Parallel()
|
||||
testCases := map[string]struct {
|
||||
initialHTS hostToServer
|
||||
host string
|
||||
country string
|
||||
city string
|
||||
tcp bool
|
||||
udp bool
|
||||
expectedHTS hostToServer
|
||||
}{
|
||||
"empty host to server": {
|
||||
initialHTS: hostToServer{},
|
||||
host: "host",
|
||||
country: "country",
|
||||
city: "city",
|
||||
tcp: true,
|
||||
udp: true,
|
||||
expectedHTS: hostToServer{
|
||||
"host": {
|
||||
Hostname: "host",
|
||||
Country: "country",
|
||||
City: "city",
|
||||
TCP: true,
|
||||
UDP: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
"add server": {
|
||||
initialHTS: hostToServer{
|
||||
"existing host": {},
|
||||
},
|
||||
host: "host",
|
||||
country: "country",
|
||||
city: "city",
|
||||
tcp: true,
|
||||
udp: true,
|
||||
expectedHTS: hostToServer{
|
||||
"existing host": {},
|
||||
"host": models.IvpnServer{
|
||||
Hostname: "host",
|
||||
Country: "country",
|
||||
City: "city",
|
||||
TCP: true,
|
||||
UDP: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
"extend existing server": {
|
||||
initialHTS: hostToServer{
|
||||
"host": models.IvpnServer{
|
||||
Hostname: "host",
|
||||
Country: "country",
|
||||
City: "city",
|
||||
TCP: true,
|
||||
},
|
||||
},
|
||||
host: "host",
|
||||
country: "country",
|
||||
city: "city",
|
||||
tcp: false,
|
||||
udp: true,
|
||||
expectedHTS: hostToServer{
|
||||
"host": models.IvpnServer{
|
||||
Hostname: "host",
|
||||
Country: "country",
|
||||
City: "city",
|
||||
TCP: true,
|
||||
UDP: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
for name, testCase := range testCases {
|
||||
testCase := testCase
|
||||
t.Run(name, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
testCase.initialHTS.add(testCase.host, testCase.country, testCase.city, testCase.tcp, testCase.udp)
|
||||
assert.Equal(t, testCase.expectedHTS, testCase.initialHTS)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func Test_hostToServer_toHostsSlice(t *testing.T) {
|
||||
t.Parallel()
|
||||
testCases := map[string]struct {
|
||||
hts hostToServer
|
||||
hosts []string
|
||||
}{
|
||||
"empty host to server": {
|
||||
hts: hostToServer{},
|
||||
hosts: []string{},
|
||||
},
|
||||
"single host": {
|
||||
hts: hostToServer{
|
||||
"A": {},
|
||||
},
|
||||
hosts: []string{"A"},
|
||||
},
|
||||
"multiple hosts": {
|
||||
hts: hostToServer{
|
||||
"A": {},
|
||||
"B": {},
|
||||
},
|
||||
hosts: []string{"A", "B"},
|
||||
},
|
||||
}
|
||||
for name, testCase := range testCases {
|
||||
testCase := testCase
|
||||
t.Run(name, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
hosts := testCase.hts.toHostsSlice()
|
||||
assert.ElementsMatch(t, testCase.hosts, hosts)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func Test_hostToServer_adaptWithIPs(t *testing.T) {
|
||||
t.Parallel()
|
||||
testCases := map[string]struct {
|
||||
initialHTS hostToServer
|
||||
hostToIPs map[string][]net.IP
|
||||
expectedHTS hostToServer
|
||||
}{
|
||||
"create server": {
|
||||
initialHTS: hostToServer{},
|
||||
hostToIPs: map[string][]net.IP{
|
||||
"A": {{1, 2, 3, 4}},
|
||||
},
|
||||
expectedHTS: hostToServer{
|
||||
"A": models.IvpnServer{
|
||||
IPs: []net.IP{{1, 2, 3, 4}},
|
||||
},
|
||||
},
|
||||
},
|
||||
"add IPs to existing server": {
|
||||
initialHTS: hostToServer{
|
||||
"A": models.IvpnServer{
|
||||
Country: "country",
|
||||
},
|
||||
},
|
||||
hostToIPs: map[string][]net.IP{
|
||||
"A": {{1, 2, 3, 4}},
|
||||
},
|
||||
expectedHTS: hostToServer{
|
||||
"A": models.IvpnServer{
|
||||
Country: "country",
|
||||
IPs: []net.IP{{1, 2, 3, 4}},
|
||||
},
|
||||
},
|
||||
},
|
||||
"remove server without IP": {
|
||||
initialHTS: hostToServer{
|
||||
"A": models.IvpnServer{
|
||||
Country: "country",
|
||||
},
|
||||
},
|
||||
hostToIPs: map[string][]net.IP{},
|
||||
expectedHTS: hostToServer{},
|
||||
},
|
||||
}
|
||||
for name, testCase := range testCases {
|
||||
testCase := testCase
|
||||
t.Run(name, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
testCase.initialHTS.adaptWithIPs(testCase.hostToIPs)
|
||||
assert.Equal(t, testCase.expectedHTS, testCase.initialHTS)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func Test_hostToServer_toServersSlice(t *testing.T) {
|
||||
t.Parallel()
|
||||
testCases := map[string]struct {
|
||||
hts hostToServer
|
||||
servers []models.IvpnServer
|
||||
}{
|
||||
"empty host to server": {
|
||||
hts: hostToServer{},
|
||||
servers: []models.IvpnServer{},
|
||||
},
|
||||
"multiple servers": {
|
||||
hts: hostToServer{
|
||||
"A": {Country: "A"},
|
||||
"B": {Country: "B"},
|
||||
},
|
||||
servers: []models.IvpnServer{
|
||||
{Country: "A"},
|
||||
{Country: "B"},
|
||||
},
|
||||
},
|
||||
}
|
||||
for name, testCase := range testCases {
|
||||
testCase := testCase
|
||||
t.Run(name, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
servers := testCase.hts.toServersSlice()
|
||||
assert.ElementsMatch(t, testCase.servers, servers)
|
||||
})
|
||||
}
|
||||
}
|
||||
9
internal/updater/providers/ivpn/roundtrip_test.go
Normal file
9
internal/updater/providers/ivpn/roundtrip_test.go
Normal file
@@ -0,0 +1,9 @@
|
||||
package ivpn
|
||||
|
||||
import "net/http"
|
||||
|
||||
type roundTripFunc func(r *http.Request) (*http.Response, error)
|
||||
|
||||
func (f roundTripFunc) RoundTrip(r *http.Request) (*http.Response, error) {
|
||||
return f(r)
|
||||
}
|
||||
@@ -6,78 +6,64 @@ import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"strings"
|
||||
"net/http"
|
||||
|
||||
"github.com/qdm12/gluetun/internal/models"
|
||||
"github.com/qdm12/gluetun/internal/updater/openvpn"
|
||||
"github.com/qdm12/gluetun/internal/updater/resolver"
|
||||
"github.com/qdm12/gluetun/internal/updater/unzip"
|
||||
)
|
||||
|
||||
var ErrNotEnoughServers = errors.New("not enough servers found")
|
||||
var (
|
||||
ErrFetchAPI = errors.New("failed fetching API")
|
||||
ErrNotEnoughServers = errors.New("not enough servers found")
|
||||
)
|
||||
|
||||
func GetServers(ctx context.Context, unzipper unzip.Unzipper,
|
||||
func GetServers(ctx context.Context, client *http.Client,
|
||||
presolver resolver.Parallel, minServers int) (
|
||||
servers []models.IvpnServer, warnings []string, err error) {
|
||||
const url = "https://www.ivpn.net/releases/config/ivpn-openvpn-config.zip"
|
||||
contents, err := unzipper.FetchAndExtract(ctx, url)
|
||||
data, err := fetchAPI(ctx, client)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
} else if len(contents) < minServers {
|
||||
return nil, nil, fmt.Errorf("%w: %s", ErrFetchAPI, err)
|
||||
}
|
||||
|
||||
hosts := make([]string, 0, len(data.Servers))
|
||||
|
||||
for _, serverData := range data.Servers {
|
||||
host := serverData.Hostnames.OpenVPN
|
||||
|
||||
if host == "" {
|
||||
continue // Wireguard
|
||||
}
|
||||
|
||||
hosts = append(hosts, host)
|
||||
}
|
||||
|
||||
if len(hosts) < minServers {
|
||||
return nil, nil, fmt.Errorf("%w: %d and expected at least %d",
|
||||
ErrNotEnoughServers, len(contents), minServers)
|
||||
ErrNotEnoughServers, len(hosts), minServers)
|
||||
}
|
||||
|
||||
hts := make(hostToServer)
|
||||
|
||||
for fileName, content := range contents {
|
||||
if !strings.HasSuffix(fileName, ".ovpn") {
|
||||
continue // not an OpenVPN file
|
||||
}
|
||||
|
||||
tcp, udp, err := openvpn.ExtractProto(content)
|
||||
if err != nil {
|
||||
// treat error as warning and go to next file
|
||||
warning := err.Error() + ": in " + fileName
|
||||
warnings = append(warnings, warning)
|
||||
continue
|
||||
}
|
||||
|
||||
host, warning, err := openvpn.ExtractHost(content)
|
||||
if warning != "" {
|
||||
warnings = append(warnings, warning)
|
||||
}
|
||||
if err != nil {
|
||||
// treat error as warning and go to next file
|
||||
warning := err.Error() + " in " + fileName
|
||||
warnings = append(warnings, warning)
|
||||
continue
|
||||
}
|
||||
|
||||
country, city := parseFilename(fileName)
|
||||
|
||||
hts.add(host, country, city, tcp, udp)
|
||||
}
|
||||
|
||||
if len(hts) < minServers {
|
||||
return nil, warnings, fmt.Errorf("%w: %d and expected at least %d",
|
||||
ErrNotEnoughServers, len(hts), minServers)
|
||||
}
|
||||
|
||||
hosts := hts.toHostsSlice()
|
||||
hostToIPs, newWarnings, err := resolveHosts(ctx, presolver, hosts, minServers)
|
||||
warnings = append(warnings, newWarnings...)
|
||||
hostToIPs, warnings, err := resolveHosts(ctx, presolver, hosts, minServers)
|
||||
if err != nil {
|
||||
return nil, warnings, err
|
||||
}
|
||||
|
||||
hts.adaptWithIPs(hostToIPs)
|
||||
servers = make([]models.IvpnServer, 0, len(hosts))
|
||||
for _, serverData := range data.Servers {
|
||||
host := serverData.Hostnames.OpenVPN
|
||||
if serverData.Hostnames.OpenVPN == "" {
|
||||
continue // Wireguard
|
||||
}
|
||||
|
||||
servers = hts.toServersSlice()
|
||||
|
||||
if len(servers) < minServers {
|
||||
return nil, warnings, fmt.Errorf("%w: %d and expected at least %d",
|
||||
ErrNotEnoughServers, len(servers), minServers)
|
||||
server := models.IvpnServer{
|
||||
Country: serverData.Country,
|
||||
City: serverData.City,
|
||||
ISP: serverData.ISP,
|
||||
Hostname: serverData.Hostnames.OpenVPN,
|
||||
// TCP is not supported
|
||||
UDP: true,
|
||||
IPs: hostToIPs[host],
|
||||
}
|
||||
servers = append(servers, server)
|
||||
}
|
||||
|
||||
sortServers(servers)
|
||||
|
||||
@@ -3,27 +3,30 @@ package ivpn
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"io/ioutil"
|
||||
"net"
|
||||
"net/http"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/golang/mock/gomock"
|
||||
"github.com/qdm12/gluetun/internal/models"
|
||||
"github.com/qdm12/gluetun/internal/updater/resolver"
|
||||
"github.com/qdm12/gluetun/internal/updater/resolver/mock_resolver"
|
||||
"github.com/qdm12/gluetun/internal/updater/unzip/mock_unzip"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func Test_GetServers(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
testCases := map[string]struct {
|
||||
// Inputs
|
||||
minServers int
|
||||
|
||||
// Unzip
|
||||
unzipContents map[string][]byte
|
||||
unzipErr error
|
||||
// From API
|
||||
responseBody string
|
||||
responseStatus int
|
||||
|
||||
// Resolution
|
||||
expectResolve bool
|
||||
@@ -38,47 +41,15 @@ func Test_GetServers(t *testing.T) {
|
||||
warnings []string
|
||||
err error
|
||||
}{
|
||||
"unzipper error": {
|
||||
unzipErr: errors.New("dummy"),
|
||||
err: errors.New("dummy"),
|
||||
},
|
||||
"not enough unzip contents": {
|
||||
minServers: 1,
|
||||
unzipContents: map[string][]byte{},
|
||||
err: errors.New("not enough servers found: 0 and expected at least 1"),
|
||||
},
|
||||
"no openvpn file": {
|
||||
minServers: 1,
|
||||
unzipContents: map[string][]byte{"somefile.txt": {}},
|
||||
err: errors.New("not enough servers found: 0 and expected at least 1"),
|
||||
},
|
||||
"invalid proto": {
|
||||
minServers: 1,
|
||||
unzipContents: map[string][]byte{"badproto.ovpn": []byte(`proto invalid`)},
|
||||
warnings: []string{"unknown protocol: invalid: in badproto.ovpn"},
|
||||
err: errors.New("not enough servers found: 0 and expected at least 1"),
|
||||
},
|
||||
"no host": {
|
||||
minServers: 1,
|
||||
unzipContents: map[string][]byte{"nohost.ovpn": []byte(``)},
|
||||
warnings: []string{"remote host not found in nohost.ovpn"},
|
||||
err: errors.New("not enough servers found: 0 and expected at least 1"),
|
||||
},
|
||||
"multiple hosts": {
|
||||
minServers: 1,
|
||||
unzipContents: map[string][]byte{
|
||||
"MultiHosts.ovpn": []byte("remote hosta\nremote hostb"),
|
||||
},
|
||||
expectResolve: true,
|
||||
hostsToResolve: []string{"hosta"},
|
||||
resolveSettings: getResolveSettings(1),
|
||||
warnings: []string{"only using the first host \"hosta\" and discarding 1 other hosts"},
|
||||
err: errors.New("not enough servers found: 0 and expected at least 1"),
|
||||
"http response error": {
|
||||
responseStatus: http.StatusNoContent,
|
||||
err: errors.New("failed fetching API: HTTP status code not OK: 204 No Content"),
|
||||
},
|
||||
"resolve error": {
|
||||
unzipContents: map[string][]byte{
|
||||
"config.ovpn": []byte("remote hosta"),
|
||||
},
|
||||
responseBody: `{"servers":[
|
||||
{"hostnames":{"openvpn":"hosta"}}
|
||||
]}`,
|
||||
responseStatus: http.StatusOK,
|
||||
expectResolve: true,
|
||||
hostsToResolve: []string{"hosta"},
|
||||
resolveSettings: getResolveSettings(0),
|
||||
@@ -87,12 +58,22 @@ func Test_GetServers(t *testing.T) {
|
||||
warnings: []string{"resolve warning"},
|
||||
err: errors.New("dummy"),
|
||||
},
|
||||
"not enough servers": {
|
||||
minServers: 2,
|
||||
responseBody: `{"servers":[
|
||||
{"hostnames":{"openvpn":"hosta"}}
|
||||
]}`,
|
||||
responseStatus: http.StatusOK,
|
||||
err: errors.New("not enough servers found: 1 and expected at least 2"),
|
||||
},
|
||||
"success": {
|
||||
minServers: 1,
|
||||
unzipContents: map[string][]byte{
|
||||
"Country1-City_A.ovpn": []byte("remote hosta"),
|
||||
"Country2-City_B.ovpn": []byte("remote hostb"),
|
||||
},
|
||||
responseBody: `{"servers":[
|
||||
{"country":"Country1","city":"City A","hostnames":{"openvpn":"hosta"}},
|
||||
{"country":"Country2","city":"City B","hostnames":{"openvpn":"hostb"}},
|
||||
{"country":"Country3","city":"City C","hostnames":{"wireguard":"hostc"}}
|
||||
]}`,
|
||||
responseStatus: http.StatusOK,
|
||||
expectResolve: true,
|
||||
hostsToResolve: []string{"hosta", "hostb"},
|
||||
resolveSettings: getResolveSettings(1),
|
||||
@@ -116,10 +97,17 @@ func Test_GetServers(t *testing.T) {
|
||||
|
||||
ctx := context.Background()
|
||||
|
||||
unzipper := mock_unzip.NewMockUnzipper(ctrl)
|
||||
const zipURL = "https://www.ivpn.net/releases/config/ivpn-openvpn-config.zip"
|
||||
unzipper.EXPECT().FetchAndExtract(ctx, zipURL).
|
||||
Return(testCase.unzipContents, testCase.unzipErr)
|
||||
client := &http.Client{
|
||||
Transport: roundTripFunc(func(r *http.Request) (*http.Response, error) {
|
||||
assert.Equal(t, http.MethodGet, r.Method)
|
||||
assert.Equal(t, r.URL.String(), "https://api.ivpn.net/v4/servers/stats")
|
||||
return &http.Response{
|
||||
StatusCode: testCase.responseStatus,
|
||||
Status: http.StatusText(testCase.responseStatus),
|
||||
Body: ioutil.NopCloser(strings.NewReader(testCase.responseBody)),
|
||||
}, nil
|
||||
}),
|
||||
}
|
||||
|
||||
presolver := mock_resolver.NewMockParallel(ctrl)
|
||||
if testCase.expectResolve {
|
||||
@@ -127,7 +115,7 @@ func Test_GetServers(t *testing.T) {
|
||||
Return(testCase.hostToIPs, testCase.resolveWarnings, testCase.resolveErr)
|
||||
}
|
||||
|
||||
servers, warnings, err := GetServers(ctx, unzipper, presolver, testCase.minServers)
|
||||
servers, warnings, err := GetServers(ctx, client, presolver, testCase.minServers)
|
||||
|
||||
assert.Equal(t, testCase.servers, servers)
|
||||
assert.Equal(t, testCase.warnings, warnings)
|
||||
|
||||
Reference in New Issue
Block a user