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
677 changed files with 82235 additions and 130383 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,16 +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 # Git config
- ~/.gitconfig:/root/.gitconfig - ~/.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,34 +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
linters: linters:
enable: enable:
# - cyclop # - cyclop
# - errorlint # - errorlint
- asasalint # - ireturn
# - varnamelen
# - wrapcheck
- asciicheck - asciicheck
- bidichk - bidichk
- bodyclose - bodyclose
@@ -45,7 +57,6 @@ linters:
- durationcheck - durationcheck
- errchkjson - errchkjson
- errname - errname
- execinquery
- exhaustive - exhaustive
- exportloopref - exportloopref
- forcetypeassert - forcetypeassert
@@ -65,9 +76,8 @@ linters:
- goprintffuncname - goprintffuncname
- gosec - gosec
- grouper - grouper
- ifshort
- importas - importas
- interfacebloat
- ireturn
- lll - lll
- maintidx - maintidx
- makezero - makezero
@@ -78,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
@@ -91,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.51.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= \
@@ -111,25 +104,16 @@ ENV VPN_SERVICE_PROVIDER=pia \
PRIVATE_INTERNET_ACCESS_VPN_PORT_FORWARDING=off \ PRIVATE_INTERNET_ACCESS_VPN_PORT_FORWARDING=off \
PRIVATE_INTERNET_ACCESS_VPN_PORT_FORWARDING_STATUS_FILE="/tmp/gluetun/forwarded_port" \ PRIVATE_INTERNET_ACCESS_VPN_PORT_FORWARDING_STATUS_FILE="/tmp/gluetun/forwarded_port" \
# # Cyberghost only: # # Cyberghost only:
OPENVPN_CERT= \
OPENVPN_KEY= \
OPENVPN_CLIENTCRT_SECRETFILE=/run/secrets/openvpn_clientcrt \ OPENVPN_CLIENTCRT_SECRETFILE=/run/secrets/openvpn_clientcrt \
OPENVPN_CLIENTKEY_SECRETFILE=/run/secrets/openvpn_clientkey \ OPENVPN_CLIENTKEY_SECRETFILE=/run/secrets/openvpn_clientkey \
# # VPNSecure only:
OPENVPN_ENCRYPTED_KEY= \
OPENVPN_ENCRYPTED_KEY_SECRETFILE=/run/secrets/openvpn_encrypted_key \
OPENVPN_KEY_PASSPHRASE= \
OPENVPN_KEY_PASSPHRASE_SECRETFILE=/run/secrets/openvpn_key_passphrase \
# # Nordvpn only: # # Nordvpn only:
SERVER_NUMBER= \ SERVER_NUMBER= \
# # PIA only: # # PIA 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,7 +124,7 @@ ENV VPN_SERVICE_PROVIDER=pia \
LOG_LEVEL=info \ LOG_LEVEL=info \
# Health # Health
HEALTH_SERVER_ADDRESS=127.0.0.1:9999 \ HEALTH_SERVER_ADDRESS=127.0.0.1:9999 \
HEALTH_TARGET_ADDRESS=cloudflare.com:443 \ HEALTH_TARGET_ADDRESS=github.com:443 \
HEALTH_VPN_DURATION_INITIAL=6s \ HEALTH_VPN_DURATION_INITIAL=6s \
HEALTH_VPN_DURATION_ADDITION=5s \ HEALTH_VPN_DURATION_ADDITION=5s \
# DNS over TLS # DNS over TLS
@@ -178,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" \
@@ -197,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())
} }
}() }()
@@ -335,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?
@@ -360,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(
@@ -390,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))
@@ -404,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
@@ -435,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)
@@ -453,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))
@@ -505,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
}

40
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.14.1 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,38 +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.1.1-0.20211129163951-9ada19101fc5 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/net v0.0.0-20220418201149-a630d4f3e7a2 golang.zx2c4.com/wireguard v0.0.0-20210805125648-3957e9b9dd19
golang.org/x/sys v0.6.0 golang.zx2c4.com/wireguard/wgctrl v0.0.0-20210803171230-4253848d036c
golang.org/x/text v0.8.0
golang.zx2c4.com/wireguard v0.0.0-20220703234212-c31a7b1ab478
golang.zx2c4.com/wireguard/wgctrl v0.0.0-20220504211119-3d4a969bb56b
inet.af/netaddr v0.0.0-20210718074554-06ca8145d722 inet.af/netaddr v0.0.0-20210718074554-06ca8145d722
) )
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.7 // 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.0 // 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-20210108033219-3eb7198706b2 // 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.0.0-20220411220226-7b82a4e95df4 // indirect golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97 // indirect
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c // indirect golang.org/x/net v0.0.0-20210504132125-bbd867fde50d // indirect
golang.zx2c4.com/wintun v0.0.0-20211104114900-415007cec224 // indirect gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
) )

