Compare commits

..

165 Commits

Author SHA1 Message Date
dependabot[bot]
44bc60b00d Chore(deps): Bump docker/build-push-action from 4.0.0 to 4.1.1 (#1684) 2023-06-28 14:28:59 +02:00
dependabot[bot]
6f0be57860 Chore(deps): Bump golang.org/x/text from 0.9.0 to 0.10.0 (#1681) 2023-06-28 14:28:44 +02:00
Quentin McGaw
d3d8484b8e hotfix(env): case sensitivity for OPENVPN_CUSTOM_CONFIG 2023-06-28 12:27:13 +00:00
Quentin McGaw
515ae8efb3 hotfix(nordvpn): update url 2023-06-18 11:00:36 +00:00
Quentin McGaw
83826e1253 hotfix(settings): fix godot lint error 2023-06-12 13:51:50 +00:00
Quentin McGaw
4292a500ae fix(wireguard): delete existing Wireguard link before adding it 2023-06-10 20:23:21 +00:00
Quentin McGaw
4a0f9c36ba hotfix(nordvpn): accept countries in SERVER_REGIONS 2023-06-10 16:29:30 +00:00
Quentin McGaw
ea1991496e hotfix(routing): remove debug prints 2023-06-08 22:44:08 +00:00
Quentin McGaw
4675572328 hotfix(routing): change main table from 0 to 254 2023-06-08 20:03:07 +00:00
Quentin McGaw
412921fc1f hotfix(routing): ignore non-main table for routes
- When searching for default routes
- When searching for local networks
2023-06-08 19:50:42 +00:00
Quentin McGaw
1c905d0e6f chore(labels): add problem category labels
- Config problem
- Routing
- IPv6
- Port forwarding
2023-06-08 10:04:09 +00:00
Quentin McGaw
2ec9293324 feat(wireguard): MTU defaults to 1400 instead of 1420 2023-06-08 09:50:21 +00:00
Quentin McGaw
9b39a301a8 chore(routing): remove unused VPNDestinationIP 2023-06-08 09:17:27 +00:00
Quentin McGaw
cade2b99bf chore(routing): unexport IPIsPrivate as ipIsPrivate 2023-06-08 09:14:17 +00:00
Quentin McGaw
40cdb4f662 fix(netlink): RouteList list routes from all tables
- Do not filter by link anymore
- IPv6 detection simplified
2023-06-08 09:12:46 +00:00
Quentin McGaw
c58d6d4de2 chore(lint): upgrade to v1.53.2 and add linters
- gosmopolitan
- mirror
- tagalign
- zerologlint
2023-06-08 07:43:30 +00:00
Quentin McGaw
0da2b6ad0b chore(lint): add musttag linter and fix lint errors
Breaking change: JSON fields changed in the server API
2023-06-08 07:43:26 +00:00
Quentin McGaw
37f0e5c73b chore(lint): add linters dupword, paralleltest and gocheckcompilerdirectives 2023-06-08 07:40:37 +00:00
Quentin McGaw
a9cd7be3f9 chore(sources/env): bump gosettings to v0.3.0-rc13
- Use `RetroKeys` option with env.* method calls
- Use `CSV*` typed methods
- Inject `handleDeprecatedKey` function
2023-06-08 07:40:37 +00:00
Julio Gutierrez
07459ee854 feat(nordvpn): new API endpoint and wireguard support (#1380)
Co-authored-by: Quentin McGaw <quentin.mcgaw@gmail.com>
2023-06-08 09:39:07 +02:00
Quentin McGaw
943943e8d1 fix(settings): MergeWithSlice for both elements nil 2023-06-01 10:00:44 +00:00
Quentin McGaw
5927ee9dec chore(ci): trigger for PR to other branches 2023-06-01 09:09:01 +00:00
Quentin McGaw
3b136e02db chore(secrets): add test for readSecretFileAsStringPtr 2023-06-01 09:07:25 +00:00
Quentin McGaw
482447c151 chore(env): bump qdm12/gosettings to v0.3.0-rc11 2023-06-01 09:07:22 +00:00
Quentin McGaw
5d8fbf8006 fix(sources/secrets): do not lowercase env secret file paths 2023-06-01 08:20:13 +00:00
Quentin McGaw
2ab80771d9 feat(shadowsocks): bump from v0.4.0 to v0.5.0-rc1 2023-05-31 14:31:56 +00:00
Quentin McGaw
7399c00508 chore(sources/env): bump gosettings to v0.3.0-rc9 2023-05-31 14:31:56 +00:00
Leeroy Ding
2d2f657851 docs(readme): fix Alpine version from 3.17 to 3.18 (#1636) 2023-05-31 16:27:10 +02:00
dependabot[bot]
0e21fdc9de Chore(deps): Bump github.com/stretchr/testify from 1.8.3 to 1.8.4 (#1633) 2023-05-31 16:24:49 +02:00
Quentin McGaw
b87b2109b1 chore(settings): use gosettings/sources/env functions 2023-05-30 13:02:10 +00:00
Quentin McGaw
2c30984a10 hotfix(env): read some settings with case sensitivity 2023-05-30 12:46:10 +00:00
Quentin McGaw
47593928f9 fix(settings): use qdm12/gosettings env.Get 2023-05-29 20:43:06 +00:00
Quentin McGaw
b961284845 feat(dev): specify vscode recommendations 2023-05-29 16:42:00 +00:00
Quentin McGaw
b5d230d47a chore(dev): set build tag as linux for cross development 2023-05-29 16:40:10 +00:00
Quentin McGaw
c2972f7bf6 chore(dev): update devcontainer definitions 2023-05-29 15:57:09 +00:00
Quentin McGaw
aed235f52d chore(httpproxy): add Test_returnRedirect to prevent error wrap of ErrUseLastResponse 2023-05-29 09:44:49 +00:00
Quentin McGaw
bfe5e4380f fix(httpproxy): redirect from http to https 2023-05-29 09:39:48 +00:00
Quentin McGaw
eca182a32f chore(tun): not linux or not darwin tagged files 2023-05-29 09:36:29 +00:00
Quentin McGaw
caabaf918e feat(dev): support development on darwin (OSX)
- Netlink linux tagged files
- Netlink linux || darwin tagged files
- Create non-implemented files for NOT linux
- Create non-implemented files for NOT linux and NOT darwin
- Specify wireguard netlink integration test as for linux only
2023-05-29 07:26:59 +00:00
Quentin McGaw
d6924597dd chore(netlink): separate linux only and OS independent code
- Move `Addr` and its `String` method to `types.go`
- Move `IsWireguardSupported` to `wireguard.go` to have `family.go` OS independant
- Remove dependency on vishvananda/netlink in `ipv6.go`
- Move `Link` to `types.go`
- Move `Route` to `types.go`
- Move `Rule` and its `String` method to `types.go`
2023-05-29 06:56:55 +00:00
Quentin McGaw
c26476a2fd chore(netlink): remove unused link fields 2023-05-29 06:56:52 +00:00
Quentin McGaw
5be0d0bbba feat(wireguard): debug logs log obfuscated keys 2023-05-29 06:45:12 +00:00
Quentin McGaw
38ddcfa756 chore(netlink): define own types with minimal fields
- Allow to swap `github.com/vishvananda/netlink`
- Allow to add build tags for each platform
- One step closer to development on non-Linux platforms
2023-05-29 06:44:58 +00:00
Quentin McGaw
163ac48ce4 chore(wireguard): fix netlink integration test
- Broken since recent commit 9d1a0b60a2
2023-05-29 05:54:01 +00:00
Quentin McGaw
def407d610 chore(settings): use qdm12/gosettings functions
- use: FileExists, ObfuscateKey, BoolToYesNo
- remove local functions moved to gosettings
2023-05-28 10:33:36 +00:00
Quentin McGaw
22b2e2cc6e chore(deps): bump qdm12/gosettings to v0.3.0-rc4 2023-05-28 10:29:15 +00:00
Quentin McGaw
c92962e97c chore(deps): tidy Go dependencies 2023-05-28 10:26:25 +00:00
Quentin McGaw
9d1a0b60a2 fix(netlink): use AddrReplace instead of AddrAdd 2023-05-28 10:22:51 +00:00
Quentin McGaw
9cf2c9c4d2 chore(settings): remove now unused helpers/messages.go 2023-05-28 10:22:51 +00:00
Quentin McGaw
e7150ba254 chore(settings): remove unused settings helpers 2023-05-28 10:22:51 +00:00
Filippo Buletto
7ba70f19ef fix(settings): fix httpproxy.go error message (#1596) 2023-05-27 20:01:55 +02:00
dependabot[bot]
9488a9f88a Chore(deps): Bump github.com/breml/rootcerts from 0.2.10 to 0.2.11 (#1567) 2023-05-27 20:01:17 +02:00
dependabot[bot]
020196f1c3 Chore(deps): Bump github.com/stretchr/testify from 1.8.2 to 1.8.3 (#1575) 2023-05-27 20:01:08 +02:00
Quentin McGaw
7e325715c7 hotfix(settings): case insensitivity for server filters 2023-05-27 08:53:18 +00:00
Quentin McGaw
75670a80b8 chore(deps): bump gosettings and govalid 2023-05-27 08:52:41 +00:00
Quentin McGaw
a43973c093 chore(settings): use github.com/qdm12/gosettings 2023-05-25 12:08:43 +00:00
Quentin McGaw
1827a03afd fix(airvpn): allow Airvpn as Wireguard provider 2023-05-24 21:47:31 +00:00
Quentin McGaw
3100cc1e5e hotfix(routing): unmap ipv4-in-ipv6 when converting 2023-05-22 08:03:52 +00:00
Quentin McGaw
eed62fdc6d fix(routing): ip family match function
- ipv4-in-ipv6 should match ipv6
2023-05-22 06:01:52 +00:00
Quentin McGaw
d2b8dbcb10 chore(routing): remove old assigned ip debug log 2023-05-22 06:01:07 +00:00
Quentin McGaw
90d43856ef fix(routing): net.IPNet to netip.Prefix conversion 2023-05-22 06:00:24 +00:00
Quentin McGaw
86f95cb390 chore(docker): bump Alpine from 3.17 to 3.18 2023-05-21 13:25:01 +00:00
Quentin McGaw
3b807e2ca9 feat(openvpn): add support for openvpn 2.6 2023-05-21 13:23:51 +00:00
Quentin McGaw
e8f2296a0d change(openvpn): Openvpn 2.4 no longer supported 2023-05-21 13:20:02 +00:00
Lars Haalck
1dd38bc658 feat(wireguard): WIREGUARD_MTU enviromnent variable (#1571) 2023-05-21 15:11:07 +02:00
Quentin McGaw
63303bc311 fix(mullvad): add aes-256-gcm cipher 2023-05-21 12:33:27 +00:00
Julio Gutierrez
5200ee5722 chore(settings): use generics for helping functions (#1427) 2023-05-20 22:37:23 +02:00
Quentin McGaw
86ec75722a chore(wireguard): use netip.AddrPort instead of *net.UDPAddr 2023-05-20 20:06:12 +00:00
Quentin McGaw
0a29337c3b chore(all): replace net.IP with netip.Addr 2023-05-20 20:06:12 +00:00
Quentin McGaw
00ee6ff9a7 chore(wireguard): fix netlink integration tests 2023-05-20 20:06:12 +00:00
Quentin McGaw
6d0a2a968f chore(settings): remove unneeded CopyNetipPrefix 2023-05-20 20:06:12 +00:00
dependabot[bot]
4bb77ebcc5 Chore(deps): Bump golang.org/x/net from 0.9.0 to 0.10.0 (#1561) 2023-05-10 11:00:50 +02:00
dependabot[bot]
56ecfcb9f4 Chore(deps): Bump golang.org/x/sys from 0.7.0 to 0.8.0 (#1557) 2023-05-10 10:58:31 +02:00
15ky3
9a0fcbc011 fix(perfectprivacy): update cert and key (#1549)
Credits to @Thamos88 and @15ky3
2023-05-10 10:56:32 +02:00
Quentin McGaw
b6c8399c3b feat(health): HEALTH_SUCCESS_WAIT_DURATION 2023-05-07 09:35:51 +00:00
Quentin McGaw
7a88a09341 chore(healthcheck): prefer Go dialer 2023-05-06 07:14:34 +00:00
Quentin McGaw
912b31cfc6 fix(settings): clarify Wireguard provider error 2023-05-01 08:00:25 +00:00
Quentin McGaw
d21a943779 chore(all): use netip.Prefix for ip networks
- remove usage of `net.IPNet`
- remove usage of `netaddr.IPPrefix`
2023-04-27 13:42:50 +00:00
Quentin McGaw
801a7fd6fe chore(routing): simplify default routes for loop 2023-04-27 10:41:18 +00:00
Quentin McGaw
80053f6b7d feat(routing): log default route family as string 2023-04-27 10:41:03 +00:00
Quentin McGaw
e165bb6870 chore(dev): do not bind mount ~/.gitconfig 2023-04-27 10:27:40 +00:00
Quentin McGaw
67bd1171ae feat(env): rename vpn port forwarding variables
- `VPN_PORT_FORWARDING_STATUS_FILE`
- `VPN_PORT_FORWARDING`
- Deprecate PIA specific variables for VPN port forwarding
2023-04-27 10:23:55 +00:00
Quentin McGaw
4e2e46014d chore(settings): inet.af/netaddr -> net/netip 2023-04-23 11:43:50 +00:00
Quentin McGaw
1693c59e0d chore(lint): fix issues
- sources/env: remove unused `envToInt`
- fix `ireturn` error for `newCipherDESCBCBlock`
2023-04-22 11:02:53 +00:00
Quentin McGaw
9d4105ee59 chore(settings): remove unneeded pointers.go 2023-04-22 11:02:53 +00:00
Quentin McGaw
19585da3bc chore(deps): bump inet.af/netaddr 2023-04-20 23:25:41 +00:00
Quentin McGaw
51f830cfc1 chore(wireguard): bump dependencies 2023-04-20 23:24:58 +00:00
Quentin McGaw
804ea7ebd6 feat(surfshark): update servers data 2023-04-20 23:22:27 +00:00
Quentin McGaw
3294b8df60 feat(perfectprivacy): update servers data 2023-04-20 23:10:57 +00:00
Quentin McGaw
d77ec7a6cb fix(perfectprivacy): remove check for hostname in servers 2023-04-20 23:10:06 +00:00
Quentin McGaw
219d1f371c chore(all): wrap all sentinel errors
- Force to use `errors.Is` instead of `==` to compare errors
2023-04-20 23:10:06 +00:00
Quentin McGaw
fa7fd5f076 fix(pprof): settings rates can be nil 2023-04-20 23:10:02 +00:00
dependabot[bot]
d4f8eea7bf Chore(deps): Bump github.com/vishvananda/netlink from 1.1.1-0.20211129163951-9ada19101fc5 to 1.2.1-beta.2 (#1414) 2023-04-12 05:30:45 -07:00
Quentin McGaw
723d0f5e12 chore(lint): upgrade from v1.51.2 to v1.52.2 2023-04-12 09:40:00 +00:00
dependabot[bot]
20f4d8cc0b Chore(deps): Bump github.com/fatih/color from 1.14.1 to 1.15.0 (#1484) 2023-04-11 09:04:35 -07:00
dependabot[bot]
64cca69bf3 Chore(deps): Bump golang.org/x/net from 0.0.0-20220418201149-a630d4f3e7a2 to 0.9.0 (#1509) 2023-04-11 09:04:11 -07:00
Kyle Manna
fc8a2abb8f fix(routing): add policy rules for each destination local networks (#1493) 2023-04-11 09:03:07 -07:00
Quentin McGaw
16ecf48b89 fix(vpnunlimited): lower TLS security level to 0 (#1476) 2023-04-11 14:08:54 +02:00
Quentin McGaw
8fa4fd1b64 chore(labels): update labels
- remove issue category labels
- Add temporary status labels
- Add complexity labels
2023-04-03 11:58:12 +00:00
Quentin McGaw
4db6d1ecf9 chore(dev): add openssl to dev container 2023-04-03 10:41:37 +00:00
Quentin McGaw
3b86927ca7 fix(vpnsecure): upgrade Openvpn key encryption if needed (#1471) 2023-04-03 03:40:09 -07:00
Quentin McGaw
8bfa2f9b27 chore(docker): loosen pin for openssl 1.1
- Credits to @kylemanna
2023-04-03 08:21:58 +00:00
Quentin McGaw
fe2a3e4d11 chore(docker): remove no longer needed apk-tools 2023-04-03 08:20:08 +00:00
Quentin McGaw
b0451d8e50 feat(docker): install full-featured wget
- Fixes #1260, #1494
- Does not spawn openssl zombie processes
2023-04-03 08:10:48 +00:00
Quentin McGaw
a0b9044fd3 fix(hidemyass): add warning of end of life
- Fixes #1498
- Credits to @Fukitsu
2023-04-03 07:58:04 +00:00
Quentin McGaw
c7a841f4b4 chore(openvpn/extract): simplify PEM extract 2023-04-01 16:57:18 +00:00
Quentin McGaw
4ba159e483 chore(all): review error wrappings
- remove repetitive `cannot` and `failed` prefixes
- rename `unmarshaling` to `decoding`
2023-04-01 16:57:18 +00:00
dependabot[bot]
63a696d7e7 Chore(deps): Bump docker/build-push-action from 3.2.0 to 4.0.0 (#1378) 2023-04-01 08:28:34 -07:00
Quentin McGaw
d457342b46 feat(log): warn Openvpn 2.4 is to be removed 2023-04-01 15:24:42 +00:00
Quentin McGaw
c246dae2cc feat(log): log warnings about user settings
- Warn when using Openvpn 2.4 and SlickVPN
- Warn when using Openvpn 2.5 and SlickVPN
2023-04-01 15:22:32 +00:00
Quentin McGaw
0f4a2e5224 hotfix(deps): bump old openssl to 1.1.1t-r2 2023-03-31 09:34:33 +00:00
dependabot[bot]
db262050d5 Chore(deps): Bump github.com/stretchr/testify from 1.8.1 to 1.8.2 (#1483) 2023-03-30 10:54:44 -07:00
Quentin McGaw
227cdea0c8 fix(slickvpn): allow AES-256-GCM 2023-03-26 12:38:55 +02:00
Quentin McGaw
33a6f1c01b fix(slickvpn): lower TLS security level to 0 2023-03-26 12:38:55 +02:00
Quentin McGaw
f6f3c110f0 fix(slickvpn): all servers support TCP and UDP 2023-03-26 12:38:55 +02:00
Quentin McGaw
27a3f2c846 fix(slickvpn): precise default TCP port as 443 2023-03-26 12:38:55 +02:00
dependabot[bot]
62169baeea Chore(deps): Bump golang.org/x/text from 0.5.0 to 0.8.0 (#1436) 2023-03-26 12:36:29 +02:00
dependabot[bot]
4b18636a91 Chore(deps): Bump golang.org/x/sys from 0.3.0 to 0.6.0 (#1438) 2023-03-25 22:11:01 +01:00
Quentin McGaw
51432ca05f hotfix(health): remove previous err debug line 2023-03-25 17:34:56 +00:00
colereynolds
b5ebdcd040 docs(readme): add servers updater environment variables (#1393) 2023-03-25 16:14:07 +01:00
Quentin McGaw
416c1ee113 chore(deps): tidy dependencies 2023-03-25 15:09:27 +00:00
Quentin McGaw
fe97e28461 fix(health): log link to Wiki on VPN restart 2023-03-25 15:09:13 +00:00
Quentin McGaw
cbd8711a21 feat(airvpn): update servers data 2023-03-25 14:50:49 +00:00
stevenl4
7578e52ed5 fix(ipvanish): updater zip file url (#1449) 2023-03-25 15:36:44 +01:00
Quentin McGaw
0df68f76d5 fix(airvpn): remove commas from city names 2023-03-25 12:55:21 +00:00
Quentin McGaw
9a528c42f8 chore(settings): precise base64 DER for some OpenVPN fields 2023-03-25 12:10:01 +00:00
Quentin McGaw
5607916af6 hotfix: bump old openssl to 1.1.1t-r1 2023-03-23 16:36:14 +00:00
Quentin McGaw
4ad7a2a444 feat(mullvad): update servers data 2023-03-23 15:23:53 +00:00
Quentin McGaw
ab5dbdca97 feat(pia): update servers data 2023-03-01 13:00:18 +00:00
Quentin McGaw
a97fcda283 fix(version): add name in version check error 2023-02-27 20:16:55 +00:00
Quentin McGaw
e955adc1e1 hotfix: install older openssh for openvpn 2.4 2023-02-27 05:21:42 +00:00
Quentin McGaw
ac5141b411 Chore(deps): Bump github.com/breml/rootcerts from 0.2.8 to 0.2.10 2023-02-26 18:14:59 +00:00
dependabot[bot]
f8c189e48a Chore(deps): Bump github.com/fatih/color from 1.13.0 to 1.14.1 (#1369) 2023-02-26 11:14:15 -08:00
Quentin McGaw
2f2a904c64 feat(mullvad): update servers data 2023-02-26 15:16:22 +00:00
Quentin McGaw
9261dca8ab chore(lint): bump from v1.49.0 to v1.51.2 2023-02-26 15:15:34 +00:00
Quentin McGaw
7b5d5c3884 feat(alpine): bump from 3.16 to 3.17 2023-02-26 15:15:34 +00:00
Quentin McGaw
7c80d80904 chore(build): upgrade Go from 1.19 to 1.20 2023-02-26 15:15:30 +00:00
Quentin McGaw
ea40b84ec0 fix(settings): print outbound subnets correctly 2022-12-31 17:46:55 +00:00
Quentin McGaw
4e6ef649c4 fix(airvpn): remove commas from API locations 2022-12-31 17:30:31 +00:00
Quentin McGaw
dd40f1d2e6 chore(devcontainer): same ssh bind mount for all platforms 2022-12-31 17:30:31 +00:00
dependabot[bot]
490693bb26 Chore(deps): Bump golang.org/x/text from 0.4.0 to 0.5.0 (#1275) 2022-12-15 04:58:43 -05:00
Quentin McGaw
c8d33ca5f3 fix(surfshark): update location data
- Add 2 new 'HK' servers
- Remove 3 servers no longer resolving
2022-12-15 09:29:48 +00:00
Quentin McGaw
e6df026332 feat(surfshark): update servers data 2022-12-15 09:29:09 +00:00
Quentin McGaw
7a30343053 docs(readme): document alternative ghcr image name 2022-12-15 09:02:37 +00:00
ksurl
fc02ae9c13 feat(docker): ghcr.io/qdm12/gluetun image (#1231) 2022-12-14 16:04:41 -05:00
Quentin McGaw
f70f0aca9c fix(settings): validate Wireguard addresses depending on IPv6 support 2022-12-14 11:52:03 +00:00
Quentin McGaw
16acd1b162 chore(netlink): log ipv6 support at debug level 2022-12-14 11:52:03 +00:00
Quentin McGaw
2e3eb1fd7b fix(wireguard): ignore IPv6 addresses if IPv6 is not supported 2022-12-14 11:52:03 +00:00
Quentin McGaw
a4cf17f81e fix(netlink): change logger level 2022-12-14 11:50:36 +00:00
Quentin McGaw
c0a301611d fix(health): set config to default in healthcheck mode 2022-12-07 10:34:24 +00:00
Quentin McGaw
cc934f5c68 hotfix(netlink): ipv6 detection for nil src/dst in routes 2022-12-02 11:39:37 +00:00
Quentin McGaw
74426f6202 feat(netlink): add debug logger 2022-12-02 11:26:52 +00:00
Quentin McGaw
03ed3cb1c8 feat(wireguard): WIREGUARD_IMPLEMENTATION variable
- Can be `auto` (default), `userspace` or `kernelspace`
2022-12-02 11:16:27 +00:00
Quentin McGaw
1b1335835b fix(netlink): inspect each route for IPv6 support 2022-12-01 12:18:46 +00:00
Quentin McGaw
5070dbcf7f feat(fastestvpn): update servers data 2022-11-30 19:21:56 +00:00
rsquarev
90b9d85742 fix(fastesvpn): updater zip file url (#1264) 2022-11-30 14:19:14 -05:00
Quentin McGaw
7a3b9941aa fix(exit): exit with 1 on runtime error 2022-11-18 09:46:31 +00:00
Quentin McGaw
698095f0a0 fix(pprof): do not run if disabled 2022-11-15 12:45:47 +00:00
Quentin McGaw
5a06d8e155 fix(firewall): iptables detection with permission denied 2022-11-15 12:34:25 +00:00
Quentin McGaw
7421dcb45f feat(openvpn): explain ip route error in logs
- `RTNETLINK answers: File exists` changed to warning with explanation
- `Linux route add command failed:` changed to warning with explanation
2022-11-11 09:48:55 +00:00
Quentin McGaw
554a6cdb92 feat(healthcheck): add FAQ url on unhealthy log 2022-11-11 09:43:07 +00:00
Quentin McGaw
5aa39be973 fix(firewall): remove previously allowed input ports 2022-11-11 09:19:03 +00:00
Quentin McGaw
192a7a56a3 fix(httpproxy): lower shutdown wait from 2s to 100ms 2022-10-31 11:21:25 +00:00
Quentin McGaw
1d1657e9be fix(pia): remove username+password from login bad http status code 2022-10-31 11:00:43 +00:00
Quentin McGaw
49b7301295 feat(mullvad): update servers data 2022-10-31 10:44:36 +00:00
314 changed files with 116662 additions and 52098 deletions

View File

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

View File

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

View File

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

View File

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

43
.github/labels.yml vendored
View File

@@ -1,18 +1,13 @@
- name: "Bug :bug:" # Temporary status
color: "b60205" - name: "🗯️ Waiting for feedback"
description: "" color: "aadefa"
- name: "Feature request :bulb:"
color: "0e8a16"
description: ""
- name: "Help wanted :pray:"
color: "4caf50"
description: ""
- name: "Documentation :memo:"
color: "c5def5"
description: ""
- name: "Needs more info :thinking:"
color: "795548"
description: "" description: ""
- name: "🔴 Blocked"
color: "ff3f14"
description: "Blocked by another issue or pull request"
- name: "🔒 After next release"
color: "e8f274"
description: "Will be done after the next release"
# Priority # Priority
- name: "🚨 Urgent" - name: "🚨 Urgent"
@@ -22,6 +17,14 @@
color: "4285f4" color: "4285f4"
description: "" description: ""
# Complexity
- name: "☣️ Hard to do"
color: "7d0008"
description: ""
- name: "🟩 Easy to do"
color: "34cf43"
description: ""
# VPN providers # VPN providers
- name: ":cloud: AirVPN" - name: ":cloud: AirVPN"
color: "cfe8d4" color: "cfe8d4"
@@ -92,6 +95,9 @@
description: "" description: ""
# Problem category # Problem category
- name: "Config problem"
color: "ffc7ea"
description: ""
- name: "Openvpn" - name: "Openvpn"
color: "ffc7ea" color: "ffc7ea"
description: "" description: ""
@@ -104,6 +110,15 @@
- name: "Firewall" - name: "Firewall"
color: "ffc7ea" color: "ffc7ea"
description: "" description: ""
- name: "Routing"
color: "ffc7ea"
description: ""
- name: "IPv6"
color: "ffc7ea"
description: ""
- name: "Port forwarding"
color: "ffc7ea"
description: ""
- name: "HTTP proxy" - name: "HTTP proxy"
color: "ffc7ea" color: "ffc7ea"
description: "" description: ""

View File

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

View File

@@ -17,8 +17,6 @@ on:
- go.mod - go.mod
- go.sum - go.sum
pull_request: pull_request:
branches:
- master
paths: paths:
- .github/workflows/ci.yml - .github/workflows/ci.yml
- cmd/** - cmd/**
@@ -93,6 +91,7 @@ jobs:
permissions: permissions:
actions: read actions: read
contents: read contents: read
packages: write
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- uses: actions/checkout@v3 - uses: actions/checkout@v3
@@ -106,6 +105,7 @@ jobs:
flavor: | flavor: |
latest=${{ github.ref == format('refs/heads/{0}', github.event.repository.default_branch) }} latest=${{ github.ref == format('refs/heads/{0}', github.event.repository.default_branch) }}
images: | images: |
ghcr.io/qdm12/gluetun
qmcgaw/gluetun qmcgaw/gluetun
qmcgaw/private-internet-access qmcgaw/private-internet-access
tags: | tags: |
@@ -123,12 +123,18 @@ jobs:
username: qmcgaw username: qmcgaw
password: ${{ secrets.DOCKERHUB_PASSWORD }} password: ${{ secrets.DOCKERHUB_PASSWORD }}
- uses: docker/login-action@v2
with:
registry: ghcr.io
username: qdm12
password: ${{ github.token }}
- name: Short commit - name: Short commit
id: shortcommit id: shortcommit
run: echo "::set-output name=value::$(git rev-parse --short HEAD)" run: echo "::set-output name=value::$(git rev-parse --short HEAD)"
- name: Build and push final image - name: Build and push final image
uses: docker/build-push-action@v3.2.0 uses: docker/build-push-action@v4.1.1
with: with:
platforms: linux/amd64,linux/386,linux/arm64,linux/arm/v6,linux/arm/v7,linux/ppc64le platforms: linux/amd64,linux/386,linux/arm64,linux/arm/v6,linux/arm/v7,linux/ppc64le
labels: ${{ steps.meta.outputs.labels }} labels: ${{ steps.meta.outputs.labels }}

View File

@@ -29,6 +29,10 @@ issues:
- text: "returns interface \\(github\\.com\\/vishvananda\\/netlink\\.Link\\)" - text: "returns interface \\(github\\.com\\/vishvananda\\/netlink\\.Link\\)"
linters: linters:
- ireturn - ireturn
- path: "internal\\/openvpn\\/pkcs8\\/descbc\\.go"
text: "newCipherDESCBCBlock returns interface \\(github\\.com\\/youmark\\/pkcs8\\.Cipher\\)"
linters:
- ireturn
linters: linters:
enable: enable:
@@ -42,6 +46,7 @@ linters:
- decorder - decorder
- dogsled - dogsled
- dupl - dupl
- dupword
- durationcheck - durationcheck
- errchkjson - errchkjson
- errname - errname
@@ -50,6 +55,7 @@ linters:
- exportloopref - exportloopref
- forcetypeassert - forcetypeassert
- gci - gci
- gocheckcompilerdirectives
- gochecknoglobals - gochecknoglobals
- gochecknoinits - gochecknoinits
- gocognit - gocognit
@@ -64,6 +70,7 @@ linters:
- gomoddirectives - gomoddirectives
- goprintffuncname - goprintffuncname
- gosec - gosec
- gosmopolitan
- grouper - grouper
- importas - importas
- interfacebloat - interfacebloat
@@ -71,7 +78,9 @@ linters:
- lll - lll
- maintidx - maintidx
- makezero - makezero
- mirror
- misspell - misspell
- musttag
- nakedret - nakedret
- nestif - nestif
- nilerr - nilerr
@@ -79,6 +88,7 @@ linters:
- noctx - noctx
- nolintlint - nolintlint
- nosprintfhostport - nosprintfhostport
- paralleltest
- prealloc - prealloc
- predeclared - predeclared
- promlinter - promlinter
@@ -86,6 +96,7 @@ linters:
- revive - revive
- rowserrcheck - rowserrcheck
- sqlclosecheck - sqlclosecheck
- tagalign
- tenv - tenv
- thelper - thelper
- tparallel - tparallel
@@ -94,6 +105,7 @@ linters:
- usestdlibvars - usestdlibvars
- wastedassign - wastedassign
- whitespace - whitespace
- zerologlint
run: run:
skip-dirs: skip-dirs:

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

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

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

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

View File

@@ -1,8 +1,8 @@
ARG ALPINE_VERSION=3.16 ARG ALPINE_VERSION=3.18
ARG GO_ALPINE_VERSION=3.16 ARG GO_ALPINE_VERSION=3.18
ARG GO_VERSION=1.19 ARG GO_VERSION=1.20
ARG XCPUTRANSLATE_VERSION=v0.6.0 ARG XCPUTRANSLATE_VERSION=v0.6.0
ARG GOLANGCI_LINT_VERSION=v1.49.0 ARG GOLANGCI_LINT_VERSION=v1.53.2
ARG MOCKGEN_VERSION=v1.6.0 ARG MOCKGEN_VERSION=v1.6.0
ARG BUILDPLATFORM=linux/amd64 ARG BUILDPLATFORM=linux/amd64
@@ -97,6 +97,8 @@ ENV VPN_SERVICE_PROVIDER=pia \
WIREGUARD_PRESHARED_KEY= \ WIREGUARD_PRESHARED_KEY= \
WIREGUARD_PUBLIC_KEY= \ WIREGUARD_PUBLIC_KEY= \
WIREGUARD_ADDRESSES= \ WIREGUARD_ADDRESSES= \
WIREGUARD_MTU=1400 \
WIREGUARD_IMPLEMENTATION=auto \
# VPN server filtering # VPN server filtering
SERVER_REGIONS= \ SERVER_REGIONS= \
SERVER_COUNTRIES= \ SERVER_COUNTRIES= \
@@ -107,8 +109,8 @@ ENV VPN_SERVICE_PROVIDER=pia \
OWNED_ONLY=no \ OWNED_ONLY=no \
# # Private Internet Access only: # # Private Internet Access only:
PRIVATE_INTERNET_ACCESS_OPENVPN_ENCRYPTION_PRESET= \ PRIVATE_INTERNET_ACCESS_OPENVPN_ENCRYPTION_PRESET= \
PRIVATE_INTERNET_ACCESS_VPN_PORT_FORWARDING=off \ VPN_PORT_FORWARDING=off \
PRIVATE_INTERNET_ACCESS_VPN_PORT_FORWARDING_STATUS_FILE="/tmp/gluetun/forwarded_port" \ VPN_PORT_FORWARDING_STATUS_FILE="/tmp/gluetun/forwarded_port" \
# # Cyberghost only: # # Cyberghost only:
OPENVPN_CERT= \ OPENVPN_CERT= \
OPENVPN_KEY= \ OPENVPN_KEY= \
@@ -140,6 +142,7 @@ ENV VPN_SERVICE_PROVIDER=pia \
# Health # Health
HEALTH_SERVER_ADDRESS=127.0.0.1:9999 \ HEALTH_SERVER_ADDRESS=127.0.0.1:9999 \
HEALTH_TARGET_ADDRESS=cloudflare.com:443 \ HEALTH_TARGET_ADDRESS=cloudflare.com:443 \
HEALTH_SUCCESS_WAIT_DURATION=5s \
HEALTH_VPN_DURATION_INITIAL=6s \ HEALTH_VPN_DURATION_INITIAL=6s \
HEALTH_VPN_DURATION_ADDITION=5s \ HEALTH_VPN_DURATION_ADDITION=5s \
# DNS over TLS # DNS over TLS
@@ -196,12 +199,12 @@ 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=1 CMD /gluetun-entrypoint healthcheck
ARG TARGETPLATFORM ARG TARGETPLATFORM
RUN apk add --no-cache --update -l apk-tools && \ RUN apk add --no-cache --update -l wget && \
apk add --no-cache --update -X "https://dl-cdn.alpinelinux.org/alpine/v3.12/main" openvpn==2.4.12-r0 && \ apk add --no-cache --update -X "https://dl-cdn.alpinelinux.org/alpine/v3.17/main" openvpn\~2.5 && \
mv /usr/sbin/openvpn /usr/sbin/openvpn2.4 && \ 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 ip6tables unbound tzdata && \
mv /usr/sbin/openvpn /usr/sbin/openvpn2.5 && \ mv /usr/sbin/openvpn /usr/sbin/openvpn2.6 && \
# Fix vulnerability issue # Fix vulnerability issue
apk add --no-cache --update busybox && \ apk add --no-cache --update busybox && \
rm -rf /var/cache/apk/* /etc/unbound/* /usr/sbin/unbound-* /etc/openvpn/*.sh /usr/lib/openvpn/plugins/openvpn-plugin-down-root.so && \ rm -rf /var/cache/apk/* /etc/unbound/* /usr/sbin/unbound-* /etc/openvpn/*.sh /usr/lib/openvpn/plugins/openvpn-plugin-down-root.so && \

255
README.md
View File

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

View File

@@ -77,7 +77,8 @@ func main() {
args := os.Args args := os.Args
tun := tun.New() tun := tun.New()
netLinker := netlink.New() netLinkDebugLogger := logger.New(log.SetComponent("netlink"))
netLinker := netlink.New(netLinkDebugLogger)
cli := cli.New() cli := cli.New()
cmder := command.NewCmder() cmder := command.NewCmder()
@@ -91,12 +92,13 @@ func main() {
errorCh <- _main(ctx, buildInfo, args, logger, muxReader, tun, netLinker, cmder, cli) errorCh <- _main(ctx, buildInfo, args, logger, muxReader, tun, netLinker, cmder, cli)
}() }()
var err error
select { select {
case signal := <-signalCh: case signal := <-signalCh:
fmt.Println("") fmt.Println("")
logger.Warn("Caught OS signal " + signal.String() + ", shutting down") logger.Warn("Caught OS signal " + signal.String() + ", shutting down")
cancel() cancel()
case err := <-errorCh: case err = <-errorCh:
close(errorCh) close(errorCh)
if err == nil { // expected exit such as healthcheck if err == nil { // expected exit such as healthcheck
os.Exit(0) os.Exit(0)
@@ -108,22 +110,27 @@ func main() {
const shutdownGracePeriod = 5 * time.Second const shutdownGracePeriod = 5 * time.Second
timer := time.NewTimer(shutdownGracePeriod) timer := time.NewTimer(shutdownGracePeriod)
select { select {
case err := <-errorCh: case shutdownErr := <-errorCh:
if !timer.Stop() { if !timer.Stop() {
<-timer.C <-timer.C
} }
if err == nil { if shutdownErr != nil {
logger.Info("Shutdown successful") logger.Warnf("Shutdown not completed gracefully: %s", shutdownErr)
os.Exit(0) os.Exit(1)
} }
logger.Warnf("Shutdown not completed gracefully: %s", err)
logger.Info("Shutdown successful")
if err != nil {
os.Exit(1)
}
os.Exit(0)
case <-timer.C: case <-timer.C:
logger.Warn("Shutdown timed out") logger.Warn("Shutdown timed out")
os.Exit(1)
case signal := <-signalCh: case signal := <-signalCh:
logger.Warn("Caught OS signal " + signal.String() + ", forcing shut down") logger.Warn("Caught OS signal " + signal.String() + ", forcing shut down")
os.Exit(1)
} }
os.Exit(1)
} }
var ( var (
@@ -183,6 +190,7 @@ func _main(ctx context.Context, buildInfo models.BuildInformation,
// - firewall Debug and Enabled are booleans parsed from source // - firewall Debug and Enabled are booleans parsed from source
logger.Patch(log.SetLevel(*allSettings.Log.Level)) logger.Patch(log.SetLevel(*allSettings.Log.Level))
netLinker.PatchLoggerLevel(*allSettings.Log.Level)
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
@@ -224,7 +232,12 @@ func _main(ctx context.Context, buildInfo models.BuildInformation,
return err return err
} }
err = allSettings.Validate(storage) ipv6Supported, err := netLinker.IsIPv6Supported()
if err != nil {
return fmt.Errorf("checking for IPv6 support: %w", err)
}
err = allSettings.Validate(storage, ipv6Supported)
if err != nil { if err != nil {
return err return err
} }
@@ -232,7 +245,7 @@ func _main(ctx context.Context, buildInfo models.BuildInformation,
allSettings.Pprof.HTTPServer.Logger = logger.New(log.SetComponent("pprof")) allSettings.Pprof.HTTPServer.Logger = logger.New(log.SetComponent("pprof"))
pprofServer, err := pprof.New(allSettings.Pprof) pprofServer, err := pprof.New(allSettings.Pprof)
if err != nil { if err != nil {
return fmt.Errorf("cannot create Pprof server: %w", err) return fmt.Errorf("creating Pprof server: %w", err)
} }
puid, pgid := int(*allSettings.System.PUID), int(*allSettings.System.PGID) puid, pgid := int(*allSettings.System.PUID), int(*allSettings.System.PGID)
@@ -251,8 +264,8 @@ func _main(ctx context.Context, buildInfo models.BuildInformation,
err = printVersions(ctx, logger, []printVersionElement{ err = printVersions(ctx, logger, []printVersionElement{
{name: "Alpine", getVersion: alpineConf.Version}, {name: "Alpine", getVersion: alpineConf.Version},
{name: "OpenVPN 2.4", getVersion: ovpnConf.Version24},
{name: "OpenVPN 2.5", getVersion: ovpnConf.Version25}, {name: "OpenVPN 2.5", getVersion: ovpnConf.Version25},
{name: "OpenVPN 2.6", getVersion: ovpnConf.Version26},
{name: "Unbound", getVersion: dnsConf.Version}, {name: "Unbound", getVersion: dnsConf.Version},
{name: "IPtables", getVersion: func(ctx context.Context) (version string, err error) { {name: "IPtables", getVersion: func(ctx context.Context) (version string, err error) {
return firewall.Version(ctx, cmder) return firewall.Version(ctx, cmder)
@@ -264,6 +277,10 @@ func _main(ctx context.Context, buildInfo models.BuildInformation,
logger.Info(allSettings.String()) logger.Info(allSettings.String())
for _, warning := range allSettings.Warnings() {
logger.Warn(warning)
}
if err := os.MkdirAll("/tmp/gluetun", 0644); err != nil { if err := os.MkdirAll("/tmp/gluetun", 0644); err != nil {
return err return err
} }
@@ -274,7 +291,7 @@ func _main(ctx context.Context, buildInfo models.BuildInformation,
const defaultUsername = "nonrootuser" const defaultUsername = "nonrootuser"
nonRootUsername, err := alpineConf.CreateUser(defaultUsername, puid) nonRootUsername, err := alpineConf.CreateUser(defaultUsername, puid)
if err != nil { if err != nil {
return fmt.Errorf("cannot create user: %w", err) return fmt.Errorf("creating user: %w", err)
} }
if nonRootUsername != defaultUsername { if nonRootUsername != defaultUsername {
logger.Info("using existing username " + nonRootUsername + " corresponding to user id " + fmt.Sprint(puid)) logger.Info("using existing username " + nonRootUsername + " corresponding to user id " + fmt.Sprint(puid))
@@ -288,22 +305,11 @@ func _main(ctx context.Context, buildInfo models.BuildInformation,
return err return err
} }
ipv6Supported, err := netLinker.IsIPv6Supported()
if err != nil {
return fmt.Errorf("checking for IPv6 support: %w", err)
}
if ipv6Supported {
logger.Info("IPv6 is supported")
} else {
logger.Info("IPv6 is not supported")
}
if err := routingConf.Setup(); err != nil { if err := routingConf.Setup(); err != nil {
if strings.Contains(err.Error(), "operation not permitted") { if strings.Contains(err.Error(), "operation not permitted") {
logger.Warn("💡 Tip: Are you passing NET_ADMIN capability to gluetun?") logger.Warn("💡 Tip: Are you passing NET_ADMIN capability to gluetun?")
} }
return fmt.Errorf("cannot setup routing: %w", err) return fmt.Errorf("setting up routing: %w", err)
} }
defer func() { defer func() {
routingLogger.Info("routing cleanup...") routingLogger.Info("routing cleanup...")
@@ -319,6 +325,11 @@ func _main(ctx context.Context, buildInfo models.BuildInformation,
return err return err
} }
err = routingConf.AddLocalRules(localNetworks)
if err != nil {
return fmt.Errorf("adding local rules: %w", err)
}
const tunDevice = "/dev/net/tun" const tunDevice = "/dev/net/tun"
if err := tun.Check(tunDevice); err != nil { if err := tun.Check(tunDevice); err != nil {
logger.Info(err.Error() + "; creating it...") logger.Info(err.Error() + "; creating it...")
@@ -354,11 +365,14 @@ func _main(ctx context.Context, buildInfo models.BuildInformation,
tickersGroupHandler := goshutdown.NewGroupHandler("tickers", defaultGroupOptions...) tickersGroupHandler := goshutdown.NewGroupHandler("tickers", defaultGroupOptions...)
otherGroupHandler := goshutdown.NewGroupHandler("other", defaultGroupOptions...) otherGroupHandler := goshutdown.NewGroupHandler("other", defaultGroupOptions...)
pprofReady := make(chan struct{}) if *allSettings.Pprof.Enabled {
pprofHandler, pprofCtx, pprofDone := goshutdown.NewGoRoutineHandler("pprof server") // TODO run in run loop so this can be patched at runtime
go pprofServer.Run(pprofCtx, pprofReady, pprofDone) pprofReady := make(chan struct{})
otherGroupHandler.Add(pprofHandler) pprofHandler, pprofCtx, pprofDone := goshutdown.NewGoRoutineHandler("pprof server")
<-pprofReady go pprofServer.Run(pprofCtx, pprofReady, pprofDone)
otherGroupHandler.Add(pprofHandler)
<-pprofReady
}
portForwardLogger := logger.New(log.SetComponent("port forwarding")) portForwardLogger := logger.New(log.SetComponent("port forwarding"))
portForwardLooper := portforward.NewLoop(allSettings.VPN.Provider.PortForwarding, portForwardLooper := portforward.NewLoop(allSettings.VPN.Provider.PortForwarding,
@@ -446,9 +460,10 @@ func _main(ctx context.Context, buildInfo models.BuildInformation,
"http server", goroutine.OptionTimeout(defaultShutdownTimeout)) "http server", goroutine.OptionTimeout(defaultShutdownTimeout))
httpServer, err := server.New(httpServerCtx, controlServerAddress, controlServerLogging, httpServer, err := server.New(httpServerCtx, controlServerAddress, controlServerLogging,
logger.New(log.SetComponent("http server")), logger.New(log.SetComponent("http server")),
buildInfo, vpnLooper, portForwardLooper, unboundLooper, updaterLooper, publicIPLooper, storage) buildInfo, vpnLooper, portForwardLooper, unboundLooper, updaterLooper, publicIPLooper,
storage, ipv6Supported)
if err != nil { if err != nil {
return fmt.Errorf("cannot setup control server: %w", err) return fmt.Errorf("setting up control server: %w", err)
} }
httpServerReady := make(chan struct{}) httpServerReady := make(chan struct{})
go httpServer.Run(httpServerCtx, httpServerReady, httpServerDone) go httpServer.Run(httpServerCtx, httpServerReady, httpServerDone)
@@ -495,7 +510,7 @@ func printVersions(ctx context.Context, logger infoer,
for _, element := range elements { for _, element := range elements {
version, err := element.getVersion(ctx) version, err := element.getVersion(ctx)
if err != nil { if err != nil {
return err return fmt.Errorf("getting %s version: %w", element.name, err)
} }
logger.Info(element.name + " version: " + version) logger.Info(element.name + " version: " + version)
} }
@@ -510,35 +525,35 @@ type netLinker interface {
Linker Linker
IsWireguardSupported() (ok bool, err error) IsWireguardSupported() (ok bool, err error)
IsIPv6Supported() (ok bool, err error) IsIPv6Supported() (ok bool, err error)
PatchLoggerLevel(level log.Level)
} }
type Addresser interface { type Addresser interface {
AddrList(link netlink.Link, family int) ( AddrList(link netlink.Link, family int) (
addresses []netlink.Addr, err error) addresses []netlink.Addr, err error)
AddrAdd(link netlink.Link, addr *netlink.Addr) error AddrReplace(link netlink.Link, addr netlink.Addr) error
} }
type Router interface { type Router interface {
RouteList(link netlink.Link, family int) ( RouteList(family int) (routes []netlink.Route, err error)
routes []netlink.Route, err error) RouteAdd(route netlink.Route) error
RouteAdd(route *netlink.Route) error RouteDel(route netlink.Route) error
RouteDel(route *netlink.Route) error RouteReplace(route netlink.Route) error
RouteReplace(route *netlink.Route) error
} }
type Ruler interface { type Ruler interface {
RuleList(family int) (rules []netlink.Rule, err error) RuleList(family int) (rules []netlink.Rule, err error)
RuleAdd(rule *netlink.Rule) error RuleAdd(rule netlink.Rule) error
RuleDel(rule *netlink.Rule) error RuleDel(rule netlink.Rule) error
} }
type Linker interface { type Linker interface {
LinkList() (links []netlink.Link, err error) LinkList() (links []netlink.Link, err error)
LinkByName(name string) (link netlink.Link, err error) LinkByName(name string) (link netlink.Link, err error)
LinkByIndex(index int) (link netlink.Link, err error) LinkByIndex(index int) (link netlink.Link, err error)
LinkAdd(link netlink.Link) (err error) LinkAdd(link netlink.Link) (linkIndex int, err error)
LinkDel(link netlink.Link) (err error) LinkDel(link netlink.Link) (err error)
LinkSetUp(link netlink.Link) (err error) LinkSetUp(link netlink.Link) (linkIndex int, err error)
LinkSetDown(link netlink.Link) (err error) LinkSetDown(link netlink.Link) (err error)
} }

47
go.mod
View File

@@ -1,48 +1,51 @@
module github.com/qdm12/gluetun module github.com/qdm12/gluetun
go 1.19 go 1.20
require ( require (
github.com/breml/rootcerts v0.2.8 github.com/breml/rootcerts v0.2.11
github.com/fatih/color v1.13.0 github.com/fatih/color v1.15.0
github.com/golang/mock v1.6.0 github.com/golang/mock v1.6.0
github.com/qdm12/dns v1.11.0 github.com/qdm12/dns v1.11.0
github.com/qdm12/golibs v0.0.0-20210822203818-5c568b0777b6 github.com/qdm12/golibs v0.0.0-20210822203818-5c568b0777b6
github.com/qdm12/gosettings v0.3.0-rc13
github.com/qdm12/goshutdown v0.3.0 github.com/qdm12/goshutdown v0.3.0
github.com/qdm12/gosplash v0.1.0 github.com/qdm12/gosplash v0.1.0
github.com/qdm12/gotree v0.2.0 github.com/qdm12/gotree v0.2.0
github.com/qdm12/govalid v0.1.0 github.com/qdm12/govalid v0.2.0-rc1
github.com/qdm12/log v0.1.0 github.com/qdm12/log v0.1.0
github.com/qdm12/ss-server v0.4.0 github.com/qdm12/ss-server v0.5.0-rc1
github.com/qdm12/updated v0.0.0-20210603204757-205acfe6937e github.com/qdm12/updated v0.0.0-20210603204757-205acfe6937e
github.com/stretchr/testify v1.8.1 github.com/stretchr/testify v1.8.4
github.com/vishvananda/netlink v1.1.1-0.20211129163951-9ada19101fc5 github.com/vishvananda/netlink v1.2.1-beta.2
golang.org/x/net v0.0.0-20220418201149-a630d4f3e7a2 github.com/youmark/pkcs8 v0.0.0-20201027041543-1326539a0a0a
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f golang.org/x/net v0.10.0
golang.org/x/text v0.4.0 golang.org/x/sys v0.8.0
golang.zx2c4.com/wireguard v0.0.0-20220703234212-c31a7b1ab478 golang.org/x/text v0.10.0
golang.zx2c4.com/wireguard/wgctrl v0.0.0-20220504211119-3d4a969bb56b golang.zx2c4.com/wireguard v0.0.0-20230325221338-052af4a8072b
inet.af/netaddr v0.0.0-20210718074554-06ca8145d722 golang.zx2c4.com/wireguard/wgctrl v0.0.0-20230215201556-9c5414ab4bde
inet.af/netaddr v0.0.0-20220811202034-502d2d690317
) )
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.7 // indirect github.com/google/go-cmp v0.5.9 // indirect
github.com/josharian/native v1.0.0 // indirect github.com/josharian/native v1.0.0 // indirect
github.com/mattn/go-colorable v0.1.9 // indirect github.com/mattn/go-colorable v0.1.13 // indirect
github.com/mattn/go-isatty v0.0.14 // indirect github.com/mattn/go-isatty v0.0.17 // indirect
github.com/mdlayher/genetlink v1.2.0 // indirect github.com/mdlayher/genetlink v1.2.0 // indirect
github.com/mdlayher/netlink v1.6.0 // indirect github.com/mdlayher/netlink v1.6.2 // indirect
github.com/mdlayher/socket v0.2.3 // indirect github.com/mdlayher/socket v0.2.3 // 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
github.com/riobard/go-bloom v0.0.0-20200614022211-cdc8013cb5b3 // indirect github.com/riobard/go-bloom v0.0.0-20200614022211-cdc8013cb5b3 // indirect
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-20210108033219-3eb7198706b2 // indirect go4.org/intern v0.0.0-20211027215823-ae77deb06f29 // indirect
go4.org/unsafe/assume-no-moving-gc v0.0.0-20220617031537-928513b29760 // indirect go4.org/unsafe/assume-no-moving-gc v0.0.0-20230221090011-e4bae7ad2296 // indirect
golang.org/x/crypto v0.0.0-20220411220226-7b82a4e95df4 // indirect golang.org/x/crypto v0.9.0 // indirect
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c // indirect golang.org/x/exp v0.0.0-20230522175609-2e198f4a06a1 // indirect
golang.zx2c4.com/wintun v0.0.0-20211104114900-415007cec224 // indirect golang.org/x/sync v0.1.0 // indirect
golang.zx2c4.com/wintun v0.0.0-20230126152724-0fa3db229ce2 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect
) )

103
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.8 h1:hNPyNa+MghU9ZKqWy+MYxvouNqE70jvOBIa5v70z/P8= github.com/breml/rootcerts v0.2.11 h1:njUAtoyZ6HUXPAPk63tGz0BEZk1/6gyfqK5fTzksHkM=
github.com/breml/rootcerts v0.2.8/go.mod h1:24FDtzYMpqIeYC7QzaE8VPRQaFZU5TIUDlyk8qwjD88= github.com/breml/rootcerts v0.2.11/go.mod h1:S/PKh+4d1HUn4HQovEB8hPJZO6pUZYrIhmXBhsegfXw=
github.com/creack/pty v1.1.7/go.mod h1:lj5s0c3V2DBrqTV7llrYr5NG6My20zk30Fl46Y7DoTY= github.com/creack/pty v1.1.7/go.mod h1:lj5s0c3V2DBrqTV7llrYr5NG6My20zk30Fl46Y7DoTY=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
@@ -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.13.0 h1:8LOYc1KYPPmyKMuN8QV2DNRWNbLo6LZ0iLs8+mlH53w= github.com/fatih/color v1.15.0 h1:kOqh6YHBtK8aywxGerMG2Eq3H6Qgoqeo13Bk2Mv/nBs=
github.com/fatih/color v1.13.0/go.mod h1:kLAiJbzzSOZDVNGyDpeOxJ47H46qBXwg5ILebYFFOfk= github.com/fatih/color v1.15.0/go.mod h1:0h5ZqXfHYED7Bhv2ZJamyIOUej9KtShiJESRwBDUSsw=
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=
@@ -36,10 +36,12 @@ github.com/golang/mock v1.5.0/go.mod h1:CWnOUgYIOo4TcNZ0wHX3YZCqsaM1I1Jvs6v3mP3K
github.com/golang/mock v1.6.0 h1:ErTB+efbowRARo13NNdxyJji2egdxLGQhRaY+DUumQc= github.com/golang/mock v1.6.0 h1:ErTB+efbowRARo13NNdxyJji2egdxLGQhRaY+DUumQc=
github.com/golang/mock v1.6.0/go.mod h1:p6yTPP+5HYm5mzsMV8JkE6ZKdX+/wYM6Hr+LicevLPs= github.com/golang/mock v1.6.0/go.mod h1:p6yTPP+5HYm5mzsMV8JkE6ZKdX+/wYM6Hr+LicevLPs=
github.com/gomodule/redigo v2.0.0+incompatible/go.mod h1:B4C85qUVwatsJoIUNIfCRsp7qO0iAmpGFZ4EELWSbC4= github.com/gomodule/redigo v2.0.0+incompatible/go.mod h1:B4C85qUVwatsJoIUNIfCRsp7qO0iAmpGFZ4EELWSbC4=
github.com/google/btree v1.0.1 h1:gK4Kx5IaGY9CD5sPJ36FHiBJ6ZXl0kilRiiCj+jdYp4=
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.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.7 h1:81/ik6ipDQS2aGcBfIN5dHDB36BwrStyeAQquSYCV4o=
github.com/google/go-cmp v0.5.7/go.mod h1:n+brtR0CgQNWTVd5ZUFpTBC8YFBDLK/h/bpaJ8/DtOE= github.com/google/go-cmp v0.5.7/go.mod h1:n+brtR0CgQNWTVd5ZUFpTBC8YFBDLK/h/bpaJ8/DtOE=
github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38=
github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
github.com/google/uuid v1.0.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/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=
@@ -56,15 +58,17 @@ github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
github.com/kyokomi/emoji v2.2.4+incompatible/go.mod h1:mZ6aGCD7yk8j6QY6KICwnZ2pxoszVseX1DNoGtU2tBA= github.com/kyokomi/emoji v2.2.4+incompatible/go.mod h1:mZ6aGCD7yk8j6QY6KICwnZ2pxoszVseX1DNoGtU2tBA=
github.com/mailru/easyjson v0.0.0-20180823135443-60711f1a8329/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= github.com/mailru/easyjson v0.0.0-20180823135443-60711f1a8329/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc=
github.com/mattn/go-colorable v0.1.8/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= github.com/mattn/go-colorable v0.1.8/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc=
github.com/mattn/go-colorable v0.1.9 h1:sqDoxXbdeALODt0DAeJCVp38ps9ZogZEAXjus69YV3U= github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA=
github.com/mattn/go-colorable v0.1.9/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg=
github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU=
github.com/mattn/go-isatty v0.0.14 h1:yVuAays6BHfxijgZPzw+3Zlu5yQgKGP2/hcQbHb7S9Y= github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94= github.com/mattn/go-isatty v0.0.17 h1:BTarxUcIeDqL27Mc+vyvdWYSL28zpIhv3RoTdsLMPng=
github.com/mattn/go-isatty v0.0.17/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
github.com/mdlayher/genetlink v1.2.0 h1:4yrIkRV5Wfk1WfpWTcoOlGmsWgQj3OtQN9ZsbrE+XtU= github.com/mdlayher/genetlink v1.2.0 h1:4yrIkRV5Wfk1WfpWTcoOlGmsWgQj3OtQN9ZsbrE+XtU=
github.com/mdlayher/genetlink v1.2.0/go.mod h1:ra5LDov2KrUCZJiAtEvXXZBxGMInICMXIwshlJ+qRxQ= github.com/mdlayher/genetlink v1.2.0/go.mod h1:ra5LDov2KrUCZJiAtEvXXZBxGMInICMXIwshlJ+qRxQ=
github.com/mdlayher/netlink v1.6.0 h1:rOHX5yl7qnlpiVkFWoqccueppMtXzeziFjWAjLg6sz0=
github.com/mdlayher/netlink v1.6.0/go.mod h1:0o3PlBmGst1xve7wQ7j/hwpNaFaH4qCRyWCdcZk8/vA= github.com/mdlayher/netlink v1.6.0/go.mod h1:0o3PlBmGst1xve7wQ7j/hwpNaFaH4qCRyWCdcZk8/vA=
github.com/mdlayher/netlink v1.6.2 h1:D2zGSkvYsJ6NreeED3JiVTu1lj2sIYATqSaZlhPzUgQ=
github.com/mdlayher/netlink v1.6.2/go.mod h1:O1HXX2sIWSMJ3Qn1BYZk1yZM+7iMki/uYGGiwGyq/iU=
github.com/mdlayher/socket v0.1.1/go.mod h1:mYV5YIZAfHh4dzDVzI8x8tWLWCliuX8Mon5Awbj+qDs= github.com/mdlayher/socket v0.1.1/go.mod h1:mYV5YIZAfHh4dzDVzI8x8tWLWCliuX8Mon5Awbj+qDs=
github.com/mdlayher/socket v0.2.3 h1:XZA2X2TjdOwNoNPVPclRCURoX/hokBY8nkTmRZFEheM= 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/mdlayher/socket v0.2.3/go.mod h1:bz12/FozYNH/VbvC3q7TRIK/Y6dH1kCKsXaUeXi/FmY=
@@ -87,18 +91,20 @@ github.com/qdm12/golibs v0.0.0-20210603202746-e5494e9c2ebb/go.mod h1:15RBzkun0i8
github.com/qdm12/golibs v0.0.0-20210723175634-a75ca7fd74c2/go.mod h1:6aRbg4Z/bTbm9JfxsGXfWKHi7zsOvPfUTK1S5HuAFKg= github.com/qdm12/golibs v0.0.0-20210723175634-a75ca7fd74c2/go.mod h1:6aRbg4Z/bTbm9JfxsGXfWKHi7zsOvPfUTK1S5HuAFKg=
github.com/qdm12/golibs v0.0.0-20210822203818-5c568b0777b6 h1:bge5AL7cjHJMPz+5IOz5yF01q/l8No6+lIEBieA8gMg= github.com/qdm12/golibs v0.0.0-20210822203818-5c568b0777b6 h1:bge5AL7cjHJMPz+5IOz5yF01q/l8No6+lIEBieA8gMg=
github.com/qdm12/golibs v0.0.0-20210822203818-5c568b0777b6/go.mod h1:6aRbg4Z/bTbm9JfxsGXfWKHi7zsOvPfUTK1S5HuAFKg= github.com/qdm12/golibs v0.0.0-20210822203818-5c568b0777b6/go.mod h1:6aRbg4Z/bTbm9JfxsGXfWKHi7zsOvPfUTK1S5HuAFKg=
github.com/qdm12/gosettings v0.3.0-rc13 h1:fag+/hFPBUcNk3a5ifUbwNS2VgXFpxindkl8mQNk76U=
github.com/qdm12/gosettings v0.3.0-rc13/go.mod h1:JRV3opOpHvnKlIA29lKQMdYw1WSMVMfHYLLHPHol5ME=
github.com/qdm12/goshutdown v0.3.0 h1:pqBpJkdwlZlfTEx4QHtS8u8CXx6pG0fVo6S1N0MpSEM= github.com/qdm12/goshutdown v0.3.0 h1:pqBpJkdwlZlfTEx4QHtS8u8CXx6pG0fVo6S1N0MpSEM=
github.com/qdm12/goshutdown v0.3.0/go.mod h1:EqZ46No00kCTZ5qzdd3qIzY6ayhMt24QI8Mh8LVQYmM= github.com/qdm12/goshutdown v0.3.0/go.mod h1:EqZ46No00kCTZ5qzdd3qIzY6ayhMt24QI8Mh8LVQYmM=
github.com/qdm12/gosplash v0.1.0 h1:Sfl+zIjFZFP7b0iqf2l5UkmEY97XBnaKkH3FNY6Gf7g= github.com/qdm12/gosplash v0.1.0 h1:Sfl+zIjFZFP7b0iqf2l5UkmEY97XBnaKkH3FNY6Gf7g=
github.com/qdm12/gosplash v0.1.0/go.mod h1:+A3fWW4/rUeDXhY3ieBzwghKdnIPFJgD8K3qQkenJlw= github.com/qdm12/gosplash v0.1.0/go.mod h1:+A3fWW4/rUeDXhY3ieBzwghKdnIPFJgD8K3qQkenJlw=
github.com/qdm12/gotree v0.2.0 h1:+58ltxkNLUyHtATFereAcOjBVfY6ETqRex8XK90Fb/c= github.com/qdm12/gotree v0.2.0 h1:+58ltxkNLUyHtATFereAcOjBVfY6ETqRex8XK90Fb/c=
github.com/qdm12/gotree v0.2.0/go.mod h1:1SdFaqKZuI46U1apbXIf25pDMNnrPuYLEqMF/qL4lY4= github.com/qdm12/gotree v0.2.0/go.mod h1:1SdFaqKZuI46U1apbXIf25pDMNnrPuYLEqMF/qL4lY4=
github.com/qdm12/govalid v0.1.0 h1:UIFVmuaAg0Q+h0GeyfcFEZ5sQ5KJPvRQwycC1/cqDN8= github.com/qdm12/govalid v0.2.0-rc1 h1:4iYQvU4ibrASgzelsEgZX4JyKX3UTB/DcHObzQ7BXtw=
github.com/qdm12/govalid v0.1.0/go.mod h1:CyS/OEQdOvunBgrtIsW93fjd4jBkwZPBjGSpxq3NwA4= github.com/qdm12/govalid v0.2.0-rc1/go.mod h1:/uWzVWMuS71wmbsVnlUxpQiy6EAXqm8eQ2RbyA72roQ=
github.com/qdm12/log v0.1.0 h1:jYBd/xscHYpblzZAd2kjZp2YmuYHjAAfbTViJWxoPTw= github.com/qdm12/log v0.1.0 h1:jYBd/xscHYpblzZAd2kjZp2YmuYHjAAfbTViJWxoPTw=
github.com/qdm12/log v0.1.0/go.mod h1:Vchi5M8uBvHfPNIblN4mjXn/oSbiWguQIbsgF1zdQPI= github.com/qdm12/log v0.1.0/go.mod h1:Vchi5M8uBvHfPNIblN4mjXn/oSbiWguQIbsgF1zdQPI=
github.com/qdm12/ss-server v0.4.0 h1:lMMYfDGc9P86Lyvd3+p8lK4hhgHUKDzjZC91FqJYkDU= github.com/qdm12/ss-server v0.5.0-rc1 h1:2rJEhDnUUc9AKtvyVu+CrnJwvdEjMaB1zFRQvTUlDPw=
github.com/qdm12/ss-server v0.4.0/go.mod h1:AY0p4huvPUPW+/CiWsJcDgT6sneDryk26VXSccPNCxY= github.com/qdm12/ss-server v0.5.0-rc1/go.mod h1:IoFYGpVpxfIB/dMTr0PnSegdhV1gEfZLS9Tr1Qn8uRg=
github.com/qdm12/updated v0.0.0-20210603204757-205acfe6937e h1:4q+uFLawkaQRq3yARYLsjJPZd2wYwxn4g6G/5v0xW1g= github.com/qdm12/updated v0.0.0-20210603204757-205acfe6937e h1:4q+uFLawkaQRq3yARYLsjJPZd2wYwxn4g6G/5v0xW1g=
github.com/qdm12/updated v0.0.0-20210603204757-205acfe6937e/go.mod h1:UvJRGkZ9XL3/D7e7JiTTVLm1F3Cymd3/gFpD6frEpBo= github.com/qdm12/updated v0.0.0-20210603204757-205acfe6937e/go.mod h1:UvJRGkZ9XL3/D7e7JiTTVLm1F3Cymd3/gFpD6frEpBo=
github.com/riobard/go-bloom v0.0.0-20200614022211-cdc8013cb5b3 h1:f/FNXud6gA3MNr8meMVVGxhp+QBTqY91tM8HjEuMjGg= github.com/riobard/go-bloom v0.0.0-20200614022211-cdc8013cb5b3 h1:f/FNXud6gA3MNr8meMVVGxhp+QBTqY91tM8HjEuMjGg=
@@ -107,44 +113,49 @@ github.com/sergi/go-diff v1.0.0/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAm
github.com/src-d/gcfg v1.4.0/go.mod h1:p/UMsR43ujA89BJY9duynAwIpvqEujIH/jFlfL7jWoI= github.com/src-d/gcfg v1.4.0/go.mod h1:p/UMsR43ujA89BJY9duynAwIpvqEujIH/jFlfL7jWoI=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.2.0/go.mod h1:qt09Ya8vawLte6SNmTgCsAVtYtaKzEcn8ATUoHMkEqE= github.com/stretchr/objx v0.2.0/go.mod h1:qt09Ya8vawLte6SNmTgCsAVtYtaKzEcn8ATUoHMkEqE=
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk=
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
github.com/stretchr/testify v1.8.1 h1:w7B6lhMri9wdJUVmEZPGGhZzrYTPvgJArz7wNPgYKsk= github.com/vishvananda/netlink v1.2.1-beta.2 h1:Llsql0lnQEbHj0I1OuKyp8otXp0r3q0mPkuhwHfStVs=
github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= github.com/vishvananda/netlink v1.2.1-beta.2/go.mod h1:twkDnbuQxJYemMlGd4JFIcuhgX83tXhKS2B/PRMpOho=
github.com/vishvananda/netlink v1.1.1-0.20211129163951-9ada19101fc5 h1:b/k/BVWzWRS5v6AB0gf2ckFSbFsHN5jR0HoNso1pN+w=
github.com/vishvananda/netlink v1.1.1-0.20211129163951-9ada19101fc5/go.mod h1:twkDnbuQxJYemMlGd4JFIcuhgX83tXhKS2B/PRMpOho=
github.com/vishvananda/netns v0.0.0-20200728191858-db3c7e526aae h1:4hwBBUfQCFe3Cym0ZtKyq7L16eZUtYKs+BaHDN6mAns= github.com/vishvananda/netns v0.0.0-20200728191858-db3c7e526aae h1:4hwBBUfQCFe3Cym0ZtKyq7L16eZUtYKs+BaHDN6mAns=
github.com/vishvananda/netns v0.0.0-20200728191858-db3c7e526aae/go.mod h1:DD4vA1DwXk04H54A1oHXtwZmA0grkVMdPxx/VGLCah0= github.com/vishvananda/netns v0.0.0-20200728191858-db3c7e526aae/go.mod h1:DD4vA1DwXk04H54A1oHXtwZmA0grkVMdPxx/VGLCah0=
github.com/xanzy/ssh-agent v0.2.1/go.mod h1:mLlQY/MoOhWBj+gOGMQkOeiEvkx+8pJSI+0Bx9h2kr4= github.com/xanzy/ssh-agent v0.2.1/go.mod h1:mLlQY/MoOhWBj+gOGMQkOeiEvkx+8pJSI+0Bx9h2kr4=
github.com/yl2chen/cidranger v1.0.2/go.mod h1:9U1yz7WPYDwf0vpNWFaeRh0bjwz5RVgRy/9UEQfHl0g= github.com/yl2chen/cidranger v1.0.2/go.mod h1:9U1yz7WPYDwf0vpNWFaeRh0bjwz5RVgRy/9UEQfHl0g=
github.com/youmark/pkcs8 v0.0.0-20201027041543-1326539a0a0a h1:fZHgsYlfvtyqToslyjUt3VOPF4J7aK/3MPcK7xp3PDk=
github.com/youmark/pkcs8 v0.0.0-20201027041543-1326539a0a0a/go.mod h1:ul22v+Nro/R083muKhosV54bj5niojjWZvU8xrevuH4=
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
go4.org/intern v0.0.0-20210108033219-3eb7198706b2 h1:VFTf+jjIgsldaz/Mr00VaCSswHJrI2hIjQygE/W4IMg=
go4.org/intern v0.0.0-20210108033219-3eb7198706b2/go.mod h1:vLqJ+12kCw61iCWsPto0EOHhBS+o4rO5VIucbc9g2Cc= go4.org/intern v0.0.0-20210108033219-3eb7198706b2/go.mod h1:vLqJ+12kCw61iCWsPto0EOHhBS+o4rO5VIucbc9g2Cc=
go4.org/intern v0.0.0-20211027215823-ae77deb06f29 h1:UXLjNohABv4S58tHmeuIZDO6e3mHpW2Dx33gaNt03LE=
go4.org/intern v0.0.0-20211027215823-ae77deb06f29/go.mod h1:cS2ma+47FKrLPdXFpr7CuxiTW3eyJbWew4qx0qtQWDA=
go4.org/unsafe/assume-no-moving-gc v0.0.0-20201222175341-b30ae309168e/go.mod h1:FftLjUGFEDu5k8lt0ddY+HcrH/qU/0qk+H8j9/nTl3E= go4.org/unsafe/assume-no-moving-gc v0.0.0-20201222175341-b30ae309168e/go.mod h1:FftLjUGFEDu5k8lt0ddY+HcrH/qU/0qk+H8j9/nTl3E=
go4.org/unsafe/assume-no-moving-gc v0.0.0-20201222180813-1025295fd063/go.mod h1:FftLjUGFEDu5k8lt0ddY+HcrH/qU/0qk+H8j9/nTl3E= go4.org/unsafe/assume-no-moving-gc v0.0.0-20201222180813-1025295fd063/go.mod h1:FftLjUGFEDu5k8lt0ddY+HcrH/qU/0qk+H8j9/nTl3E=
go4.org/unsafe/assume-no-moving-gc v0.0.0-20220617031537-928513b29760 h1:FyBZqvoA/jbNzuAWLQE2kG820zMAkcilx6BMjGbL/E4= go4.org/unsafe/assume-no-moving-gc v0.0.0-20211027215541-db492cf91b37/go.mod h1:FftLjUGFEDu5k8lt0ddY+HcrH/qU/0qk+H8j9/nTl3E=
go4.org/unsafe/assume-no-moving-gc v0.0.0-20220617031537-928513b29760/go.mod h1:FftLjUGFEDu5k8lt0ddY+HcrH/qU/0qk+H8j9/nTl3E= go4.org/unsafe/assume-no-moving-gc v0.0.0-20220617031537-928513b29760/go.mod h1:FftLjUGFEDu5k8lt0ddY+HcrH/qU/0qk+H8j9/nTl3E=
go4.org/unsafe/assume-no-moving-gc v0.0.0-20230221090011-e4bae7ad2296 h1:QJ/xcIANMLApehfgPCHnfK1hZiaMmbaTVmPv7DAoTbo=
go4.org/unsafe/assume-no-moving-gc v0.0.0-20230221090011-e4bae7ad2296/go.mod h1:FftLjUGFEDu5k8lt0ddY+HcrH/qU/0qk+H8j9/nTl3E=
golang.org/x/crypto v0.0.0-20190219172222-a4c6cb3142f2/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20190219172222-a4c6cb3142f2/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20190701094942-4def268fd1a4/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20190701094942-4def268fd1a4/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20200302210943-78000ba7a073/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-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.0.0-20220411220226-7b82a4e95df4 h1:kUhD7nTDoI3fVd9G4ORWrbV5NY0liEs/Jg2pv5f+bBA= golang.org/x/crypto v0.9.0 h1:LF6fAI+IutBocDJ2OT0Q1g8plpYljMZ4+lty+dsqw3g=
golang.org/x/crypto v0.0.0-20220411220226-7b82a4e95df4/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= golang.org/x/crypto v0.9.0/go.mod h1:yrmDGqONDYtNj3tH8X9dzUun2m2lzPa9ngI6/RUPGR0=
golang.org/x/exp v0.0.0-20230522175609-2e198f4a06a1 h1:k/i9J1pBpvlfR+9QsetwPyERsqu1GIbi967PQMq3Ivc=
golang.org/x/exp v0.0.0-20230522175609-2e198f4a06a1/go.mod h1:V1LtkGg67GoY2N1AnLN78QLrzxkLyJw7RJb1gzOOz9w=
golang.org/x/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=
@@ -153,12 +164,15 @@ golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v
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.0.0-20210928044308-7d9f5e0b762b/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/net v0.0.0-20220127200216-cd36cc0744dd/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= golang.org/x/net v0.0.0-20220127200216-cd36cc0744dd/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
golang.org/x/net v0.0.0-20220418201149-a630d4f3e7a2 h1:6mzvA99KwZxbOrxww4EvWVQUnN1+xEu9tafK5ZxkYeA= golang.org/x/net v0.0.0-20220923203811-8be639271d50/go.mod h1:YDH+HFinaLZZlnHAfSS6ZXJJ9M9t4Dl22yv3iI2vPwk=
golang.org/x/net v0.0.0-20220418201149-a630d4f3e7a2/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= golang.org/x/net v0.10.0 h1:X2//UzNDwYmtCLn7To6G58Wr6f5ahEAQgKNzv9Y951M=
golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg=
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 h1:5KslGYwFpkhGh+Q16bwMP3cOontH8FOep7tGV86Y7SQ=
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/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=
golang.org/x/sys v0.0.0-20190221075227-b4e8571b14e0/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190221075227-b4e8571b14e0/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
@@ -175,12 +189,14 @@ golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7w
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/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-20210630005230-0f9fa26af87c/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-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-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-20220128215802-99c3d69c2c27/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f h1:v4INt8xihDGvnrfjMDVXGxw9wrfxYyCjk0KbXjhR55s= golang.org/x/sys v0.0.0-20220319134239-a9b59b0215f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/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.8.0 h1:EBmGv8NaZBZTWvrbjNoL6HVt+IVy3QDQpJs7VRIw3tU=
golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= 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=
@@ -188,8 +204,9 @@ golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
golang.org/x/text v0.4.0 h1:BrVqGRd7+k1DiOgtnFvAkoQEWQvBc25ouMJM6429SFg= golang.org/x/text v0.10.0 h1:UpjohKhiEgNc0CSauXmwYftY1+LlaC75SJwh0SgCX58=
golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= golang.org/x/text v0.10.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
golang.org/x/time v0.0.0-20191024005414-555d28b269f0 h1:/5xXl8Y5W96D+TtHSlonuFqGHIWVuyCkGJLwGh9JJFs=
golang.org/x/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=
@@ -199,14 +216,13 @@ 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-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE=
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.zx2c4.com/wintun v0.0.0-20211104114900-415007cec224 h1:Ug9qvr1myri/zFN6xL17LSCBGFDnphBBhzmILHsM5TY= golang.zx2c4.com/wintun v0.0.0-20230126152724-0fa3db229ce2 h1:B82qJJgjvYKsXS9jeunTOisW56dUokqW/FOteYJJ/yg=
golang.zx2c4.com/wintun v0.0.0-20211104114900-415007cec224/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-20220703234212-c31a7b1ab478 h1:vDy//hdR+GnROE3OdYbQKt9rdtNdHkDtONvpRwmls/0= golang.zx2c4.com/wireguard v0.0.0-20230325221338-052af4a8072b h1:J1CaxgLerRR5lgx3wnr6L04cJFbWoceSK9JWBdglINo=
golang.zx2c4.com/wireguard v0.0.0-20220703234212-c31a7b1ab478/go.mod h1:bVQfyl2sCM/QIIGHpWbFGfHPuDvqnCNkT6MQLTCjO/U= golang.zx2c4.com/wireguard v0.0.0-20230325221338-052af4a8072b/go.mod h1:tqur9LnfstdR9ep2LaJT4lFUl0EjlHtge+gAjmsHUG4=
golang.zx2c4.com/wireguard/wgctrl v0.0.0-20220504211119-3d4a969bb56b h1:9JncmKXcUwE918my+H6xmjBdhK2jM/UTUNXxhRG1BAk= golang.zx2c4.com/wireguard/wgctrl v0.0.0-20230215201556-9c5414ab4bde h1:ybF7AMzIUikL9x4LgwEmzhXtzRpKNqngme1VGDWz+Nk=
golang.zx2c4.com/wireguard/wgctrl v0.0.0-20220504211119-3d4a969bb56b/go.mod h1:yp4gl6zOlnDGOZeWeDfMwQcsdOIQnMdhuPx9mwwWBL4= golang.zx2c4.com/wireguard/wgctrl v0.0.0-20230215201556-9c5414ab4bde/go.mod h1:mQqgjkW8GQQcJQsbBvK890TKqUK1DfKWkuBGbOkuMHQ=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 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=
@@ -219,6 +235,7 @@ 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=
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-20210718074554-06ca8145d722 h1:Qws2rZnQudC58cIagVucPQDLmMi3kAXgxscsgD0v6DU= inet.af/netaddr v0.0.0-20220811202034-502d2d690317 h1:U2fwK6P2EqmopP/hFLTOAjWTki0qgd4GMJn5X8wOleU=
inet.af/netaddr v0.0.0-20210718074554-06ca8145d722/go.mod h1:z0nx+Dh+7N7CC8V5ayHtHGpZpxLQZZxkIaaz6HN65Ls= inet.af/netaddr v0.0.0-20220811202034-502d2d690317/go.mod h1:OIezDfdzOgFhuw4HuWapWq2e9l0H9tK4F1j+ETRtF3k=

View File

@@ -7,7 +7,7 @@ import (
"strings" "strings"
) )
func (a *Alpine) Version(ctx context.Context) (version string, err error) { func (a *Alpine) Version(context.Context) (version string, err error) {
file, err := os.OpenFile(a.alpineReleasePath, os.O_RDONLY, 0) file, err := os.OpenFile(a.alpineReleasePath, os.O_RDONLY, 0)
if err != nil { if err != nil {
return "", err return "", err

View File

@@ -2,6 +2,6 @@ package cli
import "context" import "context"
func (c *CLI) CI(context context.Context) error { func (c *CLI) CI(context.Context) error {
return nil return nil
} }

View File

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

View File

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

View File

@@ -8,9 +8,9 @@ func newNoopLogger() *noopLogger {
return new(noopLogger) return new(noopLogger)
} }
func (l *noopLogger) Debug(s string) {} func (l *noopLogger) Debug(string) {}
func (l *noopLogger) Info(s string) {} func (l *noopLogger) Info(string) {}
func (l *noopLogger) Warn(s string) {} func (l *noopLogger) Warn(string) {}
func (l *noopLogger) Error(s string) {} func (l *noopLogger) Error(string) {}
func (l *noopLogger) PatchLevel(level logging.Level) {} func (l *noopLogger) PatchLevel(logging.Level) {}
func (l *noopLogger) PatchPrefix(prefix string) {} func (l *noopLogger) PatchPrefix(string) {}

View File

@@ -3,8 +3,8 @@ package cli
import ( import (
"context" "context"
"fmt" "fmt"
"net"
"net/http" "net/http"
"net/netip"
"strings" "strings"
"time" "time"
@@ -28,11 +28,11 @@ type Unzipper interface {
type ParallelResolver interface { type ParallelResolver interface {
Resolve(ctx context.Context, settings resolver.ParallelSettings) ( Resolve(ctx context.Context, settings resolver.ParallelSettings) (
hostToIPs map[string][]net.IP, warnings []string, err error) hostToIPs map[string][]netip.Addr, warnings []string, err error)
} }
type IPFetcher interface { type IPFetcher interface {
FetchMultiInfo(ctx context.Context, ips []net.IP) (data []ipinfo.Response, err error) FetchMultiInfo(ctx context.Context, ips []netip.Addr) (data []ipinfo.Response, err error)
} }
type IPv6Checker interface { type IPv6Checker interface {
@@ -51,15 +51,15 @@ func (c *CLI) OpenvpnConfig(logger OpenvpnConfigLogger, source Source,
return err return err
} }
if err = allSettings.Validate(storage); err != nil {
return err
}
ipv6Supported, err := ipv6Checker.IsIPv6Supported() ipv6Supported, err := ipv6Checker.IsIPv6Supported()
if err != nil { if err != nil {
return fmt.Errorf("checking for IPv6 support: %w", err) return fmt.Errorf("checking for IPv6 support: %w", err)
} }
if err = allSettings.Validate(storage, ipv6Supported); err != nil {
return fmt.Errorf("validating settings: %w", err)
}
// Unused by this CLI command // Unused by this CLI command
unzipper := (Unzipper)(nil) unzipper := (Unzipper)(nil)
client := (*http.Client)(nil) client := (*http.Client)(nil)

View File

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

View File

@@ -2,9 +2,9 @@ package settings
import ( import (
"fmt" "fmt"
"net" "net/netip"
"github.com/qdm12/gluetun/internal/configuration/settings/helpers" "github.com/qdm12/gosettings"
"github.com/qdm12/gotree" "github.com/qdm12/gotree"
) )
@@ -13,9 +13,9 @@ type DNS struct {
// ServerAddress is the DNS server to use inside // ServerAddress is the DNS server to use inside
// the Go program and for the system. // the Go program and for the system.
// It defaults to '127.0.0.1' to be used with the // It defaults to '127.0.0.1' to be used with the
// DoT server. It cannot be nil in the internal // DoT server. It cannot be the zero value in the internal
// state. // state.
ServerAddress net.IP ServerAddress netip.Addr
// KeepNameserver is true if the Docker DNS server // KeepNameserver is true if the Docker DNS server
// found in /etc/resolv.conf should be kept. // found in /etc/resolv.conf should be kept.
// Note settings this to true will go around the // Note settings this to true will go around the
@@ -31,7 +31,7 @@ type DNS struct {
func (d DNS) validate() (err error) { func (d DNS) validate() (err error) {
err = d.DoT.validate() err = d.DoT.validate()
if err != nil { if err != nil {
return fmt.Errorf("failed validating DoT settings: %w", err) return fmt.Errorf("validating DoT settings: %w", err)
} }
return nil return nil
@@ -39,8 +39,8 @@ func (d DNS) validate() (err error) {
func (d *DNS) Copy() (copied DNS) { func (d *DNS) Copy() (copied DNS) {
return DNS{ return DNS{
ServerAddress: helpers.CopyIP(d.ServerAddress), ServerAddress: d.ServerAddress,
KeepNameserver: helpers.CopyBoolPtr(d.KeepNameserver), KeepNameserver: gosettings.CopyPointer(d.KeepNameserver),
DoT: d.DoT.copy(), DoT: d.DoT.copy(),
} }
} }
@@ -48,8 +48,8 @@ func (d *DNS) Copy() (copied DNS) {
// mergeWith merges the other settings into any // mergeWith merges the other settings into any
// unset field of the receiver settings object. // unset field of the receiver settings object.
func (d *DNS) mergeWith(other DNS) { func (d *DNS) mergeWith(other DNS) {
d.ServerAddress = helpers.MergeWithIP(d.ServerAddress, other.ServerAddress) d.ServerAddress = gosettings.MergeWithValidator(d.ServerAddress, other.ServerAddress)
d.KeepNameserver = helpers.MergeWithBool(d.KeepNameserver, other.KeepNameserver) d.KeepNameserver = gosettings.MergeWithPointer(d.KeepNameserver, other.KeepNameserver)
d.DoT.mergeWith(other.DoT) d.DoT.mergeWith(other.DoT)
} }
@@ -57,15 +57,15 @@ func (d *DNS) mergeWith(other DNS) {
// settings object with any field set in the other // settings object with any field set in the other
// settings. // settings.
func (d *DNS) overrideWith(other DNS) { func (d *DNS) overrideWith(other DNS) {
d.ServerAddress = helpers.OverrideWithIP(d.ServerAddress, other.ServerAddress) d.ServerAddress = gosettings.OverrideWithValidator(d.ServerAddress, other.ServerAddress)
d.KeepNameserver = helpers.OverrideWithBool(d.KeepNameserver, other.KeepNameserver) d.KeepNameserver = gosettings.OverrideWithPointer(d.KeepNameserver, other.KeepNameserver)
d.DoT.overrideWith(other.DoT) d.DoT.overrideWith(other.DoT)
} }
func (d *DNS) setDefaults() { func (d *DNS) setDefaults() {
localhost := net.IPv4(127, 0, 0, 1) //nolint:gomnd localhost := netip.AddrFrom4([4]byte{127, 0, 0, 1})
d.ServerAddress = helpers.DefaultIP(d.ServerAddress, localhost) d.ServerAddress = gosettings.DefaultValidator(d.ServerAddress, localhost)
d.KeepNameserver = helpers.DefaultBool(d.KeepNameserver, false) d.KeepNameserver = gosettings.DefaultPointer(d.KeepNameserver, false)
d.DoT.setDefaults() d.DoT.setDefaults()
} }
@@ -76,7 +76,7 @@ func (d DNS) String() string {
func (d DNS) toLinesNode() (node *gotree.Node) { func (d DNS) toLinesNode() (node *gotree.Node) {
node = gotree.New("DNS settings:") node = gotree.New("DNS settings:")
node.Appendf("DNS server address to use: %s", d.ServerAddress) node.Appendf("DNS server address to use: %s", d.ServerAddress)
node.Appendf("Keep existing nameserver(s): %s", helpers.BoolPtrToYesNo(d.KeepNameserver)) node.Appendf("Keep existing nameserver(s): %s", gosettings.BoolToYesNo(d.KeepNameserver))
node.AppendNode(d.DoT.toLinesNode()) node.AppendNode(d.DoT.toLinesNode())
return node return node
} }

View File

@@ -3,12 +3,12 @@ package settings
import ( import (
"errors" "errors"
"fmt" "fmt"
"net/netip"
"regexp" "regexp"
"github.com/qdm12/dns/pkg/blacklist" "github.com/qdm12/dns/pkg/blacklist"
"github.com/qdm12/gluetun/internal/configuration/settings/helpers" "github.com/qdm12/gosettings"
"github.com/qdm12/gotree" "github.com/qdm12/gotree"
"inet.af/netaddr"
) )
// DNSBlacklist is settings for the DNS blacklist building. // DNSBlacklist is settings for the DNS blacklist building.
@@ -18,14 +18,14 @@ type DNSBlacklist struct {
BlockSurveillance *bool BlockSurveillance *bool
AllowedHosts []string AllowedHosts []string
AddBlockedHosts []string AddBlockedHosts []string
AddBlockedIPs []netaddr.IP AddBlockedIPs []netip.Addr
AddBlockedIPPrefixes []netaddr.IPPrefix AddBlockedIPPrefixes []netip.Prefix
} }
func (b *DNSBlacklist) setDefaults() { func (b *DNSBlacklist) setDefaults() {
b.BlockMalicious = helpers.DefaultBool(b.BlockMalicious, true) b.BlockMalicious = gosettings.DefaultPointer(b.BlockMalicious, true)
b.BlockAds = helpers.DefaultBool(b.BlockAds, false) b.BlockAds = gosettings.DefaultPointer(b.BlockAds, false)
b.BlockSurveillance = helpers.DefaultBool(b.BlockSurveillance, true) b.BlockSurveillance = gosettings.DefaultPointer(b.BlockSurveillance, true)
} }
var hostRegex = regexp.MustCompile(`^([a-zA-Z0-9]|[a-zA-Z0-9_][a-zA-Z0-9\-_]{0,61}[a-zA-Z0-9_])(\.([a-zA-Z0-9]|[a-zA-Z0-9_][a-zA-Z0-9\-_]{0,61}[a-zA-Z0-9]))*$`) //nolint:lll var hostRegex = regexp.MustCompile(`^([a-zA-Z0-9]|[a-zA-Z0-9_][a-zA-Z0-9\-_]{0,61}[a-zA-Z0-9_])(\.([a-zA-Z0-9]|[a-zA-Z0-9_][a-zA-Z0-9\-_]{0,61}[a-zA-Z0-9]))*$`) //nolint:lll
@@ -53,34 +53,34 @@ func (b DNSBlacklist) validate() (err error) {
func (b DNSBlacklist) copy() (copied DNSBlacklist) { func (b DNSBlacklist) copy() (copied DNSBlacklist) {
return DNSBlacklist{ return DNSBlacklist{
BlockMalicious: helpers.CopyBoolPtr(b.BlockMalicious), BlockMalicious: gosettings.CopyPointer(b.BlockMalicious),
BlockAds: helpers.CopyBoolPtr(b.BlockAds), BlockAds: gosettings.CopyPointer(b.BlockAds),
BlockSurveillance: helpers.CopyBoolPtr(b.BlockSurveillance), BlockSurveillance: gosettings.CopyPointer(b.BlockSurveillance),
AllowedHosts: helpers.CopyStringSlice(b.AllowedHosts), AllowedHosts: gosettings.CopySlice(b.AllowedHosts),
AddBlockedHosts: helpers.CopyStringSlice(b.AddBlockedHosts), AddBlockedHosts: gosettings.CopySlice(b.AddBlockedHosts),
AddBlockedIPs: helpers.CopyNetaddrIPsSlice(b.AddBlockedIPs), AddBlockedIPs: gosettings.CopySlice(b.AddBlockedIPs),
AddBlockedIPPrefixes: helpers.CopyIPPrefixSlice(b.AddBlockedIPPrefixes), AddBlockedIPPrefixes: gosettings.CopySlice(b.AddBlockedIPPrefixes),
} }
} }
func (b *DNSBlacklist) mergeWith(other DNSBlacklist) { func (b *DNSBlacklist) mergeWith(other DNSBlacklist) {
b.BlockMalicious = helpers.MergeWithBool(b.BlockMalicious, other.BlockMalicious) b.BlockMalicious = gosettings.MergeWithPointer(b.BlockMalicious, other.BlockMalicious)
b.BlockAds = helpers.MergeWithBool(b.BlockAds, other.BlockAds) b.BlockAds = gosettings.MergeWithPointer(b.BlockAds, other.BlockAds)
b.BlockSurveillance = helpers.MergeWithBool(b.BlockSurveillance, other.BlockSurveillance) b.BlockSurveillance = gosettings.MergeWithPointer(b.BlockSurveillance, other.BlockSurveillance)
b.AllowedHosts = helpers.MergeStringSlices(b.AllowedHosts, other.AllowedHosts) b.AllowedHosts = gosettings.MergeWithSlice(b.AllowedHosts, other.AllowedHosts)
b.AddBlockedHosts = helpers.MergeStringSlices(b.AddBlockedHosts, other.AddBlockedHosts) b.AddBlockedHosts = gosettings.MergeWithSlice(b.AddBlockedHosts, other.AddBlockedHosts)
b.AddBlockedIPs = helpers.MergeNetaddrIPsSlices(b.AddBlockedIPs, other.AddBlockedIPs) b.AddBlockedIPs = gosettings.MergeWithSlice(b.AddBlockedIPs, other.AddBlockedIPs)
b.AddBlockedIPPrefixes = helpers.MergeIPPrefixesSlices(b.AddBlockedIPPrefixes, other.AddBlockedIPPrefixes) b.AddBlockedIPPrefixes = gosettings.MergeWithSlice(b.AddBlockedIPPrefixes, other.AddBlockedIPPrefixes)
} }
func (b *DNSBlacklist) overrideWith(other DNSBlacklist) { func (b *DNSBlacklist) overrideWith(other DNSBlacklist) {
b.BlockMalicious = helpers.OverrideWithBool(b.BlockMalicious, other.BlockMalicious) b.BlockMalicious = gosettings.OverrideWithPointer(b.BlockMalicious, other.BlockMalicious)
b.BlockAds = helpers.OverrideWithBool(b.BlockAds, other.BlockAds) b.BlockAds = gosettings.OverrideWithPointer(b.BlockAds, other.BlockAds)
b.BlockSurveillance = helpers.OverrideWithBool(b.BlockSurveillance, other.BlockSurveillance) b.BlockSurveillance = gosettings.OverrideWithPointer(b.BlockSurveillance, other.BlockSurveillance)
b.AllowedHosts = helpers.OverrideWithStringSlice(b.AllowedHosts, other.AllowedHosts) b.AllowedHosts = gosettings.OverrideWithSlice(b.AllowedHosts, other.AllowedHosts)
b.AddBlockedHosts = helpers.OverrideWithStringSlice(b.AddBlockedHosts, other.AddBlockedHosts) b.AddBlockedHosts = gosettings.OverrideWithSlice(b.AddBlockedHosts, other.AddBlockedHosts)
b.AddBlockedIPs = helpers.OverrideWithNetaddrIPsSlice(b.AddBlockedIPs, other.AddBlockedIPs) b.AddBlockedIPs = gosettings.OverrideWithSlice(b.AddBlockedIPs, other.AddBlockedIPs)
b.AddBlockedIPPrefixes = helpers.OverrideWithIPPrefixesSlice(b.AddBlockedIPPrefixes, other.AddBlockedIPPrefixes) b.AddBlockedIPPrefixes = gosettings.OverrideWithSlice(b.AddBlockedIPPrefixes, other.AddBlockedIPPrefixes)
} }
func (b DNSBlacklist) ToBlacklistFormat() (settings blacklist.BuilderSettings, err error) { func (b DNSBlacklist) ToBlacklistFormat() (settings blacklist.BuilderSettings, err error) {
@@ -90,8 +90,8 @@ func (b DNSBlacklist) ToBlacklistFormat() (settings blacklist.BuilderSettings, e
BlockSurveillance: *b.BlockSurveillance, BlockSurveillance: *b.BlockSurveillance,
AllowedHosts: b.AllowedHosts, AllowedHosts: b.AllowedHosts,
AddBlockedHosts: b.AddBlockedHosts, AddBlockedHosts: b.AddBlockedHosts,
AddBlockedIPs: b.AddBlockedIPs, AddBlockedIPs: netipAddressesToNetaddrIPs(b.AddBlockedIPs),
AddBlockedIPPrefixes: b.AddBlockedIPPrefixes, AddBlockedIPPrefixes: netipPrefixesToNetaddrIPPrefixes(b.AddBlockedIPPrefixes),
}, nil }, nil
} }
@@ -102,9 +102,9 @@ func (b DNSBlacklist) String() string {
func (b DNSBlacklist) toLinesNode() (node *gotree.Node) { func (b DNSBlacklist) toLinesNode() (node *gotree.Node) {
node = gotree.New("DNS filtering settings:") node = gotree.New("DNS filtering settings:")
node.Appendf("Block malicious: %s", helpers.BoolPtrToYesNo(b.BlockMalicious)) node.Appendf("Block malicious: %s", gosettings.BoolToYesNo(b.BlockMalicious))
node.Appendf("Block ads: %s", helpers.BoolPtrToYesNo(b.BlockAds)) node.Appendf("Block ads: %s", gosettings.BoolToYesNo(b.BlockAds))
node.Appendf("Block surveillance: %s", helpers.BoolPtrToYesNo(b.BlockSurveillance)) node.Appendf("Block surveillance: %s", gosettings.BoolToYesNo(b.BlockSurveillance))
if len(b.AllowedHosts) > 0 { if len(b.AllowedHosts) > 0 {
allowedHostsNode := node.Appendf("Allowed hosts:") allowedHostsNode := node.Appendf("Allowed hosts:")

View File

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

View File

@@ -39,9 +39,11 @@ var (
ErrWireguardEndpointPortNotSet = errors.New("endpoint port is not set") ErrWireguardEndpointPortNotSet = errors.New("endpoint port is not set")
ErrWireguardEndpointPortSet = errors.New("endpoint port is set") ErrWireguardEndpointPortSet = errors.New("endpoint port is set")
ErrWireguardInterfaceAddressNotSet = errors.New("interface address is not set") ErrWireguardInterfaceAddressNotSet = errors.New("interface address is not set")
ErrWireguardInterfaceAddressIPv6 = errors.New("interface address is IPv6 but IPv6 is not supported")
ErrWireguardInterfaceNotValid = errors.New("interface name is not valid") ErrWireguardInterfaceNotValid = errors.New("interface name is not valid")
ErrWireguardPreSharedKeyNotSet = errors.New("pre-shared key is not set") ErrWireguardPreSharedKeyNotSet = errors.New("pre-shared key is not set")
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")
ErrWireguardImplementationNotValid = errors.New("implementation is not valid")
) )

View File

@@ -2,9 +2,9 @@ package settings
import ( import (
"fmt" "fmt"
"net" "net/netip"
"github.com/qdm12/gluetun/internal/configuration/settings/helpers" "github.com/qdm12/gosettings"
"github.com/qdm12/gotree" "github.com/qdm12/gotree"
) )
@@ -12,7 +12,7 @@ import (
type Firewall struct { type Firewall struct {
VPNInputPorts []uint16 VPNInputPorts []uint16
InputPorts []uint16 InputPorts []uint16
OutboundSubnets []net.IPNet OutboundSubnets []netip.Prefix
Enabled *bool Enabled *bool
Debug *bool Debug *bool
} }
@@ -40,11 +40,11 @@ func hasZeroPort(ports []uint16) (has bool) {
func (f *Firewall) copy() (copied Firewall) { func (f *Firewall) copy() (copied Firewall) {
return Firewall{ return Firewall{
VPNInputPorts: helpers.CopyUint16Slice(f.VPNInputPorts), VPNInputPorts: gosettings.CopySlice(f.VPNInputPorts),
InputPorts: helpers.CopyUint16Slice(f.InputPorts), InputPorts: gosettings.CopySlice(f.InputPorts),
OutboundSubnets: helpers.CopyIPNetSlice(f.OutboundSubnets), OutboundSubnets: gosettings.CopySlice(f.OutboundSubnets),
Enabled: helpers.CopyBoolPtr(f.Enabled), Enabled: gosettings.CopyPointer(f.Enabled),
Debug: helpers.CopyBoolPtr(f.Debug), Debug: gosettings.CopyPointer(f.Debug),
} }
} }
@@ -53,27 +53,27 @@ func (f *Firewall) copy() (copied Firewall) {
// It merges values of slices together, even if they // It merges values of slices together, even if they
// are set in the receiver settings. // are set in the receiver settings.
func (f *Firewall) mergeWith(other Firewall) { func (f *Firewall) mergeWith(other Firewall) {
f.VPNInputPorts = helpers.MergeUint16Slices(f.VPNInputPorts, other.VPNInputPorts) f.VPNInputPorts = gosettings.MergeWithSlice(f.VPNInputPorts, other.VPNInputPorts)
f.InputPorts = helpers.MergeUint16Slices(f.InputPorts, other.InputPorts) f.InputPorts = gosettings.MergeWithSlice(f.InputPorts, other.InputPorts)
f.OutboundSubnets = helpers.MergeIPNetsSlices(f.OutboundSubnets, other.OutboundSubnets) f.OutboundSubnets = gosettings.MergeWithSlice(f.OutboundSubnets, other.OutboundSubnets)
f.Enabled = helpers.MergeWithBool(f.Enabled, other.Enabled) f.Enabled = gosettings.MergeWithPointer(f.Enabled, other.Enabled)
f.Debug = helpers.MergeWithBool(f.Debug, other.Debug) f.Debug = gosettings.MergeWithPointer(f.Debug, other.Debug)
} }
// overrideWith overrides fields of the receiver // overrideWith overrides fields of the receiver
// settings object with any field set in the other // settings object with any field set in the other
// settings. // settings.
func (f *Firewall) overrideWith(other Firewall) { func (f *Firewall) overrideWith(other Firewall) {
f.VPNInputPorts = helpers.OverrideWithUint16Slice(f.VPNInputPorts, other.VPNInputPorts) f.VPNInputPorts = gosettings.OverrideWithSlice(f.VPNInputPorts, other.VPNInputPorts)
f.InputPorts = helpers.OverrideWithUint16Slice(f.InputPorts, other.InputPorts) f.InputPorts = gosettings.OverrideWithSlice(f.InputPorts, other.InputPorts)
f.OutboundSubnets = helpers.OverrideWithIPNetsSlice(f.OutboundSubnets, other.OutboundSubnets) f.OutboundSubnets = gosettings.OverrideWithSlice(f.OutboundSubnets, other.OutboundSubnets)
f.Enabled = helpers.OverrideWithBool(f.Enabled, other.Enabled) f.Enabled = gosettings.OverrideWithPointer(f.Enabled, other.Enabled)
f.Debug = helpers.OverrideWithBool(f.Debug, other.Debug) f.Debug = gosettings.OverrideWithPointer(f.Debug, other.Debug)
} }
func (f *Firewall) setDefaults() { func (f *Firewall) setDefaults() {
f.Enabled = helpers.DefaultBool(f.Enabled, true) f.Enabled = gosettings.DefaultPointer(f.Enabled, true)
f.Debug = helpers.DefaultBool(f.Debug, false) f.Debug = gosettings.DefaultPointer(f.Debug, false)
} }
func (f Firewall) String() string { func (f Firewall) String() string {
@@ -83,7 +83,7 @@ func (f Firewall) String() string {
func (f Firewall) toLinesNode() (node *gotree.Node) { func (f Firewall) toLinesNode() (node *gotree.Node) {
node = gotree.New("Firewall settings:") node = gotree.New("Firewall settings:")
node.Appendf("Enabled: %s", helpers.BoolPtrToYesNo(f.Enabled)) node.Appendf("Enabled: %s", gosettings.BoolToYesNo(f.Enabled))
if !*f.Enabled { if !*f.Enabled {
return node return node
} }
@@ -109,7 +109,8 @@ func (f Firewall) toLinesNode() (node *gotree.Node) {
if len(f.OutboundSubnets) > 0 { if len(f.OutboundSubnets) > 0 {
outboundSubnets := node.Appendf("Outbound subnets:") outboundSubnets := node.Appendf("Outbound subnets:")
for _, subnet := range f.OutboundSubnets { for _, subnet := range f.OutboundSubnets {
outboundSubnets.Appendf("%s", subnet) subnet := subnet
outboundSubnets.Appendf("%s", &subnet)
} }
} }

View File

@@ -5,7 +5,7 @@ import (
"os" "os"
"time" "time"
"github.com/qdm12/gluetun/internal/configuration/settings/helpers" "github.com/qdm12/gosettings"
"github.com/qdm12/gotree" "github.com/qdm12/gotree"
"github.com/qdm12/govalid/address" "github.com/qdm12/govalid/address"
) )
@@ -20,18 +20,24 @@ type Health struct {
// duration of the HTTP server. It defaults to 100 milliseconds. // duration of the HTTP server. It defaults to 100 milliseconds.
ReadHeaderTimeout time.Duration ReadHeaderTimeout time.Duration
// ReadTimeout is the HTTP read timeout duration of the // ReadTimeout is the HTTP read timeout duration of the
// HTTP server. It defaults to 500 milliseconds. // HTTP server. It defaults to 500 milliseconds.
ReadTimeout time.Duration ReadTimeout time.Duration
// TargetAddress is the address (host or host:port) // TargetAddress is the address (host or host:port)
// to TCP dial to periodically for the health check. // to TCP dial to periodically for the health check.
// It cannot be the empty string in the internal state. // It cannot be the empty string in the internal state.
TargetAddress string TargetAddress string
VPN HealthyWait // SuccessWait is the duration to wait to re-run the
// healthcheck after a successful healthcheck.
// It defaults to 5 seconds and cannot be zero in
// the internal state.
SuccessWait time.Duration
// VPN has health settings specific to the VPN loop.
VPN HealthyWait
} }
func (h Health) Validate() (err error) { func (h Health) Validate() (err error) {
uid := os.Getuid() uid := os.Getuid()
_, err = address.Validate(h.ServerAddress, err = address.Validate(h.ServerAddress,
address.OptionListening(uid)) address.OptionListening(uid))
if err != nil { if err != nil {
return fmt.Errorf("server listening address is not valid: %w", err) return fmt.Errorf("server listening address is not valid: %w", err)
@@ -51,6 +57,7 @@ func (h *Health) copy() (copied Health) {
ReadHeaderTimeout: h.ReadHeaderTimeout, ReadHeaderTimeout: h.ReadHeaderTimeout,
ReadTimeout: h.ReadTimeout, ReadTimeout: h.ReadTimeout,
TargetAddress: h.TargetAddress, TargetAddress: h.TargetAddress,
SuccessWait: h.SuccessWait,
VPN: h.VPN.copy(), VPN: h.VPN.copy(),
} }
} }
@@ -58,10 +65,11 @@ func (h *Health) copy() (copied Health) {
// MergeWith merges the other settings into any // MergeWith merges the other settings into any
// unset field of the receiver settings object. // unset field of the receiver settings object.
func (h *Health) MergeWith(other Health) { func (h *Health) MergeWith(other Health) {
h.ServerAddress = helpers.MergeWithString(h.ServerAddress, other.ServerAddress) h.ServerAddress = gosettings.MergeWithString(h.ServerAddress, other.ServerAddress)
h.ReadHeaderTimeout = helpers.MergeWithDuration(h.ReadHeaderTimeout, other.ReadHeaderTimeout) h.ReadHeaderTimeout = gosettings.MergeWithNumber(h.ReadHeaderTimeout, other.ReadHeaderTimeout)
h.ReadTimeout = helpers.MergeWithDuration(h.ReadTimeout, other.ReadTimeout) h.ReadTimeout = gosettings.MergeWithNumber(h.ReadTimeout, other.ReadTimeout)
h.TargetAddress = helpers.MergeWithString(h.TargetAddress, other.TargetAddress) h.TargetAddress = gosettings.MergeWithString(h.TargetAddress, other.TargetAddress)
h.SuccessWait = gosettings.MergeWithNumber(h.SuccessWait, other.SuccessWait)
h.VPN.mergeWith(other.VPN) h.VPN.mergeWith(other.VPN)
} }
@@ -69,20 +77,23 @@ func (h *Health) MergeWith(other Health) {
// settings object with any field set in the other // settings object with any field set in the other
// settings. // settings.
func (h *Health) OverrideWith(other Health) { func (h *Health) OverrideWith(other Health) {
h.ServerAddress = helpers.OverrideWithString(h.ServerAddress, other.ServerAddress) h.ServerAddress = gosettings.OverrideWithString(h.ServerAddress, other.ServerAddress)
h.ReadHeaderTimeout = helpers.OverrideWithDuration(h.ReadHeaderTimeout, other.ReadHeaderTimeout) h.ReadHeaderTimeout = gosettings.OverrideWithNumber(h.ReadHeaderTimeout, other.ReadHeaderTimeout)
h.ReadTimeout = helpers.OverrideWithDuration(h.ReadTimeout, other.ReadTimeout) h.ReadTimeout = gosettings.OverrideWithNumber(h.ReadTimeout, other.ReadTimeout)
h.TargetAddress = helpers.OverrideWithString(h.TargetAddress, other.TargetAddress) h.TargetAddress = gosettings.OverrideWithString(h.TargetAddress, other.TargetAddress)
h.SuccessWait = gosettings.OverrideWithNumber(h.SuccessWait, other.SuccessWait)
h.VPN.overrideWith(other.VPN) h.VPN.overrideWith(other.VPN)
} }
func (h *Health) SetDefaults() { func (h *Health) SetDefaults() {
h.ServerAddress = helpers.DefaultString(h.ServerAddress, "127.0.0.1:9999") h.ServerAddress = gosettings.DefaultString(h.ServerAddress, "127.0.0.1:9999")
const defaultReadHeaderTimeout = 100 * time.Millisecond const defaultReadHeaderTimeout = 100 * time.Millisecond
h.ReadHeaderTimeout = helpers.DefaultDuration(h.ReadHeaderTimeout, defaultReadHeaderTimeout) h.ReadHeaderTimeout = gosettings.DefaultNumber(h.ReadHeaderTimeout, defaultReadHeaderTimeout)
const defaultReadTimeout = 500 * time.Millisecond const defaultReadTimeout = 500 * time.Millisecond
h.ReadTimeout = helpers.DefaultDuration(h.ReadTimeout, defaultReadTimeout) h.ReadTimeout = gosettings.DefaultNumber(h.ReadTimeout, defaultReadTimeout)
h.TargetAddress = helpers.DefaultString(h.TargetAddress, "cloudflare.com:443") h.TargetAddress = gosettings.DefaultString(h.TargetAddress, "cloudflare.com:443")
const defaultSuccessWait = 5 * time.Second
h.SuccessWait = gosettings.DefaultNumber(h.SuccessWait, defaultSuccessWait)
h.VPN.setDefaults() h.VPN.setDefaults()
} }
@@ -94,6 +105,7 @@ func (h Health) toLinesNode() (node *gotree.Node) {
node = gotree.New("Health settings:") node = gotree.New("Health settings:")
node.Appendf("Server listening address: %s", h.ServerAddress) node.Appendf("Server listening address: %s", h.ServerAddress)
node.Appendf("Target address: %s", h.TargetAddress) node.Appendf("Target address: %s", h.TargetAddress)
node.Appendf("Duration to wait after success: %s", h.SuccessWait)
node.Appendf("Read header timeout: %s", h.ReadHeaderTimeout) node.Appendf("Read header timeout: %s", h.ReadHeaderTimeout)
node.Appendf("Read timeout: %s", h.ReadTimeout) node.Appendf("Read timeout: %s", h.ReadTimeout)
node.AppendNode(h.VPN.toLinesNode("VPN")) node.AppendNode(h.VPN.toLinesNode("VPN"))

View File

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

View File

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

View File

@@ -1,199 +0,0 @@
package helpers
import (
"net"
"time"
"github.com/qdm12/log"
"inet.af/netaddr"
)
func CopyStringPtr(original *string) (copied *string) {
if original == nil {
return nil
}
copied = new(string)
*copied = *original
return copied
}
func CopyBoolPtr(original *bool) (copied *bool) {
if original == nil {
return nil
}
copied = new(bool)
*copied = *original
return copied
}
func CopyUint8Ptr(original *uint8) (copied *uint8) {
if original == nil {
return nil
}
copied = new(uint8)
*copied = *original
return copied
}
func CopyUint16Ptr(original *uint16) (copied *uint16) {
if original == nil {
return nil
}
copied = new(uint16)
*copied = *original
return copied
}
func CopyUint32Ptr(original *uint32) (copied *uint32) {
if original == nil {
return nil
}
copied = new(uint32)
*copied = *original
return copied
}
func CopyIntPtr(original *int) (copied *int) {
if original == nil {
return nil
}
copied = new(int)
*copied = *original
return copied
}
func CopyDurationPtr(original *time.Duration) (copied *time.Duration) {
if original == nil {
return nil
}
copied = new(time.Duration)
*copied = *original
return copied
}
func CopyLogLevelPtr(original *log.Level) (copied *log.Level) {
if original == nil {
return nil
}
copied = new(log.Level)
*copied = *original
return copied
}
func CopyIP(original net.IP) (copied net.IP) {
if original == nil {
return nil
}
copied = make(net.IP, len(original))
copy(copied, original)
return copied
}
func CopyIPNet(original net.IPNet) (copied net.IPNet) {
if original.IP != nil {
copied.IP = make(net.IP, len(original.IP))
copy(copied.IP, original.IP)
}
if original.Mask != nil {
copied.Mask = make(net.IPMask, len(original.Mask))
copy(copied.Mask, original.Mask)
}
return copied
}
func CopyIPNetPtr(original *net.IPNet) (copied *net.IPNet) {
if original == nil {
return nil
}
copied = new(net.IPNet)
*copied = CopyIPNet(*original)
return copied
}
func CopyNetaddrIP(original netaddr.IP) (copied netaddr.IP) {
b, err := original.MarshalBinary()
if err != nil {
panic(err)
}
err = copied.UnmarshalBinary(b)
if err != nil {
panic(err)
}
return copied
}
func CopyIPPrefix(original netaddr.IPPrefix) (copied netaddr.IPPrefix) {
b, err := original.MarshalText()
if err != nil {
panic(err)
}
err = copied.UnmarshalText(b)
if err != nil {
panic(err)
}
return copied
}
func CopyStringSlice(original []string) (copied []string) {
if original == nil {
return nil
}
copied = make([]string, len(original))
copy(copied, original)
return copied
}
func CopyUint16Slice(original []uint16) (copied []uint16) {
if original == nil {
return nil
}
copied = make([]uint16, len(original))
copy(copied, original)
return copied
}
func CopyIPNetSlice(original []net.IPNet) (copied []net.IPNet) {
if original == nil {
return nil
}
copied = make([]net.IPNet, len(original))
for i := range original {
copied[i] = CopyIPNet(original[i])
}
return copied
}
func CopyIPPrefixSlice(original []netaddr.IPPrefix) (copied []netaddr.IPPrefix) {
if original == nil {
return nil
}
copied = make([]netaddr.IPPrefix, len(original))
for i := range original {
copied[i] = CopyIPPrefix(original[i])
}
return copied
}
func CopyNetaddrIPsSlice(original []netaddr.IP) (copied []netaddr.IP) {
if original == nil {
return nil
}
copied = make([]netaddr.IP, len(original))
for i := range original {
copied[i] = CopyNetaddrIP(original[i])
}
return copied
}

View File

@@ -1,110 +0,0 @@
package helpers
import (
"net"
"time"
"github.com/qdm12/log"
)
func DefaultInt(existing *int, defaultValue int) (
result *int) {
if existing != nil {
return existing
}
result = new(int)
*result = defaultValue
return result
}
func DefaultUint8(existing *uint8, defaultValue uint8) (
result *uint8) {
if existing != nil {
return existing
}
result = new(uint8)
*result = defaultValue
return result
}
func DefaultUint16(existing *uint16, defaultValue uint16) (
result *uint16) {
if existing != nil {
return existing
}
result = new(uint16)
*result = defaultValue
return result
}
func DefaultUint32(existing *uint32, defaultValue uint32) (
result *uint32) {
if existing != nil {
return existing
}
result = new(uint32)
*result = defaultValue
return result
}
func DefaultBool(existing *bool, defaultValue bool) (
result *bool) {
if existing != nil {
return existing
}
result = new(bool)
*result = defaultValue
return result
}
func DefaultString(existing string, defaultValue string) (
result string) {
if existing != "" {
return existing
}
return defaultValue
}
func DefaultStringPtr(existing *string, defaultValue string) (result *string) {
if existing != nil {
return existing
}
result = new(string)
*result = defaultValue
return result
}
func DefaultDuration(existing time.Duration,
defaultValue time.Duration) (result time.Duration) {
if existing != 0 {
return existing
}
return defaultValue
}
func DefaultDurationPtr(existing *time.Duration,
defaultValue time.Duration) (result *time.Duration) {
if existing != nil {
return existing
}
result = new(time.Duration)
*result = defaultValue
return result
}
func DefaultLogLevel(existing *log.Level,
defaultValue log.Level) (result *log.Level) {
if existing != nil {
return existing
}
result = new(log.Level)
*result = defaultValue
return result
}
func DefaultIP(existing net.IP, defaultValue net.IP) (
result net.IP) {
if existing != nil {
return existing
}
return defaultValue
}

View File

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

View File

@@ -1,266 +0,0 @@
package helpers
import (
"net"
"net/http"
"time"
"github.com/qdm12/log"
"inet.af/netaddr"
)
func MergeWithBool(existing, other *bool) (result *bool) {
if existing != nil {
return existing
} else if other == nil {
return nil
}
result = new(bool)
*result = *other
return result
}
func MergeWithString(existing, other string) (result string) {
if existing != "" {
return existing
}
return other
}
func MergeWithInt(existing, other int) (result int) {
if existing != 0 {
return existing
}
return other
}
func MergeWithFloat64(existing, other float64) (result float64) {
if existing != 0 {
return existing
}
return other
}
func MergeWithStringPtr(existing, other *string) (result *string) {
if existing != nil {
return existing
} else if other == nil {
return nil
}
result = new(string)
*result = *other
return result
}
func MergeWithIntPtr(existing, other *int) (result *int) {
if existing != nil {
return existing
} else if other == nil {
return nil
}
result = new(int)
*result = *other
return result
}
func MergeWithUint8(existing, other *uint8) (result *uint8) {
if existing != nil {
return existing
} else if other == nil {
return nil
}
result = new(uint8)
*result = *other
return result
}
func MergeWithUint16(existing, other *uint16) (result *uint16) {
if existing != nil {
return existing
} else if other == nil {
return nil
}
result = new(uint16)
*result = *other
return result
}
func MergeWithUint32(existing, other *uint32) (result *uint32) {
if existing != nil {
return existing
} else if other == nil {
return nil
}
result = new(uint32)
*result = *other
return result
}
func MergeWithIP(existing, other net.IP) (result net.IP) {
if existing != nil {
return existing
} else if other == nil {
return nil
}
result = make(net.IP, len(other))
copy(result, other)
return result
}
func MergeWithDuration(existing, other time.Duration) (result time.Duration) {
if existing != 0 {
return existing
}
return other
}
func MergeWithDurationPtr(existing, other *time.Duration) (result *time.Duration) {
if existing != nil {
return existing
}
return other
}
func MergeWithLogLevel(existing, other *log.Level) (result *log.Level) {
if existing != nil {
return existing
} else if other == nil {
return nil
}
result = new(log.Level)
*result = *other
return result
}
func MergeWithHTTPHandler(existing, other http.Handler) (result http.Handler) {
if existing != nil {
return existing
}
return other
}
func MergeStringSlices(a, b []string) (result []string) {
if a == nil && b == nil {
return nil
}
seen := make(map[string]struct{}, len(a)+len(b))
result = make([]string, 0, len(a)+len(b))
for _, s := range a {
if _, ok := seen[s]; ok {
continue // duplicate
}
result = append(result, s)
seen[s] = struct{}{}
}
for _, s := range b {
if _, ok := seen[s]; ok {
continue // duplicate
}
result = append(result, s)
seen[s] = struct{}{}
}
return result
}
func MergeUint16Slices(a, b []uint16) (result []uint16) {
if a == nil && b == nil {
return nil
}
seen := make(map[uint16]struct{}, len(a)+len(b))
result = make([]uint16, 0, len(a)+len(b))
for _, n := range a {
if _, ok := seen[n]; ok {
continue // duplicate
}
result = append(result, n)
seen[n] = struct{}{}
}
for _, n := range b {
if _, ok := seen[n]; ok {
continue // duplicate
}
result = append(result, n)
seen[n] = struct{}{}
}
return result
}
func MergeIPNetsSlices(a, b []net.IPNet) (result []net.IPNet) {
if a == nil && b == nil {
return nil
}
seen := make(map[string]struct{}, len(a)+len(b))
result = make([]net.IPNet, 0, len(a)+len(b))
for _, ipNet := range a {
key := ipNet.String()
if _, ok := seen[key]; ok {
continue // duplicate
}
result = append(result, ipNet)
seen[key] = struct{}{}
}
for _, ipNet := range b {
key := ipNet.String()
if _, ok := seen[key]; ok {
continue // duplicate
}
result = append(result, ipNet)
seen[key] = struct{}{}
}
return result
}
func MergeNetaddrIPsSlices(a, b []netaddr.IP) (result []netaddr.IP) {
if a == nil && b == nil {
return nil
}
seen := make(map[string]struct{}, len(a)+len(b))
result = make([]netaddr.IP, 0, len(a)+len(b))
for _, ip := range a {
key := ip.String()
if _, ok := seen[key]; ok {
continue // duplicate
}
result = append(result, ip)
seen[key] = struct{}{}
}
for _, ip := range b {
key := ip.String()
if _, ok := seen[key]; ok {
continue // duplicate
}
result = append(result, ip)
seen[key] = struct{}{}
}
return result
}
func MergeIPPrefixesSlices(a, b []netaddr.IPPrefix) (result []netaddr.IPPrefix) {
if a == nil && b == nil {
return nil
}
seen := make(map[string]struct{}, len(a)+len(b))
result = make([]netaddr.IPPrefix, 0, len(a)+len(b))
for _, ipPrefix := range a {
key := ipPrefix.String()
if _, ok := seen[key]; ok {
continue // duplicate
}
result = append(result, ipPrefix)
seen[key] = struct{}{}
}
for _, ipPrefix := range b {
key := ipPrefix.String()
if _, ok := seen[key]; ok {
continue // duplicate
}
result = append(result, ipPrefix)
seen[key] = struct{}{}
}
return result
}

View File

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

View File

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

View File

@@ -1,173 +0,0 @@
package helpers
import (
"net"
"net/http"
"time"
"github.com/qdm12/log"
"inet.af/netaddr"
)
func OverrideWithBool(existing, other *bool) (result *bool) {
if other == nil {
return existing
}
result = new(bool)
*result = *other
return result
}
func OverrideWithString(existing, other string) (result string) {
if other == "" {
return existing
}
return other
}
func OverrideWithInt(existing, other int) (result int) {
if other == 0 {
return existing
}
return other
}
func OverrideWithFloat64(existing, other float64) (result float64) {
if other == 0 {
return existing
}
return other
}
func OverrideWithStringPtr(existing, other *string) (result *string) {
if other == nil {
return existing
}
result = new(string)
*result = *other
return result
}
func OverrideWithIntPtr(existing, other *int) (result *int) {
if other == nil {
return existing
}
result = new(int)
*result = *other
return result
}
func OverrideWithUint8(existing, other *uint8) (result *uint8) {
if other == nil {
return existing
}
result = new(uint8)
*result = *other
return result
}
func OverrideWithUint16(existing, other *uint16) (result *uint16) {
if other == nil {
return existing
}
result = new(uint16)
*result = *other
return result
}
func OverrideWithUint32(existing, other *uint32) (result *uint32) {
if other == nil {
return existing
}
result = new(uint32)
*result = *other
return result
}
func OverrideWithIP(existing, other net.IP) (result net.IP) {
if other == nil {
return existing
}
result = make(net.IP, len(other))
copy(result, other)
return result
}
func OverrideWithDuration(existing, other time.Duration) (
result time.Duration) {
if other == 0 {
return existing
}
return other
}
func OverrideWithDurationPtr(existing, other *time.Duration) (
result *time.Duration) {
if other == nil {
return existing
}
result = new(time.Duration)
*result = *other
return result
}
func OverrideWithLogLevel(existing, other *log.Level) (result *log.Level) {
if other == nil {
return existing
}
result = new(log.Level)
*result = *other
return result
}
func OverrideWithHTTPHandler(existing, other http.Handler) (result http.Handler) {
if other != nil {
return other
}
return existing
}
func OverrideWithStringSlice(existing, other []string) (result []string) {
if other == nil {
return existing
}
result = make([]string, len(other))
copy(result, other)
return result
}
func OverrideWithUint16Slice(existing, other []uint16) (result []uint16) {
if other == nil {
return existing
}
result = make([]uint16, len(other))
copy(result, other)
return result
}
func OverrideWithIPNetsSlice(existing, other []net.IPNet) (result []net.IPNet) {
if other == nil {
return existing
}
result = make([]net.IPNet, len(other))
copy(result, other)
return result
}
func OverrideWithNetaddrIPsSlice(existing, other []netaddr.IP) (result []netaddr.IP) {
if other == nil {
return existing
}
result = make([]netaddr.IP, len(other))
copy(result, other)
return result
}
func OverrideWithIPPrefixesSlice(existing, other []netaddr.IPPrefix) (result []netaddr.IPPrefix) {
if other == nil {
return existing
}
result = make([]netaddr.IPPrefix, len(other))
copy(result, other)
return result
}

View File

@@ -1,11 +0,0 @@
package helpers
import "time"
// StringPtr returns a pointer to the string value
// passed as argument.
func StringPtr(s string) *string { return &s }
// DurationPtr returns a pointer to the duration value
// passed as argument.
func DurationPtr(d time.Duration) *time.Duration { return &d }

View File

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

View File

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

View File

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

View File

@@ -0,0 +1,36 @@
package settings
import (
"net/netip"
"inet.af/netaddr"
)
func netipAddressToNetaddrIP(address netip.Addr) (ip netaddr.IP) {
if address.Is4() {
return netaddr.IPFrom4(address.As4())
}
return netaddr.IPFrom16(address.As16())
}
func netipAddressesToNetaddrIPs(addresses []netip.Addr) (ips []netaddr.IP) {
ips = make([]netaddr.IP, len(addresses))
for i := range addresses {
ips[i] = netipAddressToNetaddrIP(addresses[i])
}
return ips
}
func netipPrefixToNetaddrIPPrefix(prefix netip.Prefix) (ipPrefix netaddr.IPPrefix) {
netaddrIP := netipAddressToNetaddrIP(prefix.Addr())
bits := prefix.Bits()
return netaddr.IPPrefixFrom(netaddrIP, uint8(bits))
}
func netipPrefixesToNetaddrIPPrefixes(prefixes []netip.Prefix) (ipPrefixes []netaddr.IPPrefix) {
ipPrefixes = make([]netaddr.IPPrefix, len(prefixes))
for i := range ipPrefixes {
ipPrefixes[i] = netipPrefixToNetaddrIPPrefix(prefixes[i])
}
return ipPrefixes
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -3,6 +3,9 @@ package settings
import ( import (
"fmt" "fmt"
"github.com/qdm12/gluetun/internal/configuration/settings/helpers"
"github.com/qdm12/gluetun/internal/constants/providers"
"github.com/qdm12/gluetun/internal/constants/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/gotree" "github.com/qdm12/gotree"
@@ -31,7 +34,7 @@ type Storage interface {
// Validate validates all the settings and returns an error // Validate validates all the settings and returns an error
// if one of them is not valid. // if one of them is not valid.
// TODO v4 remove pointer for receiver (because of Surfshark). // TODO v4 remove pointer for receiver (because of Surfshark).
func (s *Settings) Validate(storage Storage) (err error) { func (s *Settings) Validate(storage Storage, ipv6Supported bool) (err error) {
nameToValidation := map[string]func() error{ nameToValidation := map[string]func() error{
"control server": s.ControlServer.validate, "control server": s.ControlServer.validate,
"dns": s.DNS.validate, "dns": s.DNS.validate,
@@ -46,7 +49,7 @@ func (s *Settings) Validate(storage Storage) (err error) {
"version": s.Version.validate, "version": s.Version.validate,
// Pprof validation done in pprof constructor // Pprof validation done in pprof constructor
"VPN": func() error { "VPN": func() error {
return s.VPN.Validate(storage) return s.VPN.Validate(storage, ipv6Supported)
}, },
} }
@@ -95,7 +98,7 @@ func (s *Settings) MergeWith(other Settings) {
} }
func (s *Settings) OverrideWith(other Settings, func (s *Settings) OverrideWith(other Settings,
storage Storage) (err error) { storage Storage, ipv6Supported bool) (err error) {
patchedSettings := s.copy() patchedSettings := s.copy()
patchedSettings.ControlServer.overrideWith(other.ControlServer) patchedSettings.ControlServer.overrideWith(other.ControlServer)
patchedSettings.DNS.overrideWith(other.DNS) patchedSettings.DNS.overrideWith(other.DNS)
@@ -110,7 +113,7 @@ func (s *Settings) OverrideWith(other Settings,
patchedSettings.Version.overrideWith(other.Version) patchedSettings.Version.overrideWith(other.Version)
patchedSettings.VPN.OverrideWith(other.VPN) patchedSettings.VPN.OverrideWith(other.VPN)
patchedSettings.Pprof.OverrideWith(other.Pprof) patchedSettings.Pprof.OverrideWith(other.Pprof)
err = patchedSettings.Validate(storage) err = patchedSettings.Validate(storage, ipv6Supported)
if err != nil { if err != nil {
return err return err
} }
@@ -157,3 +160,24 @@ func (s Settings) toLinesNode() (node *gotree.Node) {
return node return node
} }
func (s Settings) Warnings() (warnings []string) {
if *s.VPN.Provider.Name == providers.HideMyAss {
warnings = append(warnings, "HideMyAss dropped support for Linux OpenVPN "+
" so this will likely not work anymore. See https://github.com/qdm12/gluetun/issues/1498.")
}
if helpers.IsOneOf(*s.VPN.Provider.Name, providers.SlickVPN) &&
s.VPN.Type == vpn.OpenVPN {
warnings = append(warnings, "OpenVPN 2.5 uses OpenSSL 3 "+
"which prohibits the usage of weak security in today's standards. "+
*s.VPN.Provider.Name+" uses weak security which is out "+
"of Gluetun's control so the only workaround is to allow such weaknesses "+
`using the OpenVPN option tls-cipher "DEFAULT:@SECLEVEL=0". `+
"You might want to reach to your provider so they upgrade their certificates. "+
"Once this is done, you will have to let the Gluetun maintainers know "+
"by creating an issue, attaching the new certificate and we will update Gluetun.")
}
return warnings
}

View File

@@ -66,6 +66,7 @@ func Test_Settings_String(t *testing.T) {
├── Health settings: ├── Health settings:
| ├── Server listening address: 127.0.0.1:9999 | ├── Server listening address: 127.0.0.1:9999
| ├── Target address: cloudflare.com:443 | ├── Target address: cloudflare.com:443
| ├── Duration to wait after success: 5s
| ├── Read header timeout: 100ms | ├── Read header timeout: 100ms
| ├── Read timeout: 500ms | ├── Read timeout: 500ms
| └── VPN wait durations: | └── VPN wait durations:

View File

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

View File

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

View File

@@ -3,25 +3,24 @@ package settings
import ( import (
"errors" "errors"
"fmt" "fmt"
"net" "net/netip"
"github.com/qdm12/dns/pkg/provider" "github.com/qdm12/dns/pkg/provider"
"github.com/qdm12/dns/pkg/unbound" "github.com/qdm12/dns/pkg/unbound"
"github.com/qdm12/gluetun/internal/configuration/settings/helpers" "github.com/qdm12/gosettings"
"github.com/qdm12/gotree" "github.com/qdm12/gotree"
"inet.af/netaddr"
) )
// Unbound is settings for the Unbound program. // Unbound is settings for the Unbound program.
type Unbound struct { type Unbound struct {
Providers []string Providers []string `json:"providers"`
Caching *bool Caching *bool `json:"caching"`
IPv6 *bool IPv6 *bool `json:"ipv6"`
VerbosityLevel *uint8 VerbosityLevel *uint8 `json:"verbosity_level"`
VerbosityDetailsLevel *uint8 VerbosityDetailsLevel *uint8 `json:"verbosity_details_level"`
ValidationLogLevel *uint8 ValidationLogLevel *uint8 `json:"validation_log_level"`
Username string Username string `json:"username"`
Allowed []netaddr.IPPrefix Allowed []netip.Prefix `json:"allowed"`
} }
func (u *Unbound) setDefaults() { func (u *Unbound) setDefaults() {
@@ -31,26 +30,26 @@ func (u *Unbound) setDefaults() {
} }
} }
u.Caching = helpers.DefaultBool(u.Caching, true) u.Caching = gosettings.DefaultPointer(u.Caching, true)
u.IPv6 = helpers.DefaultBool(u.IPv6, false) u.IPv6 = gosettings.DefaultPointer(u.IPv6, false)
const defaultVerbosityLevel = 1 const defaultVerbosityLevel = 1
u.VerbosityLevel = helpers.DefaultUint8(u.VerbosityLevel, defaultVerbosityLevel) u.VerbosityLevel = gosettings.DefaultPointer(u.VerbosityLevel, defaultVerbosityLevel)
const defaultVerbosityDetailsLevel = 0 const defaultVerbosityDetailsLevel = 0
u.VerbosityDetailsLevel = helpers.DefaultUint8(u.VerbosityDetailsLevel, defaultVerbosityDetailsLevel) u.VerbosityDetailsLevel = gosettings.DefaultPointer(u.VerbosityDetailsLevel, defaultVerbosityDetailsLevel)
const defaultValidationLogLevel = 0 const defaultValidationLogLevel = 0
u.ValidationLogLevel = helpers.DefaultUint8(u.ValidationLogLevel, defaultValidationLogLevel) u.ValidationLogLevel = gosettings.DefaultPointer(u.ValidationLogLevel, defaultValidationLogLevel)
if u.Allowed == nil { if u.Allowed == nil {
u.Allowed = []netaddr.IPPrefix{ u.Allowed = []netip.Prefix{
netaddr.IPPrefixFrom(netaddr.IPv4(0, 0, 0, 0), 0), netip.PrefixFrom(netip.AddrFrom4([4]byte{}), 0),
netaddr.IPPrefixFrom(netaddr.IPv6Raw([16]byte{}), 0), netip.PrefixFrom(netip.AddrFrom16([16]byte{}), 0),
} }
} }
u.Username = helpers.DefaultString(u.Username, "root") u.Username = gosettings.DefaultString(u.Username, "root")
} }
var ( var (
@@ -95,37 +94,37 @@ func (u Unbound) validate() (err error) {
func (u Unbound) copy() (copied Unbound) { func (u Unbound) copy() (copied Unbound) {
return Unbound{ return Unbound{
Providers: helpers.CopyStringSlice(u.Providers), Providers: gosettings.CopySlice(u.Providers),
Caching: helpers.CopyBoolPtr(u.Caching), Caching: gosettings.CopyPointer(u.Caching),
IPv6: helpers.CopyBoolPtr(u.IPv6), IPv6: gosettings.CopyPointer(u.IPv6),
VerbosityLevel: helpers.CopyUint8Ptr(u.VerbosityLevel), VerbosityLevel: gosettings.CopyPointer(u.VerbosityLevel),
VerbosityDetailsLevel: helpers.CopyUint8Ptr(u.VerbosityDetailsLevel), VerbosityDetailsLevel: gosettings.CopyPointer(u.VerbosityDetailsLevel),
ValidationLogLevel: helpers.CopyUint8Ptr(u.ValidationLogLevel), ValidationLogLevel: gosettings.CopyPointer(u.ValidationLogLevel),
Username: u.Username, Username: u.Username,
Allowed: helpers.CopyIPPrefixSlice(u.Allowed), Allowed: gosettings.CopySlice(u.Allowed),
} }
} }
func (u *Unbound) mergeWith(other Unbound) { func (u *Unbound) mergeWith(other Unbound) {
u.Providers = helpers.MergeStringSlices(u.Providers, other.Providers) u.Providers = gosettings.MergeWithSlice(u.Providers, other.Providers)
u.Caching = helpers.MergeWithBool(u.Caching, other.Caching) u.Caching = gosettings.MergeWithPointer(u.Caching, other.Caching)
u.IPv6 = helpers.MergeWithBool(u.IPv6, other.IPv6) u.IPv6 = gosettings.MergeWithPointer(u.IPv6, other.IPv6)
u.VerbosityLevel = helpers.MergeWithUint8(u.VerbosityLevel, other.VerbosityLevel) u.VerbosityLevel = gosettings.MergeWithPointer(u.VerbosityLevel, other.VerbosityLevel)
u.VerbosityDetailsLevel = helpers.MergeWithUint8(u.VerbosityDetailsLevel, other.VerbosityDetailsLevel) u.VerbosityDetailsLevel = gosettings.MergeWithPointer(u.VerbosityDetailsLevel, other.VerbosityDetailsLevel)
u.ValidationLogLevel = helpers.MergeWithUint8(u.ValidationLogLevel, other.ValidationLogLevel) u.ValidationLogLevel = gosettings.MergeWithPointer(u.ValidationLogLevel, other.ValidationLogLevel)
u.Username = helpers.MergeWithString(u.Username, other.Username) u.Username = gosettings.MergeWithString(u.Username, other.Username)
u.Allowed = helpers.MergeIPPrefixesSlices(u.Allowed, other.Allowed) u.Allowed = gosettings.MergeWithSlice(u.Allowed, other.Allowed)
} }
func (u *Unbound) overrideWith(other Unbound) { func (u *Unbound) overrideWith(other Unbound) {
u.Providers = helpers.OverrideWithStringSlice(u.Providers, other.Providers) u.Providers = gosettings.OverrideWithSlice(u.Providers, other.Providers)
u.Caching = helpers.OverrideWithBool(u.Caching, other.Caching) u.Caching = gosettings.OverrideWithPointer(u.Caching, other.Caching)
u.IPv6 = helpers.OverrideWithBool(u.IPv6, other.IPv6) u.IPv6 = gosettings.OverrideWithPointer(u.IPv6, other.IPv6)
u.VerbosityLevel = helpers.OverrideWithUint8(u.VerbosityLevel, other.VerbosityLevel) u.VerbosityLevel = gosettings.OverrideWithPointer(u.VerbosityLevel, other.VerbosityLevel)
u.VerbosityDetailsLevel = helpers.OverrideWithUint8(u.VerbosityDetailsLevel, other.VerbosityDetailsLevel) u.VerbosityDetailsLevel = gosettings.OverrideWithPointer(u.VerbosityDetailsLevel, other.VerbosityDetailsLevel)
u.ValidationLogLevel = helpers.OverrideWithUint8(u.ValidationLogLevel, other.ValidationLogLevel) u.ValidationLogLevel = gosettings.OverrideWithPointer(u.ValidationLogLevel, other.ValidationLogLevel)
u.Username = helpers.OverrideWithString(u.Username, other.Username) u.Username = gosettings.OverrideWithString(u.Username, other.Username)
u.Allowed = helpers.OverrideWithIPPrefixesSlice(u.Allowed, other.Allowed) u.Allowed = gosettings.OverrideWithSlice(u.Allowed, other.Allowed)
} }
func (u Unbound) ToUnboundFormat() (settings unbound.Settings, err error) { func (u Unbound) ToUnboundFormat() (settings unbound.Settings, err error) {
@@ -149,20 +148,30 @@ func (u Unbound) ToUnboundFormat() (settings unbound.Settings, err error) {
VerbosityDetailsLevel: *u.VerbosityDetailsLevel, VerbosityDetailsLevel: *u.VerbosityDetailsLevel,
ValidationLogLevel: *u.ValidationLogLevel, ValidationLogLevel: *u.ValidationLogLevel,
AccessControl: unbound.AccessControlSettings{ AccessControl: unbound.AccessControlSettings{
Allowed: u.Allowed, Allowed: netipPrefixesToNetaddrIPPrefixes(u.Allowed),
}, },
Username: u.Username, Username: u.Username,
}, nil }, nil
} }
func (u Unbound) GetFirstPlaintextIPv4() (ipv4 net.IP, err error) { var (
ErrConvertingNetip = errors.New("converting net.IP to netip.Addr failed")
)
func (u Unbound) GetFirstPlaintextIPv4() (ipv4 netip.Addr, err error) {
s := u.Providers[0] s := u.Providers[0]
provider, err := provider.Parse(s) provider, err := provider.Parse(s)
if err != nil { if err != nil {
return nil, err return ipv4, err
} }
return provider.DNS().IPv4[0], nil ip := provider.DNS().IPv4[0]
ipv4, ok := netip.AddrFromSlice(ip)
if !ok {
return ipv4, fmt.Errorf("%w: for ip %s (%#v)",
ErrConvertingNetip, ip, ip)
}
return ipv4.Unmap(), nil
} }
func (u Unbound) String() string { func (u Unbound) String() string {
@@ -177,8 +186,8 @@ func (u Unbound) toLinesNode() (node *gotree.Node) {
authServers.Appendf(provider) authServers.Appendf(provider)
} }
node.Appendf("Caching: %s", helpers.BoolPtrToYesNo(u.Caching)) node.Appendf("Caching: %s", gosettings.BoolToYesNo(u.Caching))
node.Appendf("IPv6: %s", helpers.BoolPtrToYesNo(u.IPv6)) node.Appendf("IPv6: %s", gosettings.BoolToYesNo(u.IPv6))
node.Appendf("Verbosity level: %d", *u.VerbosityLevel) node.Appendf("Verbosity level: %d", *u.VerbosityLevel)
node.Appendf("Verbosity details level: %d", *u.VerbosityDetailsLevel) node.Appendf("Verbosity details level: %d", *u.VerbosityDetailsLevel)
node.Appendf("Validation log level: %d", *u.ValidationLogLevel) node.Appendf("Validation log level: %d", *u.ValidationLogLevel)

View File

@@ -2,11 +2,11 @@ package settings
import ( import (
"encoding/json" "encoding/json"
"net/netip"
"testing" "testing"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
"inet.af/netaddr"
) )
func Test_Unbound_JSON(t *testing.T) { func Test_Unbound_JSON(t *testing.T) {
@@ -20,18 +20,18 @@ func Test_Unbound_JSON(t *testing.T) {
VerbosityDetailsLevel: nil, VerbosityDetailsLevel: nil,
ValidationLogLevel: uint8Ptr(0), ValidationLogLevel: uint8Ptr(0),
Username: "user", Username: "user",
Allowed: []netaddr.IPPrefix{ Allowed: []netip.Prefix{
netaddr.IPPrefixFrom(netaddr.IPv4(0, 0, 0, 0), 0), netip.PrefixFrom(netip.AddrFrom4([4]byte{}), 0),
netaddr.IPPrefixFrom(netaddr.IPv6Raw([16]byte{}), 0), netip.PrefixFrom(netip.AddrFrom16([16]byte{}), 0),
}, },
} }
b, err := json.Marshal(settings) b, err := json.Marshal(settings)
require.NoError(t, err) require.NoError(t, err)
const expected = `{"Providers":["cloudflare"],"Caching":true,"IPv6":false,` + const expected = `{"providers":["cloudflare"],"caching":true,"ipv6":false,` +
`"VerbosityLevel":1,"VerbosityDetailsLevel":null,"ValidationLogLevel":0,` + `"verbosity_level":1,"verbosity_details_level":null,"validation_log_level":0,` +
`"Username":"user","Allowed":["0.0.0.0/0","::/0"]}` `"username":"user","allowed":["0.0.0.0/0","::/0"]}`
assert.Equal(t, expected, string(b)) assert.Equal(t, expected, string(b))

View File

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

View File

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

View File

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

View File

@@ -2,11 +2,13 @@ package settings
import ( import (
"fmt" "fmt"
"net" "net/netip"
"regexp" "regexp"
"github.com/qdm12/gluetun/internal/configuration/settings/helpers" "github.com/qdm12/gluetun/internal/configuration/settings/helpers"
"github.com/qdm12/gluetun/internal/constants/providers" "github.com/qdm12/gluetun/internal/constants/providers"
"github.com/qdm12/gosettings"
"github.com/qdm12/gosettings/validate"
"github.com/qdm12/gotree" "github.com/qdm12/gotree"
"golang.zx2c4.com/wireguard/wgctrl/wgtypes" "golang.zx2c4.com/wireguard/wgctrl/wgtypes"
) )
@@ -15,29 +17,42 @@ import (
type Wireguard struct { type Wireguard struct {
// PrivateKey is the Wireguard client peer private key. // PrivateKey is the Wireguard client peer private key.
// It cannot be nil in the internal state. // It cannot be nil in the internal state.
PrivateKey *string PrivateKey *string `json:"private_key"`
// PreSharedKey is the Wireguard pre-shared key. // PreSharedKey is the Wireguard pre-shared key.
// It can be the empty string to indicate there // It can be the empty string to indicate there
// is no pre-shared key. // is no pre-shared key.
// It cannot be nil in the internal state. // It cannot be nil in the internal state.
PreSharedKey *string PreSharedKey *string `json:"pre_shared_key"`
// Addresses are the Wireguard interface addresses. // Addresses are the Wireguard interface addresses.
Addresses []net.IPNet Addresses []netip.Prefix `json:"addresses"`
// Interface is the name of the Wireguard interface // Interface is the name of the Wireguard interface
// to create. It cannot be the empty string in the // to create. It cannot be the empty string in the
// internal state. // internal state.
Interface string Interface string `json:"interface"`
// Maximum Transmission Unit (MTU) of the Wireguard interface.
// It cannot be zero in the internal state, and defaults to
// 1400. Note it is not the wireguard-go MTU default of 1420
// because this impacts bandwidth a lot on some VPN providers,
// see https://github.com/qdm12/gluetun/issues/1650.
MTU uint16 `json:"mtu"`
// Implementation is the Wireguard implementation to use.
// It can be "auto", "userspace" or "kernelspace".
// It defaults to "auto" and cannot be the empty string
// in the internal state.
Implementation string `json:"implementation"`
} }
var regexpInterfaceName = regexp.MustCompile(`^[a-zA-Z0-9_]+$`) var regexpInterfaceName = regexp.MustCompile(`^[a-zA-Z0-9_]+$`)
// Validate validates Wireguard settings. // Validate validates Wireguard settings.
// It should only be ran if the VPN type chosen is Wireguard. // It should only be ran if the VPN type chosen is Wireguard.
func (w Wireguard) validate(vpnProvider string) (err error) { func (w Wireguard) validate(vpnProvider string, ipv6Supported bool) (err error) {
if !helpers.IsOneOf(vpnProvider, if !helpers.IsOneOf(vpnProvider,
providers.Airvpn,
providers.Custom, providers.Custom,
providers.Ivpn, providers.Ivpn,
providers.Mullvad, providers.Mullvad,
providers.Nordvpn,
providers.Surfshark, providers.Surfshark,
providers.Windscribe, providers.Windscribe,
) { ) {
@@ -47,7 +62,7 @@ func (w Wireguard) validate(vpnProvider string) (err error) {
// Validate PrivateKey // Validate PrivateKey
if *w.PrivateKey == "" { if *w.PrivateKey == "" {
return ErrWireguardPrivateKeyNotSet return fmt.Errorf("%w", ErrWireguardPrivateKeyNotSet)
} }
_, err = wgtypes.ParseKey(*w.PrivateKey) _, err = wgtypes.ParseKey(*w.PrivateKey)
if err != nil { if err != nil {
@@ -70,13 +85,18 @@ func (w Wireguard) validate(vpnProvider string) (err error) {
// Validate Addresses // Validate Addresses
if len(w.Addresses) == 0 { if len(w.Addresses) == 0 {
return ErrWireguardInterfaceAddressNotSet return fmt.Errorf("%w", ErrWireguardInterfaceAddressNotSet)
} }
for i, ipNet := range w.Addresses { for i, ipNet := range w.Addresses {
if ipNet.IP == nil || ipNet.Mask == nil { if !ipNet.IsValid() {
return fmt.Errorf("%w: for address at index %d: %s", return fmt.Errorf("%w: for address at index %d: %s",
ErrWireguardInterfaceAddressNotSet, i, ipNet.String()) ErrWireguardInterfaceAddressNotSet, i, ipNet.String())
} }
if !ipv6Supported && ipNet.Addr().Is6() {
return fmt.Errorf("%w: address %s",
ErrWireguardInterfaceAddressIPv6, ipNet)
}
} }
// Validate interface // Validate interface
@@ -85,36 +105,55 @@ func (w Wireguard) validate(vpnProvider string) (err error) {
ErrWireguardInterfaceNotValid, w.Interface, regexpInterfaceName) ErrWireguardInterfaceNotValid, w.Interface, regexpInterfaceName)
} }
validImplementations := []string{"auto", "userspace", "kernelspace"}
if err := validate.IsOneOf(w.Implementation, validImplementations...); err != nil {
return fmt.Errorf("%w: %w", ErrWireguardImplementationNotValid, err)
}
return nil return nil
} }
func (w *Wireguard) copy() (copied Wireguard) { func (w *Wireguard) copy() (copied Wireguard) {
return Wireguard{ return Wireguard{
PrivateKey: helpers.CopyStringPtr(w.PrivateKey), PrivateKey: gosettings.CopyPointer(w.PrivateKey),
PreSharedKey: helpers.CopyStringPtr(w.PreSharedKey), PreSharedKey: gosettings.CopyPointer(w.PreSharedKey),
Addresses: helpers.CopyIPNetSlice(w.Addresses), Addresses: gosettings.CopySlice(w.Addresses),
Interface: w.Interface, Interface: w.Interface,
MTU: w.MTU,
Implementation: w.Implementation,
} }
} }
func (w *Wireguard) mergeWith(other Wireguard) { func (w *Wireguard) mergeWith(other Wireguard) {
w.PrivateKey = helpers.MergeWithStringPtr(w.PrivateKey, other.PrivateKey) w.PrivateKey = gosettings.MergeWithPointer(w.PrivateKey, other.PrivateKey)
w.PreSharedKey = helpers.MergeWithStringPtr(w.PreSharedKey, other.PreSharedKey) w.PreSharedKey = gosettings.MergeWithPointer(w.PreSharedKey, other.PreSharedKey)
w.Addresses = helpers.MergeIPNetsSlices(w.Addresses, other.Addresses) w.Addresses = gosettings.MergeWithSlice(w.Addresses, other.Addresses)
w.Interface = helpers.MergeWithString(w.Interface, other.Interface) w.Interface = gosettings.MergeWithString(w.Interface, other.Interface)
w.MTU = gosettings.MergeWithNumber(w.MTU, other.MTU)
w.Implementation = gosettings.MergeWithString(w.Implementation, other.Implementation)
} }
func (w *Wireguard) overrideWith(other Wireguard) { func (w *Wireguard) overrideWith(other Wireguard) {
w.PrivateKey = helpers.OverrideWithStringPtr(w.PrivateKey, other.PrivateKey) w.PrivateKey = gosettings.OverrideWithPointer(w.PrivateKey, other.PrivateKey)
w.PreSharedKey = helpers.OverrideWithStringPtr(w.PreSharedKey, other.PreSharedKey) w.PreSharedKey = gosettings.OverrideWithPointer(w.PreSharedKey, other.PreSharedKey)
w.Addresses = helpers.OverrideWithIPNetsSlice(w.Addresses, other.Addresses) w.Addresses = gosettings.OverrideWithSlice(w.Addresses, other.Addresses)
w.Interface = helpers.OverrideWithString(w.Interface, other.Interface) w.Interface = gosettings.OverrideWithString(w.Interface, other.Interface)
w.MTU = gosettings.OverrideWithNumber(w.MTU, other.MTU)
w.Implementation = gosettings.OverrideWithString(w.Implementation, other.Implementation)
} }
func (w *Wireguard) setDefaults() { func (w *Wireguard) setDefaults(vpnProvider string) {
w.PrivateKey = helpers.DefaultStringPtr(w.PrivateKey, "") w.PrivateKey = gosettings.DefaultPointer(w.PrivateKey, "")
w.PreSharedKey = helpers.DefaultStringPtr(w.PreSharedKey, "") w.PreSharedKey = gosettings.DefaultPointer(w.PreSharedKey, "")
w.Interface = helpers.DefaultString(w.Interface, "wg0") if vpnProvider == providers.Nordvpn {
defaultNordVPNAddress := netip.AddrFrom4([4]byte{10, 5, 0, 2})
defaultNordVPNPrefix := netip.PrefixFrom(defaultNordVPNAddress, defaultNordVPNAddress.BitLen())
w.Addresses = gosettings.DefaultSlice(w.Addresses, []netip.Prefix{defaultNordVPNPrefix})
}
w.Interface = gosettings.DefaultString(w.Interface, "wg0")
const defaultMTU = 1400
w.MTU = gosettings.DefaultNumber(w.MTU, defaultMTU)
w.Implementation = gosettings.DefaultString(w.Implementation, "auto")
} }
func (w Wireguard) String() string { func (w Wireguard) String() string {
@@ -125,12 +164,12 @@ func (w Wireguard) toLinesNode() (node *gotree.Node) {
node = gotree.New("Wireguard settings:") node = gotree.New("Wireguard settings:")
if *w.PrivateKey != "" { if *w.PrivateKey != "" {
s := helpers.ObfuscateWireguardKey(*w.PrivateKey) s := gosettings.ObfuscateKey(*w.PrivateKey)
node.Appendf("Private key: %s", s) node.Appendf("Private key: %s", s)
} }
if *w.PreSharedKey != "" { if *w.PreSharedKey != "" {
s := helpers.ObfuscateWireguardKey(*w.PreSharedKey) s := gosettings.ObfuscateKey(*w.PreSharedKey)
node.Appendf("Pre-shared key: %s", s) node.Appendf("Pre-shared key: %s", s)
} }
@@ -139,7 +178,12 @@ func (w Wireguard) toLinesNode() (node *gotree.Node) {
addressesNode.Appendf(address.String()) addressesNode.Appendf(address.String())
} }
node.Appendf("Network interface: %s", w.Interface) interfaceNode := node.Appendf("Network interface: %s", w.Interface)
interfaceNode.Appendf("MTU: %d", w.MTU)
if w.Implementation != "auto" {
node.Appendf("Implementation: %s", w.Implementation)
}
return node return node
} }

View File

@@ -2,10 +2,11 @@ package settings
import ( import (
"fmt" "fmt"
"net" "net/netip"
"github.com/qdm12/gluetun/internal/configuration/settings/helpers"
"github.com/qdm12/gluetun/internal/constants/providers" "github.com/qdm12/gluetun/internal/constants/providers"
"github.com/qdm12/gosettings"
"github.com/qdm12/gosettings/validate"
"github.com/qdm12/gotree" "github.com/qdm12/gotree"
"golang.zx2c4.com/wireguard/wgctrl/wgtypes" "golang.zx2c4.com/wireguard/wgctrl/wgtypes"
) )
@@ -15,20 +16,20 @@ type WireguardSelection struct {
// It is only used with VPN providers generating Wireguard // It is only used with VPN providers generating Wireguard
// configurations specific to each server and user. // configurations specific to each server and user.
// To indicate it should not be used, it should be set // To indicate it should not be used, it should be set
// to the empty net.IP{} slice. It can never be nil // to netaddr.IPv4Unspecified(). It can never be the zero value
// in the internal state. // in the internal state.
EndpointIP net.IP EndpointIP netip.Addr `json:"endpoint_ip"`
// EndpointPort is a the server port to use for the VPN server. // EndpointPort is a the server port to use for the VPN server.
// It is optional for VPN providers IVPN, Mullvad, Surfshark // It is optional for VPN providers IVPN, Mullvad, Surfshark
// and Windscribe, and compulsory for the others. // and Windscribe, and compulsory for the others.
// When optional, it can be set to 0 to indicate not use // When optional, it can be set to 0 to indicate not use
// a custom endpoint port. It cannot be nil in the internal // a custom endpoint port. It cannot be nil in the internal
// state. // state.
EndpointPort *uint16 EndpointPort *uint16 `json:"endpoint_port"`
// PublicKey is the server public key. // PublicKey is the server public key.
// It is only used with VPN providers generating Wireguard // It is only used with VPN providers generating Wireguard
// configurations specific to each server and user. // configurations specific to each server and user.
PublicKey string PublicKey string `json:"public_key"`
} }
// Validate validates WireguardSelection settings. // Validate validates WireguardSelection settings.
@@ -37,11 +38,11 @@ func (w WireguardSelection) validate(vpnProvider string) (err error) {
// Validate EndpointIP // Validate EndpointIP
switch vpnProvider { switch vpnProvider {
case providers.Airvpn, providers.Ivpn, providers.Mullvad, case providers.Airvpn, providers.Ivpn, providers.Mullvad,
providers.Surfshark, providers.Windscribe: providers.Nordvpn, providers.Surfshark, providers.Windscribe:
// endpoint IP addresses are baked in // endpoint IP addresses are baked in
case providers.Custom: case providers.Custom:
if len(w.EndpointIP) == 0 { if !w.EndpointIP.IsValid() || w.EndpointIP.IsUnspecified() {
return ErrWireguardEndpointIPNotSet return fmt.Errorf("%w", ErrWireguardEndpointIPNotSet)
} }
default: // Providers not supporting Wireguard default: // Providers not supporting Wireguard
} }
@@ -51,12 +52,12 @@ func (w WireguardSelection) validate(vpnProvider string) (err error) {
// EndpointPort is required // EndpointPort is required
case providers.Custom: case providers.Custom:
if *w.EndpointPort == 0 { if *w.EndpointPort == 0 {
return ErrWireguardEndpointPortNotSet return fmt.Errorf("%w", ErrWireguardEndpointPortNotSet)
} }
// EndpointPort cannot be set // EndpointPort cannot be set
case providers.Surfshark: case providers.Surfshark, providers.Nordvpn:
if *w.EndpointPort != 0 { if *w.EndpointPort != 0 {
return ErrWireguardEndpointPortSet return fmt.Errorf("%w", ErrWireguardEndpointPortSet)
} }
case providers.Airvpn, providers.Ivpn, providers.Mullvad, providers.Windscribe: case providers.Airvpn, providers.Ivpn, providers.Mullvad, providers.Windscribe:
// EndpointPort is optional and can be 0 // EndpointPort is optional and can be 0
@@ -76,12 +77,12 @@ func (w WireguardSelection) validate(vpnProvider string) (err error) {
allowed = []uint16{53, 80, 123, 443, 1194, 65142} allowed = []uint16{53, 80, 123, 443, 1194, 65142}
} }
if helpers.Uint16IsOneOf(*w.EndpointPort, allowed) { err = validate.IsOneOf(*w.EndpointPort, allowed...)
if err == nil {
break break
} }
return fmt.Errorf("%w: %d for VPN service provider %s; %s", return fmt.Errorf("%w: for VPN service provider %s: %w",
ErrWireguardEndpointPortNotAllowed, w.EndpointPort, vpnProvider, ErrWireguardEndpointPortNotAllowed, vpnProvider, err)
helpers.PortChoicesOrString(allowed))
default: // Providers not supporting Wireguard default: // Providers not supporting Wireguard
} }
@@ -92,7 +93,7 @@ func (w WireguardSelection) validate(vpnProvider string) (err error) {
// public keys are baked in // public keys are baked in
case providers.Custom: case providers.Custom:
if w.PublicKey == "" { if w.PublicKey == "" {
return ErrWireguardPublicKeyNotSet return fmt.Errorf("%w", ErrWireguardPublicKeyNotSet)
} }
default: // Providers not supporting Wireguard default: // Providers not supporting Wireguard
} }
@@ -109,27 +110,27 @@ func (w WireguardSelection) validate(vpnProvider string) (err error) {
func (w *WireguardSelection) copy() (copied WireguardSelection) { func (w *WireguardSelection) copy() (copied WireguardSelection) {
return WireguardSelection{ return WireguardSelection{
EndpointIP: helpers.CopyIP(w.EndpointIP), EndpointIP: w.EndpointIP,
EndpointPort: helpers.CopyUint16Ptr(w.EndpointPort), EndpointPort: gosettings.CopyPointer(w.EndpointPort),
PublicKey: w.PublicKey, PublicKey: w.PublicKey,
} }
} }
func (w *WireguardSelection) mergeWith(other WireguardSelection) { func (w *WireguardSelection) mergeWith(other WireguardSelection) {
w.EndpointIP = helpers.MergeWithIP(w.EndpointIP, other.EndpointIP) w.EndpointIP = gosettings.MergeWithValidator(w.EndpointIP, other.EndpointIP)
w.EndpointPort = helpers.MergeWithUint16(w.EndpointPort, other.EndpointPort) w.EndpointPort = gosettings.MergeWithPointer(w.EndpointPort, other.EndpointPort)
w.PublicKey = helpers.MergeWithString(w.PublicKey, other.PublicKey) w.PublicKey = gosettings.MergeWithString(w.PublicKey, other.PublicKey)
} }
func (w *WireguardSelection) overrideWith(other WireguardSelection) { func (w *WireguardSelection) overrideWith(other WireguardSelection) {
w.EndpointIP = helpers.OverrideWithIP(w.EndpointIP, other.EndpointIP) w.EndpointIP = gosettings.OverrideWithValidator(w.EndpointIP, other.EndpointIP)
w.EndpointPort = helpers.OverrideWithUint16(w.EndpointPort, other.EndpointPort) w.EndpointPort = gosettings.OverrideWithPointer(w.EndpointPort, other.EndpointPort)
w.PublicKey = helpers.OverrideWithString(w.PublicKey, other.PublicKey) w.PublicKey = gosettings.OverrideWithString(w.PublicKey, other.PublicKey)
} }
func (w *WireguardSelection) setDefaults() { func (w *WireguardSelection) setDefaults() {
w.EndpointIP = helpers.DefaultIP(w.EndpointIP, net.IP{}) w.EndpointIP = gosettings.DefaultValidator(w.EndpointIP, netip.IPv4Unspecified())
w.EndpointPort = helpers.DefaultUint16(w.EndpointPort, 0) w.EndpointPort = gosettings.DefaultPointer(w.EndpointPort, 0)
} }
func (w WireguardSelection) String() string { func (w WireguardSelection) String() string {
@@ -139,7 +140,7 @@ func (w WireguardSelection) String() string {
func (w WireguardSelection) toLinesNode() (node *gotree.Node) { func (w WireguardSelection) toLinesNode() (node *gotree.Node) {
node = gotree.New("Wireguard selection settings:") node = gotree.New("Wireguard selection settings:")
if len(w.EndpointIP) > 0 { if !w.EndpointIP.IsUnspecified() {
node.Appendf("Endpoint IP address: %s", w.EndpointIP) node.Appendf("Endpoint IP address: %s", w.EndpointIP)
} }

View File

@@ -2,7 +2,7 @@ package env
import ( import (
"fmt" "fmt"
"net" "net/netip"
"github.com/qdm12/gluetun/internal/configuration/settings" "github.com/qdm12/gluetun/internal/configuration/settings"
) )
@@ -13,9 +13,9 @@ func (s *Source) readDNS() (dns settings.DNS, err error) {
return dns, err return dns, err
} }
dns.KeepNameserver, err = envToBoolPtr("DNS_KEEP_NAMESERVER") dns.KeepNameserver, err = s.env.BoolPtr("DNS_KEEP_NAMESERVER")
if err != nil { if err != nil {
return dns, fmt.Errorf("environment variable DNS_KEEP_NAMESERVER: %w", err) return dns, err
} }
dns.DoT, err = s.readDoT() dns.DoT, err = s.readDoT()
@@ -26,20 +26,25 @@ func (s *Source) readDNS() (dns settings.DNS, err error) {
return dns, nil return dns, nil
} }
func (s *Source) readDNSServerAddress() (address net.IP, err error) { func (s *Source) readDNSServerAddress() (address netip.Addr, err error) {
key, value := s.getEnvWithRetro("DNS_ADDRESS", "DNS_PLAINTEXT_ADDRESS") const currentKey = "DNS_ADDRESS"
if value == "" { key := firstKeySet(s.env, "DNS_PLAINTEXT_ADDRESS", currentKey)
return nil, nil switch key {
case "":
return address, nil
case currentKey:
default: // Retro-compatibility
s.handleDeprecatedKey(key, currentKey)
} }
address = net.ParseIP(value) address, err = s.env.NetipAddr(key)
if address == nil { if err != nil {
return nil, fmt.Errorf("environment variable %s: %w: %s", key, ErrIPAddressParse, value) return address, err
} }
// TODO remove in v4 // TODO remove in v4
if !address.Equal(net.IPv4(127, 0, 0, 1)) { //nolint:gomnd if address.Unmap().Compare(netip.AddrFrom4([4]byte{127, 0, 0, 1})) != 0 {
s.warner.Warn(key + " is set to " + value + s.warner.Warn(key + " is set to " + address.String() +
" so the DNS over TLS (DoT) server will not be used." + " so the DNS over TLS (DoT) server will not be used." +
" The default value changed to 127.0.0.1 so it uses the internal DoT serves." + " The default value changed to 127.0.0.1 so it uses the internal DoT serves." +
" If the DoT server fails to start, the IPv4 address of the first plaintext DNS server" + " If the DoT server fails to start, the IPv4 address of the first plaintext DNS server" +

View File

@@ -3,76 +3,62 @@ package env
import ( import (
"errors" "errors"
"fmt" "fmt"
"net/netip"
"github.com/qdm12/gluetun/internal/configuration/settings" "github.com/qdm12/gluetun/internal/configuration/settings"
"github.com/qdm12/govalid/binary" "github.com/qdm12/gosettings/sources/env"
"inet.af/netaddr"
) )
func (s *Source) readDNSBlacklist() (blacklist settings.DNSBlacklist, err error) { func (s *Source) readDNSBlacklist() (blacklist settings.DNSBlacklist, err error) {
blacklist.BlockMalicious, err = envToBoolPtr("BLOCK_MALICIOUS") blacklist.BlockMalicious, err = s.env.BoolPtr("BLOCK_MALICIOUS")
if err != nil {
return blacklist, fmt.Errorf("environment variable BLOCK_MALICIOUS: %w", err)
}
blacklist.BlockSurveillance, err = s.readBlockSurveillance()
if err != nil { if err != nil {
return blacklist, err return blacklist, err
} }
blacklist.BlockAds, err = envToBoolPtr("BLOCK_ADS") blacklist.BlockSurveillance, err = s.env.BoolPtr("BLOCK_SURVEILLANCE",
env.RetroKeys("BLOCK_NSA"))
if err != nil { if err != nil {
return blacklist, fmt.Errorf("environment variable BLOCK_ADS: %w", err) return blacklist, err
}
blacklist.BlockAds, err = s.env.BoolPtr("BLOCK_ADS")
if err != nil {
return blacklist, err
} }
blacklist.AddBlockedIPs, blacklist.AddBlockedIPPrefixes, blacklist.AddBlockedIPs, blacklist.AddBlockedIPPrefixes,
err = readDoTPrivateAddresses() // TODO v4 split in 2 err = s.readDoTPrivateAddresses() // TODO v4 split in 2
if err != nil { if err != nil {
return blacklist, err return blacklist, err
} }
blacklist.AllowedHosts = envToCSV("UNBLOCK") // TODO v4 change name blacklist.AllowedHosts = s.env.CSV("UNBLOCK") // TODO v4 change name
return blacklist, nil return blacklist, nil
} }
func (s *Source) readBlockSurveillance() (blocked *bool, err error) {
key, value := s.getEnvWithRetro("BLOCK_SURVEILLANCE", "BLOCK_NSA")
if value == "" {
return nil, nil //nolint:nilnil
}
blocked = new(bool)
*blocked, err = binary.Validate(value)
if err != nil {
return nil, fmt.Errorf("environment variable %s: %w", key, err)
}
return blocked, nil
}
var ( var (
ErrPrivateAddressNotValid = errors.New("private address is not a valid IP or CIDR range") ErrPrivateAddressNotValid = errors.New("private address is not a valid IP or CIDR range")
) )
func readDoTPrivateAddresses() (ips []netaddr.IP, func (s *Source) readDoTPrivateAddresses() (ips []netip.Addr,
ipPrefixes []netaddr.IPPrefix, err error) { ipPrefixes []netip.Prefix, err error) {
privateAddresses := envToCSV("DOT_PRIVATE_ADDRESS") privateAddresses := s.env.CSV("DOT_PRIVATE_ADDRESS")
if len(privateAddresses) == 0 { if len(privateAddresses) == 0 {
return nil, nil, nil return nil, nil, nil
} }
ips = make([]netaddr.IP, 0, len(privateAddresses)) ips = make([]netip.Addr, 0, len(privateAddresses))
ipPrefixes = make([]netaddr.IPPrefix, 0, len(privateAddresses)) ipPrefixes = make([]netip.Prefix, 0, len(privateAddresses))
for _, privateAddress := range privateAddresses { for _, privateAddress := range privateAddresses {
ip, err := netaddr.ParseIP(privateAddress) ip, err := netip.ParseAddr(privateAddress)
if err == nil { if err == nil {
ips = append(ips, ip) ips = append(ips, ip)
continue continue
} }
ipPrefix, err := netaddr.ParseIPPrefix(privateAddress) ipPrefix, err := netip.ParsePrefix(privateAddress)
if err == nil { if err == nil {
ipPrefixes = append(ipPrefixes, ipPrefix) ipPrefixes = append(ipPrefixes, ipPrefix)
continue continue

View File

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

View File

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

View File

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

View File

@@ -3,146 +3,31 @@ package env
import ( import (
"fmt" "fmt"
"os" "os"
"strconv"
"strings"
"time"
"github.com/qdm12/govalid/binary" "github.com/qdm12/gosettings/sources/env"
"github.com/qdm12/govalid/integer"
) )
// getCleanedEnv returns an environment variable value with
// surrounding spaces and trailing new line characters removed.
func getCleanedEnv(envKey string) (value string) {
value = os.Getenv(envKey)
value = strings.TrimSpace(value)
value = strings.TrimSuffix(value, "\r\n")
value = strings.TrimSuffix(value, "\n")
return value
}
func envToCSV(envKey string) (values []string) {
csv := getCleanedEnv(envKey)
if csv == "" {
return nil
}
return lowerAndSplit(csv)
}
func envToInt(envKey string) (n int, err error) {
s := getCleanedEnv(envKey)
if s == "" {
return 0, nil
}
return strconv.Atoi(s)
}
func envToFloat64(envKey string) (f float64, err error) {
s := getCleanedEnv(envKey)
if s == "" {
return 0, nil
}
const bits = 64
return strconv.ParseFloat(s, bits)
}
func envToStringPtr(envKey string) (stringPtr *string) {
s := getCleanedEnv(envKey)
if s == "" {
return nil
}
return &s
}
func envToBoolPtr(envKey string) (boolPtr *bool, err error) {
s := getCleanedEnv(envKey)
if s == "" {
return nil, nil //nolint:nilnil
}
value, err := binary.Validate(s)
if err != nil {
return nil, err
}
return &value, nil
}
func envToIntPtr(envKey string) (intPtr *int, err error) {
s := getCleanedEnv(envKey)
if s == "" {
return nil, nil //nolint:nilnil
}
value, err := strconv.Atoi(s)
if err != nil {
return nil, err
}
return &value, nil
}
func envToUint8Ptr(envKey string) (uint8Ptr *uint8, err error) {
s := getCleanedEnv(envKey)
if s == "" {
return nil, nil //nolint:nilnil
}
const min, max = 0, 255
value, err := integer.Validate(s, integer.OptionRange(min, max))
if err != nil {
return nil, err
}
uint8Ptr = new(uint8)
*uint8Ptr = uint8(value)
return uint8Ptr, nil
}
func envToUint16Ptr(envKey string) (uint16Ptr *uint16, err error) {
s := getCleanedEnv(envKey)
if s == "" {
return nil, nil //nolint:nilnil
}
const min, max = 0, 65535
value, err := integer.Validate(s, integer.OptionRange(min, max))
if err != nil {
return nil, err
}
uint16Ptr = new(uint16)
*uint16Ptr = uint16(value)
return uint16Ptr, nil
}
func envToDurationPtr(envKey string) (durationPtr *time.Duration, err error) {
s := getCleanedEnv(envKey)
if s == "" {
return nil, nil //nolint:nilnil
}
durationPtr = new(time.Duration)
*durationPtr, err = time.ParseDuration(s)
if err != nil {
return nil, err
}
return durationPtr, nil
}
func lowerAndSplit(csv string) (values []string) {
csv = strings.ToLower(csv)
return strings.Split(csv, ",")
}
func unsetEnvKeys(envKeys []string, err error) (newErr error) { func unsetEnvKeys(envKeys []string, err error) (newErr error) {
newErr = err newErr = err
for _, envKey := range envKeys { for _, envKey := range envKeys {
unsetErr := os.Unsetenv(envKey) unsetErr := os.Unsetenv(envKey)
if unsetErr != nil && newErr == nil { if unsetErr != nil && newErr == nil {
newErr = fmt.Errorf("cannot unset environment variable %s: %w", envKey, unsetErr) newErr = fmt.Errorf("unsetting environment variable %s: %w", envKey, unsetErr)
} }
} }
return newErr return newErr
} }
func stringPtr(s string) *string { return &s } func ptrTo[T any](value T) *T {
func uint32Ptr(n uint32) *uint32 { return &n } return &value
func boolPtr(b bool) *bool { return &b } }
func firstKeySet(e env.Env, keys ...string) (firstKeySet string) {
for _, key := range keys {
value := e.Get(key)
if value != nil {
return key
}
}
return ""
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -2,35 +2,24 @@ package secrets
import ( import (
"fmt" "fmt"
"os"
"strings"
"github.com/qdm12/gluetun/internal/configuration/sources/files" "github.com/qdm12/gluetun/internal/configuration/sources/files"
"github.com/qdm12/gluetun/internal/openvpn/extract" "github.com/qdm12/gluetun/internal/openvpn/extract"
"github.com/qdm12/gosettings/sources/env"
) )
// getCleanedEnv returns an environment variable value with func (s *Source) readSecretFileAsStringPtr(secretPathEnvKey, defaultSecretPath string) (
// surrounding spaces and trailing new line characters removed.
func getCleanedEnv(envKey string) (value string) {
value = os.Getenv(envKey)
value = strings.TrimSpace(value)
value = strings.TrimSuffix(value, "\r\n")
value = strings.TrimSuffix(value, "\n")
return value
}
func readSecretFileAsStringPtr(secretPathEnvKey, defaultSecretPath string) (
stringPtr *string, err error) { stringPtr *string, err error) {
path := getCleanedEnv(secretPathEnvKey) path := s.env.String(secretPathEnvKey, env.ForceLowercase(false))
if path == "" { if path == "" {
path = defaultSecretPath path = defaultSecretPath
} }
return files.ReadFromFile(path) return files.ReadFromFile(path)
} }
func readPEMSecretFile(secretPathEnvKey, defaultSecretPath string) ( func (s *Source) readPEMSecretFile(secretPathEnvKey, defaultSecretPath string) (
base64Ptr *string, err error) { base64Ptr *string, err error) {
pemData, err := readSecretFileAsStringPtr(secretPathEnvKey, defaultSecretPath) pemData, err := s.readSecretFileAsStringPtr(secretPathEnvKey, defaultSecretPath)
if err != nil { if err != nil {
return nil, fmt.Errorf("reading secret file: %w", err) return nil, fmt.Errorf("reading secret file: %w", err)
} }

View File

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

View File

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

View File

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

View File

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

View File

@@ -6,13 +6,13 @@ import (
"github.com/qdm12/gluetun/internal/configuration/settings" "github.com/qdm12/gluetun/internal/configuration/settings"
) )
func readShadowsocks() (settings settings.Shadowsocks, err error) { func (s *Source) readShadowsocks() (settings settings.Shadowsocks, err error) {
settings.Password, err = readSecretFileAsStringPtr( settings.Password, err = s.readSecretFileAsStringPtr(
"SHADOWSOCKS_PASSWORD_SECRETFILE", "SHADOWSOCKS_PASSWORD_SECRETFILE",
"/run/secrets/shadowsocks_password", "/run/secrets/shadowsocks_password",
) )
if err != nil { if err != nil {
return settings, fmt.Errorf("cannot read Shadowsocks password secret file: %w", err) return settings, fmt.Errorf("reading Shadowsocks password secret file: %w", err)
} }
return settings, nil return settings, nil

View File

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

View File

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

View File

@@ -1,7 +1,7 @@
package dns package dns
import ( import (
"net" "net/netip"
"github.com/qdm12/dns/pkg/nameserver" "github.com/qdm12/dns/pkg/nameserver"
) )
@@ -12,14 +12,14 @@ func (l *Loop) useUnencryptedDNS(fallback bool) {
// Try with user provided plaintext ip address // Try with user provided plaintext ip address
// if it's not 127.0.0.1 (default for DoT) // if it's not 127.0.0.1 (default for DoT)
targetIP := settings.ServerAddress targetIP := settings.ServerAddress
if targetIP != nil && !targetIP.Equal(net.IPv4(127, 0, 0, 1)) { //nolint:gomnd if targetIP.Compare(netip.AddrFrom4([4]byte{127, 0, 0, 1})) != 0 {
if fallback { if fallback {
l.logger.Info("falling back on plaintext DNS at address " + targetIP.String()) l.logger.Info("falling back on plaintext DNS at address " + targetIP.String())
} else { } else {
l.logger.Info("using plaintext DNS at address " + targetIP.String()) l.logger.Info("using plaintext DNS at address " + targetIP.String())
} }
nameserver.UseDNSInternally(targetIP) nameserver.UseDNSInternally(targetIP.AsSlice())
err := nameserver.UseDNSSystemWide(l.resolvConf, targetIP, *settings.KeepNameserver) err := nameserver.UseDNSSystemWide(l.resolvConf, targetIP.AsSlice(), *settings.KeepNameserver)
if err != nil { if err != nil {
l.logger.Error(err.Error()) l.logger.Error(err.Error())
} }
@@ -38,8 +38,8 @@ func (l *Loop) useUnencryptedDNS(fallback bool) {
} else { } else {
l.logger.Info("using plaintext DNS at address " + targetIP.String()) l.logger.Info("using plaintext DNS at address " + targetIP.String())
} }
nameserver.UseDNSInternally(targetIP) nameserver.UseDNSInternally(targetIP.AsSlice())
err = nameserver.UseDNSSystemWide(l.resolvConf, targetIP, *settings.KeepNameserver) err = nameserver.UseDNSSystemWide(l.resolvConf, targetIP.AsSlice(), *settings.KeepNameserver)
if err != nil { if err != nil {
l.logger.Error(err.Error()) l.logger.Error(err.Error())
} }

View File

@@ -43,8 +43,8 @@ func (l *Loop) setupUnbound(ctx context.Context) (
} }
// use Unbound // use Unbound
nameserver.UseDNSInternally(settings.ServerAddress) nameserver.UseDNSInternally(settings.ServerAddress.AsSlice())
err = nameserver.UseDNSSystemWide(l.resolvConf, settings.ServerAddress, err = nameserver.UseDNSSystemWide(l.resolvConf, settings.ServerAddress.AsSlice(),
*settings.KeepNameserver) *settings.KeepNameserver)
if err != nil { if err != nil {
l.logger.Error(err.Error()) l.logger.Error(err.Error())

View File

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

View File

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

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