Compare commits
1 Commits
ovpn
...
openvpn-2.
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
5f3301f3a3 |
1
.github/ISSUE_TEMPLATE/bug.yml
vendored
1
.github/ISSUE_TEMPLATE/bug.yml
vendored
@@ -56,7 +56,6 @@ body:
|
|||||||
- IVPN
|
- IVPN
|
||||||
- Mullvad
|
- Mullvad
|
||||||
- NordVPN
|
- NordVPN
|
||||||
- OVPN
|
|
||||||
- Privado
|
- Privado
|
||||||
- Private Internet Access
|
- Private Internet Access
|
||||||
- PrivateVPN
|
- PrivateVPN
|
||||||
|
|||||||
2
.github/labels.yml
vendored
2
.github/labels.yml
vendored
@@ -62,8 +62,6 @@
|
|||||||
color: "cfe8d4"
|
color: "cfe8d4"
|
||||||
- name: "☁️ NordVPN"
|
- name: "☁️ NordVPN"
|
||||||
color: "cfe8d4"
|
color: "cfe8d4"
|
||||||
- name: "☁️ OVPN"
|
|
||||||
color: "cfe8d4"
|
|
||||||
- name: "☁️ Perfect Privacy"
|
- name: "☁️ Perfect Privacy"
|
||||||
color: "cfe8d4"
|
color: "cfe8d4"
|
||||||
- name: "☁️ PIA"
|
- name: "☁️ PIA"
|
||||||
|
|||||||
2
.github/workflows/ci.yml
vendored
2
.github/workflows/ci.yml
vendored
@@ -59,7 +59,7 @@ jobs:
|
|||||||
- name: Run tests in test container
|
- name: Run tests in test container
|
||||||
run: |
|
run: |
|
||||||
touch coverage.txt
|
touch coverage.txt
|
||||||
docker run --rm --device /dev/net/tun \
|
docker run --rm \
|
||||||
-v "$(pwd)/coverage.txt:/tmp/gobuild/coverage.txt" \
|
-v "$(pwd)/coverage.txt:/tmp/gobuild/coverage.txt" \
|
||||||
test-container
|
test-container
|
||||||
|
|
||||||
|
|||||||
2
.github/workflows/markdown.yml
vendored
2
.github/workflows/markdown.yml
vendored
@@ -20,7 +20,7 @@ jobs:
|
|||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v4
|
||||||
|
|
||||||
- uses: DavidAnson/markdownlint-cli2-action@v18
|
- uses: DavidAnson/markdownlint-cli2-action@v16
|
||||||
with:
|
with:
|
||||||
globs: "**.md"
|
globs: "**.md"
|
||||||
config: .markdownlint.json
|
config: .markdownlint.json
|
||||||
|
|||||||
@@ -85,7 +85,7 @@ ENV VPN_SERVICE_PROVIDER=pia \
|
|||||||
OPENVPN_PASSWORD= \
|
OPENVPN_PASSWORD= \
|
||||||
OPENVPN_USER_SECRETFILE=/run/secrets/openvpn_user \
|
OPENVPN_USER_SECRETFILE=/run/secrets/openvpn_user \
|
||||||
OPENVPN_PASSWORD_SECRETFILE=/run/secrets/openvpn_password \
|
OPENVPN_PASSWORD_SECRETFILE=/run/secrets/openvpn_password \
|
||||||
OPENVPN_VERSION=2.6 \
|
OPENVPN_VERSION=2.4 \
|
||||||
OPENVPN_VERBOSITY=1 \
|
OPENVPN_VERBOSITY=1 \
|
||||||
OPENVPN_FLAGS= \
|
OPENVPN_FLAGS= \
|
||||||
OPENVPN_CIPHERS= \
|
OPENVPN_CIPHERS= \
|
||||||
@@ -125,8 +125,6 @@ ENV VPN_SERVICE_PROVIDER=pia \
|
|||||||
VPN_PORT_FORWARDING_STATUS_FILE="/tmp/gluetun/forwarded_port" \
|
VPN_PORT_FORWARDING_STATUS_FILE="/tmp/gluetun/forwarded_port" \
|
||||||
VPN_PORT_FORWARDING_USERNAME= \
|
VPN_PORT_FORWARDING_USERNAME= \
|
||||||
VPN_PORT_FORWARDING_PASSWORD= \
|
VPN_PORT_FORWARDING_PASSWORD= \
|
||||||
VPN_PORT_FORWARDING_UP_COMMAND= \
|
|
||||||
VPN_PORT_FORWARDING_DOWN_COMMAND= \
|
|
||||||
# # Cyberghost only:
|
# # Cyberghost only:
|
||||||
OPENVPN_CERT= \
|
OPENVPN_CERT= \
|
||||||
OPENVPN_KEY= \
|
OPENVPN_KEY= \
|
||||||
@@ -147,7 +145,7 @@ ENV VPN_SERVICE_PROVIDER=pia \
|
|||||||
# # ProtonVPN only:
|
# # ProtonVPN only:
|
||||||
SECURE_CORE_ONLY= \
|
SECURE_CORE_ONLY= \
|
||||||
TOR_ONLY= \
|
TOR_ONLY= \
|
||||||
# # Surfshark and ovpn only:
|
# # Surfshark only:
|
||||||
MULTIHOP_ONLY= \
|
MULTIHOP_ONLY= \
|
||||||
# # VPN Secure only:
|
# # VPN Secure only:
|
||||||
PREMIUM_ONLY= \
|
PREMIUM_ONLY= \
|
||||||
@@ -226,6 +224,9 @@ EXPOSE 8000/tcp 8888/tcp 8388/tcp 8388/udp
|
|||||||
HEALTHCHECK --interval=5s --timeout=5s --start-period=10s --retries=3 CMD /gluetun-entrypoint healthcheck
|
HEALTHCHECK --interval=5s --timeout=5s --start-period=10s --retries=3 CMD /gluetun-entrypoint healthcheck
|
||||||
ARG TARGETPLATFORM
|
ARG TARGETPLATFORM
|
||||||
RUN apk add --no-cache --update -l wget && \
|
RUN apk add --no-cache --update -l wget && \
|
||||||
|
apk add --no-cache --update -X "https://dl-cdn.alpinelinux.org/alpine/v3.12/main" openvpn\~2.4 && \
|
||||||
|
apk add --no-cache --update -X "https://dl-cdn.alpinelinux.org/alpine/v3.16/main" openssl\~1.1 && \
|
||||||
|
mv /usr/sbin/openvpn /usr/sbin/openvpn2.4 && \
|
||||||
apk add --no-cache --update -X "https://dl-cdn.alpinelinux.org/alpine/v3.17/main" openvpn\~2.5 && \
|
apk add --no-cache --update -X "https://dl-cdn.alpinelinux.org/alpine/v3.17/main" openvpn\~2.5 && \
|
||||||
mv /usr/sbin/openvpn /usr/sbin/openvpn2.5 && \
|
mv /usr/sbin/openvpn /usr/sbin/openvpn2.5 && \
|
||||||
apk del openvpn && \
|
apk del openvpn && \
|
||||||
|
|||||||
@@ -57,10 +57,10 @@ Lightweight swiss-knife-like VPN client to multiple VPN service providers
|
|||||||
## Features
|
## Features
|
||||||
|
|
||||||
- Based on Alpine 3.20 for a small Docker image of 35.6MB
|
- Based on Alpine 3.20 for a small Docker image of 35.6MB
|
||||||
- Supports: **AirVPN**, **Cyberghost**, **ExpressVPN**, **FastestVPN**, **Giganews**, **HideMyAss**, **IPVanish**, **IVPN**, **Mullvad**, **NordVPN**, **Ovpn**, **Perfect Privacy**, **Privado**, **Private Internet Access**, **PrivateVPN**, **ProtonVPN**, **PureVPN**, **SlickVPN**, **Surfshark**, **TorGuard**, **VPNSecure.me**, **VPNUnlimited**, **Vyprvpn**, **WeVPN**, **Windscribe** servers
|
- Supports: **AirVPN**, **Cyberghost**, **ExpressVPN**, **FastestVPN**, **Giganews**, **HideMyAss**, **IPVanish**, **IVPN**, **Mullvad**, **NordVPN**, **Perfect Privacy**, **Privado**, **Private Internet Access**, **PrivateVPN**, **ProtonVPN**, **PureVPN**, **SlickVPN**, **Surfshark**, **TorGuard**, **VPNSecure.me**, **VPNUnlimited**, **Vyprvpn**, **WeVPN**, **Windscribe** servers
|
||||||
- Supports OpenVPN for all providers listed
|
- Supports OpenVPN for all providers listed
|
||||||
- Supports Wireguard both kernelspace and userspace
|
- Supports Wireguard both kernelspace and userspace
|
||||||
- For **AirVPN**, **FastestVPN**, **Ivpn**, **Mullvad**, **NordVPN**, **Ovpn**, **Perfect privacy**, **ProtonVPN**, **Surfshark** and **Windscribe**
|
- For **AirVPN**, **FastestVPN**, **Ivpn**, **Mullvad**, **NordVPN**, **Perfect privacy**, **ProtonVPN**, **Surfshark** and **Windscribe**
|
||||||
- For **Cyberghost**, **Private Internet Access**, **PrivateVPN**, **PureVPN**, **Torguard**, **VPN Unlimited**, **VyprVPN** and **WeVPN** using [the custom provider](https://github.com/qdm12/gluetun-wiki/blob/main/setup/providers/custom.md)
|
- For **Cyberghost**, **Private Internet Access**, **PrivateVPN**, **PureVPN**, **Torguard**, **VPN Unlimited**, **VyprVPN** and **WeVPN** using [the custom provider](https://github.com/qdm12/gluetun-wiki/blob/main/setup/providers/custom.md)
|
||||||
- For custom Wireguard configurations using [the custom provider](https://github.com/qdm12/gluetun-wiki/blob/main/setup/providers/custom.md)
|
- For custom Wireguard configurations using [the custom provider](https://github.com/qdm12/gluetun-wiki/blob/main/setup/providers/custom.md)
|
||||||
- More in progress, see [#134](https://github.com/qdm12/gluetun/issues/134)
|
- More in progress, see [#134](https://github.com/qdm12/gluetun/issues/134)
|
||||||
|
|||||||
@@ -270,6 +270,7 @@ func _main(ctx context.Context, buildInfo models.BuildInformation,
|
|||||||
|
|
||||||
err = printVersions(ctx, logger, []printVersionElement{
|
err = printVersions(ctx, logger, []printVersionElement{
|
||||||
{name: "Alpine", getVersion: alpineConf.Version},
|
{name: "Alpine", getVersion: alpineConf.Version},
|
||||||
|
{name: "OpenVPN 2.4", getVersion: ovpnConf.Version24},
|
||||||
{name: "OpenVPN 2.5", getVersion: ovpnConf.Version25},
|
{name: "OpenVPN 2.5", getVersion: ovpnConf.Version25},
|
||||||
{name: "OpenVPN 2.6", getVersion: ovpnConf.Version26},
|
{name: "OpenVPN 2.6", getVersion: ovpnConf.Version26},
|
||||||
{name: "IPtables", getVersion: firewallConf.Version},
|
{name: "IPtables", getVersion: firewallConf.Version},
|
||||||
@@ -380,7 +381,7 @@ func _main(ctx context.Context, buildInfo models.BuildInformation,
|
|||||||
|
|
||||||
portForwardLogger := logger.New(log.SetComponent("port forwarding"))
|
portForwardLogger := logger.New(log.SetComponent("port forwarding"))
|
||||||
portForwardLooper := portforward.NewLoop(allSettings.VPN.Provider.PortForwarding,
|
portForwardLooper := portforward.NewLoop(allSettings.VPN.Provider.PortForwarding,
|
||||||
routingConf, httpClient, firewallConf, portForwardLogger, cmder, puid, pgid)
|
routingConf, httpClient, firewallConf, portForwardLogger, puid, pgid)
|
||||||
portForwardRunError, err := portForwardLooper.Start(ctx)
|
portForwardRunError, err := portForwardLooper.Start(ctx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("starting port forwarding loop: %w", err)
|
return fmt.Errorf("starting port forwarding loop: %w", err)
|
||||||
|
|||||||
18
go.mod
18
go.mod
@@ -3,27 +3,27 @@ module github.com/qdm12/gluetun
|
|||||||
go 1.23
|
go 1.23
|
||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/breml/rootcerts v0.2.19
|
github.com/breml/rootcerts v0.2.18
|
||||||
github.com/fatih/color v1.18.0
|
github.com/fatih/color v1.18.0
|
||||||
github.com/golang/mock v1.6.0
|
github.com/golang/mock v1.6.0
|
||||||
github.com/klauspost/compress v1.17.11
|
github.com/klauspost/compress v1.17.11
|
||||||
github.com/klauspost/pgzip v1.2.6
|
github.com/klauspost/pgzip v1.2.6
|
||||||
github.com/pelletier/go-toml/v2 v2.2.3
|
github.com/pelletier/go-toml/v2 v2.2.2
|
||||||
github.com/qdm12/dns/v2 v2.0.0-rc8
|
github.com/qdm12/dns/v2 v2.0.0-rc8
|
||||||
github.com/qdm12/gosettings v0.4.4
|
github.com/qdm12/gosettings v0.4.3
|
||||||
github.com/qdm12/goshutdown v0.3.0
|
github.com/qdm12/goshutdown v0.3.0
|
||||||
github.com/qdm12/gosplash v0.2.0
|
github.com/qdm12/gosplash v0.2.0
|
||||||
github.com/qdm12/gotree v0.3.0
|
github.com/qdm12/gotree v0.3.0
|
||||||
github.com/qdm12/log v0.1.0
|
github.com/qdm12/log v0.1.0
|
||||||
github.com/qdm12/ss-server v0.6.0
|
github.com/qdm12/ss-server v0.6.0
|
||||||
github.com/stretchr/testify v1.10.0
|
github.com/stretchr/testify v1.9.0
|
||||||
github.com/ulikunitz/xz v0.5.11
|
github.com/ulikunitz/xz v0.5.11
|
||||||
github.com/vishvananda/netlink v1.2.1
|
github.com/vishvananda/netlink v1.2.1
|
||||||
github.com/youmark/pkcs8 v0.0.0-20201027041543-1326539a0a0a
|
github.com/youmark/pkcs8 v0.0.0-20201027041543-1326539a0a0a
|
||||||
golang.org/x/exp v0.0.0-20241009180824-f66d83c29e7c
|
golang.org/x/exp v0.0.0-20241009180824-f66d83c29e7c
|
||||||
golang.org/x/net v0.31.0
|
golang.org/x/net v0.30.0
|
||||||
golang.org/x/sys v0.27.0
|
golang.org/x/sys v0.26.0
|
||||||
golang.org/x/text v0.20.0
|
golang.org/x/text v0.19.0
|
||||||
golang.zx2c4.com/wireguard v0.0.0-20231211153847-12269c276173
|
golang.zx2c4.com/wireguard v0.0.0-20231211153847-12269c276173
|
||||||
golang.zx2c4.com/wireguard/wgctrl v0.0.0-20230429144221-925a1e7659e6
|
golang.zx2c4.com/wireguard/wgctrl v0.0.0-20230429144221-925a1e7659e6
|
||||||
gopkg.in/ini.v1 v1.67.0
|
gopkg.in/ini.v1 v1.67.0
|
||||||
@@ -50,9 +50,9 @@ require (
|
|||||||
github.com/qdm12/goservices v0.1.0 // indirect
|
github.com/qdm12/goservices v0.1.0 // indirect
|
||||||
github.com/riobard/go-bloom v0.0.0-20200614022211-cdc8013cb5b3 // indirect
|
github.com/riobard/go-bloom v0.0.0-20200614022211-cdc8013cb5b3 // indirect
|
||||||
github.com/vishvananda/netns v0.0.4 // indirect
|
github.com/vishvananda/netns v0.0.4 // indirect
|
||||||
golang.org/x/crypto v0.29.0 // indirect
|
golang.org/x/crypto v0.28.0 // indirect
|
||||||
golang.org/x/mod v0.21.0 // indirect
|
golang.org/x/mod v0.21.0 // indirect
|
||||||
golang.org/x/sync v0.9.0 // indirect
|
golang.org/x/sync v0.8.0 // indirect
|
||||||
golang.org/x/tools v0.26.0 // indirect
|
golang.org/x/tools v0.26.0 // indirect
|
||||||
golang.zx2c4.com/wintun v0.0.0-20230126152724-0fa3db229ce2 // indirect
|
golang.zx2c4.com/wintun v0.0.0-20230126152724-0fa3db229ce2 // indirect
|
||||||
google.golang.org/protobuf v1.35.1 // indirect
|
google.golang.org/protobuf v1.35.1 // indirect
|
||||||
|
|||||||
45
go.sum
45
go.sum
@@ -1,9 +1,10 @@
|
|||||||
github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=
|
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/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=
|
||||||
github.com/breml/rootcerts v0.2.19 h1:3D/qwAC1xoh82GmZ21mYzQ1NaLOICUVntIo+MRZYr4U=
|
github.com/breml/rootcerts v0.2.18 h1:KjZaNT7AX/akUjzpStuwTMQs42YHlPyc6NmdwShVba0=
|
||||||
github.com/breml/rootcerts v0.2.19/go.mod h1:S/PKh+4d1HUn4HQovEB8hPJZO6pUZYrIhmXBhsegfXw=
|
github.com/breml/rootcerts v0.2.18/go.mod h1:S/PKh+4d1HUn4HQovEB8hPJZO6pUZYrIhmXBhsegfXw=
|
||||||
github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs=
|
github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs=
|
||||||
github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
|
github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
|
||||||
|
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 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
github.com/fatih/color v1.18.0 h1:S8gINlzdQ840/4pfAwic/ZE0djQEH3wM94VfqLTZcOM=
|
github.com/fatih/color v1.18.0 h1:S8gINlzdQ840/4pfAwic/ZE0djQEH3wM94VfqLTZcOM=
|
||||||
@@ -41,8 +42,8 @@ github.com/mikioh/ipaddr v0.0.0-20190404000644-d465c8ab6721 h1:RlZweED6sbSArvlE9
|
|||||||
github.com/mikioh/ipaddr v0.0.0-20190404000644-d465c8ab6721/go.mod h1:Ickgr2WtCLZ2MDGd4Gr0geeCH5HybhRJbonOgQpvSxc=
|
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 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA=
|
||||||
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ=
|
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ=
|
||||||
github.com/pelletier/go-toml/v2 v2.2.3 h1:YmeHyLY8mFWbdkNWwpr+qIL2bEqT0o95WSdkNHvL12M=
|
github.com/pelletier/go-toml/v2 v2.2.2 h1:aYUidT7k73Pcl9nb2gScu7NSrKCSHIDE89b3+6Wq+LM=
|
||||||
github.com/pelletier/go-toml/v2 v2.2.3/go.mod h1:MfCQTFTvCcUyyvvwm1+G6H/jORL20Xlb6rzQu9GuUkc=
|
github.com/pelletier/go-toml/v2 v2.2.2/go.mod h1:1t835xjRzz80PqgE6HHgN2JOsmgYu/h4qDAS4n929Rs=
|
||||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||||
github.com/prometheus/client_golang v1.20.5 h1:cxppBPuYhUnsO6yo/aoRol4L7q7UFfdm+bR9r+8l63Y=
|
github.com/prometheus/client_golang v1.20.5 h1:cxppBPuYhUnsO6yo/aoRol4L7q7UFfdm+bR9r+8l63Y=
|
||||||
@@ -57,8 +58,8 @@ github.com/qdm12/dns/v2 v2.0.0-rc8 h1:kbgKPkbT+79nScfuZ0ZcVhksTGo8IUqQ8TTQGnQlZ1
|
|||||||
github.com/qdm12/dns/v2 v2.0.0-rc8/go.mod h1:VaF02KWEL7xNV4oKfG4N9nEv/kR6bqyIcBReCV5NJhw=
|
github.com/qdm12/dns/v2 v2.0.0-rc8/go.mod h1:VaF02KWEL7xNV4oKfG4N9nEv/kR6bqyIcBReCV5NJhw=
|
||||||
github.com/qdm12/goservices v0.1.0 h1:9sODefm/yuIGS7ynCkEnNlMTAYn9GzPhtcK4F69JWvc=
|
github.com/qdm12/goservices v0.1.0 h1:9sODefm/yuIGS7ynCkEnNlMTAYn9GzPhtcK4F69JWvc=
|
||||||
github.com/qdm12/goservices v0.1.0/go.mod h1:/JOFsAnHFiSjyoXxa5FlfX903h20K5u/3rLzCjYVMck=
|
github.com/qdm12/goservices v0.1.0/go.mod h1:/JOFsAnHFiSjyoXxa5FlfX903h20K5u/3rLzCjYVMck=
|
||||||
github.com/qdm12/gosettings v0.4.4 h1:SM6tOZDf6k8qbjWU8KWyBF4mWIixfsKCfh9DGRLHlj4=
|
github.com/qdm12/gosettings v0.4.3 h1:oGAjiKVtml9oHVlPQo6H3yk6TmtWpVYicNeGFcM7AP8=
|
||||||
github.com/qdm12/gosettings v0.4.4/go.mod h1:CPrt2YC4UsURTrslmhxocVhMCW03lIrqdH2hzIf5prg=
|
github.com/qdm12/gosettings v0.4.3/go.mod h1:CPrt2YC4UsURTrslmhxocVhMCW03lIrqdH2hzIf5prg=
|
||||||
github.com/qdm12/goshutdown v0.3.0 h1:pqBpJkdwlZlfTEx4QHtS8u8CXx6pG0fVo6S1N0MpSEM=
|
github.com/qdm12/goshutdown v0.3.0 h1:pqBpJkdwlZlfTEx4QHtS8u8CXx6pG0fVo6S1N0MpSEM=
|
||||||
github.com/qdm12/goshutdown v0.3.0/go.mod h1:EqZ46No00kCTZ5qzdd3qIzY6ayhMt24QI8Mh8LVQYmM=
|
github.com/qdm12/goshutdown v0.3.0/go.mod h1:EqZ46No00kCTZ5qzdd3qIzY6ayhMt24QI8Mh8LVQYmM=
|
||||||
github.com/qdm12/gosplash v0.2.0 h1:DOxCEizbW6ZG+FgpH2oK1atT6bM8MHL9GZ2ywSS4zZY=
|
github.com/qdm12/gosplash v0.2.0 h1:DOxCEizbW6ZG+FgpH2oK1atT6bM8MHL9GZ2ywSS4zZY=
|
||||||
@@ -73,8 +74,15 @@ github.com/riobard/go-bloom v0.0.0-20200614022211-cdc8013cb5b3 h1:f/FNXud6gA3MNr
|
|||||||
github.com/riobard/go-bloom v0.0.0-20200614022211-cdc8013cb5b3/go.mod h1:HgjTstvQsPGkxUsCd2KWxErBblirPizecHcpD3ffK+s=
|
github.com/riobard/go-bloom v0.0.0-20200614022211-cdc8013cb5b3/go.mod h1:HgjTstvQsPGkxUsCd2KWxErBblirPizecHcpD3ffK+s=
|
||||||
github.com/rogpeppe/go-internal v1.13.1 h1:KvO1DLK/DRN07sQ1LQKScxyZJuNnedQ5/wKSR38lUII=
|
github.com/rogpeppe/go-internal v1.13.1 h1:KvO1DLK/DRN07sQ1LQKScxyZJuNnedQ5/wKSR38lUII=
|
||||||
github.com/rogpeppe/go-internal v1.13.1/go.mod h1:uMEvuHeurkdAXX61udpOXGD/AzZDWNMNyH2VO9fmH0o=
|
github.com/rogpeppe/go-internal v1.13.1/go.mod h1:uMEvuHeurkdAXX61udpOXGD/AzZDWNMNyH2VO9fmH0o=
|
||||||
github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA=
|
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||||
github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
|
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
|
||||||
|
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
|
||||||
|
github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA=
|
||||||
|
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||||
|
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
|
||||||
|
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
|
||||||
|
github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=
|
||||||
|
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
|
||||||
github.com/ulikunitz/xz v0.5.11 h1:kpFauv27b6ynzBNT/Xy+1k+fK4WswhN/6PN5WhFAGw8=
|
github.com/ulikunitz/xz v0.5.11 h1:kpFauv27b6ynzBNT/Xy+1k+fK4WswhN/6PN5WhFAGw8=
|
||||||
github.com/ulikunitz/xz v0.5.11/go.mod h1:nbz6k7qbPmH4IRqmfOplQw/tblSgqTqBwxkY0oWt/14=
|
github.com/ulikunitz/xz v0.5.11/go.mod h1:nbz6k7qbPmH4IRqmfOplQw/tblSgqTqBwxkY0oWt/14=
|
||||||
github.com/vishvananda/netlink v1.2.1 h1:pfLv/qlJUwOTPvtWREA7c3PI4u81YkqZw1DYhI2HmLA=
|
github.com/vishvananda/netlink v1.2.1 h1:pfLv/qlJUwOTPvtWREA7c3PI4u81YkqZw1DYhI2HmLA=
|
||||||
@@ -87,8 +95,8 @@ github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1
|
|||||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||||
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||||
golang.org/x/crypto v0.0.0-20200302210943-78000ba7a073/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
golang.org/x/crypto v0.0.0-20200302210943-78000ba7a073/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||||
golang.org/x/crypto v0.29.0 h1:L5SG1JTTXupVV3n6sUqMTeWbjAyfPwoda2DLX8J8FrQ=
|
golang.org/x/crypto v0.28.0 h1:GBDwsMXVQi34v5CCYUm2jkJvu4cbtru2U4TN2PSyQnw=
|
||||||
golang.org/x/crypto v0.29.0/go.mod h1:+F4F4N5hv6v38hfeYwTdx20oUvLLc+QfrE9Ax9HtgRg=
|
golang.org/x/crypto v0.28.0/go.mod h1:rmgy+3RHxRZMyY0jjAJShp2zgEdOqj2AO7U0pYmeQ7U=
|
||||||
golang.org/x/exp v0.0.0-20241009180824-f66d83c29e7c h1:7dEasQXItcW1xKJ2+gg5VOiBnqWrJc+rq0DPKyvvdbY=
|
golang.org/x/exp v0.0.0-20241009180824-f66d83c29e7c h1:7dEasQXItcW1xKJ2+gg5VOiBnqWrJc+rq0DPKyvvdbY=
|
||||||
golang.org/x/exp v0.0.0-20241009180824-f66d83c29e7c/go.mod h1:NQtJDoLvd6faHhE7m4T/1IY708gDefGGjR/iUW8yQQ8=
|
golang.org/x/exp v0.0.0-20241009180824-f66d83c29e7c/go.mod h1:NQtJDoLvd6faHhE7m4T/1IY708gDefGGjR/iUW8yQQ8=
|
||||||
golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||||
@@ -97,12 +105,12 @@ golang.org/x/mod v0.21.0/go.mod h1:6SkKJ3Xj0I0BrPOZoBy3bdMptDDU9oJrpohJ3eWZ1fY=
|
|||||||
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
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-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||||
golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM=
|
golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM=
|
||||||
golang.org/x/net v0.31.0 h1:68CPQngjLL0r2AlUKiSxtQFKvzRVbnzLwMUn5SzcLHo=
|
golang.org/x/net v0.30.0 h1:AcW1SDZMkb8IpzCdQUaIq2sP4sZ4zw+55h6ynffypl4=
|
||||||
golang.org/x/net v0.31.0/go.mod h1:P4fl1q7dY2hnZFxEk4pPSkDHF+QqjitcnDjUQyMM+pM=
|
golang.org/x/net v0.30.0/go.mod h1:2wGyMJ5iFasEhkwi13ChkO/t1ECNC4X4eBKkVFyYFlU=
|
||||||
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
golang.org/x/sync v0.9.0 h1:fEo0HyrW1GIgZdpbhCRO0PkJajUS5H9IFUztCgEo2jQ=
|
golang.org/x/sync v0.8.0 h1:3NFvSEYkUoMifnESzZl15y791HH1qU2xm6eCJU5ZPXQ=
|
||||||
golang.org/x/sync v0.9.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
|
golang.org/x/sync v0.8.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
|
||||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
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-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
@@ -112,13 +120,13 @@ golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBc
|
|||||||
golang.org/x/sys v0.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.10.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.10.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.27.0 h1:wBqf8DvsY9Y/2P8gAfPDEYNuS30J4lPHJxXSb/nJZ+s=
|
golang.org/x/sys v0.26.0 h1:KHjCJyddX0LoSTb3J+vWpupP9p0oznkqVk/IfjymZbo=
|
||||||
golang.org/x/sys v0.27.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
golang.org/x/sys v0.26.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||||
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||||
golang.org/x/text v0.20.0 h1:gK/Kv2otX8gz+wn7Rmb3vT96ZwuoxnQlY+HlJVj7Qug=
|
golang.org/x/text v0.19.0 h1:kTxAhCbGbxhK0IwgSKiMO5awPoDQ0RpfiVYBfK860YM=
|
||||||
golang.org/x/text v0.20.0/go.mod h1:D4IsuqiFMhST5bX19pQ9ikHC2GsaKyk/oF+pn3ducp4=
|
golang.org/x/text v0.19.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY=
|
||||||
golang.org/x/time v0.0.0-20220210224613-90d013bbcef8 h1:vVKdlvoWBphwdxWKrFZEuM0kGgGLxUOYcY4U/2Vjg44=
|
golang.org/x/time v0.0.0-20220210224613-90d013bbcef8 h1:vVKdlvoWBphwdxWKrFZEuM0kGgGLxUOYcY4U/2Vjg44=
|
||||||
golang.org/x/time v0.0.0-20220210224613-90d013bbcef8/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
golang.org/x/time v0.0.0-20220210224613-90d013bbcef8/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||||
@@ -142,6 +150,7 @@ gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntN
|
|||||||
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
|
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
|
||||||
gopkg.in/ini.v1 v1.67.0 h1:Dgnx+6+nfE+IfzjUEISNeydPJh9AXNNsWbGP9KzCsOA=
|
gopkg.in/ini.v1 v1.67.0 h1:Dgnx+6+nfE+IfzjUEISNeydPJh9AXNNsWbGP9KzCsOA=
|
||||||
gopkg.in/ini.v1 v1.67.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
|
gopkg.in/ini.v1 v1.67.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
|
||||||
|
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 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||||
gvisor.dev/gvisor v0.0.0-20230927004350-cbd86285d259 h1:TbRPT0HtzFP3Cno1zZo7yPzEEnfu8EjLfl6IU9VfqkQ=
|
gvisor.dev/gvisor v0.0.0-20230927004350-cbd86285d259 h1:TbRPT0HtzFP3Cno1zZo7yPzEEnfu8EjLfl6IU9VfqkQ=
|
||||||
|
|||||||
@@ -56,7 +56,6 @@ func (c *CLI) OpenvpnConfig(logger OpenvpnConfigLogger, reader *reader.Reader,
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
allSettings.SetDefaults()
|
|
||||||
|
|
||||||
ipv6Supported, err := ipv6Checker.IsIPv6Supported()
|
ipv6Supported, err := ipv6Checker.IsIPv6Supported()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|||||||
@@ -1,150 +0,0 @@
|
|||||||
package command
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bytes"
|
|
||||||
"errors"
|
|
||||||
"fmt"
|
|
||||||
"strings"
|
|
||||||
"unicode/utf8"
|
|
||||||
)
|
|
||||||
|
|
||||||
var (
|
|
||||||
ErrCommandEmpty = errors.New("command is empty")
|
|
||||||
ErrSingleQuoteUnterminated = errors.New("unterminated single-quoted string")
|
|
||||||
ErrDoubleQuoteUnterminated = errors.New("unterminated double-quoted string")
|
|
||||||
ErrEscapeUnterminated = errors.New("unterminated backslash-escape")
|
|
||||||
)
|
|
||||||
|
|
||||||
// Split splits a command string into a slice of arguments.
|
|
||||||
// This is especially important for commands such as:
|
|
||||||
// /bin/sh -c "echo hello"
|
|
||||||
// which should be split into: ["/bin/sh", "-c", "echo hello"]
|
|
||||||
// It supports backslash-escapes, single-quotes and double-quotes.
|
|
||||||
// It does not support:
|
|
||||||
// - the $" quoting style.
|
|
||||||
// - expansion (brace, shell or pathname).
|
|
||||||
func Split(command string) (words []string, err error) {
|
|
||||||
if command == "" {
|
|
||||||
return nil, fmt.Errorf("%w", ErrCommandEmpty)
|
|
||||||
}
|
|
||||||
|
|
||||||
const bufferSize = 1024
|
|
||||||
buffer := bytes.NewBuffer(make([]byte, bufferSize))
|
|
||||||
|
|
||||||
startIndex := 0
|
|
||||||
|
|
||||||
for startIndex < len(command) {
|
|
||||||
// skip any split characters at the start
|
|
||||||
character, runeSize := utf8.DecodeRuneInString(command[startIndex:])
|
|
||||||
switch {
|
|
||||||
case strings.ContainsRune(" \n\t", character):
|
|
||||||
startIndex += runeSize
|
|
||||||
case character == '\\':
|
|
||||||
// Look ahead to eventually skip an escaped newline
|
|
||||||
if command[startIndex+runeSize:] == "" {
|
|
||||||
return nil, fmt.Errorf("%w: %q", ErrEscapeUnterminated, command)
|
|
||||||
}
|
|
||||||
character, runeSize := utf8.DecodeRuneInString(command[startIndex+runeSize:])
|
|
||||||
if character == '\n' {
|
|
||||||
startIndex += runeSize + runeSize // backslash and newline
|
|
||||||
}
|
|
||||||
default:
|
|
||||||
var word string
|
|
||||||
buffer.Reset()
|
|
||||||
word, startIndex, err = splitWord(command, startIndex, buffer)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("splitting word in %q: %w", command, err)
|
|
||||||
}
|
|
||||||
words = append(words, word)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return words, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// WARNING: buffer must be cleared before calling this function.
|
|
||||||
func splitWord(input string, startIndex int, buffer *bytes.Buffer) (
|
|
||||||
word string, newStartIndex int, err error,
|
|
||||||
) {
|
|
||||||
cursor := startIndex
|
|
||||||
for cursor < len(input) {
|
|
||||||
character, runeLength := utf8.DecodeRuneInString(input[cursor:])
|
|
||||||
cursor += runeLength
|
|
||||||
if character == '"' ||
|
|
||||||
character == '\'' ||
|
|
||||||
character == '\\' ||
|
|
||||||
character == ' ' ||
|
|
||||||
character == '\n' ||
|
|
||||||
character == '\t' {
|
|
||||||
buffer.WriteString(input[startIndex : cursor-runeLength])
|
|
||||||
}
|
|
||||||
|
|
||||||
switch {
|
|
||||||
case strings.ContainsRune(" \n\t", character): // spacing character
|
|
||||||
return buffer.String(), cursor, nil
|
|
||||||
case character == '"':
|
|
||||||
return handleDoubleQuoted(input, cursor, buffer)
|
|
||||||
case character == '\'':
|
|
||||||
return handleSingleQuoted(input, cursor, buffer)
|
|
||||||
case character == '\\':
|
|
||||||
return handleEscaped(input, cursor, buffer)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
buffer.WriteString(input[startIndex:])
|
|
||||||
return buffer.String(), len(input), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func handleDoubleQuoted(input string, startIndex int, buffer *bytes.Buffer) (
|
|
||||||
word string, newStartIndex int, err error,
|
|
||||||
) {
|
|
||||||
cursor := startIndex
|
|
||||||
for cursor < len(input) {
|
|
||||||
nextCharacter, nextRuneLength := utf8.DecodeRuneInString(input[cursor:])
|
|
||||||
cursor += nextRuneLength
|
|
||||||
switch nextCharacter {
|
|
||||||
case '"': // end of the double quoted string
|
|
||||||
buffer.WriteString(input[startIndex : cursor-nextRuneLength])
|
|
||||||
return splitWord(input, cursor, buffer)
|
|
||||||
case '\\': // escaped character
|
|
||||||
escapedCharacter, escapedRuneLength := utf8.DecodeRuneInString(input[cursor:])
|
|
||||||
cursor += escapedRuneLength
|
|
||||||
if !strings.ContainsRune("$`\"\n\\", escapedCharacter) {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
buffer.WriteString(input[startIndex : cursor-nextRuneLength-escapedRuneLength])
|
|
||||||
if escapedCharacter != '\n' {
|
|
||||||
// skip backslash entirely for the newline character
|
|
||||||
buffer.WriteRune(escapedCharacter)
|
|
||||||
}
|
|
||||||
startIndex = cursor
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return "", 0, fmt.Errorf("%w", ErrDoubleQuoteUnterminated)
|
|
||||||
}
|
|
||||||
|
|
||||||
func handleSingleQuoted(input string, startIndex int, buffer *bytes.Buffer) (
|
|
||||||
word string, newStartIndex int, err error,
|
|
||||||
) {
|
|
||||||
closingQuoteIndex := strings.IndexRune(input[startIndex:], '\'')
|
|
||||||
if closingQuoteIndex == -1 {
|
|
||||||
return "", 0, fmt.Errorf("%w", ErrSingleQuoteUnterminated)
|
|
||||||
}
|
|
||||||
buffer.WriteString(input[startIndex : startIndex+closingQuoteIndex])
|
|
||||||
const singleQuoteRuneLength = 1
|
|
||||||
startIndex += closingQuoteIndex + singleQuoteRuneLength
|
|
||||||
return splitWord(input, startIndex, buffer)
|
|
||||||
}
|
|
||||||
|
|
||||||
func handleEscaped(input string, startIndex int, buffer *bytes.Buffer) (
|
|
||||||
word string, newStartIndex int, err error,
|
|
||||||
) {
|
|
||||||
if input[startIndex:] == "" {
|
|
||||||
return "", 0, fmt.Errorf("%w", ErrEscapeUnterminated)
|
|
||||||
}
|
|
||||||
character, runeLength := utf8.DecodeRuneInString(input[startIndex:])
|
|
||||||
if character != '\n' { // backslash-escaped newline is ignored
|
|
||||||
buffer.WriteString(input[startIndex : startIndex+runeLength])
|
|
||||||
}
|
|
||||||
startIndex += runeLength
|
|
||||||
return splitWord(input, startIndex, buffer)
|
|
||||||
}
|
|
||||||
@@ -1,110 +0,0 @@
|
|||||||
package command
|
|
||||||
|
|
||||||
import (
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
"github.com/stretchr/testify/assert"
|
|
||||||
)
|
|
||||||
|
|
||||||
func Test_Split(t *testing.T) {
|
|
||||||
t.Parallel()
|
|
||||||
|
|
||||||
testCases := map[string]struct {
|
|
||||||
command string
|
|
||||||
words []string
|
|
||||||
errWrapped error
|
|
||||||
errMessage string
|
|
||||||
}{
|
|
||||||
"empty": {
|
|
||||||
command: "",
|
|
||||||
errWrapped: ErrCommandEmpty,
|
|
||||||
errMessage: "command is empty",
|
|
||||||
},
|
|
||||||
"concrete_sh_command": {
|
|
||||||
command: `/bin/sh -c "echo 123"`,
|
|
||||||
words: []string{"/bin/sh", "-c", "echo 123"},
|
|
||||||
},
|
|
||||||
"single_word": {
|
|
||||||
command: "word1",
|
|
||||||
words: []string{"word1"},
|
|
||||||
},
|
|
||||||
"two_words_single_space": {
|
|
||||||
command: "word1 word2",
|
|
||||||
words: []string{"word1", "word2"},
|
|
||||||
},
|
|
||||||
"two_words_multiple_space": {
|
|
||||||
command: "word1 word2",
|
|
||||||
words: []string{"word1", "word2"},
|
|
||||||
},
|
|
||||||
"two_words_no_expansion": {
|
|
||||||
command: "word1* word2?",
|
|
||||||
words: []string{"word1*", "word2?"},
|
|
||||||
},
|
|
||||||
"escaped_single quote": {
|
|
||||||
command: "ain\\'t good",
|
|
||||||
words: []string{"ain't", "good"},
|
|
||||||
},
|
|
||||||
"escaped_single_quote_all_single_quoted": {
|
|
||||||
command: "'ain'\\''t good'",
|
|
||||||
words: []string{"ain't good"},
|
|
||||||
},
|
|
||||||
"empty_single_quoted": {
|
|
||||||
command: "word1 '' word2",
|
|
||||||
words: []string{"word1", "", "word2"},
|
|
||||||
},
|
|
||||||
"escaped_newline": {
|
|
||||||
command: "word1\\\nword2",
|
|
||||||
words: []string{"word1word2"},
|
|
||||||
},
|
|
||||||
"quoted_newline": {
|
|
||||||
command: "text \"with\na\" quoted newline",
|
|
||||||
words: []string{"text", "with\na", "quoted", "newline"},
|
|
||||||
},
|
|
||||||
"quoted_escaped_newline": {
|
|
||||||
command: "\"word1\\d\\\\\\\" word2\\\nword3 word4\"",
|
|
||||||
words: []string{"word1\\d\\\" word2word3 word4"},
|
|
||||||
},
|
|
||||||
"escaped_separated_newline": {
|
|
||||||
command: "word1 \\\n word2",
|
|
||||||
words: []string{"word1", "word2"},
|
|
||||||
},
|
|
||||||
"double_quotes_no_spacing": {
|
|
||||||
command: "word1\"word2\"word3",
|
|
||||||
words: []string{"word1word2word3"},
|
|
||||||
},
|
|
||||||
"unterminated_single_quote": {
|
|
||||||
command: "'abc'\\''def",
|
|
||||||
errWrapped: ErrSingleQuoteUnterminated,
|
|
||||||
errMessage: `splitting word in "'abc'\\''def": unterminated single-quoted string`,
|
|
||||||
},
|
|
||||||
"unterminated_double_quote": {
|
|
||||||
command: "\"abc'def",
|
|
||||||
errWrapped: ErrDoubleQuoteUnterminated,
|
|
||||||
errMessage: `splitting word in "\"abc'def": unterminated double-quoted string`,
|
|
||||||
},
|
|
||||||
"unterminated_escape": {
|
|
||||||
command: "abc\\",
|
|
||||||
errWrapped: ErrEscapeUnterminated,
|
|
||||||
errMessage: `splitting word in "abc\\": unterminated backslash-escape`,
|
|
||||||
},
|
|
||||||
"unterminated_escape_only": {
|
|
||||||
command: " \\",
|
|
||||||
errWrapped: ErrEscapeUnterminated,
|
|
||||||
errMessage: `unterminated backslash-escape: " \\"`,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
for name, testCase := range testCases {
|
|
||||||
t.Run(name, func(t *testing.T) {
|
|
||||||
t.Parallel()
|
|
||||||
|
|
||||||
words, err := Split(testCase.command)
|
|
||||||
|
|
||||||
assert.Equal(t, testCase.words, words)
|
|
||||||
assert.ErrorIs(t, err, testCase.errWrapped)
|
|
||||||
if testCase.errWrapped != nil {
|
|
||||||
assert.EqualError(t, err, testCase.errMessage)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -19,7 +19,7 @@ import (
|
|||||||
// OpenVPN contains settings to configure the OpenVPN client.
|
// OpenVPN contains settings to configure the OpenVPN client.
|
||||||
type OpenVPN struct {
|
type OpenVPN struct {
|
||||||
// Version is the OpenVPN version to run.
|
// Version is the OpenVPN version to run.
|
||||||
// It can only be "2.5" or "2.6".
|
// It can only be "2.4".
|
||||||
Version string `json:"version"`
|
Version string `json:"version"`
|
||||||
// User is the OpenVPN authentication username.
|
// User is the OpenVPN authentication username.
|
||||||
// It cannot be nil in the internal state if OpenVPN is used.
|
// It cannot be nil in the internal state if OpenVPN is used.
|
||||||
@@ -90,7 +90,7 @@ var ivpnAccountID = regexp.MustCompile(`^(i|ivpn)\-[a-zA-Z0-9]{4}\-[a-zA-Z0-9]{4
|
|||||||
|
|
||||||
func (o OpenVPN) validate(vpnProvider string) (err error) {
|
func (o OpenVPN) validate(vpnProvider string) (err error) {
|
||||||
// Validate version
|
// Validate version
|
||||||
validVersions := []string{openvpn.Openvpn25, openvpn.Openvpn26}
|
validVersions := []string{openvpn.Openvpn24}
|
||||||
if err = validate.IsOneOf(o.Version, validVersions...); err != nil {
|
if err = validate.IsOneOf(o.Version, validVersions...); err != nil {
|
||||||
return fmt.Errorf("%w: %w", ErrOpenVPNVersionIsNotValid, err)
|
return fmt.Errorf("%w: %w", ErrOpenVPNVersionIsNotValid, err)
|
||||||
}
|
}
|
||||||
@@ -289,7 +289,7 @@ func (o *OpenVPN) overrideWith(other OpenVPN) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (o *OpenVPN) setDefaults(vpnProvider string) {
|
func (o *OpenVPN) setDefaults(vpnProvider string) {
|
||||||
o.Version = gosettings.DefaultComparable(o.Version, openvpn.Openvpn26)
|
o.Version = gosettings.DefaultComparable(o.Version, openvpn.Openvpn24)
|
||||||
o.User = gosettings.DefaultPointer(o.User, "")
|
o.User = gosettings.DefaultPointer(o.User, "")
|
||||||
if vpnProvider == providers.Mullvad {
|
if vpnProvider == providers.Mullvad {
|
||||||
o.Password = gosettings.DefaultPointer(o.Password, "m")
|
o.Password = gosettings.DefaultPointer(o.Password, "m")
|
||||||
|
|||||||
@@ -65,7 +65,7 @@ func (o OpenVPNSelection) validate(vpnProvider string) (err error) {
|
|||||||
switch vpnProvider {
|
switch vpnProvider {
|
||||||
// no restriction on port
|
// no restriction on port
|
||||||
case providers.Custom, providers.Cyberghost, providers.HideMyAss,
|
case providers.Custom, providers.Cyberghost, providers.HideMyAss,
|
||||||
providers.Ovpn, providers.Privatevpn, providers.Torguard:
|
providers.Privatevpn, providers.Torguard:
|
||||||
// no custom port allowed
|
// no custom port allowed
|
||||||
case providers.Expressvpn, providers.Fastestvpn,
|
case providers.Expressvpn, providers.Fastestvpn,
|
||||||
providers.Giganews, providers.Ipvanish, providers.Nordvpn,
|
providers.Giganews, providers.Ipvanish, providers.Nordvpn,
|
||||||
|
|||||||
@@ -29,14 +29,6 @@ type PortForwarding struct {
|
|||||||
// to write to a file. It cannot be nil for the
|
// to write to a file. It cannot be nil for the
|
||||||
// internal state
|
// internal state
|
||||||
Filepath *string `json:"status_file_path"`
|
Filepath *string `json:"status_file_path"`
|
||||||
// UpCommand is the command to use when the port forwarding is up.
|
|
||||||
// It can be the empty string to indicate not to run a command.
|
|
||||||
// It cannot be nil in the internal state.
|
|
||||||
UpCommand *string `json:"up_command"`
|
|
||||||
// DownCommand is the command to use after the port forwarding goes down.
|
|
||||||
// It can be the empty string to indicate to NOT run a command.
|
|
||||||
// It cannot be nil in the internal state.
|
|
||||||
DownCommand *string `json:"down_command"`
|
|
||||||
// ListeningPort is the port traffic would be redirected to from the
|
// ListeningPort is the port traffic would be redirected to from the
|
||||||
// forwarded port. The redirection is disabled if it is set to 0, which
|
// forwarded port. The redirection is disabled if it is set to 0, which
|
||||||
// is its default as well.
|
// is its default as well.
|
||||||
@@ -92,8 +84,6 @@ func (p *PortForwarding) Copy() (copied PortForwarding) {
|
|||||||
Enabled: gosettings.CopyPointer(p.Enabled),
|
Enabled: gosettings.CopyPointer(p.Enabled),
|
||||||
Provider: gosettings.CopyPointer(p.Provider),
|
Provider: gosettings.CopyPointer(p.Provider),
|
||||||
Filepath: gosettings.CopyPointer(p.Filepath),
|
Filepath: gosettings.CopyPointer(p.Filepath),
|
||||||
UpCommand: gosettings.CopyPointer(p.UpCommand),
|
|
||||||
DownCommand: gosettings.CopyPointer(p.DownCommand),
|
|
||||||
ListeningPort: gosettings.CopyPointer(p.ListeningPort),
|
ListeningPort: gosettings.CopyPointer(p.ListeningPort),
|
||||||
Username: p.Username,
|
Username: p.Username,
|
||||||
Password: p.Password,
|
Password: p.Password,
|
||||||
@@ -104,8 +94,6 @@ func (p *PortForwarding) OverrideWith(other PortForwarding) {
|
|||||||
p.Enabled = gosettings.OverrideWithPointer(p.Enabled, other.Enabled)
|
p.Enabled = gosettings.OverrideWithPointer(p.Enabled, other.Enabled)
|
||||||
p.Provider = gosettings.OverrideWithPointer(p.Provider, other.Provider)
|
p.Provider = gosettings.OverrideWithPointer(p.Provider, other.Provider)
|
||||||
p.Filepath = gosettings.OverrideWithPointer(p.Filepath, other.Filepath)
|
p.Filepath = gosettings.OverrideWithPointer(p.Filepath, other.Filepath)
|
||||||
p.UpCommand = gosettings.OverrideWithPointer(p.UpCommand, other.UpCommand)
|
|
||||||
p.DownCommand = gosettings.OverrideWithPointer(p.DownCommand, other.DownCommand)
|
|
||||||
p.ListeningPort = gosettings.OverrideWithPointer(p.ListeningPort, other.ListeningPort)
|
p.ListeningPort = gosettings.OverrideWithPointer(p.ListeningPort, other.ListeningPort)
|
||||||
p.Username = gosettings.OverrideWithComparable(p.Username, other.Username)
|
p.Username = gosettings.OverrideWithComparable(p.Username, other.Username)
|
||||||
p.Password = gosettings.OverrideWithComparable(p.Password, other.Password)
|
p.Password = gosettings.OverrideWithComparable(p.Password, other.Password)
|
||||||
@@ -115,8 +103,6 @@ func (p *PortForwarding) setDefaults() {
|
|||||||
p.Enabled = gosettings.DefaultPointer(p.Enabled, false)
|
p.Enabled = gosettings.DefaultPointer(p.Enabled, false)
|
||||||
p.Provider = gosettings.DefaultPointer(p.Provider, "")
|
p.Provider = gosettings.DefaultPointer(p.Provider, "")
|
||||||
p.Filepath = gosettings.DefaultPointer(p.Filepath, "/tmp/gluetun/forwarded_port")
|
p.Filepath = gosettings.DefaultPointer(p.Filepath, "/tmp/gluetun/forwarded_port")
|
||||||
p.UpCommand = gosettings.DefaultPointer(p.UpCommand, "")
|
|
||||||
p.DownCommand = gosettings.DefaultPointer(p.DownCommand, "")
|
|
||||||
p.ListeningPort = gosettings.DefaultPointer(p.ListeningPort, 0)
|
p.ListeningPort = gosettings.DefaultPointer(p.ListeningPort, 0)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -149,13 +135,6 @@ func (p PortForwarding) toLinesNode() (node *gotree.Node) {
|
|||||||
}
|
}
|
||||||
node.Appendf("Forwarded port file path: %s", filepath)
|
node.Appendf("Forwarded port file path: %s", filepath)
|
||||||
|
|
||||||
if *p.UpCommand != "" {
|
|
||||||
node.Appendf("Forwarded port up command: %s", *p.UpCommand)
|
|
||||||
}
|
|
||||||
if *p.DownCommand != "" {
|
|
||||||
node.Appendf("Forwarded port down command: %s", *p.DownCommand)
|
|
||||||
}
|
|
||||||
|
|
||||||
if p.Username != "" {
|
if p.Username != "" {
|
||||||
credentialsNode := node.Appendf("Credentials:")
|
credentialsNode := node.Appendf("Credentials:")
|
||||||
credentialsNode.Appendf("Username: %s", p.Username)
|
credentialsNode.Appendf("Username: %s", p.Username)
|
||||||
@@ -184,12 +163,6 @@ func (p *PortForwarding) read(r *reader.Reader) (err error) {
|
|||||||
"PRIVATE_INTERNET_ACCESS_VPN_PORT_FORWARDING_STATUS_FILE",
|
"PRIVATE_INTERNET_ACCESS_VPN_PORT_FORWARDING_STATUS_FILE",
|
||||||
))
|
))
|
||||||
|
|
||||||
p.UpCommand = r.Get("VPN_PORT_FORWARDING_UP_COMMAND",
|
|
||||||
reader.ForceLowercase(false))
|
|
||||||
|
|
||||||
p.DownCommand = r.Get("VPN_PORT_FORWARDING_DOWN_COMMAND",
|
|
||||||
reader.ForceLowercase(false))
|
|
||||||
|
|
||||||
p.ListeningPort, err = r.Uint16Ptr("VPN_PORT_FORWARDING_LISTENING_PORT")
|
p.ListeningPort, err = r.Uint16Ptr("VPN_PORT_FORWARDING_LISTENING_PORT")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
|
|||||||
@@ -39,7 +39,6 @@ func (p *Provider) validate(vpnType string, filterChoicesGetter FilterChoicesGet
|
|||||||
providers.Ivpn,
|
providers.Ivpn,
|
||||||
providers.Mullvad,
|
providers.Mullvad,
|
||||||
providers.Nordvpn,
|
providers.Nordvpn,
|
||||||
providers.Ovpn,
|
|
||||||
providers.Protonvpn,
|
providers.Protonvpn,
|
||||||
providers.Surfshark,
|
providers.Surfshark,
|
||||||
providers.Windscribe,
|
providers.Windscribe,
|
||||||
|
|||||||
@@ -4,9 +4,7 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"net/netip"
|
"net/netip"
|
||||||
|
|
||||||
"github.com/qdm12/gluetun/internal/configuration/settings/helpers"
|
|
||||||
"github.com/qdm12/gluetun/internal/constants/providers"
|
"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/models"
|
||||||
"github.com/qdm12/gluetun/internal/pprof"
|
"github.com/qdm12/gluetun/internal/pprof"
|
||||||
"github.com/qdm12/gosettings/reader"
|
"github.com/qdm12/gosettings/reader"
|
||||||
@@ -162,18 +160,6 @@ func (s Settings) Warnings() (warnings []string) {
|
|||||||
" so this will likely not work anymore. See https://github.com/qdm12/gluetun/issues/1498.")
|
" so this will likely not work anymore. See https://github.com/qdm12/gluetun/issues/1498.")
|
||||||
}
|
}
|
||||||
|
|
||||||
if helpers.IsOneOf(s.VPN.Provider.Name, providers.SlickVPN) &&
|
|
||||||
s.VPN.Type == vpn.OpenVPN {
|
|
||||||
warnings = append(warnings, "OpenVPN 2.5 and 2.6 use OpenSSL 3 "+
|
|
||||||
"which prohibits the usage of weak security in today's standards. "+
|
|
||||||
s.VPN.Provider.Name+" uses weak security which is out "+
|
|
||||||
"of Gluetun's control so the only workaround is to allow such weaknesses "+
|
|
||||||
`using the OpenVPN option tls-cipher "DEFAULT:@SECLEVEL=0". `+
|
|
||||||
"You might want to reach to your provider so they upgrade their certificates. "+
|
|
||||||
"Once this is done, you will have to let the Gluetun maintainers know "+
|
|
||||||
"by creating an issue, attaching the new certificate and we will update Gluetun.")
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO remove in v4
|
// TODO remove in v4
|
||||||
if s.DNS.ServerAddress.Unmap().Compare(netip.AddrFrom4([4]byte{127, 0, 0, 1})) != 0 {
|
if s.DNS.ServerAddress.Unmap().Compare(netip.AddrFrom4([4]byte{127, 0, 0, 1})) != 0 {
|
||||||
warnings = append(warnings, "DNS address is set to "+s.DNS.ServerAddress.String()+
|
warnings = append(warnings, "DNS address is set to "+s.DNS.ServerAddress.String()+
|
||||||
|
|||||||
@@ -30,7 +30,7 @@ func Test_Settings_String(t *testing.T) {
|
|||||||
| | ├── Protocol: UDP
|
| | ├── Protocol: UDP
|
||||||
| | └── Private Internet Access encryption preset: strong
|
| | └── Private Internet Access encryption preset: strong
|
||||||
| └── OpenVPN settings:
|
| └── OpenVPN settings:
|
||||||
| ├── OpenVPN version: 2.6
|
| ├── OpenVPN version: 2.4
|
||||||
| ├── User: [not set]
|
| ├── User: [not set]
|
||||||
| ├── Password: [not set]
|
| ├── Password: [not set]
|
||||||
| ├── Private Internet Access encryption preset: strong
|
| ├── Private Internet Access encryption preset: strong
|
||||||
|
|||||||
@@ -65,7 +65,6 @@ func (w Wireguard) validate(vpnProvider string, ipv6Supported bool) (err error)
|
|||||||
providers.Ivpn,
|
providers.Ivpn,
|
||||||
providers.Mullvad,
|
providers.Mullvad,
|
||||||
providers.Nordvpn,
|
providers.Nordvpn,
|
||||||
providers.Ovpn,
|
|
||||||
providers.Protonvpn,
|
providers.Protonvpn,
|
||||||
providers.Surfshark,
|
providers.Surfshark,
|
||||||
providers.Windscribe,
|
providers.Windscribe,
|
||||||
|
|||||||
@@ -4,7 +4,6 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"net/netip"
|
"net/netip"
|
||||||
|
|
||||||
"github.com/qdm12/gluetun/internal/configuration/settings/helpers"
|
|
||||||
"github.com/qdm12/gluetun/internal/constants/providers"
|
"github.com/qdm12/gluetun/internal/constants/providers"
|
||||||
"github.com/qdm12/gosettings"
|
"github.com/qdm12/gosettings"
|
||||||
"github.com/qdm12/gosettings/reader"
|
"github.com/qdm12/gosettings/reader"
|
||||||
@@ -22,7 +21,7 @@ type WireguardSelection struct {
|
|||||||
// in the internal state.
|
// in the internal state.
|
||||||
EndpointIP netip.Addr `json:"endpoint_ip"`
|
EndpointIP netip.Addr `json:"endpoint_ip"`
|
||||||
// EndpointPort is a the server port to use for the VPN server.
|
// EndpointPort is a the server port to use for the VPN server.
|
||||||
// It is optional for VPN providers IVPN, Mullvad, Ovpn, Surfshark
|
// It is optional for VPN providers IVPN, Mullvad, Surfshark
|
||||||
// and Windscribe, and compulsory for the others.
|
// and Windscribe, and compulsory for the others.
|
||||||
// When optional, it can be set to 0 to indicate not use
|
// When optional, it can be set to 0 to indicate not use
|
||||||
// a custom endpoint port. It cannot be nil in the internal
|
// a custom endpoint port. It cannot be nil in the internal
|
||||||
@@ -40,9 +39,8 @@ func (w WireguardSelection) validate(vpnProvider string) (err error) {
|
|||||||
// Validate EndpointIP
|
// Validate EndpointIP
|
||||||
switch vpnProvider {
|
switch vpnProvider {
|
||||||
case providers.Airvpn, providers.Fastestvpn, providers.Ivpn,
|
case providers.Airvpn, providers.Fastestvpn, providers.Ivpn,
|
||||||
providers.Mullvad, providers.Nordvpn, providers.Ovpn,
|
providers.Mullvad, providers.Nordvpn, providers.Protonvpn,
|
||||||
providers.Protonvpn, providers.Surfshark,
|
providers.Surfshark, providers.Windscribe:
|
||||||
providers.Windscribe:
|
|
||||||
// endpoint IP addresses are baked in
|
// endpoint IP addresses are baked in
|
||||||
case providers.Custom:
|
case providers.Custom:
|
||||||
if !w.EndpointIP.IsValid() || w.EndpointIP.IsUnspecified() {
|
if !w.EndpointIP.IsValid() || w.EndpointIP.IsUnspecified() {
|
||||||
@@ -64,16 +62,12 @@ func (w WireguardSelection) validate(vpnProvider string) (err error) {
|
|||||||
if *w.EndpointPort != 0 {
|
if *w.EndpointPort != 0 {
|
||||||
return fmt.Errorf("%w", ErrWireguardEndpointPortSet)
|
return fmt.Errorf("%w", ErrWireguardEndpointPortSet)
|
||||||
}
|
}
|
||||||
case providers.Airvpn, providers.Ivpn, providers.Mullvad,
|
case providers.Airvpn, providers.Ivpn, providers.Mullvad, providers.Windscribe:
|
||||||
providers.Ovpn, providers.Windscribe:
|
|
||||||
// EndpointPort is optional and can be 0
|
// EndpointPort is optional and can be 0
|
||||||
if *w.EndpointPort == 0 {
|
if *w.EndpointPort == 0 {
|
||||||
break // no custom endpoint port set
|
break // no custom endpoint port set
|
||||||
}
|
}
|
||||||
if helpers.IsOneOf(vpnProvider,
|
if vpnProvider == providers.Mullvad {
|
||||||
providers.Mullvad,
|
|
||||||
providers.Ovpn,
|
|
||||||
) {
|
|
||||||
break // no restriction on custom endpoint port value
|
break // no restriction on custom endpoint port value
|
||||||
}
|
}
|
||||||
var allowed []uint16
|
var allowed []uint16
|
||||||
@@ -98,7 +92,7 @@ func (w WireguardSelection) validate(vpnProvider string) (err error) {
|
|||||||
// Validate PublicKey
|
// Validate PublicKey
|
||||||
switch vpnProvider {
|
switch vpnProvider {
|
||||||
case providers.Fastestvpn, providers.Ivpn, providers.Mullvad,
|
case providers.Fastestvpn, providers.Ivpn, providers.Mullvad,
|
||||||
providers.Ovpn, providers.Surfshark, providers.Windscribe:
|
providers.Surfshark, providers.Windscribe:
|
||||||
// public keys are baked in
|
// public keys are baked in
|
||||||
case providers.Custom:
|
case providers.Custom:
|
||||||
if w.PublicKey == "" {
|
if w.PublicKey == "" {
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
package openvpn
|
package openvpn
|
||||||
|
|
||||||
const (
|
const (
|
||||||
Openvpn25 = "2.5"
|
Openvpn24 = "2.4"
|
||||||
Openvpn26 = "2.6"
|
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -15,7 +15,6 @@ const (
|
|||||||
Ivpn = "ivpn"
|
Ivpn = "ivpn"
|
||||||
Mullvad = "mullvad"
|
Mullvad = "mullvad"
|
||||||
Nordvpn = "nordvpn"
|
Nordvpn = "nordvpn"
|
||||||
Ovpn = "ovpn"
|
|
||||||
Perfectprivacy = "perfect privacy"
|
Perfectprivacy = "perfect privacy"
|
||||||
Privado = "privado"
|
Privado = "privado"
|
||||||
PrivateInternetAccess = "private internet access"
|
PrivateInternetAccess = "private internet access"
|
||||||
@@ -45,7 +44,6 @@ func All() []string {
|
|||||||
Ivpn,
|
Ivpn,
|
||||||
Mullvad,
|
Mullvad,
|
||||||
Nordvpn,
|
Nordvpn,
|
||||||
Ovpn,
|
|
||||||
Perfectprivacy,
|
Perfectprivacy,
|
||||||
Privado,
|
Privado,
|
||||||
PrivateInternetAccess,
|
PrivateInternetAccess,
|
||||||
|
|||||||
@@ -22,7 +22,7 @@ type chainRule struct {
|
|||||||
packets uint64
|
packets uint64
|
||||||
bytes uint64
|
bytes uint64
|
||||||
target string // "ACCEPT", "DROP", "REJECT" or "REDIRECT"
|
target string // "ACCEPT", "DROP", "REJECT" or "REDIRECT"
|
||||||
protocol string // "icmp", "tcp", "udp" or "" for all protocols.
|
protocol string // "tcp", "udp" or "" for all protocols.
|
||||||
inputInterface string // input interface, for example "tun0" or "*""
|
inputInterface string // input interface, for example "tun0" or "*""
|
||||||
outputInterface string // output interface, for example "eth0" or "*""
|
outputInterface string // output interface, for example "eth0" or "*""
|
||||||
source netip.Prefix // source IP CIDR, for example 0.0.0.0/0. Must be valid.
|
source netip.Prefix // source IP CIDR, for example 0.0.0.0/0. Must be valid.
|
||||||
@@ -324,8 +324,6 @@ var ErrProtocolUnknown = errors.New("unknown protocol")
|
|||||||
func parseProtocol(s string) (protocol string, err error) {
|
func parseProtocol(s string) (protocol string, err error) {
|
||||||
switch s {
|
switch s {
|
||||||
case "0":
|
case "0":
|
||||||
case "1":
|
|
||||||
protocol = "icmp"
|
|
||||||
case "6":
|
case "6":
|
||||||
protocol = "tcp"
|
protocol = "tcp"
|
||||||
case "17":
|
case "17":
|
||||||
|
|||||||
@@ -56,8 +56,7 @@ num pkts bytes target prot opt in out source destinati
|
|||||||
num pkts bytes target prot opt in out source destination
|
num pkts bytes target prot opt in out source destination
|
||||||
1 0 0 ACCEPT 17 -- tun0 * 0.0.0.0/0 0.0.0.0/0 udp dpt:55405
|
1 0 0 ACCEPT 17 -- tun0 * 0.0.0.0/0 0.0.0.0/0 udp dpt:55405
|
||||||
2 0 0 ACCEPT 6 -- tun0 * 0.0.0.0/0 0.0.0.0/0 tcp dpt:55405
|
2 0 0 ACCEPT 6 -- tun0 * 0.0.0.0/0 0.0.0.0/0 tcp dpt:55405
|
||||||
3 0 0 ACCEPT 1 -- tun0 * 0.0.0.0/0 0.0.0.0/0
|
3 0 0 DROP 0 -- tun0 * 1.2.3.4 0.0.0.0/0
|
||||||
4 0 0 DROP 0 -- tun0 * 1.2.3.4 0.0.0.0/0
|
|
||||||
`,
|
`,
|
||||||
table: chain{
|
table: chain{
|
||||||
name: "INPUT",
|
name: "INPUT",
|
||||||
@@ -93,17 +92,6 @@ num pkts bytes target prot opt in out source destinati
|
|||||||
lineNumber: 3,
|
lineNumber: 3,
|
||||||
packets: 0,
|
packets: 0,
|
||||||
bytes: 0,
|
bytes: 0,
|
||||||
target: "ACCEPT",
|
|
||||||
protocol: "icmp",
|
|
||||||
inputInterface: "tun0",
|
|
||||||
outputInterface: "*",
|
|
||||||
source: netip.MustParsePrefix("0.0.0.0/0"),
|
|
||||||
destination: netip.MustParsePrefix("0.0.0.0/0"),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
lineNumber: 4,
|
|
||||||
packets: 0,
|
|
||||||
bytes: 0,
|
|
||||||
target: "DROP",
|
target: "DROP",
|
||||||
protocol: "",
|
protocol: "",
|
||||||
inputInterface: "tun0",
|
inputInterface: "tun0",
|
||||||
|
|||||||
@@ -92,7 +92,7 @@ func testIptablesPath(ctx context.Context, path string,
|
|||||||
// Set policy as the existing policy so no mutation is done.
|
// Set policy as the existing policy so no mutation is done.
|
||||||
// This is an extra check for some buggy kernels where setting the policy
|
// This is an extra check for some buggy kernels where setting the policy
|
||||||
// does not work.
|
// does not work.
|
||||||
cmd = exec.CommandContext(ctx, path, "-nL", "INPUT")
|
cmd = exec.CommandContext(ctx, path, "-L", "INPUT")
|
||||||
output, err = runner.Run(cmd)
|
output, err = runner.Run(cmd)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
unsupportedMessage = fmt.Sprintf("%s (%s)", output, err)
|
unsupportedMessage = fmt.Sprintf("%s (%s)", output, err)
|
||||||
|
|||||||
@@ -24,7 +24,7 @@ func newDeleteTestRuleMatcher(path string) *cmdMatcher {
|
|||||||
|
|
||||||
func newListInputRulesMatcher(path string) *cmdMatcher {
|
func newListInputRulesMatcher(path string) *cmdMatcher {
|
||||||
return newCmdMatcher(path,
|
return newCmdMatcher(path,
|
||||||
"^-nL$", "^INPUT$")
|
"^-L$", "^INPUT$")
|
||||||
}
|
}
|
||||||
|
|
||||||
func newSetPolicyMatcher(path, inputPolicy string) *cmdMatcher { //nolint:unparam
|
func newSetPolicyMatcher(path, inputPolicy string) *cmdMatcher { //nolint:unparam
|
||||||
|
|||||||
@@ -36,8 +36,6 @@ type Server struct {
|
|||||||
PortForward bool `json:"port_forward,omitempty"`
|
PortForward bool `json:"port_forward,omitempty"`
|
||||||
Keep bool `json:"keep,omitempty"`
|
Keep bool `json:"keep,omitempty"`
|
||||||
IPs []netip.Addr `json:"ips,omitempty"`
|
IPs []netip.Addr `json:"ips,omitempty"`
|
||||||
PortsTCP []uint16 `json:"ports_tcp,omitempty"`
|
|
||||||
PortsUDP []uint16 `json:"ports_udp,omitempty"`
|
|
||||||
}
|
}
|
||||||
|
|
||||||
var (
|
var (
|
||||||
|
|||||||
@@ -2,7 +2,6 @@ package natpmp
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"net"
|
|
||||||
"net/netip"
|
"net/netip"
|
||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
@@ -24,15 +23,14 @@ func Test_Client_ExternalAddress(t *testing.T) {
|
|||||||
durationSinceStartOfEpoch time.Duration
|
durationSinceStartOfEpoch time.Duration
|
||||||
externalIPv4Address netip.Addr
|
externalIPv4Address netip.Addr
|
||||||
err error
|
err error
|
||||||
errMessageRegex string
|
errMessage string
|
||||||
}{
|
}{
|
||||||
"failure": {
|
"failure": {
|
||||||
ctx: canceledCtx,
|
ctx: canceledCtx,
|
||||||
gateway: netip.AddrFrom4([4]byte{127, 0, 0, 1}),
|
gateway: netip.AddrFrom4([4]byte{127, 0, 0, 1}),
|
||||||
initialConnDuration: initialConnectionDuration,
|
initialConnDuration: initialConnectionDuration,
|
||||||
err: net.ErrClosed,
|
err: context.Canceled,
|
||||||
errMessageRegex: "executing remote procedure call: setting connection deadline: " +
|
errMessage: "executing remote procedure call: reading from udp connection: context canceled",
|
||||||
"set udp 127.0.0.1:[1-9][0-9]{1,4}: use of closed network connection",
|
|
||||||
},
|
},
|
||||||
"success": {
|
"success": {
|
||||||
ctx: context.Background(),
|
ctx: context.Background(),
|
||||||
@@ -62,7 +60,7 @@ func Test_Client_ExternalAddress(t *testing.T) {
|
|||||||
durationSinceStartOfEpoch, externalIPv4Address, err := client.ExternalAddress(testCase.ctx, testCase.gateway)
|
durationSinceStartOfEpoch, externalIPv4Address, err := client.ExternalAddress(testCase.ctx, testCase.gateway)
|
||||||
assert.ErrorIs(t, err, testCase.err)
|
assert.ErrorIs(t, err, testCase.err)
|
||||||
if testCase.err != nil {
|
if testCase.err != nil {
|
||||||
assert.Regexp(t, testCase.errMessageRegex, err.Error())
|
assert.EqualError(t, err, testCase.errMessage)
|
||||||
}
|
}
|
||||||
assert.Equal(t, testCase.durationSinceStartOfEpoch, durationSinceStartOfEpoch)
|
assert.Equal(t, testCase.durationSinceStartOfEpoch, durationSinceStartOfEpoch)
|
||||||
assert.Equal(t, testCase.externalIPv4Address, externalIPv4Address)
|
assert.Equal(t, testCase.externalIPv4Address, externalIPv4Address)
|
||||||
|
|||||||
@@ -45,10 +45,8 @@ func (c *Client) rpc(ctx context.Context, gateway netip.Addr,
|
|||||||
cancel()
|
cancel()
|
||||||
<-endGoroutineDone
|
<-endGoroutineDone
|
||||||
}()
|
}()
|
||||||
ctxListeningReady := make(chan struct{})
|
|
||||||
go func() {
|
go func() {
|
||||||
defer close(endGoroutineDone)
|
defer close(endGoroutineDone)
|
||||||
close(ctxListeningReady)
|
|
||||||
// Context is canceled either by the parent context or
|
// Context is canceled either by the parent context or
|
||||||
// when this function returns.
|
// when this function returns.
|
||||||
<-ctx.Done()
|
<-ctx.Done()
|
||||||
@@ -62,7 +60,6 @@ func (c *Client) rpc(ctx context.Context, gateway netip.Addr,
|
|||||||
}
|
}
|
||||||
err = fmt.Errorf("%w; closing connection: %w", err, closeErr)
|
err = fmt.Errorf("%w; closing connection: %w", err, closeErr)
|
||||||
}()
|
}()
|
||||||
<-ctxListeningReady // really to make unit testing reliable
|
|
||||||
|
|
||||||
const maxResponseSize = 16
|
const maxResponseSize = 16
|
||||||
response = make([]byte, maxResponseSize)
|
response = make([]byte, maxResponseSize)
|
||||||
|
|||||||
@@ -13,8 +13,7 @@ import (
|
|||||||
var ErrVersionUnknown = errors.New("OpenVPN version is unknown")
|
var ErrVersionUnknown = errors.New("OpenVPN version is unknown")
|
||||||
|
|
||||||
const (
|
const (
|
||||||
binOpenvpn25 = "openvpn2.5"
|
binOpenvpn24 = "openvpn2.4"
|
||||||
binOpenvpn26 = "openvpn2.6"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func start(ctx context.Context, starter CmdStarter, version string, flags []string) (
|
func start(ctx context.Context, starter CmdStarter, version string, flags []string) (
|
||||||
@@ -22,10 +21,8 @@ func start(ctx context.Context, starter CmdStarter, version string, flags []stri
|
|||||||
) {
|
) {
|
||||||
var bin string
|
var bin string
|
||||||
switch version {
|
switch version {
|
||||||
case openvpn.Openvpn25:
|
case openvpn.Openvpn24:
|
||||||
bin = binOpenvpn25
|
bin = binOpenvpn24
|
||||||
case openvpn.Openvpn26:
|
|
||||||
bin = binOpenvpn26
|
|
||||||
default:
|
default:
|
||||||
return nil, nil, nil, fmt.Errorf("%w: %s", ErrVersionUnknown, version)
|
return nil, nil, nil, fmt.Errorf("%w: %s", ErrVersionUnknown, version)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,12 +8,8 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
)
|
)
|
||||||
|
|
||||||
func (c *Configurator) Version25(ctx context.Context) (version string, err error) {
|
func (c *Configurator) Version24(ctx context.Context) (version string, err error) {
|
||||||
return c.version(ctx, binOpenvpn25)
|
return c.version(ctx, binOpenvpn24)
|
||||||
}
|
|
||||||
|
|
||||||
func (c *Configurator) Version26(ctx context.Context) (version string, err error) {
|
|
||||||
return c.version(ctx, binOpenvpn26)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
var ErrVersionTooShort = errors.New("version output is too short")
|
var ErrVersionTooShort = errors.New("version output is too short")
|
||||||
|
|||||||
@@ -3,7 +3,6 @@ package portforward
|
|||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"net/netip"
|
"net/netip"
|
||||||
"os/exec"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
type Service interface {
|
type Service interface {
|
||||||
@@ -30,8 +29,3 @@ type Logger interface {
|
|||||||
Warn(s string)
|
Warn(s string)
|
||||||
Error(s string)
|
Error(s string)
|
||||||
}
|
}
|
||||||
|
|
||||||
type Cmder interface {
|
|
||||||
Start(cmd *exec.Cmd) (stdoutLines, stderrLines <-chan string,
|
|
||||||
waitError <-chan error, startErr error)
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -20,7 +20,6 @@ type Loop struct {
|
|||||||
client *http.Client
|
client *http.Client
|
||||||
portAllower PortAllower
|
portAllower PortAllower
|
||||||
logger Logger
|
logger Logger
|
||||||
cmder Cmder
|
|
||||||
// Fixed parameters
|
// Fixed parameters
|
||||||
uid, gid int
|
uid, gid int
|
||||||
// Internal channels and locks
|
// Internal channels and locks
|
||||||
@@ -35,7 +34,7 @@ type Loop struct {
|
|||||||
|
|
||||||
func NewLoop(settings settings.PortForwarding, routing Routing,
|
func NewLoop(settings settings.PortForwarding, routing Routing,
|
||||||
client *http.Client, portAllower PortAllower,
|
client *http.Client, portAllower PortAllower,
|
||||||
logger Logger, cmder Cmder, uid, gid int,
|
logger Logger, uid, gid int,
|
||||||
) *Loop {
|
) *Loop {
|
||||||
return &Loop{
|
return &Loop{
|
||||||
settings: Settings{
|
settings: Settings{
|
||||||
@@ -43,8 +42,6 @@ func NewLoop(settings settings.PortForwarding, routing Routing,
|
|||||||
Service: service.Settings{
|
Service: service.Settings{
|
||||||
Enabled: settings.Enabled,
|
Enabled: settings.Enabled,
|
||||||
Filepath: *settings.Filepath,
|
Filepath: *settings.Filepath,
|
||||||
UpCommand: *settings.UpCommand,
|
|
||||||
DownCommand: *settings.DownCommand,
|
|
||||||
ListeningPort: *settings.ListeningPort,
|
ListeningPort: *settings.ListeningPort,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@@ -52,7 +49,6 @@ func NewLoop(settings settings.PortForwarding, routing Routing,
|
|||||||
client: client,
|
client: client,
|
||||||
portAllower: portAllower,
|
portAllower: portAllower,
|
||||||
logger: logger,
|
logger: logger,
|
||||||
cmder: cmder,
|
|
||||||
uid: uid,
|
uid: uid,
|
||||||
gid: gid,
|
gid: gid,
|
||||||
}
|
}
|
||||||
@@ -119,7 +115,7 @@ func (l *Loop) run(runCtx context.Context, runDone chan<- struct{},
|
|||||||
*serviceSettings.Enabled = *serviceSettings.Enabled && *l.settings.VPNIsUp
|
*serviceSettings.Enabled = *serviceSettings.Enabled && *l.settings.VPNIsUp
|
||||||
|
|
||||||
l.service = service.New(serviceSettings, l.routing, l.client,
|
l.service = service.New(serviceSettings, l.routing, l.client,
|
||||||
l.portAllower, l.logger, l.cmder, l.uid, l.gid)
|
l.portAllower, l.logger, l.uid, l.gid)
|
||||||
|
|
||||||
var err error
|
var err error
|
||||||
serviceRunError, err = l.service.Start(runCtx)
|
serviceRunError, err = l.service.Start(runCtx)
|
||||||
|
|||||||
@@ -1,59 +0,0 @@
|
|||||||
package service
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"fmt"
|
|
||||||
"os/exec"
|
|
||||||
"strings"
|
|
||||||
|
|
||||||
"github.com/qdm12/gluetun/internal/command"
|
|
||||||
)
|
|
||||||
|
|
||||||
func runCommand(ctx context.Context, cmder Cmder, logger Logger,
|
|
||||||
commandTemplate string, ports []uint16,
|
|
||||||
) (err error) {
|
|
||||||
portStrings := make([]string, len(ports))
|
|
||||||
for i, port := range ports {
|
|
||||||
portStrings[i] = fmt.Sprint(int(port))
|
|
||||||
}
|
|
||||||
portsString := strings.Join(portStrings, ",")
|
|
||||||
commandString := strings.ReplaceAll(commandTemplate, "{{PORTS}}", portsString)
|
|
||||||
args, err := command.Split(commandString)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("parsing command: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
cmd := exec.CommandContext(ctx, args[0], args[1:]...) // #nosec G204
|
|
||||||
stdout, stderr, waitError, err := cmder.Start(cmd)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
streamCtx, streamCancel := context.WithCancel(context.Background())
|
|
||||||
streamDone := make(chan struct{})
|
|
||||||
go streamLines(streamCtx, streamDone, logger, stdout, stderr)
|
|
||||||
|
|
||||||
err = <-waitError
|
|
||||||
streamCancel()
|
|
||||||
<-streamDone
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
func streamLines(ctx context.Context, done chan<- struct{},
|
|
||||||
logger Logger, stdout, stderr <-chan string,
|
|
||||||
) {
|
|
||||||
defer close(done)
|
|
||||||
|
|
||||||
var line string
|
|
||||||
|
|
||||||
for {
|
|
||||||
select {
|
|
||||||
case <-ctx.Done():
|
|
||||||
return
|
|
||||||
case line = <-stdout:
|
|
||||||
logger.Info(line)
|
|
||||||
case line = <-stderr:
|
|
||||||
logger.Error(line)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,28 +0,0 @@
|
|||||||
//go:build linux
|
|
||||||
|
|
||||||
package service
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
gomock "github.com/golang/mock/gomock"
|
|
||||||
"github.com/qdm12/gluetun/internal/command"
|
|
||||||
"github.com/stretchr/testify/require"
|
|
||||||
)
|
|
||||||
|
|
||||||
func Test_Service_runCommand(t *testing.T) {
|
|
||||||
t.Parallel()
|
|
||||||
ctrl := gomock.NewController(t)
|
|
||||||
|
|
||||||
ctx := context.Background()
|
|
||||||
cmder := command.New()
|
|
||||||
const commandTemplate = `/bin/sh -c "echo {{PORTS}}"`
|
|
||||||
ports := []uint16{1234, 5678}
|
|
||||||
logger := NewMockLogger(ctrl)
|
|
||||||
logger.EXPECT().Info("1234,5678")
|
|
||||||
|
|
||||||
err := runCommand(ctx, cmder, logger, commandTemplate, ports)
|
|
||||||
|
|
||||||
require.NoError(t, err)
|
|
||||||
}
|
|
||||||
@@ -3,7 +3,6 @@ package service
|
|||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"net/netip"
|
"net/netip"
|
||||||
"os/exec"
|
|
||||||
|
|
||||||
"github.com/qdm12/gluetun/internal/provider/utils"
|
"github.com/qdm12/gluetun/internal/provider/utils"
|
||||||
)
|
)
|
||||||
@@ -33,8 +32,3 @@ type PortForwarder interface {
|
|||||||
ports []uint16, err error)
|
ports []uint16, err error)
|
||||||
KeepPortForward(ctx context.Context, objects utils.PortForwardObjects) (err error)
|
KeepPortForward(ctx context.Context, objects utils.PortForwardObjects) (err error)
|
||||||
}
|
}
|
||||||
|
|
||||||
type Cmder interface {
|
|
||||||
Start(cmd *exec.Cmd) (stdoutLines, stderrLines <-chan string,
|
|
||||||
waitError <-chan error, startErr error)
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -1,3 +0,0 @@
|
|||||||
package service
|
|
||||||
|
|
||||||
//go:generate mockgen -destination=mocks_test.go -package=$GOPACKAGE . Logger
|
|
||||||
@@ -1,82 +0,0 @@
|
|||||||
// Code generated by MockGen. DO NOT EDIT.
|
|
||||||
// Source: github.com/qdm12/gluetun/internal/portforward/service (interfaces: Logger)
|
|
||||||
|
|
||||||
// Package service is a generated GoMock package.
|
|
||||||
package service
|
|
||||||
|
|
||||||
import (
|
|
||||||
reflect "reflect"
|
|
||||||
|
|
||||||
gomock "github.com/golang/mock/gomock"
|
|
||||||
)
|
|
||||||
|
|
||||||
// MockLogger is a mock of Logger interface.
|
|
||||||
type MockLogger struct {
|
|
||||||
ctrl *gomock.Controller
|
|
||||||
recorder *MockLoggerMockRecorder
|
|
||||||
}
|
|
||||||
|
|
||||||
// MockLoggerMockRecorder is the mock recorder for MockLogger.
|
|
||||||
type MockLoggerMockRecorder struct {
|
|
||||||
mock *MockLogger
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewMockLogger creates a new mock instance.
|
|
||||||
func NewMockLogger(ctrl *gomock.Controller) *MockLogger {
|
|
||||||
mock := &MockLogger{ctrl: ctrl}
|
|
||||||
mock.recorder = &MockLoggerMockRecorder{mock}
|
|
||||||
return mock
|
|
||||||
}
|
|
||||||
|
|
||||||
// EXPECT returns an object that allows the caller to indicate expected use.
|
|
||||||
func (m *MockLogger) EXPECT() *MockLoggerMockRecorder {
|
|
||||||
return m.recorder
|
|
||||||
}
|
|
||||||
|
|
||||||
// Debug mocks base method.
|
|
||||||
func (m *MockLogger) Debug(arg0 string) {
|
|
||||||
m.ctrl.T.Helper()
|
|
||||||
m.ctrl.Call(m, "Debug", arg0)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Debug indicates an expected call of Debug.
|
|
||||||
func (mr *MockLoggerMockRecorder) Debug(arg0 interface{}) *gomock.Call {
|
|
||||||
mr.mock.ctrl.T.Helper()
|
|
||||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Debug", reflect.TypeOf((*MockLogger)(nil).Debug), arg0)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Error mocks base method.
|
|
||||||
func (m *MockLogger) Error(arg0 string) {
|
|
||||||
m.ctrl.T.Helper()
|
|
||||||
m.ctrl.Call(m, "Error", arg0)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Error indicates an expected call of Error.
|
|
||||||
func (mr *MockLoggerMockRecorder) Error(arg0 interface{}) *gomock.Call {
|
|
||||||
mr.mock.ctrl.T.Helper()
|
|
||||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Error", reflect.TypeOf((*MockLogger)(nil).Error), arg0)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Info mocks base method.
|
|
||||||
func (m *MockLogger) Info(arg0 string) {
|
|
||||||
m.ctrl.T.Helper()
|
|
||||||
m.ctrl.Call(m, "Info", arg0)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Info indicates an expected call of Info.
|
|
||||||
func (mr *MockLoggerMockRecorder) Info(arg0 interface{}) *gomock.Call {
|
|
||||||
mr.mock.ctrl.T.Helper()
|
|
||||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Info", reflect.TypeOf((*MockLogger)(nil).Info), arg0)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Warn mocks base method.
|
|
||||||
func (m *MockLogger) Warn(arg0 string) {
|
|
||||||
m.ctrl.T.Helper()
|
|
||||||
m.ctrl.Call(m, "Warn", arg0)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Warn indicates an expected call of Warn.
|
|
||||||
func (mr *MockLoggerMockRecorder) Warn(arg0 interface{}) *gomock.Call {
|
|
||||||
mr.mock.ctrl.T.Helper()
|
|
||||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Warn", reflect.TypeOf((*MockLogger)(nil).Warn), arg0)
|
|
||||||
}
|
|
||||||
@@ -19,7 +19,6 @@ type Service struct {
|
|||||||
client *http.Client
|
client *http.Client
|
||||||
portAllower PortAllower
|
portAllower PortAllower
|
||||||
logger Logger
|
logger Logger
|
||||||
cmder Cmder
|
|
||||||
// Internal channels and locks
|
// Internal channels and locks
|
||||||
startStopMutex sync.Mutex
|
startStopMutex sync.Mutex
|
||||||
keepPortCancel context.CancelFunc
|
keepPortCancel context.CancelFunc
|
||||||
@@ -27,7 +26,7 @@ type Service struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func New(settings Settings, routing Routing, client *http.Client,
|
func New(settings Settings, routing Routing, client *http.Client,
|
||||||
portAllower PortAllower, logger Logger, cmder Cmder, puid, pgid int,
|
portAllower PortAllower, logger Logger, puid, pgid int,
|
||||||
) *Service {
|
) *Service {
|
||||||
return &Service{
|
return &Service{
|
||||||
// Fixed parameters
|
// Fixed parameters
|
||||||
@@ -39,7 +38,6 @@ func New(settings Settings, routing Routing, client *http.Client,
|
|||||||
client: client,
|
client: client,
|
||||||
portAllower: portAllower,
|
portAllower: portAllower,
|
||||||
logger: logger,
|
logger: logger,
|
||||||
cmder: cmder,
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -12,8 +12,6 @@ type Settings struct {
|
|||||||
Enabled *bool
|
Enabled *bool
|
||||||
PortForwarder PortForwarder
|
PortForwarder PortForwarder
|
||||||
Filepath string
|
Filepath string
|
||||||
UpCommand string
|
|
||||||
DownCommand string
|
|
||||||
Interface string // needed for PIA, PrivateVPN and ProtonVPN, tun0 for example
|
Interface string // needed for PIA, PrivateVPN and ProtonVPN, tun0 for example
|
||||||
ServerName string // needed for PIA
|
ServerName string // needed for PIA
|
||||||
CanPortForward bool // needed for PIA
|
CanPortForward bool // needed for PIA
|
||||||
@@ -26,8 +24,6 @@ func (s Settings) Copy() (copied Settings) {
|
|||||||
copied.Enabled = gosettings.CopyPointer(s.Enabled)
|
copied.Enabled = gosettings.CopyPointer(s.Enabled)
|
||||||
copied.PortForwarder = s.PortForwarder
|
copied.PortForwarder = s.PortForwarder
|
||||||
copied.Filepath = s.Filepath
|
copied.Filepath = s.Filepath
|
||||||
copied.UpCommand = s.UpCommand
|
|
||||||
copied.DownCommand = s.DownCommand
|
|
||||||
copied.Interface = s.Interface
|
copied.Interface = s.Interface
|
||||||
copied.ServerName = s.ServerName
|
copied.ServerName = s.ServerName
|
||||||
copied.CanPortForward = s.CanPortForward
|
copied.CanPortForward = s.CanPortForward
|
||||||
@@ -41,8 +37,6 @@ func (s *Settings) OverrideWith(update Settings) {
|
|||||||
s.Enabled = gosettings.OverrideWithPointer(s.Enabled, update.Enabled)
|
s.Enabled = gosettings.OverrideWithPointer(s.Enabled, update.Enabled)
|
||||||
s.PortForwarder = gosettings.OverrideWithComparable(s.PortForwarder, update.PortForwarder)
|
s.PortForwarder = gosettings.OverrideWithComparable(s.PortForwarder, update.PortForwarder)
|
||||||
s.Filepath = gosettings.OverrideWithComparable(s.Filepath, update.Filepath)
|
s.Filepath = gosettings.OverrideWithComparable(s.Filepath, update.Filepath)
|
||||||
s.UpCommand = gosettings.OverrideWithComparable(s.UpCommand, update.UpCommand)
|
|
||||||
s.DownCommand = gosettings.OverrideWithComparable(s.DownCommand, update.DownCommand)
|
|
||||||
s.Interface = gosettings.OverrideWithComparable(s.Interface, update.Interface)
|
s.Interface = gosettings.OverrideWithComparable(s.Interface, update.Interface)
|
||||||
s.ServerName = gosettings.OverrideWithComparable(s.ServerName, update.ServerName)
|
s.ServerName = gosettings.OverrideWithComparable(s.ServerName, update.ServerName)
|
||||||
s.CanPortForward = gosettings.OverrideWithComparable(s.CanPortForward, update.CanPortForward)
|
s.CanPortForward = gosettings.OverrideWithComparable(s.CanPortForward, update.CanPortForward)
|
||||||
|
|||||||
@@ -73,14 +73,6 @@ func (s *Service) Start(ctx context.Context) (runError <-chan error, err error)
|
|||||||
s.ports = ports
|
s.ports = ports
|
||||||
s.portMutex.Unlock()
|
s.portMutex.Unlock()
|
||||||
|
|
||||||
if s.settings.UpCommand != "" {
|
|
||||||
err = runCommand(ctx, s.cmder, s.logger, s.settings.UpCommand, ports)
|
|
||||||
if err != nil {
|
|
||||||
err = fmt.Errorf("running up command: %w", err)
|
|
||||||
s.logger.Error(err.Error())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
keepPortCtx, keepPortCancel := context.WithCancel(context.Background())
|
keepPortCtx, keepPortCancel := context.WithCancel(context.Background())
|
||||||
s.keepPortCancel = keepPortCancel
|
s.keepPortCancel = keepPortCancel
|
||||||
runErrorCh := make(chan error)
|
runErrorCh := make(chan error)
|
||||||
|
|||||||
@@ -4,7 +4,6 @@ import (
|
|||||||
"context"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
"os"
|
"os"
|
||||||
"time"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func (s *Service) Stop() (err error) {
|
func (s *Service) Stop() (err error) {
|
||||||
@@ -31,17 +30,6 @@ func (s *Service) cleanup() (err error) {
|
|||||||
s.portMutex.Lock()
|
s.portMutex.Lock()
|
||||||
defer s.portMutex.Unlock()
|
defer s.portMutex.Unlock()
|
||||||
|
|
||||||
if s.settings.DownCommand != "" {
|
|
||||||
const downTimeout = 60 * time.Second
|
|
||||||
ctx, cancel := context.WithTimeout(context.Background(), downTimeout)
|
|
||||||
defer cancel()
|
|
||||||
err = runCommand(ctx, s.cmder, s.logger, s.settings.DownCommand, s.ports)
|
|
||||||
if err != nil {
|
|
||||||
err = fmt.Errorf("running down command: %w", err)
|
|
||||||
s.logger.Error(err.Error())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, port := range s.ports {
|
for _, port := range s.ports {
|
||||||
err = s.portAllower.RemoveAllowedPort(context.Background(), port)
|
err = s.portAllower.RemoveAllowedPort(context.Background(), port)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|||||||
@@ -28,6 +28,8 @@ func (p *Provider) OpenVPNConfig(connection models.Connection,
|
|||||||
}
|
}
|
||||||
|
|
||||||
switch settings.Version {
|
switch settings.Version {
|
||||||
|
case openvpn.Openvpn24:
|
||||||
|
providerSettings.Ciphers = []string{openvpn.AES256cbc}
|
||||||
case openvpn.Openvpn25, openvpn.Openvpn26:
|
case openvpn.Openvpn25, openvpn.Openvpn26:
|
||||||
providerSettings.Ciphers = []string{
|
providerSettings.Ciphers = []string{
|
||||||
openvpn.AES256gcm, openvpn.AES256cbc, openvpn.AES192gcm,
|
openvpn.AES256gcm, openvpn.AES256cbc, openvpn.AES192gcm,
|
||||||
|
|||||||
@@ -64,8 +64,8 @@ func Test_modifyConfig(t *testing.T) {
|
|||||||
"suppress-timestamps",
|
"suppress-timestamps",
|
||||||
"auth-user-pass /etc/openvpn/auth.conf",
|
"auth-user-pass /etc/openvpn/auth.conf",
|
||||||
"verb 0",
|
"verb 0",
|
||||||
"data-ciphers-fallback cipher",
|
"cipher cipher", //nolint:dupword
|
||||||
"data-ciphers cipher",
|
"ncp-ciphers cipher",
|
||||||
"auth sha512",
|
"auth sha512",
|
||||||
"mssfix 1000",
|
"mssfix 1000",
|
||||||
"pull-filter ignore \"route-ipv6\"",
|
"pull-filter ignore \"route-ipv6\"",
|
||||||
|
|||||||
@@ -14,17 +14,13 @@ func (p *Provider) OpenVPNConfig(connection models.Connection,
|
|||||||
providerSettings := utils.OpenVPNProviderSettings{
|
providerSettings := utils.OpenVPNProviderSettings{
|
||||||
AuthUserPass: true,
|
AuthUserPass: true,
|
||||||
Ciphers: []string{
|
Ciphers: []string{
|
||||||
openvpn.AES256gcm,
|
|
||||||
openvpn.AES256cbc,
|
openvpn.AES256cbc,
|
||||||
},
|
},
|
||||||
Auth: openvpn.SHA256,
|
Auth: openvpn.SHA256,
|
||||||
VerifyX509Type: "name",
|
VerifyX509Type: "name",
|
||||||
TLSCipher: "TLS-DHE-RSA-WITH-AES-256-CBC-SHA:TLS-DHE-DSS-WITH-AES-256-CBC-SHA:TLS-RSA-WITH-AES-256-CBC-SHA",
|
TLSCipher: "TLS-DHE-RSA-WITH-AES-256-CBC-SHA:TLS-DHE-DSS-WITH-AES-256-CBC-SHA:TLS-RSA-WITH-AES-256-CBC-SHA",
|
||||||
CAs: []string{"MIIErzCCA5egAwIBAgIJAMYKzSS8uPKDMA0GCSqGSIb3DQEBDQUAMIGVMQswCQYDVQQGEwJVUzELMAkGA1UECBMCRkwxFDASBgNVBAcTC1dpbnRlciBQYXJrMREwDwYDVQQKEwhJUFZhbmlzaDEVMBMGA1UECxMMSVBWYW5pc2ggVlBOMRQwEgYDVQQDEwtJUFZhbmlzaCBDQTEjMCEGCSqGSIb3DQEJARYUc3VwcG9ydEBpcHZhbmlzaC5jb20wIBcNMjIwNTA5MjAyMDQ1WhgPMjA4MjA0MjQyMDIwNDVaMIGVMQswCQYDVQQGEwJVUzELMAkGA1UECBMCRkwxFDASBgNVBAcTC1dpbnRlciBQYXJrMREwDwYDVQQKEwhJUFZhbmlzaDEVMBMGA1UECxMMSVBWYW5pc2ggVlBOMRQwEgYDVQQDEwtJUFZhbmlzaCBDQTEjMCEGCSqGSIb3DQEJARYUc3VwcG9ydEBpcHZhbmlzaC5jb20wggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQC30MFY2v8go65jdOYM/nHu9hlHQMbEttdTxPIDMFuNS0UUxuHGUeJdVCtkeaDOKH3jHsGBczu1amYwphVv6A1qox1YTrzRCbec7CaHL926VcOQQcDAPTmL+JPHhlpR21Xa+woHFGDW90LgASLAPtupXgc6LXfFwb3vVpDnkyPUp4J0DRo2+lq3UtbHaONbGx8jyzYu/kWSiLUc7X69OedoSwlmsGACQteki2o/b0uKTf84Ei+QEjGUquGJU+LETmo2IP55I+KuyZE6+zIiiegm25jgPDkrqlw2UrJiLCjUg4VhTdjF9/AUmT5tJbhZUGGx1/l0bGr+44ea7PmB3DELAgMBAAGjgf0wgfowDAYDVR0TBAUwAwEB/zAdBgNVHQ4EFgQUS/0UJYkd58Fwg9f2nxEcJU4Z7q4wgcoGA1UdIwSBwjCBv4AUS/0UJYkd58Fwg9f2nxEcJU4Z7q6hgZukgZgwgZUxCzAJBgNVBAYTAlVTMQswCQYDVQQIEwJGTDEUMBIGA1UEBxMLV2ludGVyIFBhcmsxETAPBgNVBAoTCElQVmFuaXNoMRUwEwYDVQQLEwxJUFZhbmlzaCBWUE4xFDASBgNVBAMTC0lQVmFuaXNoIENBMSMwIQYJKoZIhvcNAQkBFhRzdXBwb3J0QGlwdmFuaXNoLmNvbYIJAMYKzSS8uPKDMA0GCSqGSIb3DQEBDQUAA4IBAQCc9JV7IR8BfBrF/BQTXg0SZMZyyMAxR2jfW9qMHKSeJuZVVjfHiqoynEgBCNbn71wZWv3OF/Thu9BJ4GiYJ2Bc9nIa90D1NGYgiOVYLGXfUUqy5FgfrsWh0Go5oYm9l7W9pWfIifwsaZynkY0rTIHn32FF0H3+wZrGrEUzVL6qi+KD8iR3cBbLT+xUzulMTBp4JYaQnxpV4fZNS0ZsNrWKFWz4Iz1SSBcsnvUhfWs1aKx4yOJQx33Pc+KwpUI+meTlMjoh+AoTriooKU2MbOqLQl32y3pR0MP3fX4HDVFRylxdckEc+VryGNHQLUJiIBKBCORih/YiRhtEhpoBxmkw"}, //nolint:lll
|
CAs: []string{"MIIErTCCA5WgAwIBAgIJAMYKzSS8uPKDMA0GCSqGSIb3DQEBDQUAMIGVMQswCQYDVQQGEwJVUzELMAkGA1UECBMCRkwxFDASBgNVBAcTC1dpbnRlciBQYXJrMREwDwYDVQQKEwhJUFZhbmlzaDEVMBMGA1UECxMMSVBWYW5pc2ggVlBOMRQwEgYDVQQDEwtJUFZhbmlzaCBDQTEjMCEGCSqGSIb3DQEJARYUc3VwcG9ydEBpcHZhbmlzaC5jb20wHhcNMTIwMTExMTkzMjIwWhcNMjgxMTAyMTkzMjIwWjCBlTELMAkGA1UEBhMCVVMxCzAJBgNVBAgTAkZMMRQwEgYDVQQHEwtXaW50ZXIgUGFyazERMA8GA1UEChMISVBWYW5pc2gxFTATBgNVBAsTDElQVmFuaXNoIFZQTjEUMBIGA1UEAxMLSVBWYW5pc2ggQ0ExIzAhBgkqhkiG9w0BCQEWFHN1cHBvcnRAaXB2YW5pc2guY29tMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAt9DBWNr/IKOuY3TmDP5x7vYZR0DGxLbXU8TyAzBbjUtFFMbhxlHiXVQrZHmgzih94x7BgXM7tWpmMKYVb+gNaqMdWE680Qm3nOwmhy/dulXDkEHAwD05i/iTx4ZaUdtV2vsKBxRg1vdC4AEiwD7bqV4HOi13xcG971aQ55Mj1KeCdA0aNvpat1LWx2jjWxsfI8s2Lv5Fkoi1HO1+vTnnaEsJZrBgAkLXpItqP29Lik3/OBIvkBIxlKrhiVPixE5qNiD+eSPirsmROvsyIonoJtuY4Dw5K6pcNlKyYiwo1IOFYU3YxffwFJk+bSW4WVBhsdf5dGxq/uOHmuz5gdwxCwIDAQABo4H9MIH6MAwGA1UdEwQFMAMBAf8wHQYDVR0OBBYEFEv9FCWJHefBcIPX9p8RHCVOGe6uMIHKBgNVHSMEgcIwgb+AFEv9FCWJHefBcIPX9p8RHCVOGe6uoYGbpIGYMIGVMQswCQYDVQQGEwJVUzELMAkGA1UECBMCRkwxFDASBgNVBAcTC1dpbnRlciBQYXJrMREwDwYDVQQKEwhJUFZhbmlzaDEVMBMGA1UECxMMSVBWYW5pc2ggVlBOMRQwEgYDVQQDEwtJUFZhbmlzaCBDQTEjMCEGCSqGSIb3DQEJARYUc3VwcG9ydEBpcHZhbmlzaC5jb22CCQDGCs0kvLjygzANBgkqhkiG9w0BAQ0FAAOCAQEAI2dkh/43ksV2fdYpVGhYaFZPVqCJoToCez0IvOmLeLGzow+EOSrY508oyjYeNP4VJEjApqo0NrMbKl8g/8bpLBcotOCF1c1HZ+y9v7648uumh01SMjsbBeHOuQcLb+7gX6c0pEmxWv8qj5JiW3/1L1bktnjW5Yp5oFkFSMXjOnIoYKHyKLjN2jtwH6XowUNYpg4qVtKU0CXPdOznWcd9/zSfa393HwJPeeVLbKYaFMC4IEbIUmKYtWyoJ9pJ58smU3pWsHZUg9Zc0LZZNjkNlBdQSLmUHAJ33Bd7pJS0JQeiWviC+4UTmzEWRKa7pDGnYRYNu2cUo0/voStphv8EVA=="}, //nolint:lll
|
||||||
MssFix: 1320,
|
MssFix: 1320,
|
||||||
ExtraLines: []string{
|
|
||||||
"comp-lzo", // Explicitly disable compression
|
|
||||||
},
|
|
||||||
}
|
}
|
||||||
return utils.OpenVPNConfig(providerSettings, connection, settings, ipv6Supported)
|
return utils.OpenVPNConfig(providerSettings, connection, settings, ipv6Supported)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,15 +0,0 @@
|
|||||||
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.randSource)
|
|
||||||
}
|
|
||||||
@@ -1,128 +0,0 @@
|
|||||||
package ovpn
|
|
||||||
|
|
||||||
import (
|
|
||||||
"errors"
|
|
||||||
"math/rand"
|
|
||||||
"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)
|
|
||||||
randSource := rand.NewSource(0)
|
|
||||||
|
|
||||||
client := (*http.Client)(nil)
|
|
||||||
provider := New(storage, randSource, 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)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,41 +0,0 @@
|
|||||||
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",
|
|
||||||
ExtraLines: []string{
|
|
||||||
"replay-window 256",
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
if strings.HasSuffix(connection.Hostname, "singapore.ovpn.com") {
|
|
||||||
providerSettings.TLSCrypt = providerSettings.TLSAuth
|
|
||||||
providerSettings.TLSAuth = ""
|
|
||||||
providerSettings.KeyDirection = ""
|
|
||||||
}
|
|
||||||
|
|
||||||
return utils.OpenVPNConfig(providerSettings, connection, settings, ipv6Supported)
|
|
||||||
}
|
|
||||||
@@ -1,30 +0,0 @@
|
|||||||
package ovpn
|
|
||||||
|
|
||||||
import (
|
|
||||||
"math/rand"
|
|
||||||
"net/http"
|
|
||||||
|
|
||||||
"github.com/qdm12/gluetun/internal/constants/providers"
|
|
||||||
"github.com/qdm12/gluetun/internal/provider/common"
|
|
||||||
"github.com/qdm12/gluetun/internal/provider/ovpn/updater"
|
|
||||||
)
|
|
||||||
|
|
||||||
type Provider struct {
|
|
||||||
storage common.Storage
|
|
||||||
randSource rand.Source
|
|
||||||
common.Fetcher
|
|
||||||
}
|
|
||||||
|
|
||||||
func New(storage common.Storage, randSource rand.Source,
|
|
||||||
client *http.Client,
|
|
||||||
) *Provider {
|
|
||||||
return &Provider{
|
|
||||||
storage: storage,
|
|
||||||
randSource: randSource,
|
|
||||||
Fetcher: updater.New(client),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *Provider) Name() string {
|
|
||||||
return providers.Ovpn
|
|
||||||
}
|
|
||||||
@@ -1,179 +0,0 @@
|
|||||||
package updater
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"encoding/json"
|
|
||||||
"errors"
|
|
||||||
"fmt"
|
|
||||||
"net/http"
|
|
||||||
"net/netip"
|
|
||||||
"strings"
|
|
||||||
|
|
||||||
"github.com/qdm12/gluetun/internal/provider/common"
|
|
||||||
)
|
|
||||||
|
|
||||||
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 string `json:"public_key"`
|
|
||||||
WireguardPorts []uint16 `json:"wireguard_ports"`
|
|
||||||
}
|
|
||||||
|
|
||||||
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("%w: %d %s", common.ErrHTTPStatusCodeNotOK,
|
|
||||||
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
|
|
||||||
}
|
|
||||||
|
|
||||||
var (
|
|
||||||
ErrCityNotSet = errors.New("city is not set")
|
|
||||||
ErrCountryNameNotSet = errors.New("country name is not set")
|
|
||||||
ErrServersNotSet = errors.New("servers array is not set")
|
|
||||||
)
|
|
||||||
|
|
||||||
func (a *apiDataCenter) validate() (err error) {
|
|
||||||
conditionalErrors := []conditionalError{
|
|
||||||
{err: ErrCityNotSet, condition: a.City == ""},
|
|
||||||
{err: ErrCountryNameNotSet, condition: a.CountryName == ""},
|
|
||||||
{err: ErrServersNotSet, 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
|
|
||||||
}
|
|
||||||
|
|
||||||
var (
|
|
||||||
ErrIPFieldNotValid = errors.New("ip address is not set")
|
|
||||||
ErrHostnameFieldNotSet = errors.New("hostname field is not set")
|
|
||||||
ErrPublicKeyFieldNotSet = errors.New("public key field is not set")
|
|
||||||
ErrWireguardPortsNotSet = errors.New("wireguard ports array is not set")
|
|
||||||
ErrWireguardPortNotDefault = errors.New("wireguard port is not the default 9929")
|
|
||||||
)
|
|
||||||
|
|
||||||
func (a *apiServer) validate() (err error) {
|
|
||||||
const defaultWireguardPort = 9929
|
|
||||||
conditionalErrors := []conditionalError{
|
|
||||||
{err: ErrIPFieldNotValid, condition: !a.IP.IsValid()},
|
|
||||||
{err: ErrHostnameFieldNotSet, condition: a.Ptr == ""},
|
|
||||||
{err: ErrPublicKeyFieldNotSet, condition: a.PublicKey == ""},
|
|
||||||
{err: ErrWireguardPortsNotSet, condition: len(a.WireguardPorts) == 0},
|
|
||||||
{
|
|
||||||
err: ErrWireguardPortNotDefault,
|
|
||||||
condition: len(a.WireguardPorts) != 1 || a.WireguardPorts[0] != defaultWireguardPort,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
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 error
|
|
||||||
condition bool
|
|
||||||
}
|
|
||||||
|
|
||||||
type joinedError struct {
|
|
||||||
errs []error
|
|
||||||
}
|
|
||||||
|
|
||||||
func (e *joinedError) Unwrap() []error {
|
|
||||||
return e.errs
|
|
||||||
}
|
|
||||||
|
|
||||||
func (e *joinedError) Error() string {
|
|
||||||
errStrings := make([]string, len(e.errs))
|
|
||||||
for i, err := range e.errs {
|
|
||||||
errStrings[i] = err.Error()
|
|
||||||
}
|
|
||||||
return strings.Join(errStrings, "; ")
|
|
||||||
}
|
|
||||||
|
|
||||||
func collectErrors(conditionalErrors []conditionalError) (err error) {
|
|
||||||
errs := make([]error, 0, len(conditionalErrors))
|
|
||||||
for _, conditionalError := range conditionalErrors {
|
|
||||||
if !conditionalError.condition {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
errs = append(errs, conditionalError.err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(errs) == 0 {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
return &joinedError{
|
|
||||||
errs: errs,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,115 +0,0 @@
|
|||||||
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 status code 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=",
|
|
||||||
WireguardPorts: []uint16{9929},
|
|
||||||
},
|
|
||||||
}},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
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)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,9 +0,0 @@
|
|||||||
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)
|
|
||||||
}
|
|
||||||
@@ -1,66 +0,0 @@
|
|||||||
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"
|
|
||||||
)
|
|
||||||
|
|
||||||
var ErrResponseSuccessFalse = errors.New("response success field is false")
|
|
||||||
|
|
||||||
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, fmt.Errorf("%w", ErrResponseSuccessFalse)
|
|
||||||
}
|
|
||||||
|
|
||||||
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
|
|
||||||
servers = append(servers, openVPNServer)
|
|
||||||
|
|
||||||
wireguardServer := baseServer
|
|
||||||
wireguardServer.VPN = vpn.Wireguard
|
|
||||||
wireguardServer.WgPubKey = apiServer.PublicKey
|
|
||||||
servers = append(servers, wireguardServer)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
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
|
|
||||||
}
|
|
||||||
@@ -1,181 +0,0 @@
|
|||||||
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,
|
|
||||||
errWrapped: common.ErrHTTPStatusCodeNotOK,
|
|
||||||
errMessage: "fetching API: HTTP status code not OK: 204 No Content",
|
|
||||||
},
|
|
||||||
"success_field_false": {
|
|
||||||
responseStatus: http.StatusOK,
|
|
||||||
responseBody: `{"success": false}`,
|
|
||||||
errWrapped: ErrResponseSuccessFalse,
|
|
||||||
errMessage: "response success field is false",
|
|
||||||
},
|
|
||||||
"validation_failed": {
|
|
||||||
responseStatus: http.StatusOK,
|
|
||||||
responseBody: `{
|
|
||||||
"success": true,
|
|
||||||
"datacenters": [
|
|
||||||
{
|
|
||||||
"city": "Vienna",
|
|
||||||
"servers": [
|
|
||||||
{}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}`,
|
|
||||||
errWrapped: ErrCountryNameNotSet,
|
|
||||||
errMessage: "validating data center 1 of 1: data center Vienna: country name is not set",
|
|
||||||
},
|
|
||||||
"not_enough_servers": {
|
|
||||||
minServers: 3,
|
|
||||||
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=",
|
|
||||||
"wireguard_ports": [9929],
|
|
||||||
"multihop_openvpn_port": 20044,
|
|
||||||
"multihop_wireguard_port": 30044
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}`,
|
|
||||||
errWrapped: common.ErrNotEnoughServers,
|
|
||||||
errMessage: "not enough servers found: 2 and expected at least 3",
|
|
||||||
},
|
|
||||||
"success": {
|
|
||||||
minServers: 2,
|
|
||||||
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=",
|
|
||||||
"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.Wireguard,
|
|
||||||
WgPubKey: "r83LIc0Q2F8s3dY9x5y17Yz8wTADJc7giW1t5eSmoXc=",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
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)
|
|
||||||
assert.ErrorIs(t, err, testCase.errWrapped)
|
|
||||||
if testCase.errWrapped != nil {
|
|
||||||
assert.EqualError(t, err, testCase.errMessage)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,15 +0,0 @@
|
|||||||
package updater
|
|
||||||
|
|
||||||
import (
|
|
||||||
"net/http"
|
|
||||||
)
|
|
||||||
|
|
||||||
type Updater struct {
|
|
||||||
client *http.Client
|
|
||||||
}
|
|
||||||
|
|
||||||
func New(client *http.Client) *Updater {
|
|
||||||
return &Updater{
|
|
||||||
client: client,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -21,7 +21,6 @@ import (
|
|||||||
"github.com/qdm12/gluetun/internal/provider/ivpn"
|
"github.com/qdm12/gluetun/internal/provider/ivpn"
|
||||||
"github.com/qdm12/gluetun/internal/provider/mullvad"
|
"github.com/qdm12/gluetun/internal/provider/mullvad"
|
||||||
"github.com/qdm12/gluetun/internal/provider/nordvpn"
|
"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/perfectprivacy"
|
||||||
"github.com/qdm12/gluetun/internal/provider/privado"
|
"github.com/qdm12/gluetun/internal/provider/privado"
|
||||||
"github.com/qdm12/gluetun/internal/provider/privateinternetaccess"
|
"github.com/qdm12/gluetun/internal/provider/privateinternetaccess"
|
||||||
@@ -72,7 +71,6 @@ func NewProviders(storage Storage, timeNow func() time.Time,
|
|||||||
providers.Ivpn: ivpn.New(storage, randSource, client, updaterWarner, parallelResolver),
|
providers.Ivpn: ivpn.New(storage, randSource, client, updaterWarner, parallelResolver),
|
||||||
providers.Mullvad: mullvad.New(storage, randSource, client),
|
providers.Mullvad: mullvad.New(storage, randSource, client),
|
||||||
providers.Nordvpn: nordvpn.New(storage, randSource, client, updaterWarner),
|
providers.Nordvpn: nordvpn.New(storage, randSource, client, updaterWarner),
|
||||||
providers.Ovpn: ovpn.New(storage, randSource, client),
|
|
||||||
providers.Perfectprivacy: perfectprivacy.New(storage, randSource, unzipper, updaterWarner),
|
providers.Perfectprivacy: perfectprivacy.New(storage, randSource, unzipper, updaterWarner),
|
||||||
providers.Privado: privado.New(storage, randSource, ipFetcher, unzipper, updaterWarner, parallelResolver),
|
providers.Privado: privado.New(storage, randSource, ipFetcher, unzipper, updaterWarner, parallelResolver),
|
||||||
providers.PrivateInternetAccess: privateinternetaccess.New(storage, randSource, timeNow, client),
|
providers.PrivateInternetAccess: privateinternetaccess.New(storage, randSource, timeNow, client),
|
||||||
|
|||||||
@@ -31,11 +31,5 @@ func (p *Provider) OpenVPNConfig(connection models.Connection,
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
// SlickVPN's certificate is sha1WithRSAEncryption and sha1 is now
|
|
||||||
// rejected by openssl 3.x.x which is used by OpenVPN >= 2.5.
|
|
||||||
// We lower the security level to 3 to allow this algorithm,
|
|
||||||
// see https://www.openssl.org/docs/man1.1.1/man3/SSL_CTX_set_security_level.html
|
|
||||||
providerSettings.TLSCipher = "DEFAULT:@SECLEVEL=0"
|
|
||||||
|
|
||||||
return utils.OpenVPNConfig(providerSettings, connection, settings, ipv6Supported)
|
return utils.OpenVPNConfig(providerSettings, connection, settings, ipv6Supported)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ func CipherLines(ciphers []string) (lines []string) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return []string{
|
return []string{
|
||||||
"data-ciphers-fallback " + ciphers[0],
|
"cipher " + ciphers[0],
|
||||||
"data-ciphers " + strings.Join(ciphers, ":"),
|
"ncp-ciphers " + strings.Join(ciphers, ":"),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -16,16 +16,16 @@ func Test_CipherLines(t *testing.T) {
|
|||||||
"empty version": {
|
"empty version": {
|
||||||
ciphers: []string{"AES"},
|
ciphers: []string{"AES"},
|
||||||
lines: []string{
|
lines: []string{
|
||||||
"data-ciphers-fallback AES",
|
"cipher AES",
|
||||||
"data-ciphers AES",
|
"ncp-ciphers AES",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
"2.5": {
|
"2.4": {
|
||||||
ciphers: []string{"AES", "CBC"},
|
ciphers: []string{"AES", "CBC"},
|
||||||
version: "2.5",
|
version: "2.4",
|
||||||
lines: []string{
|
lines: []string{
|
||||||
"data-ciphers-fallback AES",
|
"cipher AES",
|
||||||
"data-ciphers AES:CBC",
|
"ncp-ciphers AES:CBC",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -44,6 +44,8 @@ func GetConnection(provider string,
|
|||||||
}
|
}
|
||||||
|
|
||||||
protocol := getProtocol(selection)
|
protocol := getProtocol(selection)
|
||||||
|
port := getPort(selection, defaults.OpenVPNTCPPort,
|
||||||
|
defaults.OpenVPNUDPPort, defaults.WireguardPort)
|
||||||
|
|
||||||
connections := make([]models.Connection, 0, len(servers))
|
connections := make([]models.Connection, 0, len(servers))
|
||||||
for _, server := range servers {
|
for _, server := range servers {
|
||||||
@@ -59,9 +61,6 @@ func GetConnection(provider string,
|
|||||||
hostname = server.OvpnX509
|
hostname = server.OvpnX509
|
||||||
}
|
}
|
||||||
|
|
||||||
port := getPort(selection, server, defaults.OpenVPNTCPPort,
|
|
||||||
defaults.OpenVPNUDPPort, defaults.WireguardPort)
|
|
||||||
|
|
||||||
connection := models.Connection{
|
connection := models.Connection{
|
||||||
Type: selection.VPN,
|
Type: selection.VPN,
|
||||||
IP: ip,
|
IP: ip,
|
||||||
|
|||||||
@@ -6,44 +6,29 @@ import (
|
|||||||
"github.com/qdm12/gluetun/internal/configuration/settings"
|
"github.com/qdm12/gluetun/internal/configuration/settings"
|
||||||
"github.com/qdm12/gluetun/internal/constants"
|
"github.com/qdm12/gluetun/internal/constants"
|
||||||
"github.com/qdm12/gluetun/internal/constants/vpn"
|
"github.com/qdm12/gluetun/internal/constants/vpn"
|
||||||
"github.com/qdm12/gluetun/internal/models"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func getPort(selection settings.ServerSelection, server models.Server,
|
func getPort(selection settings.ServerSelection,
|
||||||
defaultOpenVPNTCP, defaultOpenVPNUDP, defaultWireguard uint16,
|
defaultOpenVPNTCP, defaultOpenVPNUDP, defaultWireguard uint16,
|
||||||
) (port uint16) {
|
) (port uint16) {
|
||||||
switch selection.VPN {
|
switch selection.VPN {
|
||||||
case vpn.Wireguard:
|
case vpn.Wireguard:
|
||||||
customPort := *selection.Wireguard.EndpointPort
|
customPort := *selection.Wireguard.EndpointPort
|
||||||
if customPort > 0 {
|
if customPort > 0 {
|
||||||
// Note: servers filtering ensures the custom port is within the
|
|
||||||
// server ports defined if any is set.
|
|
||||||
return customPort
|
return customPort
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(server.PortsUDP) > 0 {
|
|
||||||
defaultWireguard = server.PortsUDP[0]
|
|
||||||
}
|
|
||||||
checkDefined("Wireguard", defaultWireguard)
|
checkDefined("Wireguard", defaultWireguard)
|
||||||
return defaultWireguard
|
return defaultWireguard
|
||||||
default: // OpenVPN
|
default: // OpenVPN
|
||||||
customPort := *selection.OpenVPN.CustomPort
|
customPort := *selection.OpenVPN.CustomPort
|
||||||
if customPort > 0 {
|
if customPort > 0 {
|
||||||
// Note: servers filtering ensures the custom port is within the
|
|
||||||
// server ports defined if any is set.
|
|
||||||
return customPort
|
return customPort
|
||||||
}
|
}
|
||||||
if selection.OpenVPN.Protocol == constants.TCP {
|
if selection.OpenVPN.Protocol == constants.TCP {
|
||||||
if len(server.PortsTCP) > 0 {
|
|
||||||
defaultOpenVPNTCP = server.PortsTCP[0]
|
|
||||||
}
|
|
||||||
checkDefined("OpenVPN TCP", defaultOpenVPNTCP)
|
checkDefined("OpenVPN TCP", defaultOpenVPNTCP)
|
||||||
return defaultOpenVPNTCP
|
return defaultOpenVPNTCP
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(server.PortsUDP) > 0 {
|
|
||||||
defaultOpenVPNUDP = server.PortsUDP[0]
|
|
||||||
}
|
|
||||||
checkDefined("OpenVPN UDP", defaultOpenVPNUDP)
|
checkDefined("OpenVPN UDP", defaultOpenVPNUDP)
|
||||||
return defaultOpenVPNUDP
|
return defaultOpenVPNUDP
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,7 +6,6 @@ import (
|
|||||||
"github.com/qdm12/gluetun/internal/configuration/settings"
|
"github.com/qdm12/gluetun/internal/configuration/settings"
|
||||||
"github.com/qdm12/gluetun/internal/constants"
|
"github.com/qdm12/gluetun/internal/constants"
|
||||||
"github.com/qdm12/gluetun/internal/constants/vpn"
|
"github.com/qdm12/gluetun/internal/constants/vpn"
|
||||||
"github.com/qdm12/gluetun/internal/models"
|
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -24,7 +23,6 @@ func Test_GetPort(t *testing.T) {
|
|||||||
|
|
||||||
testCases := map[string]struct {
|
testCases := map[string]struct {
|
||||||
selection settings.ServerSelection
|
selection settings.ServerSelection
|
||||||
server models.Server
|
|
||||||
defaultOpenVPNTCP uint16
|
defaultOpenVPNTCP uint16
|
||||||
defaultOpenVPNUDP uint16
|
defaultOpenVPNUDP uint16
|
||||||
defaultWireguard uint16
|
defaultWireguard uint16
|
||||||
@@ -51,20 +49,6 @@ func Test_GetPort(t *testing.T) {
|
|||||||
defaultWireguard: defaultWireguard,
|
defaultWireguard: defaultWireguard,
|
||||||
port: defaultOpenVPNUDP,
|
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": {
|
"OpenVPN UDP no default port defined": {
|
||||||
selection: settings.ServerSelection{
|
selection: settings.ServerSelection{
|
||||||
VPN: vpn.OpenVPN,
|
VPN: vpn.OpenVPN,
|
||||||
@@ -105,20 +89,6 @@ func Test_GetPort(t *testing.T) {
|
|||||||
},
|
},
|
||||||
port: 1234,
|
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": {
|
"Wireguard": {
|
||||||
selection: settings.ServerSelection{
|
selection: settings.ServerSelection{
|
||||||
VPN: vpn.Wireguard,
|
VPN: vpn.Wireguard,
|
||||||
@@ -136,19 +106,6 @@ func Test_GetPort(t *testing.T) {
|
|||||||
defaultWireguard: defaultWireguard,
|
defaultWireguard: defaultWireguard,
|
||||||
port: 1234,
|
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": {
|
"Wireguard no default port defined": {
|
||||||
selection: settings.ServerSelection{
|
selection: settings.ServerSelection{
|
||||||
VPN: vpn.Wireguard,
|
VPN: vpn.Wireguard,
|
||||||
@@ -164,7 +121,6 @@ func Test_GetPort(t *testing.T) {
|
|||||||
if testCase.panics != "" {
|
if testCase.panics != "" {
|
||||||
assert.PanicsWithValue(t, testCase.panics, func() {
|
assert.PanicsWithValue(t, testCase.panics, func() {
|
||||||
_ = getPort(testCase.selection,
|
_ = getPort(testCase.selection,
|
||||||
testCase.server,
|
|
||||||
testCase.defaultOpenVPNTCP,
|
testCase.defaultOpenVPNTCP,
|
||||||
testCase.defaultOpenVPNUDP,
|
testCase.defaultOpenVPNUDP,
|
||||||
testCase.defaultWireguard)
|
testCase.defaultWireguard)
|
||||||
@@ -173,7 +129,6 @@ func Test_GetPort(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
port := getPort(testCase.selection,
|
port := getPort(testCase.selection,
|
||||||
testCase.server,
|
|
||||||
testCase.defaultOpenVPNTCP,
|
testCase.defaultOpenVPNTCP,
|
||||||
testCase.defaultOpenVPNUDP,
|
testCase.defaultOpenVPNUDP,
|
||||||
testCase.defaultWireguard)
|
testCase.defaultWireguard)
|
||||||
|
|||||||
@@ -3,11 +3,7 @@ package api
|
|||||||
import (
|
import (
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"maps"
|
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/url"
|
|
||||||
"regexp"
|
|
||||||
"slices"
|
|
||||||
"strings"
|
"strings"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -20,8 +16,6 @@ const (
|
|||||||
IP2Location Provider = "ip2location"
|
IP2Location Provider = "ip2location"
|
||||||
)
|
)
|
||||||
|
|
||||||
const echoipPrefix = "echoip#"
|
|
||||||
|
|
||||||
type NameToken struct {
|
type NameToken struct {
|
||||||
Name string
|
Name string
|
||||||
Token string
|
Token string
|
||||||
@@ -36,19 +30,15 @@ func New(nameTokenPairs []NameToken, client *http.Client) (
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("parsing API name: %w", err)
|
return nil, fmt.Errorf("parsing API name: %w", err)
|
||||||
}
|
}
|
||||||
switch {
|
switch provider {
|
||||||
case provider == Cloudflare:
|
case Cloudflare:
|
||||||
fetchers[i] = newCloudflare(client)
|
fetchers[i] = newCloudflare(client)
|
||||||
case provider == IfConfigCo:
|
case IfConfigCo:
|
||||||
const ifConfigCoURL = "https://ifconfig.co"
|
fetchers[i] = newIfConfigCo(client)
|
||||||
fetchers[i] = newEchoip(client, ifConfigCoURL)
|
case IPInfo:
|
||||||
case provider == IPInfo:
|
|
||||||
fetchers[i] = newIPInfo(client, nameTokenPair.Token)
|
fetchers[i] = newIPInfo(client, nameTokenPair.Token)
|
||||||
case provider == IP2Location:
|
case IP2Location:
|
||||||
fetchers[i] = newIP2Location(client, nameTokenPair.Token)
|
fetchers[i] = newIP2Location(client, nameTokenPair.Token)
|
||||||
case strings.HasPrefix(string(provider), echoipPrefix):
|
|
||||||
url := strings.TrimPrefix(string(provider), echoipPrefix)
|
|
||||||
fetchers[i] = newEchoip(client, url)
|
|
||||||
default:
|
default:
|
||||||
panic("provider not valid: " + provider)
|
panic("provider not valid: " + provider)
|
||||||
}
|
}
|
||||||
@@ -56,88 +46,20 @@ func New(nameTokenPairs []NameToken, client *http.Client) (
|
|||||||
return fetchers, nil
|
return fetchers, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
var regexEchoipURL = regexp.MustCompile(`^http(s|):\/\/.+$`)
|
|
||||||
|
|
||||||
var ErrProviderNotValid = errors.New("API name is not valid")
|
var ErrProviderNotValid = errors.New("API name is not valid")
|
||||||
|
|
||||||
func ParseProvider(s string) (provider Provider, err error) {
|
func ParseProvider(s string) (provider Provider, err error) {
|
||||||
possibleProviders := []Provider{
|
switch strings.ToLower(s) {
|
||||||
Cloudflare,
|
case "cloudflare":
|
||||||
IfConfigCo,
|
return Cloudflare, nil
|
||||||
IP2Location,
|
case string(IfConfigCo):
|
||||||
IPInfo,
|
return IfConfigCo, nil
|
||||||
|
case "ipinfo":
|
||||||
|
return IPInfo, nil
|
||||||
|
case "ip2location":
|
||||||
|
return IP2Location, nil
|
||||||
|
default:
|
||||||
|
return "", fmt.Errorf(`%w: %q can only be "cloudflare", "ifconfigco", "ip2location" or "ipinfo"`,
|
||||||
|
ErrProviderNotValid, s)
|
||||||
}
|
}
|
||||||
stringToProvider := make(map[string]Provider, len(possibleProviders))
|
|
||||||
for _, provider := range possibleProviders {
|
|
||||||
stringToProvider[string(provider)] = provider
|
|
||||||
}
|
|
||||||
provider, ok := stringToProvider[strings.ToLower(s)]
|
|
||||||
if ok {
|
|
||||||
return provider, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
customPrefixToURLRegex := map[string]*regexp.Regexp{
|
|
||||||
echoipPrefix: regexEchoipURL,
|
|
||||||
}
|
|
||||||
for prefix, urlRegex := range customPrefixToURLRegex {
|
|
||||||
match, err := checkCustomURL(s, prefix, urlRegex)
|
|
||||||
if !match {
|
|
||||||
continue
|
|
||||||
} else if err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
return Provider(s), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
providerStrings := make([]string, 0, len(stringToProvider)+len(customPrefixToURLRegex))
|
|
||||||
for _, providerString := range slices.Sorted(maps.Keys(stringToProvider)) {
|
|
||||||
providerStrings = append(providerStrings, `"`+providerString+`"`)
|
|
||||||
}
|
|
||||||
for _, prefix := range slices.Sorted(maps.Keys(customPrefixToURLRegex)) {
|
|
||||||
providerStrings = append(providerStrings, "a custom "+prefix+" url")
|
|
||||||
}
|
|
||||||
|
|
||||||
return "", fmt.Errorf(`%w: %q can only be %s`,
|
|
||||||
ErrProviderNotValid, s, orStrings(providerStrings))
|
|
||||||
}
|
|
||||||
|
|
||||||
var ErrCustomURLNotValid = errors.New("custom URL is not valid")
|
|
||||||
|
|
||||||
func checkCustomURL(s, prefix string, regex *regexp.Regexp) (match bool, err error) {
|
|
||||||
if !strings.HasPrefix(s, prefix) {
|
|
||||||
return false, nil
|
|
||||||
}
|
|
||||||
s = strings.TrimPrefix(s, prefix)
|
|
||||||
_, err = url.Parse(s)
|
|
||||||
if err != nil {
|
|
||||||
return true, fmt.Errorf("%s %w: %w", prefix, ErrCustomURLNotValid, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if regex.MatchString(s) {
|
|
||||||
return true, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
return true, fmt.Errorf("%s %w: %q does not match regular expression: %s",
|
|
||||||
prefix, ErrCustomURLNotValid, s, regex)
|
|
||||||
}
|
|
||||||
|
|
||||||
func orStrings(strings []string) (result string) {
|
|
||||||
return joinStrings(strings, "or")
|
|
||||||
}
|
|
||||||
|
|
||||||
func joinStrings(strings []string, lastJoin string) (result string) {
|
|
||||||
if len(strings) == 0 {
|
|
||||||
return ""
|
|
||||||
}
|
|
||||||
|
|
||||||
result = strings[0]
|
|
||||||
for i := 1; i < len(strings); i++ {
|
|
||||||
if i < len(strings)-1 {
|
|
||||||
result += ", " + strings[i]
|
|
||||||
} else {
|
|
||||||
result += " " + lastJoin + " " + strings[i]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return result
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,68 +0,0 @@
|
|||||||
package api
|
|
||||||
|
|
||||||
import (
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
"github.com/stretchr/testify/assert"
|
|
||||||
)
|
|
||||||
|
|
||||||
func Test_ParseProvider(t *testing.T) {
|
|
||||||
t.Parallel()
|
|
||||||
|
|
||||||
testCases := map[string]struct {
|
|
||||||
s string
|
|
||||||
provider Provider
|
|
||||||
errWrapped error
|
|
||||||
errMessage string
|
|
||||||
}{
|
|
||||||
"empty": {
|
|
||||||
errWrapped: ErrProviderNotValid,
|
|
||||||
errMessage: `API name is not valid: "" can only be ` +
|
|
||||||
`"cloudflare", "ifconfigco", "ip2location", "ipinfo" or a custom echoip# url`,
|
|
||||||
},
|
|
||||||
"invalid": {
|
|
||||||
s: "xyz",
|
|
||||||
errWrapped: ErrProviderNotValid,
|
|
||||||
errMessage: `API name is not valid: "xyz" can only be ` +
|
|
||||||
`"cloudflare", "ifconfigco", "ip2location", "ipinfo" or a custom echoip# url`,
|
|
||||||
},
|
|
||||||
"ipinfo": {
|
|
||||||
s: "ipinfo",
|
|
||||||
provider: IPInfo,
|
|
||||||
},
|
|
||||||
"IpInfo": {
|
|
||||||
s: "IpInfo",
|
|
||||||
provider: IPInfo,
|
|
||||||
},
|
|
||||||
"echoip_url_empty": {
|
|
||||||
s: "echoip#",
|
|
||||||
errWrapped: ErrCustomURLNotValid,
|
|
||||||
errMessage: `echoip# custom URL is not valid: "" ` +
|
|
||||||
`does not match regular expression: ^http(s|):\/\/.+$`,
|
|
||||||
},
|
|
||||||
"echoip_url_invalid": {
|
|
||||||
s: "echoip#postgres://localhost:3451",
|
|
||||||
errWrapped: ErrCustomURLNotValid,
|
|
||||||
errMessage: `echoip# custom URL is not valid: "postgres://localhost:3451" ` +
|
|
||||||
`does not match regular expression: ^http(s|):\/\/.+$`,
|
|
||||||
},
|
|
||||||
"echoip_url_valid": {
|
|
||||||
s: "echoip#http://localhost:3451",
|
|
||||||
provider: Provider("echoip#http://localhost:3451"),
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
for name, testCase := range testCases {
|
|
||||||
t.Run(name, func(t *testing.T) {
|
|
||||||
t.Parallel()
|
|
||||||
|
|
||||||
provider, err := ParseProvider(testCase.s)
|
|
||||||
|
|
||||||
assert.Equal(t, testCase.provider, provider)
|
|
||||||
assert.ErrorIs(t, err, testCase.errWrapped)
|
|
||||||
if testCase.errWrapped != nil {
|
|
||||||
assert.EqualError(t, err, testCase.errMessage)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -6,45 +6,39 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/netip"
|
"net/netip"
|
||||||
"strings"
|
|
||||||
|
|
||||||
"github.com/qdm12/gluetun/internal/models"
|
"github.com/qdm12/gluetun/internal/models"
|
||||||
)
|
)
|
||||||
|
|
||||||
type echoip struct {
|
type ifConfigCo struct {
|
||||||
client *http.Client
|
client *http.Client
|
||||||
url string
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func newEchoip(client *http.Client, url string) *echoip {
|
func newIfConfigCo(client *http.Client) *ifConfigCo {
|
||||||
return &echoip{
|
return &ifConfigCo{
|
||||||
client: client,
|
client: client,
|
||||||
url: url,
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (e *echoip) String() string {
|
func (i *ifConfigCo) String() string {
|
||||||
s := e.url
|
return string(IfConfigCo)
|
||||||
s = strings.TrimPrefix(s, "http://")
|
|
||||||
s = strings.TrimPrefix(s, "https://")
|
|
||||||
return s
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (e *echoip) CanFetchAnyIP() bool {
|
func (i *ifConfigCo) CanFetchAnyIP() bool {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
func (e *echoip) Token() string {
|
func (i *ifConfigCo) Token() string {
|
||||||
return ""
|
return ""
|
||||||
}
|
}
|
||||||
|
|
||||||
// FetchInfo obtains information on the ip address provided
|
// FetchInfo obtains information on the ip address provided
|
||||||
// using the echoip API at the url given. If the ip is the zero value,
|
// using the ifconfig.co/json API. If the ip is the zero value,
|
||||||
// the public IP address of the machine is used as the IP.
|
// the public IP address of the machine is used as the IP.
|
||||||
func (e *echoip) FetchInfo(ctx context.Context, ip netip.Addr) (
|
func (i *ifConfigCo) FetchInfo(ctx context.Context, ip netip.Addr) (
|
||||||
result models.PublicIP, err error,
|
result models.PublicIP, err error,
|
||||||
) {
|
) {
|
||||||
url := e.url + "/json"
|
url := "https://ifconfig.co/json"
|
||||||
if ip.IsValid() {
|
if ip.IsValid() {
|
||||||
url += "?ip=" + ip.String()
|
url += "?ip=" + ip.String()
|
||||||
}
|
}
|
||||||
@@ -54,7 +48,7 @@ func (e *echoip) FetchInfo(ctx context.Context, ip netip.Addr) (
|
|||||||
return result, err
|
return result, err
|
||||||
}
|
}
|
||||||
|
|
||||||
response, err := e.client.Do(request)
|
response, err := i.client.Do(request)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return result, err
|
return result, err
|
||||||
}
|
}
|
||||||
@@ -2,7 +2,6 @@ package storage
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"slices"
|
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/qdm12/gluetun/internal/configuration/settings"
|
"github.com/qdm12/gluetun/internal/configuration/settings"
|
||||||
@@ -122,10 +121,6 @@ func filterServer(server models.Server,
|
|||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
if filterByPorts(selection, server.PortsTCP) {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO filter port forward server for PIA
|
// TODO filter port forward server for PIA
|
||||||
|
|
||||||
return false
|
return false
|
||||||
@@ -169,21 +164,3 @@ func filterByProtocol(selection settings.ServerSelection,
|
|||||||
return (wantTCP && !serverTCP) || (wantUDP && !serverUDP)
|
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)
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -8,7 +8,6 @@ import (
|
|||||||
|
|
||||||
"github.com/qdm12/gluetun/internal/configuration/settings"
|
"github.com/qdm12/gluetun/internal/configuration/settings"
|
||||||
"github.com/qdm12/gluetun/internal/constants"
|
"github.com/qdm12/gluetun/internal/constants"
|
||||||
"github.com/qdm12/gluetun/internal/constants/vpn"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func commaJoin(slice []string) string {
|
func commaJoin(slice []string) string {
|
||||||
@@ -17,7 +16,7 @@ func commaJoin(slice []string) string {
|
|||||||
|
|
||||||
var ErrNoServerFound = errors.New("no server found")
|
var ErrNoServerFound = errors.New("no server found")
|
||||||
|
|
||||||
func noServerFoundError(selection settings.ServerSelection) (err error) { //nolint:gocyclo
|
func noServerFoundError(selection settings.ServerSelection) (err error) {
|
||||||
var messageParts []string
|
var messageParts []string
|
||||||
|
|
||||||
messageParts = append(messageParts, "VPN "+selection.VPN)
|
messageParts = append(messageParts, "VPN "+selection.VPN)
|
||||||
@@ -154,15 +153,6 @@ func noServerFoundError(selection settings.ServerSelection) (err error) { //noli
|
|||||||
"target ip address "+selection.TargetIP.String())
|
"target ip address "+selection.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, "; ")
|
message := "for " + strings.Join(messageParts, "; ")
|
||||||
|
|
||||||
return fmt.Errorf("%w: %s", ErrNoServerFound, message)
|
return fmt.Errorf("%w: %s", ErrNoServerFound, message)
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -2,7 +2,6 @@ package wireguard
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"strings"
|
|
||||||
|
|
||||||
"github.com/qdm12/gluetun/internal/netlink"
|
"github.com/qdm12/gluetun/internal/netlink"
|
||||||
)
|
)
|
||||||
@@ -17,10 +16,6 @@ func (w *Wireguard) addRule(rulePriority int, firewallMark uint32,
|
|||||||
rule.Table = int(firewallMark)
|
rule.Table = int(firewallMark)
|
||||||
rule.Family = family
|
rule.Family = family
|
||||||
if err := w.netlink.RuleAdd(rule); err != nil {
|
if err := w.netlink.RuleAdd(rule); err != nil {
|
||||||
if strings.HasSuffix(err.Error(), "file exists") {
|
|
||||||
w.logger.Info("if you are using Kubernetes, this may fix the error below: " +
|
|
||||||
"https://github.com/qdm12/gluetun-wiki/blob/main/setup/advanced/kubernetes.md#adding-ipv6-rule--file-exists")
|
|
||||||
}
|
|
||||||
return nil, fmt.Errorf("adding %s: %w", rule, err)
|
return nil, fmt.Errorf("adding %s: %w", rule, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user