Compare commits
1 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
732f826ec2 |
2
.github/ISSUE_TEMPLATE/bug.yml
vendored
2
.github/ISSUE_TEMPLATE/bug.yml
vendored
@@ -54,10 +54,8 @@ body:
|
|||||||
- PrivateVPN
|
- PrivateVPN
|
||||||
- ProtonVPN
|
- ProtonVPN
|
||||||
- PureVPN
|
- PureVPN
|
||||||
- SlickVPN
|
|
||||||
- Surfshark
|
- Surfshark
|
||||||
- TorGuard
|
- TorGuard
|
||||||
- VPNSecure.me
|
|
||||||
- VPNUnlimited
|
- VPNUnlimited
|
||||||
- VyprVPN
|
- VyprVPN
|
||||||
- WeVPN
|
- WeVPN
|
||||||
|
|||||||
5
.github/labels.yml
vendored
5
.github/labels.yml
vendored
@@ -64,17 +64,12 @@
|
|||||||
- name: ":cloud: PureVPN"
|
- name: ":cloud: PureVPN"
|
||||||
color: "cfe8d4"
|
color: "cfe8d4"
|
||||||
description: ""
|
description: ""
|
||||||
- name: ":cloud: SlickVPN"
|
|
||||||
color: "cfe8d4"
|
|
||||||
description: ""
|
|
||||||
- name: ":cloud: Surfshark"
|
- name: ":cloud: Surfshark"
|
||||||
color: "cfe8d4"
|
color: "cfe8d4"
|
||||||
description: ""
|
description: ""
|
||||||
- name: ":cloud: Torguard"
|
- name: ":cloud: Torguard"
|
||||||
color: "cfe8d4"
|
color: "cfe8d4"
|
||||||
description: ""
|
description: ""
|
||||||
- name: ":cloud: VPNSecure.me"
|
|
||||||
color: "cfe8d4"
|
|
||||||
- name: ":cloud: VPNUnlimited"
|
- name: ":cloud: VPNUnlimited"
|
||||||
color: "cfe8d4"
|
color: "cfe8d4"
|
||||||
description: ""
|
description: ""
|
||||||
|
|||||||
5
.github/workflows/ci.yml
vendored
5
.github/workflows/ci.yml
vendored
@@ -49,9 +49,6 @@ jobs:
|
|||||||
- name: Linting
|
- name: Linting
|
||||||
run: docker build --target lint .
|
run: docker build --target lint .
|
||||||
|
|
||||||
- name: Mocks check
|
|
||||||
run: docker build --target mocks .
|
|
||||||
|
|
||||||
- name: Build test image
|
- name: Build test image
|
||||||
run: docker build --target test -t test-container .
|
run: docker build --target test -t test-container .
|
||||||
|
|
||||||
@@ -126,7 +123,7 @@ jobs:
|
|||||||
run: echo "::set-output name=value::$(git rev-parse --short HEAD)"
|
run: echo "::set-output name=value::$(git rev-parse --short HEAD)"
|
||||||
|
|
||||||
- name: Build and push final image
|
- name: Build and push final image
|
||||||
uses: docker/build-push-action@v3.1.1
|
uses: docker/build-push-action@v3.0.0
|
||||||
with:
|
with:
|
||||||
platforms: linux/amd64,linux/386,linux/arm64,linux/arm/v6,linux/arm/v7,linux/ppc64le
|
platforms: linux/amd64,linux/386,linux/arm64,linux/arm/v6,linux/arm/v7,linux/ppc64le
|
||||||
labels: ${{ steps.meta.outputs.labels }}
|
labels: ${{ steps.meta.outputs.labels }}
|
||||||
|
|||||||
2
.github/workflows/labels.yml
vendored
2
.github/workflows/labels.yml
vendored
@@ -7,8 +7,6 @@ on:
|
|||||||
- .github/workflows/labels.yml
|
- .github/workflows/labels.yml
|
||||||
jobs:
|
jobs:
|
||||||
labeler:
|
labeler:
|
||||||
permissions:
|
|
||||||
issues: write
|
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v3
|
- uses: actions/checkout@v3
|
||||||
|
|||||||
@@ -19,6 +19,7 @@ issues:
|
|||||||
source: "^.+= os\\.OpenFile\\(.+, .+, 0[0-9]{3}\\)"
|
source: "^.+= os\\.OpenFile\\(.+, .+, 0[0-9]{3}\\)"
|
||||||
linters:
|
linters:
|
||||||
- gomnd
|
- gomnd
|
||||||
|
|
||||||
- text: "^mnd: Magic number: 0[0-9]{3}, in <argument> detected$"
|
- text: "^mnd: Magic number: 0[0-9]{3}, in <argument> detected$"
|
||||||
source: "^.+= os\\.MkdirAll\\(.+, 0[0-9]{3}\\)"
|
source: "^.+= os\\.MkdirAll\\(.+, 0[0-9]{3}\\)"
|
||||||
linters:
|
linters:
|
||||||
@@ -34,7 +35,8 @@ linters:
|
|||||||
enable:
|
enable:
|
||||||
# - cyclop
|
# - cyclop
|
||||||
# - errorlint
|
# - errorlint
|
||||||
- asasalint
|
# - varnamelen
|
||||||
|
# - wrapcheck
|
||||||
- asciicheck
|
- asciicheck
|
||||||
- bidichk
|
- bidichk
|
||||||
- bodyclose
|
- bodyclose
|
||||||
@@ -65,8 +67,8 @@ linters:
|
|||||||
- goprintffuncname
|
- goprintffuncname
|
||||||
- gosec
|
- gosec
|
||||||
- grouper
|
- grouper
|
||||||
|
- ifshort
|
||||||
- importas
|
- importas
|
||||||
- interfacebloat
|
|
||||||
- ireturn
|
- ireturn
|
||||||
- lll
|
- lll
|
||||||
- maintidx
|
- maintidx
|
||||||
@@ -81,8 +83,8 @@ linters:
|
|||||||
- nosprintfhostport
|
- nosprintfhostport
|
||||||
- prealloc
|
- prealloc
|
||||||
- predeclared
|
- predeclared
|
||||||
|
- predeclared
|
||||||
- promlinter
|
- promlinter
|
||||||
- reassign
|
|
||||||
- revive
|
- revive
|
||||||
- rowserrcheck
|
- rowserrcheck
|
||||||
- sqlclosecheck
|
- sqlclosecheck
|
||||||
@@ -91,7 +93,6 @@ linters:
|
|||||||
- tparallel
|
- tparallel
|
||||||
- unconvert
|
- unconvert
|
||||||
- unparam
|
- unparam
|
||||||
- usestdlibvars
|
|
||||||
- wastedassign
|
- wastedassign
|
||||||
- whitespace
|
- whitespace
|
||||||
|
|
||||||
|
|||||||
29
Dockerfile
29
Dockerfile
@@ -2,21 +2,17 @@ ARG ALPINE_VERSION=3.16
|
|||||||
ARG GO_ALPINE_VERSION=3.16
|
ARG GO_ALPINE_VERSION=3.16
|
||||||
ARG GO_VERSION=1.17
|
ARG GO_VERSION=1.17
|
||||||
ARG XCPUTRANSLATE_VERSION=v0.6.0
|
ARG XCPUTRANSLATE_VERSION=v0.6.0
|
||||||
ARG GOLANGCI_LINT_VERSION=v1.49.0
|
ARG GOLANGCI_LINT_VERSION=v1.46.2
|
||||||
ARG MOCKGEN_VERSION=v1.6.0
|
|
||||||
ARG BUILDPLATFORM=linux/amd64
|
ARG BUILDPLATFORM=linux/amd64
|
||||||
|
|
||||||
FROM --platform=${BUILDPLATFORM} qmcgaw/xcputranslate:${XCPUTRANSLATE_VERSION} AS xcputranslate
|
FROM --platform=${BUILDPLATFORM} qmcgaw/xcputranslate:${XCPUTRANSLATE_VERSION} AS xcputranslate
|
||||||
FROM --platform=${BUILDPLATFORM} qmcgaw/binpot:golangci-lint-${GOLANGCI_LINT_VERSION} AS golangci-lint
|
FROM --platform=${BUILDPLATFORM} qmcgaw/binpot:golangci-lint-${GOLANGCI_LINT_VERSION} AS golangci-lint
|
||||||
FROM --platform=${BUILDPLATFORM} qmcgaw/binpot:mockgen-${MOCKGEN_VERSION} AS mockgen
|
|
||||||
|
|
||||||
FROM --platform=${BUILDPLATFORM} golang:${GO_VERSION}-alpine${GO_ALPINE_VERSION} AS base
|
FROM --platform=${BUILDPLATFORM} golang:${GO_VERSION}-alpine${GO_ALPINE_VERSION} AS base
|
||||||
COPY --from=xcputranslate /xcputranslate /usr/local/bin/xcputranslate
|
COPY --from=xcputranslate /xcputranslate /usr/local/bin/xcputranslate
|
||||||
# Note: findutils needed to have xargs support `-d` flag for mocks stage.
|
RUN apk --update add git g++
|
||||||
RUN apk --update add git g++ findutils
|
|
||||||
ENV CGO_ENABLED=0
|
ENV CGO_ENABLED=0
|
||||||
COPY --from=golangci-lint /bin /go/bin/golangci-lint
|
COPY --from=golangci-lint /bin /go/bin/golangci-lint
|
||||||
COPY --from=mockgen /bin /go/bin/mockgen
|
|
||||||
WORKDIR /tmp/gobuild
|
WORKDIR /tmp/gobuild
|
||||||
COPY go.mod go.sum ./
|
COPY go.mod go.sum ./
|
||||||
RUN go mod download
|
RUN go mod download
|
||||||
@@ -34,18 +30,6 @@ FROM --platform=${BUILDPLATFORM} base AS lint
|
|||||||
COPY .golangci.yml ./
|
COPY .golangci.yml ./
|
||||||
RUN golangci-lint run --timeout=10m
|
RUN golangci-lint run --timeout=10m
|
||||||
|
|
||||||
FROM --platform=${BUILDPLATFORM} base AS mocks
|
|
||||||
RUN git init && \
|
|
||||||
git config user.email ci@localhost && \
|
|
||||||
git config user.name ci && \
|
|
||||||
git config core.fileMode false && \
|
|
||||||
git add -A && \
|
|
||||||
git commit -m "snapshot" && \
|
|
||||||
grep -lr -E '^// Code generated by MockGen\. DO NOT EDIT\.$' . | xargs -r -d '\n' rm && \
|
|
||||||
go generate -run "mockgen" ./... && \
|
|
||||||
git diff --exit-code && \
|
|
||||||
rm -rf .git/
|
|
||||||
|
|
||||||
FROM --platform=${BUILDPLATFORM} base AS build
|
FROM --platform=${BUILDPLATFORM} base AS build
|
||||||
ARG TARGETPLATFORM
|
ARG TARGETPLATFORM
|
||||||
ARG VERSION=unknown
|
ARG VERSION=unknown
|
||||||
@@ -111,15 +95,8 @@ ENV VPN_SERVICE_PROVIDER=pia \
|
|||||||
PRIVATE_INTERNET_ACCESS_VPN_PORT_FORWARDING=off \
|
PRIVATE_INTERNET_ACCESS_VPN_PORT_FORWARDING=off \
|
||||||
PRIVATE_INTERNET_ACCESS_VPN_PORT_FORWARDING_STATUS_FILE="/tmp/gluetun/forwarded_port" \
|
PRIVATE_INTERNET_ACCESS_VPN_PORT_FORWARDING_STATUS_FILE="/tmp/gluetun/forwarded_port" \
|
||||||
# # Cyberghost only:
|
# # Cyberghost only:
|
||||||
OPENVPN_CERT= \
|
|
||||||
OPENVPN_KEY= \
|
|
||||||
OPENVPN_CLIENTCRT_SECRETFILE=/run/secrets/openvpn_clientcrt \
|
OPENVPN_CLIENTCRT_SECRETFILE=/run/secrets/openvpn_clientcrt \
|
||||||
OPENVPN_CLIENTKEY_SECRETFILE=/run/secrets/openvpn_clientkey \
|
OPENVPN_CLIENTKEY_SECRETFILE=/run/secrets/openvpn_clientkey \
|
||||||
# # VPNSecure only:
|
|
||||||
OPENVPN_ENCRYPTED_KEY= \
|
|
||||||
OPENVPN_ENCRYPTED_KEY_SECRETFILE=/run/secrets/openvpn_encrypted_key \
|
|
||||||
OPENVPN_KEY_PASSPHRASE= \
|
|
||||||
OPENVPN_KEY_PASSPHRASE_SECRETFILE=/run/secrets/openvpn_key_passphrase \
|
|
||||||
# # Nordvpn only:
|
# # Nordvpn only:
|
||||||
SERVER_NUMBER= \
|
SERVER_NUMBER= \
|
||||||
# # PIA only:
|
# # PIA only:
|
||||||
@@ -128,8 +105,6 @@ ENV VPN_SERVICE_PROVIDER=pia \
|
|||||||
FREE_ONLY= \
|
FREE_ONLY= \
|
||||||
# # Surfshark only:
|
# # Surfshark only:
|
||||||
MULTIHOP_ONLY= \
|
MULTIHOP_ONLY= \
|
||||||
# # VPN Secure only:
|
|
||||||
PREMIUM_ONLY= \
|
|
||||||
# Firewall
|
# Firewall
|
||||||
FIREWALL=on \
|
FIREWALL=on \
|
||||||
FIREWALL_VPN_INPUT_PORTS= \
|
FIREWALL_VPN_INPUT_PORTS= \
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
# Gluetun VPN client
|
# Gluetun VPN client
|
||||||
|
|
||||||
Lightweight swiss-knife-like VPN client to multiple VPN service providers
|
Lightweight swiss-knife-like VPN client to multiple VPN sercice providers
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
@@ -58,7 +58,7 @@ Lightweight swiss-knife-like VPN client to multiple VPN service providers
|
|||||||
## Features
|
## Features
|
||||||
|
|
||||||
- Based on Alpine 3.16 for a small Docker image of 29MB
|
- Based on Alpine 3.16 for a small Docker image of 29MB
|
||||||
- Supports: **Cyberghost**, **ExpressVPN**, **FastestVPN**, **HideMyAss**, **IPVanish**, **IVPN**, **Mullvad**, **NordVPN**, **Perfect Privacy**, **Privado**, **Private Internet Access**, **PrivateVPN**, **ProtonVPN**, **PureVPN**, **SlickVPN**, **Surfshark**, **TorGuard**, **VPNSecure.me**, **VPNUnlimited**, **Vyprvpn**, **WeVPN**, **Windscribe** servers
|
- Supports: **Cyberghost**, **ExpressVPN**, **FastestVPN**, **HideMyAss**, **IPVanish**, **IVPN**, **Mullvad**, **NordVPN**, **Perfect Privacy**, **Privado**, **Private Internet Access**, **PrivateVPN**, **ProtonVPN**, **PureVPN**, **Surfshark**, **TorGuard**, **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 **Mullvad**, **Ivpn** and **Windscribe**
|
- For **Mullvad**, **Ivpn** and **Windscribe**
|
||||||
|
|||||||
@@ -431,7 +431,7 @@ func _main(ctx context.Context, buildInfo models.BuildInformation,
|
|||||||
"http server", goroutine.OptionTimeout(defaultShutdownTimeout))
|
"http server", goroutine.OptionTimeout(defaultShutdownTimeout))
|
||||||
httpServer, err := server.New(httpServerCtx, controlServerAddress, controlServerLogging,
|
httpServer, err := server.New(httpServerCtx, controlServerAddress, controlServerLogging,
|
||||||
logger.New(log.SetComponent("http server")),
|
logger.New(log.SetComponent("http server")),
|
||||||
buildInfo, vpnLooper, portForwardLooper, unboundLooper, updaterLooper, publicIPLooper, storage)
|
buildInfo, vpnLooper, portForwardLooper, unboundLooper, updaterLooper, publicIPLooper)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("cannot setup control server: %w", err)
|
return fmt.Errorf("cannot setup control server: %w", err)
|
||||||
}
|
}
|
||||||
@@ -489,34 +489,18 @@ func printVersions(ctx context.Context, logger infoer,
|
|||||||
}
|
}
|
||||||
|
|
||||||
type netLinker interface {
|
type netLinker interface {
|
||||||
Addresser
|
|
||||||
Router
|
|
||||||
Ruler
|
|
||||||
Linker
|
|
||||||
IsWireguardSupported() (ok bool, err error)
|
|
||||||
}
|
|
||||||
|
|
||||||
type Addresser interface {
|
|
||||||
AddrList(link netlink.Link, family int) (
|
AddrList(link netlink.Link, family int) (
|
||||||
addresses []netlink.Addr, err error)
|
addresses []netlink.Addr, err error)
|
||||||
AddrAdd(link netlink.Link, addr *netlink.Addr) error
|
AddrAdd(link netlink.Link, addr *netlink.Addr) error
|
||||||
}
|
IsWireguardSupported() (ok bool, err error)
|
||||||
|
|
||||||
type Router interface {
|
|
||||||
RouteList(link netlink.Link, family int) (
|
RouteList(link netlink.Link, family int) (
|
||||||
routes []netlink.Route, err error)
|
routes []netlink.Route, err error)
|
||||||
RouteAdd(route *netlink.Route) error
|
RouteAdd(route *netlink.Route) error
|
||||||
RouteDel(route *netlink.Route) error
|
RouteDel(route *netlink.Route) error
|
||||||
RouteReplace(route *netlink.Route) error
|
RouteReplace(route *netlink.Route) error
|
||||||
}
|
|
||||||
|
|
||||||
type Ruler interface {
|
|
||||||
RuleList(family int) (rules []netlink.Rule, err error)
|
RuleList(family int) (rules []netlink.Rule, err error)
|
||||||
RuleAdd(rule *netlink.Rule) error
|
RuleAdd(rule *netlink.Rule) error
|
||||||
RuleDel(rule *netlink.Rule) error
|
RuleDel(rule *netlink.Rule) error
|
||||||
}
|
|
||||||
|
|
||||||
type Linker interface {
|
|
||||||
LinkList() (links []netlink.Link, err error)
|
LinkList() (links []netlink.Link, err error)
|
||||||
LinkByName(name string) (link netlink.Link, err error)
|
LinkByName(name string) (link netlink.Link, err error)
|
||||||
LinkByIndex(index int) (link netlink.Link, err error)
|
LinkByIndex(index int) (link netlink.Link, err error)
|
||||||
|
|||||||
6
go.mod
6
go.mod
@@ -3,7 +3,7 @@ module github.com/qdm12/gluetun
|
|||||||
go 1.17
|
go 1.17
|
||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/breml/rootcerts v0.2.6
|
github.com/breml/rootcerts v0.2.3
|
||||||
github.com/fatih/color v1.13.0
|
github.com/fatih/color v1.13.0
|
||||||
github.com/golang/mock v1.6.0
|
github.com/golang/mock v1.6.0
|
||||||
github.com/qdm12/dns v1.11.0
|
github.com/qdm12/dns v1.11.0
|
||||||
@@ -15,9 +15,8 @@ require (
|
|||||||
github.com/qdm12/log v0.1.0
|
github.com/qdm12/log v0.1.0
|
||||||
github.com/qdm12/ss-server v0.4.0
|
github.com/qdm12/ss-server v0.4.0
|
||||||
github.com/qdm12/updated v0.0.0-20210603204757-205acfe6937e
|
github.com/qdm12/updated v0.0.0-20210603204757-205acfe6937e
|
||||||
github.com/stretchr/testify v1.8.0
|
github.com/stretchr/testify v1.7.2
|
||||||
github.com/vishvananda/netlink v1.1.1-0.20211129163951-9ada19101fc5
|
github.com/vishvananda/netlink v1.1.1-0.20211129163951-9ada19101fc5
|
||||||
golang.org/x/net v0.0.0-20210916014120-12bc252f5db8
|
|
||||||
golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c
|
golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c
|
||||||
golang.org/x/text v0.3.7
|
golang.org/x/text v0.3.7
|
||||||
golang.zx2c4.com/wireguard v0.0.0-20210805125648-3957e9b9dd19
|
golang.zx2c4.com/wireguard v0.0.0-20210805125648-3957e9b9dd19
|
||||||
@@ -41,5 +40,6 @@ require (
|
|||||||
go4.org/intern v0.0.0-20210108033219-3eb7198706b2 // indirect
|
go4.org/intern v0.0.0-20210108033219-3eb7198706b2 // indirect
|
||||||
go4.org/unsafe/assume-no-moving-gc v0.0.0-20220617031537-928513b29760 // indirect
|
go4.org/unsafe/assume-no-moving-gc v0.0.0-20220617031537-928513b29760 // indirect
|
||||||
golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97 // indirect
|
golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97 // indirect
|
||||||
|
golang.org/x/net v0.0.0-20210504132125-bbd867fde50d // indirect
|
||||||
gopkg.in/yaml.v3 v3.0.1 // indirect
|
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||||
)
|
)
|
||||||
|
|||||||
12
go.sum
12
go.sum
@@ -4,8 +4,8 @@ github.com/alcortesm/tgz v0.0.0-20161220082320-9c5fe88206d7/go.mod h1:6zEj6s6u/g
|
|||||||
github.com/anmitsu/go-shlex v0.0.0-20161002113705-648efa622239/go.mod h1:2FmKhYUyUczH0OGQWaF5ceTx0UBShxjsH6f8oGKYe2c=
|
github.com/anmitsu/go-shlex v0.0.0-20161002113705-648efa622239/go.mod h1:2FmKhYUyUczH0OGQWaF5ceTx0UBShxjsH6f8oGKYe2c=
|
||||||
github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5/go.mod h1:wHh0iHkYZB8zMSxRWpUBQtwG5a7fFgvEO+odwuTv2gs=
|
github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5/go.mod h1:wHh0iHkYZB8zMSxRWpUBQtwG5a7fFgvEO+odwuTv2gs=
|
||||||
github.com/asaskevich/govalidator v0.0.0-20180720115003-f9ffefc3facf/go.mod h1:lB+ZfQJz7igIIfQNfa7Ml4HSf2uFQQRzpGGRXenZAgY=
|
github.com/asaskevich/govalidator v0.0.0-20180720115003-f9ffefc3facf/go.mod h1:lB+ZfQJz7igIIfQNfa7Ml4HSf2uFQQRzpGGRXenZAgY=
|
||||||
github.com/breml/rootcerts v0.2.6 h1:CdPczjzItec+wopLoDsBAFcLEai2q7Yayfg/94/q/2E=
|
github.com/breml/rootcerts v0.2.3 h1:1vkYjKOiHVSyuz9Ue4AOrViEvUm8gk8phTg0vbcuU0A=
|
||||||
github.com/breml/rootcerts v0.2.6/go.mod h1:24FDtzYMpqIeYC7QzaE8VPRQaFZU5TIUDlyk8qwjD88=
|
github.com/breml/rootcerts v0.2.3/go.mod h1:24FDtzYMpqIeYC7QzaE8VPRQaFZU5TIUDlyk8qwjD88=
|
||||||
github.com/creack/pty v1.1.7/go.mod h1:lj5s0c3V2DBrqTV7llrYr5NG6My20zk30Fl46Y7DoTY=
|
github.com/creack/pty v1.1.7/go.mod h1:lj5s0c3V2DBrqTV7llrYr5NG6My20zk30Fl46Y7DoTY=
|
||||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
github.com/davecgh/go-spew v1.1.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=
|
||||||
@@ -127,14 +127,13 @@ github.com/sergi/go-diff v1.0.0/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAm
|
|||||||
github.com/src-d/gcfg v1.4.0/go.mod h1:p/UMsR43ujA89BJY9duynAwIpvqEujIH/jFlfL7jWoI=
|
github.com/src-d/gcfg v1.4.0/go.mod h1:p/UMsR43ujA89BJY9duynAwIpvqEujIH/jFlfL7jWoI=
|
||||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||||
github.com/stretchr/objx v0.2.0/go.mod h1:qt09Ya8vawLte6SNmTgCsAVtYtaKzEcn8ATUoHMkEqE=
|
github.com/stretchr/objx v0.2.0/go.mod h1:qt09Ya8vawLte6SNmTgCsAVtYtaKzEcn8ATUoHMkEqE=
|
||||||
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
|
|
||||||
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
|
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
|
||||||
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
|
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
|
||||||
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
|
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
|
||||||
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||||
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||||
github.com/stretchr/testify v1.8.0 h1:pSgiaMZlXftHpm5L7V1+rVB+AZJydKsMxsQBIJw4PKk=
|
github.com/stretchr/testify v1.7.2 h1:4jaiDzPyXQvSd7D0EjG45355tLlV3VOECpq10pLC+8s=
|
||||||
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
|
github.com/stretchr/testify v1.7.2/go.mod h1:R6va5+xMeoiuVRoj+gSkQ7d3FALtqAAGI1FQKckRals=
|
||||||
github.com/vishvananda/netlink v1.1.1-0.20211129163951-9ada19101fc5 h1:b/k/BVWzWRS5v6AB0gf2ckFSbFsHN5jR0HoNso1pN+w=
|
github.com/vishvananda/netlink v1.1.1-0.20211129163951-9ada19101fc5 h1:b/k/BVWzWRS5v6AB0gf2ckFSbFsHN5jR0HoNso1pN+w=
|
||||||
github.com/vishvananda/netlink v1.1.1-0.20211129163951-9ada19101fc5/go.mod h1:twkDnbuQxJYemMlGd4JFIcuhgX83tXhKS2B/PRMpOho=
|
github.com/vishvananda/netlink v1.1.1-0.20211129163951-9ada19101fc5/go.mod h1:twkDnbuQxJYemMlGd4JFIcuhgX83tXhKS2B/PRMpOho=
|
||||||
github.com/vishvananda/netns v0.0.0-20200728191858-db3c7e526aae h1:4hwBBUfQCFe3Cym0ZtKyq7L16eZUtYKs+BaHDN6mAns=
|
github.com/vishvananda/netns v0.0.0-20200728191858-db3c7e526aae h1:4hwBBUfQCFe3Cym0ZtKyq7L16eZUtYKs+BaHDN6mAns=
|
||||||
@@ -179,9 +178,8 @@ golang.org/x/net v0.0.0-20201224014010-6772e930b67b/go.mod h1:m0MpNAwzfU5UDzcl9v
|
|||||||
golang.org/x/net v0.0.0-20210119194325-5f4716e94777/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
golang.org/x/net v0.0.0-20210119194325-5f4716e94777/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
||||||
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
||||||
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.0.0-20210504132125-bbd867fde50d h1:nTDGCTeAu2LhcsHTRzjyIUbZHCJ4QePArsm27Hka0UM=
|
||||||
golang.org/x/net v0.0.0-20210504132125-bbd867fde50d/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
golang.org/x/net v0.0.0-20210504132125-bbd867fde50d/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
||||||
golang.org/x/net v0.0.0-20210916014120-12bc252f5db8 h1:/6y1LfuqNuQdHAm0jjtPtgRcxIxjVZgm5OTu8/QhZvk=
|
|
||||||
golang.org/x/net v0.0.0-20210916014120-12bc252f5db8/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
|
||||||
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-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c h1:5KslGYwFpkhGh+Q16bwMP3cOontH8FOep7tGV86Y7SQ=
|
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c h1:5KslGYwFpkhGh+Q16bwMP3cOontH8FOep7tGV86Y7SQ=
|
||||||
|
|||||||
@@ -65,7 +65,7 @@ func (d *DoT) copy() (copied DoT) {
|
|||||||
// unset field of the receiver settings object.
|
// unset field of the receiver settings object.
|
||||||
func (d *DoT) mergeWith(other DoT) {
|
func (d *DoT) mergeWith(other DoT) {
|
||||||
d.Enabled = helpers.MergeWithBool(d.Enabled, other.Enabled)
|
d.Enabled = helpers.MergeWithBool(d.Enabled, other.Enabled)
|
||||||
d.UpdatePeriod = helpers.MergeWithDurationPtr(d.UpdatePeriod, other.UpdatePeriod)
|
d.UpdatePeriod = helpers.MergeWithDuration(d.UpdatePeriod, other.UpdatePeriod)
|
||||||
d.Unbound.mergeWith(other.Unbound)
|
d.Unbound.mergeWith(other.Unbound)
|
||||||
d.Blacklist.mergeWith(other.Blacklist)
|
d.Blacklist.mergeWith(other.Blacklist)
|
||||||
}
|
}
|
||||||
@@ -75,7 +75,7 @@ func (d *DoT) mergeWith(other DoT) {
|
|||||||
// settings.
|
// settings.
|
||||||
func (d *DoT) overrideWith(other DoT) {
|
func (d *DoT) overrideWith(other DoT) {
|
||||||
d.Enabled = helpers.OverrideWithBool(d.Enabled, other.Enabled)
|
d.Enabled = helpers.OverrideWithBool(d.Enabled, other.Enabled)
|
||||||
d.UpdatePeriod = helpers.OverrideWithDurationPtr(d.UpdatePeriod, other.UpdatePeriod)
|
d.UpdatePeriod = helpers.OverrideWithDuration(d.UpdatePeriod, other.UpdatePeriod)
|
||||||
d.Unbound.overrideWith(other.Unbound)
|
d.Unbound.overrideWith(other.Unbound)
|
||||||
d.Blacklist.overrideWith(other.Blacklist)
|
d.Blacklist.overrideWith(other.Blacklist)
|
||||||
}
|
}
|
||||||
@@ -83,7 +83,7 @@ func (d *DoT) overrideWith(other DoT) {
|
|||||||
func (d *DoT) setDefaults() {
|
func (d *DoT) setDefaults() {
|
||||||
d.Enabled = helpers.DefaultBool(d.Enabled, true)
|
d.Enabled = helpers.DefaultBool(d.Enabled, true)
|
||||||
const defaultUpdatePeriod = 24 * time.Hour
|
const defaultUpdatePeriod = 24 * time.Hour
|
||||||
d.UpdatePeriod = helpers.DefaultDurationPtr(d.UpdatePeriod, defaultUpdatePeriod)
|
d.UpdatePeriod = helpers.DefaultDuration(d.UpdatePeriod, defaultUpdatePeriod)
|
||||||
d.Unbound.setDefaults()
|
d.Unbound.setDefaults()
|
||||||
d.Blacklist.setDefaults()
|
d.Blacklist.setDefaults()
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -17,7 +17,6 @@ var (
|
|||||||
ErrOpenVPNCustomPortNotAllowed = errors.New("custom endpoint port is not allowed")
|
ErrOpenVPNCustomPortNotAllowed = errors.New("custom endpoint port is not allowed")
|
||||||
ErrOpenVPNEncryptionPresetNotValid = errors.New("PIA encryption preset is not valid")
|
ErrOpenVPNEncryptionPresetNotValid = errors.New("PIA encryption preset is not valid")
|
||||||
ErrOpenVPNInterfaceNotValid = errors.New("interface name is not valid")
|
ErrOpenVPNInterfaceNotValid = errors.New("interface name is not valid")
|
||||||
ErrOpenVPNKeyPassphraseIsEmpty = errors.New("key passphrase is empty")
|
|
||||||
ErrOpenVPNMSSFixIsTooHigh = errors.New("mssfix option value is too high")
|
ErrOpenVPNMSSFixIsTooHigh = errors.New("mssfix option value is too high")
|
||||||
ErrOpenVPNPasswordIsEmpty = errors.New("password is empty")
|
ErrOpenVPNPasswordIsEmpty = errors.New("password is empty")
|
||||||
ErrOpenVPNTCPNotSupported = errors.New("TCP protocol is not supported")
|
ErrOpenVPNTCPNotSupported = errors.New("TCP protocol is not supported")
|
||||||
|
|||||||
@@ -3,7 +3,6 @@ package settings
|
|||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"os"
|
"os"
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/qdm12/gluetun/internal/configuration/settings/helpers"
|
"github.com/qdm12/gluetun/internal/configuration/settings/helpers"
|
||||||
"github.com/qdm12/gotree"
|
"github.com/qdm12/gotree"
|
||||||
@@ -16,12 +15,6 @@ type Health struct {
|
|||||||
// for the health check server.
|
// for the health check server.
|
||||||
// It cannot be the empty string in the internal state.
|
// It cannot be the empty string in the internal state.
|
||||||
ServerAddress string
|
ServerAddress string
|
||||||
// ReadHeaderTimeout is the HTTP server header read timeout
|
|
||||||
// duration of the HTTP server. It defaults to 100 milliseconds.
|
|
||||||
ReadHeaderTimeout time.Duration
|
|
||||||
// ReadTimeout is the HTTP read timeout duration of the
|
|
||||||
// HTTP server. It defaults to 500 milliseconds.
|
|
||||||
ReadTimeout time.Duration
|
|
||||||
// TargetAddress is the address (host or host:port)
|
// TargetAddress is the address (host or host:port)
|
||||||
// to TCP dial to periodically for the health check.
|
// to TCP dial to periodically for the health check.
|
||||||
// It cannot be the empty string in the internal state.
|
// It cannot be the empty string in the internal state.
|
||||||
@@ -47,11 +40,9 @@ func (h Health) Validate() (err error) {
|
|||||||
|
|
||||||
func (h *Health) copy() (copied Health) {
|
func (h *Health) copy() (copied Health) {
|
||||||
return Health{
|
return Health{
|
||||||
ServerAddress: h.ServerAddress,
|
ServerAddress: h.ServerAddress,
|
||||||
ReadHeaderTimeout: h.ReadHeaderTimeout,
|
TargetAddress: h.TargetAddress,
|
||||||
ReadTimeout: h.ReadTimeout,
|
VPN: h.VPN.copy(),
|
||||||
TargetAddress: h.TargetAddress,
|
|
||||||
VPN: h.VPN.copy(),
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -59,8 +50,6 @@ func (h *Health) copy() (copied Health) {
|
|||||||
// unset field of the receiver settings object.
|
// unset field of the receiver settings object.
|
||||||
func (h *Health) MergeWith(other Health) {
|
func (h *Health) MergeWith(other Health) {
|
||||||
h.ServerAddress = helpers.MergeWithString(h.ServerAddress, other.ServerAddress)
|
h.ServerAddress = helpers.MergeWithString(h.ServerAddress, other.ServerAddress)
|
||||||
h.ReadHeaderTimeout = helpers.MergeWithDuration(h.ReadHeaderTimeout, other.ReadHeaderTimeout)
|
|
||||||
h.ReadTimeout = helpers.MergeWithDuration(h.ReadTimeout, other.ReadTimeout)
|
|
||||||
h.TargetAddress = helpers.MergeWithString(h.TargetAddress, other.TargetAddress)
|
h.TargetAddress = helpers.MergeWithString(h.TargetAddress, other.TargetAddress)
|
||||||
h.VPN.mergeWith(other.VPN)
|
h.VPN.mergeWith(other.VPN)
|
||||||
}
|
}
|
||||||
@@ -70,18 +59,12 @@ func (h *Health) MergeWith(other Health) {
|
|||||||
// settings.
|
// settings.
|
||||||
func (h *Health) OverrideWith(other Health) {
|
func (h *Health) OverrideWith(other Health) {
|
||||||
h.ServerAddress = helpers.OverrideWithString(h.ServerAddress, other.ServerAddress)
|
h.ServerAddress = helpers.OverrideWithString(h.ServerAddress, other.ServerAddress)
|
||||||
h.ReadHeaderTimeout = helpers.OverrideWithDuration(h.ReadHeaderTimeout, other.ReadHeaderTimeout)
|
|
||||||
h.ReadTimeout = helpers.OverrideWithDuration(h.ReadTimeout, other.ReadTimeout)
|
|
||||||
h.TargetAddress = helpers.OverrideWithString(h.TargetAddress, other.TargetAddress)
|
h.TargetAddress = helpers.OverrideWithString(h.TargetAddress, other.TargetAddress)
|
||||||
h.VPN.overrideWith(other.VPN)
|
h.VPN.overrideWith(other.VPN)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (h *Health) SetDefaults() {
|
func (h *Health) SetDefaults() {
|
||||||
h.ServerAddress = helpers.DefaultString(h.ServerAddress, "127.0.0.1:9999")
|
h.ServerAddress = helpers.DefaultString(h.ServerAddress, "127.0.0.1:9999")
|
||||||
const defaultReadHeaderTimeout = 100 * time.Millisecond
|
|
||||||
h.ReadHeaderTimeout = helpers.DefaultDuration(h.ReadHeaderTimeout, defaultReadHeaderTimeout)
|
|
||||||
const defaultReadTimeout = 500 * time.Millisecond
|
|
||||||
h.ReadTimeout = helpers.DefaultDuration(h.ReadTimeout, defaultReadTimeout)
|
|
||||||
h.TargetAddress = helpers.DefaultString(h.TargetAddress, "cloudflare.com:443")
|
h.TargetAddress = helpers.DefaultString(h.TargetAddress, "cloudflare.com:443")
|
||||||
h.VPN.setDefaults()
|
h.VPN.setDefaults()
|
||||||
}
|
}
|
||||||
@@ -94,8 +77,6 @@ func (h Health) toLinesNode() (node *gotree.Node) {
|
|||||||
node = gotree.New("Health settings:")
|
node = gotree.New("Health settings:")
|
||||||
node.Appendf("Server listening address: %s", h.ServerAddress)
|
node.Appendf("Server listening address: %s", h.ServerAddress)
|
||||||
node.Appendf("Target address: %s", h.TargetAddress)
|
node.Appendf("Target address: %s", h.TargetAddress)
|
||||||
node.Appendf("Read header timeout: %s", h.ReadHeaderTimeout)
|
|
||||||
node.Appendf("Read timeout: %s", h.ReadTimeout)
|
|
||||||
node.AppendNode(h.VPN.toLinesNode("VPN"))
|
node.AppendNode(h.VPN.toLinesNode("VPN"))
|
||||||
return node
|
return node
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -35,23 +35,23 @@ func (h *HealthyWait) copy() (copied HealthyWait) {
|
|||||||
// mergeWith merges the other settings into any
|
// mergeWith merges the other settings into any
|
||||||
// unset field of the receiver settings object.
|
// unset field of the receiver settings object.
|
||||||
func (h *HealthyWait) mergeWith(other HealthyWait) {
|
func (h *HealthyWait) mergeWith(other HealthyWait) {
|
||||||
h.Initial = helpers.MergeWithDurationPtr(h.Initial, other.Initial)
|
h.Initial = helpers.MergeWithDuration(h.Initial, other.Initial)
|
||||||
h.Addition = helpers.MergeWithDurationPtr(h.Addition, other.Addition)
|
h.Addition = helpers.MergeWithDuration(h.Addition, other.Addition)
|
||||||
}
|
}
|
||||||
|
|
||||||
// overrideWith overrides fields of the receiver
|
// overrideWith overrides fields of the receiver
|
||||||
// settings object with any field set in the other
|
// settings object with any field set in the other
|
||||||
// settings.
|
// settings.
|
||||||
func (h *HealthyWait) overrideWith(other HealthyWait) {
|
func (h *HealthyWait) overrideWith(other HealthyWait) {
|
||||||
h.Initial = helpers.OverrideWithDurationPtr(h.Initial, other.Initial)
|
h.Initial = helpers.OverrideWithDuration(h.Initial, other.Initial)
|
||||||
h.Addition = helpers.OverrideWithDurationPtr(h.Addition, other.Addition)
|
h.Addition = helpers.OverrideWithDuration(h.Addition, other.Addition)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (h *HealthyWait) setDefaults() {
|
func (h *HealthyWait) setDefaults() {
|
||||||
const initialDurationDefault = 6 * time.Second
|
const initialDurationDefault = 6 * time.Second
|
||||||
const additionDurationDefault = 5 * time.Second
|
const additionDurationDefault = 5 * time.Second
|
||||||
h.Initial = helpers.DefaultDurationPtr(h.Initial, initialDurationDefault)
|
h.Initial = helpers.DefaultDuration(h.Initial, initialDurationDefault)
|
||||||
h.Addition = helpers.DefaultDurationPtr(h.Addition, additionDurationDefault)
|
h.Addition = helpers.DefaultDuration(h.Addition, additionDurationDefault)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (h HealthyWait) String() string {
|
func (h HealthyWait) String() string {
|
||||||
|
|||||||
@@ -73,15 +73,7 @@ func DefaultStringPtr(existing *string, defaultValue string) (result *string) {
|
|||||||
return result
|
return result
|
||||||
}
|
}
|
||||||
|
|
||||||
func DefaultDuration(existing time.Duration,
|
func DefaultDuration(existing *time.Duration,
|
||||||
defaultValue time.Duration) (result time.Duration) {
|
|
||||||
if existing != 0 {
|
|
||||||
return existing
|
|
||||||
}
|
|
||||||
return defaultValue
|
|
||||||
}
|
|
||||||
|
|
||||||
func DefaultDurationPtr(existing *time.Duration,
|
|
||||||
defaultValue time.Duration) (result *time.Duration) {
|
defaultValue time.Duration) (result *time.Duration) {
|
||||||
if existing != nil {
|
if existing != nil {
|
||||||
return existing
|
return existing
|
||||||
|
|||||||
@@ -107,14 +107,7 @@ func MergeWithIP(existing, other net.IP) (result net.IP) {
|
|||||||
return result
|
return result
|
||||||
}
|
}
|
||||||
|
|
||||||
func MergeWithDuration(existing, other time.Duration) (result time.Duration) {
|
func MergeWithDuration(existing, other *time.Duration) (result *time.Duration) {
|
||||||
if existing != 0 {
|
|
||||||
return existing
|
|
||||||
}
|
|
||||||
return other
|
|
||||||
}
|
|
||||||
|
|
||||||
func MergeWithDurationPtr(existing, other *time.Duration) (result *time.Duration) {
|
|
||||||
if existing != nil {
|
if existing != nil {
|
||||||
return existing
|
return existing
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -93,16 +93,7 @@ func OverrideWithIP(existing, other net.IP) (result net.IP) {
|
|||||||
return result
|
return result
|
||||||
}
|
}
|
||||||
|
|
||||||
func OverrideWithDuration(existing, other time.Duration) (
|
func OverrideWithDuration(existing, other *time.Duration) (result *time.Duration) {
|
||||||
result time.Duration) {
|
|
||||||
if other == 0 {
|
|
||||||
return existing
|
|
||||||
}
|
|
||||||
return other
|
|
||||||
}
|
|
||||||
|
|
||||||
func OverrideWithDurationPtr(existing, other *time.Duration) (
|
|
||||||
result *time.Duration) {
|
|
||||||
if other == nil {
|
if other == nil {
|
||||||
return existing
|
return existing
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,7 +3,6 @@ package settings
|
|||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"os"
|
"os"
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/qdm12/gluetun/internal/configuration/settings/helpers"
|
"github.com/qdm12/gluetun/internal/configuration/settings/helpers"
|
||||||
"github.com/qdm12/gotree"
|
"github.com/qdm12/gotree"
|
||||||
@@ -34,12 +33,6 @@ type HTTPProxy struct {
|
|||||||
// each request/response. It cannot be nil in the
|
// each request/response. It cannot be nil in the
|
||||||
// internal state.
|
// internal state.
|
||||||
Log *bool
|
Log *bool
|
||||||
// ReadHeaderTimeout is the HTTP header read timeout duration
|
|
||||||
// of the HTTP server. It defaults to 1 second if left unset.
|
|
||||||
ReadHeaderTimeout time.Duration
|
|
||||||
// ReadTimeout is the HTTP read timeout duration
|
|
||||||
// of the HTTP server. It defaults to 3 seconds if left unset.
|
|
||||||
ReadTimeout time.Duration
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (h HTTPProxy) validate() (err error) {
|
func (h HTTPProxy) validate() (err error) {
|
||||||
@@ -56,14 +49,12 @@ func (h HTTPProxy) validate() (err error) {
|
|||||||
|
|
||||||
func (h *HTTPProxy) copy() (copied HTTPProxy) {
|
func (h *HTTPProxy) copy() (copied HTTPProxy) {
|
||||||
return HTTPProxy{
|
return HTTPProxy{
|
||||||
User: helpers.CopyStringPtr(h.User),
|
User: helpers.CopyStringPtr(h.User),
|
||||||
Password: helpers.CopyStringPtr(h.Password),
|
Password: helpers.CopyStringPtr(h.Password),
|
||||||
ListeningAddress: h.ListeningAddress,
|
ListeningAddress: h.ListeningAddress,
|
||||||
Enabled: helpers.CopyBoolPtr(h.Enabled),
|
Enabled: helpers.CopyBoolPtr(h.Enabled),
|
||||||
Stealth: helpers.CopyBoolPtr(h.Stealth),
|
Stealth: helpers.CopyBoolPtr(h.Stealth),
|
||||||
Log: helpers.CopyBoolPtr(h.Log),
|
Log: helpers.CopyBoolPtr(h.Log),
|
||||||
ReadHeaderTimeout: h.ReadHeaderTimeout,
|
|
||||||
ReadTimeout: h.ReadTimeout,
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -76,8 +67,6 @@ func (h *HTTPProxy) mergeWith(other HTTPProxy) {
|
|||||||
h.Enabled = helpers.MergeWithBool(h.Enabled, other.Enabled)
|
h.Enabled = helpers.MergeWithBool(h.Enabled, other.Enabled)
|
||||||
h.Stealth = helpers.MergeWithBool(h.Stealth, other.Stealth)
|
h.Stealth = helpers.MergeWithBool(h.Stealth, other.Stealth)
|
||||||
h.Log = helpers.MergeWithBool(h.Log, other.Log)
|
h.Log = helpers.MergeWithBool(h.Log, other.Log)
|
||||||
h.ReadHeaderTimeout = helpers.MergeWithDuration(h.ReadHeaderTimeout, other.ReadHeaderTimeout)
|
|
||||||
h.ReadTimeout = helpers.MergeWithDuration(h.ReadTimeout, other.ReadTimeout)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// overrideWith overrides fields of the receiver
|
// overrideWith overrides fields of the receiver
|
||||||
@@ -90,8 +79,6 @@ func (h *HTTPProxy) overrideWith(other HTTPProxy) {
|
|||||||
h.Enabled = helpers.OverrideWithBool(h.Enabled, other.Enabled)
|
h.Enabled = helpers.OverrideWithBool(h.Enabled, other.Enabled)
|
||||||
h.Stealth = helpers.OverrideWithBool(h.Stealth, other.Stealth)
|
h.Stealth = helpers.OverrideWithBool(h.Stealth, other.Stealth)
|
||||||
h.Log = helpers.OverrideWithBool(h.Log, other.Log)
|
h.Log = helpers.OverrideWithBool(h.Log, other.Log)
|
||||||
h.ReadHeaderTimeout = helpers.OverrideWithDuration(h.ReadHeaderTimeout, other.ReadHeaderTimeout)
|
|
||||||
h.ReadTimeout = helpers.OverrideWithDuration(h.ReadTimeout, other.ReadTimeout)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (h *HTTPProxy) setDefaults() {
|
func (h *HTTPProxy) setDefaults() {
|
||||||
@@ -101,10 +88,6 @@ func (h *HTTPProxy) setDefaults() {
|
|||||||
h.Enabled = helpers.DefaultBool(h.Enabled, false)
|
h.Enabled = helpers.DefaultBool(h.Enabled, false)
|
||||||
h.Stealth = helpers.DefaultBool(h.Stealth, false)
|
h.Stealth = helpers.DefaultBool(h.Stealth, false)
|
||||||
h.Log = helpers.DefaultBool(h.Log, false)
|
h.Log = helpers.DefaultBool(h.Log, false)
|
||||||
const defaultReadHeaderTimeout = time.Second
|
|
||||||
h.ReadHeaderTimeout = helpers.DefaultDuration(h.ReadHeaderTimeout, defaultReadHeaderTimeout)
|
|
||||||
const defaultReadTimeout = 3 * time.Second
|
|
||||||
h.ReadTimeout = helpers.DefaultDuration(h.ReadTimeout, defaultReadTimeout)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (h HTTPProxy) String() string {
|
func (h HTTPProxy) String() string {
|
||||||
@@ -123,8 +106,6 @@ func (h HTTPProxy) toLinesNode() (node *gotree.Node) {
|
|||||||
node.Appendf("Password: %s", helpers.ObfuscatePassword(*h.Password))
|
node.Appendf("Password: %s", helpers.ObfuscatePassword(*h.Password))
|
||||||
node.Appendf("Stealth mode: %s", helpers.BoolPtrToYesNo(h.Stealth))
|
node.Appendf("Stealth mode: %s", helpers.BoolPtrToYesNo(h.Stealth))
|
||||||
node.Appendf("Log: %s", helpers.BoolPtrToYesNo(h.Log))
|
node.Appendf("Log: %s", helpers.BoolPtrToYesNo(h.Log))
|
||||||
node.Appendf("Read header timeout: %s", h.ReadHeaderTimeout)
|
|
||||||
node.Appendf("Read timeout: %s", h.ReadTimeout)
|
|
||||||
|
|
||||||
return node
|
return node
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -20,15 +20,13 @@ type OpenVPN struct {
|
|||||||
// It can only be "2.4" or "2.5".
|
// It can only be "2.4" or "2.5".
|
||||||
Version string
|
Version string
|
||||||
// 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 an empty string in the internal state
|
||||||
// It is usually required but in some cases can be the empty string
|
// if OpenVPN is used.
|
||||||
// to indicate no user+password authentication is needed.
|
User string
|
||||||
User *string
|
|
||||||
// Password is the OpenVPN authentication password.
|
// Password is the OpenVPN authentication password.
|
||||||
// It cannot be nil in the internal state if OpenVPN is used.
|
// It cannot be an empty string in the internal state
|
||||||
// It is usually required but in some cases can be the empty string
|
// if OpenVPN is used.
|
||||||
// to indicate no user+password authentication is needed.
|
Password string
|
||||||
Password *string
|
|
||||||
// ConfFile is a custom OpenVPN configuration file path.
|
// ConfFile is a custom OpenVPN configuration file path.
|
||||||
// It can be set to the empty string for it to be ignored.
|
// It can be set to the empty string for it to be ignored.
|
||||||
// It cannot be nil in the internal state.
|
// It cannot be nil in the internal state.
|
||||||
@@ -42,25 +40,16 @@ type OpenVPN struct {
|
|||||||
// It cannot be nil in the internal state.
|
// It cannot be nil in the internal state.
|
||||||
// It is ignored if it is set to the empty string.
|
// It is ignored if it is set to the empty string.
|
||||||
Auth *string
|
Auth *string
|
||||||
// Cert is the OpenVPN certificate for the <cert> block.
|
// ClientCrt is the OpenVPN client certificate.
|
||||||
// This is notably used by Cyberghost and VPN secure.
|
// This is notably used by Cyberghost.
|
||||||
// It can be set to the empty string to be ignored.
|
// It can be set to the empty string to be ignored.
|
||||||
// It cannot be nil in the internal state.
|
// It cannot be nil in the internal state.
|
||||||
Cert *string
|
ClientCrt *string
|
||||||
// Key is the OpenVPN key.
|
// ClientKey is the OpenVPN client key.
|
||||||
// This is used by Cyberghost and VPN Unlimited.
|
// This is used by Cyberghost and VPN Unlimited.
|
||||||
// It can be set to the empty string to be ignored.
|
// It can be set to the empty string to be ignored.
|
||||||
// It cannot be nil in the internal state.
|
// It cannot be nil in the internal state.
|
||||||
Key *string
|
ClientKey *string
|
||||||
// EncryptedKey is the content of an encrypted
|
|
||||||
// key for OpenVPN. It is used by VPN secure.
|
|
||||||
// It defaults to the empty string meaning it is not
|
|
||||||
// to be used. KeyPassphrase must be set if this one is set.
|
|
||||||
EncryptedKey *string
|
|
||||||
// KeyPassphrase is the key passphrase to be used by OpenVPN
|
|
||||||
// to decrypt the EncryptedPrivateKey. It defaults to the
|
|
||||||
// empty string and must be set if EncryptedPrivateKey is set.
|
|
||||||
KeyPassphrase *string
|
|
||||||
// PIAEncPreset is the encryption preset for
|
// PIAEncPreset is the encryption preset for
|
||||||
// Private Internet Access. It can be set to an
|
// Private Internet Access. It can be set to an
|
||||||
// empty string for other providers.
|
// empty string for other providers.
|
||||||
@@ -100,14 +89,14 @@ func (o OpenVPN) validate(vpnProvider string) (err error) {
|
|||||||
|
|
||||||
isCustom := vpnProvider == providers.Custom
|
isCustom := vpnProvider == providers.Custom
|
||||||
|
|
||||||
if !isCustom && *o.User == "" {
|
if !isCustom && o.User == "" {
|
||||||
return ErrOpenVPNUserIsEmpty
|
return ErrOpenVPNUserIsEmpty
|
||||||
}
|
}
|
||||||
|
|
||||||
passwordRequired := !isCustom &&
|
passwordRequired := !isCustom &&
|
||||||
(vpnProvider != providers.Ivpn || !ivpnAccountID.MatchString(*o.User))
|
(vpnProvider != providers.Ivpn || !ivpnAccountID.MatchString(o.User))
|
||||||
|
|
||||||
if passwordRequired && *o.Password == "" {
|
if passwordRequired && o.Password == "" {
|
||||||
return ErrOpenVPNPasswordIsEmpty
|
return ErrOpenVPNPasswordIsEmpty
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -116,25 +105,16 @@ func (o OpenVPN) validate(vpnProvider string) (err error) {
|
|||||||
return fmt.Errorf("custom configuration file: %w", err)
|
return fmt.Errorf("custom configuration file: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
err = validateOpenVPNClientCertificate(vpnProvider, *o.Cert)
|
err = validateOpenVPNClientCertificate(vpnProvider, *o.ClientCrt)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("client certificate: %w", err)
|
return fmt.Errorf("client certificate: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
err = validateOpenVPNClientKey(vpnProvider, *o.Key)
|
err = validateOpenVPNClientKey(vpnProvider, *o.ClientKey)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("client key: %w", err)
|
return fmt.Errorf("client key: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
err = validateOpenVPNEncryptedKey(vpnProvider, *o.EncryptedKey)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("encrypted key: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if *o.EncryptedKey != "" && *o.KeyPassphrase == "" {
|
|
||||||
return fmt.Errorf("%w", ErrOpenVPNKeyPassphraseIsEmpty)
|
|
||||||
}
|
|
||||||
|
|
||||||
const maxMSSFix = 10000
|
const maxMSSFix = 10000
|
||||||
if *o.MSSFix > maxMSSFix {
|
if *o.MSSFix > maxMSSFix {
|
||||||
return fmt.Errorf("%w: %d is over the maximum value of %d",
|
return fmt.Errorf("%w: %d is over the maximum value of %d",
|
||||||
@@ -183,7 +163,6 @@ func validateOpenVPNClientCertificate(vpnProvider,
|
|||||||
switch vpnProvider {
|
switch vpnProvider {
|
||||||
case
|
case
|
||||||
providers.Cyberghost,
|
providers.Cyberghost,
|
||||||
providers.VPNSecure,
|
|
||||||
providers.VPNUnlimited:
|
providers.VPNUnlimited:
|
||||||
if clientCert == "" {
|
if clientCert == "" {
|
||||||
return ErrMissingValue
|
return ErrMissingValue
|
||||||
@@ -223,42 +202,23 @@ func validateOpenVPNClientKey(vpnProvider, clientKey string) (err error) {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func validateOpenVPNEncryptedKey(vpnProvider,
|
|
||||||
encryptedPrivateKey string) (err error) {
|
|
||||||
if vpnProvider == providers.VPNSecure && encryptedPrivateKey == "" {
|
|
||||||
return ErrMissingValue
|
|
||||||
}
|
|
||||||
|
|
||||||
if encryptedPrivateKey == "" {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
_, err = base64.StdEncoding.DecodeString(encryptedPrivateKey)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (o *OpenVPN) copy() (copied OpenVPN) {
|
func (o *OpenVPN) copy() (copied OpenVPN) {
|
||||||
return OpenVPN{
|
return OpenVPN{
|
||||||
Version: o.Version,
|
Version: o.Version,
|
||||||
User: helpers.CopyStringPtr(o.User),
|
User: o.User,
|
||||||
Password: helpers.CopyStringPtr(o.Password),
|
Password: o.Password,
|
||||||
ConfFile: helpers.CopyStringPtr(o.ConfFile),
|
ConfFile: helpers.CopyStringPtr(o.ConfFile),
|
||||||
Ciphers: helpers.CopyStringSlice(o.Ciphers),
|
Ciphers: helpers.CopyStringSlice(o.Ciphers),
|
||||||
Auth: helpers.CopyStringPtr(o.Auth),
|
Auth: helpers.CopyStringPtr(o.Auth),
|
||||||
Cert: helpers.CopyStringPtr(o.Cert),
|
ClientCrt: helpers.CopyStringPtr(o.ClientCrt),
|
||||||
Key: helpers.CopyStringPtr(o.Key),
|
ClientKey: helpers.CopyStringPtr(o.ClientKey),
|
||||||
EncryptedKey: helpers.CopyStringPtr(o.EncryptedKey),
|
PIAEncPreset: helpers.CopyStringPtr(o.PIAEncPreset),
|
||||||
KeyPassphrase: helpers.CopyStringPtr(o.KeyPassphrase),
|
IPv6: helpers.CopyBoolPtr(o.IPv6),
|
||||||
PIAEncPreset: helpers.CopyStringPtr(o.PIAEncPreset),
|
MSSFix: helpers.CopyUint16Ptr(o.MSSFix),
|
||||||
IPv6: helpers.CopyBoolPtr(o.IPv6),
|
Interface: o.Interface,
|
||||||
MSSFix: helpers.CopyUint16Ptr(o.MSSFix),
|
ProcessUser: o.ProcessUser,
|
||||||
Interface: o.Interface,
|
Verbosity: helpers.CopyIntPtr(o.Verbosity),
|
||||||
ProcessUser: o.ProcessUser,
|
Flags: helpers.CopyStringSlice(o.Flags),
|
||||||
Verbosity: helpers.CopyIntPtr(o.Verbosity),
|
|
||||||
Flags: helpers.CopyStringSlice(o.Flags),
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -266,15 +226,13 @@ func (o *OpenVPN) copy() (copied OpenVPN) {
|
|||||||
// unset field of the receiver settings object.
|
// unset field of the receiver settings object.
|
||||||
func (o *OpenVPN) mergeWith(other OpenVPN) {
|
func (o *OpenVPN) mergeWith(other OpenVPN) {
|
||||||
o.Version = helpers.MergeWithString(o.Version, other.Version)
|
o.Version = helpers.MergeWithString(o.Version, other.Version)
|
||||||
o.User = helpers.MergeWithStringPtr(o.User, other.User)
|
o.User = helpers.MergeWithString(o.User, other.User)
|
||||||
o.Password = helpers.MergeWithStringPtr(o.Password, other.Password)
|
o.Password = helpers.MergeWithString(o.Password, other.Password)
|
||||||
o.ConfFile = helpers.MergeWithStringPtr(o.ConfFile, other.ConfFile)
|
o.ConfFile = helpers.MergeWithStringPtr(o.ConfFile, other.ConfFile)
|
||||||
o.Ciphers = helpers.MergeStringSlices(o.Ciphers, other.Ciphers)
|
o.Ciphers = helpers.MergeStringSlices(o.Ciphers, other.Ciphers)
|
||||||
o.Auth = helpers.MergeWithStringPtr(o.Auth, other.Auth)
|
o.Auth = helpers.MergeWithStringPtr(o.Auth, other.Auth)
|
||||||
o.Cert = helpers.MergeWithStringPtr(o.Cert, other.Cert)
|
o.ClientCrt = helpers.MergeWithStringPtr(o.ClientCrt, other.ClientCrt)
|
||||||
o.Key = helpers.MergeWithStringPtr(o.Key, other.Key)
|
o.ClientKey = helpers.MergeWithStringPtr(o.ClientKey, other.ClientKey)
|
||||||
o.EncryptedKey = helpers.MergeWithStringPtr(o.EncryptedKey, other.EncryptedKey)
|
|
||||||
o.KeyPassphrase = helpers.MergeWithStringPtr(o.KeyPassphrase, other.KeyPassphrase)
|
|
||||||
o.PIAEncPreset = helpers.MergeWithStringPtr(o.PIAEncPreset, other.PIAEncPreset)
|
o.PIAEncPreset = helpers.MergeWithStringPtr(o.PIAEncPreset, other.PIAEncPreset)
|
||||||
o.IPv6 = helpers.MergeWithBool(o.IPv6, other.IPv6)
|
o.IPv6 = helpers.MergeWithBool(o.IPv6, other.IPv6)
|
||||||
o.MSSFix = helpers.MergeWithUint16(o.MSSFix, other.MSSFix)
|
o.MSSFix = helpers.MergeWithUint16(o.MSSFix, other.MSSFix)
|
||||||
@@ -289,15 +247,13 @@ func (o *OpenVPN) mergeWith(other OpenVPN) {
|
|||||||
// settings.
|
// settings.
|
||||||
func (o *OpenVPN) overrideWith(other OpenVPN) {
|
func (o *OpenVPN) overrideWith(other OpenVPN) {
|
||||||
o.Version = helpers.OverrideWithString(o.Version, other.Version)
|
o.Version = helpers.OverrideWithString(o.Version, other.Version)
|
||||||
o.User = helpers.OverrideWithStringPtr(o.User, other.User)
|
o.User = helpers.OverrideWithString(o.User, other.User)
|
||||||
o.Password = helpers.OverrideWithStringPtr(o.Password, other.Password)
|
o.Password = helpers.OverrideWithString(o.Password, other.Password)
|
||||||
o.ConfFile = helpers.OverrideWithStringPtr(o.ConfFile, other.ConfFile)
|
o.ConfFile = helpers.OverrideWithStringPtr(o.ConfFile, other.ConfFile)
|
||||||
o.Ciphers = helpers.OverrideWithStringSlice(o.Ciphers, other.Ciphers)
|
o.Ciphers = helpers.OverrideWithStringSlice(o.Ciphers, other.Ciphers)
|
||||||
o.Auth = helpers.OverrideWithStringPtr(o.Auth, other.Auth)
|
o.Auth = helpers.OverrideWithStringPtr(o.Auth, other.Auth)
|
||||||
o.Cert = helpers.OverrideWithStringPtr(o.Cert, other.Cert)
|
o.ClientCrt = helpers.OverrideWithStringPtr(o.ClientCrt, other.ClientCrt)
|
||||||
o.Key = helpers.OverrideWithStringPtr(o.Key, other.Key)
|
o.ClientKey = helpers.OverrideWithStringPtr(o.ClientKey, other.ClientKey)
|
||||||
o.EncryptedKey = helpers.OverrideWithStringPtr(o.EncryptedKey, other.EncryptedKey)
|
|
||||||
o.KeyPassphrase = helpers.OverrideWithStringPtr(o.KeyPassphrase, other.KeyPassphrase)
|
|
||||||
o.PIAEncPreset = helpers.OverrideWithStringPtr(o.PIAEncPreset, other.PIAEncPreset)
|
o.PIAEncPreset = helpers.OverrideWithStringPtr(o.PIAEncPreset, other.PIAEncPreset)
|
||||||
o.IPv6 = helpers.OverrideWithBool(o.IPv6, other.IPv6)
|
o.IPv6 = helpers.OverrideWithBool(o.IPv6, other.IPv6)
|
||||||
o.MSSFix = helpers.OverrideWithUint16(o.MSSFix, other.MSSFix)
|
o.MSSFix = helpers.OverrideWithUint16(o.MSSFix, other.MSSFix)
|
||||||
@@ -309,19 +265,14 @@ func (o *OpenVPN) overrideWith(other OpenVPN) {
|
|||||||
|
|
||||||
func (o *OpenVPN) setDefaults(vpnProvider string) {
|
func (o *OpenVPN) setDefaults(vpnProvider string) {
|
||||||
o.Version = helpers.DefaultString(o.Version, openvpn.Openvpn25)
|
o.Version = helpers.DefaultString(o.Version, openvpn.Openvpn25)
|
||||||
o.User = helpers.DefaultStringPtr(o.User, "")
|
|
||||||
if vpnProvider == providers.Mullvad {
|
if vpnProvider == providers.Mullvad {
|
||||||
o.Password = helpers.DefaultStringPtr(o.Password, "m")
|
o.Password = "m"
|
||||||
} else {
|
|
||||||
o.Password = helpers.DefaultStringPtr(o.Password, "")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
o.ConfFile = helpers.DefaultStringPtr(o.ConfFile, "")
|
o.ConfFile = helpers.DefaultStringPtr(o.ConfFile, "")
|
||||||
o.Auth = helpers.DefaultStringPtr(o.Auth, "")
|
o.Auth = helpers.DefaultStringPtr(o.Auth, "")
|
||||||
o.Cert = helpers.DefaultStringPtr(o.Cert, "")
|
o.ClientCrt = helpers.DefaultStringPtr(o.ClientCrt, "")
|
||||||
o.Key = helpers.DefaultStringPtr(o.Key, "")
|
o.ClientKey = helpers.DefaultStringPtr(o.ClientKey, "")
|
||||||
o.EncryptedKey = helpers.DefaultStringPtr(o.EncryptedKey, "")
|
|
||||||
o.KeyPassphrase = helpers.DefaultStringPtr(o.KeyPassphrase, "")
|
|
||||||
|
|
||||||
var defaultEncPreset string
|
var defaultEncPreset string
|
||||||
if vpnProvider == providers.PrivateInternetAccess {
|
if vpnProvider == providers.PrivateInternetAccess {
|
||||||
@@ -343,8 +294,8 @@ func (o OpenVPN) String() string {
|
|||||||
func (o OpenVPN) toLinesNode() (node *gotree.Node) {
|
func (o OpenVPN) toLinesNode() (node *gotree.Node) {
|
||||||
node = gotree.New("OpenVPN settings:")
|
node = gotree.New("OpenVPN settings:")
|
||||||
node.Appendf("OpenVPN version: %s", o.Version)
|
node.Appendf("OpenVPN version: %s", o.Version)
|
||||||
node.Appendf("User: %s", helpers.ObfuscatePassword(*o.User))
|
node.Appendf("User: %s", helpers.ObfuscatePassword(o.User))
|
||||||
node.Appendf("Password: %s", helpers.ObfuscatePassword(*o.Password))
|
node.Appendf("Password: %s", helpers.ObfuscatePassword(o.Password))
|
||||||
|
|
||||||
if *o.ConfFile != "" {
|
if *o.ConfFile != "" {
|
||||||
node.Appendf("Custom configuration file: %s", *o.ConfFile)
|
node.Appendf("Custom configuration file: %s", *o.ConfFile)
|
||||||
@@ -358,17 +309,12 @@ func (o OpenVPN) toLinesNode() (node *gotree.Node) {
|
|||||||
node.Appendf("Auth: %s", *o.Auth)
|
node.Appendf("Auth: %s", *o.Auth)
|
||||||
}
|
}
|
||||||
|
|
||||||
if *o.Cert != "" {
|
if *o.ClientCrt != "" {
|
||||||
node.Appendf("Client crt: %s", helpers.ObfuscateData(*o.Cert))
|
node.Appendf("Client crt: %s", helpers.ObfuscateData(*o.ClientCrt))
|
||||||
}
|
}
|
||||||
|
|
||||||
if *o.Key != "" {
|
if *o.ClientKey != "" {
|
||||||
node.Appendf("Client key: %s", helpers.ObfuscateData(*o.Key))
|
node.Appendf("Client key: %s", helpers.ObfuscateData(*o.ClientKey))
|
||||||
}
|
|
||||||
|
|
||||||
if *o.EncryptedKey != "" {
|
|
||||||
node.Appendf("Encrypted key: %s (key passhrapse %s)",
|
|
||||||
helpers.ObfuscateData(*o.EncryptedKey), helpers.ObfuscatePassword(*o.KeyPassphrase))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if *o.PIAEncPreset != "" {
|
if *o.PIAEncPreset != "" {
|
||||||
|
|||||||
@@ -60,8 +60,8 @@ func (o OpenVPNSelection) validate(vpnProvider string) (err error) {
|
|||||||
case providers.Expressvpn, providers.Fastestvpn,
|
case providers.Expressvpn, providers.Fastestvpn,
|
||||||
providers.Ipvanish, providers.Nordvpn,
|
providers.Ipvanish, providers.Nordvpn,
|
||||||
providers.Privado, providers.Purevpn,
|
providers.Privado, providers.Purevpn,
|
||||||
providers.Surfshark, providers.VPNSecure,
|
providers.Surfshark, providers.VPNUnlimited,
|
||||||
providers.VPNUnlimited, providers.Vyprvpn:
|
providers.Vyprvpn:
|
||||||
return fmt.Errorf("%w: for VPN service provider %s",
|
return fmt.Errorf("%w: for VPN service provider %s",
|
||||||
ErrOpenVPNCustomPortNotAllowed, vpnProvider)
|
ErrOpenVPNCustomPortNotAllowed, vpnProvider)
|
||||||
default:
|
default:
|
||||||
@@ -82,9 +82,6 @@ func (o OpenVPNSelection) validate(vpnProvider string) (err error) {
|
|||||||
case providers.Protonvpn:
|
case providers.Protonvpn:
|
||||||
allowedTCP = []uint16{443, 5995, 8443}
|
allowedTCP = []uint16{443, 5995, 8443}
|
||||||
allowedUDP = []uint16{80, 443, 1194, 4569, 5060}
|
allowedUDP = []uint16{80, 443, 1194, 4569, 5060}
|
||||||
case providers.SlickVPN:
|
|
||||||
allowedTCP = []uint16{443, 8080, 8888}
|
|
||||||
allowedUDP = []uint16{443, 8080, 8888}
|
|
||||||
case providers.Wevpn:
|
case providers.Wevpn:
|
||||||
allowedTCP = []uint16{53, 1195, 1199, 2018}
|
allowedTCP = []uint16{53, 1195, 1199, 2018}
|
||||||
allowedUDP = []uint16{80, 1194, 1198}
|
allowedUDP = []uint16{80, 1194, 1198}
|
||||||
|
|||||||
@@ -48,18 +48,18 @@ func (p *PublicIP) copy() (copied PublicIP) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (p *PublicIP) mergeWith(other PublicIP) {
|
func (p *PublicIP) mergeWith(other PublicIP) {
|
||||||
p.Period = helpers.MergeWithDurationPtr(p.Period, other.Period)
|
p.Period = helpers.MergeWithDuration(p.Period, other.Period)
|
||||||
p.IPFilepath = helpers.MergeWithStringPtr(p.IPFilepath, other.IPFilepath)
|
p.IPFilepath = helpers.MergeWithStringPtr(p.IPFilepath, other.IPFilepath)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *PublicIP) overrideWith(other PublicIP) {
|
func (p *PublicIP) overrideWith(other PublicIP) {
|
||||||
p.Period = helpers.OverrideWithDurationPtr(p.Period, other.Period)
|
p.Period = helpers.OverrideWithDuration(p.Period, other.Period)
|
||||||
p.IPFilepath = helpers.OverrideWithStringPtr(p.IPFilepath, other.IPFilepath)
|
p.IPFilepath = helpers.OverrideWithStringPtr(p.IPFilepath, other.IPFilepath)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *PublicIP) setDefaults() {
|
func (p *PublicIP) setDefaults() {
|
||||||
const defaultPeriod = 12 * time.Hour
|
const defaultPeriod = 12 * time.Hour
|
||||||
p.Period = helpers.DefaultDurationPtr(p.Period, defaultPeriod)
|
p.Period = helpers.DefaultDuration(p.Period, defaultPeriod)
|
||||||
p.IPFilepath = helpers.DefaultStringPtr(p.IPFilepath, "/tmp/gluetun/ip")
|
p.IPFilepath = helpers.DefaultStringPtr(p.IPFilepath, "/tmp/gluetun/ip")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -45,10 +45,6 @@ type ServerSelection struct { //nolint:maligned
|
|||||||
// FreeOnly is true if VPN servers that are not free should
|
// FreeOnly is true if VPN servers that are not free should
|
||||||
// be filtered. This is used with ProtonVPN and VPN Unlimited.
|
// be filtered. This is used with ProtonVPN and VPN Unlimited.
|
||||||
FreeOnly *bool
|
FreeOnly *bool
|
||||||
// PremiumOnly is true if VPN servers that are not premium should
|
|
||||||
// be filtered. This is used with VPN Secure.
|
|
||||||
// TODO extend to providers using FreeOnly.
|
|
||||||
PremiumOnly *bool
|
|
||||||
// StreamOnly is true if VPN servers not for streaming should
|
// StreamOnly is true if VPN servers not for streaming should
|
||||||
// be filtered. This is used with VPNUnlimited.
|
// be filtered. This is used with VPNUnlimited.
|
||||||
StreamOnly *bool
|
StreamOnly *bool
|
||||||
@@ -67,10 +63,8 @@ type ServerSelection struct { //nolint:maligned
|
|||||||
var (
|
var (
|
||||||
ErrOwnedOnlyNotSupported = errors.New("owned only filter is not supported")
|
ErrOwnedOnlyNotSupported = errors.New("owned only filter is not supported")
|
||||||
ErrFreeOnlyNotSupported = errors.New("free only filter is not supported")
|
ErrFreeOnlyNotSupported = errors.New("free only filter is not supported")
|
||||||
ErrPremiumOnlyNotSupported = errors.New("premium only filter is not supported")
|
|
||||||
ErrStreamOnlyNotSupported = errors.New("stream only filter is not supported")
|
ErrStreamOnlyNotSupported = errors.New("stream only filter is not supported")
|
||||||
ErrMultiHopOnlyNotSupported = errors.New("multi hop only filter is not supported")
|
ErrMultiHopOnlyNotSupported = errors.New("multi hop only filter is not supported")
|
||||||
ErrFreePremiumBothSet = errors.New("free only and premium only filters are both set")
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func (ss *ServerSelection) validate(vpnServiceProvider string,
|
func (ss *ServerSelection) validate(vpnServiceProvider string,
|
||||||
@@ -109,18 +103,6 @@ func (ss *ServerSelection) validate(vpnServiceProvider string,
|
|||||||
ErrFreeOnlyNotSupported, vpnServiceProvider)
|
ErrFreeOnlyNotSupported, vpnServiceProvider)
|
||||||
}
|
}
|
||||||
|
|
||||||
if *ss.PremiumOnly &&
|
|
||||||
!helpers.IsOneOf(vpnServiceProvider,
|
|
||||||
providers.VPNSecure,
|
|
||||||
) {
|
|
||||||
return fmt.Errorf("%w: for VPN service provider %s",
|
|
||||||
ErrPremiumOnlyNotSupported, vpnServiceProvider)
|
|
||||||
}
|
|
||||||
|
|
||||||
if *ss.FreeOnly && *ss.PremiumOnly {
|
|
||||||
return ErrFreePremiumBothSet
|
|
||||||
}
|
|
||||||
|
|
||||||
if *ss.StreamOnly &&
|
if *ss.StreamOnly &&
|
||||||
!helpers.IsOneOf(vpnServiceProvider,
|
!helpers.IsOneOf(vpnServiceProvider,
|
||||||
providers.Protonvpn,
|
providers.Protonvpn,
|
||||||
@@ -212,7 +194,6 @@ func (ss *ServerSelection) copy() (copied ServerSelection) {
|
|||||||
Numbers: helpers.CopyUint16Slice(ss.Numbers),
|
Numbers: helpers.CopyUint16Slice(ss.Numbers),
|
||||||
OwnedOnly: helpers.CopyBoolPtr(ss.OwnedOnly),
|
OwnedOnly: helpers.CopyBoolPtr(ss.OwnedOnly),
|
||||||
FreeOnly: helpers.CopyBoolPtr(ss.FreeOnly),
|
FreeOnly: helpers.CopyBoolPtr(ss.FreeOnly),
|
||||||
PremiumOnly: helpers.CopyBoolPtr(ss.PremiumOnly),
|
|
||||||
StreamOnly: helpers.CopyBoolPtr(ss.StreamOnly),
|
StreamOnly: helpers.CopyBoolPtr(ss.StreamOnly),
|
||||||
MultiHopOnly: helpers.CopyBoolPtr(ss.MultiHopOnly),
|
MultiHopOnly: helpers.CopyBoolPtr(ss.MultiHopOnly),
|
||||||
OpenVPN: ss.OpenVPN.copy(),
|
OpenVPN: ss.OpenVPN.copy(),
|
||||||
@@ -232,7 +213,6 @@ func (ss *ServerSelection) mergeWith(other ServerSelection) {
|
|||||||
ss.Numbers = helpers.MergeUint16Slices(ss.Numbers, other.Numbers)
|
ss.Numbers = helpers.MergeUint16Slices(ss.Numbers, other.Numbers)
|
||||||
ss.OwnedOnly = helpers.MergeWithBool(ss.OwnedOnly, other.OwnedOnly)
|
ss.OwnedOnly = helpers.MergeWithBool(ss.OwnedOnly, other.OwnedOnly)
|
||||||
ss.FreeOnly = helpers.MergeWithBool(ss.FreeOnly, other.FreeOnly)
|
ss.FreeOnly = helpers.MergeWithBool(ss.FreeOnly, other.FreeOnly)
|
||||||
ss.PremiumOnly = helpers.MergeWithBool(ss.PremiumOnly, other.PremiumOnly)
|
|
||||||
ss.StreamOnly = helpers.MergeWithBool(ss.StreamOnly, other.StreamOnly)
|
ss.StreamOnly = helpers.MergeWithBool(ss.StreamOnly, other.StreamOnly)
|
||||||
ss.MultiHopOnly = helpers.MergeWithBool(ss.MultiHopOnly, other.MultiHopOnly)
|
ss.MultiHopOnly = helpers.MergeWithBool(ss.MultiHopOnly, other.MultiHopOnly)
|
||||||
|
|
||||||
@@ -252,7 +232,6 @@ func (ss *ServerSelection) overrideWith(other ServerSelection) {
|
|||||||
ss.Numbers = helpers.OverrideWithUint16Slice(ss.Numbers, other.Numbers)
|
ss.Numbers = helpers.OverrideWithUint16Slice(ss.Numbers, other.Numbers)
|
||||||
ss.OwnedOnly = helpers.OverrideWithBool(ss.OwnedOnly, other.OwnedOnly)
|
ss.OwnedOnly = helpers.OverrideWithBool(ss.OwnedOnly, other.OwnedOnly)
|
||||||
ss.FreeOnly = helpers.OverrideWithBool(ss.FreeOnly, other.FreeOnly)
|
ss.FreeOnly = helpers.OverrideWithBool(ss.FreeOnly, other.FreeOnly)
|
||||||
ss.PremiumOnly = helpers.OverrideWithBool(ss.PremiumOnly, other.PremiumOnly)
|
|
||||||
ss.StreamOnly = helpers.OverrideWithBool(ss.StreamOnly, other.StreamOnly)
|
ss.StreamOnly = helpers.OverrideWithBool(ss.StreamOnly, other.StreamOnly)
|
||||||
ss.MultiHopOnly = helpers.OverrideWithBool(ss.MultiHopOnly, other.MultiHopOnly)
|
ss.MultiHopOnly = helpers.OverrideWithBool(ss.MultiHopOnly, other.MultiHopOnly)
|
||||||
ss.OpenVPN.overrideWith(other.OpenVPN)
|
ss.OpenVPN.overrideWith(other.OpenVPN)
|
||||||
@@ -264,7 +243,6 @@ func (ss *ServerSelection) setDefaults(vpnProvider string) {
|
|||||||
ss.TargetIP = helpers.DefaultIP(ss.TargetIP, net.IP{})
|
ss.TargetIP = helpers.DefaultIP(ss.TargetIP, net.IP{})
|
||||||
ss.OwnedOnly = helpers.DefaultBool(ss.OwnedOnly, false)
|
ss.OwnedOnly = helpers.DefaultBool(ss.OwnedOnly, false)
|
||||||
ss.FreeOnly = helpers.DefaultBool(ss.FreeOnly, false)
|
ss.FreeOnly = helpers.DefaultBool(ss.FreeOnly, false)
|
||||||
ss.PremiumOnly = helpers.DefaultBool(ss.PremiumOnly, false)
|
|
||||||
ss.StreamOnly = helpers.DefaultBool(ss.StreamOnly, false)
|
ss.StreamOnly = helpers.DefaultBool(ss.StreamOnly, false)
|
||||||
ss.MultiHopOnly = helpers.DefaultBool(ss.MultiHopOnly, false)
|
ss.MultiHopOnly = helpers.DefaultBool(ss.MultiHopOnly, false)
|
||||||
ss.OpenVPN.setDefaults(vpnProvider)
|
ss.OpenVPN.setDefaults(vpnProvider)
|
||||||
@@ -321,10 +299,6 @@ func (ss ServerSelection) toLinesNode() (node *gotree.Node) {
|
|||||||
node.Appendf("Free only servers: yes")
|
node.Appendf("Free only servers: yes")
|
||||||
}
|
}
|
||||||
|
|
||||||
if *ss.PremiumOnly {
|
|
||||||
node.Appendf("Premium only servers: yes")
|
|
||||||
}
|
|
||||||
|
|
||||||
if *ss.StreamOnly {
|
if *ss.StreamOnly {
|
||||||
node.Appendf("Stream only servers: yes")
|
node.Appendf("Stream only servers: yes")
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -46,7 +46,7 @@ func (s *Settings) Validate(storage Storage) (err error) {
|
|||||||
"version": s.Version.validate,
|
"version": s.Version.validate,
|
||||||
// Pprof validation done in pprof constructor
|
// Pprof validation done in pprof constructor
|
||||||
"VPN": func() error {
|
"VPN": func() error {
|
||||||
return s.VPN.Validate(storage)
|
return s.VPN.validate(storage)
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -73,7 +73,7 @@ func (s *Settings) copy() (copied Settings) {
|
|||||||
System: s.System.copy(),
|
System: s.System.copy(),
|
||||||
Updater: s.Updater.copy(),
|
Updater: s.Updater.copy(),
|
||||||
Version: s.Version.copy(),
|
Version: s.Version.copy(),
|
||||||
VPN: s.VPN.Copy(),
|
VPN: s.VPN.copy(),
|
||||||
Pprof: s.Pprof.Copy(),
|
Pprof: s.Pprof.Copy(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -108,7 +108,7 @@ func (s *Settings) OverrideWith(other Settings,
|
|||||||
patchedSettings.System.overrideWith(other.System)
|
patchedSettings.System.overrideWith(other.System)
|
||||||
patchedSettings.Updater.overrideWith(other.Updater)
|
patchedSettings.Updater.overrideWith(other.Updater)
|
||||||
patchedSettings.Version.overrideWith(other.Version)
|
patchedSettings.Version.overrideWith(other.Version)
|
||||||
patchedSettings.VPN.OverrideWith(other.VPN)
|
patchedSettings.VPN.overrideWith(other.VPN)
|
||||||
patchedSettings.Pprof.OverrideWith(other.Pprof)
|
patchedSettings.Pprof.OverrideWith(other.Pprof)
|
||||||
err = patchedSettings.Validate(storage)
|
err = patchedSettings.Validate(storage)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|||||||
@@ -67,8 +67,6 @@ func Test_Settings_String(t *testing.T) {
|
|||||||
├── Health settings:
|
├── Health settings:
|
||||||
| ├── Server listening address: 127.0.0.1:9999
|
| ├── Server listening address: 127.0.0.1:9999
|
||||||
| ├── Target address: cloudflare.com:443
|
| ├── Target address: cloudflare.com:443
|
||||||
| ├── Read header timeout: 100ms
|
|
||||||
| ├── Read timeout: 500ms
|
|
||||||
| └── VPN wait durations:
|
| └── VPN wait durations:
|
||||||
| ├── Initial duration: 6s
|
| ├── Initial duration: 6s
|
||||||
| └── Additional duration: 5s
|
| └── Additional duration: 5s
|
||||||
|
|||||||
@@ -73,7 +73,7 @@ func (u *Updater) copy() (copied Updater) {
|
|||||||
// mergeWith merges the other settings into any
|
// mergeWith merges the other settings into any
|
||||||
// unset field of the receiver settings object.
|
// unset field of the receiver settings object.
|
||||||
func (u *Updater) mergeWith(other Updater) {
|
func (u *Updater) mergeWith(other Updater) {
|
||||||
u.Period = helpers.MergeWithDurationPtr(u.Period, other.Period)
|
u.Period = helpers.MergeWithDuration(u.Period, other.Period)
|
||||||
u.DNSAddress = helpers.MergeWithString(u.DNSAddress, other.DNSAddress)
|
u.DNSAddress = helpers.MergeWithString(u.DNSAddress, other.DNSAddress)
|
||||||
u.MinRatio = helpers.MergeWithFloat64(u.MinRatio, other.MinRatio)
|
u.MinRatio = helpers.MergeWithFloat64(u.MinRatio, other.MinRatio)
|
||||||
u.Providers = helpers.MergeStringSlices(u.Providers, other.Providers)
|
u.Providers = helpers.MergeStringSlices(u.Providers, other.Providers)
|
||||||
@@ -83,14 +83,14 @@ func (u *Updater) mergeWith(other Updater) {
|
|||||||
// settings object with any field set in the other
|
// settings object with any field set in the other
|
||||||
// settings.
|
// settings.
|
||||||
func (u *Updater) overrideWith(other Updater) {
|
func (u *Updater) overrideWith(other Updater) {
|
||||||
u.Period = helpers.OverrideWithDurationPtr(u.Period, other.Period)
|
u.Period = helpers.OverrideWithDuration(u.Period, other.Period)
|
||||||
u.DNSAddress = helpers.OverrideWithString(u.DNSAddress, other.DNSAddress)
|
u.DNSAddress = helpers.OverrideWithString(u.DNSAddress, other.DNSAddress)
|
||||||
u.MinRatio = helpers.OverrideWithFloat64(u.MinRatio, other.MinRatio)
|
u.MinRatio = helpers.OverrideWithFloat64(u.MinRatio, other.MinRatio)
|
||||||
u.Providers = helpers.OverrideWithStringSlice(u.Providers, other.Providers)
|
u.Providers = helpers.OverrideWithStringSlice(u.Providers, other.Providers)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (u *Updater) SetDefaults(vpnProvider string) {
|
func (u *Updater) SetDefaults(vpnProvider string) {
|
||||||
u.Period = helpers.DefaultDurationPtr(u.Period, 0)
|
u.Period = helpers.DefaultDuration(u.Period, 0)
|
||||||
u.DNSAddress = helpers.DefaultString(u.DNSAddress, "1.1.1.1:53")
|
u.DNSAddress = helpers.DefaultString(u.DNSAddress, "1.1.1.1:53")
|
||||||
|
|
||||||
if u.MinRatio == 0 {
|
if u.MinRatio == 0 {
|
||||||
|
|||||||
@@ -20,7 +20,7 @@ type VPN struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// TODO v4 remove pointer for receiver (because of Surfshark).
|
// TODO v4 remove pointer for receiver (because of Surfshark).
|
||||||
func (v *VPN) Validate(storage Storage) (err error) {
|
func (v *VPN) validate(storage Storage) (err error) {
|
||||||
// Validate Type
|
// Validate Type
|
||||||
validVPNTypes := []string{vpn.OpenVPN, vpn.Wireguard}
|
validVPNTypes := []string{vpn.OpenVPN, vpn.Wireguard}
|
||||||
if !helpers.IsOneOf(v.Type, validVPNTypes...) {
|
if !helpers.IsOneOf(v.Type, validVPNTypes...) {
|
||||||
@@ -48,7 +48,7 @@ func (v *VPN) Validate(storage Storage) (err error) {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (v *VPN) Copy() (copied VPN) {
|
func (v *VPN) copy() (copied VPN) {
|
||||||
return VPN{
|
return VPN{
|
||||||
Type: v.Type,
|
Type: v.Type,
|
||||||
Provider: v.Provider.copy(),
|
Provider: v.Provider.copy(),
|
||||||
@@ -64,7 +64,7 @@ func (v *VPN) mergeWith(other VPN) {
|
|||||||
v.Wireguard.mergeWith(other.Wireguard)
|
v.Wireguard.mergeWith(other.Wireguard)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (v *VPN) OverrideWith(other VPN) {
|
func (v *VPN) overrideWith(other VPN) {
|
||||||
v.Type = helpers.OverrideWithString(v.Type, other.Type)
|
v.Type = helpers.OverrideWithString(v.Type, other.Type)
|
||||||
v.Provider.overrideWith(other.Provider)
|
v.Provider.overrideWith(other.Provider)
|
||||||
v.OpenVPN.overrideWith(other.OpenVPN)
|
v.OpenVPN.overrideWith(other.OpenVPN)
|
||||||
|
|||||||
40
internal/configuration/sources/env/openvpn.go
vendored
40
internal/configuration/sources/env/openvpn.go
vendored
@@ -11,8 +11,7 @@ import (
|
|||||||
func (r *Reader) readOpenVPN() (
|
func (r *Reader) readOpenVPN() (
|
||||||
openVPN settings.OpenVPN, err error) {
|
openVPN settings.OpenVPN, err error) {
|
||||||
defer func() {
|
defer func() {
|
||||||
err = unsetEnvKeys([]string{"OPENVPN_KEY", "OPENVPN_CERT",
|
err = unsetEnvKeys([]string{"OPENVPN_CLIENTKEY", "OPENVPN_CLIENTCRT"}, err)
|
||||||
"OPENVPN_KEY_PASSPHRASE", "OPENVPN_ENCRYPTED_KEY"}, err)
|
|
||||||
}()
|
}()
|
||||||
|
|
||||||
openVPN.Version = getCleanedEnv("OPENVPN_VERSION")
|
openVPN.Version = getCleanedEnv("OPENVPN_VERSION")
|
||||||
@@ -31,11 +30,8 @@ func (r *Reader) readOpenVPN() (
|
|||||||
openVPN.Auth = &auth
|
openVPN.Auth = &auth
|
||||||
}
|
}
|
||||||
|
|
||||||
openVPN.Cert = envToStringPtr("OPENVPN_CERT")
|
openVPN.ClientCrt = envToStringPtr("OPENVPN_CLIENTCRT")
|
||||||
openVPN.Key = envToStringPtr("OPENVPN_KEY")
|
openVPN.ClientKey = envToStringPtr("OPENVPN_CLIENTKEY")
|
||||||
openVPN.EncryptedKey = envToStringPtr("OPENVPN_ENCRYPTED_KEY")
|
|
||||||
|
|
||||||
openVPN.KeyPassphrase = r.readOpenVPNKeyPassphrase()
|
|
||||||
|
|
||||||
openVPN.PIAEncPreset = r.readPIAEncryptionPreset()
|
openVPN.PIAEncPreset = r.readPIAEncryptionPreset()
|
||||||
|
|
||||||
@@ -69,37 +65,17 @@ func (r *Reader) readOpenVPN() (
|
|||||||
return openVPN, nil
|
return openVPN, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *Reader) readOpenVPNUser() (user *string) {
|
func (r *Reader) readOpenVPNUser() (user string) {
|
||||||
user = new(string)
|
_, user = r.getEnvWithRetro("OPENVPN_USER", "USER")
|
||||||
_, *user = r.getEnvWithRetro("OPENVPN_USER", "USER")
|
|
||||||
if *user == "" {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Remove spaces in user ID to simplify user's life, thanks @JeordyR
|
// Remove spaces in user ID to simplify user's life, thanks @JeordyR
|
||||||
*user = strings.ReplaceAll(*user, " ", "")
|
return strings.ReplaceAll(user, " ", "")
|
||||||
return user
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *Reader) readOpenVPNPassword() (password *string) {
|
func (r *Reader) readOpenVPNPassword() (password string) {
|
||||||
password = new(string)
|
_, password = r.getEnvWithRetro("OPENVPN_PASSWORD", "PASSWORD")
|
||||||
_, *password = r.getEnvWithRetro("OPENVPN_PASSWORD", "PASSWORD")
|
|
||||||
if *password == "" {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
return password
|
return password
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *Reader) readOpenVPNKeyPassphrase() (passphrase *string) {
|
|
||||||
passphrase = new(string)
|
|
||||||
*passphrase = getCleanedEnv("OPENVPN_KEY_PASSPHRASE")
|
|
||||||
if *passphrase == "" {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
return passphrase
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *Reader) readPIAEncryptionPreset() (presetPtr *string) {
|
func (r *Reader) readPIAEncryptionPreset() (presetPtr *string) {
|
||||||
_, preset := r.getEnvWithRetro(
|
_, preset := r.getEnvWithRetro(
|
||||||
"PRIVATE_INTERNET_ACCESS_OPENVPN_ENCRYPTION_PRESET",
|
"PRIVATE_INTERNET_ACCESS_OPENVPN_ENCRYPTION_PRESET",
|
||||||
|
|||||||
@@ -77,12 +77,6 @@ func (r *Reader) readServerSelection(vpnProvider, vpnType string) (
|
|||||||
return ss, fmt.Errorf("environment variable FREE_ONLY: %w", err)
|
return ss, fmt.Errorf("environment variable FREE_ONLY: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// VPNSecure only
|
|
||||||
ss.PremiumOnly, err = envToBoolPtr("PREMIUM_ONLY")
|
|
||||||
if err != nil {
|
|
||||||
return ss, fmt.Errorf("environment variable PREMIUM_ONLY: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// VPNUnlimited only
|
// VPNUnlimited only
|
||||||
ss.MultiHopOnly, err = envToBoolPtr("MULTIHOP_ONLY")
|
ss.MultiHopOnly, err = envToBoolPtr("MULTIHOP_ONLY")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|||||||
@@ -11,23 +11,18 @@ const (
|
|||||||
OpenVPNClientKeyPath = "/gluetun/client.key"
|
OpenVPNClientKeyPath = "/gluetun/client.key"
|
||||||
// OpenVPNClientCertificatePath is the OpenVPN client certificate filepath.
|
// OpenVPNClientCertificatePath is the OpenVPN client certificate filepath.
|
||||||
OpenVPNClientCertificatePath = "/gluetun/client.crt"
|
OpenVPNClientCertificatePath = "/gluetun/client.crt"
|
||||||
openVPNEncryptedKey = "/gluetun/openvpn_encrypted_key"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func (r *Reader) readOpenVPN() (settings settings.OpenVPN, err error) {
|
func (r *Reader) readOpenVPN() (settings settings.OpenVPN, err error) {
|
||||||
settings.Key, err = readPEMFile(OpenVPNClientKeyPath)
|
settings.ClientKey, err = readPEMFile(OpenVPNClientKeyPath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return settings, fmt.Errorf("client key: %w", err)
|
return settings, fmt.Errorf("client key: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
settings.Cert, err = readPEMFile(OpenVPNClientCertificatePath)
|
settings.ClientCrt, err = readPEMFile(OpenVPNClientCertificatePath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return settings, fmt.Errorf("client certificate: %w", err)
|
return settings, fmt.Errorf("client certificate: %w", err)
|
||||||
}
|
}
|
||||||
settings.EncryptedKey, err = readPEMFile(openVPNEncryptedKey)
|
|
||||||
if err != nil {
|
|
||||||
return settings, fmt.Errorf("reading encrypted key file: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
return settings, nil
|
return settings, nil
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -28,6 +28,21 @@ func readSecretFileAsStringPtr(secretPathEnvKey, defaultSecretPath string) (
|
|||||||
return files.ReadFromFile(path)
|
return files.ReadFromFile(path)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func readSecretFileAsString(secretPathEnvKey, defaultSecretPath string) (
|
||||||
|
s string, err error) {
|
||||||
|
path := getCleanedEnv(secretPathEnvKey)
|
||||||
|
if path == "" {
|
||||||
|
path = defaultSecretPath
|
||||||
|
}
|
||||||
|
stringPtr, err := files.ReadFromFile(path)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
} else if stringPtr == nil {
|
||||||
|
return "", nil
|
||||||
|
}
|
||||||
|
return *stringPtr, nil
|
||||||
|
}
|
||||||
|
|
||||||
func readPEMSecretFile(secretPathEnvKey, defaultSecretPath string) (
|
func readPEMSecretFile(secretPathEnvKey, defaultSecretPath string) (
|
||||||
base64Ptr *string, err error) {
|
base64Ptr *string, err error) {
|
||||||
pemData, err := readSecretFileAsStringPtr(secretPathEnvKey, defaultSecretPath)
|
pemData, err := readSecretFileAsStringPtr(secretPathEnvKey, defaultSecretPath)
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ import (
|
|||||||
|
|
||||||
func readOpenVPN() (
|
func readOpenVPN() (
|
||||||
settings settings.OpenVPN, err error) {
|
settings settings.OpenVPN, err error) {
|
||||||
settings.User, err = readSecretFileAsStringPtr(
|
settings.User, err = readSecretFileAsString(
|
||||||
"OPENVPN_USER_SECRETFILE",
|
"OPENVPN_USER_SECRETFILE",
|
||||||
"/run/secrets/openvpn_user",
|
"/run/secrets/openvpn_user",
|
||||||
)
|
)
|
||||||
@@ -16,7 +16,7 @@ func readOpenVPN() (
|
|||||||
return settings, fmt.Errorf("cannot read user file: %w", err)
|
return settings, fmt.Errorf("cannot read user file: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
settings.Password, err = readSecretFileAsStringPtr(
|
settings.Password, err = readSecretFileAsString(
|
||||||
"OPENVPN_PASSWORD_SECRETFILE",
|
"OPENVPN_PASSWORD_SECRETFILE",
|
||||||
"/run/secrets/openvpn_password",
|
"/run/secrets/openvpn_password",
|
||||||
)
|
)
|
||||||
@@ -24,7 +24,7 @@ func readOpenVPN() (
|
|||||||
return settings, fmt.Errorf("cannot read password file: %w", err)
|
return settings, fmt.Errorf("cannot read password file: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
settings.Key, err = readPEMSecretFile(
|
settings.ClientKey, err = readPEMSecretFile(
|
||||||
"OPENVPN_CLIENTKEY_SECRETFILE",
|
"OPENVPN_CLIENTKEY_SECRETFILE",
|
||||||
"/run/secrets/openvpn_clientkey",
|
"/run/secrets/openvpn_clientkey",
|
||||||
)
|
)
|
||||||
@@ -32,23 +32,7 @@ func readOpenVPN() (
|
|||||||
return settings, fmt.Errorf("cannot read client key file: %w", err)
|
return settings, fmt.Errorf("cannot read client key file: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
settings.EncryptedKey, err = readPEMSecretFile(
|
settings.ClientCrt, err = readPEMSecretFile(
|
||||||
"OPENVPN_ENCRYPTED_KEY_SECRETFILE",
|
|
||||||
"/run/secrets/openvpn_encrypted_key",
|
|
||||||
)
|
|
||||||
if err != nil {
|
|
||||||
return settings, fmt.Errorf("reading encrypted key file: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
settings.KeyPassphrase, err = readSecretFileAsStringPtr(
|
|
||||||
"OPENVPN_KEY_PASSPHRASE_SECRETFILE",
|
|
||||||
"/run/secrets/openvpn_key_passphrase",
|
|
||||||
)
|
|
||||||
if err != nil {
|
|
||||||
return settings, fmt.Errorf("reading key passphrase file: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
settings.Cert, err = readPEMSecretFile(
|
|
||||||
"OPENVPN_CLIENTCRT_SECRETFILE",
|
"OPENVPN_CLIENTCRT_SECRETFILE",
|
||||||
"/run/secrets/openvpn_clientcrt",
|
"/run/secrets/openvpn_clientcrt",
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -3,7 +3,4 @@ package openvpn
|
|||||||
const (
|
const (
|
||||||
// AuthConf is the file path to the OpenVPN auth file.
|
// AuthConf is the file path to the OpenVPN auth file.
|
||||||
AuthConf = "/etc/openvpn/auth.conf"
|
AuthConf = "/etc/openvpn/auth.conf"
|
||||||
// AskPassPath is the file path to the decryption passphrase for
|
|
||||||
// and encrypted private key, which is pointed by `askpass`.
|
|
||||||
AskPassPath = "/etc/openvpn/askpass" //nolint:gosec
|
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -19,10 +19,8 @@ const (
|
|||||||
Privatevpn = "privatevpn"
|
Privatevpn = "privatevpn"
|
||||||
Protonvpn = "protonvpn"
|
Protonvpn = "protonvpn"
|
||||||
Purevpn = "purevpn"
|
Purevpn = "purevpn"
|
||||||
SlickVPN = "slickvpn"
|
|
||||||
Surfshark = "surfshark"
|
Surfshark = "surfshark"
|
||||||
Torguard = "torguard"
|
Torguard = "torguard"
|
||||||
VPNSecure = "vpnsecure"
|
|
||||||
VPNUnlimited = "vpn unlimited"
|
VPNUnlimited = "vpn unlimited"
|
||||||
Vyprvpn = "vyprvpn"
|
Vyprvpn = "vyprvpn"
|
||||||
Wevpn = "wevpn"
|
Wevpn = "wevpn"
|
||||||
@@ -46,10 +44,8 @@ func All() []string {
|
|||||||
Privatevpn,
|
Privatevpn,
|
||||||
Protonvpn,
|
Protonvpn,
|
||||||
Purevpn,
|
Purevpn,
|
||||||
SlickVPN,
|
|
||||||
Surfshark,
|
Surfshark,
|
||||||
Torguard,
|
Torguard,
|
||||||
VPNSecure,
|
|
||||||
VPNUnlimited,
|
VPNUnlimited,
|
||||||
Vyprvpn,
|
Vyprvpn,
|
||||||
Wevpn,
|
Wevpn,
|
||||||
|
|||||||
@@ -14,10 +14,8 @@ func (s *Server) Run(ctx context.Context, done chan<- struct{}) {
|
|||||||
go s.runHealthcheckLoop(ctx, loopDone)
|
go s.runHealthcheckLoop(ctx, loopDone)
|
||||||
|
|
||||||
server := http.Server{
|
server := http.Server{
|
||||||
Addr: s.config.ServerAddress,
|
Addr: s.config.ServerAddress,
|
||||||
Handler: s.handler,
|
Handler: s.handler,
|
||||||
ReadHeaderTimeout: s.config.ReadHeaderTimeout,
|
|
||||||
ReadTimeout: s.config.ReadTimeout,
|
|
||||||
}
|
}
|
||||||
serverDone := make(chan struct{})
|
serverDone := make(chan struct{})
|
||||||
go func() {
|
go func() {
|
||||||
|
|||||||
@@ -23,7 +23,7 @@ func (l *Loop) Run(ctx context.Context, done chan<- struct{}) {
|
|||||||
settings := l.state.GetSettings()
|
settings := l.state.GetSettings()
|
||||||
server := New(runCtx, settings.ListeningAddress, l.logger,
|
server := New(runCtx, settings.ListeningAddress, l.logger,
|
||||||
*settings.Stealth, *settings.Log, *settings.User,
|
*settings.Stealth, *settings.Log, *settings.User,
|
||||||
*settings.Password, settings.ReadHeaderTimeout, settings.ReadTimeout)
|
*settings.Password)
|
||||||
|
|
||||||
errorCh := make(chan error)
|
errorCh := make(chan error)
|
||||||
go server.Run(runCtx, errorCh)
|
go server.Run(runCtx, errorCh)
|
||||||
|
|||||||
@@ -8,35 +8,25 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
type Server struct {
|
type Server struct {
|
||||||
address string
|
address string
|
||||||
handler http.Handler
|
handler http.Handler
|
||||||
logger infoErrorer
|
logger infoErrorer
|
||||||
internalWG *sync.WaitGroup
|
internalWG *sync.WaitGroup
|
||||||
readHeaderTimeout time.Duration
|
|
||||||
readTimeout time.Duration
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func New(ctx context.Context, address string, logger Logger,
|
func New(ctx context.Context, address string, logger Logger,
|
||||||
stealth, verbose bool, username, password string,
|
stealth, verbose bool, username, password string) *Server {
|
||||||
readHeaderTimeout, readTimeout time.Duration) *Server {
|
|
||||||
wg := &sync.WaitGroup{}
|
wg := &sync.WaitGroup{}
|
||||||
return &Server{
|
return &Server{
|
||||||
address: address,
|
address: address,
|
||||||
handler: newHandler(ctx, wg, logger, stealth, verbose, username, password),
|
handler: newHandler(ctx, wg, logger, stealth, verbose, username, password),
|
||||||
logger: logger,
|
logger: logger,
|
||||||
internalWG: wg,
|
internalWG: wg,
|
||||||
readHeaderTimeout: readHeaderTimeout,
|
|
||||||
readTimeout: readTimeout,
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Server) Run(ctx context.Context, errorCh chan<- error) {
|
func (s *Server) Run(ctx context.Context, errorCh chan<- error) {
|
||||||
server := http.Server{
|
server := http.Server{Addr: s.address, Handler: s.handler}
|
||||||
Addr: s.address,
|
|
||||||
Handler: s.handler,
|
|
||||||
ReadHeaderTimeout: s.readHeaderTimeout,
|
|
||||||
ReadTimeout: s.readTimeout,
|
|
||||||
}
|
|
||||||
go func() {
|
go func() {
|
||||||
<-ctx.Done()
|
<-ctx.Done()
|
||||||
const shutdownGraceDuration = 2 * time.Second
|
const shutdownGraceDuration = 2 * time.Second
|
||||||
|
|||||||
@@ -2,10 +2,13 @@ package httpserver
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"regexp"
|
"regexp"
|
||||||
|
"time"
|
||||||
|
|
||||||
gomock "github.com/golang/mock/gomock"
|
gomock "github.com/golang/mock/gomock"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
func durationPtr(d time.Duration) *time.Duration { return &d }
|
||||||
|
|
||||||
var _ Logger = (*testLogger)(nil)
|
var _ Logger = (*testLogger)(nil)
|
||||||
|
|
||||||
type testLogger struct{}
|
type testLogger struct{}
|
||||||
|
|||||||
@@ -11,12 +11,7 @@ import (
|
|||||||
// The done channel has an error written to when the HTTP server
|
// The done channel has an error written to when the HTTP server
|
||||||
// is terminated, and can be nil or not nil.
|
// is terminated, and can be nil or not nil.
|
||||||
func (s *Server) Run(ctx context.Context, ready chan<- struct{}, done chan<- struct{}) {
|
func (s *Server) Run(ctx context.Context, ready chan<- struct{}, done chan<- struct{}) {
|
||||||
server := http.Server{
|
server := http.Server{Addr: s.address, Handler: s.handler}
|
||||||
Addr: s.address,
|
|
||||||
Handler: s.handler,
|
|
||||||
ReadHeaderTimeout: s.readHeaderTimeout,
|
|
||||||
ReadTimeout: s.readTimeout,
|
|
||||||
}
|
|
||||||
|
|
||||||
crashed := make(chan struct{})
|
crashed := make(chan struct{})
|
||||||
shutdownDone := make(chan struct{})
|
shutdownDone := make(chan struct{})
|
||||||
|
|||||||
@@ -9,13 +9,11 @@ import (
|
|||||||
// Server is an HTTP server implementation, which uses
|
// Server is an HTTP server implementation, which uses
|
||||||
// the HTTP handler provided.
|
// the HTTP handler provided.
|
||||||
type Server struct {
|
type Server struct {
|
||||||
address string
|
address string
|
||||||
addressSet chan struct{}
|
addressSet chan struct{}
|
||||||
handler http.Handler
|
handler http.Handler
|
||||||
logger Logger
|
logger Logger
|
||||||
readHeaderTimeout time.Duration
|
shutdownTimeout time.Duration
|
||||||
readTimeout time.Duration
|
|
||||||
shutdownTimeout time.Duration
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// New creates a new HTTP server with the given settings.
|
// New creates a new HTTP server with the given settings.
|
||||||
@@ -28,12 +26,10 @@ func New(settings Settings) (s *Server, err error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return &Server{
|
return &Server{
|
||||||
address: settings.Address,
|
address: settings.Address,
|
||||||
addressSet: make(chan struct{}),
|
addressSet: make(chan struct{}),
|
||||||
handler: settings.Handler,
|
handler: settings.Handler,
|
||||||
logger: settings.Logger,
|
logger: settings.Logger,
|
||||||
readHeaderTimeout: settings.ReadHeaderTimeout,
|
shutdownTimeout: *settings.ShutdownTimeout,
|
||||||
readTimeout: settings.ReadTimeout,
|
|
||||||
shutdownTimeout: settings.ShutdownTimeout,
|
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -29,20 +29,16 @@ func Test_New(t *testing.T) {
|
|||||||
},
|
},
|
||||||
"filled settings": {
|
"filled settings": {
|
||||||
settings: Settings{
|
settings: Settings{
|
||||||
Address: ":8001",
|
Address: ":8001",
|
||||||
Handler: someHandler,
|
Handler: someHandler,
|
||||||
Logger: someLogger,
|
Logger: someLogger,
|
||||||
ReadHeaderTimeout: time.Second,
|
ShutdownTimeout: durationPtr(time.Second),
|
||||||
ReadTimeout: time.Second,
|
|
||||||
ShutdownTimeout: time.Second,
|
|
||||||
},
|
},
|
||||||
expected: &Server{
|
expected: &Server{
|
||||||
address: ":8001",
|
address: ":8001",
|
||||||
handler: someHandler,
|
handler: someHandler,
|
||||||
logger: someLogger,
|
logger: someLogger,
|
||||||
readHeaderTimeout: time.Second,
|
shutdownTimeout: time.Second,
|
||||||
readTimeout: time.Second,
|
|
||||||
shutdownTimeout: time.Second,
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -22,34 +22,23 @@ type Settings struct {
|
|||||||
// Logger is the logger to use.
|
// Logger is the logger to use.
|
||||||
// It must be set and cannot be left to nil.
|
// It must be set and cannot be left to nil.
|
||||||
Logger Logger
|
Logger Logger
|
||||||
// ReadHeaderTimeout is the HTTP header read timeout duration
|
|
||||||
// of the HTTP server. It defaults to 3 seconds if left unset.
|
|
||||||
ReadHeaderTimeout time.Duration
|
|
||||||
// ReadTimeout is the HTTP read timeout duration
|
|
||||||
// of the HTTP server. It defaults to 3 seconds if left unset.
|
|
||||||
ReadTimeout time.Duration
|
|
||||||
// ShutdownTimeout is the shutdown timeout duration
|
// ShutdownTimeout is the shutdown timeout duration
|
||||||
// of the HTTP server. It defaults to 3 seconds if left unset.
|
// of the HTTP server. It defaults to 3 seconds.
|
||||||
ShutdownTimeout time.Duration
|
ShutdownTimeout *time.Duration
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Settings) SetDefaults() {
|
func (s *Settings) SetDefaults() {
|
||||||
s.Address = helpers.DefaultString(s.Address, ":8000")
|
s.Address = helpers.DefaultString(s.Address, ":8000")
|
||||||
const defaultReadTimeout = 3 * time.Second
|
|
||||||
s.ReadHeaderTimeout = helpers.DefaultDuration(s.ReadHeaderTimeout, defaultReadTimeout)
|
|
||||||
s.ReadTimeout = helpers.DefaultDuration(s.ReadTimeout, defaultReadTimeout)
|
|
||||||
const defaultShutdownTimeout = 3 * time.Second
|
const defaultShutdownTimeout = 3 * time.Second
|
||||||
s.ShutdownTimeout = helpers.DefaultDuration(s.ShutdownTimeout, defaultShutdownTimeout)
|
s.ShutdownTimeout = helpers.DefaultDuration(s.ShutdownTimeout, defaultShutdownTimeout)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s Settings) Copy() Settings {
|
func (s Settings) Copy() Settings {
|
||||||
return Settings{
|
return Settings{
|
||||||
Address: s.Address,
|
Address: s.Address,
|
||||||
Handler: s.Handler,
|
Handler: s.Handler,
|
||||||
Logger: s.Logger,
|
Logger: s.Logger,
|
||||||
ReadHeaderTimeout: s.ReadHeaderTimeout,
|
ShutdownTimeout: helpers.CopyDurationPtr(s.ShutdownTimeout),
|
||||||
ReadTimeout: s.ReadTimeout,
|
|
||||||
ShutdownTimeout: s.ShutdownTimeout,
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -59,8 +48,6 @@ func (s *Settings) MergeWith(other Settings) {
|
|||||||
if s.Logger == nil {
|
if s.Logger == nil {
|
||||||
s.Logger = other.Logger
|
s.Logger = other.Logger
|
||||||
}
|
}
|
||||||
s.ReadHeaderTimeout = helpers.MergeWithDuration(s.ReadHeaderTimeout, other.ReadHeaderTimeout)
|
|
||||||
s.ReadTimeout = helpers.MergeWithDuration(s.ReadTimeout, other.ReadTimeout)
|
|
||||||
s.ShutdownTimeout = helpers.MergeWithDuration(s.ShutdownTimeout, other.ShutdownTimeout)
|
s.ShutdownTimeout = helpers.MergeWithDuration(s.ShutdownTimeout, other.ShutdownTimeout)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -70,17 +57,13 @@ func (s *Settings) OverrideWith(other Settings) {
|
|||||||
if other.Logger != nil {
|
if other.Logger != nil {
|
||||||
s.Logger = other.Logger
|
s.Logger = other.Logger
|
||||||
}
|
}
|
||||||
s.ReadHeaderTimeout = helpers.OverrideWithDuration(s.ReadHeaderTimeout, other.ReadHeaderTimeout)
|
|
||||||
s.ReadTimeout = helpers.OverrideWithDuration(s.ReadTimeout, other.ReadTimeout)
|
|
||||||
s.ShutdownTimeout = helpers.OverrideWithDuration(s.ShutdownTimeout, other.ShutdownTimeout)
|
s.ShutdownTimeout = helpers.OverrideWithDuration(s.ShutdownTimeout, other.ShutdownTimeout)
|
||||||
}
|
}
|
||||||
|
|
||||||
var (
|
var (
|
||||||
ErrHandlerIsNotSet = errors.New("HTTP handler cannot be left unset")
|
ErrHandlerIsNotSet = errors.New("HTTP handler cannot be left unset")
|
||||||
ErrLoggerIsNotSet = errors.New("logger cannot be left unset")
|
ErrLoggerIsNotSet = errors.New("logger cannot be left unset")
|
||||||
ErrReadHeaderTimeoutTooSmall = errors.New("read header timeout is too small")
|
ErrShutdownTimeoutTooSmall = errors.New("shutdown timeout is too small")
|
||||||
ErrReadTimeoutTooSmall = errors.New("read timeout is too small")
|
|
||||||
ErrShutdownTimeoutTooSmall = errors.New("shutdown timeout is too small")
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func (s Settings) Validate() (err error) {
|
func (s Settings) Validate() (err error) {
|
||||||
@@ -98,24 +81,11 @@ func (s Settings) Validate() (err error) {
|
|||||||
return ErrLoggerIsNotSet
|
return ErrLoggerIsNotSet
|
||||||
}
|
}
|
||||||
|
|
||||||
const minReadTimeout = time.Millisecond
|
|
||||||
if s.ReadHeaderTimeout < minReadTimeout {
|
|
||||||
return fmt.Errorf("%w: %s must be at least %s",
|
|
||||||
ErrReadHeaderTimeoutTooSmall,
|
|
||||||
s.ReadHeaderTimeout, minReadTimeout)
|
|
||||||
}
|
|
||||||
|
|
||||||
if s.ReadTimeout < minReadTimeout {
|
|
||||||
return fmt.Errorf("%w: %s must be at least %s",
|
|
||||||
ErrReadTimeoutTooSmall,
|
|
||||||
s.ReadTimeout, minReadTimeout)
|
|
||||||
}
|
|
||||||
|
|
||||||
const minShutdownTimeout = 5 * time.Millisecond
|
const minShutdownTimeout = 5 * time.Millisecond
|
||||||
if s.ShutdownTimeout < minShutdownTimeout {
|
if *s.ShutdownTimeout < minShutdownTimeout {
|
||||||
return fmt.Errorf("%w: %s must be at least %s",
|
return fmt.Errorf("%w: %s must be at least %s",
|
||||||
ErrShutdownTimeoutTooSmall,
|
ErrShutdownTimeoutTooSmall,
|
||||||
s.ShutdownTimeout, minShutdownTimeout)
|
*s.ShutdownTimeout, minShutdownTimeout)
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
@@ -124,9 +94,7 @@ func (s Settings) Validate() (err error) {
|
|||||||
func (s Settings) ToLinesNode() (node *gotree.Node) {
|
func (s Settings) ToLinesNode() (node *gotree.Node) {
|
||||||
node = gotree.New("HTTP server settings:")
|
node = gotree.New("HTTP server settings:")
|
||||||
node.Appendf("Listening address: %s", s.Address)
|
node.Appendf("Listening address: %s", s.Address)
|
||||||
node.Appendf("Read header timeout: %s", s.ReadHeaderTimeout)
|
node.Appendf("Shutdown timeout: %s", *s.ShutdownTimeout)
|
||||||
node.Appendf("Read timeout: %s", s.ReadTimeout)
|
|
||||||
node.Appendf("Shutdown timeout: %s", s.ShutdownTimeout)
|
|
||||||
return node
|
return node
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -21,24 +21,18 @@ func Test_Settings_SetDefaults(t *testing.T) {
|
|||||||
"empty settings": {
|
"empty settings": {
|
||||||
settings: Settings{},
|
settings: Settings{},
|
||||||
expected: Settings{
|
expected: Settings{
|
||||||
Address: ":8000",
|
Address: ":8000",
|
||||||
ReadHeaderTimeout: defaultTimeout,
|
ShutdownTimeout: durationPtr(defaultTimeout),
|
||||||
ReadTimeout: defaultTimeout,
|
|
||||||
ShutdownTimeout: defaultTimeout,
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
"filled settings": {
|
"filled settings": {
|
||||||
settings: Settings{
|
settings: Settings{
|
||||||
Address: ":8001",
|
Address: ":8001",
|
||||||
ReadHeaderTimeout: time.Second,
|
ShutdownTimeout: durationPtr(time.Second),
|
||||||
ReadTimeout: time.Second,
|
|
||||||
ShutdownTimeout: time.Second,
|
|
||||||
},
|
},
|
||||||
expected: Settings{
|
expected: Settings{
|
||||||
Address: ":8001",
|
Address: ":8001",
|
||||||
ReadHeaderTimeout: time.Second,
|
ShutdownTimeout: durationPtr(time.Second),
|
||||||
ReadTimeout: time.Second,
|
|
||||||
ShutdownTimeout: time.Second,
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
@@ -68,20 +62,16 @@ func Test_Settings_Copy(t *testing.T) {
|
|||||||
"empty settings": {},
|
"empty settings": {},
|
||||||
"filled settings": {
|
"filled settings": {
|
||||||
settings: Settings{
|
settings: Settings{
|
||||||
Address: ":8001",
|
Address: ":8001",
|
||||||
Handler: someHandler,
|
Handler: someHandler,
|
||||||
Logger: someLogger,
|
Logger: someLogger,
|
||||||
ReadHeaderTimeout: time.Second,
|
ShutdownTimeout: durationPtr(time.Second),
|
||||||
ReadTimeout: time.Second,
|
|
||||||
ShutdownTimeout: time.Second,
|
|
||||||
},
|
},
|
||||||
expected: Settings{
|
expected: Settings{
|
||||||
Address: ":8001",
|
Address: ":8001",
|
||||||
Handler: someHandler,
|
Handler: someHandler,
|
||||||
Logger: someLogger,
|
Logger: someLogger,
|
||||||
ReadHeaderTimeout: time.Second,
|
ShutdownTimeout: durationPtr(time.Second),
|
||||||
ReadTimeout: time.Second,
|
|
||||||
ShutdownTimeout: time.Second,
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
@@ -112,38 +102,30 @@ func Test_Settings_MergeWith(t *testing.T) {
|
|||||||
"merge empty with empty": {},
|
"merge empty with empty": {},
|
||||||
"merge empty with filled": {
|
"merge empty with filled": {
|
||||||
other: Settings{
|
other: Settings{
|
||||||
Address: ":8001",
|
Address: ":8001",
|
||||||
Handler: someHandler,
|
Handler: someHandler,
|
||||||
Logger: someLogger,
|
Logger: someLogger,
|
||||||
ReadHeaderTimeout: time.Second,
|
ShutdownTimeout: durationPtr(time.Second),
|
||||||
ReadTimeout: time.Second,
|
|
||||||
ShutdownTimeout: time.Second,
|
|
||||||
},
|
},
|
||||||
expected: Settings{
|
expected: Settings{
|
||||||
Address: ":8001",
|
Address: ":8001",
|
||||||
Handler: someHandler,
|
Handler: someHandler,
|
||||||
Logger: someLogger,
|
Logger: someLogger,
|
||||||
ReadHeaderTimeout: time.Second,
|
ShutdownTimeout: durationPtr(time.Second),
|
||||||
ReadTimeout: time.Second,
|
|
||||||
ShutdownTimeout: time.Second,
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
"merge filled with empty": {
|
"merge filled with empty": {
|
||||||
settings: Settings{
|
settings: Settings{
|
||||||
Address: ":8001",
|
Address: ":8001",
|
||||||
Handler: someHandler,
|
Handler: someHandler,
|
||||||
Logger: someLogger,
|
Logger: someLogger,
|
||||||
ReadHeaderTimeout: time.Second,
|
ShutdownTimeout: durationPtr(time.Second),
|
||||||
ReadTimeout: time.Second,
|
|
||||||
ShutdownTimeout: time.Second,
|
|
||||||
},
|
},
|
||||||
expected: Settings{
|
expected: Settings{
|
||||||
Address: ":8001",
|
Address: ":8001",
|
||||||
Handler: someHandler,
|
Handler: someHandler,
|
||||||
Logger: someLogger,
|
Logger: someLogger,
|
||||||
ReadHeaderTimeout: time.Second,
|
ShutdownTimeout: durationPtr(time.Second),
|
||||||
ReadTimeout: time.Second,
|
|
||||||
ShutdownTimeout: time.Second,
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
@@ -174,62 +156,48 @@ func Test_Settings_OverrideWith(t *testing.T) {
|
|||||||
"override empty with empty": {},
|
"override empty with empty": {},
|
||||||
"override empty with filled": {
|
"override empty with filled": {
|
||||||
other: Settings{
|
other: Settings{
|
||||||
Address: ":8001",
|
Address: ":8001",
|
||||||
Handler: someHandler,
|
Handler: someHandler,
|
||||||
Logger: someLogger,
|
Logger: someLogger,
|
||||||
ReadHeaderTimeout: time.Second,
|
ShutdownTimeout: durationPtr(time.Second),
|
||||||
ReadTimeout: time.Second,
|
|
||||||
ShutdownTimeout: time.Second,
|
|
||||||
},
|
},
|
||||||
expected: Settings{
|
expected: Settings{
|
||||||
Address: ":8001",
|
Address: ":8001",
|
||||||
Handler: someHandler,
|
Handler: someHandler,
|
||||||
Logger: someLogger,
|
Logger: someLogger,
|
||||||
ReadHeaderTimeout: time.Second,
|
ShutdownTimeout: durationPtr(time.Second),
|
||||||
ReadTimeout: time.Second,
|
|
||||||
ShutdownTimeout: time.Second,
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
"override filled with empty": {
|
"override filled with empty": {
|
||||||
settings: Settings{
|
settings: Settings{
|
||||||
Address: ":8001",
|
Address: ":8001",
|
||||||
Handler: someHandler,
|
Handler: someHandler,
|
||||||
Logger: someLogger,
|
Logger: someLogger,
|
||||||
ReadHeaderTimeout: time.Second,
|
ShutdownTimeout: durationPtr(time.Second),
|
||||||
ReadTimeout: time.Second,
|
|
||||||
ShutdownTimeout: time.Second,
|
|
||||||
},
|
},
|
||||||
expected: Settings{
|
expected: Settings{
|
||||||
Address: ":8001",
|
Address: ":8001",
|
||||||
Handler: someHandler,
|
Handler: someHandler,
|
||||||
Logger: someLogger,
|
Logger: someLogger,
|
||||||
ReadHeaderTimeout: time.Second,
|
ShutdownTimeout: durationPtr(time.Second),
|
||||||
ReadTimeout: time.Second,
|
|
||||||
ShutdownTimeout: time.Second,
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
"override filled with filled": {
|
"override filled with filled": {
|
||||||
settings: Settings{
|
settings: Settings{
|
||||||
Address: ":8001",
|
Address: ":8001",
|
||||||
Handler: someHandler,
|
Handler: someHandler,
|
||||||
Logger: someLogger,
|
Logger: someLogger,
|
||||||
ReadHeaderTimeout: time.Second,
|
ShutdownTimeout: durationPtr(time.Second),
|
||||||
ReadTimeout: time.Second,
|
|
||||||
ShutdownTimeout: time.Second,
|
|
||||||
},
|
},
|
||||||
other: Settings{
|
other: Settings{
|
||||||
Address: ":8002",
|
Address: ":8002",
|
||||||
ReadHeaderTimeout: time.Hour,
|
ShutdownTimeout: durationPtr(time.Hour),
|
||||||
ReadTimeout: time.Hour,
|
|
||||||
ShutdownTimeout: time.Hour,
|
|
||||||
},
|
},
|
||||||
expected: Settings{
|
expected: Settings{
|
||||||
Address: ":8002",
|
Address: ":8002",
|
||||||
Handler: someHandler,
|
Handler: someHandler,
|
||||||
Logger: someLogger,
|
Logger: someLogger,
|
||||||
ReadHeaderTimeout: time.Hour,
|
ShutdownTimeout: durationPtr(time.Hour),
|
||||||
ReadTimeout: time.Hour,
|
|
||||||
ShutdownTimeout: time.Hour,
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
@@ -279,47 +247,22 @@ func Test_Settings_Validate(t *testing.T) {
|
|||||||
errWrapped: ErrLoggerIsNotSet,
|
errWrapped: ErrLoggerIsNotSet,
|
||||||
errMessage: ErrLoggerIsNotSet.Error(),
|
errMessage: ErrLoggerIsNotSet.Error(),
|
||||||
},
|
},
|
||||||
"read header timeout too small": {
|
|
||||||
settings: Settings{
|
|
||||||
Address: ":8000",
|
|
||||||
Handler: someHandler,
|
|
||||||
Logger: someLogger,
|
|
||||||
ReadHeaderTimeout: time.Nanosecond,
|
|
||||||
},
|
|
||||||
errWrapped: ErrReadHeaderTimeoutTooSmall,
|
|
||||||
errMessage: "read header timeout is too small: 1ns must be at least 1ms",
|
|
||||||
},
|
|
||||||
"read timeout too small": {
|
|
||||||
settings: Settings{
|
|
||||||
Address: ":8000",
|
|
||||||
Handler: someHandler,
|
|
||||||
Logger: someLogger,
|
|
||||||
ReadHeaderTimeout: time.Millisecond,
|
|
||||||
ReadTimeout: time.Nanosecond,
|
|
||||||
},
|
|
||||||
errWrapped: ErrReadTimeoutTooSmall,
|
|
||||||
errMessage: "read timeout is too small: 1ns must be at least 1ms",
|
|
||||||
},
|
|
||||||
"shutdown timeout too small": {
|
"shutdown timeout too small": {
|
||||||
settings: Settings{
|
settings: Settings{
|
||||||
Address: ":8000",
|
Address: ":8000",
|
||||||
Handler: someHandler,
|
Handler: someHandler,
|
||||||
Logger: someLogger,
|
Logger: someLogger,
|
||||||
ReadHeaderTimeout: time.Millisecond,
|
ShutdownTimeout: durationPtr(time.Millisecond),
|
||||||
ReadTimeout: time.Millisecond,
|
|
||||||
ShutdownTimeout: time.Millisecond,
|
|
||||||
},
|
},
|
||||||
errWrapped: ErrShutdownTimeoutTooSmall,
|
errWrapped: ErrShutdownTimeoutTooSmall,
|
||||||
errMessage: "shutdown timeout is too small: 1ms must be at least 5ms",
|
errMessage: "shutdown timeout is too small: 1ms must be at least 5ms",
|
||||||
},
|
},
|
||||||
"valid settings": {
|
"valid settings": {
|
||||||
settings: Settings{
|
settings: Settings{
|
||||||
Address: ":8000",
|
Address: ":8000",
|
||||||
Handler: someHandler,
|
Handler: someHandler,
|
||||||
Logger: someLogger,
|
Logger: someLogger,
|
||||||
ReadHeaderTimeout: time.Millisecond,
|
ShutdownTimeout: durationPtr(time.Second),
|
||||||
ReadTimeout: time.Millisecond,
|
|
||||||
ShutdownTimeout: time.Second,
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
@@ -348,15 +291,11 @@ func Test_Settings_String(t *testing.T) {
|
|||||||
}{
|
}{
|
||||||
"all values": {
|
"all values": {
|
||||||
settings: Settings{
|
settings: Settings{
|
||||||
Address: ":8000",
|
Address: ":8000",
|
||||||
ReadHeaderTimeout: time.Millisecond,
|
ShutdownTimeout: durationPtr(time.Second),
|
||||||
ReadTimeout: time.Millisecond,
|
|
||||||
ShutdownTimeout: time.Second,
|
|
||||||
},
|
},
|
||||||
s: `HTTP server settings:
|
s: `HTTP server settings:
|
||||||
├── Listening address: :8000
|
├── Listening address: :8000
|
||||||
├── Read header timeout: 1ms
|
|
||||||
├── Read timeout: 1ms
|
|
||||||
└── Shutdown timeout: 1s`,
|
└── Shutdown timeout: 1s`,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -29,7 +29,6 @@ const (
|
|||||||
numberHeader = "Number"
|
numberHeader = "Number"
|
||||||
ownedHeader = "Owned"
|
ownedHeader = "Owned"
|
||||||
portForwardHeader = "Port forwarding"
|
portForwardHeader = "Port forwarding"
|
||||||
premiumHeader = "Premium"
|
|
||||||
regionHeader = "Region"
|
regionHeader = "Region"
|
||||||
streamHeader = "Stream"
|
streamHeader = "Stream"
|
||||||
tcpHeader = "TCP"
|
tcpHeader = "TCP"
|
||||||
@@ -63,8 +62,6 @@ func (s *Server) ToMarkdown(headers ...string) (markdown string) {
|
|||||||
fields[i] = boolToMarkdown(s.Owned)
|
fields[i] = boolToMarkdown(s.Owned)
|
||||||
case portForwardHeader:
|
case portForwardHeader:
|
||||||
fields[i] = boolToMarkdown(s.PortForward)
|
fields[i] = boolToMarkdown(s.PortForward)
|
||||||
case premiumHeader:
|
|
||||||
fields[i] = boolToMarkdown(s.Premium)
|
|
||||||
case regionHeader:
|
case regionHeader:
|
||||||
fields[i] = s.Region
|
fields[i] = s.Region
|
||||||
case streamHeader:
|
case streamHeader:
|
||||||
@@ -126,14 +123,10 @@ func getMarkdownHeaders(vpnProvider string) (headers []string) {
|
|||||||
return []string{countryHeader, regionHeader, cityHeader, hostnameHeader, freeHeader}
|
return []string{countryHeader, regionHeader, cityHeader, hostnameHeader, freeHeader}
|
||||||
case providers.Purevpn:
|
case providers.Purevpn:
|
||||||
return []string{countryHeader, regionHeader, cityHeader, hostnameHeader, tcpHeader, udpHeader}
|
return []string{countryHeader, regionHeader, cityHeader, hostnameHeader, tcpHeader, udpHeader}
|
||||||
case providers.SlickVPN:
|
|
||||||
return []string{regionHeader, countryHeader, cityHeader, hostnameHeader}
|
|
||||||
case providers.Surfshark:
|
case providers.Surfshark:
|
||||||
return []string{regionHeader, countryHeader, cityHeader, hostnameHeader, multiHopHeader, tcpHeader, udpHeader}
|
return []string{regionHeader, countryHeader, cityHeader, hostnameHeader, multiHopHeader, tcpHeader, udpHeader}
|
||||||
case providers.Torguard:
|
case providers.Torguard:
|
||||||
return []string{countryHeader, cityHeader, hostnameHeader, tcpHeader, udpHeader}
|
return []string{countryHeader, cityHeader, hostnameHeader, tcpHeader, udpHeader}
|
||||||
case providers.VPNSecure:
|
|
||||||
return []string{regionHeader, cityHeader, hostnameHeader, premiumHeader}
|
|
||||||
case providers.VPNUnlimited:
|
case providers.VPNUnlimited:
|
||||||
return []string{countryHeader, cityHeader, hostnameHeader, freeHeader, streamHeader, tcpHeader, udpHeader}
|
return []string{countryHeader, cityHeader, hostnameHeader, freeHeader, streamHeader, tcpHeader, udpHeader}
|
||||||
case providers.Vyprvpn:
|
case providers.Vyprvpn:
|
||||||
|
|||||||
@@ -29,7 +29,6 @@ type Server struct {
|
|||||||
WgPubKey string `json:"wgpubkey,omitempty"`
|
WgPubKey string `json:"wgpubkey,omitempty"`
|
||||||
Free bool `json:"free,omitempty"`
|
Free bool `json:"free,omitempty"`
|
||||||
Stream bool `json:"stream,omitempty"`
|
Stream bool `json:"stream,omitempty"`
|
||||||
Premium bool `json:"premium,omitempty"`
|
|
||||||
PortForward bool `json:"port_forward,omitempty"`
|
PortForward bool `json:"port_forward,omitempty"`
|
||||||
Keep bool `json:"keep,omitempty"`
|
Keep bool `json:"keep,omitempty"`
|
||||||
IPs []net.IP `json:"ips,omitempty"`
|
IPs []net.IP `json:"ips,omitempty"`
|
||||||
|
|||||||
@@ -1,55 +1,65 @@
|
|||||||
package openvpn
|
package openvpn
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"io"
|
||||||
"os"
|
"os"
|
||||||
"strings"
|
"strings"
|
||||||
)
|
)
|
||||||
|
|
||||||
// WriteAuthFile writes the OpenVPN auth file to disk with the right permissions.
|
// WriteAuthFile writes the OpenVPN auth file to disk with the right permissions.
|
||||||
func (c *Configurator) WriteAuthFile(user, password string) error {
|
func (c *Configurator) WriteAuthFile(user, password string) error {
|
||||||
content := strings.Join([]string{user, password}, "\n")
|
file, err := os.Open(c.authFilePath)
|
||||||
return writeIfDifferent(c.authFilePath, content, c.puid, c.pgid)
|
|
||||||
}
|
|
||||||
|
|
||||||
// WriteAskPassFile writes the OpenVPN askpass file to disk with the right permissions.
|
|
||||||
func (c *Configurator) WriteAskPassFile(passphrase string) error {
|
|
||||||
return writeIfDifferent(c.askPassPath, passphrase, c.puid, c.pgid)
|
|
||||||
}
|
|
||||||
|
|
||||||
func writeIfDifferent(path, content string, puid, pgid int) (err error) {
|
|
||||||
fileStat, err := os.Stat(path)
|
|
||||||
if err != nil && !os.IsNotExist(err) {
|
if err != nil && !os.IsNotExist(err) {
|
||||||
return fmt.Errorf("obtaining file information: %w", err)
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
const perm = os.FileMode(0400)
|
|
||||||
var writeData, setChown bool
|
|
||||||
if os.IsNotExist(err) {
|
if os.IsNotExist(err) {
|
||||||
writeData = true
|
file, err = os.OpenFile(c.authFilePath, os.O_WRONLY|os.O_CREATE, 0400)
|
||||||
setChown = true
|
|
||||||
} else {
|
|
||||||
data, err := os.ReadFile(path)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("reading file: %w", err)
|
return err
|
||||||
}
|
}
|
||||||
writeData = string(data) != content
|
_, err = file.WriteString(user + "\n" + password)
|
||||||
setChown = fileStat.Mode().Perm() != perm
|
if err != nil {
|
||||||
|
_ = file.Close()
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
err = file.Chown(c.puid, c.pgid)
|
||||||
|
if err != nil {
|
||||||
|
_ = file.Close()
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return file.Close()
|
||||||
}
|
}
|
||||||
|
|
||||||
if writeData {
|
data, err := io.ReadAll(file)
|
||||||
err = os.WriteFile(path, []byte(content), perm)
|
if err != nil {
|
||||||
if err != nil {
|
_ = file.Close()
|
||||||
return fmt.Errorf("writing file: %w", err)
|
return err
|
||||||
}
|
}
|
||||||
|
if err := file.Close(); err != nil {
|
||||||
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
if setChown {
|
lines := strings.Split(string(data), "\n")
|
||||||
err = os.Chown(path, puid, pgid)
|
if len(lines) > 1 && lines[0] == user && lines[1] == password {
|
||||||
if err != nil {
|
return nil
|
||||||
return fmt.Errorf("setting file permissions: %w", err)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
c.logger.Info("username and password changed in " + c.authFilePath)
|
||||||
|
file, err = os.OpenFile(c.authFilePath, os.O_TRUNC|os.O_WRONLY, 0400)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
_, err = file.WriteString(user + "\n" + password)
|
||||||
|
if err != nil {
|
||||||
|
_ = file.Close()
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
err = file.Chown(c.puid, c.pgid)
|
||||||
|
if err != nil {
|
||||||
|
_ = file.Close()
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return file.Close()
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,7 +10,6 @@ type Configurator struct {
|
|||||||
cmder command.RunStarter
|
cmder command.RunStarter
|
||||||
configPath string
|
configPath string
|
||||||
authFilePath string
|
authFilePath string
|
||||||
askPassPath string
|
|
||||||
puid, pgid int
|
puid, pgid int
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -21,7 +20,6 @@ func New(logger Infoer, cmder command.RunStarter,
|
|||||||
cmder: cmder,
|
cmder: cmder,
|
||||||
configPath: configPath,
|
configPath: configPath,
|
||||||
authFilePath: openvpn.AuthConf,
|
authFilePath: openvpn.AuthConf,
|
||||||
askPassPath: openvpn.AskPassPath,
|
|
||||||
puid: puid,
|
puid: puid,
|
||||||
pgid: pgid,
|
pgid: pgid,
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,11 +2,13 @@ package pprof
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"regexp"
|
"regexp"
|
||||||
|
"time"
|
||||||
|
|
||||||
gomock "github.com/golang/mock/gomock"
|
gomock "github.com/golang/mock/gomock"
|
||||||
)
|
)
|
||||||
|
|
||||||
func boolPtr(b bool) *bool { return &b }
|
func boolPtr(b bool) *bool { return &b }
|
||||||
|
func durationPtr(d time.Duration) *time.Duration { return &d }
|
||||||
|
|
||||||
var _ gomock.Matcher = (*regexMatcher)(nil)
|
var _ gomock.Matcher = (*regexMatcher)(nil)
|
||||||
|
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ package pprof
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"io"
|
"io/ioutil"
|
||||||
"net/http"
|
"net/http"
|
||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
@@ -29,7 +29,7 @@ func Test_Server(t *testing.T) {
|
|||||||
HTTPServer: httpserver.Settings{
|
HTTPServer: httpserver.Settings{
|
||||||
Address: address,
|
Address: address,
|
||||||
Logger: logger,
|
Logger: logger,
|
||||||
ShutdownTimeout: httpServerShutdownTimeout,
|
ShutdownTimeout: durationPtr(httpServerShutdownTimeout),
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -96,7 +96,7 @@ func Test_Server(t *testing.T) {
|
|||||||
assert.Equalf(t, http.StatusOK, httpResult.response.StatusCode,
|
assert.Equalf(t, http.StatusOK, httpResult.response.StatusCode,
|
||||||
"unexpected status code for URL %s: %s", httpResult.url, http.StatusText(httpResult.response.StatusCode))
|
"unexpected status code for URL %s: %s", httpResult.url, http.StatusText(httpResult.response.StatusCode))
|
||||||
|
|
||||||
b, err := io.ReadAll(httpResult.response.Body)
|
b, err := ioutil.ReadAll(httpResult.response.Body)
|
||||||
require.NoErrorf(t, err, "unexpected error for URL %s: %s", httpResult.url, err)
|
require.NoErrorf(t, err, "unexpected error for URL %s: %s", httpResult.url, err)
|
||||||
assert.NotEmptyf(t, b, "response body is empty for URL %s", httpResult.url)
|
assert.NotEmptyf(t, b, "response body is empty for URL %s", httpResult.url)
|
||||||
|
|
||||||
|
|||||||
@@ -2,7 +2,6 @@ package pprof
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"errors"
|
"errors"
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/qdm12/gluetun/internal/configuration/settings/helpers"
|
"github.com/qdm12/gluetun/internal/configuration/settings/helpers"
|
||||||
"github.com/qdm12/gluetun/internal/httpserver"
|
"github.com/qdm12/gluetun/internal/httpserver"
|
||||||
@@ -28,8 +27,6 @@ type Settings struct {
|
|||||||
func (s *Settings) SetDefaults() {
|
func (s *Settings) SetDefaults() {
|
||||||
s.Enabled = helpers.DefaultBool(s.Enabled, false)
|
s.Enabled = helpers.DefaultBool(s.Enabled, false)
|
||||||
s.HTTPServer.Address = helpers.DefaultString(s.HTTPServer.Address, "localhost:6060")
|
s.HTTPServer.Address = helpers.DefaultString(s.HTTPServer.Address, "localhost:6060")
|
||||||
const defaultReadTimeout = 5 * time.Minute // for CPU profiling
|
|
||||||
s.HTTPServer.ReadTimeout = helpers.DefaultDuration(s.HTTPServer.ReadTimeout, defaultReadTimeout)
|
|
||||||
s.HTTPServer.SetDefaults()
|
s.HTTPServer.SetDefaults()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -21,10 +21,8 @@ func Test_Settings_SetDefaults(t *testing.T) {
|
|||||||
expected: Settings{
|
expected: Settings{
|
||||||
Enabled: boolPtr(false),
|
Enabled: boolPtr(false),
|
||||||
HTTPServer: httpserver.Settings{
|
HTTPServer: httpserver.Settings{
|
||||||
Address: "localhost:6060",
|
Address: "localhost:6060",
|
||||||
ReadHeaderTimeout: 3 * time.Second,
|
ShutdownTimeout: durationPtr(3 * time.Second),
|
||||||
ReadTimeout: 5 * time.Minute,
|
|
||||||
ShutdownTimeout: 3 * time.Second,
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@@ -34,10 +32,8 @@ func Test_Settings_SetDefaults(t *testing.T) {
|
|||||||
BlockProfileRate: 1,
|
BlockProfileRate: 1,
|
||||||
MutexProfileRate: 1,
|
MutexProfileRate: 1,
|
||||||
HTTPServer: httpserver.Settings{
|
HTTPServer: httpserver.Settings{
|
||||||
Address: ":6061",
|
Address: ":6061",
|
||||||
ReadHeaderTimeout: time.Second,
|
ShutdownTimeout: durationPtr(time.Second),
|
||||||
ReadTimeout: time.Second,
|
|
||||||
ShutdownTimeout: time.Second,
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
expected: Settings{
|
expected: Settings{
|
||||||
@@ -45,10 +41,8 @@ func Test_Settings_SetDefaults(t *testing.T) {
|
|||||||
BlockProfileRate: 1,
|
BlockProfileRate: 1,
|
||||||
MutexProfileRate: 1,
|
MutexProfileRate: 1,
|
||||||
HTTPServer: httpserver.Settings{
|
HTTPServer: httpserver.Settings{
|
||||||
Address: ":6061",
|
Address: ":6061",
|
||||||
ReadHeaderTimeout: time.Second,
|
ShutdownTimeout: durationPtr(time.Second),
|
||||||
ReadTimeout: time.Second,
|
|
||||||
ShutdownTimeout: time.Second,
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@@ -81,7 +75,7 @@ func Test_Settings_Copy(t *testing.T) {
|
|||||||
MutexProfileRate: 1,
|
MutexProfileRate: 1,
|
||||||
HTTPServer: httpserver.Settings{
|
HTTPServer: httpserver.Settings{
|
||||||
Address: ":6061",
|
Address: ":6061",
|
||||||
ShutdownTimeout: time.Second,
|
ShutdownTimeout: durationPtr(time.Second),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
expected: Settings{
|
expected: Settings{
|
||||||
@@ -90,7 +84,7 @@ func Test_Settings_Copy(t *testing.T) {
|
|||||||
MutexProfileRate: 1,
|
MutexProfileRate: 1,
|
||||||
HTTPServer: httpserver.Settings{
|
HTTPServer: httpserver.Settings{
|
||||||
Address: ":6061",
|
Address: ":6061",
|
||||||
ShutdownTimeout: time.Second,
|
ShutdownTimeout: durationPtr(time.Second),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@@ -284,12 +278,10 @@ func Test_Settings_Validate(t *testing.T) {
|
|||||||
"valid settings": {
|
"valid settings": {
|
||||||
settings: Settings{
|
settings: Settings{
|
||||||
HTTPServer: httpserver.Settings{
|
HTTPServer: httpserver.Settings{
|
||||||
Address: ":8000",
|
Address: ":8000",
|
||||||
Handler: http.NewServeMux(),
|
Handler: http.NewServeMux(),
|
||||||
Logger: &MockLogger{},
|
Logger: &MockLogger{},
|
||||||
ReadHeaderTimeout: time.Second,
|
ShutdownTimeout: durationPtr(time.Second),
|
||||||
ReadTimeout: time.Second,
|
|
||||||
ShutdownTimeout: time.Second,
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@@ -329,7 +321,7 @@ func Test_Settings_String(t *testing.T) {
|
|||||||
MutexProfileRate: 1,
|
MutexProfileRate: 1,
|
||||||
HTTPServer: httpserver.Settings{
|
HTTPServer: httpserver.Settings{
|
||||||
Address: ":8000",
|
Address: ":8000",
|
||||||
ShutdownTimeout: time.Second,
|
ShutdownTimeout: durationPtr(time.Second),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
s: `Pprof settings:
|
s: `Pprof settings:
|
||||||
@@ -337,8 +329,6 @@ func Test_Settings_String(t *testing.T) {
|
|||||||
├── Mutex profile rate: 1
|
├── Mutex profile rate: 1
|
||||||
└── HTTP server settings:
|
└── HTTP server settings:
|
||||||
├── Listening address: :8000
|
├── Listening address: :8000
|
||||||
├── Read header timeout: 0s
|
|
||||||
├── Read timeout: 0s
|
|
||||||
└── Shutdown timeout: 1s`,
|
└── Shutdown timeout: 1s`,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
// Code generated by MockGen. DO NOT EDIT.
|
// Code generated by MockGen. DO NOT EDIT.
|
||||||
// Source: github.com/qdm12/gluetun/internal/provider/common (interfaces: ParallelResolver,Storage,Unzipper,Warner)
|
// Source: github.com/qdm12/gluetun/internal/provider/common (interfaces: ParallelResolver,Storage,Unzipper)
|
||||||
|
|
||||||
// Package common is a generated GoMock package.
|
// Package common is a generated GoMock package.
|
||||||
package common
|
package common
|
||||||
@@ -144,38 +144,3 @@ func (mr *MockUnzipperMockRecorder) FetchAndExtract(arg0, arg1 interface{}) *gom
|
|||||||
mr.mock.ctrl.T.Helper()
|
mr.mock.ctrl.T.Helper()
|
||||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "FetchAndExtract", reflect.TypeOf((*MockUnzipper)(nil).FetchAndExtract), arg0, arg1)
|
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "FetchAndExtract", reflect.TypeOf((*MockUnzipper)(nil).FetchAndExtract), arg0, arg1)
|
||||||
}
|
}
|
||||||
|
|
||||||
// MockWarner is a mock of Warner interface.
|
|
||||||
type MockWarner struct {
|
|
||||||
ctrl *gomock.Controller
|
|
||||||
recorder *MockWarnerMockRecorder
|
|
||||||
}
|
|
||||||
|
|
||||||
// MockWarnerMockRecorder is the mock recorder for MockWarner.
|
|
||||||
type MockWarnerMockRecorder struct {
|
|
||||||
mock *MockWarner
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewMockWarner creates a new mock instance.
|
|
||||||
func NewMockWarner(ctrl *gomock.Controller) *MockWarner {
|
|
||||||
mock := &MockWarner{ctrl: ctrl}
|
|
||||||
mock.recorder = &MockWarnerMockRecorder{mock}
|
|
||||||
return mock
|
|
||||||
}
|
|
||||||
|
|
||||||
// EXPECT returns an object that allows the caller to indicate expected use.
|
|
||||||
func (m *MockWarner) EXPECT() *MockWarnerMockRecorder {
|
|
||||||
return m.recorder
|
|
||||||
}
|
|
||||||
|
|
||||||
// Warn mocks base method.
|
|
||||||
func (m *MockWarner) Warn(arg0 string) {
|
|
||||||
m.ctrl.T.Helper()
|
|
||||||
m.ctrl.Call(m, "Warn", arg0)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Warn indicates an expected call of Warn.
|
|
||||||
func (mr *MockWarnerMockRecorder) Warn(arg0 interface{}) *gomock.Call {
|
|
||||||
mr.mock.ctrl.T.Helper()
|
|
||||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Warn", reflect.TypeOf((*MockWarner)(nil).Warn), arg0)
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -2,4 +2,4 @@ package common
|
|||||||
|
|
||||||
// Exceptionally, these mocks are exported since they are used by all
|
// Exceptionally, these mocks are exported since they are used by all
|
||||||
// provider subpackages tests, and it reduces test code duplication a lot.
|
// provider subpackages tests, and it reduces test code duplication a lot.
|
||||||
//go:generate mockgen -destination=mocks.go -package $GOPACKAGE . ParallelResolver,Storage,Unzipper,Warner
|
//go:generate mockgen -destination=mocks.go -package $GOPACKAGE . ParallelResolver,Storage,Unzipper
|
||||||
|
|||||||
@@ -74,7 +74,7 @@ func modifyConfig(lines []string, connection models.Connection,
|
|||||||
modified = append(modified, "pull-filter ignore \"auth-token\"") // prevent auth failed loop
|
modified = append(modified, "pull-filter ignore \"auth-token\"") // prevent auth failed loop
|
||||||
modified = append(modified, "auth-retry nointeract")
|
modified = append(modified, "auth-retry nointeract")
|
||||||
modified = append(modified, "suppress-timestamps")
|
modified = append(modified, "suppress-timestamps")
|
||||||
if *settings.User != "" {
|
if settings.User != "" {
|
||||||
modified = append(modified, "auth-user-pass "+openvpn.AuthConf)
|
modified = append(modified, "auth-user-pass "+openvpn.AuthConf)
|
||||||
}
|
}
|
||||||
modified = append(modified, "verb "+strconv.Itoa(*settings.Verbosity))
|
modified = append(modified, "verb "+strconv.Itoa(*settings.Verbosity))
|
||||||
|
|||||||
@@ -36,7 +36,7 @@ func Test_modifyConfig(t *testing.T) {
|
|||||||
"auth bla",
|
"auth bla",
|
||||||
},
|
},
|
||||||
settings: settings.OpenVPN{
|
settings: settings.OpenVPN{
|
||||||
User: stringPtr("user"),
|
User: "user",
|
||||||
Ciphers: []string{"cipher"},
|
Ciphers: []string{"cipher"},
|
||||||
Auth: stringPtr("auth"),
|
Auth: stringPtr("auth"),
|
||||||
MSSFix: uint16Ptr(1000),
|
MSSFix: uint16Ptr(1000),
|
||||||
|
|||||||
@@ -33,11 +33,5 @@ func getHostToURL(ctx context.Context, client *http.Client, protocol string) (
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
const failEarly = true
|
return openvpn.FetchMultiFiles(ctx, client, urls)
|
||||||
hostToURL, errors := openvpn.FetchMultiFiles(ctx, client, urls, failEarly)
|
|
||||||
if len(errors) > 0 {
|
|
||||||
return nil, errors[0]
|
|
||||||
}
|
|
||||||
|
|
||||||
return hostToURL, nil
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,3 @@
|
|||||||
|
package updater
|
||||||
|
|
||||||
|
//go:generate mockgen -destination=mocks_test.go -package $GOPACKAGE . Warner
|
||||||
46
internal/provider/ipvanish/updater/mocks_test.go
Normal file
46
internal/provider/ipvanish/updater/mocks_test.go
Normal file
@@ -0,0 +1,46 @@
|
|||||||
|
// Code generated by MockGen. DO NOT EDIT.
|
||||||
|
// Source: github.com/qdm12/gluetun/internal/provider/ipvanish/updater (interfaces: Warner)
|
||||||
|
|
||||||
|
// Package ipvanish is a generated GoMock package.
|
||||||
|
package updater
|
||||||
|
|
||||||
|
import (
|
||||||
|
reflect "reflect"
|
||||||
|
|
||||||
|
gomock "github.com/golang/mock/gomock"
|
||||||
|
)
|
||||||
|
|
||||||
|
// MockWarner is a mock of Warner interface.
|
||||||
|
type MockWarner struct {
|
||||||
|
ctrl *gomock.Controller
|
||||||
|
recorder *MockWarnerMockRecorder
|
||||||
|
}
|
||||||
|
|
||||||
|
// MockWarnerMockRecorder is the mock recorder for MockWarner.
|
||||||
|
type MockWarnerMockRecorder struct {
|
||||||
|
mock *MockWarner
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewMockWarner creates a new mock instance.
|
||||||
|
func NewMockWarner(ctrl *gomock.Controller) *MockWarner {
|
||||||
|
mock := &MockWarner{ctrl: ctrl}
|
||||||
|
mock.recorder = &MockWarnerMockRecorder{mock}
|
||||||
|
return mock
|
||||||
|
}
|
||||||
|
|
||||||
|
// EXPECT returns an object that allows the caller to indicate expected use.
|
||||||
|
func (m *MockWarner) EXPECT() *MockWarnerMockRecorder {
|
||||||
|
return m.recorder
|
||||||
|
}
|
||||||
|
|
||||||
|
// Warn mocks base method.
|
||||||
|
func (m *MockWarner) Warn(arg0 string) {
|
||||||
|
m.ctrl.T.Helper()
|
||||||
|
m.ctrl.Call(m, "Warn", arg0)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Warn indicates an expected call of Warn.
|
||||||
|
func (mr *MockWarnerMockRecorder) Warn(arg0 interface{}) *gomock.Call {
|
||||||
|
mr.mock.ctrl.T.Helper()
|
||||||
|
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Warn", reflect.TypeOf((*MockWarner)(nil).Warn), arg0)
|
||||||
|
}
|
||||||
@@ -60,7 +60,7 @@ func Test_Updater_GetServers(t *testing.T) {
|
|||||||
"invalid proto": {
|
"invalid proto": {
|
||||||
minServers: 1,
|
minServers: 1,
|
||||||
warnerBuilder: func(ctrl *gomock.Controller) common.Warner {
|
warnerBuilder: func(ctrl *gomock.Controller) common.Warner {
|
||||||
warner := common.NewMockWarner(ctrl)
|
warner := NewMockWarner(ctrl)
|
||||||
warner.EXPECT().Warn("unknown protocol: invalid in badproto.ovpn")
|
warner.EXPECT().Warn("unknown protocol: invalid in badproto.ovpn")
|
||||||
return warner
|
return warner
|
||||||
},
|
},
|
||||||
@@ -70,7 +70,7 @@ func Test_Updater_GetServers(t *testing.T) {
|
|||||||
"no host": {
|
"no host": {
|
||||||
minServers: 1,
|
minServers: 1,
|
||||||
warnerBuilder: func(ctrl *gomock.Controller) common.Warner {
|
warnerBuilder: func(ctrl *gomock.Controller) common.Warner {
|
||||||
warner := common.NewMockWarner(ctrl)
|
warner := NewMockWarner(ctrl)
|
||||||
warner.EXPECT().Warn("remote host not found in nohost.ovpn")
|
warner.EXPECT().Warn("remote host not found in nohost.ovpn")
|
||||||
return warner
|
return warner
|
||||||
},
|
},
|
||||||
@@ -80,7 +80,7 @@ func Test_Updater_GetServers(t *testing.T) {
|
|||||||
"multiple hosts": {
|
"multiple hosts": {
|
||||||
minServers: 1,
|
minServers: 1,
|
||||||
warnerBuilder: func(ctrl *gomock.Controller) common.Warner {
|
warnerBuilder: func(ctrl *gomock.Controller) common.Warner {
|
||||||
warner := common.NewMockWarner(ctrl)
|
warner := NewMockWarner(ctrl)
|
||||||
warner.EXPECT().Warn("only using the first host \"hosta\" and discarding 1 other hosts")
|
warner.EXPECT().Warn("only using the first host \"hosta\" and discarding 1 other hosts")
|
||||||
return warner
|
return warner
|
||||||
},
|
},
|
||||||
@@ -103,7 +103,7 @@ func Test_Updater_GetServers(t *testing.T) {
|
|||||||
},
|
},
|
||||||
"resolve error": {
|
"resolve error": {
|
||||||
warnerBuilder: func(ctrl *gomock.Controller) common.Warner {
|
warnerBuilder: func(ctrl *gomock.Controller) common.Warner {
|
||||||
warner := common.NewMockWarner(ctrl)
|
warner := NewMockWarner(ctrl)
|
||||||
warner.EXPECT().Warn("resolve warning")
|
warner.EXPECT().Warn("resolve warning")
|
||||||
return warner
|
return warner
|
||||||
},
|
},
|
||||||
@@ -129,7 +129,7 @@ func Test_Updater_GetServers(t *testing.T) {
|
|||||||
"filename parsing error": {
|
"filename parsing error": {
|
||||||
minServers: 1,
|
minServers: 1,
|
||||||
warnerBuilder: func(ctrl *gomock.Controller) common.Warner {
|
warnerBuilder: func(ctrl *gomock.Controller) common.Warner {
|
||||||
warner := common.NewMockWarner(ctrl)
|
warner := NewMockWarner(ctrl)
|
||||||
warner.EXPECT().Warn("country code is unknown: unknown in ipvanish-unknown-City-A-hosta.ovpn")
|
warner.EXPECT().Warn("country code is unknown: unknown in ipvanish-unknown-City-A-hosta.ovpn")
|
||||||
return warner
|
return warner
|
||||||
},
|
},
|
||||||
@@ -141,7 +141,7 @@ func Test_Updater_GetServers(t *testing.T) {
|
|||||||
"success": {
|
"success": {
|
||||||
minServers: 1,
|
minServers: 1,
|
||||||
warnerBuilder: func(ctrl *gomock.Controller) common.Warner {
|
warnerBuilder: func(ctrl *gomock.Controller) common.Warner {
|
||||||
warner := common.NewMockWarner(ctrl)
|
warner := NewMockWarner(ctrl)
|
||||||
warner.EXPECT().Warn("resolve warning")
|
warner.EXPECT().Warn("resolve warning")
|
||||||
return warner
|
return warner
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import (
|
|||||||
"context"
|
"context"
|
||||||
"errors"
|
"errors"
|
||||||
"io"
|
"io"
|
||||||
|
"io/ioutil"
|
||||||
"net/http"
|
"net/http"
|
||||||
"strings"
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
@@ -31,11 +32,11 @@ func Test_fetchAPI(t *testing.T) {
|
|||||||
},
|
},
|
||||||
"no server": {
|
"no server": {
|
||||||
responseStatus: http.StatusOK,
|
responseStatus: http.StatusOK,
|
||||||
responseBody: io.NopCloser(strings.NewReader(`{}`)),
|
responseBody: ioutil.NopCloser(strings.NewReader(`{}`)),
|
||||||
},
|
},
|
||||||
"success": {
|
"success": {
|
||||||
responseStatus: http.StatusOK,
|
responseStatus: http.StatusOK,
|
||||||
responseBody: io.NopCloser(strings.NewReader(`{"servers":[
|
responseBody: ioutil.NopCloser(strings.NewReader(`{"servers":[
|
||||||
{"country":"Country1","city":"City A","isp":"xyz","is_active":true,"hostnames":{"openvpn":"hosta"}},
|
{"country":"Country1","city":"City A","isp":"xyz","is_active":true,"hostnames":{"openvpn":"hosta"}},
|
||||||
{"country":"Country2","city":"City B","isp":"abc","is_active":false,"hostnames":{"openvpn":"hostb"}}
|
{"country":"Country2","city":"City B","isp":"abc","is_active":false,"hostnames":{"openvpn":"hostb"}}
|
||||||
]}`)),
|
]}`)),
|
||||||
|
|||||||
3
internal/provider/ivpn/updater/mocks_generate_test.go
Normal file
3
internal/provider/ivpn/updater/mocks_generate_test.go
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
package updater
|
||||||
|
|
||||||
|
//go:generate mockgen -destination=mocks_test.go -package $GOPACKAGE . Warner
|
||||||
46
internal/provider/ivpn/updater/mocks_test.go
Normal file
46
internal/provider/ivpn/updater/mocks_test.go
Normal file
@@ -0,0 +1,46 @@
|
|||||||
|
// Code generated by MockGen. DO NOT EDIT.
|
||||||
|
// Source: github.com/qdm12/gluetun/internal/provider/ivpn/updater (interfaces: Warner)
|
||||||
|
|
||||||
|
// Package ivpn is a generated GoMock package.
|
||||||
|
package updater
|
||||||
|
|
||||||
|
import (
|
||||||
|
reflect "reflect"
|
||||||
|
|
||||||
|
gomock "github.com/golang/mock/gomock"
|
||||||
|
)
|
||||||
|
|
||||||
|
// MockWarner is a mock of Warner interface.
|
||||||
|
type MockWarner struct {
|
||||||
|
ctrl *gomock.Controller
|
||||||
|
recorder *MockWarnerMockRecorder
|
||||||
|
}
|
||||||
|
|
||||||
|
// MockWarnerMockRecorder is the mock recorder for MockWarner.
|
||||||
|
type MockWarnerMockRecorder struct {
|
||||||
|
mock *MockWarner
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewMockWarner creates a new mock instance.
|
||||||
|
func NewMockWarner(ctrl *gomock.Controller) *MockWarner {
|
||||||
|
mock := &MockWarner{ctrl: ctrl}
|
||||||
|
mock.recorder = &MockWarnerMockRecorder{mock}
|
||||||
|
return mock
|
||||||
|
}
|
||||||
|
|
||||||
|
// EXPECT returns an object that allows the caller to indicate expected use.
|
||||||
|
func (m *MockWarner) EXPECT() *MockWarnerMockRecorder {
|
||||||
|
return m.recorder
|
||||||
|
}
|
||||||
|
|
||||||
|
// Warn mocks base method.
|
||||||
|
func (m *MockWarner) Warn(arg0 string) {
|
||||||
|
m.ctrl.T.Helper()
|
||||||
|
m.ctrl.Call(m, "Warn", arg0)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Warn indicates an expected call of Warn.
|
||||||
|
func (mr *MockWarnerMockRecorder) Warn(arg0 interface{}) *gomock.Call {
|
||||||
|
mr.mock.ctrl.T.Helper()
|
||||||
|
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Warn", reflect.TypeOf((*MockWarner)(nil).Warn), arg0)
|
||||||
|
}
|
||||||
@@ -3,7 +3,7 @@ package updater
|
|||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"errors"
|
"errors"
|
||||||
"io"
|
"io/ioutil"
|
||||||
"net"
|
"net"
|
||||||
"net/http"
|
"net/http"
|
||||||
"strings"
|
"strings"
|
||||||
@@ -51,7 +51,7 @@ func Test_Updater_GetServers(t *testing.T) {
|
|||||||
},
|
},
|
||||||
"resolve error": {
|
"resolve error": {
|
||||||
warnerBuilder: func(ctrl *gomock.Controller) common.Warner {
|
warnerBuilder: func(ctrl *gomock.Controller) common.Warner {
|
||||||
warner := common.NewMockWarner(ctrl)
|
warner := NewMockWarner(ctrl)
|
||||||
warner.EXPECT().Warn("resolve warning")
|
warner.EXPECT().Warn("resolve warning")
|
||||||
return warner
|
return warner
|
||||||
},
|
},
|
||||||
@@ -87,7 +87,7 @@ func Test_Updater_GetServers(t *testing.T) {
|
|||||||
"success": {
|
"success": {
|
||||||
minServers: 1,
|
minServers: 1,
|
||||||
warnerBuilder: func(ctrl *gomock.Controller) common.Warner {
|
warnerBuilder: func(ctrl *gomock.Controller) common.Warner {
|
||||||
warner := common.NewMockWarner(ctrl)
|
warner := NewMockWarner(ctrl)
|
||||||
warner.EXPECT().Warn("resolve warning")
|
warner.EXPECT().Warn("resolve warning")
|
||||||
return warner
|
return warner
|
||||||
},
|
},
|
||||||
@@ -145,7 +145,7 @@ func Test_Updater_GetServers(t *testing.T) {
|
|||||||
return &http.Response{
|
return &http.Response{
|
||||||
StatusCode: testCase.responseStatus,
|
StatusCode: testCase.responseStatus,
|
||||||
Status: http.StatusText(testCase.responseStatus),
|
Status: http.StatusText(testCase.responseStatus),
|
||||||
Body: io.NopCloser(strings.NewReader(testCase.responseBody)),
|
Body: ioutil.NopCloser(strings.NewReader(testCase.responseBody)),
|
||||||
}, nil
|
}, nil
|
||||||
}),
|
}),
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -25,10 +25,8 @@ import (
|
|||||||
"github.com/qdm12/gluetun/internal/provider/privatevpn"
|
"github.com/qdm12/gluetun/internal/provider/privatevpn"
|
||||||
"github.com/qdm12/gluetun/internal/provider/protonvpn"
|
"github.com/qdm12/gluetun/internal/provider/protonvpn"
|
||||||
"github.com/qdm12/gluetun/internal/provider/purevpn"
|
"github.com/qdm12/gluetun/internal/provider/purevpn"
|
||||||
"github.com/qdm12/gluetun/internal/provider/slickvpn"
|
|
||||||
"github.com/qdm12/gluetun/internal/provider/surfshark"
|
"github.com/qdm12/gluetun/internal/provider/surfshark"
|
||||||
"github.com/qdm12/gluetun/internal/provider/torguard"
|
"github.com/qdm12/gluetun/internal/provider/torguard"
|
||||||
"github.com/qdm12/gluetun/internal/provider/vpnsecure"
|
|
||||||
"github.com/qdm12/gluetun/internal/provider/vpnunlimited"
|
"github.com/qdm12/gluetun/internal/provider/vpnunlimited"
|
||||||
"github.com/qdm12/gluetun/internal/provider/vyprvpn"
|
"github.com/qdm12/gluetun/internal/provider/vyprvpn"
|
||||||
"github.com/qdm12/gluetun/internal/provider/wevpn"
|
"github.com/qdm12/gluetun/internal/provider/wevpn"
|
||||||
@@ -73,10 +71,8 @@ func NewProviders(storage Storage, timeNow func() time.Time,
|
|||||||
providers.Privatevpn: privatevpn.New(storage, randSource, unzipper, updaterWarner, parallelResolver),
|
providers.Privatevpn: privatevpn.New(storage, randSource, unzipper, updaterWarner, parallelResolver),
|
||||||
providers.Protonvpn: protonvpn.New(storage, randSource, client, updaterWarner, parallelResolver),
|
providers.Protonvpn: protonvpn.New(storage, randSource, client, updaterWarner, parallelResolver),
|
||||||
providers.Purevpn: purevpn.New(storage, randSource, ipFetcher, unzipper, updaterWarner, parallelResolver),
|
providers.Purevpn: purevpn.New(storage, randSource, ipFetcher, unzipper, updaterWarner, parallelResolver),
|
||||||
providers.SlickVPN: slickvpn.New(storage, randSource, client, updaterWarner, parallelResolver),
|
|
||||||
providers.Surfshark: surfshark.New(storage, randSource, client, unzipper, updaterWarner, parallelResolver),
|
providers.Surfshark: surfshark.New(storage, randSource, client, unzipper, updaterWarner, parallelResolver),
|
||||||
providers.Torguard: torguard.New(storage, randSource, unzipper, updaterWarner, parallelResolver),
|
providers.Torguard: torguard.New(storage, randSource, unzipper, updaterWarner, parallelResolver),
|
||||||
providers.VPNSecure: vpnsecure.New(storage, randSource, client, updaterWarner, parallelResolver),
|
|
||||||
providers.VPNUnlimited: vpnunlimited.New(storage, randSource, unzipper, updaterWarner, parallelResolver),
|
providers.VPNUnlimited: vpnunlimited.New(storage, randSource, unzipper, updaterWarner, parallelResolver),
|
||||||
providers.Vyprvpn: vyprvpn.New(storage, randSource, unzipper, updaterWarner, parallelResolver),
|
providers.Vyprvpn: vyprvpn.New(storage, randSource, unzipper, updaterWarner, parallelResolver),
|
||||||
providers.Wevpn: wevpn.New(storage, randSource, updaterWarner, parallelResolver),
|
providers.Wevpn: wevpn.New(storage, randSource, updaterWarner, parallelResolver),
|
||||||
|
|||||||
@@ -1,13 +0,0 @@
|
|||||||
package slickvpn
|
|
||||||
|
|
||||||
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) (
|
|
||||||
connection models.Connection, err error) {
|
|
||||||
defaults := utils.NewConnectionDefaults(0, 443, 0) //nolint:gomnd
|
|
||||||
return utils.GetConnection(p.Name(), p.storage, selection, defaults, p.randSource)
|
|
||||||
}
|
|
||||||
@@ -1,30 +0,0 @@
|
|||||||
package slickvpn
|
|
||||||
|
|
||||||
import (
|
|
||||||
"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) (lines []string) {
|
|
||||||
const pingSeconds = 10
|
|
||||||
const bufSize = 393216
|
|
||||||
providerSettings := utils.OpenVPNProviderSettings{
|
|
||||||
RemoteCertTLS: true,
|
|
||||||
AuthUserPass: true,
|
|
||||||
Ciphers: []string{
|
|
||||||
openvpn.AES256cbc,
|
|
||||||
},
|
|
||||||
Ping: pingSeconds,
|
|
||||||
SndBuf: bufSize,
|
|
||||||
RcvBuf: bufSize,
|
|
||||||
// Certificate found from https://www.slickvpn.com/tutorials/using-openvpn-configuration-files/
|
|
||||||
CA: "MIIESDCCAzCgAwIBAgIJAKHK5bbBPSU2MA0GCSqGSIb3DQEBBQUAMHUxCzAJBgNVBAYTAlVTMQwwCgYDVQQIEwNWUE4xDDAKBgNVBAcTA1ZQTjEMMAoGA1UEChMDVlBOMQwwCgYDVQQLEwNWUE4xDDAKBgNVBAMTA1ZQTjEMMAoGA1UEKRMDVlBOMRIwEAYJKoZIhvcNAQkBFgNWUE4wHhcNMjIwMjE0MjEzNDQwWhcNMzIwMjEyMjEzNDQwWjB1MQswCQYDVQQGEwJVUzEMMAoGA1UECBMDVlBOMQwwCgYDVQQHEwNWUE4xDDAKBgNVBAoTA1ZQTjEMMAoGA1UECxMDVlBOMQwwCgYDVQQDEwNWUE4xDDAKBgNVBCkTA1ZQTjESMBAGCSqGSIb3DQEJARYDVlBOMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAwUl1XkfGo3c1uFsvgbO3C3yvu0+cHs9IUSsju5U9cPNCo53mqRHU/qntCC+ldIDKN+dNWn7eURIDszy+flutkgucs0qgETy5fzpXnVMtiKmMiOYWiJDor7j7QivRaxoT/O2fyjxvVCL8gMa60ekWSGBT6isYY8t8BnwTPVP0KvDm36wdaqLmubhf2XGvka/hhNx0SXMmz2x3OJ8BcoypZVLLk/+Qm6DJh1KxyDi4kI+jBC41QuaKKDNwb0kth1304eqZoUeCXtGkzl91y76ODAfdqzXf9WYJdgkXpOm53Cg7FtB42AqPRqHJVwYxDnQyrFwy4a3CWqFJnKtxJM/WlwIDAQABo4HaMIHXMB0GA1UdDgQWBBRSzxAtISfbSRPr0fmhwNZc8kOeKzCBpwYDVR0jBIGfMIGcgBRSzxAtISfbSRPr0fmhwNZc8kOeK6F5pHcwdTELMAkGA1UEBhMCVVMxDDAKBgNVBAgTA1ZQTjEMMAoGA1UEBxMDVlBOMQwwCgYDVQQKEwNWUE4xDDAKBgNVBAsTA1ZQTjEMMAoGA1UEAxMDVlBOMQwwCgYDVQQpEwNWUE4xEjAQBgkqhkiG9w0BCQEWA1ZQToIJAKHK5bbBPSU2MAwGA1UdEwQFMAMBAf8wDQYJKoZIhvcNAQEFBQADggEBAGuKFW765F3D5wax5IFSQbEtr+rVHgjR8jiYTzxOCmbLaU4oj6phOhfQJiTTADQYgCIC/DN0HsAEEqrKkwEn8KdAoNiAWfqCV/eqnK83y7yRDGx6/zfsch+PAzKZouMJLrvR9RYbHq7m3adZv84YLge7FE1JqFk1j6rSa4dUUnGQPrQgr9Sasnz8O8KK45XH6fqKrsd4p485n+BXPDzWVsHl4M5dqQV7qUZTazbzzh4NyP5/RQ6Oh5jqMN7po4qiqWv1pu0EKDxUG6gcECc2cTQwHhIOPeCGdHS7uuI2FlLnHaCUFBgi8zTsZxaeaPuPch5M7Zxbdg0GBhS2SmNi+io=", //nolint:lll
|
|
||||||
ExtraLines: []string{
|
|
||||||
"redirect-gateway",
|
|
||||||
},
|
|
||||||
}
|
|
||||||
return utils.OpenVPNConfig(providerSettings, connection, settings)
|
|
||||||
}
|
|
||||||
@@ -1,33 +0,0 @@
|
|||||||
package slickvpn
|
|
||||||
|
|
||||||
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/slickvpn/updater"
|
|
||||||
"github.com/qdm12/gluetun/internal/provider/utils"
|
|
||||||
)
|
|
||||||
|
|
||||||
type Provider struct {
|
|
||||||
storage common.Storage
|
|
||||||
randSource rand.Source
|
|
||||||
utils.NoPortForwarder
|
|
||||||
common.Fetcher
|
|
||||||
}
|
|
||||||
|
|
||||||
func New(storage common.Storage, randSource rand.Source,
|
|
||||||
client *http.Client, updaterWarner common.Warner,
|
|
||||||
parallelResolver common.ParallelResolver) *Provider {
|
|
||||||
return &Provider{
|
|
||||||
storage: storage,
|
|
||||||
randSource: randSource,
|
|
||||||
NoPortForwarder: utils.NewNoPortForwarding(providers.SlickVPN),
|
|
||||||
Fetcher: updater.New(client, updaterWarner, parallelResolver),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *Provider) Name() string {
|
|
||||||
return providers.SlickVPN
|
|
||||||
}
|
|
||||||
@@ -1,26 +0,0 @@
|
|||||||
package updater
|
|
||||||
|
|
||||||
import (
|
|
||||||
"os"
|
|
||||||
"strings"
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
"github.com/stretchr/testify/require"
|
|
||||||
"golang.org/x/net/html"
|
|
||||||
)
|
|
||||||
|
|
||||||
func parseTestHTML(t *testing.T, htmlString string) *html.Node {
|
|
||||||
t.Helper()
|
|
||||||
rootNode, err := html.Parse(strings.NewReader(htmlString))
|
|
||||||
require.NoError(t, err)
|
|
||||||
return rootNode
|
|
||||||
}
|
|
||||||
|
|
||||||
func parseTestDataIndexHTML(t *testing.T) *html.Node {
|
|
||||||
t.Helper()
|
|
||||||
|
|
||||||
data, err := os.ReadFile("testdata/index.html")
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
return parseTestHTML(t, string(data))
|
|
||||||
}
|
|
||||||
@@ -1,28 +0,0 @@
|
|||||||
package updater
|
|
||||||
|
|
||||||
import (
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/qdm12/gluetun/internal/updater/resolver"
|
|
||||||
)
|
|
||||||
|
|
||||||
func parallelResolverSettings(hosts []string) (settings resolver.ParallelSettings) {
|
|
||||||
const (
|
|
||||||
maxFailRatio = 0.1
|
|
||||||
maxDuration = 20 * time.Second
|
|
||||||
betweenDuration = time.Second
|
|
||||||
maxNoNew = 2
|
|
||||||
maxFails = 2
|
|
||||||
)
|
|
||||||
return resolver.ParallelSettings{
|
|
||||||
Hosts: hosts,
|
|
||||||
MaxFailRatio: maxFailRatio,
|
|
||||||
Repeat: resolver.RepeatSettings{
|
|
||||||
MaxDuration: maxDuration,
|
|
||||||
BetweenDuration: betweenDuration,
|
|
||||||
MaxNoNew: maxNoNew,
|
|
||||||
MaxFails: maxFails,
|
|
||||||
SortIPs: true,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,82 +0,0 @@
|
|||||||
package updater
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"fmt"
|
|
||||||
"sort"
|
|
||||||
|
|
||||||
"github.com/qdm12/gluetun/internal/constants/vpn"
|
|
||||||
"github.com/qdm12/gluetun/internal/models"
|
|
||||||
"github.com/qdm12/gluetun/internal/provider/common"
|
|
||||||
"github.com/qdm12/gluetun/internal/updater/openvpn"
|
|
||||||
)
|
|
||||||
|
|
||||||
func (u *Updater) FetchServers(ctx context.Context, minServers int) (
|
|
||||||
servers []models.Server, err error) {
|
|
||||||
hostToData, err := fetchServers(ctx, u.client)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("fetching and parsing website: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
openvpnURLs := make([]string, 0, len(hostToData))
|
|
||||||
for _, data := range hostToData {
|
|
||||||
openvpnURLs = append(openvpnURLs, data.ovpnURL)
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(openvpnURLs) < minServers {
|
|
||||||
return nil, fmt.Errorf("%w: %d and expected at least %d",
|
|
||||||
common.ErrNotEnoughServers, len(openvpnURLs), minServers)
|
|
||||||
}
|
|
||||||
|
|
||||||
const failEarly = false // some URLs from the website are not valid
|
|
||||||
udpHostToURL, errors := openvpn.FetchMultiFiles(ctx, u.client, openvpnURLs, failEarly)
|
|
||||||
for _, err := range errors {
|
|
||||||
u.warner.Warn(fmt.Sprintf("fetching OpenVPN files: %s", err))
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(udpHostToURL) < minServers {
|
|
||||||
return nil, fmt.Errorf("%w: %d and expected at least %d",
|
|
||||||
common.ErrNotEnoughServers, len(udpHostToURL), minServers)
|
|
||||||
}
|
|
||||||
|
|
||||||
hosts := make([]string, 0, len(udpHostToURL))
|
|
||||||
for host := range udpHostToURL {
|
|
||||||
hosts = append(hosts, host)
|
|
||||||
}
|
|
||||||
|
|
||||||
resolveSettings := parallelResolverSettings(hosts)
|
|
||||||
hostToIPs, warnings, err := u.parallelResolver.Resolve(ctx, resolveSettings)
|
|
||||||
for _, warning := range warnings {
|
|
||||||
u.warner.Warn(warning)
|
|
||||||
}
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("resolving hosts: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(hostToIPs) < minServers {
|
|
||||||
return nil, fmt.Errorf("%w: %d and expected at least %d",
|
|
||||||
common.ErrNotEnoughServers, len(hosts), minServers)
|
|
||||||
}
|
|
||||||
|
|
||||||
servers = make([]models.Server, 0, len(hostToIPs))
|
|
||||||
for host, IPs := range hostToIPs {
|
|
||||||
_, udp := udpHostToURL[host]
|
|
||||||
|
|
||||||
serverData := hostToData[host]
|
|
||||||
|
|
||||||
server := models.Server{
|
|
||||||
VPN: vpn.OpenVPN,
|
|
||||||
Region: serverData.region,
|
|
||||||
Country: serverData.country,
|
|
||||||
City: serverData.city,
|
|
||||||
Hostname: host,
|
|
||||||
UDP: udp,
|
|
||||||
IPs: IPs,
|
|
||||||
}
|
|
||||||
servers = append(servers, server)
|
|
||||||
}
|
|
||||||
|
|
||||||
sort.Sort(models.SortableServers(servers))
|
|
||||||
|
|
||||||
return servers, nil
|
|
||||||
}
|
|
||||||
File diff suppressed because one or more lines are too long
@@ -1,22 +0,0 @@
|
|||||||
package updater
|
|
||||||
|
|
||||||
import (
|
|
||||||
"net/http"
|
|
||||||
|
|
||||||
"github.com/qdm12/gluetun/internal/provider/common"
|
|
||||||
)
|
|
||||||
|
|
||||||
type Updater struct {
|
|
||||||
client *http.Client
|
|
||||||
parallelResolver common.ParallelResolver
|
|
||||||
warner common.Warner
|
|
||||||
}
|
|
||||||
|
|
||||||
func New(client *http.Client, warner common.Warner,
|
|
||||||
parallelResolver common.ParallelResolver) *Updater {
|
|
||||||
return &Updater{
|
|
||||||
client: client,
|
|
||||||
parallelResolver: parallelResolver,
|
|
||||||
warner: warner,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,149 +0,0 @@
|
|||||||
package updater
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"errors"
|
|
||||||
"fmt"
|
|
||||||
"net/http"
|
|
||||||
"regexp"
|
|
||||||
|
|
||||||
htmlutils "github.com/qdm12/gluetun/internal/updater/html"
|
|
||||||
"golang.org/x/net/html"
|
|
||||||
)
|
|
||||||
|
|
||||||
func fetchServers(ctx context.Context, client *http.Client) (
|
|
||||||
hostToData map[string]serverData, err error) {
|
|
||||||
const url = "https://www.slickvpn.com/locations/"
|
|
||||||
rootNode, err := htmlutils.Fetch(ctx, client, url)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("fetching HTML code: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
hostToData, err = parseHTML(rootNode)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("parsing HTML: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
return hostToData, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
type serverData struct {
|
|
||||||
ovpnURL string
|
|
||||||
country string
|
|
||||||
region string
|
|
||||||
city string
|
|
||||||
}
|
|
||||||
|
|
||||||
var (
|
|
||||||
ErrLocationTableNotFound = errors.New("HTML location table node not found")
|
|
||||||
ErrTbodyNotFound = errors.New("HTML tbody node not found")
|
|
||||||
ErrExtractOpenVPNURL = errors.New("failed extracting OpenVPN URL")
|
|
||||||
)
|
|
||||||
|
|
||||||
func parseHTML(rootNode *html.Node) (hostToData map[string]serverData, err error) {
|
|
||||||
locationTableNode := htmlutils.BFS(rootNode, matchLocationTable)
|
|
||||||
if locationTableNode == nil {
|
|
||||||
return nil, htmlutils.WrapError(ErrLocationTableNotFound, rootNode)
|
|
||||||
}
|
|
||||||
|
|
||||||
tBodyNode := htmlutils.BFS(locationTableNode, matchTbody)
|
|
||||||
if tBodyNode == nil {
|
|
||||||
return nil, htmlutils.WrapError(ErrTbodyNotFound, rootNode)
|
|
||||||
}
|
|
||||||
|
|
||||||
rowNodes := htmlutils.DirectChildren(tBodyNode, matchTr)
|
|
||||||
hostToData = make(map[string]serverData, len(rowNodes))
|
|
||||||
|
|
||||||
for _, rowNode := range rowNodes {
|
|
||||||
hostname, data, err := parseRowNode(rowNode)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("parsing row node: %w", err)
|
|
||||||
}
|
|
||||||
hostToData[hostname] = data
|
|
||||||
}
|
|
||||||
|
|
||||||
return hostToData, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func parseRowNode(rowNode *html.Node) (hostname string, data serverData, err error) {
|
|
||||||
columnIndex := 0
|
|
||||||
const (
|
|
||||||
columnIndexContinent = 0
|
|
||||||
columnIndexCountry = 1
|
|
||||||
columnIndexCity = 2
|
|
||||||
columnIndexConfig = 3
|
|
||||||
)
|
|
||||||
for cellNode := rowNode.FirstChild; cellNode != nil; cellNode = cellNode.NextSibling {
|
|
||||||
if cellNode.FirstChild == nil {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
switch columnIndex {
|
|
||||||
case columnIndexContinent:
|
|
||||||
data.region = cellNode.FirstChild.Data
|
|
||||||
case columnIndexCountry:
|
|
||||||
data.country = cellNode.FirstChild.Data
|
|
||||||
case columnIndexCity:
|
|
||||||
data.city = cellNode.FirstChild.Data
|
|
||||||
case columnIndexConfig:
|
|
||||||
linkNodes := htmlutils.DirectChildren(cellNode, matchA)
|
|
||||||
for _, linkNode := range linkNodes {
|
|
||||||
if linkNode.FirstChild.Data != "OpenVPN" {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
data.ovpnURL = htmlutils.Attribute(linkNode, "href")
|
|
||||||
if data.ovpnURL == "" {
|
|
||||||
return "", data, htmlutils.WrapError(ErrExtractOpenVPNURL, linkNode)
|
|
||||||
}
|
|
||||||
|
|
||||||
hostname, err = extractHostnameFromURL(data.ovpnURL)
|
|
||||||
if err != nil {
|
|
||||||
return "", data, fmt.Errorf("extracting hostname from url: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
columnIndex++
|
|
||||||
if columnIndex == columnIndexConfig+1 {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return hostname, data, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func matchLocationTable(rootNode *html.Node) (match bool) {
|
|
||||||
return htmlutils.MatchID("location-table")(rootNode)
|
|
||||||
}
|
|
||||||
|
|
||||||
func matchTbody(locationTableNode *html.Node) (match bool) {
|
|
||||||
return htmlutils.MatchData("tbody")(locationTableNode)
|
|
||||||
}
|
|
||||||
|
|
||||||
func matchTr(tbodyNode *html.Node) (match bool) {
|
|
||||||
return htmlutils.MatchData("tr")(tbodyNode)
|
|
||||||
}
|
|
||||||
|
|
||||||
func matchA(cellNode *html.Node) (match bool) {
|
|
||||||
return htmlutils.MatchData("a")(cellNode)
|
|
||||||
}
|
|
||||||
|
|
||||||
var serverNameRegex = regexp.MustCompile(`^.+\/(?P<serverName>.+)\.ovpn$`)
|
|
||||||
|
|
||||||
var (
|
|
||||||
ErrExtractHostnameFromURL = errors.New("cannot extract hostname from url")
|
|
||||||
)
|
|
||||||
|
|
||||||
func extractHostnameFromURL(url string) (hostname string, err error) {
|
|
||||||
matches := serverNameRegex.FindStringSubmatch(url)
|
|
||||||
const minMatches = 2
|
|
||||||
if len(matches) < minMatches {
|
|
||||||
return "", fmt.Errorf("%w: %s has less than 2 matches for %s",
|
|
||||||
ErrExtractHostnameFromURL, url, serverNameRegex)
|
|
||||||
}
|
|
||||||
hostname = matches[1]
|
|
||||||
return hostname, nil
|
|
||||||
}
|
|
||||||
@@ -1,269 +0,0 @@
|
|||||||
package updater
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"io"
|
|
||||||
"net/http"
|
|
||||||
"strings"
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
"github.com/stretchr/testify/assert"
|
|
||||||
"golang.org/x/net/html"
|
|
||||||
)
|
|
||||||
|
|
||||||
type roundTripFunc func(r *http.Request) (*http.Response, error)
|
|
||||||
|
|
||||||
func (f roundTripFunc) RoundTrip(r *http.Request) (*http.Response, error) {
|
|
||||||
return f(r)
|
|
||||||
}
|
|
||||||
|
|
||||||
func Test_fetchServers(t *testing.T) {
|
|
||||||
t.Parallel()
|
|
||||||
|
|
||||||
canceledCtx, cancel := context.WithCancel(context.Background())
|
|
||||||
cancel()
|
|
||||||
|
|
||||||
testCases := map[string]struct {
|
|
||||||
ctx context.Context
|
|
||||||
responseStatus int
|
|
||||||
responseBody io.ReadCloser
|
|
||||||
hostToData map[string]serverData
|
|
||||||
errWrapped error
|
|
||||||
errMessage string
|
|
||||||
}{
|
|
||||||
"context canceled": {
|
|
||||||
ctx: canceledCtx,
|
|
||||||
errWrapped: context.Canceled,
|
|
||||||
errMessage: `fetching HTML code: Get "https://www.slickvpn.com/locations/": context canceled`,
|
|
||||||
},
|
|
||||||
"success": {
|
|
||||||
ctx: context.Background(),
|
|
||||||
responseStatus: http.StatusOK,
|
|
||||||
//nolint:lll
|
|
||||||
responseBody: io.NopCloser(strings.NewReader(`
|
|
||||||
<div>
|
|
||||||
<table id="location-table">
|
|
||||||
<tbody>
|
|
||||||
<tr>
|
|
||||||
<td>South America</td>
|
|
||||||
<td>Chile</td>
|
|
||||||
<td>Vina del Mar</td>
|
|
||||||
<td> <a
|
|
||||||
href="https://www.slickvpn.com/wp-content/themes/slickvpn-theme/vpn-configs/ovpn/gw1.kna1.slickvpn.com.ovpn">OpenVPN</a>
|
|
||||||
| <a
|
|
||||||
href="https://www.slickvpn.com/wp-content/themes/slickvpn-theme/vpn-configs/viscosity/gw1.kna1.slickvpn.com_viscosity.ovpn">Viscosity</a>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
</tbody>
|
|
||||||
</table>
|
|
||||||
</div>
|
|
||||||
`)),
|
|
||||||
hostToData: map[string]serverData{
|
|
||||||
"gw1.kna1.slickvpn.com": {
|
|
||||||
ovpnURL: "https://www.slickvpn.com/wp-content/themes/slickvpn-theme/vpn-configs/ovpn/gw1.kna1.slickvpn.com.ovpn", //nolint:lll
|
|
||||||
country: "Chile",
|
|
||||||
region: "South America",
|
|
||||||
city: "Vina del Mar",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
for name, testCase := range testCases {
|
|
||||||
testCase := testCase
|
|
||||||
t.Run(name, func(t *testing.T) {
|
|
||||||
t.Parallel()
|
|
||||||
|
|
||||||
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.slickvpn.com/locations/")
|
|
||||||
|
|
||||||
ctxErr := r.Context().Err()
|
|
||||||
if ctxErr != nil {
|
|
||||||
return nil, ctxErr
|
|
||||||
}
|
|
||||||
|
|
||||||
return &http.Response{
|
|
||||||
StatusCode: http.StatusOK,
|
|
||||||
Status: http.StatusText(testCase.responseStatus),
|
|
||||||
Body: testCase.responseBody,
|
|
||||||
}, nil
|
|
||||||
}),
|
|
||||||
}
|
|
||||||
|
|
||||||
hostToData, err := fetchServers(testCase.ctx, client)
|
|
||||||
|
|
||||||
assert.ErrorIs(t, err, testCase.errWrapped)
|
|
||||||
if testCase.errWrapped != nil {
|
|
||||||
assert.EqualError(t, err, testCase.errMessage)
|
|
||||||
}
|
|
||||||
assert.Equal(t, testCase.hostToData, hostToData)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func Test_parseHTML(t *testing.T) {
|
|
||||||
t.Parallel()
|
|
||||||
|
|
||||||
testCases := map[string]struct {
|
|
||||||
rootNode *html.Node
|
|
||||||
hostToData map[string]serverData
|
|
||||||
errWrapped error
|
|
||||||
errMessage string
|
|
||||||
}{
|
|
||||||
"empty html": {
|
|
||||||
rootNode: parseTestHTML(t, ""),
|
|
||||||
errWrapped: ErrLocationTableNotFound,
|
|
||||||
errMessage: `HTML location table node not found: in HTML code: <html><head></head><body></body></html>`,
|
|
||||||
},
|
|
||||||
"test data": {
|
|
||||||
rootNode: parseTestDataIndexHTML(t),
|
|
||||||
//nolint:lll
|
|
||||||
hostToData: map[string]serverData{
|
|
||||||
"gw1.ams1.slickvpn.com": {ovpnURL: "https://www.slickvpn.com/wp-content/themes/slickvpn-theme/vpn-configs/ovpn/gw1.ams1.slickvpn.com.ovpn", country: "Netherlands", region: "Europe", city: "Amsterdam"},
|
|
||||||
"gw1.ams2.slickvpn.com": {ovpnURL: "https://www.slickvpn.com/wp-content/themes/slickvpn-theme/vpn-configs/ovpn/gw1.ams2.slickvpn.com.ovpn", country: "Netherlands", region: "Europe", city: "Amsterdam"},
|
|
||||||
"gw1.ams3.slickvpn.com": {ovpnURL: "https://www.slickvpn.com/wp-content/themes/slickvpn-theme/vpn-configs/ovpn/gw1.ams3.slickvpn.com.ovpn", country: "Netherlands", region: "Europe", city: "Amsterdam"},
|
|
||||||
"gw1.ams4.slickvpn.com": {ovpnURL: "https://www.slickvpn.com/wp-content/themes/slickvpn-theme/vpn-configs/ovpn/gw1.ams4.slickvpn.com.ovpn", country: "Netherlands", region: "Europe", city: "Amsterdam"},
|
|
||||||
"gw1.arn1.slickvpn.com": {ovpnURL: "https://www.slickvpn.com/wp-content/themes/slickvpn-theme/vpn-configs/ovpn/gw1.arn1.slickvpn.com.ovpn", country: "Sweden", region: "Europe", city: "Stockholm"},
|
|
||||||
"gw1.arn3.slickvpn.com": {ovpnURL: "https://www.slickvpn.com/wp-content/themes/slickvpn-theme/vpn-configs/ovpn/gw1.arn3.slickvpn.com.ovpn", country: "Sweden", region: "Europe", city: "Stockholm"},
|
|
||||||
"gw1.ath1.slickvpn.com": {ovpnURL: "https://www.slickvpn.com/wp-content/themes/slickvpn-theme/vpn-configs/ovpn/gw1.ath1.slickvpn.com.ovpn", country: "Greece", region: "Europe", city: "Athens"},
|
|
||||||
"gw1.atl1.slickvpn.com": {ovpnURL: "https://www.slickvpn.com/wp-content/themes/slickvpn-theme/vpn-configs/ovpn/gw1.atl1.slickvpn.com.ovpn", country: "United States", region: "North America", city: "Atlanta"},
|
|
||||||
"gw1.atl3.slickvpn.com": {ovpnURL: "https://www.slickvpn.com/wp-content/themes/slickvpn-theme/vpn-configs/ovpn/gw1.atl3.slickvpn.com.ovpn", country: "United States", region: "North America", city: "Atlanta"},
|
|
||||||
"gw1.beg1.slickvpn.com": {ovpnURL: "https://www.slickvpn.com/wp-content/themes/slickvpn-theme/vpn-configs/ovpn/gw1.beg1.slickvpn.com.ovpn", country: "Serbia", region: "Europe", city: "Belgrade"},
|
|
||||||
"gw1.bkk1.slickvpn.com": {ovpnURL: "https://www.slickvpn.com/wp-content/themes/slickvpn-theme/vpn-configs/ovpn/gw1.bkk1.slickvpn.com.ovpn", country: "Thailand", region: "Asia", city: "Bangkok"},
|
|
||||||
"gw1.blr1.slickvpn.com": {ovpnURL: "https://www.slickvpn.com/wp-content/themes/slickvpn-theme/vpn-configs/ovpn/gw1.blr1.slickvpn.com.ovpn", country: "India", region: "Asia", city: "Bangalore"},
|
|
||||||
"gw1.bne1.slickvpn.com": {ovpnURL: "https://www.slickvpn.com/wp-content/themes/slickvpn-theme/vpn-configs/ovpn/gw1.bne1.slickvpn.com.ovpn", country: "Australia", region: "Oceania", city: "Brisbane"},
|
|
||||||
"gw1.bom1.slickvpn.com": {ovpnURL: "https://www.slickvpn.com/wp-content/themes/slickvpn-theme/vpn-configs/ovpn/gw1.bom1.slickvpn.com.ovpn", country: "India", region: "Asia", city: "Mumbai"},
|
|
||||||
"gw1.bos1.slickvpn.com": {ovpnURL: "https://www.slickvpn.com/wp-content/themes/slickvpn-theme/vpn-configs/ovpn/gw1.bos1.slickvpn.com.ovpn", country: "United States", region: "North America", city: "Boston"},
|
|
||||||
"gw1.bud1.slickvpn.com": {ovpnURL: "https://www.slickvpn.com/wp-content/themes/slickvpn-theme/vpn-configs/ovpn/gw1.bud1.slickvpn.com.ovpn", country: "Hungary", region: "Europe", city: "Budapest"},
|
|
||||||
"gw1.buf1.slickvpn.com": {ovpnURL: "https://www.slickvpn.com/wp-content/themes/slickvpn-theme/vpn-configs/ovpn/gw1.buf1.slickvpn.com.ovpn", country: "United States", region: "North America", city: "Buffalo"},
|
|
||||||
"gw1.buh2.slickvpn.com": {ovpnURL: "https://www.slickvpn.com/wp-content/themes/slickvpn-theme/vpn-configs/ovpn/gw1.buh2.slickvpn.com.ovpn", country: "Romania", region: "Europe", city: "Bucharest"},
|
|
||||||
"gw1.cdg1.slickvpn.com": {ovpnURL: "https://www.slickvpn.com/wp-content/themes/slickvpn-theme/vpn-configs/ovpn/gw1.cdg1.slickvpn.com.ovpn", country: "France", region: "Europe", city: "Paris"},
|
|
||||||
"gw1.cgk1.slickvpn.com": {ovpnURL: "https://www.slickvpn.com/wp-content/themes/slickvpn-theme/vpn-configs/ovpn/gw1.cgk1.slickvpn.com.ovpn", country: "Indonesia", region: "Asia", city: "Jakarta"},
|
|
||||||
"gw1.cmh1.slickvpn.com": {ovpnURL: "https://www.slickvpn.com/wp-content/themes/slickvpn-theme/vpn-configs/ovpn/gw1.cmh1.slickvpn.com.ovpn", country: "United States", region: "North America", city: "Columbus"},
|
|
||||||
"gw1.cph1.slickvpn.com": {ovpnURL: "https://www.slickvpn.com/wp-content/themes/slickvpn-theme/vpn-configs/ovpn/gw1.cph1.slickvpn.com.ovpn", country: "Denmark", region: "Europe", city: "Copenhagen"},
|
|
||||||
"gw1.cvt1.slickvpn.com": {ovpnURL: "https://www.slickvpn.com/wp-content/themes/slickvpn-theme/vpn-configs/ovpn/gw1.cvt1.slickvpn.com.ovpn", country: "United Kingdom", region: "Europe", city: "Coventry"},
|
|
||||||
"gw1.dbq1.slickvpn.com": {ovpnURL: "https://www.slickvpn.com/wp-content/themes/slickvpn-theme/vpn-configs/ovpn/gw1.dbq1.slickvpn.com.ovpn", country: "United States", region: "North America", city: "Dubuque"},
|
|
||||||
"gw1.den1.slickvpn.com": {ovpnURL: "https://www.slickvpn.com/wp-content/themes/slickvpn-theme/vpn-configs/ovpn/gw1.den1.slickvpn.com.ovpn", country: "United States", region: "North America", city: "Denver"},
|
|
||||||
"gw1.dfw2.slickvpn.com": {ovpnURL: "https://www.slickvpn.com/wp-content/themes/slickvpn-theme/vpn-configs/ovpn/gw1.dfw2.slickvpn.com.ovpn", country: "United States", region: "North America", city: "Dallas"},
|
|
||||||
"gw1.dfw3.slickvpn.com": {ovpnURL: "https://www.slickvpn.com/wp-content/themes/slickvpn-theme/vpn-configs/ovpn/gw1.dfw3.slickvpn.com.ovpn", country: "United States", region: "North America", city: "Dallas"},
|
|
||||||
"gw1.dub1.slickvpn.com": {ovpnURL: "https://www.slickvpn.com/wp-content/themes/slickvpn-theme/vpn-configs/ovpn/gw1.dub1.slickvpn.com.ovpn", country: "Ireland", region: "Europe", city: "Dublin"},
|
|
||||||
"gw1.ewr1.slickvpn.com": {ovpnURL: "https://www.slickvpn.com/wp-content/themes/slickvpn-theme/vpn-configs/ovpn/gw1.ewr1.slickvpn.com.ovpn", country: "United States", region: "North America", city: "Newark"},
|
|
||||||
"gw1.fra1.slickvpn.com": {ovpnURL: "https://www.slickvpn.com/wp-content/themes/slickvpn-theme/vpn-configs/ovpn/gw1.fra1.slickvpn.com.ovpn", country: "Germany", region: "Europe", city: "Frankfurt"},
|
|
||||||
"gw1.fra2.slickvpn.com": {ovpnURL: "https://www.slickvpn.com/wp-content/themes/slickvpn-theme/vpn-configs/ovpn/gw1.fra2.slickvpn.com.ovpn", country: "Germany", region: "Europe", city: "Frankfurt"},
|
|
||||||
"gw1.gru2.slickvpn.com": {ovpnURL: "https://www.slickvpn.com/wp-content/themes/slickvpn-theme/vpn-configs/ovpn/gw1.gru2.slickvpn.com.ovpn", country: "Brazil", region: "South America", city: "Sao Paulo"},
|
|
||||||
"gw1.grz1.slickvpn.com": {ovpnURL: "https://www.slickvpn.com/wp-content/themes/slickvpn-theme/vpn-configs/ovpn/gw1.grz1.slickvpn.com.ovpn", country: "Austria", region: "Europe", city: "Graz"},
|
|
||||||
"gw1.had2.slickvpn.com": {ovpnURL: "https://www.slickvpn.com/wp-content/themes/slickvpn-theme/vpn-configs/ovpn/gw1.had2.slickvpn.com.ovpn", country: "Sweden", region: "Europe", city: "Halmstad"},
|
|
||||||
"gw1.hkg2.slickvpn.com": {ovpnURL: "https://www.slickvpn.com/wp-content/themes/slickvpn-theme/vpn-configs/ovpn/gw1.hkg2.slickvpn.com.ovpn", country: "Hong Kong", region: "Asia", city: "Hong Kong"},
|
|
||||||
"gw1.iad1.slickvpn.com": {ovpnURL: "https://www.slickvpn.com/wp-content/themes/slickvpn-theme/vpn-configs/ovpn/gw1.iad1.slickvpn.com.ovpn", country: "United States", region: "North America", city: "Washington"},
|
|
||||||
"gw1.iev1.slickvpn.com": {ovpnURL: "https://www.slickvpn.com/wp-content/themes/slickvpn-theme/vpn-configs/ovpn/gw1.iev1.slickvpn.com.ovpn", country: "Ukraine", region: "Europe", city: "Kiev"},
|
|
||||||
"gw1.iom1.slickvpn.com": {ovpnURL: "https://www.slickvpn.com/wp-content/themes/slickvpn-theme/vpn-configs/ovpn/gw1.iom1.slickvpn.com.ovpn", country: "United Kingdom", region: "Europe", city: "Isle Of Man"},
|
|
||||||
"gw1.kiv1.slickvpn.com": {ovpnURL: "https://www.slickvpn.com/wp-content/themes/slickvpn-theme/vpn-configs/ovpn/gw1.kiv1.slickvpn.com.ovpn", country: "Moldova", region: "Europe", city: "Chisinau"},
|
|
||||||
"gw1.kna1.slickvpn.com": {ovpnURL: "https://www.slickvpn.com/wp-content/themes/slickvpn-theme/vpn-configs/ovpn/gw1.kna1.slickvpn.com.ovpn", country: "Chile", region: "South America", city: "Vina del Mar"},
|
|
||||||
"gw1.kul1.slickvpn.com": {ovpnURL: "https://www.slickvpn.com/wp-content/themes/slickvpn-theme/vpn-configs/ovpn/gw1.kul1.slickvpn.com.ovpn", country: "Malaysia", region: "Asia", city: "Kuala Lumpur"},
|
|
||||||
"gw1.las1.slickvpn.com": {ovpnURL: "https://www.slickvpn.com/wp-content/themes/slickvpn-theme/vpn-configs/ovpn/gw1.las1.slickvpn.com.ovpn", country: "United States", region: "North America", city: "Las Vegas"},
|
|
||||||
"gw1.lax1.slickvpn.com": {ovpnURL: "https://www.slickvpn.com/wp-content/themes/slickvpn-theme/vpn-configs/ovpn/gw1.lax1.slickvpn.com.ovpn", country: "United States", region: "North America", city: "Los Angeles"},
|
|
||||||
"gw1.lax2.slickvpn.com": {ovpnURL: "https://www.slickvpn.com/wp-content/themes/slickvpn-theme/vpn-configs/ovpn/gw1.lax2.slickvpn.com.ovpn", country: "United States", region: "North America", city: "Los Angeles"},
|
|
||||||
"gw1.led1.slickvpn.com": {ovpnURL: "https://www.slickvpn.com/wp-content/themes/slickvpn-theme/vpn-configs/ovpn/gw1.led1.slickvpn.com.ovpn", country: "Russian Federation", region: "Europe", city: "St Petersburg"},
|
|
||||||
"gw1.lga1.slickvpn.com": {ovpnURL: "https://www.slickvpn.com/wp-content/themes/slickvpn-theme/vpn-configs/ovpn/gw1.lga1.slickvpn.com.ovpn", country: "United States", region: "North America", city: "New York"},
|
|
||||||
"gw1.lga2.slickvpn.com": {ovpnURL: "https://www.slickvpn.com/wp-content/themes/slickvpn-theme/vpn-configs/ovpn/gw1.lga2.slickvpn.com.ovpn", country: "United States", region: "North America", city: "New York"},
|
|
||||||
"gw1.lhr1.slickvpn.com": {ovpnURL: "https://www.slickvpn.com/wp-content/themes/slickvpn-theme/vpn-configs/ovpn/gw1.lhr1.slickvpn.com.ovpn", country: "United Kingdom", region: "Europe", city: "London"},
|
|
||||||
"gw1.lhr2.slickvpn.com": {ovpnURL: "https://www.slickvpn.com/wp-content/themes/slickvpn-theme/vpn-configs/ovpn/gw1.lhr2.slickvpn.com.ovpn", country: "United Kingdom", region: "Europe", city: "London"},
|
|
||||||
"gw1.lil1.slickvpn.com": {ovpnURL: "https://www.slickvpn.com/wp-content/themes/slickvpn-theme/vpn-configs/ovpn/gw1.lil1.slickvpn.com.ovpn", country: "France", region: "Europe", city: "Lille"},
|
|
||||||
"gw1.lju1.slickvpn.com": {ovpnURL: "https://www.slickvpn.com/wp-content/themes/slickvpn-theme/vpn-configs/ovpn/gw1.lju1.slickvpn.com.ovpn", country: "Slovenia", region: "Europe", city: "Ljubljana"},
|
|
||||||
"gw1.mad1.slickvpn.com": {ovpnURL: "https://www.slickvpn.com/wp-content/themes/slickvpn-theme/vpn-configs/ovpn/gw1.mad1.slickvpn.com.ovpn", country: "Spain", region: "Europe", city: "Madrid"},
|
|
||||||
"gw1.man2.slickvpn.com": {ovpnURL: "https://www.slickvpn.com/wp-content/themes/slickvpn-theme/vpn-configs/ovpn/gw1.man2.slickvpn.com.ovpn", country: "United Kingdom", region: "Europe", city: "Manchester"},
|
|
||||||
"gw1.mci2.slickvpn.com": {ovpnURL: "https://www.slickvpn.com/wp-content/themes/slickvpn-theme/vpn-configs/ovpn/gw1.mci2.slickvpn.com.ovpn", country: "United States", region: "North America", city: "Kansas City"},
|
|
||||||
"gw1.mrn1.slickvpn.com": {ovpnURL: "https://www.slickvpn.com/wp-content/themes/slickvpn-theme/vpn-configs/ovpn/gw1.mrn1.slickvpn.com.ovpn", country: "United States", region: "North America", city: "Morganton"},
|
|
||||||
"gw1.mxp1.slickvpn.com": {ovpnURL: "https://www.slickvpn.com/wp-content/themes/slickvpn-theme/vpn-configs/ovpn/gw1.mxp1.slickvpn.com.ovpn", country: "Italy", region: "Europe", city: "Milan"},
|
|
||||||
"gw1.mxp2.slickvpn.com": {ovpnURL: "https://www.slickvpn.com/wp-content/themes/slickvpn-theme/vpn-configs/ovpn/gw1.mxp2.slickvpn.com.ovpn", country: "Italy", region: "Europe", city: "Milan"},
|
|
||||||
"gw1.nrt1.slickvpn.com": {ovpnURL: "https://www.slickvpn.com/wp-content/themes/slickvpn-theme/vpn-configs/ovpn/gw1.nrt1.slickvpn.com.ovpn", country: "Japan", region: "Asia", city: "Tokyo"},
|
|
||||||
"gw1.nue1.slickvpn.com": {ovpnURL: "https://www.slickvpn.com/wp-content/themes/slickvpn-theme/vpn-configs/ovpn/gw1.nue1.slickvpn.com.ovpn", country: "Germany", region: "Europe", city: "Nürnberg"},
|
|
||||||
"gw1.ord3.slickvpn.com": {ovpnURL: "https://www.slickvpn.com/wp-content/themes/slickvpn-theme/vpn-configs/ovpn/gw1.ord3.slickvpn.com.ovpn", country: "United States", region: "North America", city: "Chicago"},
|
|
||||||
"gw1.ord4.slickvpn.com": {ovpnURL: "https://www.slickvpn.com/wp-content/themes/slickvpn-theme/vpn-configs/ovpn/gw1.ord4.slickvpn.com.ovpn", country: "United States", region: "North America", city: "Chicago"},
|
|
||||||
"gw1.ost2.slickvpn.com": {ovpnURL: "https://www.slickvpn.com/wp-content/themes/slickvpn-theme/vpn-configs/ovpn/gw1.ost2.slickvpn.com.ovpn", country: "Belgium", region: "Europe", city: "Ostend"},
|
|
||||||
"gw1.pao1.slickvpn.com": {ovpnURL: "https://www.slickvpn.com/wp-content/themes/slickvpn-theme/vpn-configs/ovpn/gw1.pao1.slickvpn.com.ovpn", country: "United States", region: "North America", city: "Palo Alto"},
|
|
||||||
"gw1.phx2.slickvpn.com": {ovpnURL: "https://www.slickvpn.com/wp-content/themes/slickvpn-theme/vpn-configs/ovpn/gw1.phx2.slickvpn.com.ovpn", country: "United States", region: "North America", city: "Phoenix"},
|
|
||||||
"gw1.prg1.slickvpn.com": {ovpnURL: "https://www.slickvpn.com/wp-content/themes/slickvpn-theme/vpn-configs/ovpn/gw1.prg1.slickvpn.com.ovpn", country: "Czech Republic", region: "Europe", city: "Prague"},
|
|
||||||
"gw1.prg2.slickvpn.com": {ovpnURL: "https://www.slickvpn.com/wp-content/themes/slickvpn-theme/vpn-configs/ovpn/gw1.prg2.slickvpn.com.ovpn", country: "Czech Republic", region: "Europe", city: "Prague"},
|
|
||||||
"gw1.rcs1.slickvpn.com": {ovpnURL: "https://www.slickvpn.com/wp-content/themes/slickvpn-theme/vpn-configs/ovpn/gw1.rcs1.slickvpn.com.ovpn", country: "United Kingdom", region: "Europe", city: "Rochester"},
|
|
||||||
"gw1.rkv1.slickvpn.com": {ovpnURL: "https://www.slickvpn.com/wp-content/themes/slickvpn-theme/vpn-configs/ovpn/gw1.rkv1.slickvpn.com.ovpn", country: "Iceland", region: "Europe", city: "Reykjavik"},
|
|
||||||
"gw1.san1.slickvpn.com": {ovpnURL: "https://www.slickvpn.com/wp-content/themes/slickvpn-theme/vpn-configs/ovpn/gw1.san1.slickvpn.com.ovpn", country: "United States", region: "North America", city: "San Diego"},
|
|
||||||
"gw1.sea1.slickvpn.com": {ovpnURL: "https://www.slickvpn.com/wp-content/themes/slickvpn-theme/vpn-configs/ovpn/gw1.sea1.slickvpn.com.ovpn", country: "United States", region: "North America", city: "Seattle"},
|
|
||||||
"gw1.sea2.slickvpn.com": {ovpnURL: "https://www.slickvpn.com/wp-content/themes/slickvpn-theme/vpn-configs/ovpn/gw1.sea2.slickvpn.com.ovpn", country: "United States", region: "North America", city: "Seattle"},
|
|
||||||
"gw1.sin1.slickvpn.com": {ovpnURL: "https://www.slickvpn.com/wp-content/themes/slickvpn-theme/vpn-configs/ovpn/gw1.sin1.slickvpn.com.ovpn", country: "Singapore", region: "Asia", city: "Singapore"},
|
|
||||||
"gw1.sin2.slickvpn.com": {ovpnURL: "https://www.slickvpn.com/wp-content/themes/slickvpn-theme/vpn-configs/ovpn/gw1.sin2.slickvpn.com.ovpn", country: "Singapore", region: "Asia", city: "Singapore"},
|
|
||||||
"gw1.sjc2.slickvpn.com": {ovpnURL: "https://www.slickvpn.com/wp-content/themes/slickvpn-theme/vpn-configs/ovpn/gw1.sjc2.slickvpn.com.ovpn", country: "United States", region: "North America", city: "San Jose"},
|
|
||||||
"gw1.skg1.slickvpn.com": {ovpnURL: "https://www.slickvpn.com/wp-content/themes/slickvpn-theme/vpn-configs/ovpn/gw1.skg1.slickvpn.com.ovpn", country: "Greece", region: "Europe", city: "Thessaloniki"},
|
|
||||||
"gw1.sou1.slickvpn.com": {ovpnURL: "https://www.slickvpn.com/wp-content/themes/slickvpn-theme/vpn-configs/ovpn/gw1.sou1.slickvpn.com.ovpn", country: "United Kingdom", region: "Europe", city: "Eastleigh near Southampton"},
|
|
||||||
"gw1.stl1.slickvpn.com": {ovpnURL: "https://www.slickvpn.com/wp-content/themes/slickvpn-theme/vpn-configs/ovpn/gw1.stl1.slickvpn.com.ovpn", country: "United States", region: "North America", city: "St Louis"},
|
|
||||||
"gw1.svo1.slickvpn.com": {ovpnURL: "https://www.slickvpn.com/wp-content/themes/slickvpn-theme/vpn-configs/ovpn/gw1.svo1.slickvpn.com.ovpn", country: "Russian Federation", region: "Europe", city: "Moscow"},
|
|
||||||
"gw1.svo2.slickvpn.com": {ovpnURL: "https://www.slickvpn.com/wp-content/themes/slickvpn-theme/vpn-configs/ovpn/gw1.svo2.slickvpn.com.ovpn", country: "Russian Federation", region: "Europe", city: "Moscow"},
|
|
||||||
"gw1.syd1.slickvpn.com": {ovpnURL: "https://www.slickvpn.com/wp-content/themes/slickvpn-theme/vpn-configs/ovpn/gw1.syd1.slickvpn.com.ovpn", country: "Australia", region: "Oceania", city: "Sydney"},
|
|
||||||
"gw1.syd2.slickvpn.com": {ovpnURL: "https://www.slickvpn.com/wp-content/themes/slickvpn-theme/vpn-configs/ovpn/gw1.syd2.slickvpn.com.ovpn", country: "Australia", region: "Oceania", city: "Sydney"},
|
|
||||||
"gw1.tll1.slickvpn.com": {ovpnURL: "https://www.slickvpn.com/wp-content/themes/slickvpn-theme/vpn-configs/ovpn/gw1.tll1.slickvpn.com.ovpn", country: "Estonia", region: "Europe", city: "Tallinn"},
|
|
||||||
"gw1.tlv2.slickvpn.com": {ovpnURL: "https://www.slickvpn.com/wp-content/themes/slickvpn-theme/vpn-configs/ovpn/gw1.tlv2.slickvpn.com.ovpn", country: "Israel", region: "Asia", city: "Tel Aviv Yafo"},
|
|
||||||
"gw1.tpa1.slickvpn.com": {ovpnURL: "https://www.slickvpn.com/wp-content/themes/slickvpn-theme/vpn-configs/ovpn/gw1.tpa1.slickvpn.com.ovpn", country: "United States", region: "North America", city: "Tampa"},
|
|
||||||
"gw1.trf1.slickvpn.com": {ovpnURL: "https://www.slickvpn.com/wp-content/themes/slickvpn-theme/vpn-configs/ovpn/gw1.trf1.slickvpn.com.ovpn", country: "Norway", region: "Europe", city: "Torp"},
|
|
||||||
"gw1.waw1.slickvpn.com": {ovpnURL: "https://www.slickvpn.com/wp-content/themes/slickvpn-theme/vpn-configs/ovpn/gw1.waw1.slickvpn.com.ovpn", country: "Poland", region: "Europe", city: "Warsaw"},
|
|
||||||
"gw1.yei1.slickvpn.com": {ovpnURL: "https://www.slickvpn.com/wp-content/themes/slickvpn-theme/vpn-configs/ovpn/gw1.yei1.slickvpn.com.ovpn", country: "Turkey", region: "Asia", city: "Bursa"},
|
|
||||||
"gw1.yul1.slickvpn.com": {ovpnURL: "https://www.slickvpn.com/wp-content/themes/slickvpn-theme/vpn-configs/ovpn/gw1.yul1.slickvpn.com.ovpn", country: "Canada", region: "North America", city: "Montreal"},
|
|
||||||
"gw1.yul2.slickvpn.com": {ovpnURL: "https://www.slickvpn.com/wp-content/themes/slickvpn-theme/vpn-configs/ovpn/gw1.yul2.slickvpn.com.ovpn", country: "Canada", region: "North America", city: "Montreal"},
|
|
||||||
"gw1.yvr1.slickvpn.com": {ovpnURL: "https://www.slickvpn.com/wp-content/themes/slickvpn-theme/vpn-configs/ovpn/gw1.yvr1.slickvpn.com.ovpn", country: "Canada", region: "North America", city: "Vancouver"},
|
|
||||||
"gw1.yyz1.slickvpn.com": {ovpnURL: "https://www.slickvpn.com/wp-content/themes/slickvpn-theme/vpn-configs/ovpn/gw1.yyz1.slickvpn.com.ovpn", country: "Canada", region: "North America", city: "Toronto"},
|
|
||||||
"gw1.zrh1.slickvpn.com": {ovpnURL: "https://www.slickvpn.com/wp-content/themes/slickvpn-theme/vpn-configs/ovpn/gw1.zrh1.slickvpn.com.ovpn", country: "Switzerland", region: "Europe", city: "Zurich"},
|
|
||||||
"gw2.ams3.slickvpn.com": {ovpnURL: "https://www.slickvpn.com/wp-content/themes/slickvpn-theme/vpn-configs/ovpn/gw2.ams3.slickvpn.com.ovpn", country: "Netherlands", region: "Europe", city: "Amsterdam"},
|
|
||||||
"gw2.atl3.slickvpn.com": {ovpnURL: "https://www.slickvpn.com/wp-content/themes/slickvpn-theme/vpn-configs/ovpn/gw2.atl3.slickvpn.com.ovpn", country: "United States", region: "North America", city: "Atlanta"},
|
|
||||||
"gw2.bcn2.slickvpn.com": {ovpnURL: "https://www.slickvpn.com/wp-content/themes/slickvpn-theme/vpn-configs/ovpn/gw2.bcn2.slickvpn.com.ovpn", country: "Spain", region: "Europe", city: "Barcelona"},
|
|
||||||
"gw2.clt1.slickvpn.com": {ovpnURL: "https://www.slickvpn.com/wp-content/themes/slickvpn-theme/vpn-configs/ovpn/gw2.clt1.slickvpn.com.ovpn", country: "United States", region: "North America", city: "Charlotte"},
|
|
||||||
"gw2.dfw3.slickvpn.com": {ovpnURL: "https://www.slickvpn.com/wp-content/themes/slickvpn-theme/vpn-configs/ovpn/gw2.dfw3.slickvpn.com.ovpn", country: "United States", region: "North America", city: "Dallas"},
|
|
||||||
"gw2.ewr1.slickvpn.com": {ovpnURL: "https://www.slickvpn.com/wp-content/themes/slickvpn-theme/vpn-configs/ovpn/gw2.ewr1.slickvpn.com.ovpn", country: "United States", region: "North America", city: "Newark"},
|
|
||||||
"gw2.fra1.slickvpn.com": {ovpnURL: "https://www.slickvpn.com/wp-content/themes/slickvpn-theme/vpn-configs/ovpn/gw2.fra1.slickvpn.com.ovpn", country: "Germany", region: "Europe", city: "Frankfurt"},
|
|
||||||
"gw2.hou1.slickvpn.com": {ovpnURL: "https://www.slickvpn.com/wp-content/themes/slickvpn-theme/vpn-configs/ovpn/gw2.hou1.slickvpn.com.ovpn", country: "United States", region: "North America", city: "Houston"},
|
|
||||||
"gw2.iad1.slickvpn.com": {ovpnURL: "https://www.slickvpn.com/wp-content/themes/slickvpn-theme/vpn-configs/ovpn/gw2.iad1.slickvpn.com.ovpn", country: "United States", region: "North America", city: "Washington"},
|
|
||||||
"gw2.lhr2.slickvpn.com": {ovpnURL: "https://www.slickvpn.com/wp-content/themes/slickvpn-theme/vpn-configs/ovpn/gw2.lhr2.slickvpn.com.ovpn", country: "United Kingdom", region: "Europe", city: "London"},
|
|
||||||
"gw2.mel1.slickvpn.com": {ovpnURL: "https://www.slickvpn.com/wp-content/themes/slickvpn-theme/vpn-configs/ovpn/gw2.mel1.slickvpn.com.ovpn", country: "Australia", region: "Oceania", city: "Melbourne"},
|
|
||||||
"gw2.mia3.slickvpn.com": {ovpnURL: "https://www.slickvpn.com/wp-content/themes/slickvpn-theme/vpn-configs/ovpn/gw2.mia3.slickvpn.com.ovpn", country: "United States", region: "North America", city: "Miami"},
|
|
||||||
"gw2.mia4.slickvpn.com": {ovpnURL: "https://www.slickvpn.com/wp-content/themes/slickvpn-theme/vpn-configs/ovpn/gw2.mia4.slickvpn.com.ovpn", country: "United States", region: "North America", city: "Miami"},
|
|
||||||
"gw2.mxp2.slickvpn.com": {ovpnURL: "https://www.slickvpn.com/wp-content/themes/slickvpn-theme/vpn-configs/ovpn/gw2.mxp2.slickvpn.com.ovpn", country: "Italy", region: "Europe", city: "Milan"},
|
|
||||||
"gw2.ord1.slickvpn.com": {ovpnURL: "https://www.slickvpn.com/wp-content/themes/slickvpn-theme/vpn-configs/ovpn/gw2.ord1.slickvpn.com.ovpn", country: "United States", region: "North America", city: "Chicago"},
|
|
||||||
"gw2.ost2.slickvpn.com": {ovpnURL: "https://www.slickvpn.com/wp-content/themes/slickvpn-theme/vpn-configs/ovpn/gw2.ost2.slickvpn.com.ovpn", country: "Belgium", region: "Europe", city: "Ostend"},
|
|
||||||
"gw2.pao1.slickvpn.com": {ovpnURL: "https://www.slickvpn.com/wp-content/themes/slickvpn-theme/vpn-configs/ovpn/gw2.pao1.slickvpn.com.ovpn", country: "United States", region: "North America", city: "Palo Alto"},
|
|
||||||
"gw2.prg1.slickvpn.com": {ovpnURL: "https://www.slickvpn.com/wp-content/themes/slickvpn-theme/vpn-configs/ovpn/gw2.prg1.slickvpn.com.ovpn", country: "Czech Republic", region: "Europe", city: "Prague"},
|
|
||||||
"gw2.pty1.slickvpn.com": {ovpnURL: "https://www.slickvpn.com/wp-content/themes/slickvpn-theme/vpn-configs/ovpn/gw2.pty1.slickvpn.com.ovpn", country: "Panama", region: "North America", city: "Panama City"},
|
|
||||||
"gw2.sin2.slickvpn.com": {ovpnURL: "https://www.slickvpn.com/wp-content/themes/slickvpn-theme/vpn-configs/ovpn/gw2.sin2.slickvpn.com.ovpn", country: "Singapore", region: "Asia", city: "Singapore"},
|
|
||||||
"gw2.slc1.slickvpn.com": {ovpnURL: "https://www.slickvpn.com/wp-content/themes/slickvpn-theme/vpn-configs/ovpn/gw2.slc1.slickvpn.com.ovpn", country: "United States", region: "North America", city: "Salt Lake City"},
|
|
||||||
"gw2.syd2.slickvpn.com": {ovpnURL: "https://www.slickvpn.com/wp-content/themes/slickvpn-theme/vpn-configs/ovpn/gw2.syd2.slickvpn.com.ovpn", country: "Australia", region: "Oceania", city: "Sydney"},
|
|
||||||
"gw2.tpe1.slickvpn.com": {ovpnURL: "https://www.slickvpn.com/wp-content/themes/slickvpn-theme/vpn-configs/ovpn/gw2.tpe1.slickvpn.com.ovpn", country: "Taiwan", region: "Asia", city: "Taipei"},
|
|
||||||
"gw2.yul2.slickvpn.com": {ovpnURL: "https://www.slickvpn.com/wp-content/themes/slickvpn-theme/vpn-configs/ovpn/gw2.yul2.slickvpn.com.ovpn", country: "Canada", region: "North America", city: "Montreal"},
|
|
||||||
"gw2.yyz1.slickvpn.com": {ovpnURL: "https://www.slickvpn.com/wp-content/themes/slickvpn-theme/vpn-configs/ovpn/gw2.yyz1.slickvpn.com.ovpn", country: "Canada", region: "North America", city: "Toronto"},
|
|
||||||
"gw3.dfw3.slickvpn.com": {ovpnURL: "https://www.slickvpn.com/wp-content/themes/slickvpn-theme/vpn-configs/ovpn/gw3.dfw3.slickvpn.com.ovpn", country: "United States", region: "North America", city: "Dallas"},
|
|
||||||
"gw3.ewr1.slickvpn.com": {ovpnURL: "https://www.slickvpn.com/wp-content/themes/slickvpn-theme/vpn-configs/ovpn/gw3.ewr1.slickvpn.com.ovpn", country: "United States", region: "North America", city: "Newark"},
|
|
||||||
"gw3.iad1.slickvpn.com": {ovpnURL: "https://www.slickvpn.com/wp-content/themes/slickvpn-theme/vpn-configs/ovpn/gw3.iad1.slickvpn.com.ovpn", country: "United States", region: "North America", city: "Washington"},
|
|
||||||
"gw3.lax3.slickvpn.com": {ovpnURL: "https://www.slickvpn.com/wp-content/themes/slickvpn-theme/vpn-configs/ovpn/gw3.lax3.slickvpn.com.ovpn", country: "United States", region: "North America", city: "Los Angeles"},
|
|
||||||
"gw3.lhr1.slickvpn.com": {ovpnURL: "https://www.slickvpn.com/wp-content/themes/slickvpn-theme/vpn-configs/ovpn/gw3.lhr1.slickvpn.com.ovpn", country: "United Kingdom", region: "Europe", city: "London"},
|
|
||||||
"gw3.lhr2.slickvpn.com": {ovpnURL: "https://www.slickvpn.com/wp-content/themes/slickvpn-theme/vpn-configs/ovpn/gw3.lhr2.slickvpn.com.ovpn", country: "United Kingdom", region: "Europe", city: "London"},
|
|
||||||
"gw3.pao1.slickvpn.com": {ovpnURL: "https://www.slickvpn.com/wp-content/themes/slickvpn-theme/vpn-configs/ovpn/gw3.pao1.slickvpn.com.ovpn", country: "United States", region: "North America", city: "Palo Alto"},
|
|
||||||
"gw3.per1.slickvpn.com": {ovpnURL: "https://www.slickvpn.com/wp-content/themes/slickvpn-theme/vpn-configs/ovpn/gw3.per1.slickvpn.com.ovpn", country: "Australia", region: "Oceania", city: "Perth"},
|
|
||||||
"gw3.sou1.slickvpn.com": {ovpnURL: "https://www.slickvpn.com/wp-content/themes/slickvpn-theme/vpn-configs/ovpn/gw3.sou1.slickvpn.com.ovpn", country: "United Kingdom", region: "Europe", city: "Eastleigh near Southampton"},
|
|
||||||
"gw4.lhr1.slickvpn.com": {ovpnURL: "https://www.slickvpn.com/wp-content/themes/slickvpn-theme/vpn-configs/ovpn/gw4.lhr1.slickvpn.com.ovpn", country: "United Kingdom", region: "Europe", city: "London"},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
for name, testCase := range testCases {
|
|
||||||
testCase := testCase
|
|
||||||
t.Run(name, func(t *testing.T) {
|
|
||||||
t.Parallel()
|
|
||||||
|
|
||||||
hostToData, err := parseHTML(testCase.rootNode)
|
|
||||||
|
|
||||||
assert.Equal(t, testCase.hostToData, hostToData)
|
|
||||||
assert.ErrorIs(t, err, testCase.errWrapped)
|
|
||||||
if testCase.errWrapped != nil {
|
|
||||||
assert.EqualError(t, err, testCase.errMessage)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -12,7 +12,7 @@ type ServerLocation struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// TODO remove retroRegion and servers from API in v4.
|
// TODO remove retroRegion and servers from API in v4.
|
||||||
func LocationData() (data []ServerLocation) {
|
func LocationData() (data []ServerLocation) { //nolint:maintidx
|
||||||
//nolint:lll
|
//nolint:lll
|
||||||
return []ServerLocation{
|
return []ServerLocation{
|
||||||
{Region: "Asia Pacific", Country: "Australia", City: "Adelaide", RetroLoc: "Australia Adelaide", Hostname: "au-adl.prod.surfshark.com", MultiHop: false},
|
{Region: "Asia Pacific", Country: "Australia", City: "Adelaide", RetroLoc: "Australia Adelaide", Hostname: "au-adl.prod.surfshark.com", MultiHop: false},
|
||||||
@@ -22,9 +22,23 @@ func LocationData() (data []ServerLocation) {
|
|||||||
{Region: "Asia Pacific", Country: "Australia", City: "Sydney", RetroLoc: "Australia Sydney", Hostname: "au-syd.prod.surfshark.com", MultiHop: false},
|
{Region: "Asia Pacific", Country: "Australia", City: "Sydney", RetroLoc: "Australia Sydney", Hostname: "au-syd.prod.surfshark.com", MultiHop: false},
|
||||||
{Region: "Asia Pacific", Country: "Azerbaijan", City: "Baku", RetroLoc: "Azerbaijan", Hostname: "az-bak.prod.surfshark.com", MultiHop: false},
|
{Region: "Asia Pacific", Country: "Azerbaijan", City: "Baku", RetroLoc: "Azerbaijan", Hostname: "az-bak.prod.surfshark.com", MultiHop: false},
|
||||||
{Region: "Asia Pacific", Country: "Hong Kong", City: "Hong Kong", RetroLoc: "Hong Kong", Hostname: "hk-hkg.prod.surfshark.com", MultiHop: false},
|
{Region: "Asia Pacific", Country: "Hong Kong", City: "Hong Kong", RetroLoc: "Hong Kong", Hostname: "hk-hkg.prod.surfshark.com", MultiHop: false},
|
||||||
|
{Region: "Asia Pacific", Country: "India", City: "Chennai", RetroLoc: "India Chennai", Hostname: "in-chn.prod.surfshark.com", MultiHop: false},
|
||||||
|
{Region: "Asia Pacific", Country: "India", City: "Indore", RetroLoc: "India Indore", Hostname: "in-idr.prod.surfshark.com", MultiHop: false},
|
||||||
|
{Region: "Asia Pacific", Country: "India", City: "Mumbai", RetroLoc: "India Mumbai", Hostname: "in-mum.prod.surfshark.com", MultiHop: false},
|
||||||
{Region: "Asia Pacific", Country: "Indonesia", City: "Jakarta", RetroLoc: "Indonesia", Hostname: "id-jak.prod.surfshark.com", MultiHop: false},
|
{Region: "Asia Pacific", Country: "Indonesia", City: "Jakarta", RetroLoc: "Indonesia", Hostname: "id-jak.prod.surfshark.com", MultiHop: false},
|
||||||
|
{Region: "Asia Pacific", Country: "Japan", City: "Tokyo", RetroLoc: "Japan Tokyo st001", Hostname: "jp-tok-st001.prod.surfshark.com", MultiHop: false},
|
||||||
|
{Region: "Asia Pacific", Country: "Japan", City: "Tokyo", RetroLoc: "Japan Tokyo st002", Hostname: "jp-tok-st002.prod.surfshark.com", MultiHop: false},
|
||||||
|
{Region: "Asia Pacific", Country: "Japan", City: "Tokyo", RetroLoc: "Japan Tokyo st003", Hostname: "jp-tok-st003.prod.surfshark.com", MultiHop: false},
|
||||||
{Region: "Asia Pacific", Country: "Japan", City: "Tokyo", RetroLoc: "Japan Tokyo st004", Hostname: "jp-tok-st004.prod.surfshark.com", MultiHop: false},
|
{Region: "Asia Pacific", Country: "Japan", City: "Tokyo", RetroLoc: "Japan Tokyo st004", Hostname: "jp-tok-st004.prod.surfshark.com", MultiHop: false},
|
||||||
|
{Region: "Asia Pacific", Country: "Japan", City: "Tokyo", RetroLoc: "Japan Tokyo st005", Hostname: "jp-tok-st005.prod.surfshark.com", MultiHop: false},
|
||||||
|
{Region: "Asia Pacific", Country: "Japan", City: "Tokyo", RetroLoc: "Japan Tokyo st006", Hostname: "jp-tok-st006.prod.surfshark.com", MultiHop: false},
|
||||||
{Region: "Asia Pacific", Country: "Japan", City: "Tokyo", RetroLoc: "Japan Tokyo st007", Hostname: "jp-tok-st007.prod.surfshark.com", MultiHop: false},
|
{Region: "Asia Pacific", Country: "Japan", City: "Tokyo", RetroLoc: "Japan Tokyo st007", Hostname: "jp-tok-st007.prod.surfshark.com", MultiHop: false},
|
||||||
|
{Region: "Asia Pacific", Country: "Japan", City: "Tokyo", RetroLoc: "Japan Tokyo st008", Hostname: "jp-tok-st008.prod.surfshark.com", MultiHop: false},
|
||||||
|
{Region: "Asia Pacific", Country: "Japan", City: "Tokyo", RetroLoc: "Japan Tokyo st009", Hostname: "jp-tok-st009.prod.surfshark.com", MultiHop: false},
|
||||||
|
{Region: "Asia Pacific", Country: "Japan", City: "Tokyo", RetroLoc: "Japan Tokyo st010", Hostname: "jp-tok-st010.prod.surfshark.com", MultiHop: false},
|
||||||
|
{Region: "Asia Pacific", Country: "Japan", City: "Tokyo", RetroLoc: "Japan Tokyo st011", Hostname: "jp-tok-st011.prod.surfshark.com", MultiHop: false},
|
||||||
|
{Region: "Asia Pacific", Country: "Japan", City: "Tokyo", RetroLoc: "Japan Tokyo st012", Hostname: "jp-tok-st012.prod.surfshark.com", MultiHop: false},
|
||||||
|
{Region: "Asia Pacific", Country: "Japan", City: "Tokyo", RetroLoc: "Japan Tokyo st013", Hostname: "jp-tok-st013.prod.surfshark.com", MultiHop: false},
|
||||||
{Region: "Asia Pacific", Country: "Japan", City: "Tokyo", Hostname: "jp-tok-st014.prod.surfshark.com"},
|
{Region: "Asia Pacific", Country: "Japan", City: "Tokyo", Hostname: "jp-tok-st014.prod.surfshark.com"},
|
||||||
{Region: "Asia Pacific", Country: "Japan", City: "Tokyo", Hostname: "jp-tok-st015.prod.surfshark.com"},
|
{Region: "Asia Pacific", Country: "Japan", City: "Tokyo", Hostname: "jp-tok-st015.prod.surfshark.com"},
|
||||||
{Region: "Asia Pacific", Country: "Japan", City: "Tokyo", Hostname: "jp-tok-st016.prod.surfshark.com"},
|
{Region: "Asia Pacific", Country: "Japan", City: "Tokyo", Hostname: "jp-tok-st016.prod.surfshark.com"},
|
||||||
@@ -38,12 +52,17 @@ func LocationData() (data []ServerLocation) {
|
|||||||
{Region: "Asia Pacific", Country: "Japan", City: "Tokyo", Hostname: "jp-tok-st024.prod.surfshark.com"},
|
{Region: "Asia Pacific", Country: "Japan", City: "Tokyo", Hostname: "jp-tok-st024.prod.surfshark.com"},
|
||||||
{Region: "Asia Pacific", Country: "Japan", City: "Tokyo", Hostname: "jp-tok-st025.prod.surfshark.com"},
|
{Region: "Asia Pacific", Country: "Japan", City: "Tokyo", Hostname: "jp-tok-st025.prod.surfshark.com"},
|
||||||
{Region: "Asia Pacific", Country: "Japan", City: "Tokyo", RetroLoc: "Japan Tokyo", Hostname: "jp-tok.prod.surfshark.com", MultiHop: false},
|
{Region: "Asia Pacific", Country: "Japan", City: "Tokyo", RetroLoc: "Japan Tokyo", Hostname: "jp-tok.prod.surfshark.com", MultiHop: false},
|
||||||
|
{Region: "Asia Pacific", Country: "Kazakhstan", City: "Oral", RetroLoc: "Kazakhstan", Hostname: "kz-ura.prod.surfshark.com", MultiHop: false},
|
||||||
{Region: "Asia Pacific", Country: "Malaysia", City: "Kuala Lumpur", RetroLoc: "Malaysia", Hostname: "my-kul.prod.surfshark.com", MultiHop: false},
|
{Region: "Asia Pacific", Country: "Malaysia", City: "Kuala Lumpur", RetroLoc: "Malaysia", Hostname: "my-kul.prod.surfshark.com", MultiHop: false},
|
||||||
{Region: "Asia Pacific", Country: "New Zealand", City: "Auckland", RetroLoc: "New Zealand", Hostname: "nz-akl.prod.surfshark.com", MultiHop: false},
|
{Region: "Asia Pacific", Country: "New Zealand", City: "Auckland", RetroLoc: "New Zealand", Hostname: "nz-akl.prod.surfshark.com", MultiHop: false},
|
||||||
{Region: "Asia Pacific", Country: "Philippines", City: "Manila", RetroLoc: "Philippines", Hostname: "ph-mnl.prod.surfshark.com", MultiHop: false},
|
{Region: "Asia Pacific", Country: "Philippines", City: "Manila", RetroLoc: "Philippines", Hostname: "ph-mnl.prod.surfshark.com", MultiHop: false},
|
||||||
{Region: "Asia Pacific", Country: "Singapore Hong Kong", City: "Hong Kong", RetroLoc: "Singapore Hong Kong", Hostname: "sg-hk.prod.surfshark.com", MultiHop: true},
|
{Region: "Asia Pacific", Country: "Singapore Hong Kong", City: "Hong Kong", RetroLoc: "Singapore Hong Kong", Hostname: "sg-hk.prod.surfshark.com", MultiHop: true},
|
||||||
{Region: "Asia Pacific", Country: "Singapore in", City: "", RetroLoc: "Singapore in", Hostname: "sg-in.prod.surfshark.com", MultiHop: true},
|
{Region: "Asia Pacific", Country: "Singapore in", City: "", RetroLoc: "Singapore in", Hostname: "sg-in.prod.surfshark.com", MultiHop: true},
|
||||||
{Region: "Asia Pacific", Country: "Singapore", City: "Singapore", RetroLoc: "Singapore mp001", Hostname: "sg-sng-mp001.prod.surfshark.com", MultiHop: false},
|
{Region: "Asia Pacific", Country: "Singapore", City: "Singapore", RetroLoc: "Singapore mp001", Hostname: "sg-sng-mp001.prod.surfshark.com", MultiHop: false},
|
||||||
|
{Region: "Asia Pacific", Country: "Singapore", City: "Singapore", RetroLoc: "Singapore st001", Hostname: "sg-sng-st001.prod.surfshark.com", MultiHop: false},
|
||||||
|
{Region: "Asia Pacific", Country: "Singapore", City: "Singapore", RetroLoc: "Singapore st002", Hostname: "sg-sng-st002.prod.surfshark.com", MultiHop: false},
|
||||||
|
{Region: "Asia Pacific", Country: "Singapore", City: "Singapore", RetroLoc: "Singapore st003", Hostname: "sg-sng-st003.prod.surfshark.com", MultiHop: false},
|
||||||
|
{Region: "Asia Pacific", Country: "Singapore", City: "Singapore", RetroLoc: "Singapore st004", Hostname: "sg-sng-st004.prod.surfshark.com", MultiHop: false},
|
||||||
{Region: "Asia Pacific", Country: "Singapore", City: "Singapore", Hostname: "sg-sng-st005.prod.surfshark.com"},
|
{Region: "Asia Pacific", Country: "Singapore", City: "Singapore", Hostname: "sg-sng-st005.prod.surfshark.com"},
|
||||||
{Region: "Asia Pacific", Country: "Singapore", City: "Singapore", Hostname: "sg-sng-st006.prod.surfshark.com"},
|
{Region: "Asia Pacific", Country: "Singapore", City: "Singapore", Hostname: "sg-sng-st006.prod.surfshark.com"},
|
||||||
{Region: "Asia Pacific", Country: "Singapore", City: "Singapore", Hostname: "sg-sng-st007.prod.surfshark.com"},
|
{Region: "Asia Pacific", Country: "Singapore", City: "Singapore", Hostname: "sg-sng-st007.prod.surfshark.com"},
|
||||||
@@ -78,8 +97,6 @@ func LocationData() (data []ServerLocation) {
|
|||||||
{Region: "Europe", Country: "Germany", City: "Frankfurt am Main", RetroLoc: "Germany Frankfurt am Main st003", Hostname: "de-fra-st003.prod.surfshark.com", MultiHop: false},
|
{Region: "Europe", Country: "Germany", City: "Frankfurt am Main", RetroLoc: "Germany Frankfurt am Main st003", Hostname: "de-fra-st003.prod.surfshark.com", MultiHop: false},
|
||||||
{Region: "Europe", Country: "Germany", City: "Frankfurt am Main", RetroLoc: "Germany Frankfurt am Main st004", Hostname: "de-fra-st004.prod.surfshark.com", MultiHop: false},
|
{Region: "Europe", Country: "Germany", City: "Frankfurt am Main", RetroLoc: "Germany Frankfurt am Main st004", Hostname: "de-fra-st004.prod.surfshark.com", MultiHop: false},
|
||||||
{Region: "Europe", Country: "Germany", City: "Frankfurt am Main", RetroLoc: "Germany Frankfurt am Main st005", Hostname: "de-fra-st005.prod.surfshark.com", MultiHop: false},
|
{Region: "Europe", Country: "Germany", City: "Frankfurt am Main", RetroLoc: "Germany Frankfurt am Main st005", Hostname: "de-fra-st005.prod.surfshark.com", MultiHop: false},
|
||||||
{Region: "Europe", Country: "Germany", City: "Frankfurt am Main", RetroLoc: "Germany Frankfurt am Main st006", Hostname: "de-fra-st006.prod.surfshark.com", MultiHop: false},
|
|
||||||
{Region: "Europe", Country: "Germany", City: "Frankfurt am Main", RetroLoc: "Germany Frankfurt am Main st007", Hostname: "de-fra-st007.prod.surfshark.com", MultiHop: false},
|
|
||||||
{Region: "Europe", Country: "Germany", City: "Frankfurt am Main", RetroLoc: "Germany Frankfurt am Main", Hostname: "de-fra.prod.surfshark.com", MultiHop: false},
|
{Region: "Europe", Country: "Germany", City: "Frankfurt am Main", RetroLoc: "Germany Frankfurt am Main", Hostname: "de-fra.prod.surfshark.com", MultiHop: false},
|
||||||
{Region: "Europe", Country: "Germany", City: "Frankfurt am Main", RetroLoc: "Germany Frankfurt mp001", Hostname: "de-fra-mp001.prod.surfshark.com", MultiHop: false},
|
{Region: "Europe", Country: "Germany", City: "Frankfurt am Main", RetroLoc: "Germany Frankfurt mp001", Hostname: "de-fra-mp001.prod.surfshark.com", MultiHop: false},
|
||||||
{Region: "Europe", Country: "Greece", City: "Athens", RetroLoc: "Greece", Hostname: "gr-ath.prod.surfshark.com", MultiHop: false},
|
{Region: "Europe", Country: "Greece", City: "Athens", RetroLoc: "Greece", Hostname: "gr-ath.prod.surfshark.com", MultiHop: false},
|
||||||
@@ -102,6 +119,7 @@ func LocationData() (data []ServerLocation) {
|
|||||||
{Region: "Europe", Country: "Portugal", City: "Lisbon", RetroLoc: "Portugal Lisbon", Hostname: "pt-lis.prod.surfshark.com", MultiHop: false},
|
{Region: "Europe", Country: "Portugal", City: "Lisbon", RetroLoc: "Portugal Lisbon", Hostname: "pt-lis.prod.surfshark.com", MultiHop: false},
|
||||||
{Region: "Europe", Country: "Portugal", City: "Porto", RetroLoc: "Portugal Porto", Hostname: "pt-opo.prod.surfshark.com", MultiHop: false},
|
{Region: "Europe", Country: "Portugal", City: "Porto", RetroLoc: "Portugal Porto", Hostname: "pt-opo.prod.surfshark.com", MultiHop: false},
|
||||||
{Region: "Europe", Country: "Romania", City: "Bucharest", RetroLoc: "Romania", Hostname: "ro-buc.prod.surfshark.com", MultiHop: false},
|
{Region: "Europe", Country: "Romania", City: "Bucharest", RetroLoc: "Romania", Hostname: "ro-buc.prod.surfshark.com", MultiHop: false},
|
||||||
|
{Region: "Europe", Country: "Russia", City: "Moscow", RetroLoc: "Russia Moscow", Hostname: "ru-mos.prod.surfshark.com", MultiHop: false},
|
||||||
{Region: "Europe", Country: "Serbia", City: "Belgrade", RetroLoc: "Serbia", Hostname: "rs-beg.prod.surfshark.com", MultiHop: false},
|
{Region: "Europe", Country: "Serbia", City: "Belgrade", RetroLoc: "Serbia", Hostname: "rs-beg.prod.surfshark.com", MultiHop: false},
|
||||||
{Region: "Europe", Country: "Singapore Netherlands", City: "", RetroLoc: "Singapore Netherlands", Hostname: "sg-nl.prod.surfshark.com", MultiHop: true},
|
{Region: "Europe", Country: "Singapore Netherlands", City: "", RetroLoc: "Singapore Netherlands", Hostname: "sg-nl.prod.surfshark.com", MultiHop: true},
|
||||||
{Region: "Europe", Country: "Slovakia", City: "Bratislava", RetroLoc: "Slovekia", Hostname: "sk-bts.prod.surfshark.com", MultiHop: false},
|
{Region: "Europe", Country: "Slovakia", City: "Bratislava", RetroLoc: "Slovekia", Hostname: "sk-bts.prod.surfshark.com", MultiHop: false},
|
||||||
@@ -157,6 +175,7 @@ func LocationData() (data []ServerLocation) {
|
|||||||
{Region: "The Americas", Country: "United States", City: "Las Vegas", RetroLoc: "US Las Vegas", Hostname: "us-las.prod.surfshark.com", MultiHop: false},
|
{Region: "The Americas", Country: "United States", City: "Las Vegas", RetroLoc: "US Las Vegas", Hostname: "us-las.prod.surfshark.com", MultiHop: false},
|
||||||
{Region: "The Americas", Country: "United States", City: "Latham", RetroLoc: "US Latham", Hostname: "us-ltm.prod.surfshark.com", MultiHop: false},
|
{Region: "The Americas", Country: "United States", City: "Latham", RetroLoc: "US Latham", Hostname: "us-ltm.prod.surfshark.com", MultiHop: false},
|
||||||
{Region: "The Americas", Country: "United States", City: "Los Angeles", RetroLoc: "US Los Angeles", Hostname: "us-lax.prod.surfshark.com", MultiHop: false},
|
{Region: "The Americas", Country: "United States", City: "Los Angeles", RetroLoc: "US Los Angeles", Hostname: "us-lax.prod.surfshark.com", MultiHop: false},
|
||||||
|
{Region: "The Americas", Country: "United States", City: "Manassas", RetroLoc: "US Maryland", Hostname: "us-mnz.prod.surfshark.com", MultiHop: false},
|
||||||
{Region: "The Americas", Country: "United States", City: "Miami", RetroLoc: "US Miami", Hostname: "us-mia.prod.surfshark.com", MultiHop: false},
|
{Region: "The Americas", Country: "United States", City: "Miami", RetroLoc: "US Miami", Hostname: "us-mia.prod.surfshark.com", MultiHop: false},
|
||||||
{Region: "The Americas", Country: "United States", City: "New York", RetroLoc: "US New York City mp001", Hostname: "us-nyc-mp001.prod.surfshark.com", MultiHop: false},
|
{Region: "The Americas", Country: "United States", City: "New York", RetroLoc: "US New York City mp001", Hostname: "us-nyc-mp001.prod.surfshark.com", MultiHop: false},
|
||||||
{Region: "The Americas", Country: "United States", City: "New York", RetroLoc: "US New York City st001", Hostname: "us-nyc-st001.prod.surfshark.com", MultiHop: false},
|
{Region: "The Americas", Country: "United States", City: "New York", RetroLoc: "US New York City st001", Hostname: "us-nyc-st001.prod.surfshark.com", MultiHop: false},
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import (
|
|||||||
"context"
|
"context"
|
||||||
"errors"
|
"errors"
|
||||||
"io"
|
"io"
|
||||||
|
"io/ioutil"
|
||||||
"net/http"
|
"net/http"
|
||||||
"strings"
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
@@ -33,7 +34,7 @@ func Test_addServersFromAPI(t *testing.T) {
|
|||||||
"existinghost": {Hostname: "existinghost"},
|
"existinghost": {Hostname: "existinghost"},
|
||||||
},
|
},
|
||||||
responseStatus: http.StatusOK,
|
responseStatus: http.StatusOK,
|
||||||
responseBody: io.NopCloser(strings.NewReader(`[
|
responseBody: ioutil.NopCloser(strings.NewReader(`[
|
||||||
{"connectionName":"host1","region":"region1","country":"country1","location":"location1"},
|
{"connectionName":"host1","region":"region1","country":"country1","location":"location1"},
|
||||||
{"connectionName":"host2","region":"region2","country":"country1","location":"location2"}
|
{"connectionName":"host2","region":"region2","country":"country1","location":"location2"}
|
||||||
]`)),
|
]`)),
|
||||||
@@ -110,12 +111,12 @@ func Test_fetchAPI(t *testing.T) {
|
|||||||
},
|
},
|
||||||
"no server": {
|
"no server": {
|
||||||
responseStatus: http.StatusOK,
|
responseStatus: http.StatusOK,
|
||||||
responseBody: io.NopCloser(strings.NewReader(`[]`)),
|
responseBody: ioutil.NopCloser(strings.NewReader(`[]`)),
|
||||||
data: []serverData{},
|
data: []serverData{},
|
||||||
},
|
},
|
||||||
"success": {
|
"success": {
|
||||||
responseStatus: http.StatusOK,
|
responseStatus: http.StatusOK,
|
||||||
responseBody: io.NopCloser(strings.NewReader(`[
|
responseBody: ioutil.NopCloser(strings.NewReader(`[
|
||||||
{"connectionName":"host1","region":"region1","country":"country1","location":"location1"},
|
{"connectionName":"host1","region":"region1","country":"country1","location":"location1"},
|
||||||
{"connectionName":"host2","region":"region2","country":"country1","location":"location2"}
|
{"connectionName":"host2","region":"region2","country":"country1","location":"location2"}
|
||||||
]`)),
|
]`)),
|
||||||
|
|||||||
@@ -40,10 +40,6 @@ func filterServer(server models.Server,
|
|||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
if *selection.PremiumOnly && !server.Premium {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
if *selection.StreamOnly && !server.Stream {
|
if *selection.StreamOnly && !server.Stream {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -88,19 +88,6 @@ func Test_FilterServers(t *testing.T) {
|
|||||||
{Free: true, VPN: vpn.OpenVPN, UDP: true},
|
{Free: true, VPN: vpn.OpenVPN, UDP: true},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
"filter by premium only": {
|
|
||||||
selection: settings.ServerSelection{
|
|
||||||
PremiumOnly: boolPtr(true),
|
|
||||||
}.WithDefaults(providers.Surfshark),
|
|
||||||
servers: []models.Server{
|
|
||||||
{Premium: false, VPN: vpn.OpenVPN, UDP: true},
|
|
||||||
{Premium: true, VPN: vpn.OpenVPN, UDP: true},
|
|
||||||
{Premium: false, VPN: vpn.OpenVPN, UDP: true},
|
|
||||||
},
|
|
||||||
filtered: []models.Server{
|
|
||||||
{Premium: true, VPN: vpn.OpenVPN, UDP: true},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
"filter by stream only": {
|
"filter by stream only": {
|
||||||
selection: settings.ServerSelection{
|
selection: settings.ServerSelection{
|
||||||
StreamOnly: boolPtr(true),
|
StreamOnly: boolPtr(true),
|
||||||
|
|||||||
@@ -188,17 +188,12 @@ func OpenVPNConfig(provider OpenVPNProviderSettings,
|
|||||||
lines.addLines(WrapOpenvpnTLSCrypt(provider.TLSCrypt))
|
lines.addLines(WrapOpenvpnTLSCrypt(provider.TLSCrypt))
|
||||||
}
|
}
|
||||||
|
|
||||||
if *settings.EncryptedKey != "" {
|
if *settings.ClientCrt != "" {
|
||||||
lines.add("askpass", openvpn.AskPassPath)
|
lines.addLines(WrapOpenvpnCert(*settings.ClientCrt))
|
||||||
lines.addLines(WrapOpenvpnEncryptedKey(*settings.EncryptedKey))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if *settings.Cert != "" {
|
if *settings.ClientKey != "" {
|
||||||
lines.addLines(WrapOpenvpnCert(*settings.Cert))
|
lines.addLines(WrapOpenvpnKey(*settings.ClientKey))
|
||||||
}
|
|
||||||
|
|
||||||
if *settings.Key != "" {
|
|
||||||
lines.addLines(WrapOpenvpnKey(*settings.Key))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
lines.addLines(provider.ExtraLines)
|
lines.addLines(provider.ExtraLines)
|
||||||
@@ -287,16 +282,6 @@ func WrapOpenvpnKey(clientKey string) (lines []string) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func WrapOpenvpnEncryptedKey(encryptedKey string) (lines []string) {
|
|
||||||
return []string{
|
|
||||||
"<key>",
|
|
||||||
"-----BEGIN ENCRYPTED PRIVATE KEY-----",
|
|
||||||
encryptedKey,
|
|
||||||
"-----END ENCRYPTED PRIVATE KEY-----",
|
|
||||||
"</key>",
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func WrapOpenvpnRSAKey(rsaPrivateKey string) (lines []string) {
|
func WrapOpenvpnRSAKey(rsaPrivateKey string) (lines []string) {
|
||||||
return []string{
|
return []string{
|
||||||
"<key>",
|
"<key>",
|
||||||
|
|||||||
@@ -1,14 +0,0 @@
|
|||||||
package vpnsecure
|
|
||||||
|
|
||||||
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) (
|
|
||||||
connection models.Connection, err error) {
|
|
||||||
defaults := utils.NewConnectionDefaults(110, 1282, 0) //nolint:gomnd
|
|
||||||
return utils.GetConnection(p.Name(),
|
|
||||||
p.storage, selection, defaults, p.randSource)
|
|
||||||
}
|
|
||||||
@@ -1,26 +0,0 @@
|
|||||||
package vpnsecure
|
|
||||||
|
|
||||||
import (
|
|
||||||
"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) (lines []string) {
|
|
||||||
//nolint:gomnd
|
|
||||||
providerSettings := utils.OpenVPNProviderSettings{
|
|
||||||
RemoteCertTLS: true,
|
|
||||||
AuthUserPass: true,
|
|
||||||
Ping: 10,
|
|
||||||
// note DES-CBC is not added since it's quite unsecure
|
|
||||||
Ciphers: []string{openvpn.AES256cbc, openvpn.AES128cbc},
|
|
||||||
ExtraLines: []string{
|
|
||||||
"comp-lzo",
|
|
||||||
"float",
|
|
||||||
},
|
|
||||||
CA: "MIIEJjCCAw6gAwIBAgIJAMkzh6p4m6XfMA0GCSqGSIb3DQEBCwUAMGkxCzAJBgNVBAYTAlVTMQswCQYDVQQIEwJOWTERMA8GA1UEBxMITmV3IFlvcmsxFTATBgNVBAoTDHZwbnNlY3VyZS5tZTEjMCEGCSqGSIb3DQEJARYUc3VwcG9ydEB2cG5zZWN1cmUubWUwIBcNMTcwNTA2MTMzMTQyWhgPMjkzODA4MjYxMzMxNDJaMGkxCzAJBgNVBAYTAlVTMQswCQYDVQQIEwJOWTERMA8GA1UEBxMITmV3IFlvcmsxFTATBgNVBAoTDHZwbnNlY3VyZS5tZTEjMCEGCSqGSIb3DQEJARYUc3VwcG9ydEB2cG5zZWN1cmUubWUwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDiClT1wcZ6oovYjSxUJIQplrBSQRKB44uymC8evohzK7q67x0NE2sLz5Zn9ZiC7RnXQCtEqJfHqjuqjaH5MghjhUDnRbZS/8ElxdGKn9FPvs9b+aTVGSfrQm5KKoVigwAye3ilNiWAyy6MDlBeoKluQ4xW7SGiVZRxLcJbLAmjmfCjBS7eUGbtA8riTkIegFo4WFiy9G76zQWw1V26kDhyzcJNT4xO7USMPUeZthy13g+zi9+rcILhEAnl776sIil6w8UVK8xevFKBlOPk+YyXlo4eZiuppq300ogaS+fX/0mfD7DDE+Gk5/nCeACDNiBlfQ3ol/De8Cm60HWEUtZVAgMBAAGjgc4wgcswHQYDVR0OBBYEFBJyf4mpGT3dIu65/1zAFqCgGxZoMIGbBgNVHSMEgZMwgZCAFBJyf4mpGT3dIu65/1zAFqCgGxZooW2kazBpMQswCQYDVQQGEwJVUzELMAkGA1UECBMCTlkxETAPBgNVBAcTCE5ldyBZb3JrMRUwEwYDVQQKEwx2cG5zZWN1cmUubWUxIzAhBgkqhkiG9w0BCQEWFHN1cHBvcnRAdnBuc2VjdXJlLm1lggkAyTOHqnibpd8wDAYDVR0TBAUwAwEB/zANBgkqhkiG9w0BAQsFAAOCAQEArbTAibGQilY4Lu2RAVPjNx14SfojueBroeN7NIpAFUfbifPQRWvLamzRfxFTO0PXRc2pw/It7oa8yM7BsZj0vOiZY2p1JBHZwKom6tiSUVENDGW6JaYtiaE8XPyjfA5Yhfx4FefmaJ1veDYid18S+VVpt+Y+UIUxNmg1JB3CCUwbjl+dWlcvDBy4+jI+sZ7A1LF3uX64ZucDQ/XrpuopHhvDjw7g1PpKXsRqBYL+cpxUI7GrINBa/rGvXqv/NvFH8bguggknWKxKhd+jyMqkW3Ws258e0OwHz7gQ+tTJ909tR0TxJhZGkHatNSbpwW1Y52A972+9gYJMadSfm4bUHA==", //nolint:lll
|
|
||||||
}
|
|
||||||
return utils.OpenVPNConfig(providerSettings, connection, settings)
|
|
||||||
}
|
|
||||||
@@ -1,33 +0,0 @@
|
|||||||
package vpnsecure
|
|
||||||
|
|
||||||
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/utils"
|
|
||||||
"github.com/qdm12/gluetun/internal/provider/vpnsecure/updater"
|
|
||||||
)
|
|
||||||
|
|
||||||
type Provider struct {
|
|
||||||
storage common.Storage
|
|
||||||
randSource rand.Source
|
|
||||||
utils.NoPortForwarder
|
|
||||||
common.Fetcher
|
|
||||||
}
|
|
||||||
|
|
||||||
func New(storage common.Storage, randSource rand.Source,
|
|
||||||
client *http.Client, updaterWarner common.Warner,
|
|
||||||
parallelResolver common.ParallelResolver) *Provider {
|
|
||||||
return &Provider{
|
|
||||||
storage: storage,
|
|
||||||
randSource: randSource,
|
|
||||||
NoPortForwarder: utils.NewNoPortForwarding(providers.VPNSecure),
|
|
||||||
Fetcher: updater.New(client, updaterWarner, parallelResolver),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *Provider) Name() string {
|
|
||||||
return providers.VPNSecure
|
|
||||||
}
|
|
||||||
@@ -1,26 +0,0 @@
|
|||||||
package updater
|
|
||||||
|
|
||||||
import (
|
|
||||||
"os"
|
|
||||||
"strings"
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
"github.com/stretchr/testify/require"
|
|
||||||
"golang.org/x/net/html"
|
|
||||||
)
|
|
||||||
|
|
||||||
func parseTestHTML(t *testing.T, htmlString string) *html.Node {
|
|
||||||
t.Helper()
|
|
||||||
rootNode, err := html.Parse(strings.NewReader(htmlString))
|
|
||||||
require.NoError(t, err)
|
|
||||||
return rootNode
|
|
||||||
}
|
|
||||||
|
|
||||||
func parseTestDataIndexHTML(t *testing.T) *html.Node {
|
|
||||||
t.Helper()
|
|
||||||
|
|
||||||
data, err := os.ReadFile("testdata/index.html")
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
return parseTestHTML(t, string(data))
|
|
||||||
}
|
|
||||||
@@ -1,38 +0,0 @@
|
|||||||
package updater
|
|
||||||
|
|
||||||
import (
|
|
||||||
"net"
|
|
||||||
|
|
||||||
"github.com/qdm12/gluetun/internal/models"
|
|
||||||
)
|
|
||||||
|
|
||||||
type hostToServer map[string]models.Server
|
|
||||||
|
|
||||||
func (hts hostToServer) toHostsSlice() (hosts []string) {
|
|
||||||
hosts = make([]string, 0, len(hts))
|
|
||||||
for host := range hts {
|
|
||||||
hosts = append(hosts, host)
|
|
||||||
}
|
|
||||||
return hosts
|
|
||||||
}
|
|
||||||
|
|
||||||
func (hts hostToServer) adaptWithIPs(hostToIPs map[string][]net.IP) {
|
|
||||||
for host, IPs := range hostToIPs {
|
|
||||||
server := hts[host]
|
|
||||||
server.IPs = IPs
|
|
||||||
hts[host] = server
|
|
||||||
}
|
|
||||||
for host, server := range hts {
|
|
||||||
if len(server.IPs) == 0 {
|
|
||||||
delete(hts, host)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (hts hostToServer) toServersSlice() (servers []models.Server) {
|
|
||||||
servers = make([]models.Server, 0, len(hts))
|
|
||||||
for _, server := range hts {
|
|
||||||
servers = append(servers, server)
|
|
||||||
}
|
|
||||||
return servers
|
|
||||||
}
|
|
||||||
@@ -1,26 +0,0 @@
|
|||||||
package updater
|
|
||||||
|
|
||||||
import (
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/qdm12/gluetun/internal/updater/resolver"
|
|
||||||
)
|
|
||||||
|
|
||||||
func parallelResolverSettings(hosts []string) (settings resolver.ParallelSettings) {
|
|
||||||
const (
|
|
||||||
maxDuration = 5 * time.Second
|
|
||||||
maxFailRatio = 0.1
|
|
||||||
maxNoNew = 2
|
|
||||||
maxFails = 3
|
|
||||||
)
|
|
||||||
return resolver.ParallelSettings{
|
|
||||||
Hosts: hosts,
|
|
||||||
MaxFailRatio: maxFailRatio,
|
|
||||||
Repeat: resolver.RepeatSettings{
|
|
||||||
MaxDuration: maxDuration,
|
|
||||||
MaxNoNew: maxNoNew,
|
|
||||||
MaxFails: maxFails,
|
|
||||||
SortIPs: true,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,57 +0,0 @@
|
|||||||
package updater
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"fmt"
|
|
||||||
"sort"
|
|
||||||
|
|
||||||
"github.com/qdm12/gluetun/internal/constants/vpn"
|
|
||||||
"github.com/qdm12/gluetun/internal/models"
|
|
||||||
"github.com/qdm12/gluetun/internal/provider/common"
|
|
||||||
)
|
|
||||||
|
|
||||||
func (u *Updater) FetchServers(ctx context.Context, minServers int) (
|
|
||||||
servers []models.Server, err error) {
|
|
||||||
servers, err = fetchServers(ctx, u.client, u.warner)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("cannot fetch servers: %w", err)
|
|
||||||
} else if len(servers) < minServers {
|
|
||||||
return nil, fmt.Errorf("%w: %d and expected at least %d",
|
|
||||||
common.ErrNotEnoughServers, len(servers), minServers)
|
|
||||||
}
|
|
||||||
|
|
||||||
hts := make(hostToServer, len(servers))
|
|
||||||
for _, server := range servers {
|
|
||||||
hts[server.Hostname] = server
|
|
||||||
}
|
|
||||||
|
|
||||||
hosts := hts.toHostsSlice()
|
|
||||||
|
|
||||||
resolveSettings := parallelResolverSettings(hosts)
|
|
||||||
hostToIPs, warnings, err := u.parallelResolver.Resolve(ctx, resolveSettings)
|
|
||||||
for _, warning := range warnings {
|
|
||||||
u.warner.Warn(warning)
|
|
||||||
}
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(hostToIPs) < minServers {
|
|
||||||
return nil, fmt.Errorf("%w: %d and expected at least %d",
|
|
||||||
common.ErrNotEnoughServers, len(servers), minServers)
|
|
||||||
}
|
|
||||||
|
|
||||||
hts.adaptWithIPs(hostToIPs)
|
|
||||||
|
|
||||||
servers = hts.toServersSlice()
|
|
||||||
|
|
||||||
for i := range servers {
|
|
||||||
servers[i].VPN = vpn.OpenVPN
|
|
||||||
servers[i].UDP = true
|
|
||||||
servers[i].TCP = true
|
|
||||||
}
|
|
||||||
|
|
||||||
sort.Sort(models.SortableServers(servers))
|
|
||||||
|
|
||||||
return servers, nil
|
|
||||||
}
|
|
||||||
7345
internal/provider/vpnsecure/updater/testdata/index.html
vendored
7345
internal/provider/vpnsecure/updater/testdata/index.html
vendored
File diff suppressed because one or more lines are too long
@@ -1,22 +0,0 @@
|
|||||||
package updater
|
|
||||||
|
|
||||||
import (
|
|
||||||
"net/http"
|
|
||||||
|
|
||||||
"github.com/qdm12/gluetun/internal/provider/common"
|
|
||||||
)
|
|
||||||
|
|
||||||
type Updater struct {
|
|
||||||
client *http.Client
|
|
||||||
parallelResolver common.ParallelResolver
|
|
||||||
warner common.Warner
|
|
||||||
}
|
|
||||||
|
|
||||||
func New(client *http.Client, warner common.Warner,
|
|
||||||
parallelResolver common.ParallelResolver) *Updater {
|
|
||||||
return &Updater{
|
|
||||||
client: client,
|
|
||||||
parallelResolver: parallelResolver,
|
|
||||||
warner: warner,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,239 +0,0 @@
|
|||||||
package updater
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"errors"
|
|
||||||
"fmt"
|
|
||||||
"net/http"
|
|
||||||
"strings"
|
|
||||||
|
|
||||||
"github.com/qdm12/gluetun/internal/models"
|
|
||||||
"github.com/qdm12/gluetun/internal/provider/common"
|
|
||||||
htmlutils "github.com/qdm12/gluetun/internal/updater/html"
|
|
||||||
"golang.org/x/net/html"
|
|
||||||
)
|
|
||||||
|
|
||||||
func fetchServers(ctx context.Context, client *http.Client,
|
|
||||||
warner common.Warner) (servers []models.Server, err error) {
|
|
||||||
const url = "https://www.vpnsecure.me/vpn-locations/"
|
|
||||||
rootNode, err := htmlutils.Fetch(ctx, client, url)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("fetching HTML code: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
servers, warnings, err := parseHTML(rootNode)
|
|
||||||
for _, warning := range warnings {
|
|
||||||
warner.Warn(warning)
|
|
||||||
}
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("parsing HTML code: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
return servers, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
var (
|
|
||||||
ErrHTMLServersDivNotFound = errors.New("HTML servers container div not found")
|
|
||||||
)
|
|
||||||
|
|
||||||
const divString = "div"
|
|
||||||
|
|
||||||
func parseHTML(rootNode *html.Node) (servers []models.Server,
|
|
||||||
warnings []string, err error) {
|
|
||||||
// Find div container for all servers, searching with BFS.
|
|
||||||
serversDiv := findServersDiv(rootNode)
|
|
||||||
if serversDiv == nil {
|
|
||||||
return nil, nil, htmlutils.WrapError(ErrHTMLServersDivNotFound, rootNode)
|
|
||||||
}
|
|
||||||
|
|
||||||
for countryNode := serversDiv.FirstChild; countryNode != nil; countryNode = countryNode.NextSibling {
|
|
||||||
if countryNode.Data != divString {
|
|
||||||
// empty line(s) and tab(s)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
country := findCountry(countryNode)
|
|
||||||
if country == "" {
|
|
||||||
warnings = append(warnings, htmlutils.WrapWarning("country not found", countryNode))
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
grid := htmlutils.BFS(countryNode, matchGridDiv)
|
|
||||||
if grid == nil {
|
|
||||||
warnings = append(warnings, htmlutils.WrapWarning("grid div not found", countryNode))
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
gridItems := htmlutils.DirectChildren(grid, matchGridItem)
|
|
||||||
if len(gridItems) == 0 {
|
|
||||||
warnings = append(warnings, htmlutils.WrapWarning("no grid item found", grid))
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, gridItem := range gridItems {
|
|
||||||
server, warning := parseHTMLGridItem(gridItem)
|
|
||||||
if warning != "" {
|
|
||||||
warnings = append(warnings, warning)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
server.Country = country
|
|
||||||
servers = append(servers, server)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return servers, warnings, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func parseHTMLGridItem(gridItem *html.Node) (
|
|
||||||
server models.Server, warning string) {
|
|
||||||
gridItemDT := htmlutils.DirectChild(gridItem, matchDT)
|
|
||||||
if gridItemDT == nil {
|
|
||||||
return server, htmlutils.WrapWarning("grid item <dt> not found", gridItem)
|
|
||||||
}
|
|
||||||
|
|
||||||
host := findHost(gridItemDT)
|
|
||||||
if host == "" {
|
|
||||||
return server, htmlutils.WrapWarning("host not found", gridItemDT)
|
|
||||||
}
|
|
||||||
|
|
||||||
status := findStatus(gridItemDT)
|
|
||||||
if !strings.EqualFold(status, "up") {
|
|
||||||
warning := fmt.Sprintf("skipping server with host %s which has status %q", host, status)
|
|
||||||
warning = htmlutils.WrapWarning(warning, gridItemDT)
|
|
||||||
return server, warning
|
|
||||||
}
|
|
||||||
|
|
||||||
gridItemDD := htmlutils.DirectChild(gridItem, matchDD)
|
|
||||||
if gridItemDD == nil {
|
|
||||||
return server, htmlutils.WrapWarning("grid item dd not found", gridItem)
|
|
||||||
}
|
|
||||||
|
|
||||||
region := findSpanStrong(gridItemDD, "Region:")
|
|
||||||
if region == "" {
|
|
||||||
warning := fmt.Sprintf("region for host %s not found", host)
|
|
||||||
return server, htmlutils.WrapWarning(warning, gridItemDD)
|
|
||||||
}
|
|
||||||
|
|
||||||
city := findSpanStrong(gridItemDD, "City:")
|
|
||||||
if city == "" {
|
|
||||||
warning := fmt.Sprintf("region for host %s not found", host)
|
|
||||||
return server, htmlutils.WrapWarning(warning, gridItemDD)
|
|
||||||
}
|
|
||||||
|
|
||||||
premiumString := findSpanStrong(gridItemDD, "Premium:")
|
|
||||||
if premiumString == "" {
|
|
||||||
warning := fmt.Sprintf("premium for host %s not found", host)
|
|
||||||
return server, htmlutils.WrapWarning(warning, gridItemDD)
|
|
||||||
}
|
|
||||||
|
|
||||||
return models.Server{
|
|
||||||
Region: region,
|
|
||||||
City: city,
|
|
||||||
Hostname: host + ".isponeder.com",
|
|
||||||
Premium: strings.EqualFold(premiumString, "yes"),
|
|
||||||
}, ""
|
|
||||||
}
|
|
||||||
|
|
||||||
func findCountry(countryNode *html.Node) (country string) {
|
|
||||||
for node := countryNode.FirstChild; node != nil; node = node.NextSibling {
|
|
||||||
if node.Data != "a" {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
for subNode := node.FirstChild; subNode != nil; subNode = subNode.NextSibling {
|
|
||||||
if subNode.Data != "h4" {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
return subNode.FirstChild.Data
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return ""
|
|
||||||
}
|
|
||||||
|
|
||||||
func findServersDiv(rootNode *html.Node) (serversDiv *html.Node) {
|
|
||||||
locationsDiv := htmlutils.BFS(rootNode, matchLocationsListDiv)
|
|
||||||
if locationsDiv == nil {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
return htmlutils.BFS(locationsDiv, matchServersDiv)
|
|
||||||
}
|
|
||||||
|
|
||||||
func findHost(gridItemDT *html.Node) (host string) {
|
|
||||||
hostNode := htmlutils.DirectChild(gridItemDT, matchText)
|
|
||||||
return strings.TrimSpace(hostNode.Data)
|
|
||||||
}
|
|
||||||
|
|
||||||
func matchText(node *html.Node) (match bool) {
|
|
||||||
if node.Type != html.TextNode {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
data := strings.TrimSpace(node.Data)
|
|
||||||
return data != ""
|
|
||||||
}
|
|
||||||
|
|
||||||
func findStatus(gridItemDT *html.Node) (status string) {
|
|
||||||
statusNode := htmlutils.DirectChild(gridItemDT, matchStatusSpan)
|
|
||||||
return strings.TrimSpace(statusNode.FirstChild.Data)
|
|
||||||
}
|
|
||||||
|
|
||||||
func matchServersDiv(node *html.Node) (match bool) {
|
|
||||||
return node != nil && node.Data == divString &&
|
|
||||||
htmlutils.HasClassStrings(node, "blk__i")
|
|
||||||
}
|
|
||||||
|
|
||||||
func matchLocationsListDiv(node *html.Node) (match bool) {
|
|
||||||
return node != nil && node.Data == divString &&
|
|
||||||
htmlutils.HasClassStrings(node, "locations-list")
|
|
||||||
}
|
|
||||||
|
|
||||||
func matchGridDiv(node *html.Node) (match bool) {
|
|
||||||
return node != nil && node.Data == divString &&
|
|
||||||
htmlutils.HasClassStrings(node, "grid--locations")
|
|
||||||
}
|
|
||||||
|
|
||||||
func matchGridItem(node *html.Node) (match bool) {
|
|
||||||
return node != nil && node.Data == "dl" &&
|
|
||||||
htmlutils.HasClassStrings(node, "grid__i")
|
|
||||||
}
|
|
||||||
|
|
||||||
func matchDT(node *html.Node) (match bool) {
|
|
||||||
return node != nil && node.Data == "dt"
|
|
||||||
}
|
|
||||||
|
|
||||||
func matchDD(node *html.Node) (match bool) {
|
|
||||||
return node != nil && node.Data == "dd"
|
|
||||||
}
|
|
||||||
|
|
||||||
func matchStatusSpan(node *html.Node) (match bool) {
|
|
||||||
return node.Data == "span" && htmlutils.HasClassStrings(node, "status")
|
|
||||||
}
|
|
||||||
|
|
||||||
func findSpanStrong(gridItemDD *html.Node, spanData string) (
|
|
||||||
strongValue string) {
|
|
||||||
spanFound := false
|
|
||||||
for child := gridItemDD.FirstChild; child != nil; child = child.NextSibling {
|
|
||||||
if !htmlutils.MatchData("div")(child) {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
for subchild := child.FirstChild; subchild != nil; subchild = subchild.NextSibling {
|
|
||||||
if htmlutils.MatchData("span")(subchild) && subchild.FirstChild.Data == spanData {
|
|
||||||
spanFound = true
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if !spanFound {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
for subchild := child.FirstChild; subchild != nil; subchild = subchild.NextSibling {
|
|
||||||
if htmlutils.MatchData("strong")(subchild) {
|
|
||||||
return subchild.FirstChild.Data
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return ""
|
|
||||||
}
|
|
||||||
@@ -1,234 +0,0 @@
|
|||||||
package updater
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"io"
|
|
||||||
"net/http"
|
|
||||||
"strings"
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
"github.com/golang/mock/gomock"
|
|
||||||
"github.com/qdm12/gluetun/internal/models"
|
|
||||||
"github.com/qdm12/gluetun/internal/provider/common"
|
|
||||||
"github.com/stretchr/testify/assert"
|
|
||||||
"golang.org/x/net/html"
|
|
||||||
)
|
|
||||||
|
|
||||||
type roundTripFunc func(r *http.Request) (*http.Response, error)
|
|
||||||
|
|
||||||
func (f roundTripFunc) RoundTrip(r *http.Request) (*http.Response, error) {
|
|
||||||
return f(r)
|
|
||||||
}
|
|
||||||
|
|
||||||
func Test_fetchServers(t *testing.T) {
|
|
||||||
t.Parallel()
|
|
||||||
|
|
||||||
canceledCtx, cancel := context.WithCancel(context.Background())
|
|
||||||
cancel()
|
|
||||||
|
|
||||||
testCases := map[string]struct {
|
|
||||||
ctx context.Context
|
|
||||||
responseStatus int
|
|
||||||
responseBody io.ReadCloser
|
|
||||||
servers []models.Server
|
|
||||||
errWrapped error
|
|
||||||
errMessage string
|
|
||||||
}{
|
|
||||||
"context canceled": {
|
|
||||||
ctx: canceledCtx,
|
|
||||||
errWrapped: context.Canceled,
|
|
||||||
errMessage: `fetching HTML code: Get "https://www.vpnsecure.me/vpn-locations/": context canceled`,
|
|
||||||
},
|
|
||||||
"success": {
|
|
||||||
ctx: context.Background(),
|
|
||||||
responseStatus: http.StatusOK,
|
|
||||||
responseBody: io.NopCloser(strings.NewReader(`
|
|
||||||
<div class="blk blk--white locations-list">
|
|
||||||
<div class="blk__i">
|
|
||||||
<div>
|
|
||||||
<a href="https://www.vpnsecure.me/vpn-locations/australia/">
|
|
||||||
<h4>Australia</h4>
|
|
||||||
</a>
|
|
||||||
<div class="grid grid--3 grid--locations">
|
|
||||||
<dl class="grid__i">
|
|
||||||
<dt>
|
|
||||||
au1
|
|
||||||
<span class="status status--up">up</span>
|
|
||||||
</dt>
|
|
||||||
<dd>
|
|
||||||
<div><span>City:</span> <strong>City</strong></div>
|
|
||||||
<div><span>Region:</span> <strong>Region</strong></div>
|
|
||||||
<div><span>Premium:</span> <strong>YES</strong></div>
|
|
||||||
|
|
||||||
</dd>
|
|
||||||
</dl>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
`)),
|
|
||||||
servers: []models.Server{
|
|
||||||
{
|
|
||||||
Country: "Australia",
|
|
||||||
City: "City",
|
|
||||||
Region: "Region",
|
|
||||||
Hostname: "au1.isponeder.com",
|
|
||||||
Premium: true,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
for name, testCase := range testCases {
|
|
||||||
testCase := testCase
|
|
||||||
t.Run(name, func(t *testing.T) {
|
|
||||||
t.Parallel()
|
|
||||||
ctrl := gomock.NewController(t)
|
|
||||||
|
|
||||||
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.vpnsecure.me/vpn-locations/")
|
|
||||||
|
|
||||||
ctxErr := r.Context().Err()
|
|
||||||
if ctxErr != nil {
|
|
||||||
return nil, ctxErr
|
|
||||||
}
|
|
||||||
|
|
||||||
return &http.Response{
|
|
||||||
StatusCode: http.StatusOK,
|
|
||||||
Status: http.StatusText(testCase.responseStatus),
|
|
||||||
Body: testCase.responseBody,
|
|
||||||
}, nil
|
|
||||||
}),
|
|
||||||
}
|
|
||||||
|
|
||||||
warner := common.NewMockWarner(ctrl)
|
|
||||||
|
|
||||||
servers, err := fetchServers(testCase.ctx, client, warner)
|
|
||||||
|
|
||||||
assert.ErrorIs(t, err, testCase.errWrapped)
|
|
||||||
if testCase.errWrapped != nil {
|
|
||||||
assert.EqualError(t, err, testCase.errMessage)
|
|
||||||
}
|
|
||||||
assert.Equal(t, testCase.servers, servers)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func Test_parseHTML(t *testing.T) {
|
|
||||||
t.Parallel()
|
|
||||||
|
|
||||||
testCases := map[string]struct {
|
|
||||||
rootNode *html.Node
|
|
||||||
servers []models.Server
|
|
||||||
warnings []string
|
|
||||||
errWrapped error
|
|
||||||
errMessage string
|
|
||||||
}{
|
|
||||||
"empty html": {
|
|
||||||
rootNode: parseTestHTML(t, ""),
|
|
||||||
errWrapped: ErrHTMLServersDivNotFound,
|
|
||||||
errMessage: `HTML servers container div not found: in HTML code: <html><head></head><body></body></html>`,
|
|
||||||
},
|
|
||||||
"test data": {
|
|
||||||
rootNode: parseTestDataIndexHTML(t),
|
|
||||||
warnings: []string{
|
|
||||||
"no grid item found: in HTML code: <div class=\"grid grid--3 grid--locations\">\n </div>",
|
|
||||||
},
|
|
||||||
//nolint:lll
|
|
||||||
servers: []models.Server{
|
|
||||||
{Country: "Australia", Region: "Queensland", City: "Brisbane", Hostname: "au1.isponeder.com", Premium: true},
|
|
||||||
{Country: "Australia", Region: "New South Wales", City: "Sydney", Hostname: "au2.isponeder.com"},
|
|
||||||
{Country: "Australia", Region: "New South Wales", City: "Sydney", Hostname: "au3.isponeder.com"},
|
|
||||||
{Country: "Australia", Region: "New South Wales", City: "Sydney", Hostname: "au4.isponeder.com", Premium: true},
|
|
||||||
{Country: "Austria", Region: "Vienna", City: "Vienna", Hostname: "at1.isponeder.com", Premium: true},
|
|
||||||
{Country: "Austria", Region: "Vienna", City: "Vienna", Hostname: "at2.isponeder.com"},
|
|
||||||
{Country: "Brazil", Region: "Sao Paulo", City: "Sao Paulo", Hostname: "br1.isponeder.com", Premium: true},
|
|
||||||
{Country: "Belgium", Region: "Flanders", City: "Zaventem", Hostname: "be1.isponeder.com"},
|
|
||||||
{Country: "Belgium", Region: "Brussels Hoofdstedelijk Gewest", City: "Brussel", Hostname: "be2.isponeder.com"},
|
|
||||||
{Country: "Canada", Region: "Ontario", City: "Richmond Hill", Hostname: "ca1.isponeder.com"},
|
|
||||||
{Country: "Canada", Region: "Ontario", City: "Richmond Hill", Hostname: "ca2.isponeder.com"},
|
|
||||||
{Country: "Canada", Region: "Quebec", City: "Montréal", Hostname: "ca3.isponeder.com", Premium: true},
|
|
||||||
{Country: "Denmark", Region: "Capital Region", City: "Copenhagen", Hostname: "dk1.isponeder.com", Premium: true},
|
|
||||||
{Country: "Denmark", Region: "Capital Region", City: "Copenhagen", Hostname: "dk2.isponeder.com", Premium: true},
|
|
||||||
{Country: "Denmark", Region: "Capital Region", City: "Ballerup", Hostname: "dk3.isponeder.com"},
|
|
||||||
{Country: "France", Region: "Île-de-France", City: "Paris", Hostname: "fr1.isponeder.com"},
|
|
||||||
{Country: "France", Region: "Île-de-France", City: "Paris", Hostname: "fr2.isponeder.com"},
|
|
||||||
{Country: "France", Region: "Grand Est", City: "Strasbourg", Hostname: "fr3.isponeder.com"},
|
|
||||||
{Country: "Germany", Region: "Hesse", City: "Frankfurt am Main", Hostname: "de1.isponeder.com"},
|
|
||||||
{Country: "Germany", Region: "Hesse", City: "Frankfurt am Main", Hostname: "de2.isponeder.com"},
|
|
||||||
{Country: "Germany", Region: "Hesse", City: "Frankfurt am Main", Hostname: "de3.isponeder.com"},
|
|
||||||
{Country: "Germany", Region: "Hesse", City: "Frankfurt am Main", Hostname: "de4.isponeder.com"},
|
|
||||||
{Country: "Germany", Region: "Hesse", City: "Limburg an der Lahn", Hostname: "de5.isponeder.com"},
|
|
||||||
{Country: "Germany", Region: "Hesse", City: "Frankfurt am Main", Hostname: "de6.isponeder.com"},
|
|
||||||
{Country: "Hungary", Region: "Budapest", City: "Budapest", Hostname: "hu1.isponeder.com", Premium: true},
|
|
||||||
{Country: "India", Region: "Karnataka", City: "Doddaballapura", Hostname: "in1.isponeder.com"},
|
|
||||||
{Country: "Indonesia", Region: "Special Capital Region of Jakarta", City: "Jakarta", Hostname: "id1.isponeder.com"},
|
|
||||||
{Country: "Ireland", Region: "Dublin City", City: "Dublin", Hostname: "ie1.isponeder.com"},
|
|
||||||
{Country: "Israel", Region: "Tel Aviv", City: "Tel Aviv", Hostname: "il1.isponeder.com", Premium: true},
|
|
||||||
{Country: "Italy", Region: "Lombardy", City: "Milan", Hostname: "it1.isponeder.com", Premium: true},
|
|
||||||
{Country: "Japan", Region: "Tokyo", City: "Tokyo", Hostname: "jp2.isponeder.com", Premium: true},
|
|
||||||
{Country: "Mexico", Region: "México", City: "Ampliación San Mateo (Colonia Solidaridad)", Hostname: "mx1.isponeder.com"},
|
|
||||||
{Country: "Netherlands", Region: "North Holland", City: "Haarlem", Hostname: "nl1.isponeder.com"},
|
|
||||||
{Country: "Netherlands", Region: "South Holland", City: "Naaldwijk", Hostname: "nl2.isponeder.com"},
|
|
||||||
{Country: "New Zealand", Region: "Auckland", City: "Auckland", Hostname: "nz1.isponeder.com"},
|
|
||||||
{Country: "Norway", Region: "Oslo", City: "Oslo", Hostname: "no1.isponeder.com", Premium: true},
|
|
||||||
{Country: "Norway", Region: "Stockholm", City: "Stockholm", Hostname: "no2.isponeder.com", Premium: true},
|
|
||||||
{Country: "Poland", Region: "Mazovia", City: "Warsaw", Hostname: "pl1.isponeder.com", Premium: true},
|
|
||||||
{Country: "Romania", Region: "Bucure?ti", City: "Bucharest", Hostname: "ro1.isponeder.com", Premium: true},
|
|
||||||
{Country: "Russia", Region: "Moscow", City: "Moscow", Hostname: "ru1.isponeder.com", Premium: true},
|
|
||||||
{Country: "Singapore", Region: "Singapore", City: "Singapore", Hostname: "sg1.isponeder.com", Premium: true},
|
|
||||||
{Country: "South Africa", Region: "Western Cape", City: "Cape Town", Hostname: "za1.isponeder.com", Premium: true},
|
|
||||||
{Country: "Spain", Region: "Madrid", City: "Madrid", Hostname: "es2.isponeder.com"},
|
|
||||||
{Country: "Spain", Region: "Valencia", City: "Valencia", Hostname: "se1.isponeder.com"},
|
|
||||||
{Country: "Sweden", Region: "Stockholm", City: "Stockholm", Hostname: "se2.isponeder.com", Premium: true},
|
|
||||||
{Country: "Sweden", Region: "Stockholm", City: "Stockholm", Hostname: "se3.isponeder.com"},
|
|
||||||
{Country: "Switzerland", Region: "Vaud", City: "Lausanne", Hostname: "ch1.isponeder.com"},
|
|
||||||
{Country: "Switzerland", Region: "Geneva", City: "Geneva", Hostname: "ch1.isponeder.com", Premium: true},
|
|
||||||
{Country: "Switzerland", Region: "Geneva", City: "Genève", Hostname: "ch2.isponeder.com", Premium: true},
|
|
||||||
{Country: "Ukraine", Region: "Poltavs'ka Oblast'", City: "Kremenchuk", Hostname: "ua1.isponeder.com", Premium: true},
|
|
||||||
{Country: "United Arab Emirates", Region: "Maharashtra", City: "Mumbai", Hostname: "ae1.isponeder.com", Premium: true},
|
|
||||||
{Country: "United Kingdom", Region: "England", City: "London", Hostname: "uk2.isponeder.com"},
|
|
||||||
{Country: "United Kingdom", Region: "England", City: "Kent", Hostname: "uk3.isponeder.com"},
|
|
||||||
{Country: "United Kingdom", Region: "England", City: "London", Hostname: "uk4.isponeder.com"},
|
|
||||||
{Country: "United Kingdom", Region: "England", City: "London", Hostname: "uk5.isponeder.com"},
|
|
||||||
{Country: "United Kingdom", Region: "Brent", City: "Harlesden", Hostname: "uk6.isponeder.com"},
|
|
||||||
{Country: "United Kingdom", Region: "England", City: "Manchester", Hostname: "uk7.isponeder.com"},
|
|
||||||
{Country: "United States", Region: "New Jersey", City: "Secaucus", Hostname: "us1.isponeder.com"},
|
|
||||||
{Country: "United States", Region: "New York", City: "New York City", Hostname: "us10.isponeder.com"},
|
|
||||||
{Country: "United States", Region: "California", City: "Los Angeles", Hostname: "us11.isponeder.com"},
|
|
||||||
{Country: "United States", Region: "Illinois", City: "Chicago", Hostname: "us12.isponeder.com"},
|
|
||||||
{Country: "United States", Region: "California", City: "Los Angeles", Hostname: "us13.isponeder.com"},
|
|
||||||
{Country: "United States", Region: "California", City: "Los Angeles", Hostname: "us14.isponeder.com"},
|
|
||||||
{Country: "United States", Region: "California", City: "Los Angeles", Hostname: "us15.isponeder.com"},
|
|
||||||
{Country: "United States", Region: "Illinois", City: "Chicago", Hostname: "us16.isponeder.com"},
|
|
||||||
{Country: "United States", Region: "New York", City: "New York City", Hostname: "us2.isponeder.com"},
|
|
||||||
{Country: "United States", Region: "Oregon", City: "Portland", Hostname: "us3.isponeder.com", Premium: true},
|
|
||||||
{Country: "United States", Region: "Illinois", City: "Chicago", Hostname: "us4.isponeder.com"},
|
|
||||||
{Country: "United States", Region: "California", City: "Los Angeles", Hostname: "us5.isponeder.com"},
|
|
||||||
{Country: "United States", Region: "California", City: "Los Angeles", Hostname: "us6.isponeder.com"},
|
|
||||||
{Country: "United States", Region: "Illinois", City: "Chicago", Hostname: "us7.isponeder.com"},
|
|
||||||
{Country: "United States", Region: "Georgia", City: "Atlanta", Hostname: "us8.isponeder.com"},
|
|
||||||
{Country: "United States", Region: "Georgia", City: "Atlanta", Hostname: "us9.isponeder.com"},
|
|
||||||
{Country: "Hong Kong", Region: "Central and Western", City: "Hong Kong", Hostname: "hk1.isponeder.com"},
|
|
||||||
{Country: "United States West", Region: "California", City: "Los Angeles", Hostname: "us3.isponeder.com", Premium: true},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
for name, testCase := range testCases {
|
|
||||||
testCase := testCase
|
|
||||||
t.Run(name, func(t *testing.T) {
|
|
||||||
t.Parallel()
|
|
||||||
|
|
||||||
servers, warnings, err := parseHTML(testCase.rootNode)
|
|
||||||
|
|
||||||
assert.Equal(t, testCase.servers, servers)
|
|
||||||
assert.Equal(t, testCase.warnings, warnings)
|
|
||||||
assert.ErrorIs(t, err, testCase.errWrapped)
|
|
||||||
if testCase.errWrapped != nil {
|
|
||||||
assert.EqualError(t, err, testCase.errMessage)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -30,7 +30,7 @@ func (u *Updater) FetchServers(ctx context.Context, minServers int) (
|
|||||||
x5090Name := group.OvpnX509
|
x5090Name := group.OvpnX509
|
||||||
wgPubKey := group.WgPubKey
|
wgPubKey := group.WgPubKey
|
||||||
for _, node := range group.Nodes {
|
for _, node := range group.Nodes {
|
||||||
ips := make([]net.IP, 0, 2) //nolint:gomnd
|
ips := make([]net.IP, 0, 2) // nolint:gomnd
|
||||||
if node.IP != nil {
|
if node.IP != nil {
|
||||||
ips = append(ips, node.IP)
|
ips = append(ips, node.IP)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,34 +8,18 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
type NetLinker interface {
|
type NetLinker interface {
|
||||||
Addresser
|
|
||||||
Router
|
|
||||||
Ruler
|
|
||||||
Linker
|
|
||||||
IsWireguardSupported() (ok bool, err error)
|
|
||||||
}
|
|
||||||
|
|
||||||
type Addresser interface {
|
|
||||||
AddrList(link netlink.Link, family int) (
|
AddrList(link netlink.Link, family int) (
|
||||||
addresses []netlink.Addr, err error)
|
addresses []netlink.Addr, err error)
|
||||||
AddrAdd(link netlink.Link, addr *netlink.Addr) error
|
AddrAdd(link netlink.Link, addr *netlink.Addr) error
|
||||||
}
|
IsWireguardSupported() (ok bool, err error)
|
||||||
|
|
||||||
type Router interface {
|
|
||||||
RouteList(link netlink.Link, family int) (
|
RouteList(link netlink.Link, family int) (
|
||||||
routes []netlink.Route, err error)
|
routes []netlink.Route, err error)
|
||||||
RouteAdd(route *netlink.Route) error
|
RouteAdd(route *netlink.Route) error
|
||||||
RouteDel(route *netlink.Route) error
|
RouteDel(route *netlink.Route) error
|
||||||
RouteReplace(route *netlink.Route) error
|
RouteReplace(route *netlink.Route) error
|
||||||
}
|
|
||||||
|
|
||||||
type Ruler interface {
|
|
||||||
RuleList(family int) (rules []netlink.Rule, err error)
|
RuleList(family int) (rules []netlink.Rule, err error)
|
||||||
RuleAdd(rule *netlink.Rule) error
|
RuleAdd(rule *netlink.Rule) error
|
||||||
RuleDel(rule *netlink.Rule) error
|
RuleDel(rule *netlink.Rule) error
|
||||||
}
|
|
||||||
|
|
||||||
type Linker interface {
|
|
||||||
LinkList() (links []netlink.Link, err error)
|
LinkList() (links []netlink.Link, err error)
|
||||||
LinkByName(name string) (link netlink.Link, err error)
|
LinkByName(name string) (link netlink.Link, err error)
|
||||||
LinkByIndex(index int) (link netlink.Link, err error)
|
LinkByIndex(index int) (link netlink.Link, err error)
|
||||||
|
|||||||
@@ -32,10 +32,10 @@ func (h *dnsHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
|||||||
case http.MethodPut:
|
case http.MethodPut:
|
||||||
h.setStatus(w, r)
|
h.setStatus(w, r)
|
||||||
default:
|
default:
|
||||||
http.Error(w, "method "+r.Method+" not supported", http.StatusBadRequest)
|
http.Error(w, "", http.StatusNotFound)
|
||||||
}
|
}
|
||||||
default:
|
default:
|
||||||
http.Error(w, "route "+r.RequestURI+" not supported", http.StatusBadRequest)
|
http.Error(w, "", http.StatusNotFound)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -15,18 +15,16 @@ func newHandler(ctx context.Context, logger infoWarner, logging bool,
|
|||||||
unboundLooper DNSLoop,
|
unboundLooper DNSLoop,
|
||||||
updaterLooper UpdaterLooper,
|
updaterLooper UpdaterLooper,
|
||||||
publicIPLooper PublicIPLoop,
|
publicIPLooper PublicIPLoop,
|
||||||
storage Storage,
|
|
||||||
) http.Handler {
|
) http.Handler {
|
||||||
handler := &handler{}
|
handler := &handler{}
|
||||||
|
|
||||||
vpn := newVPNHandler(ctx, vpnLooper, storage, logger)
|
|
||||||
openvpn := newOpenvpnHandler(ctx, vpnLooper, pfGetter, logger)
|
openvpn := newOpenvpnHandler(ctx, vpnLooper, pfGetter, logger)
|
||||||
dns := newDNSHandler(ctx, unboundLooper, logger)
|
dns := newDNSHandler(ctx, unboundLooper, logger)
|
||||||
updater := newUpdaterHandler(ctx, updaterLooper, logger)
|
updater := newUpdaterHandler(ctx, updaterLooper, logger)
|
||||||
publicip := newPublicIPHandler(publicIPLooper, logger)
|
publicip := newPublicIPHandler(publicIPLooper, logger)
|
||||||
|
|
||||||
handler.v0 = newHandlerV0(ctx, logger, vpnLooper, unboundLooper, updaterLooper)
|
handler.v0 = newHandlerV0(ctx, logger, vpnLooper, unboundLooper, updaterLooper)
|
||||||
handler.v1 = newHandlerV1(logger, buildInfo, vpn, openvpn, dns, updater, publicip)
|
handler.v1 = newHandlerV1(logger, buildInfo, openvpn, dns, updater, publicip)
|
||||||
|
|
||||||
handlerWithLog := withLogMiddleware(handler, logger, logging)
|
handlerWithLog := withLogMiddleware(handler, logger, logging)
|
||||||
handler.setLogEnabled = handlerWithLog.setEnabled
|
handler.setLogEnabled = handlerWithLog.setEnabled
|
||||||
|
|||||||
@@ -28,7 +28,7 @@ type handlerV0 struct {
|
|||||||
|
|
||||||
func (h *handlerV0) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
func (h *handlerV0) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||||
if r.Method != http.MethodGet {
|
if r.Method != http.MethodGet {
|
||||||
http.Error(w, "unversioned API: only supports GET method", http.StatusBadRequest)
|
http.Error(w, "unversioned API: only supports GET method", http.StatusNotFound)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
switch r.RequestURI {
|
switch r.RequestURI {
|
||||||
@@ -63,6 +63,6 @@ func (h *handlerV0) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
|||||||
h.logger.Warn(err.Error())
|
h.logger.Warn(err.Error())
|
||||||
}
|
}
|
||||||
default:
|
default:
|
||||||
http.Error(w, "unversioned API: requested URI not found", http.StatusBadRequest)
|
http.Error(w, "unversioned API: requested URI not found", http.StatusNotFound)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,11 +10,10 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
func newHandlerV1(w warner, buildInfo models.BuildInformation,
|
func newHandlerV1(w warner, buildInfo models.BuildInformation,
|
||||||
vpn, openvpn, dns, updater, publicip http.Handler) http.Handler {
|
openvpn, dns, updater, publicip http.Handler) http.Handler {
|
||||||
return &handlerV1{
|
return &handlerV1{
|
||||||
warner: w,
|
warner: w,
|
||||||
buildInfo: buildInfo,
|
buildInfo: buildInfo,
|
||||||
vpn: vpn,
|
|
||||||
openvpn: openvpn,
|
openvpn: openvpn,
|
||||||
dns: dns,
|
dns: dns,
|
||||||
updater: updater,
|
updater: updater,
|
||||||
@@ -25,7 +24,6 @@ func newHandlerV1(w warner, buildInfo models.BuildInformation,
|
|||||||
type handlerV1 struct {
|
type handlerV1 struct {
|
||||||
warner warner
|
warner warner
|
||||||
buildInfo models.BuildInformation
|
buildInfo models.BuildInformation
|
||||||
vpn http.Handler
|
|
||||||
openvpn http.Handler
|
openvpn http.Handler
|
||||||
dns http.Handler
|
dns http.Handler
|
||||||
updater http.Handler
|
updater http.Handler
|
||||||
@@ -36,8 +34,6 @@ func (h *handlerV1) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
|||||||
switch {
|
switch {
|
||||||
case r.RequestURI == "/version" && r.Method == http.MethodGet:
|
case r.RequestURI == "/version" && r.Method == http.MethodGet:
|
||||||
h.getVersion(w)
|
h.getVersion(w)
|
||||||
case strings.HasPrefix(r.RequestURI, "/vpn"):
|
|
||||||
h.vpn.ServeHTTP(w, r)
|
|
||||||
case strings.HasPrefix(r.RequestURI, "/openvpn"):
|
case strings.HasPrefix(r.RequestURI, "/openvpn"):
|
||||||
h.openvpn.ServeHTTP(w, r)
|
h.openvpn.ServeHTTP(w, r)
|
||||||
case strings.HasPrefix(r.RequestURI, "/dns"):
|
case strings.HasPrefix(r.RequestURI, "/dns"):
|
||||||
@@ -48,7 +44,7 @@ func (h *handlerV1) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
|||||||
h.publicip.ServeHTTP(w, r)
|
h.publicip.ServeHTTP(w, r)
|
||||||
default:
|
default:
|
||||||
errString := fmt.Sprintf("%s %s not found", r.Method, r.RequestURI)
|
errString := fmt.Sprintf("%s %s not found", r.Method, r.RequestURI)
|
||||||
http.Error(w, errString, http.StatusBadRequest)
|
http.Error(w, errString, http.StatusNotFound)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -12,7 +12,6 @@ type VPNLooper interface {
|
|||||||
ApplyStatus(ctx context.Context, status models.LoopStatus) (
|
ApplyStatus(ctx context.Context, status models.LoopStatus) (
|
||||||
outcome string, err error)
|
outcome string, err error)
|
||||||
GetSettings() (settings settings.VPN)
|
GetSettings() (settings settings.VPN)
|
||||||
SetSettings(ctx context.Context, settings settings.VPN) (outcome string)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type DNSLoop interface {
|
type DNSLoop interface {
|
||||||
@@ -28,7 +27,3 @@ type PortForwardedGetter interface {
|
|||||||
type PublicIPLoop interface {
|
type PublicIPLoop interface {
|
||||||
GetData() (data models.PublicIP)
|
GetData() (data models.PublicIP)
|
||||||
}
|
}
|
||||||
|
|
||||||
type Storage interface {
|
|
||||||
GetFilterChoices(provider string) models.FilterChoices
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -34,24 +34,24 @@ func (h *openvpnHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
|||||||
case http.MethodPut:
|
case http.MethodPut:
|
||||||
h.setStatus(w, r)
|
h.setStatus(w, r)
|
||||||
default:
|
default:
|
||||||
http.Error(w, "method "+r.Method+" not supported", http.StatusBadRequest)
|
http.Error(w, "", http.StatusNotFound)
|
||||||
}
|
}
|
||||||
case "/settings":
|
case "/settings":
|
||||||
switch r.Method {
|
switch r.Method {
|
||||||
case http.MethodGet:
|
case http.MethodGet:
|
||||||
h.getSettings(w)
|
h.getSettings(w)
|
||||||
default:
|
default:
|
||||||
http.Error(w, "method "+r.Method+" not supported", http.StatusBadRequest)
|
http.Error(w, "", http.StatusNotFound)
|
||||||
}
|
}
|
||||||
case "/portforwarded":
|
case "/portforwarded":
|
||||||
switch r.Method {
|
switch r.Method {
|
||||||
case http.MethodGet:
|
case http.MethodGet:
|
||||||
h.getPortForwarded(w)
|
h.getPortForwarded(w)
|
||||||
default:
|
default:
|
||||||
http.Error(w, "method "+r.Method+" not supported", http.StatusBadRequest)
|
http.Error(w, "", http.StatusNotFound)
|
||||||
}
|
}
|
||||||
default:
|
default:
|
||||||
http.Error(w, "route "+r.RequestURI+" not supported", http.StatusBadRequest)
|
http.Error(w, "", http.StatusNotFound)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -94,6 +94,8 @@ func (h *openvpnHandler) setStatus(w http.ResponseWriter, r *http.Request) {
|
|||||||
func (h *openvpnHandler) getSettings(w http.ResponseWriter) {
|
func (h *openvpnHandler) getSettings(w http.ResponseWriter) {
|
||||||
vpnSettings := h.looper.GetSettings()
|
vpnSettings := h.looper.GetSettings()
|
||||||
settings := vpnSettings.OpenVPN
|
settings := vpnSettings.OpenVPN
|
||||||
|
settings.User = "redacted"
|
||||||
|
settings.Password = "redacted"
|
||||||
encoder := json.NewEncoder(w)
|
encoder := json.NewEncoder(w)
|
||||||
if err := encoder.Encode(settings); err != nil {
|
if err := encoder.Encode(settings); err != nil {
|
||||||
h.warner.Warn(err.Error())
|
h.warner.Warn(err.Error())
|
||||||
|
|||||||
@@ -26,10 +26,10 @@ func (h *publicIPHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
|||||||
case http.MethodGet:
|
case http.MethodGet:
|
||||||
h.getPublicIP(w)
|
h.getPublicIP(w)
|
||||||
default:
|
default:
|
||||||
http.Error(w, "method "+r.Method+" not supported", http.StatusBadRequest)
|
http.Error(w, "", http.StatusNotFound)
|
||||||
}
|
}
|
||||||
default:
|
default:
|
||||||
http.Error(w, "route "+r.RequestURI+" not supported", http.StatusBadRequest)
|
http.Error(w, "", http.StatusNotFound)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -11,10 +11,9 @@ import (
|
|||||||
func New(ctx context.Context, address string, logEnabled bool, logger Logger,
|
func New(ctx context.Context, address string, logEnabled bool, logger Logger,
|
||||||
buildInfo models.BuildInformation, openvpnLooper VPNLooper,
|
buildInfo models.BuildInformation, openvpnLooper VPNLooper,
|
||||||
pfGetter PortForwardedGetter, unboundLooper DNSLoop,
|
pfGetter PortForwardedGetter, unboundLooper DNSLoop,
|
||||||
updaterLooper UpdaterLooper, publicIPLooper PublicIPLoop, storage Storage) (
|
updaterLooper UpdaterLooper, publicIPLooper PublicIPLoop) (server *httpserver.Server, err error) {
|
||||||
server *httpserver.Server, err error) {
|
|
||||||
handler := newHandler(ctx, logger, logEnabled, buildInfo,
|
handler := newHandler(ctx, logger, logEnabled, buildInfo,
|
||||||
openvpnLooper, pfGetter, unboundLooper, updaterLooper, publicIPLooper, storage)
|
openvpnLooper, pfGetter, unboundLooper, updaterLooper, publicIPLooper)
|
||||||
|
|
||||||
httpServerSettings := httpserver.Settings{
|
httpServerSettings := httpserver.Settings{
|
||||||
Address: address,
|
Address: address,
|
||||||
|
|||||||
@@ -44,10 +44,10 @@ func (h *updaterHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
|||||||
case http.MethodPut:
|
case http.MethodPut:
|
||||||
h.setStatus(w, r)
|
h.setStatus(w, r)
|
||||||
default:
|
default:
|
||||||
http.Error(w, "method "+r.Method+" not supported", http.StatusBadRequest)
|
http.Error(w, "", http.StatusNotFound)
|
||||||
}
|
}
|
||||||
default:
|
default:
|
||||||
http.Error(w, "route "+r.RequestURI+" not supported", http.StatusBadRequest)
|
http.Error(w, "", http.StatusNotFound)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user