Compare commits

..

2 Commits

Author SHA1 Message Date
Quentin McGaw
6d48f9c2ba fix(routing): net.IPNet to netip.Prefix conversion 2023-05-22 05:56:27 +00:00
Quentin McGaw
f712d77642 fix(firewall): prevent IP family mix in acceptOutputFromIPToSubnet 2023-05-21 18:06:18 +00:00
164 changed files with 56566 additions and 122080 deletions

View File

@@ -9,21 +9,17 @@ It works on Linux, Windows and OSX.
- [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
- [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
- Ensure your host has the following and that they are accessible by Docker:
- `~/.ssh` directory
- `~/.gitconfig` file (can be empty)
## 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. 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
@@ -33,9 +29,13 @@ You can make changes to the [Dockerfile](Dockerfile) and then rebuild the image.
```Dockerfile
FROM qmcgaw/godevcontainer
USER root
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:
- 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
You can bind mount a shell script to `/root/.welcome.sh` to replace the [current welcome script](shell/.welcome.sh).
You can bind mount a shell script to `/home/vscode/.welcome.sh` to replace the [current welcome script](shell/.welcome.sh).
### Publish a port
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.
To access a port from your host to your development container, publish a port in [docker-compose.yml](docker-compose.yml).
### Run other services

View File

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

View File

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

12
.github/labels.yml vendored
View File

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

View File

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

View File

@@ -17,6 +17,8 @@ on:
- go.mod
- go.sum
pull_request:
branches:
- master
paths:
- .github/workflows/ci.yml
- cmd/**
@@ -134,7 +136,7 @@ jobs:
run: echo "::set-output name=value::$(git rev-parse --short HEAD)"
- name: Build and push final image
uses: docker/build-push-action@v4.1.1
uses: docker/build-push-action@v4.0.0
with:
platforms: linux/amd64,linux/386,linux/arm64,linux/arm/v6,linux/arm/v7,linux/ppc64le
labels: ${{ steps.meta.outputs.labels }}

View File

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

View File

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

29
.vscode/settings.json vendored
View File

@@ -1,29 +0,0 @@
{
// 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.18
ARG GO_ALPINE_VERSION=3.18
ARG ALPINE_VERSION=3.17
ARG GO_ALPINE_VERSION=3.17
ARG GO_VERSION=1.20
ARG XCPUTRANSLATE_VERSION=v0.6.0
ARG GOLANGCI_LINT_VERSION=v1.53.2
ARG GOLANGCI_LINT_VERSION=v1.52.2
ARG MOCKGEN_VERSION=v1.6.0
ARG BUILDPLATFORM=linux/amd64
@@ -97,7 +97,6 @@ ENV VPN_SERVICE_PROVIDER=pia \
WIREGUARD_PRESHARED_KEY= \
WIREGUARD_PUBLIC_KEY= \
WIREGUARD_ADDRESSES= \
WIREGUARD_MTU=1400 \
WIREGUARD_IMPLEMENTATION=auto \
# VPN server filtering
SERVER_REGIONS= \
@@ -200,11 +199,12 @@ EXPOSE 8000/tcp 8888/tcp 8388/tcp 8388/udp
HEALTHCHECK --interval=5s --timeout=5s --start-period=10s --retries=1 CMD /gluetun-entrypoint healthcheck
ARG TARGETPLATFORM
RUN apk add --no-cache --update -l wget && \
apk add --no-cache --update -X "https://dl-cdn.alpinelinux.org/alpine/v3.17/main" openvpn\~2.5 && \
mv /usr/sbin/openvpn /usr/sbin/openvpn2.5 && \
apk add --no-cache --update -X "https://dl-cdn.alpinelinux.org/alpine/v3.12/main" openvpn==2.4.12-r0 && \
apk add --no-cache --update -X "https://dl-cdn.alpinelinux.org/alpine/v3.16/main" openssl\~1.1 && \
mv /usr/sbin/openvpn /usr/sbin/openvpn2.4 && \
apk del openvpn && \
apk add --no-cache --update openvpn ca-certificates iptables ip6tables unbound tzdata && \
mv /usr/sbin/openvpn /usr/sbin/openvpn2.6 && \
mv /usr/sbin/openvpn /usr/sbin/openvpn2.5 && \
# Fix vulnerability issue
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 && \

View File

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

View File

@@ -264,8 +264,8 @@ func _main(ctx context.Context, buildInfo models.BuildInformation,
err = printVersions(ctx, logger, []printVersionElement{
{name: "Alpine", getVersion: alpineConf.Version},
{name: "OpenVPN 2.4", getVersion: ovpnConf.Version24},
{name: "OpenVPN 2.5", getVersion: ovpnConf.Version25},
{name: "OpenVPN 2.6", getVersion: ovpnConf.Version26},
{name: "Unbound", getVersion: dnsConf.Version},
{name: "IPtables", getVersion: func(ctx context.Context) (version string, err error) {
return firewall.Version(ctx, cmder)
@@ -531,29 +531,30 @@ type netLinker interface {
type Addresser interface {
AddrList(link netlink.Link, family int) (
addresses []netlink.Addr, err error)
AddrReplace(link netlink.Link, addr netlink.Addr) error
AddrAdd(link netlink.Link, addr *netlink.Addr) error
}
type Router interface {
RouteList(family int) (routes []netlink.Route, err error)
RouteAdd(route netlink.Route) error
RouteDel(route netlink.Route) error
RouteReplace(route netlink.Route) error
RouteList(link netlink.Link, family int) (
routes []netlink.Route, err error)
RouteAdd(route *netlink.Route) error
RouteDel(route *netlink.Route) error
RouteReplace(route *netlink.Route) error
}
type Ruler interface {
RuleList(family int) (rules []netlink.Rule, err error)
RuleAdd(rule netlink.Rule) error
RuleDel(rule netlink.Rule) error
RuleAdd(rule *netlink.Rule) error
RuleDel(rule *netlink.Rule) error
}
type Linker interface {
LinkList() (links []netlink.Link, err error)
LinkByName(name string) (link netlink.Link, err error)
LinkByIndex(index int) (link netlink.Link, err error)
LinkAdd(link netlink.Link) (linkIndex int, err error)
LinkAdd(link netlink.Link) (err error)
LinkDel(link netlink.Link) (err error)
LinkSetUp(link netlink.Link) (linkIndex int, err error)
LinkSetUp(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
require (
github.com/breml/rootcerts v0.2.11
github.com/breml/rootcerts v0.2.10
github.com/fatih/color v1.15.0
github.com/golang/mock v1.6.0
github.com/qdm12/dns v1.11.0
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/gosplash v0.1.0
github.com/qdm12/gotree v0.2.0
github.com/qdm12/govalid v0.2.0-rc1
github.com/qdm12/govalid v0.1.0
github.com/qdm12/log v0.1.0
github.com/qdm12/ss-server v0.5.0-rc1
github.com/qdm12/ss-server v0.4.0
github.com/qdm12/updated v0.0.0-20210603204757-205acfe6937e
github.com/stretchr/testify v1.8.4
github.com/stretchr/testify v1.8.2
github.com/vishvananda/netlink v1.2.1-beta.2
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/sys v0.8.0
golang.org/x/text v0.10.0
golang.org/x/text v0.9.0
golang.zx2c4.com/wireguard v0.0.0-20230325221338-052af4a8072b
golang.zx2c4.com/wireguard/wgctrl v0.0.0-20230215201556-9c5414ab4bde
inet.af/netaddr v0.0.0-20220811202034-502d2d690317
@@ -43,8 +43,7 @@ require (
github.com/vishvananda/netns v0.0.0-20200728191858-db3c7e526aae // indirect
go4.org/intern v0.0.0-20211027215823-ae77deb06f29 // indirect
go4.org/unsafe/assume-no-moving-gc v0.0.0-20230221090011-e4bae7ad2296 // indirect
golang.org/x/crypto v0.9.0 // indirect
golang.org/x/exp v0.0.0-20230522175609-2e198f4a06a1 // indirect
golang.org/x/crypto v0.6.0 // indirect
golang.org/x/sync v0.1.0 // indirect
golang.zx2c4.com/wintun v0.0.0-20230126152724-0fa3db229ce2 // 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/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/breml/rootcerts v0.2.11 h1:njUAtoyZ6HUXPAPk63tGz0BEZk1/6gyfqK5fTzksHkM=
github.com/breml/rootcerts v0.2.11/go.mod h1:S/PKh+4d1HUn4HQovEB8hPJZO6pUZYrIhmXBhsegfXw=
github.com/breml/rootcerts v0.2.10 h1:UGVZ193UTSUASpGtg6pbDwzOd7XQP+at0Ssg1/2E4h8=
github.com/breml/rootcerts v0.2.10/go.mod h1:24FDtzYMpqIeYC7QzaE8VPRQaFZU5TIUDlyk8qwjD88=
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.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
@@ -91,20 +91,18 @@ 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-20210822203818-5c568b0777b6 h1:bge5AL7cjHJMPz+5IOz5yF01q/l8No6+lIEBieA8gMg=
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/go.mod h1:EqZ46No00kCTZ5qzdd3qIzY6ayhMt24QI8Mh8LVQYmM=
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/gotree v0.2.0 h1:+58ltxkNLUyHtATFereAcOjBVfY6ETqRex8XK90Fb/c=
github.com/qdm12/gotree v0.2.0/go.mod h1:1SdFaqKZuI46U1apbXIf25pDMNnrPuYLEqMF/qL4lY4=
github.com/qdm12/govalid v0.2.0-rc1 h1:4iYQvU4ibrASgzelsEgZX4JyKX3UTB/DcHObzQ7BXtw=
github.com/qdm12/govalid v0.2.0-rc1/go.mod h1:/uWzVWMuS71wmbsVnlUxpQiy6EAXqm8eQ2RbyA72roQ=
github.com/qdm12/govalid v0.1.0 h1:UIFVmuaAg0Q+h0GeyfcFEZ5sQ5KJPvRQwycC1/cqDN8=
github.com/qdm12/govalid v0.1.0/go.mod h1:CyS/OEQdOvunBgrtIsW93fjd4jBkwZPBjGSpxq3NwA4=
github.com/qdm12/log v0.1.0 h1:jYBd/xscHYpblzZAd2kjZp2YmuYHjAAfbTViJWxoPTw=
github.com/qdm12/log v0.1.0/go.mod h1:Vchi5M8uBvHfPNIblN4mjXn/oSbiWguQIbsgF1zdQPI=
github.com/qdm12/ss-server v0.5.0-rc1 h1:2rJEhDnUUc9AKtvyVu+CrnJwvdEjMaB1zFRQvTUlDPw=
github.com/qdm12/ss-server v0.5.0-rc1/go.mod h1:IoFYGpVpxfIB/dMTr0PnSegdhV1gEfZLS9Tr1Qn8uRg=
github.com/qdm12/ss-server v0.4.0 h1:lMMYfDGc9P86Lyvd3+p8lK4hhgHUKDzjZC91FqJYkDU=
github.com/qdm12/ss-server v0.4.0/go.mod h1:AY0p4huvPUPW+/CiWsJcDgT6sneDryk26VXSccPNCxY=
github.com/qdm12/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/riobard/go-bloom v0.0.0-20200614022211-cdc8013cb5b3 h1:f/FNXud6gA3MNr8meMVVGxhp+QBTqY91tM8HjEuMjGg=
@@ -113,12 +111,16 @@ 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/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.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.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
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.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk=
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
github.com/stretchr/testify v1.8.2 h1:+h33VjcLVPDHtOdpUCuF+7gSuG3yGIftsP1YvFihtJ8=
github.com/stretchr/testify v1.8.2/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
github.com/vishvananda/netlink v1.2.1-beta.2 h1:Llsql0lnQEbHj0I1OuKyp8otXp0r3q0mPkuhwHfStVs=
github.com/vishvananda/netlink v1.2.1-beta.2/go.mod h1:twkDnbuQxJYemMlGd4JFIcuhgX83tXhKS2B/PRMpOho=
github.com/vishvananda/netns v0.0.0-20200728191858-db3c7e526aae h1:4hwBBUfQCFe3Cym0ZtKyq7L16eZUtYKs+BaHDN6mAns=
@@ -146,10 +148,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-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.9.0 h1:LF6fAI+IutBocDJ2OT0Q1g8plpYljMZ4+lty+dsqw3g=
golang.org/x/crypto v0.9.0/go.mod h1:yrmDGqONDYtNj3tH8X9dzUun2m2lzPa9ngI6/RUPGR0=
golang.org/x/exp v0.0.0-20230522175609-2e198f4a06a1 h1:k/i9J1pBpvlfR+9QsetwPyERsqu1GIbi967PQMq3Ivc=
golang.org/x/exp v0.0.0-20230522175609-2e198f4a06a1/go.mod h1:V1LtkGg67GoY2N1AnLN78QLrzxkLyJw7RJb1gzOOz9w=
golang.org/x/crypto v0.6.0 h1:qfktjS5LUO+fFKeJXZ+ikTRijMmljikvG68fpMMruSc=
golang.org/x/crypto v0.6.0/go.mod h1:OFC/31mSvZgRz0V1QTNCzfAI1aIRzbiufJtkMIlEp58=
golang.org/x/exp v0.0.0-20230519143937-03e91628a987 h1:3xJIFvzUFbu4ls0BTBYcgbCGhA63eAOEMxIHugyXJqA=
golang.org/x/exp v0.0.0-20230519143937-03e91628a987/go.mod h1:V1LtkGg67GoY2N1AnLN78QLrzxkLyJw7RJb1gzOOz9w=
golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
@@ -204,8 +206,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.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.10.0 h1:UpjohKhiEgNc0CSauXmwYftY1+LlaC75SJwh0SgCX58=
golang.org/x/text v0.10.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
golang.org/x/text v0.9.0 h1:2sjJmO8cDvYveuX97RDLsxlyUxLl+GHoLxBiRdHllBE=
golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
golang.org/x/time v0.0.0-20191024005414-555d28b269f0 h1:/5xXl8Y5W96D+TtHSlonuFqGHIWVuyCkGJLwGh9JJFs=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20190729092621-ff9f1409240a/go.mod h1:jcCCGcm9btYwXyDqrUWc6MKQKKGJCWEQ3AfLSRIbEuI=

View File

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

View File

@@ -7,7 +7,7 @@ import (
"regexp"
"github.com/qdm12/dns/pkg/blacklist"
"github.com/qdm12/gosettings"
"github.com/qdm12/gluetun/internal/configuration/settings/helpers"
"github.com/qdm12/gotree"
)
@@ -23,9 +23,9 @@ type DNSBlacklist struct {
}
func (b *DNSBlacklist) setDefaults() {
b.BlockMalicious = gosettings.DefaultPointer(b.BlockMalicious, true)
b.BlockAds = gosettings.DefaultPointer(b.BlockAds, false)
b.BlockSurveillance = gosettings.DefaultPointer(b.BlockSurveillance, true)
b.BlockMalicious = helpers.DefaultPointer(b.BlockMalicious, true)
b.BlockAds = helpers.DefaultPointer(b.BlockAds, false)
b.BlockSurveillance = helpers.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
@@ -53,34 +53,34 @@ func (b DNSBlacklist) validate() (err error) {
func (b DNSBlacklist) copy() (copied DNSBlacklist) {
return DNSBlacklist{
BlockMalicious: gosettings.CopyPointer(b.BlockMalicious),
BlockAds: gosettings.CopyPointer(b.BlockAds),
BlockSurveillance: gosettings.CopyPointer(b.BlockSurveillance),
AllowedHosts: gosettings.CopySlice(b.AllowedHosts),
AddBlockedHosts: gosettings.CopySlice(b.AddBlockedHosts),
AddBlockedIPs: gosettings.CopySlice(b.AddBlockedIPs),
AddBlockedIPPrefixes: gosettings.CopySlice(b.AddBlockedIPPrefixes),
BlockMalicious: helpers.CopyPointer(b.BlockMalicious),
BlockAds: helpers.CopyPointer(b.BlockAds),
BlockSurveillance: helpers.CopyPointer(b.BlockSurveillance),
AllowedHosts: helpers.CopySlice(b.AllowedHosts),
AddBlockedHosts: helpers.CopySlice(b.AddBlockedHosts),
AddBlockedIPs: helpers.CopySlice(b.AddBlockedIPs),
AddBlockedIPPrefixes: helpers.CopySlice(b.AddBlockedIPPrefixes),
}
}
func (b *DNSBlacklist) mergeWith(other DNSBlacklist) {
b.BlockMalicious = gosettings.MergeWithPointer(b.BlockMalicious, other.BlockMalicious)
b.BlockAds = gosettings.MergeWithPointer(b.BlockAds, other.BlockAds)
b.BlockSurveillance = gosettings.MergeWithPointer(b.BlockSurveillance, other.BlockSurveillance)
b.AllowedHosts = gosettings.MergeWithSlice(b.AllowedHosts, other.AllowedHosts)
b.AddBlockedHosts = gosettings.MergeWithSlice(b.AddBlockedHosts, other.AddBlockedHosts)
b.AddBlockedIPs = gosettings.MergeWithSlice(b.AddBlockedIPs, other.AddBlockedIPs)
b.AddBlockedIPPrefixes = gosettings.MergeWithSlice(b.AddBlockedIPPrefixes, other.AddBlockedIPPrefixes)
b.BlockMalicious = helpers.MergeWithPointer(b.BlockMalicious, other.BlockMalicious)
b.BlockAds = helpers.MergeWithPointer(b.BlockAds, other.BlockAds)
b.BlockSurveillance = helpers.MergeWithPointer(b.BlockSurveillance, other.BlockSurveillance)
b.AllowedHosts = helpers.MergeSlices(b.AllowedHosts, other.AllowedHosts)
b.AddBlockedHosts = helpers.MergeSlices(b.AddBlockedHosts, other.AddBlockedHosts)
b.AddBlockedIPs = helpers.MergeSlices(b.AddBlockedIPs, other.AddBlockedIPs)
b.AddBlockedIPPrefixes = helpers.MergeSlices(b.AddBlockedIPPrefixes, other.AddBlockedIPPrefixes)
}
func (b *DNSBlacklist) overrideWith(other DNSBlacklist) {
b.BlockMalicious = gosettings.OverrideWithPointer(b.BlockMalicious, other.BlockMalicious)
b.BlockAds = gosettings.OverrideWithPointer(b.BlockAds, other.BlockAds)
b.BlockSurveillance = gosettings.OverrideWithPointer(b.BlockSurveillance, other.BlockSurveillance)
b.AllowedHosts = gosettings.OverrideWithSlice(b.AllowedHosts, other.AllowedHosts)
b.AddBlockedHosts = gosettings.OverrideWithSlice(b.AddBlockedHosts, other.AddBlockedHosts)
b.AddBlockedIPs = gosettings.OverrideWithSlice(b.AddBlockedIPs, other.AddBlockedIPs)
b.AddBlockedIPPrefixes = gosettings.OverrideWithSlice(b.AddBlockedIPPrefixes, other.AddBlockedIPPrefixes)
b.BlockMalicious = helpers.OverrideWithPointer(b.BlockMalicious, other.BlockMalicious)
b.BlockAds = helpers.OverrideWithPointer(b.BlockAds, other.BlockAds)
b.BlockSurveillance = helpers.OverrideWithPointer(b.BlockSurveillance, other.BlockSurveillance)
b.AllowedHosts = helpers.OverrideWithSlice(b.AllowedHosts, other.AllowedHosts)
b.AddBlockedHosts = helpers.OverrideWithSlice(b.AddBlockedHosts, other.AddBlockedHosts)
b.AddBlockedIPs = helpers.OverrideWithSlice(b.AddBlockedIPs, other.AddBlockedIPs)
b.AddBlockedIPPrefixes = helpers.OverrideWithSlice(b.AddBlockedIPPrefixes, other.AddBlockedIPPrefixes)
}
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) {
node = gotree.New("DNS filtering settings:")
node.Appendf("Block malicious: %s", gosettings.BoolToYesNo(b.BlockMalicious))
node.Appendf("Block ads: %s", gosettings.BoolToYesNo(b.BlockAds))
node.Appendf("Block surveillance: %s", gosettings.BoolToYesNo(b.BlockSurveillance))
node.Appendf("Block malicious: %s", helpers.BoolPtrToYesNo(b.BlockMalicious))
node.Appendf("Block ads: %s", helpers.BoolPtrToYesNo(b.BlockAds))
node.Appendf("Block surveillance: %s", helpers.BoolPtrToYesNo(b.BlockSurveillance))
if len(b.AllowedHosts) > 0 {
allowedHostsNode := node.Appendf("Allowed hosts:")

View File

@@ -5,7 +5,7 @@ import (
"fmt"
"time"
"github.com/qdm12/gosettings"
"github.com/qdm12/gluetun/internal/configuration/settings/helpers"
"github.com/qdm12/gotree"
)
@@ -54,8 +54,8 @@ func (d DoT) validate() (err error) {
func (d *DoT) copy() (copied DoT) {
return DoT{
Enabled: gosettings.CopyPointer(d.Enabled),
UpdatePeriod: gosettings.CopyPointer(d.UpdatePeriod),
Enabled: helpers.CopyPointer(d.Enabled),
UpdatePeriod: helpers.CopyPointer(d.UpdatePeriod),
Unbound: d.Unbound.copy(),
Blacklist: d.Blacklist.copy(),
}
@@ -64,8 +64,8 @@ func (d *DoT) copy() (copied DoT) {
// mergeWith merges the other settings into any
// unset field of the receiver settings object.
func (d *DoT) mergeWith(other DoT) {
d.Enabled = gosettings.MergeWithPointer(d.Enabled, other.Enabled)
d.UpdatePeriod = gosettings.MergeWithPointer(d.UpdatePeriod, other.UpdatePeriod)
d.Enabled = helpers.MergeWithPointer(d.Enabled, other.Enabled)
d.UpdatePeriod = helpers.MergeWithPointer(d.UpdatePeriod, other.UpdatePeriod)
d.Unbound.mergeWith(other.Unbound)
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.
func (d *DoT) overrideWith(other DoT) {
d.Enabled = gosettings.OverrideWithPointer(d.Enabled, other.Enabled)
d.UpdatePeriod = gosettings.OverrideWithPointer(d.UpdatePeriod, other.UpdatePeriod)
d.Enabled = helpers.OverrideWithPointer(d.Enabled, other.Enabled)
d.UpdatePeriod = helpers.OverrideWithPointer(d.UpdatePeriod, other.UpdatePeriod)
d.Unbound.overrideWith(other.Unbound)
d.Blacklist.overrideWith(other.Blacklist)
}
func (d *DoT) setDefaults() {
d.Enabled = gosettings.DefaultPointer(d.Enabled, true)
d.Enabled = helpers.DefaultPointer(d.Enabled, true)
const defaultUpdatePeriod = 24 * time.Hour
d.UpdatePeriod = gosettings.DefaultPointer(d.UpdatePeriod, defaultUpdatePeriod)
d.UpdatePeriod = helpers.DefaultPointer(d.UpdatePeriod, defaultUpdatePeriod)
d.Unbound.setDefaults()
d.Blacklist.setDefaults()
}
@@ -95,7 +95,7 @@ func (d DoT) String() string {
func (d DoT) toLinesNode() (node *gotree.Node) {
node = gotree.New("DNS over TLS settings:")
node.Appendf("Enabled: %s", gosettings.BoolToYesNo(d.Enabled))
node.Appendf("Enabled: %s", helpers.BoolPtrToYesNo(d.Enabled))
if !*d.Enabled {
return node
}

View File

@@ -4,7 +4,7 @@ import (
"fmt"
"net/netip"
"github.com/qdm12/gosettings"
"github.com/qdm12/gluetun/internal/configuration/settings/helpers"
"github.com/qdm12/gotree"
)
@@ -40,11 +40,11 @@ func hasZeroPort(ports []uint16) (has bool) {
func (f *Firewall) copy() (copied Firewall) {
return Firewall{
VPNInputPorts: gosettings.CopySlice(f.VPNInputPorts),
InputPorts: gosettings.CopySlice(f.InputPorts),
OutboundSubnets: gosettings.CopySlice(f.OutboundSubnets),
Enabled: gosettings.CopyPointer(f.Enabled),
Debug: gosettings.CopyPointer(f.Debug),
VPNInputPorts: helpers.CopySlice(f.VPNInputPorts),
InputPorts: helpers.CopySlice(f.InputPorts),
OutboundSubnets: helpers.CopySlice(f.OutboundSubnets),
Enabled: helpers.CopyPointer(f.Enabled),
Debug: helpers.CopyPointer(f.Debug),
}
}
@@ -53,27 +53,27 @@ func (f *Firewall) copy() (copied Firewall) {
// It merges values of slices together, even if they
// are set in the receiver settings.
func (f *Firewall) mergeWith(other Firewall) {
f.VPNInputPorts = gosettings.MergeWithSlice(f.VPNInputPorts, other.VPNInputPorts)
f.InputPorts = gosettings.MergeWithSlice(f.InputPorts, other.InputPorts)
f.OutboundSubnets = gosettings.MergeWithSlice(f.OutboundSubnets, other.OutboundSubnets)
f.Enabled = gosettings.MergeWithPointer(f.Enabled, other.Enabled)
f.Debug = gosettings.MergeWithPointer(f.Debug, other.Debug)
f.VPNInputPorts = helpers.MergeSlices(f.VPNInputPorts, other.VPNInputPorts)
f.InputPorts = helpers.MergeSlices(f.InputPorts, other.InputPorts)
f.OutboundSubnets = helpers.MergeSlices(f.OutboundSubnets, other.OutboundSubnets)
f.Enabled = helpers.MergeWithPointer(f.Enabled, other.Enabled)
f.Debug = helpers.MergeWithPointer(f.Debug, other.Debug)
}
// overrideWith overrides fields of the receiver
// settings object with any field set in the other
// settings.
func (f *Firewall) overrideWith(other Firewall) {
f.VPNInputPorts = gosettings.OverrideWithSlice(f.VPNInputPorts, other.VPNInputPorts)
f.InputPorts = gosettings.OverrideWithSlice(f.InputPorts, other.InputPorts)
f.OutboundSubnets = gosettings.OverrideWithSlice(f.OutboundSubnets, other.OutboundSubnets)
f.Enabled = gosettings.OverrideWithPointer(f.Enabled, other.Enabled)
f.Debug = gosettings.OverrideWithPointer(f.Debug, other.Debug)
f.VPNInputPorts = helpers.OverrideWithSlice(f.VPNInputPorts, other.VPNInputPorts)
f.InputPorts = helpers.OverrideWithSlice(f.InputPorts, other.InputPorts)
f.OutboundSubnets = helpers.OverrideWithSlice(f.OutboundSubnets, other.OutboundSubnets)
f.Enabled = helpers.OverrideWithPointer(f.Enabled, other.Enabled)
f.Debug = helpers.OverrideWithPointer(f.Debug, other.Debug)
}
func (f *Firewall) setDefaults() {
f.Enabled = gosettings.DefaultPointer(f.Enabled, true)
f.Debug = gosettings.DefaultPointer(f.Debug, false)
f.Enabled = helpers.DefaultPointer(f.Enabled, true)
f.Debug = helpers.DefaultPointer(f.Debug, false)
}
func (f Firewall) String() string {
@@ -83,7 +83,7 @@ func (f Firewall) String() string {
func (f Firewall) toLinesNode() (node *gotree.Node) {
node = gotree.New("Firewall settings:")
node.Appendf("Enabled: %s", gosettings.BoolToYesNo(f.Enabled))
node.Appendf("Enabled: %s", helpers.BoolPtrToYesNo(f.Enabled))
if !*f.Enabled {
return node
}

View File

@@ -5,7 +5,7 @@ import (
"os"
"time"
"github.com/qdm12/gosettings"
"github.com/qdm12/gluetun/internal/configuration/settings/helpers"
"github.com/qdm12/gotree"
"github.com/qdm12/govalid/address"
)
@@ -37,7 +37,7 @@ type Health struct {
func (h Health) Validate() (err error) {
uid := os.Getuid()
err = address.Validate(h.ServerAddress,
_, err = address.Validate(h.ServerAddress,
address.OptionListening(uid))
if err != nil {
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
// unset field of the receiver settings object.
func (h *Health) MergeWith(other Health) {
h.ServerAddress = gosettings.MergeWithString(h.ServerAddress, other.ServerAddress)
h.ReadHeaderTimeout = gosettings.MergeWithNumber(h.ReadHeaderTimeout, other.ReadHeaderTimeout)
h.ReadTimeout = gosettings.MergeWithNumber(h.ReadTimeout, other.ReadTimeout)
h.TargetAddress = gosettings.MergeWithString(h.TargetAddress, other.TargetAddress)
h.SuccessWait = gosettings.MergeWithNumber(h.SuccessWait, other.SuccessWait)
h.ServerAddress = helpers.MergeWithString(h.ServerAddress, other.ServerAddress)
h.ReadHeaderTimeout = helpers.MergeWithNumber(h.ReadHeaderTimeout, other.ReadHeaderTimeout)
h.ReadTimeout = helpers.MergeWithNumber(h.ReadTimeout, other.ReadTimeout)
h.TargetAddress = helpers.MergeWithString(h.TargetAddress, other.TargetAddress)
h.SuccessWait = helpers.MergeWithNumber(h.SuccessWait, other.SuccessWait)
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.
func (h *Health) OverrideWith(other Health) {
h.ServerAddress = gosettings.OverrideWithString(h.ServerAddress, other.ServerAddress)
h.ReadHeaderTimeout = gosettings.OverrideWithNumber(h.ReadHeaderTimeout, other.ReadHeaderTimeout)
h.ReadTimeout = gosettings.OverrideWithNumber(h.ReadTimeout, other.ReadTimeout)
h.TargetAddress = gosettings.OverrideWithString(h.TargetAddress, other.TargetAddress)
h.SuccessWait = gosettings.OverrideWithNumber(h.SuccessWait, other.SuccessWait)
h.ServerAddress = helpers.OverrideWithString(h.ServerAddress, other.ServerAddress)
h.ReadHeaderTimeout = helpers.OverrideWithNumber(h.ReadHeaderTimeout, other.ReadHeaderTimeout)
h.ReadTimeout = helpers.OverrideWithNumber(h.ReadTimeout, other.ReadTimeout)
h.TargetAddress = helpers.OverrideWithString(h.TargetAddress, other.TargetAddress)
h.SuccessWait = helpers.OverrideWithNumber(h.SuccessWait, other.SuccessWait)
h.VPN.overrideWith(other.VPN)
}
func (h *Health) SetDefaults() {
h.ServerAddress = gosettings.DefaultString(h.ServerAddress, "127.0.0.1:9999")
h.ServerAddress = helpers.DefaultString(h.ServerAddress, "127.0.0.1:9999")
const defaultReadHeaderTimeout = 100 * time.Millisecond
h.ReadHeaderTimeout = gosettings.DefaultNumber(h.ReadHeaderTimeout, defaultReadHeaderTimeout)
h.ReadHeaderTimeout = helpers.DefaultNumber(h.ReadHeaderTimeout, defaultReadHeaderTimeout)
const defaultReadTimeout = 500 * time.Millisecond
h.ReadTimeout = gosettings.DefaultNumber(h.ReadTimeout, defaultReadTimeout)
h.TargetAddress = gosettings.DefaultString(h.TargetAddress, "cloudflare.com:443")
h.ReadTimeout = helpers.DefaultNumber(h.ReadTimeout, defaultReadTimeout)
h.TargetAddress = helpers.DefaultString(h.TargetAddress, "cloudflare.com:443")
const defaultSuccessWait = 5 * time.Second
h.SuccessWait = gosettings.DefaultNumber(h.SuccessWait, defaultSuccessWait)
h.SuccessWait = helpers.DefaultNumber(h.SuccessWait, defaultSuccessWait)
h.VPN.setDefaults()
}

View File

@@ -3,7 +3,7 @@ package settings
import (
"time"
"github.com/qdm12/gosettings"
"github.com/qdm12/gluetun/internal/configuration/settings/helpers"
"github.com/qdm12/gotree"
)
@@ -27,31 +27,31 @@ func (h HealthyWait) validate() (err error) {
// unset field of the receiver settings object.
func (h *HealthyWait) copy() (copied HealthyWait) {
return HealthyWait{
Initial: gosettings.CopyPointer(h.Initial),
Addition: gosettings.CopyPointer(h.Addition),
Initial: helpers.CopyPointer(h.Initial),
Addition: helpers.CopyPointer(h.Addition),
}
}
// mergeWith merges the other settings into any
// unset field of the receiver settings object.
func (h *HealthyWait) mergeWith(other HealthyWait) {
h.Initial = gosettings.MergeWithPointer(h.Initial, other.Initial)
h.Addition = gosettings.MergeWithPointer(h.Addition, other.Addition)
h.Initial = helpers.MergeWithPointer(h.Initial, other.Initial)
h.Addition = helpers.MergeWithPointer(h.Addition, other.Addition)
}
// overrideWith overrides fields of the receiver
// settings object with any field set in the other
// settings.
func (h *HealthyWait) overrideWith(other HealthyWait) {
h.Initial = gosettings.OverrideWithPointer(h.Initial, other.Initial)
h.Addition = gosettings.OverrideWithPointer(h.Addition, other.Addition)
h.Initial = helpers.OverrideWithPointer(h.Initial, other.Initial)
h.Addition = helpers.OverrideWithPointer(h.Addition, other.Addition)
}
func (h *HealthyWait) setDefaults() {
const initialDurationDefault = 6 * time.Second
const additionDurationDefault = 5 * time.Second
h.Initial = gosettings.DefaultPointer(h.Initial, initialDurationDefault)
h.Addition = gosettings.DefaultPointer(h.Addition, additionDurationDefault)
h.Initial = helpers.DefaultPointer(h.Initial, initialDurationDefault)
h.Addition = helpers.DefaultPointer(h.Addition, additionDurationDefault)
}
func (h HealthyWait) String() string {

View File

@@ -1,6 +1,12 @@
package helpers
func IsOneOf[T comparable](value T, choices ...T) (ok bool) {
import (
"errors"
"fmt"
"strings"
)
func IsOneOf(value string, choices ...string) (ok bool) {
for _, choice := range choices {
if value == choice {
return true
@@ -8,3 +14,39 @@ func IsOneOf[T comparable](value T, choices ...T) (ok bool) {
}
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

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

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

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

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

View File

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

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

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

@@ -0,0 +1,52 @@
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,5 +1,12 @@
package helpers
func BoolPtrToYesNo(b *bool) string {
if *b {
return "yes"
}
return "no"
}
func TCPPtrToString(tcp *bool) string {
if *tcp {
return "TCP"

View File

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

View File

@@ -1,7 +1,7 @@
package settings
import (
"github.com/qdm12/gosettings"
"github.com/qdm12/gluetun/internal/configuration/settings/helpers"
"github.com/qdm12/gotree"
"github.com/qdm12/log"
)
@@ -19,25 +19,25 @@ func (l Log) validate() (err error) {
func (l *Log) copy() (copied Log) {
return Log{
Level: gosettings.CopyPointer(l.Level),
Level: helpers.CopyPointer(l.Level),
}
}
// mergeWith merges the other settings into any
// unset field of the receiver settings object.
func (l *Log) mergeWith(other Log) {
l.Level = gosettings.MergeWithPointer(l.Level, other.Level)
l.Level = helpers.MergeWithPointer(l.Level, other.Level)
}
// overrideWith overrides fields of the receiver
// settings object with any field set in the other
// settings.
func (l *Log) overrideWith(other Log) {
l.Level = gosettings.OverrideWithPointer(l.Level, other.Level)
l.Level = helpers.OverrideWithPointer(l.Level, other.Level)
}
func (l *Log) setDefaults() {
l.Level = gosettings.DefaultPointer(l.Level, log.LevelInfo)
l.Level = helpers.DefaultPointer(l.Level, log.LevelInfo)
}
func (l Log) String() string {

View File

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

View File

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

View File

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

View File

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

View File

@@ -5,7 +5,7 @@ import (
"path/filepath"
"time"
"github.com/qdm12/gosettings"
"github.com/qdm12/gluetun/internal/configuration/settings/helpers"
"github.com/qdm12/gotree"
)
@@ -42,25 +42,25 @@ func (p PublicIP) validate() (err error) {
func (p *PublicIP) copy() (copied PublicIP) {
return PublicIP{
Period: gosettings.CopyPointer(p.Period),
IPFilepath: gosettings.CopyPointer(p.IPFilepath),
Period: helpers.CopyPointer(p.Period),
IPFilepath: helpers.CopyPointer(p.IPFilepath),
}
}
func (p *PublicIP) mergeWith(other PublicIP) {
p.Period = gosettings.MergeWithPointer(p.Period, other.Period)
p.IPFilepath = gosettings.MergeWithPointer(p.IPFilepath, other.IPFilepath)
p.Period = helpers.MergeWithPointer(p.Period, other.Period)
p.IPFilepath = helpers.MergeWithPointer(p.IPFilepath, other.IPFilepath)
}
func (p *PublicIP) overrideWith(other PublicIP) {
p.Period = gosettings.OverrideWithPointer(p.Period, other.Period)
p.IPFilepath = gosettings.OverrideWithPointer(p.IPFilepath, other.IPFilepath)
p.Period = helpers.OverrideWithPointer(p.Period, other.Period)
p.IPFilepath = helpers.OverrideWithPointer(p.IPFilepath, other.IPFilepath)
}
func (p *PublicIP) setDefaults() {
const defaultPeriod = 12 * time.Hour
p.Period = gosettings.DefaultPointer(p.Period, defaultPeriod)
p.IPFilepath = gosettings.DefaultPointer(p.IPFilepath, "/tmp/gluetun/ip")
p.Period = helpers.DefaultPointer(p.Period, defaultPeriod)
p.IPFilepath = helpers.DefaultPointer(p.IPFilepath, "/tmp/gluetun/ip")
}
func (p PublicIP) String() string {

View File

@@ -6,7 +6,7 @@ import (
"os"
"strconv"
"github.com/qdm12/gosettings"
"github.com/qdm12/gluetun/internal/configuration/settings/helpers"
"github.com/qdm12/gotree"
)
@@ -43,29 +43,29 @@ func (c ControlServer) validate() (err error) {
func (c *ControlServer) copy() (copied ControlServer) {
return ControlServer{
Address: gosettings.CopyPointer(c.Address),
Log: gosettings.CopyPointer(c.Log),
Address: helpers.CopyPointer(c.Address),
Log: helpers.CopyPointer(c.Log),
}
}
// mergeWith merges the other settings into any
// unset field of the receiver settings object.
func (c *ControlServer) mergeWith(other ControlServer) {
c.Address = gosettings.MergeWithPointer(c.Address, other.Address)
c.Log = gosettings.MergeWithPointer(c.Log, other.Log)
c.Address = helpers.MergeWithPointer(c.Address, other.Address)
c.Log = helpers.MergeWithPointer(c.Log, other.Log)
}
// overrideWith overrides fields of the receiver
// settings object with any field set in the other
// settings.
func (c *ControlServer) overrideWith(other ControlServer) {
c.Address = gosettings.OverrideWithPointer(c.Address, other.Address)
c.Log = gosettings.OverrideWithPointer(c.Log, other.Log)
c.Address = helpers.OverrideWithPointer(c.Address, other.Address)
c.Log = helpers.OverrideWithPointer(c.Log, other.Log)
}
func (c *ControlServer) setDefaults() {
c.Address = gosettings.DefaultPointer(c.Address, ":8000")
c.Log = gosettings.DefaultPointer(c.Log, true)
c.Address = helpers.DefaultPointer(c.Address, ":8000")
c.Log = helpers.DefaultPointer(c.Log, true)
}
func (c ControlServer) String() string {
@@ -75,6 +75,6 @@ func (c ControlServer) String() string {
func (c ControlServer) toLinesNode() (node *gotree.Node) {
node = gotree.New("Control server settings:")
node.Appendf("Listening address: %s", *c.Address)
node.Appendf("Logging: %s", gosettings.BoolToYesNo(c.Log))
node.Appendf("Logging: %s", helpers.BoolPtrToYesNo(c.Log))
return node
}

View File

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

View File

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

View File

@@ -1,7 +1,7 @@
package settings
import (
"github.com/qdm12/gosettings"
"github.com/qdm12/gluetun/internal/configuration/settings/helpers"
"github.com/qdm12/gotree"
"github.com/qdm12/ss-server/pkg/tcpudp"
)
@@ -21,7 +21,7 @@ func (s Shadowsocks) validate() (err error) {
func (s *Shadowsocks) copy() (copied Shadowsocks) {
return Shadowsocks{
Enabled: gosettings.CopyPointer(s.Enabled),
Enabled: helpers.CopyPointer(s.Enabled),
Settings: s.Settings.Copy(),
}
}
@@ -29,20 +29,20 @@ func (s *Shadowsocks) copy() (copied Shadowsocks) {
// mergeWith merges the other settings into any
// unset field of the receiver settings object.
func (s *Shadowsocks) mergeWith(other Shadowsocks) {
s.Enabled = gosettings.MergeWithPointer(s.Enabled, other.Enabled)
s.Settings = s.Settings.MergeWith(other.Settings)
s.Enabled = helpers.MergeWithPointer(s.Enabled, other.Enabled)
s.Settings.MergeWith(other.Settings)
}
// overrideWith overrides fields of the receiver
// settings object with any field set in the other
// settings.
func (s *Shadowsocks) overrideWith(other Shadowsocks) {
s.Enabled = gosettings.OverrideWithPointer(s.Enabled, other.Enabled)
s.Enabled = helpers.OverrideWithPointer(s.Enabled, other.Enabled)
s.Settings.OverrideWith(other.Settings)
}
func (s *Shadowsocks) setDefaults() {
s.Enabled = gosettings.DefaultPointer(s.Enabled, false)
s.Enabled = helpers.DefaultPointer(s.Enabled, false)
s.Settings.SetDefaults()
}
@@ -53,16 +53,16 @@ func (s Shadowsocks) String() string {
func (s Shadowsocks) toLinesNode() (node *gotree.Node) {
node = gotree.New("Shadowsocks server settings:")
node.Appendf("Enabled: %s", gosettings.BoolToYesNo(s.Enabled))
node.Appendf("Enabled: %s", helpers.BoolPtrToYesNo(s.Enabled))
if !*s.Enabled {
return node
}
// 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("Password: %s", gosettings.ObfuscateKey(*s.Password))
node.Appendf("Log addresses: %s", gosettings.BoolToYesNo(s.LogAddresses))
node.Appendf("Password: %s", helpers.ObfuscatePassword(*s.Password))
node.Appendf("Log addresses: %s", helpers.BoolPtrToYesNo(s.LogAddresses))
return node
}

View File

@@ -1,7 +1,7 @@
package settings
import (
"github.com/qdm12/gosettings"
"github.com/qdm12/gluetun/internal/configuration/settings/helpers"
"github.com/qdm12/gotree"
)
@@ -19,28 +19,28 @@ func (s System) validate() (err error) {
func (s *System) copy() (copied System) {
return System{
PUID: gosettings.CopyPointer(s.PUID),
PGID: gosettings.CopyPointer(s.PGID),
PUID: helpers.CopyPointer(s.PUID),
PGID: helpers.CopyPointer(s.PGID),
Timezone: s.Timezone,
}
}
func (s *System) mergeWith(other System) {
s.PUID = gosettings.MergeWithPointer(s.PUID, other.PUID)
s.PGID = gosettings.MergeWithPointer(s.PGID, other.PGID)
s.Timezone = gosettings.MergeWithString(s.Timezone, other.Timezone)
s.PUID = helpers.MergeWithPointer(s.PUID, other.PUID)
s.PGID = helpers.MergeWithPointer(s.PGID, other.PGID)
s.Timezone = helpers.MergeWithString(s.Timezone, other.Timezone)
}
func (s *System) overrideWith(other System) {
s.PUID = gosettings.OverrideWithPointer(s.PUID, other.PUID)
s.PGID = gosettings.OverrideWithPointer(s.PGID, other.PGID)
s.Timezone = gosettings.OverrideWithString(s.Timezone, other.Timezone)
s.PUID = helpers.OverrideWithPointer(s.PUID, other.PUID)
s.PGID = helpers.OverrideWithPointer(s.PGID, other.PGID)
s.Timezone = helpers.OverrideWithString(s.Timezone, other.Timezone)
}
func (s *System) setDefaults() {
const defaultID = 1000
s.PUID = gosettings.DefaultPointer(s.PUID, defaultID)
s.PGID = gosettings.DefaultPointer(s.PGID, defaultID)
s.PUID = helpers.DefaultPointer(s.PUID, defaultID)
s.PGID = helpers.DefaultPointer(s.PGID, defaultID)
}
func (s System) String() string {

View File

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

View File

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

View File

@@ -1,7 +1,7 @@
package settings
import (
"github.com/qdm12/gosettings"
"github.com/qdm12/gluetun/internal/configuration/settings/helpers"
"github.com/qdm12/gotree"
)
@@ -19,25 +19,25 @@ func (v Version) validate() (err error) {
func (v *Version) copy() (copied Version) {
return Version{
Enabled: gosettings.CopyPointer(v.Enabled),
Enabled: helpers.CopyPointer(v.Enabled),
}
}
// mergeWith merges the other settings into any
// unset field of the receiver settings object.
func (v *Version) mergeWith(other Version) {
v.Enabled = gosettings.MergeWithPointer(v.Enabled, other.Enabled)
v.Enabled = helpers.MergeWithPointer(v.Enabled, other.Enabled)
}
// overrideWith overrides fields of the receiver
// settings object with any field set in the other
// settings.
func (v *Version) overrideWith(other Version) {
v.Enabled = gosettings.OverrideWithPointer(v.Enabled, other.Enabled)
v.Enabled = helpers.OverrideWithPointer(v.Enabled, other.Enabled)
}
func (v *Version) setDefaults() {
v.Enabled = gosettings.DefaultPointer(v.Enabled, true)
v.Enabled = helpers.DefaultPointer(v.Enabled, true)
}
func (v Version) String() string {
@@ -47,7 +47,7 @@ func (v Version) String() string {
func (v Version) toLinesNode() (node *gotree.Node) {
node = gotree.New("Version settings:")
node.Appendf("Enabled: %s", gosettings.BoolToYesNo(v.Enabled))
node.Appendf("Enabled: %s", helpers.BoolPtrToYesNo(v.Enabled))
return node
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -1,36 +1,80 @@
package env
import (
"errors"
"fmt"
"net/netip"
"strconv"
"github.com/qdm12/gluetun/internal/configuration/settings"
"github.com/qdm12/gosettings/sources/env"
)
func (s *Source) readFirewall() (firewall settings.Firewall, err error) {
firewall.VPNInputPorts, err = s.env.CSVUint16("FIREWALL_VPN_INPUT_PORTS")
vpnInputPortStrings := envToCSV("FIREWALL_VPN_INPUT_PORTS")
firewall.VPNInputPorts, err = stringsToPorts(vpnInputPortStrings)
if err != nil {
return firewall, err
return firewall, fmt.Errorf("environment variable FIREWALL_VPN_INPUT_PORTS: %w", err)
}
firewall.InputPorts, err = s.env.CSVUint16("FIREWALL_INPUT_PORTS")
inputPortStrings := envToCSV("FIREWALL_INPUT_PORTS")
firewall.InputPorts, err = stringsToPorts(inputPortStrings)
if err != nil {
return firewall, err
return firewall, fmt.Errorf("environment variable FIREWALL_INPUT_PORTS: %w", err)
}
firewall.OutboundSubnets, err = s.env.CSVNetipPrefixes("FIREWALL_OUTBOUND_SUBNETS",
env.RetroKeys("EXTRA_SUBNETS"))
outboundSubnetsKey, _ := s.getEnvWithRetro("FIREWALL_OUTBOUND_SUBNETS", "EXTRA_SUBNETS")
outboundSubnetStrings := envToCSV(outboundSubnetsKey)
firewall.OutboundSubnets, err = stringsToNetipPrefixes(outboundSubnetStrings)
if err != nil {
return firewall, err
return firewall, fmt.Errorf("environment variable %s: %w", outboundSubnetsKey, err)
}
firewall.Enabled, err = s.env.BoolPtr("FIREWALL")
firewall.Enabled, err = envToBoolPtr("FIREWALL")
if err != nil {
return firewall, err
return firewall, fmt.Errorf("environment variable FIREWALL: %w", err)
}
firewall.Debug, err = s.env.BoolPtr("FIREWALL_DEBUG")
firewall.Debug, err = envToBoolPtr("FIREWALL_DEBUG")
if err != nil {
return firewall, err
return firewall, fmt.Errorf("environment variable FIREWALL_DEBUG: %w", err)
}
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,35 +1,51 @@
package env
import (
"fmt"
"time"
"github.com/qdm12/gluetun/internal/configuration/settings"
"github.com/qdm12/gosettings/sources/env"
)
func (s *Source) ReadHealth() (health settings.Health, err error) {
health.ServerAddress = s.env.String("HEALTH_SERVER_ADDRESS")
health.TargetAddress = s.env.String("HEALTH_TARGET_ADDRESS",
env.RetroKeys("HEALTH_ADDRESS_TO_PING"))
health.ServerAddress = getCleanedEnv("HEALTH_SERVER_ADDRESS")
_, health.TargetAddress = s.getEnvWithRetro("HEALTH_TARGET_ADDRESS", "HEALTH_ADDRESS_TO_PING")
successWaitPtr, err := s.env.DurationPtr("HEALTH_SUCCESS_WAIT_DURATION")
successWaitPtr, err := envToDurationPtr("HEALTH_SUCCESS_WAIT_DURATION")
if err != nil {
return health, err
return health, fmt.Errorf("environment variable HEALTH_SUCCESS_WAIT_DURATION: %w", err)
} else if successWaitPtr != nil {
health.SuccessWait = *successWaitPtr
}
health.VPN.Initial, err = s.env.DurationPtr(
health.VPN.Initial, err = s.readDurationWithRetro(
"HEALTH_VPN_DURATION_INITIAL",
env.RetroKeys("HEALTH_OPENVPN_DURATION_INITIAL"))
"HEALTH_OPENVPN_DURATION_INITIAL")
if err != nil {
return health, err
}
health.VPN.Addition, err = s.env.DurationPtr(
health.VPN.Addition, err = s.readDurationWithRetro(
"HEALTH_VPN_DURATION_ADDITION",
env.RetroKeys("HEALTH_OPENVPN_DURATION_ADDITION"))
"HEALTH_OPENVPN_DURATION_ADDITION")
if err != nil {
return health, err
}
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,10 +3,127 @@ package env
import (
"fmt"
"os"
"strconv"
"strings"
"time"
"github.com/qdm12/gosettings/sources/env"
"github.com/qdm12/govalid/binary"
"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) {
newErr = err
for _, envKey := range envKeys {
@@ -18,16 +135,6 @@ func unsetEnvKeys(envKeys []string, err error) (newErr error) {
return newErr
}
func ptrTo[T any](value T) *T {
return &value
}
func firstKeySet(e env.Env, keys ...string) (firstKeySet string) {
for _, key := range keys {
value := e.Get(key)
if value != nil {
return key
}
}
return ""
}
func stringPtr(s string) *string { return &s }
func uint32Ptr(n uint32) *uint32 { return &n }
func boolPtr(b bool) *bool { return &b }

View File

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

View File

@@ -9,8 +9,8 @@ import (
"github.com/qdm12/log"
)
func (s *Source) readLog() (log settings.Log, err error) {
log.Level, err = s.readLogLevel()
func readLog() (log settings.Log, err error) {
log.Level, err = readLogLevel()
if err != nil {
return log, err
}
@@ -18,14 +18,14 @@ func (s *Source) readLog() (log settings.Log, err error) {
return log, nil
}
func (s *Source) readLogLevel() (level *log.Level, err error) {
value := s.env.String("LOG_LEVEL")
if value == "" {
func readLogLevel() (level *log.Level, err error) {
s := getCleanedEnv("LOG_LEVEL")
if s == "" {
return nil, nil //nolint:nilnil
}
level = new(log.Level)
*level, err = parseLogLevel(value)
*level, err = parseLogLevel(s)
if err != nil {
return nil, fmt.Errorf("environment variable LOG_LEVEL: %w", err)
}

View File

@@ -1,10 +1,11 @@
package env
import (
"fmt"
"strings"
"github.com/qdm12/gluetun/internal/configuration/settings"
"github.com/qdm12/gosettings/sources/env"
"github.com/qdm12/govalid/binary"
)
func (s *Source) readOpenVPN() (
@@ -14,64 +15,113 @@ func (s *Source) readOpenVPN() (
"OPENVPN_KEY_PASSPHRASE", "OPENVPN_ENCRYPTED_KEY"}, err)
}()
openVPN.Version = s.env.String("OPENVPN_VERSION")
openVPN.User = s.env.Get("OPENVPN_USER",
env.RetroKeys("USER"), env.ForceLowercase(false))
openVPN.Password = s.env.Get("OPENVPN_PASSWORD",
env.RetroKeys("PASSWORD"), env.ForceLowercase(false))
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")
openVPN.Cert = s.env.Get("OPENVPN_CERT", env.ForceLowercase(false))
openVPN.Key = s.env.Get("OPENVPN_KEY", env.ForceLowercase(false))
openVPN.EncryptedKey = s.env.Get("OPENVPN_ENCRYPTED_KEY", env.ForceLowercase(false))
openVPN.KeyPassphrase = s.env.Get("OPENVPN_KEY_PASSPHRASE", env.ForceLowercase(false))
openVPN.Version = getCleanedEnv("OPENVPN_VERSION")
openVPN.User = s.readOpenVPNUser()
openVPN.Password = s.readOpenVPNPassword()
confFile := getCleanedEnv("OPENVPN_CUSTOM_CONFIG")
if confFile != "" {
openVPN.ConfFile = &confFile
}
ciphersKey, _ := s.getEnvWithRetro("OPENVPN_CIPHERS", "OPENVPN_CIPHER")
openVPN.Ciphers = envToCSV(ciphersKey)
auth := getCleanedEnv("OPENVPN_AUTH")
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.MSSFix, err = s.env.Uint16Ptr("OPENVPN_MSSFIX")
openVPN.MSSFix, err = envToUint16Ptr("OPENVPN_MSSFIX")
if err != nil {
return openVPN, err
return openVPN, fmt.Errorf("environment variable OPENVPN_MSSFIX: %w", err)
}
openVPN.Interface = s.env.String("VPN_INTERFACE",
env.RetroKeys("OPENVPN_INTERFACE"), env.ForceLowercase(false))
_, openVPN.Interface = s.getEnvWithRetro("VPN_INTERFACE", "OPENVPN_INTERFACE")
openVPN.ProcessUser, err = s.readOpenVPNProcessUser()
if err != nil {
return openVPN, err
}
openVPN.Verbosity, err = s.env.IntPtr("OPENVPN_VERBOSITY")
openVPN.Verbosity, err = envToIntPtr("OPENVPN_VERBOSITY")
if err != nil {
return openVPN, err
return openVPN, fmt.Errorf("environment variable OPENVPN_VERBOSITY: %w", err)
}
flagsPtr := s.env.Get("OPENVPN_FLAGS", env.ForceLowercase(false))
if flagsPtr != nil {
openVPN.Flags = strings.Fields(*flagsPtr)
flagsStr := getCleanedEnv("OPENVPN_FLAGS")
if flagsStr != "" {
openVPN.Flags = strings.Fields(flagsStr)
}
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) {
return s.env.Get(
_, preset := s.getEnvWithRetro(
"PRIVATE_INTERNET_ACCESS_OPENVPN_ENCRYPTION_PRESET",
env.RetroKeys("ENCRYPTION", "PIA_ENCRYPTION"))
"PIA_ENCRYPTION", "ENCRYPTION")
if preset != "" {
return &preset
}
return nil
}
func (s *Source) readOpenVPNProcessUser() (processUser string, err error) {
value, err := s.env.BoolPtr("OPENVPN_ROOT") // Retro-compatibility
if err != nil {
return "", err
} else if value != nil {
if *value {
return "root", nil
}
const defaultNonRootUser = "nonrootuser"
return defaultNonRootUser, nil
key, value := s.getEnvWithRetro("OPENVPN_PROCESS_USER", "OPENVPN_ROOT")
if key == "OPENVPN_PROCESS_USER" {
return value, nil
}
return s.env.String("OPENVPN_PROCESS_USER"), nil
// Retro-compatibility
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,20 +7,22 @@ import (
"github.com/qdm12/gluetun/internal/configuration/settings"
"github.com/qdm12/gluetun/internal/constants"
"github.com/qdm12/gosettings/sources/env"
"github.com/qdm12/govalid/port"
)
func (s *Source) readOpenVPNSelection() (
selection settings.OpenVPNSelection, err error) {
selection.ConfFile = s.env.Get("OPENVPN_CUSTOM_CONFIG", env.ForceLowercase(false))
confFile := getCleanedEnv("OPENVPN_CUSTOM_CONFIG")
if confFile != "" {
selection.ConfFile = &confFile
}
selection.TCP, err = s.readOpenVPNProtocol()
if err != nil {
return selection, err
}
selection.CustomPort, err = s.env.Uint16Ptr("VPN_ENDPOINT_PORT",
env.RetroKeys("PORT", "OPENVPN_PORT"))
selection.CustomPort, err = s.readOpenVPNCustomPort()
if err != nil {
return selection, err
}
@@ -33,24 +35,32 @@ func (s *Source) readOpenVPNSelection() (
var ErrOpenVPNProtocolNotValid = errors.New("OpenVPN protocol is not valid")
func (s *Source) readOpenVPNProtocol() (tcp *bool, err error) {
const currentKey = "OPENVPN_PROTOCOL"
envKey := firstKeySet(s.env, "PROTOCOL", currentKey)
switch envKey {
envKey, protocol := s.getEnvWithRetro("OPENVPN_PROTOCOL", "PROTOCOL")
switch strings.ToLower(protocol) {
case "":
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:
return ptrTo(false), nil
return boolPtr(false), nil
case constants.TCP:
return ptrTo(true), nil
return boolPtr(true), nil
default:
return nil, fmt.Errorf("environment variable %s: %w: %s",
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,27 +1,29 @@
package env
import (
"fmt"
"github.com/qdm12/gluetun/internal/configuration/settings"
"github.com/qdm12/gosettings/sources/env"
)
func (s *Source) readPortForward() (
portForwarding settings.PortForwarding, err error) {
portForwarding.Enabled, err = s.env.BoolPtr("VPN_PORT_FORWARDING",
env.RetroKeys(
"PORT_FORWARDING",
"PRIVATE_INTERNET_ACCESS_VPN_PORT_FORWARDING",
))
key, _ := s.getEnvWithRetro(
"VPN_PORT_FORWARDING",
"PRIVATE_INTERNET_ACCESS_VPN_PORT_FORWARDING",
"PORT_FORWARDING")
portForwarding.Enabled, err = envToBoolPtr(key)
if err != nil {
return portForwarding, err
return portForwarding, fmt.Errorf("environment variable %s: %w", key, err)
}
portForwarding.Filepath = s.env.Get("VPN_PORT_FORWARDING_STATUS_FILE",
env.ForceLowercase(false),
env.RetroKeys(
"PORT_FORWARDING_STATUS_FILE",
"PRIVATE_INTERNET_ACCESS_VPN_PORT_FORWARDING_STATUS_FILE",
))
_, value := s.getEnvWithRetro(
"VPN_PORT_FORWARDING_STATUS_FILE",
"PRIVATE_INTERNET_ACCESS_VPN_PORT_FORWARDING_STATUS_FILE",
"PORT_FORWARDING_STATUS_FILE")
if value != "" {
portForwarding.Filepath = stringPtr(value)
}
return portForwarding, nil
}

View File

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

View File

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

View File

@@ -1,18 +1,42 @@
package env
import (
"fmt"
"time"
"github.com/qdm12/gluetun/internal/configuration/settings"
"github.com/qdm12/gosettings/sources/env"
)
func (s *Source) readPublicIP() (publicIP settings.PublicIP, err error) {
publicIP.Period, err = s.env.DurationPtr("PUBLICIP_PERIOD")
publicIP.Period, err = readPublicIPPeriod()
if err != nil {
return publicIP, err
}
publicIP.IPFilepath = s.env.Get("PUBLICIP_FILE",
env.ForceLowercase(false), env.RetroKeys("IP_STATUS_FILE"))
publicIP.IPFilepath = s.readPublicIPFilepath()
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,16 +1,11 @@
package env
import (
"os"
"github.com/qdm12/gluetun/internal/configuration/settings"
"github.com/qdm12/gosettings/sources/env"
)
type Source struct {
env env.Env
warner Warner
handleDeprecatedKey func(deprecatedKey, newKey string)
warner Warner
}
type Warner interface {
@@ -18,16 +13,8 @@ type Warner interface {
}
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{
env: *env.New(os.Environ(), handleDeprecatedKey),
warner: warner,
handleDeprecatedKey: handleDeprecatedKey,
warner: warner,
}
}
@@ -59,7 +46,7 @@ func (s *Source) Read() (settings settings.Settings, err error) {
return settings, err
}
settings.Log, err = s.readLog()
settings.Log, err = readLog()
if err != nil {
return settings, err
}
@@ -69,12 +56,12 @@ func (s *Source) Read() (settings settings.Settings, err error) {
return settings, err
}
settings.Updater, err = s.readUpdater()
settings.Updater, err = readUpdater()
if err != nil {
return settings, err
}
settings.Version, err = s.readVersion()
settings.Version, err = readVersion()
if err != nil {
return settings, err
}
@@ -94,10 +81,37 @@ func (s *Source) Read() (settings settings.Settings, err error) {
return settings, err
}
settings.Pprof, err = s.readPprof()
settings.Pprof, err = readPprof()
if err != nil {
return settings, err
}
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,11 +1,14 @@
package env
import (
"fmt"
"github.com/qdm12/gluetun/internal/configuration/settings"
"github.com/qdm12/govalid/binary"
)
func (s *Source) readControlServer() (controlServer settings.ControlServer, err error) {
controlServer.Log, err = s.env.BoolPtr("HTTP_CONTROL_SERVER_LOG")
controlServer.Log, err = readControlServerLog()
if err != nil {
return controlServer, err
}
@@ -15,17 +18,31 @@ func (s *Source) readControlServer() (controlServer settings.ControlServer, err
return controlServer, nil
}
func (s *Source) readControlServerAddress() (address *string) {
const currentKey = "HTTP_CONTROL_SERVER_ADDRESS"
key := firstKeySet(s.env, "CONTROL_SERVER_ADDRESS", currentKey)
if key == currentKey {
return s.env.Get(key)
func readControlServerLog() (enabled *bool, err error) {
s := getCleanedEnv("HTTP_CONTROL_SERVER_LOG")
if s == "" {
return nil, nil //nolint:nilnil
}
s.handleDeprecatedKey(key, currentKey)
value := s.env.Get("CONTROL_SERVER_ADDRESS")
if value == nil {
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) {
key, value := s.getEnvWithRetro("HTTP_CONTROL_SERVER_ADDRESS", "HTTP_CONTROL_SERVER_PORT")
if value == "" {
return nil
}
return ptrTo(":" + *value)
if key == "HTTP_CONTROL_SERVER_ADDRESS" {
return &value
}
address = new(string)
*address = ":" + value
return address
}

View File

@@ -2,69 +2,98 @@ package env
import (
"errors"
"fmt"
"net/netip"
"strconv"
"strings"
"github.com/qdm12/gluetun/internal/configuration/settings"
"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) (
ss settings.ServerSelection, err error) {
ss.VPN = vpnType
ss.TargetIP, err = s.env.NetipAddr("VPN_ENDPOINT_IP",
env.RetroKeys("OPENVPN_TARGET_IP"))
ss.TargetIP, err = s.readOpenVPNTargetIP()
if err != nil {
return ss, err
}
ss.Countries = s.env.CSV("SERVER_COUNTRIES", env.RetroKeys("COUNTRY"))
countriesKey, _ := s.getEnvWithRetro("SERVER_COUNTRIES", "COUNTRY")
ss.Countries = envToCSV(countriesKey)
if vpnProvider == providers.Cyberghost && len(ss.Countries) == 0 {
// Retro-compatibility for Cyberghost using the REGION variable
ss.Countries = s.env.CSV("REGION")
ss.Countries = envToCSV("REGION")
if len(ss.Countries) > 0 {
s.handleDeprecatedKey("REGION", "SERVER_COUNTRIES")
s.onRetroActive("REGION", "SERVER_COUNTRIES")
}
}
ss.Regions = s.env.CSV("SERVER_REGIONS", env.RetroKeys("REGION"))
ss.Cities = s.env.CSV("SERVER_CITIES", env.RetroKeys("CITY"))
ss.ISPs = s.env.CSV("ISP")
ss.Hostnames = s.env.CSV("SERVER_HOSTNAMES", env.RetroKeys("SERVER_HOSTNAME"))
ss.Names = s.env.CSV("SERVER_NAMES", env.RetroKeys("SERVER_NAME"))
ss.Numbers, err = s.env.CSVUint16("SERVER_NUMBER")
if err != nil {
return ss, err
regionsKey, _ := s.getEnvWithRetro("SERVER_REGIONS", "REGION")
ss.Regions = envToCSV(regionsKey)
citiesKey, _ := s.getEnvWithRetro("SERVER_CITIES", "CITY")
ss.Cities = envToCSV(citiesKey)
ss.ISPs = envToCSV("ISP")
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
ss.OwnedOnly, err = s.env.BoolPtr("OWNED_ONLY", env.RetroKeys("OWNED"))
ss.OwnedOnly, err = s.readOwnedOnly()
if err != nil {
return ss, err
}
// VPNUnlimited and ProtonVPN only
ss.FreeOnly, err = s.env.BoolPtr("FREE_ONLY")
ss.FreeOnly, err = envToBoolPtr("FREE_ONLY")
if err != nil {
return ss, err
return ss, fmt.Errorf("environment variable FREE_ONLY: %w", err)
}
// VPNSecure only
ss.PremiumOnly, err = s.env.BoolPtr("PREMIUM_ONLY")
ss.PremiumOnly, err = envToBoolPtr("PREMIUM_ONLY")
if err != nil {
return ss, err
return ss, fmt.Errorf("environment variable PREMIUM_ONLY: %w", err)
}
// VPNUnlimited only
ss.MultiHopOnly, err = s.env.BoolPtr("MULTIHOP_ONLY")
ss.MultiHopOnly, err = envToBoolPtr("MULTIHOP_ONLY")
if err != nil {
return ss, err
return ss, fmt.Errorf("environment variable MULTIHOP_ONLY: %w", err)
}
// VPNUnlimited only
ss.MultiHopOnly, err = s.env.BoolPtr("STREAM_ONLY")
ss.MultiHopOnly, err = envToBoolPtr("STREAM_ONLY")
if err != nil {
return ss, err
return ss, fmt.Errorf("environment variable STREAM_ONLY: %w", err)
}
ss.OpenVPN, err = s.readOpenVPNSelection()
@@ -83,3 +112,26 @@ func (s *Source) readServerSelection(vpnProvider, vpnType string) (
var (
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,41 +2,43 @@ package env
import (
"fmt"
"strings"
"github.com/qdm12/gluetun/internal/configuration/settings"
"github.com/qdm12/gosettings/sources/env"
)
func (s *Source) readShadowsocks() (shadowsocks settings.Shadowsocks, err error) {
shadowsocks.Enabled, err = s.env.BoolPtr("SHADOWSOCKS")
shadowsocks.Enabled, err = envToBoolPtr("SHADOWSOCKS")
if err != nil {
return shadowsocks, err
return shadowsocks, fmt.Errorf("environment variable SHADOWSOCKS: %w", err)
}
shadowsocks.Address, err = s.readShadowsocksAddress()
shadowsocks.Address = s.readShadowsocksAddress()
shadowsocks.LogAddresses, err = envToBoolPtr("SHADOWSOCKS_LOG")
if err != nil {
return shadowsocks, err
return shadowsocks, fmt.Errorf("environment variable SHADOWSOCKS_LOG: %w", err)
}
shadowsocks.LogAddresses, err = s.env.BoolPtr("SHADOWSOCKS_LOG")
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))
shadowsocks.CipherName = s.readShadowsocksCipher()
shadowsocks.Password = envToStringPtr("SHADOWSOCKS_PASSWORD")
return shadowsocks, nil
}
func (s *Source) readShadowsocksAddress() (address *string, err error) {
const currentKey = "SHADOWSOCKS_LISTENING_ADDRESS"
port, err := s.env.Uint16Ptr("SHADOWSOCKS_PORT") // retro-compatibility
if err != nil {
return nil, err
} else if port != nil {
s.handleDeprecatedKey("SHADOWSOCKS_PORT", currentKey)
return ptrTo(fmt.Sprintf(":%d", *port)), nil
func (s *Source) readShadowsocksAddress() (address string) {
key, value := s.getEnvWithRetro("SHADOWSOCKS_LISTENING_ADDRESS", "SHADOWSOCKS_PORT")
if value == "" {
return ""
}
return s.env.Get(currentKey), nil
if key == "SHADOWSOCKS_LISTENING_ADDRESS" {
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,22 +1,55 @@
package env
import (
"errors"
"fmt"
"strconv"
"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) {
system.PUID, err = s.env.Uint32Ptr("PUID", env.RetroKeys("UID"))
system.PUID, err = s.readID("PUID", "UID")
if err != nil {
return system, err
}
system.PGID, err = s.env.Uint32Ptr("PGID", env.RetroKeys("GID"))
system.PGID, err = s.readID("PGID", "GID")
if err != nil {
return system, err
}
system.Timezone = s.env.String("TZ")
system.Timezone = getCleanedEnv("TZ")
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

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

View File

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

View File

@@ -1,14 +1,32 @@
package env
import (
"fmt"
"github.com/qdm12/gluetun/internal/configuration/settings"
"github.com/qdm12/govalid/binary"
)
func (s *Source) readVersion() (version settings.Version, err error) {
version.Enabled, err = s.env.BoolPtr("VERSION_INFORMATION")
func readVersion() (version settings.Version, err error) {
version.Enabled, err = readVersionEnabled()
if err != nil {
return version, err
}
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,12 +2,13 @@ package env
import (
"fmt"
"strings"
"github.com/qdm12/gluetun/internal/configuration/settings"
)
func (s *Source) readVPN() (vpn settings.VPN, err error) {
vpn.Type = s.env.String("VPN_TYPE")
vpn.Type = strings.ToLower(getCleanedEnv("VPN_TYPE"))
vpn.Provider, err = s.readProvider(vpn.Type)
if err != nil {

View File

@@ -1,29 +1,44 @@
package env
import (
"fmt"
"net/netip"
"os"
"strings"
"github.com/qdm12/gluetun/internal/configuration/settings"
"github.com/qdm12/gosettings/sources/env"
)
func (s *Source) readWireguard() (wireguard settings.Wireguard, err error) {
defer func() {
err = unsetEnvKeys([]string{"WIREGUARD_PRIVATE_KEY", "WIREGUARD_PRESHARED_KEY"}, err)
}()
wireguard.PrivateKey = s.env.Get("WIREGUARD_PRIVATE_KEY", env.ForceLowercase(false))
wireguard.PreSharedKey = s.env.Get("WIREGUARD_PRESHARED_KEY", env.ForceLowercase(false))
wireguard.Interface = s.env.String("VPN_INTERFACE",
env.RetroKeys("WIREGUARD_INTERFACE"), env.ForceLowercase(false))
wireguard.Implementation = s.env.String("WIREGUARD_IMPLEMENTATION")
wireguard.Addresses, err = s.env.CSVNetipPrefixes("WIREGUARD_ADDRESSES",
env.RetroKeys("WIREGUARD_ADDRESS"))
wireguard.PrivateKey = envToStringPtr("WIREGUARD_PRIVATE_KEY")
wireguard.PreSharedKey = envToStringPtr("WIREGUARD_PRESHARED_KEY")
_, wireguard.Interface = s.getEnvWithRetro("VPN_INTERFACE", "WIREGUARD_INTERFACE")
wireguard.Implementation = os.Getenv("WIREGUARD_IMPLEMENTATION")
wireguard.Addresses, err = s.readWireguardAddresses()
if err != nil {
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
}
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,23 +1,55 @@
package env
import (
"fmt"
"net/netip"
"github.com/qdm12/gluetun/internal/configuration/settings"
"github.com/qdm12/gosettings/sources/env"
"github.com/qdm12/govalid/port"
)
func (s *Source) readWireguardSelection() (
selection settings.WireguardSelection, err error) {
selection.EndpointIP, err = s.env.NetipAddr("VPN_ENDPOINT_IP", env.RetroKeys("WIREGUARD_ENDPOINT_IP"))
selection.EndpointIP, err = s.readWireguardEndpointIP()
if err != nil {
return selection, err
}
selection.EndpointPort, err = s.env.Uint16Ptr("VPN_ENDPOINT_PORT", env.RetroKeys("WIREGUARD_ENDPOINT_PORT"))
selection.EndpointPort, err = s.readWireguardCustomPort()
if err != nil {
return selection, err
}
selection.PublicKey = s.env.String("WIREGUARD_PUBLIC_KEY", env.ForceLowercase(false))
selection.PublicKey = getCleanedEnv("WIREGUARD_PUBLIC_KEY")
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,24 +2,35 @@ package secrets
import (
"fmt"
"os"
"strings"
"github.com/qdm12/gluetun/internal/configuration/sources/files"
"github.com/qdm12/gluetun/internal/openvpn/extract"
"github.com/qdm12/gosettings/sources/env"
)
func (s *Source) readSecretFileAsStringPtr(secretPathEnvKey, defaultSecretPath string) (
// 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 readSecretFileAsStringPtr(secretPathEnvKey, defaultSecretPath string) (
stringPtr *string, err error) {
path := s.env.String(secretPathEnvKey, env.ForceLowercase(false))
path := getCleanedEnv(secretPathEnvKey)
if path == "" {
path = defaultSecretPath
}
return files.ReadFromFile(path)
}
func (s *Source) readPEMSecretFile(secretPathEnvKey, defaultSecretPath string) (
func readPEMSecretFile(secretPathEnvKey, defaultSecretPath string) (
base64Ptr *string, err error) {
pemData, err := s.readSecretFileAsStringPtr(secretPathEnvKey, defaultSecretPath)
pemData, err := readSecretFileAsStringPtr(secretPathEnvKey, defaultSecretPath)
if err != nil {
return nil, fmt.Errorf("reading secret file: %w", err)
}

View File

@@ -1,92 +0,0 @@
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"
)
func (s *Source) readHTTPProxy() (settings settings.HTTPProxy, err error) {
settings.User, err = s.readSecretFileAsStringPtr(
func readHTTPProxy() (settings settings.HTTPProxy, err error) {
settings.User, err = readSecretFileAsStringPtr(
"HTTPPROXY_USER_SECRETFILE",
"/run/secrets/httpproxy_user",
)
@@ -15,12 +15,12 @@ func (s *Source) readHTTPProxy() (settings settings.HTTPProxy, err error) {
return settings, fmt.Errorf("reading HTTP proxy user secret file: %w", err)
}
settings.Password, err = s.readSecretFileAsStringPtr(
settings.Password, err = readSecretFileAsStringPtr(
"HTTPPROXY_PASSWORD_SECRETFILE",
"/run/secrets/httpproxy_password",
)
if err != nil {
return settings, fmt.Errorf("reading HTTP proxy password secret file: %w", err)
return settings, fmt.Errorf("reading OpenVPN password secret file: %w", err)
}
return settings, nil

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -157,6 +157,13 @@ func (c *Config) acceptOutputTrafficToVPN(ctx context.Context,
func (c *Config) acceptOutputFromIPToSubnet(ctx context.Context,
intf string, sourceIP netip.Addr, destinationSubnet netip.Prefix, remove bool) error {
doIPv4 := sourceIP.Is4() && destinationSubnet.Addr().Is4()
doIPv6 := sourceIP.Is6() && destinationSubnet.Addr().Is6()
if !doIPv4 && !doIPv6 {
c.logger.Debug(fmt.Sprintf(
"source IP address %s and destination subnet %s are not of the same IP family, skipping...",
sourceIP, destinationSubnet))
return nil
}
interfaceFlag := "-o " + intf
if intf == "*" { // all interfaces

View File

@@ -2,6 +2,7 @@ package httpproxy
import (
"context"
"fmt"
"net/http"
"sync"
"time"
@@ -65,8 +66,5 @@ var hopHeaders = [...]string{ //nolint:gochecknoglobals
// Do not follow redirect, but directly return the redirect response.
func returnRedirect(*http.Request, []*http.Request) error {
// 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
return fmt.Errorf("%w", http.ErrUseLastResponse)
}

View File

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

View File

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

View File

@@ -1,32 +1,14 @@
//go:build linux || darwin
package netlink
import (
"github.com/vishvananda/netlink"
)
import "github.com/vishvananda/netlink"
type Addr = netlink.Addr
func (n *NetLink) AddrList(link Link, family int) (
addresses []Addr, err error) {
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
return netlink.AddrList(link, family)
}
func (n *NetLink) AddrReplace(link Link, addr Addr) error {
netlinkLink := linkToNetlinkLink(&link)
netlinkAddress := netlink.Addr{
IPNet: netipPrefixToIPNet(addr.Network),
}
return netlink.AddrReplace(netlinkLink, &netlinkAddress)
func (n *NetLink) AddrAdd(link Link, addr *Addr) error {
return netlink.AddrAdd(link, addr)
}

View File

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

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

@@ -1,146 +0,0 @@
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,23 +2,39 @@ package netlink
import (
"fmt"
"github.com/vishvananda/netlink"
)
//nolint:revive
const (
FamilyAll = 0
FamilyV4 = 2
FamilyV6 = 10
FAMILY_ALL = netlink.FAMILY_ALL
FAMILY_V4 = netlink.FAMILY_V4
FAMILY_V6 = netlink.FAMILY_V6
)
func FamilyToString(family int) string {
switch family {
case FamilyAll:
return "all" //nolint:goconst
case FamilyV4:
case FAMILY_ALL:
return "all"
case FAMILY_V4:
return "v4"
case FamilyV6:
case FAMILY_V6:
return "v6"
default:
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

@@ -1,5 +1,3 @@
//go:build linux || darwin
package netlink
import (

View File

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

View File

@@ -1,105 +1,37 @@
//go:build linux || darwin
package netlink
import "github.com/vishvananda/netlink"
type (
Link = netlink.Link
Bridge = netlink.Bridge
Wireguard = netlink.Wireguard
)
func (n *NetLink) LinkList() (links []Link, err error) {
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
return netlink.LinkList()
}
func (n *NetLink) LinkByName(name string) (link Link, err error) {
netlinkLink, err := netlink.LinkByName(name)
if err != nil {
return Link{}, err
}
return netlinkLinkToLink(netlinkLink), nil
return netlink.LinkByName(name)
}
func (n *NetLink) LinkByIndex(index int) (link Link, err error) {
netlinkLink, err := netlink.LinkByIndex(index)
if err != nil {
return Link{}, err
}
return netlinkLinkToLink(netlinkLink), nil
return netlink.LinkByIndex(index)
}
func (n *NetLink) LinkAdd(link Link) (linkIndex int, err error) {
netlinkLink := linkToNetlinkLink(&link)
err = netlink.LinkAdd(netlinkLink)
if err != nil {
return 0, err
}
return netlinkLink.Attrs().Index, nil
func (n *NetLink) LinkAdd(link Link) (err error) {
return netlink.LinkAdd(link)
}
func (n *NetLink) LinkDel(link Link) (err error) {
return netlink.LinkDel(linkToNetlinkLink(&link))
return netlink.LinkDel(link)
}
func (n *NetLink) LinkSetUp(link Link) (linkIndex int, err error) {
netlinkLink := linkToNetlinkLink(&link)
err = netlink.LinkSetUp(netlinkLink)
if err != nil {
return 0, err
}
return netlinkLink.Attrs().Index, nil
func (n *NetLink) LinkSetUp(link Link) (err error) {
return netlink.LinkSetUp(link)
}
func (n *NetLink) LinkSetDown(link Link) (err error) {
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),
},
}
return netlink.LinkSetDown(link)
}

View File

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

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

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