Compare commits

..

448 Commits

Author SHA1 Message Date
Quentin McGaw
b9a9319cb4 fix(ci): trigger CI on published releases 2021-11-27 12:09:33 +00:00
Quentin McGaw
77e4317135 chore(dev): fix devcontainer post create command 2021-11-27 12:01:07 +00:00
dependabot[bot]
b10d97e53a Chore(deps): Bump github.com/breml/rootcerts from 0.1.1 to 0.2.0 (#722)
Bumps [github.com/breml/rootcerts](https://github.com/breml/rootcerts) from 0.1.1 to 0.2.0.
- [Release notes](https://github.com/breml/rootcerts/releases)
- [Commits](https://github.com/breml/rootcerts/compare/v0.1.1...v0.2.0)

---
updated-dependencies:
- dependency-name: github.com/breml/rootcerts
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2021-11-27 06:58:32 -05:00
dependabot[bot]
648a4c04d7 Build(deps): Bump actions/checkout from 2.3.4 to 2.4.0 (#705)
Bumps [actions/checkout](https://github.com/actions/checkout) from 2.3.4 to 2.4.0.
- [Release notes](https://github.com/actions/checkout/releases)
- [Changelog](https://github.com/actions/checkout/blob/main/CHANGELOG.md)
- [Commits](https://github.com/actions/checkout/compare/v2.3.4...v2.4.0)

---
updated-dependencies:
- dependency-name: actions/checkout
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2021-11-27 06:58:16 -05:00
Quentin McGaw
3ca674dca7 feat(windscribe): update server information 2021-11-18 22:00:05 +00:00
Quentin McGaw
fa97fd496e feat(torguard): update server information 2021-11-18 09:35:30 +00:00
Quentin McGaw
c76a7ee8da chore(dot): add error description for update files failure 2021-11-18 08:12:07 +00:00
Quentin McGaw
80f6b78332 chore(config): fix bad error wrapping 2021-11-17 22:32:33 +00:00
Quentin McGaw
8dc54a7c44 feat(privatevpn): support OPENVPN_PORT 2021-11-17 22:32:18 +00:00
Quentin McGaw
8f080c537b fix(privatevpn): openvpn configuration values 2021-11-17 22:26:18 +00:00
Quentin McGaw
427cf86f44 chore(ci) disable snyk analysis for false positive 2021-11-12 23:39:01 +00:00
Quentin McGaw
2d244c08e7 Fix: 2 low vulnerability busybox issues 2021-11-12 23:04:42 +00:00
Quentin McGaw
82c0f523aa fix: openvpn at /usr/sbin/openvpn2.5
- Fix operation on QNAP devices
- Refer to #157
2021-11-12 22:48:19 +00:00
Quentin McGaw
c07a0b0ada chore(lint): add bidichk, ifshort, nilnil and tenv 2021-11-08 22:41:20 +00:00
Quentin McGaw
e4c306c0ee chore(linter): update golangci-lint to v1.43.0 2021-11-07 21:26:31 +00:00
Quentin McGaw
6ffb94f819 fix(updater): cli error message 2021-11-07 21:25:10 +00:00
Quentin McGaw
142238e8b7 feat(protonvpn): update built-in servers data 2021-11-07 21:24:57 +00:00
Quentin McGaw (desktop)
678e23c7d6 Change: run OpenVPN as root to clean routes on exit 2021-11-01 22:51:03 +00:00
Quentin McGaw (desktop)
0abcebe1d8 Feat: update NordVPN server information 2021-11-01 22:48:36 +00:00
Quentin McGaw (desktop)
f398af1169 Fix: check github http status code for version 2021-10-29 01:37:45 +00:00
Quentin McGaw (desktop)
afbea415e3 Maint: replace with for markdown generated tables 2021-10-25 22:38:59 +00:00
Quentin McGaw (desktop)
225bd5d25b Fix: CI to use short commits 2021-10-21 13:46:50 +00:00
Quentin McGaw (desktop)
3651cc6161 Maint: CI image tags rework 2021-10-16 14:58:11 +00:00
Quentin McGaw (desktop)
dc674014ff Fix: vyprvpn: openvpn comp-lzo option 2021-10-14 19:55:48 +00:00
Quentin McGaw (desktop)
0e0e03949d Docs: add urgent and low priority labels 2021-10-14 16:37:09 +00:00
Quentin McGaw (desktop)
f5bf5c236a Hotfix: CI if condition 2021-10-14 16:32:43 +00:00
dependabot[bot]
94480ecabb Maint: bump docker/build-push-action from 2.6.1 to 2.7.0 (#664) 2021-10-14 09:31:08 -07:00
dependabot[bot]
31ef9b1d45 Maint: bump github.com/breml/rootcerts from 0.1.0 to 0.1.1 (#668) 2021-10-14 09:30:21 -07:00
Quentin McGaw (desktop)
bf76132fd4 Maint: fix dependabot CI trigger 2021-10-14 16:29:22 +00:00
Quentin McGaw (desktop)
8cc2983318 Fix: NordVPN: Re-add comp-lzo option 2021-10-13 00:04:51 +00:00
Quentin McGaw (desktop)
caeca18ed7 Hotfix: ci workflow 2021-10-06 18:54:44 +00:00
dependabot[bot]
50febb41ff Maint: bump docker/build-push-action from 2.6.1 to 2.7.0 (#583) 2021-10-06 11:47:06 -07:00
Quentin McGaw (desktop)
79293e067c Doc: fix readme Wiki links 2021-10-06 17:47:08 +00:00
Quentin McGaw (desktop)
f45be80591 Maint: CI changes
- Only trigger on push and PR to master
- Do not push images for branches
- Add fork only workflow
- Add dependabot only workflow
- Do not trigger ci workflow from forked/dependabot PRs
2021-10-06 14:23:01 +00:00
Quentin McGaw (desktop)
d405ba8dca Feat: run OpenVPN without root 2021-10-05 21:33:15 +00:00
Quentin McGaw (desktop)
ca975b1c01 Feat: multiple OpenVPN ciphers for negotiation
- Perfect privacy to accept AES-256-CBC and AES-256-GCM
- Cyberghost default cipher set to AES-256-GCM
- `OPENVPN_CIPHER` accept comma separated cipher values
- Use `ncp-ciphers` for OpenVPN 2.4
2021-10-05 20:36:23 +00:00
Quentin McGaw
e0e3ca3832 Feat: Perfect privacy support (#606) 2021-10-05 10:44:15 -07:00
Quentin McGaw (desktop)
e7c952cbf7 Maint: remove opendns.com due to bad x509 cert 2021-09-30 16:01:35 +00:00
Quentin McGaw (desktop)
85ad2dd39a Maint: simplify warning logging in http proxy 2021-09-30 16:01:02 +00:00
Quentin McGaw (desktop)
0c4f0ec17b Doc: add ref to image tags in bug issue template 2021-09-30 15:34:21 +00:00
Quentin McGaw (desktop)
5ad4136955 Maint: move splash at start of program 2021-09-30 15:28:24 +00:00
Quentin McGaw (desktop)
a432de95a9 Maint: deduplicate ProtonVPN servers by entry IP 2021-09-30 15:23:18 +00:00
Quentin McGaw (desktop)
1d25a0e18c Fix: server data version diff when reading file 2021-09-30 15:22:57 +00:00
Quentin McGaw (desktop)
29fd95685f Doc: add custom provider option for bug issue template 2021-09-29 20:41:38 +00:00
Quentin McGaw (desktop)
62a6016882 Fix: FastestVPN new OpenVPN config
- Add required `comp-lzo`
- Add `reneg-sec 0` to match their config
- Do not filter `auth-token`
- Set UDP options only when using UDP
2021-09-28 13:52:07 +00:00
Quentin McGaw (desktop)
18a4a79763 Fix: log errors as error for OpenVPN 2021-09-28 11:57:32 +00:00
Quentin McGaw (desktop)
56ea722f93 Doc: update wiki issue template to use yml format 2021-09-27 23:47:10 +00:00
Quentin McGaw (desktop)
d2ab974933 Doc: update feature request issue template to use yml format 2021-09-27 23:44:15 +00:00
Quentin McGaw (desktop)
37d7a8b5fe Doc: add Unraid template discussion link to issues 2021-09-27 23:40:22 +00:00
Quentin McGaw (desktop)
e4dcadd825 Doc: update bug issue template to use yml format 2021-09-27 23:37:40 +00:00
Quentin McGaw (desktop)
fee99e9fe3 Doc: add Github discussion link to issue config 2021-09-27 14:15:56 +00:00
Quentin McGaw (desktop)
8ac4826126 Doc: add Console Substack interview link 2021-09-27 13:24:31 +00:00
Quentin McGaw (desktop)
7deb12e06d Maint: use github.com/breml/rootcerts 2021-09-26 22:26:11 +00:00
Quentin McGaw (desktop)
d6e218141b Maint: Go program uses time/tzdata instead of OS 2021-09-26 22:25:28 +00:00
Quentin McGaw (desktop)
f44121b044 Maint: upgrade qdm12/goshutdown to v0.3.0 2021-09-26 22:23:52 +00:00
Quentin McGaw (desktop)
5d8d92462d Feat: update FastestVPN server information 2021-09-25 13:30:29 +00:00
Quentin McGaw
985cf7b7dd Feat: ExpressVPN support (#623) 2021-09-23 10:19:30 -07:00
dependabot[bot]
dcbc10fd57 Build(deps): Bump github.com/fatih/color from 1.12.0 to 1.13.0 (#635) 2021-09-23 10:08:31 -07:00
Quentin McGaw (desktop)
79f243e98d Maint: package local log levels 2021-09-23 17:06:09 +00:00
Quentin McGaw (desktop)
cf95692b93 Maint: package local narrow Logger interfaces 2021-09-23 17:06:09 +00:00
Quentin McGaw
d8e008606f Feat: WeVPN support (#591) 2021-09-23 07:58:13 -07:00
Quentin McGaw (desktop)
3cd26a9f61 Feat: debug log Wireguard keys 2021-09-23 14:42:28 +00:00
Quentin McGaw (desktop)
5d74320ee7 Maint: truncate servers format output file 2021-09-23 14:38:46 +00:00
Quentin McGaw (desktop)
f9aadeef1c Maint: Remove CYBERGHOST_GROUP (change)
- It does not make any sense with newer server data
- It was to be deprecated anyway
2021-09-23 13:54:24 +00:00
Quentin McGaw (desktop)
625de1c834 Maint: migrate Cyberghost REGION to COUNTRY 2021-09-23 13:28:32 +00:00
Quentin McGaw (desktop)
1c0a3ed1a4 Feat: update Cyberghost servers data 2021-09-23 13:26:21 +00:00
Quentin McGaw (desktop)
03ba9169f4 Feat: format-servers CLI command 2021-09-23 13:13:17 +00:00
Quentin McGaw (desktop)
c22e0e9db7 Fix: HideMyAss: Cote d'Ivoire server country 2021-09-21 23:56:53 +00:00
Quentin McGaw (desktop)
6bcbaf085d Maint: remove NordVPN SERVER_NAME filter
- Filter was not effective
- Is to be deprecated in v4 anyway
- Bump NordVPN server model version to `3`
- Remove `Name` field from NordVPN server model
2021-09-21 23:56:29 +00:00
Quentin McGaw (desktop)
9a1d9c5d74 Fix: PIA's SERVER_NAME variable 2021-09-21 22:31:50 +00:00
Quentin McGaw (desktop)
59a3a072e0 Feat: support IPv6 routing for Wireguard 2021-09-21 15:12:48 +00:00
Quentin McGaw (desktop)
9f001bbc06 Feat: log wireguard server endpoint 2021-09-18 19:12:27 +00:00
Quentin McGaw (desktop)
b8356b60a6 Maint: use OPENVPN_PORT instead of PORT
with retro-compatibility
2021-09-18 16:09:21 +00:00
Quentin McGaw (desktop)
e2e218c74b Feat: update Mullvad server information 2021-09-18 15:37:49 +00:00
Quentin McGaw (desktop)
3bf23cbae5 Maint: remove enabled by default linters 2021-09-17 18:05:07 +00:00
Quentin McGaw (desktop)
da562d8206 Doc: update maintenance document 2021-09-16 20:42:39 +00:00
Quentin McGaw (desktop)
81bf83db13 Maint: remove disable-occ from PIA (match Wiki) 2021-09-16 20:32:34 +00:00
Quentin McGaw (desktop)
7a25dcd130 Doc: remove duplicate docker-compose.yml 2021-09-16 20:32:04 +00:00
Quentin McGaw (desktop)
877c7e1a9f Doc: update readme with updated Wiki 2021-09-16 20:24:30 +00:00
Quentin McGaw (desktop)
77b2512745 Doc: add wiki issue template 2021-09-16 19:53:37 +00:00
Quentin McGaw (desktop)
749b73ef15 Doc: remove help issue template 2021-09-16 19:51:32 +00:00
Quentin McGaw (desktop)
e499eca12c Maint: remove assignees in Github issues 2021-09-16 19:51:14 +00:00
Quentin McGaw (desktop)
80f25c34e5 Hotfix: default cyberghost cipher to aes-128-gcm 2021-09-15 12:54:36 +00:00
Quentin McGaw (desktop)
61677fbce2 Maint: migrate PROTOCOL to OPENVPN_PROTOCOL 2021-09-14 19:27:13 +00:00
Quentin McGaw (desktop)
dc6171185e Maint: add more linters to golangci-lint 2021-09-14 19:26:46 +00:00
Quentin McGaw (desktop)
f7e4331e93 Fix: PureVPN remove AES-256-CBC cipher 2021-09-14 15:47:06 +00:00
Quentin McGaw (desktop)
1340511b64 Maint: re-order OpenVPN options 2021-09-14 15:46:40 +00:00
Quentin McGaw (desktop)
c3078f84e8 Maint: OpenVPN option: remove all ping-* options
- Use the built-in healthcheck vpn restart mechanism instead
- Restarting with `ping-restart` or `ping-exit` would only restart with the same `remote` connection
- Specify `ping` options as VPN specific to ensure the server doesn't disconnect us
2021-09-14 15:23:56 +00:00
Quentin McGaw (desktop)
9f65157a0d Maint: OpenVPN: add explicit-exit-notify for UDP 2021-09-14 15:13:40 +00:00
Quentin McGaw (desktop)
89166cdabf Maint: OpenVPN: PIA: add tls-exit option 2021-09-14 15:09:22 +00:00
Quentin McGaw (desktop)
b872973e8b Maint: OpenVPN option tls-client removed
- It's redundant with `client` option
- Affects FastestVPN
2021-09-14 15:04:50 +00:00
Quentin McGaw (desktop)
2000e72357 Maint: OpenVPN option: remove tun-mtu 1500
- Since it defaults to `1500`
- Affects FastestVPN
- Affects NordVPN
- Affects ProtonVPN
- Affects Surfshark
- Affects Torguard
2021-09-14 14:59:04 +00:00
Quentin McGaw (desktop)
836e53642d Maint: OpenVPN option keepalive replaced by ping options 2021-09-14 14:57:31 +00:00
Quentin McGaw (desktop)
af3f882bb8 Maint: OpenVPN: only add persist-key when running without root 2021-09-14 14:55:39 +00:00
Quentin McGaw (desktop)
2ab05b9350 Maint: OpenVPN: only add persist-tun when running without root 2021-09-14 14:54:59 +00:00
Quentin McGaw (desktop)
1022eb8a6e Maint: remove OpenVPN option route-method
- Unneeded unless running on Windows
- Affects PureVPN
2021-09-14 14:49:02 +00:00
Quentin McGaw (desktop)
15fe62de32 Maint: remove OpenVPN route-delay option
- Affects Cyberghost
- Affects PureVPN
2021-09-14 14:48:14 +00:00
Quentin McGaw (desktop)
83d87f83f9 Maint: remove useless OpenVPN ping-timer-rem 2021-09-14 14:47:27 +00:00
Quentin McGaw (desktop)
76a0c1f6c4 Fix: OpenVPN remove compression options (security)
- Affects FastestVPN
- Affects Hide My Ass
- Affects IP Vanish
- Affects IVPN
- Affects NordVPN
- Affects PrivateVPN
- Affects ProtonVPN
- Affects VPN Unlimited
- Affects VyprVPN
2021-09-14 14:45:30 +00:00
Quentin McGaw (desktop)
a1588302a7 Change: Windscribe: OpenVPN cipher aes-256-gcm 2021-09-14 13:22:43 +00:00
Quentin McGaw (desktop)
91ce790b6b Fix: OpenVPN custom: do not deduplicate lines
- Remove case by case lines to avoid duplicates
- Do not deduplicate all lines
2021-09-14 12:32:15 +00:00
Quentin McGaw (desktop)
5d3982c2d2 Docs: update Wireguard support list in readme 2021-09-13 20:23:16 +00:00
Quentin McGaw (desktop)
2cf7f7b268 Maint: WIREGUARD_PORT to WIREGUARD_ENDPOINT_PORT 2021-09-13 20:06:47 +00:00
Quentin McGaw (desktop)
8645d978ba Feat: VPNSP=custom for Wireguard
- `WIREGUARD_PUBLIC_KEY` variable
- `WIREGUARD_ENDPOINT_IP` variable
2021-09-13 19:33:04 +00:00
Quentin McGaw (desktop)
cc18b158f4 Maint: remove all script-security OpenVPN options
- Affects Cyberghost
- Affects Mullvad
- Affects PureVPN
- Affects Surfshark
- Affects Torguard
- Affects Windscribe
2021-09-13 16:05:14 +00:00
Quentin McGaw (desktop)
0730b6db6e Maint: remove ncp-disable deprecated option
- Affects Cyberghost
- Affects PIA
- Affects Torguard
- Affects Windscribe
2021-09-13 15:59:33 +00:00
Quentin McGaw (desktop)
3d2a360401 Fix: remove OpenVPN compression (PIA, torguard) 2021-09-13 15:56:25 +00:00
Quentin McGaw (desktop)
0c60dab384 Maint: remove deprecated keysize OpenVPN option 2021-09-13 15:55:16 +00:00
Quentin McGaw (desktop)
f5f0ad7f28 Maint: remove deprecated tun-ipv6 option 2021-09-13 15:54:01 +00:00
Quentin McGaw
f807f756eb VPNSP value custom for OpenVPN custom config files (#621)
- Retro-compatibility: `OPENVPN_CUSTOM_CONFIG` set implies `VPNSP=custom`
- Change: `up` and `down` options are not filtered out
- Change: `OPENVPN_INTERFACE` overrides the network interface defined in the configuration file
- Change: `PORT` overrides any port found in the configuration file
- Feat: config file is read when building the OpenVPN configuration, so it's effectively reloaded on VPN restarts
- Feat: extract values from custom file at start to log out valid settings
- Maint: `internal/openvpn/extract` package instead of `internal/openvpn/custom` package
- Maint: All providers' `BuildConf` method return an error
- Maint: rename `CustomConfig` to `ConfFile` in Settings structures
2021-09-13 08:30:14 -07:00
Quentin McGaw (desktop)
11af6c10f1 HotFix: use newer HEALTH variables in Dockerfile 2021-09-13 01:30:37 +00:00
Quentin McGaw (desktop)
40342619e7 Maint: dynamically set allowed VPN input ports
- Feat: allow to change VPN type at runtime
- Feat: allow to change interface name at runtime
- Maint: Add cleanup method to cleanup VPN loop on a vpn shutdown
- Change: allow VPN inputs ports only when tunnel is up
2021-09-13 00:50:20 +00:00
Quentin McGaw (desktop)
19bf62c21f Fix: set non block on TUN device 2021-09-12 13:32:50 +00:00
Quentin McGaw (desktop)
2ea00d149f Feat: adapt logger prefix to VPN used
- `openvpn: ` for OpenVPN
- `wireguard: ` for Wireguard
2021-09-12 13:27:30 +00:00
Quentin McGaw (desktop)
cc677bde93 Maint: change default ping address to github.com to test DNS 2021-09-11 22:27:32 +00:00
Quentin McGaw (desktop)
6627cda96c Feat: HEALTH_ADDRESS_TO_PING variable
- Defaults to `1.1.1.1`
- Add more Ping integration tests with different addresses
- Add unit test pinging 127.0.0.1
- Add comment explaining why we need to use ICMP instead of UDP
2021-09-11 22:22:55 +00:00
Quentin McGaw (desktop)
cade2732b0 Maint: improve internal/configuration/health_test.go unit test 2021-09-11 22:14:37 +00:00
Quentin McGaw (desktop)
541a4a3271 Feat: healthcheck uses ping instead of DNS 2021-09-11 21:49:46 +00:00
Quentin McGaw (desktop)
0eccd068e5 Maint: rename health OpenVPN names to VPN
- `HEALTH_OPENVPN_DURATION_INITIAL` renamed to `HEALTH_VPN_DURATION_INITIAL` with retro-compatiblity
- `HEALTH_OPENVPN_DURATION_ADDITION` renamed to `HEALTH_VPN_DURATION_ADDITION` with retro-compatiblity
2021-09-11 21:04:21 +00:00
Quentin McGaw (desktop)
87f4b9e422 Docs: update maintenance document 2021-09-11 15:29:29 +00:00
Quentin McGaw (desktop)
bcaf2e42fd Maint: re-order Dockerfile environment variables 2021-09-11 15:24:00 +00:00
Quentin McGaw (desktop)
d39201f9b3 Fix: public IP loop deadlock 2021-09-10 22:54:02 +00:00
Quentin McGaw (desktop)
8ac2a816c3 Fix: close HTTP client connections when tunnel comes up 2021-09-10 22:53:05 +00:00
Quentin McGaw (desktop)
344f1bf9ee Docs: add wireguard in top description 2021-09-10 22:18:29 +00:00
Quentin McGaw (desktop)
f0a006fc43 Docs: add fix the unraid template link 2021-09-10 21:11:08 +00:00
Quentin McGaw (desktop)
145da0b21d Maint: rename wireguard CustomPort 2021-09-10 19:07:14 +00:00
Quentin McGaw (desktop)
094de89a3e Fix: PIA_ENCRYPTION default value outside Docker
- Defaults to `strong` instead of strong certificate string
- No impact on Docker images since variable is set to `strong` in Dockerfile
- Only read `PIA_ENCRYPTION` if service provider is PIA
2021-09-09 21:36:14 +00:00
Quentin McGaw (desktop)
65ace12def Maint: internal/openvpn/parse package
- Parse PEM key data for Cyberghost and VPNUnlimited
- Add more unit tests
2021-09-08 16:40:19 +00:00
Quentin McGaw (desktop)
9afe455635 Fix: missing status code check for Windscribe API 2021-09-08 16:09:32 +00:00
Quentin McGaw (desktop)
45ce422a89 Maint: use type aliases in internal/netlink 2021-09-07 02:35:39 +00:00
Quentin McGaw (desktop)
4a0738cd49 Fix: repo servers.json path 2021-09-06 13:41:45 +00:00
Quentin McGaw (desktop)
6b6caa435f Fix: clear IP data when VPN is stopped 2021-09-06 13:28:05 +00:00
Quentin McGaw (desktop)
f9cb71027c Feat: location data at /v1/publicip/ip 2021-09-05 22:54:10 +00:00
Quentin McGaw (desktop)
82ac568ee3 Fix: wireguard cleanup preventing restarts 2021-09-04 22:29:04 +00:00
Quentin McGaw (desktop)
61afdce788 Hotfix: Wireguard WIREGUARD_ADDRESSES setting 2021-08-28 20:59:39 +00:00
Quentin McGaw (desktop)
119cac5a67 Feat: OPENVPN_TARGET_IP overrides IP
- Check target IP matches a server for Wireguard since we need the public key
- Streamline connection picking for all providers
2021-08-28 19:07:44 +00:00
Quentin McGaw (desktop)
c6fedd9214 Feat: support csv addresses in WIREGUARD_ADDRESS 2021-08-28 18:43:23 +00:00
Quentin McGaw (desktop)
da525e039d Fix: update Mullvad annoucement logged 2021-08-28 18:14:28 +00:00
Quentin McGaw (desktop)
29d92fd307 Fix: Surfshark REGION retro-compatibility 2021-08-28 18:14:21 +00:00
Quentin McGaw (desktop)
3863cc439e Maint: internal/storage rework
- No more global variables
- Inject merged servers to configuration package
- Fix #566: configuration parsing to use persisted servers.json
- Move server data files from `internal/constants` to `internal/storage`
2021-08-27 19:10:03 +00:00
Quentin McGaw (desktop)
b1cfc03fc5 Maint: internal/storage remove Windscribe debug logs 2021-08-27 12:10:49 +00:00
Quentin McGaw (desktop)
f706071048 Fix: FIREWALL_VPN_INPUT_PORTS for Wireguard 2021-08-26 19:54:48 +00:00
Quentin McGaw (desktop)
501ae2741b Fix: FIREWALL_OUTBOUND_SUBNETS ip rules 2021-08-26 15:46:19 +00:00
Quentin McGaw (desktop)
5b75635386 Maint: fix rules equality check for nil networks 2021-08-26 14:33:51 +00:00
Quentin McGaw (desktop)
2901db3cf3 Maint: internal/routing IP rules functions
- Take in `src` as `*net.IPNet` instead of `net.IP`
- Take `dst` IP network
- Debug logged `ip rule` dynamically built
- Add unit tests for all IP rules functions
2021-08-26 13:59:43 +00:00
Quentin McGaw (desktop)
6c2a3e36b5 Maint: rename outboundsubnets.go to outbound.go 2021-08-25 19:09:42 +00:00
Quentin McGaw (desktop)
8b125e6e95 Maint: internal/routing/inbound.go file 2021-08-25 19:08:55 +00:00
Quentin McGaw (desktop)
e1cc14e055 Fix: firewall inherits log level from LOG_LEVEL 2021-08-25 17:55:46 +00:00
Quentin McGaw (desktop)
d6659552df Maint: refactor internal/routing
- Split Go files better
- Reduce public API for exported errors
2021-08-25 17:52:05 +00:00
Quentin McGaw (desktop)
67001fa958 Maint: rename files in internal/subnet 2021-08-25 17:27:10 +00:00
Quentin McGaw (desktop)
ffeeae91ab Maint: merge subnet.FindSubnetsToAdd and subnet.FindSubnetsToRemove in subnet.FindSubnetsToChange 2021-08-25 17:25:36 +00:00
Quentin McGaw (desktop)
04fad1b781 Maint: internal/subnet package 2021-08-25 17:22:48 +00:00
Quentin McGaw (desktop)
dcaf952986 Maint: http proxy server constructor returns struct 2021-08-25 17:03:55 +00:00
Quentin McGaw (desktop)
ca3b9e892d Maint: http proxy HTTPS handling simplifications 2021-08-25 17:02:50 +00:00
Quentin McGaw (desktop)
9f12ffc069 Fix: MULTIHOP_ONLY defaults to no 2021-08-24 13:12:40 +00:00
Quentin McGaw (desktop)
0d6800a515 Fix: panic for certain no server found errors 2021-08-23 21:19:53 +00:00
Quentin McGaw (desktop)
b3d8b78205 Maint: only internal/netlink depends on github.com/vishvananda/netlink 2021-08-23 21:12:28 +00:00
Quentin McGaw (desktop)
ee82a85543 Maint: internal/routing uses internal/netlink 2021-08-23 20:56:10 +00:00
Quentin McGaw (desktop)
7907146aaf Maint: rework IPIsPrivate in internal/routing 2021-08-23 20:50:50 +00:00
Quentin McGaw (desktop)
1a677ce4f7 Maint: internal/routing returns *Routine struct 2021-08-23 20:50:32 +00:00
Quentin McGaw (desktop)
f1a6594474 Maint: utils.FilterByProtocol function 2021-08-23 20:16:29 +00:00
Quentin McGaw
f1a82d9d9c Feat: rework Surfshark servers data (#575)
- Feat: `MULTIHOP_ONLY` variable
- Feat: `COUNTRY` variable
- Feat: `CITY` variable
- Feat: `REGION` variable, with retro-compatibility
- Feat: merge servers from API, zip and hardcoded hostnames
- Fix: remove outdated and duplicate servers
- Maint: faster update with fully parallel DNS resolutions
2021-08-23 10:25:00 -07:00
Quentin McGaw (desktop)
8b52af0d03 Maint: common GetPort for OpenVPN+Wireguard providers 2021-08-23 16:13:20 +00:00
Quentin McGaw (desktop)
dbf5c569ea Maint: common GetProtocol for OpenVPN+Wireguard providers 2021-08-23 16:07:47 +00:00
Quentin McGaw (desktop)
06a2d79cb4 Feat: Wireguard support for Ivpn (#584) 2021-08-23 16:01:01 +00:00
Quentin McGaw (desktop)
eb6238ee52 Feat: WIREGUARD_PORT for Mullvad 2021-08-23 16:00:40 +00:00
Quentin McGaw (desktop)
f41fec57ed Feat: IVPN supports TCP and custom port 2021-08-23 13:34:00 +00:00
Quentin McGaw
c348343b22 IVPN server data update code and ISP filter (#578)
- Use IVPN's HTTP API instead of their .zip file
- Unit tests for API and GetServers
- Paves the way for Wireguard
- Update server information for IVPN
- Add `ISP` filter for IVPN
2021-08-22 20:11:56 -07:00
Quentin McGaw
b69dcb62e3 LOG_LEVEL variable (#577) 2021-08-22 18:57:10 -07:00
Quentin McGaw (laptop)
e4a260f148 Maint: upgrade qdm12/golibs 2021-08-22 20:44:14 +00:00
Quentin McGaw
614eb10d67 Wireguard support for Mullvad and Windscribe (#565)
- `internal/wireguard` client package with unit tests
- Implementation works with kernel space or user space if unavailable
- `WIREGUARD_PRIVATE_KEY`
- `WIREGUARD_ADDRESS`
- `WIREGUARD_PRESHARED_KEY`
- `WIREGUARD_PORT`
- `internal/netlink` package used by `internal/wireguard`
2021-08-22 14:58:39 -07:00
Quentin McGaw
0bfd58a3f5 Fix: sorted IP addresses for servers.json (#574)
- Reduce deltas between updates
- Applies to the following providers
  - IPVanish
  - IVPN
  - Surfshark
  - Torguard
  - VPNUnlimited
2021-08-21 16:03:18 -07:00
Quentin McGaw (desktop)
ff56857fc8 Fix: port forwarding VPN interface specification 2021-08-21 18:16:44 +00:00
Quentin McGaw (desktop)
8d258feff7 Hot fix: interface name set for openvpn configs 2021-08-20 01:13:04 +00:00
Quentin McGaw (desktop)
96ee1bbfb2 Maint: upgrade from Go 1.16 to Go 1.17 2021-08-20 00:07:41 +00:00
Quentin McGaw (desktop)
abaf688ad8 Doc: update readme
- Image size lowered to 34MB
- Using Alpine 3.14
- Beta wireguard support
2021-08-19 23:53:47 +00:00
Quentin McGaw (desktop)
bec8ff27ae Feat: OPENVPN_INTERFACE defaulting to tun0
- Fix: custom config with custom network interface name for firewall
- Keep VPN tunnel interface in firewall state
- Vul fix: only allow traffic through vpn interface when needed
- Adapt code to adapt to network interface name
- Remove outdated TUN and TAP constants
2021-08-19 23:22:55 +00:00
Quentin McGaw (desktop)
7191d4e911 Maint: upgrade golibs, fix logger settings inheritance 2021-08-19 19:29:50 +00:00
Quentin McGaw (desktop)
6f59bc3037 Maint: simplify provider configuration logging 2021-08-19 17:41:37 +00:00
Quentin McGaw (desktop)
5c2286f4e8 Maint: simplify settings code in internal/vpn 2021-08-19 14:57:11 +00:00
Quentin McGaw (desktop)
9218c7ef19 Maint: create OpenVPN runner in VPN run loop 2021-08-19 14:45:57 +00:00
Quentin McGaw (desktop)
3d8e61900b Maint: make VPN connection not specific to OpenVPN
- Add VPN field to ServerSelection struct
- Set VPN type to server selection at start using VPN_TYPE
- Change OpenVPNConnection to Connection with Type field
- Rename Provider GetOpenVPNConnection to GetConnection
- Rename GetTargetIPOpenVPNConnection to GetTargetIPConnection
- Rename PickRandomOpenVPNConnection to PickRandomConnection
- Add 'OpenVPN' prefix to OpenVPN specific methods on connection
2021-08-19 14:09:41 +00:00
Quentin McGaw (desktop)
105d81c018 Maint: move Openvpn package files
- Move internal/openvpn/config/*.go to internal/openvpn/
- Move internal/openvpn/setup.go to internal/vpn/openvpn.go
2021-08-19 13:31:12 +00:00
Quentin McGaw (desktop)
d4ca5cf257 Maint: internal/vpn package for vpn loop 2021-08-18 22:01:04 +00:00
Quentin McGaw (desktop)
05018ec971 Maint: use VPN settings instead of OpenVPN in loop 2021-08-18 21:27:09 +00:00
Quentin McGaw (desktop)
538bc72c3c Maint: better log when cathing an OS signal 2021-08-18 21:22:27 +00:00
Quentin McGaw (desktop)
0027a76c49 Maint: move OpenVPN streams processing to config package 2021-08-18 21:16:28 +00:00
Quentin McGaw (desktop)
a0cb6fabfd Maint: rename openvpn command.go to start.go 2021-08-18 20:47:03 +00:00
Quentin McGaw (desktop)
9e5400f52d Maint: split out OpenVPN version functions to openvpn/config/version.go 2021-08-18 20:46:20 +00:00
Quentin McGaw (desktop)
7a1d0ff3ec Maint: internal/openvpn setup.go file 2021-08-18 20:43:47 +00:00
Quentin McGaw (desktop)
d9fbecaa01 Maint: minor changes to openvpn/config package
- Constructor returns concrete struct instead of interface
- Rename conf to openvpnConf in openvpn loop
2021-08-18 20:28:42 +00:00
Quentin McGaw (desktop)
ecdf9396a5 Maint: move OpenVPN configurator to openvpn/config 2021-08-18 20:23:50 +00:00
Quentin McGaw (desktop)
df51aa40f4 Maint: split custom config files in openvpn/custom 2021-08-18 20:18:49 +00:00
Quentin McGaw (desktop)
996942af47 Maint: move custom config files to custom package 2021-08-18 20:14:02 +00:00
Quentin McGaw (desktop)
f17a4eae3e Maint: rework OpenVPN custom configuration code
- Refactor code and errors returned
- Add unit tests
- Make custom config code independent from loop
2021-08-18 20:12:26 +00:00
Quentin McGaw (desktop)
c515603d2f Fix: Openvpn custom config: remove user set 2021-08-18 17:41:53 +00:00
Quentin McGaw (desktop)
14c3b6429b Maint: openvpn process user in Openvpn settings 2021-08-18 16:16:47 +00:00
Quentin McGaw (desktop)
bd110b960b Maint: remove startPFCh from Openvpn loop 2021-08-18 16:07:35 +00:00
Quentin McGaw (desktop)
3ad4319163 Maint: minor Openvpn loop simplifications 2021-08-18 15:52:38 +00:00
Quentin McGaw (desktop)
97340ec70b Fix: chown openvpn configuration file 2021-08-18 15:47:11 +00:00
Quentin McGaw (desktop)
5140a7b010 Maint: set PUID and PGID in openvpn configurator 2021-08-18 15:44:58 +00:00
Quentin McGaw (desktop)
bd74879303 Maint: read all settings first 2021-08-18 15:42:19 +00:00
Quentin McGaw (desktop)
da30ae287f Maint: decouple OpenVPN config writer from loop 2021-08-18 15:35:07 +00:00
Quentin McGaw (desktop)
6a545aa088 Maint: tun package to handle tun device operations
- Moved from openvpn package to tun package
- TUN check verifies Rdev value
- TUN create
- Inject as interface to main function
- Add integration test
- Clearer log message for end users if tun device does not exist
- Remove unix package (unneeded for tests)
- Remove tun file opening at the end of tun file creation
- Do not mock unix.Mkdev (no OS operation)
- Remove Tun operations from OpenVPN configurator
2021-08-18 15:31:08 +00:00
Quentin McGaw (desktop)
384a4bae3a Hotfix: PIA: encryption preset reading 2021-08-17 19:35:57 +00:00
Quentin McGaw (desktop)
e65f924cd7 Maint: remove custom config readProvider constructor 2021-08-17 17:53:13 +00:00
Quentin McGaw (desktop)
9105b33e9f Maint: configuration Openvpn selection structure
- Move network protocol from ServerSelection to OpenVPNSelection child
- Move PIA encryption preset from ServerSelection to OpenVPNSelection child
- Move custom port from ServerSelection to OpenVPNSelection child
2021-08-17 16:54:22 +00:00
Quentin McGaw (desktop)
cc2235653a Maint: refactor VPN configuration structure
- Paves the way for Wireguard
- VPN struct contains Type, Openvpn and Provider configurations
- OpenVPN specific options (e.g. client key) moved from Provider to Openvpn configuration struct
- Move Provider configuration from OpenVPN configuration to VPN
- HTTP control server returns only openvpn settings (not provider settings)
2021-08-17 15:44:11 +00:00
Quentin McGaw (desktop)
a00de75f61 Maint: rename utility names to be Openvpn specific
- GetTargetIPConnection to GetTargetIPOpenVPNConnection
- PickRandomConnection to PickRandomOpenVPNConnection
2021-08-17 14:08:53 +00:00
Quentin McGaw (desktop)
836412b032 Maint: move routeReadyEvents to openvpn package 2021-08-16 19:19:41 +00:00
Quentin McGaw (desktop)
ba16270059 Maint: context aware collectLines functions 2021-08-16 19:19:33 +00:00
Quentin McGaw (desktop)
2c73672e64 Fix: restore PIA error if region does not support port forwarding 2021-08-16 19:16:05 +00:00
Quentin McGaw (desktop)
74b7c81195 Fix: apk-tools culnerability fix installation
- Install apk-tools before using apk
- Install latest apk-tools so it can be rebuilt in the future
2021-08-09 14:49:45 +00:00
Quentin McGaw (desktop)
a021ff6b22 Fix: loopstate mutex unlocking
- Fix #547
- Fix all run loops for restarts
2021-08-09 14:35:55 +00:00
Quentin McGaw (desktop)
6d1a90cac0 Fix: use apk-tools 2.12.7-r0
- valid for ppc64le
- additional security fix
2021-08-09 01:21:19 +00:00
Quentin McGaw (desktop)
1f47c16102 Fix: windscribe: only get openvpn IP addresses 2021-08-09 01:18:51 +00:00
Quentin McGaw (desktop)
abbcf60aed Fix: port forward get route, fixes #552 2021-08-01 15:01:28 +00:00
Quentin McGaw (desktop)
f339c882d7 Feat: updater cyberghost servers 2021-07-31 22:38:18 +00:00
Quentin McGaw (desktop)
982536e9e8 Fix & feat: Cyberghost server groups
- Allow multiple comma separated values for CYBERGHOST_GROUP
- Defaults to all UDP groups
- If TCP is enabled, defaults to all TCP groups
- Check groups specified match the protocol
- Default Cyberghost group to empty
- Adjust formatting and messages
2021-07-31 14:53:34 +00:00
Quentin McGaw (desktop)
c17b351efb Fix: cyberghost: explicit-exit-notify only for UDP 2021-07-31 14:02:02 +00:00
Quentin McGaw (desktop)
130bebf2c6 Doc: add unraid template link to issue templates 2021-07-30 19:48:42 +00:00
Quentin McGaw (desktop)
83c4ad2e59 Hotfix: fix shadowsocks config parsing, refix #548 2021-07-29 13:50:40 +00:00
Quentin McGaw (desktop)
0bcc6ed597 Fix: port forwarding deadlock bug, fix #547 2021-07-29 01:13:16 +00:00
Quentin McGaw (desktop)
c61f854edc Maint: upgrade ss-server to v0.3.0
- `SHADOWSOCKS_PORT` in retrocompatibility
- `SHADOWSOCKS_METHOD` in retrocompatibility
- `SHADOWSOCKS_ADDRESS` added
- `SHADOWSOCKS_CIPHER` added
- Shadowsocks config inherit from ss-server's Settings
- Log adapter removed as no longer needed
2021-07-29 00:48:46 +00:00
Quentin McGaw
2998cf5e48 Maint: port forwarding refactoring (#543)
- portforward package
- portforward run loop
- Less functional arguments and cycles
2021-07-28 08:35:44 -07:00
Quentin McGaw
c777f8d97d Feat: add verify-x509-name to Windscribe Openvpn config (#529) 2021-07-28 07:18:08 -07:00
Quentin McGaw (desktop)
7d4f5c8906 Fix: Alpine vulnerability with apk 2021-07-27 19:45:23 +00:00
Quentin McGaw (desktop)
da39d07d48 Maint: log line fix for updater version diff 2021-07-27 15:18:10 +00:00
Quentin McGaw (desktop)
b98f2456c0 Fix: deadlock for openvpn, dns and publicip loops 2021-07-27 14:12:22 +00:00
Quentin McGaw (desktop)
564cc2b0bc Maint: move misplaced writeOpenvpnConf in openvpn 2021-07-26 16:30:51 +00:00
Quentin McGaw (desktop)
49885c63c4 Maint: common no port forwarding implementation 2021-07-26 16:29:40 +00:00
Quentin McGaw (desktop)
d7a6caa2ac Maint: routing interface composition 2021-07-26 16:18:53 +00:00
Quentin McGaw (desktop)
73c383fd65 Maint: remove routing configurator from Openvpn Loop 2021-07-26 16:18:35 +00:00
Quentin McGaw (desktop)
10b270f742 Maint: remove routing from firewall configurator 2021-07-26 16:17:01 +00:00
Quentin McGaw (desktop)
7a222923c7 Maint: use narrower interfaces for firewall config 2021-07-26 16:07:50 +00:00
Quentin McGaw (desktop)
430512dd27 Maint: openvpn configurator interface composition 2021-07-26 16:03:04 +00:00
Quentin McGaw (desktop)
d5ba15c23b Maint: improve http proxy loop Run 2021-07-26 01:42:37 +00:00
Quentin McGaw (desktop)
037b43ee10 Maint: add completed status to loopstate 2021-07-26 01:38:49 +00:00
Quentin McGaw (desktop)
ab910403c6 Fix: public IP loop being stuck 2021-07-26 01:35:43 +00:00
Quentin McGaw (laptop)
8105437815 Maint: add missing interface compilation checks 2021-07-24 19:54:15 +00:00
Quentin McGaw (laptop)
7b20cec035 Maint: rename SettingsGetterSetter to SettingsGetSetter 2021-07-24 19:49:50 +00:00
Quentin McGaw (laptop)
8d512852a4 Maint: rework publicip package
- Use loopstate package
- Loop interface composition
- Return concrete struct from constructors
- Split into more files
- Add publicip/state package
2021-07-24 19:49:11 +00:00
Quentin McGaw (laptop)
c8ad9b942a Maint: openvpn loop is a concrete struct 2021-07-24 19:14:49 +00:00
Quentin McGaw (laptop)
8153d4bb2a Maint: better openvpn loop interface composition 2021-07-24 18:56:42 +00:00
Quentin McGaw (laptop)
849dfee200 Maint: http proxy return concrete Loop struct 2021-07-24 18:52:19 +00:00
Quentin McGaw (laptop)
85540d96b6 Maint: interface composition for HTTP proxy loop
- Change SetStatus to ApplyStatus
- Add Runner interface
- Add SettingsGetterSetter alias to state.SettingsGetterSetter
2021-07-24 18:50:17 +00:00
Quentin McGaw (laptop)
7479974d79 Maint: dns package state rework
- Interface composition with loopstate interfaces
- Use loopstate.Manager
- Create dns/state package for handling settings
2021-07-24 18:34:55 +00:00
Quentin McGaw (laptop)
3f1fb52fcb Maint: upgrade qdm12 dependencies
- Upgrade qdm12/golibs
- Upgrade qdm12/dns to v1.11.0
2021-07-24 17:59:22 +00:00
Quentin McGaw (desktop)
7e343d7006 Maint: use loopstate for httpproxy 2021-07-23 20:47:36 +00:00
Quentin McGaw (desktop)
72a5e1f695 Maint: openvpn package split files 2021-07-23 20:46:57 +00:00
Quentin McGaw (desktop)
253310bd1a Maint: loopstate package used in Openvpn state 2021-07-23 20:41:45 +00:00
Quentin McGaw (desktop)
fa6ccb08bd Fix: openvpn loop: unlock read mutex for GetSettingsAndServers 2021-07-23 20:13:02 +00:00
Quentin McGaw (desktop)
762507855e Maint: split httpproxy files 2021-07-23 19:25:48 +00:00
Quentin McGaw (desktop)
54610866f2 Maint: healthcheck package interface rework
- return concrete struct type
- Add compilation checks for implementations
2021-07-23 19:22:41 +00:00
Quentin McGaw (desktop)
c39ff5c233 Maint: move duration formatting to qdm12/golibs 2021-07-23 19:17:23 +00:00
Quentin McGaw (desktop)
2ddc784965 Maint: firewall package interface rework
- return concrete struct type
- split interface is sub-interfaces
2021-07-23 19:12:16 +00:00
Quentin McGaw (desktop)
10aabe8375 Hotfix: cli, alpine and dns interface name changes 2021-07-23 19:11:49 +00:00
Quentin McGaw (desktop)
122647b39d Maint: pass network values to firewall constructor 2021-07-23 19:04:17 +00:00
Quentin McGaw (desktop)
02492c34a7 Maint: dns package interface rework
- return concrete struct type
- split interface is sub-interfaces
2021-07-23 18:57:29 +00:00
Quentin McGaw (desktop)
9436f604ba Maint: split Go files in dns package 2021-07-23 18:55:53 +00:00
Quentin McGaw (desktop)
d9ca0deb08 Maint: cli package interface rework
- return concrete struct type
- split interface is sub-interfaces
2021-07-23 18:52:38 +00:00
Quentin McGaw (desktop)
0b985e8c35 Maint: alpine package interface rework
- return concrete struct type
- split interface is sub-interfaces
2021-07-23 18:51:51 +00:00
Quentin McGaw (desktop)
c5d92ae02c Maint: inject Commander to openvpn and firewall 2021-07-23 18:25:30 +00:00
Quentin McGaw (desktop)
94b60d9f70 Maint: firewall and routing use logger.Debug
- Remove SetVerbose and SetDebug from both
- Log routing teardown
- Default logging level set to info
2021-07-23 18:20:18 +00:00
Quentin McGaw (desktop)
b23eb8f29d Maint: prefer empty string comparison 2021-07-23 17:39:38 +00:00
Quentin McGaw (desktop)
3c44214d01 Maint: pass only single strings to logger methods
- Do not assume formatting from logger's interface
- Allow to change golibs in the future to accept only strings for logger methods
2021-07-23 17:36:08 +00:00
Quentin McGaw (desktop)
21f4cf7ab5 Maint: do not mock os functions
- Use filepaths with /tmp for tests instead
- Only mock functions where filepath can't be specified such as user.Lookup
2021-07-23 16:06:19 +00:00
Quentin McGaw (desktop)
e94684aa39 Fix: version diff for VPN server information 2021-07-23 02:51:49 +00:00
Quentin McGaw (desktop)
a34cc48197 Feat: update all servers for all providers 2021-07-23 02:47:29 +00:00
Quentin McGaw (desktop)
b262d91ccc Feat: add -all flag to update all VPN servers 2021-07-23 02:47:04 +00:00
Quentin McGaw (desktop)
39aa983771 Maint: upgrade golibs and env error wrapping 2021-07-23 02:34:15 +00:00
Quentin McGaw (desktop)
5b9887dade Maint: use qdm12/gosplash 2021-07-22 20:56:47 +00:00
Quentin McGaw (desktop)
c33402ce66 Feat: HEALTH_SERVER_ADDRESS 2021-07-22 20:45:17 +00:00
Quentin McGaw (desktop)
6f58f84151 Maint: improve health code 2021-07-22 20:18:52 +00:00
Quentin McGaw (desktop)
6acb7caf5b Feat: Env variables to set health timeouts
- HEALTH_OPENVPN_DURATION_INITIAL
- HEALTH_OPENVPN_DURATION_ADDITION
2021-07-22 20:13:20 +00:00
Quentin McGaw (desktop)
8beff34cca Maint: remove debug line in health server 2021-07-22 13:43:19 +00:00
TJJP
478e0f74f7 Fix: Windscribe Openvpn config (#528)
See https://blog.windscribe.com/openvpn-security-improvements-and-changes-7b04ea49222

> OpenVPN compression phaseout is in progress and will be completed by August 3rd 2021.
If you downloaded configs from this page before you saw this message, you need to re-download them now, or simply remove the compress or comp-lzo flags from the config that you downloaded after July 20th 2021.
2021-07-21 12:48:10 -04:00
Quentin McGaw (desktop)
b7bd23ab60 Fix: buildDate renamed to created in Dockerfile 2021-07-20 23:10:33 +00:00
Quentin McGaw (desktop)
82533c1453 Maint: improve servers data embedding
- use embed.FS to have immutable data
- use sync.Once to parse only once without data races
2021-07-20 19:01:49 +00:00
Quentin McGaw (desktop)
e0735b57ce Maint: build all images fully in parallel 2021-07-20 15:47:28 +00:00
Quentin McGaw (desktop)
1e0bfc3b0c Maint: rename BUILD_DATE to CREATED 2021-07-20 15:28:02 +00:00
Quentin McGaw (desktop)
cb0e89a38e Maint: use curly braces around BUILDPLATFORM 2021-07-20 15:27:16 +00:00
Quentin McGaw (desktop)
da4d528463 Maint: hardcoded data in JSON embedded file
- Server information, versions and timestamps together in internal/constants/servers.json
- breaking change: updater cli uses -enduser instead of -file
- breaking change: updater cli uses -maintainer instead of -stdout
- Fix: replace special last a character with 'a' from Bogota for PrivateVPN
- Feat: do not write out servers and timestamp if no change was detected
2021-07-20 03:01:26 +00:00
Quentin McGaw (desktop)
394abbbe35 Feat: specify Openvpn flags with OPENVPN_FLAGS 2021-07-19 15:10:53 +00:00
Quentin McGaw (desktop)
fd39bc8518 Maint: upgrade inet.af/netaddr to 2021-07-18 2021-07-19 13:28:13 +00:00
dependabot[bot]
2663e8fba7 Bump docker/build-push-action from 2.4.0 to 2.6.1 (#513)
Bumps [docker/build-push-action](https://github.com/docker/build-push-action) from 2.4.0 to 2.6.1.
- [Release notes](https://github.com/docker/build-push-action/releases)
- [Commits](https://github.com/docker/build-push-action/compare/v2.4.0...v2.6.1)

---
updated-dependencies:
- dependency-name: docker/build-push-action
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2021-07-18 19:22:59 -07:00
Quentin McGaw (desktop)
faebac6a77 Maint: rename build.yml to ci.yml for linting 2021-07-19 02:20:23 +00:00
Quentin McGaw (desktop)
bc1b09e997 Maint: remove microbadger hook from CI 2021-07-19 02:18:22 +00:00
Quentin McGaw (desktop)
af358f777b Feat: pull filter ipv6 if OPENVPN_IPV6 is off 2021-07-19 01:46:20 +00:00
Quentin McGaw (desktop)
c0d27b4bfc Maint: rework openvpn restart on unhealthy 2021-07-18 03:17:48 +00:00
Quentin McGaw (desktop)
7e50c95823 Maint: minor DNS loop fixes and changes 2021-07-16 21:21:09 +00:00
Quentin McGaw (desktop)
39068dda17 Maint: rework Openvpn run loop 2021-07-16 21:20:34 +00:00
Quentin McGaw (desktop)
8185979ca4 Fix: deadlock on dns shutdown when starting up 2021-07-16 20:11:57 +00:00
Quentin McGaw (desktop)
7c44188130 Fix: controlled interrupt exit for subprograms
- Openvpn and Unbound do not receive OS signals
- Openvpn and Unbound run in a different process group than the entrypoint
- Openvpn and Unbound are gracefully shutdown by the entrypoint
- Update golibs with a modified command package
- Update dns to v1.9.0 where Unbound is luanched in its own group
2021-07-16 20:04:17 +00:00
Quentin McGaw (desktop)
c2d527bbd3 Fix: openvpn run loop panic about stdout streams 2021-07-16 19:02:04 +00:00
Quentin McGaw (desktop)
ac3ff095a1 Maint: rework DNS run loop
- Fix fragile user triggered logic
- Simplify state
- Lock loop when crashed
2021-07-16 19:00:56 +00:00
Quentin McGaw (desktop)
0ed738cd61 Maint: make all set status context aware 2021-07-16 00:49:59 +00:00
Quentin McGaw (desktop)
6bbb7c8f7d Maint: remove outdated Auth log warning about PIA 2021-07-16 00:49:50 +00:00
Quentin McGaw (desktop)
d29429808c Maint: deduplicate error logs for goshutdown 2021-07-15 23:02:33 +00:00
Quentin McGaw (desktop)
09eccd7cd9 Fix: events routing behavior when version information is disabled 2021-07-15 22:43:30 +00:00
Quentin McGaw (desktop)
bb2b8b4514 Fix: events routing exit when gluetun stops at start 2021-07-15 22:42:58 +00:00
Quentin McGaw (desktop)
e20b9c5774 Doc: simplify metdata and move it at top of readme 2021-07-14 22:17:51 +00:00
Quentin McGaw (desktop)
3badfa197a Doc: use native markdown for svg title image 2021-07-14 22:08:40 +00:00
Quentin McGaw (desktop)
dee372e71b Doc: add video 2021-07-14 00:31:27 +00:00
Quentin McGaw (desktop)
679be6e1bd Feat: clean suffix new lines for credentials 2021-07-06 14:37:59 +00:00
Quentin McGaw (desktop)
92212fdd11 Fix: Cert validation for IPVanish 2021-07-01 18:28:24 +00:00
Quentin McGaw (desktop)
a6fb1ad9ef Feat: update IPVanish server information 2021-07-01 18:28:12 +00:00
Quentin McGaw (desktop)
87d712fbd7 Feature: update ProtonVPN server information 2021-06-28 15:30:35 +00:00
Quentin McGaw (desktop)
023809f099 Feature: upgrade to Alpine 3.14
- Release note: https://wiki.alpinelinux.org/wiki/Release_Notes_for_Alpine_3.14.0
2021-06-25 19:01:00 +00:00
Quentin McGaw (desktop)
ace37370d1 Maint: xcputranslate version as build argument 2021-06-25 18:57:04 +00:00
Quentin McGaw (desktop)
8efbd4fac1 Maint: download golangci-lint from qmcgaw/binpot 2021-06-25 18:56:18 +00:00
Quentin McGaw (desktop)
06c8792887 Doc: clarify setup instructions 2021-06-22 15:21:49 +00:00
Quentin McGaw (desktop)
3ea376a1b2 Doc: maintenance document 2021-06-22 14:42:15 +00:00
Quentin McGaw (desktop)
9667d30907 Doc: add code highlighting how-to to issue templates 2021-06-22 14:41:48 +00:00
Quentin McGaw (desktop)
3f7ccc6c49 Feature: improve Cyberghost updater
- Waits up to 20s for resolutions
- Update server information and timestamp
2021-06-21 20:29:55 +00:00
Quentin McGaw (desktop)
dd97ff5895 Maintenance: cache xcputranslate 2021-06-21 18:50:30 +00:00
Quentin McGaw (desktop)
2e4d80d9bc Maintenance: sleep for cross building 2021-06-21 18:35:40 +00:00
Quentin McGaw (desktop)
1227dc5a2b Maintenance: upgrade xcputranslate to v0.6.0 2021-06-21 18:01:21 +00:00
Quentin McGaw (desktop)
ed828bc733 Hotfix: VPN Unlimited variable choices 2021-06-21 13:32:03 +00:00
Quentin McGaw (desktop)
c25a018c05 Maintenance: CI deduplicate base stage build 2021-06-21 13:01:53 +00:00
Quentin McGaw (desktop)
266596af68 Fix errors introduced with golangci-lint 1.41.1 2021-06-20 16:39:38 +00:00
Quentin McGaw
2c77b73ebc IPVanish support (#475)
- Fix #410 and #416
2021-06-20 09:21:48 -07:00
Quentin McGaw
d81d4bbda3 VPN Unlimited support (#499)
- Fixes #420 
- Revert to docker/build-push-action@v2.4.0
2021-06-20 09:18:03 -07:00
Quentin McGaw (desktop)
400affe429 Maintenance: add revive linter 2021-06-20 16:12:39 +00:00
Quentin McGaw (desktop)
d3c63680e8 Maintenance: ugprade golangci-lint to v1.41.1 2021-06-20 16:12:09 +00:00
Quentin McGaw (desktop)
28de8a834c Maintenance: upgrade golang/mock to v1.6.0 2021-06-19 17:24:41 +00:00
Quentin McGaw (desktop)
208374fc54 Fix: Use name prefix for TLS check for IVPN 2021-06-19 16:34:50 +00:00
Quentin McGaw (desktop)
535a136a27 Feature: add IVPN Bulgaria and Spain servers 2021-06-19 16:34:36 +00:00
Quentin McGaw (desktop)
ba4c3e30a4 Doc: docker-compose.yml does not use secrets 2021-06-17 22:46:30 +00:00
Quentin McGaw (desktop)
16d8a388cb Maintenance: better layer caching
- Install g++ in base image before copying code
- Install xcputranslate in base image before copying code
- Install golangci-lint in base image before copying code
- Install golangci-lint using go get directly
2021-06-15 12:27:32 +00:00
Quentin McGaw (desktop)
5ea31b0b64 Maintenance: set entrypoint for test Docker stage 2021-06-15 12:25:57 +00:00
Quentin McGaw (desktop)
582c6d1c43 Fix: only use and write auth file if user is set
- Apply to custom openvpn configuration without username
2021-06-14 14:25:37 +00:00
Quentin McGaw (desktop)
c63ae3f3af Fix: custom openvpn config settings log 2021-06-14 14:24:38 +00:00
Quentin McGaw (desktop)
4c0df96a95 Maintenance: use github.com/qdm12/goshutdown 2021-06-10 15:03:47 +00:00
Quentin McGaw (desktop)
05c6b9379a Maintenance: prevent exit race condition for loops 2021-06-10 14:13:08 +00:00
Quentin McGaw (desktop)
fb7fdcd925 Fix: change PureVPN default cipher to AES-256-GCM 2021-06-08 00:24:46 +00:00
Quentin McGaw (desktop)
1774e2ad88 Maintenance: update list of linters 2021-06-07 23:31:52 +00:00
Quentin McGaw (desktop)
a402d9135e Fix: remote line for custom OpenVPN config 2021-06-07 19:46:21 +00:00
Quentin McGaw (desktop)
3d2c56d9ee Fix: custom cipher for custom files on Openvpn 2.5 2021-06-07 19:45:19 +00:00
Quentin McGaw (desktop)
f9308e6fed Remove dependency on github.com/kyokomi/emoji 2021-06-06 15:38:49 +00:00
Quentin McGaw (desktop)
6710468020 Maintenance: upgrade Go dependencies
- Upgrade fatih/color to v1.12.0
- Upgrade qdm12/dns to v1.8.0
- Upgrade qdm12/golibs
- Upgrade qdm12/updated
2021-06-03 21:31:50 +00:00
Quentin McGaw (desktop)
ad1981fff6 Maintenance: update PureVPN server information 2021-06-02 14:32:15 +00:00
Quentin McGaw (desktop)
01f9e71912 Fix: none encryption preset for PIA
- Set cipher and auth to `none`
- Add `ncp-disable` OpenVPN option in every case
2021-06-01 13:52:57 +00:00
Quentin McGaw (desktop)
d41b75ee35 Documentation: add discussion link for help issues 2021-06-01 13:44:04 +00:00
Quentin McGaw (desktop)
b829490aac Feature: OPENVPN_VERSION which can be 2.4 or 2.5 2021-05-31 18:54:36 +00:00
Quentin McGaw (desktop)
7002bf8e34 Maintenance: improve printVersion function
- Print program versions in order given
- Exit program on any error as each program is required
2021-05-31 18:47:38 +00:00
Quentin McGaw (desktop)
625ea493fb Maintenance: remove unused openvpn files 2021-05-31 17:55:56 +00:00
Quentin McGaw (desktop)
79b3b2823b Hotfix: remote line for Hidemyass and ivpn 2021-05-31 02:37:20 +00:00
Quentin McGaw (desktop)
9be912e9fd HotFix: IVPN add TCP and UDP fields 2021-05-31 00:41:44 +00:00
Quentin McGaw (desktop)
3c3cd431cd Feature: Support none encryption preset for PIA 2021-05-31 00:32:39 +00:00
Quentin McGaw (desktop)
8b8bab5c58 Feature: IVPN support 2021-05-31 00:11:16 +00:00
Quentin McGaw (desktop)
835fa6c41f Fix: HideMyAss Openvpn remote line 2021-05-30 21:25:55 +00:00
Quentin McGaw (desktop)
8a6cf221a9 Fix: HideMyAss hostnames choices 2021-05-30 20:27:57 +00:00
Quentin McGaw (desktop)
876563c492 Maintenance: improve error wrapping 2021-05-30 16:14:08 +00:00
Quentin McGaw (desktop)
be22c8547f Maintenance: use io instead of ioutil if possible 2021-05-30 03:13:19 +00:00
Quentin McGaw (desktop)
82d98c4859 Maintenance: add more linters to .golangci.yml 2021-05-30 03:09:22 +00:00
Quentin McGaw (desktop)
f1b5341f33 Maintenance: listen on all IP interfaces 2021-05-30 02:58:10 +00:00
Quentin McGaw (desktop)
b3829493ea Maintenance: upgrade ss-server to v0.2.0 2021-05-28 16:26:26 +00:00
Quentin McGaw (desktop)
7db1253967 Maintenance: upgrade golangci-lint to 1.40.1 2021-05-28 16:24:06 +00:00
Quentin McGaw (desktop)
449db40d5f Feature: make Shadowsocks password compulsory 2021-05-28 16:23:44 +00:00
Quentin McGaw
d5d0311bc6 Documentation: issue template warnings 2021-05-25 20:11:23 +00:00
Quentin McGaw
0c4f01a892 Feature: Protonvpn filter servers with FREE_ONLY 2021-05-23 21:51:12 +00:00
Quentin McGaw
bc7246f882 Maintenance: update ProtonVPN server information 2021-05-23 17:40:25 +00:00
Quentin McGaw
da65f3b016 Maintenance: generate Openvpn conf for 2.4 or 2.5 2021-05-23 17:40:14 +00:00
Quentin McGaw
a8c574219d Fix: log level for TLS error from debug to warn 2021-05-23 16:24:04 +00:00
Quentin McGaw
a3751a77aa Fix: log custom port only if set (PIA, Windscribe) 2021-05-19 17:53:11 +00:00
Quentin McGaw
4f521e4dcb Feature: show Alpine version at start 2021-05-19 14:30:43 +00:00
Quentin McGaw
a9589d8d5b Fix: only use Openvpn fast-io when using UDP 2021-05-18 23:46:20 +00:00
Quentin McGaw
13e75aaf20 Maintenance: upgrade to qdm12/dns v1.7.0
- Fix rebinding protection for IPv6 mapped IPv4 networks
- Use netaddr package for DNS blacklisting
2021-05-14 17:54:35 +00:00
Quentin McGaw
0c9bd8aaa0 Maintenance: upgrade golang.org/x/sys 2021-05-14 14:08:55 +00:00
Quentin McGaw
5dba91c9ab Maintenance: qdm12/dns from v1.4.0 to v1.6.0 2021-05-14 14:07:17 +00:00
Quentin McGaw
7d6763cde7 Maintenance: upgrade golibs (affects logger) 2021-05-14 14:07:16 +00:00
dependabot[bot]
dd1b23773e Bump actions/checkout from 2 to 2.3.4 (#453)
Bumps [actions/checkout](https://github.com/actions/checkout) from 2 to 2.3.4.
- [Release notes](https://github.com/actions/checkout/releases)
- [Changelog](https://github.com/actions/checkout/blob/main/CHANGELOG.md)
- [Commits](https://github.com/actions/checkout/compare/v2...v2.3.4)

Signed-off-by: dependabot[bot] <support@github.com>

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2021-05-12 17:25:07 -04:00
Quentin McGaw
33253c0cfc Fix: PIA port forwarding nil url map 2021-05-12 12:47:34 +00:00
Quentin McGaw
0099c06056 Hotfix: remove unused code 2021-05-12 00:57:00 +00:00
Quentin McGaw
1540660cc3 Change: keep firewall on shutdown to avoid leaks 2021-05-11 22:25:42 +00:00
Quentin McGaw
cff5e693d2 Maintenance: shutdown order
- Order of threads to shutdown (control then tickers then health etc.)
- Rely on closing channels instead of waitgroups
- Move exit logs from each package to the shutdown package
2021-05-11 22:24:32 +00:00
Quentin McGaw
5159c1dc83 Maintenance: remove outdated Dockerfile comment 2021-05-11 22:13:16 +00:00
Quentin McGaw
ccc7ad7cbd Change: do not exit on Openvpn config error 2021-05-11 18:23:19 +00:00
Quentin McGaw
c8a61ca687 Maintenance: use signal.NotifyContext 2021-05-11 18:17:59 +00:00
Quentin McGaw
61e36d6aff Maintenance: error wrapping of alpine package 2021-05-11 17:52:29 +00:00
Quentin McGaw
e8c8742bae Maintenance: split each provider in a package
- Fix VyprVPN port
- Fix missing Auth overrides
2021-05-11 17:10:51 +00:00
Quentin McGaw
1cb93d76ed Feature: only teardown routing if changes occurred 2021-05-10 22:16:26 +00:00
Quentin McGaw
dadc939aab Feature: NET_ADMIN tip on routing permission error 2021-05-10 21:31:08 +00:00
Quentin McGaw
c59ea781e3 Maintenance: Protocol selection as boolean in code 2021-05-10 18:18:12 +00:00
Quentin McGaw
810ff62c26 Maintenance: improve error codes in IP routing 2021-05-10 17:33:31 +00:00
Quentin McGaw
5a0418bba6 Feature: re-fetch PIA API to obtain more servers 2021-05-10 16:17:44 +00:00
Quentin McGaw
baf506ae27 Feature: multiple IP addresses per PIA server 2021-05-10 15:44:46 +00:00
Quentin McGaw
52ff03ae41 Feature: 3 IP addresses per Windscribe server 2021-05-10 14:34:42 +00:00
Quentin McGaw
2d95edf8ab Feature: Filter VyprVPN servers by hostname, and:
- Extract if server supports TCP and UDP (never TCP now)
- Filter servers by protocol (unused for now)
2021-05-10 02:12:13 +00:00
Quentin McGaw
95b0fb81d6 Feature: Multiple IPs for each Torguard server
- Fallback on IP from configuration file if DNS resolution fails
- Download both TCP and UDP zip files to detect support for each
- Filter servers by supported network protocol
-
2021-05-10 01:48:52 +00:00
Quentin McGaw
eff65dce00 Feature: filter Surfshark servers by hostname 2021-05-10 01:24:46 +00:00
Quentin McGaw
6c1c069261 Feature: filter by hostname for PureVPN servers
- Record support for TCP and UDP for each hostname
- Fix: each hostname supports only TCP or UDP, not both
- Update PureVPN server information
2021-05-10 00:36:14 +00:00
Quentin McGaw
4fe1e062f2 Feature: filter PIA servers by hostname and name 2021-05-09 16:49:22 +00:00
Quentin McGaw
1fb0840e72 Maintenance: Privado server not found error 2021-05-09 16:32:59 +00:00
Quentin McGaw
689ddf8bf0 Maintenance: fix flakky ip unit test 2021-05-09 03:30:54 +00:00
Quentin McGaw
d243ac49f3 Fix #444 Mullvad servers filtering 2021-05-09 01:56:02 +00:00
Quentin McGaw
de8f018b14 Feature: Snyk code analysis for code and image 2021-05-09 01:11:58 +00:00
Quentin McGaw
8407542600 Feature: filter by country, region and city for Privado 2021-05-09 00:51:34 +00:00
Quentin McGaw
a7a5cca8dd Maintenance: parallelize IP information fetch 2021-05-08 23:37:32 +00:00
Quentin McGaw
d9a70fd094 Maintenance: improve publicip with Result struct 2021-05-08 23:30:29 +00:00
Quentin McGaw
248cc0d3d3 Feature: filter by name and hostname for NordVPN 2021-05-08 22:51:59 +00:00
Quentin McGaw
2924d711cb Maintenance: add empty SERVER_NAME in Dockerfile 2021-05-08 19:32:52 +00:00
Quentin McGaw
d7db105a2f Fix: ProtonVPN SERVER_NAME 2021-05-08 19:32:16 +00:00
Quentin McGaw
2ec2f45c82 Feature: filter by hostname for Mullvad servers 2021-05-08 19:17:36 +00:00
Quentin McGaw
a34769ae02 Feature: filter by hostname for Cyberghost servers 2021-05-08 19:05:11 +00:00
Quentin McGaw
c0e4d805b1 Maintenance: storage package logTimeDiff function 2021-05-08 02:51:39 +00:00
Quentin McGaw
6770336274 Maintenance: add missing server merging logic 2021-05-08 01:15:49 +00:00
Quentin McGaw
8d431dbb34 Feature update all server information 2021-05-08 01:03:09 +00:00
Quentin McGaw
e8e7b83297 Maintenance: refactor servers updater code
- Require at least 80% of number of servers now to pass
- Each provider is in its own package with a common structure
- Unzip package with unzipper interface
- Openvpn package with extraction and download functions
2021-05-08 00:59:42 +00:00
Quentin McGaw
442340dcf2 Feature: create /gluetun if it does not exist 2021-05-06 21:10:28 +00:00
Quentin McGaw
91b037a335 Feature: update hardcoded servers for DNS resolution based VPN providers 2021-05-06 18:51:31 +00:00
Quentin McGaw
d5ef3de64c Feature: more robust updater DNS resolution
- Parallel resolver to resolve multiple hosts
- Repeat resolver to repeat resolution for a single host
- Additional parameters for fault toleration
- Do not update servers if e.g. > 10% DNS resolutions failed
- resolver package in updater package
2021-05-06 18:48:14 +00:00
Quentin McGaw
167a0b0b29 Restart unhealthy (#417) (#441) 2021-05-04 15:36:12 -04:00
Quentin McGaw
954e3c70b2 Feature: Protonvpn support (#437 clone on #434) 2021-04-25 15:44:45 -04:00
Quentin McGaw
b02a80abbd Feature: update PIA server information 2021-04-24 13:54:13 +00:00
Quentin McGaw
04313d3c3b Maintenance: devcontainer changes
- Bind mount for root only
- Support for Windows Hyperv bind mounts
- Run go mod tidy after go mod download
- Use :z flag for possibly shared bind mounts
- Bind mount zsh_history
- Bind mount docker config directory
2021-04-24 13:53:48 +00:00
Quentin McGaw
fb8279f8f0 Fix: remove pull-filter ignore ping-restart 2021-04-19 19:51:00 +00:00
Quentin McGaw
e0e56595c6 Fix: only run ip6tables if it is supported by the Kernel (#431)
- Fix #430
2021-04-19 14:35:29 -04:00
Quentin McGaw
44d8cf9d4e Replace Surfshark default cipher with aes 256 gcm 2021-04-19 18:00:58 +00:00
Quentin McGaw
282c1e53ec Clear firewall rules on shutdown, fix #276 2021-04-19 14:27:38 +00:00
Quentin McGaw
7ba98af1cc Feature/Bugfix: IPv6 blocking (#428)
- Feature/Bugfix: Block all IPv6 traffic with `ip6tables` by default
- Feature: Adapt existing firewall code to handle IPv4 and IPv6, depending on user inputs and environment
- Maintenance: improve error wrapping in the firewall package
2021-04-19 09:24:46 -04:00
Quentin McGaw
d3df5aaa52 Upgrade system and package versions
- Alpine from 3.12 to 3.13 and:
- Openvpn from 2.4.10 to 2.5.1
- Unbound from 1.10.1 to 1.13.0
- Iptables from 1.8.4 to 1.8.6
2021-04-19 00:31:46 +00:00
Quentin McGaw
1c83dcab5e Maintenance: upgrade golangci-lint to 1.39.0 2021-04-19 00:20:43 +00:00
Quentin McGaw
6208081788 Fix: PIA port forwarding (#427)
- Update PIA token URL
- Change base64 decoding to standard decoding
- Add unit tests
- Remove environment variable `GODEBUG=x509ignoreCN=0`
- Fixes #423 
- Fixes #292 
- Closes #264 
- Closes #293
2021-04-17 16:21:17 -04:00
Quentin McGaw
3795e92a82 Hotfix: lint error in Surfshark constants 2021-04-16 22:37:51 +00:00
Quentin McGaw
0636123e7a Feature: add more Surfshark servers
- Add servers missing from surfshark zip file
- Fixes #424 and re-add multihop servers
- Fix logic to try resolving old vpn servers for Surfshark
2021-04-16 22:31:09 +00:00
Michael Robbins
69f9461bcd Fix: restricting route listing to IPv4 only (#419) 2021-04-11 08:50:59 -04:00
Quentin McGaw
d1558a3472 Fix lint error from PR merge 2021-04-09 17:44:22 +00:00
Michael Robbins
8230596f98 Feature: uplift the 'localSubnet' concept to cover all local ethernet interfaces (#413) 2021-04-09 13:08:20 -04:00
Quentin McGaw
cc4117e054 Change PIA settings, refers to #265 2021-04-01 18:53:21 +00:00
Quentin McGaw
a0ddbc037f Update new provider issue template 2021-04-01 18:29:55 +00:00
Quentin McGaw
de82d4e616 Fix: use udp by default for custom openvpn config 2021-03-15 02:13:10 +00:00
Quentin McGaw
fa220f9e93 Feature: custom Openvpn configuration file, fixes #223 (#402) 2021-03-13 08:51:05 -05:00
Quentin McGaw
aca112fa42 CI: Build for all architectures in branches 2021-03-09 00:16:24 +00:00
Quentin McGaw
9f4077d35d Feature: FastestVPN support (#383) 2021-03-05 23:12:19 -05:00
Quentin McGaw
9509b855f1 Feature: PrivateVPN support (#393) 2021-03-05 22:58:57 -05:00
Quentin McGaw
be72f4a046 Feature: Hide My Ass VPN provider support (#401) 2021-03-05 22:45:54 -05:00
Quentin McGaw
8b36ce198f Maintenance: 8.8.8.8 as the cli updater DNS 2021-03-05 22:46:21 +00:00
Quentin McGaw
71de05dc68 Maintenance: updater DNS resolution more resilient 2021-03-05 22:46:14 +00:00
Quentin McGaw
83b5a9457a Maintenance: upgrade golangci-lint to 1.37.0 2021-03-03 01:16:05 +00:00
Quentin McGaw
0b7ada9fd9 Maintenance: use Go 1.16 to build binary 2021-03-03 01:15:14 +00:00
Quentin McGaw
92bcef0b1c Maintenance: unique choices from hardcoded servers 2021-02-26 13:21:55 +00:00
Quentin McGaw
a10c4056d0 Maintenance: simplify env comments in Dockerfile 2021-02-26 13:02:43 +00:00
Quentin McGaw
1fd3ee7149 Maintenance: sort alphabetically providers in code 2021-02-26 12:58:58 +00:00
dependabot[bot]
e3a157bfe1 Maintenance: bump golang/mock from 1.4.4 to 1.5.0 (#394) 2021-02-26 07:39:29 -05:00
Quentin McGaw
b446aa6590 Maintenance: use native HTTP client for updater 2021-02-26 00:42:55 +00:00
Quentin McGaw
c54ee71e1d Maintenance: new logging, shorter with less deps 2021-02-25 23:51:29 +00:00
637 changed files with 142227 additions and 16614 deletions

View File

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

View File

@@ -19,6 +19,7 @@ It works on Linux, Windows and OSX.
1. Open the command palette in Visual Studio Code (CTRL+SHIFT+P).
1. Select `Remote-Containers: Open Folder in Container...` and choose the project directory.
1. For Docker running on Windows HyperV, if you want to use SSH keys, bind mount them at `/tmp/.ssh` by changing the `volumes` section in the [docker-compose.yml](docker-compose.yml).
## Customization

View File

@@ -8,7 +8,7 @@
"vscode"
],
"shutdownAction": "stopCompose",
"postCreateCommand": "go mod download",
"postCreateCommand": "~/.windows.sh && go mod download && go mod tidy",
"workspaceFolder": "/workspace",
"extensions": [
"golang.go",

View File

@@ -4,21 +4,29 @@ services:
vscode:
build: .
image: godevcontainer
devices:
- /dev/net/tun:/dev/net/tun
volumes:
- ../:/workspace
# Docker socket to access Docker server
- /var/run/docker.sock:/var/run/docker.sock
# SSH directory
- ~/.ssh:/home/vscode/.ssh
- ~/.ssh:/root/.ssh
# Docker configuration
- ~/.docker:/root/.docker:z
# SSH directory for Linux, OSX and WSL
- ~/.ssh:/root/.ssh:z
# For Windows without WSL, a copy will be made
# from /tmp/.ssh to ~/.ssh to fix permissions
#- ~/.ssh:/tmp/.ssh:ro
# Shell history persistence
- ~/.zsh_history:/root/.zsh_history:z
# Git config
- ~/.gitconfig:/home/districter/.gitconfig
- ~/.gitconfig:/root/.gitconfig
- ~/.gitconfig:/root/.gitconfig:z
environment:
- TZ=
cap_add:
# For debugging with dlv
- SYS_PTRACE
# - SYS_PTRACE
- NET_ADMIN
security_opt:
# For debugging with dlv
- seccomp:unconfined

View File

@@ -1,37 +0,0 @@
---
name: Bug
about: Report a bug
title: 'Bug: FILL THIS TEXT!'
labels: ":bug: bug"
assignees: qdm12
---
**Is this urgent?**: No
**Host OS** (approximate answer is fine too): Ubuntu 18
**CPU arch** or **device name**: amd64
**What VPN provider are you using**:
**What are you using to run your container?**: Docker Compose
**What is the version of the program** (See the line at the top of your logs)
```
Running version latest built on 2020-03-13T01:30:06Z (commit d0f678c)
```
**What's the problem** 🤔
That feature doesn't work
**Share your logs... (careful to remove in example tokens)**
```log
PASTE YOUR LOGS
IN THERE
```

107
.github/ISSUE_TEMPLATE/bug.yml vendored Normal file
View File

@@ -0,0 +1,107 @@
name: Bug
description: Report a bug
title: "Bug: "
labels: [":bug: bug"]
body:
- type: markdown
attributes:
value: |
Thanks for taking the time to fill out this bug report!
- type: dropdown
id: urgent
attributes:
label: Is this urgent?
description: |
Is this a critical bug, or do you need this fixed urgently?
If this is urgent, note you can use one of the [image tags available](https://github.com/qdm12/gluetun/wiki/Docker-image-tags) if that can help.
options:
- "No"
- "Yes"
- type: input
id: host-os
attributes:
label: Host OS
description: What is your host OS?
placeholder: "Debian Buster"
- type: dropdown
id: cpu-arch
attributes:
label: CPU arch
description: You can find it on Linux with `uname -m`.
options:
- x86_64
- aarch64
- armv7l
- "386"
- s390x
- ppc64le
- type: dropdown
id: vpn-service-provider
attributes:
label: VPN service provider
options:
- Custom
- Cyberghost
- ExpressVPN
- FastestVPN
- HideMyAss
- IPVanish
- IVPN
- Mullvad
- NordVPN
- Privado
- Private Internet Access
- PrivateVPN
- ProtonVPN
- PureVPN
- Surfshark
- TorGuard
- VPNUnlimited
- VyprVPN
- WeVPN
- Windscribe
validations:
required: true
- type: dropdown
id: docker
attributes:
label: What are you using to run the container
options:
- docker run
- docker-compose
- Portainer
- Kubernetes
- Podman
- Other
validations:
required: true
- type: input
id: version
attributes:
label: What is the version of Gluetun
description: |
Copy paste the version line at the top of your logs.
It should be in the form `Running version latest built on 2020-03-13T01:30:06Z (commit d0f678c)`.
validations:
required: true
- type: textarea
id: problem
attributes:
label: "What's the problem 🤔"
placeholder: "That feature does not work..."
validations:
required: true
- type: textarea
id: logs
attributes:
label: Share your logs
description: No sensitive information is logged out except when running with `LOG_LEVEL=debug`.
render: log
validations:
required: true
- type: textarea
id: config
attributes:
label: Share your configuration
description: Share your configuration such as `docker-compose.yml`. Ensure to remove credentials.
render: yml

7
.github/ISSUE_TEMPLATE/config.yml vendored Normal file
View File

@@ -0,0 +1,7 @@
contact_links:
- name: Configuration help?
url: https://github.com/qdm12/gluetun/discussions/new
about: Please create a Github discussion.
- name: Unraid template issue
url: https://github.com/qdm12/gluetun/discussions/550
about: Please read the relevant Github discussion.

View File

@@ -1,17 +0,0 @@
---
name: Feature request
about: Suggest a feature to add to this project
title: 'Feature request: FILL THIS TEXT!'
labels: ":bulb: feature request"
assignees: qdm12
---
**What's the feature?** 🧐
- Support this new feature because that and that
**Optional extra information** 🚀
- I tried `docker run something` and it doesn't work
- That [url](https://github.com/qdm12/gluetun) is interesting

View File

@@ -0,0 +1,19 @@
name: Feature request
description: Suggest a feature to add to Gluetun
title: "Feature request: "
labels: [":bulb: feature request"]
body:
- type: textarea
id: description
attributes:
label: "What's the feature 🧐"
placeholder: "Make the tunnel resistant to earth quakes"
validations:
required: true
- type: textarea
id: extra
attributes:
label: "Extra information and references"
placeholder: |
- I tried `docker run something` and it doesn't work
- That [url](https://github.com/qdm12/gluetun) is interesting

View File

@@ -1,53 +0,0 @@
---
name: Help
about: Ask for help
title: 'Help: FILL THIS TEXT!'
labels: ":pray: help wanted"
assignees:
---
**Is this urgent?**: No
**Host OS** (approximate answer is fine too): Ubuntu 18
**CPU arch** or **device name**: amd64
**What VPN provider are you using**:
**What is the version of the program** (See the line at the top of your logs)
```
Running version latest built on 2020-03-13T01:30:06Z (commit d0f678c)
```
**What's the problem** 🤔
That feature doesn't work
**Share your logs... (careful to remove in example tokens)**
```log
PASTE YOUR LOGS
IN THERE
```
**What are you using to run your container?**: Docker Compose
Please also share your configuration file:
```yml
your .yml
content
in here
```
or
```sh
# your docker
# run command
# in here
```

View File

@@ -13,4 +13,5 @@ One of the following is required:
- Publicly accessible URL to the list of servers **and attach** an example Openvpn configuration file for both TCP and UDP
If the list of servers requires to login **or** is hidden behind an interactive configurator,
it's not possible to support the provider yet. Please instead subscribe to issue #223 which would solve it.
you can only use a custom Openvpn configuration file.
[The Wiki](https://github.com/qdm12/gluetun/wiki/Openvpn-file) describes how to do so.

18
.github/ISSUE_TEMPLATE/wiki issue.yml vendored Normal file
View File

@@ -0,0 +1,18 @@
name: Wiki issue
description: Report a Wiki issue
title: "Wiki issue: "
labels: ["📄 Wiki issue"]
body:
- type: input
id: url
attributes:
label: "URL to the Wiki page"
placeholder: "https://github.com/qdm12/gluetun/wiki/OpenVPN-options"
validations:
required: true
- type: textarea
id: description
attributes:
label: "What's the issue?"
validations:
required: true

40
.github/labels.yml vendored
View File

@@ -14,22 +14,53 @@
color: "795548"
description: ""
# Priority
- name: "🚨 Urgent"
color: "d5232f"
description: ""
- name: "💤 Low priority"
color: "4285f4"
description: ""
# VPN providers
- name: ":cloud: Cyberghost"
color: "cfe8d4"
description: ""
- name: ":cloud: HideMyAss"
color: "cfe8d4"
description: ""
- name: ":cloud: IPVanish"
color: "cfe8d4"
description: ""
- name: ":cloud: IVPN"
color: "cfe8d4"
description: ""
- name: ":cloud: ExpressVPN"
color: "cfe8d4"
description: ""
- name: ":cloud: FastestVPN"
color: "cfe8d4"
description: ""
- name: ":cloud: Mullvad"
color: "cfe8d4"
description: ""
- name: ":cloud: NordVPN"
color: "cfe8d4"
description: ""
- name: ":cloud: Perfect Privacy"
color: "cfe8d4"
description: ""
- name: ":cloud: PIA"
color: "cfe8d4"
description: ""
- name: ":cloud: Privado"
color: "cfe8d4"
description: ""
- name: ":cloud: PrivateVPN"
color: "cfe8d4"
description: ""
- name: ":cloud: ProtonVPN"
color: "cfe8d4"
- name: ":cloud: PureVPN"
color: "cfe8d4"
description: ""
@@ -39,9 +70,15 @@
- name: ":cloud: Torguard"
color: "cfe8d4"
description: ""
- name: ":cloud: VPNUnlimited"
color: "cfe8d4"
description: ""
- name: ":cloud: Vyprvpn"
color: "cfe8d4"
description: ""
- name: ":cloud: WeVPN"
color: "cfe8d4"
description: ""
- name: ":cloud: Windscribe"
color: "cfe8d4"
description: ""
@@ -50,6 +87,9 @@
- name: "Openvpn"
color: "ffc7ea"
description: ""
- name: "Wireguard"
color: "ffc7ea"
description: ""
- name: "Unbound (DNS over TLS)"
color: "ffc7ea"
description: ""

View File

@@ -1,100 +0,0 @@
name: CI
on:
push:
paths:
- .github/workflows/build.yml
- cmd/**
- internal/**
- pkg/**
- .dockerignore
- .golangci.yml
- Dockerfile
- go.mod
- go.sum
jobs:
verify:
runs-on: ubuntu-latest
env:
DOCKER_BUILDKIT: "1"
steps:
- uses: actions/checkout@v2
- name: Linting
run: docker build --target lint .
- name: Go mod tidy check
run: docker build --target tidy .
- name: Build test image
run: docker build --target test -t test-container .
- name: Run tests in test container
run: |
touch coverage.txt
docker run --rm \
-v "$(pwd)/coverage.txt:/tmp/gobuild/coverage.txt" \
test-container \
go test \
-race \
-coverpkg=./... \
-coverprofile=coverage.txt \
-covermode=atomic \
./...
# We run this here to use the caching of the previous steps
- if: github.event_name == 'push'
name: Build final image
run: docker build .
publish:
needs: [verify]
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- uses: docker/setup-qemu-action@v1
- uses: docker/setup-buildx-action@v1
- uses: docker/login-action@v1
with:
username: qmcgaw
password: ${{ secrets.DOCKERHUB_PASSWORD }}
- name: Set variables
id: vars
env:
EVENT_NAME: ${{ github.event_name }}
run: |
BRANCH=${GITHUB_REF#refs/heads/}
TAG=${GITHUB_REF#refs/tags/}
echo ::set-output name=commit::$(git rev-parse --short HEAD)
echo ::set-output name=build_date::$(date -u +%Y-%m-%dT%H:%M:%SZ)
if [ "$TAG" != "$GITHUB_REF" ]; then
echo ::set-output name=version::$TAG
echo ::set-output name=platforms::linux/amd64,linux/386,linux/arm64,linux/arm/v6,linux/arm/v7,linux/ppc64le
elif [ "$BRANCH" = "master" ]; then
echo ::set-output name=version::latest
echo ::set-output name=platforms::linux/amd64,linux/386,linux/arm64,linux/arm/v6,linux/arm/v7,linux/ppc64le
else
echo ::set-output name=version::$BRANCH
echo ::set-output name=platforms::linux/amd64
fi
- name: Build and push final image
uses: docker/build-push-action@v2
with:
platforms: ${{ steps.vars.outputs.platforms }}
build-args: |
BUILD_DATE=${{ steps.vars.outputs.build_date }}
COMMIT=${{ steps.vars.outputs.commit }}
VERSION=${{ steps.vars.outputs.version }}
tags: |
qmcgaw/gluetun:${{ steps.vars.outputs.version }}
qmcgaw/private-internet-access:${{ steps.vars.outputs.version }}
push: true
- if: github.event_name == 'push' && github.event.ref == 'refs/heads/master'
name: Microbadger hook
run: curl -X POST https://hooks.microbadger.com/images/qmcgaw/gluetun/l-keGI7p4IhX4QuIDMFYKhsZ1L0=
continue-on-error: true

144
.github/workflows/ci.yml vendored Normal file
View File

@@ -0,0 +1,144 @@
name: CI
on:
release:
types:
- published
push:
branches:
- master
paths:
- .github/workflows/ci.yml
- cmd/**
- internal/**
- pkg/**
- .dockerignore
- .golangci.yml
- Dockerfile
- go.mod
- go.sum
pull_request:
branches:
- master
paths:
- .github/workflows/ci.yml
- cmd/**
- internal/**
- pkg/**
- .dockerignore
- .golangci.yml
- Dockerfile
- go.mod
- go.sum
jobs:
verify:
# Only run if it's a push event or if it's a PR from this repository, and it is not dependabot.
if: |
github.actor != 'dependabot[bot]' &&
(github.event_name == 'push' ||
github.event_name == 'release' ||
(github.event_name == 'pull_request' && github.event.pull_request.head.repo.full_name == github.repository))
runs-on: ubuntu-latest
env:
DOCKER_BUILDKIT: "1"
steps:
- uses: actions/checkout@v2.4.0
- name: Linting
run: docker build --target lint .
- name: Go mod tidy check
run: docker build --target tidy .
- name: Build test image
run: docker build --target test -t test-container .
- name: Run tests in test container
run: |
touch coverage.txt
docker run --rm \
-v "$(pwd)/coverage.txt:/tmp/gobuild/coverage.txt" \
test-container
- name: Code security analysis
uses: snyk/actions/golang@master
env:
SNYK_TOKEN: ${{ secrets.SNYK_TOKEN }}
- name: Build final image
run: docker build -t final-image .
# - name: Image security analysis
# uses: snyk/actions/docker@master
# env:
# SNYK_TOKEN: ${{ secrets.SNYK_TOKEN }}
# with:
# image: final-image
publish:
# Only run if it's a push event or if it's a PR from this repository
if: |
github.event_name == 'push' ||
github.event_name == 'release' ||
(github.event_name == 'pull_request' && github.event.pull_request.head.repo.full_name == github.repository)
needs: [verify]
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2.4.0
- uses: docker/setup-qemu-action@v1
- uses: docker/setup-buildx-action@v1
- uses: docker/login-action@v1
with:
username: qmcgaw
password: ${{ secrets.DOCKERHUB_PASSWORD }}
- name: Check for semver tag
id: semvercheck
run: |
if [[ ${{ github.ref }} =~ ^refs/tags/v[0-9]+\.[0-9]+\.[0-9]+$ ]]; then
MATCH=true
else
MATCH=false
fi
if [[ ! ${{ github.ref }} =~ ^refs/tags/v0\. ]]; then
MATCH=$MATCH_nonzero
fi
echo ::set-output name=match::$MATCH
# extract metadata (tags, labels) for Docker
# https://github.com/docker/metadata-action
- name: Extract Docker metadata
id: meta
uses: docker/metadata-action@v3
with:
flavor: |
latest=${{ github.ref == 'refs/heads/master' }}
images: |
qmcgaw/gluetun
qmcgaw/private-internet-access
tags: |
type=ref,event=branch,enable=${{ github.ref != 'refs/heads/master' }}
type=ref,event=pr
type=ref,event=tag,enable=${{ !startsWith(steps.semvercheck.outputs.match, 'true') }}
type=semver,pattern=v{{major}}.{{minor}}.{{patch}},enable=${{ startsWith(steps.semvercheck.outputs.match, 'true') }}
type=semver,pattern=v{{major}}.{{minor}},enable=${{ startsWith(steps.semvercheck.outputs.match, 'true') }}
type=semver,pattern=v{{major}},enable=${{ startsWith(steps.semvercheck.outputs.match, 'true_nonzero') }}
type=raw,value=latest,enable=${{ !startsWith(steps.semvercheck.outputs.match, 'true') }}
- name: Short commit
id: shortcommit
run: echo "::set-output name=value::$(git rev-parse --short HEAD)"
- name: Build and push final image
uses: docker/build-push-action@v2.7.0
with:
platforms: linux/amd64,linux/386,linux/arm64,linux/arm/v6,linux/arm/v7,linux/ppc64le
labels: ${{ steps.meta.outputs.labels }}
build-args: |
CREATED=${{ fromJSON(steps.meta.outputs.json).labels['org.opencontainers.image.created'] }}
COMMIT=${{ steps.shortcommit.outputs.value }}
VERSION=${{ fromJSON(steps.meta.outputs.json).labels['org.opencontainers.image.version'] }}
tags: ${{ steps.meta.outputs.tags }}
push: true

37
.github/workflows/dependabot.yml vendored Normal file
View File

@@ -0,0 +1,37 @@
name: Dependabot
on:
pull_request:
branches:
- master
paths:
- .github/workflows/dependabot.yml
- cmd/**
- internal/**
- pkg/**
- .dockerignore
- .golangci.yml
- Dockerfile
- go.mod
- go.sum
jobs:
verify:
if: ${{ github.actor == 'dependabot[bot]' }}
runs-on: ubuntu-latest
env:
DOCKER_BUILDKIT: "1"
steps:
- uses: actions/checkout@v2.4.0
- name: Build test image
run: docker build --target test -t test-container .
- name: Run tests in test container
run: |
touch coverage.txt
docker run --rm \
-v "$(pwd)/coverage.txt:/tmp/gobuild/coverage.txt" \
test-container
- name: Build final image
run: docker build -t final-image .

View File

@@ -10,7 +10,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v2
uses: actions/checkout@v2.4.0
- name: Docker Hub Description
uses: peter-evans/dockerhub-description@v2
with:

40
.github/workflows/fork.yml vendored Normal file
View File

@@ -0,0 +1,40 @@
name: Fork
on:
pull_request:
branches:
- master
paths:
- .github/workflows/fork.yml
- cmd/**
- internal/**
- pkg/**
- .dockerignore
- .golangci.yml
- Dockerfile
- go.mod
- go.sum
jobs:
verify:
if: github.event.pull_request.head.repo.full_name != github.repository && github.actor != 'dependabot[bot]'
runs-on: ubuntu-latest
env:
DOCKER_BUILDKIT: "1"
steps:
- uses: actions/checkout@v2.4.0
- name: Linting
run: docker build --target lint .
- name: Build test image
run: docker build --target test -t test-container .
- name: Run tests in test container
run: |
touch coverage.txt
docker run --rm \
-v "$(pwd)/coverage.txt:/tmp/gobuild/coverage.txt" \
test-container
- name: Build final image
run: docker build -t final-image .

View File

@@ -9,7 +9,7 @@ jobs:
labeler:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- uses: actions/checkout@v2.4.0
- uses: crazy-max/ghaction-github-labeler@v3
with:
yaml-file: .github/labels.yml

View File

@@ -8,7 +8,7 @@ jobs:
misspell:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- uses: actions/checkout@v2.4.0
- uses: reviewdog/action-misspell@v1
with:
locale: "US"

1
.gitignore vendored Normal file
View File

@@ -0,0 +1 @@
scratch.txt

View File

@@ -1,6 +1,4 @@
linters-settings:
maligned:
suggest-new: true
misspell:
locale: US
@@ -10,23 +8,42 @@ issues:
linters:
- dupl
- maligned
- goerr113
- path: internal/server/
linters:
- dupl
- path: internal/configuration/
linters:
- dupl
- path: internal/constants/
linters:
- dupl
- text: "exported: exported var Err*"
linters:
- revive
- text: "mnd: Magic number: 0644*"
linters:
- gomnd
- text: "mnd: Magic number: 0400*"
linters:
- gomnd
linters:
disable-all: true
enable:
# - cyclop
# - errorlint
# - ireturn
# - varnamelen
# - wrapcheck
- asciicheck
- bidichk
- bodyclose
- deadcode
- dogsled
- dupl
- errcheck
- durationcheck
- errname
- exhaustive
- exportloopref
- forcetypeassert
- gci
- gochecknoglobals
- gochecknoinits
@@ -35,34 +52,37 @@ linters:
- gocritic
- gocyclo
- godot
- goerr113
- goheader
- goimports
- golint
- gomnd
- gomoddirectives
- goprintffuncname
- gosec
- gosimple
- govet
- ineffassign
- interfacer
- ifshort
- importas
- lll
- maligned
- makezero
- misspell
- nakedret
- nestif
- nilerr
- nilnil
- noctx
- nolintlint
- prealloc
- predeclared
- predeclared
- promlinter
- revive
- rowserrcheck
- scopelint
- sqlclosecheck
- staticcheck
- structcheck
- typecheck
- tenv
- thelper
- tparallel
- unconvert
- unparam
- unused
- varcheck
- wastedassign
- whitespace
run:

View File

@@ -1,31 +1,36 @@
ARG ALPINE_VERSION=3.12
ARG GO_VERSION=1.15
ARG ALPINE_VERSION=3.14
ARG GO_ALPINE_VERSION=3.14
ARG GO_VERSION=1.17
ARG XCPUTRANSLATE_VERSION=v0.6.0
ARG GOLANGCI_LINT_VERSION=v1.43.0
ARG BUILDPLATFORM=linux/amd64
FROM --platform=$BUILDPLATFORM golang:${GO_VERSION}-alpine${ALPINE_VERSION} AS base
RUN apk --update add git
FROM --platform=${BUILDPLATFORM} qmcgaw/xcputranslate:${XCPUTRANSLATE_VERSION} AS xcputranslate
FROM --platform=${BUILDPLATFORM} qmcgaw/binpot:golangci-lint-${GOLANGCI_LINT_VERSION} AS golangci-lint
FROM --platform=${BUILDPLATFORM} golang:${GO_VERSION}-alpine${GO_ALPINE_VERSION} AS base
COPY --from=xcputranslate /xcputranslate /usr/local/bin/xcputranslate
RUN apk --update add git g++
ENV CGO_ENABLED=0
COPY --from=golangci-lint /bin /go/bin/golangci-lint
WORKDIR /tmp/gobuild
COPY go.mod go.sum ./
RUN go mod download
COPY cmd/ ./cmd/
COPY internal/ ./internal/
FROM --platform=$BUILDPLATFORM base AS test
FROM --platform=${BUILDPLATFORM} base AS test
# Note on the go race detector:
# - we set CGO_ENABLED=1 to have it enabled
# - we install g++ to support the race detector
# - we installed g++ to support the race detector
ENV CGO_ENABLED=1
RUN apk --update --no-cache add g++
ENTRYPOINT go test -race -coverpkg=./... -coverprofile=coverage.txt -covermode=atomic ./...
FROM --platform=$BUILDPLATFORM base AS lint
ARG GOLANGCI_LINT_VERSION=v1.35.2
RUN wget -O- -nv https://raw.githubusercontent.com/golangci/golangci-lint/master/install.sh | \
sh -s -- -b /usr/local/bin ${GOLANGCI_LINT_VERSION}
FROM --platform=${BUILDPLATFORM} base AS lint
COPY .golangci.yml ./
RUN golangci-lint run --timeout=10m
FROM --platform=$BUILDPLATFORM base AS tidy
FROM --platform=${BUILDPLATFORM} base AS tidy
RUN git init && \
git config user.email ci@localhost && \
git config user.name ci && \
@@ -34,27 +39,26 @@ RUN git init && \
go mod tidy && \
git diff --exit-code -- go.mod
FROM --platform=$BUILDPLATFORM base AS build
COPY --from=qmcgaw/xcputranslate:v0.4.0 /xcputranslate /usr/local/bin/xcputranslate
FROM --platform=${BUILDPLATFORM} base AS build
ARG TARGETPLATFORM
ARG VERSION=unknown
ARG BUILD_DATE="an unknown date"
ARG CREATED="an unknown date"
ARG COMMIT=unknown
RUN GOARCH="$(xcputranslate -field arch -targetplatform ${TARGETPLATFORM})" \
GOARM="$(xcputranslate -field arm -targetplatform ${TARGETPLATFORM})" \
RUN GOARCH="$(xcputranslate translate -field arch -targetplatform ${TARGETPLATFORM})" \
GOARM="$(xcputranslate translate -field arm -targetplatform ${TARGETPLATFORM})" \
go build -trimpath -ldflags="-s -w \
-X 'main.version=$VERSION' \
-X 'main.buildDate=$BUILD_DATE' \
-X 'main.created=$CREATED' \
-X 'main.commit=$COMMIT' \
" -o entrypoint cmd/gluetun/main.go
FROM alpine:${ALPINE_VERSION}
ARG VERSION=unknown
ARG BUILD_DATE="an unknown date"
ARG CREATED="an unknown date"
ARG COMMIT=unknown
LABEL \
org.opencontainers.image.authors="quentin.mcgaw@gmail.com" \
org.opencontainers.image.created=$BUILD_DATE \
org.opencontainers.image.created=$CREATED \
org.opencontainers.image.version=$VERSION \
org.opencontainers.image.revision=$COMMIT \
org.opencontainers.image.url="https://github.com/qdm12/gluetun" \
@@ -63,50 +67,72 @@ LABEL \
org.opencontainers.image.title="VPN swiss-knife like client for multiple VPN providers" \
org.opencontainers.image.description="VPN swiss-knife like client to tunnel to multiple VPN servers using OpenVPN, IPtables, DNS over TLS, Shadowsocks, an HTTP proxy and Alpine Linux"
ENV VPNSP=pia \
VERSION_INFORMATION=on \
PROTOCOL=udp \
VPN_TYPE=openvpn \
# OpenVPN
OPENVPN_PROTOCOL=udp \
OPENVPN_USER= \
OPENVPN_PASSWORD= \
OPENVPN_USER_SECRETFILE=/run/secrets/openvpn_user \
OPENVPN_PASSWORD_SECRETFILE=/run/secrets/openvpn_password \
OPENVPN_VERSION=2.5 \
OPENVPN_VERBOSITY=1 \
OPENVPN_FLAGS= \
OPENVPN_CIPHER= \
OPENVPN_AUTH= \
OPENVPN_ROOT=yes \
OPENVPN_TARGET_IP= \
OPENVPN_IPV6=off \
TZ= \
PUID= \
PGID= \
PUBLICIP_FILE="/tmp/gluetun/ip" \
# PIA, Windscribe, Surfshark, Cyberghost, Vyprvpn, NordVPN, PureVPN only
OPENVPN_USER= \
OPENVPN_PASSWORD= \
USER_SECRETFILE=/run/secrets/openvpn_user \
PASSWORD_SECRETFILE=/run/secrets/openvpn_password \
OPENVPN_CUSTOM_CONFIG= \
OPENVPN_INTERFACE=tun0 \
OPENVPN_PORT= \
# Wireguard
WIREGUARD_PRIVATE_KEY= \
WIREGUARD_PRESHARED_KEY= \
WIREGUARD_PUBLIC_KEY= \
WIREGUARD_ADDRESS= \
WIREGUARD_ENDPOINT_IP= \
WIREGUARD_ENDPOINT_PORT= \
WIREGUARD_INTERFACE=wg0 \
# VPN server filtering
REGION= \
# PIA only
COUNTRY= \
CITY= \
SERVER_HOSTNAME= \
# # Mullvad only:
ISP= \
OWNED=no \
# # Private Internet Access only:
PIA_ENCRYPTION=strong \
PORT_FORWARDING=off \
PORT_FORWARDING_STATUS_FILE="/tmp/gluetun/forwarded_port" \
# Mullvad and PureVPN only
COUNTRY= \
# Mullvad, PureVPN, Windscribe only
CITY= \
# Windscribe only
SERVER_HOSTNAME= \
# Mullvad only
ISP= \
OWNED=no \
# Mullvad and Windscribe only
PORT= \
# Cyberghost only
CYBERGHOST_GROUP="Premium UDP Europe" \
# # Cyberghost only:
OPENVPN_CLIENTCRT_SECRETFILE=/run/secrets/openvpn_clientcrt \
OPENVPN_CLIENTKEY_SECRETFILE=/run/secrets/openvpn_clientkey \
# NordVPN only
# # Nordvpn only:
SERVER_NUMBER= \
# Openvpn
OPENVPN_CIPHER= \
OPENVPN_AUTH= \
# # PIA and ProtonVPN only:
SERVER_NAME= \
# # ProtonVPN only:
FREE_ONLY= \
# # Surfshark only:
MULTIHOP_ONLY= \
# Firewall
FIREWALL=on \
FIREWALL_VPN_INPUT_PORTS= \
FIREWALL_INPUT_PORTS= \
FIREWALL_OUTBOUND_SUBNETS= \
FIREWALL_DEBUG=off \
# Logging
LOG_LEVEL=info \
# Health
HEALTH_SERVER_ADDRESS=127.0.0.1:9999 \
HEALTH_ADDRESS_TO_PING=github.com \
HEALTH_VPN_DURATION_INITIAL=6s \
HEALTH_VPN_DURATION_ADDITION=5s \
# DNS over TLS
DOT=on \
DOT_PROVIDERS=cloudflare \
DOT_PRIVATE_ADDRESS=127.0.0.1/8,10.0.0.0/8,172.16.0.0/12,192.168.0.0/16,169.254.0.0/16,::1/128,fc00::/7,fe80::/10,::ffff:0:0/96 \
DOT_PRIVATE_ADDRESS=127.0.0.1/8,10.0.0.0/8,172.16.0.0/12,192.168.0.0/16,169.254.0.0/16,::1/128,fc00::/7,fe80::/10,::ffff:7f00:1/104,::ffff:a00:0/104,::ffff:a9fe:0/112,::ffff:ac10:0/108,::ffff:c0a8:0/112 \
DOT_VERBOSITY=1 \
DOT_VERBOSITY_DETAILS=0 \
DOT_VALIDATION_LOGLEVEL=0 \
@@ -119,12 +145,6 @@ ENV VPNSP=pia \
DNS_UPDATE_PERIOD=24h \
DNS_PLAINTEXT_ADDRESS=1.1.1.1 \
DNS_KEEP_NAMESERVER=off \
# Firewall
FIREWALL=on \
FIREWALL_VPN_INPUT_PORTS= \
FIREWALL_INPUT_PORTS= \
FIREWALL_OUTBOUND_SUBNETS= \
FIREWALL_DEBUG=off \
# HTTP proxy
HTTPPROXY= \
HTTPPROXY_LOG=off \
@@ -136,19 +156,34 @@ ENV VPNSP=pia \
# Shadowsocks
SHADOWSOCKS=off \
SHADOWSOCKS_LOG=off \
SHADOWSOCKS_PORT=8388 \
SHADOWSOCKS_ADDRESS=":8388" \
SHADOWSOCKS_PASSWORD= \
SHADOWSOCKS_PASSWORD_SECRETFILE=/run/secrets/shadowsocks_password \
SHADOWSOCKS_METHOD=chacha20-ietf-poly1305 \
UPDATER_PERIOD=0
SHADOWSOCKS_CIPHER=chacha20-ietf-poly1305 \
# Server data updater
UPDATER_PERIOD=0 \
# Public IP
PUBLICIP_FILE="/tmp/gluetun/ip" \
PUBLICIP_PERIOD=12h \
# Extras
VERSION_INFORMATION=on \
TZ= \
PUID= \
PGID=
ENTRYPOINT ["/entrypoint"]
EXPOSE 8000/tcp 8888/tcp 8388/tcp 8388/udp
HEALTHCHECK --interval=5s --timeout=5s --start-period=10s --retries=1 CMD /entrypoint healthcheck
RUN apk add -q --progress --no-cache --update openvpn ca-certificates iptables ip6tables unbound tzdata && \
rm -rf /var/cache/apk/* /etc/unbound/* /usr/sbin/unbound-* && \
ARG TARGETPLATFORM
RUN apk add --no-cache --update -l apk-tools && \
apk add --no-cache --update -X "https://dl-cdn.alpinelinux.org/alpine/v3.12/main" openvpn==2.4.11-r0 && \
mv /usr/sbin/openvpn /usr/sbin/openvpn2.4 && \
apk del openvpn && \
apk add --no-cache --update openvpn ca-certificates iptables ip6tables unbound tzdata && \
mv /usr/sbin/openvpn /usr/sbin/openvpn2.5 && \
# Fix vulnerability issue
apk add --no-cache --update busybox && \
rm -rf /var/cache/apk/* /etc/unbound/* /usr/sbin/unbound-* /etc/openvpn/*.sh /usr/lib/openvpn/plugins/openvpn-plugin-down-root.so && \
deluser openvpn && \
deluser unbound && \
mkdir /gluetun
# TODO remove once SAN is added to PIA servers certificates, see https://github.com/pia-foss/manual-connections/issues/10
ENV GODEBUG=x509ignoreCN=0
COPY --from=build /tmp/gobuild/entrypoint /entrypoint

153
README.md
View File

@@ -1,116 +1,125 @@
# Gluetun VPN client
*Lightweight swiss-knife-like VPN client to tunnel to Private Internet Access,
Mullvad, Windscribe, Surfshark Cyberghost, VyprVPN, NordVPN, PureVPN, Privado and TorGuard VPN servers, using Go, OpenVPN, iptables, DNS over TLS, ShadowSocks and an HTTP proxy*
*Lightweight swiss-knife-like VPN client to tunnel to Cyberghost, ExpressVPN, FastestVPN,
HideMyAss, IPVanish, IVPN, Mullvad, NordVPN, Perfect Privacy, Privado, Private Internet Access, PrivateVPN,
ProtonVPN, PureVPN, Surfshark, TorGuard, VPNUnlimited, VyprVPN, WeVPN and Windscribe VPN servers
using Go, OpenVPN or Wireguard, iptables, DNS over TLS, ShadowSocks and an HTTP proxy*
**ANNOUNCEMENT**: *New Docker image name `qmcgaw/gluetun`*
**ANNOUNCEMENT**: Wireguard is now supported for all providers supporting it!
<img height="250" src="https://raw.githubusercontent.com/qdm12/gluetun/master/title.svg?sanitize=true">
![Title image](https://raw.githubusercontent.com/qdm12/gluetun/master/title.svg)
[![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)
[![Size](https://img.shields.io/docker/image-size/qmcgaw/gluetun/latest?label=Latest%20image)](https://hub.docker.com/r/qmcgaw/gluetun/tags)
[![Build status](https://github.com/qdm12/gluetun/actions/workflows/ci.yml/badge.svg)](https://github.com/qdm12/gluetun/actions/workflows/ci.yml)
[![Docker Pulls](https://img.shields.io/docker/pulls/qmcgaw/private-internet-access.svg)](https://hub.docker.com/r/qmcgaw/private-internet-access)
[![Docker Pulls](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 stars qmcgaw/gluetun](https://img.shields.io/docker/stars/qmcgaw/gluetun.svg)](https://hub.docker.com/r/qmcgaw/gluetun)
[![Docker stars qmcgaw/private-internet-access](https://img.shields.io/docker/stars/qmcgaw/private-internet-access.svg)](https://hub.docker.com/r/qmcgaw/gluetun)
![Last release](https://img.shields.io/github/release/qdm12/gluetun?label=Last%20release)
![Last Docker tag](https://img.shields.io/docker/v/qmcgaw/gluetun?sort=semver&label=Last%20Docker%20tag)
![GitHub Release Date](https://img.shields.io/github/release-date/qdm12/gluetun?label=Last%20release%20date)
[![Last release size](https://img.shields.io/docker/image-size/qmcgaw/gluetun?sort=semver&label=Last%20released%20image)](https://hub.docker.com/r/qmcgaw/gluetun/tags?page=1&ordering=last_updated)
![GitHub last release date](https://img.shields.io/github/release-date/qdm12/gluetun?label=Last%20release%20date)
![Commits since release](https://img.shields.io/github/commits-since/qdm12/gluetun/latest?sort=semver)
[![GitHub last commit](https://img.shields.io/github/last-commit/qdm12/gluetun.svg)](https://github.com/qdm12/gluetun/commits)
[![Latest size](https://img.shields.io/docker/image-size/qmcgaw/gluetun/latest?label=Latest%20image)](https://hub.docker.com/r/qmcgaw/gluetun/tags)
[![GitHub last commit](https://img.shields.io/github/last-commit/qdm12/gluetun.svg)](https://github.com/qdm12/gluetun/commits/master)
[![GitHub commit activity](https://img.shields.io/github/commit-activity/y/qdm12/gluetun.svg)](https://github.com/qdm12/gluetun/graphs/contributors)
[![GitHub closed PRs](https://img.shields.io/github/issues-pr-closed/qdm12/gluetun.svg)](https://github.com/qdm12/gluetun/pulls?q=is%3Apr+is%3Aclosed)
[![GitHub issues](https://img.shields.io/github/issues/qdm12/gluetun.svg)](https://github.com/qdm12/gluetun/issues)
[![GitHub closed issues](https://img.shields.io/github/issues-closed/qdm12/gluetun.svg)](https://github.com/qdm12/gluetun/issues?q=is%3Aissue+is%3Aclosed)
[![Lines of code](https://img.shields.io/tokei/lines/github/qdm12/gluetun)](https://github.com/qdm12/gluetun)
![Code size](https://img.shields.io/github/languages/code-size/qdm12/gluetun)
![GitHub repo size](https://img.shields.io/github/repo-size/qdm12/gluetun)
![Go version](https://img.shields.io/github/go-mod/go-version/qdm12/gluetun)
![Visitors count](https://visitor-badge.laobi.icu/badge?page_id=gluetun.readme)
## Quick links
- Problem or suggestion?
- [Start a discussion](https://github.com/qdm12/gluetun/discussions)
- [Create an issue](https://github.com/qdm12/gluetun/issues)
- [Setup](#Setup)
- [Features](#Features)
- Problem?
- [Check the Wiki](https://github.com/qdm12/gluetun/wiki)
- [Start a discussion](https://github.com/qdm12/gluetun/discussions)
- [Fix the Unraid template](https://github.com/qdm12/gluetun/discussions/550)
- Suggestion?
- [Create an issue](https://github.com/qdm12/gluetun/issues)
- [Join the Slack channel](https://join.slack.com/t/qdm12/shared_invite/enQtOTE0NjcxNTM1ODc5LTYyZmVlOTM3MGI4ZWU0YmJkMjUxNmQ4ODQ2OTAwYzMxMTlhY2Q1MWQyOWUyNjc2ODliNjFjMDUxNWNmNzk5MDk)
- Happy?
- Sponsor me on [github.com/sponsors/qdm12](https://github.com/sponsors/qdm12)
- Donate to [paypal.me/qmcgaw](https://www.paypal.me/qmcgaw)
- Drop me [an email](mailto:quentin.mcgaw@gmail.com)
- Video:
[![Video Gif](https://i.imgur.com/CetWunc.gif)](https://youtu.be/0F6I03LQcI4)
- [Substack Console interview](https://console.substack.com/p/console-72)
## Features
- Based on Alpine 3.12 for a small Docker image of 52MB
- Supports **Private Internet Access**, **Mullvad**, **Windscribe**, **Surfshark**, **Cyberghost**, **Vyprvpn**, **NordVPN**, **PureVPN**, **Privado** and **TorGuard** servers
- Supports Openvpn only for now
- Based on Alpine 3.14 for a small Docker image of 33MB
- Supports: **Cyberghost**, **ExpressVPN**, **FastestVPN**, **HideMyAss**, **IPVanish**, **IVPN**, **Mullvad**, **NordVPN**, **Perfect Privacy**, **Privado**, **Private Internet Access**, **PrivateVPN**, **ProtonVPN**, **PureVPN**, **Surfshark**, **TorGuard**, **VPNUnlimited**, **Vyprvpn**, **WeVPN**, **Windscribe** servers
- Supports OpenVPN for all providers listed
- Supports Wireguard
- For **Mullvad**, **Ivpn** and **Windscribe**
- For **Torguard**, **VPN Unlimited** and **WeVPN** using [the custom provider](https://github.com/qdm12/gluetun/wiki/Custom-provider)
- For custom Wireguard configurations using [the custom provider](https://github.com/qdm12/gluetun/wiki/Custom-provider)
- More in progress, see [#134](https://github.com/qdm12/gluetun/issues/134)
- DNS over TLS baked in with service provider(s) of your choice
- DNS fine blocking of malicious/ads/surveillance hostnames and IP addresses, with live update every 24 hours
- Choose the vpn network protocol, `udp` or `tcp`
- Built in firewall kill switch to allow traffic only with needed the VPN servers and LAN devices
- Built in Shadowsocks proxy (protocol based on SOCKS5 with an encryption layer, tunnels TCP+UDP)
- Built in HTTP proxy (tunnels HTTP and HTTPS through TCP)
- [Connect other containers to it](https://github.com/qdm12/gluetun/wiki/Connect-to-gluetun)
- [Connect LAN devices to it](https://github.com/qdm12/gluetun/wiki/Connect-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)
- Compatible with amd64, i686 (32 bit), **ARM** 64 bit, ARM 32 bit v6 and v7, and even ppc64le 🎆
- VPN server side port forwarding for Private Internet Access and Vyprvpn
- [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
- Subprograms all drop root privileges once launched
- Subprograms output streams are all merged together
- Unbound subprogram drops root privileges once launched
- Can work as a Kubernetes sidecar container, thanks @rorph
## Setup
1. On some devices you may need to setup your tunnel kernel module on your host with `insmod /lib/modules/tun.ko` or `modprobe tun`
- [Synology users Wiki page](https://github.com/qdm12/gluetun/wiki/Synology-setup)
1. Launch the container with:
🎉 There are now instructions specific to each VPN provider with examples to help you get started as quickly as possible!
```bash
docker run -d --name gluetun --cap-add=NET_ADMIN \
-e VPNSP="private internet access" -e REGION="CA Montreal" \
-e OPENVPN_USER=js89ds7 -e OPENVPN_PASSWORD=8fd9s239G \
-v /yourpath:/gluetun \
qmcgaw/gluetun
```
Go to the [Wiki](https://github.com/qdm12/gluetun/wiki)!
or use [docker-compose.yml](https://github.com/qdm12/gluetun/blob/master/docker-compose.yml) with:
[🐛 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+)
```bash
echo "your openvpn username" > openvpn_user
echo "your openvpn password" > openvpn_password
docker-compose up -d
```
Here's a docker-compose.yml for the laziest:
You should probably check the many [environment variables](https://github.com/qdm12/gluetun/wiki/Environment-variables) available to adapt the container to your needs.
## Further setup
The following points are all optional but should give you insights on all the possibilities with this container.
- Use [Docker secrets](https://github.com/qdm12/gluetun/wiki/Docker-secrets) to read your credentials instead of environment variables
- [Test your setup](https://github.com/qdm12/gluetun/wiki/Test-your-setup)
- [How to connect other containers and devices to Gluetun](https://github.com/qdm12/gluetun/wiki/Connect-to-gluetun)
- [VPN server side port forwarding](https://github.com/qdm12/gluetun/wiki/Port-forwarding)
- [HTTP control server](https://github.com/qdm12/gluetun/wiki/HTTP-Control-server) to automate things, restart Openvpn etc.
- Update the image with `docker pull qmcgaw/gluetun:latest`. See this [Wiki document](https://github.com/qdm12/gluetun/wiki/Docker-image-tags) for Docker tags available.
```yml
version: "3"
services:
gluetun:
image: qmcgaw/gluetun
cap_add:
- NET_ADMIN
ports:
- 8888:8888/tcp # HTTP proxy
- 8388:8388/tcp # Shadowsocks
- 8388:8388/udp # Shadowsocks
volumes:
- /yourpath:/gluetun
environment:
# See https://github.com/qdm12/gluetun/wiki
- VPNSP=ivpn
- VPN_TYPE=openvpn
# OpenVPN:
- OPENVPN_USER=
- OPENVPN_PASSWORD=
# Wireguard:
# - WIREGUARD_PRIVATE_KEY=wOEI9rqqbDwnN8/Bpp22sVz48T71vJ4fYmFWujulwUU=
# - WIREGUARD_ADDRESS=10.64.222.21/32
# Timezone for accurate log times
- TZ=
```
## License
[![MIT](https://img.shields.io/github/license/qdm12/gluetun)](https://github.com/qdm12/gluetun/master/LICENSE)
## Metadata
[![GitHub commit activity](https://img.shields.io/github/commit-activity/y/qdm12/gluetun.svg)](https://github.com/qdm12/gluetun/commits)
[![GitHub closed PRs](https://img.shields.io/github/issues-pr-closed/qdm12/gluetun.svg)](https://github.com/qdm12/gluetun/pulls?q=is%3Apr+is%3Aclosed)
[![GitHub issues](https://img.shields.io/github/issues/qdm12/gluetun.svg)](https://github.com/qdm12/gluetun/issues)
[![GitHub closed issues](https://img.shields.io/github/issues-closed/qdm12/gluetun.svg)](https://github.com/qdm12/gluetun/issues?q=is%3Aissue+is%3Aclosed)
![Visitors count](https://visitor-badge.laobi.icu/badge?page_id=gluetun.readme)
![GitHub stars](https://img.shields.io/github/stars/qdm12/gluetun?style=social)
![GitHub watchers](https://img.shields.io/github/watchers/qdm12/gluetun?style=social)
![Contributors](https://img.shields.io/github/contributors/qdm12/gluetun)
![GitHub forks](https://img.shields.io/github/forks/qdm12/gluetun?style=social)
![Code size](https://img.shields.io/github/languages/code-size/qdm12/gluetun)
![GitHub repo size](https://img.shields.io/github/repo-size/qdm12/gluetun)
[![dockeri.co](https://dockeri.co/image/qmcgaw/gluetun)](https://hub.docker.com/r/qmcgaw/gluetun)
![Docker Layers for latest](https://img.shields.io/microbadger/layers/qmcgaw/gluetun/latest?label=Docker%20image%20layers)
![Go version](https://img.shields.io/github/go-mod/go-version/qdm12/gluetun)

View File

@@ -2,15 +2,18 @@ package main
import (
"context"
"errors"
"fmt"
"net"
"net/http"
nativeos "os"
"os"
"os/signal"
"sync"
"strconv"
"strings"
"syscall"
"time"
_ "time/tzdata"
_ "github.com/breml/rootcerts"
"github.com/qdm12/dns/pkg/unbound"
"github.com/qdm12/gluetun/internal/alpine"
"github.com/qdm12/gluetun/internal/cli"
@@ -20,78 +23,84 @@ import (
"github.com/qdm12/gluetun/internal/firewall"
"github.com/qdm12/gluetun/internal/healthcheck"
"github.com/qdm12/gluetun/internal/httpproxy"
gluetunLogging "github.com/qdm12/gluetun/internal/logging"
"github.com/qdm12/gluetun/internal/models"
"github.com/qdm12/gluetun/internal/netlink"
"github.com/qdm12/gluetun/internal/openvpn"
"github.com/qdm12/gluetun/internal/portforward"
"github.com/qdm12/gluetun/internal/publicip"
"github.com/qdm12/gluetun/internal/routing"
"github.com/qdm12/gluetun/internal/server"
"github.com/qdm12/gluetun/internal/shadowsocks"
"github.com/qdm12/gluetun/internal/storage"
"github.com/qdm12/gluetun/internal/unix"
"github.com/qdm12/gluetun/internal/tun"
"github.com/qdm12/gluetun/internal/updater"
versionpkg "github.com/qdm12/gluetun/internal/version"
"github.com/qdm12/gluetun/internal/vpn"
"github.com/qdm12/golibs/command"
"github.com/qdm12/golibs/logging"
"github.com/qdm12/golibs/os"
"github.com/qdm12/golibs/os/user"
"github.com/qdm12/golibs/params"
"github.com/qdm12/goshutdown"
"github.com/qdm12/goshutdown/goroutine"
"github.com/qdm12/goshutdown/group"
"github.com/qdm12/goshutdown/order"
"github.com/qdm12/gosplash"
"github.com/qdm12/updated/pkg/dnscrypto"
)
//nolint:gochecknoglobals
var (
version = "unknown"
commit = "unknown"
buildDate = "an unknown date"
version = "unknown"
commit = "unknown"
created = "an unknown date"
)
var (
errSetupRouting = errors.New("cannot setup routing")
errCreateUser = errors.New("cannot create user")
)
func main() {
buildInfo := models.BuildInformation{
Version: version,
Commit: commit,
BuildDate: buildDate,
Version: version,
Commit: commit,
Created: created,
}
ctx := context.Background()
ctx, cancel := context.WithCancel(ctx)
background := context.Background()
signalCtx, stop := signal.NotifyContext(background, syscall.SIGINT, syscall.SIGTERM, os.Interrupt)
ctx, cancel := context.WithCancel(background)
logger, err := logging.NewLogger(logging.ConsoleEncoding, logging.InfoLevel)
if err != nil {
fmt.Println(err)
nativeos.Exit(1)
}
logger := logging.New(logging.Settings{
Level: logging.LevelInfo,
})
args := nativeos.Args
os := os.New()
osUser := user.New()
unix := unix.New()
args := os.Args
tun := tun.New()
netLinker := netlink.New()
cli := cli.New()
env := params.New()
cmder := command.NewCmder()
errorCh := make(chan error)
go func() {
errorCh <- _main(ctx, buildInfo, args, logger, os, osUser, unix, cli)
errorCh <- _main(ctx, buildInfo, args, logger, env, tun, netLinker, cmder, cli)
}()
signalsCh := make(chan nativeos.Signal, 1)
signal.Notify(signalsCh,
syscall.SIGINT,
syscall.SIGTERM,
nativeos.Interrupt,
)
select {
case signal := <-signalsCh:
logger.Warn("Caught OS signal %s, shutting down", signal)
case <-signalCtx.Done():
stop()
fmt.Println("")
logger.Warn("Caught OS signal, shutting down")
cancel()
case err := <-errorCh:
stop()
close(errorCh)
if err == nil { // expected exit such as healthcheck
nativeos.Exit(0)
os.Exit(0)
}
logger.Error(err)
logger.Error(err.Error())
cancel()
}
cancel()
const shutdownGracePeriod = 5 * time.Second
timer := time.NewTimer(shutdownGracePeriod)
select {
@@ -104,55 +113,99 @@ func main() {
logger.Warn("Shutdown timed out")
}
nativeos.Exit(1)
os.Exit(1)
}
var (
errCommandUnknown = errors.New("command is unknown")
)
//nolint:gocognit,gocyclo
func _main(ctx context.Context, buildInfo models.BuildInformation,
args []string, logger logging.Logger, os os.OS, osUser user.OSUser, unix unix.Unix,
cli cli.CLI) error {
args []string, logger logging.ParentLogger, env params.Interface,
tun tun.Interface, netLinker netlink.NetLinker, cmder command.RunStarter,
cli cli.CLIer) error {
if len(args) > 1 { // cli operation
switch args[1] {
case "healthcheck":
return cli.HealthCheck(ctx)
return cli.HealthCheck(ctx, env, logger)
case "clientkey":
return cli.ClientKey(args[2:], os.OpenFile)
return cli.ClientKey(args[2:])
case "openvpnconfig":
return cli.OpenvpnConfig(os)
return cli.OpenvpnConfig(logger, env)
case "update":
return cli.Update(ctx, args[2:], os)
return cli.Update(ctx, args[2:], logger)
case "format-servers":
return cli.FormatServers(args[2:])
default:
return fmt.Errorf("command %q is unknown", args[1])
return fmt.Errorf("%w: %s", errCommandUnknown, args[1])
}
}
ctx, cancel := context.WithCancel(ctx)
defer cancel()
announcementExp, err := time.Parse(time.RFC3339, "2021-10-02T00:00:00Z")
if err != nil {
return err
}
splashSettings := gosplash.Settings{
User: "qdm12",
Repository: "gluetun",
Emails: []string{"quentin.mcgaw@gmail.com"},
Version: buildInfo.Version,
Commit: buildInfo.Commit,
BuildDate: buildInfo.Created,
Announcement: "Wireguard is now supported for Mullvad, IVPN and Windscribe!",
AnnounceExp: announcementExp,
// Sponsor information
PaypalUser: "qmcgaw",
GithubSponsor: "qdm12",
}
for _, line := range gosplash.MakeLines(splashSettings) {
fmt.Println(line)
}
// TODO run this in a loop or in openvpn to reload from file without restarting
storageLogger := logger.NewChild(logging.Settings{Prefix: "storage: "})
storage, err := storage.New(storageLogger, constants.ServersData)
if err != nil {
return err
}
allServers := storage.GetServers()
var allSettings configuration.Settings
err = allSettings.Read(env, allServers,
logger.NewChild(logging.Settings{Prefix: "configuration: "}))
if err != nil {
return err
}
logger.PatchLevel(allSettings.Log.Level)
puid, pgid := allSettings.System.PUID, allSettings.System.PGID
const clientTimeout = 15 * time.Second
httpClient := &http.Client{Timeout: clientTimeout}
// Create configurators
alpineConf := alpine.NewConfigurator(os.OpenFile, osUser)
ovpnConf := openvpn.NewConfigurator(logger, os, unix)
alpineConf := alpine.New()
ovpnConf := openvpn.New(
logger.NewChild(logging.Settings{Prefix: "openvpn configurator: "}),
cmder, puid, pgid)
dnsCrypto := dnscrypto.New(httpClient, "", "")
const cacertsPath = "/etc/ssl/certs/ca-certificates.crt"
dnsConf := unbound.NewConfigurator(logger, os.OpenFile, dnsCrypto,
dnsConf := unbound.NewConfigurator(nil, cmder, dnsCrypto,
"/etc/unbound", "/usr/sbin/unbound", cacertsPath)
routingConf := routing.NewRouting(logger)
firewallConf := firewall.NewConfigurator(logger, routingConf, os.OpenFile)
fmt.Println(gluetunLogging.Splash(buildInfo))
printVersions(ctx, logger, map[string]func(ctx context.Context) (string, error){
"OpenVPN": ovpnConf.Version,
"Unbound": dnsConf.Version,
"IPtables": firewallConf.Version,
err = printVersions(ctx, logger, []printVersionElement{
{name: "Alpine", getVersion: alpineConf.Version},
{name: "OpenVPN 2.4", getVersion: ovpnConf.Version24},
{name: "OpenVPN 2.5", getVersion: ovpnConf.Version25},
{name: "Unbound", getVersion: dnsConf.Version},
{name: "IPtables", getVersion: func(ctx context.Context) (version string, err error) {
return firewall.Version(ctx, cmder)
}},
})
var allSettings configuration.Settings
err := allSettings.Read(params.NewEnv(), os, logger.WithPrefix("configuration: "))
if err != nil {
return err
}
logger.Info(allSettings.String())
if err := os.MkdirAll("/tmp/gluetun", 0644); err != nil {
@@ -162,40 +215,39 @@ func _main(ctx context.Context, buildInfo models.BuildInformation,
return err
}
// TODO run this in a loop or in openvpn to reload from file without restarting
storage := storage.New(logger, os, constants.ServersData)
allServers, err := storage.SyncServers(constants.GetAllServers())
if err != nil {
return err
}
// Should never change
puid, pgid := allSettings.System.PUID, allSettings.System.PGID
const defaultUsername = "nonrootuser"
nonRootUsername, err := alpineConf.CreateUser(defaultUsername, puid)
if err != nil {
return err
return fmt.Errorf("%w: %s", errCreateUser, err)
}
if nonRootUsername != defaultUsername {
logger.Info("using existing username %s corresponding to user id %d", nonRootUsername, puid)
logger.Info("using existing username " + nonRootUsername + " corresponding to user id " + fmt.Sprint(puid))
}
// set it for Unbound
// TODO remove this when migrating to qdm12/dns v2
allSettings.DNS.Unbound.Username = nonRootUsername
allSettings.VPN.OpenVPN.ProcUser = nonRootUsername
if err := os.Chown("/etc/unbound", puid, pgid); err != nil {
return err
}
firewallLogLevel := allSettings.Log.Level
if allSettings.Firewall.Debug {
firewallConf.SetDebug()
routingConf.SetDebug()
firewallLogLevel = logging.LevelDebug
}
routingLogger := logger.NewChild(logging.Settings{
Prefix: "routing: ",
Level: firewallLogLevel,
})
routingConf := routing.New(netLinker, routingLogger)
defaultInterface, defaultGateway, err := routingConf.DefaultRoute()
if err != nil {
return err
}
localSubnet, err := routingConf.LocalSubnet()
localNetworks, err := routingConf.LocalNetworks()
if err != nil {
return err
}
@@ -205,15 +257,23 @@ func _main(ctx context.Context, buildInfo models.BuildInformation,
return err
}
firewallConf.SetNetworkInformation(defaultInterface, defaultGateway, localSubnet, defaultIP)
firewallLogger := logger.NewChild(logging.Settings{
Prefix: "firewall: ",
Level: firewallLogLevel,
})
firewallConf := firewall.NewConfig(firewallLogger, cmder,
defaultInterface, defaultGateway, localNetworks, defaultIP)
if err := routingConf.Setup(); err != nil {
return err
if strings.Contains(err.Error(), "operation not permitted") {
logger.Warn("💡 Tip: Are you passing NET_ADMIN capability to gluetun?")
}
return fmt.Errorf("%w: %s", errSetupRouting, err)
}
defer func() {
routingConf.SetVerbose(false)
logger.Info("routing cleanup...")
if err := routingConf.TearDown(); err != nil {
logger.Error(err)
logger.Error("cannot teardown routing: " + err.Error())
}
}()
@@ -224,17 +284,14 @@ func _main(ctx context.Context, buildInfo models.BuildInformation,
return err
}
if err := ovpnConf.CheckTUN(); err != nil {
logger.Warn(err)
err = ovpnConf.CreateTUN()
if err := tun.Check(constants.TunnelDevice); err != nil {
logger.Info(err.Error() + "; creating it...")
err = tun.Create(constants.TunnelDevice)
if err != nil {
return err
}
}
tunnelReadyCh := make(chan struct{})
defer close(tunnelReadyCh)
if allSettings.Firewall.Enabled {
err := firewallConf.SetEnabled(ctx, true) // disabled by default
if err != nil {
@@ -242,13 +299,6 @@ func _main(ctx context.Context, buildInfo models.BuildInformation,
}
}
for _, vpnPort := range allSettings.Firewall.VPNInputPorts {
err = firewallConf.SetAllowedPort(ctx, vpnPort, string(constants.TUN))
if err != nil {
return err
}
}
for _, port := range allSettings.Firewall.InputPorts {
err = firewallConf.SetAllowedPort(ctx, port, defaultInterface)
if err != nil {
@@ -256,153 +306,149 @@ func _main(ctx context.Context, buildInfo models.BuildInformation,
}
} // TODO move inside firewall?
wg := &sync.WaitGroup{}
// Shutdown settings
const totalShutdownTimeout = 3 * time.Second
const defaultShutdownTimeout = 400 * time.Millisecond
defaultShutdownOnSuccess := func(goRoutineName string) {
logger.Info(goRoutineName + ": terminated ✔️")
}
defaultShutdownOnFailure := func(goRoutineName string, err error) {
logger.Warn(goRoutineName + ": " + err.Error() + " ⚠️")
}
defaultGroupOptions := []group.Option{
group.OptionTimeout(defaultShutdownTimeout),
group.OptionOnSuccess(defaultShutdownOnSuccess)}
openvpnLooper := openvpn.NewLooper(allSettings.OpenVPN, nonRootUsername, puid, pgid, allServers,
ovpnConf, firewallConf, routingConf, logger, httpClient, os.OpenFile, tunnelReadyCh, cancel)
wg.Add(1)
// wait for restartOpenvpn
go openvpnLooper.Run(ctx, wg)
controlGroupHandler := goshutdown.NewGroupHandler("control", defaultGroupOptions...)
tickersGroupHandler := goshutdown.NewGroupHandler("tickers", defaultGroupOptions...)
otherGroupHandler := goshutdown.NewGroupHandler("other", defaultGroupOptions...)
portForwardLogger := logger.NewChild(logging.Settings{Prefix: "port forwarding: "})
portForwardLooper := portforward.NewLoop(allSettings.VPN.Provider.PortForwarding,
httpClient, firewallConf, portForwardLogger)
portForwardHandler, portForwardCtx, portForwardDone := goshutdown.NewGoRoutineHandler(
"port forwarding", goroutine.OptionTimeout(time.Second))
go portForwardLooper.Run(portForwardCtx, portForwardDone)
unboundLogger := logger.NewChild(logging.Settings{Prefix: "dns over tls: "})
unboundLooper := dns.NewLoop(dnsConf, allSettings.DNS, httpClient,
unboundLogger)
dnsHandler, dnsCtx, dnsDone := goshutdown.NewGoRoutineHandler(
"unbound", goroutine.OptionTimeout(defaultShutdownTimeout))
// wait for unboundLooper.Restart or its ticker launched with RunRestartTicker
go unboundLooper.Run(dnsCtx, dnsDone)
otherGroupHandler.Add(dnsHandler)
dnsTickerHandler, dnsTickerCtx, dnsTickerDone := goshutdown.NewGoRoutineHandler(
"dns ticker", goroutine.OptionTimeout(defaultShutdownTimeout))
go unboundLooper.RunRestartTicker(dnsTickerCtx, dnsTickerDone)
controlGroupHandler.Add(dnsTickerHandler)
publicIPLooper := publicip.NewLoop(httpClient,
logger.NewChild(logging.Settings{Prefix: "ip getter: "}),
allSettings.PublicIP, puid, pgid)
pubIPHandler, pubIPCtx, pubIPDone := goshutdown.NewGoRoutineHandler(
"public IP", goroutine.OptionTimeout(defaultShutdownTimeout))
go publicIPLooper.Run(pubIPCtx, pubIPDone)
otherGroupHandler.Add(pubIPHandler)
pubIPTickerHandler, pubIPTickerCtx, pubIPTickerDone := goshutdown.NewGoRoutineHandler(
"public IP", goroutine.OptionTimeout(defaultShutdownTimeout))
go publicIPLooper.RunRestartTicker(pubIPTickerCtx, pubIPTickerDone)
tickersGroupHandler.Add(pubIPTickerHandler)
vpnLogger := logger.NewChild(logging.Settings{Prefix: "vpn: "})
vpnLooper := vpn.NewLoop(allSettings.VPN, allSettings.Firewall.VPNInputPorts,
allServers, ovpnConf, netLinker, firewallConf, routingConf, portForwardLooper,
cmder, publicIPLooper, unboundLooper, vpnLogger, httpClient,
buildInfo, allSettings.VersionInformation)
vpnHandler, vpnCtx, vpnDone := goshutdown.NewGoRoutineHandler(
"vpn", goroutine.OptionTimeout(time.Second))
go vpnLooper.Run(vpnCtx, vpnDone)
updaterLooper := updater.NewLooper(allSettings.Updater,
allServers, storage, openvpnLooper.SetServers, httpClient, logger)
wg.Add(1)
allServers, storage, vpnLooper.SetServers, httpClient,
logger.NewChild(logging.Settings{Prefix: "updater: "}))
updaterHandler, updaterCtx, updaterDone := goshutdown.NewGoRoutineHandler(
"updater", goroutine.OptionTimeout(defaultShutdownTimeout))
// wait for updaterLooper.Restart() or its ticket launched with RunRestartTicker
go updaterLooper.Run(ctx, wg)
go updaterLooper.Run(updaterCtx, updaterDone)
tickersGroupHandler.Add(updaterHandler)
unboundLooper := dns.NewLooper(dnsConf, allSettings.DNS, httpClient,
logger, nonRootUsername, puid, pgid)
wg.Add(1)
// wait for unboundLooper.Restart or its ticker launched with RunRestartTicker
go unboundLooper.Run(ctx, wg)
updaterTickerHandler, updaterTickerCtx, updaterTickerDone := goshutdown.NewGoRoutineHandler(
"updater ticker", goroutine.OptionTimeout(defaultShutdownTimeout))
go updaterLooper.RunRestartTicker(updaterTickerCtx, updaterTickerDone)
controlGroupHandler.Add(updaterTickerHandler)
publicIPLooper := publicip.NewLooper(
httpClient, logger, allSettings.PublicIP, puid, pgid, os)
wg.Add(1)
go publicIPLooper.Run(ctx, wg)
wg.Add(1)
go publicIPLooper.RunRestartTicker(ctx, wg)
httpProxyLooper := httpproxy.NewLoop(
logger.NewChild(logging.Settings{Prefix: "http proxy: "}),
allSettings.HTTPProxy)
httpProxyHandler, httpProxyCtx, httpProxyDone := goshutdown.NewGoRoutineHandler(
"http proxy", goroutine.OptionTimeout(defaultShutdownTimeout))
go httpProxyLooper.Run(httpProxyCtx, httpProxyDone)
otherGroupHandler.Add(httpProxyHandler)
httpProxyLooper := httpproxy.NewLooper(logger, allSettings.HTTPProxy)
wg.Add(1)
go httpProxyLooper.Run(ctx, wg)
shadowsocksLooper := shadowsocks.NewLooper(allSettings.ShadowSocks,
logger.NewChild(logging.Settings{Prefix: "shadowsocks: "}))
shadowsocksHandler, shadowsocksCtx, shadowsocksDone := goshutdown.NewGoRoutineHandler(
"shadowsocks proxy", goroutine.OptionTimeout(defaultShutdownTimeout))
go shadowsocksLooper.Run(shadowsocksCtx, shadowsocksDone)
otherGroupHandler.Add(shadowsocksHandler)
shadowsocksLooper := shadowsocks.NewLooper(allSettings.ShadowSocks, logger)
wg.Add(1)
go shadowsocksLooper.Run(ctx, wg)
wg.Add(1)
go routeReadyEvents(ctx, wg, buildInfo, tunnelReadyCh,
unboundLooper, updaterLooper, publicIPLooper, routingConf, logger, httpClient,
allSettings.VersionInformation, allSettings.OpenVPN.Provider.PortForwarding.Enabled, openvpnLooper.PortForward,
)
controlServerAddress := fmt.Sprintf("0.0.0.0:%d", allSettings.ControlServer.Port)
controlServerAddress := ":" + strconv.Itoa(int(allSettings.ControlServer.Port))
controlServerLogging := allSettings.ControlServer.Log
httpServer := server.New(controlServerAddress, controlServerLogging,
logger, buildInfo, openvpnLooper, unboundLooper, updaterLooper, publicIPLooper)
wg.Add(1)
go httpServer.Run(ctx, wg)
httpServerHandler, httpServerCtx, httpServerDone := goshutdown.NewGoRoutineHandler(
"http server", goroutine.OptionTimeout(defaultShutdownTimeout))
httpServer := server.New(httpServerCtx, controlServerAddress, controlServerLogging,
logger.NewChild(logging.Settings{Prefix: "http server: "}),
buildInfo, vpnLooper, portForwardLooper, unboundLooper, updaterLooper, publicIPLooper)
go httpServer.Run(httpServerCtx, httpServerDone)
controlGroupHandler.Add(httpServerHandler)
healthcheckServer := healthcheck.NewServer(
constants.HealthcheckAddress, logger)
wg.Add(1)
go healthcheckServer.Run(ctx, wg)
healthLogger := logger.NewChild(logging.Settings{Prefix: "healthcheck: "})
healthcheckServer := healthcheck.NewServer(allSettings.Health, healthLogger, vpnLooper)
healthServerHandler, healthServerCtx, healthServerDone := goshutdown.NewGoRoutineHandler(
"HTTP health server", goroutine.OptionTimeout(defaultShutdownTimeout))
go healthcheckServer.Run(healthServerCtx, healthServerDone)
// Start openvpn for the first time in a blocking call
// until openvpn is launched
_, _ = openvpnLooper.SetStatus(constants.Running) // TODO option to disable with variable
orderHandler := goshutdown.NewOrderHandler("gluetun",
order.OptionTimeout(totalShutdownTimeout),
order.OptionOnSuccess(defaultShutdownOnSuccess),
order.OptionOnFailure(defaultShutdownOnFailure))
orderHandler.Append(controlGroupHandler, tickersGroupHandler, healthServerHandler,
vpnHandler, portForwardHandler, otherGroupHandler)
// Start VPN for the first time in a blocking call
// until the VPN is launched
_, _ = vpnLooper.ApplyStatus(ctx, constants.Running) // TODO option to disable with variable
<-ctx.Done()
if allSettings.OpenVPN.Provider.PortForwarding.Enabled {
logger.Info("Clearing forwarded port status file %s", allSettings.OpenVPN.Provider.PortForwarding.Filepath)
if err := os.Remove(allSettings.OpenVPN.Provider.PortForwarding.Filepath); err != nil {
logger.Error(err)
}
}
wg.Wait()
return nil
return orderHandler.Shutdown(context.Background())
}
func printVersions(ctx context.Context, logger logging.Logger,
versionFunctions map[string]func(ctx context.Context) (string, error)) {
type printVersionElement struct {
name string
getVersion func(ctx context.Context) (version string, err error)
}
type infoer interface {
Info(s string)
}
func printVersions(ctx context.Context, logger infoer,
elements []printVersionElement) (err error) {
const timeout = 5 * time.Second
ctx, cancel := context.WithTimeout(ctx, timeout)
defer cancel()
for name, f := range versionFunctions {
version, err := f(ctx)
for _, element := range elements {
version, err := element.getVersion(ctx)
if err != nil {
logger.Error(err)
} else {
logger.Info("%s version: %s", name, version)
}
}
}
func routeReadyEvents(ctx context.Context, wg *sync.WaitGroup, buildInfo models.BuildInformation,
tunnelReadyCh <-chan struct{},
unboundLooper dns.Looper, updaterLooper updater.Looper, publicIPLooper publicip.Looper,
routing routing.Routing, logger logging.Logger, httpClient *http.Client,
versionInformation, portForwardingEnabled bool, startPortForward func(vpnGateway net.IP)) {
defer wg.Done()
tickerWg := &sync.WaitGroup{}
// for linters only
var restartTickerContext context.Context
var restartTickerCancel context.CancelFunc = func() {}
first := true
for {
select {
case <-ctx.Done():
restartTickerCancel() // for linters only
tickerWg.Wait()
return
case <-tunnelReadyCh: // blocks until openvpn is connected
vpnDestination, err := routing.VPNDestinationIP()
if err != nil {
logger.Warn(err)
} else {
logger.Info("VPN routing IP address: %s", vpnDestination)
}
if unboundLooper.GetSettings().Enabled {
_, _ = unboundLooper.SetStatus(constants.Running)
}
restartTickerCancel() // stop previous restart tickers
tickerWg.Wait()
restartTickerContext, restartTickerCancel = context.WithCancel(ctx)
// Runs the Public IP getter job once
_, _ = publicIPLooper.SetStatus(constants.Running)
if !versionInformation {
break
}
if first {
first = false
message, err := versionpkg.GetMessage(ctx, buildInfo, httpClient)
if err != nil {
logger.Error(err)
} else {
logger.Info(message)
}
}
//nolint:gomnd
tickerWg.Add(2)
go unboundLooper.RunRestartTicker(restartTickerContext, tickerWg)
go updaterLooper.RunRestartTicker(restartTickerContext, tickerWg)
if portForwardingEnabled {
// vpnGateway required only for PIA
vpnGateway, err := routing.VPNLocalGatewayIP()
if err != nil {
logger.Error(err)
}
logger.Info("VPN gateway IP address: %s", vpnGateway)
startPortForward(vpnGateway)
}
return err
}
logger.Info(element.name + " version: " + version)
}
return nil
}

View File

@@ -1,31 +0,0 @@
version: "3.7"
services:
gluetun:
image: qmcgaw/gluetun
container_name: gluetun
cap_add:
- NET_ADMIN
network_mode: bridge
ports:
- 8888:8888/tcp # HTTP proxy
- 8388:8388/tcp # Shadowsocks
- 8388:8388/udp # Shadowsocks
- 8000:8000/tcp # Built-in HTTP control server
# command:
volumes:
- /yourpath:/gluetun
secrets:
- openvpn_user
- openvpn_password
environment:
# More variables are available, see the readme table
- VPNSP=private internet access
# Timezone for accurate logs times
- TZ=
restart: always
secrets:
openvpn_user:
file: ./openvpn_user
openvpn_password:
file: ./openvpn_password

46
go.mod
View File

@@ -1,16 +1,44 @@
module github.com/qdm12/gluetun
go 1.15
go 1.17
require (
github.com/fatih/color v1.10.0
github.com/golang/mock v1.4.4
github.com/kyokomi/emoji v2.2.4+incompatible
github.com/qdm12/dns v1.4.0
github.com/qdm12/golibs v0.0.0-20210206072445-35759e951561
github.com/qdm12/ss-server v0.1.0
github.com/qdm12/updated v0.0.0-20210102005021-dd457d77f94a
github.com/breml/rootcerts v0.2.0
github.com/fatih/color v1.13.0
github.com/go-ping/ping v0.0.0-20210911151512-381826476871
github.com/golang/mock v1.6.0
github.com/qdm12/dns v1.11.0
github.com/qdm12/golibs v0.0.0-20210822203818-5c568b0777b6
github.com/qdm12/goshutdown v0.3.0
github.com/qdm12/gosplash v0.1.0
github.com/qdm12/ss-server v0.3.0
github.com/qdm12/updated v0.0.0-20210603204757-205acfe6937e
github.com/stretchr/testify v1.7.0
github.com/vishvananda/netlink v1.1.0
golang.org/x/sys v0.0.0-20201223074533-0d417f636930
golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c
golang.zx2c4.com/wireguard v0.0.0-20210805125648-3957e9b9dd19
golang.zx2c4.com/wireguard/wgctrl v0.0.0-20210803171230-4253848d036c
inet.af/netaddr v0.0.0-20210718074554-06ca8145d722
)
require (
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/google/go-cmp v0.5.5 // indirect
github.com/google/uuid v1.2.0 // indirect
github.com/josharian/native v0.0.0-20200817173448-b6b71def0850 // indirect
github.com/mattn/go-colorable v0.1.9 // indirect
github.com/mattn/go-isatty v0.0.14 // indirect
github.com/mdlayher/genetlink v1.0.0 // indirect
github.com/mdlayher/netlink v1.4.0 // indirect
github.com/miekg/dns v1.1.40 // indirect
github.com/mr-tron/base58 v1.2.0 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
github.com/riobard/go-bloom v0.0.0-20200614022211-cdc8013cb5b3 // indirect
github.com/vishvananda/netns v0.0.0-20191106174202-0a2b9b5464df // indirect
go4.org/intern v0.0.0-20210108033219-3eb7198706b2 // indirect
go4.org/unsafe/assume-no-moving-gc v0.0.0-20201222180813-1025295fd063 // indirect
golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97 // indirect
golang.org/x/net v0.0.0-20210504132125-bbd867fde50d // indirect
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c // indirect
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c // indirect
)

236
go.sum
View File

@@ -1,121 +1,133 @@
github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ=
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
github.com/PuerkitoBio/purell v1.1.0 h1:rmGxhojJlM0tuKtfdvliR84CFHljx9ag64t2xmVkjK4=
github.com/PuerkitoBio/purell v1.1.0/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0=
github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578 h1:d+Bc7a5rLufV/sSk/8dngufqelfh6jnri85riMAaF/M=
github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578/go.mod h1:uGdkoq3SwY9Y+13GIhn11/XLaGBb4BfwItxLd5jeuXE=
github.com/alcortesm/tgz v0.0.0-20161220082320-9c5fe88206d7/go.mod h1:6zEj6s6u/ghQa61ZWa/C2Aw3RkjiTBOix7dkqa1VLIs=
github.com/anmitsu/go-shlex v0.0.0-20161002113705-648efa622239/go.mod h1:2FmKhYUyUczH0OGQWaF5ceTx0UBShxjsH6f8oGKYe2c=
github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5/go.mod h1:wHh0iHkYZB8zMSxRWpUBQtwG5a7fFgvEO+odwuTv2gs=
github.com/asaskevich/govalidator v0.0.0-20180720115003-f9ffefc3facf h1:eg0MeVzsP1G42dRafH3vf+al2vQIJU0YHX+1Tw87oco=
github.com/asaskevich/govalidator v0.0.0-20180720115003-f9ffefc3facf/go.mod h1:lB+ZfQJz7igIIfQNfa7Ml4HSf2uFQQRzpGGRXenZAgY=
github.com/breml/rootcerts v0.2.0 h1:bBIgVe8bS0Ec+orgWpZ/GRYt3a0O8yoW+g2kSBY2aLE=
github.com/breml/rootcerts v0.2.0/go.mod h1:24FDtzYMpqIeYC7QzaE8VPRQaFZU5TIUDlyk8qwjD88=
github.com/creack/pty v1.1.7/go.mod h1:lj5s0c3V2DBrqTV7llrYr5NG6My20zk30Fl46Y7DoTY=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/docker/go-units v0.3.3/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk=
github.com/dvyukov/go-fuzz v0.0.0-20210103155950-6a8e9d1f2415/go.mod h1:11Gm+ccJnvAhCNLlf5+cS9KjtbaD5I5zaZpFMsTHWTw=
github.com/emirpasic/gods v1.12.0/go.mod h1:YfzfFFoVP/catgzJb4IKIqXjX78Ha8FMSDh3ymbK86o=
github.com/fatih/color v1.9.0 h1:8xPHl4/q1VyqGIPif1F+1V3Y3lSmrq01EabUW3CoW5s=
github.com/fatih/color v1.9.0/go.mod h1:eQcE1qtQxscV5RaZvpXrrb8Drkc3/DdQ+uUYCNjL+zU=
github.com/fatih/color v1.10.0 h1:s36xzo75JdqLaaWoiEHk767eHiwo0598uUxyfiPkDsg=
github.com/fatih/color v1.10.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.13.0/go.mod h1:kLAiJbzzSOZDVNGyDpeOxJ47H46qBXwg5ILebYFFOfk=
github.com/flynn/go-shlex v0.0.0-20150515145356-3f9db97f8568/go.mod h1:xEzjJPgXI435gkrCt3MPfRiAkVrwSbHsst4LCFVfpJc=
github.com/gliderlabs/ssh v0.2.2/go.mod h1:U7qILu1NlMHj9FlMhZLlkCdDnU1DBEAqr0aevW3Awn0=
github.com/globalsign/mgo v0.0.0-20180905125535-1ca0a4f7cbcb h1:D4uzjWwKYQ5XnAvUbuvHW93esHg7F8N/OYeBBcJoTr0=
github.com/globalsign/mgo v0.0.0-20180905125535-1ca0a4f7cbcb/go.mod h1:xkRDCp4j0OGD1HRkm4kmhM+pmpv3AKq5SU7GMg4oO/Q=
github.com/go-openapi/analysis v0.0.0-20180825180245-b006789cd277/go.mod h1:k70tL6pCuVxPJOHXQ+wIac1FUrvNkHolPie/cLEU6hI=
github.com/go-openapi/analysis v0.17.0 h1:8JV+dzJJiK46XqGLqqLav8ZfEiJECp8jlOFhpiCdZ+0=
github.com/go-openapi/analysis v0.17.0/go.mod h1:IowGgpVeD0vNm45So8nr+IcQ3pxVtpRoBWb8PVZO0ik=
github.com/go-openapi/errors v0.17.0/go.mod h1:LcZQpmvG4wyF5j4IhA73wkLFQg+QJXOQHVjmcZxhka0=
github.com/go-openapi/errors v0.17.2 h1:azEQ8Fnx0jmtFF2fxsnmd6I0x6rsweUF63qqSO1NmKk=
github.com/go-openapi/errors v0.17.2/go.mod h1:LcZQpmvG4wyF5j4IhA73wkLFQg+QJXOQHVjmcZxhka0=
github.com/go-openapi/jsonpointer v0.17.0 h1:nH6xp8XdXHx8dqveo0ZuJBluCO2qGrPbDNZ0dwoRHP0=
github.com/go-openapi/jsonpointer v0.17.0/go.mod h1:cOnomiV+CVVwFLk0A/MExoFMjwdsUdVpsRhURCKh+3M=
github.com/go-openapi/jsonreference v0.17.0 h1:yJW3HCkTHg7NOA+gZ83IPHzUSnUzGXhGmsdiCcMexbA=
github.com/go-openapi/jsonreference v0.17.0/go.mod h1:g4xxGn04lDIRh0GJb5QlpE3HfopLOL6uZrK/VgnsK9I=
github.com/go-openapi/loads v0.17.0 h1:H22nMs3GDQk4SwAaFQ+jLNw+0xoFeCueawhZlv8MBYs=
github.com/go-openapi/loads v0.17.0/go.mod h1:72tmFy5wsWx89uEVddd0RjRWPZm92WRLhf7AC+0+OOU=
github.com/go-openapi/runtime v0.0.0-20180920151709-4f900dc2ade9/go.mod h1:6v9a6LTXWQCdL8k1AO3cvqx5OtZY/Y9wKTgaoP6YRfA=
github.com/go-openapi/runtime v0.17.2 h1:/ZK67ikFhQAMFFH/aPu2MaGH7QjP4wHBvHYOVIzDAw0=
github.com/go-openapi/runtime v0.17.2/go.mod h1:QO936ZXeisByFmZEO1IS1Dqhtf4QV1sYYFtIq6Ld86Q=
github.com/go-openapi/spec v0.17.0 h1:XNvrt8FlSVP8T1WuhbAFF6QDhJc0zsoWzX4wXARhhpE=
github.com/go-openapi/spec v0.17.0/go.mod h1:XkF/MOi14NmjsfZ8VtAKf8pIlbZzyoTvZsdfssdxcBI=
github.com/go-openapi/strfmt v0.17.0 h1:1isAxYf//QDTnVzbLAMrUK++0k1EjeLJU/gTOR0o3Mc=
github.com/go-openapi/strfmt v0.17.0/go.mod h1:P82hnJI0CXkErkXi8IKjPbNBM6lV6+5pLP5l494TcyU=
github.com/go-openapi/swag v0.17.0 h1:iqrgMg7Q7SvtbWLlltPrkMs0UBJI6oTSs79JFRUi880=
github.com/go-openapi/swag v0.17.0/go.mod h1:AByQ+nYG6gQg71GINrmuDXCPWdL640yX49/kXLo40Tg=
github.com/go-openapi/validate v0.17.0 h1:pqoViQz3YLOGIhAmD0N4Lt6pa/3Gnj3ymKqQwq8iS6U=
github.com/go-openapi/validate v0.17.0/go.mod h1:Uh4HdOzKt19xGIGm1qHf/ofbX1YQ4Y+MYsct2VUrAJ4=
github.com/golang/mock v1.4.3 h1:GV+pQPG/EUUbkh47niozDcADz6go/dUwhVzdUQHIVRw=
github.com/golang/mock v1.4.3/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw=
github.com/golang/mock v1.4.4 h1:l75CXGRSwbaYNpl/Z2X1XIIAMSCquvXgpVZDhwEIJsc=
github.com/golang/mock v1.4.4/go.mod h1:l3mdAwkq5BuhzHwde/uurv3sEJeZMXNpwsxVWU71h+4=
github.com/go-ping/ping v0.0.0-20210911151512-381826476871 h1:wtjTfjwAR/BYYMJ+QOLI/3J/qGEI0fgrkZvgsEWK2/Q=
github.com/go-ping/ping v0.0.0-20210911151512-381826476871/go.mod h1:xIFjORFzTxqIV/tDVGO4eDy/bLuSyawEeojSm3GfRGk=
github.com/golang/mock v1.5.0/go.mod h1:CWnOUgYIOo4TcNZ0wHX3YZCqsaM1I1Jvs6v3mP3KVu8=
github.com/golang/mock v1.6.0 h1:ErTB+efbowRARo13NNdxyJji2egdxLGQhRaY+DUumQc=
github.com/golang/mock v1.6.0/go.mod h1:p6yTPP+5HYm5mzsMV8JkE6ZKdX+/wYM6Hr+LicevLPs=
github.com/gomodule/redigo v2.0.0+incompatible/go.mod h1:B4C85qUVwatsJoIUNIfCRsp7qO0iAmpGFZ4EELWSbC4=
github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI=
github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.5 h1:Khx7svrCpmxxtHBq5j2mp/xVjsi8hQMfNLvJFAlrGgU=
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/uuid v1.0.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/gotify/go-api-client/v2 v2.0.4 h1:0w8skCr8aLBDKaQDg31LKKHUGF7rt7zdRpR+6cqIAlE=
github.com/google/uuid v1.2.0 h1:qJYtXnJRWmpe7m/3XlyhrsLrEURqHRM2kxzoxXqyUDs=
github.com/google/uuid v1.2.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/gotify/go-api-client/v2 v2.0.4/go.mod h1:VKiah/UK20bXsr0JObE1eBVLW44zbBouzjuri9iwjFU=
github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99/go.mod h1:1lJo3i6rXxKeerYnT8Nvf0QmHCRC1n8sfWVwXF2Frvo=
github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI=
github.com/josharian/native v0.0.0-20200817173448-b6b71def0850 h1:uhL5Gw7BINiiPAo24A2sxkcDI0Jt/sqp1v5xQCniEFA=
github.com/josharian/native v0.0.0-20200817173448-b6b71def0850/go.mod h1:7X/raswPFr05uY3HiLlYeyQntB6OO7E/d2Cu7qoaN2w=
github.com/jsimonetti/rtnetlink v0.0.0-20190606172950-9527aa82566a/go.mod h1:Oz+70psSo5OFh8DBl0Zv2ACw7Esh6pPUphlvZG9x7uw=
github.com/jsimonetti/rtnetlink v0.0.0-20200117123717-f846d4f6c1f4/go.mod h1:WGuG/smIU4J/54PblvSbh+xvCZmpJnFgr3ds6Z55XMQ=
github.com/jsimonetti/rtnetlink v0.0.0-20201009170750-9c6f07d100c1/go.mod h1:hqoO/u39cqLeBLebZ8fWdE96O7FxrAsRYhnVOdgHxok=
github.com/jsimonetti/rtnetlink v0.0.0-20201216134343-bde56ed16391/go.mod h1:cR77jAZG3Y3bsb8hF6fHJbFoyFukLFOkQ98S0pQz3xw=
github.com/jsimonetti/rtnetlink v0.0.0-20201220180245-69540ac93943/go.mod h1:z4c53zj6Eex712ROyh8WI0ihysb5j2ROyV42iNogmAs=
github.com/jsimonetti/rtnetlink v0.0.0-20210122163228-8d122574c736/go.mod h1:ZXpIyOK59ZnN7J0BV99cZUPmsqDRZ3eq5X+st7u/oSA=
github.com/jsimonetti/rtnetlink v0.0.0-20210212075122-66c871082f2b h1:c3NTyLNozICy8B4mlMXemD3z/gXgQzVXZS/HqT+i3do=
github.com/jsimonetti/rtnetlink v0.0.0-20210212075122-66c871082f2b/go.mod h1:8w9Rh8m+aHZIG69YPGGem1i5VzoyRC8nw2kA8B+ik5U=
github.com/kevinburke/ssh_config v0.0.0-20190725054713-01f96b0aa0cd/go.mod h1:CT57kijsi8u/K/BOFA39wgDQJ9CxiF4nAY/ojJ6r6mM=
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI=
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
github.com/kr/pty v1.1.8/go.mod h1:O1sed60cT9XZ5uDucP5qwvh+TE3NnUj51EiZO/lmSfw=
github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
github.com/kyokomi/emoji v2.2.4+incompatible h1:np0woGKwx9LiHAQmwZx79Oc0rHpNw3o+3evou4BEPv4=
github.com/kyokomi/emoji v2.2.4+incompatible/go.mod h1:mZ6aGCD7yk8j6QY6KICwnZ2pxoszVseX1DNoGtU2tBA=
github.com/mailru/easyjson v0.0.0-20180823135443-60711f1a8329 h1:2gxZ0XQIU/5z3Z3bUBu+FXuk2pFbkN6tcwi/pjyaDic=
github.com/mailru/easyjson v0.0.0-20180823135443-60711f1a8329/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc=
github.com/mattn/go-colorable v0.1.4 h1:snbPLB8fVfU9iwbbo30TPtbLRzwWu6aJS6Xh4eaaviA=
github.com/mattn/go-colorable v0.1.4/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE=
github.com/mattn/go-colorable v0.1.8 h1:c1ghPdyEDarC70ftn0y+A/Ee++9zz8ljHG1b13eJ0s8=
github.com/mattn/go-colorable v0.1.8/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc=
github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s=
github.com/mattn/go-isatty v0.0.11 h1:FxPOTFNqGkuDUGi3H/qkUbQO4ZiBa2brKq5r0l8TGeM=
github.com/mattn/go-isatty v0.0.11/go.mod h1:PhnuNfih5lzO57/f3n+odYbM4JtupLOxQOAqxQCu2WE=
github.com/mattn/go-isatty v0.0.12 h1:wuysRhFDzyxgEmMf5xjvJ2M9dZoWAXNNr5LSBS7uHXY=
github.com/mattn/go-colorable v0.1.9 h1:sqDoxXbdeALODt0DAeJCVp38ps9ZogZEAXjus69YV3U=
github.com/mattn/go-colorable v0.1.9/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc=
github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU=
github.com/mattn/go-isatty v0.0.14 h1:yVuAays6BHfxijgZPzw+3Zlu5yQgKGP2/hcQbHb7S9Y=
github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94=
github.com/mdlayher/ethtool v0.0.0-20210210192532-2b88debcdd43 h1:WgyLFv10Ov49JAQI/ZLUkCZ7VJS3r74hwFIGXJsgZlY=
github.com/mdlayher/ethtool v0.0.0-20210210192532-2b88debcdd43/go.mod h1:+t7E0lkKfbBsebllff1xdTmyJt8lH37niI6kwFk9OTo=
github.com/mdlayher/genetlink v1.0.0 h1:OoHN1OdyEIkScEmRgxLEe2M9U8ClMytqA5niynLtfj0=
github.com/mdlayher/genetlink v1.0.0/go.mod h1:0rJ0h4itni50A86M2kHcgS85ttZazNt7a8H2a2cw0Gc=
github.com/mdlayher/netlink v0.0.0-20190409211403-11939a169225/go.mod h1:eQB3mZE4aiYnlUsyGGCOpPETfdQq4Jhsgf1fk3cwQaA=
github.com/mdlayher/netlink v1.0.0/go.mod h1:KxeJAFOFLG6AjpyDkQ/iIhxygIUKD+vcwqcnu43w/+M=
github.com/mdlayher/netlink v1.1.0/go.mod h1:H4WCitaheIsdF9yOYu8CFmCgQthAPIWZmcKp9uZHgmY=
github.com/mdlayher/netlink v1.1.1/go.mod h1:WTYpFb/WTvlRJAyKhZL5/uy69TDDpHHu2VZmb2XgV7o=
github.com/mdlayher/netlink v1.2.0/go.mod h1:kwVW1io0AZy9A1E2YYgaD4Cj+C+GPkU6klXCMzIJ9p8=
github.com/mdlayher/netlink v1.2.1/go.mod h1:bacnNlfhqHqqLo4WsYeXSqfyXkInQ9JneWI68v1KwSU=
github.com/mdlayher/netlink v1.2.2-0.20210123213345-5cc92139ae3e/go.mod h1:bacnNlfhqHqqLo4WsYeXSqfyXkInQ9JneWI68v1KwSU=
github.com/mdlayher/netlink v1.3.0/go.mod h1:xK/BssKuwcRXHrtN04UBkwQ6dY9VviGGuriDdoPSWys=
github.com/mdlayher/netlink v1.4.0 h1:n3ARR+Fm0dDv37dj5wSWZXDKcy+U0zwcXS3zKMnSiT0=
github.com/mdlayher/netlink v1.4.0/go.mod h1:dRJi5IABcZpBD2A3D0Mv/AiX8I9uDEu5oGkAVrekmf8=
github.com/miekg/dns v1.1.40 h1:pyyPFfGMnciYUk/mXpKkVmeMQjfXqt3FAJ2hy7tPiLA=
github.com/miekg/dns v1.1.40/go.mod h1:KNUDUusw/aVsxyTYZM1oqvCicbwhgbNgztCETuNZ7xM=
github.com/mikioh/ipaddr v0.0.0-20190404000644-d465c8ab6721 h1:RlZweED6sbSArvlE924+mUcZuXKLBHA35U7LN621Bws=
github.com/mikioh/ipaddr v0.0.0-20190404000644-d465c8ab6721/go.mod h1:Ickgr2WtCLZ2MDGd4Gr0geeCH5HybhRJbonOgQpvSxc=
github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
github.com/mitchellh/mapstructure v1.1.2 h1:fmNYVwqnSfB9mZU6OS2O6GsXM+wcskZDuKQzvN1EDeE=
github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
github.com/mr-tron/base58 v1.1.3 h1:v+sk57XuaCKGXpWtVBX8YJzO7hMGx4Aajh4TQbdEFdc=
github.com/mr-tron/base58 v1.1.3/go.mod h1:BinMc/sQntlIE1frQmRFPUoPA1Zkr8VRgBdjWI2mNwc=
github.com/mr-tron/base58 v1.2.0 h1:T/HDJBh4ZCPbU39/+c3rRvE0uKBQlU27+QI8LJ4t64o=
github.com/mr-tron/base58 v1.2.0/go.mod h1:BinMc/sQntlIE1frQmRFPUoPA1Zkr8VRgBdjWI2mNwc=
github.com/pborman/uuid v1.2.0/go.mod h1:X/NO0urCmaxf9VXbdlT7C2Yzkj2IKimNn4k+gtPdI/k=
github.com/pelletier/go-buffruneio v0.2.0/go.mod h1:JkE26KsDizTr40EUHkXVtNPvgGtbSNq5BcowyYOWdKo=
github.com/phayes/permbits v0.0.0-20190612203442-39d7c581d2ee h1:P6U24L02WMfj9ymZTxl7CxS73JC99x3ukk+DBkgQGQs=
github.com/phayes/permbits v0.0.0-20190612203442-39d7c581d2ee/go.mod h1:3uODdxMgOaPYeWU7RzZLxVtJHZ/x1f/iHkBZuKJDzuY=
github.com/pkg/errors v0.8.1 h1:iURUrRGxPUNPdy5/HRSm+Yj6okJ6UtLINN0Q9M4+h3I=
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/qdm12/dns v1.4.0 h1:P8kVMGo7yIEZSk18fA9XQh9faL1CW20aHosWP064MAA=
github.com/qdm12/dns v1.4.0/go.mod h1:WUY4/U8Z2O8888DPrahrIBv8GdYeoIcEy4aUDecZ+UM=
github.com/qdm12/golibs v0.0.0-20201227203847-2fd99ffdfdba/go.mod h1:pikkTN7g7zRuuAnERwqW1yAFq6pYmxrxpjiwGvb0Ysc=
github.com/qdm12/golibs v0.0.0-20210124192933-79a950eaf217 h1:/eMBq0vbc/KmVPXbwLfssp547pp6APRS1x/JNmPvm0s=
github.com/qdm12/golibs v0.0.0-20210124192933-79a950eaf217/go.mod h1:pikkTN7g7zRuuAnERwqW1yAFq6pYmxrxpjiwGvb0Ysc=
github.com/qdm12/golibs v0.0.0-20210206072445-35759e951561 h1:YgdQYYj4cEq8jK9TWCItlOOLfmDMVMajcp0YGVCW7cA=
github.com/qdm12/golibs v0.0.0-20210206072445-35759e951561/go.mod h1:pikkTN7g7zRuuAnERwqW1yAFq6pYmxrxpjiwGvb0Ysc=
github.com/qdm12/ss-server v0.1.0 h1:WV9MkHCDEWRwe4WpnYFeR/zcZAxYoTbfntLDnw9AQ50=
github.com/qdm12/ss-server v0.1.0/go.mod h1:ABVUkxubboL3vqBkOwDV9glX1/x7SnYrckBe5d+M/zw=
github.com/qdm12/updated v0.0.0-20210102005021-dd457d77f94a h1:gkyP+gMEeBgMgyRYGrVNcoy6cL1065IvXsyfB6xboIc=
github.com/qdm12/updated v0.0.0-20210102005021-dd457d77f94a/go.mod h1:bbJGxEYCnsA8WU4vBcXYU6mOoHyzdP458FIKP4mfLJM=
github.com/qdm12/dns v1.11.0 h1:jpcD5DZXXQSQe5a263PL09ghukiIdptvXFOZvyKEm6Q=
github.com/qdm12/dns v1.11.0/go.mod h1:FmQsNOUcrrZ4UFzWAiED56AKXeNgaX3ySbmPwEfNjjE=
github.com/qdm12/golibs v0.0.0-20210603202746-e5494e9c2ebb/go.mod h1:15RBzkun0i8XB7ADIoLJWp9ITRgsz3LroEI2FiOXLRg=
github.com/qdm12/golibs v0.0.0-20210723175634-a75ca7fd74c2/go.mod h1:6aRbg4Z/bTbm9JfxsGXfWKHi7zsOvPfUTK1S5HuAFKg=
github.com/qdm12/golibs v0.0.0-20210822203818-5c568b0777b6 h1:bge5AL7cjHJMPz+5IOz5yF01q/l8No6+lIEBieA8gMg=
github.com/qdm12/golibs v0.0.0-20210822203818-5c568b0777b6/go.mod h1:6aRbg4Z/bTbm9JfxsGXfWKHi7zsOvPfUTK1S5HuAFKg=
github.com/qdm12/goshutdown v0.3.0 h1:pqBpJkdwlZlfTEx4QHtS8u8CXx6pG0fVo6S1N0MpSEM=
github.com/qdm12/goshutdown v0.3.0/go.mod h1:EqZ46No00kCTZ5qzdd3qIzY6ayhMt24QI8Mh8LVQYmM=
github.com/qdm12/gosplash v0.1.0 h1:Sfl+zIjFZFP7b0iqf2l5UkmEY97XBnaKkH3FNY6Gf7g=
github.com/qdm12/gosplash v0.1.0/go.mod h1:+A3fWW4/rUeDXhY3ieBzwghKdnIPFJgD8K3qQkenJlw=
github.com/qdm12/ss-server v0.3.0 h1:BfKv4OU6dYb2KcDMYpTc7LIuO2jB73g3JCzy988GrLI=
github.com/qdm12/ss-server v0.3.0/go.mod h1:ug+nWfuzKw/h5fxL1B6e9/OhkVuWJX4i2V1Pf0pJU1o=
github.com/qdm12/updated v0.0.0-20210603204757-205acfe6937e h1:4q+uFLawkaQRq3yARYLsjJPZd2wYwxn4g6G/5v0xW1g=
github.com/qdm12/updated v0.0.0-20210603204757-205acfe6937e/go.mod h1:UvJRGkZ9XL3/D7e7JiTTVLm1F3Cymd3/gFpD6frEpBo=
github.com/riobard/go-bloom v0.0.0-20200614022211-cdc8013cb5b3 h1:f/FNXud6gA3MNr8meMVVGxhp+QBTqY91tM8HjEuMjGg=
github.com/riobard/go-bloom v0.0.0-20200614022211-cdc8013cb5b3/go.mod h1:HgjTstvQsPGkxUsCd2KWxErBblirPizecHcpD3ffK+s=
github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
github.com/sergi/go-diff v1.0.0/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAmXWZgo=
github.com/src-d/gcfg v1.4.0/go.mod h1:p/UMsR43ujA89BJY9duynAwIpvqEujIH/jFlfL7jWoI=
github.com/stretchr/objx v0.1.0 h1:4G4v2dO3VZwixGIRoQ5Lfboy6nUhCyYzaqnIAPPhYs4=
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/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
github.com/stretchr/testify v1.6.1 h1:hDPOHmpOpP40lSULcqw7IrRb/u7w6RpDC9399XyoNd0=
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY=
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/vishvananda/netlink v1.1.0 h1:1iyaYNBLmP6L0220aDnYQpo1QEV4t4hJ+xEEhhJH8j0=
@@ -124,60 +136,109 @@ github.com/vishvananda/netns v0.0.0-20191106174202-0a2b9b5464df h1:OviZH7qLw/7Zo
github.com/vishvananda/netns v0.0.0-20191106174202-0a2b9b5464df/go.mod h1:JP3t17pCcGlemwknint6hfoeCVQrEMVwxRLRjXpq+BU=
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=
go.uber.org/atomic v1.5.0 h1:OI5t8sDa1Or+q8AeE+yKeB/SDYioSHAgcVljj9JIETY=
go.uber.org/atomic v1.5.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ=
go.uber.org/multierr v1.3.0 h1:sFPn2GLc3poCkfrpIXGhBD2X0CMIo4Q/zSULXrj/+uc=
go.uber.org/multierr v1.3.0/go.mod h1:VgVr7evmIr6uPjLBxg28wmKNXyqE9akIJ5XnfpiKl+4=
go.uber.org/tools v0.0.0-20190618225709-2cfd321de3ee h1:0mgffUl7nfd+FpvXMVz4IDEaUSmT1ysygQC7qYo7sG4=
go.uber.org/tools v0.0.0-20190618225709-2cfd321de3ee/go.mod h1:vJERXedbb3MVM5f9Ejo0C68/HhF8uaILCdgjnY+goOA=
go.uber.org/zap v1.13.0 h1:nR6NoDBgAf67s68NhaXbsojM+2gxp3S1hWkHDl27pVU=
go.uber.org/zap v1.13.0/go.mod h1:zwrFLgMcdUuIBviXEYEH1YKNaOBnKXsx2IPda5bBwHM=
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
go4.org/intern v0.0.0-20210108033219-3eb7198706b2 h1:VFTf+jjIgsldaz/Mr00VaCSswHJrI2hIjQygE/W4IMg=
go4.org/intern v0.0.0-20210108033219-3eb7198706b2/go.mod h1:vLqJ+12kCw61iCWsPto0EOHhBS+o4rO5VIucbc9g2Cc=
go4.org/unsafe/assume-no-moving-gc v0.0.0-20201222175341-b30ae309168e/go.mod h1:FftLjUGFEDu5k8lt0ddY+HcrH/qU/0qk+H8j9/nTl3E=
go4.org/unsafe/assume-no-moving-gc v0.0.0-20201222180813-1025295fd063 h1:1tk03FUNpulq2cuWpXZWj649rwJpk0d20rxWiopKRmc=
go4.org/unsafe/assume-no-moving-gc v0.0.0-20201222180813-1025295fd063/go.mod h1:FftLjUGFEDu5k8lt0ddY+HcrH/qU/0qk+H8j9/nTl3E=
golang.org/x/crypto v0.0.0-20190219172222-a4c6cb3142f2/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20190701094942-4def268fd1a4/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20200117160349-530e935923ad/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20200728195943-123391ffb6de h1:ikNHVSjEfnvz6sxdSPCaPt572qowuyMDMJLLm3Db3ig=
golang.org/x/crypto v0.0.0-20200728195943-123391ffb6de/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20201221181555-eec23a3978ad h1:DN0cp81fZ3njFcrLCytUHRSUkqBjfTo4Tx9RJTWs0EY=
golang.org/x/crypto v0.0.0-20201221181555-eec23a3978ad/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I=
golang.org/x/lint v0.0.0-20190930215403-16217165b5de h1:5hukYrvBGR8/eNkX5mdUezrA6JiaEZDtJb9Ei+1LlBs=
golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc=
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20210220033148-5ea612d1eb83/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I=
golang.org/x/crypto v0.0.0-20210503195802-e9a32991a82e/go.mod h1:P+XmwS30IXTQdn5tA2iutPOUgjI07+tq3H3K9MVA1s8=
golang.org/x/crypto v0.0.0-20210513164829-c07d793c2f9a/go.mod h1:P+XmwS30IXTQdn5tA2iutPOUgjI07+tq3H3K9MVA1s8=
golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97 h1:/UOmuWzQfxxo9UtlXMwuQU8CMgg1eZXqTRwkSQJWKOI=
golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/net v0.0.0-20181005035420-146acd28ed58/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20190724013045-ca1201d0de80/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20190827160401-ba9fcec4b297/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20190923162816-aa69164e4478/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20191007182048-72f939374954/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20201010224723-4f7140c49acb/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
golang.org/x/net v0.0.0-20201216054612-986b41b23924/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
golang.org/x/net v0.0.0-20201224014010-6772e930b67b/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
golang.org/x/net v0.0.0-20210119194325-5f4716e94777/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
golang.org/x/net v0.0.0-20210316092652-d523dce5a7f4/go.mod h1:RBQZq4jEuRlivfhVLdyRGr576XBO4/greRjx4P4O3yc=
golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM=
golang.org/x/net v0.0.0-20210504132125-bbd867fde50d h1:nTDGCTeAu2LhcsHTRzjyIUbZHCJ4QePArsm27Hka0UM=
golang.org/x/net v0.0.0-20210504132125-bbd867fde50d/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c h1:5KslGYwFpkhGh+Q16bwMP3cOontH8FOep7tGV86Y7SQ=
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190221075227-b4e8571b14e0/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190411185658-b44545bcd369/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190606203320-7fc4e5ec1444/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190826190057-c7b8b68b1456/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190924154521-2837fb4f24fe/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191008105621-543471e840be/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201223074533-0d417f636930 h1:vRgIt+nup/B/BwIS0g2oC0haq0iqbV3ZA+u6+0TlNCo=
golang.org/x/sys v0.0.0-20201223074533-0d417f636930/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201009025420-dfb3f7c4e634/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201118182958-a01c418693c7/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201218084310-7d0127a74742/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210110051926-789bb1bd4061/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210123111255-9b0068b26619/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210216163648-f7da38b97c65/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210309040221-94ec62e08169/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210315160823-c6e025ad8005/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210403161142-5e06dd20ab57/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210503173754-0981d6026fa6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c h1:F1jZWGFhYfh0Ci55sIpILtKKK8p3i2/krTr0H1rg74I=
golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw=
golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
golang.org/x/tools v0.0.0-20190729092621-ff9f1409240a/go.mod h1:jcCCGcm9btYwXyDqrUWc6MKQKKGJCWEQ3AfLSRIbEuI=
golang.org/x/tools v0.0.0-20191029041327-9cc4af7d6b2c/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191029190741-b9c20aec41a5 h1:hKsoRgsbwY1NafxrwTs+k64bikrLBkAgPir1TNCj3Zs=
golang.org/x/tools v0.0.0-20191029190741-b9c20aec41a5/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191216052735-49a3e744a425/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.1.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0=
golang.org/x/tools v0.1.1/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE=
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.zx2c4.com/wireguard v0.0.0-20210427022245-097af6e1351b/go.mod h1:a057zjmoc00UN7gVkaJt2sXVK523kMJcogDTEvPIasg=
golang.zx2c4.com/wireguard v0.0.0-20210805125648-3957e9b9dd19 h1:ab2jcw2W91Rz07eHAb8Lic7sFQKO0NhBftjv6m/gL/0=
golang.zx2c4.com/wireguard v0.0.0-20210805125648-3957e9b9dd19/go.mod h1:laHzsbfMhGSobUmruXWAyMKKHSqvIcrqZJMyHD+/3O8=
golang.zx2c4.com/wireguard/wgctrl v0.0.0-20210803171230-4253848d036c h1:ADNrRDI5NR23/TUCnEmlLZLt4u9DnZ2nwRkPrAcFvto=
golang.zx2c4.com/wireguard/wgctrl v0.0.0-20210803171230-4253848d036c/go.mod h1:+1XihzyZUBJcSc5WO9SwNA7v26puQwOEDwanaxfNXPQ=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY=
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
gopkg.in/src-d/go-billy.v4 v4.3.2/go.mod h1:nDjArDMp+XMs1aFAESLRjfGSgfvoYN0hDfzEk0GjC98=
gopkg.in/src-d/go-git-fixtures.v3 v3.5.0/go.mod h1:dLBcvytrw/TYZsNTWCnkNF2DSIlzWYqTe3rJR56Ac7g=
gopkg.in/src-d/go-git.v4 v4.13.1/go.mod h1:nx5NYcxdKxq5fpltdHnPa2Exj4Sx0EclMWZQbYDu2z8=
@@ -186,7 +247,6 @@ gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
honnef.co/go/tools v0.0.1-2019.2.3 h1:3JgtbtFHMiCmsznwGVTUWbgGov+pVqnlf1dEJTNAXeM=
honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg=
rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0=
rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA=
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-20210718074554-06ca8145d722/go.mod h1:z0nx+Dh+7N7CC8V5ayHtHGpZpxLQZZxkIaaz6HN65Ls=

View File

@@ -2,22 +2,28 @@
package alpine
import (
"github.com/qdm12/golibs/os"
"github.com/qdm12/golibs/os/user"
"os/user"
)
type Configurator interface {
CreateUser(username string, uid int) (createdUsername string, err error)
var _ Alpiner = (*Alpine)(nil)
type Alpiner interface {
UserCreater
VersionGetter
}
type configurator struct {
openFile os.OpenFileFunc
osUser user.OSUser
type Alpine struct {
alpineReleasePath string
passwdPath string
lookupID func(uid string) (*user.User, error)
lookup func(username string) (*user.User, error)
}
func NewConfigurator(openFile os.OpenFileFunc, osUser user.OSUser) Configurator {
return &configurator{
openFile: openFile,
osUser: osUser,
func New() *Alpine {
return &Alpine{
alpineReleasePath: "/etc/alpine-release",
passwdPath: "/etc/passwd",
lookupID: user.LookupId,
lookup: user.Lookup,
}
}

View File

@@ -1,35 +1,51 @@
package alpine
import (
"errors"
"fmt"
"os"
"os/user"
"strconv"
)
var (
ErrUserAlreadyExists = errors.New("user already exists")
)
type UserCreater interface {
CreateUser(username string, uid int) (createdUsername string, err error)
}
// CreateUser creates a user in Alpine with the given UID.
func (c *configurator) CreateUser(username string, uid int) (createdUsername string, err error) {
UIDStr := fmt.Sprintf("%d", uid)
u, err := c.osUser.LookupID(UIDStr)
func (a *Alpine) CreateUser(username string, uid int) (createdUsername string, err error) {
UIDStr := strconv.Itoa(uid)
u, err := a.lookupID(UIDStr)
_, unknownUID := err.(user.UnknownUserIdError)
if err != nil && !unknownUID {
return "", fmt.Errorf("cannot create user: %w", err)
} else if u != nil {
return "", err
}
if u != nil {
if u.Username == username {
return "", nil
}
return u.Username, nil
}
u, err = c.osUser.Lookup(username)
u, err = a.lookup(username)
_, unknownUsername := err.(user.UnknownUserError)
if err != nil && !unknownUsername {
return "", fmt.Errorf("cannot create user: %w", err)
} else if u != nil {
return "", fmt.Errorf("cannot create user: user with name %s already exists for ID %s instead of %d",
username, u.Uid, uid)
return "", err
}
file, err := c.openFile("/etc/passwd", os.O_APPEND|os.O_WRONLY, 0644)
if u != nil {
return "", fmt.Errorf("%w: with name %s for ID %s instead of %d",
ErrUserAlreadyExists, username, u.Uid, uid)
}
file, err := os.OpenFile(a.passwdPath, os.O_APPEND|os.O_WRONLY, 0644)
if err != nil {
return "", fmt.Errorf("cannot create user: %w", err)
return "", err
}
s := fmt.Sprintf("%s:x:%d:::/dev/null:/sbin/nologin\n", username, uid)
_, err = file.WriteString(s)
@@ -37,5 +53,6 @@ func (c *configurator) CreateUser(username string, uid int) (createdUsername str
_ = file.Close()
return "", err
}
return username, file.Close()
}

View File

@@ -0,0 +1,31 @@
package alpine
import (
"context"
"io"
"os"
"strings"
)
type VersionGetter interface {
Version(ctx context.Context) (version string, err error)
}
func (a *Alpine) Version(ctx context.Context) (version string, err error) {
file, err := os.OpenFile(a.alpineReleasePath, os.O_RDONLY, 0)
if err != nil {
return "", err
}
b, err := io.ReadAll(file)
if err != nil {
return "", err
}
if err := file.Close(); err != nil {
return "", err
}
version = strings.ReplaceAll(string(b), "\n", "")
return version, nil
}

View File

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

View File

@@ -1,21 +1,22 @@
// Package cli defines an interface CLI to run command line operations.
package cli
import (
"context"
var _ CLIer = (*CLI)(nil)
"github.com/qdm12/golibs/os"
)
type CLI interface {
ClientKey(args []string, openFile os.OpenFileFunc) error
HealthCheck(ctx context.Context) error
OpenvpnConfig(os os.OS) error
Update(ctx context.Context, args []string, os os.OS) error
type CLIer interface {
ClientKeyFormatter
HealthChecker
OpenvpnConfigMaker
Updater
ServersFormatter
}
type cli struct{}
func New() CLI {
return &cli{}
type CLI struct {
repoServersPath string
}
func New() *CLI {
return &CLI{
repoServersPath: "./internal/storage/servers.json",
}
}

View File

@@ -3,24 +3,28 @@ package cli
import (
"flag"
"fmt"
"io/ioutil"
"io"
"os"
"strings"
"github.com/qdm12/gluetun/internal/constants"
"github.com/qdm12/golibs/os"
)
func (c *cli) ClientKey(args []string, openFile os.OpenFileFunc) error {
type ClientKeyFormatter interface {
ClientKey(args []string) error
}
func (c *CLI) ClientKey(args []string) error {
flagSet := flag.NewFlagSet("clientkey", flag.ExitOnError)
filepath := flagSet.String("path", constants.ClientKey, "file path to the client.key file")
if err := flagSet.Parse(args); err != nil {
return err
}
file, err := openFile(*filepath, os.O_RDONLY, 0)
file, err := os.OpenFile(*filepath, os.O_RDONLY, 0)
if err != nil {
return err
}
data, err := ioutil.ReadAll(file)
data, err := io.ReadAll(file)
if err != nil {
_ = file.Close()
return err

View File

@@ -0,0 +1,133 @@
package cli
import (
"errors"
"flag"
"fmt"
"os"
"path/filepath"
"github.com/qdm12/gluetun/internal/constants"
"github.com/qdm12/gluetun/internal/storage"
)
type ServersFormatter interface {
FormatServers(args []string) error
}
var (
ErrFormatNotRecognized = errors.New("format is not recognized")
ErrProviderUnspecified = errors.New("VPN provider to format was not specified")
ErrOpenOutputFile = errors.New("cannot open output file")
ErrWriteOutput = errors.New("cannot write to output file")
ErrCloseOutputFile = errors.New("cannot close output file")
)
func (c *CLI) FormatServers(args []string) error {
var format, output string
var cyberghost, expressvpn, fastestvpn, hideMyAss, ipvanish, ivpn, mullvad,
nordvpn, perfectPrivacy, pia, privado, privatevpn, protonvpn, purevpn, surfshark,
torguard, vpnUnlimited, vyprvpn, wevpn, windscribe bool
flagSet := flag.NewFlagSet("markdown", flag.ExitOnError)
flagSet.StringVar(&format, "format", "markdown", "Format to use which can be: 'markdown'")
flagSet.StringVar(&output, "output", "/dev/stdout", "Output file to write the formatted data to")
flagSet.BoolVar(&cyberghost, "cyberghost", false, "Format Cyberghost servers")
flagSet.BoolVar(&expressvpn, "expressvpn", false, "Format ExpressVPN servers")
flagSet.BoolVar(&fastestvpn, "fastestvpn", false, "Format FastestVPN servers")
flagSet.BoolVar(&hideMyAss, "hidemyass", false, "Format HideMyAss servers")
flagSet.BoolVar(&ipvanish, "ipvanish", false, "Format IpVanish servers")
flagSet.BoolVar(&ivpn, "ivpn", false, "Format IVPN servers")
flagSet.BoolVar(&mullvad, "mullvad", false, "Format Mullvad servers")
flagSet.BoolVar(&nordvpn, "nordvpn", false, "Format Nordvpn servers")
flagSet.BoolVar(&perfectPrivacy, "perfectprivacy", false, "Format Perfect Privacy servers")
flagSet.BoolVar(&pia, "pia", false, "Format Private Internet Access servers")
flagSet.BoolVar(&privado, "privado", false, "Format Privado servers")
flagSet.BoolVar(&privatevpn, "privatevpn", false, "Format Private VPN servers")
flagSet.BoolVar(&protonvpn, "protonvpn", false, "Format Protonvpn servers")
flagSet.BoolVar(&purevpn, "purevpn", false, "Format Purevpn servers")
flagSet.BoolVar(&surfshark, "surfshark", false, "Format Surfshark servers")
flagSet.BoolVar(&torguard, "torguard", false, "Format Torguard servers")
flagSet.BoolVar(&vpnUnlimited, "vpnunlimited", false, "Format VPN Unlimited servers")
flagSet.BoolVar(&vyprvpn, "vyprvpn", false, "Format Vyprvpn servers")
flagSet.BoolVar(&wevpn, "wevpn", false, "Format WeVPN servers")
flagSet.BoolVar(&windscribe, "windscribe", false, "Format Windscribe servers")
if err := flagSet.Parse(args); err != nil {
return err
}
if format != "markdown" {
return fmt.Errorf("%w: %s", ErrFormatNotRecognized, format)
}
logger := newNoopLogger()
storage, err := storage.New(logger, constants.ServersData)
if err != nil {
return fmt.Errorf("%w: %s", ErrNewStorage, err)
}
currentServers := storage.GetServers()
var formatted string
switch {
case cyberghost:
formatted = currentServers.Cyberghost.ToMarkdown()
case expressvpn:
formatted = currentServers.Expressvpn.ToMarkdown()
case fastestvpn:
formatted = currentServers.Fastestvpn.ToMarkdown()
case hideMyAss:
formatted = currentServers.HideMyAss.ToMarkdown()
case ipvanish:
formatted = currentServers.Ipvanish.ToMarkdown()
case ivpn:
formatted = currentServers.Ivpn.ToMarkdown()
case mullvad:
formatted = currentServers.Mullvad.ToMarkdown()
case nordvpn:
formatted = currentServers.Nordvpn.ToMarkdown()
case perfectPrivacy:
formatted = currentServers.Perfectprivacy.ToMarkdown()
case pia:
formatted = currentServers.Pia.ToMarkdown()
case privado:
formatted = currentServers.Privado.ToMarkdown()
case privatevpn:
formatted = currentServers.Privatevpn.ToMarkdown()
case protonvpn:
formatted = currentServers.Protonvpn.ToMarkdown()
case purevpn:
formatted = currentServers.Purevpn.ToMarkdown()
case surfshark:
formatted = currentServers.Surfshark.ToMarkdown()
case torguard:
formatted = currentServers.Torguard.ToMarkdown()
case vpnUnlimited:
formatted = currentServers.VPNUnlimited.ToMarkdown()
case vyprvpn:
formatted = currentServers.Vyprvpn.ToMarkdown()
case wevpn:
formatted = currentServers.Wevpn.ToMarkdown()
case windscribe:
formatted = currentServers.Windscribe.ToMarkdown()
default:
return ErrProviderUnspecified
}
output = filepath.Clean(output)
file, err := os.OpenFile(output, os.O_TRUNC|os.O_WRONLY|os.O_CREATE, 0644)
if err != nil {
return fmt.Errorf("%w: %s", ErrOpenOutputFile, err)
}
_, err = fmt.Fprint(file, formatted)
if err != nil {
_ = file.Close()
return fmt.Errorf("%w: %s", ErrWriteOutput, err)
}
err = file.Close()
if err != nil {
return fmt.Errorf("%w: %s", ErrCloseOutputFile, err)
}
return nil
}

View File

@@ -2,19 +2,38 @@ package cli
import (
"context"
"net"
"net/http"
"time"
"github.com/qdm12/gluetun/internal/constants"
"github.com/qdm12/gluetun/internal/configuration"
"github.com/qdm12/gluetun/internal/healthcheck"
"github.com/qdm12/golibs/params"
)
func (c *cli) HealthCheck(ctx context.Context) error {
type HealthChecker interface {
HealthCheck(ctx context.Context, env params.Interface, warner configuration.Warner) error
}
func (c *CLI) HealthCheck(ctx context.Context, env params.Interface,
warner configuration.Warner) error {
// Extract the health server port from the configuration.
config := configuration.Health{}
err := config.Read(env, warner)
if err != nil {
return err
}
_, port, err := net.SplitHostPort(config.ServerAddress)
if err != nil {
return err
}
const timeout = 10 * time.Second
httpClient := &http.Client{Timeout: timeout}
healthchecker := healthcheck.NewChecker(httpClient)
client := healthcheck.NewClient(httpClient)
ctx, cancel := context.WithTimeout(ctx, timeout)
defer cancel()
const url = "http://" + constants.HealthcheckAddress
return healthchecker.Check(ctx, url)
url := "http://127.0.0.1:" + port
return client.Check(ctx, url)
}

View File

@@ -0,0 +1,16 @@
package cli
import "github.com/qdm12/golibs/logging"
type noopLogger struct{}
func newNoopLogger() *noopLogger {
return new(noopLogger)
}
func (l *noopLogger) Debug(s string) {}
func (l *noopLogger) Info(s string) {}
func (l *noopLogger) Warn(s string) {}
func (l *noopLogger) Error(s string) {}
func (l *noopLogger) PatchLevel(level logging.Level) {}
func (l *noopLogger) PatchPrefix(prefix string) {}

View File

@@ -9,33 +9,40 @@ import (
"github.com/qdm12/gluetun/internal/constants"
"github.com/qdm12/gluetun/internal/provider"
"github.com/qdm12/gluetun/internal/storage"
"github.com/qdm12/golibs/logging"
"github.com/qdm12/golibs/os"
"github.com/qdm12/golibs/params"
)
func (c *cli) OpenvpnConfig(os os.OS) error {
logger, err := logging.NewLogger(logging.ConsoleEncoding, logging.InfoLevel)
type OpenvpnConfigMaker interface {
OpenvpnConfig(logger OpenvpnConfigLogger, env params.Interface) error
}
type OpenvpnConfigLogger interface {
Info(s string)
Warn(s string)
}
func (c *CLI) OpenvpnConfig(logger OpenvpnConfigLogger, env params.Interface) error {
storage, err := storage.New(logger, constants.ServersData)
if err != nil {
return err
}
allServers := storage.GetServers()
var allSettings configuration.Settings
err = allSettings.Read(env, allServers, logger)
if err != nil {
return err
}
providerConf := provider.New(allSettings.VPN.Provider.Name, allServers, time.Now)
connection, err := providerConf.GetConnection(allSettings.VPN.Provider.ServerSelection)
if err != nil {
return err
}
lines, err := providerConf.BuildConf(connection, allSettings.VPN.OpenVPN)
if err != nil {
return err
}
var allSettings configuration.Settings
err = allSettings.Read(params.NewEnv(), os, logger)
if err != nil {
return err
}
allServers, err := storage.New(logger, os, constants.ServersData).
SyncServers(constants.GetAllServers())
if err != nil {
return err
}
providerConf := provider.New(allSettings.OpenVPN.Provider.Name, allServers, time.Now)
connection, err := providerConf.GetOpenVPNConnection(allSettings.OpenVPN.Provider.ServerSelection)
if err != nil {
return err
}
lines := providerConf.BuildConf(connection, "nonroortuser", allSettings.OpenVPN)
fmt.Println(strings.Join(lines, "\n"))
return nil
}

View File

@@ -2,64 +2,120 @@ package cli
import (
"context"
"encoding/json"
"errors"
"flag"
"fmt"
"net/http"
"os"
"time"
"github.com/qdm12/gluetun/internal/configuration"
"github.com/qdm12/gluetun/internal/constants"
"github.com/qdm12/gluetun/internal/models"
"github.com/qdm12/gluetun/internal/storage"
"github.com/qdm12/gluetun/internal/updater"
"github.com/qdm12/golibs/logging"
"github.com/qdm12/golibs/os"
)
func (c *cli) Update(ctx context.Context, args []string, os os.OS) error {
var (
ErrModeUnspecified = errors.New("at least one of -enduser or -maintainer must be specified")
ErrNewStorage = errors.New("cannot create storage")
ErrUpdateServerInformation = errors.New("cannot update server information")
ErrWriteToFile = errors.New("cannot write updated information to file")
)
type Updater interface {
Update(ctx context.Context, args []string, logger UpdaterLogger) error
}
type UpdaterLogger interface {
Info(s string)
Warn(s string)
Error(s string)
}
func (c *CLI) Update(ctx context.Context, args []string, logger UpdaterLogger) error {
options := configuration.Updater{CLI: true}
var flushToFile bool
var endUserMode, maintainerMode, updateAll bool
flagSet := flag.NewFlagSet("update", flag.ExitOnError)
flagSet.BoolVar(&flushToFile, "file", false, "Write results to /gluetun/servers.json (for end users)")
flagSet.BoolVar(&options.Stdout, "stdout", false, "Write results to console to modify the program (for maintainers)")
flagSet.StringVar(&options.DNSAddress, "dns", "1.1.1.1", "DNS resolver address to use")
flagSet.BoolVar(&endUserMode, "enduser", false, "Write results to /gluetun/servers.json (for end users)")
flagSet.BoolVar(&maintainerMode, "maintainer", false,
"Write results to ./internal/storage/servers.json to modify the program (for maintainers)")
flagSet.StringVar(&options.DNSAddress, "dns", "8.8.8.8", "DNS resolver address to use")
flagSet.BoolVar(&updateAll, "all", false, "Update servers for all VPN providers")
flagSet.BoolVar(&options.Cyberghost, "cyberghost", false, "Update Cyberghost servers")
flagSet.BoolVar(&options.Expressvpn, "expressvpn", false, "Update ExpressVPN servers")
flagSet.BoolVar(&options.Fastestvpn, "fastestvpn", false, "Update FastestVPN servers")
flagSet.BoolVar(&options.HideMyAss, "hidemyass", false, "Update HideMyAss servers")
flagSet.BoolVar(&options.Ipvanish, "ipvanish", false, "Update IpVanish servers")
flagSet.BoolVar(&options.Ivpn, "ivpn", false, "Update IVPN servers")
flagSet.BoolVar(&options.Mullvad, "mullvad", false, "Update Mullvad servers")
flagSet.BoolVar(&options.Nordvpn, "nordvpn", false, "Update Nordvpn servers")
flagSet.BoolVar(&options.Perfectprivacy, "perfectprivacy", false, "Update Perfect Privacy servers")
flagSet.BoolVar(&options.PIA, "pia", false, "Update Private Internet Access post-summer 2020 servers")
flagSet.BoolVar(&options.Privado, "privado", false, "Update Privado servers")
flagSet.BoolVar(&options.Privatevpn, "privatevpn", false, "Update Private VPN servers")
flagSet.BoolVar(&options.Protonvpn, "protonvpn", false, "Update Protonvpn servers")
flagSet.BoolVar(&options.Purevpn, "purevpn", false, "Update Purevpn servers")
flagSet.BoolVar(&options.Surfshark, "surfshark", false, "Update Surfshark servers")
flagSet.BoolVar(&options.Torguard, "torguard", false, "Update Torguard servers")
flagSet.BoolVar(&options.VPNUnlimited, "vpnunlimited", false, "Update VPN Unlimited servers")
flagSet.BoolVar(&options.Vyprvpn, "vyprvpn", false, "Update Vyprvpn servers")
flagSet.BoolVar(&options.Wevpn, "wevpn", false, "Update WeVPN servers")
flagSet.BoolVar(&options.Windscribe, "windscribe", false, "Update Windscribe servers")
if err := flagSet.Parse(args); err != nil {
return err
}
logger, err := logging.NewLogger(logging.ConsoleEncoding, logging.InfoLevel)
if err != nil {
return err
if !endUserMode && !maintainerMode {
return ErrModeUnspecified
}
if !flushToFile && !options.Stdout {
return fmt.Errorf("at least one of -file or -stdout must be specified")
if updateAll {
options.EnableAll()
}
const clientTimeout = 10 * time.Second
httpClient := &http.Client{Timeout: clientTimeout}
storage := storage.New(logger, os, constants.ServersData)
currentServers, err := storage.SyncServers(constants.GetAllServers())
storage, err := storage.New(logger, constants.ServersData)
if err != nil {
return fmt.Errorf("cannot update servers: %w", err)
return fmt.Errorf("%w: %s", ErrNewStorage, err)
}
currentServers := storage.GetServers()
updater := updater.New(options, httpClient, currentServers, logger)
allServers, err := updater.UpdateServers(ctx)
if err != nil {
return err
return fmt.Errorf("%w: %s", ErrUpdateServerInformation, err)
}
if flushToFile {
if endUserMode {
if err := storage.FlushToFile(allServers); err != nil {
return fmt.Errorf("cannot update servers: %w", err)
return fmt.Errorf("%w: %s", ErrWriteToFile, err)
}
}
if maintainerMode {
if err := writeToEmbeddedJSON(c.repoServersPath, allServers); err != nil {
return fmt.Errorf("%w: %s", ErrWriteToFile, err)
}
}
return nil
}
func writeToEmbeddedJSON(repoServersPath string,
allServers models.AllServers) error {
const perms = 0600
f, err := os.OpenFile(repoServersPath,
os.O_TRUNC|os.O_WRONLY|os.O_CREATE, perms)
if err != nil {
return err
}
defer f.Close()
encoder := json.NewEncoder(f)
encoder.SetIndent("", " ")
return encoder.Encode(allServers)
}

View File

@@ -0,0 +1,95 @@
package configuration
import (
"errors"
"fmt"
"net"
"github.com/qdm12/gluetun/internal/constants"
"github.com/qdm12/golibs/params"
)
var (
errCustomNotSupported = errors.New("custom provider is not supported")
errCustomExtractFromFile = errors.New("cannot extract configuration from file")
)
func (settings *Provider) readCustom(r reader, vpnType string) (err error) {
settings.Name = constants.Custom
switch vpnType {
case constants.OpenVPN:
return settings.ServerSelection.OpenVPN.readCustom(r)
case constants.Wireguard:
return settings.ServerSelection.Wireguard.readCustom(r)
default:
return fmt.Errorf("%w: for VPN type %s", errCustomNotSupported, vpnType)
}
}
func (settings *OpenVPNSelection) readCustom(r reader) (err error) {
configFile, err := r.env.Get("OPENVPN_CUSTOM_CONFIG", params.CaseSensitiveValue(), params.Compulsory())
if err != nil {
return fmt.Errorf("environment variable OPENVPN_CUSTOM_CONFIG: %w", err)
}
settings.ConfFile = configFile
// For display and consistency purposes only,
// these values are not actually used since the file is re-read
// before each OpenVPN start.
_, connection, err := r.ovpnExt.Data(configFile)
if err != nil {
return fmt.Errorf("%w: %s", errCustomExtractFromFile, err)
}
settings.TCP = connection.Protocol == constants.TCP
return nil
}
func (settings *OpenVPN) readCustom(r reader) (err error) {
settings.ConfFile, err = r.env.Path("OPENVPN_CUSTOM_CONFIG",
params.Compulsory(), params.CaseSensitiveValue())
if err != nil {
return fmt.Errorf("environment variable OPENVPN_CUSTOM_CONFIG: %w", err)
}
return nil
}
func (settings *WireguardSelection) readCustom(r reader) (err error) {
settings.PublicKey, err = r.env.Get("WIREGUARD_PUBLIC_KEY",
params.CaseSensitiveValue(), params.Compulsory())
if err != nil {
return fmt.Errorf("environment variable WIREGUARD_PUBLIC_KEY: %w", err)
}
settings.EndpointIP, err = readWireguardEndpointIP(r.env)
if err != nil {
return err
}
settings.EndpointPort, err = r.env.Port("WIREGUARD_ENDPOINT_PORT", params.Compulsory(),
params.RetroKeys([]string{"WIREGUARD_PORT"}, r.onRetroActive))
if err != nil {
return fmt.Errorf("environment variable WIREGUARD_ENDPOINT_PORT: %w", err)
}
return nil
}
// readWireguardEndpointIP reads and parses the server endpoint IP
// address from the environment variable WIREGUARD_ENDPOINT_IP.
func readWireguardEndpointIP(env params.Interface) (endpointIP net.IP, err error) {
s, err := env.Get("WIREGUARD_ENDPOINT_IP", params.Compulsory())
if err != nil {
return nil, fmt.Errorf("environment variable WIREGUARD_ENDPOINT_IP: %w", err)
}
endpointIP = net.ParseIP(s)
if endpointIP == nil {
return nil, fmt.Errorf("environment variable WIREGUARD_ENDPOINT_IP: %w: %s",
ErrInvalidIP, s)
}
return endpointIP, nil
}

View File

@@ -1,107 +1,47 @@
package configuration
import (
"encoding/pem"
"fmt"
"strings"
"github.com/qdm12/gluetun/internal/constants"
"github.com/qdm12/golibs/params"
)
func (settings *Provider) cyberghostLines() (lines []string) {
lines = append(lines, lastIndent+"Server group: "+settings.ServerSelection.Group)
if len(settings.ServerSelection.Regions) > 0 {
lines = append(lines, lastIndent+"Regions: "+commaJoin(settings.ServerSelection.Regions))
}
if settings.ExtraConfigOptions.ClientKey != "" {
lines = append(lines, lastIndent+"Client key is set")
}
if settings.ExtraConfigOptions.ClientCertificate != "" {
lines = append(lines, lastIndent+"Client certificate is set")
}
return lines
}
func (settings *Provider) readCyberghost(r reader) (err error) {
settings.Name = constants.Cyberghost
settings.ServerSelection.Protocol, err = readProtocol(r.env)
if err != nil {
return err
}
servers := r.servers.GetCyberghost()
settings.ServerSelection.TargetIP, err = readTargetIP(r.env)
if err != nil {
return err
}
settings.ExtraConfigOptions.ClientKey, err = readCyberghostClientKey(r)
settings.ServerSelection.Countries, err = r.env.CSVInside("COUNTRY",
constants.CyberghostCountryChoices(servers),
params.RetroKeys([]string{"REGION"}, r.onRetroActive))
if err != nil {
return err
return fmt.Errorf("environment variable COUNTRY: %w", err)
}
settings.ExtraConfigOptions.ClientCertificate, err = readCyberghostClientCertificate(r)
settings.ServerSelection.Hostnames, err = r.env.CSVInside("SERVER_HOSTNAME",
constants.CyberghostHostnameChoices(servers))
if err != nil {
return err
return fmt.Errorf("environment variable SERVER_HOSTNAME: %w", err)
}
settings.ServerSelection.Group, err = r.env.Inside("CYBERGHOST_GROUP",
constants.CyberghostGroupChoices(), params.Default("Premium UDP Europe"))
return settings.ServerSelection.OpenVPN.readProtocolAndPort(r)
}
func (settings *OpenVPN) readCyberghost(r reader) (err error) {
settings.ClientKey, err = readClientKey(r)
if err != nil {
return err
return fmt.Errorf("%w: %s", errClientKey, err)
}
settings.ServerSelection.Regions, err = r.env.CSVInside("REGION", constants.CyberghostRegionChoices())
settings.ClientCrt, err = readClientCertificate(r)
if err != nil {
return err
return fmt.Errorf("%w: %s", errClientCert, err)
}
return nil
}
func readCyberghostClientKey(r reader) (clientKey string, err error) {
b, err := r.getFromFileOrSecretFile("OPENVPN_CLIENTKEY", constants.ClientKey)
if err != nil {
return "", err
}
return extractClientKey(b)
}
func extractClientKey(b []byte) (key string, err error) {
pemBlock, _ := pem.Decode(b)
if pemBlock == nil {
return "", fmt.Errorf("cannot decode PEM block from client key")
}
parsedBytes := pem.EncodeToMemory(pemBlock)
s := string(parsedBytes)
s = strings.ReplaceAll(s, "\n", "")
s = strings.TrimPrefix(s, "-----BEGIN PRIVATE KEY-----")
s = strings.TrimSuffix(s, "-----END PRIVATE KEY-----")
return s, nil
}
func readCyberghostClientCertificate(r reader) (clientCertificate string, err error) {
b, err := r.getFromFileOrSecretFile("OPENVPN_CLIENTCRT", constants.ClientCertificate)
if err != nil {
return "", err
}
return extractClientCertificate(b)
}
func extractClientCertificate(b []byte) (certificate string, err error) {
pemBlock, _ := pem.Decode(b)
if pemBlock == nil {
return "", fmt.Errorf("cannot decode PEM block from client certificate")
}
parsedBytes := pem.EncodeToMemory(pemBlock)
s := string(parsedBytes)
s = strings.ReplaceAll(s, "\n", "")
s = strings.TrimPrefix(s, "-----BEGIN CERTIFICATE-----")
s = strings.TrimSuffix(s, "-----END CERTIFICATE-----")
return s, nil
}

View File

@@ -1,175 +0,0 @@
package configuration
import (
"fmt"
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func Test_extractClientKey(t *testing.T) {
t.Parallel()
const validPEM = `
-----BEGIN PRIVATE KEY-----
MIIJQwIBADANBgkqhkiG9w0BAQEFAASCCS0wggkpAgEAAoICAQCrQDrezCptkWxX
ywm3KdXtvti+rPF3vfzOmXRKiKXDMpMxzoiaD5Wspirxxjr4C+B14xTwZjJZfxJL
2HpPdOeBmA5tmAoGUESspnzxR/N1T4Uggx0vlAzFo0UZ0sutV6CJK19Kk38REwlG
AB8gl6JYeSUuu6qREjrLVwFRH72acvC/p4jBki/MjAfEaeHc0yDJT9jpjpchw+Hx
Ymy+1BnfNTAfGDdTVx9qWb+ByQ7xfvzuD9AOeqiWApDzZIuDDsaWn2orv+syoJVo
rV52/F+75zks6+fzQ+0sotBlRyvsZKGi80F89RIHwG+5LNSuRDWnVvrwv1oc6V2/
lMidwT7yb0kXt0IRW6JzbaHyB2LkPazBlr6IPNupk83x9t2Buw0HI2SQKHMKOChU
i2/906yLUOo3QpAi3Wk1c/Xu9DvGR/pOA15WCakiAfG3Fq6hUxNncmpOMeOLF/ez
L19jZ3KA4E2Te4+GA0NYlXgkDbsIILWapHwqHXcDukynHisr7RawjrvXoLyasm4L
O66aNXK9wtipSMDA7tdlQP6Xe9bHflDHxwreiuEGxnrsvLU7LHBWdD7UT2/u8zdr
pimqi4L7W5p5aOBMn8jSVCO9+4CAxiLlc2qx5vb4/EPMsdQfacYP7vY9iVh/qPi3
bcUVGUlg8wAJDrTksxU1K3FVR7BEPwIDAQABAoICAAhyrbTJ+5nWH7MhCASqIqyM
yqJ1Y6AVlkAW397BaPP9Lbe6SZDYDfkrZVjx/3y3EUafgivtzrQNibiGIFqFGNqS
xrtvUadIFGsz91vrwb3aw2V8MldjhVHGoSUJ+hQ+C2RY6GWEazNLbhyu6tovwMl+
iHAKv/pSHOZlD2KSH0dcPjYmLJ/n90Wu7r8ovgSnwalMsBWtfBUlVaMTyOuNCQ2y
0QHnrusElD8p2EGtynftXMrdqtTcBi8IR2BKaHt5oiBSEum/mPmxZE16p/tUreBW
IsLtjE663htimMc2QJtzx2mDeIqSiGYrfxdyd7d1E/SCXPS9a9ObS42k6FSn8NPu
K5kN6fPV5EDM2CqKEt9QZPlyrjZIuffOZtJj0xPuTwhRle4SOtyjn2c/vsv9Fkrp
B6B1v7T4+SSOIedOYkL+FP/IexMNG/ZTB5Y2hrZ03JW9RGpFAa4//qGG2qUCR3hE
rVS6v58qO/3+TCFSn/TI8AfcTcJbes3yTbVyLH6NAjATfYqJDJJFf+PG0qKc8q1N
KvXmT+x4JiBBM32cOg11GPflxIZSKi9He50hnPGnC042N06ba/pkUPG49XwE37hn
kIGmcFGcDIMDTEZnPBogPFqGpepYdwGWxbadRiUoX2wgurHRRmA0YM32MjVky9C1
12Q/Jy9PIk/qdjYdWfAhAoIBAQDcvxfUx7MKMFgYYm4E51X+7B9QoxdhVaxcoVgK
VwfvedsLi0Bk1B1JVSXqnNfyDZbpxFz2v5Xd/dSit2rjnfBm+DoJYN9ZNnrbIH+s
qsO1DuHZvMZlRDJbpt7PpVH/pcf7rEWRY+avkMMsiGwI/ruDs17eu7jULeG7N4jb
kh1mdvF7K56O6Xe8jGJu5qaOPRWOkABK1cEOjQ5hB1iAwO/ua5hehP87SvbYzIhz
nQTE3AqTWgWbIyC4R85U7tS9hsXnSQ/ICM9pWcyN0Y667LwR2tX0QKl5M/YoM0sG
mw+VQED8O2R45jTzSAcox77dRg55Pp3Xexsp2iVvaTIeAaevAoIBAQDGmZS1gFO4
TEgQXHdmibLizDUHLuw662GC+3Hilx+nZBZtWOc6t8yquUyggSKQmBDiKAf0ipMe
xFao+5I3StJJ2P4Vel95Vcu8KgqCF736Q1iNgDHuW8ho8e0y+YE371x5co3POGC0
SfbcnRTXQx2+wWXzZDh+KtnaDUyDN12/qCIUyAuSVLwEM28ZFM3qadG1aUdCB5oe
o8jfgsg6YSukm4uE/tuI3/wAI7FkaCqvt/zkLauRff5FcNa7os4EKtNnGfebxscP
yFYpMsW9VI0rfmYz02gho33lnofs4o8x/gxh6t5zfVbsZ7vUiSDJBahWboG9aE99
OY2TKb6ibsBxAoIBAQChDBVR2oPnqg+Lcrw7fZ8Cxbeu992F2KBQUDHQEWCruSYy
zNwk84+OQb3Q5a6yXHG+iNEd//ZRp+8q60/jUgXiybRlxTQNfS6ykYo0Kb1wabQi
S5Qeq1tl/F9P9JfXQFafaTaz9MOHUMDjy3+uLFIXqpRLQX995R9rm/+P2ZDzgVF5
///E2dXOTElACax3117TzIE6F6qqeASGi3ppLNmfAwZ95t/inTVsRARE/MhO6w4Y
JLQ0U7N6XoDM/BVfVGUr8OS/lpXjkW0oBjvwaehnylUPxuEdmOg8ufdBkX0T8XW3
z4jkn2cAGouGl/vKqWLD2AgF/j16Ejn/hyrWM3TnAoIBAA6lSssrwIDJ11KljwSX
yQJirtJtymv56cIACwD7xhDRF7pOoRa6cTRx383CWCszm6Mh8pw9D+Zn8kAZ9Ulw
khtyDiLFWH8ZLaIds5Kub4siJkihGI2MZTYgCS8GKVpXo4ktQnnynWcOQU85okzR
nULw/jS5wlTDkjc7XdYbYiV9H65KplfPOeJRbLL7zsensBhhwCiFaP8zct/QxDVR
7yb/dYWESepJIktcVnuiFuvIdLTbDVj4YqT6UkuaEPlLszVaO+FYAlwOmRQGs4Bn
2NVJR/4wa/B3HxSs4Tc96fN02bLq4CbCKoPajoZ46lsIuMZO9fBi3eHNObyNiopu
AnECggEBAJiJ0tK/PGh+Q9uv57Z4QcmbawoxMQW1qK/rLYwacYsSpzo8VhbZf+Jh
8biMg9AIQsLWnqmB3gmndePArGXkSxnilRozNLaeclTZy7rh00BctTEfgee4Kxdi
JKkJlVK0CE8I6txVRqkoOMyxsk1kRZ4l2yW2nxzyWlJKwvD75x2PQ6xWvpLAggyn
q00I3MzNIgR123jytN1NyC7l+mnGoC23ToXM7B3/PQjGYTq3jawKomrX1cmwzKBT
+pzjtJSWvMeUEZQS1PpOhxpPBRHagdKXt+ug2DqDtU6rfpDGtTBh5QNkg5SA7lxZ
zZjrL52saevO25cigVl+hxcnY8DTpbk=
-----END PRIVATE KEY-----
`
const validKeyString = "MIIJQwIBADANBgkqhkiG9w0BAQEFAASCCS0wggkpAgEAAoICAQCrQDrezCptkWxXywm3KdXtvti+rPF3vfzOmXRKiKXDMpMxzoiaD5Wspirxxjr4C+B14xTwZjJZfxJL2HpPdOeBmA5tmAoGUESspnzxR/N1T4Uggx0vlAzFo0UZ0sutV6CJK19Kk38REwlGAB8gl6JYeSUuu6qREjrLVwFRH72acvC/p4jBki/MjAfEaeHc0yDJT9jpjpchw+HxYmy+1BnfNTAfGDdTVx9qWb+ByQ7xfvzuD9AOeqiWApDzZIuDDsaWn2orv+syoJVorV52/F+75zks6+fzQ+0sotBlRyvsZKGi80F89RIHwG+5LNSuRDWnVvrwv1oc6V2/lMidwT7yb0kXt0IRW6JzbaHyB2LkPazBlr6IPNupk83x9t2Buw0HI2SQKHMKOChUi2/906yLUOo3QpAi3Wk1c/Xu9DvGR/pOA15WCakiAfG3Fq6hUxNncmpOMeOLF/ezL19jZ3KA4E2Te4+GA0NYlXgkDbsIILWapHwqHXcDukynHisr7RawjrvXoLyasm4LO66aNXK9wtipSMDA7tdlQP6Xe9bHflDHxwreiuEGxnrsvLU7LHBWdD7UT2/u8zdrpimqi4L7W5p5aOBMn8jSVCO9+4CAxiLlc2qx5vb4/EPMsdQfacYP7vY9iVh/qPi3bcUVGUlg8wAJDrTksxU1K3FVR7BEPwIDAQABAoICAAhyrbTJ+5nWH7MhCASqIqyMyqJ1Y6AVlkAW397BaPP9Lbe6SZDYDfkrZVjx/3y3EUafgivtzrQNibiGIFqFGNqSxrtvUadIFGsz91vrwb3aw2V8MldjhVHGoSUJ+hQ+C2RY6GWEazNLbhyu6tovwMl+iHAKv/pSHOZlD2KSH0dcPjYmLJ/n90Wu7r8ovgSnwalMsBWtfBUlVaMTyOuNCQ2y0QHnrusElD8p2EGtynftXMrdqtTcBi8IR2BKaHt5oiBSEum/mPmxZE16p/tUreBWIsLtjE663htimMc2QJtzx2mDeIqSiGYrfxdyd7d1E/SCXPS9a9ObS42k6FSn8NPuK5kN6fPV5EDM2CqKEt9QZPlyrjZIuffOZtJj0xPuTwhRle4SOtyjn2c/vsv9FkrpB6B1v7T4+SSOIedOYkL+FP/IexMNG/ZTB5Y2hrZ03JW9RGpFAa4//qGG2qUCR3hErVS6v58qO/3+TCFSn/TI8AfcTcJbes3yTbVyLH6NAjATfYqJDJJFf+PG0qKc8q1NKvXmT+x4JiBBM32cOg11GPflxIZSKi9He50hnPGnC042N06ba/pkUPG49XwE37hnkIGmcFGcDIMDTEZnPBogPFqGpepYdwGWxbadRiUoX2wgurHRRmA0YM32MjVky9C112Q/Jy9PIk/qdjYdWfAhAoIBAQDcvxfUx7MKMFgYYm4E51X+7B9QoxdhVaxcoVgKVwfvedsLi0Bk1B1JVSXqnNfyDZbpxFz2v5Xd/dSit2rjnfBm+DoJYN9ZNnrbIH+sqsO1DuHZvMZlRDJbpt7PpVH/pcf7rEWRY+avkMMsiGwI/ruDs17eu7jULeG7N4jbkh1mdvF7K56O6Xe8jGJu5qaOPRWOkABK1cEOjQ5hB1iAwO/ua5hehP87SvbYzIhznQTE3AqTWgWbIyC4R85U7tS9hsXnSQ/ICM9pWcyN0Y667LwR2tX0QKl5M/YoM0sGmw+VQED8O2R45jTzSAcox77dRg55Pp3Xexsp2iVvaTIeAaevAoIBAQDGmZS1gFO4TEgQXHdmibLizDUHLuw662GC+3Hilx+nZBZtWOc6t8yquUyggSKQmBDiKAf0ipMexFao+5I3StJJ2P4Vel95Vcu8KgqCF736Q1iNgDHuW8ho8e0y+YE371x5co3POGC0SfbcnRTXQx2+wWXzZDh+KtnaDUyDN12/qCIUyAuSVLwEM28ZFM3qadG1aUdCB5oeo8jfgsg6YSukm4uE/tuI3/wAI7FkaCqvt/zkLauRff5FcNa7os4EKtNnGfebxscPyFYpMsW9VI0rfmYz02gho33lnofs4o8x/gxh6t5zfVbsZ7vUiSDJBahWboG9aE99OY2TKb6ibsBxAoIBAQChDBVR2oPnqg+Lcrw7fZ8Cxbeu992F2KBQUDHQEWCruSYyzNwk84+OQb3Q5a6yXHG+iNEd//ZRp+8q60/jUgXiybRlxTQNfS6ykYo0Kb1wabQiS5Qeq1tl/F9P9JfXQFafaTaz9MOHUMDjy3+uLFIXqpRLQX995R9rm/+P2ZDzgVF5///E2dXOTElACax3117TzIE6F6qqeASGi3ppLNmfAwZ95t/inTVsRARE/MhO6w4YJLQ0U7N6XoDM/BVfVGUr8OS/lpXjkW0oBjvwaehnylUPxuEdmOg8ufdBkX0T8XW3z4jkn2cAGouGl/vKqWLD2AgF/j16Ejn/hyrWM3TnAoIBAA6lSssrwIDJ11KljwSXyQJirtJtymv56cIACwD7xhDRF7pOoRa6cTRx383CWCszm6Mh8pw9D+Zn8kAZ9UlwkhtyDiLFWH8ZLaIds5Kub4siJkihGI2MZTYgCS8GKVpXo4ktQnnynWcOQU85okzRnULw/jS5wlTDkjc7XdYbYiV9H65KplfPOeJRbLL7zsensBhhwCiFaP8zct/QxDVR7yb/dYWESepJIktcVnuiFuvIdLTbDVj4YqT6UkuaEPlLszVaO+FYAlwOmRQGs4Bn2NVJR/4wa/B3HxSs4Tc96fN02bLq4CbCKoPajoZ46lsIuMZO9fBi3eHNObyNiopuAnECggEBAJiJ0tK/PGh+Q9uv57Z4QcmbawoxMQW1qK/rLYwacYsSpzo8VhbZf+Jh8biMg9AIQsLWnqmB3gmndePArGXkSxnilRozNLaeclTZy7rh00BctTEfgee4KxdiJKkJlVK0CE8I6txVRqkoOMyxsk1kRZ4l2yW2nxzyWlJKwvD75x2PQ6xWvpLAggynq00I3MzNIgR123jytN1NyC7l+mnGoC23ToXM7B3/PQjGYTq3jawKomrX1cmwzKBT+pzjtJSWvMeUEZQS1PpOhxpPBRHagdKXt+ug2DqDtU6rfpDGtTBh5QNkg5SA7lxZzZjrL52saevO25cigVl+hxcnY8DTpbk=" //nolint:lll
testCases := map[string]struct {
b []byte
key string
err error
}{
"no input": {
err: fmt.Errorf("cannot decode PEM block from client key"),
},
"bad input": {
b: []byte{1, 2, 3},
err: fmt.Errorf("cannot decode PEM block from client key"),
},
"valid key": {
b: []byte(validPEM),
key: validKeyString,
},
}
for name, testCase := range testCases {
testCase := testCase
t.Run(name, func(t *testing.T) {
t.Parallel()
key, err := extractClientKey(testCase.b)
if testCase.err != nil {
require.Error(t, err)
assert.Equal(t, testCase.err.Error(), err.Error())
} else {
assert.NoError(t, err)
}
assert.Equal(t, testCase.key, key)
})
}
}
func Test_extractClientCertificate(t *testing.T) {
t.Parallel()
const validPEM = `
-----BEGIN CERTIFICATE-----
MIIGrDCCBJSgAwIBAgIEAdTnfTANBgkqhkiG9w0BAQsFADB7MQswCQYDVQQGEwJS
TzESMBAGA1UEBxMJQnVjaGFyZXN0MRgwFgYDVQQKEw9DeWJlckdob3N0IFMuQS4x
GzAZBgNVBAMTEkN5YmVyR2hvc3QgUm9vdCBDQTEhMB8GCSqGSIb3DQEJARYSaW5m
b0BjeWJlcmdob3N0LnJvMB4XDTIwMDcwNDE1MjkzNloXDTMwMDcwMjE1MjkzNlow
fTELMAkGA1UEBhMCUk8xEjAQBgNVBAcMCUJ1Y2hhcmVzdDEYMBYGA1UECgwPQ3li
ZXJHaG9zdCBTLkEuMR0wGwYDVQQDDBRjLmoua2xhdmVyQGdtYWlsLmNvbTEhMB8G
CSqGSIb3DQEJARYSaW5mb0BjeWJlcmdob3N0LnJvMIICIjANBgkqhkiG9w0BAQEF
AAOCAg8AMIICCgKCAgEAobp2NlGUHMNBe08YEOnVG3QJjF3ZaXbRhE/II9rmtgJT
NZtDohGChvFlNRsExKzVrKxHCeuJkVffwzQ6fYk4/M1RdYLJUh0UVw3e4WdApw8E
7TJZxDYm4SHQNXUvt1Rt5TjslcXxIpDZgrMSc/kHROYEL9tdgdzPZErUJehXyJPh
EzIrzmAJh501x7WwKPz9ctSVlItyavqEWFF2vyUa6X9DYmD9mQTz5c+VXNO5DkXm
PFBIaEVDnvFtcjGJ56yEvFnWVukL+OUX7ezowrIOFOcp9udjgpeiHq+XvsQ6ER0D
Jt25MiEId3NjkxtZ8BitDftTcLN/kt81hWKT7adMVc3kpIZ80cxrwRCttMd7sHAz
KI9u7pMxv10eUOsIEY87ewBe3l6KvEnjA+9uIjim6gLLebDIaEH50Ee9PzNJ8fqQ
2u54Ab4bt00/H1sUnJ6Ss/+WsQDOK1BsPRKKcnHZntOlHrs2Tu5+txKNU2cOapI8
SjVULUNKrRXASbpfWnLUfri/HO742bJb/TjkOJcOxta3hTPFAhaRWBusVlB41XVH
euH5DAhugYXeSNK6/6Ul8YvKUNH/7QbxuGIGXfth19Xl4QLI1umyEjZopSlt3tOi
O2V1soVNSQCCfxXVoCTMESMLjhkjWdmBDhdy2GTW7S4YoJfqVKiS18rYkN7I4ZMC
AwEAAaOCATQwggEwMAkGA1UdEwQCMAAwCwYDVR0PBAQDAgWgMDQGCWCGSAGG+EIB
DQQnFiVDeWJlckdob3N0IEdlbmVyYXRlZCBVc2VyIENlcnRpZmljYXRlMBEGCWCG
SAGG+EIBAQQEAwIHgDAdBgNVHQ4EFgQULwUtU5s6pL2NN9gPeEnKX0dhwiswga0G
A1UdIwSBpTCBooAU6tdK1g/He5qzjeAoM5eHt4in9iWhf6R9MHsxCzAJBgNVBAYT
AlJPMRIwEAYDVQQHEwlCdWNoYXJlc3QxGDAWBgNVBAoTD0N5YmVyR2hvc3QgUy5B
LjEbMBkGA1UEAxMSQ3liZXJHaG9zdCBSb290IENBMSEwHwYJKoZIhvcNAQkBFhJp
bmZvQGN5YmVyZ2hvc3Qucm+CCQCcVButZsQ0uzANBgkqhkiG9w0BAQsFAAOCAgEA
ystGIMYhQWaEdTqlnLCytrr8657t+PuidZMNNIaPB3wN2Fi2xKf14DTg03mqxjmP
Pb+f+PVNIOV5PdWD4jcQwOP1GEboGV0DFzlRGeAtDcvKwdee4oASJbZq1CETqDao
hQTxKEWC+UBk2F36nOaEI6Sab+Mb4cR9//PAwvzOqrXuGF5NuIOX7eFtCMQSgQq6
lRRqTQjekm0Dxigx4JA92Jo2qZRwCJ0T3IXBJGL831HCFJbDWv8PV3lsfFb/i2+v
r54uywFQVWWp18dYi97gipfuQ4zRg2Ldx5aXSmnhhKpg5ioZvtk043QofF12YORh
obElqavRbvvhZvlCouvcuoq9QKi7IPe5SJZkZ1X7ezMesCwBzwFpt6vRUAcslsNF
bcYS1iSENlY/PTcDqBhbKuc9yAhq+/aUgaY/8VF5RWVzSRZufbf3BPwOkE4K0Uyb
aobO/YX0JOkCacAD+4tdR6YSXNIMMRAOCBQvxbxFXaHzhwhzBAjdsC56FrJKwXvQ
rRLU3tF4P0zFMeNTay8uTtUXugDK7EnklLESuYdpUJ8bUMlAUhJBi6UFI9/icMud
xXvLRvhnBW9EtKib5JnVFUovcEUt+3EJbyst05nkL4YPjQS4TC9DHdo5SyRAy1Tp
iOCYTbretAFZRhh6ycUN5hBeN8GMQxiMreMtDV4PEIQ=
-----END CERTIFICATE-----
`
const validCertificateString = "MIIGrDCCBJSgAwIBAgIEAdTnfTANBgkqhkiG9w0BAQsFADB7MQswCQYDVQQGEwJSTzESMBAGA1UEBxMJQnVjaGFyZXN0MRgwFgYDVQQKEw9DeWJlckdob3N0IFMuQS4xGzAZBgNVBAMTEkN5YmVyR2hvc3QgUm9vdCBDQTEhMB8GCSqGSIb3DQEJARYSaW5mb0BjeWJlcmdob3N0LnJvMB4XDTIwMDcwNDE1MjkzNloXDTMwMDcwMjE1MjkzNlowfTELMAkGA1UEBhMCUk8xEjAQBgNVBAcMCUJ1Y2hhcmVzdDEYMBYGA1UECgwPQ3liZXJHaG9zdCBTLkEuMR0wGwYDVQQDDBRjLmoua2xhdmVyQGdtYWlsLmNvbTEhMB8GCSqGSIb3DQEJARYSaW5mb0BjeWJlcmdob3N0LnJvMIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAobp2NlGUHMNBe08YEOnVG3QJjF3ZaXbRhE/II9rmtgJTNZtDohGChvFlNRsExKzVrKxHCeuJkVffwzQ6fYk4/M1RdYLJUh0UVw3e4WdApw8E7TJZxDYm4SHQNXUvt1Rt5TjslcXxIpDZgrMSc/kHROYEL9tdgdzPZErUJehXyJPhEzIrzmAJh501x7WwKPz9ctSVlItyavqEWFF2vyUa6X9DYmD9mQTz5c+VXNO5DkXmPFBIaEVDnvFtcjGJ56yEvFnWVukL+OUX7ezowrIOFOcp9udjgpeiHq+XvsQ6ER0DJt25MiEId3NjkxtZ8BitDftTcLN/kt81hWKT7adMVc3kpIZ80cxrwRCttMd7sHAzKI9u7pMxv10eUOsIEY87ewBe3l6KvEnjA+9uIjim6gLLebDIaEH50Ee9PzNJ8fqQ2u54Ab4bt00/H1sUnJ6Ss/+WsQDOK1BsPRKKcnHZntOlHrs2Tu5+txKNU2cOapI8SjVULUNKrRXASbpfWnLUfri/HO742bJb/TjkOJcOxta3hTPFAhaRWBusVlB41XVHeuH5DAhugYXeSNK6/6Ul8YvKUNH/7QbxuGIGXfth19Xl4QLI1umyEjZopSlt3tOiO2V1soVNSQCCfxXVoCTMESMLjhkjWdmBDhdy2GTW7S4YoJfqVKiS18rYkN7I4ZMCAwEAAaOCATQwggEwMAkGA1UdEwQCMAAwCwYDVR0PBAQDAgWgMDQGCWCGSAGG+EIBDQQnFiVDeWJlckdob3N0IEdlbmVyYXRlZCBVc2VyIENlcnRpZmljYXRlMBEGCWCGSAGG+EIBAQQEAwIHgDAdBgNVHQ4EFgQULwUtU5s6pL2NN9gPeEnKX0dhwiswga0GA1UdIwSBpTCBooAU6tdK1g/He5qzjeAoM5eHt4in9iWhf6R9MHsxCzAJBgNVBAYTAlJPMRIwEAYDVQQHEwlCdWNoYXJlc3QxGDAWBgNVBAoTD0N5YmVyR2hvc3QgUy5BLjEbMBkGA1UEAxMSQ3liZXJHaG9zdCBSb290IENBMSEwHwYJKoZIhvcNAQkBFhJpbmZvQGN5YmVyZ2hvc3Qucm+CCQCcVButZsQ0uzANBgkqhkiG9w0BAQsFAAOCAgEAystGIMYhQWaEdTqlnLCytrr8657t+PuidZMNNIaPB3wN2Fi2xKf14DTg03mqxjmPPb+f+PVNIOV5PdWD4jcQwOP1GEboGV0DFzlRGeAtDcvKwdee4oASJbZq1CETqDaohQTxKEWC+UBk2F36nOaEI6Sab+Mb4cR9//PAwvzOqrXuGF5NuIOX7eFtCMQSgQq6lRRqTQjekm0Dxigx4JA92Jo2qZRwCJ0T3IXBJGL831HCFJbDWv8PV3lsfFb/i2+vr54uywFQVWWp18dYi97gipfuQ4zRg2Ldx5aXSmnhhKpg5ioZvtk043QofF12YORhobElqavRbvvhZvlCouvcuoq9QKi7IPe5SJZkZ1X7ezMesCwBzwFpt6vRUAcslsNFbcYS1iSENlY/PTcDqBhbKuc9yAhq+/aUgaY/8VF5RWVzSRZufbf3BPwOkE4K0UybaobO/YX0JOkCacAD+4tdR6YSXNIMMRAOCBQvxbxFXaHzhwhzBAjdsC56FrJKwXvQrRLU3tF4P0zFMeNTay8uTtUXugDK7EnklLESuYdpUJ8bUMlAUhJBi6UFI9/icMudxXvLRvhnBW9EtKib5JnVFUovcEUt+3EJbyst05nkL4YPjQS4TC9DHdo5SyRAy1TpiOCYTbretAFZRhh6ycUN5hBeN8GMQxiMreMtDV4PEIQ=" //nolint:lll
testCases := map[string]struct {
b []byte
certificate string
err error
}{
"no input": {
err: fmt.Errorf("cannot decode PEM block from client certificate"),
},
"bad input": {
b: []byte{1, 2, 3},
err: fmt.Errorf("cannot decode PEM block from client certificate"),
},
"valid key": {
b: []byte(validPEM),
certificate: validCertificateString,
},
}
for name, testCase := range testCases {
testCase := testCase
t.Run(name, func(t *testing.T) {
t.Parallel()
certificate, err := extractClientCertificate(testCase.b)
if testCase.err != nil {
require.Error(t, err)
assert.Equal(t, testCase.err.Error(), err.Error())
} else {
assert.NoError(t, err)
}
assert.Equal(t, testCase.certificate, certificate)
})
}
}

View File

@@ -7,21 +7,19 @@ import (
"strings"
"time"
unboundmodels "github.com/qdm12/dns/pkg/models"
unbound "github.com/qdm12/dns/pkg/unbound"
"github.com/qdm12/dns/pkg/blacklist"
"github.com/qdm12/dns/pkg/unbound"
"github.com/qdm12/golibs/params"
)
// DNS contains settings to configure Unbound for DNS over TLS operation.
type DNS struct { //nolint:maligned
Enabled bool
PlaintextAddress net.IP
KeepNameserver bool
BlockMalicious bool
BlockAds bool
BlockSurveillance bool
UpdatePeriod time.Duration
Unbound unboundmodels.Settings
Enabled bool
PlaintextAddress net.IP
KeepNameserver bool
UpdatePeriod time.Duration
Unbound unbound.Settings
BlacklistBuild blacklist.BuilderSettings
}
func (settings *DNS) String() string {
@@ -50,16 +48,9 @@ func (settings *DNS) lines() (lines []string) {
lines = append(lines, indent+indent+indent+line)
}
if settings.BlockMalicious {
lines = append(lines, indent+indent+lastIndent+"Block malicious: enabled")
}
if settings.BlockAds {
lines = append(lines, indent+indent+lastIndent+"Block ads: enabled")
}
if settings.BlockSurveillance {
lines = append(lines, indent+indent+lastIndent+"Block surveillance: enabled")
lines = append(lines, indent+indent+lastIndent+"Blacklist:")
for _, line := range settings.BlacklistBuild.Lines(indent, lastIndent) {
lines = append(lines, indent+indent+indent+line)
}
if settings.UpdatePeriod > 0 {
@@ -71,15 +62,13 @@ func (settings *DNS) lines() (lines []string) {
var (
ErrUnboundSettings = errors.New("failed getting Unbound settings")
ErrDNSProviderNoData = errors.New("DNS provider has no associated data")
ErrDNSProviderNoTLS = errors.New("DNS provider does not support DNS over TLS")
ErrDNSNoIPv6Support = errors.New("no DNS provider supports IPv6")
ErrBlacklistSettings = errors.New("failed getting DNS blacklist settings")
)
func (settings *DNS) read(r reader) (err error) {
settings.Enabled, err = r.env.OnOff("DOT", params.Default("on"))
if err != nil {
return err
return fmt.Errorf("environment variable DOT: %w", err)
}
// Plain DNS settings
@@ -88,50 +77,24 @@ func (settings *DNS) read(r reader) (err error) {
}
settings.KeepNameserver, err = r.env.OnOff("DNS_KEEP_NAMESERVER", params.Default("off"))
if err != nil {
return err
return fmt.Errorf("environment variable DNS_KEEP_NAMESERVER: %w", err)
}
// DNS over TLS external settings
settings.BlockMalicious, err = r.env.OnOff("BLOCK_MALICIOUS", params.Default("on"))
if err != nil {
return err
}
settings.BlockSurveillance, err = r.env.OnOff("BLOCK_SURVEILLANCE", params.Default("on"),
params.RetroKeys([]string{"BLOCK_NSA"}, r.onRetroActive))
if err != nil {
return err
}
settings.BlockAds, err = r.env.OnOff("BLOCK_ADS", params.Default("off"))
if err != nil {
return err
if err := settings.readBlacklistBuilding(r); err != nil {
return fmt.Errorf("%w: %s", ErrBlacklistSettings, err)
}
settings.UpdatePeriod, err = r.env.Duration("DNS_UPDATE_PERIOD", params.Default("24h"))
if err != nil {
return err
return fmt.Errorf("environment variable DNS_UPDATE_PERIOD: %w", err)
}
// Unbound settings
if err := settings.readUnbound(r); err != nil {
return fmt.Errorf("%w: %s", ErrUnboundSettings, err)
}
// Consistency check
IPv6Support := false
for _, provider := range settings.Unbound.Providers {
providerData, ok := unbound.GetProviderData(provider)
switch {
case !ok:
return fmt.Errorf("%w: %s", ErrDNSProviderNoData, provider)
case !providerData.SupportsTLS:
return fmt.Errorf("%w: %s", ErrDNSProviderNoTLS, provider)
case providerData.SupportsIPv6:
IPv6Support = true
}
}
if settings.Unbound.IPv6 && !IPv6Support {
return ErrDNSNoIPv6Support
}
return nil
}
@@ -139,10 +102,10 @@ var (
ErrDNSAddressNotAnIP = errors.New("DNS plaintext address is not an IP address")
)
func (settings *DNS) readDNSPlaintext(env params.Env) error {
func (settings *DNS) readDNSPlaintext(env params.Interface) error {
s, err := env.Get("DNS_PLAINTEXT_ADDRESS", params.Default("1.1.1.1"))
if err != nil {
return err
return fmt.Errorf("environment variable DNS_PLAINTEXT_ADDRESS: %w", err)
}
settings.PlaintextAddress = net.ParseIP(s)

View File

@@ -5,7 +5,9 @@ import (
"testing"
"time"
"github.com/qdm12/dns/pkg/models"
"github.com/qdm12/dns/pkg/blacklist"
"github.com/qdm12/dns/pkg/provider"
"github.com/qdm12/dns/pkg/unbound"
"github.com/stretchr/testify/assert"
)
@@ -28,13 +30,17 @@ func Test_DNS_Lines(t *testing.T) {
settings: DNS{
Enabled: true,
KeepNameserver: true,
Unbound: models.Settings{
Providers: []string{"cloudflare"},
Unbound: unbound.Settings{
Providers: []provider.Provider{
provider.Cloudflare(),
},
},
BlockMalicious: true,
BlockAds: true,
BlockSurveillance: true,
UpdatePeriod: time.Hour,
BlacklistBuild: blacklist.BuilderSettings{
BlockMalicious: true,
BlockAds: true,
BlockSurveillance: true,
},
UpdatePeriod: time.Hour,
},
lines: []string{
"|--DNS:",
@@ -42,7 +48,7 @@ func Test_DNS_Lines(t *testing.T) {
" |--DNS over TLS:",
" |--Unbound:",
" |--DNS over TLS providers:",
" |--cloudflare",
" |--Cloudflare",
" |--Listening port: 0",
" |--Access control:",
" |--Allowed:",
@@ -52,12 +58,9 @@ func Test_DNS_Lines(t *testing.T) {
" |--Verbosity level: 0/5",
" |--Verbosity details level: 0/4",
" |--Validation log level: 0/2",
" |--Blocked hostnames:",
" |--Blocked IP addresses:",
" |--Allowed hostnames:",
" |--Block malicious: enabled",
" |--Block ads: enabled",
" |--Block surveillance: enabled",
" |--Username: ",
" |--Blacklist:",
" |--Blocked categories: malicious, surveillance, ads",
" |--Update: every 1h0m0s",
},
},

View File

@@ -0,0 +1,87 @@
package configuration
import (
"errors"
"fmt"
"github.com/qdm12/golibs/params"
"inet.af/netaddr"
)
func (settings *DNS) readBlacklistBuilding(r reader) (err error) {
settings.BlacklistBuild.BlockMalicious, err = r.env.OnOff("BLOCK_MALICIOUS", params.Default("on"))
if err != nil {
return fmt.Errorf("environment variable BLOCK_MALICIOUS: %w", err)
}
settings.BlacklistBuild.BlockSurveillance, err = r.env.OnOff("BLOCK_SURVEILLANCE", params.Default("on"),
params.RetroKeys([]string{"BLOCK_NSA"}, r.onRetroActive))
if err != nil {
return fmt.Errorf("environment variable BLOCK_SURVEILLANCE (or BLOCK_NSA): %w", err)
}
settings.BlacklistBuild.BlockAds, err = r.env.OnOff("BLOCK_ADS", params.Default("off"))
if err != nil {
return fmt.Errorf("environment variable BLOCK_ADS: %w", err)
}
if err := settings.readPrivateAddresses(r.env); err != nil {
return err
}
return settings.readBlacklistUnblockedHostnames(r)
}
var (
ErrInvalidPrivateAddress = errors.New("private address is not a valid IP or CIDR range")
)
func (settings *DNS) readPrivateAddresses(env params.Interface) (err error) {
privateAddresses, err := env.CSV("DOT_PRIVATE_ADDRESS")
if err != nil {
return fmt.Errorf("environment variable DOT_PRIVATE_ADDRESS: %w", err)
} else if len(privateAddresses) == 0 {
return nil
}
ips := make([]netaddr.IP, 0, len(privateAddresses))
ipPrefixes := make([]netaddr.IPPrefix, 0, len(privateAddresses))
for _, address := range privateAddresses {
ip, err := netaddr.ParseIP(address)
if err == nil {
ips = append(ips, ip)
continue
}
ipPrefix, err := netaddr.ParseIPPrefix(address)
if err == nil {
ipPrefixes = append(ipPrefixes, ipPrefix)
continue
}
return fmt.Errorf("%w: %s", ErrInvalidPrivateAddress, address)
}
settings.BlacklistBuild.AddBlockedIPs = append(settings.BlacklistBuild.AddBlockedIPs, ips...)
settings.BlacklistBuild.AddBlockedIPPrefixes = append(settings.BlacklistBuild.AddBlockedIPPrefixes, ipPrefixes...)
return nil
}
func (settings *DNS) readBlacklistUnblockedHostnames(r reader) (err error) {
hostnames, err := r.env.CSV("UNBLOCK")
if err != nil {
return fmt.Errorf("environment variable UNBLOCK: %w", err)
} else if len(hostnames) == 0 {
return nil
}
for _, hostname := range hostnames {
if !r.regex.MatchHostname(hostname) {
return fmt.Errorf("%w: %s", ErrInvalidHostname, hostname)
}
}
settings.BlacklistBuild.AllowedHosts = append(settings.BlacklistBuild.AllowedHosts, hostnames...)
return nil
}

View File

@@ -0,0 +1,40 @@
package configuration
import (
"fmt"
"github.com/qdm12/gluetun/internal/constants"
)
func (settings *Provider) readExpressvpn(r reader) (err error) {
settings.Name = constants.Expressvpn
servers := r.servers.GetExpressvpn()
settings.ServerSelection.TargetIP, err = readTargetIP(r.env)
if err != nil {
return err
}
settings.ServerSelection.Hostnames, err = r.env.CSVInside("SERVER_HOSTNAME",
constants.ExpressvpnHostnameChoices(servers))
if err != nil {
return fmt.Errorf("environment variable SERVER_HOSTNAME: %w", err)
}
settings.ServerSelection.Countries, err = r.env.CSVInside("COUNTRY", constants.ExpressvpnCountriesChoices(servers))
if err != nil {
return fmt.Errorf("environment variable COUNTRY: %w", err)
}
settings.ServerSelection.Cities, err = r.env.CSVInside("CITY", constants.ExpressvpnCityChoices(servers))
if err != nil {
return fmt.Errorf("environment variable CITY: %w", err)
}
settings.ServerSelection.OpenVPN.TCP, err = readOpenVPNProtocol(r)
if err != nil {
return err
}
return nil
}

View File

@@ -0,0 +1,30 @@
package configuration
import (
"fmt"
"github.com/qdm12/gluetun/internal/constants"
)
func (settings *Provider) readFastestvpn(r reader) (err error) {
settings.Name = constants.Fastestvpn
servers := r.servers.GetFastestvpn()
settings.ServerSelection.TargetIP, err = readTargetIP(r.env)
if err != nil {
return err
}
settings.ServerSelection.Hostnames, err = r.env.CSVInside("SERVER_HOSTNAME",
constants.FastestvpnHostnameChoices(servers))
if err != nil {
return fmt.Errorf("environment variable SERVER_HOSTNAME: %w", err)
}
settings.ServerSelection.Countries, err = r.env.CSVInside("COUNTRY", constants.FastestvpnCountriesChoices(servers))
if err != nil {
return fmt.Errorf("environment variable COUNTRY: %w", err)
}
return settings.ServerSelection.OpenVPN.readProtocolOnly(r)
}

View File

@@ -1,6 +1,7 @@
package configuration
import (
"fmt"
"net"
"strings"
@@ -53,12 +54,12 @@ func (settings *Firewall) lines() (lines []string) {
func (settings *Firewall) read(r reader) (err error) {
settings.Enabled, err = r.env.OnOff("FIREWALL", params.Default("on"))
if err != nil {
return err
return fmt.Errorf("environment variable FIREWALL: %w", err)
}
settings.Debug, err = r.env.OnOff("FIREWALL_DEBUG", params.Default("off"))
if err != nil {
return err
return fmt.Errorf("environment variable FIREWALL_DEBUG: %w", err)
}
if err := settings.readVPNInputPorts(r.env); err != nil {
@@ -69,25 +70,30 @@ func (settings *Firewall) read(r reader) (err error) {
return err
}
if err := settings.readOutboundSubnets(r); err != nil {
return err
}
return settings.readOutboundSubnets(r)
}
func (settings *Firewall) readVPNInputPorts(env params.Interface) (err error) {
settings.VPNInputPorts, err = readCSVPorts(env, "FIREWALL_VPN_INPUT_PORTS")
if err != nil {
return fmt.Errorf("environment variable FIREWALL_VPN_INPUT_PORTS: %w", err)
}
return nil
}
func (settings *Firewall) readVPNInputPorts(env params.Env) (err error) {
settings.VPNInputPorts, err = readCSVPorts(env, "FIREWALL_VPN_INPUT_PORTS")
return err
}
func (settings *Firewall) readInputPorts(env params.Env) (err error) {
func (settings *Firewall) readInputPorts(env params.Interface) (err error) {
settings.InputPorts, err = readCSVPorts(env, "FIREWALL_INPUT_PORTS")
return err
if err != nil {
return fmt.Errorf("environment variable FIREWALL_INPUT_PORTS: %w", err)
}
return nil
}
func (settings *Firewall) readOutboundSubnets(r reader) (err error) {
retroOption := params.RetroKeys([]string{"EXTRA_SUBNETS"}, r.onRetroActive)
settings.OutboundSubnets, err = readCSVIPNets(r.env, "FIREWALL_OUTBOUND_SUBNETS", retroOption)
return err
if err != nil {
return fmt.Errorf("environment variable FIREWALL_OUTBOUND_SUBNETS: %w", err)
}
return nil
}

View File

@@ -0,0 +1,72 @@
package configuration
import (
"fmt"
"strings"
"github.com/qdm12/gluetun/internal/models"
"github.com/qdm12/golibs/params"
)
// Health contains settings for the healthcheck and health server.
type Health struct {
ServerAddress string
AddressToPing string
VPN HealthyWait
}
func (settings *Health) String() string {
return strings.Join(settings.lines(), "\n")
}
func (settings *Health) lines() (lines []string) {
lines = append(lines, lastIndent+"Health:")
lines = append(lines, indent+lastIndent+"Server address: "+settings.ServerAddress)
lines = append(lines, indent+lastIndent+"Address to ping: "+settings.AddressToPing)
lines = append(lines, indent+lastIndent+"VPN:")
for _, line := range settings.VPN.lines() {
lines = append(lines, indent+indent+line)
}
return lines
}
// Read is to be used for the healthcheck query mode.
func (settings *Health) Read(env params.Interface, warner Warner) (err error) {
reader := newReader(env, models.AllServers{}, warner) // note: no need for servers data
return settings.read(reader)
}
func (settings *Health) read(r reader) (err error) {
var warning string
settings.ServerAddress, warning, err = r.env.ListeningAddress(
"HEALTH_SERVER_ADDRESS", params.Default("127.0.0.1:9999"))
if warning != "" {
r.warner.Warn("environment variable HEALTH_SERVER_ADDRESS: " + warning)
}
if err != nil {
return fmt.Errorf("environment variable HEALTH_SERVER_ADDRESS: %w", err)
}
settings.AddressToPing, err = r.env.Get("HEALTH_ADDRESS_TO_PING", params.Default("github.com"))
if err != nil {
return fmt.Errorf("environment variable HEALTH_ADDRESS_TO_PING: %w", err)
}
retroKeyOption := params.RetroKeys([]string{"HEALTH_OPENVPN_DURATION_INITIAL"}, r.onRetroActive)
settings.VPN.Initial, err = r.env.Duration("HEALTH_VPN_DURATION_INITIAL", params.Default("6s"), retroKeyOption)
if err != nil {
return fmt.Errorf("environment variable HEALTH_VPN_DURATION_INITIAL: %w", err)
}
retroKeyOption = params.RetroKeys([]string{"HEALTH_OPENVPN_DURATION_ADDITION"}, r.onRetroActive)
settings.VPN.Addition, err = r.env.Duration("HEALTH_VPN_DURATION_ADDITION", params.Default("5s"), retroKeyOption)
if err != nil {
return fmt.Errorf("environment variable HEALTH_VPN_DURATION_ADDITION: %w", err)
}
return nil
}

View File

@@ -0,0 +1,272 @@
package configuration
import (
"errors"
"testing"
"time"
"github.com/golang/mock/gomock"
"github.com/qdm12/golibs/params/mock_params"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func Test_Health_String(t *testing.T) {
t.Parallel()
health := Health{
ServerAddress: "a",
AddressToPing: "b",
}
const expected = `|--Health:
|--Server address: a
|--Address to ping: b
|--VPN:
|--Initial duration: 0s`
s := health.String()
assert.Equal(t, expected, s)
}
func Test_Health_lines(t *testing.T) {
t.Parallel()
testCases := map[string]struct {
settings Health
lines []string
}{
"empty": {
lines: []string{
"|--Health:",
" |--Server address: ",
" |--Address to ping: ",
" |--VPN:",
" |--Initial duration: 0s",
},
},
"filled settings": {
settings: Health{
ServerAddress: "address:9999",
AddressToPing: "github.com",
VPN: HealthyWait{
Initial: time.Second,
Addition: time.Minute,
},
},
lines: []string{
"|--Health:",
" |--Server address: address:9999",
" |--Address to ping: github.com",
" |--VPN:",
" |--Initial duration: 1s",
" |--Addition duration: 1m0s",
},
},
}
for name, testCase := range testCases {
testCase := testCase
t.Run(name, func(t *testing.T) {
t.Parallel()
lines := testCase.settings.lines()
assert.Equal(t, testCase.lines, lines)
})
}
}
func Test_Health_read(t *testing.T) {
t.Parallel()
errDummy := errors.New("dummy")
type stringCall struct {
call bool
s string
err error
}
type stringCallWithWarning struct {
call bool
s string
warning string
err error
}
type durationCall struct {
call bool
duration time.Duration
err error
}
testCases := map[string]struct {
serverAddress stringCallWithWarning
addressToPing stringCall
vpnInitial durationCall
vpnAddition durationCall
expected Health
err error
}{
"success": {
serverAddress: stringCallWithWarning{
call: true,
s: "127.0.0.1:9999",
},
addressToPing: stringCall{
call: true,
s: "1.2.3.4",
},
vpnInitial: durationCall{
call: true,
duration: time.Second,
},
vpnAddition: durationCall{
call: true,
duration: time.Minute,
},
expected: Health{
ServerAddress: "127.0.0.1:9999",
AddressToPing: "1.2.3.4",
VPN: HealthyWait{
Initial: time.Second,
Addition: time.Minute,
},
},
},
"listening address error": {
serverAddress: stringCallWithWarning{
call: true,
s: "127.0.0.1:9999",
warning: "warning",
err: errDummy,
},
expected: Health{
ServerAddress: "127.0.0.1:9999",
},
err: errors.New("environment variable HEALTH_SERVER_ADDRESS: dummy"),
},
"address to ping error": {
serverAddress: stringCallWithWarning{
call: true,
},
addressToPing: stringCall{
call: true,
s: "address",
err: errDummy,
},
expected: Health{
AddressToPing: "address",
},
err: errors.New("environment variable HEALTH_ADDRESS_TO_PING: dummy"),
},
"initial error": {
serverAddress: stringCallWithWarning{
call: true,
},
addressToPing: stringCall{
call: true,
},
vpnInitial: durationCall{
call: true,
duration: time.Second,
err: errDummy,
},
expected: Health{
VPN: HealthyWait{
Initial: time.Second,
},
},
err: errors.New("environment variable HEALTH_VPN_DURATION_INITIAL: dummy"),
},
"addition error": {
serverAddress: stringCallWithWarning{
call: true,
},
addressToPing: stringCall{
call: true,
},
vpnInitial: durationCall{
call: true,
duration: time.Second,
},
vpnAddition: durationCall{
call: true,
duration: time.Minute,
err: errDummy,
},
expected: Health{
VPN: HealthyWait{
Initial: time.Second,
Addition: time.Minute,
},
},
err: errors.New("environment variable HEALTH_VPN_DURATION_ADDITION: dummy"),
},
}
for name, testCase := range testCases {
testCase := testCase
t.Run(name, func(t *testing.T) {
t.Parallel()
ctrl := gomock.NewController(t)
env := mock_params.NewMockInterface(ctrl)
warner := NewMockWarner(ctrl)
if testCase.serverAddress.call {
value := testCase.serverAddress.s
warning := testCase.serverAddress.warning
err := testCase.serverAddress.err
env.EXPECT().ListeningAddress("HEALTH_SERVER_ADDRESS", gomock.Any()).
Return(value, warning, err)
if warning != "" {
warner.EXPECT().Warn("environment variable HEALTH_SERVER_ADDRESS: " + warning)
}
}
if testCase.addressToPing.call {
value := testCase.addressToPing.s
err := testCase.addressToPing.err
env.EXPECT().Get("HEALTH_ADDRESS_TO_PING", gomock.Any()).
Return(value, err)
}
if testCase.vpnInitial.call {
value := testCase.vpnInitial.duration
err := testCase.vpnInitial.err
env.EXPECT().
Duration("HEALTH_VPN_DURATION_INITIAL", gomock.Any()).
Return(value, err)
}
if testCase.vpnAddition.call {
value := testCase.vpnAddition.duration
err := testCase.vpnAddition.err
env.EXPECT().
Duration("HEALTH_VPN_DURATION_ADDITION", gomock.Any()).
Return(value, err)
}
r := reader{
env: env,
warner: warner,
}
var health Health
err := health.read(r)
if testCase.err != nil {
require.Error(t, err)
assert.Equal(t, testCase.err.Error(), err.Error())
} else {
assert.NoError(t, err)
}
assert.Equal(t, testCase.expected, health)
})
}
}

View File

@@ -0,0 +1,55 @@
package configuration
import (
"testing"
"time"
"github.com/stretchr/testify/assert"
)
func Test_HealthyWait_String(t *testing.T) {
t.Parallel()
var healthyWait HealthyWait
const expected = "|--Initial duration: 0s"
s := healthyWait.String()
assert.Equal(t, expected, s)
}
func Test_HealthyWait_lines(t *testing.T) {
t.Parallel()
testCases := map[string]struct {
settings HealthyWait
lines []string
}{
"empty": {
lines: []string{
"|--Initial duration: 0s",
},
},
"filled settings": {
settings: HealthyWait{
Initial: time.Second,
Addition: time.Minute,
},
lines: []string{
"|--Initial duration: 1s",
"|--Addition duration: 1m0s",
},
},
}
for name, testCase := range testCases {
testCase := testCase
t.Run(name, func(t *testing.T) {
t.Parallel()
lines := testCase.settings.lines()
assert.Equal(t, testCase.lines, lines)
})
}
}

View File

@@ -0,0 +1,30 @@
package configuration
import (
"strings"
"time"
)
type HealthyWait struct {
// Initial is the initial duration to wait for the program
// to be healthy before taking action.
Initial time.Duration
// Addition is the duration to add to the Initial duration
// after Initial has expired to wait longer for the program
// to be healthy.
Addition time.Duration
}
func (settings *HealthyWait) String() string {
return strings.Join(settings.lines(), "\n")
}
func (settings *HealthyWait) lines() (lines []string) {
lines = append(lines, lastIndent+"Initial duration: "+settings.Initial.String())
if settings.Addition > 0 {
lines = append(lines, lastIndent+"Addition duration: "+settings.Addition.String())
}
return lines
}

View File

@@ -0,0 +1,40 @@
package configuration
import (
"fmt"
"github.com/qdm12/gluetun/internal/constants"
)
func (settings *Provider) readHideMyAss(r reader) (err error) {
settings.Name = constants.HideMyAss
servers := r.servers.GetHideMyAss()
settings.ServerSelection.TargetIP, err = readTargetIP(r.env)
if err != nil {
return err
}
settings.ServerSelection.Countries, err = r.env.CSVInside("COUNTRY", constants.HideMyAssCountryChoices(servers))
if err != nil {
return fmt.Errorf("environment variable COUNTRY: %w", err)
}
settings.ServerSelection.Regions, err = r.env.CSVInside("REGION", constants.HideMyAssCountryChoices(servers))
if err != nil {
return fmt.Errorf("environment variable REGION: %w", err)
}
settings.ServerSelection.Cities, err = r.env.CSVInside("CITY", constants.HideMyAssCityChoices(servers))
if err != nil {
return fmt.Errorf("environment variable CITY: %w", err)
}
settings.ServerSelection.Hostnames, err = r.env.CSVInside("SERVER_HOSTNAME",
constants.HideMyAssHostnameChoices(servers))
if err != nil {
return fmt.Errorf("environment variable SERVER_HOSTNAME: %w", err)
}
return settings.ServerSelection.OpenVPN.readProtocolAndPort(r)
}

View File

@@ -1,6 +1,7 @@
package configuration
import (
"fmt"
"strconv"
"strings"
@@ -49,24 +50,24 @@ func (settings *HTTPProxy) read(r reader) (err error) {
settings.Enabled, err = r.env.OnOff("HTTPPROXY", params.Default("off"),
params.RetroKeys([]string{"TINYPROXY", "PROXY"}, r.onRetroActive))
if err != nil {
return err
return fmt.Errorf("environment variable HTTPPROXY (or TINYPROXY, PROXY): %w", err)
}
settings.User, err = r.getFromEnvOrSecretFile("HTTPPROXY_USER", false, // compulsory
[]string{"TINYPROXY_USER", "PROXY_USER"})
if err != nil {
return err
return fmt.Errorf("environment variable HTTPPROXY_USER (or TINYPROXY_USER, PROXY_USER): %w", err)
}
settings.Password, err = r.getFromEnvOrSecretFile("HTTPPROXY_PASSWORD", false,
[]string{"TINYPROXY_PASSWORD", "PROXY_PASSWORD"})
if err != nil {
return err
return fmt.Errorf("environment variable HTTPPROXY_PASSWORD (or TINYPROXY_PASSWORD, PROXY_PASSWORD): %w", err)
}
settings.Stealth, err = r.env.OnOff("HTTPPROXY_STEALTH", params.Default("off"))
if err != nil {
return err
return fmt.Errorf("environment variable HTTPPROXY_STEALTH: %w", err)
}
if err := settings.readLog(r); err != nil {
@@ -77,10 +78,10 @@ func (settings *HTTPProxy) read(r reader) (err error) {
settings.Port, warning, err = r.env.ListeningPort("HTTPPROXY_PORT", params.Default("8888"),
params.RetroKeys([]string{"TINYPROXY_PORT", "PROXY_PORT"}, r.onRetroActive))
if len(warning) > 0 {
r.logger.Warn(warning)
r.warner.Warn(warning)
}
if err != nil {
return err
return fmt.Errorf("environment variable HTTPPROXY_PORT (or TINYPROXY_PORT, PROXY_PORT): %w", err)
}
return nil
@@ -90,7 +91,7 @@ func (settings *HTTPProxy) readLog(r reader) error {
s, err := r.env.Get("HTTPPROXY_LOG",
params.RetroKeys([]string{"PROXY_LOG_LEVEL", "TINYPROXY_LOG"}, r.onRetroActive))
if err != nil {
return err
return fmt.Errorf("environment variable HTTPPROXY_LOG (or TINYPROXY_LOG, PROXY_LOG_LEVEL): %w", err)
}
switch strings.ToLower(s) {

View File

@@ -0,0 +1,35 @@
package configuration
import (
"fmt"
"github.com/qdm12/gluetun/internal/constants"
)
func (settings *Provider) readIpvanish(r reader) (err error) {
settings.Name = constants.Ipvanish
servers := r.servers.GetIpvanish()
settings.ServerSelection.TargetIP, err = readTargetIP(r.env)
if err != nil {
return err
}
settings.ServerSelection.Countries, err = r.env.CSVInside("COUNTRY", constants.IpvanishCountryChoices(servers))
if err != nil {
return fmt.Errorf("environment variable COUNTRY: %w", err)
}
settings.ServerSelection.Cities, err = r.env.CSVInside("CITY", constants.IpvanishCityChoices(servers))
if err != nil {
return fmt.Errorf("environment variable CITY: %w", err)
}
settings.ServerSelection.Hostnames, err = r.env.CSVInside("SERVER_HOSTNAME",
constants.IpvanishHostnameChoices(servers))
if err != nil {
return fmt.Errorf("environment variable SERVER_HOSTNAME: %w", err)
}
return settings.ServerSelection.OpenVPN.readProtocolOnly(r)
}

View File

@@ -0,0 +1,170 @@
package configuration
import (
"errors"
"net"
"testing"
"github.com/golang/mock/gomock"
"github.com/qdm12/gluetun/internal/constants"
"github.com/qdm12/gluetun/internal/models"
"github.com/qdm12/golibs/params/mock_params"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func Test_Provider_readIpvanish(t *testing.T) {
t.Parallel()
var errDummy = errors.New("dummy test error")
type singleStringCall struct {
call bool
value string
err error
}
type sliceStringCall struct {
call bool
values []string
err error
}
testCases := map[string]struct {
targetIP singleStringCall
countries sliceStringCall
cities sliceStringCall
hostnames sliceStringCall
protocol singleStringCall
settings Provider
err error
}{
"target IP error": {
targetIP: singleStringCall{call: true, value: "something", err: errDummy},
settings: Provider{
Name: constants.Ipvanish,
},
err: errors.New("environment variable OPENVPN_TARGET_IP: dummy test error"),
},
"countries error": {
targetIP: singleStringCall{call: true},
countries: sliceStringCall{call: true, err: errDummy},
settings: Provider{
Name: constants.Ipvanish,
},
err: errors.New("environment variable COUNTRY: dummy test error"),
},
"cities error": {
targetIP: singleStringCall{call: true},
countries: sliceStringCall{call: true},
cities: sliceStringCall{call: true, err: errDummy},
settings: Provider{
Name: constants.Ipvanish,
},
err: errors.New("environment variable CITY: dummy test error"),
},
"hostnames error": {
targetIP: singleStringCall{call: true},
countries: sliceStringCall{call: true},
cities: sliceStringCall{call: true},
hostnames: sliceStringCall{call: true, err: errDummy},
settings: Provider{
Name: constants.Ipvanish,
},
err: errors.New("environment variable SERVER_HOSTNAME: dummy test error"),
},
"protocol error": {
targetIP: singleStringCall{call: true},
countries: sliceStringCall{call: true},
cities: sliceStringCall{call: true},
hostnames: sliceStringCall{call: true},
protocol: singleStringCall{call: true, err: errDummy},
settings: Provider{
Name: constants.Ipvanish,
},
err: errors.New("environment variable OPENVPN_PROTOCOL: dummy test error"),
},
"default settings": {
targetIP: singleStringCall{call: true},
countries: sliceStringCall{call: true},
cities: sliceStringCall{call: true},
hostnames: sliceStringCall{call: true},
protocol: singleStringCall{call: true},
settings: Provider{
Name: constants.Ipvanish,
},
},
"set settings": {
targetIP: singleStringCall{call: true, value: "1.2.3.4"},
countries: sliceStringCall{call: true, values: []string{"A", "B"}},
cities: sliceStringCall{call: true, values: []string{"C", "D"}},
hostnames: sliceStringCall{call: true, values: []string{"E", "F"}},
protocol: singleStringCall{call: true, value: constants.TCP},
settings: Provider{
Name: constants.Ipvanish,
ServerSelection: ServerSelection{
OpenVPN: OpenVPNSelection{
TCP: true,
},
TargetIP: net.IPv4(1, 2, 3, 4),
Countries: []string{"A", "B"},
Cities: []string{"C", "D"},
Hostnames: []string{"E", "F"},
},
},
},
}
for name, testCase := range testCases {
testCase := testCase
t.Run(name, func(t *testing.T) {
t.Parallel()
ctrl := gomock.NewController(t)
servers := []models.IpvanishServer{{Hostname: "a"}}
allServers := models.AllServers{
Ipvanish: models.IpvanishServers{
Servers: servers,
},
}
env := mock_params.NewMockInterface(ctrl)
if testCase.targetIP.call {
env.EXPECT().Get("OPENVPN_TARGET_IP").
Return(testCase.targetIP.value, testCase.targetIP.err)
}
if testCase.countries.call {
env.EXPECT().CSVInside("COUNTRY", constants.IpvanishCountryChoices(servers)).
Return(testCase.countries.values, testCase.countries.err)
}
if testCase.cities.call {
env.EXPECT().CSVInside("CITY", constants.IpvanishCityChoices(servers)).
Return(testCase.cities.values, testCase.cities.err)
}
if testCase.hostnames.call {
env.EXPECT().CSVInside("SERVER_HOSTNAME", constants.IpvanishHostnameChoices(servers)).
Return(testCase.hostnames.values, testCase.hostnames.err)
}
if testCase.protocol.call {
env.EXPECT().Inside("OPENVPN_PROTOCOL", []string{constants.TCP, constants.UDP}, gomock.Any()).
Return(testCase.protocol.value, testCase.protocol.err)
}
r := reader{
servers: allServers,
env: env,
}
var settings Provider
err := settings.readIpvanish(r)
if testCase.err != nil {
require.Error(t, err)
assert.Equal(t, testCase.err.Error(), err.Error())
} else {
assert.NoError(t, err)
}
assert.Equal(t, testCase.settings, settings)
})
}
}

View File

@@ -0,0 +1,73 @@
package configuration
import (
"fmt"
"github.com/qdm12/gluetun/internal/constants"
"github.com/qdm12/golibs/params"
)
func (settings *Provider) readIvpn(r reader) (err error) {
settings.Name = constants.Ivpn
servers := r.servers.GetIvpn()
settings.ServerSelection.TargetIP, err = readTargetIP(r.env)
if err != nil {
return err
}
settings.ServerSelection.Countries, err = r.env.CSVInside("COUNTRY", constants.IvpnCountryChoices(servers))
if err != nil {
return fmt.Errorf("environment variable COUNTRY: %w", err)
}
settings.ServerSelection.Cities, err = r.env.CSVInside("CITY", constants.IvpnCityChoices(servers))
if err != nil {
return fmt.Errorf("environment variable CITY: %w", err)
}
settings.ServerSelection.ISPs, err = r.env.CSVInside("ISP", constants.IvpnISPChoices(servers))
if err != nil {
return fmt.Errorf("environment variable ISP: %w", err)
}
settings.ServerSelection.Hostnames, err = r.env.CSVInside("SERVER_HOSTNAME", constants.IvpnHostnameChoices(servers))
if err != nil {
return fmt.Errorf("environment variable SERVER_HOSTNAME: %w", err)
}
err = settings.ServerSelection.OpenVPN.readIVPN(r)
if err != nil {
return err
}
return settings.ServerSelection.Wireguard.readIVPN(r.env)
}
func (settings *OpenVPNSelection) readIVPN(r reader) (err error) {
settings.TCP, err = readOpenVPNProtocol(r)
if err != nil {
return err
}
settings.CustomPort, err = readOpenVPNCustomPort(r, openvpnPortValidation{
tcp: settings.TCP,
allowedTCP: []uint16{80, 443, 1443},
allowedUDP: []uint16{53, 1194, 2049, 2050},
})
if err != nil {
return err
}
return nil
}
func (settings *WireguardSelection) readIVPN(env params.Interface) (err error) {
settings.EndpointPort, err = readWireguardCustomPort(env,
[]uint16{2049, 2050, 53, 30587, 41893, 48574, 58237})
if err != nil {
return err
}
return nil
}

View File

@@ -0,0 +1,274 @@
package configuration
import (
"errors"
"net"
"testing"
"github.com/golang/mock/gomock"
"github.com/qdm12/gluetun/internal/constants"
"github.com/qdm12/gluetun/internal/models"
"github.com/qdm12/golibs/params/mock_params"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func Test_Provider_readIvpn(t *testing.T) { //nolint:gocognit
t.Parallel()
var errDummy = errors.New("dummy test error")
type singleStringCall struct {
call bool
value string
err error
}
type portCall struct {
getCall bool
getValue string // "" or "0"
getErr error
portCall bool
portValue uint16
portErr error
}
type sliceStringCall struct {
call bool
values []string
err error
}
testCases := map[string]struct {
targetIP singleStringCall
countries sliceStringCall
cities sliceStringCall
isps sliceStringCall
hostnames sliceStringCall
protocol singleStringCall
ovpnPort portCall
ovpnOldPort portCall
wgPort portCall
wgOldPort portCall
settings Provider
err error
}{
"target IP error": {
targetIP: singleStringCall{call: true, value: "something", err: errDummy},
settings: Provider{
Name: constants.Ivpn,
},
err: errors.New("environment variable OPENVPN_TARGET_IP: dummy test error"),
},
"countries error": {
targetIP: singleStringCall{call: true},
countries: sliceStringCall{call: true, err: errDummy},
settings: Provider{
Name: constants.Ivpn,
},
err: errors.New("environment variable COUNTRY: dummy test error"),
},
"cities error": {
targetIP: singleStringCall{call: true},
countries: sliceStringCall{call: true},
cities: sliceStringCall{call: true, err: errDummy},
settings: Provider{
Name: constants.Ivpn,
},
err: errors.New("environment variable CITY: dummy test error"),
},
"isps error": {
targetIP: singleStringCall{call: true},
countries: sliceStringCall{call: true},
cities: sliceStringCall{call: true},
isps: sliceStringCall{call: true, err: errDummy},
settings: Provider{
Name: constants.Ivpn,
},
err: errors.New("environment variable ISP: dummy test error"),
},
"hostnames error": {
targetIP: singleStringCall{call: true},
countries: sliceStringCall{call: true},
cities: sliceStringCall{call: true},
isps: sliceStringCall{call: true},
hostnames: sliceStringCall{call: true, err: errDummy},
settings: Provider{
Name: constants.Ivpn,
},
err: errors.New("environment variable SERVER_HOSTNAME: dummy test error"),
},
"openvpn protocol error": {
targetIP: singleStringCall{call: true},
countries: sliceStringCall{call: true},
cities: sliceStringCall{call: true},
isps: sliceStringCall{call: true},
hostnames: sliceStringCall{call: true},
protocol: singleStringCall{call: true, err: errDummy},
settings: Provider{
Name: constants.Ivpn,
},
err: errors.New("environment variable OPENVPN_PROTOCOL: dummy test error"),
},
"openvpn custom port error": {
targetIP: singleStringCall{call: true},
countries: sliceStringCall{call: true},
cities: sliceStringCall{call: true},
isps: sliceStringCall{call: true},
hostnames: sliceStringCall{call: true},
protocol: singleStringCall{call: true},
ovpnPort: portCall{getCall: true, getErr: errDummy},
settings: Provider{
Name: constants.Ivpn,
},
err: errors.New("environment variable OPENVPN_PORT: dummy test error"),
},
"wireguard custom port error": {
targetIP: singleStringCall{call: true},
countries: sliceStringCall{call: true},
cities: sliceStringCall{call: true},
isps: sliceStringCall{call: true},
hostnames: sliceStringCall{call: true},
protocol: singleStringCall{call: true},
ovpnPort: portCall{getCall: true, getValue: "0"},
ovpnOldPort: portCall{getCall: true, getValue: "0"},
wgPort: portCall{getCall: true, getErr: errDummy},
settings: Provider{
Name: constants.Ivpn,
},
err: errors.New("environment variable WIREGUARD_ENDPOINT_PORT: dummy test error"),
},
"default settings": {
targetIP: singleStringCall{call: true},
countries: sliceStringCall{call: true},
cities: sliceStringCall{call: true},
isps: sliceStringCall{call: true},
hostnames: sliceStringCall{call: true},
protocol: singleStringCall{call: true},
ovpnPort: portCall{getCall: true, getValue: "0"},
ovpnOldPort: portCall{getCall: true, getValue: "0"},
wgPort: portCall{getCall: true, getValue: "0"},
wgOldPort: portCall{getCall: true, getValue: "0"},
settings: Provider{
Name: constants.Ivpn,
},
},
"set settings": {
targetIP: singleStringCall{call: true, value: "1.2.3.4"},
countries: sliceStringCall{call: true, values: []string{"A", "B"}},
cities: sliceStringCall{call: true, values: []string{"C", "D"}},
isps: sliceStringCall{call: true, values: []string{"ISP 1"}},
hostnames: sliceStringCall{call: true, values: []string{"E", "F"}},
protocol: singleStringCall{call: true, value: constants.TCP},
ovpnPort: portCall{getCall: true, portCall: true, portValue: 443},
wgPort: portCall{getCall: true, portCall: true, portValue: 2049},
settings: Provider{
Name: constants.Ivpn,
ServerSelection: ServerSelection{
OpenVPN: OpenVPNSelection{
TCP: true,
CustomPort: 443,
},
Wireguard: WireguardSelection{
EndpointPort: 2049,
},
TargetIP: net.IPv4(1, 2, 3, 4),
Countries: []string{"A", "B"},
Cities: []string{"C", "D"},
ISPs: []string{"ISP 1"},
Hostnames: []string{"E", "F"},
},
},
},
}
for name, testCase := range testCases {
testCase := testCase
t.Run(name, func(t *testing.T) {
t.Parallel()
ctrl := gomock.NewController(t)
env := mock_params.NewMockInterface(ctrl)
servers := []models.IvpnServer{{Hostname: "a"}}
allServers := models.AllServers{
Ivpn: models.IvpnServers{
Servers: servers,
},
}
if testCase.targetIP.call {
env.EXPECT().Get("OPENVPN_TARGET_IP").
Return(testCase.targetIP.value, testCase.targetIP.err)
}
if testCase.countries.call {
env.EXPECT().CSVInside("COUNTRY", constants.IvpnCountryChoices(servers)).
Return(testCase.countries.values, testCase.countries.err)
}
if testCase.cities.call {
env.EXPECT().CSVInside("CITY", constants.IvpnCityChoices(servers)).
Return(testCase.cities.values, testCase.cities.err)
}
if testCase.isps.call {
env.EXPECT().CSVInside("ISP", constants.IvpnISPChoices(servers)).
Return(testCase.isps.values, testCase.isps.err)
}
if testCase.hostnames.call {
env.EXPECT().CSVInside("SERVER_HOSTNAME", constants.IvpnHostnameChoices(servers)).
Return(testCase.hostnames.values, testCase.hostnames.err)
}
if testCase.protocol.call {
env.EXPECT().Inside("OPENVPN_PROTOCOL", []string{constants.TCP, constants.UDP}, gomock.Any()).
Return(testCase.protocol.value, testCase.protocol.err)
}
if testCase.ovpnPort.getCall {
env.EXPECT().Get("OPENVPN_PORT", gomock.Any()).
Return(testCase.ovpnPort.getValue, testCase.ovpnPort.getErr)
}
if testCase.ovpnPort.portCall {
env.EXPECT().Port("OPENVPN_PORT").
Return(testCase.ovpnPort.portValue, testCase.ovpnPort.portErr)
}
if testCase.ovpnOldPort.getCall {
env.EXPECT().Get("PORT", gomock.Any()).
Return(testCase.ovpnOldPort.getValue, testCase.ovpnOldPort.getErr)
}
if testCase.ovpnOldPort.portCall {
env.EXPECT().Port("PORT").
Return(testCase.ovpnOldPort.portValue, testCase.ovpnOldPort.portErr)
}
if testCase.wgPort.getCall {
env.EXPECT().Get("WIREGUARD_ENDPOINT_PORT", gomock.Any()).
Return(testCase.wgPort.getValue, testCase.wgPort.getErr)
}
if testCase.wgPort.portCall {
env.EXPECT().Port("WIREGUARD_ENDPOINT_PORT").
Return(testCase.wgPort.portValue, testCase.wgPort.portErr)
}
if testCase.wgOldPort.getCall {
env.EXPECT().Get("WIREGUARD_PORT", gomock.Any()).
Return(testCase.wgOldPort.getValue, testCase.wgOldPort.getErr)
}
if testCase.wgOldPort.portCall {
env.EXPECT().Port("WIREGUARD_PORT").
Return(testCase.wgOldPort.portValue, testCase.wgOldPort.portErr)
}
r := reader{
servers: allServers,
env: env,
}
var settings Provider
err := settings.readIvpn(r)
if testCase.err != nil {
require.Error(t, err)
assert.Equal(t, testCase.err.Error(), err.Error())
} else {
assert.NoError(t, err)
}
assert.Equal(t, testCase.settings, settings)
})
}
}

View File

@@ -0,0 +1,29 @@
package configuration
import (
"errors"
"github.com/qdm12/gluetun/internal/constants"
"github.com/qdm12/gluetun/internal/openvpn/parse"
)
var (
errClientCert = errors.New("cannot read client certificate")
errClientKey = errors.New("cannot read client key")
)
func readClientKey(r reader) (clientKey string, err error) {
b, err := r.getFromFileOrSecretFile("OPENVPN_CLIENTKEY", constants.ClientKey)
if err != nil {
return "", err
}
return parse.ExtractPrivateKey(b)
}
func readClientCertificate(r reader) (clientCertificate string, err error) {
b, err := r.getFromFileOrSecretFile("OPENVPN_CLIENTCRT", constants.ClientCertificate)
if err != nil {
return "", err
}
return parse.ExtractCert(b)
}

View File

@@ -0,0 +1,30 @@
package configuration
import (
"fmt"
"github.com/qdm12/golibs/logging"
"github.com/qdm12/golibs/params"
)
type Log struct {
Level logging.Level `json:"level"`
}
func (settings *Log) lines() (lines []string) {
lines = append(lines, lastIndent+"Log:")
lines = append(lines, indent+lastIndent+"Level: "+settings.Level.String())
return lines
}
func (settings *Log) read(env params.Interface) (err error) {
defaultLevel := logging.LevelInfo.String()
settings.Level, err = env.LogLevel("LOG_LEVEL", params.Default(defaultLevel))
if err != nil {
return fmt.Errorf("environment variable LOG_LEVEL: %w", err)
}
return nil
}

View File

@@ -1,76 +1,74 @@
package configuration
import (
"strconv"
"fmt"
"github.com/qdm12/gluetun/internal/constants"
"github.com/qdm12/golibs/params"
)
func (settings *Provider) mullvadLines() (lines []string) {
if len(settings.ServerSelection.Countries) > 0 {
lines = append(lines, lastIndent+"Countries: "+commaJoin(settings.ServerSelection.Countries))
}
if len(settings.ServerSelection.Cities) > 0 {
lines = append(lines, lastIndent+"Cities: "+commaJoin(settings.ServerSelection.Cities))
}
if len(settings.ServerSelection.ISPs) > 0 {
lines = append(lines, lastIndent+"ISPs: "+commaJoin(settings.ServerSelection.ISPs))
}
if settings.ServerSelection.CustomPort > 0 {
lines = append(lines, lastIndent+"Custom port: "+strconv.Itoa(int(settings.ServerSelection.CustomPort)))
}
if settings.ExtraConfigOptions.OpenVPNIPv6 {
lines = append(lines, lastIndent+"IPv6: enabled")
}
return lines
}
func (settings *Provider) readMullvad(r reader) (err error) {
settings.Name = constants.Mullvad
settings.ServerSelection.Protocol, err = readProtocol(r.env)
if err != nil {
return err
}
servers := r.servers.GetMullvad()
settings.ServerSelection.TargetIP, err = readTargetIP(r.env)
if err != nil {
return err
}
settings.ServerSelection.Countries, err = r.env.CSVInside("COUNTRY", constants.MullvadCountryChoices())
settings.ServerSelection.Countries, err = r.env.CSVInside("COUNTRY", constants.MullvadCountryChoices(servers))
if err != nil {
return err
return fmt.Errorf("environment variable COUNTRY: %w", err)
}
settings.ServerSelection.Cities, err = r.env.CSVInside("CITY", constants.MullvadCityChoices())
settings.ServerSelection.Cities, err = r.env.CSVInside("CITY", constants.MullvadCityChoices(servers))
if err != nil {
return err
return fmt.Errorf("environment variable CITY: %w", err)
}
settings.ServerSelection.ISPs, err = r.env.CSVInside("ISP", constants.MullvadISPChoices())
settings.ServerSelection.Hostnames, err = r.env.CSVInside("SERVER_HOSTNAME", constants.MullvadHostnameChoices(servers))
if err != nil {
return err
return fmt.Errorf("environment variable SERVER_HOSTNAME: %w", err)
}
settings.ServerSelection.CustomPort, err = readCustomPort(r.env, settings.ServerSelection.Protocol,
[]uint16{80, 443, 1401}, []uint16{53, 1194, 1195, 1196, 1197, 1300, 1301, 1302, 1303, 1400})
settings.ServerSelection.ISPs, err = r.env.CSVInside("ISP", constants.MullvadISPChoices(servers))
if err != nil {
return err
return fmt.Errorf("environment variable ISP: %w", err)
}
settings.ServerSelection.Owned, err = r.env.YesNo("OWNED", params.Default("no"))
if err != nil {
return fmt.Errorf("environment variable OWNED: %w", err)
}
err = settings.ServerSelection.OpenVPN.readMullvad(r)
if err != nil {
return err
}
settings.ExtraConfigOptions.OpenVPNIPv6, err = r.env.OnOff("OPENVPN_IPV6", params.Default("off"))
return settings.ServerSelection.Wireguard.readMullvad(r.env)
}
func (settings *OpenVPNSelection) readMullvad(r reader) (err error) {
settings.TCP, err = readOpenVPNProtocol(r)
if err != nil {
return err
}
settings.CustomPort, err = readOpenVPNCustomPort(r, openvpnPortValidation{
tcp: settings.TCP,
allowedTCP: []uint16{80, 443, 1401},
allowedUDP: []uint16{53, 1194, 1195, 1196, 1197, 1300, 1301, 1302, 1303, 1400},
})
if err != nil {
return err
}
return nil
}
func (settings *WireguardSelection) readMullvad(env params.Interface) (err error) {
settings.EndpointPort, err = readWireguardCustomPort(env, nil)
if err != nil {
return err
}

View File

@@ -8,38 +8,23 @@ import (
"github.com/qdm12/golibs/params"
)
func (settings *Provider) nordvpnLines() (lines []string) {
if len(settings.ServerSelection.Regions) > 0 {
lines = append(lines, lastIndent+"Regions: "+commaJoin(settings.ServerSelection.Regions))
}
if numbersUint16 := settings.ServerSelection.Numbers; len(numbersUint16) > 0 {
numbersString := make([]string, len(numbersUint16))
for i, numberUint16 := range numbersUint16 {
numbersString[i] = strconv.Itoa(int(numberUint16))
}
lines = append(lines, lastIndent+"Numbers: "+commaJoin(numbersString))
}
return lines
}
func (settings *Provider) readNordvpn(r reader) (err error) {
settings.Name = constants.Nordvpn
settings.ServerSelection.Protocol, err = readProtocol(r.env)
if err != nil {
return err
}
servers := r.servers.GetNordvpn()
settings.ServerSelection.TargetIP, err = readTargetIP(r.env)
if err != nil {
return err
}
settings.ServerSelection.Regions, err = r.env.CSVInside("REGION", constants.NordvpnRegionChoices())
settings.ServerSelection.Regions, err = r.env.CSVInside("REGION", constants.NordvpnRegionChoices(servers))
if err != nil {
return err
return fmt.Errorf("environment variable REGION: %w", err)
}
settings.ServerSelection.Hostnames, err = r.env.CSVInside("SERVER_HOSTNAME", constants.NordvpnHostnameChoices(servers))
if err != nil {
return fmt.Errorf("environment variable SERVER_HOSTNAME: %w", err)
}
settings.ServerSelection.Numbers, err = readNordVPNServerNumbers(r.env)
@@ -47,11 +32,12 @@ func (settings *Provider) readNordvpn(r reader) (err error) {
return err
}
return nil
return settings.ServerSelection.OpenVPN.readProtocolOnly(r)
}
func readNordVPNServerNumbers(env params.Env) (numbers []uint16, err error) {
possibilities := make([]string, 65537)
func readNordVPNServerNumbers(env params.Interface) (numbers []uint16, err error) {
const possiblePortsCount = 65537
possibilities := make([]string, possiblePortsCount)
for i := range possibilities {
possibilities[i] = fmt.Sprintf("%d", i)
}

View File

@@ -3,6 +3,7 @@ package configuration
import (
"errors"
"fmt"
"regexp"
"strconv"
"strings"
@@ -15,11 +16,19 @@ type OpenVPN struct {
User string `json:"user"`
Password string `json:"password"`
Verbosity int `json:"verbosity"`
Flags []string `json:"flags"`
MSSFix uint16 `json:"mssfix"`
Root bool `json:"run_as_root"`
Cipher string `json:"cipher"`
Ciphers []string `json:"ciphers"`
Auth string `json:"auth"`
Provider Provider `json:"provider"`
ConfFile string `json:"conf_file"`
Version string `json:"version"`
ClientCrt string `json:"-"` // Cyberghost
ClientKey string `json:"-"` // Cyberghost, VPNUnlimited
EncPreset string `json:"encryption_preset"` // PIA
IPv6 bool `json:"ipv6"` // Mullvad
ProcUser string `json:"procuser"` // Process username
Interface string `json:"interface"`
}
func (settings *OpenVPN) String() string {
@@ -29,112 +38,170 @@ func (settings *OpenVPN) String() string {
func (settings *OpenVPN) lines() (lines []string) {
lines = append(lines, lastIndent+"OpenVPN:")
lines = append(lines, indent+lastIndent+"Version: "+settings.Version)
lines = append(lines, indent+lastIndent+"Verbosity level: "+strconv.Itoa(settings.Verbosity))
lines = append(lines, indent+lastIndent+"Network interface: "+settings.Interface)
if len(settings.Flags) > 0 {
lines = append(lines, indent+lastIndent+"Flags: "+strings.Join(settings.Flags, " "))
}
if settings.Root {
lines = append(lines, indent+lastIndent+"Run as root: enabled")
}
if len(settings.Cipher) > 0 {
lines = append(lines, indent+lastIndent+"Custom cipher: "+settings.Cipher)
if len(settings.Ciphers) > 0 {
lines = append(lines, indent+lastIndent+"Custom ciphers: "+commaJoin(settings.Ciphers))
}
if len(settings.Auth) > 0 {
lines = append(lines, indent+lastIndent+"Custom auth algorithm: "+settings.Auth)
}
lines = append(lines, indent+lastIndent+"Provider:")
for _, line := range settings.Provider.lines() {
lines = append(lines, indent+indent+line)
if settings.ConfFile != "" {
lines = append(lines, indent+lastIndent+"Configuration file: "+settings.ConfFile)
}
if settings.ClientKey != "" {
lines = append(lines, indent+lastIndent+"Client key is set")
}
if settings.ClientCrt != "" {
lines = append(lines, indent+lastIndent+"Client certificate is set")
}
if settings.IPv6 {
lines = append(lines, indent+lastIndent+"IPv6: enabled")
}
if settings.EncPreset != "" { // PIA only
lines = append(lines, indent+lastIndent+"Encryption preset: "+settings.EncPreset)
}
return lines
}
var (
ErrInvalidVPNProvider = errors.New("invalid VPN provider")
)
func (settings *OpenVPN) read(r reader) (err error) {
vpnsp, err := r.env.Inside("VPNSP", []string{
"pia", "private internet access", "mullvad", "windscribe", "surfshark", "torguard",
"cyberghost", "vyprvpn", "nordvpn", "purevpn", "privado"},
params.Default("private internet access"))
if err != nil {
return err
}
if vpnsp == "pia" { // retro compatibility
vpnsp = "private internet access"
func (settings *OpenVPN) read(r reader, serviceProvider string) (err error) {
credentialsRequired := false
switch serviceProvider {
case constants.Custom:
case constants.VPNUnlimited:
default:
credentialsRequired = true
}
settings.Provider.Name = vpnsp
settings.User, err = r.getFromEnvOrSecretFile("OPENVPN_USER", true, []string{"USER"})
settings.User, err = r.getFromEnvOrSecretFile("OPENVPN_USER", credentialsRequired, []string{"USER"})
if err != nil {
return err
return fmt.Errorf("environment variable OPENVPN_USER: %w", err)
}
// Remove spaces in user ID to simplify user's life, thanks @JeordyR
settings.User = strings.ReplaceAll(settings.User, " ", "")
if settings.Provider.Name == constants.Mullvad {
if serviceProvider == constants.Mullvad {
settings.Password = "m"
} else {
settings.Password, err = r.getFromEnvOrSecretFile("OPENVPN_PASSWORD", true, []string{"PASSWORD"})
settings.Password, err = r.getFromEnvOrSecretFile("OPENVPN_PASSWORD", credentialsRequired, []string{"PASSWORD"})
if err != nil {
return err
}
}
settings.Verbosity, err = r.env.IntRange("OPENVPN_VERBOSITY", 0, 6, params.Default("1"))
settings.Version, err = r.env.Inside("OPENVPN_VERSION",
[]string{constants.Openvpn24, constants.Openvpn25}, params.Default(constants.Openvpn25))
if err != nil {
return err
return fmt.Errorf("environment variable OPENVPN_VERSION: %w", err)
}
settings.Root, err = r.env.YesNo("OPENVPN_ROOT", params.Default("yes"))
settings.Verbosity, err = r.env.IntRange("OPENVPN_VERBOSITY", 0, 6, params.Default("1")) //nolint:gomnd
if err != nil {
return err
return fmt.Errorf("environment variable OPENVPN_VERBOSITY: %w", err)
}
settings.Cipher, err = r.env.Get("OPENVPN_CIPHER")
settings.Flags = []string{}
flagsStr, err := r.env.Get("OPENVPN_FLAGS")
if err != nil {
return err
return fmt.Errorf("environment variable OPENVPN_FLAGS: %w", err)
}
if flagsStr != "" {
settings.Flags = strings.Fields(flagsStr)
}
settings.Root, err = r.env.YesNo("OPENVPN_ROOT", params.Default("no"))
if err != nil {
return fmt.Errorf("environment variable OPENVPN_ROOT: %w", err)
}
settings.Ciphers, err = r.env.CSV("OPENVPN_CIPHER")
if err != nil {
return fmt.Errorf("environment variable OPENVPN_CIPHER: %w", err)
}
settings.Auth, err = r.env.Get("OPENVPN_AUTH")
if err != nil {
return err
return fmt.Errorf("environment variable OPENVPN_AUTH: %w", err)
}
mssFix, err := r.env.IntRange("OPENVPN_MSSFIX", 0, 10000, params.Default("0"))
const maxMSSFix = 10000
mssFix, err := r.env.IntRange("OPENVPN_MSSFIX", 0, maxMSSFix, params.Default("0"))
if err != nil {
return err
return fmt.Errorf("environment variable OPENVPN_MSSFIX: %w", err)
}
settings.MSSFix = uint16(mssFix)
var readProvider func(r reader) error
switch settings.Provider.Name {
case constants.PrivateInternetAccess:
readProvider = settings.Provider.readPrivateInternetAccess
case constants.Mullvad:
readProvider = settings.Provider.readMullvad
case constants.Windscribe:
readProvider = settings.Provider.readWindscribe
case constants.Surfshark:
readProvider = settings.Provider.readSurfshark
case constants.Cyberghost:
readProvider = settings.Provider.readCyberghost
case constants.Vyprvpn:
readProvider = settings.Provider.readVyprvpn
case constants.Nordvpn:
readProvider = settings.Provider.readNordvpn
case constants.Purevpn:
readProvider = settings.Provider.readPurevpn
case constants.Privado:
readProvider = settings.Provider.readPrivado
case constants.Torguard:
readProvider = settings.Provider.readTorguard
default:
return fmt.Errorf("%w: %s", ErrInvalidVPNProvider, settings.Provider.Name)
settings.IPv6, err = r.env.OnOff("OPENVPN_IPV6", params.Default("off"))
if err != nil {
return fmt.Errorf("environment variable OPENVPN_IPV6: %w", err)
}
return readProvider(r)
settings.Interface, err = readInterface(r.env)
if err != nil {
return err
}
switch serviceProvider {
case constants.Custom:
err = settings.readCustom(r) // read OPENVPN_CUSTOM_CONFIG
case constants.Cyberghost:
err = settings.readCyberghost(r)
case constants.PrivateInternetAccess:
settings.EncPreset, err = getPIAEncryptionPreset(r)
case constants.VPNUnlimited:
err = settings.readVPNUnlimited(r)
case constants.Wevpn:
err = settings.readWevpn(r)
}
if err != nil {
return err
}
return nil
}
func readOpenVPNProtocol(r reader) (tcp bool, err error) {
protocol, err := r.env.Inside("OPENVPN_PROTOCOL", []string{constants.TCP, constants.UDP},
params.Default(constants.UDP), params.RetroKeys([]string{"PROTOCOL"}, r.onRetroActive))
if err != nil {
return false, fmt.Errorf("environment variable OPENVPN_PROTOCOL: %w", err)
}
return protocol == constants.TCP, nil
}
const openvpnIntfRegexString = `^.*[0-9]$`
var openvpnIntfRegexp = regexp.MustCompile(openvpnIntfRegexString)
var errInterfaceNameNotValid = errors.New("interface name is not valid")
func readInterface(env params.Interface) (intf string, err error) {
intf, err = env.Get("OPENVPN_INTERFACE", params.Default("tun0"))
if err != nil {
return "", fmt.Errorf("environment variable OPENVPN_INTERFACE: %w", err)
}
if !openvpnIntfRegexp.MatchString(intf) {
return "", fmt.Errorf("%w: does not match regex %s: %s",
errInterfaceNameNotValid, openvpnIntfRegexString, intf)
}
return intf, nil
}

View File

@@ -11,15 +11,28 @@ import (
func Test_OpenVPN_JSON(t *testing.T) {
t.Parallel()
in := OpenVPN{
Root: true,
Provider: Provider{
Name: "name",
},
Root: true,
Flags: []string{},
Ciphers: []string{},
}
data, err := json.Marshal(in)
data, err := json.MarshalIndent(in, "", " ")
require.NoError(t, err)
//nolint:lll
assert.Equal(t, `{"user":"","password":"","verbosity":0,"mssfix":0,"run_as_root":true,"cipher":"","auth":"","provider":{"name":"name","server_selection":{"network_protocol":"","regions":null,"group":"","countries":null,"cities":null,"hostnames":null,"isps":null,"owned":false,"custom_port":0,"numbers":null,"encryption_preset":""},"extra_config":{"encryption_preset":"","openvpn_ipv6":false},"port_forwarding":{"enabled":false,"filepath":""}}}`, string(data))
assert.Equal(t, `{
"user": "",
"password": "",
"verbosity": 0,
"flags": [],
"mssfix": 0,
"run_as_root": true,
"ciphers": [],
"auth": "",
"conf_file": "",
"version": "",
"encryption_preset": "",
"ipv6": false,
"procuser": "",
"interface": ""
}`, string(data))
var out OpenVPN
err = json.Unmarshal(data, &out)
require.NoError(t, err)

View File

@@ -0,0 +1,43 @@
package configuration
import (
"fmt"
"github.com/qdm12/gluetun/internal/constants"
)
func (settings *Provider) readPerfectPrivacy(r reader) (err error) {
settings.Name = constants.Perfectprivacy
servers := r.servers.GetPerfectprivacy()
settings.ServerSelection.TargetIP, err = readTargetIP(r.env)
if err != nil {
return err
}
settings.ServerSelection.Cities, err = r.env.CSVInside("CITY", constants.PerfectprivacyCityChoices(servers))
if err != nil {
return fmt.Errorf("environment variable CITY: %w", err)
}
return settings.ServerSelection.OpenVPN.readPerfectPrivacy(r)
}
func (settings *OpenVPNSelection) readPerfectPrivacy(r reader) (err error) {
settings.TCP, err = readOpenVPNProtocol(r)
if err != nil {
return err
}
portValidation := openvpnPortValidation{
tcp: settings.TCP,
allowedTCP: []uint16{44, 443, 4433},
allowedUDP: []uint16{44, 443, 4433},
}
settings.CustomPort, err = readOpenVPNCustomPort(r, portValidation)
if err != nil {
return err
}
return nil
}

View File

@@ -1,33 +1,38 @@
package configuration
import (
"fmt"
"github.com/qdm12/gluetun/internal/constants"
)
func (settings *Provider) privadoLines() (lines []string) {
if len(settings.ServerSelection.Hostnames) > 0 {
lines = append(lines, lastIndent+"Hostnames: "+commaJoin(settings.ServerSelection.Hostnames))
}
return lines
}
func (settings *Provider) readPrivado(r reader) (err error) {
settings.Name = constants.Privado
settings.ServerSelection.Protocol, err = readProtocol(r.env)
if err != nil {
return err
}
servers := r.servers.GetPrivado()
settings.ServerSelection.TargetIP, err = readTargetIP(r.env)
if err != nil {
return err
}
settings.ServerSelection.Hostnames, err = r.env.CSVInside("SERVER_HOSTNAME", constants.PrivadoHostnameChoices())
settings.ServerSelection.Countries, err = r.env.CSVInside("COUNTRY", constants.PrivadoCountryChoices(servers))
if err != nil {
return err
return fmt.Errorf("environment variable COUNTRY: %w", err)
}
settings.ServerSelection.Regions, err = r.env.CSVInside("REGION", constants.PrivadoRegionChoices(servers))
if err != nil {
return fmt.Errorf("environment variable REGION: %w", err)
}
settings.ServerSelection.Cities, err = r.env.CSVInside("CITY", constants.PrivadoCityChoices(servers))
if err != nil {
return fmt.Errorf("environment variable CITY: %w", err)
}
settings.ServerSelection.Hostnames, err = r.env.CSVInside("SERVER_HOSTNAME", constants.PrivadoHostnameChoices(servers))
if err != nil {
return fmt.Errorf("environment variable SERVER_HOSTNAME: %w", err)
}
return nil

View File

@@ -1,77 +1,75 @@
package configuration
import (
"strconv"
"fmt"
"github.com/qdm12/gluetun/internal/constants"
"github.com/qdm12/golibs/params"
)
func (settings *Provider) privateinternetaccessLines() (lines []string) {
if len(settings.ServerSelection.Regions) > 0 {
lines = append(lines, lastIndent+"Regions: "+commaJoin(settings.ServerSelection.Regions))
}
lines = append(lines, lastIndent+"Encryption preset: "+settings.ServerSelection.EncryptionPreset)
lines = append(lines, lastIndent+"Custom port: "+strconv.Itoa(int(settings.ServerSelection.CustomPort)))
if settings.PortForwarding.Enabled {
lines = append(lines, lastIndent+"Port forwarding:")
for _, line := range settings.PortForwarding.lines() {
lines = append(lines, indent+line)
}
}
return lines
}
func (settings *Provider) readPrivateInternetAccess(r reader) (err error) {
settings.Name = constants.PrivateInternetAccess
settings.ServerSelection.Protocol, err = readProtocol(r.env)
if err != nil {
return err
}
servers := r.servers.GetPia()
settings.ServerSelection.TargetIP, err = readTargetIP(r.env)
if err != nil {
return err
}
encryptionPreset, err := r.env.Inside("PIA_ENCRYPTION",
[]string{constants.PIAEncryptionPresetNormal, constants.PIAEncryptionPresetStrong},
params.RetroKeys([]string{"ENCRYPTION"}, r.onRetroActive),
params.Default(constants.PIACertificateStrong),
)
settings.ServerSelection.Regions, err = r.env.CSVInside("REGION", constants.PIAGeoChoices(servers))
if err != nil {
return err
}
settings.ServerSelection.EncryptionPreset = encryptionPreset
settings.ExtraConfigOptions.EncryptionPreset = encryptionPreset
settings.ServerSelection.Regions, err = r.env.CSVInside("REGION", constants.PIAGeoChoices())
if err != nil {
return err
return fmt.Errorf("environment variable REGION: %w", err)
}
settings.ServerSelection.CustomPort, err = readPortOrZero(r.env, "PORT")
settings.ServerSelection.Hostnames, err = r.env.CSVInside("SERVER_HOSTNAME", constants.PIAHostnameChoices(servers))
if err != nil {
return err
return fmt.Errorf("environment variable SERVER_HOSTNAME: %w", err)
}
settings.ServerSelection.Names, err = r.env.CSVInside("SERVER_NAME", constants.PIANameChoices(servers))
if err != nil {
return fmt.Errorf("environment variable SERVER_NAME: %w", err)
}
settings.PortForwarding.Enabled, err = r.env.OnOff("PORT_FORWARDING", params.Default("off"))
if err != nil {
return err
return fmt.Errorf("environment variable PORT_FORWARDING: %w", err)
}
if settings.PortForwarding.Enabled {
settings.PortForwarding.Filepath, err = r.env.Path("PORT_FORWARDING_STATUS_FILE",
params.Default("/tmp/gluetun/forwarded_port"), params.CaseSensitiveValue())
if err != nil {
return err
return fmt.Errorf("environment variable PORT_FORWARDING_STATUS_FILE: %w", err)
}
}
return settings.ServerSelection.OpenVPN.readPrivateInternetAccess(r)
}
func (settings *OpenVPNSelection) readPrivateInternetAccess(r reader) (err error) {
settings.EncPreset, err = getPIAEncryptionPreset(r)
if err != nil {
return err
}
settings.CustomPort, err = readOpenVPNCustomPort(r, openvpnPortValidation{allAllowed: true})
if err != nil {
return err
}
return nil
}
func getPIAEncryptionPreset(r reader) (encryptionPreset string, err error) {
encryptionPreset, err = r.env.Inside("PIA_ENCRYPTION",
[]string{constants.PIAEncryptionPresetNone, constants.PIAEncryptionPresetNormal, constants.PIAEncryptionPresetStrong},
params.RetroKeys([]string{"ENCRYPTION"}, r.onRetroActive),
params.Default(constants.PIAEncryptionPresetStrong),
)
if err != nil {
return "", fmt.Errorf("environment variable PIA_ENCRYPTION: %w", err)
}
return encryptionPreset, nil
}

View File

@@ -0,0 +1,35 @@
package configuration
import (
"fmt"
"github.com/qdm12/gluetun/internal/constants"
)
func (settings *Provider) readPrivatevpn(r reader) (err error) {
settings.Name = constants.Privatevpn
servers := r.servers.GetPrivatevpn()
settings.ServerSelection.TargetIP, err = readTargetIP(r.env)
if err != nil {
return err
}
settings.ServerSelection.Countries, err = r.env.CSVInside("COUNTRY", constants.PrivatevpnCountryChoices(servers))
if err != nil {
return fmt.Errorf("environment variable COUNTRY: %w", err)
}
settings.ServerSelection.Cities, err = r.env.CSVInside("CITY", constants.PrivatevpnCityChoices(servers))
if err != nil {
return fmt.Errorf("environment variable CITY: %w", err)
}
settings.ServerSelection.Hostnames, err = r.env.CSVInside("SERVER_HOSTNAME",
constants.PrivatevpnHostnameChoices(servers))
if err != nil {
return fmt.Errorf("environment variable SERVER_HOSTNAME: %w", err)
}
return settings.ServerSelection.OpenVPN.readProtocolAndPort(r)
}

View File

@@ -0,0 +1,51 @@
package configuration
import (
"fmt"
"github.com/qdm12/gluetun/internal/constants"
"github.com/qdm12/golibs/params"
)
func (settings *Provider) readProtonvpn(r reader) (err error) {
settings.Name = constants.Protonvpn
servers := r.servers.GetProtonvpn()
settings.ServerSelection.TargetIP, err = readTargetIP(r.env)
if err != nil {
return err
}
settings.ServerSelection.Countries, err = r.env.CSVInside("COUNTRY", constants.ProtonvpnCountryChoices(servers))
if err != nil {
return fmt.Errorf("environment variable COUNTRY: %w", err)
}
settings.ServerSelection.Regions, err = r.env.CSVInside("REGION", constants.ProtonvpnRegionChoices(servers))
if err != nil {
return fmt.Errorf("environment variable REGION: %w", err)
}
settings.ServerSelection.Cities, err = r.env.CSVInside("CITY", constants.ProtonvpnCityChoices(servers))
if err != nil {
return fmt.Errorf("environment variable CITY: %w", err)
}
settings.ServerSelection.Names, err = r.env.CSVInside("SERVER_NAME", constants.ProtonvpnNameChoices(servers))
if err != nil {
return fmt.Errorf("environment variable SERVER_NAME: %w", err)
}
settings.ServerSelection.Hostnames, err = r.env.CSVInside("SERVER_HOSTNAME",
constants.ProtonvpnHostnameChoices(servers))
if err != nil {
return fmt.Errorf("environment variable SERVER_HOSTNAME: %w", err)
}
settings.ServerSelection.FreeOnly, err = r.env.YesNo("FREE_ONLY", params.Default("no"))
if err != nil {
return fmt.Errorf("environment variable FREE_ONLY: %w", err)
}
return settings.ServerSelection.OpenVPN.readProtocolAndPort(r)
}

View File

@@ -12,96 +12,237 @@ import (
// Provider contains settings specific to a VPN provider.
type Provider struct {
Name string `json:"name"`
ServerSelection ServerSelection `json:"server_selection"`
ExtraConfigOptions ExtraConfigOptions `json:"extra_config"`
PortForwarding PortForwarding `json:"port_forwarding"`
Name string `json:"name"`
ServerSelection ServerSelection `json:"server_selection"`
PortForwarding PortForwarding `json:"port_forwarding"`
}
func (settings *Provider) lines() (lines []string) {
if settings.Name == "" { // custom OpenVPN configuration
return nil
}
lines = append(lines, lastIndent+strings.Title(settings.Name)+" settings:")
lines = append(lines, indent+lastIndent+"Network protocol: "+settings.ServerSelection.Protocol)
if settings.ServerSelection.TargetIP != nil {
lines = append(lines, indent+lastIndent+"Target IP address: "+settings.ServerSelection.TargetIP.String())
}
var providerLines []string
switch strings.ToLower(settings.Name) {
case "cyberghost":
providerLines = settings.cyberghostLines()
case "mullvad":
providerLines = settings.mullvadLines()
case "nordvpn":
providerLines = settings.nordvpnLines()
case "privado":
providerLines = settings.privadoLines()
case "private internet access":
providerLines = settings.privateinternetaccessLines()
case "purevpn":
providerLines = settings.purevpnLines()
case "surfshark":
providerLines = settings.surfsharkLines()
case "torguard":
providerLines = settings.torguardLines()
case "vyprvpn":
providerLines = settings.vyprvpnLines()
case "windscribe":
providerLines = settings.windscribeLines()
default:
panic(`Missing lines method for provider "` +
settings.Name + `"! Please create a Github issue.`)
}
for _, line := range providerLines {
for _, line := range settings.ServerSelection.toLines() {
lines = append(lines, indent+line)
}
if settings.PortForwarding.Enabled { // PIA
lines = append(lines, indent+lastIndent+"Port forwarding:")
for _, line := range settings.PortForwarding.lines() {
lines = append(lines, indent+indent+line)
}
}
return lines
}
var (
ErrInvalidVPNProvider = errors.New("invalid VPN provider")
)
func (settings *Provider) read(r reader, vpnType string) error {
err := settings.readVPNServiceProvider(r, vpnType)
if err != nil {
return err
}
switch settings.Name {
case constants.Custom:
err = settings.readCustom(r, vpnType)
case constants.Cyberghost:
err = settings.readCyberghost(r)
case constants.Expressvpn:
err = settings.readExpressvpn(r)
case constants.Fastestvpn:
err = settings.readFastestvpn(r)
case constants.HideMyAss:
err = settings.readHideMyAss(r)
case constants.Ipvanish:
err = settings.readIpvanish(r)
case constants.Ivpn:
err = settings.readIvpn(r)
case constants.Mullvad:
err = settings.readMullvad(r)
case constants.Nordvpn:
err = settings.readNordvpn(r)
case constants.Perfectprivacy:
err = settings.readPerfectPrivacy(r)
case constants.Privado:
err = settings.readPrivado(r)
case constants.PrivateInternetAccess:
err = settings.readPrivateInternetAccess(r)
case constants.Privatevpn:
err = settings.readPrivatevpn(r)
case constants.Protonvpn:
err = settings.readProtonvpn(r)
case constants.Purevpn:
err = settings.readPurevpn(r)
case constants.Surfshark:
err = settings.readSurfshark(r)
case constants.Torguard:
err = settings.readTorguard(r)
case constants.VPNUnlimited:
err = settings.readVPNUnlimited(r)
case constants.Vyprvpn:
err = settings.readVyprvpn(r)
case constants.Wevpn:
err = settings.readWevpn(r)
case constants.Windscribe:
err = settings.readWindscribe(r)
default:
return fmt.Errorf("%w: %s", ErrInvalidVPNProvider, settings.Name)
}
if err != nil {
return err
}
settings.ServerSelection.VPN = vpnType
return nil
}
func (settings *Provider) readVPNServiceProvider(r reader, vpnType string) (err error) {
var allowedVPNServiceProviders []string
switch vpnType {
case constants.OpenVPN:
allowedVPNServiceProviders = []string{
constants.Custom,
"cyberghost", constants.Expressvpn, "fastestvpn", "hidemyass", "ipvanish",
"ivpn", "mullvad", "nordvpn",
constants.Perfectprivacy, "privado", "pia", "private internet access", "privatevpn", "protonvpn",
"purevpn", "surfshark", "torguard", constants.VPNUnlimited, "vyprvpn",
constants.Wevpn, "windscribe"}
case constants.Wireguard:
allowedVPNServiceProviders = []string{
constants.Custom, constants.Ivpn,
constants.Mullvad, constants.Windscribe,
}
}
vpnsp, err := r.env.Inside("VPNSP", allowedVPNServiceProviders,
params.Default("private internet access"))
if err != nil {
return fmt.Errorf("environment variable VPNSP: %w", err)
}
if vpnsp == "pia" { // retro compatibility
vpnsp = "private internet access"
}
if settings.isOpenVPNCustomConfig(r.env) { // retro compatibility
vpnsp = constants.Custom
}
settings.Name = vpnsp
return nil
}
func commaJoin(slice []string) string {
return strings.Join(slice, ", ")
}
func readProtocol(env params.Env) (protocol string, err error) {
return env.Inside("PROTOCOL", []string{constants.TCP, constants.UDP}, params.Default(constants.UDP))
func protoToString(tcp bool) string {
if tcp {
return constants.TCP
}
return constants.UDP
}
func readTargetIP(env params.Env) (targetIP net.IP, err error) {
return readIP(env, "OPENVPN_TARGET_IP")
}
var (
ErrInvalidProtocol = errors.New("invalid network protocol")
)
func readCustomPort(env params.Env, protocol string,
allowedTCP, allowedUDP []uint16) (port uint16, err error) {
port, err = readPortOrZero(env, "PORT")
func readTargetIP(env params.Interface) (targetIP net.IP, err error) {
targetIP, err = readIP(env, "OPENVPN_TARGET_IP")
if err != nil {
return 0, err
return nil, fmt.Errorf("environment variable OPENVPN_TARGET_IP: %w", err)
}
return targetIP, nil
}
type openvpnPortValidation struct {
allAllowed bool
tcp bool
allowedTCP []uint16
allowedUDP []uint16
}
func readOpenVPNCustomPort(r reader, validation openvpnPortValidation) (
port uint16, err error) {
port, err = readPortOrZero(r.env, "OPENVPN_PORT")
if err != nil {
return 0, fmt.Errorf("environment variable OPENVPN_PORT: %w", err)
} else if port == 0 {
return 0, nil
// Try using old variable name
port, err = readPortOrZero(r.env, "PORT")
if err != nil {
r.onRetroActive("PORT", "OPENVPN_PORT")
return 0, fmt.Errorf("environment variable PORT: %w", err)
}
}
switch protocol {
case constants.TCP:
for i := range allowedTCP {
if allowedTCP[i] == port {
return port, nil
}
}
return 0, fmt.Errorf("%w: port %d for TCP protocol", ErrInvalidPort, port)
case constants.UDP:
for i := range allowedUDP {
if allowedUDP[i] == port {
return port, nil
}
}
return 0, fmt.Errorf("%w: port %d for UDP protocol", ErrInvalidPort, port)
default:
return 0, fmt.Errorf("%w: %s", ErrInvalidProtocol, protocol)
if port == 0 || validation.allAllowed {
return port, nil
}
if validation.tcp {
for _, allowedPort := range validation.allowedTCP {
if port == allowedPort {
return port, nil
}
}
return 0, fmt.Errorf(
"environment variable PORT: %w: port %d for TCP protocol, can only be one of %s",
ErrInvalidPort, port, portsToString(validation.allowedTCP))
}
for _, allowedPort := range validation.allowedUDP {
if port == allowedPort {
return port, nil
}
}
return 0, fmt.Errorf(
"environment variable PORT: %w: port %d for UDP protocol, can only be one of %s",
ErrInvalidPort, port, portsToString(validation.allowedUDP))
}
// note: set allowed to an empty slice to allow all valid ports
func readWireguardCustomPort(env params.Interface, allowed []uint16) (port uint16, err error) {
port, err = readPortOrZero(env, "WIREGUARD_ENDPOINT_PORT")
if err != nil {
return 0, fmt.Errorf("environment variable WIREGUARD_ENDPOINT_PORT: %w", err)
} else if port == 0 {
port, _ = readPortOrZero(env, "WIREGUARD_PORT")
if err == nil {
return port, nil // 0 or WIREGUARD_PORT value
}
return 0, nil // default 0
}
if len(allowed) == 0 {
return port, nil
}
for i := range allowed {
if allowed[i] == port {
return port, nil
}
}
return 0, fmt.Errorf(
"environment variable WIREGUARD_PORT: %w: port %d, can only be one of %s",
ErrInvalidPort, port, portsToString(allowed))
}
func portsToString(ports []uint16) string {
slice := make([]string, len(ports))
for i := range ports {
slice[i] = fmt.Sprint(ports[i])
}
return strings.Join(slice, ", ")
}
// isOpenVPNCustomConfig is for retro compatibility to set VPNSP=custom
// if OPENVPN_CUSTOM_CONFIG is set.
func (settings Provider) isOpenVPNCustomConfig(env params.Interface) (ok bool) {
s, _ := env.Get("VPN_TYPE")
isOpenVPN := s == constants.OpenVPN
s, _ = env.Get("OPENVPN_CUSTOM_CONFIG")
return isOpenVPN && s != ""
}

View File

@@ -24,86 +24,231 @@ func Test_Provider_lines(t *testing.T) {
settings: Provider{
Name: constants.Cyberghost,
ServerSelection: ServerSelection{
Protocol: constants.UDP,
Group: "group",
Regions: []string{"a", "El country"},
},
ExtraConfigOptions: ExtraConfigOptions{
ClientKey: "a",
ClientCertificate: "a",
VPN: constants.OpenVPN,
Countries: []string{"a", "El country"},
},
},
lines: []string{
"|--Cyberghost settings:",
" |--Network protocol: udp",
" |--Server group: group",
" |--Regions: a, El country",
" |--Client key is set",
" |--Client certificate is set",
" |--Countries: a, El country",
" |--OpenVPN selection:",
" |--Protocol: udp",
},
},
"expressvpn": {
settings: Provider{
Name: constants.Expressvpn,
ServerSelection: ServerSelection{
VPN: constants.OpenVPN,
Hostnames: []string{"a", "b"},
Countries: []string{"c", "d"},
Cities: []string{"e", "f"},
},
},
lines: []string{
"|--Expressvpn settings:",
" |--Countries: c, d",
" |--Cities: e, f",
" |--Hostnames: a, b",
" |--OpenVPN selection:",
" |--Protocol: udp",
},
},
"fastestvpn": {
settings: Provider{
Name: constants.Fastestvpn,
ServerSelection: ServerSelection{
VPN: constants.OpenVPN,
Hostnames: []string{"a", "b"},
Countries: []string{"c", "d"},
},
},
lines: []string{
"|--Fastestvpn settings:",
" |--Countries: c, d",
" |--Hostnames: a, b",
" |--OpenVPN selection:",
" |--Protocol: udp",
},
},
"hidemyass": {
settings: Provider{
Name: constants.HideMyAss,
ServerSelection: ServerSelection{
VPN: constants.OpenVPN,
Countries: []string{"a", "b"},
Cities: []string{"c", "d"},
Hostnames: []string{"e", "f"},
},
},
lines: []string{
"|--Hidemyass settings:",
" |--Countries: a, b",
" |--Cities: c, d",
" |--Hostnames: e, f",
" |--OpenVPN selection:",
" |--Protocol: udp",
},
},
"ipvanish": {
settings: Provider{
Name: constants.Ipvanish,
ServerSelection: ServerSelection{
VPN: constants.OpenVPN,
Countries: []string{"a", "b"},
Cities: []string{"c", "d"},
Hostnames: []string{"e", "f"},
},
},
lines: []string{
"|--Ipvanish settings:",
" |--Countries: a, b",
" |--Cities: c, d",
" |--Hostnames: e, f",
" |--OpenVPN selection:",
" |--Protocol: udp",
},
},
"ivpn": {
settings: Provider{
Name: constants.Ivpn,
ServerSelection: ServerSelection{
VPN: constants.OpenVPN,
Countries: []string{"a", "b"},
Cities: []string{"c", "d"},
Hostnames: []string{"e", "f"},
},
},
lines: []string{
"|--Ivpn settings:",
" |--Countries: a, b",
" |--Cities: c, d",
" |--Hostnames: e, f",
" |--OpenVPN selection:",
" |--Protocol: udp",
},
},
"mullvad": {
settings: Provider{
Name: constants.Mullvad,
ServerSelection: ServerSelection{
Protocol: constants.UDP,
Countries: []string{"a", "b"},
Cities: []string{"c", "d"},
ISPs: []string{"e", "f"},
CustomPort: 1,
},
ExtraConfigOptions: ExtraConfigOptions{
OpenVPNIPv6: true,
VPN: constants.OpenVPN,
Countries: []string{"a", "b"},
Cities: []string{"c", "d"},
ISPs: []string{"e", "f"},
OpenVPN: OpenVPNSelection{
CustomPort: 1,
},
},
},
lines: []string{
"|--Mullvad settings:",
" |--Network protocol: udp",
" |--Countries: a, b",
" |--Cities: c, d",
" |--ISPs: e, f",
" |--Custom port: 1",
" |--IPv6: enabled",
" |--OpenVPN selection:",
" |--Protocol: udp",
" |--Custom port: 1",
},
},
"nordvpn": {
settings: Provider{
Name: constants.Nordvpn,
ServerSelection: ServerSelection{
Protocol: constants.UDP,
Regions: []string{"a", "b"},
Numbers: []uint16{1, 2},
VPN: constants.OpenVPN,
Regions: []string{"a", "b"},
Numbers: []uint16{1, 2},
},
},
lines: []string{
"|--Nordvpn settings:",
" |--Network protocol: udp",
" |--Regions: a, b",
" |--Numbers: 1, 2",
" |--OpenVPN selection:",
" |--Protocol: udp",
},
},
"perfectprivacy": {
settings: Provider{
Name: constants.Perfectprivacy,
ServerSelection: ServerSelection{
VPN: constants.OpenVPN,
Cities: []string{"a", "b"},
},
},
lines: []string{
"|--Perfect Privacy settings:",
" |--Cities: a, b",
" |--OpenVPN selection:",
" |--Protocol: udp",
},
},
"privado": {
settings: Provider{
Name: constants.Privado,
ServerSelection: ServerSelection{
Protocol: constants.UDP,
VPN: constants.OpenVPN,
Hostnames: []string{"a", "b"},
},
},
lines: []string{
"|--Privado settings:",
" |--Network protocol: udp",
" |--Hostnames: a, b",
" |--OpenVPN selection:",
" |--Protocol: udp",
},
},
"privatevpn": {
settings: Provider{
Name: constants.Privatevpn,
ServerSelection: ServerSelection{
VPN: constants.OpenVPN,
Hostnames: []string{"a", "b"},
Countries: []string{"c", "d"},
Cities: []string{"e", "f"},
},
},
lines: []string{
"|--Privatevpn settings:",
" |--Countries: c, d",
" |--Cities: e, f",
" |--Hostnames: a, b",
" |--OpenVPN selection:",
" |--Protocol: udp",
},
},
"protonvpn": {
settings: Provider{
Name: constants.Protonvpn,
ServerSelection: ServerSelection{
VPN: constants.OpenVPN,
Countries: []string{"a", "b"},
Regions: []string{"c", "d"},
Cities: []string{"e", "f"},
Names: []string{"g", "h"},
Hostnames: []string{"i", "j"},
},
},
lines: []string{
"|--Protonvpn settings:",
" |--Countries: a, b",
" |--Regions: c, d",
" |--Cities: e, f",
" |--Hostnames: i, j",
" |--Names: g, h",
" |--OpenVPN selection:",
" |--Protocol: udp",
},
},
"private internet access": {
settings: Provider{
Name: constants.PrivateInternetAccess,
ServerSelection: ServerSelection{
Protocol: constants.UDP,
Regions: []string{"a", "b"},
EncryptionPreset: constants.PIAEncryptionPresetStrong,
CustomPort: 1,
VPN: constants.OpenVPN,
Regions: []string{"a", "b"},
OpenVPN: OpenVPNSelection{
CustomPort: 1,
},
},
PortForwarding: PortForwarding{
Enabled: true,
@@ -112,10 +257,10 @@ func Test_Provider_lines(t *testing.T) {
},
lines: []string{
"|--Private Internet Access settings:",
" |--Network protocol: udp",
" |--Regions: a, b",
" |--Encryption preset: strong",
" |--Custom port: 1",
" |--OpenVPN selection:",
" |--Protocol: udp",
" |--Custom port: 1",
" |--Port forwarding:",
" |--File path: /here",
},
@@ -124,7 +269,7 @@ func Test_Provider_lines(t *testing.T) {
settings: Provider{
Name: constants.Purevpn,
ServerSelection: ServerSelection{
Protocol: constants.UDP,
VPN: constants.OpenVPN,
Regions: []string{"a", "b"},
Countries: []string{"c", "d"},
Cities: []string{"e", "f"},
@@ -132,31 +277,33 @@ func Test_Provider_lines(t *testing.T) {
},
lines: []string{
"|--Purevpn settings:",
" |--Network protocol: udp",
" |--Regions: a, b",
" |--Countries: c, d",
" |--Regions: a, b",
" |--Cities: e, f",
" |--OpenVPN selection:",
" |--Protocol: udp",
},
},
"surfshark": {
settings: Provider{
Name: constants.Surfshark,
ServerSelection: ServerSelection{
Protocol: constants.UDP,
Regions: []string{"a", "b"},
VPN: constants.OpenVPN,
Regions: []string{"a", "b"},
},
},
lines: []string{
"|--Surfshark settings:",
" |--Network protocol: udp",
" |--Regions: a, b",
" |--OpenVPN selection:",
" |--Protocol: udp",
},
},
"torguard": {
settings: Provider{
Name: constants.Torguard,
ServerSelection: ServerSelection{
Protocol: constants.UDP,
VPN: constants.OpenVPN,
Countries: []string{"a", "b"},
Cities: []string{"c", "d"},
Hostnames: []string{"e"},
@@ -164,44 +311,93 @@ func Test_Provider_lines(t *testing.T) {
},
lines: []string{
"|--Torguard settings:",
" |--Network protocol: udp",
" |--Countries: a, b",
" |--Cities: c, d",
" |--Hostnames: e",
" |--OpenVPN selection:",
" |--Protocol: udp",
},
},
constants.VPNUnlimited: {
settings: Provider{
Name: constants.VPNUnlimited,
ServerSelection: ServerSelection{
VPN: constants.OpenVPN,
Countries: []string{"a", "b"},
Cities: []string{"c", "d"},
Hostnames: []string{"e", "f"},
FreeOnly: true,
StreamOnly: true,
},
},
lines: []string{
"|--Vpn Unlimited settings:",
" |--Countries: a, b",
" |--Cities: c, d",
" |--Free servers only",
" |--Stream servers only",
" |--Hostnames: e, f",
" |--OpenVPN selection:",
" |--Protocol: udp",
},
},
"vyprvpn": {
settings: Provider{
Name: constants.Vyprvpn,
ServerSelection: ServerSelection{
Protocol: constants.UDP,
Regions: []string{"a", "b"},
VPN: constants.OpenVPN,
Regions: []string{"a", "b"},
},
},
lines: []string{
"|--Vyprvpn settings:",
" |--Network protocol: udp",
" |--Regions: a, b",
" |--OpenVPN selection:",
" |--Protocol: udp",
},
},
"wevpn": {
settings: Provider{
Name: constants.Wevpn,
ServerSelection: ServerSelection{
VPN: constants.OpenVPN,
Cities: []string{"a", "b"},
Hostnames: []string{"c", "d"},
OpenVPN: OpenVPNSelection{
CustomPort: 1,
},
},
},
lines: []string{
"|--Wevpn settings:",
" |--Cities: a, b",
" |--Hostnames: c, d",
" |--OpenVPN selection:",
" |--Protocol: udp",
" |--Custom port: 1",
},
},
"windscribe": {
settings: Provider{
Name: constants.Windscribe,
ServerSelection: ServerSelection{
Protocol: constants.UDP,
Regions: []string{"a", "b"},
Cities: []string{"c", "d"},
Hostnames: []string{"e", "f"},
CustomPort: 1,
VPN: constants.OpenVPN,
Regions: []string{"a", "b"},
Cities: []string{"c", "d"},
Hostnames: []string{"e", "f"},
OpenVPN: OpenVPNSelection{
CustomPort: 1,
},
},
},
lines: []string{
"|--Windscribe settings:",
" |--Network protocol: udp",
" |--Regions: a, b",
" |--Cities: c, d",
" |--Hostnames: e, f",
" |--Custom port: 1",
" |--OpenVPN selection:",
" |--Protocol: udp",
" |--Custom port: 1",
},
},
}
@@ -222,18 +418,18 @@ func Test_readProtocol(t *testing.T) {
t.Parallel()
testCases := map[string]struct {
mockStr string
mockErr error
protocol string
err error
mockStr string
mockErr error
tcp bool
err error
}{
"error": {
mockErr: errDummy,
err: errDummy,
err: errors.New("environment variable OPENVPN_PROTOCOL: dummy"),
},
"success": {
mockStr: "tcp",
protocol: constants.TCP,
mockStr: "tcp",
tcp: true,
},
}
@@ -243,12 +439,15 @@ func Test_readProtocol(t *testing.T) {
t.Parallel()
ctrl := gomock.NewController(t)
env := mock_params.NewMockEnv(ctrl)
env := mock_params.NewMockInterface(ctrl)
env.EXPECT().
Inside("PROTOCOL", []string{"tcp", "udp"}, gomock.Any()).
Inside("OPENVPN_PROTOCOL", []string{"tcp", "udp"}, gomock.Any(), gomock.Any()).
Return(testCase.mockStr, testCase.mockErr)
reader := reader{
env: env,
}
protocol, err := readProtocol(env)
tcp, err := readOpenVPNProtocol(reader)
if testCase.err != nil {
require.Error(t, err)
@@ -257,7 +456,7 @@ func Test_readProtocol(t *testing.T) {
assert.NoError(t, err)
}
assert.Equal(t, testCase.protocol, protocol)
assert.Equal(t, testCase.tcp, tcp)
})
}
}

View File

@@ -1,6 +1,7 @@
package configuration
import (
"fmt"
"strings"
"time"
@@ -32,14 +33,14 @@ func (settings *PublicIP) lines() (lines []string) {
func (settings *PublicIP) read(r reader) (err error) {
settings.Period, err = r.env.Duration("PUBLICIP_PERIOD", params.Default("12h"))
if err != nil {
return err
return fmt.Errorf("environment variable PUBLICIP_PERIOD: %w", err)
}
settings.IPFilepath, err = r.env.Path("PUBLICIP_FILE", params.CaseSensitiveValue(),
params.Default("/tmp/gluetun/ip"),
params.RetroKeys([]string{"IP_STATUS_FILE"}, r.onRetroActive))
if err != nil {
return err
return fmt.Errorf("environment variable PUBLICIP_FILE (or IP_STATUS_FILE): %w", err)
}
return nil

View File

@@ -1,52 +1,39 @@
package configuration
import (
"fmt"
"github.com/qdm12/gluetun/internal/constants"
)
func (settings *Provider) purevpnLines() (lines []string) {
if len(settings.ServerSelection.Regions) > 0 {
lines = append(lines, lastIndent+"Regions: "+commaJoin(settings.ServerSelection.Regions))
}
if len(settings.ServerSelection.Countries) > 0 {
lines = append(lines, lastIndent+"Countries: "+commaJoin(settings.ServerSelection.Countries))
}
if len(settings.ServerSelection.Cities) > 0 {
lines = append(lines, lastIndent+"Cities: "+commaJoin(settings.ServerSelection.Cities))
}
return lines
}
func (settings *Provider) readPurevpn(r reader) (err error) {
settings.Name = constants.Purevpn
settings.ServerSelection.Protocol, err = readProtocol(r.env)
if err != nil {
return err
}
servers := r.servers.GetPurevpn()
settings.ServerSelection.TargetIP, err = readTargetIP(r.env)
if err != nil {
return err
}
settings.ServerSelection.Regions, err = r.env.CSVInside("REGION", constants.PurevpnRegionChoices())
settings.ServerSelection.Regions, err = r.env.CSVInside("REGION", constants.PurevpnRegionChoices(servers))
if err != nil {
return err
return fmt.Errorf("environment variable REGION: %w", err)
}
settings.ServerSelection.Countries, err = r.env.CSVInside("COUNTRY", constants.PurevpnCountryChoices())
settings.ServerSelection.Countries, err = r.env.CSVInside("COUNTRY", constants.PurevpnCountryChoices(servers))
if err != nil {
return err
return fmt.Errorf("environment variable COUNTRY: %w", err)
}
settings.ServerSelection.Cities, err = r.env.CSVInside("CITY", constants.PurevpnCityChoices())
settings.ServerSelection.Cities, err = r.env.CSVInside("CITY", constants.PurevpnCityChoices(servers))
if err != nil {
return err
return fmt.Errorf("environment variable CITY: %w", err)
}
return nil
settings.ServerSelection.Hostnames, err = r.env.CSVInside("SERVER_HOSTNAME", constants.PurevpnHostnameChoices(servers))
if err != nil {
return fmt.Errorf("environment variable SERVER_HOSTNAME: %w", err)
}
return settings.ServerSelection.OpenVPN.readProtocolOnly(r)
}

View File

@@ -7,44 +7,52 @@ import (
"strconv"
"strings"
"github.com/qdm12/golibs/logging"
"github.com/qdm12/golibs/os"
"github.com/qdm12/gluetun/internal/models"
ovpnextract "github.com/qdm12/gluetun/internal/openvpn/extract"
"github.com/qdm12/golibs/params"
"github.com/qdm12/golibs/verification"
)
//go:generate mockgen -destination=warner_mock_test.go -package configuration . Warner
type reader struct {
env params.Env
logger logging.Logger
regex verification.Regex
os os.OS
servers models.AllServers
env params.Interface
warner Warner
regex verification.Regex
ovpnExt ovpnextract.Interface
}
func newReader(env params.Env, os os.OS, logger logging.Logger) reader {
type Warner interface {
Warn(s string)
}
func newReader(env params.Interface,
servers models.AllServers, warner Warner) reader {
return reader{
env: env,
logger: logger,
regex: verification.NewRegex(),
os: os,
servers: servers,
env: env,
warner: warner,
regex: verification.NewRegex(),
ovpnExt: ovpnextract.New(),
}
}
func (r *reader) onRetroActive(oldKey, newKey string) {
r.logger.Warn(
"You are using the old environment variable %s, please consider changing it to %s",
oldKey, newKey,
)
r.warner.Warn(
"You are using the old environment variable " + oldKey +
", please consider changing it to " + newKey)
}
var (
ErrInvalidPort = errors.New("invalid port")
)
func readCSVPorts(env params.Env, key string) (ports []uint16, err error) {
func readCSVPorts(env params.Interface, key string) (ports []uint16, err error) {
s, err := env.Get(key)
if err != nil {
return nil, err
} else if len(s) == 0 {
} else if s == "" {
return nil, nil
}
@@ -53,11 +61,9 @@ func readCSVPorts(env params.Env, key string) (ports []uint16, err error) {
for i, portStr := range portsStr {
portInt, err := strconv.Atoi(portStr)
if err != nil {
return nil, fmt.Errorf("%w: %q from environment variable %s: %s",
ErrInvalidPort, portStr, key, err)
return nil, fmt.Errorf("%w: %s: %s", ErrInvalidPort, portStr, err)
} else if portInt <= 0 || portInt > 65535 {
return nil, fmt.Errorf("%w: %d from environment variable %s: must be between 1 and 65535",
ErrInvalidPort, portInt, key)
return nil, fmt.Errorf("%w: %d: must be between 1 and 65535", ErrInvalidPort, portInt)
}
ports[i] = uint16(portInt)
}
@@ -69,7 +75,7 @@ var (
ErrInvalidIPNet = errors.New("invalid IP network")
)
func readCSVIPNets(env params.Env, key string, options ...params.OptionSetter) (
func readCSVIPNets(env params.Interface, key string, options ...params.OptionSetter) (
ipNets []net.IPNet, err error) {
s, err := env.Get(key, options...)
if err != nil {
@@ -83,11 +89,10 @@ func readCSVIPNets(env params.Env, key string, options ...params.OptionSetter) (
for i, ipNetStr := range ipNetsStr {
_, ipNet, err := net.ParseCIDR(ipNetStr)
if err != nil {
return nil, fmt.Errorf("%w: %q from environment variable %s: %s",
ErrInvalidIPNet, ipNetStr, key, err)
return nil, fmt.Errorf("%w: %s: %s",
ErrInvalidIPNet, ipNetStr, err)
} else if ipNet == nil {
return nil, fmt.Errorf("%w: %q from environment variable %s: subnet is nil",
ErrInvalidIPNet, ipNetStr, key)
return nil, fmt.Errorf("%w: %s: subnet is nil", ErrInvalidIPNet, ipNetStr)
}
ipNets[i] = *ipNet
}
@@ -99,9 +104,9 @@ var (
ErrInvalidIP = errors.New("invalid IP address")
)
func readIP(env params.Env, key string) (ip net.IP, err error) {
func readIP(env params.Interface, key string) (ip net.IP, err error) {
s, err := env.Get(key)
if len(s) == 0 {
if s == "" {
return nil, nil
} else if err != nil {
return nil, err
@@ -115,13 +120,13 @@ func readIP(env params.Env, key string) (ip net.IP, err error) {
return ip, nil
}
func readPortOrZero(env params.Env, key string) (port uint16, err error) {
s, err := env.Get(key)
func readPortOrZero(env params.Interface, key string) (port uint16, err error) {
s, err := env.Get(key, params.Default("0"))
if err != nil {
return 0, err
}
if len(s) == 0 || s == "0" {
if s == "0" {
return 0, nil
}

View File

@@ -3,10 +3,10 @@ package configuration
import (
"errors"
"fmt"
"io/ioutil"
"io"
"os"
"strings"
"github.com/qdm12/golibs/os"
"github.com/qdm12/golibs/params"
)
@@ -18,6 +18,12 @@ var (
ErrFilesDoNotExist = errors.New("files do not exist")
)
func cleanSuffix(value string) string {
value = strings.TrimSuffix(value, "\n")
value = strings.TrimSuffix(value, "\r")
return value
}
func (r *reader) getFromEnvOrSecretFile(envKey string, compulsory bool, retroKeys []string) (value string, err error) {
envOptions := []params.OptionSetter{
params.Compulsory(), // to fallback on file reading
@@ -27,37 +33,40 @@ func (r *reader) getFromEnvOrSecretFile(envKey string, compulsory bool, retroKey
}
value, envErr := r.env.Get(envKey, envOptions...)
if envErr == nil {
value = cleanSuffix(value)
return value, nil
}
secretFilepathEnvKey := envKey + "_SECRETFILE"
defaultSecretFile := "/run/secrets/" + strings.ToLower(envKey)
filepath, err := r.env.Get(envKey+"_SECRETFILE",
filepath, err := r.env.Get(secretFilepathEnvKey,
params.CaseSensitiveValue(),
params.Default(defaultSecretFile),
)
if err != nil {
return "", fmt.Errorf("%w: %s", ErrGetSecretFilepath, err)
return "", fmt.Errorf("%w: environment variable %s: %s",
ErrGetSecretFilepath, secretFilepathEnvKey, err)
}
file, fileErr := r.os.OpenFile(filepath, os.O_RDONLY, 0)
file, fileErr := os.OpenFile(filepath, os.O_RDONLY, 0)
if os.IsNotExist(fileErr) {
if compulsory {
return "", envErr
return "", fmt.Errorf("environment variable %s: %w", envKey, envErr)
}
return "", nil
} else if fileErr != nil {
return "", fmt.Errorf("%w: %s", ErrReadSecretFile, fileErr)
return "", fmt.Errorf("%w: %s: %s", ErrReadSecretFile, filepath, fileErr)
}
b, err := ioutil.ReadAll(file)
b, err := io.ReadAll(file)
if err != nil {
return "", fmt.Errorf("%w: %s", ErrReadSecretFile, err)
return "", fmt.Errorf("%w: %s: %s", ErrReadSecretFile, filepath, err)
}
value = string(b)
value = strings.TrimSuffix(value, "\n")
if compulsory && len(value) == 0 {
return "", ErrSecretFileIsEmpty
value = cleanSuffix(value)
if compulsory && value == "" {
return "", fmt.Errorf("%s: %w", filepath, ErrSecretFileIsEmpty)
}
return value, nil
@@ -67,15 +76,16 @@ func (r *reader) getFromEnvOrSecretFile(envKey string, compulsory bool, retroKey
func (r *reader) getFromFileOrSecretFile(secretName, filepath string) (
b []byte, err error) {
defaultSecretFile := "/run/secrets/" + strings.ToLower(secretName)
secretFilepath, err := r.env.Get(strings.ToUpper(secretName)+"_SECRETFILE",
key := strings.ToUpper(secretName) + "_SECRETFILE"
secretFilepath, err := r.env.Get(key,
params.CaseSensitiveValue(),
params.Default(defaultSecretFile),
)
if err != nil {
return b, fmt.Errorf("%w: %s", ErrGetSecretFilepath, err)
return b, fmt.Errorf("environment variable %s: %w: %s", key, ErrGetSecretFilepath, err)
}
b, err = readFromFile(r.os.OpenFile, secretFilepath)
b, err = readFromFile(secretFilepath)
if err != nil && !os.IsNotExist(err) {
return b, fmt.Errorf("%w: %s", ErrReadSecretFile, err)
} else if err == nil {
@@ -83,7 +93,7 @@ func (r *reader) getFromFileOrSecretFile(secretName, filepath string) (
}
// Secret file does not exist, try the non secret file
b, err = readFromFile(r.os.OpenFile, filepath)
b, err = readFromFile(filepath)
if err != nil && !os.IsNotExist(err) {
return nil, fmt.Errorf("%w: %s", ErrReadSecretFile, err)
} else if err == nil {
@@ -92,12 +102,12 @@ func (r *reader) getFromFileOrSecretFile(secretName, filepath string) (
return nil, fmt.Errorf("%w: %s and %s", ErrFilesDoNotExist, secretFilepath, filepath)
}
func readFromFile(openFile os.OpenFileFunc, filepath string) (b []byte, err error) {
file, err := openFile(filepath, os.O_RDONLY, 0)
func readFromFile(filepath string) (b []byte, err error) {
file, err := os.Open(filepath)
if err != nil {
return nil, err
}
b, err = ioutil.ReadAll(file)
b, err = io.ReadAll(file)
if err != nil {
_ = file.Close()
return nil, err

View File

@@ -1,43 +1,179 @@
package configuration
import (
"fmt"
"net"
"github.com/qdm12/gluetun/internal/constants"
)
type ServerSelection struct {
type ServerSelection struct { //nolint:maligned
// Common
Protocol string `json:"network_protocol"`
VPN string `json:"vpn"` // note: this is required
TargetIP net.IP `json:"target_ip,omitempty"`
// TODO comments
// Cyberghost, PIA, Surfshark, Windscribe, Vyprvpn, NordVPN
// Cyberghost, PIA, Protonvpn, Surfshark, Windscribe, Vyprvpn, NordVPN
Regions []string `json:"regions"`
// Cyberghost
Group string `json:"group"`
Countries []string `json:"countries"` // Mullvad, PureVPN
Cities []string `json:"cities"` // Mullvad, PureVPN, Windscribe
Hostnames []string `json:"hostnames"` // Windscribe, Privado
// Expressvpn, Fastestvpn, HideMyAss, IPVanish, IVPN, Mullvad, PrivateVPN, Protonvpn, PureVPN, VPNUnlimited
Countries []string `json:"countries"`
// Expressvpn, HideMyAss, IPVanish, IVPN, Mullvad, Perfectprivacy, PrivateVPN, Protonvpn,
// PureVPN, VPNUnlimited, WeVPN, Windscribe
Cities []string `json:"cities"`
// Expressvpn, Fastestvpn, HideMyAss, IPVanish, IVPN, PrivateVPN, Windscribe, Privado, Protonvpn, VPNUnlimited, WeVPN
Hostnames []string `json:"hostnames"`
Names []string `json:"names"` // Protonvpn
// Mullvad
ISPs []string `json:"isps"`
Owned bool `json:"owned"`
// Mullvad, Windscribe, PIA
CustomPort uint16 `json:"custom_port"`
// NordVPN
Numbers []uint16 `json:"numbers"`
// PIA
EncryptionPreset string `json:"encryption_preset"`
// ProtonVPN
FreeOnly bool `json:"free_only"`
// VPNUnlimited
StreamOnly bool `json:"stream_only"`
// Surfshark
MultiHopOnly bool `json:"multihop_only"`
OpenVPN OpenVPNSelection `json:"openvpn"`
Wireguard WireguardSelection `json:"wireguard"`
}
type ExtraConfigOptions struct {
ClientCertificate string `json:"-"` // Cyberghost
ClientKey string `json:"-"` // Cyberghost
EncryptionPreset string `json:"encryption_preset"` // PIA
OpenVPNIPv6 bool `json:"openvpn_ipv6"` // Mullvad
func (selection ServerSelection) toLines() (lines []string) {
if selection.TargetIP != nil {
lines = append(lines, lastIndent+"Target IP address: "+selection.TargetIP.String())
}
if len(selection.Countries) > 0 {
lines = append(lines, lastIndent+"Countries: "+commaJoin(selection.Countries))
}
if len(selection.Regions) > 0 {
lines = append(lines, lastIndent+"Regions: "+commaJoin(selection.Regions))
}
if len(selection.Cities) > 0 {
lines = append(lines, lastIndent+"Cities: "+commaJoin(selection.Cities))
}
if len(selection.ISPs) > 0 {
lines = append(lines, lastIndent+"ISPs: "+commaJoin(selection.ISPs))
}
if selection.FreeOnly {
lines = append(lines, lastIndent+"Free servers only")
}
if selection.StreamOnly {
lines = append(lines, lastIndent+"Stream servers only")
}
if len(selection.Hostnames) > 0 {
lines = append(lines, lastIndent+"Hostnames: "+commaJoin(selection.Hostnames))
}
if len(selection.Names) > 0 {
lines = append(lines, lastIndent+"Names: "+commaJoin(selection.Names))
}
if len(selection.Numbers) > 0 {
numbersString := make([]string, len(selection.Numbers))
for i, numberUint16 := range selection.Numbers {
numbersString[i] = fmt.Sprint(numberUint16)
}
lines = append(lines, lastIndent+"Numbers: "+commaJoin(numbersString))
}
if selection.VPN == constants.OpenVPN {
lines = append(lines, selection.OpenVPN.lines()...)
} else { // wireguard
lines = append(lines, selection.Wireguard.lines()...)
}
return lines
}
type OpenVPNSelection struct {
ConfFile string `json:"conf_file"` // Custom configuration file path
TCP bool `json:"tcp"` // UDP if TCP is false
CustomPort uint16 `json:"custom_port"` // HideMyAss, Mullvad, PIA, ProtonVPN, WeVPN, Windscribe
EncPreset string `json:"encryption_preset"` // PIA - needed to get the port number
}
func (settings *OpenVPNSelection) lines() (lines []string) {
lines = append(lines, lastIndent+"OpenVPN selection:")
if settings.ConfFile != "" {
lines = append(lines, indent+lastIndent+"Custom configuration file: "+settings.ConfFile)
}
lines = append(lines, indent+lastIndent+"Protocol: "+protoToString(settings.TCP))
if settings.CustomPort != 0 {
lines = append(lines, indent+lastIndent+"Custom port: "+fmt.Sprint(settings.CustomPort))
}
if settings.EncPreset != "" {
lines = append(lines, indent+lastIndent+"PIA encryption preset: "+settings.EncPreset)
}
return lines
}
func (settings *OpenVPNSelection) readProtocolOnly(r reader) (err error) {
settings.TCP, err = readOpenVPNProtocol(r)
return err
}
func (settings *OpenVPNSelection) readProtocolAndPort(r reader) (err error) {
settings.TCP, err = readOpenVPNProtocol(r)
if err != nil {
return err
}
settings.CustomPort, err = readOpenVPNCustomPort(r, openvpnPortValidation{allAllowed: true})
if err != nil {
return err
}
return nil
}
type WireguardSelection struct {
// EndpointPort is a the server port to use for the VPN server.
// It is optional for Wireguard VPN providers IVPN, Mullvad
// and Windscribe, and compulsory for the others
EndpointPort uint16 `json:"port,omitempty"`
// PublicKey is the server public key.
// It is only used with VPN providers generating Wireguard
// configurations specific to each server and user.
PublicKey string `json:"publickey,omitempty"`
// EndpointIP is the server endpoint IP address.
// It is only used with VPN providers generating Wireguard
// configurations specific to each server and user.
EndpointIP net.IP `json:"endpoint_ip,omitempty"`
}
func (settings *WireguardSelection) lines() (lines []string) {
lines = append(lines, lastIndent+"Wireguard selection:")
if settings.PublicKey != "" {
lines = append(lines, indent+lastIndent+"Public key: "+settings.PublicKey)
}
if settings.EndpointIP != nil {
endpoint := settings.EndpointIP.String() + ":" + fmt.Sprint(settings.EndpointPort)
lines = append(lines, indent+lastIndent+"Server endpoint: "+endpoint)
} else if settings.EndpointPort != 0 {
lines = append(lines, indent+lastIndent+"Custom port: "+fmt.Sprint(settings.EndpointPort))
}
return lines
}
// PortForwarding contains settings for port forwarding.

View File

@@ -1,6 +1,7 @@
package configuration
import (
"fmt"
"strconv"
"strings"
@@ -32,17 +33,17 @@ func (settings *ControlServer) lines() (lines []string) {
func (settings *ControlServer) read(r reader) (err error) {
settings.Log, err = r.env.OnOff("HTTP_CONTROL_SERVER_LOG", params.Default("on"))
if err != nil {
return err
return fmt.Errorf("environment variable HTTP_CONTROL_SERVER_LOG: %w", err)
}
var warning string
settings.Port, warning, err = r.env.ListeningPort(
"HTTP_CONTROL_SERVER_PORT", params.Default("8000"))
if len(warning) > 0 {
r.logger.Warn(warning)
r.warner.Warn(warning)
}
if err != nil {
return err
return fmt.Errorf("environment variable HTTP_CONTROL_SERVER_PORT: %w", err)
}
return nil

View File

@@ -1,16 +1,17 @@
package configuration
import (
"errors"
"fmt"
"strings"
"github.com/qdm12/golibs/logging"
"github.com/qdm12/golibs/os"
"github.com/qdm12/gluetun/internal/models"
"github.com/qdm12/golibs/params"
)
// Settings contains all settings for the program to run.
type Settings struct {
OpenVPN OpenVPN
VPN VPN
System System
DNS DNS
Firewall Firewall
@@ -20,6 +21,8 @@ type Settings struct {
PublicIP PublicIP
VersionInformation bool
ControlServer ControlServer
Health Health
Log Log
}
func (settings *Settings) String() string {
@@ -28,12 +31,14 @@ func (settings *Settings) String() string {
func (settings *Settings) lines() (lines []string) {
lines = append(lines, "Settings summary below:")
lines = append(lines, settings.OpenVPN.lines()...)
lines = append(lines, settings.VPN.lines()...)
lines = append(lines, settings.DNS.lines()...)
lines = append(lines, settings.Firewall.lines()...)
lines = append(lines, settings.Log.lines()...)
lines = append(lines, settings.System.lines()...)
lines = append(lines, settings.HTTPProxy.lines()...)
lines = append(lines, settings.ShadowSocks.lines()...)
lines = append(lines, settings.Health.lines()...)
lines = append(lines, settings.ControlServer.lines()...)
lines = append(lines, settings.Updater.lines()...)
lines = append(lines, settings.PublicIP.lines()...)
@@ -43,46 +48,61 @@ func (settings *Settings) lines() (lines []string) {
return lines
}
var (
ErrVPN = errors.New("cannot read VPN settings")
ErrSystem = errors.New("cannot read System settings")
ErrDNS = errors.New("cannot read DNS settings")
ErrFirewall = errors.New("cannot read firewall settings")
ErrHTTPProxy = errors.New("cannot read HTTP proxy settings")
ErrShadowsocks = errors.New("cannot read Shadowsocks settings")
ErrControlServer = errors.New("cannot read control server settings")
ErrUpdater = errors.New("cannot read Updater settings")
ErrPublicIP = errors.New("cannot read Public IP getter settings")
ErrHealth = errors.New("cannot read health settings")
ErrLog = errors.New("cannot read log settings")
)
// Read obtains all configuration options for the program and returns an error as soon
// as an error is encountered reading them.
func (settings *Settings) Read(env params.Env, os os.OS, logger logging.Logger) (err error) {
r := newReader(env, os, logger)
func (settings *Settings) Read(env params.Interface, servers models.AllServers,
warner Warner) (err error) {
r := newReader(env, servers, warner)
settings.VersionInformation, err = r.env.OnOff("VERSION_INFORMATION", params.Default("on"))
if err != nil {
return err
return fmt.Errorf("environment variable VERSION_INFORMATION: %w", err)
}
if err := settings.OpenVPN.read(r); err != nil {
return err
if err := settings.VPN.read(r); err != nil {
return fmt.Errorf("%w: %s", ErrVPN, err)
}
if err := settings.System.read(r); err != nil {
return err
return fmt.Errorf("%w: %s", ErrSystem, err)
}
if err := settings.DNS.read(r); err != nil {
return err
return fmt.Errorf("%w: %s", ErrDNS, err)
}
if err := settings.Firewall.read(r); err != nil {
return err
return fmt.Errorf("%w: %s", ErrFirewall, err)
}
if err := settings.HTTPProxy.read(r); err != nil {
return err
return fmt.Errorf("%w: %s", ErrHTTPProxy, err)
}
if err := settings.ShadowSocks.read(r); err != nil {
return err
return fmt.Errorf("%w: %s", ErrShadowsocks, err)
}
if err := settings.ControlServer.read(r); err != nil {
return err
return fmt.Errorf("%w: %s", ErrControlServer, err)
}
if err := settings.Updater.read(r); err != nil {
return err
return fmt.Errorf("%w: %s", ErrUpdater, err)
}
if ip := settings.DNS.PlaintextAddress; ip != nil {
@@ -90,7 +110,15 @@ func (settings *Settings) Read(env params.Env, os os.OS, logger logging.Logger)
}
if err := settings.PublicIP.read(r); err != nil {
return err
return fmt.Errorf("%w: %s", ErrPublicIP, err)
}
if err := settings.Health.read(r); err != nil {
return fmt.Errorf("%w: %s", ErrHealth, err)
}
if err := settings.Log.read(r.env); err != nil {
return fmt.Errorf("%w: %s", ErrLog, err)
}
return nil

View File

@@ -16,25 +16,44 @@ func Test_Settings_lines(t *testing.T) {
}{
"default settings": {
settings: Settings{
OpenVPN: OpenVPN{
VPN: VPN{
Type: constants.OpenVPN,
Provider: Provider{
Name: constants.Mullvad,
ServerSelection: ServerSelection{
VPN: constants.OpenVPN,
},
},
OpenVPN: OpenVPN{
Version: constants.Openvpn25,
Interface: "tun",
},
},
},
lines: []string{
"Settings summary below:",
"|--OpenVPN:",
" |--Verbosity level: 0",
" |--Provider:",
" |--Mullvad settings:",
" |--Network protocol: ",
"|--VPN:",
" |--Type: openvpn",
" |--OpenVPN:",
" |--Version: 2.5",
" |--Verbosity level: 0",
" |--Network interface: tun",
" |--Mullvad settings:",
" |--OpenVPN selection:",
" |--Protocol: udp",
"|--DNS:",
"|--Firewall: disabled ⚠️",
"|--Log:",
" |--Level: DEBUG",
"|--System:",
" |--Process user ID: 0",
" |--Process group ID: 0",
" |--Timezone: NOT SET ⚠️ - it can cause time related issues",
"|--Health:",
" |--Server address: ",
" |--Address to ping: ",
" |--VPN:",
" |--Initial duration: 0s",
"|--HTTP control server:",
" |--Listening port: 0",
"|--Public IP getter: disabled",

View File

@@ -1,19 +1,17 @@
package configuration
import (
"strconv"
"fmt"
"strings"
"github.com/qdm12/golibs/params"
"github.com/qdm12/ss-server/pkg/tcpudp"
)
// ShadowSocks contains settings to configure the Shadowsocks server.
type ShadowSocks struct {
Method string
Password string
Port uint16
Enabled bool
Log bool
Enabled bool
tcpudp.Settings
}
func (settings *ShadowSocks) String() string {
@@ -27,12 +25,12 @@ func (settings *ShadowSocks) lines() (lines []string) {
lines = append(lines, lastIndent+"Shadowsocks server:")
lines = append(lines, indent+lastIndent+"Listening port: "+strconv.Itoa(int(settings.Port)))
lines = append(lines, indent+lastIndent+"Listening address: "+settings.Address)
lines = append(lines, indent+lastIndent+"Method: "+settings.Method)
lines = append(lines, indent+lastIndent+"Cipher: "+settings.CipherName)
if settings.Log {
lines = append(lines, indent+lastIndent+"Logging: enabled")
if settings.LogAddresses {
lines = append(lines, indent+lastIndent+"Log addresses: enabled")
}
return lines
@@ -40,29 +38,31 @@ func (settings *ShadowSocks) lines() (lines []string) {
func (settings *ShadowSocks) read(r reader) (err error) {
settings.Enabled, err = r.env.OnOff("SHADOWSOCKS", params.Default("off"))
if err != nil || !settings.Enabled {
return err
if !settings.Enabled {
return nil
} else if err != nil {
return fmt.Errorf("environment variable SHADOWSOCKS: %w", err)
}
settings.Password, err = r.getFromEnvOrSecretFile("SHADOWSOCKS_PASSWORD", false, nil)
settings.Password, err = r.getFromEnvOrSecretFile("SHADOWSOCKS_PASSWORD", settings.Enabled, nil)
if err != nil {
return err
}
settings.Log, err = r.env.OnOff("SHADOWSOCKS_LOG", params.Default("off"))
settings.LogAddresses, err = r.env.OnOff("SHADOWSOCKS_LOG", params.Default("off"))
if err != nil {
return err
return fmt.Errorf("environment variable SHADOWSOCKS_LOG: %w", err)
}
settings.Method, err = r.env.Get("SHADOWSOCKS_METHOD", params.Default("chacha20-ietf-poly1305"))
settings.CipherName, err = r.env.Get("SHADOWSOCKS_CIPHER", params.Default("chacha20-ietf-poly1305"),
params.RetroKeys([]string{"SHADOWSOCKS_METHOD"}, r.onRetroActive))
if err != nil {
return err
return fmt.Errorf("environment variable SHADOWSOCKS_CIPHER (or SHADOWSOCKS_METHOD): %w", err)
}
var warning string
settings.Port, warning, err = r.env.ListeningPort("SHADOWSOCKS_PORT", params.Default("8388"))
if len(warning) > 0 {
r.logger.Warn(warning)
warning, err := settings.getAddress(r.env)
if warning != "" {
r.warner.Warn(warning)
}
if err != nil {
return err
@@ -70,3 +70,40 @@ func (settings *ShadowSocks) read(r reader) (err error) {
return nil
}
func (settings *ShadowSocks) getAddress(env params.Interface) (
warning string, err error) {
address, err := env.Get("SHADOWSOCKS_LISTENING_ADDRESS")
if err != nil {
return "", fmt.Errorf("environment variable SHADOWSOCKS_LISTENING_ADDRESS: %w", err)
}
if address != "" {
address, warning, err := env.ListeningAddress("SHADOWSOCKS_LISTENING_ADDRESS")
if err != nil {
return "", fmt.Errorf("environment variable SHADOWSOCKS_LISTENING_ADDRESS: %w", err)
}
settings.Address = address
return warning, nil
}
// Retro-compatibility
const retroWarning = "You are using the old environment variable " +
"SHADOWSOCKS_PORT, please consider using " +
"SHADOWSOCKS_LISTENING_ADDRESS instead"
portStr, err := env.Get("SHADOWSOCKS_PORT")
if err != nil {
return retroWarning, fmt.Errorf("environment variable SHADOWSOCKS_PORT: %w", err)
} else if portStr != "" {
port, _, err := env.ListeningPort("SHADOWSOCKS_PORT")
if err != nil {
return retroWarning, fmt.Errorf("environment variable SHADOWSOCKS_PORT: %w", err)
}
settings.Address = ":" + fmt.Sprint(port)
return retroWarning, nil
}
// Default value
settings.Address = ":8388"
return "", nil
}

View File

@@ -1,34 +1,102 @@
package configuration
import (
"fmt"
"strings"
"github.com/qdm12/gluetun/internal/constants"
"github.com/qdm12/gluetun/internal/models"
"github.com/qdm12/golibs/params"
)
func (settings *Provider) surfsharkLines() (lines []string) {
if len(settings.ServerSelection.Regions) > 0 {
lines = append(lines, lastIndent+"Regions: "+commaJoin(settings.ServerSelection.Regions))
}
return lines
}
func (settings *Provider) readSurfshark(r reader) (err error) {
settings.Name = constants.Surfshark
settings.ServerSelection.Protocol, err = readProtocol(r.env)
if err != nil {
return err
}
servers := r.servers.GetSurfshark()
settings.ServerSelection.TargetIP, err = readTargetIP(r.env)
if err != nil {
return err
}
settings.ServerSelection.Regions, err = r.env.CSVInside("REGION", constants.SurfsharkRegionChoices())
settings.ServerSelection.Countries, err = r.env.CSVInside("COUNTRY", constants.SurfsharkCountryChoices(servers))
if err != nil {
return err
return fmt.Errorf("environment variable COUNTRY: %w", err)
}
return nil
settings.ServerSelection.Cities, err = r.env.CSVInside("CITY", constants.SurfsharkCityChoices(servers))
if err != nil {
return fmt.Errorf("environment variable CITY: %w", err)
}
settings.ServerSelection.Hostnames, err = r.env.CSVInside("SERVER_HOSTNAME",
constants.SurfsharkHostnameChoices(servers))
if err != nil {
return fmt.Errorf("environment variable SERVER_HOSTNAME: %w", err)
}
regionChoices := constants.SurfsharkRegionChoices(servers)
regionChoices = append(regionChoices, constants.SurfsharkRetroLocChoices(servers)...)
settings.ServerSelection.Regions, err = r.env.CSVInside("REGION", regionChoices)
if err != nil {
return fmt.Errorf("environment variable REGION: %w", err)
}
// Retro compatibility
// TODO remove in v4
settings.ServerSelection = surfsharkRetroRegion(settings.ServerSelection)
settings.ServerSelection.MultiHopOnly, err = r.env.YesNo("MULTIHOP_ONLY", params.Default("no"))
if err != nil {
return fmt.Errorf("environment variable MULTIHOP_ONLY: %w", err)
}
return settings.ServerSelection.OpenVPN.readProtocolOnly(r)
}
func surfsharkRetroRegion(selection ServerSelection) (
updatedSelection ServerSelection) {
locationData := constants.SurfsharkLocationData()
retroToLocation := make(map[string]models.SurfsharkLocationData, len(locationData))
for _, data := range locationData {
if data.RetroLoc == "" {
continue
}
retroToLocation[strings.ToLower(data.RetroLoc)] = data
}
for i, region := range selection.Regions {
location, ok := retroToLocation[region]
if !ok {
continue
}
selection.Regions[i] = strings.ToLower(location.Region)
selection.Countries = append(selection.Countries, strings.ToLower(location.Country))
selection.Cities = append(selection.Cities, strings.ToLower(location.City)) // even empty string
selection.Hostnames = append(selection.Hostnames, location.Hostname)
}
selection.Regions = dedupSlice(selection.Regions)
selection.Countries = dedupSlice(selection.Countries)
selection.Cities = dedupSlice(selection.Cities)
selection.Hostnames = dedupSlice(selection.Hostnames)
return selection
}
func dedupSlice(slice []string) (deduped []string) {
if slice == nil {
return nil
}
deduped = make([]string, 0, len(slice))
seen := make(map[string]struct{}, len(slice))
for _, s := range slice {
if _, ok := seen[s]; !ok {
seen[s] = struct{}{}
deduped = append(deduped, s)
}
}
return deduped
}

View File

@@ -0,0 +1,305 @@
package configuration
import (
"errors"
"net"
"testing"
"github.com/golang/mock/gomock"
"github.com/qdm12/gluetun/internal/constants"
"github.com/qdm12/gluetun/internal/models"
"github.com/qdm12/golibs/params/mock_params"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func Test_Provider_readSurfshark(t *testing.T) {
t.Parallel()
var errDummy = errors.New("dummy test error")
type stringCall struct {
call bool
value string
err error
}
type boolCall struct {
call bool
value bool
err error
}
type sliceStringCall struct {
call bool
values []string
err error
}
testCases := map[string]struct {
targetIP stringCall
countries sliceStringCall
cities sliceStringCall
hostnames sliceStringCall
regions sliceStringCall
multiHop boolCall
protocol stringCall
settings Provider
err error
}{
"target IP error": {
targetIP: stringCall{call: true, value: "something", err: errDummy},
settings: Provider{
Name: constants.Surfshark,
},
err: errors.New("environment variable OPENVPN_TARGET_IP: dummy test error"),
},
"countries error": {
targetIP: stringCall{call: true},
countries: sliceStringCall{call: true, err: errDummy},
settings: Provider{
Name: constants.Surfshark,
},
err: errors.New("environment variable COUNTRY: dummy test error"),
},
"cities error": {
targetIP: stringCall{call: true},
countries: sliceStringCall{call: true},
cities: sliceStringCall{call: true, err: errDummy},
settings: Provider{
Name: constants.Surfshark,
},
err: errors.New("environment variable CITY: dummy test error"),
},
"hostnames error": {
targetIP: stringCall{call: true},
countries: sliceStringCall{call: true},
cities: sliceStringCall{call: true},
hostnames: sliceStringCall{call: true, err: errDummy},
settings: Provider{
Name: constants.Surfshark,
},
err: errors.New("environment variable SERVER_HOSTNAME: dummy test error"),
},
"regions error": {
targetIP: stringCall{call: true},
countries: sliceStringCall{call: true},
cities: sliceStringCall{call: true},
hostnames: sliceStringCall{call: true},
regions: sliceStringCall{call: true, err: errDummy},
settings: Provider{
Name: constants.Surfshark,
},
err: errors.New("environment variable REGION: dummy test error"),
},
"multi hop error": {
targetIP: stringCall{call: true},
countries: sliceStringCall{call: true},
cities: sliceStringCall{call: true},
hostnames: sliceStringCall{call: true},
regions: sliceStringCall{call: true},
multiHop: boolCall{call: true, err: errDummy},
settings: Provider{
Name: constants.Surfshark,
},
err: errors.New("environment variable MULTIHOP_ONLY: dummy test error"),
},
"openvpn protocol error": {
targetIP: stringCall{call: true},
countries: sliceStringCall{call: true},
cities: sliceStringCall{call: true},
hostnames: sliceStringCall{call: true},
regions: sliceStringCall{call: true},
multiHop: boolCall{call: true},
protocol: stringCall{call: true, err: errDummy},
settings: Provider{
Name: constants.Surfshark,
},
err: errors.New("environment variable OPENVPN_PROTOCOL: dummy test error"),
},
"default settings": {
targetIP: stringCall{call: true},
countries: sliceStringCall{call: true},
cities: sliceStringCall{call: true},
hostnames: sliceStringCall{call: true},
regions: sliceStringCall{call: true},
multiHop: boolCall{call: true},
protocol: stringCall{call: true},
settings: Provider{
Name: constants.Surfshark,
},
},
"set settings": {
targetIP: stringCall{call: true, value: "1.2.3.4"},
countries: sliceStringCall{call: true, values: []string{"A", "B"}},
cities: sliceStringCall{call: true, values: []string{"C", "D"}},
regions: sliceStringCall{call: true, values: []string{
"E", "F", "netherlands amsterdam",
}}, // Netherlands Amsterdam is for retro compatibility test
multiHop: boolCall{call: true},
hostnames: sliceStringCall{call: true, values: []string{"E", "F"}},
protocol: stringCall{call: true, value: constants.TCP},
settings: Provider{
Name: constants.Surfshark,
ServerSelection: ServerSelection{
OpenVPN: OpenVPNSelection{
TCP: true,
},
TargetIP: net.IPv4(1, 2, 3, 4),
Regions: []string{"E", "F", "europe"},
Countries: []string{"A", "B", "netherlands"},
Cities: []string{"C", "D", "amsterdam"},
Hostnames: []string{"E", "F", "nl-ams.prod.surfshark.com"},
},
},
},
"Netherlands Amsterdam": {
targetIP: stringCall{call: true},
countries: sliceStringCall{call: true},
cities: sliceStringCall{call: true},
regions: sliceStringCall{call: true, values: []string{"netherlands amsterdam"}},
multiHop: boolCall{call: true},
hostnames: sliceStringCall{call: true},
protocol: stringCall{call: true},
settings: Provider{
Name: constants.Surfshark,
ServerSelection: ServerSelection{
Regions: []string{"europe"},
Countries: []string{"netherlands"},
Cities: []string{"amsterdam"},
Hostnames: []string{"nl-ams.prod.surfshark.com"},
},
},
},
}
for name, testCase := range testCases {
testCase := testCase
t.Run(name, func(t *testing.T) {
t.Parallel()
ctrl := gomock.NewController(t)
env := mock_params.NewMockInterface(ctrl)
servers := []models.SurfsharkServer{{Hostname: "a"}}
allServers := models.AllServers{
Surfshark: models.SurfsharkServers{
Servers: servers,
},
}
if testCase.targetIP.call {
env.EXPECT().Get("OPENVPN_TARGET_IP").
Return(testCase.targetIP.value, testCase.targetIP.err)
}
if testCase.countries.call {
env.EXPECT().CSVInside("COUNTRY", constants.SurfsharkCountryChoices(servers)).
Return(testCase.countries.values, testCase.countries.err)
}
if testCase.cities.call {
env.EXPECT().CSVInside("CITY", constants.SurfsharkCityChoices(servers)).
Return(testCase.cities.values, testCase.cities.err)
}
if testCase.hostnames.call {
env.EXPECT().CSVInside("SERVER_HOSTNAME", constants.SurfsharkHostnameChoices(servers)).
Return(testCase.hostnames.values, testCase.hostnames.err)
}
if testCase.regions.call {
regionChoices := constants.SurfsharkRegionChoices(servers)
regionChoices = append(regionChoices, constants.SurfsharkRetroLocChoices(servers)...)
env.EXPECT().CSVInside("REGION", regionChoices).
Return(testCase.regions.values, testCase.regions.err)
}
if testCase.multiHop.call {
env.EXPECT().YesNo("MULTIHOP_ONLY", gomock.Any()).
Return(testCase.multiHop.value, testCase.multiHop.err)
}
if testCase.protocol.call {
env.EXPECT().Inside("OPENVPN_PROTOCOL", []string{constants.TCP, constants.UDP}, gomock.Any()).
Return(testCase.protocol.value, testCase.protocol.err)
}
r := reader{
servers: allServers,
env: env,
}
var settings Provider
err := settings.readSurfshark(r)
if testCase.err != nil {
require.Error(t, err)
assert.Equal(t, testCase.err.Error(), err.Error())
} else {
assert.NoError(t, err)
}
assert.Equal(t, testCase.settings, settings)
})
}
}
func Test_surfsharkRetroRegion(t *testing.T) {
t.Parallel()
testCases := map[string]struct {
original ServerSelection
modified ServerSelection
}{
"empty": {},
"1 retro region": {
original: ServerSelection{
Regions: []string{"australia adelaide"},
},
modified: ServerSelection{
Regions: []string{"asia pacific"},
Countries: []string{"australia"},
Cities: []string{"adelaide"},
Hostnames: []string{"au-adl.prod.surfshark.com"},
},
},
"2 overlapping retro regions": {
original: ServerSelection{
Regions: []string{"australia adelaide", "australia melbourne"},
},
modified: ServerSelection{
Regions: []string{"asia pacific"},
Countries: []string{"australia"},
Cities: []string{"adelaide", "melbourne"},
Hostnames: []string{"au-adl.prod.surfshark.com", "au-mel.prod.surfshark.com"},
},
},
"2 distinct retro regions": {
original: ServerSelection{
Regions: []string{"australia adelaide", "netherlands amsterdam"},
},
modified: ServerSelection{
Regions: []string{"asia pacific", "europe"},
Countries: []string{"australia", "netherlands"},
Cities: []string{"adelaide", "amsterdam"},
Hostnames: []string{"au-adl.prod.surfshark.com", "nl-ams.prod.surfshark.com"},
},
},
"retro region with existing region": {
// note TestRegion will be ignored in the filters downstream
original: ServerSelection{
Regions: []string{"TestRegion", "australia adelaide"},
},
modified: ServerSelection{
Regions: []string{"TestRegion", "asia pacific"},
Countries: []string{"australia"},
Cities: []string{"adelaide"},
Hostnames: []string{"au-adl.prod.surfshark.com"},
},
},
}
for name, testCase := range testCases {
testCase := testCase
t.Run(name, func(t *testing.T) {
t.Parallel()
selection := surfsharkRetroRegion(testCase.original)
assert.Equal(t, testCase.modified, selection)
})
}
}

View File

@@ -1,6 +1,7 @@
package configuration
import (
"fmt"
"strconv"
"strings"
@@ -32,21 +33,22 @@ func (settings *System) lines() (lines []string) {
}
func (settings *System) read(r reader) (err error) {
settings.PUID, err = r.env.IntRange("PUID", 0, 65535, params.Default("1000"),
const maxID = 65535
settings.PUID, err = r.env.IntRange("PUID", 0, maxID, params.Default("1000"),
params.RetroKeys([]string{"UID"}, r.onRetroActive))
if err != nil {
return err
return fmt.Errorf("environment variable PUID (or UID): %w", err)
}
settings.PGID, err = r.env.IntRange("PGID", 0, 65535, params.Default("1000"),
settings.PGID, err = r.env.IntRange("PGID", 0, maxID, params.Default("1000"),
params.RetroKeys([]string{"GID"}, r.onRetroActive))
if err != nil {
return err
return fmt.Errorf("environment variable PGID (or GID): %w", err)
}
settings.Timezone, err = r.env.Get("TZ")
if err != nil {
return err
return fmt.Errorf("environment variable TZ: %w", err)
}
return nil

View File

@@ -1,52 +1,35 @@
package configuration
import (
"fmt"
"github.com/qdm12/gluetun/internal/constants"
)
func (settings *Provider) torguardLines() (lines []string) {
if len(settings.ServerSelection.Countries) > 0 {
lines = append(lines, lastIndent+"Countries: "+commaJoin(settings.ServerSelection.Countries))
}
if len(settings.ServerSelection.Cities) > 0 {
lines = append(lines, lastIndent+"Cities: "+commaJoin(settings.ServerSelection.Cities))
}
if len(settings.ServerSelection.Hostnames) > 0 {
lines = append(lines, lastIndent+"Hostnames: "+commaJoin(settings.ServerSelection.Hostnames))
}
return lines
}
func (settings *Provider) readTorguard(r reader) (err error) {
settings.Name = constants.Torguard
settings.ServerSelection.Protocol, err = readProtocol(r.env)
if err != nil {
return err
}
servers := r.servers.GetTorguard()
settings.ServerSelection.TargetIP, err = readTargetIP(r.env)
if err != nil {
return err
}
settings.ServerSelection.Countries, err = r.env.CSVInside("COUNTRY", constants.TorguardCountryChoices())
settings.ServerSelection.Countries, err = r.env.CSVInside("COUNTRY", constants.TorguardCountryChoices(servers))
if err != nil {
return err
return fmt.Errorf("environment variable COUNTRY: %w", err)
}
settings.ServerSelection.Cities, err = r.env.CSVInside("CITY", constants.TorguardCityChoices())
settings.ServerSelection.Cities, err = r.env.CSVInside("CITY", constants.TorguardCityChoices(servers))
if err != nil {
return err
return fmt.Errorf("environment variable CITY: %w", err)
}
settings.ServerSelection.Hostnames, err = r.env.CSVInside("SERVER_HOSTNAME", constants.TorguardHostnamesChoices())
settings.ServerSelection.Hostnames, err = r.env.CSVInside("SERVER_HOSTNAME",
constants.TorguardHostnameChoices(servers))
if err != nil {
return err
return fmt.Errorf("environment variable SERVER_HOSTNAME: %w", err)
}
return nil
return settings.ServerSelection.OpenVPN.readProtocolAndPort(r)
}

View File

@@ -3,11 +3,11 @@ package configuration
import (
"errors"
"fmt"
"net"
"strings"
unbound "github.com/qdm12/dns/pkg/unbound"
"github.com/qdm12/dns/pkg/provider"
"github.com/qdm12/golibs/params"
"inet.af/netaddr"
)
func (settings *DNS) readUnbound(r reader) (err error) {
@@ -19,51 +19,37 @@ func (settings *DNS) readUnbound(r reader) (err error) {
settings.Unbound.Caching, err = r.env.OnOff("DOT_CACHING", params.Default("on"))
if err != nil {
return err
return fmt.Errorf("environment variable DOT_CACHING: %w", err)
}
settings.Unbound.IPv4 = true
settings.Unbound.IPv6, err = r.env.OnOff("DOT_IPV6", params.Default("off"))
if err != nil {
return err
return fmt.Errorf("environment variable DOT_IPV6: %w", err)
}
verbosityLevel, err := r.env.IntRange("DOT_VERBOSITY", 0, 5, params.Default("1"))
verbosityLevel, err := r.env.IntRange("DOT_VERBOSITY", 0, 5, params.Default("1")) //nolint:gomnd
if err != nil {
return err
return fmt.Errorf("environment variable DOT_VERBOSITY: %w", err)
}
settings.Unbound.VerbosityLevel = uint8(verbosityLevel)
verbosityDetailsLevel, err := r.env.IntRange("DOT_VERBOSITY_DETAILS", 0, 4, params.Default("0"))
verbosityDetailsLevel, err := r.env.IntRange("DOT_VERBOSITY_DETAILS", 0, 4, params.Default("0")) //nolint:gomnd
if err != nil {
return err
return fmt.Errorf("environment variable DOT_VERBOSITY_DETAILS: %w", err)
}
settings.Unbound.VerbosityDetailsLevel = uint8(verbosityDetailsLevel)
validationLogLevel, err := r.env.IntRange("DOT_VALIDATION_LOGLEVEL", 0, 2, params.Default("0"))
validationLogLevel, err := r.env.IntRange("DOT_VALIDATION_LOGLEVEL", 0, 2, params.Default("0")) //nolint:gomnd
if err != nil {
return err
return fmt.Errorf("environment variable DOT_VALIDATION_LOGLEVEL: %w", err)
}
settings.Unbound.ValidationLogLevel = uint8(validationLogLevel)
if err := settings.readUnboundPrivateAddresses(r.env); err != nil {
return err
}
if err := settings.readUnboundUnblockedHostnames(r); err != nil {
return err
}
settings.Unbound.AccessControl.Allowed = []net.IPNet{
{
IP: net.IPv4zero,
Mask: net.IPv4Mask(0, 0, 0, 0),
},
{
IP: net.IPv6zero,
Mask: net.IPMask{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
},
settings.Unbound.AccessControl.Allowed = []netaddr.IPPrefix{
netaddr.IPPrefixFrom(netaddr.IPv4(0, 0, 0, 0), 0),
netaddr.IPPrefixFrom(netaddr.IPv6Raw([16]byte{}), 0),
}
return nil
@@ -73,61 +59,21 @@ var (
ErrInvalidDNSOverTLSProvider = errors.New("invalid DNS over TLS provider")
)
func (settings *DNS) readUnboundProviders(env params.Env) (err error) {
func (settings *DNS) readUnboundProviders(env params.Interface) (err error) {
s, err := env.Get("DOT_PROVIDERS", params.Default("cloudflare"))
if err != nil {
return err
return fmt.Errorf("environment variable DOT_PROVIDERS: %w", err)
}
for _, provider := range strings.Split(s, ",") {
_, ok := unbound.GetProviderData(provider)
if !ok {
return fmt.Errorf("%w: %s", ErrInvalidDNSOverTLSProvider, provider)
for _, field := range strings.Split(s, ",") {
dnsProvider, err := provider.Parse(field)
if err != nil {
return fmt.Errorf("%w: %s", ErrInvalidDNSOverTLSProvider, err)
}
settings.Unbound.Providers = append(settings.Unbound.Providers, provider)
settings.Unbound.Providers = append(settings.Unbound.Providers, dnsProvider)
}
return nil
}
var (
ErrInvalidPrivateAddress = errors.New("private address is not a valid IP or CIDR range")
)
func (settings *DNS) readUnboundPrivateAddresses(env params.Env) (err error) {
privateAddresses, err := env.CSV("DOT_PRIVATE_ADDRESS")
if err != nil {
return err
} else if len(privateAddresses) == 0 {
return nil
}
for _, address := range privateAddresses {
ip := net.ParseIP(address)
_, _, err := net.ParseCIDR(address)
if ip == nil && err != nil {
return fmt.Errorf("%w: %s", ErrInvalidPrivateAddress, address)
}
}
settings.Unbound.BlockedIPs = append(
settings.Unbound.BlockedIPs, privateAddresses...)
return nil
}
var (
ErrInvalidHostname = errors.New("invalid hostname")
)
func (settings *DNS) readUnboundUnblockedHostnames(r reader) (err error) {
hostnames, err := r.env.CSV("UNBLOCK")
if err != nil {
return err
} else if len(hostnames) == 0 {
return nil
}
for _, hostname := range hostnames {
if !r.regex.MatchHostname(hostname) {
return fmt.Errorf("%w: %s", ErrInvalidHostname, hostname)
}
}
settings.Unbound.AllowedHostnames = append(
settings.Unbound.AllowedHostnames, hostnames...)
return nil
}

View File

@@ -0,0 +1,80 @@
package configuration
import (
"errors"
"testing"
"github.com/golang/mock/gomock"
"github.com/qdm12/dns/pkg/provider"
"github.com/qdm12/dns/pkg/unbound"
"github.com/qdm12/golibs/params/mock_params"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func Test_DNS_readUnboundProviders(t *testing.T) {
t.Parallel()
testCases := map[string]struct {
envValue string
envErr error
expected DNS
err error
}{
"bad value": {
envValue: "invalid",
err: errors.New(`invalid DNS over TLS provider: cannot parse provider: "invalid"`),
},
"env error": {
envErr: errors.New("env error"),
err: errors.New("environment variable DOT_PROVIDERS: env error"),
},
"multiple valid values": {
envValue: "cloudflare,google",
expected: DNS{
Unbound: unbound.Settings{
Providers: []provider.Provider{
provider.Cloudflare(),
provider.Google(),
},
},
},
},
"one invalid value in two": {
envValue: "cloudflare,invalid",
expected: DNS{
Unbound: unbound.Settings{
Providers: []provider.Provider{
provider.Cloudflare(),
},
},
},
err: errors.New(`invalid DNS over TLS provider: cannot parse provider: "invalid"`),
},
}
for name, testCase := range testCases {
testCase := testCase
t.Run(name, func(t *testing.T) {
t.Parallel()
ctrl := gomock.NewController(t)
env := mock_params.NewMockInterface(ctrl)
env.EXPECT().Get("DOT_PROVIDERS", gomock.Any()).
Return(testCase.envValue, testCase.envErr)
var settings DNS
err := settings.readUnboundProviders(env)
if testCase.err != nil {
require.Error(t, err)
assert.Equal(t, testCase.err.Error(), err.Error())
} else {
assert.NoError(t, err)
}
assert.Equal(t, testCase.expected, settings)
})
}
}

View File

@@ -1,6 +1,7 @@
package configuration
import (
"fmt"
"strings"
"time"
@@ -8,21 +9,30 @@ import (
)
type Updater struct {
Period time.Duration `json:"period"`
DNSAddress string `json:"dns_address"`
Cyberghost bool `json:"cyberghost"`
Mullvad bool `json:"mullvad"`
Nordvpn bool `json:"nordvpn"`
PIA bool `json:"pia"`
Privado bool `json:"privado"`
Purevpn bool `json:"purevpn"`
Surfshark bool `json:"surfshark"`
Torguard bool `json:"torguard"`
Vyprvpn bool `json:"vyprvpn"`
Windscribe bool `json:"windscribe"`
Period time.Duration `json:"period"`
DNSAddress string `json:"dns_address"`
Cyberghost bool `json:"cyberghost"`
Expressvpn bool `json:"expressvpn"`
Fastestvpn bool `json:"fastestvpn"`
HideMyAss bool `json:"hidemyass"`
Ipvanish bool `json:"ipvanish"`
Ivpn bool `json:"ivpn"`
Mullvad bool `json:"mullvad"`
Nordvpn bool `json:"nordvpn"`
Perfectprivacy bool `json:"perfectprivacy"`
PIA bool `json:"pia"`
Privado bool `json:"privado"`
Privatevpn bool `json:"privatevpn"`
Protonvpn bool `json:"protonvpn"`
Purevpn bool `json:"purevpn"`
Surfshark bool `json:"surfshark"`
Torguard bool `json:"torguard"`
VPNUnlimited bool `json:"vpnunlimited"`
Vyprvpn bool `json:"vyprvpn"`
Wevpn bool `json:"wevpn"`
Windscribe bool `json:"windscribe"`
// The two below should be used in CLI mode only
Stdout bool `json:"-"` // in order to update constants file (maintainer side)
CLI bool `json:"-"`
CLI bool `json:"-"`
}
func (settings *Updater) String() string {
@@ -41,18 +51,30 @@ func (settings *Updater) lines() (lines []string) {
return lines
}
func (settings *Updater) read(r reader) (err error) {
func (settings *Updater) EnableAll() {
settings.Cyberghost = true
settings.HideMyAss = true
settings.Ipvanish = true
settings.Ivpn = true
settings.Mullvad = true
settings.Nordvpn = true
settings.Perfectprivacy = true
settings.Privado = true
settings.PIA = true
settings.Privado = true
settings.Privatevpn = true
settings.Protonvpn = true
settings.Purevpn = true
settings.Surfshark = true
settings.Torguard = true
settings.VPNUnlimited = true
settings.Vyprvpn = true
settings.Wevpn = true
settings.Windscribe = true
settings.Stdout = false
settings.CLI = false
}
func (settings *Updater) read(r reader) (err error) {
settings.EnableAll()
// use cloudflare in plaintext to not be blocked by DNS over TLS by default.
// If a plaintext address is set in the DNS settings, this one will be used.
// TODO use custom future encrypted DNS written in Go without blocking
@@ -61,7 +83,7 @@ func (settings *Updater) read(r reader) (err error) {
settings.Period, err = r.env.Duration("UPDATER_PERIOD", params.Default("0"))
if err != nil {
return err
return fmt.Errorf("environment variable UPDATER_PERIOD: %w", err)
}
return nil

View File

@@ -0,0 +1,79 @@
package configuration
import (
"errors"
"fmt"
"strings"
"github.com/qdm12/gluetun/internal/constants"
"github.com/qdm12/golibs/params"
)
type VPN struct {
Type string `json:"type"`
OpenVPN OpenVPN `json:"openvpn"`
Wireguard Wireguard `json:"wireguard"`
Provider Provider `json:"provider"`
}
func (settings *VPN) String() string {
return strings.Join(settings.lines(), "\n")
}
func (settings *VPN) lines() (lines []string) {
lines = append(lines, lastIndent+"VPN:")
lines = append(lines, indent+lastIndent+"Type: "+settings.Type)
var vpnLines []string
switch settings.Type {
case constants.OpenVPN:
vpnLines = settings.OpenVPN.lines()
case constants.Wireguard:
vpnLines = settings.Wireguard.lines()
}
for _, line := range vpnLines {
lines = append(lines, indent+line)
}
for _, line := range settings.Provider.lines() {
lines = append(lines, indent+line)
}
return lines
}
var (
errReadProviderSettings = errors.New("cannot read provider settings")
errReadOpenVPNSettings = errors.New("cannot read OpenVPN settings")
errReadWireguardSettings = errors.New("cannot read Wireguard settings")
)
func (settings *VPN) read(r reader) (err error) {
vpnType, err := r.env.Inside("VPN_TYPE",
[]string{constants.OpenVPN, constants.Wireguard},
params.Default(constants.OpenVPN))
if err != nil {
return fmt.Errorf("environment variable VPN_TYPE: %w", err)
}
settings.Type = vpnType
if err := settings.Provider.read(r, vpnType); err != nil {
return fmt.Errorf("%w: %s", errReadProviderSettings, err)
}
switch settings.Type {
case constants.OpenVPN:
err = settings.OpenVPN.read(r, settings.Provider.Name)
if err != nil {
return fmt.Errorf("%w: %s", errReadOpenVPNSettings, err)
}
case constants.Wireguard:
err = settings.Wireguard.read(r)
if err != nil {
return fmt.Errorf("%w: %s", errReadWireguardSettings, err)
}
}
return nil
}

View File

@@ -0,0 +1,60 @@
package configuration
import (
"fmt"
"github.com/qdm12/gluetun/internal/constants"
"github.com/qdm12/golibs/params"
)
func (settings *Provider) readVPNUnlimited(r reader) (err error) {
settings.Name = constants.VPNUnlimited
servers := r.servers.GetVPNUnlimited()
settings.ServerSelection.TargetIP, err = readTargetIP(r.env)
if err != nil {
return err
}
settings.ServerSelection.Countries, err = r.env.CSVInside("COUNTRY", constants.VPNUnlimitedCountryChoices(servers))
if err != nil {
return fmt.Errorf("environment variable COUNTRY: %w", err)
}
settings.ServerSelection.Cities, err = r.env.CSVInside("CITY", constants.VPNUnlimitedCityChoices(servers))
if err != nil {
return fmt.Errorf("environment variable CITY: %w", err)
}
settings.ServerSelection.Hostnames, err = r.env.CSVInside("SERVER_HOSTNAME",
constants.VPNUnlimitedHostnameChoices(servers))
if err != nil {
return fmt.Errorf("environment variable SERVER_HOSTNAME: %w", err)
}
settings.ServerSelection.FreeOnly, err = r.env.YesNo("FREE_ONLY", params.Default("no"))
if err != nil {
return fmt.Errorf("environment variable FREE_ONLY: %w", err)
}
settings.ServerSelection.StreamOnly, err = r.env.YesNo("STREAM_ONLY", params.Default("no"))
if err != nil {
return fmt.Errorf("environment variable STREAM_ONLY: %w", err)
}
return settings.ServerSelection.OpenVPN.readProtocolOnly(r)
}
func (settings *OpenVPN) readVPNUnlimited(r reader) (err error) {
settings.ClientKey, err = readClientKey(r)
if err != nil {
return fmt.Errorf("%w: %s", errClientKey, err)
}
settings.ClientCrt, err = readClientCertificate(r)
if err != nil {
return fmt.Errorf("%w: %s", errClientCert, err)
}
return nil
}

View File

@@ -1,33 +1,23 @@
package configuration
import (
"fmt"
"github.com/qdm12/gluetun/internal/constants"
)
func (settings *Provider) vyprvpnLines() (lines []string) {
if len(settings.ServerSelection.Regions) > 0 {
lines = append(lines, lastIndent+"Regions: "+commaJoin(settings.ServerSelection.Regions))
}
return lines
}
func (settings *Provider) readVyprvpn(r reader) (err error) {
settings.Name = constants.Vyprvpn
settings.ServerSelection.Protocol, err = readProtocol(r.env)
if err != nil {
return err
}
servers := r.servers.GetVyprvpn()
settings.ServerSelection.TargetIP, err = readTargetIP(r.env)
if err != nil {
return err
}
settings.ServerSelection.Regions, err = r.env.CSVInside("REGION", constants.VyprvpnRegionChoices())
settings.ServerSelection.Regions, err = r.env.CSVInside("REGION", constants.VyprvpnRegionChoices(servers))
if err != nil {
return err
return fmt.Errorf("environment variable REGION: %w", err)
}
return nil

View File

@@ -0,0 +1,46 @@
// Code generated by MockGen. DO NOT EDIT.
// Source: github.com/qdm12/gluetun/internal/configuration (interfaces: Warner)
// Package configuration is a generated GoMock package.
package configuration
import (
reflect "reflect"
gomock "github.com/golang/mock/gomock"
)
// MockWarner is a mock of Warner interface.
type MockWarner struct {
ctrl *gomock.Controller
recorder *MockWarnerMockRecorder
}
// MockWarnerMockRecorder is the mock recorder for MockWarner.
type MockWarnerMockRecorder struct {
mock *MockWarner
}
// NewMockWarner creates a new mock instance.
func NewMockWarner(ctrl *gomock.Controller) *MockWarner {
mock := &MockWarner{ctrl: ctrl}
mock.recorder = &MockWarnerMockRecorder{mock}
return mock
}
// EXPECT returns an object that allows the caller to indicate expected use.
func (m *MockWarner) EXPECT() *MockWarnerMockRecorder {
return m.recorder
}
// Warn mocks base method.
func (m *MockWarner) Warn(arg0 string) {
m.ctrl.T.Helper()
m.ctrl.Call(m, "Warn", arg0)
}
// Warn indicates an expected call of Warn.
func (mr *MockWarnerMockRecorder) Warn(arg0 interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Warn", reflect.TypeOf((*MockWarner)(nil).Warn), arg0)
}

View File

@@ -0,0 +1,57 @@
package configuration
import (
"fmt"
"github.com/qdm12/gluetun/internal/constants"
)
func (settings *Provider) readWevpn(r reader) (err error) {
settings.Name = constants.Wevpn
servers := r.servers.GetWevpn()
settings.ServerSelection.TargetIP, err = readTargetIP(r.env)
if err != nil {
return err
}
settings.ServerSelection.Cities, err = r.env.CSVInside("CITY", constants.WevpnCityChoices(servers))
if err != nil {
return fmt.Errorf("environment variable CITY: %w", err)
}
settings.ServerSelection.Hostnames, err = r.env.CSVInside("SERVER_HOSTNAME", constants.WevpnHostnameChoices(servers))
if err != nil {
return fmt.Errorf("environment variable SERVER_HOSTNAME: %w", err)
}
return settings.ServerSelection.OpenVPN.readWevpn(r)
}
func (settings *OpenVPNSelection) readWevpn(r reader) (err error) {
settings.TCP, err = readOpenVPNProtocol(r)
if err != nil {
return err
}
validation := openvpnPortValidation{
tcp: settings.TCP,
allowedTCP: []uint16{53, 1195, 1199, 2018},
allowedUDP: []uint16{80, 1194, 1198},
}
settings.CustomPort, err = readOpenVPNCustomPort(r, validation)
if err != nil {
return err
}
return nil
}
func (settings *OpenVPN) readWevpn(r reader) (err error) {
settings.ClientKey, err = readClientKey(r)
if err != nil {
return err
}
return nil
}

View File

@@ -1,60 +1,66 @@
package configuration
import (
"strconv"
"fmt"
"github.com/qdm12/gluetun/internal/constants"
"github.com/qdm12/golibs/params"
)
func (settings *Provider) windscribeLines() (lines []string) {
if len(settings.ServerSelection.Regions) > 0 {
lines = append(lines, lastIndent+"Regions: "+commaJoin(settings.ServerSelection.Regions))
}
if len(settings.ServerSelection.Cities) > 0 {
lines = append(lines, lastIndent+"Cities: "+commaJoin(settings.ServerSelection.Cities))
}
if len(settings.ServerSelection.Hostnames) > 0 {
lines = append(lines, lastIndent+"Hostnames: "+commaJoin(settings.ServerSelection.Hostnames))
}
lines = append(lines, lastIndent+"Custom port: "+strconv.Itoa(int(settings.ServerSelection.CustomPort)))
return lines
}
func (settings *Provider) readWindscribe(r reader) (err error) {
settings.Name = constants.Windscribe
settings.ServerSelection.Protocol, err = readProtocol(r.env)
if err != nil {
return err
}
servers := r.servers.GetWindscribe()
settings.ServerSelection.TargetIP, err = readTargetIP(r.env)
if err != nil {
return err
}
settings.ServerSelection.Regions, err = r.env.CSVInside("REGION", constants.WindscribeRegionChoices())
settings.ServerSelection.Regions, err = r.env.CSVInside("REGION", constants.WindscribeRegionChoices(servers))
if err != nil {
return fmt.Errorf("environment variable REGION: %w", err)
}
settings.ServerSelection.Cities, err = r.env.CSVInside("CITY", constants.WindscribeCityChoices(servers))
if err != nil {
return fmt.Errorf("environment variable CITY: %w", err)
}
settings.ServerSelection.Hostnames, err = r.env.CSVInside("SERVER_HOSTNAME",
constants.WindscribeHostnameChoices(servers))
if err != nil {
return fmt.Errorf("environment variable SERVER_HOSTNAME: %w", err)
}
err = settings.ServerSelection.OpenVPN.readWindscribe(r)
if err != nil {
return err
}
settings.ServerSelection.Cities, err = r.env.CSVInside("CITY", constants.WindscribeCityChoices())
return settings.ServerSelection.Wireguard.readWindscribe(r.env)
}
func (settings *OpenVPNSelection) readWindscribe(r reader) (err error) {
settings.TCP, err = readOpenVPNProtocol(r)
if err != nil {
return err
}
settings.ServerSelection.Hostnames, err = r.env.CSVInside("SERVER_HOSTNAME", constants.WindscribeHostnameChoices())
if err != nil {
return err
}
settings.ServerSelection.CustomPort, err = readCustomPort(r.env, settings.ServerSelection.Protocol,
[]uint16{21, 22, 80, 123, 143, 443, 587, 1194, 3306, 8080, 54783},
[]uint16{53, 80, 123, 443, 1194, 54783})
settings.CustomPort, err = readOpenVPNCustomPort(r, openvpnPortValidation{
tcp: settings.TCP,
allowedTCP: []uint16{21, 22, 80, 123, 143, 443, 587, 1194, 3306, 8080, 54783},
allowedUDP: []uint16{53, 80, 123, 443, 1194, 54783},
})
if err != nil {
return err
}
return nil
}
func (settings *WireguardSelection) readWindscribe(env params.Interface) (err error) {
settings.EndpointPort, err = readWireguardCustomPort(env,
[]uint16{53, 80, 123, 443, 1194, 65142})
if err != nil {
return err
}

View File

@@ -0,0 +1,88 @@
package configuration
import (
"fmt"
"net"
"strings"
"github.com/qdm12/golibs/params"
)
// Wireguard contains settings to configure the Wireguard client.
type Wireguard struct {
PrivateKey string `json:"privatekey"`
PreSharedKey string `json:"presharedkey"`
Addresses []*net.IPNet `json:"addresses"`
Interface string `json:"interface"`
}
func (settings *Wireguard) String() string {
return strings.Join(settings.lines(), "\n")
}
func (settings *Wireguard) lines() (lines []string) {
lines = append(lines, lastIndent+"Wireguard:")
lines = append(lines, indent+lastIndent+"Network interface: "+settings.Interface)
if settings.PrivateKey != "" {
lines = append(lines, indent+lastIndent+"Private key is set")
}
if settings.PreSharedKey != "" {
lines = append(lines, indent+lastIndent+"Pre-shared key is set")
}
if len(settings.Addresses) > 0 {
lines = append(lines, indent+lastIndent+"Addresses: ")
for _, address := range settings.Addresses {
lines = append(lines, indent+indent+lastIndent+address.String())
}
}
return lines
}
func (settings *Wireguard) read(r reader) (err error) {
settings.PrivateKey, err = r.env.Get("WIREGUARD_PRIVATE_KEY",
params.CaseSensitiveValue(), params.Unset(), params.Compulsory())
if err != nil {
return fmt.Errorf("environment variable WIREGUARD_PRIVATE_KEY: %w", err)
}
settings.PreSharedKey, err = r.env.Get("WIREGUARD_PRESHARED_KEY",
params.CaseSensitiveValue(), params.Unset())
if err != nil {
return fmt.Errorf("environment variable WIREGUARD_PRESHARED_KEY: %w", err)
}
err = settings.readAddresses(r.env)
if err != nil {
return err
}
settings.Interface, err = r.env.Get("WIREGUARD_INTERFACE", params.Default("wg0"))
if err != nil {
return fmt.Errorf("environment variable WIREGUARD_INTERFACE: %w", err)
}
return nil
}
func (settings *Wireguard) readAddresses(env params.Interface) (err error) {
addressStrings, err := env.CSV("WIREGUARD_ADDRESS", params.Compulsory())
if err != nil {
return fmt.Errorf("environment variable WIREGUARD_ADDRESS: %w", err)
}
for _, addressString := range addressStrings {
ip, ipNet, err := net.ParseCIDR(addressString)
if err != nil {
return fmt.Errorf("environment variable WIREGUARD_ADDRESS: address %s: %w", addressString, err)
}
ipNet.IP = ip
settings.Addresses = append(settings.Addresses, ipNet)
}
return nil
}

View File

@@ -1,5 +0,0 @@
package constants
const (
HealthcheckAddress = "127.0.0.1:9999"
)

View File

@@ -1,3 +1,25 @@
// Package constants defines constants shared throughout the program.
// It also defines constant maps and slices using functions.
package constants
import "sort"
func makeChoicesUnique(choices []string) []string {
uniqueChoices := map[string]struct{}{}
for _, choice := range choices {
uniqueChoices[choice] = struct{}{}
}
uniqueChoicesSlice := make([]string, len(uniqueChoices))
i := 0
for choice := range uniqueChoices {
uniqueChoicesSlice[i] = choice
i++
}
sort.Slice(uniqueChoicesSlice, func(i, j int) bool {
return uniqueChoicesSlice[i] < uniqueChoicesSlice[j]
})
return uniqueChoicesSlice
}

View File

@@ -2,253 +2,255 @@ package constants
func CountryCodes() map[string]string {
return map[string]string{
"af": "Afghanistan",
"ax": "Aland Islands",
"al": "Albania",
"dz": "Algeria",
"as": "American Samoa",
"ad": "Andorra",
"ao": "Angola",
"ai": "Anguilla",
"aq": "Antarctica",
"ag": "Antigua and Barbuda",
"ar": "Argentina",
"am": "Armenia",
"aw": "Aruba",
"au": "Australia",
"at": "Austria",
"az": "Azerbaijan",
"bs": "Bahamas",
"bh": "Bahrain",
"bd": "Bangladesh",
"bb": "Barbados",
"by": "Belarus",
"be": "Belgium",
"bz": "Belize",
"bj": "Benin",
"bm": "Bermuda",
"bt": "Bhutan",
"bo": "Bolivia",
"bq": "Bonaire",
"ba": "Bosnia and Herzegovina",
"bw": "Botswana",
"bv": "Bouvet Island",
"br": "Brazil",
"io": "British Indian Ocean Territory",
"vg": "British Virgin Islands",
"bn": "Brunei Darussalam",
"bg": "Bulgaria",
"bf": "Burkina Faso",
"bi": "Burundi",
"kh": "Cambodia",
"cm": "Cameroon",
"ca": "Canada",
"cv": "Cape Verde",
"ky": "Cayman Islands",
"cf": "Central African Republic",
"td": "Chad",
"cl": "Chile",
"cn": "China",
"cx": "Christmas Island",
"cc": "Cocos Islands",
"co": "Colombia",
"km": "Comoros",
"cg": "Congo",
"ck": "Cook Islands",
"cr": "Costa Rica",
"ci": "Cote d'Ivoire",
"hr": "Croatia",
"cu": "Cuba",
"cw": "Curacao",
"cy": "Cyprus",
"cz": "Czech Republic",
"cd": "Democratic Republic of the Congo",
"dk": "Denmark",
"dj": "Djibouti",
"dm": "Dominica",
"do": "Dominican Republic",
"ec": "Ecuador",
"eg": "Egypt",
"sv": "El Salvador",
"gq": "Equatorial Guinea",
"er": "Eritrea",
"ee": "Estonia",
"et": "Ethiopia",
"fk": "Falkland Islands",
"fo": "Faroe Islands",
"fj": "Fiji",
"fi": "Finland",
"fr": "France",
"gf": "French Guiana",
"pf": "French Polynesia",
"tf": "French Southern Territories",
"ga": "Gabon",
"gm": "Gambia",
"ge": "Georgia",
"de": "Germany",
"gh": "Ghana",
"gi": "Gibraltar",
"gr": "Greece",
"gl": "Greenland",
"gd": "Grenada",
"gp": "Guadeloupe",
"gu": "Guam",
"gt": "Guatemala",
"gg": "Guernsey",
"gw": "Guinea-Bissau",
"gn": "Guinea",
"gy": "Guyana",
"ht": "Haiti",
"hm": "Heard Island and McDonald Islands",
"hn": "Honduras",
"hk": "Hong Kong",
"hu": "Hungary",
"is": "Iceland",
"in": "India",
"id": "Indonesia",
"ir": "Iran",
"iq": "Iraq",
"ie": "Ireland",
"im": "Isle of Man",
"il": "Israel",
"it": "Italy",
"jm": "Jamaica",
"jp": "Japan",
"je": "Jersey",
"jo": "Jordan",
"kz": "Kazakhstan",
"ke": "Kenya",
"ki": "Kiribati",
"kr": "Korea",
"kw": "Kuwait",
"kg": "Kyrgyzstan",
"la": "Lao People's Democratic Republic",
"lv": "Latvia",
"lb": "Lebanon",
"ls": "Lesotho",
"lr": "Liberia",
"ly": "Libya",
"li": "Liechtenstein",
"lt": "Lithuania",
"lu": "Luxembourg",
"mo": "Macao",
"mk": "Macedonia",
"mg": "Madagascar",
"mw": "Malawi",
"my": "Malaysia",
"mv": "Maldives",
"ml": "Mali",
"mt": "Malta",
"mh": "Marshall Islands",
"mq": "Martinique",
"mr": "Mauritania",
"mu": "Mauritius",
"yt": "Mayotte",
"mx": "Mexico",
"fm": "Micronesia",
"md": "Moldova",
"mc": "Monaco",
"mn": "Mongolia",
"me": "Montenegro",
"ms": "Montserrat",
"ma": "Morocco",
"mz": "Mozambique",
"mm": "Myanmar",
"na": "Namibia",
"nr": "Nauru",
"np": "Nepal",
"nl": "Netherlands",
"nc": "New Caledonia",
"nz": "New Zealand",
"ni": "Nicaragua",
"ne": "Niger",
"ng": "Nigeria",
"nu": "Niue",
"nf": "Norfolk Island",
"mp": "Northern Mariana Islands",
"no": "Norway",
"om": "Oman",
"pk": "Pakistan",
"pw": "Palau",
"ps": "Palestine, State of",
"pa": "Panama",
"pg": "Papua New Guinea",
"py": "Paraguay",
"pe": "Peru",
"ph": "Philippines",
"pn": "Pitcairn",
"pl": "Poland",
"pt": "Portugal",
"pr": "Puerto Rico",
"qa": "Qatar",
"re": "Reunion",
"ro": "Romania",
"ru": "Russian Federation",
"rw": "Rwanda",
"bl": "Saint Barthelemy",
"sh": "Saint Helena",
"kn": "Saint Kitts and Nevis",
"lc": "Saint Lucia",
"mf": "Saint Martin",
"pm": "Saint Pierre and Miquelon",
"vc": "Saint Vincent and the Grenadines",
"ws": "Samoa",
"sm": "San Marino",
"st": "Sao Tome and Principe",
"sa": "Saudi Arabia",
"sn": "Senegal",
"rs": "Serbia",
"sc": "Seychelles",
"sl": "Sierra Leone",
"sg": "Singapore",
"sx": "Sint Maarten",
"sk": "Slovakia",
"si": "Slovenia",
"sb": "Solomon Islands",
"so": "Somalia",
"za": "South Africa",
"gs": "South Georgia and the South Sandwich Islands",
"ss": "South Sudan",
"es": "Spain",
"lk": "Sri Lanka",
"sd": "Sudan",
"sr": "Suriname",
"sj": "Svalbard and Jan Mayen",
"sz": "Swaziland",
"se": "Sweden",
"ch": "Switzerland",
"sy": "Syrian Arab Republic",
"tw": "Taiwan",
"tj": "Tajikistan",
"tz": "Tanzania",
"th": "Thailand",
"tl": "Timor-Leste",
"tg": "Togo",
"tk": "Tokelau",
"to": "Tonga",
"tt": "Trinidad and Tobago",
"tn": "Tunisia",
"tr": "Turkey",
"tm": "Turkmenistan",
"tc": "Turks and Caicos Islands",
"tv": "Tuvalu",
"ug": "Uganda",
"ua": "Ukraine",
"ae": "United Arab Emirates",
"gb": "United Kingdom",
"um": "United States Minor Outlying Islands",
"us": "United States",
"uy": "Uruguay",
"vi": "US Virgin Islands",
"uz": "Uzbekistan",
"vu": "Vanuatu",
"va": "Vatican City State",
"ve": "Venezuela",
"vn": "Vietnam",
"wf": "Wallis and Futuna",
"eh": "Western Sahara",
"ye": "Yemen",
"zm": "Zambia",
"zw": "Zimbabwe",
"af": "Afghanistan",
"ax": "Aland Islands",
"al": "Albania",
"dz": "Algeria",
"as": "American Samoa",
"ad": "Andorra",
"ao": "Angola",
"ai": "Anguilla",
"aq": "Antarctica",
"ag": "Antigua and Barbuda",
"ar": "Argentina",
"am": "Armenia",
"aw": "Aruba",
"au": "Australia",
"at": "Austria",
"az": "Azerbaijan",
"bs": "Bahamas",
"bh": "Bahrain",
"bd": "Bangladesh",
"bb": "Barbados",
"by": "Belarus",
"be": "Belgium",
"bz": "Belize",
"bj": "Benin",
"bm": "Bermuda",
"bt": "Bhutan",
"bo": "Bolivia",
"bq": "Bonaire",
"ba": "Bosnia and Herzegovina",
"bw": "Botswana",
"bv": "Bouvet Island",
"br": "Brazil",
"io": "British Indian Ocean Territory",
"vg": "British Virgin Islands",
"bn": "Brunei Darussalam",
"bg": "Bulgaria",
"bf": "Burkina Faso",
"bi": "Burundi",
"kh": "Cambodia",
"cm": "Cameroon",
"ca": "Canada",
"cv": "Cape Verde",
"ky": "Cayman Islands",
"cf": "Central African Republic",
"td": "Chad",
"cl": "Chile",
"cn": "China",
"cx": "Christmas Island",
"cc": "Cocos Islands",
"co": "Colombia",
"km": "Comoros",
"cg": "Congo",
"ck": "Cook Islands",
"cr": "Costa Rica",
"ci": "Cote d'Ivoire",
"hr": "Croatia",
"cu": "Cuba",
"cw": "Curacao",
"cy": "Cyprus",
"cz": "Czech Republic",
"cd": "Democratic Republic of the Congo",
"dk": "Denmark",
"dj": "Djibouti",
"dm": "Dominica",
"do": "Dominican Republic",
"ec": "Ecuador",
"eg": "Egypt",
"sv": "El Salvador",
"gq": "Equatorial Guinea",
"er": "Eritrea",
"ee": "Estonia",
"et": "Ethiopia",
"fk": "Falkland Islands",
"fo": "Faroe Islands",
"fj": "Fiji",
"fi": "Finland",
"fr": "France",
"gf": "French Guiana",
"pf": "French Polynesia",
"tf": "French Southern Territories",
"ga": "Gabon",
"gm": "Gambia",
"ge": "Georgia",
"de": "Germany",
"gh": "Ghana",
"gi": "Gibraltar",
"gr": "Greece",
"gl": "Greenland",
"gd": "Grenada",
"gp": "Guadeloupe",
"gu": "Guam",
"gt": "Guatemala",
"gg": "Guernsey",
"gw": "Guinea-Bissau",
"gn": "Guinea",
"gy": "Guyana",
"ht": "Haiti",
"hm": "Heard Island and McDonald Islands",
"hn": "Honduras",
"hk": "Hong Kong",
"hu": "Hungary",
"is": "Iceland",
"in": "India",
"id": "Indonesia",
"ir": "Iran",
"iq": "Iraq",
"ie": "Ireland",
"im": "Isle of Man",
"il": "Israel",
"it": "Italy",
"jm": "Jamaica",
"jp": "Japan",
"je": "Jersey",
"jo": "Jordan",
"kz": "Kazakhstan",
"ke": "Kenya",
"ki": "Kiribati",
"kr": "Korea",
"kw": "Kuwait",
"kg": "Kyrgyzstan",
"la": "Lao People's Democratic Republic",
"lv": "Latvia",
"lb": "Lebanon",
"ls": "Lesotho",
"lr": "Liberia",
"ly": "Libya",
"li": "Liechtenstein",
"lt": "Lithuania",
"lu": "Luxembourg",
"mo": "Macao",
"mk": "Macedonia",
"mg": "Madagascar",
"mw": "Malawi",
"my": "Malaysia",
"mys": "Kuala Lumpur",
"mv": "Maldives",
"ml": "Mali",
"mt": "Malta",
"mh": "Marshall Islands",
"mq": "Martinique",
"mr": "Mauritania",
"mu": "Mauritius",
"yt": "Mayotte",
"mx": "Mexico",
"fm": "Micronesia",
"md": "Moldova",
"mc": "Monaco",
"mn": "Mongolia",
"me": "Montenegro",
"ms": "Montserrat",
"ma": "Morocco",
"mz": "Mozambique",
"mm": "Myanmar",
"na": "Namibia",
"nr": "Nauru",
"np": "Nepal",
"nl": "Netherlands",
"nc": "New Caledonia",
"nz": "New Zealand",
"ni": "Nicaragua",
"ne": "Niger",
"ng": "Nigeria",
"nu": "Niue",
"nf": "Norfolk Island",
"mp": "Northern Mariana Islands",
"no": "Norway",
"om": "Oman",
"pk": "Pakistan",
"pw": "Palau",
"ps": "Palestine, State of",
"pa": "Panama",
"pg": "Papua New Guinea",
"py": "Paraguay",
"pe": "Peru",
"ph": "Philippines",
"pn": "Pitcairn",
"pl": "Poland",
"pt": "Portugal",
"pr": "Puerto Rico",
"qa": "Qatar",
"re": "Reunion",
"ro": "Romania",
"ru": "Russian Federation",
"rw": "Rwanda",
"bl": "Saint Barthelemy",
"sh": "Saint Helena",
"kn": "Saint Kitts and Nevis",
"lc": "Saint Lucia",
"mf": "Saint Martin",
"pm": "Saint Pierre and Miquelon",
"vc": "Saint Vincent and the Grenadines",
"ws": "Samoa",
"sm": "San Marino",
"st": "Sao Tome and Principe",
"sa": "Saudi Arabia",
"sn": "Senegal",
"rs": "Serbia",
"sc": "Seychelles",
"sl": "Sierra Leone",
"sg": "Singapore",
"sx": "Sint Maarten",
"sk": "Slovakia",
"si": "Slovenia",
"sb": "Solomon Islands",
"so": "Somalia",
"za": "South Africa",
"gs": "South Georgia and the South Sandwich Islands",
"ss": "South Sudan",
"es": "Spain",
"lk": "Sri Lanka",
"sd": "Sudan",
"sr": "Suriname",
"sj": "Svalbard and Jan Mayen",
"sz": "Swaziland",
"se": "Sweden",
"ch": "Switzerland",
"sy": "Syrian Arab Republic",
"tw": "Taiwan",
"tj": "Tajikistan",
"tz": "Tanzania",
"th": "Thailand",
"tl": "Timor-Leste",
"tg": "Togo",
"tk": "Tokelau",
"to": "Tonga",
"tt": "Trinidad and Tobago",
"tn": "Tunisia",
"tr": "Turkey",
"tm": "Turkmenistan",
"tc": "Turks and Caicos Islands",
"tv": "Tuvalu",
"ug": "Uganda",
"ua": "Ukraine",
"ae": "United Arab Emirates",
"gb": "United Kingdom",
"uk": "United Kingdom",
"um": "United States Minor Outlying Islands",
"us": "United States",
"uy": "Uruguay",
"vi": "US Virgin Islands",
"uz": "Uzbekistan",
"vu": "Vanuatu",
"va": "Vatican City State",
"ve": "Venezuela",
"vn": "Vietnam",
"wf": "Wallis and Futuna",
"eh": "Western Sahara",
"ye": "Yemen",
"zm": "Zambia",
"zw": "Zimbabwe",
}
}

View File

@@ -1,9 +1,6 @@
package constants
import (
"net"
"sort"
"github.com/qdm12/gluetun/internal/models"
)
@@ -12,213 +9,18 @@ const (
CyberghostCertificate = "MIIGWjCCBEKgAwIBAgIJAJxUG61mxDS7MA0GCSqGSIb3DQEBDQUAMHsxCzAJBgNVBAYTAlJPMRIwEAYDVQQHEwlCdWNoYXJlc3QxGDAWBgNVBAoTD0N5YmVyR2hvc3QgUy5BLjEbMBkGA1UEAxMSQ3liZXJHaG9zdCBSb290IENBMSEwHwYJKoZIhvcNAQkBFhJpbmZvQGN5YmVyZ2hvc3Qucm8wHhcNMTcwNjE5MDgxNzI1WhcNMzcwNjE0MDgxNzI1WjB7MQswCQYDVQQGEwJSTzESMBAGA1UEBxMJQnVjaGFyZXN0MRgwFgYDVQQKEw9DeWJlckdob3N0IFMuQS4xGzAZBgNVBAMTEkN5YmVyR2hvc3QgUm9vdCBDQTEhMB8GCSqGSIb3DQEJARYSaW5mb0BjeWJlcmdob3N0LnJvMIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEA7O8+mji2FlQhJXn/G4VLrKPjGtxgQBAdjo0dZEQzKX08q14dLkslmOLgShStWKrOiLXGAvB1rPvvk613jtA0KjQLpgyLy9lIWohQKYjj5jrJYXMZMkbSHBYI9L8L7iezBEFYrjYKdDo51nq99wRFhKdbyKKjDh3e2L2SVEZLT1ogkK5gWzjvH+mjjtjUUicK+YjGwWOz6I+KKaG4Ve/D/cE6nCLbhHIMMnargZEu7sqA6BFeS4kEP/ZdCZoTSX2n43XV1q63nJt/v0KDetbZDciFVW9h9SVPG4qT44p0550N+Mom7zTX7S/ID5T9dplgU8sRGtIMrG0cIMD9zmpFgUnMusCrR7jJFr0sMAveTbgZg95LmstV6R6WKZkSFdUrE0DHl4dHoZvTFX+1LhwhHgjgDLaosX0vhG/C/7LpoVWimd6RRQT3M9o4Fa1TuhfvBzQ20QHrmRV/yKvGNK0xckZ6EZ/QY7Z55ORU15Tgab4ebnblYPWoEmn0mIYP3LFFeoR5OS1EX7+j4kPv+bwPGsmpHjxmZyq2Y7sJBpbOCJgbkn52WZdPBIRDpPdIHQ8pAJC4T0iMK9xvAwWNl/V6EYYNpR97osyEDXn+BTdAHlhJ5fck9KlwI9mb1Kg1bhbvbmaIAiOLenSULYf3j6rI1ygo3R2cCyybtuAq8M7z0OECAwEAAaOB4DCB3TAdBgNVHQ4EFgQU6tdK1g/He5qzjeAoM5eHt4in9iUwga0GA1UdIwSBpTCBooAU6tdK1g/He5qzjeAoM5eHt4in9iWhf6R9MHsxCzAJBgNVBAYTAlJPMRIwEAYDVQQHEwlCdWNoYXJlc3QxGDAWBgNVBAoTD0N5YmVyR2hvc3QgUy5BLjEbMBkGA1UEAxMSQ3liZXJHaG9zdCBSb290IENBMSEwHwYJKoZIhvcNAQkBFhJpbmZvQGN5YmVyZ2hvc3Qucm+CCQCcVButZsQ0uzAMBgNVHRMEBTADAQH/MA0GCSqGSIb3DQEBDQUAA4ICAQDNyQ92kj4qiNjnHk99qvnFw9qGfwB9ofaPL74zh0G5hEe3Wgb2o4fqUGnvUNgOu53gJksz3DcPQ8t40wfmm9I1Z8tiM9qrqvkuQ+nKcLgdooXtEsTybPIYDZ2cWR/5E0TKRvC7RFzKgQ4D77Vbi4TdaHiDV7ZNfU1iLCoBGcYm80hcUHEs5KIVLwUmcSOTmbZBySJxcSD0yUpS7nlZGwLY6VQrU+JFwDSisbXT4DXf3iSzp7FzW0/u/SFvWsPHrjE0hkPoZPalYvouaJEHKAhip0ZwSmitlxbBnmm8+K/3c9mLA5/uXrirfpuhhs8V3lyV2mczVtSiTl6gpi88gc//JY80JeHdupjO25T3XEzY9cpxecmkWaUEjLMx4wVoXQuUiPonfILM6OLwi+zUS8gQErdFeGvcQXbncPa4SdJuHkF8lgiX2i8S8fPGdXvU37E9bdAXwP5nZriYq1s0D59Qfvz+vLXVkmyZp6ztxjKjKolemPMak0Y5c1Q4RjNF6tmQoFuy/ACSkWy14Tzu2dFp7UiVbGg1FOvKhfs48zC2/IUQv1arqmPT/9LVq3B2DVT9UKXRUXX/f/jSSsVjkz4uUe2jUyL+XHX1nSmROTPHSAJ+oKf0BLnfqUxFkEUTwLnayssP2nwGgq35b7wEbTFIXdrjHGFUVQIDeERz8UThew=="
)
func CyberghostRegionChoices() (choices []string) {
uniqueChoices := map[string]struct{}{}
for _, server := range CyberghostServers() {
uniqueChoices[server.Region] = struct{}{}
func CyberghostCountryChoices(servers []models.CyberghostServer) (choices []string) {
choices = make([]string, len(servers))
for i := range servers {
choices[i] = servers[i].Country
}
for choice := range uniqueChoices {
choices = append(choices, choice)
}
sort.Slice(choices, func(i, j int) bool {
return choices[i] < choices[j]
})
return choices
return makeUnique(choices)
}
func CyberghostGroupChoices() (choices []string) {
uniqueChoices := map[string]struct{}{}
for _, server := range CyberghostServers() {
uniqueChoices[server.Group] = struct{}{}
}
for choice := range uniqueChoices {
choices = append(choices, choice)
}
sort.Slice(choices, func(i, j int) bool {
return choices[i] < choices[j]
})
return choices
}
//nolint:lll
// CyberghostServers returns a slice with the server information for each
// of the Cyberghost server.
func CyberghostServers() []models.CyberghostServer {
return []models.CyberghostServer{
{Region: "Albania", Group: "Premium TCP Europe", IPs: []net.IP{{31, 171, 152, 99}, {31, 171, 152, 100}, {31, 171, 152, 101}, {31, 171, 152, 102}, {31, 171, 152, 105}, {31, 171, 152, 108}, {31, 171, 152, 132}, {31, 171, 152, 136}, {31, 171, 152, 139}, {31, 171, 152, 140}}},
{Region: "Albania", Group: "Premium UDP Europe", IPs: []net.IP{{31, 171, 152, 102}, {31, 171, 152, 103}, {31, 171, 152, 105}, {31, 171, 152, 106}, {31, 171, 152, 108}, {31, 171, 152, 109}, {31, 171, 152, 110}, {31, 171, 152, 135}, {31, 171, 152, 137}, {31, 171, 152, 139}}},
{Region: "Algeria", Group: "Premium UDP Europe", IPs: []net.IP{{176, 125, 228, 131}, {176, 125, 228, 132}, {176, 125, 228, 133}, {176, 125, 228, 134}, {176, 125, 228, 135}, {176, 125, 228, 136}, {176, 125, 228, 137}, {176, 125, 228, 140}, {176, 125, 228, 142}, {176, 125, 228, 143}}},
{Region: "Algeria", Group: "Premium TCP Europe", IPs: []net.IP{{176, 125, 228, 131}, {176, 125, 228, 132}, {176, 125, 228, 133}, {176, 125, 228, 134}, {176, 125, 228, 136}, {176, 125, 228, 139}, {176, 125, 228, 140}, {176, 125, 228, 141}, {176, 125, 228, 142}, {176, 125, 228, 143}}},
{Region: "Andorra", Group: "Premium UDP Europe", IPs: []net.IP{{188, 241, 82, 132}, {188, 241, 82, 133}, {188, 241, 82, 135}, {188, 241, 82, 138}, {188, 241, 82, 141}, {188, 241, 82, 144}, {188, 241, 82, 146}, {188, 241, 82, 157}, {188, 241, 82, 159}, {188, 241, 82, 166}}},
{Region: "Andorra", Group: "Premium TCP Europe", IPs: []net.IP{{188, 241, 82, 135}, {188, 241, 82, 136}, {188, 241, 82, 140}, {188, 241, 82, 142}, {188, 241, 82, 149}, {188, 241, 82, 151}, {188, 241, 82, 154}, {188, 241, 82, 160}, {188, 241, 82, 163}, {188, 241, 82, 165}}},
{Region: "Argentina", Group: "Premium UDP USA", IPs: []net.IP{{190, 106, 130, 15}, {190, 106, 130, 16}, {190, 106, 130, 20}, {190, 106, 130, 21}, {190, 106, 130, 22}, {190, 106, 130, 23}, {190, 106, 130, 37}, {190, 106, 130, 39}, {190, 106, 130, 43}, {190, 106, 130, 44}}},
{Region: "Argentina", Group: "Premium TCP USA", IPs: []net.IP{{190, 106, 130, 16}, {190, 106, 130, 17}, {190, 106, 130, 18}, {190, 106, 130, 26}, {190, 106, 130, 38}, {190, 106, 130, 40}, {190, 106, 130, 41}, {190, 106, 130, 42}, {190, 106, 130, 45}, {190, 106, 130, 52}}},
{Region: "Armenia", Group: "Premium UDP Europe", IPs: []net.IP{{185, 253, 160, 131}, {185, 253, 160, 132}, {185, 253, 160, 134}, {185, 253, 160, 135}, {185, 253, 160, 136}, {185, 253, 160, 137}, {185, 253, 160, 138}, {185, 253, 160, 139}, {185, 253, 160, 141}, {185, 253, 160, 144}}},
{Region: "Armenia", Group: "Premium TCP Europe", IPs: []net.IP{{185, 253, 160, 133}, {185, 253, 160, 134}, {185, 253, 160, 136}, {185, 253, 160, 137}, {185, 253, 160, 138}, {185, 253, 160, 139}, {185, 253, 160, 140}, {185, 253, 160, 141}, {185, 253, 160, 143}, {185, 253, 160, 144}}},
{Region: "Australia", Group: "Premium TCP Asia", IPs: []net.IP{{43, 242, 68, 111}, {43, 242, 68, 113}, {43, 242, 68, 120}, {202, 60, 80, 16}, {202, 60, 80, 72}, {202, 60, 80, 96}, {202, 60, 80, 151}, {202, 60, 80, 172}, {202, 60, 80, 173}, {202, 60, 80, 178}}},
{Region: "Australia", Group: "Premium UDP Asia", IPs: []net.IP{{43, 242, 68, 83}, {43, 242, 68, 94}, {202, 60, 80, 11}, {202, 60, 80, 21}, {202, 60, 80, 69}, {202, 60, 80, 107}, {202, 60, 80, 122}, {202, 60, 80, 123}, {202, 60, 80, 132}, {202, 60, 80, 162}}},
{Region: "Austria", Group: "Premium TCP Europe", IPs: []net.IP{{89, 187, 168, 134}, {89, 187, 168, 139}, {89, 187, 168, 140}, {89, 187, 168, 145}, {89, 187, 168, 146}, {89, 187, 168, 150}, {89, 187, 168, 167}, {89, 187, 168, 169}, {89, 187, 168, 180}, {89, 187, 168, 182}}},
{Region: "Austria", Group: "Premium UDP Europe", IPs: []net.IP{{89, 187, 168, 131}, {89, 187, 168, 132}, {89, 187, 168, 144}, {89, 187, 168, 147}, {89, 187, 168, 150}, {89, 187, 168, 151}, {89, 187, 168, 152}, {89, 187, 168, 163}, {89, 187, 168, 175}, {89, 187, 168, 177}}},
{Region: "Bahamas", Group: "Premium TCP USA", IPs: []net.IP{{95, 181, 238, 131}, {95, 181, 238, 132}, {95, 181, 238, 137}, {95, 181, 238, 139}, {95, 181, 238, 140}, {95, 181, 238, 141}, {95, 181, 238, 144}, {95, 181, 238, 146}, {95, 181, 238, 147}, {95, 181, 238, 153}}},
{Region: "Bahamas", Group: "Premium UDP USA", IPs: []net.IP{{95, 181, 238, 131}, {95, 181, 238, 133}, {95, 181, 238, 137}, {95, 181, 238, 138}, {95, 181, 238, 140}, {95, 181, 238, 141}, {95, 181, 238, 142}, {95, 181, 238, 146}, {95, 181, 238, 152}, {95, 181, 238, 153}}},
{Region: "Bangladesh", Group: "Premium UDP Asia", IPs: []net.IP{{84, 252, 93, 131}, {84, 252, 93, 132}, {84, 252, 93, 133}, {84, 252, 93, 134}, {84, 252, 93, 137}, {84, 252, 93, 140}, {84, 252, 93, 142}, {84, 252, 93, 143}, {84, 252, 93, 144}, {84, 252, 93, 145}}},
{Region: "Bangladesh", Group: "Premium TCP Asia", IPs: []net.IP{{84, 252, 93, 131}, {84, 252, 93, 132}, {84, 252, 93, 135}, {84, 252, 93, 136}, {84, 252, 93, 139}, {84, 252, 93, 140}, {84, 252, 93, 141}, {84, 252, 93, 142}, {84, 252, 93, 144}, {84, 252, 93, 145}}},
{Region: "Belarus", Group: "Premium UDP Europe", IPs: []net.IP{{45, 132, 194, 5}, {45, 132, 194, 6}, {45, 132, 194, 10}, {45, 132, 194, 13}, {45, 132, 194, 14}, {45, 132, 194, 20}, {45, 132, 194, 22}, {45, 132, 194, 23}, {45, 132, 194, 38}, {45, 132, 194, 48}}},
{Region: "Belarus", Group: "Premium TCP Europe", IPs: []net.IP{{45, 132, 194, 3}, {45, 132, 194, 5}, {45, 132, 194, 7}, {45, 132, 194, 10}, {45, 132, 194, 15}, {45, 132, 194, 35}, {45, 132, 194, 37}, {45, 132, 194, 40}, {45, 132, 194, 43}, {45, 132, 194, 47}}},
{Region: "Belgium", Group: "Premium UDP Europe", IPs: []net.IP{{185, 210, 217, 14}, {185, 232, 21, 119}, {193, 9, 114, 211}, {193, 9, 114, 221}, {194, 110, 115, 201}, {194, 110, 115, 214}, {194, 110, 115, 216}, {194, 110, 115, 220}, {194, 110, 115, 221}, {194, 110, 115, 233}}},
{Region: "Belgium", Group: "Premium TCP Europe", IPs: []net.IP{{5, 253, 205, 27}, {37, 120, 143, 58}, {37, 120, 143, 59}, {37, 120, 143, 165}, {185, 210, 217, 60}, {185, 232, 21, 115}, {185, 232, 21, 118}, {193, 9, 114, 228}, {194, 110, 115, 215}, {194, 110, 115, 232}}},
{Region: "Bosnia and Herzegovina", Group: "Premium UDP Europe", IPs: []net.IP{{185, 99, 3, 57}, {185, 99, 3, 58}, {185, 99, 3, 72}, {185, 99, 3, 73}, {185, 99, 3, 74}, {185, 99, 3, 130}, {185, 99, 3, 131}, {185, 99, 3, 134}, {185, 99, 3, 135}, {185, 99, 3, 136}}},
{Region: "Bosnia and Herzegovina", Group: "Premium TCP Europe", IPs: []net.IP{{185, 99, 3, 57}, {185, 99, 3, 58}, {185, 99, 3, 72}, {185, 99, 3, 73}, {185, 99, 3, 74}, {185, 99, 3, 130}, {185, 99, 3, 131}, {185, 99, 3, 134}, {185, 99, 3, 135}, {185, 99, 3, 136}}},
{Region: "Brazil", Group: "Premium UDP USA", IPs: []net.IP{{188, 241, 177, 8}, {188, 241, 177, 10}, {188, 241, 177, 12}, {188, 241, 177, 13}, {188, 241, 177, 14}, {188, 241, 177, 30}, {188, 241, 177, 36}, {188, 241, 177, 42}, {188, 241, 177, 45}, {188, 241, 177, 46}}},
{Region: "Brazil", Group: "Premium TCP USA", IPs: []net.IP{{188, 241, 177, 9}, {188, 241, 177, 12}, {188, 241, 177, 13}, {188, 241, 177, 19}, {188, 241, 177, 29}, {188, 241, 177, 35}, {188, 241, 177, 40}, {188, 241, 177, 41}, {188, 241, 177, 43}, {188, 241, 177, 44}}},
{Region: "Bulgaria", Group: "Premium UDP Europe", IPs: []net.IP{{37, 120, 152, 100}, {37, 120, 152, 101}, {37, 120, 152, 102}, {37, 120, 152, 103}, {37, 120, 152, 104}, {37, 120, 152, 105}, {37, 120, 152, 106}, {37, 120, 152, 108}, {37, 120, 152, 109}, {37, 120, 152, 110}}},
{Region: "Bulgaria", Group: "Premium TCP Europe", IPs: []net.IP{{37, 120, 152, 99}, {37, 120, 152, 100}, {37, 120, 152, 101}, {37, 120, 152, 102}, {37, 120, 152, 104}, {37, 120, 152, 105}, {37, 120, 152, 106}, {37, 120, 152, 107}, {37, 120, 152, 108}, {37, 120, 152, 109}}},
{Region: "Cambodia", Group: "Premium TCP Asia", IPs: []net.IP{{188, 215, 235, 37}, {188, 215, 235, 41}, {188, 215, 235, 45}, {188, 215, 235, 46}, {188, 215, 235, 47}, {188, 215, 235, 49}, {188, 215, 235, 52}, {188, 215, 235, 53}, {188, 215, 235, 54}, {188, 215, 235, 57}}},
{Region: "Cambodia", Group: "Premium UDP Asia", IPs: []net.IP{{188, 215, 235, 36}, {188, 215, 235, 37}, {188, 215, 235, 40}, {188, 215, 235, 41}, {188, 215, 235, 45}, {188, 215, 235, 47}, {188, 215, 235, 50}, {188, 215, 235, 51}, {188, 215, 235, 56}, {188, 215, 235, 57}}},
{Region: "Canada", Group: "Premium TCP USA", IPs: []net.IP{{37, 120, 205, 4}, {37, 120, 205, 26}, {66, 115, 142, 171}, {89, 47, 234, 103}, {176, 113, 74, 22}, {176, 113, 74, 77}, {176, 113, 74, 135}, {176, 113, 74, 137}, {176, 113, 74, 201}, {176, 113, 74, 205}}},
{Region: "Canada", Group: "Premium UDP USA", IPs: []net.IP{{37, 120, 205, 24}, {66, 115, 142, 162}, {66, 115, 142, 172}, {89, 47, 234, 85}, {89, 47, 234, 118}, {172, 98, 89, 146}, {172, 98, 89, 159}, {172, 98, 89, 172}, {176, 113, 74, 156}, {176, 113, 74, 206}}},
{Region: "China", Group: "Premium TCP Asia", IPs: []net.IP{{188, 241, 80, 131}, {188, 241, 80, 132}, {188, 241, 80, 133}, {188, 241, 80, 134}, {188, 241, 80, 136}, {188, 241, 80, 137}, {188, 241, 80, 139}, {188, 241, 80, 140}, {188, 241, 80, 141}, {188, 241, 80, 142}}},
{Region: "China", Group: "Premium UDP Asia", IPs: []net.IP{{188, 241, 80, 132}, {188, 241, 80, 133}, {188, 241, 80, 134}, {188, 241, 80, 135}, {188, 241, 80, 136}, {188, 241, 80, 137}, {188, 241, 80, 138}, {188, 241, 80, 139}, {188, 241, 80, 140}, {188, 241, 80, 141}}},
{Region: "Costa Rica", Group: "Premium TCP USA", IPs: []net.IP{{143, 202, 160, 67}, {143, 202, 160, 68}, {143, 202, 160, 69}, {143, 202, 160, 70}, {143, 202, 160, 72}, {143, 202, 160, 73}, {143, 202, 160, 74}, {143, 202, 160, 75}, {143, 202, 160, 76}, {143, 202, 160, 78}}},
{Region: "Cyprus", Group: "Premium UDP Europe", IPs: []net.IP{{185, 253, 162, 131}, {185, 253, 162, 134}, {185, 253, 162, 136}, {185, 253, 162, 137}, {185, 253, 162, 139}, {185, 253, 162, 140}, {185, 253, 162, 141}, {185, 253, 162, 142}, {185, 253, 162, 143}, {185, 253, 162, 144}}},
{Region: "Cyprus", Group: "Premium TCP Europe", IPs: []net.IP{{185, 253, 162, 131}, {185, 253, 162, 132}, {185, 253, 162, 134}, {185, 253, 162, 135}, {185, 253, 162, 136}, {185, 253, 162, 139}, {185, 253, 162, 140}, {185, 253, 162, 141}, {185, 253, 162, 143}, {185, 253, 162, 144}}},
{Region: "Czech Republic", Group: "Premium TCP Europe", IPs: []net.IP{{185, 216, 35, 231}, {185, 216, 35, 236}, {195, 181, 161, 3}, {195, 181, 161, 7}, {195, 181, 161, 11}, {195, 181, 161, 14}, {195, 181, 161, 15}, {195, 181, 161, 16}, {195, 181, 161, 17}, {195, 181, 161, 22}}},
{Region: "Czech Republic", Group: "Premium UDP Europe", IPs: []net.IP{{185, 216, 35, 227}, {185, 216, 35, 235}, {195, 181, 161, 3}, {195, 181, 161, 5}, {195, 181, 161, 7}, {195, 181, 161, 11}, {195, 181, 161, 12}, {195, 181, 161, 14}, {195, 181, 161, 15}, {195, 181, 161, 20}}},
{Region: "Denmark", Group: "Premium TCP Europe", IPs: []net.IP{{37, 120, 145, 86}, {37, 120, 194, 45}, {37, 120, 194, 46}, {37, 120, 194, 61}, {95, 174, 65, 172}, {95, 174, 65, 174}, {185, 206, 224, 237}, {185, 206, 224, 244}, {185, 206, 224, 245}, {185, 206, 224, 246}}},
{Region: "Denmark", Group: "Premium UDP Europe", IPs: []net.IP{{37, 120, 194, 56}, {37, 120, 194, 61}, {95, 174, 65, 164}, {95, 174, 65, 165}, {95, 174, 65, 167}, {95, 174, 65, 172}, {185, 206, 224, 235}, {185, 206, 224, 244}, {185, 206, 224, 247}, {185, 206, 224, 249}}},
{Region: "Egypt", Group: "Premium TCP Europe", IPs: []net.IP{{188, 214, 122, 37}, {188, 214, 122, 39}, {188, 214, 122, 40}, {188, 214, 122, 51}, {188, 214, 122, 53}, {188, 214, 122, 54}, {188, 214, 122, 56}, {188, 214, 122, 58}, {188, 214, 122, 59}, {188, 214, 122, 62}}},
{Region: "Egypt", Group: "Premium UDP Europe", IPs: []net.IP{{188, 214, 122, 35}, {188, 214, 122, 38}, {188, 214, 122, 39}, {188, 214, 122, 44}, {188, 214, 122, 46}, {188, 214, 122, 48}, {188, 214, 122, 50}, {188, 214, 122, 52}, {188, 214, 122, 54}, {188, 214, 122, 58}}},
{Region: "Estonia", Group: "Premium TCP Europe", IPs: []net.IP{{95, 153, 31, 82}, {95, 153, 31, 83}, {95, 153, 31, 84}, {95, 153, 31, 85}, {95, 153, 31, 86}, {95, 153, 31, 87}, {95, 153, 31, 89}, {95, 153, 31, 90}, {95, 153, 31, 92}, {95, 153, 31, 93}}},
{Region: "Estonia", Group: "Premium UDP Europe", IPs: []net.IP{{95, 153, 31, 83}, {95, 153, 31, 84}, {95, 153, 31, 85}, {95, 153, 31, 86}, {95, 153, 31, 87}, {95, 153, 31, 88}, {95, 153, 31, 90}, {95, 153, 31, 92}, {95, 153, 31, 93}, {95, 153, 31, 94}}},
{Region: "Finland", Group: "Premium UDP Europe", IPs: []net.IP{{188, 126, 89, 133}, {188, 126, 89, 134}, {188, 126, 89, 137}, {188, 126, 89, 138}, {188, 126, 89, 143}, {188, 126, 89, 144}, {188, 126, 89, 149}, {188, 126, 89, 150}, {188, 126, 89, 152}, {188, 126, 89, 154}}},
{Region: "Finland", Group: "Premium TCP Europe", IPs: []net.IP{{188, 126, 89, 131}, {188, 126, 89, 132}, {188, 126, 89, 133}, {188, 126, 89, 137}, {188, 126, 89, 138}, {188, 126, 89, 142}, {188, 126, 89, 145}, {188, 126, 89, 153}, {188, 126, 89, 154}, {188, 126, 89, 155}}},
{Region: "France", Group: "Premium UDP Europe", IPs: []net.IP{{84, 17, 60, 31}, {84, 17, 60, 93}, {84, 17, 60, 136}, {84, 17, 60, 173}, {84, 17, 60, 177}, {84, 17, 60, 184}, {84, 17, 61, 10}, {84, 17, 61, 92}, {84, 17, 61, 155}, {138, 199, 26, 174}}},
{Region: "France", Group: "Premium TCP Europe", IPs: []net.IP{{84, 17, 60, 40}, {84, 17, 60, 85}, {84, 17, 60, 98}, {84, 17, 60, 150}, {84, 17, 60, 178}, {84, 17, 61, 5}, {84, 17, 61, 212}, {138, 199, 26, 76}, {138, 199, 26, 147}, {138, 199, 26, 219}}},
{Region: "Georgia", Group: "Premium TCP Europe", IPs: []net.IP{{95, 181, 236, 131}, {95, 181, 236, 132}, {95, 181, 236, 133}, {95, 181, 236, 135}, {95, 181, 236, 137}, {95, 181, 236, 138}, {95, 181, 236, 139}, {95, 181, 236, 140}, {95, 181, 236, 142}, {95, 181, 236, 144}}},
{Region: "Georgia", Group: "Premium UDP Europe", IPs: []net.IP{{95, 181, 236, 131}, {95, 181, 236, 132}, {95, 181, 236, 133}, {95, 181, 236, 134}, {95, 181, 236, 138}, {95, 181, 236, 139}, {95, 181, 236, 140}, {95, 181, 236, 141}, {95, 181, 236, 143}, {95, 181, 236, 144}}},
{Region: "Germany", Group: "Premium TCP Europe", IPs: []net.IP{{37, 120, 217, 30}, {37, 120, 217, 61}, {84, 17, 48, 13}, {84, 17, 48, 18}, {84, 17, 48, 22}, {84, 17, 48, 81}, {84, 17, 48, 185}, {84, 17, 49, 242}, {154, 28, 188, 128}, {154, 28, 188, 167}}},
{Region: "Germany", Group: "Premium UDP Europe", IPs: []net.IP{{37, 120, 217, 38}, {84, 17, 48, 55}, {84, 17, 48, 140}, {84, 17, 48, 188}, {84, 17, 48, 192}, {84, 17, 48, 205}, {84, 17, 49, 128}, {84, 17, 49, 191}, {84, 17, 49, 236}, {154, 28, 188, 82}}},
{Region: "Greece", Group: "Premium UDP Europe", IPs: []net.IP{{154, 57, 3, 131}, {154, 57, 3, 133}, {154, 57, 3, 136}, {154, 57, 3, 137}, {154, 57, 3, 140}, {154, 57, 3, 141}, {188, 123, 126, 167}, {188, 123, 126, 171}, {188, 123, 126, 175}, {188, 123, 126, 176}}},
{Region: "Greece", Group: "Premium TCP Europe", IPs: []net.IP{{154, 57, 3, 131}, {154, 57, 3, 133}, {154, 57, 3, 134}, {154, 57, 3, 137}, {154, 57, 3, 139}, {188, 123, 126, 170}, {188, 123, 126, 172}, {188, 123, 126, 173}, {188, 123, 126, 175}, {188, 123, 126, 177}}},
{Region: "Greenland", Group: "Premium UDP Europe", IPs: []net.IP{{91, 90, 120, 3}, {91, 90, 120, 5}, {91, 90, 120, 6}, {91, 90, 120, 7}, {91, 90, 120, 8}, {91, 90, 120, 9}, {91, 90, 120, 10}, {91, 90, 120, 14}, {91, 90, 120, 15}, {91, 90, 120, 17}}},
{Region: "Greenland", Group: "Premium TCP Europe", IPs: []net.IP{{91, 90, 120, 3}, {91, 90, 120, 4}, {91, 90, 120, 5}, {91, 90, 120, 6}, {91, 90, 120, 9}, {91, 90, 120, 10}, {91, 90, 120, 11}, {91, 90, 120, 13}, {91, 90, 120, 14}, {91, 90, 120, 17}}},
{Region: "Hong Kong", Group: "Premium TCP Asia", IPs: []net.IP{{84, 17, 56, 34}, {84, 17, 56, 41}, {84, 17, 56, 133}, {84, 17, 56, 147}, {84, 17, 56, 148}, {84, 17, 56, 153}, {84, 17, 56, 163}, {84, 17, 56, 167}, {84, 17, 56, 171}, {84, 17, 56, 184}}},
{Region: "Hong Kong", Group: "Premium UDP Asia", IPs: []net.IP{{84, 17, 56, 39}, {84, 17, 56, 52}, {84, 17, 56, 55}, {84, 17, 56, 135}, {84, 17, 56, 136}, {84, 17, 56, 145}, {84, 17, 56, 149}, {84, 17, 56, 165}, {84, 17, 56, 167}, {84, 17, 56, 170}}},
{Region: "Hungary", Group: "Premium TCP Europe", IPs: []net.IP{{86, 106, 74, 243}, {86, 106, 74, 244}, {86, 106, 74, 247}, {86, 106, 74, 251}, {86, 106, 74, 252}, {86, 106, 74, 253}, {185, 189, 114, 117}, {185, 189, 114, 119}, {185, 189, 114, 124}, {185, 189, 114, 126}}},
{Region: "Hungary", Group: "Premium UDP Europe", IPs: []net.IP{{86, 106, 74, 252}, {86, 106, 74, 253}, {86, 106, 74, 254}, {185, 189, 114, 116}, {185, 189, 114, 117}, {185, 189, 114, 118}, {185, 189, 114, 121}, {185, 189, 114, 123}, {185, 189, 114, 125}, {185, 189, 114, 126}}},
{Region: "Iceland", Group: "Premium UDP Europe", IPs: []net.IP{{45, 133, 193, 4}, {45, 133, 193, 5}, {45, 133, 193, 6}, {45, 133, 193, 8}, {45, 133, 193, 9}, {45, 133, 193, 10}, {45, 133, 193, 11}, {45, 133, 193, 12}, {45, 133, 193, 13}, {45, 133, 193, 14}}},
{Region: "Iceland", Group: "Premium TCP Europe", IPs: []net.IP{{45, 133, 193, 3}, {45, 133, 193, 4}, {45, 133, 193, 5}, {45, 133, 193, 6}, {45, 133, 193, 7}, {45, 133, 193, 8}, {45, 133, 193, 9}, {45, 133, 193, 10}, {45, 133, 193, 11}, {45, 133, 193, 13}}},
{Region: "India", Group: "Premium TCP Europe", IPs: []net.IP{{43, 241, 71, 117}, {43, 241, 71, 118}, {43, 241, 71, 122}, {43, 241, 71, 123}, {43, 241, 71, 125}, {43, 241, 71, 148}, {43, 241, 71, 151}, {43, 241, 71, 152}, {43, 241, 71, 154}, {43, 241, 71, 156}}},
{Region: "India", Group: "Premium UDP Europe", IPs: []net.IP{{43, 241, 71, 115}, {43, 241, 71, 119}, {43, 241, 71, 123}, {43, 241, 71, 124}, {43, 241, 71, 148}, {43, 241, 71, 149}, {43, 241, 71, 151}, {43, 241, 71, 153}, {43, 241, 71, 155}, {43, 241, 71, 157}}},
{Region: "Indonesia", Group: "Premium TCP Asia", IPs: []net.IP{{113, 20, 29, 243}, {113, 20, 29, 244}, {113, 20, 29, 245}, {113, 20, 29, 246}, {113, 20, 29, 247}, {113, 20, 29, 249}, {113, 20, 29, 250}, {113, 20, 29, 252}, {113, 20, 29, 253}, {113, 20, 29, 254}}},
{Region: "Indonesia", Group: "Premium UDP Asia", IPs: []net.IP{{113, 20, 29, 243}, {113, 20, 29, 244}, {113, 20, 29, 245}, {113, 20, 29, 246}, {113, 20, 29, 247}, {113, 20, 29, 248}, {113, 20, 29, 249}, {113, 20, 29, 250}, {113, 20, 29, 251}, {113, 20, 29, 253}}},
{Region: "Iran", Group: "Premium UDP Asia", IPs: []net.IP{{62, 133, 46, 3}, {62, 133, 46, 5}, {62, 133, 46, 7}, {62, 133, 46, 8}, {62, 133, 46, 9}, {62, 133, 46, 11}, {62, 133, 46, 12}, {62, 133, 46, 13}, {62, 133, 46, 14}, {62, 133, 46, 15}}},
{Region: "Iran", Group: "Premium TCP Asia", IPs: []net.IP{{62, 133, 46, 3}, {62, 133, 46, 6}, {62, 133, 46, 7}, {62, 133, 46, 8}, {62, 133, 46, 9}, {62, 133, 46, 10}, {62, 133, 46, 11}, {62, 133, 46, 12}, {62, 133, 46, 14}, {62, 133, 46, 15}}},
{Region: "Ireland", Group: "Premium UDP Europe", IPs: []net.IP{{77, 81, 139, 35}, {77, 81, 139, 40}, {77, 81, 139, 41}, {77, 81, 139, 45}, {84, 247, 48, 3}, {84, 247, 48, 8}, {84, 247, 48, 10}, {84, 247, 48, 19}, {84, 247, 48, 21}, {84, 247, 48, 29}}},
{Region: "Ireland", Group: "Premium TCP Europe", IPs: []net.IP{{77, 81, 139, 40}, {84, 247, 48, 3}, {84, 247, 48, 4}, {84, 247, 48, 7}, {84, 247, 48, 10}, {84, 247, 48, 20}, {84, 247, 48, 23}, {84, 247, 48, 24}, {84, 247, 48, 26}, {84, 247, 48, 30}}},
{Region: "Isle of Man", Group: "Premium TCP Europe", IPs: []net.IP{{91, 90, 124, 147}, {91, 90, 124, 148}, {91, 90, 124, 149}, {91, 90, 124, 152}, {91, 90, 124, 153}, {91, 90, 124, 155}, {91, 90, 124, 156}, {91, 90, 124, 157}, {91, 90, 124, 158}, {91, 90, 124, 159}}},
{Region: "Isle of Man", Group: "Premium UDP Europe", IPs: []net.IP{{91, 90, 124, 148}, {91, 90, 124, 149}, {91, 90, 124, 150}, {91, 90, 124, 152}, {91, 90, 124, 153}, {91, 90, 124, 154}, {91, 90, 124, 155}, {91, 90, 124, 157}, {91, 90, 124, 158}, {91, 90, 124, 159}}},
{Region: "Israel", Group: "Premium TCP Europe", IPs: []net.IP{{160, 116, 0, 163}, {160, 116, 0, 164}, {160, 116, 0, 165}, {160, 116, 0, 166}, {160, 116, 0, 168}, {160, 116, 0, 169}, {160, 116, 0, 170}, {160, 116, 0, 171}, {160, 116, 0, 172}, {160, 116, 0, 174}}},
{Region: "Israel", Group: "Premium UDP Europe", IPs: []net.IP{{160, 116, 0, 163}, {160, 116, 0, 164}, {160, 116, 0, 165}, {160, 116, 0, 166}, {160, 116, 0, 167}, {160, 116, 0, 168}, {160, 116, 0, 169}, {160, 116, 0, 170}, {160, 116, 0, 171}, {160, 116, 0, 172}}},
{Region: "Italy", Group: "Premium UDP Europe", IPs: []net.IP{{84, 17, 58, 21}, {84, 17, 58, 94}, {87, 101, 94, 68}, {185, 217, 71, 132}, {185, 217, 71, 135}, {185, 217, 71, 154}, {212, 102, 55, 97}, {212, 102, 55, 111}, {212, 102, 55, 113}, {212, 102, 55, 118}}},
{Region: "Italy", Group: "Premium TCP Europe", IPs: []net.IP{{84, 17, 58, 13}, {84, 17, 58, 22}, {84, 17, 58, 23}, {84, 17, 58, 110}, {185, 217, 71, 134}, {185, 217, 71, 155}, {212, 102, 55, 97}, {212, 102, 55, 104}, {212, 102, 55, 114}, {212, 102, 55, 118}}},
{Region: "Japan", Group: "Premium UDP Asia", IPs: []net.IP{{156, 146, 35, 7}, {156, 146, 35, 8}, {156, 146, 35, 10}, {156, 146, 35, 12}, {156, 146, 35, 13}, {156, 146, 35, 14}, {156, 146, 35, 15}, {156, 146, 35, 16}, {156, 146, 35, 22}, {156, 146, 35, 49}}},
{Region: "Japan", Group: "Premium TCP Asia", IPs: []net.IP{{156, 146, 35, 4}, {156, 146, 35, 6}, {156, 146, 35, 9}, {156, 146, 35, 12}, {156, 146, 35, 16}, {156, 146, 35, 21}, {156, 146, 35, 22}, {156, 146, 35, 28}, {156, 146, 35, 29}, {156, 146, 35, 49}}},
{Region: "Kazakhstan", Group: "Premium UDP Europe", IPs: []net.IP{{62, 133, 47, 131}, {62, 133, 47, 132}, {62, 133, 47, 133}, {62, 133, 47, 134}, {62, 133, 47, 135}, {62, 133, 47, 137}, {62, 133, 47, 139}, {62, 133, 47, 140}, {62, 133, 47, 143}, {62, 133, 47, 144}}},
{Region: "Kazakhstan", Group: "Premium TCP Europe", IPs: []net.IP{{62, 133, 47, 132}, {62, 133, 47, 133}, {62, 133, 47, 135}, {62, 133, 47, 137}, {62, 133, 47, 138}, {62, 133, 47, 140}, {62, 133, 47, 141}, {62, 133, 47, 142}, {62, 133, 47, 143}, {62, 133, 47, 144}}},
{Region: "Kenya", Group: "Premium TCP Asia", IPs: []net.IP{{62, 12, 118, 195}, {62, 12, 118, 196}, {62, 12, 118, 197}, {62, 12, 118, 198}, {62, 12, 118, 199}, {62, 12, 118, 200}, {62, 12, 118, 201}, {62, 12, 118, 202}, {62, 12, 118, 203}, {62, 12, 118, 204}}},
{Region: "Kenya", Group: "Premium UDP Asia", IPs: []net.IP{{62, 12, 118, 195}, {62, 12, 118, 196}, {62, 12, 118, 197}, {62, 12, 118, 198}, {62, 12, 118, 199}, {62, 12, 118, 200}, {62, 12, 118, 201}, {62, 12, 118, 202}, {62, 12, 118, 203}, {62, 12, 118, 204}}},
{Region: "Korea", Group: "Premium TCP Asia", IPs: []net.IP{{27, 255, 75, 227}, {27, 255, 75, 228}, {27, 255, 75, 229}, {27, 255, 75, 238}, {27, 255, 75, 243}, {27, 255, 75, 245}, {27, 255, 75, 246}, {27, 255, 75, 249}, {27, 255, 75, 252}, {27, 255, 75, 254}}},
{Region: "Korea", Group: "Premium UDP Asia", IPs: []net.IP{{27, 255, 75, 227}, {27, 255, 75, 229}, {27, 255, 75, 231}, {27, 255, 75, 232}, {27, 255, 75, 234}, {27, 255, 75, 235}, {27, 255, 75, 236}, {27, 255, 75, 245}, {27, 255, 75, 247}, {27, 255, 75, 253}}},
{Region: "Latvia", Group: "Premium UDP Europe", IPs: []net.IP{{109, 248, 148, 245}, {109, 248, 148, 247}, {109, 248, 148, 252}, {109, 248, 148, 254}, {109, 248, 149, 20}, {109, 248, 149, 21}, {109, 248, 149, 27}, {109, 248, 149, 28}, {109, 248, 149, 29}, {109, 248, 149, 30}}},
{Region: "Latvia", Group: "Premium TCP Europe", IPs: []net.IP{{109, 248, 148, 243}, {109, 248, 148, 244}, {109, 248, 148, 252}, {109, 248, 149, 19}, {109, 248, 149, 23}, {109, 248, 149, 25}, {109, 248, 149, 27}, {109, 248, 149, 28}, {109, 248, 149, 29}, {109, 248, 149, 30}}},
{Region: "Liechtenstein", Group: "Premium TCP Europe", IPs: []net.IP{{91, 90, 122, 131}, {91, 90, 122, 133}, {91, 90, 122, 134}, {91, 90, 122, 135}, {91, 90, 122, 136}, {91, 90, 122, 137}, {91, 90, 122, 142}, {91, 90, 122, 143}, {91, 90, 122, 144}, {91, 90, 122, 145}}},
{Region: "Liechtenstein", Group: "Premium UDP Europe", IPs: []net.IP{{91, 90, 122, 131}, {91, 90, 122, 132}, {91, 90, 122, 134}, {91, 90, 122, 136}, {91, 90, 122, 138}, {91, 90, 122, 141}, {91, 90, 122, 142}, {91, 90, 122, 143}, {91, 90, 122, 144}, {91, 90, 122, 145}}},
{Region: "Lithuania", Group: "Premium TCP Europe", IPs: []net.IP{{85, 206, 162, 209}, {85, 206, 162, 211}, {85, 206, 162, 216}, {85, 206, 162, 217}, {85, 206, 162, 218}, {85, 206, 162, 221}, {85, 206, 165, 25}, {85, 206, 165, 26}, {85, 206, 165, 30}, {85, 206, 165, 31}}},
{Region: "Lithuania", Group: "Premium UDP Europe", IPs: []net.IP{{85, 206, 162, 209}, {85, 206, 162, 211}, {85, 206, 162, 216}, {85, 206, 162, 217}, {85, 206, 162, 219}, {85, 206, 162, 221}, {85, 206, 165, 19}, {85, 206, 165, 20}, {85, 206, 165, 23}, {85, 206, 165, 30}}},
{Region: "Luxembourg", Group: "Premium UDP Europe", IPs: []net.IP{{5, 253, 204, 19}, {5, 253, 204, 20}, {5, 253, 204, 22}, {5, 253, 204, 23}, {5, 253, 204, 24}, {5, 253, 204, 27}, {5, 253, 204, 29}, {5, 253, 204, 35}, {5, 253, 204, 44}, {5, 253, 204, 45}}},
{Region: "Luxembourg", Group: "Premium TCP Europe", IPs: []net.IP{{5, 253, 204, 9}, {5, 253, 204, 10}, {5, 253, 204, 24}, {5, 253, 204, 28}, {5, 253, 204, 30}, {5, 253, 204, 35}, {5, 253, 204, 38}, {5, 253, 204, 39}, {5, 253, 204, 41}, {5, 253, 204, 46}}},
{Region: "Macao", Group: "Premium TCP Asia", IPs: []net.IP{{84, 252, 92, 131}, {84, 252, 92, 133}, {84, 252, 92, 134}, {84, 252, 92, 135}, {84, 252, 92, 136}, {84, 252, 92, 139}, {84, 252, 92, 141}, {84, 252, 92, 143}, {84, 252, 92, 144}, {84, 252, 92, 145}}},
{Region: "Macao", Group: "Premium UDP Asia", IPs: []net.IP{{84, 252, 92, 131}, {84, 252, 92, 132}, {84, 252, 92, 133}, {84, 252, 92, 138}, {84, 252, 92, 140}, {84, 252, 92, 141}, {84, 252, 92, 142}, {84, 252, 92, 143}, {84, 252, 92, 144}, {84, 252, 92, 145}}},
{Region: "Macedonia", Group: "Premium UDP Europe", IPs: []net.IP{{185, 225, 28, 3}, {185, 225, 28, 4}, {185, 225, 28, 5}, {185, 225, 28, 6}, {185, 225, 28, 7}, {185, 225, 28, 8}, {185, 225, 28, 9}, {185, 225, 28, 10}, {185, 225, 28, 11}, {185, 225, 28, 12}}},
{Region: "Macedonia", Group: "Premium TCP Europe", IPs: []net.IP{{185, 225, 28, 3}, {185, 225, 28, 4}, {185, 225, 28, 5}, {185, 225, 28, 6}, {185, 225, 28, 7}, {185, 225, 28, 8}, {185, 225, 28, 9}, {185, 225, 28, 10}, {185, 225, 28, 11}, {185, 225, 28, 12}}},
{Region: "Malaysia", Group: "Premium TCP Asia", IPs: []net.IP{{139, 5, 177, 69}, {139, 5, 177, 70}, {139, 5, 177, 71}, {139, 5, 177, 72}, {139, 5, 177, 73}, {139, 5, 177, 74}, {139, 5, 177, 75}, {139, 5, 177, 76}, {139, 5, 177, 77}, {139, 5, 177, 78}}},
{Region: "Malaysia", Group: "Premium UDP Asia", IPs: []net.IP{{139, 5, 177, 69}, {139, 5, 177, 70}, {139, 5, 177, 71}, {139, 5, 177, 72}, {139, 5, 177, 73}, {139, 5, 177, 74}, {139, 5, 177, 75}, {139, 5, 177, 76}, {139, 5, 177, 77}, {139, 5, 177, 78}}},
{Region: "Malta", Group: "Premium UDP Europe", IPs: []net.IP{{176, 125, 230, 131}, {176, 125, 230, 132}, {176, 125, 230, 133}, {176, 125, 230, 135}, {176, 125, 230, 136}, {176, 125, 230, 140}, {176, 125, 230, 141}, {176, 125, 230, 142}, {176, 125, 230, 143}, {176, 125, 230, 145}}},
{Region: "Malta", Group: "Premium TCP Europe", IPs: []net.IP{{176, 125, 230, 132}, {176, 125, 230, 133}, {176, 125, 230, 134}, {176, 125, 230, 135}, {176, 125, 230, 136}, {176, 125, 230, 138}, {176, 125, 230, 140}, {176, 125, 230, 142}, {176, 125, 230, 143}, {176, 125, 230, 144}}},
{Region: "Mexico", Group: "Premium UDP USA", IPs: []net.IP{{77, 81, 142, 132}, {77, 81, 142, 136}, {77, 81, 142, 137}, {77, 81, 142, 139}, {77, 81, 142, 140}, {77, 81, 142, 143}, {77, 81, 142, 146}, {77, 81, 142, 149}, {77, 81, 142, 154}, {77, 81, 142, 155}}},
{Region: "Mexico", Group: "Premium TCP USA", IPs: []net.IP{{77, 81, 142, 130}, {77, 81, 142, 132}, {77, 81, 142, 134}, {77, 81, 142, 136}, {77, 81, 142, 138}, {77, 81, 142, 142}, {77, 81, 142, 146}, {77, 81, 142, 150}, {77, 81, 142, 154}, {77, 81, 142, 155}}},
{Region: "Moldova", Group: "Premium UDP Europe", IPs: []net.IP{{178, 175, 130, 243}, {178, 175, 130, 244}, {178, 175, 130, 245}, {178, 175, 130, 246}, {178, 175, 130, 250}, {178, 175, 130, 251}, {178, 175, 130, 252}, {178, 175, 130, 254}, {178, 175, 142, 133}, {178, 175, 142, 134}}},
{Region: "Moldova", Group: "Premium TCP Europe", IPs: []net.IP{{178, 175, 130, 243}, {178, 175, 130, 244}, {178, 175, 130, 245}, {178, 175, 130, 246}, {178, 175, 130, 250}, {178, 175, 130, 252}, {178, 175, 130, 253}, {178, 175, 130, 254}, {178, 175, 142, 132}, {178, 175, 142, 134}}},
{Region: "Monaco", Group: "Premium UDP Europe", IPs: []net.IP{{95, 181, 233, 131}, {95, 181, 233, 134}, {95, 181, 233, 135}, {95, 181, 233, 136}, {95, 181, 233, 137}, {95, 181, 233, 138}, {95, 181, 233, 140}, {95, 181, 233, 141}, {95, 181, 233, 142}, {95, 181, 233, 143}}},
{Region: "Monaco", Group: "Premium TCP Europe", IPs: []net.IP{{95, 181, 233, 131}, {95, 181, 233, 132}, {95, 181, 233, 134}, {95, 181, 233, 136}, {95, 181, 233, 137}, {95, 181, 233, 140}, {95, 181, 233, 141}, {95, 181, 233, 142}, {95, 181, 233, 143}, {95, 181, 233, 144}}},
{Region: "Mongolia", Group: "Premium TCP Asia", IPs: []net.IP{{185, 253, 163, 132}, {185, 253, 163, 134}, {185, 253, 163, 136}, {185, 253, 163, 137}, {185, 253, 163, 138}, {185, 253, 163, 139}, {185, 253, 163, 140}, {185, 253, 163, 142}, {185, 253, 163, 144}, {185, 253, 163, 145}}},
{Region: "Mongolia", Group: "Premium UDP Asia", IPs: []net.IP{{185, 253, 163, 131}, {185, 253, 163, 134}, {185, 253, 163, 136}, {185, 253, 163, 137}, {185, 253, 163, 139}, {185, 253, 163, 140}, {185, 253, 163, 141}, {185, 253, 163, 142}, {185, 253, 163, 144}, {185, 253, 163, 145}}},
{Region: "Montenegro", Group: "Premium UDP Europe", IPs: []net.IP{{176, 125, 229, 132}, {176, 125, 229, 133}, {176, 125, 229, 134}, {176, 125, 229, 135}, {176, 125, 229, 137}, {176, 125, 229, 139}, {176, 125, 229, 140}, {176, 125, 229, 141}, {176, 125, 229, 142}, {176, 125, 229, 145}}},
{Region: "Montenegro", Group: "Premium TCP Europe", IPs: []net.IP{{176, 125, 229, 131}, {176, 125, 229, 134}, {176, 125, 229, 135}, {176, 125, 229, 137}, {176, 125, 229, 138}, {176, 125, 229, 140}, {176, 125, 229, 141}, {176, 125, 229, 142}, {176, 125, 229, 143}, {176, 125, 229, 145}}},
{Region: "Morocco", Group: "Premium UDP Europe", IPs: []net.IP{{95, 181, 232, 131}, {95, 181, 232, 132}, {95, 181, 232, 134}, {95, 181, 232, 135}, {95, 181, 232, 139}, {95, 181, 232, 140}, {95, 181, 232, 141}, {95, 181, 232, 142}, {95, 181, 232, 143}, {95, 181, 232, 144}}},
{Region: "Morocco", Group: "Premium TCP Europe", IPs: []net.IP{{95, 181, 232, 131}, {95, 181, 232, 133}, {95, 181, 232, 134}, {95, 181, 232, 135}, {95, 181, 232, 137}, {95, 181, 232, 138}, {95, 181, 232, 139}, {95, 181, 232, 141}, {95, 181, 232, 142}, {95, 181, 232, 143}}},
{Region: "Netherlands", Group: "Premium TCP Europe", IPs: []net.IP{{84, 17, 47, 2}, {84, 17, 47, 3}, {84, 17, 47, 9}, {84, 17, 47, 15}, {84, 17, 47, 16}, {84, 17, 47, 54}, {84, 17, 47, 69}, {84, 17, 47, 81}, {84, 17, 47, 111}, {195, 181, 172, 72}}},
{Region: "Netherlands", Group: "Premium UDP Europe", IPs: []net.IP{{84, 17, 47, 9}, {84, 17, 47, 34}, {84, 17, 47, 52}, {84, 17, 47, 53}, {84, 17, 47, 66}, {84, 17, 47, 73}, {84, 17, 47, 95}, {139, 28, 217, 222}, {195, 181, 172, 67}, {195, 181, 172, 68}}},
{Region: "New Zealand", Group: "Premium TCP Asia", IPs: []net.IP{{114, 141, 194, 2}, {114, 141, 194, 3}, {114, 141, 194, 4}, {114, 141, 194, 5}, {114, 141, 194, 7}, {114, 141, 194, 8}, {114, 141, 194, 10}, {114, 141, 194, 12}, {114, 141, 194, 13}, {114, 141, 194, 14}}},
{Region: "New Zealand", Group: "Premium UDP Asia", IPs: []net.IP{{114, 141, 194, 2}, {114, 141, 194, 3}, {114, 141, 194, 5}, {114, 141, 194, 6}, {114, 141, 194, 7}, {114, 141, 194, 8}, {114, 141, 194, 9}, {114, 141, 194, 11}, {114, 141, 194, 12}, {114, 141, 194, 13}}},
{Region: "Nigeria", Group: "Premium TCP Europe", IPs: []net.IP{{102, 165, 25, 68}, {102, 165, 25, 69}, {102, 165, 25, 70}, {102, 165, 25, 71}, {102, 165, 25, 72}, {102, 165, 25, 73}, {102, 165, 25, 74}, {102, 165, 25, 76}, {102, 165, 25, 77}, {102, 165, 25, 78}}},
{Region: "Nigeria", Group: "Premium UDP Europe", IPs: []net.IP{{102, 165, 25, 68}, {102, 165, 25, 69}, {102, 165, 25, 70}, {102, 165, 25, 71}, {102, 165, 25, 72}, {102, 165, 25, 74}, {102, 165, 25, 75}, {102, 165, 25, 76}, {102, 165, 25, 77}, {102, 165, 25, 78}}},
{Region: "Norway", Group: "Premium UDP Europe", IPs: []net.IP{{45, 12, 223, 137}, {45, 12, 223, 140}, {45, 12, 223, 141}, {82, 102, 27, 92}, {185, 206, 225, 29}, {185, 206, 225, 231}, {185, 206, 225, 235}, {185, 253, 97, 248}, {185, 253, 97, 249}, {185, 253, 97, 250}}},
{Region: "Norway", Group: "Premium TCP Europe", IPs: []net.IP{{185, 206, 225, 229}, {185, 206, 225, 230}, {185, 206, 225, 231}, {185, 206, 225, 232}, {185, 206, 225, 234}, {185, 253, 97, 236}, {185, 253, 97, 243}, {185, 253, 97, 245}, {185, 253, 97, 247}, {185, 253, 97, 251}}},
{Region: "Pakistan", Group: "Premium TCP Europe", IPs: []net.IP{{103, 76, 3, 244}, {103, 76, 3, 245}, {103, 76, 3, 246}, {103, 76, 3, 247}, {103, 76, 3, 248}, {103, 76, 3, 249}, {103, 76, 3, 250}, {103, 76, 3, 251}, {103, 76, 3, 252}, {103, 76, 3, 253}}},
{Region: "Pakistan", Group: "Premium UDP Europe", IPs: []net.IP{{103, 76, 3, 244}, {103, 76, 3, 245}, {103, 76, 3, 246}, {103, 76, 3, 247}, {103, 76, 3, 248}, {103, 76, 3, 249}, {103, 76, 3, 250}, {103, 76, 3, 251}, {103, 76, 3, 252}, {103, 76, 3, 253}}},
{Region: "Panama", Group: "Premium UDP Europe", IPs: []net.IP{{91, 90, 126, 131}, {91, 90, 126, 132}, {91, 90, 126, 133}, {91, 90, 126, 134}, {91, 90, 126, 135}, {91, 90, 126, 136}, {91, 90, 126, 139}, {91, 90, 126, 141}, {91, 90, 126, 143}, {91, 90, 126, 144}}},
{Region: "Panama", Group: "Premium TCP Europe", IPs: []net.IP{{91, 90, 126, 131}, {91, 90, 126, 132}, {91, 90, 126, 134}, {91, 90, 126, 135}, {91, 90, 126, 137}, {91, 90, 126, 139}, {91, 90, 126, 140}, {91, 90, 126, 142}, {91, 90, 126, 144}, {91, 90, 126, 145}}},
{Region: "Philippines", Group: "Premium UDP Asia", IPs: []net.IP{{188, 214, 125, 35}, {188, 214, 125, 36}, {188, 214, 125, 41}, {188, 214, 125, 44}, {188, 214, 125, 47}, {188, 214, 125, 52}, {188, 214, 125, 53}, {188, 214, 125, 54}, {188, 214, 125, 55}, {188, 214, 125, 60}}},
{Region: "Philippines", Group: "Premium TCP Asia", IPs: []net.IP{{188, 214, 125, 36}, {188, 214, 125, 37}, {188, 214, 125, 39}, {188, 214, 125, 43}, {188, 214, 125, 45}, {188, 214, 125, 47}, {188, 214, 125, 48}, {188, 214, 125, 57}, {188, 214, 125, 58}, {188, 214, 125, 59}}},
{Region: "Poland", Group: "Premium TCP Europe", IPs: []net.IP{{37, 120, 156, 5}, {37, 120, 156, 9}, {37, 120, 156, 19}, {37, 120, 156, 27}, {37, 120, 156, 29}, {37, 120, 156, 35}, {37, 120, 156, 36}, {51, 75, 56, 40}, {51, 75, 56, 41}, {51, 75, 56, 44}}},
{Region: "Poland", Group: "Premium UDP Europe", IPs: []net.IP{{37, 120, 156, 4}, {37, 120, 156, 10}, {37, 120, 156, 16}, {37, 120, 156, 23}, {37, 120, 156, 27}, {37, 120, 156, 29}, {37, 120, 156, 30}, {37, 120, 156, 38}, {37, 120, 156, 39}, {51, 75, 56, 36}}},
{Region: "Portugal", Group: "Premium TCP Europe", IPs: []net.IP{{89, 26, 243, 1}, {89, 26, 243, 98}, {89, 26, 243, 112}, {89, 26, 243, 113}, {89, 26, 243, 115}, {89, 26, 243, 195}, {89, 26, 243, 196}, {89, 26, 243, 197}, {89, 26, 243, 198}, {89, 26, 243, 199}}},
{Region: "Portugal", Group: "Premium UDP Europe", IPs: []net.IP{{89, 26, 243, 1}, {89, 26, 243, 99}, {89, 26, 243, 113}, {89, 26, 243, 115}, {89, 26, 243, 194}, {89, 26, 243, 195}, {89, 26, 243, 196}, {89, 26, 243, 197}, {89, 26, 243, 198}, {89, 26, 243, 199}}},
{Region: "Qatar", Group: "Premium TCP Europe", IPs: []net.IP{{95, 181, 234, 132}, {95, 181, 234, 133}, {95, 181, 234, 134}, {95, 181, 234, 136}, {95, 181, 234, 138}, {95, 181, 234, 140}, {95, 181, 234, 141}, {95, 181, 234, 142}, {95, 181, 234, 143}, {95, 181, 234, 144}}},
{Region: "Qatar", Group: "Premium UDP Europe", IPs: []net.IP{{95, 181, 234, 131}, {95, 181, 234, 132}, {95, 181, 234, 133}, {95, 181, 234, 134}, {95, 181, 234, 137}, {95, 181, 234, 138}, {95, 181, 234, 141}, {95, 181, 234, 142}, {95, 181, 234, 143}, {95, 181, 234, 144}}},
{Region: "Russian Federation", Group: "Premium TCP Europe", IPs: []net.IP{{5, 8, 16, 67}, {5, 8, 16, 71}, {5, 8, 16, 73}, {5, 8, 16, 75}, {5, 8, 16, 76}, {5, 8, 16, 87}, {5, 8, 16, 88}, {5, 8, 16, 89}, {5, 8, 16, 90}, {5, 8, 16, 104}}},
{Region: "Russian Federation", Group: "Premium UDP Europe", IPs: []net.IP{{5, 8, 16, 68}, {5, 8, 16, 75}, {5, 8, 16, 84}, {5, 8, 16, 86}, {5, 8, 16, 87}, {5, 8, 16, 88}, {5, 8, 16, 104}, {5, 8, 16, 106}, {5, 8, 16, 107}, {5, 8, 16, 110}}},
{Region: "Saudi Arabia", Group: "Premium UDP Europe", IPs: []net.IP{{95, 181, 235, 132}, {95, 181, 235, 133}, {95, 181, 235, 134}, {95, 181, 235, 135}, {95, 181, 235, 136}, {95, 181, 235, 137}, {95, 181, 235, 139}, {95, 181, 235, 141}, {95, 181, 235, 142}, {95, 181, 235, 143}}},
{Region: "Saudi Arabia", Group: "Premium TCP Europe", IPs: []net.IP{{95, 181, 235, 134}, {95, 181, 235, 136}, {95, 181, 235, 137}, {95, 181, 235, 138}, {95, 181, 235, 139}, {95, 181, 235, 140}, {95, 181, 235, 141}, {95, 181, 235, 142}, {95, 181, 235, 143}, {95, 181, 235, 144}}},
{Region: "Serbia", Group: "Premium UDP Europe", IPs: []net.IP{{37, 120, 193, 182}, {37, 120, 193, 185}, {37, 120, 193, 187}, {141, 98, 103, 35}, {141, 98, 103, 37}, {141, 98, 103, 39}, {141, 98, 103, 41}, {141, 98, 103, 42}, {141, 98, 103, 44}, {141, 98, 103, 46}}},
{Region: "Serbia", Group: "Premium TCP Europe", IPs: []net.IP{{37, 120, 193, 180}, {37, 120, 193, 182}, {37, 120, 193, 183}, {37, 120, 193, 184}, {37, 120, 193, 190}, {141, 98, 103, 35}, {141, 98, 103, 36}, {141, 98, 103, 39}, {141, 98, 103, 40}, {141, 98, 103, 42}}},
{Region: "Singapore", Group: "Premium TCP Asia", IPs: []net.IP{{37, 120, 151, 59}, {37, 120, 151, 134}, {37, 120, 151, 135}, {37, 120, 151, 136}, {37, 120, 151, 140}, {37, 120, 151, 141}, {84, 17, 39, 172}, {84, 17, 39, 173}, {84, 17, 39, 178}, {84, 17, 39, 179}}},
{Region: "Singapore", Group: "Premium UDP Asia", IPs: []net.IP{{37, 120, 151, 52}, {37, 120, 151, 133}, {37, 120, 151, 138}, {37, 120, 151, 140}, {37, 120, 151, 142}, {84, 17, 39, 171}, {84, 17, 39, 176}, {84, 17, 39, 177}, {84, 17, 39, 179}, {84, 17, 39, 182}}},
{Region: "Slovakia", Group: "Premium TCP Europe", IPs: []net.IP{{185, 245, 85, 227}, {185, 245, 85, 228}, {185, 245, 85, 229}, {185, 245, 85, 230}, {185, 245, 85, 231}, {185, 245, 85, 232}, {185, 245, 85, 233}, {185, 245, 85, 234}, {185, 245, 85, 235}, {185, 245, 85, 236}}},
{Region: "Slovakia", Group: "Premium UDP Europe", IPs: []net.IP{{185, 245, 85, 227}, {185, 245, 85, 228}, {185, 245, 85, 229}, {185, 245, 85, 230}, {185, 245, 85, 231}, {185, 245, 85, 232}, {185, 245, 85, 233}, {185, 245, 85, 234}, {185, 245, 85, 235}, {185, 245, 85, 236}}},
{Region: "Slovenia", Group: "Premium TCP Europe", IPs: []net.IP{{146, 247, 25, 79}, {146, 247, 25, 80}, {146, 247, 25, 81}, {146, 247, 25, 82}, {146, 247, 25, 83}, {146, 247, 25, 84}, {146, 247, 25, 85}, {146, 247, 25, 86}, {146, 247, 25, 88}, {146, 247, 25, 89}}},
{Region: "Slovenia", Group: "Premium UDP Europe", IPs: []net.IP{{146, 247, 25, 79}, {146, 247, 25, 80}, {146, 247, 25, 81}, {146, 247, 25, 82}, {146, 247, 25, 83}, {146, 247, 25, 84}, {146, 247, 25, 86}, {146, 247, 25, 88}, {146, 247, 25, 89}, {146, 247, 25, 90}}},
{Region: "South Africa", Group: "Premium UDP Europe", IPs: []net.IP{{197, 85, 7, 26}, {197, 85, 7, 27}, {197, 85, 7, 28}, {197, 85, 7, 29}, {197, 85, 7, 30}, {197, 85, 7, 31}, {197, 85, 7, 131}, {197, 85, 7, 132}, {197, 85, 7, 133}, {197, 85, 7, 134}}},
{Region: "South Africa", Group: "Premium UDP Asia", IPs: []net.IP{{165, 73, 248, 211}, {165, 73, 248, 213}, {165, 73, 248, 215}, {165, 73, 248, 219}, {165, 73, 248, 221}, {165, 73, 248, 228}, {165, 73, 248, 231}, {165, 73, 248, 232}, {165, 73, 248, 235}, {165, 73, 248, 237}}},
{Region: "South Africa", Group: "Premium TCP Europe", IPs: []net.IP{{197, 85, 7, 26}, {197, 85, 7, 27}, {197, 85, 7, 28}, {197, 85, 7, 29}, {197, 85, 7, 30}, {197, 85, 7, 31}, {197, 85, 7, 131}, {197, 85, 7, 132}, {197, 85, 7, 133}, {197, 85, 7, 134}}},
{Region: "South Africa", Group: "Premium TCP Asia", IPs: []net.IP{{165, 73, 248, 214}, {165, 73, 248, 215}, {165, 73, 248, 216}, {165, 73, 248, 218}, {165, 73, 248, 219}, {165, 73, 248, 221}, {165, 73, 248, 229}, {165, 73, 248, 235}, {165, 73, 248, 236}, {165, 73, 248, 238}}},
{Region: "Spain", Group: "Premium TCP Europe", IPs: []net.IP{{82, 102, 26, 204}, {82, 102, 26, 205}, {84, 17, 62, 130}, {84, 17, 62, 131}, {84, 17, 62, 140}, {84, 17, 62, 147}, {185, 93, 3, 109}, {185, 93, 3, 110}, {185, 93, 3, 112}, {185, 253, 99, 205}}},
{Region: "Spain", Group: "Premium UDP Europe", IPs: []net.IP{{37, 120, 142, 54}, {37, 120, 142, 55}, {37, 120, 142, 58}, {37, 120, 142, 150}, {82, 102, 26, 196}, {82, 102, 26, 218}, {84, 17, 62, 138}, {185, 93, 3, 106}, {185, 93, 182, 139}, {185, 253, 99, 202}}},
{Region: "Sri Lanka", Group: "Premium UDP Europe", IPs: []net.IP{{95, 181, 239, 131}, {95, 181, 239, 134}, {95, 181, 239, 136}, {95, 181, 239, 137}, {95, 181, 239, 138}, {95, 181, 239, 139}, {95, 181, 239, 140}, {95, 181, 239, 141}, {95, 181, 239, 142}, {95, 181, 239, 143}}},
{Region: "Sri Lanka", Group: "Premium TCP Europe", IPs: []net.IP{{95, 181, 239, 131}, {95, 181, 239, 133}, {95, 181, 239, 134}, {95, 181, 239, 135}, {95, 181, 239, 136}, {95, 181, 239, 137}, {95, 181, 239, 140}, {95, 181, 239, 141}, {95, 181, 239, 143}, {95, 181, 239, 144}}},
{Region: "Sweden", Group: "Premium UDP Europe", IPs: []net.IP{{188, 126, 73, 202}, {188, 126, 73, 209}, {188, 126, 73, 215}, {188, 126, 73, 221}, {195, 246, 120, 150}, {195, 246, 120, 156}, {195, 246, 120, 160}, {195, 246, 120, 169}, {195, 246, 120, 170}, {195, 246, 120, 173}}},
{Region: "Sweden", Group: "Premium TCP Europe", IPs: []net.IP{{188, 126, 73, 204}, {188, 126, 73, 214}, {188, 126, 73, 218}, {188, 126, 73, 221}, {195, 246, 120, 145}, {195, 246, 120, 149}, {195, 246, 120, 151}, {195, 246, 120, 154}, {195, 246, 120, 171}, {195, 246, 120, 179}}},
{Region: "Switzerland", Group: "Premium TCP Europe", IPs: []net.IP{{84, 17, 52, 11}, {84, 17, 52, 12}, {84, 17, 52, 22}, {84, 17, 52, 49}, {84, 17, 52, 55}, {84, 17, 52, 62}, {84, 17, 52, 85}, {185, 32, 222, 13}, {185, 189, 150, 61}, {185, 189, 150, 73}}},
{Region: "Switzerland", Group: "Premium UDP Europe", IPs: []net.IP{{84, 17, 52, 20}, {84, 17, 52, 33}, {84, 17, 52, 38}, {84, 17, 52, 51}, {84, 17, 52, 69}, {84, 17, 52, 74}, {185, 32, 222, 16}, {185, 32, 222, 118}, {185, 32, 222, 120}, {195, 225, 118, 52}}},
{Region: "Taiwan", Group: "Premium TCP Asia", IPs: []net.IP{{45, 133, 181, 102}, {45, 133, 181, 103}, {45, 133, 181, 106}, {45, 133, 181, 108}, {45, 133, 181, 109}, {45, 133, 181, 110}, {45, 133, 181, 112}, {45, 133, 181, 116}, {45, 133, 181, 120}, {45, 133, 181, 123}}},
{Region: "Taiwan", Group: "Premium UDP Asia", IPs: []net.IP{{45, 133, 181, 99}, {45, 133, 181, 101}, {45, 133, 181, 105}, {45, 133, 181, 110}, {45, 133, 181, 111}, {45, 133, 181, 114}, {45, 133, 181, 116}, {45, 133, 181, 117}, {45, 133, 181, 121}, {45, 133, 181, 123}}},
{Region: "Thailand", Group: "Premium UDP Asia", IPs: []net.IP{{119, 59, 121, 163}, {119, 59, 121, 165}, {119, 59, 121, 166}, {119, 59, 121, 167}, {119, 59, 121, 168}, {119, 59, 121, 169}, {119, 59, 121, 170}, {119, 59, 121, 172}, {119, 59, 121, 173}, {119, 59, 121, 175}}},
{Region: "Thailand", Group: "Premium TCP Asia", IPs: []net.IP{{119, 59, 98, 213}, {119, 59, 98, 244}, {119, 59, 121, 163}, {119, 59, 121, 164}, {119, 59, 121, 166}, {119, 59, 121, 169}, {119, 59, 121, 170}, {119, 59, 121, 171}, {119, 59, 121, 172}, {119, 59, 121, 175}}},
{Region: "Turkey", Group: "Premium TCP Europe", IPs: []net.IP{{188, 213, 34, 3}, {188, 213, 34, 6}, {188, 213, 34, 10}, {188, 213, 34, 19}, {188, 213, 34, 24}, {188, 213, 34, 28}, {188, 213, 34, 37}, {188, 213, 34, 43}, {188, 213, 34, 104}, {188, 213, 34, 109}}},
{Region: "Turkey", Group: "Premium UDP Europe", IPs: []net.IP{{188, 213, 34, 14}, {188, 213, 34, 22}, {188, 213, 34, 26}, {188, 213, 34, 27}, {188, 213, 34, 30}, {188, 213, 34, 39}, {188, 213, 34, 42}, {188, 213, 34, 43}, {188, 213, 34, 100}, {188, 213, 34, 106}}},
{Region: "Ukraine", Group: "Premium TCP Europe", IPs: []net.IP{{31, 28, 161, 21}, {31, 28, 163, 40}, {31, 28, 163, 43}, {31, 28, 163, 50}, {62, 149, 7, 165}, {62, 149, 7, 166}, {62, 149, 7, 173}, {62, 149, 29, 36}, {62, 149, 29, 48}, {62, 149, 29, 56}}},
{Region: "Ukraine", Group: "Premium UDP Europe", IPs: []net.IP{{31, 28, 161, 20}, {31, 28, 163, 35}, {31, 28, 163, 40}, {31, 28, 163, 42}, {31, 28, 163, 52}, {31, 28, 163, 55}, {62, 149, 7, 172}, {62, 149, 7, 173}, {62, 149, 29, 37}, {62, 149, 29, 58}}},
{Region: "United Arab Emirates", Group: "Premium TCP Europe", IPs: []net.IP{{217, 138, 193, 179}, {217, 138, 193, 180}, {217, 138, 193, 182}, {217, 138, 193, 184}, {217, 138, 193, 185}, {217, 138, 193, 186}, {217, 138, 193, 187}, {217, 138, 193, 188}, {217, 138, 193, 189}, {217, 138, 193, 190}}},
{Region: "United Arab Emirates", Group: "Premium UDP Europe", IPs: []net.IP{{217, 138, 193, 179}, {217, 138, 193, 180}, {217, 138, 193, 182}, {217, 138, 193, 183}, {217, 138, 193, 184}, {217, 138, 193, 185}, {217, 138, 193, 186}, {217, 138, 193, 187}, {217, 138, 193, 188}, {217, 138, 193, 189}}},
{Region: "United Kingdom", Group: "Premium UDP Europe", IPs: []net.IP{{37, 120, 159, 101}, {37, 235, 96, 9}, {81, 92, 206, 164}, {81, 92, 206, 169}, {84, 17, 51, 97}, {84, 17, 51, 123}, {89, 238, 167, 85}, {89, 238, 167, 91}, {109, 169, 14, 97}, {141, 98, 100, 73}}},
{Region: "United Kingdom", Group: "Premium TCP Europe", IPs: []net.IP{{37, 120, 133, 165}, {37, 120, 159, 81}, {37, 235, 96, 9}, {84, 17, 51, 44}, {84, 17, 51, 107}, {89, 238, 167, 39}, {89, 238, 167, 54}, {143, 244, 39, 135}, {143, 244, 39, 221}, {143, 244, 39, 232}}},
{Region: "United States", Group: "Premium UDP USA", IPs: []net.IP{{23, 105, 161, 116}, {143, 244, 51, 84}, {156, 146, 37, 43}, {156, 146, 37, 73}, {156, 146, 37, 100}, {156, 146, 37, 103}, {156, 146, 51, 20}, {156, 146, 51, 103}, {156, 146, 51, 128}, {156, 146, 59, 172}}},
{Region: "United States", Group: "Premium TCP USA", IPs: []net.IP{{89, 187, 171, 139}, {89, 187, 171, 161}, {89, 187, 182, 38}, {143, 244, 51, 111}, {156, 146, 37, 101}, {156, 146, 37, 123}, {156, 146, 49, 133}, {156, 146, 51, 103}, {156, 146, 51, 146}, {185, 242, 5, 115}}},
{Region: "Venezuela", Group: "Premium TCP USA", IPs: []net.IP{{95, 181, 237, 131}, {95, 181, 237, 134}, {95, 181, 237, 136}, {95, 181, 237, 137}, {95, 181, 237, 138}, {95, 181, 237, 139}, {95, 181, 237, 140}, {95, 181, 237, 141}, {95, 181, 237, 142}, {95, 181, 237, 143}}},
{Region: "Venezuela", Group: "Premium UDP USA", IPs: []net.IP{{95, 181, 237, 131}, {95, 181, 237, 132}, {95, 181, 237, 135}, {95, 181, 237, 136}, {95, 181, 237, 137}, {95, 181, 237, 138}, {95, 181, 237, 140}, {95, 181, 237, 142}, {95, 181, 237, 143}, {95, 181, 237, 144}}},
{Region: "Vietnam", Group: "Premium TCP Asia", IPs: []net.IP{{188, 214, 152, 99}, {188, 214, 152, 100}, {188, 214, 152, 101}, {188, 214, 152, 102}, {188, 214, 152, 103}, {188, 214, 152, 105}, {188, 214, 152, 106}, {188, 214, 152, 107}, {188, 214, 152, 108}, {188, 214, 152, 110}}},
{Region: "Vietnam", Group: "Premium UDP Asia", IPs: []net.IP{{188, 214, 152, 99}, {188, 214, 152, 100}, {188, 214, 152, 102}, {188, 214, 152, 103}, {188, 214, 152, 104}, {188, 214, 152, 105}, {188, 214, 152, 106}, {188, 214, 152, 108}, {188, 214, 152, 109}, {188, 214, 152, 110}}},
func CyberghostHostnameChoices(servers []models.CyberghostServer) (choices []string) {
choices = make([]string, len(servers))
for i := range servers {
choices[i] = servers[i].Hostname
}
return makeUnique(choices)
}

View File

@@ -0,0 +1,37 @@
package constants
import (
"github.com/qdm12/gluetun/internal/models"
)
//nolint:lll
const (
ExpressvpnCert = "MIIDTjCCAregAwIBAgIDKzZvMA0GCSqGSIb3DQEBCwUAMIGFMQswCQYDVQQGEwJVUzELMAkGA1UECBMCQ0ExFTATBgNVBAcTDFNhbkZyYW5jaXNjbzEVMBMGA1UEChMMRm9ydC1GdW5zdG9uMRgwFgYDVQQDEw9Gb3J0LUZ1bnN0b24gQ0ExITAfBgkqhkiG9w0BCQEWEm1lQG15aG9zdC5teWRvbWFpbjAgFw0xNjExMDMwMzA2MThaGA8yMDY2MTEwMzAzMDYxOFowgYoxCzAJBgNVBAYTAlZHMQwwCgYDVQQIDANCVkkxEzARBgNVBAoMCkV4cHJlc3NWUE4xEzARBgNVBAsMCkV4cHJlc3NWUE4xHDAaBgNVBAMME2V4cHJlc3N2cG5fY3VzdG9tZXIxJTAjBgkqhkiG9w0BCQEWFnN1cHBvcnRAZXhwcmVzc3Zwbi5jb20wggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCrOYt/KOi2uMDGev3pXg8j1SO4J/4EVWDF7vJcKr2jrZlqD/zuAFx2W1YWvwumPO6PKH4PU9621aNdiumaUkv/RplCfznnnxqobhJuTE2oA+rS1bOq+9OhHwF9jgNXNVk+XX4d0toST5uGE6Z3OdmPBur8o5AlCf78PDSAwpFOw5HrgLqOEU4hTweC1/czX2VsvsHv22HRI6JMZgP8gGQii/p9iukqfaJvGdPciL5p1QRBUQIi8P8pNvEp1pVIpxYj7/LOUqb2DxFvgmp2v1IQ0Yu88SWsFk84+xAYHzfkLyS31Sqj5uLRBnJqx3fIlOihQ50GI72fwPMwo+OippvVAgMBAAGjPzA9MAwGA1UdEwEB/wQCMAAwDgYDVR0PAQH/BAQDAgSwMB0GA1UdDgQWBBSkBM1TCX9kBgFsv2RmOzudMXa9njANBgkqhkiG9w0BAQsFAAOBgQA+2e4b+33zFmA+1ZQ46kWkfiB+fEeDyMwMLeYYyDS2d8mZhNZKdOw7dy4Ifz9Vqzp4aKuQ6j61c6k1UaQQL0tskqWVzslSFvs9NZyUAJLLdGUc5TT2MiLwiXQwd4UvH6bGeePdhvB4+ZbW7VMD7TE8hZhjhAL4F6yAP1EQvg3LDA=="
ExpressvpnRSAKey = "MIIEpAIBAAKCAQEAqzmLfyjotrjAxnr96V4PI9UjuCf+BFVgxe7yXCq9o62Zag/87gBcdltWFr8Lpjzujyh+D1PettWjXYrpmlJL/0aZQn85558aqG4SbkxNqAPq0tWzqvvToR8BfY4DVzVZPl1+HdLaEk+bhhOmdznZjwbq/KOQJQn+/Dw0gMKRTsOR64C6jhFOIU8Hgtf3M19lbL7B79th0SOiTGYD/IBkIov6fYrpKn2ibxnT3Ii+adUEQVECIvD/KTbxKdaVSKcWI+/yzlKm9g8Rb4Jqdr9SENGLvPElrBZPOPsQGB835C8kt9Uqo+bi0QZyasd3yJTooUOdBiO9n8DzMKPjoqab1QIDAQABAoIBAHgsekC0SKi+AOcNOZqJ3pxqophE0V7fQX2KWGXhxZnUZMFxGTc936deMYzjZ1y0lUa6x8cgOUcfqHol3hDmw9oWBckLHGv5Wi9umdb6DOLoZO62+FQATSdfaJ9jheq2Ub2YxsRN0apaXzB6KDKz0oM0+sZ4Udn9Kw6DfuIELRIWwEx4w0v3gKW7YLC4Jkc4AwLkPK03xEA/qImfkCmaMPLhrgVQt+IFfP8bXzL7CCC04rNU/IS8pyjex+iUolnQZlbXntF7Bm4V2mz0827ZVqrgAb/hEQRlsTW3rRkVh+rrdoUE7BCZRTFmRCbLoShjN6XuSf4sAus8ch4UEN12gN0CgYEA4o/tSvij1iPaXLmt4KOEuxqmSGB8MLKhFde8lBbNdrDgxiIH9bH7khKx15XRTX0qLDbs8b2/UJygZG0Aa1kIBqZTXTgeMAuxPRTesALJPdqQ/ROnbJcdFkI7gllrAG8VB0fH4wTRsRd0vWEB6YlCdE107u6LEsLAHxOj9Q5819cCgYEAwXjx9RkQ2qITBx5Ewib8YsltA0n3cmRomPicLlsnKV5DfvyCLpFIsZ1h3f9dUpfxRLwzp8wcoLiq9cCoOGdu1udw/yBTqmhaXWhUK/g77f9Ze2ZB1OEhuyKLYJ1vW/h/Z/a1aPCMxZqsDTPCePsuO8Cez5gqs8LjM3W7EyzRxDMCgYEAvhHrDFt975fSiLoJgo0MPIAGAnBXn+8sLwv3m/FpW+rWF8LTFK/Fku12H5wDpNOdvswxijkauIE+GiJMGMLvdcyx4WHECaC1h73reJRNykOEIZ0Md5BrCZJ1JEzp9Mo8RQhWTEFtvfkkqgApP4g0pSeaMx0StaGG1kt+4IbP+68CgYBrZdQKlquAck/Vt7u7eyDHRcE5/ilaWtqlb/xizz7h++3D5C/v4b5UumTFcyg+3RGVclPKZcfOgDSGzzeSd/hTW46iUTOgeOUQzQVMkzPRXdoyYgVRQtgSpY5xR3O1vjAbahwx8LZ0SvQPMBhYSDbV/Isr+fBacWjl/AipEEwxeQKBgQDdrAEnVlOFoCLw4sUjsPoxkLjhTAgI7CYk5NNxX67Rnj0tp+Y49+sGUhl5sCGfMKkLShiON5P2oxZa+B0aPtQjsdnsFPa1uaZkK4c++SS6AetzYRpVDLmLp7/1CulE0z3O0sBekpwiuaqLJ9ZccC81g4+2j8j6c50rIAct3hxIxw=="
ExpressvpnTLSAuthOpenvpnStaticKeyV1 = "48d9999bd71095b10649c7cb471c1051b1afdece597cea06909b99303a18c67401597b12c04a787e98cdb619ee960d90a0165529dc650f3a5c6fbe77c91c137dcf55d863fcbe314df5f0b45dbe974d9bde33ef5b4803c3985531c6c23ca6906d6cd028efc8585d1b9e71003566bd7891b9cc9212bcba510109922eed87f5c8e66d8e59cbd82575261f02777372b2cd4ca5214c4a6513ff26dd568f574fd40d6cd450fc788160ff68434ce2bf6afb00e710a3198538f14c4d45d84ab42637872e778a6b35a124e700920879f1d003ba93dccdb953cdf32bea03f365760b0ed8002098d4ce20d045b45a83a8432cc737677aed27125592a7148d25c87fdbe0a3f6"
ExpressvpnCA = "MIIF+DCCA+CgAwIBAgIBATANBgkqhkiG9w0BAQ0FADCBhDELMAkGA1UEBhMCVkcxDDAKBgNVBAgMA0JWSTETMBEGA1UECgwKRXhwcmVzc1ZQTjETMBEGA1UECwwKRXhwcmVzc1ZQTjEWMBQGA1UEAwwNRXhwcmVzc1ZQTiBDQTElMCMGCSqGSIb3DQEJARYWc3VwcG9ydEBleHByZXNzdnBuLmNvbTAeFw0xNTEwMjEwMDAwMDBaFw0yNjA0MDEyMTEyMDBaMIGEMQswCQYDVQQGEwJWRzEMMAoGA1UECAwDQlZJMRMwEQYDVQQKDApFeHByZXNzVlBOMRMwEQYDVQQLDApFeHByZXNzVlBOMRYwFAYDVQQDDA1FeHByZXNzVlBOIENBMSUwIwYJKoZIhvcNAQkBFhZzdXBwb3J0QGV4cHJlc3N2cG4uY29tMIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAxzXvHZ25OsESKRMQFINHJNqE9kVRLWJS50oVB2jxobudPhCsWvJSApvar8CB2RrqkVMhXu2HT3FBtDL91INg070qAyjjRpzEbDPWqQ1+G0tk0sjiJt2mXPJK2IlNFnhe6rTs09Pkpcp8qRhfZay/dIlmagohQAr4JvYL1Ajg9A3sLb8JkY03H6GhOF8EKYTqhrEppCcg4sQKQhNSytRoQAm8Ta+tnTYIedwWpqjUXP9YXFOvljPaixfYug24eAkpTjeuWTcELSyfnuiBeK+z9+5OYunhqFt2QZMq33kLFZGMN2gHRCzngxxphurypsPRo7jiFgQI1yLt8uZsEZ+otGEK91jjKfOC+g9TBy2RUtxk1neWcQ6syXDuc3rBNrGA8iM0ZoEqQ1BC8xWr3NYlSjqN+1mgpTAX3/Dxze4GzHd7AmYaYJV8xnKBVNphlMlg1giCAu5QXjMxPbfCgZiEFq/uq0SOKQJeT3AI/uVPSvwCMWByjyMbDpKKAK8Hy3UT5m4bCNu8J7bxj+vdnq0A2HPwtF0FwBl/TIM3zNsyFrZZ0j6jLRT50mFsgDBKcD4L/J5rjdCsKPu5rodhxe38rCx2GknP1Zkov4yoVCcR48+CQwg3oBkq0/EflvWUvcYApzs9SomUM/g+8Q/V0WOfJmFWuxN9YntZlnzHRSRjrvMCAwEAAaNzMHEwHQYDVR0OBBYEFIzmQGj8xS+0LLklwqHD45VVOZRJMB8GA1UdIwQYMBaAFIzmQGj8xS+0LLklwqHD45VVOZRJMA8GA1UdEwEB/wQFMAMBAf8wCwYDVR0PBAQDAgEGMBEGCWCGSAGG+EIBAQQEAwIBFjANBgkqhkiG9w0BAQ0FAAOCAgEAbHfuMKtojm1NgX7qSU2Rm2B5L8G0FuFP0L40dj8O5WHt45j2z8coMK90vrUnQEZNQmRzot7v3XjVzVlxBWYSsCEApTsSDNi/4BNFP8H/BUUtJuy2GFTO4wDVJnqNkZOHBmyVD75s1Y+W8a+zB4jkMeDEhOHZdwQ0l1fJDDgXal5f1UT5F5WH6/RwHmWTwX4GxuCiIVtx70CjkXqhM8yZtTp1UtHLRNYcNSIes0vrAPHPgoA5z9B8UvsOjuP+mfcjzi0LGGrY+2pJu0BKO2dRnarIZZABETIisI3FokoTszx5jpRPyxyUTuRDKWHrvi0PPtOmC8nFahfugWFUi6uBsqCaSeuex+ahnTPCq0b1l0Ozpg0YeE8CW1TL9Y92b01up2c+PP6wZOIm3JyTH+L5smDFbh80V42dKyGNdPXMg5IcJhj3YfAy4k8h/qbWY57KFcIzKx40bFsoI7PeydbGtT/dIoFLSZRLW5bleXNgG9mXZp270UeEC6CpATCS6uVl8LVT1I02uulHUpFaRmTEOrmMxsXGt6UAwYTY55K/B8uuID341xKbeC0kzhuN2gsL5UJaocBHyWK/AqwbeBttdhOCLwoaj7+nSViPxICObKrg3qavGNCvtwy/fEegK9X/wlp2e2CFlIhFbadeXOBr9Fn8ypYPP17mTqe98OJYM04="
)
func ExpressvpnCountriesChoices(servers []models.ExpressvpnServer) (choices []string) {
choices = make([]string, len(servers))
for i := range servers {
choices[i] = servers[i].Country
}
return makeUnique(choices)
}
func ExpressvpnCityChoices(servers []models.ExpressvpnServer) (choices []string) {
choices = make([]string, len(servers))
for i := range servers {
choices[i] = servers[i].City
}
return makeUnique(choices)
}
func ExpressvpnHostnameChoices(servers []models.ExpressvpnServer) (choices []string) {
choices = make([]string, len(servers))
for i := range servers {
choices[i] = servers[i].Hostname
}
return makeUnique(choices)
}

View File

@@ -0,0 +1,27 @@
package constants
import (
"github.com/qdm12/gluetun/internal/models"
)
//nolint:lll
const (
FastestvpnCertificate = "MIIFQjCCAyqgAwIBAgIIUfxepT+rr8owDQYJKoZIhvcNAQEMBQAwPzELMAkGA1UEBhMCS1kxEzARBgNVBAoTCkZhc3Rlc3RWUE4xGzAZBgNVBAMTEkZhc3Rlc3RWUE4gUm9vdCBDQTAeFw0xNzA5MTYwMDAxNDZaFw0yNzA5MTQwMDAxNDZaMD8xCzAJBgNVBAYTAktZMRMwEQYDVQQKEwpGYXN0ZXN0VlBOMRswGQYDVQQDExJGYXN0ZXN0VlBOIFJvb3QgQ0EwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQC1Xj+WfPTozFynFqc+c3CVrggIllaXEl5bY5VgFynXkqCTM6lSrfC4pNjGXUbqWe6RnGJbM4/6kUn+lQDjFSQV1rzP2eDS8+r5+X2WXh4AoeNRUWhvSG+HiHD/B2EFK+Nd5BRSdUjpKWAtsCmT2bBt7nT0jN1OdeNrLJeyF8siAqv/oQzKznF9aIe/N01b2M8ZOFTzoXi2fZAckgGWui8NB/lzkVIJqSkAPRL8qiJLuRCPVOX1PFD8vV//R8/QumtfbcYBMo6vCk2HmWdrh5OQHPxb3KJtbtG+Z1j8x6HGEAe17djYepBiRMyCEQvYgfD6tvFylc4IquhqE9yaP60PJod5TxpWnRQ6HIGSeBm+S+rYSMalTZ8+pUqOOA+IQCYpfpx6EKIJL/VsW2C7cXdvudxDhXPI5lR/QidCb9Ohq3WkfxXaYwzrngdg2avmNqId9R4KESuM9GoHW0dszfyBCh5wYfeaffMElfDam3B92NUwyhZwtIiv623WVXY9PPz+EDjSJsIAu2Vi1vdJyA4nD4k9Lwmx/1zTc/UaYVLsiBqL2WdfvFTeoWoV+dNxQXSEPhB8gwi8x4O4lZW0cwVy/6fa8KMY8gZbcbSTr7U5bRERfW8l+jY+mYKQ/M/ccgpxaHiw1/+4LWfbJQ7VhJJrTyN0C36FQzY1URkSXg+53wIDAQABo0IwQDAPBgNVHRMBAf8EBTADAQH/MA4GA1UdDwEB/wQEAwIBBjAdBgNVHQ4EFgQUmVEL4x6xdCqiqu2OBLs27EA8xGYwDQYJKoZIhvcNAQEMBQADggIBABCpITvO1+R4T9v2+onHiFxU5JjtCZ0zkXqRCMp/Z0UIYbeo1p07pZCPAUjBfGPCkAaR++OiG9sysALdJf8Y6HQKcyuAcWUqQnaIhoZ2JcAP7EKq7uCqsMhcYZD/j3O/3RPtSW5UOx6ItDU+Ua0t9Edho9whNw0VQXmo1JjYoP3FzPjuKoDWTSO1q5eYlZfwcTcs55O2shNkFafPg/6cCm5j6v9nyHrM3sk4LjkrBPUXVx2m/aoz219t8O9Ha9/CdMKXsPO/8gTUzpgnzSgPnGnBmi5xr1nspVN8X4E2f3D+DKqBim3YgslD68NcuFQvJ0/BxZzWVbrr+QXoyzaiCgXuogpIDc2bB6oRXqFnHNz36d4QJmJdWdSaijiS/peQ6EOPgOZ1GuObLWlDCBZLNeQ+N6QaiJxVO4XUj/s22i1IRtwdz84TRHrbWiIpEymsqmb/Ep5r4xV5d6+791axclfOTH7tQrY/SbPtTJI4OEgNekI8YfadQifpelF82MsFFEZuaQn0lj+fvLGtE/zKh3OdLTxRc5TAgBB+0T81+JQosygNr2aFFG0hxar1eyw/gLeG8H+7Ie50pyPvXO4OgB6Key8rSExpilQXlvAT1qX0qS3/K1i/9QkSE9ftIPT6vtwLV2sVQzfyanI4IZgWC6ryhvNLsRn0NFnQclor0+aq"
FastestvpnOpenvpnStaticKeyV1 = "697fe793b32cb5091d30f2326d5d124a9412e93d0a44ef7361395d76528fcbfc82c3859dccea70a93cfa8fae409709bff75f844cf5ff0c237f426d0c20969233db0e706edb6bdf195ec3dc11b3f76bc807a77e74662d9a800c8cd1144ebb67b7f0d3f1281d1baf522bfe03b7c3f963b1364fc0769400e413b61ca7b43ab19fac9e0f77e41efd4bda7fd77b1de2d7d7855cbbe3e620cecceac72c21a825b243e651f44d90e290e09c3ad650de8fca99c858bc7caad584bc69b11e5c9fd9381c69c505ec487a65912c672d83ed0113b5a74ddfbd3ab33b3683cec593557520a72c4d6cce46111f56f3396cc3ce7183edce553c68ea0796cf6c4375fad00aaa2a42"
)
func FastestvpnCountriesChoices(servers []models.FastestvpnServer) (choices []string) {
choices = make([]string, len(servers))
for i := range servers {
choices[i] = servers[i].Country
}
return makeUnique(choices)
}
func FastestvpnHostnameChoices(servers []models.FastestvpnServer) (choices []string) {
choices = make([]string, len(servers))
for i := range servers {
choices[i] = servers[i].Hostname
}
return makeUnique(choices)
}

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