Compare commits

..

11 Commits

Author SHA1 Message Date
Quentin McGaw 4a670635c4 log out json from dns leak test 2026-06-24 22:25:32 +00:00
Quentin McGaw b5f815640e SERVER_DEDICATED option 2026-06-24 22:12:30 +00:00
Quentin McGaw 506c9140ba PR feedback 2026-06-24 22:12:30 +00:00
Quentin McGaw 80763145dc Remove unneeded allow-compression asym 2026-06-24 22:12:30 +00:00
Quentin McGaw cfb8fcf00f Add missing "key-direction 1" 2026-06-24 22:12:30 +00:00
Quentin McGaw b9421221da Set TLS crypt for Singapore hostnames only 2026-06-24 22:12:30 +00:00
Quentin McGaw 83feb533de Add missing openvpn options
- CA
- TLS auth
- TLS crypt (for singapore)
- `allow-compression asym`
- `replay-window 256`
- remote-cert-tls server
- move aes256gcm as preferred cipher
2026-06-24 22:12:30 +00:00
Quentin McGaw 1b12dcb5b4 initial code 2026-06-24 22:12:29 +00:00
dependabot[bot] a17591dcdb Chore(deps): Bump github.com/mdlayher/genetlink from 1.3.2 to 1.4.0 (#3348) 2026-06-24 21:51:14 +02:00
dependabot[bot] c31c566282 Chore(deps): Bump golang.org/x/text from 0.37.0 to 0.38.0 (#3362) 2026-06-24 21:50:47 +02:00
dependabot[bot] 496458ca4e Chore(deps): Bump github.com/amnezia-vpn/amneziawg-go from 0.2.16 to 0.2.18 (#3347) 2026-06-24 21:50:27 +02:00
33 changed files with 1033 additions and 113 deletions
+1
View File
@@ -56,6 +56,7 @@ body:
- IVPN
- Mullvad
- NordVPN
- OVPN
- Privado
- Private Internet Access
- PrivateVPN
+2
View File
@@ -64,6 +64,8 @@
color: "cfe8d4"
- name: "☁️ NordVPN"
color: "cfe8d4"
- name: "☁️ OVPN"
color: "cfe8d4"
- name: "☁️ Perfect Privacy"
color: "cfe8d4"
- name: "☁️ PIA"
+3 -1
View File
@@ -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= \
+2 -2
View File
@@ -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)
+14 -14
View File
@@ -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
+30 -28
View File
@@ -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,
+8 -4
View File
@@ -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)
}
+3
View File
@@ -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) {
+15
View File
@@ -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)
}
+126
View File
@@ -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)
})
}
}
+38
View File
@@ -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)
}
+28
View File
@@ -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
}
+153
View File
@@ -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, "; "))
}
+118
View File
@@ -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)
}
+82
View File
@@ -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)
}
})
}
}
+15
View File
@@ -0,0 +1,15 @@
package updater
import (
"net/http"
)
type Updater struct {
client *http.Client
}
func New(client *http.Client) *Updater {
return &Updater{
client: client,
}
}
+2
View File
@@ -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),
+3 -2
View File
@@ -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,
+16 -1
View File
@@ -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
}
+45
View File
@@ -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)
+4 -12
View File
@@ -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
}
+5 -39
View File
@@ -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)
}
+33
View File
@@ -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)
}
+10 -1
View File
@@ -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)
+2 -1
View File
@@ -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
+4 -1
View File
@@ -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 {