Compare commits
20 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d0f5d0c007 | ||
|
|
06949554dd | ||
|
|
8e8cbe0d37 | ||
|
|
3d72f6092b | ||
|
|
0ac047f9de | ||
|
|
129002a058 | ||
|
|
e95095ad5c | ||
|
|
efecb0ea0e | ||
|
|
b504227625 | ||
|
|
2b298de59d | ||
|
|
3d4c4e505a | ||
|
|
948ebdc4e1 | ||
|
|
7cf46a0ac3 | ||
|
|
faa8340eea | ||
|
|
8267d0e3bd | ||
|
|
f04ac56454 | ||
|
|
d9997f5234 | ||
|
|
ac5c13e9c9 | ||
|
|
7570a79ce6 | ||
|
|
c47858fdda |
@@ -37,12 +37,12 @@
|
||||
"go.useLanguageServer": true,
|
||||
"[go]": {
|
||||
"editor.codeActionsOnSave": {
|
||||
"source.organizeImports": "explicit"
|
||||
"source.organizeImports": true
|
||||
}
|
||||
},
|
||||
"[go.mod]": {
|
||||
"editor.codeActionsOnSave": {
|
||||
"source.organizeImports": "explicit"
|
||||
"source.organizeImports": true
|
||||
}
|
||||
},
|
||||
"gopls": {
|
||||
|
||||
5
.github/ISSUE_TEMPLATE/config.yml
vendored
5
.github/ISSUE_TEMPLATE/config.yml
vendored
@@ -1,10 +1,9 @@
|
||||
blank_issues_enabled: false
|
||||
contact_links:
|
||||
- name: Report a Wiki issue
|
||||
url: https://github.com/qdm12/gluetun-wiki/issues/new/choose
|
||||
url: https://github.com/qdm12/gluetun-wiki/issues/new
|
||||
about: Please create an issue on the gluetun-wiki repository.
|
||||
- name: Configuration help?
|
||||
url: https://github.com/qdm12/gluetun/discussions/new/choose
|
||||
url: https://github.com/qdm12/gluetun/discussions/new
|
||||
about: Please create a Github discussion.
|
||||
- name: Unraid template issue
|
||||
url: https://github.com/qdm12/gluetun/discussions/550
|
||||
|
||||
189
.github/labels.yml
vendored
189
.github/labels.yml
vendored
@@ -1,140 +1,133 @@
|
||||
- name: "Status: 🗯️ Waiting for feedback"
|
||||
color: "f7d692"
|
||||
- name: "Status: 🔴 Blocked"
|
||||
color: "f7d692"
|
||||
# Temporary status
|
||||
- name: "🗯️ Waiting for feedback"
|
||||
color: "aadefa"
|
||||
description: ""
|
||||
- name: "🔴 Blocked"
|
||||
color: "ff3f14"
|
||||
description: "Blocked by another issue or pull request"
|
||||
- name: "Status: 📌 Before next release"
|
||||
color: "f7d692"
|
||||
description: "Has to be done before the next release"
|
||||
- name: "Status: 🔒 After next release"
|
||||
color: "f7d692"
|
||||
- name: "🔒 After next release"
|
||||
color: "e8f274"
|
||||
description: "Will be done after the next release"
|
||||
|
||||
- name: "Closed: ⚰️ Inactive"
|
||||
color: "959a9c"
|
||||
description: "No answer was received for weeks"
|
||||
- name: "Closed: 👥 Duplicate"
|
||||
color: "959a9c"
|
||||
description: "Issue duplicates an existing issue"
|
||||
- name: "Closed: 🗑️ Bad issue"
|
||||
color: "959a9c"
|
||||
- name: "Closed: ☠️ cannot be done"
|
||||
color: "959a9c"
|
||||
# Priority
|
||||
- name: "🚨 Urgent"
|
||||
color: "d5232f"
|
||||
description: ""
|
||||
- name: "💤 Low priority"
|
||||
color: "4285f4"
|
||||
description: ""
|
||||
|
||||
- name: "Priority: 🚨 Urgent"
|
||||
color: "03adfc"
|
||||
- name: "Priority: 💤 Low priority"
|
||||
color: "03adfc"
|
||||
|
||||
- name: "Complexity: ☣️ Hard to do"
|
||||
color: "ff9efc"
|
||||
- name: "Complexity: 🟩 Easy to do"
|
||||
color: "ff9efc"
|
||||
|
||||
- name: "Popularity: ❤️🔥 extreme"
|
||||
color: "ffc7ea"
|
||||
- name: "Popularity: ❤️ high"
|
||||
color: "ffc7ea"
|
||||
# Complexity
|
||||
- name: "☣️ Hard to do"
|
||||
color: "7d0008"
|
||||
description: ""
|
||||
- name: "🟩 Easy to do"
|
||||
color: "34cf43"
|
||||
description: ""
|
||||
|
||||
# VPN providers
|
||||
- name: "☁️ AirVPN"
|
||||
- name: ":cloud: AirVPN"
|
||||
color: "cfe8d4"
|
||||
- name: "☁️ Custom"
|
||||
description: ""
|
||||
- name: ":cloud: Cyberghost"
|
||||
color: "cfe8d4"
|
||||
- name: "☁️ Cyberghost"
|
||||
description: ""
|
||||
- name: ":cloud: HideMyAss"
|
||||
color: "cfe8d4"
|
||||
- name: "☁️ HideMyAss"
|
||||
description: ""
|
||||
- name: ":cloud: IPVanish"
|
||||
color: "cfe8d4"
|
||||
- name: "☁️ IPVanish"
|
||||
description: ""
|
||||
- name: ":cloud: IVPN"
|
||||
color: "cfe8d4"
|
||||
- name: "☁️ IVPN"
|
||||
description: ""
|
||||
- name: ":cloud: ExpressVPN"
|
||||
color: "cfe8d4"
|
||||
- name: "☁️ ExpressVPN"
|
||||
description: ""
|
||||
- name: ":cloud: FastestVPN"
|
||||
color: "cfe8d4"
|
||||
- name: "☁️ FastestVPN"
|
||||
description: ""
|
||||
- name: ":cloud: Mullvad"
|
||||
color: "cfe8d4"
|
||||
- name: "☁️ Mullvad"
|
||||
description: ""
|
||||
- name: ":cloud: NordVPN"
|
||||
color: "cfe8d4"
|
||||
- name: "☁️ NordVPN"
|
||||
description: ""
|
||||
- name: ":cloud: Perfect Privacy"
|
||||
color: "cfe8d4"
|
||||
- name: "☁️ Perfect Privacy"
|
||||
description: ""
|
||||
- name: ":cloud: PIA"
|
||||
color: "cfe8d4"
|
||||
- name: "☁️ PIA"
|
||||
description: ""
|
||||
- name: ":cloud: Privado"
|
||||
color: "cfe8d4"
|
||||
- name: "☁️ Privado"
|
||||
description: ""
|
||||
- name: ":cloud: PrivateVPN"
|
||||
color: "cfe8d4"
|
||||
- name: "☁️ PrivateVPN"
|
||||
description: ""
|
||||
- name: ":cloud: ProtonVPN"
|
||||
color: "cfe8d4"
|
||||
- name: "☁️ ProtonVPN"
|
||||
- name: ":cloud: PureVPN"
|
||||
color: "cfe8d4"
|
||||
- name: "☁️ PureVPN"
|
||||
description: ""
|
||||
- name: ":cloud: SlickVPN"
|
||||
color: "cfe8d4"
|
||||
- name: "☁️ SlickVPN"
|
||||
description: ""
|
||||
- name: ":cloud: Surfshark"
|
||||
color: "cfe8d4"
|
||||
- name: "☁️ Surfshark"
|
||||
description: ""
|
||||
- name: ":cloud: Torguard"
|
||||
color: "cfe8d4"
|
||||
- name: "☁️ Torguard"
|
||||
description: ""
|
||||
- name: ":cloud: VPNSecure.me"
|
||||
color: "cfe8d4"
|
||||
- name: "☁️ VPNSecure.me"
|
||||
- name: ":cloud: VPNUnlimited"
|
||||
color: "cfe8d4"
|
||||
- name: "☁️ VPNUnlimited"
|
||||
description: ""
|
||||
- name: ":cloud: Vyprvpn"
|
||||
color: "cfe8d4"
|
||||
- name: "☁️ Vyprvpn"
|
||||
description: ""
|
||||
- name: ":cloud: WeVPN"
|
||||
color: "cfe8d4"
|
||||
- name: "☁️ WeVPN"
|
||||
color: "cfe8d4"
|
||||
- name: "☁️ Windscribe"
|
||||
description: ""
|
||||
- name: ":cloud: Windscribe"
|
||||
color: "cfe8d4"
|
||||
description: ""
|
||||
|
||||
- name: "Category: Config problem 📝"
|
||||
# Problem category
|
||||
- name: "Config problem"
|
||||
color: "ffc7ea"
|
||||
- name: "Category: Healthcheck 🩺"
|
||||
description: ""
|
||||
- name: "Openvpn"
|
||||
color: "ffc7ea"
|
||||
- name: "Category: Documentation ✒️"
|
||||
description: "A problem with the readme or a code comment."
|
||||
description: ""
|
||||
- name: "Wireguard"
|
||||
color: "ffc7ea"
|
||||
- name: "Category: Maintenance ⛓️"
|
||||
description: "Anything related to code or other maintenance"
|
||||
description: ""
|
||||
- name: "Unbound (DNS over TLS)"
|
||||
color: "ffc7ea"
|
||||
- name: "Category: Logs 📚"
|
||||
description: "Something to change in logs"
|
||||
description: ""
|
||||
- name: "Firewall"
|
||||
color: "ffc7ea"
|
||||
- name: "Category: Good idea 🎯"
|
||||
description: "This is a good idea, judged by the maintainers"
|
||||
description: ""
|
||||
- name: "Routing"
|
||||
color: "ffc7ea"
|
||||
- name: "Category: Motivated! 🙌"
|
||||
description: "Your pumpness makes me pumped! The issue or PR shows great motivation!"
|
||||
description: ""
|
||||
- name: "IPv6"
|
||||
color: "ffc7ea"
|
||||
- name: "Category: Foolproof settings 👼"
|
||||
description: ""
|
||||
- name: "Port forwarding"
|
||||
color: "ffc7ea"
|
||||
- name: "Category: Label missing ❗"
|
||||
description: ""
|
||||
- name: "HTTP proxy"
|
||||
color: "ffc7ea"
|
||||
- name: "Category: updater ♻️"
|
||||
description: ""
|
||||
- name: "Shadowsocks"
|
||||
color: "ffc7ea"
|
||||
description: "Concerns the code to update servers data"
|
||||
- name: "Category: New provider 🆕"
|
||||
description: ""
|
||||
- name: "Healthcheck server"
|
||||
color: "ffc7ea"
|
||||
- name: "Category: OpenVPN 🔐"
|
||||
color: "ffc7ea"
|
||||
- name: "Category: Wireguard 🔐"
|
||||
color: "ffc7ea"
|
||||
- name: "Category: DNS 📠"
|
||||
color: "ffc7ea"
|
||||
- name: "Category: Firewall ⛓️"
|
||||
color: "ffc7ea"
|
||||
- name: "Category: Routing 🛤️"
|
||||
color: "ffc7ea"
|
||||
- name: "Category: IPv6 🛰️"
|
||||
color: "ffc7ea"
|
||||
- name: "Category: VPN port forwarding 📥"
|
||||
color: "ffc7ea"
|
||||
- name: "Category: HTTP proxy 🔁"
|
||||
color: "ffc7ea"
|
||||
- name: "Category: Shadowsocks 🔁"
|
||||
color: "ffc7ea"
|
||||
- name: "Category: control server ⚙️"
|
||||
color: "ffc7ea"
|
||||
- name: "Category: kernel 🧠"
|
||||
color: "ffc7ea"
|
||||
- name: "Category: public IP service 💬"
|
||||
description: ""
|
||||
- name: "Control server"
|
||||
color: "ffc7ea"
|
||||
description: ""
|
||||
|
||||
3
.github/workflows/ci.yml
vendored
3
.github/workflows/ci.yml
vendored
@@ -74,9 +74,6 @@ jobs:
|
||||
security-events: write
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/setup-go@v5
|
||||
with:
|
||||
go-version: "^1.22"
|
||||
- uses: github/codeql-action/init@v3
|
||||
with:
|
||||
languages: go
|
||||
|
||||
13
.github/workflows/configs/mlc-config.json
vendored
13
.github/workflows/configs/mlc-config.json
vendored
@@ -1,13 +0,0 @@
|
||||
{
|
||||
"ignorePatterns": [
|
||||
{
|
||||
"pattern": "^https://console.substack.com/p/console-72$"
|
||||
}
|
||||
],
|
||||
"timeout": "20s",
|
||||
"retryOn429": false,
|
||||
"fallbackRetryDelay": "30s",
|
||||
"aliveStatusCodes": [
|
||||
200
|
||||
]
|
||||
}
|
||||
5
.github/workflows/markdown.yml
vendored
5
.github/workflows/markdown.yml
vendored
@@ -20,7 +20,7 @@ jobs:
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- uses: DavidAnson/markdownlint-cli2-action@v16
|
||||
- uses: DavidAnson/markdownlint-cli2-action@v14
|
||||
with:
|
||||
globs: "**.md"
|
||||
config: .markdownlint.json
|
||||
@@ -35,9 +35,8 @@ jobs:
|
||||
- uses: gaurav-nelson/github-action-markdown-link-check@v1
|
||||
with:
|
||||
use-quiet-mode: yes
|
||||
config-file: .github/workflows/configs/mlc-config.json
|
||||
|
||||
- uses: peter-evans/dockerhub-description@v4
|
||||
- uses: peter-evans/dockerhub-description@v3
|
||||
if: github.repository == 'qdm12/gluetun' && github.event_name == 'push'
|
||||
with:
|
||||
username: qmcgaw
|
||||
|
||||
@@ -120,3 +120,9 @@ linters:
|
||||
- wastedassign
|
||||
- whitespace
|
||||
- zerologlint
|
||||
|
||||
run:
|
||||
skip-dirs:
|
||||
- .devcontainer
|
||||
- .github
|
||||
- doc
|
||||
|
||||
2
.vscode/settings.json
vendored
2
.vscode/settings.json
vendored
@@ -21,7 +21,7 @@
|
||||
"go.useLanguageServer": true,
|
||||
"[go]": {
|
||||
"editor.codeActionsOnSave": {
|
||||
"source.organizeImports": "explicit"
|
||||
"source.organizeImports": true
|
||||
}
|
||||
},
|
||||
"go.lintTool": "golangci-lint",
|
||||
|
||||
28
Dockerfile
28
Dockerfile
@@ -1,6 +1,6 @@
|
||||
ARG ALPINE_VERSION=3.20
|
||||
ARG GO_ALPINE_VERSION=3.20
|
||||
ARG GO_VERSION=1.22
|
||||
ARG ALPINE_VERSION=3.18
|
||||
ARG GO_ALPINE_VERSION=3.18
|
||||
ARG GO_VERSION=1.21
|
||||
ARG XCPUTRANSLATE_VERSION=v0.6.0
|
||||
ARG GOLANGCI_LINT_VERSION=v1.56.2
|
||||
ARG MOCKGEN_VERSION=v1.6.0
|
||||
@@ -76,16 +76,16 @@ LABEL \
|
||||
ENV VPN_SERVICE_PROVIDER=pia \
|
||||
VPN_TYPE=openvpn \
|
||||
# Common VPN options
|
||||
VPN_ENDPOINT_IP= \
|
||||
VPN_ENDPOINT_PORT= \
|
||||
VPN_INTERFACE=tun0 \
|
||||
# OpenVPN
|
||||
OPENVPN_ENDPOINT_IP= \
|
||||
OPENVPN_ENDPOINT_PORT= \
|
||||
OPENVPN_PROTOCOL=udp \
|
||||
OPENVPN_USER= \
|
||||
OPENVPN_PASSWORD= \
|
||||
OPENVPN_USER_SECRETFILE=/run/secrets/openvpn_user \
|
||||
OPENVPN_PASSWORD_SECRETFILE=/run/secrets/openvpn_password \
|
||||
OPENVPN_VERSION=2.6 \
|
||||
OPENVPN_VERSION=2.5 \
|
||||
OPENVPN_VERBOSITY=1 \
|
||||
OPENVPN_FLAGS= \
|
||||
OPENVPN_CIPHERS= \
|
||||
@@ -93,8 +93,6 @@ ENV VPN_SERVICE_PROVIDER=pia \
|
||||
OPENVPN_PROCESS_USER=root \
|
||||
OPENVPN_CUSTOM_CONFIG= \
|
||||
# Wireguard
|
||||
WIREGUARD_ENDPOINT_IP= \
|
||||
WIREGUARD_ENDPOINT_PORT= \
|
||||
WIREGUARD_CONF_SECRETFILE=/run/secrets/wg0.conf \
|
||||
WIREGUARD_PRIVATE_KEY= \
|
||||
WIREGUARD_PRIVATE_KEY_SECRETFILE=/run/secrets/wireguard_private_key \
|
||||
@@ -102,7 +100,6 @@ ENV VPN_SERVICE_PROVIDER=pia \
|
||||
WIREGUARD_PRESHARED_KEY_SECRETFILE=/run/secrets/wireguard_preshared_key \
|
||||
WIREGUARD_PUBLIC_KEY= \
|
||||
WIREGUARD_ALLOWED_IPS= \
|
||||
WIREGUARD_PERSISTENT_KEEPALIVE_INTERVAL=0 \
|
||||
WIREGUARD_ADDRESSES= \
|
||||
WIREGUARD_ADDRESSES_SECRETFILE=/run/secrets/wireguard_addresses \
|
||||
WIREGUARD_MTU=1400 \
|
||||
@@ -122,8 +119,6 @@ ENV VPN_SERVICE_PROVIDER=pia \
|
||||
VPN_PORT_FORWARDING_LISTENING_PORT=0 \
|
||||
VPN_PORT_FORWARDING_PROVIDER= \
|
||||
VPN_PORT_FORWARDING_STATUS_FILE="/tmp/gluetun/forwarded_port" \
|
||||
VPN_PORT_FORWARDING_USERNAME= \
|
||||
VPN_PORT_FORWARDING_PASSWORD= \
|
||||
# # Cyberghost only:
|
||||
OPENVPN_CERT= \
|
||||
OPENVPN_KEY= \
|
||||
@@ -140,8 +135,6 @@ ENV VPN_SERVICE_PROVIDER=pia \
|
||||
SERVER_NAMES= \
|
||||
# # ProtonVPN only:
|
||||
FREE_ONLY= \
|
||||
SECURE_CORE_ONLY= \
|
||||
TOR_ONLY= \
|
||||
# # Surfshark only:
|
||||
MULTIHOP_ONLY= \
|
||||
# # VPN Secure only:
|
||||
@@ -149,7 +142,7 @@ ENV VPN_SERVICE_PROVIDER=pia \
|
||||
# # PIA only:
|
||||
PORT_FORWARD_ONLY= \
|
||||
# Firewall
|
||||
FIREWALL_ENABLED_DISABLING_IT_SHOOTS_YOU_IN_YOUR_FOOT=on \
|
||||
FIREWALL=on \
|
||||
FIREWALL_VPN_INPUT_PORTS= \
|
||||
FIREWALL_INPUT_PORTS= \
|
||||
FIREWALL_OUTBOUND_SUBNETS= \
|
||||
@@ -197,7 +190,6 @@ ENV VPN_SERVICE_PROVIDER=pia \
|
||||
# Control server
|
||||
HTTP_CONTROL_SERVER_LOG=on \
|
||||
HTTP_CONTROL_SERVER_ADDRESS=":8000" \
|
||||
HTTP_CONTROL_SERVER_AUTH_CONFIG_FILEPATH=/gluetun/auth/config.toml \
|
||||
# Server data updater
|
||||
UPDATER_PERIOD=0 \
|
||||
UPDATER_MIN_RATIO=0.8 \
|
||||
@@ -219,14 +211,16 @@ ENV VPN_SERVICE_PROVIDER=pia \
|
||||
PGID=
|
||||
ENTRYPOINT ["/gluetun-entrypoint"]
|
||||
EXPOSE 8000/tcp 8888/tcp 8388/tcp 8388/udp
|
||||
HEALTHCHECK --interval=5s --timeout=5s --start-period=10s --retries=3 CMD /gluetun-entrypoint healthcheck
|
||||
HEALTHCHECK --interval=5s --timeout=5s --start-period=10s --retries=1 CMD /gluetun-entrypoint healthcheck
|
||||
ARG TARGETPLATFORM
|
||||
RUN apk add --no-cache --update -l wget && \
|
||||
apk add --no-cache --update -X "https://dl-cdn.alpinelinux.org/alpine/v3.17/main" openvpn\~2.5 && \
|
||||
mv /usr/sbin/openvpn /usr/sbin/openvpn2.5 && \
|
||||
apk del openvpn && \
|
||||
apk add --no-cache --update openvpn ca-certificates iptables iptables-legacy unbound tzdata && \
|
||||
apk add --no-cache --update openvpn ca-certificates iptables ip6tables unbound tzdata && \
|
||||
mv /usr/sbin/openvpn /usr/sbin/openvpn2.6 && \
|
||||
# Fix vulnerability issue
|
||||
apk add --no-cache --update busybox && \
|
||||
rm -rf /var/cache/apk/* /etc/unbound/* /usr/sbin/unbound-* /etc/openvpn/*.sh /usr/lib/openvpn/plugins/openvpn-plugin-down-root.so && \
|
||||
deluser openvpn && \
|
||||
deluser unbound && \
|
||||
|
||||
12
README.md
12
README.md
@@ -56,24 +56,24 @@ Lightweight swiss-knife-like VPN client to multiple VPN service providers
|
||||
|
||||
## Features
|
||||
|
||||
- Based on Alpine 3.20 for a small Docker image of 35.6MB
|
||||
- Based on Alpine 3.18 for a small Docker image of 35.6MB
|
||||
- Supports: **AirVPN**, **Cyberghost**, **ExpressVPN**, **FastestVPN**, **HideMyAss**, **IPVanish**, **IVPN**, **Mullvad**, **NordVPN**, **Perfect Privacy**, **Privado**, **Private Internet Access**, **PrivateVPN**, **ProtonVPN**, **PureVPN**, **SlickVPN**, **Surfshark**, **TorGuard**, **VPNSecure.me**, **VPNUnlimited**, **Vyprvpn**, **WeVPN**, **Windscribe** servers
|
||||
- Supports OpenVPN for all providers listed
|
||||
- Supports Wireguard both kernelspace and userspace
|
||||
- For **AirVPN**, **FastestVPN**, **Ivpn**, **Mullvad**, **NordVPN**, **Perfect privacy**, **ProtonVPN**, **Surfshark** and **Windscribe**
|
||||
- For **Cyberghost**, **Private Internet Access**, **PrivateVPN**, **PureVPN**, **Torguard**, **VPN Unlimited**, **VyprVPN** and **WeVPN** using [the custom provider](https://github.com/qdm12/gluetun-wiki/blob/main/setup/providers/custom.md)
|
||||
- For **AirVPN**, **Ivpn**, **Mullvad**, **NordVPN**, **Surfshark** and **Windscribe**
|
||||
- For **ProtonVPN**, **PureVPN**, **Torguard**, **VPN Unlimited** and **WeVPN** using [the custom provider](https://github.com/qdm12/gluetun-wiki/blob/main/setup/providers/custom.md)
|
||||
- For custom Wireguard configurations using [the custom provider](https://github.com/qdm12/gluetun-wiki/blob/main/setup/providers/custom.md)
|
||||
- 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 server (protocol based on SOCKS5 with an encryption layer, tunnels TCP+UDP)
|
||||
- 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/blob/main/setup/connect-a-container-to-gluetun.md)
|
||||
- [Connect LAN devices to it](https://github.com/qdm12/gluetun-wiki/blob/main/setup/connect-a-lan-device-to-gluetun.md)
|
||||
- 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 [Perfect Privacy](https://github.com/qdm12/gluetun-wiki/blob/main/setup/providers/perfect-privacy.md#vpn-server-port-forwarding), [Private Internet Access](https://github.com/qdm12/gluetun-wiki/blob/main/setup/providers/private-internet-access.md#vpn-server-port-forwarding) and [ProtonVPN](https://github.com/qdm12/gluetun-wiki/blob/main/setup/providers/protonvpn.md#vpn-server-port-forwarding)
|
||||
- [Custom VPN server side port forwarding for Private Internet Access](https://github.com/qdm12/gluetun-wiki/blob/main/setup/providers/private-internet-access.md#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
|
||||
@@ -84,7 +84,7 @@ Lightweight swiss-knife-like VPN client to multiple VPN service providers
|
||||
|
||||
Go to the [Wiki](https://github.com/qdm12/gluetun-wiki)!
|
||||
|
||||
[🐛 Found a bug in the Wiki?!](https://github.com/qdm12/gluetun-wiki/issues/new/choose)
|
||||
[🐛 Found a bug in the Wiki?!](https://github.com/qdm12/gluetun-wiki/issues/new)
|
||||
|
||||
Here's a docker-compose.yml for the laziest:
|
||||
|
||||
|
||||
@@ -17,7 +17,9 @@ import (
|
||||
"github.com/qdm12/gluetun/internal/alpine"
|
||||
"github.com/qdm12/gluetun/internal/cli"
|
||||
"github.com/qdm12/gluetun/internal/configuration/settings"
|
||||
"github.com/qdm12/gluetun/internal/configuration/sources/env"
|
||||
"github.com/qdm12/gluetun/internal/configuration/sources/files"
|
||||
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"
|
||||
@@ -43,8 +45,6 @@ import (
|
||||
"github.com/qdm12/gluetun/internal/updater/unzip"
|
||||
"github.com/qdm12/gluetun/internal/vpn"
|
||||
"github.com/qdm12/golibs/command"
|
||||
"github.com/qdm12/gosettings/reader"
|
||||
"github.com/qdm12/gosettings/reader/sources/env"
|
||||
"github.com/qdm12/goshutdown"
|
||||
"github.com/qdm12/goshutdown/goroutine"
|
||||
"github.com/qdm12/goshutdown/group"
|
||||
@@ -82,21 +82,14 @@ func main() {
|
||||
cli := cli.New()
|
||||
cmder := command.NewCmder()
|
||||
|
||||
reader := reader.New(reader.Settings{
|
||||
Sources: []reader.Source{
|
||||
secrets.New(logger),
|
||||
files.New(logger),
|
||||
env.New(env.Settings{}),
|
||||
},
|
||||
HandleDeprecatedKey: func(source, deprecatedKey, currentKey string) {
|
||||
logger.Warn("You are using the old " + source + " " + deprecatedKey +
|
||||
", please consider changing it to " + currentKey)
|
||||
},
|
||||
})
|
||||
secretsReader := secrets.New()
|
||||
filesReader := files.New()
|
||||
envReader := env.New(logger)
|
||||
muxReader := mux.New(secretsReader, filesReader, envReader)
|
||||
|
||||
errorCh := make(chan error)
|
||||
go func() {
|
||||
errorCh <- _main(ctx, buildInfo, args, logger, reader, tun, netLinker, cmder, cli)
|
||||
errorCh <- _main(ctx, buildInfo, args, logger, muxReader, tun, netLinker, cmder, cli)
|
||||
}()
|
||||
|
||||
var err error
|
||||
@@ -146,29 +139,27 @@ var (
|
||||
|
||||
//nolint:gocognit,gocyclo,maintidx
|
||||
func _main(ctx context.Context, buildInfo models.BuildInformation,
|
||||
args []string, logger log.LoggerInterface, reader *reader.Reader,
|
||||
args []string, logger log.LoggerInterface, source Source,
|
||||
tun Tun, netLinker netLinker, cmder command.RunStarter,
|
||||
cli clier) error {
|
||||
if len(args) > 1 { // cli operation
|
||||
switch args[1] {
|
||||
case "healthcheck":
|
||||
return cli.HealthCheck(ctx, reader, logger)
|
||||
return cli.HealthCheck(ctx, source, logger)
|
||||
case "clientkey":
|
||||
return cli.ClientKey(args[2:])
|
||||
case "openvpnconfig":
|
||||
return cli.OpenvpnConfig(logger, reader, netLinker)
|
||||
return cli.OpenvpnConfig(logger, source, netLinker)
|
||||
case "update":
|
||||
return cli.Update(ctx, args[2:], logger)
|
||||
case "format-servers":
|
||||
return cli.FormatServers(args[2:])
|
||||
case "genkey":
|
||||
return cli.GenKey(args[2:])
|
||||
default:
|
||||
return fmt.Errorf("%w: %s", errCommandUnknown, args[1])
|
||||
}
|
||||
}
|
||||
|
||||
announcementExp, err := time.Parse(time.RFC3339, "2024-12-01T00:00:00Z")
|
||||
announcementExp, err := time.Parse(time.RFC3339, "2023-07-01T00:00:00Z")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -178,8 +169,8 @@ func _main(ctx context.Context, buildInfo models.BuildInformation,
|
||||
Emails: []string{"quentin.mcgaw@gmail.com"},
|
||||
Version: buildInfo.Version,
|
||||
Commit: buildInfo.Commit,
|
||||
Created: buildInfo.Created,
|
||||
Announcement: "All control server routes will become private by default after the v3.41.0 release",
|
||||
BuildDate: buildInfo.Created,
|
||||
Announcement: "Wiki moved to https://github.com/qdm12/gluetun-wiki",
|
||||
AnnounceExp: announcementExp,
|
||||
// Sponsor information
|
||||
PaypalUser: "qmcgaw",
|
||||
@@ -189,22 +180,17 @@ func _main(ctx context.Context, buildInfo models.BuildInformation,
|
||||
fmt.Println(line)
|
||||
}
|
||||
|
||||
var allSettings settings.Settings
|
||||
err = allSettings.Read(reader)
|
||||
allSettings, err := source.Read()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
allSettings.SetDefaults()
|
||||
|
||||
// Note: no need to validate minimal settings for the firewall:
|
||||
// - global log level is parsed below
|
||||
// - global log level is parsed from source
|
||||
// - firewall Debug and Enabled are booleans parsed from source
|
||||
logLevel, err := log.ParseLevel(allSettings.Log.Level)
|
||||
if err != nil {
|
||||
return fmt.Errorf("log level: %w", err)
|
||||
}
|
||||
logger.Patch(log.SetLevel(logLevel))
|
||||
netLinker.PatchLoggerLevel(logLevel)
|
||||
|
||||
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
|
||||
@@ -281,7 +267,9 @@ func _main(ctx context.Context, buildInfo models.BuildInformation,
|
||||
{name: "OpenVPN 2.5", getVersion: ovpnConf.Version25},
|
||||
{name: "OpenVPN 2.6", getVersion: ovpnConf.Version26},
|
||||
{name: "Unbound", getVersion: dnsConf.Version},
|
||||
{name: "IPtables", getVersion: firewallConf.Version},
|
||||
{name: "IPtables", getVersion: func(ctx context.Context) (version string, err error) {
|
||||
return firewall.Version(ctx, cmder)
|
||||
}},
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -476,7 +464,6 @@ 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")),
|
||||
allSettings.ControlServer.AuthFilePath,
|
||||
buildInfo, vpnLooper, portForwardLooper, unboundLooper, updaterLooper, publicIPLooper,
|
||||
storage, ipv6Supported)
|
||||
if err != nil {
|
||||
@@ -595,13 +582,18 @@ type Linker interface {
|
||||
type clier interface {
|
||||
ClientKey(args []string) error
|
||||
FormatServers(args []string) error
|
||||
OpenvpnConfig(logger cli.OpenvpnConfigLogger, reader *reader.Reader, ipv6Checker cli.IPv6Checker) error
|
||||
HealthCheck(ctx context.Context, reader *reader.Reader, 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
|
||||
GenKey(args []string) error
|
||||
}
|
||||
|
||||
type Tun interface {
|
||||
Check(tunDevice string) error
|
||||
Create(tunDevice string) error
|
||||
}
|
||||
|
||||
type Source interface {
|
||||
Read() (settings settings.Settings, err error)
|
||||
ReadHealth() (health settings.Health, err error)
|
||||
String() string
|
||||
}
|
||||
|
||||
40
go.mod
40
go.mod
@@ -1,32 +1,32 @@
|
||||
module github.com/qdm12/gluetun
|
||||
|
||||
go 1.22
|
||||
go 1.21
|
||||
|
||||
require (
|
||||
github.com/breml/rootcerts v0.2.17
|
||||
github.com/fatih/color v1.17.0
|
||||
github.com/fatih/color v1.16.0
|
||||
github.com/golang/mock v1.6.0
|
||||
github.com/klauspost/compress v1.17.8
|
||||
github.com/klauspost/compress v1.17.4
|
||||
github.com/klauspost/pgzip v1.2.6
|
||||
github.com/pelletier/go-toml/v2 v2.2.2
|
||||
github.com/qdm12/dns v1.11.0
|
||||
github.com/qdm12/golibs v0.0.0-20210822203818-5c568b0777b6
|
||||
github.com/qdm12/gosettings v0.4.2
|
||||
github.com/qdm12/gosettings v0.4.0-rc1
|
||||
github.com/qdm12/goshutdown v0.3.0
|
||||
github.com/qdm12/gosplash v0.2.0
|
||||
github.com/qdm12/gosplash v0.1.0
|
||||
github.com/qdm12/gotree v0.2.0
|
||||
github.com/qdm12/govalid v0.2.0-rc1
|
||||
github.com/qdm12/log v0.1.0
|
||||
github.com/qdm12/ss-server v0.6.0
|
||||
github.com/qdm12/ss-server v0.5.0
|
||||
github.com/qdm12/updated v0.0.0-20210603204757-205acfe6937e
|
||||
github.com/stretchr/testify v1.9.0
|
||||
github.com/stretchr/testify v1.8.4
|
||||
github.com/ulikunitz/xz v0.5.11
|
||||
github.com/vishvananda/netlink v1.2.1-beta.2
|
||||
github.com/youmark/pkcs8 v0.0.0-20201027041543-1326539a0a0a
|
||||
golang.org/x/net v0.25.0
|
||||
golang.org/x/sys v0.20.0
|
||||
golang.org/x/text v0.15.0
|
||||
golang.zx2c4.com/wireguard v0.0.0-20231211153847-12269c276173
|
||||
golang.zx2c4.com/wireguard/wgctrl v0.0.0-20230429144221-925a1e7659e6
|
||||
golang.org/x/net v0.19.0
|
||||
golang.org/x/sys v0.15.0
|
||||
golang.org/x/text v0.14.0
|
||||
golang.zx2c4.com/wireguard v0.0.0-20230325221338-052af4a8072b
|
||||
golang.zx2c4.com/wireguard/wgctrl v0.0.0-20230215201556-9c5414ab4bde
|
||||
gopkg.in/ini.v1 v1.67.0
|
||||
inet.af/netaddr v0.0.0-20220811202034-502d2d690317
|
||||
)
|
||||
@@ -34,12 +34,12 @@ require (
|
||||
require (
|
||||
github.com/davecgh/go-spew v1.1.1 // indirect
|
||||
github.com/google/go-cmp v0.5.9 // indirect
|
||||
github.com/josharian/native v1.1.0 // 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.20 // indirect
|
||||
github.com/mdlayher/genetlink v1.3.2 // indirect
|
||||
github.com/mdlayher/netlink v1.7.2 // indirect
|
||||
github.com/mdlayher/socket v0.4.1 // indirect
|
||||
github.com/mdlayher/genetlink v1.2.0 // indirect
|
||||
github.com/mdlayher/netlink v1.6.2 // 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
|
||||
@@ -47,11 +47,9 @@ require (
|
||||
github.com/vishvananda/netns v0.0.0-20200728191858-db3c7e526aae // indirect
|
||||
go4.org/intern v0.0.0-20211027215823-ae77deb06f29 // indirect
|
||||
go4.org/unsafe/assume-no-moving-gc v0.0.0-20230525183740-e7c30c78aeb2 // indirect
|
||||
golang.org/x/crypto v0.23.0 // indirect
|
||||
golang.org/x/exp v0.0.0-20231110203233-9a3e6036ecaa // indirect
|
||||
golang.org/x/crypto v0.17.0 // indirect
|
||||
golang.org/x/exp v0.0.0-20230522175609-2e198f4a06a1 // indirect
|
||||
golang.org/x/sync v0.1.0 // indirect
|
||||
golang.zx2c4.com/wintun v0.0.0-20230126152724-0fa3db229ce2 // indirect
|
||||
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||
kernel.org/pub/linux/libs/security/libcap/cap v1.2.69 // indirect
|
||||
kernel.org/pub/linux/libs/security/libcap/psx v1.2.69 // indirect
|
||||
)
|
||||
|
||||
107
go.sum
107
go.sum
@@ -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.17.0 h1:GlRw1BRJxkpqUCBKzKOw098ed57fEsKeNjpTe3cSjK4=
|
||||
github.com/fatih/color v1.17.0/go.mod h1:YZ7TlrGPkiz6ku9fK3TLD/pl3CpsiFyu8N92HLgmosI=
|
||||
github.com/fatih/color v1.16.0 h1:zmkK9Ngbjj+K0yRhTVONQh1p/HknKYSlNT+vZCzyokM=
|
||||
github.com/fatih/color v1.16.0/go.mod h1:fL2Sau1YI5c0pdGEVCbKQbLXB6edEj1ZgiY4NijnWvE=
|
||||
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=
|
||||
@@ -39,17 +39,19 @@ github.com/gomodule/redigo v2.0.0+incompatible/go.mod h1:B4C85qUVwatsJoIUNIfCRsp
|
||||
github.com/google/btree v1.0.1 h1:gK4Kx5IaGY9CD5sPJ36FHiBJ6ZXl0kilRiiCj+jdYp4=
|
||||
github.com/google/btree v1.0.1/go.mod h1:xXMiIv4Fb/0kKde4SpL7qlzvu5cMJDRkFDxJfI9uaxA=
|
||||
github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
|
||||
github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.5.7/go.mod h1:n+brtR0CgQNWTVd5ZUFpTBC8YFBDLK/h/bpaJ8/DtOE=
|
||||
github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38=
|
||||
github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
||||
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 v1.1.0 h1:uuaP0hAbW7Y4l0ZRQ6C9zfb7Mg1mbFKry/xzDAfmtLA=
|
||||
github.com/josharian/native v1.1.0/go.mod h1:7X/raswPFr05uY3HiLlYeyQntB6OO7E/d2Cu7qoaN2w=
|
||||
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/klauspost/compress v1.17.8 h1:YcnTYrq7MikUT7k0Yb5eceMmALQPYBW/Xltxn0NAMnU=
|
||||
github.com/klauspost/compress v1.17.8/go.mod h1:Di0epgTjJY877eYKx5yC51cX2A2Vl2ibi7bDH9ttBbw=
|
||||
github.com/klauspost/compress v1.17.4 h1:Ej5ixsIri7BrIjBkRZLTo6ghwrEtHFk7ijlczPW4fZ4=
|
||||
github.com/klauspost/compress v1.17.4/go.mod h1:/dCuZOvVtNoHsyb+cuJD3itjs3NbnF6KH9zAO4BDxPM=
|
||||
github.com/klauspost/pgzip v1.2.6 h1:8RXeL5crjEUFnR2/Sn6GJNWtSQ3Dk8pq4CL3jvdDyjU=
|
||||
github.com/klauspost/pgzip v1.2.6/go.mod h1:Ch1tH69qFZu15pkjo5kYi6mth2Zzwzt50oCQKQE9RUs=
|
||||
github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI=
|
||||
@@ -67,12 +69,14 @@ github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Ky
|
||||
github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
|
||||
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
|
||||
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
|
||||
github.com/mdlayher/genetlink v1.3.2 h1:KdrNKe+CTu+IbZnm/GVUMXSqBBLqcGpRDa0xkQy56gw=
|
||||
github.com/mdlayher/genetlink v1.3.2/go.mod h1:tcC3pkCrPUGIKKsCsp0B3AdaaKuHtaxoJRz3cc+528o=
|
||||
github.com/mdlayher/netlink v1.7.2 h1:/UtM3ofJap7Vl4QWCPDGXY8d3GIY2UGSDbK+QWmY8/g=
|
||||
github.com/mdlayher/netlink v1.7.2/go.mod h1:xraEF7uJbxLhc5fpHL4cPe221LI2bdttWlU+ZGLfQSw=
|
||||
github.com/mdlayher/socket v0.4.1 h1:eM9y2/jlbs1M615oshPQOHZzj6R6wMT7bX5NPiQvn2U=
|
||||
github.com/mdlayher/socket v0.4.1/go.mod h1:cAqeGjoufqdxWkD7DkpyS+wcefOtmu5OQ8KuoJGIReA=
|
||||
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/go.mod h1:0o3PlBmGst1xve7wQ7j/hwpNaFaH4qCRyWCdcZk8/vA=
|
||||
github.com/mdlayher/netlink v1.6.2 h1:D2zGSkvYsJ6NreeED3JiVTu1lj2sIYATqSaZlhPzUgQ=
|
||||
github.com/mdlayher/netlink v1.6.2/go.mod h1:O1HXX2sIWSMJ3Qn1BYZk1yZM+7iMki/uYGGiwGyq/iU=
|
||||
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=
|
||||
@@ -83,8 +87,6 @@ github.com/mr-tron/base58 v1.2.0 h1:T/HDJBh4ZCPbU39/+c3rRvE0uKBQlU27+QI8LJ4t64o=
|
||||
github.com/mr-tron/base58 v1.2.0/go.mod h1:BinMc/sQntlIE1frQmRFPUoPA1Zkr8VRgBdjWI2mNwc=
|
||||
github.com/pborman/uuid v1.2.0/go.mod h1:X/NO0urCmaxf9VXbdlT7C2Yzkj2IKimNn4k+gtPdI/k=
|
||||
github.com/pelletier/go-buffruneio v0.2.0/go.mod h1:JkE26KsDizTr40EUHkXVtNPvgGtbSNq5BcowyYOWdKo=
|
||||
github.com/pelletier/go-toml/v2 v2.2.2 h1:aYUidT7k73Pcl9nb2gScu7NSrKCSHIDE89b3+6Wq+LM=
|
||||
github.com/pelletier/go-toml/v2 v2.2.2/go.mod h1:1t835xjRzz80PqgE6HHgN2JOsmgYu/h4qDAS4n929Rs=
|
||||
github.com/phayes/permbits v0.0.0-20190612203442-39d7c581d2ee/go.mod h1:3uODdxMgOaPYeWU7RzZLxVtJHZ/x1f/iHkBZuKJDzuY=
|
||||
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||
@@ -95,18 +97,20 @@ github.com/qdm12/golibs v0.0.0-20210603202746-e5494e9c2ebb/go.mod h1:15RBzkun0i8
|
||||
github.com/qdm12/golibs v0.0.0-20210723175634-a75ca7fd74c2/go.mod h1:6aRbg4Z/bTbm9JfxsGXfWKHi7zsOvPfUTK1S5HuAFKg=
|
||||
github.com/qdm12/golibs v0.0.0-20210822203818-5c568b0777b6 h1:bge5AL7cjHJMPz+5IOz5yF01q/l8No6+lIEBieA8gMg=
|
||||
github.com/qdm12/golibs v0.0.0-20210822203818-5c568b0777b6/go.mod h1:6aRbg4Z/bTbm9JfxsGXfWKHi7zsOvPfUTK1S5HuAFKg=
|
||||
github.com/qdm12/gosettings v0.4.2 h1:Gb39NScPr7OQV+oy0o1OD7A121udITDJuUGa7ljDF58=
|
||||
github.com/qdm12/gosettings v0.4.2/go.mod h1:CPrt2YC4UsURTrslmhxocVhMCW03lIrqdH2hzIf5prg=
|
||||
github.com/qdm12/gosettings v0.4.0-rc1 h1:UYA92yyeDPbmZysIuG65yrpZVPtdIoRmtEHft/AyI38=
|
||||
github.com/qdm12/gosettings v0.4.0-rc1/go.mod h1:JRV3opOpHvnKlIA29lKQMdYw1WSMVMfHYLLHPHol5ME=
|
||||
github.com/qdm12/goshutdown v0.3.0 h1:pqBpJkdwlZlfTEx4QHtS8u8CXx6pG0fVo6S1N0MpSEM=
|
||||
github.com/qdm12/goshutdown v0.3.0/go.mod h1:EqZ46No00kCTZ5qzdd3qIzY6ayhMt24QI8Mh8LVQYmM=
|
||||
github.com/qdm12/gosplash v0.2.0 h1:DOxCEizbW6ZG+FgpH2oK1atT6bM8MHL9GZ2ywSS4zZY=
|
||||
github.com/qdm12/gosplash v0.2.0/go.mod h1:k+1PzhO0th9cpX4q2Nneu4xTsndXqrM/x7NTIYmJ4jo=
|
||||
github.com/qdm12/gosplash v0.1.0 h1:Sfl+zIjFZFP7b0iqf2l5UkmEY97XBnaKkH3FNY6Gf7g=
|
||||
github.com/qdm12/gosplash v0.1.0/go.mod h1:+A3fWW4/rUeDXhY3ieBzwghKdnIPFJgD8K3qQkenJlw=
|
||||
github.com/qdm12/gotree v0.2.0 h1:+58ltxkNLUyHtATFereAcOjBVfY6ETqRex8XK90Fb/c=
|
||||
github.com/qdm12/gotree v0.2.0/go.mod h1:1SdFaqKZuI46U1apbXIf25pDMNnrPuYLEqMF/qL4lY4=
|
||||
github.com/qdm12/govalid v0.2.0-rc1 h1:4iYQvU4ibrASgzelsEgZX4JyKX3UTB/DcHObzQ7BXtw=
|
||||
github.com/qdm12/govalid v0.2.0-rc1/go.mod h1:/uWzVWMuS71wmbsVnlUxpQiy6EAXqm8eQ2RbyA72roQ=
|
||||
github.com/qdm12/log v0.1.0 h1:jYBd/xscHYpblzZAd2kjZp2YmuYHjAAfbTViJWxoPTw=
|
||||
github.com/qdm12/log v0.1.0/go.mod h1:Vchi5M8uBvHfPNIblN4mjXn/oSbiWguQIbsgF1zdQPI=
|
||||
github.com/qdm12/ss-server v0.6.0 h1:OaOdCIBXx0z3DGHPT6Th0v88vGa3MtAS4oRgUsDHGZE=
|
||||
github.com/qdm12/ss-server v0.6.0/go.mod h1:0BO/zEmtTiLDlmQEcjtoHTC+w+cWxwItjBuGP6TWM78=
|
||||
github.com/qdm12/ss-server v0.5.0 h1:ARAqJayohDM51BmJ/R5Yplkpo+Qxgp7xizBF1HWd7uQ=
|
||||
github.com/qdm12/ss-server v0.5.0/go.mod h1:eFd8PL/uy0ZvJ4KeSUzToruJctVQoYqXk+LRy9vcOiI=
|
||||
github.com/qdm12/updated v0.0.0-20210603204757-205acfe6937e h1:4q+uFLawkaQRq3yARYLsjJPZd2wYwxn4g6G/5v0xW1g=
|
||||
github.com/qdm12/updated v0.0.0-20210603204757-205acfe6937e/go.mod h1:UvJRGkZ9XL3/D7e7JiTTVLm1F3Cymd3/gFpD6frEpBo=
|
||||
github.com/riobard/go-bloom v0.0.0-20200614022211-cdc8013cb5b3 h1:f/FNXud6gA3MNr8meMVVGxhp+QBTqY91tM8HjEuMjGg=
|
||||
@@ -115,18 +119,12 @@ github.com/sergi/go-diff v1.0.0/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAm
|
||||
github.com/src-d/gcfg v1.4.0/go.mod h1:p/UMsR43ujA89BJY9duynAwIpvqEujIH/jFlfL7jWoI=
|
||||
github.com/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/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA=
|
||||
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/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
|
||||
github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk=
|
||||
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
|
||||
github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=
|
||||
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
|
||||
github.com/ulikunitz/xz v0.5.11 h1:kpFauv27b6ynzBNT/Xy+1k+fK4WswhN/6PN5WhFAGw8=
|
||||
github.com/ulikunitz/xz v0.5.11/go.mod h1:nbz6k7qbPmH4IRqmfOplQw/tblSgqTqBwxkY0oWt/14=
|
||||
github.com/vishvananda/netlink v1.2.1-beta.2 h1:Llsql0lnQEbHj0I1OuKyp8otXp0r3q0mPkuhwHfStVs=
|
||||
@@ -156,26 +154,31 @@ golang.org/x/crypto v0.0.0-20200302210943-78000ba7a073/go.mod h1:LzIPMQfyMNhhGPh
|
||||
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||
golang.org/x/crypto v0.0.0-20210513164829-c07d793c2f9a/go.mod h1:P+XmwS30IXTQdn5tA2iutPOUgjI07+tq3H3K9MVA1s8=
|
||||
golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
||||
golang.org/x/crypto v0.23.0 h1:dIJU/v2J8Mdglj/8rJ6UUOM3Zc9zLZxVZwwxMooUSAI=
|
||||
golang.org/x/crypto v0.23.0/go.mod h1:CKFgDieR+mRhux2Lsu27y0fO304Db0wZe70UKqHu0v8=
|
||||
golang.org/x/exp v0.0.0-20231110203233-9a3e6036ecaa h1:FRnLl4eNAQl8hwxVVC17teOw8kdjVDVAiFMtgUdTSRQ=
|
||||
golang.org/x/exp v0.0.0-20231110203233-9a3e6036ecaa/go.mod h1:zk2irFbV9DP96SEBUUAy67IdHUaZuSnrz1n472HUCLE=
|
||||
golang.org/x/crypto v0.17.0 h1:r8bRNjWL3GshPW3gkd+RpvzWrZAwPS49OmTGZ/uhM4k=
|
||||
golang.org/x/crypto v0.17.0/go.mod h1:gCAAfMLgwOJRpTjQ2zCCt2OcSfYMTeZVSRtQlPC7Nq4=
|
||||
golang.org/x/exp v0.0.0-20230522175609-2e198f4a06a1 h1:k/i9J1pBpvlfR+9QsetwPyERsqu1GIbi967PQMq3Ivc=
|
||||
golang.org/x/exp v0.0.0-20230522175609-2e198f4a06a1/go.mod h1:V1LtkGg67GoY2N1AnLN78QLrzxkLyJw7RJb1gzOOz9w=
|
||||
golang.org/x/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-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20190724013045-ca1201d0de80/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-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
|
||||
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.25.0 h1:d/OCCoBEUq33pjydKrGQhw7IlUPI2Oylr+8qLx49kac=
|
||||
golang.org/x/net v0.25.0/go.mod h1:JkAGAh7GEvH74S6FOH42FLoXpXbE/aqXSrIQjXgsiwM=
|
||||
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-20220923203811-8be639271d50/go.mod h1:YDH+HFinaLZZlnHAfSS6ZXJJ9M9t4Dl22yv3iI2vPwk=
|
||||
golang.org/x/net v0.19.0 h1:zTwKpTd2XuCqf8huc7Fo2iSy+4RHPd10s4KzeTnVr1c=
|
||||
golang.org/x/net v0.19.0/go.mod h1:CfAk/cbD4CthTvqiEl8NpboMuiuOYsAr/7NOjZJtv1U=
|
||||
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/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20220923202941-7f9b1623fab7/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.1.0 h1:wsuoTGHzEhffawBOhz5CYhcrV4IdKZbEyZjBMuTp12o=
|
||||
golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
@@ -191,20 +194,29 @@ golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7w
|
||||
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
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-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-20220319134239-a9b59b0215f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220728004956-3c1f35247d10/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.20.0 h1:Od9JTbYCk261bKm4M/mw7AklTlFYIa0bIp9BgSm1S8Y=
|
||||
golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/sys v0.15.0 h1:h48lPFYpsTvQJZF4EKyI4aLHaev3CxivZmv7yZig9pc=
|
||||
golang.org/x/sys v0.15.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
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.15.0 h1:h1V/4gjBv8v9cjcR6+AR5+/cIYK5N/WAgiv4xlsEtAk=
|
||||
golang.org/x/text v0.15.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
|
||||
golang.org/x/time v0.0.0-20220210224613-90d013bbcef8 h1:vVKdlvoWBphwdxWKrFZEuM0kGgGLxUOYcY4U/2Vjg44=
|
||||
golang.org/x/time v0.0.0-20220210224613-90d013bbcef8/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
|
||||
golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ=
|
||||
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
|
||||
golang.org/x/time v0.0.0-20191024005414-555d28b269f0 h1:/5xXl8Y5W96D+TtHSlonuFqGHIWVuyCkGJLwGh9JJFs=
|
||||
golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||
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=
|
||||
@@ -213,13 +225,14 @@ golang.org/x/tools v0.1.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0=
|
||||
golang.org/x/tools v0.1.1/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
|
||||
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.zx2c4.com/wintun v0.0.0-20230126152724-0fa3db229ce2 h1:B82qJJgjvYKsXS9jeunTOisW56dUokqW/FOteYJJ/yg=
|
||||
golang.zx2c4.com/wintun v0.0.0-20230126152724-0fa3db229ce2/go.mod h1:deeaetjYA+DHMHg+sMSMI58GrEteJUUzzw7en6TJQcI=
|
||||
golang.zx2c4.com/wireguard v0.0.0-20231211153847-12269c276173 h1:/jFs0duh4rdb8uIfPMv78iAJGcPKDeqAFnaLBropIC4=
|
||||
golang.zx2c4.com/wireguard v0.0.0-20231211153847-12269c276173/go.mod h1:tkCQ4FQXmpAgYVh++1cq16/dH4QJtmvpRv19DWGAHSA=
|
||||
golang.zx2c4.com/wireguard/wgctrl v0.0.0-20230429144221-925a1e7659e6 h1:CawjfCvYQH2OU3/TnxLx97WDSUDRABfT18pCOYwc2GE=
|
||||
golang.zx2c4.com/wireguard/wgctrl v0.0.0-20230429144221-925a1e7659e6/go.mod h1:3rxYc4HtVcSG9gVaTs2GEBdehh+sYPOwKtyUWEOTb80=
|
||||
golang.zx2c4.com/wireguard v0.0.0-20230325221338-052af4a8072b h1:J1CaxgLerRR5lgx3wnr6L04cJFbWoceSK9JWBdglINo=
|
||||
golang.zx2c4.com/wireguard v0.0.0-20230325221338-052af4a8072b/go.mod h1:tqur9LnfstdR9ep2LaJT4lFUl0EjlHtge+gAjmsHUG4=
|
||||
golang.zx2c4.com/wireguard/wgctrl v0.0.0-20230215201556-9c5414ab4bde h1:ybF7AMzIUikL9x4LgwEmzhXtzRpKNqngme1VGDWz+Nk=
|
||||
golang.zx2c4.com/wireguard/wgctrl v0.0.0-20230215201556-9c5414ab4bde/go.mod h1:mQqgjkW8GQQcJQsbBvK890TKqUK1DfKWkuBGbOkuMHQ=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY=
|
||||
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
@@ -234,12 +247,8 @@ gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
gvisor.dev/gvisor v0.0.0-20230927004350-cbd86285d259 h1:TbRPT0HtzFP3Cno1zZo7yPzEEnfu8EjLfl6IU9VfqkQ=
|
||||
gvisor.dev/gvisor v0.0.0-20230927004350-cbd86285d259/go.mod h1:AVgIgHMwK63XvmAzWG9vLQ41YnVHN0du0tEC46fI7yY=
|
||||
gvisor.dev/gvisor v0.0.0-20221203005347-703fd9b7fbc0 h1:Wobr37noukisGxpKo5jAsLREcpj61RxrWYzD8uwveOY=
|
||||
gvisor.dev/gvisor v0.0.0-20221203005347-703fd9b7fbc0/go.mod h1:Dn5idtptoW1dIos9U6A2rpebLs/MtTwFacjKb8jLdQA=
|
||||
inet.af/netaddr v0.0.0-20210511181906-37180328850c/go.mod h1:z0nx+Dh+7N7CC8V5ayHtHGpZpxLQZZxkIaaz6HN65Ls=
|
||||
inet.af/netaddr v0.0.0-20220811202034-502d2d690317 h1:U2fwK6P2EqmopP/hFLTOAjWTki0qgd4GMJn5X8wOleU=
|
||||
inet.af/netaddr v0.0.0-20220811202034-502d2d690317/go.mod h1:OIezDfdzOgFhuw4HuWapWq2e9l0H9tK4F1j+ETRtF3k=
|
||||
kernel.org/pub/linux/libs/security/libcap/cap v1.2.69 h1:N0m3tKYbkRMmDobh/47ngz+AWeV7PcfXMDi8xu3Vrag=
|
||||
kernel.org/pub/linux/libs/security/libcap/cap v1.2.69/go.mod h1:Tk5Ip2TuxaWGpccL7//rAsLRH6RQ/jfqTGxuN/+i/FQ=
|
||||
kernel.org/pub/linux/libs/security/libcap/psx v1.2.69 h1:IdrOs1ZgwGw5CI+BH6GgVVlOt+LAXoPyh7enr8lfaXs=
|
||||
kernel.org/pub/linux/libs/security/libcap/psx v1.2.69/go.mod h1:+l6Ee2F59XiJ2I6WR5ObpC1utCQJZ/VLsEbQCD8RG24=
|
||||
|
||||
@@ -6,12 +6,13 @@ import (
|
||||
"io"
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
"github.com/qdm12/gluetun/internal/configuration/sources/files"
|
||||
)
|
||||
|
||||
func (c *CLI) ClientKey(args []string) error {
|
||||
flagSet := flag.NewFlagSet("clientkey", flag.ExitOnError)
|
||||
const openVPNClientKeyPath = "/gluetun/client.key" // TODO deduplicate?
|
||||
filepath := flagSet.String("path", openVPNClientKeyPath, "file path to the client.key file")
|
||||
filepath := flagSet.String("path", files.OpenVPNClientKeyPath, "file path to the client.key file")
|
||||
if err := flagSet.Parse(args); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -1,66 +0,0 @@
|
||||
package cli
|
||||
|
||||
import (
|
||||
"crypto/rand"
|
||||
"flag"
|
||||
"fmt"
|
||||
)
|
||||
|
||||
func (c *CLI) GenKey(args []string) (err error) {
|
||||
flagSet := flag.NewFlagSet("genkey", flag.ExitOnError)
|
||||
err = flagSet.Parse(args)
|
||||
if err != nil {
|
||||
return fmt.Errorf("parsing flags: %w", err)
|
||||
}
|
||||
|
||||
const keyLength = 128 / 8
|
||||
keyBytes := make([]byte, keyLength)
|
||||
|
||||
_, _ = rand.Read(keyBytes)
|
||||
|
||||
key := base58Encode(keyBytes)
|
||||
fmt.Println(key)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func base58Encode(data []byte) string {
|
||||
const alphabet = "123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz"
|
||||
const radix = 58
|
||||
|
||||
zcount := 0
|
||||
for zcount < len(data) && data[zcount] == 0 {
|
||||
zcount++
|
||||
}
|
||||
|
||||
// integer simplification of ceil(log(256)/log(58))
|
||||
ceilLog256Div58 := (len(data)-zcount)*555/406 + 1 //nolint:gomnd
|
||||
size := zcount + ceilLog256Div58
|
||||
|
||||
output := make([]byte, size)
|
||||
|
||||
high := size - 1
|
||||
for _, b := range data {
|
||||
i := size - 1
|
||||
for carry := uint32(b); i > high || carry != 0; i-- {
|
||||
carry += 256 * uint32(output[i]) //nolint:gomnd
|
||||
output[i] = byte(carry % radix)
|
||||
carry /= radix
|
||||
}
|
||||
high = i
|
||||
}
|
||||
|
||||
// Determine the additional "zero-gap" in the output buffer
|
||||
additionalZeroGapEnd := zcount
|
||||
for additionalZeroGapEnd < size && output[additionalZeroGapEnd] == 0 {
|
||||
additionalZeroGapEnd++
|
||||
}
|
||||
|
||||
val := output[additionalZeroGapEnd-zcount:]
|
||||
size = len(val)
|
||||
for i := range val {
|
||||
output[i] = alphabet[val[i]]
|
||||
}
|
||||
|
||||
return string(output[:size])
|
||||
}
|
||||
@@ -6,15 +6,12 @@ import (
|
||||
"net/http"
|
||||
"time"
|
||||
|
||||
"github.com/qdm12/gluetun/internal/configuration/settings"
|
||||
"github.com/qdm12/gluetun/internal/healthcheck"
|
||||
"github.com/qdm12/gosettings/reader"
|
||||
)
|
||||
|
||||
func (c *CLI) HealthCheck(ctx context.Context, reader *reader.Reader, _ Warner) (err error) {
|
||||
func (c *CLI) HealthCheck(ctx context.Context, source Source, _ Warner) error {
|
||||
// Extract the health server port from the configuration.
|
||||
var config settings.Health
|
||||
err = config.Read(reader)
|
||||
config, err := source.ReadHealth()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -8,14 +8,12 @@ import (
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/qdm12/gluetun/internal/configuration/settings"
|
||||
"github.com/qdm12/gluetun/internal/constants"
|
||||
"github.com/qdm12/gluetun/internal/models"
|
||||
"github.com/qdm12/gluetun/internal/openvpn/extract"
|
||||
"github.com/qdm12/gluetun/internal/provider"
|
||||
"github.com/qdm12/gluetun/internal/storage"
|
||||
"github.com/qdm12/gluetun/internal/updater/resolver"
|
||||
"github.com/qdm12/gosettings/reader"
|
||||
)
|
||||
|
||||
type OpenvpnConfigLogger interface {
|
||||
@@ -41,15 +39,14 @@ type IPv6Checker interface {
|
||||
IsIPv6Supported() (supported bool, err error)
|
||||
}
|
||||
|
||||
func (c *CLI) OpenvpnConfig(logger OpenvpnConfigLogger, reader *reader.Reader,
|
||||
func (c *CLI) OpenvpnConfig(logger OpenvpnConfigLogger, source Source,
|
||||
ipv6Checker IPv6Checker) error {
|
||||
storage, err := storage.New(logger, constants.ServersData)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
var allSettings settings.Settings
|
||||
err = allSettings.Read(reader)
|
||||
allSettings, err := source.Read()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -73,7 +70,7 @@ func (c *CLI) OpenvpnConfig(logger OpenvpnConfigLogger, reader *reader.Reader,
|
||||
|
||||
providers := provider.NewProviders(storage, time.Now, warner, client,
|
||||
unzipper, parallelResolver, ipFetcher, openvpnFileExtractor)
|
||||
providerConf := providers.Get(allSettings.VPN.Provider.Name)
|
||||
providerConf := providers.Get(*allSettings.VPN.Provider.Name)
|
||||
connection, err := providerConf.GetConnection(
|
||||
allSettings.VPN.Provider.ServerSelection, ipv6Supported)
|
||||
if err != nil {
|
||||
|
||||
@@ -5,7 +5,6 @@ import (
|
||||
"net/netip"
|
||||
|
||||
"github.com/qdm12/gosettings"
|
||||
"github.com/qdm12/gosettings/reader"
|
||||
"github.com/qdm12/gotree"
|
||||
)
|
||||
|
||||
@@ -50,6 +49,14 @@ func (d *DNS) Copy() (copied DNS) {
|
||||
}
|
||||
}
|
||||
|
||||
// mergeWith merges the other settings into any
|
||||
// unset field of the receiver settings object.
|
||||
func (d *DNS) mergeWith(other DNS) {
|
||||
d.ServerAddress = gosettings.MergeWithValidator(d.ServerAddress, other.ServerAddress)
|
||||
d.KeepNameserver = gosettings.MergeWithPointer(d.KeepNameserver, other.KeepNameserver)
|
||||
d.DoT.mergeWith(other.DoT)
|
||||
}
|
||||
|
||||
// overrideWith overrides fields of the receiver
|
||||
// settings object with any field set in the other
|
||||
// settings.
|
||||
@@ -80,22 +87,3 @@ func (d DNS) toLinesNode() (node *gotree.Node) {
|
||||
node.AppendNode(d.DoT.toLinesNode())
|
||||
return node
|
||||
}
|
||||
|
||||
func (d *DNS) read(r *reader.Reader) (err error) {
|
||||
d.ServerAddress, err = r.NetipAddr("DNS_ADDRESS", reader.RetroKeys("DNS_PLAINTEXT_ADDRESS"))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
d.KeepNameserver, err = r.BoolPtr("DNS_KEEP_NAMESERVER")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = d.DoT.read(r)
|
||||
if err != nil {
|
||||
return fmt.Errorf("DNS over TLS settings: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -8,7 +8,6 @@ import (
|
||||
|
||||
"github.com/qdm12/dns/pkg/blacklist"
|
||||
"github.com/qdm12/gosettings"
|
||||
"github.com/qdm12/gosettings/reader"
|
||||
"github.com/qdm12/gotree"
|
||||
)
|
||||
|
||||
@@ -64,6 +63,16 @@ func (b DNSBlacklist) copy() (copied DNSBlacklist) {
|
||||
}
|
||||
}
|
||||
|
||||
func (b *DNSBlacklist) mergeWith(other DNSBlacklist) {
|
||||
b.BlockMalicious = gosettings.MergeWithPointer(b.BlockMalicious, other.BlockMalicious)
|
||||
b.BlockAds = gosettings.MergeWithPointer(b.BlockAds, other.BlockAds)
|
||||
b.BlockSurveillance = gosettings.MergeWithPointer(b.BlockSurveillance, other.BlockSurveillance)
|
||||
b.AllowedHosts = gosettings.MergeWithSlice(b.AllowedHosts, other.AllowedHosts)
|
||||
b.AddBlockedHosts = gosettings.MergeWithSlice(b.AddBlockedHosts, other.AddBlockedHosts)
|
||||
b.AddBlockedIPs = gosettings.MergeWithSlice(b.AddBlockedIPs, other.AddBlockedIPs)
|
||||
b.AddBlockedIPPrefixes = gosettings.MergeWithSlice(b.AddBlockedIPPrefixes, other.AddBlockedIPPrefixes)
|
||||
}
|
||||
|
||||
func (b *DNSBlacklist) overrideWith(other DNSBlacklist) {
|
||||
b.BlockMalicious = gosettings.OverrideWithPointer(b.BlockMalicious, other.BlockMalicious)
|
||||
b.BlockAds = gosettings.OverrideWithPointer(b.BlockAds, other.BlockAds)
|
||||
@@ -127,66 +136,3 @@ func (b DNSBlacklist) toLinesNode() (node *gotree.Node) {
|
||||
|
||||
return node
|
||||
}
|
||||
|
||||
func (b *DNSBlacklist) read(r *reader.Reader) (err error) {
|
||||
b.BlockMalicious, err = r.BoolPtr("BLOCK_MALICIOUS")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
b.BlockSurveillance, err = r.BoolPtr("BLOCK_SURVEILLANCE",
|
||||
reader.RetroKeys("BLOCK_NSA"))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
b.BlockAds, err = r.BoolPtr("BLOCK_ADS")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
b.AddBlockedIPs, b.AddBlockedIPPrefixes,
|
||||
err = readDoTPrivateAddresses(r) // TODO v4 split in 2
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
b.AllowedHosts = r.CSV("UNBLOCK") // TODO v4 change name
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
var (
|
||||
ErrPrivateAddressNotValid = errors.New("private address is not a valid IP or CIDR range")
|
||||
)
|
||||
|
||||
func readDoTPrivateAddresses(reader *reader.Reader) (ips []netip.Addr,
|
||||
ipPrefixes []netip.Prefix, err error) {
|
||||
privateAddresses := reader.CSV("DOT_PRIVATE_ADDRESS")
|
||||
if len(privateAddresses) == 0 {
|
||||
return nil, nil, nil
|
||||
}
|
||||
|
||||
ips = make([]netip.Addr, 0, len(privateAddresses))
|
||||
ipPrefixes = make([]netip.Prefix, 0, len(privateAddresses))
|
||||
|
||||
for _, privateAddress := range privateAddresses {
|
||||
ip, err := netip.ParseAddr(privateAddress)
|
||||
if err == nil {
|
||||
ips = append(ips, ip)
|
||||
continue
|
||||
}
|
||||
|
||||
ipPrefix, err := netip.ParsePrefix(privateAddress)
|
||||
if err == nil {
|
||||
ipPrefixes = append(ipPrefixes, ipPrefix)
|
||||
continue
|
||||
}
|
||||
|
||||
return nil, nil, fmt.Errorf(
|
||||
"environment variable DOT_PRIVATE_ADDRESS: %w: %s",
|
||||
ErrPrivateAddressNotValid, privateAddress)
|
||||
}
|
||||
|
||||
return ips, ipPrefixes, nil
|
||||
}
|
||||
|
||||
@@ -6,7 +6,6 @@ import (
|
||||
"time"
|
||||
|
||||
"github.com/qdm12/gosettings"
|
||||
"github.com/qdm12/gosettings/reader"
|
||||
"github.com/qdm12/gotree"
|
||||
)
|
||||
|
||||
@@ -62,6 +61,15 @@ func (d *DoT) copy() (copied DoT) {
|
||||
}
|
||||
}
|
||||
|
||||
// mergeWith merges the other settings into any
|
||||
// unset field of the receiver settings object.
|
||||
func (d *DoT) mergeWith(other DoT) {
|
||||
d.Enabled = gosettings.MergeWithPointer(d.Enabled, other.Enabled)
|
||||
d.UpdatePeriod = gosettings.MergeWithPointer(d.UpdatePeriod, other.UpdatePeriod)
|
||||
d.Unbound.mergeWith(other.Unbound)
|
||||
d.Blacklist.mergeWith(other.Blacklist)
|
||||
}
|
||||
|
||||
// overrideWith overrides fields of the receiver
|
||||
// settings object with any field set in the other
|
||||
// settings.
|
||||
@@ -103,27 +111,3 @@ func (d DoT) toLinesNode() (node *gotree.Node) {
|
||||
|
||||
return node
|
||||
}
|
||||
|
||||
func (d *DoT) read(reader *reader.Reader) (err error) {
|
||||
d.Enabled, err = reader.BoolPtr("DOT")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
d.UpdatePeriod, err = reader.DurationPtr("DNS_UPDATE_PERIOD")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = d.Unbound.read(reader)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = d.Blacklist.read(reader)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -3,7 +3,6 @@ package settings
|
||||
import "errors"
|
||||
|
||||
var (
|
||||
ErrValueUnknown = errors.New("value is unknown")
|
||||
ErrCityNotValid = errors.New("the city specified is not valid")
|
||||
ErrControlServerPrivilegedPort = errors.New("cannot use privileged port without running as root")
|
||||
ErrCategoryNotValid = errors.New("the category specified is not valid")
|
||||
@@ -28,8 +27,6 @@ var (
|
||||
ErrOpenVPNVerbosityIsOutOfBounds = errors.New("verbosity value is out of bounds")
|
||||
ErrOpenVPNVersionIsNotValid = errors.New("version is not valid")
|
||||
ErrPortForwardingEnabled = errors.New("port forwarding cannot be enabled")
|
||||
ErrPortForwardingUserEmpty = errors.New("port forwarding username is empty")
|
||||
ErrPortForwardingPasswordEmpty = errors.New("port forwarding password is empty")
|
||||
ErrPublicIPPeriodTooShort = errors.New("public IP address check period is too short")
|
||||
ErrRegionNotValid = errors.New("the region specified is not valid")
|
||||
ErrServerAddressNotValid = errors.New("server listening address is not valid")
|
||||
@@ -52,6 +49,5 @@ var (
|
||||
ErrWireguardPrivateKeyNotSet = errors.New("private key is not set")
|
||||
ErrWireguardPublicKeyNotSet = errors.New("public key is not set")
|
||||
ErrWireguardPublicKeyNotValid = errors.New("public key is not valid")
|
||||
ErrWireguardKeepAliveNegative = errors.New("persistent keep alive interval is negative")
|
||||
ErrWireguardImplementationNotValid = errors.New("implementation is not valid")
|
||||
)
|
||||
|
||||
@@ -5,7 +5,6 @@ import (
|
||||
"net/netip"
|
||||
|
||||
"github.com/qdm12/gosettings"
|
||||
"github.com/qdm12/gosettings/reader"
|
||||
"github.com/qdm12/gotree"
|
||||
)
|
||||
|
||||
@@ -55,6 +54,18 @@ func (f *Firewall) copy() (copied Firewall) {
|
||||
}
|
||||
}
|
||||
|
||||
// mergeWith merges the other settings into any
|
||||
// unset field of the receiver settings object.
|
||||
// It merges values of slices together, even if they
|
||||
// are set in the receiver settings.
|
||||
func (f *Firewall) mergeWith(other Firewall) {
|
||||
f.VPNInputPorts = gosettings.MergeWithSlice(f.VPNInputPorts, other.VPNInputPorts)
|
||||
f.InputPorts = gosettings.MergeWithSlice(f.InputPorts, other.InputPorts)
|
||||
f.OutboundSubnets = gosettings.MergeWithSlice(f.OutboundSubnets, other.OutboundSubnets)
|
||||
f.Enabled = gosettings.MergeWithPointer(f.Enabled, other.Enabled)
|
||||
f.Debug = gosettings.MergeWithPointer(f.Debug, other.Debug)
|
||||
}
|
||||
|
||||
// overrideWith overrides fields of the receiver
|
||||
// settings object with any field set in the other
|
||||
// settings.
|
||||
@@ -111,33 +122,3 @@ func (f Firewall) toLinesNode() (node *gotree.Node) {
|
||||
|
||||
return node
|
||||
}
|
||||
|
||||
func (f *Firewall) read(r *reader.Reader) (err error) {
|
||||
f.VPNInputPorts, err = r.CSVUint16("FIREWALL_VPN_INPUT_PORTS")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
f.InputPorts, err = r.CSVUint16("FIREWALL_INPUT_PORTS")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
f.OutboundSubnets, err = r.CSVNetipPrefixes(
|
||||
"FIREWALL_OUTBOUND_SUBNETS", reader.RetroKeys("EXTRA_SUBNETS"))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
f.Enabled, err = r.BoolPtr("FIREWALL_ENABLED_DISABLING_IT_SHOOTS_YOU_IN_YOUR_FOOT")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
f.Debug, err = r.BoolPtr("FIREWALL_DEBUG")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -6,9 +6,8 @@ import (
|
||||
"time"
|
||||
|
||||
"github.com/qdm12/gosettings"
|
||||
"github.com/qdm12/gosettings/reader"
|
||||
"github.com/qdm12/gosettings/validate"
|
||||
"github.com/qdm12/gotree"
|
||||
"github.com/qdm12/govalid/address"
|
||||
)
|
||||
|
||||
// Health contains settings for the healthcheck and health server.
|
||||
@@ -37,7 +36,9 @@ type Health struct {
|
||||
}
|
||||
|
||||
func (h Health) Validate() (err error) {
|
||||
err = validate.ListeningAddress(h.ServerAddress, os.Getuid())
|
||||
uid := os.Getuid()
|
||||
err = address.Validate(h.ServerAddress,
|
||||
address.OptionListening(uid))
|
||||
if err != nil {
|
||||
return fmt.Errorf("server listening address is not valid: %w", err)
|
||||
}
|
||||
@@ -61,27 +62,38 @@ func (h *Health) copy() (copied Health) {
|
||||
}
|
||||
}
|
||||
|
||||
// MergeWith merges the other settings into any
|
||||
// unset field of the receiver settings object.
|
||||
func (h *Health) MergeWith(other Health) {
|
||||
h.ServerAddress = gosettings.MergeWithString(h.ServerAddress, other.ServerAddress)
|
||||
h.ReadHeaderTimeout = gosettings.MergeWithNumber(h.ReadHeaderTimeout, other.ReadHeaderTimeout)
|
||||
h.ReadTimeout = gosettings.MergeWithNumber(h.ReadTimeout, other.ReadTimeout)
|
||||
h.TargetAddress = gosettings.MergeWithString(h.TargetAddress, other.TargetAddress)
|
||||
h.SuccessWait = gosettings.MergeWithNumber(h.SuccessWait, other.SuccessWait)
|
||||
h.VPN.mergeWith(other.VPN)
|
||||
}
|
||||
|
||||
// OverrideWith overrides fields of the receiver
|
||||
// settings object with any field set in the other
|
||||
// settings.
|
||||
func (h *Health) OverrideWith(other Health) {
|
||||
h.ServerAddress = gosettings.OverrideWithComparable(h.ServerAddress, other.ServerAddress)
|
||||
h.ReadHeaderTimeout = gosettings.OverrideWithComparable(h.ReadHeaderTimeout, other.ReadHeaderTimeout)
|
||||
h.ReadTimeout = gosettings.OverrideWithComparable(h.ReadTimeout, other.ReadTimeout)
|
||||
h.TargetAddress = gosettings.OverrideWithComparable(h.TargetAddress, other.TargetAddress)
|
||||
h.SuccessWait = gosettings.OverrideWithComparable(h.SuccessWait, other.SuccessWait)
|
||||
h.ServerAddress = gosettings.OverrideWithString(h.ServerAddress, other.ServerAddress)
|
||||
h.ReadHeaderTimeout = gosettings.OverrideWithNumber(h.ReadHeaderTimeout, other.ReadHeaderTimeout)
|
||||
h.ReadTimeout = gosettings.OverrideWithNumber(h.ReadTimeout, other.ReadTimeout)
|
||||
h.TargetAddress = gosettings.OverrideWithString(h.TargetAddress, other.TargetAddress)
|
||||
h.SuccessWait = gosettings.OverrideWithNumber(h.SuccessWait, other.SuccessWait)
|
||||
h.VPN.overrideWith(other.VPN)
|
||||
}
|
||||
|
||||
func (h *Health) SetDefaults() {
|
||||
h.ServerAddress = gosettings.DefaultComparable(h.ServerAddress, "127.0.0.1:9999")
|
||||
h.ServerAddress = gosettings.DefaultString(h.ServerAddress, "127.0.0.1:9999")
|
||||
const defaultReadHeaderTimeout = 100 * time.Millisecond
|
||||
h.ReadHeaderTimeout = gosettings.DefaultComparable(h.ReadHeaderTimeout, defaultReadHeaderTimeout)
|
||||
h.ReadHeaderTimeout = gosettings.DefaultNumber(h.ReadHeaderTimeout, defaultReadHeaderTimeout)
|
||||
const defaultReadTimeout = 500 * time.Millisecond
|
||||
h.ReadTimeout = gosettings.DefaultComparable(h.ReadTimeout, defaultReadTimeout)
|
||||
h.TargetAddress = gosettings.DefaultComparable(h.TargetAddress, "cloudflare.com:443")
|
||||
h.ReadTimeout = gosettings.DefaultNumber(h.ReadTimeout, defaultReadTimeout)
|
||||
h.TargetAddress = gosettings.DefaultString(h.TargetAddress, "cloudflare.com:443")
|
||||
const defaultSuccessWait = 5 * time.Second
|
||||
h.SuccessWait = gosettings.DefaultComparable(h.SuccessWait, defaultSuccessWait)
|
||||
h.SuccessWait = gosettings.DefaultNumber(h.SuccessWait, defaultSuccessWait)
|
||||
h.VPN.setDefaults()
|
||||
}
|
||||
|
||||
@@ -99,21 +111,3 @@ func (h Health) toLinesNode() (node *gotree.Node) {
|
||||
node.AppendNode(h.VPN.toLinesNode("VPN"))
|
||||
return node
|
||||
}
|
||||
|
||||
func (h *Health) Read(r *reader.Reader) (err error) {
|
||||
h.ServerAddress = r.String("HEALTH_SERVER_ADDRESS")
|
||||
h.TargetAddress = r.String("HEALTH_TARGET_ADDRESS",
|
||||
reader.RetroKeys("HEALTH_ADDRESS_TO_PING"))
|
||||
|
||||
h.SuccessWait, err = r.Duration("HEALTH_SUCCESS_WAIT_DURATION")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = h.VPN.read(r)
|
||||
if err != nil {
|
||||
return fmt.Errorf("VPN health settings: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -4,7 +4,6 @@ import (
|
||||
"time"
|
||||
|
||||
"github.com/qdm12/gosettings"
|
||||
"github.com/qdm12/gosettings/reader"
|
||||
"github.com/qdm12/gotree"
|
||||
)
|
||||
|
||||
@@ -24,6 +23,8 @@ func (h HealthyWait) validate() (err error) {
|
||||
return nil
|
||||
}
|
||||
|
||||
// mergeWith merges the other settings into any
|
||||
// unset field of the receiver settings object.
|
||||
func (h *HealthyWait) copy() (copied HealthyWait) {
|
||||
return HealthyWait{
|
||||
Initial: gosettings.CopyPointer(h.Initial),
|
||||
@@ -31,6 +32,13 @@ func (h *HealthyWait) copy() (copied HealthyWait) {
|
||||
}
|
||||
}
|
||||
|
||||
// mergeWith merges the other settings into any
|
||||
// unset field of the receiver settings object.
|
||||
func (h *HealthyWait) mergeWith(other HealthyWait) {
|
||||
h.Initial = gosettings.MergeWithPointer(h.Initial, other.Initial)
|
||||
h.Addition = gosettings.MergeWithPointer(h.Addition, other.Addition)
|
||||
}
|
||||
|
||||
// overrideWith overrides fields of the receiver
|
||||
// settings object with any field set in the other
|
||||
// settings.
|
||||
@@ -56,21 +64,3 @@ func (h HealthyWait) toLinesNode(kind string) (node *gotree.Node) {
|
||||
node.Appendf("Additional duration: %s", *h.Addition)
|
||||
return node
|
||||
}
|
||||
|
||||
func (h *HealthyWait) read(r *reader.Reader) (err error) {
|
||||
h.Initial, err = r.DurationPtr(
|
||||
"HEALTH_VPN_DURATION_INITIAL",
|
||||
reader.RetroKeys("HEALTH_OPENVPN_DURATION_INITIAL"))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
h.Addition, err = r.DurationPtr(
|
||||
"HEALTH_VPN_DURATION_ADDITION",
|
||||
reader.RetroKeys("HEALTH_OPENVPN_DURATION_ADDITION"))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -1,5 +0,0 @@
|
||||
package settings
|
||||
|
||||
func ptrTo[T any](value T) *T {
|
||||
return &value
|
||||
}
|
||||
8
internal/configuration/settings/helpers/string.go
Normal file
8
internal/configuration/settings/helpers/string.go
Normal file
@@ -0,0 +1,8 @@
|
||||
package helpers
|
||||
|
||||
func TCPPtrToString(tcp *bool) string {
|
||||
if *tcp {
|
||||
return "TCP"
|
||||
}
|
||||
return "UDP"
|
||||
}
|
||||
@@ -3,13 +3,11 @@ package settings
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/qdm12/gosettings"
|
||||
"github.com/qdm12/gosettings/reader"
|
||||
"github.com/qdm12/gosettings/validate"
|
||||
"github.com/qdm12/gotree"
|
||||
"github.com/qdm12/govalid/address"
|
||||
)
|
||||
|
||||
// HTTPProxy contains settings to configure the HTTP proxy.
|
||||
@@ -46,7 +44,9 @@ type HTTPProxy struct {
|
||||
|
||||
func (h HTTPProxy) validate() (err error) {
|
||||
// Do not validate user and password
|
||||
err = validate.ListeningAddress(h.ListeningAddress, os.Getuid())
|
||||
|
||||
uid := os.Getuid()
|
||||
err = address.Validate(h.ListeningAddress, address.OptionListening(uid))
|
||||
if err != nil {
|
||||
return fmt.Errorf("%w: %s", ErrServerAddressNotValid, h.ListeningAddress)
|
||||
}
|
||||
@@ -67,31 +67,44 @@ func (h *HTTPProxy) copy() (copied HTTPProxy) {
|
||||
}
|
||||
}
|
||||
|
||||
// mergeWith merges the other settings into any
|
||||
// unset field of the receiver settings object.
|
||||
func (h *HTTPProxy) mergeWith(other HTTPProxy) {
|
||||
h.User = gosettings.MergeWithPointer(h.User, other.User)
|
||||
h.Password = gosettings.MergeWithPointer(h.Password, other.Password)
|
||||
h.ListeningAddress = gosettings.MergeWithString(h.ListeningAddress, other.ListeningAddress)
|
||||
h.Enabled = gosettings.MergeWithPointer(h.Enabled, other.Enabled)
|
||||
h.Stealth = gosettings.MergeWithPointer(h.Stealth, other.Stealth)
|
||||
h.Log = gosettings.MergeWithPointer(h.Log, other.Log)
|
||||
h.ReadHeaderTimeout = gosettings.MergeWithNumber(h.ReadHeaderTimeout, other.ReadHeaderTimeout)
|
||||
h.ReadTimeout = gosettings.MergeWithNumber(h.ReadTimeout, other.ReadTimeout)
|
||||
}
|
||||
|
||||
// overrideWith overrides fields of the receiver
|
||||
// settings object with any field set in the other
|
||||
// settings.
|
||||
func (h *HTTPProxy) overrideWith(other HTTPProxy) {
|
||||
h.User = gosettings.OverrideWithPointer(h.User, other.User)
|
||||
h.Password = gosettings.OverrideWithPointer(h.Password, other.Password)
|
||||
h.ListeningAddress = gosettings.OverrideWithComparable(h.ListeningAddress, other.ListeningAddress)
|
||||
h.ListeningAddress = gosettings.OverrideWithString(h.ListeningAddress, other.ListeningAddress)
|
||||
h.Enabled = gosettings.OverrideWithPointer(h.Enabled, other.Enabled)
|
||||
h.Stealth = gosettings.OverrideWithPointer(h.Stealth, other.Stealth)
|
||||
h.Log = gosettings.OverrideWithPointer(h.Log, other.Log)
|
||||
h.ReadHeaderTimeout = gosettings.OverrideWithComparable(h.ReadHeaderTimeout, other.ReadHeaderTimeout)
|
||||
h.ReadTimeout = gosettings.OverrideWithComparable(h.ReadTimeout, other.ReadTimeout)
|
||||
h.ReadHeaderTimeout = gosettings.OverrideWithNumber(h.ReadHeaderTimeout, other.ReadHeaderTimeout)
|
||||
h.ReadTimeout = gosettings.OverrideWithNumber(h.ReadTimeout, other.ReadTimeout)
|
||||
}
|
||||
|
||||
func (h *HTTPProxy) setDefaults() {
|
||||
h.User = gosettings.DefaultPointer(h.User, "")
|
||||
h.Password = gosettings.DefaultPointer(h.Password, "")
|
||||
h.ListeningAddress = gosettings.DefaultComparable(h.ListeningAddress, ":8888")
|
||||
h.ListeningAddress = gosettings.DefaultString(h.ListeningAddress, ":8888")
|
||||
h.Enabled = gosettings.DefaultPointer(h.Enabled, false)
|
||||
h.Stealth = gosettings.DefaultPointer(h.Stealth, false)
|
||||
h.Log = gosettings.DefaultPointer(h.Log, false)
|
||||
const defaultReadHeaderTimeout = time.Second
|
||||
h.ReadHeaderTimeout = gosettings.DefaultComparable(h.ReadHeaderTimeout, defaultReadHeaderTimeout)
|
||||
h.ReadHeaderTimeout = gosettings.DefaultNumber(h.ReadHeaderTimeout, defaultReadHeaderTimeout)
|
||||
const defaultReadTimeout = 3 * time.Second
|
||||
h.ReadTimeout = gosettings.DefaultComparable(h.ReadTimeout, defaultReadTimeout)
|
||||
h.ReadTimeout = gosettings.DefaultNumber(h.ReadTimeout, defaultReadTimeout)
|
||||
}
|
||||
|
||||
func (h HTTPProxy) String() string {
|
||||
@@ -115,68 +128,3 @@ func (h HTTPProxy) toLinesNode() (node *gotree.Node) {
|
||||
|
||||
return node
|
||||
}
|
||||
|
||||
func (h *HTTPProxy) read(r *reader.Reader) (err error) {
|
||||
h.User = r.Get("HTTPPROXY_USER",
|
||||
reader.RetroKeys("PROXY_USER", "TINYPROXY_USER"),
|
||||
reader.ForceLowercase(false))
|
||||
|
||||
h.Password = r.Get("HTTPPROXY_PASSWORD",
|
||||
reader.RetroKeys("PROXY_PASSWORD", "TINYPROXY_PASSWORD"),
|
||||
reader.ForceLowercase(false))
|
||||
|
||||
h.ListeningAddress, err = readHTTProxyListeningAddress(r)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
h.Enabled, err = r.BoolPtr("HTTPPROXY", reader.RetroKeys("PROXY", "TINYPROXY"))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
h.Stealth, err = r.BoolPtr("HTTPPROXY_STEALTH")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
h.Log, err = readHTTProxyLog(r)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func readHTTProxyListeningAddress(r *reader.Reader) (listeningAddress string, err error) {
|
||||
// Retro-compatible keys using a port only
|
||||
port, err := r.Uint16Ptr("",
|
||||
reader.RetroKeys("HTTPPROXY_PORT", "TINYPROXY_PORT", "PROXY_PORT"),
|
||||
reader.IsRetro("HTTPPROXY_LISTENING_ADDRESS"))
|
||||
if err != nil {
|
||||
return "", err
|
||||
} else if port != nil {
|
||||
return fmt.Sprintf(":%d", *port), nil
|
||||
}
|
||||
const currentKey = "HTTPPROXY_LISTENING_ADDRESS"
|
||||
return r.String(currentKey), nil
|
||||
}
|
||||
|
||||
func readHTTProxyLog(r *reader.Reader) (enabled *bool, err error) {
|
||||
const currentKey = "HTTPPROXY_LOG"
|
||||
// Retro-compatible keys using different boolean verbs
|
||||
value := r.String("",
|
||||
reader.RetroKeys("PROXY_LOG", "TINYPROXY_LOG"),
|
||||
reader.IsRetro(currentKey))
|
||||
switch strings.ToLower(value) {
|
||||
case "":
|
||||
return r.BoolPtr(currentKey)
|
||||
case "on", "info", "connect", "notice":
|
||||
return ptrTo(true), nil
|
||||
case "disabled", "no", "off":
|
||||
return ptrTo(false), nil
|
||||
default:
|
||||
return nil, fmt.Errorf("HTTP retro-compatible proxy log setting: %w: %s",
|
||||
ErrValueUnknown, value)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,10 +1,7 @@
|
||||
package settings
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/qdm12/gosettings"
|
||||
"github.com/qdm12/gosettings/reader"
|
||||
"github.com/qdm12/gotree"
|
||||
"github.com/qdm12/log"
|
||||
)
|
||||
@@ -12,33 +9,35 @@ import (
|
||||
// Log contains settings to configure the logger.
|
||||
type Log struct {
|
||||
// Level is the log level of the logger.
|
||||
// It cannot be empty in the internal state.
|
||||
Level string
|
||||
// It cannot be nil in the internal state.
|
||||
Level *log.Level
|
||||
}
|
||||
|
||||
func (l Log) validate() (err error) {
|
||||
_, err = log.ParseLevel(l.Level)
|
||||
if err != nil {
|
||||
return fmt.Errorf("level: %w", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (l *Log) copy() (copied Log) {
|
||||
return Log{
|
||||
Level: l.Level,
|
||||
Level: gosettings.CopyPointer(l.Level),
|
||||
}
|
||||
}
|
||||
|
||||
// mergeWith merges the other settings into any
|
||||
// unset field of the receiver settings object.
|
||||
func (l *Log) mergeWith(other Log) {
|
||||
l.Level = gosettings.MergeWithPointer(l.Level, other.Level)
|
||||
}
|
||||
|
||||
// overrideWith overrides fields of the receiver
|
||||
// settings object with any field set in the other
|
||||
// settings.
|
||||
func (l *Log) overrideWith(other Log) {
|
||||
l.Level = gosettings.OverrideWithComparable(l.Level, other.Level)
|
||||
l.Level = gosettings.OverrideWithPointer(l.Level, other.Level)
|
||||
}
|
||||
|
||||
func (l *Log) setDefaults() {
|
||||
l.Level = gosettings.DefaultComparable(l.Level, log.LevelInfo.String())
|
||||
l.Level = gosettings.DefaultPointer(l.Level, log.LevelInfo)
|
||||
}
|
||||
|
||||
func (l Log) String() string {
|
||||
@@ -47,11 +46,6 @@ func (l Log) String() string {
|
||||
|
||||
func (l Log) toLinesNode() (node *gotree.Node) {
|
||||
node = gotree.New("Log settings:")
|
||||
node.Appendf("Log level: %s", l.Level)
|
||||
node.Appendf("Log level: %s", l.Level.String())
|
||||
return node
|
||||
}
|
||||
|
||||
func (l *Log) read(r *reader.Reader) (err error) {
|
||||
l.Level = r.String("LOG_LEVEL")
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -4,14 +4,12 @@ import (
|
||||
"encoding/base64"
|
||||
"fmt"
|
||||
"regexp"
|
||||
"strings"
|
||||
|
||||
"github.com/qdm12/gluetun/internal/constants/openvpn"
|
||||
"github.com/qdm12/gluetun/internal/constants/providers"
|
||||
"github.com/qdm12/gluetun/internal/openvpn/extract"
|
||||
"github.com/qdm12/gluetun/internal/provider/privateinternetaccess/presets"
|
||||
"github.com/qdm12/gosettings"
|
||||
"github.com/qdm12/gosettings/reader"
|
||||
"github.com/qdm12/gosettings/validate"
|
||||
"github.com/qdm12/gotree"
|
||||
)
|
||||
@@ -263,11 +261,32 @@ func (o *OpenVPN) copy() (copied OpenVPN) {
|
||||
}
|
||||
}
|
||||
|
||||
// mergeWith merges the other settings into any
|
||||
// unset field of the receiver settings object.
|
||||
func (o *OpenVPN) mergeWith(other OpenVPN) {
|
||||
o.Version = gosettings.MergeWithString(o.Version, other.Version)
|
||||
o.User = gosettings.MergeWithPointer(o.User, other.User)
|
||||
o.Password = gosettings.MergeWithPointer(o.Password, other.Password)
|
||||
o.ConfFile = gosettings.MergeWithPointer(o.ConfFile, other.ConfFile)
|
||||
o.Ciphers = gosettings.MergeWithSlice(o.Ciphers, other.Ciphers)
|
||||
o.Auth = gosettings.MergeWithPointer(o.Auth, other.Auth)
|
||||
o.Cert = gosettings.MergeWithPointer(o.Cert, other.Cert)
|
||||
o.Key = gosettings.MergeWithPointer(o.Key, other.Key)
|
||||
o.EncryptedKey = gosettings.MergeWithPointer(o.EncryptedKey, other.EncryptedKey)
|
||||
o.KeyPassphrase = gosettings.MergeWithPointer(o.KeyPassphrase, other.KeyPassphrase)
|
||||
o.PIAEncPreset = gosettings.MergeWithPointer(o.PIAEncPreset, other.PIAEncPreset)
|
||||
o.MSSFix = gosettings.MergeWithPointer(o.MSSFix, other.MSSFix)
|
||||
o.Interface = gosettings.MergeWithString(o.Interface, other.Interface)
|
||||
o.ProcessUser = gosettings.MergeWithString(o.ProcessUser, other.ProcessUser)
|
||||
o.Verbosity = gosettings.MergeWithPointer(o.Verbosity, other.Verbosity)
|
||||
o.Flags = gosettings.MergeWithSlice(o.Flags, other.Flags)
|
||||
}
|
||||
|
||||
// overrideWith overrides fields of the receiver
|
||||
// settings object with any field set in the other
|
||||
// settings.
|
||||
func (o *OpenVPN) overrideWith(other OpenVPN) {
|
||||
o.Version = gosettings.OverrideWithComparable(o.Version, other.Version)
|
||||
o.Version = gosettings.OverrideWithString(o.Version, other.Version)
|
||||
o.User = gosettings.OverrideWithPointer(o.User, other.User)
|
||||
o.Password = gosettings.OverrideWithPointer(o.Password, other.Password)
|
||||
o.ConfFile = gosettings.OverrideWithPointer(o.ConfFile, other.ConfFile)
|
||||
@@ -279,14 +298,14 @@ func (o *OpenVPN) overrideWith(other OpenVPN) {
|
||||
o.KeyPassphrase = gosettings.OverrideWithPointer(o.KeyPassphrase, other.KeyPassphrase)
|
||||
o.PIAEncPreset = gosettings.OverrideWithPointer(o.PIAEncPreset, other.PIAEncPreset)
|
||||
o.MSSFix = gosettings.OverrideWithPointer(o.MSSFix, other.MSSFix)
|
||||
o.Interface = gosettings.OverrideWithComparable(o.Interface, other.Interface)
|
||||
o.ProcessUser = gosettings.OverrideWithComparable(o.ProcessUser, other.ProcessUser)
|
||||
o.Interface = gosettings.OverrideWithString(o.Interface, other.Interface)
|
||||
o.ProcessUser = gosettings.OverrideWithString(o.ProcessUser, other.ProcessUser)
|
||||
o.Verbosity = gosettings.OverrideWithPointer(o.Verbosity, other.Verbosity)
|
||||
o.Flags = gosettings.OverrideWithSlice(o.Flags, other.Flags)
|
||||
}
|
||||
|
||||
func (o *OpenVPN) setDefaults(vpnProvider string) {
|
||||
o.Version = gosettings.DefaultComparable(o.Version, openvpn.Openvpn26)
|
||||
o.Version = gosettings.DefaultString(o.Version, openvpn.Openvpn25)
|
||||
o.User = gosettings.DefaultPointer(o.User, "")
|
||||
if vpnProvider == providers.Mullvad {
|
||||
o.Password = gosettings.DefaultPointer(o.Password, "m")
|
||||
@@ -307,8 +326,8 @@ func (o *OpenVPN) setDefaults(vpnProvider string) {
|
||||
}
|
||||
o.PIAEncPreset = gosettings.DefaultPointer(o.PIAEncPreset, defaultEncPreset)
|
||||
o.MSSFix = gosettings.DefaultPointer(o.MSSFix, 0)
|
||||
o.Interface = gosettings.DefaultComparable(o.Interface, "tun0")
|
||||
o.ProcessUser = gosettings.DefaultComparable(o.ProcessUser, "root")
|
||||
o.Interface = gosettings.DefaultString(o.Interface, "tun0")
|
||||
o.ProcessUser = gosettings.DefaultString(o.ProcessUser, "root")
|
||||
o.Verbosity = gosettings.DefaultPointer(o.Verbosity, 1)
|
||||
}
|
||||
|
||||
@@ -376,58 +395,3 @@ func (o OpenVPN) WithDefaults(provider string) OpenVPN {
|
||||
o.setDefaults(provider)
|
||||
return o
|
||||
}
|
||||
|
||||
func (o *OpenVPN) read(r *reader.Reader) (err error) {
|
||||
o.Version = r.String("OPENVPN_VERSION")
|
||||
o.User = r.Get("OPENVPN_USER", reader.RetroKeys("USER"), reader.ForceLowercase(false))
|
||||
o.Password = r.Get("OPENVPN_PASSWORD", reader.RetroKeys("PASSWORD"), reader.ForceLowercase(false))
|
||||
o.ConfFile = r.Get("OPENVPN_CUSTOM_CONFIG", reader.ForceLowercase(false))
|
||||
o.Ciphers = r.CSV("OPENVPN_CIPHERS", reader.RetroKeys("OPENVPN_CIPHER"))
|
||||
o.Auth = r.Get("OPENVPN_AUTH")
|
||||
o.Cert = r.Get("OPENVPN_CERT", reader.ForceLowercase(false))
|
||||
o.Key = r.Get("OPENVPN_KEY", reader.ForceLowercase(false))
|
||||
o.EncryptedKey = r.Get("OPENVPN_ENCRYPTED_KEY", reader.ForceLowercase(false))
|
||||
o.KeyPassphrase = r.Get("OPENVPN_KEY_PASSPHRASE", reader.ForceLowercase(false))
|
||||
o.PIAEncPreset = r.Get("PRIVATE_INTERNET_ACCESS_OPENVPN_ENCRYPTION_PRESET",
|
||||
reader.RetroKeys("ENCRYPTION", "PIA_ENCRYPTION"))
|
||||
|
||||
o.MSSFix, err = r.Uint16Ptr("OPENVPN_MSSFIX")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
o.Interface = r.String("VPN_INTERFACE",
|
||||
reader.RetroKeys("OPENVPN_INTERFACE"), reader.ForceLowercase(false))
|
||||
|
||||
o.ProcessUser, err = readOpenVPNProcessUser(r)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
o.Verbosity, err = r.IntPtr("OPENVPN_VERBOSITY")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
flagsPtr := r.Get("OPENVPN_FLAGS", reader.ForceLowercase(false))
|
||||
if flagsPtr != nil {
|
||||
o.Flags = strings.Fields(*flagsPtr)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func readOpenVPNProcessUser(r *reader.Reader) (processUser string, err error) {
|
||||
value, err := r.BoolPtr("OPENVPN_ROOT") // Retro-compatibility
|
||||
if err != nil {
|
||||
return "", err
|
||||
} else if value != nil {
|
||||
if *value {
|
||||
return "root", nil
|
||||
}
|
||||
const defaultNonRootUser = "nonrootuser"
|
||||
return defaultNonRootUser, nil
|
||||
}
|
||||
|
||||
return r.String("OPENVPN_PROCESS_USER"), nil
|
||||
}
|
||||
|
||||
@@ -2,14 +2,11 @@ package settings
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/qdm12/gluetun/internal/configuration/settings/helpers"
|
||||
"github.com/qdm12/gluetun/internal/constants"
|
||||
"github.com/qdm12/gluetun/internal/constants/providers"
|
||||
"github.com/qdm12/gluetun/internal/provider/privateinternetaccess/presets"
|
||||
"github.com/qdm12/gosettings"
|
||||
"github.com/qdm12/gosettings/reader"
|
||||
"github.com/qdm12/gosettings/validate"
|
||||
"github.com/qdm12/gotree"
|
||||
)
|
||||
@@ -20,10 +17,10 @@ type OpenVPNSelection struct {
|
||||
// NOT use a custom configuration file.
|
||||
// It cannot be nil in the internal state.
|
||||
ConfFile *string `json:"config_file_path"`
|
||||
// Protocol is the OpenVPN network protocol to use,
|
||||
// and can be udp or tcp. It cannot be the empty string
|
||||
// in the internal state.
|
||||
Protocol string `json:"protocol"`
|
||||
// TCP is true if the OpenVPN protocol is TCP,
|
||||
// and false for UDP.
|
||||
// It cannot be nil in the internal state.
|
||||
TCP *bool `json:"tcp"`
|
||||
// CustomPort is the OpenVPN server endpoint port.
|
||||
// It can be set to 0 to indicate no custom port should
|
||||
// be used. It cannot be nil in the internal state.
|
||||
@@ -43,13 +40,8 @@ func (o OpenVPNSelection) validate(vpnProvider string) (err error) {
|
||||
}
|
||||
}
|
||||
|
||||
err = validate.IsOneOf(o.Protocol, constants.UDP, constants.TCP)
|
||||
if err != nil {
|
||||
return fmt.Errorf("network protocol: %w", err)
|
||||
}
|
||||
|
||||
// Validate TCP
|
||||
if o.Protocol == constants.TCP && helpers.IsOneOf(vpnProvider,
|
||||
if *o.TCP && helpers.IsOneOf(vpnProvider,
|
||||
providers.Ipvanish,
|
||||
providers.Perfectprivacy,
|
||||
providers.Privado,
|
||||
@@ -111,7 +103,7 @@ func (o OpenVPNSelection) validate(vpnProvider string) (err error) {
|
||||
}
|
||||
|
||||
allowedPorts := allowedUDP
|
||||
if o.Protocol == constants.TCP {
|
||||
if *o.TCP {
|
||||
allowedPorts = allowedTCP
|
||||
}
|
||||
err = validate.IsOneOf(*o.CustomPort, allowedPorts...)
|
||||
@@ -140,22 +132,29 @@ func (o OpenVPNSelection) validate(vpnProvider string) (err error) {
|
||||
func (o *OpenVPNSelection) copy() (copied OpenVPNSelection) {
|
||||
return OpenVPNSelection{
|
||||
ConfFile: gosettings.CopyPointer(o.ConfFile),
|
||||
Protocol: o.Protocol,
|
||||
TCP: gosettings.CopyPointer(o.TCP),
|
||||
CustomPort: gosettings.CopyPointer(o.CustomPort),
|
||||
PIAEncPreset: gosettings.CopyPointer(o.PIAEncPreset),
|
||||
}
|
||||
}
|
||||
|
||||
func (o *OpenVPNSelection) mergeWith(other OpenVPNSelection) {
|
||||
o.ConfFile = gosettings.MergeWithPointer(o.ConfFile, other.ConfFile)
|
||||
o.TCP = gosettings.MergeWithPointer(o.TCP, other.TCP)
|
||||
o.CustomPort = gosettings.MergeWithPointer(o.CustomPort, other.CustomPort)
|
||||
o.PIAEncPreset = gosettings.MergeWithPointer(o.PIAEncPreset, other.PIAEncPreset)
|
||||
}
|
||||
|
||||
func (o *OpenVPNSelection) overrideWith(other OpenVPNSelection) {
|
||||
o.ConfFile = gosettings.OverrideWithPointer(o.ConfFile, other.ConfFile)
|
||||
o.Protocol = gosettings.OverrideWithComparable(o.Protocol, other.Protocol)
|
||||
o.TCP = gosettings.OverrideWithPointer(o.TCP, other.TCP)
|
||||
o.CustomPort = gosettings.OverrideWithPointer(o.CustomPort, other.CustomPort)
|
||||
o.PIAEncPreset = gosettings.OverrideWithPointer(o.PIAEncPreset, other.PIAEncPreset)
|
||||
}
|
||||
|
||||
func (o *OpenVPNSelection) setDefaults(vpnProvider string) {
|
||||
o.ConfFile = gosettings.DefaultPointer(o.ConfFile, "")
|
||||
o.Protocol = gosettings.DefaultComparable(o.Protocol, constants.UDP)
|
||||
o.TCP = gosettings.DefaultPointer(o.TCP, false)
|
||||
o.CustomPort = gosettings.DefaultPointer(o.CustomPort, 0)
|
||||
|
||||
var defaultEncPreset string
|
||||
@@ -171,7 +170,7 @@ func (o OpenVPNSelection) String() string {
|
||||
|
||||
func (o OpenVPNSelection) toLinesNode() (node *gotree.Node) {
|
||||
node = gotree.New("OpenVPN server selection settings:")
|
||||
node.Appendf("Protocol: %s", strings.ToUpper(o.Protocol))
|
||||
node.Appendf("Protocol: %s", helpers.TCPPtrToString(o.TCP))
|
||||
|
||||
if *o.CustomPort != 0 {
|
||||
node.Appendf("Custom port: %d", *o.CustomPort)
|
||||
@@ -187,23 +186,3 @@ func (o OpenVPNSelection) toLinesNode() (node *gotree.Node) {
|
||||
|
||||
return node
|
||||
}
|
||||
|
||||
func (o *OpenVPNSelection) read(r *reader.Reader) (err error) {
|
||||
o.ConfFile = r.Get("OPENVPN_CUSTOM_CONFIG", reader.ForceLowercase(false))
|
||||
|
||||
o.Protocol = r.String("OPENVPN_PROTOCOL", reader.RetroKeys("PROTOCOL"))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
o.CustomPort, err = r.Uint16Ptr("OPENVPN_ENDPOINT_PORT",
|
||||
reader.RetroKeys("PORT", "OPENVPN_PORT", "VPN_ENDPOINT_PORT"))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
o.PIAEncPreset = r.Get("PRIVATE_INTERNET_ACCESS_OPENVPN_ENCRYPTION_PRESET",
|
||||
reader.RetroKeys("ENCRYPTION", "PIA_ENCRYPTION"))
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -6,7 +6,6 @@ import (
|
||||
|
||||
"github.com/qdm12/gluetun/internal/constants/providers"
|
||||
"github.com/qdm12/gosettings"
|
||||
"github.com/qdm12/gosettings/reader"
|
||||
"github.com/qdm12/gosettings/validate"
|
||||
"github.com/qdm12/gotree"
|
||||
)
|
||||
@@ -19,7 +18,7 @@ type PortForwarding struct {
|
||||
// Provider is set to specify which custom port forwarding code
|
||||
// should be used. This is especially necessary for the custom
|
||||
// provider using Wireguard for a provider where Wireguard is not
|
||||
// natively supported but custom port forwarding code is available.
|
||||
// natively supported but custom port forwading code is available.
|
||||
// It defaults to the empty string, meaning the current provider
|
||||
// should be the one used for port forwarding.
|
||||
// It cannot be nil for the internal state.
|
||||
@@ -33,10 +32,6 @@ type PortForwarding struct {
|
||||
// forwarded port. The redirection is disabled if it is set to 0, which
|
||||
// is its default as well.
|
||||
ListeningPort *uint16 `json:"listening_port"`
|
||||
// Username is only used for Private Internet Access port forwarding.
|
||||
Username string `json:"username"`
|
||||
// Password is only used for Private Internet Access port forwarding.
|
||||
Password string `json:"password"`
|
||||
}
|
||||
|
||||
func (p PortForwarding) Validate(vpnProvider string) (err error) {
|
||||
@@ -50,7 +45,6 @@ func (p PortForwarding) Validate(vpnProvider string) (err error) {
|
||||
providerSelected = *p.Provider
|
||||
}
|
||||
validProviders := []string{
|
||||
providers.Perfectprivacy,
|
||||
providers.PrivateInternetAccess,
|
||||
providers.Protonvpn,
|
||||
}
|
||||
@@ -66,15 +60,6 @@ func (p PortForwarding) Validate(vpnProvider string) (err error) {
|
||||
}
|
||||
}
|
||||
|
||||
if providerSelected == providers.PrivateInternetAccess {
|
||||
switch {
|
||||
case p.Username == "":
|
||||
return fmt.Errorf("%w", ErrPortForwardingUserEmpty)
|
||||
case p.Password == "":
|
||||
return fmt.Errorf("%w", ErrPortForwardingPasswordEmpty)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -84,18 +69,21 @@ func (p *PortForwarding) Copy() (copied PortForwarding) {
|
||||
Provider: gosettings.CopyPointer(p.Provider),
|
||||
Filepath: gosettings.CopyPointer(p.Filepath),
|
||||
ListeningPort: gosettings.CopyPointer(p.ListeningPort),
|
||||
Username: p.Username,
|
||||
Password: p.Password,
|
||||
}
|
||||
}
|
||||
|
||||
func (p *PortForwarding) mergeWith(other PortForwarding) {
|
||||
p.Enabled = gosettings.MergeWithPointer(p.Enabled, other.Enabled)
|
||||
p.Provider = gosettings.MergeWithPointer(p.Provider, other.Provider)
|
||||
p.Filepath = gosettings.MergeWithPointer(p.Filepath, other.Filepath)
|
||||
p.ListeningPort = gosettings.MergeWithPointer(p.ListeningPort, other.ListeningPort)
|
||||
}
|
||||
|
||||
func (p *PortForwarding) OverrideWith(other PortForwarding) {
|
||||
p.Enabled = gosettings.OverrideWithPointer(p.Enabled, other.Enabled)
|
||||
p.Provider = gosettings.OverrideWithPointer(p.Provider, other.Provider)
|
||||
p.Filepath = gosettings.OverrideWithPointer(p.Filepath, other.Filepath)
|
||||
p.ListeningPort = gosettings.OverrideWithPointer(p.ListeningPort, other.ListeningPort)
|
||||
p.Username = gosettings.OverrideWithComparable(p.Username, other.Username)
|
||||
p.Password = gosettings.OverrideWithComparable(p.Password, other.Password)
|
||||
}
|
||||
|
||||
func (p *PortForwarding) setDefaults() {
|
||||
@@ -134,54 +122,5 @@ func (p PortForwarding) toLinesNode() (node *gotree.Node) {
|
||||
}
|
||||
node.Appendf("Forwarded port file path: %s", filepath)
|
||||
|
||||
if p.Username != "" {
|
||||
credentialsNode := node.Appendf("Credentials:")
|
||||
credentialsNode.Appendf("Username: %s", p.Username)
|
||||
credentialsNode.Appendf("Password: %s", gosettings.ObfuscateKey(p.Password))
|
||||
}
|
||||
|
||||
return node
|
||||
}
|
||||
|
||||
func (p *PortForwarding) read(r *reader.Reader) (err error) {
|
||||
p.Enabled, err = r.BoolPtr("VPN_PORT_FORWARDING",
|
||||
reader.RetroKeys(
|
||||
"PORT_FORWARDING",
|
||||
"PRIVATE_INTERNET_ACCESS_VPN_PORT_FORWARDING",
|
||||
))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
p.Provider = r.Get("VPN_PORT_FORWARDING_PROVIDER")
|
||||
|
||||
p.Filepath = r.Get("VPN_PORT_FORWARDING_STATUS_FILE",
|
||||
reader.ForceLowercase(false),
|
||||
reader.RetroKeys(
|
||||
"PORT_FORWARDING_STATUS_FILE",
|
||||
"PRIVATE_INTERNET_ACCESS_VPN_PORT_FORWARDING_STATUS_FILE",
|
||||
))
|
||||
|
||||
p.ListeningPort, err = r.Uint16Ptr("VPN_PORT_FORWARDING_LISTENING_PORT")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
usernameKeys := []string{"VPN_PORT_FORWARDING_USERNAME", "OPENVPN_USER", "USER"}
|
||||
for _, key := range usernameKeys {
|
||||
p.Username = r.String(key, reader.ForceLowercase(false))
|
||||
if p.Username != "" {
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
passwordKeys := []string{"VPN_PORT_FORWARDING_PASSWORD", "OPENVPN_PASSWORD", "PASSWORD"}
|
||||
for _, key := range passwordKeys {
|
||||
p.Password = r.String(key, reader.ForceLowercase(false))
|
||||
if p.Password != "" {
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -2,12 +2,10 @@ package settings
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/qdm12/gluetun/internal/constants/providers"
|
||||
"github.com/qdm12/gluetun/internal/constants/vpn"
|
||||
"github.com/qdm12/gosettings"
|
||||
"github.com/qdm12/gosettings/reader"
|
||||
"github.com/qdm12/gosettings/validate"
|
||||
"github.com/qdm12/gotree"
|
||||
)
|
||||
@@ -15,8 +13,8 @@ import (
|
||||
// Provider contains settings specific to a VPN provider.
|
||||
type Provider struct {
|
||||
// Name is the VPN service provider name.
|
||||
// It cannot be the empty string in the internal state.
|
||||
Name string `json:"name"`
|
||||
// It cannot be nil in the internal state.
|
||||
Name *string `json:"name"`
|
||||
// ServerSelection is the settings to
|
||||
// select the VPN server.
|
||||
ServerSelection ServerSelection `json:"server_selection"`
|
||||
@@ -35,25 +33,23 @@ func (p *Provider) validate(vpnType string, storage Storage) (err error) {
|
||||
validNames = []string{
|
||||
providers.Airvpn,
|
||||
providers.Custom,
|
||||
providers.Fastestvpn,
|
||||
providers.Ivpn,
|
||||
providers.Mullvad,
|
||||
providers.Nordvpn,
|
||||
providers.Protonvpn,
|
||||
providers.Surfshark,
|
||||
providers.Windscribe,
|
||||
}
|
||||
}
|
||||
if err = validate.IsOneOf(p.Name, validNames...); err != nil {
|
||||
if err = validate.IsOneOf(*p.Name, validNames...); err != nil {
|
||||
return fmt.Errorf("%w for Wireguard: %w", ErrVPNProviderNameNotValid, err)
|
||||
}
|
||||
|
||||
err = p.ServerSelection.validate(p.Name, storage)
|
||||
err = p.ServerSelection.validate(*p.Name, storage)
|
||||
if err != nil {
|
||||
return fmt.Errorf("server selection: %w", err)
|
||||
}
|
||||
|
||||
err = p.PortForwarding.Validate(p.Name)
|
||||
err = p.PortForwarding.Validate(*p.Name)
|
||||
if err != nil {
|
||||
return fmt.Errorf("port forwarding: %w", err)
|
||||
}
|
||||
@@ -63,22 +59,28 @@ func (p *Provider) validate(vpnType string, storage Storage) (err error) {
|
||||
|
||||
func (p *Provider) copy() (copied Provider) {
|
||||
return Provider{
|
||||
Name: p.Name,
|
||||
Name: gosettings.CopyPointer(p.Name),
|
||||
ServerSelection: p.ServerSelection.copy(),
|
||||
PortForwarding: p.PortForwarding.Copy(),
|
||||
}
|
||||
}
|
||||
|
||||
func (p *Provider) mergeWith(other Provider) {
|
||||
p.Name = gosettings.MergeWithPointer(p.Name, other.Name)
|
||||
p.ServerSelection.mergeWith(other.ServerSelection)
|
||||
p.PortForwarding.mergeWith(other.PortForwarding)
|
||||
}
|
||||
|
||||
func (p *Provider) overrideWith(other Provider) {
|
||||
p.Name = gosettings.OverrideWithComparable(p.Name, other.Name)
|
||||
p.Name = gosettings.OverrideWithPointer(p.Name, other.Name)
|
||||
p.ServerSelection.overrideWith(other.ServerSelection)
|
||||
p.PortForwarding.OverrideWith(other.PortForwarding)
|
||||
}
|
||||
|
||||
func (p *Provider) setDefaults() {
|
||||
p.Name = gosettings.DefaultComparable(p.Name, providers.PrivateInternetAccess)
|
||||
p.Name = gosettings.DefaultPointer(p.Name, providers.PrivateInternetAccess)
|
||||
p.ServerSelection.setDefaults(*p.Name)
|
||||
p.PortForwarding.setDefaults()
|
||||
p.ServerSelection.setDefaults(p.Name, *p.PortForwarding.Enabled)
|
||||
}
|
||||
|
||||
func (p Provider) String() string {
|
||||
@@ -87,42 +89,8 @@ func (p Provider) String() string {
|
||||
|
||||
func (p Provider) toLinesNode() (node *gotree.Node) {
|
||||
node = gotree.New("VPN provider settings:")
|
||||
node.Appendf("Name: %s", p.Name)
|
||||
node.Appendf("Name: %s", *p.Name)
|
||||
node.AppendNode(p.ServerSelection.toLinesNode())
|
||||
node.AppendNode(p.PortForwarding.toLinesNode())
|
||||
return node
|
||||
}
|
||||
|
||||
func (p *Provider) read(r *reader.Reader, vpnType string) (err error) {
|
||||
p.Name = readVPNServiceProvider(r, vpnType)
|
||||
|
||||
err = p.ServerSelection.read(r, p.Name, vpnType)
|
||||
if err != nil {
|
||||
return fmt.Errorf("server selection: %w", err)
|
||||
}
|
||||
|
||||
err = p.PortForwarding.read(r)
|
||||
if err != nil {
|
||||
return fmt.Errorf("port forwarding: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func readVPNServiceProvider(r *reader.Reader, vpnType string) (vpnProvider string) {
|
||||
vpnProvider = r.String("VPN_SERVICE_PROVIDER", reader.RetroKeys("VPNSP"))
|
||||
if vpnProvider == "" {
|
||||
if vpnType != vpn.Wireguard && r.Get("OPENVPN_CUSTOM_CONFIG") != nil {
|
||||
// retro compatibility
|
||||
return providers.Custom
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
vpnProvider = strings.ToLower(vpnProvider)
|
||||
if vpnProvider == "pia" { // retro compatibility
|
||||
return providers.PrivateInternetAccess
|
||||
}
|
||||
|
||||
return vpnProvider
|
||||
}
|
||||
|
||||
@@ -7,7 +7,6 @@ import (
|
||||
|
||||
"github.com/qdm12/gluetun/internal/publicip/api"
|
||||
"github.com/qdm12/gosettings"
|
||||
"github.com/qdm12/gosettings/reader"
|
||||
"github.com/qdm12/gotree"
|
||||
)
|
||||
|
||||
@@ -78,10 +77,17 @@ func (p *PublicIP) copy() (copied PublicIP) {
|
||||
}
|
||||
}
|
||||
|
||||
func (p *PublicIP) mergeWith(other PublicIP) {
|
||||
p.Period = gosettings.MergeWithPointer(p.Period, other.Period)
|
||||
p.IPFilepath = gosettings.MergeWithPointer(p.IPFilepath, other.IPFilepath)
|
||||
p.API = gosettings.MergeWithString(p.API, other.API)
|
||||
p.APIToken = gosettings.MergeWithPointer(p.APIToken, other.APIToken)
|
||||
}
|
||||
|
||||
func (p *PublicIP) overrideWith(other PublicIP) {
|
||||
p.Period = gosettings.OverrideWithPointer(p.Period, other.Period)
|
||||
p.IPFilepath = gosettings.OverrideWithPointer(p.IPFilepath, other.IPFilepath)
|
||||
p.API = gosettings.OverrideWithComparable(p.API, other.API)
|
||||
p.API = gosettings.OverrideWithString(p.API, other.API)
|
||||
p.APIToken = gosettings.OverrideWithPointer(p.APIToken, other.APIToken)
|
||||
}
|
||||
|
||||
@@ -89,7 +95,7 @@ func (p *PublicIP) setDefaults() {
|
||||
const defaultPeriod = 12 * time.Hour
|
||||
p.Period = gosettings.DefaultPointer(p.Period, defaultPeriod)
|
||||
p.IPFilepath = gosettings.DefaultPointer(p.IPFilepath, "/tmp/gluetun/ip")
|
||||
p.API = gosettings.DefaultComparable(p.API, "ipinfo")
|
||||
p.API = gosettings.DefaultString(p.API, "ipinfo")
|
||||
p.APIToken = gosettings.DefaultPointer(p.APIToken, "")
|
||||
}
|
||||
|
||||
@@ -123,16 +129,3 @@ func (p PublicIP) toLinesNode() (node *gotree.Node) {
|
||||
|
||||
return node
|
||||
}
|
||||
|
||||
func (p *PublicIP) read(r *reader.Reader) (err error) {
|
||||
p.Period, err = r.DurationPtr("PUBLICIP_PERIOD")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
p.IPFilepath = r.Get("PUBLICIP_FILE",
|
||||
reader.ForceLowercase(false), reader.RetroKeys("IP_STATUS_FILE"))
|
||||
p.API = r.String("PUBLICIP_API")
|
||||
p.APIToken = r.Get("PUBLICIP_API_TOKEN")
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -7,7 +7,6 @@ import (
|
||||
"strconv"
|
||||
|
||||
"github.com/qdm12/gosettings"
|
||||
"github.com/qdm12/gosettings/reader"
|
||||
"github.com/qdm12/gotree"
|
||||
)
|
||||
|
||||
@@ -19,11 +18,6 @@ type ControlServer struct {
|
||||
// Log can be true or false to enable logging on requests.
|
||||
// It cannot be nil in the internal state.
|
||||
Log *bool
|
||||
// AuthFilePath is the path to the file containing the authentication
|
||||
// configuration for the middleware.
|
||||
// It cannot be empty in the internal state and defaults to
|
||||
// /gluetun/auth/config.toml.
|
||||
AuthFilePath string
|
||||
}
|
||||
|
||||
func (c ControlServer) validate() (err error) {
|
||||
@@ -49,25 +43,29 @@ func (c ControlServer) validate() (err error) {
|
||||
|
||||
func (c *ControlServer) copy() (copied ControlServer) {
|
||||
return ControlServer{
|
||||
Address: gosettings.CopyPointer(c.Address),
|
||||
Log: gosettings.CopyPointer(c.Log),
|
||||
AuthFilePath: c.AuthFilePath,
|
||||
Address: gosettings.CopyPointer(c.Address),
|
||||
Log: gosettings.CopyPointer(c.Log),
|
||||
}
|
||||
}
|
||||
|
||||
// mergeWith merges the other settings into any
|
||||
// unset field of the receiver settings object.
|
||||
func (c *ControlServer) mergeWith(other ControlServer) {
|
||||
c.Address = gosettings.MergeWithPointer(c.Address, other.Address)
|
||||
c.Log = gosettings.MergeWithPointer(c.Log, other.Log)
|
||||
}
|
||||
|
||||
// overrideWith overrides fields of the receiver
|
||||
// settings object with any field set in the other
|
||||
// settings.
|
||||
func (c *ControlServer) overrideWith(other ControlServer) {
|
||||
c.Address = gosettings.OverrideWithPointer(c.Address, other.Address)
|
||||
c.Log = gosettings.OverrideWithPointer(c.Log, other.Log)
|
||||
c.AuthFilePath = gosettings.OverrideWithComparable(c.AuthFilePath, other.AuthFilePath)
|
||||
}
|
||||
|
||||
func (c *ControlServer) setDefaults() {
|
||||
c.Address = gosettings.DefaultPointer(c.Address, ":8000")
|
||||
c.Log = gosettings.DefaultPointer(c.Log, true)
|
||||
c.AuthFilePath = gosettings.DefaultComparable(c.AuthFilePath, "/gluetun/auth/config.toml")
|
||||
}
|
||||
|
||||
func (c ControlServer) String() string {
|
||||
@@ -78,19 +76,5 @@ func (c ControlServer) toLinesNode() (node *gotree.Node) {
|
||||
node = gotree.New("Control server settings:")
|
||||
node.Appendf("Listening address: %s", *c.Address)
|
||||
node.Appendf("Logging: %s", gosettings.BoolToYesNo(c.Log))
|
||||
node.Appendf("Authentication file path: %s", c.AuthFilePath)
|
||||
return node
|
||||
}
|
||||
|
||||
func (c *ControlServer) read(r *reader.Reader) (err error) {
|
||||
c.Log, err = r.BoolPtr("HTTP_CONTROL_SERVER_LOG")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
c.Address = r.Get("HTTP_CONTROL_SERVER_ADDRESS")
|
||||
|
||||
c.AuthFilePath = r.String("HTTP_CONTROL_SERVER_AUTH_CONFIG_FILEPATH")
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -12,7 +12,6 @@ import (
|
||||
"github.com/qdm12/gluetun/internal/constants/vpn"
|
||||
"github.com/qdm12/gluetun/internal/models"
|
||||
"github.com/qdm12/gosettings"
|
||||
"github.com/qdm12/gosettings/reader"
|
||||
"github.com/qdm12/gosettings/validate"
|
||||
"github.com/qdm12/gotree"
|
||||
)
|
||||
@@ -55,21 +54,14 @@ type ServerSelection struct { //nolint:maligned
|
||||
// TODO extend to providers using FreeOnly.
|
||||
PremiumOnly *bool `json:"premium_only"`
|
||||
// StreamOnly is true if VPN servers not for streaming should
|
||||
// be filtered. This is used with ProtonVPN and VPNUnlimited.
|
||||
// be filtered. This is used with VPNUnlimited.
|
||||
StreamOnly *bool `json:"stream_only"`
|
||||
// MultiHopOnly is true if VPN servers that are not multihop
|
||||
// should be filtered. This is used with Surfshark.
|
||||
MultiHopOnly *bool `json:"multi_hop_only"`
|
||||
// PortForwardOnly is true if VPN servers that don't support
|
||||
// port forwarding should be filtered. This is used with PIA
|
||||
// and ProtonVPN.
|
||||
// port forwarding should be filtered. This is used with PIA.
|
||||
PortForwardOnly *bool `json:"port_forward_only"`
|
||||
// SecureCoreOnly is true if VPN servers without secure core should
|
||||
// be filtered. This is used with ProtonVPN.
|
||||
SecureCoreOnly *bool `json:"secure_core_only"`
|
||||
// TorOnly is true if VPN servers without tor should
|
||||
// be filtered. This is used with ProtonVPN.
|
||||
TorOnly *bool `json:"tor_only"`
|
||||
// OpenVPN contains settings to select OpenVPN servers
|
||||
// and the final connection.
|
||||
OpenVPN OpenVPNSelection `json:"openvpn"`
|
||||
@@ -86,8 +78,6 @@ var (
|
||||
ErrMultiHopOnlyNotSupported = errors.New("multi hop only filter is not supported")
|
||||
ErrPortForwardOnlyNotSupported = errors.New("port forwarding only filter is not supported")
|
||||
ErrFreePremiumBothSet = errors.New("free only and premium only filters are both set")
|
||||
ErrSecureCoreOnlyNotSupported = errors.New("secure core only filter is not supported")
|
||||
ErrTorOnlyNotSupported = errors.New("tor only filter is not supported")
|
||||
)
|
||||
|
||||
func (ss *ServerSelection) validate(vpnServiceProvider string,
|
||||
@@ -111,19 +101,60 @@ func (ss *ServerSelection) validate(vpnServiceProvider string,
|
||||
*ss = surfsharkRetroRegion(*ss)
|
||||
}
|
||||
|
||||
err = validateServerFilters(*ss, filterChoices, vpnServiceProvider)
|
||||
err = validateServerFilters(*ss, filterChoices)
|
||||
if err != nil {
|
||||
return fmt.Errorf("for VPN service provider %s: %w", vpnServiceProvider, err)
|
||||
}
|
||||
|
||||
err = validateSubscriptionTierFilters(*ss, vpnServiceProvider)
|
||||
if err != nil {
|
||||
return fmt.Errorf("for VPN service provider %s: %w", vpnServiceProvider, err)
|
||||
if *ss.OwnedOnly &&
|
||||
vpnServiceProvider != providers.Mullvad {
|
||||
return fmt.Errorf("%w: for VPN service provider %s",
|
||||
ErrOwnedOnlyNotSupported, vpnServiceProvider)
|
||||
}
|
||||
|
||||
err = validateFeatureFilters(*ss, vpnServiceProvider)
|
||||
if err != nil {
|
||||
return fmt.Errorf("for VPN service provider %s: %w", vpnServiceProvider, err)
|
||||
if *ss.FreeOnly &&
|
||||
!helpers.IsOneOf(vpnServiceProvider,
|
||||
providers.Protonvpn,
|
||||
providers.VPNUnlimited,
|
||||
) {
|
||||
return fmt.Errorf("%w: for VPN service provider %s",
|
||||
ErrFreeOnlyNotSupported, vpnServiceProvider)
|
||||
}
|
||||
|
||||
if *ss.PremiumOnly &&
|
||||
!helpers.IsOneOf(vpnServiceProvider,
|
||||
providers.VPNSecure,
|
||||
) {
|
||||
return fmt.Errorf("%w: for VPN service provider %s",
|
||||
ErrPremiumOnlyNotSupported, vpnServiceProvider)
|
||||
}
|
||||
|
||||
if *ss.FreeOnly && *ss.PremiumOnly {
|
||||
return fmt.Errorf("%w", ErrFreePremiumBothSet)
|
||||
}
|
||||
|
||||
if *ss.StreamOnly &&
|
||||
!helpers.IsOneOf(vpnServiceProvider,
|
||||
providers.Protonvpn,
|
||||
providers.VPNUnlimited,
|
||||
) {
|
||||
return fmt.Errorf("%w: for VPN service provider %s",
|
||||
ErrStreamOnlyNotSupported, vpnServiceProvider)
|
||||
}
|
||||
|
||||
if *ss.MultiHopOnly &&
|
||||
vpnServiceProvider != providers.Surfshark {
|
||||
return fmt.Errorf("%w: for VPN service provider %s",
|
||||
ErrMultiHopOnlyNotSupported, vpnServiceProvider)
|
||||
}
|
||||
|
||||
if *ss.PortForwardOnly &&
|
||||
vpnServiceProvider != providers.PrivateInternetAccess {
|
||||
// ProtonVPN also supports port forwarding, but on all their servers, so these
|
||||
// don't have the port forwarding boolean field. As a consequence, we only allow
|
||||
// the use of PortForwardOnly for Private Internet Access.
|
||||
return fmt.Errorf("%w: for VPN service provider %s",
|
||||
ErrPortForwardOnlyNotSupported, vpnServiceProvider)
|
||||
}
|
||||
|
||||
if ss.VPN == vpn.OpenVPN {
|
||||
@@ -164,8 +195,7 @@ func getLocationFilterChoices(vpnServiceProvider string,
|
||||
|
||||
// validateServerFilters validates filters against the choices given as arguments.
|
||||
// Set an argument to nil to pass the check for a particular filter.
|
||||
func validateServerFilters(settings ServerSelection, filterChoices models.FilterChoices,
|
||||
vpnServiceProvider string) (err error) {
|
||||
func validateServerFilters(settings ServerSelection, filterChoices models.FilterChoices) (err error) {
|
||||
err = validate.AreAllOneOfCaseInsensitive(settings.Countries, filterChoices.Countries)
|
||||
if err != nil {
|
||||
return fmt.Errorf("%w: %w", ErrCountryNotValid, err)
|
||||
@@ -191,20 +221,6 @@ func validateServerFilters(settings ServerSelection, filterChoices models.Filter
|
||||
return fmt.Errorf("%w: %w", ErrHostnameNotValid, err)
|
||||
}
|
||||
|
||||
if vpnServiceProvider == providers.Custom {
|
||||
switch len(settings.Names) {
|
||||
case 0:
|
||||
case 1:
|
||||
// Allow a single name to be specified for the custom provider in case
|
||||
// the user wants to use VPN server side port forwarding with PIA
|
||||
// which requires a server name for TLS verification.
|
||||
filterChoices.Names = settings.Names
|
||||
default:
|
||||
return fmt.Errorf("%w: %d names specified instead of "+
|
||||
"0 or 1 for the custom provider",
|
||||
ErrNameNotValid, len(settings.Names))
|
||||
}
|
||||
}
|
||||
err = validate.AreAllOneOfCaseInsensitive(settings.Names, filterChoices.Names)
|
||||
if err != nil {
|
||||
return fmt.Errorf("%w: %w", ErrNameNotValid, err)
|
||||
@@ -218,44 +234,6 @@ func validateServerFilters(settings ServerSelection, filterChoices models.Filter
|
||||
return nil
|
||||
}
|
||||
|
||||
func validateSubscriptionTierFilters(settings ServerSelection, vpnServiceProvider string) error {
|
||||
switch {
|
||||
case *settings.FreeOnly &&
|
||||
!helpers.IsOneOf(vpnServiceProvider, providers.Protonvpn, providers.VPNUnlimited):
|
||||
return fmt.Errorf("%w", ErrFreeOnlyNotSupported)
|
||||
case *settings.PremiumOnly &&
|
||||
!helpers.IsOneOf(vpnServiceProvider, providers.VPNSecure):
|
||||
return fmt.Errorf("%w", ErrPremiumOnlyNotSupported)
|
||||
case *settings.FreeOnly && *settings.PremiumOnly:
|
||||
return fmt.Errorf("%w", ErrFreePremiumBothSet)
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
func validateFeatureFilters(settings ServerSelection, vpnServiceProvider string) error {
|
||||
switch {
|
||||
case *settings.OwnedOnly && vpnServiceProvider != providers.Mullvad:
|
||||
return fmt.Errorf("%w", ErrOwnedOnlyNotSupported)
|
||||
case vpnServiceProvider == providers.Protonvpn && *settings.FreeOnly && *settings.PortForwardOnly:
|
||||
return fmt.Errorf("%w: together with free only filter", ErrPortForwardOnlyNotSupported)
|
||||
case *settings.StreamOnly &&
|
||||
!helpers.IsOneOf(vpnServiceProvider, providers.Protonvpn, providers.VPNUnlimited):
|
||||
return fmt.Errorf("%w", ErrStreamOnlyNotSupported)
|
||||
case *settings.MultiHopOnly && vpnServiceProvider != providers.Surfshark:
|
||||
return fmt.Errorf("%w", ErrMultiHopOnlyNotSupported)
|
||||
case *settings.PortForwardOnly &&
|
||||
!helpers.IsOneOf(vpnServiceProvider, providers.PrivateInternetAccess, providers.Protonvpn):
|
||||
return fmt.Errorf("%w", ErrPortForwardOnlyNotSupported)
|
||||
case *settings.SecureCoreOnly && vpnServiceProvider != providers.Protonvpn:
|
||||
return fmt.Errorf("%w", ErrSecureCoreOnlyNotSupported)
|
||||
case *settings.TorOnly && vpnServiceProvider != providers.Protonvpn:
|
||||
return fmt.Errorf("%w", ErrTorOnlyNotSupported)
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
func (ss *ServerSelection) copy() (copied ServerSelection) {
|
||||
return ServerSelection{
|
||||
VPN: ss.VPN,
|
||||
@@ -272,8 +250,6 @@ func (ss *ServerSelection) copy() (copied ServerSelection) {
|
||||
FreeOnly: gosettings.CopyPointer(ss.FreeOnly),
|
||||
PremiumOnly: gosettings.CopyPointer(ss.PremiumOnly),
|
||||
StreamOnly: gosettings.CopyPointer(ss.StreamOnly),
|
||||
SecureCoreOnly: gosettings.CopyPointer(ss.SecureCoreOnly),
|
||||
TorOnly: gosettings.CopyPointer(ss.TorOnly),
|
||||
PortForwardOnly: gosettings.CopyPointer(ss.PortForwardOnly),
|
||||
MultiHopOnly: gosettings.CopyPointer(ss.MultiHopOnly),
|
||||
OpenVPN: ss.OpenVPN.copy(),
|
||||
@@ -281,8 +257,30 @@ func (ss *ServerSelection) copy() (copied ServerSelection) {
|
||||
}
|
||||
}
|
||||
|
||||
func (ss *ServerSelection) mergeWith(other ServerSelection) {
|
||||
ss.VPN = gosettings.MergeWithString(ss.VPN, other.VPN)
|
||||
ss.TargetIP = gosettings.MergeWithValidator(ss.TargetIP, other.TargetIP)
|
||||
ss.Countries = gosettings.MergeWithSlice(ss.Countries, other.Countries)
|
||||
ss.Categories = gosettings.MergeWithSlice(ss.Categories, other.Categories)
|
||||
ss.Regions = gosettings.MergeWithSlice(ss.Regions, other.Regions)
|
||||
ss.Cities = gosettings.MergeWithSlice(ss.Cities, other.Cities)
|
||||
ss.ISPs = gosettings.MergeWithSlice(ss.ISPs, other.ISPs)
|
||||
ss.Hostnames = gosettings.MergeWithSlice(ss.Hostnames, other.Hostnames)
|
||||
ss.Names = gosettings.MergeWithSlice(ss.Names, other.Names)
|
||||
ss.Numbers = gosettings.MergeWithSlice(ss.Numbers, other.Numbers)
|
||||
ss.OwnedOnly = gosettings.MergeWithPointer(ss.OwnedOnly, other.OwnedOnly)
|
||||
ss.FreeOnly = gosettings.MergeWithPointer(ss.FreeOnly, other.FreeOnly)
|
||||
ss.PremiumOnly = gosettings.MergeWithPointer(ss.PremiumOnly, other.PremiumOnly)
|
||||
ss.StreamOnly = gosettings.MergeWithPointer(ss.StreamOnly, other.StreamOnly)
|
||||
ss.MultiHopOnly = gosettings.MergeWithPointer(ss.MultiHopOnly, other.MultiHopOnly)
|
||||
ss.PortForwardOnly = gosettings.MergeWithPointer(ss.PortForwardOnly, other.PortForwardOnly)
|
||||
|
||||
ss.OpenVPN.mergeWith(other.OpenVPN)
|
||||
ss.Wireguard.mergeWith(other.Wireguard)
|
||||
}
|
||||
|
||||
func (ss *ServerSelection) overrideWith(other ServerSelection) {
|
||||
ss.VPN = gosettings.OverrideWithComparable(ss.VPN, other.VPN)
|
||||
ss.VPN = gosettings.OverrideWithString(ss.VPN, other.VPN)
|
||||
ss.TargetIP = gosettings.OverrideWithValidator(ss.TargetIP, other.TargetIP)
|
||||
ss.Countries = gosettings.OverrideWithSlice(ss.Countries, other.Countries)
|
||||
ss.Categories = gosettings.OverrideWithSlice(ss.Categories, other.Categories)
|
||||
@@ -296,30 +294,21 @@ func (ss *ServerSelection) overrideWith(other ServerSelection) {
|
||||
ss.FreeOnly = gosettings.OverrideWithPointer(ss.FreeOnly, other.FreeOnly)
|
||||
ss.PremiumOnly = gosettings.OverrideWithPointer(ss.PremiumOnly, other.PremiumOnly)
|
||||
ss.StreamOnly = gosettings.OverrideWithPointer(ss.StreamOnly, other.StreamOnly)
|
||||
ss.SecureCoreOnly = gosettings.OverrideWithPointer(ss.SecureCoreOnly, other.SecureCoreOnly)
|
||||
ss.TorOnly = gosettings.OverrideWithPointer(ss.TorOnly, other.TorOnly)
|
||||
ss.MultiHopOnly = gosettings.OverrideWithPointer(ss.MultiHopOnly, other.MultiHopOnly)
|
||||
ss.PortForwardOnly = gosettings.OverrideWithPointer(ss.PortForwardOnly, other.PortForwardOnly)
|
||||
ss.OpenVPN.overrideWith(other.OpenVPN)
|
||||
ss.Wireguard.overrideWith(other.Wireguard)
|
||||
}
|
||||
|
||||
func (ss *ServerSelection) setDefaults(vpnProvider string, portForwardingEnabled bool) {
|
||||
ss.VPN = gosettings.DefaultComparable(ss.VPN, vpn.OpenVPN)
|
||||
func (ss *ServerSelection) setDefaults(vpnProvider string) {
|
||||
ss.VPN = gosettings.DefaultString(ss.VPN, vpn.OpenVPN)
|
||||
ss.TargetIP = gosettings.DefaultValidator(ss.TargetIP, netip.IPv4Unspecified())
|
||||
ss.OwnedOnly = gosettings.DefaultPointer(ss.OwnedOnly, false)
|
||||
ss.FreeOnly = gosettings.DefaultPointer(ss.FreeOnly, false)
|
||||
ss.PremiumOnly = gosettings.DefaultPointer(ss.PremiumOnly, false)
|
||||
ss.StreamOnly = gosettings.DefaultPointer(ss.StreamOnly, false)
|
||||
ss.SecureCoreOnly = gosettings.DefaultPointer(ss.SecureCoreOnly, false)
|
||||
ss.TorOnly = gosettings.DefaultPointer(ss.TorOnly, false)
|
||||
ss.MultiHopOnly = gosettings.DefaultPointer(ss.MultiHopOnly, false)
|
||||
defaultPortForwardOnly := false
|
||||
if portForwardingEnabled && helpers.IsOneOf(vpnProvider,
|
||||
providers.PrivateInternetAccess, providers.Protonvpn) {
|
||||
defaultPortForwardOnly = true
|
||||
}
|
||||
ss.PortForwardOnly = gosettings.DefaultPointer(ss.PortForwardOnly, defaultPortForwardOnly)
|
||||
ss.PortForwardOnly = gosettings.DefaultPointer(ss.PortForwardOnly, false)
|
||||
ss.OpenVPN.setDefaults(vpnProvider)
|
||||
ss.Wireguard.setDefaults()
|
||||
}
|
||||
@@ -386,14 +375,6 @@ func (ss ServerSelection) toLinesNode() (node *gotree.Node) {
|
||||
node.Appendf("Stream only servers: yes")
|
||||
}
|
||||
|
||||
if *ss.SecureCoreOnly {
|
||||
node.Appendf("Secure Core only servers: yes")
|
||||
}
|
||||
|
||||
if *ss.TorOnly {
|
||||
node.Appendf("Tor only servers: yes")
|
||||
}
|
||||
|
||||
if *ss.MultiHopOnly {
|
||||
node.Appendf("Multi-hop only servers: yes")
|
||||
}
|
||||
@@ -414,95 +395,6 @@ func (ss ServerSelection) toLinesNode() (node *gotree.Node) {
|
||||
// WithDefaults is a shorthand using setDefaults.
|
||||
// It's used in unit tests in other packages.
|
||||
func (ss ServerSelection) WithDefaults(provider string) ServerSelection {
|
||||
const portForwardingEnabled = false
|
||||
ss.setDefaults(provider, portForwardingEnabled)
|
||||
ss.setDefaults(provider)
|
||||
return ss
|
||||
}
|
||||
|
||||
func (ss *ServerSelection) read(r *reader.Reader,
|
||||
vpnProvider, vpnType string) (err error) {
|
||||
ss.VPN = vpnType
|
||||
|
||||
ss.TargetIP, err = r.NetipAddr("OPENVPN_ENDPOINT_IP",
|
||||
reader.RetroKeys("OPENVPN_TARGET_IP", "VPN_ENDPOINT_IP"))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
countriesRetroKeys := []string{"COUNTRY"}
|
||||
if vpnProvider == providers.Cyberghost {
|
||||
countriesRetroKeys = append(countriesRetroKeys, "REGION")
|
||||
}
|
||||
ss.Countries = r.CSV("SERVER_COUNTRIES", reader.RetroKeys(countriesRetroKeys...))
|
||||
|
||||
ss.Regions = r.CSV("SERVER_REGIONS", reader.RetroKeys("REGION"))
|
||||
ss.Cities = r.CSV("SERVER_CITIES", reader.RetroKeys("CITY"))
|
||||
ss.ISPs = r.CSV("ISP")
|
||||
ss.Hostnames = r.CSV("SERVER_HOSTNAMES", reader.RetroKeys("SERVER_HOSTNAME"))
|
||||
ss.Names = r.CSV("SERVER_NAMES", reader.RetroKeys("SERVER_NAME"))
|
||||
ss.Numbers, err = r.CSVUint16("SERVER_NUMBER")
|
||||
ss.Categories = r.CSV("SERVER_CATEGORIES")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Mullvad only
|
||||
ss.OwnedOnly, err = r.BoolPtr("OWNED_ONLY", reader.RetroKeys("OWNED"))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// VPNUnlimited and ProtonVPN only
|
||||
ss.FreeOnly, err = r.BoolPtr("FREE_ONLY")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// VPNSecure only
|
||||
ss.PremiumOnly, err = r.BoolPtr("PREMIUM_ONLY")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Surfshark only
|
||||
ss.MultiHopOnly, err = r.BoolPtr("MULTIHOP_ONLY")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// VPNUnlimited and ProtonVPN only
|
||||
ss.StreamOnly, err = r.BoolPtr("STREAM_ONLY")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// ProtonVPN only
|
||||
ss.SecureCoreOnly, err = r.BoolPtr("SECURE_CORE_ONLY")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// ProtonVPN only
|
||||
ss.TorOnly, err = r.BoolPtr("TOR_ONLY")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// PIA and ProtonVPN only
|
||||
ss.PortForwardOnly, err = r.BoolPtr("PORT_FORWARD_ONLY")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = ss.OpenVPN.read(r)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = ss.Wireguard.read(r)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -2,14 +2,12 @@ package settings
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/netip"
|
||||
|
||||
"github.com/qdm12/gluetun/internal/configuration/settings/helpers"
|
||||
"github.com/qdm12/gluetun/internal/constants/providers"
|
||||
"github.com/qdm12/gluetun/internal/constants/vpn"
|
||||
"github.com/qdm12/gluetun/internal/models"
|
||||
"github.com/qdm12/gluetun/internal/pprof"
|
||||
"github.com/qdm12/gosettings/reader"
|
||||
"github.com/qdm12/gotree"
|
||||
)
|
||||
|
||||
@@ -83,6 +81,22 @@ func (s *Settings) copy() (copied Settings) {
|
||||
}
|
||||
}
|
||||
|
||||
func (s *Settings) MergeWith(other Settings) {
|
||||
s.ControlServer.mergeWith(other.ControlServer)
|
||||
s.DNS.mergeWith(other.DNS)
|
||||
s.Firewall.mergeWith(other.Firewall)
|
||||
s.Health.MergeWith(other.Health)
|
||||
s.HTTPProxy.mergeWith(other.HTTPProxy)
|
||||
s.Log.mergeWith(other.Log)
|
||||
s.PublicIP.mergeWith(other.PublicIP)
|
||||
s.Shadowsocks.mergeWith(other.Shadowsocks)
|
||||
s.System.mergeWith(other.System)
|
||||
s.Updater.mergeWith(other.Updater)
|
||||
s.Version.mergeWith(other.Version)
|
||||
s.VPN.mergeWith(other.VPN)
|
||||
s.Pprof.MergeWith(other.Pprof)
|
||||
}
|
||||
|
||||
func (s *Settings) OverrideWith(other Settings,
|
||||
storage Storage, ipv6Supported bool) (err error) {
|
||||
patchedSettings := s.copy()
|
||||
@@ -119,7 +133,7 @@ func (s *Settings) SetDefaults() {
|
||||
s.System.setDefaults()
|
||||
s.Version.setDefaults()
|
||||
s.VPN.setDefaults()
|
||||
s.Updater.SetDefaults(s.VPN.Provider.Name)
|
||||
s.Updater.SetDefaults(*s.VPN.Provider.Name)
|
||||
s.Pprof.SetDefaults()
|
||||
}
|
||||
|
||||
@@ -148,16 +162,16 @@ func (s Settings) toLinesNode() (node *gotree.Node) {
|
||||
}
|
||||
|
||||
func (s Settings) Warnings() (warnings []string) {
|
||||
if s.VPN.Provider.Name == providers.HideMyAss {
|
||||
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) &&
|
||||
if helpers.IsOneOf(*s.VPN.Provider.Name, providers.SlickVPN) &&
|
||||
s.VPN.Type == vpn.OpenVPN {
|
||||
warnings = append(warnings, "OpenVPN 2.5 and 2.6 use OpenSSL 3 "+
|
||||
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 "+
|
||||
*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. "+
|
||||
@@ -165,41 +179,5 @@ func (s Settings) Warnings() (warnings []string) {
|
||||
"by creating an issue, attaching the new certificate and we will update Gluetun.")
|
||||
}
|
||||
|
||||
// TODO remove in v4
|
||||
if s.DNS.ServerAddress.Unmap().Compare(netip.AddrFrom4([4]byte{127, 0, 0, 1})) != 0 {
|
||||
warnings = append(warnings, "DNS address is set to "+s.DNS.ServerAddress.String()+
|
||||
" so the DNS over TLS (DoT) server will not be used."+
|
||||
" The default value changed to 127.0.0.1 so it uses the internal DoT serves."+
|
||||
" If the DoT server fails to start, the IPv4 address of the first plaintext DNS server"+
|
||||
" corresponding to the first DoT provider chosen is used.")
|
||||
}
|
||||
|
||||
return warnings
|
||||
}
|
||||
|
||||
func (s *Settings) Read(r *reader.Reader) (err error) {
|
||||
readFunctions := map[string]func(r *reader.Reader) error{
|
||||
"control server": s.ControlServer.read,
|
||||
"DNS": s.DNS.read,
|
||||
"firewall": s.Firewall.read,
|
||||
"health": s.Health.Read,
|
||||
"http proxy": s.HTTPProxy.read,
|
||||
"log": s.Log.read,
|
||||
"public ip": s.PublicIP.read,
|
||||
"shadowsocks": s.Shadowsocks.read,
|
||||
"system": s.System.read,
|
||||
"updater": s.Updater.read,
|
||||
"version": s.Version.read,
|
||||
"VPN": s.VPN.read,
|
||||
"profiling": s.Pprof.Read,
|
||||
}
|
||||
|
||||
for name, read := range readFunctions {
|
||||
err = read(r)
|
||||
if err != nil {
|
||||
return fmt.Errorf("reading %s settings: %w", name, err)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -30,7 +30,7 @@ func Test_Settings_String(t *testing.T) {
|
||||
| | ├── Protocol: UDP
|
||||
| | └── Private Internet Access encryption preset: strong
|
||||
| └── OpenVPN settings:
|
||||
| ├── OpenVPN version: 2.6
|
||||
| ├── OpenVPN version: 2.5
|
||||
| ├── User: [not set]
|
||||
| ├── Password: [not set]
|
||||
| ├── Private Internet Access encryption preset: strong
|
||||
@@ -78,8 +78,7 @@ func Test_Settings_String(t *testing.T) {
|
||||
| └── Enabled: no
|
||||
├── Control server settings:
|
||||
| ├── Listening address: :8000
|
||||
| ├── Logging: yes
|
||||
| └── Authentication file path: /gluetun/auth/config.toml
|
||||
| └── Logging: yes
|
||||
├── OS Alpine settings:
|
||||
| ├── Process UID: 1000
|
||||
| └── Process GID: 1000
|
||||
|
||||
@@ -1,10 +1,7 @@
|
||||
package settings
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/qdm12/gosettings"
|
||||
"github.com/qdm12/gosettings/reader"
|
||||
"github.com/qdm12/gotree"
|
||||
"github.com/qdm12/ss-server/pkg/tcpudp"
|
||||
)
|
||||
@@ -29,6 +26,13 @@ func (s *Shadowsocks) copy() (copied Shadowsocks) {
|
||||
}
|
||||
}
|
||||
|
||||
// mergeWith merges the other settings into any
|
||||
// unset field of the receiver settings object.
|
||||
func (s *Shadowsocks) mergeWith(other Shadowsocks) {
|
||||
s.Enabled = gosettings.MergeWithPointer(s.Enabled, other.Enabled)
|
||||
s.Settings = s.Settings.MergeWith(other.Settings)
|
||||
}
|
||||
|
||||
// overrideWith overrides fields of the receiver
|
||||
// settings object with any field set in the other
|
||||
// settings.
|
||||
@@ -55,44 +59,10 @@ func (s Shadowsocks) toLinesNode() (node *gotree.Node) {
|
||||
}
|
||||
|
||||
// TODO have ToLinesNode in qdm12/ss-server
|
||||
node.Appendf("Listening address: %s", *s.Settings.Address)
|
||||
node.Appendf("Cipher: %s", s.Settings.CipherName)
|
||||
node.Appendf("Password: %s", gosettings.ObfuscateKey(*s.Settings.Password))
|
||||
node.Appendf("Log addresses: %s", gosettings.BoolToYesNo(s.Settings.LogAddresses))
|
||||
node.Appendf("Listening address: %s", *s.Address)
|
||||
node.Appendf("Cipher: %s", s.CipherName)
|
||||
node.Appendf("Password: %s", gosettings.ObfuscateKey(*s.Password))
|
||||
node.Appendf("Log addresses: %s", gosettings.BoolToYesNo(s.LogAddresses))
|
||||
|
||||
return node
|
||||
}
|
||||
|
||||
func (s *Shadowsocks) read(r *reader.Reader) (err error) {
|
||||
s.Enabled, err = r.BoolPtr("SHADOWSOCKS")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
s.Settings.Address, err = readShadowsocksAddress(r)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
s.Settings.LogAddresses, err = r.BoolPtr("SHADOWSOCKS_LOG")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
s.Settings.CipherName = r.String("SHADOWSOCKS_CIPHER",
|
||||
reader.RetroKeys("SHADOWSOCKS_METHOD"))
|
||||
s.Settings.Password = r.Get("SHADOWSOCKS_PASSWORD",
|
||||
reader.ForceLowercase(false))
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func readShadowsocksAddress(r *reader.Reader) (address *string, err error) {
|
||||
const currentKey = "SHADOWSOCKS_LISTENING_ADDRESS"
|
||||
port, err := r.Uint16Ptr("SHADOWSOCKS_PORT", reader.IsRetro(currentKey)) // retro-compatibility
|
||||
if err != nil {
|
||||
return nil, err
|
||||
} else if port != nil {
|
||||
return ptrTo(fmt.Sprintf(":%d", *port)), nil
|
||||
}
|
||||
|
||||
return r.Get(currentKey), nil
|
||||
}
|
||||
|
||||
@@ -2,7 +2,6 @@ package settings
|
||||
|
||||
import (
|
||||
"github.com/qdm12/gosettings"
|
||||
"github.com/qdm12/gosettings/reader"
|
||||
"github.com/qdm12/gotree"
|
||||
)
|
||||
|
||||
@@ -26,10 +25,16 @@ func (s *System) copy() (copied System) {
|
||||
}
|
||||
}
|
||||
|
||||
func (s *System) mergeWith(other System) {
|
||||
s.PUID = gosettings.MergeWithPointer(s.PUID, other.PUID)
|
||||
s.PGID = gosettings.MergeWithPointer(s.PGID, other.PGID)
|
||||
s.Timezone = gosettings.MergeWithString(s.Timezone, other.Timezone)
|
||||
}
|
||||
|
||||
func (s *System) overrideWith(other System) {
|
||||
s.PUID = gosettings.OverrideWithPointer(s.PUID, other.PUID)
|
||||
s.PGID = gosettings.OverrideWithPointer(s.PGID, other.PGID)
|
||||
s.Timezone = gosettings.OverrideWithComparable(s.Timezone, other.Timezone)
|
||||
s.Timezone = gosettings.OverrideWithString(s.Timezone, other.Timezone)
|
||||
}
|
||||
|
||||
func (s *System) setDefaults() {
|
||||
@@ -54,18 +59,3 @@ func (s System) toLinesNode() (node *gotree.Node) {
|
||||
|
||||
return node
|
||||
}
|
||||
|
||||
func (s *System) read(r *reader.Reader) (err error) {
|
||||
s.PUID, err = r.Uint32Ptr("PUID", reader.RetroKeys("UID"))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
s.PGID, err = r.Uint32Ptr("PGID", reader.RetroKeys("GID"))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
s.Timezone = r.String("TZ")
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -8,7 +8,6 @@ import (
|
||||
"github.com/qdm12/dns/pkg/provider"
|
||||
"github.com/qdm12/dns/pkg/unbound"
|
||||
"github.com/qdm12/gosettings"
|
||||
"github.com/qdm12/gosettings/reader"
|
||||
"github.com/qdm12/gotree"
|
||||
)
|
||||
|
||||
@@ -50,7 +49,7 @@ func (u *Unbound) setDefaults() {
|
||||
}
|
||||
}
|
||||
|
||||
u.Username = gosettings.DefaultComparable(u.Username, "root")
|
||||
u.Username = gosettings.DefaultString(u.Username, "root")
|
||||
}
|
||||
|
||||
var (
|
||||
@@ -106,6 +105,17 @@ func (u Unbound) copy() (copied Unbound) {
|
||||
}
|
||||
}
|
||||
|
||||
func (u *Unbound) mergeWith(other Unbound) {
|
||||
u.Providers = gosettings.MergeWithSlice(u.Providers, other.Providers)
|
||||
u.Caching = gosettings.MergeWithPointer(u.Caching, other.Caching)
|
||||
u.IPv6 = gosettings.MergeWithPointer(u.IPv6, other.IPv6)
|
||||
u.VerbosityLevel = gosettings.MergeWithPointer(u.VerbosityLevel, other.VerbosityLevel)
|
||||
u.VerbosityDetailsLevel = gosettings.MergeWithPointer(u.VerbosityDetailsLevel, other.VerbosityDetailsLevel)
|
||||
u.ValidationLogLevel = gosettings.MergeWithPointer(u.ValidationLogLevel, other.ValidationLogLevel)
|
||||
u.Username = gosettings.MergeWithString(u.Username, other.Username)
|
||||
u.Allowed = gosettings.MergeWithSlice(u.Allowed, other.Allowed)
|
||||
}
|
||||
|
||||
func (u *Unbound) overrideWith(other Unbound) {
|
||||
u.Providers = gosettings.OverrideWithSlice(u.Providers, other.Providers)
|
||||
u.Caching = gosettings.OverrideWithPointer(u.Caching, other.Caching)
|
||||
@@ -113,7 +123,7 @@ func (u *Unbound) overrideWith(other Unbound) {
|
||||
u.VerbosityLevel = gosettings.OverrideWithPointer(u.VerbosityLevel, other.VerbosityLevel)
|
||||
u.VerbosityDetailsLevel = gosettings.OverrideWithPointer(u.VerbosityDetailsLevel, other.VerbosityDetailsLevel)
|
||||
u.ValidationLogLevel = gosettings.OverrideWithPointer(u.ValidationLogLevel, other.ValidationLogLevel)
|
||||
u.Username = gosettings.OverrideWithComparable(u.Username, other.Username)
|
||||
u.Username = gosettings.OverrideWithString(u.Username, other.Username)
|
||||
u.Allowed = gosettings.OverrideWithSlice(u.Allowed, other.Allowed)
|
||||
}
|
||||
|
||||
@@ -190,34 +200,3 @@ func (u Unbound) toLinesNode() (node *gotree.Node) {
|
||||
|
||||
return node
|
||||
}
|
||||
|
||||
func (u *Unbound) read(reader *reader.Reader) (err error) {
|
||||
u.Providers = reader.CSV("DOT_PROVIDERS")
|
||||
|
||||
u.Caching, err = reader.BoolPtr("DOT_CACHING")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
u.IPv6, err = reader.BoolPtr("DOT_IPV6")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
u.VerbosityLevel, err = reader.Uint8Ptr("DOT_VERBOSITY")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
u.VerbosityDetailsLevel, err = reader.Uint8Ptr("DOT_VERBOSITY_DETAILS")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
u.ValidationLogLevel, err = reader.Uint8Ptr("DOT_VALIDATION_LOGLEVEL")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -7,7 +7,6 @@ import (
|
||||
|
||||
"github.com/qdm12/gluetun/internal/constants/providers"
|
||||
"github.com/qdm12/gosettings"
|
||||
"github.com/qdm12/gosettings/reader"
|
||||
"github.com/qdm12/gosettings/validate"
|
||||
"github.com/qdm12/gotree"
|
||||
)
|
||||
@@ -65,19 +64,28 @@ func (u *Updater) copy() (copied Updater) {
|
||||
}
|
||||
}
|
||||
|
||||
// mergeWith merges the other settings into any
|
||||
// unset field of the receiver settings object.
|
||||
func (u *Updater) mergeWith(other Updater) {
|
||||
u.Period = gosettings.MergeWithPointer(u.Period, other.Period)
|
||||
u.DNSAddress = gosettings.MergeWithString(u.DNSAddress, other.DNSAddress)
|
||||
u.MinRatio = gosettings.MergeWithNumber(u.MinRatio, other.MinRatio)
|
||||
u.Providers = gosettings.MergeWithSlice(u.Providers, other.Providers)
|
||||
}
|
||||
|
||||
// overrideWith overrides fields of the receiver
|
||||
// settings object with any field set in the other
|
||||
// settings.
|
||||
func (u *Updater) overrideWith(other Updater) {
|
||||
u.Period = gosettings.OverrideWithPointer(u.Period, other.Period)
|
||||
u.DNSAddress = gosettings.OverrideWithComparable(u.DNSAddress, other.DNSAddress)
|
||||
u.MinRatio = gosettings.OverrideWithComparable(u.MinRatio, other.MinRatio)
|
||||
u.DNSAddress = gosettings.OverrideWithString(u.DNSAddress, other.DNSAddress)
|
||||
u.MinRatio = gosettings.OverrideWithNumber(u.MinRatio, other.MinRatio)
|
||||
u.Providers = gosettings.OverrideWithSlice(u.Providers, other.Providers)
|
||||
}
|
||||
|
||||
func (u *Updater) SetDefaults(vpnProvider string) {
|
||||
u.Period = gosettings.DefaultPointer(u.Period, 0)
|
||||
u.DNSAddress = gosettings.DefaultComparable(u.DNSAddress, "1.1.1.1:53")
|
||||
u.DNSAddress = gosettings.DefaultString(u.DNSAddress, "1.1.1.1:53")
|
||||
|
||||
if u.MinRatio == 0 {
|
||||
const defaultMinRatio = 0.8
|
||||
@@ -106,33 +114,3 @@ func (u Updater) toLinesNode() (node *gotree.Node) {
|
||||
|
||||
return node
|
||||
}
|
||||
|
||||
func (u *Updater) read(r *reader.Reader) (err error) {
|
||||
u.Period, err = r.DurationPtr("UPDATER_PERIOD")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
u.DNSAddress, err = readUpdaterDNSAddress()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
u.MinRatio, err = r.Float64("UPDATER_MIN_RATIO")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
u.Providers = r.CSV("UPDATER_VPN_SERVICE_PROVIDERS")
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func readUpdaterDNSAddress() (address string, err error) {
|
||||
// TODO this is currently using Cloudflare in
|
||||
// plaintext to not be blocked by DNS over TLS by default.
|
||||
// If a plaintext address is set in the DNS settings, this one will be used.
|
||||
// use custom future encrypted DNS written in Go without blocking
|
||||
// as it's too much trouble to start another parallel unbound instance for now.
|
||||
return "", nil
|
||||
}
|
||||
|
||||
@@ -2,7 +2,6 @@ package settings
|
||||
|
||||
import (
|
||||
"github.com/qdm12/gosettings"
|
||||
"github.com/qdm12/gosettings/reader"
|
||||
"github.com/qdm12/gotree"
|
||||
)
|
||||
|
||||
@@ -24,6 +23,12 @@ func (v *Version) copy() (copied Version) {
|
||||
}
|
||||
}
|
||||
|
||||
// mergeWith merges the other settings into any
|
||||
// unset field of the receiver settings object.
|
||||
func (v *Version) mergeWith(other Version) {
|
||||
v.Enabled = gosettings.MergeWithPointer(v.Enabled, other.Enabled)
|
||||
}
|
||||
|
||||
// overrideWith overrides fields of the receiver
|
||||
// settings object with any field set in the other
|
||||
// settings.
|
||||
@@ -46,12 +51,3 @@ func (v Version) toLinesNode() (node *gotree.Node) {
|
||||
|
||||
return node
|
||||
}
|
||||
|
||||
func (v *Version) read(r *reader.Reader) (err error) {
|
||||
v.Enabled, err = r.BoolPtr("VERSION_INFORMATION")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -5,7 +5,6 @@ import (
|
||||
|
||||
"github.com/qdm12/gluetun/internal/constants/vpn"
|
||||
"github.com/qdm12/gosettings"
|
||||
"github.com/qdm12/gosettings/reader"
|
||||
"github.com/qdm12/gosettings/validate"
|
||||
"github.com/qdm12/gotree"
|
||||
)
|
||||
@@ -34,12 +33,12 @@ func (v *VPN) Validate(storage Storage, ipv6Supported bool) (err error) {
|
||||
}
|
||||
|
||||
if v.Type == vpn.OpenVPN {
|
||||
err := v.OpenVPN.validate(v.Provider.Name)
|
||||
err := v.OpenVPN.validate(*v.Provider.Name)
|
||||
if err != nil {
|
||||
return fmt.Errorf("OpenVPN settings: %w", err)
|
||||
}
|
||||
} else {
|
||||
err := v.Wireguard.validate(v.Provider.Name, ipv6Supported)
|
||||
err := v.Wireguard.validate(*v.Provider.Name, ipv6Supported)
|
||||
if err != nil {
|
||||
return fmt.Errorf("Wireguard settings: %w", err)
|
||||
}
|
||||
@@ -57,18 +56,25 @@ func (v *VPN) Copy() (copied VPN) {
|
||||
}
|
||||
}
|
||||
|
||||
func (v *VPN) mergeWith(other VPN) {
|
||||
v.Type = gosettings.MergeWithString(v.Type, other.Type)
|
||||
v.Provider.mergeWith(other.Provider)
|
||||
v.OpenVPN.mergeWith(other.OpenVPN)
|
||||
v.Wireguard.mergeWith(other.Wireguard)
|
||||
}
|
||||
|
||||
func (v *VPN) OverrideWith(other VPN) {
|
||||
v.Type = gosettings.OverrideWithComparable(v.Type, other.Type)
|
||||
v.Type = gosettings.OverrideWithString(v.Type, other.Type)
|
||||
v.Provider.overrideWith(other.Provider)
|
||||
v.OpenVPN.overrideWith(other.OpenVPN)
|
||||
v.Wireguard.overrideWith(other.Wireguard)
|
||||
}
|
||||
|
||||
func (v *VPN) setDefaults() {
|
||||
v.Type = gosettings.DefaultComparable(v.Type, vpn.OpenVPN)
|
||||
v.Type = gosettings.DefaultString(v.Type, vpn.OpenVPN)
|
||||
v.Provider.setDefaults()
|
||||
v.OpenVPN.setDefaults(v.Provider.Name)
|
||||
v.Wireguard.setDefaults(v.Provider.Name)
|
||||
v.OpenVPN.setDefaults(*v.Provider.Name)
|
||||
v.Wireguard.setDefaults(*v.Provider.Name)
|
||||
}
|
||||
|
||||
func (v VPN) String() string {
|
||||
@@ -88,24 +94,3 @@ func (v VPN) toLinesNode() (node *gotree.Node) {
|
||||
|
||||
return node
|
||||
}
|
||||
|
||||
func (v *VPN) read(r *reader.Reader) (err error) {
|
||||
v.Type = r.String("VPN_TYPE")
|
||||
|
||||
err = v.Provider.read(r, v.Type)
|
||||
if err != nil {
|
||||
return fmt.Errorf("VPN provider: %w", err)
|
||||
}
|
||||
|
||||
err = v.OpenVPN.read(r)
|
||||
if err != nil {
|
||||
return fmt.Errorf("OpenVPN: %w", err)
|
||||
}
|
||||
|
||||
err = v.Wireguard.read(r)
|
||||
if err != nil {
|
||||
return fmt.Errorf("wireguard: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -4,13 +4,10 @@ import (
|
||||
"fmt"
|
||||
"net/netip"
|
||||
"regexp"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/qdm12/gluetun/internal/configuration/settings/helpers"
|
||||
"github.com/qdm12/gluetun/internal/constants/providers"
|
||||
"github.com/qdm12/gosettings"
|
||||
"github.com/qdm12/gosettings/reader"
|
||||
"github.com/qdm12/gosettings/validate"
|
||||
"github.com/qdm12/gotree"
|
||||
"golang.zx2c4.com/wireguard/wgctrl/wgtypes"
|
||||
@@ -35,8 +32,7 @@ type Wireguard struct {
|
||||
// Interface is the name of the Wireguard interface
|
||||
// to create. It cannot be the empty string in the
|
||||
// internal state.
|
||||
Interface string `json:"interface"`
|
||||
PersistentKeepaliveInterval *time.Duration `json:"persistent_keep_alive_interval"`
|
||||
Interface string `json:"interface"`
|
||||
// Maximum Transmission Unit (MTU) of the Wireguard interface.
|
||||
// It cannot be zero in the internal state, and defaults to
|
||||
// 1400. Note it is not the wireguard-go MTU default of 1420
|
||||
@@ -58,11 +54,9 @@ func (w Wireguard) validate(vpnProvider string, ipv6Supported bool) (err error)
|
||||
if !helpers.IsOneOf(vpnProvider,
|
||||
providers.Airvpn,
|
||||
providers.Custom,
|
||||
providers.Fastestvpn,
|
||||
providers.Ivpn,
|
||||
providers.Mullvad,
|
||||
providers.Nordvpn,
|
||||
providers.Protonvpn,
|
||||
providers.Surfshark,
|
||||
providers.Windscribe,
|
||||
) {
|
||||
@@ -127,11 +121,6 @@ func (w Wireguard) validate(vpnProvider string, ipv6Supported bool) (err error)
|
||||
}
|
||||
}
|
||||
|
||||
if *w.PersistentKeepaliveInterval < 0 {
|
||||
return fmt.Errorf("%w: %s", ErrWireguardKeepAliveNegative,
|
||||
*w.PersistentKeepaliveInterval)
|
||||
}
|
||||
|
||||
// Validate interface
|
||||
if !regexpInterfaceName.MatchString(w.Interface) {
|
||||
return fmt.Errorf("%w: '%s' does not match regex '%s'",
|
||||
@@ -148,52 +137,53 @@ func (w Wireguard) validate(vpnProvider string, ipv6Supported bool) (err error)
|
||||
|
||||
func (w *Wireguard) copy() (copied Wireguard) {
|
||||
return Wireguard{
|
||||
PrivateKey: gosettings.CopyPointer(w.PrivateKey),
|
||||
PreSharedKey: gosettings.CopyPointer(w.PreSharedKey),
|
||||
Addresses: gosettings.CopySlice(w.Addresses),
|
||||
AllowedIPs: gosettings.CopySlice(w.AllowedIPs),
|
||||
PersistentKeepaliveInterval: gosettings.CopyPointer(w.PersistentKeepaliveInterval),
|
||||
Interface: w.Interface,
|
||||
MTU: w.MTU,
|
||||
Implementation: w.Implementation,
|
||||
PrivateKey: gosettings.CopyPointer(w.PrivateKey),
|
||||
PreSharedKey: gosettings.CopyPointer(w.PreSharedKey),
|
||||
Addresses: gosettings.CopySlice(w.Addresses),
|
||||
AllowedIPs: gosettings.CopySlice(w.AllowedIPs),
|
||||
Interface: w.Interface,
|
||||
MTU: w.MTU,
|
||||
Implementation: w.Implementation,
|
||||
}
|
||||
}
|
||||
|
||||
func (w *Wireguard) mergeWith(other Wireguard) {
|
||||
w.PrivateKey = gosettings.MergeWithPointer(w.PrivateKey, other.PrivateKey)
|
||||
w.PreSharedKey = gosettings.MergeWithPointer(w.PreSharedKey, other.PreSharedKey)
|
||||
w.Addresses = gosettings.MergeWithSlice(w.Addresses, other.Addresses)
|
||||
w.AllowedIPs = gosettings.MergeWithSlice(w.AllowedIPs, other.AllowedIPs)
|
||||
w.Interface = gosettings.MergeWithString(w.Interface, other.Interface)
|
||||
w.MTU = gosettings.MergeWithNumber(w.MTU, other.MTU)
|
||||
w.Implementation = gosettings.MergeWithString(w.Implementation, other.Implementation)
|
||||
}
|
||||
|
||||
func (w *Wireguard) overrideWith(other Wireguard) {
|
||||
w.PrivateKey = gosettings.OverrideWithPointer(w.PrivateKey, other.PrivateKey)
|
||||
w.PreSharedKey = gosettings.OverrideWithPointer(w.PreSharedKey, other.PreSharedKey)
|
||||
w.Addresses = gosettings.OverrideWithSlice(w.Addresses, other.Addresses)
|
||||
w.AllowedIPs = gosettings.OverrideWithSlice(w.AllowedIPs, other.AllowedIPs)
|
||||
w.PersistentKeepaliveInterval = gosettings.OverrideWithPointer(w.PersistentKeepaliveInterval,
|
||||
other.PersistentKeepaliveInterval)
|
||||
w.Interface = gosettings.OverrideWithComparable(w.Interface, other.Interface)
|
||||
w.MTU = gosettings.OverrideWithComparable(w.MTU, other.MTU)
|
||||
w.Implementation = gosettings.OverrideWithComparable(w.Implementation, other.Implementation)
|
||||
w.Interface = gosettings.OverrideWithString(w.Interface, other.Interface)
|
||||
w.MTU = gosettings.OverrideWithNumber(w.MTU, other.MTU)
|
||||
w.Implementation = gosettings.OverrideWithString(w.Implementation, other.Implementation)
|
||||
}
|
||||
|
||||
func (w *Wireguard) setDefaults(vpnProvider string) {
|
||||
w.PrivateKey = gosettings.DefaultPointer(w.PrivateKey, "")
|
||||
w.PreSharedKey = gosettings.DefaultPointer(w.PreSharedKey, "")
|
||||
switch vpnProvider {
|
||||
case providers.Nordvpn:
|
||||
if vpnProvider == providers.Nordvpn {
|
||||
defaultNordVPNAddress := netip.AddrFrom4([4]byte{10, 5, 0, 2})
|
||||
defaultNordVPNPrefix := netip.PrefixFrom(defaultNordVPNAddress, defaultNordVPNAddress.BitLen())
|
||||
w.Addresses = gosettings.DefaultSlice(w.Addresses, []netip.Prefix{defaultNordVPNPrefix})
|
||||
case providers.Protonvpn:
|
||||
defaultAddress := netip.AddrFrom4([4]byte{10, 2, 0, 2})
|
||||
defaultPrefix := netip.PrefixFrom(defaultAddress, defaultAddress.BitLen())
|
||||
w.Addresses = gosettings.DefaultSlice(w.Addresses, []netip.Prefix{defaultPrefix})
|
||||
}
|
||||
defaultAllowedIPs := []netip.Prefix{
|
||||
netip.PrefixFrom(netip.IPv4Unspecified(), 0),
|
||||
netip.PrefixFrom(netip.IPv6Unspecified(), 0),
|
||||
}
|
||||
w.AllowedIPs = gosettings.DefaultSlice(w.AllowedIPs, defaultAllowedIPs)
|
||||
w.PersistentKeepaliveInterval = gosettings.DefaultPointer(w.PersistentKeepaliveInterval, 0)
|
||||
w.Interface = gosettings.DefaultComparable(w.Interface, "wg0")
|
||||
w.Interface = gosettings.DefaultString(w.Interface, "wg0")
|
||||
const defaultMTU = 1400
|
||||
w.MTU = gosettings.DefaultComparable(w.MTU, defaultMTU)
|
||||
w.Implementation = gosettings.DefaultComparable(w.Implementation, "auto")
|
||||
w.MTU = gosettings.DefaultNumber(w.MTU, defaultMTU)
|
||||
w.Implementation = gosettings.DefaultString(w.Implementation, "auto")
|
||||
}
|
||||
|
||||
func (w Wireguard) String() string {
|
||||
@@ -223,10 +213,6 @@ func (w Wireguard) toLinesNode() (node *gotree.Node) {
|
||||
allowedIPsNode.Appendf(allowedIP.String())
|
||||
}
|
||||
|
||||
if *w.PersistentKeepaliveInterval > 0 {
|
||||
node.Appendf("Persistent keepalive interval: %s", w.PersistentKeepaliveInterval)
|
||||
}
|
||||
|
||||
interfaceNode := node.Appendf("Network interface: %s", w.Interface)
|
||||
interfaceNode.Appendf("MTU: %d", w.MTU)
|
||||
|
||||
@@ -236,44 +222,3 @@ func (w Wireguard) toLinesNode() (node *gotree.Node) {
|
||||
|
||||
return node
|
||||
}
|
||||
|
||||
func (w *Wireguard) read(r *reader.Reader) (err error) {
|
||||
w.PrivateKey = r.Get("WIREGUARD_PRIVATE_KEY", reader.ForceLowercase(false))
|
||||
w.PreSharedKey = r.Get("WIREGUARD_PRESHARED_KEY", reader.ForceLowercase(false))
|
||||
w.Interface = r.String("VPN_INTERFACE",
|
||||
reader.RetroKeys("WIREGUARD_INTERFACE"), reader.ForceLowercase(false))
|
||||
w.Implementation = r.String("WIREGUARD_IMPLEMENTATION")
|
||||
|
||||
addressStrings := r.CSV("WIREGUARD_ADDRESSES", reader.RetroKeys("WIREGUARD_ADDRESS"))
|
||||
// WARNING: do not initialize w.Addresses to an empty slice
|
||||
// or the defaults for nordvpn will not work.
|
||||
for _, addressString := range addressStrings {
|
||||
if !strings.ContainsRune(addressString, '/') {
|
||||
addressString += "/32"
|
||||
}
|
||||
addressString = strings.TrimSpace(addressString)
|
||||
address, err := netip.ParsePrefix(addressString)
|
||||
if err != nil {
|
||||
return fmt.Errorf("parsing address: %w", err)
|
||||
}
|
||||
w.Addresses = append(w.Addresses, address)
|
||||
}
|
||||
|
||||
w.AllowedIPs, err = r.CSVNetipPrefixes("WIREGUARD_ALLOWED_IPS")
|
||||
if err != nil {
|
||||
return err // already wrapped
|
||||
}
|
||||
|
||||
w.PersistentKeepaliveInterval, err = r.DurationPtr("WIREGUARD_PERSISTENT_KEEPALIVE_INTERVAL")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
mtuPtr, err := r.Uint16Ptr("WIREGUARD_MTU")
|
||||
if err != nil {
|
||||
return err
|
||||
} else if mtuPtr != nil {
|
||||
w.MTU = *mtuPtr
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -6,7 +6,6 @@ import (
|
||||
|
||||
"github.com/qdm12/gluetun/internal/constants/providers"
|
||||
"github.com/qdm12/gosettings"
|
||||
"github.com/qdm12/gosettings/reader"
|
||||
"github.com/qdm12/gosettings/validate"
|
||||
"github.com/qdm12/gotree"
|
||||
"golang.zx2c4.com/wireguard/wgctrl/wgtypes"
|
||||
@@ -38,9 +37,8 @@ type WireguardSelection struct {
|
||||
func (w WireguardSelection) validate(vpnProvider string) (err error) {
|
||||
// Validate EndpointIP
|
||||
switch vpnProvider {
|
||||
case providers.Airvpn, providers.Fastestvpn, providers.Ivpn,
|
||||
providers.Mullvad, providers.Nordvpn, providers.Protonvpn,
|
||||
providers.Surfshark, providers.Windscribe:
|
||||
case providers.Airvpn, providers.Ivpn, providers.Mullvad,
|
||||
providers.Nordvpn, providers.Surfshark, providers.Windscribe:
|
||||
// endpoint IP addresses are baked in
|
||||
case providers.Custom:
|
||||
if !w.EndpointIP.IsValid() || w.EndpointIP.IsUnspecified() {
|
||||
@@ -57,8 +55,7 @@ func (w WireguardSelection) validate(vpnProvider string) (err error) {
|
||||
return fmt.Errorf("%w", ErrWireguardEndpointPortNotSet)
|
||||
}
|
||||
// EndpointPort cannot be set
|
||||
case providers.Fastestvpn, providers.Nordvpn,
|
||||
providers.Protonvpn, providers.Surfshark:
|
||||
case providers.Surfshark, providers.Nordvpn:
|
||||
if *w.EndpointPort != 0 {
|
||||
return fmt.Errorf("%w", ErrWireguardEndpointPortSet)
|
||||
}
|
||||
@@ -91,7 +88,7 @@ func (w WireguardSelection) validate(vpnProvider string) (err error) {
|
||||
|
||||
// Validate PublicKey
|
||||
switch vpnProvider {
|
||||
case providers.Fastestvpn, providers.Ivpn, providers.Mullvad,
|
||||
case providers.Ivpn, providers.Mullvad,
|
||||
providers.Surfshark, providers.Windscribe:
|
||||
// public keys are baked in
|
||||
case providers.Custom:
|
||||
@@ -119,10 +116,16 @@ func (w *WireguardSelection) copy() (copied WireguardSelection) {
|
||||
}
|
||||
}
|
||||
|
||||
func (w *WireguardSelection) mergeWith(other WireguardSelection) {
|
||||
w.EndpointIP = gosettings.MergeWithValidator(w.EndpointIP, other.EndpointIP)
|
||||
w.EndpointPort = gosettings.MergeWithPointer(w.EndpointPort, other.EndpointPort)
|
||||
w.PublicKey = gosettings.MergeWithString(w.PublicKey, other.PublicKey)
|
||||
}
|
||||
|
||||
func (w *WireguardSelection) overrideWith(other WireguardSelection) {
|
||||
w.EndpointIP = gosettings.OverrideWithValidator(w.EndpointIP, other.EndpointIP)
|
||||
w.EndpointPort = gosettings.OverrideWithPointer(w.EndpointPort, other.EndpointPort)
|
||||
w.PublicKey = gosettings.OverrideWithComparable(w.PublicKey, other.PublicKey)
|
||||
w.PublicKey = gosettings.OverrideWithString(w.PublicKey, other.PublicKey)
|
||||
}
|
||||
|
||||
func (w *WireguardSelection) setDefaults() {
|
||||
@@ -151,18 +154,3 @@ func (w WireguardSelection) toLinesNode() (node *gotree.Node) {
|
||||
|
||||
return node
|
||||
}
|
||||
|
||||
func (w *WireguardSelection) read(r *reader.Reader) (err error) {
|
||||
w.EndpointIP, err = r.NetipAddr("WIREGUARD_ENDPOINT_IP", reader.RetroKeys("VPN_ENDPOINT_IP"))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
w.EndpointPort, err = r.Uint16Ptr("WIREGUARD_ENDPOINT_PORT", reader.RetroKeys("VPN_ENDPOINT_PORT"))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
w.PublicKey = r.String("WIREGUARD_PUBLIC_KEY", reader.ForceLowercase(false))
|
||||
return nil
|
||||
}
|
||||
|
||||
55
internal/configuration/sources/env/dns.go
vendored
Normal file
55
internal/configuration/sources/env/dns.go
vendored
Normal file
@@ -0,0 +1,55 @@
|
||||
package env
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/netip"
|
||||
|
||||
"github.com/qdm12/gluetun/internal/configuration/settings"
|
||||
)
|
||||
|
||||
func (s *Source) readDNS() (dns settings.DNS, err error) {
|
||||
dns.ServerAddress, err = s.readDNSServerAddress()
|
||||
if err != nil {
|
||||
return dns, err
|
||||
}
|
||||
|
||||
dns.KeepNameserver, err = s.env.BoolPtr("DNS_KEEP_NAMESERVER")
|
||||
if err != nil {
|
||||
return dns, err
|
||||
}
|
||||
|
||||
dns.DoT, err = s.readDoT()
|
||||
if err != nil {
|
||||
return dns, fmt.Errorf("DoT settings: %w", err)
|
||||
}
|
||||
|
||||
return dns, nil
|
||||
}
|
||||
|
||||
func (s *Source) readDNSServerAddress() (address netip.Addr, err error) {
|
||||
const currentKey = "DNS_ADDRESS"
|
||||
key := firstKeySet(s.env, "DNS_PLAINTEXT_ADDRESS", currentKey)
|
||||
switch key {
|
||||
case "":
|
||||
return address, nil
|
||||
case currentKey:
|
||||
default: // Retro-compatibility
|
||||
s.handleDeprecatedKey(key, currentKey)
|
||||
}
|
||||
|
||||
address, err = s.env.NetipAddr(key)
|
||||
if err != nil {
|
||||
return address, err
|
||||
}
|
||||
|
||||
// TODO remove in v4
|
||||
if address.Unmap().Compare(netip.AddrFrom4([4]byte{127, 0, 0, 1})) != 0 {
|
||||
s.warner.Warn(key + " is set to " + address.String() +
|
||||
" so the DNS over TLS (DoT) server will not be used." +
|
||||
" The default value changed to 127.0.0.1 so it uses the internal DoT serves." +
|
||||
" If the DoT server fails to start, the IPv4 address of the first plaintext DNS server" +
|
||||
" corresponding to the first DoT provider chosen is used.")
|
||||
}
|
||||
|
||||
return address, nil
|
||||
}
|
||||
73
internal/configuration/sources/env/dnsblacklist.go
vendored
Normal file
73
internal/configuration/sources/env/dnsblacklist.go
vendored
Normal file
@@ -0,0 +1,73 @@
|
||||
package env
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"net/netip"
|
||||
|
||||
"github.com/qdm12/gluetun/internal/configuration/settings"
|
||||
"github.com/qdm12/gosettings/sources/env"
|
||||
)
|
||||
|
||||
func (s *Source) readDNSBlacklist() (blacklist settings.DNSBlacklist, err error) {
|
||||
blacklist.BlockMalicious, err = s.env.BoolPtr("BLOCK_MALICIOUS")
|
||||
if err != nil {
|
||||
return blacklist, err
|
||||
}
|
||||
|
||||
blacklist.BlockSurveillance, err = s.env.BoolPtr("BLOCK_SURVEILLANCE",
|
||||
env.RetroKeys("BLOCK_NSA"))
|
||||
if err != nil {
|
||||
return blacklist, err
|
||||
}
|
||||
|
||||
blacklist.BlockAds, err = s.env.BoolPtr("BLOCK_ADS")
|
||||
if err != nil {
|
||||
return blacklist, err
|
||||
}
|
||||
|
||||
blacklist.AddBlockedIPs, blacklist.AddBlockedIPPrefixes,
|
||||
err = s.readDoTPrivateAddresses() // TODO v4 split in 2
|
||||
if err != nil {
|
||||
return blacklist, err
|
||||
}
|
||||
|
||||
blacklist.AllowedHosts = s.env.CSV("UNBLOCK") // TODO v4 change name
|
||||
|
||||
return blacklist, nil
|
||||
}
|
||||
|
||||
var (
|
||||
ErrPrivateAddressNotValid = errors.New("private address is not a valid IP or CIDR range")
|
||||
)
|
||||
|
||||
func (s *Source) readDoTPrivateAddresses() (ips []netip.Addr,
|
||||
ipPrefixes []netip.Prefix, err error) {
|
||||
privateAddresses := s.env.CSV("DOT_PRIVATE_ADDRESS")
|
||||
if len(privateAddresses) == 0 {
|
||||
return nil, nil, nil
|
||||
}
|
||||
|
||||
ips = make([]netip.Addr, 0, len(privateAddresses))
|
||||
ipPrefixes = make([]netip.Prefix, 0, len(privateAddresses))
|
||||
|
||||
for _, privateAddress := range privateAddresses {
|
||||
ip, err := netip.ParseAddr(privateAddress)
|
||||
if err == nil {
|
||||
ips = append(ips, ip)
|
||||
continue
|
||||
}
|
||||
|
||||
ipPrefix, err := netip.ParsePrefix(privateAddress)
|
||||
if err == nil {
|
||||
ipPrefixes = append(ipPrefixes, ipPrefix)
|
||||
continue
|
||||
}
|
||||
|
||||
return nil, nil, fmt.Errorf(
|
||||
"environment variable DOT_PRIVATE_ADDRESS: %w: %s",
|
||||
ErrPrivateAddressNotValid, privateAddress)
|
||||
}
|
||||
|
||||
return ips, ipPrefixes, nil
|
||||
}
|
||||
29
internal/configuration/sources/env/dot.go
vendored
Normal file
29
internal/configuration/sources/env/dot.go
vendored
Normal file
@@ -0,0 +1,29 @@
|
||||
package env
|
||||
|
||||
import (
|
||||
"github.com/qdm12/gluetun/internal/configuration/settings"
|
||||
)
|
||||
|
||||
func (s *Source) readDoT() (dot settings.DoT, err error) {
|
||||
dot.Enabled, err = s.env.BoolPtr("DOT")
|
||||
if err != nil {
|
||||
return dot, err
|
||||
}
|
||||
|
||||
dot.UpdatePeriod, err = s.env.DurationPtr("DNS_UPDATE_PERIOD")
|
||||
if err != nil {
|
||||
return dot, err
|
||||
}
|
||||
|
||||
dot.Unbound, err = s.readUnbound()
|
||||
if err != nil {
|
||||
return dot, err
|
||||
}
|
||||
|
||||
dot.Blacklist, err = s.readDNSBlacklist()
|
||||
if err != nil {
|
||||
return dot, err
|
||||
}
|
||||
|
||||
return dot, nil
|
||||
}
|
||||
36
internal/configuration/sources/env/firewall.go
vendored
Normal file
36
internal/configuration/sources/env/firewall.go
vendored
Normal file
@@ -0,0 +1,36 @@
|
||||
package env
|
||||
|
||||
import (
|
||||
"github.com/qdm12/gluetun/internal/configuration/settings"
|
||||
"github.com/qdm12/gosettings/sources/env"
|
||||
)
|
||||
|
||||
func (s *Source) readFirewall() (firewall settings.Firewall, err error) {
|
||||
firewall.VPNInputPorts, err = s.env.CSVUint16("FIREWALL_VPN_INPUT_PORTS")
|
||||
if err != nil {
|
||||
return firewall, err
|
||||
}
|
||||
|
||||
firewall.InputPorts, err = s.env.CSVUint16("FIREWALL_INPUT_PORTS")
|
||||
if err != nil {
|
||||
return firewall, err
|
||||
}
|
||||
|
||||
firewall.OutboundSubnets, err = s.env.CSVNetipPrefixes("FIREWALL_OUTBOUND_SUBNETS",
|
||||
env.RetroKeys("EXTRA_SUBNETS"))
|
||||
if err != nil {
|
||||
return firewall, err
|
||||
}
|
||||
|
||||
firewall.Enabled, err = s.env.BoolPtr("FIREWALL")
|
||||
if err != nil {
|
||||
return firewall, err
|
||||
}
|
||||
|
||||
firewall.Debug, err = s.env.BoolPtr("FIREWALL_DEBUG")
|
||||
if err != nil {
|
||||
return firewall, err
|
||||
}
|
||||
|
||||
return firewall, nil
|
||||
}
|
||||
35
internal/configuration/sources/env/health.go
vendored
Normal file
35
internal/configuration/sources/env/health.go
vendored
Normal file
@@ -0,0 +1,35 @@
|
||||
package env
|
||||
|
||||
import (
|
||||
"github.com/qdm12/gluetun/internal/configuration/settings"
|
||||
"github.com/qdm12/gosettings/sources/env"
|
||||
)
|
||||
|
||||
func (s *Source) ReadHealth() (health settings.Health, err error) {
|
||||
health.ServerAddress = s.env.String("HEALTH_SERVER_ADDRESS")
|
||||
health.TargetAddress = s.env.String("HEALTH_TARGET_ADDRESS",
|
||||
env.RetroKeys("HEALTH_ADDRESS_TO_PING"))
|
||||
|
||||
successWaitPtr, err := s.env.DurationPtr("HEALTH_SUCCESS_WAIT_DURATION")
|
||||
if err != nil {
|
||||
return health, err
|
||||
} else if successWaitPtr != nil {
|
||||
health.SuccessWait = *successWaitPtr
|
||||
}
|
||||
|
||||
health.VPN.Initial, err = s.env.DurationPtr(
|
||||
"HEALTH_VPN_DURATION_INITIAL",
|
||||
env.RetroKeys("HEALTH_OPENVPN_DURATION_INITIAL"))
|
||||
if err != nil {
|
||||
return health, err
|
||||
}
|
||||
|
||||
health.VPN.Addition, err = s.env.DurationPtr(
|
||||
"HEALTH_VPN_DURATION_ADDITION",
|
||||
env.RetroKeys("HEALTH_OPENVPN_DURATION_ADDITION"))
|
||||
if err != nil {
|
||||
return health, err
|
||||
}
|
||||
|
||||
return health, nil
|
||||
}
|
||||
33
internal/configuration/sources/env/helpers.go
vendored
Normal file
33
internal/configuration/sources/env/helpers.go
vendored
Normal file
@@ -0,0 +1,33 @@
|
||||
package env
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
|
||||
"github.com/qdm12/gosettings/sources/env"
|
||||
)
|
||||
|
||||
func unsetEnvKeys(envKeys []string, err error) (newErr error) {
|
||||
newErr = err
|
||||
for _, envKey := range envKeys {
|
||||
unsetErr := os.Unsetenv(envKey)
|
||||
if unsetErr != nil && newErr == nil {
|
||||
newErr = fmt.Errorf("unsetting environment variable %s: %w", envKey, unsetErr)
|
||||
}
|
||||
}
|
||||
return newErr
|
||||
}
|
||||
|
||||
func ptrTo[T any](value T) *T {
|
||||
return &value
|
||||
}
|
||||
|
||||
func firstKeySet(e env.Env, keys ...string) (firstKeySet string) {
|
||||
for _, key := range keys {
|
||||
value := e.Get(key)
|
||||
if value != nil {
|
||||
return key
|
||||
}
|
||||
}
|
||||
return ""
|
||||
}
|
||||
84
internal/configuration/sources/env/httproxy.go
vendored
Normal file
84
internal/configuration/sources/env/httproxy.go
vendored
Normal file
@@ -0,0 +1,84 @@
|
||||
package env
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/qdm12/gluetun/internal/configuration/settings"
|
||||
"github.com/qdm12/gosettings/sources/env"
|
||||
"github.com/qdm12/govalid/binary"
|
||||
)
|
||||
|
||||
func (s *Source) readHTTPProxy() (httpProxy settings.HTTPProxy, err error) {
|
||||
httpProxy.User = s.env.Get("HTTPPROXY_USER",
|
||||
env.RetroKeys("PROXY_USER", "TINYPROXY_USER"),
|
||||
env.ForceLowercase(false))
|
||||
|
||||
httpProxy.Password = s.env.Get("HTTPPROXY_PASSWORD",
|
||||
env.RetroKeys("PROXY_PASSWORD", "TINYPROXY_PASSWORD"),
|
||||
env.ForceLowercase(false))
|
||||
|
||||
httpProxy.ListeningAddress, err = s.readHTTProxyListeningAddress()
|
||||
if err != nil {
|
||||
return httpProxy, err
|
||||
}
|
||||
|
||||
httpProxy.Enabled, err = s.env.BoolPtr("HTTPPROXY", env.RetroKeys("PROXY", "TINYPROXY"))
|
||||
if err != nil {
|
||||
return httpProxy, err
|
||||
}
|
||||
|
||||
httpProxy.Stealth, err = s.env.BoolPtr("HTTPPROXY_STEALTH")
|
||||
if err != nil {
|
||||
return httpProxy, err
|
||||
}
|
||||
|
||||
httpProxy.Log, err = s.readHTTProxyLog()
|
||||
if err != nil {
|
||||
return httpProxy, err
|
||||
}
|
||||
|
||||
return httpProxy, nil
|
||||
}
|
||||
|
||||
func (s *Source) readHTTProxyListeningAddress() (listeningAddress string, err error) {
|
||||
const currentKey = "HTTPPROXY_LISTENING_ADDRESS"
|
||||
key := firstKeySet(s.env, "HTTPPROXY_PORT", "TINYPROXY_PORT", "PROXY_PORT",
|
||||
currentKey)
|
||||
switch key {
|
||||
case "":
|
||||
return "", nil
|
||||
case currentKey:
|
||||
return s.env.String(key), nil
|
||||
}
|
||||
|
||||
// Retro-compatible keys using a port only
|
||||
s.handleDeprecatedKey(key, currentKey)
|
||||
port, err := s.env.Uint16Ptr(key)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return fmt.Sprintf(":%d", *port), nil
|
||||
}
|
||||
|
||||
func (s *Source) readHTTProxyLog() (enabled *bool, err error) {
|
||||
const currentKey = "HTTPPROXY_LOG"
|
||||
key := firstKeySet(s.env, "PROXY_LOG", "TINYPROXY_LOG", "HTTPPROXY_LOG")
|
||||
switch key {
|
||||
case "":
|
||||
return nil, nil //nolint:nilnil
|
||||
case currentKey:
|
||||
return s.env.BoolPtr(key)
|
||||
}
|
||||
|
||||
// Retro-compatible keys using different boolean verbs
|
||||
s.handleDeprecatedKey(key, currentKey)
|
||||
value := s.env.String(key)
|
||||
retroOption := binary.OptionEnabled("on", "info", "connect", "notice")
|
||||
|
||||
enabled, err = binary.Validate(value, retroOption)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("environment variable %s: %w", key, err)
|
||||
}
|
||||
|
||||
return enabled, nil
|
||||
}
|
||||
53
internal/configuration/sources/env/log.go
vendored
Normal file
53
internal/configuration/sources/env/log.go
vendored
Normal file
@@ -0,0 +1,53 @@
|
||||
package env
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/qdm12/gluetun/internal/configuration/settings"
|
||||
"github.com/qdm12/log"
|
||||
)
|
||||
|
||||
func (s *Source) readLog() (log settings.Log, err error) {
|
||||
log.Level, err = s.readLogLevel()
|
||||
if err != nil {
|
||||
return log, err
|
||||
}
|
||||
|
||||
return log, nil
|
||||
}
|
||||
|
||||
func (s *Source) readLogLevel() (level *log.Level, err error) {
|
||||
value := s.env.String("LOG_LEVEL")
|
||||
if value == "" {
|
||||
return nil, nil //nolint:nilnil
|
||||
}
|
||||
|
||||
level = new(log.Level)
|
||||
*level, err = parseLogLevel(value)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("environment variable LOG_LEVEL: %w", err)
|
||||
}
|
||||
|
||||
return level, nil
|
||||
}
|
||||
|
||||
var ErrLogLevelUnknown = errors.New("log level is unknown")
|
||||
|
||||
func parseLogLevel(s string) (level log.Level, err error) {
|
||||
switch strings.ToLower(s) {
|
||||
case "debug":
|
||||
return log.LevelDebug, nil
|
||||
case "info":
|
||||
return log.LevelInfo, nil
|
||||
case "warning":
|
||||
return log.LevelWarn, nil
|
||||
case "error":
|
||||
return log.LevelError, nil
|
||||
default:
|
||||
return level, fmt.Errorf(
|
||||
"%w: %q is not valid and can be one of debug, info, warning or error",
|
||||
ErrLogLevelUnknown, s)
|
||||
}
|
||||
}
|
||||
77
internal/configuration/sources/env/openvpn.go
vendored
Normal file
77
internal/configuration/sources/env/openvpn.go
vendored
Normal file
@@ -0,0 +1,77 @@
|
||||
package env
|
||||
|
||||
import (
|
||||
"strings"
|
||||
|
||||
"github.com/qdm12/gluetun/internal/configuration/settings"
|
||||
"github.com/qdm12/gosettings/sources/env"
|
||||
)
|
||||
|
||||
func (s *Source) readOpenVPN() (
|
||||
openVPN settings.OpenVPN, err error) {
|
||||
defer func() {
|
||||
err = unsetEnvKeys([]string{"OPENVPN_KEY", "OPENVPN_CERT",
|
||||
"OPENVPN_KEY_PASSPHRASE", "OPENVPN_ENCRYPTED_KEY"}, err)
|
||||
}()
|
||||
|
||||
openVPN.Version = s.env.String("OPENVPN_VERSION")
|
||||
openVPN.User = s.env.Get("OPENVPN_USER",
|
||||
env.RetroKeys("USER"), env.ForceLowercase(false))
|
||||
openVPN.Password = s.env.Get("OPENVPN_PASSWORD",
|
||||
env.RetroKeys("PASSWORD"), env.ForceLowercase(false))
|
||||
openVPN.ConfFile = s.env.Get("OPENVPN_CUSTOM_CONFIG", env.ForceLowercase(false))
|
||||
openVPN.Ciphers = s.env.CSV("OPENVPN_CIPHERS", env.RetroKeys("OPENVPN_CIPHER"))
|
||||
openVPN.Auth = s.env.Get("OPENVPN_AUTH")
|
||||
openVPN.Cert = s.env.Get("OPENVPN_CERT", env.ForceLowercase(false))
|
||||
openVPN.Key = s.env.Get("OPENVPN_KEY", env.ForceLowercase(false))
|
||||
openVPN.EncryptedKey = s.env.Get("OPENVPN_ENCRYPTED_KEY", env.ForceLowercase(false))
|
||||
openVPN.KeyPassphrase = s.env.Get("OPENVPN_KEY_PASSPHRASE", env.ForceLowercase(false))
|
||||
|
||||
openVPN.PIAEncPreset = s.readPIAEncryptionPreset()
|
||||
|
||||
openVPN.MSSFix, err = s.env.Uint16Ptr("OPENVPN_MSSFIX")
|
||||
if err != nil {
|
||||
return openVPN, err
|
||||
}
|
||||
|
||||
openVPN.Interface = s.env.String("VPN_INTERFACE",
|
||||
env.RetroKeys("OPENVPN_INTERFACE"), env.ForceLowercase(false))
|
||||
|
||||
openVPN.ProcessUser, err = s.readOpenVPNProcessUser()
|
||||
if err != nil {
|
||||
return openVPN, err
|
||||
}
|
||||
|
||||
openVPN.Verbosity, err = s.env.IntPtr("OPENVPN_VERBOSITY")
|
||||
if err != nil {
|
||||
return openVPN, err
|
||||
}
|
||||
|
||||
flagsPtr := s.env.Get("OPENVPN_FLAGS", env.ForceLowercase(false))
|
||||
if flagsPtr != nil {
|
||||
openVPN.Flags = strings.Fields(*flagsPtr)
|
||||
}
|
||||
|
||||
return openVPN, nil
|
||||
}
|
||||
|
||||
func (s *Source) readPIAEncryptionPreset() (presetPtr *string) {
|
||||
return s.env.Get(
|
||||
"PRIVATE_INTERNET_ACCESS_OPENVPN_ENCRYPTION_PRESET",
|
||||
env.RetroKeys("ENCRYPTION", "PIA_ENCRYPTION"))
|
||||
}
|
||||
|
||||
func (s *Source) readOpenVPNProcessUser() (processUser string, err error) {
|
||||
value, err := s.env.BoolPtr("OPENVPN_ROOT") // Retro-compatibility
|
||||
if err != nil {
|
||||
return "", err
|
||||
} else if value != nil {
|
||||
if *value {
|
||||
return "root", nil
|
||||
}
|
||||
const defaultNonRootUser = "nonrootuser"
|
||||
return defaultNonRootUser, nil
|
||||
}
|
||||
|
||||
return s.env.String("OPENVPN_PROCESS_USER"), nil
|
||||
}
|
||||
56
internal/configuration/sources/env/openvpnselection.go
vendored
Normal file
56
internal/configuration/sources/env/openvpnselection.go
vendored
Normal file
@@ -0,0 +1,56 @@
|
||||
package env
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/qdm12/gluetun/internal/configuration/settings"
|
||||
"github.com/qdm12/gluetun/internal/constants"
|
||||
"github.com/qdm12/gosettings/sources/env"
|
||||
)
|
||||
|
||||
func (s *Source) readOpenVPNSelection() (
|
||||
selection settings.OpenVPNSelection, err error) {
|
||||
selection.ConfFile = s.env.Get("OPENVPN_CUSTOM_CONFIG", env.ForceLowercase(false))
|
||||
|
||||
selection.TCP, err = s.readOpenVPNProtocol()
|
||||
if err != nil {
|
||||
return selection, err
|
||||
}
|
||||
|
||||
selection.CustomPort, err = s.env.Uint16Ptr("VPN_ENDPOINT_PORT",
|
||||
env.RetroKeys("PORT", "OPENVPN_PORT"))
|
||||
if err != nil {
|
||||
return selection, err
|
||||
}
|
||||
|
||||
selection.PIAEncPreset = s.readPIAEncryptionPreset()
|
||||
|
||||
return selection, nil
|
||||
}
|
||||
|
||||
var ErrOpenVPNProtocolNotValid = errors.New("OpenVPN protocol is not valid")
|
||||
|
||||
func (s *Source) readOpenVPNProtocol() (tcp *bool, err error) {
|
||||
const currentKey = "OPENVPN_PROTOCOL"
|
||||
envKey := firstKeySet(s.env, "PROTOCOL", currentKey)
|
||||
switch envKey {
|
||||
case "":
|
||||
return nil, nil //nolint:nilnil
|
||||
case currentKey:
|
||||
default: // Retro compatibility
|
||||
s.handleDeprecatedKey(envKey, currentKey)
|
||||
}
|
||||
|
||||
protocol := s.env.String(envKey)
|
||||
switch strings.ToLower(protocol) {
|
||||
case constants.UDP:
|
||||
return ptrTo(false), nil
|
||||
case constants.TCP:
|
||||
return ptrTo(true), nil
|
||||
default:
|
||||
return nil, fmt.Errorf("environment variable %s: %w: %s",
|
||||
envKey, ErrOpenVPNProtocolNotValid, protocol)
|
||||
}
|
||||
}
|
||||
34
internal/configuration/sources/env/portforward.go
vendored
Normal file
34
internal/configuration/sources/env/portforward.go
vendored
Normal file
@@ -0,0 +1,34 @@
|
||||
package env
|
||||
|
||||
import (
|
||||
"github.com/qdm12/gluetun/internal/configuration/settings"
|
||||
"github.com/qdm12/gosettings/sources/env"
|
||||
)
|
||||
|
||||
func (s *Source) readPortForward() (
|
||||
portForwarding settings.PortForwarding, err error) {
|
||||
portForwarding.Enabled, err = s.env.BoolPtr("VPN_PORT_FORWARDING",
|
||||
env.RetroKeys(
|
||||
"PORT_FORWARDING",
|
||||
"PRIVATE_INTERNET_ACCESS_VPN_PORT_FORWARDING",
|
||||
))
|
||||
if err != nil {
|
||||
return portForwarding, err
|
||||
}
|
||||
|
||||
portForwarding.Provider = s.env.Get("VPN_PORT_FORWARDING_PROVIDER")
|
||||
|
||||
portForwarding.Filepath = s.env.Get("VPN_PORT_FORWARDING_STATUS_FILE",
|
||||
env.ForceLowercase(false),
|
||||
env.RetroKeys(
|
||||
"PORT_FORWARDING_STATUS_FILE",
|
||||
"PRIVATE_INTERNET_ACCESS_VPN_PORT_FORWARDING_STATUS_FILE",
|
||||
))
|
||||
|
||||
portForwarding.ListeningPort, err = s.env.Uint16Ptr("VPN_PORT_FORWARDING_LISTENING_PORT")
|
||||
if err != nil {
|
||||
return portForwarding, err
|
||||
}
|
||||
|
||||
return portForwarding, nil
|
||||
}
|
||||
26
internal/configuration/sources/env/pprof.go
vendored
Normal file
26
internal/configuration/sources/env/pprof.go
vendored
Normal file
@@ -0,0 +1,26 @@
|
||||
package env
|
||||
|
||||
import (
|
||||
"github.com/qdm12/gluetun/internal/pprof"
|
||||
)
|
||||
|
||||
func (s *Source) readPprof() (settings pprof.Settings, err error) {
|
||||
settings.Enabled, err = s.env.BoolPtr("PPROF_ENABLED")
|
||||
if err != nil {
|
||||
return settings, err
|
||||
}
|
||||
|
||||
settings.BlockProfileRate, err = s.env.IntPtr("PPROF_BLOCK_PROFILE_RATE")
|
||||
if err != nil {
|
||||
return settings, err
|
||||
}
|
||||
|
||||
settings.MutexProfileRate, err = s.env.IntPtr("PPROF_MUTEX_PROFILE_RATE")
|
||||
if err != nil {
|
||||
return settings, err
|
||||
}
|
||||
|
||||
settings.HTTPServer.Address = s.env.String("PPROF_HTTP_SERVER_ADDRESS")
|
||||
|
||||
return settings, nil
|
||||
}
|
||||
50
internal/configuration/sources/env/provider.go
vendored
Normal file
50
internal/configuration/sources/env/provider.go
vendored
Normal file
@@ -0,0 +1,50 @@
|
||||
package env
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/qdm12/gluetun/internal/configuration/settings"
|
||||
"github.com/qdm12/gluetun/internal/constants/providers"
|
||||
"github.com/qdm12/gluetun/internal/constants/vpn"
|
||||
"github.com/qdm12/gosettings/sources/env"
|
||||
)
|
||||
|
||||
func (s *Source) readProvider(vpnType string) (provider settings.Provider, err error) {
|
||||
provider.Name = s.readVPNServiceProvider(vpnType)
|
||||
var providerName string
|
||||
if provider.Name != nil {
|
||||
providerName = *provider.Name
|
||||
}
|
||||
|
||||
provider.ServerSelection, err = s.readServerSelection(providerName, vpnType)
|
||||
if err != nil {
|
||||
return provider, fmt.Errorf("server selection: %w", err)
|
||||
}
|
||||
|
||||
provider.PortForwarding, err = s.readPortForward()
|
||||
if err != nil {
|
||||
return provider, fmt.Errorf("port forwarding: %w", err)
|
||||
}
|
||||
|
||||
return provider, nil
|
||||
}
|
||||
|
||||
func (s *Source) readVPNServiceProvider(vpnType string) (vpnProviderPtr *string) {
|
||||
valuePtr := s.env.Get("VPN_SERVICE_PROVIDER", env.RetroKeys("VPNSP"))
|
||||
if valuePtr == nil {
|
||||
if vpnType != vpn.Wireguard && s.env.Get("OPENVPN_CUSTOM_CONFIG") != nil {
|
||||
// retro compatibility
|
||||
return ptrTo(providers.Custom)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
value := *valuePtr
|
||||
value = strings.ToLower(value)
|
||||
if value == "pia" { // retro compatibility
|
||||
return ptrTo(providers.PrivateInternetAccess)
|
||||
}
|
||||
|
||||
return ptrTo(value)
|
||||
}
|
||||
22
internal/configuration/sources/env/publicip.go
vendored
Normal file
22
internal/configuration/sources/env/publicip.go
vendored
Normal file
@@ -0,0 +1,22 @@
|
||||
package env
|
||||
|
||||
import (
|
||||
"github.com/qdm12/gluetun/internal/configuration/settings"
|
||||
"github.com/qdm12/gosettings/sources/env"
|
||||
)
|
||||
|
||||
func (s *Source) readPublicIP() (publicIP settings.PublicIP, err error) {
|
||||
publicIP.Period, err = s.env.DurationPtr("PUBLICIP_PERIOD")
|
||||
if err != nil {
|
||||
return publicIP, err
|
||||
}
|
||||
|
||||
publicIP.IPFilepath = s.env.Get("PUBLICIP_FILE",
|
||||
env.ForceLowercase(false), env.RetroKeys("IP_STATUS_FILE"))
|
||||
|
||||
publicIP.API = s.env.String("PUBLICIP_API")
|
||||
|
||||
publicIP.APIToken = s.env.Get("PUBLICIP_API_TOKEN")
|
||||
|
||||
return publicIP, nil
|
||||
}
|
||||
103
internal/configuration/sources/env/reader.go
vendored
Normal file
103
internal/configuration/sources/env/reader.go
vendored
Normal file
@@ -0,0 +1,103 @@
|
||||
package env
|
||||
|
||||
import (
|
||||
"os"
|
||||
|
||||
"github.com/qdm12/gluetun/internal/configuration/settings"
|
||||
"github.com/qdm12/gosettings/sources/env"
|
||||
)
|
||||
|
||||
type Source struct {
|
||||
env env.Env
|
||||
warner Warner
|
||||
handleDeprecatedKey func(deprecatedKey, newKey string)
|
||||
}
|
||||
|
||||
type Warner interface {
|
||||
Warn(s string)
|
||||
}
|
||||
|
||||
func New(warner Warner) *Source {
|
||||
handleDeprecatedKey := func(deprecatedKey, newKey string) {
|
||||
warner.Warn(
|
||||
"You are using the old environment variable " + deprecatedKey +
|
||||
", please consider changing it to " + newKey)
|
||||
}
|
||||
|
||||
return &Source{
|
||||
env: *env.New(os.Environ(), handleDeprecatedKey),
|
||||
warner: warner,
|
||||
handleDeprecatedKey: handleDeprecatedKey,
|
||||
}
|
||||
}
|
||||
|
||||
func (s *Source) String() string { return "environment variables" }
|
||||
|
||||
func (s *Source) Read() (settings settings.Settings, err error) {
|
||||
settings.VPN, err = s.readVPN()
|
||||
if err != nil {
|
||||
return settings, err
|
||||
}
|
||||
|
||||
settings.Firewall, err = s.readFirewall()
|
||||
if err != nil {
|
||||
return settings, err
|
||||
}
|
||||
|
||||
settings.System, err = s.readSystem()
|
||||
if err != nil {
|
||||
return settings, err
|
||||
}
|
||||
|
||||
settings.Health, err = s.ReadHealth()
|
||||
if err != nil {
|
||||
return settings, err
|
||||
}
|
||||
|
||||
settings.HTTPProxy, err = s.readHTTPProxy()
|
||||
if err != nil {
|
||||
return settings, err
|
||||
}
|
||||
|
||||
settings.Log, err = s.readLog()
|
||||
if err != nil {
|
||||
return settings, err
|
||||
}
|
||||
|
||||
settings.PublicIP, err = s.readPublicIP()
|
||||
if err != nil {
|
||||
return settings, err
|
||||
}
|
||||
|
||||
settings.Updater, err = s.readUpdater()
|
||||
if err != nil {
|
||||
return settings, err
|
||||
}
|
||||
|
||||
settings.Version, err = s.readVersion()
|
||||
if err != nil {
|
||||
return settings, err
|
||||
}
|
||||
|
||||
settings.Shadowsocks, err = s.readShadowsocks()
|
||||
if err != nil {
|
||||
return settings, err
|
||||
}
|
||||
|
||||
settings.DNS, err = s.readDNS()
|
||||
if err != nil {
|
||||
return settings, err
|
||||
}
|
||||
|
||||
settings.ControlServer, err = s.readControlServer()
|
||||
if err != nil {
|
||||
return settings, err
|
||||
}
|
||||
|
||||
settings.Pprof, err = s.readPprof()
|
||||
if err != nil {
|
||||
return settings, err
|
||||
}
|
||||
|
||||
return settings, nil
|
||||
}
|
||||
31
internal/configuration/sources/env/server.go
vendored
Normal file
31
internal/configuration/sources/env/server.go
vendored
Normal file
@@ -0,0 +1,31 @@
|
||||
package env
|
||||
|
||||
import (
|
||||
"github.com/qdm12/gluetun/internal/configuration/settings"
|
||||
)
|
||||
|
||||
func (s *Source) readControlServer() (controlServer settings.ControlServer, err error) {
|
||||
controlServer.Log, err = s.env.BoolPtr("HTTP_CONTROL_SERVER_LOG")
|
||||
if err != nil {
|
||||
return controlServer, err
|
||||
}
|
||||
|
||||
controlServer.Address = s.readControlServerAddress()
|
||||
|
||||
return controlServer, nil
|
||||
}
|
||||
|
||||
func (s *Source) readControlServerAddress() (address *string) {
|
||||
const currentKey = "HTTP_CONTROL_SERVER_ADDRESS"
|
||||
key := firstKeySet(s.env, "CONTROL_SERVER_ADDRESS", currentKey)
|
||||
if key == currentKey {
|
||||
return s.env.Get(key)
|
||||
}
|
||||
|
||||
s.handleDeprecatedKey(key, currentKey)
|
||||
value := s.env.Get("CONTROL_SERVER_ADDRESS")
|
||||
if value == nil {
|
||||
return nil
|
||||
}
|
||||
return ptrTo(":" + *value)
|
||||
}
|
||||
92
internal/configuration/sources/env/serverselection.go
vendored
Normal file
92
internal/configuration/sources/env/serverselection.go
vendored
Normal file
@@ -0,0 +1,92 @@
|
||||
package env
|
||||
|
||||
import (
|
||||
"errors"
|
||||
|
||||
"github.com/qdm12/gluetun/internal/configuration/settings"
|
||||
"github.com/qdm12/gluetun/internal/constants/providers"
|
||||
"github.com/qdm12/gosettings/sources/env"
|
||||
)
|
||||
|
||||
func (s *Source) readServerSelection(vpnProvider, vpnType string) (
|
||||
ss settings.ServerSelection, err error) {
|
||||
ss.VPN = vpnType
|
||||
|
||||
ss.TargetIP, err = s.env.NetipAddr("VPN_ENDPOINT_IP",
|
||||
env.RetroKeys("OPENVPN_TARGET_IP"))
|
||||
if err != nil {
|
||||
return ss, err
|
||||
}
|
||||
|
||||
ss.Countries = s.env.CSV("SERVER_COUNTRIES", env.RetroKeys("COUNTRY"))
|
||||
if vpnProvider == providers.Cyberghost && len(ss.Countries) == 0 {
|
||||
// Retro-compatibility for Cyberghost using the REGION variable
|
||||
ss.Countries = s.env.CSV("REGION")
|
||||
if len(ss.Countries) > 0 {
|
||||
s.handleDeprecatedKey("REGION", "SERVER_COUNTRIES")
|
||||
}
|
||||
}
|
||||
|
||||
ss.Regions = s.env.CSV("SERVER_REGIONS", env.RetroKeys("REGION"))
|
||||
ss.Cities = s.env.CSV("SERVER_CITIES", env.RetroKeys("CITY"))
|
||||
ss.ISPs = s.env.CSV("ISP")
|
||||
ss.Hostnames = s.env.CSV("SERVER_HOSTNAMES", env.RetroKeys("SERVER_HOSTNAME"))
|
||||
ss.Names = s.env.CSV("SERVER_NAMES", env.RetroKeys("SERVER_NAME"))
|
||||
ss.Numbers, err = s.env.CSVUint16("SERVER_NUMBER")
|
||||
ss.Categories = s.env.CSV("SERVER_CATEGORIES")
|
||||
if err != nil {
|
||||
return ss, err
|
||||
}
|
||||
|
||||
// Mullvad only
|
||||
ss.OwnedOnly, err = s.env.BoolPtr("OWNED_ONLY", env.RetroKeys("OWNED"))
|
||||
if err != nil {
|
||||
return ss, err
|
||||
}
|
||||
|
||||
// VPNUnlimited and ProtonVPN only
|
||||
ss.FreeOnly, err = s.env.BoolPtr("FREE_ONLY")
|
||||
if err != nil {
|
||||
return ss, err
|
||||
}
|
||||
|
||||
// VPNSecure only
|
||||
ss.PremiumOnly, err = s.env.BoolPtr("PREMIUM_ONLY")
|
||||
if err != nil {
|
||||
return ss, err
|
||||
}
|
||||
|
||||
// Surfshark only
|
||||
ss.MultiHopOnly, err = s.env.BoolPtr("MULTIHOP_ONLY")
|
||||
if err != nil {
|
||||
return ss, err
|
||||
}
|
||||
|
||||
// VPNUnlimited only
|
||||
ss.StreamOnly, err = s.env.BoolPtr("STREAM_ONLY")
|
||||
if err != nil {
|
||||
return ss, err
|
||||
}
|
||||
|
||||
// PIA only
|
||||
ss.PortForwardOnly, err = s.env.BoolPtr("PORT_FORWARD_ONLY")
|
||||
if err != nil {
|
||||
return ss, err
|
||||
}
|
||||
|
||||
ss.OpenVPN, err = s.readOpenVPNSelection()
|
||||
if err != nil {
|
||||
return ss, err
|
||||
}
|
||||
|
||||
ss.Wireguard, err = s.readWireguardSelection()
|
||||
if err != nil {
|
||||
return ss, err
|
||||
}
|
||||
|
||||
return ss, nil
|
||||
}
|
||||
|
||||
var (
|
||||
ErrInvalidIP = errors.New("invalid IP address")
|
||||
)
|
||||
42
internal/configuration/sources/env/shadowsocks.go
vendored
Normal file
42
internal/configuration/sources/env/shadowsocks.go
vendored
Normal file
@@ -0,0 +1,42 @@
|
||||
package env
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/qdm12/gluetun/internal/configuration/settings"
|
||||
"github.com/qdm12/gosettings/sources/env"
|
||||
)
|
||||
|
||||
func (s *Source) readShadowsocks() (shadowsocks settings.Shadowsocks, err error) {
|
||||
shadowsocks.Enabled, err = s.env.BoolPtr("SHADOWSOCKS")
|
||||
if err != nil {
|
||||
return shadowsocks, err
|
||||
}
|
||||
|
||||
shadowsocks.Address, err = s.readShadowsocksAddress()
|
||||
if err != nil {
|
||||
return shadowsocks, err
|
||||
}
|
||||
shadowsocks.LogAddresses, err = s.env.BoolPtr("SHADOWSOCKS_LOG")
|
||||
if err != nil {
|
||||
return shadowsocks, err
|
||||
}
|
||||
shadowsocks.CipherName = s.env.String("SHADOWSOCKS_CIPHER",
|
||||
env.RetroKeys("SHADOWSOCKS_METHOD"))
|
||||
shadowsocks.Password = s.env.Get("SHADOWSOCKS_PASSWORD", env.ForceLowercase(false))
|
||||
|
||||
return shadowsocks, nil
|
||||
}
|
||||
|
||||
func (s *Source) readShadowsocksAddress() (address *string, err error) {
|
||||
const currentKey = "SHADOWSOCKS_LISTENING_ADDRESS"
|
||||
port, err := s.env.Uint16Ptr("SHADOWSOCKS_PORT") // retro-compatibility
|
||||
if err != nil {
|
||||
return nil, err
|
||||
} else if port != nil {
|
||||
s.handleDeprecatedKey("SHADOWSOCKS_PORT", currentKey)
|
||||
return ptrTo(fmt.Sprintf(":%d", *port)), nil
|
||||
}
|
||||
|
||||
return s.env.Get(currentKey), nil
|
||||
}
|
||||
22
internal/configuration/sources/env/system.go
vendored
Normal file
22
internal/configuration/sources/env/system.go
vendored
Normal file
@@ -0,0 +1,22 @@
|
||||
package env
|
||||
|
||||
import (
|
||||
"github.com/qdm12/gluetun/internal/configuration/settings"
|
||||
"github.com/qdm12/gosettings/sources/env"
|
||||
)
|
||||
|
||||
func (s *Source) readSystem() (system settings.System, err error) {
|
||||
system.PUID, err = s.env.Uint32Ptr("PUID", env.RetroKeys("UID"))
|
||||
if err != nil {
|
||||
return system, err
|
||||
}
|
||||
|
||||
system.PGID, err = s.env.Uint32Ptr("PGID", env.RetroKeys("GID"))
|
||||
if err != nil {
|
||||
return system, err
|
||||
}
|
||||
|
||||
system.Timezone = s.env.String("TZ")
|
||||
|
||||
return system, nil
|
||||
}
|
||||
36
internal/configuration/sources/env/unbound.go
vendored
Normal file
36
internal/configuration/sources/env/unbound.go
vendored
Normal file
@@ -0,0 +1,36 @@
|
||||
package env
|
||||
|
||||
import (
|
||||
"github.com/qdm12/gluetun/internal/configuration/settings"
|
||||
)
|
||||
|
||||
func (s *Source) readUnbound() (unbound settings.Unbound, err error) {
|
||||
unbound.Providers = s.env.CSV("DOT_PROVIDERS")
|
||||
|
||||
unbound.Caching, err = s.env.BoolPtr("DOT_CACHING")
|
||||
if err != nil {
|
||||
return unbound, err
|
||||
}
|
||||
|
||||
unbound.IPv6, err = s.env.BoolPtr("DOT_IPV6")
|
||||
if err != nil {
|
||||
return unbound, err
|
||||
}
|
||||
|
||||
unbound.VerbosityLevel, err = s.env.Uint8Ptr("DOT_VERBOSITY")
|
||||
if err != nil {
|
||||
return unbound, err
|
||||
}
|
||||
|
||||
unbound.VerbosityDetailsLevel, err = s.env.Uint8Ptr("DOT_VERBOSITY_DETAILS")
|
||||
if err != nil {
|
||||
return unbound, err
|
||||
}
|
||||
|
||||
unbound.ValidationLogLevel, err = s.env.Uint8Ptr("DOT_VALIDATION_LOGLEVEL")
|
||||
if err != nil {
|
||||
return unbound, err
|
||||
}
|
||||
|
||||
return unbound, nil
|
||||
}
|
||||
35
internal/configuration/sources/env/updater.go
vendored
Normal file
35
internal/configuration/sources/env/updater.go
vendored
Normal file
@@ -0,0 +1,35 @@
|
||||
package env
|
||||
|
||||
import (
|
||||
"github.com/qdm12/gluetun/internal/configuration/settings"
|
||||
)
|
||||
|
||||
func (s *Source) readUpdater() (updater settings.Updater, err error) {
|
||||
updater.Period, err = s.env.DurationPtr("UPDATER_PERIOD")
|
||||
if err != nil {
|
||||
return updater, err
|
||||
}
|
||||
|
||||
updater.DNSAddress, err = readUpdaterDNSAddress()
|
||||
if err != nil {
|
||||
return updater, err
|
||||
}
|
||||
|
||||
updater.MinRatio, err = s.env.Float64("UPDATER_MIN_RATIO")
|
||||
if err != nil {
|
||||
return updater, err
|
||||
}
|
||||
|
||||
updater.Providers = s.env.CSV("UPDATER_VPN_SERVICE_PROVIDERS")
|
||||
|
||||
return updater, nil
|
||||
}
|
||||
|
||||
func readUpdaterDNSAddress() (address string, err error) {
|
||||
// TODO this is currently using Cloudflare in
|
||||
// plaintext to not be blocked by DNS over TLS by default.
|
||||
// If a plaintext address is set in the DNS settings, this one will be used.
|
||||
// use custom future encrypted DNS written in Go without blocking
|
||||
// as it's too much trouble to start another parallel unbound instance for now.
|
||||
return "", nil
|
||||
}
|
||||
14
internal/configuration/sources/env/version.go
vendored
Normal file
14
internal/configuration/sources/env/version.go
vendored
Normal file
@@ -0,0 +1,14 @@
|
||||
package env
|
||||
|
||||
import (
|
||||
"github.com/qdm12/gluetun/internal/configuration/settings"
|
||||
)
|
||||
|
||||
func (s *Source) readVersion() (version settings.Version, err error) {
|
||||
version.Enabled, err = s.env.BoolPtr("VERSION_INFORMATION")
|
||||
if err != nil {
|
||||
return version, err
|
||||
}
|
||||
|
||||
return version, nil
|
||||
}
|
||||
28
internal/configuration/sources/env/vpn.go
vendored
Normal file
28
internal/configuration/sources/env/vpn.go
vendored
Normal file
@@ -0,0 +1,28 @@
|
||||
package env
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/qdm12/gluetun/internal/configuration/settings"
|
||||
)
|
||||
|
||||
func (s *Source) readVPN() (vpn settings.VPN, err error) {
|
||||
vpn.Type = s.env.String("VPN_TYPE")
|
||||
|
||||
vpn.Provider, err = s.readProvider(vpn.Type)
|
||||
if err != nil {
|
||||
return vpn, fmt.Errorf("VPN provider: %w", err)
|
||||
}
|
||||
|
||||
vpn.OpenVPN, err = s.readOpenVPN()
|
||||
if err != nil {
|
||||
return vpn, fmt.Errorf("OpenVPN: %w", err)
|
||||
}
|
||||
|
||||
vpn.Wireguard, err = s.readWireguard()
|
||||
if err != nil {
|
||||
return vpn, fmt.Errorf("wireguard: %w", err)
|
||||
}
|
||||
|
||||
return vpn, nil
|
||||
}
|
||||
33
internal/configuration/sources/env/wireguard.go
vendored
Normal file
33
internal/configuration/sources/env/wireguard.go
vendored
Normal file
@@ -0,0 +1,33 @@
|
||||
package env
|
||||
|
||||
import (
|
||||
"github.com/qdm12/gluetun/internal/configuration/settings"
|
||||
"github.com/qdm12/gosettings/sources/env"
|
||||
)
|
||||
|
||||
func (s *Source) readWireguard() (wireguard settings.Wireguard, err error) {
|
||||
defer func() {
|
||||
err = unsetEnvKeys([]string{"WIREGUARD_PRIVATE_KEY", "WIREGUARD_PRESHARED_KEY"}, err)
|
||||
}()
|
||||
wireguard.PrivateKey = s.env.Get("WIREGUARD_PRIVATE_KEY", env.ForceLowercase(false))
|
||||
wireguard.PreSharedKey = s.env.Get("WIREGUARD_PRESHARED_KEY", env.ForceLowercase(false))
|
||||
wireguard.Interface = s.env.String("VPN_INTERFACE",
|
||||
env.RetroKeys("WIREGUARD_INTERFACE"), env.ForceLowercase(false))
|
||||
wireguard.Implementation = s.env.String("WIREGUARD_IMPLEMENTATION")
|
||||
wireguard.Addresses, err = s.env.CSVNetipPrefixes("WIREGUARD_ADDRESSES",
|
||||
env.RetroKeys("WIREGUARD_ADDRESS"))
|
||||
if err != nil {
|
||||
return wireguard, err // already wrapped
|
||||
}
|
||||
wireguard.AllowedIPs, err = s.env.CSVNetipPrefixes("WIREGUARD_ALLOWED_IPS")
|
||||
if err != nil {
|
||||
return wireguard, err // already wrapped
|
||||
}
|
||||
mtuPtr, err := s.env.Uint16Ptr("WIREGUARD_MTU")
|
||||
if err != nil {
|
||||
return wireguard, err
|
||||
} else if mtuPtr != nil {
|
||||
wireguard.MTU = *mtuPtr
|
||||
}
|
||||
return wireguard, nil
|
||||
}
|
||||
23
internal/configuration/sources/env/wireguardselection.go
vendored
Normal file
23
internal/configuration/sources/env/wireguardselection.go
vendored
Normal file
@@ -0,0 +1,23 @@
|
||||
package env
|
||||
|
||||
import (
|
||||
"github.com/qdm12/gluetun/internal/configuration/settings"
|
||||
"github.com/qdm12/gosettings/sources/env"
|
||||
)
|
||||
|
||||
func (s *Source) readWireguardSelection() (
|
||||
selection settings.WireguardSelection, err error) {
|
||||
selection.EndpointIP, err = s.env.NetipAddr("VPN_ENDPOINT_IP", env.RetroKeys("WIREGUARD_ENDPOINT_IP"))
|
||||
if err != nil {
|
||||
return selection, err
|
||||
}
|
||||
|
||||
selection.EndpointPort, err = s.env.Uint16Ptr("VPN_ENDPOINT_PORT", env.RetroKeys("WIREGUARD_ENDPOINT_PORT"))
|
||||
if err != nil {
|
||||
return selection, err
|
||||
}
|
||||
|
||||
selection.PublicKey = s.env.String("WIREGUARD_PUBLIC_KEY", env.ForceLowercase(false))
|
||||
|
||||
return selection, nil
|
||||
}
|
||||
5
internal/configuration/sources/files/health.go
Normal file
5
internal/configuration/sources/files/health.go
Normal file
@@ -0,0 +1,5 @@
|
||||
package files
|
||||
|
||||
import "github.com/qdm12/gluetun/internal/configuration/settings"
|
||||
|
||||
func (s *Source) ReadHealth() (settings settings.Health, err error) { return settings, nil }
|
||||
@@ -9,45 +9,47 @@ import (
|
||||
"github.com/qdm12/gluetun/internal/openvpn/extract"
|
||||
)
|
||||
|
||||
// ReadFromFile reads the content of the file as a string,
|
||||
// and returns if the file was present or not with isSet.
|
||||
func ReadFromFile(filepath string) (content string, isSet bool, err error) {
|
||||
// ReadFromFile reads the content of the file as a string.
|
||||
// It returns a nil string pointer if the file does not exist.
|
||||
func ReadFromFile(filepath string) (s *string, err error) {
|
||||
file, err := os.Open(filepath)
|
||||
if err != nil {
|
||||
if os.IsNotExist(err) {
|
||||
return "", false, nil
|
||||
return nil, nil //nolint:nilnil
|
||||
}
|
||||
return "", false, fmt.Errorf("opening file: %w", err)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
b, err := io.ReadAll(file)
|
||||
if err != nil {
|
||||
_ = file.Close()
|
||||
return "", false, fmt.Errorf("reading file: %w", err)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if err := file.Close(); err != nil {
|
||||
return "", false, fmt.Errorf("closing file: %w", err)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
content = string(b)
|
||||
content := string(b)
|
||||
content = strings.TrimSuffix(content, "\r\n")
|
||||
content = strings.TrimSuffix(content, "\n")
|
||||
return content, true, nil
|
||||
return &content, nil
|
||||
}
|
||||
|
||||
func ReadPEMFile(filepath string) (base64Str string, isSet bool, err error) {
|
||||
pemData, isSet, err := ReadFromFile(filepath)
|
||||
func readPEMFile(filepath string) (base64Ptr *string, err error) {
|
||||
pemData, err := ReadFromFile(filepath)
|
||||
if err != nil {
|
||||
return "", false, fmt.Errorf("reading file: %w", err)
|
||||
} else if !isSet {
|
||||
return "", false, nil
|
||||
return nil, fmt.Errorf("reading file: %w", err)
|
||||
}
|
||||
|
||||
base64Str, err = extract.PEM([]byte(pemData))
|
||||
if err != nil {
|
||||
return "", false, fmt.Errorf("extracting base64 encoded data from PEM content: %w", err)
|
||||
if pemData == nil {
|
||||
return nil, nil //nolint:nilnil
|
||||
}
|
||||
|
||||
return base64Str, true, nil
|
||||
base64Data, err := extract.PEM([]byte(*pemData))
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("extracting base64 encoded data from PEM content: %w", err)
|
||||
}
|
||||
|
||||
return &base64Data, nil
|
||||
}
|
||||
|
||||
3
internal/configuration/sources/files/helpers_test.go
Normal file
3
internal/configuration/sources/files/helpers_test.go
Normal file
@@ -0,0 +1,3 @@
|
||||
package files
|
||||
|
||||
func ptrTo[T any](x T) *T { return &x }
|
||||
@@ -1,5 +0,0 @@
|
||||
package files
|
||||
|
||||
type Warner interface {
|
||||
Warnf(format string, a ...interface{})
|
||||
}
|
||||
33
internal/configuration/sources/files/openvpn.go
Normal file
33
internal/configuration/sources/files/openvpn.go
Normal file
@@ -0,0 +1,33 @@
|
||||
package files
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/qdm12/gluetun/internal/configuration/settings"
|
||||
)
|
||||
|
||||
const (
|
||||
// OpenVPNClientKeyPath is the OpenVPN client key filepath.
|
||||
OpenVPNClientKeyPath = "/gluetun/client.key"
|
||||
// OpenVPNClientCertificatePath is the OpenVPN client certificate filepath.
|
||||
OpenVPNClientCertificatePath = "/gluetun/client.crt"
|
||||
openVPNEncryptedKey = "/gluetun/openvpn_encrypted_key"
|
||||
)
|
||||
|
||||
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)
|
||||
}
|
||||
|
||||
settings.Cert, err = readPEMFile(OpenVPNClientCertificatePath)
|
||||
if err != nil {
|
||||
return settings, fmt.Errorf("client certificate: %w", err)
|
||||
}
|
||||
settings.EncryptedKey, err = readPEMFile(openVPNEncryptedKey)
|
||||
if err != nil {
|
||||
return settings, fmt.Errorf("reading encrypted key file: %w", err)
|
||||
}
|
||||
|
||||
return settings, nil
|
||||
}
|
||||
16
internal/configuration/sources/files/provider.go
Normal file
16
internal/configuration/sources/files/provider.go
Normal file
@@ -0,0 +1,16 @@
|
||||
package files
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/qdm12/gluetun/internal/configuration/settings"
|
||||
)
|
||||
|
||||
func (s *Source) readProvider() (provider settings.Provider, err error) {
|
||||
provider.ServerSelection, err = s.readServerSelection()
|
||||
if err != nil {
|
||||
return provider, fmt.Errorf("server selection: %w", err)
|
||||
}
|
||||
|
||||
return provider, nil
|
||||
}
|
||||
@@ -1,101 +1,32 @@
|
||||
package files
|
||||
|
||||
import (
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"github.com/qdm12/gluetun/internal/configuration/settings"
|
||||
)
|
||||
|
||||
type Source struct {
|
||||
rootDirectory string
|
||||
environ map[string]string
|
||||
warner Warner
|
||||
cached struct {
|
||||
wireguardLoaded bool
|
||||
wireguardConf WireguardConfig
|
||||
}
|
||||
wireguardConfigPath string
|
||||
}
|
||||
|
||||
func New(warner Warner) (source *Source) {
|
||||
osEnviron := os.Environ()
|
||||
environ := make(map[string]string, len(osEnviron))
|
||||
for _, pair := range osEnviron {
|
||||
const maxSplit = 2
|
||||
split := strings.SplitN(pair, "=", maxSplit)
|
||||
environ[split[0]] = split[1]
|
||||
}
|
||||
|
||||
func New() *Source {
|
||||
const wireguardConfigPath = "/gluetun/wireguard/wg0.conf"
|
||||
return &Source{
|
||||
rootDirectory: "/gluetun",
|
||||
environ: environ,
|
||||
warner: warner,
|
||||
wireguardConfigPath: wireguardConfigPath,
|
||||
}
|
||||
}
|
||||
|
||||
func (s *Source) String() string { return "files" }
|
||||
|
||||
func (s *Source) Get(key string) (value string, isSet bool) {
|
||||
if key == "" {
|
||||
return "", false
|
||||
}
|
||||
// TODO v4 custom environment variable to set the files parent directory
|
||||
// and not to set each file to a specific path
|
||||
envKey := strings.ToUpper(key)
|
||||
envKey = strings.ReplaceAll(envKey, "-", "_")
|
||||
envKey += "_FILE"
|
||||
path := s.environ[envKey]
|
||||
if path == "" {
|
||||
path = filepath.Join(s.rootDirectory, key)
|
||||
}
|
||||
|
||||
// Special file handling
|
||||
switch key {
|
||||
// TODO timezone from /etc/localtime
|
||||
case "client.crt", "client.key", "openvpn_encrypted_key":
|
||||
value, isSet, err := ReadPEMFile(path)
|
||||
if err != nil {
|
||||
s.warner.Warnf("skipping %s: parsing PEM: %s", path, err)
|
||||
}
|
||||
return value, isSet
|
||||
case "wireguard_private_key":
|
||||
return strPtrToStringIsSet(s.lazyLoadWireguardConf().PrivateKey)
|
||||
case "wireguard_preshared_key":
|
||||
return strPtrToStringIsSet(s.lazyLoadWireguardConf().PreSharedKey)
|
||||
case "wireguard_addresses":
|
||||
return strPtrToStringIsSet(s.lazyLoadWireguardConf().Addresses)
|
||||
case "wireguard_public_key":
|
||||
return strPtrToStringIsSet(s.lazyLoadWireguardConf().PublicKey)
|
||||
case "wireguard_endpoint_ip":
|
||||
return strPtrToStringIsSet(s.lazyLoadWireguardConf().EndpointIP)
|
||||
case "wireguard_endpoint_port":
|
||||
return strPtrToStringIsSet(s.lazyLoadWireguardConf().EndpointPort)
|
||||
}
|
||||
|
||||
value, isSet, err := ReadFromFile(path)
|
||||
func (s *Source) Read() (settings settings.Settings, err error) {
|
||||
settings.VPN, err = s.readVPN()
|
||||
if err != nil {
|
||||
s.warner.Warnf("skipping %s: reading file: %s", path, err)
|
||||
return settings, err
|
||||
}
|
||||
return value, isSet
|
||||
}
|
||||
|
||||
func (s *Source) KeyTransform(key string) string {
|
||||
switch key {
|
||||
// TODO v4 remove these irregular cases
|
||||
case "OPENVPN_KEY":
|
||||
return "client.key"
|
||||
case "OPENVPN_CERT":
|
||||
return "client.crt"
|
||||
case "OPENVPN_ENCRYPTED_KEY":
|
||||
return "openvpn_encrypted_key"
|
||||
default:
|
||||
key = strings.ToLower(key) // HTTPROXY_USER -> httpproxy_user
|
||||
return key
|
||||
settings.System, err = s.readSystem()
|
||||
if err != nil {
|
||||
return settings, err
|
||||
}
|
||||
}
|
||||
|
||||
func strPtrToStringIsSet(ptr *string) (s string, isSet bool) {
|
||||
if ptr == nil {
|
||||
return "", false
|
||||
}
|
||||
return *ptr, true
|
||||
return settings, nil
|
||||
}
|
||||
|
||||
16
internal/configuration/sources/files/serverselection.go
Normal file
16
internal/configuration/sources/files/serverselection.go
Normal file
@@ -0,0 +1,16 @@
|
||||
package files
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/qdm12/gluetun/internal/configuration/settings"
|
||||
)
|
||||
|
||||
func (s *Source) readServerSelection() (selection settings.ServerSelection, err error) {
|
||||
selection.Wireguard, err = s.readWireguardSelection()
|
||||
if err != nil {
|
||||
return selection, fmt.Errorf("wireguard: %w", err)
|
||||
}
|
||||
|
||||
return selection, nil
|
||||
}
|
||||
10
internal/configuration/sources/files/system.go
Normal file
10
internal/configuration/sources/files/system.go
Normal file
@@ -0,0 +1,10 @@
|
||||
package files
|
||||
|
||||
import (
|
||||
"github.com/qdm12/gluetun/internal/configuration/settings"
|
||||
)
|
||||
|
||||
func (s *Source) readSystem() (system settings.System, err error) {
|
||||
// TODO timezone from /etc/localtime
|
||||
return system, nil
|
||||
}
|
||||
26
internal/configuration/sources/files/vpn.go
Normal file
26
internal/configuration/sources/files/vpn.go
Normal file
@@ -0,0 +1,26 @@
|
||||
package files
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/qdm12/gluetun/internal/configuration/settings"
|
||||
)
|
||||
|
||||
func (s *Source) readVPN() (vpn settings.VPN, err error) {
|
||||
vpn.Provider, err = s.readProvider()
|
||||
if err != nil {
|
||||
return vpn, fmt.Errorf("provider: %w", err)
|
||||
}
|
||||
|
||||
vpn.OpenVPN, err = s.readOpenVPN()
|
||||
if err != nil {
|
||||
return vpn, fmt.Errorf("OpenVPN: %w", err)
|
||||
}
|
||||
|
||||
vpn.Wireguard, err = s.readWireguard()
|
||||
if err != nil {
|
||||
return vpn, fmt.Errorf("wireguard: %w", err)
|
||||
}
|
||||
|
||||
return vpn, nil
|
||||
}
|
||||
@@ -1,115 +1,124 @@
|
||||
package files
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"net/netip"
|
||||
"regexp"
|
||||
"strings"
|
||||
|
||||
"github.com/qdm12/gluetun/internal/configuration/settings"
|
||||
"golang.zx2c4.com/wireguard/wgctrl/wgtypes"
|
||||
"gopkg.in/ini.v1"
|
||||
)
|
||||
|
||||
func (s *Source) lazyLoadWireguardConf() WireguardConfig {
|
||||
if s.cached.wireguardLoaded {
|
||||
return s.cached.wireguardConf
|
||||
}
|
||||
|
||||
s.cached.wireguardLoaded = true
|
||||
var err error
|
||||
s.cached.wireguardConf, err = ParseWireguardConf(filepath.Join(s.rootDirectory, "wireguard", "wg0.conf"))
|
||||
func (s *Source) readWireguard() (wireguard settings.Wireguard, err error) {
|
||||
fileStringPtr, err := ReadFromFile(s.wireguardConfigPath)
|
||||
if err != nil {
|
||||
s.warner.Warnf("skipping Wireguard config: %s", err)
|
||||
return wireguard, fmt.Errorf("reading file: %w", err)
|
||||
}
|
||||
return s.cached.wireguardConf
|
||||
}
|
||||
|
||||
type WireguardConfig struct {
|
||||
PrivateKey *string
|
||||
PreSharedKey *string
|
||||
Addresses *string
|
||||
PublicKey *string
|
||||
EndpointIP *string
|
||||
EndpointPort *string
|
||||
if fileStringPtr == nil {
|
||||
return wireguard, nil
|
||||
}
|
||||
|
||||
rawData := []byte(*fileStringPtr)
|
||||
return ParseWireguardConf(rawData)
|
||||
}
|
||||
|
||||
var (
|
||||
regexINISectionNotExist = regexp.MustCompile(`^section ".+" does not exist$`)
|
||||
regexINIKeyNotExist = regexp.MustCompile(`key ".*" not exists$`)
|
||||
)
|
||||
|
||||
func ParseWireguardConf(path string) (config WireguardConfig, err error) {
|
||||
iniFile, err := ini.InsensitiveLoad(path)
|
||||
func ParseWireguardConf(rawData []byte) (wireguard settings.Wireguard, err error) {
|
||||
iniFile, err := ini.Load(rawData)
|
||||
if err != nil {
|
||||
if errors.Is(err, os.ErrNotExist) {
|
||||
return WireguardConfig{}, nil
|
||||
}
|
||||
return WireguardConfig{}, fmt.Errorf("loading ini from reader: %w", err)
|
||||
return wireguard, fmt.Errorf("loading ini from reader: %w", err)
|
||||
}
|
||||
|
||||
interfaceSection, err := iniFile.GetSection("Interface")
|
||||
if err == nil {
|
||||
config.PrivateKey, config.Addresses = parseWireguardInterfaceSection(interfaceSection)
|
||||
err = parseWireguardInterfaceSection(interfaceSection, &wireguard)
|
||||
if err != nil {
|
||||
return wireguard, fmt.Errorf("parsing interface section: %w", err)
|
||||
}
|
||||
} else if !regexINISectionNotExist.MatchString(err.Error()) {
|
||||
// can never happen
|
||||
return WireguardConfig{}, fmt.Errorf("getting interface section: %w", err)
|
||||
return wireguard, fmt.Errorf("getting interface section: %w", err)
|
||||
}
|
||||
|
||||
peerSection, err := iniFile.GetSection("Peer")
|
||||
if err == nil {
|
||||
config.PreSharedKey, config.PublicKey, config.EndpointIP,
|
||||
config.EndpointPort = parseWireguardPeerSection(peerSection)
|
||||
wireguard.PreSharedKey, err = parseINIWireguardKey(peerSection, "PresharedKey")
|
||||
if err != nil {
|
||||
return wireguard, fmt.Errorf("parsing peer section: %w", err)
|
||||
}
|
||||
} else if !regexINISectionNotExist.MatchString(err.Error()) {
|
||||
// can never happen
|
||||
return WireguardConfig{}, fmt.Errorf("getting peer section: %w", err)
|
||||
return wireguard, fmt.Errorf("getting peer section: %w", err)
|
||||
}
|
||||
|
||||
return config, nil
|
||||
return wireguard, nil
|
||||
}
|
||||
|
||||
func parseWireguardInterfaceSection(interfaceSection *ini.Section) (
|
||||
privateKey, addresses *string) {
|
||||
privateKey = getINIKeyFromSection(interfaceSection, "PrivateKey")
|
||||
addresses = getINIKeyFromSection(interfaceSection, "Address")
|
||||
return privateKey, addresses
|
||||
}
|
||||
|
||||
var (
|
||||
ErrEndpointHostNotIP = errors.New("endpoint host is not an IP")
|
||||
)
|
||||
|
||||
func parseWireguardPeerSection(peerSection *ini.Section) (
|
||||
preSharedKey, publicKey, endpointIP, endpointPort *string) {
|
||||
preSharedKey = getINIKeyFromSection(peerSection, "PresharedKey")
|
||||
publicKey = getINIKeyFromSection(peerSection, "PublicKey")
|
||||
endpoint := getINIKeyFromSection(peerSection, "Endpoint")
|
||||
if endpoint != nil {
|
||||
parts := strings.Split(*endpoint, ":")
|
||||
endpointIP = &parts[0]
|
||||
const partsWithPort = 2
|
||||
if len(parts) >= partsWithPort {
|
||||
endpointPort = new(string)
|
||||
*endpointPort = strings.Join(parts[1:], ":")
|
||||
}
|
||||
func parseWireguardInterfaceSection(interfaceSection *ini.Section,
|
||||
wireguard *settings.Wireguard) (err error) {
|
||||
wireguard.PrivateKey, err = parseINIWireguardKey(interfaceSection, "PrivateKey")
|
||||
if err != nil {
|
||||
return err // error is already wrapped correctly
|
||||
}
|
||||
|
||||
return preSharedKey, publicKey, endpointIP, endpointPort
|
||||
wireguard.Addresses, err = parseINIWireguardAddress(interfaceSection)
|
||||
if err != nil {
|
||||
return err // error is already wrapped correctly
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
var (
|
||||
regexINIKeyNotExist = regexp.MustCompile(`key ".*" not exists$`)
|
||||
)
|
||||
|
||||
func getINIKeyFromSection(section *ini.Section, key string) (value *string) {
|
||||
iniKey, err := section.GetKey(key)
|
||||
func parseINIWireguardKey(section *ini.Section, keyName string) (
|
||||
key *string, err error) {
|
||||
iniKey, err := section.GetKey(keyName)
|
||||
if err != nil {
|
||||
if regexINIKeyNotExist.MatchString(err.Error()) {
|
||||
return nil
|
||||
return nil, nil //nolint:nilnil
|
||||
}
|
||||
// can never happen
|
||||
panic(fmt.Sprintf("getting key %q: %s", key, err))
|
||||
return nil, fmt.Errorf("getting %s key: %w", keyName, err)
|
||||
}
|
||||
value = new(string)
|
||||
*value = iniKey.String()
|
||||
return value
|
||||
|
||||
key = new(string)
|
||||
*key = iniKey.String()
|
||||
_, err = wgtypes.ParseKey(*key)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("parsing %s: %s: %w", keyName, *key, err)
|
||||
}
|
||||
return key, nil
|
||||
}
|
||||
|
||||
func parseINIWireguardAddress(section *ini.Section) (
|
||||
addresses []netip.Prefix, err error) {
|
||||
addressKey, err := section.GetKey("Address")
|
||||
if err != nil {
|
||||
if regexINIKeyNotExist.MatchString(err.Error()) {
|
||||
return nil, nil
|
||||
}
|
||||
// can never happen
|
||||
return nil, fmt.Errorf("getting Address key: %w", err)
|
||||
}
|
||||
|
||||
addressStrings := strings.Split(addressKey.String(), ",")
|
||||
addresses = make([]netip.Prefix, len(addressStrings))
|
||||
for i, addressString := range addressStrings {
|
||||
addressString = strings.TrimSpace(addressString)
|
||||
if !strings.ContainsRune(addressString, '/') {
|
||||
addressString += "/32"
|
||||
}
|
||||
addresses[i], err = netip.ParsePrefix(addressString)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("parsing address: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
return addresses, nil
|
||||
}
|
||||
|
||||
@@ -1,42 +1,48 @@
|
||||
package files
|
||||
|
||||
import (
|
||||
"net/netip"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"testing"
|
||||
|
||||
"github.com/qdm12/gluetun/internal/configuration/settings"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
"gopkg.in/ini.v1"
|
||||
)
|
||||
|
||||
func ptrTo[T any](value T) *T { return &value }
|
||||
|
||||
func Test_Source_ParseWireguardConf(t *testing.T) {
|
||||
func Test_Source_readWireguard(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
t.Run("fail reading from file", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
dirPath := t.TempDir()
|
||||
wireguard, err := ParseWireguardConf(dirPath)
|
||||
assert.Equal(t, WireguardConfig{}, wireguard)
|
||||
source := &Source{
|
||||
wireguardConfigPath: dirPath,
|
||||
}
|
||||
wireguard, err := source.readWireguard()
|
||||
assert.Equal(t, settings.Wireguard{}, wireguard)
|
||||
assert.Error(t, err)
|
||||
assert.Regexp(t, `loading ini from reader: BOM: read .+: is a directory`, err.Error())
|
||||
assert.Regexp(t, `reading file: read .+: is a directory`, err.Error())
|
||||
})
|
||||
|
||||
t.Run("no file", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
noFile := filepath.Join(t.TempDir(), "doesnotexist")
|
||||
wireguard, err := ParseWireguardConf(noFile)
|
||||
assert.Equal(t, WireguardConfig{}, wireguard)
|
||||
source := &Source{
|
||||
wireguardConfigPath: noFile,
|
||||
}
|
||||
wireguard, err := source.readWireguard()
|
||||
assert.Equal(t, settings.Wireguard{}, wireguard)
|
||||
assert.NoError(t, err)
|
||||
})
|
||||
|
||||
testCases := map[string]struct {
|
||||
fileContent string
|
||||
wireguard WireguardConfig
|
||||
wireguard settings.Wireguard
|
||||
errMessage string
|
||||
}{
|
||||
"ini load error": {
|
||||
@@ -44,14 +50,14 @@ func Test_Source_ParseWireguardConf(t *testing.T) {
|
||||
errMessage: "loading ini from reader: key-value delimiter not found: invalid",
|
||||
},
|
||||
"empty file": {},
|
||||
"interface_section_missing": {
|
||||
"interface section parsing error": {
|
||||
fileContent: `
|
||||
[Peer]
|
||||
PresharedKey = YJ680VN+dGrdsWNjSFqZ6vvwuiNhbq502ZL3G7Q3o3g=
|
||||
[Interface]
|
||||
PrivateKey = x
|
||||
`,
|
||||
wireguard: WireguardConfig{
|
||||
PreSharedKey: ptrTo("YJ680VN+dGrdsWNjSFqZ6vvwuiNhbq502ZL3G7Q3o3g="),
|
||||
},
|
||||
errMessage: "parsing interface section: parsing PrivateKey: " +
|
||||
"x: wgtypes: failed to parse base64-encoded key: " +
|
||||
"illegal base64 data at input byte 0",
|
||||
},
|
||||
"success": {
|
||||
fileContent: `
|
||||
@@ -63,10 +69,12 @@ DNS = 193.138.218.74
|
||||
[Peer]
|
||||
PresharedKey = YJ680VN+dGrdsWNjSFqZ6vvwuiNhbq502ZL3G7Q3o3g=
|
||||
`,
|
||||
wireguard: WireguardConfig{
|
||||
wireguard: settings.Wireguard{
|
||||
PrivateKey: ptrTo("QOlCgyA/Sn/c/+YNTIEohrjm8IZV+OZ2AUFIoX20sk8="),
|
||||
PreSharedKey: ptrTo("YJ680VN+dGrdsWNjSFqZ6vvwuiNhbq502ZL3G7Q3o3g="),
|
||||
Addresses: ptrTo("10.38.22.35/32"),
|
||||
Addresses: []netip.Prefix{
|
||||
netip.PrefixFrom(netip.AddrFrom4([4]byte{10, 38, 22, 35}), 32),
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
@@ -80,7 +88,11 @@ PresharedKey = YJ680VN+dGrdsWNjSFqZ6vvwuiNhbq502ZL3G7Q3o3g=
|
||||
err := os.WriteFile(configFile, []byte(testCase.fileContent), 0600)
|
||||
require.NoError(t, err)
|
||||
|
||||
wireguard, err := ParseWireguardConf(configFile)
|
||||
source := &Source{
|
||||
wireguardConfigPath: configFile,
|
||||
}
|
||||
|
||||
wireguard, err := source.readWireguard()
|
||||
|
||||
assert.Equal(t, testCase.wireguard, wireguard)
|
||||
if testCase.errMessage != "" {
|
||||
@@ -97,26 +109,34 @@ func Test_parseWireguardInterfaceSection(t *testing.T) {
|
||||
|
||||
testCases := map[string]struct {
|
||||
iniData string
|
||||
privateKey *string
|
||||
addresses *string
|
||||
wireguard settings.Wireguard
|
||||
errMessage string
|
||||
}{
|
||||
"no_fields": {
|
||||
iniData: `[Interface]`,
|
||||
},
|
||||
"only_private_key": {
|
||||
"private key error": {
|
||||
iniData: `[Interface]
|
||||
PrivateKey = x
|
||||
`,
|
||||
privateKey: ptrTo("x"),
|
||||
PrivateKey = x`,
|
||||
errMessage: "parsing PrivateKey: x: " +
|
||||
"wgtypes: failed to parse base64-encoded key: " +
|
||||
"illegal base64 data at input byte 0",
|
||||
},
|
||||
"all_fields": {
|
||||
"address error": {
|
||||
iniData: `[Interface]
|
||||
Address = x
|
||||
`,
|
||||
errMessage: "parsing address: netip.ParsePrefix(\"x/32\"): ParseAddr(\"x\"): unable to parse IP",
|
||||
},
|
||||
"success": {
|
||||
iniData: `
|
||||
[Interface]
|
||||
PrivateKey = QOlCgyA/Sn/c/+YNTIEohrjm8IZV+OZ2AUFIoX20sk8=
|
||||
Address = 10.38.22.35/32
|
||||
`,
|
||||
privateKey: ptrTo("QOlCgyA/Sn/c/+YNTIEohrjm8IZV+OZ2AUFIoX20sk8="),
|
||||
addresses: ptrTo("10.38.22.35/32"),
|
||||
wireguard: settings.Wireguard{
|
||||
PrivateKey: ptrTo("QOlCgyA/Sn/c/+YNTIEohrjm8IZV+OZ2AUFIoX20sk8="),
|
||||
Addresses: []netip.Prefix{
|
||||
netip.PrefixFrom(netip.AddrFrom4([4]byte{10, 38, 22, 35}), 32),
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
@@ -130,74 +150,109 @@ Address = 10.38.22.35/32
|
||||
iniSection, err := iniFile.GetSection("Interface")
|
||||
require.NoError(t, err)
|
||||
|
||||
privateKey, addresses := parseWireguardInterfaceSection(iniSection)
|
||||
var wireguard settings.Wireguard
|
||||
err = parseWireguardInterfaceSection(iniSection, &wireguard)
|
||||
|
||||
assert.Equal(t, testCase.privateKey, privateKey)
|
||||
assert.Equal(t, testCase.addresses, addresses)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func Test_parseWireguardPeerSection(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
testCases := map[string]struct {
|
||||
iniData string
|
||||
preSharedKey *string
|
||||
publicKey *string
|
||||
endpointIP *string
|
||||
endpointPort *string
|
||||
errMessage string
|
||||
}{
|
||||
"public key set": {
|
||||
iniData: `[Peer]
|
||||
PublicKey = QOlCgyA/Sn/c/+YNTIEohrjm8IZV+OZ2AUFIoX20sk8=`,
|
||||
publicKey: ptrTo("QOlCgyA/Sn/c/+YNTIEohrjm8IZV+OZ2AUFIoX20sk8="),
|
||||
},
|
||||
"endpoint_only_host": {
|
||||
iniData: `[Peer]
|
||||
Endpoint = x`,
|
||||
endpointIP: ptrTo("x"),
|
||||
},
|
||||
"endpoint_no_port": {
|
||||
iniData: `[Peer]
|
||||
Endpoint = x:`,
|
||||
endpointIP: ptrTo("x"),
|
||||
endpointPort: ptrTo(""),
|
||||
},
|
||||
"valid_endpoint": {
|
||||
iniData: `[Peer]
|
||||
Endpoint = 1.2.3.4:51820`,
|
||||
endpointIP: ptrTo("1.2.3.4"),
|
||||
endpointPort: ptrTo("51820"),
|
||||
},
|
||||
"all_set": {
|
||||
iniData: `[Peer]
|
||||
PublicKey = QOlCgyA/Sn/c/+YNTIEohrjm8IZV+OZ2AUFIoX20sk8=
|
||||
Endpoint = 1.2.3.4:51820`,
|
||||
publicKey: ptrTo("QOlCgyA/Sn/c/+YNTIEohrjm8IZV+OZ2AUFIoX20sk8="),
|
||||
endpointIP: ptrTo("1.2.3.4"),
|
||||
endpointPort: ptrTo("51820"),
|
||||
},
|
||||
}
|
||||
|
||||
for testName, testCase := range testCases {
|
||||
testCase := testCase
|
||||
t.Run(testName, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
iniFile, err := ini.Load([]byte(testCase.iniData))
|
||||
require.NoError(t, err)
|
||||
iniSection, err := iniFile.GetSection("Peer")
|
||||
require.NoError(t, err)
|
||||
|
||||
preSharedKey, publicKey, endpointIP,
|
||||
endpointPort := parseWireguardPeerSection(iniSection)
|
||||
|
||||
assert.Equal(t, testCase.preSharedKey, preSharedKey)
|
||||
assert.Equal(t, testCase.publicKey, publicKey)
|
||||
assert.Equal(t, testCase.endpointIP, endpointIP)
|
||||
assert.Equal(t, testCase.endpointPort, endpointPort)
|
||||
assert.Equal(t, testCase.wireguard, wireguard)
|
||||
if testCase.errMessage != "" {
|
||||
assert.EqualError(t, err, testCase.errMessage)
|
||||
} else {
|
||||
assert.NoError(t, err)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func Test_parseINIWireguardKey(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
testCases := map[string]struct {
|
||||
fileContent string
|
||||
keyName string
|
||||
key *string
|
||||
errMessage string
|
||||
}{
|
||||
"key does not exist": {
|
||||
fileContent: `[Interface]`,
|
||||
keyName: "PrivateKey",
|
||||
},
|
||||
"bad Wireguard key": {
|
||||
fileContent: `[Interface]
|
||||
PrivateKey = x`,
|
||||
keyName: "PrivateKey",
|
||||
errMessage: "parsing PrivateKey: x: " +
|
||||
"wgtypes: failed to parse base64-encoded key: " +
|
||||
"illegal base64 data at input byte 0",
|
||||
},
|
||||
"success": {
|
||||
fileContent: `[Interface]
|
||||
PrivateKey = QOlCgyA/Sn/c/+YNTIEohrjm8IZV+OZ2AUFIoX20sk8=`,
|
||||
keyName: "PrivateKey",
|
||||
key: ptrTo("QOlCgyA/Sn/c/+YNTIEohrjm8IZV+OZ2AUFIoX20sk8="),
|
||||
},
|
||||
}
|
||||
|
||||
for testName, testCase := range testCases {
|
||||
testCase := testCase
|
||||
t.Run(testName, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
iniFile, err := ini.Load([]byte(testCase.fileContent))
|
||||
require.NoError(t, err)
|
||||
iniSection, err := iniFile.GetSection("Interface")
|
||||
require.NoError(t, err)
|
||||
|
||||
key, err := parseINIWireguardKey(iniSection, testCase.keyName)
|
||||
|
||||
assert.Equal(t, testCase.key, key)
|
||||
if testCase.errMessage != "" {
|
||||
assert.EqualError(t, err, testCase.errMessage)
|
||||
} else {
|
||||
assert.NoError(t, err)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func Test_parseINIWireguardAddress(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
testCases := map[string]struct {
|
||||
fileContent string
|
||||
addresses []netip.Prefix
|
||||
errMessage string
|
||||
}{
|
||||
"key does not exist": {
|
||||
fileContent: `[Interface]`,
|
||||
},
|
||||
"bad address": {
|
||||
fileContent: `[Interface]
|
||||
Address = x`,
|
||||
errMessage: "parsing address: netip.ParsePrefix(\"x/32\"): ParseAddr(\"x\"): unable to parse IP",
|
||||
},
|
||||
"success": {
|
||||
fileContent: `[Interface]
|
||||
Address = 1.2.3.4/32, 5.6.7.8/32`,
|
||||
addresses: []netip.Prefix{
|
||||
netip.PrefixFrom(netip.AddrFrom4([4]byte{1, 2, 3, 4}), 32),
|
||||
netip.PrefixFrom(netip.AddrFrom4([4]byte{5, 6, 7, 8}), 32),
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for testName, testCase := range testCases {
|
||||
testCase := testCase
|
||||
t.Run(testName, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
iniFile, err := ini.Load([]byte(testCase.fileContent))
|
||||
require.NoError(t, err)
|
||||
iniSection, err := iniFile.GetSection("Interface")
|
||||
require.NoError(t, err)
|
||||
|
||||
addresses, err := parseINIWireguardAddress(iniSection)
|
||||
|
||||
assert.Equal(t, testCase.addresses, addresses)
|
||||
if testCase.errMessage != "" {
|
||||
assert.EqualError(t, err, testCase.errMessage)
|
||||
} else {
|
||||
|
||||
83
internal/configuration/sources/files/wireguardselection.go
Normal file
83
internal/configuration/sources/files/wireguardselection.go
Normal file
@@ -0,0 +1,83 @@
|
||||
package files
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"net"
|
||||
"net/netip"
|
||||
|
||||
"github.com/qdm12/gluetun/internal/configuration/settings"
|
||||
"github.com/qdm12/govalid/port"
|
||||
"gopkg.in/ini.v1"
|
||||
)
|
||||
|
||||
var (
|
||||
ErrEndpointHostNotIP = errors.New("endpoint host is not an IP")
|
||||
)
|
||||
|
||||
func (s *Source) readWireguardSelection() (selection settings.WireguardSelection, err error) {
|
||||
fileStringPtr, err := ReadFromFile(s.wireguardConfigPath)
|
||||
if err != nil {
|
||||
return selection, fmt.Errorf("reading file: %w", err)
|
||||
}
|
||||
|
||||
if fileStringPtr == nil {
|
||||
return selection, nil
|
||||
}
|
||||
|
||||
rawData := []byte(*fileStringPtr)
|
||||
iniFile, err := ini.Load(rawData)
|
||||
if err != nil {
|
||||
return selection, fmt.Errorf("loading ini from reader: %w", err)
|
||||
}
|
||||
|
||||
peerSection, err := iniFile.GetSection("Peer")
|
||||
if err == nil {
|
||||
err = parseWireguardPeerSection(peerSection, &selection)
|
||||
if err != nil {
|
||||
return selection, fmt.Errorf("parsing peer section: %w", err)
|
||||
}
|
||||
} else if !regexINISectionNotExist.MatchString(err.Error()) {
|
||||
// can never happen
|
||||
return selection, fmt.Errorf("getting peer section: %w", err)
|
||||
}
|
||||
|
||||
return selection, nil
|
||||
}
|
||||
|
||||
func parseWireguardPeerSection(peerSection *ini.Section,
|
||||
selection *settings.WireguardSelection) (err error) {
|
||||
publicKeyPtr, err := parseINIWireguardKey(peerSection, "PublicKey")
|
||||
if err != nil {
|
||||
return err // error is already wrapped correctly
|
||||
} else if publicKeyPtr != nil {
|
||||
selection.PublicKey = *publicKeyPtr
|
||||
}
|
||||
|
||||
endpointKey, err := peerSection.GetKey("Endpoint")
|
||||
if err == nil {
|
||||
endpoint := endpointKey.String()
|
||||
host, portString, err := net.SplitHostPort(endpoint)
|
||||
if err != nil {
|
||||
return fmt.Errorf("splitting endpoint: %w", err)
|
||||
}
|
||||
|
||||
ip, err := netip.ParseAddr(host)
|
||||
if err != nil {
|
||||
return fmt.Errorf("%w: %w", ErrEndpointHostNotIP, err)
|
||||
}
|
||||
|
||||
endpointPort, err := port.Validate(portString)
|
||||
if err != nil {
|
||||
return fmt.Errorf("port from Endpoint key: %w", err)
|
||||
}
|
||||
|
||||
selection.EndpointIP = ip
|
||||
selection.EndpointPort = &endpointPort
|
||||
} else if !regexINIKeyNotExist.MatchString(err.Error()) {
|
||||
// can never happen
|
||||
return fmt.Errorf("getting endpoint key: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
181
internal/configuration/sources/files/wireguardselection_test.go
Normal file
181
internal/configuration/sources/files/wireguardselection_test.go
Normal file
@@ -0,0 +1,181 @@
|
||||
package files
|
||||
|
||||
import (
|
||||
"net/netip"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"testing"
|
||||
|
||||
"github.com/qdm12/gluetun/internal/configuration/settings"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
"gopkg.in/ini.v1"
|
||||
)
|
||||
|
||||
func uint16Ptr(n uint16) *uint16 { return &n }
|
||||
|
||||
func Test_Source_readWireguardSelection(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
t.Run("fail reading from file", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
dirPath := t.TempDir()
|
||||
source := &Source{
|
||||
wireguardConfigPath: dirPath,
|
||||
}
|
||||
wireguard, err := source.readWireguardSelection()
|
||||
assert.Equal(t, settings.WireguardSelection{}, wireguard)
|
||||
assert.Error(t, err)
|
||||
assert.Regexp(t, `reading file: read .+: is a directory`, err.Error())
|
||||
})
|
||||
|
||||
t.Run("no file", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
noFile := filepath.Join(t.TempDir(), "doesnotexist")
|
||||
source := &Source{
|
||||
wireguardConfigPath: noFile,
|
||||
}
|
||||
wireguard, err := source.readWireguardSelection()
|
||||
assert.Equal(t, settings.WireguardSelection{}, wireguard)
|
||||
assert.NoError(t, err)
|
||||
})
|
||||
|
||||
testCases := map[string]struct {
|
||||
fileContent string
|
||||
selection settings.WireguardSelection
|
||||
errMessage string
|
||||
}{
|
||||
"ini load error": {
|
||||
fileContent: "invalid",
|
||||
errMessage: "loading ini from reader: key-value delimiter not found: invalid",
|
||||
},
|
||||
"empty file": {},
|
||||
"peer section parsing error": {
|
||||
fileContent: `
|
||||
[Peer]
|
||||
PublicKey = x
|
||||
`,
|
||||
errMessage: "parsing peer section: parsing PublicKey: " +
|
||||
"x: wgtypes: failed to parse base64-encoded key: " +
|
||||
"illegal base64 data at input byte 0",
|
||||
},
|
||||
"success": {
|
||||
fileContent: `
|
||||
[Peer]
|
||||
PublicKey = QOlCgyA/Sn/c/+YNTIEohrjm8IZV+OZ2AUFIoX20sk8=
|
||||
Endpoint = 1.2.3.4:51820
|
||||
`,
|
||||
selection: settings.WireguardSelection{
|
||||
PublicKey: "QOlCgyA/Sn/c/+YNTIEohrjm8IZV+OZ2AUFIoX20sk8=",
|
||||
EndpointIP: netip.AddrFrom4([4]byte{1, 2, 3, 4}),
|
||||
EndpointPort: uint16Ptr(51820),
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for testName, testCase := range testCases {
|
||||
testCase := testCase
|
||||
t.Run(testName, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
configFile := filepath.Join(t.TempDir(), "wg.conf")
|
||||
err := os.WriteFile(configFile, []byte(testCase.fileContent), 0600)
|
||||
require.NoError(t, err)
|
||||
|
||||
source := &Source{
|
||||
wireguardConfigPath: configFile,
|
||||
}
|
||||
|
||||
wireguard, err := source.readWireguardSelection()
|
||||
|
||||
assert.Equal(t, testCase.selection, wireguard)
|
||||
if testCase.errMessage != "" {
|
||||
assert.EqualError(t, err, testCase.errMessage)
|
||||
} else {
|
||||
assert.NoError(t, err)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func Test_parseWireguardPeerSection(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
testCases := map[string]struct {
|
||||
iniData string
|
||||
selection settings.WireguardSelection
|
||||
errMessage string
|
||||
}{
|
||||
"public key error": {
|
||||
iniData: `[Peer]
|
||||
PublicKey = x`,
|
||||
errMessage: "parsing PublicKey: x: " +
|
||||
"wgtypes: failed to parse base64-encoded key: " +
|
||||
"illegal base64 data at input byte 0",
|
||||
},
|
||||
"public key set": {
|
||||
iniData: `[Peer]
|
||||
PublicKey = QOlCgyA/Sn/c/+YNTIEohrjm8IZV+OZ2AUFIoX20sk8=`,
|
||||
selection: settings.WireguardSelection{
|
||||
PublicKey: "QOlCgyA/Sn/c/+YNTIEohrjm8IZV+OZ2AUFIoX20sk8=",
|
||||
},
|
||||
},
|
||||
"missing port in endpoint": {
|
||||
iniData: `[Peer]
|
||||
Endpoint = x`,
|
||||
errMessage: "splitting endpoint: address x: missing port in address",
|
||||
},
|
||||
"endpoint host is not IP": {
|
||||
iniData: `[Peer]
|
||||
Endpoint = website.com:51820`,
|
||||
errMessage: "endpoint host is not an IP: ParseAddr(\"website.com\"): unexpected character (at \"website.com\")",
|
||||
},
|
||||
"endpoint port is not valid": {
|
||||
iniData: `[Peer]
|
||||
Endpoint = 1.2.3.4:518299`,
|
||||
errMessage: "port from Endpoint key: port cannot be higher than 65535: 518299",
|
||||
},
|
||||
"valid endpoint": {
|
||||
iniData: `[Peer]
|
||||
Endpoint = 1.2.3.4:51820`,
|
||||
selection: settings.WireguardSelection{
|
||||
EndpointIP: netip.AddrFrom4([4]byte{1, 2, 3, 4}),
|
||||
EndpointPort: uint16Ptr(51820),
|
||||
},
|
||||
},
|
||||
"all set": {
|
||||
iniData: `[Peer]
|
||||
PublicKey = QOlCgyA/Sn/c/+YNTIEohrjm8IZV+OZ2AUFIoX20sk8=
|
||||
Endpoint = 1.2.3.4:51820`,
|
||||
selection: settings.WireguardSelection{
|
||||
PublicKey: "QOlCgyA/Sn/c/+YNTIEohrjm8IZV+OZ2AUFIoX20sk8=",
|
||||
EndpointIP: netip.AddrFrom4([4]byte{1, 2, 3, 4}),
|
||||
EndpointPort: uint16Ptr(51820),
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for testName, testCase := range testCases {
|
||||
testCase := testCase
|
||||
t.Run(testName, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
iniFile, err := ini.Load([]byte(testCase.iniData))
|
||||
require.NoError(t, err)
|
||||
iniSection, err := iniFile.GetSection("Peer")
|
||||
require.NoError(t, err)
|
||||
|
||||
var selection settings.WireguardSelection
|
||||
err = parseWireguardPeerSection(iniSection, &selection)
|
||||
|
||||
assert.Equal(t, testCase.selection, selection)
|
||||
if testCase.errMessage != "" {
|
||||
assert.EqualError(t, err, testCase.errMessage)
|
||||
} else {
|
||||
assert.NoError(t, err)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
69
internal/configuration/sources/merge/reader.go
Normal file
69
internal/configuration/sources/merge/reader.go
Normal file
@@ -0,0 +1,69 @@
|
||||
package merge
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/qdm12/gluetun/internal/configuration/settings"
|
||||
)
|
||||
|
||||
type ConfigSource interface {
|
||||
Read() (settings settings.Settings, err error)
|
||||
ReadHealth() (settings settings.Health, err error)
|
||||
String() string
|
||||
}
|
||||
|
||||
type Source struct {
|
||||
sources []ConfigSource
|
||||
}
|
||||
|
||||
func New(sources ...ConfigSource) *Source {
|
||||
return &Source{
|
||||
sources: sources,
|
||||
}
|
||||
}
|
||||
|
||||
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, ", ")
|
||||
}
|
||||
|
||||
// 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 (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)
|
||||
}
|
||||
settings.MergeWith(settingsFromSource)
|
||||
}
|
||||
settings.SetDefaults()
|
||||
return settings, nil
|
||||
}
|
||||
|
||||
// ReadHealth reads the health settings for each source, merging unset fields
|
||||
// with field set by the next source.
|
||||
// It then set defaults to remaining unset fields, and validate
|
||||
// all the fields.
|
||||
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)
|
||||
}
|
||||
settings.MergeWith(settingsFromSource)
|
||||
}
|
||||
settings.SetDefaults()
|
||||
|
||||
err = settings.Validate()
|
||||
if err != nil {
|
||||
return settings, err
|
||||
}
|
||||
|
||||
return settings, nil
|
||||
}
|
||||
5
internal/configuration/sources/secrets/health.go
Normal file
5
internal/configuration/sources/secrets/health.go
Normal file
@@ -0,0 +1,5 @@
|
||||
package secrets
|
||||
|
||||
import "github.com/qdm12/gluetun/internal/configuration/settings"
|
||||
|
||||
func (s *Source) ReadHealth() (settings settings.Health, err error) { return settings, nil }
|
||||
@@ -1,8 +1,58 @@
|
||||
package secrets
|
||||
|
||||
func strPtrToStringIsSet(ptr *string) (s string, isSet bool) {
|
||||
if ptr == nil {
|
||||
return "", false
|
||||
import (
|
||||
"fmt"
|
||||
"net/netip"
|
||||
"strings"
|
||||
|
||||
"github.com/qdm12/gluetun/internal/configuration/sources/files"
|
||||
"github.com/qdm12/gluetun/internal/openvpn/extract"
|
||||
"github.com/qdm12/gosettings/sources/env"
|
||||
)
|
||||
|
||||
func (s *Source) readSecretFileAsStringPtr(secretPathEnvKey, defaultSecretPath string) (
|
||||
stringPtr *string, err error) {
|
||||
path := s.env.String(secretPathEnvKey, env.ForceLowercase(false))
|
||||
if path == "" {
|
||||
path = defaultSecretPath
|
||||
}
|
||||
return *ptr, true
|
||||
return files.ReadFromFile(path)
|
||||
}
|
||||
|
||||
func (s *Source) readPEMSecretFile(secretPathEnvKey, defaultSecretPath string) (
|
||||
base64Ptr *string, err error) {
|
||||
pemData, err := s.readSecretFileAsStringPtr(secretPathEnvKey, defaultSecretPath)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("reading secret file: %w", err)
|
||||
}
|
||||
|
||||
if pemData == nil {
|
||||
return nil, nil //nolint:nilnil
|
||||
}
|
||||
|
||||
base64Data, err := extract.PEM([]byte(*pemData))
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("extracting base64 encoded data from PEM content: %w", err)
|
||||
}
|
||||
|
||||
return &base64Data, nil
|
||||
}
|
||||
|
||||
func parseAddresses(addressesCSV string) (addresses []netip.Prefix, err error) {
|
||||
if addressesCSV == "" {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
addressStrings := strings.Split(addressesCSV, ",")
|
||||
addresses = make([]netip.Prefix, len(addressStrings))
|
||||
for i, addressString := range addressStrings {
|
||||
addressString = strings.TrimSpace(addressString)
|
||||
addresses[i], err = netip.ParsePrefix(addressString)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("parsing address %d of %d: %w",
|
||||
i+1, len(addressStrings), err)
|
||||
}
|
||||
}
|
||||
|
||||
return addresses, nil
|
||||
}
|
||||
|
||||
92
internal/configuration/sources/secrets/helpers_test.go
Normal file
92
internal/configuration/sources/secrets/helpers_test.go
Normal file
@@ -0,0 +1,92 @@
|
||||
package secrets
|
||||
|
||||
import (
|
||||
"os"
|
||||
"path/filepath"
|
||||
"testing"
|
||||
|
||||
"github.com/qdm12/gosettings/sources/env"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func ptrTo[T any](value T) *T { return &value }
|
||||
|
||||
func Test_readSecretFileAsStringPtr(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
testCases := map[string]struct {
|
||||
source func(tempDir string) Source
|
||||
secretPathEnvKey string
|
||||
defaultSecretFileName string
|
||||
setupFile func(tempDir string) error
|
||||
stringPtr *string
|
||||
errWrapped error
|
||||
errMessage string
|
||||
}{
|
||||
"no_secret_file": {
|
||||
defaultSecretFileName: "default_secret_file",
|
||||
secretPathEnvKey: "SECRET_FILE",
|
||||
},
|
||||
"empty_secret_file": {
|
||||
defaultSecretFileName: "default_secret_file",
|
||||
secretPathEnvKey: "SECRET_FILE",
|
||||
setupFile: func(tempDir string) error {
|
||||
secretFilepath := filepath.Join(tempDir, "default_secret_file")
|
||||
return os.WriteFile(secretFilepath, nil, os.ModePerm)
|
||||
},
|
||||
stringPtr: ptrTo(""),
|
||||
},
|
||||
"default_secret_file": {
|
||||
defaultSecretFileName: "default_secret_file",
|
||||
secretPathEnvKey: "SECRET_FILE",
|
||||
setupFile: func(tempDir string) error {
|
||||
secretFilepath := filepath.Join(tempDir, "default_secret_file")
|
||||
return os.WriteFile(secretFilepath, []byte("A"), os.ModePerm)
|
||||
},
|
||||
stringPtr: ptrTo("A"),
|
||||
},
|
||||
"env_specified_secret_file": {
|
||||
source: func(tempDir string) Source {
|
||||
secretFilepath := filepath.Join(tempDir, "secret_file")
|
||||
environ := []string{"SECRET_FILE=" + secretFilepath}
|
||||
return Source{env: *env.New(environ, nil)}
|
||||
},
|
||||
defaultSecretFileName: "default_secret_file",
|
||||
secretPathEnvKey: "SECRET_FILE",
|
||||
setupFile: func(tempDir string) error {
|
||||
secretFilepath := filepath.Join(tempDir, "secret_file")
|
||||
return os.WriteFile(secretFilepath, []byte("B"), os.ModePerm)
|
||||
},
|
||||
stringPtr: ptrTo("B"),
|
||||
},
|
||||
}
|
||||
|
||||
for name, testCase := range testCases {
|
||||
testCase := testCase
|
||||
t.Run(name, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
tempDir := t.TempDir()
|
||||
|
||||
var source Source
|
||||
if testCase.source != nil {
|
||||
source = testCase.source(tempDir)
|
||||
}
|
||||
|
||||
defaultSecretPath := filepath.Join(tempDir, testCase.defaultSecretFileName)
|
||||
if testCase.setupFile != nil {
|
||||
err := testCase.setupFile(tempDir)
|
||||
require.NoError(t, err)
|
||||
}
|
||||
|
||||
stringPtr, err := source.readSecretFileAsStringPtr(
|
||||
testCase.secretPathEnvKey, defaultSecretPath)
|
||||
|
||||
assert.Equal(t, testCase.stringPtr, stringPtr)
|
||||
assert.ErrorIs(t, err, testCase.errWrapped)
|
||||
if testCase.errWrapped != nil {
|
||||
assert.EqualError(t, err, testCase.errMessage)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
27
internal/configuration/sources/secrets/httpproxy.go
Normal file
27
internal/configuration/sources/secrets/httpproxy.go
Normal file
@@ -0,0 +1,27 @@
|
||||
package secrets
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/qdm12/gluetun/internal/configuration/settings"
|
||||
)
|
||||
|
||||
func (s *Source) readHTTPProxy() (settings settings.HTTPProxy, err error) {
|
||||
settings.User, err = s.readSecretFileAsStringPtr(
|
||||
"HTTPPROXY_USER_SECRETFILE",
|
||||
"/run/secrets/httpproxy_user",
|
||||
)
|
||||
if err != nil {
|
||||
return settings, fmt.Errorf("reading HTTP proxy user secret file: %w", err)
|
||||
}
|
||||
|
||||
settings.Password, err = s.readSecretFileAsStringPtr(
|
||||
"HTTPPROXY_PASSWORD_SECRETFILE",
|
||||
"/run/secrets/httpproxy_password",
|
||||
)
|
||||
if err != nil {
|
||||
return settings, fmt.Errorf("reading HTTP proxy password secret file: %w", err)
|
||||
}
|
||||
|
||||
return settings, nil
|
||||
}
|
||||
@@ -1,5 +0,0 @@
|
||||
package secrets
|
||||
|
||||
type Warner interface {
|
||||
Warnf(format string, a ...interface{})
|
||||
}
|
||||
60
internal/configuration/sources/secrets/openvpn.go
Normal file
60
internal/configuration/sources/secrets/openvpn.go
Normal file
@@ -0,0 +1,60 @@
|
||||
package secrets
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/qdm12/gluetun/internal/configuration/settings"
|
||||
)
|
||||
|
||||
func (s *Source) readOpenVPN() (
|
||||
settings settings.OpenVPN, err error) {
|
||||
settings.User, err = s.readSecretFileAsStringPtr(
|
||||
"OPENVPN_USER_SECRETFILE",
|
||||
"/run/secrets/openvpn_user",
|
||||
)
|
||||
if err != nil {
|
||||
return settings, fmt.Errorf("reading user file: %w", err)
|
||||
}
|
||||
|
||||
settings.Password, err = s.readSecretFileAsStringPtr(
|
||||
"OPENVPN_PASSWORD_SECRETFILE",
|
||||
"/run/secrets/openvpn_password",
|
||||
)
|
||||
if err != nil {
|
||||
return settings, fmt.Errorf("reading password file: %w", err)
|
||||
}
|
||||
|
||||
settings.Key, err = s.readPEMSecretFile(
|
||||
"OPENVPN_CLIENTKEY_SECRETFILE",
|
||||
"/run/secrets/openvpn_clientkey",
|
||||
)
|
||||
if err != nil {
|
||||
return settings, fmt.Errorf("reading client key file: %w", err)
|
||||
}
|
||||
|
||||
settings.EncryptedKey, err = s.readPEMSecretFile(
|
||||
"OPENVPN_ENCRYPTED_KEY_SECRETFILE",
|
||||
"/run/secrets/openvpn_encrypted_key",
|
||||
)
|
||||
if err != nil {
|
||||
return settings, fmt.Errorf("reading encrypted key file: %w", err)
|
||||
}
|
||||
|
||||
settings.KeyPassphrase, err = s.readSecretFileAsStringPtr(
|
||||
"OPENVPN_KEY_PASSPHRASE_SECRETFILE",
|
||||
"/run/secrets/openvpn_key_passphrase",
|
||||
)
|
||||
if err != nil {
|
||||
return settings, fmt.Errorf("reading key passphrase file: %w", err)
|
||||
}
|
||||
|
||||
settings.Cert, err = s.readPEMSecretFile(
|
||||
"OPENVPN_CLIENTCRT_SECRETFILE",
|
||||
"/run/secrets/openvpn_clientcrt",
|
||||
)
|
||||
if err != nil {
|
||||
return settings, fmt.Errorf("reading client certificate file: %w", err)
|
||||
}
|
||||
|
||||
return settings, nil
|
||||
}
|
||||
@@ -1,106 +1,46 @@
|
||||
package secrets
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"github.com/qdm12/gluetun/internal/configuration/sources/files"
|
||||
"github.com/qdm12/gluetun/internal/configuration/settings"
|
||||
"github.com/qdm12/gosettings/sources/env"
|
||||
)
|
||||
|
||||
type Source struct {
|
||||
rootDirectory string
|
||||
environ map[string]string
|
||||
warner Warner
|
||||
cached struct {
|
||||
wireguardLoaded bool
|
||||
wireguardConf files.WireguardConfig
|
||||
}
|
||||
env env.Env
|
||||
}
|
||||
|
||||
func New(warner Warner) (source *Source) {
|
||||
const rootDirectory = "/run/secrets"
|
||||
osEnviron := os.Environ()
|
||||
environ := make(map[string]string, len(osEnviron))
|
||||
for _, pair := range osEnviron {
|
||||
const maxSplit = 2
|
||||
split := strings.SplitN(pair, "=", maxSplit)
|
||||
environ[split[0]] = split[1]
|
||||
}
|
||||
|
||||
func New() *Source {
|
||||
handleDeprecatedKey := (func(deprecatedKey, newKey string))(nil)
|
||||
return &Source{
|
||||
rootDirectory: rootDirectory,
|
||||
environ: environ,
|
||||
warner: warner,
|
||||
env: *env.New(os.Environ(), handleDeprecatedKey),
|
||||
}
|
||||
}
|
||||
|
||||
func (s *Source) String() string { return "secret files" }
|
||||
|
||||
func (s *Source) Get(key string) (value string, isSet bool) {
|
||||
if key == "" {
|
||||
return "", false
|
||||
}
|
||||
// TODO v4 custom environment variable to set the secrets parent directory
|
||||
// and not to set each secret file to a specific path
|
||||
envKey := strings.ToUpper(key)
|
||||
envKey = strings.ReplaceAll(envKey, "-", "_")
|
||||
envKey += "_SECRETFILE" // TODO v4 change _SECRETFILE to _FILE
|
||||
path := s.environ[envKey]
|
||||
if path == "" {
|
||||
path = filepath.Join(s.rootDirectory, key)
|
||||
}
|
||||
|
||||
// Special file parsing
|
||||
switch key {
|
||||
// TODO timezone from /etc/localtime
|
||||
case "openvpn_clientcrt", "openvpn_clientkey", "openvpn_encrypted_key":
|
||||
value, isSet, err := files.ReadPEMFile(path)
|
||||
if err != nil {
|
||||
s.warner.Warnf("skipping %s: parsing PEM: %s", path, err)
|
||||
}
|
||||
return value, isSet
|
||||
case "wireguard_private_key":
|
||||
privateKey := s.lazyLoadWireguardConf().PrivateKey
|
||||
if privateKey != nil {
|
||||
return *privateKey, true
|
||||
} // else continue to read from individual secret file
|
||||
case "wireguard_preshared_key":
|
||||
preSharedKey := s.lazyLoadWireguardConf().PreSharedKey
|
||||
if preSharedKey != nil {
|
||||
return *preSharedKey, true
|
||||
} // else continue to read from individual secret file
|
||||
case "wireguard_addresses":
|
||||
addresses := s.lazyLoadWireguardConf().Addresses
|
||||
if addresses != nil {
|
||||
return *addresses, true
|
||||
} // else continue to read from individual secret file
|
||||
case "wireguard_public_key":
|
||||
return strPtrToStringIsSet(s.lazyLoadWireguardConf().PublicKey)
|
||||
case "wireguard_endpoint_ip":
|
||||
return strPtrToStringIsSet(s.lazyLoadWireguardConf().EndpointIP)
|
||||
case "wireguard_endpoint_port":
|
||||
return strPtrToStringIsSet(s.lazyLoadWireguardConf().EndpointPort)
|
||||
}
|
||||
|
||||
value, isSet, err := files.ReadFromFile(path)
|
||||
func (s *Source) Read() (settings settings.Settings, err error) {
|
||||
settings.VPN, err = s.readVPN()
|
||||
if err != nil {
|
||||
s.warner.Warnf("skipping %s: reading file: %s", path, err)
|
||||
return settings, err
|
||||
}
|
||||
return value, isSet
|
||||
}
|
||||
|
||||
func (s *Source) KeyTransform(key string) string {
|
||||
switch key {
|
||||
// TODO v4 remove these irregular cases
|
||||
case "OPENVPN_KEY":
|
||||
return "openvpn_clientkey"
|
||||
case "OPENVPN_CERT":
|
||||
return "openvpn_clientcrt"
|
||||
case "OPENVPN_ENCRYPTED_KEY":
|
||||
return "openvpn_encrypted_key"
|
||||
default:
|
||||
key = strings.ToLower(key) // HTTPROXY_USER -> httpproxy_user
|
||||
return key
|
||||
settings.HTTPProxy, err = s.readHTTPProxy()
|
||||
if err != nil {
|
||||
return settings, err
|
||||
}
|
||||
|
||||
settings.Shadowsocks, err = s.readShadowsocks()
|
||||
if err != nil {
|
||||
return settings, err
|
||||
}
|
||||
|
||||
settings.VPN.Wireguard, err = s.readWireguard()
|
||||
if err != nil {
|
||||
return settings, fmt.Errorf("reading Wireguard: %w", err)
|
||||
}
|
||||
|
||||
return settings, nil
|
||||
}
|
||||
|
||||
@@ -1,102 +0,0 @@
|
||||
package secrets
|
||||
|
||||
import (
|
||||
"os"
|
||||
"path/filepath"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func Test_Source_Get(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
testCases := map[string]struct {
|
||||
makeSource func(tempDir string) (source *Source, err error)
|
||||
key string
|
||||
value string
|
||||
isSet bool
|
||||
}{
|
||||
"empty_key": {
|
||||
makeSource: func(tempDir string) (source *Source, err error) {
|
||||
return &Source{
|
||||
rootDirectory: tempDir,
|
||||
environ: map[string]string{},
|
||||
}, nil
|
||||
},
|
||||
},
|
||||
"no_secret_file": {
|
||||
makeSource: func(tempDir string) (source *Source, err error) {
|
||||
return &Source{
|
||||
rootDirectory: tempDir,
|
||||
environ: map[string]string{},
|
||||
}, nil
|
||||
},
|
||||
key: "test_file",
|
||||
},
|
||||
"empty_secret_file": {
|
||||
makeSource: func(tempDir string) (source *Source, err error) {
|
||||
secretFilepath := filepath.Join(tempDir, "test_file")
|
||||
err = os.WriteFile(secretFilepath, nil, os.ModePerm)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &Source{
|
||||
rootDirectory: tempDir,
|
||||
environ: map[string]string{},
|
||||
}, nil
|
||||
},
|
||||
key: "test_file",
|
||||
isSet: true,
|
||||
},
|
||||
"default_secret_file": {
|
||||
makeSource: func(tempDir string) (source *Source, err error) {
|
||||
secretFilepath := filepath.Join(tempDir, "test_file")
|
||||
err = os.WriteFile(secretFilepath, []byte{'A'}, os.ModePerm)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &Source{
|
||||
rootDirectory: tempDir,
|
||||
environ: map[string]string{},
|
||||
}, nil
|
||||
},
|
||||
key: "test_file",
|
||||
value: "A",
|
||||
isSet: true,
|
||||
},
|
||||
"env_specified_secret_file": {
|
||||
makeSource: func(tempDir string) (source *Source, err error) {
|
||||
secretFilepath := filepath.Join(tempDir, "test_file_custom")
|
||||
err = os.WriteFile(secretFilepath, []byte{'A'}, os.ModePerm)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &Source{
|
||||
rootDirectory: tempDir,
|
||||
environ: map[string]string{
|
||||
"TEST_FILE_SECRETFILE": secretFilepath,
|
||||
},
|
||||
}, nil
|
||||
},
|
||||
key: "test_file",
|
||||
value: "A",
|
||||
isSet: true,
|
||||
},
|
||||
}
|
||||
|
||||
for name, testCase := range testCases {
|
||||
testCase := testCase
|
||||
t.Run(name, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
source, err := testCase.makeSource(t.TempDir())
|
||||
require.NoError(t, err)
|
||||
|
||||
value, isSet := source.Get(testCase.key)
|
||||
assert.Equal(t, testCase.value, value)
|
||||
assert.Equal(t, testCase.isSet, isSet)
|
||||
})
|
||||
}
|
||||
}
|
||||
19
internal/configuration/sources/secrets/shadowsocks.go
Normal file
19
internal/configuration/sources/secrets/shadowsocks.go
Normal file
@@ -0,0 +1,19 @@
|
||||
package secrets
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/qdm12/gluetun/internal/configuration/settings"
|
||||
)
|
||||
|
||||
func (s *Source) readShadowsocks() (settings settings.Shadowsocks, err error) {
|
||||
settings.Password, err = s.readSecretFileAsStringPtr(
|
||||
"SHADOWSOCKS_PASSWORD_SECRETFILE",
|
||||
"/run/secrets/shadowsocks_password",
|
||||
)
|
||||
if err != nil {
|
||||
return settings, fmt.Errorf("reading Shadowsocks password secret file: %w", err)
|
||||
}
|
||||
|
||||
return settings, nil
|
||||
}
|
||||
21
internal/configuration/sources/secrets/vpn.go
Normal file
21
internal/configuration/sources/secrets/vpn.go
Normal file
@@ -0,0 +1,21 @@
|
||||
package secrets
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/qdm12/gluetun/internal/configuration/settings"
|
||||
)
|
||||
|
||||
func (s *Source) readVPN() (vpn settings.VPN, err error) {
|
||||
vpn.OpenVPN, err = s.readOpenVPN()
|
||||
if err != nil {
|
||||
return vpn, fmt.Errorf("reading OpenVPN settings: %w", err)
|
||||
}
|
||||
|
||||
vpn.Wireguard, err = s.readWireguard()
|
||||
if err != nil {
|
||||
return vpn, fmt.Errorf("reading Wireguard settings: %w", err)
|
||||
}
|
||||
|
||||
return vpn, nil
|
||||
}
|
||||
@@ -1,27 +1,52 @@
|
||||
package secrets
|
||||
|
||||
import (
|
||||
"os"
|
||||
"path/filepath"
|
||||
"fmt"
|
||||
|
||||
"github.com/qdm12/gluetun/internal/configuration/settings"
|
||||
"github.com/qdm12/gluetun/internal/configuration/sources/files"
|
||||
)
|
||||
|
||||
func (s *Source) lazyLoadWireguardConf() files.WireguardConfig {
|
||||
if s.cached.wireguardLoaded {
|
||||
return s.cached.wireguardConf
|
||||
}
|
||||
|
||||
path := os.Getenv("WIREGUARD_CONF_SECRETFILE")
|
||||
if path == "" {
|
||||
path = filepath.Join(s.rootDirectory, "wg0.conf")
|
||||
}
|
||||
|
||||
s.cached.wireguardLoaded = true
|
||||
var err error
|
||||
s.cached.wireguardConf, err = files.ParseWireguardConf(path)
|
||||
func (s *Source) readWireguard() (settings settings.Wireguard, err error) {
|
||||
wireguardConf, err := s.readSecretFileAsStringPtr(
|
||||
"WIREGUARD_CONF_SECRETFILE",
|
||||
"/run/secrets/wg0.conf",
|
||||
)
|
||||
if err != nil {
|
||||
s.warner.Warnf("skipping Wireguard config: %s", err)
|
||||
return settings, fmt.Errorf("reading Wireguard conf secret file: %w", err)
|
||||
} else if wireguardConf != nil {
|
||||
// Wireguard ini config file takes precedence over individual secrets
|
||||
return files.ParseWireguardConf([]byte(*wireguardConf))
|
||||
}
|
||||
return s.cached.wireguardConf
|
||||
|
||||
settings.PrivateKey, err = s.readSecretFileAsStringPtr(
|
||||
"WIREGUARD_PRIVATE_KEY_SECRETFILE",
|
||||
"/run/secrets/wireguard_private_key",
|
||||
)
|
||||
if err != nil {
|
||||
return settings, fmt.Errorf("reading private key file: %w", err)
|
||||
}
|
||||
|
||||
settings.PreSharedKey, err = s.readSecretFileAsStringPtr(
|
||||
"WIREGUARD_PRESHARED_KEY_SECRETFILE",
|
||||
"/run/secrets/wireguard_preshared_key",
|
||||
)
|
||||
if err != nil {
|
||||
return settings, fmt.Errorf("reading preshared key file: %w", err)
|
||||
}
|
||||
|
||||
wireguardAddressesCSV, err := s.readSecretFileAsStringPtr(
|
||||
"WIREGUARD_ADDRESSES_SECRETFILE",
|
||||
"/run/secrets/wireguard_addresses",
|
||||
)
|
||||
if err != nil {
|
||||
return settings, fmt.Errorf("reading addresses file: %w", err)
|
||||
} else if wireguardAddressesCSV != nil {
|
||||
settings.Addresses, err = parseAddresses(*wireguardAddressesCSV)
|
||||
if err != nil {
|
||||
return settings, fmt.Errorf("parsing addresses: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
return settings, nil
|
||||
}
|
||||
|
||||
@@ -1,98 +0,0 @@
|
||||
package firewall
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"os/exec"
|
||||
"strconv"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// isDeleteMatchInstruction returns true if the iptables instruction
|
||||
// is a delete instruction by rule matching. It returns false if the
|
||||
// instruction is a delete instruction by line number, or not a delete
|
||||
// instruction.
|
||||
func isDeleteMatchInstruction(instruction string) bool {
|
||||
fields := strings.Fields(instruction)
|
||||
for i, field := range fields {
|
||||
switch {
|
||||
case field != "-D" && field != "--delete": //nolint:goconst
|
||||
continue
|
||||
case i == len(fields)-1: // malformed: missing chain name
|
||||
return false
|
||||
case i == len(fields)-2: // chain name is last field
|
||||
return true
|
||||
default:
|
||||
// chain name is fields[i+1]
|
||||
const base, bitLength = 10, 16
|
||||
_, err := strconv.ParseUint(fields[i+2], base, bitLength)
|
||||
return err != nil // not a line number
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func deleteIPTablesRule(ctx context.Context, iptablesBinary, instruction string,
|
||||
runner Runner, logger Logger) (err error) {
|
||||
targetRule, err := parseIptablesInstruction(instruction)
|
||||
if err != nil {
|
||||
return fmt.Errorf("parsing iptables command: %w", err)
|
||||
}
|
||||
|
||||
lineNumber, err := findLineNumber(ctx, iptablesBinary,
|
||||
targetRule, runner, logger)
|
||||
if err != nil {
|
||||
return fmt.Errorf("finding iptables chain rule line number: %w", err)
|
||||
} else if lineNumber == 0 {
|
||||
logger.Debug("rule matching \"" + instruction + "\" not found")
|
||||
return nil
|
||||
}
|
||||
logger.Debug(fmt.Sprintf("found iptables chain rule matching %q at line number %d",
|
||||
instruction, lineNumber))
|
||||
|
||||
cmd := exec.CommandContext(ctx, iptablesBinary, "-t", targetRule.table,
|
||||
"-D", targetRule.chain, fmt.Sprint(lineNumber)) // #nosec G204
|
||||
logger.Debug(cmd.String())
|
||||
output, err := runner.Run(cmd)
|
||||
if err != nil {
|
||||
err = fmt.Errorf("command failed: %q: %w", cmd, err)
|
||||
if output != "" {
|
||||
err = fmt.Errorf("%w: %s", err, output)
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// findLineNumber finds the line number of an iptables rule.
|
||||
// It returns 0 if the rule is not found.
|
||||
func findLineNumber(ctx context.Context, iptablesBinary string,
|
||||
instruction iptablesInstruction, runner Runner, logger Logger) (
|
||||
lineNumber uint16, err error) {
|
||||
listFlags := []string{"-t", instruction.table, "-L", instruction.chain,
|
||||
"--line-numbers", "-n", "-v"}
|
||||
cmd := exec.CommandContext(ctx, iptablesBinary, listFlags...) // #nosec G204
|
||||
logger.Debug(cmd.String())
|
||||
output, err := runner.Run(cmd)
|
||||
if err != nil {
|
||||
err = fmt.Errorf("command failed: %q: %w", cmd, err)
|
||||
if output != "" {
|
||||
err = fmt.Errorf("%w: %s", err, output)
|
||||
}
|
||||
return 0, err
|
||||
}
|
||||
|
||||
chain, err := parseChain(output)
|
||||
if err != nil {
|
||||
return 0, fmt.Errorf("parsing chain list: %w", err)
|
||||
}
|
||||
|
||||
for _, rule := range chain.rules {
|
||||
if instruction.equalToRule(instruction.table, chain.name, rule) {
|
||||
return rule.lineNumber, nil
|
||||
}
|
||||
}
|
||||
|
||||
return 0, nil
|
||||
}
|
||||
@@ -1,188 +0,0 @@
|
||||
package firewall
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"testing"
|
||||
|
||||
"github.com/golang/mock/gomock"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func Test_isDeleteMatchInstruction(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
testCases := map[string]struct {
|
||||
instruction string
|
||||
isDeleteMatch bool
|
||||
}{
|
||||
"not_delete": {
|
||||
instruction: "-t nat -A PREROUTING -i tun0 -j ACCEPT",
|
||||
},
|
||||
"malformed_missing_chain_name": {
|
||||
instruction: "-t nat -D",
|
||||
},
|
||||
"delete_chain_name_last_field": {
|
||||
instruction: "-t nat --delete PREROUTING",
|
||||
isDeleteMatch: true,
|
||||
},
|
||||
"delete_match": {
|
||||
instruction: "-t nat --delete PREROUTING -i tun0 -j ACCEPT",
|
||||
isDeleteMatch: true,
|
||||
},
|
||||
"delete_line_number_last_field": {
|
||||
instruction: "-t nat -D PREROUTING 2",
|
||||
},
|
||||
"delete_line_number": {
|
||||
instruction: "-t nat -D PREROUTING 2 -i tun0 -j ACCEPT",
|
||||
},
|
||||
}
|
||||
for name, testCase := range testCases {
|
||||
testCase := testCase
|
||||
t.Run(name, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
isDeleteMatch := isDeleteMatchInstruction(testCase.instruction)
|
||||
|
||||
assert.Equal(t, testCase.isDeleteMatch, isDeleteMatch)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func newCmdMatcherListRules(iptablesBinary, table, chain string) *cmdMatcher { //nolint:unparam
|
||||
return newCmdMatcher(iptablesBinary, "^-t$", "^"+table+"$", "^-L$", "^"+chain+"$",
|
||||
"^--line-numbers$", "^-n$", "^-v$")
|
||||
}
|
||||
|
||||
func Test_deleteIPTablesRule(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
const iptablesBinary = "/sbin/iptables"
|
||||
errTest := errors.New("test error")
|
||||
|
||||
testCases := map[string]struct {
|
||||
instruction string
|
||||
makeRunner func(ctrl *gomock.Controller) *MockRunner
|
||||
makeLogger func(ctrl *gomock.Controller) *MockLogger
|
||||
errWrapped error
|
||||
errMessage string
|
||||
}{
|
||||
"invalid_instruction": {
|
||||
instruction: "invalid",
|
||||
errWrapped: ErrIptablesCommandMalformed,
|
||||
errMessage: "parsing iptables command: iptables command is malformed: " +
|
||||
"fields count 1 is not even: \"invalid\"",
|
||||
},
|
||||
"list_error": {
|
||||
instruction: "-t nat --delete PREROUTING -i tun0 -p tcp --dport 43716 -j REDIRECT --to-ports 5678",
|
||||
makeRunner: func(ctrl *gomock.Controller) *MockRunner {
|
||||
runner := NewMockRunner(ctrl)
|
||||
runner.EXPECT().
|
||||
Run(newCmdMatcherListRules(iptablesBinary, "nat", "PREROUTING")).
|
||||
Return("", errTest)
|
||||
return runner
|
||||
},
|
||||
makeLogger: func(ctrl *gomock.Controller) *MockLogger {
|
||||
logger := NewMockLogger(ctrl)
|
||||
logger.EXPECT().Debug("/sbin/iptables -t nat -L PREROUTING --line-numbers -n -v")
|
||||
return logger
|
||||
},
|
||||
errWrapped: errTest,
|
||||
errMessage: `finding iptables chain rule line number: command failed: ` +
|
||||
`"/sbin/iptables -t nat -L PREROUTING --line-numbers -n -v": test error`,
|
||||
},
|
||||
"rule_not_found": {
|
||||
instruction: "-t nat --delete PREROUTING -i tun0 -p tcp --dport 43716 -j REDIRECT --to-ports 5678",
|
||||
makeRunner: func(ctrl *gomock.Controller) *MockRunner {
|
||||
runner := NewMockRunner(ctrl)
|
||||
runner.EXPECT().Run(newCmdMatcherListRules(iptablesBinary, "nat", "PREROUTING")).
|
||||
Return(`Chain PREROUTING (policy ACCEPT 0 packets, 0 bytes)
|
||||
num pkts bytes target prot opt in out source destination
|
||||
1 0 0 REDIRECT 6 -- tun0 * 0.0.0.0/0 0.0.0.0/0 tcp dpt:5000 redir ports 9999`, //nolint:lll
|
||||
nil)
|
||||
return runner
|
||||
},
|
||||
makeLogger: func(ctrl *gomock.Controller) *MockLogger {
|
||||
logger := NewMockLogger(ctrl)
|
||||
logger.EXPECT().Debug("/sbin/iptables -t nat -L PREROUTING --line-numbers -n -v")
|
||||
logger.EXPECT().Debug("rule matching \"-t nat --delete PREROUTING " +
|
||||
"-i tun0 -p tcp --dport 43716 -j REDIRECT --to-ports 5678\" not found")
|
||||
return logger
|
||||
},
|
||||
},
|
||||
"rule_found_delete_error": {
|
||||
instruction: "-t nat --delete PREROUTING -i tun0 -p tcp --dport 43716 -j REDIRECT --to-ports 5678",
|
||||
makeRunner: func(ctrl *gomock.Controller) *MockRunner {
|
||||
runner := NewMockRunner(ctrl)
|
||||
runner.EXPECT().Run(newCmdMatcherListRules(iptablesBinary, "nat", "PREROUTING")).
|
||||
Return("Chain PREROUTING (policy ACCEPT 0 packets, 0 bytes)\n"+
|
||||
"num pkts bytes target prot opt in out source destination \n"+
|
||||
"1 0 0 REDIRECT 6 -- tun0 * 0.0.0.0/0 0.0.0.0/0 tcp dpt:5000 redir ports 9999\n"+ //nolint:lll
|
||||
"2 0 0 REDIRECT 6 -- tun0 * 0.0.0.0/0 0.0.0.0/0 tcp dpt:43716 redir ports 5678\n", //nolint:lll
|
||||
nil)
|
||||
runner.EXPECT().Run(newCmdMatcher(iptablesBinary, "^-t$", "^nat$",
|
||||
"^-D$", "^PREROUTING$", "^2$")).Return("details", errTest)
|
||||
return runner
|
||||
},
|
||||
makeLogger: func(ctrl *gomock.Controller) *MockLogger {
|
||||
logger := NewMockLogger(ctrl)
|
||||
logger.EXPECT().Debug("/sbin/iptables -t nat -L PREROUTING --line-numbers -n -v")
|
||||
logger.EXPECT().Debug("found iptables chain rule matching \"-t nat --delete PREROUTING " +
|
||||
"-i tun0 -p tcp --dport 43716 -j REDIRECT --to-ports 5678\" at line number 2")
|
||||
logger.EXPECT().Debug("/sbin/iptables -t nat -D PREROUTING 2")
|
||||
return logger
|
||||
},
|
||||
errWrapped: errTest,
|
||||
errMessage: "command failed: \"/sbin/iptables -t nat -D PREROUTING 2\": test error: details",
|
||||
},
|
||||
"rule_found_delete_success": {
|
||||
instruction: "-t nat --delete PREROUTING -i tun0 -p tcp --dport 43716 -j REDIRECT --to-ports 5678",
|
||||
makeRunner: func(ctrl *gomock.Controller) *MockRunner {
|
||||
runner := NewMockRunner(ctrl)
|
||||
runner.EXPECT().Run(newCmdMatcherListRules(iptablesBinary, "nat", "PREROUTING")).
|
||||
Return("Chain PREROUTING (policy ACCEPT 0 packets, 0 bytes)\n"+
|
||||
"num pkts bytes target prot opt in out source destination \n"+
|
||||
"1 0 0 REDIRECT 6 -- tun0 * 0.0.0.0/0 0.0.0.0/0 tcp dpt:5000 redir ports 9999\n"+ //nolint:lll
|
||||
"2 0 0 REDIRECT 6 -- tun0 * 0.0.0.0/0 0.0.0.0/0 tcp dpt:43716 redir ports 5678\n", //nolint:lll
|
||||
nil)
|
||||
runner.EXPECT().Run(newCmdMatcher(iptablesBinary, "^-t$", "^nat$",
|
||||
"^-D$", "^PREROUTING$", "^2$")).Return("", nil)
|
||||
return runner
|
||||
},
|
||||
makeLogger: func(ctrl *gomock.Controller) *MockLogger {
|
||||
logger := NewMockLogger(ctrl)
|
||||
logger.EXPECT().Debug("/sbin/iptables -t nat -L PREROUTING --line-numbers -n -v")
|
||||
logger.EXPECT().Debug("found iptables chain rule matching \"-t nat --delete PREROUTING " +
|
||||
"-i tun0 -p tcp --dport 43716 -j REDIRECT --to-ports 5678\" at line number 2")
|
||||
logger.EXPECT().Debug("/sbin/iptables -t nat -D PREROUTING 2")
|
||||
return logger
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for name, testCase := range testCases {
|
||||
testCase := testCase
|
||||
t.Run(name, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
ctrl := gomock.NewController(t)
|
||||
|
||||
ctx := context.Background()
|
||||
instruction := testCase.instruction
|
||||
var runner *MockRunner
|
||||
if testCase.makeRunner != nil {
|
||||
runner = testCase.makeRunner(ctrl)
|
||||
}
|
||||
var logger *MockLogger
|
||||
if testCase.makeLogger != nil {
|
||||
logger = testCase.makeLogger(ctrl)
|
||||
}
|
||||
|
||||
err := deleteIPTablesRule(ctx, iptablesBinary, instruction, runner, logger)
|
||||
|
||||
assert.ErrorIs(t, err, testCase.errWrapped)
|
||||
if testCase.errWrapped != nil {
|
||||
assert.EqualError(t, err, testCase.errMessage)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -38,7 +38,7 @@ type Config struct { //nolint:maligned
|
||||
func NewConfig(ctx context.Context, logger Logger,
|
||||
runner command.Runner, defaultRoutes []routing.DefaultRoute,
|
||||
localNetworks []routing.LocalNetwork) (config *Config, err error) {
|
||||
iptables, err := checkIptablesSupport(ctx, runner, "iptables", "iptables-nft", "iptables-legacy")
|
||||
iptables, err := checkIptablesSupport(ctx, runner, "iptables", "iptables-nft")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
@@ -1,13 +0,0 @@
|
||||
package firewall
|
||||
|
||||
import "github.com/qdm12/golibs/command"
|
||||
|
||||
type Runner interface {
|
||||
Run(cmd command.ExecCmd) (output string, err error)
|
||||
}
|
||||
|
||||
type Logger interface {
|
||||
Debug(s string)
|
||||
Info(s string)
|
||||
Error(s string)
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user