Compare commits

..

113 Commits

Author SHA1 Message Date
Quentin McGaw
16ecf48b89 fix(vpnunlimited): lower TLS security level to 0 (#1476) 2023-04-11 14:08:54 +02:00
Quentin McGaw
8fa4fd1b64 chore(labels): update labels
- remove issue category labels
- Add temporary status labels
- Add complexity labels
2023-04-03 11:58:12 +00:00
Quentin McGaw
4db6d1ecf9 chore(dev): add openssl to dev container 2023-04-03 10:41:37 +00:00
Quentin McGaw
3b86927ca7 fix(vpnsecure): upgrade Openvpn key encryption if needed (#1471) 2023-04-03 03:40:09 -07:00
Quentin McGaw
8bfa2f9b27 chore(docker): loosen pin for openssl 1.1
- Credits to @kylemanna
2023-04-03 08:21:58 +00:00
Quentin McGaw
fe2a3e4d11 chore(docker): remove no longer needed apk-tools 2023-04-03 08:20:08 +00:00
Quentin McGaw
b0451d8e50 feat(docker): install full-featured wget
- Fixes #1260, #1494
- Does not spawn openssl zombie processes
2023-04-03 08:10:48 +00:00
Quentin McGaw
a0b9044fd3 fix(hidemyass): add warning of end of life
- Fixes #1498
- Credits to @Fukitsu
2023-04-03 07:58:04 +00:00
Quentin McGaw
c7a841f4b4 chore(openvpn/extract): simplify PEM extract 2023-04-01 16:57:18 +00:00
Quentin McGaw
4ba159e483 chore(all): review error wrappings
- remove repetitive `cannot` and `failed` prefixes
- rename `unmarshaling` to `decoding`
2023-04-01 16:57:18 +00:00
dependabot[bot]
63a696d7e7 Chore(deps): Bump docker/build-push-action from 3.2.0 to 4.0.0 (#1378) 2023-04-01 08:28:34 -07:00
Quentin McGaw
d457342b46 feat(log): warn Openvpn 2.4 is to be removed 2023-04-01 15:24:42 +00:00
Quentin McGaw
c246dae2cc feat(log): log warnings about user settings
- Warn when using Openvpn 2.4 and SlickVPN
- Warn when using Openvpn 2.5 and SlickVPN
2023-04-01 15:22:32 +00:00
Quentin McGaw
0f4a2e5224 hotfix(deps): bump old openssl to 1.1.1t-r2 2023-03-31 09:34:33 +00:00
dependabot[bot]
db262050d5 Chore(deps): Bump github.com/stretchr/testify from 1.8.1 to 1.8.2 (#1483) 2023-03-30 10:54:44 -07:00
Quentin McGaw
227cdea0c8 fix(slickvpn): allow AES-256-GCM 2023-03-26 12:38:55 +02:00
Quentin McGaw
33a6f1c01b fix(slickvpn): lower TLS security level to 0 2023-03-26 12:38:55 +02:00
Quentin McGaw
f6f3c110f0 fix(slickvpn): all servers support TCP and UDP 2023-03-26 12:38:55 +02:00
Quentin McGaw
27a3f2c846 fix(slickvpn): precise default TCP port as 443 2023-03-26 12:38:55 +02:00
dependabot[bot]
62169baeea Chore(deps): Bump golang.org/x/text from 0.5.0 to 0.8.0 (#1436) 2023-03-26 12:36:29 +02:00
dependabot[bot]
4b18636a91 Chore(deps): Bump golang.org/x/sys from 0.3.0 to 0.6.0 (#1438) 2023-03-25 22:11:01 +01:00
Quentin McGaw
51432ca05f hotfix(health): remove previous err debug line 2023-03-25 17:34:56 +00:00
colereynolds
b5ebdcd040 docs(readme): add servers updater environment variables (#1393) 2023-03-25 16:14:07 +01:00
Quentin McGaw
416c1ee113 chore(deps): tidy dependencies 2023-03-25 15:09:27 +00:00
Quentin McGaw
fe97e28461 fix(health): log link to Wiki on VPN restart 2023-03-25 15:09:13 +00:00
Quentin McGaw
cbd8711a21 feat(airvpn): update servers data 2023-03-25 14:50:49 +00:00
stevenl4
7578e52ed5 fix(ipvanish): updater zip file url (#1449) 2023-03-25 15:36:44 +01:00
Quentin McGaw
0df68f76d5 fix(airvpn): remove commas from city names 2023-03-25 12:55:21 +00:00
Quentin McGaw
9a528c42f8 chore(settings): precise base64 DER for some OpenVPN fields 2023-03-25 12:10:01 +00:00
Quentin McGaw
5607916af6 hotfix: bump old openssl to 1.1.1t-r1 2023-03-23 16:36:14 +00:00
Quentin McGaw
4ad7a2a444 feat(mullvad): update servers data 2023-03-23 15:23:53 +00:00
Quentin McGaw
ab5dbdca97 feat(pia): update servers data 2023-03-01 13:00:18 +00:00
Quentin McGaw
a97fcda283 fix(version): add name in version check error 2023-02-27 20:16:55 +00:00
Quentin McGaw
e955adc1e1 hotfix: install older openssh for openvpn 2.4 2023-02-27 05:21:42 +00:00
Quentin McGaw
ac5141b411 Chore(deps): Bump github.com/breml/rootcerts from 0.2.8 to 0.2.10 2023-02-26 18:14:59 +00:00
dependabot[bot]
f8c189e48a Chore(deps): Bump github.com/fatih/color from 1.13.0 to 1.14.1 (#1369) 2023-02-26 11:14:15 -08:00
Quentin McGaw
2f2a904c64 feat(mullvad): update servers data 2023-02-26 15:16:22 +00:00
Quentin McGaw
9261dca8ab chore(lint): bump from v1.49.0 to v1.51.2 2023-02-26 15:15:34 +00:00
Quentin McGaw
7b5d5c3884 feat(alpine): bump from 3.16 to 3.17 2023-02-26 15:15:34 +00:00
Quentin McGaw
7c80d80904 chore(build): upgrade Go from 1.19 to 1.20 2023-02-26 15:15:30 +00:00
Quentin McGaw
ea40b84ec0 fix(settings): print outbound subnets correctly 2022-12-31 17:46:55 +00:00
Quentin McGaw
4e6ef649c4 fix(airvpn): remove commas from API locations 2022-12-31 17:30:31 +00:00
Quentin McGaw
dd40f1d2e6 chore(devcontainer): same ssh bind mount for all platforms 2022-12-31 17:30:31 +00:00
dependabot[bot]
490693bb26 Chore(deps): Bump golang.org/x/text from 0.4.0 to 0.5.0 (#1275) 2022-12-15 04:58:43 -05:00
Quentin McGaw
c8d33ca5f3 fix(surfshark): update location data
- Add 2 new 'HK' servers
- Remove 3 servers no longer resolving
2022-12-15 09:29:48 +00:00
Quentin McGaw
e6df026332 feat(surfshark): update servers data 2022-12-15 09:29:09 +00:00
Quentin McGaw
7a30343053 docs(readme): document alternative ghcr image name 2022-12-15 09:02:37 +00:00
ksurl
fc02ae9c13 feat(docker): ghcr.io/qdm12/gluetun image (#1231) 2022-12-14 16:04:41 -05:00
Quentin McGaw
f70f0aca9c fix(settings): validate Wireguard addresses depending on IPv6 support 2022-12-14 11:52:03 +00:00
Quentin McGaw
16acd1b162 chore(netlink): log ipv6 support at debug level 2022-12-14 11:52:03 +00:00
Quentin McGaw
2e3eb1fd7b fix(wireguard): ignore IPv6 addresses if IPv6 is not supported 2022-12-14 11:52:03 +00:00
Quentin McGaw
a4cf17f81e fix(netlink): change logger level 2022-12-14 11:50:36 +00:00
Quentin McGaw
c0a301611d fix(health): set config to default in healthcheck mode 2022-12-07 10:34:24 +00:00
Quentin McGaw
cc934f5c68 hotfix(netlink): ipv6 detection for nil src/dst in routes 2022-12-02 11:39:37 +00:00
Quentin McGaw
74426f6202 feat(netlink): add debug logger 2022-12-02 11:26:52 +00:00
Quentin McGaw
03ed3cb1c8 feat(wireguard): WIREGUARD_IMPLEMENTATION variable
- Can be `auto` (default), `userspace` or `kernelspace`
2022-12-02 11:16:27 +00:00
Quentin McGaw
1b1335835b fix(netlink): inspect each route for IPv6 support 2022-12-01 12:18:46 +00:00
Quentin McGaw
5070dbcf7f feat(fastestvpn): update servers data 2022-11-30 19:21:56 +00:00
rsquarev
90b9d85742 fix(fastesvpn): updater zip file url (#1264) 2022-11-30 14:19:14 -05:00
Quentin McGaw
7a3b9941aa fix(exit): exit with 1 on runtime error 2022-11-18 09:46:31 +00:00
Quentin McGaw
698095f0a0 fix(pprof): do not run if disabled 2022-11-15 12:45:47 +00:00
Quentin McGaw
5a06d8e155 fix(firewall): iptables detection with permission denied 2022-11-15 12:34:25 +00:00
Quentin McGaw
7421dcb45f feat(openvpn): explain ip route error in logs
- `RTNETLINK answers: File exists` changed to warning with explanation
- `Linux route add command failed:` changed to warning with explanation
2022-11-11 09:48:55 +00:00
Quentin McGaw
554a6cdb92 feat(healthcheck): add FAQ url on unhealthy log 2022-11-11 09:43:07 +00:00
Quentin McGaw
5aa39be973 fix(firewall): remove previously allowed input ports 2022-11-11 09:19:03 +00:00
Quentin McGaw
192a7a56a3 fix(httpproxy): lower shutdown wait from 2s to 100ms 2022-10-31 11:21:25 +00:00
Quentin McGaw
1d1657e9be fix(pia): remove username+password from login bad http status code 2022-10-31 11:00:43 +00:00
Quentin McGaw
49b7301295 feat(mullvad): update servers data 2022-10-31 10:44:36 +00:00
dependabot[bot]
126804c15e Chore(deps): Bump golang.org/x/text from 0.3.7 to 0.4.0 (#1198) 2022-10-28 07:24:35 -04:00
dependabot[bot]
a7643c6201 Chore(deps): Bump github.com/stretchr/testify from 1.8.0 to 1.8.1 (#1210) 2022-10-28 07:24:20 -04:00
dependabot[bot]
db2de5fc84 Chore(deps): Bump docker/build-push-action from 3.1.1 to 3.2.0 (#1193) 2022-10-28 07:24:04 -04:00
dependabot[bot]
5c7b9aa6a1 Chore(deps): Bump github.com/breml/rootcerts from 0.2.6 to 0.2.8 (#1173) 2022-10-28 07:23:40 -04:00
Quentin McGaw
63890c159e feat(servers): update FastestVPN servers 2022-10-28 11:02:39 +00:00
Quentin McGaw
e7d5ae5dc1 fix(fastestvpn): handle lowercase .ovpn filenames 2022-10-28 11:02:24 +00:00
Quentin McGaw
b275354a92 fix(env): trim space for wireguard addresses 2022-10-28 10:49:51 +00:00
Quentin McGaw
ac02a64d17 fix(ipinfo): handle 403 as too many requests 2022-10-28 10:35:29 +00:00
Quentin McGaw
9c80150e09 fix(publicip): no retry when too many requests to ipinfo.io 2022-10-28 10:35:09 +00:00
Quentin McGaw
31a8bc9062 feat(servers): update PureVPN 2022-10-28 09:11:33 +00:00
Quentin McGaw
f15dde6502 feat(providers): add AirVPN support (#1145) 2022-10-17 02:54:56 -04:00
Quentin McGaw
f70609c464 fix(wireguard): ignore IPv6 addresses if IPv6 disabled 2022-10-17 06:31:32 +00:00
Quentin McGaw
c954e6f231 fix: parse udp4, udp6, tcp4 or tcp6 2022-10-16 16:54:12 +00:00
Quentin McGaw
cb804577a9 feat(httpproxy): log credentials sent on mismatch 2022-10-02 09:31:03 +00:00
Quentin McGaw
e5be20d719 fix(exit): exit with 0 on successful shutdown 2022-09-14 13:23:31 +00:00
EkilDeew
875690ab18 feat(network): enable ipv6 connection and tunneling (#1114)
Co-authored-by: Quentin McGaw <quentin.mcgaw@gmail.com>
2022-09-13 17:18:10 -07:00
Quentin McGaw
6a5aa8eddb fix(openvpn): do not set tun-ipv6
- Server should push `tun-ipv6` if it is available
- Add ignore filter for `tun-ipv6` if ipv6 is not supported on client
- Fixes #435
2022-09-14 00:03:31 +00:00
Quentin McGaw
7fdc7de210 feat(ipv6): use ipv6 endpoint IPs if supported 2022-09-12 21:31:37 +00:00
Quentin McGaw
dd7630997b fix(vpnsecure): allow empty OpenVPN user+password 2022-09-10 14:46:17 +00:00
Quentin McGaw
aba5ca4536 fix(cyberghost): remove outdated server groups
- Remove `94-1` pemium udp usa
- Remove `95-1` premium udp asia
- Remove `93-1` pemium udp usa
- Remove `96-1` premium tcp asia
- Update servers data
2022-09-08 12:19:12 +00:00
Quentin McGaw
7506625f40 chore(tests): fix netlink test for previous crash 2022-09-06 12:17:51 +00:00
Quentin McGaw
5ddd703f6a feat(vpn): auto detection of IPv6 support
- `OPENVPN_IPV6` removed
- Affects OpenVPN
- Use the same mechanism for OpenVPN and Wireguard
- Check only once at program start since this is unlikely to change at runtime
- Log if IPv6 is supported
- Remove `IPv6` boolean from settings structs
- Move IPv6 detection as a method on NetLinker
2022-09-06 12:16:29 +00:00
Quentin McGaw
71c51a7455 chore(provider/utils): do not check for empty wg keys 2022-09-05 15:50:02 +00:00
Quentin McGaw
284d4340b1 fix(tests): fix netlink wireguard test 2022-09-05 15:23:31 +00:00
Quentin McGaw
2c1281d0a2 hotfix(tests): panic tests for previous commit 2022-09-05 15:04:43 +00:00
Stijn Hoop
532df9f8d4 fix(privateinternetaccess): get token for port forwarding (#1132) 2022-09-05 08:01:48 -07:00
Quentin McGaw
45b7da1058 chore(dev): improve missing provider panic string 2022-09-05 14:51:30 +00:00
Quentin McGaw
907daff483 chore(build): tidy Go modules dependencies 2022-09-04 23:01:02 +00:00
Quentin McGaw
7757e8a114 chore(dev): improve update command launch config
- Run without `debug` mode
- Run from workspace folder so it writes to the right path
- Pick `-maintainer` or `-enduser` update mode
2022-09-04 18:40:08 +00:00
Quentin McGaw
e59e28152f fix(ivpn): update mechanism for Wireguard servers 2022-09-02 00:36:13 +00:00
Quentin McGaw
2fe0594db7 feat(servers): update ProtonVPN servers data 2022-08-30 11:44:34 +00:00
Quentin McGaw
794e96b449 docs(readme): add ProtonVPN and PureVPN to Wireguard support 2022-08-29 00:31:38 +00:00
Quentin McGaw
07282f414c chore(wireguard): upgrade wireguard depdencies 2022-08-27 18:37:24 +00:00
Quentin McGaw
e583f9de47 fix(codeql): fix integer parsing (false positive) 2022-08-27 16:45:29 +00:00
Quentin McGaw
8570e09eb9 chore(config): rename Reader to Source struct 2022-08-26 15:40:35 +00:00
Quentin McGaw
ae5cba519c chore(config): define Source interface locally where needed 2022-08-26 15:03:59 +00:00
Quentin McGaw
26f3832187 chore(config): rename mux source to merge 2022-08-26 14:59:35 +00:00
Quentin McGaw
5989f29035 feat(surfshark): Wireguard support (#587) 2022-08-26 07:55:46 -07:00
Quentin McGaw
4ace99f318 chore(servers): remove "udp": true for Wireguard 2022-08-25 13:24:22 +00:00
Quentin McGaw
d1c5e00df8 fix(updater): error when server has not the minimal information 2022-08-25 13:23:27 +00:00
Quentin McGaw
5eacb46226 feat(servers): update servers data for Ivpn, Mullvad and Windscribe 2022-08-25 13:05:20 +00:00
Quentin McGaw
6c17612310 chore(filter): no network protocol filter for Wireguard 2022-08-25 13:03:58 +00:00
Quentin McGaw
fba73a0a0f fix(settings): OPENVPN_CUSTOM_CONFIG precedence for custom provider only if VPN_SERVICE_PROVIDER is empty 2022-08-25 04:01:17 +00:00
Quentin McGaw
4faef87c03 chore(build): bump Go from 1.18 to 1.19 2022-08-24 21:54:49 +00:00
Quentin McGaw
5914cb0e37 chore(build): bump Go from 1.17 to 1.18
- Unneeded disabled linters are: `rowserrcheck`, `sqlclosecheck`
- Disabled linter is `wastedassign` which is tolerable
2022-08-24 21:54:08 +00:00
244 changed files with 33968 additions and 11961 deletions

View File

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

View File

@@ -12,10 +12,10 @@ services:
# Docker configuration
- ~/.docker:/root/.docker
# SSH directory for Linux, OSX and WSL
- ~/.ssh:/root/.ssh
# For Windows without WSL, a copy will be made
# from /tmp/.ssh to ~/.ssh to fix permissions
#- ~/.ssh:/tmp/.ssh:ro
# 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

View File

@@ -40,6 +40,7 @@ body:
attributes:
label: VPN service provider
options:
- AirVPN
- Custom
- Cyberghost
- ExpressVPN

34
.github/labels.yml vendored
View File

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

View File

@@ -45,6 +45,8 @@ jobs:
with:
locale: "US"
level: error
exclude: |
./internal/storage/servers.json
- name: Linting
run: docker build --target lint .
@@ -91,6 +93,7 @@ jobs:
permissions:
actions: read
contents: read
packages: write
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
@@ -104,6 +107,7 @@ jobs:
flavor: |
latest=${{ github.ref == format('refs/heads/{0}', github.event.repository.default_branch) }}
images: |
ghcr.io/qdm12/gluetun
qmcgaw/gluetun
qmcgaw/private-internet-access
tags: |
@@ -121,12 +125,18 @@ jobs:
username: qmcgaw
password: ${{ secrets.DOCKERHUB_PASSWORD }}
- uses: docker/login-action@v2
with:
registry: ghcr.io
username: qdm12
password: ${{ github.token }}
- name: Short commit
id: shortcommit
run: echo "::set-output name=value::$(git rev-parse --short HEAD)"
- name: Build and push final image
uses: docker/build-push-action@v3.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 }}

16
.vscode/launch.json vendored
View File

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

View File

@@ -1,8 +1,8 @@
ARG ALPINE_VERSION=3.16
ARG GO_ALPINE_VERSION=3.16
ARG GO_VERSION=1.17
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.49.0
ARG GOLANGCI_LINT_VERSION=v1.51.2
ARG MOCKGEN_VERSION=v1.6.0
ARG BUILDPLATFORM=linux/amd64
@@ -91,13 +91,13 @@ ENV VPN_SERVICE_PROVIDER=pia \
OPENVPN_CIPHERS= \
OPENVPN_AUTH= \
OPENVPN_PROCESS_USER= \
OPENVPN_IPV6=off \
OPENVPN_CUSTOM_CONFIG= \
# Wireguard
WIREGUARD_PRIVATE_KEY= \
WIREGUARD_PRESHARED_KEY= \
WIREGUARD_PUBLIC_KEY= \
WIREGUARD_ADDRESSES= \
WIREGUARD_IMPLEMENTATION=auto \
# VPN server filtering
SERVER_REGIONS= \
SERVER_COUNTRIES= \
@@ -197,8 +197,9 @@ ENTRYPOINT ["/gluetun-entrypoint"]
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 apk-tools && \
RUN apk add --no-cache --update -l wget && \
apk add --no-cache --update -X "https://dl-cdn.alpinelinux.org/alpine/v3.12/main" openvpn==2.4.12-r0 && \
apk add --no-cache --update -X "https://dl-cdn.alpinelinux.org/alpine/v3.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 && \

255
README.md
View File

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

View File

@@ -16,10 +16,10 @@ import (
"github.com/qdm12/dns/pkg/unbound"
"github.com/qdm12/gluetun/internal/alpine"
"github.com/qdm12/gluetun/internal/cli"
"github.com/qdm12/gluetun/internal/configuration/sources"
"github.com/qdm12/gluetun/internal/configuration/settings"
"github.com/qdm12/gluetun/internal/configuration/sources/env"
"github.com/qdm12/gluetun/internal/configuration/sources/files"
"github.com/qdm12/gluetun/internal/configuration/sources/mux"
mux "github.com/qdm12/gluetun/internal/configuration/sources/merge"
"github.com/qdm12/gluetun/internal/configuration/sources/secrets"
"github.com/qdm12/gluetun/internal/constants"
"github.com/qdm12/gluetun/internal/dns"
@@ -77,7 +77,8 @@ func main() {
args := os.Args
tun := tun.New()
netLinker := netlink.New()
netLinkDebugLogger := logger.New(log.SetComponent("netlink"))
netLinker := netlink.New(netLinkDebugLogger)
cli := cli.New()
cmder := command.NewCmder()
@@ -91,12 +92,13 @@ func main() {
errorCh <- _main(ctx, buildInfo, args, logger, muxReader, tun, netLinker, cmder, cli)
}()
var err error
select {
case signal := <-signalCh:
fmt.Println("")
logger.Warn("Caught OS signal " + signal.String() + ", shutting down")
cancel()
case err := <-errorCh:
case err = <-errorCh:
close(errorCh)
if err == nil { // expected exit such as healthcheck
os.Exit(0)
@@ -108,18 +110,27 @@ func main() {
const shutdownGracePeriod = 5 * time.Second
timer := time.NewTimer(shutdownGracePeriod)
select {
case <-errorCh:
case shutdownErr := <-errorCh:
if !timer.Stop() {
<-timer.C
}
if shutdownErr != nil {
logger.Warnf("Shutdown not completed gracefully: %s", shutdownErr)
os.Exit(1)
}
logger.Info("Shutdown successful")
if err != nil {
os.Exit(1)
}
os.Exit(0)
case <-timer.C:
logger.Warn("Shutdown timed out")
os.Exit(1)
case signal := <-signalCh:
logger.Warn("Caught OS signal " + signal.String() + ", forcing shut down")
os.Exit(1)
}
os.Exit(1)
}
var (
@@ -128,7 +139,7 @@ var (
//nolint:gocognit,gocyclo,maintidx
func _main(ctx context.Context, buildInfo models.BuildInformation,
args []string, logger log.LoggerInterface, source sources.Source,
args []string, logger log.LoggerInterface, source Source,
tun Tun, netLinker netLinker, cmder command.RunStarter,
cli clier) error {
if len(args) > 1 { // cli operation
@@ -138,7 +149,7 @@ func _main(ctx context.Context, buildInfo models.BuildInformation,
case "clientkey":
return cli.ClientKey(args[2:])
case "openvpnconfig":
return cli.OpenvpnConfig(logger, source)
return cli.OpenvpnConfig(logger, source, netLinker)
case "update":
return cli.Update(ctx, args[2:], logger)
case "format-servers":
@@ -179,6 +190,7 @@ func _main(ctx context.Context, buildInfo models.BuildInformation,
// - firewall Debug and Enabled are booleans parsed from source
logger.Patch(log.SetLevel(*allSettings.Log.Level))
netLinker.PatchLoggerLevel(*allSettings.Log.Level)
routingLogger := logger.New(log.SetComponent("routing"))
if *allSettings.Firewall.Debug { // To remove in v4
@@ -220,7 +232,12 @@ func _main(ctx context.Context, buildInfo models.BuildInformation,
return err
}
err = allSettings.Validate(storage)
ipv6Supported, err := netLinker.IsIPv6Supported()
if err != nil {
return fmt.Errorf("checking for IPv6 support: %w", err)
}
err = allSettings.Validate(storage, ipv6Supported)
if err != nil {
return err
}
@@ -228,7 +245,7 @@ func _main(ctx context.Context, buildInfo models.BuildInformation,
allSettings.Pprof.HTTPServer.Logger = logger.New(log.SetComponent("pprof"))
pprofServer, err := pprof.New(allSettings.Pprof)
if err != nil {
return fmt.Errorf("cannot create Pprof server: %w", err)
return fmt.Errorf("creating Pprof server: %w", err)
}
puid, pgid := int(*allSettings.System.PUID), int(*allSettings.System.PGID)
@@ -260,6 +277,10 @@ func _main(ctx context.Context, buildInfo models.BuildInformation,
logger.Info(allSettings.String())
for _, warning := range allSettings.Warnings() {
logger.Warn(warning)
}
if err := os.MkdirAll("/tmp/gluetun", 0644); err != nil {
return err
}
@@ -270,7 +291,7 @@ func _main(ctx context.Context, buildInfo models.BuildInformation,
const defaultUsername = "nonrootuser"
nonRootUsername, err := alpineConf.CreateUser(defaultUsername, puid)
if err != nil {
return fmt.Errorf("cannot create user: %w", err)
return fmt.Errorf("creating user: %w", err)
}
if nonRootUsername != defaultUsername {
logger.Info("using existing username " + nonRootUsername + " corresponding to user id " + fmt.Sprint(puid))
@@ -288,7 +309,7 @@ func _main(ctx context.Context, buildInfo models.BuildInformation,
if strings.Contains(err.Error(), "operation not permitted") {
logger.Warn("💡 Tip: Are you passing NET_ADMIN capability to gluetun?")
}
return fmt.Errorf("cannot setup routing: %w", err)
return fmt.Errorf("setting up routing: %w", err)
}
defer func() {
routingLogger.Info("routing cleanup...")
@@ -339,11 +360,14 @@ func _main(ctx context.Context, buildInfo models.BuildInformation,
tickersGroupHandler := goshutdown.NewGroupHandler("tickers", defaultGroupOptions...)
otherGroupHandler := goshutdown.NewGroupHandler("other", defaultGroupOptions...)
pprofReady := make(chan struct{})
pprofHandler, pprofCtx, pprofDone := goshutdown.NewGoRoutineHandler("pprof server")
go pprofServer.Run(pprofCtx, pprofReady, pprofDone)
otherGroupHandler.Add(pprofHandler)
<-pprofReady
if *allSettings.Pprof.Enabled {
// TODO run in run loop so this can be patched at runtime
pprofReady := make(chan struct{})
pprofHandler, pprofCtx, pprofDone := goshutdown.NewGoRoutineHandler("pprof server")
go pprofServer.Run(pprofCtx, pprofReady, pprofDone)
otherGroupHandler.Add(pprofHandler)
<-pprofReady
}
portForwardLogger := logger.New(log.SetComponent("port forwarding"))
portForwardLooper := portforward.NewLoop(allSettings.VPN.Provider.PortForwarding,
@@ -389,7 +413,7 @@ func _main(ctx context.Context, buildInfo models.BuildInformation,
httpClient, unzipper, parallelResolver, ipFetcher, openvpnFileExtractor)
vpnLogger := logger.New(log.SetComponent("vpn"))
vpnLooper := vpn.NewLoop(allSettings.VPN, allSettings.Firewall.VPNInputPorts,
vpnLooper := vpn.NewLoop(allSettings.VPN, ipv6Supported, allSettings.Firewall.VPNInputPorts,
providers, storage, ovpnConf, netLinker, firewallConf, routingConf, portForwardLooper,
cmder, publicIPLooper, unboundLooper, vpnLogger, httpClient,
buildInfo, *allSettings.Version.Enabled)
@@ -431,9 +455,10 @@ func _main(ctx context.Context, buildInfo models.BuildInformation,
"http server", goroutine.OptionTimeout(defaultShutdownTimeout))
httpServer, err := server.New(httpServerCtx, controlServerAddress, controlServerLogging,
logger.New(log.SetComponent("http server")),
buildInfo, vpnLooper, portForwardLooper, unboundLooper, updaterLooper, publicIPLooper, storage)
buildInfo, vpnLooper, portForwardLooper, unboundLooper, updaterLooper, publicIPLooper,
storage, ipv6Supported)
if err != nil {
return fmt.Errorf("cannot setup control server: %w", err)
return fmt.Errorf("setting up control server: %w", err)
}
httpServerReady := make(chan struct{})
go httpServer.Run(httpServerCtx, httpServerReady, httpServerDone)
@@ -480,7 +505,7 @@ func printVersions(ctx context.Context, logger infoer,
for _, element := range elements {
version, err := element.getVersion(ctx)
if err != nil {
return err
return fmt.Errorf("getting %s version: %w", element.name, err)
}
logger.Info(element.name + " version: " + version)
}
@@ -494,6 +519,8 @@ type netLinker interface {
Ruler
Linker
IsWireguardSupported() (ok bool, err error)
IsIPv6Supported() (ok bool, err error)
PatchLoggerLevel(level log.Level)
}
type Addresser interface {
@@ -529,8 +556,8 @@ type Linker interface {
type clier interface {
ClientKey(args []string) error
FormatServers(args []string) error
OpenvpnConfig(logger cli.OpenvpnConfigLogger, source sources.Source) error
HealthCheck(ctx context.Context, source sources.Source, warner cli.Warner) error
OpenvpnConfig(logger cli.OpenvpnConfigLogger, source cli.Source, ipv6Checker cli.IPv6Checker) error
HealthCheck(ctx context.Context, source cli.Source, warner cli.Warner) error
Update(ctx context.Context, args []string, logger cli.UpdaterLogger) error
}
@@ -538,3 +565,9 @@ type Tun interface {
Check(tunDevice string) error
Create(tunDevice string) error
}
type Source interface {
Read() (settings settings.Settings, err error)
ReadHealth() (health settings.Health, err error)
String() string
}

38
go.mod
View File

@@ -1,10 +1,10 @@
module github.com/qdm12/gluetun
go 1.17
go 1.20
require (
github.com/breml/rootcerts v0.2.6
github.com/fatih/color v1.13.0
github.com/breml/rootcerts v0.2.10
github.com/fatih/color v1.14.1
github.com/golang/mock v1.6.0
github.com/qdm12/dns v1.11.0
github.com/qdm12/golibs v0.0.0-20210822203818-5c568b0777b6
@@ -15,31 +15,35 @@ require (
github.com/qdm12/log v0.1.0
github.com/qdm12/ss-server v0.4.0
github.com/qdm12/updated v0.0.0-20210603204757-205acfe6937e
github.com/stretchr/testify v1.8.0
github.com/stretchr/testify v1.8.2
github.com/vishvananda/netlink v1.1.1-0.20211129163951-9ada19101fc5
golang.org/x/net v0.0.0-20210916014120-12bc252f5db8
golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c
golang.org/x/text v0.3.7
golang.zx2c4.com/wireguard v0.0.0-20210805125648-3957e9b9dd19
golang.zx2c4.com/wireguard/wgctrl v0.0.0-20210803171230-4253848d036c
github.com/youmark/pkcs8 v0.0.0-20201027041543-1326539a0a0a
golang.org/x/net v0.0.0-20220418201149-a630d4f3e7a2
golang.org/x/sys v0.6.0
golang.org/x/text v0.8.0
golang.zx2c4.com/wireguard v0.0.0-20220703234212-c31a7b1ab478
golang.zx2c4.com/wireguard/wgctrl v0.0.0-20220504211119-3d4a969bb56b
inet.af/netaddr v0.0.0-20210718074554-06ca8145d722
)
require (
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/google/go-cmp v0.5.5 // indirect
github.com/josharian/native v0.0.0-20200817173448-b6b71def0850 // indirect
github.com/mattn/go-colorable v0.1.9 // indirect
github.com/mattn/go-isatty v0.0.14 // indirect
github.com/mdlayher/genetlink v1.0.0 // indirect
github.com/mdlayher/netlink v1.4.0 // indirect
github.com/google/go-cmp v0.5.7 // indirect
github.com/josharian/native v1.0.0 // indirect
github.com/mattn/go-colorable v0.1.13 // indirect
github.com/mattn/go-isatty v0.0.17 // indirect
github.com/mdlayher/genetlink v1.2.0 // indirect
github.com/mdlayher/netlink v1.6.0 // indirect
github.com/mdlayher/socket v0.2.3 // indirect
github.com/miekg/dns v1.1.40 // indirect
github.com/mr-tron/base58 v1.2.0 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
github.com/riobard/go-bloom v0.0.0-20200614022211-cdc8013cb5b3 // indirect
github.com/vishvananda/netns v0.0.0-20200728191858-db3c7e526aae // indirect
go4.org/intern v0.0.0-20210108033219-3eb7198706b2 // indirect
go4.org/unsafe/assume-no-moving-gc v0.0.0-20220617031537-928513b29760 // indirect
golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97 // indirect
go4.org/unsafe/assume-no-moving-gc v0.0.0-20230221090011-e4bae7ad2296 // indirect
golang.org/x/crypto v0.0.0-20220411220226-7b82a4e95df4 // indirect
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c // indirect
golang.zx2c4.com/wintun v0.0.0-20211104114900-415007cec224 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
)

133
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.6 h1:CdPczjzItec+wopLoDsBAFcLEai2q7Yayfg/94/q/2E=
github.com/breml/rootcerts v0.2.6/go.mod h1:24FDtzYMpqIeYC7QzaE8VPRQaFZU5TIUDlyk8qwjD88=
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=
@@ -14,8 +14,8 @@ github.com/docker/go-units v0.3.3/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDD
github.com/dvyukov/go-fuzz v0.0.0-20210103155950-6a8e9d1f2415/go.mod h1:11Gm+ccJnvAhCNLlf5+cS9KjtbaD5I5zaZpFMsTHWTw=
github.com/emirpasic/gods v1.12.0/go.mod h1:YfzfFFoVP/catgzJb4IKIqXjX78Ha8FMSDh3ymbK86o=
github.com/fatih/color v1.12.0/go.mod h1:ELkj/draVOlAH/xkhN6mQ50Qd0MPOk5AAr3maGEBuJM=
github.com/fatih/color v1.13.0 h1:8LOYc1KYPPmyKMuN8QV2DNRWNbLo6LZ0iLs8+mlH53w=
github.com/fatih/color v1.13.0/go.mod h1:kLAiJbzzSOZDVNGyDpeOxJ47H46qBXwg5ILebYFFOfk=
github.com/fatih/color v1.14.1 h1:qfhVLaG5s+nCROl1zJsZRxFeYrHLqWroPOQ8BWiNb4w=
github.com/fatih/color v1.14.1/go.mod h1:2oHN61fhTpgcxD3TSWCgKDiH1+x4OiDVVGH8WlgGZGg=
github.com/flynn/go-shlex v0.0.0-20150515145356-3f9db97f8568/go.mod h1:xEzjJPgXI435gkrCt3MPfRiAkVrwSbHsst4LCFVfpJc=
github.com/gliderlabs/ssh v0.2.2/go.mod h1:U7qILu1NlMHj9FlMhZLlkCdDnU1DBEAqr0aevW3Awn0=
github.com/globalsign/mgo v0.0.0-20180905125535-1ca0a4f7cbcb/go.mod h1:xkRDCp4j0OGD1HRkm4kmhM+pmpv3AKq5SU7GMg4oO/Q=
@@ -36,28 +36,16 @@ github.com/golang/mock v1.5.0/go.mod h1:CWnOUgYIOo4TcNZ0wHX3YZCqsaM1I1Jvs6v3mP3K
github.com/golang/mock v1.6.0 h1:ErTB+efbowRARo13NNdxyJji2egdxLGQhRaY+DUumQc=
github.com/golang/mock v1.6.0/go.mod h1:p6yTPP+5HYm5mzsMV8JkE6ZKdX+/wYM6Hr+LicevLPs=
github.com/gomodule/redigo v2.0.0+incompatible/go.mod h1:B4C85qUVwatsJoIUNIfCRsp7qO0iAmpGFZ4EELWSbC4=
github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.5 h1:Khx7svrCpmxxtHBq5j2mp/xVjsi8hQMfNLvJFAlrGgU=
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.7 h1:81/ik6ipDQS2aGcBfIN5dHDB36BwrStyeAQquSYCV4o=
github.com/google/go-cmp v0.5.7/go.mod h1:n+brtR0CgQNWTVd5ZUFpTBC8YFBDLK/h/bpaJ8/DtOE=
github.com/google/uuid v1.0.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/gotify/go-api-client/v2 v2.0.4/go.mod h1:VKiah/UK20bXsr0JObE1eBVLW44zbBouzjuri9iwjFU=
github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99/go.mod h1:1lJo3i6rXxKeerYnT8Nvf0QmHCRC1n8sfWVwXF2Frvo=
github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI=
github.com/josharian/native v0.0.0-20200817173448-b6b71def0850 h1:uhL5Gw7BINiiPAo24A2sxkcDI0Jt/sqp1v5xQCniEFA=
github.com/josharian/native v0.0.0-20200817173448-b6b71def0850/go.mod h1:7X/raswPFr05uY3HiLlYeyQntB6OO7E/d2Cu7qoaN2w=
github.com/jsimonetti/rtnetlink v0.0.0-20190606172950-9527aa82566a/go.mod h1:Oz+70psSo5OFh8DBl0Zv2ACw7Esh6pPUphlvZG9x7uw=
github.com/jsimonetti/rtnetlink v0.0.0-20200117123717-f846d4f6c1f4/go.mod h1:WGuG/smIU4J/54PblvSbh+xvCZmpJnFgr3ds6Z55XMQ=
github.com/jsimonetti/rtnetlink v0.0.0-20201009170750-9c6f07d100c1/go.mod h1:hqoO/u39cqLeBLebZ8fWdE96O7FxrAsRYhnVOdgHxok=
github.com/jsimonetti/rtnetlink v0.0.0-20201216134343-bde56ed16391/go.mod h1:cR77jAZG3Y3bsb8hF6fHJbFoyFukLFOkQ98S0pQz3xw=
github.com/jsimonetti/rtnetlink v0.0.0-20201220180245-69540ac93943/go.mod h1:z4c53zj6Eex712ROyh8WI0ihysb5j2ROyV42iNogmAs=
github.com/jsimonetti/rtnetlink v0.0.0-20210122163228-8d122574c736/go.mod h1:ZXpIyOK59ZnN7J0BV99cZUPmsqDRZ3eq5X+st7u/oSA=
github.com/jsimonetti/rtnetlink v0.0.0-20210212075122-66c871082f2b h1:c3NTyLNozICy8B4mlMXemD3z/gXgQzVXZS/HqT+i3do=
github.com/jsimonetti/rtnetlink v0.0.0-20210212075122-66c871082f2b/go.mod h1:8w9Rh8m+aHZIG69YPGGem1i5VzoyRC8nw2kA8B+ik5U=
github.com/josharian/native v1.0.0 h1:Ts/E8zCSEsG17dUqv7joXJFybuMLjQfWE04tsBODTxk=
github.com/josharian/native v1.0.0/go.mod h1:7X/raswPFr05uY3HiLlYeyQntB6OO7E/d2Cu7qoaN2w=
github.com/kevinburke/ssh_config v0.0.0-20190725054713-01f96b0aa0cd/go.mod h1:CT57kijsi8u/K/BOFA39wgDQJ9CxiF4nAY/ojJ6r6mM=
github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI=
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
@@ -68,29 +56,22 @@ github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
github.com/kyokomi/emoji v2.2.4+incompatible/go.mod h1:mZ6aGCD7yk8j6QY6KICwnZ2pxoszVseX1DNoGtU2tBA=
github.com/mailru/easyjson v0.0.0-20180823135443-60711f1a8329/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc=
github.com/mattn/go-colorable v0.1.8/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc=
github.com/mattn/go-colorable v0.1.9 h1:sqDoxXbdeALODt0DAeJCVp38ps9ZogZEAXjus69YV3U=
github.com/mattn/go-colorable v0.1.9/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc=
github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA=
github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg=
github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU=
github.com/mattn/go-isatty v0.0.14 h1:yVuAays6BHfxijgZPzw+3Zlu5yQgKGP2/hcQbHb7S9Y=
github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94=
github.com/mdlayher/ethtool v0.0.0-20210210192532-2b88debcdd43 h1:WgyLFv10Ov49JAQI/ZLUkCZ7VJS3r74hwFIGXJsgZlY=
github.com/mdlayher/ethtool v0.0.0-20210210192532-2b88debcdd43/go.mod h1:+t7E0lkKfbBsebllff1xdTmyJt8lH37niI6kwFk9OTo=
github.com/mdlayher/genetlink v1.0.0 h1:OoHN1OdyEIkScEmRgxLEe2M9U8ClMytqA5niynLtfj0=
github.com/mdlayher/genetlink v1.0.0/go.mod h1:0rJ0h4itni50A86M2kHcgS85ttZazNt7a8H2a2cw0Gc=
github.com/mdlayher/netlink v0.0.0-20190409211403-11939a169225/go.mod h1:eQB3mZE4aiYnlUsyGGCOpPETfdQq4Jhsgf1fk3cwQaA=
github.com/mdlayher/netlink v1.0.0/go.mod h1:KxeJAFOFLG6AjpyDkQ/iIhxygIUKD+vcwqcnu43w/+M=
github.com/mdlayher/netlink v1.1.0/go.mod h1:H4WCitaheIsdF9yOYu8CFmCgQthAPIWZmcKp9uZHgmY=
github.com/mdlayher/netlink v1.1.1/go.mod h1:WTYpFb/WTvlRJAyKhZL5/uy69TDDpHHu2VZmb2XgV7o=
github.com/mdlayher/netlink v1.2.0/go.mod h1:kwVW1io0AZy9A1E2YYgaD4Cj+C+GPkU6klXCMzIJ9p8=
github.com/mdlayher/netlink v1.2.1/go.mod h1:bacnNlfhqHqqLo4WsYeXSqfyXkInQ9JneWI68v1KwSU=
github.com/mdlayher/netlink v1.2.2-0.20210123213345-5cc92139ae3e/go.mod h1:bacnNlfhqHqqLo4WsYeXSqfyXkInQ9JneWI68v1KwSU=
github.com/mdlayher/netlink v1.3.0/go.mod h1:xK/BssKuwcRXHrtN04UBkwQ6dY9VviGGuriDdoPSWys=
github.com/mdlayher/netlink v1.4.0 h1:n3ARR+Fm0dDv37dj5wSWZXDKcy+U0zwcXS3zKMnSiT0=
github.com/mdlayher/netlink v1.4.0/go.mod h1:dRJi5IABcZpBD2A3D0Mv/AiX8I9uDEu5oGkAVrekmf8=
github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
github.com/mattn/go-isatty v0.0.17 h1:BTarxUcIeDqL27Mc+vyvdWYSL28zpIhv3RoTdsLMPng=
github.com/mattn/go-isatty v0.0.17/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
github.com/mdlayher/genetlink v1.2.0 h1:4yrIkRV5Wfk1WfpWTcoOlGmsWgQj3OtQN9ZsbrE+XtU=
github.com/mdlayher/genetlink v1.2.0/go.mod h1:ra5LDov2KrUCZJiAtEvXXZBxGMInICMXIwshlJ+qRxQ=
github.com/mdlayher/netlink v1.6.0 h1:rOHX5yl7qnlpiVkFWoqccueppMtXzeziFjWAjLg6sz0=
github.com/mdlayher/netlink v1.6.0/go.mod h1:0o3PlBmGst1xve7wQ7j/hwpNaFaH4qCRyWCdcZk8/vA=
github.com/mdlayher/socket v0.1.1/go.mod h1:mYV5YIZAfHh4dzDVzI8x8tWLWCliuX8Mon5Awbj+qDs=
github.com/mdlayher/socket v0.2.3 h1:XZA2X2TjdOwNoNPVPclRCURoX/hokBY8nkTmRZFEheM=
github.com/mdlayher/socket v0.2.3/go.mod h1:bz12/FozYNH/VbvC3q7TRIK/Y6dH1kCKsXaUeXi/FmY=
github.com/miekg/dns v1.1.40 h1:pyyPFfGMnciYUk/mXpKkVmeMQjfXqt3FAJ2hy7tPiLA=
github.com/miekg/dns v1.1.40/go.mod h1:KNUDUusw/aVsxyTYZM1oqvCicbwhgbNgztCETuNZ7xM=
github.com/mikioh/ipaddr v0.0.0-20190404000644-d465c8ab6721 h1:RlZweED6sbSArvlE924+mUcZuXKLBHA35U7LN621Bws=
github.com/mikioh/ipaddr v0.0.0-20190404000644-d465c8ab6721/go.mod h1:Ickgr2WtCLZ2MDGd4Gr0geeCH5HybhRJbonOgQpvSxc=
github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
github.com/mr-tron/base58 v1.2.0 h1:T/HDJBh4ZCPbU39/+c3rRvE0uKBQlU27+QI8LJ4t64o=
@@ -128,106 +109,91 @@ github.com/src-d/gcfg v1.4.0/go.mod h1:p/UMsR43ujA89BJY9duynAwIpvqEujIH/jFlfL7jW
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.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.8.0 h1:pSgiaMZlXftHpm5L7V1+rVB+AZJydKsMxsQBIJw4PKk=
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
github.com/stretchr/testify v1.8.2 h1:+h33VjcLVPDHtOdpUCuF+7gSuG3yGIftsP1YvFihtJ8=
github.com/stretchr/testify v1.8.2/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
github.com/vishvananda/netlink v1.1.1-0.20211129163951-9ada19101fc5 h1:b/k/BVWzWRS5v6AB0gf2ckFSbFsHN5jR0HoNso1pN+w=
github.com/vishvananda/netlink v1.1.1-0.20211129163951-9ada19101fc5/go.mod h1:twkDnbuQxJYemMlGd4JFIcuhgX83tXhKS2B/PRMpOho=
github.com/vishvananda/netns v0.0.0-20200728191858-db3c7e526aae h1:4hwBBUfQCFe3Cym0ZtKyq7L16eZUtYKs+BaHDN6mAns=
github.com/vishvananda/netns v0.0.0-20200728191858-db3c7e526aae/go.mod h1:DD4vA1DwXk04H54A1oHXtwZmA0grkVMdPxx/VGLCah0=
github.com/xanzy/ssh-agent v0.2.1/go.mod h1:mLlQY/MoOhWBj+gOGMQkOeiEvkx+8pJSI+0Bx9h2kr4=
github.com/yl2chen/cidranger v1.0.2/go.mod h1:9U1yz7WPYDwf0vpNWFaeRh0bjwz5RVgRy/9UEQfHl0g=
github.com/youmark/pkcs8 v0.0.0-20201027041543-1326539a0a0a h1:fZHgsYlfvtyqToslyjUt3VOPF4J7aK/3MPcK7xp3PDk=
github.com/youmark/pkcs8 v0.0.0-20201027041543-1326539a0a0a/go.mod h1:ul22v+Nro/R083muKhosV54bj5niojjWZvU8xrevuH4=
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
go4.org/intern v0.0.0-20210108033219-3eb7198706b2 h1:VFTf+jjIgsldaz/Mr00VaCSswHJrI2hIjQygE/W4IMg=
go4.org/intern v0.0.0-20210108033219-3eb7198706b2/go.mod h1:vLqJ+12kCw61iCWsPto0EOHhBS+o4rO5VIucbc9g2Cc=
go4.org/unsafe/assume-no-moving-gc v0.0.0-20201222175341-b30ae309168e/go.mod h1:FftLjUGFEDu5k8lt0ddY+HcrH/qU/0qk+H8j9/nTl3E=
go4.org/unsafe/assume-no-moving-gc v0.0.0-20201222180813-1025295fd063/go.mod h1:FftLjUGFEDu5k8lt0ddY+HcrH/qU/0qk+H8j9/nTl3E=
go4.org/unsafe/assume-no-moving-gc v0.0.0-20220617031537-928513b29760 h1:FyBZqvoA/jbNzuAWLQE2kG820zMAkcilx6BMjGbL/E4=
go4.org/unsafe/assume-no-moving-gc v0.0.0-20220617031537-928513b29760/go.mod h1:FftLjUGFEDu5k8lt0ddY+HcrH/qU/0qk+H8j9/nTl3E=
go4.org/unsafe/assume-no-moving-gc v0.0.0-20230221090011-e4bae7ad2296 h1:QJ/xcIANMLApehfgPCHnfK1hZiaMmbaTVmPv7DAoTbo=
go4.org/unsafe/assume-no-moving-gc v0.0.0-20230221090011-e4bae7ad2296/go.mod h1:FftLjUGFEDu5k8lt0ddY+HcrH/qU/0qk+H8j9/nTl3E=
golang.org/x/crypto v0.0.0-20190219172222-a4c6cb3142f2/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20190701094942-4def268fd1a4/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20200302210943-78000ba7a073/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20210220033148-5ea612d1eb83/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I=
golang.org/x/crypto v0.0.0-20210503195802-e9a32991a82e/go.mod h1:P+XmwS30IXTQdn5tA2iutPOUgjI07+tq3H3K9MVA1s8=
golang.org/x/crypto v0.0.0-20210513164829-c07d793c2f9a/go.mod h1:P+XmwS30IXTQdn5tA2iutPOUgjI07+tq3H3K9MVA1s8=
golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97 h1:/UOmuWzQfxxo9UtlXMwuQU8CMgg1eZXqTRwkSQJWKOI=
golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/crypto v0.0.0-20220411220226-7b82a4e95df4 h1:kUhD7nTDoI3fVd9G4ORWrbV5NY0liEs/Jg2pv5f+bBA=
golang.org/x/crypto v0.0.0-20220411220226-7b82a4e95df4/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/net v0.0.0-20181005035420-146acd28ed58/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20190724013045-ca1201d0de80/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20190827160401-ba9fcec4b297/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20190923162816-aa69164e4478/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20191007182048-72f939374954/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20201010224723-4f7140c49acb/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
golang.org/x/net v0.0.0-20201216054612-986b41b23924/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
golang.org/x/net v0.0.0-20201224014010-6772e930b67b/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
golang.org/x/net v0.0.0-20210119194325-5f4716e94777/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM=
golang.org/x/net v0.0.0-20210504132125-bbd867fde50d/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/net v0.0.0-20210916014120-12bc252f5db8 h1:/6y1LfuqNuQdHAm0jjtPtgRcxIxjVZgm5OTu8/QhZvk=
golang.org/x/net v0.0.0-20210916014120-12bc252f5db8/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/net v0.0.0-20210928044308-7d9f5e0b762b/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/net v0.0.0-20220127200216-cd36cc0744dd/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
golang.org/x/net v0.0.0-20220418201149-a630d4f3e7a2 h1:6mzvA99KwZxbOrxww4EvWVQUnN1+xEu9tafK5ZxkYeA=
golang.org/x/net v0.0.0-20220418201149-a630d4f3e7a2/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c h1:5KslGYwFpkhGh+Q16bwMP3cOontH8FOep7tGV86Y7SQ=
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190221075227-b4e8571b14e0/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190411185658-b44545bcd369/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190826190057-c7b8b68b1456/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190924154521-2837fb4f24fe/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191008105621-543471e840be/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200217220822-9197077df867/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200728102440-3e129f6d46b1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201009025420-dfb3f7c4e634/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201118182958-a01c418693c7/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201218084310-7d0127a74742/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210110051926-789bb1bd4061/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210123111255-9b0068b26619/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210216163648-f7da38b97c65/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210309040221-94ec62e08169/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210403161142-5e06dd20ab57/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210503173754-0981d6026fa6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c h1:F1jZWGFhYfh0Ci55sIpILtKKK8p3i2/krTr0H1rg74I=
golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw=
golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220128215802-99c3d69c2c27/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.6.0 h1:MVltZSvRTcU2ljQOhs94SXPftV6DCNnZViHeQps87pQ=
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.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 h1:olpwvP2KacW1ZWvsR7uQhoyTYvKAupfQrRGBFM352Gk=
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
golang.org/x/text v0.8.0 h1:57P1ETyNKtuIjB4SRd15iJxuhj8Gc416Y78H3qgMh68=
golang.org/x/text v0.8.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20190729092621-ff9f1409240a/go.mod h1:jcCCGcm9btYwXyDqrUWc6MKQKKGJCWEQ3AfLSRIbEuI=
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
@@ -239,11 +205,12 @@ golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8T
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE=
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.zx2c4.com/wireguard v0.0.0-20210427022245-097af6e1351b/go.mod h1:a057zjmoc00UN7gVkaJt2sXVK523kMJcogDTEvPIasg=
golang.zx2c4.com/wireguard v0.0.0-20210805125648-3957e9b9dd19 h1:ab2jcw2W91Rz07eHAb8Lic7sFQKO0NhBftjv6m/gL/0=
golang.zx2c4.com/wireguard v0.0.0-20210805125648-3957e9b9dd19/go.mod h1:laHzsbfMhGSobUmruXWAyMKKHSqvIcrqZJMyHD+/3O8=
golang.zx2c4.com/wireguard/wgctrl v0.0.0-20210803171230-4253848d036c h1:ADNrRDI5NR23/TUCnEmlLZLt4u9DnZ2nwRkPrAcFvto=
golang.zx2c4.com/wireguard/wgctrl v0.0.0-20210803171230-4253848d036c/go.mod h1:+1XihzyZUBJcSc5WO9SwNA7v26puQwOEDwanaxfNXPQ=
golang.zx2c4.com/wintun v0.0.0-20211104114900-415007cec224 h1:Ug9qvr1myri/zFN6xL17LSCBGFDnphBBhzmILHsM5TY=
golang.zx2c4.com/wintun v0.0.0-20211104114900-415007cec224/go.mod h1:deeaetjYA+DHMHg+sMSMI58GrEteJUUzzw7en6TJQcI=
golang.zx2c4.com/wireguard v0.0.0-20220703234212-c31a7b1ab478 h1:vDy//hdR+GnROE3OdYbQKt9rdtNdHkDtONvpRwmls/0=
golang.zx2c4.com/wireguard v0.0.0-20220703234212-c31a7b1ab478/go.mod h1:bVQfyl2sCM/QIIGHpWbFGfHPuDvqnCNkT6MQLTCjO/U=
golang.zx2c4.com/wireguard/wgctrl v0.0.0-20220504211119-3d4a969bb56b h1:9JncmKXcUwE918my+H6xmjBdhK2jM/UTUNXxhRG1BAk=
golang.zx2c4.com/wireguard/wgctrl v0.0.0-20220504211119-3d4a969bb56b/go.mod h1:yp4gl6zOlnDGOZeWeDfMwQcsdOIQnMdhuPx9mwwWBL4=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY=
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=

View File

@@ -73,7 +73,7 @@ func (c *CLI) FormatServers(args []string) error {
logger := newNoopLogger()
storage, err := storage.New(logger, constants.ServersData)
if err != nil {
return fmt.Errorf("cannot create servers storage: %w", err)
return fmt.Errorf("creating servers storage: %w", err)
}
formatted := storage.FormatToMarkdown(providerToFormat)
@@ -81,18 +81,18 @@ func (c *CLI) FormatServers(args []string) error {
output = filepath.Clean(output)
file, err := os.OpenFile(output, os.O_TRUNC|os.O_WRONLY|os.O_CREATE, 0644)
if err != nil {
return fmt.Errorf("cannot open output file: %w", err)
return fmt.Errorf("opening output file: %w", err)
}
_, err = fmt.Fprint(file, formatted)
if err != nil {
_ = file.Close()
return fmt.Errorf("cannot write to output file: %w", err)
return fmt.Errorf("writing to output file: %w", err)
}
err = file.Close()
if err != nil {
return fmt.Errorf("cannot close output file: %w", err)
return fmt.Errorf("closing output file: %w", err)
}
return nil

View File

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

View File

@@ -1,9 +1,9 @@
package sources
package cli
import "github.com/qdm12/gluetun/internal/configuration/settings"
type Source interface {
Read() (settings settings.Settings, err error)
ReadHealth() (settings settings.Health, err error)
ReadHealth() (health settings.Health, err error)
String() string
}

View File

@@ -8,7 +8,6 @@ import (
"strings"
"time"
"github.com/qdm12/gluetun/internal/configuration/sources"
"github.com/qdm12/gluetun/internal/constants"
"github.com/qdm12/gluetun/internal/openvpn/extract"
"github.com/qdm12/gluetun/internal/provider"
@@ -36,7 +35,12 @@ type IPFetcher interface {
FetchMultiInfo(ctx context.Context, ips []net.IP) (data []ipinfo.Response, err error)
}
func (c *CLI) OpenvpnConfig(logger OpenvpnConfigLogger, source sources.Source) error {
type IPv6Checker interface {
IsIPv6Supported() (supported bool, err error)
}
func (c *CLI) OpenvpnConfig(logger OpenvpnConfigLogger, source Source,
ipv6Checker IPv6Checker) error {
storage, err := storage.New(logger, constants.ServersData)
if err != nil {
return err
@@ -47,8 +51,13 @@ func (c *CLI) OpenvpnConfig(logger OpenvpnConfigLogger, source sources.Source) e
return err
}
if err = allSettings.Validate(storage); err != nil {
return err
ipv6Supported, err := ipv6Checker.IsIPv6Supported()
if err != nil {
return fmt.Errorf("checking for IPv6 support: %w", err)
}
if err = allSettings.Validate(storage, ipv6Supported); err != nil {
return fmt.Errorf("validating settings: %w", err)
}
// Unused by this CLI command
@@ -62,12 +71,14 @@ func (c *CLI) OpenvpnConfig(logger OpenvpnConfigLogger, source sources.Source) e
providers := provider.NewProviders(storage, time.Now, warner, client,
unzipper, parallelResolver, ipFetcher, openvpnFileExtractor)
providerConf := providers.Get(*allSettings.VPN.Provider.Name)
connection, err := providerConf.GetConnection(allSettings.VPN.Provider.ServerSelection)
connection, err := providerConf.GetConnection(
allSettings.VPN.Provider.ServerSelection, ipv6Supported)
if err != nil {
return err
}
lines := providerConf.OpenVPNConfig(connection, allSettings.VPN.OpenVPN)
lines := providerConf.OpenVPNConfig(connection,
allSettings.VPN.OpenVPN, ipv6Supported)
fmt.Println(strings.Join(lines, "\n"))
return nil

View File

@@ -72,7 +72,7 @@ func (c *CLI) Update(ctx context.Context, args []string, logger UpdaterLogger) e
storage, err := storage.New(logger, constants.ServersData)
if err != nil {
return fmt.Errorf("cannot create servers storage: %w", err)
return fmt.Errorf("creating servers storage: %w", err)
}
const clientTimeout = 10 * time.Second
@@ -88,13 +88,13 @@ func (c *CLI) Update(ctx context.Context, args []string, logger UpdaterLogger) e
updater := updater.New(httpClient, storage, providers, logger)
err = updater.UpdateServers(ctx, options.Providers, options.MinRatio)
if err != nil {
return fmt.Errorf("cannot update server information: %w", err)
return fmt.Errorf("updating server information: %w", err)
}
if maintainerMode {
err := storage.FlushToFile(c.repoServersPath)
if err != nil {
return fmt.Errorf("cannot write servers data to embedded JSON file: %w", err)
return fmt.Errorf("writing servers data to embedded JSON file: %w", err)
}
}

View File

@@ -31,7 +31,7 @@ type DNS struct {
func (d DNS) validate() (err error) {
err = d.DoT.validate()
if err != nil {
return fmt.Errorf("failed validating DoT settings: %w", err)
return fmt.Errorf("validating DoT settings: %w", err)
}
return nil

View File

@@ -37,10 +37,13 @@ var (
ErrWireguardEndpointIPNotSet = errors.New("endpoint IP is not set")
ErrWireguardEndpointPortNotAllowed = errors.New("endpoint port is not allowed")
ErrWireguardEndpointPortNotSet = errors.New("endpoint port is not set")
ErrWireguardEndpointPortSet = errors.New("endpoint port is set")
ErrWireguardInterfaceAddressNotSet = errors.New("interface address is not set")
ErrWireguardInterfaceAddressIPv6 = errors.New("interface address is IPv6 but IPv6 is not supported")
ErrWireguardInterfaceNotValid = errors.New("interface name is not valid")
ErrWireguardPreSharedKeyNotSet = errors.New("pre-shared key is not set")
ErrWireguardPrivateKeyNotSet = errors.New("private key is not set")
ErrWireguardPublicKeyNotSet = errors.New("public key is not set")
ErrWireguardPublicKeyNotValid = errors.New("public key is not valid")
ErrWireguardImplementationNotValid = errors.New("implementation is not valid")
)

View File

@@ -109,7 +109,8 @@ func (f Firewall) toLinesNode() (node *gotree.Node) {
if len(f.OutboundSubnets) > 0 {
outboundSubnets := node.Appendf("Outbound subnets:")
for _, subnet := range f.OutboundSubnets {
outboundSubnets.Appendf("%s", subnet)
subnet := subnet
outboundSubnets.Appendf("%s", &subnet)
}
}

View File

@@ -42,18 +42,18 @@ type OpenVPN struct {
// It cannot be nil in the internal state.
// It is ignored if it is set to the empty string.
Auth *string
// Cert is the OpenVPN certificate for the <cert> block.
// Cert is the base64 encoded DER of an OpenVPN certificate for the <cert> block.
// This is notably used by Cyberghost and VPN secure.
// It can be set to the empty string to be ignored.
// It cannot be nil in the internal state.
Cert *string
// Key is the OpenVPN key.
// 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
// EncryptedKey is the content of an encrypted
// key for OpenVPN. It is used by VPN secure.
// 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
@@ -65,10 +65,6 @@ type OpenVPN struct {
// Private Internet Access. It can be set to an
// empty string for other providers.
PIAEncPreset *string
// IPv6 is set to true if IPv6 routing should be
// set to be tunnel in OpenVPN, and false otherwise.
// It cannot be nil in the internal state.
IPv6 *bool // TODO automate like with Wireguard
// MSSFix is the value (1 to 10000) to set for the
// mssfix option for OpenVPN. It is ignored if set to 0.
// It cannot be nil in the internal state.
@@ -99,12 +95,15 @@ func (o OpenVPN) validate(vpnProvider string) (err error) {
}
isCustom := vpnProvider == providers.Custom
isUserRequired := !isCustom &&
vpnProvider != providers.Airvpn &&
vpnProvider != providers.VPNSecure
if !isCustom && *o.User == "" {
if isUserRequired && *o.User == "" {
return ErrOpenVPNUserIsEmpty
}
passwordRequired := !isCustom &&
passwordRequired := isUserRequired &&
(vpnProvider != providers.Ivpn || !ivpnAccountID.MatchString(*o.User))
if passwordRequired && *o.Password == "" {
@@ -172,7 +171,7 @@ func validateOpenVPNConfigFilepath(isCustom bool,
extractor := extract.New()
_, _, err = extractor.Data(confFile)
if err != nil {
return fmt.Errorf("failed extracting information from custom configuration file: %w", err)
return fmt.Errorf("extracting information from custom configuration file: %w", err)
}
return nil
@@ -182,6 +181,7 @@ func validateOpenVPNClientCertificate(vpnProvider,
clientCert string) (err error) {
switch vpnProvider {
case
providers.Airvpn,
providers.Cyberghost,
providers.VPNSecure,
providers.VPNUnlimited:
@@ -204,6 +204,7 @@ func validateOpenVPNClientCertificate(vpnProvider,
func validateOpenVPNClientKey(vpnProvider, clientKey string) (err error) {
switch vpnProvider {
case
providers.Airvpn,
providers.Cyberghost,
providers.VPNUnlimited,
providers.Wevpn:
@@ -253,7 +254,6 @@ func (o *OpenVPN) copy() (copied OpenVPN) {
EncryptedKey: helpers.CopyStringPtr(o.EncryptedKey),
KeyPassphrase: helpers.CopyStringPtr(o.KeyPassphrase),
PIAEncPreset: helpers.CopyStringPtr(o.PIAEncPreset),
IPv6: helpers.CopyBoolPtr(o.IPv6),
MSSFix: helpers.CopyUint16Ptr(o.MSSFix),
Interface: o.Interface,
ProcessUser: o.ProcessUser,
@@ -276,7 +276,6 @@ func (o *OpenVPN) mergeWith(other OpenVPN) {
o.EncryptedKey = helpers.MergeWithStringPtr(o.EncryptedKey, other.EncryptedKey)
o.KeyPassphrase = helpers.MergeWithStringPtr(o.KeyPassphrase, other.KeyPassphrase)
o.PIAEncPreset = helpers.MergeWithStringPtr(o.PIAEncPreset, other.PIAEncPreset)
o.IPv6 = helpers.MergeWithBool(o.IPv6, other.IPv6)
o.MSSFix = helpers.MergeWithUint16(o.MSSFix, other.MSSFix)
o.Interface = helpers.MergeWithString(o.Interface, other.Interface)
o.ProcessUser = helpers.MergeWithString(o.ProcessUser, other.ProcessUser)
@@ -299,7 +298,6 @@ func (o *OpenVPN) overrideWith(other OpenVPN) {
o.EncryptedKey = helpers.OverrideWithStringPtr(o.EncryptedKey, other.EncryptedKey)
o.KeyPassphrase = helpers.OverrideWithStringPtr(o.KeyPassphrase, other.KeyPassphrase)
o.PIAEncPreset = helpers.OverrideWithStringPtr(o.PIAEncPreset, other.PIAEncPreset)
o.IPv6 = helpers.OverrideWithBool(o.IPv6, other.IPv6)
o.MSSFix = helpers.OverrideWithUint16(o.MSSFix, other.MSSFix)
o.Interface = helpers.OverrideWithString(o.Interface, other.Interface)
o.ProcessUser = helpers.OverrideWithString(o.ProcessUser, other.ProcessUser)
@@ -328,8 +326,6 @@ func (o *OpenVPN) setDefaults(vpnProvider string) {
defaultEncPreset = presets.Strong
}
o.PIAEncPreset = helpers.DefaultStringPtr(o.PIAEncPreset, defaultEncPreset)
o.IPv6 = helpers.DefaultBool(o.IPv6, false)
o.MSSFix = helpers.DefaultUint16(o.MSSFix, 0)
o.Interface = helpers.DefaultString(o.Interface, "tun0")
o.ProcessUser = helpers.DefaultString(o.ProcessUser, "root")
@@ -375,8 +371,6 @@ func (o OpenVPN) toLinesNode() (node *gotree.Node) {
node.Appendf("Private Internet Access encryption preset: %s", *o.PIAEncPreset)
}
node.Appendf("Tunnel IPv6: %s", helpers.BoolPtrToYesNo(o.IPv6))
if *o.MSSFix > 0 {
node.Appendf("MSS Fix: %d", *o.MSSFix)
}

View File

@@ -67,6 +67,12 @@ func (o OpenVPNSelection) validate(vpnProvider string) (err error) {
default:
var allowedTCP, allowedUDP []uint16
switch vpnProvider {
case providers.Airvpn:
allowedTCP = []uint16{
53, 80, 443, // IP in 1, 3
1194, 2018, 41185, // IP in 1, 2, 3, 4
}
allowedUDP = []uint16{53, 80, 443, 1194, 2018, 41185}
case providers.Ivpn:
allowedTCP = []uint16{80, 443, 1143}
allowedUDP = []uint16{53, 1194, 2049, 2050}

View File

@@ -30,9 +30,11 @@ func (p *Provider) validate(vpnType string, storage Storage) (err error) {
validNames = append(validNames, "pia") // Retro-compatibility
} else { // Wireguard
validNames = []string{
providers.Airvpn,
providers.Custom,
providers.Ivpn,
providers.Mullvad,
providers.Surfshark,
providers.Windscribe,
}
}

View File

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

View File

@@ -34,7 +34,6 @@ func Test_Settings_String(t *testing.T) {
| ├── User: [not set]
| ├── Password: [not set]
| ├── Private Internet Access encryption preset: strong
| ├── Tunnel IPv6: no
| ├── Network interface: tun0
| ├── Run OpenVPN as: root
| └── Verbosity level: 1

View File

@@ -20,7 +20,7 @@ type VPN struct {
}
// TODO v4 remove pointer for receiver (because of Surfshark).
func (v *VPN) Validate(storage Storage) (err error) {
func (v *VPN) Validate(storage Storage, ipv6Supported bool) (err error) {
// Validate Type
validVPNTypes := []string{vpn.OpenVPN, vpn.Wireguard}
if !helpers.IsOneOf(v.Type, validVPNTypes...) {
@@ -39,7 +39,7 @@ func (v *VPN) Validate(storage Storage) (err error) {
return fmt.Errorf("OpenVPN settings: %w", err)
}
} else {
err := v.Wireguard.validate(*v.Provider.Name)
err := v.Wireguard.validate(*v.Provider.Name, ipv6Supported)
if err != nil {
return fmt.Errorf("Wireguard settings: %w", err)
}

View File

@@ -27,17 +27,23 @@ type Wireguard struct {
// to create. It cannot be the empty string in the
// internal state.
Interface string
// Implementation is the Wireguard implementation to use.
// It can be "auto", "userspace" or "kernelspace".
// It defaults to "auto" and cannot be the empty string
// in the internal state.
Implementation string
}
var regexpInterfaceName = regexp.MustCompile(`^[a-zA-Z0-9_]+$`)
// Validate validates Wireguard settings.
// It should only be ran if the VPN type chosen is Wireguard.
func (w Wireguard) validate(vpnProvider string) (err error) {
func (w Wireguard) validate(vpnProvider string, ipv6Supported bool) (err error) {
if !helpers.IsOneOf(vpnProvider,
providers.Custom,
providers.Ivpn,
providers.Mullvad,
providers.Surfshark,
providers.Windscribe,
) {
// do not validate for VPN provider not supporting Wireguard
@@ -53,6 +59,12 @@ func (w Wireguard) validate(vpnProvider string) (err error) {
return fmt.Errorf("private key is not valid: %w", err)
}
if vpnProvider == providers.Airvpn {
if *w.PreSharedKey == "" {
return fmt.Errorf("%w", ErrWireguardPreSharedKeyNotSet)
}
}
// Validate PreSharedKey
if *w.PreSharedKey != "" { // Note: this is optional
_, err = wgtypes.ParseKey(*w.PreSharedKey)
@@ -70,6 +82,12 @@ func (w Wireguard) validate(vpnProvider string) (err error) {
return fmt.Errorf("%w: for address at index %d: %s",
ErrWireguardInterfaceAddressNotSet, i, ipNet.String())
}
ipv6Net := ipNet.IP.To4() == nil
if ipv6Net && !ipv6Supported {
return fmt.Errorf("%w: address %s",
ErrWireguardInterfaceAddressIPv6, ipNet)
}
}
// Validate interface
@@ -78,15 +96,22 @@ func (w Wireguard) validate(vpnProvider string) (err error) {
ErrWireguardInterfaceNotValid, w.Interface, regexpInterfaceName)
}
validImplementations := []string{"auto", "userspace", "kernelspace"}
if !helpers.IsOneOf(w.Implementation, validImplementations...) {
return fmt.Errorf("%w: %s must be one of %s", ErrWireguardImplementationNotValid,
w.Implementation, helpers.ChoicesOrString(validImplementations))
}
return nil
}
func (w *Wireguard) copy() (copied Wireguard) {
return Wireguard{
PrivateKey: helpers.CopyStringPtr(w.PrivateKey),
PreSharedKey: helpers.CopyStringPtr(w.PreSharedKey),
Addresses: helpers.CopyIPNetSlice(w.Addresses),
Interface: w.Interface,
PrivateKey: helpers.CopyStringPtr(w.PrivateKey),
PreSharedKey: helpers.CopyStringPtr(w.PreSharedKey),
Addresses: helpers.CopyIPNetSlice(w.Addresses),
Interface: w.Interface,
Implementation: w.Implementation,
}
}
@@ -95,6 +120,7 @@ func (w *Wireguard) mergeWith(other Wireguard) {
w.PreSharedKey = helpers.MergeWithStringPtr(w.PreSharedKey, other.PreSharedKey)
w.Addresses = helpers.MergeIPNetsSlices(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) {
@@ -102,12 +128,14 @@ func (w *Wireguard) overrideWith(other Wireguard) {
w.PreSharedKey = helpers.OverrideWithStringPtr(w.PreSharedKey, other.PreSharedKey)
w.Addresses = helpers.OverrideWithIPNetsSlice(w.Addresses, other.Addresses)
w.Interface = helpers.OverrideWithString(w.Interface, other.Interface)
w.Implementation = helpers.OverrideWithString(w.Implementation, other.Implementation)
}
func (w *Wireguard) setDefaults() {
w.PrivateKey = helpers.DefaultStringPtr(w.PrivateKey, "")
w.PreSharedKey = helpers.DefaultStringPtr(w.PreSharedKey, "")
w.Interface = helpers.DefaultString(w.Interface, "wg0")
w.Implementation = helpers.DefaultString(w.Implementation, "auto")
}
func (w Wireguard) String() string {
@@ -134,5 +162,9 @@ func (w Wireguard) toLinesNode() (node *gotree.Node) {
node.Appendf("Network interface: %s", w.Interface)
if w.Implementation != "auto" {
node.Appendf("Implementation: %s", w.Implementation)
}
return node
}

View File

@@ -19,7 +19,7 @@ type WireguardSelection struct {
// in the internal state.
EndpointIP net.IP
// EndpointPort is a the server port to use for the VPN server.
// It is optional for VPN providers IVPN, Mullvad
// 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
@@ -36,7 +36,9 @@ type WireguardSelection struct {
func (w WireguardSelection) validate(vpnProvider string) (err error) {
// Validate EndpointIP
switch vpnProvider {
case providers.Ivpn, providers.Mullvad, providers.Windscribe: // endpoint IP addresses are baked in
case providers.Airvpn, providers.Ivpn, providers.Mullvad,
providers.Surfshark, providers.Windscribe:
// endpoint IP addresses are baked in
case providers.Custom:
if len(w.EndpointIP) == 0 {
return ErrWireguardEndpointIPNotSet
@@ -51,7 +53,12 @@ func (w WireguardSelection) validate(vpnProvider string) (err error) {
if *w.EndpointPort == 0 {
return ErrWireguardEndpointPortNotSet
}
case providers.Ivpn, providers.Mullvad, providers.Windscribe:
// EndpointPort cannot be set
case providers.Surfshark:
if *w.EndpointPort != 0 {
return ErrWireguardEndpointPortSet
}
case providers.Airvpn, providers.Ivpn, providers.Mullvad, providers.Windscribe:
// EndpointPort is optional and can be 0
if *w.EndpointPort == 0 {
break // no custom endpoint port set
@@ -61,6 +68,8 @@ func (w WireguardSelection) validate(vpnProvider string) (err error) {
}
var allowed []uint16
switch vpnProvider {
case providers.Airvpn:
allowed = []uint16{1637, 47107}
case providers.Ivpn:
allowed = []uint16{2049, 2050, 53, 30587, 41893, 48574, 58237}
case providers.Windscribe:
@@ -78,7 +87,9 @@ func (w WireguardSelection) validate(vpnProvider string) (err error) {
// Validate PublicKey
switch vpnProvider {
case providers.Ivpn, providers.Mullvad, providers.Windscribe: // public keys are baked in
case providers.Ivpn, providers.Mullvad,
providers.Surfshark, providers.Windscribe:
// public keys are baked in
case providers.Custom:
if w.PublicKey == "" {
return ErrWireguardPublicKeyNotSet

View File

@@ -7,8 +7,8 @@ import (
"github.com/qdm12/gluetun/internal/configuration/settings"
)
func (r *Reader) readDNS() (dns settings.DNS, err error) {
dns.ServerAddress, err = r.readDNSServerAddress()
func (s *Source) readDNS() (dns settings.DNS, err error) {
dns.ServerAddress, err = s.readDNSServerAddress()
if err != nil {
return dns, err
}
@@ -18,7 +18,7 @@ func (r *Reader) readDNS() (dns settings.DNS, err error) {
return dns, fmt.Errorf("environment variable DNS_KEEP_NAMESERVER: %w", err)
}
dns.DoT, err = r.readDoT()
dns.DoT, err = s.readDoT()
if err != nil {
return dns, fmt.Errorf("DoT settings: %w", err)
}
@@ -26,22 +26,22 @@ func (r *Reader) readDNS() (dns settings.DNS, err error) {
return dns, nil
}
func (r *Reader) readDNSServerAddress() (address net.IP, err error) {
key, s := r.getEnvWithRetro("DNS_ADDRESS", "DNS_PLAINTEXT_ADDRESS")
if s == "" {
func (s *Source) readDNSServerAddress() (address net.IP, err error) {
key, value := s.getEnvWithRetro("DNS_ADDRESS", "DNS_PLAINTEXT_ADDRESS")
if value == "" {
return nil, nil
}
address = net.ParseIP(s)
address = net.ParseIP(value)
if address == nil {
return nil, fmt.Errorf("environment variable %s: %w: %s", key, ErrIPAddressParse, s)
return nil, fmt.Errorf("environment variable %s: %w: %s", key, ErrIPAddressParse, value)
}
// TODO remove in v4
if !address.Equal(net.IPv4(127, 0, 0, 1)) { //nolint:gomnd
r.warner.Warn(key + " is set to " + s +
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 server." +
" 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" +
" corresponding to the first DoT provider chosen is used.")
}

View File

@@ -9,13 +9,13 @@ import (
"inet.af/netaddr"
)
func (r *Reader) readDNSBlacklist() (blacklist settings.DNSBlacklist, err error) {
func (s *Source) readDNSBlacklist() (blacklist settings.DNSBlacklist, err error) {
blacklist.BlockMalicious, err = envToBoolPtr("BLOCK_MALICIOUS")
if err != nil {
return blacklist, fmt.Errorf("environment variable BLOCK_MALICIOUS: %w", err)
}
blacklist.BlockSurveillance, err = r.readBlockSurveillance()
blacklist.BlockSurveillance, err = s.readBlockSurveillance()
if err != nil {
return blacklist, err
}
@@ -36,8 +36,8 @@ func (r *Reader) readDNSBlacklist() (blacklist settings.DNSBlacklist, err error)
return blacklist, nil
}
func (r *Reader) readBlockSurveillance() (blocked *bool, err error) {
key, value := r.getEnvWithRetro("BLOCK_SURVEILLANCE", "BLOCK_NSA")
func (s *Source) readBlockSurveillance() (blocked *bool, err error) {
key, value := s.getEnvWithRetro("BLOCK_SURVEILLANCE", "BLOCK_NSA")
if value == "" {
return nil, nil //nolint:nilnil
}

View File

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

View File

@@ -9,7 +9,7 @@ import (
"github.com/qdm12/gluetun/internal/configuration/settings"
)
func (r *Reader) readFirewall() (firewall settings.Firewall, err error) {
func (s *Source) readFirewall() (firewall settings.Firewall, err error) {
vpnInputPortStrings := envToCSV("FIREWALL_VPN_INPUT_PORTS")
firewall.VPNInputPorts, err = stringsToPorts(vpnInputPortStrings)
if err != nil {
@@ -22,7 +22,7 @@ func (r *Reader) readFirewall() (firewall settings.Firewall, err error) {
return firewall, fmt.Errorf("environment variable FIREWALL_INPUT_PORTS: %w", err)
}
outboundSubnetsKey, _ := r.getEnvWithRetro("FIREWALL_OUTBOUND_SUBNETS", "EXTRA_SUBNETS")
outboundSubnetsKey, _ := s.getEnvWithRetro("FIREWALL_OUTBOUND_SUBNETS", "EXTRA_SUBNETS")
outboundSubnetStrings := envToCSV(outboundSubnetsKey)
firewall.OutboundSubnets, err = stringsToIPNets(outboundSubnetStrings)
if err != nil {
@@ -73,7 +73,7 @@ func stringsToIPNets(ss []string) (ipNets []net.IPNet, err error) {
for i, s := range ss {
ip, ipNet, err := net.ParseCIDR(s)
if err != nil {
return nil, fmt.Errorf("cannot parse IP network %q: %w", s, err)
return nil, fmt.Errorf("parsing IP network %q: %w", s, err)
}
ipNet.IP = ip
ipNets[i] = *ipNet

View File

@@ -7,18 +7,18 @@ import (
"github.com/qdm12/gluetun/internal/configuration/settings"
)
func (r *Reader) ReadHealth() (health settings.Health, err error) {
func (s *Source) ReadHealth() (health settings.Health, err error) {
health.ServerAddress = getCleanedEnv("HEALTH_SERVER_ADDRESS")
_, health.TargetAddress = r.getEnvWithRetro("HEALTH_TARGET_ADDRESS", "HEALTH_ADDRESS_TO_PING")
_, health.TargetAddress = s.getEnvWithRetro("HEALTH_TARGET_ADDRESS", "HEALTH_ADDRESS_TO_PING")
health.VPN.Initial, err = r.readDurationWithRetro(
health.VPN.Initial, err = s.readDurationWithRetro(
"HEALTH_VPN_DURATION_INITIAL",
"HEALTH_OPENVPN_DURATION_INITIAL")
if err != nil {
return health, err
}
health.VPN.Addition, err = r.readDurationWithRetro(
health.VPN.Addition, err = s.readDurationWithRetro(
"HEALTH_VPN_DURATION_ADDITION",
"HEALTH_OPENVPN_DURATION_ADDITION")
if err != nil {
@@ -28,14 +28,14 @@ func (r *Reader) ReadHealth() (health settings.Health, err error) {
return health, nil
}
func (r *Reader) readDurationWithRetro(envKey, retroEnvKey string) (d *time.Duration, err error) {
envKey, s := r.getEnvWithRetro(envKey, retroEnvKey)
if s == "" {
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(s)
*d, err = time.ParseDuration(value)
if err != nil {
return nil, fmt.Errorf("environment variable %s: %w", envKey, err)
}

View File

@@ -137,7 +137,7 @@ func unsetEnvKeys(envKeys []string, err error) (newErr error) {
for _, envKey := range envKeys {
unsetErr := os.Unsetenv(envKey)
if unsetErr != nil && newErr == nil {
newErr = fmt.Errorf("cannot unset environment variable %s: %w", envKey, unsetErr)
newErr = fmt.Errorf("unsetting environment variable %s: %w", envKey, unsetErr)
}
}
return newErr

View File

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

View File

@@ -8,7 +8,7 @@ import (
"github.com/qdm12/govalid/binary"
)
func (r *Reader) readOpenVPN() (
func (s *Source) readOpenVPN() (
openVPN settings.OpenVPN, err error) {
defer func() {
err = unsetEnvKeys([]string{"OPENVPN_KEY", "OPENVPN_CERT",
@@ -16,14 +16,14 @@ func (r *Reader) readOpenVPN() (
}()
openVPN.Version = getCleanedEnv("OPENVPN_VERSION")
openVPN.User = r.readOpenVPNUser()
openVPN.Password = r.readOpenVPNPassword()
openVPN.User = s.readOpenVPNUser()
openVPN.Password = s.readOpenVPNPassword()
confFile := getCleanedEnv("OPENVPN_CUSTOM_CONFIG")
if confFile != "" {
openVPN.ConfFile = &confFile
}
ciphersKey, _ := r.getEnvWithRetro("OPENVPN_CIPHERS", "OPENVPN_CIPHER")
ciphersKey, _ := s.getEnvWithRetro("OPENVPN_CIPHERS", "OPENVPN_CIPHER")
openVPN.Ciphers = envToCSV(ciphersKey)
auth := getCleanedEnv("OPENVPN_AUTH")
@@ -35,23 +35,18 @@ func (r *Reader) readOpenVPN() (
openVPN.Key = envToStringPtr("OPENVPN_KEY")
openVPN.EncryptedKey = envToStringPtr("OPENVPN_ENCRYPTED_KEY")
openVPN.KeyPassphrase = r.readOpenVPNKeyPassphrase()
openVPN.KeyPassphrase = s.readOpenVPNKeyPassphrase()
openVPN.PIAEncPreset = r.readPIAEncryptionPreset()
openVPN.IPv6, err = envToBoolPtr("OPENVPN_IPV6")
if err != nil {
return openVPN, fmt.Errorf("environment variable OPENVPN_IPV6: %w", err)
}
openVPN.PIAEncPreset = s.readPIAEncryptionPreset()
openVPN.MSSFix, err = envToUint16Ptr("OPENVPN_MSSFIX")
if err != nil {
return openVPN, fmt.Errorf("environment variable OPENVPN_MSSFIX: %w", err)
}
_, openVPN.Interface = r.getEnvWithRetro("VPN_INTERFACE", "OPENVPN_INTERFACE")
_, openVPN.Interface = s.getEnvWithRetro("VPN_INTERFACE", "OPENVPN_INTERFACE")
openVPN.ProcessUser, err = r.readOpenVPNProcessUser()
openVPN.ProcessUser, err = s.readOpenVPNProcessUser()
if err != nil {
return openVPN, err
}
@@ -69,9 +64,9 @@ func (r *Reader) readOpenVPN() (
return openVPN, nil
}
func (r *Reader) readOpenVPNUser() (user *string) {
func (s *Source) readOpenVPNUser() (user *string) {
user = new(string)
_, *user = r.getEnvWithRetro("OPENVPN_USER", "USER")
_, *user = s.getEnvWithRetro("OPENVPN_USER", "USER")
if *user == "" {
return nil
}
@@ -81,9 +76,9 @@ func (r *Reader) readOpenVPNUser() (user *string) {
return user
}
func (r *Reader) readOpenVPNPassword() (password *string) {
func (s *Source) readOpenVPNPassword() (password *string) {
password = new(string)
_, *password = r.getEnvWithRetro("OPENVPN_PASSWORD", "PASSWORD")
_, *password = s.getEnvWithRetro("OPENVPN_PASSWORD", "PASSWORD")
if *password == "" {
return nil
}
@@ -91,7 +86,7 @@ func (r *Reader) readOpenVPNPassword() (password *string) {
return password
}
func (r *Reader) readOpenVPNKeyPassphrase() (passphrase *string) {
func (s *Source) readOpenVPNKeyPassphrase() (passphrase *string) {
passphrase = new(string)
*passphrase = getCleanedEnv("OPENVPN_KEY_PASSPHRASE")
if *passphrase == "" {
@@ -100,8 +95,8 @@ func (r *Reader) readOpenVPNKeyPassphrase() (passphrase *string) {
return passphrase
}
func (r *Reader) readPIAEncryptionPreset() (presetPtr *string) {
_, preset := r.getEnvWithRetro(
func (s *Source) readPIAEncryptionPreset() (presetPtr *string) {
_, preset := s.getEnvWithRetro(
"PRIVATE_INTERNET_ACCESS_OPENVPN_ENCRYPTION_PRESET",
"PIA_ENCRYPTION", "ENCRYPTION")
if preset != "" {
@@ -110,8 +105,8 @@ func (r *Reader) readPIAEncryptionPreset() (presetPtr *string) {
return nil
}
func (r *Reader) readOpenVPNProcessUser() (processUser string, err error) {
key, value := r.getEnvWithRetro("OPENVPN_PROCESS_USER", "OPENVPN_ROOT")
func (s *Source) readOpenVPNProcessUser() (processUser string, err error) {
key, value := s.getEnvWithRetro("OPENVPN_PROCESS_USER", "OPENVPN_ROOT")
if key == "OPENVPN_PROCESS_USER" {
return value, nil
}

View File

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

View File

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

View File

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

View File

@@ -7,13 +7,13 @@ import (
"github.com/qdm12/gluetun/internal/configuration/settings"
)
func (r *Reader) readPublicIP() (publicIP settings.PublicIP, err error) {
func (s *Source) readPublicIP() (publicIP settings.PublicIP, err error) {
publicIP.Period, err = readPublicIPPeriod()
if err != nil {
return publicIP, err
}
publicIP.IPFilepath = r.readPublicIPFilepath()
publicIP.IPFilepath = s.readPublicIPFilepath()
return publicIP, nil
}
@@ -33,10 +33,10 @@ func readPublicIPPeriod() (period *time.Duration, err error) {
return period, nil
}
func (r *Reader) readPublicIPFilepath() (filepath *string) {
_, s := r.getEnvWithRetro("PUBLICIP_FILE", "IP_STATUS_FILE")
if s != "" {
return &s
func (s *Source) readPublicIPFilepath() (filepath *string) {
_, value := s.getEnvWithRetro("PUBLICIP_FILE", "IP_STATUS_FILE")
if value != "" {
return &value
}
return nil
}

View File

@@ -2,12 +2,9 @@ package env
import (
"github.com/qdm12/gluetun/internal/configuration/settings"
"github.com/qdm12/gluetun/internal/configuration/sources"
)
var _ sources.Source = (*Reader)(nil)
type Reader struct {
type Source struct {
warner Warner
}
@@ -15,36 +12,36 @@ type Warner interface {
Warn(s string)
}
func New(warner Warner) *Reader {
return &Reader{
func New(warner Warner) *Source {
return &Source{
warner: warner,
}
}
func (r *Reader) String() string { return "environment variables" }
func (s *Source) String() string { return "environment variables" }
func (r *Reader) Read() (settings settings.Settings, err error) {
settings.VPN, err = r.readVPN()
func (s *Source) Read() (settings settings.Settings, err error) {
settings.VPN, err = s.readVPN()
if err != nil {
return settings, err
}
settings.Firewall, err = r.readFirewall()
settings.Firewall, err = s.readFirewall()
if err != nil {
return settings, err
}
settings.System, err = r.readSystem()
settings.System, err = s.readSystem()
if err != nil {
return settings, err
}
settings.Health, err = r.ReadHealth()
settings.Health, err = s.ReadHealth()
if err != nil {
return settings, err
}
settings.HTTPProxy, err = r.readHTTPProxy()
settings.HTTPProxy, err = s.readHTTPProxy()
if err != nil {
return settings, err
}
@@ -54,7 +51,7 @@ func (r *Reader) Read() (settings settings.Settings, err error) {
return settings, err
}
settings.PublicIP, err = r.readPublicIP()
settings.PublicIP, err = s.readPublicIP()
if err != nil {
return settings, err
}
@@ -69,17 +66,17 @@ func (r *Reader) Read() (settings settings.Settings, err error) {
return settings, err
}
settings.Shadowsocks, err = r.readShadowsocks()
settings.Shadowsocks, err = s.readShadowsocks()
if err != nil {
return settings, err
}
settings.DNS, err = r.readDNS()
settings.DNS, err = s.readDNS()
if err != nil {
return settings, err
}
settings.ControlServer, err = r.readControlServer()
settings.ControlServer, err = s.readControlServer()
if err != nil {
return settings, err
}
@@ -92,8 +89,8 @@ func (r *Reader) Read() (settings settings.Settings, err error) {
return settings, nil
}
func (r *Reader) onRetroActive(oldKey, newKey string) {
r.warner.Warn(
func (s *Source) onRetroActive(oldKey, newKey string) {
s.warner.Warn(
"You are using the old environment variable " + oldKey +
", please consider changing it to " + newKey)
}
@@ -104,14 +101,14 @@ func (r *Reader) onRetroActive(oldKey, newKey string) {
// 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 (r *Reader) getEnvWithRetro(currentKey string,
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 != "" {
r.onRetroActive(key, currentKey)
s.onRetroActive(key, currentKey)
return key, value
}
}

View File

@@ -7,13 +7,13 @@ import (
"github.com/qdm12/govalid/binary"
)
func (r *Reader) readControlServer() (controlServer settings.ControlServer, err error) {
func (s *Source) readControlServer() (controlServer settings.ControlServer, err error) {
controlServer.Log, err = readControlServerLog()
if err != nil {
return controlServer, err
}
controlServer.Address = r.readControlServerAddress()
controlServer.Address = s.readControlServerAddress()
return controlServer, nil
}
@@ -32,17 +32,17 @@ func readControlServerLog() (enabled *bool, err error) {
return &log, nil
}
func (r *Reader) readControlServerAddress() (address *string) {
key, s := r.getEnvWithRetro("HTTP_CONTROL_SERVER_ADDRESS", "HTTP_CONTROL_SERVER_PORT")
if s == "" {
func (s *Source) readControlServerAddress() (address *string) {
key, value := s.getEnvWithRetro("HTTP_CONTROL_SERVER_ADDRESS", "HTTP_CONTROL_SERVER_PORT")
if value == "" {
return nil
}
if key == "HTTP_CONTROL_SERVER_ADDRESS" {
return &s
return &value
}
address = new(string)
*address = ":" + s
*address = ":" + value
return address
}

View File

@@ -15,44 +15,45 @@ var (
ErrServerNumberNotValid = errors.New("server number is not valid")
)
func (r *Reader) readServerSelection(vpnProvider, vpnType string) (
func (s *Source) readServerSelection(vpnProvider, vpnType string) (
ss settings.ServerSelection, err error) {
ss.VPN = vpnType
ss.TargetIP, err = r.readOpenVPNTargetIP()
ss.TargetIP, err = s.readOpenVPNTargetIP()
if err != nil {
return ss, err
}
countriesKey, _ := r.getEnvWithRetro("SERVER_COUNTRIES", "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 = envToCSV("REGION")
if len(ss.Countries) > 0 {
r.onRetroActive("REGION", "SERVER_COUNTRIES")
s.onRetroActive("REGION", "SERVER_COUNTRIES")
}
}
regionsKey, _ := r.getEnvWithRetro("SERVER_REGIONS", "REGION")
regionsKey, _ := s.getEnvWithRetro("SERVER_REGIONS", "REGION")
ss.Regions = envToCSV(regionsKey)
citiesKey, _ := r.getEnvWithRetro("SERVER_CITIES", "CITY")
citiesKey, _ := s.getEnvWithRetro("SERVER_CITIES", "CITY")
ss.Cities = envToCSV(citiesKey)
ss.ISPs = envToCSV("ISP")
hostnamesKey, _ := r.getEnvWithRetro("SERVER_HOSTNAMES", "SERVER_HOSTNAME")
hostnamesKey, _ := s.getEnvWithRetro("SERVER_HOSTNAMES", "SERVER_HOSTNAME")
ss.Hostnames = envToCSV(hostnamesKey)
serverNamesKey, _ := r.getEnvWithRetro("SERVER_NAMES", "SERVER_NAME")
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 {
number, err := strconv.Atoi(numberString)
const base, bitSize = 10, 16
number, err := strconv.ParseInt(numberString, base, bitSize)
if err != nil {
return ss, fmt.Errorf("%w: %s",
ErrServerNumberNotValid, numberString)
@@ -66,7 +67,7 @@ func (r *Reader) readServerSelection(vpnProvider, vpnType string) (
}
// Mullvad only
ss.OwnedOnly, err = r.readOwnedOnly()
ss.OwnedOnly, err = s.readOwnedOnly()
if err != nil {
return ss, err
}
@@ -95,12 +96,12 @@ func (r *Reader) readServerSelection(vpnProvider, vpnType string) (
return ss, fmt.Errorf("environment variable STREAM_ONLY: %w", err)
}
ss.OpenVPN, err = r.readOpenVPNSelection()
ss.OpenVPN, err = s.readOpenVPNSelection()
if err != nil {
return ss, err
}
ss.Wireguard, err = r.readWireguardSelection()
ss.Wireguard, err = s.readWireguardSelection()
if err != nil {
return ss, err
}
@@ -112,23 +113,23 @@ var (
ErrInvalidIP = errors.New("invalid IP address")
)
func (r *Reader) readOpenVPNTargetIP() (ip net.IP, err error) {
envKey, s := r.getEnvWithRetro("VPN_ENDPOINT_IP", "OPENVPN_TARGET_IP")
if s == "" {
func (s *Source) readOpenVPNTargetIP() (ip net.IP, err error) {
envKey, value := s.getEnvWithRetro("VPN_ENDPOINT_IP", "OPENVPN_TARGET_IP")
if value == "" {
return nil, nil
}
ip = net.ParseIP(s)
ip = net.ParseIP(value)
if ip == nil {
return nil, fmt.Errorf("environment variable %s: %w: %s",
envKey, ErrInvalidIP, s)
envKey, ErrInvalidIP, value)
}
return ip, nil
}
func (r *Reader) readOwnedOnly() (ownedOnly *bool, err error) {
envKey, _ := r.getEnvWithRetro("OWNED_ONLY", "OWNED")
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)

View File

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

View File

@@ -14,13 +14,13 @@ var (
ErrSystemTimezoneNotValid = errors.New("timezone is not valid")
)
func (r *Reader) readSystem() (system settings.System, err error) {
system.PUID, err = r.readID("PUID", "UID")
func (s *Source) readSystem() (system settings.System, err error) {
system.PUID, err = s.readID("PUID", "UID")
if err != nil {
return system, err
}
system.PGID, err = r.readID("PGID", "GID")
system.PGID, err = s.readID("PGID", "GID")
if err != nil {
return system, err
}
@@ -32,9 +32,9 @@ func (r *Reader) readSystem() (system settings.System, err error) {
var ErrSystemIDNotValid = errors.New("system ID is not valid")
func (r *Reader) readID(key, retroKey string) (
func (s *Source) readID(key, retroKey string) (
id *uint32, err error) {
idEnvKey, idString := r.getEnvWithRetro(key, retroKey)
idEnvKey, idString := s.getEnvWithRetro(key, retroKey)
if idString == "" {
return nil, nil //nolint:nilnil
}

View File

@@ -74,8 +74,8 @@ func Test_Reader_readID(t *testing.T) {
setTestEnv(t, key, testCase.keyValue)
setTestEnv(t, retroKey, testCase.retroValue)
reader := &Reader{}
id, err := reader.readID(key, retroKey)
source := &Source{}
id, err := source.readID(key, retroKey)
assert.ErrorIs(t, err, testCase.errWrapped)
if err != nil {

View File

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

View File

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

View File

@@ -9,14 +9,14 @@ import (
"github.com/qdm12/govalid/port"
)
func (r *Reader) readWireguardSelection() (
func (s *Source) readWireguardSelection() (
selection settings.WireguardSelection, err error) {
selection.EndpointIP, err = r.readWireguardEndpointIP()
selection.EndpointIP, err = s.readWireguardEndpointIP()
if err != nil {
return selection, err
}
selection.EndpointPort, err = r.readWireguardCustomPort()
selection.EndpointPort, err = s.readWireguardCustomPort()
if err != nil {
return selection, err
}
@@ -28,29 +28,29 @@ func (r *Reader) readWireguardSelection() (
var ErrIPAddressParse = errors.New("cannot parse IP address")
func (r *Reader) readWireguardEndpointIP() (endpointIP net.IP, err error) {
key, s := r.getEnvWithRetro("VPN_ENDPOINT_IP", "WIREGUARD_ENDPOINT_IP")
if s == "" {
func (s *Source) readWireguardEndpointIP() (endpointIP net.IP, err error) {
key, value := s.getEnvWithRetro("VPN_ENDPOINT_IP", "WIREGUARD_ENDPOINT_IP")
if value == "" {
return nil, nil
}
endpointIP = net.ParseIP(s)
endpointIP = net.ParseIP(value)
if endpointIP == nil {
return nil, fmt.Errorf("environment variable %s: %w: %s",
key, ErrIPAddressParse, s)
key, ErrIPAddressParse, value)
}
return endpointIP, nil
}
func (r *Reader) readWireguardCustomPort() (customPort *uint16, err error) {
key, s := r.getEnvWithRetro("VPN_ENDPOINT_PORT", "WIREGUARD_ENDPOINT_PORT")
if s == "" {
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(s)
*customPort, err = port.Validate(value)
if err != nil {
return nil, fmt.Errorf("environment variable %s: %w", key, err)
}

View File

@@ -2,4 +2,4 @@ package files
import "github.com/qdm12/gluetun/internal/configuration/settings"
func (r *Reader) ReadHealth() (settings settings.Health, err error) { return settings, nil }
func (s *Source) ReadHealth() (settings settings.Health, err error) { return settings, nil }

View File

@@ -14,7 +14,7 @@ const (
openVPNEncryptedKey = "/gluetun/openvpn_encrypted_key"
)
func (r *Reader) readOpenVPN() (settings settings.OpenVPN, err error) {
func (s *Source) readOpenVPN() (settings settings.OpenVPN, err error) {
settings.Key, err = readPEMFile(OpenVPNClientKeyPath)
if err != nil {
return settings, fmt.Errorf("client key: %w", err)

View File

@@ -2,26 +2,23 @@ package files
import (
"github.com/qdm12/gluetun/internal/configuration/settings"
"github.com/qdm12/gluetun/internal/configuration/sources"
)
var _ sources.Source = (*Reader)(nil)
type Source struct{}
type Reader struct{}
func New() *Reader {
return &Reader{}
func New() *Source {
return &Source{}
}
func (r *Reader) String() string { return "files" }
func (s *Source) String() string { return "files" }
func (r *Reader) Read() (settings settings.Settings, err error) {
settings.VPN, err = r.readVPN()
func (s *Source) Read() (settings settings.Settings, err error) {
settings.VPN, err = s.readVPN()
if err != nil {
return settings, err
}
settings.System, err = r.readSystem()
settings.System, err = s.readSystem()
if err != nil {
return settings, err
}

View File

@@ -4,7 +4,7 @@ import (
"github.com/qdm12/gluetun/internal/configuration/settings"
)
func (r *Reader) readSystem() (system settings.System, err error) {
func (s *Source) readSystem() (system settings.System, err error) {
// TODO timezone from /etc/localtime
return system, nil
}

View File

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

View File

@@ -1,29 +1,32 @@
package mux
package merge
import (
"fmt"
"strings"
"github.com/qdm12/gluetun/internal/configuration/settings"
"github.com/qdm12/gluetun/internal/configuration/sources"
)
var _ sources.Source = (*Reader)(nil)
type Reader struct {
sources []sources.Source
type ConfigSource interface {
Read() (settings settings.Settings, err error)
ReadHealth() (settings settings.Health, err error)
String() string
}
func New(sources ...sources.Source) *Reader {
return &Reader{
type Source struct {
sources []ConfigSource
}
func New(sources ...ConfigSource) *Source {
return &Source{
sources: sources,
}
}
func (r *Reader) String() string {
sources := make([]string, len(r.sources))
for i := range r.sources {
sources[i] = r.sources[i].String()
func (s *Source) String() string {
sources := make([]string, len(s.sources))
for i := range s.sources {
sources[i] = s.sources[i].String()
}
return strings.Join(sources, ", ")
}
@@ -31,8 +34,8 @@ func (r *Reader) String() string {
// Read reads the settings for each source, merging unset fields
// with field set by the next source.
// It then set defaults to remaining unset fields.
func (r *Reader) Read() (settings settings.Settings, err error) {
for _, source := range r.sources {
func (s *Source) Read() (settings settings.Settings, err error) {
for _, source := range s.sources {
settingsFromSource, err := source.Read()
if err != nil {
return settings, fmt.Errorf("reading from %s: %w", source, err)
@@ -47,8 +50,8 @@ func (r *Reader) Read() (settings settings.Settings, err error) {
// with field set by the next source.
// It then set defaults to remaining unset fields, and validate
// all the fields.
func (r *Reader) ReadHealth() (settings settings.Health, err error) {
for _, source := range r.sources {
func (s *Source) ReadHealth() (settings settings.Health, err error) {
for _, source := range s.sources {
settingsFromSource, err := source.ReadHealth()
if err != nil {
return settings, fmt.Errorf("reading from %s: %w", source, err)

View File

@@ -2,4 +2,4 @@ package secrets
import "github.com/qdm12/gluetun/internal/configuration/settings"
func (r *Reader) ReadHealth() (settings settings.Health, err error) { return settings, nil }
func (s *Source) ReadHealth() (settings settings.Health, err error) { return settings, nil }

View File

@@ -12,7 +12,7 @@ func readHTTPProxy() (settings settings.HTTPProxy, err error) {
"/run/secrets/httpproxy_user",
)
if err != nil {
return settings, fmt.Errorf("cannot read HTTP proxy user secret file: %w", err)
return settings, fmt.Errorf("reading HTTP proxy user secret file: %w", err)
}
settings.Password, err = readSecretFileAsStringPtr(
@@ -20,7 +20,7 @@ func readHTTPProxy() (settings settings.HTTPProxy, err error) {
"/run/secrets/httpproxy_password",
)
if err != nil {
return settings, fmt.Errorf("cannot read OpenVPN password secret file: %w", err)
return settings, fmt.Errorf("reading OpenVPN password secret file: %w", err)
}
return settings, nil

View File

@@ -13,7 +13,7 @@ func readOpenVPN() (
"/run/secrets/openvpn_user",
)
if err != nil {
return settings, fmt.Errorf("cannot read user file: %w", err)
return settings, fmt.Errorf("reading user file: %w", err)
}
settings.Password, err = readSecretFileAsStringPtr(
@@ -21,7 +21,7 @@ func readOpenVPN() (
"/run/secrets/openvpn_password",
)
if err != nil {
return settings, fmt.Errorf("cannot read password file: %w", err)
return settings, fmt.Errorf("reading password file: %w", err)
}
settings.Key, err = readPEMSecretFile(
@@ -29,7 +29,7 @@ func readOpenVPN() (
"/run/secrets/openvpn_clientkey",
)
if err != nil {
return settings, fmt.Errorf("cannot read client key file: %w", err)
return settings, fmt.Errorf("reading client key file: %w", err)
}
settings.EncryptedKey, err = readPEMSecretFile(
@@ -53,7 +53,7 @@ func readOpenVPN() (
"/run/secrets/openvpn_clientcrt",
)
if err != nil {
return settings, fmt.Errorf("cannot read client certificate file: %w", err)
return settings, fmt.Errorf("reading client certificate file: %w", err)
}
return settings, nil

View File

@@ -2,21 +2,17 @@ package secrets
import (
"github.com/qdm12/gluetun/internal/configuration/settings"
"github.com/qdm12/gluetun/internal/configuration/sources"
)
var _ sources.Source = (*Reader)(nil)
type Source struct{}
type Reader struct {
func New() *Source {
return &Source{}
}
func New() *Reader {
return &Reader{}
}
func (s *Source) String() string { return "secret files" }
func (r *Reader) String() string { return "secret files" }
func (r *Reader) Read() (settings settings.Settings, err error) {
func (s *Source) Read() (settings settings.Settings, err error) {
settings.VPN, err = readVPN()
if err != nil {
return settings, err

View File

@@ -12,7 +12,7 @@ func readShadowsocks() (settings settings.Shadowsocks, err error) {
"/run/secrets/shadowsocks_password",
)
if err != nil {
return settings, fmt.Errorf("cannot read Shadowsocks password secret file: %w", err)
return settings, fmt.Errorf("reading Shadowsocks password secret file: %w", err)
}
return settings, nil

View File

@@ -9,7 +9,7 @@ import (
func readVPN() (vpn settings.VPN, err error) {
vpn.OpenVPN, err = readOpenVPN()
if err != nil {
return vpn, fmt.Errorf("cannot read OpenVPN settings: %w", err)
return vpn, fmt.Errorf("reading OpenVPN settings: %w", err)
}
return vpn, nil

View File

@@ -1,8 +1,11 @@
package openvpn
const (
AES128cbc = "aes-128-cbc"
AES256cbc = "aes-256-cbc"
AES128gcm = "aes-128-gcm"
AES256gcm = "aes-256-gcm"
AES128cbc = "aes-128-cbc"
AES192cbc = "aes-192-cbc"
AES256cbc = "aes-256-cbc"
AES128gcm = "aes-128-gcm"
AES192gcm = "aes-192-gcm"
AES256gcm = "aes-256-gcm"
Chacha20Poly1305 = "chacha20-poly1305"
)

View File

@@ -3,6 +3,7 @@ package providers
const (
// Custom is the VPN provider name for custom
// VPN configurations.
Airvpn = "airvpn"
Custom = "custom"
Cyberghost = "cyberghost"
Example = "example"
@@ -32,6 +33,7 @@ const (
// All returns all the providers except the custom provider.
func All() []string {
return []string{
Airvpn,
Cyberghost,
Expressvpn,
Fastestvpn,

View File

@@ -48,7 +48,7 @@ func (cm *cmdMatcher) String() string {
return fmt.Sprintf("path %s, argument regular expressions %v", cm.path, cm.argsRegex)
}
func newCmdMatcher(path string, argsRegex ...string) *cmdMatcher { //nolint:unparam
func newCmdMatcher(path string, argsRegex ...string) *cmdMatcher {
argsRegexp := make([]*regexp.Regexp, len(argsRegex))
for i, argRegex := range argsRegex {
argsRegexp[i] = regexp.MustCompile(argRegex)

View File

@@ -21,7 +21,7 @@ func (c *Config) SetEnabled(ctx context.Context, enabled bool) (err error) {
if !enabled {
c.logger.Info("disabling...")
if err = c.disable(ctx); err != nil {
return fmt.Errorf("cannot disable firewall: %w", err)
return fmt.Errorf("disabling firewall: %w", err)
}
c.enabled = false
c.logger.Info("disabled successfully")
@@ -31,7 +31,7 @@ func (c *Config) SetEnabled(ctx context.Context, enabled bool) (err error) {
c.logger.Info("enabling...")
if err := c.enable(ctx); err != nil {
return fmt.Errorf("cannot enable firewall: %w", err)
return fmt.Errorf("enabling firewall: %w", err)
}
c.enabled = true
c.logger.Info("enabled successfully")
@@ -41,13 +41,13 @@ func (c *Config) SetEnabled(ctx context.Context, enabled bool) (err error) {
func (c *Config) disable(ctx context.Context) (err error) {
if err = c.clearAllRules(ctx); err != nil {
return fmt.Errorf("cannot clear all rules: %w", err)
return fmt.Errorf("clearing all rules: %w", err)
}
if err = c.setIPv4AllPolicies(ctx, "ACCEPT"); err != nil {
return fmt.Errorf("cannot set ipv4 policies: %w", err)
return fmt.Errorf("setting ipv4 policies: %w", err)
}
if err = c.setIPv6AllPolicies(ctx, "ACCEPT"); err != nil {
return fmt.Errorf("cannot set ipv6 policies: %w", err)
return fmt.Errorf("setting ipv6 policies: %w", err)
}
return nil
}
@@ -101,6 +101,9 @@ func (c *Config) enable(ctx context.Context) (err error) {
if err := c.acceptOutputFromIPToSubnet(ctx, network.InterfaceName, network.IP, *network.IPNet, remove); err != nil {
return err
}
if err = c.acceptIpv6MulticastOutput(ctx, network.InterfaceName, remove); err != nil {
return err
}
}
if err = c.allowOutboundSubnets(ctx); err != nil {
@@ -120,7 +123,7 @@ func (c *Config) enable(ctx context.Context) (err error) {
}
if err := c.runUserPostRules(ctx, c.customRulesPath, remove); err != nil {
return fmt.Errorf("cannot run user defined post firewall rules: %w", err)
return fmt.Errorf("running user defined post firewall rules: %w", err)
}
return nil
@@ -135,7 +138,7 @@ func (c *Config) allowVPNIP(ctx context.Context) (err error) {
for _, defaultRoute := range c.defaultRoutes {
err = c.acceptOutputTrafficToVPN(ctx, defaultRoute.NetInterface, c.vpnConnection, remove)
if err != nil {
return fmt.Errorf("cannot accept output traffic through VPN: %w", err)
return fmt.Errorf("accepting output traffic through VPN: %w", err)
}
}
@@ -162,7 +165,7 @@ func (c *Config) allowInputPorts(ctx context.Context) (err error) {
const remove = false
err = c.acceptInputToPort(ctx, netInterface, port, remove)
if err != nil {
return fmt.Errorf("cannot accept input port %d on interface %s: %w",
return fmt.Errorf("accepting input port %d on interface %s: %w",
port, netInterface, err)
}
}

View File

@@ -179,6 +179,18 @@ func (c *Config) acceptOutputFromIPToSubnet(ctx context.Context,
return c.runIP6tablesInstruction(ctx, instruction)
}
// NDP uses multicast address (theres no broadcast in IPv6 like ARP uses in IPv4).
func (c *Config) acceptIpv6MulticastOutput(ctx context.Context,
intf string, remove bool) error {
interfaceFlag := "-o " + intf
if intf == "*" { // all interfaces
interfaceFlag = ""
}
instruction := fmt.Sprintf("%s OUTPUT %s -d ff02::1:ff/104 -j ACCEPT",
appendOrDelete(remove), interfaceFlag)
return c.runIP6tablesInstruction(ctx, instruction)
}
// Used for port forwarding, with intf set to tun.
func (c *Config) acceptInputToPort(ctx context.Context, intf string, port uint16, remove bool) error {
interfaceFlag := "-i " + intf
@@ -245,7 +257,7 @@ func (c *Config) runUserPostRules(ctx context.Context, filepath string, remove b
case ipv4:
err = c.runIptablesInstruction(ctx, rule)
case c.ip6Tables == "":
err = fmt.Errorf("cannot run user ip6tables rule: %w", ErrNeedIP6Tables)
err = fmt.Errorf("running user ip6tables rule: %w", ErrNeedIP6Tables)
default: // ipv6
err = c.runIP6tablesInstruction(ctx, rule)
}

View File

@@ -28,7 +28,7 @@ func (c *Config) SetOutboundSubnets(ctx context.Context, subnets []net.IPNet) (e
c.removeOutboundSubnets(ctx, subnetsToRemove)
if err := c.addOutboundSubnets(ctx, subnetsToAdd); err != nil {
return fmt.Errorf("cannot set allowed outbound subnets: %w", err)
return fmt.Errorf("setting allowed outbound subnets: %w", err)
}
return nil

View File

@@ -36,10 +36,11 @@ func (c *Config) SetAllowedPort(ctx context.Context, port uint16, intf string) (
const remove = false
if err := c.acceptInputToPort(ctx, intf, port, remove); err != nil {
return fmt.Errorf("cannot allow input to port %d through interface %s: %w",
return fmt.Errorf("allowing input to port %d through interface %s: %w",
port, intf, err)
}
netInterfaces[intf] = struct{}{}
c.allowedInputPorts[port] = netInterfaces
return nil
}
@@ -69,7 +70,7 @@ func (c *Config) RemoveAllowedPort(ctx context.Context, port uint16) (err error)
for netInterface := range interfacesSet {
err := c.acceptInputToPort(ctx, netInterface, port, remove)
if err != nil {
return fmt.Errorf("cannot remove allowed port %d on interface %s: %w",
return fmt.Errorf("removing allowed port %d on interface %s: %w",
port, netInterface, err)
}
delete(interfacesSet, netInterface)

View File

@@ -6,6 +6,7 @@ import (
"fmt"
"math/rand"
"os/exec"
"sort"
"strings"
"github.com/qdm12/golibs/command"
@@ -20,7 +21,7 @@ var (
func checkIptablesSupport(ctx context.Context, runner command.Runner,
iptablesPathsToTry ...string) (iptablesPath string, err error) {
var lastUnsupportedMessage string
iptablesPathToUnsupportedMessage := make(map[string]string, len(iptablesPathsToTry))
for _, pathToTest := range iptablesPathsToTry {
ok, unsupportedMessage, err := testIptablesPath(ctx, pathToTest, runner)
if err != nil {
@@ -29,17 +30,35 @@ func checkIptablesSupport(ctx context.Context, runner command.Runner,
iptablesPath = pathToTest
break
}
lastUnsupportedMessage = unsupportedMessage
iptablesPathToUnsupportedMessage[pathToTest] = unsupportedMessage
}
if iptablesPath == "" { // all iptables to try failed
return "", fmt.Errorf("%w: from %s: last error is: %s",
ErrIPTablesNotSupported, strings.Join(iptablesPathsToTry, ", "),
lastUnsupportedMessage)
if iptablesPath != "" {
// some paths may be unsupported but that does not matter
// since we found one working.
return iptablesPath, nil
}
return iptablesPath, nil
allArePermissionDenied := true
allUnsupportedMessages := make(sort.StringSlice, 0, len(iptablesPathToUnsupportedMessage))
for iptablesPath, unsupportedMessage := range iptablesPathToUnsupportedMessage {
if !isPermissionDenied(unsupportedMessage) {
allArePermissionDenied = false
}
unsupportedMessage = iptablesPath + ": " + unsupportedMessage
allUnsupportedMessages = append(allUnsupportedMessages, unsupportedMessage)
}
allUnsupportedMessages.Sort() // predictable order for tests
if allArePermissionDenied {
// If the error is related to a denied permission for all iptables path,
// return an error describing what to do from an end-user perspective.
return "", fmt.Errorf("%w: %s", ErrNetAdminMissing, strings.Join(allUnsupportedMessages, "; "))
}
return "", fmt.Errorf("%w: errors encountered are: %s",
ErrIPTablesNotSupported, strings.Join(allUnsupportedMessages, "; "))
}
func testIptablesPath(ctx context.Context, path string,
@@ -56,14 +75,6 @@ func testIptablesPath(ctx context.Context, path string,
"-A", "OUTPUT", "-o", testInterfaceName, "-j", "DROP")
output, err := runner.Run(cmd)
if err != nil {
if isPermissionDenied(output) {
// If the error is related to a denied permission,
// return an error describing what to do from an end-user
// perspective. This is a critical error and likely
// applies to all iptables.
criticalErr = fmt.Errorf("%w: %s", ErrNetAdminMissing, output)
return false, "", criticalErr
}
unsupportedMessage = fmt.Sprintf("%s (%s)", output, err)
return false, unsupportedMessage, nil
}
@@ -84,10 +95,6 @@ func testIptablesPath(ctx context.Context, path string,
cmd = exec.CommandContext(ctx, path, "-L", "INPUT")
output, err = runner.Run(cmd)
if err != nil {
if isPermissionDenied(output) {
criticalErr = fmt.Errorf("%w: %s", ErrNetAdminMissing, output)
return false, "", criticalErr
}
unsupportedMessage = fmt.Sprintf("%s (%s)", output, err)
return false, unsupportedMessage, nil
}
@@ -109,10 +116,6 @@ func testIptablesPath(ctx context.Context, path string,
cmd = exec.CommandContext(ctx, path, "--policy", "INPUT", inputPolicy)
output, err = runner.Run(cmd)
if err != nil {
if isPermissionDenied(output) {
criticalErr = fmt.Errorf("%w: %s", ErrNetAdminMissing, output)
return false, "", criticalErr
}
unsupportedMessage = fmt.Sprintf("%s (%s)", output, err)
return false, unsupportedMessage, nil
}

View File

@@ -8,10 +8,130 @@ import (
"github.com/golang/mock/gomock"
"github.com/qdm12/golibs/command"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
//go:generate mockgen -destination=runner_mock_test.go -package $GOPACKAGE github.com/qdm12/golibs/command Runner
func newAppendTestRuleMatcher(path string) *cmdMatcher {
return newCmdMatcher(path,
"^-A$", "^OUTPUT$", "^-o$", "^[a-z0-9]{15}$",
"^-j$", "^DROP$")
}
func newDeleteTestRuleMatcher(path string) *cmdMatcher {
return newCmdMatcher(path,
"^-D$", "^OUTPUT$", "^-o$", "^[a-z0-9]{15}$",
"^-j$", "^DROP$")
}
func newListInputRulesMatcher(path string) *cmdMatcher {
return newCmdMatcher(path,
"^-L$", "^INPUT$")
}
func newSetPolicyMatcher(path, inputPolicy string) *cmdMatcher { //nolint:unparam
return newCmdMatcher(path,
"^--policy$", "^INPUT$", "^"+inputPolicy+"$")
}
func Test_checkIptablesSupport(t *testing.T) {
t.Parallel()
ctx := context.Background()
errDummy := errors.New("exit code 4")
const inputPolicy = "ACCEPT"
testCases := map[string]struct {
buildRunner func(ctrl *gomock.Controller) command.Runner
iptablesPathsToTry []string
iptablesPath string
errSentinel error
errMessage string
}{
"critical error when checking": {
buildRunner: func(ctrl *gomock.Controller) command.Runner {
runner := NewMockRunner(ctrl)
runner.EXPECT().Run(newAppendTestRuleMatcher("path1")).
Return("", nil)
runner.EXPECT().Run(newDeleteTestRuleMatcher("path1")).
Return("output", errDummy)
return runner
},
iptablesPathsToTry: []string{"path1", "path2"},
errSentinel: ErrTestRuleCleanup,
errMessage: "for path1: failed cleaning up test rule: " +
"output (exit code 4)",
},
"found valid path": {
buildRunner: func(ctrl *gomock.Controller) command.Runner {
runner := NewMockRunner(ctrl)
runner.EXPECT().Run(newAppendTestRuleMatcher("path1")).
Return("", nil)
runner.EXPECT().Run(newDeleteTestRuleMatcher("path1")).
Return("", nil)
runner.EXPECT().Run(newListInputRulesMatcher("path1")).
Return("Chain INPUT (policy "+inputPolicy+")", nil)
runner.EXPECT().Run(newSetPolicyMatcher("path1", inputPolicy)).
Return("", nil)
return runner
},
iptablesPathsToTry: []string{"path1", "path2"},
iptablesPath: "path1",
},
"all permission denied": {
buildRunner: func(ctrl *gomock.Controller) command.Runner {
runner := NewMockRunner(ctrl)
runner.EXPECT().Run(newAppendTestRuleMatcher("path1")).
Return("Permission denied (you must be root) more context", errDummy)
runner.EXPECT().Run(newAppendTestRuleMatcher("path2")).
Return("context: Permission denied (you must be root)", errDummy)
return runner
},
iptablesPathsToTry: []string{"path1", "path2"},
errSentinel: ErrNetAdminMissing,
errMessage: "NET_ADMIN capability is missing: " +
"path1: Permission denied (you must be root) more context (exit code 4); " +
"path2: context: Permission denied (you must be root) (exit code 4)",
},
"no valid path": {
buildRunner: func(ctrl *gomock.Controller) command.Runner {
runner := NewMockRunner(ctrl)
runner.EXPECT().Run(newAppendTestRuleMatcher("path1")).
Return("output 1", errDummy)
runner.EXPECT().Run(newAppendTestRuleMatcher("path2")).
Return("output 2", errDummy)
return runner
},
iptablesPathsToTry: []string{"path1", "path2"},
errSentinel: ErrIPTablesNotSupported,
errMessage: "no iptables supported found: " +
"errors encountered are: " +
"path1: output 1 (exit code 4); " +
"path2: output 2 (exit code 4)",
},
}
for name, testCase := range testCases {
testCase := testCase
t.Run(name, func(t *testing.T) {
t.Parallel()
ctrl := gomock.NewController(t)
runner := testCase.buildRunner(ctrl)
iptablesPath, err :=
checkIptablesSupport(ctx, runner, testCase.iptablesPathsToTry...)
require.ErrorIs(t, err, testCase.errSentinel)
if testCase.errSentinel != nil {
assert.EqualError(t, err, testCase.errMessage)
}
assert.Equal(t, testCase.iptablesPath, iptablesPath)
})
}
}
func Test_testIptablesPath(t *testing.T) {
t.Parallel()
@@ -20,17 +140,6 @@ func Test_testIptablesPath(t *testing.T) {
errDummy := errors.New("exit code 4")
const inputPolicy = "ACCEPT"
appendTestRuleMatcher := newCmdMatcher(path,
"^-A$", "^OUTPUT$", "^-o$", "^[a-z0-9]{15}$",
"^-j$", "^DROP$")
deleteTestRuleMatcher := newCmdMatcher(path,
"^-D$", "^OUTPUT$", "^-o$", "^[a-z0-9]{15}$",
"^-j$", "^DROP$")
listInputRulesMatcher := newCmdMatcher(path,
"^-L$", "^INPUT$")
setPolicyMatcher := newCmdMatcher(path,
"^--policy$", "^INPUT$", "^"+inputPolicy+"$")
testCases := map[string]struct {
buildRunner func(ctrl *gomock.Controller) command.Runner
ok bool
@@ -41,18 +150,16 @@ func Test_testIptablesPath(t *testing.T) {
"append test rule permission denied": {
buildRunner: func(ctrl *gomock.Controller) command.Runner {
runner := NewMockRunner(ctrl)
runner.EXPECT().Run(appendTestRuleMatcher).
runner.EXPECT().Run(newAppendTestRuleMatcher(path)).
Return("Permission denied (you must be root)", errDummy)
return runner
},
criticalErrWrapped: ErrNetAdminMissing,
criticalErrMessage: "NET_ADMIN capability is missing: " +
"Permission denied (you must be root)",
unsupportedMessage: "Permission denied (you must be root) (exit code 4)",
},
"append test rule unsupported": {
buildRunner: func(ctrl *gomock.Controller) command.Runner {
runner := NewMockRunner(ctrl)
runner.EXPECT().Run(appendTestRuleMatcher).
runner.EXPECT().Run(newAppendTestRuleMatcher(path)).
Return("some output", errDummy)
return runner
},
@@ -61,8 +168,8 @@ func Test_testIptablesPath(t *testing.T) {
"remove test rule error": {
buildRunner: func(ctrl *gomock.Controller) command.Runner {
runner := NewMockRunner(ctrl)
runner.EXPECT().Run(appendTestRuleMatcher).Return("", nil)
runner.EXPECT().Run(deleteTestRuleMatcher).
runner.EXPECT().Run(newAppendTestRuleMatcher(path)).Return("", nil)
runner.EXPECT().Run(newDeleteTestRuleMatcher(path)).
Return("some output", errDummy)
return runner
},
@@ -72,22 +179,20 @@ func Test_testIptablesPath(t *testing.T) {
"list input rules permission denied": {
buildRunner: func(ctrl *gomock.Controller) command.Runner {
runner := NewMockRunner(ctrl)
runner.EXPECT().Run(appendTestRuleMatcher).Return("", nil)
runner.EXPECT().Run(deleteTestRuleMatcher).Return("", nil)
runner.EXPECT().Run(listInputRulesMatcher).
runner.EXPECT().Run(newAppendTestRuleMatcher(path)).Return("", nil)
runner.EXPECT().Run(newDeleteTestRuleMatcher(path)).Return("", nil)
runner.EXPECT().Run(newListInputRulesMatcher(path)).
Return("Permission denied (you must be root)", errDummy)
return runner
},
criticalErrWrapped: ErrNetAdminMissing,
criticalErrMessage: "NET_ADMIN capability is missing: " +
"Permission denied (you must be root)",
unsupportedMessage: "Permission denied (you must be root) (exit code 4)",
},
"list input rules unsupported": {
buildRunner: func(ctrl *gomock.Controller) command.Runner {
runner := NewMockRunner(ctrl)
runner.EXPECT().Run(appendTestRuleMatcher).Return("", nil)
runner.EXPECT().Run(deleteTestRuleMatcher).Return("", nil)
runner.EXPECT().Run(listInputRulesMatcher).
runner.EXPECT().Run(newAppendTestRuleMatcher(path)).Return("", nil)
runner.EXPECT().Run(newDeleteTestRuleMatcher(path)).Return("", nil)
runner.EXPECT().Run(newListInputRulesMatcher(path)).
Return("some output", errDummy)
return runner
},
@@ -96,9 +201,9 @@ func Test_testIptablesPath(t *testing.T) {
"list input rules no policy": {
buildRunner: func(ctrl *gomock.Controller) command.Runner {
runner := NewMockRunner(ctrl)
runner.EXPECT().Run(appendTestRuleMatcher).Return("", nil)
runner.EXPECT().Run(deleteTestRuleMatcher).Return("", nil)
runner.EXPECT().Run(listInputRulesMatcher).
runner.EXPECT().Run(newAppendTestRuleMatcher(path)).Return("", nil)
runner.EXPECT().Run(newDeleteTestRuleMatcher(path)).Return("", nil)
runner.EXPECT().Run(newListInputRulesMatcher(path)).
Return("some\noutput", nil)
return runner
},
@@ -108,26 +213,24 @@ func Test_testIptablesPath(t *testing.T) {
"set policy permission denied": {
buildRunner: func(ctrl *gomock.Controller) command.Runner {
runner := NewMockRunner(ctrl)
runner.EXPECT().Run(appendTestRuleMatcher).Return("", nil)
runner.EXPECT().Run(deleteTestRuleMatcher).Return("", nil)
runner.EXPECT().Run(listInputRulesMatcher).
runner.EXPECT().Run(newAppendTestRuleMatcher(path)).Return("", nil)
runner.EXPECT().Run(newDeleteTestRuleMatcher(path)).Return("", nil)
runner.EXPECT().Run(newListInputRulesMatcher(path)).
Return("\nChain INPUT (policy "+inputPolicy+")\nxx\n", nil)
runner.EXPECT().Run(setPolicyMatcher).
runner.EXPECT().Run(newSetPolicyMatcher(path, inputPolicy)).
Return("Permission denied (you must be root)", errDummy)
return runner
},
criticalErrWrapped: ErrNetAdminMissing,
criticalErrMessage: "NET_ADMIN capability is missing: " +
"Permission denied (you must be root)",
unsupportedMessage: "Permission denied (you must be root) (exit code 4)",
},
"set policy unsupported": {
buildRunner: func(ctrl *gomock.Controller) command.Runner {
runner := NewMockRunner(ctrl)
runner.EXPECT().Run(appendTestRuleMatcher).Return("", nil)
runner.EXPECT().Run(deleteTestRuleMatcher).Return("", nil)
runner.EXPECT().Run(listInputRulesMatcher).
runner.EXPECT().Run(newAppendTestRuleMatcher(path)).Return("", nil)
runner.EXPECT().Run(newDeleteTestRuleMatcher(path)).Return("", nil)
runner.EXPECT().Run(newListInputRulesMatcher(path)).
Return("\nChain INPUT (policy "+inputPolicy+")\nxx\n", nil)
runner.EXPECT().Run(setPolicyMatcher).
runner.EXPECT().Run(newSetPolicyMatcher(path, inputPolicy)).
Return("some output", errDummy)
return runner
},
@@ -136,11 +239,12 @@ func Test_testIptablesPath(t *testing.T) {
"success": {
buildRunner: func(ctrl *gomock.Controller) command.Runner {
runner := NewMockRunner(ctrl)
runner.EXPECT().Run(appendTestRuleMatcher).Return("", nil)
runner.EXPECT().Run(deleteTestRuleMatcher).Return("", nil)
runner.EXPECT().Run(listInputRulesMatcher).
runner.EXPECT().Run(newAppendTestRuleMatcher(path)).Return("", nil)
runner.EXPECT().Run(newDeleteTestRuleMatcher(path)).Return("", nil)
runner.EXPECT().Run(newListInputRulesMatcher(path)).
Return("\nChain INPUT (policy "+inputPolicy+")\nxx\n", nil)
runner.EXPECT().Run(setPolicyMatcher).Return("some output", nil)
runner.EXPECT().Run(newSetPolicyMatcher(path, inputPolicy)).
Return("some output", nil)
return runner
},
ok: true,

View File

@@ -45,13 +45,13 @@ func (c *Config) SetVPNConnection(ctx context.Context,
for _, defaultRoute := range c.defaultRoutes {
if err := c.acceptOutputTrafficToVPN(ctx, defaultRoute.NetInterface, connection, remove); err != nil {
return fmt.Errorf("cannot allow output traffic through VPN connection: %w", err)
return fmt.Errorf("allowing output traffic through VPN connection: %w", err)
}
}
c.vpnConnection = connection
if err = c.acceptOutputThroughInterface(ctx, vpnIntf, remove); err != nil {
return fmt.Errorf("cannot accept output traffic through interface %s: %w", vpnIntf, err)
return fmt.Errorf("accepting output traffic through interface %s: %w", vpnIntf, err)
}
c.vpnIntf = vpnIntf

View File

@@ -74,12 +74,12 @@ func (s *Server) healthCheck(ctx context.Context) (err error) {
const dialNetwork = "tcp4"
connection, err := s.dialer.DialContext(ctx, dialNetwork, address)
if err != nil {
return fmt.Errorf("cannot dial: %w", err)
return fmt.Errorf("dialing: %w", err)
}
err = connection.Close()
if err != nil {
return fmt.Errorf("cannot close connection: %w", err)
return fmt.Errorf("closing connection: %w", err)
}
return nil
@@ -91,7 +91,7 @@ func makeAddressToDial(address string) (addressToDial string, err error) {
addrErr := new(net.AddrError)
ok := errors.As(err, &addrErr)
if !ok || addrErr.Err != "missing port in address" {
return "", fmt.Errorf("cannot split host and port from address: %w", err)
return "", fmt.Errorf("splitting host and port from address: %w", err)
}
host = address
const defaultPort = "443"

View File

@@ -85,7 +85,7 @@ func Test_makeAddressToDial(t *testing.T) {
},
"bad address": {
address: "test.com::",
err: fmt.Errorf("cannot split host and port from address: address test.com::: too many colons in address"), //nolint:lll
err: fmt.Errorf("splitting host and port from address: address test.com::: too many colons in address"), //nolint:lll
},
}

View File

@@ -15,7 +15,8 @@ type vpnHealth struct {
func (s *Server) onUnhealthyVPN(ctx context.Context) {
s.logger.Info("program has been unhealthy for " +
s.vpn.healthyWait.String() + ": restarting VPN")
s.vpn.healthyWait.String() + ": restarting VPN " +
"(see https://github.com/qdm12/gluetun/wiki/Healthcheck)")
_, _ = s.vpn.loop.ApplyStatus(ctx, constants.Stopped)
_, _ = s.vpn.loop.ApplyStatus(ctx, constants.Running)
s.vpn.healthyWait += *s.config.VPN.Addition

View File

@@ -2,6 +2,7 @@ package httpproxy
import (
"encoding/base64"
"fmt"
"net/http"
"strings"
)
@@ -32,7 +33,8 @@ func (h *handler) isAuthorized(responseWriter http.ResponseWriter, request *http
return false
}
if h.username != usernamePassword[0] || h.password != usernamePassword[1] {
h.logger.Info("Username or password mismatch from " + request.RemoteAddr)
h.logger.Info(fmt.Sprintf("Username (%q) or password (%q) mismatch from %s",
usernamePassword[0], usernamePassword[1], request.RemoteAddr))
h.logger.Debug("username provided \"" + usernamePassword[0] +
"\" and password provided \"" + usernamePassword[1] + "\"")
responseWriter.WriteHeader(http.StatusUnauthorized)

View File

@@ -39,7 +39,7 @@ func (s *Server) Run(ctx context.Context, errorCh chan<- error) {
}
go func() {
<-ctx.Done()
const shutdownGraceDuration = 2 * time.Second
const shutdownGraceDuration = 100 * time.Millisecond
shutdownCtx, cancel := context.WithTimeout(context.Background(), shutdownGraceDuration)
defer cancel()
if err := server.Shutdown(shutdownCtx); err != nil {

View File

@@ -5,6 +5,7 @@ import (
"strings"
"github.com/qdm12/gluetun/internal/constants/providers"
"github.com/qdm12/gluetun/internal/constants/vpn"
)
func boolToMarkdown(b bool) string {
@@ -26,6 +27,7 @@ const (
hostnameHeader = "Hostname"
ispHeader = "ISP"
multiHopHeader = "MultiHop"
nameHeader = "Name"
numberHeader = "Number"
ownedHeader = "Owned"
portForwardHeader = "Port forwarding"
@@ -57,6 +59,8 @@ func (s *Server) ToMarkdown(headers ...string) (markdown string) {
fields[i] = s.ISP
case multiHopHeader:
fields[i] = boolToMarkdown(s.MultiHop)
case nameHeader:
fields[i] = s.ServerName
case numberHeader:
fields[i] = fmt.Sprint(s.Number)
case ownedHeader:
@@ -72,7 +76,7 @@ func (s *Server) ToMarkdown(headers ...string) (markdown string) {
case tcpHeader:
fields[i] = boolToMarkdown(s.TCP)
case udpHeader:
fields[i] = boolToMarkdown(s.UDP)
fields[i] = boolToMarkdown(s.UDP || s.VPN == vpn.Wireguard)
case vpnHeader:
fields[i] = s.VPN
}
@@ -98,6 +102,9 @@ func (s *Servers) ToMarkdown(vpnProvider string) (markdown string) {
func getMarkdownHeaders(vpnProvider string) (headers []string) {
switch vpnProvider {
case providers.Airvpn:
return []string{regionHeader, countryHeader, cityHeader, vpnHeader,
udpHeader, tcpHeader, hostnameHeader, nameHeader}
case providers.Cyberghost:
return []string{countryHeader, hostnameHeader, tcpHeader, udpHeader}
case providers.Expressvpn:

View File

@@ -39,7 +39,8 @@ var (
ErrVPNFieldEmpty = errors.New("vpn field is empty")
ErrHostnameFieldEmpty = errors.New("hostname field is empty")
ErrIPsFieldEmpty = errors.New("ips field is empty")
ErrNoNetworkProtocol = errors.New("both TCP and UDP fields are false")
ErrNoNetworkProtocol = errors.New("both TCP and UDP fields are false for OpenVPN")
ErrNetworkProtocolSet = errors.New("no network protocol should be set")
ErrWireguardPublicKeyEmpty = errors.New("wireguard public key field is empty")
)
@@ -51,7 +52,9 @@ func (s *Server) HasMinimumInformation() (err error) {
return ErrHostnameFieldEmpty
case len(s.IPs) == 0:
return ErrIPsFieldEmpty
case !s.TCP && !s.UDP:
case s.VPN == vpn.Wireguard && (s.TCP || s.UDP):
return ErrNetworkProtocolSet
case s.VPN == vpn.OpenVPN && !s.TCP && !s.UDP:
return ErrNoNetworkProtocol
case s.VPN == vpn.Wireguard && s.WgPubKey == "":
return ErrWireguardPublicKeyEmpty

View File

@@ -27,13 +27,13 @@ func (a *AllServers) MarshalJSON() (data []byte, err error) {
_, err = buffer.WriteString("{")
if err != nil {
return nil, fmt.Errorf("cannot write opening bracket: %w", err)
return nil, fmt.Errorf("writing opening bracket: %w", err)
}
versionString := fmt.Sprintf(`"version":%d`, a.Version)
_, err = buffer.WriteString(versionString)
if err != nil {
return nil, fmt.Errorf("cannot write schema version string: %w", err)
return nil, fmt.Errorf("writing schema version string: %w", err)
}
sortedProviders := make(sort.StringSlice, 0, len(a.ProviderToServers))
@@ -46,26 +46,26 @@ func (a *AllServers) MarshalJSON() (data []byte, err error) {
providerKey := fmt.Sprintf(`,"%s":`, provider)
_, err = buffer.WriteString(providerKey)
if err != nil {
return nil, fmt.Errorf("cannot write provider key %s: %w",
return nil, fmt.Errorf("writing provider key %s: %w",
providerKey, err)
}
servers := a.ProviderToServers[provider]
serversJSON, err := json.Marshal(servers)
if err != nil {
return nil, fmt.Errorf("failed encoding servers for provider %s: %w",
return nil, fmt.Errorf("encoding servers for provider %s: %w",
provider, err)
}
_, err = buffer.Write(serversJSON)
if err != nil {
return nil, fmt.Errorf("cannot write JSON servers data for provider %s: %w",
return nil, fmt.Errorf("writing JSON servers data for provider %s: %w",
provider, err)
}
}
_, err = buffer.WriteString("}")
if err != nil {
return nil, fmt.Errorf("cannot write closing bracket: %w", err)
return nil, fmt.Errorf("writing closing bracket: %w", err)
}
return buffer.Bytes(), nil
@@ -127,14 +127,14 @@ func (a *AllServers) UnmarshalJSON(data []byte) (err error) {
jsonValue, err := json.Marshal(value)
if err != nil {
return fmt.Errorf("cannot marshal %s servers: %w",
return fmt.Errorf("encoding %s servers: %w",
key, err)
}
var servers Servers
err = json.Unmarshal(jsonValue, &servers)
if err != nil {
return fmt.Errorf("cannot unmarshal %s servers: %w",
return fmt.Errorf("decoding %s servers: %w",
key, err)
}

View File

@@ -16,7 +16,7 @@ const (
func (n *NetLink) IsWireguardSupported() (ok bool, err error) {
families, err := netlink.GenlFamilyList()
if err != nil {
return false, fmt.Errorf("cannot list gen 1 families: %w", err)
return false, fmt.Errorf("listing gen 1 families: %w", err)
}
for _, family := range families {
if family.Name == "wireguard" {

View File

@@ -0,0 +1,8 @@
package netlink
import "github.com/qdm12/log"
type DebugLogger interface {
Debugf(format string, args ...any)
Patch(options ...log.Option)
}

40
internal/netlink/ipv6.go Normal file
View File

@@ -0,0 +1,40 @@
package netlink
import (
"fmt"
"github.com/vishvananda/netlink"
)
func (n *NetLink) IsIPv6Supported() (supported bool, err error) {
links, err := n.LinkList()
if err != nil {
return false, fmt.Errorf("listing links: %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
}
totalRoutes++
}
}
n.debugLogger.Debugf("IPv6 is not supported after searching %d links and %d routes",
len(links), totalRoutes)
return false, nil
}

View File

@@ -1,7 +1,17 @@
package netlink
type NetLink struct{}
import "github.com/qdm12/log"
func New() *NetLink {
return &NetLink{}
type NetLink struct {
debugLogger DebugLogger
}
func New(debugLogger DebugLogger) *NetLink {
return &NetLink{
debugLogger: debugLogger,
}
}
func (n *NetLink) PatchLoggerLevel(level log.Level) {
n.debugLogger.Patch(log.SetLevel(level))
}

View File

@@ -17,12 +17,12 @@ func (e *Extractor) Data(filepath string) (lines []string,
connection models.Connection, err error) {
lines, err = readCustomConfigLines(filepath)
if err != nil {
return nil, connection, fmt.Errorf("cannot read configuration file: %w", err)
return nil, connection, fmt.Errorf("reading configuration file: %w", err)
}
connection, err = extractDataFromLines(lines)
if err != nil {
return nil, connection, fmt.Errorf("cannot extract connection from file: %w", err)
return nil, connection, fmt.Errorf("extracting connection from file: %w", err)
}
return lines, connection, nil

View File

@@ -54,14 +54,14 @@ func extractDataFromLine(line string) (
case strings.HasPrefix(line, "proto "):
protocol, err = extractProto(line)
if err != nil {
return nil, 0, "", fmt.Errorf("failed extracting protocol from proto line: %w", err)
return nil, 0, "", fmt.Errorf("extracting protocol from proto line: %w", err)
}
return nil, 0, protocol, nil
case strings.HasPrefix(line, "remote "):
ip, port, protocol, err = extractRemote(line)
if err != nil {
return nil, 0, "", fmt.Errorf("failed extracting from remote line: %w", err)
return nil, 0, "", fmt.Errorf("extracting from remote line: %w", err)
}
return ip, port, protocol, nil
}
@@ -81,7 +81,7 @@ func extractProto(line string) (protocol string, err error) {
}
switch fields[1] {
case "tcp", "udp":
case "tcp", "tcp4", "tcp6", "udp", "udp4", "udp6":
default:
return "", fmt.Errorf("%w: %s", errProtocolNotSupported, fields[1])
}

View File

@@ -29,7 +29,7 @@ func Test_extractDataFromLines(t *testing.T) {
},
"extraction error": {
lines: []string{"bla bla", "proto bad", "remote 1.2.3.4 1194 tcp"},
err: errors.New("on line 2: failed extracting protocol from proto line: network protocol not supported: bad"),
err: errors.New("on line 2: extracting protocol from proto line: network protocol not supported: bad"),
},
"only use first values found": {
lines: []string{"proto udp", "proto tcp", "remote 1.2.3.4 443 tcp", "remote 5.2.3.4 1194 udp"},

View File

@@ -1,33 +1,23 @@
package extract
import (
"encoding/base64"
"encoding/pem"
"errors"
"regexp"
"strings"
"fmt"
)
var (
errPEMDecode = errors.New("cannot decode PEM encoded block")
)
var (
regexPEMBegin = regexp.MustCompile(`-----BEGIN [A-Za-z ]+-----`)
regexPEMEnd = regexp.MustCompile(`-----END [A-Za-z ]+-----`)
)
func PEM(b []byte) (encodedData string, err error) {
pemBlock, _ := pem.Decode(b)
if pemBlock == nil {
return "", errPEMDecode
return "", fmt.Errorf("%w", errPEMDecode)
}
encodedBytes := pem.EncodeToMemory(pemBlock)
encodedData = string(encodedBytes)
encodedData = strings.ReplaceAll(encodedData, "\n", "")
beginPrefix := regexPEMBegin.FindString(encodedData)
encodedData = strings.TrimPrefix(encodedData, beginPrefix)
endPrefix := regexPEMEnd.FindString(encodedData)
encodedData = strings.TrimSuffix(encodedData, endPrefix)
der := pemBlock.Bytes
encodedData = base64.StdEncoding.EncodeToString(der)
return encodedData, nil
}

View File

@@ -1,6 +1,7 @@
package extract
import (
"bytes"
"testing"
"github.com/stretchr/testify/assert"
@@ -24,6 +25,14 @@ func Test_PEM(t *testing.T) {
errWrapped: errPEMDecode,
errMessage: "cannot decode PEM encoded block",
},
"valid data with extras": {
b: bytes.Join([][]byte{
{1, 2, 3},
[]byte(validCertPEM),
{4, 5, 6},
}, []byte("\n")),
encodedData: validCertData,
},
"valid data": {
b: []byte(validCertPEM),
encodedData: validCertData,

View File

@@ -65,6 +65,16 @@ That error usually happens because either:
filtered = s
level = levelInfo
}
switch {
case filtered == "RTNETLINK answers: File exists":
filtered = "OpenVPN tried to add an IP route which already exists (" + filtered + ")"
level = levelWarn
case strings.HasPrefix(filtered, "Linux route add command failed: "):
filtered = "Previous error details: " + filtered
level = levelWarn
}
filtered = constants.ColorOpenvpn().Sprintf(filtered)
return filtered, level
}

View File

@@ -43,6 +43,36 @@ func Test_processLogLine(t *testing.T) {
"AUTH: Received control message: AUTH_FAILED",
"AUTH: Received control message: AUTH_FAILED\n\nYour credentials might be wrong 🤨\n\n",
levelError},
"TLS key negotiation error": {
s: "TLS Error: TLS key negotiation failed to occur within " +
"60 seconds (check your network connectivity)",
filtered: "TLS Error: TLS key negotiation failed to occur within " +
"60 seconds (check your network connectivity)" + `
🚒🚒🚒🚒🚒🚨🚨🚨🚨🚨🚨🚒🚒🚒🚒🚒
That error usually happens because either:
1. The VPN server IP address you are trying to connect to is no longer valid 🔌
Update your server information using https://github.com/qdm12/gluetun/wiki/Updating-Servers
2. The VPN server crashed 💥, try changing your VPN servers filtering options such as SERVER_REGIONS
3. Your Internet connection is not working 🤯, ensure it works
4. Something else ➡️ https://github.com/qdm12/gluetun/issues/new/choose
`,
level: levelWarn,
},
"RTNETLINK answers: File exists": {
s: "ERROR: RTNETLINK answers: File exists",
filtered: "OpenVPN tried to add an IP route which already exists " +
"(RTNETLINK answers: File exists)",
level: levelWarn,
},
"Linux route add command failed": {
s: "ERROR: Linux route add command failed: some error",
filtered: "Previous error details: Linux route add command failed: some error",
level: levelWarn,
},
}
for name, tc := range tests {
tc := tc

View File

@@ -0,0 +1,53 @@
package pkcs8
import (
"crypto/x509/pkix"
"encoding/asn1"
"errors"
"fmt"
)
var (
// Algorithm identifiers are listed at
// https://www.ibm.com/docs/en/zos/2.3.0?topic=programming-object-identifiers
oidDESCBC = asn1.ObjectIdentifier{1, 3, 14, 3, 2, 7} //nolint:gochecknoglobals
)
var (
ErrEncryptionAlgorithmNotPBES2 = errors.New("encryption algorithm is not PBES2")
)
type encryptedPrivateKey struct {
EncryptionAlgorithm pkix.AlgorithmIdentifier
EncryptedData []byte
}
type encryptedAlgorithmParams struct {
KeyDerivationFunc pkix.AlgorithmIdentifier
EncryptionScheme pkix.AlgorithmIdentifier
}
func getEncryptionAlgorithmOid(der []byte) (
encryptionSchemeAlgorithm asn1.ObjectIdentifier, err error) {
var encryptedPrivateKeyData encryptedPrivateKey
_, err = asn1.Unmarshal(der, &encryptedPrivateKeyData)
if err != nil {
return nil, fmt.Errorf("decoding asn1 encrypted private key data: %w", err)
}
oidPBES2 := asn1.ObjectIdentifier{1, 2, 840, 113549, 1, 5, 13}
oidAlgorithm := encryptedPrivateKeyData.EncryptionAlgorithm.Algorithm
if !oidAlgorithm.Equal(oidPBES2) {
return nil, fmt.Errorf("%w: %s instead of PBES2 %s",
ErrEncryptionAlgorithmNotPBES2, oidAlgorithm, oidPBES2)
}
var encryptionAlgorithmParams encryptedAlgorithmParams
paramBytes := encryptedPrivateKeyData.EncryptionAlgorithm.Parameters.FullBytes
_, err = asn1.Unmarshal(paramBytes, &encryptionAlgorithmParams)
if err != nil {
return nil, fmt.Errorf("decoding asn1 encryption algorithm parameters: %w", err)
}
return encryptionAlgorithmParams.EncryptionScheme.Algorithm, nil
}

View File

@@ -0,0 +1,106 @@
package pkcs8
import (
"crypto/x509/pkix"
"encoding/asn1"
"encoding/pem"
"errors"
"fmt"
"os"
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
pkcs8lib "github.com/youmark/pkcs8"
)
func Test_getEncryptionAlgorithmOid(t *testing.T) {
t.Parallel()
testCases := map[string]struct {
makeDER func() (der []byte, err error)
encryptionSchemeAlgorithm asn1.ObjectIdentifier
errMessage string
}{
"empty data": {
makeDER: func() (der []byte, err error) { return nil, nil },
errMessage: "decoding asn1 encrypted private key data: " +
"asn1: syntax error: sequence truncated",
},
"algorithm not pbes2": {
makeDER: func() (der []byte, err error) {
data := encryptedPrivateKey{
EncryptionAlgorithm: pkix.AlgorithmIdentifier{
Algorithm: asn1.ObjectIdentifier{1, 2, 3, 4},
},
}
return asn1.Marshal(data)
},
errMessage: "encryption algorithm is not PBES2: " +
"1.2.3.4 instead of PBES2 1.2.840.113549.1.5.13",
},
"empty params full bytes": {
makeDER: func() (der []byte, err error) {
data := encryptedPrivateKey{
EncryptionAlgorithm: pkix.AlgorithmIdentifier{
Algorithm: asn1.ObjectIdentifier{1, 2, 840, 113549, 1, 5, 13},
Parameters: asn1.RawValue{
FullBytes: []byte{},
},
},
}
return asn1.Marshal(data)
},
errMessage: "decoding asn1 encryption algorithm parameters: " +
"asn1: structure error: tags don't match " +
"(16 vs {class:0 tag:0 length:0 isCompound:false}) {optional:false explicit:false application:false private:false defaultValue:<nil> tag:<nil> stringType:0 timeType:0 set:false omitEmpty:false} encryptedAlgorithmParams @2", //nolint:lll
},
"DES-CBC DER": {
makeDER: func() (der []byte, err error) {
DESCBCEncryptedPEM, err := os.ReadFile("testdata/rsa_pkcs8_descbc_encrypted.pem")
if err != nil {
return nil, fmt.Errorf("reading file: %w", err)
}
pemBlock, _ := pem.Decode(DESCBCEncryptedPEM)
if pemBlock == nil {
return nil, errors.New("failed to decode PEM")
}
return pemBlock.Bytes, nil
},
encryptionSchemeAlgorithm: oidDESCBC,
},
"AES-128-CBC DER": {
makeDER: func() (der []byte, err error) {
AES128CBCEncryptedPEM, err := os.ReadFile("testdata/rsa_pkcs8_aes128cbc_encrypted.pem")
if err != nil {
return nil, fmt.Errorf("reading file: %w", err)
}
pemBlock, _ := pem.Decode(AES128CBCEncryptedPEM)
if pemBlock == nil {
return nil, errors.New("failed to decode PEM")
}
return pemBlock.Bytes, nil
},
encryptionSchemeAlgorithm: pkcs8lib.AES128CBC.OID(),
},
}
for name, testCase := range testCases {
testCase := testCase
t.Run(name, func(t *testing.T) {
t.Parallel()
der, err := testCase.makeDER()
require.NoError(t, err)
encryptionSchemeAlgorithm, err := getEncryptionAlgorithmOid(der)
if testCase.errMessage != "" {
assert.EqualError(t, err, testCase.errMessage)
} else {
assert.NoError(t, err)
}
assert.Equal(t, testCase.encryptionSchemeAlgorithm, encryptionSchemeAlgorithm)
})
}
}

View File

@@ -0,0 +1,59 @@
package pkcs8
import (
"bytes"
"crypto/cipher"
"crypto/des" //nolint:gosec
"encoding/asn1"
"fmt"
pkcs8lib "github.com/youmark/pkcs8"
)
func init() { //nolint:gochecknoinits
pkcs8lib.RegisterCipher(oidDESCBC, newCipherDESCBCBlock)
}
func newCipherDESCBCBlock() pkcs8lib.Cipher { //nolint:ireturn
return cipherDESCBC{}
}
type cipherDESCBC struct{}
func (c cipherDESCBC) IVSize() int {
return des.BlockSize
}
func (c cipherDESCBC) KeySize() int {
return 8 //nolint:gomnd
}
func (c cipherDESCBC) OID() asn1.ObjectIdentifier {
return oidDESCBC
}
func (c cipherDESCBC) Encrypt(key, iv, plaintext []byte) ([]byte, error) {
block, err := des.NewCipher(key) //nolint:gosec
if err != nil {
return nil, fmt.Errorf("creating DES cipher: %w", err)
}
blockEncrypter := cipher.NewCBCEncrypter(block, iv)
paddingLen := block.BlockSize() - (len(plaintext) % block.BlockSize())
ciphertext := make([]byte, len(plaintext)+paddingLen)
copy(ciphertext, plaintext)
copy(ciphertext[len(plaintext):],
bytes.Repeat([]byte{byte(paddingLen)}, paddingLen))
blockEncrypter.CryptBlocks(ciphertext, ciphertext)
return ciphertext, nil
}
func (c cipherDESCBC) Decrypt(key, iv, ciphertext []byte) ([]byte, error) {
block, err := des.NewCipher(key) //nolint:gosec
if err != nil {
return nil, fmt.Errorf("creating DES cipher: %w", err)
}
blockDecrypter := cipher.NewCBCDecrypter(block, iv)
plaintext := make([]byte, len(ciphertext))
blockDecrypter.CryptBlocks(plaintext, ciphertext)
return plaintext, nil
}

View File

@@ -0,0 +1,12 @@
The key files in this directory are generated using OpenSSL.
Re-generating them is fine and should work with existing tests.
For DES encrypted RSA key files, openssl version 1.x.x is required, and the following commands in order generate the files:
openssl genpkey -algorithm RSA -pkeyopt rsa_keygen_bits:512 -des -pass pass:password -out rsa_pkcs8_aes128cbc_encrypted.pem
openssl pkcs8 -topk8 -in rsa_pkcs8_aes128cbc_encrypted.pem -passin pass:password -nocrypt -out rsa_pkcs8_aes128cbc_decrypted.pem
For AES encrypted RSA key files, the following commands in order generate the files:
openssl genpkey -algorithm RSA -pkeyopt rsa_keygen_bits:512 -aes-128-cbc -pass pass:password -out rsa_pkcs8_descbc_encrypted.pem
openssl pkcs8 -topk8 -in rsa_pkcs8_descbc_encrypted.pem -passin pass:password -nocrypt -out rsa_pkcs8_descbc_decrypted.pem

View File

@@ -0,0 +1,10 @@
-----BEGIN PRIVATE KEY-----
MIIBVgIBADANBgkqhkiG9w0BAQEFAASCAUAwggE8AgEAAkEAsont6TMS9RVqjXoi
wF/oKZCwbWM4HmCJvp5Z2dOfKabt+7FOTJiD7APLJKva6791HDTuyBu7+HFQCzW3
ghLuiwIDAQABAkB09FuwHq/1cmEJao+nO2xHBiw8i/lwFMdG4k5znegujL4g16i7
+afWrMd54jYNPGiKuSNObB2BZR1j8tz/jvbxAiEA3d7bVwtWdaZVIV5t9uqrq5fG
j3eXfNemTu1HQDmVqNMCIQDOALECY98KURR4NJueTKNuvawkuWFhizfKKTfS5B6Q
aQIhANsF/RFYp+lMYg2m4nc2AnJKSkGmlW0wlYSkyAmmzw7xAiEAqSz+MSVNnU5a
ziD+D/GGYkKYJYysgYvwZDCXbLT0uMkCIQDZghteTq2MMwIWWUJti3nc6nCICaJu
d5O9Sm7BcOSuoA==
-----END PRIVATE KEY-----

View File

@@ -0,0 +1,12 @@
-----BEGIN ENCRYPTED PRIVATE KEY-----
MIIBvTBXBgkqhkiG9w0BBQ0wSjApBgkqhkiG9w0BBQwwHAQIX7rAZ9pfZ4ACAggA
MAwGCCqGSIb3DQIJBQAwHQYJYIZIAWUDBAECBBBVQ5G606jCKrKADBAiKwcPBIIB
YBbudvVfqdLKm9LBFOAcUQk+sdFrq6e2r/xnuqM7VY6Ru4pMOmVMhHMMCFkqHLjx
f7hN+xjk3XpYyoptnozPBOhypZrjd6IeEJSkBtU5BZR8fP0Bhny5NYHGcyPR6MZA
5iX/0fnyMlrncG67UNwoZQjfg7jEO3mAjuCW/F74xtPQ90ZHtw8mYC26fa09uQR4
ptL9XqZuw4+U//3CuOheKqI17wulKAb4NwJckYbKyOik+J4yAi0ScgO73pD1FFvl
qBxcpyvEqFQqkOlcbR9YwVBAXeW8cbpZJd+MilSs7Ru/phHrP9wz5chYDrocbeG/
H8FhCCvZnJ3zC3P3FPRNPtoaduJ0MbYpaMv4hyP3tEbzbslPA1v14ES3U+w0gmdD
zpsy0oplQK9d9wL2TKBwyALcUx5BhtcqKsUXwBOWXMToc4lIXUVl0UVYwULibmEd
yK6ajugNxG95X+BJjGvWu/U=
-----END ENCRYPTED PRIVATE KEY-----

View File

@@ -0,0 +1,10 @@
-----BEGIN PRIVATE KEY-----
MIIBVgIBADANBgkqhkiG9w0BAQEFAASCAUAwggE8AgEAAkEAuU3FTtbPm8OjZ/d8
vVd+seQcrCGgwxigKpOszFfOOXKxfy2CgpjE1Ga2h0UneJ6pq0KZyY+ggYAX8PaS
U6R3HwIDAQABAkEAibQPkjzz3u8Nua8i1Zn1nsDDxe7fhtv/+mPvn5MIv4sFRS71
0o9+SPNIQn7aJcGIqyBzHYdQg3/wGla+LA+msQIhAOt+hy1dnaWTSXIrIuPt+sSP
Fjk80ijfxntXHNU6qExjAiEAyXBurrTdQs6D61ZzdlOFzgUs/FHa4dmWmxXuFsdv
8RUCIQCIZQJaLiyOp94UOBO/PCjQC6ftguKeNe25plzWy2CKzQIgXBpBMTZXGG2u
WZMcldSYkFtDd1bB2pQPTXeYdefYYgUCIQDVH3ysySFXIlHJulgcxvriXTfY4goY
TQ0PL0Ow7sIz6A==
-----END PRIVATE KEY-----

View File

@@ -0,0 +1,12 @@
-----BEGIN ENCRYPTED PRIVATE KEY-----
MIIBsTBLBgkqhkiG9w0BBQ0wPjApBgkqhkiG9w0BBQwwHAQIZK8yPqcvVqoCAggA
MAwGCCqGSIb3DQIJBQAwEQYFKw4DAgcECI7C8b+gk6UJBIIBYGQQ4UcglyUqSFC7
JiA+Gh01K1odfdLJKLh30+iescrFenII4Vv4rX5609URhn2iHCXhlnZ0+9geRR9k
dQSKXaDVVGQw3bQUKgS+lZDAeLV4PS7c+KW0xLpXWJxBPs6NXQMxoJZ23UA391EH
p8gKzZqUKk/rEOP68wr3IpHqaD3xggzN+4eA4ZKj4OktmWfUjgC7RQIZSaMxfq+D
q+4D5onp+B4C2WRfjnN/N2g7UhzKWGvhjKyogvl82PuY9Vp1qPwQGdg5wdJ/2UVX
QNvbkT21Wrv1ffFuIDS1/lCPnd8RAl2Q7chfLyut4BjP0tlmYNxRwQU2mT3KZOrB
wwhWgXZtBwj4LjyasVkKe4hyVfRXN5NgONvqxof3VdZUHzOegOapNbEmfhNwVogj
1gwRWL7etAbYKjiMPFzZJAiU97+UkqveguldeoHmvWRDTLqxgZw5M4wkPPldb+u8
d1vCDDQ=
-----END ENCRYPTED PRIVATE KEY-----

View File

@@ -0,0 +1,52 @@
package pkcs8
import (
"encoding/base64"
"errors"
"fmt"
pkcs8lib "github.com/youmark/pkcs8"
)
var (
ErrUnsupportedKeyType = errors.New("unsupported key type")
)
// UpgradeEncryptedKey eventually upgrades an encrypted key to a newer encryption
// if its encryption is too weak for Openvpn/Openssl.
// If the key is encrypted using DES-CBC, it is decrypted and re-encrypted using AES-256-CBC.
// Otherwise, the key is returned unmodified.
// Note this function only supports:
// - PKCS8 encrypted keys
// - RSA and ECDSA keys
// - DES-CBC, 3DES, AES-128-CBC, AES-192-CBC, AES-256-CBC, AES-128-GCM, AES-192-GCM
// and AES-256-GCM encryption algorithms.
func UpgradeEncryptedKey(encryptedPKCS8DERKey, passphrase string) (securelyEncryptedPKCS8DERKey string, err error) {
der, err := base64.StdEncoding.DecodeString(encryptedPKCS8DERKey)
if err != nil {
return "", fmt.Errorf("decoding base64 encoded DER: %w", err)
}
oidEncryptionAlgorithm, err := getEncryptionAlgorithmOid(der)
if err != nil {
return "", fmt.Errorf("finding encryption algorithm oid: %w", err)
}
if !oidEncryptionAlgorithm.Equal(oidDESCBC) {
return encryptedPKCS8DERKey, nil
}
// Convert DES-CBC encrypted key to an AES256CBC encrypted key
privateKey, err := pkcs8lib.ParsePKCS8PrivateKey(der, []byte(passphrase))
if err != nil {
return "", fmt.Errorf("parsing pkcs8 encrypted private key: %w", err)
}
der, err = pkcs8lib.MarshalPrivateKey(privateKey, []byte(passphrase), pkcs8lib.DefaultOpts)
if err != nil {
return "", fmt.Errorf("encrypting and encoding private key: %w", err)
}
securelyEncryptedPKCS8DERKey = base64.StdEncoding.EncodeToString(der)
return securelyEncryptedPKCS8DERKey, nil
}

View File

@@ -0,0 +1,75 @@
package pkcs8
import (
"crypto/x509"
"encoding/base64"
"encoding/pem"
"os"
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"github.com/youmark/pkcs8"
)
func parsePEMFile(t *testing.T, pemFilepath string) (base64DER string) {
t.Helper()
bytes, err := os.ReadFile(pemFilepath)
require.NoError(t, err)
pemBlock, _ := pem.Decode(bytes)
require.NotNil(t, pemBlock)
derBytes := pemBlock.Bytes
base64DER = base64.StdEncoding.EncodeToString(derBytes)
return base64DER
}
func Test_UpgradeEncryptedKey(t *testing.T) {
t.Parallel()
testCases := map[string]struct {
encryptedPKCS8base64DERKey string
passphrase string
decryptedPKCS8Base64DERKey string
errMessage string
}{
"AES-128-CBC key": {
encryptedPKCS8base64DERKey: parsePEMFile(t, "testdata/rsa_pkcs8_aes128cbc_encrypted.pem"),
passphrase: "password",
decryptedPKCS8Base64DERKey: parsePEMFile(t, "testdata/rsa_pkcs8_aes128cbc_decrypted.pem"),
},
"DES-CBC key": {
encryptedPKCS8base64DERKey: parsePEMFile(t, "testdata/rsa_pkcs8_descbc_encrypted.pem"),
passphrase: "password",
decryptedPKCS8Base64DERKey: parsePEMFile(t, "testdata/rsa_pkcs8_descbc_decrypted.pem"),
},
}
for name, testCase := range testCases {
testCase := testCase
t.Run(name, func(t *testing.T) {
t.Parallel()
securelyEncryptedPKCS8DERKey, err := UpgradeEncryptedKey(testCase.encryptedPKCS8base64DERKey, testCase.passphrase)
if testCase.errMessage != "" {
assert.EqualError(t, err, testCase.errMessage)
return
}
assert.NoError(t, err)
// Decrypt possible re-encrypted key to verify it matches the expected
// corresponding decrypted key.
der, err := base64.StdEncoding.DecodeString(securelyEncryptedPKCS8DERKey)
require.NoError(t, err)
privateKey, err := pkcs8.ParsePKCS8PrivateKey(der, []byte(testCase.passphrase))
require.NoError(t, err)
der, err = x509.MarshalPKCS8PrivateKey(privateKey)
require.NoError(t, err)
base64DER := base64.StdEncoding.EncodeToString(der)
assert.Equal(t, testCase.decryptedPKCS8Base64DERKey, base64DER)
})
}
}

View File

@@ -0,0 +1,14 @@
package airvpn
import (
"github.com/qdm12/gluetun/internal/configuration/settings"
"github.com/qdm12/gluetun/internal/models"
"github.com/qdm12/gluetun/internal/provider/utils"
)
func (p *Provider) GetConnection(selection settings.ServerSelection, ipv6Supported bool) (
connection models.Connection, err error) {
defaults := utils.NewConnectionDefaults(443, 1194, 1637) //nolint:gomnd
return utils.GetConnection(p.Name(),
p.storage, selection, defaults, ipv6Supported, p.randSource)
}

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