diff --git a/README.md b/README.md index e9319ef6..3a68e8cc 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,7 @@ # Gluetun VPN client *Lightweight swiss-knife-like VPN client to tunnel to Private Internet Access, -Mullvad, Windscribe, Surfshark Cyberghost, VyprVPN, NordVPN and PureVPN VPN servers, using Go, OpenVPN, iptables, DNS over TLS, ShadowSocks and an HTTP proxy* +Mullvad, Windscribe, Surfshark Cyberghost, VyprVPN, NordVPN, PureVPN and Privado VPN servers, using Go, OpenVPN, iptables, DNS over TLS, ShadowSocks and an HTTP proxy* **ANNOUNCEMENT**: *Github Wiki reworked* @@ -28,7 +28,7 @@ Mullvad, Windscribe, Surfshark Cyberghost, VyprVPN, NordVPN and PureVPN VPN serv ## Features - Based on Alpine 3.12 for a small Docker image of 52MB -- Supports **Private Internet Access**, **Mullvad**, **Windscribe**, **Surfshark**, **Cyberghost**, **Vyprvpn**, **NordVPN** and **PureVPN** servers +- Supports **Private Internet Access**, **Mullvad**, **Windscribe**, **Surfshark**, **Cyberghost**, **Vyprvpn**, **NordVPN**, **PureVPN** and **Privado** servers - Supports Openvpn only for now - DNS over TLS baked in with service provider(s) of your choice - DNS fine blocking of malicious/ads/surveillance hostnames and IP addresses, with live update every 24 hours @@ -96,7 +96,7 @@ docker run --rm --network=container:gluetun alpine:3.12 wget -qO- https://ipinfo | Variable | Default | Choices | Description | | --- | --- | --- | --- | -| 🏁 `VPNSP` | `private internet access` | `private internet access`, `mullvad`, `windscribe`, `surfshark`, `vyprvpn`, `nordvpn`, `purevpn` | VPN Service Provider | +| 🏁 `VPNSP` | `private internet access` | `private internet access`, `mullvad`, `windscribe`, `surfshark`, `vyprvpn`, `nordvpn`, `purevpn`, `privado` | VPN Service Provider | | `IP_STATUS_FILE` | `/tmp/gluetun/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 | @@ -202,6 +202,15 @@ docker run --rm --network=container:gluetun alpine:3.12 wget -qO- https://ipinfo | `COUNTRY` | | One of the [PureVPN countries](https://support.purevpn.com/vpn-servers) | VPN server country | | `CITY` | | One of the [PureVPN cities](https://support.purevpn.com/vpn-servers) | VPN server city | +- Privado + + | Variable | Default | Choices | Description | + | --- | --- | --- | --- | + | 🏁 `USER` | | | Your username | + | 🏁 `PASSWORD` | | | Your password | + | `CITY` | | One of the Privado city codes, i.e. `ams` | VPN server city | + | `SERVER_NUMBER` | | Server integer number | Optional server number. For example `2` for `sof-002` | + ### DNS over TLS None of the following values are required. diff --git a/internal/cli/cli.go b/internal/cli/cli.go index 70aa2246..371fff5c 100644 --- a/internal/cli/cli.go +++ b/internal/cli/cli.go @@ -93,6 +93,7 @@ func Update(args []string) error { flagSet.BoolVar(&options.Mullvad, "mullvad", false, "Update Mullvad servers") flagSet.BoolVar(&options.Nordvpn, "nordvpn", false, "Update Nordvpn servers") flagSet.BoolVar(&options.PIA, "pia", false, "Update Private Internet Access post-summer 2020 servers") + flagSet.BoolVar(&options.Privado, "privado", false, "Update Privado servers") flagSet.BoolVar(&options.Purevpn, "purevpn", false, "Update Purevpn servers") flagSet.BoolVar(&options.Surfshark, "surfshark", false, "Update Surfshark servers") flagSet.BoolVar(&options.Vyprvpn, "vyprvpn", false, "Update Vyprvpn servers") diff --git a/internal/constants/privado.go b/internal/constants/privado.go new file mode 100644 index 00000000..fee1bb39 --- /dev/null +++ b/internal/constants/privado.go @@ -0,0 +1,206 @@ +package constants + +import ( + "net" + + "github.com/qdm12/gluetun/internal/models" +) + +//nolint:lll +const ( + PrivadoCertificate = "MIIFKDCCAxCgAwIBAgIJAMtrmqZxIV/OMA0GCSqGSIb3DQEBDQUAMBIxEDAOBgNVBAMMB1ByaXZhZG8wHhcNMjAwMTA4MjEyODQ1WhcNMzUwMTA5MjEyODQ1WjASMRAwDgYDVQQDDAdQcml2YWRvMIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAxPwOgiwNJzZTnKIXwAB0TSu/Lu2qt2U2I8obtQjwhi/7OrfmbmYykSdro70al2XPhnwAGGdCxW6LDnp0UN/IOhD11mgBPo14f5CLkBQjSJ6VN5miPbvK746LsNZl9H8rQGvDuPo4CG9BfPZMiDRGlsMxij/jztzgT1gmuxQ7WHfFRcNzBas1dHa9hV/d3TU6/t47x4SE/ljdcCtJiu7Zn6ODKQoys3mB7Luz2ngqUJWvkqsg+E4+3eJ0M8Hlbn5TPaRJBID7DAdYo6Vs6xGCYr981ThFcmoIQ10js10yANrrfGAzd03b3TnLAgko0uQMHjliMZL6L8sWOPHxyxJI0us88SFh4UgcFyRHKHPKux7w24SxAlZUYoUcTHp9VjG5XvDKYxzgV2RdM4ulBGbQRQ3y3/CyddsyQYMvA55Ets0LfPaBvDIcct70iXijGsdvlX1du3ArGpG7Vaje/RU4nbbGT6HYRdt5YyZfof288ukMOSj20nVcmS+c/4tqsxSerRb1aq5LOi1IemSkTMeC5gCbexk+L1vl7NT/58sxjGmu5bXwnvev/lIItfi2AlITrfUSEv19iDMKkeshwn/+sFJBMWYyluP+yJ56yR+MWoXvLlSWphLDTqq19yx3BZn0P1tgbXoR0g8PTdJFcz8z3RIb7myVLYulV1oGG/3rka0CAwEAAaOBgDB+MB0GA1UdDgQWBBTFtJkZCVDuDAD6k5bJzefjJdO3DTBCBgNVHSMEOzA5gBTFtJkZCVDuDAD6k5bJzefjJdO3DaEWpBQwEjEQMA4GA1UEAwwHUHJpdmFkb4IJAMtrmqZxIV/OMAwGA1UdEwQFMAMBAf8wCwYDVR0PBAQDAgEGMA0GCSqGSIb3DQEBDQUAA4ICAQB7MUSXMeBb9wlSv4sUaT1JHEwE26nlBw+TKmezfuPU5pBlY0LYr6qQZY95DHqsRJ7ByUzGUrGo17dNGXlcuNc6TAaQQEDRPo6y+LVh2TWMk15TUMI+MkqryJtCret7xGvDigKYMJgBy58HN3RAVr1B7cL9youwzLgc2Y/NcFKvnQJKeiIYAJ7g0CcnJiQvgZTS7xdwkEBXfsngmUCIG320DLPEL+Ze0HiUrxwWljMRya6i40AeH3Zu2i532xX1wV5+cjA4RJWIKg6ri/Q54iFGtZrA9/nc6y9uoQHkmz8cGyVUmJxFzMrrIICVqUtVRxLhkTMe4UzwRWTBeGgtW4tS0yq1QonAKfOyjgRw/CeY55D2UGvnAFZdTadtYXS4Alu2P9zdwoEk3fzHiVmDjqfJVr5wz9383aABUFrPI3nz6ed/Z6LZflKh1k+DUDEp8NxU4klUULWsSOKoa5zGX51G8cdHxwQLImXvtGuN5eSR8jCTgxFZhdps/xes4KkyfIz9FMYG748M+uOTgKITf4zdJ9BAyiQaOufVQZ8WjhWzWk9YHec9VqPkzpWNGkVjiRI5ewuXwZzZ164tMv2hikBXSuUCnFz37/ZNwGlDi0oBdDszCk2GxccdFHHaCSmpjU5MrdJ+5IhtTKGeTx+US2hTIVHQFIO99DmacxSYvLNcSQ==" +) + +func PrivadoCityChoices() (choices []string) { + servers := PrivadoServers() + choices = make([]string, len(servers)) + for i := range servers { + choices[i] = servers[i].City + } + return choices +} + +//nolint:gomnd +func PrivadoServers() []models.PrivadoServer { + return []models.PrivadoServer{ + {City: "akl", Number: 1, IP: net.IP{23, 254, 104, 114}}, + {City: "akl", Number: 2, IP: net.IP{23, 254, 104, 120}}, + {City: "akl", Number: 3, IP: net.IP{23, 254, 104, 51}}, + {City: "ams", Number: 1, IP: net.IP{91, 148, 224, 10}}, + {City: "ams", Number: 10, IP: net.IP{91, 148, 228, 20}}, + {City: "ams", Number: 11, IP: net.IP{91, 148, 228, 30}}, + {City: "ams", Number: 12, IP: net.IP{91, 148, 228, 40}}, + {City: "ams", Number: 13, IP: net.IP{91, 148, 228, 50}}, + {City: "ams", Number: 14, IP: net.IP{91, 148, 228, 60}}, + {City: "ams", Number: 15, IP: net.IP{91, 148, 228, 70}}, + {City: "ams", Number: 16, IP: net.IP{91, 148, 228, 80}}, + {City: "ams", Number: 2, IP: net.IP{91, 148, 224, 20}}, + {City: "ams", Number: 3, IP: net.IP{91, 148, 224, 30}}, + {City: "ams", Number: 4, IP: net.IP{91, 148, 224, 40}}, + {City: "ams", Number: 5, IP: net.IP{91, 148, 224, 50}}, + {City: "ams", Number: 6, IP: net.IP{91, 148, 224, 60}}, + {City: "ams", Number: 7, IP: net.IP{91, 148, 224, 70}}, + {City: "ams", Number: 8, IP: net.IP{91, 148, 224, 80}}, + {City: "ams", Number: 9, IP: net.IP{91, 148, 228, 10}}, + {City: "arn", Number: 1, IP: net.IP{86, 106, 103, 67}}, + {City: "arn", Number: 2, IP: net.IP{86, 106, 103, 74}}, + {City: "arn", Number: 3, IP: net.IP{86, 106, 103, 81}}, + {City: "ath", Number: 1, IP: net.IP{188, 123, 126, 61}}, + {City: "ath", Number: 2, IP: net.IP{188, 123, 126, 64}}, + {City: "ath", Number: 3, IP: net.IP{188, 123, 126, 68}}, + {City: "ath", Number: 4, IP: net.IP{188, 123, 126, 72}}, + {City: "beg", Number: 1, IP: net.IP{89, 38, 224, 19}}, + {City: "beg", Number: 2, IP: net.IP{89, 38, 224, 25}}, + {City: "bkk", Number: 1, IP: net.IP{119, 59, 111, 3}}, + {City: "bkk", Number: 2, IP: net.IP{119, 59, 111, 11}}, + {City: "bom", Number: 1, IP: net.IP{103, 26, 204, 61}}, + {City: "bom", Number: 2, IP: net.IP{103, 26, 204, 70}}, + {City: "bru", Number: 1, IP: net.IP{217, 138, 211, 163}}, + {City: "bru", Number: 2, IP: net.IP{217, 138, 211, 170}}, + {City: "bru", Number: 3, IP: net.IP{217, 138, 211, 177}}, + {City: "bru", Number: 4, IP: net.IP{217, 138, 211, 184}}, + {City: "bts", Number: 1, IP: net.IP{37, 120, 221, 227}}, + {City: "bts", Number: 2, IP: net.IP{37, 120, 221, 233}}, + {City: "bud", Number: 1, IP: net.IP{185, 128, 26, 194}}, + {City: "bud", Number: 2, IP: net.IP{185, 128, 26, 200}}, + {City: "cdg", Number: 1, IP: net.IP{89, 40, 183, 99}}, + {City: "cdg", Number: 2, IP: net.IP{89, 40, 183, 106}}, + {City: "cdg", Number: 3, IP: net.IP{89, 40, 183, 113}}, + {City: "cdg", Number: 4, IP: net.IP{89, 40, 183, 120}}, + {City: "cph", Number: 1, IP: net.IP{2, 58, 46, 35}}, + {City: "cph", Number: 2, IP: net.IP{2, 58, 46, 42}}, + {City: "cph", Number: 3, IP: net.IP{2, 58, 46, 49}}, + {City: "cph", Number: 4, IP: net.IP{2, 58, 46, 56}}, + {City: "dca", Number: 1, IP: net.IP{85, 12, 61, 10}}, + {City: "dca", Number: 13, IP: net.IP{185, 247, 68, 3}}, + {City: "dca", Number: 14, IP: net.IP{185, 247, 68, 10}}, + {City: "dca", Number: 15, IP: net.IP{185, 247, 68, 17}}, + {City: "dca", Number: 16, IP: net.IP{185, 247, 68, 24}}, + {City: "dca", Number: 2, IP: net.IP{85, 12, 61, 20}}, + {City: "dca", Number: 3, IP: net.IP{85, 12, 61, 30}}, + {City: "dca", Number: 4, IP: net.IP{85, 12, 61, 40}}, + {City: "dca", Number: 5, IP: net.IP{85, 12, 61, 50}}, + {City: "dca", Number: 6, IP: net.IP{85, 12, 61, 60}}, + {City: "dca", Number: 7, IP: net.IP{85, 12, 61, 70}}, + {City: "dca", Number: 8, IP: net.IP{85, 12, 61, 80}}, + {City: "dfw", Number: 1, IP: net.IP{23, 105, 32, 243}}, + {City: "dfw", Number: 2, IP: net.IP{23, 105, 32, 244}}, + {City: "dub", Number: 1, IP: net.IP{84, 247, 48, 227}}, + {City: "dub", Number: 2, IP: net.IP{84, 247, 48, 234}}, + {City: "dub", Number: 3, IP: net.IP{84, 247, 48, 241}}, + {City: "dub", Number: 4, IP: net.IP{84, 247, 48, 248}}, + {City: "eze", Number: 1, IP: net.IP{168, 205, 93, 211}}, + {City: "eze", Number: 2, IP: net.IP{168, 205, 93, 217}}, + {City: "fra", Number: 1, IP: net.IP{91, 148, 232, 10}}, + {City: "fra", Number: 2, IP: net.IP{91, 148, 232, 20}}, + {City: "fra", Number: 3, IP: net.IP{91, 148, 232, 30}}, + {City: "fra", Number: 4, IP: net.IP{91, 148, 232, 40}}, + {City: "fra", Number: 5, IP: net.IP{91, 148, 233, 7}}, + {City: "fra", Number: 6, IP: net.IP{91, 148, 233, 8}}, + {City: "fra", Number: 7, IP: net.IP{91, 148, 233, 9}}, + {City: "fra", Number: 8, IP: net.IP{91, 148, 233, 10}}, + {City: "gru", Number: 1, IP: net.IP{177, 54, 145, 193}}, + {City: "gru", Number: 2, IP: net.IP{177, 54, 145, 197}}, + {City: "hel", Number: 1, IP: net.IP{194, 34, 134, 219}}, + {City: "hel", Number: 2, IP: net.IP{194, 34, 134, 227}}, + {City: "hkg", Number: 1, IP: net.IP{209, 58, 185, 88}}, + {City: "hkg", Number: 2, IP: net.IP{209, 58, 185, 97}}, + {City: "hkg", Number: 3, IP: net.IP{209, 58, 185, 108}}, + {City: "hkg", Number: 4, IP: net.IP{209, 58, 185, 120}}, + {City: "icn", Number: 1, IP: net.IP{169, 56, 73, 146}}, + {City: "icn", Number: 2, IP: net.IP{169, 56, 73, 153}}, + {City: "iev", Number: 1, IP: net.IP{176, 103, 52, 40}}, + {City: "iev", Number: 2, IP: net.IP{176, 103, 53, 40}}, + {City: "ist", Number: 1, IP: net.IP{185, 84, 183, 3}}, + {City: "ist", Number: 2, IP: net.IP{185, 84, 183, 4}}, + {City: "jfk", Number: 1, IP: net.IP{217, 138, 208, 99}}, + {City: "jfk", Number: 2, IP: net.IP{217, 138, 208, 106}}, + {City: "jfk", Number: 3, IP: net.IP{217, 138, 208, 113}}, + {City: "jfk", Number: 4, IP: net.IP{217, 138, 208, 120}}, + {City: "jnb", Number: 1, IP: net.IP{172, 107, 93, 131}}, + {City: "jnb", Number: 2, IP: net.IP{172, 107, 93, 137}}, + {City: "lax", Number: 10, IP: net.IP{45, 152, 182, 234}}, + {City: "lax", Number: 11, IP: net.IP{45, 152, 182, 241}}, + {City: "lax", Number: 12, IP: net.IP{45, 152, 182, 248}}, + {City: "lax", Number: 9, IP: net.IP{45, 152, 182, 227}}, + {City: "lis", Number: 1, IP: net.IP{89, 26, 243, 153}}, + {City: "lis", Number: 2, IP: net.IP{89, 26, 243, 154}}, + {City: "lon", Number: 1, IP: net.IP{217, 138, 195, 163}}, + {City: "lon", Number: 2, IP: net.IP{217, 138, 195, 170}}, + {City: "lon", Number: 3, IP: net.IP{217, 138, 195, 177}}, + {City: "lon", Number: 4, IP: net.IP{217, 138, 195, 184}}, + {City: "mad", Number: 1, IP: net.IP{217, 138, 218, 131}}, + {City: "man", Number: 1, IP: net.IP{217, 138, 196, 131}}, + {City: "man", Number: 2, IP: net.IP{217, 138, 196, 138}}, + {City: "man", Number: 3, IP: net.IP{217, 138, 196, 145}}, + {City: "man", Number: 4, IP: net.IP{217, 138, 196, 152}}, + {City: "mex", Number: 1, IP: net.IP{169, 57, 96, 52}}, + {City: "mex", Number: 2, IP: net.IP{169, 57, 96, 57}}, + {City: "mia", Number: 1, IP: net.IP{86, 106, 87, 131}}, + {City: "mia", Number: 2, IP: net.IP{86, 106, 87, 138}}, + {City: "mia", Number: 3, IP: net.IP{86, 106, 87, 145}}, + {City: "mia", Number: 4, IP: net.IP{86, 106, 87, 152}}, + {City: "mxp", Number: 1, IP: net.IP{89, 40, 182, 195}}, + {City: "mxp", Number: 2, IP: net.IP{89, 40, 182, 201}}, + {City: "nrt", Number: 1, IP: net.IP{217, 138, 252, 3}}, + {City: "nrt", Number: 2, IP: net.IP{217, 138, 252, 10}}, + {City: "nrt", Number: 3, IP: net.IP{217, 138, 252, 17}}, + {City: "nrt", Number: 4, IP: net.IP{217, 138, 252, 24}}, + {City: "ord", Number: 1, IP: net.IP{23, 108, 95, 129}}, + {City: "ord", Number: 2, IP: net.IP{23, 108, 95, 167}}, + {City: "osl", Number: 1, IP: net.IP{84, 247, 50, 115}}, + {City: "osl", Number: 2, IP: net.IP{84, 247, 50, 119}}, + {City: "osl", Number: 3, IP: net.IP{84, 247, 50, 123}}, + {City: "otp", Number: 1, IP: net.IP{89, 46, 102, 179}}, + {City: "otp", Number: 2, IP: net.IP{89, 46, 102, 185}}, + {City: "phx", Number: 1, IP: net.IP{91, 148, 236, 10}}, + {City: "phx", Number: 2, IP: net.IP{91, 148, 236, 20}}, + {City: "phx", Number: 3, IP: net.IP{91, 148, 236, 30}}, + {City: "phx", Number: 4, IP: net.IP{91, 148, 236, 40}}, + {City: "phx", Number: 5, IP: net.IP{91, 148, 236, 50}}, + {City: "phx", Number: 6, IP: net.IP{91, 148, 236, 60}}, + {City: "phx", Number: 7, IP: net.IP{91, 148, 236, 70}}, + {City: "phx", Number: 8, IP: net.IP{91, 148, 236, 80}}, + {City: "prg", Number: 1, IP: net.IP{185, 216, 35, 99}}, + {City: "prg", Number: 2, IP: net.IP{185, 216, 35, 105}}, + {City: "rix", Number: 1, IP: net.IP{109, 248, 149, 35}}, + {City: "rix", Number: 2, IP: net.IP{109, 248, 149, 40}}, + {City: "rkv", Number: 1, IP: net.IP{82, 221, 131, 78}}, + {City: "rkv", Number: 2, IP: net.IP{82, 221, 131, 127}}, + {City: "sea", Number: 1, IP: net.IP{23, 81, 208, 96}}, + {City: "sea", Number: 2, IP: net.IP{23, 81, 208, 104}}, + {City: "sin", Number: 1, IP: net.IP{92, 119, 178, 131}}, + {City: "sin", Number: 2, IP: net.IP{92, 119, 178, 138}}, + {City: "sin", Number: 3, IP: net.IP{92, 119, 178, 145}}, + {City: "sin", Number: 4, IP: net.IP{92, 119, 178, 152}}, + {City: "sof", Number: 1, IP: net.IP{217, 138, 221, 163}}, + {City: "sof", Number: 2, IP: net.IP{217, 138, 221, 169}}, + {City: "stl", Number: 1, IP: net.IP{148, 72, 170, 145}}, + {City: "stl", Number: 2, IP: net.IP{148, 72, 172, 82}}, + {City: "syd", Number: 1, IP: net.IP{93, 115, 35, 35}}, + {City: "syd", Number: 2, IP: net.IP{93, 115, 35, 42}}, + {City: "syd", Number: 3, IP: net.IP{93, 115, 35, 49}}, + {City: "syd", Number: 4, IP: net.IP{93, 115, 35, 56}}, + {City: "vie", Number: 1, IP: net.IP{5, 253, 207, 227}}, + {City: "vie", Number: 2, IP: net.IP{5, 253, 207, 234}}, + {City: "vie", Number: 3, IP: net.IP{5, 253, 207, 241}}, + {City: "vie", Number: 4, IP: net.IP{5, 253, 207, 248}}, + {City: "vno", Number: 1, IP: net.IP{185, 64, 104, 176}}, + {City: "vno", Number: 2, IP: net.IP{185, 64, 104, 180}}, + {City: "waw", Number: 1, IP: net.IP{217, 138, 209, 163}}, + {City: "waw", Number: 2, IP: net.IP{217, 138, 209, 164}}, + {City: "waw", Number: 3, IP: net.IP{217, 138, 209, 165}}, + {City: "waw", Number: 4, IP: net.IP{217, 138, 209, 166}}, + {City: "yul", Number: 1, IP: net.IP{217, 138, 213, 67}}, + {City: "yul", Number: 2, IP: net.IP{217, 138, 213, 74}}, + {City: "yul", Number: 3, IP: net.IP{217, 138, 213, 81}}, + {City: "yul", Number: 4, IP: net.IP{217, 138, 213, 88}}, + {City: "yvr", Number: 1, IP: net.IP{71, 19, 248, 57}}, + {City: "yvr", Number: 2, IP: net.IP{71, 19, 248, 113}}, + {City: "yyz", Number: 3, IP: net.IP{199, 189, 27, 19}}, + {City: "zrh", Number: 1, IP: net.IP{185, 156, 175, 195}}, + {City: "zrh", Number: 2, IP: net.IP{185, 156, 175, 202}}, + {City: "zrh", Number: 3, IP: net.IP{185, 156, 175, 209}}, + {City: "zrh", Number: 4, IP: net.IP{185, 156, 175, 216}}, + } +} diff --git a/internal/constants/servers.go b/internal/constants/servers.go index fa94623e..325eab04 100644 --- a/internal/constants/servers.go +++ b/internal/constants/servers.go @@ -31,6 +31,11 @@ func GetAllServers() (allServers models.AllServers) { Timestamp: 1599323261, Servers: PurevpnServers(), }, + Privado: models.PrivadoServers{ + Version: 1, + Timestamp: 1604546335, + Servers: PrivadoServers(), + }, Surfshark: models.SurfsharkServers{ Version: 1, Timestamp: 1599957644, diff --git a/internal/constants/servers_test.go b/internal/constants/servers_test.go index d5a543c4..69c8a23f 100644 --- a/internal/constants/servers_test.go +++ b/internal/constants/servers_test.go @@ -54,6 +54,11 @@ func Test_versions(t *testing.T) { version: allServers.Pia.Version, digest: "f1e01afe", }, + "Privado": { + model: models.PrivadoServer{}, + version: allServers.Privado.Version, + digest: "d7f96824", + }, "Purevpn": { model: models.PurevpnServer{}, version: allServers.Purevpn.Version, @@ -135,6 +140,11 @@ func Test_timestamps(t *testing.T) { timestamp: allServers.Purevpn.Timestamp, digest: "cdf9b708", }, + "Privado": { + servers: allServers.Privado.Servers, + timestamp: allServers.Privado.Timestamp, + digest: "3ccd3e0f", + }, "Surfshark": { servers: allServers.Surfshark.Servers, timestamp: allServers.Surfshark.Timestamp, diff --git a/internal/constants/splash.go b/internal/constants/splash.go index 9276d2e8..8538743d 100644 --- a/internal/constants/splash.go +++ b/internal/constants/splash.go @@ -2,9 +2,9 @@ package constants const ( // Announcement is a message announcement. - Announcement = "Port forwarding is working for PIA v4 servers" + Announcement = "Support for Privado" // AnnouncementExpiration is the expiration date of the announcement in format yyyy-mm-dd. - AnnouncementExpiration = "2020-11-15" + AnnouncementExpiration = "2020-11-25" ) const ( diff --git a/internal/constants/vpn.go b/internal/constants/vpn.go index 2d0c2d0c..70341d29 100644 --- a/internal/constants/vpn.go +++ b/internal/constants/vpn.go @@ -21,6 +21,8 @@ const ( Nordvpn models.VPNProvider = "nordvpn" // PureVPN is a VPN provider. Purevpn models.VPNProvider = "purevpn" + // Privado is a VPN provider. + Privado models.VPNProvider = "privado" ) const ( diff --git a/internal/models/openvpn.go b/internal/models/openvpn.go index 6f20cd91..b09077ba 100644 --- a/internal/models/openvpn.go +++ b/internal/models/openvpn.go @@ -6,8 +6,10 @@ type OpenVPNConnection struct { IP net.IP Port uint16 Protocol NetworkProtocol + Hostname string // Privado for tls verification } func (o *OpenVPNConnection) Equal(other OpenVPNConnection) bool { - return o.IP.Equal(other.IP) && o.Port == other.Port && o.Protocol == other.Protocol + return o.IP.Equal(other.IP) && o.Port == other.Port && o.Protocol == other.Protocol && + o.Hostname == other.Hostname } diff --git a/internal/models/selection.go b/internal/models/selection.go index 1df6ca1d..d1644002 100644 --- a/internal/models/selection.go +++ b/internal/models/selection.go @@ -130,6 +130,11 @@ func (p *ProviderSettings) String() string { "Countries: "+commaJoin(p.ServerSelection.Countries), "Cities: "+commaJoin(p.ServerSelection.Cities), ) + case "privado": + settingsList = append(settingsList, + "Cities: "+commaJoin(p.ServerSelection.Cities), + "Server numbers: "+commaJoin(numbers), + ) default: settingsList = append(settingsList, "", diff --git a/internal/models/server.go b/internal/models/server.go index dbcb4ebb..b55009c1 100644 --- a/internal/models/server.go +++ b/internal/models/server.go @@ -107,6 +107,17 @@ func (s *PurevpnServer) String() string { s.Region, s.Country, s.City, goStringifyIPs(s.IPs)) } +type PrivadoServer struct { + IP net.IP `json:"ip"` + City string `json:"city"` + Number uint16 `json:"number"` +} + +func (s *PrivadoServer) String() string { + return fmt.Sprintf("{City: %q, Number: %d, IP: %s}", + s.City, s.Number, goStringifyIP(s.IP)) +} + func goStringifyIP(ip net.IP) string { s := fmt.Sprintf("%#v", ip) s = strings.TrimSuffix(strings.TrimPrefix(s, "net.IP{"), "}") diff --git a/internal/models/servers.go b/internal/models/servers.go index aa234982..9b1486ae 100644 --- a/internal/models/servers.go +++ b/internal/models/servers.go @@ -6,6 +6,7 @@ type AllServers struct { Mullvad MullvadServers `json:"mullvad"` Nordvpn NordvpnServers `json:"nordvpn"` Pia PiaServers `json:"pia"` + Privado PrivadoServers `json:"privado"` Purevpn PurevpnServers `json:"purevpn"` Surfshark SurfsharkServers `json:"surfshark"` Vyprvpn VyprvpnServers `json:"vyprvpn"` @@ -32,6 +33,11 @@ type PiaServers struct { Timestamp int64 `json:"timestamp"` Servers []PIAServer `json:"servers"` } +type PrivadoServers struct { + Version uint16 `json:"version"` + Timestamp int64 `json:"timestamp"` + Servers []PrivadoServer `json:"servers"` +} type PurevpnServers struct { Version uint16 `json:"version"` Timestamp int64 `json:"timestamp"` diff --git a/internal/params/params.go b/internal/params/params.go index e405ab72..6a54d522 100644 --- a/internal/params/params.go +++ b/internal/params/params.go @@ -91,6 +91,10 @@ type Reader interface { GetNordvpnRegions() (regions []string, err error) GetNordvpnNumbers() (numbers []uint16, err error) + // Privado getters + GetPrivadoCities() (regions []string, err error) + GetPrivadoNumbers() (numbers []uint16, err error) + // PureVPN getters GetPurevpnRegions() (regions []string, err error) GetPurevpnCountries() (countries []string, err error) @@ -148,9 +152,9 @@ func (r *reader) GetVPNSP() (vpnServiceProvider models.VPNProvider, err error) { s, err := r.envParams.GetValueIfInside( "VPNSP", []string{ - "pia", "private internet access", "private internet access old", + "pia", "private internet access", "mullvad", "windscribe", "surfshark", "cyberghost", - "vyprvpn", "nordvpn", "purevpn", + "vyprvpn", "nordvpn", "purevpn", "privado", }, libparams.Default("private internet access")) if s == "pia" { s = "private internet access" diff --git a/internal/params/privado.go b/internal/params/privado.go new file mode 100644 index 00000000..15a0004c --- /dev/null +++ b/internal/params/privado.go @@ -0,0 +1,37 @@ +package params + +import ( + "fmt" + "strconv" + + "github.com/qdm12/gluetun/internal/constants" +) + +// GetPrivadoCities obtains the cities for the Privado server from the +// environment variable CITY. +func (r *reader) GetPrivadoCities() (regions []string, err error) { + return r.envParams.GetCSVInPossibilities("CITY", constants.PrivadoCityChoices()) +} + +// GetPrivadoNumbers obtains the server numbers (optional) for the Privado servers from the +// environment variable SERVER_NUMBER. +func (r *reader) GetPrivadoNumbers() (numbers []uint16, err error) { + possibilities := make([]string, 65537) + for i := range possibilities { + possibilities[i] = fmt.Sprintf("%d", i) + } + possibilities[65536] = "" + values, err := r.envParams.GetCSVInPossibilities("SERVER_NUMBER", possibilities) + if err != nil { + return nil, err + } + numbers = make([]uint16, len(values)) + for i := range values { + n, err := strconv.Atoi(values[i]) + if err != nil { + return nil, err + } + numbers[i] = uint16(n) + } + return numbers, nil +} diff --git a/internal/provider/constants.go b/internal/provider/constants.go index bfd76cf3..a4a683a2 100644 --- a/internal/provider/constants.go +++ b/internal/provider/constants.go @@ -2,4 +2,5 @@ package provider const ( aes256cbc = "aes-256-cbc" + sha256 = "sha256" ) diff --git a/internal/provider/cyberghost.go b/internal/provider/cyberghost.go index 83ada783..ac2a35b0 100644 --- a/internal/provider/cyberghost.go +++ b/internal/provider/cyberghost.go @@ -68,7 +68,7 @@ func (c *cyberghost) BuildConf(connection models.OpenVPNConnection, verbosity, cipher = aes256cbc } if len(auth) == 0 { - auth = "SHA256" + auth = sha256 } lines = []string{ "client", diff --git a/internal/provider/privado.go b/internal/provider/privado.go new file mode 100644 index 00000000..a604b9e5 --- /dev/null +++ b/internal/provider/privado.go @@ -0,0 +1,144 @@ +package provider + +import ( + "context" + "fmt" + "math/rand" + "net" + "net/http" + + "github.com/qdm12/gluetun/internal/constants" + "github.com/qdm12/gluetun/internal/firewall" + "github.com/qdm12/gluetun/internal/models" + "github.com/qdm12/golibs/files" + "github.com/qdm12/golibs/logging" +) + +type privado struct { + servers []models.PrivadoServer + randSource rand.Source +} + +func newPrivado(servers []models.PrivadoServer, timeNow timeNowFunc) *privado { + return &privado{ + servers: servers, + randSource: rand.NewSource(timeNow().UnixNano()), + } +} + +func (s *privado) filterServers(cities []string, numbers []uint16) (servers []models.PrivadoServer) { + numbersStr := make([]string, len(numbers)) + for i := range numbers { + numbersStr[i] = fmt.Sprintf("%d", numbers[i]) + } + for _, server := range s.servers { + numberStr := fmt.Sprintf("%d", server.Number) + switch { + case + filterByPossibilities(server.City, cities), + filterByPossibilities(numberStr, numbersStr): + default: + servers = append(servers, server) + } + } + return servers +} + +func makePrivadoHostname(city string, number uint16) string { + numberString := "" + const ten, hundred = 10, 100 + switch { + case number < ten: + numberString = fmt.Sprintf("00%d", number) + case number < hundred: + numberString = fmt.Sprintf("0%d", number) + default: + numberString = fmt.Sprintf("%d", number) + } + return fmt.Sprintf("%s-%s.vpn.privado.io", city, numberString) +} + +func (s *privado) GetOpenVPNConnection(selection models.ServerSelection) ( + connection models.OpenVPNConnection, err error) { + var port uint16 = 1194 + switch selection.Protocol { + case constants.UDP: + default: + return connection, fmt.Errorf("protocol %q is not supported by Privado", selection.Protocol) + } + + if selection.TargetIP != nil { + return models.OpenVPNConnection{IP: selection.TargetIP, Port: port, Protocol: selection.Protocol}, nil + } + + servers := s.filterServers(selection.Cities, selection.Numbers) + if len(servers) == 0 { + return connection, fmt.Errorf("no server found for cities %s and server numbers %v", + commaJoin(selection.Cities), selection.Numbers) + } + + connections := make([]models.OpenVPNConnection, len(servers)) + for i := range servers { + connection := models.OpenVPNConnection{ + IP: servers[i].IP, + Port: port, + Protocol: selection.Protocol, + Hostname: makePrivadoHostname(servers[i].City, servers[i].Number), + } + connections = append(connections, connection) + } + + return pickRandomConnection(connections, s.randSource), nil +} + +func (s *privado) BuildConf(connection models.OpenVPNConnection, verbosity, uid, gid int, root bool, + cipher, auth string, extras models.ExtraConfigOptions) (lines []string) { + if len(cipher) == 0 { + cipher = aes256cbc + } + if len(auth) == 0 { + auth = sha256 + } + lines = []string{ + "client", + "dev tun", + "nobind", + "persist-key", + + // Privado specific + "tls-cipher TLS-DHE-RSA-WITH-AES-256-CBC-SHA:TLS-DHE-DSS-WITH-AES-256-CBC-SHA:TLS-RSA-WITH-AES-256-CBC-SHA", + fmt.Sprintf("verify-x509-name %s name", connection.Hostname), + + // Added constant values + "auth-nocache", + "mute-replay-warnings", + "pull-filter ignore \"auth-token\"", // prevent auth failed loops + "auth-retry nointeract", + "suppress-timestamps", + + // Modified variables + fmt.Sprintf("verb %d", verbosity), + fmt.Sprintf("auth-user-pass %s", constants.OpenVPNAuthConf), + fmt.Sprintf("proto %s", connection.Protocol), + fmt.Sprintf("remote %s %d", connection.IP, connection.Port), + fmt.Sprintf("cipher %s", cipher), + fmt.Sprintf("auth %s", auth), + } + if !root { + lines = append(lines, "user nonrootuser") + } + lines = append(lines, []string{ + "", + "-----BEGIN CERTIFICATE-----", + constants.PrivadoCertificate, + "-----END CERTIFICATE-----", + "", + }...) + return lines +} + +func (s *privado) PortForward(ctx context.Context, client *http.Client, + fileManager files.FileManager, pfLogger logging.Logger, gateway net.IP, fw firewall.Configurator, + syncState func(port uint16) (pfFilepath models.Filepath)) { + panic("port forwarding is not supported for privado") +} diff --git a/internal/provider/provider.go b/internal/provider/provider.go index 5584d9d8..c231345d 100644 --- a/internal/provider/provider.go +++ b/internal/provider/provider.go @@ -40,6 +40,8 @@ func New(provider models.VPNProvider, allServers models.AllServers, timeNow time return newNordvpn(allServers.Nordvpn.Servers, timeNow) case constants.Purevpn: return newPurevpn(allServers.Purevpn.Servers, timeNow) + case constants.Privado: + return newPrivado(allServers.Privado.Servers, timeNow) default: return nil // should never occur } diff --git a/internal/settings/openvpn.go b/internal/settings/openvpn.go index 0b11e45b..5f210aa0 100644 --- a/internal/settings/openvpn.go +++ b/internal/settings/openvpn.go @@ -68,6 +68,8 @@ func GetOpenVPNSettings(paramsReader params.Reader, vpnProvider models.VPNProvid settings.Provider, err = GetNordvpnSettings(paramsReader) case constants.Purevpn: settings.Provider, err = GetPurevpnSettings(paramsReader) + case constants.Privado: + settings.Provider, err = GetPrivadoSettings(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 cdcb894a..327eb6b0 100644 --- a/internal/settings/providers.go +++ b/internal/settings/providers.go @@ -232,3 +232,25 @@ func GetPurevpnSettings(paramsReader params.Reader) (settings models.ProviderSet } return settings, nil } + +// GetPrivadoSettings obtains Privado settings from environment variables using the params package. +func GetPrivadoSettings(paramsReader params.Reader) (settings models.ProviderSettings, err error) { + settings.Name = constants.Privado + 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.Cities, err = paramsReader.GetPrivadoCities() + if err != nil { + return settings, err + } + settings.ServerSelection.Numbers, err = paramsReader.GetPrivadoNumbers() + if err != nil { + return settings, err + } + return settings, nil +} diff --git a/internal/storage/merge.go b/internal/storage/merge.go index 3a9aaa87..62c46a21 100644 --- a/internal/storage/merge.go +++ b/internal/storage/merge.go @@ -47,6 +47,12 @@ func (s *storage) mergeServers(hardcoded, persistent models.AllServers) (merged merged.Pia = persistent.Pia } } + merged.Privado = hardcoded.Privado + if persistent.Privado.Timestamp > hardcoded.Privado.Timestamp { + s.logger.Info("Using Privado servers from file (%s more recent)", + getUnixTimeDifference(persistent.Privado.Timestamp, hardcoded.Privado.Timestamp)) + merged.Privado = persistent.Privado + } merged.Purevpn = hardcoded.Purevpn if persistent.Purevpn.Timestamp > hardcoded.Purevpn.Timestamp { s.logger.Info("Using Purevpn servers from file (%s more recent)", diff --git a/internal/storage/sync.go b/internal/storage/sync.go index c974cb7d..87e8e806 100644 --- a/internal/storage/sync.go +++ b/internal/storage/sync.go @@ -18,6 +18,7 @@ func countServers(allServers models.AllServers) int { len(allServers.Mullvad.Servers) + len(allServers.Nordvpn.Servers) + len(allServers.Pia.Servers) + + len(allServers.Privado.Servers) + len(allServers.Purevpn.Servers) + len(allServers.Surfshark.Servers) + len(allServers.Vyprvpn.Servers) + diff --git a/internal/updater/options.go b/internal/updater/options.go index 19727961..b26fdc88 100644 --- a/internal/updater/options.go +++ b/internal/updater/options.go @@ -5,6 +5,7 @@ type Options struct { Mullvad bool Nordvpn bool PIA bool + Privado bool Purevpn bool Surfshark bool Vyprvpn bool diff --git a/internal/updater/privado.go b/internal/updater/privado.go new file mode 100644 index 00000000..06bd9d90 --- /dev/null +++ b/internal/updater/privado.go @@ -0,0 +1,114 @@ +package updater + +import ( + "context" + "fmt" + "net" + "sort" + "strconv" + "strings" + + "github.com/qdm12/gluetun/internal/models" + "github.com/qdm12/golibs/network" +) + +func (u *updater) updatePrivado(ctx context.Context) (err error) { + servers, warnings, err := findPrivadoServersFromZip(ctx, u.client, u.lookupIP) + if u.options.CLI { + for _, warning := range warnings { + u.logger.Warn("Privado: %s", warning) + } + } + if err != nil { + return fmt.Errorf("cannot update Privado servers: %w", err) + } + if u.options.Stdout { + u.println(stringifyPrivadoServers(servers)) + } + u.servers.Privado.Timestamp = u.timeNow().Unix() + u.servers.Privado.Servers = servers + return nil +} + +func findPrivadoServersFromZip(ctx context.Context, client network.Client, lookupIP lookupIPFunc) ( + servers []models.PrivadoServer, warnings []string, err error) { + const zipURL = "https://privado.io/apps/ovpn_configs.zip" + contents, err := fetchAndExtractFiles(ctx, client, zipURL) + if err != nil { + return nil, nil, err + } + for fileName, content := range contents { + if err := ctx.Err(); err != nil { + return nil, warnings, err + } + remoteLines := extractRemoteLinesFromOpenvpn(content) + if len(remoteLines) == 0 { + return nil, warnings, fmt.Errorf("cannot find any remote lines in %s", fileName) + } + hosts := extractHostnamesFromRemoteLines(remoteLines) + if len(hosts) == 0 { + return nil, warnings, fmt.Errorf("cannot find any hosts in %s", fileName) + } else if len(hosts) > 1 { + warning := fmt.Sprintf("more than one host in %q, only taking first one %q into account", fileName, hosts[0]) + warnings = append(warnings, warning) + } + host := hosts[0] + if net.ParseIP(host) != nil { + warning := fmt.Sprintf("ignoring IP address host %q in %s", host, fileName) + warnings = append(warnings, warning) + continue + } + const repetition = 1 + IPs, err := resolveRepeat(ctx, lookupIP, host, repetition) + switch { + case err != nil: + return nil, warnings, err + case len(IPs) == 0: + warning := fmt.Sprintf("no IP address found for host %q", host) + warnings = append(warnings, warning) + continue + case len(IPs) > 1: + warning := fmt.Sprintf("more than one IP address found for host %q", host) + warnings = append(warnings, warning) + } + subdomain := strings.TrimSuffix(host, ".vpn.privado.io") + parts := strings.Split(subdomain, "-") + const expectedParts = 2 + if len(parts) != expectedParts { + warning := fmt.Sprintf("malformed subdomain %q: cannot find city and server number", subdomain) + warnings = append(warnings, warning) + continue + } + city, serverNumberString := parts[0], parts[1] + serverNumberInt, err := strconv.ParseInt(serverNumberString, 10, 16) + if err != nil { + warning := fmt.Sprintf("malformed server number %q: %s", serverNumberString, err) + warnings = append(warnings, warning) + continue + } + server := models.PrivadoServer{ + City: city, + Number: uint16(serverNumberInt), + IP: IPs[0], + } + servers = append(servers, server) + } + + sort.Slice(servers, func(i, j int) bool { + keyA := servers[i].City + fmt.Sprintf("%d", servers[i].Number) + keyB := servers[j].City + fmt.Sprintf("%d", servers[j].Number) + return keyA < keyB + }) + return servers, warnings, nil +} + +func stringifyPrivadoServers(servers []models.PrivadoServer) (s string) { + s = "func PrivadoServers() []models.PrivadoServer {\n" + s += " return []models.PrivadoServer{\n" + for _, server := range servers { + s += " " + server.String() + ",\n" + } + s += " }\n" + s += "}" + return s +} diff --git a/internal/updater/updater.go b/internal/updater/updater.go index 146d4ec7..b34bac0f 100644 --- a/internal/updater/updater.go +++ b/internal/updater/updater.go @@ -90,6 +90,16 @@ func (u *updater) UpdateServers(ctx context.Context) (allServers models.AllServe } } + if u.options.Privado { + u.logger.Info("updating Privado servers...") + if err := u.updatePrivado(ctx); err != nil { + u.logger.Error(err) + } + if ctx.Err() != nil { + return allServers, ctx.Err() + } + } + if u.options.Purevpn { u.logger.Info("updating PureVPN servers...") // TODO support servers offering only TCP or only UDP