feat(providers): add AirVPN support (#1145)

This commit is contained in:
Quentin McGaw
2022-10-17 02:54:56 -04:00
committed by GitHub
parent f70609c464
commit f15dde6502
22 changed files with 18722 additions and 10 deletions

View File

@@ -40,6 +40,7 @@ body:
attributes:
label: VPN service provider
options:
- AirVPN
- Custom
- Cyberghost
- ExpressVPN

3
.github/labels.yml vendored
View File

@@ -23,6 +23,9 @@
description: ""
# VPN providers
- name: ":cloud: AirVPN"
color: "cfe8d4"
description: ""
- name: ":cloud: Cyberghost"
color: "cfe8d4"
description: ""

View File

@@ -45,6 +45,8 @@ jobs:
with:
locale: "US"
level: error
exclude: |
./internal/storage/servers.json
- name: Linting
run: docker build --target lint .

View File

@@ -58,7 +58,7 @@ Lightweight swiss-knife-like VPN client to multiple VPN service providers
## Features
- Based on Alpine 3.16 for a small Docker image of 29MB
- Supports: **Cyberghost**, **ExpressVPN**, **FastestVPN**, **HideMyAss**, **IPVanish**, **IVPN**, **Mullvad**, **NordVPN**, **Perfect Privacy**, **Privado**, **Private Internet Access**, **PrivateVPN**, **ProtonVPN**, **PureVPN**, **SlickVPN**, **Surfshark**, **TorGuard**, **VPNSecure.me**, **VPNUnlimited**, **Vyprvpn**, **WeVPN**, **Windscribe** servers
- Supports: **AirVPN**, **Cyberghost**, **ExpressVPN**, **FastestVPN**, **HideMyAss**, **IPVanish**, **IVPN**, **Mullvad**, **NordVPN**, **Perfect Privacy**, **Privado**, **Private Internet Access**, **PrivateVPN**, **ProtonVPN**, **PureVPN**, **SlickVPN**, **Surfshark**, **TorGuard**, **VPNSecure.me**, **VPNUnlimited**, **Vyprvpn**, **WeVPN**, **Windscribe** servers
- Supports OpenVPN for all providers listed
- Supports Wireguard both kernelspace and userspace
- For **Mullvad**, **Ivpn**, **Surfshark** and **Windscribe**

View File

@@ -95,7 +95,9 @@ func (o OpenVPN) validate(vpnProvider string) (err error) {
}
isCustom := vpnProvider == providers.Custom
isUserRequired := !isCustom && vpnProvider != providers.VPNSecure
isUserRequired := !isCustom &&
vpnProvider != providers.Airvpn &&
vpnProvider != providers.VPNSecure
if isUserRequired && *o.User == "" {
return ErrOpenVPNUserIsEmpty
@@ -179,6 +181,7 @@ func validateOpenVPNClientCertificate(vpnProvider,
clientCert string) (err error) {
switch vpnProvider {
case
providers.Airvpn,
providers.Cyberghost,
providers.VPNSecure,
providers.VPNUnlimited:
@@ -201,6 +204,7 @@ func validateOpenVPNClientCertificate(vpnProvider,
func validateOpenVPNClientKey(vpnProvider, clientKey string) (err error) {
switch vpnProvider {
case
providers.Airvpn,
providers.Cyberghost,
providers.VPNUnlimited,
providers.Wevpn:

View File

@@ -67,6 +67,12 @@ func (o OpenVPNSelection) validate(vpnProvider string) (err error) {
default:
var allowedTCP, allowedUDP []uint16
switch vpnProvider {
case providers.Airvpn:
allowedTCP = []uint16{
53, 80, 443, // IP in 1, 3
1194, 2018, 41185, // IP in 1, 2, 3, 4
}
allowedUDP = []uint16{53, 80, 443, 1194, 2018, 41185}
case providers.Ivpn:
allowedTCP = []uint16{80, 443, 1143}
allowedUDP = []uint16{53, 1194, 2049, 2050}

View File

@@ -30,6 +30,7 @@ func (p *Provider) validate(vpnType string, storage Storage) (err error) {
validNames = append(validNames, "pia") // Retro-compatibility
} else { // Wireguard
validNames = []string{
providers.Airvpn,
providers.Custom,
providers.Ivpn,
providers.Mullvad,

View File

@@ -54,6 +54,12 @@ func (w Wireguard) validate(vpnProvider string) (err error) {
return fmt.Errorf("private key is not valid: %w", err)
}
if vpnProvider == providers.Airvpn {
if *w.PreSharedKey == "" {
return fmt.Errorf("%w", ErrWireguardPreSharedKeyNotSet)
}
}
// Validate PreSharedKey
if *w.PreSharedKey != "" { // Note: this is optional
_, err = wgtypes.ParseKey(*w.PreSharedKey)

View File

@@ -36,7 +36,7 @@ type WireguardSelection struct {
func (w WireguardSelection) validate(vpnProvider string) (err error) {
// Validate EndpointIP
switch vpnProvider {
case providers.Ivpn, providers.Mullvad,
case providers.Airvpn, providers.Ivpn, providers.Mullvad,
providers.Surfshark, providers.Windscribe:
// endpoint IP addresses are baked in
case providers.Custom:
@@ -58,7 +58,7 @@ func (w WireguardSelection) validate(vpnProvider string) (err error) {
if *w.EndpointPort != 0 {
return ErrWireguardEndpointPortSet
}
case providers.Ivpn, providers.Mullvad, providers.Windscribe:
case providers.Airvpn, providers.Ivpn, providers.Mullvad, providers.Windscribe:
// EndpointPort is optional and can be 0
if *w.EndpointPort == 0 {
break // no custom endpoint port set
@@ -68,6 +68,8 @@ func (w WireguardSelection) validate(vpnProvider string) (err error) {
}
var allowed []uint16
switch vpnProvider {
case providers.Airvpn:
allowed = []uint16{1637, 47107}
case providers.Ivpn:
allowed = []uint16{2049, 2050, 53, 30587, 41893, 48574, 58237}
case providers.Windscribe:

View File

@@ -1,8 +1,11 @@
package openvpn
const (
AES128cbc = "aes-128-cbc"
AES256cbc = "aes-256-cbc"
AES128gcm = "aes-128-gcm"
AES256gcm = "aes-256-gcm"
AES128cbc = "aes-128-cbc"
AES192cbc = "aes-192-cbc"
AES256cbc = "aes-256-cbc"
AES128gcm = "aes-128-gcm"
AES192gcm = "aes-192-gcm"
AES256gcm = "aes-256-gcm"
Chacha20Poly1305 = "chacha20-poly1305"
)

View File

@@ -3,6 +3,7 @@ package providers
const (
// Custom is the VPN provider name for custom
// VPN configurations.
Airvpn = "airvpn"
Custom = "custom"
Cyberghost = "cyberghost"
Example = "example"
@@ -32,6 +33,7 @@ const (
// All returns all the providers except the custom provider.
func All() []string {
return []string{
Airvpn,
Cyberghost,
Expressvpn,
Fastestvpn,

View File

@@ -5,6 +5,7 @@ import (
"strings"
"github.com/qdm12/gluetun/internal/constants/providers"
"github.com/qdm12/gluetun/internal/constants/vpn"
)
func boolToMarkdown(b bool) string {
@@ -26,6 +27,7 @@ const (
hostnameHeader = "Hostname"
ispHeader = "ISP"
multiHopHeader = "MultiHop"
nameHeader = "Name"
numberHeader = "Number"
ownedHeader = "Owned"
portForwardHeader = "Port forwarding"
@@ -57,6 +59,8 @@ func (s *Server) ToMarkdown(headers ...string) (markdown string) {
fields[i] = s.ISP
case multiHopHeader:
fields[i] = boolToMarkdown(s.MultiHop)
case nameHeader:
fields[i] = s.ServerName
case numberHeader:
fields[i] = fmt.Sprint(s.Number)
case ownedHeader:
@@ -72,7 +76,7 @@ func (s *Server) ToMarkdown(headers ...string) (markdown string) {
case tcpHeader:
fields[i] = boolToMarkdown(s.TCP)
case udpHeader:
fields[i] = boolToMarkdown(s.UDP)
fields[i] = boolToMarkdown(s.UDP || s.VPN == vpn.Wireguard)
case vpnHeader:
fields[i] = s.VPN
}
@@ -98,6 +102,9 @@ func (s *Servers) ToMarkdown(vpnProvider string) (markdown string) {
func getMarkdownHeaders(vpnProvider string) (headers []string) {
switch vpnProvider {
case providers.Airvpn:
return []string{regionHeader, countryHeader, cityHeader, vpnHeader,
udpHeader, tcpHeader, hostnameHeader, nameHeader}
case providers.Cyberghost:
return []string{countryHeader, hostnameHeader, tcpHeader, udpHeader}
case providers.Expressvpn:

View File

@@ -0,0 +1,14 @@
package airvpn
import (
"github.com/qdm12/gluetun/internal/configuration/settings"
"github.com/qdm12/gluetun/internal/models"
"github.com/qdm12/gluetun/internal/provider/utils"
)
func (p *Provider) GetConnection(selection settings.ServerSelection, ipv6Supported bool) (
connection models.Connection, err error) {
defaults := utils.NewConnectionDefaults(443, 1194, 1637) //nolint:gomnd
return utils.GetConnection(p.Name(),
p.storage, selection, defaults, ipv6Supported, p.randSource)
}

View File

@@ -0,0 +1,44 @@
package airvpn
import (
"fmt"
"github.com/qdm12/gluetun/internal/configuration/settings"
"github.com/qdm12/gluetun/internal/constants/openvpn"
"github.com/qdm12/gluetun/internal/models"
"github.com/qdm12/gluetun/internal/provider/utils"
)
func (p *Provider) OpenVPNConfig(connection models.Connection,
settings settings.OpenVPN, ipv6Supported bool) (lines []string) {
providerSettings := utils.OpenVPNProviderSettings{
AuthUserPass: true,
RemoteCertTLS: true,
Auth: openvpn.SHA512,
CA: "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", //nolint:lll
TLSCrypt: "a3a7d8f4e778d279d9076a8d47a9aa0c6054aed5752ddefa5efac1ea982740f1ffcabadf0d3709dae18c1fad61e14f72a8eb7cb931ed0209e67c1d325353a657a1198ef649f1c23861a2a19f2c6b27aa5e43be761e0c71e9c2e8d33b75af289effb1b1e4ec603d865f74e2b4348ff631c5c81202d90003ed263dca4022aa9861520e00cc26e40fa171b9985a2763ccb4c63560b7e6b0f897978fb25a2d5889cd6d46a29509fa09830aff18d6e81a8dc1a0182402e3039c3316180e618705ca35f2763f8a62ca5983d145faa2276532ae5e18459a0b729dc67f41b928e592b39467ec3d79c70205595718b1bce56ca4ff58e692ce09c8282d2770d2bf5c217c06", //nolint:lll
ExtraLines: []string{
"comp-lzo no", // Explicitly disable compression
"push-peer-info",
},
}
switch settings.Version {
case openvpn.Openvpn24:
providerSettings.Ciphers = []string{openvpn.AES256cbc}
case openvpn.Openvpn25:
providerSettings.Ciphers = []string{
openvpn.AES256gcm, openvpn.AES256cbc, openvpn.AES192gcm,
openvpn.AES192cbc, openvpn.AES128gcm, openvpn.AES128cbc,
openvpn.Chacha20Poly1305}
default:
panic(fmt.Sprintf("openvpn version %q is not implemented", settings.Version))
}
providerSettings.SetEnv = map[string]string{"UV_IPV6": "no"}
if ipv6Supported {
providerSettings.SetEnv["UV_IPV6"] = "yes"
}
return utils.OpenVPNConfig(providerSettings, connection, settings, ipv6Supported)
}

View File

@@ -0,0 +1,32 @@
package airvpn
import (
"math/rand"
"net/http"
"github.com/qdm12/gluetun/internal/constants/providers"
"github.com/qdm12/gluetun/internal/provider/airvpn/updater"
"github.com/qdm12/gluetun/internal/provider/common"
"github.com/qdm12/gluetun/internal/provider/utils"
)
type Provider struct {
storage common.Storage
randSource rand.Source
utils.NoPortForwarder
common.Fetcher
}
func New(storage common.Storage, randSource rand.Source,
client *http.Client) *Provider {
return &Provider{
storage: storage,
randSource: randSource,
NoPortForwarder: utils.NewNoPortForwarding(providers.Example),
Fetcher: updater.New(client),
}
}
func (p *Provider) Name() string {
return providers.Airvpn
}

View File

@@ -0,0 +1,65 @@
package updater
import (
"context"
"encoding/json"
"fmt"
"net"
"net/http"
"github.com/qdm12/gluetun/internal/provider/common"
)
type apiData struct {
Servers []apiServer `json:"servers"`
}
type apiServer struct {
PublicName string `json:"public_name"`
CountryName string `json:"country_name"`
CountryCode string `json:"country_code"`
Location string `json:"location"`
Continent string `json:"continent"`
IPv4In1 net.IP `json:"ip_v4_in1"`
IPv4In2 net.IP `json:"ip_v4_in2"`
IPv4In3 net.IP `json:"ip_v4_in3"`
IPv4In4 net.IP `json:"ip_v4_in4"`
IPv6In1 net.IP `json:"ip_v6_in1"`
IPv6In2 net.IP `json:"ip_v6_in2"`
IPv6In3 net.IP `json:"ip_v6_in3"`
IPv6In4 net.IP `json:"ip_v6_in4"`
Health string `json:"health"`
}
func fetchAPI(ctx context.Context, client *http.Client) (
data apiData, err error) {
const url = "https://airvpn.org/api/status/"
request, err := http.NewRequestWithContext(ctx, http.MethodGet, url, nil)
if err != nil {
return data, fmt.Errorf("creating HTTP request: %w", err)
}
response, err := client.Do(request)
if err != nil {
return data, fmt.Errorf("doing HTTP request: %w", err)
}
if response.StatusCode != http.StatusOK {
_ = response.Body.Close()
return data, fmt.Errorf("%w: %d %s",
common.ErrHTTPStatusCodeNotOK, response.StatusCode, response.Status)
}
decoder := json.NewDecoder(response.Body)
if err := decoder.Decode(&data); err != nil {
_ = response.Body.Close()
return data, fmt.Errorf("unmarshaling response body: %w", err)
}
if err := response.Body.Close(); err != nil {
return data, fmt.Errorf("closing response body: %w", err)
}
return data, nil
}

View File

@@ -0,0 +1,97 @@
package updater
import (
"context"
"fmt"
"net"
"sort"
"github.com/qdm12/gluetun/internal/constants/vpn"
"github.com/qdm12/gluetun/internal/models"
"github.com/qdm12/gluetun/internal/provider/common"
)
func (u *Updater) FetchServers(ctx context.Context, minServers int) (
servers []models.Server, err error) {
data, err := fetchAPI(ctx, u.client)
if err != nil {
return nil, fmt.Errorf("fetching API: %w", err)
}
// every API server model has:
// - Wireguard server using IPv4In1
// - Wiregard server using IPv6In1
// - OpenVPN TCP+UDP+SSH+SSL server with tls-auth using IPv4In1 and IPv6In1
// - OpenVPN TCP+UDP+SSH+SSL server with tls-auth using IPv4In2 and IPv6In2
// - OpenVPN TCP+UDP+SSH+SSL server with tls-crypt using IPv4In3 and IPv6In3
// - OpenVPN TCP+UDP+SSH+SSL server with tls-crypt using IPv6In4 and IPv6In4
const numberOfServersPerAPIServer = 1 + // Wireguard server using IPv4In1
1 + // Wiregard server using IPv6In1
4 + // OpenVPN TCP server with tls-auth using IPv4In3, IPv6In3, IPv4In4, IPv6In4
4 // OpenVPN UDP server with tls-auth using IPv4In3, IPv6In3, IPv4In4, IPv6In4
projectedNumberOfServers := numberOfServersPerAPIServer * len(data.Servers)
if projectedNumberOfServers < minServers {
return nil, fmt.Errorf("%w: %d and expected at least %d",
common.ErrNotEnoughServers, projectedNumberOfServers, minServers)
}
servers = make([]models.Server, 0, projectedNumberOfServers)
for _, apiServer := range data.Servers {
if apiServer.Health != "ok" {
continue
}
baseServer := models.Server{
ServerName: apiServer.PublicName,
Country: apiServer.CountryName,
City: apiServer.Location,
Region: apiServer.Continent,
}
baseWireguardServer := baseServer
baseWireguardServer.VPN = vpn.Wireguard
baseWireguardServer.WgPubKey = "PyLCXAQT8KkM4T+dUsOQfn+Ub3pGxfGlxkIApuig+hk="
ipv4WireguadServer := baseWireguardServer
ipv4WireguadServer.IPs = []net.IP{apiServer.IPv4In1}
ipv4WireguadServer.Hostname = apiServer.CountryCode + ".vpn.airdns.org"
servers = append(servers, ipv4WireguadServer)
ipv6WireguadServer := baseWireguardServer
ipv6WireguadServer.IPs = []net.IP{apiServer.IPv6In1}
ipv6WireguadServer.Hostname = apiServer.CountryCode + ".ipv6.vpn.airdns.org"
servers = append(servers, ipv6WireguadServer)
baseOpenVPNServer := baseServer
baseOpenVPNServer.VPN = vpn.OpenVPN
baseOpenVPNServer.UDP = true
baseOpenVPNServer.TCP = true
// Ignore IPs 1 and 2 since tls-crypt is superior to tls-auth really.
ipv4In3OpenVPNServer := baseOpenVPNServer
ipv4In3OpenVPNServer.IPs = []net.IP{apiServer.IPv4In3}
ipv4In3OpenVPNServer.Hostname = apiServer.CountryCode + "3.vpn.airdns.org"
servers = append(servers, ipv4In3OpenVPNServer)
ipv6In3OpenVPNServer := baseOpenVPNServer
ipv6In3OpenVPNServer.IPs = []net.IP{apiServer.IPv6In3}
ipv6In3OpenVPNServer.Hostname = apiServer.CountryCode + "3.ipv6.vpn.airdns.org"
servers = append(servers, ipv6In3OpenVPNServer)
ipv4In4OpenVPNServer := baseOpenVPNServer
ipv4In4OpenVPNServer.IPs = []net.IP{apiServer.IPv4In4}
ipv4In4OpenVPNServer.Hostname = apiServer.CountryCode + "4.vpn.airdns.org"
servers = append(servers, ipv4In4OpenVPNServer)
ipv6In4OpenVPNServer := baseOpenVPNServer
ipv6In4OpenVPNServer.IPs = []net.IP{apiServer.IPv6In4}
ipv6In4OpenVPNServer.Hostname = apiServer.CountryCode + "4.ipv6.vpn.airdns.org"
servers = append(servers, ipv6In4OpenVPNServer)
}
sort.Sort(models.SortableServers(servers))
return servers, nil
}

View File

@@ -0,0 +1,15 @@
package updater
import (
"net/http"
)
type Updater struct {
client *http.Client
}
func New(client *http.Client) *Updater {
return &Updater{
client: client,
}
}

View File

@@ -10,7 +10,10 @@ import (
"github.com/qdm12/gluetun/internal/updater/resolver"
)
var ErrNotEnoughServers = errors.New("not enough servers found")
var (
ErrNotEnoughServers = errors.New("not enough servers found")
ErrHTTPStatusCodeNotOK = errors.New("HTTP status code not OK")
)
type Fetcher interface {
FetchServers(ctx context.Context, minServers int) (servers []models.Server, err error)

View File

@@ -9,6 +9,7 @@ import (
"github.com/qdm12/gluetun/internal/configuration/settings"
"github.com/qdm12/gluetun/internal/constants/providers"
"github.com/qdm12/gluetun/internal/models"
"github.com/qdm12/gluetun/internal/provider/airvpn"
"github.com/qdm12/gluetun/internal/provider/common"
"github.com/qdm12/gluetun/internal/provider/custom"
"github.com/qdm12/gluetun/internal/provider/cyberghost"
@@ -58,6 +59,7 @@ func NewProviders(storage Storage, timeNow func() time.Time,
//nolint:lll
providerNameToProvider := map[string]Provider{
providers.Airvpn: airvpn.New(storage, randSource, client),
providers.Custom: custom.New(extractor),
providers.Cyberghost: cyberghost.New(storage, randSource, parallelResolver),
providers.Expressvpn: expressvpn.New(storage, randSource, unzipper, updaterWarner, parallelResolver),

View File

@@ -42,6 +42,7 @@ type OpenVPNProviderSettings struct {
RenegDisabled bool
RenegSec uint16
KeyDirection string
SetEnv map[string]string
ExtraLines []string
UDPLines []string
IPv6Lines []string
@@ -168,6 +169,10 @@ func OpenVPNConfig(provider OpenVPNProviderSettings,
lines.addLines(provider.IPv6Lines)
}
for envKey, envValue := range provider.SetEnv {
lines.add("setenv", envKey, envValue)
}
if provider.CA != "" {
lines.addLines(WrapOpenvpnCA(provider.CA))
}

File diff suppressed because it is too large Load Diff