Compare commits

...

110 Commits

Author SHA1 Message Date
Quentin McGaw
09c47c740c fix(version): log last release by tag name alphabetically instead of date 2024-08-09 07:43:48 +00:00
dependabot[bot]
ecbfc02713 Chore(deps): Bump github.com/breml/rootcerts from 0.2.16 to 0.2.17 (#2316) 2024-08-09 09:07:35 +02:00
Quentin McGaw
7be9288685 fix(privatevpn): set openvpn vpn type for no hostname server 2024-08-09 06:24:06 +00:00
Quentin McGaw
d1f57d0e36 chore(deps): bump gosplash to v0.2.0
- Merge same links in the same line
- Add `/choose` suffix to github links
2024-08-05 17:46:31 +00:00
Quentin McGaw
74ea1a0f5a hotfix(firewall): prefer ip6tables (nft) instead of ip6tables-legacy 2024-08-05 14:01:27 +00:00
Quentin McGaw
2a9ab29e7d fix(firewall): VPN_PORT_FORWARDING_LISTENING_PORT behavior fixed again
- allow redirection destination port in INPUT table
2024-08-05 13:57:30 +00:00
Quentin McGaw
8be78a5741 chore(github): add /choose suffix to issue and discussion links 2024-08-05 13:39:32 +00:00
Quentin McGaw
4a669c3458 chore(dev): upgrade organizeImports vscode setting from true to explicit 2024-08-05 13:39:01 +00:00
Quentin McGaw
ae5b71a864 chore(lint): remove now invalid skip-dirs configuration block 2024-08-05 13:38:32 +00:00
Quentin McGaw
6fff2ce1a4 chore(deps): tidy Go modules dependencies 2024-08-05 13:38:15 +00:00
Quentin McGaw
f6165d206a fix(firewall): VPN_PORT_FORWARDING_LISTENING_PORT behavior fixed
by not restricting the destination address to 127.0.0.1
2024-08-05 13:37:49 +00:00
Quentin McGaw
8dbe7b8888 hotfix(readme): add perfect privacy as port forwarding natively supported 2024-08-04 09:00:06 +00:00
Quentin McGaw
10f43d7a70 chore(github): add before next release github label 2024-08-04 08:35:57 +00:00
Quentin McGaw
01283def17 fix(format-servers): add missing vpn type column for natively supported providers
- nordvpn
- surfshark
2024-08-04 08:33:24 +00:00
Quentin McGaw
b32e085354 docs(readme): update list of providers supporting Wireguard with the custom provider 2024-08-03 14:32:41 +00:00
Quentin McGaw
ac9446e296 feat(protonvpn): Wireguard support (#2390) 2024-08-03 16:10:35 +02:00
Quentin McGaw
dea4080a7b fix(custom-openvpn): remove comments before parsing file 2024-08-03 13:37:57 +00:00
Quentin McGaw
2e63dba817 docs(readme): add protonvpn as custom port forwarding implementation 2024-08-03 09:54:14 +00:00
Quentin McGaw
10384c9e37 chore(github): add labels "Custom" and "Category: logs" 2024-08-01 12:20:55 +00:00
Quentin McGaw
34e8f5f3a9 hotfix(custom): assume all custom servers support port forwarding
- Fix custom wireguard with the protonvpn port forwarding implementation
- Might fix #2389
2024-08-01 11:52:38 +00:00
Quentin McGaw
ceb6ff4ca4 hotfix(protonvpn): fix free detection and update p2p->port_forward 2024-07-31 21:04:05 +00:00
Quentin McGaw
4c3da54303 chore(example/updater): simplify update code for openvpn+wireguard servers 2024-07-31 16:08:49 +00:00
Quentin McGaw
5d75bbc869 feat(config): only use port forwarding servers when port forwarding is enabled for ProtonVPN and PIA 2024-07-31 14:49:33 +00:00
Quentin McGaw
72e227f87d fix(config): log out if port forwarding only servers is enabled 2024-07-31 14:44:19 +00:00
Quentin McGaw
c5c37e7f96 hotfix(protonvpn): port forward only option
- Allow to use with Protonvpn (not just PIA)
- Update code comments
- Check server supports port forwarding when invoking port forward code
2024-07-31 14:43:59 +00:00
Quentin McGaw
aaf3019d8c hotfix(protonvpn): add markdown headers for servers 2024-07-31 14:33:24 +00:00
Quentin McGaw
5191f3558f hotfix(protonvpn): drop P2P_ONLY in favor of PORT_FORWARD_ONLY 2024-07-31 14:29:31 +00:00
Quentin McGaw
13ffffb157 feat(fastestvpn): Wireguard support (#2383)
Credits to @Zerauskire for the initial investigation and @jvanderzande for an initial implementation as well as reviewing the pull request
2024-07-31 16:16:50 +02:00
Quentin McGaw
7bc2972b27 feat(perfectprivacy): port forwarding support (#2378) 2024-07-30 22:00:26 +02:00
Quentin McGaw
ab08a5e666 feat(fastestvpn): update servers data using API instead of zip file
- Add city filter
- More dynamic to servers updates on fastestvpn's end
- Update servers data
2024-07-30 14:50:32 +00:00
Quentin McGaw
8c730a6e4a chore(port-forward): support multiple port forwarded 2024-07-29 13:55:28 +00:00
Quentin McGaw
4c47b6f142 feat(protonvpn): determine free status with tier value 2024-07-29 13:55:27 +00:00
Quentin McGaw
264480b659 hotfix(pia): remove crl verify
- Refers to issue #2376
2024-07-29 08:23:39 +00:00
Koen van Zuijlen
cb99f90bb5 feat(protonvpn): feature filters (#2182)
- `SECURE_CORE_ONLY`
- `TOR_ONLY`
- `P2P_ONLY`
2024-07-29 08:57:31 +02:00
Quentin McGaw
2bf2525bc5 chore(config): split server filter validation for features and subscription tier
- `validateSubscriptionTierFilters` function
- `validateFeatureFilters` function
- idea introduced in #2182
2024-07-29 06:18:28 +00:00
Quentin McGaw
26705f5a23 hotfix(firewall): re-add iptables-legacy for setups with nft kernel support 2024-07-29 05:43:34 +00:00
Quentin McGaw
ddbfdc9f14 feat(firewall): prefer using iptables nft instead of legacy 2024-07-28 14:29:00 +00:00
Quentin McGaw
9807d5f8f5 feat(docker): bump Alpine from 3.19 to 3.20
- Fix iptables and iptables-nft behavior
- Address systems not supporting iptables-legacy, see #2304
2024-07-28 12:43:33 +00:00
Quentin McGaw
921992ebc7 chore(build): do not upgrade busybox since vulnerabilities are fixed now 2024-07-28 12:41:54 +00:00
Quentin McGaw
8331ce6010 chore(github): disable blank issues 2024-07-27 11:44:23 +00:00
Quentin McGaw
36c8da7ea7 hotfix(config): split common VPN options per VPN type
- Split `VPN_ENDPOINT_IP` in `OPENVPN_ENDPOINT_IP` and `WIREGUARD_ENDPOINT_IP`
- Split `VPN_ENDPOINT_PORT` in `OPENVPN_ENDPOINT_PORT` and `WIREGUARD_ENDPOINT_PORT`
- Fixes bad usage of Wireguard config file endpoint for OpenVPN #2347
2024-07-27 10:42:01 +00:00
Quentin McGaw
73832d8b49 hotfix(firewall): add iptables -m flag for input port instructions 2024-07-26 11:40:12 +00:00
Quentin McGaw
a03041cfea hotfix(config): do not log retro-compat warning when using OPENVPN_USER or OPENVPN_PASSWORD 2024-07-26 08:42:32 +00:00
Quentin McGaw
e7381b3800 chore(config): rename FIREWALL to FIREWALL_ENABLED_DISABLING_IT_SHOOTS_YOU_IN_YOUR_FOOT 2024-07-26 08:25:05 +00:00
Quentin McGaw
9d50c23532 hotfix(config): upgrade gosettings to v0.4.2
- Fix handling of retro-compatible keys
2024-07-12 19:54:17 +00:00
Quentin McGaw
0501743814 feat(pia): port forwarding options VPN_PORT_FORWARDING_USERNAME and VPN_PORT_FORWARDING_PASSWORD
- Retro-compatible with `OPENVPN_USER` + `OPENVPN_PASSWORD`
- No more reading for the OpenVPN auth file
- Allow to use PIA port forwarding with Wireguard
2024-07-09 14:44:46 +00:00
Quentin McGaw
06c9bc55d3 hotfix(firewall): prefer using ip6tables-legacy
- Fixes issue #2334
2024-07-08 08:57:57 +00:00
Quentin McGaw
fe05521f2b feat(config): read wireguard config file without case sensitivity 2024-06-28 20:57:57 +00:00
Quentin McGaw
93ed87d12b hotfix(pia): allow one value in SERVER_NAMES for the custom provider 2024-06-17 22:34:59 +00:00
Quentin McGaw
4218dba177 fix(publicip): abort ip data fetch if vpn context is canceled
- Prevents requesting the public IP address N times after N VPN failures
- Fetching runs with a context local to the 'single run'
- Single run writes single run result to a channel back to the caller, RunOnce is now blocking
2024-05-18 18:06:01 +00:00
Quentin McGaw
7872ab91dc chore(pia): return an error to pf loop if server cannot port forward 2024-05-18 12:00:57 +00:00
dependabot[bot]
c9e75bd697 Chore(deps): Bump golang.org/x/net from 0.24.0 to 0.25.0 (#2269) 2024-05-18 12:10:58 +02:00
dependabot[bot]
7453f7f59a Chore(deps): Bump github.com/fatih/color from 1.16.0 to 1.17.0 (#2279) 2024-05-18 12:10:49 +02:00
Quentin McGaw
19a9ac9fd7 hotfix(torguard): remove second certificate 2024-05-16 18:50:36 +00:00
Quentin McGaw
ecb06836b5 chore(deps): bump wireguard go dependencies 2024-05-10 14:38:18 +00:00
Quentin McGaw
1e25372189 fix(health): docker healthcheck has 3 retries 2024-05-10 14:31:01 +00:00
Quentin McGaw
6042a9e3c2 feat(health): change timeout mechanism
- Healthcheck timeout is no longer fixed to 3 seconds
- Healthcheck timeout increases from 2s to 4s, 6s, 8s, 10s
- No 1 second wait time between check retries after failure
- VPN internal restart may be delayed by a maximum of 10 seconds
2024-05-10 14:15:03 +00:00
Quentin McGaw
fd4689ee70 fix(ipv6): detect ignoring loopback route destinations 2024-05-10 14:15:03 +00:00
Quentin McGaw
4bd16373f2 fix(torguard): update OpenVPN configuration
- add aes-128-gcm and aes-128-cbc ciphers
- add extra CA certificate value
- remove mssfix option
- remove sndbuf and rcvbuf ption
- remove ping option
- remove reneg option
2024-05-10 14:14:42 +00:00
Quentin McGaw
ce642a6d8b hotfix(firewall): prefer using iptables-legacy over nf_tables
- due to nf_tables bugs I discovered and reported
2024-05-09 14:33:34 +00:00
Quentin McGaw
ef6874fe57 fix(firewall): query iptables version for iptables found 2024-05-04 16:19:30 +00:00
Quentin McGaw
29bc60bc35 chore(github): add labels
- Popularity extreme and high
- Closed cannot be done
- Categories kernel and public IP service
2024-05-02 17:07:38 +00:00
Quentin McGaw
fb145d68a0 hotfix(firewall): support iptables-legacy for older kernels 2024-05-02 16:54:29 +00:00
Quentin McGaw
6dd27e53d4 chore(portforward): remove PIA dependency on storage package 2024-05-02 09:18:35 +00:00
Quentin McGaw
e0a977cf83 change(openvpn): default upgraded from 2.5 to 2.6 2024-05-02 08:13:51 +00:00
Quentin McGaw
4d002a3ad6 feat(docker): bump Alpine from 3.18 to 3.19 2024-05-02 07:43:05 +00:00
dependabot[bot]
4206859cad Chore(deps): Bump peter-evans/dockerhub-description from 3 to 4 (#2075) 2024-05-02 09:25:48 +02:00
wanshuangcheng
5dacbb994f chore(all): fix typos in code comments (#2216) 2024-05-02 09:24:49 +02:00
dependabot[bot]
ebf4bf9ea8 Chore(deps): Bump golang.org/x/net from 0.22.0 to 0.24.0 (#2208) 2024-05-02 09:20:22 +02:00
dependabot[bot]
241a9930c9 Chore(deps): Bump github.com/klauspost/compress from 1.17.7 to 1.17.8 (#2218) 2024-05-02 09:20:02 +02:00
Quentin McGaw
f1e8200cfc chore(deps): tidy go modules 2024-05-02 07:02:51 +00:00
dependabot[bot]
03eddb1698 Chore(deps): Bump DavidAnson/markdownlint-cli2-action from 14 to 16 (#2214) 2024-05-02 08:42:17 +02:00
Quentin McGaw
b25ee21e3e fix(custom): do not set server name if it does not exist 2024-05-01 19:35:40 +00:00
Quentin McGaw
7e0738d113 fix(vpnunlimited): allow OpenVPN TCP on port 1197 2024-04-30 08:05:22 +00:00
Quentin McGaw
0b078e5f5e fix(vpnunlimited): change UDP port from 1194 to 1197 2024-04-30 08:04:02 +00:00
Quentin McGaw
45fe38e670 fix(custom): parse port option line for OpenVPN 2024-04-30 08:02:28 +00:00
Quentin McGaw
72e2e4b82c fix(custom): set server name if names filter is not empty
- fix PIA port forwarding code usage
- refers to #2147
2024-04-29 19:23:34 +00:00
Quentin McGaw
bdc594c297 feat(airvpn): set default mssfix to 1320-28 2024-04-29 13:43:28 +00:00
Quentin McGaw
1afe01d8cd feat(vpnsecure): update servers data 2024-04-29 09:46:48 +00:00
Quentin McGaw
234e54ac5c chore(vpnsecure): associate "N / A" with no data for servers 2024-04-29 09:46:40 +00:00
Quentin McGaw
49b8f8b443 feat(surfshark): update servers data 2024-04-29 09:38:31 +00:00
Quentin McGaw
ce75c5ca21 fix(surfshark): remove outdated hardcoded retro servers 2024-04-29 09:37:58 +00:00
Quentin McGaw
e07966f71e fix(tun): only create tun device if it does not exist
- do not create if it exists and is problematic
- wrap errors with a better context
2024-04-29 09:29:06 +00:00
Quentin McGaw
c5395adfea hotfix(ci): skip console dead link check 2024-04-28 19:42:39 +00:00
Quentin McGaw
9d1ec69b73 chore(github): remove from_name in labels configuration 2024-04-28 19:28:04 +00:00
Quentin McGaw
ee8802ee86 docs(readme): clarify shadowsocks proxy is a server
- Fixes issue #2191
2024-04-28 19:27:14 +00:00
Quentin McGaw
0d7115c832 chore(github): review all labels
- add closed labels
- add category labels
- rename labels
- add label category prefix
- add emojis for each label
2024-04-28 19:24:46 +00:00
Quentin McGaw
08fb049f63 chore(github): remove empty label description fields 2024-04-28 13:43:35 +00:00
Quentin McGaw
c87c0e12fe feat(wireguard): WIREGUARD_PERSISTENT_KEEPALIVE_INTERVAL option 2024-04-25 10:44:13 +00:00
Quentin McGaw
7b4befce61 hotfix(settings): openvpn encrypted key reading from file 2024-04-08 07:40:14 +00:00
Quentin McGaw
6709a248d6 hotfix(settings): HTTPPROXY_LOG reading fixed 2024-04-08 07:35:24 +00:00
Quentin McGaw
bf4cc0dabf fix(server): /openvpn route status get and put
- get status return stopped if running wireguard
- put status changes vpn type if running wireguard
2024-04-08 07:28:56 +00:00
Quentin McGaw
982100782c hotfix(config/nordvpn): do not initialize wg addresses to an empty slice 2024-04-02 14:48:40 +00:00
Quentin McGaw
4afbe9332f hotfix(settings): trim space for each wireguard address 2024-04-01 13:04:00 +00:00
Quentin McGaw
4019ee3ea1 hotfix(settings): fix retro-compatible key message 2024-04-01 12:59:47 +00:00
Quentin McGaw
e859c60343 hotfix(config/wireguard): add /32 to address without bits specified 2024-03-29 15:06:34 +00:00
Quentin McGaw
8454123cae fix(publicip/api): ip2location parsing for latitude and longitude 2024-03-29 14:54:45 +00:00
Quentin McGaw
6b2f350ec9 hotfix(config): update to gosettings v0.4.1
- fix bad reading of files and secret files
2024-03-26 07:24:45 +00:00
Quentin McGaw
e01ce9c6d8 hotfix(config): read wireguard config from /gluetun/wireguard/wg0.conf 2024-03-26 07:04:26 +00:00
Quentin McGaw
ecc80a5a9e chore(config): upgrade to gosettings v0.4.0
- drop qdm12/govalid dependency
- upgrade qdm12/ss-server to v0.6.0
- do not unset sensitive config settings (makes no sense to me)
2024-03-25 19:14:20 +00:00
Quentin McGaw
23b0320cfb chore(tun): fix unit test for unprivileged user 2024-03-25 18:44:47 +00:00
Quentin McGaw
3e79509c97 chore(build): bump Go from 1.21 to 1.22 2024-03-25 18:44:44 +00:00
Quentin McGaw
2185f347ce chore(deps): bump github.com/stretchr/testify to v1.9.0 2024-03-25 18:29:55 +00:00
dependabot[bot]
aa3ef5a1c2 Chore(deps): Bump golang.org/x/net from 0.19.0 to 0.22.0 (#2138) 2024-03-25 18:29:55 +00:00
dependabot[bot]
acec050b95 Chore(deps): Bump golang.org/x/sys from 0.15.0 to 0.18.0 (#2139) 2024-03-25 18:29:55 +00:00
dependabot[bot]
9ca97fb04f Chore(deps): Bump github.com/klauspost/compress from 1.17.4 to 1.17.7 (#2178) 2024-03-25 18:29:55 +00:00
Quentin McGaw
4776948af6 chore(dev): fix source.organizeImports vscode setting value 2024-03-25 18:29:55 +00:00
Quentin McGaw
4d9c619b24 chore(config): use openvpn protocol string field instead of TCP bool 2024-03-25 18:29:51 +00:00
Quentin McGaw
62007bf1a1 chore(config): provider name field as string instead of string pointer 2024-03-23 09:41:25 +00:00
Quentin McGaw
7674efe8d7 chore(config): remove bad retro-compatiblity for HTTP_CONTROL_SERVER_ADDRESS
- Retro-compatible variable key CONTROL_SERVER_ADDRESS was never defined
- Old variable key CONTROL_SERVER_PORT was removed in v3.28.0 and no complain so far
2024-03-23 09:37:54 +00:00
189 changed files with 22847 additions and 10288 deletions

View File

@@ -37,12 +37,12 @@
"go.useLanguageServer": true, "go.useLanguageServer": true,
"[go]": { "[go]": {
"editor.codeActionsOnSave": { "editor.codeActionsOnSave": {
"source.organizeImports": true "source.organizeImports": "explicit"
} }
}, },
"[go.mod]": { "[go.mod]": {
"editor.codeActionsOnSave": { "editor.codeActionsOnSave": {
"source.organizeImports": true "source.organizeImports": "explicit"
} }
}, },
"gopls": { "gopls": {

View File

@@ -1,9 +1,10 @@
blank_issues_enabled: false
contact_links: contact_links:
- name: Report a Wiki issue - name: Report a Wiki issue
url: https://github.com/qdm12/gluetun-wiki/issues/new url: https://github.com/qdm12/gluetun-wiki/issues/new/choose
about: Please create an issue on the gluetun-wiki repository. about: Please create an issue on the gluetun-wiki repository.
- name: Configuration help? - name: Configuration help?
url: https://github.com/qdm12/gluetun/discussions/new url: https://github.com/qdm12/gluetun/discussions/new/choose
about: Please create a Github discussion. about: Please create a Github discussion.
- name: Unraid template issue - name: Unraid template issue
url: https://github.com/qdm12/gluetun/discussions/550 url: https://github.com/qdm12/gluetun/discussions/550

189
.github/labels.yml vendored
View File

@@ -1,133 +1,140 @@
# Temporary status - name: "Status: 🗯️ Waiting for feedback"
- name: "🗯️ Waiting for feedback" color: "f7d692"
color: "aadefa" - name: "Status: 🔴 Blocked"
description: "" color: "f7d692"
- name: "🔴 Blocked"
color: "ff3f14"
description: "Blocked by another issue or pull request" description: "Blocked by another issue or pull request"
- name: "🔒 After next release" - name: "Status: 📌 Before next release"
color: "e8f274" color: "f7d692"
description: "Has to be done before the next release"
- name: "Status: 🔒 After next release"
color: "f7d692"
description: "Will be done after the next release" description: "Will be done after the next release"
# Priority - name: "Closed: ⚰️ Inactive"
- name: "🚨 Urgent" color: "959a9c"
color: "d5232f" description: "No answer was received for weeks"
description: "" - name: "Closed: 👥 Duplicate"
- name: "💤 Low priority" color: "959a9c"
color: "4285f4" description: "Issue duplicates an existing issue"
description: "" - name: "Closed: 🗑️ Bad issue"
color: "959a9c"
- name: "Closed: ☠️ cannot be done"
color: "959a9c"
# Complexity - name: "Priority: 🚨 Urgent"
- name: "☣️ Hard to do" color: "03adfc"
color: "7d0008" - name: "Priority: 💤 Low priority"
description: "" color: "03adfc"
- name: "🟩 Easy to do"
color: "34cf43" - name: "Complexity: ☣️ Hard to do"
description: "" color: "ff9efc"
- name: "Complexity: 🟩 Easy to do"
color: "ff9efc"
- name: "Popularity: ❤️‍🔥 extreme"
color: "ffc7ea"
- name: "Popularity: ❤️ high"
color: "ffc7ea"
# VPN providers # VPN providers
- name: ":cloud: AirVPN" - name: "☁️ AirVPN"
color: "cfe8d4" color: "cfe8d4"
description: "" - name: "☁️ Custom"
- name: ":cloud: Cyberghost"
color: "cfe8d4" color: "cfe8d4"
description: "" - name: "☁️ Cyberghost"
- name: ":cloud: HideMyAss"
color: "cfe8d4" color: "cfe8d4"
description: "" - name: "☁️ HideMyAss"
- name: ":cloud: IPVanish"
color: "cfe8d4" color: "cfe8d4"
description: "" - name: "☁️ IPVanish"
- name: ":cloud: IVPN"
color: "cfe8d4" color: "cfe8d4"
description: "" - name: "☁️ IVPN"
- name: ":cloud: ExpressVPN"
color: "cfe8d4" color: "cfe8d4"
description: "" - name: "☁️ ExpressVPN"
- name: ":cloud: FastestVPN"
color: "cfe8d4" color: "cfe8d4"
description: "" - name: "☁️ FastestVPN"
- name: ":cloud: Mullvad"
color: "cfe8d4" color: "cfe8d4"
description: "" - name: "☁️ Mullvad"
- name: ":cloud: NordVPN"
color: "cfe8d4" color: "cfe8d4"
description: "" - name: "☁️ NordVPN"
- name: ":cloud: Perfect Privacy"
color: "cfe8d4" color: "cfe8d4"
description: "" - name: "☁️ Perfect Privacy"
- name: ":cloud: PIA"
color: "cfe8d4" color: "cfe8d4"
description: "" - name: "☁️ PIA"
- name: ":cloud: Privado"
color: "cfe8d4" color: "cfe8d4"
description: "" - name: "☁️ Privado"
- name: ":cloud: PrivateVPN"
color: "cfe8d4" color: "cfe8d4"
description: "" - name: "☁️ PrivateVPN"
- name: ":cloud: ProtonVPN"
color: "cfe8d4" color: "cfe8d4"
- name: ":cloud: PureVPN" - name: "☁️ ProtonVPN"
color: "cfe8d4" color: "cfe8d4"
description: "" - name: "☁️ PureVPN"
- name: ":cloud: SlickVPN"
color: "cfe8d4" color: "cfe8d4"
description: "" - name: "☁️ SlickVPN"
- name: ":cloud: Surfshark"
color: "cfe8d4" color: "cfe8d4"
description: "" - name: "☁️ Surfshark"
- name: ":cloud: Torguard"
color: "cfe8d4" color: "cfe8d4"
description: "" - name: "☁️ Torguard"
- name: ":cloud: VPNSecure.me"
color: "cfe8d4" color: "cfe8d4"
- name: ":cloud: VPNUnlimited" - name: "☁️ VPNSecure.me"
color: "cfe8d4" color: "cfe8d4"
description: "" - name: "☁️ VPNUnlimited"
- name: ":cloud: Vyprvpn"
color: "cfe8d4" color: "cfe8d4"
description: "" - name: "☁️ Vyprvpn"
- name: ":cloud: WeVPN"
color: "cfe8d4" color: "cfe8d4"
description: "" - name: "☁️ WeVPN"
- name: ":cloud: Windscribe" color: "cfe8d4"
- name: "☁️ Windscribe"
color: "cfe8d4" color: "cfe8d4"
description: ""
# Problem category - name: "Category: Config problem 📝"
- name: "Config problem"
color: "ffc7ea" color: "ffc7ea"
description: "" - name: "Category: Healthcheck 🩺"
- name: "Openvpn"
color: "ffc7ea" color: "ffc7ea"
description: "" - name: "Category: Documentation ✒️"
- name: "Wireguard" description: "A problem with the readme or a code comment."
color: "ffc7ea" color: "ffc7ea"
description: "" - name: "Category: Maintenance ⛓️"
- name: "Unbound (DNS over TLS)" description: "Anything related to code or other maintenance"
color: "ffc7ea" color: "ffc7ea"
description: "" - name: "Category: Logs 📚"
- name: "Firewall" description: "Something to change in logs"
color: "ffc7ea" color: "ffc7ea"
description: "" - name: "Category: Good idea 🎯"
- name: "Routing" description: "This is a good idea, judged by the maintainers"
color: "ffc7ea" color: "ffc7ea"
description: "" - name: "Category: Motivated! 🙌"
- name: "IPv6" description: "Your pumpness makes me pumped! The issue or PR shows great motivation!"
color: "ffc7ea" color: "ffc7ea"
description: "" - name: "Category: Foolproof settings 👼"
- name: "Port forwarding"
color: "ffc7ea" color: "ffc7ea"
description: "" - name: "Category: Label missing ❗"
- name: "HTTP proxy"
color: "ffc7ea" color: "ffc7ea"
description: "" - name: "Category: updater ♻️"
- name: "Shadowsocks"
color: "ffc7ea" color: "ffc7ea"
description: "" description: "Concerns the code to update servers data"
- name: "Healthcheck server" - name: "Category: New provider 🆕"
color: "ffc7ea" color: "ffc7ea"
description: "" - name: "Category: OpenVPN 🔐"
- name: "Control server" 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 💬"
color: "ffc7ea" color: "ffc7ea"
description: ""

View File

@@ -74,6 +74,9 @@ jobs:
security-events: write security-events: write
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v4
- uses: actions/setup-go@v5
with:
go-version: "^1.22"
- uses: github/codeql-action/init@v3 - uses: github/codeql-action/init@v3
with: with:
languages: go languages: go

View File

@@ -0,0 +1,13 @@
{
"ignorePatterns": [
{
"pattern": "^https://console.substack.com/p/console-72$"
}
],
"timeout": "20s",
"retryOn429": false,
"fallbackRetryDelay": "30s",
"aliveStatusCodes": [
200
]
}

View File

@@ -20,7 +20,7 @@ jobs:
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v4
- uses: DavidAnson/markdownlint-cli2-action@v14 - uses: DavidAnson/markdownlint-cli2-action@v16
with: with:
globs: "**.md" globs: "**.md"
config: .markdownlint.json config: .markdownlint.json
@@ -35,8 +35,9 @@ jobs:
- uses: gaurav-nelson/github-action-markdown-link-check@v1 - uses: gaurav-nelson/github-action-markdown-link-check@v1
with: with:
use-quiet-mode: yes use-quiet-mode: yes
config-file: .github/workflows/configs/mlc-config.json
- uses: peter-evans/dockerhub-description@v3 - uses: peter-evans/dockerhub-description@v4
if: github.repository == 'qdm12/gluetun' && github.event_name == 'push' if: github.repository == 'qdm12/gluetun' && github.event_name == 'push'
with: with:
username: qmcgaw username: qmcgaw

View File

@@ -120,9 +120,3 @@ linters:
- wastedassign - wastedassign
- whitespace - whitespace
- zerologlint - zerologlint
run:
skip-dirs:
- .devcontainer
- .github
- doc

View File

@@ -21,7 +21,7 @@
"go.useLanguageServer": true, "go.useLanguageServer": true,
"[go]": { "[go]": {
"editor.codeActionsOnSave": { "editor.codeActionsOnSave": {
"source.organizeImports": true "source.organizeImports": "explicit"
} }
}, },
"go.lintTool": "golangci-lint", "go.lintTool": "golangci-lint",

View File

@@ -1,6 +1,6 @@
ARG ALPINE_VERSION=3.18 ARG ALPINE_VERSION=3.20
ARG GO_ALPINE_VERSION=3.18 ARG GO_ALPINE_VERSION=3.20
ARG GO_VERSION=1.21 ARG GO_VERSION=1.22
ARG XCPUTRANSLATE_VERSION=v0.6.0 ARG XCPUTRANSLATE_VERSION=v0.6.0
ARG GOLANGCI_LINT_VERSION=v1.56.2 ARG GOLANGCI_LINT_VERSION=v1.56.2
ARG MOCKGEN_VERSION=v1.6.0 ARG MOCKGEN_VERSION=v1.6.0
@@ -76,16 +76,16 @@ LABEL \
ENV VPN_SERVICE_PROVIDER=pia \ ENV VPN_SERVICE_PROVIDER=pia \
VPN_TYPE=openvpn \ VPN_TYPE=openvpn \
# Common VPN options # Common VPN options
VPN_ENDPOINT_IP= \
VPN_ENDPOINT_PORT= \
VPN_INTERFACE=tun0 \ VPN_INTERFACE=tun0 \
# OpenVPN # OpenVPN
OPENVPN_ENDPOINT_IP= \
OPENVPN_ENDPOINT_PORT= \
OPENVPN_PROTOCOL=udp \ OPENVPN_PROTOCOL=udp \
OPENVPN_USER= \ OPENVPN_USER= \
OPENVPN_PASSWORD= \ OPENVPN_PASSWORD= \
OPENVPN_USER_SECRETFILE=/run/secrets/openvpn_user \ OPENVPN_USER_SECRETFILE=/run/secrets/openvpn_user \
OPENVPN_PASSWORD_SECRETFILE=/run/secrets/openvpn_password \ OPENVPN_PASSWORD_SECRETFILE=/run/secrets/openvpn_password \
OPENVPN_VERSION=2.5 \ OPENVPN_VERSION=2.6 \
OPENVPN_VERBOSITY=1 \ OPENVPN_VERBOSITY=1 \
OPENVPN_FLAGS= \ OPENVPN_FLAGS= \
OPENVPN_CIPHERS= \ OPENVPN_CIPHERS= \
@@ -93,6 +93,8 @@ ENV VPN_SERVICE_PROVIDER=pia \
OPENVPN_PROCESS_USER=root \ OPENVPN_PROCESS_USER=root \
OPENVPN_CUSTOM_CONFIG= \ OPENVPN_CUSTOM_CONFIG= \
# Wireguard # Wireguard
WIREGUARD_ENDPOINT_IP= \
WIREGUARD_ENDPOINT_PORT= \
WIREGUARD_CONF_SECRETFILE=/run/secrets/wg0.conf \ WIREGUARD_CONF_SECRETFILE=/run/secrets/wg0.conf \
WIREGUARD_PRIVATE_KEY= \ WIREGUARD_PRIVATE_KEY= \
WIREGUARD_PRIVATE_KEY_SECRETFILE=/run/secrets/wireguard_private_key \ WIREGUARD_PRIVATE_KEY_SECRETFILE=/run/secrets/wireguard_private_key \
@@ -100,6 +102,7 @@ ENV VPN_SERVICE_PROVIDER=pia \
WIREGUARD_PRESHARED_KEY_SECRETFILE=/run/secrets/wireguard_preshared_key \ WIREGUARD_PRESHARED_KEY_SECRETFILE=/run/secrets/wireguard_preshared_key \
WIREGUARD_PUBLIC_KEY= \ WIREGUARD_PUBLIC_KEY= \
WIREGUARD_ALLOWED_IPS= \ WIREGUARD_ALLOWED_IPS= \
WIREGUARD_PERSISTENT_KEEPALIVE_INTERVAL=0 \
WIREGUARD_ADDRESSES= \ WIREGUARD_ADDRESSES= \
WIREGUARD_ADDRESSES_SECRETFILE=/run/secrets/wireguard_addresses \ WIREGUARD_ADDRESSES_SECRETFILE=/run/secrets/wireguard_addresses \
WIREGUARD_MTU=1400 \ WIREGUARD_MTU=1400 \
@@ -119,6 +122,8 @@ ENV VPN_SERVICE_PROVIDER=pia \
VPN_PORT_FORWARDING_LISTENING_PORT=0 \ VPN_PORT_FORWARDING_LISTENING_PORT=0 \
VPN_PORT_FORWARDING_PROVIDER= \ VPN_PORT_FORWARDING_PROVIDER= \
VPN_PORT_FORWARDING_STATUS_FILE="/tmp/gluetun/forwarded_port" \ VPN_PORT_FORWARDING_STATUS_FILE="/tmp/gluetun/forwarded_port" \
VPN_PORT_FORWARDING_USERNAME= \
VPN_PORT_FORWARDING_PASSWORD= \
# # Cyberghost only: # # Cyberghost only:
OPENVPN_CERT= \ OPENVPN_CERT= \
OPENVPN_KEY= \ OPENVPN_KEY= \
@@ -135,6 +140,8 @@ ENV VPN_SERVICE_PROVIDER=pia \
SERVER_NAMES= \ SERVER_NAMES= \
# # ProtonVPN only: # # ProtonVPN only:
FREE_ONLY= \ FREE_ONLY= \
SECURE_CORE_ONLY= \
TOR_ONLY= \
# # Surfshark only: # # Surfshark only:
MULTIHOP_ONLY= \ MULTIHOP_ONLY= \
# # VPN Secure only: # # VPN Secure only:
@@ -142,7 +149,7 @@ ENV VPN_SERVICE_PROVIDER=pia \
# # PIA only: # # PIA only:
PORT_FORWARD_ONLY= \ PORT_FORWARD_ONLY= \
# Firewall # Firewall
FIREWALL=on \ FIREWALL_ENABLED_DISABLING_IT_SHOOTS_YOU_IN_YOUR_FOOT=on \
FIREWALL_VPN_INPUT_PORTS= \ FIREWALL_VPN_INPUT_PORTS= \
FIREWALL_INPUT_PORTS= \ FIREWALL_INPUT_PORTS= \
FIREWALL_OUTBOUND_SUBNETS= \ FIREWALL_OUTBOUND_SUBNETS= \
@@ -211,16 +218,14 @@ ENV VPN_SERVICE_PROVIDER=pia \
PGID= PGID=
ENTRYPOINT ["/gluetun-entrypoint"] ENTRYPOINT ["/gluetun-entrypoint"]
EXPOSE 8000/tcp 8888/tcp 8388/tcp 8388/udp EXPOSE 8000/tcp 8888/tcp 8388/tcp 8388/udp
HEALTHCHECK --interval=5s --timeout=5s --start-period=10s --retries=1 CMD /gluetun-entrypoint healthcheck HEALTHCHECK --interval=5s --timeout=5s --start-period=10s --retries=3 CMD /gluetun-entrypoint healthcheck
ARG TARGETPLATFORM ARG TARGETPLATFORM
RUN apk add --no-cache --update -l wget && \ RUN apk add --no-cache --update -l wget && \
apk add --no-cache --update -X "https://dl-cdn.alpinelinux.org/alpine/v3.17/main" openvpn\~2.5 && \ 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 && \ mv /usr/sbin/openvpn /usr/sbin/openvpn2.5 && \
apk del openvpn && \ apk del openvpn && \
apk add --no-cache --update openvpn ca-certificates iptables ip6tables unbound tzdata && \ apk add --no-cache --update openvpn ca-certificates iptables iptables-legacy unbound tzdata && \
mv /usr/sbin/openvpn /usr/sbin/openvpn2.6 && \ 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 && \ 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 openvpn && \
deluser unbound && \ deluser unbound && \

View File

@@ -56,24 +56,24 @@ Lightweight swiss-knife-like VPN client to multiple VPN service providers
## Features ## Features
- Based on Alpine 3.18 for a small Docker image of 35.6MB - Based on Alpine 3.20 for a small Docker image of 35.6MB
- Supports: **AirVPN**, **Cyberghost**, **ExpressVPN**, **FastestVPN**, **HideMyAss**, **IPVanish**, **IVPN**, **Mullvad**, **NordVPN**, **Perfect Privacy**, **Privado**, **Private Internet Access**, **PrivateVPN**, **ProtonVPN**, **PureVPN**, **SlickVPN**, **Surfshark**, **TorGuard**, **VPNSecure.me**, **VPNUnlimited**, **Vyprvpn**, **WeVPN**, **Windscribe** servers - Supports: **AirVPN**, **Cyberghost**, **ExpressVPN**, **FastestVPN**, **HideMyAss**, **IPVanish**, **IVPN**, **Mullvad**, **NordVPN**, **Perfect Privacy**, **Privado**, **Private Internet Access**, **PrivateVPN**, **ProtonVPN**, **PureVPN**, **SlickVPN**, **Surfshark**, **TorGuard**, **VPNSecure.me**, **VPNUnlimited**, **Vyprvpn**, **WeVPN**, **Windscribe** servers
- Supports OpenVPN for all providers listed - Supports OpenVPN for all providers listed
- Supports Wireguard both kernelspace and userspace - Supports Wireguard both kernelspace and userspace
- For **AirVPN**, **Ivpn**, **Mullvad**, **NordVPN**, **Surfshark** and **Windscribe** - For **AirVPN**, **FastestVPN**, **Ivpn**, **Mullvad**, **NordVPN**, **Perfect privacy**, **ProtonVPN**, **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 **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 custom Wireguard configurations 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) - 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 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 - 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` - 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 firewall kill switch to allow traffic only with needed the VPN servers and LAN devices
- Built in Shadowsocks proxy (protocol based on SOCKS5 with an encryption layer, tunnels TCP+UDP) - Built in Shadowsocks proxy server (protocol based on SOCKS5 with an encryption layer, tunnels TCP+UDP)
- Built in HTTP proxy (tunnels HTTP and HTTPS through TCP) - 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 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) - [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 🎆 - Compatible with amd64, i686 (32 bit), **ARM** 64 bit, ARM 32 bit v6 and v7, and even ppc64le 🎆
- [Custom VPN server side port forwarding for Private Internet Access](https://github.com/qdm12/gluetun-wiki/blob/main/setup/providers/private-internet-access.md#vpn-server-port-forwarding) - 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)
- Possibility of split horizon DNS by selecting multiple DNS over TLS providers - Possibility of split horizon DNS by selecting multiple DNS over TLS providers
- Unbound subprogram drops root privileges once launched - Unbound subprogram drops root privileges once launched
- Can work as a Kubernetes sidecar container, thanks @rorph - 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)! Go to the [Wiki](https://github.com/qdm12/gluetun-wiki)!
[🐛 Found a bug in the Wiki?!](https://github.com/qdm12/gluetun-wiki/issues/new) [🐛 Found a bug in the Wiki?!](https://github.com/qdm12/gluetun-wiki/issues/new/choose)
Here's a docker-compose.yml for the laziest: Here's a docker-compose.yml for the laziest:

View File

@@ -17,9 +17,7 @@ import (
"github.com/qdm12/gluetun/internal/alpine" "github.com/qdm12/gluetun/internal/alpine"
"github.com/qdm12/gluetun/internal/cli" "github.com/qdm12/gluetun/internal/cli"
"github.com/qdm12/gluetun/internal/configuration/settings" "github.com/qdm12/gluetun/internal/configuration/settings"
"github.com/qdm12/gluetun/internal/configuration/sources/env"
"github.com/qdm12/gluetun/internal/configuration/sources/files" "github.com/qdm12/gluetun/internal/configuration/sources/files"
mux "github.com/qdm12/gluetun/internal/configuration/sources/merge"
"github.com/qdm12/gluetun/internal/configuration/sources/secrets" "github.com/qdm12/gluetun/internal/configuration/sources/secrets"
"github.com/qdm12/gluetun/internal/constants" "github.com/qdm12/gluetun/internal/constants"
"github.com/qdm12/gluetun/internal/dns" "github.com/qdm12/gluetun/internal/dns"
@@ -45,6 +43,8 @@ import (
"github.com/qdm12/gluetun/internal/updater/unzip" "github.com/qdm12/gluetun/internal/updater/unzip"
"github.com/qdm12/gluetun/internal/vpn" "github.com/qdm12/gluetun/internal/vpn"
"github.com/qdm12/golibs/command" "github.com/qdm12/golibs/command"
"github.com/qdm12/gosettings/reader"
"github.com/qdm12/gosettings/reader/sources/env"
"github.com/qdm12/goshutdown" "github.com/qdm12/goshutdown"
"github.com/qdm12/goshutdown/goroutine" "github.com/qdm12/goshutdown/goroutine"
"github.com/qdm12/goshutdown/group" "github.com/qdm12/goshutdown/group"
@@ -82,14 +82,21 @@ func main() {
cli := cli.New() cli := cli.New()
cmder := command.NewCmder() cmder := command.NewCmder()
secretsReader := secrets.New() reader := reader.New(reader.Settings{
filesReader := files.New() Sources: []reader.Source{
envReader := env.New(logger) secrets.New(logger),
muxReader := mux.New(secretsReader, filesReader, envReader) 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)
},
})
errorCh := make(chan error) errorCh := make(chan error)
go func() { go func() {
errorCh <- _main(ctx, buildInfo, args, logger, muxReader, tun, netLinker, cmder, cli) errorCh <- _main(ctx, buildInfo, args, logger, reader, tun, netLinker, cmder, cli)
}() }()
var err error var err error
@@ -139,17 +146,17 @@ var (
//nolint:gocognit,gocyclo,maintidx //nolint:gocognit,gocyclo,maintidx
func _main(ctx context.Context, buildInfo models.BuildInformation, func _main(ctx context.Context, buildInfo models.BuildInformation,
args []string, logger log.LoggerInterface, source Source, args []string, logger log.LoggerInterface, reader *reader.Reader,
tun Tun, netLinker netLinker, cmder command.RunStarter, tun Tun, netLinker netLinker, cmder command.RunStarter,
cli clier) error { cli clier) error {
if len(args) > 1 { // cli operation if len(args) > 1 { // cli operation
switch args[1] { switch args[1] {
case "healthcheck": case "healthcheck":
return cli.HealthCheck(ctx, source, logger) return cli.HealthCheck(ctx, reader, logger)
case "clientkey": case "clientkey":
return cli.ClientKey(args[2:]) return cli.ClientKey(args[2:])
case "openvpnconfig": case "openvpnconfig":
return cli.OpenvpnConfig(logger, source, netLinker) return cli.OpenvpnConfig(logger, reader, netLinker)
case "update": case "update":
return cli.Update(ctx, args[2:], logger) return cli.Update(ctx, args[2:], logger)
case "format-servers": case "format-servers":
@@ -169,7 +176,7 @@ func _main(ctx context.Context, buildInfo models.BuildInformation,
Emails: []string{"quentin.mcgaw@gmail.com"}, Emails: []string{"quentin.mcgaw@gmail.com"},
Version: buildInfo.Version, Version: buildInfo.Version,
Commit: buildInfo.Commit, Commit: buildInfo.Commit,
BuildDate: buildInfo.Created, Created: buildInfo.Created,
Announcement: "Wiki moved to https://github.com/qdm12/gluetun-wiki", Announcement: "Wiki moved to https://github.com/qdm12/gluetun-wiki",
AnnounceExp: announcementExp, AnnounceExp: announcementExp,
// Sponsor information // Sponsor information
@@ -180,17 +187,22 @@ func _main(ctx context.Context, buildInfo models.BuildInformation,
fmt.Println(line) fmt.Println(line)
} }
allSettings, err := source.Read() var allSettings settings.Settings
err = allSettings.Read(reader)
if err != nil { if err != nil {
return err return err
} }
allSettings.SetDefaults()
// Note: no need to validate minimal settings for the firewall: // Note: no need to validate minimal settings for the firewall:
// - global log level is parsed from source // - global log level is parsed below
// - firewall Debug and Enabled are booleans parsed from source // - firewall Debug and Enabled are booleans parsed from source
logLevel, err := log.ParseLevel(allSettings.Log.Level)
logger.Patch(log.SetLevel(*allSettings.Log.Level)) if err != nil {
netLinker.PatchLoggerLevel(*allSettings.Log.Level) return fmt.Errorf("log level: %w", err)
}
logger.Patch(log.SetLevel(logLevel))
netLinker.PatchLoggerLevel(logLevel)
routingLogger := logger.New(log.SetComponent("routing")) routingLogger := logger.New(log.SetComponent("routing"))
if *allSettings.Firewall.Debug { // To remove in v4 if *allSettings.Firewall.Debug { // To remove in v4
@@ -267,9 +279,7 @@ func _main(ctx context.Context, buildInfo models.BuildInformation,
{name: "OpenVPN 2.5", getVersion: ovpnConf.Version25}, {name: "OpenVPN 2.5", getVersion: ovpnConf.Version25},
{name: "OpenVPN 2.6", getVersion: ovpnConf.Version26}, {name: "OpenVPN 2.6", getVersion: ovpnConf.Version26},
{name: "Unbound", getVersion: dnsConf.Version}, {name: "Unbound", getVersion: dnsConf.Version},
{name: "IPtables", getVersion: func(ctx context.Context) (version string, err error) { {name: "IPtables", getVersion: firewallConf.Version},
return firewall.Version(ctx, cmder)
}},
}) })
if err != nil { if err != nil {
return err return err
@@ -331,11 +341,15 @@ func _main(ctx context.Context, buildInfo models.BuildInformation,
} }
const tunDevice = "/dev/net/tun" const tunDevice = "/dev/net/tun"
if err := tun.Check(tunDevice); err != nil { err = tun.Check(tunDevice)
if err != nil {
if !errors.Is(err, os.ErrNotExist) {
return fmt.Errorf("checking TUN device: %w (see the Wiki errors/tun page)", err)
}
logger.Info(err.Error() + "; creating it...") logger.Info(err.Error() + "; creating it...")
err = tun.Create(tunDevice) err = tun.Create(tunDevice)
if err != nil { if err != nil {
return err return fmt.Errorf("creating tun device: %w", err)
} }
} }
@@ -578,8 +592,8 @@ type Linker interface {
type clier interface { type clier interface {
ClientKey(args []string) error ClientKey(args []string) error
FormatServers(args []string) error FormatServers(args []string) error
OpenvpnConfig(logger cli.OpenvpnConfigLogger, source cli.Source, ipv6Checker cli.IPv6Checker) error OpenvpnConfig(logger cli.OpenvpnConfigLogger, reader *reader.Reader, ipv6Checker cli.IPv6Checker) error
HealthCheck(ctx context.Context, source cli.Source, warner cli.Warner) error HealthCheck(ctx context.Context, reader *reader.Reader, warner cli.Warner) error
Update(ctx context.Context, args []string, logger cli.UpdaterLogger) error Update(ctx context.Context, args []string, logger cli.UpdaterLogger) error
} }
@@ -587,9 +601,3 @@ type Tun interface {
Check(tunDevice string) error Check(tunDevice string) error
Create(tunDevice string) error Create(tunDevice string) error
} }
type Source interface {
Read() (settings settings.Settings, err error)
ReadHealth() (health settings.Health, err error)
String() string
}

41
go.mod
View File

@@ -1,32 +1,31 @@
module github.com/qdm12/gluetun module github.com/qdm12/gluetun
go 1.21 go 1.22
require ( require (
github.com/breml/rootcerts v0.2.16 github.com/breml/rootcerts v0.2.17
github.com/fatih/color v1.16.0 github.com/fatih/color v1.17.0
github.com/golang/mock v1.6.0 github.com/golang/mock v1.6.0
github.com/klauspost/compress v1.17.4 github.com/klauspost/compress v1.17.8
github.com/klauspost/pgzip v1.2.6 github.com/klauspost/pgzip v1.2.6
github.com/qdm12/dns v1.11.0 github.com/qdm12/dns v1.11.0
github.com/qdm12/golibs v0.0.0-20210822203818-5c568b0777b6 github.com/qdm12/golibs v0.0.0-20210822203818-5c568b0777b6
github.com/qdm12/gosettings v0.4.0-rc1 github.com/qdm12/gosettings v0.4.2
github.com/qdm12/goshutdown v0.3.0 github.com/qdm12/goshutdown v0.3.0
github.com/qdm12/gosplash v0.1.0 github.com/qdm12/gosplash v0.2.0
github.com/qdm12/gotree v0.2.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/log v0.1.0
github.com/qdm12/ss-server v0.5.0 github.com/qdm12/ss-server v0.6.0
github.com/qdm12/updated v0.0.0-20210603204757-205acfe6937e github.com/qdm12/updated v0.0.0-20210603204757-205acfe6937e
github.com/stretchr/testify v1.8.4 github.com/stretchr/testify v1.9.0
github.com/ulikunitz/xz v0.5.11 github.com/ulikunitz/xz v0.5.11
github.com/vishvananda/netlink v1.2.1-beta.2 github.com/vishvananda/netlink v1.2.1-beta.2
github.com/youmark/pkcs8 v0.0.0-20201027041543-1326539a0a0a github.com/youmark/pkcs8 v0.0.0-20201027041543-1326539a0a0a
golang.org/x/net v0.19.0 golang.org/x/net v0.25.0
golang.org/x/sys v0.15.0 golang.org/x/sys v0.20.0
golang.org/x/text v0.14.0 golang.org/x/text v0.15.0
golang.zx2c4.com/wireguard v0.0.0-20230325221338-052af4a8072b golang.zx2c4.com/wireguard v0.0.0-20231211153847-12269c276173
golang.zx2c4.com/wireguard/wgctrl v0.0.0-20230215201556-9c5414ab4bde golang.zx2c4.com/wireguard/wgctrl v0.0.0-20230429144221-925a1e7659e6
gopkg.in/ini.v1 v1.67.0 gopkg.in/ini.v1 v1.67.0
inet.af/netaddr v0.0.0-20220811202034-502d2d690317 inet.af/netaddr v0.0.0-20220811202034-502d2d690317
) )
@@ -34,12 +33,12 @@ require (
require ( require (
github.com/davecgh/go-spew v1.1.1 // indirect github.com/davecgh/go-spew v1.1.1 // indirect
github.com/google/go-cmp v0.5.9 // indirect github.com/google/go-cmp v0.5.9 // indirect
github.com/josharian/native v1.0.0 // indirect github.com/josharian/native v1.1.0 // indirect
github.com/mattn/go-colorable v0.1.13 // indirect github.com/mattn/go-colorable v0.1.13 // indirect
github.com/mattn/go-isatty v0.0.20 // indirect github.com/mattn/go-isatty v0.0.20 // indirect
github.com/mdlayher/genetlink v1.2.0 // indirect github.com/mdlayher/genetlink v1.3.2 // indirect
github.com/mdlayher/netlink v1.6.2 // indirect github.com/mdlayher/netlink v1.7.2 // indirect
github.com/mdlayher/socket v0.2.3 // indirect github.com/mdlayher/socket v0.4.1 // indirect
github.com/miekg/dns v1.1.40 // indirect github.com/miekg/dns v1.1.40 // indirect
github.com/mr-tron/base58 v1.2.0 // indirect github.com/mr-tron/base58 v1.2.0 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect
@@ -47,9 +46,11 @@ require (
github.com/vishvananda/netns v0.0.0-20200728191858-db3c7e526aae // indirect github.com/vishvananda/netns v0.0.0-20200728191858-db3c7e526aae // indirect
go4.org/intern v0.0.0-20211027215823-ae77deb06f29 // indirect go4.org/intern v0.0.0-20211027215823-ae77deb06f29 // indirect
go4.org/unsafe/assume-no-moving-gc v0.0.0-20230525183740-e7c30c78aeb2 // indirect go4.org/unsafe/assume-no-moving-gc v0.0.0-20230525183740-e7c30c78aeb2 // indirect
golang.org/x/crypto v0.17.0 // indirect golang.org/x/crypto v0.23.0 // indirect
golang.org/x/exp v0.0.0-20230522175609-2e198f4a06a1 // indirect golang.org/x/exp v0.0.0-20231110203233-9a3e6036ecaa // indirect
golang.org/x/sync v0.1.0 // indirect golang.org/x/sync v0.1.0 // indirect
golang.zx2c4.com/wintun v0.0.0-20230126152724-0fa3db229ce2 // indirect golang.zx2c4.com/wintun v0.0.0-20230126152724-0fa3db229ce2 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect
kernel.org/pub/linux/libs/security/libcap/cap v1.2.69 // indirect
kernel.org/pub/linux/libs/security/libcap/psx v1.2.69 // indirect
) )

105
go.sum
View File

@@ -4,8 +4,8 @@ github.com/alcortesm/tgz v0.0.0-20161220082320-9c5fe88206d7/go.mod h1:6zEj6s6u/g
github.com/anmitsu/go-shlex v0.0.0-20161002113705-648efa622239/go.mod h1:2FmKhYUyUczH0OGQWaF5ceTx0UBShxjsH6f8oGKYe2c= github.com/anmitsu/go-shlex v0.0.0-20161002113705-648efa622239/go.mod h1:2FmKhYUyUczH0OGQWaF5ceTx0UBShxjsH6f8oGKYe2c=
github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5/go.mod h1:wHh0iHkYZB8zMSxRWpUBQtwG5a7fFgvEO+odwuTv2gs= github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5/go.mod h1:wHh0iHkYZB8zMSxRWpUBQtwG5a7fFgvEO+odwuTv2gs=
github.com/asaskevich/govalidator v0.0.0-20180720115003-f9ffefc3facf/go.mod h1:lB+ZfQJz7igIIfQNfa7Ml4HSf2uFQQRzpGGRXenZAgY= github.com/asaskevich/govalidator v0.0.0-20180720115003-f9ffefc3facf/go.mod h1:lB+ZfQJz7igIIfQNfa7Ml4HSf2uFQQRzpGGRXenZAgY=
github.com/breml/rootcerts v0.2.16 h1:yN1TGvicfHx8dKz3OQRIrx/5nE/iN3XT1ibqGbd6urc= github.com/breml/rootcerts v0.2.17 h1:0/M2BE2Apw0qEJCXDOkaiu7d5Sx5ObNfe1BkImJ4u1I=
github.com/breml/rootcerts v0.2.16/go.mod h1:S/PKh+4d1HUn4HQovEB8hPJZO6pUZYrIhmXBhsegfXw= github.com/breml/rootcerts v0.2.17/go.mod h1:S/PKh+4d1HUn4HQovEB8hPJZO6pUZYrIhmXBhsegfXw=
github.com/creack/pty v1.1.7/go.mod h1:lj5s0c3V2DBrqTV7llrYr5NG6My20zk30Fl46Y7DoTY= github.com/creack/pty v1.1.7/go.mod h1:lj5s0c3V2DBrqTV7llrYr5NG6My20zk30Fl46Y7DoTY=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
@@ -14,8 +14,8 @@ github.com/docker/go-units v0.3.3/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDD
github.com/dvyukov/go-fuzz v0.0.0-20210103155950-6a8e9d1f2415/go.mod h1:11Gm+ccJnvAhCNLlf5+cS9KjtbaD5I5zaZpFMsTHWTw= github.com/dvyukov/go-fuzz v0.0.0-20210103155950-6a8e9d1f2415/go.mod h1:11Gm+ccJnvAhCNLlf5+cS9KjtbaD5I5zaZpFMsTHWTw=
github.com/emirpasic/gods v1.12.0/go.mod h1:YfzfFFoVP/catgzJb4IKIqXjX78Ha8FMSDh3ymbK86o= github.com/emirpasic/gods v1.12.0/go.mod h1:YfzfFFoVP/catgzJb4IKIqXjX78Ha8FMSDh3ymbK86o=
github.com/fatih/color v1.12.0/go.mod h1:ELkj/draVOlAH/xkhN6mQ50Qd0MPOk5AAr3maGEBuJM= github.com/fatih/color v1.12.0/go.mod h1:ELkj/draVOlAH/xkhN6mQ50Qd0MPOk5AAr3maGEBuJM=
github.com/fatih/color v1.16.0 h1:zmkK9Ngbjj+K0yRhTVONQh1p/HknKYSlNT+vZCzyokM= github.com/fatih/color v1.17.0 h1:GlRw1BRJxkpqUCBKzKOw098ed57fEsKeNjpTe3cSjK4=
github.com/fatih/color v1.16.0/go.mod h1:fL2Sau1YI5c0pdGEVCbKQbLXB6edEj1ZgiY4NijnWvE= github.com/fatih/color v1.17.0/go.mod h1:YZ7TlrGPkiz6ku9fK3TLD/pl3CpsiFyu8N92HLgmosI=
github.com/flynn/go-shlex v0.0.0-20150515145356-3f9db97f8568/go.mod h1:xEzjJPgXI435gkrCt3MPfRiAkVrwSbHsst4LCFVfpJc= github.com/flynn/go-shlex v0.0.0-20150515145356-3f9db97f8568/go.mod h1:xEzjJPgXI435gkrCt3MPfRiAkVrwSbHsst4LCFVfpJc=
github.com/gliderlabs/ssh v0.2.2/go.mod h1:U7qILu1NlMHj9FlMhZLlkCdDnU1DBEAqr0aevW3Awn0= github.com/gliderlabs/ssh v0.2.2/go.mod h1:U7qILu1NlMHj9FlMhZLlkCdDnU1DBEAqr0aevW3Awn0=
github.com/globalsign/mgo v0.0.0-20180905125535-1ca0a4f7cbcb/go.mod h1:xkRDCp4j0OGD1HRkm4kmhM+pmpv3AKq5SU7GMg4oO/Q= github.com/globalsign/mgo v0.0.0-20180905125535-1ca0a4f7cbcb/go.mod h1:xkRDCp4j0OGD1HRkm4kmhM+pmpv3AKq5SU7GMg4oO/Q=
@@ -39,19 +39,17 @@ 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 h1:gK4Kx5IaGY9CD5sPJ36FHiBJ6ZXl0kilRiiCj+jdYp4=
github.com/google/btree v1.0.1/go.mod h1:xXMiIv4Fb/0kKde4SpL7qlzvu5cMJDRkFDxJfI9uaxA= 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.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 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38=
github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= 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/google/uuid v1.0.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/gotify/go-api-client/v2 v2.0.4/go.mod h1:VKiah/UK20bXsr0JObE1eBVLW44zbBouzjuri9iwjFU= github.com/gotify/go-api-client/v2 v2.0.4/go.mod h1:VKiah/UK20bXsr0JObE1eBVLW44zbBouzjuri9iwjFU=
github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99/go.mod h1:1lJo3i6rXxKeerYnT8Nvf0QmHCRC1n8sfWVwXF2Frvo= github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99/go.mod h1:1lJo3i6rXxKeerYnT8Nvf0QmHCRC1n8sfWVwXF2Frvo=
github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI= github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI=
github.com/josharian/native v1.0.0 h1:Ts/E8zCSEsG17dUqv7joXJFybuMLjQfWE04tsBODTxk= github.com/josharian/native v1.1.0 h1:uuaP0hAbW7Y4l0ZRQ6C9zfb7Mg1mbFKry/xzDAfmtLA=
github.com/josharian/native v1.0.0/go.mod h1:7X/raswPFr05uY3HiLlYeyQntB6OO7E/d2Cu7qoaN2w= github.com/josharian/native v1.1.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/kevinburke/ssh_config v0.0.0-20190725054713-01f96b0aa0cd/go.mod h1:CT57kijsi8u/K/BOFA39wgDQJ9CxiF4nAY/ojJ6r6mM=
github.com/klauspost/compress v1.17.4 h1:Ej5ixsIri7BrIjBkRZLTo6ghwrEtHFk7ijlczPW4fZ4= github.com/klauspost/compress v1.17.8 h1:YcnTYrq7MikUT7k0Yb5eceMmALQPYBW/Xltxn0NAMnU=
github.com/klauspost/compress v1.17.4/go.mod h1:/dCuZOvVtNoHsyb+cuJD3itjs3NbnF6KH9zAO4BDxPM= github.com/klauspost/compress v1.17.8/go.mod h1:Di0epgTjJY877eYKx5yC51cX2A2Vl2ibi7bDH9ttBbw=
github.com/klauspost/pgzip v1.2.6 h1:8RXeL5crjEUFnR2/Sn6GJNWtSQ3Dk8pq4CL3jvdDyjU= github.com/klauspost/pgzip v1.2.6 h1:8RXeL5crjEUFnR2/Sn6GJNWtSQ3Dk8pq4CL3jvdDyjU=
github.com/klauspost/pgzip v1.2.6/go.mod h1:Ch1tH69qFZu15pkjo5kYi6mth2Zzwzt50oCQKQE9RUs= github.com/klauspost/pgzip v1.2.6/go.mod h1:Ch1tH69qFZu15pkjo5kYi6mth2Zzwzt50oCQKQE9RUs=
github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI= github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI=
@@ -69,14 +67,12 @@ 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.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 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
github.com/mdlayher/genetlink v1.2.0 h1:4yrIkRV5Wfk1WfpWTcoOlGmsWgQj3OtQN9ZsbrE+XtU= github.com/mdlayher/genetlink v1.3.2 h1:KdrNKe+CTu+IbZnm/GVUMXSqBBLqcGpRDa0xkQy56gw=
github.com/mdlayher/genetlink v1.2.0/go.mod h1:ra5LDov2KrUCZJiAtEvXXZBxGMInICMXIwshlJ+qRxQ= github.com/mdlayher/genetlink v1.3.2/go.mod h1:tcC3pkCrPUGIKKsCsp0B3AdaaKuHtaxoJRz3cc+528o=
github.com/mdlayher/netlink v1.6.0/go.mod h1:0o3PlBmGst1xve7wQ7j/hwpNaFaH4qCRyWCdcZk8/vA= github.com/mdlayher/netlink v1.7.2 h1:/UtM3ofJap7Vl4QWCPDGXY8d3GIY2UGSDbK+QWmY8/g=
github.com/mdlayher/netlink v1.6.2 h1:D2zGSkvYsJ6NreeED3JiVTu1lj2sIYATqSaZlhPzUgQ= github.com/mdlayher/netlink v1.7.2/go.mod h1:xraEF7uJbxLhc5fpHL4cPe221LI2bdttWlU+ZGLfQSw=
github.com/mdlayher/netlink v1.6.2/go.mod h1:O1HXX2sIWSMJ3Qn1BYZk1yZM+7iMki/uYGGiwGyq/iU= github.com/mdlayher/socket v0.4.1 h1:eM9y2/jlbs1M615oshPQOHZzj6R6wMT7bX5NPiQvn2U=
github.com/mdlayher/socket v0.1.1/go.mod h1:mYV5YIZAfHh4dzDVzI8x8tWLWCliuX8Mon5Awbj+qDs= github.com/mdlayher/socket v0.4.1/go.mod h1:cAqeGjoufqdxWkD7DkpyS+wcefOtmu5OQ8KuoJGIReA=
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 h1:pyyPFfGMnciYUk/mXpKkVmeMQjfXqt3FAJ2hy7tPiLA=
github.com/miekg/dns v1.1.40/go.mod h1:KNUDUusw/aVsxyTYZM1oqvCicbwhgbNgztCETuNZ7xM= github.com/miekg/dns v1.1.40/go.mod h1:KNUDUusw/aVsxyTYZM1oqvCicbwhgbNgztCETuNZ7xM=
github.com/mikioh/ipaddr v0.0.0-20190404000644-d465c8ab6721 h1:RlZweED6sbSArvlE924+mUcZuXKLBHA35U7LN621Bws= github.com/mikioh/ipaddr v0.0.0-20190404000644-d465c8ab6721 h1:RlZweED6sbSArvlE924+mUcZuXKLBHA35U7LN621Bws=
@@ -97,20 +93,18 @@ github.com/qdm12/golibs v0.0.0-20210603202746-e5494e9c2ebb/go.mod h1:15RBzkun0i8
github.com/qdm12/golibs v0.0.0-20210723175634-a75ca7fd74c2/go.mod h1:6aRbg4Z/bTbm9JfxsGXfWKHi7zsOvPfUTK1S5HuAFKg= github.com/qdm12/golibs v0.0.0-20210723175634-a75ca7fd74c2/go.mod h1:6aRbg4Z/bTbm9JfxsGXfWKHi7zsOvPfUTK1S5HuAFKg=
github.com/qdm12/golibs v0.0.0-20210822203818-5c568b0777b6 h1:bge5AL7cjHJMPz+5IOz5yF01q/l8No6+lIEBieA8gMg= github.com/qdm12/golibs v0.0.0-20210822203818-5c568b0777b6 h1:bge5AL7cjHJMPz+5IOz5yF01q/l8No6+lIEBieA8gMg=
github.com/qdm12/golibs v0.0.0-20210822203818-5c568b0777b6/go.mod h1:6aRbg4Z/bTbm9JfxsGXfWKHi7zsOvPfUTK1S5HuAFKg= github.com/qdm12/golibs v0.0.0-20210822203818-5c568b0777b6/go.mod h1:6aRbg4Z/bTbm9JfxsGXfWKHi7zsOvPfUTK1S5HuAFKg=
github.com/qdm12/gosettings v0.4.0-rc1 h1:UYA92yyeDPbmZysIuG65yrpZVPtdIoRmtEHft/AyI38= github.com/qdm12/gosettings v0.4.2 h1:Gb39NScPr7OQV+oy0o1OD7A121udITDJuUGa7ljDF58=
github.com/qdm12/gosettings v0.4.0-rc1/go.mod h1:JRV3opOpHvnKlIA29lKQMdYw1WSMVMfHYLLHPHol5ME= github.com/qdm12/gosettings v0.4.2/go.mod h1:CPrt2YC4UsURTrslmhxocVhMCW03lIrqdH2hzIf5prg=
github.com/qdm12/goshutdown v0.3.0 h1:pqBpJkdwlZlfTEx4QHtS8u8CXx6pG0fVo6S1N0MpSEM= github.com/qdm12/goshutdown v0.3.0 h1:pqBpJkdwlZlfTEx4QHtS8u8CXx6pG0fVo6S1N0MpSEM=
github.com/qdm12/goshutdown v0.3.0/go.mod h1:EqZ46No00kCTZ5qzdd3qIzY6ayhMt24QI8Mh8LVQYmM= github.com/qdm12/goshutdown v0.3.0/go.mod h1:EqZ46No00kCTZ5qzdd3qIzY6ayhMt24QI8Mh8LVQYmM=
github.com/qdm12/gosplash v0.1.0 h1:Sfl+zIjFZFP7b0iqf2l5UkmEY97XBnaKkH3FNY6Gf7g= github.com/qdm12/gosplash v0.2.0 h1:DOxCEizbW6ZG+FgpH2oK1atT6bM8MHL9GZ2ywSS4zZY=
github.com/qdm12/gosplash v0.1.0/go.mod h1:+A3fWW4/rUeDXhY3ieBzwghKdnIPFJgD8K3qQkenJlw= github.com/qdm12/gosplash v0.2.0/go.mod h1:k+1PzhO0th9cpX4q2Nneu4xTsndXqrM/x7NTIYmJ4jo=
github.com/qdm12/gotree v0.2.0 h1:+58ltxkNLUyHtATFereAcOjBVfY6ETqRex8XK90Fb/c= github.com/qdm12/gotree v0.2.0 h1:+58ltxkNLUyHtATFereAcOjBVfY6ETqRex8XK90Fb/c=
github.com/qdm12/gotree v0.2.0/go.mod h1:1SdFaqKZuI46U1apbXIf25pDMNnrPuYLEqMF/qL4lY4= github.com/qdm12/gotree v0.2.0/go.mod h1:1SdFaqKZuI46U1apbXIf25pDMNnrPuYLEqMF/qL4lY4=
github.com/qdm12/govalid v0.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 h1:jYBd/xscHYpblzZAd2kjZp2YmuYHjAAfbTViJWxoPTw=
github.com/qdm12/log v0.1.0/go.mod h1:Vchi5M8uBvHfPNIblN4mjXn/oSbiWguQIbsgF1zdQPI= github.com/qdm12/log v0.1.0/go.mod h1:Vchi5M8uBvHfPNIblN4mjXn/oSbiWguQIbsgF1zdQPI=
github.com/qdm12/ss-server v0.5.0 h1:ARAqJayohDM51BmJ/R5Yplkpo+Qxgp7xizBF1HWd7uQ= github.com/qdm12/ss-server v0.6.0 h1:OaOdCIBXx0z3DGHPT6Th0v88vGa3MtAS4oRgUsDHGZE=
github.com/qdm12/ss-server v0.5.0/go.mod h1:eFd8PL/uy0ZvJ4KeSUzToruJctVQoYqXk+LRy9vcOiI= github.com/qdm12/ss-server v0.6.0/go.mod h1:0BO/zEmtTiLDlmQEcjtoHTC+w+cWxwItjBuGP6TWM78=
github.com/qdm12/updated v0.0.0-20210603204757-205acfe6937e h1:4q+uFLawkaQRq3yARYLsjJPZd2wYwxn4g6G/5v0xW1g= github.com/qdm12/updated v0.0.0-20210603204757-205acfe6937e h1:4q+uFLawkaQRq3yARYLsjJPZd2wYwxn4g6G/5v0xW1g=
github.com/qdm12/updated v0.0.0-20210603204757-205acfe6937e/go.mod h1:UvJRGkZ9XL3/D7e7JiTTVLm1F3Cymd3/gFpD6frEpBo= github.com/qdm12/updated v0.0.0-20210603204757-205acfe6937e/go.mod h1:UvJRGkZ9XL3/D7e7JiTTVLm1F3Cymd3/gFpD6frEpBo=
github.com/riobard/go-bloom v0.0.0-20200614022211-cdc8013cb5b3 h1:f/FNXud6gA3MNr8meMVVGxhp+QBTqY91tM8HjEuMjGg= github.com/riobard/go-bloom v0.0.0-20200614022211-cdc8013cb5b3 h1:f/FNXud6gA3MNr8meMVVGxhp+QBTqY91tM8HjEuMjGg=
@@ -123,8 +117,8 @@ github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXf
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk= github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= 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 h1:kpFauv27b6ynzBNT/Xy+1k+fK4WswhN/6PN5WhFAGw8=
github.com/ulikunitz/xz v0.5.11/go.mod h1:nbz6k7qbPmH4IRqmfOplQw/tblSgqTqBwxkY0oWt/14= github.com/ulikunitz/xz v0.5.11/go.mod h1:nbz6k7qbPmH4IRqmfOplQw/tblSgqTqBwxkY0oWt/14=
github.com/vishvananda/netlink v1.2.1-beta.2 h1:Llsql0lnQEbHj0I1OuKyp8otXp0r3q0mPkuhwHfStVs= github.com/vishvananda/netlink v1.2.1-beta.2 h1:Llsql0lnQEbHj0I1OuKyp8otXp0r3q0mPkuhwHfStVs=
@@ -154,31 +148,26 @@ golang.org/x/crypto v0.0.0-20200302210943-78000ba7a073/go.mod h1:LzIPMQfyMNhhGPh
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20210513164829-c07d793c2f9a/go.mod h1:P+XmwS30IXTQdn5tA2iutPOUgjI07+tq3H3K9MVA1s8= golang.org/x/crypto v0.0.0-20210513164829-c07d793c2f9a/go.mod h1:P+XmwS30IXTQdn5tA2iutPOUgjI07+tq3H3K9MVA1s8=
golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/crypto v0.17.0 h1:r8bRNjWL3GshPW3gkd+RpvzWrZAwPS49OmTGZ/uhM4k= golang.org/x/crypto v0.23.0 h1:dIJU/v2J8Mdglj/8rJ6UUOM3Zc9zLZxVZwwxMooUSAI=
golang.org/x/crypto v0.17.0/go.mod h1:gCAAfMLgwOJRpTjQ2zCCt2OcSfYMTeZVSRtQlPC7Nq4= golang.org/x/crypto v0.23.0/go.mod h1:CKFgDieR+mRhux2Lsu27y0fO304Db0wZe70UKqHu0v8=
golang.org/x/exp v0.0.0-20230522175609-2e198f4a06a1 h1:k/i9J1pBpvlfR+9QsetwPyERsqu1GIbi967PQMq3Ivc= golang.org/x/exp v0.0.0-20231110203233-9a3e6036ecaa h1:FRnLl4eNAQl8hwxVVC17teOw8kdjVDVAiFMtgUdTSRQ=
golang.org/x/exp v0.0.0-20230522175609-2e198f4a06a1/go.mod h1:V1LtkGg67GoY2N1AnLN78QLrzxkLyJw7RJb1gzOOz9w= golang.org/x/exp v0.0.0-20231110203233-9a3e6036ecaa/go.mod h1:zk2irFbV9DP96SEBUUAy67IdHUaZuSnrz1n472HUCLE=
golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/net v0.0.0-20181005035420-146acd28ed58/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20181005035420-146acd28ed58/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20190724013045-ca1201d0de80/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20190724013045-ca1201d0de80/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20190923162816-aa69164e4478/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20190923162816-aa69164e4478/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM= golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM=
golang.org/x/net v0.0.0-20210928044308-7d9f5e0b762b/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.25.0 h1:d/OCCoBEUq33pjydKrGQhw7IlUPI2Oylr+8qLx49kac=
golang.org/x/net v0.0.0-20220127200216-cd36cc0744dd/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= golang.org/x/net v0.25.0/go.mod h1:JkAGAh7GEvH74S6FOH42FLoXpXbE/aqXSrIQjXgsiwM=
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-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/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 h1:wsuoTGHzEhffawBOhz5CYhcrV4IdKZbEyZjBMuTp12o=
golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
@@ -194,29 +183,20 @@ 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-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-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-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-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-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.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.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.15.0 h1:h48lPFYpsTvQJZF4EKyI4aLHaev3CxivZmv7yZig9pc= golang.org/x/sys v0.20.0 h1:Od9JTbYCk261bKm4M/mw7AklTlFYIa0bIp9BgSm1S8Y=
golang.org/x/sys v0.15.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/sys v0.20.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-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.15.0 h1:h1V/4gjBv8v9cjcR6+AR5+/cIYK5N/WAgiv4xlsEtAk=
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= golang.org/x/text v0.15.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ= golang.org/x/time v0.0.0-20220210224613-90d013bbcef8 h1:vVKdlvoWBphwdxWKrFZEuM0kGgGLxUOYcY4U/2Vjg44=
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= golang.org/x/time v0.0.0-20220210224613-90d013bbcef8/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
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-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20190729092621-ff9f1409240a/go.mod h1:jcCCGcm9btYwXyDqrUWc6MKQKKGJCWEQ3AfLSRIbEuI= golang.org/x/tools v0.0.0-20190729092621-ff9f1409240a/go.mod h1:jcCCGcm9btYwXyDqrUWc6MKQKKGJCWEQ3AfLSRIbEuI=
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
@@ -225,14 +205,13 @@ 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/tools v0.1.1/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.zx2c4.com/wintun v0.0.0-20230126152724-0fa3db229ce2 h1:B82qJJgjvYKsXS9jeunTOisW56dUokqW/FOteYJJ/yg= golang.zx2c4.com/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/wintun v0.0.0-20230126152724-0fa3db229ce2/go.mod h1:deeaetjYA+DHMHg+sMSMI58GrEteJUUzzw7en6TJQcI=
golang.zx2c4.com/wireguard v0.0.0-20230325221338-052af4a8072b h1:J1CaxgLerRR5lgx3wnr6L04cJFbWoceSK9JWBdglINo= golang.zx2c4.com/wireguard v0.0.0-20231211153847-12269c276173 h1:/jFs0duh4rdb8uIfPMv78iAJGcPKDeqAFnaLBropIC4=
golang.zx2c4.com/wireguard v0.0.0-20230325221338-052af4a8072b/go.mod h1:tqur9LnfstdR9ep2LaJT4lFUl0EjlHtge+gAjmsHUG4= golang.zx2c4.com/wireguard v0.0.0-20231211153847-12269c276173/go.mod h1:tkCQ4FQXmpAgYVh++1cq16/dH4QJtmvpRv19DWGAHSA=
golang.zx2c4.com/wireguard/wgctrl v0.0.0-20230215201556-9c5414ab4bde h1:ybF7AMzIUikL9x4LgwEmzhXtzRpKNqngme1VGDWz+Nk= golang.zx2c4.com/wireguard/wgctrl v0.0.0-20230429144221-925a1e7659e6 h1:CawjfCvYQH2OU3/TnxLx97WDSUDRABfT18pCOYwc2GE=
golang.zx2c4.com/wireguard/wgctrl v0.0.0-20230215201556-9c5414ab4bde/go.mod h1:mQqgjkW8GQQcJQsbBvK890TKqUK1DfKWkuBGbOkuMHQ= golang.zx2c4.com/wireguard/wgctrl v0.0.0-20230429144221-925a1e7659e6/go.mod h1:3rxYc4HtVcSG9gVaTs2GEBdehh+sYPOwKtyUWEOTb80=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY=
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
@@ -247,8 +226,12 @@ 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.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 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gvisor.dev/gvisor v0.0.0-20221203005347-703fd9b7fbc0 h1:Wobr37noukisGxpKo5jAsLREcpj61RxrWYzD8uwveOY= gvisor.dev/gvisor v0.0.0-20230927004350-cbd86285d259 h1:TbRPT0HtzFP3Cno1zZo7yPzEEnfu8EjLfl6IU9VfqkQ=
gvisor.dev/gvisor v0.0.0-20221203005347-703fd9b7fbc0/go.mod h1:Dn5idtptoW1dIos9U6A2rpebLs/MtTwFacjKb8jLdQA= gvisor.dev/gvisor v0.0.0-20230927004350-cbd86285d259/go.mod h1:AVgIgHMwK63XvmAzWG9vLQ41YnVHN0du0tEC46fI7yY=
inet.af/netaddr v0.0.0-20210511181906-37180328850c/go.mod h1:z0nx+Dh+7N7CC8V5ayHtHGpZpxLQZZxkIaaz6HN65Ls= inet.af/netaddr v0.0.0-20210511181906-37180328850c/go.mod h1:z0nx+Dh+7N7CC8V5ayHtHGpZpxLQZZxkIaaz6HN65Ls=
inet.af/netaddr v0.0.0-20220811202034-502d2d690317 h1:U2fwK6P2EqmopP/hFLTOAjWTki0qgd4GMJn5X8wOleU= inet.af/netaddr v0.0.0-20220811202034-502d2d690317 h1:U2fwK6P2EqmopP/hFLTOAjWTki0qgd4GMJn5X8wOleU=
inet.af/netaddr v0.0.0-20220811202034-502d2d690317/go.mod h1:OIezDfdzOgFhuw4HuWapWq2e9l0H9tK4F1j+ETRtF3k= 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=

View File

@@ -6,13 +6,12 @@ import (
"io" "io"
"os" "os"
"strings" "strings"
"github.com/qdm12/gluetun/internal/configuration/sources/files"
) )
func (c *CLI) ClientKey(args []string) error { func (c *CLI) ClientKey(args []string) error {
flagSet := flag.NewFlagSet("clientkey", flag.ExitOnError) flagSet := flag.NewFlagSet("clientkey", flag.ExitOnError)
filepath := flagSet.String("path", files.OpenVPNClientKeyPath, "file path to the client.key file") const openVPNClientKeyPath = "/gluetun/client.key" // TODO deduplicate?
filepath := flagSet.String("path", openVPNClientKeyPath, "file path to the client.key file")
if err := flagSet.Parse(args); err != nil { if err := flagSet.Parse(args); err != nil {
return err return err
} }

View File

@@ -6,12 +6,15 @@ import (
"net/http" "net/http"
"time" "time"
"github.com/qdm12/gluetun/internal/configuration/settings"
"github.com/qdm12/gluetun/internal/healthcheck" "github.com/qdm12/gluetun/internal/healthcheck"
"github.com/qdm12/gosettings/reader"
) )
func (c *CLI) HealthCheck(ctx context.Context, source Source, _ Warner) error { func (c *CLI) HealthCheck(ctx context.Context, reader *reader.Reader, _ Warner) (err error) {
// Extract the health server port from the configuration. // Extract the health server port from the configuration.
config, err := source.ReadHealth() var config settings.Health
err = config.Read(reader)
if err != nil { if err != nil {
return err return err
} }

View File

@@ -8,12 +8,14 @@ import (
"strings" "strings"
"time" "time"
"github.com/qdm12/gluetun/internal/configuration/settings"
"github.com/qdm12/gluetun/internal/constants" "github.com/qdm12/gluetun/internal/constants"
"github.com/qdm12/gluetun/internal/models" "github.com/qdm12/gluetun/internal/models"
"github.com/qdm12/gluetun/internal/openvpn/extract" "github.com/qdm12/gluetun/internal/openvpn/extract"
"github.com/qdm12/gluetun/internal/provider" "github.com/qdm12/gluetun/internal/provider"
"github.com/qdm12/gluetun/internal/storage" "github.com/qdm12/gluetun/internal/storage"
"github.com/qdm12/gluetun/internal/updater/resolver" "github.com/qdm12/gluetun/internal/updater/resolver"
"github.com/qdm12/gosettings/reader"
) )
type OpenvpnConfigLogger interface { type OpenvpnConfigLogger interface {
@@ -39,14 +41,15 @@ type IPv6Checker interface {
IsIPv6Supported() (supported bool, err error) IsIPv6Supported() (supported bool, err error)
} }
func (c *CLI) OpenvpnConfig(logger OpenvpnConfigLogger, source Source, func (c *CLI) OpenvpnConfig(logger OpenvpnConfigLogger, reader *reader.Reader,
ipv6Checker IPv6Checker) error { ipv6Checker IPv6Checker) error {
storage, err := storage.New(logger, constants.ServersData) storage, err := storage.New(logger, constants.ServersData)
if err != nil { if err != nil {
return err return err
} }
allSettings, err := source.Read() var allSettings settings.Settings
err = allSettings.Read(reader)
if err != nil { if err != nil {
return err return err
} }
@@ -70,7 +73,7 @@ func (c *CLI) OpenvpnConfig(logger OpenvpnConfigLogger, source Source,
providers := provider.NewProviders(storage, time.Now, warner, client, providers := provider.NewProviders(storage, time.Now, warner, client,
unzipper, parallelResolver, ipFetcher, openvpnFileExtractor) unzipper, parallelResolver, ipFetcher, openvpnFileExtractor)
providerConf := providers.Get(*allSettings.VPN.Provider.Name) providerConf := providers.Get(allSettings.VPN.Provider.Name)
connection, err := providerConf.GetConnection( connection, err := providerConf.GetConnection(
allSettings.VPN.Provider.ServerSelection, ipv6Supported) allSettings.VPN.Provider.ServerSelection, ipv6Supported)
if err != nil { if err != nil {

View File

@@ -5,6 +5,7 @@ import (
"net/netip" "net/netip"
"github.com/qdm12/gosettings" "github.com/qdm12/gosettings"
"github.com/qdm12/gosettings/reader"
"github.com/qdm12/gotree" "github.com/qdm12/gotree"
) )
@@ -49,14 +50,6 @@ 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 // overrideWith overrides fields of the receiver
// settings object with any field set in the other // settings object with any field set in the other
// settings. // settings.
@@ -87,3 +80,22 @@ func (d DNS) toLinesNode() (node *gotree.Node) {
node.AppendNode(d.DoT.toLinesNode()) node.AppendNode(d.DoT.toLinesNode())
return node 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
}

View File

@@ -8,6 +8,7 @@ import (
"github.com/qdm12/dns/pkg/blacklist" "github.com/qdm12/dns/pkg/blacklist"
"github.com/qdm12/gosettings" "github.com/qdm12/gosettings"
"github.com/qdm12/gosettings/reader"
"github.com/qdm12/gotree" "github.com/qdm12/gotree"
) )
@@ -63,16 +64,6 @@ 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) { func (b *DNSBlacklist) overrideWith(other DNSBlacklist) {
b.BlockMalicious = gosettings.OverrideWithPointer(b.BlockMalicious, other.BlockMalicious) b.BlockMalicious = gosettings.OverrideWithPointer(b.BlockMalicious, other.BlockMalicious)
b.BlockAds = gosettings.OverrideWithPointer(b.BlockAds, other.BlockAds) b.BlockAds = gosettings.OverrideWithPointer(b.BlockAds, other.BlockAds)
@@ -136,3 +127,66 @@ func (b DNSBlacklist) toLinesNode() (node *gotree.Node) {
return 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
}

View File

@@ -6,6 +6,7 @@ import (
"time" "time"
"github.com/qdm12/gosettings" "github.com/qdm12/gosettings"
"github.com/qdm12/gosettings/reader"
"github.com/qdm12/gotree" "github.com/qdm12/gotree"
) )
@@ -61,15 +62,6 @@ 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 // overrideWith overrides fields of the receiver
// settings object with any field set in the other // settings object with any field set in the other
// settings. // settings.
@@ -111,3 +103,27 @@ func (d DoT) toLinesNode() (node *gotree.Node) {
return 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
}

View File

@@ -3,6 +3,7 @@ package settings
import "errors" import "errors"
var ( var (
ErrValueUnknown = errors.New("value is unknown")
ErrCityNotValid = errors.New("the city specified is not valid") ErrCityNotValid = errors.New("the city specified is not valid")
ErrControlServerPrivilegedPort = errors.New("cannot use privileged port without running as root") ErrControlServerPrivilegedPort = errors.New("cannot use privileged port without running as root")
ErrCategoryNotValid = errors.New("the category specified is not valid") ErrCategoryNotValid = errors.New("the category specified is not valid")
@@ -27,6 +28,8 @@ var (
ErrOpenVPNVerbosityIsOutOfBounds = errors.New("verbosity value is out of bounds") ErrOpenVPNVerbosityIsOutOfBounds = errors.New("verbosity value is out of bounds")
ErrOpenVPNVersionIsNotValid = errors.New("version is not valid") ErrOpenVPNVersionIsNotValid = errors.New("version is not valid")
ErrPortForwardingEnabled = errors.New("port forwarding cannot be enabled") 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") ErrPublicIPPeriodTooShort = errors.New("public IP address check period is too short")
ErrRegionNotValid = errors.New("the region specified is not valid") ErrRegionNotValid = errors.New("the region specified is not valid")
ErrServerAddressNotValid = errors.New("server listening address is not valid") ErrServerAddressNotValid = errors.New("server listening address is not valid")
@@ -49,5 +52,6 @@ var (
ErrWireguardPrivateKeyNotSet = errors.New("private key is not set") ErrWireguardPrivateKeyNotSet = errors.New("private key is not set")
ErrWireguardPublicKeyNotSet = errors.New("public key is not set") ErrWireguardPublicKeyNotSet = errors.New("public key is not set")
ErrWireguardPublicKeyNotValid = errors.New("public key is not valid") ErrWireguardPublicKeyNotValid = errors.New("public key is not valid")
ErrWireguardKeepAliveNegative = errors.New("persistent keep alive interval is negative")
ErrWireguardImplementationNotValid = errors.New("implementation is not valid") ErrWireguardImplementationNotValid = errors.New("implementation is not valid")
) )

View File

@@ -5,6 +5,7 @@ import (
"net/netip" "net/netip"
"github.com/qdm12/gosettings" "github.com/qdm12/gosettings"
"github.com/qdm12/gosettings/reader"
"github.com/qdm12/gotree" "github.com/qdm12/gotree"
) )
@@ -54,18 +55,6 @@ 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 // overrideWith overrides fields of the receiver
// settings object with any field set in the other // settings object with any field set in the other
// settings. // settings.
@@ -122,3 +111,33 @@ func (f Firewall) toLinesNode() (node *gotree.Node) {
return 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
}

View File

@@ -6,8 +6,9 @@ import (
"time" "time"
"github.com/qdm12/gosettings" "github.com/qdm12/gosettings"
"github.com/qdm12/gosettings/reader"
"github.com/qdm12/gosettings/validate"
"github.com/qdm12/gotree" "github.com/qdm12/gotree"
"github.com/qdm12/govalid/address"
) )
// Health contains settings for the healthcheck and health server. // Health contains settings for the healthcheck and health server.
@@ -36,9 +37,7 @@ type Health struct {
} }
func (h Health) Validate() (err error) { func (h Health) Validate() (err error) {
uid := os.Getuid() err = validate.ListeningAddress(h.ServerAddress, os.Getuid())
err = address.Validate(h.ServerAddress,
address.OptionListening(uid))
if err != nil { if err != nil {
return fmt.Errorf("server listening address is not valid: %w", err) return fmt.Errorf("server listening address is not valid: %w", err)
} }
@@ -62,38 +61,27 @@ 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 // OverrideWith overrides fields of the receiver
// settings object with any field set in the other // settings object with any field set in the other
// settings. // settings.
func (h *Health) OverrideWith(other Health) { func (h *Health) OverrideWith(other Health) {
h.ServerAddress = gosettings.OverrideWithString(h.ServerAddress, other.ServerAddress) h.ServerAddress = gosettings.OverrideWithComparable(h.ServerAddress, other.ServerAddress)
h.ReadHeaderTimeout = gosettings.OverrideWithNumber(h.ReadHeaderTimeout, other.ReadHeaderTimeout) h.ReadHeaderTimeout = gosettings.OverrideWithComparable(h.ReadHeaderTimeout, other.ReadHeaderTimeout)
h.ReadTimeout = gosettings.OverrideWithNumber(h.ReadTimeout, other.ReadTimeout) h.ReadTimeout = gosettings.OverrideWithComparable(h.ReadTimeout, other.ReadTimeout)
h.TargetAddress = gosettings.OverrideWithString(h.TargetAddress, other.TargetAddress) h.TargetAddress = gosettings.OverrideWithComparable(h.TargetAddress, other.TargetAddress)
h.SuccessWait = gosettings.OverrideWithNumber(h.SuccessWait, other.SuccessWait) h.SuccessWait = gosettings.OverrideWithComparable(h.SuccessWait, other.SuccessWait)
h.VPN.overrideWith(other.VPN) h.VPN.overrideWith(other.VPN)
} }
func (h *Health) SetDefaults() { func (h *Health) SetDefaults() {
h.ServerAddress = gosettings.DefaultString(h.ServerAddress, "127.0.0.1:9999") h.ServerAddress = gosettings.DefaultComparable(h.ServerAddress, "127.0.0.1:9999")
const defaultReadHeaderTimeout = 100 * time.Millisecond const defaultReadHeaderTimeout = 100 * time.Millisecond
h.ReadHeaderTimeout = gosettings.DefaultNumber(h.ReadHeaderTimeout, defaultReadHeaderTimeout) h.ReadHeaderTimeout = gosettings.DefaultComparable(h.ReadHeaderTimeout, defaultReadHeaderTimeout)
const defaultReadTimeout = 500 * time.Millisecond const defaultReadTimeout = 500 * time.Millisecond
h.ReadTimeout = gosettings.DefaultNumber(h.ReadTimeout, defaultReadTimeout) h.ReadTimeout = gosettings.DefaultComparable(h.ReadTimeout, defaultReadTimeout)
h.TargetAddress = gosettings.DefaultString(h.TargetAddress, "cloudflare.com:443") h.TargetAddress = gosettings.DefaultComparable(h.TargetAddress, "cloudflare.com:443")
const defaultSuccessWait = 5 * time.Second const defaultSuccessWait = 5 * time.Second
h.SuccessWait = gosettings.DefaultNumber(h.SuccessWait, defaultSuccessWait) h.SuccessWait = gosettings.DefaultComparable(h.SuccessWait, defaultSuccessWait)
h.VPN.setDefaults() h.VPN.setDefaults()
} }
@@ -111,3 +99,21 @@ func (h Health) toLinesNode() (node *gotree.Node) {
node.AppendNode(h.VPN.toLinesNode("VPN")) node.AppendNode(h.VPN.toLinesNode("VPN"))
return node 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
}

View File

@@ -4,6 +4,7 @@ import (
"time" "time"
"github.com/qdm12/gosettings" "github.com/qdm12/gosettings"
"github.com/qdm12/gosettings/reader"
"github.com/qdm12/gotree" "github.com/qdm12/gotree"
) )
@@ -23,8 +24,6 @@ func (h HealthyWait) validate() (err error) {
return nil return nil
} }
// mergeWith merges the other settings into any
// unset field of the receiver settings object.
func (h *HealthyWait) copy() (copied HealthyWait) { func (h *HealthyWait) copy() (copied HealthyWait) {
return HealthyWait{ return HealthyWait{
Initial: gosettings.CopyPointer(h.Initial), Initial: gosettings.CopyPointer(h.Initial),
@@ -32,13 +31,6 @@ 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 // overrideWith overrides fields of the receiver
// settings object with any field set in the other // settings object with any field set in the other
// settings. // settings.
@@ -64,3 +56,21 @@ func (h HealthyWait) toLinesNode(kind string) (node *gotree.Node) {
node.Appendf("Additional duration: %s", *h.Addition) node.Appendf("Additional duration: %s", *h.Addition)
return node 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
}

View File

@@ -0,0 +1,5 @@
package settings
func ptrTo[T any](value T) *T {
return &value
}

View File

@@ -1,8 +0,0 @@
package helpers
func TCPPtrToString(tcp *bool) string {
if *tcp {
return "TCP"
}
return "UDP"
}

View File

@@ -3,11 +3,13 @@ package settings
import ( import (
"fmt" "fmt"
"os" "os"
"strings"
"time" "time"
"github.com/qdm12/gosettings" "github.com/qdm12/gosettings"
"github.com/qdm12/gosettings/reader"
"github.com/qdm12/gosettings/validate"
"github.com/qdm12/gotree" "github.com/qdm12/gotree"
"github.com/qdm12/govalid/address"
) )
// HTTPProxy contains settings to configure the HTTP proxy. // HTTPProxy contains settings to configure the HTTP proxy.
@@ -44,9 +46,7 @@ type HTTPProxy struct {
func (h HTTPProxy) validate() (err error) { func (h HTTPProxy) validate() (err error) {
// Do not validate user and password // 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 { if err != nil {
return fmt.Errorf("%w: %s", ErrServerAddressNotValid, h.ListeningAddress) return fmt.Errorf("%w: %s", ErrServerAddressNotValid, h.ListeningAddress)
} }
@@ -67,44 +67,31 @@ 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 // overrideWith overrides fields of the receiver
// settings object with any field set in the other // settings object with any field set in the other
// settings. // settings.
func (h *HTTPProxy) overrideWith(other HTTPProxy) { func (h *HTTPProxy) overrideWith(other HTTPProxy) {
h.User = gosettings.OverrideWithPointer(h.User, other.User) h.User = gosettings.OverrideWithPointer(h.User, other.User)
h.Password = gosettings.OverrideWithPointer(h.Password, other.Password) h.Password = gosettings.OverrideWithPointer(h.Password, other.Password)
h.ListeningAddress = gosettings.OverrideWithString(h.ListeningAddress, other.ListeningAddress) h.ListeningAddress = gosettings.OverrideWithComparable(h.ListeningAddress, other.ListeningAddress)
h.Enabled = gosettings.OverrideWithPointer(h.Enabled, other.Enabled) h.Enabled = gosettings.OverrideWithPointer(h.Enabled, other.Enabled)
h.Stealth = gosettings.OverrideWithPointer(h.Stealth, other.Stealth) h.Stealth = gosettings.OverrideWithPointer(h.Stealth, other.Stealth)
h.Log = gosettings.OverrideWithPointer(h.Log, other.Log) h.Log = gosettings.OverrideWithPointer(h.Log, other.Log)
h.ReadHeaderTimeout = gosettings.OverrideWithNumber(h.ReadHeaderTimeout, other.ReadHeaderTimeout) h.ReadHeaderTimeout = gosettings.OverrideWithComparable(h.ReadHeaderTimeout, other.ReadHeaderTimeout)
h.ReadTimeout = gosettings.OverrideWithNumber(h.ReadTimeout, other.ReadTimeout) h.ReadTimeout = gosettings.OverrideWithComparable(h.ReadTimeout, other.ReadTimeout)
} }
func (h *HTTPProxy) setDefaults() { func (h *HTTPProxy) setDefaults() {
h.User = gosettings.DefaultPointer(h.User, "") h.User = gosettings.DefaultPointer(h.User, "")
h.Password = gosettings.DefaultPointer(h.Password, "") h.Password = gosettings.DefaultPointer(h.Password, "")
h.ListeningAddress = gosettings.DefaultString(h.ListeningAddress, ":8888") h.ListeningAddress = gosettings.DefaultComparable(h.ListeningAddress, ":8888")
h.Enabled = gosettings.DefaultPointer(h.Enabled, false) h.Enabled = gosettings.DefaultPointer(h.Enabled, false)
h.Stealth = gosettings.DefaultPointer(h.Stealth, false) h.Stealth = gosettings.DefaultPointer(h.Stealth, false)
h.Log = gosettings.DefaultPointer(h.Log, false) h.Log = gosettings.DefaultPointer(h.Log, false)
const defaultReadHeaderTimeout = time.Second const defaultReadHeaderTimeout = time.Second
h.ReadHeaderTimeout = gosettings.DefaultNumber(h.ReadHeaderTimeout, defaultReadHeaderTimeout) h.ReadHeaderTimeout = gosettings.DefaultComparable(h.ReadHeaderTimeout, defaultReadHeaderTimeout)
const defaultReadTimeout = 3 * time.Second const defaultReadTimeout = 3 * time.Second
h.ReadTimeout = gosettings.DefaultNumber(h.ReadTimeout, defaultReadTimeout) h.ReadTimeout = gosettings.DefaultComparable(h.ReadTimeout, defaultReadTimeout)
} }
func (h HTTPProxy) String() string { func (h HTTPProxy) String() string {
@@ -128,3 +115,68 @@ func (h HTTPProxy) toLinesNode() (node *gotree.Node) {
return 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)
}
}

View File

@@ -1,7 +1,10 @@
package settings package settings
import ( import (
"fmt"
"github.com/qdm12/gosettings" "github.com/qdm12/gosettings"
"github.com/qdm12/gosettings/reader"
"github.com/qdm12/gotree" "github.com/qdm12/gotree"
"github.com/qdm12/log" "github.com/qdm12/log"
) )
@@ -9,35 +12,33 @@ import (
// Log contains settings to configure the logger. // Log contains settings to configure the logger.
type Log struct { type Log struct {
// Level is the log level of the logger. // Level is the log level of the logger.
// It cannot be nil in the internal state. // It cannot be empty in the internal state.
Level *log.Level Level string
} }
func (l Log) validate() (err error) { func (l Log) validate() (err error) {
_, err = log.ParseLevel(l.Level)
if err != nil {
return fmt.Errorf("level: %w", err)
}
return nil return nil
} }
func (l *Log) copy() (copied Log) { func (l *Log) copy() (copied Log) {
return Log{ return Log{
Level: gosettings.CopyPointer(l.Level), Level: 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 // overrideWith overrides fields of the receiver
// settings object with any field set in the other // settings object with any field set in the other
// settings. // settings.
func (l *Log) overrideWith(other Log) { func (l *Log) overrideWith(other Log) {
l.Level = gosettings.OverrideWithPointer(l.Level, other.Level) l.Level = gosettings.OverrideWithComparable(l.Level, other.Level)
} }
func (l *Log) setDefaults() { func (l *Log) setDefaults() {
l.Level = gosettings.DefaultPointer(l.Level, log.LevelInfo) l.Level = gosettings.DefaultComparable(l.Level, log.LevelInfo.String())
} }
func (l Log) String() string { func (l Log) String() string {
@@ -46,6 +47,11 @@ func (l Log) String() string {
func (l Log) toLinesNode() (node *gotree.Node) { func (l Log) toLinesNode() (node *gotree.Node) {
node = gotree.New("Log settings:") node = gotree.New("Log settings:")
node.Appendf("Log level: %s", l.Level.String()) node.Appendf("Log level: %s", l.Level)
return node return node
} }
func (l *Log) read(r *reader.Reader) (err error) {
l.Level = r.String("LOG_LEVEL")
return nil
}

View File

@@ -4,12 +4,14 @@ import (
"encoding/base64" "encoding/base64"
"fmt" "fmt"
"regexp" "regexp"
"strings"
"github.com/qdm12/gluetun/internal/constants/openvpn" "github.com/qdm12/gluetun/internal/constants/openvpn"
"github.com/qdm12/gluetun/internal/constants/providers" "github.com/qdm12/gluetun/internal/constants/providers"
"github.com/qdm12/gluetun/internal/openvpn/extract" "github.com/qdm12/gluetun/internal/openvpn/extract"
"github.com/qdm12/gluetun/internal/provider/privateinternetaccess/presets" "github.com/qdm12/gluetun/internal/provider/privateinternetaccess/presets"
"github.com/qdm12/gosettings" "github.com/qdm12/gosettings"
"github.com/qdm12/gosettings/reader"
"github.com/qdm12/gosettings/validate" "github.com/qdm12/gosettings/validate"
"github.com/qdm12/gotree" "github.com/qdm12/gotree"
) )
@@ -261,32 +263,11 @@ 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 // overrideWith overrides fields of the receiver
// settings object with any field set in the other // settings object with any field set in the other
// settings. // settings.
func (o *OpenVPN) overrideWith(other OpenVPN) { func (o *OpenVPN) overrideWith(other OpenVPN) {
o.Version = gosettings.OverrideWithString(o.Version, other.Version) o.Version = gosettings.OverrideWithComparable(o.Version, other.Version)
o.User = gosettings.OverrideWithPointer(o.User, other.User) o.User = gosettings.OverrideWithPointer(o.User, other.User)
o.Password = gosettings.OverrideWithPointer(o.Password, other.Password) o.Password = gosettings.OverrideWithPointer(o.Password, other.Password)
o.ConfFile = gosettings.OverrideWithPointer(o.ConfFile, other.ConfFile) o.ConfFile = gosettings.OverrideWithPointer(o.ConfFile, other.ConfFile)
@@ -298,14 +279,14 @@ func (o *OpenVPN) overrideWith(other OpenVPN) {
o.KeyPassphrase = gosettings.OverrideWithPointer(o.KeyPassphrase, other.KeyPassphrase) o.KeyPassphrase = gosettings.OverrideWithPointer(o.KeyPassphrase, other.KeyPassphrase)
o.PIAEncPreset = gosettings.OverrideWithPointer(o.PIAEncPreset, other.PIAEncPreset) o.PIAEncPreset = gosettings.OverrideWithPointer(o.PIAEncPreset, other.PIAEncPreset)
o.MSSFix = gosettings.OverrideWithPointer(o.MSSFix, other.MSSFix) o.MSSFix = gosettings.OverrideWithPointer(o.MSSFix, other.MSSFix)
o.Interface = gosettings.OverrideWithString(o.Interface, other.Interface) o.Interface = gosettings.OverrideWithComparable(o.Interface, other.Interface)
o.ProcessUser = gosettings.OverrideWithString(o.ProcessUser, other.ProcessUser) o.ProcessUser = gosettings.OverrideWithComparable(o.ProcessUser, other.ProcessUser)
o.Verbosity = gosettings.OverrideWithPointer(o.Verbosity, other.Verbosity) o.Verbosity = gosettings.OverrideWithPointer(o.Verbosity, other.Verbosity)
o.Flags = gosettings.OverrideWithSlice(o.Flags, other.Flags) o.Flags = gosettings.OverrideWithSlice(o.Flags, other.Flags)
} }
func (o *OpenVPN) setDefaults(vpnProvider string) { func (o *OpenVPN) setDefaults(vpnProvider string) {
o.Version = gosettings.DefaultString(o.Version, openvpn.Openvpn25) o.Version = gosettings.DefaultComparable(o.Version, openvpn.Openvpn26)
o.User = gosettings.DefaultPointer(o.User, "") o.User = gosettings.DefaultPointer(o.User, "")
if vpnProvider == providers.Mullvad { if vpnProvider == providers.Mullvad {
o.Password = gosettings.DefaultPointer(o.Password, "m") o.Password = gosettings.DefaultPointer(o.Password, "m")
@@ -326,8 +307,8 @@ func (o *OpenVPN) setDefaults(vpnProvider string) {
} }
o.PIAEncPreset = gosettings.DefaultPointer(o.PIAEncPreset, defaultEncPreset) o.PIAEncPreset = gosettings.DefaultPointer(o.PIAEncPreset, defaultEncPreset)
o.MSSFix = gosettings.DefaultPointer(o.MSSFix, 0) o.MSSFix = gosettings.DefaultPointer(o.MSSFix, 0)
o.Interface = gosettings.DefaultString(o.Interface, "tun0") o.Interface = gosettings.DefaultComparable(o.Interface, "tun0")
o.ProcessUser = gosettings.DefaultString(o.ProcessUser, "root") o.ProcessUser = gosettings.DefaultComparable(o.ProcessUser, "root")
o.Verbosity = gosettings.DefaultPointer(o.Verbosity, 1) o.Verbosity = gosettings.DefaultPointer(o.Verbosity, 1)
} }
@@ -395,3 +376,58 @@ func (o OpenVPN) WithDefaults(provider string) OpenVPN {
o.setDefaults(provider) o.setDefaults(provider)
return o 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
}

View File

@@ -2,11 +2,14 @@ package settings
import ( import (
"fmt" "fmt"
"strings"
"github.com/qdm12/gluetun/internal/configuration/settings/helpers" "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/constants/providers"
"github.com/qdm12/gluetun/internal/provider/privateinternetaccess/presets" "github.com/qdm12/gluetun/internal/provider/privateinternetaccess/presets"
"github.com/qdm12/gosettings" "github.com/qdm12/gosettings"
"github.com/qdm12/gosettings/reader"
"github.com/qdm12/gosettings/validate" "github.com/qdm12/gosettings/validate"
"github.com/qdm12/gotree" "github.com/qdm12/gotree"
) )
@@ -17,10 +20,10 @@ type OpenVPNSelection struct {
// NOT use a custom configuration file. // NOT use a custom configuration file.
// It cannot be nil in the internal state. // It cannot be nil in the internal state.
ConfFile *string `json:"config_file_path"` ConfFile *string `json:"config_file_path"`
// TCP is true if the OpenVPN protocol is TCP, // Protocol is the OpenVPN network protocol to use,
// and false for UDP. // and can be udp or tcp. It cannot be the empty string
// It cannot be nil in the internal state. // in the internal state.
TCP *bool `json:"tcp"` Protocol string `json:"protocol"`
// CustomPort is the OpenVPN server endpoint port. // CustomPort is the OpenVPN server endpoint port.
// It can be set to 0 to indicate no custom port should // It can be set to 0 to indicate no custom port should
// be used. It cannot be nil in the internal state. // be used. It cannot be nil in the internal state.
@@ -40,12 +43,16 @@ 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 // Validate TCP
if *o.TCP && helpers.IsOneOf(vpnProvider, if o.Protocol == constants.TCP && helpers.IsOneOf(vpnProvider,
providers.Ipvanish, providers.Ipvanish,
providers.Perfectprivacy, providers.Perfectprivacy,
providers.Privado, providers.Privado,
providers.VPNUnlimited,
providers.Vyprvpn, providers.Vyprvpn,
) { ) {
return fmt.Errorf("%w: for VPN service provider %s", return fmt.Errorf("%w: for VPN service provider %s",
@@ -104,7 +111,7 @@ func (o OpenVPNSelection) validate(vpnProvider string) (err error) {
} }
allowedPorts := allowedUDP allowedPorts := allowedUDP
if *o.TCP { if o.Protocol == constants.TCP {
allowedPorts = allowedTCP allowedPorts = allowedTCP
} }
err = validate.IsOneOf(*o.CustomPort, allowedPorts...) err = validate.IsOneOf(*o.CustomPort, allowedPorts...)
@@ -133,29 +140,22 @@ func (o OpenVPNSelection) validate(vpnProvider string) (err error) {
func (o *OpenVPNSelection) copy() (copied OpenVPNSelection) { func (o *OpenVPNSelection) copy() (copied OpenVPNSelection) {
return OpenVPNSelection{ return OpenVPNSelection{
ConfFile: gosettings.CopyPointer(o.ConfFile), ConfFile: gosettings.CopyPointer(o.ConfFile),
TCP: gosettings.CopyPointer(o.TCP), Protocol: o.Protocol,
CustomPort: gosettings.CopyPointer(o.CustomPort), CustomPort: gosettings.CopyPointer(o.CustomPort),
PIAEncPreset: gosettings.CopyPointer(o.PIAEncPreset), 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) { func (o *OpenVPNSelection) overrideWith(other OpenVPNSelection) {
o.ConfFile = gosettings.OverrideWithPointer(o.ConfFile, other.ConfFile) o.ConfFile = gosettings.OverrideWithPointer(o.ConfFile, other.ConfFile)
o.TCP = gosettings.OverrideWithPointer(o.TCP, other.TCP) o.Protocol = gosettings.OverrideWithComparable(o.Protocol, other.Protocol)
o.CustomPort = gosettings.OverrideWithPointer(o.CustomPort, other.CustomPort) o.CustomPort = gosettings.OverrideWithPointer(o.CustomPort, other.CustomPort)
o.PIAEncPreset = gosettings.OverrideWithPointer(o.PIAEncPreset, other.PIAEncPreset) o.PIAEncPreset = gosettings.OverrideWithPointer(o.PIAEncPreset, other.PIAEncPreset)
} }
func (o *OpenVPNSelection) setDefaults(vpnProvider string) { func (o *OpenVPNSelection) setDefaults(vpnProvider string) {
o.ConfFile = gosettings.DefaultPointer(o.ConfFile, "") o.ConfFile = gosettings.DefaultPointer(o.ConfFile, "")
o.TCP = gosettings.DefaultPointer(o.TCP, false) o.Protocol = gosettings.DefaultComparable(o.Protocol, constants.UDP)
o.CustomPort = gosettings.DefaultPointer(o.CustomPort, 0) o.CustomPort = gosettings.DefaultPointer(o.CustomPort, 0)
var defaultEncPreset string var defaultEncPreset string
@@ -171,7 +171,7 @@ func (o OpenVPNSelection) String() string {
func (o OpenVPNSelection) toLinesNode() (node *gotree.Node) { func (o OpenVPNSelection) toLinesNode() (node *gotree.Node) {
node = gotree.New("OpenVPN server selection settings:") node = gotree.New("OpenVPN server selection settings:")
node.Appendf("Protocol: %s", helpers.TCPPtrToString(o.TCP)) node.Appendf("Protocol: %s", strings.ToUpper(o.Protocol))
if *o.CustomPort != 0 { if *o.CustomPort != 0 {
node.Appendf("Custom port: %d", *o.CustomPort) node.Appendf("Custom port: %d", *o.CustomPort)
@@ -187,3 +187,23 @@ func (o OpenVPNSelection) toLinesNode() (node *gotree.Node) {
return 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
}

View File

@@ -6,6 +6,7 @@ import (
"github.com/qdm12/gluetun/internal/constants/providers" "github.com/qdm12/gluetun/internal/constants/providers"
"github.com/qdm12/gosettings" "github.com/qdm12/gosettings"
"github.com/qdm12/gosettings/reader"
"github.com/qdm12/gosettings/validate" "github.com/qdm12/gosettings/validate"
"github.com/qdm12/gotree" "github.com/qdm12/gotree"
) )
@@ -18,7 +19,7 @@ type PortForwarding struct {
// Provider is set to specify which custom port forwarding code // Provider is set to specify which custom port forwarding code
// should be used. This is especially necessary for the custom // should be used. This is especially necessary for the custom
// provider using Wireguard for a provider where Wireguard is not // provider using Wireguard for a provider where Wireguard is not
// natively supported but custom port forwading code is available. // natively supported but custom port forwarding code is available.
// It defaults to the empty string, meaning the current provider // It defaults to the empty string, meaning the current provider
// should be the one used for port forwarding. // should be the one used for port forwarding.
// It cannot be nil for the internal state. // It cannot be nil for the internal state.
@@ -32,6 +33,10 @@ type PortForwarding struct {
// forwarded port. The redirection is disabled if it is set to 0, which // forwarded port. The redirection is disabled if it is set to 0, which
// is its default as well. // is its default as well.
ListeningPort *uint16 `json:"listening_port"` 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) { func (p PortForwarding) Validate(vpnProvider string) (err error) {
@@ -45,6 +50,7 @@ func (p PortForwarding) Validate(vpnProvider string) (err error) {
providerSelected = *p.Provider providerSelected = *p.Provider
} }
validProviders := []string{ validProviders := []string{
providers.Perfectprivacy,
providers.PrivateInternetAccess, providers.PrivateInternetAccess,
providers.Protonvpn, providers.Protonvpn,
} }
@@ -60,6 +66,15 @@ 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 return nil
} }
@@ -69,21 +84,18 @@ func (p *PortForwarding) Copy() (copied PortForwarding) {
Provider: gosettings.CopyPointer(p.Provider), Provider: gosettings.CopyPointer(p.Provider),
Filepath: gosettings.CopyPointer(p.Filepath), Filepath: gosettings.CopyPointer(p.Filepath),
ListeningPort: gosettings.CopyPointer(p.ListeningPort), 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) { func (p *PortForwarding) OverrideWith(other PortForwarding) {
p.Enabled = gosettings.OverrideWithPointer(p.Enabled, other.Enabled) p.Enabled = gosettings.OverrideWithPointer(p.Enabled, other.Enabled)
p.Provider = gosettings.OverrideWithPointer(p.Provider, other.Provider) p.Provider = gosettings.OverrideWithPointer(p.Provider, other.Provider)
p.Filepath = gosettings.OverrideWithPointer(p.Filepath, other.Filepath) p.Filepath = gosettings.OverrideWithPointer(p.Filepath, other.Filepath)
p.ListeningPort = gosettings.OverrideWithPointer(p.ListeningPort, other.ListeningPort) 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() { func (p *PortForwarding) setDefaults() {
@@ -122,5 +134,54 @@ func (p PortForwarding) toLinesNode() (node *gotree.Node) {
} }
node.Appendf("Forwarded port file path: %s", filepath) 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 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
}

View File

@@ -2,10 +2,12 @@ package settings
import ( import (
"fmt" "fmt"
"strings"
"github.com/qdm12/gluetun/internal/constants/providers" "github.com/qdm12/gluetun/internal/constants/providers"
"github.com/qdm12/gluetun/internal/constants/vpn" "github.com/qdm12/gluetun/internal/constants/vpn"
"github.com/qdm12/gosettings" "github.com/qdm12/gosettings"
"github.com/qdm12/gosettings/reader"
"github.com/qdm12/gosettings/validate" "github.com/qdm12/gosettings/validate"
"github.com/qdm12/gotree" "github.com/qdm12/gotree"
) )
@@ -13,8 +15,8 @@ import (
// Provider contains settings specific to a VPN provider. // Provider contains settings specific to a VPN provider.
type Provider struct { type Provider struct {
// Name is the VPN service provider name. // Name is the VPN service provider name.
// It cannot be nil in the internal state. // It cannot be the empty string in the internal state.
Name *string `json:"name"` Name string `json:"name"`
// ServerSelection is the settings to // ServerSelection is the settings to
// select the VPN server. // select the VPN server.
ServerSelection ServerSelection `json:"server_selection"` ServerSelection ServerSelection `json:"server_selection"`
@@ -33,23 +35,25 @@ func (p *Provider) validate(vpnType string, storage Storage) (err error) {
validNames = []string{ validNames = []string{
providers.Airvpn, providers.Airvpn,
providers.Custom, providers.Custom,
providers.Fastestvpn,
providers.Ivpn, providers.Ivpn,
providers.Mullvad, providers.Mullvad,
providers.Nordvpn, providers.Nordvpn,
providers.Protonvpn,
providers.Surfshark, providers.Surfshark,
providers.Windscribe, 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) 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 { if err != nil {
return fmt.Errorf("server selection: %w", err) return fmt.Errorf("server selection: %w", err)
} }
err = p.PortForwarding.Validate(*p.Name) err = p.PortForwarding.Validate(p.Name)
if err != nil { if err != nil {
return fmt.Errorf("port forwarding: %w", err) return fmt.Errorf("port forwarding: %w", err)
} }
@@ -59,28 +63,22 @@ func (p *Provider) validate(vpnType string, storage Storage) (err error) {
func (p *Provider) copy() (copied Provider) { func (p *Provider) copy() (copied Provider) {
return Provider{ return Provider{
Name: gosettings.CopyPointer(p.Name), Name: p.Name,
ServerSelection: p.ServerSelection.copy(), ServerSelection: p.ServerSelection.copy(),
PortForwarding: p.PortForwarding.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) { func (p *Provider) overrideWith(other Provider) {
p.Name = gosettings.OverrideWithPointer(p.Name, other.Name) p.Name = gosettings.OverrideWithComparable(p.Name, other.Name)
p.ServerSelection.overrideWith(other.ServerSelection) p.ServerSelection.overrideWith(other.ServerSelection)
p.PortForwarding.OverrideWith(other.PortForwarding) p.PortForwarding.OverrideWith(other.PortForwarding)
} }
func (p *Provider) setDefaults() { func (p *Provider) setDefaults() {
p.Name = gosettings.DefaultPointer(p.Name, providers.PrivateInternetAccess) p.Name = gosettings.DefaultComparable(p.Name, providers.PrivateInternetAccess)
p.ServerSelection.setDefaults(*p.Name)
p.PortForwarding.setDefaults() p.PortForwarding.setDefaults()
p.ServerSelection.setDefaults(p.Name, *p.PortForwarding.Enabled)
} }
func (p Provider) String() string { func (p Provider) String() string {
@@ -89,8 +87,42 @@ func (p Provider) String() string {
func (p Provider) toLinesNode() (node *gotree.Node) { func (p Provider) toLinesNode() (node *gotree.Node) {
node = gotree.New("VPN provider settings:") 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.ServerSelection.toLinesNode())
node.AppendNode(p.PortForwarding.toLinesNode()) node.AppendNode(p.PortForwarding.toLinesNode())
return node 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
}

View File

@@ -7,6 +7,7 @@ import (
"github.com/qdm12/gluetun/internal/publicip/api" "github.com/qdm12/gluetun/internal/publicip/api"
"github.com/qdm12/gosettings" "github.com/qdm12/gosettings"
"github.com/qdm12/gosettings/reader"
"github.com/qdm12/gotree" "github.com/qdm12/gotree"
) )
@@ -77,17 +78,10 @@ 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) { func (p *PublicIP) overrideWith(other PublicIP) {
p.Period = gosettings.OverrideWithPointer(p.Period, other.Period) p.Period = gosettings.OverrideWithPointer(p.Period, other.Period)
p.IPFilepath = gosettings.OverrideWithPointer(p.IPFilepath, other.IPFilepath) p.IPFilepath = gosettings.OverrideWithPointer(p.IPFilepath, other.IPFilepath)
p.API = gosettings.OverrideWithString(p.API, other.API) p.API = gosettings.OverrideWithComparable(p.API, other.API)
p.APIToken = gosettings.OverrideWithPointer(p.APIToken, other.APIToken) p.APIToken = gosettings.OverrideWithPointer(p.APIToken, other.APIToken)
} }
@@ -95,7 +89,7 @@ func (p *PublicIP) setDefaults() {
const defaultPeriod = 12 * time.Hour const defaultPeriod = 12 * time.Hour
p.Period = gosettings.DefaultPointer(p.Period, defaultPeriod) p.Period = gosettings.DefaultPointer(p.Period, defaultPeriod)
p.IPFilepath = gosettings.DefaultPointer(p.IPFilepath, "/tmp/gluetun/ip") p.IPFilepath = gosettings.DefaultPointer(p.IPFilepath, "/tmp/gluetun/ip")
p.API = gosettings.DefaultString(p.API, "ipinfo") p.API = gosettings.DefaultComparable(p.API, "ipinfo")
p.APIToken = gosettings.DefaultPointer(p.APIToken, "") p.APIToken = gosettings.DefaultPointer(p.APIToken, "")
} }
@@ -129,3 +123,16 @@ func (p PublicIP) toLinesNode() (node *gotree.Node) {
return 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
}

View File

@@ -7,6 +7,7 @@ import (
"strconv" "strconv"
"github.com/qdm12/gosettings" "github.com/qdm12/gosettings"
"github.com/qdm12/gosettings/reader"
"github.com/qdm12/gotree" "github.com/qdm12/gotree"
) )
@@ -48,13 +49,6 @@ func (c *ControlServer) copy() (copied ControlServer) {
} }
} }
// 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 // overrideWith overrides fields of the receiver
// settings object with any field set in the other // settings object with any field set in the other
// settings. // settings.
@@ -78,3 +72,12 @@ func (c ControlServer) toLinesNode() (node *gotree.Node) {
node.Appendf("Logging: %s", gosettings.BoolToYesNo(c.Log)) node.Appendf("Logging: %s", gosettings.BoolToYesNo(c.Log))
return node 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")
return nil
}

View File

@@ -12,6 +12,7 @@ import (
"github.com/qdm12/gluetun/internal/constants/vpn" "github.com/qdm12/gluetun/internal/constants/vpn"
"github.com/qdm12/gluetun/internal/models" "github.com/qdm12/gluetun/internal/models"
"github.com/qdm12/gosettings" "github.com/qdm12/gosettings"
"github.com/qdm12/gosettings/reader"
"github.com/qdm12/gosettings/validate" "github.com/qdm12/gosettings/validate"
"github.com/qdm12/gotree" "github.com/qdm12/gotree"
) )
@@ -54,14 +55,21 @@ type ServerSelection struct { //nolint:maligned
// TODO extend to providers using FreeOnly. // TODO extend to providers using FreeOnly.
PremiumOnly *bool `json:"premium_only"` PremiumOnly *bool `json:"premium_only"`
// StreamOnly is true if VPN servers not for streaming should // StreamOnly is true if VPN servers not for streaming should
// be filtered. This is used with VPNUnlimited. // be filtered. This is used with ProtonVPN and VPNUnlimited.
StreamOnly *bool `json:"stream_only"` StreamOnly *bool `json:"stream_only"`
// MultiHopOnly is true if VPN servers that are not multihop // MultiHopOnly is true if VPN servers that are not multihop
// should be filtered. This is used with Surfshark. // should be filtered. This is used with Surfshark.
MultiHopOnly *bool `json:"multi_hop_only"` MultiHopOnly *bool `json:"multi_hop_only"`
// PortForwardOnly is true if VPN servers that don't support // PortForwardOnly is true if VPN servers that don't support
// port forwarding should be filtered. This is used with PIA. // port forwarding should be filtered. This is used with PIA
// and ProtonVPN.
PortForwardOnly *bool `json:"port_forward_only"` 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 // OpenVPN contains settings to select OpenVPN servers
// and the final connection. // and the final connection.
OpenVPN OpenVPNSelection `json:"openvpn"` OpenVPN OpenVPNSelection `json:"openvpn"`
@@ -78,6 +86,8 @@ var (
ErrMultiHopOnlyNotSupported = errors.New("multi hop only filter is not supported") ErrMultiHopOnlyNotSupported = errors.New("multi hop only filter is not supported")
ErrPortForwardOnlyNotSupported = errors.New("port forwarding 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") 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, func (ss *ServerSelection) validate(vpnServiceProvider string,
@@ -101,60 +111,19 @@ func (ss *ServerSelection) validate(vpnServiceProvider string,
*ss = surfsharkRetroRegion(*ss) *ss = surfsharkRetroRegion(*ss)
} }
err = validateServerFilters(*ss, filterChoices) err = validateServerFilters(*ss, filterChoices, vpnServiceProvider)
if err != nil { if err != nil {
return fmt.Errorf("for VPN service provider %s: %w", vpnServiceProvider, err) return fmt.Errorf("for VPN service provider %s: %w", vpnServiceProvider, err)
} }
if *ss.OwnedOnly && err = validateSubscriptionTierFilters(*ss, vpnServiceProvider)
vpnServiceProvider != providers.Mullvad { if err != nil {
return fmt.Errorf("%w: for VPN service provider %s", return fmt.Errorf("for VPN service provider %s: %w", vpnServiceProvider, err)
ErrOwnedOnlyNotSupported, vpnServiceProvider)
} }
if *ss.FreeOnly && err = validateFeatureFilters(*ss, vpnServiceProvider)
!helpers.IsOneOf(vpnServiceProvider, if err != nil {
providers.Protonvpn, return fmt.Errorf("for VPN service provider %s: %w", vpnServiceProvider, err)
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 { if ss.VPN == vpn.OpenVPN {
@@ -195,7 +164,8 @@ func getLocationFilterChoices(vpnServiceProvider string,
// validateServerFilters validates filters against the choices given as arguments. // validateServerFilters validates filters against the choices given as arguments.
// Set an argument to nil to pass the check for a particular filter. // Set an argument to nil to pass the check for a particular filter.
func validateServerFilters(settings ServerSelection, filterChoices models.FilterChoices) (err error) { func validateServerFilters(settings ServerSelection, filterChoices models.FilterChoices,
vpnServiceProvider string) (err error) {
err = validate.AreAllOneOfCaseInsensitive(settings.Countries, filterChoices.Countries) err = validate.AreAllOneOfCaseInsensitive(settings.Countries, filterChoices.Countries)
if err != nil { if err != nil {
return fmt.Errorf("%w: %w", ErrCountryNotValid, err) return fmt.Errorf("%w: %w", ErrCountryNotValid, err)
@@ -221,6 +191,12 @@ func validateServerFilters(settings ServerSelection, filterChoices models.Filter
return fmt.Errorf("%w: %w", ErrHostnameNotValid, err) return fmt.Errorf("%w: %w", ErrHostnameNotValid, err)
} }
if vpnServiceProvider == providers.Custom && len(settings.Names) == 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
}
err = validate.AreAllOneOfCaseInsensitive(settings.Names, filterChoices.Names) err = validate.AreAllOneOfCaseInsensitive(settings.Names, filterChoices.Names)
if err != nil { if err != nil {
return fmt.Errorf("%w: %w", ErrNameNotValid, err) return fmt.Errorf("%w: %w", ErrNameNotValid, err)
@@ -234,6 +210,42 @@ func validateServerFilters(settings ServerSelection, filterChoices models.Filter
return nil 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 *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) { func (ss *ServerSelection) copy() (copied ServerSelection) {
return ServerSelection{ return ServerSelection{
VPN: ss.VPN, VPN: ss.VPN,
@@ -250,6 +262,8 @@ func (ss *ServerSelection) copy() (copied ServerSelection) {
FreeOnly: gosettings.CopyPointer(ss.FreeOnly), FreeOnly: gosettings.CopyPointer(ss.FreeOnly),
PremiumOnly: gosettings.CopyPointer(ss.PremiumOnly), PremiumOnly: gosettings.CopyPointer(ss.PremiumOnly),
StreamOnly: gosettings.CopyPointer(ss.StreamOnly), StreamOnly: gosettings.CopyPointer(ss.StreamOnly),
SecureCoreOnly: gosettings.CopyPointer(ss.SecureCoreOnly),
TorOnly: gosettings.CopyPointer(ss.TorOnly),
PortForwardOnly: gosettings.CopyPointer(ss.PortForwardOnly), PortForwardOnly: gosettings.CopyPointer(ss.PortForwardOnly),
MultiHopOnly: gosettings.CopyPointer(ss.MultiHopOnly), MultiHopOnly: gosettings.CopyPointer(ss.MultiHopOnly),
OpenVPN: ss.OpenVPN.copy(), OpenVPN: ss.OpenVPN.copy(),
@@ -257,30 +271,8 @@ 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) { func (ss *ServerSelection) overrideWith(other ServerSelection) {
ss.VPN = gosettings.OverrideWithString(ss.VPN, other.VPN) ss.VPN = gosettings.OverrideWithComparable(ss.VPN, other.VPN)
ss.TargetIP = gosettings.OverrideWithValidator(ss.TargetIP, other.TargetIP) ss.TargetIP = gosettings.OverrideWithValidator(ss.TargetIP, other.TargetIP)
ss.Countries = gosettings.OverrideWithSlice(ss.Countries, other.Countries) ss.Countries = gosettings.OverrideWithSlice(ss.Countries, other.Countries)
ss.Categories = gosettings.OverrideWithSlice(ss.Categories, other.Categories) ss.Categories = gosettings.OverrideWithSlice(ss.Categories, other.Categories)
@@ -294,21 +286,30 @@ func (ss *ServerSelection) overrideWith(other ServerSelection) {
ss.FreeOnly = gosettings.OverrideWithPointer(ss.FreeOnly, other.FreeOnly) ss.FreeOnly = gosettings.OverrideWithPointer(ss.FreeOnly, other.FreeOnly)
ss.PremiumOnly = gosettings.OverrideWithPointer(ss.PremiumOnly, other.PremiumOnly) ss.PremiumOnly = gosettings.OverrideWithPointer(ss.PremiumOnly, other.PremiumOnly)
ss.StreamOnly = gosettings.OverrideWithPointer(ss.StreamOnly, other.StreamOnly) 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.MultiHopOnly = gosettings.OverrideWithPointer(ss.MultiHopOnly, other.MultiHopOnly)
ss.PortForwardOnly = gosettings.OverrideWithPointer(ss.PortForwardOnly, other.PortForwardOnly) ss.PortForwardOnly = gosettings.OverrideWithPointer(ss.PortForwardOnly, other.PortForwardOnly)
ss.OpenVPN.overrideWith(other.OpenVPN) ss.OpenVPN.overrideWith(other.OpenVPN)
ss.Wireguard.overrideWith(other.Wireguard) ss.Wireguard.overrideWith(other.Wireguard)
} }
func (ss *ServerSelection) setDefaults(vpnProvider string) { func (ss *ServerSelection) setDefaults(vpnProvider string, portForwardingEnabled bool) {
ss.VPN = gosettings.DefaultString(ss.VPN, vpn.OpenVPN) ss.VPN = gosettings.DefaultComparable(ss.VPN, vpn.OpenVPN)
ss.TargetIP = gosettings.DefaultValidator(ss.TargetIP, netip.IPv4Unspecified()) ss.TargetIP = gosettings.DefaultValidator(ss.TargetIP, netip.IPv4Unspecified())
ss.OwnedOnly = gosettings.DefaultPointer(ss.OwnedOnly, false) ss.OwnedOnly = gosettings.DefaultPointer(ss.OwnedOnly, false)
ss.FreeOnly = gosettings.DefaultPointer(ss.FreeOnly, false) ss.FreeOnly = gosettings.DefaultPointer(ss.FreeOnly, false)
ss.PremiumOnly = gosettings.DefaultPointer(ss.PremiumOnly, false) ss.PremiumOnly = gosettings.DefaultPointer(ss.PremiumOnly, false)
ss.StreamOnly = gosettings.DefaultPointer(ss.StreamOnly, 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) ss.MultiHopOnly = gosettings.DefaultPointer(ss.MultiHopOnly, false)
ss.PortForwardOnly = gosettings.DefaultPointer(ss.PortForwardOnly, false) defaultPortForwardOnly := false
if portForwardingEnabled && helpers.IsOneOf(vpnProvider,
providers.PrivateInternetAccess, providers.Protonvpn) {
defaultPortForwardOnly = true
}
ss.PortForwardOnly = gosettings.DefaultPointer(ss.PortForwardOnly, defaultPortForwardOnly)
ss.OpenVPN.setDefaults(vpnProvider) ss.OpenVPN.setDefaults(vpnProvider)
ss.Wireguard.setDefaults() ss.Wireguard.setDefaults()
} }
@@ -375,10 +376,22 @@ func (ss ServerSelection) toLinesNode() (node *gotree.Node) {
node.Appendf("Stream only servers: yes") 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 { if *ss.MultiHopOnly {
node.Appendf("Multi-hop only servers: yes") node.Appendf("Multi-hop only servers: yes")
} }
if *ss.PortForwardOnly {
node.Appendf("Port forwarding only servers: yes")
}
if ss.VPN == vpn.OpenVPN { if ss.VPN == vpn.OpenVPN {
node.AppendNode(ss.OpenVPN.toLinesNode()) node.AppendNode(ss.OpenVPN.toLinesNode())
} else { } else {
@@ -391,6 +404,95 @@ func (ss ServerSelection) toLinesNode() (node *gotree.Node) {
// WithDefaults is a shorthand using setDefaults. // WithDefaults is a shorthand using setDefaults.
// It's used in unit tests in other packages. // It's used in unit tests in other packages.
func (ss ServerSelection) WithDefaults(provider string) ServerSelection { func (ss ServerSelection) WithDefaults(provider string) ServerSelection {
ss.setDefaults(provider) const portForwardingEnabled = false
ss.setDefaults(provider, portForwardingEnabled)
return ss 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
}

View File

@@ -2,12 +2,14 @@ package settings
import ( import (
"fmt" "fmt"
"net/netip"
"github.com/qdm12/gluetun/internal/configuration/settings/helpers" "github.com/qdm12/gluetun/internal/configuration/settings/helpers"
"github.com/qdm12/gluetun/internal/constants/providers" "github.com/qdm12/gluetun/internal/constants/providers"
"github.com/qdm12/gluetun/internal/constants/vpn" "github.com/qdm12/gluetun/internal/constants/vpn"
"github.com/qdm12/gluetun/internal/models" "github.com/qdm12/gluetun/internal/models"
"github.com/qdm12/gluetun/internal/pprof" "github.com/qdm12/gluetun/internal/pprof"
"github.com/qdm12/gosettings/reader"
"github.com/qdm12/gotree" "github.com/qdm12/gotree"
) )
@@ -81,22 +83,6 @@ 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, func (s *Settings) OverrideWith(other Settings,
storage Storage, ipv6Supported bool) (err error) { storage Storage, ipv6Supported bool) (err error) {
patchedSettings := s.copy() patchedSettings := s.copy()
@@ -133,7 +119,7 @@ func (s *Settings) SetDefaults() {
s.System.setDefaults() s.System.setDefaults()
s.Version.setDefaults() s.Version.setDefaults()
s.VPN.setDefaults() s.VPN.setDefaults()
s.Updater.SetDefaults(*s.VPN.Provider.Name) s.Updater.SetDefaults(s.VPN.Provider.Name)
s.Pprof.SetDefaults() s.Pprof.SetDefaults()
} }
@@ -162,16 +148,16 @@ func (s Settings) toLinesNode() (node *gotree.Node) {
} }
func (s Settings) Warnings() (warnings []string) { 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 "+ warnings = append(warnings, "HideMyAss dropped support for Linux OpenVPN "+
" so this will likely not work anymore. See https://github.com/qdm12/gluetun/issues/1498.") " 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 { s.VPN.Type == vpn.OpenVPN {
warnings = append(warnings, "OpenVPN 2.5 uses OpenSSL 3 "+ warnings = append(warnings, "OpenVPN 2.5 and 2.6 use OpenSSL 3 "+
"which prohibits the usage of weak security in today's standards. "+ "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 "+ "of Gluetun's control so the only workaround is to allow such weaknesses "+
`using the OpenVPN option tls-cipher "DEFAULT:@SECLEVEL=0". `+ `using the OpenVPN option tls-cipher "DEFAULT:@SECLEVEL=0". `+
"You might want to reach to your provider so they upgrade their certificates. "+ "You might want to reach to your provider so they upgrade their certificates. "+
@@ -179,5 +165,41 @@ func (s Settings) Warnings() (warnings []string) {
"by creating an issue, attaching the new certificate and we will update Gluetun.") "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 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
}

View File

@@ -30,7 +30,7 @@ func Test_Settings_String(t *testing.T) {
| | ├── Protocol: UDP | | ├── Protocol: UDP
| | └── Private Internet Access encryption preset: strong | | └── Private Internet Access encryption preset: strong
| └── OpenVPN settings: | └── OpenVPN settings:
| ├── OpenVPN version: 2.5 | ├── OpenVPN version: 2.6
| ├── User: [not set] | ├── User: [not set]
| ├── Password: [not set] | ├── Password: [not set]
| ├── Private Internet Access encryption preset: strong | ├── Private Internet Access encryption preset: strong

View File

@@ -1,7 +1,10 @@
package settings package settings
import ( import (
"fmt"
"github.com/qdm12/gosettings" "github.com/qdm12/gosettings"
"github.com/qdm12/gosettings/reader"
"github.com/qdm12/gotree" "github.com/qdm12/gotree"
"github.com/qdm12/ss-server/pkg/tcpudp" "github.com/qdm12/ss-server/pkg/tcpudp"
) )
@@ -26,13 +29,6 @@ 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 // overrideWith overrides fields of the receiver
// settings object with any field set in the other // settings object with any field set in the other
// settings. // settings.
@@ -59,10 +55,44 @@ func (s Shadowsocks) toLinesNode() (node *gotree.Node) {
} }
// TODO have ToLinesNode in qdm12/ss-server // TODO have ToLinesNode in qdm12/ss-server
node.Appendf("Listening address: %s", *s.Address) node.Appendf("Listening address: %s", *s.Settings.Address)
node.Appendf("Cipher: %s", s.CipherName) node.Appendf("Cipher: %s", s.Settings.CipherName)
node.Appendf("Password: %s", gosettings.ObfuscateKey(*s.Password)) node.Appendf("Password: %s", gosettings.ObfuscateKey(*s.Settings.Password))
node.Appendf("Log addresses: %s", gosettings.BoolToYesNo(s.LogAddresses)) node.Appendf("Log addresses: %s", gosettings.BoolToYesNo(s.Settings.LogAddresses))
return node 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
}

View File

@@ -2,6 +2,7 @@ package settings
import ( import (
"github.com/qdm12/gosettings" "github.com/qdm12/gosettings"
"github.com/qdm12/gosettings/reader"
"github.com/qdm12/gotree" "github.com/qdm12/gotree"
) )
@@ -25,16 +26,10 @@ 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) { func (s *System) overrideWith(other System) {
s.PUID = gosettings.OverrideWithPointer(s.PUID, other.PUID) s.PUID = gosettings.OverrideWithPointer(s.PUID, other.PUID)
s.PGID = gosettings.OverrideWithPointer(s.PGID, other.PGID) s.PGID = gosettings.OverrideWithPointer(s.PGID, other.PGID)
s.Timezone = gosettings.OverrideWithString(s.Timezone, other.Timezone) s.Timezone = gosettings.OverrideWithComparable(s.Timezone, other.Timezone)
} }
func (s *System) setDefaults() { func (s *System) setDefaults() {
@@ -59,3 +54,18 @@ func (s System) toLinesNode() (node *gotree.Node) {
return 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
}

View File

@@ -8,6 +8,7 @@ import (
"github.com/qdm12/dns/pkg/provider" "github.com/qdm12/dns/pkg/provider"
"github.com/qdm12/dns/pkg/unbound" "github.com/qdm12/dns/pkg/unbound"
"github.com/qdm12/gosettings" "github.com/qdm12/gosettings"
"github.com/qdm12/gosettings/reader"
"github.com/qdm12/gotree" "github.com/qdm12/gotree"
) )
@@ -49,7 +50,7 @@ func (u *Unbound) setDefaults() {
} }
} }
u.Username = gosettings.DefaultString(u.Username, "root") u.Username = gosettings.DefaultComparable(u.Username, "root")
} }
var ( var (
@@ -105,17 +106,6 @@ 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) { func (u *Unbound) overrideWith(other Unbound) {
u.Providers = gosettings.OverrideWithSlice(u.Providers, other.Providers) u.Providers = gosettings.OverrideWithSlice(u.Providers, other.Providers)
u.Caching = gosettings.OverrideWithPointer(u.Caching, other.Caching) u.Caching = gosettings.OverrideWithPointer(u.Caching, other.Caching)
@@ -123,7 +113,7 @@ func (u *Unbound) overrideWith(other Unbound) {
u.VerbosityLevel = gosettings.OverrideWithPointer(u.VerbosityLevel, other.VerbosityLevel) u.VerbosityLevel = gosettings.OverrideWithPointer(u.VerbosityLevel, other.VerbosityLevel)
u.VerbosityDetailsLevel = gosettings.OverrideWithPointer(u.VerbosityDetailsLevel, other.VerbosityDetailsLevel) u.VerbosityDetailsLevel = gosettings.OverrideWithPointer(u.VerbosityDetailsLevel, other.VerbosityDetailsLevel)
u.ValidationLogLevel = gosettings.OverrideWithPointer(u.ValidationLogLevel, other.ValidationLogLevel) u.ValidationLogLevel = gosettings.OverrideWithPointer(u.ValidationLogLevel, other.ValidationLogLevel)
u.Username = gosettings.OverrideWithString(u.Username, other.Username) u.Username = gosettings.OverrideWithComparable(u.Username, other.Username)
u.Allowed = gosettings.OverrideWithSlice(u.Allowed, other.Allowed) u.Allowed = gosettings.OverrideWithSlice(u.Allowed, other.Allowed)
} }
@@ -200,3 +190,34 @@ func (u Unbound) toLinesNode() (node *gotree.Node) {
return 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
}

View File

@@ -7,6 +7,7 @@ import (
"github.com/qdm12/gluetun/internal/constants/providers" "github.com/qdm12/gluetun/internal/constants/providers"
"github.com/qdm12/gosettings" "github.com/qdm12/gosettings"
"github.com/qdm12/gosettings/reader"
"github.com/qdm12/gosettings/validate" "github.com/qdm12/gosettings/validate"
"github.com/qdm12/gotree" "github.com/qdm12/gotree"
) )
@@ -64,28 +65,19 @@ 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 // overrideWith overrides fields of the receiver
// settings object with any field set in the other // settings object with any field set in the other
// settings. // settings.
func (u *Updater) overrideWith(other Updater) { func (u *Updater) overrideWith(other Updater) {
u.Period = gosettings.OverrideWithPointer(u.Period, other.Period) u.Period = gosettings.OverrideWithPointer(u.Period, other.Period)
u.DNSAddress = gosettings.OverrideWithString(u.DNSAddress, other.DNSAddress) u.DNSAddress = gosettings.OverrideWithComparable(u.DNSAddress, other.DNSAddress)
u.MinRatio = gosettings.OverrideWithNumber(u.MinRatio, other.MinRatio) u.MinRatio = gosettings.OverrideWithComparable(u.MinRatio, other.MinRatio)
u.Providers = gosettings.OverrideWithSlice(u.Providers, other.Providers) u.Providers = gosettings.OverrideWithSlice(u.Providers, other.Providers)
} }
func (u *Updater) SetDefaults(vpnProvider string) { func (u *Updater) SetDefaults(vpnProvider string) {
u.Period = gosettings.DefaultPointer(u.Period, 0) u.Period = gosettings.DefaultPointer(u.Period, 0)
u.DNSAddress = gosettings.DefaultString(u.DNSAddress, "1.1.1.1:53") u.DNSAddress = gosettings.DefaultComparable(u.DNSAddress, "1.1.1.1:53")
if u.MinRatio == 0 { if u.MinRatio == 0 {
const defaultMinRatio = 0.8 const defaultMinRatio = 0.8
@@ -114,3 +106,33 @@ func (u Updater) toLinesNode() (node *gotree.Node) {
return 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
}

View File

@@ -2,6 +2,7 @@ package settings
import ( import (
"github.com/qdm12/gosettings" "github.com/qdm12/gosettings"
"github.com/qdm12/gosettings/reader"
"github.com/qdm12/gotree" "github.com/qdm12/gotree"
) )
@@ -23,12 +24,6 @@ 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 // overrideWith overrides fields of the receiver
// settings object with any field set in the other // settings object with any field set in the other
// settings. // settings.
@@ -51,3 +46,12 @@ func (v Version) toLinesNode() (node *gotree.Node) {
return 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
}

View File

@@ -5,6 +5,7 @@ import (
"github.com/qdm12/gluetun/internal/constants/vpn" "github.com/qdm12/gluetun/internal/constants/vpn"
"github.com/qdm12/gosettings" "github.com/qdm12/gosettings"
"github.com/qdm12/gosettings/reader"
"github.com/qdm12/gosettings/validate" "github.com/qdm12/gosettings/validate"
"github.com/qdm12/gotree" "github.com/qdm12/gotree"
) )
@@ -33,12 +34,12 @@ func (v *VPN) Validate(storage Storage, ipv6Supported bool) (err error) {
} }
if v.Type == vpn.OpenVPN { if v.Type == vpn.OpenVPN {
err := v.OpenVPN.validate(*v.Provider.Name) err := v.OpenVPN.validate(v.Provider.Name)
if err != nil { if err != nil {
return fmt.Errorf("OpenVPN settings: %w", err) return fmt.Errorf("OpenVPN settings: %w", err)
} }
} else { } else {
err := v.Wireguard.validate(*v.Provider.Name, ipv6Supported) err := v.Wireguard.validate(v.Provider.Name, ipv6Supported)
if err != nil { if err != nil {
return fmt.Errorf("Wireguard settings: %w", err) return fmt.Errorf("Wireguard settings: %w", err)
} }
@@ -56,25 +57,18 @@ 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) { func (v *VPN) OverrideWith(other VPN) {
v.Type = gosettings.OverrideWithString(v.Type, other.Type) v.Type = gosettings.OverrideWithComparable(v.Type, other.Type)
v.Provider.overrideWith(other.Provider) v.Provider.overrideWith(other.Provider)
v.OpenVPN.overrideWith(other.OpenVPN) v.OpenVPN.overrideWith(other.OpenVPN)
v.Wireguard.overrideWith(other.Wireguard) v.Wireguard.overrideWith(other.Wireguard)
} }
func (v *VPN) setDefaults() { func (v *VPN) setDefaults() {
v.Type = gosettings.DefaultString(v.Type, vpn.OpenVPN) v.Type = gosettings.DefaultComparable(v.Type, vpn.OpenVPN)
v.Provider.setDefaults() v.Provider.setDefaults()
v.OpenVPN.setDefaults(*v.Provider.Name) v.OpenVPN.setDefaults(v.Provider.Name)
v.Wireguard.setDefaults(*v.Provider.Name) v.Wireguard.setDefaults(v.Provider.Name)
} }
func (v VPN) String() string { func (v VPN) String() string {
@@ -94,3 +88,24 @@ func (v VPN) toLinesNode() (node *gotree.Node) {
return 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
}

View File

@@ -4,10 +4,13 @@ import (
"fmt" "fmt"
"net/netip" "net/netip"
"regexp" "regexp"
"strings"
"time"
"github.com/qdm12/gluetun/internal/configuration/settings/helpers" "github.com/qdm12/gluetun/internal/configuration/settings/helpers"
"github.com/qdm12/gluetun/internal/constants/providers" "github.com/qdm12/gluetun/internal/constants/providers"
"github.com/qdm12/gosettings" "github.com/qdm12/gosettings"
"github.com/qdm12/gosettings/reader"
"github.com/qdm12/gosettings/validate" "github.com/qdm12/gosettings/validate"
"github.com/qdm12/gotree" "github.com/qdm12/gotree"
"golang.zx2c4.com/wireguard/wgctrl/wgtypes" "golang.zx2c4.com/wireguard/wgctrl/wgtypes"
@@ -32,7 +35,8 @@ type Wireguard struct {
// Interface is the name of the Wireguard interface // Interface is the name of the Wireguard interface
// to create. It cannot be the empty string in the // to create. It cannot be the empty string in the
// internal state. // internal state.
Interface string `json:"interface"` Interface string `json:"interface"`
PersistentKeepaliveInterval *time.Duration `json:"persistent_keep_alive_interval"`
// Maximum Transmission Unit (MTU) of the Wireguard interface. // Maximum Transmission Unit (MTU) of the Wireguard interface.
// It cannot be zero in the internal state, and defaults to // It cannot be zero in the internal state, and defaults to
// 1400. Note it is not the wireguard-go MTU default of 1420 // 1400. Note it is not the wireguard-go MTU default of 1420
@@ -54,9 +58,11 @@ func (w Wireguard) validate(vpnProvider string, ipv6Supported bool) (err error)
if !helpers.IsOneOf(vpnProvider, if !helpers.IsOneOf(vpnProvider,
providers.Airvpn, providers.Airvpn,
providers.Custom, providers.Custom,
providers.Fastestvpn,
providers.Ivpn, providers.Ivpn,
providers.Mullvad, providers.Mullvad,
providers.Nordvpn, providers.Nordvpn,
providers.Protonvpn,
providers.Surfshark, providers.Surfshark,
providers.Windscribe, providers.Windscribe,
) { ) {
@@ -121,6 +127,11 @@ func (w Wireguard) validate(vpnProvider string, ipv6Supported bool) (err error)
} }
} }
if *w.PersistentKeepaliveInterval < 0 {
return fmt.Errorf("%w: %s", ErrWireguardKeepAliveNegative,
*w.PersistentKeepaliveInterval)
}
// Validate interface // Validate interface
if !regexpInterfaceName.MatchString(w.Interface) { if !regexpInterfaceName.MatchString(w.Interface) {
return fmt.Errorf("%w: '%s' does not match regex '%s'", return fmt.Errorf("%w: '%s' does not match regex '%s'",
@@ -137,53 +148,52 @@ func (w Wireguard) validate(vpnProvider string, ipv6Supported bool) (err error)
func (w *Wireguard) copy() (copied Wireguard) { func (w *Wireguard) copy() (copied Wireguard) {
return Wireguard{ return Wireguard{
PrivateKey: gosettings.CopyPointer(w.PrivateKey), PrivateKey: gosettings.CopyPointer(w.PrivateKey),
PreSharedKey: gosettings.CopyPointer(w.PreSharedKey), PreSharedKey: gosettings.CopyPointer(w.PreSharedKey),
Addresses: gosettings.CopySlice(w.Addresses), Addresses: gosettings.CopySlice(w.Addresses),
AllowedIPs: gosettings.CopySlice(w.AllowedIPs), AllowedIPs: gosettings.CopySlice(w.AllowedIPs),
Interface: w.Interface, PersistentKeepaliveInterval: gosettings.CopyPointer(w.PersistentKeepaliveInterval),
MTU: w.MTU, Interface: w.Interface,
Implementation: w.Implementation, 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) { func (w *Wireguard) overrideWith(other Wireguard) {
w.PrivateKey = gosettings.OverrideWithPointer(w.PrivateKey, other.PrivateKey) w.PrivateKey = gosettings.OverrideWithPointer(w.PrivateKey, other.PrivateKey)
w.PreSharedKey = gosettings.OverrideWithPointer(w.PreSharedKey, other.PreSharedKey) w.PreSharedKey = gosettings.OverrideWithPointer(w.PreSharedKey, other.PreSharedKey)
w.Addresses = gosettings.OverrideWithSlice(w.Addresses, other.Addresses) w.Addresses = gosettings.OverrideWithSlice(w.Addresses, other.Addresses)
w.AllowedIPs = gosettings.OverrideWithSlice(w.AllowedIPs, other.AllowedIPs) w.AllowedIPs = gosettings.OverrideWithSlice(w.AllowedIPs, other.AllowedIPs)
w.Interface = gosettings.OverrideWithString(w.Interface, other.Interface) w.PersistentKeepaliveInterval = gosettings.OverrideWithPointer(w.PersistentKeepaliveInterval,
w.MTU = gosettings.OverrideWithNumber(w.MTU, other.MTU) other.PersistentKeepaliveInterval)
w.Implementation = gosettings.OverrideWithString(w.Implementation, other.Implementation) w.Interface = gosettings.OverrideWithComparable(w.Interface, other.Interface)
w.MTU = gosettings.OverrideWithComparable(w.MTU, other.MTU)
w.Implementation = gosettings.OverrideWithComparable(w.Implementation, other.Implementation)
} }
func (w *Wireguard) setDefaults(vpnProvider string) { func (w *Wireguard) setDefaults(vpnProvider string) {
w.PrivateKey = gosettings.DefaultPointer(w.PrivateKey, "") w.PrivateKey = gosettings.DefaultPointer(w.PrivateKey, "")
w.PreSharedKey = gosettings.DefaultPointer(w.PreSharedKey, "") w.PreSharedKey = gosettings.DefaultPointer(w.PreSharedKey, "")
if vpnProvider == providers.Nordvpn { switch vpnProvider {
case providers.Nordvpn:
defaultNordVPNAddress := netip.AddrFrom4([4]byte{10, 5, 0, 2}) defaultNordVPNAddress := netip.AddrFrom4([4]byte{10, 5, 0, 2})
defaultNordVPNPrefix := netip.PrefixFrom(defaultNordVPNAddress, defaultNordVPNAddress.BitLen()) defaultNordVPNPrefix := netip.PrefixFrom(defaultNordVPNAddress, defaultNordVPNAddress.BitLen())
w.Addresses = gosettings.DefaultSlice(w.Addresses, []netip.Prefix{defaultNordVPNPrefix}) 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{ defaultAllowedIPs := []netip.Prefix{
netip.PrefixFrom(netip.IPv4Unspecified(), 0), netip.PrefixFrom(netip.IPv4Unspecified(), 0),
netip.PrefixFrom(netip.IPv6Unspecified(), 0), netip.PrefixFrom(netip.IPv6Unspecified(), 0),
} }
w.AllowedIPs = gosettings.DefaultSlice(w.AllowedIPs, defaultAllowedIPs) w.AllowedIPs = gosettings.DefaultSlice(w.AllowedIPs, defaultAllowedIPs)
w.Interface = gosettings.DefaultString(w.Interface, "wg0") w.PersistentKeepaliveInterval = gosettings.DefaultPointer(w.PersistentKeepaliveInterval, 0)
w.Interface = gosettings.DefaultComparable(w.Interface, "wg0")
const defaultMTU = 1400 const defaultMTU = 1400
w.MTU = gosettings.DefaultNumber(w.MTU, defaultMTU) w.MTU = gosettings.DefaultComparable(w.MTU, defaultMTU)
w.Implementation = gosettings.DefaultString(w.Implementation, "auto") w.Implementation = gosettings.DefaultComparable(w.Implementation, "auto")
} }
func (w Wireguard) String() string { func (w Wireguard) String() string {
@@ -213,6 +223,10 @@ func (w Wireguard) toLinesNode() (node *gotree.Node) {
allowedIPsNode.Appendf(allowedIP.String()) 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 := node.Appendf("Network interface: %s", w.Interface)
interfaceNode.Appendf("MTU: %d", w.MTU) interfaceNode.Appendf("MTU: %d", w.MTU)
@@ -222,3 +236,44 @@ func (w Wireguard) toLinesNode() (node *gotree.Node) {
return 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
}

View File

@@ -6,6 +6,7 @@ import (
"github.com/qdm12/gluetun/internal/constants/providers" "github.com/qdm12/gluetun/internal/constants/providers"
"github.com/qdm12/gosettings" "github.com/qdm12/gosettings"
"github.com/qdm12/gosettings/reader"
"github.com/qdm12/gosettings/validate" "github.com/qdm12/gosettings/validate"
"github.com/qdm12/gotree" "github.com/qdm12/gotree"
"golang.zx2c4.com/wireguard/wgctrl/wgtypes" "golang.zx2c4.com/wireguard/wgctrl/wgtypes"
@@ -37,8 +38,9 @@ type WireguardSelection struct {
func (w WireguardSelection) validate(vpnProvider string) (err error) { func (w WireguardSelection) validate(vpnProvider string) (err error) {
// Validate EndpointIP // Validate EndpointIP
switch vpnProvider { switch vpnProvider {
case providers.Airvpn, providers.Ivpn, providers.Mullvad, case providers.Airvpn, providers.Fastestvpn, providers.Ivpn,
providers.Nordvpn, providers.Surfshark, providers.Windscribe: providers.Mullvad, providers.Nordvpn, providers.Protonvpn,
providers.Surfshark, providers.Windscribe:
// endpoint IP addresses are baked in // endpoint IP addresses are baked in
case providers.Custom: case providers.Custom:
if !w.EndpointIP.IsValid() || w.EndpointIP.IsUnspecified() { if !w.EndpointIP.IsValid() || w.EndpointIP.IsUnspecified() {
@@ -55,7 +57,8 @@ func (w WireguardSelection) validate(vpnProvider string) (err error) {
return fmt.Errorf("%w", ErrWireguardEndpointPortNotSet) return fmt.Errorf("%w", ErrWireguardEndpointPortNotSet)
} }
// EndpointPort cannot be set // EndpointPort cannot be set
case providers.Surfshark, providers.Nordvpn: case providers.Fastestvpn, providers.Nordvpn,
providers.Protonvpn, providers.Surfshark:
if *w.EndpointPort != 0 { if *w.EndpointPort != 0 {
return fmt.Errorf("%w", ErrWireguardEndpointPortSet) return fmt.Errorf("%w", ErrWireguardEndpointPortSet)
} }
@@ -88,7 +91,7 @@ func (w WireguardSelection) validate(vpnProvider string) (err error) {
// Validate PublicKey // Validate PublicKey
switch vpnProvider { switch vpnProvider {
case providers.Ivpn, providers.Mullvad, case providers.Fastestvpn, providers.Ivpn, providers.Mullvad,
providers.Surfshark, providers.Windscribe: providers.Surfshark, providers.Windscribe:
// public keys are baked in // public keys are baked in
case providers.Custom: case providers.Custom:
@@ -116,16 +119,10 @@ 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) { func (w *WireguardSelection) overrideWith(other WireguardSelection) {
w.EndpointIP = gosettings.OverrideWithValidator(w.EndpointIP, other.EndpointIP) w.EndpointIP = gosettings.OverrideWithValidator(w.EndpointIP, other.EndpointIP)
w.EndpointPort = gosettings.OverrideWithPointer(w.EndpointPort, other.EndpointPort) w.EndpointPort = gosettings.OverrideWithPointer(w.EndpointPort, other.EndpointPort)
w.PublicKey = gosettings.OverrideWithString(w.PublicKey, other.PublicKey) w.PublicKey = gosettings.OverrideWithComparable(w.PublicKey, other.PublicKey)
} }
func (w *WireguardSelection) setDefaults() { func (w *WireguardSelection) setDefaults() {
@@ -154,3 +151,18 @@ func (w WireguardSelection) toLinesNode() (node *gotree.Node) {
return 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
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -1,33 +0,0 @@
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 ""
}

View File

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

View File

@@ -1,53 +0,0 @@
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)
}
}

View File

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

View File

@@ -1,56 +0,0 @@
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)
}
}

View File

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

View File

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

View File

@@ -1,50 +0,0 @@
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)
}

View File

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

View File

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

View File

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

View File

@@ -1,92 +0,0 @@
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")
)

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -9,47 +9,45 @@ import (
"github.com/qdm12/gluetun/internal/openvpn/extract" "github.com/qdm12/gluetun/internal/openvpn/extract"
) )
// ReadFromFile reads the content of the file as a string. // ReadFromFile reads the content of the file as a string,
// It returns a nil string pointer if the file does not exist. // and returns if the file was present or not with isSet.
func ReadFromFile(filepath string) (s *string, err error) { func ReadFromFile(filepath string) (content string, isSet bool, err error) {
file, err := os.Open(filepath) file, err := os.Open(filepath)
if err != nil { if err != nil {
if os.IsNotExist(err) { if os.IsNotExist(err) {
return nil, nil //nolint:nilnil return "", false, nil
} }
return nil, err return "", false, fmt.Errorf("opening file: %w", err)
} }
b, err := io.ReadAll(file) b, err := io.ReadAll(file)
if err != nil { if err != nil {
_ = file.Close() _ = file.Close()
return nil, err return "", false, fmt.Errorf("reading file: %w", err)
} }
if err := file.Close(); err != nil { if err := file.Close(); err != nil {
return nil, err return "", false, fmt.Errorf("closing file: %w", err)
} }
content := string(b) content = string(b)
content = strings.TrimSuffix(content, "\r\n") content = strings.TrimSuffix(content, "\r\n")
content = strings.TrimSuffix(content, "\n") content = strings.TrimSuffix(content, "\n")
return &content, nil return content, true, nil
} }
func readPEMFile(filepath string) (base64Ptr *string, err error) { func ReadPEMFile(filepath string) (base64Str string, isSet bool, err error) {
pemData, err := ReadFromFile(filepath) pemData, isSet, err := ReadFromFile(filepath)
if err != nil { if err != nil {
return nil, fmt.Errorf("reading file: %w", err) return "", false, fmt.Errorf("reading file: %w", err)
} else if !isSet {
return "", false, nil
} }
if pemData == nil { base64Str, err = extract.PEM([]byte(pemData))
return nil, nil //nolint:nilnil
}
base64Data, err := extract.PEM([]byte(*pemData))
if err != nil { if err != nil {
return nil, fmt.Errorf("extracting base64 encoded data from PEM content: %w", err) return "", false, fmt.Errorf("extracting base64 encoded data from PEM content: %w", err)
} }
return &base64Data, nil return base64Str, true, nil
} }

View File

@@ -1,3 +0,0 @@
package files
func ptrTo[T any](x T) *T { return &x }

View File

@@ -0,0 +1,5 @@
package files
type Warner interface {
Warnf(format string, a ...interface{})
}

View File

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

View File

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

View File

@@ -1,32 +1,101 @@
package files package files
import ( import (
"github.com/qdm12/gluetun/internal/configuration/settings" "os"
"path/filepath"
"strings"
) )
type Source struct { type Source struct {
wireguardConfigPath string rootDirectory string
environ map[string]string
warner Warner
cached struct {
wireguardLoaded bool
wireguardConf WireguardConfig
}
} }
func New() *Source { func New(warner Warner) (source *Source) {
const wireguardConfigPath = "/gluetun/wireguard/wg0.conf" 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]
}
return &Source{ return &Source{
wireguardConfigPath: wireguardConfigPath, rootDirectory: "/gluetun",
environ: environ,
warner: warner,
} }
} }
func (s *Source) String() string { return "files" } func (s *Source) String() string { return "files" }
func (s *Source) Read() (settings settings.Settings, err error) { func (s *Source) Get(key string) (value string, isSet bool) {
settings.VPN, err = s.readVPN() if key == "" {
if err != nil { return "", false
return settings, err }
// 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)
} }
settings.System, err = s.readSystem() // Special file handling
if err != nil { switch key {
return settings, err // 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)
} }
return settings, nil value, isSet, err := ReadFromFile(path)
if err != nil {
s.warner.Warnf("skipping %s: reading file: %s", path, 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
}
}
func strPtrToStringIsSet(ptr *string) (s string, isSet bool) {
if ptr == nil {
return "", false
}
return *ptr, true
} }

View File

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

View File

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

View File

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

View File

@@ -1,124 +1,115 @@
package files package files
import ( import (
"errors"
"fmt" "fmt"
"net/netip" "os"
"path/filepath"
"regexp" "regexp"
"strings" "strings"
"github.com/qdm12/gluetun/internal/configuration/settings"
"golang.zx2c4.com/wireguard/wgctrl/wgtypes"
"gopkg.in/ini.v1" "gopkg.in/ini.v1"
) )
func (s *Source) readWireguard() (wireguard settings.Wireguard, err error) { func (s *Source) lazyLoadWireguardConf() WireguardConfig {
fileStringPtr, err := ReadFromFile(s.wireguardConfigPath) 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"))
if err != nil { if err != nil {
return wireguard, fmt.Errorf("reading file: %w", err) s.warner.Warnf("skipping Wireguard config: %s", err)
} }
return s.cached.wireguardConf
}
if fileStringPtr == nil { type WireguardConfig struct {
return wireguard, nil PrivateKey *string
} PreSharedKey *string
Addresses *string
rawData := []byte(*fileStringPtr) PublicKey *string
return ParseWireguardConf(rawData) EndpointIP *string
EndpointPort *string
} }
var ( var (
regexINISectionNotExist = regexp.MustCompile(`^section ".+" does not exist$`) regexINISectionNotExist = regexp.MustCompile(`^section ".+" does not exist$`)
regexINIKeyNotExist = regexp.MustCompile(`key ".*" not exists$`)
) )
func ParseWireguardConf(rawData []byte) (wireguard settings.Wireguard, err error) { func ParseWireguardConf(path string) (config WireguardConfig, err error) {
iniFile, err := ini.Load(rawData) iniFile, err := ini.InsensitiveLoad(path)
if err != nil { if err != nil {
return wireguard, fmt.Errorf("loading ini from reader: %w", err) if errors.Is(err, os.ErrNotExist) {
return WireguardConfig{}, nil
}
return WireguardConfig{}, fmt.Errorf("loading ini from reader: %w", err)
} }
interfaceSection, err := iniFile.GetSection("Interface") interfaceSection, err := iniFile.GetSection("Interface")
if err == nil { if err == nil {
err = parseWireguardInterfaceSection(interfaceSection, &wireguard) config.PrivateKey, config.Addresses = parseWireguardInterfaceSection(interfaceSection)
if err != nil {
return wireguard, fmt.Errorf("parsing interface section: %w", err)
}
} else if !regexINISectionNotExist.MatchString(err.Error()) { } else if !regexINISectionNotExist.MatchString(err.Error()) {
// can never happen // can never happen
return wireguard, fmt.Errorf("getting interface section: %w", err) return WireguardConfig{}, fmt.Errorf("getting interface section: %w", err)
} }
peerSection, err := iniFile.GetSection("Peer") peerSection, err := iniFile.GetSection("Peer")
if err == nil { if err == nil {
wireguard.PreSharedKey, err = parseINIWireguardKey(peerSection, "PresharedKey") config.PreSharedKey, config.PublicKey, config.EndpointIP,
if err != nil { config.EndpointPort = parseWireguardPeerSection(peerSection)
return wireguard, fmt.Errorf("parsing peer section: %w", err)
}
} else if !regexINISectionNotExist.MatchString(err.Error()) { } else if !regexINISectionNotExist.MatchString(err.Error()) {
// can never happen // can never happen
return wireguard, fmt.Errorf("getting peer section: %w", err) return WireguardConfig{}, fmt.Errorf("getting peer section: %w", err)
} }
return wireguard, nil return config, nil
} }
func parseWireguardInterfaceSection(interfaceSection *ini.Section, func parseWireguardInterfaceSection(interfaceSection *ini.Section) (
wireguard *settings.Wireguard) (err error) { privateKey, addresses *string) {
wireguard.PrivateKey, err = parseINIWireguardKey(interfaceSection, "PrivateKey") privateKey = getINIKeyFromSection(interfaceSection, "PrivateKey")
if err != nil { addresses = getINIKeyFromSection(interfaceSection, "Address")
return err // error is already wrapped correctly return privateKey, addresses
}
wireguard.Addresses, err = parseINIWireguardAddress(interfaceSection)
if err != nil {
return err // error is already wrapped correctly
}
return nil
} }
func parseINIWireguardKey(section *ini.Section, keyName string) ( var (
key *string, err error) { ErrEndpointHostNotIP = errors.New("endpoint host is not an IP")
iniKey, err := section.GetKey(keyName) )
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:], ":")
}
}
return preSharedKey, publicKey, endpointIP, endpointPort
}
var (
regexINIKeyNotExist = regexp.MustCompile(`key ".*" not exists$`)
)
func getINIKeyFromSection(section *ini.Section, key string) (value *string) {
iniKey, err := section.GetKey(key)
if err != nil { if err != nil {
if regexINIKeyNotExist.MatchString(err.Error()) { if regexINIKeyNotExist.MatchString(err.Error()) {
return nil, nil //nolint:nilnil return nil
} }
// can never happen // can never happen
return nil, fmt.Errorf("getting %s key: %w", keyName, err) panic(fmt.Sprintf("getting key %q: %s", key, err))
} }
value = new(string)
key = new(string) *value = iniKey.String()
*key = iniKey.String() return value
_, 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
} }

View File

@@ -1,48 +1,42 @@
package files package files
import ( import (
"net/netip"
"os" "os"
"path/filepath" "path/filepath"
"testing" "testing"
"github.com/qdm12/gluetun/internal/configuration/settings"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
"gopkg.in/ini.v1" "gopkg.in/ini.v1"
) )
func Test_Source_readWireguard(t *testing.T) { func ptrTo[T any](value T) *T { return &value }
func Test_Source_ParseWireguardConf(t *testing.T) {
t.Parallel() t.Parallel()
t.Run("fail reading from file", func(t *testing.T) { t.Run("fail reading from file", func(t *testing.T) {
t.Parallel() t.Parallel()
dirPath := t.TempDir() dirPath := t.TempDir()
source := &Source{ wireguard, err := ParseWireguardConf(dirPath)
wireguardConfigPath: dirPath, assert.Equal(t, WireguardConfig{}, wireguard)
}
wireguard, err := source.readWireguard()
assert.Equal(t, settings.Wireguard{}, wireguard)
assert.Error(t, err) assert.Error(t, err)
assert.Regexp(t, `reading file: read .+: is a directory`, err.Error()) assert.Regexp(t, `loading ini from reader: BOM: read .+: is a directory`, err.Error())
}) })
t.Run("no file", func(t *testing.T) { t.Run("no file", func(t *testing.T) {
t.Parallel() t.Parallel()
noFile := filepath.Join(t.TempDir(), "doesnotexist") noFile := filepath.Join(t.TempDir(), "doesnotexist")
source := &Source{ wireguard, err := ParseWireguardConf(noFile)
wireguardConfigPath: noFile, assert.Equal(t, WireguardConfig{}, wireguard)
}
wireguard, err := source.readWireguard()
assert.Equal(t, settings.Wireguard{}, wireguard)
assert.NoError(t, err) assert.NoError(t, err)
}) })
testCases := map[string]struct { testCases := map[string]struct {
fileContent string fileContent string
wireguard settings.Wireguard wireguard WireguardConfig
errMessage string errMessage string
}{ }{
"ini load error": { "ini load error": {
@@ -50,14 +44,14 @@ func Test_Source_readWireguard(t *testing.T) {
errMessage: "loading ini from reader: key-value delimiter not found: invalid", errMessage: "loading ini from reader: key-value delimiter not found: invalid",
}, },
"empty file": {}, "empty file": {},
"interface section parsing error": { "interface_section_missing": {
fileContent: ` fileContent: `
[Interface] [Peer]
PrivateKey = x PresharedKey = YJ680VN+dGrdsWNjSFqZ6vvwuiNhbq502ZL3G7Q3o3g=
`, `,
errMessage: "parsing interface section: parsing PrivateKey: " + wireguard: WireguardConfig{
"x: wgtypes: failed to parse base64-encoded key: " + PreSharedKey: ptrTo("YJ680VN+dGrdsWNjSFqZ6vvwuiNhbq502ZL3G7Q3o3g="),
"illegal base64 data at input byte 0", },
}, },
"success": { "success": {
fileContent: ` fileContent: `
@@ -69,12 +63,10 @@ DNS = 193.138.218.74
[Peer] [Peer]
PresharedKey = YJ680VN+dGrdsWNjSFqZ6vvwuiNhbq502ZL3G7Q3o3g= PresharedKey = YJ680VN+dGrdsWNjSFqZ6vvwuiNhbq502ZL3G7Q3o3g=
`, `,
wireguard: settings.Wireguard{ wireguard: WireguardConfig{
PrivateKey: ptrTo("QOlCgyA/Sn/c/+YNTIEohrjm8IZV+OZ2AUFIoX20sk8="), PrivateKey: ptrTo("QOlCgyA/Sn/c/+YNTIEohrjm8IZV+OZ2AUFIoX20sk8="),
PreSharedKey: ptrTo("YJ680VN+dGrdsWNjSFqZ6vvwuiNhbq502ZL3G7Q3o3g="), PreSharedKey: ptrTo("YJ680VN+dGrdsWNjSFqZ6vvwuiNhbq502ZL3G7Q3o3g="),
Addresses: []netip.Prefix{ Addresses: ptrTo("10.38.22.35/32"),
netip.PrefixFrom(netip.AddrFrom4([4]byte{10, 38, 22, 35}), 32),
},
}, },
}, },
} }
@@ -88,11 +80,7 @@ PresharedKey = YJ680VN+dGrdsWNjSFqZ6vvwuiNhbq502ZL3G7Q3o3g=
err := os.WriteFile(configFile, []byte(testCase.fileContent), 0600) err := os.WriteFile(configFile, []byte(testCase.fileContent), 0600)
require.NoError(t, err) require.NoError(t, err)
source := &Source{ wireguard, err := ParseWireguardConf(configFile)
wireguardConfigPath: configFile,
}
wireguard, err := source.readWireguard()
assert.Equal(t, testCase.wireguard, wireguard) assert.Equal(t, testCase.wireguard, wireguard)
if testCase.errMessage != "" { if testCase.errMessage != "" {
@@ -109,34 +97,26 @@ func Test_parseWireguardInterfaceSection(t *testing.T) {
testCases := map[string]struct { testCases := map[string]struct {
iniData string iniData string
wireguard settings.Wireguard privateKey *string
errMessage string addresses *string
}{ }{
"private key error": { "no_fields": {
iniData: `[Interface] iniData: `[Interface]`,
PrivateKey = x`,
errMessage: "parsing PrivateKey: x: " +
"wgtypes: failed to parse base64-encoded key: " +
"illegal base64 data at input byte 0",
}, },
"address error": { "only_private_key": {
iniData: `[Interface] iniData: `[Interface]
Address = x PrivateKey = x
`, `,
errMessage: "parsing address: netip.ParsePrefix(\"x/32\"): ParseAddr(\"x\"): unable to parse IP", privateKey: ptrTo("x"),
}, },
"success": { "all_fields": {
iniData: ` iniData: `
[Interface] [Interface]
PrivateKey = QOlCgyA/Sn/c/+YNTIEohrjm8IZV+OZ2AUFIoX20sk8= PrivateKey = QOlCgyA/Sn/c/+YNTIEohrjm8IZV+OZ2AUFIoX20sk8=
Address = 10.38.22.35/32 Address = 10.38.22.35/32
`, `,
wireguard: settings.Wireguard{ privateKey: ptrTo("QOlCgyA/Sn/c/+YNTIEohrjm8IZV+OZ2AUFIoX20sk8="),
PrivateKey: ptrTo("QOlCgyA/Sn/c/+YNTIEohrjm8IZV+OZ2AUFIoX20sk8="), addresses: ptrTo("10.38.22.35/32"),
Addresses: []netip.Prefix{
netip.PrefixFrom(netip.AddrFrom4([4]byte{10, 38, 22, 35}), 32),
},
},
}, },
} }
@@ -150,109 +130,74 @@ Address = 10.38.22.35/32
iniSection, err := iniFile.GetSection("Interface") iniSection, err := iniFile.GetSection("Interface")
require.NoError(t, err) require.NoError(t, err)
var wireguard settings.Wireguard privateKey, addresses := parseWireguardInterfaceSection(iniSection)
err = parseWireguardInterfaceSection(iniSection, &wireguard)
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.privateKey, privateKey)
assert.Equal(t, testCase.addresses, addresses) 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)
if testCase.errMessage != "" { if testCase.errMessage != "" {
assert.EqualError(t, err, testCase.errMessage) assert.EqualError(t, err, testCase.errMessage)
} else { } else {

View File

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

View File

@@ -1,181 +0,0 @@
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)
}
})
}
}

View File

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

View File

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

View File

@@ -1,58 +1,8 @@
package secrets package secrets
import ( func strPtrToStringIsSet(ptr *string) (s string, isSet bool) {
"fmt" if ptr == nil {
"net/netip" return "", false
"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 files.ReadFromFile(path) return *ptr, true
}
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
} }

View File

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

View File

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

View File

@@ -0,0 +1,5 @@
package secrets
type Warner interface {
Warnf(format string, a ...interface{})
}

View File

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

View File

@@ -1,46 +1,106 @@
package secrets package secrets
import ( import (
"fmt"
"os" "os"
"path/filepath"
"strings"
"github.com/qdm12/gluetun/internal/configuration/settings" "github.com/qdm12/gluetun/internal/configuration/sources/files"
"github.com/qdm12/gosettings/sources/env"
) )
type Source struct { type Source struct {
env env.Env rootDirectory string
environ map[string]string
warner Warner
cached struct {
wireguardLoaded bool
wireguardConf files.WireguardConfig
}
} }
func New() *Source { func New(warner Warner) (source *Source) {
handleDeprecatedKey := (func(deprecatedKey, newKey string))(nil) 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]
}
return &Source{ return &Source{
env: *env.New(os.Environ(), handleDeprecatedKey), rootDirectory: rootDirectory,
environ: environ,
warner: warner,
} }
} }
func (s *Source) String() string { return "secret files" } func (s *Source) String() string { return "secret files" }
func (s *Source) Read() (settings settings.Settings, err error) { func (s *Source) Get(key string) (value string, isSet bool) {
settings.VPN, err = s.readVPN() if key == "" {
if err != nil { return "", false
return settings, err }
// 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)
} }
settings.HTTPProxy, err = s.readHTTPProxy() // Special file parsing
if err != nil { switch key {
return settings, err // 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)
} }
settings.Shadowsocks, err = s.readShadowsocks() value, isSet, err := files.ReadFromFile(path)
if err != nil { if err != nil {
return settings, err s.warner.Warnf("skipping %s: reading file: %s", path, 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.VPN.Wireguard, err = s.readWireguard()
if err != nil {
return settings, fmt.Errorf("reading Wireguard: %w", err)
}
return settings, nil
} }

View File

@@ -0,0 +1,102 @@
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)
})
}
}

View File

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

View File

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

View File

@@ -1,52 +1,27 @@
package secrets package secrets
import ( import (
"fmt" "os"
"path/filepath"
"github.com/qdm12/gluetun/internal/configuration/settings"
"github.com/qdm12/gluetun/internal/configuration/sources/files" "github.com/qdm12/gluetun/internal/configuration/sources/files"
) )
func (s *Source) readWireguard() (settings settings.Wireguard, err error) { func (s *Source) lazyLoadWireguardConf() files.WireguardConfig {
wireguardConf, err := s.readSecretFileAsStringPtr( if s.cached.wireguardLoaded {
"WIREGUARD_CONF_SECRETFILE", return s.cached.wireguardConf
"/run/secrets/wg0.conf",
)
if err != nil {
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))
} }
settings.PrivateKey, err = s.readSecretFileAsStringPtr( path := os.Getenv("WIREGUARD_CONF_SECRETFILE")
"WIREGUARD_PRIVATE_KEY_SECRETFILE", if path == "" {
"/run/secrets/wireguard_private_key", path = filepath.Join(s.rootDirectory, "wg0.conf")
)
if err != nil {
return settings, fmt.Errorf("reading private key file: %w", err)
} }
settings.PreSharedKey, err = s.readSecretFileAsStringPtr( s.cached.wireguardLoaded = true
"WIREGUARD_PRESHARED_KEY_SECRETFILE", var err error
"/run/secrets/wireguard_preshared_key", s.cached.wireguardConf, err = files.ParseWireguardConf(path)
)
if err != nil { if err != nil {
return settings, fmt.Errorf("reading preshared key file: %w", err) s.warner.Warnf("skipping Wireguard config: %s", err)
} }
return s.cached.wireguardConf
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
} }

View File

@@ -38,7 +38,7 @@ type Config struct { //nolint:maligned
func NewConfig(ctx context.Context, logger Logger, func NewConfig(ctx context.Context, logger Logger,
runner command.Runner, defaultRoutes []routing.DefaultRoute, runner command.Runner, defaultRoutes []routing.DefaultRoute,
localNetworks []routing.LocalNetwork) (config *Config, err error) { localNetworks []routing.LocalNetwork) (config *Config, err error) {
iptables, err := checkIptablesSupport(ctx, runner, "iptables", "iptables-nft") iptables, err := checkIptablesSupport(ctx, runner, "iptables", "iptables-nft", "iptables-legacy")
if err != nil { if err != nil {
return nil, err return nil, err
} }

View File

@@ -15,7 +15,7 @@ import (
// empty string path is returned. // empty string path is returned.
func findIP6tablesSupported(ctx context.Context, runner command.Runner) ( func findIP6tablesSupported(ctx context.Context, runner command.Runner) (
ip6tablesPath string, err error) { ip6tablesPath string, err error) {
ip6tablesPath, err = checkIptablesSupport(ctx, runner, "ip6tables", "ip6tables-nft") ip6tablesPath, err = checkIptablesSupport(ctx, runner, "ip6tables", "ip6tables-nft", "ip6tables-legacy")
if errors.Is(err, ErrIPTablesNotSupported) { if errors.Is(err, ErrIPTablesNotSupported) {
return "", nil return "", nil
} else if err != nil { } else if err != nil {

View File

@@ -11,7 +11,6 @@ import (
"strings" "strings"
"github.com/qdm12/gluetun/internal/models" "github.com/qdm12/gluetun/internal/models"
"github.com/qdm12/golibs/command"
) )
var ( var (
@@ -44,9 +43,9 @@ func flipRule(rule string) string {
} }
// Version obtains the version of the installed iptables. // Version obtains the version of the installed iptables.
func Version(ctx context.Context, runner command.Runner) (string, error) { func (c *Config) Version(ctx context.Context) (string, error) {
cmd := exec.CommandContext(ctx, "iptables", "--version") cmd := exec.CommandContext(ctx, c.ipTables, "--version") //nolint:gosec
output, err := runner.Run(cmd) output, err := c.runner.Run(cmd)
if err != nil { if err != nil {
return "", err return "", err
} }
@@ -197,8 +196,8 @@ func (c *Config) acceptInputToPort(ctx context.Context, intf string, port uint16
interfaceFlag = "" interfaceFlag = ""
} }
return c.runMixedIptablesInstructions(ctx, []string{ return c.runMixedIptablesInstructions(ctx, []string{
fmt.Sprintf("%s INPUT %s -p tcp --dport %d -j ACCEPT", appendOrDelete(remove), interfaceFlag, port), fmt.Sprintf("%s INPUT %s -p tcp -m tcp --dport %d -j ACCEPT", appendOrDelete(remove), interfaceFlag, port),
fmt.Sprintf("%s INPUT %s -p udp --dport %d -j ACCEPT", appendOrDelete(remove), interfaceFlag, port), fmt.Sprintf("%s INPUT %s -p udp -m udp --dport %d -j ACCEPT", appendOrDelete(remove), interfaceFlag, port),
}) })
} }
@@ -211,10 +210,14 @@ func (c *Config) redirectPort(ctx context.Context, intf string,
} }
err = c.runIptablesInstructions(ctx, []string{ err = c.runIptablesInstructions(ctx, []string{
fmt.Sprintf("-t nat %s PREROUTING %s -d 127.0.0.1 -p tcp --dport %d -j REDIRECT --to-ports %d", fmt.Sprintf("-t nat %s PREROUTING %s -p tcp --dport %d -j REDIRECT --to-ports %d",
appendOrDelete(remove), interfaceFlag, sourcePort, destinationPort), appendOrDelete(remove), interfaceFlag, sourcePort, destinationPort),
fmt.Sprintf("-t nat %s PREROUTING %s -d 127.0.0.1 -p udp --dport %d -j REDIRECT --to-ports %d", fmt.Sprintf("%s INPUT %s -p tcp -m tcp --dport %d -j ACCEPT",
appendOrDelete(remove), interfaceFlag, destinationPort),
fmt.Sprintf("-t nat %s PREROUTING %s -p udp --dport %d -j REDIRECT --to-ports %d",
appendOrDelete(remove), interfaceFlag, sourcePort, destinationPort), appendOrDelete(remove), interfaceFlag, sourcePort, destinationPort),
fmt.Sprintf("%s INPUT %s -p udp -m udp --dport %d -j ACCEPT",
appendOrDelete(remove), interfaceFlag, destinationPort),
}) })
if err != nil { if err != nil {
return fmt.Errorf("redirecting IPv4 source port %d to destination port %d on interface %s: %w", return fmt.Errorf("redirecting IPv4 source port %d to destination port %d on interface %s: %w",
@@ -222,10 +225,14 @@ func (c *Config) redirectPort(ctx context.Context, intf string,
} }
err = c.runIP6tablesInstructions(ctx, []string{ err = c.runIP6tablesInstructions(ctx, []string{
fmt.Sprintf("-t nat %s PREROUTING %s -d ::1 -p tcp --dport %d -j REDIRECT --to-ports %d", fmt.Sprintf("-t nat %s PREROUTING %s -p tcp --dport %d -j REDIRECT --to-ports %d",
appendOrDelete(remove), interfaceFlag, sourcePort, destinationPort), appendOrDelete(remove), interfaceFlag, sourcePort, destinationPort),
fmt.Sprintf("-t nat %s PREROUTING %s -d ::1 -p udp --dport %d -j REDIRECT --to-ports %d", fmt.Sprintf("%s INPUT %s -p tcp -m tcp --dport %d -j ACCEPT",
appendOrDelete(remove), interfaceFlag, destinationPort),
fmt.Sprintf("-t nat %s PREROUTING %s -p udp --dport %d -j REDIRECT --to-ports %d",
appendOrDelete(remove), interfaceFlag, sourcePort, destinationPort), appendOrDelete(remove), interfaceFlag, sourcePort, destinationPort),
fmt.Sprintf("%s INPUT %s -p udp -m udp --dport %d -j ACCEPT",
appendOrDelete(remove), interfaceFlag, destinationPort),
}) })
if err != nil { if err != nil {
return fmt.Errorf("redirecting IPv6 source port %d to destination port %d on interface %s: %w", return fmt.Errorf("redirecting IPv6 source port %d to destination port %d on interface %s: %w",
@@ -270,12 +277,18 @@ func (c *Config) runUserPostRules(ctx context.Context, filepath string, remove b
case strings.HasPrefix(line, "iptables-nft "): case strings.HasPrefix(line, "iptables-nft "):
ipv4 = true ipv4 = true
rule = strings.TrimPrefix(line, "iptables-nft ") rule = strings.TrimPrefix(line, "iptables-nft ")
case strings.HasPrefix(line, "iptables-legacy "):
ipv4 = true
rule = strings.TrimPrefix(line, "iptables-legacy ")
case strings.HasPrefix(line, "ip6tables "): case strings.HasPrefix(line, "ip6tables "):
ipv4 = false ipv4 = false
rule = strings.TrimPrefix(line, "ip6tables ") rule = strings.TrimPrefix(line, "ip6tables ")
case strings.HasPrefix(line, "ip6tables-nft "): case strings.HasPrefix(line, "ip6tables-nft "):
ipv4 = false ipv4 = false
rule = strings.TrimPrefix(line, "ip6tables-nft ") rule = strings.TrimPrefix(line, "ip6tables-nft ")
case strings.HasPrefix(line, "ip6tables-legacy "):
ipv4 = false
rule = strings.TrimPrefix(line, "ip6tables-legacy ")
default: default:
continue continue
} }

View File

@@ -11,53 +11,56 @@ import (
func (s *Server) runHealthcheckLoop(ctx context.Context, done chan<- struct{}) { func (s *Server) runHealthcheckLoop(ctx context.Context, done chan<- struct{}) {
defer close(done) defer close(done)
timeoutIndex := 0
healthcheckTimeouts := []time.Duration{
2 * time.Second,
4 * time.Second,
6 * time.Second,
8 * time.Second,
// This can be useful when the connection is under stress
// See https://github.com/qdm12/gluetun/issues/2270
10 * time.Second,
}
s.vpn.healthyTimer = time.NewTimer(s.vpn.healthyWait) s.vpn.healthyTimer = time.NewTimer(s.vpn.healthyWait)
for { for {
previousErr := s.handler.getErr() previousErr := s.handler.getErr()
const healthcheckTimeout = 3 * time.Second timeout := healthcheckTimeouts[timeoutIndex]
healthcheckCtx, healthcheckCancel := context.WithTimeout( healthcheckCtx, healthcheckCancel := context.WithTimeout(
ctx, healthcheckTimeout) ctx, timeout)
err := s.healthCheck(healthcheckCtx) err := s.healthCheck(healthcheckCtx)
healthcheckCancel() healthcheckCancel()
s.handler.setErr(err) s.handler.setErr(err)
if previousErr != nil && err == nil { switch {
case previousErr != nil && err == nil: // First success
s.logger.Info("healthy!") s.logger.Info("healthy!")
timeoutIndex = 0
s.vpn.healthyTimer.Stop() s.vpn.healthyTimer.Stop()
s.vpn.healthyWait = *s.config.VPN.Initial s.vpn.healthyWait = *s.config.VPN.Initial
} else if previousErr == nil && err != nil { case previousErr == nil && err != nil: // First failure
s.logger.Debug("unhealthy: " + err.Error()) s.logger.Debug("unhealthy: " + err.Error())
s.vpn.healthyTimer.Stop() s.vpn.healthyTimer.Stop()
s.vpn.healthyTimer = time.NewTimer(s.vpn.healthyWait) s.vpn.healthyTimer = time.NewTimer(s.vpn.healthyWait)
} case previousErr != nil && err != nil: // Nth failure
if timeoutIndex < len(healthcheckTimeouts)-1 {
if err != nil { // try again after 1 second timeoutIndex++
timer := time.NewTimer(time.Second) }
select {
case <-s.vpn.healthyTimer.C:
timeoutIndex = 0 // retry next with the smallest timeout
s.onUnhealthyVPN(ctx)
default:
}
case previousErr == nil && err == nil: // Nth success
timer := time.NewTimer(s.config.SuccessWait)
select { select {
case <-ctx.Done(): case <-ctx.Done():
if !timer.Stop() {
<-timer.C
}
return return
case <-timer.C: case <-timer.C:
case <-s.vpn.healthyTimer.C:
s.onUnhealthyVPN(ctx)
} }
continue
}
// Success, check again after the success wait duration
timer := time.NewTimer(s.config.SuccessWait)
select {
case <-ctx.Done():
if !timer.Stop() {
<-timer.C
}
return
case <-timer.C:
} }
} }
} }

View File

@@ -8,8 +8,8 @@ import (
"time" "time"
"github.com/qdm12/gosettings" "github.com/qdm12/gosettings"
"github.com/qdm12/gosettings/validate"
"github.com/qdm12/gotree" "github.com/qdm12/gotree"
"github.com/qdm12/govalid/address"
) )
type Settings struct { type Settings struct {
@@ -34,12 +34,12 @@ type Settings struct {
} }
func (s *Settings) SetDefaults() { func (s *Settings) SetDefaults() {
s.Address = gosettings.DefaultString(s.Address, ":8000") s.Address = gosettings.DefaultComparable(s.Address, ":8000")
const defaultReadTimeout = 3 * time.Second const defaultReadTimeout = 3 * time.Second
s.ReadHeaderTimeout = gosettings.DefaultNumber(s.ReadHeaderTimeout, defaultReadTimeout) s.ReadHeaderTimeout = gosettings.DefaultComparable(s.ReadHeaderTimeout, defaultReadTimeout)
s.ReadTimeout = gosettings.DefaultNumber(s.ReadTimeout, defaultReadTimeout) s.ReadTimeout = gosettings.DefaultComparable(s.ReadTimeout, defaultReadTimeout)
const defaultShutdownTimeout = 3 * time.Second const defaultShutdownTimeout = 3 * time.Second
s.ShutdownTimeout = gosettings.DefaultNumber(s.ShutdownTimeout, defaultShutdownTimeout) s.ShutdownTimeout = gosettings.DefaultComparable(s.ShutdownTimeout, defaultShutdownTimeout)
} }
func (s Settings) Copy() Settings { func (s Settings) Copy() Settings {
@@ -53,26 +53,15 @@ func (s Settings) Copy() Settings {
} }
} }
func (s *Settings) MergeWith(other Settings) {
s.Address = gosettings.MergeWithString(s.Address, other.Address)
s.Handler = gosettings.MergeWithInterface(s.Handler, other.Handler)
if s.Logger == nil {
s.Logger = other.Logger
}
s.ReadHeaderTimeout = gosettings.MergeWithNumber(s.ReadHeaderTimeout, other.ReadHeaderTimeout)
s.ReadTimeout = gosettings.MergeWithNumber(s.ReadTimeout, other.ReadTimeout)
s.ShutdownTimeout = gosettings.MergeWithNumber(s.ShutdownTimeout, other.ShutdownTimeout)
}
func (s *Settings) OverrideWith(other Settings) { func (s *Settings) OverrideWith(other Settings) {
s.Address = gosettings.OverrideWithString(s.Address, other.Address) s.Address = gosettings.OverrideWithComparable(s.Address, other.Address)
s.Handler = gosettings.OverrideWithInterface(s.Handler, other.Handler) s.Handler = gosettings.OverrideWithComparable(s.Handler, other.Handler)
if other.Logger != nil { if other.Logger != nil {
s.Logger = other.Logger s.Logger = other.Logger
} }
s.ReadHeaderTimeout = gosettings.OverrideWithNumber(s.ReadHeaderTimeout, other.ReadHeaderTimeout) s.ReadHeaderTimeout = gosettings.OverrideWithComparable(s.ReadHeaderTimeout, other.ReadHeaderTimeout)
s.ReadTimeout = gosettings.OverrideWithNumber(s.ReadTimeout, other.ReadTimeout) s.ReadTimeout = gosettings.OverrideWithComparable(s.ReadTimeout, other.ReadTimeout)
s.ShutdownTimeout = gosettings.OverrideWithNumber(s.ShutdownTimeout, other.ShutdownTimeout) s.ShutdownTimeout = gosettings.OverrideWithComparable(s.ShutdownTimeout, other.ShutdownTimeout)
} }
var ( var (
@@ -84,8 +73,7 @@ var (
) )
func (s Settings) Validate() (err error) { func (s Settings) Validate() (err error) {
uid := os.Getuid() err = validate.ListeningAddress(s.Address, os.Getuid())
err = address.Validate(s.Address, address.OptionListening(uid))
if err != nil { if err != nil {
return err return err
} }

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