Compare commits

..

1 Commits

Author SHA1 Message Date
Quentin McGaw
e1c20595b1 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-21 20:38:55 +00:00
707 changed files with 83920 additions and 131641 deletions

View File

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

View File

@@ -8,7 +8,7 @@
"vscode" "vscode"
], ],
"shutdownAction": "stopCompose", "shutdownAction": "stopCompose",
"postCreateCommand": "source ~/.windows.sh && go mod download && go mod tidy", "postCreateCommand": "~/.windows.sh && go mod download && go mod tidy",
"workspaceFolder": "/workspace", "workspaceFolder": "/workspace",
"extensions": [ "extensions": [
"golang.go", "golang.go",
@@ -25,7 +25,6 @@
"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,6 +3,7 @@ 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:
@@ -10,14 +11,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 - ~/.docker:/root/.docker:z
# SSH directory for Linux, OSX and WSL # SSH directory for Linux, OSX and WSL
# On Linux and OSX, a symlink /mnt/ssh <-> ~/.ssh is - ~/.ssh:/root/.ssh:z
# created in the container. On Windows, files are copied # For Windows without WSL, a copy will be made
# from /mnt/ssh to ~/.ssh to fix permissions. # from /tmp/.ssh to ~/.ssh to fix permissions
- ~/.ssh:/mnt/ssh #- ~/.ssh:/tmp/.ssh:ro
# Shell history persistence # Shell history persistence
- ~/.zsh_history:/root/.zsh_history - ~/.zsh_history:/root/.zsh_history:z
# Git config
- ~/.gitconfig:/root/.gitconfig:z
environment: environment:
- TZ= - TZ=
cap_add: cap_add:

View File

@@ -40,7 +40,6 @@ body:
attributes: attributes:
label: VPN service provider label: VPN service provider
options: options:
- AirVPN
- Custom - Custom
- Cyberghost - Cyberghost
- ExpressVPN - ExpressVPN
@@ -55,10 +54,8 @@ body:
- PrivateVPN - PrivateVPN
- ProtonVPN - ProtonVPN
- PureVPN - PureVPN
- SlickVPN
- Surfshark - Surfshark
- TorGuard - TorGuard
- VPNSecure.me
- VPNUnlimited - VPNUnlimited
- VyprVPN - VyprVPN
- WeVPN - WeVPN
@@ -99,7 +96,7 @@ body:
attributes: attributes:
label: Share your logs label: Share your logs
description: No sensitive information is logged out except when running with `LOG_LEVEL=debug`. description: No sensitive information is logged out except when running with `LOG_LEVEL=debug`.
render: plain text render: log
validations: validations:
required: true required: true
- type: textarea - type: textarea

39
.github/labels.yml vendored
View File

@@ -1,13 +1,18 @@
# Temporary status - name: "Bug :bug:"
- name: "🗯️ Waiting for feedback" color: "b60205"
color: "aadefa" description: ""
- name: "Feature request :bulb:"
color: "0e8a16"
description: ""
- name: "Help wanted :pray:"
color: "4caf50"
description: ""
- name: "Documentation :memo:"
color: "c5def5"
description: ""
- name: "Needs more info :thinking:"
color: "795548"
description: "" description: ""
- name: "🔴 Blocked"
color: "ff3f14"
description: "Blocked by another issue or pull request"
- name: "🔒 After next release"
color: "e8f274"
description: "Will be done after the next release"
# Priority # Priority
- name: "🚨 Urgent" - name: "🚨 Urgent"
@@ -17,18 +22,7 @@
color: "4285f4" color: "4285f4"
description: "" description: ""
# Complexity
- name: "☣️ Hard to do"
color: "7d0008"
description: ""
- name: "🟩 Easy to do"
color: "34cf43"
description: ""
# VPN providers # VPN providers
- name: ":cloud: AirVPN"
color: "cfe8d4"
description: ""
- name: ":cloud: Cyberghost" - name: ":cloud: Cyberghost"
color: "cfe8d4" color: "cfe8d4"
description: "" description: ""
@@ -70,17 +64,12 @@
- name: ":cloud: PureVPN" - name: ":cloud: PureVPN"
color: "cfe8d4" color: "cfe8d4"
description: "" description: ""
- name: ":cloud: SlickVPN"
color: "cfe8d4"
description: ""
- name: ":cloud: Surfshark" - name: ":cloud: Surfshark"
color: "cfe8d4" color: "cfe8d4"
description: "" description: ""
- name: ":cloud: Torguard" - name: ":cloud: Torguard"
color: "cfe8d4" color: "cfe8d4"
description: "" description: ""
- name: ":cloud: VPNSecure.me"
color: "cfe8d4"
- name: ":cloud: VPNUnlimited" - name: ":cloud: VPNUnlimited"
color: "cfe8d4" color: "cfe8d4"
description: "" description: ""

View File

@@ -1,37 +0,0 @@
name: No trigger file paths
on:
push:
branches:
- master
paths-ignore:
- .github/workflows/ci.yml
- cmd/**
- internal/**
- pkg/**
- .dockerignore
- .golangci.yml
- Dockerfile
- go.mod
- go.sum
pull_request:
branches:
- master
paths-ignore:
- .github/workflows/ci.yml
- cmd/**
- internal/**
- pkg/**
- .dockerignore
- .golangci.yml
- Dockerfile
- go.mod
- go.sum
jobs:
verify:
runs-on: ubuntu-latest
permissions:
actions: read
steps:
- name: No trigger path triggered for required verify workflow.
run: exit 0

View File

@@ -32,27 +32,28 @@ on:
jobs: jobs:
verify: verify:
# Only run if it's a push event or if it's a PR from this repository, and it is not dependabot.
if: |
github.actor != 'dependabot[bot]' &&
(github.event_name == 'push' ||
github.event_name == 'release' ||
(github.event_name == 'pull_request' && github.event.pull_request.head.repo.full_name == github.repository))
runs-on: ubuntu-latest runs-on: ubuntu-latest
permissions:
actions: read
contents: read
env: env:
DOCKER_BUILDKIT: "1" DOCKER_BUILDKIT: "1"
steps: steps:
- uses: actions/checkout@v3 - uses: actions/checkout@v2.4.0
- uses: reviewdog/action-misspell@v1 - uses: reviewdog/action-misspell@v1
with: with:
locale: "US" locale: "US"
level: error level: error
exclude: |
./internal/storage/servers.json
- name: Linting - name: Linting
run: docker build --target lint . run: docker build --target lint .
- name: Mocks check - name: Go mod tidy check
run: docker build --target mocks . run: docker build --target tidy .
- name: Build test image - name: Build test image
run: docker build --target test -t test-container . run: docker build --target test -t test-container .
@@ -64,79 +65,65 @@ jobs:
-v "$(pwd)/coverage.txt:/tmp/gobuild/coverage.txt" \ -v "$(pwd)/coverage.txt:/tmp/gobuild/coverage.txt" \
test-container test-container
- name: Code security analysis
uses: snyk/actions/golang@master
env:
SNYK_TOKEN: ${{ secrets.SNYK_TOKEN }}
- name: Build final image - name: Build final image
run: docker build -t final-image . run: docker build -t final-image .
codeql: # - name: Image security analysis
runs-on: ubuntu-latest # uses: snyk/actions/docker@master
permissions: # env:
actions: read # SNYK_TOKEN: ${{ secrets.SNYK_TOKEN }}
contents: read # with:
security-events: write # image: final-image
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
publish: publish:
# Only run if it's a push event or if it's a PR from this repository
if: | if: |
github.repository == 'qdm12/gluetun' && github.event_name == 'push' ||
( github.event_name == 'release' ||
github.event_name == 'push' || (github.event_name == 'pull_request' && github.event.pull_request.head.repo.full_name == github.repository)
github.event_name == 'release' || needs: [verify]
(github.event_name == 'pull_request' && github.event.pull_request.head.repo.full_name == github.repository && github.actor != 'dependabot[bot]')
)
needs: [verify, codeql]
permissions:
actions: read
contents: read
packages: write
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- uses: actions/checkout@v3 - uses: actions/checkout@v2.4.0
# extract metadata (tags, labels) for Docker # extract metadata (tags, labels) for Docker
# https://github.com/docker/metadata-action # https://github.com/docker/metadata-action
- name: Extract Docker metadata - name: Extract Docker metadata
id: meta id: meta
uses: docker/metadata-action@v4 uses: docker/metadata-action@v3
with: with:
flavor: | flavor: |
latest=${{ github.ref == format('refs/heads/{0}', github.event.repository.default_branch) }} latest=${{ github.ref == format('refs/heads/{0}', github.event.repository.default_branch) }}
images: | images: |
ghcr.io/qdm12/gluetun
qmcgaw/gluetun qmcgaw/gluetun
qmcgaw/private-internet-access qmcgaw/private-internet-access
tags: | tags: |
type=ref,event=branch,enable=${{ github.ref != format('refs/heads/{0}', github.event.repository.default_branch) }}
type=ref,event=pr type=ref,event=pr
type=semver,pattern=v{{major}}.{{minor}}.{{patch}} type=semver,pattern=v{{major}}.{{minor}}.{{patch}}
type=semver,pattern=v{{major}}.{{minor}} type=semver,pattern=v{{major}}.{{minor}}
type=semver,pattern=v{{major}},enable=${{ !startsWith(github.ref, 'refs/tags/v0.') }} type=semver,pattern=v{{major}},enable=${{ !startsWith(github.ref, 'refs/tags/v0.') }}
type=raw,value=latest,enable=${{ github.ref == format('refs/heads/{0}', github.event.repository.default_branch) }} type=raw,value=latest,enable=${{ github.ref == format('refs/heads/{0}', github.event.repository.default_branch) }}
- uses: docker/setup-qemu-action@v2 - uses: docker/setup-qemu-action@v1
- uses: docker/setup-buildx-action@v2 - uses: docker/setup-buildx-action@v1
- uses: docker/login-action@v2 - uses: docker/login-action@v1
with: with:
username: qmcgaw username: qmcgaw
password: ${{ secrets.DOCKERHUB_PASSWORD }} password: ${{ secrets.DOCKERHUB_PASSWORD }}
- uses: docker/login-action@v2
with:
registry: ghcr.io
username: qdm12
password: ${{ github.token }}
- name: Short commit - name: Short commit
id: shortcommit id: shortcommit
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@v4.0.0 uses: docker/build-push-action@v2.8.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 }}

37
.github/workflows/dependabot.yml vendored Normal file
View File

@@ -0,0 +1,37 @@
name: Dependabot
on:
pull_request:
branches:
- master
paths:
- .github/workflows/dependabot.yml
- cmd/**
- internal/**
- pkg/**
- .dockerignore
- .golangci.yml
- Dockerfile
- go.mod
- go.sum
jobs:
verify:
if: ${{ github.actor == 'dependabot[bot]' }}
runs-on: ubuntu-latest
env:
DOCKER_BUILDKIT: "1"
steps:
- uses: actions/checkout@v2.4.0
- name: Build test image
run: docker build --target test -t test-container .
- name: Run tests in test container
run: |
touch coverage.txt
docker run --rm \
-v "$(pwd)/coverage.txt:/tmp/gobuild/coverage.txt" \
test-container
- name: Build final image
run: docker build -t final-image .

View File

@@ -1,22 +1,18 @@
name: Docker Hub description name: Docker Hub description
on: on:
push: push:
branches: branches: [master]
- master
paths: paths:
- README.md - README.md
- .github/workflows/dockerhub-description.yml - .github/workflows/dockerhub-description.yml
jobs: jobs:
docker-hub-description: dockerHubDescription:
if: github.repository == 'qdm12/gluetun'
runs-on: ubuntu-latest runs-on: ubuntu-latest
permissions:
actions: read
contents: read
steps: steps:
- uses: actions/checkout@v3 - name: Checkout
uses: actions/checkout@v2.4.0
- uses: peter-evans/dockerhub-description@v3 - name: Docker Hub Description
uses: peter-evans/dockerhub-description@v2
with: with:
username: qmcgaw username: qmcgaw
password: ${{ secrets.DOCKERHUB_PASSWORD }} password: ${{ secrets.DOCKERHUB_PASSWORD }}

40
.github/workflows/fork.yml vendored Normal file
View File

@@ -0,0 +1,40 @@
name: Fork
on:
pull_request:
branches:
- master
paths:
- .github/workflows/fork.yml
- cmd/**
- internal/**
- pkg/**
- .dockerignore
- .golangci.yml
- Dockerfile
- go.mod
- go.sum
jobs:
verify:
if: github.event.pull_request.head.repo.full_name != github.repository && github.actor != 'dependabot[bot]'
runs-on: ubuntu-latest
env:
DOCKER_BUILDKIT: "1"
steps:
- uses: actions/checkout@v2.4.0
- name: Linting
run: docker build --target lint .
- name: Build test image
run: docker build --target test -t test-container .
- name: Run tests in test container
run: |
touch coverage.txt
docker run --rm \
-v "$(pwd)/coverage.txt:/tmp/gobuild/coverage.txt" \
test-container
- name: Build final image
run: docker build -t final-image .

View File

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

View File

@@ -7,38 +7,46 @@ issues:
- path: _test\.go - path: _test\.go
linters: linters:
- dupl - dupl
- maligned
- goerr113 - goerr113
- containedctx - containedctx
- path: "internal\\/server\\/.+\\.go" - path: internal/server/
linters: linters:
- dupl - dupl
- path: "internal\\/configuration\\/settings\\/.+\\.go" - path: internal/configuration/
linters: linters:
- dupl - dupl
- text: "^mnd: Magic number: 0[0-9]{3}, in <argument> detected$" - path: internal/constants/
source: "^.+= os\\.OpenFile\\(.+, .+, 0[0-9]{3}\\)" linters:
- dupl
- text: "exported: exported var Err*"
linters:
- revive
- text: "mnd: Magic number: 0644*"
linters: linters:
- gomnd - gomnd
- text: "^mnd: Magic number: 0[0-9]{3}, in <argument> detected$" - text: "mnd: Magic number: 0400*"
source: "^.+= os\\.MkdirAll\\(.+, 0[0-9]{3}\\)"
linters: linters:
- gomnd - gomnd
- text: "variable 'mssFix' is only used in the if-statement*"
path: "openvpnconf.go"
linters:
- ifshort
- text: "variable 'auth' is only used in the if-statement*"
path: "openvpnconf.go"
linters:
- ifshort
- linters: - linters:
- lll - lll
source: "^//go:generate .+$" source: "^//go:generate "
- text: "returns interface \\(github\\.com\\/vishvananda\\/netlink\\.Link\\)"
linters:
- ireturn
- path: "internal\\/openvpn\\/pkcs8\\/descbc\\.go"
text: "newCipherDESCBCBlock returns interface \\(github\\.com\\/youmark\\/pkcs8\\.Cipher\\)"
linters:
- ireturn
linters: linters:
enable: enable:
# - cyclop # - cyclop
# - errorlint # - errorlint
- asasalint # - ireturn
# - varnamelen
# - wrapcheck
- asciicheck - asciicheck
- bidichk - bidichk
- bodyclose - bodyclose
@@ -49,7 +57,6 @@ linters:
- durationcheck - durationcheck
- errchkjson - errchkjson
- errname - errname
- execinquery
- exhaustive - exhaustive
- exportloopref - exportloopref
- forcetypeassert - forcetypeassert
@@ -69,9 +76,8 @@ linters:
- goprintffuncname - goprintffuncname
- gosec - gosec
- grouper - grouper
- ifshort
- importas - importas
- interfacebloat
- ireturn
- lll - lll
- maintidx - maintidx
- makezero - makezero
@@ -82,11 +88,10 @@ linters:
- nilnil - nilnil
- noctx - noctx
- nolintlint - nolintlint
- nosprintfhostport
- prealloc - prealloc
- predeclared - predeclared
- predeclared
- promlinter - promlinter
- reassign
- revive - revive
- rowserrcheck - rowserrcheck
- sqlclosecheck - sqlclosecheck
@@ -95,7 +100,6 @@ linters:
- tparallel - tparallel
- unconvert - unconvert
- unparam - unparam
- usestdlibvars
- wastedassign - wastedassign
- whitespace - whitespace

35
.vscode/launch.json vendored
View File

@@ -1,35 +0,0 @@
{
"version": "0.2.0",
"configurations": [
{
"name": "Update a VPN provider servers data",
"type": "go",
"request": "launch",
"cwd": "${workspaceFolder}",
"program": "cmd/gluetun/main.go",
"args": [
"update",
"${input:updateMode}",
"-providers",
"${input:provider}"
],
}
],
"inputs": [
{
"id": "provider",
"type": "promptString",
"description": "Please enter a provider (or comma separated list of providers)",
},
{
"id": "updateMode",
"type": "pickString",
"description": "Update mode to use",
"options": [
"-maintainer",
"-enduser"
],
"default": "-maintainer"
},
]
}

View File

@@ -1,22 +1,18 @@
ARG ALPINE_VERSION=3.17 ARG ALPINE_VERSION=3.15
ARG GO_ALPINE_VERSION=3.17 ARG GO_ALPINE_VERSION=3.15
ARG GO_VERSION=1.20 ARG GO_VERSION=1.17
ARG XCPUTRANSLATE_VERSION=v0.6.0 ARG XCPUTRANSLATE_VERSION=v0.6.0
ARG GOLANGCI_LINT_VERSION=v1.52.2 ARG GOLANGCI_LINT_VERSION=v1.44.2
ARG MOCKGEN_VERSION=v1.6.0
ARG BUILDPLATFORM=linux/amd64 ARG BUILDPLATFORM=linux/amd64
FROM --platform=${BUILDPLATFORM} qmcgaw/xcputranslate:${XCPUTRANSLATE_VERSION} AS xcputranslate FROM --platform=${BUILDPLATFORM} qmcgaw/xcputranslate:${XCPUTRANSLATE_VERSION} AS xcputranslate
FROM --platform=${BUILDPLATFORM} qmcgaw/binpot:golangci-lint-${GOLANGCI_LINT_VERSION} AS golangci-lint FROM --platform=${BUILDPLATFORM} qmcgaw/binpot:golangci-lint-${GOLANGCI_LINT_VERSION} AS golangci-lint
FROM --platform=${BUILDPLATFORM} qmcgaw/binpot:mockgen-${MOCKGEN_VERSION} AS mockgen
FROM --platform=${BUILDPLATFORM} golang:${GO_VERSION}-alpine${GO_ALPINE_VERSION} AS base FROM --platform=${BUILDPLATFORM} golang:${GO_VERSION}-alpine${GO_ALPINE_VERSION} AS base
COPY --from=xcputranslate /xcputranslate /usr/local/bin/xcputranslate COPY --from=xcputranslate /xcputranslate /usr/local/bin/xcputranslate
# Note: findutils needed to have xargs support `-d` flag for mocks stage. RUN apk --update add git g++
RUN apk --update add git g++ findutils
ENV CGO_ENABLED=0 ENV CGO_ENABLED=0
COPY --from=golangci-lint /bin /go/bin/golangci-lint COPY --from=golangci-lint /bin /go/bin/golangci-lint
COPY --from=mockgen /bin /go/bin/mockgen
WORKDIR /tmp/gobuild WORKDIR /tmp/gobuild
COPY go.mod go.sum ./ COPY go.mod go.sum ./
RUN go mod download RUN go mod download
@@ -34,17 +30,14 @@ FROM --platform=${BUILDPLATFORM} base AS lint
COPY .golangci.yml ./ COPY .golangci.yml ./
RUN golangci-lint run --timeout=10m RUN golangci-lint run --timeout=10m
FROM --platform=${BUILDPLATFORM} base AS mocks FROM --platform=${BUILDPLATFORM} base AS tidy
RUN git init && \ RUN git init && \
git config user.email ci@localhost && \ git config user.email ci@localhost && \
git config user.name ci && \ git config user.name ci && \
git config core.fileMode false && \ git add -A && git commit -m ci && \
git add -A && \ sed -i '/\/\/ indirect/d' go.mod && \
git commit -m "snapshot" && \ go mod tidy && \
grep -lr -E '^// Code generated by MockGen\. DO NOT EDIT\.$' . | xargs -r -d '\n' rm && \ git diff --exit-code -- go.mod
go generate -run "mockgen" ./... && \
git diff --exit-code && \
rm -rf .git/
FROM --platform=${BUILDPLATFORM} base AS build FROM --platform=${BUILDPLATFORM} base AS build
ARG TARGETPLATFORM ARG TARGETPLATFORM
@@ -91,13 +84,13 @@ ENV VPN_SERVICE_PROVIDER=pia \
OPENVPN_CIPHERS= \ OPENVPN_CIPHERS= \
OPENVPN_AUTH= \ OPENVPN_AUTH= \
OPENVPN_PROCESS_USER= \ OPENVPN_PROCESS_USER= \
OPENVPN_IPV6=off \
OPENVPN_CUSTOM_CONFIG= \ OPENVPN_CUSTOM_CONFIG= \
# Wireguard # Wireguard
WIREGUARD_PRIVATE_KEY= \ WIREGUARD_PRIVATE_KEY= \
WIREGUARD_PRESHARED_KEY= \ WIREGUARD_PRESHARED_KEY= \
WIREGUARD_PUBLIC_KEY= \ WIREGUARD_PUBLIC_KEY= \
WIREGUARD_ADDRESSES= \ WIREGUARD_ADDRESSES= \
WIREGUARD_IMPLEMENTATION=auto \
# VPN server filtering # VPN server filtering
SERVER_REGIONS= \ SERVER_REGIONS= \
SERVER_COUNTRIES= \ SERVER_COUNTRIES= \
@@ -108,28 +101,19 @@ ENV VPN_SERVICE_PROVIDER=pia \
OWNED_ONLY=no \ OWNED_ONLY=no \
# # Private Internet Access only: # # Private Internet Access only:
PRIVATE_INTERNET_ACCESS_OPENVPN_ENCRYPTION_PRESET= \ PRIVATE_INTERNET_ACCESS_OPENVPN_ENCRYPTION_PRESET= \
VPN_PORT_FORWARDING=off \ PRIVATE_INTERNET_ACCESS_VPN_PORT_FORWARDING=off \
VPN_PORT_FORWARDING_STATUS_FILE="/tmp/gluetun/forwarded_port" \ PRIVATE_INTERNET_ACCESS_VPN_PORT_FORWARDING_STATUS_FILE="/tmp/gluetun/forwarded_port" \
# # Cyberghost only: # # Cyberghost only:
OPENVPN_CERT= \
OPENVPN_KEY= \
OPENVPN_CLIENTCRT_SECRETFILE=/run/secrets/openvpn_clientcrt \ OPENVPN_CLIENTCRT_SECRETFILE=/run/secrets/openvpn_clientcrt \
OPENVPN_CLIENTKEY_SECRETFILE=/run/secrets/openvpn_clientkey \ OPENVPN_CLIENTKEY_SECRETFILE=/run/secrets/openvpn_clientkey \
# # VPNSecure only:
OPENVPN_ENCRYPTED_KEY= \
OPENVPN_ENCRYPTED_KEY_SECRETFILE=/run/secrets/openvpn_encrypted_key \
OPENVPN_KEY_PASSPHRASE= \
OPENVPN_KEY_PASSPHRASE_SECRETFILE=/run/secrets/openvpn_key_passphrase \
# # Nordvpn only: # # Nordvpn only:
SERVER_NUMBER= \ SERVER_NUMBER= \
# # PIA only: # # PIA and ProtonVPN only:
SERVER_NAMES= \ SERVER_NAMES= \
# # ProtonVPN only: # # ProtonVPN only:
FREE_ONLY= \ FREE_ONLY= \
# # Surfshark only: # # Surfshark only:
MULTIHOP_ONLY= \ MULTIHOP_ONLY= \
# # VPN Secure only:
PREMIUM_ONLY= \
# Firewall # Firewall
FIREWALL=on \ FIREWALL=on \
FIREWALL_VPN_INPUT_PORTS= \ FIREWALL_VPN_INPUT_PORTS= \
@@ -140,8 +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_TARGET_ADDRESS=cloudflare.com:443 \ HEALTH_TARGET_ADDRESS=github.com:443 \
HEALTH_SUCCESS_WAIT_DURATION=5s \
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
@@ -179,7 +162,6 @@ ENV VPN_SERVICE_PROVIDER=pia \
HTTP_CONTROL_SERVER_ADDRESS=":8000" \ HTTP_CONTROL_SERVER_ADDRESS=":8000" \
# Server data updater # Server data updater
UPDATER_PERIOD=0 \ UPDATER_PERIOD=0 \
UPDATER_MIN_RATIO=0.8 \
UPDATER_VPN_SERVICE_PROVIDERS= \ UPDATER_VPN_SERVICE_PROVIDERS= \
# Public IP # Public IP
PUBLICIP_FILE="/tmp/gluetun/ip" \ PUBLICIP_FILE="/tmp/gluetun/ip" \
@@ -198,9 +180,8 @@ ENTRYPOINT ["/gluetun-entrypoint"]
EXPOSE 8000/tcp 8888/tcp 8388/tcp 8388/udp 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 wget && \ 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.12-r0 && \ 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.16/main" openssl\~1.1 && \
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

@@ -1,6 +1,11 @@
# Gluetun VPN client # Gluetun VPN client
Lightweight swiss-knife-like VPN client to multiple VPN service providers *Lightweight swiss-knife-like VPN client to tunnel to Cyberghost, ExpressVPN, FastestVPN,
HideMyAss, IPVanish, IVPN, Mullvad, NordVPN, Perfect Privacy, Privado, Private Internet Access, PrivateVPN,
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*
**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)
@@ -48,7 +53,6 @@ Lightweight swiss-knife-like VPN client to multiple VPN service providers
- Sponsor me on [github.com/sponsors/qdm12](https://github.com/sponsors/qdm12) - Sponsor me on [github.com/sponsors/qdm12](https://github.com/sponsors/qdm12)
- Donate to [paypal.me/qmcgaw](https://www.paypal.me/qmcgaw) - Donate to [paypal.me/qmcgaw](https://www.paypal.me/qmcgaw)
- Drop me [an email](mailto:quentin.mcgaw@gmail.com) - Drop me [an email](mailto:quentin.mcgaw@gmail.com)
- **Want to add a VPN provider?** check [Development](https://github.com/qdm12/gluetun/wiki/Development) and [Add a provider](https://github.com/qdm12/gluetun/wiki/Add-a-provider)
- Video: - Video:
[![Video Gif](https://i.imgur.com/CetWunc.gif)](https://youtu.be/0F6I03LQcI4) [![Video Gif](https://i.imgur.com/CetWunc.gif)](https://youtu.be/0F6I03LQcI4)
@@ -57,12 +61,12 @@ Lightweight swiss-knife-like VPN client to multiple VPN service providers
## Features ## Features
- Based on Alpine 3.17 for a small Docker image of 42MB - Based on Alpine 3.15 for a small Docker image of 29MB
- Supports: **AirVPN**, **Cyberghost**, **ExpressVPN**, **FastestVPN**, **HideMyAss**, **IPVanish**, **IVPN**, **Mullvad**, **NordVPN**, **Perfect Privacy**, **Privado**, **Private Internet Access**, **PrivateVPN**, **ProtonVPN**, **PureVPN**, **SlickVPN**, **Surfshark**, **TorGuard**, **VPNSecure.me**, **VPNUnlimited**, **Vyprvpn**, **WeVPN**, **Windscribe** servers - Supports: **Cyberghost**, **ExpressVPN**, **FastestVPN**, **HideMyAss**, **IPVanish**, **IVPN**, **Mullvad**, **NordVPN**, **Perfect Privacy**, **Privado**, **Private Internet Access**, **PrivateVPN**, **ProtonVPN**, **PureVPN**, **Surfshark**, **TorGuard**, **VPNUnlimited**, **Vyprvpn**, **WeVPN**, **Windscribe** servers
- Supports OpenVPN for all providers listed - Supports OpenVPN for all providers listed
- Supports Wireguard both kernelspace and userspace - Supports Wireguard both kernelspace and userspace
- For **Mullvad**, **Ivpn**, **Surfshark** and **Windscribe** - For **Mullvad**, **Ivpn** and **Windscribe**
- For **ProtonVPN**, **PureVPN**, **Torguard**, **VPN Unlimited** and **WeVPN** using [the custom provider](https://github.com/qdm12/gluetun/wiki/Custom-provider) - For **Torguard**, **VPN Unlimited** and **WeVPN** using [the custom provider](https://github.com/qdm12/gluetun/wiki/Custom-provider)
- For custom Wireguard configurations using [the custom provider](https://github.com/qdm12/gluetun/wiki/Custom-provider) - For custom Wireguard configurations using [the custom provider](https://github.com/qdm12/gluetun/wiki/Custom-provider)
- More in progress, see [#134](https://github.com/qdm12/gluetun/issues/134) - More in progress, see [#134](https://github.com/qdm12/gluetun/issues/134)
- DNS over TLS baked in with service provider(s) of your choice - DNS over TLS baked in with service provider(s) of your choice
@@ -98,8 +102,6 @@ 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
@@ -118,13 +120,8 @@ services:
# - WIREGUARD_ADDRESSES=10.64.222.21/32 # - WIREGUARD_ADDRESSES=10.64.222.21/32
# Timezone for accurate log times # Timezone for accurate log times
- TZ= - TZ=
# Server list updater. See https://github.com/qdm12/gluetun/wiki/Updating-Servers#periodic-update
- UPDATER_PERIOD=
- UPDATER_VPN_SERVICE_PROVIDERS=
``` ```
🆕 Image also available as `ghcr.io/qdm12/gluetun`
## License ## License
[![MIT](https://img.shields.io/github/license/qdm12/gluetun)](https://github.com/qdm12/gluetun/master/LICENSE) [![MIT](https://img.shields.io/github/license/qdm12/gluetun)](https://github.com/qdm12/gluetun/master/LICENSE)

View File

@@ -16,10 +16,10 @@ import (
"github.com/qdm12/dns/pkg/unbound" "github.com/qdm12/dns/pkg/unbound"
"github.com/qdm12/gluetun/internal/alpine" "github.com/qdm12/gluetun/internal/alpine"
"github.com/qdm12/gluetun/internal/cli" "github.com/qdm12/gluetun/internal/cli"
"github.com/qdm12/gluetun/internal/configuration/settings" "github.com/qdm12/gluetun/internal/configuration/sources"
"github.com/qdm12/gluetun/internal/configuration/sources/env" "github.com/qdm12/gluetun/internal/configuration/sources/env"
"github.com/qdm12/gluetun/internal/configuration/sources/files" "github.com/qdm12/gluetun/internal/configuration/sources/files"
mux "github.com/qdm12/gluetun/internal/configuration/sources/merge" "github.com/qdm12/gluetun/internal/configuration/sources/mux"
"github.com/qdm12/gluetun/internal/configuration/sources/secrets" "github.com/qdm12/gluetun/internal/configuration/sources/secrets"
"github.com/qdm12/gluetun/internal/constants" "github.com/qdm12/gluetun/internal/constants"
"github.com/qdm12/gluetun/internal/dns" "github.com/qdm12/gluetun/internal/dns"
@@ -29,28 +29,23 @@ import (
"github.com/qdm12/gluetun/internal/models" "github.com/qdm12/gluetun/internal/models"
"github.com/qdm12/gluetun/internal/netlink" "github.com/qdm12/gluetun/internal/netlink"
"github.com/qdm12/gluetun/internal/openvpn" "github.com/qdm12/gluetun/internal/openvpn"
"github.com/qdm12/gluetun/internal/openvpn/extract"
"github.com/qdm12/gluetun/internal/portforward" "github.com/qdm12/gluetun/internal/portforward"
"github.com/qdm12/gluetun/internal/pprof" "github.com/qdm12/gluetun/internal/pprof"
"github.com/qdm12/gluetun/internal/provider"
"github.com/qdm12/gluetun/internal/publicip" "github.com/qdm12/gluetun/internal/publicip"
"github.com/qdm12/gluetun/internal/publicip/ipinfo"
"github.com/qdm12/gluetun/internal/routing" "github.com/qdm12/gluetun/internal/routing"
"github.com/qdm12/gluetun/internal/server" "github.com/qdm12/gluetun/internal/server"
"github.com/qdm12/gluetun/internal/shadowsocks" "github.com/qdm12/gluetun/internal/shadowsocks"
"github.com/qdm12/gluetun/internal/storage" "github.com/qdm12/gluetun/internal/storage"
"github.com/qdm12/gluetun/internal/tun" "github.com/qdm12/gluetun/internal/tun"
updater "github.com/qdm12/gluetun/internal/updater/loop" "github.com/qdm12/gluetun/internal/updater"
"github.com/qdm12/gluetun/internal/updater/resolver"
"github.com/qdm12/gluetun/internal/updater/unzip"
"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"
) )
@@ -69,16 +64,16 @@ func main() {
} }
background := context.Background() background := context.Background()
signalCh := make(chan os.Signal, 1) signalCtx, stop := signal.NotifyContext(background, syscall.SIGINT, syscall.SIGTERM, os.Interrupt)
signal.Notify(signalCh, os.Interrupt, syscall.SIGTERM)
ctx, cancel := context.WithCancel(background) ctx, cancel := context.WithCancel(background)
logger := log.New(log.SetLevel(log.LevelInfo)) logger := logging.New(logging.Settings{
Level: logging.LevelInfo,
})
args := os.Args args := os.Args
tun := tun.New() tun := tun.New()
netLinkDebugLogger := logger.New(log.SetComponent("netlink")) netLinker := netlink.New()
netLinker := netlink.New(netLinkDebugLogger)
cli := cli.New() cli := cli.New()
cmder := command.NewCmder() cmder := command.NewCmder()
@@ -92,13 +87,14 @@ func main() {
errorCh <- _main(ctx, buildInfo, args, logger, muxReader, tun, netLinker, cmder, cli) errorCh <- _main(ctx, buildInfo, args, logger, muxReader, tun, netLinker, cmder, cli)
}() }()
var err error
select { select {
case signal := <-signalCh: case <-signalCtx.Done():
stop()
fmt.Println("") fmt.Println("")
logger.Warn("Caught OS signal " + signal.String() + ", shutting down") logger.Warn("Caught OS signal, 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)
@@ -110,27 +106,16 @@ func main() {
const shutdownGracePeriod = 5 * time.Second const shutdownGracePeriod = 5 * time.Second
timer := time.NewTimer(shutdownGracePeriod) timer := time.NewTimer(shutdownGracePeriod)
select { select {
case shutdownErr := <-errorCh: case <-errorCh:
if !timer.Stop() { if !timer.Stop() {
<-timer.C <-timer.C
} }
if shutdownErr != nil {
logger.Warnf("Shutdown not completed gracefully: %s", shutdownErr)
os.Exit(1)
}
logger.Info("Shutdown successful") logger.Info("Shutdown successful")
if err != nil {
os.Exit(1)
}
os.Exit(0)
case <-timer.C: case <-timer.C:
logger.Warn("Shutdown timed out") logger.Warn("Shutdown timed out")
os.Exit(1)
case signal := <-signalCh:
logger.Warn("Caught OS signal " + signal.String() + ", forcing shut down")
os.Exit(1)
} }
os.Exit(1)
} }
var ( var (
@@ -139,9 +124,9 @@ 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 log.LoggerInterface, source Source, args []string, logger logging.ParentLogger, source sources.Source,
tun Tun, netLinker netLinker, cmder command.RunStarter, tun tun.Interface, netLinker netlink.NetLinker, cmder command.RunStarter,
cli clier) error { cli cli.CLIer) error {
if len(args) > 1 { // cli operation if len(args) > 1 { // cli operation
switch args[1] { switch args[1] {
case "healthcheck": case "healthcheck":
@@ -149,7 +134,7 @@ func _main(ctx context.Context, buildInfo models.BuildInformation,
case "clientkey": case "clientkey":
return cli.ClientKey(args[2:]) return cli.ClientKey(args[2:])
case "openvpnconfig": case "openvpnconfig":
return cli.OpenvpnConfig(logger, source, netLinker) return cli.OpenvpnConfig(logger, source)
case "update": case "update":
return cli.Update(ctx, args[2:], logger) return cli.Update(ctx, args[2:], logger)
case "format-servers": case "format-servers":
@@ -189,16 +174,17 @@ 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.Patch(log.SetLevel(*allSettings.Log.Level)) logger.PatchLevel(*allSettings.Log.Level)
netLinker.PatchLoggerLevel(*allSettings.Log.Level)
routingLogger := logger.New(log.SetComponent("routing")) routingLogger := logger.NewChild(logging.Settings{
Prefix: "routing: ",
})
if *allSettings.Firewall.Debug { // To remove in v4 if *allSettings.Firewall.Debug { // To remove in v4
routingLogger.Patch(log.SetLevel(log.LevelDebug)) routingLogger.PatchLevel(logging.LevelDebug)
} }
routingConf := routing.New(netLinker, routingLogger) routingConf := routing.New(netLinker, routingLogger)
defaultRoutes, err := routingConf.DefaultRoutes() defaultInterface, defaultGateway, err := routingConf.DefaultRoute()
if err != nil { if err != nil {
return err return err
} }
@@ -208,16 +194,19 @@ func _main(ctx context.Context, buildInfo models.BuildInformation,
return err return err
} }
firewallLogger := logger.New(log.SetComponent("firewall")) defaultIP, err := routingConf.DefaultIP()
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 {
@@ -226,26 +215,23 @@ 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.New(log.SetComponent("storage")) storageLogger := logger.NewChild(logging.Settings{Prefix: "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
} }
ipv6Supported, err := netLinker.IsIPv6Supported() allServers := storage.GetServers()
if err != nil {
return fmt.Errorf("checking for IPv6 support: %w", err)
}
err = allSettings.Validate(storage, ipv6Supported) err = allSettings.Validate(allServers)
if err != nil { if err != nil {
return err return err
} }
allSettings.Pprof.HTTPServer.Logger = logger.New(log.SetComponent("pprof")) allSettings.Pprof.HTTPServer.Logger = logger
pprofServer, err := pprof.New(allSettings.Pprof) pprofServer, err := pprof.New(allSettings.Pprof)
if err != nil { if err != nil {
return fmt.Errorf("creating Pprof server: %w", err) return fmt.Errorf("cannot create Pprof server: %w", err)
} }
puid, pgid := int(*allSettings.System.PUID), int(*allSettings.System.PGID) puid, pgid := int(*allSettings.System.PUID), int(*allSettings.System.PGID)
@@ -255,7 +241,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.New(log.SetComponent("openvpn configurator")), logger.NewChild(logging.Settings{Prefix: "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"
@@ -277,10 +263,6 @@ func _main(ctx context.Context, buildInfo models.BuildInformation,
logger.Info(allSettings.String()) logger.Info(allSettings.String())
for _, warning := range allSettings.Warnings() {
logger.Warn(warning)
}
if err := os.MkdirAll("/tmp/gluetun", 0644); err != nil { if err := os.MkdirAll("/tmp/gluetun", 0644); err != nil {
return err return err
} }
@@ -291,7 +273,7 @@ func _main(ctx context.Context, buildInfo models.BuildInformation,
const defaultUsername = "nonrootuser" const defaultUsername = "nonrootuser"
nonRootUsername, err := alpineConf.CreateUser(defaultUsername, puid) nonRootUsername, err := alpineConf.CreateUser(defaultUsername, puid)
if err != nil { if err != nil {
return fmt.Errorf("creating user: %w", err) return fmt.Errorf("cannot create user: %w", err)
} }
if nonRootUsername != defaultUsername { if nonRootUsername != defaultUsername {
logger.Info("using existing username " + nonRootUsername + " corresponding to user id " + fmt.Sprint(puid)) logger.Info("using existing username " + nonRootUsername + " corresponding to user id " + fmt.Sprint(puid))
@@ -309,12 +291,12 @@ func _main(ctx context.Context, buildInfo models.BuildInformation,
if strings.Contains(err.Error(), "operation not permitted") { if strings.Contains(err.Error(), "operation not permitted") {
logger.Warn("💡 Tip: Are you passing NET_ADMIN capability to gluetun?") logger.Warn("💡 Tip: Are you passing NET_ADMIN capability to gluetun?")
} }
return fmt.Errorf("setting up routing: %w", err) return fmt.Errorf("cannot setup routing: %w", err)
} }
defer func() { defer func() {
routingLogger.Info("routing cleanup...") logger.Info("routing cleanup...")
if err := routingConf.TearDown(); err != nil { if err := routingConf.TearDown(); err != nil {
routingLogger.Error("cannot teardown routing: " + err.Error()) logger.Error("cannot teardown routing: " + err.Error())
} }
}() }()
@@ -325,11 +307,6 @@ func _main(ctx context.Context, buildInfo models.BuildInformation,
return err return err
} }
err = routingConf.AddLocalRules(localNetworks)
if err != nil {
return fmt.Errorf("adding local rules: %w", err)
}
const tunDevice = "/dev/net/tun" const tunDevice = "/dev/net/tun"
if err := tun.Check(tunDevice); err != nil { if err := tun.Check(tunDevice); err != nil {
logger.Info(err.Error() + "; creating it...") logger.Info(err.Error() + "; creating it...")
@@ -340,11 +317,9 @@ func _main(ctx context.Context, buildInfo models.BuildInformation,
} }
for _, port := range allSettings.Firewall.InputPorts { for _, port := range allSettings.Firewall.InputPorts {
for _, defaultRoute := range defaultRoutes { err = firewallConf.SetAllowedPort(ctx, port, defaultInterface)
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?
@@ -365,23 +340,20 @@ func _main(ctx context.Context, buildInfo models.BuildInformation,
tickersGroupHandler := goshutdown.NewGroupHandler("tickers", defaultGroupOptions...) tickersGroupHandler := goshutdown.NewGroupHandler("tickers", defaultGroupOptions...)
otherGroupHandler := goshutdown.NewGroupHandler("other", defaultGroupOptions...) otherGroupHandler := goshutdown.NewGroupHandler("other", defaultGroupOptions...)
if *allSettings.Pprof.Enabled { pprofReady := make(chan struct{})
// TODO run in run loop so this can be patched at runtime pprofHandler, pprofCtx, pprofDone := goshutdown.NewGoRoutineHandler("pprof server")
pprofReady := make(chan struct{}) go pprofServer.Run(pprofCtx, pprofReady, pprofDone)
pprofHandler, pprofCtx, pprofDone := goshutdown.NewGoRoutineHandler("pprof server") otherGroupHandler.Add(pprofHandler)
go pprofServer.Run(pprofCtx, pprofReady, pprofDone) <-pprofReady
otherGroupHandler.Add(pprofHandler)
<-pprofReady
}
portForwardLogger := logger.New(log.SetComponent("port forwarding")) portForwardLogger := logger.NewChild(logging.Settings{Prefix: "port forwarding: "})
portForwardLooper := portforward.NewLoop(allSettings.VPN.Provider.PortForwarding, portForwardLooper := portforward.NewLoop(allSettings.VPN.Provider.PortForwarding,
httpClient, firewallConf, portForwardLogger, puid, pgid) 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.New(log.SetComponent("dns over tls")) unboundLogger := logger.NewChild(logging.Settings{Prefix: "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(
@@ -395,9 +367,8 @@ func _main(ctx context.Context, buildInfo models.BuildInformation,
go unboundLooper.RunRestartTicker(dnsTickerCtx, dnsTickerDone) go unboundLooper.RunRestartTicker(dnsTickerCtx, dnsTickerDone)
controlGroupHandler.Add(dnsTickerHandler) controlGroupHandler.Add(dnsTickerHandler)
ipFetcher := ipinfo.New(httpClient) publicIPLooper := publicip.NewLoop(httpClient,
publicIPLooper := publicip.NewLoop(ipFetcher, 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))
@@ -409,25 +380,18 @@ func _main(ctx context.Context, buildInfo models.BuildInformation,
go publicIPLooper.RunRestartTicker(pubIPTickerCtx, pubIPTickerDone) go publicIPLooper.RunRestartTicker(pubIPTickerCtx, pubIPTickerDone)
tickersGroupHandler.Add(pubIPTickerHandler) tickersGroupHandler.Add(pubIPTickerHandler)
updaterLogger := logger.New(log.SetComponent("updater")) vpnLogger := logger.NewChild(logging.Settings{Prefix: "vpn: "})
vpnLooper := vpn.NewLoop(allSettings.VPN, allSettings.Firewall.VPNInputPorts,
unzipper := unzip.New(httpClient) allServers, ovpnConf, netLinker, firewallConf, routingConf, portForwardLooper,
parallelResolver := resolver.NewParallelResolver(allSettings.Updater.DNSAddress)
openvpnFileExtractor := extract.New()
providers := provider.NewProviders(storage, time.Now, updaterLogger,
httpClient, unzipper, parallelResolver, ipFetcher, openvpnFileExtractor)
vpnLogger := logger.New(log.SetComponent("vpn"))
vpnLooper := vpn.NewLoop(allSettings.VPN, ipv6Supported, allSettings.Firewall.VPNInputPorts,
providers, storage, ovpnConf, netLinker, firewallConf, routingConf, portForwardLooper,
cmder, publicIPLooper, unboundLooper, vpnLogger, httpClient, cmder, publicIPLooper, unboundLooper, vpnLogger, httpClient,
buildInfo, *allSettings.Version.Enabled) buildInfo, *allSettings.Version.Enabled)
vpnHandler, vpnCtx, vpnDone := goshutdown.NewGoRoutineHandler( vpnHandler, vpnCtx, vpnDone := goshutdown.NewGoRoutineHandler(
"vpn", goroutine.OptionTimeout(time.Second)) "vpn", goroutine.OptionTimeout(time.Second))
go vpnLooper.Run(vpnCtx, vpnDone) go vpnLooper.Run(vpnCtx, vpnDone)
updaterLooper := updater.NewLoop(allSettings.Updater, updaterLooper := updater.NewLooper(allSettings.Updater,
providers, storage, httpClient, updaterLogger) allServers, storage, vpnLooper.SetServers, httpClient,
logger.NewChild(logging.Settings{Prefix: "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
@@ -440,15 +404,15 @@ func _main(ctx context.Context, buildInfo models.BuildInformation,
controlGroupHandler.Add(updaterTickerHandler) controlGroupHandler.Add(updaterTickerHandler)
httpProxyLooper := httpproxy.NewLoop( httpProxyLooper := httpproxy.NewLoop(
logger.New(log.SetComponent("http proxy")), logger.NewChild(logging.Settings{Prefix: "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))
go httpProxyLooper.Run(httpProxyCtx, httpProxyDone) go httpProxyLooper.Run(httpProxyCtx, httpProxyDone)
otherGroupHandler.Add(httpProxyHandler) otherGroupHandler.Add(httpProxyHandler)
shadowsocksLooper := shadowsocks.NewLoop(allSettings.Shadowsocks, shadowsocksLooper := shadowsocks.NewLooper(allSettings.Shadowsocks,
logger.New(log.SetComponent("shadowsocks"))) logger.NewChild(logging.Settings{Prefix: "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)
@@ -458,19 +422,13 @@ 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, err := server.New(httpServerCtx, controlServerAddress, controlServerLogging, httpServer := server.New(httpServerCtx, controlServerAddress, controlServerLogging,
logger.New(log.SetComponent("http server")), logger.NewChild(logging.Settings{Prefix: "http server: "}),
buildInfo, vpnLooper, portForwardLooper, unboundLooper, updaterLooper, publicIPLooper, buildInfo, vpnLooper, portForwardLooper, unboundLooper, updaterLooper, publicIPLooper)
storage, ipv6Supported) go httpServer.Run(httpServerCtx, httpServerDone)
if err != nil {
return fmt.Errorf("setting up control server: %w", err)
}
httpServerReady := make(chan struct{})
go httpServer.Run(httpServerCtx, httpServerReady, httpServerDone)
<-httpServerReady
controlGroupHandler.Add(httpServerHandler) controlGroupHandler.Add(httpServerHandler)
healthLogger := logger.New(log.SetComponent("healthcheck")) healthLogger := logger.NewChild(logging.Settings{Prefix: "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))
@@ -510,69 +468,10 @@ func printVersions(ctx context.Context, logger infoer,
for _, element := range elements { for _, element := range elements {
version, err := element.getVersion(ctx) version, err := element.getVersion(ctx)
if err != nil { if err != nil {
return fmt.Errorf("getting %s version: %w", element.name, err) return err
} }
logger.Info(element.name + " version: " + version) logger.Info(element.name + " version: " + version)
} }
return nil return nil
} }
type netLinker interface {
Addresser
Router
Ruler
Linker
IsWireguardSupported() (ok bool, err error)
IsIPv6Supported() (ok bool, err error)
PatchLoggerLevel(level log.Level)
}
type Addresser interface {
AddrList(link netlink.Link, family int) (
addresses []netlink.Addr, err error)
AddrAdd(link netlink.Link, addr *netlink.Addr) error
}
type Router interface {
RouteList(link netlink.Link, family int) (
routes []netlink.Route, err error)
RouteAdd(route *netlink.Route) error
RouteDel(route *netlink.Route) error
RouteReplace(route *netlink.Route) error
}
type Ruler interface {
RuleList(family int) (rules []netlink.Rule, err error)
RuleAdd(rule *netlink.Rule) error
RuleDel(rule *netlink.Rule) error
}
type Linker interface {
LinkList() (links []netlink.Link, err error)
LinkByName(name string) (link netlink.Link, err error)
LinkByIndex(index int) (link netlink.Link, err error)
LinkAdd(link netlink.Link) (err error)
LinkDel(link netlink.Link) (err error)
LinkSetUp(link netlink.Link) (err error)
LinkSetDown(link netlink.Link) (err error)
}
type clier interface {
ClientKey(args []string) error
FormatServers(args []string) error
OpenvpnConfig(logger cli.OpenvpnConfigLogger, source cli.Source, ipv6Checker cli.IPv6Checker) error
HealthCheck(ctx context.Context, source cli.Source, warner cli.Warner) error
Update(ctx context.Context, args []string, logger cli.UpdaterLogger) error
}
type Tun interface {
Check(tunDevice string) error
Create(tunDevice string) error
}
type Source interface {
Read() (settings settings.Settings, err error)
ReadHealth() (health settings.Health, err error)
String() string
}

47
go.mod
View File

@@ -1,10 +1,10 @@
module github.com/qdm12/gluetun module github.com/qdm12/gluetun
go 1.20 go 1.17
require ( require (
github.com/breml/rootcerts v0.2.10 github.com/breml/rootcerts v0.2.2
github.com/fatih/color v1.15.0 github.com/fatih/color v1.13.0
github.com/golang/mock v1.6.0 github.com/golang/mock v1.6.0
github.com/qdm12/dns v1.11.0 github.com/qdm12/dns v1.11.0
github.com/qdm12/golibs v0.0.0-20210822203818-5c568b0777b6 github.com/qdm12/golibs v0.0.0-20210822203818-5c568b0777b6
@@ -12,39 +12,32 @@ 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.8.2 github.com/stretchr/testify v1.7.0
github.com/vishvananda/netlink v1.2.1-beta.2 github.com/vishvananda/netlink v1.1.1-0.20211129163951-9ada19101fc5
github.com/youmark/pkcs8 v0.0.0-20201027041543-1326539a0a0a golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c
golang.org/x/exp v0.0.0-20230519143937-03e91628a987 golang.zx2c4.com/wireguard v0.0.0-20210805125648-3957e9b9dd19
golang.org/x/net v0.10.0 golang.zx2c4.com/wireguard/wgctrl v0.0.0-20210803171230-4253848d036c
golang.org/x/sys v0.8.0 inet.af/netaddr v0.0.0-20210718074554-06ca8145d722
golang.org/x/text v0.9.0
golang.zx2c4.com/wireguard v0.0.0-20230325221338-052af4a8072b
golang.zx2c4.com/wireguard/wgctrl v0.0.0-20230215201556-9c5414ab4bde
inet.af/netaddr v0.0.0-20220811202034-502d2d690317
) )
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.9 // indirect github.com/google/go-cmp v0.5.5 // indirect
github.com/josharian/native v1.0.0 // indirect github.com/josharian/native v0.0.0-20200817173448-b6b71def0850 // indirect
github.com/mattn/go-colorable v0.1.13 // indirect github.com/mattn/go-colorable v0.1.9 // indirect
github.com/mattn/go-isatty v0.0.17 // indirect github.com/mattn/go-isatty v0.0.14 // indirect
github.com/mdlayher/genetlink v1.2.0 // indirect github.com/mdlayher/genetlink v1.0.0 // indirect
github.com/mdlayher/netlink v1.6.2 // indirect github.com/mdlayher/netlink v1.4.0 // indirect
github.com/mdlayher/socket v0.2.3 // indirect
github.com/miekg/dns v1.1.40 // indirect github.com/miekg/dns v1.1.40 // indirect
github.com/mr-tron/base58 v1.2.0 // indirect github.com/mr-tron/base58 v1.2.0 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect
github.com/riobard/go-bloom v0.0.0-20200614022211-cdc8013cb5b3 // indirect github.com/riobard/go-bloom v0.0.0-20200614022211-cdc8013cb5b3 // indirect
github.com/vishvananda/netns v0.0.0-20200728191858-db3c7e526aae // indirect github.com/vishvananda/netns v0.0.0-20200728191858-db3c7e526aae // indirect
go4.org/intern v0.0.0-20211027215823-ae77deb06f29 // indirect go4.org/intern v0.0.0-20210108033219-3eb7198706b2 // indirect
go4.org/unsafe/assume-no-moving-gc v0.0.0-20230221090011-e4bae7ad2296 // indirect go4.org/unsafe/assume-no-moving-gc v0.0.0-20201222180813-1025295fd063 // indirect
golang.org/x/crypto v0.6.0 // indirect golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97 // indirect
golang.org/x/sync v0.1.0 // indirect golang.org/x/net v0.0.0-20210504132125-bbd867fde50d // indirect
golang.zx2c4.com/wintun v0.0.0-20230126152724-0fa3db229ce2 // indirect gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
) )

168
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.10 h1:UGVZ193UTSUASpGtg6pbDwzOd7XQP+at0Ssg1/2E4h8= github.com/breml/rootcerts v0.2.2 h1:hkHEpbTdYaNvDoYeq+mwRvCeg/YTTl23DjQ1Tnj71Zs=
github.com/breml/rootcerts v0.2.10/go.mod h1:24FDtzYMpqIeYC7QzaE8VPRQaFZU5TIUDlyk8qwjD88= github.com/breml/rootcerts v0.2.2/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=
@@ -14,8 +14,8 @@ github.com/docker/go-units v0.3.3/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDD
github.com/dvyukov/go-fuzz v0.0.0-20210103155950-6a8e9d1f2415/go.mod h1:11Gm+ccJnvAhCNLlf5+cS9KjtbaD5I5zaZpFMsTHWTw= github.com/dvyukov/go-fuzz v0.0.0-20210103155950-6a8e9d1f2415/go.mod h1:11Gm+ccJnvAhCNLlf5+cS9KjtbaD5I5zaZpFMsTHWTw=
github.com/emirpasic/gods v1.12.0/go.mod h1:YfzfFFoVP/catgzJb4IKIqXjX78Ha8FMSDh3ymbK86o= github.com/emirpasic/gods v1.12.0/go.mod h1:YfzfFFoVP/catgzJb4IKIqXjX78Ha8FMSDh3ymbK86o=
github.com/fatih/color v1.12.0/go.mod h1:ELkj/draVOlAH/xkhN6mQ50Qd0MPOk5AAr3maGEBuJM= github.com/fatih/color v1.12.0/go.mod h1:ELkj/draVOlAH/xkhN6mQ50Qd0MPOk5AAr3maGEBuJM=
github.com/fatih/color v1.15.0 h1:kOqh6YHBtK8aywxGerMG2Eq3H6Qgoqeo13Bk2Mv/nBs= github.com/fatih/color v1.13.0 h1:8LOYc1KYPPmyKMuN8QV2DNRWNbLo6LZ0iLs8+mlH53w=
github.com/fatih/color v1.15.0/go.mod h1:0h5ZqXfHYED7Bhv2ZJamyIOUej9KtShiJESRwBDUSsw= github.com/fatih/color v1.13.0/go.mod h1:kLAiJbzzSOZDVNGyDpeOxJ47H46qBXwg5ILebYFFOfk=
github.com/flynn/go-shlex v0.0.0-20150515145356-3f9db97f8568/go.mod h1:xEzjJPgXI435gkrCt3MPfRiAkVrwSbHsst4LCFVfpJc= github.com/flynn/go-shlex v0.0.0-20150515145356-3f9db97f8568/go.mod h1:xEzjJPgXI435gkrCt3MPfRiAkVrwSbHsst4LCFVfpJc=
github.com/gliderlabs/ssh v0.2.2/go.mod h1:U7qILu1NlMHj9FlMhZLlkCdDnU1DBEAqr0aevW3Awn0= github.com/gliderlabs/ssh v0.2.2/go.mod h1:U7qILu1NlMHj9FlMhZLlkCdDnU1DBEAqr0aevW3Awn0=
github.com/globalsign/mgo v0.0.0-20180905125535-1ca0a4f7cbcb/go.mod h1:xkRDCp4j0OGD1HRkm4kmhM+pmpv3AKq5SU7GMg4oO/Q= github.com/globalsign/mgo v0.0.0-20180905125535-1ca0a4f7cbcb/go.mod h1:xkRDCp4j0OGD1HRkm4kmhM+pmpv3AKq5SU7GMg4oO/Q=
@@ -36,18 +36,28 @@ github.com/golang/mock v1.5.0/go.mod h1:CWnOUgYIOo4TcNZ0wHX3YZCqsaM1I1Jvs6v3mP3K
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=
github.com/gomodule/redigo v2.0.0+incompatible/go.mod h1:B4C85qUVwatsJoIUNIfCRsp7qO0iAmpGFZ4EELWSbC4= github.com/gomodule/redigo v2.0.0+incompatible/go.mod h1:B4C85qUVwatsJoIUNIfCRsp7qO0iAmpGFZ4EELWSbC4=
github.com/google/btree v1.0.1 h1:gK4Kx5IaGY9CD5sPJ36FHiBJ6ZXl0kilRiiCj+jdYp4= github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
github.com/google/go-cmp v0.5.7/go.mod h1:n+brtR0CgQNWTVd5ZUFpTBC8YFBDLK/h/bpaJ8/DtOE= github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38= github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
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/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/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=
github.com/josharian/native v1.0.0 h1:Ts/E8zCSEsG17dUqv7joXJFybuMLjQfWE04tsBODTxk= github.com/josharian/native v0.0.0-20200817173448-b6b71def0850 h1:uhL5Gw7BINiiPAo24A2sxkcDI0Jt/sqp1v5xQCniEFA=
github.com/josharian/native v1.0.0/go.mod h1:7X/raswPFr05uY3HiLlYeyQntB6OO7E/d2Cu7qoaN2w= github.com/josharian/native v0.0.0-20200817173448-b6b71def0850/go.mod h1:7X/raswPFr05uY3HiLlYeyQntB6OO7E/d2Cu7qoaN2w=
github.com/jsimonetti/rtnetlink v0.0.0-20190606172950-9527aa82566a/go.mod h1:Oz+70psSo5OFh8DBl0Zv2ACw7Esh6pPUphlvZG9x7uw=
github.com/jsimonetti/rtnetlink v0.0.0-20200117123717-f846d4f6c1f4/go.mod h1:WGuG/smIU4J/54PblvSbh+xvCZmpJnFgr3ds6Z55XMQ=
github.com/jsimonetti/rtnetlink v0.0.0-20201009170750-9c6f07d100c1/go.mod h1:hqoO/u39cqLeBLebZ8fWdE96O7FxrAsRYhnVOdgHxok=
github.com/jsimonetti/rtnetlink v0.0.0-20201216134343-bde56ed16391/go.mod h1:cR77jAZG3Y3bsb8hF6fHJbFoyFukLFOkQ98S0pQz3xw=
github.com/jsimonetti/rtnetlink v0.0.0-20201220180245-69540ac93943/go.mod h1:z4c53zj6Eex712ROyh8WI0ihysb5j2ROyV42iNogmAs=
github.com/jsimonetti/rtnetlink v0.0.0-20210122163228-8d122574c736/go.mod h1:ZXpIyOK59ZnN7J0BV99cZUPmsqDRZ3eq5X+st7u/oSA=
github.com/jsimonetti/rtnetlink v0.0.0-20210212075122-66c871082f2b h1:c3NTyLNozICy8B4mlMXemD3z/gXgQzVXZS/HqT+i3do=
github.com/jsimonetti/rtnetlink v0.0.0-20210212075122-66c871082f2b/go.mod h1:8w9Rh8m+aHZIG69YPGGem1i5VzoyRC8nw2kA8B+ik5U=
github.com/kevinburke/ssh_config v0.0.0-20190725054713-01f96b0aa0cd/go.mod h1:CT57kijsi8u/K/BOFA39wgDQJ9CxiF4nAY/ojJ6r6mM= github.com/kevinburke/ssh_config v0.0.0-20190725054713-01f96b0aa0cd/go.mod h1:CT57kijsi8u/K/BOFA39wgDQJ9CxiF4nAY/ojJ6r6mM=
github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI= github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI=
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
@@ -58,23 +68,29 @@ github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
github.com/kyokomi/emoji v2.2.4+incompatible/go.mod h1:mZ6aGCD7yk8j6QY6KICwnZ2pxoszVseX1DNoGtU2tBA= github.com/kyokomi/emoji v2.2.4+incompatible/go.mod h1:mZ6aGCD7yk8j6QY6KICwnZ2pxoszVseX1DNoGtU2tBA=
github.com/mailru/easyjson v0.0.0-20180823135443-60711f1a8329/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= github.com/mailru/easyjson v0.0.0-20180823135443-60711f1a8329/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc=
github.com/mattn/go-colorable v0.1.8/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= github.com/mattn/go-colorable v0.1.8/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc=
github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA= github.com/mattn/go-colorable v0.1.9 h1:sqDoxXbdeALODt0DAeJCVp38ps9ZogZEAXjus69YV3U=
github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg= github.com/mattn/go-colorable v0.1.9/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc=
github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU=
github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= github.com/mattn/go-isatty v0.0.14 h1:yVuAays6BHfxijgZPzw+3Zlu5yQgKGP2/hcQbHb7S9Y=
github.com/mattn/go-isatty v0.0.17 h1:BTarxUcIeDqL27Mc+vyvdWYSL28zpIhv3RoTdsLMPng= github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94=
github.com/mattn/go-isatty v0.0.17/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= github.com/mdlayher/ethtool v0.0.0-20210210192532-2b88debcdd43 h1:WgyLFv10Ov49JAQI/ZLUkCZ7VJS3r74hwFIGXJsgZlY=
github.com/mdlayher/genetlink v1.2.0 h1:4yrIkRV5Wfk1WfpWTcoOlGmsWgQj3OtQN9ZsbrE+XtU= github.com/mdlayher/ethtool v0.0.0-20210210192532-2b88debcdd43/go.mod h1:+t7E0lkKfbBsebllff1xdTmyJt8lH37niI6kwFk9OTo=
github.com/mdlayher/genetlink v1.2.0/go.mod h1:ra5LDov2KrUCZJiAtEvXXZBxGMInICMXIwshlJ+qRxQ= github.com/mdlayher/genetlink v1.0.0 h1:OoHN1OdyEIkScEmRgxLEe2M9U8ClMytqA5niynLtfj0=
github.com/mdlayher/netlink v1.6.0/go.mod h1:0o3PlBmGst1xve7wQ7j/hwpNaFaH4qCRyWCdcZk8/vA= github.com/mdlayher/genetlink v1.0.0/go.mod h1:0rJ0h4itni50A86M2kHcgS85ttZazNt7a8H2a2cw0Gc=
github.com/mdlayher/netlink v1.6.2 h1:D2zGSkvYsJ6NreeED3JiVTu1lj2sIYATqSaZlhPzUgQ= github.com/mdlayher/netlink v0.0.0-20190409211403-11939a169225/go.mod h1:eQB3mZE4aiYnlUsyGGCOpPETfdQq4Jhsgf1fk3cwQaA=
github.com/mdlayher/netlink v1.6.2/go.mod h1:O1HXX2sIWSMJ3Qn1BYZk1yZM+7iMki/uYGGiwGyq/iU= github.com/mdlayher/netlink v1.0.0/go.mod h1:KxeJAFOFLG6AjpyDkQ/iIhxygIUKD+vcwqcnu43w/+M=
github.com/mdlayher/socket v0.1.1/go.mod h1:mYV5YIZAfHh4dzDVzI8x8tWLWCliuX8Mon5Awbj+qDs= github.com/mdlayher/netlink v1.1.0/go.mod h1:H4WCitaheIsdF9yOYu8CFmCgQthAPIWZmcKp9uZHgmY=
github.com/mdlayher/socket v0.2.3 h1:XZA2X2TjdOwNoNPVPclRCURoX/hokBY8nkTmRZFEheM= github.com/mdlayher/netlink v1.1.1/go.mod h1:WTYpFb/WTvlRJAyKhZL5/uy69TDDpHHu2VZmb2XgV7o=
github.com/mdlayher/socket v0.2.3/go.mod h1:bz12/FozYNH/VbvC3q7TRIK/Y6dH1kCKsXaUeXi/FmY= github.com/mdlayher/netlink v1.2.0/go.mod h1:kwVW1io0AZy9A1E2YYgaD4Cj+C+GPkU6klXCMzIJ9p8=
github.com/mdlayher/netlink v1.2.1/go.mod h1:bacnNlfhqHqqLo4WsYeXSqfyXkInQ9JneWI68v1KwSU=
github.com/mdlayher/netlink v1.2.2-0.20210123213345-5cc92139ae3e/go.mod h1:bacnNlfhqHqqLo4WsYeXSqfyXkInQ9JneWI68v1KwSU=
github.com/mdlayher/netlink v1.3.0/go.mod h1:xK/BssKuwcRXHrtN04UBkwQ6dY9VviGGuriDdoPSWys=
github.com/mdlayher/netlink v1.4.0 h1:n3ARR+Fm0dDv37dj5wSWZXDKcy+U0zwcXS3zKMnSiT0=
github.com/mdlayher/netlink v1.4.0/go.mod h1:dRJi5IABcZpBD2A3D0Mv/AiX8I9uDEu5oGkAVrekmf8=
github.com/miekg/dns v1.1.40 h1:pyyPFfGMnciYUk/mXpKkVmeMQjfXqt3FAJ2hy7tPiLA= github.com/miekg/dns v1.1.40 h1:pyyPFfGMnciYUk/mXpKkVmeMQjfXqt3FAJ2hy7tPiLA=
github.com/miekg/dns v1.1.40/go.mod h1:KNUDUusw/aVsxyTYZM1oqvCicbwhgbNgztCETuNZ7xM= github.com/miekg/dns v1.1.40/go.mod h1:KNUDUusw/aVsxyTYZM1oqvCicbwhgbNgztCETuNZ7xM=
github.com/mikioh/ipaddr v0.0.0-20190404000644-d465c8ab6721 h1:RlZweED6sbSArvlE924+mUcZuXKLBHA35U7LN621Bws= github.com/mikioh/ipaddr v0.0.0-20190404000644-d465c8ab6721 h1:RlZweED6sbSArvlE924+mUcZuXKLBHA35U7LN621Bws=
github.com/mikioh/ipaddr v0.0.0-20190404000644-d465c8ab6721/go.mod h1:Ickgr2WtCLZ2MDGd4Gr0geeCH5HybhRJbonOgQpvSxc=
github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
github.com/mr-tron/base58 v1.2.0 h1:T/HDJBh4ZCPbU39/+c3rRvE0uKBQlU27+QI8LJ4t64o= github.com/mr-tron/base58 v1.2.0 h1:T/HDJBh4ZCPbU39/+c3rRvE0uKBQlU27+QI8LJ4t64o=
@@ -99,8 +115,6 @@ 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=
@@ -111,104 +125,100 @@ github.com/sergi/go-diff v1.0.0/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAm
github.com/src-d/gcfg v1.4.0/go.mod h1:p/UMsR43ujA89BJY9duynAwIpvqEujIH/jFlfL7jWoI= github.com/src-d/gcfg v1.4.0/go.mod h1:p/UMsR43ujA89BJY9duynAwIpvqEujIH/jFlfL7jWoI=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.2.0/go.mod h1:qt09Ya8vawLte6SNmTgCsAVtYtaKzEcn8ATUoHMkEqE= github.com/stretchr/objx v0.2.0/go.mod h1:qt09Ya8vawLte6SNmTgCsAVtYtaKzEcn8ATUoHMkEqE=
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
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/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/vishvananda/netlink v1.1.1-0.20211129163951-9ada19101fc5 h1:b/k/BVWzWRS5v6AB0gf2ckFSbFsHN5jR0HoNso1pN+w=
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= github.com/vishvananda/netlink v1.1.1-0.20211129163951-9ada19101fc5/go.mod h1:twkDnbuQxJYemMlGd4JFIcuhgX83tXhKS2B/PRMpOho=
github.com/stretchr/testify v1.8.2 h1:+h33VjcLVPDHtOdpUCuF+7gSuG3yGIftsP1YvFihtJ8=
github.com/stretchr/testify v1.8.2/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
github.com/vishvananda/netlink v1.2.1-beta.2 h1:Llsql0lnQEbHj0I1OuKyp8otXp0r3q0mPkuhwHfStVs=
github.com/vishvananda/netlink v1.2.1-beta.2/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=
github.com/vishvananda/netns v0.0.0-20200728191858-db3c7e526aae/go.mod h1:DD4vA1DwXk04H54A1oHXtwZmA0grkVMdPxx/VGLCah0= github.com/vishvananda/netns v0.0.0-20200728191858-db3c7e526aae/go.mod h1:DD4vA1DwXk04H54A1oHXtwZmA0grkVMdPxx/VGLCah0=
github.com/xanzy/ssh-agent v0.2.1/go.mod h1:mLlQY/MoOhWBj+gOGMQkOeiEvkx+8pJSI+0Bx9h2kr4= github.com/xanzy/ssh-agent v0.2.1/go.mod h1:mLlQY/MoOhWBj+gOGMQkOeiEvkx+8pJSI+0Bx9h2kr4=
github.com/yl2chen/cidranger v1.0.2/go.mod h1:9U1yz7WPYDwf0vpNWFaeRh0bjwz5RVgRy/9UEQfHl0g= github.com/yl2chen/cidranger v1.0.2/go.mod h1:9U1yz7WPYDwf0vpNWFaeRh0bjwz5RVgRy/9UEQfHl0g=
github.com/youmark/pkcs8 v0.0.0-20201027041543-1326539a0a0a h1:fZHgsYlfvtyqToslyjUt3VOPF4J7aK/3MPcK7xp3PDk=
github.com/youmark/pkcs8 v0.0.0-20201027041543-1326539a0a0a/go.mod h1:ul22v+Nro/R083muKhosV54bj5niojjWZvU8xrevuH4=
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
go4.org/intern v0.0.0-20210108033219-3eb7198706b2 h1:VFTf+jjIgsldaz/Mr00VaCSswHJrI2hIjQygE/W4IMg=
go4.org/intern v0.0.0-20210108033219-3eb7198706b2/go.mod h1:vLqJ+12kCw61iCWsPto0EOHhBS+o4rO5VIucbc9g2Cc= go4.org/intern v0.0.0-20210108033219-3eb7198706b2/go.mod h1:vLqJ+12kCw61iCWsPto0EOHhBS+o4rO5VIucbc9g2Cc=
go4.org/intern v0.0.0-20211027215823-ae77deb06f29 h1:UXLjNohABv4S58tHmeuIZDO6e3mHpW2Dx33gaNt03LE=
go4.org/intern v0.0.0-20211027215823-ae77deb06f29/go.mod h1:cS2ma+47FKrLPdXFpr7CuxiTW3eyJbWew4qx0qtQWDA=
go4.org/unsafe/assume-no-moving-gc v0.0.0-20201222175341-b30ae309168e/go.mod h1:FftLjUGFEDu5k8lt0ddY+HcrH/qU/0qk+H8j9/nTl3E= go4.org/unsafe/assume-no-moving-gc v0.0.0-20201222175341-b30ae309168e/go.mod h1:FftLjUGFEDu5k8lt0ddY+HcrH/qU/0qk+H8j9/nTl3E=
go4.org/unsafe/assume-no-moving-gc v0.0.0-20201222180813-1025295fd063 h1:1tk03FUNpulq2cuWpXZWj649rwJpk0d20rxWiopKRmc=
go4.org/unsafe/assume-no-moving-gc v0.0.0-20201222180813-1025295fd063/go.mod h1:FftLjUGFEDu5k8lt0ddY+HcrH/qU/0qk+H8j9/nTl3E= go4.org/unsafe/assume-no-moving-gc v0.0.0-20201222180813-1025295fd063/go.mod h1:FftLjUGFEDu5k8lt0ddY+HcrH/qU/0qk+H8j9/nTl3E=
go4.org/unsafe/assume-no-moving-gc v0.0.0-20211027215541-db492cf91b37/go.mod h1:FftLjUGFEDu5k8lt0ddY+HcrH/qU/0qk+H8j9/nTl3E=
go4.org/unsafe/assume-no-moving-gc v0.0.0-20220617031537-928513b29760/go.mod h1:FftLjUGFEDu5k8lt0ddY+HcrH/qU/0qk+H8j9/nTl3E=
go4.org/unsafe/assume-no-moving-gc v0.0.0-20230221090011-e4bae7ad2296 h1:QJ/xcIANMLApehfgPCHnfK1hZiaMmbaTVmPv7DAoTbo=
go4.org/unsafe/assume-no-moving-gc v0.0.0-20230221090011-e4bae7ad2296/go.mod h1:FftLjUGFEDu5k8lt0ddY+HcrH/qU/0qk+H8j9/nTl3E=
golang.org/x/crypto v0.0.0-20190219172222-a4c6cb3142f2/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20190219172222-a4c6cb3142f2/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20190701094942-4def268fd1a4/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20190701094942-4def268fd1a4/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20200302210943-78000ba7a073/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20210220033148-5ea612d1eb83/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I=
golang.org/x/crypto v0.0.0-20210503195802-e9a32991a82e/go.mod h1:P+XmwS30IXTQdn5tA2iutPOUgjI07+tq3H3K9MVA1s8=
golang.org/x/crypto v0.0.0-20210513164829-c07d793c2f9a/go.mod h1:P+XmwS30IXTQdn5tA2iutPOUgjI07+tq3H3K9MVA1s8= golang.org/x/crypto v0.0.0-20210513164829-c07d793c2f9a/go.mod h1:P+XmwS30IXTQdn5tA2iutPOUgjI07+tq3H3K9MVA1s8=
golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97 h1:/UOmuWzQfxxo9UtlXMwuQU8CMgg1eZXqTRwkSQJWKOI=
golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/crypto v0.6.0 h1:qfktjS5LUO+fFKeJXZ+ikTRijMmljikvG68fpMMruSc=
golang.org/x/crypto v0.6.0/go.mod h1:OFC/31mSvZgRz0V1QTNCzfAI1aIRzbiufJtkMIlEp58=
golang.org/x/exp v0.0.0-20230519143937-03e91628a987 h1:3xJIFvzUFbu4ls0BTBYcgbCGhA63eAOEMxIHugyXJqA=
golang.org/x/exp v0.0.0-20230519143937-03e91628a987/go.mod h1:V1LtkGg67GoY2N1AnLN78QLrzxkLyJw7RJb1gzOOz9w=
golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/net v0.0.0-20181005035420-146acd28ed58/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20181005035420-146acd28ed58/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20190724013045-ca1201d0de80/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20190724013045-ca1201d0de80/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20190827160401-ba9fcec4b297/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20190923162816-aa69164e4478/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20190923162816-aa69164e4478/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20191007182048-72f939374954/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20201010224723-4f7140c49acb/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
golang.org/x/net v0.0.0-20201216054612-986b41b23924/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-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM= golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM=
golang.org/x/net v0.0.0-20210928044308-7d9f5e0b762b/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20210504132125-bbd867fde50d h1:nTDGCTeAu2LhcsHTRzjyIUbZHCJ4QePArsm27Hka0UM=
golang.org/x/net v0.0.0-20220127200216-cd36cc0744dd/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= golang.org/x/net v0.0.0-20210504132125-bbd867fde50d/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/net v0.0.0-20220923203811-8be639271d50/go.mod h1:YDH+HFinaLZZlnHAfSS6ZXJJ9M9t4Dl22yv3iI2vPwk=
golang.org/x/net v0.10.0 h1:X2//UzNDwYmtCLn7To6G58Wr6f5ahEAQgKNzv9Y951M=
golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c h1:5KslGYwFpkhGh+Q16bwMP3cOontH8FOep7tGV86Y7SQ=
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20220923202941-7f9b1623fab7/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.1.0 h1:wsuoTGHzEhffawBOhz5CYhcrV4IdKZbEyZjBMuTp12o=
golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190221075227-b4e8571b14e0/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190221075227-b4e8571b14e0/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190411185658-b44545bcd369/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190826190057-c7b8b68b1456/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190924154521-2837fb4f24fe/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190924154521-2837fb4f24fe/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191008105621-543471e840be/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200217220822-9197077df867/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200217220822-9197077df867/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200728102440-3e129f6d46b1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200728102440-3e129f6d46b1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201009025420-dfb3f7c4e634/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201118182958-a01c418693c7/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201218084310-7d0127a74742/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210110051926-789bb1bd4061/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210123111255-9b0068b26619/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-20210309040221-94ec62e08169/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-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210503173754-0981d6026fa6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c h1:F1jZWGFhYfh0Ci55sIpILtKKK8p3i2/krTr0H1rg74I=
golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220128215802-99c3d69c2c27/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw=
golang.org/x/sys v0.0.0-20220319134239-a9b59b0215f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220728004956-3c1f35247d10/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.8.0 h1:EBmGv8NaZBZTWvrbjNoL6HVt+IVy3QDQpJs7VRIw3tU=
golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
golang.org/x/text v0.9.0 h1:2sjJmO8cDvYveuX97RDLsxlyUxLl+GHoLxBiRdHllBE=
golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
golang.org/x/time v0.0.0-20191024005414-555d28b269f0 h1:/5xXl8Y5W96D+TtHSlonuFqGHIWVuyCkGJLwGh9JJFs=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20190729092621-ff9f1409240a/go.mod h1:jcCCGcm9btYwXyDqrUWc6MKQKKGJCWEQ3AfLSRIbEuI= golang.org/x/tools v0.0.0-20190729092621-ff9f1409240a/go.mod h1:jcCCGcm9btYwXyDqrUWc6MKQKKGJCWEQ3AfLSRIbEuI=
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
@@ -218,13 +228,13 @@ golang.org/x/tools v0.1.1/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE=
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.zx2c4.com/wintun v0.0.0-20230126152724-0fa3db229ce2 h1:B82qJJgjvYKsXS9jeunTOisW56dUokqW/FOteYJJ/yg= golang.zx2c4.com/wireguard v0.0.0-20210427022245-097af6e1351b/go.mod h1:a057zjmoc00UN7gVkaJt2sXVK523kMJcogDTEvPIasg=
golang.zx2c4.com/wintun v0.0.0-20230126152724-0fa3db229ce2/go.mod h1:deeaetjYA+DHMHg+sMSMI58GrEteJUUzzw7en6TJQcI= golang.zx2c4.com/wireguard v0.0.0-20210805125648-3957e9b9dd19 h1:ab2jcw2W91Rz07eHAb8Lic7sFQKO0NhBftjv6m/gL/0=
golang.zx2c4.com/wireguard v0.0.0-20230325221338-052af4a8072b h1:J1CaxgLerRR5lgx3wnr6L04cJFbWoceSK9JWBdglINo= golang.zx2c4.com/wireguard v0.0.0-20210805125648-3957e9b9dd19/go.mod h1:laHzsbfMhGSobUmruXWAyMKKHSqvIcrqZJMyHD+/3O8=
golang.zx2c4.com/wireguard v0.0.0-20230325221338-052af4a8072b/go.mod h1:tqur9LnfstdR9ep2LaJT4lFUl0EjlHtge+gAjmsHUG4= golang.zx2c4.com/wireguard/wgctrl v0.0.0-20210803171230-4253848d036c h1:ADNrRDI5NR23/TUCnEmlLZLt4u9DnZ2nwRkPrAcFvto=
golang.zx2c4.com/wireguard/wgctrl v0.0.0-20230215201556-9c5414ab4bde h1:ybF7AMzIUikL9x4LgwEmzhXtzRpKNqngme1VGDWz+Nk= golang.zx2c4.com/wireguard/wgctrl v0.0.0-20210803171230-4253848d036c/go.mod h1:+1XihzyZUBJcSc5WO9SwNA7v26puQwOEDwanaxfNXPQ=
golang.zx2c4.com/wireguard/wgctrl v0.0.0-20230215201556-9c5414ab4bde/go.mod h1:mQqgjkW8GQQcJQsbBvK890TKqUK1DfKWkuBGbOkuMHQ=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY=
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
@@ -234,10 +244,8 @@ gopkg.in/src-d/go-git.v4 v4.13.1/go.mod h1:nx5NYcxdKxq5fpltdHnPa2Exj4Sx0EclMWZQb
gopkg.in/warnings.v0 v0.1.2/go.mod h1:jksf8JmL6Qr/oQM2OXTHunEvvTAsrWBLb6OOjuVWRNI= gopkg.in/warnings.v0 v0.1.2/go.mod h1:jksf8JmL6Qr/oQM2OXTHunEvvTAsrWBLb6OOjuVWRNI=
gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gvisor.dev/gvisor v0.0.0-20221203005347-703fd9b7fbc0 h1:Wobr37noukisGxpKo5jAsLREcpj61RxrWYzD8uwveOY=
inet.af/netaddr v0.0.0-20210511181906-37180328850c/go.mod h1:z0nx+Dh+7N7CC8V5ayHtHGpZpxLQZZxkIaaz6HN65Ls= inet.af/netaddr v0.0.0-20210511181906-37180328850c/go.mod h1:z0nx+Dh+7N7CC8V5ayHtHGpZpxLQZZxkIaaz6HN65Ls=
inet.af/netaddr v0.0.0-20220811202034-502d2d690317 h1:U2fwK6P2EqmopP/hFLTOAjWTki0qgd4GMJn5X8wOleU= inet.af/netaddr v0.0.0-20210718074554-06ca8145d722 h1:Qws2rZnQudC58cIagVucPQDLmMi3kAXgxscsgD0v6DU=
inet.af/netaddr v0.0.0-20220811202034-502d2d690317/go.mod h1:OIezDfdzOgFhuw4HuWapWq2e9l0H9tK4F1j+ETRtF3k= inet.af/netaddr v0.0.0-20210718074554-06ca8145d722/go.mod h1:z0nx+Dh+7N7CC8V5ayHtHGpZpxLQZZxkIaaz6HN65Ls=

View File

@@ -1,9 +1,17 @@
// Package alpine defines a configurator to interact with the Alpine operating system.
package alpine package alpine
import ( import (
"os/user" "os/user"
) )
var _ Alpiner = (*Alpine)(nil)
type Alpiner interface {
UserCreater
VersionGetter
}
type Alpine struct { type Alpine struct {
alpineReleasePath string alpineReleasePath string
passwdPath string passwdPath string

View File

@@ -12,6 +12,10 @@ var (
ErrUserAlreadyExists = errors.New("user already exists") ErrUserAlreadyExists = errors.New("user already exists")
) )
type UserCreater interface {
CreateUser(username string, uid int) (createdUsername string, err error)
}
// CreateUser creates a user in Alpine with the given UID. // CreateUser creates a user in Alpine with the given UID.
func (a *Alpine) CreateUser(username string, uid int) (createdUsername string, err error) { func (a *Alpine) CreateUser(username string, uid int) (createdUsername string, err error) {
UIDStr := strconv.Itoa(uid) UIDStr := strconv.Itoa(uid)

View File

@@ -7,7 +7,11 @@ import (
"strings" "strings"
) )
func (a *Alpine) Version(context.Context) (version string, err error) { type VersionGetter interface {
Version(ctx context.Context) (version string, err error)
}
func (a *Alpine) Version(ctx context.Context) (version string, err error) {
file, err := os.OpenFile(a.alpineReleasePath, os.O_RDONLY, 0) file, err := os.OpenFile(a.alpineReleasePath, os.O_RDONLY, 0)
if err != nil { if err != nil {
return "", err return "", err

View File

@@ -2,6 +2,6 @@ package cli
import "context" import "context"
func (c *CLI) CI(context.Context) error { func (c *CLI) CI(context context.Context) error {
return nil return nil
} }

View File

@@ -1,5 +1,16 @@
// Package cli defines an interface CLI to run command line operations.
package cli package cli
var _ CLIer = (*CLI)(nil)
type CLIer interface {
ClientKeyFormatter
HealthChecker
OpenvpnConfigMaker
Updater
ServersFormatter
}
type CLI struct { type CLI struct {
repoServersPath string repoServersPath string
} }

View File

@@ -10,6 +10,10 @@ import (
"github.com/qdm12/gluetun/internal/configuration/sources/files" "github.com/qdm12/gluetun/internal/configuration/sources/files"
) )
type ClientKeyFormatter interface {
ClientKey(args []string) error
}
func (c *CLI) ClientKey(args []string) error { func (c *CLI) ClientKey(args []string) error {
flagSet := flag.NewFlagSet("clientkey", flag.ExitOnError) flagSet := flag.NewFlagSet("clientkey", flag.ExitOnError)
filepath := flagSet.String("path", files.OpenVPNClientKeyPath, "file path to the client.key file") filepath := flagSet.String("path", files.OpenVPNClientKeyPath, "file path to the client.key file")

View File

@@ -6,44 +6,48 @@ import (
"fmt" "fmt"
"os" "os"
"path/filepath" "path/filepath"
"strings"
"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"
"golang.org/x/text/cases"
"golang.org/x/text/language"
) )
type ServersFormatter interface {
FormatServers(args []string) error
}
var ( var (
ErrFormatNotRecognized = errors.New("format is not recognized") ErrFormatNotRecognized = errors.New("format is not recognized")
ErrProviderUnspecified = errors.New("VPN provider to format was not specified") ErrProviderUnspecified = errors.New("VPN provider to format was not specified")
ErrMultipleProvidersToFormat = errors.New("more than one VPN provider to format were specified")
) )
func addProviderFlag(flagSet *flag.FlagSet, providerToFormat map[string]*bool,
provider string, titleCaser cases.Caser) {
boolPtr, ok := providerToFormat[provider]
if !ok {
panic(fmt.Sprintf("unknown provider in format map: %s", provider))
}
flagSet.BoolVar(boolPtr, provider, false, "Format "+titleCaser.String(provider)+" servers")
}
func (c *CLI) FormatServers(args []string) error { func (c *CLI) FormatServers(args []string) error {
var format, output string var format, output string
allProviders := providers.All() var cyberghost, expressvpn, fastestvpn, hideMyAss, ipvanish, ivpn, mullvad,
providersToFormat := make(map[string]*bool, len(allProviders)) nordvpn, perfectPrivacy, pia, privado, privatevpn, protonvpn, purevpn, surfshark,
for _, provider := range allProviders { torguard, vpnUnlimited, vyprvpn, wevpn, windscribe bool
providersToFormat[provider] = new(bool)
}
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")
titleCaser := cases.Title(language.English) flagSet.BoolVar(&cyberghost, "cyberghost", false, "Format Cyberghost servers")
for _, provider := range allProviders { flagSet.BoolVar(&expressvpn, "expressvpn", false, "Format ExpressVPN servers")
addProviderFlag(flagSet, providersToFormat, provider, titleCaser) flagSet.BoolVar(&fastestvpn, "fastestvpn", false, "Format FastestVPN servers")
} flagSet.BoolVar(&hideMyAss, "hidemyass", false, "Format HideMyAss servers")
flagSet.BoolVar(&ipvanish, "ipvanish", false, "Format IpVanish servers")
flagSet.BoolVar(&ivpn, "ivpn", false, "Format IVPN servers")
flagSet.BoolVar(&mullvad, "mullvad", false, "Format Mullvad servers")
flagSet.BoolVar(&nordvpn, "nordvpn", false, "Format Nordvpn servers")
flagSet.BoolVar(&perfectPrivacy, "perfectprivacy", false, "Format Perfect Privacy servers")
flagSet.BoolVar(&pia, "pia", false, "Format Private Internet Access servers")
flagSet.BoolVar(&privado, "privado", false, "Format Privado servers")
flagSet.BoolVar(&privatevpn, "privatevpn", false, "Format Private VPN servers")
flagSet.BoolVar(&protonvpn, "protonvpn", false, "Format Protonvpn servers")
flagSet.BoolVar(&purevpn, "purevpn", false, "Format Purevpn servers")
flagSet.BoolVar(&surfshark, "surfshark", false, "Format Surfshark servers")
flagSet.BoolVar(&torguard, "torguard", false, "Format Torguard servers")
flagSet.BoolVar(&vpnUnlimited, "vpnunlimited", false, "Format VPN Unlimited servers")
flagSet.BoolVar(&vyprvpn, "vyprvpn", false, "Format Vyprvpn servers")
flagSet.BoolVar(&wevpn, "wevpn", false, "Format WeVPN servers")
flagSet.BoolVar(&windscribe, "windscribe", false, "Format Windscribe servers")
if err := flagSet.Parse(args); err != nil { if err := flagSet.Parse(args); err != nil {
return err return err
} }
@@ -52,47 +56,74 @@ func (c *CLI) FormatServers(args []string) error {
return fmt.Errorf("%w: %s", ErrFormatNotRecognized, format) return fmt.Errorf("%w: %s", ErrFormatNotRecognized, format)
} }
// Verify only one provider is set to be formatted.
var providers []string
for provider, formatPtr := range providersToFormat {
if *formatPtr {
providers = append(providers, provider)
}
}
switch len(providers) {
case 0:
return fmt.Errorf("%w", ErrProviderUnspecified)
case 1:
default:
return fmt.Errorf("%w: %d specified: %s",
ErrMultipleProvidersToFormat, len(providers),
strings.Join(providers, ", "))
}
providerToFormat := providers[0]
logger := newNoopLogger() logger := newNoopLogger()
storage, err := storage.New(logger, constants.ServersData) storage, err := storage.New(logger, constants.ServersData)
if err != nil { if err != nil {
return fmt.Errorf("creating servers storage: %w", err) return fmt.Errorf("cannot create servers storage: %w", err)
} }
currentServers := storage.GetServers()
formatted := storage.FormatToMarkdown(providerToFormat) var formatted string
switch {
case cyberghost:
formatted = currentServers.Cyberghost.ToMarkdown()
case expressvpn:
formatted = currentServers.Expressvpn.ToMarkdown()
case fastestvpn:
formatted = currentServers.Fastestvpn.ToMarkdown()
case hideMyAss:
formatted = currentServers.HideMyAss.ToMarkdown()
case ipvanish:
formatted = currentServers.Ipvanish.ToMarkdown()
case ivpn:
formatted = currentServers.Ivpn.ToMarkdown()
case mullvad:
formatted = currentServers.Mullvad.ToMarkdown()
case nordvpn:
formatted = currentServers.Nordvpn.ToMarkdown()
case perfectPrivacy:
formatted = currentServers.Perfectprivacy.ToMarkdown()
case pia:
formatted = currentServers.Pia.ToMarkdown()
case privado:
formatted = currentServers.Privado.ToMarkdown()
case privatevpn:
formatted = currentServers.Privatevpn.ToMarkdown()
case protonvpn:
formatted = currentServers.Protonvpn.ToMarkdown()
case purevpn:
formatted = currentServers.Purevpn.ToMarkdown()
case surfshark:
formatted = currentServers.Surfshark.ToMarkdown()
case torguard:
formatted = currentServers.Torguard.ToMarkdown()
case vpnUnlimited:
formatted = currentServers.VPNUnlimited.ToMarkdown()
case vyprvpn:
formatted = currentServers.Vyprvpn.ToMarkdown()
case wevpn:
formatted = currentServers.Wevpn.ToMarkdown()
case windscribe:
formatted = currentServers.Windscribe.ToMarkdown()
default:
return ErrProviderUnspecified
}
output = filepath.Clean(output) output = filepath.Clean(output)
file, err := os.OpenFile(output, os.O_TRUNC|os.O_WRONLY|os.O_CREATE, 0644) file, err := os.OpenFile(output, os.O_TRUNC|os.O_WRONLY|os.O_CREATE, 0644)
if err != nil { if err != nil {
return fmt.Errorf("opening output file: %w", err) return fmt.Errorf("cannot open output file: %w", err)
} }
_, err = fmt.Fprint(file, formatted) _, err = fmt.Fprint(file, formatted)
if err != nil { if err != nil {
_ = file.Close() _ = file.Close()
return fmt.Errorf("writing to output file: %w", err) return fmt.Errorf("cannot write to output file: %w", err)
} }
err = file.Close() err = file.Close()
if err != nil { if err != nil {
return fmt.Errorf("closing output file: %w", err) return fmt.Errorf("cannot close output file: %w", err)
} }
return nil return nil

View File

@@ -6,18 +6,21 @@ import (
"net/http" "net/http"
"time" "time"
"github.com/qdm12/gluetun/internal/configuration/sources"
"github.com/qdm12/gluetun/internal/healthcheck" "github.com/qdm12/gluetun/internal/healthcheck"
) )
func (c *CLI) HealthCheck(ctx context.Context, source Source, _ Warner) error { type HealthChecker interface {
HealthCheck(ctx context.Context, source sources.Source, warner Warner) error
}
func (c *CLI) HealthCheck(ctx context.Context, source sources.Source, warner Warner) error {
// Extract the health server port from the configuration. // Extract the health server port from the configuration.
config, err := source.ReadHealth() config, err := source.ReadHealth()
if err != nil { if err != nil {
return err return err
} }
config.SetDefaults()
err = config.Validate() err = config.Validate()
if err != nil { if err != nil {
return err return err

View File

@@ -8,9 +8,9 @@ func newNoopLogger() *noopLogger {
return new(noopLogger) return new(noopLogger)
} }
func (l *noopLogger) Debug(string) {} func (l *noopLogger) Debug(s string) {}
func (l *noopLogger) Info(string) {} func (l *noopLogger) Info(s string) {}
func (l *noopLogger) Warn(string) {} func (l *noopLogger) Warn(s string) {}
func (l *noopLogger) Error(string) {} func (l *noopLogger) Error(s string) {}
func (l *noopLogger) PatchLevel(logging.Level) {} func (l *noopLogger) PatchLevel(level logging.Level) {}
func (l *noopLogger) PatchPrefix(string) {} func (l *noopLogger) PatchPrefix(prefix string) {}

View File

@@ -1,85 +1,51 @@
package cli package cli
import ( import (
"context"
"fmt" "fmt"
"net/http"
"net/netip"
"strings" "strings"
"time" "time"
"github.com/qdm12/gluetun/internal/configuration/sources"
"github.com/qdm12/gluetun/internal/constants" "github.com/qdm12/gluetun/internal/constants"
"github.com/qdm12/gluetun/internal/openvpn/extract"
"github.com/qdm12/gluetun/internal/provider" "github.com/qdm12/gluetun/internal/provider"
"github.com/qdm12/gluetun/internal/publicip/ipinfo"
"github.com/qdm12/gluetun/internal/storage" "github.com/qdm12/gluetun/internal/storage"
"github.com/qdm12/gluetun/internal/updater/resolver"
) )
type OpenvpnConfigMaker interface {
OpenvpnConfig(logger OpenvpnConfigLogger, source sources.Source) error
}
type OpenvpnConfigLogger interface { type OpenvpnConfigLogger interface {
Info(s string) Info(s string)
Warn(s string) Warn(s string)
} }
type Unzipper interface { func (c *CLI) OpenvpnConfig(logger OpenvpnConfigLogger, source sources.Source) error {
FetchAndExtract(ctx context.Context, url string) (
contents map[string][]byte, err error)
}
type ParallelResolver interface {
Resolve(ctx context.Context, settings resolver.ParallelSettings) (
hostToIPs map[string][]netip.Addr, warnings []string, err error)
}
type IPFetcher interface {
FetchMultiInfo(ctx context.Context, ips []netip.Addr) (data []ipinfo.Response, err error)
}
type IPv6Checker interface {
IsIPv6Supported() (supported bool, err error)
}
func (c *CLI) OpenvpnConfig(logger OpenvpnConfigLogger, source Source,
ipv6Checker IPv6Checker) error {
storage, err := storage.New(logger, constants.ServersData) storage, err := storage.New(logger, constants.ServersData)
if err != nil { if err != nil {
return err return err
} }
allServers := storage.GetServers()
allSettings, err := source.Read() allSettings, err := source.Read()
if err != nil { if err != nil {
return err return err
} }
ipv6Supported, err := ipv6Checker.IsIPv6Supported() if err = allSettings.Validate(allServers); err != nil {
return err
}
providerConf := provider.New(*allSettings.VPN.Provider.Name, allServers, time.Now)
connection, err := providerConf.GetConnection(allSettings.VPN.Provider.ServerSelection)
if err != nil { if err != nil {
return fmt.Errorf("checking for IPv6 support: %w", err) return err
} }
lines, err := providerConf.BuildConf(connection, allSettings.VPN.OpenVPN)
if err = allSettings.Validate(storage, ipv6Supported); err != nil {
return fmt.Errorf("validating settings: %w", err)
}
// Unused by this CLI command
unzipper := (Unzipper)(nil)
client := (*http.Client)(nil)
warner := (Warner)(nil)
parallelResolver := (ParallelResolver)(nil)
ipFetcher := (IPFetcher)(nil)
openvpnFileExtractor := extract.New()
providers := provider.NewProviders(storage, time.Now, warner, client,
unzipper, parallelResolver, ipFetcher, openvpnFileExtractor)
providerConf := providers.Get(*allSettings.VPN.Provider.Name)
connection, err := providerConf.GetConnection(
allSettings.VPN.Provider.ServerSelection, ipv6Supported)
if err != nil { if err != nil {
return err return err
} }
lines := providerConf.OpenVPNConfig(connection,
allSettings.VPN.OpenVPN, ipv6Supported)
fmt.Println(strings.Join(lines, "\n")) fmt.Println(strings.Join(lines, "\n"))
return nil return nil
} }

View File

@@ -2,48 +2,50 @@ package cli
import ( import (
"context" "context"
"encoding/json"
"errors" "errors"
"flag" "flag"
"fmt" "fmt"
"net"
"net/http" "net/http"
"os"
"strings" "strings"
"time" "time"
"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/openvpn/extract"
"github.com/qdm12/gluetun/internal/provider"
"github.com/qdm12/gluetun/internal/publicip/ipinfo"
"github.com/qdm12/gluetun/internal/storage" "github.com/qdm12/gluetun/internal/storage"
"github.com/qdm12/gluetun/internal/updater" "github.com/qdm12/gluetun/internal/updater"
"github.com/qdm12/gluetun/internal/updater/resolver"
"github.com/qdm12/gluetun/internal/updater/unzip"
) )
var ( var (
ErrModeUnspecified = errors.New("at least one of -enduser or -maintainer must be specified") ErrModeUnspecified = errors.New("at least one of -enduser or -maintainer must be specified")
ErrDNSAddress = errors.New("DNS address is not valid")
ErrNoProviderSpecified = errors.New("no provider was specified") ErrNoProviderSpecified = errors.New("no provider was specified")
) )
type Updater interface {
Update(ctx context.Context, args []string, logger UpdaterLogger) error
}
type UpdaterLogger interface { type UpdaterLogger interface {
Info(s string) Info(s string)
Warn(s string) Warn(s string)
Error(s string) Error(s string)
} }
func boolPtr(b bool) *bool { return &b }
func (c *CLI) Update(ctx context.Context, args []string, logger UpdaterLogger) error { func (c *CLI) Update(ctx context.Context, args []string, logger UpdaterLogger) error {
options := settings.Updater{} options := settings.Updater{CLI: boolPtr(true)}
var endUserMode, maintainerMode, updateAll bool var endUserMode, maintainerMode, updateAll bool
var csvProviders string var dnsAddress, csvProviders string
flagSet := flag.NewFlagSet("update", flag.ExitOnError) flagSet := flag.NewFlagSet("update", flag.ExitOnError)
flagSet.BoolVar(&endUserMode, "enduser", false, "Write results to /gluetun/servers.json (for end users)") flagSet.BoolVar(&endUserMode, "enduser", false, "Write results to /gluetun/servers.json (for end users)")
flagSet.BoolVar(&maintainerMode, "maintainer", false, flagSet.BoolVar(&maintainerMode, "maintainer", false,
"Write results to ./internal/storage/servers.json to modify the program (for maintainers)") "Write results to ./internal/storage/servers.json to modify the program (for maintainers)")
flagSet.StringVar(&options.DNSAddress, "dns", "8.8.8.8", "DNS resolver address to use") flagSet.StringVar(&dnsAddress, "dns", "8.8.8.8", "DNS resolver address to use")
const defaultMinRatio = 0.8
flagSet.Float64Var(&options.MinRatio, "minratio", defaultMinRatio,
"Minimum ratio of servers to find for the update to succeed")
flagSet.BoolVar(&updateAll, "all", false, "Update servers for all VPN providers") flagSet.BoolVar(&updateAll, "all", false, "Update servers for all VPN providers")
flagSet.StringVar(&csvProviders, "providers", "", "CSV string of VPN providers to update server data for") flagSet.StringVar(&csvProviders, "providers", "", "CSV string of VPN providers to update server data for")
if err := flagSet.Parse(args); err != nil { if err := flagSet.Parse(args); err != nil {
@@ -51,14 +53,24 @@ func (c *CLI) Update(ctx context.Context, args []string, logger UpdaterLogger) e
} }
if !endUserMode && !maintainerMode { if !endUserMode && !maintainerMode {
return fmt.Errorf("%w", ErrModeUnspecified) return ErrModeUnspecified
}
options.DNSAddress = net.ParseIP(dnsAddress)
if options.DNSAddress == nil {
return fmt.Errorf("%w: %s", ErrDNSAddress, dnsAddress)
} }
if updateAll { if updateAll {
options.Providers = providers.All() for _, provider := range constants.AllProviders() {
if provider == constants.Custom {
continue
}
options.Providers = append(options.Providers, provider)
}
} else { } else {
if csvProviders == "" { if csvProviders == "" {
return fmt.Errorf("%w", ErrNoProviderSpecified) return ErrNoProviderSpecified
} }
options.Providers = strings.Split(csvProviders, ",") options.Providers = strings.Split(csvProviders, ",")
} }
@@ -70,33 +82,48 @@ func (c *CLI) Update(ctx context.Context, args []string, logger UpdaterLogger) e
return fmt.Errorf("options validation failed: %w", err) return fmt.Errorf("options validation failed: %w", err)
} }
storage, err := storage.New(logger, constants.ServersData)
if err != nil {
return fmt.Errorf("creating servers storage: %w", err)
}
const clientTimeout = 10 * time.Second const clientTimeout = 10 * time.Second
httpClient := &http.Client{Timeout: clientTimeout} httpClient := &http.Client{Timeout: clientTimeout}
unzipper := unzip.New(httpClient)
parallelResolver := resolver.NewParallelResolver(options.DNSAddress)
ipFetcher := ipinfo.New(httpClient)
openvpnFileExtractor := extract.New()
providers := provider.NewProviders(storage, time.Now, logger, httpClient, storage, err := storage.New(logger, constants.ServersData)
unzipper, parallelResolver, ipFetcher, openvpnFileExtractor)
updater := updater.New(httpClient, storage, providers, logger)
err = updater.UpdateServers(ctx, options.Providers, options.MinRatio)
if err != nil { if err != nil {
return fmt.Errorf("updating server information: %w", err) return fmt.Errorf("cannot create servers storage: %w", err)
}
currentServers := storage.GetServers()
updater := updater.New(options, httpClient, currentServers, logger)
allServers, err := updater.UpdateServers(ctx)
if err != nil {
return fmt.Errorf("cannot update server information: %w", err)
}
if endUserMode {
if err := storage.FlushToFile(allServers); err != nil {
return fmt.Errorf("cannot write updated information to file: %w", err)
}
} }
if maintainerMode { if maintainerMode {
err := storage.FlushToFile(c.repoServersPath) if err := writeToEmbeddedJSON(c.repoServersPath, allServers); err != nil {
if err != nil { return fmt.Errorf("cannot write updated information to file: %w", err)
return fmt.Errorf("writing servers data to embedded JSON file: %w", err)
} }
} }
return nil return nil
} }
func writeToEmbeddedJSON(repoServersPath string,
allServers models.AllServers) error {
const perms = 0600
f, err := os.OpenFile(repoServersPath,
os.O_TRUNC|os.O_WRONLY|os.O_CREATE, perms)
if err != nil {
return err
}
defer f.Close()
encoder := json.NewEncoder(f)
encoder.SetIndent("", " ")
return encoder.Encode(allServers)
}

View File

@@ -2,7 +2,7 @@ package settings
import ( import (
"fmt" "fmt"
"net/netip" "net"
"github.com/qdm12/gluetun/internal/configuration/settings/helpers" "github.com/qdm12/gluetun/internal/configuration/settings/helpers"
"github.com/qdm12/gotree" "github.com/qdm12/gotree"
@@ -13,9 +13,9 @@ type DNS struct {
// ServerAddress is the DNS server to use inside // ServerAddress is the DNS server to use inside
// the Go program and for the system. // the Go program and for the system.
// It defaults to '127.0.0.1' to be used with the // It defaults to '127.0.0.1' to be used with the
// DoT server. It cannot be the zero value in the internal // DoT server. It cannot be nil in the internal
// state. // state.
ServerAddress netip.Addr ServerAddress net.IP
// KeepNameserver is true if the Docker DNS server // KeepNameserver is true if the Docker DNS server
// found in /etc/resolv.conf should be kept. // found in /etc/resolv.conf should be kept.
// Note settings this to true will go around the // Note settings this to true will go around the
@@ -31,7 +31,7 @@ type DNS struct {
func (d DNS) validate() (err error) { func (d DNS) validate() (err error) {
err = d.DoT.validate() err = d.DoT.validate()
if err != nil { if err != nil {
return fmt.Errorf("validating DoT settings: %w", err) return fmt.Errorf("failed validating DoT settings: %w", err)
} }
return nil return nil
@@ -39,8 +39,8 @@ func (d DNS) validate() (err error) {
func (d *DNS) Copy() (copied DNS) { func (d *DNS) Copy() (copied DNS) {
return DNS{ return DNS{
ServerAddress: d.ServerAddress, ServerAddress: helpers.CopyIP(d.ServerAddress),
KeepNameserver: helpers.CopyPointer(d.KeepNameserver), KeepNameserver: helpers.CopyBoolPtr(d.KeepNameserver),
DoT: d.DoT.copy(), DoT: d.DoT.copy(),
} }
} }
@@ -49,7 +49,7 @@ func (d *DNS) Copy() (copied DNS) {
// unset field of the receiver settings object. // unset field of the receiver settings object.
func (d *DNS) mergeWith(other DNS) { func (d *DNS) mergeWith(other DNS) {
d.ServerAddress = helpers.MergeWithIP(d.ServerAddress, other.ServerAddress) d.ServerAddress = helpers.MergeWithIP(d.ServerAddress, other.ServerAddress)
d.KeepNameserver = helpers.MergeWithPointer(d.KeepNameserver, other.KeepNameserver) d.KeepNameserver = helpers.MergeWithBool(d.KeepNameserver, other.KeepNameserver)
d.DoT.mergeWith(other.DoT) d.DoT.mergeWith(other.DoT)
} }
@@ -58,14 +58,14 @@ func (d *DNS) mergeWith(other DNS) {
// settings. // settings.
func (d *DNS) overrideWith(other DNS) { func (d *DNS) overrideWith(other DNS) {
d.ServerAddress = helpers.OverrideWithIP(d.ServerAddress, other.ServerAddress) d.ServerAddress = helpers.OverrideWithIP(d.ServerAddress, other.ServerAddress)
d.KeepNameserver = helpers.OverrideWithPointer(d.KeepNameserver, other.KeepNameserver) d.KeepNameserver = helpers.OverrideWithBool(d.KeepNameserver, other.KeepNameserver)
d.DoT.overrideWith(other.DoT) d.DoT.overrideWith(other.DoT)
} }
func (d *DNS) setDefaults() { func (d *DNS) setDefaults() {
localhost := netip.AddrFrom4([4]byte{127, 0, 0, 1}) localhost := net.IPv4(127, 0, 0, 1) //nolint:gomnd
d.ServerAddress = helpers.DefaultIP(d.ServerAddress, localhost) d.ServerAddress = helpers.DefaultIP(d.ServerAddress, localhost)
d.KeepNameserver = helpers.DefaultPointer(d.KeepNameserver, false) d.KeepNameserver = helpers.DefaultBool(d.KeepNameserver, false)
d.DoT.setDefaults() d.DoT.setDefaults()
} }

View File

@@ -3,12 +3,12 @@ package settings
import ( import (
"errors" "errors"
"fmt" "fmt"
"net/netip"
"regexp" "regexp"
"github.com/qdm12/dns/pkg/blacklist" "github.com/qdm12/dns/pkg/blacklist"
"github.com/qdm12/gluetun/internal/configuration/settings/helpers" "github.com/qdm12/gluetun/internal/configuration/settings/helpers"
"github.com/qdm12/gotree" "github.com/qdm12/gotree"
"inet.af/netaddr"
) )
// DNSBlacklist is settings for the DNS blacklist building. // DNSBlacklist is settings for the DNS blacklist building.
@@ -18,14 +18,14 @@ type DNSBlacklist struct {
BlockSurveillance *bool BlockSurveillance *bool
AllowedHosts []string AllowedHosts []string
AddBlockedHosts []string AddBlockedHosts []string
AddBlockedIPs []netip.Addr AddBlockedIPs []netaddr.IP
AddBlockedIPPrefixes []netip.Prefix AddBlockedIPPrefixes []netaddr.IPPrefix
} }
func (b *DNSBlacklist) setDefaults() { func (b *DNSBlacklist) setDefaults() {
b.BlockMalicious = helpers.DefaultPointer(b.BlockMalicious, true) b.BlockMalicious = helpers.DefaultBool(b.BlockMalicious, true)
b.BlockAds = helpers.DefaultPointer(b.BlockAds, false) b.BlockAds = helpers.DefaultBool(b.BlockAds, false)
b.BlockSurveillance = helpers.DefaultPointer(b.BlockSurveillance, true) b.BlockSurveillance = helpers.DefaultBool(b.BlockSurveillance, true)
} }
var hostRegex = regexp.MustCompile(`^([a-zA-Z0-9]|[a-zA-Z0-9_][a-zA-Z0-9\-_]{0,61}[a-zA-Z0-9_])(\.([a-zA-Z0-9]|[a-zA-Z0-9_][a-zA-Z0-9\-_]{0,61}[a-zA-Z0-9]))*$`) //nolint:lll var hostRegex = regexp.MustCompile(`^([a-zA-Z0-9]|[a-zA-Z0-9_][a-zA-Z0-9\-_]{0,61}[a-zA-Z0-9_])(\.([a-zA-Z0-9]|[a-zA-Z0-9_][a-zA-Z0-9\-_]{0,61}[a-zA-Z0-9]))*$`) //nolint:lll
@@ -53,34 +53,34 @@ func (b DNSBlacklist) validate() (err error) {
func (b DNSBlacklist) copy() (copied DNSBlacklist) { func (b DNSBlacklist) copy() (copied DNSBlacklist) {
return DNSBlacklist{ return DNSBlacklist{
BlockMalicious: helpers.CopyPointer(b.BlockMalicious), BlockMalicious: helpers.CopyBoolPtr(b.BlockMalicious),
BlockAds: helpers.CopyPointer(b.BlockAds), BlockAds: helpers.CopyBoolPtr(b.BlockAds),
BlockSurveillance: helpers.CopyPointer(b.BlockSurveillance), BlockSurveillance: helpers.CopyBoolPtr(b.BlockSurveillance),
AllowedHosts: helpers.CopySlice(b.AllowedHosts), AllowedHosts: helpers.CopyStringSlice(b.AllowedHosts),
AddBlockedHosts: helpers.CopySlice(b.AddBlockedHosts), AddBlockedHosts: helpers.CopyStringSlice(b.AddBlockedHosts),
AddBlockedIPs: helpers.CopySlice(b.AddBlockedIPs), AddBlockedIPs: helpers.CopyNetaddrIPsSlice(b.AddBlockedIPs),
AddBlockedIPPrefixes: helpers.CopySlice(b.AddBlockedIPPrefixes), AddBlockedIPPrefixes: helpers.CopyIPPrefixSlice(b.AddBlockedIPPrefixes),
} }
} }
func (b *DNSBlacklist) mergeWith(other DNSBlacklist) { func (b *DNSBlacklist) mergeWith(other DNSBlacklist) {
b.BlockMalicious = helpers.MergeWithPointer(b.BlockMalicious, other.BlockMalicious) b.BlockMalicious = helpers.MergeWithBool(b.BlockMalicious, other.BlockMalicious)
b.BlockAds = helpers.MergeWithPointer(b.BlockAds, other.BlockAds) b.BlockAds = helpers.MergeWithBool(b.BlockAds, other.BlockAds)
b.BlockSurveillance = helpers.MergeWithPointer(b.BlockSurveillance, other.BlockSurveillance) b.BlockSurveillance = helpers.MergeWithBool(b.BlockSurveillance, other.BlockSurveillance)
b.AllowedHosts = helpers.MergeSlices(b.AllowedHosts, other.AllowedHosts) b.AllowedHosts = helpers.MergeStringSlices(b.AllowedHosts, other.AllowedHosts)
b.AddBlockedHosts = helpers.MergeSlices(b.AddBlockedHosts, other.AddBlockedHosts) b.AddBlockedHosts = helpers.MergeStringSlices(b.AddBlockedHosts, other.AddBlockedHosts)
b.AddBlockedIPs = helpers.MergeSlices(b.AddBlockedIPs, other.AddBlockedIPs) b.AddBlockedIPs = helpers.MergeNetaddrIPsSlices(b.AddBlockedIPs, other.AddBlockedIPs)
b.AddBlockedIPPrefixes = helpers.MergeSlices(b.AddBlockedIPPrefixes, other.AddBlockedIPPrefixes) b.AddBlockedIPPrefixes = helpers.MergeIPPrefixesSlices(b.AddBlockedIPPrefixes, other.AddBlockedIPPrefixes)
} }
func (b *DNSBlacklist) overrideWith(other DNSBlacklist) { func (b *DNSBlacklist) overrideWith(other DNSBlacklist) {
b.BlockMalicious = helpers.OverrideWithPointer(b.BlockMalicious, other.BlockMalicious) b.BlockMalicious = helpers.OverrideWithBool(b.BlockMalicious, other.BlockMalicious)
b.BlockAds = helpers.OverrideWithPointer(b.BlockAds, other.BlockAds) b.BlockAds = helpers.OverrideWithBool(b.BlockAds, other.BlockAds)
b.BlockSurveillance = helpers.OverrideWithPointer(b.BlockSurveillance, other.BlockSurveillance) b.BlockSurveillance = helpers.OverrideWithBool(b.BlockSurveillance, other.BlockSurveillance)
b.AllowedHosts = helpers.OverrideWithSlice(b.AllowedHosts, other.AllowedHosts) b.AllowedHosts = helpers.OverrideWithStringSlice(b.AllowedHosts, other.AllowedHosts)
b.AddBlockedHosts = helpers.OverrideWithSlice(b.AddBlockedHosts, other.AddBlockedHosts) b.AddBlockedHosts = helpers.OverrideWithStringSlice(b.AddBlockedHosts, other.AddBlockedHosts)
b.AddBlockedIPs = helpers.OverrideWithSlice(b.AddBlockedIPs, other.AddBlockedIPs) b.AddBlockedIPs = helpers.OverrideWithNetaddrIPsSlice(b.AddBlockedIPs, other.AddBlockedIPs)
b.AddBlockedIPPrefixes = helpers.OverrideWithSlice(b.AddBlockedIPPrefixes, other.AddBlockedIPPrefixes) b.AddBlockedIPPrefixes = helpers.OverrideWithIPPrefixesSlice(b.AddBlockedIPPrefixes, other.AddBlockedIPPrefixes)
} }
func (b DNSBlacklist) ToBlacklistFormat() (settings blacklist.BuilderSettings, err error) { func (b DNSBlacklist) ToBlacklistFormat() (settings blacklist.BuilderSettings, err error) {
@@ -90,8 +90,8 @@ func (b DNSBlacklist) ToBlacklistFormat() (settings blacklist.BuilderSettings, e
BlockSurveillance: *b.BlockSurveillance, BlockSurveillance: *b.BlockSurveillance,
AllowedHosts: b.AllowedHosts, AllowedHosts: b.AllowedHosts,
AddBlockedHosts: b.AddBlockedHosts, AddBlockedHosts: b.AddBlockedHosts,
AddBlockedIPs: netipAddressesToNetaddrIPs(b.AddBlockedIPs), AddBlockedIPs: b.AddBlockedIPs,
AddBlockedIPPrefixes: netipPrefixesToNetaddrIPPrefixes(b.AddBlockedIPPrefixes), AddBlockedIPPrefixes: b.AddBlockedIPPrefixes,
}, nil }, nil
} }

View File

@@ -54,8 +54,8 @@ func (d DoT) validate() (err error) {
func (d *DoT) copy() (copied DoT) { func (d *DoT) copy() (copied DoT) {
return DoT{ return DoT{
Enabled: helpers.CopyPointer(d.Enabled), Enabled: helpers.CopyBoolPtr(d.Enabled),
UpdatePeriod: helpers.CopyPointer(d.UpdatePeriod), UpdatePeriod: helpers.CopyDurationPtr(d.UpdatePeriod),
Unbound: d.Unbound.copy(), Unbound: d.Unbound.copy(),
Blacklist: d.Blacklist.copy(), Blacklist: d.Blacklist.copy(),
} }
@@ -64,8 +64,8 @@ func (d *DoT) copy() (copied DoT) {
// mergeWith merges the other settings into any // mergeWith merges the other settings into any
// unset field of the receiver settings object. // unset field of the receiver settings object.
func (d *DoT) mergeWith(other DoT) { func (d *DoT) mergeWith(other DoT) {
d.Enabled = helpers.MergeWithPointer(d.Enabled, other.Enabled) d.Enabled = helpers.MergeWithBool(d.Enabled, other.Enabled)
d.UpdatePeriod = helpers.MergeWithPointer(d.UpdatePeriod, other.UpdatePeriod) d.UpdatePeriod = helpers.MergeWithDuration(d.UpdatePeriod, other.UpdatePeriod)
d.Unbound.mergeWith(other.Unbound) d.Unbound.mergeWith(other.Unbound)
d.Blacklist.mergeWith(other.Blacklist) d.Blacklist.mergeWith(other.Blacklist)
} }
@@ -74,16 +74,16 @@ func (d *DoT) mergeWith(other DoT) {
// settings object with any field set in the other // settings object with any field set in the other
// settings. // settings.
func (d *DoT) overrideWith(other DoT) { func (d *DoT) overrideWith(other DoT) {
d.Enabled = helpers.OverrideWithPointer(d.Enabled, other.Enabled) d.Enabled = helpers.OverrideWithBool(d.Enabled, other.Enabled)
d.UpdatePeriod = helpers.OverrideWithPointer(d.UpdatePeriod, other.UpdatePeriod) d.UpdatePeriod = helpers.OverrideWithDuration(d.UpdatePeriod, other.UpdatePeriod)
d.Unbound.overrideWith(other.Unbound) d.Unbound.overrideWith(other.Unbound)
d.Blacklist.overrideWith(other.Blacklist) d.Blacklist.overrideWith(other.Blacklist)
} }
func (d *DoT) setDefaults() { func (d *DoT) setDefaults() {
d.Enabled = helpers.DefaultPointer(d.Enabled, true) d.Enabled = helpers.DefaultBool(d.Enabled, true)
const defaultUpdatePeriod = 24 * time.Hour const defaultUpdatePeriod = 24 * time.Hour
d.UpdatePeriod = helpers.DefaultPointer(d.UpdatePeriod, defaultUpdatePeriod) d.UpdatePeriod = helpers.DefaultDuration(d.UpdatePeriod, defaultUpdatePeriod)
d.Unbound.setDefaults() d.Unbound.setDefaults()
d.Blacklist.setDefaults() d.Blacklist.setDefaults()
} }

View File

@@ -10,14 +10,12 @@ var (
ErrFirewallZeroPort = errors.New("cannot have a zero port to block") ErrFirewallZeroPort = errors.New("cannot have a zero port to block")
ErrHostnameNotValid = errors.New("the hostname specified is not valid") ErrHostnameNotValid = errors.New("the hostname specified is not valid")
ErrISPNotValid = errors.New("the ISP specified is not valid") ErrISPNotValid = errors.New("the ISP specified is not valid")
ErrMinRatioNotValid = errors.New("minimum ratio is not valid")
ErrMissingValue = errors.New("missing value") ErrMissingValue = errors.New("missing value")
ErrNameNotValid = errors.New("the server name specified is not valid") ErrNameNotValid = errors.New("the server name specified is not valid")
ErrOpenVPNClientKeyMissing = errors.New("client key is missing") ErrOpenVPNClientKeyMissing = errors.New("client key is missing")
ErrOpenVPNCustomPortNotAllowed = errors.New("custom endpoint port is not allowed") ErrOpenVPNCustomPortNotAllowed = errors.New("custom endpoint port is not allowed")
ErrOpenVPNEncryptionPresetNotValid = errors.New("PIA encryption preset is not valid") ErrOpenVPNEncryptionPresetNotValid = errors.New("PIA encryption preset is not valid")
ErrOpenVPNInterfaceNotValid = errors.New("interface name is not valid") ErrOpenVPNInterfaceNotValid = errors.New("interface name is not valid")
ErrOpenVPNKeyPassphraseIsEmpty = errors.New("key passphrase is empty")
ErrOpenVPNMSSFixIsTooHigh = errors.New("mssfix option value is too high") ErrOpenVPNMSSFixIsTooHigh = errors.New("mssfix option value is too high")
ErrOpenVPNPasswordIsEmpty = errors.New("password is empty") ErrOpenVPNPasswordIsEmpty = errors.New("password is empty")
ErrOpenVPNTCPNotSupported = errors.New("TCP protocol is not supported") ErrOpenVPNTCPNotSupported = errors.New("TCP protocol is not supported")
@@ -37,13 +35,10 @@ var (
ErrWireguardEndpointIPNotSet = errors.New("endpoint IP is not set") ErrWireguardEndpointIPNotSet = errors.New("endpoint IP is not set")
ErrWireguardEndpointPortNotAllowed = errors.New("endpoint port is not allowed") ErrWireguardEndpointPortNotAllowed = errors.New("endpoint port is not allowed")
ErrWireguardEndpointPortNotSet = errors.New("endpoint port is not set") ErrWireguardEndpointPortNotSet = errors.New("endpoint port is not set")
ErrWireguardEndpointPortSet = errors.New("endpoint port is set")
ErrWireguardInterfaceAddressNotSet = errors.New("interface address is not set") ErrWireguardInterfaceAddressNotSet = errors.New("interface address is not set")
ErrWireguardInterfaceAddressIPv6 = errors.New("interface address is IPv6 but IPv6 is not supported")
ErrWireguardInterfaceNotValid = errors.New("interface name is not valid") ErrWireguardInterfaceNotValid = errors.New("interface name is not valid")
ErrWireguardPreSharedKeyNotSet = errors.New("pre-shared key is not set") ErrWireguardPreSharedKeyNotSet = errors.New("pre-shared key is not set")
ErrWireguardPrivateKeyNotSet = errors.New("private key is not set") ErrWireguardPrivateKeyNotSet = errors.New("private key is not set")
ErrWireguardPublicKeyNotSet = errors.New("public key is not set") ErrWireguardPublicKeyNotSet = errors.New("public key is not set")
ErrWireguardPublicKeyNotValid = errors.New("public key is not valid") ErrWireguardPublicKeyNotValid = errors.New("public key is not valid")
ErrWireguardImplementationNotValid = errors.New("implementation is not valid")
) )

View File

@@ -2,7 +2,7 @@ package settings
import ( import (
"fmt" "fmt"
"net/netip" "net"
"github.com/qdm12/gluetun/internal/configuration/settings/helpers" "github.com/qdm12/gluetun/internal/configuration/settings/helpers"
"github.com/qdm12/gotree" "github.com/qdm12/gotree"
@@ -12,7 +12,7 @@ import (
type Firewall struct { type Firewall struct {
VPNInputPorts []uint16 VPNInputPorts []uint16
InputPorts []uint16 InputPorts []uint16
OutboundSubnets []netip.Prefix OutboundSubnets []net.IPNet
Enabled *bool Enabled *bool
Debug *bool Debug *bool
} }
@@ -40,11 +40,11 @@ func hasZeroPort(ports []uint16) (has bool) {
func (f *Firewall) copy() (copied Firewall) { func (f *Firewall) copy() (copied Firewall) {
return Firewall{ return Firewall{
VPNInputPorts: helpers.CopySlice(f.VPNInputPorts), VPNInputPorts: helpers.CopyUint16Slice(f.VPNInputPorts),
InputPorts: helpers.CopySlice(f.InputPorts), InputPorts: helpers.CopyUint16Slice(f.InputPorts),
OutboundSubnets: helpers.CopySlice(f.OutboundSubnets), OutboundSubnets: helpers.CopyIPNetSlice(f.OutboundSubnets),
Enabled: helpers.CopyPointer(f.Enabled), Enabled: helpers.CopyBoolPtr(f.Enabled),
Debug: helpers.CopyPointer(f.Debug), Debug: helpers.CopyBoolPtr(f.Debug),
} }
} }
@@ -53,27 +53,27 @@ func (f *Firewall) copy() (copied Firewall) {
// It merges values of slices together, even if they // It merges values of slices together, even if they
// are set in the receiver settings. // are set in the receiver settings.
func (f *Firewall) mergeWith(other Firewall) { func (f *Firewall) mergeWith(other Firewall) {
f.VPNInputPorts = helpers.MergeSlices(f.VPNInputPorts, other.VPNInputPorts) f.VPNInputPorts = helpers.MergeUint16Slices(f.VPNInputPorts, other.VPNInputPorts)
f.InputPorts = helpers.MergeSlices(f.InputPorts, other.InputPorts) f.InputPorts = helpers.MergeUint16Slices(f.InputPorts, other.InputPorts)
f.OutboundSubnets = helpers.MergeSlices(f.OutboundSubnets, other.OutboundSubnets) f.OutboundSubnets = helpers.MergeIPNetsSlices(f.OutboundSubnets, other.OutboundSubnets)
f.Enabled = helpers.MergeWithPointer(f.Enabled, other.Enabled) f.Enabled = helpers.MergeWithBool(f.Enabled, other.Enabled)
f.Debug = helpers.MergeWithPointer(f.Debug, other.Debug) f.Debug = helpers.MergeWithBool(f.Debug, other.Debug)
} }
// overrideWith overrides fields of the receiver // overrideWith overrides fields of the receiver
// settings object with any field set in the other // settings object with any field set in the other
// settings. // settings.
func (f *Firewall) overrideWith(other Firewall) { func (f *Firewall) overrideWith(other Firewall) {
f.VPNInputPorts = helpers.OverrideWithSlice(f.VPNInputPorts, other.VPNInputPorts) f.VPNInputPorts = helpers.OverrideWithUint16Slice(f.VPNInputPorts, other.VPNInputPorts)
f.InputPorts = helpers.OverrideWithSlice(f.InputPorts, other.InputPorts) f.InputPorts = helpers.OverrideWithUint16Slice(f.InputPorts, other.InputPorts)
f.OutboundSubnets = helpers.OverrideWithSlice(f.OutboundSubnets, other.OutboundSubnets) f.OutboundSubnets = helpers.OverrideWithIPNetsSlice(f.OutboundSubnets, other.OutboundSubnets)
f.Enabled = helpers.OverrideWithPointer(f.Enabled, other.Enabled) f.Enabled = helpers.OverrideWithBool(f.Enabled, other.Enabled)
f.Debug = helpers.OverrideWithPointer(f.Debug, other.Debug) f.Debug = helpers.OverrideWithBool(f.Debug, other.Debug)
} }
func (f *Firewall) setDefaults() { func (f *Firewall) setDefaults() {
f.Enabled = helpers.DefaultPointer(f.Enabled, true) f.Enabled = helpers.DefaultBool(f.Enabled, true)
f.Debug = helpers.DefaultPointer(f.Debug, false) f.Debug = helpers.DefaultBool(f.Debug, false)
} }
func (f Firewall) String() string { func (f Firewall) String() string {
@@ -109,8 +109,7 @@ func (f Firewall) toLinesNode() (node *gotree.Node) {
if len(f.OutboundSubnets) > 0 { if len(f.OutboundSubnets) > 0 {
outboundSubnets := node.Appendf("Outbound subnets:") outboundSubnets := node.Appendf("Outbound subnets:")
for _, subnet := range f.OutboundSubnets { for _, subnet := range f.OutboundSubnets {
subnet := subnet outboundSubnets.Appendf("%s", subnet)
outboundSubnets.Appendf("%s", &subnet)
} }
} }

View File

@@ -3,7 +3,6 @@ package settings
import ( import (
"fmt" "fmt"
"os" "os"
"time"
"github.com/qdm12/gluetun/internal/configuration/settings/helpers" "github.com/qdm12/gluetun/internal/configuration/settings/helpers"
"github.com/qdm12/gotree" "github.com/qdm12/gotree"
@@ -16,23 +15,11 @@ type Health struct {
// for the health check server. // for the health check server.
// It cannot be the empty string in the internal state. // It cannot be the empty string in the internal state.
ServerAddress string ServerAddress string
// ReadHeaderTimeout is the HTTP server header read timeout
// duration of the HTTP server. It defaults to 100 milliseconds.
ReadHeaderTimeout time.Duration
// ReadTimeout is the HTTP read timeout duration of the
// HTTP server. It defaults to 500 milliseconds.
ReadTimeout time.Duration
// TargetAddress is the address (host or host:port) // TargetAddress is the address (host or host:port)
// to TCP dial to periodically for the health check. // to TCP dial to periodically for the health check.
// It cannot be the empty string in the internal state. // It cannot be the empty string in the internal state.
TargetAddress string TargetAddress string
// SuccessWait is the duration to wait to re-run the VPN HealthyWait
// healthcheck after a successful healthcheck.
// It defaults to 5 seconds and cannot be zero in
// the internal state.
SuccessWait time.Duration
// VPN has health settings specific to the VPN loop.
VPN HealthyWait
} }
func (h Health) Validate() (err error) { func (h Health) Validate() (err error) {
@@ -53,12 +40,9 @@ func (h Health) Validate() (err error) {
func (h *Health) copy() (copied Health) { func (h *Health) copy() (copied Health) {
return Health{ return Health{
ServerAddress: h.ServerAddress, ServerAddress: h.ServerAddress,
ReadHeaderTimeout: h.ReadHeaderTimeout, TargetAddress: h.TargetAddress,
ReadTimeout: h.ReadTimeout, VPN: h.VPN.copy(),
TargetAddress: h.TargetAddress,
SuccessWait: h.SuccessWait,
VPN: h.VPN.copy(),
} }
} }
@@ -66,10 +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.ReadHeaderTimeout = helpers.MergeWithNumber(h.ReadHeaderTimeout, other.ReadHeaderTimeout)
h.ReadTimeout = helpers.MergeWithNumber(h.ReadTimeout, other.ReadTimeout)
h.TargetAddress = helpers.MergeWithString(h.TargetAddress, other.TargetAddress) h.TargetAddress = helpers.MergeWithString(h.TargetAddress, other.TargetAddress)
h.SuccessWait = helpers.MergeWithNumber(h.SuccessWait, other.SuccessWait)
h.VPN.mergeWith(other.VPN) h.VPN.mergeWith(other.VPN)
} }
@@ -78,22 +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.ReadHeaderTimeout = helpers.OverrideWithNumber(h.ReadHeaderTimeout, other.ReadHeaderTimeout)
h.ReadTimeout = helpers.OverrideWithNumber(h.ReadTimeout, other.ReadTimeout)
h.TargetAddress = helpers.OverrideWithString(h.TargetAddress, other.TargetAddress) h.TargetAddress = helpers.OverrideWithString(h.TargetAddress, other.TargetAddress)
h.SuccessWait = helpers.OverrideWithNumber(h.SuccessWait, other.SuccessWait)
h.VPN.overrideWith(other.VPN) h.VPN.overrideWith(other.VPN)
} }
func (h *Health) SetDefaults() { func (h *Health) SetDefaults() {
h.ServerAddress = helpers.DefaultString(h.ServerAddress, "127.0.0.1:9999") h.ServerAddress = helpers.DefaultString(h.ServerAddress, "127.0.0.1:9999")
const defaultReadHeaderTimeout = 100 * time.Millisecond h.TargetAddress = helpers.DefaultString(h.TargetAddress, "github.com:443")
h.ReadHeaderTimeout = helpers.DefaultNumber(h.ReadHeaderTimeout, defaultReadHeaderTimeout)
const defaultReadTimeout = 500 * time.Millisecond
h.ReadTimeout = helpers.DefaultNumber(h.ReadTimeout, defaultReadTimeout)
h.TargetAddress = helpers.DefaultString(h.TargetAddress, "cloudflare.com:443")
const defaultSuccessWait = 5 * time.Second
h.SuccessWait = helpers.DefaultNumber(h.SuccessWait, defaultSuccessWait)
h.VPN.setDefaults() h.VPN.setDefaults()
} }
@@ -105,9 +77,6 @@ func (h Health) toLinesNode() (node *gotree.Node) {
node = gotree.New("Health settings:") node = gotree.New("Health settings:")
node.Appendf("Server listening address: %s", h.ServerAddress) node.Appendf("Server listening address: %s", h.ServerAddress)
node.Appendf("Target address: %s", h.TargetAddress) node.Appendf("Target address: %s", h.TargetAddress)
node.Appendf("Duration to wait after success: %s", h.SuccessWait)
node.Appendf("Read header timeout: %s", h.ReadHeaderTimeout)
node.Appendf("Read timeout: %s", h.ReadTimeout)
node.AppendNode(h.VPN.toLinesNode("VPN")) node.AppendNode(h.VPN.toLinesNode("VPN"))
return node return node
} }

View File

@@ -27,31 +27,31 @@ func (h HealthyWait) validate() (err error) {
// unset field of the receiver settings object. // unset field of the receiver settings object.
func (h *HealthyWait) copy() (copied HealthyWait) { func (h *HealthyWait) copy() (copied HealthyWait) {
return HealthyWait{ return HealthyWait{
Initial: helpers.CopyPointer(h.Initial), Initial: helpers.CopyDurationPtr(h.Initial),
Addition: helpers.CopyPointer(h.Addition), Addition: helpers.CopyDurationPtr(h.Addition),
} }
} }
// mergeWith merges the other settings into any // mergeWith merges the other settings into any
// unset field of the receiver settings object. // unset field of the receiver settings object.
func (h *HealthyWait) mergeWith(other HealthyWait) { func (h *HealthyWait) mergeWith(other HealthyWait) {
h.Initial = helpers.MergeWithPointer(h.Initial, other.Initial) h.Initial = helpers.MergeWithDuration(h.Initial, other.Initial)
h.Addition = helpers.MergeWithPointer(h.Addition, other.Addition) h.Addition = helpers.MergeWithDuration(h.Addition, other.Addition)
} }
// overrideWith overrides fields of the receiver // overrideWith overrides fields of the receiver
// settings object with any field set in the other // settings object with any field set in the other
// settings. // settings.
func (h *HealthyWait) overrideWith(other HealthyWait) { func (h *HealthyWait) overrideWith(other HealthyWait) {
h.Initial = helpers.OverrideWithPointer(h.Initial, other.Initial) h.Initial = helpers.OverrideWithDuration(h.Initial, other.Initial)
h.Addition = helpers.OverrideWithPointer(h.Addition, other.Addition) h.Addition = helpers.OverrideWithDuration(h.Addition, other.Addition)
} }
func (h *HealthyWait) setDefaults() { func (h *HealthyWait) setDefaults() {
const initialDurationDefault = 6 * time.Second const initialDurationDefault = 6 * time.Second
const additionDurationDefault = 5 * time.Second const additionDurationDefault = 5 * time.Second
h.Initial = helpers.DefaultPointer(h.Initial, initialDurationDefault) h.Initial = helpers.DefaultDuration(h.Initial, initialDurationDefault)
h.Addition = helpers.DefaultPointer(h.Addition, additionDurationDefault) h.Addition = helpers.DefaultDuration(h.Addition, additionDurationDefault)
} }
func (h HealthyWait) String() string { func (h HealthyWait) String() string {

View File

@@ -22,7 +22,7 @@ var (
func AreAllOneOf(values, choices []string) (err error) { func AreAllOneOf(values, choices []string) (err error) {
if len(values) > 0 && len(choices) == 0 { if len(values) > 0 && len(choices) == 0 {
return fmt.Errorf("%w", ErrNoChoice) return ErrNoChoice
} }
set := make(map[string]struct{}, len(choices)) set := make(map[string]struct{}, len(choices))

View File

@@ -1,20 +1,190 @@
package helpers package helpers
import ( import (
"net/netip" "net"
"time"
"golang.org/x/exp/slices" "github.com/qdm12/golibs/logging"
"inet.af/netaddr"
) )
func CopyPointer[T any](original *T) (copied *T) { func CopyStringPtr(original *string) (copied *string) {
if original == nil { if original == nil {
return nil return nil
} }
copied = new(T) copied = new(string)
*copied = *original *copied = *original
return copied return copied
} }
func CopySlice[T string | uint16 | netip.Addr | netip.Prefix](original []T) (copied []T) { func CopyBoolPtr(original *bool) (copied *bool) {
return slices.Clone(original) if original == nil {
return nil
}
copied = new(bool)
*copied = *original
return copied
}
func CopyUint8Ptr(original *uint8) (copied *uint8) {
if original == nil {
return nil
}
copied = new(uint8)
*copied = *original
return copied
}
func CopyUint16Ptr(original *uint16) (copied *uint16) {
if original == nil {
return nil
}
copied = new(uint16)
*copied = *original
return copied
}
func CopyIntPtr(original *int) (copied *int) {
if original == nil {
return nil
}
copied = new(int)
*copied = *original
return copied
}
func CopyDurationPtr(original *time.Duration) (copied *time.Duration) {
if original == nil {
return nil
}
copied = new(time.Duration)
*copied = *original
return copied
}
func CopyLogLevelPtr(original *logging.Level) (copied *logging.Level) {
if original == nil {
return nil
}
copied = new(logging.Level)
*copied = *original
return copied
}
func CopyIP(original net.IP) (copied net.IP) {
if original == nil {
return nil
}
copied = make(net.IP, len(original))
copy(copied, original)
return copied
}
func CopyIPNet(original net.IPNet) (copied net.IPNet) {
if original.IP != nil {
copied.IP = make(net.IP, len(original.IP))
copy(copied.IP, original.IP)
}
if original.Mask != nil {
copied.Mask = make(net.IPMask, len(original.Mask))
copy(copied.Mask, original.Mask)
}
return copied
}
func CopyIPNetPtr(original *net.IPNet) (copied *net.IPNet) {
if original == nil {
return nil
}
copied = new(net.IPNet)
*copied = CopyIPNet(*original)
return copied
}
func CopyNetaddrIP(original netaddr.IP) (copied netaddr.IP) {
b, err := original.MarshalBinary()
if err != nil {
panic(err)
}
err = copied.UnmarshalBinary(b)
if err != nil {
panic(err)
}
return copied
}
func CopyIPPrefix(original netaddr.IPPrefix) (copied netaddr.IPPrefix) {
b, err := original.MarshalText()
if err != nil {
panic(err)
}
err = copied.UnmarshalText(b)
if err != nil {
panic(err)
}
return copied
}
func CopyStringSlice(original []string) (copied []string) {
if original == nil {
return nil
}
copied = make([]string, len(original))
copy(copied, original)
return copied
}
func CopyUint16Slice(original []uint16) (copied []uint16) {
if original == nil {
return nil
}
copied = make([]uint16, len(original))
copy(copied, original)
return copied
}
func CopyIPNetSlice(original []net.IPNet) (copied []net.IPNet) {
if original == nil {
return nil
}
copied = make([]net.IPNet, len(original))
for i := range original {
copied[i] = CopyIPNet(original[i])
}
return copied
}
func CopyIPPrefixSlice(original []netaddr.IPPrefix) (copied []netaddr.IPPrefix) {
if original == nil {
return nil
}
copied = make([]netaddr.IPPrefix, len(original))
for i := range original {
copied[i] = CopyIPPrefix(original[i])
}
return copied
}
func CopyNetaddrIPsSlice(original []netaddr.IP) (copied []netaddr.IP) {
if original == nil {
return nil
}
copied = make([]netaddr.IP, len(original))
for i := range original {
copied[i] = CopyNetaddrIP(original[i])
}
return copied
} }

View File

@@ -1,15 +1,48 @@
package helpers package helpers
import ( import (
"net/netip" "net"
"time"
"github.com/qdm12/golibs/logging"
) )
func DefaultPointer[T any](existing *T, defaultValue T) ( func DefaultInt(existing *int, defaultValue int) (
result *T) { result *int) {
if existing != nil { if existing != nil {
return existing return existing
} }
result = new(T) result = new(int)
*result = defaultValue
return result
}
func DefaultUint8(existing *uint8, defaultValue uint8) (
result *uint8) {
if existing != nil {
return existing
}
result = new(uint8)
*result = defaultValue
return result
}
func DefaultUint16(existing *uint16, defaultValue uint16) (
result *uint16) {
if existing != nil {
return existing
}
result = new(uint16)
*result = defaultValue
return result
}
func DefaultBool(existing *bool, defaultValue bool) (
result *bool) {
if existing != nil {
return existing
}
result = new(bool)
*result = defaultValue *result = defaultValue
return result return result
} }
@@ -22,17 +55,38 @@ func DefaultString(existing string, defaultValue string) (
return defaultValue return defaultValue
} }
func DefaultNumber[T Number](existing T, defaultValue T) ( //nolint:ireturn func DefaultStringPtr(existing *string, defaultValue string) (result *string) {
result T) { if existing != nil {
if existing != 0 {
return existing return existing
} }
return defaultValue result = new(string)
*result = defaultValue
return result
} }
func DefaultIP(existing netip.Addr, defaultValue netip.Addr) ( func DefaultDuration(existing *time.Duration,
result netip.Addr) { defaultValue time.Duration) (result *time.Duration) {
if existing.IsValid() { if existing != nil {
return existing
}
result = new(time.Duration)
*result = defaultValue
return result
}
func DefaultLogLevel(existing *logging.Level,
defaultValue logging.Level) (result *logging.Level) {
if existing != nil {
return existing
}
result = new(logging.Level)
*result = defaultValue
return result
}
func DefaultIP(existing net.IP, defaultValue net.IP) (
result net.IP) {
if existing != nil {
return existing return existing
} }
return defaultValue return defaultValue

View File

@@ -1,10 +0,0 @@
package helpers
import "time"
type Number interface {
uint8 | uint16 | uint32 | uint64 | uint |
int8 | int16 | int32 | int64 | int |
float32 | float64 |
time.Duration
}

View File

@@ -1,17 +1,21 @@
package helpers package helpers
import ( import (
"net"
"net/http" "net/http"
"net/netip" "time"
"github.com/qdm12/golibs/logging"
"inet.af/netaddr"
) )
func MergeWithPointer[T any](existing, other *T) (result *T) { func MergeWithBool(existing, other *bool) (result *bool) {
if existing != nil { if existing != nil {
return existing return existing
} else if other == nil { } else if other == nil {
return nil return nil
} }
result = new(T) result = new(bool)
*result = *other *result = *other
return result return result
} }
@@ -23,20 +27,86 @@ func MergeWithString(existing, other string) (result string) {
return other return other
} }
func MergeWithNumber[T Number](existing, other T) (result T) { //nolint:ireturn func MergeWithInt(existing, other int) (result int) {
if existing != 0 { if existing != 0 {
return existing return existing
} }
return other return other
} }
func MergeWithIP(existing, other netip.Addr) (result netip.Addr) { func MergeWithStringPtr(existing, other *string) (result *string) {
if existing.IsValid() { if existing != nil {
return existing
} else if other == nil {
return nil
}
result = new(string)
*result = *other
return result
}
func MergeWithIntPtr(existing, other *int) (result *int) {
if existing != nil {
return existing
} else if other == nil {
return nil
}
result = new(int)
*result = *other
return result
}
func MergeWithUint8(existing, other *uint8) (result *uint8) {
if existing != nil {
return existing
} else if other == nil {
return nil
}
result = new(uint8)
*result = *other
return result
}
func MergeWithUint16(existing, other *uint16) (result *uint16) {
if existing != nil {
return existing
} else if other == nil {
return nil
}
result = new(uint16)
*result = *other
return result
}
func MergeWithIP(existing, other net.IP) (result net.IP) {
if existing != nil {
return existing
} else if other == nil {
return nil
}
result = make(net.IP, len(other))
copy(result, other)
return result
}
func MergeWithDuration(existing, other *time.Duration) (result *time.Duration) {
if existing != nil {
return existing return existing
} }
return other return other
} }
func MergeWithLogLevel(existing, other *logging.Level) (result *logging.Level) {
if existing != nil {
return existing
} else if other == nil {
return nil
}
result = new(logging.Level)
*result = *other
return result
}
func MergeWithHTTPHandler(existing, other http.Handler) (result http.Handler) { func MergeWithHTTPHandler(existing, other http.Handler) (result http.Handler) {
if existing != nil { if existing != nil {
return existing return existing
@@ -44,13 +114,13 @@ func MergeWithHTTPHandler(existing, other http.Handler) (result http.Handler) {
return other return other
} }
func MergeSlices[T comparable](a, b []T) (result []T) { func MergeStringSlices(a, b []string) (result []string) {
if a == nil && b == nil { if a == nil && b == nil {
return nil return nil
} }
seen := make(map[T]struct{}, len(a)+len(b)) seen := make(map[string]struct{}, len(a)+len(b))
result = make([]T, 0, len(a)+len(b)) result = make([]string, 0, len(a)+len(b))
for _, s := range a { for _, s := range a {
if _, ok := seen[s]; ok { if _, ok := seen[s]; ok {
continue // duplicate continue // duplicate
@@ -67,3 +137,105 @@ func MergeSlices[T comparable](a, b []T) (result []T) {
} }
return result return result
} }
func MergeUint16Slices(a, b []uint16) (result []uint16) {
if a == nil && b == nil {
return nil
}
seen := make(map[uint16]struct{}, len(a)+len(b))
result = make([]uint16, 0, len(a)+len(b))
for _, n := range a {
if _, ok := seen[n]; ok {
continue // duplicate
}
result = append(result, n)
seen[n] = struct{}{}
}
for _, n := range b {
if _, ok := seen[n]; ok {
continue // duplicate
}
result = append(result, n)
seen[n] = struct{}{}
}
return result
}
func MergeIPNetsSlices(a, b []net.IPNet) (result []net.IPNet) {
if a == nil && b == nil {
return nil
}
seen := make(map[string]struct{}, len(a)+len(b))
result = make([]net.IPNet, 0, len(a)+len(b))
for _, ipNet := range a {
key := ipNet.String()
if _, ok := seen[key]; ok {
continue // duplicate
}
result = append(result, ipNet)
seen[key] = struct{}{}
}
for _, ipNet := range b {
key := ipNet.String()
if _, ok := seen[key]; ok {
continue // duplicate
}
result = append(result, ipNet)
seen[key] = struct{}{}
}
return result
}
func MergeNetaddrIPsSlices(a, b []netaddr.IP) (result []netaddr.IP) {
if a == nil && b == nil {
return nil
}
seen := make(map[string]struct{}, len(a)+len(b))
result = make([]netaddr.IP, 0, len(a)+len(b))
for _, ip := range a {
key := ip.String()
if _, ok := seen[key]; ok {
continue // duplicate
}
result = append(result, ip)
seen[key] = struct{}{}
}
for _, ip := range b {
key := ip.String()
if _, ok := seen[key]; ok {
continue // duplicate
}
result = append(result, ip)
seen[key] = struct{}{}
}
return result
}
func MergeIPPrefixesSlices(a, b []netaddr.IPPrefix) (result []netaddr.IPPrefix) {
if a == nil && b == nil {
return nil
}
seen := make(map[string]struct{}, len(a)+len(b))
result = make([]netaddr.IPPrefix, 0, len(a)+len(b))
for _, ipPrefix := range a {
key := ipPrefix.String()
if _, ok := seen[key]; ok {
continue // duplicate
}
result = append(result, ipPrefix)
seen[key] = struct{}{}
}
for _, ipPrefix := range b {
key := ipPrefix.String()
if _, ok := seen[key]; ok {
continue // duplicate
}
result = append(result, ipPrefix)
seen[key] = struct{}{}
}
return result
}

View File

@@ -1,15 +1,19 @@
package helpers package helpers
import ( import (
"net"
"net/http" "net/http"
"net/netip" "time"
"github.com/qdm12/golibs/logging"
"inet.af/netaddr"
) )
func OverrideWithPointer[T any](existing, other *T) (result *T) { func OverrideWithBool(existing, other *bool) (result *bool) {
if other == nil { if other == nil {
return existing return existing
} }
result = new(T) result = new(bool)
*result = *other *result = *other
return result return result
} }
@@ -21,18 +25,74 @@ func OverrideWithString(existing, other string) (result string) {
return other return other
} }
func OverrideWithNumber[T Number](existing, other T) (result T) { //nolint:ireturn func OverrideWithInt(existing, other int) (result int) {
if other == 0 { if other == 0 {
return existing return existing
} }
return other return other
} }
func OverrideWithIP(existing, other netip.Addr) (result netip.Addr) { func OverrideWithStringPtr(existing, other *string) (result *string) {
if !other.IsValid() { if other == nil {
return existing return existing
} }
return other result = new(string)
*result = *other
return result
}
func OverrideWithIntPtr(existing, other *int) (result *int) {
if other == nil {
return existing
}
result = new(int)
*result = *other
return result
}
func OverrideWithUint8(existing, other *uint8) (result *uint8) {
if other == nil {
return existing
}
result = new(uint8)
*result = *other
return result
}
func OverrideWithUint16(existing, other *uint16) (result *uint16) {
if other == nil {
return existing
}
result = new(uint16)
*result = *other
return result
}
func OverrideWithIP(existing, other net.IP) (result net.IP) {
if other == nil {
return existing
}
result = make(net.IP, len(other))
copy(result, other)
return result
}
func OverrideWithDuration(existing, other *time.Duration) (result *time.Duration) {
if other == nil {
return existing
}
result = new(time.Duration)
*result = *other
return result
}
func OverrideWithLogLevel(existing, other *logging.Level) (result *logging.Level) {
if other == nil {
return existing
}
result = new(logging.Level)
*result = *other
return result
} }
func OverrideWithHTTPHandler(existing, other http.Handler) (result http.Handler) { func OverrideWithHTTPHandler(existing, other http.Handler) (result http.Handler) {
@@ -42,11 +102,47 @@ func OverrideWithHTTPHandler(existing, other http.Handler) (result http.Handler)
return existing return existing
} }
func OverrideWithSlice[T any](existing, other []T) (result []T) { func OverrideWithStringSlice(existing, other []string) (result []string) {
if other == nil { if other == nil {
return existing return existing
} }
result = make([]T, len(other)) result = make([]string, len(other))
copy(result, other)
return result
}
func OverrideWithUint16Slice(existing, other []uint16) (result []uint16) {
if other == nil {
return existing
}
result = make([]uint16, len(other))
copy(result, other)
return result
}
func OverrideWithIPNetsSlice(existing, other []net.IPNet) (result []net.IPNet) {
if other == nil {
return existing
}
result = make([]net.IPNet, len(other))
copy(result, other)
return result
}
func OverrideWithNetaddrIPsSlice(existing, other []netaddr.IP) (result []netaddr.IP) {
if other == nil {
return existing
}
result = make([]netaddr.IP, len(other))
copy(result, other)
return result
}
func OverrideWithIPPrefixesSlice(existing, other []netaddr.IPPrefix) (result []netaddr.IPPrefix) {
if other == nil {
return existing
}
result = make([]netaddr.IPPrefix, len(other))
copy(result, other) copy(result, other)
return result return result
} }

View File

@@ -0,0 +1,11 @@
package helpers
import "time"
// StringPtr returns a pointer to the string value
// passed as argument.
func StringPtr(s string) *string { return &s }
// DurationPtr returns a pointer to the duration value
// passed as argument.
func DurationPtr(d time.Duration) *time.Duration { return &d }

View File

@@ -3,7 +3,6 @@ package settings
import ( import (
"fmt" "fmt"
"os" "os"
"time"
"github.com/qdm12/gluetun/internal/configuration/settings/helpers" "github.com/qdm12/gluetun/internal/configuration/settings/helpers"
"github.com/qdm12/gotree" "github.com/qdm12/gotree"
@@ -34,12 +33,6 @@ type HTTPProxy struct {
// each request/response. It cannot be nil in the // each request/response. It cannot be nil in the
// internal state. // internal state.
Log *bool Log *bool
// ReadHeaderTimeout is the HTTP header read timeout duration
// of the HTTP server. It defaults to 1 second if left unset.
ReadHeaderTimeout time.Duration
// ReadTimeout is the HTTP read timeout duration
// of the HTTP server. It defaults to 3 seconds if left unset.
ReadTimeout time.Duration
} }
func (h HTTPProxy) validate() (err error) { func (h HTTPProxy) validate() (err error) {
@@ -56,55 +49,45 @@ func (h HTTPProxy) validate() (err error) {
func (h *HTTPProxy) copy() (copied HTTPProxy) { func (h *HTTPProxy) copy() (copied HTTPProxy) {
return HTTPProxy{ return HTTPProxy{
User: helpers.CopyPointer(h.User), User: helpers.CopyStringPtr(h.User),
Password: helpers.CopyPointer(h.Password), Password: helpers.CopyStringPtr(h.Password),
ListeningAddress: h.ListeningAddress, ListeningAddress: h.ListeningAddress,
Enabled: helpers.CopyPointer(h.Enabled), Enabled: helpers.CopyBoolPtr(h.Enabled),
Stealth: helpers.CopyPointer(h.Stealth), Stealth: helpers.CopyBoolPtr(h.Stealth),
Log: helpers.CopyPointer(h.Log), Log: helpers.CopyBoolPtr(h.Log),
ReadHeaderTimeout: h.ReadHeaderTimeout,
ReadTimeout: h.ReadTimeout,
} }
} }
// mergeWith merges the other settings into any // mergeWith merges the other settings into any
// unset field of the receiver settings object. // unset field of the receiver settings object.
func (h *HTTPProxy) mergeWith(other HTTPProxy) { func (h *HTTPProxy) mergeWith(other HTTPProxy) {
h.User = helpers.MergeWithPointer(h.User, other.User) h.User = helpers.MergeWithStringPtr(h.User, other.User)
h.Password = helpers.MergeWithPointer(h.Password, other.Password) h.Password = helpers.MergeWithStringPtr(h.Password, other.Password)
h.ListeningAddress = helpers.MergeWithString(h.ListeningAddress, other.ListeningAddress) h.ListeningAddress = helpers.MergeWithString(h.ListeningAddress, other.ListeningAddress)
h.Enabled = helpers.MergeWithPointer(h.Enabled, other.Enabled) h.Enabled = helpers.MergeWithBool(h.Enabled, other.Enabled)
h.Stealth = helpers.MergeWithPointer(h.Stealth, other.Stealth) h.Stealth = helpers.MergeWithBool(h.Stealth, other.Stealth)
h.Log = helpers.MergeWithPointer(h.Log, other.Log) h.Log = helpers.MergeWithBool(h.Log, other.Log)
h.ReadHeaderTimeout = helpers.MergeWithNumber(h.ReadHeaderTimeout, other.ReadHeaderTimeout)
h.ReadTimeout = helpers.MergeWithNumber(h.ReadTimeout, other.ReadTimeout)
} }
// overrideWith overrides fields of the receiver // overrideWith overrides fields of the receiver
// settings object with any field set in the other // settings object with any field set in the other
// settings. // settings.
func (h *HTTPProxy) overrideWith(other HTTPProxy) { func (h *HTTPProxy) overrideWith(other HTTPProxy) {
h.User = helpers.OverrideWithPointer(h.User, other.User) h.User = helpers.OverrideWithStringPtr(h.User, other.User)
h.Password = helpers.OverrideWithPointer(h.Password, other.Password) h.Password = helpers.OverrideWithStringPtr(h.Password, other.Password)
h.ListeningAddress = helpers.OverrideWithString(h.ListeningAddress, other.ListeningAddress) h.ListeningAddress = helpers.OverrideWithString(h.ListeningAddress, other.ListeningAddress)
h.Enabled = helpers.OverrideWithPointer(h.Enabled, other.Enabled) h.Enabled = helpers.OverrideWithBool(h.Enabled, other.Enabled)
h.Stealth = helpers.OverrideWithPointer(h.Stealth, other.Stealth) h.Stealth = helpers.OverrideWithBool(h.Stealth, other.Stealth)
h.Log = helpers.OverrideWithPointer(h.Log, other.Log) h.Log = helpers.OverrideWithBool(h.Log, other.Log)
h.ReadHeaderTimeout = helpers.OverrideWithNumber(h.ReadHeaderTimeout, other.ReadHeaderTimeout)
h.ReadTimeout = helpers.OverrideWithNumber(h.ReadTimeout, other.ReadTimeout)
} }
func (h *HTTPProxy) setDefaults() { func (h *HTTPProxy) setDefaults() {
h.User = helpers.DefaultPointer(h.User, "") h.User = helpers.DefaultStringPtr(h.User, "")
h.Password = helpers.DefaultPointer(h.Password, "") h.Password = helpers.DefaultStringPtr(h.Password, "")
h.ListeningAddress = helpers.DefaultString(h.ListeningAddress, ":8888") h.ListeningAddress = helpers.DefaultString(h.ListeningAddress, ":8888")
h.Enabled = helpers.DefaultPointer(h.Enabled, false) h.Enabled = helpers.DefaultBool(h.Enabled, false)
h.Stealth = helpers.DefaultPointer(h.Stealth, false) h.Stealth = helpers.DefaultBool(h.Stealth, false)
h.Log = helpers.DefaultPointer(h.Log, false) h.Log = helpers.DefaultBool(h.Log, false)
const defaultReadHeaderTimeout = time.Second
h.ReadHeaderTimeout = helpers.DefaultNumber(h.ReadHeaderTimeout, defaultReadHeaderTimeout)
const defaultReadTimeout = 3 * time.Second
h.ReadTimeout = helpers.DefaultNumber(h.ReadTimeout, defaultReadTimeout)
} }
func (h HTTPProxy) String() string { func (h HTTPProxy) String() string {
@@ -123,8 +106,6 @@ func (h HTTPProxy) toLinesNode() (node *gotree.Node) {
node.Appendf("Password: %s", helpers.ObfuscatePassword(*h.Password)) node.Appendf("Password: %s", helpers.ObfuscatePassword(*h.Password))
node.Appendf("Stealth mode: %s", helpers.BoolPtrToYesNo(h.Stealth)) node.Appendf("Stealth mode: %s", helpers.BoolPtrToYesNo(h.Stealth))
node.Appendf("Log: %s", helpers.BoolPtrToYesNo(h.Log)) node.Appendf("Log: %s", helpers.BoolPtrToYesNo(h.Log))
node.Appendf("Read header timeout: %s", h.ReadHeaderTimeout)
node.Appendf("Read timeout: %s", h.ReadTimeout)
return node return node
} }

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 *log.Level Level *logging.Level
} }
func (l Log) validate() (err error) { func (l Log) validate() (err error) {
@@ -19,25 +19,25 @@ func (l Log) validate() (err error) {
func (l *Log) copy() (copied Log) { func (l *Log) copy() (copied Log) {
return Log{ return Log{
Level: helpers.CopyPointer(l.Level), Level: helpers.CopyLogLevelPtr(l.Level),
} }
} }
// mergeWith merges the other settings into any // mergeWith merges the other settings into any
// unset field of the receiver settings object. // unset field of the receiver settings object.
func (l *Log) mergeWith(other Log) { func (l *Log) mergeWith(other Log) {
l.Level = helpers.MergeWithPointer(l.Level, other.Level) l.Level = helpers.MergeWithLogLevel(l.Level, other.Level)
} }
// overrideWith overrides fields of the receiver // overrideWith overrides fields of the receiver
// settings object with any field set in the other // settings object with any field set in the other
// settings. // settings.
func (l *Log) overrideWith(other Log) { func (l *Log) overrideWith(other Log) {
l.Level = helpers.OverrideWithPointer(l.Level, other.Level) l.Level = helpers.OverrideWithLogLevel(l.Level, other.Level)
} }
func (l *Log) setDefaults() { func (l *Log) setDefaults() {
l.Level = helpers.DefaultPointer(l.Level, log.LevelInfo) l.Level = helpers.DefaultLogLevel(l.Level, logging.LevelInfo)
} }
func (l Log) String() string { func (l Log) String() string {

View File

@@ -1,36 +0,0 @@
package settings
import (
"net/netip"
"inet.af/netaddr"
)
func netipAddressToNetaddrIP(address netip.Addr) (ip netaddr.IP) {
if address.Is4() {
return netaddr.IPFrom4(address.As4())
}
return netaddr.IPFrom16(address.As16())
}
func netipAddressesToNetaddrIPs(addresses []netip.Addr) (ips []netaddr.IP) {
ips = make([]netaddr.IP, len(addresses))
for i := range addresses {
ips[i] = netipAddressToNetaddrIP(addresses[i])
}
return ips
}
func netipPrefixToNetaddrIPPrefix(prefix netip.Prefix) (ipPrefix netaddr.IPPrefix) {
netaddrIP := netipAddressToNetaddrIP(prefix.Addr())
bits := prefix.Bits()
return netaddr.IPPrefixFrom(netaddrIP, uint8(bits))
}
func netipPrefixesToNetaddrIPPrefixes(prefixes []netip.Prefix) (ipPrefixes []netaddr.IPPrefix) {
ipPrefixes = make([]netaddr.IPPrefix, len(prefixes))
for i := range ipPrefixes {
ipPrefixes[i] = netipPrefixToNetaddrIPPrefix(prefixes[i])
}
return ipPrefixes
}

View File

@@ -1,16 +1,12 @@
package settings package settings
import ( import (
"encoding/base64"
"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/openvpn" "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/extract"
"github.com/qdm12/gluetun/internal/provider/privateinternetaccess/presets"
"github.com/qdm12/gotree" "github.com/qdm12/gotree"
) )
@@ -20,15 +16,13 @@ type OpenVPN struct {
// It can only be "2.4" or "2.5". // It can only be "2.4" or "2.5".
Version string Version string
// User is the OpenVPN authentication username. // User is the OpenVPN authentication username.
// It cannot be nil in the internal state if OpenVPN is used. // It cannot be an empty string in the internal state
// It is usually required but in some cases can be the empty string // if OpenVPN is used.
// to indicate no user+password authentication is needed. User string
User *string
// Password is the OpenVPN authentication password. // Password is the OpenVPN authentication password.
// It cannot be nil in the internal state if OpenVPN is used. // It cannot be an empty string in the internal state
// It is usually required but in some cases can be the empty string // if OpenVPN is used.
// to indicate no user+password authentication is needed. Password string
Password *string
// ConfFile is a custom OpenVPN configuration file path. // ConfFile is a custom OpenVPN configuration file path.
// It can be set to the empty string for it to be ignored. // It can be set to the empty string for it to be ignored.
// It cannot be nil in the internal state. // It cannot be nil in the internal state.
@@ -42,29 +36,24 @@ type OpenVPN struct {
// It cannot be nil in the internal state. // It cannot be nil in the internal state.
// It is ignored if it is set to the empty string. // It is ignored if it is set to the empty string.
Auth *string Auth *string
// Cert is the base64 encoded DER of an OpenVPN certificate for the <cert> block. // ClientCrt is the OpenVPN client certificate.
// This is notably used by Cyberghost and VPN secure. // This is notably used by Cyberghost.
// It can be set to the empty string to be ignored. // It can be set to the empty string to be ignored.
// It cannot be nil in the internal state. // It cannot be nil in the internal state.
Cert *string ClientCrt *string
// Key is the base64 encoded DER of an OpenVPN key. // ClientKey is the OpenVPN client key.
// This is used by Cyberghost and VPN Unlimited. // This is used by Cyberghost and VPN Unlimited.
// It can be set to the empty string to be ignored. // It can be set to the empty string to be ignored.
// It cannot be nil in the internal state. // It cannot be nil in the internal state.
Key *string ClientKey *string
// EncryptedKey is the base64 encoded DER of an encrypted key for OpenVPN.
// It is used by VPN secure.
// It defaults to the empty string meaning it is not
// to be used. KeyPassphrase must be set if this one is set.
EncryptedKey *string
// KeyPassphrase is the key passphrase to be used by OpenVPN
// to decrypt the EncryptedPrivateKey. It defaults to the
// empty string and must be set if EncryptedPrivateKey is set.
KeyPassphrase *string
// PIAEncPreset is the encryption preset for // PIAEncPreset is the encryption preset for
// Private Internet Access. It can be set to an // Private Internet Access. It can be set to an
// empty string for other providers. // empty string for other providers.
PIAEncPreset *string PIAEncPreset *string
// IPv6 is set to true if IPv6 routing should be
// set to be tunnel in OpenVPN, and false otherwise.
// It cannot be nil in the internal state.
IPv6 *bool // TODO automate like with Wireguard
// MSSFix is the value (1 to 10000) to set for the // MSSFix is the value (1 to 10000) to set for the
// mssfix option for OpenVPN. It is ignored if set to 0. // mssfix option for OpenVPN. It is ignored if set to 0.
// It cannot be nil in the internal state. // It cannot be nil in the internal state.
@@ -84,30 +73,22 @@ 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{openvpn.Openvpn24, openvpn.Openvpn25} validVersions := []string{constants.Openvpn24, constants.Openvpn25}
if !helpers.IsOneOf(o.Version, validVersions...) { if !helpers.IsOneOf(o.Version, validVersions...) {
return fmt.Errorf("%w: %q can only be one of %s", return fmt.Errorf("%w: %q can only be one of %s",
ErrOpenVPNVersionIsNotValid, o.Version, strings.Join(validVersions, ", ")) ErrOpenVPNVersionIsNotValid, o.Version, strings.Join(validVersions, ", "))
} }
isCustom := vpnProvider == providers.Custom isCustom := vpnProvider == constants.Custom
isUserRequired := !isCustom &&
vpnProvider != providers.Airvpn &&
vpnProvider != providers.VPNSecure
if isUserRequired && *o.User == "" { if !isCustom && o.User == "" {
return fmt.Errorf("%w", ErrOpenVPNUserIsEmpty) return ErrOpenVPNUserIsEmpty
} }
passwordRequired := isUserRequired && if !isCustom && o.Password == "" {
(vpnProvider != providers.Ivpn || !ivpnAccountID.MatchString(*o.User)) return ErrOpenVPNPasswordIsEmpty
if passwordRequired && *o.Password == "" {
return fmt.Errorf("%w", ErrOpenVPNPasswordIsEmpty)
} }
err = validateOpenVPNConfigFilepath(isCustom, *o.ConfFile) err = validateOpenVPNConfigFilepath(isCustom, *o.ConfFile)
@@ -115,25 +96,16 @@ func (o OpenVPN) validate(vpnProvider string) (err error) {
return fmt.Errorf("custom configuration file: %w", err) return fmt.Errorf("custom configuration file: %w", err)
} }
err = validateOpenVPNClientCertificate(vpnProvider, *o.Cert) err = validateOpenVPNClientCertificate(vpnProvider, *o.ClientCrt)
if err != nil { if err != nil {
return fmt.Errorf("client certificate: %w", err) return fmt.Errorf("client certificate: %w", err)
} }
err = validateOpenVPNClientKey(vpnProvider, *o.Key) err = validateOpenVPNClientKey(vpnProvider, *o.ClientKey)
if err != nil { if err != nil {
return fmt.Errorf("client key: %w", err) return fmt.Errorf("client key: %w", err)
} }
err = validateOpenVPNEncryptedKey(vpnProvider, *o.EncryptedKey)
if err != nil {
return fmt.Errorf("encrypted key: %w", err)
}
if *o.EncryptedKey != "" && *o.KeyPassphrase == "" {
return fmt.Errorf("%w", ErrOpenVPNKeyPassphraseIsEmpty)
}
const maxMSSFix = 10000 const maxMSSFix = 10000
if *o.MSSFix > maxMSSFix { if *o.MSSFix > maxMSSFix {
return fmt.Errorf("%w: %d is over the maximum value of %d", return fmt.Errorf("%w: %d is over the maximum value of %d",
@@ -160,7 +132,7 @@ func validateOpenVPNConfigFilepath(isCustom bool,
} }
if confFile == "" { if confFile == "" {
return fmt.Errorf("%w", ErrFilepathMissing) return ErrFilepathMissing
} }
err = helpers.FileExists(confFile) err = helpers.FileExists(confFile)
@@ -168,12 +140,6 @@ func validateOpenVPNConfigFilepath(isCustom bool,
return err return err
} }
extractor := extract.New()
_, _, err = extractor.Data(confFile)
if err != nil {
return fmt.Errorf("extracting information from custom configuration file: %w", err)
}
return nil return nil
} }
@@ -181,12 +147,10 @@ func validateOpenVPNClientCertificate(vpnProvider,
clientCert string) (err error) { clientCert string) (err error) {
switch vpnProvider { switch vpnProvider {
case case
providers.Airvpn, constants.Cyberghost,
providers.Cyberghost, constants.VPNUnlimited:
providers.VPNSecure,
providers.VPNUnlimited:
if clientCert == "" { if clientCert == "" {
return fmt.Errorf("%w", ErrMissingValue) return ErrMissingValue
} }
} }
@@ -194,7 +158,7 @@ func validateOpenVPNClientCertificate(vpnProvider,
return nil return nil
} }
_, err = base64.StdEncoding.DecodeString(clientCert) _, err = parse.ExtractCert([]byte(clientCert))
if err != nil { if err != nil {
return err return err
} }
@@ -204,12 +168,11 @@ func validateOpenVPNClientCertificate(vpnProvider,
func validateOpenVPNClientKey(vpnProvider, clientKey string) (err error) { func validateOpenVPNClientKey(vpnProvider, clientKey string) (err error) {
switch vpnProvider { switch vpnProvider {
case case
providers.Airvpn, constants.Cyberghost,
providers.Cyberghost, constants.VPNUnlimited,
providers.VPNUnlimited, constants.Wevpn:
providers.Wevpn:
if clientKey == "" { if clientKey == "" {
return fmt.Errorf("%w", ErrMissingValue) return ErrMissingValue
} }
} }
@@ -217,24 +180,7 @@ func validateOpenVPNClientKey(vpnProvider, clientKey string) (err error) {
return nil return nil
} }
_, err = base64.StdEncoding.DecodeString(clientKey) _, err = parse.ExtractPrivateKey([]byte(clientKey))
if err != nil {
return err
}
return nil
}
func validateOpenVPNEncryptedKey(vpnProvider,
encryptedPrivateKey string) (err error) {
if vpnProvider == providers.VPNSecure && encryptedPrivateKey == "" {
return fmt.Errorf("%w", ErrMissingValue)
}
if encryptedPrivateKey == "" {
return nil
}
_, err = base64.StdEncoding.DecodeString(encryptedPrivateKey)
if err != nil { if err != nil {
return err return err
} }
@@ -243,22 +189,21 @@ func validateOpenVPNEncryptedKey(vpnProvider,
func (o *OpenVPN) copy() (copied OpenVPN) { func (o *OpenVPN) copy() (copied OpenVPN) {
return OpenVPN{ return OpenVPN{
Version: o.Version, Version: o.Version,
User: helpers.CopyPointer(o.User), User: o.User,
Password: helpers.CopyPointer(o.Password), Password: o.Password,
ConfFile: helpers.CopyPointer(o.ConfFile), ConfFile: helpers.CopyStringPtr(o.ConfFile),
Ciphers: helpers.CopySlice(o.Ciphers), Ciphers: helpers.CopyStringSlice(o.Ciphers),
Auth: helpers.CopyPointer(o.Auth), Auth: helpers.CopyStringPtr(o.Auth),
Cert: helpers.CopyPointer(o.Cert), ClientCrt: helpers.CopyStringPtr(o.ClientCrt),
Key: helpers.CopyPointer(o.Key), ClientKey: helpers.CopyStringPtr(o.ClientKey),
EncryptedKey: helpers.CopyPointer(o.EncryptedKey), PIAEncPreset: helpers.CopyStringPtr(o.PIAEncPreset),
KeyPassphrase: helpers.CopyPointer(o.KeyPassphrase), IPv6: helpers.CopyBoolPtr(o.IPv6),
PIAEncPreset: helpers.CopyPointer(o.PIAEncPreset), MSSFix: helpers.CopyUint16Ptr(o.MSSFix),
MSSFix: helpers.CopyPointer(o.MSSFix), Interface: o.Interface,
Interface: o.Interface, ProcessUser: o.ProcessUser,
ProcessUser: o.ProcessUser, Verbosity: helpers.CopyIntPtr(o.Verbosity),
Verbosity: helpers.CopyPointer(o.Verbosity), Flags: helpers.CopyStringSlice(o.Flags),
Flags: helpers.CopySlice(o.Flags),
} }
} }
@@ -266,21 +211,20 @@ func (o *OpenVPN) copy() (copied OpenVPN) {
// unset field of the receiver settings object. // unset field of the receiver settings object.
func (o *OpenVPN) mergeWith(other OpenVPN) { func (o *OpenVPN) mergeWith(other OpenVPN) {
o.Version = helpers.MergeWithString(o.Version, other.Version) o.Version = helpers.MergeWithString(o.Version, other.Version)
o.User = helpers.MergeWithPointer(o.User, other.User) o.User = helpers.MergeWithString(o.User, other.User)
o.Password = helpers.MergeWithPointer(o.Password, other.Password) o.Password = helpers.MergeWithString(o.Password, other.Password)
o.ConfFile = helpers.MergeWithPointer(o.ConfFile, other.ConfFile) o.ConfFile = helpers.MergeWithStringPtr(o.ConfFile, other.ConfFile)
o.Ciphers = helpers.MergeSlices(o.Ciphers, other.Ciphers) o.Ciphers = helpers.MergeStringSlices(o.Ciphers, other.Ciphers)
o.Auth = helpers.MergeWithPointer(o.Auth, other.Auth) o.Auth = helpers.MergeWithStringPtr(o.Auth, other.Auth)
o.Cert = helpers.MergeWithPointer(o.Cert, other.Cert) o.ClientCrt = helpers.MergeWithStringPtr(o.ClientCrt, other.ClientCrt)
o.Key = helpers.MergeWithPointer(o.Key, other.Key) o.ClientKey = helpers.MergeWithStringPtr(o.ClientKey, other.ClientKey)
o.EncryptedKey = helpers.MergeWithPointer(o.EncryptedKey, other.EncryptedKey) o.PIAEncPreset = helpers.MergeWithStringPtr(o.PIAEncPreset, other.PIAEncPreset)
o.KeyPassphrase = helpers.MergeWithPointer(o.KeyPassphrase, other.KeyPassphrase) o.IPv6 = helpers.MergeWithBool(o.IPv6, other.IPv6)
o.PIAEncPreset = helpers.MergeWithPointer(o.PIAEncPreset, other.PIAEncPreset) o.MSSFix = helpers.MergeWithUint16(o.MSSFix, other.MSSFix)
o.MSSFix = helpers.MergeWithPointer(o.MSSFix, other.MSSFix)
o.Interface = helpers.MergeWithString(o.Interface, other.Interface) o.Interface = helpers.MergeWithString(o.Interface, other.Interface)
o.ProcessUser = helpers.MergeWithString(o.ProcessUser, other.ProcessUser) o.ProcessUser = helpers.MergeWithString(o.ProcessUser, other.ProcessUser)
o.Verbosity = helpers.MergeWithPointer(o.Verbosity, other.Verbosity) o.Verbosity = helpers.MergeWithIntPtr(o.Verbosity, other.Verbosity)
o.Flags = helpers.MergeSlices(o.Flags, other.Flags) o.Flags = helpers.MergeStringSlices(o.Flags, other.Flags)
} }
// overrideWith overrides fields of the receiver // overrideWith overrides fields of the receiver
@@ -288,48 +232,44 @@ func (o *OpenVPN) mergeWith(other OpenVPN) {
// settings. // settings.
func (o *OpenVPN) overrideWith(other OpenVPN) { func (o *OpenVPN) overrideWith(other OpenVPN) {
o.Version = helpers.OverrideWithString(o.Version, other.Version) o.Version = helpers.OverrideWithString(o.Version, other.Version)
o.User = helpers.OverrideWithPointer(o.User, other.User) o.User = helpers.OverrideWithString(o.User, other.User)
o.Password = helpers.OverrideWithPointer(o.Password, other.Password) o.Password = helpers.OverrideWithString(o.Password, other.Password)
o.ConfFile = helpers.OverrideWithPointer(o.ConfFile, other.ConfFile) o.ConfFile = helpers.OverrideWithStringPtr(o.ConfFile, other.ConfFile)
o.Ciphers = helpers.OverrideWithSlice(o.Ciphers, other.Ciphers) o.Ciphers = helpers.OverrideWithStringSlice(o.Ciphers, other.Ciphers)
o.Auth = helpers.OverrideWithPointer(o.Auth, other.Auth) o.Auth = helpers.OverrideWithStringPtr(o.Auth, other.Auth)
o.Cert = helpers.OverrideWithPointer(o.Cert, other.Cert) o.ClientCrt = helpers.OverrideWithStringPtr(o.ClientCrt, other.ClientCrt)
o.Key = helpers.OverrideWithPointer(o.Key, other.Key) o.ClientKey = helpers.OverrideWithStringPtr(o.ClientKey, other.ClientKey)
o.EncryptedKey = helpers.OverrideWithPointer(o.EncryptedKey, other.EncryptedKey) o.PIAEncPreset = helpers.OverrideWithStringPtr(o.PIAEncPreset, other.PIAEncPreset)
o.KeyPassphrase = helpers.OverrideWithPointer(o.KeyPassphrase, other.KeyPassphrase) o.IPv6 = helpers.OverrideWithBool(o.IPv6, other.IPv6)
o.PIAEncPreset = helpers.OverrideWithPointer(o.PIAEncPreset, other.PIAEncPreset) o.MSSFix = helpers.OverrideWithUint16(o.MSSFix, other.MSSFix)
o.MSSFix = helpers.OverrideWithPointer(o.MSSFix, other.MSSFix)
o.Interface = helpers.OverrideWithString(o.Interface, other.Interface) o.Interface = helpers.OverrideWithString(o.Interface, other.Interface)
o.ProcessUser = helpers.OverrideWithString(o.ProcessUser, other.ProcessUser) o.ProcessUser = helpers.OverrideWithString(o.ProcessUser, other.ProcessUser)
o.Verbosity = helpers.OverrideWithPointer(o.Verbosity, other.Verbosity) o.Verbosity = helpers.OverrideWithIntPtr(o.Verbosity, other.Verbosity)
o.Flags = helpers.OverrideWithSlice(o.Flags, other.Flags) o.Flags = helpers.OverrideWithStringSlice(o.Flags, other.Flags)
} }
func (o *OpenVPN) setDefaults(vpnProvider string) { func (o *OpenVPN) setDefaults(vpnProvider string) {
o.Version = helpers.DefaultString(o.Version, openvpn.Openvpn25) o.Version = helpers.DefaultString(o.Version, constants.Openvpn25)
o.User = helpers.DefaultPointer(o.User, "") if vpnProvider == constants.Mullvad {
if vpnProvider == providers.Mullvad { o.Password = "m"
o.Password = helpers.DefaultPointer(o.Password, "m")
} else {
o.Password = helpers.DefaultPointer(o.Password, "")
} }
o.ConfFile = helpers.DefaultPointer(o.ConfFile, "") o.ConfFile = helpers.DefaultStringPtr(o.ConfFile, "")
o.Auth = helpers.DefaultPointer(o.Auth, "") o.Auth = helpers.DefaultStringPtr(o.Auth, "")
o.Cert = helpers.DefaultPointer(o.Cert, "") o.ClientCrt = helpers.DefaultStringPtr(o.ClientCrt, "")
o.Key = helpers.DefaultPointer(o.Key, "") o.ClientKey = helpers.DefaultStringPtr(o.ClientKey, "")
o.EncryptedKey = helpers.DefaultPointer(o.EncryptedKey, "")
o.KeyPassphrase = helpers.DefaultPointer(o.KeyPassphrase, "")
var defaultEncPreset string var defaultEncPreset string
if vpnProvider == providers.PrivateInternetAccess { if vpnProvider == constants.PrivateInternetAccess {
defaultEncPreset = presets.Strong defaultEncPreset = constants.PIAEncryptionPresetStrong
} }
o.PIAEncPreset = helpers.DefaultPointer(o.PIAEncPreset, defaultEncPreset) o.PIAEncPreset = helpers.DefaultStringPtr(o.PIAEncPreset, defaultEncPreset)
o.MSSFix = helpers.DefaultPointer(o.MSSFix, 0)
o.IPv6 = helpers.DefaultBool(o.IPv6, false)
o.MSSFix = helpers.DefaultUint16(o.MSSFix, 0)
o.Interface = helpers.DefaultString(o.Interface, "tun0") o.Interface = helpers.DefaultString(o.Interface, "tun0")
o.ProcessUser = helpers.DefaultString(o.ProcessUser, "root") o.ProcessUser = helpers.DefaultString(o.ProcessUser, "root")
o.Verbosity = helpers.DefaultPointer(o.Verbosity, 1) o.Verbosity = helpers.DefaultInt(o.Verbosity, 1)
} }
func (o OpenVPN) String() string { func (o OpenVPN) String() string {
@@ -339,8 +279,8 @@ func (o OpenVPN) String() string {
func (o OpenVPN) toLinesNode() (node *gotree.Node) { func (o OpenVPN) toLinesNode() (node *gotree.Node) {
node = gotree.New("OpenVPN settings:") node = gotree.New("OpenVPN settings:")
node.Appendf("OpenVPN version: %s", o.Version) node.Appendf("OpenVPN version: %s", o.Version)
node.Appendf("User: %s", helpers.ObfuscatePassword(*o.User)) node.Appendf("User: %s", helpers.ObfuscatePassword(o.User))
node.Appendf("Password: %s", helpers.ObfuscatePassword(*o.Password)) node.Appendf("Password: %s", helpers.ObfuscatePassword(o.Password))
if *o.ConfFile != "" { if *o.ConfFile != "" {
node.Appendf("Custom configuration file: %s", *o.ConfFile) node.Appendf("Custom configuration file: %s", *o.ConfFile)
@@ -354,23 +294,20 @@ func (o OpenVPN) toLinesNode() (node *gotree.Node) {
node.Appendf("Auth: %s", *o.Auth) node.Appendf("Auth: %s", *o.Auth)
} }
if *o.Cert != "" { if *o.ClientCrt != "" {
node.Appendf("Client crt: %s", helpers.ObfuscateData(*o.Cert)) node.Appendf("Client crt: %s", helpers.ObfuscateData(*o.ClientCrt))
} }
if *o.Key != "" { if *o.ClientKey != "" {
node.Appendf("Client key: %s", helpers.ObfuscateData(*o.Key)) node.Appendf("Client key: %s", helpers.ObfuscateData(*o.ClientKey))
}
if *o.EncryptedKey != "" {
node.Appendf("Encrypted key: %s (key passhrapse %s)",
helpers.ObfuscateData(*o.EncryptedKey), helpers.ObfuscatePassword(*o.KeyPassphrase))
} }
if *o.PIAEncPreset != "" { if *o.PIAEncPreset != "" {
node.Appendf("Private Internet Access encryption preset: %s", *o.PIAEncPreset) node.Appendf("Private Internet Access encryption preset: %s", *o.PIAEncPreset)
} }
node.Appendf("Tunnel IPv6: %s", helpers.BoolPtrToYesNo(o.IPv6))
if *o.MSSFix > 0 { if *o.MSSFix > 0 {
node.Appendf("MSS Fix: %d", *o.MSSFix) node.Appendf("MSS Fix: %d", *o.MSSFix)
} }

View File

@@ -1,44 +0,0 @@
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

@@ -4,8 +4,7 @@ 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/providers" "github.com/qdm12/gluetun/internal/constants"
"github.com/qdm12/gluetun/internal/provider/privateinternetaccess/presets"
"github.com/qdm12/gotree" "github.com/qdm12/gotree"
) )
@@ -40,11 +39,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,
providers.Ipvanish, constants.Ipvanish,
providers.Perfectprivacy, constants.Perfectprivacy,
providers.Privado, constants.Privado,
providers.VPNUnlimited, constants.VPNUnlimited,
providers.Vyprvpn, constants.Vyprvpn,
) { ) {
return fmt.Errorf("%w: for VPN service provider %s", return fmt.Errorf("%w: for VPN service provider %s",
ErrOpenVPNTCPNotSupported, vpnProvider) ErrOpenVPNTCPNotSupported, vpnProvider)
@@ -54,47 +53,33 @@ 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 providers.Cyberghost, providers.HideMyAss, case constants.Cyberghost, constants.HideMyAss,
providers.Privatevpn, providers.Torguard: constants.PrivateInternetAccess, constants.Privatevpn,
constants.Protonvpn, constants.Torguard:
// no custom port allowed // no custom port allowed
case providers.Expressvpn, providers.Fastestvpn, case constants.Expressvpn, constants.Fastestvpn,
providers.Ipvanish, providers.Nordvpn, constants.Ipvanish, constants.Nordvpn,
providers.Privado, providers.Purevpn, constants.Privado, constants.Purevpn,
providers.Surfshark, providers.VPNSecure, constants.Surfshark, constants.VPNUnlimited,
providers.VPNUnlimited, providers.Vyprvpn: constants.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 providers.Airvpn: case constants.Ivpn:
allowedTCP = []uint16{
53, 80, 443, // IP in 1, 3
1194, 2018, 41185, // IP in 1, 2, 3, 4
}
allowedUDP = []uint16{53, 80, 443, 1194, 2018, 41185}
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 providers.Mullvad: case constants.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 providers.Perfectprivacy: case constants.Perfectprivacy:
allowedTCP = []uint16{44, 443, 4433} allowedTCP = []uint16{44, 443, 4433}
allowedUDP = []uint16{44, 443, 4433} allowedUDP = []uint16{44, 443, 4433}
case providers.PrivateInternetAccess: case constants.Wevpn:
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.SlickVPN:
allowedTCP = []uint16{443, 8080, 8888}
allowedUDP = []uint16{443, 8080, 8888}
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 providers.Windscribe: case constants.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}
} }
@@ -112,11 +97,11 @@ func (o OpenVPNSelection) validate(vpnProvider string) (err error) {
} }
// Validate EncPreset // Validate EncPreset
if vpnProvider == providers.PrivateInternetAccess { if vpnProvider == constants.PrivateInternetAccess {
validEncryptionPresets := []string{ validEncryptionPresets := []string{
presets.None, constants.PIAEncryptionPresetNone,
presets.Normal, constants.PIAEncryptionPresetNormal,
presets.Strong, constants.PIAEncryptionPresetStrong,
} }
if !helpers.IsOneOf(*o.PIAEncPreset, validEncryptionPresets...) { if !helpers.IsOneOf(*o.PIAEncPreset, validEncryptionPresets...) {
return fmt.Errorf("%w: %s; valid presets are %s", return fmt.Errorf("%w: %s; valid presets are %s",
@@ -130,37 +115,37 @@ func (o OpenVPNSelection) validate(vpnProvider string) (err error) {
func (o *OpenVPNSelection) copy() (copied OpenVPNSelection) { func (o *OpenVPNSelection) copy() (copied OpenVPNSelection) {
return OpenVPNSelection{ return OpenVPNSelection{
ConfFile: helpers.CopyPointer(o.ConfFile), ConfFile: helpers.CopyStringPtr(o.ConfFile),
TCP: helpers.CopyPointer(o.TCP), TCP: helpers.CopyBoolPtr(o.TCP),
CustomPort: helpers.CopyPointer(o.CustomPort), CustomPort: helpers.CopyUint16Ptr(o.CustomPort),
PIAEncPreset: helpers.CopyPointer(o.PIAEncPreset), PIAEncPreset: helpers.CopyStringPtr(o.PIAEncPreset),
} }
} }
func (o *OpenVPNSelection) mergeWith(other OpenVPNSelection) { func (o *OpenVPNSelection) mergeWith(other OpenVPNSelection) {
o.ConfFile = helpers.MergeWithPointer(o.ConfFile, other.ConfFile) o.ConfFile = helpers.MergeWithStringPtr(o.ConfFile, other.ConfFile)
o.TCP = helpers.MergeWithPointer(o.TCP, other.TCP) o.TCP = helpers.MergeWithBool(o.TCP, other.TCP)
o.CustomPort = helpers.MergeWithPointer(o.CustomPort, other.CustomPort) o.CustomPort = helpers.MergeWithUint16(o.CustomPort, other.CustomPort)
o.PIAEncPreset = helpers.MergeWithPointer(o.PIAEncPreset, other.PIAEncPreset) o.PIAEncPreset = helpers.MergeWithStringPtr(o.PIAEncPreset, other.PIAEncPreset)
} }
func (o *OpenVPNSelection) overrideWith(other OpenVPNSelection) { func (o *OpenVPNSelection) overrideWith(other OpenVPNSelection) {
o.ConfFile = helpers.OverrideWithPointer(o.ConfFile, other.ConfFile) o.ConfFile = helpers.OverrideWithStringPtr(o.ConfFile, other.ConfFile)
o.TCP = helpers.OverrideWithPointer(o.TCP, other.TCP) o.TCP = helpers.OverrideWithBool(o.TCP, other.TCP)
o.CustomPort = helpers.OverrideWithPointer(o.CustomPort, other.CustomPort) o.CustomPort = helpers.OverrideWithUint16(o.CustomPort, other.CustomPort)
o.PIAEncPreset = helpers.OverrideWithPointer(o.PIAEncPreset, other.PIAEncPreset) o.PIAEncPreset = helpers.OverrideWithStringPtr(o.PIAEncPreset, other.PIAEncPreset)
} }
func (o *OpenVPNSelection) setDefaults(vpnProvider string) { func (o *OpenVPNSelection) setDefaults(vpnProvider string) {
o.ConfFile = helpers.DefaultPointer(o.ConfFile, "") o.ConfFile = helpers.DefaultStringPtr(o.ConfFile, "")
o.TCP = helpers.DefaultPointer(o.TCP, false) o.TCP = helpers.DefaultBool(o.TCP, false)
o.CustomPort = helpers.DefaultPointer(o.CustomPort, 0) o.CustomPort = helpers.DefaultUint16(o.CustomPort, 0)
var defaultEncPreset string var defaultEncPreset string
if vpnProvider == providers.PrivateInternetAccess { if vpnProvider == constants.PrivateInternetAccess {
defaultEncPreset = presets.Strong defaultEncPreset = constants.PIAEncryptionPresetStrong
} }
o.PIAEncPreset = helpers.DefaultPointer(o.PIAEncPreset, defaultEncPreset) o.PIAEncPreset = helpers.DefaultStringPtr(o.PIAEncPreset, defaultEncPreset)
} }
func (o OpenVPNSelection) String() string { func (o OpenVPNSelection) String() string {

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/providers" "github.com/qdm12/gluetun/internal/constants"
"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{providers.PrivateInternetAccess} validProviders := []string{constants.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, ", "))
@@ -47,24 +47,24 @@ func (p PortForwarding) validate(vpnProvider string) (err error) {
func (p *PortForwarding) copy() (copied PortForwarding) { func (p *PortForwarding) copy() (copied PortForwarding) {
return PortForwarding{ return PortForwarding{
Enabled: helpers.CopyPointer(p.Enabled), Enabled: helpers.CopyBoolPtr(p.Enabled),
Filepath: helpers.CopyPointer(p.Filepath), Filepath: helpers.CopyStringPtr(p.Filepath),
} }
} }
func (p *PortForwarding) mergeWith(other PortForwarding) { func (p *PortForwarding) mergeWith(other PortForwarding) {
p.Enabled = helpers.MergeWithPointer(p.Enabled, other.Enabled) p.Enabled = helpers.MergeWithBool(p.Enabled, other.Enabled)
p.Filepath = helpers.MergeWithPointer(p.Filepath, other.Filepath) p.Filepath = helpers.MergeWithStringPtr(p.Filepath, other.Filepath)
} }
func (p *PortForwarding) overrideWith(other PortForwarding) { func (p *PortForwarding) overrideWith(other PortForwarding) {
p.Enabled = helpers.OverrideWithPointer(p.Enabled, other.Enabled) p.Enabled = helpers.OverrideWithBool(p.Enabled, other.Enabled)
p.Filepath = helpers.OverrideWithPointer(p.Filepath, other.Filepath) p.Filepath = helpers.OverrideWithStringPtr(p.Filepath, other.Filepath)
} }
func (p *PortForwarding) setDefaults() { func (p *PortForwarding) setDefaults() {
p.Enabled = helpers.DefaultPointer(p.Enabled, false) p.Enabled = helpers.DefaultBool(p.Enabled, false)
p.Filepath = helpers.DefaultPointer(p.Filepath, "/tmp/gluetun/forwarded_port") p.Filepath = helpers.DefaultStringPtr(p.Filepath, "/tmp/gluetun/forwarded_port")
} }
func (p PortForwarding) String() string { func (p PortForwarding) String() string {

View File

@@ -4,8 +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/providers" "github.com/qdm12/gluetun/internal/constants"
"github.com/qdm12/gluetun/internal/constants/vpn" "github.com/qdm12/gluetun/internal/models"
"github.com/qdm12/gotree" "github.com/qdm12/gotree"
) )
@@ -22,28 +22,26 @@ type Provider struct {
} }
// TODO v4 remove pointer for receiver (because of Surfshark). // TODO v4 remove pointer for receiver (because of Surfshark).
func (p *Provider) validate(vpnType string, storage Storage) (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 == vpn.OpenVPN { if vpnType == constants.OpenVPN {
validNames = providers.AllWithCustom() validNames = constants.AllProviders()
validNames = append(validNames, "pia") // Retro-compatibility validNames = append(validNames, "pia") // Retro-compatibility
} else { // Wireguard } else { // Wireguard
validNames = []string{ validNames = []string{
providers.Airvpn, constants.Custom,
providers.Custom, constants.Ivpn,
providers.Ivpn, constants.Mullvad,
providers.Mullvad, constants.Windscribe,
providers.Surfshark,
providers.Windscribe,
} }
} }
if !helpers.IsOneOf(*p.Name, validNames...) { if !helpers.IsOneOf(*p.Name, validNames...) {
return fmt.Errorf("%w for Wireguard: %q can only be one of %s", return fmt.Errorf("%w: %q can only be one of %s",
ErrVPNProviderNameNotValid, *p.Name, helpers.ChoicesOrString(validNames)) ErrVPNProviderNameNotValid, *p.Name, helpers.ChoicesOrString(validNames))
} }
err = p.ServerSelection.validate(*p.Name, storage) err = p.ServerSelection.validate(*p.Name, allServers)
if err != nil { if err != nil {
return fmt.Errorf("server selection: %w", err) return fmt.Errorf("server selection: %w", err)
} }
@@ -58,26 +56,26 @@ func (p *Provider) validate(vpnType string, storage Storage) (err error) {
func (p *Provider) copy() (copied Provider) { func (p *Provider) copy() (copied Provider) {
return Provider{ return Provider{
Name: helpers.CopyPointer(p.Name), Name: helpers.CopyStringPtr(p.Name),
ServerSelection: p.ServerSelection.copy(), ServerSelection: p.ServerSelection.copy(),
PortForwarding: p.PortForwarding.copy(), PortForwarding: p.PortForwarding.copy(),
} }
} }
func (p *Provider) mergeWith(other Provider) { func (p *Provider) mergeWith(other Provider) {
p.Name = helpers.MergeWithPointer(p.Name, other.Name) p.Name = helpers.MergeWithStringPtr(p.Name, other.Name)
p.ServerSelection.mergeWith(other.ServerSelection) p.ServerSelection.mergeWith(other.ServerSelection)
p.PortForwarding.mergeWith(other.PortForwarding) p.PortForwarding.mergeWith(other.PortForwarding)
} }
func (p *Provider) overrideWith(other Provider) { func (p *Provider) overrideWith(other Provider) {
p.Name = helpers.OverrideWithPointer(p.Name, other.Name) p.Name = helpers.OverrideWithStringPtr(p.Name, other.Name)
p.ServerSelection.overrideWith(other.ServerSelection) p.ServerSelection.overrideWith(other.ServerSelection)
p.PortForwarding.overrideWith(other.PortForwarding) p.PortForwarding.overrideWith(other.PortForwarding)
} }
func (p *Provider) setDefaults() { func (p *Provider) setDefaults() {
p.Name = helpers.DefaultPointer(p.Name, providers.PrivateInternetAccess) p.Name = helpers.DefaultStringPtr(p.Name, constants.PrivateInternetAccess)
p.ServerSelection.setDefaults(*p.Name) p.ServerSelection.setDefaults(*p.Name)
p.PortForwarding.setDefaults() p.PortForwarding.setDefaults()
} }

View File

@@ -42,25 +42,25 @@ func (p PublicIP) validate() (err error) {
func (p *PublicIP) copy() (copied PublicIP) { func (p *PublicIP) copy() (copied PublicIP) {
return PublicIP{ return PublicIP{
Period: helpers.CopyPointer(p.Period), Period: helpers.CopyDurationPtr(p.Period),
IPFilepath: helpers.CopyPointer(p.IPFilepath), IPFilepath: helpers.CopyStringPtr(p.IPFilepath),
} }
} }
func (p *PublicIP) mergeWith(other PublicIP) { func (p *PublicIP) mergeWith(other PublicIP) {
p.Period = helpers.MergeWithPointer(p.Period, other.Period) p.Period = helpers.MergeWithDuration(p.Period, other.Period)
p.IPFilepath = helpers.MergeWithPointer(p.IPFilepath, other.IPFilepath) p.IPFilepath = helpers.MergeWithStringPtr(p.IPFilepath, other.IPFilepath)
} }
func (p *PublicIP) overrideWith(other PublicIP) { func (p *PublicIP) overrideWith(other PublicIP) {
p.Period = helpers.OverrideWithPointer(p.Period, other.Period) p.Period = helpers.OverrideWithDuration(p.Period, other.Period)
p.IPFilepath = helpers.OverrideWithPointer(p.IPFilepath, other.IPFilepath) p.IPFilepath = helpers.OverrideWithStringPtr(p.IPFilepath, other.IPFilepath)
} }
func (p *PublicIP) setDefaults() { func (p *PublicIP) setDefaults() {
const defaultPeriod = 12 * time.Hour const defaultPeriod = 12 * time.Hour
p.Period = helpers.DefaultPointer(p.Period, defaultPeriod) p.Period = helpers.DefaultDuration(p.Period, defaultPeriod)
p.IPFilepath = helpers.DefaultPointer(p.IPFilepath, "/tmp/gluetun/ip") p.IPFilepath = helpers.DefaultStringPtr(p.IPFilepath, "/tmp/gluetun/ip")
} }
func (p PublicIP) String() string { func (p PublicIP) String() string {

View File

@@ -43,29 +43,29 @@ func (c ControlServer) validate() (err error) {
func (c *ControlServer) copy() (copied ControlServer) { func (c *ControlServer) copy() (copied ControlServer) {
return ControlServer{ return ControlServer{
Address: helpers.CopyPointer(c.Address), Address: helpers.CopyStringPtr(c.Address),
Log: helpers.CopyPointer(c.Log), Log: helpers.CopyBoolPtr(c.Log),
} }
} }
// mergeWith merges the other settings into any // mergeWith merges the other settings into any
// unset field of the receiver settings object. // unset field of the receiver settings object.
func (c *ControlServer) mergeWith(other ControlServer) { func (c *ControlServer) mergeWith(other ControlServer) {
c.Address = helpers.MergeWithPointer(c.Address, other.Address) c.Address = helpers.MergeWithStringPtr(c.Address, other.Address)
c.Log = helpers.MergeWithPointer(c.Log, other.Log) c.Log = helpers.MergeWithBool(c.Log, other.Log)
} }
// overrideWith overrides fields of the receiver // overrideWith overrides fields of the receiver
// settings object with any field set in the other // settings object with any field set in the other
// settings. // settings.
func (c *ControlServer) overrideWith(other ControlServer) { func (c *ControlServer) overrideWith(other ControlServer) {
c.Address = helpers.OverrideWithPointer(c.Address, other.Address) c.Address = helpers.OverrideWithStringPtr(c.Address, other.Address)
c.Log = helpers.OverrideWithPointer(c.Log, other.Log) c.Log = helpers.OverrideWithBool(c.Log, other.Log)
} }
func (c *ControlServer) setDefaults() { func (c *ControlServer) setDefaults() {
c.Address = helpers.DefaultPointer(c.Address, ":8000") c.Address = helpers.DefaultStringPtr(c.Address, ":8000")
c.Log = helpers.DefaultPointer(c.Log, true) c.Log = helpers.DefaultBool(c.Log, true)
} }
func (c ControlServer) String() string { func (c ControlServer) String() string {

View File

@@ -3,13 +3,12 @@ package settings
import ( import (
"errors" "errors"
"fmt" "fmt"
"net/netip" "net"
"strings" "strings"
"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/providers" "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"
) )
@@ -21,10 +20,10 @@ type ServerSelection struct { //nolint:maligned
VPN string VPN string
// TargetIP is the server endpoint IP address to use. // TargetIP is the server endpoint IP address to use.
// It will override any IP address from the picked // It will override any IP address from the picked
// built-in server. It cannot be the empty value in the internal // built-in server. It cannot be nil in the internal
// state, and can be set to the unspecified address to indicate // state, and can be set to an empty net.IP{} to indicate
// there is not target IP address to use. // there is not target IP address to use.
TargetIP netip.Addr TargetIP net.IP
// Counties is the list of countries to filter VPN servers with. // Counties is the list of countries to filter VPN servers with.
Countries []string Countries []string
// Regions is the list of regions to filter VPN servers with. // Regions is the list of regions to filter VPN servers with.
@@ -45,10 +44,6 @@ type ServerSelection struct { //nolint:maligned
// FreeOnly is true if VPN servers that are not free should // FreeOnly is true if VPN servers that are not free should
// be filtered. This is used with ProtonVPN and VPN Unlimited. // be filtered. This is used with ProtonVPN and VPN Unlimited.
FreeOnly *bool FreeOnly *bool
// PremiumOnly is true if VPN servers that are not premium should
// be filtered. This is used with VPN Secure.
// TODO extend to providers using FreeOnly.
PremiumOnly *bool
// StreamOnly is true if VPN servers not for streaming should // StreamOnly is true if VPN servers not for streaming should
// be filtered. This is used with VPNUnlimited. // be filtered. This is used with VPNUnlimited.
StreamOnly *bool StreamOnly *bool
@@ -67,26 +62,26 @@ type ServerSelection struct { //nolint:maligned
var ( var (
ErrOwnedOnlyNotSupported = errors.New("owned only filter is not supported") ErrOwnedOnlyNotSupported = errors.New("owned only filter is not supported")
ErrFreeOnlyNotSupported = errors.New("free only filter is not supported") ErrFreeOnlyNotSupported = errors.New("free only filter is not supported")
ErrPremiumOnlyNotSupported = errors.New("premium only filter is not supported")
ErrStreamOnlyNotSupported = errors.New("stream only filter is not supported") ErrStreamOnlyNotSupported = errors.New("stream only filter is not supported")
ErrMultiHopOnlyNotSupported = errors.New("multi hop only filter is not supported") ErrMultiHopOnlyNotSupported = errors.New("multi hop only filter is not supported")
ErrFreePremiumBothSet = errors.New("free only and premium only filters are both set")
) )
func (ss *ServerSelection) validate(vpnServiceProvider string, func (ss *ServerSelection) validate(vpnServiceProvider string,
storage Storage) (err error) { allServers models.AllServers) (err error) {
switch ss.VPN { switch ss.VPN {
case vpn.OpenVPN, vpn.Wireguard: case constants.OpenVPN, constants.Wireguard:
default: default:
return fmt.Errorf("%w: %s", ErrVPNTypeNotValid, ss.VPN) return fmt.Errorf("%w: %s", ErrVPNTypeNotValid, ss.VPN)
} }
filterChoices, err := getLocationFilterChoices(vpnServiceProvider, ss, storage) countryChoices, regionChoices, cityChoices,
ispChoices, nameChoices, hostnameChoices, err := getLocationFilterChoices(vpnServiceProvider, ss, allServers)
if err != nil { if err != nil {
return err // already wrapped error return err // already wrapped error
} }
err = validateServerFilters(*ss, filterChoices) err = validateServerFilters(*ss, countryChoices, regionChoices, cityChoices,
ispChoices, nameChoices, hostnameChoices)
if err != nil { if err != nil {
if errors.Is(err, helpers.ErrNoChoice) { if errors.Is(err, helpers.ErrNoChoice) {
return fmt.Errorf("for VPN service provider %s: %w", vpnServiceProvider, err) return fmt.Errorf("for VPN service provider %s: %w", vpnServiceProvider, err)
@@ -95,48 +90,36 @@ func (ss *ServerSelection) validate(vpnServiceProvider string,
} }
if *ss.OwnedOnly && if *ss.OwnedOnly &&
vpnServiceProvider != providers.Mullvad { vpnServiceProvider != constants.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,
providers.Protonvpn, constants.Protonvpn,
providers.VPNUnlimited, constants.VPNUnlimited,
) { ) {
return fmt.Errorf("%w: for VPN service provider %s", return fmt.Errorf("%w: for VPN service provider %s",
ErrFreeOnlyNotSupported, vpnServiceProvider) ErrFreeOnlyNotSupported, vpnServiceProvider)
} }
if *ss.PremiumOnly &&
!helpers.IsOneOf(vpnServiceProvider,
providers.VPNSecure,
) {
return fmt.Errorf("%w: for VPN service provider %s",
ErrPremiumOnlyNotSupported, vpnServiceProvider)
}
if *ss.FreeOnly && *ss.PremiumOnly {
return fmt.Errorf("%w", ErrFreePremiumBothSet)
}
if *ss.StreamOnly && if *ss.StreamOnly &&
!helpers.IsOneOf(vpnServiceProvider, !helpers.IsOneOf(vpnServiceProvider,
providers.Protonvpn, constants.Protonvpn,
providers.VPNUnlimited, constants.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 != providers.Surfshark { vpnServiceProvider != constants.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 == vpn.OpenVPN { if ss.VPN == constants.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)
@@ -151,48 +134,155 @@ func (ss *ServerSelection) validate(vpnServiceProvider string,
return nil return nil
} }
func getLocationFilterChoices(vpnServiceProvider string, func getLocationFilterChoices(vpnServiceProvider string, ss *ServerSelection,
ss *ServerSelection, storage Storage) (filterChoices models.FilterChoices, allServers models.AllServers) (
countryChoices, regionChoices, cityChoices,
ispChoices, nameChoices, hostnameChoices []string,
err error) { err error) {
filterChoices = storage.GetFilterChoices(vpnServiceProvider) switch vpnServiceProvider {
case constants.Custom:
if vpnServiceProvider == providers.Surfshark { case constants.Cyberghost:
// // Retro compatibility servers := allServers.GetCyberghost()
countryChoices = validation.CyberghostCountryChoices(servers)
hostnameChoices = validation.CyberghostHostnameChoices(servers)
case constants.Expressvpn:
servers := allServers.GetExpressvpn()
countryChoices = validation.ExpressvpnCountriesChoices(servers)
cityChoices = validation.ExpressvpnCityChoices(servers)
hostnameChoices = validation.ExpressvpnHostnameChoices(servers)
case constants.Fastestvpn:
servers := allServers.GetFastestvpn()
countryChoices = validation.FastestvpnCountriesChoices(servers)
hostnameChoices = validation.FastestvpnHostnameChoices(servers)
case constants.HideMyAss:
servers := allServers.GetHideMyAss()
countryChoices = validation.HideMyAssCountryChoices(servers)
regionChoices = validation.HideMyAssRegionChoices(servers)
cityChoices = validation.HideMyAssCityChoices(servers)
hostnameChoices = validation.HideMyAssHostnameChoices(servers)
case constants.Ipvanish:
servers := allServers.GetIpvanish()
countryChoices = validation.IpvanishCountryChoices(servers)
cityChoices = validation.IpvanishCityChoices(servers)
hostnameChoices = validation.IpvanishHostnameChoices(servers)
case constants.Ivpn:
servers := allServers.GetIvpn()
countryChoices = validation.IvpnCountryChoices(servers)
cityChoices = validation.IvpnCityChoices(servers)
ispChoices = validation.IvpnISPChoices(servers)
hostnameChoices = validation.IvpnHostnameChoices(servers)
case constants.Mullvad:
servers := allServers.GetMullvad()
countryChoices = validation.MullvadCountryChoices(servers)
cityChoices = validation.MullvadCityChoices(servers)
ispChoices = validation.MullvadISPChoices(servers)
hostnameChoices = validation.MullvadHostnameChoices(servers)
case constants.Nordvpn:
servers := allServers.GetNordvpn()
regionChoices = validation.NordvpnRegionChoices(servers)
hostnameChoices = validation.NordvpnHostnameChoices(servers)
case constants.Perfectprivacy:
servers := allServers.GetPerfectprivacy()
cityChoices = validation.PerfectprivacyCityChoices(servers)
case constants.Privado:
servers := allServers.GetPrivado()
countryChoices = validation.PrivadoCountryChoices(servers)
regionChoices = validation.PrivadoRegionChoices(servers)
cityChoices = validation.PrivadoCityChoices(servers)
hostnameChoices = validation.PrivadoHostnameChoices(servers)
case constants.PrivateInternetAccess:
servers := allServers.GetPia()
regionChoices = validation.PIAGeoChoices(servers)
hostnameChoices = validation.PIAHostnameChoices(servers)
nameChoices = validation.PIANameChoices(servers)
case constants.Privatevpn:
servers := allServers.GetPrivatevpn()
countryChoices = validation.PrivatevpnCountryChoices(servers)
cityChoices = validation.PrivatevpnCityChoices(servers)
hostnameChoices = validation.PrivatevpnHostnameChoices(servers)
case constants.Protonvpn:
servers := allServers.GetProtonvpn()
countryChoices = validation.ProtonvpnCountryChoices(servers)
regionChoices = validation.ProtonvpnRegionChoices(servers)
cityChoices = validation.ProtonvpnCityChoices(servers)
nameChoices = validation.ProtonvpnNameChoices(servers)
hostnameChoices = validation.ProtonvpnHostnameChoices(servers)
case constants.Purevpn:
servers := allServers.GetPurevpn()
countryChoices = validation.PurevpnCountryChoices(servers)
regionChoices = validation.PurevpnRegionChoices(servers)
cityChoices = validation.PurevpnCityChoices(servers)
hostnameChoices = validation.PurevpnHostnameChoices(servers)
case constants.Surfshark:
servers := allServers.GetSurfshark()
countryChoices = validation.SurfsharkCountryChoices(servers)
cityChoices = validation.SurfsharkCityChoices(servers)
hostnameChoices = validation.SurfsharkHostnameChoices(servers)
regionChoices = validation.SurfsharkRegionChoices(servers)
// TODO v4 remove // TODO v4 remove
filterChoices.Regions = append(filterChoices.Regions, validation.SurfsharkRetroLocChoices()...) regionChoices = append(regionChoices, validation.SurfsharkRetroLocChoices()...)
if err := helpers.AreAllOneOf(ss.Regions, filterChoices.Regions); err != nil { if err := helpers.AreAllOneOf(ss.Regions, regionChoices); err != nil {
return models.FilterChoices{}, fmt.Errorf("%w: %s", ErrRegionNotValid, err) return nil, nil, nil, nil, nil, nil, fmt.Errorf("%w: %s", ErrRegionNotValid, err)
} }
// Retro compatibility
// TODO remove in v4
*ss = surfsharkRetroRegion(*ss) *ss = surfsharkRetroRegion(*ss)
case constants.Torguard:
servers := allServers.GetTorguard()
countryChoices = validation.TorguardCountryChoices(servers)
cityChoices = validation.TorguardCityChoices(servers)
hostnameChoices = validation.TorguardHostnameChoices(servers)
case constants.VPNUnlimited:
servers := allServers.GetVPNUnlimited()
countryChoices = validation.VPNUnlimitedCountryChoices(servers)
cityChoices = validation.VPNUnlimitedCityChoices(servers)
hostnameChoices = validation.VPNUnlimitedHostnameChoices(servers)
case constants.Vyprvpn:
servers := allServers.GetVyprvpn()
regionChoices = validation.VyprvpnRegionChoices(servers)
case constants.Wevpn:
servers := allServers.GetWevpn()
cityChoices = validation.WevpnCityChoices(servers)
hostnameChoices = validation.WevpnHostnameChoices(servers)
case constants.Windscribe:
servers := allServers.GetWindscribe()
regionChoices = validation.WindscribeRegionChoices(servers)
cityChoices = validation.WindscribeCityChoices(servers)
hostnameChoices = validation.WindscribeHostnameChoices(servers)
default:
return nil, nil, nil, nil, nil, nil, fmt.Errorf("%w: %s", ErrVPNProviderNameNotValid, vpnServiceProvider)
} }
return filterChoices, nil return countryChoices, regionChoices, cityChoices,
ispChoices, nameChoices, hostnameChoices, nil
} }
// validateServerFilters validates filters against the choices given as arguments. // validateServerFilters validates filters against the choices given as arguments.
// Set an argument to nil to pass the check for a particular filter. // Set an argument to nil to pass the check for a particular filter.
func validateServerFilters(settings ServerSelection, filterChoices models.FilterChoices) (err error) { func validateServerFilters(settings ServerSelection,
if err := helpers.AreAllOneOf(settings.Countries, filterChoices.Countries); err != nil { countryChoices, regionChoices, cityChoices, ispChoices,
nameChoices, hostnameChoices []string) (err error) {
if err := helpers.AreAllOneOf(settings.Countries, countryChoices); err != nil {
return fmt.Errorf("%w: %s", ErrCountryNotValid, err) return fmt.Errorf("%w: %s", ErrCountryNotValid, err)
} }
if err := helpers.AreAllOneOf(settings.Regions, filterChoices.Regions); err != nil { if err := helpers.AreAllOneOf(settings.Regions, regionChoices); err != nil {
return fmt.Errorf("%w: %s", ErrRegionNotValid, err) return fmt.Errorf("%w: %s", ErrRegionNotValid, err)
} }
if err := helpers.AreAllOneOf(settings.Cities, filterChoices.Cities); err != nil { if err := helpers.AreAllOneOf(settings.Cities, cityChoices); err != nil {
return fmt.Errorf("%w: %s", ErrCityNotValid, err) return fmt.Errorf("%w: %s", ErrCityNotValid, err)
} }
if err := helpers.AreAllOneOf(settings.ISPs, filterChoices.ISPs); err != nil { if err := helpers.AreAllOneOf(settings.ISPs, ispChoices); err != nil {
return fmt.Errorf("%w: %s", ErrISPNotValid, err) return fmt.Errorf("%w: %s", ErrISPNotValid, err)
} }
if err := helpers.AreAllOneOf(settings.Hostnames, filterChoices.Hostnames); err != nil { if err := helpers.AreAllOneOf(settings.Hostnames, hostnameChoices); err != nil {
return fmt.Errorf("%w: %s", ErrHostnameNotValid, err) return fmt.Errorf("%w: %s", ErrHostnameNotValid, err)
} }
if err := helpers.AreAllOneOf(settings.Names, filterChoices.Names); err != nil { if err := helpers.AreAllOneOf(settings.Names, nameChoices); err != nil {
return fmt.Errorf("%w: %s", ErrNameNotValid, err) return fmt.Errorf("%w: %s", ErrNameNotValid, err)
} }
@@ -202,19 +292,18 @@ func validateServerFilters(settings ServerSelection, filterChoices models.Filter
func (ss *ServerSelection) copy() (copied ServerSelection) { func (ss *ServerSelection) copy() (copied ServerSelection) {
return ServerSelection{ return ServerSelection{
VPN: ss.VPN, VPN: ss.VPN,
TargetIP: ss.TargetIP, TargetIP: helpers.CopyIP(ss.TargetIP),
Countries: helpers.CopySlice(ss.Countries), Countries: helpers.CopyStringSlice(ss.Countries),
Regions: helpers.CopySlice(ss.Regions), Regions: helpers.CopyStringSlice(ss.Regions),
Cities: helpers.CopySlice(ss.Cities), Cities: helpers.CopyStringSlice(ss.Cities),
ISPs: helpers.CopySlice(ss.ISPs), ISPs: helpers.CopyStringSlice(ss.ISPs),
Hostnames: helpers.CopySlice(ss.Hostnames), Hostnames: helpers.CopyStringSlice(ss.Hostnames),
Names: helpers.CopySlice(ss.Names), Names: helpers.CopyStringSlice(ss.Names),
Numbers: helpers.CopySlice(ss.Numbers), Numbers: helpers.CopyUint16Slice(ss.Numbers),
OwnedOnly: helpers.CopyPointer(ss.OwnedOnly), OwnedOnly: helpers.CopyBoolPtr(ss.OwnedOnly),
FreeOnly: helpers.CopyPointer(ss.FreeOnly), FreeOnly: helpers.CopyBoolPtr(ss.FreeOnly),
PremiumOnly: helpers.CopyPointer(ss.PremiumOnly), StreamOnly: helpers.CopyBoolPtr(ss.StreamOnly),
StreamOnly: helpers.CopyPointer(ss.StreamOnly), MultiHopOnly: helpers.CopyBoolPtr(ss.MultiHopOnly),
MultiHopOnly: helpers.CopyPointer(ss.MultiHopOnly),
OpenVPN: ss.OpenVPN.copy(), OpenVPN: ss.OpenVPN.copy(),
Wireguard: ss.Wireguard.copy(), Wireguard: ss.Wireguard.copy(),
} }
@@ -223,18 +312,17 @@ func (ss *ServerSelection) copy() (copied ServerSelection) {
func (ss *ServerSelection) mergeWith(other ServerSelection) { func (ss *ServerSelection) mergeWith(other ServerSelection) {
ss.VPN = helpers.MergeWithString(ss.VPN, other.VPN) ss.VPN = helpers.MergeWithString(ss.VPN, other.VPN)
ss.TargetIP = helpers.MergeWithIP(ss.TargetIP, other.TargetIP) ss.TargetIP = helpers.MergeWithIP(ss.TargetIP, other.TargetIP)
ss.Countries = helpers.MergeSlices(ss.Countries, other.Countries) ss.Countries = helpers.MergeStringSlices(ss.Countries, other.Countries)
ss.Regions = helpers.MergeSlices(ss.Regions, other.Regions) ss.Regions = helpers.MergeStringSlices(ss.Regions, other.Regions)
ss.Cities = helpers.MergeSlices(ss.Cities, other.Cities) ss.Cities = helpers.MergeStringSlices(ss.Cities, other.Cities)
ss.ISPs = helpers.MergeSlices(ss.ISPs, other.ISPs) ss.ISPs = helpers.MergeStringSlices(ss.ISPs, other.ISPs)
ss.Hostnames = helpers.MergeSlices(ss.Hostnames, other.Hostnames) ss.Hostnames = helpers.MergeStringSlices(ss.Hostnames, other.Hostnames)
ss.Names = helpers.MergeSlices(ss.Names, other.Names) ss.Names = helpers.MergeStringSlices(ss.Names, other.Names)
ss.Numbers = helpers.MergeSlices(ss.Numbers, other.Numbers) ss.Numbers = helpers.MergeUint16Slices(ss.Numbers, other.Numbers)
ss.OwnedOnly = helpers.MergeWithPointer(ss.OwnedOnly, other.OwnedOnly) ss.OwnedOnly = helpers.MergeWithBool(ss.OwnedOnly, other.OwnedOnly)
ss.FreeOnly = helpers.MergeWithPointer(ss.FreeOnly, other.FreeOnly) ss.FreeOnly = helpers.MergeWithBool(ss.FreeOnly, other.FreeOnly)
ss.PremiumOnly = helpers.MergeWithPointer(ss.PremiumOnly, other.PremiumOnly) ss.StreamOnly = helpers.MergeWithBool(ss.StreamOnly, other.StreamOnly)
ss.StreamOnly = helpers.MergeWithPointer(ss.StreamOnly, other.StreamOnly) ss.MultiHopOnly = helpers.MergeWithBool(ss.MultiHopOnly, other.MultiHopOnly)
ss.MultiHopOnly = helpers.MergeWithPointer(ss.MultiHopOnly, other.MultiHopOnly)
ss.OpenVPN.mergeWith(other.OpenVPN) ss.OpenVPN.mergeWith(other.OpenVPN)
ss.Wireguard.mergeWith(other.Wireguard) ss.Wireguard.mergeWith(other.Wireguard)
@@ -243,30 +331,28 @@ func (ss *ServerSelection) mergeWith(other ServerSelection) {
func (ss *ServerSelection) overrideWith(other ServerSelection) { func (ss *ServerSelection) overrideWith(other ServerSelection) {
ss.VPN = helpers.OverrideWithString(ss.VPN, other.VPN) ss.VPN = helpers.OverrideWithString(ss.VPN, other.VPN)
ss.TargetIP = helpers.OverrideWithIP(ss.TargetIP, other.TargetIP) ss.TargetIP = helpers.OverrideWithIP(ss.TargetIP, other.TargetIP)
ss.Countries = helpers.OverrideWithSlice(ss.Countries, other.Countries) ss.Countries = helpers.OverrideWithStringSlice(ss.Countries, other.Countries)
ss.Regions = helpers.OverrideWithSlice(ss.Regions, other.Regions) ss.Regions = helpers.OverrideWithStringSlice(ss.Regions, other.Regions)
ss.Cities = helpers.OverrideWithSlice(ss.Cities, other.Cities) ss.Cities = helpers.OverrideWithStringSlice(ss.Cities, other.Cities)
ss.ISPs = helpers.OverrideWithSlice(ss.ISPs, other.ISPs) ss.ISPs = helpers.OverrideWithStringSlice(ss.ISPs, other.ISPs)
ss.Hostnames = helpers.OverrideWithSlice(ss.Hostnames, other.Hostnames) ss.Hostnames = helpers.OverrideWithStringSlice(ss.Hostnames, other.Hostnames)
ss.Names = helpers.OverrideWithSlice(ss.Names, other.Names) ss.Names = helpers.OverrideWithStringSlice(ss.Names, other.Names)
ss.Numbers = helpers.OverrideWithSlice(ss.Numbers, other.Numbers) ss.Numbers = helpers.OverrideWithUint16Slice(ss.Numbers, other.Numbers)
ss.OwnedOnly = helpers.OverrideWithPointer(ss.OwnedOnly, other.OwnedOnly) ss.OwnedOnly = helpers.OverrideWithBool(ss.OwnedOnly, other.OwnedOnly)
ss.FreeOnly = helpers.OverrideWithPointer(ss.FreeOnly, other.FreeOnly) ss.FreeOnly = helpers.OverrideWithBool(ss.FreeOnly, other.FreeOnly)
ss.PremiumOnly = helpers.OverrideWithPointer(ss.PremiumOnly, other.PremiumOnly) ss.StreamOnly = helpers.OverrideWithBool(ss.StreamOnly, other.StreamOnly)
ss.StreamOnly = helpers.OverrideWithPointer(ss.StreamOnly, other.StreamOnly) ss.MultiHopOnly = helpers.OverrideWithBool(ss.MultiHopOnly, other.MultiHopOnly)
ss.MultiHopOnly = helpers.OverrideWithPointer(ss.MultiHopOnly, other.MultiHopOnly)
ss.OpenVPN.overrideWith(other.OpenVPN) ss.OpenVPN.overrideWith(other.OpenVPN)
ss.Wireguard.overrideWith(other.Wireguard) ss.Wireguard.overrideWith(other.Wireguard)
} }
func (ss *ServerSelection) setDefaults(vpnProvider string) { func (ss *ServerSelection) setDefaults(vpnProvider string) {
ss.VPN = helpers.DefaultString(ss.VPN, vpn.OpenVPN) ss.VPN = helpers.DefaultString(ss.VPN, constants.OpenVPN)
ss.TargetIP = helpers.DefaultIP(ss.TargetIP, netip.IPv4Unspecified()) ss.TargetIP = helpers.DefaultIP(ss.TargetIP, net.IP{})
ss.OwnedOnly = helpers.DefaultPointer(ss.OwnedOnly, false) ss.OwnedOnly = helpers.DefaultBool(ss.OwnedOnly, false)
ss.FreeOnly = helpers.DefaultPointer(ss.FreeOnly, false) ss.FreeOnly = helpers.DefaultBool(ss.FreeOnly, false)
ss.PremiumOnly = helpers.DefaultPointer(ss.PremiumOnly, false) ss.StreamOnly = helpers.DefaultBool(ss.StreamOnly, false)
ss.StreamOnly = helpers.DefaultPointer(ss.StreamOnly, false) ss.MultiHopOnly = helpers.DefaultBool(ss.MultiHopOnly, false)
ss.MultiHopOnly = helpers.DefaultPointer(ss.MultiHopOnly, false)
ss.OpenVPN.setDefaults(vpnProvider) ss.OpenVPN.setDefaults(vpnProvider)
ss.Wireguard.setDefaults() ss.Wireguard.setDefaults()
} }
@@ -278,7 +364,7 @@ func (ss ServerSelection) String() string {
func (ss ServerSelection) toLinesNode() (node *gotree.Node) { func (ss ServerSelection) toLinesNode() (node *gotree.Node) {
node = gotree.New("Server selection settings:") node = gotree.New("Server selection settings:")
node.Appendf("VPN type: %s", ss.VPN) node.Appendf("VPN type: %s", ss.VPN)
if !ss.TargetIP.IsUnspecified() { if len(ss.TargetIP) > 0 {
node.Appendf("Target IP address: %s", ss.TargetIP) node.Appendf("Target IP address: %s", ss.TargetIP)
} }
@@ -321,10 +407,6 @@ func (ss ServerSelection) toLinesNode() (node *gotree.Node) {
node.Appendf("Free only servers: yes") node.Appendf("Free only servers: yes")
} }
if *ss.PremiumOnly {
node.Appendf("Premium only servers: yes")
}
if *ss.StreamOnly { if *ss.StreamOnly {
node.Appendf("Stream only servers: yes") node.Appendf("Stream only servers: yes")
} }
@@ -333,7 +415,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 == vpn.OpenVPN { if ss.VPN == constants.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

@@ -3,10 +3,6 @@ package settings
import ( import (
"fmt" "fmt"
"github.com/qdm12/gluetun/internal/configuration/settings/helpers"
"github.com/qdm12/gluetun/internal/constants/openvpn"
"github.com/qdm12/gluetun/internal/constants/providers"
"github.com/qdm12/gluetun/internal/constants/vpn"
"github.com/qdm12/gluetun/internal/models" "github.com/qdm12/gluetun/internal/models"
"github.com/qdm12/gluetun/internal/pprof" "github.com/qdm12/gluetun/internal/pprof"
"github.com/qdm12/gotree" "github.com/qdm12/gotree"
@@ -28,14 +24,10 @@ type Settings struct {
Pprof pprof.Settings Pprof pprof.Settings
} }
type Storage interface {
GetFilterChoices(provider string) models.FilterChoices
}
// Validate validates all the settings and returns an error // Validate validates all the settings and returns an error
// if one of them is not valid. // if one of them is not valid.
// TODO v4 remove pointer for receiver (because of Surfshark). // TODO v4 remove pointer for receiver (because of Surfshark).
func (s *Settings) Validate(storage Storage, ipv6Supported bool) (err error) { func (s *Settings) Validate(allServers models.AllServers) (err error) {
nameToValidation := map[string]func() error{ nameToValidation := map[string]func() error{
"control server": s.ControlServer.validate, "control server": s.ControlServer.validate,
"dns": s.DNS.validate, "dns": s.DNS.validate,
@@ -50,7 +42,7 @@ func (s *Settings) Validate(storage Storage, ipv6Supported bool) (err error) {
"version": s.Version.validate, "version": s.Version.validate,
// Pprof validation done in pprof constructor // Pprof validation done in pprof constructor
"VPN": func() error { "VPN": func() error {
return s.VPN.Validate(storage, ipv6Supported) return s.VPN.validate(allServers)
}, },
} }
@@ -77,7 +69,7 @@ func (s *Settings) copy() (copied Settings) {
System: s.System.copy(), System: s.System.copy(),
Updater: s.Updater.copy(), Updater: s.Updater.copy(),
Version: s.Version.copy(), Version: s.Version.copy(),
VPN: s.VPN.Copy(), VPN: s.VPN.copy(),
Pprof: s.Pprof.Copy(), Pprof: s.Pprof.Copy(),
} }
} }
@@ -99,7 +91,7 @@ func (s *Settings) MergeWith(other Settings) {
} }
func (s *Settings) OverrideWith(other Settings, func (s *Settings) OverrideWith(other Settings,
storage Storage, ipv6Supported bool) (err error) { allServers models.AllServers) (err error) {
patchedSettings := s.copy() patchedSettings := s.copy()
patchedSettings.ControlServer.overrideWith(other.ControlServer) patchedSettings.ControlServer.overrideWith(other.ControlServer)
patchedSettings.DNS.overrideWith(other.DNS) patchedSettings.DNS.overrideWith(other.DNS)
@@ -112,9 +104,9 @@ func (s *Settings) OverrideWith(other Settings,
patchedSettings.System.overrideWith(other.System) patchedSettings.System.overrideWith(other.System)
patchedSettings.Updater.overrideWith(other.Updater) patchedSettings.Updater.overrideWith(other.Updater)
patchedSettings.Version.overrideWith(other.Version) patchedSettings.Version.overrideWith(other.Version)
patchedSettings.VPN.OverrideWith(other.VPN) patchedSettings.VPN.overrideWith(other.VPN)
patchedSettings.Pprof.OverrideWith(other.Pprof) patchedSettings.Pprof.MergeWith(other.Pprof)
err = patchedSettings.Validate(storage, ipv6Supported) err = patchedSettings.Validate(allServers)
if err != nil { if err != nil {
return err return err
} }
@@ -161,37 +153,3 @@ func (s Settings) toLinesNode() (node *gotree.Node) {
return node return node
} }
func (s Settings) Warnings() (warnings []string) {
if *s.VPN.Provider.Name == providers.HideMyAss {
warnings = append(warnings, "HideMyAss dropped support for Linux OpenVPN "+
" so this will likely not work anymore. See https://github.com/qdm12/gluetun/issues/1498.")
}
if helpers.IsOneOf(*s.VPN.Provider.Name, providers.SlickVPN) &&
s.VPN.Type == vpn.OpenVPN {
if s.VPN.OpenVPN.Version == openvpn.Openvpn24 {
warnings = append(warnings, "OpenVPN 2.4 uses OpenSSL 1.1.1 "+
"which allows the usage of weak security in today's standards. "+
"This can be ok if good security is enforced by the VPN provider. "+
"However, "+*s.VPN.Provider.Name+" uses weak security so you should use "+
"OpenVPN 2.5 to enforce good security practices.")
} else {
warnings = append(warnings, "OpenVPN 2.5 uses OpenSSL 3 "+
"which prohibits the usage of weak security in today's standards. "+
*s.VPN.Provider.Name+" uses weak security which is out "+
"of Gluetun's control so the only workaround is to allow such weaknesses "+
`using the OpenVPN option tls-cipher "DEFAULT:@SECLEVEL=0". `+
"You might want to reach to your provider so they upgrade their certificates. "+
"Once this is done, you will have to let the Gluetun maintainers know "+
"by creating an issue, attaching the new certificate and we will update Gluetun.")
}
}
if s.VPN.OpenVPN.Version == openvpn.Openvpn24 {
warnings = append(warnings, "OpenVPN 2.4 will be removed in release v3.34.0 (around June 2023). "+
"Please create an issue if you have a compelling reason to keep it.")
}
return warnings
}

View File

@@ -34,6 +34,7 @@ func Test_Settings_String(t *testing.T) {
| ├── User: [not set] | ├── User: [not set]
| ├── Password: [not set] | ├── Password: [not set]
| ├── Private Internet Access encryption preset: strong | ├── Private Internet Access encryption preset: strong
| ├── Tunnel IPv6: no
| ├── Network interface: tun0 | ├── Network interface: tun0
| ├── Run OpenVPN as: root | ├── Run OpenVPN as: root
| └── Verbosity level: 1 | └── Verbosity level: 1
@@ -65,10 +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
| ├── Target address: cloudflare.com:443 | ├── Target address: github.com:443
| ├── Duration to wait after success: 5s
| ├── Read header timeout: 100ms
| ├── Read timeout: 500ms
| └── VPN wait durations: | └── VPN wait durations:
| ├── Initial duration: 6s | ├── Initial duration: 6s
| └── Additional duration: 5s | └── Additional duration: 5s

View File

@@ -21,7 +21,7 @@ func (s Shadowsocks) validate() (err error) {
func (s *Shadowsocks) copy() (copied Shadowsocks) { func (s *Shadowsocks) copy() (copied Shadowsocks) {
return Shadowsocks{ return Shadowsocks{
Enabled: helpers.CopyPointer(s.Enabled), Enabled: helpers.CopyBoolPtr(s.Enabled),
Settings: s.Settings.Copy(), Settings: s.Settings.Copy(),
} }
} }
@@ -29,7 +29,7 @@ func (s *Shadowsocks) copy() (copied Shadowsocks) {
// mergeWith merges the other settings into any // mergeWith merges the other settings into any
// unset field of the receiver settings object. // unset field of the receiver settings object.
func (s *Shadowsocks) mergeWith(other Shadowsocks) { func (s *Shadowsocks) mergeWith(other Shadowsocks) {
s.Enabled = helpers.MergeWithPointer(s.Enabled, other.Enabled) s.Enabled = helpers.MergeWithBool(s.Enabled, other.Enabled)
s.Settings.MergeWith(other.Settings) s.Settings.MergeWith(other.Settings)
} }
@@ -37,12 +37,12 @@ func (s *Shadowsocks) mergeWith(other Shadowsocks) {
// settings object with any field set in the other // settings object with any field set in the other
// settings. // settings.
func (s *Shadowsocks) overrideWith(other Shadowsocks) { func (s *Shadowsocks) overrideWith(other Shadowsocks) {
s.Enabled = helpers.OverrideWithPointer(s.Enabled, other.Enabled) s.Enabled = helpers.OverrideWithBool(s.Enabled, other.Enabled)
s.Settings.OverrideWith(other.Settings) s.Settings.OverrideWith(other.Settings)
} }
func (s *Shadowsocks) setDefaults() { func (s *Shadowsocks) setDefaults() {
s.Enabled = helpers.DefaultPointer(s.Enabled, false) s.Enabled = helpers.DefaultBool(s.Enabled, false)
s.Settings.SetDefaults() s.Settings.SetDefaults()
} }

View File

@@ -3,14 +3,15 @@ package settings
import ( import (
"strings" "strings"
"github.com/qdm12/gluetun/internal/provider/surfshark/servers" "github.com/qdm12/gluetun/internal/constants"
"github.com/qdm12/gluetun/internal/models"
) )
func surfsharkRetroRegion(selection ServerSelection) ( func surfsharkRetroRegion(selection ServerSelection) (
updatedSelection ServerSelection) { updatedSelection ServerSelection) {
locationData := servers.LocationData() locationData := constants.SurfsharkLocationData()
retroToLocation := make(map[string]servers.ServerLocation, len(locationData)) retroToLocation := make(map[string]models.SurfsharkLocationData, len(locationData))
for _, data := range locationData { for _, data := range locationData {
if data.RetroLoc == "" { if data.RetroLoc == "" {
continue continue

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 *uint32 PUID *uint16
PGID *uint32 PGID *uint16
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.CopyPointer(s.PUID), PUID: helpers.CopyUint16Ptr(s.PUID),
PGID: helpers.CopyPointer(s.PGID), PGID: helpers.CopyUint16Ptr(s.PGID),
Timezone: s.Timezone, Timezone: s.Timezone,
} }
} }
func (s *System) mergeWith(other System) { func (s *System) mergeWith(other System) {
s.PUID = helpers.MergeWithPointer(s.PUID, other.PUID) s.PUID = helpers.MergeWithUint16(s.PUID, other.PUID)
s.PGID = helpers.MergeWithPointer(s.PGID, other.PGID) s.PGID = helpers.MergeWithUint16(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.OverrideWithPointer(s.PUID, other.PUID) s.PUID = helpers.OverrideWithUint16(s.PUID, other.PUID)
s.PGID = helpers.OverrideWithPointer(s.PGID, other.PGID) s.PGID = helpers.OverrideWithUint16(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.DefaultPointer(s.PUID, defaultID) s.PUID = helpers.DefaultUint16(s.PUID, defaultID)
s.PGID = helpers.DefaultPointer(s.PGID, defaultID) s.PGID = helpers.DefaultUint16(s.PGID, defaultID)
} }
func (s System) String() string { func (s System) String() string {

View File

@@ -3,12 +3,13 @@ package settings
import ( import (
"errors" "errors"
"fmt" "fmt"
"net/netip" "net"
"github.com/qdm12/dns/pkg/provider" "github.com/qdm12/dns/pkg/provider"
"github.com/qdm12/dns/pkg/unbound" "github.com/qdm12/dns/pkg/unbound"
"github.com/qdm12/gluetun/internal/configuration/settings/helpers" "github.com/qdm12/gluetun/internal/configuration/settings/helpers"
"github.com/qdm12/gotree" "github.com/qdm12/gotree"
"inet.af/netaddr"
) )
// Unbound is settings for the Unbound program. // Unbound is settings for the Unbound program.
@@ -20,7 +21,7 @@ type Unbound struct {
VerbosityDetailsLevel *uint8 VerbosityDetailsLevel *uint8
ValidationLogLevel *uint8 ValidationLogLevel *uint8
Username string Username string
Allowed []netip.Prefix Allowed []netaddr.IPPrefix
} }
func (u *Unbound) setDefaults() { func (u *Unbound) setDefaults() {
@@ -30,22 +31,22 @@ func (u *Unbound) setDefaults() {
} }
} }
u.Caching = helpers.DefaultPointer(u.Caching, true) u.Caching = helpers.DefaultBool(u.Caching, true)
u.IPv6 = helpers.DefaultPointer(u.IPv6, false) u.IPv6 = helpers.DefaultBool(u.IPv6, false)
const defaultVerbosityLevel = 1 const defaultVerbosityLevel = 1
u.VerbosityLevel = helpers.DefaultPointer(u.VerbosityLevel, defaultVerbosityLevel) u.VerbosityLevel = helpers.DefaultUint8(u.VerbosityLevel, defaultVerbosityLevel)
const defaultVerbosityDetailsLevel = 0 const defaultVerbosityDetailsLevel = 0
u.VerbosityDetailsLevel = helpers.DefaultPointer(u.VerbosityDetailsLevel, defaultVerbosityDetailsLevel) u.VerbosityDetailsLevel = helpers.DefaultUint8(u.VerbosityDetailsLevel, defaultVerbosityDetailsLevel)
const defaultValidationLogLevel = 0 const defaultValidationLogLevel = 0
u.ValidationLogLevel = helpers.DefaultPointer(u.ValidationLogLevel, defaultValidationLogLevel) u.ValidationLogLevel = helpers.DefaultUint8(u.ValidationLogLevel, defaultValidationLogLevel)
if u.Allowed == nil { if u.Allowed == nil {
u.Allowed = []netip.Prefix{ u.Allowed = []netaddr.IPPrefix{
netip.PrefixFrom(netip.AddrFrom4([4]byte{}), 0), netaddr.IPPrefixFrom(netaddr.IPv4(0, 0, 0, 0), 0),
netip.PrefixFrom(netip.AddrFrom16([16]byte{}), 0), netaddr.IPPrefixFrom(netaddr.IPv6Raw([16]byte{}), 0),
} }
} }
@@ -94,37 +95,37 @@ func (u Unbound) validate() (err error) {
func (u Unbound) copy() (copied Unbound) { func (u Unbound) copy() (copied Unbound) {
return Unbound{ return Unbound{
Providers: helpers.CopySlice(u.Providers), Providers: helpers.CopyStringSlice(u.Providers),
Caching: helpers.CopyPointer(u.Caching), Caching: helpers.CopyBoolPtr(u.Caching),
IPv6: helpers.CopyPointer(u.IPv6), IPv6: helpers.CopyBoolPtr(u.IPv6),
VerbosityLevel: helpers.CopyPointer(u.VerbosityLevel), VerbosityLevel: helpers.CopyUint8Ptr(u.VerbosityLevel),
VerbosityDetailsLevel: helpers.CopyPointer(u.VerbosityDetailsLevel), VerbosityDetailsLevel: helpers.CopyUint8Ptr(u.VerbosityDetailsLevel),
ValidationLogLevel: helpers.CopyPointer(u.ValidationLogLevel), ValidationLogLevel: helpers.CopyUint8Ptr(u.ValidationLogLevel),
Username: u.Username, Username: u.Username,
Allowed: helpers.CopySlice(u.Allowed), Allowed: helpers.CopyIPPrefixSlice(u.Allowed),
} }
} }
func (u *Unbound) mergeWith(other Unbound) { func (u *Unbound) mergeWith(other Unbound) {
u.Providers = helpers.MergeSlices(u.Providers, other.Providers) u.Providers = helpers.MergeStringSlices(u.Providers, other.Providers)
u.Caching = helpers.MergeWithPointer(u.Caching, other.Caching) u.Caching = helpers.MergeWithBool(u.Caching, other.Caching)
u.IPv6 = helpers.MergeWithPointer(u.IPv6, other.IPv6) u.IPv6 = helpers.MergeWithBool(u.IPv6, other.IPv6)
u.VerbosityLevel = helpers.MergeWithPointer(u.VerbosityLevel, other.VerbosityLevel) u.VerbosityLevel = helpers.MergeWithUint8(u.VerbosityLevel, other.VerbosityLevel)
u.VerbosityDetailsLevel = helpers.MergeWithPointer(u.VerbosityDetailsLevel, other.VerbosityDetailsLevel) u.VerbosityDetailsLevel = helpers.MergeWithUint8(u.VerbosityDetailsLevel, other.VerbosityDetailsLevel)
u.ValidationLogLevel = helpers.MergeWithPointer(u.ValidationLogLevel, other.ValidationLogLevel) u.ValidationLogLevel = helpers.MergeWithUint8(u.ValidationLogLevel, other.ValidationLogLevel)
u.Username = helpers.MergeWithString(u.Username, other.Username) u.Username = helpers.MergeWithString(u.Username, other.Username)
u.Allowed = helpers.MergeSlices(u.Allowed, other.Allowed) u.Allowed = helpers.MergeIPPrefixesSlices(u.Allowed, other.Allowed)
} }
func (u *Unbound) overrideWith(other Unbound) { func (u *Unbound) overrideWith(other Unbound) {
u.Providers = helpers.OverrideWithSlice(u.Providers, other.Providers) u.Providers = helpers.OverrideWithStringSlice(u.Providers, other.Providers)
u.Caching = helpers.OverrideWithPointer(u.Caching, other.Caching) u.Caching = helpers.OverrideWithBool(u.Caching, other.Caching)
u.IPv6 = helpers.OverrideWithPointer(u.IPv6, other.IPv6) u.IPv6 = helpers.OverrideWithBool(u.IPv6, other.IPv6)
u.VerbosityLevel = helpers.OverrideWithPointer(u.VerbosityLevel, other.VerbosityLevel) u.VerbosityLevel = helpers.OverrideWithUint8(u.VerbosityLevel, other.VerbosityLevel)
u.VerbosityDetailsLevel = helpers.OverrideWithPointer(u.VerbosityDetailsLevel, other.VerbosityDetailsLevel) u.VerbosityDetailsLevel = helpers.OverrideWithUint8(u.VerbosityDetailsLevel, other.VerbosityDetailsLevel)
u.ValidationLogLevel = helpers.OverrideWithPointer(u.ValidationLogLevel, other.ValidationLogLevel) u.ValidationLogLevel = helpers.OverrideWithUint8(u.ValidationLogLevel, other.ValidationLogLevel)
u.Username = helpers.OverrideWithString(u.Username, other.Username) u.Username = helpers.OverrideWithString(u.Username, other.Username)
u.Allowed = helpers.OverrideWithSlice(u.Allowed, other.Allowed) u.Allowed = helpers.OverrideWithIPPrefixesSlice(u.Allowed, other.Allowed)
} }
func (u Unbound) ToUnboundFormat() (settings unbound.Settings, err error) { func (u Unbound) ToUnboundFormat() (settings unbound.Settings, err error) {
@@ -148,30 +149,20 @@ func (u Unbound) ToUnboundFormat() (settings unbound.Settings, err error) {
VerbosityDetailsLevel: *u.VerbosityDetailsLevel, VerbosityDetailsLevel: *u.VerbosityDetailsLevel,
ValidationLogLevel: *u.ValidationLogLevel, ValidationLogLevel: *u.ValidationLogLevel,
AccessControl: unbound.AccessControlSettings{ AccessControl: unbound.AccessControlSettings{
Allowed: netipPrefixesToNetaddrIPPrefixes(u.Allowed), Allowed: u.Allowed,
}, },
Username: u.Username, Username: u.Username,
}, nil }, nil
} }
var ( func (u Unbound) GetFirstPlaintextIPv4() (ipv4 net.IP, err error) {
ErrConvertingNetip = errors.New("converting net.IP to netip.Addr failed")
)
func (u Unbound) GetFirstPlaintextIPv4() (ipv4 netip.Addr, err error) {
s := u.Providers[0] s := u.Providers[0]
provider, err := provider.Parse(s) provider, err := provider.Parse(s)
if err != nil { if err != nil {
return ipv4, err return nil, err
} }
ip := provider.DNS().IPv4[0] return provider.DNS().IPv4[0], nil
ipv4, ok := netip.AddrFromSlice(ip)
if !ok {
return ipv4, fmt.Errorf("%w: for ip %s (%#v)",
ErrConvertingNetip, ip, ip)
}
return ipv4.Unmap(), nil
} }
func (u Unbound) String() string { func (u Unbound) String() string {

View File

@@ -2,11 +2,11 @@ package settings
import ( import (
"encoding/json" "encoding/json"
"net/netip"
"testing" "testing"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
"inet.af/netaddr"
) )
func Test_Unbound_JSON(t *testing.T) { func Test_Unbound_JSON(t *testing.T) {
@@ -20,9 +20,9 @@ func Test_Unbound_JSON(t *testing.T) {
VerbosityDetailsLevel: nil, VerbosityDetailsLevel: nil,
ValidationLogLevel: uint8Ptr(0), ValidationLogLevel: uint8Ptr(0),
Username: "user", Username: "user",
Allowed: []netip.Prefix{ Allowed: []netaddr.IPPrefix{
netip.PrefixFrom(netip.AddrFrom4([4]byte{}), 0), netaddr.IPPrefixFrom(netaddr.IPv4(0, 0, 0, 0), 0),
netip.PrefixFrom(netip.AddrFrom16([16]byte{}), 0), netaddr.IPPrefixFrom(netaddr.IPv6Raw([16]byte{}), 0),
}, },
} }

View File

@@ -2,11 +2,12 @@ package settings
import ( import (
"fmt" "fmt"
"net"
"strings" "strings"
"time" "time"
"github.com/qdm12/gluetun/internal/configuration/settings/helpers" "github.com/qdm12/gluetun/internal/configuration/settings/helpers"
"github.com/qdm12/gluetun/internal/constants/providers" "github.com/qdm12/gluetun/internal/constants"
"github.com/qdm12/gotree" "github.com/qdm12/gotree"
) )
@@ -20,15 +21,16 @@ type Updater struct {
Period *time.Duration Period *time.Duration
// DNSAddress is the DNS server address to use // DNSAddress is the DNS server address to use
// to resolve VPN server hostnames to IP addresses. // to resolve VPN server hostnames to IP addresses.
// It cannot be the empty string in the internal state. // It cannot be nil in the internal state.
DNSAddress string DNSAddress net.IP
// MinRatio is the minimum ratio of servers to
// find per provider, compared to the total current
// number of servers. It defaults to 0.8.
MinRatio float64
// Providers is the list of VPN service providers // Providers is the list of VPN service providers
// to update server information for. // to update server information for.
Providers []string Providers []string
// CLI is to precise the updater is running in CLI
// mode. This is set automatically and cannot be set
// by settings sources. It cannot be nil in the
// internal state.
CLI *bool
} }
func (u Updater) Validate() (err error) { func (u Updater) Validate() (err error) {
@@ -38,23 +40,21 @@ func (u Updater) Validate() (err error) {
ErrUpdaterPeriodTooSmall, *u.Period, minPeriod) ErrUpdaterPeriodTooSmall, *u.Period, minPeriod)
} }
if u.MinRatio <= 0 || u.MinRatio > 1 { for i, provider := range u.Providers {
return fmt.Errorf("%w: %.2f must be between 0+ and 1",
ErrMinRatioNotValid, u.MinRatio)
}
validProviders := providers.All()
for _, provider := range u.Providers {
valid := false valid := false
for _, validProvider := range validProviders { for _, validProvider := range constants.AllProviders() {
if validProvider == constants.Custom {
continue
}
if provider == validProvider { if provider == validProvider {
valid = true valid = true
break break
} }
} }
if !valid { if !valid {
return fmt.Errorf("%w: %q can only be one of %s", return fmt.Errorf("%w: %s at index %d",
ErrVPNProviderNameNotValid, provider, helpers.ChoicesOrString(validProviders)) ErrVPNProviderNameNotValid, provider, i)
} }
} }
@@ -63,42 +63,37 @@ func (u Updater) Validate() (err error) {
func (u *Updater) copy() (copied Updater) { func (u *Updater) copy() (copied Updater) {
return Updater{ return Updater{
Period: helpers.CopyPointer(u.Period), Period: helpers.CopyDurationPtr(u.Period),
DNSAddress: u.DNSAddress, DNSAddress: helpers.CopyIP(u.DNSAddress),
MinRatio: u.MinRatio, Providers: helpers.CopyStringSlice(u.Providers),
Providers: helpers.CopySlice(u.Providers), CLI: u.CLI,
} }
} }
// mergeWith merges the other settings into any // mergeWith merges the other settings into any
// unset field of the receiver settings object. // unset field of the receiver settings object.
func (u *Updater) mergeWith(other Updater) { func (u *Updater) mergeWith(other Updater) {
u.Period = helpers.MergeWithPointer(u.Period, other.Period) u.Period = helpers.MergeWithDuration(u.Period, other.Period)
u.DNSAddress = helpers.MergeWithString(u.DNSAddress, other.DNSAddress) u.DNSAddress = helpers.MergeWithIP(u.DNSAddress, other.DNSAddress)
u.MinRatio = helpers.MergeWithNumber(u.MinRatio, other.MinRatio) u.Providers = helpers.MergeStringSlices(u.Providers, other.Providers)
u.Providers = helpers.MergeSlices(u.Providers, other.Providers) u.CLI = helpers.MergeWithBool(u.CLI, other.CLI)
} }
// overrideWith overrides fields of the receiver // overrideWith overrides fields of the receiver
// settings object with any field set in the other // settings object with any field set in the other
// settings. // settings.
func (u *Updater) overrideWith(other Updater) { func (u *Updater) overrideWith(other Updater) {
u.Period = helpers.OverrideWithPointer(u.Period, other.Period) u.Period = helpers.OverrideWithDuration(u.Period, other.Period)
u.DNSAddress = helpers.OverrideWithString(u.DNSAddress, other.DNSAddress) u.DNSAddress = helpers.OverrideWithIP(u.DNSAddress, other.DNSAddress)
u.MinRatio = helpers.OverrideWithNumber(u.MinRatio, other.MinRatio) u.Providers = helpers.OverrideWithStringSlice(u.Providers, other.Providers)
u.Providers = helpers.OverrideWithSlice(u.Providers, other.Providers) u.CLI = helpers.MergeWithBool(u.CLI, other.CLI)
} }
func (u *Updater) SetDefaults(vpnProvider string) { func (u *Updater) SetDefaults(vpnProvider string) {
u.Period = helpers.DefaultPointer(u.Period, 0) u.Period = helpers.DefaultDuration(u.Period, 0)
u.DNSAddress = helpers.DefaultString(u.DNSAddress, "1.1.1.1:53") u.DNSAddress = helpers.DefaultIP(u.DNSAddress, net.IPv4(1, 1, 1, 1))
u.CLI = helpers.DefaultBool(u.CLI, false)
if u.MinRatio == 0 { if len(u.Providers) == 0 && vpnProvider != constants.Custom {
const defaultMinRatio = 0.8
u.MinRatio = defaultMinRatio
}
if len(u.Providers) == 0 && vpnProvider != providers.Custom {
u.Providers = []string{vpnProvider} u.Providers = []string{vpnProvider}
} }
} }
@@ -115,8 +110,11 @@ func (u Updater) toLinesNode() (node *gotree.Node) {
node = gotree.New("Server data updater settings:") node = gotree.New("Server data updater settings:")
node.Appendf("Update period: %s", *u.Period) node.Appendf("Update period: %s", *u.Period)
node.Appendf("DNS address: %s", u.DNSAddress) node.Appendf("DNS address: %s", u.DNSAddress)
node.Appendf("Minimum ratio: %.1f", u.MinRatio)
node.Appendf("Providers to update: %s", strings.Join(u.Providers, ", ")) node.Appendf("Providers to update: %s", strings.Join(u.Providers, ", "))
if *u.CLI {
node.Appendf("CLI mode: enabled")
}
return node return node
} }

View File

@@ -0,0 +1,21 @@
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

@@ -0,0 +1,29 @@
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

@@ -0,0 +1,21 @@
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

@@ -0,0 +1,23 @@
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

@@ -0,0 +1,37 @@
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

@@ -0,0 +1,29 @@
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

@@ -0,0 +1,37 @@
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

@@ -0,0 +1,37 @@
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

@@ -0,0 +1,21 @@
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

@@ -0,0 +1,13 @@
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

@@ -0,0 +1,29 @@
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

@@ -0,0 +1,35 @@
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

@@ -0,0 +1,27 @@
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

@@ -0,0 +1,43 @@
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

@@ -0,0 +1,35 @@
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

@@ -1,129 +0,0 @@
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
if value == "" {
continue
}
_, 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
if value == "" {
continue
}
_, 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
if value == "" {
continue
}
_, 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
if value == "" {
continue
}
_, 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
if value == "" {
continue
}
_, 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
if value == "" {
continue
}
_, alreadySeen := seen[value]
if alreadySeen {
continue
}
seen[value] = struct{}{}
values = sortedInsert(values, value)
}
return values
}

View File

@@ -1,12 +1,47 @@
package validation package validation
import ( import (
"github.com/qdm12/gluetun/internal/provider/surfshark/servers" "sort"
"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 := servers.LocationData() locationData := constants.SurfsharkLocationData()
choices = make([]string, 0, len(locationData)) choices = make([]string, 0, len(locationData))
seen := make(map[string]struct{}, len(locationData)) seen := make(map[string]struct{}, len(locationData))
for _, data := range locationData { for _, data := range locationData {
@@ -14,8 +49,12 @@ func SurfsharkRetroLocChoices() (choices []string) {
continue continue
} }
seen[data.RetroLoc] = struct{}{} seen[data.RetroLoc] = struct{}{}
choices = sortedInsert(choices, data.RetroLoc) choices = append(choices, data.RetroLoc)
} }
sort.Slice(choices, func(i, j int) bool {
return choices[i] < choices[j]
})
return choices return choices
} }

View File

@@ -0,0 +1,29 @@
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

@@ -0,0 +1,29 @@
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

@@ -0,0 +1,13 @@
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

@@ -0,0 +1,19 @@
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

@@ -0,0 +1,27 @@
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

@@ -19,25 +19,25 @@ func (v Version) validate() (err error) {
func (v *Version) copy() (copied Version) { func (v *Version) copy() (copied Version) {
return Version{ return Version{
Enabled: helpers.CopyPointer(v.Enabled), Enabled: helpers.CopyBoolPtr(v.Enabled),
} }
} }
// mergeWith merges the other settings into any // mergeWith merges the other settings into any
// unset field of the receiver settings object. // unset field of the receiver settings object.
func (v *Version) mergeWith(other Version) { func (v *Version) mergeWith(other Version) {
v.Enabled = helpers.MergeWithPointer(v.Enabled, other.Enabled) v.Enabled = helpers.MergeWithBool(v.Enabled, other.Enabled)
} }
// overrideWith overrides fields of the receiver // overrideWith overrides fields of the receiver
// settings object with any field set in the other // settings object with any field set in the other
// settings. // settings.
func (v *Version) overrideWith(other Version) { func (v *Version) overrideWith(other Version) {
v.Enabled = helpers.OverrideWithPointer(v.Enabled, other.Enabled) v.Enabled = helpers.OverrideWithBool(v.Enabled, other.Enabled)
} }
func (v *Version) setDefaults() { func (v *Version) setDefaults() {
v.Enabled = helpers.DefaultPointer(v.Enabled, true) v.Enabled = helpers.DefaultBool(v.Enabled, true)
} }
func (v Version) String() string { func (v Version) String() string {

View File

@@ -5,7 +5,8 @@ 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/vpn" "github.com/qdm12/gluetun/internal/constants"
"github.com/qdm12/gluetun/internal/models"
"github.com/qdm12/gotree" "github.com/qdm12/gotree"
) )
@@ -20,26 +21,26 @@ type VPN struct {
} }
// TODO v4 remove pointer for receiver (because of Surfshark). // TODO v4 remove pointer for receiver (because of Surfshark).
func (v *VPN) Validate(storage Storage, ipv6Supported bool) (err error) { func (v *VPN) validate(allServers models.AllServers) (err error) {
// Validate Type // Validate Type
validVPNTypes := []string{vpn.OpenVPN, vpn.Wireguard} validVPNTypes := []string{constants.OpenVPN, constants.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, ", "))
} }
err = v.Provider.validate(v.Type, storage) err = v.Provider.validate(v.Type, allServers)
if err != nil { if err != nil {
return fmt.Errorf("provider settings: %w", err) return fmt.Errorf("provider settings: %w", err)
} }
if v.Type == vpn.OpenVPN { if v.Type == constants.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)
} }
} else { } else {
err := v.Wireguard.validate(*v.Provider.Name, ipv6Supported) err := v.Wireguard.validate(*v.Provider.Name)
if err != nil { if err != nil {
return fmt.Errorf("Wireguard settings: %w", err) return fmt.Errorf("Wireguard settings: %w", err)
} }
@@ -48,7 +49,7 @@ func (v *VPN) Validate(storage Storage, ipv6Supported bool) (err error) {
return nil return nil
} }
func (v *VPN) Copy() (copied VPN) { func (v *VPN) copy() (copied VPN) {
return VPN{ return VPN{
Type: v.Type, Type: v.Type,
Provider: v.Provider.copy(), Provider: v.Provider.copy(),
@@ -64,7 +65,7 @@ func (v *VPN) mergeWith(other VPN) {
v.Wireguard.mergeWith(other.Wireguard) v.Wireguard.mergeWith(other.Wireguard)
} }
func (v *VPN) OverrideWith(other VPN) { func (v *VPN) overrideWith(other VPN) {
v.Type = helpers.OverrideWithString(v.Type, other.Type) v.Type = helpers.OverrideWithString(v.Type, other.Type)
v.Provider.overrideWith(other.Provider) v.Provider.overrideWith(other.Provider)
v.OpenVPN.overrideWith(other.OpenVPN) v.OpenVPN.overrideWith(other.OpenVPN)
@@ -72,7 +73,7 @@ func (v *VPN) OverrideWith(other VPN) {
} }
func (v *VPN) setDefaults() { func (v *VPN) setDefaults() {
v.Type = helpers.DefaultString(v.Type, vpn.OpenVPN) v.Type = helpers.DefaultString(v.Type, constants.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()
@@ -87,7 +88,7 @@ func (v VPN) toLinesNode() (node *gotree.Node) {
node.AppendNode(v.Provider.toLinesNode()) node.AppendNode(v.Provider.toLinesNode())
if v.Type == vpn.OpenVPN { if v.Type == constants.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

@@ -2,11 +2,11 @@ package settings
import ( import (
"fmt" "fmt"
"net/netip" "net"
"regexp" "regexp"
"github.com/qdm12/gluetun/internal/configuration/settings/helpers" "github.com/qdm12/gluetun/internal/configuration/settings/helpers"
"github.com/qdm12/gluetun/internal/constants/providers" "github.com/qdm12/gluetun/internal/constants"
"github.com/qdm12/gotree" "github.com/qdm12/gotree"
"golang.zx2c4.com/wireguard/wgctrl/wgtypes" "golang.zx2c4.com/wireguard/wgctrl/wgtypes"
) )
@@ -22,29 +22,23 @@ type Wireguard struct {
// It cannot be nil in the internal state. // It cannot be nil in the internal state.
PreSharedKey *string PreSharedKey *string
// Addresses are the Wireguard interface addresses. // Addresses are the Wireguard interface addresses.
Addresses []netip.Prefix Addresses []net.IPNet
// Interface is the name of the Wireguard interface // Interface is the name of the Wireguard interface
// to create. It cannot be the empty string in the // to create. It cannot be the empty string in the
// internal state. // internal state.
Interface string Interface string
// Implementation is the Wireguard implementation to use.
// It can be "auto", "userspace" or "kernelspace".
// It defaults to "auto" and cannot be the empty string
// in the internal state.
Implementation string
} }
var regexpInterfaceName = regexp.MustCompile(`^[a-zA-Z0-9_]+$`) var regexpInterfaceName = regexp.MustCompile(`^[a-zA-Z0-9_]+$`)
// Validate validates Wireguard settings. // Validate validates Wireguard settings.
// 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, ipv6Supported bool) (err error) { func (w Wireguard) validate(vpnProvider string) (err error) {
if !helpers.IsOneOf(vpnProvider, if !helpers.IsOneOf(vpnProvider,
providers.Custom, constants.Custom,
providers.Ivpn, constants.Ivpn,
providers.Mullvad, constants.Mullvad,
providers.Surfshark, 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
@@ -52,19 +46,13 @@ func (w Wireguard) validate(vpnProvider string, ipv6Supported bool) (err error)
// Validate PrivateKey // Validate PrivateKey
if *w.PrivateKey == "" { if *w.PrivateKey == "" {
return fmt.Errorf("%w", ErrWireguardPrivateKeyNotSet) return ErrWireguardPrivateKeyNotSet
} }
_, err = wgtypes.ParseKey(*w.PrivateKey) _, err = wgtypes.ParseKey(*w.PrivateKey)
if err != nil { if err != nil {
return fmt.Errorf("private key is not valid: %w", err) return fmt.Errorf("private key is not valid: %w", err)
} }
if vpnProvider == providers.Airvpn {
if *w.PreSharedKey == "" {
return fmt.Errorf("%w", ErrWireguardPreSharedKeyNotSet)
}
}
// Validate PreSharedKey // Validate PreSharedKey
if *w.PreSharedKey != "" { // Note: this is optional if *w.PreSharedKey != "" { // Note: this is optional
_, err = wgtypes.ParseKey(*w.PreSharedKey) _, err = wgtypes.ParseKey(*w.PreSharedKey)
@@ -75,18 +63,13 @@ func (w Wireguard) validate(vpnProvider string, ipv6Supported bool) (err error)
// Validate Addresses // Validate Addresses
if len(w.Addresses) == 0 { if len(w.Addresses) == 0 {
return fmt.Errorf("%w", ErrWireguardInterfaceAddressNotSet) return ErrWireguardInterfaceAddressNotSet
} }
for i, ipNet := range w.Addresses { for i, ipNet := range w.Addresses {
if !ipNet.IsValid() { if ipNet.IP == nil || ipNet.Mask == nil {
return fmt.Errorf("%w: for address at index %d: %s", return fmt.Errorf("%w: for address at index %d: %s",
ErrWireguardInterfaceAddressNotSet, i, ipNet.String()) ErrWireguardInterfaceAddressNotSet, i, ipNet.String())
} }
if !ipv6Supported && ipNet.Addr().Is6() {
return fmt.Errorf("%w: address %s",
ErrWireguardInterfaceAddressIPv6, ipNet)
}
} }
// Validate interface // Validate interface
@@ -95,46 +78,36 @@ func (w Wireguard) validate(vpnProvider string, ipv6Supported bool) (err error)
ErrWireguardInterfaceNotValid, w.Interface, regexpInterfaceName) ErrWireguardInterfaceNotValid, w.Interface, regexpInterfaceName)
} }
validImplementations := []string{"auto", "userspace", "kernelspace"}
if !helpers.IsOneOf(w.Implementation, validImplementations...) {
return fmt.Errorf("%w: %s must be one of %s", ErrWireguardImplementationNotValid,
w.Implementation, helpers.ChoicesOrString(validImplementations))
}
return nil return nil
} }
func (w *Wireguard) copy() (copied Wireguard) { func (w *Wireguard) copy() (copied Wireguard) {
return Wireguard{ return Wireguard{
PrivateKey: helpers.CopyPointer(w.PrivateKey), PrivateKey: helpers.CopyStringPtr(w.PrivateKey),
PreSharedKey: helpers.CopyPointer(w.PreSharedKey), PreSharedKey: helpers.CopyStringPtr(w.PreSharedKey),
Addresses: helpers.CopySlice(w.Addresses), Addresses: helpers.CopyIPNetSlice(w.Addresses),
Interface: w.Interface, Interface: w.Interface,
Implementation: w.Implementation,
} }
} }
func (w *Wireguard) mergeWith(other Wireguard) { func (w *Wireguard) mergeWith(other Wireguard) {
w.PrivateKey = helpers.MergeWithPointer(w.PrivateKey, other.PrivateKey) w.PrivateKey = helpers.MergeWithStringPtr(w.PrivateKey, other.PrivateKey)
w.PreSharedKey = helpers.MergeWithPointer(w.PreSharedKey, other.PreSharedKey) w.PreSharedKey = helpers.MergeWithStringPtr(w.PreSharedKey, other.PreSharedKey)
w.Addresses = helpers.MergeSlices(w.Addresses, other.Addresses) w.Addresses = helpers.MergeIPNetsSlices(w.Addresses, other.Addresses)
w.Interface = helpers.MergeWithString(w.Interface, other.Interface) w.Interface = helpers.MergeWithString(w.Interface, other.Interface)
w.Implementation = helpers.MergeWithString(w.Implementation, other.Implementation)
} }
func (w *Wireguard) overrideWith(other Wireguard) { func (w *Wireguard) overrideWith(other Wireguard) {
w.PrivateKey = helpers.OverrideWithPointer(w.PrivateKey, other.PrivateKey) w.PrivateKey = helpers.OverrideWithStringPtr(w.PrivateKey, other.PrivateKey)
w.PreSharedKey = helpers.OverrideWithPointer(w.PreSharedKey, other.PreSharedKey) w.PreSharedKey = helpers.OverrideWithStringPtr(w.PreSharedKey, other.PreSharedKey)
w.Addresses = helpers.OverrideWithSlice(w.Addresses, other.Addresses) w.Addresses = helpers.OverrideWithIPNetsSlice(w.Addresses, other.Addresses)
w.Interface = helpers.OverrideWithString(w.Interface, other.Interface) w.Interface = helpers.OverrideWithString(w.Interface, other.Interface)
w.Implementation = helpers.OverrideWithString(w.Implementation, other.Implementation)
} }
func (w *Wireguard) setDefaults() { func (w *Wireguard) setDefaults() {
w.PrivateKey = helpers.DefaultPointer(w.PrivateKey, "") w.PrivateKey = helpers.DefaultStringPtr(w.PrivateKey, "")
w.PreSharedKey = helpers.DefaultPointer(w.PreSharedKey, "") w.PreSharedKey = helpers.DefaultStringPtr(w.PreSharedKey, "")
w.Interface = helpers.DefaultString(w.Interface, "wg0") w.Interface = helpers.DefaultString(w.Interface, "wg0")
w.Implementation = helpers.DefaultString(w.Implementation, "auto")
} }
func (w Wireguard) String() string { func (w Wireguard) String() string {
@@ -161,9 +134,5 @@ func (w Wireguard) toLinesNode() (node *gotree.Node) {
node.Appendf("Network interface: %s", w.Interface) node.Appendf("Network interface: %s", w.Interface)
if w.Implementation != "auto" {
node.Appendf("Implementation: %s", w.Implementation)
}
return node return node
} }

View File

@@ -2,10 +2,10 @@ package settings
import ( import (
"fmt" "fmt"
"net/netip" "net"
"github.com/qdm12/gluetun/internal/configuration/settings/helpers" "github.com/qdm12/gluetun/internal/configuration/settings/helpers"
"github.com/qdm12/gluetun/internal/constants/providers" "github.com/qdm12/gluetun/internal/constants"
"github.com/qdm12/gotree" "github.com/qdm12/gotree"
"golang.zx2c4.com/wireguard/wgctrl/wgtypes" "golang.zx2c4.com/wireguard/wgctrl/wgtypes"
) )
@@ -15,11 +15,11 @@ type WireguardSelection struct {
// It is only used with VPN providers generating Wireguard // It is only used with VPN providers generating Wireguard
// configurations specific to each server and user. // configurations specific to each server and user.
// To indicate it should not be used, it should be set // To indicate it should not be used, it should be set
// to netaddr.IPv4Unspecified(). It can never be the zero value // to the empty net.IP{} slice. It can never be nil
// in the internal state. // in the internal state.
EndpointIP netip.Addr EndpointIP net.IP
// EndpointPort is a the server port to use for the VPN server. // EndpointPort is a the server port to use for the VPN server.
// It is optional for VPN providers IVPN, Mullvad, Surfshark // It is optional for VPN providers IVPN, Mullvad
// and Windscribe, and compulsory for the others. // and Windscribe, and compulsory for the others.
// When optional, it can be set to 0 to indicate not use // When optional, it can be set to 0 to indicate not use
// a custom endpoint port. It cannot be nil in the internal // a custom endpoint port. It cannot be nil in the internal
@@ -36,12 +36,10 @@ 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 providers.Airvpn, providers.Ivpn, providers.Mullvad, case constants.Ivpn, constants.Mullvad, constants.Windscribe: // endpoint IP addresses are baked in
providers.Surfshark, providers.Windscribe: case constants.Custom:
// endpoint IP addresses are baked in if len(w.EndpointIP) == 0 {
case providers.Custom: return ErrWireguardEndpointIPNotSet
if !w.EndpointIP.IsValid() || w.EndpointIP.IsUnspecified() {
return fmt.Errorf("%w", ErrWireguardEndpointIPNotSet)
} }
default: // Providers not supporting Wireguard default: // Providers not supporting Wireguard
} }
@@ -49,30 +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 providers.Custom: case constants.Custom:
if *w.EndpointPort == 0 { if *w.EndpointPort == 0 {
return fmt.Errorf("%w", ErrWireguardEndpointPortNotSet) return ErrWireguardEndpointPortNotSet
} }
// EndpointPort cannot be set case constants.Ivpn, constants.Mullvad, constants.Windscribe:
case providers.Surfshark:
if *w.EndpointPort != 0 {
return fmt.Errorf("%w", ErrWireguardEndpointPortSet)
}
case providers.Airvpn, 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 == providers.Mullvad { if vpnProvider == constants.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 providers.Airvpn: case constants.Ivpn:
allowed = []uint16{1637, 47107}
case providers.Ivpn:
allowed = []uint16{2049, 2050, 53, 30587, 41893, 48574, 58237} allowed = []uint16{2049, 2050, 53, 30587, 41893, 48574, 58237}
case providers.Windscribe: case constants.Windscribe:
allowed = []uint16{53, 80, 123, 443, 1194, 65142} allowed = []uint16{53, 80, 123, 443, 1194, 65142}
} }
@@ -87,12 +78,10 @@ func (w WireguardSelection) validate(vpnProvider string) (err error) {
// Validate PublicKey // Validate PublicKey
switch vpnProvider { switch vpnProvider {
case providers.Ivpn, providers.Mullvad, case constants.Ivpn, constants.Mullvad, constants.Windscribe: // public keys are baked in
providers.Surfshark, providers.Windscribe: case constants.Custom:
// public keys are baked in
case providers.Custom:
if w.PublicKey == "" { if w.PublicKey == "" {
return fmt.Errorf("%w", ErrWireguardPublicKeyNotSet) return ErrWireguardPublicKeyNotSet
} }
default: // Providers not supporting Wireguard default: // Providers not supporting Wireguard
} }
@@ -109,27 +98,27 @@ func (w WireguardSelection) validate(vpnProvider string) (err error) {
func (w *WireguardSelection) copy() (copied WireguardSelection) { func (w *WireguardSelection) copy() (copied WireguardSelection) {
return WireguardSelection{ return WireguardSelection{
EndpointIP: w.EndpointIP, EndpointIP: helpers.CopyIP(w.EndpointIP),
EndpointPort: helpers.CopyPointer(w.EndpointPort), EndpointPort: helpers.CopyUint16Ptr(w.EndpointPort),
PublicKey: w.PublicKey, PublicKey: w.PublicKey,
} }
} }
func (w *WireguardSelection) mergeWith(other WireguardSelection) { func (w *WireguardSelection) mergeWith(other WireguardSelection) {
w.EndpointIP = helpers.MergeWithIP(w.EndpointIP, other.EndpointIP) w.EndpointIP = helpers.MergeWithIP(w.EndpointIP, other.EndpointIP)
w.EndpointPort = helpers.MergeWithPointer(w.EndpointPort, other.EndpointPort) w.EndpointPort = helpers.MergeWithUint16(w.EndpointPort, other.EndpointPort)
w.PublicKey = helpers.MergeWithString(w.PublicKey, other.PublicKey) w.PublicKey = helpers.MergeWithString(w.PublicKey, other.PublicKey)
} }
func (w *WireguardSelection) overrideWith(other WireguardSelection) { func (w *WireguardSelection) overrideWith(other WireguardSelection) {
w.EndpointIP = helpers.OverrideWithIP(w.EndpointIP, other.EndpointIP) w.EndpointIP = helpers.OverrideWithIP(w.EndpointIP, other.EndpointIP)
w.EndpointPort = helpers.OverrideWithPointer(w.EndpointPort, other.EndpointPort) w.EndpointPort = helpers.OverrideWithUint16(w.EndpointPort, other.EndpointPort)
w.PublicKey = helpers.OverrideWithString(w.PublicKey, other.PublicKey) w.PublicKey = helpers.OverrideWithString(w.PublicKey, other.PublicKey)
} }
func (w *WireguardSelection) setDefaults() { func (w *WireguardSelection) setDefaults() {
w.EndpointIP = helpers.DefaultIP(w.EndpointIP, netip.IPv4Unspecified()) w.EndpointIP = helpers.DefaultIP(w.EndpointIP, net.IP{})
w.EndpointPort = helpers.DefaultPointer(w.EndpointPort, 0) w.EndpointPort = helpers.DefaultUint16(w.EndpointPort, 0)
} }
func (w WireguardSelection) String() string { func (w WireguardSelection) String() string {
@@ -139,7 +128,7 @@ func (w WireguardSelection) String() string {
func (w WireguardSelection) toLinesNode() (node *gotree.Node) { func (w WireguardSelection) toLinesNode() (node *gotree.Node) {
node = gotree.New("Wireguard selection settings:") node = gotree.New("Wireguard selection settings:")
if !w.EndpointIP.IsUnspecified() { if len(w.EndpointIP) > 0 {
node.Appendf("Endpoint IP address: %s", w.EndpointIP) node.Appendf("Endpoint IP address: %s", w.EndpointIP)
} }

View File

@@ -2,13 +2,13 @@ package env
import ( import (
"fmt" "fmt"
"net/netip" "net"
"github.com/qdm12/gluetun/internal/configuration/settings" "github.com/qdm12/gluetun/internal/configuration/settings"
) )
func (s *Source) readDNS() (dns settings.DNS, err error) { func (r *Reader) readDNS() (dns settings.DNS, err error) {
dns.ServerAddress, err = s.readDNSServerAddress() dns.ServerAddress, err = r.readDNSServerAddress()
if err != nil { if err != nil {
return dns, err return dns, err
} }
@@ -18,7 +18,7 @@ func (s *Source) readDNS() (dns settings.DNS, err error) {
return dns, fmt.Errorf("environment variable DNS_KEEP_NAMESERVER: %w", err) return dns, fmt.Errorf("environment variable DNS_KEEP_NAMESERVER: %w", err)
} }
dns.DoT, err = s.readDoT() dns.DoT, err = r.readDoT()
if err != nil { if err != nil {
return dns, fmt.Errorf("DoT settings: %w", err) return dns, fmt.Errorf("DoT settings: %w", err)
} }
@@ -26,22 +26,22 @@ func (s *Source) readDNS() (dns settings.DNS, err error) {
return dns, nil return dns, nil
} }
func (s *Source) readDNSServerAddress() (address netip.Addr, err error) { func (r *Reader) readDNSServerAddress() (address net.IP, err error) {
key, value := s.getEnvWithRetro("DNS_ADDRESS", "DNS_PLAINTEXT_ADDRESS") key, s := r.getEnvWithRetro("DNS_ADDRESS", "DNS_PLAINTEXT_ADDRESS")
if value == "" { if s == "" {
return address, nil return nil, nil
} }
address, err = netip.ParseAddr(value) address = net.ParseIP(s)
if err != nil { if address == nil {
return address, fmt.Errorf("environment variable %s: %w", key, err) return nil, fmt.Errorf("environment variable %s: %w: %s", key, ErrIPAddressParse, s)
} }
// TODO remove in v4 // TODO remove in v4
if address.Unmap().Compare(netip.AddrFrom4([4]byte{127, 0, 0, 1})) != 0 { if !address.Equal(net.IPv4(127, 0, 0, 1)) { //nolint:gomnd
s.warner.Warn(key + " is set to " + value + r.warner.Warn(key + " is set to " + s +
" so the DNS over TLS (DoT) server will not be used." + " so the DNS over TLS (DoT) server will not be used." +
" The default value changed to 127.0.0.1 so it uses the internal DoT serves." + " The default value changed to 127.0.0.1 so it uses the internal DoT server." +
" If the DoT server fails to start, the IPv4 address of the first plaintext DNS server" + " If the DoT server fails to start, the IPv4 address of the first plaintext DNS server" +
" corresponding to the first DoT provider chosen is used.") " corresponding to the first DoT provider chosen is used.")
} }

View File

@@ -3,19 +3,19 @@ package env
import ( import (
"errors" "errors"
"fmt" "fmt"
"net/netip"
"github.com/qdm12/gluetun/internal/configuration/settings" "github.com/qdm12/gluetun/internal/configuration/settings"
"github.com/qdm12/govalid/binary" "github.com/qdm12/govalid/binary"
"inet.af/netaddr"
) )
func (s *Source) readDNSBlacklist() (blacklist settings.DNSBlacklist, err error) { func (r *Reader) readDNSBlacklist() (blacklist settings.DNSBlacklist, err error) {
blacklist.BlockMalicious, err = envToBoolPtr("BLOCK_MALICIOUS") blacklist.BlockMalicious, err = envToBoolPtr("BLOCK_MALICIOUS")
if err != nil { if err != nil {
return blacklist, fmt.Errorf("environment variable BLOCK_MALICIOUS: %w", err) return blacklist, fmt.Errorf("environment variable BLOCK_MALICIOUS: %w", err)
} }
blacklist.BlockSurveillance, err = s.readBlockSurveillance() blacklist.BlockSurveillance, err = r.readBlockSurveillance()
if err != nil { if err != nil {
return blacklist, err return blacklist, err
} }
@@ -36,8 +36,8 @@ func (s *Source) readDNSBlacklist() (blacklist settings.DNSBlacklist, err error)
return blacklist, nil return blacklist, nil
} }
func (s *Source) readBlockSurveillance() (blocked *bool, err error) { func (r *Reader) readBlockSurveillance() (blocked *bool, err error) {
key, value := s.getEnvWithRetro("BLOCK_SURVEILLANCE", "BLOCK_NSA") key, value := r.getEnvWithRetro("BLOCK_SURVEILLANCE", "BLOCK_NSA")
if value == "" { if value == "" {
return nil, nil //nolint:nilnil return nil, nil //nolint:nilnil
} }
@@ -55,24 +55,24 @@ var (
ErrPrivateAddressNotValid = errors.New("private address is not a valid IP or CIDR range") ErrPrivateAddressNotValid = errors.New("private address is not a valid IP or CIDR range")
) )
func readDoTPrivateAddresses() (ips []netip.Addr, func readDoTPrivateAddresses() (ips []netaddr.IP,
ipPrefixes []netip.Prefix, err error) { ipPrefixes []netaddr.IPPrefix, err error) {
privateAddresses := envToCSV("DOT_PRIVATE_ADDRESS") privateAddresses := envToCSV("DOT_PRIVATE_ADDRESS")
if len(privateAddresses) == 0 { if len(privateAddresses) == 0 {
return nil, nil, nil return nil, nil, nil
} }
ips = make([]netip.Addr, 0, len(privateAddresses)) ips = make([]netaddr.IP, 0, len(privateAddresses))
ipPrefixes = make([]netip.Prefix, 0, len(privateAddresses)) ipPrefixes = make([]netaddr.IPPrefix, 0, len(privateAddresses))
for _, privateAddress := range privateAddresses { for _, privateAddress := range privateAddresses {
ip, err := netip.ParseAddr(privateAddress) ip, err := netaddr.ParseIP(privateAddress)
if err == nil { if err == nil {
ips = append(ips, ip) ips = append(ips, ip)
continue continue
} }
ipPrefix, err := netip.ParsePrefix(privateAddress) ipPrefix, err := netaddr.ParseIPPrefix(privateAddress)
if err == nil { if err == nil {
ipPrefixes = append(ipPrefixes, ipPrefix) ipPrefixes = append(ipPrefixes, ipPrefix)
continue continue

View File

@@ -6,7 +6,7 @@ import (
"github.com/qdm12/gluetun/internal/configuration/settings" "github.com/qdm12/gluetun/internal/configuration/settings"
) )
func (s *Source) readDoT() (dot settings.DoT, err error) { func (r *Reader) readDoT() (dot settings.DoT, err error) {
dot.Enabled, err = envToBoolPtr("DOT") dot.Enabled, err = envToBoolPtr("DOT")
if err != nil { if err != nil {
return dot, fmt.Errorf("environment variable DOT: %w", err) return dot, fmt.Errorf("environment variable DOT: %w", err)
@@ -22,7 +22,7 @@ func (s *Source) readDoT() (dot settings.DoT, err error) {
return dot, err return dot, err
} }
dot.Blacklist, err = s.readDNSBlacklist() dot.Blacklist, err = r.readDNSBlacklist()
if err != nil { if err != nil {
return dot, err return dot, err
} }

View File

@@ -3,13 +3,13 @@ package env
import ( import (
"errors" "errors"
"fmt" "fmt"
"net/netip" "net"
"strconv" "strconv"
"github.com/qdm12/gluetun/internal/configuration/settings" "github.com/qdm12/gluetun/internal/configuration/settings"
) )
func (s *Source) readFirewall() (firewall settings.Firewall, err error) { func (r *Reader) readFirewall() (firewall settings.Firewall, err error) {
vpnInputPortStrings := envToCSV("FIREWALL_VPN_INPUT_PORTS") vpnInputPortStrings := envToCSV("FIREWALL_VPN_INPUT_PORTS")
firewall.VPNInputPorts, err = stringsToPorts(vpnInputPortStrings) firewall.VPNInputPorts, err = stringsToPorts(vpnInputPortStrings)
if err != nil { if err != nil {
@@ -22,9 +22,9 @@ func (s *Source) readFirewall() (firewall settings.Firewall, err error) {
return firewall, fmt.Errorf("environment variable FIREWALL_INPUT_PORTS: %w", err) return firewall, fmt.Errorf("environment variable FIREWALL_INPUT_PORTS: %w", err)
} }
outboundSubnetsKey, _ := s.getEnvWithRetro("FIREWALL_OUTBOUND_SUBNETS", "EXTRA_SUBNETS") outboundSubnetsKey, _ := r.getEnvWithRetro("FIREWALL_OUTBOUND_SUBNETS", "EXTRA_SUBNETS")
outboundSubnetStrings := envToCSV(outboundSubnetsKey) outboundSubnetStrings := envToCSV(outboundSubnetsKey)
firewall.OutboundSubnets, err = stringsToNetipPrefixes(outboundSubnetStrings) firewall.OutboundSubnets, err = stringsToIPNets(outboundSubnetStrings)
if err != nil { if err != nil {
return firewall, fmt.Errorf("environment variable %s: %w", outboundSubnetsKey, err) return firewall, fmt.Errorf("environment variable %s: %w", outboundSubnetsKey, err)
} }
@@ -65,16 +65,18 @@ func stringsToPorts(ss []string) (ports []uint16, err error) {
return ports, nil return ports, nil
} }
func stringsToNetipPrefixes(ss []string) (ipPrefixes []netip.Prefix, err error) { func stringsToIPNets(ss []string) (ipNets []net.IPNet, err error) {
if len(ss) == 0 { if len(ss) == 0 {
return nil, nil return nil, nil
} }
ipPrefixes = make([]netip.Prefix, len(ss)) ipNets = make([]net.IPNet, len(ss))
for i, s := range ss { for i, s := range ss {
ipPrefixes[i], err = netip.ParsePrefix(s) ip, ipNet, err := net.ParseCIDR(s)
if err != nil { if err != nil {
return nil, fmt.Errorf("parsing IP network %q: %w", s, err) return nil, fmt.Errorf("cannot parse IP network %q: %w", s, err)
} }
ipNet.IP = ip
ipNets[i] = *ipNet
} }
return ipPrefixes, nil return ipNets, nil
} }

View File

@@ -2,30 +2,24 @@ package env
import ( import (
"fmt" "fmt"
"os"
"time" "time"
"github.com/qdm12/gluetun/internal/configuration/settings" "github.com/qdm12/gluetun/internal/configuration/settings"
) )
func (s *Source) ReadHealth() (health settings.Health, err error) { func (r *Reader) ReadHealth() (health settings.Health, err error) {
health.ServerAddress = getCleanedEnv("HEALTH_SERVER_ADDRESS") health.ServerAddress = os.Getenv("HEALTH_SERVER_ADDRESS")
_, health.TargetAddress = s.getEnvWithRetro("HEALTH_TARGET_ADDRESS", "HEALTH_ADDRESS_TO_PING") _, health.TargetAddress = r.getEnvWithRetro("HEALTH_TARGET_ADDRESS", "HEALTH_ADDRESS_TO_PING")
successWaitPtr, err := envToDurationPtr("HEALTH_SUCCESS_WAIT_DURATION") health.VPN.Initial, err = r.readDurationWithRetro(
if err != nil {
return health, fmt.Errorf("environment variable HEALTH_SUCCESS_WAIT_DURATION: %w", err)
} else if successWaitPtr != nil {
health.SuccessWait = *successWaitPtr
}
health.VPN.Initial, err = s.readDurationWithRetro(
"HEALTH_VPN_DURATION_INITIAL", "HEALTH_VPN_DURATION_INITIAL",
"HEALTH_OPENVPN_DURATION_INITIAL") "HEALTH_OPENVPN_DURATION_INITIAL")
if err != nil { if err != nil {
return health, err return health, err
} }
health.VPN.Addition, err = s.readDurationWithRetro( health.VPN.Initial, 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 {
@@ -35,14 +29,14 @@ func (s *Source) ReadHealth() (health settings.Health, err error) {
return health, nil return health, nil
} }
func (s *Source) readDurationWithRetro(envKey, retroEnvKey string) (d *time.Duration, err error) { func (r *Reader) readDurationWithRetro(envKey, retroEnvKey string) (d *time.Duration, err error) {
envKey, value := s.getEnvWithRetro(envKey, retroEnvKey) envKey, s := r.getEnvWithRetro(envKey, retroEnvKey)
if value == "" { if s == "" {
return nil, nil //nolint:nilnil return nil, nil //nolint:nilnil
} }
d = new(time.Duration) d = new(time.Duration)
*d, err = time.ParseDuration(value) *d, err = time.ParseDuration(s)
if err != nil { if err != nil {
return nil, fmt.Errorf("environment variable %s: %w", envKey, err) return nil, fmt.Errorf("environment variable %s: %w", envKey, err)
} }

View File

@@ -1,6 +1,7 @@
package env package env
import ( import (
"encoding/base64"
"fmt" "fmt"
"os" "os"
"strconv" "strconv"
@@ -11,35 +12,24 @@ import (
"github.com/qdm12/govalid/integer" "github.com/qdm12/govalid/integer"
) )
// getCleanedEnv returns an environment variable value with
// surrounding spaces and trailing new line characters removed.
func getCleanedEnv(envKey string) (value string) {
value = os.Getenv(envKey)
value = strings.TrimSpace(value)
value = strings.TrimSuffix(value, "\r\n")
value = strings.TrimSuffix(value, "\n")
return value
}
func envToCSV(envKey string) (values []string) { func envToCSV(envKey string) (values []string) {
csv := getCleanedEnv(envKey) csv := os.Getenv(envKey)
if csv == "" { if csv == "" {
return nil return nil
} }
return lowerAndSplit(csv) return lowerAndSplit(csv)
} }
func envToFloat64(envKey string) (f float64, err error) { func envToInt(envKey string) (n int, err error) {
s := getCleanedEnv(envKey) s := os.Getenv(envKey)
if s == "" { if s == "" {
return 0, nil return 0, nil
} }
const bits = 64 return strconv.Atoi(s)
return strconv.ParseFloat(s, bits)
} }
func envToStringPtr(envKey string) (stringPtr *string) { func envToStringPtr(envKey string) (stringPtr *string) {
s := getCleanedEnv(envKey) s := os.Getenv(envKey)
if s == "" { if s == "" {
return nil return nil
} }
@@ -47,7 +37,7 @@ func envToStringPtr(envKey string) (stringPtr *string) {
} }
func envToBoolPtr(envKey string) (boolPtr *bool, err error) { func envToBoolPtr(envKey string) (boolPtr *bool, err error) {
s := getCleanedEnv(envKey) s := os.Getenv(envKey)
if s == "" { if s == "" {
return nil, nil //nolint:nilnil return nil, nil //nolint:nilnil
} }
@@ -59,7 +49,7 @@ func envToBoolPtr(envKey string) (boolPtr *bool, err error) {
} }
func envToIntPtr(envKey string) (intPtr *int, err error) { func envToIntPtr(envKey string) (intPtr *int, err error) {
s := getCleanedEnv(envKey) s := os.Getenv(envKey)
if s == "" { if s == "" {
return nil, nil //nolint:nilnil return nil, nil //nolint:nilnil
} }
@@ -71,7 +61,7 @@ func envToIntPtr(envKey string) (intPtr *int, err error) {
} }
func envToUint8Ptr(envKey string) (uint8Ptr *uint8, err error) { func envToUint8Ptr(envKey string) (uint8Ptr *uint8, err error) {
s := getCleanedEnv(envKey) s := os.Getenv(envKey)
if s == "" { if s == "" {
return nil, nil //nolint:nilnil return nil, nil //nolint:nilnil
} }
@@ -88,7 +78,7 @@ func envToUint8Ptr(envKey string) (uint8Ptr *uint8, err error) {
} }
func envToUint16Ptr(envKey string) (uint16Ptr *uint16, err error) { func envToUint16Ptr(envKey string) (uint16Ptr *uint16, err error) {
s := getCleanedEnv(envKey) s := os.Getenv(envKey)
if s == "" { if s == "" {
return nil, nil //nolint:nilnil return nil, nil //nolint:nilnil
} }
@@ -105,7 +95,7 @@ func envToUint16Ptr(envKey string) (uint16Ptr *uint16, err error) {
} }
func envToDurationPtr(envKey string) (durationPtr *time.Duration, err error) { func envToDurationPtr(envKey string) (durationPtr *time.Duration, err error) {
s := getCleanedEnv(envKey) s := os.Getenv(envKey)
if s == "" { if s == "" {
return nil, nil //nolint:nilnil return nil, nil //nolint:nilnil
} }
@@ -124,17 +114,26 @@ func lowerAndSplit(csv string) (values []string) {
return strings.Split(csv, ",") return strings.Split(csv, ",")
} }
func decodeBase64(b64String string) (decoded string, err error) {
b, err := base64.StdEncoding.DecodeString(b64String)
if err != nil {
return "", fmt.Errorf("cannot decode base64 string %q: %w",
b64String, err)
}
return string(b), nil
}
func unsetEnvKeys(envKeys []string, err error) (newErr error) { func unsetEnvKeys(envKeys []string, err error) (newErr error) {
newErr = err newErr = err
for _, envKey := range envKeys { for _, envKey := range envKeys {
unsetErr := os.Unsetenv(envKey) unsetErr := os.Unsetenv(envKey)
if unsetErr != nil && newErr == nil { if unsetErr != nil && newErr == nil {
newErr = fmt.Errorf("unsetting environment variable %s: %w", envKey, unsetErr) newErr = fmt.Errorf("cannot unset environment variable %s: %w", envKey, unsetErr)
} }
} }
return newErr return newErr
} }
func stringPtr(s string) *string { return &s } func stringPtr(s string) *string { return &s }
func uint32Ptr(n uint32) *uint32 { return &n } func uint16Ptr(n uint16) *uint16 { return &n }
func boolPtr(b bool) *bool { return &b } func boolPtr(b bool) *bool { return &b }

View File

@@ -7,12 +7,12 @@ import (
"github.com/qdm12/govalid/binary" "github.com/qdm12/govalid/binary"
) )
func (s *Source) readHTTPProxy() (httpProxy settings.HTTPProxy, err error) { func (r *Reader) readHTTPProxy() (httpProxy settings.HTTPProxy, err error) {
httpProxy.User = s.readHTTProxyUser() httpProxy.User = r.readHTTProxyUser()
httpProxy.Password = s.readHTTProxyPassword() httpProxy.Password = r.readHTTProxyPassword()
httpProxy.ListeningAddress = s.readHTTProxyListeningAddress() httpProxy.ListeningAddress = r.readHTTProxyListeningAddress()
httpProxy.Enabled, err = s.readHTTProxyEnabled() httpProxy.Enabled, err = r.readHTTProxyEnabled()
if err != nil { if err != nil {
return httpProxy, err return httpProxy, err
} }
@@ -22,7 +22,7 @@ func (s *Source) readHTTPProxy() (httpProxy settings.HTTPProxy, err error) {
return httpProxy, fmt.Errorf("environment variable HTTPPROXY_STEALTH: %w", err) return httpProxy, fmt.Errorf("environment variable HTTPPROXY_STEALTH: %w", err)
} }
httpProxy.Log, err = s.readHTTProxyLog() httpProxy.Log, err = r.readHTTProxyLog()
if err != nil { if err != nil {
return httpProxy, err return httpProxy, err
} }
@@ -30,38 +30,38 @@ func (s *Source) readHTTPProxy() (httpProxy settings.HTTPProxy, err error) {
return httpProxy, nil return httpProxy, nil
} }
func (s *Source) readHTTProxyUser() (user *string) { func (r *Reader) readHTTProxyUser() (user *string) {
_, value := s.getEnvWithRetro("HTTPPROXY_USER", "PROXY_USER", "TINYPROXY_USER") _, s := r.getEnvWithRetro("HTTPPROXY_USER", "PROXY_USER", "TINYPROXY_USER")
if value != "" { if s != "" {
return &value return &s
} }
return nil return nil
} }
func (s *Source) readHTTProxyPassword() (user *string) { func (r *Reader) readHTTProxyPassword() (user *string) {
_, value := s.getEnvWithRetro("HTTPPROXY_PASSWORD", "PROXY_PASSWORD", "TINYPROXY_PASSWORD") _, s := r.getEnvWithRetro("HTTPPROXY_PASSWORD", "PROXY_PASSWORD", "TINYPROXY_PASSWORD")
if value != "" { if s != "" {
return &value return &s
} }
return nil return nil
} }
func (s *Source) readHTTProxyListeningAddress() (listeningAddress string) { func (r *Reader) readHTTProxyListeningAddress() (listeningAddress string) {
key, value := s.getEnvWithRetro("HTTPPROXY_LISTENING_ADDRESS", "PROXY_PORT", "TINYPROXY_PORT", "HTTPPROXY_PORT") key, value := r.getEnvWithRetro("HTTPPROXY_LISTENING_ADDRESS", "PROXY_PORT", "TINYPROXY_PORT", "HTTPPROXY_PORT")
if key == "HTTPPROXY_LISTENING_ADDRESS" { if key == "HTTPPROXY_LISTENING_ADDRESS" {
return value return value
} }
return ":" + value return ":" + value
} }
func (s *Source) readHTTProxyEnabled() (enabled *bool, err error) { func (r *Reader) readHTTProxyEnabled() (enabled *bool, err error) {
key, value := s.getEnvWithRetro("HTTPPROXY", "PROXY", "TINYPROXY") key, s := r.getEnvWithRetro("HTTPPROXY", "PROXY", "TINYPROXY")
if value == "" { if s == "" {
return nil, nil //nolint:nilnil return nil, nil //nolint:nilnil
} }
enabled = new(bool) enabled = new(bool)
*enabled, err = binary.Validate(value) *enabled, err = binary.Validate(s)
if err != nil { if err != nil {
return nil, fmt.Errorf("environment variable %s: %w", key, err) return nil, fmt.Errorf("environment variable %s: %w", key, err)
} }
@@ -69,9 +69,9 @@ func (s *Source) readHTTProxyEnabled() (enabled *bool, err error) {
return enabled, nil return enabled, nil
} }
func (s *Source) readHTTProxyLog() (enabled *bool, err error) { func (r *Reader) readHTTProxyLog() (enabled *bool, err error) {
key, value := s.getEnvWithRetro("HTTPPROXY_LOG", "PROXY_LOG_LEVEL", "TINYPROXY_LOG") key, s := r.getEnvWithRetro("HTTPPROXY_LOG", "PROXY_LOG_LEVEL", "TINYPROXY_LOG")
if value == "" { if s == "" {
return nil, nil //nolint:nilnil return nil, nil //nolint:nilnil
} }
@@ -82,7 +82,7 @@ func (s *Source) readHTTProxyLog() (enabled *bool, err error) {
} }
enabled = new(bool) enabled = new(bool)
*enabled, err = binary.Validate(value, binaryOptions...) *enabled, err = binary.Validate(s, binaryOptions...)
if err != nil { if err != nil {
return nil, fmt.Errorf("environment variable %s: %w", key, err) return nil, fmt.Errorf("environment variable %s: %w", key, err)
} }

View File

@@ -3,10 +3,11 @@ package env
import ( import (
"errors" "errors"
"fmt" "fmt"
"os"
"strings" "strings"
"github.com/qdm12/gluetun/internal/configuration/settings" "github.com/qdm12/gluetun/internal/configuration/settings"
"github.com/qdm12/log" "github.com/qdm12/golibs/logging"
) )
func readLog() (log settings.Log, err error) { func readLog() (log settings.Log, err error) {
@@ -18,13 +19,13 @@ func readLog() (log settings.Log, err error) {
return log, nil return log, nil
} }
func readLogLevel() (level *log.Level, err error) { func readLogLevel() (level *logging.Level, err error) {
s := getCleanedEnv("LOG_LEVEL") s := os.Getenv("LOG_LEVEL")
if s == "" { if s == "" {
return nil, nil //nolint:nilnil return nil, nil //nolint:nilnil
} }
level = new(log.Level) level = new(logging.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)
@@ -35,16 +36,16 @@ func readLogLevel() (level *log.Level, err error) {
var ErrLogLevelUnknown = errors.New("log level is unknown") var ErrLogLevelUnknown = errors.New("log level is unknown")
func parseLogLevel(s string) (level log.Level, err error) { func parseLogLevel(s string) (level logging.Level, err error) {
switch strings.ToLower(s) { switch strings.ToLower(s) {
case "debug": case "debug":
return log.LevelDebug, nil return logging.LevelDebug, nil
case "info": case "info":
return log.LevelInfo, nil return logging.LevelInfo, nil
case "warning": case "warning":
return log.LevelWarn, nil return logging.LevelWarn, nil
case "error": case "error":
return log.LevelError, nil return logging.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

@@ -2,51 +2,60 @@ package env
import ( import (
"fmt" "fmt"
"os"
"strings" "strings"
"github.com/qdm12/gluetun/internal/configuration/settings" "github.com/qdm12/gluetun/internal/configuration/settings"
"github.com/qdm12/govalid/binary" "github.com/qdm12/govalid/binary"
) )
func (s *Source) readOpenVPN() ( func (r *Reader) readOpenVPN() (
openVPN settings.OpenVPN, err error) { openVPN settings.OpenVPN, err error) {
defer func() { defer func() {
err = unsetEnvKeys([]string{"OPENVPN_KEY", "OPENVPN_CERT", err = unsetEnvKeys([]string{"OPENVPN_CLIENTKEY", "OPENVPN_CLIENTCRT"}, err)
"OPENVPN_KEY_PASSPHRASE", "OPENVPN_ENCRYPTED_KEY"}, err)
}() }()
openVPN.Version = getCleanedEnv("OPENVPN_VERSION") openVPN.Version = os.Getenv("OPENVPN_VERSION")
openVPN.User = s.readOpenVPNUser() openVPN.User = r.readOpenVPNUser()
openVPN.Password = s.readOpenVPNPassword() openVPN.Password = r.readOpenVPNPassword()
confFile := getCleanedEnv("OPENVPN_CUSTOM_CONFIG") confFile := os.Getenv("OPENVPN_CUSTOM_CONFIG")
if confFile != "" { if confFile != "" {
openVPN.ConfFile = &confFile openVPN.ConfFile = &confFile
} }
ciphersKey, _ := s.getEnvWithRetro("OPENVPN_CIPHERS", "OPENVPN_CIPHER") ciphersKey, _ := r.getEnvWithRetro("OPENVPN_CIPHERS", "OPENVPN_CIPHER")
openVPN.Ciphers = envToCSV(ciphersKey) openVPN.Ciphers = envToCSV(ciphersKey)
auth := getCleanedEnv("OPENVPN_AUTH") auth := os.Getenv("OPENVPN_AUTH")
if auth != "" { if auth != "" {
openVPN.Auth = &auth openVPN.Auth = &auth
} }
openVPN.Cert = envToStringPtr("OPENVPN_CERT") openVPN.ClientCrt, err = readBase64OrNil("OPENVPN_CLIENTCRT")
openVPN.Key = envToStringPtr("OPENVPN_KEY") if err != nil {
openVPN.EncryptedKey = envToStringPtr("OPENVPN_ENCRYPTED_KEY") return openVPN, fmt.Errorf("environment variable OPENVPN_CLIENTCRT: %w", err)
}
openVPN.KeyPassphrase = s.readOpenVPNKeyPassphrase() openVPN.ClientKey, err = readBase64OrNil("OPENVPN_CLIENTKEY")
if err != nil {
return openVPN, fmt.Errorf("environment variable OPENVPN_CLIENTKEY: %w", err)
}
openVPN.PIAEncPreset = s.readPIAEncryptionPreset() openVPN.PIAEncPreset = r.readPIAEncryptionPreset()
openVPN.IPv6, err = envToBoolPtr("OPENVPN_IPV6")
if err != nil {
return openVPN, fmt.Errorf("environment variable OPENVPN_IPV6: %w", err)
}
openVPN.MSSFix, err = envToUint16Ptr("OPENVPN_MSSFIX") openVPN.MSSFix, err = envToUint16Ptr("OPENVPN_MSSFIX")
if err != nil { if err != nil {
return openVPN, fmt.Errorf("environment variable OPENVPN_MSSFIX: %w", err) return openVPN, fmt.Errorf("environment variable OPENVPN_MSSFIX: %w", err)
} }
_, openVPN.Interface = s.getEnvWithRetro("VPN_INTERFACE", "OPENVPN_INTERFACE") _, openVPN.Interface = r.getEnvWithRetro("VPN_INTERFACE", "OPENVPN_INTERFACE")
openVPN.ProcessUser, err = s.readOpenVPNProcessUser() openVPN.ProcessUser, err = r.readOpenVPNProcessUser()
if err != nil { if err != nil {
return openVPN, err return openVPN, err
} }
@@ -56,47 +65,36 @@ func (s *Source) readOpenVPN() (
return openVPN, fmt.Errorf("environment variable OPENVPN_VERBOSITY: %w", err) return openVPN, fmt.Errorf("environment variable OPENVPN_VERBOSITY: %w", err)
} }
flagsStr := getCleanedEnv("OPENVPN_FLAGS")
if flagsStr != "" {
openVPN.Flags = strings.Fields(flagsStr)
}
return openVPN, nil return openVPN, nil
} }
func (s *Source) readOpenVPNUser() (user *string) { func (r *Reader) readOpenVPNUser() (user string) {
user = new(string) _, user = r.getEnvWithRetro("OPENVPN_USER", "USER")
_, *user = s.getEnvWithRetro("OPENVPN_USER", "USER")
if *user == "" {
return nil
}
// Remove spaces in user ID to simplify user's life, thanks @JeordyR // Remove spaces in user ID to simplify user's life, thanks @JeordyR
*user = strings.ReplaceAll(*user, " ", "") return strings.ReplaceAll(user, " ", "")
return user
} }
func (s *Source) readOpenVPNPassword() (password *string) { func (r *Reader) readOpenVPNPassword() (password string) {
password = new(string) _, password = r.getEnvWithRetro("OPENVPN_PASSWORD", "PASSWORD")
_, *password = s.getEnvWithRetro("OPENVPN_PASSWORD", "PASSWORD")
if *password == "" {
return nil
}
return password return password
} }
func (s *Source) readOpenVPNKeyPassphrase() (passphrase *string) { func readBase64OrNil(envKey string) (valueOrNil *string, err error) {
passphrase = new(string) value := os.Getenv(envKey)
*passphrase = getCleanedEnv("OPENVPN_KEY_PASSPHRASE") if value == "" {
if *passphrase == "" { return nil, nil //nolint:nilnil
return nil
} }
return passphrase
decoded, err := decodeBase64(value)
if err != nil {
return nil, err
}
return &decoded, nil
} }
func (s *Source) readPIAEncryptionPreset() (presetPtr *string) { func (r *Reader) readPIAEncryptionPreset() (presetPtr *string) {
_, preset := s.getEnvWithRetro( _, preset := r.getEnvWithRetro(
"PRIVATE_INTERNET_ACCESS_OPENVPN_ENCRYPTION_PRESET", "PRIVATE_INTERNET_ACCESS_OPENVPN_ENCRYPTION_PRESET",
"PIA_ENCRYPTION", "ENCRYPTION") "PIA_ENCRYPTION", "ENCRYPTION")
if preset != "" { if preset != "" {
@@ -105,8 +103,8 @@ func (s *Source) readPIAEncryptionPreset() (presetPtr *string) {
return nil return nil
} }
func (s *Source) readOpenVPNProcessUser() (processUser string, err error) { func (r *Reader) readOpenVPNProcessUser() (processUser string, err error) {
key, value := s.getEnvWithRetro("OPENVPN_PROCESS_USER", "OPENVPN_ROOT") key, value := r.getEnvWithRetro("OPENVPN_PROCESS_USER", "OPENVPN_ROOT")
if key == "OPENVPN_PROCESS_USER" { if key == "OPENVPN_PROCESS_USER" {
return value, nil return value, nil
} }

View File

@@ -3,6 +3,7 @@ package env
import ( import (
"errors" "errors"
"fmt" "fmt"
"os"
"strings" "strings"
"github.com/qdm12/gluetun/internal/configuration/settings" "github.com/qdm12/gluetun/internal/configuration/settings"
@@ -10,32 +11,32 @@ import (
"github.com/qdm12/govalid/port" "github.com/qdm12/govalid/port"
) )
func (s *Source) readOpenVPNSelection() ( func (r *Reader) readOpenVPNSelection() (
selection settings.OpenVPNSelection, err error) { selection settings.OpenVPNSelection, err error) {
confFile := getCleanedEnv("OPENVPN_CUSTOM_CONFIG") confFile := os.Getenv("OPENVPN_CUSTOM_CONFIG")
if confFile != "" { if confFile != "" {
selection.ConfFile = &confFile selection.ConfFile = &confFile
} }
selection.TCP, err = s.readOpenVPNProtocol() selection.TCP, err = r.readOpenVPNProtocol()
if err != nil { if err != nil {
return selection, err return selection, err
} }
selection.CustomPort, err = s.readOpenVPNCustomPort() selection.CustomPort, err = r.readOpenVPNCustomPort()
if err != nil { if err != nil {
return selection, err return selection, err
} }
selection.PIAEncPreset = s.readPIAEncryptionPreset() selection.PIAEncPreset = r.readPIAEncryptionPreset()
return selection, nil return selection, nil
} }
var ErrOpenVPNProtocolNotValid = errors.New("OpenVPN protocol is not valid") var ErrOpenVPNProtocolNotValid = errors.New("OpenVPN protocol is not valid")
func (s *Source) readOpenVPNProtocol() (tcp *bool, err error) { func (r *Reader) readOpenVPNProtocol() (tcp *bool, err error) {
envKey, protocol := s.getEnvWithRetro("OPENVPN_PROTOCOL", "PROTOCOL") envKey, protocol := r.getEnvWithRetro("OPENVPN_PROTOCOL", "PROTOCOL")
switch strings.ToLower(protocol) { switch strings.ToLower(protocol) {
case "": case "":
@@ -50,14 +51,14 @@ func (s *Source) readOpenVPNProtocol() (tcp *bool, err error) {
} }
} }
func (s *Source) readOpenVPNCustomPort() (customPort *uint16, err error) { func (r *Reader) readOpenVPNCustomPort() (customPort *uint16, err error) {
key, value := s.getEnvWithRetro("VPN_ENDPOINT_PORT", "PORT", "OPENVPN_PORT") key, s := r.getEnvWithRetro("VPN_ENDPOINT_PORT", "PORT", "OPENVPN_PORT")
if value == "" { if s == "" {
return nil, nil //nolint:nilnil return nil, nil //nolint:nilnil
} }
customPort = new(uint16) customPort = new(uint16)
*customPort, err = port.Validate(value) *customPort, err = port.Validate(s)
if err != nil { if err != nil {
return nil, fmt.Errorf("environment variable %s: %w", key, err) return nil, fmt.Errorf("environment variable %s: %w", key, err)
} }

View File

@@ -6,10 +6,9 @@ import (
"github.com/qdm12/gluetun/internal/configuration/settings" "github.com/qdm12/gluetun/internal/configuration/settings"
) )
func (s *Source) readPortForward() ( func (r *Reader) readPortForward() (
portForwarding settings.PortForwarding, err error) { portForwarding settings.PortForwarding, err error) {
key, _ := s.getEnvWithRetro( key, _ := r.getEnvWithRetro(
"VPN_PORT_FORWARDING",
"PRIVATE_INTERNET_ACCESS_VPN_PORT_FORWARDING", "PRIVATE_INTERNET_ACCESS_VPN_PORT_FORWARDING",
"PORT_FORWARDING") "PORT_FORWARDING")
portForwarding.Enabled, err = envToBoolPtr(key) portForwarding.Enabled, err = envToBoolPtr(key)
@@ -17,8 +16,7 @@ func (s *Source) readPortForward() (
return portForwarding, fmt.Errorf("environment variable %s: %w", key, err) return portForwarding, fmt.Errorf("environment variable %s: %w", key, err)
} }
_, value := s.getEnvWithRetro( _, value := r.getEnvWithRetro(
"VPN_PORT_FORWARDING_STATUS_FILE",
"PRIVATE_INTERNET_ACCESS_VPN_PORT_FORWARDING_STATUS_FILE", "PRIVATE_INTERNET_ACCESS_VPN_PORT_FORWARDING_STATUS_FILE",
"PORT_FORWARDING_STATUS_FILE") "PORT_FORWARDING_STATUS_FILE")
if value != "" { if value != "" {

View File

@@ -2,6 +2,7 @@ package env
import ( import (
"fmt" "fmt"
"os"
"github.com/qdm12/gluetun/internal/pprof" "github.com/qdm12/gluetun/internal/pprof"
) )
@@ -12,17 +13,17 @@ func readPprof() (settings pprof.Settings, err error) {
return settings, fmt.Errorf("environment variable PPROF_ENABLED: %w", err) return settings, fmt.Errorf("environment variable PPROF_ENABLED: %w", err)
} }
settings.BlockProfileRate, err = envToIntPtr("PPROF_BLOCK_PROFILE_RATE") settings.BlockProfileRate, err = envToInt("PPROF_BLOCK_PROFILE_RATE")
if err != nil { if err != nil {
return settings, fmt.Errorf("environment variable PPROF_BLOCK_PROFILE_RATE: %w", err) return settings, fmt.Errorf("environment variable PPROF_BLOCK_PROFILE_RATE: %w", err)
} }
settings.MutexProfileRate, err = envToIntPtr("PPROF_MUTEX_PROFILE_RATE") settings.MutexProfileRate, err = envToInt("PPROF_MUTEX_PROFILE_RATE")
if err != nil { if err != nil {
return settings, fmt.Errorf("environment variable PPROF_MUTEX_PROFILE_RATE: %w", err) return settings, fmt.Errorf("environment variable PPROF_MUTEX_PROFILE_RATE: %w", err)
} }
settings.HTTPServer.Address = getCleanedEnv("PPROF_HTTP_SERVER_ADDRESS") settings.HTTPServer.Address = os.Getenv("PPROF_HTTP_SERVER_ADDRESS")
return settings, nil return settings, nil
} }

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