From 618441b0085a32796e0a2f05e06c8aaa3a3604ed Mon Sep 17 00:00:00 2001 From: Quentin McGaw Date: Mon, 13 Jul 2020 08:04:35 -0400 Subject: [PATCH] Vyprvpn support, fix #181 (#193) --- README.md | 18 +++++- cmd/ovpnparser/main.go | 58 ++++++++++++----- cmd/resolver/main.go | 88 ++++++++++++++++++++++++- internal/constants/vpn.go | 2 + internal/constants/vyprvpn.go | 98 ++++++++++++++++++++++++++++ internal/models/selection.go | 4 ++ internal/models/servers.go | 5 ++ internal/params/params.go | 5 +- internal/params/vypervpn.go | 11 ++++ internal/provider/provider.go | 2 + internal/provider/vyprvpn.go | 113 +++++++++++++++++++++++++++++++++ internal/settings/openvpn.go | 2 + internal/settings/providers.go | 18 ++++++ 13 files changed, 404 insertions(+), 20 deletions(-) create mode 100644 internal/constants/vyprvpn.go create mode 100644 internal/params/vypervpn.go create mode 100644 internal/provider/vyprvpn.go diff --git a/README.md b/README.md index a0e6933f..d2ba1ddc 100644 --- a/README.md +++ b/README.md @@ -68,6 +68,10 @@ - Pick the [region](https://github.com/qdm12/private-internet-access-docker/wiki/Cyberghost) +### Vyprvpn + +- Pick the [region](https://www.vyprvpn.com/server-locations) + ### Extra niche features - Possibility of split horizon DNS by selecting multiple DNS over TLS providers @@ -86,7 +90,9 @@ [![https://windscribe.com/?affid=mh7nyafu](https://raw.githubusercontent.com/qdm12/private-internet-access-docker/master/doc/windscribe.jpg)](https://windscribe.com/?affid=mh7nyafu) - Surfshark: **username** and **password** ([sign up](https://order.surfshark.com/)) - - Cyberghost: **username**, **password** and **device client key file** ([sign up](https://www.cyberghostvpn.com/en_US/buy/cyberghost-vpn-4)) + - Cyberghost: **username**, **password** and **device client key file** + ([sign up](https://www.cyberghostvpn.com/en_US/buy/cyberghost-vpn-4)) + - Vyprvpn: **username** and **password** - If you have a host or router firewall, please refer [to the firewall documentation](https://github.com/qdm12/private-internet-access-docker/blob/master/doc/firewall.md) 1. On some devices you may need to setup your tunnel kernel module on your host with `insmod /lib/modules/tun.ko` or `modprobe tun` @@ -136,7 +142,7 @@ Want more testing? ▶ [see the Wiki](https://github.com/qdm12/private-internet- | Variable | Default | Choices | Description | | --- | --- | --- | --- | -| 🏁 `VPNSP` | `private internet access` | `private internet access`, `mullvad`, `windscribe`, `surfshark` | VPN Service Provider | +| 🏁 `VPNSP` | `private internet access` | `private internet access`, `mullvad`, `windscribe`, `surfshark`, `vyprvpn` | VPN Service Provider | | `IP_STATUS_FILE` | `/ip` | Any filepath | Filepath to store the public IP address assigned | | `PROTOCOL` | `udp` | `udp` or `tcp` | Network protocol to use | | `OPENVPN_VERBOSITY` | `1` | `0` to `6` | Openvpn verbosity level | @@ -201,6 +207,14 @@ Want more testing? ▶ [see the Wiki](https://github.com/qdm12/private-internet- And use the line produced as the environment variable `CLIENT_KEY` +- VyprVPN + + | Variable | Default | Choices | Description | + | --- | --- | --- | --- | + | 🏁 `USER` | | | Your username | + | 🏁 `PASSWORD` | | | Your password | + | `REGION` | `Austria` | One of the [VyprVPN regions](https://www.vyprvpn.com/server-locations) | VPN server region | + ### DNS over TLS None of the following values are required. diff --git a/cmd/ovpnparser/main.go b/cmd/ovpnparser/main.go index da34560f..d03ffca6 100644 --- a/cmd/ovpnparser/main.go +++ b/cmd/ovpnparser/main.go @@ -9,6 +9,7 @@ import ( "net" "net/http" "os" + "path/filepath" "sort" "strings" "time" @@ -20,8 +21,9 @@ func main() { os.Exit(_main()) } +// Find subdomains from .ovpn files contained in a .zip file func _main() int { - provider := flag.String("provider", "surfshark", "VPN provider to parse openvpn files for, can be 'surfshark'") + provider := flag.String("provider", "surfshark", "VPN provider to parse openvpn files for, can be 'surfshark' or 'vyprvpn") flag.Parse() var urls []string @@ -33,6 +35,11 @@ func _main() int { "https://v2uploads.zopim.io/p/2/L/p2LbwLkvfQoSdzOl6VEltzQA6StiZqrs/12500634259669c77012765139bcfe4f4c90db1e.zip", } suffix = ".prod.surfshark.com" + case "vyprvpn": + urls = []string{ + "https://support.vyprvpn.com/hc/article_attachments/360052617332/Vypr_OpenVPN_20200320.zip", + } + suffix = ".vyprvpn.com" default: fmt.Printf("Provider %q is not supported\n", *provider) return 1 @@ -43,31 +50,44 @@ func _main() int { return 1 } - uniqueSubdomains := make(map[string]struct{}) - for _, content := range contents { + uniqueSubdomainsToFilename := make(map[string]string) + for fileName, content := range contents { subdomain, err := extractInformation(content, suffix) if err != nil { fmt.Println(err) return 1 } else if len(subdomain) > 0 { - uniqueSubdomains[subdomain] = struct{}{} + fileName = strings.TrimSuffix(fileName, ".ovpn") + fileName = strings.ReplaceAll(fileName, " - ", " ") + uniqueSubdomainsToFilename[subdomain] = fileName } } - subdomains := make([]string, len(uniqueSubdomains)) + type subdomainFilename struct { + subdomain string + fileName string + } + subdomains := make([]subdomainFilename, len(uniqueSubdomainsToFilename)) i := 0 - for subdomain := range uniqueSubdomains { - subdomains[i] = subdomain + for subdomain, fileName := range uniqueSubdomainsToFilename { + subdomains[i] = subdomainFilename{ + subdomain: subdomain, + fileName: fileName, + } i++ } sort.Slice(subdomains, func(i, j int) bool { - return subdomains[i] < subdomains[j] + return subdomains[i].subdomain < subdomains[j].subdomain }) - fmt.Println("Subdomains found are: ", strings.Join(subdomains, ",")) + fmt.Println("Subdomain Filename") + for i := range subdomains { + fmt.Printf("%s %s\n", subdomains[i].subdomain, subdomains[i].fileName) + } return 0 } -func fetchAndExtractFiles(urls ...string) (contents [][]byte, err error) { +func fetchAndExtractFiles(urls ...string) (contents map[string][]byte, err error) { client := network.NewClient(10 * time.Second) + contents = make(map[string][]byte) for _, url := range urls { zipBytes, status, err := client.GetContent(url) if err != nil { @@ -79,24 +99,30 @@ func fetchAndExtractFiles(urls ...string) (contents [][]byte, err error) { if err != nil { return nil, err } - contents = append(contents, newContents...) + for fileName, content := range newContents { + contents[fileName] = content + } } return contents, nil } -func zipExtractAll(zipBytes []byte) (contents [][]byte, err error) { +func zipExtractAll(zipBytes []byte) (contents map[string][]byte, err error) { r, err := zip.NewReader(bytes.NewReader(zipBytes), int64(len(zipBytes))) if err != nil { return nil, err } - contents = make([][]byte, len(r.File)) - for i, zf := range r.File { + contents = map[string][]byte{} + for _, zf := range r.File { + fileName := filepath.Base(zf.Name) + if !strings.HasSuffix(fileName, ".ovpn") { + continue + } f, err := zf.Open() if err != nil { return nil, err } defer f.Close() - contents[i], err = ioutil.ReadAll(f) + contents[fileName], err = ioutil.ReadAll(f) if err != nil { return nil, err } @@ -122,5 +148,5 @@ func extractInformation(content []byte, suffix string) (subdomain string, err er return strings.TrimSuffix(host, suffix), nil } } - return "", fmt.Errorf("could not find remote line") + return "", fmt.Errorf("could not find remote line in: %s", string(content)) } diff --git a/cmd/resolver/main.go b/cmd/resolver/main.go index 7bce933e..2c3e9d9e 100644 --- a/cmd/resolver/main.go +++ b/cmd/resolver/main.go @@ -18,7 +18,7 @@ func main() { func _main(ctx context.Context) int { resolverAddress := flag.String("resolver", "1.1.1.1", "DNS Resolver IP address to use") - provider := flag.String("provider", "pia", "VPN provider to resolve for, 'pia', 'windscribe' or 'cyberghost'") + provider := flag.String("provider", "pia", "VPN provider to resolve for, 'pia', 'windscribe', 'cyberghost' or 'vyprvpn'") region := flag.String("region", "all", "Comma separated list of VPN provider region names to resolve for, use 'all' to resolve all") flag.Parse() @@ -40,6 +40,9 @@ func _main(ctx context.Context) int { case "cyberghost": domain = "cg-dialup.net" servers = cyberghostServers() + case "vyprvpn": + domain = "vyprvpn.com" + servers = vyprvpnServers() default: fmt.Printf("Provider %q is not supported\n", *provider) return 1 @@ -127,6 +130,11 @@ func formatLine(provider string, s server, ips []net.IP) string { "{Region: %q, Group: %q, IPs: []net.IP{%s}},", s.region, s.group, ipString, ) + case "vyprvpn": + return fmt.Sprintf( + "{Region: %q, IPs: []net.IP{%s}},", + s.region, ipString, + ) } return "" } @@ -661,3 +669,81 @@ func cyberghostServers() []server { {subdomain: "96-1-vn", region: "Vietnam", group: "Premium TCP Asia"}, } } + +func vyprvpnServers() []server { + return []server{ + {subdomain: "ae1", region: "Dubai"}, + {subdomain: "ar1", region: "Argentina"}, + {subdomain: "at1", region: "Austria"}, + {subdomain: "au1", region: "Australia Sydney"}, + {subdomain: "au2", region: "Australia Melbourne"}, + {subdomain: "au3", region: "Australia Perth"}, + {subdomain: "be1", region: "Belgium"}, + {subdomain: "bg1", region: "Bulgaria"}, + {subdomain: "bh1", region: "Bahrain"}, + {subdomain: "br1", region: "Brazil"}, + {subdomain: "ca1", region: "Canada"}, + {subdomain: "ch1", region: "Switzerland"}, + {subdomain: "co1", region: "Columbia"}, + {subdomain: "cr1", region: "Costa Rica"}, + {subdomain: "cz1", region: "Czech Republic"}, + {subdomain: "de1", region: "Germany"}, + {subdomain: "dk1", region: "Denmark"}, + {subdomain: "dz1", region: "Algeria"}, + {subdomain: "eg1", region: "Egypt"}, + {subdomain: "es1", region: "Spain"}, + {subdomain: "eu1", region: "Netherlands"}, + {subdomain: "fi1", region: "Finland"}, + {subdomain: "fr1", region: "France"}, + {subdomain: "gr1", region: "Greece"}, + {subdomain: "hk1", region: "Hong Kong"}, + {subdomain: "id1", region: "Indonesia"}, + {subdomain: "ie1", region: "Ireland"}, + {subdomain: "il1", region: "Israel"}, + {subdomain: "in1", region: "India"}, + {subdomain: "is1", region: "Iceland"}, + {subdomain: "it1", region: "Italy"}, + {subdomain: "jp1", region: "Japan"}, + {subdomain: "kr1", region: "South Korea"}, + {subdomain: "li1", region: "Liechtenstein"}, + {subdomain: "lt1", region: "Lithuania"}, + {subdomain: "lu1", region: "Luxembourg"}, + {subdomain: "lv1", region: "Latvia"}, + {subdomain: "mh1", region: "Marshall Islands"}, + {subdomain: "mo1", region: "Macao"}, + {subdomain: "mv1", region: "Maldives"}, + {subdomain: "mx1", region: "Mexico"}, + {subdomain: "my1", region: "Malaysia"}, + {subdomain: "no1", region: "Norway"}, + {subdomain: "nz1", region: "New Zealand"}, + {subdomain: "pa1", region: "Panama"}, + {subdomain: "ph1", region: "Philippines"}, + {subdomain: "pk1", region: "Pakistan"}, + {subdomain: "pl1", region: "Poland"}, + {subdomain: "pt1", region: "Portugal"}, + {subdomain: "qa1", region: "Qatar"}, + {subdomain: "ro1", region: "Romania"}, + {subdomain: "ru1", region: "Russia"}, + {subdomain: "sa1", region: "Saudi Arabia"}, + {subdomain: "se1", region: "Sweden"}, + {subdomain: "sg1", region: "Singapore"}, + {subdomain: "si1", region: "Slovenia"}, + {subdomain: "sk1", region: "Slovakia"}, + {subdomain: "sv1", region: "El Salvador"}, + {subdomain: "th1", region: "Thailand"}, + {subdomain: "tr1", region: "Turkey"}, + {subdomain: "tw1", region: "Taiwan"}, + {subdomain: "ua1", region: "Ukraine"}, + {subdomain: "uk1", region: "United Kingdom"}, + {subdomain: "us1", region: "USA Los Angeles"}, + {subdomain: "us2", region: "USA Washington DC"}, + {subdomain: "us3", region: "USA Austin"}, + {subdomain: "us4", region: "USA Miami"}, + {subdomain: "us5", region: "USA New York"}, + {subdomain: "us6", region: "USA Chicago"}, + {subdomain: "us7", region: "USA San Francisco"}, + {subdomain: "us8", region: "USA Seattle"}, + {subdomain: "uy1", region: "Uruguay"}, + {subdomain: "vn1", region: "Vietnam"}, + } +} diff --git a/internal/constants/vpn.go b/internal/constants/vpn.go index 3fe5582d..60b5be60 100644 --- a/internal/constants/vpn.go +++ b/internal/constants/vpn.go @@ -15,6 +15,8 @@ const ( Surfshark models.VPNProvider = "surfshark" // Cyberghost is a VPN provider Cyberghost models.VPNProvider = "cyberghost" + // Vyprvpn is a VPN provider + Vyprvpn models.VPNProvider = "vyprvpn" ) const ( diff --git a/internal/constants/vyprvpn.go b/internal/constants/vyprvpn.go new file mode 100644 index 00000000..5e4d3e7a --- /dev/null +++ b/internal/constants/vyprvpn.go @@ -0,0 +1,98 @@ +package constants + +import ( + "net" + + "github.com/qdm12/private-internet-access-docker/internal/models" +) + +const ( + VyprvpnCertificate = "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" +) + +func VyprvpnRegionChoices() (choices []string) { + servers := VyprvpnServers() + choices = make([]string, len(servers)) + for i := range servers { + choices[i] = servers[i].Region + } + return choices +} + +func VyprvpnServers() []models.VyprvpnServer { + return []models.VyprvpnServer{ + {Region: "Algeria", IPs: []net.IP{{209, 99, 75, 20}}}, + {Region: "Argentina", IPs: []net.IP{{209, 99, 109, 19}}}, + {Region: "Australia Melbourne", IPs: []net.IP{{209, 99, 117, 19}}}, + {Region: "Australia Perth", IPs: []net.IP{{209, 99, 1, 19}}}, + {Region: "Australia Sydney", IPs: []net.IP{{209, 99, 117, 18}}}, + {Region: "Austria", IPs: []net.IP{{128, 90, 96, 18}}}, + {Region: "Bahrain", IPs: []net.IP{{209, 99, 115, 19}}}, + {Region: "Belgium", IPs: []net.IP{{128, 90, 96, 20}}}, + {Region: "Brazil", IPs: []net.IP{{209, 99, 109, 20}}}, + {Region: "Bulgaria", IPs: []net.IP{{128, 90, 96, 22}}}, + {Region: "Canada", IPs: []net.IP{{209, 99, 21, 18}}}, + {Region: "Columbia", IPs: []net.IP{{209, 99, 109, 21}}}, + {Region: "Costa Rica", IPs: []net.IP{{209, 99, 109, 22}}}, + {Region: "Czech Republic", IPs: []net.IP{{128, 90, 96, 24}}}, + {Region: "Denmark", IPs: []net.IP{{128, 90, 96, 28}}}, + {Region: "Dubai", IPs: []net.IP{{128, 90, 45, 104}}}, + {Region: "Egypt", IPs: []net.IP{{128, 90, 228, 43}}}, + {Region: "El Salvador", IPs: []net.IP{{209, 99, 61, 20}}}, + {Region: "Finland", IPs: []net.IP{{128, 90, 96, 32}}}, + {Region: "France", IPs: []net.IP{{128, 90, 96, 34}}}, + {Region: "Germany", IPs: []net.IP{{128, 90, 96, 26}}}, + {Region: "Greece", IPs: []net.IP{{128, 90, 228, 59}}}, + {Region: "Hong Kong", IPs: []net.IP{{128, 90, 227, 18}}}, + {Region: "Iceland", IPs: []net.IP{{209, 99, 22, 20}}}, + {Region: "India", IPs: []net.IP{{209, 99, 115, 20}}}, + {Region: "Indonesia", IPs: []net.IP{{209, 99, 1, 20}}}, + {Region: "Ireland", IPs: []net.IP{{209, 99, 22, 19}}}, + {Region: "Israel", IPs: []net.IP{{128, 90, 228, 20}}}, + {Region: "Italy", IPs: []net.IP{{128, 90, 96, 36}}}, + {Region: "Japan", IPs: []net.IP{{209, 99, 113, 18}}}, + {Region: "Latvia", IPs: []net.IP{{128, 90, 96, 44}}}, + {Region: "Liechtenstein", IPs: []net.IP{{128, 90, 96, 38}}}, + {Region: "Lithuania", IPs: []net.IP{{128, 90, 96, 40}}}, + {Region: "Luxembourg", IPs: []net.IP{{128, 90, 96, 42}}}, + {Region: "Macao", IPs: []net.IP{{128, 90, 227, 36}}}, + {Region: "Malaysia", IPs: []net.IP{{209, 99, 1, 21}}}, + {Region: "Maldives", IPs: []net.IP{{209, 99, 1, 26}}}, + {Region: "Marshall Islands", IPs: []net.IP{{209, 99, 1, 25}}}, + {Region: "Mexico", IPs: []net.IP{{209, 99, 61, 19}}}, + {Region: "Netherlands", IPs: []net.IP{{128, 90, 96, 16}}}, + {Region: "New Zealand", IPs: []net.IP{{209, 99, 117, 20}}}, + {Region: "Norway", IPs: []net.IP{{128, 90, 96, 46}}}, + {Region: "Pakistan", IPs: []net.IP{{128, 90, 228, 67}}}, + {Region: "Panama", IPs: []net.IP{{209, 99, 109, 23}}}, + {Region: "Philippines", IPs: []net.IP{{209, 99, 1, 22}}}, + {Region: "Poland", IPs: []net.IP{{128, 90, 96, 48}}}, + {Region: "Portugal", IPs: []net.IP{{128, 90, 96, 50}}}, + {Region: "Qatar", IPs: []net.IP{{209, 99, 115, 21}}}, + {Region: "Romania", IPs: []net.IP{{128, 90, 96, 52}}}, + {Region: "Russia", IPs: []net.IP{{128, 90, 96, 54}}}, + {Region: "Saudi Arabia", IPs: []net.IP{{209, 99, 115, 22}}}, + {Region: "Singapore", IPs: []net.IP{{209, 99, 1, 18}}}, + {Region: "Slovakia", IPs: []net.IP{{128, 90, 96, 60}}}, + {Region: "Slovenia", IPs: []net.IP{{128, 90, 96, 58}}}, + {Region: "South Korea", IPs: []net.IP{{209, 99, 113, 19}}}, + {Region: "Spain", IPs: []net.IP{{128, 90, 96, 30}}}, + {Region: "Sweden", IPs: []net.IP{{128, 90, 96, 56}}}, + {Region: "Switzerland", IPs: []net.IP{{209, 99, 60, 18}}}, + {Region: "Taiwan", IPs: []net.IP{{128, 90, 227, 27}}}, + {Region: "Thailand", IPs: []net.IP{{209, 99, 1, 23}}}, + {Region: "Turkey", IPs: []net.IP{{128, 90, 96, 62}}}, + {Region: "USA Austin", IPs: []net.IP{{209, 99, 61, 18}}}, + {Region: "USA Chicago", IPs: []net.IP{{209, 99, 93, 18}}}, + {Region: "USA Los Angeles", IPs: []net.IP{{209, 99, 67, 18}}}, + {Region: "USA Miami", IPs: []net.IP{{209, 99, 109, 18}}}, + {Region: "USA New York", IPs: []net.IP{{209, 99, 63, 18}}}, + {Region: "USA San Francisco", IPs: []net.IP{{209, 99, 95, 18}}}, + {Region: "USA Seattle", IPs: []net.IP{{209, 99, 94, 18}}}, + {Region: "USA Washington DC", IPs: []net.IP{{209, 99, 62, 18}}}, + {Region: "Ukraine", IPs: []net.IP{{128, 90, 96, 64}}}, + {Region: "United Kingdom", IPs: []net.IP{{209, 99, 22, 18}}}, + {Region: "Uruguay", IPs: []net.IP{{209, 99, 61, 21}}}, + {Region: "Vietnam", IPs: []net.IP{{209, 99, 1, 24}}}, + } +} diff --git a/internal/models/selection.go b/internal/models/selection.go index 2a0cead0..89dc82ac 100644 --- a/internal/models/selection.go +++ b/internal/models/selection.go @@ -90,6 +90,10 @@ func (p *ProviderSettings) String() string { "Group: "+p.ServerSelection.Group, "Region: "+p.ServerSelection.Region, ) + case "vyprvpn": + settingsList = append(settingsList, + "Region: "+p.ServerSelection.Region, + ) } if p.ServerSelection.TargetIP != nil { settingsList = append(settingsList, diff --git a/internal/models/servers.go b/internal/models/servers.go index 6d11676a..0ec11dd9 100644 --- a/internal/models/servers.go +++ b/internal/models/servers.go @@ -31,3 +31,8 @@ type CyberghostServer struct { Group string IPs []net.IP } + +type VyprvpnServer struct { + Region string + IPs []net.IP +} diff --git a/internal/params/params.go b/internal/params/params.go index 7e6abaa3..267d58e6 100644 --- a/internal/params/params.go +++ b/internal/params/params.go @@ -77,6 +77,9 @@ type Reader interface { GetCyberghostRegion() (region string, err error) GetCyberghostClientKey() (clientKey string, err error) + // Vyprvpn getters + GetVyprvpnRegion() (region string, err error) + // Shadowsocks getters GetShadowSocks() (activated bool, err error) GetShadowSocksLog() (activated bool, err error) @@ -117,7 +120,7 @@ func NewReader(logger logging.Logger) Reader { // GetVPNSP obtains the VPN service provider to use from the environment variable VPNSP func (r *reader) GetVPNSP() (vpnServiceProvider models.VPNProvider, err error) { - s, err := r.envParams.GetValueIfInside("VPNSP", []string{"pia", "private internet access", "mullvad", "windscribe", "surfshark", "cyberghost"}) + s, err := r.envParams.GetValueIfInside("VPNSP", []string{"pia", "private internet access", "mullvad", "windscribe", "surfshark", "cyberghost", "vyprvpn"}) if s == "pia" { s = "private internet access" } diff --git a/internal/params/vypervpn.go b/internal/params/vypervpn.go new file mode 100644 index 00000000..428cbc01 --- /dev/null +++ b/internal/params/vypervpn.go @@ -0,0 +1,11 @@ +package params + +import ( + "github.com/qdm12/private-internet-access-docker/internal/constants" +) + +// GetVyprvpnRegion obtains the region for the Vyprvpn server from the +// environment variable REGION +func (r *reader) GetVyprvpnRegion() (region string, err error) { + return r.envParams.GetValueIfInside("REGION", constants.VyprvpnRegionChoices()) +} diff --git a/internal/provider/provider.go b/internal/provider/provider.go index 05783a87..5ea34cbc 100644 --- a/internal/provider/provider.go +++ b/internal/provider/provider.go @@ -26,6 +26,8 @@ func New(provider models.VPNProvider, client network.Client, fileManager files.F return newSurfshark(fileManager) case constants.Cyberghost: return newCyberghost(fileManager) + case constants.Vyprvpn: + return newVyprvpn(fileManager) default: return nil // should never occur } diff --git a/internal/provider/vyprvpn.go b/internal/provider/vyprvpn.go new file mode 100644 index 00000000..3c3132ba --- /dev/null +++ b/internal/provider/vyprvpn.go @@ -0,0 +1,113 @@ +package provider + +import ( + "fmt" + "net" + "strings" + + "github.com/qdm12/golibs/files" + "github.com/qdm12/private-internet-access-docker/internal/constants" + "github.com/qdm12/private-internet-access-docker/internal/models" +) + +type vyprvpn struct { + fileManager files.FileManager + lookupIP func(host string) ([]net.IP, error) +} + +func newVyprvpn(fileManager files.FileManager) *vyprvpn { + return &vyprvpn{fileManager, net.LookupIP} +} + +func (s *vyprvpn) GetOpenVPNConnections(selection models.ServerSelection) (connections []models.OpenVPNConnection, err error) { + var IPs []net.IP + for _, server := range constants.VyprvpnServers() { + if strings.EqualFold(server.Region, selection.Region) { + IPs = server.IPs + } + } + if len(IPs) == 0 { + return nil, fmt.Errorf("no IP found for region %q", selection.Region) + } + if selection.TargetIP != nil { + found := false + for i := range IPs { + if IPs[i].Equal(selection.TargetIP) { + found = true + break + } + } + if !found { + return nil, fmt.Errorf("target IP address %q not found in IP addresses", selection.TargetIP) + } + IPs = []net.IP{selection.TargetIP} + } + var port uint16 + switch { + case selection.Protocol == constants.TCP: + return nil, fmt.Errorf("TCP protocol not supported by this VPN provider") + case selection.Protocol == constants.UDP: + port = 443 + default: + return nil, fmt.Errorf("protocol %q is unknown", selection.Protocol) + } + for _, IP := range IPs { + connections = append(connections, models.OpenVPNConnection{IP: IP, Port: port, Protocol: selection.Protocol}) + } + return connections, nil +} + +func (s *vyprvpn) BuildConf(connections []models.OpenVPNConnection, verbosity, uid, gid int, root bool, cipher, auth string, extras models.ExtraConfigOptions) (err error) { + if len(cipher) == 0 { + cipher = aes256cbc + } + if len(auth) == 0 { + auth = "SHA256" + } + lines := []string{ + "client", + "dev tun", + "nobind", + "persist-key", + "remote-cert-tls server", + + // Vyprvpn specific + "comp-lzo", + "keepalive 10 60", + // "verify-x509-name lu1.vyprvpn.com name", + "tls-cipher TLS-ECDHE-RSA-WITH-AES-256-GCM-SHA384:TLS-DHE-RSA-WITH-AES-256-CBC-SHA256:TLS-DHE-RSA-WITH-AES-256-CBC-SHA", + + // Added constant values + "auth-nocache", + "mute-replay-warnings", + "pull-filter ignore \"auth-token\"", // prevent auth failed loops + "auth-retry nointeract", + "remote-random", + "suppress-timestamps", + + // Modified variables + fmt.Sprintf("verb %d", verbosity), + fmt.Sprintf("auth-user-pass %s", constants.OpenVPNAuthConf), + fmt.Sprintf("proto %s", connections[0].Protocol), + fmt.Sprintf("cipher %s", cipher), + fmt.Sprintf("auth %s", auth), + } + if !root { + lines = append(lines, "user nonrootuser") + } + for _, connection := range connections { + lines = append(lines, fmt.Sprintf("remote %s %d", connection.IP, connection.Port)) + } + lines = append(lines, []string{ + "", + "-----BEGIN CERTIFICATE-----", + constants.VyprvpnCertificate, + "-----END CERTIFICATE-----", + "", + }...) + return s.fileManager.WriteLinesToFile(string(constants.OpenVPNConf), lines, files.Ownership(uid, gid), files.Permissions(0400)) +} + +func (s *vyprvpn) GetPortForward() (port uint16, err error) { + panic("port forwarding is not supported for vyprvpn") +} diff --git a/internal/settings/openvpn.go b/internal/settings/openvpn.go index ec6b0178..1affcc49 100644 --- a/internal/settings/openvpn.go +++ b/internal/settings/openvpn.go @@ -62,6 +62,8 @@ func GetOpenVPNSettings(paramsReader params.Reader, vpnProvider models.VPNProvid settings.Provider, err = GetSurfsharkSettings(paramsReader) case constants.Cyberghost: settings.Provider, err = GetCyberghostSettings(paramsReader) + case constants.Vyprvpn: + settings.Provider, err = GetVyprvpnSettings(paramsReader) default: err = fmt.Errorf("VPN service provider %q is not valid", vpnProvider) } diff --git a/internal/settings/providers.go b/internal/settings/providers.go index 52b249a9..d725cffb 100644 --- a/internal/settings/providers.go +++ b/internal/settings/providers.go @@ -135,3 +135,21 @@ func GetCyberghostSettings(paramsReader params.Reader) (settings models.Provider } return settings, nil } + +// GetVyprvpnSettings obtains Vyprvpn settings from environment variables using the params package. +func GetVyprvpnSettings(paramsReader params.Reader) (settings models.ProviderSettings, err error) { + settings.Name = constants.Vyprvpn + settings.ServerSelection.Protocol, err = paramsReader.GetNetworkProtocol() + if err != nil { + return settings, err + } + settings.ServerSelection.TargetIP, err = paramsReader.GetTargetIP() + if err != nil { + return settings, err + } + settings.ServerSelection.Region, err = paramsReader.GetVyprvpnRegion() + if err != nil { + return settings, err + } + return settings, nil +}