Compare commits

...

69 Commits

Author SHA1 Message Date
Quentin McGaw
e32d251cc1 hotfix(windscribe): OpenVPN certificate validation 2022-05-07 07:05:24 +00:00
Quentin McGaw
9dd5e7bf1d fix: PUID and PGID as 32 bit unsigned integers 2022-05-01 16:29:56 +00:00
Quentin McGaw
b6de6035f6 hotfix(nordvpn): use aes-256-cbc before GCM 2022-04-28 13:47:24 +00:00
Quentin McGaw
88ccaf0b83 feat(torguard): update servers data 2022-04-26 11:01:42 +00:00
Quentin McGaw
52c8bc075f feat(nordvpn): update servers data 2022-04-26 11:01:05 +00:00
Quentin McGaw
2537cd5271 fix(port-forwarding): loop exit from vpn loop 2022-04-25 08:31:32 +00:00
Quentin McGaw
db91625de4 fix(pia): port forwarding certificate
- Do not use custom PIA certificate
- Only use OS certificates
- Update unit test
2022-04-25 08:31:27 +00:00
Quentin McGaw
df78386fbe chore(ci): add codeql analysis 2022-04-23 12:30:15 -04:00
Quentin McGaw
a1d70f740a fix(nordvpn): allow aes-256-gcm for Openvpn 2.4 2022-04-23 12:53:24 +00:00
Quentin McGaw
187f42277a fix(pia): hide escaped url query values 2022-04-23 11:21:08 +00:00
Quentin McGaw
e1f89bb569 fix(health): HEALTH_VPN_DURATION_ADDITION 2022-04-23 11:09:24 +00:00
Quentin McGaw
1d94f8ab2b chore(storage): remove unneeded VPN default 2022-04-23 11:09:04 +00:00
Quentin McGaw
045ecabb78 chore(updater): set vpn field for all providers
- Bump servers model versions for all providers except mullvad, ivpn, windscribe
- Do not leave `vpn` JSON field empty for any server
2022-04-23 11:08:59 +00:00
Quentin McGaw
e6c3cb078a chore(storage): tcp and udp fields for all servers
- Updater code sets UDP and TCP compatibility for all providers
- Increase servers.json model versions for affected providers (mullvad, windscribe, privado, protonvpn, privatevpn)
- Remove retro-compatibility server defaults
- Update all affected providers servers data (mullvad, windscribe, privado, protonvpn, privatevpn)
2022-04-23 10:23:41 +00:00
Quentin McGaw
afa51b3ff6 hotfix(storage): servers json versions updated 2022-04-22 21:12:27 +00:00
Quentin McGaw
f9c80b2285 hotfix(privatevpn): add missing IP addresses 2022-04-22 21:03:38 +00:00
Quentin McGaw
fc5cf44b2c fix(firewall): iptables detection improved
1. Try setting a dummy output rule
2. Remove the dummy output rule
3. Get the INPUT table policy
4. Set the INPUT table policy to its existing policy
2022-04-22 17:23:57 +00:00
Quentin McGaw
0c0f1663b1 chore: simplify provider GetConnection 2022-04-20 15:16:55 +00:00
Quentin McGaw
306d8494d6 hotfix(servers): assume UDP+TCP if not precised 2022-04-19 11:52:05 +00:00
Quentin McGaw
f5c00c3e2d chore(filter): common filter for all providers 2022-04-18 17:08:31 +00:00
Quentin McGaw
ac9571c6b2 chore(storage): runtime defaults on servers data
- `openvpn` default VPN protocol for servers
- True UDP if VPN protocol is Wireguard
2022-04-18 12:08:26 +00:00
Quentin McGaw
934fafb64b chore(constants): internal/constants/vpn package 2022-04-18 11:14:07 +00:00
Quentin McGaw
d51514015f chore(storage): simplify reading of server file 2022-04-18 11:14:02 +00:00
Quentin McGaw
a9cfd16d53 chore(validation): uniformize server filters build 2022-04-18 07:27:00 +00:00
Quentin McGaw
1a6f26fa3b feat(nordvpn): remove OpenVPN compression 2022-04-18 07:26:53 +00:00
Quentin McGaw
0dd723b29f chore(provider): add safety connection count check 2022-04-17 16:23:53 +00:00
Quentin McGaw
7ad6fc8e73 docs(maintenance): update document 2022-04-17 16:21:21 +00:00
Quentin McGaw
31c7e6362b chore(devcontainer): multiple changes and fixes
- Fix windows script sourcing
- Remove image name to avoid conflicts
- Bind mount normally without `:z`
- Install `htop`
2022-04-17 16:21:21 +00:00
Quentin McGaw
072b42d867 chore(v4): add v4 comments about server names 2022-04-17 16:21:21 +00:00
Quentin McGaw
5d66c193aa chore(models): common Server & Servers for all providers (#943) 2022-04-17 16:21:19 +00:00
Quentin McGaw
aa729515b9 chore(models): streamline all server models IPs (#942)
- Use `IPs []net.IP` for all server models
- Use `ips` JSON field for all server models
- Merge IPv4 and IPv6 addresses together for Mullvad
2022-04-17 16:18:34 +00:00
Quentin McGaw
54b7e23974 chore(constants): internal/constants/providers
- New package to avoid package import cycles
2022-04-16 19:30:26 +00:00
Quentin McGaw
ad80e0c1ab feat(protonvpn): update servers data 2022-04-16 17:52:53 +00:00
Quentin McGaw
5d7b278957 change(protonvpn): change server name JSON field from name to server_name 2022-04-16 17:51:15 +00:00
dependabot[bot]
678caaf6a0 Chore(deps): Bump docker/build-push-action from 2.9.0 to 2.10.0 (#893) 2022-04-15 12:23:38 -04:00
dependabot[bot]
7228cd7b12 Chore(deps): Bump github.com/breml/rootcerts from 0.2.2 to 0.2.3 (#926) 2022-04-15 12:22:55 -04:00
Martin Bjeldbak Madsen
7b598a3534 docs(readme): remove announcement (#938) 2022-04-15 12:22:30 -04:00
Quentin McGaw
9cdc9e9153 feat(pia): server data updated 2022-04-11 21:29:16 +00:00
Quentin McGaw
71ab0416b0 fix(iptables): use OUTPUT chain for test instead of INPUT 2022-04-11 21:05:12 +00:00
Quentin McGaw
10a13bc8a7 fix(health): change default target address to cloudflare.com:443 2022-04-11 20:21:15 +00:00
Mirco Ianese
be386a8e33 feat(fastestvpn): update servers data (#923) 2022-04-02 13:31:00 -04:00
Quentin McGaw
c33fb8bb97 fix(env): OPENVPN_FLAGS functionality 2022-03-31 20:49:01 +00:00
Quentin McGaw
20f20f051b fix(firewall): iptables support detection
- Add dummy rule to `INPUT` to test for iptables support
- This may resolve #896
2022-03-30 09:03:25 +00:00
Quentin McGaw
179274ade0 feat(log): use github.com/qdm12/log library 2022-03-30 09:03:20 +00:00
Quentin McGaw
84607e332b chore(server): use httpserver package for control server 2022-03-30 09:00:42 +00:00
Quentin McGaw
8186ef2342 chore(httpserver): remove name field 2022-03-30 09:00:36 +00:00
Mirco Ianese
19b184adba fix(purevpn): update servers Zip file download URL (#915)
- Fix PureVPN zip file download link
- Update all PureVPN server information
2022-03-28 15:47:40 -04:00
Quentin McGaw
a97fd35d6e fix(ci): openvpn 2.4.12-r0 2022-03-28 17:32:56 +00:00
dependabot[bot]
470ca020e2 Chore(deps): Bump github.com/stretchr/testify from 1.7.0 to 1.7.1 (#897)
Bumps [github.com/stretchr/testify](https://github.com/stretchr/testify) from 1.7.0 to 1.7.1.
- [Release notes](https://github.com/stretchr/testify/releases)
- [Commits](https://github.com/stretchr/testify/compare/v1.7.0...v1.7.1)

---
updated-dependencies:
- dependency-name: github.com/stretchr/testify
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-03-28 13:23:08 -04:00
dependabot[bot]
f64d7c4343 Chore(deps): Bump peter-evans/dockerhub-description from 2 to 3 (#908)
Bumps [peter-evans/dockerhub-description](https://github.com/peter-evans/dockerhub-description) from 2 to 3.
- [Release notes](https://github.com/peter-evans/dockerhub-description/releases)
- [Commits](https://github.com/peter-evans/dockerhub-description/compare/v2...v3)

---
updated-dependencies:
- dependency-name: peter-evans/dockerhub-description
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-03-28 13:22:58 -04:00
Quentin McGaw
c6f68a64e6 fix(health): use TCP dialing instead of ping
- `HEALTH_TARGET_ADDRESS` to replace `HEALTH_ADDRESS_TO_PING`
- Remove `github.com/go-ping/ping` dependency
- Dial TCP the target address, appending `:443` if port is not set
2022-03-22 08:50:56 +00:00
Quentin McGaw
5aaa122460 feat(protonvpn): update server information 2022-03-17 19:25:33 +00:00
Quentin McGaw
de169c027f feat(privatevpn): update server information 2022-03-16 10:21:49 +00:00
Quentin McGaw
314c9663a2 fix(privatevpn): update servers without hostname 2022-03-16 10:21:42 +00:00
Quentin McGaw
21995eb3e3 feat(privado): update server information 2022-03-16 10:06:10 +00:00
Quentin McGaw
6fc700bd62 feat(mullvad): update server information 2022-03-16 10:05:01 +00:00
Quentin McGaw
acdbe2163e chore(protonvpn): remove unused exit IPs field 2022-03-16 09:44:57 +00:00
Quentin McGaw
c3a231e0ab chore(storage): omit empty fields in servers.json 2022-03-16 09:43:47 +00:00
Quentin McGaw
984e143336 feat(shutdown): log out OS signal name 2022-03-15 08:16:08 +00:00
Quentin McGaw
e2ba2f82c0 feat(routing): add IPv6 inbound routing 2022-03-13 19:36:45 +00:00
Quentin McGaw
ace5e97e68 fix(routing): only set routes for IPv4 default routes 2022-03-13 14:40:17 +00:00
Quentin McGaw
82d42297e8 chore(routing): remove unused LocalSubnetGetter 2022-03-13 13:32:19 +00:00
Quentin McGaw
f99d5e8656 feat(firewall): use all default routes
- Accept output traffic from all default routes through VPN interface
- Accept output from all default routes to outbound subnets
- Accept all input traffic on ports for all default routes
- Add IP rules for all default routes
2022-03-13 13:26:33 +00:00
dependabot[bot]
0795008c23 Chore(deps): Bump docker/build-push-action from 2.8.0 to 2.9.0 (#832)
Bumps [docker/build-push-action](https://github.com/docker/build-push-action) from 2.8.0 to 2.9.0.
- [Release notes](https://github.com/docker/build-push-action/releases)
- [Commits](https://github.com/docker/build-push-action/compare/v2.8.0...v2.9.0)

---
updated-dependencies:
- dependency-name: docker/build-push-action
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-03-09 16:09:51 -05:00
dependabot[bot]
c975a86a70 Chore(deps): Bump actions/checkout from 2.4.0 to 3 (#870)
Bumps [actions/checkout](https://github.com/actions/checkout) from 2.4.0 to 3.
- [Release notes](https://github.com/actions/checkout/releases)
- [Changelog](https://github.com/actions/checkout/blob/main/CHANGELOG.md)
- [Commits](https://github.com/actions/checkout/compare/v2.4.0...v3)

---
updated-dependencies:
- dependency-name: actions/checkout
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-03-09 16:09:28 -05:00
Quentin McGaw
69eee345d2 feat(ivpn): allow no password for account IDs
- When matching `i-xxxx-xxxx-xxxx` username
- When matching `ivpn-xxxx-xxxx-xxxx` username
2022-03-09 21:01:25 +00:00
Quentin McGaw
48afc05bcb docs(readme): re-add /dev/net/tun since some OS need it 2022-03-09 11:20:05 +00:00
Quentin McGaw
39a62f5db7 feat(firewall): improve error message when NET_ADMIN is missing 2022-03-09 11:16:10 +00:00
Quentin McGaw
006b218ade feat(firewall): auto-detect which iptables
- On `iptables` error, try to use `iptables-nft`
- On `ip6tables` error, try to use `ip6tables-nft`
2022-02-26 22:55:22 +00:00
283 changed files with 82708 additions and 64253 deletions

View File

@@ -1,2 +1,2 @@
FROM qmcgaw/godevcontainer FROM qmcgaw/godevcontainer
RUN apk add wireguard-tools RUN apk add wireguard-tools htop

View File

@@ -8,7 +8,7 @@
"vscode" "vscode"
], ],
"shutdownAction": "stopCompose", "shutdownAction": "stopCompose",
"postCreateCommand": "~/.windows.sh && go mod download && go mod tidy", "postCreateCommand": "source ~/.windows.sh && go mod download && go mod tidy",
"workspaceFolder": "/workspace", "workspaceFolder": "/workspace",
"extensions": [ "extensions": [
"golang.go", "golang.go",
@@ -25,6 +25,7 @@
"bajdzis.vscode-database", // Supports connections to mysql or postgres, over SSL, socked "bajdzis.vscode-database", // Supports connections to mysql or postgres, over SSL, socked
"IBM.output-colorizer", // Colorize your output/test logs "IBM.output-colorizer", // Colorize your output/test logs
"mohsen1.prettify-json", // Prettify JSON data "mohsen1.prettify-json", // Prettify JSON data
"github.copilot",
], ],
"settings": { "settings": {
"files.eol": "\n", "files.eol": "\n",

View File

@@ -3,7 +3,6 @@ version: "3.7"
services: services:
vscode: vscode:
build: . build: .
image: godevcontainer
devices: devices:
- /dev/net/tun:/dev/net/tun - /dev/net/tun:/dev/net/tun
volumes: volumes:
@@ -11,16 +10,16 @@ services:
# Docker socket to access Docker server # Docker socket to access Docker server
- /var/run/docker.sock:/var/run/docker.sock - /var/run/docker.sock:/var/run/docker.sock
# Docker configuration # Docker configuration
- ~/.docker:/root/.docker:z - ~/.docker:/root/.docker
# SSH directory for Linux, OSX and WSL # SSH directory for Linux, OSX and WSL
- ~/.ssh:/root/.ssh:z - ~/.ssh:/root/.ssh
# For Windows without WSL, a copy will be made # For Windows without WSL, a copy will be made
# from /tmp/.ssh to ~/.ssh to fix permissions # from /tmp/.ssh to ~/.ssh to fix permissions
#- ~/.ssh:/tmp/.ssh:ro #- ~/.ssh:/tmp/.ssh:ro
# Shell history persistence # Shell history persistence
- ~/.zsh_history:/root/.zsh_history:z - ~/.zsh_history:/root/.zsh_history
# Git config # Git config
- ~/.gitconfig:/root/.gitconfig:z - ~/.gitconfig:/root/.gitconfig
environment: environment:
- TZ= - TZ=
cap_add: cap_add:

View File

@@ -42,7 +42,7 @@ jobs:
env: env:
DOCKER_BUILDKIT: "1" DOCKER_BUILDKIT: "1"
steps: steps:
- uses: actions/checkout@v2.4.0 - uses: actions/checkout@v3
- uses: reviewdog/action-misspell@v1 - uses: reviewdog/action-misspell@v1
with: with:
@@ -89,7 +89,7 @@ jobs:
needs: [verify] needs: [verify]
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- uses: actions/checkout@v2.4.0 - uses: actions/checkout@v3
# extract metadata (tags, labels) for Docker # extract metadata (tags, labels) for Docker
# https://github.com/docker/metadata-action # https://github.com/docker/metadata-action
@@ -123,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@v2.8.0 uses: docker/build-push-action@v2.10.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 }}

25
.github/workflows/codeql.yml vendored Normal file
View File

@@ -0,0 +1,25 @@
name: "CodeQL"
on:
push:
branches: [ master ]
pull_request:
# The branches below must be a subset of the branches above
branches: [ master ]
schedule:
- cron: '44 9 * * 0'
jobs:
analyze:
runs-on: ubuntu-latest
permissions:
actions: read
contents: read
security-events: write
steps:
- uses: actions/checkout@v3
- uses: github/codeql-action/init@v2
with:
languages: go
- uses: github/codeql-action/autobuild@v2
- uses: github/codeql-action/analyze@v2

View File

@@ -21,7 +21,7 @@ jobs:
env: env:
DOCKER_BUILDKIT: "1" DOCKER_BUILDKIT: "1"
steps: steps:
- uses: actions/checkout@v2.4.0 - uses: actions/checkout@v3
- name: Build test image - name: Build test image
run: docker build --target test -t test-container . run: docker build --target test -t test-container .

View File

@@ -10,9 +10,9 @@ jobs:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- name: Checkout - name: Checkout
uses: actions/checkout@v2.4.0 uses: actions/checkout@v3
- name: Docker Hub Description - name: Docker Hub Description
uses: peter-evans/dockerhub-description@v2 uses: peter-evans/dockerhub-description@v3
with: with:
username: qmcgaw username: qmcgaw
password: ${{ secrets.DOCKERHUB_PASSWORD }} password: ${{ secrets.DOCKERHUB_PASSWORD }}

View File

@@ -21,7 +21,7 @@ jobs:
env: env:
DOCKER_BUILDKIT: "1" DOCKER_BUILDKIT: "1"
steps: steps:
- uses: actions/checkout@v2.4.0 - uses: actions/checkout@v3
- name: Linting - name: Linting
run: docker build --target lint . run: docker build --target lint .

View File

@@ -9,7 +9,7 @@ jobs:
labeler: labeler:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- uses: actions/checkout@v2.4.0 - uses: actions/checkout@v3
- uses: crazy-max/ghaction-github-labeler@v3 - uses: crazy-max/ghaction-github-labeler@v3
with: with:
yaml-file: .github/labels.yml yaml-file: .github/labels.yml

View File

@@ -124,7 +124,7 @@ ENV VPN_SERVICE_PROVIDER=pia \
LOG_LEVEL=info \ LOG_LEVEL=info \
# Health # Health
HEALTH_SERVER_ADDRESS=127.0.0.1:9999 \ HEALTH_SERVER_ADDRESS=127.0.0.1:9999 \
HEALTH_ADDRESS_TO_PING=github.com \ HEALTH_TARGET_ADDRESS=cloudflare.com:443 \
HEALTH_VPN_DURATION_INITIAL=6s \ HEALTH_VPN_DURATION_INITIAL=6s \
HEALTH_VPN_DURATION_ADDITION=5s \ HEALTH_VPN_DURATION_ADDITION=5s \
# DNS over TLS # DNS over TLS
@@ -181,7 +181,7 @@ EXPOSE 8000/tcp 8888/tcp 8388/tcp 8388/udp
HEALTHCHECK --interval=5s --timeout=5s --start-period=10s --retries=1 CMD /gluetun-entrypoint healthcheck HEALTHCHECK --interval=5s --timeout=5s --start-period=10s --retries=1 CMD /gluetun-entrypoint healthcheck
ARG TARGETPLATFORM ARG TARGETPLATFORM
RUN apk add --no-cache --update -l apk-tools && \ RUN apk add --no-cache --update -l apk-tools && \
apk add --no-cache --update -X "https://dl-cdn.alpinelinux.org/alpine/v3.12/main" openvpn==2.4.11-r0 && \ apk add --no-cache --update -X "https://dl-cdn.alpinelinux.org/alpine/v3.12/main" openvpn==2.4.12-r0 && \
mv /usr/sbin/openvpn /usr/sbin/openvpn2.4 && \ mv /usr/sbin/openvpn /usr/sbin/openvpn2.4 && \
apk del openvpn && \ apk del openvpn && \
apk add --no-cache --update openvpn ca-certificates iptables ip6tables unbound tzdata && \ apk add --no-cache --update openvpn ca-certificates iptables ip6tables unbound tzdata && \

View File

@@ -5,7 +5,6 @@ HideMyAss, IPVanish, IVPN, Mullvad, NordVPN, Perfect Privacy, Privado, Private I
ProtonVPN, PureVPN, Surfshark, TorGuard, VPNUnlimited, VyprVPN, WeVPN and Windscribe VPN servers ProtonVPN, PureVPN, Surfshark, TorGuard, VPNUnlimited, VyprVPN, WeVPN and Windscribe VPN servers
using Go, OpenVPN or Wireguard, iptables, DNS over TLS, ShadowSocks and an HTTP proxy* using Go, OpenVPN or Wireguard, iptables, DNS over TLS, ShadowSocks and an HTTP proxy*
**ANNOUNCEMENT**: Large settings refactor merged on 2022-06-01, please file issues if you find any problem!
![Title image](https://raw.githubusercontent.com/qdm12/gluetun/master/title.svg) ![Title image](https://raw.githubusercontent.com/qdm12/gluetun/master/title.svg)
@@ -102,6 +101,8 @@ services:
# line above must be uncommented to allow external containers to connect. See https://github.com/qdm12/gluetun/wiki/Connect-a-container-to-gluetun#external-container-to-gluetun # line above must be uncommented to allow external containers to connect. See https://github.com/qdm12/gluetun/wiki/Connect-a-container-to-gluetun#external-container-to-gluetun
cap_add: cap_add:
- NET_ADMIN - NET_ADMIN
devices:
- /dev/net/tun:/dev/net/tun
ports: ports:
- 8888:8888/tcp # HTTP proxy - 8888:8888/tcp # HTTP proxy
- 8388:8388/tcp # Shadowsocks - 8388:8388/tcp # Shadowsocks

View File

@@ -40,12 +40,12 @@ import (
"github.com/qdm12/gluetun/internal/updater" "github.com/qdm12/gluetun/internal/updater"
"github.com/qdm12/gluetun/internal/vpn" "github.com/qdm12/gluetun/internal/vpn"
"github.com/qdm12/golibs/command" "github.com/qdm12/golibs/command"
"github.com/qdm12/golibs/logging"
"github.com/qdm12/goshutdown" "github.com/qdm12/goshutdown"
"github.com/qdm12/goshutdown/goroutine" "github.com/qdm12/goshutdown/goroutine"
"github.com/qdm12/goshutdown/group" "github.com/qdm12/goshutdown/group"
"github.com/qdm12/goshutdown/order" "github.com/qdm12/goshutdown/order"
"github.com/qdm12/gosplash" "github.com/qdm12/gosplash"
"github.com/qdm12/log"
"github.com/qdm12/updated/pkg/dnscrypto" "github.com/qdm12/updated/pkg/dnscrypto"
) )
@@ -64,12 +64,11 @@ func main() {
} }
background := context.Background() background := context.Background()
signalCtx, stop := signal.NotifyContext(background, syscall.SIGINT, syscall.SIGTERM, os.Interrupt) signalCh := make(chan os.Signal, 1)
signal.Notify(signalCh, os.Interrupt, syscall.SIGTERM)
ctx, cancel := context.WithCancel(background) ctx, cancel := context.WithCancel(background)
logger := logging.New(logging.Settings{ logger := log.New(log.SetLevel(log.LevelInfo))
Level: logging.LevelInfo,
})
args := os.Args args := os.Args
tun := tun.New() tun := tun.New()
@@ -88,13 +87,11 @@ func main() {
}() }()
select { select {
case <-signalCtx.Done(): case signal := <-signalCh:
stop()
fmt.Println("") fmt.Println("")
logger.Warn("Caught OS signal, shutting down") logger.Warn("Caught OS signal " + signal.String() + ", shutting down")
cancel() cancel()
case err := <-errorCh: case err := <-errorCh:
stop()
close(errorCh) close(errorCh)
if err == nil { // expected exit such as healthcheck if err == nil { // expected exit such as healthcheck
os.Exit(0) os.Exit(0)
@@ -113,6 +110,8 @@ func main() {
logger.Info("Shutdown successful") logger.Info("Shutdown successful")
case <-timer.C: case <-timer.C:
logger.Warn("Shutdown timed out") logger.Warn("Shutdown timed out")
case signal := <-signalCh:
logger.Warn("Caught OS signal " + signal.String() + ", forcing shut down")
} }
os.Exit(1) os.Exit(1)
@@ -124,7 +123,7 @@ var (
//nolint:gocognit,gocyclo,maintidx //nolint:gocognit,gocyclo,maintidx
func _main(ctx context.Context, buildInfo models.BuildInformation, func _main(ctx context.Context, buildInfo models.BuildInformation,
args []string, logger logging.ParentLogger, source sources.Source, args []string, logger log.LoggerInterface, source sources.Source,
tun tun.Interface, netLinker netlink.NetLinker, cmder command.RunStarter, tun tun.Interface, netLinker netlink.NetLinker, cmder command.RunStarter,
cli cli.CLIer) error { cli cli.CLIer) error {
if len(args) > 1 { // cli operation if len(args) > 1 { // cli operation
@@ -174,17 +173,15 @@ func _main(ctx context.Context, buildInfo models.BuildInformation,
// - global log level is parsed from source // - global log level is parsed from source
// - firewall Debug and Enabled are booleans parsed from source // - firewall Debug and Enabled are booleans parsed from source
logger.PatchLevel(*allSettings.Log.Level) logger.Patch(log.SetLevel(*allSettings.Log.Level))
routingLogger := logger.NewChild(logging.Settings{ routingLogger := logger.New(log.SetComponent("routing"))
Prefix: "routing: ",
})
if *allSettings.Firewall.Debug { // To remove in v4 if *allSettings.Firewall.Debug { // To remove in v4
routingLogger.PatchLevel(logging.LevelDebug) routingLogger.Patch(log.SetLevel(log.LevelDebug))
} }
routingConf := routing.New(netLinker, routingLogger) routingConf := routing.New(netLinker, routingLogger)
defaultInterface, defaultGateway, err := routingConf.DefaultRoute() defaultRoutes, err := routingConf.DefaultRoutes()
if err != nil { if err != nil {
return err return err
} }
@@ -194,19 +191,16 @@ func _main(ctx context.Context, buildInfo models.BuildInformation,
return err return err
} }
defaultIP, err := routingConf.DefaultIP() firewallLogger := logger.New(log.SetComponent("firewall"))
if *allSettings.Firewall.Debug { // To remove in v4
firewallLogger.Patch(log.SetLevel(log.LevelDebug))
}
firewallConf, err := firewall.NewConfig(ctx, firewallLogger, cmder,
defaultRoutes, localNetworks)
if err != nil { if err != nil {
return err return err
} }
firewallLogger := logger.NewChild(logging.Settings{
Prefix: "firewall: ",
})
if *allSettings.Firewall.Debug { // To remove in v4
firewallLogger.PatchLevel(logging.LevelDebug)
}
firewallConf := firewall.NewConfig(firewallLogger, cmder,
defaultInterface, defaultGateway, localNetworks, defaultIP)
if *allSettings.Firewall.Enabled { if *allSettings.Firewall.Enabled {
err = firewallConf.SetEnabled(ctx, true) err = firewallConf.SetEnabled(ctx, true)
if err != nil { if err != nil {
@@ -215,7 +209,7 @@ func _main(ctx context.Context, buildInfo models.BuildInformation,
} }
// TODO run this in a loop or in openvpn to reload from file without restarting // TODO run this in a loop or in openvpn to reload from file without restarting
storageLogger := logger.NewChild(logging.Settings{Prefix: "storage: "}) storageLogger := logger.New(log.SetComponent("storage"))
storage, err := storage.New(storageLogger, constants.ServersData) storage, err := storage.New(storageLogger, constants.ServersData)
if err != nil { if err != nil {
return err return err
@@ -228,7 +222,7 @@ func _main(ctx context.Context, buildInfo models.BuildInformation,
return err return err
} }
allSettings.Pprof.HTTPServer.Logger = logger allSettings.Pprof.HTTPServer.Logger = logger.New(log.SetComponent("pprof"))
pprofServer, err := pprof.New(allSettings.Pprof) pprofServer, err := pprof.New(allSettings.Pprof)
if err != nil { if err != nil {
return fmt.Errorf("cannot create Pprof server: %w", err) return fmt.Errorf("cannot create Pprof server: %w", err)
@@ -241,7 +235,7 @@ func _main(ctx context.Context, buildInfo models.BuildInformation,
// Create configurators // Create configurators
alpineConf := alpine.New() alpineConf := alpine.New()
ovpnConf := openvpn.New( ovpnConf := openvpn.New(
logger.NewChild(logging.Settings{Prefix: "openvpn configurator: "}), logger.New(log.SetComponent("openvpn configurator")),
cmder, puid, pgid) cmder, puid, pgid)
dnsCrypto := dnscrypto.New(httpClient, "", "") dnsCrypto := dnscrypto.New(httpClient, "", "")
const cacertsPath = "/etc/ssl/certs/ca-certificates.crt" const cacertsPath = "/etc/ssl/certs/ca-certificates.crt"
@@ -294,9 +288,9 @@ func _main(ctx context.Context, buildInfo models.BuildInformation,
return fmt.Errorf("cannot setup routing: %w", err) return fmt.Errorf("cannot setup routing: %w", err)
} }
defer func() { defer func() {
logger.Info("routing cleanup...") routingLogger.Info("routing cleanup...")
if err := routingConf.TearDown(); err != nil { if err := routingConf.TearDown(); err != nil {
logger.Error("cannot teardown routing: " + err.Error()) routingLogger.Error("cannot teardown routing: " + err.Error())
} }
}() }()
@@ -317,10 +311,12 @@ func _main(ctx context.Context, buildInfo models.BuildInformation,
} }
for _, port := range allSettings.Firewall.InputPorts { for _, port := range allSettings.Firewall.InputPorts {
err = firewallConf.SetAllowedPort(ctx, port, defaultInterface) for _, defaultRoute := range defaultRoutes {
err = firewallConf.SetAllowedPort(ctx, port, defaultRoute.NetInterface)
if err != nil { if err != nil {
return err return err
} }
}
} // TODO move inside firewall? } // TODO move inside firewall?
// Shutdown settings // Shutdown settings
@@ -346,14 +342,14 @@ func _main(ctx context.Context, buildInfo models.BuildInformation,
otherGroupHandler.Add(pprofHandler) otherGroupHandler.Add(pprofHandler)
<-pprofReady <-pprofReady
portForwardLogger := logger.NewChild(logging.Settings{Prefix: "port forwarding: "}) portForwardLogger := logger.New(log.SetComponent("port forwarding"))
portForwardLooper := portforward.NewLoop(allSettings.VPN.Provider.PortForwarding, portForwardLooper := portforward.NewLoop(allSettings.VPN.Provider.PortForwarding,
httpClient, firewallConf, portForwardLogger) httpClient, firewallConf, portForwardLogger)
portForwardHandler, portForwardCtx, portForwardDone := goshutdown.NewGoRoutineHandler( portForwardHandler, portForwardCtx, portForwardDone := goshutdown.NewGoRoutineHandler(
"port forwarding", goroutine.OptionTimeout(time.Second)) "port forwarding", goroutine.OptionTimeout(time.Second))
go portForwardLooper.Run(portForwardCtx, portForwardDone) go portForwardLooper.Run(portForwardCtx, portForwardDone)
unboundLogger := logger.NewChild(logging.Settings{Prefix: "dns over tls: "}) unboundLogger := logger.New(log.SetComponent("dns over tls"))
unboundLooper := dns.NewLoop(dnsConf, allSettings.DNS, httpClient, unboundLooper := dns.NewLoop(dnsConf, allSettings.DNS, httpClient,
unboundLogger) unboundLogger)
dnsHandler, dnsCtx, dnsDone := goshutdown.NewGoRoutineHandler( dnsHandler, dnsCtx, dnsDone := goshutdown.NewGoRoutineHandler(
@@ -368,7 +364,7 @@ func _main(ctx context.Context, buildInfo models.BuildInformation,
controlGroupHandler.Add(dnsTickerHandler) controlGroupHandler.Add(dnsTickerHandler)
publicIPLooper := publicip.NewLoop(httpClient, publicIPLooper := publicip.NewLoop(httpClient,
logger.NewChild(logging.Settings{Prefix: "ip getter: "}), logger.New(log.SetComponent("ip getter")),
allSettings.PublicIP, puid, pgid) allSettings.PublicIP, puid, pgid)
pubIPHandler, pubIPCtx, pubIPDone := goshutdown.NewGoRoutineHandler( pubIPHandler, pubIPCtx, pubIPDone := goshutdown.NewGoRoutineHandler(
"public IP", goroutine.OptionTimeout(defaultShutdownTimeout)) "public IP", goroutine.OptionTimeout(defaultShutdownTimeout))
@@ -380,7 +376,7 @@ func _main(ctx context.Context, buildInfo models.BuildInformation,
go publicIPLooper.RunRestartTicker(pubIPTickerCtx, pubIPTickerDone) go publicIPLooper.RunRestartTicker(pubIPTickerCtx, pubIPTickerDone)
tickersGroupHandler.Add(pubIPTickerHandler) tickersGroupHandler.Add(pubIPTickerHandler)
vpnLogger := logger.NewChild(logging.Settings{Prefix: "vpn: "}) vpnLogger := logger.New(log.SetComponent("vpn"))
vpnLooper := vpn.NewLoop(allSettings.VPN, allSettings.Firewall.VPNInputPorts, vpnLooper := vpn.NewLoop(allSettings.VPN, allSettings.Firewall.VPNInputPorts,
allServers, ovpnConf, netLinker, firewallConf, routingConf, portForwardLooper, allServers, ovpnConf, netLinker, firewallConf, routingConf, portForwardLooper,
cmder, publicIPLooper, unboundLooper, vpnLogger, httpClient, cmder, publicIPLooper, unboundLooper, vpnLogger, httpClient,
@@ -391,7 +387,7 @@ func _main(ctx context.Context, buildInfo models.BuildInformation,
updaterLooper := updater.NewLooper(allSettings.Updater, updaterLooper := updater.NewLooper(allSettings.Updater,
allServers, storage, vpnLooper.SetServers, httpClient, allServers, storage, vpnLooper.SetServers, httpClient,
logger.NewChild(logging.Settings{Prefix: "updater: "})) logger.New(log.SetComponent("updater")))
updaterHandler, updaterCtx, updaterDone := goshutdown.NewGoRoutineHandler( updaterHandler, updaterCtx, updaterDone := goshutdown.NewGoRoutineHandler(
"updater", goroutine.OptionTimeout(defaultShutdownTimeout)) "updater", goroutine.OptionTimeout(defaultShutdownTimeout))
// wait for updaterLooper.Restart() or its ticket launched with RunRestartTicker // wait for updaterLooper.Restart() or its ticket launched with RunRestartTicker
@@ -404,7 +400,7 @@ func _main(ctx context.Context, buildInfo models.BuildInformation,
controlGroupHandler.Add(updaterTickerHandler) controlGroupHandler.Add(updaterTickerHandler)
httpProxyLooper := httpproxy.NewLoop( httpProxyLooper := httpproxy.NewLoop(
logger.NewChild(logging.Settings{Prefix: "http proxy: "}), logger.New(log.SetComponent("http proxy")),
allSettings.HTTPProxy) allSettings.HTTPProxy)
httpProxyHandler, httpProxyCtx, httpProxyDone := goshutdown.NewGoRoutineHandler( httpProxyHandler, httpProxyCtx, httpProxyDone := goshutdown.NewGoRoutineHandler(
"http proxy", goroutine.OptionTimeout(defaultShutdownTimeout)) "http proxy", goroutine.OptionTimeout(defaultShutdownTimeout))
@@ -412,7 +408,7 @@ func _main(ctx context.Context, buildInfo models.BuildInformation,
otherGroupHandler.Add(httpProxyHandler) otherGroupHandler.Add(httpProxyHandler)
shadowsocksLooper := shadowsocks.NewLooper(allSettings.Shadowsocks, shadowsocksLooper := shadowsocks.NewLooper(allSettings.Shadowsocks,
logger.NewChild(logging.Settings{Prefix: "shadowsocks: "})) logger.New(log.SetComponent("shadowsocks")))
shadowsocksHandler, shadowsocksCtx, shadowsocksDone := goshutdown.NewGoRoutineHandler( shadowsocksHandler, shadowsocksCtx, shadowsocksDone := goshutdown.NewGoRoutineHandler(
"shadowsocks proxy", goroutine.OptionTimeout(defaultShutdownTimeout)) "shadowsocks proxy", goroutine.OptionTimeout(defaultShutdownTimeout))
go shadowsocksLooper.Run(shadowsocksCtx, shadowsocksDone) go shadowsocksLooper.Run(shadowsocksCtx, shadowsocksDone)
@@ -422,13 +418,18 @@ func _main(ctx context.Context, buildInfo models.BuildInformation,
controlServerLogging := *allSettings.ControlServer.Log controlServerLogging := *allSettings.ControlServer.Log
httpServerHandler, httpServerCtx, httpServerDone := goshutdown.NewGoRoutineHandler( httpServerHandler, httpServerCtx, httpServerDone := goshutdown.NewGoRoutineHandler(
"http server", goroutine.OptionTimeout(defaultShutdownTimeout)) "http server", goroutine.OptionTimeout(defaultShutdownTimeout))
httpServer := server.New(httpServerCtx, controlServerAddress, controlServerLogging, httpServer, err := server.New(httpServerCtx, controlServerAddress, controlServerLogging,
logger.NewChild(logging.Settings{Prefix: "http server: "}), logger.New(log.SetComponent("http server")),
buildInfo, vpnLooper, portForwardLooper, unboundLooper, updaterLooper, publicIPLooper) buildInfo, vpnLooper, portForwardLooper, unboundLooper, updaterLooper, publicIPLooper)
go httpServer.Run(httpServerCtx, httpServerDone) if err != nil {
return fmt.Errorf("cannot setup control server: %w", err)
}
httpServerReady := make(chan struct{})
go httpServer.Run(httpServerCtx, httpServerReady, httpServerDone)
<-httpServerReady
controlGroupHandler.Add(httpServerHandler) controlGroupHandler.Add(httpServerHandler)
healthLogger := logger.NewChild(logging.Settings{Prefix: "healthcheck: "}) healthLogger := logger.New(log.SetComponent("healthcheck"))
healthcheckServer := healthcheck.NewServer(allSettings.Health, healthLogger, vpnLooper) healthcheckServer := healthcheck.NewServer(allSettings.Health, healthLogger, vpnLooper)
healthServerHandler, healthServerCtx, healthServerDone := goshutdown.NewGoRoutineHandler( healthServerHandler, healthServerCtx, healthServerDone := goshutdown.NewGoRoutineHandler(
"HTTP health server", goroutine.OptionTimeout(defaultShutdownTimeout)) "HTTP health server", goroutine.OptionTimeout(defaultShutdownTimeout))

8
go.mod
View File

@@ -3,9 +3,8 @@ module github.com/qdm12/gluetun
go 1.17 go 1.17
require ( require (
github.com/breml/rootcerts v0.2.2 github.com/breml/rootcerts v0.2.3
github.com/fatih/color v1.13.0 github.com/fatih/color v1.13.0
github.com/go-ping/ping v0.0.0-20210911151512-381826476871
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
github.com/qdm12/golibs v0.0.0-20210822203818-5c568b0777b6 github.com/qdm12/golibs v0.0.0-20210822203818-5c568b0777b6
@@ -13,9 +12,10 @@ require (
github.com/qdm12/gosplash v0.1.0 github.com/qdm12/gosplash v0.1.0
github.com/qdm12/gotree v0.2.0 github.com/qdm12/gotree v0.2.0
github.com/qdm12/govalid v0.1.0 github.com/qdm12/govalid 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.7.0 github.com/stretchr/testify v1.7.1
github.com/vishvananda/netlink v1.1.1-0.20211129163951-9ada19101fc5 github.com/vishvananda/netlink v1.1.1-0.20211129163951-9ada19101fc5
golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c
golang.zx2c4.com/wireguard v0.0.0-20210805125648-3957e9b9dd19 golang.zx2c4.com/wireguard v0.0.0-20210805125648-3957e9b9dd19
@@ -26,7 +26,6 @@ require (
require ( require (
github.com/davecgh/go-spew v1.1.1 // indirect github.com/davecgh/go-spew v1.1.1 // indirect
github.com/google/go-cmp v0.5.5 // indirect github.com/google/go-cmp v0.5.5 // indirect
github.com/google/uuid v1.2.0 // indirect
github.com/josharian/native v0.0.0-20200817173448-b6b71def0850 // indirect github.com/josharian/native v0.0.0-20200817173448-b6b71def0850 // indirect
github.com/mattn/go-colorable v0.1.9 // indirect github.com/mattn/go-colorable v0.1.9 // indirect
github.com/mattn/go-isatty v0.0.14 // indirect github.com/mattn/go-isatty v0.0.14 // indirect
@@ -41,6 +40,5 @@ require (
go4.org/unsafe/assume-no-moving-gc v0.0.0-20201222180813-1025295fd063 // indirect go4.org/unsafe/assume-no-moving-gc v0.0.0-20201222180813-1025295fd063 // 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 golang.org/x/net v0.0.0-20210504132125-bbd867fde50d // indirect
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c // indirect
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c // indirect gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c // indirect
) )

15
go.sum
View File

@@ -4,8 +4,8 @@ github.com/alcortesm/tgz v0.0.0-20161220082320-9c5fe88206d7/go.mod h1:6zEj6s6u/g
github.com/anmitsu/go-shlex v0.0.0-20161002113705-648efa622239/go.mod h1:2FmKhYUyUczH0OGQWaF5ceTx0UBShxjsH6f8oGKYe2c= github.com/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.2 h1:hkHEpbTdYaNvDoYeq+mwRvCeg/YTTl23DjQ1Tnj71Zs= github.com/breml/rootcerts v0.2.3 h1:1vkYjKOiHVSyuz9Ue4AOrViEvUm8gk8phTg0vbcuU0A=
github.com/breml/rootcerts v0.2.2/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=
@@ -32,8 +32,6 @@ github.com/go-openapi/spec v0.17.0/go.mod h1:XkF/MOi14NmjsfZ8VtAKf8pIlbZzyoTvZsd
github.com/go-openapi/strfmt v0.17.0/go.mod h1:P82hnJI0CXkErkXi8IKjPbNBM6lV6+5pLP5l494TcyU= github.com/go-openapi/strfmt v0.17.0/go.mod h1:P82hnJI0CXkErkXi8IKjPbNBM6lV6+5pLP5l494TcyU=
github.com/go-openapi/swag v0.17.0/go.mod h1:AByQ+nYG6gQg71GINrmuDXCPWdL640yX49/kXLo40Tg= github.com/go-openapi/swag v0.17.0/go.mod h1:AByQ+nYG6gQg71GINrmuDXCPWdL640yX49/kXLo40Tg=
github.com/go-openapi/validate v0.17.0/go.mod h1:Uh4HdOzKt19xGIGm1qHf/ofbX1YQ4Y+MYsct2VUrAJ4= github.com/go-openapi/validate v0.17.0/go.mod h1:Uh4HdOzKt19xGIGm1qHf/ofbX1YQ4Y+MYsct2VUrAJ4=
github.com/go-ping/ping v0.0.0-20210911151512-381826476871 h1:wtjTfjwAR/BYYMJ+QOLI/3J/qGEI0fgrkZvgsEWK2/Q=
github.com/go-ping/ping v0.0.0-20210911151512-381826476871/go.mod h1:xIFjORFzTxqIV/tDVGO4eDy/bLuSyawEeojSm3GfRGk=
github.com/golang/mock v1.5.0/go.mod h1:CWnOUgYIOo4TcNZ0wHX3YZCqsaM1I1Jvs6v3mP3KVu8= github.com/golang/mock v1.5.0/go.mod h1:CWnOUgYIOo4TcNZ0wHX3YZCqsaM1I1Jvs6v3mP3KVu8=
github.com/golang/mock v1.6.0 h1:ErTB+efbowRARo13NNdxyJji2egdxLGQhRaY+DUumQc= github.com/golang/mock v1.6.0 h1:ErTB+efbowRARo13NNdxyJji2egdxLGQhRaY+DUumQc=
github.com/golang/mock v1.6.0/go.mod h1:p6yTPP+5HYm5mzsMV8JkE6ZKdX+/wYM6Hr+LicevLPs= github.com/golang/mock v1.6.0/go.mod h1:p6yTPP+5HYm5mzsMV8JkE6ZKdX+/wYM6Hr+LicevLPs=
@@ -47,8 +45,6 @@ github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/
github.com/google/go-cmp v0.5.5 h1:Khx7svrCpmxxtHBq5j2mp/xVjsi8hQMfNLvJFAlrGgU= github.com/google/go-cmp v0.5.5 h1:Khx7svrCpmxxtHBq5j2mp/xVjsi8hQMfNLvJFAlrGgU=
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/uuid v1.0.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/google/uuid v1.0.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/google/uuid v1.2.0 h1:qJYtXnJRWmpe7m/3XlyhrsLrEURqHRM2kxzoxXqyUDs=
github.com/google/uuid v1.2.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/gotify/go-api-client/v2 v2.0.4/go.mod h1:VKiah/UK20bXsr0JObE1eBVLW44zbBouzjuri9iwjFU= github.com/gotify/go-api-client/v2 v2.0.4/go.mod h1:VKiah/UK20bXsr0JObE1eBVLW44zbBouzjuri9iwjFU=
github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99/go.mod h1:1lJo3i6rXxKeerYnT8Nvf0QmHCRC1n8sfWVwXF2Frvo= github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99/go.mod h1:1lJo3i6rXxKeerYnT8Nvf0QmHCRC1n8sfWVwXF2Frvo=
github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI= github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI=
@@ -119,6 +115,8 @@ github.com/qdm12/gotree v0.2.0 h1:+58ltxkNLUyHtATFereAcOjBVfY6ETqRex8XK90Fb/c=
github.com/qdm12/gotree v0.2.0/go.mod h1:1SdFaqKZuI46U1apbXIf25pDMNnrPuYLEqMF/qL4lY4= github.com/qdm12/gotree v0.2.0/go.mod h1:1SdFaqKZuI46U1apbXIf25pDMNnrPuYLEqMF/qL4lY4=
github.com/qdm12/govalid v0.1.0 h1:UIFVmuaAg0Q+h0GeyfcFEZ5sQ5KJPvRQwycC1/cqDN8= github.com/qdm12/govalid v0.1.0 h1:UIFVmuaAg0Q+h0GeyfcFEZ5sQ5KJPvRQwycC1/cqDN8=
github.com/qdm12/govalid v0.1.0/go.mod h1:CyS/OEQdOvunBgrtIsW93fjd4jBkwZPBjGSpxq3NwA4= github.com/qdm12/govalid v0.1.0/go.mod h1:CyS/OEQdOvunBgrtIsW93fjd4jBkwZPBjGSpxq3NwA4=
github.com/qdm12/log v0.1.0 h1:jYBd/xscHYpblzZAd2kjZp2YmuYHjAAfbTViJWxoPTw=
github.com/qdm12/log v0.1.0/go.mod h1:Vchi5M8uBvHfPNIblN4mjXn/oSbiWguQIbsgF1zdQPI=
github.com/qdm12/ss-server v0.4.0 h1:lMMYfDGc9P86Lyvd3+p8lK4hhgHUKDzjZC91FqJYkDU= github.com/qdm12/ss-server v0.4.0 h1:lMMYfDGc9P86Lyvd3+p8lK4hhgHUKDzjZC91FqJYkDU=
github.com/qdm12/ss-server v0.4.0/go.mod h1:AY0p4huvPUPW+/CiWsJcDgT6sneDryk26VXSccPNCxY= github.com/qdm12/ss-server v0.4.0/go.mod h1:AY0p4huvPUPW+/CiWsJcDgT6sneDryk26VXSccPNCxY=
github.com/qdm12/updated v0.0.0-20210603204757-205acfe6937e h1:4q+uFLawkaQRq3yARYLsjJPZd2wYwxn4g6G/5v0xW1g= github.com/qdm12/updated v0.0.0-20210603204757-205acfe6937e h1:4q+uFLawkaQRq3yARYLsjJPZd2wYwxn4g6G/5v0xW1g=
@@ -132,8 +130,9 @@ github.com/stretchr/objx v0.2.0/go.mod h1:qt09Ya8vawLte6SNmTgCsAVtYtaKzEcn8ATUoH
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 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY=
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 h1:5TQK59W5E3v0r2duFAb7P95B6hEeOyEnHRa8MjYSMTY=
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
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=
@@ -176,7 +175,6 @@ golang.org/x/net v0.0.0-20201216054612-986b41b23924/go.mod h1:m0MpNAwzfU5UDzcl9v
golang.org/x/net v0.0.0-20201224014010-6772e930b67b/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20201224014010-6772e930b67b/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
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-20210316092652-d523dce5a7f4/go.mod h1:RBQZq4jEuRlivfhVLdyRGr576XBO4/greRjx4P4O3yc=
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 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=
@@ -210,7 +208,6 @@ golang.org/x/sys v0.0.0-20210123111255-9b0068b26619/go.mod h1:h1NjWce9XRLGQEsW7w
golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210216163648-f7da38b97c65/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210216163648-f7da38b97c65/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210309040221-94ec62e08169/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210309040221-94ec62e08169/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210315160823-c6e025ad8005/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210403161142-5e06dd20ab57/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210403161142-5e06dd20ab57/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=

View File

@@ -8,6 +8,7 @@ import (
"path/filepath" "path/filepath"
"github.com/qdm12/gluetun/internal/constants" "github.com/qdm12/gluetun/internal/constants"
"github.com/qdm12/gluetun/internal/constants/providers"
"github.com/qdm12/gluetun/internal/storage" "github.com/qdm12/gluetun/internal/storage"
) )
@@ -28,26 +29,26 @@ func (c *CLI) FormatServers(args []string) error {
flagSet := flag.NewFlagSet("markdown", flag.ExitOnError) flagSet := flag.NewFlagSet("markdown", flag.ExitOnError)
flagSet.StringVar(&format, "format", "markdown", "Format to use which can be: 'markdown'") flagSet.StringVar(&format, "format", "markdown", "Format to use which can be: 'markdown'")
flagSet.StringVar(&output, "output", "/dev/stdout", "Output file to write the formatted data to") flagSet.StringVar(&output, "output", "/dev/stdout", "Output file to write the formatted data to")
flagSet.BoolVar(&cyberghost, "cyberghost", false, "Format Cyberghost servers") flagSet.BoolVar(&cyberghost, providers.Cyberghost, false, "Format Cyberghost servers")
flagSet.BoolVar(&expressvpn, "expressvpn", false, "Format ExpressVPN servers") flagSet.BoolVar(&expressvpn, providers.Expressvpn, false, "Format ExpressVPN servers")
flagSet.BoolVar(&fastestvpn, "fastestvpn", false, "Format FastestVPN servers") flagSet.BoolVar(&fastestvpn, providers.Fastestvpn, false, "Format FastestVPN servers")
flagSet.BoolVar(&hideMyAss, "hidemyass", false, "Format HideMyAss servers") flagSet.BoolVar(&hideMyAss, providers.HideMyAss, false, "Format HideMyAss servers")
flagSet.BoolVar(&ipvanish, "ipvanish", false, "Format IpVanish servers") flagSet.BoolVar(&ipvanish, providers.Ipvanish, false, "Format IpVanish servers")
flagSet.BoolVar(&ivpn, "ivpn", false, "Format IVPN servers") flagSet.BoolVar(&ivpn, providers.Ivpn, false, "Format IVPN servers")
flagSet.BoolVar(&mullvad, "mullvad", false, "Format Mullvad servers") flagSet.BoolVar(&mullvad, providers.Mullvad, false, "Format Mullvad servers")
flagSet.BoolVar(&nordvpn, "nordvpn", false, "Format Nordvpn servers") flagSet.BoolVar(&nordvpn, providers.Nordvpn, false, "Format Nordvpn servers")
flagSet.BoolVar(&perfectPrivacy, "perfectprivacy", false, "Format Perfect Privacy servers") flagSet.BoolVar(&perfectPrivacy, providers.Perfectprivacy, false, "Format Perfect Privacy servers")
flagSet.BoolVar(&pia, "pia", false, "Format Private Internet Access servers") flagSet.BoolVar(&pia, providers.PrivateInternetAccess, false, "Format Private Internet Access servers")
flagSet.BoolVar(&privado, "privado", false, "Format Privado servers") flagSet.BoolVar(&privado, providers.Privado, false, "Format Privado servers")
flagSet.BoolVar(&privatevpn, "privatevpn", false, "Format Private VPN servers") flagSet.BoolVar(&privatevpn, providers.Privatevpn, false, "Format Private VPN servers")
flagSet.BoolVar(&protonvpn, "protonvpn", false, "Format Protonvpn servers") flagSet.BoolVar(&protonvpn, providers.Protonvpn, false, "Format Protonvpn servers")
flagSet.BoolVar(&purevpn, "purevpn", false, "Format Purevpn servers") flagSet.BoolVar(&purevpn, providers.Purevpn, false, "Format Purevpn servers")
flagSet.BoolVar(&surfshark, "surfshark", false, "Format Surfshark servers") flagSet.BoolVar(&surfshark, providers.Surfshark, false, "Format Surfshark servers")
flagSet.BoolVar(&torguard, "torguard", false, "Format Torguard servers") flagSet.BoolVar(&torguard, providers.Torguard, false, "Format Torguard servers")
flagSet.BoolVar(&vpnUnlimited, "vpnunlimited", false, "Format VPN Unlimited servers") flagSet.BoolVar(&vpnUnlimited, providers.VPNUnlimited, false, "Format VPN Unlimited servers")
flagSet.BoolVar(&vyprvpn, "vyprvpn", false, "Format Vyprvpn servers") flagSet.BoolVar(&vyprvpn, providers.Vyprvpn, false, "Format Vyprvpn servers")
flagSet.BoolVar(&wevpn, "wevpn", false, "Format WeVPN servers") flagSet.BoolVar(&wevpn, providers.Wevpn, false, "Format WeVPN servers")
flagSet.BoolVar(&windscribe, "windscribe", false, "Format Windscribe servers") flagSet.BoolVar(&windscribe, providers.Windscribe, false, "Format Windscribe servers")
if err := flagSet.Parse(args); err != nil { if err := flagSet.Parse(args); err != nil {
return err return err
} }
@@ -66,45 +67,45 @@ func (c *CLI) FormatServers(args []string) error {
var formatted string var formatted string
switch { switch {
case cyberghost: case cyberghost:
formatted = currentServers.Cyberghost.ToMarkdown() formatted = currentServers.Cyberghost.ToMarkdown(providers.Cyberghost)
case expressvpn: case expressvpn:
formatted = currentServers.Expressvpn.ToMarkdown() formatted = currentServers.Expressvpn.ToMarkdown(providers.Expressvpn)
case fastestvpn: case fastestvpn:
formatted = currentServers.Fastestvpn.ToMarkdown() formatted = currentServers.Fastestvpn.ToMarkdown(providers.Fastestvpn)
case hideMyAss: case hideMyAss:
formatted = currentServers.HideMyAss.ToMarkdown() formatted = currentServers.HideMyAss.ToMarkdown(providers.HideMyAss)
case ipvanish: case ipvanish:
formatted = currentServers.Ipvanish.ToMarkdown() formatted = currentServers.Ipvanish.ToMarkdown(providers.Ipvanish)
case ivpn: case ivpn:
formatted = currentServers.Ivpn.ToMarkdown() formatted = currentServers.Ivpn.ToMarkdown(providers.Ivpn)
case mullvad: case mullvad:
formatted = currentServers.Mullvad.ToMarkdown() formatted = currentServers.Mullvad.ToMarkdown(providers.Mullvad)
case nordvpn: case nordvpn:
formatted = currentServers.Nordvpn.ToMarkdown() formatted = currentServers.Nordvpn.ToMarkdown(providers.Nordvpn)
case perfectPrivacy: case perfectPrivacy:
formatted = currentServers.Perfectprivacy.ToMarkdown() formatted = currentServers.Perfectprivacy.ToMarkdown(providers.Perfectprivacy)
case pia: case pia:
formatted = currentServers.Pia.ToMarkdown() formatted = currentServers.Pia.ToMarkdown(providers.PrivateInternetAccess)
case privado: case privado:
formatted = currentServers.Privado.ToMarkdown() formatted = currentServers.Privado.ToMarkdown(providers.Privado)
case privatevpn: case privatevpn:
formatted = currentServers.Privatevpn.ToMarkdown() formatted = currentServers.Privatevpn.ToMarkdown(providers.Privatevpn)
case protonvpn: case protonvpn:
formatted = currentServers.Protonvpn.ToMarkdown() formatted = currentServers.Protonvpn.ToMarkdown(providers.Protonvpn)
case purevpn: case purevpn:
formatted = currentServers.Purevpn.ToMarkdown() formatted = currentServers.Purevpn.ToMarkdown(providers.Purevpn)
case surfshark: case surfshark:
formatted = currentServers.Surfshark.ToMarkdown() formatted = currentServers.Surfshark.ToMarkdown(providers.Surfshark)
case torguard: case torguard:
formatted = currentServers.Torguard.ToMarkdown() formatted = currentServers.Torguard.ToMarkdown(providers.Torguard)
case vpnUnlimited: case vpnUnlimited:
formatted = currentServers.VPNUnlimited.ToMarkdown() formatted = currentServers.VPNUnlimited.ToMarkdown(providers.VPNUnlimited)
case vyprvpn: case vyprvpn:
formatted = currentServers.Vyprvpn.ToMarkdown() formatted = currentServers.Vyprvpn.ToMarkdown(providers.Vyprvpn)
case wevpn: case wevpn:
formatted = currentServers.Wevpn.ToMarkdown() formatted = currentServers.Wevpn.ToMarkdown(providers.Wevpn)
case windscribe: case windscribe:
formatted = currentServers.Windscribe.ToMarkdown() formatted = currentServers.Windscribe.ToMarkdown(providers.Windscribe)
default: default:
return ErrProviderUnspecified return ErrProviderUnspecified
} }

View File

@@ -14,6 +14,7 @@ import (
"github.com/qdm12/gluetun/internal/configuration/settings" "github.com/qdm12/gluetun/internal/configuration/settings"
"github.com/qdm12/gluetun/internal/constants" "github.com/qdm12/gluetun/internal/constants"
"github.com/qdm12/gluetun/internal/constants/providers"
"github.com/qdm12/gluetun/internal/models" "github.com/qdm12/gluetun/internal/models"
"github.com/qdm12/gluetun/internal/storage" "github.com/qdm12/gluetun/internal/storage"
"github.com/qdm12/gluetun/internal/updater" "github.com/qdm12/gluetun/internal/updater"
@@ -62,8 +63,8 @@ func (c *CLI) Update(ctx context.Context, args []string, logger UpdaterLogger) e
} }
if updateAll { if updateAll {
for _, provider := range constants.AllProviders() { for _, provider := range providers.All() {
if provider == constants.Custom { if provider == providers.Custom {
continue continue
} }
options.Providers = append(options.Providers, provider) options.Providers = append(options.Providers, provider)

View File

@@ -15,10 +15,10 @@ 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
// AddressToPing is the IP address or domain name to // TargetAddress is the address (host or host:port)
// ping 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.
AddressToPing string TargetAddress string
VPN HealthyWait VPN HealthyWait
} }
@@ -41,7 +41,7 @@ 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,
AddressToPing: h.AddressToPing, TargetAddress: h.TargetAddress,
VPN: h.VPN.copy(), VPN: h.VPN.copy(),
} }
} }
@@ -50,7 +50,7 @@ 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.AddressToPing = helpers.MergeWithString(h.AddressToPing, other.AddressToPing) h.TargetAddress = helpers.MergeWithString(h.TargetAddress, other.TargetAddress)
h.VPN.mergeWith(other.VPN) h.VPN.mergeWith(other.VPN)
} }
@@ -59,13 +59,13 @@ 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.AddressToPing = helpers.OverrideWithString(h.AddressToPing, other.AddressToPing) 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")
h.AddressToPing = helpers.DefaultString(h.AddressToPing, "github.com") h.TargetAddress = helpers.DefaultString(h.TargetAddress, "cloudflare.com:443")
h.VPN.setDefaults() h.VPN.setDefaults()
} }
@@ -76,7 +76,7 @@ func (h Health) String() string {
func (h Health) toLinesNode() (node *gotree.Node) { 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("Address to ping: %s", h.AddressToPing) node.Appendf("Target address: %s", h.TargetAddress)
node.AppendNode(h.VPN.toLinesNode("VPN")) node.AppendNode(h.VPN.toLinesNode("VPN"))
return node return node
} }

View File

@@ -4,7 +4,7 @@ import (
"net" "net"
"time" "time"
"github.com/qdm12/golibs/logging" "github.com/qdm12/log"
"inet.af/netaddr" "inet.af/netaddr"
) )
@@ -44,6 +44,15 @@ func CopyUint16Ptr(original *uint16) (copied *uint16) {
return copied return copied
} }
func CopyUint32Ptr(original *uint32) (copied *uint32) {
if original == nil {
return nil
}
copied = new(uint32)
*copied = *original
return copied
}
func CopyIntPtr(original *int) (copied *int) { func CopyIntPtr(original *int) (copied *int) {
if original == nil { if original == nil {
return nil return nil
@@ -62,11 +71,11 @@ func CopyDurationPtr(original *time.Duration) (copied *time.Duration) {
return copied return copied
} }
func CopyLogLevelPtr(original *logging.Level) (copied *logging.Level) { func CopyLogLevelPtr(original *log.Level) (copied *log.Level) {
if original == nil { if original == nil {
return nil return nil
} }
copied = new(logging.Level) copied = new(log.Level)
*copied = *original *copied = *original
return copied return copied
} }

View File

@@ -4,7 +4,7 @@ import (
"net" "net"
"time" "time"
"github.com/qdm12/golibs/logging" "github.com/qdm12/log"
) )
func DefaultInt(existing *int, defaultValue int) ( func DefaultInt(existing *int, defaultValue int) (
@@ -36,6 +36,15 @@ func DefaultUint16(existing *uint16, defaultValue uint16) (
*result = defaultValue *result = defaultValue
return result return result
} }
func DefaultUint32(existing *uint32, defaultValue uint32) (
result *uint32) {
if existing != nil {
return existing
}
result = new(uint32)
*result = defaultValue
return result
}
func DefaultBool(existing *bool, defaultValue bool) ( func DefaultBool(existing *bool, defaultValue bool) (
result *bool) { result *bool) {
@@ -74,12 +83,12 @@ func DefaultDuration(existing *time.Duration,
return result return result
} }
func DefaultLogLevel(existing *logging.Level, func DefaultLogLevel(existing *log.Level,
defaultValue logging.Level) (result *logging.Level) { defaultValue log.Level) (result *log.Level) {
if existing != nil { if existing != nil {
return existing return existing
} }
result = new(logging.Level) result = new(log.Level)
*result = defaultValue *result = defaultValue
return result return result
} }

View File

@@ -5,7 +5,7 @@ import (
"net/http" "net/http"
"time" "time"
"github.com/qdm12/golibs/logging" "github.com/qdm12/log"
"inet.af/netaddr" "inet.af/netaddr"
) )
@@ -78,6 +78,17 @@ func MergeWithUint16(existing, other *uint16) (result *uint16) {
return result return result
} }
func MergeWithUint32(existing, other *uint32) (result *uint32) {
if existing != nil {
return existing
} else if other == nil {
return nil
}
result = new(uint32)
*result = *other
return result
}
func MergeWithIP(existing, other net.IP) (result net.IP) { func MergeWithIP(existing, other net.IP) (result net.IP) {
if existing != nil { if existing != nil {
return existing return existing
@@ -96,13 +107,13 @@ func MergeWithDuration(existing, other *time.Duration) (result *time.Duration) {
return other return other
} }
func MergeWithLogLevel(existing, other *logging.Level) (result *logging.Level) { func MergeWithLogLevel(existing, other *log.Level) (result *log.Level) {
if existing != nil { if existing != nil {
return existing return existing
} else if other == nil { } else if other == nil {
return nil return nil
} }
result = new(logging.Level) result = new(log.Level)
*result = *other *result = *other
return result return result
} }

View File

@@ -5,7 +5,7 @@ import (
"net/http" "net/http"
"time" "time"
"github.com/qdm12/golibs/logging" "github.com/qdm12/log"
"inet.af/netaddr" "inet.af/netaddr"
) )
@@ -68,6 +68,15 @@ func OverrideWithUint16(existing, other *uint16) (result *uint16) {
return result return result
} }
func OverrideWithUint32(existing, other *uint32) (result *uint32) {
if other == nil {
return existing
}
result = new(uint32)
*result = *other
return result
}
func OverrideWithIP(existing, other net.IP) (result net.IP) { func OverrideWithIP(existing, other net.IP) (result net.IP) {
if other == nil { if other == nil {
return existing return existing
@@ -86,11 +95,11 @@ func OverrideWithDuration(existing, other *time.Duration) (result *time.Duration
return result return result
} }
func OverrideWithLogLevel(existing, other *logging.Level) (result *logging.Level) { func OverrideWithLogLevel(existing, other *log.Level) (result *log.Level) {
if other == nil { if other == nil {
return existing return existing
} }
result = new(logging.Level) result = new(log.Level)
*result = *other *result = *other
return result return result
} }

View File

@@ -2,15 +2,15 @@ package settings
import ( import (
"github.com/qdm12/gluetun/internal/configuration/settings/helpers" "github.com/qdm12/gluetun/internal/configuration/settings/helpers"
"github.com/qdm12/golibs/logging"
"github.com/qdm12/gotree" "github.com/qdm12/gotree"
"github.com/qdm12/log"
) )
// Log contains settings to configure the logger. // Log contains settings to configure the logger.
type Log struct { type Log struct {
// Level is the log level of the logger. // Level is the log level of the logger.
// It cannot be nil in the internal state. // It cannot be nil in the internal state.
Level *logging.Level Level *log.Level
} }
func (l Log) validate() (err error) { func (l Log) validate() (err error) {
@@ -37,7 +37,7 @@ func (l *Log) overrideWith(other Log) {
} }
func (l *Log) setDefaults() { func (l *Log) setDefaults() {
l.Level = helpers.DefaultLogLevel(l.Level, logging.LevelInfo) l.Level = helpers.DefaultLogLevel(l.Level, log.LevelInfo)
} }
func (l Log) String() string { func (l Log) String() string {

View File

@@ -2,10 +2,12 @@ package settings
import ( import (
"fmt" "fmt"
"regexp"
"strings" "strings"
"github.com/qdm12/gluetun/internal/configuration/settings/helpers" "github.com/qdm12/gluetun/internal/configuration/settings/helpers"
"github.com/qdm12/gluetun/internal/constants" "github.com/qdm12/gluetun/internal/constants"
"github.com/qdm12/gluetun/internal/constants/providers"
"github.com/qdm12/gluetun/internal/openvpn/parse" "github.com/qdm12/gluetun/internal/openvpn/parse"
"github.com/qdm12/gotree" "github.com/qdm12/gotree"
) )
@@ -73,6 +75,8 @@ type OpenVPN struct {
Flags []string Flags []string
} }
var ivpnAccountID = regexp.MustCompile(`^(i|ivpn)\-[a-zA-Z0-9]{4}\-[a-zA-Z0-9]{4}\-[a-zA-Z0-9]{4}$`)
func (o OpenVPN) validate(vpnProvider string) (err error) { func (o OpenVPN) validate(vpnProvider string) (err error) {
// Validate version // Validate version
validVersions := []string{constants.Openvpn24, constants.Openvpn25} validVersions := []string{constants.Openvpn24, constants.Openvpn25}
@@ -81,13 +85,16 @@ func (o OpenVPN) validate(vpnProvider string) (err error) {
ErrOpenVPNVersionIsNotValid, o.Version, strings.Join(validVersions, ", ")) ErrOpenVPNVersionIsNotValid, o.Version, strings.Join(validVersions, ", "))
} }
isCustom := vpnProvider == constants.Custom isCustom := vpnProvider == providers.Custom
if !isCustom && o.User == "" { if !isCustom && o.User == "" {
return ErrOpenVPNUserIsEmpty return ErrOpenVPNUserIsEmpty
} }
if !isCustom && o.Password == "" { passwordRequired := !isCustom &&
(vpnProvider != providers.Ivpn || !ivpnAccountID.MatchString(o.User))
if passwordRequired && o.Password == "" {
return ErrOpenVPNPasswordIsEmpty return ErrOpenVPNPasswordIsEmpty
} }
@@ -147,8 +154,8 @@ func validateOpenVPNClientCertificate(vpnProvider,
clientCert string) (err error) { clientCert string) (err error) {
switch vpnProvider { switch vpnProvider {
case case
constants.Cyberghost, providers.Cyberghost,
constants.VPNUnlimited: providers.VPNUnlimited:
if clientCert == "" { if clientCert == "" {
return ErrMissingValue return ErrMissingValue
} }
@@ -168,9 +175,9 @@ func validateOpenVPNClientCertificate(vpnProvider,
func validateOpenVPNClientKey(vpnProvider, clientKey string) (err error) { func validateOpenVPNClientKey(vpnProvider, clientKey string) (err error) {
switch vpnProvider { switch vpnProvider {
case case
constants.Cyberghost, providers.Cyberghost,
constants.VPNUnlimited, providers.VPNUnlimited,
constants.Wevpn: providers.Wevpn:
if clientKey == "" { if clientKey == "" {
return ErrMissingValue return ErrMissingValue
} }
@@ -250,7 +257,7 @@ func (o *OpenVPN) overrideWith(other OpenVPN) {
func (o *OpenVPN) setDefaults(vpnProvider string) { func (o *OpenVPN) setDefaults(vpnProvider string) {
o.Version = helpers.DefaultString(o.Version, constants.Openvpn25) o.Version = helpers.DefaultString(o.Version, constants.Openvpn25)
if vpnProvider == constants.Mullvad { if vpnProvider == providers.Mullvad {
o.Password = "m" o.Password = "m"
} }
@@ -260,7 +267,7 @@ func (o *OpenVPN) setDefaults(vpnProvider string) {
o.ClientKey = helpers.DefaultStringPtr(o.ClientKey, "") o.ClientKey = helpers.DefaultStringPtr(o.ClientKey, "")
var defaultEncPreset string var defaultEncPreset string
if vpnProvider == constants.PrivateInternetAccess { if vpnProvider == providers.PrivateInternetAccess {
defaultEncPreset = constants.PIAEncryptionPresetStrong defaultEncPreset = constants.PIAEncryptionPresetStrong
} }
o.PIAEncPreset = helpers.DefaultStringPtr(o.PIAEncPreset, defaultEncPreset) o.PIAEncPreset = helpers.DefaultStringPtr(o.PIAEncPreset, defaultEncPreset)

View File

@@ -0,0 +1,44 @@
package settings
import (
"testing"
"github.com/stretchr/testify/assert"
)
func Test_ivpnAccountID(t *testing.T) {
t.Parallel()
testCases := []struct {
s string
match bool
}{
{},
{s: "abc"},
{s: "i"},
{s: "ivpn"},
{s: "ivpn-aaaa"},
{s: "ivpn-aaaa-aaaa"},
{s: "ivpn-aaaa-aaaa-aaa"},
{s: "ivpn-aaaa-aaaa-aaaa", match: true},
{s: "ivpn-aaaa-aaaa-aaaaa"},
{s: "ivpn-a6B7-fP91-Zh6Y", match: true},
{s: "i-aaaa"},
{s: "i-aaaa-aaaa"},
{s: "i-aaaa-aaaa-aaa"},
{s: "i-aaaa-aaaa-aaaa", match: true},
{s: "i-aaaa-aaaa-aaaaa"},
{s: "i-a6B7-fP91-Zh6Y", match: true},
}
for _, testCase := range testCases {
testCase := testCase
t.Run(testCase.s, func(t *testing.T) {
t.Parallel()
match := ivpnAccountID.MatchString(testCase.s)
assert.Equal(t, testCase.match, match)
})
}
}

View File

@@ -5,6 +5,7 @@ import (
"github.com/qdm12/gluetun/internal/configuration/settings/helpers" "github.com/qdm12/gluetun/internal/configuration/settings/helpers"
"github.com/qdm12/gluetun/internal/constants" "github.com/qdm12/gluetun/internal/constants"
"github.com/qdm12/gluetun/internal/constants/providers"
"github.com/qdm12/gotree" "github.com/qdm12/gotree"
) )
@@ -39,11 +40,11 @@ func (o OpenVPNSelection) validate(vpnProvider string) (err error) {
// Validate TCP // Validate TCP
if *o.TCP && helpers.IsOneOf(vpnProvider, if *o.TCP && helpers.IsOneOf(vpnProvider,
constants.Ipvanish, providers.Ipvanish,
constants.Perfectprivacy, providers.Perfectprivacy,
constants.Privado, providers.Privado,
constants.VPNUnlimited, providers.VPNUnlimited,
constants.Vyprvpn, providers.Vyprvpn,
) { ) {
return fmt.Errorf("%w: for VPN service provider %s", return fmt.Errorf("%w: for VPN service provider %s",
ErrOpenVPNTCPNotSupported, vpnProvider) ErrOpenVPNTCPNotSupported, vpnProvider)
@@ -53,33 +54,39 @@ func (o OpenVPNSelection) validate(vpnProvider string) (err error) {
if *o.CustomPort != 0 { if *o.CustomPort != 0 {
switch vpnProvider { switch vpnProvider {
// no restriction on port // no restriction on port
case constants.Cyberghost, constants.HideMyAss, case providers.Cyberghost, providers.HideMyAss,
constants.PrivateInternetAccess, constants.Privatevpn, providers.PrivateInternetAccess, providers.Privatevpn,
constants.Protonvpn, constants.Torguard: providers.Protonvpn, providers.Torguard:
// no custom port allowed // no custom port allowed
case constants.Expressvpn, constants.Fastestvpn, case providers.Expressvpn, providers.Fastestvpn,
constants.Ipvanish, constants.Nordvpn, providers.Ipvanish, providers.Nordvpn,
constants.Privado, constants.Purevpn, providers.Privado, providers.Purevpn,
constants.Surfshark, constants.VPNUnlimited, providers.Surfshark, providers.VPNUnlimited,
constants.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:
var allowedTCP, allowedUDP []uint16 var allowedTCP, allowedUDP []uint16
switch vpnProvider { switch vpnProvider {
case constants.Ivpn: case providers.Ivpn:
allowedTCP = []uint16{80, 443, 1143} allowedTCP = []uint16{80, 443, 1143}
allowedUDP = []uint16{53, 1194, 2049, 2050} allowedUDP = []uint16{53, 1194, 2049, 2050}
case constants.Mullvad: case providers.Mullvad:
allowedTCP = []uint16{80, 443, 1401} allowedTCP = []uint16{80, 443, 1401}
allowedUDP = []uint16{53, 1194, 1195, 1196, 1197, 1300, 1301, 1302, 1303, 1400} allowedUDP = []uint16{53, 1194, 1195, 1196, 1197, 1300, 1301, 1302, 1303, 1400}
case constants.Perfectprivacy: case providers.Perfectprivacy:
allowedTCP = []uint16{44, 443, 4433} allowedTCP = []uint16{44, 443, 4433}
allowedUDP = []uint16{44, 443, 4433} allowedUDP = []uint16{44, 443, 4433}
case constants.Wevpn: case providers.PrivateInternetAccess:
allowedTCP = []uint16{80, 110, 443}
allowedUDP = []uint16{53, 1194, 1197, 1198, 8080, 9201}
case providers.Protonvpn:
allowedTCP = []uint16{443, 5995, 8443}
allowedUDP = []uint16{80, 443, 1194, 4569, 5060}
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}
case constants.Windscribe: case providers.Windscribe:
allowedTCP = []uint16{21, 22, 80, 123, 143, 443, 587, 1194, 3306, 8080, 54783} allowedTCP = []uint16{21, 22, 80, 123, 143, 443, 587, 1194, 3306, 8080, 54783}
allowedUDP = []uint16{53, 80, 123, 443, 1194, 54783} allowedUDP = []uint16{53, 80, 123, 443, 1194, 54783}
} }
@@ -97,7 +104,7 @@ func (o OpenVPNSelection) validate(vpnProvider string) (err error) {
} }
// Validate EncPreset // Validate EncPreset
if vpnProvider == constants.PrivateInternetAccess { if vpnProvider == providers.PrivateInternetAccess {
validEncryptionPresets := []string{ validEncryptionPresets := []string{
constants.PIAEncryptionPresetNone, constants.PIAEncryptionPresetNone,
constants.PIAEncryptionPresetNormal, constants.PIAEncryptionPresetNormal,
@@ -142,7 +149,7 @@ func (o *OpenVPNSelection) setDefaults(vpnProvider string) {
o.CustomPort = helpers.DefaultUint16(o.CustomPort, 0) o.CustomPort = helpers.DefaultUint16(o.CustomPort, 0)
var defaultEncPreset string var defaultEncPreset string
if vpnProvider == constants.PrivateInternetAccess { if vpnProvider == providers.PrivateInternetAccess {
defaultEncPreset = constants.PIAEncryptionPresetStrong defaultEncPreset = constants.PIAEncryptionPresetStrong
} }
o.PIAEncPreset = helpers.DefaultStringPtr(o.PIAEncPreset, defaultEncPreset) o.PIAEncPreset = helpers.DefaultStringPtr(o.PIAEncPreset, defaultEncPreset)

View File

@@ -6,7 +6,7 @@ import (
"strings" "strings"
"github.com/qdm12/gluetun/internal/configuration/settings/helpers" "github.com/qdm12/gluetun/internal/configuration/settings/helpers"
"github.com/qdm12/gluetun/internal/constants" "github.com/qdm12/gluetun/internal/constants/providers"
"github.com/qdm12/gotree" "github.com/qdm12/gotree"
) )
@@ -28,7 +28,7 @@ func (p PortForwarding) validate(vpnProvider string) (err error) {
} }
// Validate Enabled // Validate Enabled
validProviders := []string{constants.PrivateInternetAccess} validProviders := []string{providers.PrivateInternetAccess}
if !helpers.IsOneOf(vpnProvider, validProviders...) { if !helpers.IsOneOf(vpnProvider, validProviders...) {
return fmt.Errorf("%w: for provider %s, it is only available for %s", return fmt.Errorf("%w: for provider %s, it is only available for %s",
ErrPortForwardingEnabled, vpnProvider, strings.Join(validProviders, ", ")) ErrPortForwardingEnabled, vpnProvider, strings.Join(validProviders, ", "))

View File

@@ -4,7 +4,8 @@ import (
"fmt" "fmt"
"github.com/qdm12/gluetun/internal/configuration/settings/helpers" "github.com/qdm12/gluetun/internal/configuration/settings/helpers"
"github.com/qdm12/gluetun/internal/constants" "github.com/qdm12/gluetun/internal/constants/providers"
"github.com/qdm12/gluetun/internal/constants/vpn"
"github.com/qdm12/gluetun/internal/models" "github.com/qdm12/gluetun/internal/models"
"github.com/qdm12/gotree" "github.com/qdm12/gotree"
) )
@@ -25,15 +26,15 @@ type Provider struct {
func (p *Provider) validate(vpnType string, allServers models.AllServers) (err error) { func (p *Provider) validate(vpnType string, allServers models.AllServers) (err error) {
// Validate Name // Validate Name
var validNames []string var validNames []string
if vpnType == constants.OpenVPN { if vpnType == vpn.OpenVPN {
validNames = constants.AllProviders() validNames = providers.All()
validNames = append(validNames, "pia") // Retro-compatibility validNames = append(validNames, "pia") // Retro-compatibility
} else { // Wireguard } else { // Wireguard
validNames = []string{ validNames = []string{
constants.Custom, providers.Custom,
constants.Ivpn, providers.Ivpn,
constants.Mullvad, providers.Mullvad,
constants.Windscribe, providers.Windscribe,
} }
} }
if !helpers.IsOneOf(*p.Name, validNames...) { if !helpers.IsOneOf(*p.Name, validNames...) {
@@ -75,7 +76,7 @@ func (p *Provider) overrideWith(other Provider) {
} }
func (p *Provider) setDefaults() { func (p *Provider) setDefaults() {
p.Name = helpers.DefaultStringPtr(p.Name, constants.PrivateInternetAccess) p.Name = helpers.DefaultStringPtr(p.Name, providers.PrivateInternetAccess)
p.ServerSelection.setDefaults(*p.Name) p.ServerSelection.setDefaults(*p.Name)
p.PortForwarding.setDefaults() p.PortForwarding.setDefaults()
} }

View File

@@ -8,7 +8,8 @@ import (
"github.com/qdm12/gluetun/internal/configuration/settings/helpers" "github.com/qdm12/gluetun/internal/configuration/settings/helpers"
"github.com/qdm12/gluetun/internal/configuration/settings/validation" "github.com/qdm12/gluetun/internal/configuration/settings/validation"
"github.com/qdm12/gluetun/internal/constants" "github.com/qdm12/gluetun/internal/constants/providers"
"github.com/qdm12/gluetun/internal/constants/vpn"
"github.com/qdm12/gluetun/internal/models" "github.com/qdm12/gluetun/internal/models"
"github.com/qdm12/gotree" "github.com/qdm12/gotree"
) )
@@ -69,7 +70,7 @@ var (
func (ss *ServerSelection) validate(vpnServiceProvider string, func (ss *ServerSelection) validate(vpnServiceProvider string,
allServers models.AllServers) (err error) { allServers models.AllServers) (err error) {
switch ss.VPN { switch ss.VPN {
case constants.OpenVPN, constants.Wireguard: case vpn.OpenVPN, vpn.Wireguard:
default: default:
return fmt.Errorf("%w: %s", ErrVPNTypeNotValid, ss.VPN) return fmt.Errorf("%w: %s", ErrVPNTypeNotValid, ss.VPN)
} }
@@ -90,15 +91,15 @@ func (ss *ServerSelection) validate(vpnServiceProvider string,
} }
if *ss.OwnedOnly && if *ss.OwnedOnly &&
vpnServiceProvider != constants.Mullvad { vpnServiceProvider != providers.Mullvad {
return fmt.Errorf("%w: for VPN service provider %s", return fmt.Errorf("%w: for VPN service provider %s",
ErrOwnedOnlyNotSupported, vpnServiceProvider) ErrOwnedOnlyNotSupported, vpnServiceProvider)
} }
if *ss.FreeOnly && if *ss.FreeOnly &&
!helpers.IsOneOf(vpnServiceProvider, !helpers.IsOneOf(vpnServiceProvider,
constants.Protonvpn, providers.Protonvpn,
constants.VPNUnlimited, providers.VPNUnlimited,
) { ) {
return fmt.Errorf("%w: for VPN service provider %s", return fmt.Errorf("%w: for VPN service provider %s",
ErrFreeOnlyNotSupported, vpnServiceProvider) ErrFreeOnlyNotSupported, vpnServiceProvider)
@@ -106,20 +107,20 @@ func (ss *ServerSelection) validate(vpnServiceProvider string,
if *ss.StreamOnly && if *ss.StreamOnly &&
!helpers.IsOneOf(vpnServiceProvider, !helpers.IsOneOf(vpnServiceProvider,
constants.Protonvpn, providers.Protonvpn,
constants.VPNUnlimited, providers.VPNUnlimited,
) { ) {
return fmt.Errorf("%w: for VPN service provider %s", return fmt.Errorf("%w: for VPN service provider %s",
ErrStreamOnlyNotSupported, vpnServiceProvider) ErrStreamOnlyNotSupported, vpnServiceProvider)
} }
if *ss.MultiHopOnly && if *ss.MultiHopOnly &&
vpnServiceProvider != constants.Surfshark { vpnServiceProvider != providers.Surfshark {
return fmt.Errorf("%w: for VPN service provider %s", return fmt.Errorf("%w: for VPN service provider %s",
ErrMultiHopOnlyNotSupported, vpnServiceProvider) ErrMultiHopOnlyNotSupported, vpnServiceProvider)
} }
if ss.VPN == constants.OpenVPN { if ss.VPN == vpn.OpenVPN {
err = ss.OpenVPN.validate(vpnServiceProvider) err = ss.OpenVPN.validate(vpnServiceProvider)
if err != nil { if err != nil {
return fmt.Errorf("OpenVPN server selection settings: %w", err) return fmt.Errorf("OpenVPN server selection settings: %w", err)
@@ -140,85 +141,85 @@ func getLocationFilterChoices(vpnServiceProvider string, ss *ServerSelection,
ispChoices, nameChoices, hostnameChoices []string, ispChoices, nameChoices, hostnameChoices []string,
err error) { err error) {
switch vpnServiceProvider { switch vpnServiceProvider {
case constants.Custom: case providers.Custom:
case constants.Cyberghost: case providers.Cyberghost:
servers := allServers.GetCyberghost() servers := allServers.GetCyberghost()
countryChoices = validation.CyberghostCountryChoices(servers) countryChoices = validation.ExtractCountries(servers)
hostnameChoices = validation.CyberghostHostnameChoices(servers) hostnameChoices = validation.ExtractHostnames(servers)
case constants.Expressvpn: case providers.Expressvpn:
servers := allServers.GetExpressvpn() servers := allServers.GetExpressvpn()
countryChoices = validation.ExpressvpnCountriesChoices(servers) countryChoices = validation.ExtractCountries(servers)
cityChoices = validation.ExpressvpnCityChoices(servers) cityChoices = validation.ExtractCities(servers)
hostnameChoices = validation.ExpressvpnHostnameChoices(servers) hostnameChoices = validation.ExtractHostnames(servers)
case constants.Fastestvpn: case providers.Fastestvpn:
servers := allServers.GetFastestvpn() servers := allServers.GetFastestvpn()
countryChoices = validation.FastestvpnCountriesChoices(servers) countryChoices = validation.ExtractCountries(servers)
hostnameChoices = validation.FastestvpnHostnameChoices(servers) hostnameChoices = validation.ExtractHostnames(servers)
case constants.HideMyAss: case providers.HideMyAss:
servers := allServers.GetHideMyAss() servers := allServers.GetHideMyAss()
countryChoices = validation.HideMyAssCountryChoices(servers) countryChoices = validation.ExtractCountries(servers)
regionChoices = validation.HideMyAssRegionChoices(servers) regionChoices = validation.ExtractRegions(servers)
cityChoices = validation.HideMyAssCityChoices(servers) cityChoices = validation.ExtractCities(servers)
hostnameChoices = validation.HideMyAssHostnameChoices(servers) hostnameChoices = validation.ExtractHostnames(servers)
case constants.Ipvanish: case providers.Ipvanish:
servers := allServers.GetIpvanish() servers := allServers.GetIpvanish()
countryChoices = validation.IpvanishCountryChoices(servers) countryChoices = validation.ExtractCountries(servers)
cityChoices = validation.IpvanishCityChoices(servers) cityChoices = validation.ExtractCities(servers)
hostnameChoices = validation.IpvanishHostnameChoices(servers) hostnameChoices = validation.ExtractHostnames(servers)
case constants.Ivpn: case providers.Ivpn:
servers := allServers.GetIvpn() servers := allServers.GetIvpn()
countryChoices = validation.IvpnCountryChoices(servers) countryChoices = validation.ExtractCountries(servers)
cityChoices = validation.IvpnCityChoices(servers) cityChoices = validation.ExtractCities(servers)
ispChoices = validation.IvpnISPChoices(servers) ispChoices = validation.ExtractISPs(servers)
hostnameChoices = validation.IvpnHostnameChoices(servers) hostnameChoices = validation.ExtractHostnames(servers)
case constants.Mullvad: case providers.Mullvad:
servers := allServers.GetMullvad() servers := allServers.GetMullvad()
countryChoices = validation.MullvadCountryChoices(servers) countryChoices = validation.ExtractCountries(servers)
cityChoices = validation.MullvadCityChoices(servers) cityChoices = validation.ExtractCities(servers)
ispChoices = validation.MullvadISPChoices(servers) ispChoices = validation.ExtractISPs(servers)
hostnameChoices = validation.MullvadHostnameChoices(servers) hostnameChoices = validation.ExtractHostnames(servers)
case constants.Nordvpn: case providers.Nordvpn:
servers := allServers.GetNordvpn() servers := allServers.GetNordvpn()
regionChoices = validation.NordvpnRegionChoices(servers) regionChoices = validation.ExtractRegions(servers)
hostnameChoices = validation.NordvpnHostnameChoices(servers) hostnameChoices = validation.ExtractHostnames(servers)
case constants.Perfectprivacy: case providers.Perfectprivacy:
servers := allServers.GetPerfectprivacy() servers := allServers.GetPerfectprivacy()
cityChoices = validation.PerfectprivacyCityChoices(servers) cityChoices = validation.ExtractCities(servers)
case constants.Privado: case providers.Privado:
servers := allServers.GetPrivado() servers := allServers.GetPrivado()
countryChoices = validation.PrivadoCountryChoices(servers) countryChoices = validation.ExtractCountries(servers)
regionChoices = validation.PrivadoRegionChoices(servers) regionChoices = validation.ExtractRegions(servers)
cityChoices = validation.PrivadoCityChoices(servers) cityChoices = validation.ExtractCities(servers)
hostnameChoices = validation.PrivadoHostnameChoices(servers) hostnameChoices = validation.ExtractHostnames(servers)
case constants.PrivateInternetAccess: case providers.PrivateInternetAccess:
servers := allServers.GetPia() servers := allServers.GetPia()
regionChoices = validation.PIAGeoChoices(servers) regionChoices = validation.ExtractRegions(servers)
hostnameChoices = validation.PIAHostnameChoices(servers) hostnameChoices = validation.ExtractHostnames(servers)
nameChoices = validation.PIANameChoices(servers) nameChoices = validation.ExtractServerNames(servers)
case constants.Privatevpn: case providers.Privatevpn:
servers := allServers.GetPrivatevpn() servers := allServers.GetPrivatevpn()
countryChoices = validation.PrivatevpnCountryChoices(servers) countryChoices = validation.ExtractCountries(servers)
cityChoices = validation.PrivatevpnCityChoices(servers) cityChoices = validation.ExtractCities(servers)
hostnameChoices = validation.PrivatevpnHostnameChoices(servers) hostnameChoices = validation.ExtractHostnames(servers)
case constants.Protonvpn: case providers.Protonvpn:
servers := allServers.GetProtonvpn() servers := allServers.GetProtonvpn()
countryChoices = validation.ProtonvpnCountryChoices(servers) countryChoices = validation.ExtractCountries(servers)
regionChoices = validation.ProtonvpnRegionChoices(servers) regionChoices = validation.ExtractRegions(servers)
cityChoices = validation.ProtonvpnCityChoices(servers) cityChoices = validation.ExtractCities(servers)
nameChoices = validation.ProtonvpnNameChoices(servers) nameChoices = validation.ExtractServerNames(servers)
hostnameChoices = validation.ProtonvpnHostnameChoices(servers) hostnameChoices = validation.ExtractHostnames(servers)
case constants.Purevpn: case providers.Purevpn:
servers := allServers.GetPurevpn() servers := allServers.GetPurevpn()
countryChoices = validation.PurevpnCountryChoices(servers) countryChoices = validation.ExtractCountries(servers)
regionChoices = validation.PurevpnRegionChoices(servers) regionChoices = validation.ExtractRegions(servers)
cityChoices = validation.PurevpnCityChoices(servers) cityChoices = validation.ExtractCities(servers)
hostnameChoices = validation.PurevpnHostnameChoices(servers) hostnameChoices = validation.ExtractHostnames(servers)
case constants.Surfshark: case providers.Surfshark:
servers := allServers.GetSurfshark() servers := allServers.GetSurfshark()
countryChoices = validation.SurfsharkCountryChoices(servers) countryChoices = validation.ExtractCountries(servers)
cityChoices = validation.SurfsharkCityChoices(servers) cityChoices = validation.ExtractCities(servers)
hostnameChoices = validation.SurfsharkHostnameChoices(servers) hostnameChoices = validation.ExtractHostnames(servers)
regionChoices = validation.SurfsharkRegionChoices(servers) regionChoices = validation.ExtractRegions(servers)
// TODO v4 remove // TODO v4 remove
regionChoices = append(regionChoices, validation.SurfsharkRetroLocChoices()...) regionChoices = append(regionChoices, validation.SurfsharkRetroLocChoices()...)
if err := helpers.AreAllOneOf(ss.Regions, regionChoices); err != nil { if err := helpers.AreAllOneOf(ss.Regions, regionChoices); err != nil {
@@ -227,28 +228,28 @@ func getLocationFilterChoices(vpnServiceProvider string, ss *ServerSelection,
// Retro compatibility // Retro compatibility
// TODO remove in v4 // TODO remove in v4
*ss = surfsharkRetroRegion(*ss) *ss = surfsharkRetroRegion(*ss)
case constants.Torguard: case providers.Torguard:
servers := allServers.GetTorguard() servers := allServers.GetTorguard()
countryChoices = validation.TorguardCountryChoices(servers) countryChoices = validation.ExtractCountries(servers)
cityChoices = validation.TorguardCityChoices(servers) cityChoices = validation.ExtractCities(servers)
hostnameChoices = validation.TorguardHostnameChoices(servers) hostnameChoices = validation.ExtractHostnames(servers)
case constants.VPNUnlimited: case providers.VPNUnlimited:
servers := allServers.GetVPNUnlimited() servers := allServers.GetVPNUnlimited()
countryChoices = validation.VPNUnlimitedCountryChoices(servers) countryChoices = validation.ExtractCountries(servers)
cityChoices = validation.VPNUnlimitedCityChoices(servers) cityChoices = validation.ExtractCities(servers)
hostnameChoices = validation.VPNUnlimitedHostnameChoices(servers) hostnameChoices = validation.ExtractHostnames(servers)
case constants.Vyprvpn: case providers.Vyprvpn:
servers := allServers.GetVyprvpn() servers := allServers.GetVyprvpn()
regionChoices = validation.VyprvpnRegionChoices(servers) regionChoices = validation.ExtractRegions(servers)
case constants.Wevpn: case providers.Wevpn:
servers := allServers.GetWevpn() servers := allServers.GetWevpn()
cityChoices = validation.WevpnCityChoices(servers) cityChoices = validation.ExtractCities(servers)
hostnameChoices = validation.WevpnHostnameChoices(servers) hostnameChoices = validation.ExtractHostnames(servers)
case constants.Windscribe: case providers.Windscribe:
servers := allServers.GetWindscribe() servers := allServers.GetWindscribe()
regionChoices = validation.WindscribeRegionChoices(servers) regionChoices = validation.ExtractRegions(servers)
cityChoices = validation.WindscribeCityChoices(servers) cityChoices = validation.ExtractCities(servers)
hostnameChoices = validation.WindscribeHostnameChoices(servers) hostnameChoices = validation.ExtractHostnames(servers)
default: default:
return nil, nil, nil, nil, nil, nil, fmt.Errorf("%w: %s", ErrVPNProviderNameNotValid, vpnServiceProvider) return nil, nil, nil, nil, nil, nil, fmt.Errorf("%w: %s", ErrVPNProviderNameNotValid, vpnServiceProvider)
} }
@@ -347,7 +348,7 @@ func (ss *ServerSelection) overrideWith(other ServerSelection) {
} }
func (ss *ServerSelection) setDefaults(vpnProvider string) { func (ss *ServerSelection) setDefaults(vpnProvider string) {
ss.VPN = helpers.DefaultString(ss.VPN, constants.OpenVPN) ss.VPN = helpers.DefaultString(ss.VPN, vpn.OpenVPN)
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)
@@ -415,7 +416,7 @@ func (ss ServerSelection) toLinesNode() (node *gotree.Node) {
node.Appendf("Multi-hop only servers: yes") node.Appendf("Multi-hop only servers: yes")
} }
if ss.VPN == constants.OpenVPN { if ss.VPN == vpn.OpenVPN {
node.AppendNode(ss.OpenVPN.toLinesNode()) node.AppendNode(ss.OpenVPN.toLinesNode())
} else { } else {
node.AppendNode(ss.Wireguard.toLinesNode()) node.AppendNode(ss.Wireguard.toLinesNode())

View File

@@ -66,7 +66,7 @@ func Test_Settings_String(t *testing.T) {
| └── Log level: INFO | └── Log level: INFO
├── Health settings: ├── Health settings:
| ├── Server listening address: 127.0.0.1:9999 | ├── Server listening address: 127.0.0.1:9999
| ├── Address to ping: github.com | ├── Target address: cloudflare.com:443
| └── VPN wait durations: | └── VPN wait durations:
| ├── Initial duration: 6s | ├── Initial duration: 6s
| └── Additional duration: 5s | └── Additional duration: 5s

View File

@@ -7,8 +7,8 @@ import (
// System contains settings to configure system related elements. // System contains settings to configure system related elements.
type System struct { type System struct {
PUID *uint16 PUID *uint32
PGID *uint16 PGID *uint32
Timezone string Timezone string
} }
@@ -19,28 +19,28 @@ func (s System) validate() (err error) {
func (s *System) copy() (copied System) { func (s *System) copy() (copied System) {
return System{ return System{
PUID: helpers.CopyUint16Ptr(s.PUID), PUID: helpers.CopyUint32Ptr(s.PUID),
PGID: helpers.CopyUint16Ptr(s.PGID), PGID: helpers.CopyUint32Ptr(s.PGID),
Timezone: s.Timezone, Timezone: s.Timezone,
} }
} }
func (s *System) mergeWith(other System) { func (s *System) mergeWith(other System) {
s.PUID = helpers.MergeWithUint16(s.PUID, other.PUID) s.PUID = helpers.MergeWithUint32(s.PUID, other.PUID)
s.PGID = helpers.MergeWithUint16(s.PGID, other.PGID) s.PGID = helpers.MergeWithUint32(s.PGID, other.PGID)
s.Timezone = helpers.MergeWithString(s.Timezone, other.Timezone) s.Timezone = helpers.MergeWithString(s.Timezone, other.Timezone)
} }
func (s *System) overrideWith(other System) { func (s *System) overrideWith(other System) {
s.PUID = helpers.OverrideWithUint16(s.PUID, other.PUID) s.PUID = helpers.OverrideWithUint32(s.PUID, other.PUID)
s.PGID = helpers.OverrideWithUint16(s.PGID, other.PGID) s.PGID = helpers.OverrideWithUint32(s.PGID, other.PGID)
s.Timezone = helpers.OverrideWithString(s.Timezone, other.Timezone) s.Timezone = helpers.OverrideWithString(s.Timezone, other.Timezone)
} }
func (s *System) setDefaults() { func (s *System) setDefaults() {
const defaultID = 1000 const defaultID = 1000
s.PUID = helpers.DefaultUint16(s.PUID, defaultID) s.PUID = helpers.DefaultUint32(s.PUID, defaultID)
s.PGID = helpers.DefaultUint16(s.PGID, defaultID) s.PGID = helpers.DefaultUint32(s.PGID, defaultID)
} }
func (s System) String() string { func (s System) String() string {

View File

@@ -7,7 +7,7 @@ import (
"time" "time"
"github.com/qdm12/gluetun/internal/configuration/settings/helpers" "github.com/qdm12/gluetun/internal/configuration/settings/helpers"
"github.com/qdm12/gluetun/internal/constants" "github.com/qdm12/gluetun/internal/constants/providers"
"github.com/qdm12/gotree" "github.com/qdm12/gotree"
) )
@@ -42,8 +42,8 @@ func (u Updater) Validate() (err error) {
for i, provider := range u.Providers { for i, provider := range u.Providers {
valid := false valid := false
for _, validProvider := range constants.AllProviders() { for _, validProvider := range providers.All() {
if validProvider == constants.Custom { if validProvider == providers.Custom {
continue continue
} }
@@ -93,7 +93,7 @@ func (u *Updater) SetDefaults(vpnProvider string) {
u.Period = helpers.DefaultDuration(u.Period, 0) u.Period = helpers.DefaultDuration(u.Period, 0)
u.DNSAddress = helpers.DefaultIP(u.DNSAddress, net.IPv4(1, 1, 1, 1)) u.DNSAddress = helpers.DefaultIP(u.DNSAddress, net.IPv4(1, 1, 1, 1))
u.CLI = helpers.DefaultBool(u.CLI, false) u.CLI = helpers.DefaultBool(u.CLI, false)
if len(u.Providers) == 0 && vpnProvider != constants.Custom { if len(u.Providers) == 0 && vpnProvider != providers.Custom {
u.Providers = []string{vpnProvider} u.Providers = []string{vpnProvider}
} }
} }

View File

@@ -1,21 +0,0 @@
package validation
import (
"github.com/qdm12/gluetun/internal/models"
)
func CyberghostCountryChoices(servers []models.CyberghostServer) (choices []string) {
choices = make([]string, len(servers))
for i := range servers {
choices[i] = servers[i].Country
}
return makeUnique(choices)
}
func CyberghostHostnameChoices(servers []models.CyberghostServer) (choices []string) {
choices = make([]string, len(servers))
for i := range servers {
choices[i] = servers[i].Hostname
}
return makeUnique(choices)
}

View File

@@ -1,29 +0,0 @@
package validation
import (
"github.com/qdm12/gluetun/internal/models"
)
func ExpressvpnCountriesChoices(servers []models.ExpressvpnServer) (choices []string) {
choices = make([]string, len(servers))
for i := range servers {
choices[i] = servers[i].Country
}
return makeUnique(choices)
}
func ExpressvpnCityChoices(servers []models.ExpressvpnServer) (choices []string) {
choices = make([]string, len(servers))
for i := range servers {
choices[i] = servers[i].City
}
return makeUnique(choices)
}
func ExpressvpnHostnameChoices(servers []models.ExpressvpnServer) (choices []string) {
choices = make([]string, len(servers))
for i := range servers {
choices[i] = servers[i].Hostname
}
return makeUnique(choices)
}

View File

@@ -1,21 +0,0 @@
package validation
import (
"github.com/qdm12/gluetun/internal/models"
)
func FastestvpnCountriesChoices(servers []models.FastestvpnServer) (choices []string) {
choices = make([]string, len(servers))
for i := range servers {
choices[i] = servers[i].Country
}
return makeUnique(choices)
}
func FastestvpnHostnameChoices(servers []models.FastestvpnServer) (choices []string) {
choices = make([]string, len(servers))
for i := range servers {
choices[i] = servers[i].Hostname
}
return makeUnique(choices)
}

View File

@@ -1,23 +0,0 @@
package validation
import "sort"
func makeUnique(choices []string) (uniqueChoices []string) {
seen := make(map[string]struct{}, len(choices))
uniqueChoices = make([]string, 0, len(uniqueChoices))
for _, choice := range choices {
if _, ok := seen[choice]; ok {
continue
}
seen[choice] = struct{}{}
uniqueChoices = append(uniqueChoices, choice)
}
sort.Slice(uniqueChoices, func(i, j int) bool {
return uniqueChoices[i] < uniqueChoices[j]
})
return uniqueChoices
}

View File

@@ -1,37 +0,0 @@
package validation
import (
"github.com/qdm12/gluetun/internal/models"
)
func HideMyAssCountryChoices(servers []models.HideMyAssServer) (choices []string) {
choices = make([]string, len(servers))
for i := range servers {
choices[i] = servers[i].Country
}
return makeUnique(choices)
}
func HideMyAssRegionChoices(servers []models.HideMyAssServer) (choices []string) {
choices = make([]string, len(servers))
for i := range servers {
choices[i] = servers[i].Region
}
return makeUnique(choices)
}
func HideMyAssCityChoices(servers []models.HideMyAssServer) (choices []string) {
choices = make([]string, len(servers))
for i := range servers {
choices[i] = servers[i].City
}
return makeUnique(choices)
}
func HideMyAssHostnameChoices(servers []models.HideMyAssServer) (choices []string) {
choices = make([]string, len(servers))
for i := range servers {
choices[i] = servers[i].Hostname
}
return makeUnique(choices)
}

View File

@@ -1,29 +0,0 @@
package validation
import (
"github.com/qdm12/gluetun/internal/models"
)
func IpvanishCountryChoices(servers []models.IpvanishServer) (choices []string) {
choices = make([]string, len(servers))
for i := range servers {
choices[i] = servers[i].Country
}
return makeUnique(choices)
}
func IpvanishCityChoices(servers []models.IpvanishServer) (choices []string) {
choices = make([]string, len(servers))
for i := range servers {
choices[i] = servers[i].City
}
return makeUnique(choices)
}
func IpvanishHostnameChoices(servers []models.IpvanishServer) (choices []string) {
choices = make([]string, len(servers))
for i := range servers {
choices[i] = servers[i].Hostname
}
return makeUnique(choices)
}

View File

@@ -1,37 +0,0 @@
package validation
import (
"github.com/qdm12/gluetun/internal/models"
)
func IvpnCountryChoices(servers []models.IvpnServer) (choices []string) {
choices = make([]string, len(servers))
for i := range servers {
choices[i] = servers[i].Country
}
return makeUnique(choices)
}
func IvpnCityChoices(servers []models.IvpnServer) (choices []string) {
choices = make([]string, len(servers))
for i := range servers {
choices[i] = servers[i].City
}
return makeUnique(choices)
}
func IvpnISPChoices(servers []models.IvpnServer) (choices []string) {
choices = make([]string, len(servers))
for i := range servers {
choices[i] = servers[i].ISP
}
return makeUnique(choices)
}
func IvpnHostnameChoices(servers []models.IvpnServer) (choices []string) {
choices = make([]string, len(servers))
for i := range servers {
choices[i] = servers[i].Hostname
}
return makeUnique(choices)
}

View File

@@ -1,37 +0,0 @@
package validation
import (
"github.com/qdm12/gluetun/internal/models"
)
func MullvadCountryChoices(servers []models.MullvadServer) (choices []string) {
choices = make([]string, len(servers))
for i := range servers {
choices[i] = servers[i].Country
}
return makeUnique(choices)
}
func MullvadCityChoices(servers []models.MullvadServer) (choices []string) {
choices = make([]string, len(servers))
for i := range servers {
choices[i] = servers[i].City
}
return makeUnique(choices)
}
func MullvadHostnameChoices(servers []models.MullvadServer) (choices []string) {
choices = make([]string, len(servers))
for i := range servers {
choices[i] = servers[i].Hostname
}
return makeUnique(choices)
}
func MullvadISPChoices(servers []models.MullvadServer) (choices []string) {
choices = make([]string, len(servers))
for i := range servers {
choices[i] = servers[i].ISP
}
return makeUnique(choices)
}

View File

@@ -1,21 +0,0 @@
package validation
import (
"github.com/qdm12/gluetun/internal/models"
)
func NordvpnRegionChoices(servers []models.NordvpnServer) (choices []string) {
choices = make([]string, len(servers))
for i := range servers {
choices[i] = servers[i].Region
}
return makeUnique(choices)
}
func NordvpnHostnameChoices(servers []models.NordvpnServer) (choices []string) {
choices = make([]string, len(servers))
for i := range servers {
choices[i] = servers[i].Hostname
}
return makeUnique(choices)
}

View File

@@ -1,13 +0,0 @@
package validation
import (
"github.com/qdm12/gluetun/internal/models"
)
func PerfectprivacyCityChoices(servers []models.PerfectprivacyServer) (choices []string) {
choices = make([]string, len(servers))
for i := range servers {
choices[i] = servers[i].City
}
return makeUnique(choices)
}

View File

@@ -1,29 +0,0 @@
package validation
import (
"github.com/qdm12/gluetun/internal/models"
)
func PIAGeoChoices(servers []models.PIAServer) (choices []string) {
choices = make([]string, len(servers))
for i := range servers {
choices[i] = servers[i].Region
}
return makeUnique(choices)
}
func PIAHostnameChoices(servers []models.PIAServer) (choices []string) {
choices = make([]string, len(servers))
for i := range servers {
choices[i] = servers[i].Hostname
}
return makeUnique(choices)
}
func PIANameChoices(servers []models.PIAServer) (choices []string) {
choices = make([]string, len(servers))
for i := range servers {
choices[i] = servers[i].ServerName
}
return makeUnique(choices)
}

View File

@@ -1,35 +0,0 @@
package validation
import "github.com/qdm12/gluetun/internal/models"
func PrivadoCountryChoices(servers []models.PrivadoServer) (choices []string) {
choices = make([]string, len(servers))
for i := range servers {
choices[i] = servers[i].Country
}
return makeUnique(choices)
}
func PrivadoRegionChoices(servers []models.PrivadoServer) (choices []string) {
choices = make([]string, len(servers))
for i := range servers {
choices[i] = servers[i].Region
}
return makeUnique(choices)
}
func PrivadoCityChoices(servers []models.PrivadoServer) (choices []string) {
choices = make([]string, len(servers))
for i := range servers {
choices[i] = servers[i].City
}
return makeUnique(choices)
}
func PrivadoHostnameChoices(servers []models.PrivadoServer) (choices []string) {
choices = make([]string, len(servers))
for i := range servers {
choices[i] = servers[i].Hostname
}
return makeUnique(choices)
}

View File

@@ -1,27 +0,0 @@
package validation
import "github.com/qdm12/gluetun/internal/models"
func PrivatevpnCountryChoices(servers []models.PrivatevpnServer) (choices []string) {
choices = make([]string, len(servers))
for i := range servers {
choices[i] = servers[i].Country
}
return makeUnique(choices)
}
func PrivatevpnCityChoices(servers []models.PrivatevpnServer) (choices []string) {
choices = make([]string, len(servers))
for i := range servers {
choices[i] = servers[i].City
}
return makeUnique(choices)
}
func PrivatevpnHostnameChoices(servers []models.PrivatevpnServer) (choices []string) {
choices = make([]string, len(servers))
for i := range servers {
choices[i] = servers[i].Hostname
}
return makeUnique(choices)
}

View File

@@ -1,43 +0,0 @@
package validation
import "github.com/qdm12/gluetun/internal/models"
func ProtonvpnCountryChoices(servers []models.ProtonvpnServer) (choices []string) {
choices = make([]string, len(servers))
for i := range servers {
choices[i] = servers[i].Country
}
return makeUnique(choices)
}
func ProtonvpnRegionChoices(servers []models.ProtonvpnServer) (choices []string) {
choices = make([]string, len(servers))
for i := range servers {
choices[i] = servers[i].Region
}
return makeUnique(choices)
}
func ProtonvpnCityChoices(servers []models.ProtonvpnServer) (choices []string) {
choices = make([]string, len(servers))
for i := range servers {
choices[i] = servers[i].City
}
return makeUnique(choices)
}
func ProtonvpnNameChoices(servers []models.ProtonvpnServer) (choices []string) {
choices = make([]string, len(servers))
for i := range servers {
choices[i] = servers[i].Name
}
return makeUnique(choices)
}
func ProtonvpnHostnameChoices(servers []models.ProtonvpnServer) (choices []string) {
choices = make([]string, len(servers))
for i := range servers {
choices[i] = servers[i].Hostname
}
return makeUnique(choices)
}

View File

@@ -1,35 +0,0 @@
package validation
import "github.com/qdm12/gluetun/internal/models"
func PurevpnRegionChoices(servers []models.PurevpnServer) (choices []string) {
choices = make([]string, len(servers))
for i := range servers {
choices[i] = servers[i].Region
}
return makeUnique(choices)
}
func PurevpnCountryChoices(servers []models.PurevpnServer) (choices []string) {
choices = make([]string, len(servers))
for i := range servers {
choices[i] = servers[i].Country
}
return makeUnique(choices)
}
func PurevpnCityChoices(servers []models.PurevpnServer) (choices []string) {
choices = make([]string, len(servers))
for i := range servers {
choices[i] = servers[i].City
}
return makeUnique(choices)
}
func PurevpnHostnameChoices(servers []models.PurevpnServer) (choices []string) {
choices = make([]string, len(servers))
for i := range servers {
choices[i] = servers[i].Hostname
}
return makeUnique(choices)
}

View File

@@ -0,0 +1,111 @@
package validation
import (
"sort"
"github.com/qdm12/gluetun/internal/models"
)
func sortedInsert(ss []string, s string) []string {
i := sort.SearchStrings(ss, s)
ss = append(ss, "")
copy(ss[i+1:], ss[i:])
ss[i] = s
return ss
}
func ExtractCountries(servers []models.Server) (values []string) {
seen := make(map[string]struct{}, len(servers))
values = make([]string, 0, len(servers))
for _, server := range servers {
value := server.Country
_, alreadySeen := seen[value]
if alreadySeen {
continue
}
seen[value] = struct{}{}
values = sortedInsert(values, value)
}
return values
}
func ExtractRegions(servers []models.Server) (values []string) {
seen := make(map[string]struct{}, len(servers))
values = make([]string, 0, len(servers))
for _, server := range servers {
value := server.Region
_, alreadySeen := seen[value]
if alreadySeen {
continue
}
seen[value] = struct{}{}
values = sortedInsert(values, value)
}
return values
}
func ExtractCities(servers []models.Server) (values []string) {
seen := make(map[string]struct{}, len(servers))
values = make([]string, 0, len(servers))
for _, server := range servers {
value := server.City
_, alreadySeen := seen[value]
if alreadySeen {
continue
}
seen[value] = struct{}{}
values = sortedInsert(values, value)
}
return values
}
func ExtractISPs(servers []models.Server) (values []string) {
seen := make(map[string]struct{}, len(servers))
values = make([]string, 0, len(servers))
for _, server := range servers {
value := server.ISP
_, alreadySeen := seen[value]
if alreadySeen {
continue
}
seen[value] = struct{}{}
values = sortedInsert(values, value)
}
return values
}
func ExtractServerNames(servers []models.Server) (values []string) {
seen := make(map[string]struct{}, len(servers))
values = make([]string, 0, len(servers))
for _, server := range servers {
value := server.ServerName
_, alreadySeen := seen[value]
if alreadySeen {
continue
}
seen[value] = struct{}{}
values = sortedInsert(values, value)
}
return values
}
func ExtractHostnames(servers []models.Server) (values []string) {
seen := make(map[string]struct{}, len(servers))
values = make([]string, 0, len(servers))
for _, server := range servers {
value := server.Hostname
_, alreadySeen := seen[value]
if alreadySeen {
continue
}
seen[value] = struct{}{}
values = sortedInsert(values, value)
}
return values
}

View File

@@ -1,44 +1,9 @@
package validation package validation
import ( import (
"sort"
"github.com/qdm12/gluetun/internal/constants" "github.com/qdm12/gluetun/internal/constants"
"github.com/qdm12/gluetun/internal/models"
) )
func SurfsharkRegionChoices(servers []models.SurfsharkServer) (choices []string) {
choices = make([]string, len(servers))
for i := range servers {
choices[i] = servers[i].Region
}
return makeUnique(choices)
}
func SurfsharkCountryChoices(servers []models.SurfsharkServer) (choices []string) {
choices = make([]string, len(servers))
for i := range servers {
choices[i] = servers[i].Country
}
return makeUnique(choices)
}
func SurfsharkCityChoices(servers []models.SurfsharkServer) (choices []string) {
choices = make([]string, len(servers))
for i := range servers {
choices[i] = servers[i].City
}
return makeUnique(choices)
}
func SurfsharkHostnameChoices(servers []models.SurfsharkServer) (choices []string) {
choices = make([]string, len(servers))
for i := range servers {
choices[i] = servers[i].Hostname
}
return makeUnique(choices)
}
// TODO remove in v4. // TODO remove in v4.
func SurfsharkRetroLocChoices() (choices []string) { func SurfsharkRetroLocChoices() (choices []string) {
locationData := constants.SurfsharkLocationData() locationData := constants.SurfsharkLocationData()
@@ -49,12 +14,8 @@ func SurfsharkRetroLocChoices() (choices []string) {
continue continue
} }
seen[data.RetroLoc] = struct{}{} seen[data.RetroLoc] = struct{}{}
choices = append(choices, data.RetroLoc) choices = sortedInsert(choices, data.RetroLoc)
} }
sort.Slice(choices, func(i, j int) bool {
return choices[i] < choices[j]
})
return choices return choices
} }

View File

@@ -1,29 +0,0 @@
package validation
import (
"github.com/qdm12/gluetun/internal/models"
)
func TorguardCountryChoices(servers []models.TorguardServer) (choices []string) {
choices = make([]string, len(servers))
for i := range servers {
choices[i] = servers[i].Country
}
return makeUnique(choices)
}
func TorguardCityChoices(servers []models.TorguardServer) (choices []string) {
choices = make([]string, len(servers))
for i := range servers {
choices[i] = servers[i].City
}
return makeUnique(choices)
}
func TorguardHostnameChoices(servers []models.TorguardServer) (choices []string) {
choices = make([]string, len(servers))
for i := range servers {
choices[i] = servers[i].Hostname
}
return makeUnique(choices)
}

View File

@@ -1,29 +0,0 @@
package validation
import (
"github.com/qdm12/gluetun/internal/models"
)
func VPNUnlimitedCountryChoices(servers []models.VPNUnlimitedServer) (choices []string) {
choices = make([]string, len(servers))
for i := range servers {
choices[i] = servers[i].Country
}
return makeUnique(choices)
}
func VPNUnlimitedCityChoices(servers []models.VPNUnlimitedServer) (choices []string) {
choices = make([]string, len(servers))
for i := range servers {
choices[i] = servers[i].City
}
return makeUnique(choices)
}
func VPNUnlimitedHostnameChoices(servers []models.VPNUnlimitedServer) (choices []string) {
choices = make([]string, len(servers))
for i := range servers {
choices[i] = servers[i].Hostname
}
return makeUnique(choices)
}

View File

@@ -1,13 +0,0 @@
package validation
import (
"github.com/qdm12/gluetun/internal/models"
)
func VyprvpnRegionChoices(servers []models.VyprvpnServer) (choices []string) {
choices = make([]string, len(servers))
for i := range servers {
choices[i] = servers[i].Region
}
return makeUnique(choices)
}

View File

@@ -1,19 +0,0 @@
package validation
import "github.com/qdm12/gluetun/internal/models"
func WevpnCityChoices(servers []models.WevpnServer) (choices []string) {
choices = make([]string, len(servers))
for i := range servers {
choices[i] = servers[i].City
}
return makeUnique(choices)
}
func WevpnHostnameChoices(servers []models.WevpnServer) (choices []string) {
choices = make([]string, len(servers))
for i := range servers {
choices[i] = servers[i].Hostname
}
return makeUnique(choices)
}

View File

@@ -1,27 +0,0 @@
package validation
import "github.com/qdm12/gluetun/internal/models"
func WindscribeRegionChoices(servers []models.WindscribeServer) (choices []string) {
choices = make([]string, len(servers))
for i := range servers {
choices[i] = servers[i].Region
}
return makeUnique(choices)
}
func WindscribeCityChoices(servers []models.WindscribeServer) (choices []string) {
choices = make([]string, len(servers))
for i := range servers {
choices[i] = servers[i].City
}
return makeUnique(choices)
}
func WindscribeHostnameChoices(servers []models.WindscribeServer) (choices []string) {
choices = make([]string, len(servers))
for i := range servers {
choices[i] = servers[i].Hostname
}
return makeUnique(choices)
}

View File

@@ -5,7 +5,7 @@ import (
"strings" "strings"
"github.com/qdm12/gluetun/internal/configuration/settings/helpers" "github.com/qdm12/gluetun/internal/configuration/settings/helpers"
"github.com/qdm12/gluetun/internal/constants" "github.com/qdm12/gluetun/internal/constants/vpn"
"github.com/qdm12/gluetun/internal/models" "github.com/qdm12/gluetun/internal/models"
"github.com/qdm12/gotree" "github.com/qdm12/gotree"
) )
@@ -23,7 +23,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(allServers models.AllServers) (err error) { func (v *VPN) validate(allServers models.AllServers) (err error) {
// Validate Type // Validate Type
validVPNTypes := []string{constants.OpenVPN, constants.Wireguard} validVPNTypes := []string{vpn.OpenVPN, vpn.Wireguard}
if !helpers.IsOneOf(v.Type, validVPNTypes...) { if !helpers.IsOneOf(v.Type, validVPNTypes...) {
return fmt.Errorf("%w: %q and can only be one of %s", return fmt.Errorf("%w: %q and can only be one of %s",
ErrVPNTypeNotValid, v.Type, strings.Join(validVPNTypes, ", ")) ErrVPNTypeNotValid, v.Type, strings.Join(validVPNTypes, ", "))
@@ -34,7 +34,7 @@ func (v *VPN) validate(allServers models.AllServers) (err error) {
return fmt.Errorf("provider settings: %w", err) return fmt.Errorf("provider settings: %w", err)
} }
if v.Type == constants.OpenVPN { if v.Type == vpn.OpenVPN {
err := v.OpenVPN.validate(*v.Provider.Name) err := v.OpenVPN.validate(*v.Provider.Name)
if err != nil { if err != nil {
return fmt.Errorf("OpenVPN settings: %w", err) return fmt.Errorf("OpenVPN settings: %w", err)
@@ -73,7 +73,7 @@ func (v *VPN) overrideWith(other VPN) {
} }
func (v *VPN) setDefaults() { func (v *VPN) setDefaults() {
v.Type = helpers.DefaultString(v.Type, constants.OpenVPN) v.Type = helpers.DefaultString(v.Type, vpn.OpenVPN)
v.Provider.setDefaults() v.Provider.setDefaults()
v.OpenVPN.setDefaults(*v.Provider.Name) v.OpenVPN.setDefaults(*v.Provider.Name)
v.Wireguard.setDefaults() v.Wireguard.setDefaults()
@@ -88,7 +88,7 @@ func (v VPN) toLinesNode() (node *gotree.Node) {
node.AppendNode(v.Provider.toLinesNode()) node.AppendNode(v.Provider.toLinesNode())
if v.Type == constants.OpenVPN { if v.Type == vpn.OpenVPN {
node.AppendNode(v.OpenVPN.toLinesNode()) node.AppendNode(v.OpenVPN.toLinesNode())
} else { } else {
node.AppendNode(v.Wireguard.toLinesNode()) node.AppendNode(v.Wireguard.toLinesNode())

View File

@@ -6,7 +6,7 @@ import (
"regexp" "regexp"
"github.com/qdm12/gluetun/internal/configuration/settings/helpers" "github.com/qdm12/gluetun/internal/configuration/settings/helpers"
"github.com/qdm12/gluetun/internal/constants" "github.com/qdm12/gluetun/internal/constants/providers"
"github.com/qdm12/gotree" "github.com/qdm12/gotree"
"golang.zx2c4.com/wireguard/wgctrl/wgtypes" "golang.zx2c4.com/wireguard/wgctrl/wgtypes"
) )
@@ -35,10 +35,10 @@ var regexpInterfaceName = regexp.MustCompile(`^[a-zA-Z0-9_]+$`)
// It should only be ran if the VPN type chosen is Wireguard. // It should only be ran if the VPN type chosen is Wireguard.
func (w Wireguard) validate(vpnProvider string) (err error) { func (w Wireguard) validate(vpnProvider string) (err error) {
if !helpers.IsOneOf(vpnProvider, if !helpers.IsOneOf(vpnProvider,
constants.Custom, providers.Custom,
constants.Ivpn, providers.Ivpn,
constants.Mullvad, providers.Mullvad,
constants.Windscribe, providers.Windscribe,
) { ) {
// do not validate for VPN provider not supporting Wireguard // do not validate for VPN provider not supporting Wireguard
return nil return nil

View File

@@ -5,7 +5,7 @@ import (
"net" "net"
"github.com/qdm12/gluetun/internal/configuration/settings/helpers" "github.com/qdm12/gluetun/internal/configuration/settings/helpers"
"github.com/qdm12/gluetun/internal/constants" "github.com/qdm12/gluetun/internal/constants/providers"
"github.com/qdm12/gotree" "github.com/qdm12/gotree"
"golang.zx2c4.com/wireguard/wgctrl/wgtypes" "golang.zx2c4.com/wireguard/wgctrl/wgtypes"
) )
@@ -36,8 +36,8 @@ type WireguardSelection struct {
func (w WireguardSelection) validate(vpnProvider string) (err error) { func (w WireguardSelection) validate(vpnProvider string) (err error) {
// Validate EndpointIP // Validate EndpointIP
switch vpnProvider { switch vpnProvider {
case constants.Ivpn, constants.Mullvad, constants.Windscribe: // endpoint IP addresses are baked in case providers.Ivpn, providers.Mullvad, providers.Windscribe: // endpoint IP addresses are baked in
case constants.Custom: case providers.Custom:
if len(w.EndpointIP) == 0 { if len(w.EndpointIP) == 0 {
return ErrWireguardEndpointIPNotSet return ErrWireguardEndpointIPNotSet
} }
@@ -47,23 +47,23 @@ func (w WireguardSelection) validate(vpnProvider string) (err error) {
// Validate EndpointPort // Validate EndpointPort
switch vpnProvider { switch vpnProvider {
// EndpointPort is required // EndpointPort is required
case constants.Custom: case providers.Custom:
if *w.EndpointPort == 0 { if *w.EndpointPort == 0 {
return ErrWireguardEndpointPortNotSet return ErrWireguardEndpointPortNotSet
} }
case constants.Ivpn, constants.Mullvad, constants.Windscribe: case providers.Ivpn, providers.Mullvad, providers.Windscribe:
// EndpointPort is optional and can be 0 // EndpointPort is optional and can be 0
if *w.EndpointPort == 0 { if *w.EndpointPort == 0 {
break // no custom endpoint port set break // no custom endpoint port set
} }
if vpnProvider == constants.Mullvad { if vpnProvider == providers.Mullvad {
break // no restriction on custom endpoint port value break // no restriction on custom endpoint port value
} }
var allowed []uint16 var allowed []uint16
switch vpnProvider { switch vpnProvider {
case constants.Ivpn: case providers.Ivpn:
allowed = []uint16{2049, 2050, 53, 30587, 41893, 48574, 58237} allowed = []uint16{2049, 2050, 53, 30587, 41893, 48574, 58237}
case constants.Windscribe: case providers.Windscribe:
allowed = []uint16{53, 80, 123, 443, 1194, 65142} allowed = []uint16{53, 80, 123, 443, 1194, 65142}
} }
@@ -78,8 +78,8 @@ func (w WireguardSelection) validate(vpnProvider string) (err error) {
// Validate PublicKey // Validate PublicKey
switch vpnProvider { switch vpnProvider {
case constants.Ivpn, constants.Mullvad, constants.Windscribe: // public keys are baked in case providers.Ivpn, providers.Mullvad, providers.Windscribe: // public keys are baked in
case constants.Custom: case providers.Custom:
if w.PublicKey == "" { if w.PublicKey == "" {
return ErrWireguardPublicKeyNotSet return ErrWireguardPublicKeyNotSet
} }

View File

@@ -10,7 +10,7 @@ import (
func (r *Reader) ReadHealth() (health settings.Health, err error) { func (r *Reader) ReadHealth() (health settings.Health, err error) {
health.ServerAddress = os.Getenv("HEALTH_SERVER_ADDRESS") health.ServerAddress = os.Getenv("HEALTH_SERVER_ADDRESS")
health.AddressToPing = os.Getenv("HEALTH_ADDRESS_TO_PING") _, health.TargetAddress = r.getEnvWithRetro("HEALTH_TARGET_ADDRESS", "HEALTH_ADDRESS_TO_PING")
health.VPN.Initial, err = r.readDurationWithRetro( health.VPN.Initial, err = r.readDurationWithRetro(
"HEALTH_VPN_DURATION_INITIAL", "HEALTH_VPN_DURATION_INITIAL",
@@ -19,7 +19,7 @@ func (r *Reader) ReadHealth() (health settings.Health, err error) {
return health, err return health, err
} }
health.VPN.Initial, err = r.readDurationWithRetro( health.VPN.Addition, err = r.readDurationWithRetro(
"HEALTH_VPN_DURATION_ADDITION", "HEALTH_VPN_DURATION_ADDITION",
"HEALTH_OPENVPN_DURATION_ADDITION") "HEALTH_OPENVPN_DURATION_ADDITION")
if err != nil { if err != nil {

View File

@@ -135,5 +135,5 @@ func unsetEnvKeys(envKeys []string, err error) (newErr error) {
} }
func stringPtr(s string) *string { return &s } func stringPtr(s string) *string { return &s }
func uint16Ptr(n uint16) *uint16 { return &n } func uint32Ptr(n uint32) *uint32 { return &n }
func boolPtr(b bool) *bool { return &b } func boolPtr(b bool) *bool { return &b }

View File

@@ -20,3 +20,7 @@ func setTestEnv(t *testing.T, key, value string) {
}) })
require.NoError(t, err) require.NoError(t, err)
} }
func TestXxx(t *testing.T) {
t.Log(int(^uint32(0)))
}

View File

@@ -7,7 +7,7 @@ import (
"strings" "strings"
"github.com/qdm12/gluetun/internal/configuration/settings" "github.com/qdm12/gluetun/internal/configuration/settings"
"github.com/qdm12/golibs/logging" "github.com/qdm12/log"
) )
func readLog() (log settings.Log, err error) { func readLog() (log settings.Log, err error) {
@@ -19,13 +19,13 @@ func readLog() (log settings.Log, err error) {
return log, nil return log, nil
} }
func readLogLevel() (level *logging.Level, err error) { func readLogLevel() (level *log.Level, err error) {
s := os.Getenv("LOG_LEVEL") s := os.Getenv("LOG_LEVEL")
if s == "" { if s == "" {
return nil, nil //nolint:nilnil return nil, nil //nolint:nilnil
} }
level = new(logging.Level) level = new(log.Level)
*level, err = parseLogLevel(s) *level, err = parseLogLevel(s)
if err != nil { if err != nil {
return nil, fmt.Errorf("environment variable LOG_LEVEL: %w", err) return nil, fmt.Errorf("environment variable LOG_LEVEL: %w", err)
@@ -36,16 +36,16 @@ func readLogLevel() (level *logging.Level, err error) {
var ErrLogLevelUnknown = errors.New("log level is unknown") var ErrLogLevelUnknown = errors.New("log level is unknown")
func parseLogLevel(s string) (level logging.Level, err error) { func parseLogLevel(s string) (level log.Level, err error) {
switch strings.ToLower(s) { switch strings.ToLower(s) {
case "debug": case "debug":
return logging.LevelDebug, nil return log.LevelDebug, nil
case "info": case "info":
return logging.LevelInfo, nil return log.LevelInfo, nil
case "warning": case "warning":
return logging.LevelWarn, nil return log.LevelWarn, nil
case "error": case "error":
return logging.LevelError, nil return log.LevelError, nil
default: default:
return level, fmt.Errorf( return level, fmt.Errorf(
"%w: %q is not valid and can be one of debug, info, warning or error", "%w: %q is not valid and can be one of debug, info, warning or error",

View File

@@ -65,6 +65,11 @@ func (r *Reader) readOpenVPN() (
return openVPN, fmt.Errorf("environment variable OPENVPN_VERBOSITY: %w", err) return openVPN, fmt.Errorf("environment variable OPENVPN_VERBOSITY: %w", err)
} }
flagsStr := os.Getenv("OPENVPN_FLAGS")
if flagsStr != "" {
openVPN.Flags = strings.Fields(flagsStr)
}
return openVPN, nil return openVPN, nil
} }

View File

@@ -6,7 +6,8 @@ import (
"strings" "strings"
"github.com/qdm12/gluetun/internal/configuration/settings" "github.com/qdm12/gluetun/internal/configuration/settings"
"github.com/qdm12/gluetun/internal/constants" "github.com/qdm12/gluetun/internal/constants/providers"
"github.com/qdm12/gluetun/internal/constants/vpn"
) )
func (r *Reader) readProvider(vpnType string) (provider settings.Provider, err error) { func (r *Reader) readProvider(vpnType string) (provider settings.Provider, err error) {
@@ -33,13 +34,13 @@ func (r *Reader) readVPNServiceProvider(vpnType string) (vpnProviderPtr *string)
_, s := r.getEnvWithRetro("VPN_SERVICE_PROVIDER", "VPNSP") _, s := r.getEnvWithRetro("VPN_SERVICE_PROVIDER", "VPNSP")
s = strings.ToLower(s) s = strings.ToLower(s)
switch { switch {
case vpnType != constants.Wireguard && case vpnType != vpn.Wireguard &&
os.Getenv("OPENVPN_CUSTOM_CONFIG") != "": // retro compatibility os.Getenv("OPENVPN_CUSTOM_CONFIG") != "": // retro compatibility
return stringPtr(constants.Custom) return stringPtr(providers.Custom)
case s == "": case s == "":
return nil return nil
case s == "pia": // retro compatibility case s == "pia": // retro compatibility
return stringPtr(constants.PrivateInternetAccess) return stringPtr(providers.PrivateInternetAccess)
} }
return stringPtr(s) return stringPtr(s)
} }

View File

@@ -9,7 +9,7 @@ import (
"strings" "strings"
"github.com/qdm12/gluetun/internal/configuration/settings" "github.com/qdm12/gluetun/internal/configuration/settings"
"github.com/qdm12/gluetun/internal/constants" "github.com/qdm12/gluetun/internal/constants/providers"
) )
var ( var (
@@ -27,7 +27,7 @@ func (r *Reader) readServerSelection(vpnProvider, vpnType string) (
countriesKey, _ := r.getEnvWithRetro("SERVER_COUNTRIES", "COUNTRY") countriesKey, _ := r.getEnvWithRetro("SERVER_COUNTRIES", "COUNTRY")
ss.Countries = envToCSV(countriesKey) ss.Countries = envToCSV(countriesKey)
if vpnProvider == constants.Cyberghost && len(ss.Countries) == 0 { if vpnProvider == providers.Cyberghost && len(ss.Countries) == 0 {
// Retro-compatibility for Cyberghost using the REGION variable // Retro-compatibility for Cyberghost using the REGION variable
ss.Countries = envToCSV("REGION") ss.Countries = envToCSV("REGION")
if len(ss.Countries) > 0 { if len(ss.Countries) > 0 {

View File

@@ -34,20 +34,23 @@ func (r *Reader) readSystem() (system settings.System, err error) {
var ErrSystemIDNotValid = errors.New("system ID is not valid") var ErrSystemIDNotValid = errors.New("system ID is not valid")
func (r *Reader) readID(key, retroKey string) ( func (r *Reader) readID(key, retroKey string) (
id *uint16, err error) { id *uint32, err error) {
idEnvKey, idString := r.getEnvWithRetro(key, retroKey) idEnvKey, idString := r.getEnvWithRetro(key, retroKey)
if idString == "" { if idString == "" {
return nil, nil //nolint:nilnil return nil, nil //nolint:nilnil
} }
idInt, err := strconv.Atoi(idString) const base = 10
const bitSize = 64
const max = uint64(^uint32(0))
idUint64, err := strconv.ParseUint(idString, base, bitSize)
if err != nil { if err != nil {
return nil, fmt.Errorf("environment variable %s: %w: %s: %s", return nil, fmt.Errorf("environment variable %s: %w: %s",
idEnvKey, ErrSystemIDNotValid, idString, err) idEnvKey, ErrSystemIDNotValid, err)
} else if idInt < 0 || idInt > 65535 { } else if idUint64 > max {
return nil, fmt.Errorf("environment variable %s: %w: %d: must be between 0 and 65535", return nil, fmt.Errorf("environment variable %s: %w: %d: must be between 0 and %d",
idEnvKey, ErrSystemIDNotValid, idInt) idEnvKey, ErrSystemIDNotValid, idUint64, max)
} }
return uint16Ptr(uint16(idInt)), nil return uint32Ptr(uint32(idUint64)), nil
} }

View File

@@ -14,15 +14,51 @@ func Test_Reader_readID(t *testing.T) {
keyValue string keyValue string
retroKeyPrefix string retroKeyPrefix string
retroValue string retroValue string
id *uint16 id *uint32
errWrapped error errWrapped error
errMessage string errMessage string
}{ }{
"empty string": {
keyPrefix: "ID",
retroKeyPrefix: "RETRO_ID",
},
"invalid string": {
keyPrefix: "ID",
keyValue: "invalid",
retroKeyPrefix: "RETRO_ID",
errWrapped: ErrSystemIDNotValid,
errMessage: `environment variable IDTest_Reader_readID/invalid_string: ` +
`system ID is not valid: ` +
`strconv.ParseUint: parsing "invalid": invalid syntax`,
},
"negative number": {
keyPrefix: "ID",
keyValue: "-1",
retroKeyPrefix: "RETRO_ID",
errWrapped: ErrSystemIDNotValid,
errMessage: `environment variable IDTest_Reader_readID/negative_number: ` +
`system ID is not valid: ` +
`strconv.ParseUint: parsing "-1": invalid syntax`,
},
"id 1000": { "id 1000": {
keyPrefix: "ID", keyPrefix: "ID",
keyValue: "1000", keyValue: "1000",
retroKeyPrefix: "RETRO_ID", retroKeyPrefix: "RETRO_ID",
id: uint16Ptr(1000), id: uint32Ptr(1000),
},
"max id": {
keyPrefix: "ID",
keyValue: "4294967295",
retroKeyPrefix: "RETRO_ID",
id: uint32Ptr(4294967295),
},
"above max id": {
keyPrefix: "ID",
keyValue: "4294967296",
retroKeyPrefix: "RETRO_ID",
errWrapped: ErrSystemIDNotValid,
errMessage: `environment variable IDTest_Reader_readID/above_max_id: ` +
`system ID is not valid: 4294967296: must be between 0 and 4294967295`,
}, },
} }

View File

@@ -0,0 +1,8 @@
package constants
const (
// TCP is a network protocol (reliable and slower than UDP).
TCP string = "tcp"
// UDP is a network protocol (unreliable and faster than TCP).
UDP string = "udp"
)

View File

@@ -1,27 +0,0 @@
package constants
func AllProviders() []string {
return []string{
Custom,
Cyberghost,
Expressvpn,
Fastestvpn,
HideMyAss,
Ipvanish,
Ivpn,
Mullvad,
Nordvpn,
Perfectprivacy,
Privado,
PrivateInternetAccess,
Privatevpn,
Protonvpn,
Purevpn,
Surfshark,
Torguard,
VPNUnlimited,
Vyprvpn,
Wevpn,
Windscribe,
}
}

View File

@@ -0,0 +1,53 @@
package providers
const (
// Custom is the VPN provider name for custom
// VPN configurations.
Custom = "custom"
Cyberghost = "cyberghost"
Expressvpn = "expressvpn"
Fastestvpn = "fastestvpn"
HideMyAss = "hidemyass"
Ipvanish = "ipvanish"
Ivpn = "ivpn"
Mullvad = "mullvad"
Nordvpn = "nordvpn"
Perfectprivacy = "perfect privacy"
Privado = "privado"
PrivateInternetAccess = "private internet access"
Privatevpn = "privatevpn"
Protonvpn = "protonvpn"
Purevpn = "purevpn"
Surfshark = "surfshark"
Torguard = "torguard"
VPNUnlimited = "vpn unlimited"
Vyprvpn = "vyprvpn"
Wevpn = "wevpn"
Windscribe = "windscribe"
)
func All() []string {
return []string{
Custom,
Cyberghost,
Expressvpn,
Fastestvpn,
HideMyAss,
Ipvanish,
Ivpn,
Mullvad,
Nordvpn,
Perfectprivacy,
Privado,
PrivateInternetAccess,
Privatevpn,
Protonvpn,
Purevpn,
Surfshark,
Torguard,
VPNUnlimited,
Vyprvpn,
Wevpn,
Windscribe,
}
}

View File

@@ -1,59 +0,0 @@
package constants
const (
OpenVPN = "openvpn"
Wireguard = "wireguard"
)
const (
// Custom is the VPN provider name for custom
// VPN configurations.
Custom = "custom"
// Cyberghost is a VPN provider.
Cyberghost = "cyberghost"
// Expressvpn is a VPN provider.
Expressvpn = "expressvpn"
// Fastestvpn is a VPN provider.
Fastestvpn = "fastestvpn"
// HideMyAss is a VPN provider.
HideMyAss = "hidemyass"
// Ipvanish is a VPN provider.
Ipvanish = "ipvanish"
// Ivpn is a VPN provider.
Ivpn = "ivpn"
// Mullvad is a VPN provider.
Mullvad = "mullvad"
// Nordvpn is a VPN provider.
Nordvpn = "nordvpn"
// Perfectprivacy is a VPN provider.
Perfectprivacy = "perfect privacy"
// Privado is a VPN provider.
Privado = "privado"
// PrivateInternetAccess is a VPN provider.
PrivateInternetAccess = "private internet access"
// Privatevpn is a VPN provider.
Privatevpn = "privatevpn"
// Protonvpn is a VPN provider.
Protonvpn = "protonvpn"
// Purevpn is a VPN provider.
Purevpn = "purevpn"
// Surfshark is a VPN provider.
Surfshark = "surfshark"
// Torguard is a VPN provider.
Torguard = "torguard"
// VPNUnlimited is a VPN provider.
VPNUnlimited = "vpn unlimited"
// Vyprvpn is a VPN provider.
Vyprvpn = "vyprvpn"
// WeVPN is a VPN provider.
Wevpn = "wevpn"
// Windscribe is a VPN provider.
Windscribe = "windscribe"
)
const (
// TCP is a network protocol (reliable and slower than UDP).
TCP string = "tcp"
// UDP is a network protocol (unreliable and faster than TCP).
UDP string = "udp"
)

View File

@@ -0,0 +1,6 @@
package vpn
const (
OpenVPN = "openvpn"
Wireguard = "wireguard"
)

View File

@@ -0,0 +1,61 @@
package firewall
import (
"fmt"
"os/exec"
"regexp"
"github.com/golang/mock/gomock"
)
var _ gomock.Matcher = (*cmdMatcher)(nil)
type cmdMatcher struct {
path string
argsRegex []string
argsRegexp []*regexp.Regexp
}
func (cm *cmdMatcher) Matches(x interface{}) bool {
cmd, ok := x.(*exec.Cmd)
if !ok {
return false
}
if cmd.Path != cm.path {
return false
}
if len(cmd.Args) == 0 {
return false
}
arguments := cmd.Args[1:]
if len(arguments) != len(cm.argsRegex) {
return false
}
for i, arg := range arguments {
if !cm.argsRegexp[i].MatchString(arg) {
return false
}
}
return true
}
func (cm *cmdMatcher) String() string {
return fmt.Sprintf("path %s, argument regular expressions %v", cm.path, cm.argsRegex)
}
func newCmdMatcher(path string, argsRegex ...string) *cmdMatcher { //nolint:unparam
argsRegexp := make([]*regexp.Regexp, len(argsRegex))
for i, argRegex := range argsRegex {
argsRegexp[i] = regexp.MustCompile(argRegex)
}
return &cmdMatcher{
path: path,
argsRegex: argsRegex,
argsRegexp: argsRegexp,
}
}

View File

@@ -96,14 +96,10 @@ func (c *Config) enable(ctx context.Context) (err error) {
if err = c.acceptEstablishedRelatedTraffic(ctx, remove); err != nil { if err = c.acceptEstablishedRelatedTraffic(ctx, remove); err != nil {
return err return err
} }
if c.vpnConnection.IP != nil {
if err = c.acceptOutputTrafficToVPN(ctx, c.defaultInterface, c.vpnConnection, remove); err != nil { if err = c.allowVPNIP(ctx); err != nil {
return err return err
} }
if err = c.acceptOutputThroughInterface(ctx, c.vpnIntf, remove); err != nil {
return err
}
}
for _, network := range c.localNetworks { for _, network := range c.localNetworks {
if err := c.acceptOutputFromIPToSubnet(ctx, network.InterfaceName, network.IP, *network.IPNet, remove); err != nil { if err := c.acceptOutputFromIPToSubnet(ctx, network.InterfaceName, network.IP, *network.IPNet, remove); err != nil {
@@ -111,11 +107,9 @@ func (c *Config) enable(ctx context.Context) (err error) {
} }
} }
for _, subnet := range c.outboundSubnets { if err = c.allowOutboundSubnets(ctx); err != nil {
if err := c.acceptOutputFromIPToSubnet(ctx, c.defaultInterface, c.localIP, subnet, remove); err != nil {
return err return err
} }
}
// Allows packets from any IP address to go through eth0 / local network // Allows packets from any IP address to go through eth0 / local network
// to reach Gluetun. // to reach Gluetun.
@@ -125,11 +119,9 @@ func (c *Config) enable(ctx context.Context) (err error) {
} }
} }
for port, intf := range c.allowedInputPorts { if err = c.allowInputPorts(ctx); err != nil {
if err := c.acceptInputToPort(ctx, intf, port, remove); err != nil {
return err return err
} }
}
if err := c.runUserPostRules(ctx, c.customRulesPath, remove); err != nil { if err := c.runUserPostRules(ctx, c.customRulesPath, remove); err != nil {
return fmt.Errorf("cannot run user defined post firewall rules: %w", err) return fmt.Errorf("cannot run user defined post firewall rules: %w", err)
@@ -137,3 +129,47 @@ func (c *Config) enable(ctx context.Context) (err error) {
return nil return nil
} }
func (c *Config) allowVPNIP(ctx context.Context) (err error) {
if c.vpnConnection.IP == nil {
return nil
}
const remove = false
for _, defaultRoute := range c.defaultRoutes {
err = c.acceptOutputTrafficToVPN(ctx, defaultRoute.NetInterface, c.vpnConnection, remove)
if err != nil {
return fmt.Errorf("cannot accept output traffic through VPN: %w", err)
}
}
return nil
}
func (c *Config) allowOutboundSubnets(ctx context.Context) (err error) {
for _, subnet := range c.outboundSubnets {
for _, defaultRoute := range c.defaultRoutes {
const remove = false
err := c.acceptOutputFromIPToSubnet(ctx, defaultRoute.NetInterface,
defaultRoute.AssignedIP, subnet, remove)
if err != nil {
return err
}
}
}
return nil
}
func (c *Config) allowInputPorts(ctx context.Context) (err error) {
for port, netInterfaces := range c.allowedInputPorts {
for netInterface := range netInterfaces {
const remove = false
err = c.acceptInputToPort(ctx, netInterface, port, remove)
if err != nil {
return fmt.Errorf("cannot accept input port %d on interface %s: %w",
port, netInterface, err)
}
}
}
return nil
}

View File

@@ -27,13 +27,12 @@ type Config struct { //nolint:maligned
logger Logger logger Logger
iptablesMutex sync.Mutex iptablesMutex sync.Mutex
ip6tablesMutex sync.Mutex ip6tablesMutex sync.Mutex
defaultInterface string defaultRoutes []routing.DefaultRoute
defaultGateway net.IP
localNetworks []routing.LocalNetwork localNetworks []routing.LocalNetwork
localIP net.IP
// Fixed state // Fixed state
ip6Tables bool ipTables string
ip6Tables string
customRulesPath string customRulesPath string
// State // State
@@ -41,24 +40,34 @@ type Config struct { //nolint:maligned
vpnConnection models.Connection vpnConnection models.Connection
vpnIntf string vpnIntf string
outboundSubnets []net.IPNet outboundSubnets []net.IPNet
allowedInputPorts map[uint16]string // port to interface mapping allowedInputPorts map[uint16]map[string]struct{} // port to interfaces set mapping
stateMutex sync.Mutex stateMutex sync.Mutex
} }
// NewConfig creates a new Config instance. // NewConfig creates a new Config instance and returns an error
func NewConfig(logger Logger, runner command.Runner, // if no iptables implementation is available.
defaultInterface string, defaultGateway net.IP, func NewConfig(ctx context.Context, logger Logger,
localNetworks []routing.LocalNetwork, localIP net.IP) *Config { runner command.Runner, defaultRoutes []routing.DefaultRoute,
localNetworks []routing.LocalNetwork) (config *Config, err error) {
iptables, err := checkIptablesSupport(ctx, runner, "iptables", "iptables-nft")
if err != nil {
return nil, err
}
ip6tables, err := findIP6tablesSupported(ctx, runner)
if err != nil {
return nil, err
}
return &Config{ return &Config{
runner: runner, runner: runner,
logger: logger, logger: logger,
allowedInputPorts: make(map[uint16]string), allowedInputPorts: make(map[uint16]map[string]struct{}),
ip6Tables: ip6tablesSupported(context.Background(), runner), ipTables: iptables,
ip6Tables: ip6tables,
customRulesPath: "/iptables/post-rules.txt", customRulesPath: "/iptables/post-rules.txt",
// Obtained from routing // Obtained from routing
defaultInterface: defaultInterface, defaultRoutes: defaultRoutes,
defaultGateway: defaultGateway,
localNetworks: localNetworks, localNetworks: localNetworks,
localIP: localIP, }, nil
}
} }

View File

@@ -10,16 +10,18 @@ import (
"github.com/qdm12/golibs/command" "github.com/qdm12/golibs/command"
) )
var ( // findIP6tablesSupported checks for multiple iptables implementations
ErrIP6NotSupported = errors.New("ip6tables not supported") // and returns the iptables path that is supported. If none work, an
) // empty string path is returned.
func findIP6tablesSupported(ctx context.Context, runner command.Runner) (
func ip6tablesSupported(ctx context.Context, runner command.Runner) (supported bool) { ip6tablesPath string, err error) {
cmd := exec.CommandContext(ctx, "ip6tables", "-L") ip6tablesPath, err = checkIptablesSupport(ctx, runner, "ip6tables", "ip6tables-nft")
if _, err := runner.Run(cmd); err != nil { if errors.Is(err, ErrIPTablesNotSupported) {
return false return "", nil
} else if err != nil {
return "", err
} }
return true return ip6tablesPath, nil
} }
func (c *Config) runIP6tablesInstructions(ctx context.Context, instructions []string) error { func (c *Config) runIP6tablesInstructions(ctx context.Context, instructions []string) error {
@@ -32,18 +34,19 @@ func (c *Config) runIP6tablesInstructions(ctx context.Context, instructions []st
} }
func (c *Config) runIP6tablesInstruction(ctx context.Context, instruction string) error { func (c *Config) runIP6tablesInstruction(ctx context.Context, instruction string) error {
if !c.ip6Tables { if c.ip6Tables == "" {
return nil return nil
} }
c.ip6tablesMutex.Lock() // only one ip6tables command at once c.ip6tablesMutex.Lock() // only one ip6tables command at once
defer c.ip6tablesMutex.Unlock() defer c.ip6tablesMutex.Unlock()
c.logger.Debug("ip6tables " + instruction) c.logger.Debug(c.ip6Tables + " " + instruction)
flags := strings.Fields(instruction) flags := strings.Fields(instruction)
cmd := exec.CommandContext(ctx, "ip6tables", flags...) cmd := exec.CommandContext(ctx, c.ip6Tables, flags...) // #nosec G204
if output, err := c.runner.Run(cmd); err != nil { if output, err := c.runner.Run(cmd); err != nil {
return fmt.Errorf("command failed: \"ip6tables %s\": %s: %w", instruction, output, err) return fmt.Errorf("command failed: \"%s %s\": %s: %w",
c.ip6Tables, instruction, output, err)
} }
return nil return nil
} }

View File

@@ -71,12 +71,13 @@ func (c *Config) runIptablesInstruction(ctx context.Context, instruction string)
c.iptablesMutex.Lock() // only one iptables command at once c.iptablesMutex.Lock() // only one iptables command at once
defer c.iptablesMutex.Unlock() defer c.iptablesMutex.Unlock()
c.logger.Debug("iptables " + instruction) c.logger.Debug(c.ipTables + " " + instruction)
flags := strings.Fields(instruction) flags := strings.Fields(instruction)
cmd := exec.CommandContext(ctx, "iptables", flags...) cmd := exec.CommandContext(ctx, c.ipTables, flags...) // #nosec G204
if output, err := c.runner.Run(cmd); err != nil { if output, err := c.runner.Run(cmd); err != nil {
return fmt.Errorf("command failed: \"iptables %s\": %s: %w", instruction, output, err) return fmt.Errorf("command failed: \"%s %s\": %s: %w",
c.ipTables, instruction, output, err)
} }
return nil return nil
} }
@@ -124,7 +125,7 @@ func (c *Config) acceptInputToSubnet(ctx context.Context, intf string, destinati
if isIP4Subnet { if isIP4Subnet {
return c.runIptablesInstruction(ctx, instruction) return c.runIptablesInstruction(ctx, instruction)
} }
if !c.ip6Tables { if c.ip6Tables == "" {
return fmt.Errorf("accept input to subnet %s: %w", destination, ErrNeedIP6Tables) return fmt.Errorf("accept input to subnet %s: %w", destination, ErrNeedIP6Tables)
} }
return c.runIP6tablesInstruction(ctx, instruction) return c.runIP6tablesInstruction(ctx, instruction)
@@ -151,7 +152,7 @@ func (c *Config) acceptOutputTrafficToVPN(ctx context.Context,
isIPv4 := connection.IP.To4() != nil isIPv4 := connection.IP.To4() != nil
if isIPv4 { if isIPv4 {
return c.runIptablesInstruction(ctx, instruction) return c.runIptablesInstruction(ctx, instruction)
} else if !c.ip6Tables { } else if c.ip6Tables == "" {
return fmt.Errorf("accept output to VPN server: %w", ErrNeedIP6Tables) return fmt.Errorf("accept output to VPN server: %w", ErrNeedIP6Tables)
} }
return c.runIP6tablesInstruction(ctx, instruction) return c.runIP6tablesInstruction(ctx, instruction)
@@ -172,7 +173,7 @@ func (c *Config) acceptOutputFromIPToSubnet(ctx context.Context,
if doIPv4 { if doIPv4 {
return c.runIptablesInstruction(ctx, instruction) return c.runIptablesInstruction(ctx, instruction)
} else if !c.ip6Tables { } else if c.ip6Tables == "" {
return fmt.Errorf("accept output from %s to %s: %w", sourceIP, destinationSubnet, ErrNeedIP6Tables) return fmt.Errorf("accept output from %s to %s: %w", sourceIP, destinationSubnet, ErrNeedIP6Tables)
} }
return c.runIP6tablesInstruction(ctx, instruction) return c.runIP6tablesInstruction(ctx, instruction)
@@ -223,9 +224,15 @@ func (c *Config) runUserPostRules(ctx context.Context, filepath string, remove b
case strings.HasPrefix(line, "iptables "): case strings.HasPrefix(line, "iptables "):
ipv4 = true ipv4 = true
rule = strings.TrimPrefix(line, "iptables ") rule = strings.TrimPrefix(line, "iptables ")
case strings.HasPrefix(line, "iptables-nft "):
ipv4 = true
rule = strings.TrimPrefix(line, "iptables-nft ")
case strings.HasPrefix(line, "ip6tables "): case strings.HasPrefix(line, "ip6tables "):
ipv4 = false ipv4 = false
rule = strings.TrimPrefix(line, "ip6tables ") rule = strings.TrimPrefix(line, "ip6tables ")
case strings.HasPrefix(line, "ip6tables-nft "):
ipv4 = false
rule = strings.TrimPrefix(line, "ip6tables-nft ")
default: default:
continue continue
} }
@@ -237,7 +244,7 @@ func (c *Config) runUserPostRules(ctx context.Context, filepath string, remove b
switch { switch {
case ipv4: case ipv4:
err = c.runIptablesInstruction(ctx, rule) err = c.runIptablesInstruction(ctx, rule)
case !c.ip6Tables: case c.ip6Tables == "":
err = fmt.Errorf("cannot run user ip6tables rule: %w", ErrNeedIP6Tables) err = fmt.Errorf("cannot run user ip6tables rule: %w", ErrNeedIP6Tables)
default: // ipv6 default: // ipv6
err = c.runIP6tablesInstruction(ctx, rule) err = c.runIP6tablesInstruction(ctx, rule)

View File

@@ -41,10 +41,14 @@ func (c *Config) SetOutboundSubnets(ctx context.Context, subnets []net.IPNet) (e
func (c *Config) removeOutboundSubnets(ctx context.Context, subnets []net.IPNet) { func (c *Config) removeOutboundSubnets(ctx context.Context, subnets []net.IPNet) {
const remove = true const remove = true
for _, subNet := range subnets { for _, subNet := range subnets {
if err := c.acceptOutputFromIPToSubnet(ctx, c.defaultInterface, c.localIP, subNet, remove); err != nil { for _, defaultRoute := range c.defaultRoutes {
err := c.acceptOutputFromIPToSubnet(ctx, defaultRoute.NetInterface,
defaultRoute.AssignedIP, subNet, remove)
if err != nil {
c.logger.Error("cannot remove outdated outbound subnet: " + err.Error()) c.logger.Error("cannot remove outdated outbound subnet: " + err.Error())
continue continue
} }
}
c.outboundSubnets = subnet.RemoveSubnetFromSubnets(c.outboundSubnets, subNet) c.outboundSubnets = subnet.RemoveSubnetFromSubnets(c.outboundSubnets, subNet)
} }
} }
@@ -52,9 +56,13 @@ func (c *Config) removeOutboundSubnets(ctx context.Context, subnets []net.IPNet)
func (c *Config) addOutboundSubnets(ctx context.Context, subnets []net.IPNet) error { func (c *Config) addOutboundSubnets(ctx context.Context, subnets []net.IPNet) error {
const remove = false const remove = false
for _, subnet := range subnets { for _, subnet := range subnets {
if err := c.acceptOutputFromIPToSubnet(ctx, c.defaultInterface, c.localIP, subnet, remove); err != nil { for _, defaultRoute := range c.defaultRoutes {
err := c.acceptOutputFromIPToSubnet(ctx, defaultRoute.NetInterface,
defaultRoute.AssignedIP, subnet, remove)
if err != nil {
return err return err
} }
}
c.outboundSubnets = append(c.outboundSubnets, subnet) c.outboundSubnets = append(c.outboundSubnets, subnet)
} }
return nil return nil

View File

@@ -21,27 +21,30 @@ func (c *Config) SetAllowedPort(ctx context.Context, port uint16, intf string) (
if !c.enabled { if !c.enabled {
c.logger.Info("firewall disabled, only updating allowed ports internal state") c.logger.Info("firewall disabled, only updating allowed ports internal state")
c.allowedInputPorts[port] = intf existingInterfaces, ok := c.allowedInputPorts[port]
if !ok {
existingInterfaces = make(map[string]struct{})
}
existingInterfaces[intf] = struct{}{}
c.allowedInputPorts[port] = existingInterfaces
return nil
}
netInterfaces, has := c.allowedInputPorts[port]
if !has {
netInterfaces = make(map[string]struct{})
} else if _, exists := netInterfaces[intf]; exists {
return nil return nil
} }
c.logger.Info("setting allowed input port " + fmt.Sprint(port) + " through interface " + intf + "...") c.logger.Info("setting allowed input port " + fmt.Sprint(port) + " through interface " + intf + "...")
if existingIntf, ok := c.allowedInputPorts[port]; ok {
if intf == existingIntf {
return nil
}
const remove = true
if err := c.acceptInputToPort(ctx, existingIntf, port, remove); err != nil {
return fmt.Errorf("cannot remove old allowed port %d: %w", port, err)
}
}
const remove = false const remove = false
if err := c.acceptInputToPort(ctx, intf, port, remove); err != nil { if err := c.acceptInputToPort(ctx, intf, port, remove); err != nil {
return fmt.Errorf("cannot allow input to port %d: %w", port, err) return fmt.Errorf("cannot allow input to port %d through interface %s: %w",
port, intf, err)
} }
c.allowedInputPorts[port] = intf netInterfaces[intf] = struct{}{}
return nil return nil
} }
@@ -60,17 +63,24 @@ func (c *Config) RemoveAllowedPort(ctx context.Context, port uint16) (err error)
return nil return nil
} }
c.logger.Info("removing allowed port " + strconv.Itoa(int(port)) + " ...") c.logger.Info("removing allowed port " + strconv.Itoa(int(port)) + "...")
intf, ok := c.allowedInputPorts[port] interfacesSet, ok := c.allowedInputPorts[port]
if !ok { if !ok {
return nil return nil
} }
const remove = true const remove = true
if err := c.acceptInputToPort(ctx, intf, port, remove); err != nil { for netInterface := range interfacesSet {
return fmt.Errorf("cannot remove allowed port %d: %w", port, err) err := c.acceptInputToPort(ctx, netInterface, port, remove)
if err != nil {
return fmt.Errorf("cannot remove allowed port %d on interface %s: %w",
port, netInterface, err)
} }
delete(interfacesSet, netInterface)
}
// All interfaces were removed successfully, so remove the port entry.
delete(c.allowedInputPorts, port) delete(c.allowedInputPorts, port)
return nil return nil

View File

@@ -0,0 +1,50 @@
// Code generated by MockGen. DO NOT EDIT.
// Source: github.com/qdm12/golibs/command (interfaces: Runner)
// Package firewall is a generated GoMock package.
package firewall
import (
reflect "reflect"
gomock "github.com/golang/mock/gomock"
command "github.com/qdm12/golibs/command"
)
// MockRunner is a mock of Runner interface.
type MockRunner struct {
ctrl *gomock.Controller
recorder *MockRunnerMockRecorder
}
// MockRunnerMockRecorder is the mock recorder for MockRunner.
type MockRunnerMockRecorder struct {
mock *MockRunner
}
// NewMockRunner creates a new mock instance.
func NewMockRunner(ctrl *gomock.Controller) *MockRunner {
mock := &MockRunner{ctrl: ctrl}
mock.recorder = &MockRunnerMockRecorder{mock}
return mock
}
// EXPECT returns an object that allows the caller to indicate expected use.
func (m *MockRunner) EXPECT() *MockRunnerMockRecorder {
return m.recorder
}
// Run mocks base method.
func (m *MockRunner) Run(arg0 command.ExecCmd) (string, error) {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "Run", arg0)
ret0, _ := ret[0].(string)
ret1, _ := ret[1].(error)
return ret0, ret1
}
// Run indicates an expected call of Run.
func (mr *MockRunnerMockRecorder) Run(arg0 interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Run", reflect.TypeOf((*MockRunner)(nil).Run), arg0)
}

View File

@@ -0,0 +1,159 @@
package firewall
import (
"context"
"errors"
"fmt"
"math/rand"
"os/exec"
"strings"
"github.com/qdm12/golibs/command"
)
var (
ErrNetAdminMissing = errors.New("NET_ADMIN capability is missing")
ErrTestRuleCleanup = errors.New("failed cleaning up test rule")
ErrInputPolicyNotFound = errors.New("input policy not found")
ErrIPTablesNotSupported = errors.New("no iptables supported found")
)
func checkIptablesSupport(ctx context.Context, runner command.Runner,
iptablesPathsToTry ...string) (iptablesPath string, err error) {
var lastUnsupportedMessage string
for _, pathToTest := range iptablesPathsToTry {
ok, unsupportedMessage, err := testIptablesPath(ctx, pathToTest, runner)
if err != nil {
return "", fmt.Errorf("for %s: %w", pathToTest, err)
} else if ok {
iptablesPath = pathToTest
break
}
lastUnsupportedMessage = unsupportedMessage
}
if iptablesPath == "" { // all iptables to try failed
return "", fmt.Errorf("%w: from %s: last error is: %s",
ErrIPTablesNotSupported, strings.Join(iptablesPathsToTry, ", "),
lastUnsupportedMessage)
}
return iptablesPath, nil
}
func testIptablesPath(ctx context.Context, path string,
runner command.Runner) (ok bool, unsupportedMessage string,
criticalErr error) {
// Just listing iptables rules often work but we need
// to modify them to ensure we can support the iptables
// being tested.
// Append a test rule with a random interface name to the OUTPUT table.
// This should not affect existing rules or the network traffic.
testInterfaceName := randomInterfaceName()
cmd := exec.CommandContext(ctx, path,
"-A", "OUTPUT", "-o", testInterfaceName, "-j", "DROP")
output, err := runner.Run(cmd)
if err != nil {
if isPermissionDenied(output) {
// If the error is related to a denied permission,
// return an error describing what to do from an end-user
// perspective. This is a critical error and likely
// applies to all iptables.
criticalErr = fmt.Errorf("%w: %s", ErrNetAdminMissing, output)
return false, "", criticalErr
}
unsupportedMessage = fmt.Sprintf("%s (%s)", output, err)
return false, unsupportedMessage, nil
}
// Remove the random rule added previously for test.
cmd = exec.CommandContext(ctx, path,
"-D", "OUTPUT", "-o", testInterfaceName, "-j", "DROP")
output, err = runner.Run(cmd)
if err != nil {
// this is a critical error, we want to make sure our test rule gets removed.
criticalErr = fmt.Errorf("%w: %s (%s)", ErrTestRuleCleanup, output, err)
return false, "", criticalErr
}
// Set policy as the existing policy so no mutation is done.
// This is an extra check for some buggy kernels where setting the policy
// does not work.
cmd = exec.CommandContext(ctx, path, "-L", "INPUT")
output, err = runner.Run(cmd)
if err != nil {
if isPermissionDenied(output) {
criticalErr = fmt.Errorf("%w: %s", ErrNetAdminMissing, output)
return false, "", criticalErr
}
unsupportedMessage = fmt.Sprintf("%s (%s)", output, err)
return false, unsupportedMessage, nil
}
var inputPolicy string
for _, line := range strings.Split(output, "\n") {
inputPolicy, ok = extractInputPolicy(line)
if ok {
break
}
}
if inputPolicy == "" {
criticalErr = fmt.Errorf("%w: in INPUT rules: %s", ErrInputPolicyNotFound, output)
return false, "", criticalErr
}
// Set the policy for the INPUT table to the existing policy found.
cmd = exec.CommandContext(ctx, path, "--policy", "INPUT", inputPolicy)
output, err = runner.Run(cmd)
if err != nil {
if isPermissionDenied(output) {
criticalErr = fmt.Errorf("%w: %s", ErrNetAdminMissing, output)
return false, "", criticalErr
}
unsupportedMessage = fmt.Sprintf("%s (%s)", output, err)
return false, unsupportedMessage, nil
}
return true, "", nil // success
}
func isPermissionDenied(errMessage string) (ok bool) {
const permissionDeniedString = "Permission denied (you must be root)"
return strings.Contains(errMessage, permissionDeniedString)
}
func extractInputPolicy(line string) (policy string, ok bool) {
const prefixToFind = "Chain INPUT (policy "
i := strings.Index(line, prefixToFind)
if i == -1 {
return "", false
}
startIndex := i + len(prefixToFind)
endIndex := strings.Index(line, ")")
if endIndex < 0 {
return "", false
}
policy = line[startIndex:endIndex]
policy = strings.TrimSpace(policy)
if policy == "" {
return "", false
}
return policy, true
}
func randomInterfaceName() (interfaceName string) {
const size = 15
letterRunes := []rune("abcdefghijklmnopqrstuvwxyz0123456789")
b := make([]rune, size)
for i := range b {
letterIndex := rand.Intn(len(letterRunes)) //nolint:gosec
b[i] = letterRunes[letterIndex]
}
return string(b)
}

View File

@@ -0,0 +1,250 @@
package firewall
import (
"context"
"errors"
"testing"
"github.com/golang/mock/gomock"
"github.com/qdm12/golibs/command"
"github.com/stretchr/testify/assert"
)
//go:generate mockgen -destination=runner_mock_test.go -package $GOPACKAGE github.com/qdm12/golibs/command Runner
func Test_testIptablesPath(t *testing.T) {
t.Parallel()
ctx := context.Background()
const path = "dummypath"
errDummy := errors.New("exit code 4")
const inputPolicy = "ACCEPT"
appendTestRuleMatcher := newCmdMatcher(path,
"^-A$", "^OUTPUT$", "^-o$", "^[a-z0-9]{15}$",
"^-j$", "^DROP$")
deleteTestRuleMatcher := newCmdMatcher(path,
"^-D$", "^OUTPUT$", "^-o$", "^[a-z0-9]{15}$",
"^-j$", "^DROP$")
listInputRulesMatcher := newCmdMatcher(path,
"^-L$", "^INPUT$")
setPolicyMatcher := newCmdMatcher(path,
"^--policy$", "^INPUT$", "^"+inputPolicy+"$")
testCases := map[string]struct {
buildRunner func(ctrl *gomock.Controller) command.Runner
ok bool
unsupportedMessage string
criticalErrWrapped error
criticalErrMessage string
}{
"append test rule permission denied": {
buildRunner: func(ctrl *gomock.Controller) command.Runner {
runner := NewMockRunner(ctrl)
runner.EXPECT().Run(appendTestRuleMatcher).
Return("Permission denied (you must be root)", errDummy)
return runner
},
criticalErrWrapped: ErrNetAdminMissing,
criticalErrMessage: "NET_ADMIN capability is missing: " +
"Permission denied (you must be root)",
},
"append test rule unsupported": {
buildRunner: func(ctrl *gomock.Controller) command.Runner {
runner := NewMockRunner(ctrl)
runner.EXPECT().Run(appendTestRuleMatcher).
Return("some output", errDummy)
return runner
},
unsupportedMessage: "some output (exit code 4)",
},
"remove test rule error": {
buildRunner: func(ctrl *gomock.Controller) command.Runner {
runner := NewMockRunner(ctrl)
runner.EXPECT().Run(appendTestRuleMatcher).Return("", nil)
runner.EXPECT().Run(deleteTestRuleMatcher).
Return("some output", errDummy)
return runner
},
criticalErrWrapped: ErrTestRuleCleanup,
criticalErrMessage: "failed cleaning up test rule: some output (exit code 4)",
},
"list input rules permission denied": {
buildRunner: func(ctrl *gomock.Controller) command.Runner {
runner := NewMockRunner(ctrl)
runner.EXPECT().Run(appendTestRuleMatcher).Return("", nil)
runner.EXPECT().Run(deleteTestRuleMatcher).Return("", nil)
runner.EXPECT().Run(listInputRulesMatcher).
Return("Permission denied (you must be root)", errDummy)
return runner
},
criticalErrWrapped: ErrNetAdminMissing,
criticalErrMessage: "NET_ADMIN capability is missing: " +
"Permission denied (you must be root)",
},
"list input rules unsupported": {
buildRunner: func(ctrl *gomock.Controller) command.Runner {
runner := NewMockRunner(ctrl)
runner.EXPECT().Run(appendTestRuleMatcher).Return("", nil)
runner.EXPECT().Run(deleteTestRuleMatcher).Return("", nil)
runner.EXPECT().Run(listInputRulesMatcher).
Return("some output", errDummy)
return runner
},
unsupportedMessage: "some output (exit code 4)",
},
"list input rules no policy": {
buildRunner: func(ctrl *gomock.Controller) command.Runner {
runner := NewMockRunner(ctrl)
runner.EXPECT().Run(appendTestRuleMatcher).Return("", nil)
runner.EXPECT().Run(deleteTestRuleMatcher).Return("", nil)
runner.EXPECT().Run(listInputRulesMatcher).
Return("some\noutput", nil)
return runner
},
criticalErrWrapped: ErrInputPolicyNotFound,
criticalErrMessage: "input policy not found: in INPUT rules: some\noutput",
},
"set policy permission denied": {
buildRunner: func(ctrl *gomock.Controller) command.Runner {
runner := NewMockRunner(ctrl)
runner.EXPECT().Run(appendTestRuleMatcher).Return("", nil)
runner.EXPECT().Run(deleteTestRuleMatcher).Return("", nil)
runner.EXPECT().Run(listInputRulesMatcher).
Return("\nChain INPUT (policy "+inputPolicy+")\nxx\n", nil)
runner.EXPECT().Run(setPolicyMatcher).
Return("Permission denied (you must be root)", errDummy)
return runner
},
criticalErrWrapped: ErrNetAdminMissing,
criticalErrMessage: "NET_ADMIN capability is missing: " +
"Permission denied (you must be root)",
},
"set policy unsupported": {
buildRunner: func(ctrl *gomock.Controller) command.Runner {
runner := NewMockRunner(ctrl)
runner.EXPECT().Run(appendTestRuleMatcher).Return("", nil)
runner.EXPECT().Run(deleteTestRuleMatcher).Return("", nil)
runner.EXPECT().Run(listInputRulesMatcher).
Return("\nChain INPUT (policy "+inputPolicy+")\nxx\n", nil)
runner.EXPECT().Run(setPolicyMatcher).
Return("some output", errDummy)
return runner
},
unsupportedMessage: "some output (exit code 4)",
},
"success": {
buildRunner: func(ctrl *gomock.Controller) command.Runner {
runner := NewMockRunner(ctrl)
runner.EXPECT().Run(appendTestRuleMatcher).Return("", nil)
runner.EXPECT().Run(deleteTestRuleMatcher).Return("", nil)
runner.EXPECT().Run(listInputRulesMatcher).
Return("\nChain INPUT (policy "+inputPolicy+")\nxx\n", nil)
runner.EXPECT().Run(setPolicyMatcher).Return("some output", nil)
return runner
},
ok: true,
},
}
for name, testCase := range testCases {
testCase := testCase
t.Run(name, func(t *testing.T) {
t.Parallel()
ctrl := gomock.NewController(t)
runner := testCase.buildRunner(ctrl)
ok, unsupportedMessage, criticalErr :=
testIptablesPath(ctx, path, runner)
assert.Equal(t, testCase.ok, ok)
assert.Equal(t, testCase.unsupportedMessage, unsupportedMessage)
assert.ErrorIs(t, criticalErr, testCase.criticalErrWrapped)
if testCase.criticalErrWrapped != nil {
assert.EqualError(t, criticalErr, testCase.criticalErrMessage)
}
})
}
}
func Test_isPermissionDenied(t *testing.T) {
t.Parallel()
testCases := map[string]struct {
errMessage string
ok bool
}{
"empty error": {},
"other error": {
errMessage: "some error",
},
"permission denied": {
errMessage: "Permission denied (you must be root) have you tried blabla",
ok: true,
},
}
for name, testCase := range testCases {
testCase := testCase
t.Run(name, func(t *testing.T) {
t.Parallel()
ok := isPermissionDenied(testCase.errMessage)
assert.Equal(t, testCase.ok, ok)
})
}
}
func Test_extractInputPolicy(t *testing.T) {
t.Parallel()
testCases := map[string]struct {
line string
policy string
ok bool
}{
"empty line": {},
"random line": {
line: "random line",
},
"only first part": {
line: "Chain INPUT (policy ",
},
"empty policy": {
line: "Chain INPUT (policy )",
},
"ACCEPT policy": {
line: "Chain INPUT (policy ACCEPT)",
policy: "ACCEPT",
ok: true,
},
"ACCEPT policy with surrounding garbage": {
line: "garbage Chain INPUT (policy ACCEPT\t) )g()arbage",
policy: "ACCEPT",
ok: true,
},
}
for name, testCase := range testCases {
testCase := testCase
t.Run(name, func(t *testing.T) {
t.Parallel()
policy, ok := extractInputPolicy(testCase.line)
assert.Equal(t, testCase.policy, policy)
assert.Equal(t, testCase.ok, ok)
})
}
}
func Test_randomInterfaceName(t *testing.T) {
t.Parallel()
const expectedRegex = `^[a-z0-9]{15}$`
interfaceName := randomInterfaceName()
assert.Regexp(t, expectedRegex, interfaceName)
}

View File

@@ -31,10 +31,12 @@ func (c *Config) SetVPNConnection(ctx context.Context,
remove := true remove := true
if c.vpnConnection.IP != nil { if c.vpnConnection.IP != nil {
if err := c.acceptOutputTrafficToVPN(ctx, c.defaultInterface, c.vpnConnection, remove); err != nil { for _, defaultRoute := range c.defaultRoutes {
if err := c.acceptOutputTrafficToVPN(ctx, defaultRoute.NetInterface, c.vpnConnection, remove); err != nil {
c.logger.Error("cannot remove outdated VPN connection rule: " + err.Error()) c.logger.Error("cannot remove outdated VPN connection rule: " + err.Error())
} }
} }
}
c.vpnConnection = models.Connection{} c.vpnConnection = models.Connection{}
if c.vpnIntf != "" { if c.vpnIntf != "" {
@@ -46,9 +48,11 @@ func (c *Config) SetVPNConnection(ctx context.Context,
remove = false remove = false
if err := c.acceptOutputTrafficToVPN(ctx, c.defaultInterface, connection, remove); err != nil { for _, defaultRoute := range c.defaultRoutes {
if err := c.acceptOutputTrafficToVPN(ctx, defaultRoute.NetInterface, connection, remove); err != nil {
return fmt.Errorf("cannot allow output traffic through VPN connection: %w", err) return fmt.Errorf("cannot allow output traffic through VPN connection: %w", err)
} }
}
c.vpnConnection = connection c.vpnConnection = connection
if err = c.acceptOutputThroughInterface(ctx, vpnIntf, remove); err != nil { if err = c.acceptOutputThroughInterface(ctx, vpnIntf, remove); err != nil {

View File

@@ -3,6 +3,9 @@ package healthcheck
import ( import (
"context" "context"
"errors"
"fmt"
"net"
"time" "time"
) )
@@ -14,7 +17,12 @@ func (s *Server) runHealthcheckLoop(ctx context.Context, done chan<- struct{}) {
for { for {
previousErr := s.handler.getErr() previousErr := s.handler.getErr()
err := healthCheck(ctx, s.pinger) const healthcheckTimeout = 3 * time.Second
healthcheckCtx, healthcheckCancel := context.WithTimeout(
ctx, healthcheckTimeout)
err := s.healthCheck(healthcheckCtx)
healthcheckCancel()
s.handler.setErr(err) s.handler.setErr(err)
if previousErr != nil && err == nil { if previousErr != nil && err == nil {
@@ -56,23 +64,40 @@ func (s *Server) runHealthcheckLoop(ctx context.Context, done chan<- struct{}) {
} }
} }
func healthCheck(ctx context.Context, pinger Pinger) (err error) { func (s *Server) healthCheck(ctx context.Context) (err error) {
// TODO use mullvad API if current provider is Mullvad // TODO use mullvad API if current provider is Mullvad
// If we run without root, you need to run this on the gluetun binary:
// setcap cap_net_raw=+ep /path/to/your/compiled/binary
// Alternatively, we could have a separate binary just for healthcheck to
// reduce attack surface.
errCh := make(chan error)
go func() {
errCh <- pinger.Run()
}()
select { address, err := makeAddressToDial(s.config.TargetAddress)
case <-ctx.Done(): if err != nil {
pinger.Stop()
<-errCh
return ctx.Err()
case err = <-errCh:
return err return err
} }
const dialNetwork = "tcp4"
connection, err := s.dialer.DialContext(ctx, dialNetwork, address)
if err != nil {
return fmt.Errorf("cannot dial: %w", err)
}
err = connection.Close()
if err != nil {
return fmt.Errorf("cannot close connection: %w", err)
}
return nil
}
func makeAddressToDial(address string) (addressToDial string, err error) {
host, port, err := net.SplitHostPort(address)
if err != nil {
addrErr := new(net.AddrError)
ok := errors.As(err, &addrErr)
if !ok || addrErr.Err != "missing port in address" {
return "", fmt.Errorf("cannot split host and port from address: %w", err)
}
host = address
const defaultPort = "443"
port = defaultPort
}
address = net.JoinHostPort(host, port)
return address, nil
} }

View File

@@ -1,46 +0,0 @@
//go:build integration
package healthcheck
import (
"context"
"testing"
"time"
"github.com/stretchr/testify/assert"
)
func Test_healthCheck_ping(t *testing.T) {
t.Parallel()
const timeout = time.Second
testCases := map[string]struct {
address string
err error
}{
"github.com": {
address: "github.com",
},
"99.99.99.99": {
address: "99.99.99.99",
err: context.DeadlineExceeded,
},
}
for name, testCase := range testCases {
testCase := testCase
t.Run(name, func(t *testing.T) {
t.Parallel()
ctx, cancel := context.WithTimeout(context.Background(), timeout)
defer cancel()
pinger := newPinger(testCase.address)
err := healthCheck(ctx, pinger)
assert.ErrorIs(t, testCase.err, err)
})
}
}

View File

@@ -2,40 +2,90 @@ package healthcheck
import ( import (
"context" "context"
"errors" "fmt"
"net"
"testing" "testing"
"time" "time"
"github.com/golang/mock/gomock" "github.com/qdm12/gluetun/internal/configuration/settings"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
) )
func Test_healthCheck(t *testing.T) { func Test_Server_healthCheck(t *testing.T) {
t.Parallel() t.Parallel()
t.Run("canceled real dialer", func(t *testing.T) {
t.Parallel()
dialer := &net.Dialer{}
const address = "cloudflare.com:443"
server := &Server{
dialer: dialer,
config: settings.Health{
TargetAddress: address,
},
}
canceledCtx, cancel := context.WithCancel(context.Background()) canceledCtx, cancel := context.WithCancel(context.Background())
cancel() cancel()
someErr := errors.New("error") err := server.healthCheck(canceledCtx)
require.Error(t, err)
assert.Contains(t, err.Error(), "operation was canceled")
})
t.Run("dial localhost:0", func(t *testing.T) {
t.Parallel()
listener, err := net.Listen("tcp4", "localhost:0")
require.NoError(t, err)
t.Cleanup(func() {
err = listener.Close()
assert.NoError(t, err)
})
listeningAddress := listener.Addr()
dialer := &net.Dialer{}
server := &Server{
dialer: dialer,
config: settings.Health{
TargetAddress: listeningAddress.String(),
},
}
const timeout = 100 * time.Millisecond
ctx, cancel := context.WithTimeout(context.Background(), timeout)
defer cancel()
err = server.healthCheck(ctx)
assert.NoError(t, err)
})
}
func Test_makeAddressToDial(t *testing.T) {
t.Parallel()
testCases := map[string]struct { testCases := map[string]struct {
ctx context.Context address string
runErr error addressToDial string
stopCall bool
err error err error
}{ }{
"success": { "host without port": {
ctx: context.Background(), address: "test.com",
addressToDial: "test.com:443",
}, },
"error": { "host with port": {
ctx: context.Background(), address: "test.com:80",
runErr: someErr, addressToDial: "test.com:80",
err: someErr,
}, },
"context canceled": { "bad address": {
ctx: canceledCtx, address: "test.com::",
stopCall: true, err: fmt.Errorf("cannot split host and port from address: address test.com::: too many colons in address"), //nolint:lll
err: context.Canceled,
}, },
} }
@@ -43,54 +93,15 @@ func Test_healthCheck(t *testing.T) {
testCase := testCase testCase := testCase
t.Run(name, func(t *testing.T) { t.Run(name, func(t *testing.T) {
t.Parallel() t.Parallel()
ctrl := gomock.NewController(t)
stopped := make(chan struct{}) addressToDial, err := makeAddressToDial(testCase.address)
pinger := NewMockPinger(ctrl)
pinger.EXPECT().Run().DoAndReturn(func() error {
if testCase.stopCall {
<-stopped
}
return testCase.runErr
})
if testCase.stopCall {
pinger.EXPECT().Stop().DoAndReturn(func() {
close(stopped)
})
}
err := healthCheck(testCase.ctx, pinger)
assert.ErrorIs(t, testCase.err, err)
})
}
t.Run("canceled real pinger", func(t *testing.T) {
t.Parallel()
pinger := newPinger("github.com")
canceledCtx, cancel := context.WithCancel(context.Background())
cancel()
err := healthCheck(canceledCtx, pinger)
assert.ErrorIs(t, context.Canceled, err)
})
t.Run("ping 127.0.0.1", func(t *testing.T) {
t.Parallel()
pinger := newPinger("127.0.0.1")
const timeout = 100 * time.Millisecond
ctx, cancel := context.WithTimeout(context.Background(), timeout)
defer cancel()
err := healthCheck(ctx, pinger)
assert.Equal(t, testCase.addressToDial, addressToDial)
if testCase.err != nil {
assert.EqualError(t, err, testCase.err.Error())
} else {
assert.NoError(t, err) assert.NoError(t, err)
}
}) })
}
} }

View File

@@ -1,18 +0,0 @@
package healthcheck
import "github.com/go-ping/ping"
//go:generate mockgen -destination=pinger_mock_test.go -package healthcheck . Pinger
type Pinger interface {
Run() error
Stop()
}
func newPinger(addrToPing string) (pinger *ping.Pinger) {
const count = 1
pinger = ping.New(addrToPing)
pinger.Count = count
pinger.SetPrivileged(true) // see https://github.com/go-ping/ping#linux
return pinger
}

View File

@@ -1,60 +0,0 @@
// Code generated by MockGen. DO NOT EDIT.
// Source: github.com/qdm12/gluetun/internal/healthcheck (interfaces: Pinger)
// Package healthcheck is a generated GoMock package.
package healthcheck
import (
reflect "reflect"
gomock "github.com/golang/mock/gomock"
)
// MockPinger is a mock of Pinger interface.
type MockPinger struct {
ctrl *gomock.Controller
recorder *MockPingerMockRecorder
}
// MockPingerMockRecorder is the mock recorder for MockPinger.
type MockPingerMockRecorder struct {
mock *MockPinger
}
// NewMockPinger creates a new mock instance.
func NewMockPinger(ctrl *gomock.Controller) *MockPinger {
mock := &MockPinger{ctrl: ctrl}
mock.recorder = &MockPingerMockRecorder{mock}
return mock
}
// EXPECT returns an object that allows the caller to indicate expected use.
func (m *MockPinger) EXPECT() *MockPingerMockRecorder {
return m.recorder
}
// Run mocks base method.
func (m *MockPinger) Run() error {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "Run")
ret0, _ := ret[0].(error)
return ret0
}
// Run indicates an expected call of Run.
func (mr *MockPingerMockRecorder) Run() *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Run", reflect.TypeOf((*MockPinger)(nil).Run))
}
// Stop mocks base method.
func (m *MockPinger) Stop() {
m.ctrl.T.Helper()
m.ctrl.Call(m, "Stop")
}
// Stop indicates an expected call of Stop.
func (mr *MockPingerMockRecorder) Stop() *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Stop", reflect.TypeOf((*MockPinger)(nil).Stop))
}

View File

@@ -2,6 +2,7 @@ package healthcheck
import ( import (
"context" "context"
"net"
"github.com/qdm12/gluetun/internal/configuration/settings" "github.com/qdm12/gluetun/internal/configuration/settings"
"github.com/qdm12/gluetun/internal/vpn" "github.com/qdm12/gluetun/internal/vpn"
@@ -16,7 +17,7 @@ type ServerRunner interface {
type Server struct { type Server struct {
logger Logger logger Logger
handler *handler handler *handler
pinger Pinger dialer *net.Dialer
config settings.Health config settings.Health
vpn vpnHealth vpn vpnHealth
} }
@@ -26,7 +27,7 @@ func NewServer(config settings.Health,
return &Server{ return &Server{
logger: logger, logger: logger,
handler: newHandler(), handler: newHandler(),
pinger: newPinger(config.AddressToPing), dialer: &net.Dialer{},
config: config, config: config,
vpn: vpnHealth{ vpn: vpnHealth{
looper: vpnLooper, looper: vpnLooper,

View File

@@ -7,7 +7,6 @@ import (
gomock "github.com/golang/mock/gomock" gomock "github.com/golang/mock/gomock"
) )
func stringPtr(s string) *string { return &s }
func durationPtr(d time.Duration) *time.Duration { return &d } func durationPtr(d time.Duration) *time.Duration { return &d }
var _ Logger = (*testLogger)(nil) var _ Logger = (*testLogger)(nil)

View File

@@ -23,12 +23,11 @@ func (s *Server) Run(ctx context.Context, ready chan<- struct{}, done chan<- str
return return
} }
s.logger.Warn(s.name + " http server shutting down: " + ctx.Err().Error())
shutdownCtx, cancel := context.WithTimeout( shutdownCtx, cancel := context.WithTimeout(
context.Background(), s.shutdownTimeout) context.Background(), s.shutdownTimeout)
defer cancel() defer cancel()
if err := server.Shutdown(shutdownCtx); err != nil { if err := server.Shutdown(shutdownCtx); err != nil {
s.logger.Error(s.name + " http server failed shutting down within " + s.logger.Error("http server failed shutting down within " +
s.shutdownTimeout.String()) s.shutdownTimeout.String())
} }
}() }()
@@ -47,7 +46,7 @@ func (s *Server) Run(ctx context.Context, ready chan<- struct{}, done chan<- str
close(s.addressSet) close(s.addressSet)
// note: no further write so no need to mutex // note: no further write so no need to mutex
s.logger.Info(s.name + " http server listening on " + s.address) s.logger.Info("http server listening on " + s.address)
close(ready) close(ready)
err = server.Serve(listener) err = server.Serve(listener)

View File

@@ -16,12 +16,10 @@ func Test_Server_Run_success(t *testing.T) {
ctrl := gomock.NewController(t) ctrl := gomock.NewController(t)
logger := NewMockLogger(ctrl) logger := NewMockLogger(ctrl)
logger.EXPECT().Info(newRegexMatcher("^test http server listening on 127.0.0.1:[1-9][0-9]{0,4}$")) logger.EXPECT().Info(newRegexMatcher("^http server listening on 127.0.0.1:[1-9][0-9]{0,4}$"))
logger.EXPECT().Warn("test http server shutting down: context canceled")
const shutdownTimeout = 10 * time.Second const shutdownTimeout = 10 * time.Second
server := &Server{ server := &Server{
name: "test",
address: "127.0.0.1:0", address: "127.0.0.1:0",
addressSet: make(chan struct{}), addressSet: make(chan struct{}),
logger: logger, logger: logger,
@@ -55,7 +53,6 @@ func Test_Server_Run_failure(t *testing.T) {
logger.EXPECT().Error("listen tcp: address -1: invalid port") logger.EXPECT().Error("listen tcp: address -1: invalid port")
server := &Server{ server := &Server{
name: "test",
address: "127.0.0.1:-1", address: "127.0.0.1:-1",
addressSet: make(chan struct{}), addressSet: make(chan struct{}),
logger: logger, logger: logger,

View File

@@ -29,7 +29,6 @@ type AddressGetter interface {
// 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 {
name string
address string address string
addressSet chan struct{} addressSet chan struct{}
handler http.Handler handler http.Handler
@@ -47,7 +46,6 @@ func New(settings Settings) (s *Server, err error) {
} }
return &Server{ return &Server{
name: *settings.Name,
address: settings.Address, address: settings.Address,
addressSet: make(chan struct{}), addressSet: make(chan struct{}),
handler: settings.Handler, handler: settings.Handler,

View File

@@ -29,14 +29,12 @@ func Test_New(t *testing.T) {
}, },
"filled settings": { "filled settings": {
settings: Settings{ settings: Settings{
Name: stringPtr("name"),
Address: ":8001", Address: ":8001",
Handler: someHandler, Handler: someHandler,
Logger: someLogger, Logger: someLogger,
ShutdownTimeout: durationPtr(time.Second), ShutdownTimeout: durationPtr(time.Second),
}, },
expected: &Server{ expected: &Server{
name: "name",
address: ":8001", address: ":8001",
handler: someHandler, handler: someHandler,
logger: someLogger, logger: someLogger,

View File

@@ -5,7 +5,6 @@ import (
"fmt" "fmt"
"net/http" "net/http"
"os" "os"
"strings"
"time" "time"
"github.com/qdm12/gluetun/internal/configuration/settings/helpers" "github.com/qdm12/gluetun/internal/configuration/settings/helpers"
@@ -14,9 +13,6 @@ import (
) )
type Settings struct { type Settings struct {
// Name is the server name to use in logs.
// It defaults to the empty string.
Name *string
// Address is the server listening address. // Address is the server listening address.
// It defaults to :8000. // It defaults to :8000.
Address string Address string
@@ -32,7 +28,6 @@ type Settings struct {
} }
func (s *Settings) SetDefaults() { func (s *Settings) SetDefaults() {
s.Name = helpers.DefaultStringPtr(s.Name, "")
s.Address = helpers.DefaultString(s.Address, ":8000") s.Address = helpers.DefaultString(s.Address, ":8000")
const defaultShutdownTimeout = 3 * time.Second const defaultShutdownTimeout = 3 * time.Second
s.ShutdownTimeout = helpers.DefaultDuration(s.ShutdownTimeout, defaultShutdownTimeout) s.ShutdownTimeout = helpers.DefaultDuration(s.ShutdownTimeout, defaultShutdownTimeout)
@@ -40,7 +35,6 @@ func (s *Settings) SetDefaults() {
func (s Settings) Copy() Settings { func (s Settings) Copy() Settings {
return Settings{ return Settings{
Name: helpers.CopyStringPtr(s.Name),
Address: s.Address, Address: s.Address,
Handler: s.Handler, Handler: s.Handler,
Logger: s.Logger, Logger: s.Logger,
@@ -49,7 +43,6 @@ func (s Settings) Copy() Settings {
} }
func (s *Settings) MergeWith(other Settings) { func (s *Settings) MergeWith(other Settings) {
s.Name = helpers.MergeWithStringPtr(s.Name, other.Name)
s.Address = helpers.MergeWithString(s.Address, other.Address) s.Address = helpers.MergeWithString(s.Address, other.Address)
s.Handler = helpers.MergeWithHTTPHandler(s.Handler, other.Handler) s.Handler = helpers.MergeWithHTTPHandler(s.Handler, other.Handler)
if s.Logger == nil { if s.Logger == nil {
@@ -59,7 +52,6 @@ func (s *Settings) MergeWith(other Settings) {
} }
func (s *Settings) OverrideWith(other Settings) { func (s *Settings) OverrideWith(other Settings) {
s.Name = helpers.OverrideWithStringPtr(s.Name, other.Name)
s.Address = helpers.OverrideWithString(s.Address, other.Address) s.Address = helpers.OverrideWithString(s.Address, other.Address)
s.Handler = helpers.OverrideWithHTTPHandler(s.Handler, other.Handler) s.Handler = helpers.OverrideWithHTTPHandler(s.Handler, other.Handler)
if other.Logger != nil { if other.Logger != nil {
@@ -100,7 +92,7 @@ func (s Settings) Validate() (err error) {
} }
func (s Settings) ToLinesNode() (node *gotree.Node) { func (s Settings) ToLinesNode() (node *gotree.Node) {
node = gotree.New("%s HTTP server settings:", strings.Title(*s.Name)) node = gotree.New("HTTP server settings:")
node.Appendf("Listening address: %s", s.Address) node.Appendf("Listening address: %s", s.Address)
node.Appendf("Shutdown timeout: %s", *s.ShutdownTimeout) node.Appendf("Shutdown timeout: %s", *s.ShutdownTimeout)
return node return node

View File

@@ -21,19 +21,16 @@ func Test_Settings_SetDefaults(t *testing.T) {
"empty settings": { "empty settings": {
settings: Settings{}, settings: Settings{},
expected: Settings{ expected: Settings{
Name: stringPtr(""),
Address: ":8000", Address: ":8000",
ShutdownTimeout: durationPtr(defaultTimeout), ShutdownTimeout: durationPtr(defaultTimeout),
}, },
}, },
"filled settings": { "filled settings": {
settings: Settings{ settings: Settings{
Name: stringPtr("name"),
Address: ":8001", Address: ":8001",
ShutdownTimeout: durationPtr(time.Second), ShutdownTimeout: durationPtr(time.Second),
}, },
expected: Settings{ expected: Settings{
Name: stringPtr("name"),
Address: ":8001", Address: ":8001",
ShutdownTimeout: durationPtr(time.Second), ShutdownTimeout: durationPtr(time.Second),
}, },
@@ -65,14 +62,12 @@ func Test_Settings_Copy(t *testing.T) {
"empty settings": {}, "empty settings": {},
"filled settings": { "filled settings": {
settings: Settings{ settings: Settings{
Name: stringPtr("name"),
Address: ":8001", Address: ":8001",
Handler: someHandler, Handler: someHandler,
Logger: someLogger, Logger: someLogger,
ShutdownTimeout: durationPtr(time.Second), ShutdownTimeout: durationPtr(time.Second),
}, },
expected: Settings{ expected: Settings{
Name: stringPtr("name"),
Address: ":8001", Address: ":8001",
Handler: someHandler, Handler: someHandler,
Logger: someLogger, Logger: someLogger,
@@ -107,14 +102,12 @@ 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{
Name: stringPtr("name"),
Address: ":8001", Address: ":8001",
Handler: someHandler, Handler: someHandler,
Logger: someLogger, Logger: someLogger,
ShutdownTimeout: durationPtr(time.Second), ShutdownTimeout: durationPtr(time.Second),
}, },
expected: Settings{ expected: Settings{
Name: stringPtr("name"),
Address: ":8001", Address: ":8001",
Handler: someHandler, Handler: someHandler,
Logger: someLogger, Logger: someLogger,
@@ -123,14 +116,12 @@ func Test_Settings_MergeWith(t *testing.T) {
}, },
"merge filled with empty": { "merge filled with empty": {
settings: Settings{ settings: Settings{
Name: stringPtr("name"),
Address: ":8001", Address: ":8001",
Handler: someHandler, Handler: someHandler,
Logger: someLogger, Logger: someLogger,
ShutdownTimeout: durationPtr(time.Second), ShutdownTimeout: durationPtr(time.Second),
}, },
expected: Settings{ expected: Settings{
Name: stringPtr("name"),
Address: ":8001", Address: ":8001",
Handler: someHandler, Handler: someHandler,
Logger: someLogger, Logger: someLogger,
@@ -165,14 +156,12 @@ 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{
Name: stringPtr("name"),
Address: ":8001", Address: ":8001",
Handler: someHandler, Handler: someHandler,
Logger: someLogger, Logger: someLogger,
ShutdownTimeout: durationPtr(time.Second), ShutdownTimeout: durationPtr(time.Second),
}, },
expected: Settings{ expected: Settings{
Name: stringPtr("name"),
Address: ":8001", Address: ":8001",
Handler: someHandler, Handler: someHandler,
Logger: someLogger, Logger: someLogger,
@@ -181,14 +170,12 @@ func Test_Settings_OverrideWith(t *testing.T) {
}, },
"override filled with empty": { "override filled with empty": {
settings: Settings{ settings: Settings{
Name: stringPtr("name"),
Address: ":8001", Address: ":8001",
Handler: someHandler, Handler: someHandler,
Logger: someLogger, Logger: someLogger,
ShutdownTimeout: durationPtr(time.Second), ShutdownTimeout: durationPtr(time.Second),
}, },
expected: Settings{ expected: Settings{
Name: stringPtr("name"),
Address: ":8001", Address: ":8001",
Handler: someHandler, Handler: someHandler,
Logger: someLogger, Logger: someLogger,
@@ -197,19 +184,16 @@ func Test_Settings_OverrideWith(t *testing.T) {
}, },
"override filled with filled": { "override filled with filled": {
settings: Settings{ settings: Settings{
Name: stringPtr("name"),
Address: ":8001", Address: ":8001",
Handler: someHandler, Handler: someHandler,
Logger: someLogger, Logger: someLogger,
ShutdownTimeout: durationPtr(time.Second), ShutdownTimeout: durationPtr(time.Second),
}, },
other: Settings{ other: Settings{
Name: stringPtr("name2"),
Address: ":8002", Address: ":8002",
ShutdownTimeout: durationPtr(time.Hour), ShutdownTimeout: durationPtr(time.Hour),
}, },
expected: Settings{ expected: Settings{
Name: stringPtr("name2"),
Address: ":8002", Address: ":8002",
Handler: someHandler, Handler: someHandler,
Logger: someLogger, Logger: someLogger,
@@ -307,11 +291,10 @@ func Test_Settings_String(t *testing.T) {
}{ }{
"all values": { "all values": {
settings: Settings{ settings: Settings{
Name: stringPtr("name"),
Address: ":8000", Address: ":8000",
ShutdownTimeout: durationPtr(time.Second), ShutdownTimeout: durationPtr(time.Second),
}, },
s: `Name HTTP server settings: s: `HTTP server settings:
├── Listening address: :8000 ├── Listening address: :8000
└── Shutdown timeout: 1s`, └── Shutdown timeout: 1s`,
}, },

View File

@@ -28,246 +28,98 @@ func (a AllServers) GetCopy() (servers AllServers) {
return servers return servers
} }
func (a *AllServers) GetCyberghost() (servers []CyberghostServer) { func (a *AllServers) GetCyberghost() (servers []Server) {
if a.Cyberghost.Servers == nil { return copyServers(a.Cyberghost.Servers)
return nil
}
servers = make([]CyberghostServer, len(a.Cyberghost.Servers))
for i, serverToCopy := range a.Cyberghost.Servers {
servers[i] = serverToCopy
servers[i].IPs = copyIPs(serverToCopy.IPs)
}
return servers
} }
func (a *AllServers) GetExpressvpn() (servers []ExpressvpnServer) { func (a *AllServers) GetExpressvpn() (servers []Server) {
if a.Expressvpn.Servers == nil { return copyServers(a.Expressvpn.Servers)
return nil
}
servers = make([]ExpressvpnServer, len(a.Expressvpn.Servers))
for i, serverToCopy := range a.Expressvpn.Servers {
servers[i] = serverToCopy
servers[i].IPs = copyIPs(serverToCopy.IPs)
}
return servers
} }
func (a *AllServers) GetFastestvpn() (servers []FastestvpnServer) { func (a *AllServers) GetFastestvpn() (servers []Server) {
if a.Fastestvpn.Servers == nil { return copyServers(a.Fastestvpn.Servers)
return nil
}
servers = make([]FastestvpnServer, len(a.Fastestvpn.Servers))
for i, serverToCopy := range a.Fastestvpn.Servers {
servers[i] = serverToCopy
servers[i].IPs = copyIPs(serverToCopy.IPs)
}
return servers
} }
func (a *AllServers) GetHideMyAss() (servers []HideMyAssServer) { func (a *AllServers) GetHideMyAss() (servers []Server) {
if a.HideMyAss.Servers == nil { return copyServers(a.HideMyAss.Servers)
return nil
}
servers = make([]HideMyAssServer, len(a.HideMyAss.Servers))
for i, serverToCopy := range a.HideMyAss.Servers {
servers[i] = serverToCopy
servers[i].IPs = copyIPs(serverToCopy.IPs)
}
return servers
} }
func (a *AllServers) GetIpvanish() (servers []IpvanishServer) { func (a *AllServers) GetIpvanish() (servers []Server) {
if a.Ipvanish.Servers == nil { return copyServers(a.Ipvanish.Servers)
return nil
}
servers = make([]IpvanishServer, len(a.Ipvanish.Servers))
for i, serverToCopy := range a.Ipvanish.Servers {
servers[i] = serverToCopy
servers[i].IPs = copyIPs(serverToCopy.IPs)
}
return servers
} }
func (a *AllServers) GetIvpn() (servers []IvpnServer) { func (a *AllServers) GetIvpn() (servers []Server) {
if a.Ivpn.Servers == nil { return copyServers(a.Ivpn.Servers)
return nil
}
servers = make([]IvpnServer, len(a.Ivpn.Servers))
for i, serverToCopy := range a.Ivpn.Servers {
servers[i] = serverToCopy
servers[i].IPs = copyIPs(serverToCopy.IPs)
}
return servers
} }
func (a *AllServers) GetMullvad() (servers []MullvadServer) { func (a *AllServers) GetMullvad() (servers []Server) {
if a.Mullvad.Servers == nil { return copyServers(a.Mullvad.Servers)
return nil
}
servers = make([]MullvadServer, len(a.Mullvad.Servers))
for i, serverToCopy := range a.Mullvad.Servers {
servers[i] = serverToCopy
servers[i].IPs = copyIPs(serverToCopy.IPs)
servers[i].IPsV6 = copyIPs(serverToCopy.IPsV6)
}
return servers
} }
func (a *AllServers) GetNordvpn() (servers []NordvpnServer) { func (a *AllServers) GetNordvpn() (servers []Server) {
if a.Nordvpn.Servers == nil { return copyServers(a.Nordvpn.Servers)
return nil
}
servers = make([]NordvpnServer, len(a.Nordvpn.Servers))
for i, serverToCopy := range a.Nordvpn.Servers {
servers[i] = serverToCopy
servers[i].IP = copyIP(serverToCopy.IP)
}
return servers
} }
func (a *AllServers) GetPerfectprivacy() (servers []PerfectprivacyServer) { func (a *AllServers) GetPerfectprivacy() (servers []Server) {
if a.Perfectprivacy.Servers == nil { return copyServers(a.Perfectprivacy.Servers)
return nil
}
servers = make([]PerfectprivacyServer, len(a.Perfectprivacy.Servers))
for i, serverToCopy := range a.Perfectprivacy.Servers {
servers[i] = serverToCopy
servers[i].IPs = copyIPs(serverToCopy.IPs)
}
return servers
} }
func (a *AllServers) GetPia() (servers []PIAServer) { func (a *AllServers) GetPia() (servers []Server) {
if a.Pia.Servers == nil { return copyServers(a.Pia.Servers)
return nil
}
servers = make([]PIAServer, len(a.Pia.Servers))
for i, serverToCopy := range a.Pia.Servers {
servers[i] = serverToCopy
servers[i].IPs = copyIPs(serverToCopy.IPs)
}
return servers
} }
func (a *AllServers) GetPrivado() (servers []PrivadoServer) { func (a *AllServers) GetPrivado() (servers []Server) {
if a.Privado.Servers == nil { return copyServers(a.Privado.Servers)
return nil
}
servers = make([]PrivadoServer, len(a.Privado.Servers))
for i, serverToCopy := range a.Privado.Servers {
servers[i] = serverToCopy
servers[i].IP = copyIP(serverToCopy.IP)
}
return servers
} }
func (a *AllServers) GetPrivatevpn() (servers []PrivatevpnServer) { func (a *AllServers) GetPrivatevpn() (servers []Server) {
if a.Privatevpn.Servers == nil { return copyServers(a.Privatevpn.Servers)
return nil
}
servers = make([]PrivatevpnServer, len(a.Privatevpn.Servers))
for i, serverToCopy := range a.Privatevpn.Servers {
servers[i] = serverToCopy
servers[i].IPs = copyIPs(serverToCopy.IPs)
}
return servers
} }
func (a *AllServers) GetProtonvpn() (servers []ProtonvpnServer) { func (a *AllServers) GetProtonvpn() (servers []Server) {
if a.Protonvpn.Servers == nil { return copyServers(a.Protonvpn.Servers)
return nil
}
servers = make([]ProtonvpnServer, len(a.Protonvpn.Servers))
for i, serverToCopy := range a.Protonvpn.Servers {
servers[i] = serverToCopy
servers[i].EntryIP = copyIP(serverToCopy.EntryIP)
servers[i].ExitIPs = copyIPs(serverToCopy.ExitIPs)
}
return servers
} }
func (a *AllServers) GetPurevpn() (servers []PurevpnServer) { func (a *AllServers) GetPurevpn() (servers []Server) {
if a.Purevpn.Servers == nil { return copyServers(a.Purevpn.Servers)
return nil
}
servers = make([]PurevpnServer, len(a.Purevpn.Servers))
for i, serverToCopy := range a.Purevpn.Servers {
servers[i] = serverToCopy
servers[i].IPs = copyIPs(serverToCopy.IPs)
}
return servers
} }
func (a *AllServers) GetSurfshark() (servers []SurfsharkServer) { func (a *AllServers) GetSurfshark() (servers []Server) {
if a.Surfshark.Servers == nil { return copyServers(a.Surfshark.Servers)
return nil
}
servers = make([]SurfsharkServer, len(a.Surfshark.Servers))
for i, serverToCopy := range a.Surfshark.Servers {
servers[i] = serverToCopy
servers[i].IPs = copyIPs(serverToCopy.IPs)
}
return servers
} }
func (a *AllServers) GetTorguard() (servers []TorguardServer) { func (a *AllServers) GetTorguard() (servers []Server) {
if a.Torguard.Servers == nil { return copyServers(a.Torguard.Servers)
return nil
}
servers = make([]TorguardServer, len(a.Torguard.Servers))
for i, serverToCopy := range a.Torguard.Servers {
servers[i] = serverToCopy
servers[i].IPs = copyIPs(serverToCopy.IPs)
}
return servers
} }
func (a *AllServers) GetVPNUnlimited() (servers []VPNUnlimitedServer) { func (a *AllServers) GetVPNUnlimited() (servers []Server) {
if a.VPNUnlimited.Servers == nil { return copyServers(a.VPNUnlimited.Servers)
return nil
}
servers = make([]VPNUnlimitedServer, len(a.VPNUnlimited.Servers))
for i, serverToCopy := range a.VPNUnlimited.Servers {
servers[i] = serverToCopy
servers[i].IPs = copyIPs(serverToCopy.IPs)
}
return servers
} }
func (a *AllServers) GetVyprvpn() (servers []VyprvpnServer) { func (a *AllServers) GetVyprvpn() (servers []Server) {
if a.Vyprvpn.Servers == nil { return copyServers(a.Vyprvpn.Servers)
return nil
}
servers = make([]VyprvpnServer, len(a.Vyprvpn.Servers))
for i, serverToCopy := range a.Vyprvpn.Servers {
servers[i] = serverToCopy
servers[i].IPs = copyIPs(serverToCopy.IPs)
}
return servers
} }
func (a *AllServers) GetWevpn() (servers []WevpnServer) { func (a *AllServers) GetWevpn() (servers []Server) {
if a.Windscribe.Servers == nil { return copyServers(a.Wevpn.Servers)
return nil
}
servers = make([]WevpnServer, len(a.Wevpn.Servers))
for i, serverToCopy := range a.Wevpn.Servers {
servers[i] = serverToCopy
servers[i].IPs = copyIPs(serverToCopy.IPs)
}
return servers
} }
func (a *AllServers) GetWindscribe() (servers []WindscribeServer) { func (a *AllServers) GetWindscribe() (servers []Server) {
if a.Windscribe.Servers == nil { return copyServers(a.Windscribe.Servers)
}
func copyServers(servers []Server) (serversCopy []Server) {
if servers == nil {
return nil return nil
} }
servers = make([]WindscribeServer, len(a.Windscribe.Servers))
for i, serverToCopy := range a.Windscribe.Servers { serversCopy = make([]Server, len(servers))
servers[i] = serverToCopy for i, server := range servers {
servers[i].IPs = copyIPs(serverToCopy.IPs) serversCopy[i] = server
serversCopy[i].IPs = copyIPs(server.IPs)
} }
return servers
return serversCopy
} }
func copyIPs(toCopy []net.IP) (copied []net.IP) { func copyIPs(toCopy []net.IP) (copied []net.IP) {

View File

@@ -10,101 +10,100 @@ import (
func Test_AllServers_GetCopy(t *testing.T) { func Test_AllServers_GetCopy(t *testing.T) {
allServers := AllServers{ allServers := AllServers{
Cyberghost: CyberghostServers{ Cyberghost: Servers{
Version: 2, Version: 2,
Servers: []CyberghostServer{{ Servers: []Server{{
IPs: []net.IP{{1, 2, 3, 4}}, IPs: []net.IP{{1, 2, 3, 4}},
}}, }},
}, },
Expressvpn: ExpressvpnServers{ Expressvpn: Servers{
Servers: []ExpressvpnServer{{ Servers: []Server{{
IPs: []net.IP{{1, 2, 3, 4}}, IPs: []net.IP{{1, 2, 3, 4}},
}}, }},
}, },
Fastestvpn: FastestvpnServers{ Fastestvpn: Servers{
Servers: []FastestvpnServer{{ Servers: []Server{{
IPs: []net.IP{{1, 2, 3, 4}}, IPs: []net.IP{{1, 2, 3, 4}},
}}, }},
}, },
HideMyAss: HideMyAssServers{ HideMyAss: Servers{
Servers: []HideMyAssServer{{ Servers: []Server{{
IPs: []net.IP{{1, 2, 3, 4}}, IPs: []net.IP{{1, 2, 3, 4}},
}}, }},
}, },
Ipvanish: IpvanishServers{ Ipvanish: Servers{
Servers: []IpvanishServer{{ Servers: []Server{{
IPs: []net.IP{{1, 2, 3, 4}}, IPs: []net.IP{{1, 2, 3, 4}},
}}, }},
}, },
Ivpn: IvpnServers{ Ivpn: Servers{
Servers: []IvpnServer{{ Servers: []Server{{
IPs: []net.IP{{1, 2, 3, 4}}, IPs: []net.IP{{1, 2, 3, 4}},
}}, }},
}, },
Mullvad: MullvadServers{ Mullvad: Servers{
Servers: []MullvadServer{{ Servers: []Server{{
IPs: []net.IP{{1, 2, 3, 4}}, IPs: []net.IP{{1, 2, 3, 4}},
}}, }},
}, },
Nordvpn: NordvpnServers{ Nordvpn: Servers{
Servers: []NordvpnServer{{ Servers: []Server{{
IP: net.IP{1, 2, 3, 4},
}},
},
Perfectprivacy: PerfectprivacyServers{
Servers: []PerfectprivacyServer{{
IPs: []net.IP{{1, 2, 3, 4}}, IPs: []net.IP{{1, 2, 3, 4}},
}}, }},
}, },
Privado: PrivadoServers{ Perfectprivacy: Servers{
Servers: []PrivadoServer{{ Servers: []Server{{
IP: net.IP{1, 2, 3, 4},
}},
},
Pia: PiaServers{
Servers: []PIAServer{{
IPs: []net.IP{{1, 2, 3, 4}}, IPs: []net.IP{{1, 2, 3, 4}},
}}, }},
}, },
Privatevpn: PrivatevpnServers{ Privado: Servers{
Servers: []PrivatevpnServer{{ Servers: []Server{{
IPs: []net.IP{{1, 2, 3, 4}}, IPs: []net.IP{{1, 2, 3, 4}},
}}, }},
}, },
Protonvpn: ProtonvpnServers{ Pia: Servers{
Servers: []ProtonvpnServer{{ Servers: []Server{{
EntryIP: net.IP{1, 2, 3, 4}, IPs: []net.IP{{1, 2, 3, 4}},
ExitIPs: []net.IP{{1, 2, 3, 4}},
}}, }},
}, },
Purevpn: PurevpnServers{ Privatevpn: Servers{
Servers: []Server{{
IPs: []net.IP{{1, 2, 3, 4}},
}},
},
Protonvpn: Servers{
Servers: []Server{{
IPs: []net.IP{{1, 2, 3, 4}},
}},
},
Purevpn: Servers{
Version: 1, Version: 1,
Servers: []PurevpnServer{{ Servers: []Server{{
IPs: []net.IP{{1, 2, 3, 4}}, IPs: []net.IP{{1, 2, 3, 4}},
}}, }},
}, },
Surfshark: SurfsharkServers{ Surfshark: Servers{
Servers: []SurfsharkServer{{ Servers: []Server{{
IPs: []net.IP{{1, 2, 3, 4}}, IPs: []net.IP{{1, 2, 3, 4}},
}}, }},
}, },
Torguard: TorguardServers{ Torguard: Servers{
Servers: []TorguardServer{{ Servers: []Server{{
IPs: []net.IP{{1, 2, 3, 4}}, IPs: []net.IP{{1, 2, 3, 4}},
}}, }},
}, },
VPNUnlimited: VPNUnlimitedServers{ VPNUnlimited: Servers{
Servers: []VPNUnlimitedServer{{ Servers: []Server{{
IPs: []net.IP{{1, 2, 3, 4}}, IPs: []net.IP{{1, 2, 3, 4}},
}}, }},
}, },
Vyprvpn: VyprvpnServers{ Vyprvpn: Servers{
Servers: []VyprvpnServer{{ Servers: []Server{{
IPs: []net.IP{{1, 2, 3, 4}}, IPs: []net.IP{{1, 2, 3, 4}},
}}, }},
}, },
Windscribe: WindscribeServers{ Windscribe: Servers{
Servers: []WindscribeServer{{ Servers: []Server{{
IPs: []net.IP{{1, 2, 3, 4}}, IPs: []net.IP{{1, 2, 3, 4}},
}}, }},
}, },
@@ -117,8 +116,8 @@ func Test_AllServers_GetCopy(t *testing.T) {
func Test_AllServers_GetVyprvpn(t *testing.T) { func Test_AllServers_GetVyprvpn(t *testing.T) {
allServers := AllServers{ allServers := AllServers{
Vyprvpn: VyprvpnServers{ Vyprvpn: Servers{
Servers: []VyprvpnServer{ Servers: []Server{
{Hostname: "a", IPs: []net.IP{{1, 1, 1, 1}}}, {Hostname: "a", IPs: []net.IP{{1, 1, 1, 1}}},
{Hostname: "b", IPs: []net.IP{{2, 2, 2, 2}}}, {Hostname: "b", IPs: []net.IP{{2, 2, 2, 2}}},
}, },
@@ -127,7 +126,7 @@ func Test_AllServers_GetVyprvpn(t *testing.T) {
servers := allServers.GetVyprvpn() servers := allServers.GetVyprvpn()
expectedServers := []VyprvpnServer{ expectedServers := []Server{
{Hostname: "a", IPs: []net.IP{{1, 1, 1, 1}}}, {Hostname: "a", IPs: []net.IP{{1, 1, 1, 1}}},
{Hostname: "b", IPs: []net.IP{{2, 2, 2, 2}}}, {Hostname: "b", IPs: []net.IP{{2, 2, 2, 2}}},
} }

View File

@@ -3,6 +3,8 @@ package models
import ( import (
"fmt" "fmt"
"strings" "strings"
"github.com/qdm12/gluetun/internal/constants/providers"
) )
func boolToMarkdown(b bool) string { func boolToMarkdown(b bool) string {
@@ -14,280 +16,126 @@ func boolToMarkdown(b bool) string {
func markdownTableHeading(legendFields ...string) (markdown string) { func markdownTableHeading(legendFields ...string) (markdown string) {
return "| " + strings.Join(legendFields, " | ") + " |\n" + return "| " + strings.Join(legendFields, " | ") + " |\n" +
"|" + strings.Repeat(" --- |", len(legendFields)) + "\n" "|" + strings.Repeat(" --- |", len(legendFields))
} }
func (s *CyberghostServers) ToMarkdown() (markdown string) { const (
markdown = markdownTableHeading("Country", "Hostname", "TCP", "UDP") vpnHeader = "VPN"
for _, server := range s.Servers { countryHeader = "Country"
markdown += server.ToMarkdown() + "\n" regionHeader = "Region"
cityHeader = "City"
ispHeader = "ISP"
ownedHeader = "Owned"
numberHeader = "Number"
hostnameHeader = "Hostname"
tcpHeader = "TCP"
udpHeader = "UDP"
multiHopHeader = "MultiHop"
freeHeader = "Free"
streamHeader = "Stream"
portForwardHeader = "Port forwarding"
)
func (s *Server) ToMarkdown(headers ...string) (markdown string) {
if len(headers) == 0 {
return ""
} }
fields := make([]string, len(headers))
for i, header := range headers {
switch header {
case vpnHeader:
fields[i] = s.VPN
case countryHeader:
fields[i] = s.Country
case regionHeader:
fields[i] = s.Region
case cityHeader:
fields[i] = s.City
case ispHeader:
fields[i] = s.ISP
case ownedHeader:
fields[i] = boolToMarkdown(s.Owned)
case numberHeader:
fields[i] = fmt.Sprint(s.Number)
case hostnameHeader:
fields[i] = fmt.Sprintf("`%s`", s.Hostname)
case tcpHeader:
fields[i] = boolToMarkdown(s.TCP)
case udpHeader:
fields[i] = boolToMarkdown(s.UDP)
case multiHopHeader:
fields[i] = boolToMarkdown(s.MultiHop)
case freeHeader:
fields[i] = boolToMarkdown(s.Free)
case streamHeader:
fields[i] = boolToMarkdown(s.Stream)
case portForwardHeader:
fields[i] = boolToMarkdown(s.PortForward)
}
}
return "| " + strings.Join(fields, " | ") + " |"
}
func (s *Servers) ToMarkdown(vpnProvider string) (markdown string) {
headers := getMarkdownHeaders(vpnProvider)
legend := markdownTableHeading(headers...)
entries := make([]string, len(s.Servers))
for i, server := range s.Servers {
entries[i] = server.ToMarkdown(headers...)
}
markdown = legend + "\n" +
strings.Join(entries, "\n") + "\n"
return markdown return markdown
} }
func (s CyberghostServer) ToMarkdown() (markdown string) { func getMarkdownHeaders(vpnProvider string) (headers []string) {
return fmt.Sprintf("| %s | `%s` | %s | %s |", s.Country, s.Hostname, switch vpnProvider {
boolToMarkdown(s.TCP), boolToMarkdown(s.UDP)) case providers.Cyberghost:
} return []string{countryHeader, hostnameHeader, tcpHeader, udpHeader}
case providers.Expressvpn:
func (s *ExpressvpnServers) ToMarkdown() (markdown string) { return []string{countryHeader, cityHeader, hostnameHeader, tcpHeader, udpHeader}
markdown = markdownTableHeading("Country", "City", "Hostname", "TCP", "UDP") case providers.Fastestvpn:
for _, server := range s.Servers { return []string{countryHeader, hostnameHeader, tcpHeader, udpHeader}
markdown += server.ToMarkdown() + "\n" case providers.HideMyAss:
return []string{countryHeader, regionHeader, cityHeader, hostnameHeader, tcpHeader, udpHeader}
case providers.Ipvanish:
return []string{countryHeader, cityHeader, hostnameHeader, tcpHeader, udpHeader}
case providers.Ivpn:
return []string{countryHeader, cityHeader, ispHeader, hostnameHeader, vpnHeader, tcpHeader, udpHeader}
case providers.Mullvad:
return []string{countryHeader, cityHeader, ispHeader, ownedHeader, hostnameHeader, vpnHeader}
case providers.Nordvpn:
return []string{countryHeader, regionHeader, cityHeader, hostnameHeader}
case providers.Perfectprivacy:
return []string{cityHeader, tcpHeader, udpHeader}
case providers.Privado:
return []string{countryHeader, regionHeader, cityHeader, hostnameHeader}
case providers.PrivateInternetAccess:
return []string{regionHeader, hostnameHeader, tcpHeader, udpHeader, portForwardHeader}
case providers.Privatevpn:
return []string{countryHeader, cityHeader, hostnameHeader}
case providers.Protonvpn:
return []string{countryHeader, regionHeader, cityHeader, hostnameHeader, freeHeader}
case providers.Purevpn:
return []string{countryHeader, regionHeader, cityHeader, hostnameHeader, tcpHeader, udpHeader}
case providers.Surfshark:
return []string{regionHeader, countryHeader, cityHeader, hostnameHeader, multiHopHeader, tcpHeader, udpHeader}
case providers.Torguard:
return []string{countryHeader, cityHeader, hostnameHeader, tcpHeader, udpHeader}
case providers.VPNUnlimited:
return []string{countryHeader, cityHeader, hostnameHeader, freeHeader, streamHeader, tcpHeader, udpHeader}
case providers.Vyprvpn:
return []string{regionHeader, hostnameHeader, tcpHeader, udpHeader}
case providers.Wevpn:
return []string{cityHeader, hostnameHeader, tcpHeader, udpHeader}
case providers.Windscribe:
return []string{regionHeader, cityHeader, hostnameHeader, vpnHeader}
default:
return nil
} }
return markdown
}
func (s *ExpressvpnServer) ToMarkdown() (markdown string) {
return fmt.Sprintf("| %s | %s | `%s` | %s | %s |",
s.Country, s.City, s.Hostname,
boolToMarkdown(s.TCP), boolToMarkdown(s.UDP))
}
func (s *FastestvpnServers) ToMarkdown() (markdown string) {
markdown = markdownTableHeading("Country", "Hostname", "TCP", "UDP")
for _, server := range s.Servers {
markdown += server.ToMarkdown() + "\n"
}
return markdown
}
func (s *FastestvpnServer) ToMarkdown() (markdown string) {
return fmt.Sprintf("| %s | `%s` | %s | %s |",
s.Country, s.Hostname, boolToMarkdown(s.TCP), boolToMarkdown(s.UDP))
}
func (s *HideMyAssServers) ToMarkdown() (markdown string) {
markdown = markdownTableHeading("Country", "Region", "City", "Hostname", "TCP", "UDP")
for _, server := range s.Servers {
markdown += server.ToMarkdown() + "\n"
}
return markdown
}
func (s *HideMyAssServer) ToMarkdown() (markdown string) {
return fmt.Sprintf("| %s | %s | %s | `%s` | %s | %s |",
s.Country, s.Region, s.City, s.Hostname,
boolToMarkdown(s.TCP), boolToMarkdown(s.UDP))
}
func (s *IpvanishServers) ToMarkdown() (markdown string) {
markdown = markdownTableHeading("Country", "City", "Hostname", "TCP", "UDP")
for _, server := range s.Servers {
markdown += server.ToMarkdown() + "\n"
}
return markdown
}
func (s *IpvanishServer) ToMarkdown() (markdown string) {
return fmt.Sprintf("| %s | %s | `%s` | %s | %s |",
s.Country, s.City, s.Hostname,
boolToMarkdown(s.TCP), boolToMarkdown(s.UDP))
}
func (s *IvpnServers) ToMarkdown() (markdown string) {
markdown = markdownTableHeading("Country", "City", "ISP", "Hostname", "VPN", "TCP", "UDP")
for _, server := range s.Servers {
markdown += server.ToMarkdown() + "\n"
}
return markdown
}
func (s *IvpnServer) ToMarkdown() (markdown string) {
return fmt.Sprintf("| %s | %s | %s | `%s` | %s | %s | %s |",
s.Country, s.City, s.ISP, s.Hostname, s.VPN,
boolToMarkdown(s.TCP), boolToMarkdown(s.UDP))
}
func (s *MullvadServers) ToMarkdown() (markdown string) {
markdown = markdownTableHeading("Country", "City", "ISP", "Owned",
"Hostname", "VPN")
for _, server := range s.Servers {
markdown += server.ToMarkdown() + "\n"
}
return markdown
}
func (s *MullvadServer) ToMarkdown() (markdown string) {
return fmt.Sprintf("| %s | %s | %s | %s | `%s` | %s |",
s.Country, s.City, s.ISP, boolToMarkdown(s.Owned),
s.Hostname, s.VPN)
}
func (s *NordvpnServers) ToMarkdown() (markdown string) {
markdown = markdownTableHeading("Region", "Hostname", "TCP", "UDP")
for _, server := range s.Servers {
markdown += server.ToMarkdown() + "\n"
}
return markdown
}
func (s *NordvpnServer) ToMarkdown() (markdown string) {
return fmt.Sprintf("| %s | `%s` | %s | %s |",
s.Region, s.Hostname,
boolToMarkdown(s.TCP), boolToMarkdown(s.UDP))
}
func (s *PrivadoServers) ToMarkdown() (markdown string) {
markdown = markdownTableHeading("Country", "Region", "City", "Hostname")
for _, server := range s.Servers {
markdown += server.ToMarkdown() + "\n"
}
return markdown
}
func (s *PrivadoServer) ToMarkdown() (markdown string) {
return fmt.Sprintf("| %s | %s | %s | `%s` |",
s.Country, s.Region, s.City, s.Hostname)
}
func (s *PerfectprivacyServers) ToMarkdown() (markdown string) {
markdown = markdownTableHeading("City", "TCP", "UDP")
for _, server := range s.Servers {
markdown += server.ToMarkdown() + "\n"
}
return markdown
}
func (s *PerfectprivacyServer) ToMarkdown() (markdown string) {
return fmt.Sprintf("| %s | %s | %s |",
s.City, boolToMarkdown(s.TCP), boolToMarkdown(s.UDP))
}
func (s *PiaServers) ToMarkdown() (markdown string) {
markdown = markdownTableHeading("Region", "Hostname", "TCP", "UDP")
for _, server := range s.Servers {
markdown += server.ToMarkdown() + "\n"
}
return markdown
}
func (s *PIAServer) ToMarkdown() (markdown string) {
return fmt.Sprintf("| %s | `%s` | %s | %s |",
s.Region, s.Hostname,
boolToMarkdown(s.TCP), boolToMarkdown(s.UDP))
}
func (s *PrivatevpnServers) ToMarkdown() (markdown string) {
markdown = markdownTableHeading("Country", "City", "Hostname")
for _, server := range s.Servers {
markdown += server.ToMarkdown() + "\n"
}
return markdown
}
func (s *PrivatevpnServer) ToMarkdown() (markdown string) {
return fmt.Sprintf("| %s | %s | `%s` |",
s.Country, s.City, s.Hostname)
}
func (s *ProtonvpnServers) ToMarkdown() (markdown string) {
markdown = markdownTableHeading("Country", "Region", "City", "Hostname", "Free tier")
for _, server := range s.Servers {
markdown += server.ToMarkdown() + "\n"
}
return markdown
}
func (s *ProtonvpnServer) ToMarkdown() (markdown string) {
isFree := strings.Contains(strings.ToLower(s.Name), "free")
return fmt.Sprintf("| %s | %s | %s | `%s` | %s |",
s.Country, s.Region, s.City, s.Hostname, boolToMarkdown(isFree))
}
func (s *PurevpnServers) ToMarkdown() (markdown string) {
markdown = markdownTableHeading("Country", "Region", "City", "Hostname", "TCP", "UDP")
for _, server := range s.Servers {
markdown += server.ToMarkdown() + "\n"
}
return markdown
}
func (s *PurevpnServer) ToMarkdown() (markdown string) {
return fmt.Sprintf("| %s | %s | %s | `%s` | %s | %s |",
s.Country, s.Region, s.City, s.Hostname,
boolToMarkdown(s.TCP), boolToMarkdown(s.UDP))
}
func (s *SurfsharkServers) ToMarkdown() (markdown string) {
markdown = markdownTableHeading("Region", "Country", "City", "Hostname", "Multi-hop", "TCP", "UDP")
for _, server := range s.Servers {
markdown += server.ToMarkdown() + "\n"
}
return markdown
}
func (s *SurfsharkServer) ToMarkdown() (markdown string) {
return fmt.Sprintf("| %s | %s | %s | `%s` | %s | %s | %s |",
s.Region, s.Country, s.City, s.Hostname, boolToMarkdown(s.MultiHop),
boolToMarkdown(s.TCP), boolToMarkdown(s.UDP))
}
func (s *TorguardServers) ToMarkdown() (markdown string) {
markdown = markdownTableHeading("Country", "City", "Hostname", "TCP", "UDP")
for _, server := range s.Servers {
markdown += server.ToMarkdown() + "\n"
}
return markdown
}
func (s *TorguardServer) ToMarkdown() (markdown string) {
return fmt.Sprintf("| %s | %s | `%s` | %s | %s |",
s.Country, s.City, s.Hostname,
boolToMarkdown(s.TCP), boolToMarkdown(s.UDP))
}
func (s *VPNUnlimitedServers) ToMarkdown() (markdown string) {
markdown = markdownTableHeading("Country", "City", "Hostname", "Free tier", "Streaming", "TCP", "UDP")
for _, server := range s.Servers {
markdown += server.ToMarkdown() + "\n"
}
return markdown
}
func (s *VPNUnlimitedServer) ToMarkdown() (markdown string) {
return fmt.Sprintf("| %s | %s | `%s` | %s | %s | %s | %s |",
s.Country, s.City, s.Hostname,
boolToMarkdown(s.Free), boolToMarkdown(s.Stream),
boolToMarkdown(s.TCP), boolToMarkdown(s.UDP))
}
func (s *VyprvpnServers) ToMarkdown() (markdown string) {
markdown = markdownTableHeading("Region", "Hostname", "TCP", "UDP")
for _, server := range s.Servers {
markdown += server.ToMarkdown() + "\n"
}
return markdown
}
func (s *VyprvpnServer) ToMarkdown() (markdown string) {
return fmt.Sprintf("| %s | `%s` | %s | %s |",
s.Region, s.Hostname,
boolToMarkdown(s.TCP), boolToMarkdown(s.UDP))
}
func (s *WevpnServers) ToMarkdown() (markdown string) {
markdown = markdownTableHeading("City", "Hostname", "TCP", "UDP")
for _, server := range s.Servers {
markdown += server.ToMarkdown() + "\n"
}
return markdown
}
func (s *WevpnServer) ToMarkdown() (markdown string) {
return fmt.Sprintf("| %s | `%s` | %s | %s |",
s.City, s.Hostname, boolToMarkdown(s.TCP), boolToMarkdown(s.UDP))
}
func (s *WindscribeServers) ToMarkdown() (markdown string) {
markdown = markdownTableHeading("Region", "City", "Hostname", "VPN")
for _, server := range s.Servers {
markdown += server.ToMarkdown() + "\n"
}
return markdown
}
func (s *WindscribeServer) ToMarkdown() (markdown string) {
return fmt.Sprintf("| %s | %s | `%s` | %s |",
s.Region, s.City, s.Hostname, s.VPN)
} }

View File

@@ -3,43 +3,54 @@ package models
import ( import (
"testing" "testing"
"github.com/qdm12/gluetun/internal/constants/providers"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
) )
func Test_CyberghostServers_ToMarkdown(t *testing.T) { func Test_Servers_ToMarkdown(t *testing.T) {
t.Parallel() t.Parallel()
servers := CyberghostServers{ testCases := map[string]struct {
Servers: []CyberghostServer{ provider string
servers Servers
expectedMarkdown string
}{
providers.Cyberghost: {
provider: providers.Cyberghost,
servers: Servers{
Servers: []Server{
{Country: "a", UDP: true, Hostname: "xa"}, {Country: "a", UDP: true, Hostname: "xa"},
{Country: "b", TCP: true, Hostname: "xb"}, {Country: "b", TCP: true, Hostname: "xb"},
}, },
} },
expectedMarkdown: "| Country | Hostname | TCP | UDP |\n" +
markdown := servers.ToMarkdown()
const expected = "| Country | Hostname | TCP | UDP |\n" +
"| --- | --- | --- | --- |\n" + "| --- | --- | --- | --- |\n" +
"| a | `xa` | ❌ | ✅ |\n" + "| a | `xa` | ❌ | ✅ |\n" +
"| b | `xb` | ✅ | ❌ |\n" "| b | `xb` | ✅ | ❌ |\n",
},
assert.Equal(t, expected, markdown) providers.Fastestvpn: {
} provider: providers.Fastestvpn,
servers: Servers{
func Test_FastestvpnServers_ToMarkdown(t *testing.T) { Servers: []Server{
t.Parallel()
servers := FastestvpnServers{
Servers: []FastestvpnServer{
{Country: "a", Hostname: "xa", TCP: true}, {Country: "a", Hostname: "xa", TCP: true},
{Country: "b", Hostname: "xb", UDP: true}, {Country: "b", Hostname: "xb", UDP: true},
}, },
} },
expectedMarkdown: "| Country | Hostname | TCP | UDP |\n" +
markdown := servers.ToMarkdown()
const expected = "| Country | Hostname | TCP | UDP |\n" +
"| --- | --- | --- | --- |\n" + "| --- | --- | --- | --- |\n" +
"| a | `xa` | ✅ | ❌ |\n" + "| a | `xa` | ✅ | ❌ |\n" +
"| b | `xb` | ❌ | ✅ |\n" "| b | `xb` | ❌ | ✅ |\n",
},
}
assert.Equal(t, expected, markdown) for name, testCase := range testCases {
testCase := testCase
t.Run(name, func(t *testing.T) {
t.Parallel()
markdown := testCase.servers.ToMarkdown(testCase.provider)
assert.Equal(t, testCase.expectedMarkdown, markdown)
})
}
} }

View File

@@ -4,189 +4,25 @@ import (
"net" "net"
) )
type CyberghostServer struct { type Server struct {
Country string `json:"country"` VPN string `json:"vpn,omitempty"`
Hostname string `json:"hostname"` // Surfshark: country is also used for multi-hop
TCP bool `json:"tcp"` Country string `json:"country,omitempty"`
UDP bool `json:"udp"` Region string `json:"region,omitempty"`
IPs []net.IP `json:"ips"`
}
type ExpressvpnServer struct {
Country string `json:"country"`
City string `json:"city,omitempty"` City string `json:"city,omitempty"`
Hostname string `json:"hostname"` ISP string `json:"isp,omitempty"`
TCP bool `json:"tcp"` Owned bool `json:"owned,omitempty"`
UDP bool `json:"udp"` Number uint16 `json:"number,omitempty"`
IPs []net.IP `json:"ips"` ServerName string `json:"server_name,omitempty"`
} Hostname string `json:"hostname,omitempty"`
TCP bool `json:"tcp,omitempty"`
type FastestvpnServer struct { UDP bool `json:"udp,omitempty"`
Hostname string `json:"hostname"`
TCP bool `json:"tcp"`
UDP bool `json:"udp"`
Country string `json:"country"`
IPs []net.IP `json:"ips"`
}
type HideMyAssServer struct {
Country string `json:"country"`
Region string `json:"region"`
City string `json:"city"`
Hostname string `json:"hostname"`
TCP bool `json:"tcp"`
UDP bool `json:"udp"`
IPs []net.IP `json:"ips"`
}
type IpvanishServer struct {
Country string `json:"country"`
City string `json:"city"`
Hostname string `json:"hostname"`
TCP bool `json:"tcp"`
UDP bool `json:"udp"`
IPs []net.IP `json:"ips"`
}
type IvpnServer struct {
VPN string `json:"vpn"`
Country string `json:"country"`
City string `json:"city"`
ISP string `json:"isp"`
Hostname string `json:"hostname"`
WgPubKey string `json:"wgpubkey,omitempty"`
TCP bool `json:"tcp"`
UDP bool `json:"udp"`
IPs []net.IP `json:"ips"`
}
type MullvadServer struct {
VPN string `json:"vpn"`
IPs []net.IP `json:"ips"`
IPsV6 []net.IP `json:"ipsv6"`
Country string `json:"country"`
City string `json:"city"`
Hostname string `json:"hostname"`
ISP string `json:"isp"`
Owned bool `json:"owned"`
WgPubKey string `json:"wgpubkey,omitempty"`
}
type NordvpnServer struct { //nolint:maligned
Region string `json:"region"`
Hostname string `json:"hostname"`
Number uint16 `json:"number"`
IP net.IP `json:"ip"`
TCP bool `json:"tcp"`
UDP bool `json:"udp"`
}
type PerfectprivacyServer struct {
City string `json:"city"` // primary key
IPs []net.IP `json:"ips"`
TCP bool `json:"tcp"`
UDP bool `json:"udp"`
}
type PrivadoServer struct {
Country string `json:"country"`
Region string `json:"region"`
City string `json:"city"`
Hostname string `json:"hostname"`
IP net.IP `json:"ip"`
}
type PIAServer struct {
Region string `json:"region"`
Hostname string `json:"hostname"`
ServerName string `json:"server_name"`
TCP bool `json:"tcp"`
UDP bool `json:"udp"`
PortForward bool `json:"port_forward"`
IPs []net.IP `json:"ips"`
}
type PrivatevpnServer struct {
Country string `json:"country"`
City string `json:"city"`
Hostname string `json:"hostname"`
IPs []net.IP `json:"ip"`
}
type ProtonvpnServer struct {
Country string `json:"country"`
Region string `json:"region"`
City string `json:"city"`
Name string `json:"name"`
Hostname string `json:"hostname"`
EntryIP net.IP `json:"entry_ip"`
ExitIPs []net.IP `json:"exit_ip"` // TODO verify it matches with public IP once connected
}
type PurevpnServer struct {
Country string `json:"country"`
Region string `json:"region"`
City string `json:"city"`
Hostname string `json:"hostname"`
TCP bool `json:"tcp"`
UDP bool `json:"udp"`
IPs []net.IP `json:"ips"`
}
type SurfsharkServer struct {
Region string `json:"region"`
Country string `json:"country"` // Country is also used for multi-hop
City string `json:"city"`
RetroLoc string `json:"retroloc"` // TODO remove in v4
Hostname string `json:"hostname"`
MultiHop bool `json:"multihop"`
TCP bool `json:"tcp"`
UDP bool `json:"udp"`
IPs []net.IP `json:"ips"`
}
type TorguardServer struct {
Country string `json:"country"`
City string `json:"city"`
Hostname string `json:"hostname"`
TCP bool `json:"tcp"`
UDP bool `json:"udp"`
IPs []net.IP `json:"ips"`
}
type VPNUnlimitedServer struct {
Country string `json:"country"`
City string `json:"city"`
Hostname string `json:"hostname"`
Free bool `json:"free"`
Stream bool `json:"stream"`
TCP bool `json:"tcp"`
UDP bool `json:"udp"`
IPs []net.IP `json:"ips"`
}
type VyprvpnServer struct {
Region string `json:"region"`
Hostname string `json:"hostname"`
TCP bool `json:"tcp"`
UDP bool `json:"udp"` // only support for UDP
IPs []net.IP `json:"ips"`
}
type WevpnServer struct {
City string `json:"city"`
Hostname string `json:"hostname"`
TCP bool `json:"tcp"`
UDP bool `json:"udp"`
IPs []net.IP `json:"ips"`
}
type WindscribeServer struct {
VPN string `json:"vpn"`
Region string `json:"region"`
City string `json:"city"`
Hostname string `json:"hostname"`
OvpnX509 string `json:"x509,omitempty"` OvpnX509 string `json:"x509,omitempty"`
RetroLoc string `json:"retroloc,omitempty"` // TODO remove in v4
MultiHop bool `json:"multihop,omitempty"`
WgPubKey string `json:"wgpubkey,omitempty"` WgPubKey string `json:"wgpubkey,omitempty"`
IPs []net.IP `json:"ips"` Free bool `json:"free,omitempty"`
Stream bool `json:"stream,omitempty"`
PortForward bool `json:"port_forward,omitempty"`
IPs []net.IP `json:"ips,omitempty"`
} }

View File

@@ -2,26 +2,26 @@ package models
type AllServers struct { type AllServers struct {
Version uint16 `json:"version"` // used for migration of the top level scheme Version uint16 `json:"version"` // used for migration of the top level scheme
Cyberghost CyberghostServers `json:"cyberghost"` Cyberghost Servers `json:"cyberghost"`
Expressvpn ExpressvpnServers `json:"expressvpn"` Expressvpn Servers `json:"expressvpn"`
Fastestvpn FastestvpnServers `json:"fastestvpn"` Fastestvpn Servers `json:"fastestvpn"`
HideMyAss HideMyAssServers `json:"hidemyass"` HideMyAss Servers `json:"hidemyass"`
Ipvanish IpvanishServers `json:"ipvanish"` Ipvanish Servers `json:"ipvanish"`
Ivpn IvpnServers `json:"ivpn"` Ivpn Servers `json:"ivpn"`
Mullvad MullvadServers `json:"mullvad"` Mullvad Servers `json:"mullvad"`
Perfectprivacy PerfectprivacyServers `json:"perfectprivacy"` Perfectprivacy Servers `json:"perfectprivacy"`
Nordvpn NordvpnServers `json:"nordvpn"` Nordvpn Servers `json:"nordvpn"`
Privado PrivadoServers `json:"privado"` Privado Servers `json:"privado"`
Pia PiaServers `json:"pia"` Pia Servers `json:"pia"`
Privatevpn PrivatevpnServers `json:"privatevpn"` Privatevpn Servers `json:"privatevpn"`
Protonvpn ProtonvpnServers `json:"protonvpn"` Protonvpn Servers `json:"protonvpn"`
Purevpn PurevpnServers `json:"purevpn"` Purevpn Servers `json:"purevpn"`
Surfshark SurfsharkServers `json:"surfshark"` Surfshark Servers `json:"surfshark"`
Torguard TorguardServers `json:"torguard"` Torguard Servers `json:"torguard"`
VPNUnlimited VPNUnlimitedServers `json:"vpnunlimited"` VPNUnlimited Servers `json:"vpnunlimited"`
Vyprvpn VyprvpnServers `json:"vyprvpn"` Vyprvpn Servers `json:"vyprvpn"`
Wevpn WevpnServers `json:"wevpn"` Wevpn Servers `json:"wevpn"`
Windscribe WindscribeServers `json:"windscribe"` Windscribe Servers `json:"windscribe"`
} }
func (a *AllServers) Count() int { func (a *AllServers) Count() int {
@@ -47,103 +47,8 @@ func (a *AllServers) Count() int {
len(a.Windscribe.Servers) len(a.Windscribe.Servers)
} }
type CyberghostServers struct { type Servers struct {
Version uint16 `json:"version"` Version uint16 `json:"version"`
Timestamp int64 `json:"timestamp"` Timestamp int64 `json:"timestamp"`
Servers []CyberghostServer `json:"servers"` Servers []Server `json:"servers"`
}
type ExpressvpnServers struct {
Version uint16 `json:"version"`
Timestamp int64 `json:"timestamp"`
Servers []ExpressvpnServer `json:"servers"`
}
type FastestvpnServers struct {
Version uint16 `json:"version"`
Timestamp int64 `json:"timestamp"`
Servers []FastestvpnServer `json:"servers"`
}
type HideMyAssServers struct {
Version uint16 `json:"version"`
Timestamp int64 `json:"timestamp"`
Servers []HideMyAssServer `json:"servers"`
}
type IpvanishServers struct {
Version uint16 `json:"version"`
Timestamp int64 `json:"timestamp"`
Servers []IpvanishServer `json:"servers"`
}
type IvpnServers struct {
Version uint16 `json:"version"`
Timestamp int64 `json:"timestamp"`
Servers []IvpnServer `json:"servers"`
}
type MullvadServers struct {
Version uint16 `json:"version"`
Timestamp int64 `json:"timestamp"`
Servers []MullvadServer `json:"servers"`
}
type NordvpnServers struct {
Version uint16 `json:"version"`
Timestamp int64 `json:"timestamp"`
Servers []NordvpnServer `json:"servers"`
}
type PerfectprivacyServers struct {
Version uint16 `json:"version"`
Timestamp int64 `json:"timestamp"`
Servers []PerfectprivacyServer `json:"servers"`
}
type PrivadoServers struct {
Version uint16 `json:"version"`
Timestamp int64 `json:"timestamp"`
Servers []PrivadoServer `json:"servers"`
}
type PiaServers struct {
Version uint16 `json:"version"`
Timestamp int64 `json:"timestamp"`
Servers []PIAServer `json:"servers"`
}
type PrivatevpnServers struct {
Version uint16 `json:"version"`
Timestamp int64 `json:"timestamp"`
Servers []PrivatevpnServer `json:"servers"`
}
type ProtonvpnServers struct {
Version uint16 `json:"version"`
Timestamp int64 `json:"timestamp"`
Servers []ProtonvpnServer `json:"servers"`
}
type PurevpnServers struct {
Version uint16 `json:"version"`
Timestamp int64 `json:"timestamp"`
Servers []PurevpnServer `json:"servers"`
}
type SurfsharkServers struct {
Version uint16 `json:"version"`
Timestamp int64 `json:"timestamp"`
Servers []SurfsharkServer `json:"servers"`
}
type TorguardServers struct {
Version uint16 `json:"version"`
Timestamp int64 `json:"timestamp"`
Servers []TorguardServer `json:"servers"`
}
type VPNUnlimitedServers struct {
Version uint16 `json:"version"`
Timestamp int64 `json:"timestamp"`
Servers []VPNUnlimitedServer `json:"servers"`
}
type VyprvpnServers struct {
Version uint16 `json:"version"`
Timestamp int64 `json:"timestamp"`
Servers []VyprvpnServer `json:"servers"`
}
type WevpnServers struct {
Version uint16 `json:"version"`
Timestamp int64 `json:"timestamp"`
Servers []WevpnServer `json:"servers"`
}
type WindscribeServers struct {
Version uint16 `json:"version"`
Timestamp int64 `json:"timestamp"`
Servers []WindscribeServer `json:"servers"`
} }

Some files were not shown because too many files have changed in this diff Show More