Compare commits

..

1 Commits

Author SHA1 Message Date
Quentin McGaw
1a6e8d74d6 wip 2024-08-01 07:51:35 +00:00
36 changed files with 267 additions and 9788 deletions

View File

@@ -37,12 +37,12 @@
"go.useLanguageServer": true,
"[go]": {
"editor.codeActionsOnSave": {
"source.organizeImports": "explicit"
"source.organizeImports": true
}
},
"[go.mod]": {
"editor.codeActionsOnSave": {
"source.organizeImports": "explicit"
"source.organizeImports": true
}
},
"gopls": {

View File

@@ -1,10 +1,10 @@
blank_issues_enabled: false
contact_links:
- name: Report a Wiki issue
url: https://github.com/qdm12/gluetun-wiki/issues/new/choose
url: https://github.com/qdm12/gluetun-wiki/issues/new
about: Please create an issue on the gluetun-wiki repository.
- name: Configuration help?
url: https://github.com/qdm12/gluetun/discussions/new/choose
url: https://github.com/qdm12/gluetun/discussions/new
about: Please create a Github discussion.
- name: Unraid template issue
url: https://github.com/qdm12/gluetun/discussions/550

8
.github/labels.yml vendored
View File

@@ -3,9 +3,6 @@
- name: "Status: 🔴 Blocked"
color: "f7d692"
description: "Blocked by another issue or pull request"
- name: "Status: 📌 Before next release"
color: "f7d692"
description: "Has to be done before the next release"
- name: "Status: 🔒 After next release"
color: "f7d692"
description: "Will be done after the next release"
@@ -39,8 +36,6 @@
# VPN providers
- name: "☁️ AirVPN"
color: "cfe8d4"
- name: "☁️ Custom"
color: "cfe8d4"
- name: "☁️ Cyberghost"
color: "cfe8d4"
- name: "☁️ HideMyAss"
@@ -96,9 +91,6 @@
- name: "Category: Maintenance ⛓️"
description: "Anything related to code or other maintenance"
color: "ffc7ea"
- name: "Category: Logs 📚"
description: "Something to change in logs"
color: "ffc7ea"
- name: "Category: Good idea 🎯"
description: "This is a good idea, judged by the maintainers"
color: "ffc7ea"

View File

@@ -120,3 +120,9 @@ linters:
- wastedassign
- whitespace
- zerologlint
run:
skip-dirs:
- .devcontainer
- .github
- doc

View File

@@ -60,8 +60,8 @@ 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**, **FastestVPN**, **Ivpn**, **Mullvad**, **NordVPN**, **Perfect privacy**, **ProtonVPN**, **Surfshark** and **Windscribe**
- For **Cyberghost**, **Private Internet Access**, **PrivateVPN**, **PureVPN**, **Torguard**, **VPN Unlimited**, **VyprVPN** and **WeVPN** using [the custom provider](https://github.com/qdm12/gluetun-wiki/blob/main/setup/providers/custom.md)
- 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)
- DNS over TLS baked in with service provider(s) of your choice
@@ -73,7 +73,7 @@ Lightweight swiss-knife-like VPN client to multiple VPN service providers
- [Connect other containers to it](https://github.com/qdm12/gluetun-wiki/blob/main/setup/connect-a-container-to-gluetun.md)
- [Connect LAN devices to it](https://github.com/qdm12/gluetun-wiki/blob/main/setup/connect-a-lan-device-to-gluetun.md)
- Compatible with amd64, i686 (32 bit), **ARM** 64 bit, ARM 32 bit v6 and v7, and even ppc64le 🎆
- Custom VPN server side port forwarding for [Perfect Privacy](https://github.com/qdm12/gluetun-wiki/blob/main/setup/providers/perfect-privacy.md#vpn-server-port-forwarding), [Private Internet Access](https://github.com/qdm12/gluetun-wiki/blob/main/setup/providers/private-internet-access.md#vpn-server-port-forwarding) and [ProtonVPN](https://github.com/qdm12/gluetun-wiki/blob/main/setup/providers/protonvpn.md#vpn-server-port-forwarding)
- [Custom VPN server side port forwarding for Private Internet Access](https://github.com/qdm12/gluetun-wiki/blob/main/setup/providers/private-internet-access.md#vpn-server-port-forwarding)
- Possibility of split horizon DNS by selecting multiple DNS over TLS providers
- Unbound subprogram drops root privileges once launched
- Can work as a Kubernetes sidecar container, thanks @rorph
@@ -84,7 +84,7 @@ Lightweight swiss-knife-like VPN client to multiple VPN service providers
Go to the [Wiki](https://github.com/qdm12/gluetun-wiki)!
[🐛 Found a bug in the Wiki?!](https://github.com/qdm12/gluetun-wiki/issues/new/choose)
[🐛 Found a bug in the Wiki?!](https://github.com/qdm12/gluetun-wiki/issues/new)
Here's a docker-compose.yml for the laziest:

View File

@@ -176,7 +176,7 @@ func _main(ctx context.Context, buildInfo models.BuildInformation,
Emails: []string{"quentin.mcgaw@gmail.com"},
Version: buildInfo.Version,
Commit: buildInfo.Commit,
Created: buildInfo.Created,
BuildDate: buildInfo.Created,
Announcement: "Wiki moved to https://github.com/qdm12/gluetun-wiki",
AnnounceExp: announcementExp,
// Sponsor information

4
go.mod
View File

@@ -3,7 +3,7 @@ module github.com/qdm12/gluetun
go 1.22
require (
github.com/breml/rootcerts v0.2.17
github.com/breml/rootcerts v0.2.16
github.com/fatih/color v1.17.0
github.com/golang/mock v1.6.0
github.com/klauspost/compress v1.17.8
@@ -12,7 +12,7 @@ require (
github.com/qdm12/golibs v0.0.0-20210822203818-5c568b0777b6
github.com/qdm12/gosettings v0.4.2
github.com/qdm12/goshutdown v0.3.0
github.com/qdm12/gosplash v0.2.0
github.com/qdm12/gosplash v0.1.0
github.com/qdm12/gotree v0.2.0
github.com/qdm12/log v0.1.0
github.com/qdm12/ss-server v0.6.0

10
go.sum
View File

@@ -4,8 +4,8 @@ github.com/alcortesm/tgz v0.0.0-20161220082320-9c5fe88206d7/go.mod h1:6zEj6s6u/g
github.com/anmitsu/go-shlex v0.0.0-20161002113705-648efa622239/go.mod h1:2FmKhYUyUczH0OGQWaF5ceTx0UBShxjsH6f8oGKYe2c=
github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5/go.mod h1:wHh0iHkYZB8zMSxRWpUBQtwG5a7fFgvEO+odwuTv2gs=
github.com/asaskevich/govalidator v0.0.0-20180720115003-f9ffefc3facf/go.mod h1:lB+ZfQJz7igIIfQNfa7Ml4HSf2uFQQRzpGGRXenZAgY=
github.com/breml/rootcerts v0.2.17 h1:0/M2BE2Apw0qEJCXDOkaiu7d5Sx5ObNfe1BkImJ4u1I=
github.com/breml/rootcerts v0.2.17/go.mod h1:S/PKh+4d1HUn4HQovEB8hPJZO6pUZYrIhmXBhsegfXw=
github.com/breml/rootcerts v0.2.16 h1:yN1TGvicfHx8dKz3OQRIrx/5nE/iN3XT1ibqGbd6urc=
github.com/breml/rootcerts v0.2.16/go.mod h1:S/PKh+4d1HUn4HQovEB8hPJZO6pUZYrIhmXBhsegfXw=
github.com/creack/pty v1.1.7/go.mod h1:lj5s0c3V2DBrqTV7llrYr5NG6My20zk30Fl46Y7DoTY=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
@@ -93,12 +93,14 @@ github.com/qdm12/golibs v0.0.0-20210603202746-e5494e9c2ebb/go.mod h1:15RBzkun0i8
github.com/qdm12/golibs v0.0.0-20210723175634-a75ca7fd74c2/go.mod h1:6aRbg4Z/bTbm9JfxsGXfWKHi7zsOvPfUTK1S5HuAFKg=
github.com/qdm12/golibs v0.0.0-20210822203818-5c568b0777b6 h1:bge5AL7cjHJMPz+5IOz5yF01q/l8No6+lIEBieA8gMg=
github.com/qdm12/golibs v0.0.0-20210822203818-5c568b0777b6/go.mod h1:6aRbg4Z/bTbm9JfxsGXfWKHi7zsOvPfUTK1S5HuAFKg=
github.com/qdm12/gosettings v0.4.1 h1:c7+14jO1Y2kFXBCUfS2+QE2NgwTKfzcdJzGEFRItCI8=
github.com/qdm12/gosettings v0.4.1/go.mod h1:uItKwGXibJp2pQ0am6MBKilpjfvYTGiH+zXHd10jFj8=
github.com/qdm12/gosettings v0.4.2 h1:Gb39NScPr7OQV+oy0o1OD7A121udITDJuUGa7ljDF58=
github.com/qdm12/gosettings v0.4.2/go.mod h1:CPrt2YC4UsURTrslmhxocVhMCW03lIrqdH2hzIf5prg=
github.com/qdm12/goshutdown v0.3.0 h1:pqBpJkdwlZlfTEx4QHtS8u8CXx6pG0fVo6S1N0MpSEM=
github.com/qdm12/goshutdown v0.3.0/go.mod h1:EqZ46No00kCTZ5qzdd3qIzY6ayhMt24QI8Mh8LVQYmM=
github.com/qdm12/gosplash v0.2.0 h1:DOxCEizbW6ZG+FgpH2oK1atT6bM8MHL9GZ2ywSS4zZY=
github.com/qdm12/gosplash v0.2.0/go.mod h1:k+1PzhO0th9cpX4q2Nneu4xTsndXqrM/x7NTIYmJ4jo=
github.com/qdm12/gosplash v0.1.0 h1:Sfl+zIjFZFP7b0iqf2l5UkmEY97XBnaKkH3FNY6Gf7g=
github.com/qdm12/gosplash v0.1.0/go.mod h1:+A3fWW4/rUeDXhY3ieBzwghKdnIPFJgD8K3qQkenJlw=
github.com/qdm12/gotree v0.2.0 h1:+58ltxkNLUyHtATFereAcOjBVfY6ETqRex8XK90Fb/c=
github.com/qdm12/gotree v0.2.0/go.mod h1:1SdFaqKZuI46U1apbXIf25pDMNnrPuYLEqMF/qL4lY4=
github.com/qdm12/log v0.1.0 h1:jYBd/xscHYpblzZAd2kjZp2YmuYHjAAfbTViJWxoPTw=

View File

@@ -39,7 +39,6 @@ func (p *Provider) validate(vpnType string, storage Storage) (err error) {
providers.Ivpn,
providers.Mullvad,
providers.Nordvpn,
providers.Protonvpn,
providers.Surfshark,
providers.Windscribe,
}

View File

@@ -62,7 +62,6 @@ func (w Wireguard) validate(vpnProvider string, ipv6Supported bool) (err error)
providers.Ivpn,
providers.Mullvad,
providers.Nordvpn,
providers.Protonvpn,
providers.Surfshark,
providers.Windscribe,
) {
@@ -174,15 +173,10 @@ func (w *Wireguard) overrideWith(other Wireguard) {
func (w *Wireguard) setDefaults(vpnProvider string) {
w.PrivateKey = gosettings.DefaultPointer(w.PrivateKey, "")
w.PreSharedKey = gosettings.DefaultPointer(w.PreSharedKey, "")
switch vpnProvider {
case providers.Nordvpn:
if vpnProvider == providers.Nordvpn {
defaultNordVPNAddress := netip.AddrFrom4([4]byte{10, 5, 0, 2})
defaultNordVPNPrefix := netip.PrefixFrom(defaultNordVPNAddress, defaultNordVPNAddress.BitLen())
w.Addresses = gosettings.DefaultSlice(w.Addresses, []netip.Prefix{defaultNordVPNPrefix})
case providers.Protonvpn:
defaultAddress := netip.AddrFrom4([4]byte{10, 2, 0, 2})
defaultPrefix := netip.PrefixFrom(defaultAddress, defaultAddress.BitLen())
w.Addresses = gosettings.DefaultSlice(w.Addresses, []netip.Prefix{defaultPrefix})
}
defaultAllowedIPs := []netip.Prefix{
netip.PrefixFrom(netip.IPv4Unspecified(), 0),

View File

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

View File

@@ -3,4 +3,13 @@ package vpn
const (
OpenVPN = "openvpn"
Wireguard = "wireguard"
Both = "openvpn+wireguard"
)
func IsWireguard(s string) bool {
return s == Wireguard || s == Both
}
func IsOpenVPN(s string) bool {
return s == OpenVPN || s == Both
}

View File

@@ -15,7 +15,7 @@ import (
// empty string path is returned.
func findIP6tablesSupported(ctx context.Context, runner command.Runner) (
ip6tablesPath string, err error) {
ip6tablesPath, err = checkIptablesSupport(ctx, runner, "ip6tables", "ip6tables-nft", "ip6tables-legacy")
ip6tablesPath, err = checkIptablesSupport(ctx, runner, "ip6tables-legacy", "ip6tables", "ip6tables-nft")
if errors.Is(err, ErrIPTablesNotSupported) {
return "", nil
} else if err != nil {

View File

@@ -210,14 +210,10 @@ func (c *Config) redirectPort(ctx context.Context, intf string,
}
err = c.runIptablesInstructions(ctx, []string{
fmt.Sprintf("-t nat %s PREROUTING %s -p tcp --dport %d -j REDIRECT --to-ports %d",
fmt.Sprintf("-t nat %s PREROUTING %s -d 127.0.0.1 -p tcp --dport %d -j REDIRECT --to-ports %d",
appendOrDelete(remove), interfaceFlag, sourcePort, destinationPort),
fmt.Sprintf("%s INPUT %s -p tcp -m tcp --dport %d -j ACCEPT",
appendOrDelete(remove), interfaceFlag, destinationPort),
fmt.Sprintf("-t nat %s PREROUTING %s -p udp --dport %d -j REDIRECT --to-ports %d",
fmt.Sprintf("-t nat %s PREROUTING %s -d 127.0.0.1 -p udp --dport %d -j REDIRECT --to-ports %d",
appendOrDelete(remove), interfaceFlag, sourcePort, destinationPort),
fmt.Sprintf("%s INPUT %s -p udp -m udp --dport %d -j ACCEPT",
appendOrDelete(remove), interfaceFlag, destinationPort),
})
if err != nil {
return fmt.Errorf("redirecting IPv4 source port %d to destination port %d on interface %s: %w",
@@ -225,14 +221,10 @@ func (c *Config) redirectPort(ctx context.Context, intf string,
}
err = c.runIP6tablesInstructions(ctx, []string{
fmt.Sprintf("-t nat %s PREROUTING %s -p tcp --dport %d -j REDIRECT --to-ports %d",
fmt.Sprintf("-t nat %s PREROUTING %s -d ::1 -p tcp --dport %d -j REDIRECT --to-ports %d",
appendOrDelete(remove), interfaceFlag, sourcePort, destinationPort),
fmt.Sprintf("%s INPUT %s -p tcp -m tcp --dport %d -j ACCEPT",
appendOrDelete(remove), interfaceFlag, destinationPort),
fmt.Sprintf("-t nat %s PREROUTING %s -p udp --dport %d -j REDIRECT --to-ports %d",
fmt.Sprintf("-t nat %s PREROUTING %s -d ::1 -p udp --dport %d -j REDIRECT --to-ports %d",
appendOrDelete(remove), interfaceFlag, sourcePort, destinationPort),
fmt.Sprintf("%s INPUT %s -p udp -m udp --dport %d -j ACCEPT",
appendOrDelete(remove), interfaceFlag, destinationPort),
})
if err != nil {
return fmt.Errorf("redirecting IPv6 source port %d to destination port %d on interface %s: %w",

View File

@@ -21,7 +21,7 @@ type Connection struct {
PubKey string `json:"pubkey"`
// ServerName is used for PIA for port forwarding
ServerName string `json:"server_name,omitempty"`
// PortForward is used for PIA and ProtonVPN for port forwarding
// PortForward is used for PIA for port forwarding
PortForward bool `json:"port_forward"`
}

View File

@@ -125,7 +125,7 @@ func getMarkdownHeaders(vpnProvider string) (headers []string) {
case providers.Mullvad:
return []string{countryHeader, cityHeader, ispHeader, ownedHeader, hostnameHeader, vpnHeader}
case providers.Nordvpn:
return []string{countryHeader, regionHeader, cityHeader, hostnameHeader, vpnHeader, categoriesHeader}
return []string{countryHeader, regionHeader, cityHeader, hostnameHeader, categoriesHeader}
case providers.Perfectprivacy:
return []string{cityHeader, tcpHeader, udpHeader}
case providers.Privado:
@@ -135,15 +135,14 @@ func getMarkdownHeaders(vpnProvider string) (headers []string) {
case providers.Privatevpn:
return []string{countryHeader, cityHeader, hostnameHeader}
case providers.Protonvpn:
return []string{countryHeader, regionHeader, cityHeader, hostnameHeader, vpnHeader,
return []string{countryHeader, regionHeader, cityHeader, hostnameHeader,
freeHeader, portForwardHeader, secureHeader, torHeader}
case providers.Purevpn:
return []string{countryHeader, regionHeader, cityHeader, hostnameHeader, tcpHeader, udpHeader}
case providers.SlickVPN:
return []string{regionHeader, countryHeader, cityHeader, hostnameHeader}
case providers.Surfshark:
return []string{regionHeader, countryHeader, cityHeader, hostnameHeader,
vpnHeader, multiHopHeader, tcpHeader, udpHeader}
return []string{regionHeader, countryHeader, cityHeader, hostnameHeader, multiHopHeader, tcpHeader, udpHeader}
case providers.Torguard:
return []string{countryHeader, cityHeader, hostnameHeader, tcpHeader, udpHeader}
case providers.VPNSecure:

View File

@@ -22,8 +22,8 @@ type Server struct {
Number uint16 `json:"number,omitempty"`
ServerName string `json:"server_name,omitempty"`
Hostname string `json:"hostname,omitempty"`
TCP bool `json:"tcp,omitempty"`
UDP bool `json:"udp,omitempty"`
TCP bool `json:"tcp,omitempty"` // TODO v4 rename to openvpn_tcp
UDP bool `json:"udp,omitempty"` // TODO v4 rename to openvpn_udp
OvpnX509 string `json:"x509,omitempty"`
RetroLoc string `json:"retroloc,omitempty"` // TODO remove in v4
MultiHop bool `json:"multihop,omitempty"`
@@ -38,6 +38,25 @@ type Server struct {
IPs []netip.Addr `json:"ips,omitempty"`
}
func (s *Server) SetVPN(vpnType string) {
switch s.VPN {
case "":
s.VPN = vpnType
case vpn.Both:
return
case vpn.OpenVPN:
if vpnType == vpn.Wireguard {
s.VPN = vpn.Both
}
case vpn.Wireguard:
if vpnType == vpn.OpenVPN {
s.VPN = vpn.Both
}
default:
panic(fmt.Sprintf("VPN type %q not supported", s.VPN))
}
}
var (
ErrVPNFieldEmpty = errors.New("vpn field is empty")
ErrHostnameFieldEmpty = errors.New("hostname field is empty")
@@ -48,16 +67,18 @@ var (
)
func (s *Server) HasMinimumInformation() (err error) {
isOpenVPN := s.VPN == vpn.OpenVPN || s.VPN == vpn.Both
isWireguard := s.VPN == vpn.Wireguard || s.VPN == vpn.Both
switch {
case s.VPN == "":
return fmt.Errorf("%w", ErrVPNFieldEmpty)
case len(s.IPs) == 0:
return fmt.Errorf("%w", ErrIPsFieldEmpty)
case s.VPN == vpn.Wireguard && (s.TCP || s.UDP):
case isWireguard && !isOpenVPN && (s.TCP || s.UDP):
return fmt.Errorf("%w", ErrNetworkProtocolSet)
case s.VPN == vpn.OpenVPN && !s.TCP && !s.UDP:
case isOpenVPN && !s.TCP && !s.UDP:
return fmt.Errorf("%w", ErrNoNetworkProtocol)
case s.VPN == vpn.Wireguard && s.WgPubKey == "":
case isWireguard && s.WgPubKey == "":
return fmt.Errorf("%w", ErrWireguardPublicKeyEmpty)
default:
return nil

View File

@@ -18,11 +18,6 @@ var (
func extractDataFromLines(lines []string) (
connection models.Connection, err error) {
for i, line := range lines {
hashSymbolIndex := strings.Index(line, "#")
if hashSymbolIndex >= 0 {
line = line[:hashSymbolIndex]
}
ip, port, protocol, err := extractDataFromLine(line)
if err != nil {
return connection, fmt.Errorf("on line %d: %w", i+1, err)

View File

@@ -40,12 +40,11 @@ func getOpenVPNConnection(extractor Extractor,
connection.Port = customPort
}
// assume all custom provider servers support port forwarding
connection.PortForward = true
if len(selection.Names) > 0 {
// Set the server name for PIA port forwarding code used
// together with the custom provider.
connection.ServerName = selection.Names[0]
connection.PortForward = true
}
return connection, nil
@@ -54,17 +53,17 @@ func getOpenVPNConnection(extractor Extractor,
func getWireguardConnection(selection settings.ServerSelection) (
connection models.Connection) {
connection = models.Connection{
Type: vpn.Wireguard,
IP: selection.Wireguard.EndpointIP,
Port: *selection.Wireguard.EndpointPort,
Protocol: constants.UDP,
PubKey: selection.Wireguard.PublicKey,
PortForward: true, // assume all custom provider servers support port forwarding
Type: vpn.Wireguard,
IP: selection.Wireguard.EndpointIP,
Port: *selection.Wireguard.EndpointPort,
Protocol: constants.UDP,
PubKey: selection.Wireguard.PublicKey,
}
if len(selection.Names) > 0 {
// Set the server name for PIA port forwarding code used
// together with the custom provider.
connection.ServerName = selection.Names[0]
connection.PortForward = true
}
return connection
}

View File

@@ -68,26 +68,17 @@ func (hts hostToServerData) adaptWithIPs(hostToIPs map[string][]netip.Addr) {
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{
server := models.Server{
VPN: vpn.Both,
Hostname: hostname,
Country: serverData.country,
City: serverData.city,
IPs: serverData.ips,
TCP: serverData.openvpnTCP,
UDP: serverData.openvpnUDP,
WgPubKey: "658QxufMbjOTmB61Z7f+c7Rjg7oqWLnepTalqBERjF0=",
}
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)
}
servers = append(servers, server)
}
return servers
}

View File

@@ -35,11 +35,11 @@ func (hts hostToServer) add(data serverData) (err error) {
switch data.Type {
case "openvpn":
server.VPN = vpn.OpenVPN
server.SetVPN(vpn.OpenVPN)
server.UDP = true
server.TCP = true
case "wireguard":
server.VPN = vpn.Wireguard
server.SetVPN(vpn.Wireguard)
case "bridge":
// ignore bridge servers
return nil

View File

@@ -98,12 +98,6 @@ func extractServers(jsonServer serverData, groups map[uint32]groupData,
server.Number = number
}
var wireguardFound, openvpnFound bool
wireguardServer := server
wireguardServer.VPN = vpn.Wireguard
openVPNServer := server // accumulate UDP+TCP technologies
openVPNServer.VPN = vpn.OpenVPN
for _, technology := range jsonServer.Technologies {
if technology.Status != "online" {
continue
@@ -118,33 +112,25 @@ func extractServers(jsonServer serverData, groups map[uint32]groupData,
switch technologyData.Identifier {
case "openvpn_udp", "openvpn_dedicated_udp":
openvpnFound = true
openVPNServer.UDP = true
server.SetVPN(vpn.OpenVPN)
server.UDP = true
case "openvpn_tcp", "openvpn_dedicated_tcp":
openvpnFound = true
openVPNServer.TCP = true
server.SetVPN(vpn.OpenVPN)
server.TCP = true
case "wireguard_udp":
wireguardFound = true
wireguardServer.WgPubKey, err = jsonServer.wireguardPublicKey(technologies)
server.WgPubKey, err = jsonServer.wireguardPublicKey(technologies)
if err != nil {
warning := fmt.Sprintf("ignoring Wireguard server %s: %s", jsonServer.Name, err)
warnings = append(warnings, warning)
wireguardFound = false
continue
}
server.SetVPN(vpn.Wireguard)
default: // Ignore other technologies
continue
}
}
const maxServers = 2
servers = make([]models.Server, 0, maxServers)
if openvpnFound {
servers = append(servers, openVPNServer)
}
if wireguardFound {
servers = append(servers, wireguardServer)
}
servers = append(servers, server)
return servers, warnings
}

View File

@@ -7,7 +7,6 @@ import (
"strings"
"github.com/qdm12/gluetun/internal/constants"
"github.com/qdm12/gluetun/internal/constants/vpn"
"github.com/qdm12/gluetun/internal/models"
"github.com/qdm12/gluetun/internal/provider/common"
"github.com/qdm12/gluetun/internal/updater/openvpn"
@@ -65,7 +64,6 @@ func (u *Updater) FetchServers(ctx context.Context, minServers int) (
continue
}
server := models.Server{
VPN: vpn.OpenVPN,
Country: country,
City: city,
IPs: ips,

View File

@@ -8,7 +8,7 @@ import (
func (p *Provider) GetConnection(selection settings.ServerSelection, ipv6Supported bool) (
connection models.Connection, err error) {
defaults := utils.NewConnectionDefaults(443, 1194, 51820) //nolint:gomnd
defaults := utils.NewConnectionDefaults(443, 1194, 0) //nolint:gomnd
return utils.GetConnection(p.Name(),
p.storage, selection, defaults, ipv6Supported, p.randSource)
}

View File

@@ -28,11 +28,10 @@ type logicalServer struct {
}
type physicalServer struct {
EntryIP netip.Addr `json:"EntryIP"`
ExitIP netip.Addr `json:"ExitIP"`
Domain string `json:"Domain"`
Status uint8 `json:"Status"`
X25519PublicKey string `json:"X25519PublicKey"`
EntryIP netip.Addr `json:"EntryIP"`
ExitIP netip.Addr `json:"ExitIP"`
Domain string `json:"Domain"`
Status uint8 `json:"Status"`
}
func fetchAPI(ctx context.Context, client *http.Client) (

View File

@@ -7,7 +7,7 @@ import (
"github.com/qdm12/gluetun/internal/models"
)
type ipToServers map[string][2]models.Server // first server is OpenVPN, second is Wireguard.
type ipToServer map[string]models.Server
type features struct {
secureCore bool
@@ -16,50 +16,36 @@ type features struct {
stream bool
}
func (its ipToServers) add(country, region, city, name, hostname, wgPubKey string,
func (its ipToServer) add(country, region, city, name, hostname string,
free bool, entryIP netip.Addr, features features) {
key := entryIP.String()
servers, ok := its[key]
server, ok := its[key]
if ok {
return
}
baseServer := models.Server{
Country: country,
Region: region,
City: city,
ServerName: name,
Hostname: hostname,
Free: free,
SecureCore: features.secureCore,
Tor: features.tor,
PortForward: features.p2p,
Stream: features.stream,
IPs: []netip.Addr{entryIP},
}
openvpnServer := baseServer
openvpnServer.VPN = vpn.OpenVPN
openvpnServer.UDP = true
openvpnServer.TCP = true
servers[0] = openvpnServer
wireguardServer := baseServer
wireguardServer.VPN = vpn.Wireguard
wireguardServer.WgPubKey = wgPubKey
servers[1] = wireguardServer
its[key] = servers
server.VPN = vpn.OpenVPN
server.Country = country
server.Region = region
server.City = city
server.ServerName = name
server.Hostname = hostname
server.Free = free
server.SecureCore = features.secureCore
server.Tor = features.tor
server.PortForward = features.p2p
server.Stream = features.stream
server.UDP = true
server.TCP = true
server.IPs = []netip.Addr{entryIP}
its[key] = server
}
func (its ipToServers) toServersSlice() (serversSlice []models.Server) {
const vpnProtocols = 2
serversSlice = make([]models.Server, 0, vpnProtocols*len(its))
for _, servers := range its {
serversSlice = append(serversSlice, servers[0], servers[1])
func (its ipToServer) toServersSlice() (servers []models.Server) {
servers = make([]models.Server, 0, len(its))
for _, server := range its {
servers = append(servers, server)
}
return serversSlice
}
func (its ipToServers) numberOfServers() int {
const serversPerIP = 2
return len(its) * serversPerIP
return servers
}

View File

@@ -29,7 +29,7 @@ func (u *Updater) FetchServers(ctx context.Context, minServers int) (
common.ErrNotEnoughServers, count, minServers)
}
ipToServer := make(ipToServers, count)
ipToServer := make(ipToServer, count)
for _, logicalServer := range data.LogicalServers {
region := getStringValue(logicalServer.Region)
city := getStringValue(logicalServer.City)
@@ -65,7 +65,6 @@ func (u *Updater) FetchServers(ctx context.Context, minServers int) (
hostname := physicalServer.Domain
entryIP := physicalServer.EntryIP
wgPubKey := physicalServer.X25519PublicKey
// Note: for multi-hop use the server name or hostname
// instead of the country
@@ -75,11 +74,11 @@ func (u *Updater) FetchServers(ctx context.Context, minServers int) (
u.warner.Warn(warning)
}
ipToServer.add(country, region, city, name, hostname, wgPubKey, free, entryIP, features)
ipToServer.add(country, region, city, name, hostname, free, entryIP, features)
}
}
if ipToServer.numberOfServers() < minServers {
if len(ipToServer) < minServers {
return nil, fmt.Errorf("%w: %d and expected at least %d",
common.ErrNotEnoughServers, len(ipToServer), minServers)
}

View File

@@ -7,11 +7,12 @@ import (
"fmt"
"net/http"
"github.com/qdm12/gluetun/internal/constants/vpn"
"github.com/qdm12/gluetun/internal/provider/surfshark/servers"
)
func addServersFromAPI(ctx context.Context, client *http.Client,
hts hostToServers) (err error) {
hts hostToServer) (err error) {
data, err := fetchAPI(ctx, client)
if err != nil {
return err
@@ -25,12 +26,14 @@ func addServersFromAPI(ctx context.Context, client *http.Client,
retroLoc := locationData.RetroLoc // empty string if the host has no retro-compatible region
tcp, udp := true, true // OpenVPN servers from API supports both TCP and UDP
hts.addOpenVPN(serverData.Host, serverData.Region, serverData.Country,
serverData.Location, retroLoc, tcp, udp)
const wgPubKey = ""
hts.add(serverData.Host, vpn.OpenVPN, serverData.Region, serverData.Country,
serverData.Location, retroLoc, wgPubKey, tcp, udp)
if serverData.PubKey != "" {
hts.addWireguard(serverData.Host, serverData.Region, serverData.Country,
serverData.Location, retroLoc, serverData.PubKey)
const wgTCP, wgUDP = false, false // unused
hts.add(serverData.Host, vpn.Wireguard, serverData.Region, serverData.Country,
serverData.Location, retroLoc, serverData.PubKey, wgTCP, wgUDP)
}
}

View File

@@ -24,9 +24,9 @@ func Test_addServersFromAPI(t *testing.T) {
t.Parallel()
testCases := map[string]struct {
hts hostToServers
hts hostToServer
exchanges []httpExchange
expected hostToServers
expected hostToServer
err error
}{
"fetch API error": {
@@ -37,8 +37,8 @@ func Test_addServersFromAPI(t *testing.T) {
err: errors.New("HTTP status code not OK: 204 No Content"),
},
"success": {
hts: hostToServers{
"existinghost": []models.Server{{Hostname: "existinghost"}},
hts: hostToServer{
"existinghost": models.Server{Hostname: "existinghost"},
},
exchanges: []httpExchange{{
requestURL: "https://api.surfshark.com/v4/server/clusters/generic",
@@ -61,25 +61,19 @@ func Test_addServersFromAPI(t *testing.T) {
responseStatus: http.StatusOK,
responseBody: io.NopCloser(strings.NewReader(`[]`)),
}},
expected: map[string][]models.Server{
"existinghost": {{Hostname: "existinghost"}},
"host1": {{
VPN: vpn.OpenVPN,
expected: map[string]models.Server{
"existinghost": {Hostname: "existinghost"},
"host1": {
VPN: vpn.Both,
Region: "region1",
Country: "country1",
City: "location1",
Hostname: "host1",
TCP: true,
UDP: true,
}, {
VPN: vpn.Wireguard,
Region: "region1",
Country: "country1",
City: "location1",
Hostname: "host1",
WgPubKey: "pubKeyValue",
}},
"host2": {{
},
"host2": {
VPN: vpn.OpenVPN,
Region: "region2",
Country: "country1",
@@ -87,7 +81,7 @@ func Test_addServersFromAPI(t *testing.T) {
Hostname: "host2",
TCP: true,
UDP: true,
}},
},
},
},
}

View File

@@ -7,94 +7,62 @@ import (
"github.com/qdm12/gluetun/internal/models"
)
type hostToServers map[string][]models.Server
type hostToServer map[string]models.Server
func (hts hostToServers) addOpenVPN(host, region, country, city,
retroLoc string, tcp, udp bool) {
// Check for existing server for this host and OpenVPN.
servers := hts[host]
for i, existingServer := range servers {
if existingServer.Hostname != host ||
existingServer.VPN != vpn.OpenVPN {
continue
}
// Update OpenVPN supported protocols and return
if !existingServer.TCP {
servers[i].TCP = tcp
}
if !existingServer.UDP {
servers[i].UDP = udp
func (hts hostToServer) add(host, vpnType, region, country, city,
retroLoc, wgPubKey string, openvpnTCP, openvpnUDP bool) {
server, ok := hts[host]
if !ok {
server := models.Server{
VPN: vpnType,
Region: region,
Country: country,
City: city,
RetroLoc: retroLoc,
Hostname: host,
WgPubKey: wgPubKey,
TCP: openvpnTCP,
UDP: openvpnUDP,
}
hts[host] = server
return
}
server := models.Server{
VPN: vpn.OpenVPN,
Region: region,
Country: country,
City: city,
RetroLoc: retroLoc,
Hostname: host,
TCP: tcp,
UDP: udp,
server.SetVPN(vpnType)
if vpnType == vpn.OpenVPN {
server.TCP = server.TCP || openvpnTCP
server.UDP = server.UDP || openvpnUDP
} else if wgPubKey != "" {
server.WgPubKey = wgPubKey
}
hts[host] = append(servers, server)
hts[host] = server
}
func (hts hostToServers) addWireguard(host, region, country, city, retroLoc,
wgPubKey string) {
// Check for existing server for this host and Wireguard.
servers := hts[host]
for _, existingServer := range servers {
if existingServer.Hostname == host &&
existingServer.VPN == vpn.Wireguard {
// No update necessary for Wireguard
return
}
}
server := models.Server{
VPN: vpn.Wireguard,
Region: region,
Country: country,
City: city,
RetroLoc: retroLoc,
Hostname: host,
WgPubKey: wgPubKey,
}
hts[host] = append(servers, server)
}
func (hts hostToServers) toHostsSlice() (hosts []string) {
const vpnServerTypes = 2 // OpenVPN + Wireguard
hosts = make([]string, 0, vpnServerTypes*len(hts))
func (hts hostToServer) toHostsSlice() (hosts []string) {
hosts = make([]string, 0, len(hts))
for host := range hts {
hosts = append(hosts, host)
}
return hosts
}
func (hts hostToServers) adaptWithIPs(hostToIPs map[string][]netip.Addr) {
for host, IPs := range hostToIPs {
servers := hts[host]
for i := range servers {
servers[i].IPs = IPs
}
hts[host] = servers
}
for host, servers := range hts {
if len(servers[0].IPs) == 0 {
func (hts hostToServer) adaptWithIPs(hostToIPs map[string][]netip.Addr) {
for host, server := range hts {
ips := hostToIPs[host]
if len(ips) == 0 {
delete(hts, host)
continue
}
server.IPs = ips
hts[host] = server
}
}
func (hts hostToServers) toServersSlice() (servers []models.Server) {
const vpnServerTypes = 2 // OpenVPN + Wireguard
servers = make([]models.Server, 0, vpnServerTypes*len(hts))
for _, serversForHost := range hts {
servers = append(servers, serversForHost...)
func (hts hostToServer) toServersSlice() (servers []models.Server) {
servers = make([]models.Server, 0, len(hts))
for _, server := range hts {
servers = append(servers, server)
}
return servers
}

View File

@@ -1,11 +1,12 @@
package updater
import (
"github.com/qdm12/gluetun/internal/constants/vpn"
"github.com/qdm12/gluetun/internal/provider/surfshark/servers"
)
// getRemainingServers finds extra servers not found in the API or in the ZIP file.
func getRemainingServers(hts hostToServers) {
func getRemainingServers(hts hostToServer) {
locationData := servers.LocationData()
hostnameToLocationLeft := hostToLocation(locationData)
for _, hostnameDone := range hts.toHostsSlice() {
@@ -15,7 +16,8 @@ func getRemainingServers(hts hostToServers) {
for hostname, locationData := range hostnameToLocationLeft {
// we assume the OpenVPN server supports both TCP and UDP
const tcp, udp = true, true
hts.addOpenVPN(hostname, locationData.Region, locationData.Country,
locationData.City, locationData.RetroLoc, tcp, udp)
const wgPubKey = ""
hts.add(hostname, vpn.OpenVPN, locationData.Region, locationData.Country,
locationData.City, locationData.RetroLoc, wgPubKey, tcp, udp)
}
}

View File

@@ -11,7 +11,7 @@ import (
func (u *Updater) FetchServers(ctx context.Context, minServers int) (
servers []models.Server, err error) {
hts := make(hostToServers)
hts := make(hostToServer)
err = addServersFromAPI(ctx, u.client, hts)
if err != nil {

View File

@@ -4,13 +4,14 @@ import (
"context"
"strings"
"github.com/qdm12/gluetun/internal/constants/vpn"
"github.com/qdm12/gluetun/internal/provider/common"
"github.com/qdm12/gluetun/internal/provider/surfshark/servers"
"github.com/qdm12/gluetun/internal/updater/openvpn"
)
func addOpenVPNServersFromZip(ctx context.Context,
unzipper common.Unzipper, hts hostToServers) (
unzipper common.Unzipper, hts hostToServer) (
warnings []string, err error) {
const url = "https://my.surfshark.com/vpn/api/v1/server/configurations"
contents, err := unzipper.FetchAndExtract(ctx, url)
@@ -66,8 +67,9 @@ func addOpenVPNServersFromZip(ctx context.Context,
continue
}
hts.addOpenVPN(host, data.Region, data.Country, data.City,
data.RetroLoc, tcp, udp)
const wgPubKey = ""
hts.add(host, vpn.OpenVPN, data.Region, data.Country, data.City,
data.RetroLoc, wgPubKey, tcp, udp)
}
return warnings, nil

View File

@@ -50,11 +50,11 @@ func filterServer(server models.Server,
selection settings.ServerSelection) (filtered bool) {
// Note each condition is split to make sure
// we have full testing coverage.
if server.VPN != selection.VPN {
if server.VPN != vpn.Both && server.VPN != selection.VPN {
return true
}
if server.VPN != vpn.Wireguard &&
if selection.VPN == vpn.OpenVPN &&
filterByProtocol(selection, server.TCP, server.UDP) {
return true
}
@@ -119,8 +119,6 @@ func filterServer(server models.Server,
return true
}
// TODO filter port forward server for PIA
return false
}

File diff suppressed because it is too large Load Diff

View File

@@ -5,7 +5,6 @@ import (
"errors"
"fmt"
"net/http"
"sort"
"time"
"github.com/qdm12/gluetun/internal/models"
@@ -50,17 +49,13 @@ func getLatestRelease(ctx context.Context, client *http.Client) (tagName, name s
if err != nil {
return "", "", time, err
}
// Sort releases by tag names (semver)
sort.Slice(releases, func(i, j int) bool {
return releases[i].TagName > releases[j].TagName
})
for _, release := range releases {
if release.Prerelease {
continue
}
return release.TagName, release.Name, release.PublishedAt, nil
}
return "", "", time, fmt.Errorf("%w", errReleaseNotFound)
return "", "", time, errReleaseNotFound
}
var errCommitNotFound = errors.New("commit not found")