Servers updater (#232)
* Support for all VPN providers * Update all VPN providers servers information * Remove old tooling binaries
This commit is contained in:
12
internal/updater/alias.go
Normal file
12
internal/updater/alias.go
Normal file
@@ -0,0 +1,12 @@
|
||||
package updater
|
||||
|
||||
import (
|
||||
"context"
|
||||
"net"
|
||||
"net/http"
|
||||
)
|
||||
|
||||
type (
|
||||
httpGetFunc func(url string) (r *http.Response, err error)
|
||||
lookupIPFunc func(ctx context.Context, host string) (ips []net.IP, err error)
|
||||
)
|
||||
254
internal/updater/countries.go
Normal file
254
internal/updater/countries.go
Normal file
@@ -0,0 +1,254 @@
|
||||
package updater
|
||||
|
||||
func getCountryCodes() map[string]string { //nolint:dupl
|
||||
return map[string]string{
|
||||
"af": "Afghanistan",
|
||||
"ax": "Aland Islands",
|
||||
"al": "Albania",
|
||||
"dz": "Algeria",
|
||||
"as": "American Samoa",
|
||||
"ad": "Andorra",
|
||||
"ao": "Angola",
|
||||
"ai": "Anguilla",
|
||||
"aq": "Antarctica",
|
||||
"ag": "Antigua and Barbuda",
|
||||
"ar": "Argentina",
|
||||
"am": "Armenia",
|
||||
"aw": "Aruba",
|
||||
"au": "Australia",
|
||||
"at": "Austria",
|
||||
"az": "Azerbaijan",
|
||||
"bs": "Bahamas",
|
||||
"bh": "Bahrain",
|
||||
"bd": "Bangladesh",
|
||||
"bb": "Barbados",
|
||||
"by": "Belarus",
|
||||
"be": "Belgium",
|
||||
"bz": "Belize",
|
||||
"bj": "Benin",
|
||||
"bm": "Bermuda",
|
||||
"bt": "Bhutan",
|
||||
"bo": "Bolivia",
|
||||
"bq": "Bonaire",
|
||||
"ba": "Bosnia and Herzegovina",
|
||||
"bw": "Botswana",
|
||||
"bv": "Bouvet Island",
|
||||
"br": "Brazil",
|
||||
"io": "British Indian Ocean Territory",
|
||||
"vg": "British Virgin Islands",
|
||||
"bn": "Brunei Darussalam",
|
||||
"bg": "Bulgaria",
|
||||
"bf": "Burkina Faso",
|
||||
"bi": "Burundi",
|
||||
"kh": "Cambodia",
|
||||
"cm": "Cameroon",
|
||||
"ca": "Canada",
|
||||
"cv": "Cape Verde",
|
||||
"ky": "Cayman Islands",
|
||||
"cf": "Central African Republic",
|
||||
"td": "Chad",
|
||||
"cl": "Chile",
|
||||
"cn": "China",
|
||||
"cx": "Christmas Island",
|
||||
"cc": "Cocos Islands",
|
||||
"co": "Colombia",
|
||||
"km": "Comoros",
|
||||
"cg": "Congo",
|
||||
"ck": "Cook Islands",
|
||||
"cr": "Costa Rica",
|
||||
"ci": "Cote d'Ivoire",
|
||||
"hr": "Croatia",
|
||||
"cu": "Cuba",
|
||||
"cw": "Curacao",
|
||||
"cy": "Cyprus",
|
||||
"cz": "Czech Republic",
|
||||
"cd": "Democratic Republic of the Congo",
|
||||
"dk": "Denmark",
|
||||
"dj": "Djibouti",
|
||||
"dm": "Dominica",
|
||||
"do": "Dominican Republic",
|
||||
"ec": "Ecuador",
|
||||
"eg": "Egypt",
|
||||
"sv": "El Salvador",
|
||||
"gq": "Equatorial Guinea",
|
||||
"er": "Eritrea",
|
||||
"ee": "Estonia",
|
||||
"et": "Ethiopia",
|
||||
"fk": "Falkland Islands",
|
||||
"fo": "Faroe Islands",
|
||||
"fj": "Fiji",
|
||||
"fi": "Finland",
|
||||
"fr": "France",
|
||||
"gf": "French Guiana",
|
||||
"pf": "French Polynesia",
|
||||
"tf": "French Southern Territories",
|
||||
"ga": "Gabon",
|
||||
"gm": "Gambia",
|
||||
"ge": "Georgia",
|
||||
"de": "Germany",
|
||||
"gh": "Ghana",
|
||||
"gi": "Gibraltar",
|
||||
"gr": "Greece",
|
||||
"gl": "Greenland",
|
||||
"gd": "Grenada",
|
||||
"gp": "Guadeloupe",
|
||||
"gu": "Guam",
|
||||
"gt": "Guatemala",
|
||||
"gg": "Guernsey",
|
||||
"gw": "Guinea-Bissau",
|
||||
"gn": "Guinea",
|
||||
"gy": "Guyana",
|
||||
"ht": "Haiti",
|
||||
"hm": "Heard Island and McDonald Islands",
|
||||
"hn": "Honduras",
|
||||
"hk": "Hong Kong",
|
||||
"hu": "Hungary",
|
||||
"is": "Iceland",
|
||||
"in": "India",
|
||||
"id": "Indonesia",
|
||||
"ir": "Iran",
|
||||
"iq": "Iraq",
|
||||
"ie": "Ireland",
|
||||
"im": "Isle of Man",
|
||||
"il": "Israel",
|
||||
"it": "Italy",
|
||||
"jm": "Jamaica",
|
||||
"jp": "Japan",
|
||||
"je": "Jersey",
|
||||
"jo": "Jordan",
|
||||
"kz": "Kazakhstan",
|
||||
"ke": "Kenya",
|
||||
"ki": "Kiribati",
|
||||
"kr": "Korea",
|
||||
"kw": "Kuwait",
|
||||
"kg": "Kyrgyzstan",
|
||||
"la": "Lao People's Democratic Republic",
|
||||
"lv": "Latvia",
|
||||
"lb": "Lebanon",
|
||||
"ls": "Lesotho",
|
||||
"lr": "Liberia",
|
||||
"ly": "Libya",
|
||||
"li": "Liechtenstein",
|
||||
"lt": "Lithuania",
|
||||
"lu": "Luxembourg",
|
||||
"mo": "Macao",
|
||||
"mk": "Macedonia",
|
||||
"mg": "Madagascar",
|
||||
"mw": "Malawi",
|
||||
"my": "Malaysia",
|
||||
"mv": "Maldives",
|
||||
"ml": "Mali",
|
||||
"mt": "Malta",
|
||||
"mh": "Marshall Islands",
|
||||
"mq": "Martinique",
|
||||
"mr": "Mauritania",
|
||||
"mu": "Mauritius",
|
||||
"yt": "Mayotte",
|
||||
"mx": "Mexico",
|
||||
"fm": "Micronesia",
|
||||
"md": "Moldova",
|
||||
"mc": "Monaco",
|
||||
"mn": "Mongolia",
|
||||
"me": "Montenegro",
|
||||
"ms": "Montserrat",
|
||||
"ma": "Morocco",
|
||||
"mz": "Mozambique",
|
||||
"mm": "Myanmar",
|
||||
"na": "Namibia",
|
||||
"nr": "Nauru",
|
||||
"np": "Nepal",
|
||||
"nl": "Netherlands",
|
||||
"nc": "New Caledonia",
|
||||
"nz": "New Zealand",
|
||||
"ni": "Nicaragua",
|
||||
"ne": "Niger",
|
||||
"ng": "Nigeria",
|
||||
"nu": "Niue",
|
||||
"nf": "Norfolk Island",
|
||||
"mp": "Northern Mariana Islands",
|
||||
"no": "Norway",
|
||||
"om": "Oman",
|
||||
"pk": "Pakistan",
|
||||
"pw": "Palau",
|
||||
"ps": "Palestine, State of",
|
||||
"pa": "Panama",
|
||||
"pg": "Papua New Guinea",
|
||||
"py": "Paraguay",
|
||||
"pe": "Peru",
|
||||
"ph": "Philippines",
|
||||
"pn": "Pitcairn",
|
||||
"pl": "Poland",
|
||||
"pt": "Portugal",
|
||||
"pr": "Puerto Rico",
|
||||
"qa": "Qatar",
|
||||
"re": "Reunion",
|
||||
"ro": "Romania",
|
||||
"ru": "Russian Federation",
|
||||
"rw": "Rwanda",
|
||||
"bl": "Saint Barthelemy",
|
||||
"sh": "Saint Helena",
|
||||
"kn": "Saint Kitts and Nevis",
|
||||
"lc": "Saint Lucia",
|
||||
"mf": "Saint Martin",
|
||||
"pm": "Saint Pierre and Miquelon",
|
||||
"vc": "Saint Vincent and the Grenadines",
|
||||
"ws": "Samoa",
|
||||
"sm": "San Marino",
|
||||
"st": "Sao Tome and Principe",
|
||||
"sa": "Saudi Arabia",
|
||||
"sn": "Senegal",
|
||||
"rs": "Serbia",
|
||||
"sc": "Seychelles",
|
||||
"sl": "Sierra Leone",
|
||||
"sg": "Singapore",
|
||||
"sx": "Sint Maarten",
|
||||
"sk": "Slovakia",
|
||||
"si": "Slovenia",
|
||||
"sb": "Solomon Islands",
|
||||
"so": "Somalia",
|
||||
"za": "South Africa",
|
||||
"gs": "South Georgia and the South Sandwich Islands",
|
||||
"ss": "South Sudan",
|
||||
"es": "Spain",
|
||||
"lk": "Sri Lanka",
|
||||
"sd": "Sudan",
|
||||
"sr": "Suriname",
|
||||
"sj": "Svalbard and Jan Mayen",
|
||||
"sz": "Swaziland",
|
||||
"se": "Sweden",
|
||||
"ch": "Switzerland",
|
||||
"sy": "Syrian Arab Republic",
|
||||
"tw": "Taiwan",
|
||||
"tj": "Tajikistan",
|
||||
"tz": "Tanzania",
|
||||
"th": "Thailand",
|
||||
"tl": "Timor-Leste",
|
||||
"tg": "Togo",
|
||||
"tk": "Tokelau",
|
||||
"to": "Tonga",
|
||||
"tt": "Trinidad and Tobago",
|
||||
"tn": "Tunisia",
|
||||
"tr": "Turkey",
|
||||
"tm": "Turkmenistan",
|
||||
"tc": "Turks and Caicos Islands",
|
||||
"tv": "Tuvalu",
|
||||
"ug": "Uganda",
|
||||
"ua": "Ukraine",
|
||||
"ae": "United Arab Emirates",
|
||||
"gb": "United Kingdom",
|
||||
"um": "United States Minor Outlying Islands",
|
||||
"us": "United States",
|
||||
"uy": "Uruguay",
|
||||
"vi": "US Virgin Islands",
|
||||
"uz": "Uzbekistan",
|
||||
"vu": "Vanuatu",
|
||||
"va": "Vatican City State",
|
||||
"ve": "Venezuela",
|
||||
"vn": "Vietnam",
|
||||
"wf": "Wallis and Futuna",
|
||||
"eh": "Western Sahara",
|
||||
"ye": "Yemen",
|
||||
"zm": "Zambia",
|
||||
"zw": "Zimbabwe",
|
||||
}
|
||||
}
|
||||
336
internal/updater/cyberghost.go
Normal file
336
internal/updater/cyberghost.go
Normal file
@@ -0,0 +1,336 @@
|
||||
package updater
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"sort"
|
||||
|
||||
"github.com/qdm12/gluetun/internal/models"
|
||||
)
|
||||
|
||||
func (u *updater) updateCyberghost(ctx context.Context) {
|
||||
servers := findCyberghostServers(ctx, u.lookupIP)
|
||||
if u.options.Stdout {
|
||||
u.println(stringifyCyberghostServers(servers))
|
||||
}
|
||||
u.servers.Cyberghost.Timestamp = u.timeNow().Unix()
|
||||
u.servers.Cyberghost.Servers = servers
|
||||
}
|
||||
|
||||
func findCyberghostServers(ctx context.Context, lookupIP lookupIPFunc) (servers []models.CyberghostServer) {
|
||||
groups := getCyberghostGroups()
|
||||
allCountryCodes := getCountryCodes()
|
||||
cyberghostCountryCodes := getCyberghostSubdomainToRegion()
|
||||
possibleCountryCodes := mergeCountryCodes(cyberghostCountryCodes, allCountryCodes)
|
||||
|
||||
resultsChannel := make(chan models.CyberghostServer)
|
||||
const maxGoroutines = 10
|
||||
guard := make(chan struct{}, maxGoroutines)
|
||||
for groupID, groupName := range groups {
|
||||
for countryCode, region := range possibleCountryCodes {
|
||||
go func(groupName, groupID, region, countryCode string) {
|
||||
host := fmt.Sprintf("%s-%s.cg-dialup.net", groupID, countryCode)
|
||||
guard <- struct{}{}
|
||||
IPs, err := resolveRepeat(ctx, lookupIP, host, 2)
|
||||
if err != nil {
|
||||
IPs = nil
|
||||
}
|
||||
<-guard
|
||||
resultsChannel <- models.CyberghostServer{
|
||||
Region: region,
|
||||
Group: groupName,
|
||||
IPs: IPs,
|
||||
}
|
||||
}(groupName, groupID, region, countryCode)
|
||||
}
|
||||
}
|
||||
for i := 0; i < len(groups)*len(possibleCountryCodes); i++ {
|
||||
server := <-resultsChannel
|
||||
if server.IPs == nil {
|
||||
continue
|
||||
}
|
||||
servers = append(servers, server)
|
||||
}
|
||||
sort.Slice(servers, func(i, j int) bool {
|
||||
return servers[i].Region < servers[j].Region
|
||||
})
|
||||
return servers
|
||||
}
|
||||
|
||||
//nolint:goconst
|
||||
func stringifyCyberghostServers(servers []models.CyberghostServer) (s string) {
|
||||
s = "func CyberghostServers() []models.CyberghostServer {\n"
|
||||
s += " return []models.CyberghostServer{\n"
|
||||
for _, server := range servers {
|
||||
s += " " + server.String() + ",\n"
|
||||
}
|
||||
s += " }\n"
|
||||
s += "}"
|
||||
return s
|
||||
}
|
||||
|
||||
func getCyberghostGroups() map[string]string {
|
||||
return map[string]string{
|
||||
"87-1": "Premium UDP Europe",
|
||||
"94-1": "Premium UDP USA",
|
||||
"95-1": "Premium UDP Asia",
|
||||
"87-8": "NoSpy UDP Europe",
|
||||
"97-1": "Premium TCP Europe",
|
||||
"93-1": "Premium TCP USA",
|
||||
"96-1": "Premium TCP Asia",
|
||||
"97-8": "NoSpy TCP Europe",
|
||||
}
|
||||
}
|
||||
|
||||
func getCyberghostSubdomainToRegion() map[string]string { //nolint:dupl
|
||||
return map[string]string{
|
||||
"af": "Afghanistan",
|
||||
"ax": "Aland Islands",
|
||||
"al": "Albania",
|
||||
"dz": "Algeria",
|
||||
"as": "American Samoa",
|
||||
"ad": "Andorra",
|
||||
"ao": "Angola",
|
||||
"ai": "Anguilla",
|
||||
"aq": "Antarctica",
|
||||
"ag": "Antigua and Barbuda",
|
||||
"ar": "Argentina",
|
||||
"am": "Armenia",
|
||||
"aw": "Aruba",
|
||||
"au": "Australia",
|
||||
"at": "Austria",
|
||||
"az": "Azerbaijan",
|
||||
"bs": "Bahamas",
|
||||
"bh": "Bahrain",
|
||||
"bd": "Bangladesh",
|
||||
"bb": "Barbados",
|
||||
"by": "Belarus",
|
||||
"be": "Belgium",
|
||||
"bz": "Belize",
|
||||
"bj": "Benin",
|
||||
"bm": "Bermuda",
|
||||
"bt": "Bhutan",
|
||||
"bo": "Bolivia",
|
||||
"bq": "Bonaire",
|
||||
"ba": "Bosnia and Herzegovina",
|
||||
"bw": "Botswana",
|
||||
"bv": "Bouvet Island",
|
||||
"br": "Brazil",
|
||||
"io": "British Indian Ocean Territory",
|
||||
"vg": "British Virgin Islands",
|
||||
"bn": "Brunei Darussalam",
|
||||
"bg": "Bulgaria",
|
||||
"bf": "Burkina Faso",
|
||||
"bi": "Burundi",
|
||||
"kh": "Cambodia",
|
||||
"cm": "Cameroon",
|
||||
"ca": "Canada",
|
||||
"cv": "Cape Verde",
|
||||
"ky": "Cayman Islands",
|
||||
"cf": "Central African Republic",
|
||||
"td": "Chad",
|
||||
"cl": "Chile",
|
||||
"cn": "China",
|
||||
"cx": "Christmas Island",
|
||||
"cc": "Cocos Islands",
|
||||
"co": "Colombia",
|
||||
"km": "Comoros",
|
||||
"cg": "Congo",
|
||||
"ck": "Cook Islands",
|
||||
"cr": "Costa Rica",
|
||||
"ci": "Cote d'Ivoire",
|
||||
"hr": "Croatia",
|
||||
"cu": "Cuba",
|
||||
"cw": "Curacao",
|
||||
"cy": "Cyprus",
|
||||
"cz": "Czech Republic",
|
||||
"cd": "Democratic Republic of the Congo",
|
||||
"dk": "Denmark",
|
||||
"dj": "Djibouti",
|
||||
"dm": "Dominica",
|
||||
"do": "Dominican Republic",
|
||||
"ec": "Ecuador",
|
||||
"eg": "Egypt",
|
||||
"sv": "El Salvador",
|
||||
"gq": "Equatorial Guinea",
|
||||
"er": "Eritrea",
|
||||
"ee": "Estonia",
|
||||
"et": "Ethiopia",
|
||||
"fk": "Falkland Islands",
|
||||
"fo": "Faroe Islands",
|
||||
"fj": "Fiji",
|
||||
"fi": "Finland",
|
||||
"fr": "France",
|
||||
"gf": "French Guiana",
|
||||
"pf": "French Polynesia",
|
||||
"tf": "French Southern Territories",
|
||||
"ga": "Gabon",
|
||||
"gm": "Gambia",
|
||||
"ge": "Georgia",
|
||||
"de": "Germany",
|
||||
"gh": "Ghana",
|
||||
"gi": "Gibraltar",
|
||||
"gr": "Greece",
|
||||
"gl": "Greenland",
|
||||
"gd": "Grenada",
|
||||
"gp": "Guadeloupe",
|
||||
"gu": "Guam",
|
||||
"gt": "Guatemala",
|
||||
"gg": "Guernsey",
|
||||
"gw": "Guinea-Bissau",
|
||||
"gn": "Guinea",
|
||||
"gy": "Guyana",
|
||||
"ht": "Haiti",
|
||||
"hm": "Heard Island and McDonald Islands",
|
||||
"hn": "Honduras",
|
||||
"hk": "Hong Kong",
|
||||
"hu": "Hungary",
|
||||
"is": "Iceland",
|
||||
"in": "India",
|
||||
"id": "Indonesia",
|
||||
"ir": "Iran",
|
||||
"iq": "Iraq",
|
||||
"ie": "Ireland",
|
||||
"im": "Isle of Man",
|
||||
"il": "Israel",
|
||||
"it": "Italy",
|
||||
"jm": "Jamaica",
|
||||
"jp": "Japan",
|
||||
"je": "Jersey",
|
||||
"jo": "Jordan",
|
||||
"kz": "Kazakhstan",
|
||||
"ke": "Kenya",
|
||||
"ki": "Kiribati",
|
||||
"kr": "Korea",
|
||||
"kw": "Kuwait",
|
||||
"kg": "Kyrgyzstan",
|
||||
"la": "Lao People's Democratic Republic",
|
||||
"lv": "Latvia",
|
||||
"lb": "Lebanon",
|
||||
"ls": "Lesotho",
|
||||
"lr": "Liberia",
|
||||
"ly": "Libya",
|
||||
"li": "Liechtenstein",
|
||||
"lt": "Lithuania",
|
||||
"lu": "Luxembourg",
|
||||
"mo": "Macao",
|
||||
"mk": "Macedonia",
|
||||
"mg": "Madagascar",
|
||||
"mw": "Malawi",
|
||||
"my": "Malaysia",
|
||||
"mv": "Maldives",
|
||||
"ml": "Mali",
|
||||
"mt": "Malta",
|
||||
"mh": "Marshall Islands",
|
||||
"mq": "Martinique",
|
||||
"mr": "Mauritania",
|
||||
"mu": "Mauritius",
|
||||
"yt": "Mayotte",
|
||||
"mx": "Mexico",
|
||||
"fm": "Micronesia",
|
||||
"md": "Moldova",
|
||||
"mc": "Monaco",
|
||||
"mn": "Mongolia",
|
||||
"me": "Montenegro",
|
||||
"ms": "Montserrat",
|
||||
"ma": "Morocco",
|
||||
"mz": "Mozambique",
|
||||
"mm": "Myanmar",
|
||||
"na": "Namibia",
|
||||
"nr": "Nauru",
|
||||
"np": "Nepal",
|
||||
"nl": "Netherlands",
|
||||
"nc": "New Caledonia",
|
||||
"nz": "New Zealand",
|
||||
"ni": "Nicaragua",
|
||||
"ne": "Niger",
|
||||
"ng": "Nigeria",
|
||||
"nu": "Niue",
|
||||
"nf": "Norfolk Island",
|
||||
"mp": "Northern Mariana Islands",
|
||||
"no": "Norway",
|
||||
"om": "Oman",
|
||||
"pk": "Pakistan",
|
||||
"pw": "Palau",
|
||||
"ps": "Palestine, State of",
|
||||
"pa": "Panama",
|
||||
"pg": "Papua New Guinea",
|
||||
"py": "Paraguay",
|
||||
"pe": "Peru",
|
||||
"ph": "Philippines",
|
||||
"pn": "Pitcairn",
|
||||
"pl": "Poland",
|
||||
"pt": "Portugal",
|
||||
"pr": "Puerto Rico",
|
||||
"qa": "Qatar",
|
||||
"re": "Reunion",
|
||||
"ro": "Romania",
|
||||
"ru": "Russian Federation",
|
||||
"rw": "Rwanda",
|
||||
"bl": "Saint Barthelemy",
|
||||
"sh": "Saint Helena",
|
||||
"kn": "Saint Kitts and Nevis",
|
||||
"lc": "Saint Lucia",
|
||||
"mf": "Saint Martin",
|
||||
"pm": "Saint Pierre and Miquelon",
|
||||
"vc": "Saint Vincent and the Grenadines",
|
||||
"ws": "Samoa",
|
||||
"sm": "San Marino",
|
||||
"st": "Sao Tome and Principe",
|
||||
"sa": "Saudi Arabia",
|
||||
"sn": "Senegal",
|
||||
"rs": "Serbia",
|
||||
"sc": "Seychelles",
|
||||
"sl": "Sierra Leone",
|
||||
"sg": "Singapore",
|
||||
"sx": "Sint Maarten",
|
||||
"sk": "Slovakia",
|
||||
"si": "Slovenia",
|
||||
"sb": "Solomon Islands",
|
||||
"so": "Somalia",
|
||||
"za": "South Africa",
|
||||
"gs": "South Georgia and the South Sandwich Islands",
|
||||
"ss": "South Sudan",
|
||||
"es": "Spain",
|
||||
"lk": "Sri Lanka",
|
||||
"sd": "Sudan",
|
||||
"sr": "Suriname",
|
||||
"sj": "Svalbard and Jan Mayen",
|
||||
"sz": "Swaziland",
|
||||
"se": "Sweden",
|
||||
"ch": "Switzerland",
|
||||
"sy": "Syrian Arab Republic",
|
||||
"tw": "Taiwan",
|
||||
"tj": "Tajikistan",
|
||||
"tz": "Tanzania",
|
||||
"th": "Thailand",
|
||||
"tl": "Timor-Leste",
|
||||
"tg": "Togo",
|
||||
"tk": "Tokelau",
|
||||
"to": "Tonga",
|
||||
"tt": "Trinidad and Tobago",
|
||||
"tn": "Tunisia",
|
||||
"tr": "Turkey",
|
||||
"tm": "Turkmenistan",
|
||||
"tc": "Turks and Caicos Islands",
|
||||
"tv": "Tuvalu",
|
||||
"ug": "Uganda",
|
||||
"ua": "Ukraine",
|
||||
"ae": "United Arab Emirates",
|
||||
"gb": "United Kingdom",
|
||||
"um": "United States Minor Outlying Islands",
|
||||
"us": "United States",
|
||||
"uy": "Uruguay",
|
||||
"vi": "US Virgin Islands",
|
||||
"uz": "Uzbekistan",
|
||||
"vu": "Vanuatu",
|
||||
"va": "Vatican City State",
|
||||
"ve": "Venezuela",
|
||||
"vn": "Vietnam",
|
||||
"wf": "Wallis and Futuna",
|
||||
"eh": "Western Sahara",
|
||||
"ye": "Yemen",
|
||||
"zm": "Zambia",
|
||||
"zw": "Zimbabwe",
|
||||
}
|
||||
}
|
||||
24
internal/updater/ips.go
Normal file
24
internal/updater/ips.go
Normal file
@@ -0,0 +1,24 @@
|
||||
package updater
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"net"
|
||||
"sort"
|
||||
)
|
||||
|
||||
func uniqueSortedIPs(ips []net.IP) []net.IP {
|
||||
uniqueIPs := make(map[string]struct{})
|
||||
for _, ip := range ips {
|
||||
uniqueIPs[ip.String()] = struct{}{}
|
||||
}
|
||||
ips = make([]net.IP, len(uniqueIPs))
|
||||
i := 0
|
||||
for ip := range uniqueIPs {
|
||||
ips[i] = net.ParseIP(ip)
|
||||
i++
|
||||
}
|
||||
sort.Slice(ips, func(i, j int) bool {
|
||||
return bytes.Compare(ips[i], ips[j]) < 0
|
||||
})
|
||||
return ips
|
||||
}
|
||||
@@ -11,9 +11,22 @@ import (
|
||||
"github.com/qdm12/gluetun/internal/models"
|
||||
)
|
||||
|
||||
func (u *updater) findMullvadServers() (servers []models.MullvadServer, err error) {
|
||||
func (u *updater) updateMullvad() (err error) {
|
||||
servers, err := findMullvadServers(u.httpGet)
|
||||
if err != nil {
|
||||
return fmt.Errorf("cannot update Mullvad servers: %w", err)
|
||||
}
|
||||
if u.options.Stdout {
|
||||
u.println(stringifyMullvadServers(servers))
|
||||
}
|
||||
u.servers.Mullvad.Timestamp = u.timeNow().Unix()
|
||||
u.servers.Mullvad.Servers = servers
|
||||
return nil
|
||||
}
|
||||
|
||||
func findMullvadServers(httpGet httpGetFunc) (servers []models.MullvadServer, err error) {
|
||||
const url = "https://api.mullvad.net/www/relays/openvpn/"
|
||||
response, err := u.httpGet(url)
|
||||
response, err := httpGet(url)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -66,6 +79,8 @@ func (u *updater) findMullvadServers() (servers []models.MullvadServer, err erro
|
||||
}
|
||||
}
|
||||
for _, server := range serversByKey {
|
||||
server.IPs = uniqueSortedIPs(server.IPs)
|
||||
server.IPsV6 = uniqueSortedIPs(server.IPsV6)
|
||||
servers = append(servers, server)
|
||||
}
|
||||
sort.Slice(servers, func(i, j int) bool {
|
||||
|
||||
105
internal/updater/nordvpn.go
Normal file
105
internal/updater/nordvpn.go
Normal file
@@ -0,0 +1,105 @@
|
||||
package updater
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"net"
|
||||
"net/http"
|
||||
"sort"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/qdm12/gluetun/internal/models"
|
||||
)
|
||||
|
||||
func (u *updater) updateNordvpn() (err error) {
|
||||
servers, warnings, err := findNordvpnServers(u.httpGet)
|
||||
for _, warning := range warnings {
|
||||
u.println(warning)
|
||||
}
|
||||
if err != nil {
|
||||
return fmt.Errorf("cannot update Nordvpn servers: %w", err)
|
||||
}
|
||||
if u.options.Stdout {
|
||||
u.println(stringifyNordvpnServers(servers))
|
||||
}
|
||||
u.servers.Nordvpn.Timestamp = u.timeNow().Unix()
|
||||
u.servers.Nordvpn.Servers = servers
|
||||
return nil
|
||||
}
|
||||
|
||||
func findNordvpnServers(httpGet httpGetFunc) (servers []models.NordvpnServer, warnings []string, err error) {
|
||||
const url = "https://nordvpn.com/api/server"
|
||||
response, err := httpGet(url)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
defer response.Body.Close()
|
||||
if response.StatusCode != http.StatusOK {
|
||||
return nil, nil, fmt.Errorf(response.Status)
|
||||
}
|
||||
bytes, err := ioutil.ReadAll(response.Body)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
var data []struct {
|
||||
IPAddress string `json:"ip_address"`
|
||||
Name string `json:"name"`
|
||||
Country string `json:"country"`
|
||||
Features struct {
|
||||
UDP bool `json:"openvpn_udp"`
|
||||
TCP bool `json:"openvpn_tcp"`
|
||||
} `json:"features"`
|
||||
}
|
||||
if err := json.Unmarshal(bytes, &data); err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
sort.Slice(data, func(i, j int) bool {
|
||||
if data[i].Country == data[j].Country {
|
||||
return data[i].Name < data[j].Name
|
||||
}
|
||||
return data[i].Country < data[j].Country
|
||||
})
|
||||
|
||||
for _, jsonServer := range data {
|
||||
if !jsonServer.Features.TCP && !jsonServer.Features.UDP {
|
||||
warnings = append(warnings, fmt.Sprintf("server %q does not support TCP and UDP for openvpn", jsonServer.Name))
|
||||
continue
|
||||
}
|
||||
ip := net.ParseIP(jsonServer.IPAddress)
|
||||
if ip == nil || ip.To4() == nil {
|
||||
return nil, nil, fmt.Errorf("IP address %q is not a valid IPv4 address for server %q", jsonServer.IPAddress, jsonServer.Name)
|
||||
}
|
||||
i := strings.IndexRune(jsonServer.Name, '#')
|
||||
if i < 0 {
|
||||
return nil, nil, fmt.Errorf("No ID in server name %q", jsonServer.Name)
|
||||
}
|
||||
idString := jsonServer.Name[i+1:]
|
||||
idUint64, err := strconv.ParseUint(idString, 10, 16)
|
||||
if err != nil {
|
||||
return nil, nil, fmt.Errorf("Bad ID in server name %q", jsonServer.Name)
|
||||
}
|
||||
server := models.NordvpnServer{
|
||||
Region: jsonServer.Country,
|
||||
Number: uint16(idUint64),
|
||||
IP: ip,
|
||||
TCP: jsonServer.Features.TCP,
|
||||
UDP: jsonServer.Features.UDP,
|
||||
}
|
||||
servers = append(servers, server)
|
||||
}
|
||||
return servers, warnings, nil
|
||||
}
|
||||
|
||||
//nolint:goconst
|
||||
func stringifyNordvpnServers(servers []models.NordvpnServer) (s string) {
|
||||
s = "func NordvpnServers() []models.NordvpnServer {\n"
|
||||
s += " return []models.NordvpnServer{\n"
|
||||
for _, server := range servers {
|
||||
s += " " + server.String() + ",\n"
|
||||
}
|
||||
s += " }\n"
|
||||
s += "}"
|
||||
return s
|
||||
}
|
||||
@@ -26,3 +26,14 @@ func extractIPsFromRemoteLines(remoteLines []string) (ips []net.IP) {
|
||||
}
|
||||
return ips
|
||||
}
|
||||
|
||||
func extractHostnamesFromRemoteLines(remoteLines []string) (hostnames []string) {
|
||||
for _, remoteLine := range remoteLines {
|
||||
fields := strings.Fields(remoteLine)
|
||||
if len(fields[1]) == 0 {
|
||||
continue
|
||||
}
|
||||
hostnames = append(hostnames, fields[1])
|
||||
}
|
||||
return hostnames
|
||||
}
|
||||
|
||||
@@ -1,9 +1,16 @@
|
||||
package updater
|
||||
|
||||
type Options struct {
|
||||
PIA bool
|
||||
PIAold bool
|
||||
Mullvad bool
|
||||
File bool // update JSON file (user side)
|
||||
Stdout bool // update constants file (maintainer side)
|
||||
Cyberghost bool
|
||||
Mullvad bool
|
||||
Nordvpn bool
|
||||
PIA bool
|
||||
PIAold bool
|
||||
Purevpn bool
|
||||
Surfshark bool
|
||||
Vyprvpn bool
|
||||
Windscribe bool
|
||||
File bool // update JSON file (user side)
|
||||
Stdout bool // update constants file (maintainer side)
|
||||
DNSAddress string
|
||||
}
|
||||
|
||||
@@ -8,12 +8,32 @@ import (
|
||||
"github.com/qdm12/gluetun/internal/models"
|
||||
)
|
||||
|
||||
func findPIAServers(new bool) (servers []models.PIAServer, err error) {
|
||||
zipURL := "https://www.privateinternetaccess.com/openvpn/openvpn-ip.zip"
|
||||
if new {
|
||||
zipURL = "https://www.privateinternetaccess.com/openvpn/openvpn-ip-nextgen.zip"
|
||||
func (u *updater) updatePIA() (err error) {
|
||||
const zipURL = "https://www.privateinternetaccess.com/openvpn/openvpn-ip-nextgen.zip"
|
||||
servers, err := findPIAServersFromURL(zipURL)
|
||||
if err != nil {
|
||||
return fmt.Errorf("cannot update PIA servers: %w", err)
|
||||
}
|
||||
return findPIAServersFromURL(zipURL)
|
||||
if u.options.Stdout {
|
||||
u.println(stringifyPIAServers(servers))
|
||||
}
|
||||
u.servers.Pia.Timestamp = u.timeNow().Unix()
|
||||
u.servers.Pia.Servers = servers
|
||||
return nil
|
||||
}
|
||||
|
||||
func (u *updater) updatePIAOld() (err error) {
|
||||
const zipURL = "https://www.privateinternetaccess.com/openvpn/openvpn-ip.zip"
|
||||
servers, err := findPIAServersFromURL(zipURL)
|
||||
if err != nil {
|
||||
return fmt.Errorf("cannot update old PIA servers: %w", err)
|
||||
}
|
||||
if u.options.Stdout {
|
||||
u.println(stringifyPIAOldServers(servers))
|
||||
}
|
||||
u.servers.PiaOld.Timestamp = u.timeNow().Unix()
|
||||
u.servers.PiaOld.Servers = servers
|
||||
return nil
|
||||
}
|
||||
|
||||
func findPIAServersFromURL(zipURL string) (servers []models.PIAServer, err error) {
|
||||
@@ -33,7 +53,7 @@ func findPIAServersFromURL(zipURL string) (servers []models.PIAServer, err error
|
||||
region := strings.TrimSuffix(fileName, ".ovpn")
|
||||
server := models.PIAServer{
|
||||
Region: region,
|
||||
IPs: IPs,
|
||||
IPs: uniqueSortedIPs(IPs),
|
||||
}
|
||||
servers = append(servers, server)
|
||||
}
|
||||
|
||||
113
internal/updater/purevpn.go
Normal file
113
internal/updater/purevpn.go
Normal file
@@ -0,0 +1,113 @@
|
||||
package updater
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"sort"
|
||||
"strings"
|
||||
|
||||
"github.com/qdm12/gluetun/internal/models"
|
||||
)
|
||||
|
||||
func (u *updater) updatePurevpn(ctx context.Context) (err error) {
|
||||
servers, warnings, err := findPurevpnServers(ctx, u.httpGet, u.lookupIP)
|
||||
for _, warning := range warnings {
|
||||
u.println(warning)
|
||||
}
|
||||
if err != nil {
|
||||
return fmt.Errorf("cannot update Purevpn servers: %w", err)
|
||||
}
|
||||
if u.options.Stdout {
|
||||
u.println(stringifyPurevpnServers(servers))
|
||||
}
|
||||
u.servers.Purevpn.Timestamp = u.timeNow().Unix()
|
||||
u.servers.Purevpn.Servers = servers
|
||||
return nil
|
||||
}
|
||||
|
||||
func findPurevpnServers(ctx context.Context, httpGet httpGetFunc, lookupIP lookupIPFunc) (
|
||||
servers []models.PurevpnServer, warnings []string, err error) {
|
||||
const url = "https://support.purevpn.com/vpn-servers"
|
||||
response, err := httpGet(url)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
defer response.Body.Close()
|
||||
if response.StatusCode != http.StatusOK {
|
||||
return nil, nil, fmt.Errorf(response.Status)
|
||||
}
|
||||
bytes, err := ioutil.ReadAll(response.Body)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
const jsonPrefix = "<script>var servers = "
|
||||
const jsonSuffix = "</script>"
|
||||
s := string(bytes)
|
||||
jsonPrefixIndex := strings.Index(s, jsonPrefix)
|
||||
if jsonPrefixIndex == -1 {
|
||||
return nil, nil, fmt.Errorf("cannot find %q in html", jsonPrefix)
|
||||
}
|
||||
s = s[jsonPrefixIndex+len(jsonPrefix):]
|
||||
endIndex := strings.Index(s, jsonSuffix)
|
||||
if endIndex == -1 {
|
||||
return nil, nil, fmt.Errorf("cannot find %q after %q in html", jsonSuffix, jsonPrefix)
|
||||
}
|
||||
s = s[:endIndex]
|
||||
var data []struct {
|
||||
Region string `json:"region_name"`
|
||||
Country string `json:"country_name"`
|
||||
City string `json:"city_name"`
|
||||
TCP string `json:"tcp"`
|
||||
UDP string `json:"udp"`
|
||||
}
|
||||
if err := json.Unmarshal([]byte(s), &data); err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
sort.Slice(data, func(i, j int) bool {
|
||||
if data[i].Region == data[j].Region {
|
||||
if data[i].Country == data[j].Country {
|
||||
return data[i].City < data[j].City
|
||||
}
|
||||
return data[i].Country < data[j].Country
|
||||
}
|
||||
return data[i].Region < data[j].Region
|
||||
})
|
||||
for _, jsonServer := range data {
|
||||
if jsonServer.UDP == "" && jsonServer.TCP == "" {
|
||||
warnings = append(warnings, fmt.Sprintf("server %s %s %s does not support TCP and UDP for openvpn", jsonServer.Region, jsonServer.Country, jsonServer.City))
|
||||
continue
|
||||
}
|
||||
if jsonServer.UDP == "" || jsonServer.TCP == "" {
|
||||
warnings = append(warnings, fmt.Sprintf("server %s %s %s does not support TCP or UDP for openvpn", jsonServer.Region, jsonServer.Country, jsonServer.City))
|
||||
continue
|
||||
}
|
||||
host := jsonServer.UDP
|
||||
const repetition = 3
|
||||
IPs, err := resolveRepeat(ctx, lookupIP, host, repetition)
|
||||
if err != nil {
|
||||
warnings = append(warnings, err.Error())
|
||||
continue
|
||||
}
|
||||
servers = append(servers, models.PurevpnServer{
|
||||
Region: jsonServer.Region,
|
||||
Country: jsonServer.Country,
|
||||
City: jsonServer.City,
|
||||
IPs: IPs,
|
||||
})
|
||||
}
|
||||
return servers, warnings, nil
|
||||
}
|
||||
|
||||
func stringifyPurevpnServers(servers []models.PurevpnServer) (s string) {
|
||||
s = "func PurevpnServers() []models.PurevpnServer {\n"
|
||||
s += " return []models.PurevpnServer{\n"
|
||||
for _, server := range servers {
|
||||
s += " " + server.String() + ",\n"
|
||||
}
|
||||
s += " }\n"
|
||||
s += "}"
|
||||
return s
|
||||
}
|
||||
41
internal/updater/resolver.go
Normal file
41
internal/updater/resolver.go
Normal file
@@ -0,0 +1,41 @@
|
||||
package updater
|
||||
|
||||
import (
|
||||
"context"
|
||||
"net"
|
||||
)
|
||||
|
||||
func newResolver(resolverAddress string) *net.Resolver {
|
||||
return &net.Resolver{
|
||||
PreferGo: true,
|
||||
Dial: func(ctx context.Context, network, address string) (net.Conn, error) {
|
||||
d := net.Dialer{}
|
||||
return d.DialContext(ctx, "udp", net.JoinHostPort(resolverAddress, "53"))
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func newLookupIP(r *net.Resolver) lookupIPFunc {
|
||||
return func(ctx context.Context, host string) (ips []net.IP, err error) {
|
||||
addresses, err := r.LookupIPAddr(ctx, host)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
ips = make([]net.IP, len(addresses))
|
||||
for i := range addresses {
|
||||
ips[i] = addresses[i].IP
|
||||
}
|
||||
return ips, nil
|
||||
}
|
||||
}
|
||||
|
||||
func resolveRepeat(ctx context.Context, lookupIP lookupIPFunc, host string, n int) (ips []net.IP, err error) {
|
||||
for i := 0; i < n; i++ {
|
||||
newIPs, err := lookupIP(ctx, host)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
ips = append(ips, newIPs...)
|
||||
}
|
||||
return uniqueSortedIPs(ips), nil
|
||||
}
|
||||
249
internal/updater/surfshark.go
Normal file
249
internal/updater/surfshark.go
Normal file
@@ -0,0 +1,249 @@
|
||||
package updater
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"net"
|
||||
"sort"
|
||||
"strings"
|
||||
|
||||
"github.com/qdm12/gluetun/internal/models"
|
||||
)
|
||||
|
||||
func (u *updater) updateSurfshark(ctx context.Context) (err error) {
|
||||
servers, err := findSurfsharkServers(ctx, u.lookupIP)
|
||||
if err != nil {
|
||||
return fmt.Errorf("cannot update Surfshark servers: %w", err)
|
||||
}
|
||||
if u.options.Stdout {
|
||||
u.println(stringifySurfsharkServers(servers))
|
||||
}
|
||||
u.servers.Surfshark.Timestamp = u.timeNow().Unix()
|
||||
u.servers.Surfshark.Servers = servers
|
||||
return nil
|
||||
}
|
||||
|
||||
func findSurfsharkServers(ctx context.Context, lookupIP lookupIPFunc) (servers []models.SurfsharkServer, err error) {
|
||||
const zipURL = "https://account.surfshark.com/api/v1/server/configurations"
|
||||
contents, err := fetchAndExtractFiles(zipURL)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
for fileName, content := range contents {
|
||||
if strings.HasSuffix(fileName, "_tcp.ovpn") {
|
||||
continue // only parse UDP files
|
||||
}
|
||||
remoteLines := extractRemoteLinesFromOpenvpn(content)
|
||||
if len(remoteLines) == 0 {
|
||||
return nil, fmt.Errorf("cannot find any remote lines in %s", fileName)
|
||||
}
|
||||
hosts := extractHostnamesFromRemoteLines(remoteLines)
|
||||
if len(hosts) == 0 {
|
||||
return nil, fmt.Errorf("cannot find any hosts in %s", fileName)
|
||||
}
|
||||
var IPs []net.IP
|
||||
var region string
|
||||
for _, host := range hosts {
|
||||
if net.ParseIP(host) != nil {
|
||||
// only a few IP addresses, no idea for what region
|
||||
// ignore them
|
||||
continue
|
||||
}
|
||||
const repetition = 3
|
||||
newIPs, err := resolveRepeat(ctx, lookupIP, host, repetition)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
IPs = append(IPs, newIPs...)
|
||||
if region == "" {
|
||||
subdomain := strings.TrimSuffix(host, ".prod.surfshark.com")
|
||||
region = surfsharkSubdomainToRegion(subdomain)
|
||||
}
|
||||
}
|
||||
if len(IPs) == 0 {
|
||||
continue // only IPs, no hostnames found
|
||||
}
|
||||
if region == "" { // region not found in mapping
|
||||
region = strings.TrimSuffix(hosts[0], ".prod.surfshark.com")
|
||||
}
|
||||
server := models.SurfsharkServer{
|
||||
Region: region,
|
||||
IPs: uniqueSortedIPs(IPs),
|
||||
}
|
||||
servers = append(servers, server)
|
||||
}
|
||||
sort.Slice(servers, func(i, j int) bool {
|
||||
return servers[i].Region < servers[j].Region
|
||||
})
|
||||
return servers, nil
|
||||
}
|
||||
|
||||
func stringifySurfsharkServers(servers []models.SurfsharkServer) (s string) {
|
||||
s = "func SurfsharkServers() []models.SurfsharkServer {\n"
|
||||
s += " return []models.SurfsharkServer{\n"
|
||||
for _, server := range servers {
|
||||
s += " " + server.String() + ",\n"
|
||||
}
|
||||
s += " }\n"
|
||||
s += "}"
|
||||
return s
|
||||
}
|
||||
|
||||
func surfsharkSubdomainToRegion(subdomain string) (region string) {
|
||||
return map[string]string{
|
||||
"ae-dub": "United Arab Emirates",
|
||||
"al-tia": "Albania",
|
||||
"at-vie": "Austria",
|
||||
"au-adl": "Australia Adelaide",
|
||||
"au-bne": "Australia Brisbane",
|
||||
"au-mel": "Australia Melbourne",
|
||||
"au-per": "Australia Perth",
|
||||
"au-syd": "Australia Sydney",
|
||||
"au-us": "Australia US",
|
||||
"az-bak": "Azerbaijan",
|
||||
"ba-sjj": "Bosnia and Herzegovina",
|
||||
"be-bru": "Belgium",
|
||||
"bg-sof": "Bulgaria",
|
||||
"br-sao": "Brazil",
|
||||
"ca-mon": "Canada Montreal",
|
||||
"ca-tor": "Canada Toronto",
|
||||
"ca-us": "Canada US",
|
||||
"ca-van": "Canada Vancouver",
|
||||
"ch-zur": "Switzerland",
|
||||
"cl-san": "Chile",
|
||||
"co-bog": "Colombia",
|
||||
"cr-sjn": "Costa Rica",
|
||||
"cy-nic": "Cyprus",
|
||||
"cz-prg": "Czech Republic",
|
||||
"de-ber": "Germany Berlin",
|
||||
"de-fra": "Germany Frankfurt am Main",
|
||||
"de-fra-st001": "Germany Frankfurt am Main st001",
|
||||
"de-fra-st002": "Germany Frankfurt am Main st002",
|
||||
"de-fra-st003": "Germany Frankfurt am Main st003",
|
||||
"de-muc": "Germany Munich",
|
||||
"de-nue": "Germany Nuremberg",
|
||||
"de-sg": "Germany Singapour",
|
||||
"de-uk": "Germany UK",
|
||||
"dk-cph": "Denmark",
|
||||
"ee-tll": "Estonia",
|
||||
"es-bcn": "Spain Barcelona",
|
||||
"es-mad": "Spain Madrid",
|
||||
"es-vlc": "Spain Valencia",
|
||||
"fi-hel": "Finland",
|
||||
"fr-bod": "France Bordeaux",
|
||||
"fr-mrs": "France Marseilles",
|
||||
"fr-par": "France Paris",
|
||||
"fr-se": "France Sweden",
|
||||
"gr-ath": "Greece",
|
||||
"hk-hkg": "Hong Kong",
|
||||
"hr-zag": "Croatia",
|
||||
"hu-bud": "Hungary",
|
||||
"id-jak": "Indonesia",
|
||||
"ie-dub": "Ireland",
|
||||
"il-tlv": "Israel",
|
||||
"in-chn": "India Chennai",
|
||||
"in-idr": "India Indore",
|
||||
"in-mum": "India Mumbai",
|
||||
"in-uk": "India UK",
|
||||
"is-rkv": "Iceland",
|
||||
"it-mil": "Italy Milan",
|
||||
"it-rom": "Italy Rome",
|
||||
"jp-tok": "Japan Tokyo",
|
||||
"jp-tok-st001": "Japan Tokyo st001",
|
||||
"jp-tok-st002": "Japan Tokyo st002",
|
||||
"jp-tok-st003": "Japan Tokyo st003",
|
||||
"jp-tok-st004": "Japan Tokyo st004",
|
||||
"jp-tok-st005": "Japan Tokyo st005",
|
||||
"jp-tok-st006": "Japan Tokyo st006",
|
||||
"jp-tok-st007": "Japan Tokyo st007",
|
||||
"kr-seo": "Korea",
|
||||
"kz-ura": "Kazakhstan",
|
||||
"lu-ste": "Luxembourg",
|
||||
"lv-rig": "Latvia",
|
||||
"ly-tip": "Libya",
|
||||
"md-chi": "Moldova",
|
||||
"mk-skp": "North Macedonia",
|
||||
"my-kul": "Malaysia",
|
||||
"ng-lag": "Nigeria",
|
||||
"nl-ams": "Netherlands Amsterdam",
|
||||
"nl-ams-st001": "Netherlands Amsterdam st001",
|
||||
"nl-us": "Netherlands US",
|
||||
"no-osl": "Norway",
|
||||
"nz-akl": "New Zealand",
|
||||
"ph-mnl": "Philippines",
|
||||
"pl-gdn": "Poland Gdansk",
|
||||
"pl-waw": "Poland Warsaw",
|
||||
"pt-lis": "Portugal Lisbon",
|
||||
"pt-lou": "Portugal Loule",
|
||||
"pt-opo": "Portugal Porto",
|
||||
"py-asu": "Paraguay",
|
||||
"ro-buc": "Romania",
|
||||
"rs-beg": "Serbia",
|
||||
"ru-mos": "Russia Moscow",
|
||||
"ru-spt": "Russia St. Petersburg",
|
||||
"se-sto": "Sweden",
|
||||
"sg-hk": "Singapore Hong Kong",
|
||||
"sg-nl": "Singapore Netherlands",
|
||||
"sg-sng": "Singapore",
|
||||
"sg-in": "Singapore in",
|
||||
"sg-sng-st001": "Singapore st001",
|
||||
"sg-sng-st002": "Singapore st002",
|
||||
"sg-sng-st003": "Singapore st003",
|
||||
"sg-sng-st004": "Singapore st004",
|
||||
"sg-sng-mp001": "Singapore mp001",
|
||||
"si-lju": "Slovenia",
|
||||
"sk-bts": "Slovekia",
|
||||
"th-bkk": "Thailand",
|
||||
"tr-bur": "Turkey",
|
||||
"tw-tai": "Taiwan",
|
||||
"ua-iev": "Ukraine",
|
||||
"uk-de": "UK Germany",
|
||||
"uk-fr": "UK France",
|
||||
"uk-gla": "UK Glasgow",
|
||||
"uk-lon": "UK London",
|
||||
"uk-lon-mp001": "UK London mp001",
|
||||
"uk-lon-st001": "UK London st001",
|
||||
"uk-lon-st002": "UK London st002",
|
||||
"uk-lon-st003": "UK London st003",
|
||||
"uk-lon-st004": "UK London st004",
|
||||
"uk-lon-st005": "UK London st005",
|
||||
"uk-man": "UK Manchester",
|
||||
"us-atl": "US Atlanta",
|
||||
"us-bdn": "US Bend",
|
||||
"us-bos": "US Boston",
|
||||
"us-buf": "US Buffalo",
|
||||
"us-chi": "US Chicago",
|
||||
"us-clt": "US Charlotte",
|
||||
"us-dal": "US Dallas",
|
||||
"us-den": "US Denver",
|
||||
"us-dtw": "US Gahanna",
|
||||
"us-hou": "US Houston",
|
||||
"us-kan": "US Kansas City",
|
||||
"us-las": "US Las Vegas",
|
||||
"us-lax": "US Los Angeles",
|
||||
"us-ltm": "US Latham",
|
||||
"us-mia": "US Miami",
|
||||
"us-mnz": "US Maryland",
|
||||
"us-nl": "US Netherlands",
|
||||
"us-nyc": "US New York City",
|
||||
"us-nyc-mp001": "US New York City mp001",
|
||||
"us-nyc-st001": "US New York City st001",
|
||||
"us-nyc-st002": "US New York City st002",
|
||||
"us-nyc-st003": "US New York City st003",
|
||||
"us-nyc-st004": "US New York City st004",
|
||||
"us-nyc-st005": "US New York City st005",
|
||||
"us-orl": "US Orlando",
|
||||
"us-phx": "US Phoenix",
|
||||
"us-pt": "US Portugal",
|
||||
"us-sea": "US Seatle",
|
||||
"us-sfo": "US San Francisco",
|
||||
"us-slc": "US Salt Lake City",
|
||||
"us-stl": "US Saint Louis",
|
||||
"us-tpa": "US Tampa",
|
||||
"vn-hcm": "Vietnam",
|
||||
"za-jnb": "South Africa",
|
||||
"ar-bua": "Argentina Buenos Aires",
|
||||
"tr-ist": "Turkey Istanbul",
|
||||
"mx-mex": "Mexico City Mexico",
|
||||
}[subdomain]
|
||||
}
|
||||
@@ -1,81 +1,112 @@
|
||||
package updater
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"time"
|
||||
|
||||
"github.com/qdm12/gluetun/internal/constants"
|
||||
"github.com/qdm12/gluetun/internal/models"
|
||||
"github.com/qdm12/gluetun/internal/storage"
|
||||
)
|
||||
|
||||
type Updater interface {
|
||||
UpdateServers(options Options) error
|
||||
UpdateServers(ctx context.Context) error
|
||||
}
|
||||
|
||||
type updater struct {
|
||||
// configuration
|
||||
options Options
|
||||
storage storage.Storage
|
||||
timeNow func() time.Time
|
||||
println func(s string)
|
||||
httpGet func(url string) (resp *http.Response, err error)
|
||||
|
||||
// state
|
||||
servers models.AllServers
|
||||
|
||||
// Functions for tests
|
||||
timeNow func() time.Time
|
||||
println func(s string)
|
||||
httpGet httpGetFunc
|
||||
lookupIP lookupIPFunc
|
||||
}
|
||||
|
||||
func New(storage storage.Storage, httpClient *http.Client) Updater {
|
||||
func New(options Options, storage storage.Storage, httpClient *http.Client) Updater {
|
||||
if len(options.DNSAddress) == 0 {
|
||||
options.DNSAddress = "1.1.1.1"
|
||||
}
|
||||
resolver := newResolver(options.DNSAddress)
|
||||
return &updater{
|
||||
storage: storage,
|
||||
timeNow: time.Now,
|
||||
println: func(s string) { fmt.Println(s) },
|
||||
httpGet: httpClient.Get,
|
||||
storage: storage,
|
||||
timeNow: time.Now,
|
||||
println: func(s string) { fmt.Println(s) },
|
||||
httpGet: httpClient.Get,
|
||||
lookupIP: newLookupIP(resolver),
|
||||
options: options,
|
||||
}
|
||||
}
|
||||
|
||||
func (u *updater) UpdateServers(options Options) error {
|
||||
// TODO parallelize DNS resolution
|
||||
func (u *updater) UpdateServers(ctx context.Context) (err error) {
|
||||
const writeSync = false
|
||||
allServers, err := u.storage.SyncServers(constants.GetAllServers(), writeSync)
|
||||
u.servers, err = u.storage.SyncServers(constants.GetAllServers(), writeSync)
|
||||
if err != nil {
|
||||
return fmt.Errorf("cannot update servers: %w", err)
|
||||
}
|
||||
|
||||
if options.PIA {
|
||||
const newServers = true
|
||||
servers, err := findPIAServers(newServers)
|
||||
if err != nil {
|
||||
return fmt.Errorf("cannot update PIA servers: %w", err)
|
||||
}
|
||||
if options.Stdout {
|
||||
u.println(stringifyPIAServers(servers))
|
||||
}
|
||||
allServers.Pia.Timestamp = u.timeNow().Unix()
|
||||
allServers.Pia.Servers = servers
|
||||
if u.options.Cyberghost {
|
||||
u.updateCyberghost(ctx)
|
||||
}
|
||||
|
||||
if options.PIAold {
|
||||
const newServers = false
|
||||
servers, err := findPIAServers(newServers)
|
||||
if err != nil {
|
||||
return fmt.Errorf("cannot update PIA old servers: %w", err)
|
||||
if u.options.Mullvad {
|
||||
if err := u.updateMullvad(); err != nil {
|
||||
return err
|
||||
}
|
||||
if options.Stdout {
|
||||
u.println(stringifyPIAOldServers(servers))
|
||||
}
|
||||
allServers.PiaOld.Timestamp = u.timeNow().Unix()
|
||||
allServers.PiaOld.Servers = servers
|
||||
}
|
||||
|
||||
if options.Mullvad {
|
||||
servers, err := u.findMullvadServers()
|
||||
if err != nil {
|
||||
return fmt.Errorf("cannot update Mullvad servers: %w", err)
|
||||
if u.options.Nordvpn {
|
||||
// TODO support servers offering only TCP or only UDP
|
||||
if err := u.updateNordvpn(); err != nil {
|
||||
return err
|
||||
}
|
||||
if options.Stdout {
|
||||
u.println(stringifyMullvadServers(servers))
|
||||
}
|
||||
allServers.Mullvad.Timestamp = u.timeNow().Unix()
|
||||
allServers.Mullvad.Servers = servers
|
||||
}
|
||||
|
||||
if options.File {
|
||||
if err := u.storage.FlushToFile(allServers); err != nil {
|
||||
if u.options.PIA {
|
||||
if err := u.updatePIA(); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
if u.options.PIAold {
|
||||
if err := u.updatePIAOld(); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
if u.options.Purevpn {
|
||||
// TODO support servers offering only TCP or only UDP
|
||||
if err := u.updatePurevpn(ctx); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
if u.options.Surfshark {
|
||||
if err := u.updateSurfshark(ctx); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
if u.options.Vyprvpn {
|
||||
if err := u.updateVyprvpn(ctx); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
if u.options.Windscribe {
|
||||
u.updateWindscribe(ctx)
|
||||
}
|
||||
|
||||
if u.options.File {
|
||||
if err := u.storage.FlushToFile(u.servers); err != nil {
|
||||
return fmt.Errorf("cannot update servers: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
72
internal/updater/vyprvpn.go
Normal file
72
internal/updater/vyprvpn.go
Normal file
@@ -0,0 +1,72 @@
|
||||
package updater
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"net"
|
||||
"sort"
|
||||
"strings"
|
||||
|
||||
"github.com/qdm12/gluetun/internal/models"
|
||||
)
|
||||
|
||||
func (u *updater) updateVyprvpn(ctx context.Context) (err error) {
|
||||
servers, err := findVyprvpnServers(ctx, u.lookupIP)
|
||||
if err != nil {
|
||||
return fmt.Errorf("cannot update Vyprvpn servers: %w", err)
|
||||
}
|
||||
if u.options.Stdout {
|
||||
u.println(stringifyVyprvpnServers(servers))
|
||||
}
|
||||
u.servers.Vyprvpn.Timestamp = u.timeNow().Unix()
|
||||
u.servers.Vyprvpn.Servers = servers
|
||||
return nil
|
||||
}
|
||||
|
||||
func findVyprvpnServers(ctx context.Context, lookupIP lookupIPFunc) (servers []models.VyprvpnServer, err error) {
|
||||
const zipURL = "https://support.vyprvpn.com/hc/article_attachments/360052617332/Vypr_OpenVPN_20200320.zip"
|
||||
contents, err := fetchAndExtractFiles(zipURL)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
for fileName, content := range contents {
|
||||
remoteLines := extractRemoteLinesFromOpenvpn(content)
|
||||
if len(remoteLines) == 0 {
|
||||
return nil, fmt.Errorf("cannot find any remote lines in %s", fileName)
|
||||
}
|
||||
hosts := extractHostnamesFromRemoteLines(remoteLines)
|
||||
if len(hosts) == 0 {
|
||||
return nil, fmt.Errorf("cannot find any hosts in %s", fileName)
|
||||
}
|
||||
var IPs []net.IP
|
||||
for _, host := range hosts {
|
||||
newIPs, err := lookupIP(ctx, host)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
IPs = append(IPs, newIPs...)
|
||||
}
|
||||
region := strings.TrimSuffix(fileName, ".ovpn")
|
||||
region = strings.ReplaceAll(region, " - ", " ")
|
||||
server := models.VyprvpnServer{
|
||||
Region: region,
|
||||
IPs: uniqueSortedIPs(IPs),
|
||||
}
|
||||
servers = append(servers, server)
|
||||
}
|
||||
sort.Slice(servers, func(i, j int) bool {
|
||||
return servers[i].Region < servers[j].Region
|
||||
})
|
||||
return servers, nil
|
||||
}
|
||||
|
||||
func stringifyVyprvpnServers(servers []models.VyprvpnServer) (s string) {
|
||||
s = "func VyprvpnServers() []models.VyprvpnServer {\n"
|
||||
s += " return []models.VyprvpnServer{\n"
|
||||
for _, server := range servers {
|
||||
s += " " + server.String() + ",\n"
|
||||
}
|
||||
s += " }\n"
|
||||
s += "}"
|
||||
return s
|
||||
}
|
||||
138
internal/updater/windscribe.go
Normal file
138
internal/updater/windscribe.go
Normal file
@@ -0,0 +1,138 @@
|
||||
package updater
|
||||
|
||||
import (
|
||||
"context"
|
||||
"sort"
|
||||
|
||||
"github.com/qdm12/gluetun/internal/models"
|
||||
)
|
||||
|
||||
func (u *updater) updateWindscribe(ctx context.Context) {
|
||||
servers := findWindscribeServers(ctx, u.lookupIP)
|
||||
if u.options.Stdout {
|
||||
u.println(stringifyWindscribeServers(servers))
|
||||
}
|
||||
u.servers.Windscribe.Timestamp = u.timeNow().Unix()
|
||||
u.servers.Windscribe.Servers = servers
|
||||
}
|
||||
|
||||
func findWindscribeServers(ctx context.Context, lookupIP lookupIPFunc) (servers []models.WindscribeServer) {
|
||||
allCountryCodes := getCountryCodes()
|
||||
windscribeCountryCodes := getWindscribeSubdomainToRegion()
|
||||
possibleCountryCodes := mergeCountryCodes(windscribeCountryCodes, allCountryCodes)
|
||||
const domain = "windscribe.com"
|
||||
for countryCode, region := range possibleCountryCodes {
|
||||
host := countryCode + "." + domain
|
||||
ips, err := resolveRepeat(ctx, lookupIP, host, 2)
|
||||
if err != nil || len(ips) == 0 {
|
||||
continue
|
||||
}
|
||||
servers = append(servers, models.WindscribeServer{
|
||||
Region: region,
|
||||
IPs: ips,
|
||||
})
|
||||
}
|
||||
sort.Slice(servers, func(i, j int) bool {
|
||||
return servers[i].Region < servers[j].Region
|
||||
})
|
||||
return servers
|
||||
}
|
||||
|
||||
func mergeCountryCodes(base, extend map[string]string) (merged map[string]string) {
|
||||
merged = make(map[string]string, len(base))
|
||||
for countryCode, region := range base {
|
||||
merged[countryCode] = region
|
||||
}
|
||||
for countryCode := range base {
|
||||
delete(extend, countryCode)
|
||||
}
|
||||
for countryCode, region := range extend {
|
||||
merged[countryCode] = region
|
||||
}
|
||||
return merged
|
||||
}
|
||||
|
||||
func stringifyWindscribeServers(servers []models.WindscribeServer) (s string) {
|
||||
s = "func WindscribeServers() []models.WindscribeServer {\n"
|
||||
s += " return []models.WindscribeServer{\n"
|
||||
for _, server := range servers {
|
||||
s += " " + server.String() + ",\n"
|
||||
}
|
||||
s += " }\n"
|
||||
s += "}"
|
||||
return s
|
||||
}
|
||||
|
||||
func getWindscribeSubdomainToRegion() map[string]string {
|
||||
return map[string]string{
|
||||
"al": "Albania",
|
||||
"ar": "Argentina",
|
||||
"au": "Australia",
|
||||
"at": "Austria",
|
||||
"az": "Azerbaijan",
|
||||
"be": "Belgium",
|
||||
"ba": "Bosnia",
|
||||
"br": "Brazil",
|
||||
"bg": "Bulgaria",
|
||||
"ca": "Canada East",
|
||||
"ca-west": "Canada West",
|
||||
"co": "Colombia",
|
||||
"hr": "Croatia",
|
||||
"cy": "Cyprus",
|
||||
"cz": "Czech republic",
|
||||
"dk": "Denmark",
|
||||
"ee": "Estonia",
|
||||
"aq": "Fake antarctica",
|
||||
"fi": "Finland",
|
||||
"fr": "France",
|
||||
"ge": "Georgia",
|
||||
"de": "Germany",
|
||||
"gr": "Greece",
|
||||
"hk": "Hong kong",
|
||||
"hu": "Hungary",
|
||||
"is": "Iceland",
|
||||
"in": "India",
|
||||
"id": "Indonesia",
|
||||
"ie": "Ireland",
|
||||
"il": "Israel",
|
||||
"it": "Italy",
|
||||
"jp": "Japan",
|
||||
"lv": "Latvia",
|
||||
"lt": "Lithuania",
|
||||
"mk": "Macedonia",
|
||||
"my": "Malaysia",
|
||||
"mx": "Mexico",
|
||||
"md": "Moldova",
|
||||
"nl": "Netherlands",
|
||||
"nz": "New zealand",
|
||||
"no": "Norway",
|
||||
"ph": "Philippines",
|
||||
"pl": "Poland",
|
||||
"pt": "Portugal",
|
||||
"ro": "Romania",
|
||||
"ru": "Russia",
|
||||
"rs": "Serbia",
|
||||
"sg": "Singapore",
|
||||
"sk": "Slovakia",
|
||||
"si": "Slovenia",
|
||||
"za": "South Africa",
|
||||
"kr": "South Korea",
|
||||
"es": "Spain",
|
||||
"se": "Sweden",
|
||||
"ch": "Switzerland",
|
||||
"th": "Thailand",
|
||||
"tn": "Tunisia",
|
||||
"tr": "Turkey",
|
||||
"ua": "Ukraine",
|
||||
"ae": "United Arab Emirates",
|
||||
"uk": "United Kingdom",
|
||||
"us-central": "US Central",
|
||||
"us-east": "US East",
|
||||
"us-west": "US West",
|
||||
"vn": "Vietnam",
|
||||
"wf-ca": "Windflix CA",
|
||||
"wf-jp": "Windflix JP",
|
||||
"wf-uk": "Windflix UK",
|
||||
"wf-us": "Windflix US",
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user