feat(providers): add AirVPN support (#1145)
This commit is contained in:
1
.github/ISSUE_TEMPLATE/bug.yml
vendored
1
.github/ISSUE_TEMPLATE/bug.yml
vendored
@@ -40,6 +40,7 @@ body:
|
|||||||
attributes:
|
attributes:
|
||||||
label: VPN service provider
|
label: VPN service provider
|
||||||
options:
|
options:
|
||||||
|
- AirVPN
|
||||||
- Custom
|
- Custom
|
||||||
- Cyberghost
|
- Cyberghost
|
||||||
- ExpressVPN
|
- ExpressVPN
|
||||||
|
|||||||
3
.github/labels.yml
vendored
3
.github/labels.yml
vendored
@@ -23,6 +23,9 @@
|
|||||||
description: ""
|
description: ""
|
||||||
|
|
||||||
# VPN providers
|
# VPN providers
|
||||||
|
- name: ":cloud: AirVPN"
|
||||||
|
color: "cfe8d4"
|
||||||
|
description: ""
|
||||||
- name: ":cloud: Cyberghost"
|
- name: ":cloud: Cyberghost"
|
||||||
color: "cfe8d4"
|
color: "cfe8d4"
|
||||||
description: ""
|
description: ""
|
||||||
|
|||||||
2
.github/workflows/ci.yml
vendored
2
.github/workflows/ci.yml
vendored
@@ -45,6 +45,8 @@ jobs:
|
|||||||
with:
|
with:
|
||||||
locale: "US"
|
locale: "US"
|
||||||
level: error
|
level: error
|
||||||
|
exclude: |
|
||||||
|
./internal/storage/servers.json
|
||||||
|
|
||||||
- name: Linting
|
- name: Linting
|
||||||
run: docker build --target lint .
|
run: docker build --target lint .
|
||||||
|
|||||||
@@ -58,7 +58,7 @@ Lightweight swiss-knife-like VPN client to multiple VPN service providers
|
|||||||
## Features
|
## Features
|
||||||
|
|
||||||
- Based on Alpine 3.16 for a small Docker image of 29MB
|
- 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 OpenVPN for all providers listed
|
||||||
- Supports Wireguard both kernelspace and userspace
|
- Supports Wireguard both kernelspace and userspace
|
||||||
- For **Mullvad**, **Ivpn**, **Surfshark** and **Windscribe**
|
- For **Mullvad**, **Ivpn**, **Surfshark** and **Windscribe**
|
||||||
|
|||||||
@@ -95,7 +95,9 @@ func (o OpenVPN) validate(vpnProvider string) (err error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
isCustom := vpnProvider == providers.Custom
|
isCustom := vpnProvider == providers.Custom
|
||||||
isUserRequired := !isCustom && vpnProvider != providers.VPNSecure
|
isUserRequired := !isCustom &&
|
||||||
|
vpnProvider != providers.Airvpn &&
|
||||||
|
vpnProvider != providers.VPNSecure
|
||||||
|
|
||||||
if isUserRequired && *o.User == "" {
|
if isUserRequired && *o.User == "" {
|
||||||
return ErrOpenVPNUserIsEmpty
|
return ErrOpenVPNUserIsEmpty
|
||||||
@@ -179,6 +181,7 @@ func validateOpenVPNClientCertificate(vpnProvider,
|
|||||||
clientCert string) (err error) {
|
clientCert string) (err error) {
|
||||||
switch vpnProvider {
|
switch vpnProvider {
|
||||||
case
|
case
|
||||||
|
providers.Airvpn,
|
||||||
providers.Cyberghost,
|
providers.Cyberghost,
|
||||||
providers.VPNSecure,
|
providers.VPNSecure,
|
||||||
providers.VPNUnlimited:
|
providers.VPNUnlimited:
|
||||||
@@ -201,6 +204,7 @@ func validateOpenVPNClientCertificate(vpnProvider,
|
|||||||
func validateOpenVPNClientKey(vpnProvider, clientKey string) (err error) {
|
func validateOpenVPNClientKey(vpnProvider, clientKey string) (err error) {
|
||||||
switch vpnProvider {
|
switch vpnProvider {
|
||||||
case
|
case
|
||||||
|
providers.Airvpn,
|
||||||
providers.Cyberghost,
|
providers.Cyberghost,
|
||||||
providers.VPNUnlimited,
|
providers.VPNUnlimited,
|
||||||
providers.Wevpn:
|
providers.Wevpn:
|
||||||
|
|||||||
@@ -67,6 +67,12 @@ func (o OpenVPNSelection) validate(vpnProvider string) (err error) {
|
|||||||
default:
|
default:
|
||||||
var allowedTCP, allowedUDP []uint16
|
var allowedTCP, allowedUDP []uint16
|
||||||
switch vpnProvider {
|
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:
|
case providers.Ivpn:
|
||||||
allowedTCP = []uint16{80, 443, 1143}
|
allowedTCP = []uint16{80, 443, 1143}
|
||||||
allowedUDP = []uint16{53, 1194, 2049, 2050}
|
allowedUDP = []uint16{53, 1194, 2049, 2050}
|
||||||
|
|||||||
@@ -30,6 +30,7 @@ func (p *Provider) validate(vpnType string, storage Storage) (err error) {
|
|||||||
validNames = append(validNames, "pia") // Retro-compatibility
|
validNames = append(validNames, "pia") // Retro-compatibility
|
||||||
} else { // Wireguard
|
} else { // Wireguard
|
||||||
validNames = []string{
|
validNames = []string{
|
||||||
|
providers.Airvpn,
|
||||||
providers.Custom,
|
providers.Custom,
|
||||||
providers.Ivpn,
|
providers.Ivpn,
|
||||||
providers.Mullvad,
|
providers.Mullvad,
|
||||||
|
|||||||
@@ -54,6 +54,12 @@ func (w Wireguard) validate(vpnProvider string) (err error) {
|
|||||||
return fmt.Errorf("private key is not valid: %w", err)
|
return fmt.Errorf("private key is not valid: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if vpnProvider == providers.Airvpn {
|
||||||
|
if *w.PreSharedKey == "" {
|
||||||
|
return fmt.Errorf("%w", ErrWireguardPreSharedKeyNotSet)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Validate PreSharedKey
|
// Validate PreSharedKey
|
||||||
if *w.PreSharedKey != "" { // Note: this is optional
|
if *w.PreSharedKey != "" { // Note: this is optional
|
||||||
_, err = wgtypes.ParseKey(*w.PreSharedKey)
|
_, err = wgtypes.ParseKey(*w.PreSharedKey)
|
||||||
|
|||||||
@@ -36,7 +36,7 @@ type WireguardSelection struct {
|
|||||||
func (w WireguardSelection) validate(vpnProvider string) (err error) {
|
func (w WireguardSelection) validate(vpnProvider string) (err error) {
|
||||||
// Validate EndpointIP
|
// Validate EndpointIP
|
||||||
switch vpnProvider {
|
switch vpnProvider {
|
||||||
case providers.Ivpn, providers.Mullvad,
|
case providers.Airvpn, providers.Ivpn, providers.Mullvad,
|
||||||
providers.Surfshark, providers.Windscribe:
|
providers.Surfshark, providers.Windscribe:
|
||||||
// endpoint IP addresses are baked in
|
// endpoint IP addresses are baked in
|
||||||
case providers.Custom:
|
case providers.Custom:
|
||||||
@@ -58,7 +58,7 @@ func (w WireguardSelection) validate(vpnProvider string) (err error) {
|
|||||||
if *w.EndpointPort != 0 {
|
if *w.EndpointPort != 0 {
|
||||||
return ErrWireguardEndpointPortSet
|
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
|
// EndpointPort is optional and can be 0
|
||||||
if *w.EndpointPort == 0 {
|
if *w.EndpointPort == 0 {
|
||||||
break // no custom endpoint port set
|
break // no custom endpoint port set
|
||||||
@@ -68,6 +68,8 @@ func (w WireguardSelection) validate(vpnProvider string) (err error) {
|
|||||||
}
|
}
|
||||||
var allowed []uint16
|
var allowed []uint16
|
||||||
switch vpnProvider {
|
switch vpnProvider {
|
||||||
|
case providers.Airvpn:
|
||||||
|
allowed = []uint16{1637, 47107}
|
||||||
case providers.Ivpn:
|
case providers.Ivpn:
|
||||||
allowed = []uint16{2049, 2050, 53, 30587, 41893, 48574, 58237}
|
allowed = []uint16{2049, 2050, 53, 30587, 41893, 48574, 58237}
|
||||||
case providers.Windscribe:
|
case providers.Windscribe:
|
||||||
|
|||||||
@@ -1,8 +1,11 @@
|
|||||||
package openvpn
|
package openvpn
|
||||||
|
|
||||||
const (
|
const (
|
||||||
AES128cbc = "aes-128-cbc"
|
AES128cbc = "aes-128-cbc"
|
||||||
AES256cbc = "aes-256-cbc"
|
AES192cbc = "aes-192-cbc"
|
||||||
AES128gcm = "aes-128-gcm"
|
AES256cbc = "aes-256-cbc"
|
||||||
AES256gcm = "aes-256-gcm"
|
AES128gcm = "aes-128-gcm"
|
||||||
|
AES192gcm = "aes-192-gcm"
|
||||||
|
AES256gcm = "aes-256-gcm"
|
||||||
|
Chacha20Poly1305 = "chacha20-poly1305"
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ package providers
|
|||||||
const (
|
const (
|
||||||
// Custom is the VPN provider name for custom
|
// Custom is the VPN provider name for custom
|
||||||
// VPN configurations.
|
// VPN configurations.
|
||||||
|
Airvpn = "airvpn"
|
||||||
Custom = "custom"
|
Custom = "custom"
|
||||||
Cyberghost = "cyberghost"
|
Cyberghost = "cyberghost"
|
||||||
Example = "example"
|
Example = "example"
|
||||||
@@ -32,6 +33,7 @@ const (
|
|||||||
// All returns all the providers except the custom provider.
|
// All returns all the providers except the custom provider.
|
||||||
func All() []string {
|
func All() []string {
|
||||||
return []string{
|
return []string{
|
||||||
|
Airvpn,
|
||||||
Cyberghost,
|
Cyberghost,
|
||||||
Expressvpn,
|
Expressvpn,
|
||||||
Fastestvpn,
|
Fastestvpn,
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/qdm12/gluetun/internal/constants/providers"
|
"github.com/qdm12/gluetun/internal/constants/providers"
|
||||||
|
"github.com/qdm12/gluetun/internal/constants/vpn"
|
||||||
)
|
)
|
||||||
|
|
||||||
func boolToMarkdown(b bool) string {
|
func boolToMarkdown(b bool) string {
|
||||||
@@ -26,6 +27,7 @@ const (
|
|||||||
hostnameHeader = "Hostname"
|
hostnameHeader = "Hostname"
|
||||||
ispHeader = "ISP"
|
ispHeader = "ISP"
|
||||||
multiHopHeader = "MultiHop"
|
multiHopHeader = "MultiHop"
|
||||||
|
nameHeader = "Name"
|
||||||
numberHeader = "Number"
|
numberHeader = "Number"
|
||||||
ownedHeader = "Owned"
|
ownedHeader = "Owned"
|
||||||
portForwardHeader = "Port forwarding"
|
portForwardHeader = "Port forwarding"
|
||||||
@@ -57,6 +59,8 @@ func (s *Server) ToMarkdown(headers ...string) (markdown string) {
|
|||||||
fields[i] = s.ISP
|
fields[i] = s.ISP
|
||||||
case multiHopHeader:
|
case multiHopHeader:
|
||||||
fields[i] = boolToMarkdown(s.MultiHop)
|
fields[i] = boolToMarkdown(s.MultiHop)
|
||||||
|
case nameHeader:
|
||||||
|
fields[i] = s.ServerName
|
||||||
case numberHeader:
|
case numberHeader:
|
||||||
fields[i] = fmt.Sprint(s.Number)
|
fields[i] = fmt.Sprint(s.Number)
|
||||||
case ownedHeader:
|
case ownedHeader:
|
||||||
@@ -72,7 +76,7 @@ func (s *Server) ToMarkdown(headers ...string) (markdown string) {
|
|||||||
case tcpHeader:
|
case tcpHeader:
|
||||||
fields[i] = boolToMarkdown(s.TCP)
|
fields[i] = boolToMarkdown(s.TCP)
|
||||||
case udpHeader:
|
case udpHeader:
|
||||||
fields[i] = boolToMarkdown(s.UDP)
|
fields[i] = boolToMarkdown(s.UDP || s.VPN == vpn.Wireguard)
|
||||||
case vpnHeader:
|
case vpnHeader:
|
||||||
fields[i] = s.VPN
|
fields[i] = s.VPN
|
||||||
}
|
}
|
||||||
@@ -98,6 +102,9 @@ func (s *Servers) ToMarkdown(vpnProvider string) (markdown string) {
|
|||||||
|
|
||||||
func getMarkdownHeaders(vpnProvider string) (headers []string) {
|
func getMarkdownHeaders(vpnProvider string) (headers []string) {
|
||||||
switch vpnProvider {
|
switch vpnProvider {
|
||||||
|
case providers.Airvpn:
|
||||||
|
return []string{regionHeader, countryHeader, cityHeader, vpnHeader,
|
||||||
|
udpHeader, tcpHeader, hostnameHeader, nameHeader}
|
||||||
case providers.Cyberghost:
|
case providers.Cyberghost:
|
||||||
return []string{countryHeader, hostnameHeader, tcpHeader, udpHeader}
|
return []string{countryHeader, hostnameHeader, tcpHeader, udpHeader}
|
||||||
case providers.Expressvpn:
|
case providers.Expressvpn:
|
||||||
|
|||||||
14
internal/provider/airvpn/connection.go
Normal file
14
internal/provider/airvpn/connection.go
Normal 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)
|
||||||
|
}
|
||||||
44
internal/provider/airvpn/openvpnconf.go
Normal file
44
internal/provider/airvpn/openvpnconf.go
Normal 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)
|
||||||
|
}
|
||||||
32
internal/provider/airvpn/provider.go
Normal file
32
internal/provider/airvpn/provider.go
Normal 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
|
||||||
|
}
|
||||||
65
internal/provider/airvpn/updater/api.go
Normal file
65
internal/provider/airvpn/updater/api.go
Normal 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
|
||||||
|
}
|
||||||
97
internal/provider/airvpn/updater/servers.go
Normal file
97
internal/provider/airvpn/updater/servers.go
Normal 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
|
||||||
|
}
|
||||||
15
internal/provider/airvpn/updater/updater.go
Normal file
15
internal/provider/airvpn/updater/updater.go
Normal 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,
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -10,7 +10,10 @@ import (
|
|||||||
"github.com/qdm12/gluetun/internal/updater/resolver"
|
"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 {
|
type Fetcher interface {
|
||||||
FetchServers(ctx context.Context, minServers int) (servers []models.Server, err error)
|
FetchServers(ctx context.Context, minServers int) (servers []models.Server, err error)
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ import (
|
|||||||
"github.com/qdm12/gluetun/internal/configuration/settings"
|
"github.com/qdm12/gluetun/internal/configuration/settings"
|
||||||
"github.com/qdm12/gluetun/internal/constants/providers"
|
"github.com/qdm12/gluetun/internal/constants/providers"
|
||||||
"github.com/qdm12/gluetun/internal/models"
|
"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/common"
|
||||||
"github.com/qdm12/gluetun/internal/provider/custom"
|
"github.com/qdm12/gluetun/internal/provider/custom"
|
||||||
"github.com/qdm12/gluetun/internal/provider/cyberghost"
|
"github.com/qdm12/gluetun/internal/provider/cyberghost"
|
||||||
@@ -58,6 +59,7 @@ func NewProviders(storage Storage, timeNow func() time.Time,
|
|||||||
|
|
||||||
//nolint:lll
|
//nolint:lll
|
||||||
providerNameToProvider := map[string]Provider{
|
providerNameToProvider := map[string]Provider{
|
||||||
|
providers.Airvpn: airvpn.New(storage, randSource, client),
|
||||||
providers.Custom: custom.New(extractor),
|
providers.Custom: custom.New(extractor),
|
||||||
providers.Cyberghost: cyberghost.New(storage, randSource, parallelResolver),
|
providers.Cyberghost: cyberghost.New(storage, randSource, parallelResolver),
|
||||||
providers.Expressvpn: expressvpn.New(storage, randSource, unzipper, updaterWarner, parallelResolver),
|
providers.Expressvpn: expressvpn.New(storage, randSource, unzipper, updaterWarner, parallelResolver),
|
||||||
|
|||||||
@@ -42,6 +42,7 @@ type OpenVPNProviderSettings struct {
|
|||||||
RenegDisabled bool
|
RenegDisabled bool
|
||||||
RenegSec uint16
|
RenegSec uint16
|
||||||
KeyDirection string
|
KeyDirection string
|
||||||
|
SetEnv map[string]string
|
||||||
ExtraLines []string
|
ExtraLines []string
|
||||||
UDPLines []string
|
UDPLines []string
|
||||||
IPv6Lines []string
|
IPv6Lines []string
|
||||||
@@ -168,6 +169,10 @@ func OpenVPNConfig(provider OpenVPNProviderSettings,
|
|||||||
lines.addLines(provider.IPv6Lines)
|
lines.addLines(provider.IPv6Lines)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
for envKey, envValue := range provider.SetEnv {
|
||||||
|
lines.add("setenv", envKey, envValue)
|
||||||
|
}
|
||||||
|
|
||||||
if provider.CA != "" {
|
if provider.CA != "" {
|
||||||
lines.addLines(WrapOpenvpnCA(provider.CA))
|
lines.addLines(WrapOpenvpnCA(provider.CA))
|
||||||
}
|
}
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user