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:
Quentin McGaw 2024-07-31 16:16:50 +02:00 committed by GitHub
parent 7bc2972b27
commit 13ffffb157
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
10 changed files with 800 additions and 63 deletions

View File

@ -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 OpenVPN for all providers listed
- 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 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)

View File

@ -35,6 +35,7 @@ func (p *Provider) validate(vpnType string, storage Storage) (err error) {
validNames = []string{
providers.Airvpn,
providers.Custom,
providers.Fastestvpn,
providers.Ivpn,
providers.Mullvad,
providers.Nordvpn,

View File

@ -58,6 +58,7 @@ func (w Wireguard) validate(vpnProvider string, ipv6Supported bool) (err error)
if !helpers.IsOneOf(vpnProvider,
providers.Airvpn,
providers.Custom,
providers.Fastestvpn,
providers.Ivpn,
providers.Mullvad,
providers.Nordvpn,

View File

@ -38,8 +38,9 @@ type WireguardSelection struct {
func (w WireguardSelection) validate(vpnProvider string) (err error) {
// Validate EndpointIP
switch vpnProvider {
case providers.Airvpn, providers.Ivpn, providers.Mullvad,
providers.Nordvpn, providers.Surfshark, providers.Windscribe:
case providers.Airvpn, providers.Fastestvpn, providers.Ivpn,
providers.Mullvad, providers.Nordvpn, providers.Surfshark,
providers.Windscribe:
// endpoint IP addresses are baked in
case providers.Custom:
if !w.EndpointIP.IsValid() || w.EndpointIP.IsUnspecified() {
@ -56,7 +57,7 @@ func (w WireguardSelection) validate(vpnProvider string) (err error) {
return fmt.Errorf("%w", ErrWireguardEndpointPortNotSet)
}
// EndpointPort cannot be set
case providers.Surfshark, providers.Nordvpn:
case providers.Fastestvpn, providers.Surfshark, providers.Nordvpn:
if *w.EndpointPort != 0 {
return fmt.Errorf("%w", ErrWireguardEndpointPortSet)
}
@ -89,7 +90,7 @@ func (w WireguardSelection) validate(vpnProvider string) (err error) {
// Validate PublicKey
switch vpnProvider {
case providers.Ivpn, providers.Mullvad,
case providers.Fastestvpn, providers.Ivpn, providers.Mullvad,
providers.Surfshark, providers.Windscribe:
// public keys are baked in
case providers.Custom:

View File

@ -113,7 +113,7 @@ func getMarkdownHeaders(vpnProvider string) (headers []string) {
case providers.Expressvpn:
return []string{countryHeader, cityHeader, hostnameHeader, tcpHeader, udpHeader}
case providers.Fastestvpn:
return []string{countryHeader, hostnameHeader, tcpHeader, udpHeader}
return []string{countryHeader, hostnameHeader, vpnHeader, tcpHeader, udpHeader}
case providers.HideMyAss:
return []string{countryHeader, regionHeader, cityHeader, hostnameHeader, tcpHeader, udpHeader}
case providers.Ipvanish:

View File

@ -4,6 +4,7 @@ import (
"testing"
"github.com/qdm12/gluetun/internal/constants/providers"
"github.com/qdm12/gluetun/internal/constants/vpn"
"github.com/stretchr/testify/assert"
)
@ -32,14 +33,14 @@ func Test_Servers_ToMarkdown(t *testing.T) {
provider: providers.Fastestvpn,
servers: Servers{
Servers: []Server{
{Country: "a", Hostname: "xa", TCP: true},
{Country: "b", Hostname: "xb", UDP: true},
{Country: "a", Hostname: "xa", VPN: vpn.OpenVPN, TCP: true},
{Country: "b", Hostname: "xb", VPN: vpn.OpenVPN, UDP: true},
},
},
expectedMarkdown: "| Country | Hostname | TCP | UDP |\n" +
"| --- | --- | --- | --- |\n" +
"| a | `xa` | ✅ | ❌ |\n" +
"| b | `xb` | ❌ | ✅ |\n",
expectedMarkdown: "| Country | Hostname | VPN | TCP | UDP |\n" +
"| --- | --- | --- | --- | --- |\n" +
"| a | `xa` | openvpn | ✅ | ❌ |\n" +
"| b | `xb` | openvpn | ❌ | ✅ |\n",
},
}

View File

@ -8,7 +8,7 @@ import (
func (p *Provider) GetConnection(selection settings.ServerSelection, ipv6Supported bool) (
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(),
p.storage, selection, defaults, ipv6Supported, p.randSource)
}

View File

@ -7,32 +7,45 @@ import (
"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) {
server, ok := hts[host]
if !ok {
server.VPN = vpn.OpenVPN
server.Hostname = host
server.Country = country
server.City = city
type serverData struct {
openvpn bool
wireguard bool
country string
city string
openvpnUDP bool
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
// they are also listed with the city described, so update
// the city field.
server.City = city
serverData.city = city
}
if tcp {
server.TCP = true
}
if udp {
server.UDP = true
}
hts[host] = server
hts[host] = serverData
}
func (hts hostToServer) toHostsSlice() (hosts []string) {
func (hts hostToServerData) toHostsSlice() (hosts []string) {
hosts = make([]string, 0, len(hts))
for host := range hts {
hosts = append(hosts, host)
@ -40,23 +53,41 @@ func (hts hostToServer) toHostsSlice() (hosts []string) {
return hosts
}
func (hts hostToServer) adaptWithIPs(hostToIPs map[string][]netip.Addr) {
for host, IPs := range hostToIPs {
server := hts[host]
server.IPs = IPs
hts[host] = server
}
for host, server := range hts {
if len(server.IPs) == 0 {
func (hts hostToServerData) adaptWithIPs(hostToIPs map[string][]netip.Addr) {
for host, serverData := range hts {
ips := hostToIPs[host]
if len(ips) == 0 {
delete(hts, host)
continue
}
serverData.ips = ips
hts[host] = serverData
}
}
func (hts hostToServer) toServersSlice() (servers []models.Server) {
servers = make([]models.Server, 0, len(hts))
for _, server := range hts {
servers = append(servers, server)
func (hts hostToServerData) toServersSlice() (servers []models.Server) {
servers = make([]models.Server, 0, 2*len(hts)) //nolint:gomnd
for hostname, serverData := range hts {
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
}

View File

@ -5,14 +5,15 @@ import (
"fmt"
"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) {
protocols := []string{"tcp", "udp"}
hts := make(hostToServer)
protocols := []string{"ikev2", "tcp", "udp"}
hts := make(hostToServerData)
for _, protocol := range protocols {
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)
}
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"
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()
resolveSettings := parallelResolverSettings(hosts)
hostToIPs, warnings, err := u.parallelResolver.Resolve(ctx, resolveSettings)
@ -41,15 +45,15 @@ func (u *Updater) FetchServers(ctx context.Context, minServers int) (
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)
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))
return servers, nil

File diff suppressed because it is too large Load Diff