Compare commits

..

3 Commits

Author SHA1 Message Date
Quentin McGaw
c04cd621b0 fix(docker): openvpn 2.4.12-r0 install 2022-03-31 20:53:28 +00:00
Quentin McGaw
69c3f4e872 fix(env): OPENVPN_FLAGS functionality 2022-03-31 20:47:39 +00:00
Quentin McGaw
e1c20595b1 fix(health): use TCP dialing instead of ping
- `HEALTH_TARGET_ADDRESS` to replace `HEALTH_ADDRESS_TO_PING`
- Remove `github.com/go-ping/ping` dependency
- Dial TCP the target address, appending `:443` if port is not set
2022-03-21 20:38:55 +00:00
707 changed files with 83920 additions and 131636 deletions

View File

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

View File

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

View File

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

View File

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

39
.github/labels.yml vendored
View File

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

View File

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

View File

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

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

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

View File

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

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

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

View File

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

View File

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

35
.vscode/launch.json vendored
View File

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

View File

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

257
README.md
View File

@@ -1,130 +1,127 @@
# 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,
![Title image](https://raw.githubusercontent.com/qdm12/gluetun/master/title.svg) 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*
[![Build status](https://github.com/qdm12/gluetun/actions/workflows/ci.yml/badge.svg)](https://github.com/qdm12/gluetun/actions/workflows/ci.yml)
**ANNOUNCEMENT**: Large settings refactor merged on 2022-06-01, please file issues if you find any problem!
[![Docker pulls qmcgaw/gluetun](https://img.shields.io/docker/pulls/qmcgaw/gluetun.svg)](https://hub.docker.com/r/qmcgaw/gluetun)
[![Docker pulls qmcgaw/private-internet-access](https://img.shields.io/docker/pulls/qmcgaw/private-internet-access.svg)](https://hub.docker.com/r/qmcgaw/gluetun) ![Title image](https://raw.githubusercontent.com/qdm12/gluetun/master/title.svg)
[![Docker stars qmcgaw/gluetun](https://img.shields.io/docker/stars/qmcgaw/gluetun.svg)](https://hub.docker.com/r/qmcgaw/gluetun) [![Build status](https://github.com/qdm12/gluetun/actions/workflows/ci.yml/badge.svg)](https://github.com/qdm12/gluetun/actions/workflows/ci.yml)
[![Docker stars qmcgaw/private-internet-access](https://img.shields.io/docker/stars/qmcgaw/private-internet-access.svg)](https://hub.docker.com/r/qmcgaw/gluetun)
[![Docker pulls qmcgaw/gluetun](https://img.shields.io/docker/pulls/qmcgaw/gluetun.svg)](https://hub.docker.com/r/qmcgaw/gluetun)
![Last release](https://img.shields.io/github/release/qdm12/gluetun?label=Last%20release) [![Docker pulls qmcgaw/private-internet-access](https://img.shields.io/docker/pulls/qmcgaw/private-internet-access.svg)](https://hub.docker.com/r/qmcgaw/gluetun)
![Last Docker tag](https://img.shields.io/docker/v/qmcgaw/gluetun?sort=semver&label=Last%20Docker%20tag)
[![Last release size](https://img.shields.io/docker/image-size/qmcgaw/gluetun?sort=semver&label=Last%20released%20image)](https://hub.docker.com/r/qmcgaw/gluetun/tags?page=1&ordering=last_updated) [![Docker stars qmcgaw/gluetun](https://img.shields.io/docker/stars/qmcgaw/gluetun.svg)](https://hub.docker.com/r/qmcgaw/gluetun)
![GitHub last release date](https://img.shields.io/github/release-date/qdm12/gluetun?label=Last%20release%20date) [![Docker stars qmcgaw/private-internet-access](https://img.shields.io/docker/stars/qmcgaw/private-internet-access.svg)](https://hub.docker.com/r/qmcgaw/gluetun)
![Commits since release](https://img.shields.io/github/commits-since/qdm12/gluetun/latest?sort=semver)
![Last release](https://img.shields.io/github/release/qdm12/gluetun?label=Last%20release)
[![Latest size](https://img.shields.io/docker/image-size/qmcgaw/gluetun/latest?label=Latest%20image)](https://hub.docker.com/r/qmcgaw/gluetun/tags) ![Last Docker tag](https://img.shields.io/docker/v/qmcgaw/gluetun?sort=semver&label=Last%20Docker%20tag)
[![Last release size](https://img.shields.io/docker/image-size/qmcgaw/gluetun?sort=semver&label=Last%20released%20image)](https://hub.docker.com/r/qmcgaw/gluetun/tags?page=1&ordering=last_updated)
[![GitHub last commit](https://img.shields.io/github/last-commit/qdm12/gluetun.svg)](https://github.com/qdm12/gluetun/commits/master) ![GitHub last release date](https://img.shields.io/github/release-date/qdm12/gluetun?label=Last%20release%20date)
[![GitHub commit activity](https://img.shields.io/github/commit-activity/y/qdm12/gluetun.svg)](https://github.com/qdm12/gluetun/graphs/contributors) ![Commits since release](https://img.shields.io/github/commits-since/qdm12/gluetun/latest?sort=semver)
[![GitHub closed PRs](https://img.shields.io/github/issues-pr-closed/qdm12/gluetun.svg)](https://github.com/qdm12/gluetun/pulls?q=is%3Apr+is%3Aclosed)
[![GitHub issues](https://img.shields.io/github/issues/qdm12/gluetun.svg)](https://github.com/qdm12/gluetun/issues) [![Latest size](https://img.shields.io/docker/image-size/qmcgaw/gluetun/latest?label=Latest%20image)](https://hub.docker.com/r/qmcgaw/gluetun/tags)
[![GitHub closed issues](https://img.shields.io/github/issues-closed/qdm12/gluetun.svg)](https://github.com/qdm12/gluetun/issues?q=is%3Aissue+is%3Aclosed)
[![GitHub last commit](https://img.shields.io/github/last-commit/qdm12/gluetun.svg)](https://github.com/qdm12/gluetun/commits/master)
[![Lines of code](https://img.shields.io/tokei/lines/github/qdm12/gluetun)](https://github.com/qdm12/gluetun) [![GitHub commit activity](https://img.shields.io/github/commit-activity/y/qdm12/gluetun.svg)](https://github.com/qdm12/gluetun/graphs/contributors)
![Code size](https://img.shields.io/github/languages/code-size/qdm12/gluetun) [![GitHub closed PRs](https://img.shields.io/github/issues-pr-closed/qdm12/gluetun.svg)](https://github.com/qdm12/gluetun/pulls?q=is%3Apr+is%3Aclosed)
![GitHub repo size](https://img.shields.io/github/repo-size/qdm12/gluetun) [![GitHub issues](https://img.shields.io/github/issues/qdm12/gluetun.svg)](https://github.com/qdm12/gluetun/issues)
![Go version](https://img.shields.io/github/go-mod/go-version/qdm12/gluetun) [![GitHub closed issues](https://img.shields.io/github/issues-closed/qdm12/gluetun.svg)](https://github.com/qdm12/gluetun/issues?q=is%3Aissue+is%3Aclosed)
![Visitors count](https://visitor-badge.laobi.icu/badge?page_id=gluetun.readme) [![Lines of code](https://img.shields.io/tokei/lines/github/qdm12/gluetun)](https://github.com/qdm12/gluetun)
![Code size](https://img.shields.io/github/languages/code-size/qdm12/gluetun)
## Quick links ![GitHub repo size](https://img.shields.io/github/repo-size/qdm12/gluetun)
![Go version](https://img.shields.io/github/go-mod/go-version/qdm12/gluetun)
- [Setup](#Setup)
- [Features](#Features) ![Visitors count](https://visitor-badge.laobi.icu/badge?page_id=gluetun.readme)
- Problem?
- [Check the Wiki](https://github.com/qdm12/gluetun/wiki) ## Quick links
- [Start a discussion](https://github.com/qdm12/gluetun/discussions)
- [Fix the Unraid template](https://github.com/qdm12/gluetun/discussions/550) - [Setup](#Setup)
- Suggestion? - [Features](#Features)
- [Create an issue](https://github.com/qdm12/gluetun/issues) - Problem?
- [Join the Slack channel](https://join.slack.com/t/qdm12/shared_invite/enQtOTE0NjcxNTM1ODc5LTYyZmVlOTM3MGI4ZWU0YmJkMjUxNmQ4ODQ2OTAwYzMxMTlhY2Q1MWQyOWUyNjc2ODliNjFjMDUxNWNmNzk5MDk) - [Check the Wiki](https://github.com/qdm12/gluetun/wiki)
- Happy? - [Start a discussion](https://github.com/qdm12/gluetun/discussions)
- Sponsor me on [github.com/sponsors/qdm12](https://github.com/sponsors/qdm12) - [Fix the Unraid template](https://github.com/qdm12/gluetun/discussions/550)
- Donate to [paypal.me/qmcgaw](https://www.paypal.me/qmcgaw) - Suggestion?
- Drop me [an email](mailto:quentin.mcgaw@gmail.com) - [Create an issue](https://github.com/qdm12/gluetun/issues)
- **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) - [Join the Slack channel](https://join.slack.com/t/qdm12/shared_invite/enQtOTE0NjcxNTM1ODc5LTYyZmVlOTM3MGI4ZWU0YmJkMjUxNmQ4ODQ2OTAwYzMxMTlhY2Q1MWQyOWUyNjc2ODliNjFjMDUxNWNmNzk5MDk)
- Video: - Happy?
- Sponsor me on [github.com/sponsors/qdm12](https://github.com/sponsors/qdm12)
[![Video Gif](https://i.imgur.com/CetWunc.gif)](https://youtu.be/0F6I03LQcI4) - Donate to [paypal.me/qmcgaw](https://www.paypal.me/qmcgaw)
- Drop me [an email](mailto:quentin.mcgaw@gmail.com)
- [Substack Console interview](https://console.substack.com/p/console-72) - Video:
## Features [![Video Gif](https://i.imgur.com/CetWunc.gif)](https://youtu.be/0F6I03LQcI4)
- Based on Alpine 3.17 for a small Docker image of 42MB - [Substack Console interview](https://console.substack.com/p/console-72)
- 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 OpenVPN for all providers listed ## Features
- Supports Wireguard both kernelspace and userspace
- For **Mullvad**, **Ivpn**, **Surfshark** and **Windscribe** - Based on Alpine 3.15 for a small Docker image of 29MB
- For **ProtonVPN**, **PureVPN**, **Torguard**, **VPN Unlimited** and **WeVPN** using [the custom provider](https://github.com/qdm12/gluetun/wiki/Custom-provider) - 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
- For custom Wireguard configurations using [the custom provider](https://github.com/qdm12/gluetun/wiki/Custom-provider) - Supports OpenVPN for all providers listed
- More in progress, see [#134](https://github.com/qdm12/gluetun/issues/134) - Supports Wireguard both kernelspace and userspace
- DNS over TLS baked in with service provider(s) of your choice - For **Mullvad**, **Ivpn** and **Windscribe**
- DNS fine blocking of malicious/ads/surveillance hostnames and IP addresses, with live update every 24 hours - For **Torguard**, **VPN Unlimited** and **WeVPN** using [the custom provider](https://github.com/qdm12/gluetun/wiki/Custom-provider)
- Choose the vpn network protocol, `udp` or `tcp` - For custom Wireguard configurations using [the custom provider](https://github.com/qdm12/gluetun/wiki/Custom-provider)
- Built in firewall kill switch to allow traffic only with needed the VPN servers and LAN devices - More in progress, see [#134](https://github.com/qdm12/gluetun/issues/134)
- Built in Shadowsocks proxy (protocol based on SOCKS5 with an encryption layer, tunnels TCP+UDP) - DNS over TLS baked in with service provider(s) of your choice
- Built in HTTP proxy (tunnels HTTP and HTTPS through TCP) - DNS fine blocking of malicious/ads/surveillance hostnames and IP addresses, with live update every 24 hours
- [Connect other containers to it](https://github.com/qdm12/gluetun/wiki/Connect-a-container-to-gluetun) - Choose the vpn network protocol, `udp` or `tcp`
- [Connect LAN devices to it](https://github.com/qdm12/gluetun/wiki/Connect-a-LAN-device-to-gluetun) - Built in firewall kill switch to allow traffic only with needed the VPN servers and LAN devices
- Compatible with amd64, i686 (32 bit), **ARM** 64 bit, ARM 32 bit v6 and v7, and even ppc64le 🎆 - Built in Shadowsocks proxy (protocol based on SOCKS5 with an encryption layer, tunnels TCP+UDP)
- [Custom VPN server side port forwarding for Private Internet Access](https://github.com/qdm12/gluetun/wiki/Private-internet-access#vpn-server-port-forwarding) - Built in HTTP proxy (tunnels HTTP and HTTPS through TCP)
- Possibility of split horizon DNS by selecting multiple DNS over TLS providers - [Connect other containers to it](https://github.com/qdm12/gluetun/wiki/Connect-a-container-to-gluetun)
- Unbound subprogram drops root privileges once launched - [Connect LAN devices to it](https://github.com/qdm12/gluetun/wiki/Connect-a-LAN-device-to-gluetun)
- Can work as a Kubernetes sidecar container, thanks @rorph - Compatible with amd64, i686 (32 bit), **ARM** 64 bit, ARM 32 bit v6 and v7, and even ppc64le 🎆
- [Custom VPN server side port forwarding for Private Internet Access](https://github.com/qdm12/gluetun/wiki/Private-internet-access#vpn-server-port-forwarding)
## Setup - Possibility of split horizon DNS by selecting multiple DNS over TLS providers
- Unbound subprogram drops root privileges once launched
🎉 There are now instructions specific to each VPN provider with examples to help you get started as quickly as possible! - Can work as a Kubernetes sidecar container, thanks @rorph
Go to the [Wiki](https://github.com/qdm12/gluetun/wiki)! ## Setup
[🐛 Found a bug in the Wiki?!](https://github.com/qdm12/gluetun/issues/new?assignees=&labels=%F0%9F%93%84+Wiki+issue&template=wiki+issue.yml&title=Wiki+issue%3A+) 🎉 There are now instructions specific to each VPN provider with examples to help you get started as quickly as possible!
Here's a docker-compose.yml for the laziest: Go to the [Wiki](https://github.com/qdm12/gluetun/wiki)!
```yml [🐛 Found a bug in the Wiki?!](https://github.com/qdm12/gluetun/issues/new?assignees=&labels=%F0%9F%93%84+Wiki+issue&template=wiki+issue.yml&title=Wiki+issue%3A+)
version: "3"
services: Here's a docker-compose.yml for the laziest:
gluetun:
image: qmcgaw/gluetun ```yml
# container_name: gluetun version: "3"
# 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 services:
cap_add: gluetun:
- NET_ADMIN image: qmcgaw/gluetun
devices: # container_name: gluetun
- /dev/net/tun:/dev/net/tun # 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
ports: cap_add:
- 8888:8888/tcp # HTTP proxy - NET_ADMIN
- 8388:8388/tcp # Shadowsocks ports:
- 8388:8388/udp # Shadowsocks - 8888:8888/tcp # HTTP proxy
volumes: - 8388:8388/tcp # Shadowsocks
- /yourpath:/gluetun - 8388:8388/udp # Shadowsocks
environment: volumes:
# See https://github.com/qdm12/gluetun/wiki - /yourpath:/gluetun
- VPN_SERVICE_PROVIDER=ivpn environment:
- VPN_TYPE=openvpn # See https://github.com/qdm12/gluetun/wiki
# OpenVPN: - VPN_SERVICE_PROVIDER=ivpn
- OPENVPN_USER= - VPN_TYPE=openvpn
- OPENVPN_PASSWORD= # OpenVPN:
# Wireguard: - OPENVPN_USER=
# - WIREGUARD_PRIVATE_KEY=wOEI9rqqbDwnN8/Bpp22sVz48T71vJ4fYmFWujulwUU= - OPENVPN_PASSWORD=
# - WIREGUARD_ADDRESSES=10.64.222.21/32 # Wireguard:
# Timezone for accurate log times # - WIREGUARD_PRIVATE_KEY=wOEI9rqqbDwnN8/Bpp22sVz48T71vJ4fYmFWujulwUU=
- TZ= # - WIREGUARD_ADDRESSES=10.64.222.21/32
# Server list updater. See https://github.com/qdm12/gluetun/wiki/Updating-Servers#periodic-update # Timezone for accurate log times
- UPDATER_PERIOD= - TZ=
- UPDATER_VPN_SERVICE_PROVIDERS= ```
```
## License
🆕 Image also available as `ghcr.io/qdm12/gluetun`
[![MIT](https://img.shields.io/github/license/qdm12/gluetun)](https://github.com/qdm12/gluetun/master/LICENSE)
## License
[![MIT](https://img.shields.io/github/license/qdm12/gluetun)](https://github.com/qdm12/gluetun/master/LICENSE)

View File

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

47
go.mod
View File

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

168
go.sum
View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -2,15 +2,15 @@ package settings
import ( import (
"github.com/qdm12/gluetun/internal/configuration/settings/helpers" "github.com/qdm12/gluetun/internal/configuration/settings/helpers"
"github.com/qdm12/golibs/logging"
"github.com/qdm12/gotree" "github.com/qdm12/gotree"
"github.com/qdm12/log"
) )
// Log contains settings to configure the logger. // Log contains settings to configure the logger.
type Log struct { type Log struct {
// Level is the log level of the logger. // Level is the log level of the logger.
// It cannot be nil in the internal state. // It cannot be nil in the internal state.
Level *log.Level Level *logging.Level
} }
func (l Log) validate() (err error) { func (l Log) validate() (err error) {
@@ -19,25 +19,25 @@ func (l Log) validate() (err error) {
func (l *Log) copy() (copied Log) { func (l *Log) copy() (copied Log) {
return Log{ return Log{
Level: helpers.CopyPointer(l.Level), Level: helpers.CopyLogLevelPtr(l.Level),
} }
} }
// mergeWith merges the other settings into any // mergeWith merges the other settings into any
// unset field of the receiver settings object. // unset field of the receiver settings object.
func (l *Log) mergeWith(other Log) { func (l *Log) mergeWith(other Log) {
l.Level = helpers.MergeWithPointer(l.Level, other.Level) l.Level = helpers.MergeWithLogLevel(l.Level, other.Level)
} }
// overrideWith overrides fields of the receiver // overrideWith overrides fields of the receiver
// settings object with any field set in the other // settings object with any field set in the other
// settings. // settings.
func (l *Log) overrideWith(other Log) { func (l *Log) overrideWith(other Log) {
l.Level = helpers.OverrideWithPointer(l.Level, other.Level) l.Level = helpers.OverrideWithLogLevel(l.Level, other.Level)
} }
func (l *Log) setDefaults() { func (l *Log) setDefaults() {
l.Level = helpers.DefaultPointer(l.Level, log.LevelInfo) l.Level = helpers.DefaultLogLevel(l.Level, logging.LevelInfo)
} }
func (l Log) String() string { func (l Log) String() string {

View File

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

View File

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

View File

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

View File

@@ -4,8 +4,7 @@ import (
"fmt" "fmt"
"github.com/qdm12/gluetun/internal/configuration/settings/helpers" "github.com/qdm12/gluetun/internal/configuration/settings/helpers"
"github.com/qdm12/gluetun/internal/constants/providers" "github.com/qdm12/gluetun/internal/constants"
"github.com/qdm12/gluetun/internal/provider/privateinternetaccess/presets"
"github.com/qdm12/gotree" "github.com/qdm12/gotree"
) )
@@ -40,11 +39,11 @@ func (o OpenVPNSelection) validate(vpnProvider string) (err error) {
// Validate TCP // Validate TCP
if *o.TCP && helpers.IsOneOf(vpnProvider, if *o.TCP && helpers.IsOneOf(vpnProvider,
providers.Ipvanish, constants.Ipvanish,
providers.Perfectprivacy, constants.Perfectprivacy,
providers.Privado, constants.Privado,
providers.VPNUnlimited, constants.VPNUnlimited,
providers.Vyprvpn, constants.Vyprvpn,
) { ) {
return fmt.Errorf("%w: for VPN service provider %s", return fmt.Errorf("%w: for VPN service provider %s",
ErrOpenVPNTCPNotSupported, vpnProvider) ErrOpenVPNTCPNotSupported, vpnProvider)
@@ -54,47 +53,33 @@ func (o OpenVPNSelection) validate(vpnProvider string) (err error) {
if *o.CustomPort != 0 { if *o.CustomPort != 0 {
switch vpnProvider { switch vpnProvider {
// no restriction on port // no restriction on port
case providers.Cyberghost, providers.HideMyAss, case constants.Cyberghost, constants.HideMyAss,
providers.Privatevpn, providers.Torguard: constants.PrivateInternetAccess, constants.Privatevpn,
constants.Protonvpn, constants.Torguard:
// no custom port allowed // no custom port allowed
case providers.Expressvpn, providers.Fastestvpn, case constants.Expressvpn, constants.Fastestvpn,
providers.Ipvanish, providers.Nordvpn, constants.Ipvanish, constants.Nordvpn,
providers.Privado, providers.Purevpn, constants.Privado, constants.Purevpn,
providers.Surfshark, providers.VPNSecure, constants.Surfshark, constants.VPNUnlimited,
providers.VPNUnlimited, providers.Vyprvpn: constants.Vyprvpn:
return fmt.Errorf("%w: for VPN service provider %s", return fmt.Errorf("%w: for VPN service provider %s",
ErrOpenVPNCustomPortNotAllowed, vpnProvider) ErrOpenVPNCustomPortNotAllowed, vpnProvider)
default: default:
var allowedTCP, allowedUDP []uint16 var allowedTCP, allowedUDP []uint16
switch vpnProvider { switch vpnProvider {
case providers.Airvpn: case constants.Ivpn:
allowedTCP = []uint16{
53, 80, 443, // IP in 1, 3
1194, 2018, 41185, // IP in 1, 2, 3, 4
}
allowedUDP = []uint16{53, 80, 443, 1194, 2018, 41185}
case providers.Ivpn:
allowedTCP = []uint16{80, 443, 1143} allowedTCP = []uint16{80, 443, 1143}
allowedUDP = []uint16{53, 1194, 2049, 2050} allowedUDP = []uint16{53, 1194, 2049, 2050}
case providers.Mullvad: case constants.Mullvad:
allowedTCP = []uint16{80, 443, 1401} allowedTCP = []uint16{80, 443, 1401}
allowedUDP = []uint16{53, 1194, 1195, 1196, 1197, 1300, 1301, 1302, 1303, 1400} allowedUDP = []uint16{53, 1194, 1195, 1196, 1197, 1300, 1301, 1302, 1303, 1400}
case providers.Perfectprivacy: case constants.Perfectprivacy:
allowedTCP = []uint16{44, 443, 4433} allowedTCP = []uint16{44, 443, 4433}
allowedUDP = []uint16{44, 443, 4433} allowedUDP = []uint16{44, 443, 4433}
case providers.PrivateInternetAccess: case constants.Wevpn:
allowedTCP = []uint16{80, 110, 443}
allowedUDP = []uint16{53, 1194, 1197, 1198, 8080, 9201}
case providers.Protonvpn:
allowedTCP = []uint16{443, 5995, 8443}
allowedUDP = []uint16{80, 443, 1194, 4569, 5060}
case providers.SlickVPN:
allowedTCP = []uint16{443, 8080, 8888}
allowedUDP = []uint16{443, 8080, 8888}
case providers.Wevpn:
allowedTCP = []uint16{53, 1195, 1199, 2018} allowedTCP = []uint16{53, 1195, 1199, 2018}
allowedUDP = []uint16{80, 1194, 1198} allowedUDP = []uint16{80, 1194, 1198}
case providers.Windscribe: case constants.Windscribe:
allowedTCP = []uint16{21, 22, 80, 123, 143, 443, 587, 1194, 3306, 8080, 54783} allowedTCP = []uint16{21, 22, 80, 123, 143, 443, 587, 1194, 3306, 8080, 54783}
allowedUDP = []uint16{53, 80, 123, 443, 1194, 54783} allowedUDP = []uint16{53, 80, 123, 443, 1194, 54783}
} }
@@ -112,11 +97,11 @@ func (o OpenVPNSelection) validate(vpnProvider string) (err error) {
} }
// Validate EncPreset // Validate EncPreset
if vpnProvider == providers.PrivateInternetAccess { if vpnProvider == constants.PrivateInternetAccess {
validEncryptionPresets := []string{ validEncryptionPresets := []string{
presets.None, constants.PIAEncryptionPresetNone,
presets.Normal, constants.PIAEncryptionPresetNormal,
presets.Strong, constants.PIAEncryptionPresetStrong,
} }
if !helpers.IsOneOf(*o.PIAEncPreset, validEncryptionPresets...) { if !helpers.IsOneOf(*o.PIAEncPreset, validEncryptionPresets...) {
return fmt.Errorf("%w: %s; valid presets are %s", return fmt.Errorf("%w: %s; valid presets are %s",
@@ -130,37 +115,37 @@ func (o OpenVPNSelection) validate(vpnProvider string) (err error) {
func (o *OpenVPNSelection) copy() (copied OpenVPNSelection) { func (o *OpenVPNSelection) copy() (copied OpenVPNSelection) {
return OpenVPNSelection{ return OpenVPNSelection{
ConfFile: helpers.CopyPointer(o.ConfFile), ConfFile: helpers.CopyStringPtr(o.ConfFile),
TCP: helpers.CopyPointer(o.TCP), TCP: helpers.CopyBoolPtr(o.TCP),
CustomPort: helpers.CopyPointer(o.CustomPort), CustomPort: helpers.CopyUint16Ptr(o.CustomPort),
PIAEncPreset: helpers.CopyPointer(o.PIAEncPreset), PIAEncPreset: helpers.CopyStringPtr(o.PIAEncPreset),
} }
} }
func (o *OpenVPNSelection) mergeWith(other OpenVPNSelection) { func (o *OpenVPNSelection) mergeWith(other OpenVPNSelection) {
o.ConfFile = helpers.MergeWithPointer(o.ConfFile, other.ConfFile) o.ConfFile = helpers.MergeWithStringPtr(o.ConfFile, other.ConfFile)
o.TCP = helpers.MergeWithPointer(o.TCP, other.TCP) o.TCP = helpers.MergeWithBool(o.TCP, other.TCP)
o.CustomPort = helpers.MergeWithPointer(o.CustomPort, other.CustomPort) o.CustomPort = helpers.MergeWithUint16(o.CustomPort, other.CustomPort)
o.PIAEncPreset = helpers.MergeWithPointer(o.PIAEncPreset, other.PIAEncPreset) o.PIAEncPreset = helpers.MergeWithStringPtr(o.PIAEncPreset, other.PIAEncPreset)
} }
func (o *OpenVPNSelection) overrideWith(other OpenVPNSelection) { func (o *OpenVPNSelection) overrideWith(other OpenVPNSelection) {
o.ConfFile = helpers.OverrideWithPointer(o.ConfFile, other.ConfFile) o.ConfFile = helpers.OverrideWithStringPtr(o.ConfFile, other.ConfFile)
o.TCP = helpers.OverrideWithPointer(o.TCP, other.TCP) o.TCP = helpers.OverrideWithBool(o.TCP, other.TCP)
o.CustomPort = helpers.OverrideWithPointer(o.CustomPort, other.CustomPort) o.CustomPort = helpers.OverrideWithUint16(o.CustomPort, other.CustomPort)
o.PIAEncPreset = helpers.OverrideWithPointer(o.PIAEncPreset, other.PIAEncPreset) o.PIAEncPreset = helpers.OverrideWithStringPtr(o.PIAEncPreset, other.PIAEncPreset)
} }
func (o *OpenVPNSelection) setDefaults(vpnProvider string) { func (o *OpenVPNSelection) setDefaults(vpnProvider string) {
o.ConfFile = helpers.DefaultPointer(o.ConfFile, "") o.ConfFile = helpers.DefaultStringPtr(o.ConfFile, "")
o.TCP = helpers.DefaultPointer(o.TCP, false) o.TCP = helpers.DefaultBool(o.TCP, false)
o.CustomPort = helpers.DefaultPointer(o.CustomPort, 0) o.CustomPort = helpers.DefaultUint16(o.CustomPort, 0)
var defaultEncPreset string var defaultEncPreset string
if vpnProvider == providers.PrivateInternetAccess { if vpnProvider == constants.PrivateInternetAccess {
defaultEncPreset = presets.Strong defaultEncPreset = constants.PIAEncryptionPresetStrong
} }
o.PIAEncPreset = helpers.DefaultPointer(o.PIAEncPreset, defaultEncPreset) o.PIAEncPreset = helpers.DefaultStringPtr(o.PIAEncPreset, defaultEncPreset)
} }
func (o OpenVPNSelection) String() string { func (o OpenVPNSelection) String() string {

View File

@@ -6,7 +6,7 @@ import (
"strings" "strings"
"github.com/qdm12/gluetun/internal/configuration/settings/helpers" "github.com/qdm12/gluetun/internal/configuration/settings/helpers"
"github.com/qdm12/gluetun/internal/constants/providers" "github.com/qdm12/gluetun/internal/constants"
"github.com/qdm12/gotree" "github.com/qdm12/gotree"
) )
@@ -28,7 +28,7 @@ func (p PortForwarding) validate(vpnProvider string) (err error) {
} }
// Validate Enabled // Validate Enabled
validProviders := []string{providers.PrivateInternetAccess} validProviders := []string{constants.PrivateInternetAccess}
if !helpers.IsOneOf(vpnProvider, validProviders...) { if !helpers.IsOneOf(vpnProvider, validProviders...) {
return fmt.Errorf("%w: for provider %s, it is only available for %s", return fmt.Errorf("%w: for provider %s, it is only available for %s",
ErrPortForwardingEnabled, vpnProvider, strings.Join(validProviders, ", ")) ErrPortForwardingEnabled, vpnProvider, strings.Join(validProviders, ", "))
@@ -47,24 +47,24 @@ func (p PortForwarding) validate(vpnProvider string) (err error) {
func (p *PortForwarding) copy() (copied PortForwarding) { func (p *PortForwarding) copy() (copied PortForwarding) {
return PortForwarding{ return PortForwarding{
Enabled: helpers.CopyPointer(p.Enabled), Enabled: helpers.CopyBoolPtr(p.Enabled),
Filepath: helpers.CopyPointer(p.Filepath), Filepath: helpers.CopyStringPtr(p.Filepath),
} }
} }
func (p *PortForwarding) mergeWith(other PortForwarding) { func (p *PortForwarding) mergeWith(other PortForwarding) {
p.Enabled = helpers.MergeWithPointer(p.Enabled, other.Enabled) p.Enabled = helpers.MergeWithBool(p.Enabled, other.Enabled)
p.Filepath = helpers.MergeWithPointer(p.Filepath, other.Filepath) p.Filepath = helpers.MergeWithStringPtr(p.Filepath, other.Filepath)
} }
func (p *PortForwarding) overrideWith(other PortForwarding) { func (p *PortForwarding) overrideWith(other PortForwarding) {
p.Enabled = helpers.OverrideWithPointer(p.Enabled, other.Enabled) p.Enabled = helpers.OverrideWithBool(p.Enabled, other.Enabled)
p.Filepath = helpers.OverrideWithPointer(p.Filepath, other.Filepath) p.Filepath = helpers.OverrideWithStringPtr(p.Filepath, other.Filepath)
} }
func (p *PortForwarding) setDefaults() { func (p *PortForwarding) setDefaults() {
p.Enabled = helpers.DefaultPointer(p.Enabled, false) p.Enabled = helpers.DefaultBool(p.Enabled, false)
p.Filepath = helpers.DefaultPointer(p.Filepath, "/tmp/gluetun/forwarded_port") p.Filepath = helpers.DefaultStringPtr(p.Filepath, "/tmp/gluetun/forwarded_port")
} }
func (p PortForwarding) String() string { func (p PortForwarding) String() string {

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -2,51 +2,60 @@ package env
import ( import (
"fmt" "fmt"
"os"
"strings" "strings"
"github.com/qdm12/gluetun/internal/configuration/settings" "github.com/qdm12/gluetun/internal/configuration/settings"
"github.com/qdm12/govalid/binary" "github.com/qdm12/govalid/binary"
) )
func (s *Source) readOpenVPN() ( func (r *Reader) readOpenVPN() (
openVPN settings.OpenVPN, err error) { openVPN settings.OpenVPN, err error) {
defer func() { defer func() {
err = unsetEnvKeys([]string{"OPENVPN_KEY", "OPENVPN_CERT", err = unsetEnvKeys([]string{"OPENVPN_CLIENTKEY", "OPENVPN_CLIENTCRT"}, err)
"OPENVPN_KEY_PASSPHRASE", "OPENVPN_ENCRYPTED_KEY"}, err)
}() }()
openVPN.Version = getCleanedEnv("OPENVPN_VERSION") openVPN.Version = os.Getenv("OPENVPN_VERSION")
openVPN.User = s.readOpenVPNUser() openVPN.User = r.readOpenVPNUser()
openVPN.Password = s.readOpenVPNPassword() openVPN.Password = r.readOpenVPNPassword()
confFile := getCleanedEnv("OPENVPN_CUSTOM_CONFIG") confFile := os.Getenv("OPENVPN_CUSTOM_CONFIG")
if confFile != "" { if confFile != "" {
openVPN.ConfFile = &confFile openVPN.ConfFile = &confFile
} }
ciphersKey, _ := s.getEnvWithRetro("OPENVPN_CIPHERS", "OPENVPN_CIPHER") ciphersKey, _ := r.getEnvWithRetro("OPENVPN_CIPHERS", "OPENVPN_CIPHER")
openVPN.Ciphers = envToCSV(ciphersKey) openVPN.Ciphers = envToCSV(ciphersKey)
auth := getCleanedEnv("OPENVPN_AUTH") auth := os.Getenv("OPENVPN_AUTH")
if auth != "" { if auth != "" {
openVPN.Auth = &auth openVPN.Auth = &auth
} }
openVPN.Cert = envToStringPtr("OPENVPN_CERT") openVPN.ClientCrt, err = readBase64OrNil("OPENVPN_CLIENTCRT")
openVPN.Key = envToStringPtr("OPENVPN_KEY") if err != nil {
openVPN.EncryptedKey = envToStringPtr("OPENVPN_ENCRYPTED_KEY") return openVPN, fmt.Errorf("environment variable OPENVPN_CLIENTCRT: %w", err)
}
openVPN.KeyPassphrase = s.readOpenVPNKeyPassphrase() openVPN.ClientKey, err = readBase64OrNil("OPENVPN_CLIENTKEY")
if err != nil {
return openVPN, fmt.Errorf("environment variable OPENVPN_CLIENTKEY: %w", err)
}
openVPN.PIAEncPreset = s.readPIAEncryptionPreset() openVPN.PIAEncPreset = r.readPIAEncryptionPreset()
openVPN.IPv6, err = envToBoolPtr("OPENVPN_IPV6")
if err != nil {
return openVPN, fmt.Errorf("environment variable OPENVPN_IPV6: %w", err)
}
openVPN.MSSFix, err = envToUint16Ptr("OPENVPN_MSSFIX") openVPN.MSSFix, err = envToUint16Ptr("OPENVPN_MSSFIX")
if err != nil { if err != nil {
return openVPN, fmt.Errorf("environment variable OPENVPN_MSSFIX: %w", err) return openVPN, fmt.Errorf("environment variable OPENVPN_MSSFIX: %w", err)
} }
_, openVPN.Interface = s.getEnvWithRetro("VPN_INTERFACE", "OPENVPN_INTERFACE") _, openVPN.Interface = r.getEnvWithRetro("VPN_INTERFACE", "OPENVPN_INTERFACE")
openVPN.ProcessUser, err = s.readOpenVPNProcessUser() openVPN.ProcessUser, err = r.readOpenVPNProcessUser()
if err != nil { if err != nil {
return openVPN, err return openVPN, err
} }
@@ -56,7 +65,7 @@ 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") flagsStr := os.Getenv("OPENVPN_FLAGS")
if flagsStr != "" { if flagsStr != "" {
openVPN.Flags = strings.Fields(flagsStr) openVPN.Flags = strings.Fields(flagsStr)
} }
@@ -64,39 +73,33 @@ func (s *Source) readOpenVPN() (
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 +108,8 @@ func (s *Source) readPIAEncryptionPreset() (presetPtr *string) {
return nil return nil
} }
func (s *Source) readOpenVPNProcessUser() (processUser string, err error) { func (r *Reader) readOpenVPNProcessUser() (processUser string, err error) {
key, value := s.getEnvWithRetro("OPENVPN_PROCESS_USER", "OPENVPN_ROOT") key, value := r.getEnvWithRetro("OPENVPN_PROCESS_USER", "OPENVPN_ROOT")
if key == "OPENVPN_PROCESS_USER" { if key == "OPENVPN_PROCESS_USER" {
return value, nil return value, nil
} }

View File

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

View File

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

View File

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

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