139
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.14.1 h1:qfhVLaG5s+nCROl1zJsZRxFeYrHLqWroPOQ8BWiNb4w= github.com/fatih/color v1.13.0 h1:8LOYc1KYPPmyKMuN8QV2DNRWNbLo6LZ0iLs8+mlH53w=
github.com/fatih/color v1.14.1/go.mod h1:2oHN61fhTpgcxD3TSWCgKDiH1+x4OiDVVGH8WlgGZGg= 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,16 +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/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 h1:81/ik6ipDQS2aGcBfIN5dHDB36BwrStyeAQquSYCV4o= github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.7/go.mod h1:n+brtR0CgQNWTVd5ZUFpTBC8YFBDLK/h/bpaJ8/DtOE= github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
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=
@@ -56,22 +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 h1:rOHX5yl7qnlpiVkFWoqccueppMtXzeziFjWAjLg6sz0= github.com/mdlayher/genetlink v1.0.0/go.mod h1:0rJ0h4itni50A86M2kHcgS85ttZazNt7a8H2a2cw0Gc=
github.com/mdlayher/netlink v1.6.0/go.mod h1:0o3PlBmGst1xve7wQ7j/hwpNaFaH4qCRyWCdcZk8/vA= github.com/mdlayher/netlink v0.0.0-20190409211403-11939a169225/go.mod h1:eQB3mZE4aiYnlUsyGGCOpPETfdQq4Jhsgf1fk3cwQaA=
github.com/mdlayher/socket v0.1.1/go.mod h1:mYV5YIZAfHh4dzDVzI8x8tWLWCliuX8Mon5Awbj+qDs= github.com/mdlayher/netlink v1.0.0/go.mod h1:KxeJAFOFLG6AjpyDkQ/iIhxygIUKD+vcwqcnu43w/+M=
github.com/mdlayher/socket v0.2.3 h1:XZA2X2TjdOwNoNPVPclRCURoX/hokBY8nkTmRZFEheM= github.com/mdlayher/netlink v1.1.0/go.mod h1:H4WCitaheIsdF9yOYu8CFmCgQthAPIWZmcKp9uZHgmY=
github.com/mdlayher/socket v0.2.3/go.mod h1:bz12/FozYNH/VbvC3q7TRIK/Y6dH1kCKsXaUeXi/FmY= github.com/mdlayher/netlink v1.1.1/go.mod h1:WTYpFb/WTvlRJAyKhZL5/uy69TDDpHHu2VZmb2XgV7o=
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=
@@ -96,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=
@@ -108,92 +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/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
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.1.1-0.20211129163951-9ada19101fc5 h1:b/k/BVWzWRS5v6AB0gf2ckFSbFsHN5jR0HoNso1pN+w= github.com/vishvananda/netlink v1.1.1-0.20211129163951-9ada19101fc5 h1:b/k/BVWzWRS5v6AB0gf2ckFSbFsHN5jR0HoNso1pN+w=
github.com/vishvananda/netlink v1.1.1-0.20211129163951-9ada19101fc5/go.mod h1:twkDnbuQxJYemMlGd4JFIcuhgX83tXhKS2B/PRMpOho= github.com/vishvananda/netlink v1.1.1-0.20211129163951-9ada19101fc5/go.mod h1:twkDnbuQxJYemMlGd4JFIcuhgX83tXhKS2B/PRMpOho=
github.com/vishvananda/netns v0.0.0-20200728191858-db3c7e526aae h1:4hwBBUfQCFe3Cym0ZtKyq7L16eZUtYKs+BaHDN6mAns= github.com/vishvananda/netns v0.0.0-20200728191858-db3c7e526aae h1:4hwBBUfQCFe3Cym0ZtKyq7L16eZUtYKs+BaHDN6mAns=
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 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/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-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.0.0-20220411220226-7b82a4e95df4 h1:kUhD7nTDoI3fVd9G4ORWrbV5NY0liEs/Jg2pv5f+bBA=
golang.org/x/crypto v0.0.0-20220411220226-7b82a4e95df4/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
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-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-20220418201149-a630d4f3e7a2 h1:6mzvA99KwZxbOrxww4EvWVQUnN1+xEu9tafK5ZxkYeA=
golang.org/x/net v0.0.0-20220418201149-a630d4f3e7a2/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c h1:5KslGYwFpkhGh+Q16bwMP3cOontH8FOep7tGV86Y7SQ= golang.org/x/sync v0.0.0-20210220032951-036812b2e83c h1:5KslGYwFpkhGh+Q16bwMP3cOontH8FOep7tGV86Y7SQ=
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/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-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.6.0 h1:MVltZSvRTcU2ljQOhs94SXPftV6DCNnZViHeQps87pQ=
golang.org/x/sys v0.6.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.8.0 h1:57P1ETyNKtuIjB4SRd15iJxuhj8Gc416Y78H3qgMh68=
golang.org/x/text v0.8.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
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=
@@ -205,12 +230,11 @@ golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8T
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 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-20211104114900-415007cec224 h1:Ug9qvr1myri/zFN6xL17LSCBGFDnphBBhzmILHsM5TY= golang.zx2c4.com/wireguard v0.0.0-20210427022245-097af6e1351b/go.mod h1:a057zjmoc00UN7gVkaJt2sXVK523kMJcogDTEvPIasg=
golang.zx2c4.com/wintun v0.0.0-20211104114900-415007cec224/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-20220703234212-c31a7b1ab478 h1:vDy//hdR+GnROE3OdYbQKt9rdtNdHkDtONvpRwmls/0= golang.zx2c4.com/wireguard v0.0.0-20210805125648-3957e9b9dd19/go.mod h1:laHzsbfMhGSobUmruXWAyMKKHSqvIcrqZJMyHD+/3O8=
golang.zx2c4.com/wireguard v0.0.0-20220703234212-c31a7b1ab478/go.mod h1:bVQfyl2sCM/QIIGHpWbFGfHPuDvqnCNkT6MQLTCjO/U= golang.zx2c4.com/wireguard/wgctrl v0.0.0-20210803171230-4253848d036c h1:ADNrRDI5NR23/TUCnEmlLZLt4u9DnZ2nwRkPrAcFvto=
golang.zx2c4.com/wireguard/wgctrl v0.0.0-20220504211119-3d4a969bb56b h1:9JncmKXcUwE918my+H6xmjBdhK2jM/UTUNXxhRG1BAk= golang.zx2c4.com/wireguard/wgctrl v0.0.0-20210803171230-4253848d036c/go.mod h1:+1XihzyZUBJcSc5WO9SwNA7v26puQwOEDwanaxfNXPQ=
golang.zx2c4.com/wireguard/wgctrl v0.0.0-20220504211119-3d4a969bb56b/go.mod h1:yp4gl6zOlnDGOZeWeDfMwQcsdOIQnMdhuPx9mwwWBL4=
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=
@@ -220,9 +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=
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-20210718074554-06ca8145d722 h1:Qws2rZnQudC58cIagVucPQDLmMi3kAXgxscsgD0v6DU= inet.af/netaddr v0.0.0-20210718074554-06ca8145d722 h1:Qws2rZnQudC58cIagVucPQDLmMi3kAXgxscsgD0v6DU=
inet.af/netaddr v0.0.0-20210718074554-06ca8145d722/go.mod h1:z0nx+Dh+7N7CC8V5ayHtHGpZpxLQZZxkIaaz6HN65Ls= 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,6 +7,10 @@ import (
"strings" "strings"
) )
type VersionGetter interface {
Version(ctx context.Context) (version string, err error)
}
func (a *Alpine) 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 {

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 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 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

@@ -1,85 +1,51 @@
package cli package cli
import ( import (
"context"
"fmt" "fmt"
"net"
"net/http"
"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][]net.IP, warnings []string, err error)
}
type IPFetcher interface {
FetchMultiInfo(ctx context.Context, ips []net.IP) (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 {
@@ -54,8 +56,18 @@ func (c *CLI) Update(ctx context.Context, args []string, logger UpdaterLogger) e
return 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 ErrNoProviderSpecified return ErrNoProviderSpecified
@@ -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

@@ -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

View File

@@ -65,7 +65,7 @@ func (d *DoT) copy() (copied DoT) {
// unset field of the receiver settings object. // unset field of the receiver settings object.
func (d *DoT) mergeWith(other DoT) { func (d *DoT) mergeWith(other DoT) {
d.Enabled = helpers.MergeWithBool(d.Enabled, other.Enabled) d.Enabled = helpers.MergeWithBool(d.Enabled, other.Enabled)
d.UpdatePeriod = helpers.MergeWithDurationPtr(d.UpdatePeriod, other.UpdatePeriod) d.UpdatePeriod = helpers.MergeWithDuration(d.UpdatePeriod, other.UpdatePeriod)
d.Unbound.mergeWith(other.Unbound) d.Unbound.mergeWith(other.Unbound)
d.Blacklist.mergeWith(other.Blacklist) d.Blacklist.mergeWith(other.Blacklist)
} }
@@ -75,7 +75,7 @@ func (d *DoT) mergeWith(other DoT) {
// settings. // settings.
func (d *DoT) overrideWith(other DoT) { func (d *DoT) overrideWith(other DoT) {
d.Enabled = helpers.OverrideWithBool(d.Enabled, other.Enabled) d.Enabled = helpers.OverrideWithBool(d.Enabled, other.Enabled)
d.UpdatePeriod = helpers.OverrideWithDurationPtr(d.UpdatePeriod, other.UpdatePeriod) d.UpdatePeriod = helpers.OverrideWithDuration(d.UpdatePeriod, other.UpdatePeriod)
d.Unbound.overrideWith(other.Unbound) d.Unbound.overrideWith(other.Unbound)
d.Blacklist.overrideWith(other.Blacklist) d.Blacklist.overrideWith(other.Blacklist)
} }
@@ -83,7 +83,7 @@ func (d *DoT) overrideWith(other DoT) {
func (d *DoT) setDefaults() { func (d *DoT) setDefaults() {
d.Enabled = helpers.DefaultBool(d.Enabled, true) d.Enabled = helpers.DefaultBool(d.Enabled, true)
const defaultUpdatePeriod = 24 * time.Hour const defaultUpdatePeriod = 24 * time.Hour
d.UpdatePeriod = helpers.DefaultDurationPtr(d.UpdatePeriod, defaultUpdatePeriod) d.UpdatePeriod = helpers.DefaultDuration(d.UpdatePeriod, defaultUpdatePeriod)
d.Unbound.setDefaults() d.Unbound.setDefaults()
d.Blacklist.setDefaults() d.Blacklist.setDefaults()
} }

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

@@ -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,12 +15,6 @@ type Health struct {
// for the health check server. // for the health check server.
// It cannot be the empty string in the internal state. // It cannot be the empty string in the internal state.
ServerAddress string ServerAddress string
// ReadHeaderTimeout is the HTTP server header read timeout
// duration of the HTTP server. It defaults to 100 milliseconds.
ReadHeaderTimeout time.Duration
// ReadTimeout is the HTTP read timeout duration of the
// HTTP server. It defaults to 500 milliseconds.
ReadTimeout time.Duration
// TargetAddress is the address (host or host:port) // TargetAddress is the address (host or host:port)
// to TCP dial to periodically for the health check. // to TCP dial to periodically for the health check.
// It cannot be the empty string in the internal state. // It cannot be the empty string in the internal state.
@@ -47,11 +40,9 @@ func (h Health) Validate() (err error) {
func (h *Health) copy() (copied Health) { func (h *Health) copy() (copied Health) {
return Health{ return Health{
ServerAddress: h.ServerAddress, ServerAddress: h.ServerAddress,
ReadHeaderTimeout: h.ReadHeaderTimeout, TargetAddress: h.TargetAddress,
ReadTimeout: h.ReadTimeout, VPN: h.VPN.copy(),
TargetAddress: h.TargetAddress,
VPN: h.VPN.copy(),
} }
} }
@@ -59,8 +50,6 @@ func (h *Health) copy() (copied Health) {
// unset field of the receiver settings object. // unset field of the receiver settings object.
func (h *Health) MergeWith(other Health) { func (h *Health) MergeWith(other Health) {
h.ServerAddress = helpers.MergeWithString(h.ServerAddress, other.ServerAddress) h.ServerAddress = helpers.MergeWithString(h.ServerAddress, other.ServerAddress)
h.ReadHeaderTimeout = helpers.MergeWithDuration(h.ReadHeaderTimeout, other.ReadHeaderTimeout)
h.ReadTimeout = helpers.MergeWithDuration(h.ReadTimeout, other.ReadTimeout)
h.TargetAddress = helpers.MergeWithString(h.TargetAddress, other.TargetAddress) h.TargetAddress = helpers.MergeWithString(h.TargetAddress, other.TargetAddress)
h.VPN.mergeWith(other.VPN) h.VPN.mergeWith(other.VPN)
} }
@@ -70,19 +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.OverrideWithDuration(h.ReadHeaderTimeout, other.ReadHeaderTimeout)
h.ReadTimeout = helpers.OverrideWithDuration(h.ReadTimeout, other.ReadTimeout)
h.TargetAddress = helpers.OverrideWithString(h.TargetAddress, other.TargetAddress) h.TargetAddress = helpers.OverrideWithString(h.TargetAddress, other.TargetAddress)
h.VPN.overrideWith(other.VPN) h.VPN.overrideWith(other.VPN)
} }
func (h *Health) SetDefaults() { func (h *Health) SetDefaults() {
h.ServerAddress = helpers.DefaultString(h.ServerAddress, "127.0.0.1:9999") h.ServerAddress = helpers.DefaultString(h.ServerAddress, "127.0.0.1:9999")
const defaultReadHeaderTimeout = 100 * time.Millisecond h.TargetAddress = helpers.DefaultString(h.TargetAddress, "github.com:443")
h.ReadHeaderTimeout = helpers.DefaultDuration(h.ReadHeaderTimeout, defaultReadHeaderTimeout)
const defaultReadTimeout = 500 * time.Millisecond
h.ReadTimeout = helpers.DefaultDuration(h.ReadTimeout, defaultReadTimeout)
h.TargetAddress = helpers.DefaultString(h.TargetAddress, "cloudflare.com:443")
h.VPN.setDefaults() h.VPN.setDefaults()
} }
@@ -94,8 +77,6 @@ func (h Health) toLinesNode() (node *gotree.Node) {
node = gotree.New("Health settings:") node = gotree.New("Health settings:")
node.Appendf("Server listening address: %s", h.ServerAddress) node.Appendf("Server listening address: %s", h.ServerAddress)
node.Appendf("Target address: %s", h.TargetAddress) node.Appendf("Target address: %s", h.TargetAddress)
node.Appendf("Read header timeout: %s", h.ReadHeaderTimeout)
node.Appendf("Read timeout: %s", h.ReadTimeout)
node.AppendNode(h.VPN.toLinesNode("VPN")) node.AppendNode(h.VPN.toLinesNode("VPN"))
return node return node
} }

View File

@@ -35,23 +35,23 @@ func (h *HealthyWait) copy() (copied HealthyWait) {
// mergeWith merges the other settings into any // mergeWith merges the other settings into any
// unset field of the receiver settings object. // unset field of the receiver settings object.
func (h *HealthyWait) mergeWith(other HealthyWait) { func (h *HealthyWait) mergeWith(other HealthyWait) {
h.Initial = helpers.MergeWithDurationPtr(h.Initial, other.Initial) h.Initial = helpers.MergeWithDuration(h.Initial, other.Initial)
h.Addition = helpers.MergeWithDurationPtr(h.Addition, other.Addition) h.Addition = helpers.MergeWithDuration(h.Addition, other.Addition)
} }
// overrideWith overrides fields of the receiver // overrideWith overrides fields of the receiver
// settings object with any field set in the other // settings object with any field set in the other
// settings. // settings.
func (h *HealthyWait) overrideWith(other HealthyWait) { func (h *HealthyWait) overrideWith(other HealthyWait) {
h.Initial = helpers.OverrideWithDurationPtr(h.Initial, other.Initial) h.Initial = helpers.OverrideWithDuration(h.Initial, other.Initial)
h.Addition = helpers.OverrideWithDurationPtr(h.Addition, other.Addition) h.Addition = helpers.OverrideWithDuration(h.Addition, other.Addition)
} }
func (h *HealthyWait) setDefaults() { func (h *HealthyWait) setDefaults() {
const initialDurationDefault = 6 * time.Second const initialDurationDefault = 6 * time.Second
const additionDurationDefault = 5 * time.Second const additionDurationDefault = 5 * time.Second
h.Initial = helpers.DefaultDurationPtr(h.Initial, initialDurationDefault) h.Initial = helpers.DefaultDuration(h.Initial, initialDurationDefault)
h.Addition = helpers.DefaultDurationPtr(h.Addition, additionDurationDefault) h.Addition = helpers.DefaultDuration(h.Addition, additionDurationDefault)
} }
func (h HealthyWait) String() string { func (h HealthyWait) String() string {

View File

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

View File

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

View File

@@ -5,7 +5,7 @@ import (
"net/http" "net/http"
"time" "time"
"github.com/qdm12/log" "github.com/qdm12/golibs/logging"
"inet.af/netaddr" "inet.af/netaddr"
) )
@@ -34,13 +34,6 @@ func MergeWithInt(existing, other int) (result int) {
return other return other
} }
func MergeWithFloat64(existing, other float64) (result float64) {
if existing != 0 {
return existing
}
return other
}
func MergeWithStringPtr(existing, other *string) (result *string) { func MergeWithStringPtr(existing, other *string) (result *string) {
if existing != nil { if existing != nil {
return existing return existing
@@ -85,17 +78,6 @@ func MergeWithUint16(existing, other *uint16) (result *uint16) {
return result return result
} }
func MergeWithUint32(existing, other *uint32) (result *uint32) {
if existing != nil {
return existing
} else if other == nil {
return nil
}
result = new(uint32)
*result = *other
return result
}
func MergeWithIP(existing, other net.IP) (result net.IP) { func MergeWithIP(existing, other net.IP) (result net.IP) {
if existing != nil { if existing != nil {
return existing return existing
@@ -107,27 +89,20 @@ func MergeWithIP(existing, other net.IP) (result net.IP) {
return result return result
} }
func MergeWithDuration(existing, other time.Duration) (result time.Duration) { func MergeWithDuration(existing, other *time.Duration) (result *time.Duration) {
if existing != 0 {
return existing
}
return other
}
func MergeWithDurationPtr(existing, other *time.Duration) (result *time.Duration) {
if existing != nil { if existing != nil {
return existing return existing
} }
return other return other
} }
func MergeWithLogLevel(existing, other *log.Level) (result *log.Level) { func MergeWithLogLevel(existing, other *logging.Level) (result *logging.Level) {
if existing != nil { if existing != nil {
return existing return existing
} else if other == nil { } else if other == nil {
return nil return nil
} }
result = new(log.Level) result = new(logging.Level)
*result = *other *result = *other
return result return result
} }

View File

@@ -5,7 +5,7 @@ import (
"net/http" "net/http"
"time" "time"
"github.com/qdm12/log" "github.com/qdm12/golibs/logging"
"inet.af/netaddr" "inet.af/netaddr"
) )
@@ -32,13 +32,6 @@ func OverrideWithInt(existing, other int) (result int) {
return other return other
} }
func OverrideWithFloat64(existing, other float64) (result float64) {
if other == 0 {
return existing
}
return other
}
func OverrideWithStringPtr(existing, other *string) (result *string) { func OverrideWithStringPtr(existing, other *string) (result *string) {
if other == nil { if other == nil {
return existing return existing
@@ -75,15 +68,6 @@ func OverrideWithUint16(existing, other *uint16) (result *uint16) {
return result return result
} }
func OverrideWithUint32(existing, other *uint32) (result *uint32) {
if other == nil {
return existing
}
result = new(uint32)
*result = *other
return result
}
func OverrideWithIP(existing, other net.IP) (result net.IP) { func OverrideWithIP(existing, other net.IP) (result net.IP) {
if other == nil { if other == nil {
return existing return existing
@@ -93,16 +77,7 @@ func OverrideWithIP(existing, other net.IP) (result net.IP) {
return result return result
} }
func OverrideWithDuration(existing, other time.Duration) ( func OverrideWithDuration(existing, other *time.Duration) (result *time.Duration) {
result time.Duration) {
if other == 0 {
return existing
}
return other
}
func OverrideWithDurationPtr(existing, other *time.Duration) (
result *time.Duration) {
if other == nil { if other == nil {
return existing return existing
} }
@@ -111,11 +86,11 @@ func OverrideWithDurationPtr(existing, other *time.Duration) (
return result return result
} }
func OverrideWithLogLevel(existing, other *log.Level) (result *log.Level) { func OverrideWithLogLevel(existing, other *logging.Level) (result *logging.Level) {
if other == nil { if other == nil {
return existing return existing
} }
result = new(log.Level) result = new(logging.Level)
*result = *other *result = *other
return result return result
} }

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,14 +49,12 @@ func (h HTTPProxy) validate() (err error) {
func (h *HTTPProxy) copy() (copied HTTPProxy) { func (h *HTTPProxy) copy() (copied HTTPProxy) {
return HTTPProxy{ return HTTPProxy{
User: helpers.CopyStringPtr(h.User), User: helpers.CopyStringPtr(h.User),
Password: helpers.CopyStringPtr(h.Password), Password: helpers.CopyStringPtr(h.Password),
ListeningAddress: h.ListeningAddress, ListeningAddress: h.ListeningAddress,
Enabled: helpers.CopyBoolPtr(h.Enabled), Enabled: helpers.CopyBoolPtr(h.Enabled),
Stealth: helpers.CopyBoolPtr(h.Stealth), Stealth: helpers.CopyBoolPtr(h.Stealth),
Log: helpers.CopyBoolPtr(h.Log), Log: helpers.CopyBoolPtr(h.Log),
ReadHeaderTimeout: h.ReadHeaderTimeout,
ReadTimeout: h.ReadTimeout,
} }
} }
@@ -76,8 +67,6 @@ func (h *HTTPProxy) mergeWith(other HTTPProxy) {
h.Enabled = helpers.MergeWithBool(h.Enabled, other.Enabled) h.Enabled = helpers.MergeWithBool(h.Enabled, other.Enabled)
h.Stealth = helpers.MergeWithBool(h.Stealth, other.Stealth) h.Stealth = helpers.MergeWithBool(h.Stealth, other.Stealth)
h.Log = helpers.MergeWithBool(h.Log, other.Log) h.Log = helpers.MergeWithBool(h.Log, other.Log)
h.ReadHeaderTimeout = helpers.MergeWithDuration(h.ReadHeaderTimeout, other.ReadHeaderTimeout)
h.ReadTimeout = helpers.MergeWithDuration(h.ReadTimeout, other.ReadTimeout)
} }
// overrideWith overrides fields of the receiver // overrideWith overrides fields of the receiver
@@ -90,8 +79,6 @@ func (h *HTTPProxy) overrideWith(other HTTPProxy) {
h.Enabled = helpers.OverrideWithBool(h.Enabled, other.Enabled) h.Enabled = helpers.OverrideWithBool(h.Enabled, other.Enabled)
h.Stealth = helpers.OverrideWithBool(h.Stealth, other.Stealth) h.Stealth = helpers.OverrideWithBool(h.Stealth, other.Stealth)
h.Log = helpers.OverrideWithBool(h.Log, other.Log) h.Log = helpers.OverrideWithBool(h.Log, other.Log)
h.ReadHeaderTimeout = helpers.OverrideWithDuration(h.ReadHeaderTimeout, other.ReadHeaderTimeout)
h.ReadTimeout = helpers.OverrideWithDuration(h.ReadTimeout, other.ReadTimeout)
} }
func (h *HTTPProxy) setDefaults() { func (h *HTTPProxy) setDefaults() {
@@ -101,10 +88,6 @@ func (h *HTTPProxy) setDefaults() {
h.Enabled = helpers.DefaultBool(h.Enabled, false) h.Enabled = helpers.DefaultBool(h.Enabled, false)
h.Stealth = helpers.DefaultBool(h.Stealth, false) h.Stealth = helpers.DefaultBool(h.Stealth, false)
h.Log = helpers.DefaultBool(h.Log, false) h.Log = helpers.DefaultBool(h.Log, false)
const defaultReadHeaderTimeout = time.Second
h.ReadHeaderTimeout = helpers.DefaultDuration(h.ReadHeaderTimeout, defaultReadHeaderTimeout)
const defaultReadTimeout = 3 * time.Second
h.ReadTimeout = helpers.DefaultDuration(h.ReadTimeout, defaultReadTimeout)
} }
func (h HTTPProxy) String() string { func (h HTTPProxy) String() string {
@@ -123,8 +106,6 @@ func (h HTTPProxy) toLinesNode() (node *gotree.Node) {
node.Appendf("Password: %s", helpers.ObfuscatePassword(*h.Password)) node.Appendf("Password: %s", helpers.ObfuscatePassword(*h.Password))
node.Appendf("Stealth mode: %s", helpers.BoolPtrToYesNo(h.Stealth)) node.Appendf("Stealth mode: %s", helpers.BoolPtrToYesNo(h.Stealth))
node.Appendf("Log: %s", helpers.BoolPtrToYesNo(h.Log)) node.Appendf("Log: %s", helpers.BoolPtrToYesNo(h.Log))
node.Appendf("Read header timeout: %s", h.ReadHeaderTimeout)
node.Appendf("Read timeout: %s", h.ReadTimeout)
return node return node
} }

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) {
@@ -37,7 +37,7 @@ func (l *Log) overrideWith(other Log) {
} }
func (l *Log) setDefaults() { func (l *Log) setDefaults() {
l.Level = helpers.DefaultLogLevel(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,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,29 +73,21 @@ 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 ErrOpenVPNUserIsEmpty return ErrOpenVPNUserIsEmpty
} }
passwordRequired := isUserRequired && if !isCustom && o.Password == "" {
(vpnProvider != providers.Ivpn || !ivpnAccountID.MatchString(*o.User))
if passwordRequired && *o.Password == "" {
return ErrOpenVPNPasswordIsEmpty return ErrOpenVPNPasswordIsEmpty
} }
@@ -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",
@@ -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,10 +147,8 @@ 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 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,10 +168,9 @@ 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 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 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.CopyStringPtr(o.User), User: o.User,
Password: helpers.CopyStringPtr(o.Password), Password: o.Password,
ConfFile: helpers.CopyStringPtr(o.ConfFile), ConfFile: helpers.CopyStringPtr(o.ConfFile),
Ciphers: helpers.CopyStringSlice(o.Ciphers), Ciphers: helpers.CopyStringSlice(o.Ciphers),
Auth: helpers.CopyStringPtr(o.Auth), Auth: helpers.CopyStringPtr(o.Auth),
Cert: helpers.CopyStringPtr(o.Cert), ClientCrt: helpers.CopyStringPtr(o.ClientCrt),
Key: helpers.CopyStringPtr(o.Key), ClientKey: helpers.CopyStringPtr(o.ClientKey),
EncryptedKey: helpers.CopyStringPtr(o.EncryptedKey), PIAEncPreset: helpers.CopyStringPtr(o.PIAEncPreset),
KeyPassphrase: helpers.CopyStringPtr(o.KeyPassphrase), IPv6: helpers.CopyBoolPtr(o.IPv6),
PIAEncPreset: helpers.CopyStringPtr(o.PIAEncPreset), MSSFix: helpers.CopyUint16Ptr(o.MSSFix),
MSSFix: helpers.CopyUint16Ptr(o.MSSFix), Interface: o.Interface,
Interface: o.Interface, ProcessUser: o.ProcessUser,
ProcessUser: o.ProcessUser, Verbosity: helpers.CopyIntPtr(o.Verbosity),
Verbosity: helpers.CopyIntPtr(o.Verbosity), Flags: helpers.CopyStringSlice(o.Flags),
Flags: helpers.CopyStringSlice(o.Flags),
} }
} }
@@ -266,16 +211,15 @@ func (o *OpenVPN) copy() (copied OpenVPN) {
// unset field of the receiver settings object. // unset field of the receiver settings object.
func (o *OpenVPN) mergeWith(other OpenVPN) { func (o *OpenVPN) mergeWith(other OpenVPN) {
o.Version = helpers.MergeWithString(o.Version, other.Version) o.Version = helpers.MergeWithString(o.Version, other.Version)
o.User = helpers.MergeWithStringPtr(o.User, other.User) o.User = helpers.MergeWithString(o.User, other.User)
o.Password = helpers.MergeWithStringPtr(o.Password, other.Password) o.Password = helpers.MergeWithString(o.Password, other.Password)
o.ConfFile = helpers.MergeWithStringPtr(o.ConfFile, other.ConfFile) o.ConfFile = helpers.MergeWithStringPtr(o.ConfFile, other.ConfFile)
o.Ciphers = helpers.MergeStringSlices(o.Ciphers, other.Ciphers) o.Ciphers = helpers.MergeStringSlices(o.Ciphers, other.Ciphers)
o.Auth = helpers.MergeWithStringPtr(o.Auth, other.Auth) o.Auth = helpers.MergeWithStringPtr(o.Auth, other.Auth)
o.Cert = helpers.MergeWithStringPtr(o.Cert, other.Cert) o.ClientCrt = helpers.MergeWithStringPtr(o.ClientCrt, other.ClientCrt)
o.Key = helpers.MergeWithStringPtr(o.Key, other.Key) o.ClientKey = helpers.MergeWithStringPtr(o.ClientKey, other.ClientKey)
o.EncryptedKey = helpers.MergeWithStringPtr(o.EncryptedKey, other.EncryptedKey)
o.KeyPassphrase = helpers.MergeWithStringPtr(o.KeyPassphrase, other.KeyPassphrase)
o.PIAEncPreset = helpers.MergeWithStringPtr(o.PIAEncPreset, other.PIAEncPreset) o.PIAEncPreset = helpers.MergeWithStringPtr(o.PIAEncPreset, other.PIAEncPreset)
o.IPv6 = helpers.MergeWithBool(o.IPv6, other.IPv6)
o.MSSFix = helpers.MergeWithUint16(o.MSSFix, other.MSSFix) o.MSSFix = helpers.MergeWithUint16(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)
@@ -288,16 +232,15 @@ func (o *OpenVPN) mergeWith(other OpenVPN) {
// settings. // settings.
func (o *OpenVPN) overrideWith(other OpenVPN) { func (o *OpenVPN) overrideWith(other OpenVPN) {
o.Version = helpers.OverrideWithString(o.Version, other.Version) o.Version = helpers.OverrideWithString(o.Version, other.Version)
o.User = helpers.OverrideWithStringPtr(o.User, other.User) o.User = helpers.OverrideWithString(o.User, other.User)
o.Password = helpers.OverrideWithStringPtr(o.Password, other.Password) o.Password = helpers.OverrideWithString(o.Password, other.Password)
o.ConfFile = helpers.OverrideWithStringPtr(o.ConfFile, other.ConfFile) o.ConfFile = helpers.OverrideWithStringPtr(o.ConfFile, other.ConfFile)
o.Ciphers = helpers.OverrideWithStringSlice(o.Ciphers, other.Ciphers) o.Ciphers = helpers.OverrideWithStringSlice(o.Ciphers, other.Ciphers)
o.Auth = helpers.OverrideWithStringPtr(o.Auth, other.Auth) o.Auth = helpers.OverrideWithStringPtr(o.Auth, other.Auth)
o.Cert = helpers.OverrideWithStringPtr(o.Cert, other.Cert) o.ClientCrt = helpers.OverrideWithStringPtr(o.ClientCrt, other.ClientCrt)
o.Key = helpers.OverrideWithStringPtr(o.Key, other.Key) o.ClientKey = helpers.OverrideWithStringPtr(o.ClientKey, other.ClientKey)
o.EncryptedKey = helpers.OverrideWithStringPtr(o.EncryptedKey, other.EncryptedKey)
o.KeyPassphrase = helpers.OverrideWithStringPtr(o.KeyPassphrase, other.KeyPassphrase)
o.PIAEncPreset = helpers.OverrideWithStringPtr(o.PIAEncPreset, other.PIAEncPreset) o.PIAEncPreset = helpers.OverrideWithStringPtr(o.PIAEncPreset, other.PIAEncPreset)
o.IPv6 = helpers.OverrideWithBool(o.IPv6, other.IPv6)
o.MSSFix = helpers.OverrideWithUint16(o.MSSFix, other.MSSFix) o.MSSFix = helpers.OverrideWithUint16(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)
@@ -306,26 +249,23 @@ func (o *OpenVPN) overrideWith(other OpenVPN) {
} }
func (o *OpenVPN) setDefaults(vpnProvider string) { func (o *OpenVPN) setDefaults(vpnProvider string) {
o.Version = helpers.DefaultString(o.Version, openvpn.Openvpn25) o.Version = helpers.DefaultString(o.Version, constants.Openvpn25)
o.User = helpers.DefaultStringPtr(o.User, "") if vpnProvider == constants.Mullvad {
if vpnProvider == providers.Mullvad { o.Password = "m"
o.Password = helpers.DefaultStringPtr(o.Password, "m")
} else {
o.Password = helpers.DefaultStringPtr(o.Password, "")
} }
o.ConfFile = helpers.DefaultStringPtr(o.ConfFile, "") o.ConfFile = helpers.DefaultStringPtr(o.ConfFile, "")
o.Auth = helpers.DefaultStringPtr(o.Auth, "") o.Auth = helpers.DefaultStringPtr(o.Auth, "")
o.Cert = helpers.DefaultStringPtr(o.Cert, "") o.ClientCrt = helpers.DefaultStringPtr(o.ClientCrt, "")
o.Key = helpers.DefaultStringPtr(o.Key, "") o.ClientKey = helpers.DefaultStringPtr(o.ClientKey, "")
o.EncryptedKey = helpers.DefaultStringPtr(o.EncryptedKey, "")
o.KeyPassphrase = helpers.DefaultStringPtr(o.KeyPassphrase, "")
var defaultEncPreset string var defaultEncPreset string
if vpnProvider == providers.PrivateInternetAccess { if vpnProvider == constants.PrivateInternetAccess {
defaultEncPreset = presets.Strong defaultEncPreset = constants.PIAEncryptionPresetStrong
} }
o.PIAEncPreset = helpers.DefaultStringPtr(o.PIAEncPreset, defaultEncPreset) o.PIAEncPreset = helpers.DefaultStringPtr(o.PIAEncPreset, defaultEncPreset)
o.IPv6 = helpers.DefaultBool(o.IPv6, false)
o.MSSFix = helpers.DefaultUint16(o.MSSFix, 0) 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")
@@ -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",
@@ -157,8 +142,8 @@ func (o *OpenVPNSelection) setDefaults(vpnProvider string) {
o.CustomPort = helpers.DefaultUint16(o.CustomPort, 0) o.CustomPort = helpers.DefaultUint16(o.CustomPort, 0)
var defaultEncPreset string var defaultEncPreset string
if vpnProvider == providers.PrivateInternetAccess { if vpnProvider == constants.PrivateInternetAccess {
defaultEncPreset = presets.Strong defaultEncPreset = constants.PIAEncryptionPresetStrong
} }
o.PIAEncPreset = helpers.DefaultStringPtr(o.PIAEncPreset, defaultEncPreset) o.PIAEncPreset = helpers.DefaultStringPtr(o.PIAEncPreset, defaultEncPreset)
} }

View File

@@ -6,7 +6,7 @@ import (
"strings" "strings"
"github.com/qdm12/gluetun/internal/configuration/settings/helpers" "github.com/qdm12/gluetun/internal/configuration/settings/helpers"
"github.com/qdm12/gluetun/internal/constants/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, ", "))

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,20 +22,18 @@ 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...) {
@@ -43,7 +41,7 @@ func (p *Provider) validate(vpnType string, storage Storage) (err error) {
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)
} }
@@ -77,7 +75,7 @@ func (p *Provider) overrideWith(other Provider) {
} }
func (p *Provider) setDefaults() { func (p *Provider) setDefaults() {
p.Name = helpers.DefaultStringPtr(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

@@ -48,18 +48,18 @@ func (p *PublicIP) copy() (copied PublicIP) {
} }
func (p *PublicIP) mergeWith(other PublicIP) { func (p *PublicIP) mergeWith(other PublicIP) {
p.Period = helpers.MergeWithDurationPtr(p.Period, other.Period) p.Period = helpers.MergeWithDuration(p.Period, other.Period)
p.IPFilepath = helpers.MergeWithStringPtr(p.IPFilepath, other.IPFilepath) p.IPFilepath = helpers.MergeWithStringPtr(p.IPFilepath, other.IPFilepath)
} }
func (p *PublicIP) overrideWith(other PublicIP) { func (p *PublicIP) overrideWith(other PublicIP) {
p.Period = helpers.OverrideWithDurationPtr(p.Period, other.Period) p.Period = helpers.OverrideWithDuration(p.Period, other.Period)
p.IPFilepath = helpers.OverrideWithStringPtr(p.IPFilepath, other.IPFilepath) p.IPFilepath = helpers.OverrideWithStringPtr(p.IPFilepath, other.IPFilepath)
} }
func (p *PublicIP) setDefaults() { func (p *PublicIP) setDefaults() {
const defaultPeriod = 12 * time.Hour const defaultPeriod = 12 * time.Hour
p.Period = helpers.DefaultDurationPtr(p.Period, defaultPeriod) p.Period = helpers.DefaultDuration(p.Period, defaultPeriod)
p.IPFilepath = helpers.DefaultStringPtr(p.IPFilepath, "/tmp/gluetun/ip") p.IPFilepath = helpers.DefaultStringPtr(p.IPFilepath, "/tmp/gluetun/ip")
} }

View File

@@ -8,8 +8,7 @@ import (
"github.com/qdm12/gluetun/internal/configuration/settings/helpers" "github.com/qdm12/gluetun/internal/configuration/settings/helpers"
"github.com/qdm12/gluetun/internal/configuration/settings/validation" "github.com/qdm12/gluetun/internal/configuration/settings/validation"
"github.com/qdm12/gluetun/internal/constants/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"
) )
@@ -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 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)
} }
@@ -212,7 +302,6 @@ func (ss *ServerSelection) copy() (copied ServerSelection) {
Numbers: helpers.CopyUint16Slice(ss.Numbers), Numbers: helpers.CopyUint16Slice(ss.Numbers),
OwnedOnly: helpers.CopyBoolPtr(ss.OwnedOnly), OwnedOnly: helpers.CopyBoolPtr(ss.OwnedOnly),
FreeOnly: helpers.CopyBoolPtr(ss.FreeOnly), FreeOnly: helpers.CopyBoolPtr(ss.FreeOnly),
PremiumOnly: helpers.CopyBoolPtr(ss.PremiumOnly),
StreamOnly: helpers.CopyBoolPtr(ss.StreamOnly), StreamOnly: helpers.CopyBoolPtr(ss.StreamOnly),
MultiHopOnly: helpers.CopyBoolPtr(ss.MultiHopOnly), MultiHopOnly: helpers.CopyBoolPtr(ss.MultiHopOnly),
OpenVPN: ss.OpenVPN.copy(), OpenVPN: ss.OpenVPN.copy(),
@@ -232,7 +321,6 @@ func (ss *ServerSelection) mergeWith(other ServerSelection) {
ss.Numbers = helpers.MergeUint16Slices(ss.Numbers, other.Numbers) ss.Numbers = helpers.MergeUint16Slices(ss.Numbers, other.Numbers)
ss.OwnedOnly = helpers.MergeWithBool(ss.OwnedOnly, other.OwnedOnly) ss.OwnedOnly = helpers.MergeWithBool(ss.OwnedOnly, other.OwnedOnly)
ss.FreeOnly = helpers.MergeWithBool(ss.FreeOnly, other.FreeOnly) ss.FreeOnly = helpers.MergeWithBool(ss.FreeOnly, other.FreeOnly)
ss.PremiumOnly = helpers.MergeWithBool(ss.PremiumOnly, other.PremiumOnly)
ss.StreamOnly = helpers.MergeWithBool(ss.StreamOnly, other.StreamOnly) ss.StreamOnly = helpers.MergeWithBool(ss.StreamOnly, other.StreamOnly)
ss.MultiHopOnly = helpers.MergeWithBool(ss.MultiHopOnly, other.MultiHopOnly) ss.MultiHopOnly = helpers.MergeWithBool(ss.MultiHopOnly, other.MultiHopOnly)
@@ -252,7 +340,6 @@ func (ss *ServerSelection) overrideWith(other ServerSelection) {
ss.Numbers = helpers.OverrideWithUint16Slice(ss.Numbers, other.Numbers) ss.Numbers = helpers.OverrideWithUint16Slice(ss.Numbers, other.Numbers)
ss.OwnedOnly = helpers.OverrideWithBool(ss.OwnedOnly, other.OwnedOnly) ss.OwnedOnly = helpers.OverrideWithBool(ss.OwnedOnly, other.OwnedOnly)
ss.FreeOnly = helpers.OverrideWithBool(ss.FreeOnly, other.FreeOnly) ss.FreeOnly = helpers.OverrideWithBool(ss.FreeOnly, other.FreeOnly)
ss.PremiumOnly = helpers.OverrideWithBool(ss.PremiumOnly, other.PremiumOnly)
ss.StreamOnly = helpers.OverrideWithBool(ss.StreamOnly, other.StreamOnly) ss.StreamOnly = helpers.OverrideWithBool(ss.StreamOnly, other.StreamOnly)
ss.MultiHopOnly = helpers.OverrideWithBool(ss.MultiHopOnly, other.MultiHopOnly) ss.MultiHopOnly = helpers.OverrideWithBool(ss.MultiHopOnly, other.MultiHopOnly)
ss.OpenVPN.overrideWith(other.OpenVPN) ss.OpenVPN.overrideWith(other.OpenVPN)
@@ -260,11 +347,10 @@ func (ss *ServerSelection) overrideWith(other ServerSelection) {
} }
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, net.IP{}) ss.TargetIP = helpers.DefaultIP(ss.TargetIP, net.IP{})
ss.OwnedOnly = helpers.DefaultBool(ss.OwnedOnly, false) ss.OwnedOnly = helpers.DefaultBool(ss.OwnedOnly, false)
ss.FreeOnly = helpers.DefaultBool(ss.FreeOnly, false) ss.FreeOnly = helpers.DefaultBool(ss.FreeOnly, false)
ss.PremiumOnly = helpers.DefaultBool(ss.PremiumOnly, false)
ss.StreamOnly = helpers.DefaultBool(ss.StreamOnly, false) ss.StreamOnly = helpers.DefaultBool(ss.StreamOnly, false)
ss.MultiHopOnly = helpers.DefaultBool(ss.MultiHopOnly, false) ss.MultiHopOnly = helpers.DefaultBool(ss.MultiHopOnly, false)
ss.OpenVPN.setDefaults(vpnProvider) ss.OpenVPN.setDefaults(vpnProvider)
@@ -321,10 +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,9 +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
| ├── 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

@@ -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.CopyUint32Ptr(s.PUID), PUID: helpers.CopyUint16Ptr(s.PUID),
PGID: helpers.CopyUint32Ptr(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.MergeWithUint32(s.PUID, other.PUID) s.PUID = helpers.MergeWithUint16(s.PUID, other.PUID)
s.PGID = helpers.MergeWithUint32(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.OverrideWithUint32(s.PUID, other.PUID) s.PUID = helpers.OverrideWithUint16(s.PUID, other.PUID)
s.PGID = helpers.OverrideWithUint32(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.DefaultUint32(s.PUID, defaultID) s.PUID = helpers.DefaultUint16(s.PUID, defaultID)
s.PGID = helpers.DefaultUint32(s.PGID, defaultID) s.PGID = helpers.DefaultUint16(s.PGID, defaultID)
} }
func (s System) String() string { func (s System) String() string {

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)
} }
} }
@@ -64,41 +64,36 @@ func (u Updater) Validate() (err error) {
func (u *Updater) copy() (copied Updater) { func (u *Updater) copy() (copied Updater) {
return Updater{ return Updater{
Period: helpers.CopyDurationPtr(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.CopyStringSlice(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.MergeWithDurationPtr(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.MergeWithFloat64(u.MinRatio, other.MinRatio)
u.Providers = helpers.MergeStringSlices(u.Providers, other.Providers) u.Providers = helpers.MergeStringSlices(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.OverrideWithDurationPtr(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.OverrideWithFloat64(u.MinRatio, other.MinRatio)
u.Providers = helpers.OverrideWithStringSlice(u.Providers, other.Providers) u.Providers = helpers.OverrideWithStringSlice(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.DefaultDurationPtr(u.Period, 0) u.Period = helpers.DefaultDuration(u.Period, 0)
u.DNSAddress = helpers.DefaultString(u.DNSAddress, "1.1.1.1:53") u.DNSAddress = helpers.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

@@ -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

@@ -6,7 +6,7 @@ import (
"regexp" "regexp"
"github.com/qdm12/gluetun/internal/configuration/settings/helpers" "github.com/qdm12/gluetun/internal/configuration/settings/helpers"
"github.com/qdm12/gluetun/internal/constants/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"
) )
@@ -27,24 +27,18 @@ type Wireguard struct {
// 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
@@ -59,12 +53,6 @@ func (w Wireguard) validate(vpnProvider string, ipv6Supported bool) (err error)
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)
@@ -82,12 +70,6 @@ func (w Wireguard) validate(vpnProvider string, ipv6Supported bool) (err error)
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())
} }
ipv6Net := ipNet.IP.To4() == nil
if ipv6Net && !ipv6Supported {
return fmt.Errorf("%w: address %s",
ErrWireguardInterfaceAddressIPv6, ipNet)
}
} }
// Validate interface // Validate interface
@@ -96,22 +78,15 @@ 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.CopyStringPtr(w.PrivateKey), PrivateKey: helpers.CopyStringPtr(w.PrivateKey),
PreSharedKey: helpers.CopyStringPtr(w.PreSharedKey), PreSharedKey: helpers.CopyStringPtr(w.PreSharedKey),
Addresses: helpers.CopyIPNetSlice(w.Addresses), Addresses: helpers.CopyIPNetSlice(w.Addresses),
Interface: w.Interface, Interface: w.Interface,
Implementation: w.Implementation,
} }
} }
@@ -120,7 +95,6 @@ func (w *Wireguard) mergeWith(other Wireguard) {
w.PreSharedKey = helpers.MergeWithStringPtr(w.PreSharedKey, other.PreSharedKey) w.PreSharedKey = helpers.MergeWithStringPtr(w.PreSharedKey, other.PreSharedKey)
w.Addresses = helpers.MergeIPNetsSlices(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) {
@@ -128,14 +102,12 @@ func (w *Wireguard) overrideWith(other Wireguard) {
w.PreSharedKey = helpers.OverrideWithStringPtr(w.PreSharedKey, other.PreSharedKey) w.PreSharedKey = helpers.OverrideWithStringPtr(w.PreSharedKey, other.PreSharedKey)
w.Addresses = helpers.OverrideWithIPNetsSlice(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.DefaultStringPtr(w.PrivateKey, "") w.PrivateKey = helpers.DefaultStringPtr(w.PrivateKey, "")
w.PreSharedKey = helpers.DefaultStringPtr(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 {
@@ -162,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

@@ -5,7 +5,7 @@ import (
"net" "net"
"github.com/qdm12/gluetun/internal/configuration/settings/helpers" "github.com/qdm12/gluetun/internal/configuration/settings/helpers"
"github.com/qdm12/gluetun/internal/constants/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"
) )
@@ -19,7 +19,7 @@ type WireguardSelection struct {
// in the internal state. // in the internal state.
EndpointIP net.IP 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,10 +36,8 @@ type WireguardSelection struct {
func (w WireguardSelection) validate(vpnProvider string) (err error) { func (w WireguardSelection) validate(vpnProvider string) (err error) {
// Validate EndpointIP // Validate EndpointIP
switch vpnProvider { switch vpnProvider {
case 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
case providers.Custom:
if len(w.EndpointIP) == 0 { if len(w.EndpointIP) == 0 {
return ErrWireguardEndpointIPNotSet return ErrWireguardEndpointIPNotSet
} }
@@ -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 ErrWireguardEndpointPortNotSet return ErrWireguardEndpointPortNotSet
} }
// EndpointPort cannot be set case constants.Ivpn, constants.Mullvad, constants.Windscribe:
case providers.Surfshark:
if *w.EndpointPort != 0 {
return 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,10 +78,8 @@ 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 ErrWireguardPublicKeyNotSet return ErrWireguardPublicKeyNotSet
} }

View File

@@ -7,8 +7,8 @@ import (
"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 net.IP, 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 nil, nil return nil, nil
} }
address = net.ParseIP(value) address = net.ParseIP(s)
if address == nil { if address == nil {
return nil, fmt.Errorf("environment variable %s: %w: %s", key, ErrIPAddressParse, value) return nil, fmt.Errorf("environment variable %s: %w: %s", key, ErrIPAddressParse, s)
} }
// TODO remove in v4 // TODO remove in v4
if !address.Equal(net.IPv4(127, 0, 0, 1)) { //nolint:gomnd 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

@@ -9,13 +9,13 @@ import (
"inet.af/netaddr" "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
} }

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

@@ -9,7 +9,7 @@ import (
"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,7 +22,7 @@ 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 = stringsToIPNets(outboundSubnetStrings) firewall.OutboundSubnets, err = stringsToIPNets(outboundSubnetStrings)
if err != nil { if err != nil {
@@ -73,7 +73,7 @@ func stringsToIPNets(ss []string) (ipNets []net.IPNet, err error) {
for i, s := range ss { for i, s := range ss {
ip, ipNet, err := net.ParseCIDR(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 ipNet.IP = ip
ipNets[i] = *ipNet ipNets[i] = *ipNet

View File

@@ -2,23 +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")
health.VPN.Initial, err = s.readDurationWithRetro( health.VPN.Initial, err = r.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 {
@@ -28,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,18 +12,8 @@ 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
} }
@@ -30,24 +21,15 @@ func envToCSV(envKey string) (values []string) {
} }
func envToInt(envKey string) (n int, 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
} }
return strconv.Atoi(s) return strconv.Atoi(s)
} }
func envToFloat64(envKey string) (f float64, err error) {
s := getCleanedEnv(envKey)
if s == "" {
return 0, nil
}
const bits = 64
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
} }
@@ -55,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
} }
@@ -67,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
} }
@@ -79,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
} }
@@ -96,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
} }
@@ -113,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
} }
@@ -132,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,9 +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(
"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)
@@ -16,7 +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(
"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"
) )
@@ -22,7 +23,7 @@ func readPprof() (settings pprof.Settings, err error) {
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
} }

View File

@@ -2,26 +2,26 @@ 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/gluetun/internal/constants/providers" "github.com/qdm12/gluetun/internal/constants"
"github.com/qdm12/gluetun/internal/constants/vpn"
) )
func (s *Source) readProvider(vpnType string) (provider settings.Provider, err error) { func (r *Reader) readProvider(vpnType string) (provider settings.Provider, err error) {
provider.Name = s.readVPNServiceProvider(vpnType) provider.Name = r.readVPNServiceProvider(vpnType)
var providerName string var providerName string
if provider.Name != nil { if provider.Name != nil {
providerName = *provider.Name providerName = *provider.Name
} }
provider.ServerSelection, err = s.readServerSelection(providerName, vpnType) provider.ServerSelection, err = r.readServerSelection(providerName, vpnType)
if err != nil { if err != nil {
return provider, fmt.Errorf("server selection: %w", err) return provider, fmt.Errorf("server selection: %w", err)
} }
provider.PortForwarding, err = s.readPortForward() provider.PortForwarding, err = r.readPortForward()
if err != nil { if err != nil {
return provider, fmt.Errorf("port forwarding: %w", err) return provider, fmt.Errorf("port forwarding: %w", err)
} }
@@ -29,20 +29,17 @@ func (s *Source) readProvider(vpnType string) (provider settings.Provider, err e
return provider, nil return provider, nil
} }
func (s *Source) readVPNServiceProvider(vpnType string) (vpnProviderPtr *string) { func (r *Reader) readVPNServiceProvider(vpnType string) (vpnProviderPtr *string) {
_, value := s.getEnvWithRetro("VPN_SERVICE_PROVIDER", "VPNSP") _, s := r.getEnvWithRetro("VPN_SERVICE_PROVIDER", "VPNSP")
if value == "" { s = strings.ToLower(s)
if vpnType != vpn.Wireguard && getCleanedEnv("OPENVPN_CUSTOM_CONFIG") != "" { switch {
// retro compatibility case vpnType != constants.Wireguard &&
return stringPtr(providers.Custom) os.Getenv("OPENVPN_CUSTOM_CONFIG") != "": // retro compatibility
} return stringPtr(constants.Custom)
case s == "":
return nil return nil
case s == "pia": // retro compatibility
return stringPtr(constants.PrivateInternetAccess)
} }
return stringPtr(s)
value = strings.ToLower(value)
if value == "pia" { // retro compatibility
return stringPtr(providers.PrivateInternetAccess)
}
return stringPtr(value)
} }

View File

@@ -2,24 +2,25 @@ 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) readPublicIP() (publicIP settings.PublicIP, err error) { func (r *Reader) readPublicIP() (publicIP settings.PublicIP, err error) {
publicIP.Period, err = readPublicIPPeriod() publicIP.Period, err = readPublicIPPeriod()
if err != nil { if err != nil {
return publicIP, err return publicIP, err
} }
publicIP.IPFilepath = s.readPublicIPFilepath() publicIP.IPFilepath = r.readPublicIPFilepath()
return publicIP, nil return publicIP, nil
} }
func readPublicIPPeriod() (period *time.Duration, err error) { func readPublicIPPeriod() (period *time.Duration, err error) {
s := getCleanedEnv("PUBLICIP_PERIOD") s := os.Getenv("PUBLICIP_PERIOD")
if s == "" { if s == "" {
return nil, nil //nolint:nilnil return nil, nil //nolint:nilnil
} }
@@ -33,10 +34,10 @@ func readPublicIPPeriod() (period *time.Duration, err error) {
return period, nil return period, nil
} }
func (s *Source) readPublicIPFilepath() (filepath *string) { func (r *Reader) readPublicIPFilepath() (filepath *string) {
_, value := s.getEnvWithRetro("PUBLICIP_FILE", "IP_STATUS_FILE") _, s := r.getEnvWithRetro("PUBLICIP_FILE", "IP_STATUS_FILE")
if value != "" { if s != "" {
return &value return &s
} }
return nil return nil
} }

View File

@@ -1,10 +1,15 @@
package env package env
import ( import (
"os"
"github.com/qdm12/gluetun/internal/configuration/settings" "github.com/qdm12/gluetun/internal/configuration/settings"
"github.com/qdm12/gluetun/internal/configuration/sources"
) )
type Source struct { var _ sources.Source = (*Reader)(nil)
type Reader struct {
warner Warner warner Warner
} }
@@ -12,36 +17,36 @@ type Warner interface {
Warn(s string) Warn(s string)
} }
func New(warner Warner) *Source { func New(warner Warner) *Reader {
return &Source{ return &Reader{
warner: warner, warner: warner,
} }
} }
func (s *Source) String() string { return "environment variables" } func (r *Reader) String() string { return "environment variables" }
func (s *Source) Read() (settings settings.Settings, err error) { func (r *Reader) Read() (settings settings.Settings, err error) {
settings.VPN, err = s.readVPN() settings.VPN, err = r.readVPN()
if err != nil { if err != nil {
return settings, err return settings, err
} }
settings.Firewall, err = s.readFirewall() settings.Firewall, err = r.readFirewall()
if err != nil { if err != nil {
return settings, err return settings, err
} }
settings.System, err = s.readSystem() settings.System, err = r.readSystem()
if err != nil { if err != nil {
return settings, err return settings, err
} }
settings.Health, err = s.ReadHealth() settings.Health, err = r.ReadHealth()
if err != nil { if err != nil {
return settings, err return settings, err
} }
settings.HTTPProxy, err = s.readHTTPProxy() settings.HTTPProxy, err = r.readHTTPProxy()
if err != nil { if err != nil {
return settings, err return settings, err
} }
@@ -51,7 +56,7 @@ func (s *Source) Read() (settings settings.Settings, err error) {
return settings, err return settings, err
} }
settings.PublicIP, err = s.readPublicIP() settings.PublicIP, err = r.readPublicIP()
if err != nil { if err != nil {
return settings, err return settings, err
} }
@@ -66,17 +71,17 @@ func (s *Source) Read() (settings settings.Settings, err error) {
return settings, err return settings, err
} }
settings.Shadowsocks, err = s.readShadowsocks() settings.Shadowsocks, err = r.readShadowsocks()
if err != nil { if err != nil {
return settings, err return settings, err
} }
settings.DNS, err = s.readDNS() settings.DNS, err = r.readDNS()
if err != nil { if err != nil {
return settings, err return settings, err
} }
settings.ControlServer, err = s.readControlServer() settings.ControlServer, err = r.readControlServer()
if err != nil { if err != nil {
return settings, err return settings, err
} }
@@ -89,8 +94,8 @@ func (s *Source) Read() (settings settings.Settings, err error) {
return settings, nil return settings, nil
} }
func (s *Source) onRetroActive(oldKey, newKey string) { func (r *Reader) onRetroActive(oldKey, newKey string) {
s.warner.Warn( r.warner.Warn(
"You are using the old environment variable " + oldKey + "You are using the old environment variable " + oldKey +
", please consider changing it to " + newKey) ", please consider changing it to " + newKey)
} }
@@ -101,17 +106,17 @@ func (s *Source) onRetroActive(oldKey, newKey string) {
// and end on returning the value corresponding to the currentKey. // and end on returning the value corresponding to the currentKey.
// Note retroKeys should be in order from oldest to most // Note retroKeys should be in order from oldest to most
// recent retro-compatibility key. // recent retro-compatibility key.
func (s *Source) getEnvWithRetro(currentKey string, func (r *Reader) getEnvWithRetro(currentKey string,
retroKeys ...string) (key, value string) { retroKeys ...string) (key, value string) {
// We check retro-compatibility keys first since // We check retro-compatibility keys first since
// the current key might be set in the Dockerfile. // the current key might be set in the Dockerfile.
for _, key = range retroKeys { for _, key = range retroKeys {
value = getCleanedEnv(key) value = os.Getenv(key)
if value != "" { if value != "" {
s.onRetroActive(key, currentKey) r.onRetroActive(key, currentKey)
return key, value return key, value
} }
} }
return currentKey, getCleanedEnv(currentKey) return currentKey, os.Getenv(currentKey)
} }

View File

@@ -2,24 +2,25 @@ package env
import ( import (
"fmt" "fmt"
"os"
"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) readControlServer() (controlServer settings.ControlServer, err error) { func (r *Reader) readControlServer() (controlServer settings.ControlServer, err error) {
controlServer.Log, err = readControlServerLog() controlServer.Log, err = readControlServerLog()
if err != nil { if err != nil {
return controlServer, err return controlServer, err
} }
controlServer.Address = s.readControlServerAddress() controlServer.Address = r.readControlServerAddress()
return controlServer, nil return controlServer, nil
} }
func readControlServerLog() (enabled *bool, err error) { func readControlServerLog() (enabled *bool, err error) {
s := getCleanedEnv("HTTP_CONTROL_SERVER_LOG") s := os.Getenv("HTTP_CONTROL_SERVER_LOG")
if s == "" { if s == "" {
return nil, nil //nolint:nilnil return nil, nil //nolint:nilnil
} }
@@ -32,17 +33,17 @@ func readControlServerLog() (enabled *bool, err error) {
return &log, nil return &log, nil
} }
func (s *Source) readControlServerAddress() (address *string) { func (r *Reader) readControlServerAddress() (address *string) {
key, value := s.getEnvWithRetro("HTTP_CONTROL_SERVER_ADDRESS", "HTTP_CONTROL_SERVER_PORT") key, s := r.getEnvWithRetro("HTTP_CONTROL_SERVER_ADDRESS", "HTTP_CONTROL_SERVER_PORT")
if value == "" { if s == "" {
return nil return nil
} }
if key == "HTTP_CONTROL_SERVER_ADDRESS" { if key == "HTTP_CONTROL_SERVER_ADDRESS" {
return &value return &s
} }
address = new(string) address = new(string)
*address = ":" + value *address = ":" + s
return address return address
} }

View File

@@ -4,56 +4,56 @@ import (
"errors" "errors"
"fmt" "fmt"
"net" "net"
"os"
"strconv" "strconv"
"strings" "strings"
"github.com/qdm12/gluetun/internal/configuration/settings" "github.com/qdm12/gluetun/internal/configuration/settings"
"github.com/qdm12/gluetun/internal/constants/providers" "github.com/qdm12/gluetun/internal/constants"
) )
var ( var (
ErrServerNumberNotValid = errors.New("server number is not valid") ErrServerNumberNotValid = errors.New("server number is not valid")
) )
func (s *Source) readServerSelection(vpnProvider, vpnType string) ( func (r *Reader) readServerSelection(vpnProvider, vpnType string) (
ss settings.ServerSelection, err error) { ss settings.ServerSelection, err error) {
ss.VPN = vpnType ss.VPN = vpnType
ss.TargetIP, err = s.readOpenVPNTargetIP() ss.TargetIP, err = r.readOpenVPNTargetIP()
if err != nil { if err != nil {
return ss, err return ss, err
} }
countriesKey, _ := s.getEnvWithRetro("SERVER_COUNTRIES", "COUNTRY") countriesKey, _ := r.getEnvWithRetro("SERVER_COUNTRIES", "COUNTRY")
ss.Countries = envToCSV(countriesKey) ss.Countries = envToCSV(countriesKey)
if vpnProvider == providers.Cyberghost && len(ss.Countries) == 0 { if vpnProvider == constants.Cyberghost && len(ss.Countries) == 0 {
// Retro-compatibility for Cyberghost using the REGION variable // Retro-compatibility for Cyberghost using the REGION variable
ss.Countries = envToCSV("REGION") ss.Countries = envToCSV("REGION")
if len(ss.Countries) > 0 { if len(ss.Countries) > 0 {
s.onRetroActive("REGION", "SERVER_COUNTRIES") r.onRetroActive("REGION", "SERVER_COUNTRIES")
} }
} }
regionsKey, _ := s.getEnvWithRetro("SERVER_REGIONS", "REGION") regionsKey, _ := r.getEnvWithRetro("SERVER_REGIONS", "REGION")
ss.Regions = envToCSV(regionsKey) ss.Regions = envToCSV(regionsKey)
citiesKey, _ := s.getEnvWithRetro("SERVER_CITIES", "CITY") citiesKey, _ := r.getEnvWithRetro("SERVER_CITIES", "CITY")
ss.Cities = envToCSV(citiesKey) ss.Cities = envToCSV(citiesKey)
ss.ISPs = envToCSV("ISP") ss.ISPs = envToCSV("ISP")
hostnamesKey, _ := s.getEnvWithRetro("SERVER_HOSTNAMES", "SERVER_HOSTNAME") hostnamesKey, _ := r.getEnvWithRetro("SERVER_HOSTNAMES", "SERVER_HOSTNAME")
ss.Hostnames = envToCSV(hostnamesKey) ss.Hostnames = envToCSV(hostnamesKey)
serverNamesKey, _ := s.getEnvWithRetro("SERVER_NAMES", "SERVER_NAME") serverNamesKey, _ := r.getEnvWithRetro("SERVER_NAMES", "SERVER_NAME")
ss.Names = envToCSV(serverNamesKey) ss.Names = envToCSV(serverNamesKey)
if csv := getCleanedEnv("SERVER_NUMBER"); csv != "" { if csv := os.Getenv("SERVER_NUMBER"); csv != "" {
numbersStrings := strings.Split(csv, ",") numbersStrings := strings.Split(csv, ",")
numbers := make([]uint16, len(numbersStrings)) numbers := make([]uint16, len(numbersStrings))
for i, numberString := range numbersStrings { for i, numberString := range numbersStrings {
const base, bitSize = 10, 16 number, err := strconv.Atoi(numberString)
number, err := strconv.ParseInt(numberString, base, bitSize)
if err != nil { if err != nil {
return ss, fmt.Errorf("%w: %s", return ss, fmt.Errorf("%w: %s",
ErrServerNumberNotValid, numberString) ErrServerNumberNotValid, numberString)
@@ -67,7 +67,7 @@ func (s *Source) readServerSelection(vpnProvider, vpnType string) (
} }
// Mullvad only // Mullvad only
ss.OwnedOnly, err = s.readOwnedOnly() ss.OwnedOnly, err = r.readOwnedOnly()
if err != nil { if err != nil {
return ss, err return ss, err
} }
@@ -78,12 +78,6 @@ func (s *Source) readServerSelection(vpnProvider, vpnType string) (
return ss, fmt.Errorf("environment variable FREE_ONLY: %w", err) return ss, fmt.Errorf("environment variable FREE_ONLY: %w", err)
} }
// VPNSecure only
ss.PremiumOnly, err = envToBoolPtr("PREMIUM_ONLY")
if err != nil {
return ss, fmt.Errorf("environment variable PREMIUM_ONLY: %w", err)
}
// VPNUnlimited only // VPNUnlimited only
ss.MultiHopOnly, err = envToBoolPtr("MULTIHOP_ONLY") ss.MultiHopOnly, err = envToBoolPtr("MULTIHOP_ONLY")
if err != nil { if err != nil {
@@ -96,12 +90,12 @@ func (s *Source) readServerSelection(vpnProvider, vpnType string) (
return ss, fmt.Errorf("environment variable STREAM_ONLY: %w", err) return ss, fmt.Errorf("environment variable STREAM_ONLY: %w", err)
} }
ss.OpenVPN, err = s.readOpenVPNSelection() ss.OpenVPN, err = r.readOpenVPNSelection()
if err != nil { if err != nil {
return ss, err return ss, err
} }
ss.Wireguard, err = s.readWireguardSelection() ss.Wireguard, err = r.readWireguardSelection()
if err != nil { if err != nil {
return ss, err return ss, err
} }
@@ -113,23 +107,23 @@ var (
ErrInvalidIP = errors.New("invalid IP address") ErrInvalidIP = errors.New("invalid IP address")
) )
func (s *Source) readOpenVPNTargetIP() (ip net.IP, err error) { func (r *Reader) readOpenVPNTargetIP() (ip net.IP, err error) {
envKey, value := s.getEnvWithRetro("VPN_ENDPOINT_IP", "OPENVPN_TARGET_IP") envKey, s := r.getEnvWithRetro("VPN_ENDPOINT_IP", "OPENVPN_TARGET_IP")
if value == "" { if s == "" {
return nil, nil return nil, nil
} }
ip = net.ParseIP(value) ip = net.ParseIP(s)
if ip == nil { if ip == nil {
return nil, fmt.Errorf("environment variable %s: %w: %s", return nil, fmt.Errorf("environment variable %s: %w: %s",
envKey, ErrInvalidIP, value) envKey, ErrInvalidIP, s)
} }
return ip, nil return ip, nil
} }
func (s *Source) readOwnedOnly() (ownedOnly *bool, err error) { func (r *Reader) readOwnedOnly() (ownedOnly *bool, err error) {
envKey, _ := s.getEnvWithRetro("OWNED_ONLY", "OWNED") envKey, _ := r.getEnvWithRetro("OWNED_ONLY", "OWNED")
ownedOnly, err = envToBoolPtr(envKey) ownedOnly, err = envToBoolPtr(envKey)
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

@@ -7,25 +7,25 @@ import (
"github.com/qdm12/gluetun/internal/configuration/settings" "github.com/qdm12/gluetun/internal/configuration/settings"
) )
func (s *Source) readShadowsocks() (shadowsocks settings.Shadowsocks, err error) { func (r *Reader) readShadowsocks() (shadowsocks settings.Shadowsocks, err error) {
shadowsocks.Enabled, err = envToBoolPtr("SHADOWSOCKS") shadowsocks.Enabled, err = envToBoolPtr("SHADOWSOCKS")
if err != nil { if err != nil {
return shadowsocks, fmt.Errorf("environment variable SHADOWSOCKS: %w", err) return shadowsocks, fmt.Errorf("environment variable SHADOWSOCKS: %w", err)
} }
shadowsocks.Address = s.readShadowsocksAddress() shadowsocks.Address = r.readShadowsocksAddress()
shadowsocks.LogAddresses, err = envToBoolPtr("SHADOWSOCKS_LOG") shadowsocks.LogAddresses, err = envToBoolPtr("SHADOWSOCKS_LOG")
if err != nil { if err != nil {
return shadowsocks, fmt.Errorf("environment variable SHADOWSOCKS_LOG: %w", err) return shadowsocks, fmt.Errorf("environment variable SHADOWSOCKS_LOG: %w", err)
} }
shadowsocks.CipherName = s.readShadowsocksCipher() shadowsocks.CipherName = r.readShadowsocksCipher()
shadowsocks.Password = envToStringPtr("SHADOWSOCKS_PASSWORD") shadowsocks.Password = envToStringPtr("SHADOWSOCKS_PASSWORD")
return shadowsocks, nil return shadowsocks, nil
} }
func (s *Source) readShadowsocksAddress() (address string) { func (r *Reader) readShadowsocksAddress() (address string) {
key, value := s.getEnvWithRetro("SHADOWSOCKS_LISTENING_ADDRESS", "SHADOWSOCKS_PORT") key, value := r.getEnvWithRetro("SHADOWSOCKS_LISTENING_ADDRESS", "SHADOWSOCKS_PORT")
if value == "" { if value == "" {
return "" return ""
} }
@@ -38,7 +38,7 @@ func (s *Source) readShadowsocksAddress() (address string) {
return ":" + value return ":" + value
} }
func (s *Source) readShadowsocksCipher() (cipher string) { func (r *Reader) readShadowsocksCipher() (cipher string) {
_, cipher = s.getEnvWithRetro("SHADOWSOCKS_CIPHER", "SHADOWSOCKS_METHOD") _, cipher = r.getEnvWithRetro("SHADOWSOCKS_CIPHER", "SHADOWSOCKS_METHOD")
return strings.ToLower(cipher) return strings.ToLower(cipher)
} }

View File

@@ -3,6 +3,7 @@ package env
import ( import (
"errors" "errors"
"fmt" "fmt"
"os"
"strconv" "strconv"
"github.com/qdm12/gluetun/internal/configuration/settings" "github.com/qdm12/gluetun/internal/configuration/settings"
@@ -14,42 +15,39 @@ var (
ErrSystemTimezoneNotValid = errors.New("timezone is not valid") ErrSystemTimezoneNotValid = errors.New("timezone is not valid")
) )
func (s *Source) readSystem() (system settings.System, err error) { func (r *Reader) readSystem() (system settings.System, err error) {
system.PUID, err = s.readID("PUID", "UID") system.PUID, err = r.readID("PUID", "UID")
if err != nil { if err != nil {
return system, err return system, err
} }
system.PGID, err = s.readID("PGID", "GID") system.PGID, err = r.readID("PGID", "GID")
if err != nil { if err != nil {
return system, err return system, err
} }
system.Timezone = getCleanedEnv("TZ") system.Timezone = os.Getenv("TZ")
return system, nil return system, nil
} }
var ErrSystemIDNotValid = errors.New("system ID is not valid") var ErrSystemIDNotValid = errors.New("system ID is not valid")
func (s *Source) readID(key, retroKey string) ( func (r *Reader) readID(key, retroKey string) (
id *uint32, err error) { id *uint16, err error) {
idEnvKey, idString := s.getEnvWithRetro(key, retroKey) idEnvKey, idString := r.getEnvWithRetro(key, retroKey)
if idString == "" { if idString == "" {
return nil, nil //nolint:nilnil return nil, nil //nolint:nilnil
} }
const base = 10 idInt, err := strconv.Atoi(idString)
const bitSize = 64
const max = uint64(^uint32(0))
idUint64, err := strconv.ParseUint(idString, base, bitSize)
if err != nil { if err != nil {
return nil, fmt.Errorf("environment variable %s: %w: %s", return nil, fmt.Errorf("environment variable %s: %w: %s: %s",
idEnvKey, ErrSystemIDNotValid, err) idEnvKey, ErrSystemIDNotValid, idString, err)
} else if idUint64 > max { } else if idInt < 0 || idInt > 65535 {
return nil, fmt.Errorf("environment variable %s: %w: %d: must be between 0 and %d", return nil, fmt.Errorf("environment variable %s: %w: %d: must be between 0 and 65535",
idEnvKey, ErrSystemIDNotValid, idUint64, max) idEnvKey, ErrSystemIDNotValid, idInt)
} }
return uint32Ptr(uint32(idUint64)), nil return uint16Ptr(uint16(idInt)), nil
} }

View File

@@ -14,51 +14,15 @@ func Test_Reader_readID(t *testing.T) {
keyValue string keyValue string
retroKeyPrefix string retroKeyPrefix string
retroValue string retroValue string
id *uint32 id *uint16
errWrapped error errWrapped error
errMessage string errMessage string
}{ }{
"empty string": {
keyPrefix: "ID",
retroKeyPrefix: "RETRO_ID",
},
"invalid string": {
keyPrefix: "ID",
keyValue: "invalid",
retroKeyPrefix: "RETRO_ID",
errWrapped: ErrSystemIDNotValid,
errMessage: `environment variable IDTest_Reader_readID/invalid_string: ` +
`system ID is not valid: ` +
`strconv.ParseUint: parsing "invalid": invalid syntax`,
},
"negative number": {
keyPrefix: "ID",
keyValue: "-1",
retroKeyPrefix: "RETRO_ID",
errWrapped: ErrSystemIDNotValid,
errMessage: `environment variable IDTest_Reader_readID/negative_number: ` +
`system ID is not valid: ` +
`strconv.ParseUint: parsing "-1": invalid syntax`,
},
"id 1000": { "id 1000": {
keyPrefix: "ID", keyPrefix: "ID",
keyValue: "1000", keyValue: "1000",
retroKeyPrefix: "RETRO_ID", retroKeyPrefix: "RETRO_ID",
id: uint32Ptr(1000), id: uint16Ptr(1000),
},
"max id": {
keyPrefix: "ID",
keyValue: "4294967295",
retroKeyPrefix: "RETRO_ID",
id: uint32Ptr(4294967295),
},
"above max id": {
keyPrefix: "ID",
keyValue: "4294967296",
retroKeyPrefix: "RETRO_ID",
errWrapped: ErrSystemIDNotValid,
errMessage: `environment variable IDTest_Reader_readID/above_max_id: ` +
`system ID is not valid: 4294967296: must be between 0 and 4294967295`,
}, },
} }
@@ -74,8 +38,8 @@ func Test_Reader_readID(t *testing.T) {
setTestEnv(t, key, testCase.keyValue) setTestEnv(t, key, testCase.keyValue)
setTestEnv(t, retroKey, testCase.retroValue) setTestEnv(t, retroKey, testCase.retroValue)
source := &Source{} reader := &Reader{}
id, err := source.readID(key, retroKey) id, err := reader.readID(key, retroKey)
assert.ErrorIs(t, err, testCase.errWrapped) assert.ErrorIs(t, err, testCase.errWrapped)
if err != nil { if err != nil {

View File

@@ -2,6 +2,8 @@ package env
import ( import (
"fmt" "fmt"
"net"
"os"
"time" "time"
"github.com/qdm12/gluetun/internal/configuration/settings" "github.com/qdm12/gluetun/internal/configuration/settings"
@@ -18,18 +20,13 @@ func readUpdater() (updater settings.Updater, err error) {
return updater, err return updater, err
} }
updater.MinRatio, err = envToFloat64("UPDATER_MIN_RATIO")
if err != nil {
return updater, fmt.Errorf("environment variable UPDATER_MIN_RATIO: %w", err)
}
updater.Providers = envToCSV("UPDATER_VPN_SERVICE_PROVIDERS") updater.Providers = envToCSV("UPDATER_VPN_SERVICE_PROVIDERS")
return updater, nil return updater, nil
} }
func readUpdaterPeriod() (period *time.Duration, err error) { func readUpdaterPeriod() (period *time.Duration, err error) {
s := getCleanedEnv("UPDATER_PERIOD") s := os.Getenv("UPDATER_PERIOD")
if s == "" { if s == "" {
return nil, nil //nolint:nilnil return nil, nil //nolint:nilnil
} }
@@ -41,11 +38,11 @@ func readUpdaterPeriod() (period *time.Duration, err error) {
return period, nil return period, nil
} }
func readUpdaterDNSAddress() (address string, err error) { func readUpdaterDNSAddress() (ip net.IP, err error) {
// TODO this is currently using Cloudflare in // TODO this is currently using Cloudflare in
// plaintext to not be blocked by DNS over TLS by default. // plaintext to not be blocked by DNS over TLS by default.
// If a plaintext address is set in the DNS settings, this one will be used. // If a plaintext address is set in the DNS settings, this one will be used.
// use custom future encrypted DNS written in Go without blocking // use custom future encrypted DNS written in Go without blocking
// as it's too much trouble to start another parallel unbound instance for now. // as it's too much trouble to start another parallel unbound instance for now.
return "", nil return nil, nil
} }

View File

@@ -2,6 +2,7 @@ package env
import ( import (
"fmt" "fmt"
"os"
"github.com/qdm12/gluetun/internal/configuration/settings" "github.com/qdm12/gluetun/internal/configuration/settings"
"github.com/qdm12/govalid/binary" "github.com/qdm12/govalid/binary"
@@ -17,7 +18,7 @@ func readVersion() (version settings.Version, err error) {
} }
func readVersionEnabled() (enabled *bool, err error) { func readVersionEnabled() (enabled *bool, err error) {
s := getCleanedEnv("VERSION_INFORMATION") s := os.Getenv("VERSION_INFORMATION")
if s == "" { if s == "" {
return nil, nil //nolint:nilnil return nil, nil //nolint:nilnil
} }

View File

@@ -2,25 +2,26 @@ package env
import ( import (
"fmt" "fmt"
"os"
"strings" "strings"
"github.com/qdm12/gluetun/internal/configuration/settings" "github.com/qdm12/gluetun/internal/configuration/settings"
) )
func (s *Source) readVPN() (vpn settings.VPN, err error) { func (r *Reader) readVPN() (vpn settings.VPN, err error) {
vpn.Type = strings.ToLower(getCleanedEnv("VPN_TYPE")) vpn.Type = strings.ToLower(os.Getenv("VPN_TYPE"))
vpn.Provider, err = s.readProvider(vpn.Type) vpn.Provider, err = r.readProvider(vpn.Type)
if err != nil { if err != nil {
return vpn, fmt.Errorf("VPN provider: %w", err) return vpn, fmt.Errorf("VPN provider: %w", err)
} }
vpn.OpenVPN, err = s.readOpenVPN() vpn.OpenVPN, err = r.readOpenVPN()
if err != nil { if err != nil {
return vpn, fmt.Errorf("OpenVPN: %w", err) return vpn, fmt.Errorf("OpenVPN: %w", err)
} }
vpn.Wireguard, err = s.readWireguard() vpn.Wireguard, err = r.readWireguard()
if err != nil { if err != nil {
return vpn, fmt.Errorf("wireguard: %w", err) return vpn, fmt.Errorf("wireguard: %w", err)
} }

View File

@@ -3,29 +3,27 @@ package env
import ( import (
"fmt" "fmt"
"net" "net"
"os"
"strings" "strings"
"github.com/qdm12/gluetun/internal/configuration/settings" "github.com/qdm12/gluetun/internal/configuration/settings"
) )
func (s *Source) readWireguard() (wireguard settings.Wireguard, err error) { func (r *Reader) readWireguard() (wireguard settings.Wireguard, err error) {
defer func() { defer func() {
err = unsetEnvKeys([]string{"WIREGUARD_PRIVATE_KEY", "WIREGUARD_PRESHARED_KEY"}, err) err = unsetEnvKeys([]string{"WIREGUARD_PRIVATE_KEY", "WIREGUARD_PRESHARED_KEY"}, err)
}() }()
wireguard.PrivateKey = envToStringPtr("WIREGUARD_PRIVATE_KEY") wireguard.PrivateKey = envToStringPtr("WIREGUARD_PRIVATE_KEY")
wireguard.PreSharedKey = envToStringPtr("WIREGUARD_PRESHARED_KEY") wireguard.PreSharedKey = envToStringPtr("WIREGUARD_PRESHARED_KEY")
_, wireguard.Interface = s.getEnvWithRetro("VPN_INTERFACE", "WIREGUARD_INTERFACE") _, wireguard.Interface = r.getEnvWithRetro("VPN_INTERFACE", "WIREGUARD_INTERFACE")
wireguard.Implementation = os.Getenv("WIREGUARD_IMPLEMENTATION") wireguard.Addresses, err = r.readWireguardAddresses()
wireguard.Addresses, err = s.readWireguardAddresses()
if err != nil { if err != nil {
return wireguard, err // already wrapped return wireguard, err // already wrapped
} }
return wireguard, nil return wireguard, nil
} }
func (s *Source) readWireguardAddresses() (addresses []net.IPNet, err error) { func (r *Reader) readWireguardAddresses() (addresses []net.IPNet, err error) {
key, addressesCSV := s.getEnvWithRetro("WIREGUARD_ADDRESSES", "WIREGUARD_ADDRESS") key, addressesCSV := r.getEnvWithRetro("WIREGUARD_ADDRESSES", "WIREGUARD_ADDRESS")
if addressesCSV == "" { if addressesCSV == "" {
return nil, nil return nil, nil
} }
@@ -33,7 +31,6 @@ func (s *Source) readWireguardAddresses() (addresses []net.IPNet, err error) {
addressStrings := strings.Split(addressesCSV, ",") addressStrings := strings.Split(addressesCSV, ",")
addresses = make([]net.IPNet, len(addressStrings)) addresses = make([]net.IPNet, len(addressStrings))
for i, addressString := range addressStrings { for i, addressString := range addressStrings {
addressString = strings.TrimSpace(addressString)
ip, ipNet, err := net.ParseCIDR(addressString) ip, ipNet, err := net.ParseCIDR(addressString)
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)

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