Compare commits

...

65 Commits

Author SHA1 Message Date
dependabot[bot]
44bc60b00d Chore(deps): Bump docker/build-push-action from 4.0.0 to 4.1.1 (#1684) 2023-06-28 14:28:59 +02:00
dependabot[bot]
6f0be57860 Chore(deps): Bump golang.org/x/text from 0.9.0 to 0.10.0 (#1681) 2023-06-28 14:28:44 +02:00
Quentin McGaw
d3d8484b8e hotfix(env): case sensitivity for OPENVPN_CUSTOM_CONFIG 2023-06-28 12:27:13 +00:00
Quentin McGaw
515ae8efb3 hotfix(nordvpn): update url 2023-06-18 11:00:36 +00:00
Quentin McGaw
83826e1253 hotfix(settings): fix godot lint error 2023-06-12 13:51:50 +00:00
Quentin McGaw
4292a500ae fix(wireguard): delete existing Wireguard link before adding it 2023-06-10 20:23:21 +00:00
Quentin McGaw
4a0f9c36ba hotfix(nordvpn): accept countries in SERVER_REGIONS 2023-06-10 16:29:30 +00:00
Quentin McGaw
ea1991496e hotfix(routing): remove debug prints 2023-06-08 22:44:08 +00:00
Quentin McGaw
4675572328 hotfix(routing): change main table from 0 to 254 2023-06-08 20:03:07 +00:00
Quentin McGaw
412921fc1f hotfix(routing): ignore non-main table for routes
- When searching for default routes
- When searching for local networks
2023-06-08 19:50:42 +00:00
Quentin McGaw
1c905d0e6f chore(labels): add problem category labels
- Config problem
- Routing
- IPv6
- Port forwarding
2023-06-08 10:04:09 +00:00
Quentin McGaw
2ec9293324 feat(wireguard): MTU defaults to 1400 instead of 1420 2023-06-08 09:50:21 +00:00
Quentin McGaw
9b39a301a8 chore(routing): remove unused VPNDestinationIP 2023-06-08 09:17:27 +00:00
Quentin McGaw
cade2b99bf chore(routing): unexport IPIsPrivate as ipIsPrivate 2023-06-08 09:14:17 +00:00
Quentin McGaw
40cdb4f662 fix(netlink): RouteList list routes from all tables
- Do not filter by link anymore
- IPv6 detection simplified
2023-06-08 09:12:46 +00:00
Quentin McGaw
c58d6d4de2 chore(lint): upgrade to v1.53.2 and add linters
- gosmopolitan
- mirror
- tagalign
- zerologlint
2023-06-08 07:43:30 +00:00
Quentin McGaw
0da2b6ad0b chore(lint): add musttag linter and fix lint errors
Breaking change: JSON fields changed in the server API
2023-06-08 07:43:26 +00:00
Quentin McGaw
37f0e5c73b chore(lint): add linters dupword, paralleltest and gocheckcompilerdirectives 2023-06-08 07:40:37 +00:00
Quentin McGaw
a9cd7be3f9 chore(sources/env): bump gosettings to v0.3.0-rc13
- Use `RetroKeys` option with env.* method calls
- Use `CSV*` typed methods
- Inject `handleDeprecatedKey` function
2023-06-08 07:40:37 +00:00
Julio Gutierrez
07459ee854 feat(nordvpn): new API endpoint and wireguard support (#1380)
Co-authored-by: Quentin McGaw <quentin.mcgaw@gmail.com>
2023-06-08 09:39:07 +02:00
Quentin McGaw
943943e8d1 fix(settings): MergeWithSlice for both elements nil 2023-06-01 10:00:44 +00:00
Quentin McGaw
5927ee9dec chore(ci): trigger for PR to other branches 2023-06-01 09:09:01 +00:00
Quentin McGaw
3b136e02db chore(secrets): add test for readSecretFileAsStringPtr 2023-06-01 09:07:25 +00:00
Quentin McGaw
482447c151 chore(env): bump qdm12/gosettings to v0.3.0-rc11 2023-06-01 09:07:22 +00:00
Quentin McGaw
5d8fbf8006 fix(sources/secrets): do not lowercase env secret file paths 2023-06-01 08:20:13 +00:00
Quentin McGaw
2ab80771d9 feat(shadowsocks): bump from v0.4.0 to v0.5.0-rc1 2023-05-31 14:31:56 +00:00
Quentin McGaw
7399c00508 chore(sources/env): bump gosettings to v0.3.0-rc9 2023-05-31 14:31:56 +00:00
Leeroy Ding
2d2f657851 docs(readme): fix Alpine version from 3.17 to 3.18 (#1636) 2023-05-31 16:27:10 +02:00
dependabot[bot]
0e21fdc9de Chore(deps): Bump github.com/stretchr/testify from 1.8.3 to 1.8.4 (#1633) 2023-05-31 16:24:49 +02:00
Quentin McGaw
b87b2109b1 chore(settings): use gosettings/sources/env functions 2023-05-30 13:02:10 +00:00
Quentin McGaw
2c30984a10 hotfix(env): read some settings with case sensitivity 2023-05-30 12:46:10 +00:00
Quentin McGaw
47593928f9 fix(settings): use qdm12/gosettings env.Get 2023-05-29 20:43:06 +00:00
Quentin McGaw
b961284845 feat(dev): specify vscode recommendations 2023-05-29 16:42:00 +00:00
Quentin McGaw
b5d230d47a chore(dev): set build tag as linux for cross development 2023-05-29 16:40:10 +00:00
Quentin McGaw
c2972f7bf6 chore(dev): update devcontainer definitions 2023-05-29 15:57:09 +00:00
Quentin McGaw
aed235f52d chore(httpproxy): add Test_returnRedirect to prevent error wrap of ErrUseLastResponse 2023-05-29 09:44:49 +00:00
Quentin McGaw
bfe5e4380f fix(httpproxy): redirect from http to https 2023-05-29 09:39:48 +00:00
Quentin McGaw
eca182a32f chore(tun): not linux or not darwin tagged files 2023-05-29 09:36:29 +00:00
Quentin McGaw
caabaf918e feat(dev): support development on darwin (OSX)
- Netlink linux tagged files
- Netlink linux || darwin tagged files
- Create non-implemented files for NOT linux
- Create non-implemented files for NOT linux and NOT darwin
- Specify wireguard netlink integration test as for linux only
2023-05-29 07:26:59 +00:00
Quentin McGaw
d6924597dd chore(netlink): separate linux only and OS independent code
- Move `Addr` and its `String` method to `types.go`
- Move `IsWireguardSupported` to `wireguard.go` to have `family.go` OS independant
- Remove dependency on vishvananda/netlink in `ipv6.go`
- Move `Link` to `types.go`
- Move `Route` to `types.go`
- Move `Rule` and its `String` method to `types.go`
2023-05-29 06:56:55 +00:00
Quentin McGaw
c26476a2fd chore(netlink): remove unused link fields 2023-05-29 06:56:52 +00:00
Quentin McGaw
5be0d0bbba feat(wireguard): debug logs log obfuscated keys 2023-05-29 06:45:12 +00:00
Quentin McGaw
38ddcfa756 chore(netlink): define own types with minimal fields
- Allow to swap `github.com/vishvananda/netlink`
- Allow to add build tags for each platform
- One step closer to development on non-Linux platforms
2023-05-29 06:44:58 +00:00
Quentin McGaw
163ac48ce4 chore(wireguard): fix netlink integration test
- Broken since recent commit 9d1a0b60a2
2023-05-29 05:54:01 +00:00
Quentin McGaw
def407d610 chore(settings): use qdm12/gosettings functions
- use: FileExists, ObfuscateKey, BoolToYesNo
- remove local functions moved to gosettings
2023-05-28 10:33:36 +00:00
Quentin McGaw
22b2e2cc6e chore(deps): bump qdm12/gosettings to v0.3.0-rc4 2023-05-28 10:29:15 +00:00
Quentin McGaw
c92962e97c chore(deps): tidy Go dependencies 2023-05-28 10:26:25 +00:00
Quentin McGaw
9d1a0b60a2 fix(netlink): use AddrReplace instead of AddrAdd 2023-05-28 10:22:51 +00:00
Quentin McGaw
9cf2c9c4d2 chore(settings): remove now unused helpers/messages.go 2023-05-28 10:22:51 +00:00
Quentin McGaw
e7150ba254 chore(settings): remove unused settings helpers 2023-05-28 10:22:51 +00:00
Filippo Buletto
7ba70f19ef fix(settings): fix httpproxy.go error message (#1596) 2023-05-27 20:01:55 +02:00
dependabot[bot]
9488a9f88a Chore(deps): Bump github.com/breml/rootcerts from 0.2.10 to 0.2.11 (#1567) 2023-05-27 20:01:17 +02:00
dependabot[bot]
020196f1c3 Chore(deps): Bump github.com/stretchr/testify from 1.8.2 to 1.8.3 (#1575) 2023-05-27 20:01:08 +02:00
Quentin McGaw
7e325715c7 hotfix(settings): case insensitivity for server filters 2023-05-27 08:53:18 +00:00
Quentin McGaw
75670a80b8 chore(deps): bump gosettings and govalid 2023-05-27 08:52:41 +00:00
Quentin McGaw
a43973c093 chore(settings): use github.com/qdm12/gosettings 2023-05-25 12:08:43 +00:00
Quentin McGaw
1827a03afd fix(airvpn): allow Airvpn as Wireguard provider 2023-05-24 21:47:31 +00:00
Quentin McGaw
3100cc1e5e hotfix(routing): unmap ipv4-in-ipv6 when converting 2023-05-22 08:03:52 +00:00
Quentin McGaw
eed62fdc6d fix(routing): ip family match function
- ipv4-in-ipv6 should match ipv6
2023-05-22 06:01:52 +00:00
Quentin McGaw
d2b8dbcb10 chore(routing): remove old assigned ip debug log 2023-05-22 06:01:07 +00:00
Quentin McGaw
90d43856ef fix(routing): net.IPNet to netip.Prefix conversion 2023-05-22 06:00:24 +00:00
Quentin McGaw
86f95cb390 chore(docker): bump Alpine from 3.17 to 3.18 2023-05-21 13:25:01 +00:00
Quentin McGaw
3b807e2ca9 feat(openvpn): add support for openvpn 2.6 2023-05-21 13:23:51 +00:00
Quentin McGaw
e8f2296a0d change(openvpn): Openvpn 2.4 no longer supported 2023-05-21 13:20:02 +00:00
Lars Haalck
1dd38bc658 feat(wireguard): WIREGUARD_MTU enviromnent variable (#1571) 2023-05-21 15:11:07 +02:00
162 changed files with 106641 additions and 41062 deletions

View File

@@ -9,17 +9,21 @@ It works on Linux, Windows and OSX.
- [VS code](https://code.visualstudio.com/download) installed - [VS code](https://code.visualstudio.com/download) installed
- [VS code remote containers extension](https://marketplace.visualstudio.com/items?itemName=ms-vscode-remote.remote-containers) installed - [VS code remote containers extension](https://marketplace.visualstudio.com/items?itemName=ms-vscode-remote.remote-containers) installed
- [Docker](https://www.docker.com/products/docker-desktop) installed and running - [Docker](https://www.docker.com/products/docker-desktop) installed and running
- If you don't use Linux or WSL 2, share your home directory `~/` and the directory of your project with Docker Desktop
- [Docker Compose](https://docs.docker.com/compose/install/) installed - [Docker Compose](https://docs.docker.com/compose/install/) installed
- Ensure your host has the following and that they are accessible by Docker:
- `~/.ssh` directory
- `~/.gitconfig` file (can be empty)
## Setup ## Setup
1. Create the following files on your host if you don't have them:
```sh
touch ~/.gitconfig ~/.zsh_history
```
Note that the development container will create the empty directories `~/.docker`, `~/.ssh` and `~/.kube` if you don't have them.
1. **For Docker on OSX or Windows without WSL**: ensure your home directory `~` is accessible by Docker.
1. Open the command palette in Visual Studio Code (CTRL+SHIFT+P). 1. Open the command palette in Visual Studio Code (CTRL+SHIFT+P).
1. Select `Remote-Containers: Open Folder in Container...` and choose the project directory. 1. Select `Remote-Containers: Open Folder in Container...` and choose the project directory.
1. For Docker running on Windows HyperV, if you want to use SSH keys, bind mount them at `/tmp/.ssh` by changing the `volumes` section in the [docker-compose.yml](docker-compose.yml).
## Customization ## Customization
@@ -29,13 +33,9 @@ You can make changes to the [Dockerfile](Dockerfile) and then rebuild the image.
```Dockerfile ```Dockerfile
FROM qmcgaw/godevcontainer FROM qmcgaw/godevcontainer
USER root
RUN apk add curl RUN apk add curl
USER vscode
``` ```
Note that you may need to use `USER root` to build as root, and then change back to `USER vscode`.
To rebuild the image, either: To rebuild the image, either:
- With VSCode through the command palette, select `Remote-Containers: Rebuild and reopen in container` - With VSCode through the command palette, select `Remote-Containers: Rebuild and reopen in container`
@@ -47,11 +47,11 @@ You can customize **settings** and **extensions** in the [devcontainer.json](dev
### Entrypoint script ### Entrypoint script
You can bind mount a shell script to `/home/vscode/.welcome.sh` to replace the [current welcome script](shell/.welcome.sh). You can bind mount a shell script to `/root/.welcome.sh` to replace the [current welcome script](shell/.welcome.sh).
### Publish a port ### Publish a port
To access a port from your host to your development container, publish a port in [docker-compose.yml](docker-compose.yml). To access a port from your host to your development container, publish a port in [docker-compose.yml](docker-compose.yml). You can also now do it directly with VSCode without restarting the container.
### Run other services ### Run other services

View File

@@ -1,82 +1,73 @@
{ {
"name": "gluetun-dev", "name": "gluetun-dev",
"dockerComposeFile": [ "dockerComposeFile": [
"docker-compose.yml" "docker-compose.yml"
], ],
"service": "vscode", "service": "vscode",
"runServices": [ "runServices": [
"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": [ // "overrideCommand": "",
"golang.go", "customizations": {
"eamodio.gitlens", // IDE Git information "vscode": {
"davidanson.vscode-markdownlint", "extensions": [
"ms-azuretools.vscode-docker", // Docker integration and linting "golang.go",
"shardulm94.trailing-spaces", // Show trailing spaces "eamodio.gitlens", // IDE Git information
"Gruntfuggly.todo-tree", // Highlights TODO comments "davidanson.vscode-markdownlint",
"bierner.emojisense", // Emoji sense for markdown "ms-azuretools.vscode-docker", // Docker integration and linting
"stkb.rewrap", // rewrap comments after n characters on one line "shardulm94.trailing-spaces", // Show trailing spaces
"vscode-icons-team.vscode-icons", // Better file extension icons "Gruntfuggly.todo-tree", // Highlights TODO comments
"github.vscode-pull-request-github", // Github interaction "bierner.emojisense", // Emoji sense for markdown
"redhat.vscode-yaml", // Kubernetes, Drone syntax highlighting "stkb.rewrap", // rewrap comments after n characters on one line
"bajdzis.vscode-database", // Supports connections to mysql or postgres, over SSL, socked "vscode-icons-team.vscode-icons", // Better file extension icons
"IBM.output-colorizer", // Colorize your output/test logs "github.vscode-pull-request-github", // Github interaction
"mohsen1.prettify-json", // Prettify JSON data "redhat.vscode-yaml", // Kubernetes, Drone syntax highlighting
"github.copilot", "bajdzis.vscode-database", // Supports connections to mysql or postgres, over SSL, socked
], "IBM.output-colorizer", // Colorize your output/test logs
"settings": { "github.copilot" // AI code completion
"files.eol": "\n", ],
"remote.extensionKind": { "settings": {
"ms-azuretools.vscode-docker": "workspace" "files.eol": "\n",
}, "remote.extensionKind": {
"editor.codeActionsOnSaveTimeout": 3000, "ms-azuretools.vscode-docker": "workspace"
"go.useLanguageServer": true, },
"[go]": { "go.useLanguageServer": true,
"editor.formatOnSave": true, "[go]": {
"editor.codeActionsOnSave": { "editor.codeActionsOnSave": {
"source.organizeImports": true, "source.organizeImports": true
}, }
// Optional: Disable snippets, as they conflict with completion ranking. },
"editor.snippetSuggestions": "none" "[go.mod]": {
}, "editor.codeActionsOnSave": {
"[go.mod]": { "source.organizeImports": true
"editor.formatOnSave": true, }
"editor.codeActionsOnSave": { },
"source.organizeImports": true, "gopls": {
}, "usePlaceholders": false,
}, "staticcheck": true
"gopls": { },
"usePlaceholders": false, "go.lintTool": "golangci-lint",
"staticcheck": true "go.lintOnSave": "package",
}, "editor.formatOnSave": true,
"go.autocompleteUnimportedPackages": true, "go.buildTags": "linux",
"go.gotoSymbol.includeImports": true, "go.toolsEnvVars": {
"go.gotoSymbol.includeGoroot": true, "CGO_ENABLED": "0"
"go.lintTool": "golangci-lint", },
"go.buildOnSave": "workspace", "go.testEnvVars": {
"go.lintOnSave": "workspace", "CGO_ENABLED": "1"
"go.vetOnSave": "workspace", },
"editor.formatOnSave": true, "go.testFlags": [
"go.toolsEnvVars": { "-v",
"GOFLAGS": "-tags=", "-race"
// "CGO_ENABLED": 1 // for the race detector ],
}, "go.testTimeout": "10s",
"gopls.env": { "go.coverOnSingleTest": true,
"GOFLAGS": "-tags=" "go.coverOnSingleTestFile": true,
}, "go.coverOnTestPackage": true
"go.testEnvVars": { }
"": "" }
}, }
"go.testFlags": [
"-v",
// "-race"
],
"go.testTimeout": "10s",
"go.coverOnSingleTest": true,
"go.coverOnSingleTestFile": true,
"go.coverOnTestPackage": true
}
} }

View File

@@ -1,30 +1,28 @@
version: "3.7" version: "3.7"
services: services:
vscode: vscode:
build: . build: .
devices: volumes:
- /dev/net/tun:/dev/net/tun - ../:/workspace
volumes: # Docker socket to access Docker server
- ../:/workspace - /var/run/docker.sock:/var/run/docker.sock
# Docker socket to access Docker server # SSH directory for Linux, OSX and WSL
- /var/run/docker.sock:/var/run/docker.sock # On Linux and OSX, a symlink /mnt/ssh <-> ~/.ssh is
# Docker configuration # created in the container. On Windows, files are copied
- ~/.docker:/root/.docker # from /mnt/ssh to ~/.ssh to fix permissions.
# SSH directory for Linux, OSX and WSL - ~/.ssh:/mnt/ssh
# On Linux and OSX, a symlink /mnt/ssh <-> ~/.ssh is # Shell history persistence
# created in the container. On Windows, files are copied - ~/.zsh_history:/root/.zsh_history
# from /mnt/ssh to ~/.ssh to fix permissions. # Git config
- ~/.ssh:/mnt/ssh - ~/.gitconfig:/root/.gitconfig
# Shell history persistence environment:
- ~/.zsh_history:/root/.zsh_history - TZ=
environment: cap_add:
- TZ= # For debugging with dlv
cap_add: - SYS_PTRACE
# For debugging with dlv - NET_ADMIN
# - SYS_PTRACE security_opt:
- NET_ADMIN # For debugging with dlv
security_opt: - seccomp:unconfined
# For debugging with dlv entrypoint: [ "zsh", "-c", "while sleep 1000; do :; done" ]
- seccomp:unconfined
entrypoint: zsh -c "while sleep 1000; do :; done"

12
.github/labels.yml vendored
View File

@@ -95,6 +95,9 @@
description: "" description: ""
# Problem category # Problem category
- name: "Config problem"
color: "ffc7ea"
description: ""
- name: "Openvpn" - name: "Openvpn"
color: "ffc7ea" color: "ffc7ea"
description: "" description: ""
@@ -107,6 +110,15 @@
- name: "Firewall" - name: "Firewall"
color: "ffc7ea" color: "ffc7ea"
description: "" description: ""
- name: "Routing"
color: "ffc7ea"
description: ""
- name: "IPv6"
color: "ffc7ea"
description: ""
- name: "Port forwarding"
color: "ffc7ea"
description: ""
- name: "HTTP proxy" - name: "HTTP proxy"
color: "ffc7ea" color: "ffc7ea"
description: "" description: ""

View File

@@ -14,8 +14,6 @@ on:
- go.mod - go.mod
- go.sum - go.sum
pull_request: pull_request:
branches:
- master
paths-ignore: paths-ignore:
- .github/workflows/ci.yml - .github/workflows/ci.yml
- cmd/** - cmd/**

View File

@@ -17,8 +17,6 @@ on:
- go.mod - go.mod
- go.sum - go.sum
pull_request: pull_request:
branches:
- master
paths: paths:
- .github/workflows/ci.yml - .github/workflows/ci.yml
- cmd/** - cmd/**
@@ -136,7 +134,7 @@ jobs:
run: echo "::set-output name=value::$(git rev-parse --short HEAD)" run: echo "::set-output name=value::$(git rev-parse --short HEAD)"
- name: Build and push final image - name: Build and push final image
uses: docker/build-push-action@v4.0.0 uses: docker/build-push-action@v4.1.1
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 }}

View File

@@ -46,6 +46,7 @@ linters:
- decorder - decorder
- dogsled - dogsled
- dupl - dupl
- dupword
- durationcheck - durationcheck
- errchkjson - errchkjson
- errname - errname
@@ -54,6 +55,7 @@ linters:
- exportloopref - exportloopref
- forcetypeassert - forcetypeassert
- gci - gci
- gocheckcompilerdirectives
- gochecknoglobals - gochecknoglobals
- gochecknoinits - gochecknoinits
- gocognit - gocognit
@@ -68,6 +70,7 @@ linters:
- gomoddirectives - gomoddirectives
- goprintffuncname - goprintffuncname
- gosec - gosec
- gosmopolitan
- grouper - grouper
- importas - importas
- interfacebloat - interfacebloat
@@ -75,7 +78,9 @@ linters:
- lll - lll
- maintidx - maintidx
- makezero - makezero
- mirror
- misspell - misspell
- musttag
- nakedret - nakedret
- nestif - nestif
- nilerr - nilerr
@@ -83,6 +88,7 @@ linters:
- noctx - noctx
- nolintlint - nolintlint
- nosprintfhostport - nosprintfhostport
- paralleltest
- prealloc - prealloc
- predeclared - predeclared
- promlinter - promlinter
@@ -90,6 +96,7 @@ linters:
- revive - revive
- rowserrcheck - rowserrcheck
- sqlclosecheck - sqlclosecheck
- tagalign
- tenv - tenv
- thelper - thelper
- tparallel - tparallel
@@ -98,6 +105,7 @@ linters:
- usestdlibvars - usestdlibvars
- wastedassign - wastedassign
- whitespace - whitespace
- zerologlint
run: run:
skip-dirs: skip-dirs:

8
.vscode/extensions.json vendored Normal file
View File

@@ -0,0 +1,8 @@
{
// This list should be kept to the strict minimum
// to develop this project.
"recommendations": [
"golang.go",
"davidanson.vscode-markdownlint",
],
}

29
.vscode/settings.json vendored Normal file
View File

@@ -0,0 +1,29 @@
{
// The settings should be kept to the strict minimum
// to develop this project.
"files.eol": "\n",
"editor.formatOnSave": true,
"go.buildTags": "linux",
"go.toolsEnvVars": {
"CGO_ENABLED": "0"
},
"go.testEnvVars": {
"CGO_ENABLED": "1"
},
"go.testFlags": [
"-v",
"-race"
],
"go.testTimeout": "10s",
"go.coverOnSingleTest": true,
"go.coverOnSingleTestFile": true,
"go.coverOnTestPackage": true,
"go.useLanguageServer": true,
"[go]": {
"editor.codeActionsOnSave": {
"source.organizeImports": true
}
},
"go.lintTool": "golangci-lint",
"go.lintOnSave": "package"
}

View File

@@ -1,8 +1,8 @@
ARG ALPINE_VERSION=3.17 ARG ALPINE_VERSION=3.18
ARG GO_ALPINE_VERSION=3.17 ARG GO_ALPINE_VERSION=3.18
ARG GO_VERSION=1.20 ARG GO_VERSION=1.20
ARG XCPUTRANSLATE_VERSION=v0.6.0 ARG XCPUTRANSLATE_VERSION=v0.6.0
ARG GOLANGCI_LINT_VERSION=v1.52.2 ARG GOLANGCI_LINT_VERSION=v1.53.2
ARG MOCKGEN_VERSION=v1.6.0 ARG MOCKGEN_VERSION=v1.6.0
ARG BUILDPLATFORM=linux/amd64 ARG BUILDPLATFORM=linux/amd64
@@ -97,6 +97,7 @@ ENV VPN_SERVICE_PROVIDER=pia \
WIREGUARD_PRESHARED_KEY= \ WIREGUARD_PRESHARED_KEY= \
WIREGUARD_PUBLIC_KEY= \ WIREGUARD_PUBLIC_KEY= \
WIREGUARD_ADDRESSES= \ WIREGUARD_ADDRESSES= \
WIREGUARD_MTU=1400 \
WIREGUARD_IMPLEMENTATION=auto \ WIREGUARD_IMPLEMENTATION=auto \
# VPN server filtering # VPN server filtering
SERVER_REGIONS= \ SERVER_REGIONS= \
@@ -199,12 +200,11 @@ 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 wget && \
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.17/main" openvpn\~2.5 && \
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.5 && \
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 && \
mv /usr/sbin/openvpn /usr/sbin/openvpn2.5 && \ mv /usr/sbin/openvpn /usr/sbin/openvpn2.6 && \
# Fix vulnerability issue # Fix vulnerability issue
apk add --no-cache --update busybox && \ apk add --no-cache --update busybox && \
rm -rf /var/cache/apk/* /etc/unbound/* /usr/sbin/unbound-* /etc/openvpn/*.sh /usr/lib/openvpn/plugins/openvpn-plugin-down-root.so && \ rm -rf /var/cache/apk/* /etc/unbound/* /usr/sbin/unbound-* /etc/openvpn/*.sh /usr/lib/openvpn/plugins/openvpn-plugin-down-root.so && \

View File

@@ -35,8 +35,8 @@ Lightweight swiss-knife-like VPN client to multiple VPN service providers
## Quick links ## Quick links
- [Setup](#Setup) - [Setup](#setup)
- [Features](#Features) - [Features](#features)
- Problem? - Problem?
- [Check the Wiki](https://github.com/qdm12/gluetun/wiki) - [Check the Wiki](https://github.com/qdm12/gluetun/wiki)
- [Start a discussion](https://github.com/qdm12/gluetun/discussions) - [Start a discussion](https://github.com/qdm12/gluetun/discussions)
@@ -57,7 +57,7 @@ Lightweight swiss-knife-like VPN client to multiple VPN service providers
## Features ## Features
- Based on Alpine 3.17 for a small Docker image of 42MB - Based on Alpine 3.18 for a small Docker image of 35.6MB
- 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: **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 - Supports OpenVPN for all providers listed
- Supports Wireguard both kernelspace and userspace - Supports Wireguard both kernelspace and userspace

View File

@@ -264,8 +264,8 @@ func _main(ctx context.Context, buildInfo models.BuildInformation,
err = printVersions(ctx, logger, []printVersionElement{ err = printVersions(ctx, logger, []printVersionElement{
{name: "Alpine", getVersion: alpineConf.Version}, {name: "Alpine", getVersion: alpineConf.Version},
{name: "OpenVPN 2.4", getVersion: ovpnConf.Version24},
{name: "OpenVPN 2.5", getVersion: ovpnConf.Version25}, {name: "OpenVPN 2.5", getVersion: ovpnConf.Version25},
{name: "OpenVPN 2.6", getVersion: ovpnConf.Version26},
{name: "Unbound", getVersion: dnsConf.Version}, {name: "Unbound", getVersion: dnsConf.Version},
{name: "IPtables", getVersion: func(ctx context.Context) (version string, err error) { {name: "IPtables", getVersion: func(ctx context.Context) (version string, err error) {
return firewall.Version(ctx, cmder) return firewall.Version(ctx, cmder)
@@ -531,30 +531,29 @@ type netLinker interface {
type Addresser interface { type Addresser interface {
AddrList(link netlink.Link, family int) ( AddrList(link netlink.Link, family int) (
addresses []netlink.Addr, err error) addresses []netlink.Addr, err error)
AddrAdd(link netlink.Link, addr *netlink.Addr) error AddrReplace(link netlink.Link, addr netlink.Addr) error
} }
type Router interface { type Router interface {
RouteList(link netlink.Link, family int) ( RouteList(family int) (routes []netlink.Route, err error)
routes []netlink.Route, err error) RouteAdd(route netlink.Route) error
RouteAdd(route *netlink.Route) error RouteDel(route netlink.Route) error
RouteDel(route *netlink.Route) error RouteReplace(route netlink.Route) error
RouteReplace(route *netlink.Route) error
} }
type Ruler interface { type Ruler interface {
RuleList(family int) (rules []netlink.Rule, err error) RuleList(family int) (rules []netlink.Rule, err error)
RuleAdd(rule *netlink.Rule) error RuleAdd(rule netlink.Rule) error
RuleDel(rule *netlink.Rule) error RuleDel(rule netlink.Rule) error
} }
type Linker interface { type Linker interface {
LinkList() (links []netlink.Link, err error) LinkList() (links []netlink.Link, err error)
LinkByName(name string) (link netlink.Link, err error) LinkByName(name string) (link netlink.Link, err error)
LinkByIndex(index int) (link netlink.Link, err error) LinkByIndex(index int) (link netlink.Link, err error)
LinkAdd(link netlink.Link) (err error) LinkAdd(link netlink.Link) (linkIndex int, err error)
LinkDel(link netlink.Link) (err error) LinkDel(link netlink.Link) (err error)
LinkSetUp(link netlink.Link) (err error) LinkSetUp(link netlink.Link) (linkIndex int, err error)
LinkSetDown(link netlink.Link) (err error) LinkSetDown(link netlink.Link) (err error)
} }

15
go.mod
View File

@@ -3,25 +3,25 @@ module github.com/qdm12/gluetun
go 1.20 go 1.20
require ( require (
github.com/breml/rootcerts v0.2.10 github.com/breml/rootcerts v0.2.11
github.com/fatih/color v1.15.0 github.com/fatih/color v1.15.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
github.com/qdm12/gosettings v0.3.0-rc13
github.com/qdm12/goshutdown v0.3.0 github.com/qdm12/goshutdown v0.3.0
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.2.0-rc1
github.com/qdm12/log v0.1.0 github.com/qdm12/log v0.1.0
github.com/qdm12/ss-server v0.4.0 github.com/qdm12/ss-server v0.5.0-rc1
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.8.4
github.com/vishvananda/netlink v1.2.1-beta.2 github.com/vishvananda/netlink v1.2.1-beta.2
github.com/youmark/pkcs8 v0.0.0-20201027041543-1326539a0a0a github.com/youmark/pkcs8 v0.0.0-20201027041543-1326539a0a0a
golang.org/x/exp v0.0.0-20230519143937-03e91628a987
golang.org/x/net v0.10.0 golang.org/x/net v0.10.0
golang.org/x/sys v0.8.0 golang.org/x/sys v0.8.0
golang.org/x/text v0.9.0 golang.org/x/text v0.10.0
golang.zx2c4.com/wireguard v0.0.0-20230325221338-052af4a8072b golang.zx2c4.com/wireguard v0.0.0-20230325221338-052af4a8072b
golang.zx2c4.com/wireguard/wgctrl v0.0.0-20230215201556-9c5414ab4bde golang.zx2c4.com/wireguard/wgctrl v0.0.0-20230215201556-9c5414ab4bde
inet.af/netaddr v0.0.0-20220811202034-502d2d690317 inet.af/netaddr v0.0.0-20220811202034-502d2d690317
@@ -43,7 +43,8 @@ require (
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-20211027215823-ae77deb06f29 // indirect
go4.org/unsafe/assume-no-moving-gc v0.0.0-20230221090011-e4bae7ad2296 // indirect go4.org/unsafe/assume-no-moving-gc v0.0.0-20230221090011-e4bae7ad2296 // indirect
golang.org/x/crypto v0.6.0 // indirect golang.org/x/crypto v0.9.0 // indirect
golang.org/x/exp v0.0.0-20230522175609-2e198f4a06a1 // indirect
golang.org/x/sync v0.1.0 // indirect golang.org/x/sync v0.1.0 // indirect
golang.zx2c4.com/wintun v0.0.0-20230126152724-0fa3db229ce2 // indirect golang.zx2c4.com/wintun v0.0.0-20230126152724-0fa3db229ce2 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect

34
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.11 h1:njUAtoyZ6HUXPAPk63tGz0BEZk1/6gyfqK5fTzksHkM=
github.com/breml/rootcerts v0.2.10/go.mod h1:24FDtzYMpqIeYC7QzaE8VPRQaFZU5TIUDlyk8qwjD88= github.com/breml/rootcerts v0.2.11/go.mod h1:S/PKh+4d1HUn4HQovEB8hPJZO6pUZYrIhmXBhsegfXw=
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=
@@ -91,18 +91,20 @@ github.com/qdm12/golibs v0.0.0-20210603202746-e5494e9c2ebb/go.mod h1:15RBzkun0i8
github.com/qdm12/golibs v0.0.0-20210723175634-a75ca7fd74c2/go.mod h1:6aRbg4Z/bTbm9JfxsGXfWKHi7zsOvPfUTK1S5HuAFKg= github.com/qdm12/golibs v0.0.0-20210723175634-a75ca7fd74c2/go.mod h1:6aRbg4Z/bTbm9JfxsGXfWKHi7zsOvPfUTK1S5HuAFKg=
github.com/qdm12/golibs v0.0.0-20210822203818-5c568b0777b6 h1:bge5AL7cjHJMPz+5IOz5yF01q/l8No6+lIEBieA8gMg= github.com/qdm12/golibs v0.0.0-20210822203818-5c568b0777b6 h1:bge5AL7cjHJMPz+5IOz5yF01q/l8No6+lIEBieA8gMg=
github.com/qdm12/golibs v0.0.0-20210822203818-5c568b0777b6/go.mod h1:6aRbg4Z/bTbm9JfxsGXfWKHi7zsOvPfUTK1S5HuAFKg= github.com/qdm12/golibs v0.0.0-20210822203818-5c568b0777b6/go.mod h1:6aRbg4Z/bTbm9JfxsGXfWKHi7zsOvPfUTK1S5HuAFKg=
github.com/qdm12/gosettings v0.3.0-rc13 h1:fag+/hFPBUcNk3a5ifUbwNS2VgXFpxindkl8mQNk76U=
github.com/qdm12/gosettings v0.3.0-rc13/go.mod h1:JRV3opOpHvnKlIA29lKQMdYw1WSMVMfHYLLHPHol5ME=
github.com/qdm12/goshutdown v0.3.0 h1:pqBpJkdwlZlfTEx4QHtS8u8CXx6pG0fVo6S1N0MpSEM= github.com/qdm12/goshutdown v0.3.0 h1:pqBpJkdwlZlfTEx4QHtS8u8CXx6pG0fVo6S1N0MpSEM=
github.com/qdm12/goshutdown v0.3.0/go.mod h1:EqZ46No00kCTZ5qzdd3qIzY6ayhMt24QI8Mh8LVQYmM= github.com/qdm12/goshutdown v0.3.0/go.mod h1:EqZ46No00kCTZ5qzdd3qIzY6ayhMt24QI8Mh8LVQYmM=
github.com/qdm12/gosplash v0.1.0 h1:Sfl+zIjFZFP7b0iqf2l5UkmEY97XBnaKkH3FNY6Gf7g= github.com/qdm12/gosplash v0.1.0 h1:Sfl+zIjFZFP7b0iqf2l5UkmEY97XBnaKkH3FNY6Gf7g=
github.com/qdm12/gosplash v0.1.0/go.mod h1:+A3fWW4/rUeDXhY3ieBzwghKdnIPFJgD8K3qQkenJlw= github.com/qdm12/gosplash v0.1.0/go.mod h1:+A3fWW4/rUeDXhY3ieBzwghKdnIPFJgD8K3qQkenJlw=
github.com/qdm12/gotree v0.2.0 h1:+58ltxkNLUyHtATFereAcOjBVfY6ETqRex8XK90Fb/c= 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.2.0-rc1 h1:4iYQvU4ibrASgzelsEgZX4JyKX3UTB/DcHObzQ7BXtw=
github.com/qdm12/govalid v0.1.0/go.mod h1:CyS/OEQdOvunBgrtIsW93fjd4jBkwZPBjGSpxq3NwA4= github.com/qdm12/govalid v0.2.0-rc1/go.mod h1:/uWzVWMuS71wmbsVnlUxpQiy6EAXqm8eQ2RbyA72roQ=
github.com/qdm12/log v0.1.0 h1:jYBd/xscHYpblzZAd2kjZp2YmuYHjAAfbTViJWxoPTw= 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/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.5.0-rc1 h1:2rJEhDnUUc9AKtvyVu+CrnJwvdEjMaB1zFRQvTUlDPw=
github.com/qdm12/ss-server v0.4.0/go.mod h1:AY0p4huvPUPW+/CiWsJcDgT6sneDryk26VXSccPNCxY= github.com/qdm12/ss-server v0.5.0-rc1/go.mod h1:IoFYGpVpxfIB/dMTr0PnSegdhV1gEfZLS9Tr1Qn8uRg=
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=
github.com/qdm12/updated v0.0.0-20210603204757-205acfe6937e/go.mod h1:UvJRGkZ9XL3/D7e7JiTTVLm1F3Cymd3/gFpD6frEpBo= github.com/qdm12/updated v0.0.0-20210603204757-205acfe6937e/go.mod h1:UvJRGkZ9XL3/D7e7JiTTVLm1F3Cymd3/gFpD6frEpBo=
github.com/riobard/go-bloom v0.0.0-20200614022211-cdc8013cb5b3 h1:f/FNXud6gA3MNr8meMVVGxhp+QBTqY91tM8HjEuMjGg= github.com/riobard/go-bloom v0.0.0-20200614022211-cdc8013cb5b3 h1:f/FNXud6gA3MNr8meMVVGxhp+QBTqY91tM8HjEuMjGg=
@@ -111,16 +113,12 @@ 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/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk=
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
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 h1:Llsql0lnQEbHj0I1OuKyp8otXp0r3q0mPkuhwHfStVs=
github.com/vishvananda/netlink v1.2.1-beta.2/go.mod h1:twkDnbuQxJYemMlGd4JFIcuhgX83tXhKS2B/PRMpOho= 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=
@@ -148,10 +146,10 @@ golang.org/x/crypto v0.0.0-20200302210943-78000ba7a073/go.mod h1:LzIPMQfyMNhhGPh
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-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/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.9.0 h1:LF6fAI+IutBocDJ2OT0Q1g8plpYljMZ4+lty+dsqw3g=
golang.org/x/crypto v0.6.0/go.mod h1:OFC/31mSvZgRz0V1QTNCzfAI1aIRzbiufJtkMIlEp58= golang.org/x/crypto v0.9.0/go.mod h1:yrmDGqONDYtNj3tH8X9dzUun2m2lzPa9ngI6/RUPGR0=
golang.org/x/exp v0.0.0-20230519143937-03e91628a987 h1:3xJIFvzUFbu4ls0BTBYcgbCGhA63eAOEMxIHugyXJqA= golang.org/x/exp v0.0.0-20230522175609-2e198f4a06a1 h1:k/i9J1pBpvlfR+9QsetwPyERsqu1GIbi967PQMq3Ivc=
golang.org/x/exp v0.0.0-20230519143937-03e91628a987/go.mod h1:V1LtkGg67GoY2N1AnLN78QLrzxkLyJw7RJb1gzOOz9w= golang.org/x/exp v0.0.0-20230522175609-2e198f4a06a1/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=
@@ -206,8 +204,8 @@ 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.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
golang.org/x/text v0.9.0 h1:2sjJmO8cDvYveuX97RDLsxlyUxLl+GHoLxBiRdHllBE= golang.org/x/text v0.10.0 h1:UpjohKhiEgNc0CSauXmwYftY1+LlaC75SJwh0SgCX58=
golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= golang.org/x/text v0.10.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
golang.org/x/time v0.0.0-20191024005414-555d28b269f0 h1:/5xXl8Y5W96D+TtHSlonuFqGHIWVuyCkGJLwGh9JJFs= 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=

View File

@@ -4,7 +4,7 @@ import (
"fmt" "fmt"
"net/netip" "net/netip"
"github.com/qdm12/gluetun/internal/configuration/settings/helpers" "github.com/qdm12/gosettings"
"github.com/qdm12/gotree" "github.com/qdm12/gotree"
) )
@@ -40,7 +40,7 @@ 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: d.ServerAddress,
KeepNameserver: helpers.CopyPointer(d.KeepNameserver), KeepNameserver: gosettings.CopyPointer(d.KeepNameserver),
DoT: d.DoT.copy(), DoT: d.DoT.copy(),
} }
} }
@@ -48,8 +48,8 @@ func (d *DNS) Copy() (copied DNS) {
// 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 *DNS) mergeWith(other DNS) { func (d *DNS) mergeWith(other DNS) {
d.ServerAddress = helpers.MergeWithIP(d.ServerAddress, other.ServerAddress) d.ServerAddress = gosettings.MergeWithValidator(d.ServerAddress, other.ServerAddress)
d.KeepNameserver = helpers.MergeWithPointer(d.KeepNameserver, other.KeepNameserver) d.KeepNameserver = gosettings.MergeWithPointer(d.KeepNameserver, other.KeepNameserver)
d.DoT.mergeWith(other.DoT) d.DoT.mergeWith(other.DoT)
} }
@@ -57,15 +57,15 @@ func (d *DNS) mergeWith(other DNS) {
// settings object with any field set in the other // settings object with any field set in the other
// settings. // settings.
func (d *DNS) overrideWith(other DNS) { func (d *DNS) overrideWith(other DNS) {
d.ServerAddress = helpers.OverrideWithIP(d.ServerAddress, other.ServerAddress) d.ServerAddress = gosettings.OverrideWithValidator(d.ServerAddress, other.ServerAddress)
d.KeepNameserver = helpers.OverrideWithPointer(d.KeepNameserver, other.KeepNameserver) d.KeepNameserver = gosettings.OverrideWithPointer(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 := netip.AddrFrom4([4]byte{127, 0, 0, 1})
d.ServerAddress = helpers.DefaultIP(d.ServerAddress, localhost) d.ServerAddress = gosettings.DefaultValidator(d.ServerAddress, localhost)
d.KeepNameserver = helpers.DefaultPointer(d.KeepNameserver, false) d.KeepNameserver = gosettings.DefaultPointer(d.KeepNameserver, false)
d.DoT.setDefaults() d.DoT.setDefaults()
} }
@@ -76,7 +76,7 @@ func (d DNS) String() string {
func (d DNS) toLinesNode() (node *gotree.Node) { func (d DNS) toLinesNode() (node *gotree.Node) {
node = gotree.New("DNS settings:") node = gotree.New("DNS settings:")
node.Appendf("DNS server address to use: %s", d.ServerAddress) node.Appendf("DNS server address to use: %s", d.ServerAddress)
node.Appendf("Keep existing nameserver(s): %s", helpers.BoolPtrToYesNo(d.KeepNameserver)) node.Appendf("Keep existing nameserver(s): %s", gosettings.BoolToYesNo(d.KeepNameserver))
node.AppendNode(d.DoT.toLinesNode()) node.AppendNode(d.DoT.toLinesNode())
return node return node
} }

View File

@@ -7,7 +7,7 @@ import (
"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/gosettings"
"github.com/qdm12/gotree" "github.com/qdm12/gotree"
) )
@@ -23,9 +23,9 @@ type DNSBlacklist struct {
} }
func (b *DNSBlacklist) setDefaults() { func (b *DNSBlacklist) setDefaults() {
b.BlockMalicious = helpers.DefaultPointer(b.BlockMalicious, true) b.BlockMalicious = gosettings.DefaultPointer(b.BlockMalicious, true)
b.BlockAds = helpers.DefaultPointer(b.BlockAds, false) b.BlockAds = gosettings.DefaultPointer(b.BlockAds, false)
b.BlockSurveillance = helpers.DefaultPointer(b.BlockSurveillance, true) b.BlockSurveillance = gosettings.DefaultPointer(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: gosettings.CopyPointer(b.BlockMalicious),
BlockAds: helpers.CopyPointer(b.BlockAds), BlockAds: gosettings.CopyPointer(b.BlockAds),
BlockSurveillance: helpers.CopyPointer(b.BlockSurveillance), BlockSurveillance: gosettings.CopyPointer(b.BlockSurveillance),
AllowedHosts: helpers.CopySlice(b.AllowedHosts), AllowedHosts: gosettings.CopySlice(b.AllowedHosts),
AddBlockedHosts: helpers.CopySlice(b.AddBlockedHosts), AddBlockedHosts: gosettings.CopySlice(b.AddBlockedHosts),
AddBlockedIPs: helpers.CopySlice(b.AddBlockedIPs), AddBlockedIPs: gosettings.CopySlice(b.AddBlockedIPs),
AddBlockedIPPrefixes: helpers.CopySlice(b.AddBlockedIPPrefixes), AddBlockedIPPrefixes: gosettings.CopySlice(b.AddBlockedIPPrefixes),
} }
} }
func (b *DNSBlacklist) mergeWith(other DNSBlacklist) { func (b *DNSBlacklist) mergeWith(other DNSBlacklist) {
b.BlockMalicious = helpers.MergeWithPointer(b.BlockMalicious, other.BlockMalicious) b.BlockMalicious = gosettings.MergeWithPointer(b.BlockMalicious, other.BlockMalicious)
b.BlockAds = helpers.MergeWithPointer(b.BlockAds, other.BlockAds) b.BlockAds = gosettings.MergeWithPointer(b.BlockAds, other.BlockAds)
b.BlockSurveillance = helpers.MergeWithPointer(b.BlockSurveillance, other.BlockSurveillance) b.BlockSurveillance = gosettings.MergeWithPointer(b.BlockSurveillance, other.BlockSurveillance)
b.AllowedHosts = helpers.MergeSlices(b.AllowedHosts, other.AllowedHosts) b.AllowedHosts = gosettings.MergeWithSlice(b.AllowedHosts, other.AllowedHosts)
b.AddBlockedHosts = helpers.MergeSlices(b.AddBlockedHosts, other.AddBlockedHosts) b.AddBlockedHosts = gosettings.MergeWithSlice(b.AddBlockedHosts, other.AddBlockedHosts)
b.AddBlockedIPs = helpers.MergeSlices(b.AddBlockedIPs, other.AddBlockedIPs) b.AddBlockedIPs = gosettings.MergeWithSlice(b.AddBlockedIPs, other.AddBlockedIPs)
b.AddBlockedIPPrefixes = helpers.MergeSlices(b.AddBlockedIPPrefixes, other.AddBlockedIPPrefixes) b.AddBlockedIPPrefixes = gosettings.MergeWithSlice(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 = gosettings.OverrideWithPointer(b.BlockMalicious, other.BlockMalicious)
b.BlockAds = helpers.OverrideWithPointer(b.BlockAds, other.BlockAds) b.BlockAds = gosettings.OverrideWithPointer(b.BlockAds, other.BlockAds)
b.BlockSurveillance = helpers.OverrideWithPointer(b.BlockSurveillance, other.BlockSurveillance) b.BlockSurveillance = gosettings.OverrideWithPointer(b.BlockSurveillance, other.BlockSurveillance)
b.AllowedHosts = helpers.OverrideWithSlice(b.AllowedHosts, other.AllowedHosts) b.AllowedHosts = gosettings.OverrideWithSlice(b.AllowedHosts, other.AllowedHosts)
b.AddBlockedHosts = helpers.OverrideWithSlice(b.AddBlockedHosts, other.AddBlockedHosts) b.AddBlockedHosts = gosettings.OverrideWithSlice(b.AddBlockedHosts, other.AddBlockedHosts)
b.AddBlockedIPs = helpers.OverrideWithSlice(b.AddBlockedIPs, other.AddBlockedIPs) b.AddBlockedIPs = gosettings.OverrideWithSlice(b.AddBlockedIPs, other.AddBlockedIPs)
b.AddBlockedIPPrefixes = helpers.OverrideWithSlice(b.AddBlockedIPPrefixes, other.AddBlockedIPPrefixes) b.AddBlockedIPPrefixes = gosettings.OverrideWithSlice(b.AddBlockedIPPrefixes, other.AddBlockedIPPrefixes)
} }
func (b DNSBlacklist) ToBlacklistFormat() (settings blacklist.BuilderSettings, err error) { func (b DNSBlacklist) ToBlacklistFormat() (settings blacklist.BuilderSettings, err error) {
@@ -102,9 +102,9 @@ func (b DNSBlacklist) String() string {
func (b DNSBlacklist) toLinesNode() (node *gotree.Node) { func (b DNSBlacklist) toLinesNode() (node *gotree.Node) {
node = gotree.New("DNS filtering settings:") node = gotree.New("DNS filtering settings:")
node.Appendf("Block malicious: %s", helpers.BoolPtrToYesNo(b.BlockMalicious)) node.Appendf("Block malicious: %s", gosettings.BoolToYesNo(b.BlockMalicious))
node.Appendf("Block ads: %s", helpers.BoolPtrToYesNo(b.BlockAds)) node.Appendf("Block ads: %s", gosettings.BoolToYesNo(b.BlockAds))
node.Appendf("Block surveillance: %s", helpers.BoolPtrToYesNo(b.BlockSurveillance)) node.Appendf("Block surveillance: %s", gosettings.BoolToYesNo(b.BlockSurveillance))
if len(b.AllowedHosts) > 0 { if len(b.AllowedHosts) > 0 {
allowedHostsNode := node.Appendf("Allowed hosts:") allowedHostsNode := node.Appendf("Allowed hosts:")

View File

@@ -5,7 +5,7 @@ import (
"fmt" "fmt"
"time" "time"
"github.com/qdm12/gluetun/internal/configuration/settings/helpers" "github.com/qdm12/gosettings"
"github.com/qdm12/gotree" "github.com/qdm12/gotree"
) )
@@ -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: gosettings.CopyPointer(d.Enabled),
UpdatePeriod: helpers.CopyPointer(d.UpdatePeriod), UpdatePeriod: gosettings.CopyPointer(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 = gosettings.MergeWithPointer(d.Enabled, other.Enabled)
d.UpdatePeriod = helpers.MergeWithPointer(d.UpdatePeriod, other.UpdatePeriod) d.UpdatePeriod = gosettings.MergeWithPointer(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 = gosettings.OverrideWithPointer(d.Enabled, other.Enabled)
d.UpdatePeriod = helpers.OverrideWithPointer(d.UpdatePeriod, other.UpdatePeriod) d.UpdatePeriod = gosettings.OverrideWithPointer(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 = gosettings.DefaultPointer(d.Enabled, true)
const defaultUpdatePeriod = 24 * time.Hour const defaultUpdatePeriod = 24 * time.Hour
d.UpdatePeriod = helpers.DefaultPointer(d.UpdatePeriod, defaultUpdatePeriod) d.UpdatePeriod = gosettings.DefaultPointer(d.UpdatePeriod, defaultUpdatePeriod)
d.Unbound.setDefaults() d.Unbound.setDefaults()
d.Blacklist.setDefaults() d.Blacklist.setDefaults()
} }
@@ -95,7 +95,7 @@ func (d DoT) String() string {
func (d DoT) toLinesNode() (node *gotree.Node) { func (d DoT) toLinesNode() (node *gotree.Node) {
node = gotree.New("DNS over TLS settings:") node = gotree.New("DNS over TLS settings:")
node.Appendf("Enabled: %s", helpers.BoolPtrToYesNo(d.Enabled)) node.Appendf("Enabled: %s", gosettings.BoolToYesNo(d.Enabled))
if !*d.Enabled { if !*d.Enabled {
return node return node
} }

View File

@@ -4,7 +4,7 @@ import (
"fmt" "fmt"
"net/netip" "net/netip"
"github.com/qdm12/gluetun/internal/configuration/settings/helpers" "github.com/qdm12/gosettings"
"github.com/qdm12/gotree" "github.com/qdm12/gotree"
) )
@@ -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: gosettings.CopySlice(f.VPNInputPorts),
InputPorts: helpers.CopySlice(f.InputPorts), InputPorts: gosettings.CopySlice(f.InputPorts),
OutboundSubnets: helpers.CopySlice(f.OutboundSubnets), OutboundSubnets: gosettings.CopySlice(f.OutboundSubnets),
Enabled: helpers.CopyPointer(f.Enabled), Enabled: gosettings.CopyPointer(f.Enabled),
Debug: helpers.CopyPointer(f.Debug), Debug: gosettings.CopyPointer(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 = gosettings.MergeWithSlice(f.VPNInputPorts, other.VPNInputPorts)
f.InputPorts = helpers.MergeSlices(f.InputPorts, other.InputPorts) f.InputPorts = gosettings.MergeWithSlice(f.InputPorts, other.InputPorts)
f.OutboundSubnets = helpers.MergeSlices(f.OutboundSubnets, other.OutboundSubnets) f.OutboundSubnets = gosettings.MergeWithSlice(f.OutboundSubnets, other.OutboundSubnets)
f.Enabled = helpers.MergeWithPointer(f.Enabled, other.Enabled) f.Enabled = gosettings.MergeWithPointer(f.Enabled, other.Enabled)
f.Debug = helpers.MergeWithPointer(f.Debug, other.Debug) f.Debug = gosettings.MergeWithPointer(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 = gosettings.OverrideWithSlice(f.VPNInputPorts, other.VPNInputPorts)
f.InputPorts = helpers.OverrideWithSlice(f.InputPorts, other.InputPorts) f.InputPorts = gosettings.OverrideWithSlice(f.InputPorts, other.InputPorts)
f.OutboundSubnets = helpers.OverrideWithSlice(f.OutboundSubnets, other.OutboundSubnets) f.OutboundSubnets = gosettings.OverrideWithSlice(f.OutboundSubnets, other.OutboundSubnets)
f.Enabled = helpers.OverrideWithPointer(f.Enabled, other.Enabled) f.Enabled = gosettings.OverrideWithPointer(f.Enabled, other.Enabled)
f.Debug = helpers.OverrideWithPointer(f.Debug, other.Debug) f.Debug = gosettings.OverrideWithPointer(f.Debug, other.Debug)
} }
func (f *Firewall) setDefaults() { func (f *Firewall) setDefaults() {
f.Enabled = helpers.DefaultPointer(f.Enabled, true) f.Enabled = gosettings.DefaultPointer(f.Enabled, true)
f.Debug = helpers.DefaultPointer(f.Debug, false) f.Debug = gosettings.DefaultPointer(f.Debug, false)
} }
func (f Firewall) String() string { func (f Firewall) String() string {
@@ -83,7 +83,7 @@ func (f Firewall) String() string {
func (f Firewall) toLinesNode() (node *gotree.Node) { func (f Firewall) toLinesNode() (node *gotree.Node) {
node = gotree.New("Firewall settings:") node = gotree.New("Firewall settings:")
node.Appendf("Enabled: %s", helpers.BoolPtrToYesNo(f.Enabled)) node.Appendf("Enabled: %s", gosettings.BoolToYesNo(f.Enabled))
if !*f.Enabled { if !*f.Enabled {
return node return node
} }

View File

@@ -5,7 +5,7 @@ import (
"os" "os"
"time" "time"
"github.com/qdm12/gluetun/internal/configuration/settings/helpers" "github.com/qdm12/gosettings"
"github.com/qdm12/gotree" "github.com/qdm12/gotree"
"github.com/qdm12/govalid/address" "github.com/qdm12/govalid/address"
) )
@@ -37,7 +37,7 @@ type Health struct {
func (h Health) Validate() (err error) { func (h Health) Validate() (err error) {
uid := os.Getuid() uid := os.Getuid()
_, err = address.Validate(h.ServerAddress, err = address.Validate(h.ServerAddress,
address.OptionListening(uid)) address.OptionListening(uid))
if err != nil { if err != nil {
return fmt.Errorf("server listening address is not valid: %w", err) return fmt.Errorf("server listening address is not valid: %w", err)
@@ -65,11 +65,11 @@ func (h *Health) copy() (copied Health) {
// 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 *Health) MergeWith(other Health) { func (h *Health) MergeWith(other Health) {
h.ServerAddress = helpers.MergeWithString(h.ServerAddress, other.ServerAddress) h.ServerAddress = gosettings.MergeWithString(h.ServerAddress, other.ServerAddress)
h.ReadHeaderTimeout = helpers.MergeWithNumber(h.ReadHeaderTimeout, other.ReadHeaderTimeout) h.ReadHeaderTimeout = gosettings.MergeWithNumber(h.ReadHeaderTimeout, other.ReadHeaderTimeout)
h.ReadTimeout = helpers.MergeWithNumber(h.ReadTimeout, other.ReadTimeout) h.ReadTimeout = gosettings.MergeWithNumber(h.ReadTimeout, other.ReadTimeout)
h.TargetAddress = helpers.MergeWithString(h.TargetAddress, other.TargetAddress) h.TargetAddress = gosettings.MergeWithString(h.TargetAddress, other.TargetAddress)
h.SuccessWait = helpers.MergeWithNumber(h.SuccessWait, other.SuccessWait) h.SuccessWait = gosettings.MergeWithNumber(h.SuccessWait, other.SuccessWait)
h.VPN.mergeWith(other.VPN) h.VPN.mergeWith(other.VPN)
} }
@@ -77,23 +77,23 @@ func (h *Health) MergeWith(other Health) {
// settings object with any field set in the other // settings object with any field set in the other
// settings. // settings.
func (h *Health) OverrideWith(other Health) { func (h *Health) OverrideWith(other Health) {
h.ServerAddress = helpers.OverrideWithString(h.ServerAddress, other.ServerAddress) h.ServerAddress = gosettings.OverrideWithString(h.ServerAddress, other.ServerAddress)
h.ReadHeaderTimeout = helpers.OverrideWithNumber(h.ReadHeaderTimeout, other.ReadHeaderTimeout) h.ReadHeaderTimeout = gosettings.OverrideWithNumber(h.ReadHeaderTimeout, other.ReadHeaderTimeout)
h.ReadTimeout = helpers.OverrideWithNumber(h.ReadTimeout, other.ReadTimeout) h.ReadTimeout = gosettings.OverrideWithNumber(h.ReadTimeout, other.ReadTimeout)
h.TargetAddress = helpers.OverrideWithString(h.TargetAddress, other.TargetAddress) h.TargetAddress = gosettings.OverrideWithString(h.TargetAddress, other.TargetAddress)
h.SuccessWait = helpers.OverrideWithNumber(h.SuccessWait, other.SuccessWait) h.SuccessWait = gosettings.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 = gosettings.DefaultString(h.ServerAddress, "127.0.0.1:9999")
const defaultReadHeaderTimeout = 100 * time.Millisecond const defaultReadHeaderTimeout = 100 * time.Millisecond
h.ReadHeaderTimeout = helpers.DefaultNumber(h.ReadHeaderTimeout, defaultReadHeaderTimeout) h.ReadHeaderTimeout = gosettings.DefaultNumber(h.ReadHeaderTimeout, defaultReadHeaderTimeout)
const defaultReadTimeout = 500 * time.Millisecond const defaultReadTimeout = 500 * time.Millisecond
h.ReadTimeout = helpers.DefaultNumber(h.ReadTimeout, defaultReadTimeout) h.ReadTimeout = gosettings.DefaultNumber(h.ReadTimeout, defaultReadTimeout)
h.TargetAddress = helpers.DefaultString(h.TargetAddress, "cloudflare.com:443") h.TargetAddress = gosettings.DefaultString(h.TargetAddress, "cloudflare.com:443")
const defaultSuccessWait = 5 * time.Second const defaultSuccessWait = 5 * time.Second
h.SuccessWait = helpers.DefaultNumber(h.SuccessWait, defaultSuccessWait) h.SuccessWait = gosettings.DefaultNumber(h.SuccessWait, defaultSuccessWait)
h.VPN.setDefaults() h.VPN.setDefaults()
} }

View File

@@ -3,7 +3,7 @@ package settings
import ( import (
"time" "time"
"github.com/qdm12/gluetun/internal/configuration/settings/helpers" "github.com/qdm12/gosettings"
"github.com/qdm12/gotree" "github.com/qdm12/gotree"
) )
@@ -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: gosettings.CopyPointer(h.Initial),
Addition: helpers.CopyPointer(h.Addition), Addition: gosettings.CopyPointer(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 = gosettings.MergeWithPointer(h.Initial, other.Initial)
h.Addition = helpers.MergeWithPointer(h.Addition, other.Addition) h.Addition = gosettings.MergeWithPointer(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 = gosettings.OverrideWithPointer(h.Initial, other.Initial)
h.Addition = helpers.OverrideWithPointer(h.Addition, other.Addition) h.Addition = gosettings.OverrideWithPointer(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 = gosettings.DefaultPointer(h.Initial, initialDurationDefault)
h.Addition = helpers.DefaultPointer(h.Addition, additionDurationDefault) h.Addition = gosettings.DefaultPointer(h.Addition, additionDurationDefault)
} }
func (h HealthyWait) String() string { func (h HealthyWait) String() string {

View File

@@ -1,12 +1,6 @@
package helpers package helpers
import ( func IsOneOf[T comparable](value T, choices ...T) (ok bool) {
"errors"
"fmt"
"strings"
)
func IsOneOf(value string, choices ...string) (ok bool) {
for _, choice := range choices { for _, choice := range choices {
if value == choice { if value == choice {
return true return true
@@ -14,39 +8,3 @@ func IsOneOf(value string, choices ...string) (ok bool) {
} }
return false return false
} }
var (
ErrNoChoice = errors.New("one or more values is set but there is no possible value available")
ErrValueNotOneOf = errors.New("value is not one of the possible choices")
)
func AreAllOneOf(values, choices []string) (err error) {
if len(values) > 0 && len(choices) == 0 {
return fmt.Errorf("%w", ErrNoChoice)
}
set := make(map[string]struct{}, len(choices))
for _, choice := range choices {
choice = strings.ToLower(choice)
set[choice] = struct{}{}
}
for _, value := range values {
_, ok := set[value]
if !ok {
return fmt.Errorf("%w: value %q, choices available are %s",
ErrValueNotOneOf, value, strings.Join(choices, ", "))
}
}
return nil
}
func Uint16IsOneOf(port uint16, choices []uint16) (ok bool) {
for _, choice := range choices {
if port == choice {
return true
}
}
return false
}

View File

@@ -1,20 +0,0 @@
package helpers
import (
"net/netip"
"golang.org/x/exp/slices"
)
func CopyPointer[T any](original *T) (copied *T) {
if original == nil {
return nil
}
copied = new(T)
*copied = *original
return copied
}
func CopySlice[T string | uint16 | netip.Addr | netip.Prefix](original []T) (copied []T) {
return slices.Clone(original)
}

View File

@@ -1,39 +0,0 @@
package helpers
import (
"net/netip"
)
func DefaultPointer[T any](existing *T, defaultValue T) (
result *T) {
if existing != nil {
return existing
}
result = new(T)
*result = defaultValue
return result
}
func DefaultString(existing string, defaultValue string) (
result string) {
if existing != "" {
return existing
}
return defaultValue
}
func DefaultNumber[T Number](existing T, defaultValue T) ( //nolint:ireturn
result T) {
if existing != 0 {
return existing
}
return defaultValue
}
func DefaultIP(existing netip.Addr, defaultValue netip.Addr) (
result netip.Addr) {
if existing.IsValid() {
return existing
}
return defaultValue
}

View File

@@ -1,31 +0,0 @@
package helpers
import (
"errors"
"fmt"
"os"
"path/filepath"
)
var (
ErrFileDoesNotExist = errors.New("file does not exist")
ErrFileRead = errors.New("cannot read file")
ErrFileClose = errors.New("cannot close file")
)
func FileExists(path string) (err error) {
path = filepath.Clean(path)
f, err := os.Open(path)
if errors.Is(err, os.ErrNotExist) {
return fmt.Errorf("%w: %s", ErrFileDoesNotExist, path)
} else if err != nil {
return fmt.Errorf("%w: %s", ErrFileRead, err)
}
if err := f.Close(); err != nil {
return fmt.Errorf("%w: %s", ErrFileClose, err)
}
return nil
}

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,69 +0,0 @@
package helpers
import (
"net/http"
"net/netip"
)
func MergeWithPointer[T any](existing, other *T) (result *T) {
if existing != nil {
return existing
} else if other == nil {
return nil
}
result = new(T)
*result = *other
return result
}
func MergeWithString(existing, other string) (result string) {
if existing != "" {
return existing
}
return other
}
func MergeWithNumber[T Number](existing, other T) (result T) { //nolint:ireturn
if existing != 0 {
return existing
}
return other
}
func MergeWithIP(existing, other netip.Addr) (result netip.Addr) {
if existing.IsValid() {
return existing
}
return other
}
func MergeWithHTTPHandler(existing, other http.Handler) (result http.Handler) {
if existing != nil {
return existing
}
return other
}
func MergeSlices[T comparable](a, b []T) (result []T) {
if a == nil && b == nil {
return nil
}
seen := make(map[T]struct{}, len(a)+len(b))
result = make([]T, 0, len(a)+len(b))
for _, s := range a {
if _, ok := seen[s]; ok {
continue // duplicate
}
result = append(result, s)
seen[s] = struct{}{}
}
for _, s := range b {
if _, ok := seen[s]; ok {
continue // duplicate
}
result = append(result, s)
seen[s] = struct{}{}
}
return result
}

View File

@@ -1,29 +0,0 @@
package helpers
import (
"fmt"
"strings"
)
func ChoicesOrString(choices []string) string {
return strings.Join(
choices[:len(choices)-1], ", ") +
" or " + choices[len(choices)-1]
}
func PortChoicesOrString(ports []uint16) (s string) {
switch len(ports) {
case 0:
return "there is no allowed port"
case 1:
return "allowed port is " + fmt.Sprint(ports[0])
}
s = "allowed ports are "
portStrings := make([]string, len(ports))
for i := range ports {
portStrings[i] = fmt.Sprint(ports[i])
}
s += ChoicesOrString(portStrings)
return s
}

View File

@@ -1,25 +0,0 @@
package helpers
func ObfuscateWireguardKey(fullKey string) (obfuscatedKey string) {
const minKeyLength = 10
if len(fullKey) < minKeyLength {
return "(too short)"
}
lastIndex := len(fullKey) - 1
return fullKey[0:2] + "..." + fullKey[lastIndex-2:]
}
func ObfuscatePassword(password string) (obfuscatedPassword string) {
if password != "" {
return "[set]"
}
return "[not set]"
}
func ObfuscateData(data string) (obfuscated string) {
if data != "" {
return "[set]"
}
return "[not set]"
}

View File

@@ -1,52 +0,0 @@
package helpers
import (
"net/http"
"net/netip"
)
func OverrideWithPointer[T any](existing, other *T) (result *T) {
if other == nil {
return existing
}
result = new(T)
*result = *other
return result
}
func OverrideWithString(existing, other string) (result string) {
if other == "" {
return existing
}
return other
}
func OverrideWithNumber[T Number](existing, other T) (result T) { //nolint:ireturn
if other == 0 {
return existing
}
return other
}
func OverrideWithIP(existing, other netip.Addr) (result netip.Addr) {
if !other.IsValid() {
return existing
}
return other
}
func OverrideWithHTTPHandler(existing, other http.Handler) (result http.Handler) {
if other != nil {
return other
}
return existing
}
func OverrideWithSlice[T any](existing, other []T) (result []T) {
if other == nil {
return existing
}
result = make([]T, len(other))
copy(result, other)
return result
}

View File

@@ -1,12 +1,5 @@
package helpers package helpers
func BoolPtrToYesNo(b *bool) string {
if *b {
return "yes"
}
return "no"
}
func TCPPtrToString(tcp *bool) string { func TCPPtrToString(tcp *bool) string {
if *tcp { if *tcp {
return "TCP" return "TCP"

View File

@@ -5,7 +5,7 @@ import (
"os" "os"
"time" "time"
"github.com/qdm12/gluetun/internal/configuration/settings/helpers" "github.com/qdm12/gosettings"
"github.com/qdm12/gotree" "github.com/qdm12/gotree"
"github.com/qdm12/govalid/address" "github.com/qdm12/govalid/address"
) )
@@ -46,7 +46,7 @@ func (h HTTPProxy) validate() (err error) {
// Do not validate user and password // Do not validate user and password
uid := os.Getuid() uid := os.Getuid()
_, err = address.Validate(h.ListeningAddress, address.OptionListening(uid)) err = address.Validate(h.ListeningAddress, address.OptionListening(uid))
if err != nil { if err != nil {
return fmt.Errorf("%w: %s", ErrServerAddressNotValid, h.ListeningAddress) return fmt.Errorf("%w: %s", ErrServerAddressNotValid, h.ListeningAddress)
} }
@@ -56,12 +56,12 @@ func (h HTTPProxy) validate() (err error) {
func (h *HTTPProxy) copy() (copied HTTPProxy) { func (h *HTTPProxy) copy() (copied HTTPProxy) {
return HTTPProxy{ return HTTPProxy{
User: helpers.CopyPointer(h.User), User: gosettings.CopyPointer(h.User),
Password: helpers.CopyPointer(h.Password), Password: gosettings.CopyPointer(h.Password),
ListeningAddress: h.ListeningAddress, ListeningAddress: h.ListeningAddress,
Enabled: helpers.CopyPointer(h.Enabled), Enabled: gosettings.CopyPointer(h.Enabled),
Stealth: helpers.CopyPointer(h.Stealth), Stealth: gosettings.CopyPointer(h.Stealth),
Log: helpers.CopyPointer(h.Log), Log: gosettings.CopyPointer(h.Log),
ReadHeaderTimeout: h.ReadHeaderTimeout, ReadHeaderTimeout: h.ReadHeaderTimeout,
ReadTimeout: h.ReadTimeout, ReadTimeout: h.ReadTimeout,
} }
@@ -70,41 +70,41 @@ func (h *HTTPProxy) copy() (copied HTTPProxy) {
// 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 = gosettings.MergeWithPointer(h.User, other.User)
h.Password = helpers.MergeWithPointer(h.Password, other.Password) h.Password = gosettings.MergeWithPointer(h.Password, other.Password)
h.ListeningAddress = helpers.MergeWithString(h.ListeningAddress, other.ListeningAddress) h.ListeningAddress = gosettings.MergeWithString(h.ListeningAddress, other.ListeningAddress)
h.Enabled = helpers.MergeWithPointer(h.Enabled, other.Enabled) h.Enabled = gosettings.MergeWithPointer(h.Enabled, other.Enabled)
h.Stealth = helpers.MergeWithPointer(h.Stealth, other.Stealth) h.Stealth = gosettings.MergeWithPointer(h.Stealth, other.Stealth)
h.Log = helpers.MergeWithPointer(h.Log, other.Log) h.Log = gosettings.MergeWithPointer(h.Log, other.Log)
h.ReadHeaderTimeout = helpers.MergeWithNumber(h.ReadHeaderTimeout, other.ReadHeaderTimeout) h.ReadHeaderTimeout = gosettings.MergeWithNumber(h.ReadHeaderTimeout, other.ReadHeaderTimeout)
h.ReadTimeout = helpers.MergeWithNumber(h.ReadTimeout, other.ReadTimeout) h.ReadTimeout = gosettings.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 = gosettings.OverrideWithPointer(h.User, other.User)
h.Password = helpers.OverrideWithPointer(h.Password, other.Password) h.Password = gosettings.OverrideWithPointer(h.Password, other.Password)
h.ListeningAddress = helpers.OverrideWithString(h.ListeningAddress, other.ListeningAddress) h.ListeningAddress = gosettings.OverrideWithString(h.ListeningAddress, other.ListeningAddress)
h.Enabled = helpers.OverrideWithPointer(h.Enabled, other.Enabled) h.Enabled = gosettings.OverrideWithPointer(h.Enabled, other.Enabled)
h.Stealth = helpers.OverrideWithPointer(h.Stealth, other.Stealth) h.Stealth = gosettings.OverrideWithPointer(h.Stealth, other.Stealth)
h.Log = helpers.OverrideWithPointer(h.Log, other.Log) h.Log = gosettings.OverrideWithPointer(h.Log, other.Log)
h.ReadHeaderTimeout = helpers.OverrideWithNumber(h.ReadHeaderTimeout, other.ReadHeaderTimeout) h.ReadHeaderTimeout = gosettings.OverrideWithNumber(h.ReadHeaderTimeout, other.ReadHeaderTimeout)
h.ReadTimeout = helpers.OverrideWithNumber(h.ReadTimeout, other.ReadTimeout) h.ReadTimeout = gosettings.OverrideWithNumber(h.ReadTimeout, other.ReadTimeout)
} }
func (h *HTTPProxy) setDefaults() { func (h *HTTPProxy) setDefaults() {
h.User = helpers.DefaultPointer(h.User, "") h.User = gosettings.DefaultPointer(h.User, "")
h.Password = helpers.DefaultPointer(h.Password, "") h.Password = gosettings.DefaultPointer(h.Password, "")
h.ListeningAddress = helpers.DefaultString(h.ListeningAddress, ":8888") h.ListeningAddress = gosettings.DefaultString(h.ListeningAddress, ":8888")
h.Enabled = helpers.DefaultPointer(h.Enabled, false) h.Enabled = gosettings.DefaultPointer(h.Enabled, false)
h.Stealth = helpers.DefaultPointer(h.Stealth, false) h.Stealth = gosettings.DefaultPointer(h.Stealth, false)
h.Log = helpers.DefaultPointer(h.Log, false) h.Log = gosettings.DefaultPointer(h.Log, false)
const defaultReadHeaderTimeout = time.Second const defaultReadHeaderTimeout = time.Second
h.ReadHeaderTimeout = helpers.DefaultNumber(h.ReadHeaderTimeout, defaultReadHeaderTimeout) h.ReadHeaderTimeout = gosettings.DefaultNumber(h.ReadHeaderTimeout, defaultReadHeaderTimeout)
const defaultReadTimeout = 3 * time.Second const defaultReadTimeout = 3 * time.Second
h.ReadTimeout = helpers.DefaultNumber(h.ReadTimeout, defaultReadTimeout) h.ReadTimeout = gosettings.DefaultNumber(h.ReadTimeout, defaultReadTimeout)
} }
func (h HTTPProxy) String() string { func (h HTTPProxy) String() string {
@@ -113,16 +113,16 @@ func (h HTTPProxy) String() string {
func (h HTTPProxy) toLinesNode() (node *gotree.Node) { func (h HTTPProxy) toLinesNode() (node *gotree.Node) {
node = gotree.New("HTTP proxy settings:") node = gotree.New("HTTP proxy settings:")
node.Appendf("Enabled: %s", helpers.BoolPtrToYesNo(h.Enabled)) node.Appendf("Enabled: %s", gosettings.BoolToYesNo(h.Enabled))
if !*h.Enabled { if !*h.Enabled {
return node return node
} }
node.Appendf("Listening address: %s", h.ListeningAddress) node.Appendf("Listening address: %s", h.ListeningAddress)
node.Appendf("User: %s", *h.User) node.Appendf("User: %s", *h.User)
node.Appendf("Password: %s", helpers.ObfuscatePassword(*h.Password)) node.Appendf("Password: %s", gosettings.ObfuscateKey(*h.Password))
node.Appendf("Stealth mode: %s", helpers.BoolPtrToYesNo(h.Stealth)) node.Appendf("Stealth mode: %s", gosettings.BoolToYesNo(h.Stealth))
node.Appendf("Log: %s", helpers.BoolPtrToYesNo(h.Log)) node.Appendf("Log: %s", gosettings.BoolToYesNo(h.Log))
node.Appendf("Read header timeout: %s", h.ReadHeaderTimeout) node.Appendf("Read header timeout: %s", h.ReadHeaderTimeout)
node.Appendf("Read timeout: %s", h.ReadTimeout) node.Appendf("Read timeout: %s", h.ReadTimeout)

View File

@@ -1,7 +1,7 @@
package settings package settings
import ( import (
"github.com/qdm12/gluetun/internal/configuration/settings/helpers" "github.com/qdm12/gosettings"
"github.com/qdm12/gotree" "github.com/qdm12/gotree"
"github.com/qdm12/log" "github.com/qdm12/log"
) )
@@ -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: gosettings.CopyPointer(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 = gosettings.MergeWithPointer(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 = gosettings.OverrideWithPointer(l.Level, other.Level)
} }
func (l *Log) setDefaults() { func (l *Log) setDefaults() {
l.Level = helpers.DefaultPointer(l.Level, log.LevelInfo) l.Level = gosettings.DefaultPointer(l.Level, log.LevelInfo)
} }
func (l Log) String() string { func (l Log) String() string {

View File

@@ -0,0 +1,42 @@
package settings
// Retro-compatibility because SERVER_REGIONS changed to SERVER_COUNTRIES
// and SERVER_REGIONS is now the continent field for servers.
// TODO v4 remove.
func nordvpnRetroRegion(selection ServerSelection, validRegions, validCountries []string) (
updatedSelection ServerSelection) {
validRegionsMap := stringSliceToMap(validRegions)
validCountriesMap := stringSliceToMap(validCountries)
updatedSelection = selection.copy()
updatedSelection.Regions = make([]string, 0, len(selection.Regions))
for _, region := range selection.Regions {
_, isValid := validRegionsMap[region]
if isValid {
updatedSelection.Regions = append(updatedSelection.Regions, region)
continue
}
_, isValid = validCountriesMap[region]
if !isValid {
// Region is not valid for the country or region
// just leave it to the validation to fail it later
continue
}
// Region is not valid for a region, but is a valid country
// Handle retro-compatibility and transfer the value to the
// country field.
updatedSelection.Countries = append(updatedSelection.Countries, region)
}
return updatedSelection
}
func stringSliceToMap(slice []string) (m map[string]struct{}) {
m = make(map[string]struct{}, len(slice))
for _, s := range slice {
m[s] = struct{}{}
}
return m
}

View File

@@ -4,94 +4,93 @@ import (
"encoding/base64" "encoding/base64"
"fmt" "fmt"
"regexp" "regexp"
"strings"
"github.com/qdm12/gluetun/internal/configuration/settings/helpers"
"github.com/qdm12/gluetun/internal/constants/openvpn" "github.com/qdm12/gluetun/internal/constants/openvpn"
"github.com/qdm12/gluetun/internal/constants/providers" "github.com/qdm12/gluetun/internal/constants/providers"
"github.com/qdm12/gluetun/internal/openvpn/extract" "github.com/qdm12/gluetun/internal/openvpn/extract"
"github.com/qdm12/gluetun/internal/provider/privateinternetaccess/presets" "github.com/qdm12/gluetun/internal/provider/privateinternetaccess/presets"
"github.com/qdm12/gosettings"
"github.com/qdm12/gosettings/validate"
"github.com/qdm12/gotree" "github.com/qdm12/gotree"
) )
// OpenVPN contains settings to configure the OpenVPN client. // OpenVPN contains settings to configure the OpenVPN client.
type OpenVPN struct { type OpenVPN struct {
// Version is the OpenVPN version to run. // Version is the OpenVPN version to run.
// It can only be "2.4" or "2.5". // It can only be "2.5" or "2.6".
Version string Version string `json:"version"`
// 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 nil in the internal state if OpenVPN is used.
// It is usually required but in some cases can be the empty string // It is usually required but in some cases can be the empty string
// to indicate no user+password authentication is needed. // to indicate no user+password authentication is needed.
User *string User *string `json:"user"`
// 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 nil in the internal state if OpenVPN is used.
// It is usually required but in some cases can be the empty string // It is usually required but in some cases can be the empty string
// to indicate no user+password authentication is needed. // to indicate no user+password authentication is needed.
Password *string Password *string `json:"password"`
// 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.
ConfFile *string ConfFile *string `json:"config_file_path"`
// Ciphers is a list of ciphers to use for OpenVPN, // Ciphers is a list of ciphers to use for OpenVPN,
// different from the ones specified by the VPN // different from the ones specified by the VPN
// service provider configuration files. // service provider configuration files.
Ciphers []string Ciphers []string `json:"ciphers"`
// Auth is an auth algorithm to use in OpenVPN instead // Auth is an auth algorithm to use in OpenVPN instead
// of the one specified by the VPN service provider. // of the one specified by the VPN service provider.
// 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 `json:"auth"`
// Cert is the base64 encoded DER of an OpenVPN certificate for the <cert> block. // Cert is the base64 encoded DER of an OpenVPN certificate for the <cert> block.
// This is notably used by Cyberghost and VPN secure. // This is notably used by Cyberghost and VPN secure.
// 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 Cert *string `json:"cert"`
// Key is the base64 encoded DER of an OpenVPN key. // Key is the base64 encoded DER of an OpenVPN 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 Key *string `json:"key"`
// EncryptedKey is the base64 encoded DER of an encrypted key for OpenVPN. // EncryptedKey is the base64 encoded DER of an encrypted key for OpenVPN.
// It is used by VPN secure. // It is used by VPN secure.
// It defaults to the empty string meaning it is not // It defaults to the empty string meaning it is not
// to be used. KeyPassphrase must be set if this one is set. // to be used. KeyPassphrase must be set if this one is set.
EncryptedKey *string EncryptedKey *string `json:"encrypted_key"`
// KeyPassphrase is the key passphrase to be used by OpenVPN // KeyPassphrase is the key passphrase to be used by OpenVPN
// to decrypt the EncryptedPrivateKey. It defaults to the // to decrypt the EncryptedPrivateKey. It defaults to the
// empty string and must be set if EncryptedPrivateKey is set. // empty string and must be set if EncryptedPrivateKey is set.
KeyPassphrase *string KeyPassphrase *string `json:"key_passphrase"`
// 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 `json:"pia_encryption_preset"`
// 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.
MSSFix *uint16 MSSFix *uint16 `json:"mssfix"`
// Interface is the OpenVPN device interface name. // Interface is the OpenVPN device interface name.
// It cannot be an empty string in the internal state. // It cannot be an empty string in the internal state.
Interface string Interface string `json:"interface"`
// ProcessUser is the OpenVPN process OS username // ProcessUser is the OpenVPN process OS username
// to use. It cannot be empty in the internal state. // to use. It cannot be empty in the internal state.
// It defaults to 'root'. // It defaults to 'root'.
ProcessUser string ProcessUser string `json:"process_user"`
// Verbosity is the OpenVPN verbosity level from 0 to 6. // Verbosity is the OpenVPN verbosity level from 0 to 6.
// It cannot be nil in the internal state. // It cannot be nil in the internal state.
Verbosity *int Verbosity *int `json:"verbosity"`
// Flags is a slice of additional flags to be passed // Flags is a slice of additional flags to be passed
// to the OpenVPN program. // to the OpenVPN program.
Flags []string Flags []string `json:"flags"`
} }
var ivpnAccountID = regexp.MustCompile(`^(i|ivpn)\-[a-zA-Z0-9]{4}\-[a-zA-Z0-9]{4}\-[a-zA-Z0-9]{4}$`) 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{openvpn.Openvpn25, openvpn.Openvpn26}
if !helpers.IsOneOf(o.Version, validVersions...) { if err = validate.IsOneOf(o.Version, validVersions...); err != nil {
return fmt.Errorf("%w: %q can only be one of %s", return fmt.Errorf("%w: %w", ErrOpenVPNVersionIsNotValid, err)
ErrOpenVPNVersionIsNotValid, o.Version, strings.Join(validVersions, ", "))
} }
isCustom := vpnProvider == providers.Custom isCustom := vpnProvider == providers.Custom
@@ -163,7 +162,7 @@ func validateOpenVPNConfigFilepath(isCustom bool,
return fmt.Errorf("%w", ErrFilepathMissing) return fmt.Errorf("%w", ErrFilepathMissing)
} }
err = helpers.FileExists(confFile) err = validate.FileExists(confFile)
if err != nil { if err != nil {
return err return err
} }
@@ -244,92 +243,92 @@ 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: gosettings.CopyPointer(o.User),
Password: helpers.CopyPointer(o.Password), Password: gosettings.CopyPointer(o.Password),
ConfFile: helpers.CopyPointer(o.ConfFile), ConfFile: gosettings.CopyPointer(o.ConfFile),
Ciphers: helpers.CopySlice(o.Ciphers), Ciphers: gosettings.CopySlice(o.Ciphers),
Auth: helpers.CopyPointer(o.Auth), Auth: gosettings.CopyPointer(o.Auth),
Cert: helpers.CopyPointer(o.Cert), Cert: gosettings.CopyPointer(o.Cert),
Key: helpers.CopyPointer(o.Key), Key: gosettings.CopyPointer(o.Key),
EncryptedKey: helpers.CopyPointer(o.EncryptedKey), EncryptedKey: gosettings.CopyPointer(o.EncryptedKey),
KeyPassphrase: helpers.CopyPointer(o.KeyPassphrase), KeyPassphrase: gosettings.CopyPointer(o.KeyPassphrase),
PIAEncPreset: helpers.CopyPointer(o.PIAEncPreset), PIAEncPreset: gosettings.CopyPointer(o.PIAEncPreset),
MSSFix: helpers.CopyPointer(o.MSSFix), MSSFix: gosettings.CopyPointer(o.MSSFix),
Interface: o.Interface, Interface: o.Interface,
ProcessUser: o.ProcessUser, ProcessUser: o.ProcessUser,
Verbosity: helpers.CopyPointer(o.Verbosity), Verbosity: gosettings.CopyPointer(o.Verbosity),
Flags: helpers.CopySlice(o.Flags), Flags: gosettings.CopySlice(o.Flags),
} }
} }
// 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 (o *OpenVPN) mergeWith(other OpenVPN) { func (o *OpenVPN) mergeWith(other OpenVPN) {
o.Version = helpers.MergeWithString(o.Version, other.Version) o.Version = gosettings.MergeWithString(o.Version, other.Version)
o.User = helpers.MergeWithPointer(o.User, other.User) o.User = gosettings.MergeWithPointer(o.User, other.User)
o.Password = helpers.MergeWithPointer(o.Password, other.Password) o.Password = gosettings.MergeWithPointer(o.Password, other.Password)
o.ConfFile = helpers.MergeWithPointer(o.ConfFile, other.ConfFile) o.ConfFile = gosettings.MergeWithPointer(o.ConfFile, other.ConfFile)
o.Ciphers = helpers.MergeSlices(o.Ciphers, other.Ciphers) o.Ciphers = gosettings.MergeWithSlice(o.Ciphers, other.Ciphers)
o.Auth = helpers.MergeWithPointer(o.Auth, other.Auth) o.Auth = gosettings.MergeWithPointer(o.Auth, other.Auth)
o.Cert = helpers.MergeWithPointer(o.Cert, other.Cert) o.Cert = gosettings.MergeWithPointer(o.Cert, other.Cert)
o.Key = helpers.MergeWithPointer(o.Key, other.Key) o.Key = gosettings.MergeWithPointer(o.Key, other.Key)
o.EncryptedKey = helpers.MergeWithPointer(o.EncryptedKey, other.EncryptedKey) o.EncryptedKey = gosettings.MergeWithPointer(o.EncryptedKey, other.EncryptedKey)
o.KeyPassphrase = helpers.MergeWithPointer(o.KeyPassphrase, other.KeyPassphrase) o.KeyPassphrase = gosettings.MergeWithPointer(o.KeyPassphrase, other.KeyPassphrase)
o.PIAEncPreset = helpers.MergeWithPointer(o.PIAEncPreset, other.PIAEncPreset) o.PIAEncPreset = gosettings.MergeWithPointer(o.PIAEncPreset, other.PIAEncPreset)
o.MSSFix = helpers.MergeWithPointer(o.MSSFix, other.MSSFix) o.MSSFix = gosettings.MergeWithPointer(o.MSSFix, other.MSSFix)
o.Interface = helpers.MergeWithString(o.Interface, other.Interface) o.Interface = gosettings.MergeWithString(o.Interface, other.Interface)
o.ProcessUser = helpers.MergeWithString(o.ProcessUser, other.ProcessUser) o.ProcessUser = gosettings.MergeWithString(o.ProcessUser, other.ProcessUser)
o.Verbosity = helpers.MergeWithPointer(o.Verbosity, other.Verbosity) o.Verbosity = gosettings.MergeWithPointer(o.Verbosity, other.Verbosity)
o.Flags = helpers.MergeSlices(o.Flags, other.Flags) o.Flags = gosettings.MergeWithSlice(o.Flags, other.Flags)
} }
// 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 (o *OpenVPN) overrideWith(other OpenVPN) { func (o *OpenVPN) overrideWith(other OpenVPN) {
o.Version = helpers.OverrideWithString(o.Version, other.Version) o.Version = gosettings.OverrideWithString(o.Version, other.Version)
o.User = helpers.OverrideWithPointer(o.User, other.User) o.User = gosettings.OverrideWithPointer(o.User, other.User)
o.Password = helpers.OverrideWithPointer(o.Password, other.Password) o.Password = gosettings.OverrideWithPointer(o.Password, other.Password)
o.ConfFile = helpers.OverrideWithPointer(o.ConfFile, other.ConfFile) o.ConfFile = gosettings.OverrideWithPointer(o.ConfFile, other.ConfFile)
o.Ciphers = helpers.OverrideWithSlice(o.Ciphers, other.Ciphers) o.Ciphers = gosettings.OverrideWithSlice(o.Ciphers, other.Ciphers)
o.Auth = helpers.OverrideWithPointer(o.Auth, other.Auth) o.Auth = gosettings.OverrideWithPointer(o.Auth, other.Auth)
o.Cert = helpers.OverrideWithPointer(o.Cert, other.Cert) o.Cert = gosettings.OverrideWithPointer(o.Cert, other.Cert)
o.Key = helpers.OverrideWithPointer(o.Key, other.Key) o.Key = gosettings.OverrideWithPointer(o.Key, other.Key)
o.EncryptedKey = helpers.OverrideWithPointer(o.EncryptedKey, other.EncryptedKey) o.EncryptedKey = gosettings.OverrideWithPointer(o.EncryptedKey, other.EncryptedKey)
o.KeyPassphrase = helpers.OverrideWithPointer(o.KeyPassphrase, other.KeyPassphrase) o.KeyPassphrase = gosettings.OverrideWithPointer(o.KeyPassphrase, other.KeyPassphrase)
o.PIAEncPreset = helpers.OverrideWithPointer(o.PIAEncPreset, other.PIAEncPreset) o.PIAEncPreset = gosettings.OverrideWithPointer(o.PIAEncPreset, other.PIAEncPreset)
o.MSSFix = helpers.OverrideWithPointer(o.MSSFix, other.MSSFix) o.MSSFix = gosettings.OverrideWithPointer(o.MSSFix, other.MSSFix)
o.Interface = helpers.OverrideWithString(o.Interface, other.Interface) o.Interface = gosettings.OverrideWithString(o.Interface, other.Interface)
o.ProcessUser = helpers.OverrideWithString(o.ProcessUser, other.ProcessUser) o.ProcessUser = gosettings.OverrideWithString(o.ProcessUser, other.ProcessUser)
o.Verbosity = helpers.OverrideWithPointer(o.Verbosity, other.Verbosity) o.Verbosity = gosettings.OverrideWithPointer(o.Verbosity, other.Verbosity)
o.Flags = helpers.OverrideWithSlice(o.Flags, other.Flags) o.Flags = gosettings.OverrideWithSlice(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 = gosettings.DefaultString(o.Version, openvpn.Openvpn25)
o.User = helpers.DefaultPointer(o.User, "") o.User = gosettings.DefaultPointer(o.User, "")
if vpnProvider == providers.Mullvad { if vpnProvider == providers.Mullvad {
o.Password = helpers.DefaultPointer(o.Password, "m") o.Password = gosettings.DefaultPointer(o.Password, "m")
} else { } else {
o.Password = helpers.DefaultPointer(o.Password, "") o.Password = gosettings.DefaultPointer(o.Password, "")
} }
o.ConfFile = helpers.DefaultPointer(o.ConfFile, "") o.ConfFile = gosettings.DefaultPointer(o.ConfFile, "")
o.Auth = helpers.DefaultPointer(o.Auth, "") o.Auth = gosettings.DefaultPointer(o.Auth, "")
o.Cert = helpers.DefaultPointer(o.Cert, "") o.Cert = gosettings.DefaultPointer(o.Cert, "")
o.Key = helpers.DefaultPointer(o.Key, "") o.Key = gosettings.DefaultPointer(o.Key, "")
o.EncryptedKey = helpers.DefaultPointer(o.EncryptedKey, "") o.EncryptedKey = gosettings.DefaultPointer(o.EncryptedKey, "")
o.KeyPassphrase = helpers.DefaultPointer(o.KeyPassphrase, "") o.KeyPassphrase = gosettings.DefaultPointer(o.KeyPassphrase, "")
var defaultEncPreset string var defaultEncPreset string
if vpnProvider == providers.PrivateInternetAccess { if vpnProvider == providers.PrivateInternetAccess {
defaultEncPreset = presets.Strong defaultEncPreset = presets.Strong
} }
o.PIAEncPreset = helpers.DefaultPointer(o.PIAEncPreset, defaultEncPreset) o.PIAEncPreset = gosettings.DefaultPointer(o.PIAEncPreset, defaultEncPreset)
o.MSSFix = helpers.DefaultPointer(o.MSSFix, 0) o.MSSFix = gosettings.DefaultPointer(o.MSSFix, 0)
o.Interface = helpers.DefaultString(o.Interface, "tun0") o.Interface = gosettings.DefaultString(o.Interface, "tun0")
o.ProcessUser = helpers.DefaultString(o.ProcessUser, "root") o.ProcessUser = gosettings.DefaultString(o.ProcessUser, "root")
o.Verbosity = helpers.DefaultPointer(o.Verbosity, 1) o.Verbosity = gosettings.DefaultPointer(o.Verbosity, 1)
} }
func (o OpenVPN) String() string { func (o OpenVPN) String() string {
@@ -339,8 +338,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", gosettings.ObfuscateKey(*o.User))
node.Appendf("Password: %s", helpers.ObfuscatePassword(*o.Password)) node.Appendf("Password: %s", gosettings.ObfuscateKey(*o.Password))
if *o.ConfFile != "" { if *o.ConfFile != "" {
node.Appendf("Custom configuration file: %s", *o.ConfFile) node.Appendf("Custom configuration file: %s", *o.ConfFile)
@@ -355,16 +354,16 @@ func (o OpenVPN) toLinesNode() (node *gotree.Node) {
} }
if *o.Cert != "" { if *o.Cert != "" {
node.Appendf("Client crt: %s", helpers.ObfuscateData(*o.Cert)) node.Appendf("Client crt: %s", gosettings.ObfuscateKey(*o.Cert))
} }
if *o.Key != "" { if *o.Key != "" {
node.Appendf("Client key: %s", helpers.ObfuscateData(*o.Key)) node.Appendf("Client key: %s", gosettings.ObfuscateKey(*o.Key))
} }
if *o.EncryptedKey != "" { if *o.EncryptedKey != "" {
node.Appendf("Encrypted key: %s (key passhrapse %s)", node.Appendf("Encrypted key: %s (key passhrapse %s)",
helpers.ObfuscateData(*o.EncryptedKey), helpers.ObfuscatePassword(*o.KeyPassphrase)) gosettings.ObfuscateKey(*o.EncryptedKey), gosettings.ObfuscateKey(*o.KeyPassphrase))
} }
if *o.PIAEncPreset != "" { if *o.PIAEncPreset != "" {

View File

@@ -6,6 +6,8 @@ import (
"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/providers"
"github.com/qdm12/gluetun/internal/provider/privateinternetaccess/presets" "github.com/qdm12/gluetun/internal/provider/privateinternetaccess/presets"
"github.com/qdm12/gosettings"
"github.com/qdm12/gosettings/validate"
"github.com/qdm12/gotree" "github.com/qdm12/gotree"
) )
@@ -14,25 +16,25 @@ type OpenVPNSelection struct {
// It can be set to an empty string to indicate to // It can be set to an empty string to indicate to
// NOT use a custom configuration file. // NOT use a custom configuration file.
// It cannot be nil in the internal state. // It cannot be nil in the internal state.
ConfFile *string ConfFile *string `json:"config_file_path"`
// TCP is true if the OpenVPN protocol is TCP, // TCP is true if the OpenVPN protocol is TCP,
// and false for UDP. // and false for UDP.
// It cannot be nil in the internal state. // It cannot be nil in the internal state.
TCP *bool TCP *bool `json:"tcp"`
// CustomPort is the OpenVPN server endpoint port. // CustomPort is the OpenVPN server endpoint port.
// It can be set to 0 to indicate no custom port should // It can be set to 0 to indicate no custom port should
// be used. It cannot be nil in the internal state. // be used. It cannot be nil in the internal state.
CustomPort *uint16 // HideMyAss, Mullvad, PIA, ProtonVPN, WeVPN, Windscribe CustomPort *uint16 `json:"custom_port"`
// 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 `json:"pia_encryption_preset"`
} }
func (o OpenVPNSelection) validate(vpnProvider string) (err error) { func (o OpenVPNSelection) validate(vpnProvider string) (err error) {
// Validate ConfFile // Validate ConfFile
if confFile := *o.ConfFile; confFile != "" { if confFile := *o.ConfFile; confFile != "" {
err := helpers.FileExists(confFile) err := validate.FileExists(confFile)
if err != nil { if err != nil {
return fmt.Errorf("configuration file: %w", err) return fmt.Errorf("configuration file: %w", err)
} }
@@ -99,14 +101,14 @@ func (o OpenVPNSelection) validate(vpnProvider string) (err error) {
allowedUDP = []uint16{53, 80, 123, 443, 1194, 54783} allowedUDP = []uint16{53, 80, 123, 443, 1194, 54783}
} }
if *o.TCP && !helpers.Uint16IsOneOf(*o.CustomPort, allowedTCP) { allowedPorts := allowedUDP
return fmt.Errorf("%w: %d for VPN service provider %s; %s", if *o.TCP {
ErrOpenVPNCustomPortNotAllowed, o.CustomPort, vpnProvider, allowedPorts = allowedTCP
helpers.PortChoicesOrString(allowedTCP)) }
} else if !*o.TCP && !helpers.Uint16IsOneOf(*o.CustomPort, allowedUDP) { err = validate.IsOneOf(*o.CustomPort, allowedPorts...)
return fmt.Errorf("%w: %d for VPN service provider %s; %s", if err != nil {
ErrOpenVPNCustomPortNotAllowed, o.CustomPort, vpnProvider, return fmt.Errorf("%w: for VPN service provider %s: %w",
helpers.PortChoicesOrString(allowedUDP)) ErrOpenVPNCustomPortNotAllowed, vpnProvider, err)
} }
} }
} }
@@ -118,10 +120,8 @@ func (o OpenVPNSelection) validate(vpnProvider string) (err error) {
presets.Normal, presets.Normal,
presets.Strong, presets.Strong,
} }
if !helpers.IsOneOf(*o.PIAEncPreset, validEncryptionPresets...) { if err = validate.IsOneOf(*o.PIAEncPreset, validEncryptionPresets...); err != nil {
return fmt.Errorf("%w: %s; valid presets are %s", return fmt.Errorf("%w: %w", ErrOpenVPNEncryptionPresetNotValid, err)
ErrOpenVPNEncryptionPresetNotValid, *o.PIAEncPreset,
helpers.ChoicesOrString(validEncryptionPresets))
} }
} }
@@ -130,37 +130,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: gosettings.CopyPointer(o.ConfFile),
TCP: helpers.CopyPointer(o.TCP), TCP: gosettings.CopyPointer(o.TCP),
CustomPort: helpers.CopyPointer(o.CustomPort), CustomPort: gosettings.CopyPointer(o.CustomPort),
PIAEncPreset: helpers.CopyPointer(o.PIAEncPreset), PIAEncPreset: gosettings.CopyPointer(o.PIAEncPreset),
} }
} }
func (o *OpenVPNSelection) mergeWith(other OpenVPNSelection) { func (o *OpenVPNSelection) mergeWith(other OpenVPNSelection) {
o.ConfFile = helpers.MergeWithPointer(o.ConfFile, other.ConfFile) o.ConfFile = gosettings.MergeWithPointer(o.ConfFile, other.ConfFile)
o.TCP = helpers.MergeWithPointer(o.TCP, other.TCP) o.TCP = gosettings.MergeWithPointer(o.TCP, other.TCP)
o.CustomPort = helpers.MergeWithPointer(o.CustomPort, other.CustomPort) o.CustomPort = gosettings.MergeWithPointer(o.CustomPort, other.CustomPort)
o.PIAEncPreset = helpers.MergeWithPointer(o.PIAEncPreset, other.PIAEncPreset) o.PIAEncPreset = gosettings.MergeWithPointer(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 = gosettings.OverrideWithPointer(o.ConfFile, other.ConfFile)
o.TCP = helpers.OverrideWithPointer(o.TCP, other.TCP) o.TCP = gosettings.OverrideWithPointer(o.TCP, other.TCP)
o.CustomPort = helpers.OverrideWithPointer(o.CustomPort, other.CustomPort) o.CustomPort = gosettings.OverrideWithPointer(o.CustomPort, other.CustomPort)
o.PIAEncPreset = helpers.OverrideWithPointer(o.PIAEncPreset, other.PIAEncPreset) o.PIAEncPreset = gosettings.OverrideWithPointer(o.PIAEncPreset, other.PIAEncPreset)
} }
func (o *OpenVPNSelection) setDefaults(vpnProvider string) { func (o *OpenVPNSelection) setDefaults(vpnProvider string) {
o.ConfFile = helpers.DefaultPointer(o.ConfFile, "") o.ConfFile = gosettings.DefaultPointer(o.ConfFile, "")
o.TCP = helpers.DefaultPointer(o.TCP, false) o.TCP = gosettings.DefaultPointer(o.TCP, false)
o.CustomPort = helpers.DefaultPointer(o.CustomPort, 0) o.CustomPort = gosettings.DefaultPointer(o.CustomPort, 0)
var defaultEncPreset string var defaultEncPreset string
if vpnProvider == providers.PrivateInternetAccess { if vpnProvider == providers.PrivateInternetAccess {
defaultEncPreset = presets.Strong defaultEncPreset = presets.Strong
} }
o.PIAEncPreset = helpers.DefaultPointer(o.PIAEncPreset, defaultEncPreset) o.PIAEncPreset = gosettings.DefaultPointer(o.PIAEncPreset, defaultEncPreset)
} }
func (o OpenVPNSelection) String() string { func (o OpenVPNSelection) String() string {

View File

@@ -3,10 +3,10 @@ package settings
import ( import (
"fmt" "fmt"
"path/filepath" "path/filepath"
"strings"
"github.com/qdm12/gluetun/internal/configuration/settings/helpers"
"github.com/qdm12/gluetun/internal/constants/providers" "github.com/qdm12/gluetun/internal/constants/providers"
"github.com/qdm12/gosettings"
"github.com/qdm12/gosettings/validate"
"github.com/qdm12/gotree" "github.com/qdm12/gotree"
) )
@@ -14,12 +14,12 @@ import (
type PortForwarding struct { type PortForwarding struct {
// Enabled is true if port forwarding should be activated. // Enabled is true if port forwarding should be activated.
// It cannot be nil for the internal state. // It cannot be nil for the internal state.
Enabled *bool Enabled *bool `json:"enabled"`
// Filepath is the port forwarding status file path // Filepath is the port forwarding status file path
// to use. It can be the empty string to indicate not // to use. It can be the empty string to indicate not
// to write to a file. It cannot be nil for the // to write to a file. It cannot be nil for the
// internal state // internal state
Filepath *string Filepath *string `json:"status_file_path"`
} }
func (p PortForwarding) validate(vpnProvider string) (err error) { func (p PortForwarding) validate(vpnProvider string) (err error) {
@@ -29,9 +29,8 @@ func (p PortForwarding) validate(vpnProvider string) (err error) {
// Validate Enabled // Validate Enabled
validProviders := []string{providers.PrivateInternetAccess} validProviders := []string{providers.PrivateInternetAccess}
if !helpers.IsOneOf(vpnProvider, validProviders...) { if err = validate.IsOneOf(vpnProvider, validProviders...); err != nil {
return fmt.Errorf("%w: for provider %s, it is only available for %s", return fmt.Errorf("%w: %w", ErrPortForwardingEnabled, err)
ErrPortForwardingEnabled, vpnProvider, strings.Join(validProviders, ", "))
} }
// Validate Filepath // Validate Filepath
@@ -47,24 +46,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: gosettings.CopyPointer(p.Enabled),
Filepath: helpers.CopyPointer(p.Filepath), Filepath: gosettings.CopyPointer(p.Filepath),
} }
} }
func (p *PortForwarding) mergeWith(other PortForwarding) { func (p *PortForwarding) mergeWith(other PortForwarding) {
p.Enabled = helpers.MergeWithPointer(p.Enabled, other.Enabled) p.Enabled = gosettings.MergeWithPointer(p.Enabled, other.Enabled)
p.Filepath = helpers.MergeWithPointer(p.Filepath, other.Filepath) p.Filepath = gosettings.MergeWithPointer(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 = gosettings.OverrideWithPointer(p.Enabled, other.Enabled)
p.Filepath = helpers.OverrideWithPointer(p.Filepath, other.Filepath) p.Filepath = gosettings.OverrideWithPointer(p.Filepath, other.Filepath)
} }
func (p *PortForwarding) setDefaults() { func (p *PortForwarding) setDefaults() {
p.Enabled = helpers.DefaultPointer(p.Enabled, false) p.Enabled = gosettings.DefaultPointer(p.Enabled, false)
p.Filepath = helpers.DefaultPointer(p.Filepath, "/tmp/gluetun/forwarded_port") p.Filepath = gosettings.DefaultPointer(p.Filepath, "/tmp/gluetun/forwarded_port")
} }
func (p PortForwarding) String() string { func (p PortForwarding) String() string {

View File

@@ -3,9 +3,10 @@ package settings
import ( import (
"fmt" "fmt"
"github.com/qdm12/gluetun/internal/configuration/settings/helpers"
"github.com/qdm12/gluetun/internal/constants/providers" "github.com/qdm12/gluetun/internal/constants/providers"
"github.com/qdm12/gluetun/internal/constants/vpn" "github.com/qdm12/gluetun/internal/constants/vpn"
"github.com/qdm12/gosettings"
"github.com/qdm12/gosettings/validate"
"github.com/qdm12/gotree" "github.com/qdm12/gotree"
) )
@@ -13,12 +14,12 @@ import (
type Provider struct { type Provider struct {
// Name is the VPN service provider name. // Name is the VPN service provider name.
// It cannot be nil in the internal state. // It cannot be nil in the internal state.
Name *string Name *string `json:"name"`
// ServerSelection is the settings to // ServerSelection is the settings to
// select the VPN server. // select the VPN server.
ServerSelection ServerSelection ServerSelection ServerSelection `json:"server_selection"`
// PortForwarding is the settings about port forwarding. // PortForwarding is the settings about port forwarding.
PortForwarding PortForwarding PortForwarding PortForwarding `json:"port_forwarding"`
} }
// TODO v4 remove pointer for receiver (because of Surfshark). // TODO v4 remove pointer for receiver (because of Surfshark).
@@ -34,13 +35,13 @@ func (p *Provider) validate(vpnType string, storage Storage) (err error) {
providers.Custom, providers.Custom,
providers.Ivpn, providers.Ivpn,
providers.Mullvad, providers.Mullvad,
providers.Nordvpn,
providers.Surfshark, providers.Surfshark,
providers.Windscribe, providers.Windscribe,
} }
} }
if !helpers.IsOneOf(*p.Name, validNames...) { if err = validate.IsOneOf(*p.Name, validNames...); err != nil {
return fmt.Errorf("%w for Wireguard: %q can only be one of %s", return fmt.Errorf("%w for Wireguard: %w", ErrVPNProviderNameNotValid, err)
ErrVPNProviderNameNotValid, *p.Name, helpers.ChoicesOrString(validNames))
} }
err = p.ServerSelection.validate(*p.Name, storage) err = p.ServerSelection.validate(*p.Name, storage)
@@ -58,26 +59,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: gosettings.CopyPointer(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 = gosettings.MergeWithPointer(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 = gosettings.OverrideWithPointer(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 = gosettings.DefaultPointer(p.Name, providers.PrivateInternetAccess)
p.ServerSelection.setDefaults(*p.Name) p.ServerSelection.setDefaults(*p.Name)
p.PortForwarding.setDefaults() p.PortForwarding.setDefaults()
} }

View File

@@ -5,7 +5,7 @@ import (
"path/filepath" "path/filepath"
"time" "time"
"github.com/qdm12/gluetun/internal/configuration/settings/helpers" "github.com/qdm12/gosettings"
"github.com/qdm12/gotree" "github.com/qdm12/gotree"
) )
@@ -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: gosettings.CopyPointer(p.Period),
IPFilepath: helpers.CopyPointer(p.IPFilepath), IPFilepath: gosettings.CopyPointer(p.IPFilepath),
} }
} }
func (p *PublicIP) mergeWith(other PublicIP) { func (p *PublicIP) mergeWith(other PublicIP) {
p.Period = helpers.MergeWithPointer(p.Period, other.Period) p.Period = gosettings.MergeWithPointer(p.Period, other.Period)
p.IPFilepath = helpers.MergeWithPointer(p.IPFilepath, other.IPFilepath) p.IPFilepath = gosettings.MergeWithPointer(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 = gosettings.OverrideWithPointer(p.Period, other.Period)
p.IPFilepath = helpers.OverrideWithPointer(p.IPFilepath, other.IPFilepath) p.IPFilepath = gosettings.OverrideWithPointer(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 = gosettings.DefaultPointer(p.Period, defaultPeriod)
p.IPFilepath = helpers.DefaultPointer(p.IPFilepath, "/tmp/gluetun/ip") p.IPFilepath = gosettings.DefaultPointer(p.IPFilepath, "/tmp/gluetun/ip")
} }
func (p PublicIP) String() string { func (p PublicIP) String() string {

View File

@@ -6,7 +6,7 @@ import (
"os" "os"
"strconv" "strconv"
"github.com/qdm12/gluetun/internal/configuration/settings/helpers" "github.com/qdm12/gosettings"
"github.com/qdm12/gotree" "github.com/qdm12/gotree"
) )
@@ -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: gosettings.CopyPointer(c.Address),
Log: helpers.CopyPointer(c.Log), Log: gosettings.CopyPointer(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 = gosettings.MergeWithPointer(c.Address, other.Address)
c.Log = helpers.MergeWithPointer(c.Log, other.Log) c.Log = gosettings.MergeWithPointer(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 = gosettings.OverrideWithPointer(c.Address, other.Address)
c.Log = helpers.OverrideWithPointer(c.Log, other.Log) c.Log = gosettings.OverrideWithPointer(c.Log, other.Log)
} }
func (c *ControlServer) setDefaults() { func (c *ControlServer) setDefaults() {
c.Address = helpers.DefaultPointer(c.Address, ":8000") c.Address = gosettings.DefaultPointer(c.Address, ":8000")
c.Log = helpers.DefaultPointer(c.Log, true) c.Log = gosettings.DefaultPointer(c.Log, true)
} }
func (c ControlServer) String() string { func (c ControlServer) String() string {
@@ -75,6 +75,6 @@ func (c ControlServer) String() string {
func (c ControlServer) toLinesNode() (node *gotree.Node) { func (c ControlServer) toLinesNode() (node *gotree.Node) {
node = gotree.New("Control server settings:") node = gotree.New("Control server settings:")
node.Appendf("Listening address: %s", *c.Address) node.Appendf("Listening address: %s", *c.Address)
node.Appendf("Logging: %s", helpers.BoolPtrToYesNo(c.Log)) node.Appendf("Logging: %s", gosettings.BoolToYesNo(c.Log))
return node return node
} }

View File

@@ -11,6 +11,8 @@ import (
"github.com/qdm12/gluetun/internal/constants/providers" "github.com/qdm12/gluetun/internal/constants/providers"
"github.com/qdm12/gluetun/internal/constants/vpn" "github.com/qdm12/gluetun/internal/constants/vpn"
"github.com/qdm12/gluetun/internal/models" "github.com/qdm12/gluetun/internal/models"
"github.com/qdm12/gosettings"
"github.com/qdm12/gosettings/validate"
"github.com/qdm12/gotree" "github.com/qdm12/gotree"
) )
@@ -18,50 +20,50 @@ type ServerSelection struct { //nolint:maligned
// VPN is the VPN type which can be 'openvpn' // VPN is the VPN type which can be 'openvpn'
// or 'wireguard'. It cannot be the empty string // or 'wireguard'. It cannot be the empty string
// in the internal state. // in the internal state.
VPN string VPN string `json:"vpn"`
// 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 the empty value in the internal
// state, and can be set to the unspecified address to indicate // state, and can be set to the unspecified address to indicate
// there is not target IP address to use. // there is not target IP address to use.
TargetIP netip.Addr TargetIP netip.Addr `json:"target_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 `json:"countries"`
// Regions is the list of regions to filter VPN servers with. // Regions is the list of regions to filter VPN servers with.
Regions []string Regions []string `json:"regions"`
// Cities is the list of cities to filter VPN servers with. // Cities is the list of cities to filter VPN servers with.
Cities []string Cities []string `json:"cities"`
// ISPs is the list of ISP names to filter VPN servers with. // ISPs is the list of ISP names to filter VPN servers with.
ISPs []string ISPs []string `json:"isps"`
// Names is the list of server names to filter VPN servers with. // Names is the list of server names to filter VPN servers with.
Names []string Names []string `json:"names"`
// Numbers is the list of server numbers to filter VPN servers with. // Numbers is the list of server numbers to filter VPN servers with.
Numbers []uint16 Numbers []uint16 `json:"numbers"`
// Hostnames is the list of hostnames to filter VPN servers with. // Hostnames is the list of hostnames to filter VPN servers with.
Hostnames []string Hostnames []string `json:"hostnames"`
// OwnedOnly is true if VPN provider servers that are not owned // OwnedOnly is true if VPN provider servers that are not owned
// should be filtered. This is used with Mullvad. // should be filtered. This is used with Mullvad.
OwnedOnly *bool OwnedOnly *bool `json:"owned_only"`
// 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 `json:"free_only"`
// PremiumOnly is true if VPN servers that are not premium should // PremiumOnly is true if VPN servers that are not premium should
// be filtered. This is used with VPN Secure. // be filtered. This is used with VPN Secure.
// TODO extend to providers using FreeOnly. // TODO extend to providers using FreeOnly.
PremiumOnly *bool PremiumOnly *bool `json:"premium_only"`
// 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 `json:"stream_only"`
// MultiHopOnly is true if VPN servers that are not multihop // MultiHopOnly is true if VPN servers that are not multihop
// should be filtered. This is used with Surfshark. // should be filtered. This is used with Surfshark.
MultiHopOnly *bool MultiHopOnly *bool `json:"multi_hop_only"`
// OpenVPN contains settings to select OpenVPN servers // OpenVPN contains settings to select OpenVPN servers
// and the final connection. // and the final connection.
OpenVPN OpenVPNSelection OpenVPN OpenVPNSelection `json:"openvpn"`
// Wireguard contains settings to select Wireguard servers // Wireguard contains settings to select Wireguard servers
// and the final connection. // and the final connection.
Wireguard WireguardSelection Wireguard WireguardSelection `json:"wireguard"`
} }
var ( var (
@@ -86,12 +88,17 @@ func (ss *ServerSelection) validate(vpnServiceProvider string,
return err // already wrapped error return err // already wrapped error
} }
// Retro-compatibility
switch vpnServiceProvider {
case providers.Nordvpn:
*ss = nordvpnRetroRegion(*ss, filterChoices.Regions, filterChoices.Countries)
case providers.Surfshark:
*ss = surfsharkRetroRegion(*ss)
}
err = validateServerFilters(*ss, filterChoices) err = validateServerFilters(*ss, filterChoices)
if err != nil { if err != nil {
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)
}
return err // already wrapped error
} }
if *ss.OwnedOnly && if *ss.OwnedOnly &&
@@ -160,10 +167,10 @@ func getLocationFilterChoices(vpnServiceProvider string,
// // Retro compatibility // // Retro compatibility
// TODO v4 remove // TODO v4 remove
filterChoices.Regions = append(filterChoices.Regions, validation.SurfsharkRetroLocChoices()...) filterChoices.Regions = append(filterChoices.Regions, validation.SurfsharkRetroLocChoices()...)
if err := helpers.AreAllOneOf(ss.Regions, filterChoices.Regions); err != nil { err := validate.AreAllOneOfCaseInsensitive(ss.Regions, filterChoices.Regions)
return models.FilterChoices{}, fmt.Errorf("%w: %s", ErrRegionNotValid, err) if err != nil {
return models.FilterChoices{}, fmt.Errorf("%w: %w", ErrRegionNotValid, err)
} }
*ss = surfsharkRetroRegion(*ss)
} }
return filterChoices, nil return filterChoices, nil
@@ -172,28 +179,34 @@ func getLocationFilterChoices(vpnServiceProvider string,
// 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, filterChoices models.FilterChoices) (err error) {
if err := helpers.AreAllOneOf(settings.Countries, filterChoices.Countries); err != nil { err = validate.AreAllOneOfCaseInsensitive(settings.Countries, filterChoices.Countries)
return fmt.Errorf("%w: %s", ErrCountryNotValid, err) if err != nil {
return fmt.Errorf("%w: %w", ErrCountryNotValid, err)
} }
if err := helpers.AreAllOneOf(settings.Regions, filterChoices.Regions); err != nil { err = validate.AreAllOneOfCaseInsensitive(settings.Regions, filterChoices.Regions)
return fmt.Errorf("%w: %s", ErrRegionNotValid, err) if err != nil {
return fmt.Errorf("%w: %w", ErrRegionNotValid, err)
} }
if err := helpers.AreAllOneOf(settings.Cities, filterChoices.Cities); err != nil { err = validate.AreAllOneOfCaseInsensitive(settings.Cities, filterChoices.Cities)
return fmt.Errorf("%w: %s", ErrCityNotValid, err) if err != nil {
return fmt.Errorf("%w: %w", ErrCityNotValid, err)
} }
if err := helpers.AreAllOneOf(settings.ISPs, filterChoices.ISPs); err != nil { err = validate.AreAllOneOfCaseInsensitive(settings.ISPs, filterChoices.ISPs)
return fmt.Errorf("%w: %s", ErrISPNotValid, err) if err != nil {
return fmt.Errorf("%w: %w", ErrISPNotValid, err)
} }
if err := helpers.AreAllOneOf(settings.Hostnames, filterChoices.Hostnames); err != nil { err = validate.AreAllOneOfCaseInsensitive(settings.Hostnames, filterChoices.Hostnames)
return fmt.Errorf("%w: %s", ErrHostnameNotValid, err) if err != nil {
return fmt.Errorf("%w: %w", ErrHostnameNotValid, err)
} }
if err := helpers.AreAllOneOf(settings.Names, filterChoices.Names); err != nil { err = validate.AreAllOneOfCaseInsensitive(settings.Names, filterChoices.Names)
return fmt.Errorf("%w: %s", ErrNameNotValid, err) if err != nil {
return fmt.Errorf("%w: %w", ErrNameNotValid, err)
} }
return nil return nil
@@ -203,70 +216,70 @@ func (ss *ServerSelection) copy() (copied ServerSelection) {
return ServerSelection{ return ServerSelection{
VPN: ss.VPN, VPN: ss.VPN,
TargetIP: ss.TargetIP, TargetIP: ss.TargetIP,
Countries: helpers.CopySlice(ss.Countries), Countries: gosettings.CopySlice(ss.Countries),
Regions: helpers.CopySlice(ss.Regions), Regions: gosettings.CopySlice(ss.Regions),
Cities: helpers.CopySlice(ss.Cities), Cities: gosettings.CopySlice(ss.Cities),
ISPs: helpers.CopySlice(ss.ISPs), ISPs: gosettings.CopySlice(ss.ISPs),
Hostnames: helpers.CopySlice(ss.Hostnames), Hostnames: gosettings.CopySlice(ss.Hostnames),
Names: helpers.CopySlice(ss.Names), Names: gosettings.CopySlice(ss.Names),
Numbers: helpers.CopySlice(ss.Numbers), Numbers: gosettings.CopySlice(ss.Numbers),
OwnedOnly: helpers.CopyPointer(ss.OwnedOnly), OwnedOnly: gosettings.CopyPointer(ss.OwnedOnly),
FreeOnly: helpers.CopyPointer(ss.FreeOnly), FreeOnly: gosettings.CopyPointer(ss.FreeOnly),
PremiumOnly: helpers.CopyPointer(ss.PremiumOnly), PremiumOnly: gosettings.CopyPointer(ss.PremiumOnly),
StreamOnly: helpers.CopyPointer(ss.StreamOnly), StreamOnly: gosettings.CopyPointer(ss.StreamOnly),
MultiHopOnly: helpers.CopyPointer(ss.MultiHopOnly), MultiHopOnly: gosettings.CopyPointer(ss.MultiHopOnly),
OpenVPN: ss.OpenVPN.copy(), OpenVPN: ss.OpenVPN.copy(),
Wireguard: ss.Wireguard.copy(), Wireguard: ss.Wireguard.copy(),
} }
} }
func (ss *ServerSelection) mergeWith(other ServerSelection) { func (ss *ServerSelection) mergeWith(other ServerSelection) {
ss.VPN = helpers.MergeWithString(ss.VPN, other.VPN) ss.VPN = gosettings.MergeWithString(ss.VPN, other.VPN)
ss.TargetIP = helpers.MergeWithIP(ss.TargetIP, other.TargetIP) ss.TargetIP = gosettings.MergeWithValidator(ss.TargetIP, other.TargetIP)
ss.Countries = helpers.MergeSlices(ss.Countries, other.Countries) ss.Countries = gosettings.MergeWithSlice(ss.Countries, other.Countries)
ss.Regions = helpers.MergeSlices(ss.Regions, other.Regions) ss.Regions = gosettings.MergeWithSlice(ss.Regions, other.Regions)
ss.Cities = helpers.MergeSlices(ss.Cities, other.Cities) ss.Cities = gosettings.MergeWithSlice(ss.Cities, other.Cities)
ss.ISPs = helpers.MergeSlices(ss.ISPs, other.ISPs) ss.ISPs = gosettings.MergeWithSlice(ss.ISPs, other.ISPs)
ss.Hostnames = helpers.MergeSlices(ss.Hostnames, other.Hostnames) ss.Hostnames = gosettings.MergeWithSlice(ss.Hostnames, other.Hostnames)
ss.Names = helpers.MergeSlices(ss.Names, other.Names) ss.Names = gosettings.MergeWithSlice(ss.Names, other.Names)
ss.Numbers = helpers.MergeSlices(ss.Numbers, other.Numbers) ss.Numbers = gosettings.MergeWithSlice(ss.Numbers, other.Numbers)
ss.OwnedOnly = helpers.MergeWithPointer(ss.OwnedOnly, other.OwnedOnly) ss.OwnedOnly = gosettings.MergeWithPointer(ss.OwnedOnly, other.OwnedOnly)
ss.FreeOnly = helpers.MergeWithPointer(ss.FreeOnly, other.FreeOnly) ss.FreeOnly = gosettings.MergeWithPointer(ss.FreeOnly, other.FreeOnly)
ss.PremiumOnly = helpers.MergeWithPointer(ss.PremiumOnly, other.PremiumOnly) ss.PremiumOnly = gosettings.MergeWithPointer(ss.PremiumOnly, other.PremiumOnly)
ss.StreamOnly = helpers.MergeWithPointer(ss.StreamOnly, other.StreamOnly) ss.StreamOnly = gosettings.MergeWithPointer(ss.StreamOnly, other.StreamOnly)
ss.MultiHopOnly = helpers.MergeWithPointer(ss.MultiHopOnly, other.MultiHopOnly) ss.MultiHopOnly = gosettings.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)
} }
func (ss *ServerSelection) overrideWith(other ServerSelection) { func (ss *ServerSelection) overrideWith(other ServerSelection) {
ss.VPN = helpers.OverrideWithString(ss.VPN, other.VPN) ss.VPN = gosettings.OverrideWithString(ss.VPN, other.VPN)
ss.TargetIP = helpers.OverrideWithIP(ss.TargetIP, other.TargetIP) ss.TargetIP = gosettings.OverrideWithValidator(ss.TargetIP, other.TargetIP)
ss.Countries = helpers.OverrideWithSlice(ss.Countries, other.Countries) ss.Countries = gosettings.OverrideWithSlice(ss.Countries, other.Countries)
ss.Regions = helpers.OverrideWithSlice(ss.Regions, other.Regions) ss.Regions = gosettings.OverrideWithSlice(ss.Regions, other.Regions)
ss.Cities = helpers.OverrideWithSlice(ss.Cities, other.Cities) ss.Cities = gosettings.OverrideWithSlice(ss.Cities, other.Cities)
ss.ISPs = helpers.OverrideWithSlice(ss.ISPs, other.ISPs) ss.ISPs = gosettings.OverrideWithSlice(ss.ISPs, other.ISPs)
ss.Hostnames = helpers.OverrideWithSlice(ss.Hostnames, other.Hostnames) ss.Hostnames = gosettings.OverrideWithSlice(ss.Hostnames, other.Hostnames)
ss.Names = helpers.OverrideWithSlice(ss.Names, other.Names) ss.Names = gosettings.OverrideWithSlice(ss.Names, other.Names)
ss.Numbers = helpers.OverrideWithSlice(ss.Numbers, other.Numbers) ss.Numbers = gosettings.OverrideWithSlice(ss.Numbers, other.Numbers)
ss.OwnedOnly = helpers.OverrideWithPointer(ss.OwnedOnly, other.OwnedOnly) ss.OwnedOnly = gosettings.OverrideWithPointer(ss.OwnedOnly, other.OwnedOnly)
ss.FreeOnly = helpers.OverrideWithPointer(ss.FreeOnly, other.FreeOnly) ss.FreeOnly = gosettings.OverrideWithPointer(ss.FreeOnly, other.FreeOnly)
ss.PremiumOnly = helpers.OverrideWithPointer(ss.PremiumOnly, other.PremiumOnly) ss.PremiumOnly = gosettings.OverrideWithPointer(ss.PremiumOnly, other.PremiumOnly)
ss.StreamOnly = helpers.OverrideWithPointer(ss.StreamOnly, other.StreamOnly) ss.StreamOnly = gosettings.OverrideWithPointer(ss.StreamOnly, other.StreamOnly)
ss.MultiHopOnly = helpers.OverrideWithPointer(ss.MultiHopOnly, other.MultiHopOnly) ss.MultiHopOnly = gosettings.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 = gosettings.DefaultString(ss.VPN, vpn.OpenVPN)
ss.TargetIP = helpers.DefaultIP(ss.TargetIP, netip.IPv4Unspecified()) ss.TargetIP = gosettings.DefaultValidator(ss.TargetIP, netip.IPv4Unspecified())
ss.OwnedOnly = helpers.DefaultPointer(ss.OwnedOnly, false) ss.OwnedOnly = gosettings.DefaultPointer(ss.OwnedOnly, false)
ss.FreeOnly = helpers.DefaultPointer(ss.FreeOnly, false) ss.FreeOnly = gosettings.DefaultPointer(ss.FreeOnly, false)
ss.PremiumOnly = helpers.DefaultPointer(ss.PremiumOnly, false) ss.PremiumOnly = gosettings.DefaultPointer(ss.PremiumOnly, false)
ss.StreamOnly = helpers.DefaultPointer(ss.StreamOnly, false) ss.StreamOnly = gosettings.DefaultPointer(ss.StreamOnly, false)
ss.MultiHopOnly = helpers.DefaultPointer(ss.MultiHopOnly, false) ss.MultiHopOnly = gosettings.DefaultPointer(ss.MultiHopOnly, false)
ss.OpenVPN.setDefaults(vpnProvider) ss.OpenVPN.setDefaults(vpnProvider)
ss.Wireguard.setDefaults() ss.Wireguard.setDefaults()
} }

View File

@@ -4,7 +4,6 @@ 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/openvpn"
"github.com/qdm12/gluetun/internal/constants/providers" "github.com/qdm12/gluetun/internal/constants/providers"
"github.com/qdm12/gluetun/internal/constants/vpn" "github.com/qdm12/gluetun/internal/constants/vpn"
"github.com/qdm12/gluetun/internal/models" "github.com/qdm12/gluetun/internal/models"
@@ -170,27 +169,14 @@ func (s Settings) Warnings() (warnings []string) {
if helpers.IsOneOf(*s.VPN.Provider.Name, providers.SlickVPN) && if helpers.IsOneOf(*s.VPN.Provider.Name, providers.SlickVPN) &&
s.VPN.Type == vpn.OpenVPN { s.VPN.Type == vpn.OpenVPN {
if s.VPN.OpenVPN.Version == openvpn.Openvpn24 { warnings = append(warnings, "OpenVPN 2.5 uses OpenSSL 3 "+
warnings = append(warnings, "OpenVPN 2.4 uses OpenSSL 1.1.1 "+ "which prohibits the usage of weak security in today's standards. "+
"which allows the usage of weak security in today's standards. "+ *s.VPN.Provider.Name+" uses weak security which is out "+
"This can be ok if good security is enforced by the VPN provider. "+ "of Gluetun's control so the only workaround is to allow such weaknesses "+
"However, "+*s.VPN.Provider.Name+" uses weak security so you should use "+ `using the OpenVPN option tls-cipher "DEFAULT:@SECLEVEL=0". `+
"OpenVPN 2.5 to enforce good security practices.") "You might want to reach to your provider so they upgrade their certificates. "+
} else { "Once this is done, you will have to let the Gluetun maintainers know "+
warnings = append(warnings, "OpenVPN 2.5 uses OpenSSL 3 "+ "by creating an issue, attaching the new certificate and we will update Gluetun.")
"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 return warnings

View File

@@ -1,7 +1,7 @@
package settings package settings
import ( import (
"github.com/qdm12/gluetun/internal/configuration/settings/helpers" "github.com/qdm12/gosettings"
"github.com/qdm12/gotree" "github.com/qdm12/gotree"
"github.com/qdm12/ss-server/pkg/tcpudp" "github.com/qdm12/ss-server/pkg/tcpudp"
) )
@@ -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: gosettings.CopyPointer(s.Enabled),
Settings: s.Settings.Copy(), Settings: s.Settings.Copy(),
} }
} }
@@ -29,20 +29,20 @@ 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 = gosettings.MergeWithPointer(s.Enabled, other.Enabled)
s.Settings.MergeWith(other.Settings) s.Settings = s.Settings.MergeWith(other.Settings)
} }
// 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 (s *Shadowsocks) overrideWith(other Shadowsocks) { func (s *Shadowsocks) overrideWith(other Shadowsocks) {
s.Enabled = helpers.OverrideWithPointer(s.Enabled, other.Enabled) s.Enabled = gosettings.OverrideWithPointer(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 = gosettings.DefaultPointer(s.Enabled, false)
s.Settings.SetDefaults() s.Settings.SetDefaults()
} }
@@ -53,16 +53,16 @@ func (s Shadowsocks) String() string {
func (s Shadowsocks) toLinesNode() (node *gotree.Node) { func (s Shadowsocks) toLinesNode() (node *gotree.Node) {
node = gotree.New("Shadowsocks server settings:") node = gotree.New("Shadowsocks server settings:")
node.Appendf("Enabled: %s", helpers.BoolPtrToYesNo(s.Enabled)) node.Appendf("Enabled: %s", gosettings.BoolToYesNo(s.Enabled))
if !*s.Enabled { if !*s.Enabled {
return node return node
} }
// TODO have ToLinesNode in qdm12/ss-server // TODO have ToLinesNode in qdm12/ss-server
node.Appendf("Listening address: %s", s.Address) node.Appendf("Listening address: %s", *s.Address)
node.Appendf("Cipher: %s", s.CipherName) node.Appendf("Cipher: %s", s.CipherName)
node.Appendf("Password: %s", helpers.ObfuscatePassword(*s.Password)) node.Appendf("Password: %s", gosettings.ObfuscateKey(*s.Password))
node.Appendf("Log addresses: %s", helpers.BoolPtrToYesNo(s.LogAddresses)) node.Appendf("Log addresses: %s", gosettings.BoolToYesNo(s.LogAddresses))
return node return node
} }

View File

@@ -1,7 +1,7 @@
package settings package settings
import ( import (
"github.com/qdm12/gluetun/internal/configuration/settings/helpers" "github.com/qdm12/gosettings"
"github.com/qdm12/gotree" "github.com/qdm12/gotree"
) )
@@ -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: gosettings.CopyPointer(s.PUID),
PGID: helpers.CopyPointer(s.PGID), PGID: gosettings.CopyPointer(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 = gosettings.MergeWithPointer(s.PUID, other.PUID)
s.PGID = helpers.MergeWithPointer(s.PGID, other.PGID) s.PGID = gosettings.MergeWithPointer(s.PGID, other.PGID)
s.Timezone = helpers.MergeWithString(s.Timezone, other.Timezone) s.Timezone = gosettings.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 = gosettings.OverrideWithPointer(s.PUID, other.PUID)
s.PGID = helpers.OverrideWithPointer(s.PGID, other.PGID) s.PGID = gosettings.OverrideWithPointer(s.PGID, other.PGID)
s.Timezone = helpers.OverrideWithString(s.Timezone, other.Timezone) s.Timezone = gosettings.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 = gosettings.DefaultPointer(s.PUID, defaultID)
s.PGID = helpers.DefaultPointer(s.PGID, defaultID) s.PGID = gosettings.DefaultPointer(s.PGID, defaultID)
} }
func (s System) String() string { func (s System) String() string {

View File

@@ -7,20 +7,20 @@ import (
"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/gosettings"
"github.com/qdm12/gotree" "github.com/qdm12/gotree"
) )
// Unbound is settings for the Unbound program. // Unbound is settings for the Unbound program.
type Unbound struct { type Unbound struct {
Providers []string Providers []string `json:"providers"`
Caching *bool Caching *bool `json:"caching"`
IPv6 *bool IPv6 *bool `json:"ipv6"`
VerbosityLevel *uint8 VerbosityLevel *uint8 `json:"verbosity_level"`
VerbosityDetailsLevel *uint8 VerbosityDetailsLevel *uint8 `json:"verbosity_details_level"`
ValidationLogLevel *uint8 ValidationLogLevel *uint8 `json:"validation_log_level"`
Username string Username string `json:"username"`
Allowed []netip.Prefix Allowed []netip.Prefix `json:"allowed"`
} }
func (u *Unbound) setDefaults() { func (u *Unbound) setDefaults() {
@@ -30,17 +30,17 @@ func (u *Unbound) setDefaults() {
} }
} }
u.Caching = helpers.DefaultPointer(u.Caching, true) u.Caching = gosettings.DefaultPointer(u.Caching, true)
u.IPv6 = helpers.DefaultPointer(u.IPv6, false) u.IPv6 = gosettings.DefaultPointer(u.IPv6, false)
const defaultVerbosityLevel = 1 const defaultVerbosityLevel = 1
u.VerbosityLevel = helpers.DefaultPointer(u.VerbosityLevel, defaultVerbosityLevel) u.VerbosityLevel = gosettings.DefaultPointer(u.VerbosityLevel, defaultVerbosityLevel)
const defaultVerbosityDetailsLevel = 0 const defaultVerbosityDetailsLevel = 0
u.VerbosityDetailsLevel = helpers.DefaultPointer(u.VerbosityDetailsLevel, defaultVerbosityDetailsLevel) u.VerbosityDetailsLevel = gosettings.DefaultPointer(u.VerbosityDetailsLevel, defaultVerbosityDetailsLevel)
const defaultValidationLogLevel = 0 const defaultValidationLogLevel = 0
u.ValidationLogLevel = helpers.DefaultPointer(u.ValidationLogLevel, defaultValidationLogLevel) u.ValidationLogLevel = gosettings.DefaultPointer(u.ValidationLogLevel, defaultValidationLogLevel)
if u.Allowed == nil { if u.Allowed == nil {
u.Allowed = []netip.Prefix{ u.Allowed = []netip.Prefix{
@@ -49,7 +49,7 @@ func (u *Unbound) setDefaults() {
} }
} }
u.Username = helpers.DefaultString(u.Username, "root") u.Username = gosettings.DefaultString(u.Username, "root")
} }
var ( var (
@@ -94,37 +94,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: gosettings.CopySlice(u.Providers),
Caching: helpers.CopyPointer(u.Caching), Caching: gosettings.CopyPointer(u.Caching),
IPv6: helpers.CopyPointer(u.IPv6), IPv6: gosettings.CopyPointer(u.IPv6),
VerbosityLevel: helpers.CopyPointer(u.VerbosityLevel), VerbosityLevel: gosettings.CopyPointer(u.VerbosityLevel),
VerbosityDetailsLevel: helpers.CopyPointer(u.VerbosityDetailsLevel), VerbosityDetailsLevel: gosettings.CopyPointer(u.VerbosityDetailsLevel),
ValidationLogLevel: helpers.CopyPointer(u.ValidationLogLevel), ValidationLogLevel: gosettings.CopyPointer(u.ValidationLogLevel),
Username: u.Username, Username: u.Username,
Allowed: helpers.CopySlice(u.Allowed), Allowed: gosettings.CopySlice(u.Allowed),
} }
} }
func (u *Unbound) mergeWith(other Unbound) { func (u *Unbound) mergeWith(other Unbound) {
u.Providers = helpers.MergeSlices(u.Providers, other.Providers) u.Providers = gosettings.MergeWithSlice(u.Providers, other.Providers)
u.Caching = helpers.MergeWithPointer(u.Caching, other.Caching) u.Caching = gosettings.MergeWithPointer(u.Caching, other.Caching)
u.IPv6 = helpers.MergeWithPointer(u.IPv6, other.IPv6) u.IPv6 = gosettings.MergeWithPointer(u.IPv6, other.IPv6)
u.VerbosityLevel = helpers.MergeWithPointer(u.VerbosityLevel, other.VerbosityLevel) u.VerbosityLevel = gosettings.MergeWithPointer(u.VerbosityLevel, other.VerbosityLevel)
u.VerbosityDetailsLevel = helpers.MergeWithPointer(u.VerbosityDetailsLevel, other.VerbosityDetailsLevel) u.VerbosityDetailsLevel = gosettings.MergeWithPointer(u.VerbosityDetailsLevel, other.VerbosityDetailsLevel)
u.ValidationLogLevel = helpers.MergeWithPointer(u.ValidationLogLevel, other.ValidationLogLevel) u.ValidationLogLevel = gosettings.MergeWithPointer(u.ValidationLogLevel, other.ValidationLogLevel)
u.Username = helpers.MergeWithString(u.Username, other.Username) u.Username = gosettings.MergeWithString(u.Username, other.Username)
u.Allowed = helpers.MergeSlices(u.Allowed, other.Allowed) u.Allowed = gosettings.MergeWithSlice(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 = gosettings.OverrideWithSlice(u.Providers, other.Providers)
u.Caching = helpers.OverrideWithPointer(u.Caching, other.Caching) u.Caching = gosettings.OverrideWithPointer(u.Caching, other.Caching)
u.IPv6 = helpers.OverrideWithPointer(u.IPv6, other.IPv6) u.IPv6 = gosettings.OverrideWithPointer(u.IPv6, other.IPv6)
u.VerbosityLevel = helpers.OverrideWithPointer(u.VerbosityLevel, other.VerbosityLevel) u.VerbosityLevel = gosettings.OverrideWithPointer(u.VerbosityLevel, other.VerbosityLevel)
u.VerbosityDetailsLevel = helpers.OverrideWithPointer(u.VerbosityDetailsLevel, other.VerbosityDetailsLevel) u.VerbosityDetailsLevel = gosettings.OverrideWithPointer(u.VerbosityDetailsLevel, other.VerbosityDetailsLevel)
u.ValidationLogLevel = helpers.OverrideWithPointer(u.ValidationLogLevel, other.ValidationLogLevel) u.ValidationLogLevel = gosettings.OverrideWithPointer(u.ValidationLogLevel, other.ValidationLogLevel)
u.Username = helpers.OverrideWithString(u.Username, other.Username) u.Username = gosettings.OverrideWithString(u.Username, other.Username)
u.Allowed = helpers.OverrideWithSlice(u.Allowed, other.Allowed) u.Allowed = gosettings.OverrideWithSlice(u.Allowed, other.Allowed)
} }
func (u Unbound) ToUnboundFormat() (settings unbound.Settings, err error) { func (u Unbound) ToUnboundFormat() (settings unbound.Settings, err error) {
@@ -186,8 +186,8 @@ func (u Unbound) toLinesNode() (node *gotree.Node) {
authServers.Appendf(provider) authServers.Appendf(provider)
} }
node.Appendf("Caching: %s", helpers.BoolPtrToYesNo(u.Caching)) node.Appendf("Caching: %s", gosettings.BoolToYesNo(u.Caching))
node.Appendf("IPv6: %s", helpers.BoolPtrToYesNo(u.IPv6)) node.Appendf("IPv6: %s", gosettings.BoolToYesNo(u.IPv6))
node.Appendf("Verbosity level: %d", *u.VerbosityLevel) node.Appendf("Verbosity level: %d", *u.VerbosityLevel)
node.Appendf("Verbosity details level: %d", *u.VerbosityDetailsLevel) node.Appendf("Verbosity details level: %d", *u.VerbosityDetailsLevel)
node.Appendf("Validation log level: %d", *u.ValidationLogLevel) node.Appendf("Validation log level: %d", *u.ValidationLogLevel)

View File

@@ -29,9 +29,9 @@ func Test_Unbound_JSON(t *testing.T) {
b, err := json.Marshal(settings) b, err := json.Marshal(settings)
require.NoError(t, err) require.NoError(t, err)
const expected = `{"Providers":["cloudflare"],"Caching":true,"IPv6":false,` + const expected = `{"providers":["cloudflare"],"caching":true,"ipv6":false,` +
`"VerbosityLevel":1,"VerbosityDetailsLevel":null,"ValidationLogLevel":0,` + `"verbosity_level":1,"verbosity_details_level":null,"validation_log_level":0,` +
`"Username":"user","Allowed":["0.0.0.0/0","::/0"]}` `"username":"user","allowed":["0.0.0.0/0","::/0"]}`
assert.Equal(t, expected, string(b)) assert.Equal(t, expected, string(b))

View File

@@ -5,8 +5,9 @@ import (
"strings" "strings"
"time" "time"
"github.com/qdm12/gluetun/internal/configuration/settings/helpers"
"github.com/qdm12/gluetun/internal/constants/providers" "github.com/qdm12/gluetun/internal/constants/providers"
"github.com/qdm12/gosettings"
"github.com/qdm12/gosettings/validate"
"github.com/qdm12/gotree" "github.com/qdm12/gotree"
) )
@@ -45,16 +46,9 @@ func (u Updater) Validate() (err error) {
validProviders := providers.All() validProviders := providers.All()
for _, provider := range u.Providers { for _, provider := range u.Providers {
valid := false err = validate.IsOneOf(provider, validProviders...)
for _, validProvider := range validProviders { if err != nil {
if provider == validProvider { return fmt.Errorf("%w: %w", ErrVPNProviderNameNotValid, err)
valid = true
break
}
}
if !valid {
return fmt.Errorf("%w: %q can only be one of %s",
ErrVPNProviderNameNotValid, provider, helpers.ChoicesOrString(validProviders))
} }
} }
@@ -63,35 +57,35 @@ 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: gosettings.CopyPointer(u.Period),
DNSAddress: u.DNSAddress, DNSAddress: u.DNSAddress,
MinRatio: u.MinRatio, MinRatio: u.MinRatio,
Providers: helpers.CopySlice(u.Providers), Providers: gosettings.CopySlice(u.Providers),
} }
} }
// 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 = gosettings.MergeWithPointer(u.Period, other.Period)
u.DNSAddress = helpers.MergeWithString(u.DNSAddress, other.DNSAddress) u.DNSAddress = gosettings.MergeWithString(u.DNSAddress, other.DNSAddress)
u.MinRatio = helpers.MergeWithNumber(u.MinRatio, other.MinRatio) u.MinRatio = gosettings.MergeWithNumber(u.MinRatio, other.MinRatio)
u.Providers = helpers.MergeSlices(u.Providers, other.Providers) u.Providers = gosettings.MergeWithSlice(u.Providers, other.Providers)
} }
// 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 = gosettings.OverrideWithPointer(u.Period, other.Period)
u.DNSAddress = helpers.OverrideWithString(u.DNSAddress, other.DNSAddress) u.DNSAddress = gosettings.OverrideWithString(u.DNSAddress, other.DNSAddress)
u.MinRatio = helpers.OverrideWithNumber(u.MinRatio, other.MinRatio) u.MinRatio = gosettings.OverrideWithNumber(u.MinRatio, other.MinRatio)
u.Providers = helpers.OverrideWithSlice(u.Providers, other.Providers) u.Providers = gosettings.OverrideWithSlice(u.Providers, other.Providers)
} }
func (u *Updater) SetDefaults(vpnProvider string) { func (u *Updater) SetDefaults(vpnProvider string) {
u.Period = helpers.DefaultPointer(u.Period, 0) u.Period = gosettings.DefaultPointer(u.Period, 0)
u.DNSAddress = helpers.DefaultString(u.DNSAddress, "1.1.1.1:53") u.DNSAddress = gosettings.DefaultString(u.DNSAddress, "1.1.1.1:53")
if u.MinRatio == 0 { if u.MinRatio == 0 {
const defaultMinRatio = 0.8 const defaultMinRatio = 0.8

View File

@@ -1,7 +1,7 @@
package settings package settings
import ( import (
"github.com/qdm12/gluetun/internal/configuration/settings/helpers" "github.com/qdm12/gosettings"
"github.com/qdm12/gotree" "github.com/qdm12/gotree"
) )
@@ -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: gosettings.CopyPointer(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 = gosettings.MergeWithPointer(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 = gosettings.OverrideWithPointer(v.Enabled, other.Enabled)
} }
func (v *Version) setDefaults() { func (v *Version) setDefaults() {
v.Enabled = helpers.DefaultPointer(v.Enabled, true) v.Enabled = gosettings.DefaultPointer(v.Enabled, true)
} }
func (v Version) String() string { func (v Version) String() string {
@@ -47,7 +47,7 @@ func (v Version) String() string {
func (v Version) toLinesNode() (node *gotree.Node) { func (v Version) toLinesNode() (node *gotree.Node) {
node = gotree.New("Version settings:") node = gotree.New("Version settings:")
node.Appendf("Enabled: %s", helpers.BoolPtrToYesNo(v.Enabled)) node.Appendf("Enabled: %s", gosettings.BoolToYesNo(v.Enabled))
return node return node
} }

View File

@@ -2,10 +2,10 @@ package settings
import ( import (
"fmt" "fmt"
"strings"
"github.com/qdm12/gluetun/internal/configuration/settings/helpers"
"github.com/qdm12/gluetun/internal/constants/vpn" "github.com/qdm12/gluetun/internal/constants/vpn"
"github.com/qdm12/gosettings"
"github.com/qdm12/gosettings/validate"
"github.com/qdm12/gotree" "github.com/qdm12/gotree"
) )
@@ -13,19 +13,18 @@ type VPN struct {
// Type is the VPN type and can only be // Type is the VPN type and can only be
// 'openvpn' or 'wireguard'. It cannot be the // 'openvpn' or 'wireguard'. It cannot be the
// empty string in the internal state. // empty string in the internal state.
Type string Type string `json:"type"`
Provider Provider Provider Provider `json:"provider"`
OpenVPN OpenVPN OpenVPN OpenVPN `json:"openvpn"`
Wireguard Wireguard Wireguard Wireguard `json:"wireguard"`
} }
// 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(storage Storage, ipv6Supported bool) (err error) {
// Validate Type // Validate Type
validVPNTypes := []string{vpn.OpenVPN, vpn.Wireguard} validVPNTypes := []string{vpn.OpenVPN, vpn.Wireguard}
if !helpers.IsOneOf(v.Type, validVPNTypes...) { if err = validate.IsOneOf(v.Type, validVPNTypes...); err != nil {
return fmt.Errorf("%w: %q and can only be one of %s", return fmt.Errorf("%w: %w", ErrVPNTypeNotValid, err)
ErrVPNTypeNotValid, v.Type, strings.Join(validVPNTypes, ", "))
} }
err = v.Provider.validate(v.Type, storage) err = v.Provider.validate(v.Type, storage)
@@ -58,24 +57,24 @@ func (v *VPN) Copy() (copied VPN) {
} }
func (v *VPN) mergeWith(other VPN) { func (v *VPN) mergeWith(other VPN) {
v.Type = helpers.MergeWithString(v.Type, other.Type) v.Type = gosettings.MergeWithString(v.Type, other.Type)
v.Provider.mergeWith(other.Provider) v.Provider.mergeWith(other.Provider)
v.OpenVPN.mergeWith(other.OpenVPN) v.OpenVPN.mergeWith(other.OpenVPN)
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 = gosettings.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)
v.Wireguard.overrideWith(other.Wireguard) v.Wireguard.overrideWith(other.Wireguard)
} }
func (v *VPN) setDefaults() { func (v *VPN) setDefaults() {
v.Type = helpers.DefaultString(v.Type, vpn.OpenVPN) v.Type = gosettings.DefaultString(v.Type, vpn.OpenVPN)
v.Provider.setDefaults() v.Provider.setDefaults()
v.OpenVPN.setDefaults(*v.Provider.Name) v.OpenVPN.setDefaults(*v.Provider.Name)
v.Wireguard.setDefaults() v.Wireguard.setDefaults(*v.Provider.Name)
} }
func (v VPN) String() string { func (v VPN) String() string {

View File

@@ -7,6 +7,8 @@ import (
"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/providers"
"github.com/qdm12/gosettings"
"github.com/qdm12/gosettings/validate"
"github.com/qdm12/gotree" "github.com/qdm12/gotree"
"golang.zx2c4.com/wireguard/wgctrl/wgtypes" "golang.zx2c4.com/wireguard/wgctrl/wgtypes"
) )
@@ -15,23 +17,29 @@ import (
type Wireguard struct { type Wireguard struct {
// PrivateKey is the Wireguard client peer private key. // PrivateKey is the Wireguard client peer private key.
// It cannot be nil in the internal state. // It cannot be nil in the internal state.
PrivateKey *string PrivateKey *string `json:"private_key"`
// PreSharedKey is the Wireguard pre-shared key. // PreSharedKey is the Wireguard pre-shared key.
// It can be the empty string to indicate there // It can be the empty string to indicate there
// is no pre-shared key. // is no pre-shared key.
// It cannot be nil in the internal state. // It cannot be nil in the internal state.
PreSharedKey *string PreSharedKey *string `json:"pre_shared_key"`
// Addresses are the Wireguard interface addresses. // Addresses are the Wireguard interface addresses.
Addresses []netip.Prefix Addresses []netip.Prefix `json:"addresses"`
// 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 `json:"interface"`
// Maximum Transmission Unit (MTU) of the Wireguard interface.
// It cannot be zero in the internal state, and defaults to
// 1400. Note it is not the wireguard-go MTU default of 1420
// because this impacts bandwidth a lot on some VPN providers,
// see https://github.com/qdm12/gluetun/issues/1650.
MTU uint16 `json:"mtu"`
// Implementation is the Wireguard implementation to use. // Implementation is the Wireguard implementation to use.
// It can be "auto", "userspace" or "kernelspace". // It can be "auto", "userspace" or "kernelspace".
// It defaults to "auto" and cannot be the empty string // It defaults to "auto" and cannot be the empty string
// in the internal state. // in the internal state.
Implementation string Implementation string `json:"implementation"`
} }
var regexpInterfaceName = regexp.MustCompile(`^[a-zA-Z0-9_]+$`) var regexpInterfaceName = regexp.MustCompile(`^[a-zA-Z0-9_]+$`)
@@ -40,9 +48,11 @@ var regexpInterfaceName = regexp.MustCompile(`^[a-zA-Z0-9_]+$`)
// It should only be ran if the VPN type chosen is Wireguard. // It should only be ran if the VPN type chosen is Wireguard.
func (w Wireguard) validate(vpnProvider string, ipv6Supported bool) (err error) { func (w Wireguard) validate(vpnProvider string, ipv6Supported bool) (err error) {
if !helpers.IsOneOf(vpnProvider, if !helpers.IsOneOf(vpnProvider,
providers.Airvpn,
providers.Custom, providers.Custom,
providers.Ivpn, providers.Ivpn,
providers.Mullvad, providers.Mullvad,
providers.Nordvpn,
providers.Surfshark, providers.Surfshark,
providers.Windscribe, providers.Windscribe,
) { ) {
@@ -96,9 +106,8 @@ func (w Wireguard) validate(vpnProvider string, ipv6Supported bool) (err error)
} }
validImplementations := []string{"auto", "userspace", "kernelspace"} validImplementations := []string{"auto", "userspace", "kernelspace"}
if !helpers.IsOneOf(w.Implementation, validImplementations...) { if err := validate.IsOneOf(w.Implementation, validImplementations...); err != nil {
return fmt.Errorf("%w: %s must be one of %s", ErrWireguardImplementationNotValid, return fmt.Errorf("%w: %w", ErrWireguardImplementationNotValid, err)
w.Implementation, helpers.ChoicesOrString(validImplementations))
} }
return nil return nil
@@ -106,35 +115,45 @@ func (w Wireguard) validate(vpnProvider string, ipv6Supported bool) (err error)
func (w *Wireguard) copy() (copied Wireguard) { func (w *Wireguard) copy() (copied Wireguard) {
return Wireguard{ return Wireguard{
PrivateKey: helpers.CopyPointer(w.PrivateKey), PrivateKey: gosettings.CopyPointer(w.PrivateKey),
PreSharedKey: helpers.CopyPointer(w.PreSharedKey), PreSharedKey: gosettings.CopyPointer(w.PreSharedKey),
Addresses: helpers.CopySlice(w.Addresses), Addresses: gosettings.CopySlice(w.Addresses),
Interface: w.Interface, Interface: w.Interface,
MTU: w.MTU,
Implementation: w.Implementation, 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 = gosettings.MergeWithPointer(w.PrivateKey, other.PrivateKey)
w.PreSharedKey = helpers.MergeWithPointer(w.PreSharedKey, other.PreSharedKey) w.PreSharedKey = gosettings.MergeWithPointer(w.PreSharedKey, other.PreSharedKey)
w.Addresses = helpers.MergeSlices(w.Addresses, other.Addresses) w.Addresses = gosettings.MergeWithSlice(w.Addresses, other.Addresses)
w.Interface = helpers.MergeWithString(w.Interface, other.Interface) w.Interface = gosettings.MergeWithString(w.Interface, other.Interface)
w.Implementation = helpers.MergeWithString(w.Implementation, other.Implementation) w.MTU = gosettings.MergeWithNumber(w.MTU, other.MTU)
w.Implementation = gosettings.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 = gosettings.OverrideWithPointer(w.PrivateKey, other.PrivateKey)
w.PreSharedKey = helpers.OverrideWithPointer(w.PreSharedKey, other.PreSharedKey) w.PreSharedKey = gosettings.OverrideWithPointer(w.PreSharedKey, other.PreSharedKey)
w.Addresses = helpers.OverrideWithSlice(w.Addresses, other.Addresses) w.Addresses = gosettings.OverrideWithSlice(w.Addresses, other.Addresses)
w.Interface = helpers.OverrideWithString(w.Interface, other.Interface) w.Interface = gosettings.OverrideWithString(w.Interface, other.Interface)
w.Implementation = helpers.OverrideWithString(w.Implementation, other.Implementation) w.MTU = gosettings.OverrideWithNumber(w.MTU, other.MTU)
w.Implementation = gosettings.OverrideWithString(w.Implementation, other.Implementation)
} }
func (w *Wireguard) setDefaults() { func (w *Wireguard) setDefaults(vpnProvider string) {
w.PrivateKey = helpers.DefaultPointer(w.PrivateKey, "") w.PrivateKey = gosettings.DefaultPointer(w.PrivateKey, "")
w.PreSharedKey = helpers.DefaultPointer(w.PreSharedKey, "") w.PreSharedKey = gosettings.DefaultPointer(w.PreSharedKey, "")
w.Interface = helpers.DefaultString(w.Interface, "wg0") if vpnProvider == providers.Nordvpn {
w.Implementation = helpers.DefaultString(w.Implementation, "auto") defaultNordVPNAddress := netip.AddrFrom4([4]byte{10, 5, 0, 2})
defaultNordVPNPrefix := netip.PrefixFrom(defaultNordVPNAddress, defaultNordVPNAddress.BitLen())
w.Addresses = gosettings.DefaultSlice(w.Addresses, []netip.Prefix{defaultNordVPNPrefix})
}
w.Interface = gosettings.DefaultString(w.Interface, "wg0")
const defaultMTU = 1400
w.MTU = gosettings.DefaultNumber(w.MTU, defaultMTU)
w.Implementation = gosettings.DefaultString(w.Implementation, "auto")
} }
func (w Wireguard) String() string { func (w Wireguard) String() string {
@@ -145,12 +164,12 @@ func (w Wireguard) toLinesNode() (node *gotree.Node) {
node = gotree.New("Wireguard settings:") node = gotree.New("Wireguard settings:")
if *w.PrivateKey != "" { if *w.PrivateKey != "" {
s := helpers.ObfuscateWireguardKey(*w.PrivateKey) s := gosettings.ObfuscateKey(*w.PrivateKey)
node.Appendf("Private key: %s", s) node.Appendf("Private key: %s", s)
} }
if *w.PreSharedKey != "" { if *w.PreSharedKey != "" {
s := helpers.ObfuscateWireguardKey(*w.PreSharedKey) s := gosettings.ObfuscateKey(*w.PreSharedKey)
node.Appendf("Pre-shared key: %s", s) node.Appendf("Pre-shared key: %s", s)
} }
@@ -159,7 +178,8 @@ func (w Wireguard) toLinesNode() (node *gotree.Node) {
addressesNode.Appendf(address.String()) addressesNode.Appendf(address.String())
} }
node.Appendf("Network interface: %s", w.Interface) interfaceNode := node.Appendf("Network interface: %s", w.Interface)
interfaceNode.Appendf("MTU: %d", w.MTU)
if w.Implementation != "auto" { if w.Implementation != "auto" {
node.Appendf("Implementation: %s", w.Implementation) node.Appendf("Implementation: %s", w.Implementation)

View File

@@ -4,8 +4,9 @@ import (
"fmt" "fmt"
"net/netip" "net/netip"
"github.com/qdm12/gluetun/internal/configuration/settings/helpers"
"github.com/qdm12/gluetun/internal/constants/providers" "github.com/qdm12/gluetun/internal/constants/providers"
"github.com/qdm12/gosettings"
"github.com/qdm12/gosettings/validate"
"github.com/qdm12/gotree" "github.com/qdm12/gotree"
"golang.zx2c4.com/wireguard/wgctrl/wgtypes" "golang.zx2c4.com/wireguard/wgctrl/wgtypes"
) )
@@ -17,18 +18,18 @@ type WireguardSelection struct {
// 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 netaddr.IPv4Unspecified(). It can never be the zero value
// in the internal state. // in the internal state.
EndpointIP netip.Addr EndpointIP netip.Addr `json:"endpoint_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, Surfshark
// 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
// state. // state.
EndpointPort *uint16 EndpointPort *uint16 `json:"endpoint_port"`
// PublicKey is the server public key. // PublicKey is the server public key.
// 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.
PublicKey string PublicKey string `json:"public_key"`
} }
// Validate validates WireguardSelection settings. // Validate validates WireguardSelection settings.
@@ -37,7 +38,7 @@ func (w WireguardSelection) validate(vpnProvider string) (err error) {
// Validate EndpointIP // Validate EndpointIP
switch vpnProvider { switch vpnProvider {
case providers.Airvpn, providers.Ivpn, providers.Mullvad, case providers.Airvpn, providers.Ivpn, providers.Mullvad,
providers.Surfshark, providers.Windscribe: providers.Nordvpn, providers.Surfshark, providers.Windscribe:
// endpoint IP addresses are baked in // endpoint IP addresses are baked in
case providers.Custom: case providers.Custom:
if !w.EndpointIP.IsValid() || w.EndpointIP.IsUnspecified() { if !w.EndpointIP.IsValid() || w.EndpointIP.IsUnspecified() {
@@ -54,7 +55,7 @@ func (w WireguardSelection) validate(vpnProvider string) (err error) {
return fmt.Errorf("%w", ErrWireguardEndpointPortNotSet) return fmt.Errorf("%w", ErrWireguardEndpointPortNotSet)
} }
// EndpointPort cannot be set // EndpointPort cannot be set
case providers.Surfshark: case providers.Surfshark, providers.Nordvpn:
if *w.EndpointPort != 0 { if *w.EndpointPort != 0 {
return fmt.Errorf("%w", ErrWireguardEndpointPortSet) return fmt.Errorf("%w", ErrWireguardEndpointPortSet)
} }
@@ -76,12 +77,12 @@ func (w WireguardSelection) validate(vpnProvider string) (err error) {
allowed = []uint16{53, 80, 123, 443, 1194, 65142} allowed = []uint16{53, 80, 123, 443, 1194, 65142}
} }
if helpers.Uint16IsOneOf(*w.EndpointPort, allowed) { err = validate.IsOneOf(*w.EndpointPort, allowed...)
if err == nil {
break break
} }
return fmt.Errorf("%w: %d for VPN service provider %s; %s", return fmt.Errorf("%w: for VPN service provider %s: %w",
ErrWireguardEndpointPortNotAllowed, w.EndpointPort, vpnProvider, ErrWireguardEndpointPortNotAllowed, vpnProvider, err)
helpers.PortChoicesOrString(allowed))
default: // Providers not supporting Wireguard default: // Providers not supporting Wireguard
} }
@@ -110,26 +111,26 @@ 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: w.EndpointIP,
EndpointPort: helpers.CopyPointer(w.EndpointPort), EndpointPort: gosettings.CopyPointer(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 = gosettings.MergeWithValidator(w.EndpointIP, other.EndpointIP)
w.EndpointPort = helpers.MergeWithPointer(w.EndpointPort, other.EndpointPort) w.EndpointPort = gosettings.MergeWithPointer(w.EndpointPort, other.EndpointPort)
w.PublicKey = helpers.MergeWithString(w.PublicKey, other.PublicKey) w.PublicKey = gosettings.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 = gosettings.OverrideWithValidator(w.EndpointIP, other.EndpointIP)
w.EndpointPort = helpers.OverrideWithPointer(w.EndpointPort, other.EndpointPort) w.EndpointPort = gosettings.OverrideWithPointer(w.EndpointPort, other.EndpointPort)
w.PublicKey = helpers.OverrideWithString(w.PublicKey, other.PublicKey) w.PublicKey = gosettings.OverrideWithString(w.PublicKey, other.PublicKey)
} }
func (w *WireguardSelection) setDefaults() { func (w *WireguardSelection) setDefaults() {
w.EndpointIP = helpers.DefaultIP(w.EndpointIP, netip.IPv4Unspecified()) w.EndpointIP = gosettings.DefaultValidator(w.EndpointIP, netip.IPv4Unspecified())
w.EndpointPort = helpers.DefaultPointer(w.EndpointPort, 0) w.EndpointPort = gosettings.DefaultPointer(w.EndpointPort, 0)
} }
func (w WireguardSelection) String() string { func (w WireguardSelection) String() string {

View File

@@ -13,9 +13,9 @@ func (s *Source) readDNS() (dns settings.DNS, err error) {
return dns, err return dns, err
} }
dns.KeepNameserver, err = envToBoolPtr("DNS_KEEP_NAMESERVER") dns.KeepNameserver, err = s.env.BoolPtr("DNS_KEEP_NAMESERVER")
if err != nil { if err != nil {
return dns, fmt.Errorf("environment variable DNS_KEEP_NAMESERVER: %w", err) return dns, err
} }
dns.DoT, err = s.readDoT() dns.DoT, err = s.readDoT()
@@ -27,19 +27,24 @@ func (s *Source) readDNS() (dns settings.DNS, err error) {
} }
func (s *Source) readDNSServerAddress() (address netip.Addr, err error) { func (s *Source) readDNSServerAddress() (address netip.Addr, err error) {
key, value := s.getEnvWithRetro("DNS_ADDRESS", "DNS_PLAINTEXT_ADDRESS") const currentKey = "DNS_ADDRESS"
if value == "" { key := firstKeySet(s.env, "DNS_PLAINTEXT_ADDRESS", currentKey)
switch key {
case "":
return address, nil return address, nil
case currentKey:
default: // Retro-compatibility
s.handleDeprecatedKey(key, currentKey)
} }
address, err = netip.ParseAddr(value) address, err = s.env.NetipAddr(key)
if err != nil { if err != nil {
return address, fmt.Errorf("environment variable %s: %w", key, err) return address, err
} }
// TODO remove in v4 // TODO remove in v4
if address.Unmap().Compare(netip.AddrFrom4([4]byte{127, 0, 0, 1})) != 0 { if address.Unmap().Compare(netip.AddrFrom4([4]byte{127, 0, 0, 1})) != 0 {
s.warner.Warn(key + " is set to " + value + s.warner.Warn(key + " is set to " + address.String() +
" 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 serves." +
" 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" +

View File

@@ -6,58 +6,44 @@ import (
"net/netip" "net/netip"
"github.com/qdm12/gluetun/internal/configuration/settings" "github.com/qdm12/gluetun/internal/configuration/settings"
"github.com/qdm12/govalid/binary" "github.com/qdm12/gosettings/sources/env"
) )
func (s *Source) readDNSBlacklist() (blacklist settings.DNSBlacklist, err error) { func (s *Source) readDNSBlacklist() (blacklist settings.DNSBlacklist, err error) {
blacklist.BlockMalicious, err = envToBoolPtr("BLOCK_MALICIOUS") blacklist.BlockMalicious, err = s.env.BoolPtr("BLOCK_MALICIOUS")
if err != nil {
return blacklist, fmt.Errorf("environment variable BLOCK_MALICIOUS: %w", err)
}
blacklist.BlockSurveillance, err = s.readBlockSurveillance()
if err != nil { if err != nil {
return blacklist, err return blacklist, err
} }
blacklist.BlockAds, err = envToBoolPtr("BLOCK_ADS") blacklist.BlockSurveillance, err = s.env.BoolPtr("BLOCK_SURVEILLANCE",
env.RetroKeys("BLOCK_NSA"))
if err != nil { if err != nil {
return blacklist, fmt.Errorf("environment variable BLOCK_ADS: %w", err) return blacklist, err
}
blacklist.BlockAds, err = s.env.BoolPtr("BLOCK_ADS")
if err != nil {
return blacklist, err
} }
blacklist.AddBlockedIPs, blacklist.AddBlockedIPPrefixes, blacklist.AddBlockedIPs, blacklist.AddBlockedIPPrefixes,
err = readDoTPrivateAddresses() // TODO v4 split in 2 err = s.readDoTPrivateAddresses() // TODO v4 split in 2
if err != nil { if err != nil {
return blacklist, err return blacklist, err
} }
blacklist.AllowedHosts = envToCSV("UNBLOCK") // TODO v4 change name blacklist.AllowedHosts = s.env.CSV("UNBLOCK") // TODO v4 change name
return blacklist, nil return blacklist, nil
} }
func (s *Source) readBlockSurveillance() (blocked *bool, err error) {
key, value := s.getEnvWithRetro("BLOCK_SURVEILLANCE", "BLOCK_NSA")
if value == "" {
return nil, nil //nolint:nilnil
}
blocked = new(bool)
*blocked, err = binary.Validate(value)
if err != nil {
return nil, fmt.Errorf("environment variable %s: %w", key, err)
}
return blocked, nil
}
var ( 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 (s *Source) readDoTPrivateAddresses() (ips []netip.Addr,
ipPrefixes []netip.Prefix, err error) { ipPrefixes []netip.Prefix, err error) {
privateAddresses := envToCSV("DOT_PRIVATE_ADDRESS") privateAddresses := s.env.CSV("DOT_PRIVATE_ADDRESS")
if len(privateAddresses) == 0 { if len(privateAddresses) == 0 {
return nil, nil, nil return nil, nil, nil
} }

View File

@@ -1,23 +1,21 @@
package env package env
import ( import (
"fmt"
"github.com/qdm12/gluetun/internal/configuration/settings" "github.com/qdm12/gluetun/internal/configuration/settings"
) )
func (s *Source) readDoT() (dot settings.DoT, err error) { func (s *Source) readDoT() (dot settings.DoT, err error) {
dot.Enabled, err = envToBoolPtr("DOT") dot.Enabled, err = s.env.BoolPtr("DOT")
if err != nil { if err != nil {
return dot, fmt.Errorf("environment variable DOT: %w", err) return dot, err
} }
dot.UpdatePeriod, err = envToDurationPtr("DNS_UPDATE_PERIOD") dot.UpdatePeriod, err = s.env.DurationPtr("DNS_UPDATE_PERIOD")
if err != nil { if err != nil {
return dot, fmt.Errorf("environment variable DNS_UPDATE_PERIOD: %w", err) return dot, err
} }
dot.Unbound, err = readUnbound() dot.Unbound, err = s.readUnbound()
if err != nil { if err != nil {
return dot, err return dot, err
} }

View File

@@ -1,80 +1,36 @@
package env package env
import ( import (
"errors"
"fmt"
"net/netip"
"strconv"
"github.com/qdm12/gluetun/internal/configuration/settings" "github.com/qdm12/gluetun/internal/configuration/settings"
"github.com/qdm12/gosettings/sources/env"
) )
func (s *Source) readFirewall() (firewall settings.Firewall, err error) { func (s *Source) readFirewall() (firewall settings.Firewall, err error) {
vpnInputPortStrings := envToCSV("FIREWALL_VPN_INPUT_PORTS") firewall.VPNInputPorts, err = s.env.CSVUint16("FIREWALL_VPN_INPUT_PORTS")
firewall.VPNInputPorts, err = stringsToPorts(vpnInputPortStrings)
if err != nil { if err != nil {
return firewall, fmt.Errorf("environment variable FIREWALL_VPN_INPUT_PORTS: %w", err) return firewall, err
} }
inputPortStrings := envToCSV("FIREWALL_INPUT_PORTS") firewall.InputPorts, err = s.env.CSVUint16("FIREWALL_INPUT_PORTS")
firewall.InputPorts, err = stringsToPorts(inputPortStrings)
if err != nil { if err != nil {
return firewall, fmt.Errorf("environment variable FIREWALL_INPUT_PORTS: %w", err) return firewall, err
} }
outboundSubnetsKey, _ := s.getEnvWithRetro("FIREWALL_OUTBOUND_SUBNETS", "EXTRA_SUBNETS") firewall.OutboundSubnets, err = s.env.CSVNetipPrefixes("FIREWALL_OUTBOUND_SUBNETS",
outboundSubnetStrings := envToCSV(outboundSubnetsKey) env.RetroKeys("EXTRA_SUBNETS"))
firewall.OutboundSubnets, err = stringsToNetipPrefixes(outboundSubnetStrings)
if err != nil { if err != nil {
return firewall, fmt.Errorf("environment variable %s: %w", outboundSubnetsKey, err) return firewall, err
} }
firewall.Enabled, err = envToBoolPtr("FIREWALL") firewall.Enabled, err = s.env.BoolPtr("FIREWALL")
if err != nil { if err != nil {
return firewall, fmt.Errorf("environment variable FIREWALL: %w", err) return firewall, err
} }
firewall.Debug, err = envToBoolPtr("FIREWALL_DEBUG") firewall.Debug, err = s.env.BoolPtr("FIREWALL_DEBUG")
if err != nil { if err != nil {
return firewall, fmt.Errorf("environment variable FIREWALL_DEBUG: %w", err) return firewall, err
} }
return firewall, nil return firewall, nil
} }
var (
ErrPortParsing = errors.New("cannot parse port")
ErrPortValue = errors.New("port value is not valid")
)
func stringsToPorts(ss []string) (ports []uint16, err error) {
if len(ss) == 0 {
return nil, nil
}
ports = make([]uint16, len(ss))
for i, s := range ss {
port, err := strconv.Atoi(s)
if err != nil {
return nil, fmt.Errorf("%w: %s: %s", ErrPortParsing, s, err)
} else if port < 1 || port > 65535 {
return nil, fmt.Errorf("%w: must be between 1 and 65535: %d",
ErrPortValue, port)
}
ports[i] = uint16(port)
}
return ports, nil
}
func stringsToNetipPrefixes(ss []string) (ipPrefixes []netip.Prefix, err error) {
if len(ss) == 0 {
return nil, nil
}
ipPrefixes = make([]netip.Prefix, len(ss))
for i, s := range ss {
ipPrefixes[i], err = netip.ParsePrefix(s)
if err != nil {
return nil, fmt.Errorf("parsing IP network %q: %w", s, err)
}
}
return ipPrefixes, nil
}

View File

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

View File

@@ -3,127 +3,10 @@ package env
import ( import (
"fmt" "fmt"
"os" "os"
"strconv"
"strings"
"time"
"github.com/qdm12/govalid/binary" "github.com/qdm12/gosettings/sources/env"
"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) {
csv := getCleanedEnv(envKey)
if csv == "" {
return nil
}
return lowerAndSplit(csv)
}
func envToFloat64(envKey string) (f float64, err error) {
s := getCleanedEnv(envKey)
if s == "" {
return 0, nil
}
const bits = 64
return strconv.ParseFloat(s, bits)
}
func envToStringPtr(envKey string) (stringPtr *string) {
s := getCleanedEnv(envKey)
if s == "" {
return nil
}
return &s
}
func envToBoolPtr(envKey string) (boolPtr *bool, err error) {
s := getCleanedEnv(envKey)
if s == "" {
return nil, nil //nolint:nilnil
}
value, err := binary.Validate(s)
if err != nil {
return nil, err
}
return &value, nil
}
func envToIntPtr(envKey string) (intPtr *int, err error) {
s := getCleanedEnv(envKey)
if s == "" {
return nil, nil //nolint:nilnil
}
value, err := strconv.Atoi(s)
if err != nil {
return nil, err
}
return &value, nil
}
func envToUint8Ptr(envKey string) (uint8Ptr *uint8, err error) {
s := getCleanedEnv(envKey)
if s == "" {
return nil, nil //nolint:nilnil
}
const min, max = 0, 255
value, err := integer.Validate(s, integer.OptionRange(min, max))
if err != nil {
return nil, err
}
uint8Ptr = new(uint8)
*uint8Ptr = uint8(value)
return uint8Ptr, nil
}
func envToUint16Ptr(envKey string) (uint16Ptr *uint16, err error) {
s := getCleanedEnv(envKey)
if s == "" {
return nil, nil //nolint:nilnil
}
const min, max = 0, 65535
value, err := integer.Validate(s, integer.OptionRange(min, max))
if err != nil {
return nil, err
}
uint16Ptr = new(uint16)
*uint16Ptr = uint16(value)
return uint16Ptr, nil
}
func envToDurationPtr(envKey string) (durationPtr *time.Duration, err error) {
s := getCleanedEnv(envKey)
if s == "" {
return nil, nil //nolint:nilnil
}
durationPtr = new(time.Duration)
*durationPtr, err = time.ParseDuration(s)
if err != nil {
return nil, err
}
return durationPtr, nil
}
func lowerAndSplit(csv string) (values []string) {
csv = strings.ToLower(csv)
return strings.Split(csv, ",")
}
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 {
@@ -135,6 +18,16 @@ func unsetEnvKeys(envKeys []string, err error) (newErr error) {
return newErr return newErr
} }
func stringPtr(s string) *string { return &s } func ptrTo[T any](value T) *T {
func uint32Ptr(n uint32) *uint32 { return &n } return &value
func boolPtr(b bool) *bool { return &b } }
func firstKeySet(e env.Env, keys ...string) (firstKeySet string) {
for _, key := range keys {
value := e.Get(key)
if value != nil {
return key
}
}
return ""
}

View File

@@ -1,22 +0,0 @@
package env
import (
"os"
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
// setTestEnv is used to set environment variables in
// parallel tests.
func setTestEnv(t *testing.T, key, value string) {
t.Helper()
existing := os.Getenv(key)
err := os.Setenv(key, value) //nolint:tenv
t.Cleanup(func() {
err = os.Setenv(key, existing)
assert.NoError(t, err)
})
require.NoError(t, err)
}

View File

@@ -4,22 +4,32 @@ import (
"fmt" "fmt"
"github.com/qdm12/gluetun/internal/configuration/settings" "github.com/qdm12/gluetun/internal/configuration/settings"
"github.com/qdm12/gosettings/sources/env"
"github.com/qdm12/govalid/binary" "github.com/qdm12/govalid/binary"
) )
func (s *Source) readHTTPProxy() (httpProxy settings.HTTPProxy, err error) { func (s *Source) readHTTPProxy() (httpProxy settings.HTTPProxy, err error) {
httpProxy.User = s.readHTTProxyUser() httpProxy.User = s.env.Get("HTTPPROXY_USER",
httpProxy.Password = s.readHTTProxyPassword() env.RetroKeys("PROXY_USER", "TINYPROXY_USER"),
httpProxy.ListeningAddress = s.readHTTProxyListeningAddress() env.ForceLowercase(false))
httpProxy.Enabled, err = s.readHTTProxyEnabled() httpProxy.Password = s.env.Get("HTTPPROXY_PASSWORD",
env.RetroKeys("PROXY_PASSWORD", "TINYPROXY_PASSWORD"),
env.ForceLowercase(false))
httpProxy.ListeningAddress, err = s.readHTTProxyListeningAddress()
if err != nil { if err != nil {
return httpProxy, err return httpProxy, err
} }
httpProxy.Stealth, err = envToBoolPtr("HTTPPROXY_STEALTH") httpProxy.Enabled, err = s.env.BoolPtr("HTTPPROXY", env.RetroKeys("PROXY", "TINYPROXY"))
if err != nil { if err != nil {
return httpProxy, fmt.Errorf("environment variable HTTPPROXY_STEALTH: %w", err) return httpProxy, err
}
httpProxy.Stealth, err = s.env.BoolPtr("HTTPPROXY_STEALTH")
if err != nil {
return httpProxy, err
} }
httpProxy.Log, err = s.readHTTProxyLog() httpProxy.Log, err = s.readHTTProxyLog()
@@ -30,59 +40,42 @@ func (s *Source) readHTTPProxy() (httpProxy settings.HTTPProxy, err error) {
return httpProxy, nil return httpProxy, nil
} }
func (s *Source) readHTTProxyUser() (user *string) { func (s *Source) readHTTProxyListeningAddress() (listeningAddress string, err error) {
_, value := s.getEnvWithRetro("HTTPPROXY_USER", "PROXY_USER", "TINYPROXY_USER") const currentKey = "HTTPPROXY_LISTENING_ADDRESS"
if value != "" { key := firstKeySet(s.env, "HTTPPROXY_PORT", "TINYPROXY_PORT", "PROXY_PORT",
return &value currentKey)
} switch key {
return nil case "":
} return "", nil
case currentKey:
func (s *Source) readHTTProxyPassword() (user *string) { return s.env.String(key), nil
_, value := s.getEnvWithRetro("HTTPPROXY_PASSWORD", "PROXY_PASSWORD", "TINYPROXY_PASSWORD")
if value != "" {
return &value
}
return nil
}
func (s *Source) readHTTProxyListeningAddress() (listeningAddress string) {
key, value := s.getEnvWithRetro("HTTPPROXY_LISTENING_ADDRESS", "PROXY_PORT", "TINYPROXY_PORT", "HTTPPROXY_PORT")
if key == "HTTPPROXY_LISTENING_ADDRESS" {
return value
}
return ":" + value
}
func (s *Source) readHTTProxyEnabled() (enabled *bool, err error) {
key, value := s.getEnvWithRetro("HTTPPROXY", "PROXY", "TINYPROXY")
if value == "" {
return nil, nil //nolint:nilnil
} }
enabled = new(bool) // Retro-compatible keys using a port only
*enabled, err = binary.Validate(value) s.handleDeprecatedKey(key, currentKey)
port, err := s.env.Uint16Ptr(key)
if err != nil { if err != nil {
return nil, fmt.Errorf("environment variable %s: %w", key, err) return "", err
} }
return fmt.Sprintf(":%d", *port), nil
return enabled, nil
} }
func (s *Source) readHTTProxyLog() (enabled *bool, err error) { func (s *Source) readHTTProxyLog() (enabled *bool, err error) {
key, value := s.getEnvWithRetro("HTTPPROXY_LOG", "PROXY_LOG_LEVEL", "TINYPROXY_LOG") const currentKey = "HTTPPROXY_LOG"
if value == "" { key := firstKeySet(s.env, "PROXY_LOG", "TINYPROXY_LOG", "HTTPPROXY_LOG")
switch key {
case "":
return nil, nil //nolint:nilnil return nil, nil //nolint:nilnil
case currentKey:
return s.env.BoolPtr(key)
} }
var binaryOptions []binary.Option // Retro-compatible keys using different boolean verbs
if key != "HTTPROXY_LOG" { s.handleDeprecatedKey(key, currentKey)
retroOption := binary.OptionEnabled("on", "info", "connect", "notice") value := s.env.String(key)
binaryOptions = append(binaryOptions, retroOption) retroOption := binary.OptionEnabled("on", "info", "connect", "notice")
}
enabled = new(bool) enabled, err = binary.Validate(value, retroOption)
*enabled, err = binary.Validate(value, 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

@@ -9,8 +9,8 @@ import (
"github.com/qdm12/log" "github.com/qdm12/log"
) )
func readLog() (log settings.Log, err error) { func (s *Source) readLog() (log settings.Log, err error) {
log.Level, err = readLogLevel() log.Level, err = s.readLogLevel()
if err != nil { if err != nil {
return log, err return log, err
} }
@@ -18,14 +18,14 @@ func readLog() (log settings.Log, err error) {
return log, nil return log, nil
} }
func readLogLevel() (level *log.Level, err error) { func (s *Source) readLogLevel() (level *log.Level, err error) {
s := getCleanedEnv("LOG_LEVEL") value := s.env.String("LOG_LEVEL")
if s == "" { if value == "" {
return nil, nil //nolint:nilnil return nil, nil //nolint:nilnil
} }
level = new(log.Level) level = new(log.Level)
*level, err = parseLogLevel(s) *level, err = parseLogLevel(value)
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)
} }

View File

@@ -1,11 +1,10 @@
package env package env
import ( import (
"fmt"
"strings" "strings"
"github.com/qdm12/gluetun/internal/configuration/settings" "github.com/qdm12/gluetun/internal/configuration/settings"
"github.com/qdm12/govalid/binary" "github.com/qdm12/gosettings/sources/env"
) )
func (s *Source) readOpenVPN() ( func (s *Source) readOpenVPN() (
@@ -15,113 +14,64 @@ func (s *Source) readOpenVPN() (
"OPENVPN_KEY_PASSPHRASE", "OPENVPN_ENCRYPTED_KEY"}, err) "OPENVPN_KEY_PASSPHRASE", "OPENVPN_ENCRYPTED_KEY"}, err)
}() }()
openVPN.Version = getCleanedEnv("OPENVPN_VERSION") openVPN.Version = s.env.String("OPENVPN_VERSION")
openVPN.User = s.readOpenVPNUser() openVPN.User = s.env.Get("OPENVPN_USER",
openVPN.Password = s.readOpenVPNPassword() env.RetroKeys("USER"), env.ForceLowercase(false))
confFile := getCleanedEnv("OPENVPN_CUSTOM_CONFIG") openVPN.Password = s.env.Get("OPENVPN_PASSWORD",
if confFile != "" { env.RetroKeys("PASSWORD"), env.ForceLowercase(false))
openVPN.ConfFile = &confFile openVPN.ConfFile = s.env.Get("OPENVPN_CUSTOM_CONFIG", env.ForceLowercase(false))
} openVPN.Ciphers = s.env.CSV("OPENVPN_CIPHERS", env.RetroKeys("OPENVPN_CIPHER"))
openVPN.Auth = s.env.Get("OPENVPN_AUTH")
ciphersKey, _ := s.getEnvWithRetro("OPENVPN_CIPHERS", "OPENVPN_CIPHER") openVPN.Cert = s.env.Get("OPENVPN_CERT", env.ForceLowercase(false))
openVPN.Ciphers = envToCSV(ciphersKey) openVPN.Key = s.env.Get("OPENVPN_KEY", env.ForceLowercase(false))
openVPN.EncryptedKey = s.env.Get("OPENVPN_ENCRYPTED_KEY", env.ForceLowercase(false))
auth := getCleanedEnv("OPENVPN_AUTH") openVPN.KeyPassphrase = s.env.Get("OPENVPN_KEY_PASSPHRASE", env.ForceLowercase(false))
if auth != "" {
openVPN.Auth = &auth
}
openVPN.Cert = envToStringPtr("OPENVPN_CERT")
openVPN.Key = envToStringPtr("OPENVPN_KEY")
openVPN.EncryptedKey = envToStringPtr("OPENVPN_ENCRYPTED_KEY")
openVPN.KeyPassphrase = s.readOpenVPNKeyPassphrase()
openVPN.PIAEncPreset = s.readPIAEncryptionPreset() openVPN.PIAEncPreset = s.readPIAEncryptionPreset()
openVPN.MSSFix, err = envToUint16Ptr("OPENVPN_MSSFIX") openVPN.MSSFix, err = s.env.Uint16Ptr("OPENVPN_MSSFIX")
if err != nil { if err != nil {
return openVPN, fmt.Errorf("environment variable OPENVPN_MSSFIX: %w", err) return openVPN, err
} }
_, openVPN.Interface = s.getEnvWithRetro("VPN_INTERFACE", "OPENVPN_INTERFACE") openVPN.Interface = s.env.String("VPN_INTERFACE",
env.RetroKeys("OPENVPN_INTERFACE"), env.ForceLowercase(false))
openVPN.ProcessUser, err = s.readOpenVPNProcessUser() openVPN.ProcessUser, err = s.readOpenVPNProcessUser()
if err != nil { if err != nil {
return openVPN, err return openVPN, err
} }
openVPN.Verbosity, err = envToIntPtr("OPENVPN_VERBOSITY") openVPN.Verbosity, err = s.env.IntPtr("OPENVPN_VERBOSITY")
if err != nil { if err != nil {
return openVPN, fmt.Errorf("environment variable OPENVPN_VERBOSITY: %w", err) return openVPN, err
} }
flagsStr := getCleanedEnv("OPENVPN_FLAGS") flagsPtr := s.env.Get("OPENVPN_FLAGS", env.ForceLowercase(false))
if flagsStr != "" { if flagsPtr != nil {
openVPN.Flags = strings.Fields(flagsStr) openVPN.Flags = strings.Fields(*flagsPtr)
} }
return openVPN, nil return openVPN, nil
} }
func (s *Source) readOpenVPNUser() (user *string) {
user = new(string)
_, *user = s.getEnvWithRetro("OPENVPN_USER", "USER")
if *user == "" {
return nil
}
// Remove spaces in user ID to simplify user's life, thanks @JeordyR
*user = strings.ReplaceAll(*user, " ", "")
return user
}
func (s *Source) readOpenVPNPassword() (password *string) {
password = new(string)
_, *password = s.getEnvWithRetro("OPENVPN_PASSWORD", "PASSWORD")
if *password == "" {
return nil
}
return password
}
func (s *Source) readOpenVPNKeyPassphrase() (passphrase *string) {
passphrase = new(string)
*passphrase = getCleanedEnv("OPENVPN_KEY_PASSPHRASE")
if *passphrase == "" {
return nil
}
return passphrase
}
func (s *Source) readPIAEncryptionPreset() (presetPtr *string) { func (s *Source) readPIAEncryptionPreset() (presetPtr *string) {
_, preset := s.getEnvWithRetro( return s.env.Get(
"PRIVATE_INTERNET_ACCESS_OPENVPN_ENCRYPTION_PRESET", "PRIVATE_INTERNET_ACCESS_OPENVPN_ENCRYPTION_PRESET",
"PIA_ENCRYPTION", "ENCRYPTION") env.RetroKeys("ENCRYPTION", "PIA_ENCRYPTION"))
if preset != "" {
return &preset
}
return nil
} }
func (s *Source) readOpenVPNProcessUser() (processUser string, err error) { func (s *Source) readOpenVPNProcessUser() (processUser string, err error) {
key, value := s.getEnvWithRetro("OPENVPN_PROCESS_USER", "OPENVPN_ROOT") value, err := s.env.BoolPtr("OPENVPN_ROOT") // Retro-compatibility
if key == "OPENVPN_PROCESS_USER" { if err != nil {
return value, nil return "", err
} else if value != nil {
if *value {
return "root", nil
}
const defaultNonRootUser = "nonrootuser"
return defaultNonRootUser, nil
} }
// Retro-compatibility return s.env.String("OPENVPN_PROCESS_USER"), nil
if value == "" {
return "", nil
}
root, err := binary.Validate(value)
if err != nil {
return "", fmt.Errorf("environment variable %s: %w", key, err)
}
if root {
return "root", nil
}
const defaultNonRootUser = "nonrootuser"
return defaultNonRootUser, nil
} }

View File

@@ -7,22 +7,20 @@ import (
"github.com/qdm12/gluetun/internal/configuration/settings" "github.com/qdm12/gluetun/internal/configuration/settings"
"github.com/qdm12/gluetun/internal/constants" "github.com/qdm12/gluetun/internal/constants"
"github.com/qdm12/govalid/port" "github.com/qdm12/gosettings/sources/env"
) )
func (s *Source) readOpenVPNSelection() ( func (s *Source) readOpenVPNSelection() (
selection settings.OpenVPNSelection, err error) { selection settings.OpenVPNSelection, err error) {
confFile := getCleanedEnv("OPENVPN_CUSTOM_CONFIG") selection.ConfFile = s.env.Get("OPENVPN_CUSTOM_CONFIG", env.ForceLowercase(false))
if confFile != "" {
selection.ConfFile = &confFile
}
selection.TCP, err = s.readOpenVPNProtocol() selection.TCP, err = s.readOpenVPNProtocol()
if err != nil { if err != nil {
return selection, err return selection, err
} }
selection.CustomPort, err = s.readOpenVPNCustomPort() selection.CustomPort, err = s.env.Uint16Ptr("VPN_ENDPOINT_PORT",
env.RetroKeys("PORT", "OPENVPN_PORT"))
if err != nil { if err != nil {
return selection, err return selection, err
} }
@@ -35,32 +33,24 @@ func (s *Source) readOpenVPNSelection() (
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 (s *Source) readOpenVPNProtocol() (tcp *bool, err error) {
envKey, protocol := s.getEnvWithRetro("OPENVPN_PROTOCOL", "PROTOCOL") const currentKey = "OPENVPN_PROTOCOL"
envKey := firstKeySet(s.env, "PROTOCOL", currentKey)
switch strings.ToLower(protocol) { switch envKey {
case "": case "":
return nil, nil //nolint:nilnil return nil, nil //nolint:nilnil
case currentKey:
default: // Retro compatibility
s.handleDeprecatedKey(envKey, currentKey)
}
protocol := s.env.String(envKey)
switch strings.ToLower(protocol) {
case constants.UDP: case constants.UDP:
return boolPtr(false), nil return ptrTo(false), nil
case constants.TCP: case constants.TCP:
return boolPtr(true), nil return ptrTo(true), nil
default: default:
return nil, fmt.Errorf("environment variable %s: %w: %s", return nil, fmt.Errorf("environment variable %s: %w: %s",
envKey, ErrOpenVPNProtocolNotValid, protocol) envKey, ErrOpenVPNProtocolNotValid, protocol)
} }
} }
func (s *Source) readOpenVPNCustomPort() (customPort *uint16, err error) {
key, value := s.getEnvWithRetro("VPN_ENDPOINT_PORT", "PORT", "OPENVPN_PORT")
if value == "" {
return nil, nil //nolint:nilnil
}
customPort = new(uint16)
*customPort, err = port.Validate(value)
if err != nil {
return nil, fmt.Errorf("environment variable %s: %w", key, err)
}
return customPort, nil
}

View File

@@ -1,29 +1,27 @@
package env package env
import ( import (
"fmt"
"github.com/qdm12/gluetun/internal/configuration/settings" "github.com/qdm12/gluetun/internal/configuration/settings"
"github.com/qdm12/gosettings/sources/env"
) )
func (s *Source) readPortForward() ( func (s *Source) readPortForward() (
portForwarding settings.PortForwarding, err error) { portForwarding settings.PortForwarding, err error) {
key, _ := s.getEnvWithRetro( portForwarding.Enabled, err = s.env.BoolPtr("VPN_PORT_FORWARDING",
"VPN_PORT_FORWARDING", env.RetroKeys(
"PRIVATE_INTERNET_ACCESS_VPN_PORT_FORWARDING", "PORT_FORWARDING",
"PORT_FORWARDING") "PRIVATE_INTERNET_ACCESS_VPN_PORT_FORWARDING",
portForwarding.Enabled, err = envToBoolPtr(key) ))
if err != nil { if err != nil {
return portForwarding, fmt.Errorf("environment variable %s: %w", key, err) return portForwarding, err
} }
_, value := s.getEnvWithRetro( portForwarding.Filepath = s.env.Get("VPN_PORT_FORWARDING_STATUS_FILE",
"VPN_PORT_FORWARDING_STATUS_FILE", env.ForceLowercase(false),
"PRIVATE_INTERNET_ACCESS_VPN_PORT_FORWARDING_STATUS_FILE", env.RetroKeys(
"PORT_FORWARDING_STATUS_FILE") "PORT_FORWARDING_STATUS_FILE",
if value != "" { "PRIVATE_INTERNET_ACCESS_VPN_PORT_FORWARDING_STATUS_FILE",
portForwarding.Filepath = stringPtr(value) ))
}
return portForwarding, nil return portForwarding, nil
} }

View File

@@ -1,28 +1,26 @@
package env package env
import ( import (
"fmt"
"github.com/qdm12/gluetun/internal/pprof" "github.com/qdm12/gluetun/internal/pprof"
) )
func readPprof() (settings pprof.Settings, err error) { func (s *Source) readPprof() (settings pprof.Settings, err error) {
settings.Enabled, err = envToBoolPtr("PPROF_ENABLED") settings.Enabled, err = s.env.BoolPtr("PPROF_ENABLED")
if err != nil { if err != nil {
return settings, fmt.Errorf("environment variable PPROF_ENABLED: %w", err) return settings, err
} }
settings.BlockProfileRate, err = envToIntPtr("PPROF_BLOCK_PROFILE_RATE") settings.BlockProfileRate, err = s.env.IntPtr("PPROF_BLOCK_PROFILE_RATE")
if err != nil { if err != nil {
return settings, fmt.Errorf("environment variable PPROF_BLOCK_PROFILE_RATE: %w", err) return settings, err
} }
settings.MutexProfileRate, err = envToIntPtr("PPROF_MUTEX_PROFILE_RATE") settings.MutexProfileRate, err = s.env.IntPtr("PPROF_MUTEX_PROFILE_RATE")
if err != nil { if err != nil {
return settings, fmt.Errorf("environment variable PPROF_MUTEX_PROFILE_RATE: %w", err) return settings, err
} }
settings.HTTPServer.Address = getCleanedEnv("PPROF_HTTP_SERVER_ADDRESS") settings.HTTPServer.Address = s.env.String("PPROF_HTTP_SERVER_ADDRESS")
return settings, nil return settings, nil
} }

View File

@@ -7,6 +7,7 @@ import (
"github.com/qdm12/gluetun/internal/configuration/settings" "github.com/qdm12/gluetun/internal/configuration/settings"
"github.com/qdm12/gluetun/internal/constants/providers" "github.com/qdm12/gluetun/internal/constants/providers"
"github.com/qdm12/gluetun/internal/constants/vpn" "github.com/qdm12/gluetun/internal/constants/vpn"
"github.com/qdm12/gosettings/sources/env"
) )
func (s *Source) readProvider(vpnType string) (provider settings.Provider, err error) { func (s *Source) readProvider(vpnType string) (provider settings.Provider, err error) {
@@ -30,19 +31,20 @@ func (s *Source) readProvider(vpnType string) (provider settings.Provider, err e
} }
func (s *Source) readVPNServiceProvider(vpnType string) (vpnProviderPtr *string) { func (s *Source) readVPNServiceProvider(vpnType string) (vpnProviderPtr *string) {
_, value := s.getEnvWithRetro("VPN_SERVICE_PROVIDER", "VPNSP") valuePtr := s.env.Get("VPN_SERVICE_PROVIDER", env.RetroKeys("VPNSP"))
if value == "" { if valuePtr == nil {
if vpnType != vpn.Wireguard && getCleanedEnv("OPENVPN_CUSTOM_CONFIG") != "" { if vpnType != vpn.Wireguard && s.env.Get("OPENVPN_CUSTOM_CONFIG") != nil {
// retro compatibility // retro compatibility
return stringPtr(providers.Custom) return ptrTo(providers.Custom)
} }
return nil return nil
} }
value := *valuePtr
value = strings.ToLower(value) value = strings.ToLower(value)
if value == "pia" { // retro compatibility if value == "pia" { // retro compatibility
return stringPtr(providers.PrivateInternetAccess) return ptrTo(providers.PrivateInternetAccess)
} }
return stringPtr(value) return ptrTo(value)
} }

View File

@@ -1,42 +1,18 @@
package env package env
import ( import (
"fmt"
"time"
"github.com/qdm12/gluetun/internal/configuration/settings" "github.com/qdm12/gluetun/internal/configuration/settings"
"github.com/qdm12/gosettings/sources/env"
) )
func (s *Source) readPublicIP() (publicIP settings.PublicIP, err error) { func (s *Source) readPublicIP() (publicIP settings.PublicIP, err error) {
publicIP.Period, err = readPublicIPPeriod() publicIP.Period, err = s.env.DurationPtr("PUBLICIP_PERIOD")
if err != nil { if err != nil {
return publicIP, err return publicIP, err
} }
publicIP.IPFilepath = s.readPublicIPFilepath() publicIP.IPFilepath = s.env.Get("PUBLICIP_FILE",
env.ForceLowercase(false), env.RetroKeys("IP_STATUS_FILE"))
return publicIP, nil return publicIP, nil
} }
func readPublicIPPeriod() (period *time.Duration, err error) {
s := getCleanedEnv("PUBLICIP_PERIOD")
if s == "" {
return nil, nil //nolint:nilnil
}
period = new(time.Duration)
*period, err = time.ParseDuration(s)
if err != nil {
return nil, fmt.Errorf("environment variable PUBLICIP_PERIOD: %w", err)
}
return period, nil
}
func (s *Source) readPublicIPFilepath() (filepath *string) {
_, value := s.getEnvWithRetro("PUBLICIP_FILE", "IP_STATUS_FILE")
if value != "" {
return &value
}
return nil
}

View File

@@ -1,11 +1,16 @@
package env package env
import ( import (
"os"
"github.com/qdm12/gluetun/internal/configuration/settings" "github.com/qdm12/gluetun/internal/configuration/settings"
"github.com/qdm12/gosettings/sources/env"
) )
type Source struct { type Source struct {
warner Warner env env.Env
warner Warner
handleDeprecatedKey func(deprecatedKey, newKey string)
} }
type Warner interface { type Warner interface {
@@ -13,8 +18,16 @@ type Warner interface {
} }
func New(warner Warner) *Source { func New(warner Warner) *Source {
handleDeprecatedKey := func(deprecatedKey, newKey string) {
warner.Warn(
"You are using the old environment variable " + deprecatedKey +
", please consider changing it to " + newKey)
}
return &Source{ return &Source{
warner: warner, env: *env.New(os.Environ(), handleDeprecatedKey),
warner: warner,
handleDeprecatedKey: handleDeprecatedKey,
} }
} }
@@ -46,7 +59,7 @@ func (s *Source) Read() (settings settings.Settings, err error) {
return settings, err return settings, err
} }
settings.Log, err = readLog() settings.Log, err = s.readLog()
if err != nil { if err != nil {
return settings, err return settings, err
} }
@@ -56,12 +69,12 @@ func (s *Source) Read() (settings settings.Settings, err error) {
return settings, err return settings, err
} }
settings.Updater, err = readUpdater() settings.Updater, err = s.readUpdater()
if err != nil { if err != nil {
return settings, err return settings, err
} }
settings.Version, err = readVersion() settings.Version, err = s.readVersion()
if err != nil { if err != nil {
return settings, err return settings, err
} }
@@ -81,37 +94,10 @@ func (s *Source) Read() (settings settings.Settings, err error) {
return settings, err return settings, err
} }
settings.Pprof, err = readPprof() settings.Pprof, err = s.readPprof()
if err != nil { if err != nil {
return settings, err return settings, err
} }
return settings, nil return settings, nil
} }
func (s *Source) onRetroActive(oldKey, newKey string) {
s.warner.Warn(
"You are using the old environment variable " + oldKey +
", please consider changing it to " + newKey)
}
// getEnvWithRetro returns the first environment variable
// key and corresponding non empty value from the environment
// variable keys given. It first goes through the retroKeys
// and end on returning the value corresponding to the currentKey.
// Note retroKeys should be in order from oldest to most
// recent retro-compatibility key.
func (s *Source) getEnvWithRetro(currentKey string,
retroKeys ...string) (key, value string) {
// We check retro-compatibility keys first since
// the current key might be set in the Dockerfile.
for _, key = range retroKeys {
value = getCleanedEnv(key)
if value != "" {
s.onRetroActive(key, currentKey)
return key, value
}
}
return currentKey, getCleanedEnv(currentKey)
}

View File

@@ -1,14 +1,11 @@
package env package env
import ( import (
"fmt"
"github.com/qdm12/gluetun/internal/configuration/settings" "github.com/qdm12/gluetun/internal/configuration/settings"
"github.com/qdm12/govalid/binary"
) )
func (s *Source) readControlServer() (controlServer settings.ControlServer, err error) { func (s *Source) readControlServer() (controlServer settings.ControlServer, err error) {
controlServer.Log, err = readControlServerLog() controlServer.Log, err = s.env.BoolPtr("HTTP_CONTROL_SERVER_LOG")
if err != nil { if err != nil {
return controlServer, err return controlServer, err
} }
@@ -18,31 +15,17 @@ func (s *Source) readControlServer() (controlServer settings.ControlServer, err
return controlServer, nil return controlServer, nil
} }
func readControlServerLog() (enabled *bool, err error) {
s := getCleanedEnv("HTTP_CONTROL_SERVER_LOG")
if s == "" {
return nil, nil //nolint:nilnil
}
log, err := binary.Validate(s)
if err != nil {
return nil, fmt.Errorf("environment variable HTTP_CONTROL_SERVER_LOG: %w", err)
}
return &log, nil
}
func (s *Source) readControlServerAddress() (address *string) { func (s *Source) readControlServerAddress() (address *string) {
key, value := s.getEnvWithRetro("HTTP_CONTROL_SERVER_ADDRESS", "HTTP_CONTROL_SERVER_PORT") const currentKey = "HTTP_CONTROL_SERVER_ADDRESS"
if value == "" { key := firstKeySet(s.env, "CONTROL_SERVER_ADDRESS", currentKey)
if key == currentKey {
return s.env.Get(key)
}
s.handleDeprecatedKey(key, currentKey)
value := s.env.Get("CONTROL_SERVER_ADDRESS")
if value == nil {
return nil return nil
} }
return ptrTo(":" + *value)
if key == "HTTP_CONTROL_SERVER_ADDRESS" {
return &value
}
address = new(string)
*address = ":" + value
return address
} }

View File

@@ -2,98 +2,69 @@ package env
import ( import (
"errors" "errors"
"fmt"
"net/netip"
"strconv"
"strings"
"github.com/qdm12/gluetun/internal/configuration/settings" "github.com/qdm12/gluetun/internal/configuration/settings"
"github.com/qdm12/gluetun/internal/constants/providers" "github.com/qdm12/gluetun/internal/constants/providers"
) "github.com/qdm12/gosettings/sources/env"
var (
ErrServerNumberNotValid = errors.New("server number is not valid")
) )
func (s *Source) readServerSelection(vpnProvider, vpnType string) ( func (s *Source) readServerSelection(vpnProvider, vpnType string) (
ss settings.ServerSelection, err error) { ss settings.ServerSelection, err error) {
ss.VPN = vpnType ss.VPN = vpnType
ss.TargetIP, err = s.readOpenVPNTargetIP() ss.TargetIP, err = s.env.NetipAddr("VPN_ENDPOINT_IP",
env.RetroKeys("OPENVPN_TARGET_IP"))
if err != nil { if err != nil {
return ss, err return ss, err
} }
countriesKey, _ := s.getEnvWithRetro("SERVER_COUNTRIES", "COUNTRY") ss.Countries = s.env.CSV("SERVER_COUNTRIES", env.RetroKeys("COUNTRY"))
ss.Countries = envToCSV(countriesKey)
if vpnProvider == providers.Cyberghost && len(ss.Countries) == 0 { if vpnProvider == providers.Cyberghost && len(ss.Countries) == 0 {
// Retro-compatibility for Cyberghost using the REGION variable // Retro-compatibility for Cyberghost using the REGION variable
ss.Countries = envToCSV("REGION") ss.Countries = s.env.CSV("REGION")
if len(ss.Countries) > 0 { if len(ss.Countries) > 0 {
s.onRetroActive("REGION", "SERVER_COUNTRIES") s.handleDeprecatedKey("REGION", "SERVER_COUNTRIES")
} }
} }
regionsKey, _ := s.getEnvWithRetro("SERVER_REGIONS", "REGION") ss.Regions = s.env.CSV("SERVER_REGIONS", env.RetroKeys("REGION"))
ss.Regions = envToCSV(regionsKey) ss.Cities = s.env.CSV("SERVER_CITIES", env.RetroKeys("CITY"))
ss.ISPs = s.env.CSV("ISP")
citiesKey, _ := s.getEnvWithRetro("SERVER_CITIES", "CITY") ss.Hostnames = s.env.CSV("SERVER_HOSTNAMES", env.RetroKeys("SERVER_HOSTNAME"))
ss.Cities = envToCSV(citiesKey) ss.Names = s.env.CSV("SERVER_NAMES", env.RetroKeys("SERVER_NAME"))
ss.Numbers, err = s.env.CSVUint16("SERVER_NUMBER")
ss.ISPs = envToCSV("ISP") if err != nil {
return ss, err
hostnamesKey, _ := s.getEnvWithRetro("SERVER_HOSTNAMES", "SERVER_HOSTNAME")
ss.Hostnames = envToCSV(hostnamesKey)
serverNamesKey, _ := s.getEnvWithRetro("SERVER_NAMES", "SERVER_NAME")
ss.Names = envToCSV(serverNamesKey)
if csv := getCleanedEnv("SERVER_NUMBER"); csv != "" {
numbersStrings := strings.Split(csv, ",")
numbers := make([]uint16, len(numbersStrings))
for i, numberString := range numbersStrings {
const base, bitSize = 10, 16
number, err := strconv.ParseInt(numberString, base, bitSize)
if err != nil {
return ss, fmt.Errorf("%w: %s",
ErrServerNumberNotValid, numberString)
} else if number < 0 || number > 65535 {
return ss, fmt.Errorf("%w: %d must be between 0 and 65535",
ErrServerNumberNotValid, number)
}
numbers[i] = uint16(number)
}
ss.Numbers = numbers
} }
// Mullvad only // Mullvad only
ss.OwnedOnly, err = s.readOwnedOnly() ss.OwnedOnly, err = s.env.BoolPtr("OWNED_ONLY", env.RetroKeys("OWNED"))
if err != nil { if err != nil {
return ss, err return ss, err
} }
// VPNUnlimited and ProtonVPN only // VPNUnlimited and ProtonVPN only
ss.FreeOnly, err = envToBoolPtr("FREE_ONLY") ss.FreeOnly, err = s.env.BoolPtr("FREE_ONLY")
if err != nil { if err != nil {
return ss, fmt.Errorf("environment variable FREE_ONLY: %w", err) return ss, err
} }
// VPNSecure only // VPNSecure only
ss.PremiumOnly, err = envToBoolPtr("PREMIUM_ONLY") ss.PremiumOnly, err = s.env.BoolPtr("PREMIUM_ONLY")
if err != nil { if err != nil {
return ss, fmt.Errorf("environment variable PREMIUM_ONLY: %w", err) return ss, err
} }
// VPNUnlimited only // VPNUnlimited only
ss.MultiHopOnly, err = envToBoolPtr("MULTIHOP_ONLY") ss.MultiHopOnly, err = s.env.BoolPtr("MULTIHOP_ONLY")
if err != nil { if err != nil {
return ss, fmt.Errorf("environment variable MULTIHOP_ONLY: %w", err) return ss, err
} }
// VPNUnlimited only // VPNUnlimited only
ss.MultiHopOnly, err = envToBoolPtr("STREAM_ONLY") ss.MultiHopOnly, err = s.env.BoolPtr("STREAM_ONLY")
if err != nil { if err != nil {
return ss, fmt.Errorf("environment variable STREAM_ONLY: %w", err) return ss, err
} }
ss.OpenVPN, err = s.readOpenVPNSelection() ss.OpenVPN, err = s.readOpenVPNSelection()
@@ -112,26 +83,3 @@ func (s *Source) readServerSelection(vpnProvider, vpnType string) (
var ( var (
ErrInvalidIP = errors.New("invalid IP address") ErrInvalidIP = errors.New("invalid IP address")
) )
func (s *Source) readOpenVPNTargetIP() (ip netip.Addr, err error) {
envKey, value := s.getEnvWithRetro("VPN_ENDPOINT_IP", "OPENVPN_TARGET_IP")
if value == "" {
return ip, nil
}
ip, err = netip.ParseAddr(value)
if err != nil {
return ip, fmt.Errorf("environment variable %s: %w", envKey, err)
}
return ip, nil
}
func (s *Source) readOwnedOnly() (ownedOnly *bool, err error) {
envKey, _ := s.getEnvWithRetro("OWNED_ONLY", "OWNED")
ownedOnly, err = envToBoolPtr(envKey)
if err != nil {
return nil, fmt.Errorf("environment variable %s: %w", envKey, err)
}
return ownedOnly, nil
}

View File

@@ -2,43 +2,41 @@ package env
import ( import (
"fmt" "fmt"
"strings"
"github.com/qdm12/gluetun/internal/configuration/settings" "github.com/qdm12/gluetun/internal/configuration/settings"
"github.com/qdm12/gosettings/sources/env"
) )
func (s *Source) readShadowsocks() (shadowsocks settings.Shadowsocks, err error) { func (s *Source) readShadowsocks() (shadowsocks settings.Shadowsocks, err error) {
shadowsocks.Enabled, err = envToBoolPtr("SHADOWSOCKS") shadowsocks.Enabled, err = s.env.BoolPtr("SHADOWSOCKS")
if err != nil { if err != nil {
return shadowsocks, fmt.Errorf("environment variable SHADOWSOCKS: %w", err) return shadowsocks, err
} }
shadowsocks.Address = s.readShadowsocksAddress() shadowsocks.Address, err = s.readShadowsocksAddress()
shadowsocks.LogAddresses, err = envToBoolPtr("SHADOWSOCKS_LOG")
if err != nil { if err != nil {
return shadowsocks, fmt.Errorf("environment variable SHADOWSOCKS_LOG: %w", err) return shadowsocks, err
} }
shadowsocks.CipherName = s.readShadowsocksCipher() shadowsocks.LogAddresses, err = s.env.BoolPtr("SHADOWSOCKS_LOG")
shadowsocks.Password = envToStringPtr("SHADOWSOCKS_PASSWORD") if err != nil {
return shadowsocks, err
}
shadowsocks.CipherName = s.env.String("SHADOWSOCKS_CIPHER",
env.RetroKeys("SHADOWSOCKS_METHOD"))
shadowsocks.Password = s.env.Get("SHADOWSOCKS_PASSWORD", env.ForceLowercase(false))
return shadowsocks, nil return shadowsocks, nil
} }
func (s *Source) readShadowsocksAddress() (address string) { func (s *Source) readShadowsocksAddress() (address *string, err error) {
key, value := s.getEnvWithRetro("SHADOWSOCKS_LISTENING_ADDRESS", "SHADOWSOCKS_PORT") const currentKey = "SHADOWSOCKS_LISTENING_ADDRESS"
if value == "" { port, err := s.env.Uint16Ptr("SHADOWSOCKS_PORT") // retro-compatibility
return "" if err != nil {
return nil, err
} else if port != nil {
s.handleDeprecatedKey("SHADOWSOCKS_PORT", currentKey)
return ptrTo(fmt.Sprintf(":%d", *port)), nil
} }
if key == "SHADOWSOCKS_LISTENING_ADDRESS" { return s.env.Get(currentKey), nil
return value
}
// Retro-compatibility
return ":" + value
}
func (s *Source) readShadowsocksCipher() (cipher string) {
_, cipher = s.getEnvWithRetro("SHADOWSOCKS_CIPHER", "SHADOWSOCKS_METHOD")
return strings.ToLower(cipher)
} }

View File

@@ -1,55 +1,22 @@
package env package env
import ( import (
"errors"
"fmt"
"strconv"
"github.com/qdm12/gluetun/internal/configuration/settings" "github.com/qdm12/gluetun/internal/configuration/settings"
) "github.com/qdm12/gosettings/sources/env"
var (
ErrSystemPUIDNotValid = errors.New("PUID is not valid")
ErrSystemPGIDNotValid = errors.New("PGID is not valid")
ErrSystemTimezoneNotValid = errors.New("timezone is not valid")
) )
func (s *Source) readSystem() (system settings.System, err error) { func (s *Source) readSystem() (system settings.System, err error) {
system.PUID, err = s.readID("PUID", "UID") system.PUID, err = s.env.Uint32Ptr("PUID", env.RetroKeys("UID"))
if err != nil { if err != nil {
return system, err return system, err
} }
system.PGID, err = s.readID("PGID", "GID") system.PGID, err = s.env.Uint32Ptr("PGID", env.RetroKeys("GID"))
if err != nil { if err != nil {
return system, err return system, err
} }
system.Timezone = getCleanedEnv("TZ") system.Timezone = s.env.String("TZ")
return system, nil return system, nil
} }
var ErrSystemIDNotValid = errors.New("system ID is not valid")
func (s *Source) readID(key, retroKey string) (
id *uint32, err error) {
idEnvKey, idString := s.getEnvWithRetro(key, retroKey)
if idString == "" {
return nil, nil //nolint:nilnil
}
const base = 10
const bitSize = 64
const max = uint64(^uint32(0))
idUint64, err := strconv.ParseUint(idString, base, bitSize)
if err != nil {
return nil, fmt.Errorf("environment variable %s: %w: %s",
idEnvKey, ErrSystemIDNotValid, err)
} else if idUint64 > max {
return nil, fmt.Errorf("environment variable %s: %w: %d: must be between 0 and %d",
idEnvKey, ErrSystemIDNotValid, idUint64, max)
}
return uint32Ptr(uint32(idUint64)), nil
}

View File

@@ -1,88 +0,0 @@
package env
import (
"testing"
"github.com/stretchr/testify/assert"
)
func Test_Reader_readID(t *testing.T) {
t.Parallel()
testCases := map[string]struct {
keyPrefix string
keyValue string
retroKeyPrefix string
retroValue string
id *uint32
errWrapped error
errMessage string
}{
"empty string": {
keyPrefix: "ID",
retroKeyPrefix: "RETRO_ID",
},
"invalid string": {
keyPrefix: "ID",
keyValue: "invalid",
retroKeyPrefix: "RETRO_ID",
errWrapped: ErrSystemIDNotValid,
errMessage: `environment variable IDTest_Reader_readID/invalid_string: ` +
`system ID is not valid: ` +
`strconv.ParseUint: parsing "invalid": invalid syntax`,
},
"negative number": {
keyPrefix: "ID",
keyValue: "-1",
retroKeyPrefix: "RETRO_ID",
errWrapped: ErrSystemIDNotValid,
errMessage: `environment variable IDTest_Reader_readID/negative_number: ` +
`system ID is not valid: ` +
`strconv.ParseUint: parsing "-1": invalid syntax`,
},
"id 1000": {
keyPrefix: "ID",
keyValue: "1000",
retroKeyPrefix: "RETRO_ID",
id: uint32Ptr(1000),
},
"max id": {
keyPrefix: "ID",
keyValue: "4294967295",
retroKeyPrefix: "RETRO_ID",
id: uint32Ptr(4294967295),
},
"above max id": {
keyPrefix: "ID",
keyValue: "4294967296",
retroKeyPrefix: "RETRO_ID",
errWrapped: ErrSystemIDNotValid,
errMessage: `environment variable IDTest_Reader_readID/above_max_id: ` +
`system ID is not valid: 4294967296: must be between 0 and 4294967295`,
},
}
for name, testCase := range testCases {
testCase := testCase
t.Run(name, func(t *testing.T) {
t.Parallel()
suffix := t.Name()
key := testCase.keyPrefix + suffix
retroKey := testCase.retroKeyPrefix + suffix
setTestEnv(t, key, testCase.keyValue)
setTestEnv(t, retroKey, testCase.retroValue)
source := &Source{}
id, err := source.readID(key, retroKey)
assert.ErrorIs(t, err, testCase.errWrapped)
if err != nil {
assert.EqualError(t, err, testCase.errMessage)
}
assert.Equal(t, testCase.id, id)
})
}
}

View File

@@ -1,37 +1,35 @@
package env package env
import ( import (
"fmt"
"github.com/qdm12/gluetun/internal/configuration/settings" "github.com/qdm12/gluetun/internal/configuration/settings"
) )
func readUnbound() (unbound settings.Unbound, err error) { func (s *Source) readUnbound() (unbound settings.Unbound, err error) {
unbound.Providers = envToCSV("DOT_PROVIDERS") unbound.Providers = s.env.CSV("DOT_PROVIDERS")
unbound.Caching, err = envToBoolPtr("DOT_CACHING") unbound.Caching, err = s.env.BoolPtr("DOT_CACHING")
if err != nil { if err != nil {
return unbound, fmt.Errorf("environment variable DOT_CACHING: %w", err) return unbound, err
} }
unbound.IPv6, err = envToBoolPtr("DOT_IPV6") unbound.IPv6, err = s.env.BoolPtr("DOT_IPV6")
if err != nil { if err != nil {
return unbound, fmt.Errorf("environment variable DOT_IPV6: %w", err) return unbound, err
} }
unbound.VerbosityLevel, err = envToUint8Ptr("DOT_VERBOSITY") unbound.VerbosityLevel, err = s.env.Uint8Ptr("DOT_VERBOSITY")
if err != nil { if err != nil {
return unbound, fmt.Errorf("environment variable DOT_VERBOSITY: %w", err) return unbound, err
} }
unbound.VerbosityDetailsLevel, err = envToUint8Ptr("DOT_VERBOSITY_DETAILS") unbound.VerbosityDetailsLevel, err = s.env.Uint8Ptr("DOT_VERBOSITY_DETAILS")
if err != nil { if err != nil {
return unbound, fmt.Errorf("environment variable DOT_VERBOSITY_DETAILS: %w", err) return unbound, err
} }
unbound.ValidationLogLevel, err = envToUint8Ptr("DOT_VALIDATION_LOGLEVEL") unbound.ValidationLogLevel, err = s.env.Uint8Ptr("DOT_VALIDATION_LOGLEVEL")
if err != nil { if err != nil {
return unbound, fmt.Errorf("environment variable DOT_VALIDATION_LOGLEVEL: %w", err) return unbound, err
} }
return unbound, nil return unbound, nil

View File

@@ -1,14 +1,11 @@
package env package env
import ( import (
"fmt"
"time"
"github.com/qdm12/gluetun/internal/configuration/settings" "github.com/qdm12/gluetun/internal/configuration/settings"
) )
func readUpdater() (updater settings.Updater, err error) { func (s *Source) readUpdater() (updater settings.Updater, err error) {
updater.Period, err = readUpdaterPeriod() updater.Period, err = s.env.DurationPtr("UPDATER_PERIOD")
if err != nil { if err != nil {
return updater, err return updater, err
} }
@@ -18,29 +15,16 @@ func readUpdater() (updater settings.Updater, err error) {
return updater, err return updater, err
} }
updater.MinRatio, err = envToFloat64("UPDATER_MIN_RATIO") updater.MinRatio, err = s.env.Float64("UPDATER_MIN_RATIO")
if err != nil { if err != nil {
return updater, fmt.Errorf("environment variable UPDATER_MIN_RATIO: %w", err) return updater, err
} }
updater.Providers = envToCSV("UPDATER_VPN_SERVICE_PROVIDERS") updater.Providers = s.env.CSV("UPDATER_VPN_SERVICE_PROVIDERS")
return updater, nil return updater, nil
} }
func readUpdaterPeriod() (period *time.Duration, err error) {
s := getCleanedEnv("UPDATER_PERIOD")
if s == "" {
return nil, nil //nolint:nilnil
}
period = new(time.Duration)
*period, err = time.ParseDuration(s)
if err != nil {
return nil, fmt.Errorf("environment variable UPDATER_PERIOD: %w", err)
}
return period, nil
}
func readUpdaterDNSAddress() (address string, err error) { func readUpdaterDNSAddress() (address string, err error) {
// TODO this is currently using Cloudflare in // TODO this is currently using Cloudflare in
// plaintext to not be blocked by DNS over TLS by default. // plaintext to not be blocked by DNS over TLS by default.

View File

@@ -1,32 +1,14 @@
package env package env
import ( import (
"fmt"
"github.com/qdm12/gluetun/internal/configuration/settings" "github.com/qdm12/gluetun/internal/configuration/settings"
"github.com/qdm12/govalid/binary"
) )
func readVersion() (version settings.Version, err error) { func (s *Source) readVersion() (version settings.Version, err error) {
version.Enabled, err = readVersionEnabled() version.Enabled, err = s.env.BoolPtr("VERSION_INFORMATION")
if err != nil { if err != nil {
return version, err return version, err
} }
return version, nil return version, nil
} }
func readVersionEnabled() (enabled *bool, err error) {
s := getCleanedEnv("VERSION_INFORMATION")
if s == "" {
return nil, nil //nolint:nilnil
}
enabled = new(bool)
*enabled, err = binary.Validate(s)
if err != nil {
return nil, fmt.Errorf("environment variable VERSION_INFORMATION: %w", err)
}
return enabled, nil
}

View File

@@ -2,13 +2,12 @@ package env
import ( import (
"fmt" "fmt"
"strings"
"github.com/qdm12/gluetun/internal/configuration/settings" "github.com/qdm12/gluetun/internal/configuration/settings"
) )
func (s *Source) readVPN() (vpn settings.VPN, err error) { func (s *Source) readVPN() (vpn settings.VPN, err error) {
vpn.Type = strings.ToLower(getCleanedEnv("VPN_TYPE")) vpn.Type = s.env.String("VPN_TYPE")
vpn.Provider, err = s.readProvider(vpn.Type) vpn.Provider, err = s.readProvider(vpn.Type)
if err != nil { if err != nil {

View File

@@ -1,44 +1,29 @@
package env package env
import ( import (
"fmt"
"net/netip"
"os"
"strings"
"github.com/qdm12/gluetun/internal/configuration/settings" "github.com/qdm12/gluetun/internal/configuration/settings"
"github.com/qdm12/gosettings/sources/env"
) )
func (s *Source) readWireguard() (wireguard settings.Wireguard, err error) { func (s *Source) readWireguard() (wireguard settings.Wireguard, err error) {
defer func() { defer func() {
err = unsetEnvKeys([]string{"WIREGUARD_PRIVATE_KEY", "WIREGUARD_PRESHARED_KEY"}, err) err = unsetEnvKeys([]string{"WIREGUARD_PRIVATE_KEY", "WIREGUARD_PRESHARED_KEY"}, err)
}() }()
wireguard.PrivateKey = envToStringPtr("WIREGUARD_PRIVATE_KEY") wireguard.PrivateKey = s.env.Get("WIREGUARD_PRIVATE_KEY", env.ForceLowercase(false))
wireguard.PreSharedKey = envToStringPtr("WIREGUARD_PRESHARED_KEY") wireguard.PreSharedKey = s.env.Get("WIREGUARD_PRESHARED_KEY", env.ForceLowercase(false))
_, wireguard.Interface = s.getEnvWithRetro("VPN_INTERFACE", "WIREGUARD_INTERFACE") wireguard.Interface = s.env.String("VPN_INTERFACE",
wireguard.Implementation = os.Getenv("WIREGUARD_IMPLEMENTATION") env.RetroKeys("WIREGUARD_INTERFACE"), env.ForceLowercase(false))
wireguard.Addresses, err = s.readWireguardAddresses() wireguard.Implementation = s.env.String("WIREGUARD_IMPLEMENTATION")
wireguard.Addresses, err = s.env.CSVNetipPrefixes("WIREGUARD_ADDRESSES",
env.RetroKeys("WIREGUARD_ADDRESS"))
if err != nil { if err != nil {
return wireguard, err // already wrapped return wireguard, err // already wrapped
} }
mtuPtr, err := s.env.Uint16Ptr("WIREGUARD_MTU")
if err != nil {
return wireguard, err
} else if mtuPtr != nil {
wireguard.MTU = *mtuPtr
}
return wireguard, nil return wireguard, nil
} }
func (s *Source) readWireguardAddresses() (addresses []netip.Prefix, err error) {
key, addressesCSV := s.getEnvWithRetro("WIREGUARD_ADDRESSES", "WIREGUARD_ADDRESS")
if addressesCSV == "" {
return nil, nil
}
addressStrings := strings.Split(addressesCSV, ",")
addresses = make([]netip.Prefix, len(addressStrings))
for i, addressString := range addressStrings {
addressString = strings.TrimSpace(addressString)
addresses[i], err = netip.ParsePrefix(addressString)
if err != nil {
return nil, fmt.Errorf("environment variable %s: %w", key, err)
}
}
return addresses, nil
}

View File

@@ -1,55 +1,23 @@
package env package env
import ( import (
"fmt"
"net/netip"
"github.com/qdm12/gluetun/internal/configuration/settings" "github.com/qdm12/gluetun/internal/configuration/settings"
"github.com/qdm12/govalid/port" "github.com/qdm12/gosettings/sources/env"
) )
func (s *Source) readWireguardSelection() ( func (s *Source) readWireguardSelection() (
selection settings.WireguardSelection, err error) { selection settings.WireguardSelection, err error) {
selection.EndpointIP, err = s.readWireguardEndpointIP() selection.EndpointIP, err = s.env.NetipAddr("VPN_ENDPOINT_IP", env.RetroKeys("WIREGUARD_ENDPOINT_IP"))
if err != nil { if err != nil {
return selection, err return selection, err
} }
selection.EndpointPort, err = s.readWireguardCustomPort() selection.EndpointPort, err = s.env.Uint16Ptr("VPN_ENDPOINT_PORT", env.RetroKeys("WIREGUARD_ENDPOINT_PORT"))
if err != nil { if err != nil {
return selection, err return selection, err
} }
selection.PublicKey = getCleanedEnv("WIREGUARD_PUBLIC_KEY") selection.PublicKey = s.env.String("WIREGUARD_PUBLIC_KEY", env.ForceLowercase(false))
return selection, nil return selection, nil
} }
func (s *Source) readWireguardEndpointIP() (endpointIP netip.Addr, err error) {
key, value := s.getEnvWithRetro("VPN_ENDPOINT_IP", "WIREGUARD_ENDPOINT_IP")
if value == "" {
return endpointIP, nil
}
endpointIP, err = netip.ParseAddr(value)
if err != nil {
return endpointIP, fmt.Errorf("environment variable %s: %w", key, err)
}
return endpointIP, nil
}
func (s *Source) readWireguardCustomPort() (customPort *uint16, err error) {
key, value := s.getEnvWithRetro("VPN_ENDPOINT_PORT", "WIREGUARD_ENDPOINT_PORT")
if value == "" {
return nil, nil //nolint:nilnil
}
customPort = new(uint16)
*customPort, err = port.Validate(value)
if err != nil {
return nil, fmt.Errorf("environment variable %s: %w", key, err)
}
return customPort, nil
}

View File

@@ -2,35 +2,24 @@ package secrets
import ( import (
"fmt" "fmt"
"os"
"strings"
"github.com/qdm12/gluetun/internal/configuration/sources/files" "github.com/qdm12/gluetun/internal/configuration/sources/files"
"github.com/qdm12/gluetun/internal/openvpn/extract" "github.com/qdm12/gluetun/internal/openvpn/extract"
"github.com/qdm12/gosettings/sources/env"
) )
// getCleanedEnv returns an environment variable value with func (s *Source) readSecretFileAsStringPtr(secretPathEnvKey, defaultSecretPath string) (
// 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 readSecretFileAsStringPtr(secretPathEnvKey, defaultSecretPath string) (
stringPtr *string, err error) { stringPtr *string, err error) {
path := getCleanedEnv(secretPathEnvKey) path := s.env.String(secretPathEnvKey, env.ForceLowercase(false))
if path == "" { if path == "" {
path = defaultSecretPath path = defaultSecretPath
} }
return files.ReadFromFile(path) return files.ReadFromFile(path)
} }
func readPEMSecretFile(secretPathEnvKey, defaultSecretPath string) ( func (s *Source) readPEMSecretFile(secretPathEnvKey, defaultSecretPath string) (
base64Ptr *string, err error) { base64Ptr *string, err error) {
pemData, err := readSecretFileAsStringPtr(secretPathEnvKey, defaultSecretPath) pemData, err := s.readSecretFileAsStringPtr(secretPathEnvKey, defaultSecretPath)
if err != nil { if err != nil {
return nil, fmt.Errorf("reading secret file: %w", err) return nil, fmt.Errorf("reading secret file: %w", err)
} }

View File

@@ -0,0 +1,92 @@
package secrets
import (
"os"
"path/filepath"
"testing"
"github.com/qdm12/gosettings/sources/env"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func ptrTo[T any](value T) *T { return &value }
func Test_readSecretFileAsStringPtr(t *testing.T) {
t.Parallel()
testCases := map[string]struct {
source func(tempDir string) Source
secretPathEnvKey string
defaultSecretFileName string
setupFile func(tempDir string) error
stringPtr *string
errWrapped error
errMessage string
}{
"no_secret_file": {
defaultSecretFileName: "default_secret_file",
secretPathEnvKey: "SECRET_FILE",
},
"empty_secret_file": {
defaultSecretFileName: "default_secret_file",
secretPathEnvKey: "SECRET_FILE",
setupFile: func(tempDir string) error {
secretFilepath := filepath.Join(tempDir, "default_secret_file")
return os.WriteFile(secretFilepath, nil, os.ModePerm)
},
stringPtr: ptrTo(""),
},
"default_secret_file": {
defaultSecretFileName: "default_secret_file",
secretPathEnvKey: "SECRET_FILE",
setupFile: func(tempDir string) error {
secretFilepath := filepath.Join(tempDir, "default_secret_file")
return os.WriteFile(secretFilepath, []byte("A"), os.ModePerm)
},
stringPtr: ptrTo("A"),
},
"env_specified_secret_file": {
source: func(tempDir string) Source {
secretFilepath := filepath.Join(tempDir, "secret_file")
environ := []string{"SECRET_FILE=" + secretFilepath}
return Source{env: *env.New(environ, nil)}
},
defaultSecretFileName: "default_secret_file",
secretPathEnvKey: "SECRET_FILE",
setupFile: func(tempDir string) error {
secretFilepath := filepath.Join(tempDir, "secret_file")
return os.WriteFile(secretFilepath, []byte("B"), os.ModePerm)
},
stringPtr: ptrTo("B"),
},
}
for name, testCase := range testCases {
testCase := testCase
t.Run(name, func(t *testing.T) {
t.Parallel()
tempDir := t.TempDir()
var source Source
if testCase.source != nil {
source = testCase.source(tempDir)
}
defaultSecretPath := filepath.Join(tempDir, testCase.defaultSecretFileName)
if testCase.setupFile != nil {
err := testCase.setupFile(tempDir)
require.NoError(t, err)
}
stringPtr, err := source.readSecretFileAsStringPtr(
testCase.secretPathEnvKey, defaultSecretPath)
assert.Equal(t, testCase.stringPtr, stringPtr)
assert.ErrorIs(t, err, testCase.errWrapped)
if testCase.errWrapped != nil {
assert.EqualError(t, err, testCase.errMessage)
}
})
}
}

View File

@@ -6,8 +6,8 @@ import (
"github.com/qdm12/gluetun/internal/configuration/settings" "github.com/qdm12/gluetun/internal/configuration/settings"
) )
func readHTTPProxy() (settings settings.HTTPProxy, err error) { func (s *Source) readHTTPProxy() (settings settings.HTTPProxy, err error) {
settings.User, err = readSecretFileAsStringPtr( settings.User, err = s.readSecretFileAsStringPtr(
"HTTPPROXY_USER_SECRETFILE", "HTTPPROXY_USER_SECRETFILE",
"/run/secrets/httpproxy_user", "/run/secrets/httpproxy_user",
) )
@@ -15,12 +15,12 @@ func readHTTPProxy() (settings settings.HTTPProxy, err error) {
return settings, fmt.Errorf("reading HTTP proxy user secret file: %w", err) return settings, fmt.Errorf("reading HTTP proxy user secret file: %w", err)
} }
settings.Password, err = readSecretFileAsStringPtr( settings.Password, err = s.readSecretFileAsStringPtr(
"HTTPPROXY_PASSWORD_SECRETFILE", "HTTPPROXY_PASSWORD_SECRETFILE",
"/run/secrets/httpproxy_password", "/run/secrets/httpproxy_password",
) )
if err != nil { if err != nil {
return settings, fmt.Errorf("reading OpenVPN password secret file: %w", err) return settings, fmt.Errorf("reading HTTP proxy password secret file: %w", err)
} }
return settings, nil return settings, nil

View File

@@ -6,9 +6,9 @@ import (
"github.com/qdm12/gluetun/internal/configuration/settings" "github.com/qdm12/gluetun/internal/configuration/settings"
) )
func readOpenVPN() ( func (s *Source) readOpenVPN() (
settings settings.OpenVPN, err error) { settings settings.OpenVPN, err error) {
settings.User, err = readSecretFileAsStringPtr( settings.User, err = s.readSecretFileAsStringPtr(
"OPENVPN_USER_SECRETFILE", "OPENVPN_USER_SECRETFILE",
"/run/secrets/openvpn_user", "/run/secrets/openvpn_user",
) )
@@ -16,7 +16,7 @@ func readOpenVPN() (
return settings, fmt.Errorf("reading user file: %w", err) return settings, fmt.Errorf("reading user file: %w", err)
} }
settings.Password, err = readSecretFileAsStringPtr( settings.Password, err = s.readSecretFileAsStringPtr(
"OPENVPN_PASSWORD_SECRETFILE", "OPENVPN_PASSWORD_SECRETFILE",
"/run/secrets/openvpn_password", "/run/secrets/openvpn_password",
) )
@@ -24,7 +24,7 @@ func readOpenVPN() (
return settings, fmt.Errorf("reading password file: %w", err) return settings, fmt.Errorf("reading password file: %w", err)
} }
settings.Key, err = readPEMSecretFile( settings.Key, err = s.readPEMSecretFile(
"OPENVPN_CLIENTKEY_SECRETFILE", "OPENVPN_CLIENTKEY_SECRETFILE",
"/run/secrets/openvpn_clientkey", "/run/secrets/openvpn_clientkey",
) )
@@ -32,7 +32,7 @@ func readOpenVPN() (
return settings, fmt.Errorf("reading client key file: %w", err) return settings, fmt.Errorf("reading client key file: %w", err)
} }
settings.EncryptedKey, err = readPEMSecretFile( settings.EncryptedKey, err = s.readPEMSecretFile(
"OPENVPN_ENCRYPTED_KEY_SECRETFILE", "OPENVPN_ENCRYPTED_KEY_SECRETFILE",
"/run/secrets/openvpn_encrypted_key", "/run/secrets/openvpn_encrypted_key",
) )
@@ -40,7 +40,7 @@ func readOpenVPN() (
return settings, fmt.Errorf("reading encrypted key file: %w", err) return settings, fmt.Errorf("reading encrypted key file: %w", err)
} }
settings.KeyPassphrase, err = readSecretFileAsStringPtr( settings.KeyPassphrase, err = s.readSecretFileAsStringPtr(
"OPENVPN_KEY_PASSPHRASE_SECRETFILE", "OPENVPN_KEY_PASSPHRASE_SECRETFILE",
"/run/secrets/openvpn_key_passphrase", "/run/secrets/openvpn_key_passphrase",
) )
@@ -48,7 +48,7 @@ func readOpenVPN() (
return settings, fmt.Errorf("reading key passphrase file: %w", err) return settings, fmt.Errorf("reading key passphrase file: %w", err)
} }
settings.Cert, err = readPEMSecretFile( settings.Cert, err = s.readPEMSecretFile(
"OPENVPN_CLIENTCRT_SECRETFILE", "OPENVPN_CLIENTCRT_SECRETFILE",
"/run/secrets/openvpn_clientcrt", "/run/secrets/openvpn_clientcrt",
) )

View File

@@ -1,29 +1,37 @@
package secrets package secrets
import ( import (
"os"
"github.com/qdm12/gluetun/internal/configuration/settings" "github.com/qdm12/gluetun/internal/configuration/settings"
"github.com/qdm12/gosettings/sources/env"
) )
type Source struct{} type Source struct {
env env.Env
}
func New() *Source { func New() *Source {
return &Source{} handleDeprecatedKey := (func(deprecatedKey, newKey string))(nil)
return &Source{
env: *env.New(os.Environ(), handleDeprecatedKey),
}
} }
func (s *Source) String() string { return "secret files" } func (s *Source) String() string { return "secret files" }
func (s *Source) Read() (settings settings.Settings, err error) { func (s *Source) Read() (settings settings.Settings, err error) {
settings.VPN, err = readVPN() settings.VPN, err = s.readVPN()
if err != nil { if err != nil {
return settings, err return settings, err
} }
settings.HTTPProxy, err = readHTTPProxy() settings.HTTPProxy, err = s.readHTTPProxy()
if err != nil { if err != nil {
return settings, err return settings, err
} }
settings.Shadowsocks, err = readShadowsocks() settings.Shadowsocks, err = s.readShadowsocks()
if err != nil { if err != nil {
return settings, err return settings, err
} }

View File

@@ -6,8 +6,8 @@ import (
"github.com/qdm12/gluetun/internal/configuration/settings" "github.com/qdm12/gluetun/internal/configuration/settings"
) )
func readShadowsocks() (settings settings.Shadowsocks, err error) { func (s *Source) readShadowsocks() (settings settings.Shadowsocks, err error) {
settings.Password, err = readSecretFileAsStringPtr( settings.Password, err = s.readSecretFileAsStringPtr(
"SHADOWSOCKS_PASSWORD_SECRETFILE", "SHADOWSOCKS_PASSWORD_SECRETFILE",
"/run/secrets/shadowsocks_password", "/run/secrets/shadowsocks_password",
) )

View File

@@ -6,8 +6,8 @@ import (
"github.com/qdm12/gluetun/internal/configuration/settings" "github.com/qdm12/gluetun/internal/configuration/settings"
) )
func readVPN() (vpn settings.VPN, err error) { func (s *Source) readVPN() (vpn settings.VPN, err error) {
vpn.OpenVPN, err = readOpenVPN() vpn.OpenVPN, err = s.readOpenVPN()
if err != nil { if err != nil {
return vpn, fmt.Errorf("reading OpenVPN settings: %w", err) return vpn, fmt.Errorf("reading OpenVPN settings: %w", err)
} }

View File

@@ -1,6 +1,6 @@
package openvpn package openvpn
const ( const (
Openvpn24 = "2.4"
Openvpn25 = "2.5" Openvpn25 = "2.5"
Openvpn26 = "2.6"
) )

View File

@@ -2,7 +2,6 @@ package httpproxy
import ( import (
"context" "context"
"fmt"
"net/http" "net/http"
"sync" "sync"
"time" "time"
@@ -66,5 +65,8 @@ var hopHeaders = [...]string{ //nolint:gochecknoglobals
// Do not follow redirect, but directly return the redirect response. // Do not follow redirect, but directly return the redirect response.
func returnRedirect(*http.Request, []*http.Request) error { func returnRedirect(*http.Request, []*http.Request) error {
return fmt.Errorf("%w", http.ErrUseLastResponse) // WARNING: do not wrap this error!
// The standard library code checking against it does not use
// Go 1.13 `errors.Is` but `==`, so we cannot wrap it.
return http.ErrUseLastResponse
} }

View File

@@ -0,0 +1,16 @@
package httpproxy
import (
"net/http"
"testing"
"github.com/stretchr/testify/assert"
)
func Test_returnRedirect(t *testing.T) {
t.Parallel()
err := returnRedirect(nil, nil)
assert.Equal(t, http.ErrUseLastResponse, err)
}

View File

@@ -7,7 +7,7 @@ import (
"os" "os"
"time" "time"
"github.com/qdm12/gluetun/internal/configuration/settings/helpers" "github.com/qdm12/gosettings"
"github.com/qdm12/gotree" "github.com/qdm12/gotree"
"github.com/qdm12/govalid/address" "github.com/qdm12/govalid/address"
) )
@@ -34,12 +34,12 @@ type Settings struct {
} }
func (s *Settings) SetDefaults() { func (s *Settings) SetDefaults() {
s.Address = helpers.DefaultString(s.Address, ":8000") s.Address = gosettings.DefaultString(s.Address, ":8000")
const defaultReadTimeout = 3 * time.Second const defaultReadTimeout = 3 * time.Second
s.ReadHeaderTimeout = helpers.DefaultNumber(s.ReadHeaderTimeout, defaultReadTimeout) s.ReadHeaderTimeout = gosettings.DefaultNumber(s.ReadHeaderTimeout, defaultReadTimeout)
s.ReadTimeout = helpers.DefaultNumber(s.ReadTimeout, defaultReadTimeout) s.ReadTimeout = gosettings.DefaultNumber(s.ReadTimeout, defaultReadTimeout)
const defaultShutdownTimeout = 3 * time.Second const defaultShutdownTimeout = 3 * time.Second
s.ShutdownTimeout = helpers.DefaultNumber(s.ShutdownTimeout, defaultShutdownTimeout) s.ShutdownTimeout = gosettings.DefaultNumber(s.ShutdownTimeout, defaultShutdownTimeout)
} }
func (s Settings) Copy() Settings { func (s Settings) Copy() Settings {
@@ -54,25 +54,25 @@ func (s Settings) Copy() Settings {
} }
func (s *Settings) MergeWith(other Settings) { func (s *Settings) MergeWith(other Settings) {
s.Address = helpers.MergeWithString(s.Address, other.Address) s.Address = gosettings.MergeWithString(s.Address, other.Address)
s.Handler = helpers.MergeWithHTTPHandler(s.Handler, other.Handler) s.Handler = gosettings.MergeWithInterface(s.Handler, other.Handler)
if s.Logger == nil { if s.Logger == nil {
s.Logger = other.Logger s.Logger = other.Logger
} }
s.ReadHeaderTimeout = helpers.MergeWithNumber(s.ReadHeaderTimeout, other.ReadHeaderTimeout) s.ReadHeaderTimeout = gosettings.MergeWithNumber(s.ReadHeaderTimeout, other.ReadHeaderTimeout)
s.ReadTimeout = helpers.MergeWithNumber(s.ReadTimeout, other.ReadTimeout) s.ReadTimeout = gosettings.MergeWithNumber(s.ReadTimeout, other.ReadTimeout)
s.ShutdownTimeout = helpers.MergeWithNumber(s.ShutdownTimeout, other.ShutdownTimeout) s.ShutdownTimeout = gosettings.MergeWithNumber(s.ShutdownTimeout, other.ShutdownTimeout)
} }
func (s *Settings) OverrideWith(other Settings) { func (s *Settings) OverrideWith(other Settings) {
s.Address = helpers.OverrideWithString(s.Address, other.Address) s.Address = gosettings.OverrideWithString(s.Address, other.Address)
s.Handler = helpers.OverrideWithHTTPHandler(s.Handler, other.Handler) s.Handler = gosettings.OverrideWithInterface(s.Handler, other.Handler)
if other.Logger != nil { if other.Logger != nil {
s.Logger = other.Logger s.Logger = other.Logger
} }
s.ReadHeaderTimeout = helpers.OverrideWithNumber(s.ReadHeaderTimeout, other.ReadHeaderTimeout) s.ReadHeaderTimeout = gosettings.OverrideWithNumber(s.ReadHeaderTimeout, other.ReadHeaderTimeout)
s.ReadTimeout = helpers.OverrideWithNumber(s.ReadTimeout, other.ReadTimeout) s.ReadTimeout = gosettings.OverrideWithNumber(s.ReadTimeout, other.ReadTimeout)
s.ShutdownTimeout = helpers.OverrideWithNumber(s.ShutdownTimeout, other.ShutdownTimeout) s.ShutdownTimeout = gosettings.OverrideWithNumber(s.ShutdownTimeout, other.ShutdownTimeout)
} }
var ( var (
@@ -85,7 +85,7 @@ var (
func (s Settings) Validate() (err error) { func (s Settings) Validate() (err error) {
uid := os.Getuid() uid := os.Getuid()
_, err = address.Validate(s.Address, address.OptionListening(uid)) err = address.Validate(s.Address, address.OptionListening(uid))
if err != nil { if err != nil {
return err return err
} }

View File

@@ -11,7 +11,7 @@ import (
"github.com/qdm12/gluetun/internal/constants/providers" "github.com/qdm12/gluetun/internal/constants/providers"
) )
type AllServers struct { type AllServers struct { //nolint:musttag
Version uint16 // used for migration of the top level scheme Version uint16 // used for migration of the top level scheme
ProviderToServers map[string]Servers ProviderToServers map[string]Servers
} }

View File

@@ -1,14 +1,32 @@
//go:build linux || darwin
package netlink package netlink
import "github.com/vishvananda/netlink" import (
"github.com/vishvananda/netlink"
type Addr = netlink.Addr )
func (n *NetLink) AddrList(link Link, family int) ( func (n *NetLink) AddrList(link Link, family int) (
addresses []Addr, err error) { addresses []Addr, err error) {
return netlink.AddrList(link, family) netlinkLink := linkToNetlinkLink(&link)
netlinkAddresses, err := netlink.AddrList(netlinkLink, family)
if err != nil {
return nil, err
}
addresses = make([]Addr, len(netlinkAddresses))
for i := range netlinkAddresses {
addresses[i].Network = netIPNetToNetipPrefix(netlinkAddresses[i].IPNet)
}
return addresses, nil
} }
func (n *NetLink) AddrAdd(link Link, addr *Addr) error { func (n *NetLink) AddrReplace(link Link, addr Addr) error {
return netlink.AddrAdd(link, addr) netlinkLink := linkToNetlinkLink(&link)
netlinkAddress := netlink.Addr{
IPNet: netipPrefixToIPNet(addr.Network),
}
return netlink.AddrReplace(netlinkLink, &netlinkAddress)
} }

View File

@@ -0,0 +1,12 @@
//go:build !linux && !darwin
package netlink
func (n *NetLink) AddrList(link Link, family int) (
addresses []Addr, err error) {
panic("not implemented")
}
func (n *NetLink) AddrReplace(Link, Addr) error {
panic("not implemented")
}

View File

@@ -0,0 +1,62 @@
package netlink
import (
"fmt"
"net"
"net/netip"
)
func netipPrefixToIPNet(prefix netip.Prefix) (ipNet *net.IPNet) {
if !prefix.IsValid() {
return nil
}
prefixAddr := prefix.Addr().Unmap()
ipMask := net.CIDRMask(prefix.Bits(), prefixAddr.BitLen())
ip := netipAddrToNetIP(prefixAddr)
return &net.IPNet{
IP: ip,
Mask: ipMask,
}
}
func netIPNetToNetipPrefix(ipNet *net.IPNet) (prefix netip.Prefix) {
if ipNet == nil || (len(ipNet.IP) != net.IPv4len && len(ipNet.IP) != net.IPv6len) {
return prefix
}
var ip netip.Addr
if ipv4 := ipNet.IP.To4(); ipv4 != nil {
ip = netip.AddrFrom4([4]byte(ipv4))
} else {
ip = netip.AddrFrom16([16]byte(ipNet.IP))
}
bits, _ := ipNet.Mask.Size()
return netip.PrefixFrom(ip, bits)
}
func netipAddrToNetIP(address netip.Addr) (ip net.IP) {
switch {
case !address.IsValid():
return nil
case address.Is4() || address.Is4In6():
bytes := address.As4()
return net.IP(bytes[:])
default:
bytes := address.As16()
return net.IP(bytes[:])
}
}
func netIPToNetipAddress(ip net.IP) (address netip.Addr) {
if len(ip) != net.IPv4len && len(ip) != net.IPv6len {
return address // invalid
}
address, ok := netip.AddrFromSlice(ip)
if !ok {
panic(fmt.Sprintf("converting %#v to netip.Addr failed", ip))
}
return address.Unmap()
}

View File

@@ -0,0 +1,146 @@
package netlink
import (
"net"
"net/netip"
"testing"
"github.com/stretchr/testify/assert"
)
func Test_netipPrefixToIPNet(t *testing.T) {
t.Parallel()
testCases := map[string]struct {
prefix netip.Prefix
ipNet *net.IPNet
}{
"empty_prefix": {},
"IPv4_prefix": {
prefix: netip.PrefixFrom(netip.AddrFrom4([4]byte{1, 2, 3, 4}), 24),
ipNet: &net.IPNet{
IP: net.IP{1, 2, 3, 4},
Mask: net.IPv4Mask(255, 255, 255, 0),
},
},
"IPv4-in-IPv6_prefix": {
prefix: netip.PrefixFrom(netip.AddrFrom16(
[16]byte{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0xff, 0xff, 1, 2, 3, 4}),
24),
ipNet: &net.IPNet{
IP: net.IP{1, 2, 3, 4},
Mask: net.IPv4Mask(255, 255, 255, 0),
},
},
"IPv6_prefix": {
prefix: netip.PrefixFrom(netip.IPv6Loopback(), 8),
ipNet: &net.IPNet{
IP: net.IPv6loopback,
Mask: net.IPMask{0xff, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
},
},
}
for name, testCase := range testCases {
testCase := testCase
t.Run(name, func(t *testing.T) {
t.Parallel()
ipNet := netipPrefixToIPNet(testCase.prefix)
assert.Equal(t, testCase.ipNet, ipNet)
})
}
}
func Test_netIPNetToNetipPrefix(t *testing.T) {
t.Parallel()
testCases := map[string]struct {
ipNet *net.IPNet
prefix netip.Prefix
}{
"empty ipnet": {},
"custom sized IP in ipnet": {
ipNet: &net.IPNet{
IP: net.IP{1},
},
},
"IPv4 ipnet": {
ipNet: &net.IPNet{
IP: net.IP{1, 2, 3, 4},
Mask: net.IPMask{255, 255, 255, 0},
},
prefix: netip.PrefixFrom(netip.AddrFrom4([4]byte{1, 2, 3, 4}), 24),
},
"IPv4-in-IPv6 ipnet": {
ipNet: &net.IPNet{
IP: net.IPv4(1, 2, 3, 4),
Mask: net.IPMask{255, 255, 255, 0},
},
prefix: netip.PrefixFrom(netip.AddrFrom4([4]byte{1, 2, 3, 4}), 24),
},
"IPv6 ipnet": {
ipNet: &net.IPNet{
IP: net.IPv6loopback,
Mask: net.IPMask{0xff},
},
prefix: netip.PrefixFrom(netip.IPv6Loopback(), 8),
},
}
for name, testCase := range testCases {
testCase := testCase
t.Run(name, func(t *testing.T) {
t.Parallel()
prefix := netIPNetToNetipPrefix(testCase.ipNet)
assert.Equal(t, testCase.prefix, prefix)
})
}
}
func Test_netIPToNetipAddress(t *testing.T) {
t.Parallel()
testCases := map[string]struct {
ip net.IP
address netip.Addr
panicMessage string
}{
"nil_ip": {},
"ip_not_ipv4_or_ipv6": {
ip: net.IP{1},
},
"IPv4": {
ip: net.IPv4(1, 2, 3, 4),
address: netip.AddrFrom4([4]byte{1, 2, 3, 4}),
},
"IPv6": {
ip: net.IPv6zero,
address: netip.AddrFrom16([16]byte{}),
},
"IPv4 prefixed with 0xffff": {
ip: net.IP{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0xff, 0xff, 1, 2, 3, 4},
address: netip.AddrFrom4([4]byte{1, 2, 3, 4}),
},
}
for name, testCase := range testCases {
testCase := testCase
t.Run(name, func(t *testing.T) {
t.Parallel()
if testCase.panicMessage != "" {
assert.PanicsWithValue(t, testCase.panicMessage, func() {
netIPToNetipAddress(testCase.ip)
})
return
}
address := netIPToNetipAddress(testCase.ip)
assert.Equal(t, testCase.address, address)
})
}
}

View File

@@ -2,39 +2,23 @@ package netlink
import ( import (
"fmt" "fmt"
"github.com/vishvananda/netlink"
) )
//nolint:revive
const ( const (
FAMILY_ALL = netlink.FAMILY_ALL FamilyAll = 0
FAMILY_V4 = netlink.FAMILY_V4 FamilyV4 = 2
FAMILY_V6 = netlink.FAMILY_V6 FamilyV6 = 10
) )
func FamilyToString(family int) string { func FamilyToString(family int) string {
switch family { switch family {
case FAMILY_ALL: case FamilyAll:
return "all" return "all" //nolint:goconst
case FAMILY_V4: case FamilyV4:
return "v4" return "v4"
case FAMILY_V6: case FamilyV6:
return "v6" return "v6"
default: default:
return fmt.Sprint(family) return fmt.Sprint(family)
} }
} }
func (n *NetLink) IsWireguardSupported() (ok bool, err error) {
families, err := netlink.GenlFamilyList()
if err != nil {
return false, fmt.Errorf("listing gen 1 families: %w", err)
}
for _, family := range families {
if family.Name == "wireguard" {
return true, nil
}
}
return false, nil
}

View File

@@ -2,39 +2,31 @@ package netlink
import ( import (
"fmt" "fmt"
"github.com/vishvananda/netlink"
) )
func (n *NetLink) IsIPv6Supported() (supported bool, err error) { func (n *NetLink) IsIPv6Supported() (supported bool, err error) {
links, err := n.LinkList() routes, err := n.RouteList(FamilyV6)
if err != nil { if err != nil {
return false, fmt.Errorf("listing links: %w", err) return false, fmt.Errorf("listing IPv6 routes: %w", err)
} }
var totalRoutes uint // Check each route for IPv6 due to Podman bug listing IPv4 routes
for _, link := range links { // as IPv6 routes at container start, see:
routes, err := n.RouteList(link, netlink.FAMILY_V6) // https://github.com/qdm12/gluetun/issues/1241#issuecomment-1333405949
if err != nil { for _, route := range routes {
return false, fmt.Errorf("listing IPv6 routes for link %s: %w", sourceIsIPv6 := route.Src.IsValid() && route.Src.Is6()
link.Attrs().Name, err) destinationIsIPv6 := route.Dst.IsValid() && route.Dst.Addr().Is6()
} if sourceIsIPv6 || destinationIsIPv6 {
link, err := n.LinkByIndex(route.LinkIndex)
// Check each route for IPv6 due to Podman bug listing IPv4 routes if err != nil {
// as IPv6 routes at container start, see: return false, fmt.Errorf("finding IPv6 supported link: %w", err)
// https://github.com/qdm12/gluetun/issues/1241#issuecomment-1333405949
for _, route := range routes {
sourceIsIPv6 := route.Src != nil && route.Src.To4() == nil
destinationIsIPv6 := route.Dst != nil && route.Dst.IP.To4() == nil
if sourceIsIPv6 || destinationIsIPv6 {
n.debugLogger.Debugf("IPv6 is supported by link %s", link.Attrs().Name)
return true, nil
} }
totalRoutes++ n.debugLogger.Debugf("IPv6 is supported by link %s", link.Name)
return true, nil
} }
} }
n.debugLogger.Debugf("IPv6 is not supported after searching %d links and %d routes", n.debugLogger.Debugf("IPv6 is not supported after searching %d routes",
len(links), totalRoutes) len(routes))
return false, nil return false, nil
} }

View File

@@ -1,37 +1,105 @@
//go:build linux || darwin
package netlink package netlink
import "github.com/vishvananda/netlink" import "github.com/vishvananda/netlink"
type (
Link = netlink.Link
Bridge = netlink.Bridge
Wireguard = netlink.Wireguard
)
func (n *NetLink) LinkList() (links []Link, err error) { func (n *NetLink) LinkList() (links []Link, err error) {
return netlink.LinkList() netlinkLinks, err := netlink.LinkList()
if err != nil {
return nil, err
}
links = make([]Link, len(netlinkLinks))
for i := range netlinkLinks {
links[i] = netlinkLinkToLink(netlinkLinks[i])
}
return links, nil
} }
func (n *NetLink) LinkByName(name string) (link Link, err error) { func (n *NetLink) LinkByName(name string) (link Link, err error) {
return netlink.LinkByName(name) netlinkLink, err := netlink.LinkByName(name)
if err != nil {
return Link{}, err
}
return netlinkLinkToLink(netlinkLink), nil
} }
func (n *NetLink) LinkByIndex(index int) (link Link, err error) { func (n *NetLink) LinkByIndex(index int) (link Link, err error) {
return netlink.LinkByIndex(index) netlinkLink, err := netlink.LinkByIndex(index)
if err != nil {
return Link{}, err
}
return netlinkLinkToLink(netlinkLink), nil
} }
func (n *NetLink) LinkAdd(link Link) (err error) { func (n *NetLink) LinkAdd(link Link) (linkIndex int, err error) {
return netlink.LinkAdd(link) netlinkLink := linkToNetlinkLink(&link)
err = netlink.LinkAdd(netlinkLink)
if err != nil {
return 0, err
}
return netlinkLink.Attrs().Index, nil
} }
func (n *NetLink) LinkDel(link Link) (err error) { func (n *NetLink) LinkDel(link Link) (err error) {
return netlink.LinkDel(link) return netlink.LinkDel(linkToNetlinkLink(&link))
} }
func (n *NetLink) LinkSetUp(link Link) (err error) { func (n *NetLink) LinkSetUp(link Link) (linkIndex int, err error) {
return netlink.LinkSetUp(link) netlinkLink := linkToNetlinkLink(&link)
err = netlink.LinkSetUp(netlinkLink)
if err != nil {
return 0, err
}
return netlinkLink.Attrs().Index, nil
} }
func (n *NetLink) LinkSetDown(link Link) (err error) { func (n *NetLink) LinkSetDown(link Link) (err error) {
return netlink.LinkSetDown(link) return netlink.LinkSetDown(linkToNetlinkLink(&link))
}
type netlinkLinkImpl struct {
attrs *netlink.LinkAttrs
linkType string
}
func (n *netlinkLinkImpl) Attrs() *netlink.LinkAttrs {
return n.attrs
}
func (n *netlinkLinkImpl) Type() string {
return n.linkType
}
func netlinkLinkToLink(netlinkLink netlink.Link) Link {
attributes := netlinkLink.Attrs()
return Link{
Type: netlinkLink.Type(),
Name: attributes.Name,
Index: attributes.Index,
EncapType: attributes.EncapType,
MTU: uint16(attributes.MTU),
}
}
// Warning: we must return `netlink.Link` and not `netlinkLinkImpl`
// so that the vishvananda/netlink package can compare the returned
// value against an untyped nil.
func linkToNetlinkLink(link *Link) netlink.Link {
if link == nil {
return nil
}
return &netlinkLinkImpl{
linkType: link.Type,
attrs: &netlink.LinkAttrs{
Name: link.Name,
Index: link.Index,
EncapType: link.EncapType,
MTU: int(link.MTU),
},
}
} }

View File

@@ -0,0 +1,31 @@
//go:build !linux && !darwin
package netlink
func (n *NetLink) LinkList() (links []Link, err error) {
panic("not implemented")
}
func (n *NetLink) LinkByName(name string) (link Link, err error) {
panic("not implemented")
}
func (n *NetLink) LinkByIndex(index int) (link Link, err error) {
panic("not implemented")
}
func (n *NetLink) LinkAdd(link Link) (linkIndex int, err error) {
panic("not implemented")
}
func (n *NetLink) LinkDel(link Link) (err error) {
panic("not implemented")
}
func (n *NetLink) LinkSetUp(link Link) (linkIndex int, err error) {
panic("not implemented")
}
func (n *NetLink) LinkSetDown(link Link) (err error) {
panic("not implemented")
}

View File

@@ -1,9 +0,0 @@
package netlink
import "github.com/vishvananda/netlink"
type LinkAttrs = netlink.LinkAttrs
func NewLinkAttrs() LinkAttrs {
return netlink.NewLinkAttrs()
}

View File

@@ -1,22 +1,69 @@
//go:build linux || darwin
package netlink package netlink
import "github.com/vishvananda/netlink" import (
"github.com/vishvananda/netlink"
)
type Route = netlink.Route func (n *NetLink) RouteList(family int) (routes []Route, err error) {
// We set the filter to netlink.RT_FILTER_TABLE so that
// routes from all tables are listed, as long as the filter
// table is set to 0.
const filterMask = netlink.RT_FILTER_TABLE
// The filter is not left to `nil` otherwise non-main tables
// are ignored.
filter := &netlink.Route{}
func (n *NetLink) RouteList(link Link, family int) ( netlinkRoutes, err := netlink.RouteListFiltered(family, filter, filterMask)
routes []Route, err error) { if err != nil {
return netlink.RouteList(link, family) return nil, err
}
routes = make([]Route, len(netlinkRoutes))
for i := range netlinkRoutes {
routes[i] = netlinkRouteToRoute(netlinkRoutes[i])
}
return routes, nil
} }
func (n *NetLink) RouteAdd(route *Route) error { func (n *NetLink) RouteAdd(route Route) error {
return netlink.RouteAdd(route) netlinkRoute := routeToNetlinkRoute(route)
return netlink.RouteAdd(&netlinkRoute)
} }
func (n *NetLink) RouteDel(route *Route) error { func (n *NetLink) RouteDel(route Route) error {
return netlink.RouteDel(route) netlinkRoute := routeToNetlinkRoute(route)
return netlink.RouteDel(&netlinkRoute)
} }
func (n *NetLink) RouteReplace(route *Route) error { func (n *NetLink) RouteReplace(route Route) error {
return netlink.RouteReplace(route) netlinkRoute := routeToNetlinkRoute(route)
return netlink.RouteReplace(&netlinkRoute)
}
func netlinkRouteToRoute(netlinkRoute netlink.Route) (route Route) {
return Route{
LinkIndex: netlinkRoute.LinkIndex,
Dst: netIPNetToNetipPrefix(netlinkRoute.Dst),
Src: netIPToNetipAddress(netlinkRoute.Src),
Gw: netIPToNetipAddress(netlinkRoute.Gw),
Priority: netlinkRoute.Priority,
Family: netlinkRoute.Family,
Table: netlinkRoute.Table,
Type: netlinkRoute.Type,
}
}
func routeToNetlinkRoute(route Route) (netlinkRoute netlink.Route) {
return netlink.Route{
LinkIndex: route.LinkIndex,
Dst: netipPrefixToIPNet(route.Dst),
Src: netipAddrToNetIP(route.Src),
Gw: netipAddrToNetIP(route.Gw),
Priority: route.Priority,
Family: route.Family,
Table: route.Table,
Type: route.Type,
}
} }

View File

@@ -0,0 +1,20 @@
//go:build !linux && !darwin
package netlink
func (n *NetLink) RouteList(family int) (
routes []Route, err error) {
panic("not implemented")
}
func (n *NetLink) RouteAdd(route Route) error {
panic("not implemented")
}
func (n *NetLink) RouteDel(route Route) error {
panic("not implemented")
}
func (n *NetLink) RouteReplace(route Route) error {
panic("not implemented")
}

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