feat(fastestvpn): Wireguard support (#2383)
Credits to @Zerauskire for the initial investigation and @jvanderzande for an initial implementation as well as reviewing the pull request
This commit is contained in:
@@ -60,7 +60,7 @@ Lightweight swiss-knife-like VPN client to multiple VPN service providers
|
|||||||
- 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: **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 **AirVPN**, **Ivpn**, **Mullvad**, **NordVPN**, **Perfect privacy**, **Surfshark** and **Windscribe**
|
- For **AirVPN**, **FastestVPN**, **Ivpn**, **Mullvad**, **NordVPN**, **Perfect privacy**, **Surfshark** and **Windscribe**
|
||||||
- For **ProtonVPN**, **PureVPN**, **Torguard**, **VPN Unlimited** and **WeVPN** using [the custom provider](https://github.com/qdm12/gluetun-wiki/blob/main/setup/providers/custom.md)
|
- For **ProtonVPN**, **PureVPN**, **Torguard**, **VPN Unlimited** and **WeVPN** using [the custom provider](https://github.com/qdm12/gluetun-wiki/blob/main/setup/providers/custom.md)
|
||||||
- For custom Wireguard configurations using [the custom provider](https://github.com/qdm12/gluetun-wiki/blob/main/setup/providers/custom.md)
|
- For custom Wireguard configurations using [the custom provider](https://github.com/qdm12/gluetun-wiki/blob/main/setup/providers/custom.md)
|
||||||
- More in progress, see [#134](https://github.com/qdm12/gluetun/issues/134)
|
- More in progress, see [#134](https://github.com/qdm12/gluetun/issues/134)
|
||||||
|
|||||||
@@ -35,6 +35,7 @@ func (p *Provider) validate(vpnType string, storage Storage) (err error) {
|
|||||||
validNames = []string{
|
validNames = []string{
|
||||||
providers.Airvpn,
|
providers.Airvpn,
|
||||||
providers.Custom,
|
providers.Custom,
|
||||||
|
providers.Fastestvpn,
|
||||||
providers.Ivpn,
|
providers.Ivpn,
|
||||||
providers.Mullvad,
|
providers.Mullvad,
|
||||||
providers.Nordvpn,
|
providers.Nordvpn,
|
||||||
|
|||||||
@@ -58,6 +58,7 @@ func (w Wireguard) validate(vpnProvider string, ipv6Supported bool) (err error)
|
|||||||
if !helpers.IsOneOf(vpnProvider,
|
if !helpers.IsOneOf(vpnProvider,
|
||||||
providers.Airvpn,
|
providers.Airvpn,
|
||||||
providers.Custom,
|
providers.Custom,
|
||||||
|
providers.Fastestvpn,
|
||||||
providers.Ivpn,
|
providers.Ivpn,
|
||||||
providers.Mullvad,
|
providers.Mullvad,
|
||||||
providers.Nordvpn,
|
providers.Nordvpn,
|
||||||
|
|||||||
@@ -38,8 +38,9 @@ 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.Airvpn, providers.Ivpn, providers.Mullvad,
|
case providers.Airvpn, providers.Fastestvpn, providers.Ivpn,
|
||||||
providers.Nordvpn, providers.Surfshark, providers.Windscribe:
|
providers.Mullvad, providers.Nordvpn, providers.Surfshark,
|
||||||
|
providers.Windscribe:
|
||||||
// endpoint IP addresses are baked in
|
// endpoint IP addresses are baked in
|
||||||
case providers.Custom:
|
case providers.Custom:
|
||||||
if !w.EndpointIP.IsValid() || w.EndpointIP.IsUnspecified() {
|
if !w.EndpointIP.IsValid() || w.EndpointIP.IsUnspecified() {
|
||||||
@@ -56,7 +57,7 @@ func (w WireguardSelection) validate(vpnProvider string) (err error) {
|
|||||||
return fmt.Errorf("%w", ErrWireguardEndpointPortNotSet)
|
return fmt.Errorf("%w", ErrWireguardEndpointPortNotSet)
|
||||||
}
|
}
|
||||||
// EndpointPort cannot be set
|
// EndpointPort cannot be set
|
||||||
case providers.Surfshark, providers.Nordvpn:
|
case providers.Fastestvpn, providers.Surfshark, providers.Nordvpn:
|
||||||
if *w.EndpointPort != 0 {
|
if *w.EndpointPort != 0 {
|
||||||
return fmt.Errorf("%w", ErrWireguardEndpointPortSet)
|
return fmt.Errorf("%w", ErrWireguardEndpointPortSet)
|
||||||
}
|
}
|
||||||
@@ -89,7 +90,7 @@ func (w WireguardSelection) validate(vpnProvider string) (err error) {
|
|||||||
|
|
||||||
// Validate PublicKey
|
// Validate PublicKey
|
||||||
switch vpnProvider {
|
switch vpnProvider {
|
||||||
case providers.Ivpn, providers.Mullvad,
|
case providers.Fastestvpn, providers.Ivpn, providers.Mullvad,
|
||||||
providers.Surfshark, providers.Windscribe:
|
providers.Surfshark, providers.Windscribe:
|
||||||
// public keys are baked in
|
// public keys are baked in
|
||||||
case providers.Custom:
|
case providers.Custom:
|
||||||
|
|||||||
@@ -113,7 +113,7 @@ func getMarkdownHeaders(vpnProvider string) (headers []string) {
|
|||||||
case providers.Expressvpn:
|
case providers.Expressvpn:
|
||||||
return []string{countryHeader, cityHeader, hostnameHeader, tcpHeader, udpHeader}
|
return []string{countryHeader, cityHeader, hostnameHeader, tcpHeader, udpHeader}
|
||||||
case providers.Fastestvpn:
|
case providers.Fastestvpn:
|
||||||
return []string{countryHeader, hostnameHeader, tcpHeader, udpHeader}
|
return []string{countryHeader, hostnameHeader, vpnHeader, tcpHeader, udpHeader}
|
||||||
case providers.HideMyAss:
|
case providers.HideMyAss:
|
||||||
return []string{countryHeader, regionHeader, cityHeader, hostnameHeader, tcpHeader, udpHeader}
|
return []string{countryHeader, regionHeader, cityHeader, hostnameHeader, tcpHeader, udpHeader}
|
||||||
case providers.Ipvanish:
|
case providers.Ipvanish:
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import (
|
|||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/qdm12/gluetun/internal/constants/providers"
|
"github.com/qdm12/gluetun/internal/constants/providers"
|
||||||
|
"github.com/qdm12/gluetun/internal/constants/vpn"
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -32,14 +33,14 @@ func Test_Servers_ToMarkdown(t *testing.T) {
|
|||||||
provider: providers.Fastestvpn,
|
provider: providers.Fastestvpn,
|
||||||
servers: Servers{
|
servers: Servers{
|
||||||
Servers: []Server{
|
Servers: []Server{
|
||||||
{Country: "a", Hostname: "xa", TCP: true},
|
{Country: "a", Hostname: "xa", VPN: vpn.OpenVPN, TCP: true},
|
||||||
{Country: "b", Hostname: "xb", UDP: true},
|
{Country: "b", Hostname: "xb", VPN: vpn.OpenVPN, UDP: true},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
expectedMarkdown: "| Country | Hostname | TCP | UDP |\n" +
|
expectedMarkdown: "| Country | Hostname | VPN | TCP | UDP |\n" +
|
||||||
"| --- | --- | --- | --- |\n" +
|
"| --- | --- | --- | --- | --- |\n" +
|
||||||
"| a | `xa` | ✅ | ❌ |\n" +
|
"| a | `xa` | openvpn | ✅ | ❌ |\n" +
|
||||||
"| b | `xb` | ❌ | ✅ |\n",
|
"| b | `xb` | openvpn | ❌ | ✅ |\n",
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ import (
|
|||||||
|
|
||||||
func (p *Provider) GetConnection(selection settings.ServerSelection, ipv6Supported bool) (
|
func (p *Provider) GetConnection(selection settings.ServerSelection, ipv6Supported bool) (
|
||||||
connection models.Connection, err error) {
|
connection models.Connection, err error) {
|
||||||
defaults := utils.NewConnectionDefaults(4443, 4443, 0) //nolint:gomnd
|
defaults := utils.NewConnectionDefaults(4443, 4443, 51820) //nolint:gomnd
|
||||||
return utils.GetConnection(p.Name(),
|
return utils.GetConnection(p.Name(),
|
||||||
p.storage, selection, defaults, ipv6Supported, p.randSource)
|
p.storage, selection, defaults, ipv6Supported, p.randSource)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,32 +7,45 @@ import (
|
|||||||
"github.com/qdm12/gluetun/internal/models"
|
"github.com/qdm12/gluetun/internal/models"
|
||||||
)
|
)
|
||||||
|
|
||||||
type hostToServer map[string]models.Server
|
type hostToServerData map[string]serverData
|
||||||
|
|
||||||
func (hts hostToServer) add(host, country, city string, tcp, udp bool) {
|
type serverData struct {
|
||||||
server, ok := hts[host]
|
openvpn bool
|
||||||
if !ok {
|
wireguard bool
|
||||||
server.VPN = vpn.OpenVPN
|
country string
|
||||||
server.Hostname = host
|
city string
|
||||||
server.Country = country
|
openvpnUDP bool
|
||||||
server.City = city
|
openvpnTCP bool
|
||||||
|
ips []netip.Addr
|
||||||
|
}
|
||||||
|
|
||||||
|
func (hts hostToServerData) add(host, vpnType, country, city string, tcp, udp bool) {
|
||||||
|
serverData, ok := hts[host]
|
||||||
|
switch vpnType {
|
||||||
|
case vpn.OpenVPN:
|
||||||
|
serverData.openvpn = true
|
||||||
|
serverData.openvpnTCP = serverData.openvpnTCP || tcp
|
||||||
|
serverData.openvpnUDP = serverData.openvpnUDP || udp
|
||||||
|
case vpn.Wireguard:
|
||||||
|
serverData.wireguard = true
|
||||||
|
default:
|
||||||
|
panic("protocol not supported")
|
||||||
}
|
}
|
||||||
if city != "" {
|
|
||||||
|
if !ok {
|
||||||
|
serverData.country = country
|
||||||
|
serverData.city = city
|
||||||
|
} else if city != "" {
|
||||||
// some servers are listed without the city although
|
// some servers are listed without the city although
|
||||||
// they are also listed with the city described, so update
|
// they are also listed with the city described, so update
|
||||||
// the city field.
|
// the city field.
|
||||||
server.City = city
|
serverData.city = city
|
||||||
}
|
}
|
||||||
if tcp {
|
|
||||||
server.TCP = true
|
hts[host] = serverData
|
||||||
}
|
|
||||||
if udp {
|
|
||||||
server.UDP = true
|
|
||||||
}
|
|
||||||
hts[host] = server
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (hts hostToServer) toHostsSlice() (hosts []string) {
|
func (hts hostToServerData) toHostsSlice() (hosts []string) {
|
||||||
hosts = make([]string, 0, len(hts))
|
hosts = make([]string, 0, len(hts))
|
||||||
for host := range hts {
|
for host := range hts {
|
||||||
hosts = append(hosts, host)
|
hosts = append(hosts, host)
|
||||||
@@ -40,23 +53,41 @@ func (hts hostToServer) toHostsSlice() (hosts []string) {
|
|||||||
return hosts
|
return hosts
|
||||||
}
|
}
|
||||||
|
|
||||||
func (hts hostToServer) adaptWithIPs(hostToIPs map[string][]netip.Addr) {
|
func (hts hostToServerData) adaptWithIPs(hostToIPs map[string][]netip.Addr) {
|
||||||
for host, IPs := range hostToIPs {
|
for host, serverData := range hts {
|
||||||
server := hts[host]
|
ips := hostToIPs[host]
|
||||||
server.IPs = IPs
|
if len(ips) == 0 {
|
||||||
hts[host] = server
|
|
||||||
}
|
|
||||||
for host, server := range hts {
|
|
||||||
if len(server.IPs) == 0 {
|
|
||||||
delete(hts, host)
|
delete(hts, host)
|
||||||
|
continue
|
||||||
}
|
}
|
||||||
|
serverData.ips = ips
|
||||||
|
hts[host] = serverData
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (hts hostToServer) toServersSlice() (servers []models.Server) {
|
func (hts hostToServerData) toServersSlice() (servers []models.Server) {
|
||||||
servers = make([]models.Server, 0, len(hts))
|
servers = make([]models.Server, 0, 2*len(hts)) //nolint:gomnd
|
||||||
for _, server := range hts {
|
for hostname, serverData := range hts {
|
||||||
servers = append(servers, server)
|
baseServer := models.Server{
|
||||||
|
Hostname: hostname,
|
||||||
|
Country: serverData.country,
|
||||||
|
City: serverData.city,
|
||||||
|
IPs: serverData.ips,
|
||||||
|
}
|
||||||
|
if serverData.openvpn {
|
||||||
|
openvpnServer := baseServer
|
||||||
|
openvpnServer.VPN = vpn.OpenVPN
|
||||||
|
openvpnServer.TCP = serverData.openvpnTCP
|
||||||
|
openvpnServer.UDP = serverData.openvpnUDP
|
||||||
|
servers = append(servers, openvpnServer)
|
||||||
|
}
|
||||||
|
if serverData.wireguard {
|
||||||
|
wireguardServer := baseServer
|
||||||
|
wireguardServer.VPN = vpn.Wireguard
|
||||||
|
const wireguardPublicKey = "658QxufMbjOTmB61Z7f+c7Rjg7oqWLnepTalqBERjF0="
|
||||||
|
wireguardServer.WgPubKey = wireguardPublicKey
|
||||||
|
servers = append(servers, wireguardServer)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return servers
|
return servers
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,14 +5,15 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"sort"
|
"sort"
|
||||||
|
|
||||||
|
"github.com/qdm12/gluetun/internal/constants/vpn"
|
||||||
"github.com/qdm12/gluetun/internal/models"
|
"github.com/qdm12/gluetun/internal/models"
|
||||||
"github.com/qdm12/gluetun/internal/provider/common"
|
"github.com/qdm12/gluetun/internal/provider/common"
|
||||||
)
|
)
|
||||||
|
|
||||||
func (u *Updater) FetchServers(ctx context.Context, minServers int) (
|
func (u *Updater) FetchServers(ctx context.Context, minServers int) (
|
||||||
servers []models.Server, err error) {
|
servers []models.Server, err error) {
|
||||||
protocols := []string{"tcp", "udp"}
|
protocols := []string{"ikev2", "tcp", "udp"}
|
||||||
hts := make(hostToServer)
|
hts := make(hostToServerData)
|
||||||
|
|
||||||
for _, protocol := range protocols {
|
for _, protocol := range protocols {
|
||||||
apiServers, err := fetchAPIServers(ctx, u.client, protocol)
|
apiServers, err := fetchAPIServers(ctx, u.client, protocol)
|
||||||
@@ -20,17 +21,20 @@ func (u *Updater) FetchServers(ctx context.Context, minServers int) (
|
|||||||
return nil, fmt.Errorf("fetching %s servers from API: %w", protocol, err)
|
return nil, fmt.Errorf("fetching %s servers from API: %w", protocol, err)
|
||||||
}
|
}
|
||||||
for _, apiServer := range apiServers {
|
for _, apiServer := range apiServers {
|
||||||
|
// all hostnames from the protocols TCP, UDP and IKEV2 support Wireguard
|
||||||
|
// per https://github.com/qdm12/gluetun-wiki/issues/76#issuecomment-2125420536
|
||||||
|
const wgTCP, wgUDP = false, false // ignored
|
||||||
|
hts.add(apiServer.hostname, vpn.Wireguard, apiServer.country, apiServer.city, wgTCP, wgUDP)
|
||||||
|
|
||||||
tcp := protocol == "tcp"
|
tcp := protocol == "tcp"
|
||||||
udp := protocol == "udp"
|
udp := protocol == "udp"
|
||||||
hts.add(apiServer.hostname, apiServer.country, apiServer.city, tcp, udp)
|
if !tcp && !udp { // not an OpenVPN protocol, for example ikev2
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
hts.add(apiServer.hostname, vpn.OpenVPN, apiServer.country, apiServer.city, tcp, udp)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(hts) < minServers {
|
|
||||||
return nil, fmt.Errorf("%w: %d and expected at least %d",
|
|
||||||
common.ErrNotEnoughServers, len(hts), minServers)
|
|
||||||
}
|
|
||||||
|
|
||||||
hosts := hts.toHostsSlice()
|
hosts := hts.toHostsSlice()
|
||||||
resolveSettings := parallelResolverSettings(hosts)
|
resolveSettings := parallelResolverSettings(hosts)
|
||||||
hostToIPs, warnings, err := u.parallelResolver.Resolve(ctx, resolveSettings)
|
hostToIPs, warnings, err := u.parallelResolver.Resolve(ctx, resolveSettings)
|
||||||
@@ -41,15 +45,15 @@ func (u *Updater) FetchServers(ctx context.Context, minServers int) (
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(hostToIPs) < minServers {
|
|
||||||
return nil, fmt.Errorf("%w: %d and expected at least %d",
|
|
||||||
common.ErrNotEnoughServers, len(servers), minServers)
|
|
||||||
}
|
|
||||||
|
|
||||||
hts.adaptWithIPs(hostToIPs)
|
hts.adaptWithIPs(hostToIPs)
|
||||||
|
|
||||||
servers = hts.toServersSlice()
|
servers = hts.toServersSlice()
|
||||||
|
|
||||||
|
if len(servers) < minServers {
|
||||||
|
return nil, fmt.Errorf("%w: %d and expected at least %d",
|
||||||
|
common.ErrNotEnoughServers, len(servers), minServers)
|
||||||
|
}
|
||||||
|
|
||||||
sort.Sort(models.SortableServers(servers))
|
sort.Sort(models.SortableServers(servers))
|
||||||
|
|
||||||
return servers, nil
|
return servers, nil
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user