mirror of
https://github.com/qdm12/gluetun.git
synced 2026-06-27 22:37:33 +02:00
Compare commits
11 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 4a670635c4 | |||
| b5f815640e | |||
| 506c9140ba | |||
| 80763145dc | |||
| cfb8fcf00f | |||
| b9421221da | |||
| 83feb533de | |||
| 1b12dcb5b4 | |||
| a17591dcdb | |||
| c31c566282 | |||
| 496458ca4e |
@@ -56,6 +56,7 @@ body:
|
||||
- IVPN
|
||||
- Mullvad
|
||||
- NordVPN
|
||||
- OVPN
|
||||
- Privado
|
||||
- Private Internet Access
|
||||
- PrivateVPN
|
||||
|
||||
@@ -64,6 +64,8 @@
|
||||
color: "cfe8d4"
|
||||
- name: "☁️ NordVPN"
|
||||
color: "cfe8d4"
|
||||
- name: "☁️ OVPN"
|
||||
color: "cfe8d4"
|
||||
- name: "☁️ Perfect Privacy"
|
||||
color: "cfe8d4"
|
||||
- name: "☁️ PIA"
|
||||
|
||||
+3
-1
@@ -186,12 +186,14 @@ ENV VPN_SERVICE_PROVIDER=pia \
|
||||
# # ProtonVPN only:
|
||||
SECURE_CORE_ONLY= \
|
||||
TOR_ONLY= \
|
||||
# # Surfshark only:
|
||||
# # Surfshark and ovpn only:
|
||||
MULTIHOP_ONLY= \
|
||||
# # VPN Secure only:
|
||||
PREMIUM_ONLY= \
|
||||
# # PIA and ProtonVPN only:
|
||||
PORT_FORWARD_ONLY= \
|
||||
# # Ovpn only:
|
||||
SERVER_DEDICATED=no \
|
||||
# Firewall
|
||||
FIREWALL_ENABLED_DISABLING_IT_SHOOTS_YOU_IN_YOUR_FOOT=on \
|
||||
FIREWALL_VPN_INPUT_PORTS= \
|
||||
|
||||
@@ -60,10 +60,10 @@ Lightweight swiss-army-knife-like VPN client to multiple VPN service providers
|
||||
## Features
|
||||
|
||||
- Based on Alpine 3.23 for a small Docker image of 43.1MB
|
||||
- Supports: **AirVPN**, **Cyberghost**, **ExpressVPN**, **FastestVPN**, **Giganews**, **HideMyAss**, **IPVanish**, **IVPN**, **Mullvad** (Wireguard only), **NordVPN**, **Perfect Privacy**, **Privado**, **Private Internet Access**, **PrivateVPN**, **ProtonVPN**, **PureVPN**, **SlickVPN**, **Surfshark**, **TorGuard**, **VPNSecure.me**, **VPNUnlimited**, **Vyprvpn**, **Windscribe** servers
|
||||
- Supports: **AirVPN**, **Cyberghost**, **ExpressVPN**, **FastestVPN**, **Giganews**, **HideMyAss**, **IPVanish**, **IVPN**, **Mullvad** (Wireguard only), **NordVPN**, **Ovpn**, **Perfect Privacy**, **Privado**, **Private Internet Access**, **PrivateVPN**, **ProtonVPN**, **PureVPN**, **SlickVPN**, **Surfshark**, **TorGuard**, **VPNSecure.me**, **VPNUnlimited**, **Vyprvpn**, **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 **AirVPN**, **FastestVPN**, **Ivpn**, **Mullvad**, **NordVPN**, **Ovpn**, **Perfect privacy**, **ProtonVPN**, **Surfshark** and **Windscribe**
|
||||
- For **Cyberghost**, **Private Internet Access**, **PrivateVPN**, **PureVPN**, **Torguard**, **VPN Unlimited** and **VyprVPN** 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/passteque/gluetun/issues/134)
|
||||
|
||||
@@ -4,19 +4,19 @@ go 1.25.0
|
||||
|
||||
require (
|
||||
github.com/ProtonMail/go-srp v0.0.7
|
||||
github.com/amnezia-vpn/amneziawg-go v0.2.16
|
||||
github.com/breml/rootcerts v0.3.5
|
||||
github.com/fatih/color v1.19.0
|
||||
github.com/amnezia-vpn/amneziawg-go v0.2.18
|
||||
github.com/breml/rootcerts v0.3.4
|
||||
github.com/fatih/color v1.18.0
|
||||
github.com/golang/mock v1.6.0
|
||||
github.com/jsimonetti/rtnetlink v1.4.2
|
||||
github.com/klauspost/compress v1.18.6
|
||||
github.com/klauspost/compress v1.18.4
|
||||
github.com/klauspost/pgzip v1.2.6
|
||||
github.com/mdlayher/genetlink v1.3.2
|
||||
github.com/mdlayher/genetlink v1.4.0
|
||||
github.com/mdlayher/netlink v1.9.0
|
||||
github.com/miekg/dns v1.1.62
|
||||
github.com/pelletier/go-toml/v2 v2.4.0
|
||||
github.com/pelletier/go-toml/v2 v2.2.4
|
||||
github.com/qdm12/dns/v2 v2.0.0-rc9.0.20260421173011-9de8e7fdbe3a
|
||||
github.com/qdm12/gluetun-servers v0.1.0
|
||||
github.com/qdm12/gluetun-servers v0.1.1-0.20260522005421-14277e92ce82
|
||||
github.com/qdm12/goservices v0.1.1-0.20251104135713-6bee97bd4978
|
||||
github.com/qdm12/gosettings v0.4.4
|
||||
github.com/qdm12/goshutdown v0.3.0
|
||||
@@ -30,10 +30,10 @@ require (
|
||||
github.com/youmark/pkcs8 v0.0.0-20201027041543-1326539a0a0a
|
||||
golang.org/x/net v0.55.0
|
||||
golang.org/x/sys v0.45.0
|
||||
golang.org/x/text v0.37.0
|
||||
golang.org/x/text v0.38.0
|
||||
golang.zx2c4.com/wireguard v0.0.0-20231211153847-12269c276173
|
||||
golang.zx2c4.com/wireguard/wgctrl v0.0.0-20230429144221-925a1e7659e6
|
||||
gopkg.in/ini.v1 v1.67.3
|
||||
gopkg.in/ini.v1 v1.67.1
|
||||
)
|
||||
|
||||
require (
|
||||
@@ -45,9 +45,9 @@ require (
|
||||
github.com/cronokirby/saferith v0.33.0 // indirect
|
||||
github.com/davecgh/go-spew v1.1.1 // indirect
|
||||
github.com/google/go-cmp v0.7.0 // indirect
|
||||
github.com/mattn/go-colorable v0.1.14 // indirect
|
||||
github.com/mattn/go-colorable v0.1.13 // indirect
|
||||
github.com/mattn/go-isatty v0.0.20 // indirect
|
||||
github.com/mdlayher/socket v0.5.1 // indirect
|
||||
github.com/mdlayher/socket v0.6.0 // indirect
|
||||
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect
|
||||
github.com/pkg/errors v0.9.1 // indirect
|
||||
github.com/pmezard/go-difflib v1.0.0 // indirect
|
||||
@@ -58,9 +58,9 @@ require (
|
||||
github.com/riobard/go-bloom v0.0.0-20200614022211-cdc8013cb5b3 // indirect
|
||||
golang.org/x/crypto v0.51.0 // indirect
|
||||
golang.org/x/exp v0.0.0-20241009180824-f66d83c29e7c // indirect
|
||||
golang.org/x/mod v0.35.0 // indirect
|
||||
golang.org/x/sync v0.20.0 // indirect
|
||||
golang.org/x/tools v0.44.0 // indirect
|
||||
golang.org/x/mod v0.36.0 // indirect
|
||||
golang.org/x/sync v0.21.0 // indirect
|
||||
golang.org/x/tools v0.45.0 // indirect
|
||||
golang.zx2c4.com/wintun v0.0.0-20230126152724-0fa3db229ce2 // indirect
|
||||
google.golang.org/protobuf v1.35.1 // indirect
|
||||
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||
|
||||
@@ -6,12 +6,12 @@ github.com/ProtonMail/go-crypto v1.3.0-proton h1:tAQKQRZX/73VmzK6yHSCaRUOvS/3OYS
|
||||
github.com/ProtonMail/go-crypto v1.3.0-proton/go.mod h1:9whxjD8Rbs29b4XWbB8irEcE8KHMqaR2e7GWU1R+/PE=
|
||||
github.com/ProtonMail/go-srp v0.0.7 h1:Sos3Qk+th4tQR64vsxGIxYpN3rdnG9Wf9K4ZloC1JrI=
|
||||
github.com/ProtonMail/go-srp v0.0.7/go.mod h1:giCp+7qRnMIcCvI6V6U3S1lDDXDQYx2ewJ6F/9wdlJk=
|
||||
github.com/amnezia-vpn/amneziawg-go v0.2.16 h1:XY6HOq/xtqH8ZXMncRWkjFs85EKdN10NLNnw23kTpE0=
|
||||
github.com/amnezia-vpn/amneziawg-go v0.2.16/go.mod h1:nRkPpIzjCxMW8pZKXTRkpqAQVlmFJdVOGkeQSC7wbms=
|
||||
github.com/amnezia-vpn/amneziawg-go v0.2.18 h1:pUn7/P8qdGmHd6JmE3bCQXPblZs3vruWR98nLODQLJg=
|
||||
github.com/amnezia-vpn/amneziawg-go v0.2.18/go.mod h1:aMgOk9MuX0xI7b5TKAYp8pLM54RlXcOPzDvYw3YEO5A=
|
||||
github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=
|
||||
github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=
|
||||
github.com/breml/rootcerts v0.3.5 h1:oi7YiZ25HH52+mrKyjrMkcAFfnRDUf6HO8aUDr7RlJI=
|
||||
github.com/breml/rootcerts v0.3.5/go.mod h1:S/PKh+4d1HUn4HQovEB8hPJZO6pUZYrIhmXBhsegfXw=
|
||||
github.com/breml/rootcerts v0.3.4 h1:9i7WNl/ctd9OEAOaTfLy//Wrlfxq/tRQ7v4okYFN9Ys=
|
||||
github.com/breml/rootcerts v0.3.4/go.mod h1:S/PKh+4d1HUn4HQovEB8hPJZO6pUZYrIhmXBhsegfXw=
|
||||
github.com/bwesterb/go-ristretto v1.2.0/go.mod h1:fUIoIZaG73pV5biE2Blr2xEzDoMj7NFEuV9ekS419A0=
|
||||
github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs=
|
||||
github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
|
||||
@@ -25,8 +25,8 @@ github.com/cronokirby/saferith v0.33.0/go.mod h1:QKJhjoqUtBsXCAVEjw38mFqoi7DebT7
|
||||
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=
|
||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/fatih/color v1.19.0 h1:Zp3PiM21/9Ld6FzSKyL5c/BULoe/ONr9KlbYVOfG8+w=
|
||||
github.com/fatih/color v1.19.0/go.mod h1:zNk67I0ZUT1bEGsSGyCZYZNrHuTkJJB+r6Q9VuMi0LE=
|
||||
github.com/fatih/color v1.18.0 h1:S8gINlzdQ840/4pfAwic/ZE0djQEH3wM94VfqLTZcOM=
|
||||
github.com/fatih/color v1.18.0/go.mod h1:4FelSpRwEGDpQ12mAdzqdOukCy4u8WUtOY6lkT/6HfU=
|
||||
github.com/golang/mock v1.6.0 h1:ErTB+efbowRARo13NNdxyJji2egdxLGQhRaY+DUumQc=
|
||||
github.com/golang/mock v1.6.0/go.mod h1:p6yTPP+5HYm5mzsMV8JkE6ZKdX+/wYM6Hr+LicevLPs=
|
||||
github.com/google/btree v1.1.3 h1:CVpQJjYgC4VbzxeGVHfvZrv1ctoYCAI8vbl07Fcxlyg=
|
||||
@@ -35,32 +35,33 @@ github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
|
||||
github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=
|
||||
github.com/jsimonetti/rtnetlink v1.4.2 h1:Df9w9TZ3npHTyDn0Ev9e1uzmN2odmXd0QX+J5GTEn90=
|
||||
github.com/jsimonetti/rtnetlink v1.4.2/go.mod h1:92s6LJdE+1iOrw+F2/RO7LYI2Qd8pPpFNNUYW06gcoM=
|
||||
github.com/klauspost/compress v1.18.6 h1:2jupLlAwFm95+YDR+NwD2MEfFO9d4z4Prjl1XXDjuao=
|
||||
github.com/klauspost/compress v1.18.6/go.mod h1:cwPg85FWrGar70rWktvGQj8/hthj3wpl0PGDogxkrSQ=
|
||||
github.com/klauspost/compress v1.18.4 h1:RPhnKRAQ4Fh8zU2FY/6ZFDwTVTxgJ/EMydqSTzE9a2c=
|
||||
github.com/klauspost/compress v1.18.4/go.mod h1:R0h/fSBs8DE4ENlcrlib3PsXS61voFxhIs2DeRhCvJ4=
|
||||
github.com/klauspost/pgzip v1.2.6 h1:8RXeL5crjEUFnR2/Sn6GJNWtSQ3Dk8pq4CL3jvdDyjU=
|
||||
github.com/klauspost/pgzip v1.2.6/go.mod h1:Ch1tH69qFZu15pkjo5kYi6mth2Zzwzt50oCQKQE9RUs=
|
||||
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
|
||||
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
|
||||
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
|
||||
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
|
||||
github.com/mattn/go-colorable v0.1.14 h1:9A9LHSqF/7dyVVX6g0U9cwm9pG3kP9gSzcuIPHPsaIE=
|
||||
github.com/mattn/go-colorable v0.1.14/go.mod h1:6LmQG8QLFO4G5z1gPvYEzlUgJ2wF+stgPZH1UqBm1s8=
|
||||
github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA=
|
||||
github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg=
|
||||
github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
|
||||
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
|
||||
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
|
||||
github.com/mdlayher/genetlink v1.3.2 h1:KdrNKe+CTu+IbZnm/GVUMXSqBBLqcGpRDa0xkQy56gw=
|
||||
github.com/mdlayher/genetlink v1.3.2/go.mod h1:tcC3pkCrPUGIKKsCsp0B3AdaaKuHtaxoJRz3cc+528o=
|
||||
github.com/mdlayher/genetlink v1.4.0 h1:f/Xs7Y2T+GyX9b3dbiUhnLE9InGs5F9RxJ2JwBMl71o=
|
||||
github.com/mdlayher/genetlink v1.4.0/go.mod h1:d1hrKr8fwZU2JkcAtQUAzeTrI7nbgQSl+5k1cC0biSA=
|
||||
github.com/mdlayher/netlink v1.9.0 h1:G8+GLq2x3v4D4MVIqDdNUhTUC7TKiCy/6MDkmItfKco=
|
||||
github.com/mdlayher/netlink v1.9.0/go.mod h1:YBnl5BXsCoRuwBjKKlZ+aYmEoq0r12FDA/3JC+94KDg=
|
||||
github.com/mdlayher/socket v0.5.1 h1:VZaqt6RkGkt2OE9l3GcC6nZkqD3xKeQLyfleW/uBcos=
|
||||
github.com/mdlayher/socket v0.5.1/go.mod h1:TjPLHI1UgwEv5J1B5q0zTZq12A/6H7nKmtTanQE37IQ=
|
||||
github.com/mdlayher/socket v0.6.0 h1:ScZPaAGyO1icQnbFrhPM8mnXyMu9qukC1K4ZoM2IQKU=
|
||||
github.com/mdlayher/socket v0.6.0/go.mod h1:q7vozUAnxSqnjHc12Fik5yUKIzfZ8ITCfMkhOtE9z18=
|
||||
github.com/miekg/dns v1.1.62 h1:cN8OuEF1/x5Rq6Np+h1epln8OiyPWV+lROx9LxcGgIQ=
|
||||
github.com/miekg/dns v1.1.62/go.mod h1:mvDlcItzm+br7MToIKqkglaGhlFMHJ9DTNNWONWXbNQ=
|
||||
github.com/mikioh/ipaddr v0.0.0-20190404000644-d465c8ab6721 h1:RlZweED6sbSArvlE924+mUcZuXKLBHA35U7LN621Bws=
|
||||
github.com/mikioh/ipaddr v0.0.0-20190404000644-d465c8ab6721/go.mod h1:Ickgr2WtCLZ2MDGd4Gr0geeCH5HybhRJbonOgQpvSxc=
|
||||
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA=
|
||||
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ=
|
||||
github.com/pelletier/go-toml/v2 v2.4.0 h1:Mwu0mAkUKbittDs3/ADDWXqMmq3EOK2VHiuCkV00Row=
|
||||
github.com/pelletier/go-toml/v2 v2.4.0/go.mod h1:2gIqNv+qfxSVS7cM2xJQKtLSTLUE9V8t9Stt+h56mCY=
|
||||
github.com/pelletier/go-toml/v2 v2.2.4 h1:mye9XuhQ6gvn5h28+VilKrrPoQVanw5PMw/TB0t5Ec4=
|
||||
github.com/pelletier/go-toml/v2 v2.2.4/go.mod h1:2gIqNv+qfxSVS7cM2xJQKtLSTLUE9V8t9Stt+h56mCY=
|
||||
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
|
||||
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||
@@ -75,8 +76,8 @@ github.com/prometheus/procfs v0.15.1 h1:YagwOFzUgYfKKHX6Dr+sHT7km/hxC76UB0leargg
|
||||
github.com/prometheus/procfs v0.15.1/go.mod h1:fB45yRUv8NstnjriLhBQLuOUt+WW4BsoGhij/e3PBqk=
|
||||
github.com/qdm12/dns/v2 v2.0.0-rc9.0.20260421173011-9de8e7fdbe3a h1:TE157yPQmAbVruH0MWCQzs0vTT/6t96DkoWUXd6PVuc=
|
||||
github.com/qdm12/dns/v2 v2.0.0-rc9.0.20260421173011-9de8e7fdbe3a/go.mod h1:98foWgXJZ+g8gJIuO+fdO+oWpFei5WShMFTeN4Im2lE=
|
||||
github.com/qdm12/gluetun-servers v0.1.0 h1:w9JLghKZwI0Gzpp9p5rNANgEYUUZ1dxdxsG6NKIojaY=
|
||||
github.com/qdm12/gluetun-servers v0.1.0/go.mod h1:acttuyHyoFDu6GTbf3kAV+QXeiX8oJeh0MBic67/9z8=
|
||||
github.com/qdm12/gluetun-servers v0.1.1-0.20260522005421-14277e92ce82 h1:tE44IEW7o9yPQaO8HBeoO9RxtTTxqhboIypegrQlVt8=
|
||||
github.com/qdm12/gluetun-servers v0.1.1-0.20260522005421-14277e92ce82/go.mod h1:acttuyHyoFDu6GTbf3kAV+QXeiX8oJeh0MBic67/9z8=
|
||||
github.com/qdm12/goservices v0.1.1-0.20251104135713-6bee97bd4978 h1:TRGpCU1l0lNwtogEUSs5U+RFceYxkAJUmrGabno7J5c=
|
||||
github.com/qdm12/goservices v0.1.1-0.20251104135713-6bee97bd4978/go.mod h1:D1Po4CRQLYjccnAR2JsVlN1sBMgQrcNLONbvyuzcdTg=
|
||||
github.com/qdm12/gosettings v0.4.4 h1:SM6tOZDf6k8qbjWU8KWyBF4mWIixfsKCfh9DGRLHlj4=
|
||||
@@ -126,8 +127,8 @@ golang.org/x/exp v0.0.0-20241009180824-f66d83c29e7c/go.mod h1:NQtJDoLvd6faHhE7m4
|
||||
golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
|
||||
golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
|
||||
golang.org/x/mod v0.35.0 h1:Ww1D637e6Pg+Zb2KrWfHQUnH2dQRLBQyAtpr/haaJeM=
|
||||
golang.org/x/mod v0.35.0/go.mod h1:+GwiRhIInF8wPm+4AoT6L0FA1QWAad3OMdTRx4tFYlU=
|
||||
golang.org/x/mod v0.36.0 h1:JJjpVx6myfUsUdAzZuOSTTmRE0PfZeNWzzvKrP7amb4=
|
||||
golang.org/x/mod v0.36.0/go.mod h1:moc6ELqsWcOw5Ef3xVprK5ul/MvtVvkIXLziUOICjUQ=
|
||||
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
||||
@@ -141,8 +142,8 @@ golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJ
|
||||
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.20.0 h1:e0PTpb7pjO8GAtTs2dQ6jYa5BWYlMuX047Dco/pItO4=
|
||||
golang.org/x/sync v0.20.0/go.mod h1:9xrNwdLfx4jkKbNva9FpL6vEN7evnE43NNNJQ2LF3+0=
|
||||
golang.org/x/sync v0.21.0 h1:HLII4xRRTtCRkxYp4HNFF0Js/Og6q2i++KXbg0gHCwM=
|
||||
golang.org/x/sync v0.21.0/go.mod h1:9xrNwdLfx4jkKbNva9FpL6vEN7evnE43NNNJQ2LF3+0=
|
||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
@@ -152,6 +153,7 @@ golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBc
|
||||
golang.org/x/sys v0.0.0-20211007075335-d3039528d8ac/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.45.0 h1:dO4czNzziLiiXplLQgBCEpCvXQ3dnkn0SdaZSYdQ+FY=
|
||||
@@ -165,8 +167,8 @@ golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
|
||||
golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
|
||||
golang.org/x/text v0.8.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
|
||||
golang.org/x/text v0.37.0 h1:Cqjiwd9eSg8e0QAkyCaQTNHFIIzWtidPahFWR83rTrc=
|
||||
golang.org/x/text v0.37.0/go.mod h1:a5sjxXGs9hsn/AJVwuElvCAo9v8QYLzvavO5z2PiM38=
|
||||
golang.org/x/text v0.38.0 h1:sXmwo9DwP3OK9EZ7PqAdaooSGozfl/3a6/xJcbzPRhE=
|
||||
golang.org/x/text v0.38.0/go.mod h1:YXZt3QhHUKYT53r2lLKFIVi6Ao1jdzrTR/KQ09qyxF4=
|
||||
golang.org/x/time v0.9.0 h1:EsRrnYcQiGH+5FfbgvV4AP7qEZstoyrHB0DzarOQ4ZY=
|
||||
golang.org/x/time v0.9.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM=
|
||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
@@ -174,8 +176,8 @@ golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtn
|
||||
golang.org/x/tools v0.1.1/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
|
||||
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
|
||||
golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=
|
||||
golang.org/x/tools v0.44.0 h1:UP4ajHPIcuMjT1GqzDWRlalUEoY+uzoZKnhOjbIPD2c=
|
||||
golang.org/x/tools v0.44.0/go.mod h1:KA0AfVErSdxRZIsOVipbv3rQhVXTnlU6UhKxHd1seDI=
|
||||
golang.org/x/tools v0.45.0 h1:18qN3FAooORvApf5XjCXgsuayZOEtXf6JK18I3+ONa8=
|
||||
golang.org/x/tools v0.45.0/go.mod h1:LuUGqqaXcXMEFEruIVJVm5mgDD8vww/z/SR1gQ4uE/0=
|
||||
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
@@ -190,8 +192,8 @@ google.golang.org/protobuf v1.35.1/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojt
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
|
||||
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
|
||||
gopkg.in/ini.v1 v1.67.3 h1:iM9Lhz5MRSGhHVGGwCuzG9KO8PoirCXj/m/qTmOJJQw=
|
||||
gopkg.in/ini.v1 v1.67.3/go.mod h1:x/cyOwCgZqOkJoDIJ3c1KNHMo10+nLGAhh+kn3Zizss=
|
||||
gopkg.in/ini.v1 v1.67.1 h1:tVBILHy0R6e4wkYOn3XmiITt/hEVH4TFMYvAX2Ytz6k=
|
||||
gopkg.in/ini.v1 v1.67.1/go.mod h1:x/cyOwCgZqOkJoDIJ3c1KNHMo10+nLGAhh+kn3Zizss=
|
||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
|
||||
@@ -70,7 +70,7 @@ func (o OpenVPNSelection) validate(vpnProvider string) (err error) {
|
||||
switch vpnProvider {
|
||||
// no restriction on port
|
||||
case providers.Custom, providers.Cyberghost, providers.HideMyAss,
|
||||
providers.Privatevpn, providers.Torguard:
|
||||
providers.Ovpn, providers.Privatevpn, providers.Torguard:
|
||||
// no custom port allowed
|
||||
case providers.Expressvpn, providers.Fastestvpn,
|
||||
providers.Giganews, providers.Ipvanish,
|
||||
|
||||
@@ -49,6 +49,7 @@ func (p *Provider) validate(vpnType string, filterChoicesGetter FilterChoicesGet
|
||||
providers.Ivpn,
|
||||
providers.Mullvad,
|
||||
providers.Nordvpn,
|
||||
providers.Ovpn,
|
||||
providers.Protonvpn,
|
||||
providers.Surfshark,
|
||||
providers.Windscribe,
|
||||
|
||||
@@ -63,6 +63,9 @@ type ServerSelection struct {
|
||||
// TorOnly is true if VPN servers without tor should
|
||||
// be filtered. This is used with ProtonVPN.
|
||||
TorOnly *bool `json:"tor_only"`
|
||||
// Dedicated is true if dedicated VPN servers should be chosen only.
|
||||
// This is used with OVPN.
|
||||
Dedicated *bool `json:"dedicated"`
|
||||
// OpenVPN contains settings to select OpenVPN servers
|
||||
// and the final connection.
|
||||
OpenVPN OpenVPNSelection `json:"openvpn"`
|
||||
@@ -272,6 +275,8 @@ func validateFeatureFilters(settings ServerSelection, vpnServiceProvider string)
|
||||
return errors.New("secure core only filter is not supported")
|
||||
case *settings.TorOnly && vpnServiceProvider != providers.Protonvpn:
|
||||
return errors.New("tor only filter is not supported")
|
||||
case *settings.Dedicated && vpnServiceProvider != providers.Ovpn:
|
||||
return errors.New("dedicated filter is not supported")
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
@@ -296,6 +301,7 @@ func (ss *ServerSelection) copy() (copied ServerSelection) {
|
||||
TorOnly: gosettings.CopyPointer(ss.TorOnly),
|
||||
PortForwardOnly: gosettings.CopyPointer(ss.PortForwardOnly),
|
||||
MultiHopOnly: gosettings.CopyPointer(ss.MultiHopOnly),
|
||||
Dedicated: gosettings.CopyPointer(ss.Dedicated),
|
||||
OpenVPN: ss.OpenVPN.copy(),
|
||||
Wireguard: ss.Wireguard.copy(),
|
||||
}
|
||||
@@ -319,6 +325,7 @@ func (ss *ServerSelection) overrideWith(other ServerSelection) {
|
||||
ss.TorOnly = gosettings.OverrideWithPointer(ss.TorOnly, other.TorOnly)
|
||||
ss.MultiHopOnly = gosettings.OverrideWithPointer(ss.MultiHopOnly, other.MultiHopOnly)
|
||||
ss.PortForwardOnly = gosettings.OverrideWithPointer(ss.PortForwardOnly, other.PortForwardOnly)
|
||||
ss.Dedicated = gosettings.OverrideWithPointer(ss.Dedicated, other.Dedicated)
|
||||
ss.OpenVPN.overrideWith(other.OpenVPN)
|
||||
ss.Wireguard.overrideWith(other.Wireguard)
|
||||
}
|
||||
@@ -335,6 +342,7 @@ func (ss *ServerSelection) setDefaults(vpnProvider string, portForwardingEnabled
|
||||
defaultPortForwardOnly := portForwardingEnabled &&
|
||||
helpers.IsOneOf(vpnProvider, providers.PrivateInternetAccess, providers.Protonvpn)
|
||||
ss.PortForwardOnly = gosettings.DefaultPointer(ss.PortForwardOnly, defaultPortForwardOnly)
|
||||
ss.Dedicated = gosettings.DefaultPointer(ss.Dedicated, false)
|
||||
ss.OpenVPN.setDefaults(vpnProvider)
|
||||
ss.Wireguard.setDefaults()
|
||||
}
|
||||
@@ -410,6 +418,10 @@ func (ss ServerSelection) toLinesNode() (node *gotree.Node) {
|
||||
node.Appendf("Multi-hop only servers: yes")
|
||||
}
|
||||
|
||||
if *ss.Dedicated {
|
||||
node.Appendf("Dedicated servers: yes")
|
||||
}
|
||||
|
||||
if *ss.PortForwardOnly {
|
||||
node.Appendf("Port forwarding only servers: yes")
|
||||
}
|
||||
@@ -501,6 +513,12 @@ func (ss *ServerSelection) read(r *reader.Reader,
|
||||
return err
|
||||
}
|
||||
|
||||
// Ovpn only
|
||||
ss.Dedicated, err = r.BoolPtr("SERVER_DEDICATED")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = ss.OpenVPN.read(r)
|
||||
if err != nil {
|
||||
return err
|
||||
|
||||
@@ -5,6 +5,7 @@ import (
|
||||
"fmt"
|
||||
"net/netip"
|
||||
|
||||
"github.com/qdm12/gluetun/internal/configuration/settings/helpers"
|
||||
"github.com/qdm12/gluetun/internal/constants/providers"
|
||||
"github.com/qdm12/gosettings"
|
||||
"github.com/qdm12/gosettings/reader"
|
||||
@@ -22,7 +23,7 @@ type WireguardSelection struct {
|
||||
// It can never be the zero value in the internal state.
|
||||
EndpointIP netip.Addr `json:"endpoint_ip"`
|
||||
// EndpointPort is a the server port to use for the VPN server.
|
||||
// It is optional for VPN providers IVPN, Mullvad, Surfshark
|
||||
// It is optional for VPN providers IVPN, Mullvad, Ovpn, Surfshark
|
||||
// and Windscribe, and compulsory for the others.
|
||||
// When optional, it can be set to 0 to indicate not use
|
||||
// a custom endpoint port. It cannot be nil in the internal
|
||||
@@ -40,8 +41,9 @@ 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.Ovpn,
|
||||
providers.Protonvpn, providers.Surfshark,
|
||||
providers.Windscribe:
|
||||
// endpoint IP addresses are baked in
|
||||
case providers.Custom:
|
||||
if !w.EndpointIP.IsValid() || w.EndpointIP.IsUnspecified() {
|
||||
@@ -63,12 +65,16 @@ func (w WireguardSelection) validate(vpnProvider string) (err error) {
|
||||
if *w.EndpointPort != 0 {
|
||||
return errors.New("endpoint port is set")
|
||||
}
|
||||
case providers.Airvpn, providers.Ivpn, providers.Mullvad, providers.Windscribe:
|
||||
case providers.Airvpn, providers.Ivpn, providers.Mullvad,
|
||||
providers.Ovpn, providers.Windscribe:
|
||||
// EndpointPort is optional and can be 0
|
||||
if *w.EndpointPort == 0 {
|
||||
break // no custom endpoint port set
|
||||
}
|
||||
if vpnProvider == providers.Mullvad {
|
||||
if helpers.IsOneOf(vpnProvider,
|
||||
providers.Mullvad,
|
||||
providers.Ovpn,
|
||||
) {
|
||||
break // no restriction on custom endpoint port value
|
||||
}
|
||||
var allowed []uint16
|
||||
@@ -92,7 +98,7 @@ func (w WireguardSelection) validate(vpnProvider string) (err error) {
|
||||
// Validate PublicKey
|
||||
switch vpnProvider {
|
||||
case providers.Fastestvpn, providers.Ivpn, providers.Mullvad,
|
||||
providers.Surfshark, providers.Windscribe:
|
||||
providers.Ovpn, providers.Surfshark, providers.Windscribe:
|
||||
// public keys are baked in
|
||||
case providers.Custom:
|
||||
if w.PublicKey == "" {
|
||||
|
||||
@@ -15,6 +15,7 @@ const (
|
||||
Ivpn = "ivpn"
|
||||
Mullvad = "mullvad"
|
||||
Nordvpn = "nordvpn"
|
||||
Ovpn = "ovpn"
|
||||
Perfectprivacy = "perfect privacy"
|
||||
Privado = "privado"
|
||||
PrivateInternetAccess = "private internet access"
|
||||
@@ -43,6 +44,7 @@ func All() []string {
|
||||
Ivpn,
|
||||
Mullvad,
|
||||
Nordvpn,
|
||||
Ovpn,
|
||||
Perfectprivacy,
|
||||
Privado,
|
||||
PrivateInternetAccess,
|
||||
|
||||
@@ -4,6 +4,7 @@ import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"math"
|
||||
"math/rand/v2"
|
||||
"net/http"
|
||||
@@ -84,11 +85,14 @@ func triggerDNSQuery(ctx context.Context, client *http.Client, session string) (
|
||||
IP map[string]uint `json:"ip"`
|
||||
}
|
||||
|
||||
decoder := json.NewDecoder(response.Body)
|
||||
var data ipLeakData
|
||||
err = decoder.Decode(&data)
|
||||
rawData, err := io.ReadAll(response.Body)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("decoding response: %w", err)
|
||||
return nil, fmt.Errorf("reading response body: %w", err)
|
||||
}
|
||||
var data ipLeakData
|
||||
err = json.Unmarshal(rawData, &data)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("decoding response %q: %w", rawData, err)
|
||||
} else if data.Session != session {
|
||||
return nil, fmt.Errorf("ipleak.net session mismatch: expected %s, got %s", session, data.Session)
|
||||
}
|
||||
|
||||
@@ -34,8 +34,11 @@ type Server struct {
|
||||
SecureCore bool `json:"secure_core,omitempty"`
|
||||
Tor bool `json:"tor,omitempty"`
|
||||
PortForward bool `json:"port_forward,omitempty"`
|
||||
Dedicated bool `json:"dedicated,omitempty"`
|
||||
Keep bool `json:"keep,omitempty"`
|
||||
IPs []netip.Addr `json:"ips,omitempty"`
|
||||
PortsTCP []uint16 `json:"ports_tcp,omitempty"`
|
||||
PortsUDP []uint16 `json:"ports_udp,omitempty"`
|
||||
}
|
||||
|
||||
func (s *Server) HasMinimumInformation() (err error) {
|
||||
|
||||
@@ -0,0 +1,15 @@
|
||||
package ovpn
|
||||
|
||||
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, 9929) //nolint:mnd
|
||||
return utils.GetConnection(p.Name(),
|
||||
p.storage, selection, defaults, ipv6Supported, p.connPicker)
|
||||
}
|
||||
@@ -0,0 +1,126 @@
|
||||
package ovpn
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"net/http"
|
||||
"net/netip"
|
||||
"testing"
|
||||
|
||||
"github.com/golang/mock/gomock"
|
||||
"github.com/qdm12/gluetun/internal/configuration/settings"
|
||||
"github.com/qdm12/gluetun/internal/constants"
|
||||
"github.com/qdm12/gluetun/internal/constants/providers"
|
||||
"github.com/qdm12/gluetun/internal/constants/vpn"
|
||||
"github.com/qdm12/gluetun/internal/models"
|
||||
"github.com/qdm12/gluetun/internal/provider/common"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func Test_Provider_GetConnection(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
const provider = providers.Ovpn
|
||||
|
||||
errTest := errors.New("test error")
|
||||
|
||||
testCases := map[string]struct {
|
||||
filteredServers []models.Server
|
||||
storageErr error
|
||||
selection settings.ServerSelection
|
||||
ipv6Supported bool
|
||||
connection models.Connection
|
||||
errWrapped error
|
||||
errMessage string
|
||||
}{
|
||||
"error": {
|
||||
storageErr: errTest,
|
||||
errWrapped: errTest,
|
||||
errMessage: "filtering servers: test error",
|
||||
},
|
||||
"default_openvpn_tcp_port": {
|
||||
filteredServers: []models.Server{
|
||||
{IPs: []netip.Addr{netip.AddrFrom4([4]byte{1, 1, 1, 1})}},
|
||||
},
|
||||
selection: settings.ServerSelection{
|
||||
OpenVPN: settings.OpenVPNSelection{
|
||||
Protocol: constants.TCP,
|
||||
},
|
||||
}.WithDefaults(provider),
|
||||
connection: models.Connection{
|
||||
Type: vpn.OpenVPN,
|
||||
IP: netip.AddrFrom4([4]byte{1, 1, 1, 1}),
|
||||
Port: 443,
|
||||
Protocol: constants.TCP,
|
||||
},
|
||||
},
|
||||
"default_openvpn_udp_port": {
|
||||
filteredServers: []models.Server{
|
||||
{IPs: []netip.Addr{netip.AddrFrom4([4]byte{1, 1, 1, 1})}},
|
||||
},
|
||||
selection: settings.ServerSelection{
|
||||
OpenVPN: settings.OpenVPNSelection{
|
||||
Protocol: constants.UDP,
|
||||
},
|
||||
}.WithDefaults(provider),
|
||||
connection: models.Connection{
|
||||
Type: vpn.OpenVPN,
|
||||
IP: netip.AddrFrom4([4]byte{1, 1, 1, 1}),
|
||||
Port: 1194,
|
||||
Protocol: constants.UDP,
|
||||
},
|
||||
},
|
||||
"default_wireguard_port": {
|
||||
filteredServers: []models.Server{
|
||||
{IPs: []netip.Addr{netip.AddrFrom4([4]byte{1, 1, 1, 1})}, WgPubKey: "x"},
|
||||
},
|
||||
selection: settings.ServerSelection{
|
||||
VPN: vpn.Wireguard,
|
||||
}.WithDefaults(provider),
|
||||
connection: models.Connection{
|
||||
Type: vpn.Wireguard,
|
||||
IP: netip.AddrFrom4([4]byte{1, 1, 1, 1}),
|
||||
Port: 9929,
|
||||
Protocol: constants.UDP,
|
||||
PubKey: "x",
|
||||
},
|
||||
},
|
||||
"default_multihop_port": {
|
||||
filteredServers: []models.Server{
|
||||
{IPs: []netip.Addr{netip.AddrFrom4([4]byte{1, 1, 1, 1})}, WgPubKey: "x", PortsUDP: []uint16{30044}},
|
||||
},
|
||||
selection: settings.ServerSelection{
|
||||
VPN: vpn.Wireguard,
|
||||
}.WithDefaults(provider),
|
||||
connection: models.Connection{
|
||||
Type: vpn.Wireguard,
|
||||
IP: netip.AddrFrom4([4]byte{1, 1, 1, 1}),
|
||||
Port: 30044,
|
||||
Protocol: constants.UDP,
|
||||
PubKey: "x",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for name, testCase := range testCases {
|
||||
t.Run(name, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
ctrl := gomock.NewController(t)
|
||||
|
||||
storage := common.NewMockStorage(ctrl)
|
||||
storage.EXPECT().FilterServers(provider, testCase.selection).
|
||||
Return(testCase.filteredServers, testCase.storageErr)
|
||||
|
||||
client := (*http.Client)(nil)
|
||||
provider := New(storage, client)
|
||||
|
||||
connection, err := provider.GetConnection(testCase.selection, testCase.ipv6Supported)
|
||||
|
||||
assert.ErrorIs(t, err, testCase.errWrapped)
|
||||
if testCase.errWrapped != nil {
|
||||
assert.EqualError(t, err, testCase.errMessage)
|
||||
}
|
||||
|
||||
assert.Equal(t, testCase.connection, connection)
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
package ovpn
|
||||
|
||||
import (
|
||||
"strings"
|
||||
|
||||
"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,
|
||||
Ciphers: []string{
|
||||
openvpn.AES256gcm,
|
||||
openvpn.AES256cbc,
|
||||
openvpn.AES128gcm,
|
||||
openvpn.Chacha20Poly1305,
|
||||
},
|
||||
CAs: []string{
|
||||
"MIIEfTCCA2WgAwIBAgIJAK2aIWqpLj1/MA0GCSqGSIb3DQEBBQUAMIGFMQswCQYDVQQGEwJTRTESMBAGA1UECBMJU3RvY2tob2xtMRIwEAYDVQQHEwlTdG9ja2hvbG0xHDAaBgNVBAsTE0Zpcm1hIERhdmlkIFdpYmVyZ2gxEzARBgNVBAMTCm92cG4uc2UgY2ExGzAZBgkqhkiG9w0BCQEWDGluZm9Ab3Zwbi5zZTAeFw0xNDA4MTcxODIxMjlaFw0zNDA4MTIxODIxMjlaMIGFMQswCQYDVQQGEwJTRTESMBAGA1UECBMJU3RvY2tob2xtMRIwEAYDVQQHEwlTdG9ja2hvbG0xHDAaBgNVBAsTE0Zpcm1hIERhdmlkIFdpYmVyZ2gxEzARBgNVBAMTCm92cG4uc2UgY2ExGzAZBgkqhkiG9w0BCQEWDGluZm9Ab3Zwbi5zZTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAMR+aP4GTuZwurZuOA2NYzMfqKyZi/TJcLEPlGTB/b4CWA9bTd8f0pHPrDAZsXIEayxxB58BIFNDNiybnbO15JN/QwlsqmA+aZX6mCSkScs/rRwasM6LDo8iGx+KmYEqAgzziONGbCMnlO+OaarXte7LhZ9X6Z/bryu4xq/i1v3raak13kXsrogtu4iDzxqJE/QhbNOi0yhCdlm5RYQjmlKGdPB9pNTgcakVI4HcngRYMzBlrGin0YkvWCdpx5FrDNeld7BSWrJMNYyvd+buaid0Fu1T9/P/Srj/8AiabKoaDyiGFbZdTnGfK+04lWRvwAmvazpqbUt5Omw634jJDuMCAwEAAaOB7TCB6jAdBgNVHQ4EFgQUEvJcHHcTiDtu7bAyZw+xaqg+xdIwgboGA1UdIwSBsjCBr4AUEvJcHHcTiDtu7bAyZw+xaqg+xdKhgYukgYgwgYUxCzAJBgNVBAYTAlNFMRIwEAYDVQQIEwlTdG9ja2hvbG0xEjAQBgNVBAcTCVN0b2NraG9sbTEcMBoGA1UECxMTRmlybWEgRGF2aWQgV2liZXJnaDETMBEGA1UEAxMKb3Zwbi5zZSBjYTEbMBkGCSqGSIb3DQEJARYMaW5mb0BvdnBuLnNlggkArZohaqkuPX8wDAYDVR0TBAUwAwEB/zANBgkqhkiG9w0BAQUFAAOCAQEAJmID6OyBJbV7ayPPgquojF+FICuDdOfGVKP828cyISxcbVA04VpD0QLYVb0k9pFUx0NbgX2SvRTiFhP7LcyS1HV9s+XLCb2WItPPsrdRTwtqU2n3TlCEzWA3WOcOCtT6JSkv1eelmx1JnP0gYJrDvDvRYBFctwWhtE0bineSQkZwN6980zkknADLAiHpeZSu/AMx7CGTwA6SmoFvpNBmHXDcfe/9ZqbbYfUfyPNe+0JbMrcv1elKi+6wlEkHFaEBphiZwGEbOX1CjUMcQFgW/cIp3n50Eiyx6ktuqimhyb59P4Nw8gqH452tTtE4MM/brA5y0Q0WFBRBojfZIbGWWQ==", //nolint:lll
|
||||
},
|
||||
TLSAuth: "81782767e4d59c4464cc5d1896f1cf6015017d53ac62e2e3b94b889e00b2c69ddc01944fe1c6d895b4d80540502eb71910b8d785c9efa9e3182343532adffe1cfbb7bb6eae39c502da2748edf0fb89b8a20b0a1085cc1f06135037881bc0c4ad8f2c0f4f72d2ab466fb54af3d8264c5fddeb0f21aa0ca41863678f5fc4c44de4ca0926b36dfddc42c6f2fabd1694bdc8215b2d223b9c21dc6734c2c778093187afb8c33403b228b9af68b540c284f6d183bcc88bd41d47bd717996e499ce1cbbfa768a9723c19c58314c4d19cfed82e543ee92e73d38ad26d4fbec231c0f9f3b30773a5c87792e9bc7c34e8d7611002ebedd044e48a0f1f96527bfdcc940aa09", //nolint:lll
|
||||
KeyDirection: "1",
|
||||
}
|
||||
|
||||
if strings.HasSuffix(connection.Hostname, "singapore.ovpn.com") {
|
||||
providerSettings.TLSCrypt = providerSettings.TLSAuth
|
||||
providerSettings.TLSAuth = ""
|
||||
providerSettings.KeyDirection = ""
|
||||
}
|
||||
|
||||
return utils.OpenVPNConfig(providerSettings, connection, settings, ipv6Supported)
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
package ovpn
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
"github.com/qdm12/gluetun/internal/constants/providers"
|
||||
"github.com/qdm12/gluetun/internal/provider/common"
|
||||
"github.com/qdm12/gluetun/internal/provider/ovpn/updater"
|
||||
"github.com/qdm12/gluetun/internal/provider/utils"
|
||||
)
|
||||
|
||||
type Provider struct {
|
||||
storage common.Storage
|
||||
connPicker *utils.ConnectionPicker
|
||||
common.Fetcher
|
||||
}
|
||||
|
||||
func New(storage common.Storage, client *http.Client) *Provider {
|
||||
return &Provider{
|
||||
storage: storage,
|
||||
connPicker: utils.NewConnectionPicker(),
|
||||
Fetcher: updater.New(client),
|
||||
}
|
||||
}
|
||||
|
||||
func (p *Provider) Name() string {
|
||||
return providers.Ovpn
|
||||
}
|
||||
@@ -0,0 +1,153 @@
|
||||
package updater
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"net/netip"
|
||||
"strings"
|
||||
)
|
||||
|
||||
type apiData struct {
|
||||
Success bool `json:"success"`
|
||||
DataCenters []apiDataCenter `json:"datacenters"`
|
||||
}
|
||||
|
||||
type apiDataCenter struct {
|
||||
City string `json:"city"`
|
||||
CountryName string `json:"country_name"`
|
||||
Servers []apiServer `json:"servers"`
|
||||
}
|
||||
|
||||
type apiServer struct {
|
||||
IP netip.Addr `json:"ip"`
|
||||
Ptr string `json:"ptr"` // hostname
|
||||
Online bool `json:"online"`
|
||||
// PublicKey is for the Standard Shared Entry Point
|
||||
PublicKey string `json:"public_key"`
|
||||
// PublicKeyIPv4 is for the Public / Dedicated IP Entry Point
|
||||
PublicKeyIPv4 string `json:"public_key_ipv4"`
|
||||
WireguardPorts []uint16 `json:"wireguard_ports"`
|
||||
MultiHopOpenvpnPort uint16 `json:"multihop_openvpn_port"`
|
||||
MultiHopWireguardPort uint16 `json:"multihop_wireguard_port"`
|
||||
}
|
||||
|
||||
func fetchAPI(ctx context.Context, client *http.Client) (
|
||||
data apiData, err error,
|
||||
) {
|
||||
const url = "https://www.ovpn.com/v2/api/client/entry"
|
||||
|
||||
request, err := http.NewRequestWithContext(ctx, http.MethodGet, url, nil)
|
||||
if err != nil {
|
||||
return data, err
|
||||
}
|
||||
|
||||
response, err := client.Do(request)
|
||||
if err != nil {
|
||||
return data, err
|
||||
}
|
||||
|
||||
if response.StatusCode != http.StatusOK {
|
||||
_ = response.Body.Close()
|
||||
return data, fmt.Errorf("HTTP response status code is not OK: %d %s",
|
||||
response.StatusCode, response.Status)
|
||||
}
|
||||
|
||||
decoder := json.NewDecoder(response.Body)
|
||||
err = decoder.Decode(&data)
|
||||
if err != nil {
|
||||
_ = response.Body.Close()
|
||||
return data, fmt.Errorf("decoding response body: %w", err)
|
||||
}
|
||||
|
||||
err = response.Body.Close()
|
||||
if err != nil {
|
||||
return data, fmt.Errorf("closing response body: %w", err)
|
||||
}
|
||||
|
||||
return data, nil
|
||||
}
|
||||
|
||||
func (a *apiDataCenter) validate() (err error) {
|
||||
conditionalErrors := []conditionalError{
|
||||
{err: "city is not set", condition: a.City == ""},
|
||||
{err: "country name is not set", condition: a.CountryName == ""},
|
||||
{err: "servers array is not set", condition: len(a.Servers) == 0},
|
||||
}
|
||||
err = collectErrors(conditionalErrors)
|
||||
if err != nil {
|
||||
var dataCenterSetFields []string
|
||||
if a.CountryName != "" {
|
||||
dataCenterSetFields = append(dataCenterSetFields, a.CountryName)
|
||||
}
|
||||
if a.City != "" {
|
||||
dataCenterSetFields = append(dataCenterSetFields, a.City)
|
||||
}
|
||||
if len(dataCenterSetFields) == 0 {
|
||||
return err
|
||||
}
|
||||
return fmt.Errorf("data center %s: %w",
|
||||
strings.Join(dataCenterSetFields, ", "), err)
|
||||
}
|
||||
|
||||
for i, server := range a.Servers {
|
||||
err = server.validate()
|
||||
if err != nil {
|
||||
return fmt.Errorf("datacenter %s, %s: server %d of %d: %w",
|
||||
a.CountryName, a.City, i+1, len(a.Servers), err)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (a *apiServer) validate() (err error) {
|
||||
const defaultWireguardPort = 9929
|
||||
conditionalErrors := []conditionalError{
|
||||
{err: "ip address is not set", condition: !a.IP.IsValid()},
|
||||
{err: "hostname field is not set", condition: a.Ptr == ""},
|
||||
{err: "public key field is not set", condition: a.PublicKey == ""},
|
||||
{err: "public key IPv4 field is not set", condition: a.PublicKeyIPv4 == ""},
|
||||
{err: "wireguard ports array is not set", condition: len(a.WireguardPorts) == 0},
|
||||
{
|
||||
err: "wireguard port is not the default 9929",
|
||||
condition: len(a.WireguardPorts) != 1 || a.WireguardPorts[0] != defaultWireguardPort,
|
||||
},
|
||||
{err: "multihop OpenVPN port is not set", condition: a.MultiHopOpenvpnPort == 0},
|
||||
{err: "multihop WireGuard port is not set", condition: a.MultiHopWireguardPort == 0},
|
||||
}
|
||||
err = collectErrors(conditionalErrors)
|
||||
switch {
|
||||
case err == nil:
|
||||
return nil
|
||||
case a.Ptr != "":
|
||||
return fmt.Errorf("server %s: %w", a.Ptr, err)
|
||||
case a.IP.IsValid():
|
||||
return fmt.Errorf("server %s: %w", a.IP.String(), err)
|
||||
default:
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
type conditionalError struct {
|
||||
err string
|
||||
condition bool
|
||||
}
|
||||
|
||||
func collectErrors(conditionalErrors []conditionalError) (err error) {
|
||||
errs := make([]string, 0, len(conditionalErrors))
|
||||
for _, conditionalError := range conditionalErrors {
|
||||
if !conditionalError.condition {
|
||||
continue
|
||||
}
|
||||
errs = append(errs, conditionalError.err)
|
||||
}
|
||||
|
||||
if len(errs) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
return errors.New(strings.Join(errs, "; "))
|
||||
}
|
||||
@@ -0,0 +1,118 @@
|
||||
package updater
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"io"
|
||||
"net/http"
|
||||
"net/netip"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func Test_fetchAPI(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
testCases := map[string]struct {
|
||||
responseStatus int
|
||||
responseBody io.ReadCloser
|
||||
data apiData
|
||||
err error
|
||||
}{
|
||||
"http response status not ok": {
|
||||
responseStatus: http.StatusNoContent,
|
||||
err: errors.New("HTTP response status code is not OK: 204 No Content"),
|
||||
},
|
||||
"nil body": {
|
||||
responseStatus: http.StatusOK,
|
||||
err: errors.New("decoding response body: EOF"),
|
||||
},
|
||||
"no server": {
|
||||
responseStatus: http.StatusOK,
|
||||
responseBody: io.NopCloser(strings.NewReader(`{}`)),
|
||||
},
|
||||
"success": {
|
||||
responseStatus: http.StatusOK,
|
||||
responseBody: io.NopCloser(strings.NewReader(`{
|
||||
"success": true,
|
||||
"datacenters": [
|
||||
{
|
||||
"slug": "vienna",
|
||||
"city": "Vienna",
|
||||
"country": "AT",
|
||||
"country_name": "Austria",
|
||||
"pools": [
|
||||
"pool-1.prd.at.vienna.ovpn.com"
|
||||
],
|
||||
"ping_address": "37.120.212.227",
|
||||
"servers": [
|
||||
{
|
||||
"ip": "37.120.212.227",
|
||||
"ptr": "vpn44.prd.vienna.ovpn.com",
|
||||
"name": "VPN44 - Vienna",
|
||||
"online": true,
|
||||
"load": 8,
|
||||
"public_key": "r83LIc0Q2F8s3dY9x5y17Yz8wTADJc7giW1t5eSmoXc=",
|
||||
"public_key_ipv4": "wFbSRyjSXBmkjJodlqz7DoYn3WNDPYFUIXyIUS2QU2A=",
|
||||
"wireguard_ports": [
|
||||
9929
|
||||
],
|
||||
"multihop_openvpn_port": 20044,
|
||||
"multihop_wireguard_port": 30044
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}`)),
|
||||
data: apiData{
|
||||
Success: true,
|
||||
DataCenters: []apiDataCenter{
|
||||
{CountryName: "Austria", City: "Vienna", Servers: []apiServer{
|
||||
{
|
||||
IP: netip.MustParseAddr("37.120.212.227"),
|
||||
Ptr: "vpn44.prd.vienna.ovpn.com",
|
||||
Online: true,
|
||||
PublicKey: "r83LIc0Q2F8s3dY9x5y17Yz8wTADJc7giW1t5eSmoXc=",
|
||||
PublicKeyIPv4: "wFbSRyjSXBmkjJodlqz7DoYn3WNDPYFUIXyIUS2QU2A=",
|
||||
WireguardPorts: []uint16{9929},
|
||||
MultiHopOpenvpnPort: 20044,
|
||||
MultiHopWireguardPort: 30044,
|
||||
},
|
||||
}},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
for name, testCase := range testCases {
|
||||
t.Run(name, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
ctx := context.Background()
|
||||
|
||||
client := &http.Client{
|
||||
Transport: roundTripFunc(func(r *http.Request) (*http.Response, error) {
|
||||
assert.Equal(t, http.MethodGet, r.Method)
|
||||
assert.Equal(t, r.URL.String(), "https://www.ovpn.com/v2/api/client/entry")
|
||||
return &http.Response{
|
||||
StatusCode: testCase.responseStatus,
|
||||
Status: http.StatusText(testCase.responseStatus),
|
||||
Body: testCase.responseBody,
|
||||
}, nil
|
||||
}),
|
||||
}
|
||||
|
||||
data, err := fetchAPI(ctx, client)
|
||||
|
||||
assert.Equal(t, testCase.data, data)
|
||||
if testCase.err != nil {
|
||||
require.Error(t, err)
|
||||
assert.Equal(t, testCase.err.Error(), err.Error())
|
||||
} else {
|
||||
assert.NoError(t, err)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
package updater
|
||||
|
||||
import "net/http"
|
||||
|
||||
type roundTripFunc func(r *http.Request) (*http.Response, error)
|
||||
|
||||
func (f roundTripFunc) RoundTrip(r *http.Request) (*http.Response, error) {
|
||||
return f(r)
|
||||
}
|
||||
@@ -0,0 +1,82 @@
|
||||
package updater
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"net/netip"
|
||||
"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)
|
||||
} else if !data.Success {
|
||||
return nil, errors.New("response success field is false")
|
||||
}
|
||||
|
||||
for dataCenterIndex, dataCenter := range data.DataCenters {
|
||||
err = dataCenter.validate()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("validating data center %d of %d: %w",
|
||||
dataCenterIndex+1, len(data.DataCenters), err)
|
||||
}
|
||||
|
||||
for _, apiServer := range dataCenter.Servers {
|
||||
if !apiServer.Online {
|
||||
continue
|
||||
}
|
||||
|
||||
baseServer := models.Server{
|
||||
Country: dataCenter.CountryName,
|
||||
City: dataCenter.City,
|
||||
Hostname: apiServer.Ptr,
|
||||
IPs: []netip.Addr{apiServer.IP},
|
||||
}
|
||||
openVPNServer := baseServer
|
||||
openVPNServer.VPN = vpn.OpenVPN
|
||||
openVPNServer.TCP = true
|
||||
openVPNServer.UDP = true
|
||||
multiHopOpenVPNServer := openVPNServer
|
||||
multiHopOpenVPNServer.MultiHop = true
|
||||
multiHopOpenVPNServer.PortsTCP = []uint16{apiServer.MultiHopOpenvpnPort}
|
||||
multiHopOpenVPNServer.PortsUDP = []uint16{apiServer.MultiHopOpenvpnPort}
|
||||
servers = append(servers, openVPNServer, multiHopOpenVPNServer)
|
||||
|
||||
wireguardServer := baseServer
|
||||
wireguardServer.VPN = vpn.Wireguard
|
||||
wireguardServer.WgPubKey = apiServer.PublicKey
|
||||
multiHopWireguardServer := wireguardServer
|
||||
multiHopWireguardServer.MultiHop = true
|
||||
multiHopWireguardServer.PortsUDP = []uint16{apiServer.MultiHopWireguardPort}
|
||||
dedicatedWireguardServer := wireguardServer
|
||||
dedicatedWireguardServer.WgPubKey = apiServer.PublicKeyIPv4
|
||||
dedicatedWireguardServer.Dedicated = true
|
||||
dedicatedMultiHopWireguardServer := multiHopWireguardServer
|
||||
dedicatedMultiHopWireguardServer.WgPubKey = apiServer.PublicKeyIPv4
|
||||
dedicatedMultiHopWireguardServer.Dedicated = true
|
||||
servers = append(servers,
|
||||
wireguardServer,
|
||||
multiHopWireguardServer,
|
||||
dedicatedWireguardServer,
|
||||
dedicatedMultiHopWireguardServer,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
@@ -0,0 +1,228 @@
|
||||
package updater
|
||||
|
||||
import (
|
||||
"context"
|
||||
"io"
|
||||
"net/http"
|
||||
"net/netip"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/qdm12/gluetun/internal/constants/vpn"
|
||||
"github.com/qdm12/gluetun/internal/models"
|
||||
"github.com/qdm12/gluetun/internal/provider/common"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func Test_Updater_FetchServers(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
testCases := map[string]struct {
|
||||
// Inputs
|
||||
minServers int
|
||||
|
||||
// From API
|
||||
responseStatus int
|
||||
responseBody string
|
||||
|
||||
// Output
|
||||
servers []models.Server
|
||||
errWrapped error
|
||||
errMessage string
|
||||
}{
|
||||
"http_response_error": {
|
||||
responseStatus: http.StatusNoContent,
|
||||
errMessage: "fetching API: HTTP response status code is not OK: 204 No Content",
|
||||
},
|
||||
"success_field_false": {
|
||||
responseStatus: http.StatusOK,
|
||||
responseBody: `{"success": false}`,
|
||||
errMessage: "response success field is false",
|
||||
},
|
||||
"validation_failed": {
|
||||
responseStatus: http.StatusOK,
|
||||
responseBody: `{
|
||||
"success": true,
|
||||
"datacenters": [
|
||||
{
|
||||
"city": "Vienna",
|
||||
"servers": [
|
||||
{}
|
||||
]
|
||||
}
|
||||
]
|
||||
}`,
|
||||
errMessage: "validating data center 1 of 1: data center Vienna: country name is not set",
|
||||
},
|
||||
"not_enough_servers": {
|
||||
minServers: 7,
|
||||
responseStatus: http.StatusOK,
|
||||
responseBody: `{
|
||||
"success": true,
|
||||
"datacenters": [
|
||||
{
|
||||
"city": "Vienna",
|
||||
"country_name": "Austria",
|
||||
"servers": [
|
||||
{
|
||||
"ip": "37.120.212.227",
|
||||
"ptr": "vpn44.prd.vienna.ovpn.com",
|
||||
"online": true,
|
||||
"public_key": "r83LIc0Q2F8s3dY9x5y17Yz8wTADJc7giW1t5eSmoXc=",
|
||||
"public_key_ipv4": "wFbSRyjSXBmkjJodlqz7DoYn3WNDPYFUIXyIUS2QU2A=",
|
||||
"wireguard_ports": [9929],
|
||||
"multihop_openvpn_port": 20044,
|
||||
"multihop_wireguard_port": 30044
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}`,
|
||||
errWrapped: common.ErrNotEnoughServers,
|
||||
// Wireguard + dedicated Wireguard + Wireguard multi-hop +
|
||||
// dedicated Wireguard multi-hop + OpenVPN + OpenVPN multi-hop
|
||||
errMessage: "not enough servers found: 6 and expected at least 7",
|
||||
},
|
||||
"success": {
|
||||
minServers: 4,
|
||||
responseBody: `{
|
||||
"success": true,
|
||||
"datacenters": [
|
||||
{
|
||||
"slug": "vienna",
|
||||
"city": "Vienna",
|
||||
"country": "AT",
|
||||
"country_name": "Austria",
|
||||
"pools": [
|
||||
"pool-1.prd.at.vienna.ovpn.com"
|
||||
],
|
||||
"ping_address": "37.120.212.227",
|
||||
"servers": [
|
||||
{
|
||||
"ip": "37.120.212.227",
|
||||
"ptr": "vpn44.prd.vienna.ovpn.com",
|
||||
"name": "VPN44 - Vienna",
|
||||
"online": true,
|
||||
"load": 8,
|
||||
"public_key": "r83LIc0Q2F8s3dY9x5y17Yz8wTADJc7giW1t5eSmoXc=",
|
||||
"public_key_ipv4": "wFbSRyjSXBmkjJodlqz7DoYn3WNDPYFUIXyIUS2QU2A=",
|
||||
"wireguard_ports": [
|
||||
9929
|
||||
],
|
||||
"multihop_openvpn_port": 20044,
|
||||
"multihop_wireguard_port": 30044
|
||||
},
|
||||
{
|
||||
"ip": "37.120.212.228",
|
||||
"ptr": "vpn45.prd.vienna.ovpn.com",
|
||||
"online": false,
|
||||
"public_key": "r93LIc0Q2F8s3dY9x5y17Yz8wTADJc7giW1t5eSmoXc=",
|
||||
"public_key_ipv4": "wGbSRyjSXBmkjJodlqz7DoYn3WNDPYFUIXyIUS2QU2A=",
|
||||
"wireguard_ports": [9929],
|
||||
"multihop_openvpn_port": 20045,
|
||||
"multihop_wireguard_port": 30045
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}`,
|
||||
responseStatus: http.StatusOK,
|
||||
servers: []models.Server{
|
||||
{
|
||||
Country: "Austria",
|
||||
City: "Vienna",
|
||||
Hostname: "vpn44.prd.vienna.ovpn.com",
|
||||
IPs: []netip.Addr{netip.MustParseAddr("37.120.212.227")},
|
||||
VPN: vpn.OpenVPN,
|
||||
UDP: true,
|
||||
TCP: true,
|
||||
},
|
||||
{
|
||||
Country: "Austria",
|
||||
City: "Vienna",
|
||||
Hostname: "vpn44.prd.vienna.ovpn.com",
|
||||
IPs: []netip.Addr{netip.MustParseAddr("37.120.212.227")},
|
||||
VPN: vpn.OpenVPN,
|
||||
UDP: true,
|
||||
TCP: true,
|
||||
MultiHop: true,
|
||||
PortsTCP: []uint16{20044},
|
||||
PortsUDP: []uint16{20044},
|
||||
},
|
||||
{
|
||||
Country: "Austria",
|
||||
City: "Vienna",
|
||||
Hostname: "vpn44.prd.vienna.ovpn.com",
|
||||
IPs: []netip.Addr{netip.MustParseAddr("37.120.212.227")},
|
||||
VPN: vpn.Wireguard,
|
||||
WgPubKey: "r83LIc0Q2F8s3dY9x5y17Yz8wTADJc7giW1t5eSmoXc=",
|
||||
},
|
||||
{
|
||||
Country: "Austria",
|
||||
City: "Vienna",
|
||||
Hostname: "vpn44.prd.vienna.ovpn.com",
|
||||
IPs: []netip.Addr{netip.MustParseAddr("37.120.212.227")},
|
||||
VPN: vpn.Wireguard,
|
||||
WgPubKey: "r83LIc0Q2F8s3dY9x5y17Yz8wTADJc7giW1t5eSmoXc=",
|
||||
MultiHop: true,
|
||||
PortsUDP: []uint16{30044},
|
||||
},
|
||||
{
|
||||
Country: "Austria",
|
||||
City: "Vienna",
|
||||
Hostname: "vpn44.prd.vienna.ovpn.com",
|
||||
IPs: []netip.Addr{netip.MustParseAddr("37.120.212.227")},
|
||||
VPN: vpn.Wireguard,
|
||||
WgPubKey: "wFbSRyjSXBmkjJodlqz7DoYn3WNDPYFUIXyIUS2QU2A=",
|
||||
Dedicated: true,
|
||||
},
|
||||
{
|
||||
Country: "Austria",
|
||||
City: "Vienna",
|
||||
Hostname: "vpn44.prd.vienna.ovpn.com",
|
||||
IPs: []netip.Addr{netip.MustParseAddr("37.120.212.227")},
|
||||
VPN: vpn.Wireguard,
|
||||
WgPubKey: "wFbSRyjSXBmkjJodlqz7DoYn3WNDPYFUIXyIUS2QU2A=",
|
||||
MultiHop: true,
|
||||
Dedicated: true,
|
||||
PortsUDP: []uint16{30044},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
for name, testCase := range testCases {
|
||||
t.Run(name, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
ctx := context.Background()
|
||||
|
||||
client := &http.Client{
|
||||
Transport: roundTripFunc(func(r *http.Request) (*http.Response, error) {
|
||||
assert.Equal(t, http.MethodGet, r.Method)
|
||||
assert.Equal(t, r.URL.String(), "https://www.ovpn.com/v2/api/client/entry")
|
||||
return &http.Response{
|
||||
StatusCode: testCase.responseStatus,
|
||||
Status: http.StatusText(testCase.responseStatus),
|
||||
Body: io.NopCloser(strings.NewReader(testCase.responseBody)),
|
||||
}, nil
|
||||
}),
|
||||
}
|
||||
|
||||
updater := &Updater{
|
||||
client: client,
|
||||
}
|
||||
|
||||
servers, err := updater.FetchServers(ctx, testCase.minServers)
|
||||
|
||||
assert.Equal(t, testCase.servers, servers)
|
||||
if testCase.errMessage == "" {
|
||||
assert.NoError(t, err)
|
||||
} else {
|
||||
assert.Contains(t, err.Error(), testCase.errMessage)
|
||||
}
|
||||
if testCase.errWrapped != nil {
|
||||
assert.ErrorIs(t, err, testCase.errWrapped)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -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,
|
||||
}
|
||||
}
|
||||
@@ -20,6 +20,7 @@ import (
|
||||
"github.com/qdm12/gluetun/internal/provider/ivpn"
|
||||
"github.com/qdm12/gluetun/internal/provider/mullvad"
|
||||
"github.com/qdm12/gluetun/internal/provider/nordvpn"
|
||||
"github.com/qdm12/gluetun/internal/provider/ovpn"
|
||||
"github.com/qdm12/gluetun/internal/provider/perfectprivacy"
|
||||
"github.com/qdm12/gluetun/internal/provider/privado"
|
||||
"github.com/qdm12/gluetun/internal/provider/privateinternetaccess"
|
||||
@@ -67,6 +68,7 @@ func NewProviders(storage Storage, timeNow func() time.Time,
|
||||
providers.Ivpn: ivpn.New(storage, client, updaterWarner, parallelResolver),
|
||||
providers.Mullvad: mullvad.New(storage, client),
|
||||
providers.Nordvpn: nordvpn.New(storage, client, updaterWarner),
|
||||
providers.Ovpn: ovpn.New(storage, client),
|
||||
providers.Perfectprivacy: perfectprivacy.New(storage, unzipper, updaterWarner),
|
||||
providers.Privado: privado.New(storage, client, updaterWarner),
|
||||
providers.PrivateInternetAccess: privateinternetaccess.New(storage, timeNow, client),
|
||||
|
||||
@@ -52,8 +52,6 @@ func GetConnection(provider string,
|
||||
})
|
||||
|
||||
protocol := getProtocol(selection)
|
||||
port := getPort(selection, defaults.OpenVPNTCPPort,
|
||||
defaults.OpenVPNUDPPort, defaults.WireguardPort)
|
||||
|
||||
connections := make([]models.Connection, 0, len(servers))
|
||||
for _, server := range servers {
|
||||
@@ -69,6 +67,9 @@ func GetConnection(provider string,
|
||||
hostname = server.OvpnX509
|
||||
}
|
||||
|
||||
port := getPort(selection, server, defaults.OpenVPNTCPPort,
|
||||
defaults.OpenVPNUDPPort, defaults.WireguardPort)
|
||||
|
||||
connection := models.Connection{
|
||||
Type: selection.VPN,
|
||||
IP: ip,
|
||||
|
||||
@@ -6,29 +6,44 @@ import (
|
||||
"github.com/qdm12/gluetun/internal/configuration/settings"
|
||||
"github.com/qdm12/gluetun/internal/constants"
|
||||
"github.com/qdm12/gluetun/internal/constants/vpn"
|
||||
"github.com/qdm12/gluetun/internal/models"
|
||||
)
|
||||
|
||||
func getPort(selection settings.ServerSelection,
|
||||
func getPort(selection settings.ServerSelection, server models.Server,
|
||||
defaultOpenVPNTCP, defaultOpenVPNUDP, defaultWireguard uint16,
|
||||
) (port uint16) {
|
||||
switch selection.VPN {
|
||||
case vpn.Wireguard:
|
||||
customPort := *selection.Wireguard.EndpointPort
|
||||
if customPort > 0 {
|
||||
// Note: servers filtering ensures the custom port is within the
|
||||
// server ports defined if any is set.
|
||||
return customPort
|
||||
}
|
||||
|
||||
if len(server.PortsUDP) > 0 {
|
||||
defaultWireguard = server.PortsUDP[0]
|
||||
}
|
||||
checkDefined("Wireguard", defaultWireguard)
|
||||
return defaultWireguard
|
||||
default: // OpenVPN
|
||||
customPort := *selection.OpenVPN.CustomPort
|
||||
if customPort > 0 {
|
||||
// Note: servers filtering ensures the custom port is within the
|
||||
// server ports defined if any is set.
|
||||
return customPort
|
||||
}
|
||||
if selection.OpenVPN.Protocol == constants.TCP {
|
||||
if len(server.PortsTCP) > 0 {
|
||||
defaultOpenVPNTCP = server.PortsTCP[0]
|
||||
}
|
||||
checkDefined("OpenVPN TCP", defaultOpenVPNTCP)
|
||||
return defaultOpenVPNTCP
|
||||
}
|
||||
|
||||
if len(server.PortsUDP) > 0 {
|
||||
defaultOpenVPNUDP = server.PortsUDP[0]
|
||||
}
|
||||
checkDefined("OpenVPN UDP", defaultOpenVPNUDP)
|
||||
return defaultOpenVPNUDP
|
||||
}
|
||||
|
||||
@@ -6,6 +6,7 @@ import (
|
||||
"github.com/qdm12/gluetun/internal/configuration/settings"
|
||||
"github.com/qdm12/gluetun/internal/constants"
|
||||
"github.com/qdm12/gluetun/internal/constants/vpn"
|
||||
"github.com/qdm12/gluetun/internal/models"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
@@ -22,6 +23,7 @@ func Test_GetPort(t *testing.T) {
|
||||
|
||||
testCases := map[string]struct {
|
||||
selection settings.ServerSelection
|
||||
server models.Server
|
||||
defaultOpenVPNTCP uint16
|
||||
defaultOpenVPNUDP uint16
|
||||
defaultWireguard uint16
|
||||
@@ -48,6 +50,20 @@ func Test_GetPort(t *testing.T) {
|
||||
defaultWireguard: defaultWireguard,
|
||||
port: defaultOpenVPNUDP,
|
||||
},
|
||||
"OpenVPN_server_port_udp": {
|
||||
selection: settings.ServerSelection{
|
||||
VPN: vpn.OpenVPN,
|
||||
OpenVPN: settings.OpenVPNSelection{
|
||||
CustomPort: uint16Ptr(0),
|
||||
Protocol: constants.UDP,
|
||||
},
|
||||
},
|
||||
server: models.Server{
|
||||
PortsUDP: []uint16{1234},
|
||||
},
|
||||
defaultOpenVPNUDP: defaultOpenVPNUDP,
|
||||
port: 1234,
|
||||
},
|
||||
"OpenVPN UDP no default port defined": {
|
||||
selection: settings.ServerSelection{
|
||||
VPN: vpn.OpenVPN,
|
||||
@@ -88,6 +104,20 @@ func Test_GetPort(t *testing.T) {
|
||||
},
|
||||
port: 1234,
|
||||
},
|
||||
"OpenVPN_server_port_tcp": {
|
||||
selection: settings.ServerSelection{
|
||||
VPN: vpn.OpenVPN,
|
||||
OpenVPN: settings.OpenVPNSelection{
|
||||
CustomPort: uint16Ptr(0),
|
||||
Protocol: constants.TCP,
|
||||
},
|
||||
},
|
||||
server: models.Server{
|
||||
PortsTCP: []uint16{1234},
|
||||
},
|
||||
defaultOpenVPNTCP: defaultOpenVPNTCP,
|
||||
port: 1234,
|
||||
},
|
||||
"Wireguard": {
|
||||
selection: settings.ServerSelection{
|
||||
VPN: vpn.Wireguard,
|
||||
@@ -105,6 +135,19 @@ func Test_GetPort(t *testing.T) {
|
||||
defaultWireguard: defaultWireguard,
|
||||
port: 1234,
|
||||
},
|
||||
"Wireguard_server_port": {
|
||||
selection: settings.ServerSelection{
|
||||
VPN: vpn.Wireguard,
|
||||
Wireguard: settings.WireguardSelection{
|
||||
EndpointPort: uint16Ptr(0),
|
||||
},
|
||||
},
|
||||
server: models.Server{
|
||||
PortsUDP: []uint16{1234},
|
||||
},
|
||||
defaultWireguard: defaultWireguard,
|
||||
port: 1234,
|
||||
},
|
||||
"Wireguard no default port defined": {
|
||||
selection: settings.ServerSelection{
|
||||
VPN: vpn.Wireguard,
|
||||
@@ -120,6 +163,7 @@ func Test_GetPort(t *testing.T) {
|
||||
if testCase.panics != "" {
|
||||
assert.PanicsWithValue(t, testCase.panics, func() {
|
||||
_ = getPort(testCase.selection,
|
||||
testCase.server,
|
||||
testCase.defaultOpenVPNTCP,
|
||||
testCase.defaultOpenVPNUDP,
|
||||
testCase.defaultWireguard)
|
||||
@@ -128,6 +172,7 @@ func Test_GetPort(t *testing.T) {
|
||||
}
|
||||
|
||||
port := getPort(testCase.selection,
|
||||
testCase.server,
|
||||
testCase.defaultOpenVPNTCP,
|
||||
testCase.defaultOpenVPNUDP,
|
||||
testCase.defaultWireguard)
|
||||
|
||||
@@ -1,23 +1,15 @@
|
||||
package storage
|
||||
|
||||
import (
|
||||
"net/netip"
|
||||
"slices"
|
||||
|
||||
"github.com/qdm12/gluetun/internal/models"
|
||||
)
|
||||
|
||||
func copyServer(server models.Server) (serverCopy models.Server) {
|
||||
serverCopy = server
|
||||
serverCopy.IPs = copyIPs(server.IPs)
|
||||
serverCopy.IPs = slices.Clone(server.IPs)
|
||||
serverCopy.PortsTCP = slices.Clone(server.PortsTCP)
|
||||
serverCopy.PortsUDP = slices.Clone(server.PortsUDP)
|
||||
return serverCopy
|
||||
}
|
||||
|
||||
func copyIPs(toCopy []netip.Addr) (copied []netip.Addr) {
|
||||
if toCopy == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
copied = make([]netip.Addr, len(toCopy))
|
||||
copy(copied, toCopy)
|
||||
return copied
|
||||
}
|
||||
|
||||
@@ -21,43 +21,9 @@ func Test_copyServer(t *testing.T) {
|
||||
assert.Equal(t, server, serverCopy)
|
||||
// Check for mutation
|
||||
serverCopy.IPs[0] = netip.AddrFrom4([4]byte{9, 9, 9, 9})
|
||||
assert.NotEqual(t, server, serverCopy)
|
||||
}
|
||||
|
||||
func Test_copyIPs(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
testCases := map[string]struct {
|
||||
toCopy []netip.Addr
|
||||
copied []netip.Addr
|
||||
}{
|
||||
"nil": {},
|
||||
"empty": {
|
||||
toCopy: []netip.Addr{},
|
||||
copied: []netip.Addr{},
|
||||
},
|
||||
"single IP": {
|
||||
toCopy: []netip.Addr{netip.AddrFrom4([4]byte{1, 1, 1, 1})},
|
||||
copied: []netip.Addr{netip.AddrFrom4([4]byte{1, 1, 1, 1})},
|
||||
},
|
||||
"two IPs": {
|
||||
toCopy: []netip.Addr{netip.AddrFrom4([4]byte{1, 1, 1, 1}), netip.AddrFrom4([4]byte{2, 2, 2, 2})},
|
||||
copied: []netip.Addr{netip.AddrFrom4([4]byte{1, 1, 1, 1}), netip.AddrFrom4([4]byte{2, 2, 2, 2})},
|
||||
},
|
||||
}
|
||||
|
||||
for name, testCase := range testCases {
|
||||
t.Run(name, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
copied := copyIPs(testCase.toCopy)
|
||||
|
||||
assert.Equal(t, testCase.copied, copied)
|
||||
|
||||
if len(copied) > 0 {
|
||||
testCase.toCopy[0] = netip.AddrFrom4([4]byte{9, 9, 9, 9})
|
||||
assert.NotEqual(t, testCase.toCopy[0], testCase.copied[0])
|
||||
}
|
||||
})
|
||||
}
|
||||
serverCopy.PortsTCP = []uint16{80}
|
||||
serverCopy.PortsUDP = []uint16{53}
|
||||
assert.NotEqual(t, server.IPs, serverCopy.IPs)
|
||||
assert.NotEqual(t, server.PortsTCP, serverCopy.PortsTCP)
|
||||
assert.NotEqual(t, server.PortsUDP, serverCopy.PortsUDP)
|
||||
}
|
||||
|
||||
@@ -3,6 +3,7 @@ package storage
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"slices"
|
||||
"strings"
|
||||
|
||||
"github.com/qdm12/gluetun/internal/configuration/settings"
|
||||
@@ -48,6 +49,7 @@ func (s *Storage) FilterServers(provider string, selection settings.ServerSelect
|
||||
return servers, nil
|
||||
}
|
||||
|
||||
//nolint:gocognit,gocyclo
|
||||
func filterServer(server models.Server,
|
||||
selection settings.ServerSelection,
|
||||
) (filtered bool) {
|
||||
@@ -90,6 +92,11 @@ func filterServer(server models.Server,
|
||||
return true
|
||||
}
|
||||
|
||||
if (*selection.Dedicated && !server.Dedicated) ||
|
||||
(!*selection.Dedicated && server.Dedicated) {
|
||||
return false
|
||||
}
|
||||
|
||||
if filterByPossibilities(server.Country, selection.Countries) {
|
||||
return true
|
||||
}
|
||||
@@ -122,6 +129,14 @@ func filterServer(server models.Server,
|
||||
return true
|
||||
}
|
||||
|
||||
serverPorts := server.PortsUDP
|
||||
if server.VPN == vpn.OpenVPN && server.TCP {
|
||||
serverPorts = server.PortsTCP
|
||||
}
|
||||
if filterByPorts(selection, serverPorts) {
|
||||
return true
|
||||
}
|
||||
|
||||
// TODO filter port forward server for PIA
|
||||
|
||||
return false
|
||||
@@ -165,3 +180,21 @@ func filterByProtocol(selection settings.ServerSelection,
|
||||
return (wantTCP && !serverTCP) || (wantUDP && !serverUDP)
|
||||
}
|
||||
}
|
||||
|
||||
func filterByPorts(selection settings.ServerSelection,
|
||||
serverPorts []uint16,
|
||||
) (filtered bool) {
|
||||
if len(serverPorts) == 0 {
|
||||
return false
|
||||
}
|
||||
|
||||
customPort := *selection.OpenVPN.CustomPort
|
||||
if selection.VPN == vpn.Wireguard {
|
||||
customPort = *selection.Wireguard.EndpointPort
|
||||
}
|
||||
if customPort == 0 {
|
||||
return false
|
||||
}
|
||||
|
||||
return !slices.Contains(serverPorts, customPort)
|
||||
}
|
||||
|
||||
@@ -14,7 +14,7 @@ func commaJoin(slice []string) string {
|
||||
return strings.Join(slice, ", ")
|
||||
}
|
||||
|
||||
func noServerFoundError(selection settings.ServerSelection) (err error) {
|
||||
func noServerFoundError(selection settings.ServerSelection) (err error) { //nolint:gocyclo
|
||||
var messageParts []string
|
||||
|
||||
messageParts = append(messageParts, "VPN "+selection.VPN)
|
||||
@@ -155,6 +155,15 @@ func noServerFoundError(selection settings.ServerSelection) (err error) {
|
||||
"target ip address "+targetIP.String())
|
||||
}
|
||||
|
||||
customPort := *selection.OpenVPN.CustomPort
|
||||
if selection.VPN == vpn.Wireguard {
|
||||
customPort = *selection.Wireguard.EndpointPort
|
||||
}
|
||||
if customPort > 0 {
|
||||
messageParts = append(messageParts,
|
||||
fmt.Sprintf("%s endpoint port %d", selection.VPN, customPort))
|
||||
}
|
||||
|
||||
message := "for " + strings.Join(messageParts, "; ")
|
||||
|
||||
return fmt.Errorf("no server found: %s", message)
|
||||
|
||||
@@ -20,7 +20,8 @@ func parseHardcodedServers() (allServers models.AllServers) {
|
||||
filename := provider + ".json"
|
||||
providerFile, err := serversmodule.Files.Open(filename)
|
||||
if err != nil {
|
||||
panic(fmt.Sprintf("reading embedded provider file %s for %s: %s", filename, provider, err))
|
||||
const rootURL = "https://github.com/qdm12/gluetun-servers/blob/main/pkg/servers"
|
||||
panic(fmt.Sprintf("reading embedded provider file defined at %s/%s: %s", rootURL, filename, err))
|
||||
}
|
||||
defer providerFile.Close() // no-op
|
||||
|
||||
|
||||
@@ -33,7 +33,10 @@ func Test_parseHardcodedServers(t *testing.T) {
|
||||
func Test_parseHardcodedServers_filepathsAndEmbeddedProviderFiles(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
hardcodedServers := parseHardcodedServers()
|
||||
var hardcodedServers models.AllServers
|
||||
require.NotPanics(t, func() {
|
||||
hardcodedServers = parseHardcodedServers()
|
||||
})
|
||||
|
||||
allProviders := providers.All()
|
||||
for _, provider := range allProviders {
|
||||
|
||||
Reference in New Issue
Block a user