Compare commits

..

742 Commits

Author SHA1 Message Date
Quentin McGaw
4b86f29f59 fix(docker): openvpn 2.4.12-r0 install 2022-03-31 20:52:31 +00:00
Quentin McGaw
12eafa0f65 fix(env): OPENVPN_FLAGS functionality 2022-03-31 20:45:40 +00:00
Quentin McGaw
7acca7bc10 fix(ci): docker metadata image tags
- Move metata as top step in publish workflow
- Simplify `v0.x.x` check
- Dynamically determine base branch
2022-03-22 08:56:56 +00:00
Quentin McGaw
5cc55c92fa fix(health): use TCP dialing instead of ping
- `HEALTH_TARGET_ADDRESS` to replace `HEALTH_ADDRESS_TO_PING`
- Remove `github.com/go-ping/ping` dependency
- Dial TCP the target address, appending `:443` if port is not set
2022-03-21 20:56:33 +00:00
Quentin McGaw (desktop)
55e609cbf4 fix(ci): release tag semver check 2022-01-23 15:13:26 +00:00
Kazi
f7319eb7a5 docs(docker-compose): add container_name commented line (#806) 2022-01-22 16:44:16 -08:00
Quentin McGaw (desktop)
2cff64fd80 fix(settings): allow DNS_UPDATE_PERIOD=0 2022-01-23 00:36:45 +00:00
Quentin McGaw (desktop)
fdc0db07e0 fix(updater): do not allow or set custom provider 2022-01-22 22:23:40 +00:00
dependabot[bot]
779cb18590 Chore(deps): Bump github.com/breml/rootcerts from 0.2.0 to 0.2.1 (#769)
Bumps [github.com/breml/rootcerts](https://github.com/breml/rootcerts) from 0.2.0 to 0.2.1.
- [Release notes](https://github.com/breml/rootcerts/releases)
- [Commits](https://github.com/breml/rootcerts/compare/v0.2.0...v0.2.1)

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

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

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-01-22 13:34:43 -08:00
Quentin McGaw (desktop)
a1a1128d6d fix(settings): trim trailing new line from files 2022-01-19 00:38:04 +00:00
Quentin McGaw (desktop)
7a50daca7c feat(Dockerfile): entrypoint name to gluetun-entrypoint 2022-01-19 00:23:50 +00:00
Quentin McGaw
2ad23a09e8 fix(dockerfile): change SHADOWSOCKS_ADDRESS to SHADOWSOCKS_LISTENING_ADDRESS 2022-01-13 17:10:09 +00:00
Quentin McGaw
7e2ea4d74d hotfix(custom): allow empty user and password for OpenVPN 2022-01-13 15:18:39 +00:00
Quentin McGaw
4bdce76041 hotfix(httpproxy): Retro-compat with old env vars 2022-01-12 14:40:19 +00:00
Quentin McGaw
6f5a78c22b hotfix(settings): hostnames merging 2022-01-07 22:41:57 +00:00
Quentin McGaw
d6d529278e feat(privado): update servers data 2022-01-07 22:32:04 +00:00
Quentin McGaw
a430d15ac5 feat(protonvpn): update built-in servers data 2022-01-07 22:29:45 +00:00
Quentin McGaw
b0c2d5f299 hotfix(updater): defaults for settings in CLI mode 2022-01-07 22:29:30 +00:00
Quentin McGaw
b32c01c11a fix(custom): OPENVPN_CUSTOM_CONFIG 2022-01-07 15:12:49 +00:00
Quentin McGaw
05f42f0cb8 fix(httpproxy): HTTPPROXY_PORT variable key 2022-01-07 15:00:23 +00:00
Quentin McGaw
3efe3a524a fix(control-server): listening port value 2022-01-07 09:42:15 +00:00
Quentin McGaw
8a788dfca5 fix(settings): read env for control server 2022-01-07 09:37:31 +00:00
Quentin McGaw
d5f1589ea1 fix(settings): VPNSP value read as lowercase 2022-01-07 09:23:45 +00:00
Quentin McGaw
c40c8413b5 fix(custom): settings parsing for custom VPNSP 2022-01-07 09:23:29 +00:00
Quentin McGaw
ae074dfb2b hotfix(surfshark): REGION retro-compatibility 2022-01-06 19:16:14 +00:00
Quentin McGaw
2c8a8f6cd5 fix(settings): HTTP proxy default address 2022-01-06 17:47:06 +00:00
Quentin McGaw
4c7a09c228 fix(settings): change 2^16 to 65535 2022-01-06 17:42:10 +00:00
Quentin McGaw
ab39edc692 fix(settings): system ID max value 2022-01-06 16:48:06 +00:00
Quentin McGaw
6132cd9df2 fix(pia): encryption strong accepted 2022-01-06 15:04:40 +00:00
Quentin McGaw
7d824a5179 chore(settings): refactor settings processing (#756)
- Better settings tree structure logged using `qdm12/gotree`
- Read settings from environment variables, then files, then secret files
- Settings methods to default them, merge them and override them
- `DNS_PLAINTEXT_ADDRESS` default changed to `127.0.0.1` to use DoT. Warning added if set to something else.
- `HTTPPROXY_LISTENING_ADDRESS` instead of `HTTPPROXY_PORT` (with retro-compatibility)
2022-01-06 06:40:23 -05:00
Quentin McGaw
46738b2934 fix(hidemyass): REGION validation 2021-12-25 11:41:47 +00:00
Quentin McGaw
ca82fcb48e feat(docker): upgrade Alpine to 3.15 2021-12-14 18:16:51 +00:00
Quentin McGaw
cfa3bb3b64 feat(internal/wireguard): opportunistic kernelspace
- Auto detect if kernelspace implementation is available
- Fallback to Go userspace implementation if kernel is not available
2021-12-14 11:03:36 +00:00
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
Quentin McGaw
1748a2ae12 Fix: HTTP proxy password and log settings reading 2021-02-26 03:32:26 +00:00
Quentin McGaw
eff46aa97a Fix firewall settings parsing, fixes #392 2021-02-21 02:39:34 +00:00
Quentin McGaw
9fb186af75 Documentation: update issue templates 2021-02-20 22:29:33 +00:00
Quentin McGaw
f1b1001863 Torguard support (#387)
See discussion on #374
2021-02-17 20:36:30 -05:00
Quentin McGaw
c5af536299 Maintenance: deduplicate PIA servers by protocols 2021-02-16 13:06:58 +00:00
Quentin McGaw
b9b2f691a5 Fix: pia updater for TCP, fixes #388 2021-02-16 13:06:51 +00:00
fgeertsema
bdc8817672 Fix: HTTP proxy: return the response of a redirect, do not follow (#384)
Authored-by: Fernand Geertsema <fernand@web-iq.eu>
2021-02-15 08:40:51 -05:00
Quentin McGaw
a55acb2816 CI: Alpine s390x build removed (periodic crashes) 2021-02-14 18:59:27 +00:00
Quentin McGaw
d686c76db3 Fix: Privado SERVER_HOSTNAME selection 2021-02-14 16:40:48 +00:00
Quentin McGaw
30c1ae651e Documentation: new provider issue template 2021-02-14 16:31:31 +00:00
Quentin McGaw
adaad62fbd Feature: updater: no sleep for last DNS resolution 2021-02-12 21:27:26 +00:00
Quentin McGaw
fe5ec205fc Fix: updater uses plaintext DNS to avoid getting blocked by Unbound (#380) 2021-02-12 14:20:07 -05:00
Quentin McGaw
576400e0d9 Fix: Windscribe SERVER_HOSTNAME, fixes #379 2021-02-12 15:05:35 +00:00
Quentin McGaw
f08a03106f Feature: updater changes to have more VPN IP addresses (#364) 2021-02-11 08:40:25 -05:00
Quentin McGaw
f852b7789e Fix: surfshark: restore ping settings 2021-02-09 03:03:08 +00:00
Quentin McGaw
b0bd06bdc5 Feature: only log health when health state changes 2021-02-09 02:45:50 +00:00
Quentin McGaw
84787f0ea2 Fix: restore nordvpn ping settings, refers to #368 2021-02-09 02:30:39 +00:00
Quentin McGaw
f69b3dbbe6 Fix: Windscribe: TLS keys out of sync (#347)
* Add reneg-sec 0
* Add ncp-disable
2021-02-08 20:49:54 -05:00
Quentin McGaw
ec5ec6f02c Fix: defaults to run openvpn as root 2021-02-08 00:05:54 +00:00
Quentin McGaw
5d681e635b Fix: restore surfshark ping to 15s 2021-02-08 00:05:22 +00:00
Quentin McGaw
3deb65b529 Feature: log out country, region and city of IP 2021-02-08 00:01:14 +00:00
Quentin McGaw
3e527fee8b Fixes #370 (bug introduced) 2021-02-07 18:15:31 +00:00
Quentin McGaw
b1f1f94a76 Maintenance: remove some type aliases 2021-02-06 18:31:14 +00:00
Quentin McGaw
43e140e6cc Fix linting errors 2021-02-06 17:16:58 +00:00
Quentin McGaw
7ca9d445f1 Maintenance: package comments 2021-02-06 16:26:23 +00:00
Quentin McGaw
90aaf71270 Configuration package (#369) 2021-02-06 11:05:50 -05:00
Quentin McGaw
4f2570865c Add pull filter ignore ping restart
- Refers to OpenVPN reports error #368
2021-02-05 03:16:19 +00:00
Quentin McGaw
81556ec2e1 Maintenance: improve DNS settings log 2021-02-01 01:22:46 +00:00
Quentin McGaw
dd5a9c6067 Fix: empty connection for NordVPN and Windscribe 2021-01-31 18:45:58 +00:00
Quentin McGaw
982c50c756 Improve panic message for empty connection 2021-01-31 18:42:58 +00:00
Quentin McGaw
3c7dc9b9ad Feature: Private Internet Access custom port 2021-01-31 01:27:13 +00:00
Quentin McGaw
8f4354936c Fix: remove PureVPN route option (#339) 2021-01-30 19:54:40 -05:00
Quentin McGaw
e50941277f Fix: Alpine to 3.12 for 32 bit compatibility 2021-01-31 00:46:53 +00:00
Quentin McGaw
a72647b925 Feature: update servers information with more IP addresses (#365) 2021-01-30 14:08:14 -05:00
Quentin McGaw
e254849009 Fix: update mechanism for PIA and update servers
- Separate TCP and UDP servers as they have different CNs
- Update server hardcoded information constants
2021-01-30 18:22:15 +00:00
Quentin McGaw
5757f0e201 Fix: Only log subproc error when it's not nil 2021-01-30 18:07:12 +00:00
Quentin McGaw
75fdf7adab Fix: Pass down context to CLI commands 2021-01-30 17:29:27 +00:00
Quentin McGaw
702eafae4c Feature: update Surfshark servers information 2021-01-29 18:53:16 +00:00
Quentin McGaw
4abb8cd87f Add panic checks 2021-01-29 00:32:43 +00:00
Quentin McGaw
5194361f3b Fix public IP on restarts, refers to 359 2021-01-29 00:06:55 +00:00
Quentin McGaw
bc83b75634 (Fix) Lint errors 2021-01-26 01:09:09 +00:00
Quentin McGaw
b1ff95affa Maintenance: Fix exit race condition 2021-01-26 01:04:15 +00:00
Quentin McGaw
a243d48fb1 Maintenance: improve stream merging 2021-01-26 04:17:22 +00:00
Quentin McGaw
937d09f1c3 CI: Fix publish job CI 2021-01-23 16:58:46 +00:00
dependabot[bot]
3769092888 Bump github.com/stretchr/testify from 1.6.1 to 1.7.0 (#344) 2021-01-23 11:25:08 -05:00
Quentin McGaw
75281dee07 CI: remove risv64 as Alpine does not support it 2021-01-23 16:22:28 +00:00
Quentin McGaw
26a7c5eaef Feature: openvpn exits on TLS error 2021-01-22 13:36:56 +00:00
Quentin McGaw
1e8254fabf CI: Update golangci-lint to v1.35.2 2021-01-22 13:32:47 +00:00
Quentin McGaw
43b6509b43 Feature: upgrade to Alpine 3.13 2021-01-22 13:32:05 +00:00
Quentin McGaw
204c5b2446 Feature: add 10s ping with 60s exit ping for all 2021-01-22 13:30:06 +00:00
Quentin McGaw
7ab3347604 CI: Remove duplicate COPY in Dockerfile 2021-01-22 13:21:42 +00:00
Quentin McGaw
2f24a79d4d CI: Go mod tidy check 2021-01-22 13:20:54 +00:00
Quentin McGaw
819b1fe0f6 CI: Merge workflows in build.yml 2021-01-22 13:19:45 +00:00
Quentin McGaw
9f04b2d56c (fix) Update Nordvpn servers timestamp 2021-01-20 03:50:38 +00:00
Quentin McGaw
5eff5fac67 Update Nordvpn servers list 2021-01-20 03:44:55 +00:00
Quentin McGaw
d033d08c4d Maintenance: unit tests utils in provider package 2021-01-20 03:36:36 +00:00
Quentin McGaw
25644d061e Fix build (lint error) 2021-01-19 14:20:57 +00:00
Quentin McGaw
e7372f22cb Feature: OPENVPN_MSSFIX variable 2021-01-19 02:55:38 +00:00
Quentin McGaw
4530dd4fea Maintenance: OpenVPN BuildConf arity reduced 2021-01-19 02:42:16 +00:00
Quentin McGaw
072528af83 CI: Dockerfile fixes
- Pin xcputranslate version
- Update xcputranslate usage
- Set default BUILDPLATFORM to linux/amd64
2021-01-18 00:58:47 +00:00
Quentin McGaw
2c4d577f23 Maintenance: Update golibs and update params 2021-01-10 23:06:09 +00:00
Quentin McGaw
edd67e3473 Remove pull_request_target based workflow 2021-01-08 03:13:40 +00:00
Quentin McGaw
f389642dba Maintenance: Improve Go devcontainer settings 2021-01-08 02:27:48 +00:00
Quentin McGaw
b8690c7f83 CI: Change microbadger hook to gluetun 2021-01-08 02:25:41 +00:00
Quentin McGaw
06b809a492 Maintenance: Improve .dockerignore 2021-01-08 02:24:31 +00:00
Quentin McGaw
2ceda2faaa Documentation: Move sections to Wiki 2021-01-08 02:24:15 +00:00
Quentin McGaw
c7fc3afc21 Fix: DNS_KEEP_NAMESERVER behavior 2021-01-06 21:52:55 +00:00
Quentin McGaw
af57043afd Add docker and gomod to dependabot config 2021-01-06 04:23:09 +00:00
Quentin McGaw
4a85f3660c CI: Further reworking of workflows 2021-01-06 04:22:56 +00:00
dependabot[bot]
69713f34b2 CI: Bump crazy-max/ghaction-github-labeler from v1 to v3.1.1 (#335) 2021-01-05 22:41:01 -05:00
Quentin McGaw
55801597c6 Feature: Supports s390x and ppc64le 2021-01-06 01:31:04 +00:00
Quentin McGaw
ff3cc98d46 Maintenance: dependabot monitoring of Actions 2021-01-06 01:28:52 +00:00
Quentin McGaw
79489796ae CI: Add QEMU setup before buildx 2021-01-06 06:23:03 +00:00
Quentin McGaw
8e495494fd CI: Faster builds with buildx and xcputranslate 2021-01-06 06:10:42 +00:00
Quentin McGaw
1abb716bb6 Maintenance: Fix test data race 2021-01-06 06:09:19 +00:00
Quentin McGaw
3f012dd7a3 CI: rework Github build workflows and Dockerfile 2021-01-06 06:02:31 +00:00
Quentin McGaw
bf6bab7963 (Fix): Exit code 0 when expected (healthcheck fix) 2021-01-04 13:41:56 +00:00
Quentin McGaw
9db10f56ef Maintenance: remove vscode workspace directory 2021-01-04 03:50:31 +00:00
Quentin McGaw
3b91e351b7 Maintenance: using channels instead of wrap functions 2021-01-04 01:49:05 +00:00
Quentin McGaw
657937d272 Maintenance: Add waitgroup to collectStreamLines 2021-01-04 01:46:50 +00:00
Quentin McGaw
d294fbab15 Bug fix: Unbound log line processing, fix #333 2021-01-04 01:44:24 +00:00
Quentin McGaw
cfbf5624e1 Maintenance: rework main function 2021-01-04 01:40:07 +00:00
Quentin McGaw
c833e9a1a8 CI: Remove microbadger web hook for buildx branch and release 2021-01-03 04:03:13 +00:00
Quentin McGaw
f1b261163b Fix DNS_KEEP_NAMESERVER behavior 2021-01-03 03:52:07 +00:00
Quentin McGaw
4553240601 Feature: Improve DNS settings start log 2021-01-03 00:51:47 +00:00
Quentin McGaw
007a4536c7 Code: Access control subnets settings for Unbound 2021-01-03 00:36:03 +00:00
Quentin McGaw
31cf5d4a5a Fix #331 (DNS ready signaling fixed) 2021-01-02 23:51:05 +00:00
Quentin McGaw
3e3bd05c79 CI: Only test and lint on amd64 builds 2021-01-02 23:46:30 +00:00
Quentin McGaw
20deaf2950 Bug fix: DNS setup failure loop behavior 2021-01-02 20:39:43 +00:00
Quentin McGaw
680aef62ee (Bug fix): Fix Unbound
- Set binary filepath
- Set ca certs filepath
2021-01-02 20:39:24 +00:00
Quentin McGaw
f5eb4887a7 (Bug fix): DOT provider check 2021-01-02 19:24:01 +00:00
Quentin McGaw
dc3452c5b7 Feature: Log passed after healthcheck succeeds after a failure 2021-01-02 18:36:15 +00:00
Quentin McGaw
a67efd1ad1 Code maintenance: Using qdm/dns and qdm12/updated 2021-01-02 18:31:39 +00:00
Quentin McGaw
5dcbe79fa8 Move OS package to golibs 2021-01-02 01:57:00 +00:00
Quentin McGaw
574ac9a603 Maintenance: update buildx Github workflow to v3 2021-01-01 20:46:52 +00:00
Quentin McGaw
6871444728 Change: remove decomissioned SecureDNS option 2021-01-01 20:45:11 +00:00
Quentin McGaw
f4db7e3e53 Change: remove LibreDNS, it does'nt support DNSSEC 2021-01-01 20:44:01 +00:00
Quentin McGaw
da92b6bfb9 Bug fix: Privado server selection 2020-12-31 21:57:26 +00:00
Quentin McGaw
d713782fe1 Change: Use SERVER_HOSTNAME instead of HOSTNAME 2020-12-31 21:50:28 +00:00
Quentin McGaw
02cde5f50b Code maintenance: consistent proto type conversion 2020-12-31 21:39:34 +00:00
Quentin McGaw
c5a7a83d3a Bug fix: do not fail if servers.json is empty 2020-12-31 21:19:29 +00:00
Quentin McGaw
6655a1a5e6 Bug fix: Update hardcoded Purevpn server data
- Refers to #320
2020-12-31 21:07:49 +00:00
Quentin McGaw
b8cb181070 Bug fix: PureVPN updater from ZIP files
- Fix #317
- Refers to #320
2020-12-31 21:07:30 +00:00
Quentin McGaw
a56471fe73 Code maintenance: rework ovpn host extraction 2020-12-31 20:35:49 +00:00
Quentin McGaw
8c769812ae Documentation: minor readme improvements 2020-12-31 04:49:18 +00:00
Quentin McGaw
f7a842e4ee Documentation: readme sections moved to Wiki 2020-12-31 04:40:04 +00:00
Quentin McGaw
23c0334f68 Documentation: Add visitors count to readme 2020-12-31 03:49:01 +00:00
Quentin McGaw
e2ee7a0408 Documentation: minor issue template update 2020-12-31 03:00:15 +00:00
Quentin McGaw
8f862b3df7 Bug fix: Remove trail newline from secrets
- Fix #330
2020-12-31 02:03:51 +00:00
Quentin McGaw
ae1f91a997 Documentation: Update Docker image labels 2020-12-30 22:30:59 +00:00
Quentin McGaw
d4fb76770f Documentation: Moare badges and metadata 2020-12-30 22:29:18 +00:00
Quentin McGaw
ea28c791e6 Code maintenance: http proxy starts from Run func 2020-12-30 22:02:47 +00:00
Quentin McGaw
251555f859 Code maintenance: Shadowsocks loop refactor 2020-12-30 22:01:08 +00:00
Quentin McGaw
fa7bda7ee4 Code maintenance: remove unneeded defaultInterface in Shadowsocks 2020-12-30 21:43:45 +00:00
Quentin McGaw
f385c4203a Bug fix: truncate /etc/resolv.conf, fixing DNS
- Refers to #326
- Refers to #329
2020-12-30 20:48:41 +00:00
Quentin McGaw
1e4243dedb Bug fix: Stop DOT if disabled by new settings 2020-12-30 20:38:59 +00:00
Quentin McGaw
5f78ee7b79 Bug fix: missing mutex Unlock in DNS set settings 2020-12-30 20:37:14 +00:00
Quentin McGaw
c6eb5c1785 Bug fix: Plaintext DNS fix (#326, #329) 2020-12-30 20:36:19 +00:00
Quentin McGaw
11338b6382 Feature: faster healthcheck, fix #283 2020-12-30 19:34:11 +00:00
Quentin McGaw
6f3a074e00 Code maintenance: HTTP proxy loop reworked
- Blocking method calls on loop
- Restart proxy when settings change
- Detect server crash error and restart it
2020-12-30 18:44:46 +00:00
Quentin McGaw
e827079604 Code maintenance: updater loop waitgroup 2020-12-30 18:32:58 +00:00
Quentin McGaw
cf66db8d4b Bug fix: Stopping updater loop deadlock 2020-12-30 18:29:28 +00:00
Quentin McGaw
25acbf8501 Feature: Increasing backoff time for crashes
- Fix #247
2020-12-30 17:22:54 +00:00
Quentin McGaw
e4c7a887d2 Bug fix: healthcheck uses DOT via default resolver 2020-12-30 16:43:08 +00:00
Quentin McGaw
fb8a615660 Feature: Update PIA servers using v5 PIA API 2020-12-30 15:54:13 +00:00
Quentin McGaw
1d9d49f406 Bug fix: Privado settings log 2020-12-30 15:34:07 +00:00
Quentin McGaw
0069b59ffe Change: remove redundant dns over tls log 2020-12-30 15:29:40 +00:00
Quentin McGaw
d4ba1b1e09 Bug fix: larger timeout for healtcheck 2020-12-30 15:24:46 +00:00
Quentin McGaw
3a20b84f3a Documentation: readme changes
- Remove videos section (outdated)
- Add quick links section for help and support
- Simplify support section
2020-12-29 23:00:55 +00:00
Quentin McGaw
d52fc777ac Code maintenance: update dockerhub readme workflow 2020-12-29 22:46:44 +00:00
Quentin McGaw
5753a428d8 Documentation: announcement on newer image name 2020-12-29 22:46:18 +00:00
Quentin McGaw
85afef5775 Change: gluetun docker image name 2020-12-29 22:10:44 +00:00
Quentin McGaw
b4fc24995c Code maintenance: Microbadger hook uses continue-on-error 2020-12-29 21:35:09 +00:00
Quentin McGaw
5917bb10e4 Feature: Docker secrets, refers to #306 2020-12-29 20:47:56 +00:00
Quentin McGaw
258e150ebf Code maintenance: GetPassword signature changed 2020-12-29 20:06:24 +00:00
Quentin McGaw
96f2b2b617 Change: PASSWORD changed to OPENVPN_PASSWORD 2020-12-29 20:05:17 +00:00
Quentin McGaw
d556db079b Change: USER changed to OPENVPN_USER 2020-12-29 20:02:58 +00:00
Quentin McGaw
a811a82329 Change: Remove CLIENT_KEY variable 2020-12-29 19:54:58 +00:00
Quentin McGaw
d17a0dae1f Documentation: Missing PUID and PGID update 2020-12-29 19:46:41 +00:00
Quentin McGaw
ef40f2f91b Code maintenance: Use Unset() option for params 2020-12-29 18:29:21 +00:00
Quentin McGaw
a921f9848c Code maintenance: CLI interface abstraction 2020-12-29 18:24:03 +00:00
Quentin McGaw
95ba3261fd Code maintenance: lint bug fix for armv7 2020-12-29 18:16:29 +00:00
Quentin McGaw
fe81eb65c2 Bug fix: Program exit on Openvpn fatal error 2020-12-29 17:50:36 +00:00
Quentin McGaw
8428714cf5 Code maintenance: upgrade golangci-lint to 1.34.1 2020-12-29 17:50:12 +00:00
Quentin McGaw
bedf613cff Code maintenance: storage merging reworked 2020-12-29 17:49:38 +00:00
Quentin McGaw
e643ce5b99 Fix publicip and updater loops exit bugs 2020-12-29 16:44:55 +00:00
Quentin McGaw
cb64302294 Rename UID and GID to PUID and PGID 2020-12-29 16:44:35 +00:00
Quentin McGaw
8d5f2fec09 Code maintenance: use native Go HTTP client 2020-12-29 02:55:34 +00:00
Quentin McGaw
60e98235ca Code maintenance: Better deps injection in main.go 2020-12-29 01:21:54 +00:00
Quentin McGaw
f55fb4055f Code maintenance: OS user abstraction interface 2020-12-29 01:16:53 +00:00
Quentin McGaw
da4e410bb7 Upgrade direct dependencies 2020-12-29 01:06:08 +00:00
Quentin McGaw
cdd1f87437 Code maintenance: Remove unneeded ctrl.Finish() 2020-12-29 01:04:07 +00:00
Quentin McGaw
7058373916 Code maintenance: Unix abstraction interface
- Used for creating the tun device if it does not exist
- Mocks generated for testing
2020-12-29 01:02:47 +00:00
Quentin McGaw
8dd38fd182 Code maintenance: better JSON decoding for HTTP 2020-12-29 00:56:51 +00:00
Quentin McGaw
73479bab26 Code maintenance: OS package for file system
- OS custom internal package for file system interaction
- Remove fileManager external dependency
- Closer API to Go's native API on the OS
- Create directories at startup
- Better testability
- Move Unsetenv to os interface
2020-12-29 00:55:31 +00:00
Quentin McGaw
f5366c33bc Remove unneeded .Times(1) for unit tests mocks 2020-12-28 01:52:30 +00:00
Quentin McGaw
db886163c2 Public IP getter loop refactored 2020-12-28 01:51:55 +00:00
Quentin McGaw
91f5338db0 Fix updater loop bug 2020-12-28 01:50:13 +00:00
Quentin McGaw
82a02287ac Public IP endpoint with GET /ip fixing #319 2020-12-27 21:06:00 +00:00
Quentin McGaw
2dc674559e Re-use username for UID if it exists 2020-12-27 00:36:39 +00:00
Quentin McGaw
38e713fea2 Fix Block-outside-dns #316 2020-12-23 06:46:54 +00:00
Quentin McGaw
2cbb14c36c Fix Purevpn settings display, refers to #317 2020-12-22 14:08:12 +00:00
Quentin McGaw
610e88958e Upgrade golangci-lint to v1.33.0 2020-12-22 13:52:37 +00:00
Quentin McGaw
bb76477467 Fix #316 2020-12-22 13:49:49 +00:00
Quentin McGaw
433a799759 Fix environment variables table for Purevpn 2020-12-22 13:46:52 +00:00
Quentin McGaw
22965ccce3 Fix #315 2020-12-22 06:21:25 +00:00
Quentin McGaw
4257581f55 Loops and HTTP control server rework (#308)
- CRUD REST HTTP server
- `/v1` HTTP server prefix
- Retrocompatible with older routes (redirects to v1 or handles the requests directly)
- DNS, Updater and Openvpn refactored to have a REST-like state with new methods to change their states synchronously
- Openvpn, Unbound and Updater status, see #287
2020-12-19 20:10:34 -05:00
Quentin McGaw
d60d629105 Dev container documentation and cleanup 2020-12-08 06:24:46 +00:00
Quentin McGaw
3f721b1717 Simplify Github workflows triggers 2020-12-07 02:15:50 +00:00
Quentin McGaw
97049bfab4 Add 256x256 png logo for Unraid 2020-12-07 02:11:23 +00:00
Quentin McGaw
84944a87d3 HTTP proxy authentication fixes (#300)
- Only accepts HTTP 1.x protocols
- Only checks the credentials when the method is `CONNECT` or the request URL is absolute
- More logging on authorization failures
- Removes the authorization headers before forwarding the HTTP(s) requests
- Refers to #298
2020-12-01 22:29:31 -05:00
Quentin McGaw
fb62910b17 HTTP proxy 24 hours timeout, fix #303 2020-11-21 01:26:02 +00:00
Quentin McGaw
1cc0f5fee9 Fix #296 (Cyberghost implementation) (#297)
* Reads the client key from /gluetun/client.key
* Read the client certificate from /gluetun/client.crt
* Additional checks for client key and client certificate validity
* Fix client key file parsing if environment variable isn't present
2020-11-19 08:50:55 -05:00
Quentin McGaw
6896cf4258 Update PIA hardcoded servers information 2020-11-14 22:21:08 +00:00
Quentin McGaw
188d63c6b8 Fix #298 2020-11-13 01:14:05 +00:00
Quentin McGaw
cbc5d466f6 Fix Shadowsocks UDP logs 2020-11-13 00:42:14 +00:00
Quentin McGaw
aef14a9f6d Assimilate PIA v4 as PIA in code 2020-11-10 13:35:49 +00:00
Quentin McGaw
f48392064e Update issue templates 2020-11-10 01:29:47 +00:00
Quentin McGaw
994bdd0ca7 Update Gituhb labels 2020-11-10 01:16:12 +00:00
Quentin McGaw
40ed070f21 Filter Privado servers by hostnames only 2020-11-09 23:17:22 +00:00
Quentin McGaw
f1e4b9937b Privado support, fix #285 (#288) 2020-11-08 20:56:49 -05:00
Quentin McGaw
0423388b52 Fix build information setting at build time 2020-11-07 22:31:20 +00:00
Quentin McGaw
096a9c5fc0 Fix #289 2020-11-06 02:54:27 +00:00
Quentin McGaw
7518f74729 Refactor HTTP control server code 2020-11-05 22:26:53 +00:00
Quentin McGaw
854401a150 PureVPN servers json tag fix 2020-11-05 02:22:33 +00:00
Quentin McGaw
a7a7efe9c3 Remove PIA v3 servers support 2020-11-05 02:10:34 +00:00
Quentin McGaw
31883f9adb Windscribe API and more servers filter options, fixes #197 (#282)
- Use Windscribe API to fetch servers information
- More data on servers about region, city and hostname
- Add optional server filters with `REGION`, `CITY` and `HOSTNAME` csv environment variables
2020-11-04 20:38:35 -05:00
Quentin McGaw
3b04677f8f HTTP control server /version endpoint 2020-11-04 14:07:04 +00:00
Quentin McGaw
b5fb2b849a DOT listens on all interfaces, refers to #281 2020-11-04 03:14:27 +00:00
Quentin McGaw
0c9f74ffa4 HTTP proxy written in Go to replace Tinyproxy (#269) 2020-10-31 21:50:31 -04:00
Quentin McGaw
58da55da1e Retrocompatiblity with EXTRA_SUBNETS key 2020-10-29 23:32:15 +00:00
Quentin McGaw
db64dea664 Fix #273 (#277), adding FIREWALL_OUTBOUND_SUBNETS 2020-10-29 19:23:44 -04:00
Quentin McGaw
f7bff247aa Fix #275 2020-10-28 22:09:58 +00:00
Quentin McGaw
edc08c46d4 Health server runs on 127.0.0.1:9999, fix #272 2020-10-27 03:28:25 +00:00
Quentin McGaw
78d83145ba Increase http timeout to 30s for piav4
- Refers to #271
2020-10-26 21:53:18 +00:00
Quentin McGaw
0c81154f36 No firewall setup needed for Shadowsocks 2020-10-26 02:52:10 +00:00
Quentin McGaw
53fe08ea26 Fix Mullvad CSV city bug 2020-10-25 21:13:26 +00:00
Quentin McGaw
a6cb1a7052 Remove x/net Go dependency 2020-10-25 20:45:14 +00:00
Quentin McGaw
c64fe7e45d Routing: use 0.0.0.0/0 instead of nil 2020-10-25 20:41:09 +00:00
Quentin McGaw
a062135148 Add routing verbose option in code 2020-10-25 20:40:17 +00:00
Quentin McGaw
5ae7c15211 Unused shadowsocks code cleanup 2020-10-25 20:38:16 +00:00
Quentin McGaw
f29707fa9f Remove unneeded openvpn remote-random 2020-10-24 22:26:15 +00:00
Quentin McGaw
e97d1e4a9a Set default for DOT_CACHING in code 2020-10-24 22:24:20 +00:00
Quentin McGaw
ed4fcc17b3 Routing improvements (#268)
- Fixes #82 
- Remove `EXTRA_SUBNETS`
- Remove no longer needed iptables rules
- Reduce routing interface arity
- Routing setup is done in main.go instead of in the firewall
- Routing setup gets reverted at shutdown
2020-10-24 18:05:11 -04:00
Quentin McGaw
716eb14da1 Allow empty string for CSV variables with golibs 2020-10-24 19:09:54 +00:00
Quentin McGaw
f92489f99b Fix Nordvpn number empty string allowance 2020-10-24 18:45:44 +00:00
Quentin McGaw
ea3b3bc8a3 Netlink Go library to interact with IP routes (#267) 2020-10-22 18:55:28 -04:00
Quentin McGaw
a80cb8f9ba VSCode development container changes
- SSH directory bind mounted as read write
- Remove linters list from devcontainer, use workspace ones instead
2020-10-22 03:54:18 +00:00
Quentin McGaw
d4813ba21c Fix #265 and refers to #256
- Logs a message about auth failure for PIA v4 servers
2020-10-22 00:36:12 +00:00
Quentin McGaw
bf92008e45 Fix #263 2020-10-20 18:13:59 +00:00
Quentin McGaw
9c73faaaeb Add linters and fix lint issues 2020-10-20 02:45:28 +00:00
Quentin McGaw
f9bef8ecda Fix #102 2020-10-19 00:28:07 +00:00
Quentin McGaw
302adb26d7 Fix servers filtering for PIA 2020-10-18 23:44:16 +00:00
Quentin McGaw
af606463ea Multi options filters, fixes #231 (#262)
* OWNED environment variable for Mullvad
* CSV are now accepted for all servers filtering environment variables
2020-10-18 17:15:42 -04:00
Quentin McGaw
c932f48a95 Fixes #254 new variable FIREWALL_INPUT_PORTS (#260) 2020-10-18 09:22:28 -04:00
Quentin McGaw
84c1f46ae4 Upgrade dependencies
- Use of context for custom http client
- Remove unused nodeid for logger
- Upgrade shadowsocks dependency
2020-10-18 02:24:34 +00:00
Quentin McGaw
b27e637894 HTTP_CONTROL_SERVER_LOG variable, fixes #249 2020-10-17 22:21:20 +00:00
Quentin McGaw
4da9607b4d Do not log healthcheck HTTP requests 2020-10-17 22:17:08 +00:00
Quentin McGaw
8abc22977c Fix #261 add variable HTTP_CONTROL_SERVER_PORT 2020-10-17 22:07:15 +00:00
Quentin McGaw
6f4be72785 Using context for HTTP requests 2020-10-17 21:54:09 +00:00
Quentin McGaw
0d2ca377df PIA port forwarding final fixes (#259)
- Returns an error if the server does not support port forwarding
- TLS verification using the server common name obtained through the API
- Updated readme
- Fixes #236
2020-10-15 22:53:13 -04:00
Quentin McGaw
98f778c3bb Improve timing behavior of ticking in loops 2020-10-15 23:20:36 +00:00
Quentin McGaw
9b9ae69404 Repurpose OPENVPN_TARGET_IP for #229 2020-10-12 20:21:26 +00:00
Quentin McGaw
1c747a10c8 Fix CN data for PIA v4 servers 2020-10-12 19:34:36 +00:00
Quentin McGaw
c4354871f7 Single connection written to openvpn configuration (#258)
- From now only a single OpenVPN connection is written to the OpenVPN configuration file
- If multiple connections are matched given the user parameters (i.e. city, region), it is picked at pseudo random using the current time as the pseudo random seed.
- Not relying on Openvpn picking a random remote address, may refer to #229 
- Program is aware of which connection is to be used, in order to use its matching CN for port forwarding TLS verification with PIA v4 servers, see #236 
- Simplified firewall mechanisms
2020-10-12 15:29:58 -04:00
Quentin McGaw
9f6450502c Obtain PIA v4 server information from API (#257)
- Obtain CN for port forwarding https verification
- Obtain for each server if they support port forwarding
- Obtain for each server their IP address for openvpn UDP and openvpn TCP (one for each)
- Updater program updated to use API
- Hardcoded values updated for PIA v3 and v4 servers
- Clearer separation between pia v3 and v4
- Fixes #250
2020-10-12 13:57:45 -04:00
Quentin McGaw
ae7fc5fe96 Fix guard pattern for max parallel DNS requests 2020-10-12 17:35:46 +00:00
Quentin McGaw
ec157f102b PIA nextgen portforward (#242)
* Split provider/pia.go in piav3.go and piav4.go
* Change port forwarding signature
* Enable port forwarding parameter for PIA v4
* Fix VPN gateway IP obtention
* Setup HTTP client for TLS with custom cert
* Error message for regions not supporting pf
2020-10-12 10:55:08 -04:00
Quentin McGaw
fbecbc1c82 Fix updater guard pattern (#255) 2020-10-01 17:56:14 -04:00
Quentin McGaw
ecf76896a2 Mullvad: configurable ipv6 tunneling (#253)
- Disabled by default
- Wiki pages updated
- Readme updated
2020-09-26 09:33:24 -04:00
Quentin McGaw
ae876b93d7 Update readme Wiki links 2020-09-25 23:12:34 +00:00
Quentin McGaw
606f2cffce Fix Shadowsocks documentation, fixing #245 2020-09-18 19:56:53 +00:00
Quentin McGaw
564d9cbf90 Faster servers information updater (#248)
* Asynchronous repeatResolve
* Parallel cyberghost and PIA (v3) processing, with a 10 goroutines limit
* Add missing vyprvpn cli flag to updater
* Increase DNS repetitions to 5 in order to obtain more IP addresses
* Update old PIA IP addresses
* Add Surfshark servers by API (unused for now)
2020-09-18 15:52:28 -04:00
Quentin McGaw
c5b5ae9ca7 Add warning logs when openvpn cannot connect 2020-09-18 14:28:14 +00:00
Quentin McGaw
4e0bd46dd5 Updated Mullvad servers information 2020-09-18 14:16:17 +00:00
Quentin McGaw
f9b6e854b1 Fix surfshark updater and update server data 2020-09-13 00:41:31 +00:00
Quentin McGaw
1fc1776dbf Simplify main.go 2020-09-12 19:17:19 +00:00
Quentin McGaw
464c7074d0 Get public IP and version only when DNS is ready 2020-09-12 18:50:42 +00:00
Quentin McGaw
cb1520cb18 Write 200 http status codes to restart routes 2020-09-12 18:37:06 +00:00
Quentin McGaw
e0e450ca1c Revisit waitgroup (#241)
* Fix Add to waitgroup out of goroutines calling wg.Done()
* Pass waitgroup to other loop functions
2020-09-12 14:34:15 -04:00
Quentin McGaw
1c012e4c92 Minor readme changes
- Mullvad ipv6 startup error fix
- VPNSP variable in docker run example command
2020-09-12 18:06:52 +00:00
Quentin McGaw
78ce272bd0 Minor Dockerfile changes 2020-09-12 18:06:10 +00:00
Quentin McGaw
a19efbd923 Updater loop with period and http route (#240)
* Updater loop with period and http route
* Using DNS over TLS to update servers
* Better logging
* Remove goroutines for cyberghost updater
* Respects context for servers update (quite slow overall)
* Increase shutdown grace period to 5 seconds
* Update announcement
* Add log lines for each provider update start
2020-09-12 14:04:54 -04:00
Quentin McGaw
ee64cbf1fd Use IP addresses resolved for PIA old servers (#239)
- Fix #238
- Not using raw IPs from PIA as they seem invalid
- Update PIA old server information
2020-09-10 21:09:16 -04:00
Quentin McGaw
5b3cbb6906 Fix #235: DNS over TLS log messages 2020-09-09 21:44:50 +00:00
Quentin McGaw
443c7e36d7 Youtube videos added 2020-09-09 20:52:22 +00:00
Max Isom
22b389b6f8 Fix firewall reference to Wiki (#237) 2020-09-09 16:45:36 -04:00
Quentin McGaw
797fa33971 Servers updater (#232)
* Support for all VPN providers
* Update all VPN providers servers information
* Remove old tooling binaries
2020-09-05 12:57:16 -04:00
Quentin McGaw
9dcc00900e Healthcheck moved to HTTP control server 2020-08-31 01:57:45 +00:00
Quentin McGaw
7c102c0028 Fix #135 2020-08-30 14:48:57 +00:00
Quentin McGaw
aac5274eab Fix #224 2020-08-29 19:14:52 +00:00
Quentin McGaw
049bc5b226 Mullvad updater (#228)
* Add Mullvad to updater cli
* Update hardcoded servers for Mullvad
2020-08-29 13:19:34 -04:00
Quentin McGaw
d463e4cb69 New PIA servers support (#227)
* Adapt storage: SyncServers write to file option, export FlushToFile
* CLI built-in updater for old and new PIA servers
* Update hardcoded IP addresses for PIA old and new servers
* Add PIA old to allServers struct and update timestamps
* Adapt code to work with new and old PIA servers
* Remove PIA subdomains (unneeded) from resolver tool
2020-08-28 08:17:04 -04:00
Quentin McGaw
99ba56f574 Fix server filtering using merged server data 2020-08-27 23:06:28 +00:00
Quentin McGaw
93aaf1ab02 Fix exiting without fatalOnError function 2020-08-27 22:59:58 +00:00
Quentin McGaw
aa9693a84d Persistent server pools (#226)
* GetAllServers with version & timestamp tests
* Storage package to sync servers
* Use storage Sync to get and use servers
2020-08-25 19:38:50 -04:00
Quentin McGaw
6fc2b3dd21 Mullvad servers do not have a default port
- Refers to #218
- Checks for custom port value depending on protocol
- Remove default port from server constants
- Use 443 and 1194 ports respectively for tcp and udp
2020-08-24 01:53:24 +00:00
hyness
7e3e6f166a Add new PIA servers hostnames to resolver tool (#222)
Refers to #216
2020-08-20 19:20:59 -04:00
Quentin McGaw
c614a192a4 Shadowsocks in Go (#220), fixes #211 2020-08-20 19:19:54 -04:00
Quentin McGaw
b10a476622 Default status file base directory /tmp/gluetun 2020-08-18 01:08:24 +00:00
Quentin McGaw
15ddbdefef Bump versions and binary build changes
- Go version 1.15
- Golangci-lint 1.30
- Trim path of binary built
2020-08-17 20:39:49 -04:00
705 changed files with 150270 additions and 17394 deletions

View File

@@ -0,0 +1,5 @@
.dockerignore
devcontainer.json
docker-compose.yml
Dockerfile
README.md

2
.devcontainer/Dockerfile Normal file
View File

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

69
.devcontainer/README.md Normal file
View File

@@ -0,0 +1,69 @@
# Development container
Development container that can be used with VSCode.
It works on Linux, Windows and OSX.
## Requirements
- [VS code](https://code.visualstudio.com/download) installed
- [VS code remote containers extension](https://marketplace.visualstudio.com/items?itemName=ms-vscode-remote.remote-containers) installed
- [Docker](https://www.docker.com/products/docker-desktop) installed and running
- If you don't use Linux or WSL 2, share your home directory `~/` and the directory of your project with Docker Desktop
- [Docker Compose](https://docs.docker.com/compose/install/) installed
- Ensure your host has the following and that they are accessible by Docker:
- `~/.ssh` directory
- `~/.gitconfig` file (can be empty)
## Setup
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
### Customize the image
You can make changes to the [Dockerfile](Dockerfile) and then rebuild the image. For example, your Dockerfile could be:
```Dockerfile
FROM qmcgaw/godevcontainer
USER root
RUN apk add curl
USER vscode
```
Note that you may need to use `USER root` to build as root, and then change back to `USER vscode`.
To rebuild the image, either:
- With VSCode through the command palette, select `Remote-Containers: Rebuild and reopen in container`
- With a terminal, go to this directory and `docker-compose build`
### Customize VS code settings
You can customize **settings** and **extensions** in the [devcontainer.json](devcontainer.json) definition file.
### Entrypoint script
You can bind mount a shell script to `/home/vscode/.welcome.sh` to replace the [current welcome script](shell/.welcome.sh).
### Publish a port
To access a port from your host to your development container, publish a port in [docker-compose.yml](docker-compose.yml).
### Run other services
1. Modify [docker-compose.yml](docker-compose.yml) to launch other services at the same time as this development container, such as a test database:
```yml
database:
image: postgres
restart: always
environment:
POSTGRES_PASSWORD: password
```
1. In [devcontainer.json](devcontainer.json), change the line `"runServices": ["vscode"],` to `"runServices": ["vscode", "database"],`.
1. In the VS code command palette, rebuild the container.

View File

@@ -1,5 +1,5 @@
{
"name": "pia-dev",
"name": "gluetun-dev",
"dockerComposeFile": [
"docker-compose.yml"
],
@@ -8,108 +8,74 @@
"vscode"
],
"shutdownAction": "stopCompose",
"postCreateCommand": "go mod download",
"postCreateCommand": "~/.windows.sh && go mod download && go mod tidy",
"workspaceFolder": "/workspace",
"extensions": [
"golang.go",
"IBM.output-colorizer",
"eamodio.gitlens",
"mhutchie.git-graph",
"eamodio.gitlens", // IDE Git information
"davidanson.vscode-markdownlint",
"shardulm94.trailing-spaces",
"alefragnani.Bookmarks",
"Gruntfuggly.todo-tree",
"mohsen1.prettify-json",
"quicktype.quicktype",
"spikespaz.vscode-smoothtype",
"stkb.rewrap",
"vscode-icons-team.vscode-icons"
"ms-azuretools.vscode-docker", // Docker integration and linting
"shardulm94.trailing-spaces", // Show trailing spaces
"Gruntfuggly.todo-tree", // Highlights TODO comments
"bierner.emojisense", // Emoji sense for markdown
"stkb.rewrap", // rewrap comments after n characters on one line
"vscode-icons-team.vscode-icons", // Better file extension icons
"github.vscode-pull-request-github", // Github interaction
"redhat.vscode-yaml", // Kubernetes, Drone syntax highlighting
"bajdzis.vscode-database", // Supports connections to mysql or postgres, over SSL, socked
"IBM.output-colorizer", // Colorize your output/test logs
"mohsen1.prettify-json", // Prettify JSON data
],
"settings": {
// General settings
"files.eol": "\n",
// Docker
"remote.extensionKind": {
"ms-azuretools.vscode-docker": "workspace"
},
// Golang general settings
"editor.codeActionsOnSaveTimeout": 3000,
"go.useLanguageServer": true,
"[go]": {
"editor.formatOnSave": true,
"editor.codeActionsOnSave": {
"source.organizeImports": true,
},
// Optional: Disable snippets, as they conflict with completion ranking.
"editor.snippetSuggestions": "none"
},
"[go.mod]": {
"editor.formatOnSave": true,
"editor.codeActionsOnSave": {
"source.organizeImports": true,
},
},
"gopls": {
"usePlaceholders": false,
"staticcheck": true
},
"go.autocompleteUnimportedPackages": true,
"go.gotoSymbol.includeImports": true,
"go.gotoSymbol.includeGoroot": true,
"gopls": {
"completeUnimported": true,
"deepCompletion": true,
"usePlaceholders": false
},
"go.lintTool": "golangci-lint",
"go.lintFlags": [
"--fast",
"--enable",
"staticcheck",
"--enable",
"bodyclose",
"--enable",
"dogsled",
"--enable",
"gochecknoglobals",
"--enable",
"gochecknoinits",
"--enable",
"gocognit",
"--enable",
"goconst",
"--enable",
"gocritic",
"--enable",
"gocyclo",
"--enable",
"golint",
"--enable",
"gosec",
"--enable",
"interfacer",
"--enable",
"maligned",
"--enable",
"misspell",
"--enable",
"nakedret",
"--enable",
"prealloc",
"--enable",
"scopelint",
"--enable",
"unconvert",
"--enable",
"unparam",
"--enable",
"whitespace"
],
// Golang on save
"go.buildOnSave": "workspace",
"go.lintOnSave": "workspace",
"go.vetOnSave": "workspace",
"editor.formatOnSave": true,
"[go]": {
"editor.codeActionsOnSave": {
"source.organizeImports": true
}
},
// Golang testing
"go.toolsEnvVars": {
"GOFLAGS": "-tags=integration"
"GOFLAGS": "-tags=",
// "CGO_ENABLED": 1 // for the race detector
},
"gopls.env": {
"GOFLAGS": "-tags=integration"
"GOFLAGS": "-tags="
},
"go.testEnvVars": {
"": ""
},
"go.testEnvVars": {},
"go.testFlags": [
"-v",
// "-race"
],
"go.testTimeout": "600s",
"go.testTimeout": "10s",
"go.coverOnSingleTest": true,
"go.coverOnSingleTestFile": true,
"go.coverOnSingleTest": true
"go.coverOnTestPackage": true
}
}

View File

@@ -2,14 +2,32 @@ version: "3.7"
services:
vscode:
image: qmcgaw/godevcontainer
build: .
image: godevcontainer
devices:
- /dev/net/tun:/dev/net/tun
volumes:
- ../:/workspace
- ~/.ssh:/home/vscode/.ssh:ro
- ~/.ssh:/root/.ssh:ro
# Docker socket to access Docker server
- /var/run/docker.sock:/var/run/docker.sock
# 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:/root/.gitconfig:z
environment:
- TZ=
cap_add:
- SYS_PTRACE
# For debugging with dlv
# - SYS_PTRACE
- NET_ADMIN
security_opt:
# For debugging with dlv
- seccomp:unconfined
entrypoint: zsh -c "while sleep 1000; do :; done"

View File

@@ -1,11 +1,9 @@
.devcontainer
.git
.github
.vscode
cmd
!cmd/gluetun
doc
docker-compose.yml
Dockerfile
LICENSE
README.md
title.svg

View File

@@ -7,23 +7,12 @@ Contributions are [released](https://help.github.com/articles/github-terms-of-se
1. [Fork](https://github.com/qdm12/gluetun/fork) and clone the repository
1. Create a new branch `git checkout -b my-branch-name`
1. Modify the code
1. Ensure the docker build succeeds `docker build .`
1. Ensure the docker build succeeds `docker build .` (you might need `export DOCKER_BUILDKIT=1`)
1. Commit your modifications
1. Push to your fork and [submit a pull request](https://github.com/qdm12/gluetun/compare)
## Resources
- [Gluetun guide on development](https://github.com/qdm12/gluetun/wiki/Development)
- [Using Pull Requests](https://help.github.com/articles/about-pull-requests/)
- [How to Contribute to Open Source](https://opensource.guide/how-to-contribute/)
## Contributors
Thanks for all the contributions, whether small or not so small!
- [@JeordyR](https://github.com/JeordyR) for testing the Mullvad version and opening a [PR with a few fixes](https://github.com/qdm12/gluetun/pull/84/files) 👍
- [@rorph](https://github.com/rorph) for a [PR to pick a random region for PIA](https://github.com/qdm12/gluetun/pull/70) and a [PR to make the container work with kubernetes](https://github.com/qdm12/gluetun/pull/69)
- [@JesterEE](https://github.com/JesterEE) for a [PR to fix silly line endings in block lists back then](https://github.com/qdm12/gluetun/pull/55) 📎
- [@elmerfdz](https://github.com/elmerfdz) for a [PR to add timezone information to have correct log timestampts](https://github.com/qdm12/gluetun/pull/51) 🕙
- [@Juggels](https://github.com/Juggels) for a [PR to write the PIA forwarded port to a file](https://github.com/qdm12/gluetun/pull/43)
- [@gdlx](https://github.com/gdlx) for a [PR to fix and improve PIA port forwarding script](https://github.com/qdm12/gluetun/pull/32)
- [@janaz](https://github.com/janaz) for keeping an eye on [updating things in the Dockerfile](https://github.com/qdm12/gluetun/pull/8)

View File

@@ -1,55 +0,0 @@
---
name: Bug
about: Report a bug
title: 'Bug: ...'
labels: ":bug: bug"
assignees: qdm12
---
**TLDR**: *Describe your issue in a one liner here*
1. Is this urgent?
- [ ] Yes
- [x] No
2. What VPN service provider are you using?
- [x] PIA
- [ ] Mullvad
- [ ] Windscribe
- [ ] Surfshark
- [ ] Cyberghost
3. What's 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)`
4. What are you using to run the container?
- [ ] Docker run
- [x] Docker Compose
- [ ] Kubernetes
- [ ] Docker stack
- [ ] Docker swarm
- [ ] Podman
- [ ] Other:
5. Extra information
Logs:
```log
```
Configuration file:
```yml
```
Host OS:

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,14 +0,0 @@
---
name: Feature request
about: Suggest a feature to add to this project
title: 'Feature request: ...'
labels: ":bulb: feature request"
assignees: qdm12
---
1. What's the feature?
2. Why do you need this feature?
3. Extra information?

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,55 +0,0 @@
---
name: Help
about: Ask for help
title: 'Help: ...'
labels: ":pray: help wanted"
assignees:
---
**TLDR**: *Describe your issue in a one liner here*
1. Is this urgent?
- [ ] Yes
- [x] No
2. What VPN service provider are you using?
- [x] PIA
- [ ] Mullvad
- [ ] Windscribe
- [ ] Surfshark
- [ ] Cyberghost
3. What's 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)`
4. What are you using to run the container?
- [ ] Docker run
- [x] Docker Compose
- [ ] Kubernetes
- [ ] Docker stack
- [ ] Docker swarm
- [ ] Podman
- [ ] Other:
5. Extra information
Logs:
```log
```
Configuration file:
```yml
```
Host OS:

17
.github/ISSUE_TEMPLATE/provider.md vendored Normal file
View File

@@ -0,0 +1,17 @@
---
name: Support a VPN provider
about: Suggest a VPN provider to be supported
title: 'VPN provider support: NAME OF THE PROVIDER'
labels: ":bulb: New provider"
---
One of the following is required:
- Publicly accessible URL to a zip file containing the Openvpn configuration files
- Publicly accessible URL to a structured (JSON etc.) list of servers **and attach** an example Openvpn configuration file for both TCP and UDP
- 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,
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

15
.github/dependabot.yml vendored Normal file
View File

@@ -0,0 +1,15 @@
version: 2
updates:
# Maintain dependencies for GitHub Actions
- package-ecosystem: "github-actions"
directory: "/"
schedule:
interval: "daily"
- package-ecosystem: docker
directory: /
schedule:
interval: "daily"
- package-ecosystem: gomod
directory: /
schedule:
interval: "daily"

139
.github/labels.yml vendored
View File

@@ -1,51 +1,110 @@
- name: ":robot: bot"
color: "69cde9"
description: ""
- name: ":bug: bug"
- name: "Bug :bug:"
color: "b60205"
description: ""
- name: ":game_die: dependencies"
color: "0366d6"
description: ""
- name: ":memo: documentation"
color: "c5def5"
description: ""
- name: ":busts_in_silhouette: duplicate"
color: "cccccc"
description: ""
- name: ":sparkles: enhancement"
color: "0054ca"
description: ""
- name: ":bulb: feature request"
- name: "Feature request :bulb:"
color: "0e8a16"
description: ""
- name: ":mega: feedback"
color: "03a9f4"
description: ""
- name: ":rocket: future maybe"
color: "fef2c0"
description: ""
- name: ":hatching_chick: good first issue"
color: "7057ff"
description: ""
- name: ":pray: help wanted"
- name: "Help wanted :pray:"
color: "4caf50"
description: ""
- name: ":hand: hold"
color: "24292f"
- name: "Documentation :memo:"
color: "c5def5"
description: ""
- name: ":no_entry_sign: invalid"
color: "e6e6e6"
description: ""
- name: ":interrobang: maybe bug"
color: "ff5722"
description: ""
- name: ":thinking: needs more info"
- name: "Needs more info :thinking:"
color: "795548"
description: ""
- name: ":question: question"
color: "3f51b5"
# Priority
- name: "🚨 Urgent"
color: "d5232f"
description: ""
- name: ":coffin: wontfix"
color: "ffffff"
- 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: ""
- name: ":cloud: Surfshark"
color: "cfe8d4"
description: ""
- 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: ""
# Problem category
- name: "Openvpn"
color: "ffc7ea"
description: ""
- name: "Wireguard"
color: "ffc7ea"
description: ""
- name: "Unbound (DNS over TLS)"
color: "ffc7ea"
description: ""
- name: "Firewall"
color: "ffc7ea"
description: ""
- name: "HTTP proxy"
color: "ffc7ea"
description: ""
- name: "Shadowsocks"
color: "ffc7ea"
description: ""
- name: "Healthcheck server"
color: "ffc7ea"
description: ""
- name: "Control server"
color: "ffc7ea"
description: ""

View File

@@ -1,34 +0,0 @@
name: Docker build
on:
pull_request:
branches: [master]
paths-ignore:
- .devcontainer
- .github/ISSUE_TEMPLATE
- .github/workflows/buildx-release.yml
- .github/workflows/buildx-branch.yml
- .github/workflows/buildx-latest.yml
- .github/workflows/dockerhub-description.yml
- .github/workflows/labels.yml
- .github/workflows/misspell.yml
- .github/CODEOWNERS
- .github/CONTRIBUTING.md
- .github/FUNDING.yml
- .github/labels.yml
- .vscode
- cmd/ovpnparser
- cmd/resolver
- doc
- .gitignore
- docker-compose.yml
- LICENSE
- README.md
- title.svg
jobs:
build:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v2
- name: Build image
run: docker build .

View File

@@ -1,50 +0,0 @@
name: Buildx branch
on:
push:
branches:
- '*'
- '*/*'
- '!master'
paths-ignore:
- .devcontainer
- .github/ISSUE_TEMPLATE
- .github/workflows/build.yml
- .github/workflows/buildx-release.yml
- .github/workflows/buildx-latest.yml
- .github/workflows/dockerhub-description.yml
- .github/workflows/labels.yml
- .github/workflows/misspell.yml
- .github/CODEOWNERS
- .github/CONTRIBUTING.md
- .github/FUNDING.yml
- .github/labels.yml
- .vscode
- cmd/ovpnparser
- cmd/resolver
- doc
- .gitignore
- docker-compose.yml
- LICENSE
- README.md
- title.svg
jobs:
buildx:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- name: Buildx setup
uses: crazy-max/ghaction-docker-buildx@v1
- name: Dockerhub login
run: echo ${{ secrets.DOCKERHUB_PASSWORD }} | docker login -u qmcgaw --password-stdin 2>&1
- name: Run Buildx
run: |
docker buildx build \
--progress plain \
--platform=linux/amd64 \
--build-arg BUILD_DATE=`date -u +"%Y-%m-%dT%H:%M:%SZ"` \
--build-arg VCS_REF=`git rev-parse --short HEAD` \
--build-arg VERSION=${GITHUB_REF##*/} \
-t qmcgaw/private-internet-access:${GITHUB_REF##*/} \
--push \
.
- run: curl -X POST https://hooks.microbadger.com/images/qmcgaw/private-internet-access/tQFy7AxtSUNANPe6aoVChYdsI_I= || exit 0

View File

@@ -1,47 +0,0 @@
name: Buildx latest
on:
push:
branches: [master]
paths-ignore:
- .devcontainer
- .github/ISSUE_TEMPLATE
- .github/workflows/build.yml
- .github/workflows/buildx-branch.yml
- .github/workflows/buildx-release.yml
- .github/workflows/dockerhub-description.yml
- .github/workflows/labels.yml
- .github/workflows/misspell.yml
- .github/CODEOWNERS
- .github/CONTRIBUTING.md
- .github/FUNDING.yml
- .github/labels.yml
- .vscode
- cmd/ovpnparser
- cmd/resolver
- doc
- .gitignore
- docker-compose.yml
- LICENSE
- README.md
- title.svg
jobs:
buildx:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- name: Buildx setup
uses: crazy-max/ghaction-docker-buildx@v1
- name: Dockerhub login
run: echo ${{ secrets.DOCKERHUB_PASSWORD }} | docker login -u qmcgaw --password-stdin 2>&1
- name: Run Buildx
run: |
docker buildx build \
--progress plain \
--platform=linux/amd64,linux/386,linux/arm64,linux/arm/v7,linux/arm/v6 \
--build-arg BUILD_DATE=`date -u +"%Y-%m-%dT%H:%M:%SZ"` \
--build-arg VCS_REF=`git rev-parse --short HEAD` \
--build-arg VERSION=latest \
-t qmcgaw/private-internet-access:latest \
--push \
.
- run: curl -X POST https://hooks.microbadger.com/images/qmcgaw/private-internet-access/tQFy7AxtSUNANPe6aoVChYdsI_I= || exit 0

View File

@@ -1,47 +0,0 @@
name: Buildx release
on:
release:
types: [published]
paths-ignore:
- .devcontainer
- .github/ISSUE_TEMPLATE
- .github/workflows/build.yml
- .github/workflows/buildx-branch.yml
- .github/workflows/buildx-latest.yml
- .github/workflows/dockerhub-description.yml
- .github/workflows/labels.yml
- .github/workflows/misspell.yml
- .github/CODEOWNERS
- .github/CONTRIBUTING.md
- .github/FUNDING.yml
- .github/labels.yml
- .vscode
- cmd/ovpnparser
- cmd/resolver
- doc
- .gitignore
- docker-compose.yml
- LICENSE
- README.md
- title.svg
jobs:
buildx:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- name: Buildx setup
uses: crazy-max/ghaction-docker-buildx@v1
- name: Dockerhub login
run: echo ${{ secrets.DOCKERHUB_PASSWORD }} | docker login -u qmcgaw --password-stdin 2>&1
- name: Run Buildx
run: |
docker buildx build \
--progress plain \
--platform=linux/amd64,linux/386,linux/arm64,linux/arm/v7,linux/arm/v6 \
--build-arg BUILD_DATE=`date -u +"%Y-%m-%dT%H:%M:%SZ"` \
--build-arg VCS_REF=`git rev-parse --short HEAD` \
--build-arg VERSION=${GITHUB_REF##*/} \
-t qmcgaw/private-internet-access:${GITHUB_REF##*/} \
--push \
.
- run: curl -X POST https://hooks.microbadger.com/images/qmcgaw/private-internet-access/tQFy7AxtSUNANPe6aoVChYdsI_I= || exit 0

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

@@ -0,0 +1,135 @@
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@v3
- uses: reviewdog/action-misspell@v1
with:
locale: "US"
level: error
- 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@v3
# 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 == format('refs/heads/{0}', github.event.repository.default_branch) }}
images: |
qmcgaw/gluetun
qmcgaw/private-internet-access
tags: |
type=ref,event=branch,enable=${{ github.ref != format('refs/heads/{0}', github.event.repository.default_branch) }}
type=ref,event=pr
type=semver,pattern=v{{major}}.{{minor}}.{{patch}}
type=semver,pattern=v{{major}}.{{minor}}
type=semver,pattern=v{{major}},enable=${{ !startsWith(github.ref, 'refs/tags/v0.') }}
type=raw,value=latest,enable=${{ github.ref == format('refs/heads/{0}', github.event.repository.default_branch) }}
- 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: 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.9.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,10 +10,12 @@ 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.1.0
env:
DOCKERHUB_USERNAME: qmcgaw
DOCKERHUB_PASSWORD: ${{ secrets.DOCKERHUB_PASSWORD }}
DOCKERHUB_REPOSITORY: qmcgaw/private-internet-access
uses: peter-evans/dockerhub-description@v2
with:
username: qmcgaw
password: ${{ secrets.DOCKERHUB_PASSWORD }}
repository: qmcgaw/gluetun
short-description: Lightweight Swiss-knife VPN client to connect to several VPN providers
readme-filepath: README.md

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

@@ -1,18 +1,15 @@
name: labels
on:
push:
branches: ["master"]
branches: [master]
paths:
- '.github/labels.yml'
- '.github/workflows/labels.yml'
- .github/labels.yml
- .github/workflows/labels.yml
jobs:
labeler:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v2
- name: Labeler
if: success()
uses: crazy-max/ghaction-github-labeler@v1
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- uses: actions/checkout@v2.4.0
- uses: crazy-max/ghaction-github-labeler@v3
with:
yaml-file: .github/labels.yml

View File

@@ -8,9 +8,8 @@ jobs:
misspell:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- uses: reviewdog/action-misspell@master
- uses: actions/checkout@v2.4.0
- uses: reviewdog/action-misspell@v1
with:
github_token: ${{ secrets.GITHUB_TOKEN }}
locale: "US"
level: error

1
.gitignore vendored Normal file
View File

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

View File

@@ -1,47 +1,100 @@
linters-settings:
maligned:
suggest-new: true
misspell:
locale: US
issues:
exclude-rules:
- path: _test\.go
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
- text: "variable 'mssFix' is only used in the if-statement*"
path: "openvpnconf.go"
linters:
- ifshort
- text: "variable 'auth' is only used in the if-statement*"
path: "openvpnconf.go"
linters:
- ifshort
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
- gocognit
- goconst
- gocritic
- gocyclo
- godot
- goerr113
- goheader
- goimports
- golint
- gomnd
- gomoddirectives
- goprintffuncname
- gosec
- gosimple
- govet
- ineffassign
- interfacer
- maligned
- ifshort
- importas
- lll
- makezero
- misspell
- nakedret
- nestif
- nilerr
- nilnil
- noctx
- nolintlint
- prealloc
- predeclared
- predeclared
- promlinter
- revive
- rowserrcheck
- scopelint
- staticcheck
- structcheck
- typecheck
- sqlclosecheck
- tenv
- thelper
- tparallel
- unconvert
- unparam
- unused
- varcheck
- wastedassign
- whitespace
run:
skip-dirs:
- .devcontainer
- .github
- postgres
- doc

View File

@@ -1,9 +0,0 @@
{
"recommendations": [
"shardulm94.trailing-spaces",
"ms-azuretools.vscode-docker",
"davidanson.vscode-markdownlint",
"IBM.output-colorizer",
"golang.go"
]
}

91
.vscode/settings.json vendored
View File

@@ -1,91 +0,0 @@
{
// General settings
"files.eol": "\n",
// Docker
"remote.extensionKind": {
"ms-azuretools.vscode-docker": "workspace"
},
// Golang general settings
"go.useLanguageServer": true,
"go.autocompleteUnimportedPackages": true,
"go.gotoSymbol.includeImports": true,
"go.gotoSymbol.includeGoroot": true,
"gopls": {
"completeUnimported": true,
"deepCompletion": true,
"usePlaceholders": false
},
"go.lintTool": "golangci-lint",
"go.lintFlags": [
"--fast",
"--enable",
"rowserrcheck",
"--enable",
"bodyclose",
"--enable",
"dogsled",
"--enable",
"dupl",
"--enable",
"gochecknoglobals",
"--enable",
"gochecknoinits",
"--enable",
"gocognit",
"--enable",
"goconst",
"--enable",
"gocritic",
"--enable",
"gocyclo",
"--enable",
"goimports",
"--enable",
"golint",
"--enable",
"gosec",
"--enable",
"interfacer",
"--enable",
"maligned",
"--enable",
"misspell",
"--enable",
"nakedret",
"--enable",
"prealloc",
"--enable",
"scopelint",
"--enable",
"unconvert",
"--enable",
"unparam",
"--enable",
"whitespace"
],
// Golang on save
"go.buildOnSave": "workspace",
"go.lintOnSave": "workspace",
"go.vetOnSave": "workspace",
"editor.formatOnSave": true,
"[go]": {
"editor.codeActionsOnSave": {
"source.organizeImports": true
}
},
// Golang testing
"go.toolsEnvVars": {
"GOFLAGS": "-tags="
},
"gopls.env": {
"GOFLAGS": "-tags="
},
"go.testEnvVars": {},
"go.testFlags": [
"-v",
// "-race"
],
"go.testTimeout": "600s",
"go.coverOnSingleTestFile": true,
"go.coverOnSingleTest": true
}

View File

@@ -1,108 +1,189 @@
ARG ALPINE_VERSION=3.12
ARG GO_VERSION=1.15
FROM golang:${GO_VERSION}-alpine${ALPINE_VERSION} AS builder
RUN apk --update add git
ENV CGO_ENABLED=0
ARG GOLANGCI_LINT_VERSION=v1.30.0
RUN wget -O- -nv https://raw.githubusercontent.com/golangci/golangci-lint/master/install.sh | sh -s ${GOLANGCI_LINT_VERSION}
WORKDIR /tmp/gobuild
COPY .golangci.yml .
COPY go.mod go.sum ./
RUN go mod download 2>&1
COPY cmd/gluetun/main.go .
COPY internal/ ./internal/
RUN go test ./...
RUN golangci-lint run --timeout=10m
RUN go build -trimpath -ldflags="-s -w" -o entrypoint main.go
FROM alpine:${ALPINE_VERSION}
ARG VERSION
ARG BUILD_DATE
ARG VCS_REF
ENV VERSION=$VERSION \
BUILD_DATE=$BUILD_DATE \
VCS_REF=$VCS_REF
LABEL \
org.opencontainers.image.authors="quentin.mcgaw@gmail.com" \
org.opencontainers.image.created=$BUILD_DATE \
org.opencontainers.image.version=$VERSION \
org.opencontainers.image.revision=$VCS_REF \
org.opencontainers.image.url="https://github.com/qdm12/gluetun" \
org.opencontainers.image.documentation="https://github.com/qdm12/gluetun" \
org.opencontainers.image.source="https://github.com/qdm12/gluetun" \
org.opencontainers.image.title="VPN client for PIA, Mullvad, Windscribe, Surfshark and Cyberghost" \
org.opencontainers.image.description="VPN client to tunnel to PIA, Mullvad, Windscribe, Surfshark and Cyberghost servers using OpenVPN, IPtables, DNS over TLS and Alpine Linux"
ENV VPNSP=pia \
PROTOCOL=udp \
OPENVPN_VERBOSITY=1 \
OPENVPN_ROOT=no \
OPENVPN_TARGET_IP= \
TZ= \
UID=1000 \
GID=1000 \
IP_STATUS_FILE="/ip" \
# PIA, Windscribe, Surfshark, Cyberghost, Vyprvpn, NordVPN, PureVPN only
USER= \
PASSWORD= \
REGION= \
# PIA only
PIA_ENCRYPTION=strong \
PORT_FORWARDING=off \
PORT_FORWARDING_STATUS_FILE="/forwarded_port" \
# Mullvad and PureVPN only
COUNTRY= \
CITY= \
# Mullvad only
ISP= \
# Mullvad and Windscribe only
PORT= \
# Cyberghost only
CYBERGHOST_GROUP="Premium UDP Europe" \
# NordVPN only
SERVER_NUMBER= \
# Openvpn
OPENVPN_CIPHER= \
OPENVPN_AUTH= \
# 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_VERBOSITY=1 \
DOT_VERBOSITY_DETAILS=0 \
DOT_VALIDATION_LOGLEVEL=0 \
DOT_CACHING=on \
DOT_IPV6=off \
BLOCK_MALICIOUS=on \
BLOCK_SURVEILLANCE=off \
BLOCK_ADS=off \
UNBLOCK= \
DNS_UPDATE_PERIOD=24h \
DNS_PLAINTEXT_ADDRESS=1.1.1.1 \
DNS_KEEP_NAMESERVER=off \
# Firewall
FIREWALL=on \
EXTRA_SUBNETS= \
FIREWALL_VPN_INPUT_PORTS= \
FIREWALL_DEBUG=off \
# Tinyproxy
TINYPROXY=off \
TINYPROXY_LOG=Info \
TINYPROXY_PORT=8888 \
TINYPROXY_USER= \
TINYPROXY_PASSWORD= \
# Shadowsocks
SHADOWSOCKS=off \
SHADOWSOCKS_LOG=off \
SHADOWSOCKS_PORT=8388 \
SHADOWSOCKS_PASSWORD= \
SHADOWSOCKS_METHOD=chacha20-ietf-poly1305
ENTRYPOINT ["/entrypoint"]
EXPOSE 8000/tcp 8888/tcp 8388/tcp 8388/udp
HEALTHCHECK --interval=10m --timeout=10s --start-period=30s --retries=2 CMD /entrypoint healthcheck
RUN apk add -q --progress --no-cache --update openvpn ca-certificates iptables ip6tables unbound tinyproxy tzdata && \
rm -rf /var/cache/apk/* /etc/unbound/* /usr/sbin/unbound-* /etc/tinyproxy/tinyproxy.conf && \
deluser openvpn && \
deluser tinyproxy && \
deluser unbound
COPY --from=builder /tmp/gobuild/entrypoint /entrypoint
ARG ALPINE_VERSION=3.15
ARG GO_ALPINE_VERSION=3.15
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} 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
# Note on the go race detector:
# - we set CGO_ENABLED=1 to have it enabled
# - we installed g++ to support the race detector
ENV CGO_ENABLED=1
ENTRYPOINT go test -race -coverpkg=./... -coverprofile=coverage.txt -covermode=atomic ./...
FROM --platform=${BUILDPLATFORM} base AS lint
COPY .golangci.yml ./
RUN golangci-lint run --timeout=10m
FROM --platform=${BUILDPLATFORM} base AS tidy
RUN git init && \
git config user.email ci@localhost && \
git config user.name ci && \
git add -A && git commit -m ci && \
sed -i '/\/\/ indirect/d' go.mod && \
go mod tidy && \
git diff --exit-code -- go.mod
FROM --platform=${BUILDPLATFORM} base AS build
ARG TARGETPLATFORM
ARG VERSION=unknown
ARG CREATED="an unknown date"
ARG COMMIT=unknown
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.created=$CREATED' \
-X 'main.commit=$COMMIT' \
" -o entrypoint cmd/gluetun/main.go
FROM alpine:${ALPINE_VERSION}
ARG VERSION=unknown
ARG CREATED="an unknown date"
ARG COMMIT=unknown
LABEL \
org.opencontainers.image.authors="quentin.mcgaw@gmail.com" \
org.opencontainers.image.created=$CREATED \
org.opencontainers.image.version=$VERSION \
org.opencontainers.image.revision=$COMMIT \
org.opencontainers.image.url="https://github.com/qdm12/gluetun" \
org.opencontainers.image.documentation="https://github.com/qdm12/gluetun" \
org.opencontainers.image.source="https://github.com/qdm12/gluetun" \
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 \
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 \
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= \
COUNTRY= \
CITY= \
SERVER_HOSTNAME= \
# # Mullvad only:
ISP= \
OWNED=no \
# # Private Internet Access only:
PIA_ENCRYPTION= \
PORT_FORWARDING=off \
PORT_FORWARDING_STATUS_FILE="/tmp/gluetun/forwarded_port" \
# # Cyberghost only:
OPENVPN_CLIENTCRT_SECRETFILE=/run/secrets/openvpn_clientcrt \
OPENVPN_CLIENTKEY_SECRETFILE=/run/secrets/openvpn_clientkey \
# # Nordvpn only:
SERVER_NUMBER= \
# # 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_TARGET_ADDRESS=github.com:443 \
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: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 \
DOT_CACHING=on \
DOT_IPV6=off \
BLOCK_MALICIOUS=on \
BLOCK_SURVEILLANCE=off \
BLOCK_ADS=off \
UNBLOCK= \
DNS_UPDATE_PERIOD=24h \
DNS_PLAINTEXT_ADDRESS=127.0.0.1 \
DNS_KEEP_NAMESERVER=off \
# HTTP proxy
HTTPPROXY= \
HTTPPROXY_LOG=off \
HTTPPROXY_LISTENING_ADDRESS=":8888" \
HTTPPROXY_USER= \
HTTPPROXY_PASSWORD= \
HTTPPROXY_USER_SECRETFILE=/run/secrets/httpproxy_user \
HTTPPROXY_PASSWORD_SECRETFILE=/run/secrets/httpproxy_password \
# Shadowsocks
SHADOWSOCKS=off \
SHADOWSOCKS_LOG=off \
SHADOWSOCKS_LISTENING_ADDRESS=":8388" \
SHADOWSOCKS_PASSWORD= \
SHADOWSOCKS_PASSWORD_SECRETFILE=/run/secrets/shadowsocks_password \
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 ["/gluetun-entrypoint"]
EXPOSE 8000/tcp 8888/tcp 8388/tcp 8388/udp
HEALTHCHECK --interval=5s --timeout=5s --start-period=10s --retries=1 CMD /gluetun-entrypoint healthcheck
ARG TARGETPLATFORM
RUN apk add --no-cache --update -l apk-tools && \
apk add --no-cache --update -X "https://dl-cdn.alpinelinux.org/alpine/v3.12/main" openvpn==2.4.12-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
COPY --from=build /tmp/gobuild/entrypoint /gluetun-entrypoint

449
README.md
View File

@@ -1,378 +1,127 @@
# Gluetun VPN client
*Lightweight swiss-knife-like VPN client to tunnel to Private Internet Access,
Mullvad, Windscribe, Surfshark Cyberghost, VyprVPN, NordVPN and PureVPN VPN servers, using Go, OpenVPN,
iptables, DNS over TLS, ShadowSocks and Tinyproxy*
*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**: *[Video of the Git history of Gluetun](https://youtu.be/khipOYJtGJ0)*
**ANNOUNCEMENT**: Large settings refactor merged on 2022-06-01, please file issues if you find any problem!
<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)
[![Build status](https://github.com/qdm12/gluetun/workflows/Buildx%20latest/badge.svg)](https://github.com/qdm12/gluetun/actions?query=workflow%3A%22Buildx+latest%22)
[![Docker Pulls](https://img.shields.io/docker/pulls/qmcgaw/private-internet-access.svg)](https://hub.docker.com/r/qmcgaw/private-internet-access)
[![Docker Stars](https://img.shields.io/docker/stars/qmcgaw/private-internet-access.svg)](https://hub.docker.com/r/qmcgaw/private-internet-access)
[![Build status](https://github.com/qdm12/gluetun/actions/workflows/ci.yml/badge.svg)](https://github.com/qdm12/gluetun/actions/workflows/ci.yml)
[![GitHub last commit](https://img.shields.io/github/last-commit/qdm12/gluetun.svg)](https://github.com/qdm12/gluetun/issues)
[![GitHub commit activity](https://img.shields.io/github/commit-activity/y/qdm12/gluetun.svg)](https://github.com/qdm12/gluetun/issues)
[![Docker pulls qmcgaw/gluetun](https://img.shields.io/docker/pulls/qmcgaw/gluetun.svg)](https://hub.docker.com/r/qmcgaw/gluetun)
[![Docker pulls qmcgaw/private-internet-access](https://img.shields.io/docker/pulls/qmcgaw/private-internet-access.svg)](https://hub.docker.com/r/qmcgaw/gluetun)
[![Docker stars qmcgaw/gluetun](https://img.shields.io/docker/stars/qmcgaw/gluetun.svg)](https://hub.docker.com/r/qmcgaw/gluetun)
[![Docker stars qmcgaw/private-internet-access](https://img.shields.io/docker/stars/qmcgaw/private-internet-access.svg)](https://hub.docker.com/r/qmcgaw/gluetun)
![Last release](https://img.shields.io/github/release/qdm12/gluetun?label=Last%20release)
![Last Docker tag](https://img.shields.io/docker/v/qmcgaw/gluetun?sort=semver&label=Last%20Docker%20tag)
[![Last release size](https://img.shields.io/docker/image-size/qmcgaw/gluetun?sort=semver&label=Last%20released%20image)](https://hub.docker.com/r/qmcgaw/gluetun/tags?page=1&ordering=last_updated)
![GitHub last release date](https://img.shields.io/github/release-date/qdm12/gluetun?label=Last%20release%20date)
![Commits since release](https://img.shields.io/github/commits-since/qdm12/gluetun/latest?sort=semver)
[![Latest size](https://img.shields.io/docker/image-size/qmcgaw/gluetun/latest?label=Latest%20image)](https://hub.docker.com/r/qmcgaw/gluetun/tags)
[![GitHub last commit](https://img.shields.io/github/last-commit/qdm12/gluetun.svg)](https://github.com/qdm12/gluetun/commits/master)
[![GitHub commit activity](https://img.shields.io/github/commit-activity/y/qdm12/gluetun.svg)](https://github.com/qdm12/gluetun/graphs/contributors)
[![GitHub closed PRs](https://img.shields.io/github/issues-pr-closed/qdm12/gluetun.svg)](https://github.com/qdm12/gluetun/pulls?q=is%3Apr+is%3Aclosed)
[![GitHub issues](https://img.shields.io/github/issues/qdm12/gluetun.svg)](https://github.com/qdm12/gluetun/issues)
[![GitHub closed issues](https://img.shields.io/github/issues-closed/qdm12/gluetun.svg)](https://github.com/qdm12/gluetun/issues?q=is%3Aissue+is%3Aclosed)
[![Image size](https://images.microbadger.com/badges/image/qmcgaw/private-internet-access.svg)](https://microbadger.com/images/qmcgaw/private-internet-access)
[![Image version](https://images.microbadger.com/badges/version/qmcgaw/private-internet-access.svg)](https://microbadger.com/images/qmcgaw/private-internet-access)
[![Join Slack channel](https://img.shields.io/badge/slack-@qdm12-yellow.svg?logo=slack)](https://join.slack.com/t/qdm12/shared_invite/enQtOTE0NjcxNTM1ODc5LTYyZmVlOTM3MGI4ZWU0YmJkMjUxNmQ4ODQ2OTAwYzMxMTlhY2Q1MWQyOWUyNjc2ODliNjFjMDUxNWNmNzk5MDk)
[![Lines of code](https://img.shields.io/tokei/lines/github/qdm12/gluetun)](https://github.com/qdm12/gluetun)
![Code size](https://img.shields.io/github/languages/code-size/qdm12/gluetun)
![GitHub repo size](https://img.shields.io/github/repo-size/qdm12/gluetun)
![Go version](https://img.shields.io/github/go-mod/go-version/qdm12/gluetun)
![Visitors count](https://visitor-badge.laobi.icu/badge?page_id=gluetun.readme)
## Quick links
- [Setup](#Setup)
- [Features](#Features)
- Problem?
- [Check the Wiki](https://github.com/qdm12/gluetun/wiki)
- [Start a discussion](https://github.com/qdm12/gluetun/discussions)
- [Fix the Unraid template](https://github.com/qdm12/gluetun/discussions/550)
- Suggestion?
- [Create an issue](https://github.com/qdm12/gluetun/issues)
- [Join the Slack channel](https://join.slack.com/t/qdm12/shared_invite/enQtOTE0NjcxNTM1ODc5LTYyZmVlOTM3MGI4ZWU0YmJkMjUxNmQ4ODQ2OTAwYzMxMTlhY2Q1MWQyOWUyNjc2ODliNjFjMDUxNWNmNzk5MDk)
- Happy?
- Sponsor me on [github.com/sponsors/qdm12](https://github.com/sponsors/qdm12)
- Donate to [paypal.me/qmcgaw](https://www.paypal.me/qmcgaw)
- Drop me [an email](mailto:quentin.mcgaw@gmail.com)
- 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** and **PureVPN** servers
- Supports Openvpn only for now
- Based on Alpine 3.15 for a small Docker image of 29MB
- Supports: **Cyberghost**, **ExpressVPN**, **FastestVPN**, **HideMyAss**, **IPVanish**, **IVPN**, **Mullvad**, **NordVPN**, **Perfect Privacy**, **Privado**, **Private Internet Access**, **PrivateVPN**, **ProtonVPN**, **PureVPN**, **Surfshark**, **TorGuard**, **VPNUnlimited**, **Vyprvpn**, **WeVPN**, **Windscribe** servers
- Supports OpenVPN for all providers listed
- Supports Wireguard both kernelspace and userspace
- For **Mullvad**, **Ivpn** and **Windscribe**
- For **Torguard**, **VPN Unlimited** and **WeVPN** using [the custom provider](https://github.com/qdm12/gluetun/wiki/Custom-provider)
- For custom Wireguard configurations using [the custom provider](https://github.com/qdm12/gluetun/wiki/Custom-provider)
- More in progress, see [#134](https://github.com/qdm12/gluetun/issues/134)
- DNS over TLS baked in with service provider(s) of your choice
- DNS fine blocking of malicious/ads/surveillance hostnames and IP addresses, with live update every 24 hours
- Choose the vpn network protocol, `udp` or `tcp`
- Built in firewall kill switch to allow traffic only with needed the VPN servers and LAN devices
- Built in SOCKS5 proxy (Shadowsocks, tunnels TCP+UDP)
- Built in HTTP proxy (Tinyproxy, tunnels TCP)
- [Connect other containers to it](https://github.com/qdm12/gluetun#connect-to-it)
- [Connect LAN devices to it](https://github.com/qdm12/gluetun#connect-to-it)
- Compatible with amd64, i686 (32 bit), **ARM** 64 bit, ARM 32 bit v6 and v7 🎆
- VPN server side port forwarding for Private Internet Access and Vyprvpn
- Built in Shadowsocks proxy (protocol based on SOCKS5 with an encryption layer, tunnels TCP+UDP)
- Built in HTTP proxy (tunnels HTTP and HTTPS through TCP)
- [Connect other containers to it](https://github.com/qdm12/gluetun/wiki/Connect-a-container-to-gluetun)
- [Connect LAN devices to it](https://github.com/qdm12/gluetun/wiki/Connect-a-LAN-device-to-gluetun)
- Compatible with amd64, i686 (32 bit), **ARM** 64 bit, ARM 32 bit v6 and v7, and even ppc64le 🎆
- [Custom VPN server side port forwarding for Private Internet Access](https://github.com/qdm12/gluetun/wiki/Private-internet-access#vpn-server-port-forwarding)
- Possibility of split horizon DNS by selecting multiple DNS over TLS providers
- 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. Requirements
- A VPN account with one of the service providers supported
- If you have a host or router firewall, please refer [to the firewall documentation](https://github.com/qdm12/gluetun/blob/master/doc/firewall.md)
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*: please read [this part of the Wiki](https://github.com/qdm12/gluetun/wiki/Common-issues#synology)
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 REGION="CA Montreal" -e USER=js89ds7 -e PASSWORD=8fd9s239G \
qmcgaw/private-internet-access
```
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
docker-compose up -d
```
Here's a docker-compose.yml for the laziest:
Note that you can:
- Change the many [environment variables](#environment-variables) available
- Use `-p 8888:8888/tcp` to access the HTTP web proxy (and put your LAN in `EXTRA_SUBNETS` environment variable, in example `192.168.1.0/24`)
- Use `-p 8388:8388/tcp -p 8388:8388/udp` to access the SOCKS5 proxy (and put your LAN in `EXTRA_SUBNETS` environment variable, in example `192.168.1.0/24`)
- Use `-p 8000:8000/tcp` to access the [HTTP control server](#HTTP-control-server) built-in
**If you encounter an issue with the tun device not being available, see [the FAQ](https://github.com/qdm12/gluetun/blob/master/doc/faq.md#how-to-fix-openvpn-failing-to-start)**
1. You can update the image with `docker pull qmcgaw/private-internet-access:latest`. See the [wiki](https://github.com/qdm12/gluetun/wiki/Common-issues#use-a-release-tag) for more information on other tags available.
## Testing
Check the VPN IP address matches your expectations
```sh
docker run --rm --network=container:gluetun alpine:3.12 wget -qO- https://ipinfo.io
```yml
version: "3"
services:
gluetun:
image: qmcgaw/gluetun
# container_name: gluetun
# line above must be uncommented to allow external containers to connect. See https://github.com/qdm12/gluetun/wiki/Connect-a-container-to-gluetun#external-container-to-gluetun
cap_add:
- NET_ADMIN
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=
```
Want more testing? ▶ [see the Wiki](https://github.com/qdm12/gluetun/wiki/Testing)
## Environment variables
**TLDR**; only set the 🏁 marked environment variables to get started.
### VPN
| Variable | Default | Choices | Description |
| --- | --- | --- | --- |
| 🏁 `VPNSP` | `private internet access` | `private internet access`, `mullvad`, `windscribe`, `surfshark`, `vyprvpn`, `nordvpn`, `purevpn` | VPN Service Provider |
| `IP_STATUS_FILE` | `/ip` | Any filepath | Filepath to store the public IP address assigned |
| `PROTOCOL` | `udp` | `udp` or `tcp` | Network protocol to use |
| `OPENVPN_VERBOSITY` | `1` | `0` to `6` | Openvpn verbosity level |
| `OPENVPN_ROOT` | `no` | `yes` or `no` | Run OpenVPN as root |
| `OPENVPN_TARGET_IP` | | Valid IP address | Specify a target VPN server (or gateway) IP address to use |
| `OPENVPN_CIPHER` | | i.e. `aes-256-gcm` | Specify a custom cipher to use. It will also set `ncp-disable` if using AES GCM for PIA |
| `OPENVPN_AUTH` | | i.e. `sha256` | Specify a custom auth algorithm to use |
*For all providers below, server location parameters are all optional. By default a random server is picked using the filter settings provided.*
- Private Internet Access
| Variable | Default | Choices | Description |
| --- | --- | --- | --- |
| 🏁 `USER` | | | Your username |
| 🏁 `PASSWORD` | | | Your password |
| `REGION` | | One of the [PIA regions](https://www.privateinternetaccess.com/pages/network/) | VPN server region |
| `PIA_ENCRYPTION` | `strong` | `normal`, `strong` | Encryption preset |
| `PORT_FORWARDING` | `off` | `on`, `off` | Enable port forwarding on the VPN server |
| `PORT_FORWARDING_STATUS_FILE` | `/forwarded_port` | Any filepath | Filepath to store the forwarded port number |
- Mullvad
| Variable | Default | Choices | Description |
| --- | --- | --- | --- |
| 🏁 `USER` | | | Your user ID |
| `COUNTRY` | | One of the [Mullvad countries](https://mullvad.net/en/servers/#openvpn) | VPN server country |
| `CITY` | | One of the [Mullvad cities](https://mullvad.net/en/servers/#openvpn) | VPN server city |
| `ISP` | | One of the [Mullvad ISP](https://mullvad.net/en/servers/#openvpn) | VPN server ISP |
| `PORT` | | `80` or `443` for TCP; or `53` for UDP. Leave blank for default Mullvad server port | Custom VPN port to use |
- Windscribe
| Variable | Default | Choices | Description |
| --- | --- | --- | --- |
| 🏁 `USER` | | | Your username |
| 🏁 `PASSWORD` | | | Your password |
| `REGION` | | One of the [Windscribe regions](https://windscribe.com/status) | VPN server region |
| `PORT` | | One from the [this list of ports](https://windscribe.com/getconfig/openvpn) | Custom VPN port to use |
- Surfshark
| Variable | Default | Choices | Description |
| --- | --- | --- | --- |
| 🏁 `USER` | | | Your **service** username, found at the bottom of the [manual setup page](https://account.surfshark.com/setup/manual) |
| 🏁 `PASSWORD` | | | Your **service** password |
| `REGION` | | One of the [Surfshark regions](https://github.com/qdm12/gluetun/wiki/surfshark) | VPN server region |
- Cyberghost
| Variable | Default | Choices | Description |
| --- | --- | --- | --- |
| 🏁 `USER` | | | Your username |
| 🏁 `PASSWORD` | | | Your password |
| 🏁 `CLIENT_KEY` | | | Your device client key content, **see below** |
| `REGION` | | One of the [Cyberghost countries](https://github.com/qdm12/gluetun/wiki/Cyberghost#regions) | VPN server country |
| `CYBERGHOST_GROUP` | `Premium UDP Europe` | One of the [server groups](https://github.com/qdm12/gluetun/wiki/Cyberghost#server-groups) | Server group |
To specify your client key, you can either:
- Bind mount it at `/files/client.key`, for example with `-v /yourpath/client.key:/files/client.key:ro`
- Convert it to a single line value using:
```sh
docker run -it --rm -v /yourpath/client.key:/files/client.key:ro qmcgaw/private-internet-access clientkey
```
And use the line produced as the value for the environment variable `CLIENT_KEY`.
- Vyprvpn
| Variable | Default | Choices | Description |
| --- | --- | --- | --- |
| 🏁 `USER` | | | Your username |
| 🏁 `PASSWORD` | | | Your password |
| `REGION` | | One of the [VyprVPN regions](https://www.vyprvpn.com/server-locations) | VPN server region |
- NordVPN
| Variable | Default | Choices | Description |
| --- | --- | --- | --- |
| 🏁 `USER` | | | Your username |
| 🏁 `PASSWORD` | | | Your password |
| `REGION` | | One of the NordVPN server country, i.e. `Switzerland` | VPN server country |
| `SERVER_NUMBER` | | Server integer number | Optional server number. For example `251` for `Italy #251` |
- PureVPN
| Variable | Default | Choices | Description |
| --- | --- | --- | --- |
| 🏁 `USER` | | | Your user ID |
| 🏁 `REGION` | | One of the [PureVPN regions](https://support.purevpn.com/vpn-servers) | VPN server region |
| `COUNTRY` | | One of the [PureVPN countries](https://support.purevpn.com/vpn-servers) | VPN server country |
| `CITY` | | One of the [PureVPN cities](https://support.purevpn.com/vpn-servers) | VPN server city |
### DNS over TLS
None of the following values are required.
| Variable | Default | Choices | Description |
| --- | --- | --- | --- |
| `DOT` | `on` | `on`, `off` | Activate DNS over TLS with Unbound |
| `DOT_PROVIDERS` | `cloudflare` | `cloudflare`, `google`, `quad9`, `quadrant`, `cleanbrowsing`, `securedns`, `libredns` | Comma delimited list of DNS over TLS providers |
| `DOT_CACHING` | `on` | `on`, `off` | Unbound caching |
| `DOT_IPV6` | `off` | `on`, `off` | DNS IPv6 resolution |
| `DOT_PRIVATE_ADDRESS` | All private CIDRs ranges | | Comma separated list of CIDRs or single IP addresses Unbound won't resolve to. Note that the default setting prevents DNS rebinding |
| `DOT_VERBOSITY` | `1` | `0` to `5` | Unbound verbosity level |
| `DOT_VERBOSITY_DETAILS` | `0` | `0` to `4` | Unbound details verbosity level |
| `DOT_VALIDATION_LOGLEVEL` | `0` | `0` to `2` | Unbound validation log level |
| `DNS_UPDATE_PERIOD` | `24h` | i.e. `0`, `30s`, `5m`, `24h` | Period to update block lists and cryptographic files and restart Unbound. Set to `0` to deactivate updates |
| `BLOCK_MALICIOUS` | `on` | `on`, `off` | Block malicious hostnames and IPs with Unbound |
| `BLOCK_SURVEILLANCE` | `off` | `on`, `off` | Block surveillance hostnames and IPs with Unbound |
| `BLOCK_ADS` | `off` | `on`, `off` | Block ads hostnames and IPs with Unbound |
| `UNBLOCK` | |i.e. `domain1.com,x.domain2.co.uk` | Comma separated list of domain names to leave unblocked with Unbound |
| `DNS_PLAINTEXT_ADDRESS` | `1.1.1.1` | Any IP address | IP address to use as DNS resolver if `DOT` is `off` |
| `DNS_KEEP_NAMESERVER` | `off` | `on` or `off` | Keep the nameservers in /etc/resolv.conf untouched, but disabled DNS blocking features |
### Firewall
That one is important if you want to connect to the container from your LAN for example, using Shadowsocks or Tinyproxy.
| Variable | Default | Choices | Description |
| --- | --- | --- | --- |
| `FIREWALL` | `on` | `on` or `off` | Turn on or off the container built-in firewall. You should use it for **debugging purposes** only. |
| `EXTRA_SUBNETS` | | i.e. `192.168.1.0/24,192.168.10.121,10.0.0.5/28` | Comma separated subnets allowed in the container firewall |
| `FIREWALL_VPN_INPUT_PORTS` | | i.e. `1000,8080` | Comma separated list of ports to allow from the VPN server side (useful for **vyprvpn** port forwarding) |
| `FIREWALL_DEBUG` | `off` | `on` or `off` | Prints every firewall related command. You should use it for **debugging purposes** only. |
### Shadowsocks
| Variable | Default | Choices | Description |
| --- | --- | --- | --- |
| `SHADOWSOCKS` | `off` | `on`, `off` | Enable the internal SOCKS5 proxy Shadowsocks |
| `SHADOWSOCKS_LOG` | `off` | `on`, `off` | Enable logging |
| `SHADOWSOCKS_PORT` | `8388` | `1024` to `65535` | Internal port number for Shadowsocks to listen on |
| `SHADOWSOCKS_PASSWORD` | | | Password to use to connect to Shadowsocks |
| `SHADOWSOCKS_METHOD` | `chacha20-ietf-poly1305` | `chacha20-ietf-poly1305`, `aes-128-gcm`, `aes-256-gcm` | Method to use for Shadowsocks |
### Tinyproxy
| Variable | Default | Choices | Description |
| --- | --- | --- | --- |
| `TINYPROXY` | `off` | `on`, `off` | Enable the internal HTTP proxy tinyproxy |
| `TINYPROXY_LOG` | `Info` | `Info`, `Connect`, `Notice`, `Warning`, `Error`, `Critical` | Tinyproxy log level |
| `TINYPROXY_PORT` | `8888` | `1024` to `65535` | Internal port number for Tinyproxy to listen on |
| `TINYPROXY_USER` | | | Username to use to connect to Tinyproxy |
| `TINYPROXY_PASSWORD` | | | Password to use to connect to Tinyproxy |
### System
| Variable | Default | Choices | Description |
| --- | --- | --- | --- |
| `TZ` | | i.e. `Europe/London` | Specify a timezone to use to have correct log times |
| `UID` | `1000` | | User ID to run as non root and for ownership of files written |
| `GID` | `1000` | | Group ID to run as non root and for ownership of files written |
### Other
| Variable | Default | Choices | Description |
| --- | --- | --- | --- |
| `PUBLICIP_PERIOD` | `12h` | Valid duration | Period to check for public IP address. Set to `0` to disable. |
## Connect to it
There are various ways to achieve this, depending on your use case.
- <details><summary>Connect containers in the same docker-compose.yml as Gluetun</summary><p>
Add `network_mode: "service:gluetun"` to your *docker-compose.yml* (no need for `depends_on`)
</p></details>
- <details><summary>Connect other containers to Gluetun</summary><p>
Add `--network=container:gluetun` when launching the container, provided Gluetun is already running
</p></details>
- <details><summary>Connect containers from another docker-compose.yml</summary><p>
Add `network_mode: "container:gluetun"` to your *docker-compose.yml*, provided Gluetun is already running
</p></details>
- <details><summary>Connect LAN devices through the built-in HTTP proxy *Tinyproxy* (i.e. with Chrome, Kodi, etc.)</summary><p>
You might want to use Shadowsocks instead which tunnels UDP as well as TCP, whereas Tinyproxy only tunnels TCP.
1. Setup a HTTP proxy client, such as [SwitchyOmega for Chrome](https://chrome.google.com/webstore/detail/proxy-switchyomega/padekgcemlokbadohgkifijomclgjgif?hl=en)
1. Ensure the Gluetun container is launched with:
- port `8888` published `-p 8888:8888/tcp`
- your LAN subnet, i.e. `192.168.1.0/24`, set as `-e EXTRA_SUBNETS=192.168.1.0/24`
1. With your HTTP proxy client, connect to the Docker host (i.e. `192.168.1.10`) on port `8888`. You need to enter your credentials if you set them with `TINYPROXY_USER` and `TINYPROXY_PASSWORD`.
1. If you set `TINYPROXY_LOG` to `Info`, more information will be logged in the Docker logs
</p></details>
- <details><summary>Connect LAN devices through the built-in SOCKS5 proxy *Shadowsocks* (per app, system wide, etc.)</summary><p>
1. Setup a SOCKS5 proxy client, there is a list of [ShadowSocks clients for **all platforms**](https://shadowsocks.org/en/download/clients.html)
- **note** some clients do not tunnel UDP so your DNS queries will be done locally and not through Gluetun and its built in DNS over TLS
- Clients that support such UDP tunneling are, as far as I know:
- iOS: Potatso Lite
- OSX: ShadowsocksX
- Android: Shadowsocks by Max Lv
1. Ensure the Gluetun container is launched with:
- port `8388` published `-p 8388:8388/tcp -p 8388:8388/udp`
- your LAN subnet, i.e. `192.168.1.0/24`, set as `-e EXTRA_SUBNETS=192.168.1.0/24`
1. With your SOCKS5 proxy client
- Enter the Docker host (i.e. `192.168.1.10`) as the server IP
- Enter port TCP (and UDP, if available) `8388` as the server port
- Use the password you have set with `SHADOWSOCKS_PASSWORD`
- Choose the encryption method/algorithm to the method you specified in `SHADOWSOCKS_METHOD`
1. If you set `SHADOWSOCKS_LOG` to `on`, (a lot) more information will be logged in the Docker logs
</p></details>
- <details><summary>Access ports of containers connected to Gluetun</summary><p>
In example, to access port `8000` of container `xyz` and `9000` of container `abc` connected to Gluetun,
publish ports `8000` and `9000` for the Gluetun container and access them as you would with any other container
</p></details>
- <details><summary>Access ports of containers connected to Gluetun, all in the same docker-compose.yml</summary><p>
In example, to access port `8000` of container `xyz` and `9000` of container `abc` connected to Gluetun, publish port `8000` and `9000` for the Gluetun container.
The docker-compose.yml file would look like:
```yml
version: '3.7'
services:
gluetun:
image: qmcgaw/private-internet-access
container_name: gluetun
cap_add:
- NET_ADMIN
environment:
- USER=js89ds7
- PASSWORD=8fd9s239G
ports:
- 8000:8000/tcp
- 9000:9000/tcp
abc:
image: abc
container_name: abc
network_mode: "service:gluetun"
xyz:
image: xyz
container_name: xyz
network_mode: "service:gluetun"
```
</p></details>
## Private Internet Access port forwarding
Note that [not all regions support port forwarding](https://www.privateinternetaccess.com/helpdesk/kb/articles/how-do-i-enable-port-forwarding-on-my-vpn).
When `PORT_FORWARDING=on`, a port will be forwarded on the VPN server side and written to the file specified by `PORT_FORWARDING_STATUS_FILE=/forwarded_port`.
It can be useful to mount this file as a volume to read it from other containers, for example to configure a torrenting client.
You can also use the HTTP control server (see below) to get the port forwarded.
## HTTP control server
See [its Wiki page](https://github.com/qdm12/gluetun/wiki/HTTP-control-server)
## Development and contributing
- Contribute with code: see [the Wiki](https://github.com/qdm12/gluetun/wiki/Contributing).
- [The list of existing contributors 👍](https://github.com/qdm12/gluetun/blob/master/.github/CONTRIBUTING.md#Contributors)
- [Github workflows](https://github.com/qdm12/gluetun/actions) to know what's building
- [List of issues and feature requests](https://github.com/qdm12/gluetun/issues)
## License
This repository is under an [MIT license](https://github.com/qdm12/gluetun/master/license)
## Support
Sponsor me on [Github](https://github.com/sponsors/qdm12), donate to [paypal.me/qmcgaw](https://www.paypal.me/qmcgaw) or subscribe to a VPN provider through one of my affiliate links:
[![https://github.com/sponsors/qdm12](https://raw.githubusercontent.com/qdm12/gluetun/master/doc/sponsors.jpg)](https://github.com/sponsors/qdm12)
[![https://www.paypal.me/qmcgaw](https://raw.githubusercontent.com/qdm12/gluetun/master/doc/paypal.jpg)](https://www.paypal.me/qmcgaw)
[![https://windscribe.com/?affid=mh7nyafu](https://raw.githubusercontent.com/qdm12/gluetun/master/doc/windscribe.jpg)](https://windscribe.com/?affid=mh7nyafu)
Feel also free to have a look at [the Kanban board](https://github.com/qdm12/gluetun/projects/1) and [contribute](#Development-and-contributing) to the code or the issues discussion.
Many thanks to @Frepke, @Ralph521, G. Mendez, M. Otmar Weber, J. Perez and A. Cooper for supporting me financially 🥇👍
[![MIT](https://img.shields.io/github/license/qdm12/gluetun)](https://github.com/qdm12/gluetun/master/LICENSE)

View File

@@ -2,324 +2,464 @@ package main
import (
"context"
"errors"
"fmt"
"net/http"
"os"
"os/signal"
"strings"
"sync"
"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"
"github.com/qdm12/gluetun/internal/configuration/sources"
"github.com/qdm12/gluetun/internal/configuration/sources/env"
"github.com/qdm12/gluetun/internal/configuration/sources/files"
"github.com/qdm12/gluetun/internal/configuration/sources/mux"
"github.com/qdm12/gluetun/internal/configuration/sources/secrets"
"github.com/qdm12/gluetun/internal/constants"
"github.com/qdm12/gluetun/internal/dns"
"github.com/qdm12/gluetun/internal/firewall"
gluetunLogging "github.com/qdm12/gluetun/internal/logging"
"github.com/qdm12/gluetun/internal/healthcheck"
"github.com/qdm12/gluetun/internal/httpproxy"
"github.com/qdm12/gluetun/internal/models"
"github.com/qdm12/gluetun/internal/netlink"
"github.com/qdm12/gluetun/internal/openvpn"
"github.com/qdm12/gluetun/internal/params"
"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/settings"
"github.com/qdm12/gluetun/internal/shadowsocks"
"github.com/qdm12/gluetun/internal/tinyproxy"
"github.com/qdm12/gluetun/internal/storage"
"github.com/qdm12/gluetun/internal/tun"
"github.com/qdm12/gluetun/internal/updater"
"github.com/qdm12/gluetun/internal/vpn"
"github.com/qdm12/golibs/command"
"github.com/qdm12/golibs/files"
"github.com/qdm12/golibs/logging"
"github.com/qdm12/golibs/network"
"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"
created = "an unknown date"
)
var (
errSetupRouting = errors.New("cannot setup routing")
errCreateUser = errors.New("cannot create user")
)
func main() {
ctx := context.Background()
os.Exit(_main(ctx, os.Args))
}
func _main(background context.Context, args []string) int {
if len(args) > 1 { // cli operation
var err error
switch args[1] {
case "healthcheck":
err = cli.HealthCheck()
case "clientkey":
err = cli.ClientKey(args[2:])
case "openvpnconfig":
err = cli.OpenvpnConfig()
default:
err = fmt.Errorf("command %q is unknown", args[1])
}
if err != nil {
fmt.Println(err)
return 1
}
return 0
buildInfo := models.BuildInformation{
Version: version,
Commit: commit,
Created: created,
}
background := context.Background()
signalCtx, stop := signal.NotifyContext(background, syscall.SIGINT, syscall.SIGTERM, os.Interrupt)
ctx, cancel := context.WithCancel(background)
defer cancel()
logger := createLogger()
fatalOnError := makeFatalOnError(logger, cancel)
client := network.NewClient(15 * time.Second)
// Create configurators
fileManager := files.NewFileManager()
alpineConf := alpine.NewConfigurator(fileManager)
ovpnConf := openvpn.NewConfigurator(logger, fileManager)
dnsConf := dns.NewConfigurator(logger, client, fileManager)
routingConf := routing.NewRouting(logger, fileManager)
firewallConf := firewall.NewConfigurator(logger, routingConf, fileManager)
tinyProxyConf := tinyproxy.NewConfigurator(fileManager, logger)
streamMerger := command.NewStreamMerger()
paramsReader := params.NewReader(logger, fileManager)
fmt.Println(gluetunLogging.Splash(
paramsReader.GetVersion(),
paramsReader.GetVcsRef(),
paramsReader.GetBuildDate()))
printVersions(ctx, logger, map[string]func(ctx context.Context) (string, error){
"OpenVPN": ovpnConf.Version,
"Unbound": dnsConf.Version,
"IPtables": firewallConf.Version,
"TinyProxy": tinyProxyConf.Version,
logger := logging.New(logging.Settings{
Level: logging.LevelInfo,
})
allSettings, err := settings.GetAllSettings(paramsReader)
fatalOnError(err)
args := os.Args
tun := tun.New()
netLinker := netlink.New()
cli := cli.New()
cmder := command.NewCmder()
envReader := env.New(logger)
filesReader := files.New()
secretsReader := secrets.New()
muxReader := mux.New(envReader, filesReader, secretsReader)
errorCh := make(chan error)
go func() {
errorCh <- _main(ctx, buildInfo, args, logger, muxReader, tun, netLinker, cmder, cli)
}()
select {
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
os.Exit(0)
}
logger.Error(err.Error())
cancel()
}
const shutdownGracePeriod = 5 * time.Second
timer := time.NewTimer(shutdownGracePeriod)
select {
case <-errorCh:
if !timer.Stop() {
<-timer.C
}
logger.Info("Shutdown successful")
case <-timer.C:
logger.Warn("Shutdown timed out")
}
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.ParentLogger, source sources.Source,
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, source, logger)
case "clientkey":
return cli.ClientKey(args[2:])
case "openvpnconfig":
return cli.OpenvpnConfig(logger, source)
case "update":
return cli.Update(ctx, args[2:], logger)
case "format-servers":
return cli.FormatServers(args[2:])
default:
return fmt.Errorf("%w: %s", errCommandUnknown, args[1])
}
}
announcementExp, err := time.Parse(time.RFC3339, "2021-02-15T00: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: "Large settings parsing refactoring merged on 2022-01-06, please report any issue!",
AnnounceExp: announcementExp,
// Sponsor information
PaypalUser: "qmcgaw",
GithubSponsor: "qdm12",
}
for _, line := range gosplash.MakeLines(splashSettings) {
fmt.Println(line)
}
allSettings, err := source.Read()
if err != nil {
return err
}
// 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()
err = allSettings.Validate(allServers)
if err != nil {
return err
}
logger.PatchLevel(*allSettings.Log.Level)
puid, pgid := int(*allSettings.System.PUID), int(*allSettings.System.PGID)
const clientTimeout = 15 * time.Second
httpClient := &http.Client{Timeout: clientTimeout}
// Create configurators
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(nil, cmder, dnsCrypto,
"/etc/unbound", "/usr/sbin/unbound", cacertsPath)
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)
}},
})
if err != nil {
return err
}
logger.Info(allSettings.String())
// Should never change
uid, gid := allSettings.System.UID, allSettings.System.GID
err = alpineConf.CreateUser("nonrootuser", uid)
fatalOnError(err)
err = fileManager.SetOwnership("/etc/unbound", uid, gid)
fatalOnError(err)
err = fileManager.SetOwnership("/etc/tinyproxy", uid, gid)
fatalOnError(err)
if allSettings.Firewall.Debug {
firewallConf.SetDebug()
routingConf.SetDebug()
if err := os.MkdirAll("/tmp/gluetun", 0644); err != nil {
return err
}
if err := os.MkdirAll("/gluetun", 0644); err != nil {
return err
}
const defaultUsername = "nonrootuser"
nonRootUsername, err := alpineConf.CreateUser(defaultUsername, puid)
if err != nil {
return fmt.Errorf("%w: %s", errCreateUser, err)
}
if nonRootUsername != defaultUsername {
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.DoT.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 {
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 {
fatalOnError(err)
return err
}
localSubnet, err := routingConf.LocalSubnet()
localNetworks, err := routingConf.LocalNetworks()
if err != nil {
fatalOnError(err)
return err
}
firewallConf.SetNetworkInformation(defaultInterface, defaultGateway, localSubnet)
if err := ovpnConf.CheckTUN(); err != nil {
logger.Warn(err)
err = ovpnConf.CreateTUN()
fatalOnError(err)
}
connectedCh := make(chan struct{})
signalConnected := func() {
connectedCh <- struct{}{}
}
defer close(connectedCh)
go collectStreamLines(ctx, streamMerger, logger, signalConnected)
if allSettings.Firewall.Enabled {
err := firewallConf.SetEnabled(ctx, true) // disabled by default
fatalOnError(err)
}
err = firewallConf.SetAllowedSubnets(ctx, allSettings.Firewall.AllowedSubnets)
fatalOnError(err)
for _, vpnPort := range allSettings.Firewall.VPNInputPorts {
err = firewallConf.SetAllowedPort(ctx, vpnPort, string(constants.TUN))
fatalOnError(err)
}
wg := &sync.WaitGroup{}
openvpnLooper := openvpn.NewLooper(allSettings.VPNSP, allSettings.OpenVPN, uid, gid,
ovpnConf, firewallConf, logger, client, fileManager, streamMerger, fatalOnError)
restartOpenvpn := openvpnLooper.Restart
portForward := openvpnLooper.PortForward
getOpenvpnSettings := openvpnLooper.GetSettings
getPortForwarded := openvpnLooper.GetPortForwarded
// wait for restartOpenvpn
go openvpnLooper.Run(ctx, wg)
unboundLooper := dns.NewLooper(dnsConf, allSettings.DNS, logger, streamMerger, uid, gid)
restartUnbound := unboundLooper.Restart
// wait for restartUnbound
go unboundLooper.Run(ctx, wg)
publicIPLooper := publicip.NewLooper(client, logger, fileManager, allSettings.System.IPStatusFilepath, allSettings.PublicIPPeriod, uid, gid)
restartPublicIP := publicIPLooper.Restart
setPublicIPPeriod := publicIPLooper.SetPeriod
go publicIPLooper.Run(ctx)
go publicIPLooper.RunRestartTicker(ctx)
setPublicIPPeriod(allSettings.PublicIPPeriod) // call after RunRestartTicker
tinyproxyLooper := tinyproxy.NewLooper(tinyProxyConf, firewallConf, allSettings.TinyProxy, logger, streamMerger, uid, gid, defaultInterface)
restartTinyproxy := tinyproxyLooper.Restart
go tinyproxyLooper.Run(ctx, wg)
shadowsocksLooper := shadowsocks.NewLooper(firewallConf, allSettings.ShadowSocks, logger, defaultInterface)
restartShadowsocks := shadowsocksLooper.Restart
go shadowsocksLooper.Run(ctx, wg)
if allSettings.TinyProxy.Enabled {
restartTinyproxy()
}
if allSettings.ShadowSocks.Enabled {
restartShadowsocks()
}
go func() {
var restartTickerContext context.Context
var restartTickerCancel context.CancelFunc = func() {}
for {
select {
case <-ctx.Done():
restartTickerCancel()
return
case <-connectedCh: // blocks until openvpn is connected
restartTickerCancel()
restartTickerContext, restartTickerCancel = context.WithCancel(ctx)
go unboundLooper.RunRestartTicker(restartTickerContext)
onConnected(allSettings, logger, routingConf, portForward, restartUnbound, restartPublicIP)
}
}
}()
httpServer := server.New("0.0.0.0:8000", logger, restartOpenvpn, restartUnbound, getOpenvpnSettings, getPortForwarded)
go httpServer.Run(ctx, wg)
// Start openvpn for the first time
restartOpenvpn()
signalsCh := make(chan os.Signal, 1)
signal.Notify(signalsCh,
syscall.SIGINT,
syscall.SIGTERM,
os.Interrupt,
)
shutdownErrorsCount := 0
select {
case signal := <-signalsCh:
logger.Warn("Caught OS signal %s, shutting down", signal)
cancel()
case <-ctx.Done():
logger.Warn("context canceled, shutting down")
}
logger.Info("Clearing ip status file %s", allSettings.System.IPStatusFilepath)
if err := fileManager.Remove(string(allSettings.System.IPStatusFilepath)); err != nil {
logger.Error(err)
shutdownErrorsCount++
}
if allSettings.OpenVPN.Provider.PortForwarding.Enabled {
logger.Info("Clearing forwarded port status file %s", allSettings.OpenVPN.Provider.PortForwarding.Filepath)
if err := fileManager.Remove(string(allSettings.OpenVPN.Provider.PortForwarding.Filepath)); err != nil {
logger.Error(err)
shutdownErrorsCount++
}
}
waiting, waited := context.WithTimeout(context.Background(), time.Second)
go func() {
defer waited()
wg.Wait()
}()
<-waiting.Done()
if waiting.Err() == context.DeadlineExceeded {
if shutdownErrorsCount > 0 {
logger.Warn("Shutdown had %d errors", shutdownErrorsCount)
}
logger.Warn("Shutdown timed out")
return 1
}
if shutdownErrorsCount > 0 {
logger.Warn("Shutdown had %d errors")
return 1
}
logger.Info("Shutdown successful")
return 0
}
func makeFatalOnError(logger logging.Logger, cancel context.CancelFunc) func(err error) {
return func(err error) {
if err != nil {
logger.Error(err)
cancel()
}
}
}
func createLogger() logging.Logger {
logger, err := logging.NewLogger(logging.ConsoleEncoding, logging.InfoLevel, -1)
defaultIP, err := routingConf.DefaultIP()
if err != nil {
panic(err)
return err
}
return logger
}
func printVersions(ctx context.Context, logger logging.Logger, versionFunctions map[string]func(ctx context.Context) (string, error)) {
ctx, cancel := context.WithTimeout(ctx, 5*time.Second)
defer cancel()
for name, f := range versionFunctions {
version, err := f(ctx)
if err != nil {
logger.Error(err)
} else {
logger.Info("%s version: %s", name, version)
}
}
}
func collectStreamLines(ctx context.Context, streamMerger command.StreamMerger, logger logging.Logger, signalConnected func()) {
// Blocking line merging paramsReader for all programs: openvpn, tinyproxy, unbound and shadowsocks
logger.Info("Launching standard output merger")
streamMerger.CollectLines(ctx, func(line string) {
line, level := gluetunLogging.PostProcessLine(line)
if line == "" {
return
}
switch level {
case logging.InfoLevel:
logger.Info(line)
case logging.WarnLevel:
logger.Warn(line)
case logging.ErrorLevel:
logger.Error(line)
}
if strings.Contains(line, "Initialization Sequence Completed") {
signalConnected()
}
}, func(err error) {
logger.Warn(err)
firewallLogger := logger.NewChild(logging.Settings{
Prefix: "firewall: ",
Level: firewallLogLevel,
})
}
firewallConf := firewall.NewConfig(firewallLogger, cmder,
defaultInterface, defaultGateway, localNetworks, defaultIP)
func onConnected(allSettings settings.Settings, logger logging.Logger, routingConf routing.Routing,
portForward, restartUnbound, restartPublicIP func(),
) {
restartUnbound()
restartPublicIP()
if allSettings.OpenVPN.Provider.PortForwarding.Enabled {
time.AfterFunc(5*time.Second, portForward)
if err := routingConf.Setup(); err != nil {
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)
}
defaultInterface, _, err := routingConf.DefaultRoute()
if err != nil {
logger.Warn(err)
} else {
vpnGatewayIP, err := routingConf.VPNGatewayIP(defaultInterface)
defer func() {
logger.Info("routing cleanup...")
if err := routingConf.TearDown(); err != nil {
logger.Error("cannot teardown routing: " + err.Error())
}
}()
if err := firewallConf.SetOutboundSubnets(ctx, allSettings.Firewall.OutboundSubnets); err != nil {
return err
}
if err := routingConf.SetOutboundRoutes(allSettings.Firewall.OutboundSubnets); err != nil {
return err
}
if err := tun.Check(constants.TunnelDevice); err != nil {
logger.Info(err.Error() + "; creating it...")
err = tun.Create(constants.TunnelDevice)
if err != nil {
logger.Warn(err)
} else {
logger.Info("Gateway VPN IP address: %s", vpnGatewayIP)
return err
}
}
if *allSettings.Firewall.Enabled {
err := firewallConf.SetEnabled(ctx, true) // disabled by default
if err != nil {
return err
}
}
for _, port := range allSettings.Firewall.InputPorts {
err = firewallConf.SetAllowedPort(ctx, port, defaultInterface)
if err != nil {
return err
}
} // TODO move inside firewall?
// 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)}
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.Version.Enabled)
vpnHandler, vpnCtx, vpnDone := goshutdown.NewGoRoutineHandler(
"vpn", goroutine.OptionTimeout(time.Second))
go vpnLooper.Run(vpnCtx, vpnDone)
updaterLooper := updater.NewLooper(allSettings.Updater,
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(updaterCtx, updaterDone)
tickersGroupHandler.Add(updaterHandler)
updaterTickerHandler, updaterTickerCtx, updaterTickerDone := goshutdown.NewGoRoutineHandler(
"updater ticker", goroutine.OptionTimeout(defaultShutdownTimeout))
go updaterLooper.RunRestartTicker(updaterTickerCtx, updaterTickerDone)
controlGroupHandler.Add(updaterTickerHandler)
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)
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)
controlServerAddress := fmt.Sprintf(":%d", *allSettings.ControlServer.Port)
controlServerLogging := *allSettings.ControlServer.Log
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)
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)
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()
return orderHandler.Shutdown(context.Background())
}
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 _, element := range elements {
version, err := element.getVersion(ctx)
if err != nil {
return err
}
logger.Info(element.name + " version: " + version)
}
return nil
}

View File

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

View File

@@ -1,111 +0,0 @@
package main
import (
"encoding/json"
"flag"
"fmt"
"net/http"
"os"
"sort"
"strings"
"time"
"github.com/qdm12/golibs/network"
)
func main() {
os.Exit(_main())
}
func _main() int {
provider := flag.String("provider", "purevpn", "VPN provider to map location to subdomain, can be 'purevpn'")
flag.Parse()
client := network.NewClient(5 * time.Second)
switch *provider {
case "purevpn":
servers, warnings, err := purevpn(client)
if err != nil {
fmt.Println(err)
return 1
}
for _, server := range servers {
fmt.Printf(
"{subdomain: %q, region: %q, country: %q, city: %q},\n",
server.subdomain, server.region, server.country, server.city,
)
}
fmt.Print("\n\n")
for _, warning := range warnings {
fmt.Println(warning)
}
default:
fmt.Printf("Provider %q is not supported\n", *provider)
return 1
}
return 0
}
type purevpnServer struct {
region string
country string
city string
subdomain string // without -tcp or -udp suffix
}
func purevpn(client network.Client) (servers []purevpnServer, warnings []string, err error) {
content, status, err := client.GetContent("https://support.purevpn.com/vpn-servers")
if err != nil {
return nil, nil, err
} else if status != http.StatusOK {
return nil, nil, fmt.Errorf("HTTP status %d from Purevpn", status)
}
const jsonPrefix = "<script>var servers = "
const jsonSuffix = "</script>"
s := string(content)
jsonPrefixIndex := strings.Index(s, jsonPrefix)
if jsonPrefixIndex == -1 {
return nil, nil, fmt.Errorf("cannot find prefix %s in html", jsonPrefix)
}
if len(s[jsonPrefixIndex:]) == len(jsonPrefix) {
return nil, nil, fmt.Errorf("no body after json prefix %s", jsonPrefix)
}
s = s[jsonPrefixIndex+len(jsonPrefix):]
endIndex := strings.Index(s, jsonSuffix)
s = s[:endIndex]
var data []struct {
Region string `json:"region_name"`
Country string `json:"country_name"`
City string `json:"city_name"`
TCP string `json:"tcp"`
UDP string `json:"udp"`
}
if err := json.Unmarshal([]byte(s), &data); err != nil {
return nil, nil, err
}
sort.Slice(data, func(i, j int) bool {
if data[i].Region == data[j].Region {
if data[i].Country == data[j].Country {
return data[i].City < data[j].City
}
return data[i].Country < data[j].Country
}
return data[i].Region < data[j].Region
})
for i := range data {
if data[i].UDP == "" && data[i].TCP == "" {
warnings = append(warnings, fmt.Sprintf("server %s %s %s does not support TCP and UDP for openvpn", data[i].Region, data[i].Country, data[i].City))
continue
}
if data[i].UDP == "" || data[i].TCP == "" {
warnings = append(warnings, fmt.Sprintf("server %s %s %s does not support TCP or udp for openvpn", data[i].Region, data[i].Country, data[i].City))
}
servers = append(servers, purevpnServer{
region: data[i].Region,
country: data[i].Country,
city: data[i].City,
subdomain: strings.TrimSuffix(data[i].TCP, "-tcp.pointtoserver.com"),
})
}
return servers, warnings, nil
}

View File

@@ -1,106 +0,0 @@
package main
import (
"encoding/json"
"flag"
"fmt"
"net"
"net/http"
"os"
"sort"
"strconv"
"strings"
"time"
"github.com/qdm12/gluetun/internal/models"
"github.com/qdm12/golibs/network"
)
func main() {
os.Exit(_main())
}
func _main() int {
provider := flag.String("provider", "nordvpn", "VPN provider to map region to IP addresses using their API, can be 'nordvpn'")
flag.Parse()
client := network.NewClient(30 * time.Second) // big file so 30 seconds
switch *provider {
case "nordvpn":
servers, ignoredServers, err := nordvpn(client)
if err != nil {
fmt.Println(err)
return 1
}
for _, server := range servers {
fmt.Printf(
"{Region: %q, Number: %d, TCP: %t, UDP: %t, IP: net.IP{%s}},\n",
server.Region, server.Number, server.TCP, server.UDP, strings.ReplaceAll(server.IP.String(), ".", ", "),
)
}
fmt.Print("\n\n")
for _, serverName := range ignoredServers {
fmt.Printf("ignored server %q because it does not support both UDP and TCP\n", serverName)
}
default:
fmt.Printf("Provider %q is not supported\n", *provider)
return 1
}
return 0
}
func nordvpn(client network.Client) (servers []models.NordvpnServer, ignoredServers []string, err error) {
content, status, err := client.GetContent("https://nordvpn.com/api/server")
if err != nil {
return nil, nil, err
} else if status != http.StatusOK {
return nil, nil, fmt.Errorf("HTTP status %d from NordVPN API", status)
}
response := []struct {
IPAddress string `json:"ip_address"`
Name string `json:"name"`
Country string `json:"country"`
Features struct {
UDP bool `json:"openvpn_udp"`
TCP bool `json:"openvpn_tcp"`
} `json:"features"`
}{}
if err := json.Unmarshal(content, &response); err != nil {
return nil, nil, err
}
for _, element := range response {
if !element.Features.TCP && !element.Features.UDP {
ignoredServers = append(ignoredServers, element.Name)
}
ip := net.ParseIP(element.IPAddress)
if ip == nil {
return nil, nil, fmt.Errorf("IP address %q is not valid for server %q", element.IPAddress, element.Name)
}
i := strings.IndexRune(element.Name, '#')
if i < 0 {
return nil, nil, fmt.Errorf("No ID in server name %q", element.Name)
}
idString := element.Name[i+1:]
idUint64, err := strconv.ParseUint(idString, 10, 16)
if err != nil {
return nil, nil, fmt.Errorf("Bad ID in server name %q", element.Name)
}
id := uint16(idUint64)
server := models.NordvpnServer{
Region: element.Country,
Number: id,
IP: ip,
TCP: element.Features.TCP,
UDP: element.Features.UDP,
}
servers = append(servers, server)
}
sort.Slice(servers, func(i, j int) bool {
if servers[i].Region == servers[j].Region {
return servers[i].Number < servers[j].Number
}
return servers[i].Region < servers[j].Region
})
return servers, ignoredServers, nil
}

View File

@@ -1,152 +0,0 @@
package main
import (
"archive/zip"
"bytes"
"flag"
"fmt"
"io/ioutil"
"net"
"net/http"
"os"
"path/filepath"
"sort"
"strings"
"time"
"github.com/qdm12/golibs/network"
)
func main() {
os.Exit(_main())
}
// Find subdomains from .ovpn files contained in a .zip file
func _main() int {
provider := flag.String("provider", "surfshark", "VPN provider to parse openvpn files for, can be 'surfshark' or 'vyprvpn")
flag.Parse()
var urls []string
var suffix string
switch *provider {
case "surfshark":
urls = []string{
"https://account.surfshark.com/api/v1/server/configurations",
"https://v2uploads.zopim.io/p/2/L/p2LbwLkvfQoSdzOl6VEltzQA6StiZqrs/12500634259669c77012765139bcfe4f4c90db1e.zip",
}
suffix = ".prod.surfshark.com"
case "vyprvpn":
urls = []string{
"https://support.vyprvpn.com/hc/article_attachments/360052617332/Vypr_OpenVPN_20200320.zip",
}
suffix = ".vyprvpn.com"
default:
fmt.Printf("Provider %q is not supported\n", *provider)
return 1
}
contents, err := fetchAndExtractFiles(urls...)
if err != nil {
fmt.Println(err)
return 1
}
uniqueSubdomainsToFilename := make(map[string]string)
for fileName, content := range contents {
subdomain, err := extractInformation(content, suffix)
if err != nil {
fmt.Println(err)
return 1
} else if len(subdomain) > 0 {
fileName = strings.TrimSuffix(fileName, ".ovpn")
fileName = strings.ReplaceAll(fileName, " - ", " ")
uniqueSubdomainsToFilename[subdomain] = fileName
}
}
type subdomainFilename struct {
subdomain string
fileName string
}
subdomains := make([]subdomainFilename, len(uniqueSubdomainsToFilename))
i := 0
for subdomain, fileName := range uniqueSubdomainsToFilename {
subdomains[i] = subdomainFilename{
subdomain: subdomain,
fileName: fileName,
}
i++
}
sort.Slice(subdomains, func(i, j int) bool {
return subdomains[i].subdomain < subdomains[j].subdomain
})
fmt.Println("Subdomain Filename")
for i := range subdomains {
fmt.Printf("%s %s\n", subdomains[i].subdomain, subdomains[i].fileName)
}
return 0
}
func fetchAndExtractFiles(urls ...string) (contents map[string][]byte, err error) {
client := network.NewClient(10 * time.Second)
contents = make(map[string][]byte)
for _, url := range urls {
zipBytes, status, err := client.GetContent(url)
if err != nil {
return nil, err
} else if status != http.StatusOK {
return nil, fmt.Errorf("Getting %s results in HTTP status code %d", url, status)
}
newContents, err := zipExtractAll(zipBytes)
if err != nil {
return nil, err
}
for fileName, content := range newContents {
contents[fileName] = content
}
}
return contents, nil
}
func zipExtractAll(zipBytes []byte) (contents map[string][]byte, err error) {
r, err := zip.NewReader(bytes.NewReader(zipBytes), int64(len(zipBytes)))
if err != nil {
return nil, err
}
contents = map[string][]byte{}
for _, zf := range r.File {
fileName := filepath.Base(zf.Name)
if !strings.HasSuffix(fileName, ".ovpn") {
continue
}
f, err := zf.Open()
if err != nil {
return nil, err
}
defer f.Close()
contents[fileName], err = ioutil.ReadAll(f)
if err != nil {
return nil, err
}
if err := f.Close(); err != nil {
return nil, err
}
}
return contents, nil
}
func extractInformation(content []byte, suffix string) (subdomain string, err error) {
lines := strings.Split(string(content), "\n")
for _, line := range lines {
if strings.HasPrefix(line, "remote ") {
words := strings.Fields(line)
if len(words) < 2 {
return "", fmt.Errorf("not enough words on line %q", line)
}
host := words[1]
if net.ParseIP(host) != nil {
return "", nil // ignore IP addresses
}
return strings.TrimSuffix(host, suffix), nil
}
}
return "", fmt.Errorf("could not find remote line in: %s", string(content))
}

View File

@@ -1,919 +0,0 @@
package main
import (
"bytes"
"context"
"flag"
"fmt"
"net"
"os"
"sort"
"strings"
)
func main() {
ctx := context.Background()
os.Exit(_main(ctx))
}
func _main(ctx context.Context) int {
resolverAddress := flag.String("resolver", "1.1.1.1", "DNS Resolver IP address to use")
provider := flag.String("provider", "pia", "VPN provider to resolve for, 'pia', 'windscribe', 'cyberghost', 'vyprvpn' or 'purevpn'")
region := flag.String("region", "all", "Comma separated list of VPN provider region names to resolve for, use 'all' to resolve all")
flag.Parse()
resolver := newResolver(*resolverAddress)
lookupIP := newLookupIP(resolver)
var domain string
var servers []server
switch *provider {
case "pia":
domain = "privateinternetaccess.com"
servers = piaServers()
case "windscribe":
domain = "windscribe.com"
servers = windscribeServers()
case "surfshark":
domain = "prod.surfshark.com"
servers = surfsharkServers()
case "cyberghost":
domain = "cg-dialup.net"
servers = cyberghostServers()
case "vyprvpn":
domain = "vyprvpn.com"
servers = vyprvpnServers()
case "purevpn":
domain = "pointtoserver.com"
servers = purevpnServers()
default:
fmt.Printf("Provider %q is not supported\n", *provider)
return 1
}
if *region != "all" {
regions := strings.Split(*region, ",")
uniqueRegions := make(map[string]struct{})
for _, r := range regions {
uniqueRegions[r] = struct{}{}
}
for i := range servers {
if _, ok := uniqueRegions[servers[i].region]; !ok {
servers[i] = servers[len(servers)-1]
servers = servers[:len(servers)-1]
}
}
}
stringChannel := make(chan string)
errorChannel := make(chan error)
const maxGoroutines = 10
guard := make(chan struct{}, maxGoroutines)
for _, s := range servers {
go func(s server) {
guard <- struct{}{}
ips, err := resolveRepeat(ctx, lookupIP, s.subdomain+"."+domain, 3)
<-guard
if err != nil {
errorChannel <- err
return
}
stringChannel <- formatLine(*provider, s, ips)
}(s)
}
var lines []string
var errors []error
for range servers {
select {
case err := <-errorChannel:
errors = append(errors, err)
case s := <-stringChannel:
lines = append(lines, s)
}
}
sort.Slice(lines, func(i, j int) bool {
return lines[i] < lines[j]
})
for _, s := range lines {
fmt.Println(s)
}
if len(errors) > 0 {
fmt.Printf("\n%d errors occurred, described below\n\n", len(errors))
for _, err := range errors {
fmt.Println(err)
}
return 1
}
return 0
}
func formatLine(provider string, s server, ips []net.IP) string {
ipStrings := make([]string, len(ips))
for i := range ips {
ipStrings[i] = fmt.Sprintf("{%s}", strings.ReplaceAll(ips[i].String(), ".", ", "))
}
ipString := strings.Join(ipStrings, ", ")
switch provider {
case "pia":
return fmt.Sprintf(
"{Region: %q, IPs: []net.IP{%s}},",
s.region, ipString,
)
case "windscribe":
return fmt.Sprintf(
"{Region: %q, IPs: []net.IP{%s}},",
s.region, ipString,
)
case "surfshark":
return fmt.Sprintf(
"{Region: %q, IPs: []net.IP{%s}},",
s.region, ipString,
)
case "cyberghost":
return fmt.Sprintf(
"{Region: %q, Group: %q, IPs: []net.IP{%s}},",
s.region, s.group, ipString,
)
case "vyprvpn":
return fmt.Sprintf(
"{Region: %q, IPs: []net.IP{%s}},",
s.region, ipString,
)
case "purevpn":
return fmt.Sprintf(
"{Region: %q, Country: %q, City: %q, IPs: []net.IP{%s}},",
s.region, s.country, s.city, ipString,
)
}
return ""
}
type lookupIPFunc func(ctx context.Context, host string) (ips []net.IP, err error)
func newLookupIP(r *net.Resolver) lookupIPFunc {
return func(ctx context.Context, host string) (ips []net.IP, err error) {
addresses, err := r.LookupIPAddr(ctx, host)
if err != nil {
return nil, err
}
ips = make([]net.IP, len(addresses))
for i := range addresses {
ips[i] = addresses[i].IP
}
return ips, nil
}
}
func newResolver(ip string) *net.Resolver {
return &net.Resolver{
PreferGo: true,
Dial: func(ctx context.Context, network, address string) (net.Conn, error) {
d := net.Dialer{}
return d.DialContext(ctx, "udp", net.JoinHostPort(ip, "53"))
},
}
}
func resolveRepeat(ctx context.Context, lookupIP lookupIPFunc, host string, n int) (ips []net.IP, err error) {
for i := 0; i < n; i++ {
newIPs, err := lookupIP(ctx, host)
if err != nil {
return nil, err
}
ips = append(ips, newIPs...)
}
return uniqueSortedIPs(ips), nil
}
func uniqueSortedIPs(ips []net.IP) []net.IP {
uniqueIPs := make(map[string]struct{})
for _, ip := range ips {
uniqueIPs[ip.String()] = struct{}{}
}
ips = make([]net.IP, len(uniqueIPs))
i := 0
for ip := range uniqueIPs {
ips[i] = net.ParseIP(ip)
i++
}
sort.Slice(ips, func(i, j int) bool {
return bytes.Compare(ips[i], ips[j]) < 0
})
return ips
}
type server struct {
subdomain string
region string
group string // only for cyberghost
country string // only for purevpn
city string // only for purevpn
}
func piaServers() []server {
return []server{
{subdomain: "au-melbourne", region: "AU Melbourne"},
{subdomain: "au-perth", region: "AU Perth"},
{subdomain: "au-sydney", region: "AU Sydney"},
{subdomain: "austria", region: "Austria"},
{subdomain: "belgium", region: "Belgium"},
{subdomain: "ca-montreal", region: "CA Montreal"},
{subdomain: "ca-toronto", region: "CA Toronto"},
{subdomain: "ca-vancouver", region: "CA Vancouver"},
{subdomain: "czech", region: "Czech Republic"},
{subdomain: "de-berlin", region: "DE Berlin"},
{subdomain: "de-frankfurt", region: "DE Frankfurt"},
{subdomain: "denmark", region: "Denmark"},
{subdomain: "fi", region: "Finlan"},
{subdomain: "france", region: "France"},
{subdomain: "hungary", region: "Hungary"},
{subdomain: "in", region: "India"},
{subdomain: "ireland", region: "Ireland"},
{subdomain: "israel", region: "Israel"},
{subdomain: "italy", region: "Italy"},
{subdomain: "japan", region: "Japan"},
{subdomain: "lu", region: "Luxembourg"},
{subdomain: "mexico", region: "Mexico"},
{subdomain: "nl", region: "Netherlands"},
{subdomain: "nz", region: "New Zealand"},
{subdomain: "no", region: "Norway"},
{subdomain: "poland", region: "Poland"},
{subdomain: "ro", region: "Romania"},
{subdomain: "sg", region: "Singapore"},
{subdomain: "spain", region: "Spain"},
{subdomain: "sweden", region: "Sweden"},
{subdomain: "swiss", region: "Switzerland"},
{subdomain: "ae", region: "UAE"},
{subdomain: "uk-london", region: "UK London"},
{subdomain: "uk-manchester", region: "UK Manchester"},
{subdomain: "uk-southampton", region: "UK Southampton"},
{subdomain: "us-atlanta", region: "US Atlanta"},
{subdomain: "us-california", region: "US California"},
{subdomain: "us-chicago", region: "US Chicago"},
{subdomain: "us-dallas", region: "US Dallas"},
{subdomain: "us-denver", region: "US Denver"},
{subdomain: "us-east", region: "US East"},
{subdomain: "us-florida", region: "US Florida"},
{subdomain: "us-houston", region: "US Houston"},
{subdomain: "us-lasvegas", region: "US Las Vegas"},
{subdomain: "us-newyorkcity", region: "US New York City"},
{subdomain: "us-seattle", region: "US Seattle"},
{subdomain: "us-siliconvalley", region: "US Silicon Valley"},
{subdomain: "us-washingtondc", region: "US Washington DC"},
{subdomain: "us-west", region: "US West"},
}
}
func windscribeServers() []server {
return []server{
{subdomain: "al", region: "Albania"},
{subdomain: "ar", region: "Argentina"},
{subdomain: "au", region: "Australia"},
{subdomain: "at", region: "Austria"},
{subdomain: "az", region: "Azerbaijan"},
{subdomain: "be", region: "Belgium"},
{subdomain: "ba", region: "Bosnia"},
{subdomain: "br", region: "Brazil"},
{subdomain: "bg", region: "Bulgaria"},
{subdomain: "ca", region: "Canada East"},
{subdomain: "ca-west", region: "Canada West"},
{subdomain: "co", region: "Colombia"},
{subdomain: "hr", region: "Croatia"},
{subdomain: "cy", region: "Cyprus"},
{subdomain: "cz", region: "Czech republic"},
{subdomain: "dk", region: "Denmark"},
{subdomain: "ee", region: "Estonia"},
{subdomain: "aq", region: "Fake antarctica"},
{subdomain: "fi", region: "Finland"},
{subdomain: "fr", region: "France"},
{subdomain: "ge", region: "Georgia"},
{subdomain: "de", region: "Germany"},
{subdomain: "gr", region: "Greece"},
{subdomain: "hk", region: "Hong kong"},
{subdomain: "hu", region: "Hungary"},
{subdomain: "is", region: "Iceland"},
{subdomain: "in", region: "India"},
{subdomain: "id", region: "Indonesia"},
{subdomain: "ie", region: "Ireland"},
{subdomain: "il", region: "Israel"},
{subdomain: "it", region: "Italy"},
{subdomain: "jp", region: "Japan"},
{subdomain: "lv", region: "Latvia"},
{subdomain: "lt", region: "Lithuania"},
{subdomain: "mk", region: "Macedonia"},
{subdomain: "my", region: "Malaysia"},
{subdomain: "mx", region: "Mexico"},
{subdomain: "md", region: "Moldova"},
{subdomain: "nl", region: "Netherlands"},
{subdomain: "nz", region: "New zealand"},
{subdomain: "no", region: "Norway"},
{subdomain: "ph", region: "Philippines"},
{subdomain: "pl", region: "Poland"},
{subdomain: "pt", region: "Portugal"},
{subdomain: "ro", region: "Romania"},
{subdomain: "ru", region: "Russia"},
{subdomain: "rs", region: "Serbia"},
{subdomain: "sg", region: "Singapore"},
{subdomain: "sk", region: "Slovakia"},
{subdomain: "si", region: "Slovenia"},
{subdomain: "za", region: "South Africa"},
{subdomain: "kr", region: "South Korea"},
{subdomain: "es", region: "Spain"},
{subdomain: "se", region: "Sweden"},
{subdomain: "ch", region: "Switzerland"},
{subdomain: "th", region: "Thailand"},
{subdomain: "tn", region: "Tunisia"},
{subdomain: "tr", region: "Turkey"},
{subdomain: "ua", region: "Ukraine"},
{subdomain: "ae", region: "United Arab Emirates"},
{subdomain: "uk", region: "United Kingdom"},
{subdomain: "us-central", region: "US Central"},
{subdomain: "us-east", region: "US East"},
{subdomain: "us-west", region: "US West"},
{subdomain: "vn", region: "Vietnam"},
{subdomain: "wf-ca", region: "Windflix CA"},
{subdomain: "wf-jp", region: "Windflix JP"},
{subdomain: "wf-uk", region: "Windflix UK"},
{subdomain: "wf-us", region: "Windflix US"},
}
}
func surfsharkServers() []server {
return []server{
{subdomain: "ae-dub", region: "United Arab Emirates"},
{subdomain: "al-tia", region: "Albania"},
{subdomain: "at-vie", region: "Austria"},
{subdomain: "au-adl", region: "Australia Adelaide"},
{subdomain: "au-bne", region: "Australia Brisbane"},
{subdomain: "au-mel", region: "Australia Melbourne"},
{subdomain: "au-per", region: "Australia Perth"},
{subdomain: "au-syd", region: "Australia Sydney"},
{subdomain: "au-us", region: "Australia US"},
{subdomain: "az-bak", region: "Azerbaijan"},
{subdomain: "ba-sjj", region: "Bosnia and Herzegovina"},
{subdomain: "be-bru", region: "Belgium"},
{subdomain: "bg-sof", region: "Bulgaria"},
{subdomain: "br-sao", region: "Brazil"},
{subdomain: "ca-mon", region: "Canada Montreal"},
{subdomain: "ca-tor", region: "Canada Toronto"},
{subdomain: "ca-us", region: "Canada US"},
{subdomain: "ca-van", region: "Canada Vancouver"},
{subdomain: "ch-zur", region: "Switzerland"},
{subdomain: "cl-san", region: "Chile"},
{subdomain: "co-bog", region: "Colombia"},
{subdomain: "cr-sjn", region: "Costa Rica"},
{subdomain: "cy-nic", region: "Cyprus"},
{subdomain: "cz-prg", region: "Czech Republic"},
{subdomain: "de-ber", region: "Germany Berlin"},
{subdomain: "de-fra", region: "Germany Frankfurt am Main"},
{subdomain: "de-fra-st001", region: "Germany Frankfurt am Main st001"},
{subdomain: "de-fra-st002", region: "Germany Frankfurt am Main st002"},
{subdomain: "de-fra-st003", region: "Germany Frankfurt am Main st003"},
{subdomain: "de-muc", region: "Germany Munich"},
{subdomain: "de-nue", region: "Germany Nuremberg"},
{subdomain: "de-sg", region: "Germany Singapour"},
{subdomain: "de-uk", region: "Germany UK"},
{subdomain: "dk-cph", region: "Denmark"},
{subdomain: "ee-tll", region: "Estonia"},
{subdomain: "es-bcn", region: "Spain Barcelona"},
{subdomain: "es-mad", region: "Spain Madrid"},
{subdomain: "es-vlc", region: "Spain Valencia"},
{subdomain: "fi-hel", region: "Finland"},
{subdomain: "fr-bod", region: "France Bordeaux"},
{subdomain: "fr-mrs", region: "France Marseilles"},
{subdomain: "fr-par", region: "France Paris"},
{subdomain: "fr-se", region: "France Sweden"},
{subdomain: "gr-ath", region: "Greece"},
{subdomain: "hk-hkg", region: "Hong Kong"},
{subdomain: "hr-zag", region: "Croatia"},
{subdomain: "hu-bud", region: "Hungary"},
{subdomain: "id-jak", region: "Indonesia"},
{subdomain: "ie-dub", region: "Ireland"},
{subdomain: "il-tlv", region: "Israel"},
{subdomain: "in-chn", region: "India Chennai"},
{subdomain: "in-idr", region: "India Indore"},
{subdomain: "in-mum", region: "India Mumbai"},
{subdomain: "in-uk", region: "India UK"},
{subdomain: "is-rkv", region: "Iceland"},
{subdomain: "it-mil", region: "Italy Milan"},
{subdomain: "it-rom", region: "Italy Rome"},
{subdomain: "jp-tok", region: "Japan Tokyo"},
{subdomain: "jp-tok-st001", region: "Japan Tokyo st001"},
{subdomain: "jp-tok-st002", region: "Japan Tokyo st002"},
{subdomain: "jp-tok-st003", region: "Japan Tokyo st003"},
{subdomain: "jp-tok-st004", region: "Japan Tokyo st004"},
{subdomain: "jp-tok-st005", region: "Japan Tokyo st005"},
{subdomain: "jp-tok-st006", region: "Japan Tokyo st006"},
{subdomain: "jp-tok-st007", region: "Japan Tokyo st007"},
{subdomain: "kr-seo", region: "Korea"},
{subdomain: "kz-ura", region: "Kazakhstan"},
{subdomain: "lu-ste", region: "Luxembourg"},
{subdomain: "lv-rig", region: "Latvia"},
{subdomain: "ly-tip", region: "Libya"},
{subdomain: "md-chi", region: "Moldova"},
{subdomain: "mk-skp", region: "North Macedonia"},
{subdomain: "my-kul", region: "Malaysia"},
{subdomain: "ng-lag", region: "Nigeria"},
{subdomain: "nl-ams", region: "Netherlands Amsterdam"},
{subdomain: "nl-ams-st001", region: "Netherlands Amsterdam st001"},
{subdomain: "nl-us", region: "Netherlands US"},
{subdomain: "no-osl", region: "Norway"},
{subdomain: "nz-akl", region: "New Zealand"},
{subdomain: "ph-mnl", region: "Philippines"},
{subdomain: "pl-gdn", region: "Poland Gdansk"},
{subdomain: "pl-waw", region: "Poland Warsaw"},
{subdomain: "pt-lis", region: "Portugal Lisbon"},
{subdomain: "pt-lou", region: "Portugal Loule"},
{subdomain: "pt-opo", region: "Portugal Porto"},
{subdomain: "py-asu", region: "Paraguay"},
{subdomain: "ro-buc", region: "Romania"},
{subdomain: "rs-beg", region: "Serbia"},
{subdomain: "ru-mos", region: "Russia Moscow"},
{subdomain: "ru-spt", region: "Russia St. Petersburg"},
{subdomain: "se-sto", region: "Sweden"},
{subdomain: "sg-hk", region: "Singapore Hong Kong"},
{subdomain: "sg-nl", region: "Singapore Netherlands"},
{subdomain: "sg-sng", region: "Singapore"},
{subdomain: "sg-sng-st001", region: "Singapore st001"},
{subdomain: "sg-sng-st002", region: "Singapore st002"},
{subdomain: "sg-sng-st003", region: "Singapore st003"},
{subdomain: "sg-sng-st004", region: "Singapore st004"},
{subdomain: "si-lju", region: "Slovenia"},
{subdomain: "sk-bts", region: "Slovekia"},
{subdomain: "th-bkk", region: "Thailand"},
{subdomain: "tr-bur", region: "Turkey"},
{subdomain: "tw-tai", region: "Taiwan"},
{subdomain: "ua-iev", region: "Ukraine"},
{subdomain: "uk-de", region: "UK Germany"},
{subdomain: "uk-fr", region: "UK France"},
{subdomain: "uk-gla", region: "UK Glasgow"},
{subdomain: "uk-lon", region: "UK London"},
{subdomain: "uk-lon-st001", region: "UK London st001"},
{subdomain: "uk-lon-st002", region: "UK London st002"},
{subdomain: "uk-lon-st003", region: "UK London st003"},
{subdomain: "uk-lon-st004", region: "UK London st004"},
{subdomain: "uk-lon-st005", region: "UK London st005"},
{subdomain: "uk-man", region: "UK Manchester"},
{subdomain: "us-atl", region: "US Atlanta"},
{subdomain: "us-bdn", region: "US Bend"},
{subdomain: "us-bos", region: "US Boston"},
{subdomain: "us-buf", region: "US Buffalo"},
{subdomain: "us-chi", region: "US Chicago"},
{subdomain: "us-clt", region: "US Charlotte"},
{subdomain: "us-dal", region: "US Dallas"},
{subdomain: "us-den", region: "US Denver"},
{subdomain: "us-dtw", region: "US Gahanna"},
{subdomain: "us-hou", region: "US Houston"},
{subdomain: "us-kan", region: "US Kansas City"},
{subdomain: "us-las", region: "US Las Vegas"},
{subdomain: "us-lax", region: "US Los Angeles"},
{subdomain: "us-ltm", region: "US Latham"},
{subdomain: "us-mia", region: "US Miami"},
{subdomain: "us-mnz", region: "US Maryland"},
{subdomain: "us-nl", region: "US Netherlands"},
{subdomain: "us-nyc", region: "US New York City"},
{subdomain: "us-nyc-mp001", region: "US New York City mp001"},
{subdomain: "us-nyc-st001", region: "US New York City st001"},
{subdomain: "us-nyc-st002", region: "US New York City st002"},
{subdomain: "us-nyc-st003", region: "US New York City st003"},
{subdomain: "us-nyc-st004", region: "US New York City st004"},
{subdomain: "us-nyc-st005", region: "US New York City st005"},
{subdomain: "us-orl", region: "US Orlando"},
{subdomain: "us-phx", region: "US Phoenix"},
{subdomain: "us-pt", region: "US Portugal"},
{subdomain: "us-sea", region: "US Seatle"},
{subdomain: "us-sfo", region: "US San Francisco"},
{subdomain: "us-slc", region: "US Salt Lake City"},
{subdomain: "us-stl", region: "US Saint Louis"},
{subdomain: "us-tpa", region: "US Tampa"},
{subdomain: "vn-hcm", region: "Vietnam"},
{subdomain: "za-jnb", region: "South Africa"},
}
}
func cyberghostServers() []server {
return []server{
{subdomain: "97-1-al", region: "Albania", group: "Premium TCP Europe"},
{subdomain: "87-1-al", region: "Albania", group: "Premium UDP Europe"},
{subdomain: "87-1-dz", region: "Algeria", group: "Premium UDP Europe"},
{subdomain: "97-1-dz", region: "Algeria", group: "Premium TCP Europe"},
{subdomain: "97-1-ad", region: "Andorra", group: "Premium TCP Europe"},
{subdomain: "87-1-ad", region: "Andorra", group: "Premium UDP Europe"},
{subdomain: "94-1-ar", region: "Argentina", group: "Premium UDP USA"},
{subdomain: "93-1-ar", region: "Argentina", group: "Premium TCP USA"},
{subdomain: "87-1-am", region: "Armenia", group: "Premium UDP Europe"},
{subdomain: "97-1-am", region: "Armenia", group: "Premium TCP Europe"},
{subdomain: "95-1-au", region: "Australia", group: "Premium UDP Asia"},
{subdomain: "96-1-au", region: "Australia", group: "Premium TCP Asia"},
{subdomain: "97-1-at", region: "Austria", group: "Premium TCP Europe"},
{subdomain: "87-1-at", region: "Austria", group: "Premium UDP Europe"},
{subdomain: "93-1-bs", region: "Bahamas", group: "Premium TCP USA"},
{subdomain: "94-1-bs", region: "Bahamas", group: "Premium UDP USA"},
{subdomain: "95-1-bd", region: "Bangladesh", group: "Premium UDP Asia"},
{subdomain: "96-1-bd", region: "Bangladesh", group: "Premium TCP Asia"},
{subdomain: "97-1-by", region: "Belarus", group: "Premium TCP Europe"},
{subdomain: "87-1-by", region: "Belarus", group: "Premium UDP Europe"},
{subdomain: "97-1-be", region: "Belgium", group: "Premium TCP Europe"},
{subdomain: "87-1-be", region: "Belgium", group: "Premium UDP Europe"},
{subdomain: "87-1-ba", region: "Bosnia and Herzegovina", group: "Premium UDP Europe"},
{subdomain: "97-1-ba", region: "Bosnia and Herzegovina", group: "Premium TCP Europe"},
{subdomain: "94-1-br", region: "Brazil", group: "Premium UDP USA"},
{subdomain: "93-1-br", region: "Brazil", group: "Premium TCP USA"},
{subdomain: "87-1-bg", region: "Bulgaria", group: "Premium UDP Europe"},
{subdomain: "97-1-bg", region: "Bulgaria", group: "Premium TCP Europe"},
{subdomain: "96-1-kh", region: "Cambodia", group: "Premium TCP Asia"},
{subdomain: "95-1-kh", region: "Cambodia", group: "Premium UDP Asia"},
{subdomain: "93-1-ca", region: "Canada", group: "Premium TCP USA"},
{subdomain: "94-1-ca", region: "Canada", group: "Premium UDP USA"},
{subdomain: "93-1-cl", region: "Chile", group: "Premium TCP USA"},
{subdomain: "94-1-cl", region: "Chile", group: "Premium UDP USA"},
{subdomain: "96-1-cn", region: "China", group: "Premium TCP Asia"},
{subdomain: "95-1-cn", region: "China", group: "Premium UDP Asia"},
{subdomain: "94-1-co", region: "Colombia", group: "Premium UDP USA"},
{subdomain: "93-1-co", region: "Colombia", group: "Premium TCP USA"},
{subdomain: "93-1-cr", region: "Costa Rica", group: "Premium TCP USA"},
{subdomain: "94-1-cr", region: "Costa Rica", group: "Premium UDP USA"},
{subdomain: "87-1-cy", region: "Cyprus", group: "Premium UDP Europe"},
{subdomain: "97-1-cy", region: "Cyprus", group: "Premium TCP Europe"},
{subdomain: "97-1-cz", region: "Czech Republic", group: "Premium TCP Europe"},
{subdomain: "87-1-cz", region: "Czech Republic", group: "Premium UDP Europe"},
{subdomain: "87-1-dk", region: "Denmark", group: "Premium UDP Europe"},
{subdomain: "97-1-dk", region: "Denmark", group: "Premium TCP Europe"},
{subdomain: "87-1-eg", region: "Egypt", group: "Premium UDP Europe"},
{subdomain: "97-1-eg", region: "Egypt", group: "Premium TCP Europe"},
{subdomain: "87-1-ee", region: "Estonia", group: "Premium UDP Europe"},
{subdomain: "97-1-ee", region: "Estonia", group: "Premium TCP Europe"},
{subdomain: "97-1-fi", region: "Finland", group: "Premium TCP Europe"},
{subdomain: "87-1-fi", region: "Finland", group: "Premium UDP Europe"},
{subdomain: "87-1-fr", region: "France", group: "Premium UDP Europe"},
{subdomain: "97-1-fr", region: "France", group: "Premium TCP Europe"},
{subdomain: "87-1-ge", region: "Georgia", group: "Premium UDP Europe"},
{subdomain: "97-1-ge", region: "Georgia", group: "Premium TCP Europe"},
{subdomain: "97-1-de", region: "Germany", group: "Premium TCP Europe"},
{subdomain: "87-1-de", region: "Germany", group: "Premium UDP Europe"},
{subdomain: "87-1-gr", region: "Greece", group: "Premium UDP Europe"},
{subdomain: "97-1-gr", region: "Greece", group: "Premium TCP Europe"},
{subdomain: "97-1-gl", region: "Greenland", group: "Premium TCP Europe"},
{subdomain: "87-1-gl", region: "Greenland", group: "Premium UDP Europe"},
{subdomain: "96-1-hk", region: "Hong Kong", group: "Premium TCP Asia"},
{subdomain: "95-1-hk", region: "Hong Kong", group: "Premium UDP Asia"},
{subdomain: "87-1-hu", region: "Hungary", group: "Premium UDP Europe"},
{subdomain: "97-1-hu", region: "Hungary", group: "Premium TCP Europe"},
{subdomain: "97-1-is", region: "Iceland", group: "Premium TCP Europe"},
{subdomain: "87-1-is", region: "Iceland", group: "Premium UDP Europe"},
{subdomain: "87-1-in", region: "India", group: "Premium UDP Europe"},
{subdomain: "97-1-in", region: "India", group: "Premium TCP Europe"},
{subdomain: "95-1-id", region: "Indonesia", group: "Premium UDP Asia"},
{subdomain: "96-1-id", region: "Indonesia", group: "Premium TCP Asia"},
{subdomain: "87-1-ir", region: "Iran", group: "Premium UDP Europe"},
{subdomain: "97-1-ir", region: "Iran", group: "Premium TCP Europe"},
{subdomain: "87-1-ie", region: "Ireland", group: "Premium UDP Europe"},
{subdomain: "97-1-ie", region: "Ireland", group: "Premium TCP Europe"},
{subdomain: "87-1-im", region: "Isle of Man", group: "Premium UDP Europe"},
{subdomain: "97-1-im", region: "Isle of Man", group: "Premium TCP Europe"},
{subdomain: "87-1-il", region: "Israel", group: "Premium UDP Europe"},
{subdomain: "97-1-il", region: "Israel", group: "Premium TCP Europe"},
{subdomain: "97-1-it", region: "Italy", group: "Premium TCP Europe"},
{subdomain: "87-1-it", region: "Italy", group: "Premium UDP Europe"},
{subdomain: "95-1-jp", region: "Japan", group: "Premium UDP Asia"},
{subdomain: "96-1-jp", region: "Japan", group: "Premium TCP Asia"},
{subdomain: "97-1-kz", region: "Kazakhstan", group: "Premium TCP Europe"},
{subdomain: "87-1-kz", region: "Kazakhstan", group: "Premium UDP Europe"},
{subdomain: "95-1-ke", region: "Kenya", group: "Premium UDP Asia"},
{subdomain: "96-1-ke", region: "Kenya", group: "Premium TCP Asia"},
{subdomain: "95-1-kr", region: "Korea", group: "Premium UDP Asia"},
{subdomain: "96-1-kr", region: "Korea", group: "Premium TCP Asia"},
{subdomain: "97-1-lv", region: "Latvia", group: "Premium TCP Europe"},
{subdomain: "87-1-lv", region: "Latvia", group: "Premium UDP Europe"},
{subdomain: "97-1-li", region: "Liechtenstein", group: "Premium TCP Europe"},
{subdomain: "87-1-li", region: "Liechtenstein", group: "Premium UDP Europe"},
{subdomain: "97-1-lt", region: "Lithuania", group: "Premium TCP Europe"},
{subdomain: "87-1-lt", region: "Lithuania", group: "Premium UDP Europe"},
{subdomain: "87-1-lu", region: "Luxembourg", group: "Premium UDP Europe"},
{subdomain: "97-1-lu", region: "Luxembourg", group: "Premium TCP Europe"},
{subdomain: "96-1-mo", region: "Macao", group: "Premium TCP Asia"},
{subdomain: "95-1-mo", region: "Macao", group: "Premium UDP Asia"},
{subdomain: "97-1-mk", region: "Macedonia", group: "Premium TCP Europe"},
{subdomain: "87-1-mk", region: "Macedonia", group: "Premium UDP Europe"},
{subdomain: "95-1-my", region: "Malaysia", group: "Premium UDP Asia"},
{subdomain: "96-1-my", region: "Malaysia", group: "Premium TCP Asia"},
{subdomain: "87-1-mt", region: "Malta", group: "Premium UDP Europe"},
{subdomain: "97-1-mt", region: "Malta", group: "Premium TCP Europe"},
{subdomain: "93-1-mx", region: "Mexico", group: "Premium TCP USA"},
{subdomain: "94-1-mx", region: "Mexico", group: "Premium UDP USA"},
{subdomain: "87-1-md", region: "Moldova", group: "Premium UDP Europe"},
{subdomain: "97-1-md", region: "Moldova", group: "Premium TCP Europe"},
{subdomain: "87-1-mc", region: "Monaco", group: "Premium UDP Europe"},
{subdomain: "97-1-mc", region: "Monaco", group: "Premium TCP Europe"},
{subdomain: "96-1-mn", region: "Mongolia", group: "Premium TCP Asia"},
{subdomain: "95-1-mn", region: "Mongolia", group: "Premium UDP Asia"},
{subdomain: "87-1-me", region: "Montenegro", group: "Premium UDP Europe"},
{subdomain: "97-1-me", region: "Montenegro", group: "Premium TCP Europe"},
{subdomain: "97-1-ma", region: "Morocco", group: "Premium TCP Europe"},
{subdomain: "87-1-ma", region: "Morocco", group: "Premium UDP Europe"},
{subdomain: "97-1-nl", region: "Netherlands", group: "Premium TCP Europe"},
{subdomain: "87-1-nl", region: "Netherlands", group: "Premium UDP Europe"},
{subdomain: "95-1-nz", region: "New Zealand", group: "Premium UDP Asia"},
{subdomain: "96-1-nz", region: "New Zealand", group: "Premium TCP Asia"},
{subdomain: "87-1-ng", region: "Nigeria", group: "Premium UDP Europe"},
{subdomain: "97-1-ng", region: "Nigeria", group: "Premium TCP Europe"},
{subdomain: "97-1-no", region: "Norway", group: "Premium TCP Europe"},
{subdomain: "87-1-no", region: "Norway", group: "Premium UDP Europe"},
{subdomain: "97-1-pk", region: "Pakistan", group: "Premium TCP Europe"},
{subdomain: "87-1-pk", region: "Pakistan", group: "Premium UDP Europe"},
{subdomain: "97-1-pa", region: "Panama", group: "Premium TCP Europe"},
{subdomain: "87-1-pa", region: "Panama", group: "Premium UDP Europe"},
{subdomain: "95-1-ph", region: "Philippines", group: "Premium UDP Asia"},
{subdomain: "96-1-ph", region: "Philippines", group: "Premium TCP Asia"},
{subdomain: "97-1-pl", region: "Poland", group: "Premium TCP Europe"},
{subdomain: "87-1-pl", region: "Poland", group: "Premium UDP Europe"},
{subdomain: "97-1-pt", region: "Portugal", group: "Premium TCP Europe"},
{subdomain: "87-1-pt", region: "Portugal", group: "Premium UDP Europe"},
{subdomain: "97-1-qa", region: "Qatar", group: "Premium TCP Europe"},
{subdomain: "87-1-qa", region: "Qatar", group: "Premium UDP Europe"},
{subdomain: "87-1-ro", region: "Romania", group: "Premium UDP Europe"},
{subdomain: "87-8-ro", region: "Romania", group: "NoSpy UDP Europe"},
{subdomain: "97-8-ro", region: "Romania", group: "NoSpy TCP Europe"},
{subdomain: "97-1-ro", region: "Romania", group: "Premium TCP Europe"},
{subdomain: "87-1-ru", region: "Russian Federation", group: "Premium UDP Europe"},
{subdomain: "97-1-ru", region: "Russian Federation", group: "Premium TCP Europe"},
{subdomain: "97-1-sa", region: "Saudi Arabia", group: "Premium TCP Europe"},
{subdomain: "87-1-sa", region: "Saudi Arabia", group: "Premium UDP Europe"},
{subdomain: "97-1-rs", region: "Serbia", group: "Premium TCP Europe"},
{subdomain: "87-1-rs", region: "Serbia", group: "Premium UDP Europe"},
{subdomain: "95-1-sg", region: "Singapore", group: "Premium UDP Asia"},
{subdomain: "96-1-sg", region: "Singapore", group: "Premium TCP Asia"},
{subdomain: "87-1-sk", region: "Slovakia", group: "Premium UDP Europe"},
{subdomain: "97-1-sk", region: "Slovakia", group: "Premium TCP Europe"},
{subdomain: "87-1-si", region: "Slovenia", group: "Premium UDP Europe"},
{subdomain: "97-1-si", region: "Slovenia", group: "Premium TCP Europe"},
{subdomain: "87-1-za", region: "South Africa", group: "Premium UDP Europe"},
{subdomain: "95-1-za", region: "South Africa", group: "Premium UDP Asia"},
{subdomain: "97-1-za", region: "South Africa", group: "Premium TCP Europe"},
{subdomain: "96-1-za", region: "South Africa", group: "Premium TCP Asia"},
{subdomain: "97-1-es", region: "Spain", group: "Premium TCP Europe"},
{subdomain: "87-1-es", region: "Spain", group: "Premium UDP Europe"},
{subdomain: "97-1-lk", region: "Sri Lanka", group: "Premium TCP Europe"},
{subdomain: "87-1-lk", region: "Sri Lanka", group: "Premium UDP Europe"},
{subdomain: "97-1-se", region: "Sweden", group: "Premium TCP Europe"},
{subdomain: "87-1-se", region: "Sweden", group: "Premium UDP Europe"},
{subdomain: "87-1-ch", region: "Switzerland", group: "Premium UDP Europe"},
{subdomain: "97-1-ch", region: "Switzerland", group: "Premium TCP Europe"},
{subdomain: "96-1-tw", region: "Taiwan", group: "Premium TCP Asia"},
{subdomain: "95-1-tw", region: "Taiwan", group: "Premium UDP Asia"},
{subdomain: "96-1-th", region: "Thailand", group: "Premium TCP Asia"},
{subdomain: "95-1-th", region: "Thailand", group: "Premium UDP Asia"},
{subdomain: "87-1-tr", region: "Turkey", group: "Premium UDP Europe"},
{subdomain: "97-1-tr", region: "Turkey", group: "Premium TCP Europe"},
{subdomain: "97-1-ua", region: "Ukraine", group: "Premium TCP Europe"},
{subdomain: "87-1-ua", region: "Ukraine", group: "Premium UDP Europe"},
{subdomain: "87-1-ae", region: "United Arab Emirates", group: "Premium UDP Europe"},
{subdomain: "97-1-ae", region: "United Arab Emirates", group: "Premium TCP Europe"},
{subdomain: "97-1-gb", region: "United Kingdom", group: "Premium TCP Europe"},
{subdomain: "87-1-gb", region: "United Kingdom", group: "Premium UDP Europe"},
{subdomain: "94-1-us", region: "United States", group: "Premium UDP USA"},
{subdomain: "93-1-us", region: "United States", group: "Premium TCP USA"},
{subdomain: "87-1-ve", region: "Venezuela", group: "Premium UDP Europe"},
{subdomain: "97-1-ve", region: "Venezuela", group: "Premium TCP Europe"},
{subdomain: "95-1-vn", region: "Vietnam", group: "Premium UDP Asia"},
{subdomain: "96-1-vn", region: "Vietnam", group: "Premium TCP Asia"},
}
}
func vyprvpnServers() []server {
return []server{
{subdomain: "ae1", region: "Dubai"},
{subdomain: "ar1", region: "Argentina"},
{subdomain: "at1", region: "Austria"},
{subdomain: "au1", region: "Australia Sydney"},
{subdomain: "au2", region: "Australia Melbourne"},
{subdomain: "au3", region: "Australia Perth"},
{subdomain: "be1", region: "Belgium"},
{subdomain: "bg1", region: "Bulgaria"},
{subdomain: "bh1", region: "Bahrain"},
{subdomain: "br1", region: "Brazil"},
{subdomain: "ca1", region: "Canada"},
{subdomain: "ch1", region: "Switzerland"},
{subdomain: "co1", region: "Columbia"},
{subdomain: "cr1", region: "Costa Rica"},
{subdomain: "cz1", region: "Czech Republic"},
{subdomain: "de1", region: "Germany"},
{subdomain: "dk1", region: "Denmark"},
{subdomain: "dz1", region: "Algeria"},
{subdomain: "eg1", region: "Egypt"},
{subdomain: "es1", region: "Spain"},
{subdomain: "eu1", region: "Netherlands"},
{subdomain: "fi1", region: "Finland"},
{subdomain: "fr1", region: "France"},
{subdomain: "gr1", region: "Greece"},
{subdomain: "hk1", region: "Hong Kong"},
{subdomain: "id1", region: "Indonesia"},
{subdomain: "ie1", region: "Ireland"},
{subdomain: "il1", region: "Israel"},
{subdomain: "in1", region: "India"},
{subdomain: "is1", region: "Iceland"},
{subdomain: "it1", region: "Italy"},
{subdomain: "jp1", region: "Japan"},
{subdomain: "kr1", region: "South Korea"},
{subdomain: "li1", region: "Liechtenstein"},
{subdomain: "lt1", region: "Lithuania"},
{subdomain: "lu1", region: "Luxembourg"},
{subdomain: "lv1", region: "Latvia"},
{subdomain: "mh1", region: "Marshall Islands"},
{subdomain: "mo1", region: "Macao"},
{subdomain: "mv1", region: "Maldives"},
{subdomain: "mx1", region: "Mexico"},
{subdomain: "my1", region: "Malaysia"},
{subdomain: "no1", region: "Norway"},
{subdomain: "nz1", region: "New Zealand"},
{subdomain: "pa1", region: "Panama"},
{subdomain: "ph1", region: "Philippines"},
{subdomain: "pk1", region: "Pakistan"},
{subdomain: "pl1", region: "Poland"},
{subdomain: "pt1", region: "Portugal"},
{subdomain: "qa1", region: "Qatar"},
{subdomain: "ro1", region: "Romania"},
{subdomain: "ru1", region: "Russia"},
{subdomain: "sa1", region: "Saudi Arabia"},
{subdomain: "se1", region: "Sweden"},
{subdomain: "sg1", region: "Singapore"},
{subdomain: "si1", region: "Slovenia"},
{subdomain: "sk1", region: "Slovakia"},
{subdomain: "sv1", region: "El Salvador"},
{subdomain: "th1", region: "Thailand"},
{subdomain: "tr1", region: "Turkey"},
{subdomain: "tw1", region: "Taiwan"},
{subdomain: "ua1", region: "Ukraine"},
{subdomain: "uk1", region: "United Kingdom"},
{subdomain: "us1", region: "USA Los Angeles"},
{subdomain: "us2", region: "USA Washington DC"},
{subdomain: "us3", region: "USA Austin"},
{subdomain: "us4", region: "USA Miami"},
{subdomain: "us5", region: "USA New York"},
{subdomain: "us6", region: "USA Chicago"},
{subdomain: "us7", region: "USA San Francisco"},
{subdomain: "us8", region: "USA Seattle"},
{subdomain: "uy1", region: "Uruguay"},
{subdomain: "vn1", region: "Vietnam"},
}
}
func purevpnServers() []server {
servers := []server{
{subdomain: "vlus-dz1-ovpn", region: "Africa", country: "Algeria", city: "Algiers"},
{subdomain: "vlus-ao1-ovpn", region: "Africa", country: "Angola", city: "Benguela"},
{subdomain: "vleu-cv-ovpn", region: "Africa", country: "Cape Verde", city: "Praia"},
{subdomain: "vlus-eg1-ovpn", region: "Africa", country: "Egypt", city: "Cairo"},
{subdomain: "et1-ovpn", region: "Africa", country: "Ethiopia", city: "Addis Ababa"},
{subdomain: "gh1-ovpn", region: "Africa", country: "Ghana", city: "Accra"},
{subdomain: "ke1-ovpn", region: "Africa", country: "Kenya", city: "Mombasa"},
{subdomain: "vlus-mg1-ovpn", region: "Africa", country: "Madagascar", city: "Antananarivo"},
{subdomain: "vlus-mr1-ovpn", region: "Africa", country: "Mauritania", city: "Nouakchott"},
{subdomain: "mu1-ovpn", region: "Africa", country: "Mauritius", city: "Port Louis"},
{subdomain: "ma1-ovpn", region: "Africa", country: "Morocco", city: "Rabat"},
{subdomain: "vlus-ne1-ovpn", region: "Africa", country: "Niger", city: "Niamey"},
{subdomain: "ng1-ovpn", region: "Africa", country: "Nigeria", city: "Suleja"},
{subdomain: "vlus-sn1-ovpn", region: "Africa", country: "Senegal", city: "Dakar"},
{subdomain: "sc1-ovpn", region: "Africa", country: "Seychelles", city: "Victoria"},
{subdomain: "za2-ovpn", region: "Africa", country: "South Africa", city: "Johannesburg"},
{subdomain: "vlus-tz1-ovpn", region: "Africa", country: "Tanzania", city: "Dar Es Salaam"},
{subdomain: "vlus-tn1-ovpn", region: "Africa", country: "Tunisia", city: "Tunis"},
{subdomain: "vlus-af1-ovpn", region: "Asia", country: "Afghanistan", city: "Kabul"},
{subdomain: "sg2-ovpn", region: "Asia", country: "Armenia", city: "Singapore"},
{subdomain: "az1-ovpn", region: "Asia", country: "Azerbaijan", city: "Baku"},
{subdomain: "vlus-bd1-ovpn", region: "Asia", country: "Bangladesh", city: "Dhaka"},
{subdomain: "bn2-ovpn", region: "Asia", country: "Brunei Darussalam", city: "Bandar Seri Begawan"},
{subdomain: "kh1-ovpn", region: "Asia", country: "Cambodia", city: "Phnom Penh"},
{subdomain: "hk2-ovpn", region: "Asia", country: "Hong Kong (SAR)", city: "Hong Kong"},
{subdomain: "in2-ovpn", region: "Asia", country: "India", city: "Chennai"},
{subdomain: "idn1-ovpn", region: "Asia", country: "Indonesia", city: "Jakarta"},
{subdomain: "jp-tk1-ovpn", region: "Asia", country: "Japan", city: "Tokyo"},
{subdomain: "vlus-kz1-ovpn", region: "Asia", country: "Kazakhstan", city: "Almaty"},
{subdomain: "kr2-ovpn", region: "Asia", country: "Korea, South", city: "Seoul"},
{subdomain: "vlus-kg1-ovpn", region: "Asia", country: "Kyrgyzstan", city: "Bishkek"},
{subdomain: "vlus-la1-ovpn", region: "Asia", country: "Laos", city: "Vientiane"},
{subdomain: "mo1-ovpn", region: "Asia", country: "Macao", city: "Beyrouth"},
{subdomain: "my2-ovpn", region: "Asia", country: "Malaysia", city: "Johor Baharu"},
{subdomain: "my-kl2-ovpn", region: "Asia", country: "Malaysia", city: "Kuala Lumpur"},
{subdomain: "vlus-mn1-ovpn", region: "Asia", country: "Mongolia", city: "Ulaanbaatar"},
{subdomain: "pk1-ovpn", region: "Asia", country: "Pakistan", city: "Islamabad"},
{subdomain: "vlus-pg1-ovpn", region: "Asia", country: "Papua New Guinea", city: "Port Moresby"},
{subdomain: "vlap-ph2-ovpn", region: "Asia", country: "Philippines", city: "Manila"},
{subdomain: "vlus-lk1-ovpn", region: "Asia", country: "Sri Lanka", city: "Colombo"},
{subdomain: "tw2-ovpn", region: "Asia", country: "Taiwan", city: "Taipei"},
{subdomain: "vlus-tj-ovpn", region: "Asia", country: "Tajikistan", city: "Dushanbe"},
{subdomain: "vlap-th2-ovpn", region: "Asia", country: "Thailand", city: "Bangkok"},
{subdomain: "tr2-ovpn", region: "Asia", country: "Turkey", city: "Istanbul"},
{subdomain: "vlus-tm1-ovpn", region: "Asia", country: "Turkmenistan", city: "Ashgabat"},
{subdomain: "vlus-uz-ovpn", region: "Asia", country: "Uzbekistan", city: "Tashkent"},
{subdomain: "vlap-vn2-ovpn", region: "Asia", country: "Vietnam", city: "Hanoi"},
{subdomain: "al1-ovpn", region: "Europe", country: "Albania", city: "Tirane"},
{subdomain: "vleu-am1-ovpn", region: "Europe", country: "Armenia", city: "Yerevan"},
{subdomain: "at2-ovpn", region: "Europe", country: "Austria", city: "Vienna"},
{subdomain: "vleu-be2-ovpn", region: "Europe", country: "Belgium", city: "Brussels"},
{subdomain: "ba1-ovpn", region: "Europe", country: "Bosnia and Herzegovina", city: "Sarajevo"},
{subdomain: "bg2-ovpn", region: "Europe", country: "Bulgaria", city: "Sofia"},
{subdomain: "vlus-hr1-ovpn", region: "Europe", country: "Croatia", city: "Zagreb"},
{subdomain: "cy1-ovpn", region: "Europe", country: "Cyprus", city: "Nicosia"},
{subdomain: "dk2-ovpn", region: "Europe", country: "Denmark", city: "Copenhagen"},
{subdomain: "ee1-ovpn", region: "Europe", country: "Estonia", city: "Tallinn"},
{subdomain: "fr2-ovpn", region: "Europe", country: "France", city: "Paris"},
{subdomain: "vlus-ge1-ovpn", region: "Europe", country: "Georgia", city: "Tbilisi"},
{subdomain: "de2-ovpn", region: "Europe", country: "Germany", city: "Frankfurt"},
{subdomain: "de2-ovpn", region: "Europe", country: "Germany", city: "Munich"},
{subdomain: "de-ao1-ovpn", region: "Europe", country: "Germany", city: "Nuremberg"},
{subdomain: "gr2-ovpn", region: "Europe", country: "Greece", city: "Thessaloniki"},
{subdomain: "hu2-ovpn", region: "Europe", country: "Hungary", city: "Budapest"},
{subdomain: "is1-ovpn", region: "Europe", country: "Iceland", city: "Reykjavik"},
{subdomain: "ie2-ovpn", region: "Europe", country: "Ireland", city: "Dublin"},
{subdomain: "im1-ovpn", region: "Europe", country: "Isle of Man", city: "Onchan"},
{subdomain: "vlus-it1-ovpn", region: "Europe", country: "Italy", city: "Milano"},
{subdomain: "lv1-ovpn", region: "Europe", country: "Latvia", city: "RIGA"},
{subdomain: "li1-ovpn", region: "Europe", country: "Liechtenstein", city: "Vaduz"},
{subdomain: "lt1-ovpn", region: "Europe", country: "Lithuania", city: "Vilnius"},
{subdomain: "lu2-ovpn", region: "Europe", country: "Luxembourg", city: "Luxembourg"},
{subdomain: "mt1-ovpn", region: "Europe", country: "Malta", city: "Sliema"},
{subdomain: "mn1-ovpn", region: "Europe", country: "Monaco", city: "Monaco"},
{subdomain: "vleu-me1-ovpn", region: "Europe", country: "Montenegro", city: "Podgorica"},
{subdomain: "nl2-ovpn", region: "Europe", country: "Netherlands", city: "Amsterdam"},
{subdomain: "vleu-no2-ovpn", region: "Europe", country: "Norway", city: "Oslo"},
{subdomain: "pl2-ovpn", region: "Europe", country: "Poland", city: "Warsaw"},
{subdomain: "pt2-ovpn", region: "Europe", country: "Portugal", city: "Lisbon"},
{subdomain: "ro2-ovpn", region: "Europe", country: "Romania", city: "Bucharest"},
{subdomain: "rs2-ovpn", region: "Europe", country: "Serbia", city: "Niš"},
{subdomain: "sk1-ovpn", region: "Europe", country: "Slovakia", city: "Bratislava"},
{subdomain: "si1-ovpn", region: "Europe", country: "Slovenia", city: "Ljubljana"},
{subdomain: "es-ovpn", region: "Europe", country: "Spain", city: "Barcelona"},
{subdomain: "vlus-se1-ovpn", region: "Europe", country: "Sweden", city: "Stockholm"},
{subdomain: "ch2-ovpn", region: "Europe", country: "Switzerland", city: "Zurich"},
{subdomain: "ukg2-ovpn", region: "Europe", country: "United Kingdom", city: "Gosport"},
{subdomain: "ukl2-ovpn", region: "Europe", country: "United Kingdom", city: "London"},
{subdomain: "ukm2-ovpn", region: "Europe", country: "United Kingdom", city: "Maidenhead"},
{subdomain: "vlus-uk-man1-ovpn", region: "Europe", country: "United Kingdom", city: "Manchester"},
{subdomain: "bh-ovpn", region: "Middle East", country: "Bahrain", city: "Manama"},
{subdomain: "vlus-jo1-ovpn", region: "Middle East", country: "Jordan", city: "Amman"},
{subdomain: "vlus-kw1-ovpn", region: "Middle East", country: "Kuwait", city: "Kuwait"},
{subdomain: "om1-ovpn", region: "Middle East", country: "Oman", city: "Salalah"},
{subdomain: "qa1-ovpn", region: "Middle East", country: "Qatar", city: "Doha"},
{subdomain: "sa1-ovpn", region: "Middle East", country: "Saudi Arabia", city: "Jeddah"},
{subdomain: "ae2-ovpn", region: "Middle East", country: "United Arab Emirates", city: "Dubai"},
{subdomain: "aw1-ovpn", region: "North America", country: "Aruba", city: "Oranjestad"},
{subdomain: "vleu-bb-ovpn", region: "North America", country: "Barbados", city: "Bridgetown"},
{subdomain: "bz1-ovpn", region: "North America", country: "Belize", city: "Belmopan"},
{subdomain: "vleu-bm-ovpn", region: "North America", country: "Bermuda", city: "Hamilton"},
{subdomain: "caq1-ovpn", region: "North America", country: "Canada", city: "Montreal"},
{subdomain: "cato-ovpn", region: "North America", country: "Canada", city: "Toronto"},
{subdomain: "cav2-ovpn", region: "North America", country: "Canada", city: "Vancouver"},
{subdomain: "vleu-ky-ovpn", region: "North America", country: "Cayman Islands", city: "George Town"},
{subdomain: "vlus-cr1-ovpn", region: "North America", country: "Costa Rica", city: "San Jose"},
{subdomain: "vleu-dm-ovpn", region: "North America", country: "Dominica", city: "Roseau"},
{subdomain: "vleu-do-ovpn", region: "North America", country: "Dominican Republic", city: "Santo Domingo"},
{subdomain: "vleu-sv-ovpn", region: "North America", country: "El Salvador", city: "San Salvador"},
{subdomain: "vleu-gd-ovpn", region: "North America", country: "Grenada", city: "St George's"},
{subdomain: "vleu-gt-ovpn", region: "North America", country: "Guatemala", city: "Guatemala"},
{subdomain: "vleu-ht1-ovpn", region: "North America", country: "Haiti", city: "PORT-AU-PRINCE"},
{subdomain: "vleu-hn-ovpn", region: "North America", country: "Honduras", city: "TEGUCIGALPA"},
{subdomain: "jm1-ovpn", region: "North America", country: "Jamaica", city: "Kingston"},
{subdomain: "vlus-mx2-ovpn", region: "North America", country: "Mexico", city: "Mexico City"},
{subdomain: "vleu-ms-ovpn", region: "North America", country: "Montserrat", city: "plymouth"},
{subdomain: "pr1-ovpn", region: "North America", country: "Puerto Rico", city: "San Juan"},
{subdomain: "vleu-lc-ovpn", region: "North America", country: "Saint Lucia", city: "Castries"},
{subdomain: "bs1-ovpn", region: "North America", country: "The Bahamas", city: "Freeport"},
{subdomain: "vleu-tt-ovpn", region: "North America", country: "Trinidad and Tobago", city: "Port of Spain"},
{subdomain: "vleu-tc-ovpn", region: "North America", country: "Turks and Caicos Islands", city: "Balfour Town"},
{subdomain: "usva-ovpn", region: "North America", country: "United States", city: "Ashburn"},
{subdomain: "usil2-ovpn", region: "North America", country: "United States", city: "Chicago"},
{subdomain: "usoh1-ovpn", region: "North America", country: "United States", city: "Columbus"},
{subdomain: "usga2-ovpn", region: "North America", country: "United States", city: "Georgia"},
{subdomain: "ustx2-ovpn", region: "North America", country: "United States", city: "Houston"},
{subdomain: "usla2-ovpn", region: "North America", country: "United States", city: "Los Angeles"},
{subdomain: "usfl2-ovpn", region: "North America", country: "United States", city: "Miami"},
{subdomain: "usnj2-ovpn", region: "North America", country: "United States", city: "New Jersey"},
{subdomain: "usny2-ovpn", region: "North America", country: "United States", city: "New York"},
{subdomain: "usphx2-ovpn", region: "North America", country: "United States", city: "Phoenix"},
{subdomain: "usut2-ovpn", region: "North America", country: "United States", city: "Salt Lake City"},
{subdomain: "ussf2-ovpn", region: "North America", country: "United States", city: "San Francisco"},
{subdomain: "ussa-ovpn", region: "North America", country: "United States", city: "Seattle"},
{subdomain: "uswdc2-ovpn", region: "North America", country: "United States", city: "Washington, D.C."},
{subdomain: "au-bn-ovpn", region: "Oceania", country: "Australia", city: "Brisbane"},
{subdomain: "au-me1-ovpn", region: "Oceania", country: "Australia", city: "Melbourne"},
{subdomain: "au2-pe-ovpn", region: "Oceania", country: "Australia", city: "Perth"},
{subdomain: "au-sd2-ovpn", region: "Oceania", country: "Australia", city: "Sydney"},
{subdomain: "nz2-ovpn", region: "Oceania", country: "New Zealand", city: "Auckland"},
{subdomain: "vlus-ar1-ovpn", region: "South America", country: "Argentina", city: "Buenos Aires"},
{subdomain: "vleu-bo-ovpn", region: "South America", country: "Bolivia", city: "Sucre"},
{subdomain: "br2-ovpn", region: "South America", country: "Brazil", city: "Sao Paulo"},
{subdomain: "vg1-ovpn", region: "South America", country: "British Virgin Island", city: "Road Town"},
{subdomain: "vlbr-cl-ovpn", region: "South America", country: "Chile", city: "Santiago"},
{subdomain: "co1-ovpn", region: "South America", country: "Colombia", city: "Bogota"},
{subdomain: "ec1-ovpn", region: "South America", country: "Ecuador", city: "Quito"},
{subdomain: "vleu-gy-ovpn", region: "South America", country: "Guyana", city: "Georgetown"},
{subdomain: "pa2-ovpn", region: "South America", country: "Panama", city: "Panama City"},
{subdomain: "vleu-py-ovpn", region: "South America", country: "Paraguay", city: "Asuncion"},
{subdomain: "pe1-ovpn", region: "South America", country: "Peru", city: "Lima"},
{subdomain: "vleu-sr-ovpn", region: "South America", country: "Suriname", city: "Paramaribo"},
}
for i := range servers {
servers[i].subdomain += "-udp"
}
return servers
}

1720
doc/logo.svg Normal file

File diff suppressed because it is too large Load Diff

After

Width:  |  Height:  |  Size: 62 KiB

BIN
doc/logo_256.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 20 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 13 KiB

View File

@@ -1,39 +0,0 @@
version: "3.7"
services:
gluetun:
image: qmcgaw/private-internet-access
container_name: gluetun
cap_add:
- NET_ADMIN
network_mode: bridge
ports:
- 8888:8888/tcp # Tinyproxy
- 8388:8388/tcp # Shadowsocks
- 8388:8388/udp # Shadowsocks
- 8000:8000/tcp # Built-in HTTP control server
# command:
environment:
# More variables are available, see the readme table
- VPNSP=private internet access
# Timezone for accurate logs times
- TZ=
# All VPN providers
- USER=js89ds7
# All VPN providers but Mullvad
- PASSWORD=8fd9s239G
# Cyberghost only
- CLIENT_KEY=
# All VPN providers but Mullvad
- REGION=Austria
# Mullvad only
- COUNTRY=Sweden
# Allow for example your LAN, set to: 192.168.1.0/24
- EXTRA_SUBNETS=
restart: always

46
go.mod
View File

@@ -1,13 +1,43 @@
module github.com/qdm12/gluetun
go 1.15
go 1.17
require (
github.com/fatih/color v1.9.0
github.com/golang/mock v1.4.4
github.com/kyokomi/emoji v2.2.4+incompatible
github.com/qdm12/golibs v0.0.0-20200712151944-a0325873bf5a
github.com/qdm12/ss-server v0.0.0-20200819005413-6b516c299307
github.com/stretchr/testify v1.6.1
golang.org/x/sys v0.0.0-20200814200057-3d37ad5750ed
github.com/breml/rootcerts v0.2.1
github.com/fatih/color v1.13.0
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/gotree v0.2.0
github.com/qdm12/govalid v0.1.0
github.com/qdm12/ss-server v0.4.0
github.com/qdm12/updated v0.0.0-20210603204757-205acfe6937e
github.com/stretchr/testify v1.7.0
github.com/vishvananda/netlink v1.1.1-0.20211129163951-9ada19101fc5
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/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-20200728191858-db3c7e526aae // 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
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c // indirect
)

271
go.sum
View File

@@ -1,142 +1,251 @@
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/asaskevich/govalidator v0.0.0-20180720115003-f9ffefc3facf h1:eg0MeVzsP1G42dRafH3vf+al2vQIJU0YHX+1Tw87oco=
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/go.mod h1:lB+ZfQJz7igIIfQNfa7Ml4HSf2uFQQRzpGGRXenZAgY=
github.com/breml/rootcerts v0.2.1 h1:GZMVDXOs945764NFck0vtHSjktKYubOFM0kjf5HAuwc=
github.com/breml/rootcerts v0.2.1/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/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/globalsign/mgo v0.0.0-20180905125535-1ca0a4f7cbcb h1:D4uzjWwKYQ5XnAvUbuvHW93esHg7F8N/OYeBBcJoTr0=
github.com/dvyukov/go-fuzz v0.0.0-20210103155950-6a8e9d1f2415/go.mod h1:11Gm+ccJnvAhCNLlf5+cS9KjtbaD5I5zaZpFMsTHWTw=
github.com/emirpasic/gods v1.12.0/go.mod h1:YfzfFFoVP/catgzJb4IKIqXjX78Ha8FMSDh3ymbK86o=
github.com/fatih/color v1.12.0/go.mod h1:ELkj/draVOlAH/xkhN6mQ50Qd0MPOk5AAr3maGEBuJM=
github.com/fatih/color v1.13.0 h1:8LOYc1KYPPmyKMuN8QV2DNRWNbLo6LZ0iLs8+mlH53w=
github.com/fatih/color v1.13.0/go.mod h1:kLAiJbzzSOZDVNGyDpeOxJ47H46qBXwg5ILebYFFOfk=
github.com/flynn/go-shlex v0.0.0-20150515145356-3f9db97f8568/go.mod h1:xEzjJPgXI435gkrCt3MPfRiAkVrwSbHsst4LCFVfpJc=
github.com/gliderlabs/ssh v0.2.2/go.mod h1:U7qILu1NlMHj9FlMhZLlkCdDnU1DBEAqr0aevW3Awn0=
github.com/globalsign/mgo v0.0.0-20180905125535-1ca0a4f7cbcb/go.mod h1:xkRDCp4j0OGD1HRkm4kmhM+pmpv3AKq5SU7GMg4oO/Q=
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/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/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI=
github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.5 h1:Khx7svrCpmxxtHBq5j2mp/xVjsi8hQMfNLvJFAlrGgU=
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/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/gotify/go-api-client/v2 v2.0.4/go.mod h1:VKiah/UK20bXsr0JObE1eBVLW44zbBouzjuri9iwjFU=
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
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/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-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/mitchellh/mapstructure v1.1.2 h1:fmNYVwqnSfB9mZU6OS2O6GsXM+wcskZDuKQzvN1EDeE=
github.com/mattn/go-colorable v0.1.8/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc=
github.com/mattn/go-colorable v0.1.9 h1:sqDoxXbdeALODt0DAeJCVp38ps9ZogZEAXjus69YV3U=
github.com/mattn/go-colorable v0.1.9/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc=
github.com/mattn/go-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/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/phayes/permbits v0.0.0-20190612203442-39d7c581d2ee h1:P6U24L02WMfj9ymZTxl7CxS73JC99x3ukk+DBkgQGQs=
github.com/pelletier/go-buffruneio v0.2.0/go.mod h1:JkE26KsDizTr40EUHkXVtNPvgGtbSNq5BcowyYOWdKo=
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/golibs v0.0.0-20200712151944-a0325873bf5a h1:IyS72qFm+iXipadmUKXmpJScKXXK2GrD8yYfxXsnIYs=
github.com/qdm12/golibs v0.0.0-20200712151944-a0325873bf5a/go.mod h1:pikkTN7g7zRuuAnERwqW1yAFq6pYmxrxpjiwGvb0Ysc=
github.com/qdm12/ss-server v0.0.0-20200819005413-6b516c299307 h1:+LhVxIKpZgUM8ZcopIuc3Yjk+p76dWRdYLQiAA7caZM=
github.com/qdm12/ss-server v0.0.0-20200819005413-6b516c299307/go.mod h1:ABVUkxubboL3vqBkOwDV9glX1/x7SnYrckBe5d+M/zw=
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/gotree v0.2.0 h1:+58ltxkNLUyHtATFereAcOjBVfY6ETqRex8XK90Fb/c=
github.com/qdm12/gotree v0.2.0/go.mod h1:1SdFaqKZuI46U1apbXIf25pDMNnrPuYLEqMF/qL4lY4=
github.com/qdm12/govalid v0.1.0 h1:UIFVmuaAg0Q+h0GeyfcFEZ5sQ5KJPvRQwycC1/cqDN8=
github.com/qdm12/govalid v0.1.0/go.mod h1:CyS/OEQdOvunBgrtIsW93fjd4jBkwZPBjGSpxq3NwA4=
github.com/qdm12/ss-server v0.4.0 h1:lMMYfDGc9P86Lyvd3+p8lK4hhgHUKDzjZC91FqJYkDU=
github.com/qdm12/ss-server v0.4.0/go.mod h1:AY0p4huvPUPW+/CiWsJcDgT6sneDryk26VXSccPNCxY=
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/stretchr/objx v0.1.0 h1:4G4v2dO3VZwixGIRoQ5Lfboy6nUhCyYzaqnIAPPhYs4=
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/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 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJyk=
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=
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/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.1-0.20211129163951-9ada19101fc5 h1:b/k/BVWzWRS5v6AB0gf2ckFSbFsHN5jR0HoNso1pN+w=
github.com/vishvananda/netlink v1.1.1-0.20211129163951-9ada19101fc5/go.mod h1:twkDnbuQxJYemMlGd4JFIcuhgX83tXhKS2B/PRMpOho=
github.com/vishvananda/netns v0.0.0-20200728191858-db3c7e526aae h1:4hwBBUfQCFe3Cym0ZtKyq7L16eZUtYKs+BaHDN6mAns=
github.com/vishvananda/netns v0.0.0-20200728191858-db3c7e526aae/go.mod h1:DD4vA1DwXk04H54A1oHXtwZmA0grkVMdPxx/VGLCah0=
github.com/xanzy/ssh-agent v0.2.1/go.mod h1:mLlQY/MoOhWBj+gOGMQkOeiEvkx+8pJSI+0Bx9h2kr4=
github.com/yl2chen/cidranger v1.0.2/go.mod h1:9U1yz7WPYDwf0vpNWFaeRh0bjwz5RVgRy/9UEQfHl0g=
github.com/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-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/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-20190701094942-4def268fd1a4/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-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 h1:R/3boaszxrf1GEUWTVDzSKVwLmSJpwZ1yqXm8j0v2QI=
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20190724013045-ca1201d0de80/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20190827160401-ba9fcec4b297/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20190923162816-aa69164e4478/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20191007182048-72f939374954/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20201010224723-4f7140c49acb/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
golang.org/x/net v0.0.0-20201216054612-986b41b23924/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
golang.org/x/net v0.0.0-20201224014010-6772e930b67b/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
golang.org/x/net v0.0.0-20210119194325-5f4716e94777/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM=
golang.org/x/net v0.0.0-20210504132125-bbd867fde50d 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-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190412213103-97732733099d h1:+R4KGOnez64A81RvjARKc4UT5/tI9ujCIVX+P5KiHuI=
golang.org/x/sys v0.0.0-20190221075227-b4e8571b14e0/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190411185658-b44545bcd369/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190826190057-c7b8b68b1456/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190924154521-2837fb4f24fe/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191008105621-543471e840be/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200814200057-3d37ad5750ed h1:J22ig1FUekjjkmZUM7pTKixYm8DvrYsvrBZdunYeIuQ=
golang.org/x/sys v0.0.0-20200814200057-3d37ad5750ed/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.0 h1:g61tztE5qeGQ89tm6NTjjM9VPIm088od1l6aSorWRWg=
golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200217220822-9197077df867/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200728102440-3e129f6d46b1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201009025420-dfb3f7c4e634/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201118182958-a01c418693c7/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201218084310-7d0127a74742/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210110051926-789bb1bd4061/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210123111255-9b0068b26619/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210216163648-f7da38b97c65/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210309040221-94ec62e08169/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210403161142-5e06dd20ab57/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210503173754-0981d6026fa6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c h1:F1jZWGFhYfh0Ci55sIpILtKKK8p3i2/krTr0H1rg74I=
golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw=
golang.org/x/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/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-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/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-20190729092621-ff9f1409240a/go.mod h1:jcCCGcm9btYwXyDqrUWc6MKQKKGJCWEQ3AfLSRIbEuI=
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-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=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
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=
gopkg.in/warnings.v0 v0.1.2/go.mod h1:jksf8JmL6Qr/oQM2OXTHunEvvTAsrWBLb6OOjuVWRNI=
gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw=
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

@@ -1,25 +1,29 @@
// Package alpine defines a configurator to interact with the Alpine operating system.
package alpine
import (
"os/user"
"github.com/qdm12/golibs/files"
)
type Configurator interface {
CreateUser(username string, uid int) error
var _ Alpiner = (*Alpine)(nil)
type Alpiner interface {
UserCreater
VersionGetter
}
type configurator struct {
fileManager files.FileManager
lookupUID func(uid string) (*user.User, error)
lookupUser func(username string) (*user.User, error)
type Alpine struct {
alpineReleasePath string
passwdPath string
lookupID func(uid string) (*user.User, error)
lookup func(username string) (*user.User, error)
}
func NewConfigurator(fileManager files.FileManager) Configurator {
return &configurator{
fileManager: fileManager,
lookupUID: user.LookupId,
lookupUser: user.Lookup,
func New() *Alpine {
return &Alpine{
alpineReleasePath: "/etc/alpine-release",
passwdPath: "/etc/passwd",
lookupID: user.LookupId,
lookup: user.Lookup,
}
}

View File

@@ -1,38 +1,58 @@
package alpine
import (
"errors"
"fmt"
"os"
"os/user"
"strconv"
)
// CreateUser creates a user in Alpine with the given UID
func (c *configurator) CreateUser(username string, uid int) error {
UIDStr := fmt.Sprintf("%d", uid)
u, err := c.lookupUID(UIDStr)
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 (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 {
if u.Username == username {
return nil
}
return fmt.Errorf("user with ID %d exists with username %q instead of %q", uid, u.Username, username)
return "", err
}
u, err = c.lookupUser(username)
if u != nil {
if u.Username == username {
return "", nil
}
return u.Username, nil
}
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
}
passwd, err := c.fileManager.ReadFile("/etc/passwd")
if err != nil {
return fmt.Errorf("cannot create user: %w", err)
}
passwd = append(passwd, []byte(fmt.Sprintf("%s:x:%d:::/dev/null:/sbin/nologin\n", username, uid))...)
if err := c.fileManager.WriteToFile("/etc/passwd", passwd); err != nil {
return fmt.Errorf("cannot create user: %w", err)
if u != nil {
return "", fmt.Errorf("%w: with name %s for ID %s instead of %d",
ErrUserAlreadyExists, username, u.Uid, uid)
}
return nil
file, err := os.OpenFile(a.passwdPath, os.O_APPEND|os.O_WRONLY, 0644)
if err != nil {
return "", err
}
s := fmt.Sprintf("%s:x:%d:::/dev/null:/sbin/nologin\n", username, uid)
_, err = file.WriteString(s)
if err != nil {
_ = 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
}

7
internal/cli/ci.go Normal file
View File

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

View File

@@ -1,74 +1,22 @@
// Package cli defines an interface CLI to run command line operations.
package cli
import (
"flag"
"fmt"
"strings"
var _ CLIer = (*CLI)(nil)
"net"
"github.com/qdm12/gluetun/internal/params"
"github.com/qdm12/gluetun/internal/provider"
"github.com/qdm12/gluetun/internal/settings"
"github.com/qdm12/golibs/files"
"github.com/qdm12/golibs/logging"
)
func ClientKey(args []string) error {
flagSet := flag.NewFlagSet("clientkey", flag.ExitOnError)
filepath := flagSet.String("path", "/files/client.key", "file path to the client.key file")
if err := flagSet.Parse(args); err != nil {
return err
}
fileManager := files.NewFileManager()
data, err := fileManager.ReadFile(*filepath)
if err != nil {
return err
}
s := string(data)
s = strings.ReplaceAll(s, "\n", "")
s = strings.ReplaceAll(s, "\r", "")
s = strings.TrimPrefix(s, "-----BEGIN PRIVATE KEY-----")
s = strings.TrimSuffix(s, "-----END PRIVATE KEY-----")
fmt.Println(s)
return nil
type CLIer interface {
ClientKeyFormatter
HealthChecker
OpenvpnConfigMaker
Updater
ServersFormatter
}
func HealthCheck() error {
ips, err := net.LookupIP("github.com")
if err != nil {
return fmt.Errorf("cannot resolve github.com (%s)", err)
} else if len(ips) == 0 {
return fmt.Errorf("resolved no IP addresses for github.com")
}
return nil
type CLI struct {
repoServersPath string
}
func OpenvpnConfig() error {
logger, err := logging.NewLogger(logging.ConsoleEncoding, logging.InfoLevel, -1)
if err != nil {
return err
func New() *CLI {
return &CLI{
repoServersPath: "./internal/storage/servers.json",
}
paramsReader := params.NewReader(logger, files.NewFileManager())
allSettings, err := settings.GetAllSettings(paramsReader)
if err != nil {
return err
}
providerConf := provider.New(allSettings.OpenVPN.Provider.Name)
connections, err := providerConf.GetOpenVPNConnections(allSettings.OpenVPN.Provider.ServerSelection)
if err != nil {
return err
}
lines := providerConf.BuildConf(
connections,
allSettings.OpenVPN.Verbosity,
allSettings.System.UID,
allSettings.System.GID,
allSettings.OpenVPN.Root,
allSettings.OpenVPN.Cipher,
allSettings.OpenVPN.Auth,
allSettings.OpenVPN.Provider.ExtraConfigOptions,
)
fmt.Println(strings.Join(lines, "\n"))
return nil
}

45
internal/cli/clientkey.go Normal file
View File

@@ -0,0 +1,45 @@
package cli
import (
"flag"
"fmt"
"io"
"os"
"strings"
"github.com/qdm12/gluetun/internal/constants"
)
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 := os.OpenFile(*filepath, os.O_RDONLY, 0)
if err != nil {
return err
}
data, err := io.ReadAll(file)
if err != nil {
_ = file.Close()
return err
}
if err := file.Close(); err != nil {
return err
}
if err != nil {
return err
}
s := string(data)
s = strings.ReplaceAll(s, "\n", "")
s = strings.ReplaceAll(s, "\r", "")
s = strings.TrimPrefix(s, "-----BEGIN PRIVATE KEY-----")
s = strings.TrimSuffix(s, "-----END PRIVATE KEY-----")
fmt.Println(s)
return nil
}

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

@@ -0,0 +1,42 @@
package cli
import (
"context"
"net"
"net/http"
"time"
"github.com/qdm12/gluetun/internal/configuration/sources"
"github.com/qdm12/gluetun/internal/healthcheck"
)
type HealthChecker interface {
HealthCheck(ctx context.Context, source sources.Source, warner Warner) error
}
func (c *CLI) HealthCheck(ctx context.Context, source sources.Source, warner Warner) error {
// Extract the health server port from the configuration.
config, err := source.ReadHealth()
if err != nil {
return err
}
err = config.Validate()
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}
client := healthcheck.NewClient(httpClient)
ctx, cancel := context.WithTimeout(ctx, timeout)
defer cancel()
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

@@ -0,0 +1,51 @@
package cli
import (
"fmt"
"strings"
"time"
"github.com/qdm12/gluetun/internal/configuration/sources"
"github.com/qdm12/gluetun/internal/constants"
"github.com/qdm12/gluetun/internal/provider"
"github.com/qdm12/gluetun/internal/storage"
)
type OpenvpnConfigMaker interface {
OpenvpnConfig(logger OpenvpnConfigLogger, source sources.Source) error
}
type OpenvpnConfigLogger interface {
Info(s string)
Warn(s string)
}
func (c *CLI) OpenvpnConfig(logger OpenvpnConfigLogger, source sources.Source) error {
storage, err := storage.New(logger, constants.ServersData)
if err != nil {
return err
}
allServers := storage.GetServers()
allSettings, err := source.Read()
if err != nil {
return err
}
if err = allSettings.Validate(allServers); 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
}
fmt.Println(strings.Join(lines, "\n"))
return nil
}

132
internal/cli/update.go Normal file
View File

@@ -0,0 +1,132 @@
package cli
import (
"context"
"encoding/json"
"errors"
"flag"
"fmt"
"net"
"net/http"
"os"
"strings"
"time"
"github.com/qdm12/gluetun/internal/configuration/settings"
"github.com/qdm12/gluetun/internal/constants"
"github.com/qdm12/gluetun/internal/models"
"github.com/qdm12/gluetun/internal/storage"
"github.com/qdm12/gluetun/internal/updater"
)
var (
ErrModeUnspecified = errors.New("at least one of -enduser or -maintainer must be specified")
ErrDNSAddress = errors.New("DNS address is not valid")
ErrNoProviderSpecified = errors.New("no provider was 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 boolPtr(b bool) *bool { return &b }
func (c *CLI) Update(ctx context.Context, args []string, logger UpdaterLogger) error {
options := settings.Updater{CLI: boolPtr(true)}
var endUserMode, maintainerMode, updateAll bool
var dnsAddress, csvProviders string
flagSet := flag.NewFlagSet("update", flag.ExitOnError)
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(&dnsAddress, "dns", "8.8.8.8", "DNS resolver address to use")
flagSet.BoolVar(&updateAll, "all", false, "Update servers for all VPN providers")
flagSet.StringVar(&csvProviders, "providers", "", "CSV string of VPN providers to update server data for")
if err := flagSet.Parse(args); err != nil {
return err
}
if !endUserMode && !maintainerMode {
return ErrModeUnspecified
}
options.DNSAddress = net.ParseIP(dnsAddress)
if options.DNSAddress == nil {
return fmt.Errorf("%w: %s", ErrDNSAddress, dnsAddress)
}
if updateAll {
for _, provider := range constants.AllProviders() {
if provider == constants.Custom {
continue
}
options.Providers = append(options.Providers, provider)
}
} else {
if csvProviders == "" {
return ErrNoProviderSpecified
}
options.Providers = strings.Split(csvProviders, ",")
}
options.SetDefaults()
err := options.Validate()
if err != nil {
return fmt.Errorf("options validation failed: %w", err)
}
const clientTimeout = 10 * time.Second
httpClient := &http.Client{Timeout: clientTimeout}
storage, err := storage.New(logger, constants.ServersData)
if err != nil {
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 fmt.Errorf("%w: %s", ErrUpdateServerInformation, err)
}
if endUserMode {
if err := storage.FlushToFile(allServers); err != nil {
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)
}

5
internal/cli/warner.go Normal file
View File

@@ -0,0 +1,5 @@
package cli
type Warner interface {
Warn(s string)
}

View File

@@ -0,0 +1,82 @@
package settings
import (
"fmt"
"net"
"github.com/qdm12/gluetun/internal/configuration/settings/helpers"
"github.com/qdm12/gotree"
)
// DNS contains settings to configure DNS.
type DNS struct {
// ServerAddress is the DNS server to use inside
// the Go program and for the system.
// It defaults to '127.0.0.1' to be used with the
// DoT server. It cannot be nil in the internal
// state.
ServerAddress net.IP
// KeepNameserver is true if the Docker DNS server
// found in /etc/resolv.conf should be kept.
// Note settings this to true will go around the
// DoT server blocking.
// It defaults to false and cannot be nil in the
// internal state.
KeepNameserver *bool
// DOT contains settings to configure the DoT
// server.
DoT DoT
}
func (d DNS) validate() (err error) {
err = d.DoT.validate()
if err != nil {
return fmt.Errorf("failed validating DoT settings: %w", err)
}
return nil
}
func (d *DNS) Copy() (copied DNS) {
return DNS{
ServerAddress: helpers.CopyIP(d.ServerAddress),
KeepNameserver: helpers.CopyBoolPtr(d.KeepNameserver),
DoT: d.DoT.copy(),
}
}
// mergeWith merges the other settings into any
// unset field of the receiver settings object.
func (d *DNS) mergeWith(other DNS) {
d.ServerAddress = helpers.MergeWithIP(d.ServerAddress, other.ServerAddress)
d.KeepNameserver = helpers.MergeWithBool(d.KeepNameserver, other.KeepNameserver)
d.DoT.mergeWith(other.DoT)
}
// overrideWith overrides fields of the receiver
// settings object with any field set in the other
// settings.
func (d *DNS) overrideWith(other DNS) {
d.ServerAddress = helpers.OverrideWithIP(d.ServerAddress, other.ServerAddress)
d.KeepNameserver = helpers.OverrideWithBool(d.KeepNameserver, other.KeepNameserver)
d.DoT.overrideWith(other.DoT)
}
func (d *DNS) setDefaults() {
localhost := net.IPv4(127, 0, 0, 1) //nolint:gomnd
d.ServerAddress = helpers.DefaultIP(d.ServerAddress, localhost)
d.KeepNameserver = helpers.DefaultBool(d.KeepNameserver, false)
d.DoT.setDefaults()
}
func (d DNS) String() string {
return d.toLinesNode().String()
}
func (d DNS) toLinesNode() (node *gotree.Node) {
node = gotree.New("DNS settings:")
node.Appendf("DNS server address to use: %s", d.ServerAddress)
node.Appendf("Keep existing nameserver(s): %s", helpers.BoolPtrToYesNo(d.KeepNameserver))
node.AppendNode(d.DoT.toLinesNode())
return node
}

View File

@@ -0,0 +1,138 @@
package settings
import (
"errors"
"fmt"
"regexp"
"github.com/qdm12/dns/pkg/blacklist"
"github.com/qdm12/gluetun/internal/configuration/settings/helpers"
"github.com/qdm12/gotree"
"inet.af/netaddr"
)
// DNSBlacklist is settings for the DNS blacklist building.
type DNSBlacklist struct {
BlockMalicious *bool
BlockAds *bool
BlockSurveillance *bool
AllowedHosts []string
AddBlockedHosts []string
AddBlockedIPs []netaddr.IP
AddBlockedIPPrefixes []netaddr.IPPrefix
}
func (b *DNSBlacklist) setDefaults() {
b.BlockMalicious = helpers.DefaultBool(b.BlockMalicious, true)
b.BlockAds = helpers.DefaultBool(b.BlockAds, false)
b.BlockSurveillance = helpers.DefaultBool(b.BlockSurveillance, true)
}
var hostRegex = regexp.MustCompile(`^([a-zA-Z0-9]|[a-zA-Z0-9_][a-zA-Z0-9\-_]{0,61}[a-zA-Z0-9_])(\.([a-zA-Z0-9]|[a-zA-Z0-9_][a-zA-Z0-9\-_]{0,61}[a-zA-Z0-9]))*$`) //nolint:lll
var (
ErrAllowedHostNotValid = errors.New("allowed host is not valid")
ErrBlockedHostNotValid = errors.New("blocked host is not valid")
)
func (b DNSBlacklist) validate() (err error) {
for _, host := range b.AllowedHosts {
if !hostRegex.MatchString(host) {
return fmt.Errorf("%w: %s", ErrAllowedHostNotValid, host)
}
}
for _, host := range b.AddBlockedHosts {
if !hostRegex.MatchString(host) {
return fmt.Errorf("%w: %s", ErrBlockedHostNotValid, host)
}
}
return nil
}
func (b DNSBlacklist) copy() (copied DNSBlacklist) {
return DNSBlacklist{
BlockMalicious: helpers.CopyBoolPtr(b.BlockMalicious),
BlockAds: helpers.CopyBoolPtr(b.BlockAds),
BlockSurveillance: helpers.CopyBoolPtr(b.BlockSurveillance),
AllowedHosts: helpers.CopyStringSlice(b.AllowedHosts),
AddBlockedHosts: helpers.CopyStringSlice(b.AddBlockedHosts),
AddBlockedIPs: helpers.CopyNetaddrIPsSlice(b.AddBlockedIPs),
AddBlockedIPPrefixes: helpers.CopyIPPrefixSlice(b.AddBlockedIPPrefixes),
}
}
func (b *DNSBlacklist) mergeWith(other DNSBlacklist) {
b.BlockMalicious = helpers.MergeWithBool(b.BlockMalicious, other.BlockMalicious)
b.BlockAds = helpers.MergeWithBool(b.BlockAds, other.BlockAds)
b.BlockSurveillance = helpers.MergeWithBool(b.BlockSurveillance, other.BlockSurveillance)
b.AllowedHosts = helpers.MergeStringSlices(b.AllowedHosts, other.AllowedHosts)
b.AddBlockedHosts = helpers.MergeStringSlices(b.AddBlockedHosts, other.AddBlockedHosts)
b.AddBlockedIPs = helpers.MergeNetaddrIPsSlices(b.AddBlockedIPs, other.AddBlockedIPs)
b.AddBlockedIPPrefixes = helpers.MergeIPPrefixesSlices(b.AddBlockedIPPrefixes, other.AddBlockedIPPrefixes)
}
func (b *DNSBlacklist) overrideWith(other DNSBlacklist) {
b.BlockMalicious = helpers.OverrideWithBool(b.BlockMalicious, other.BlockMalicious)
b.BlockAds = helpers.OverrideWithBool(b.BlockAds, other.BlockAds)
b.BlockSurveillance = helpers.OverrideWithBool(b.BlockSurveillance, other.BlockSurveillance)
b.AllowedHosts = helpers.OverrideWithStringSlice(b.AllowedHosts, other.AllowedHosts)
b.AddBlockedHosts = helpers.OverrideWithStringSlice(b.AddBlockedHosts, other.AddBlockedHosts)
b.AddBlockedIPs = helpers.OverrideWithNetaddrIPsSlice(b.AddBlockedIPs, other.AddBlockedIPs)
b.AddBlockedIPPrefixes = helpers.OverrideWithIPPrefixesSlice(b.AddBlockedIPPrefixes, other.AddBlockedIPPrefixes)
}
func (b DNSBlacklist) ToBlacklistFormat() (settings blacklist.BuilderSettings, err error) {
return blacklist.BuilderSettings{
BlockMalicious: *b.BlockMalicious,
BlockAds: *b.BlockAds,
BlockSurveillance: *b.BlockSurveillance,
AllowedHosts: b.AllowedHosts,
AddBlockedHosts: b.AddBlockedHosts,
AddBlockedIPs: b.AddBlockedIPs,
AddBlockedIPPrefixes: b.AddBlockedIPPrefixes,
}, nil
}
func (b DNSBlacklist) String() string {
return b.toLinesNode().String()
}
func (b DNSBlacklist) toLinesNode() (node *gotree.Node) {
node = gotree.New("DNS filtering settings:")
node.Appendf("Block malicious: %s", helpers.BoolPtrToYesNo(b.BlockMalicious))
node.Appendf("Block ads: %s", helpers.BoolPtrToYesNo(b.BlockAds))
node.Appendf("Block surveillance: %s", helpers.BoolPtrToYesNo(b.BlockSurveillance))
if len(b.AllowedHosts) > 0 {
allowedHostsNode := node.Appendf("Allowed hosts:")
for _, host := range b.AllowedHosts {
allowedHostsNode.Appendf(host)
}
}
if len(b.AddBlockedHosts) > 0 {
blockedHostsNode := node.Appendf("Blocked hosts:")
for _, host := range b.AddBlockedHosts {
blockedHostsNode.Appendf(host)
}
}
if len(b.AddBlockedIPs) > 0 {
blockedIPsNode := node.Appendf("Blocked IP addresses:")
for _, ip := range b.AddBlockedIPs {
blockedIPsNode.Appendf(ip.String())
}
}
if len(b.AddBlockedIPPrefixes) > 0 {
blockedIPPrefixesNode := node.Appendf("Blocked IP networks:")
for _, ipNetwork := range b.AddBlockedIPPrefixes {
blockedIPPrefixesNode.Appendf(ipNetwork.String())
}
}
return node
}

View File

@@ -0,0 +1,113 @@
package settings
import (
"errors"
"fmt"
"time"
"github.com/qdm12/gluetun/internal/configuration/settings/helpers"
"github.com/qdm12/gotree"
)
// DoT contains settings to configure the DoT server.
type DoT struct {
// Enabled is true if the DoT server should be running
// and used. It defaults to true, and cannot be nil
// in the internal state.
Enabled *bool
// UpdatePeriod is the period to update DNS block
// lists and cryptographic files for DNSSEC validation.
// It can be set to 0 to disable the update.
// It defaults to 24h and cannot be nil in
// the internal state.
UpdatePeriod *time.Duration
// Unbound contains settings to configure Unbound.
Unbound Unbound
// Blacklist contains settings to configure the filter
// block lists.
Blacklist DNSBlacklist
}
var (
ErrDoTUpdatePeriodTooShort = errors.New("update period is too short")
)
func (d DoT) validate() (err error) {
const minUpdatePeriod = 30 * time.Second
if *d.UpdatePeriod != 0 && *d.UpdatePeriod < minUpdatePeriod {
return fmt.Errorf("%w: %s must be bigger than %s",
ErrDoTUpdatePeriodTooShort, *d.UpdatePeriod, minUpdatePeriod)
}
err = d.Unbound.validate()
if err != nil {
return err
}
err = d.Blacklist.validate()
if err != nil {
return err
}
return nil
}
func (d *DoT) copy() (copied DoT) {
return DoT{
Enabled: helpers.CopyBoolPtr(d.Enabled),
UpdatePeriod: helpers.CopyDurationPtr(d.UpdatePeriod),
Unbound: d.Unbound.copy(),
Blacklist: d.Blacklist.copy(),
}
}
// mergeWith merges the other settings into any
// unset field of the receiver settings object.
func (d *DoT) mergeWith(other DoT) {
d.Enabled = helpers.MergeWithBool(d.Enabled, other.Enabled)
d.UpdatePeriod = helpers.MergeWithDuration(d.UpdatePeriod, other.UpdatePeriod)
d.Unbound.mergeWith(other.Unbound)
d.Blacklist.mergeWith(other.Blacklist)
}
// overrideWith overrides fields of the receiver
// settings object with any field set in the other
// settings.
func (d *DoT) overrideWith(other DoT) {
d.Enabled = helpers.OverrideWithBool(d.Enabled, other.Enabled)
d.UpdatePeriod = helpers.OverrideWithDuration(d.UpdatePeriod, other.UpdatePeriod)
d.Unbound.overrideWith(other.Unbound)
d.Blacklist.overrideWith(other.Blacklist)
}
func (d *DoT) setDefaults() {
d.Enabled = helpers.DefaultBool(d.Enabled, true)
const defaultUpdatePeriod = 24 * time.Hour
d.UpdatePeriod = helpers.DefaultDuration(d.UpdatePeriod, defaultUpdatePeriod)
d.Unbound.setDefaults()
d.Blacklist.setDefaults()
}
func (d DoT) String() string {
return d.toLinesNode().String()
}
func (d DoT) toLinesNode() (node *gotree.Node) {
node = gotree.New("DNS over TLS settings:")
node.Appendf("Enabled: %s", helpers.BoolPtrToYesNo(d.Enabled))
if !*d.Enabled {
return node
}
update := "disabled"
if *d.UpdatePeriod > 0 {
update = "every " + d.UpdatePeriod.String()
}
node.Appendf("Update period: %s", update)
node.AppendNode(d.Unbound.toLinesNode())
node.AppendNode(d.Blacklist.toLinesNode())
return node
}

View File

@@ -0,0 +1,51 @@
package settings
import "errors"
var (
ErrCityNotValid = errors.New("the city specified is not valid")
ErrControlServerPrivilegedPort = errors.New("cannot use privileged port without running as root")
ErrCountryNotValid = errors.New("the country specified is not valid")
ErrFirewallZeroPort = errors.New("cannot have a zero port to block")
ErrHostnameNotValid = errors.New("the hostname specified is not valid")
ErrISPNotValid = errors.New("the ISP specified is not valid")
ErrNameNotValid = errors.New("the server name specified is not valid")
ErrOpenVPNClientCertMissing = errors.New("client certificate is missing")
ErrOpenVPNClientCertNotValid = errors.New("client certificate is not valid")
ErrOpenVPNClientKeyMissing = errors.New("client key is missing")
ErrOpenVPNClientKeyNotValid = errors.New("client key is not valid")
ErrOpenVPNConfigFile = errors.New("custom configuration file error")
ErrOpenVPNCustomPortNotAllowed = errors.New("custom endpoint port is not allowed")
ErrOpenVPNEncryptionPresetNotValid = errors.New("PIA encryption preset is not valid")
ErrOpenVPNInterfaceNotValid = errors.New("interface name is not valid")
ErrOpenVPNMSSFixIsTooHigh = errors.New("mssfix option value is too high")
ErrOpenVPNPasswordIsEmpty = errors.New("password is empty")
ErrOpenVPNTCPNotSupported = errors.New("TCP protocol is not supported")
ErrOpenVPNUserIsEmpty = errors.New("user is empty")
ErrOpenVPNVerbosityIsOutOfBounds = errors.New("verbosity value is out of bounds")
ErrOpenVPNVersionIsNotValid = errors.New("version is not valid")
ErrPortForwardingEnabled = errors.New("port forwarding cannot be enabled")
ErrPortForwardingFilepathNotValid = errors.New("port forwarding filepath given is not valid")
ErrPublicIPFilepathNotValid = errors.New("public IP address file path is not valid")
ErrPublicIPPeriodTooShort = errors.New("public IP address check period is too short")
ErrRegionNotValid = errors.New("the region specified is not valid")
ErrServerAddressNotValid = errors.New("server listening address is not valid")
ErrSystemPGIDNotValid = errors.New("process group id is not valid")
ErrSystemPUIDNotValid = errors.New("process user id is not valid")
ErrSystemTimezoneNotValid = errors.New("timezone is not valid")
ErrVPNProviderNameNotValid = errors.New("VPN provider name is not valid")
ErrVPNTypeNotValid = errors.New("VPN type is not valid")
ErrWireguardEndpointIPNotSet = errors.New("endpoint IP is not set")
ErrWireguardEndpointPortNotAllowed = errors.New("endpoint port is not allowed")
ErrWireguardEndpointPortNotSet = errors.New("endpoint port is not set")
ErrWireguardInterfaceAddressNotSet = errors.New("interface address is not set")
ErrWireguardInterfaceNotValid = errors.New("interface name is not valid")
ErrWireguardPreSharedKeyNotSet = errors.New("pre-shared key is not set")
ErrWireguardPreSharedKeyNotValid = errors.New("pre-shared key is not valid")
ErrWireguardPrivateKeyNotSet = errors.New("private key is not set")
ErrWireguardPrivateKeyNotValid = errors.New("private key is not valid")
ErrWireguardPublicKeyNotSet = errors.New("public key is not set")
ErrWireguardPublicKeyNotValid = errors.New("public key is not valid")
ErrUpdaterPeriodTooSmall = errors.New("VPN server data updater period is too small")
)

View File

@@ -0,0 +1,117 @@
package settings
import (
"fmt"
"net"
"github.com/qdm12/gluetun/internal/configuration/settings/helpers"
"github.com/qdm12/gotree"
)
// Firewall contains settings to customize the firewall operation.
type Firewall struct {
VPNInputPorts []uint16
InputPorts []uint16
OutboundSubnets []net.IPNet
Enabled *bool
Debug *bool
}
func (f Firewall) validate() (err error) {
if hasZeroPort(f.VPNInputPorts) {
return fmt.Errorf("VPN input ports: %w", ErrFirewallZeroPort)
}
if hasZeroPort(f.InputPorts) {
return fmt.Errorf("input ports: %w", ErrFirewallZeroPort)
}
return nil
}
func hasZeroPort(ports []uint16) (has bool) {
for _, port := range ports {
if port == 0 {
return true
}
}
return false
}
func (f *Firewall) copy() (copied Firewall) {
return Firewall{
VPNInputPorts: helpers.CopyUint16Slice(f.VPNInputPorts),
InputPorts: helpers.CopyUint16Slice(f.InputPorts),
OutboundSubnets: helpers.CopyIPNetSlice(f.OutboundSubnets),
Enabled: helpers.CopyBoolPtr(f.Enabled),
Debug: helpers.CopyBoolPtr(f.Debug),
}
}
// mergeWith merges the other settings into any
// unset field of the receiver settings object.
// It merges values of slices together, even if they
// are set in the receiver settings.
func (f *Firewall) mergeWith(other Firewall) {
f.VPNInputPorts = helpers.MergeUint16Slices(f.VPNInputPorts, other.VPNInputPorts)
f.InputPorts = helpers.MergeUint16Slices(f.InputPorts, other.InputPorts)
f.OutboundSubnets = helpers.MergeIPNetsSlices(f.OutboundSubnets, other.OutboundSubnets)
f.Enabled = helpers.MergeWithBool(f.Enabled, other.Enabled)
f.Debug = helpers.MergeWithBool(f.Debug, other.Debug)
}
// overrideWith overrides fields of the receiver
// settings object with any field set in the other
// settings.
func (f *Firewall) overrideWith(other Firewall) {
f.VPNInputPorts = helpers.OverrideWithUint16Slice(f.VPNInputPorts, other.VPNInputPorts)
f.InputPorts = helpers.OverrideWithUint16Slice(f.InputPorts, other.InputPorts)
f.OutboundSubnets = helpers.OverrideWithIPNetsSlice(f.OutboundSubnets, other.OutboundSubnets)
f.Enabled = helpers.OverrideWithBool(f.Enabled, other.Enabled)
f.Debug = helpers.OverrideWithBool(f.Debug, other.Debug)
}
func (f *Firewall) setDefaults() {
f.Enabled = helpers.DefaultBool(f.Enabled, true)
f.Debug = helpers.DefaultBool(f.Debug, false)
}
func (f Firewall) String() string {
return f.toLinesNode().String()
}
func (f Firewall) toLinesNode() (node *gotree.Node) {
node = gotree.New("Firewall settings:")
node.Appendf("Enabled: %s", helpers.BoolPtrToYesNo(f.Enabled))
if !*f.Enabled {
return node
}
if *f.Debug {
node.Appendf("Debug mode: on")
}
if len(f.VPNInputPorts) > 0 {
vpnInputPortsNode := node.Appendf("VPN input ports:")
for _, port := range f.VPNInputPorts {
vpnInputPortsNode.Appendf("%d", port)
}
}
if len(f.InputPorts) > 0 {
inputPortsNode := node.Appendf("Input ports:")
for _, port := range f.InputPorts {
inputPortsNode.Appendf("%d", port)
}
}
if len(f.OutboundSubnets) > 0 {
outboundSubnets := node.Appendf("Outbound subnets:")
for _, subnet := range f.OutboundSubnets {
outboundSubnets.Appendf("%s", subnet)
}
}
return node
}

View File

@@ -0,0 +1,83 @@
package settings
import (
"fmt"
"os"
"github.com/qdm12/gluetun/internal/configuration/settings/helpers"
"github.com/qdm12/gotree"
"github.com/qdm12/govalid/address"
)
// Health contains settings for the healthcheck and health server.
type Health struct {
// ServerAddress is the listening address
// for the health check server.
// It cannot be the empty string in the internal state.
ServerAddress string
// TargetAddress is the address (host or host:port)
// to TCP dial to periodically for the health check.
// It cannot be the empty string in the internal state.
TargetAddress string
VPN HealthyWait
}
func (h Health) Validate() (err error) {
uid := os.Getuid()
_, err = address.Validate(h.ServerAddress,
address.OptionListening(uid))
if err != nil {
return fmt.Errorf("%w: %s",
ErrServerAddressNotValid, err)
}
err = h.VPN.validate()
if err != nil {
return fmt.Errorf("health VPN settings validation failed: %w", err)
}
return nil
}
func (h *Health) copy() (copied Health) {
return Health{
ServerAddress: h.ServerAddress,
TargetAddress: h.TargetAddress,
VPN: h.VPN.copy(),
}
}
// MergeWith merges the other settings into any
// unset field of the receiver settings object.
func (h *Health) MergeWith(other Health) {
h.ServerAddress = helpers.MergeWithString(h.ServerAddress, other.ServerAddress)
h.TargetAddress = helpers.MergeWithString(h.TargetAddress, other.TargetAddress)
h.VPN.mergeWith(other.VPN)
}
// OverrideWith overrides fields of the receiver
// settings object with any field set in the other
// settings.
func (h *Health) OverrideWith(other Health) {
h.ServerAddress = helpers.OverrideWithString(h.ServerAddress, other.ServerAddress)
h.TargetAddress = helpers.OverrideWithString(h.TargetAddress, other.TargetAddress)
h.VPN.overrideWith(other.VPN)
}
func (h *Health) SetDefaults() {
h.ServerAddress = helpers.DefaultString(h.ServerAddress, "127.0.0.1:9999")
h.TargetAddress = helpers.DefaultString(h.TargetAddress, "github.com:443")
h.VPN.setDefaults()
}
func (h Health) String() string {
return h.toLinesNode().String()
}
func (h Health) toLinesNode() (node *gotree.Node) {
node = gotree.New("Health settings:")
node.Appendf("Server listening address: %s", h.ServerAddress)
node.Appendf("Target address: %s", h.TargetAddress)
node.AppendNode(h.VPN.toLinesNode("VPN"))
return node
}

View File

@@ -0,0 +1,66 @@
package settings
import (
"time"
"github.com/qdm12/gluetun/internal/configuration/settings/helpers"
"github.com/qdm12/gotree"
)
type HealthyWait struct {
// Initial is the initial duration to wait for the program
// to be healthy before taking action.
// It cannot be nil in the internal state.
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.
// It cannot be nil in the internal state.
Addition *time.Duration
}
func (h HealthyWait) validate() (err error) {
return nil
}
// mergeWith merges the other settings into any
// unset field of the receiver settings object.
func (h *HealthyWait) copy() (copied HealthyWait) {
return HealthyWait{
Initial: helpers.CopyDurationPtr(h.Initial),
Addition: helpers.CopyDurationPtr(h.Addition),
}
}
// mergeWith merges the other settings into any
// unset field of the receiver settings object.
func (h *HealthyWait) mergeWith(other HealthyWait) {
h.Initial = helpers.MergeWithDuration(h.Initial, other.Initial)
h.Addition = helpers.MergeWithDuration(h.Addition, other.Addition)
}
// overrideWith overrides fields of the receiver
// settings object with any field set in the other
// settings.
func (h *HealthyWait) overrideWith(other HealthyWait) {
h.Initial = helpers.OverrideWithDuration(h.Initial, other.Initial)
h.Addition = helpers.OverrideWithDuration(h.Addition, other.Addition)
}
func (h *HealthyWait) setDefaults() {
const initialDurationDefault = 6 * time.Second
const additionDurationDefault = 5 * time.Second
h.Initial = helpers.DefaultDuration(h.Initial, initialDurationDefault)
h.Addition = helpers.DefaultDuration(h.Addition, additionDurationDefault)
}
func (h HealthyWait) String() string {
return h.toLinesNode("Health").String()
}
func (h HealthyWait) toLinesNode(kind string) (node *gotree.Node) {
node = gotree.New(kind + " wait durations:")
node.Appendf("Initial duration: %s", *h.Initial)
node.Appendf("Additional duration: %s", *h.Addition)
return node
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -0,0 +1,4 @@
package settings
func boolPtr(b bool) *bool { return &b }
func uint8Ptr(n uint8) *uint8 { return &n }

View File

@@ -0,0 +1,112 @@
package settings
import (
"fmt"
"os"
"github.com/qdm12/gluetun/internal/configuration/settings/helpers"
"github.com/qdm12/gotree"
"github.com/qdm12/govalid/address"
)
// HTTPProxy contains settings to configure the HTTP proxy.
type HTTPProxy struct {
// User is the username to use for the HTTP proxy.
// It cannot be nil in the internal state.
User *string
// Password is the password to use for the HTTP proxy.
// It cannot be nil in the internal state.
Password *string
// ListeningAddress is the listening address
// of the HTTP proxy server.
// It cannot be the empty string in the internal state.
ListeningAddress string
// Enabled is true if the HTTP proxy server should run,
// and false otherwise. It cannot be nil in the
// internal state.
Enabled *bool
// Stealth is true if the HTTP proxy server should hide
// each request has been proxied to the destination.
// It cannot be nil in the internal state.
Stealth *bool
// Log is true if the HTTP proxy server should log
// each request/response. It cannot be nil in the
// internal state.
Log *bool
}
func (h HTTPProxy) validate() (err error) {
// Do not validate user and password
uid := os.Getuid()
_, err = address.Validate(h.ListeningAddress, address.OptionListening(uid))
if err != nil {
return fmt.Errorf("%w: %s",
ErrServerAddressNotValid, h.ListeningAddress)
}
return nil
}
func (h *HTTPProxy) copy() (copied HTTPProxy) {
return HTTPProxy{
User: helpers.CopyStringPtr(h.User),
Password: helpers.CopyStringPtr(h.Password),
ListeningAddress: h.ListeningAddress,
Enabled: helpers.CopyBoolPtr(h.Enabled),
Stealth: helpers.CopyBoolPtr(h.Stealth),
Log: helpers.CopyBoolPtr(h.Log),
}
}
// mergeWith merges the other settings into any
// unset field of the receiver settings object.
func (h *HTTPProxy) mergeWith(other HTTPProxy) {
h.User = helpers.MergeWithStringPtr(h.User, other.User)
h.Password = helpers.MergeWithStringPtr(h.Password, other.Password)
h.ListeningAddress = helpers.MergeWithString(h.ListeningAddress, other.ListeningAddress)
h.Enabled = helpers.MergeWithBool(h.Enabled, other.Enabled)
h.Stealth = helpers.MergeWithBool(h.Stealth, other.Stealth)
h.Log = helpers.MergeWithBool(h.Log, other.Log)
}
// overrideWith overrides fields of the receiver
// settings object with any field set in the other
// settings.
func (h *HTTPProxy) overrideWith(other HTTPProxy) {
h.User = helpers.OverrideWithStringPtr(h.User, other.User)
h.Password = helpers.OverrideWithStringPtr(h.Password, other.Password)
h.ListeningAddress = helpers.OverrideWithString(h.ListeningAddress, other.ListeningAddress)
h.Enabled = helpers.OverrideWithBool(h.Enabled, other.Enabled)
h.Stealth = helpers.OverrideWithBool(h.Stealth, other.Stealth)
h.Log = helpers.OverrideWithBool(h.Log, other.Log)
}
func (h *HTTPProxy) setDefaults() {
h.User = helpers.DefaultStringPtr(h.User, "")
h.Password = helpers.DefaultStringPtr(h.Password, "")
h.ListeningAddress = helpers.DefaultString(h.ListeningAddress, ":8888")
h.Enabled = helpers.DefaultBool(h.Enabled, false)
h.Stealth = helpers.DefaultBool(h.Stealth, false)
h.Log = helpers.DefaultBool(h.Log, false)
}
func (h HTTPProxy) String() string {
return h.toLinesNode().String()
}
func (h HTTPProxy) toLinesNode() (node *gotree.Node) {
node = gotree.New("HTTP proxy settings:")
node.Appendf("Enabled: %s", helpers.BoolPtrToYesNo(h.Enabled))
if !*h.Enabled {
return node
}
node.Appendf("Listening address: %s", h.ListeningAddress)
node.Appendf("User: %s", *h.User)
node.Appendf("Password: %s", helpers.ObfuscatePassword(*h.Password))
node.Appendf("Stealth mode: %s", helpers.BoolPtrToYesNo(h.Stealth))
node.Appendf("Log: %s", helpers.BoolPtrToYesNo(h.Log))
return node
}

View File

@@ -0,0 +1,51 @@
package settings
import (
"github.com/qdm12/gluetun/internal/configuration/settings/helpers"
"github.com/qdm12/golibs/logging"
"github.com/qdm12/gotree"
)
// Log contains settings to configure the logger.
type Log struct {
// Level is the log level of the logger.
// It cannot be nil in the internal state.
Level *logging.Level
}
func (l Log) validate() (err error) {
return nil
}
func (l *Log) copy() (copied Log) {
return Log{
Level: helpers.CopyLogLevelPtr(l.Level),
}
}
// mergeWith merges the other settings into any
// unset field of the receiver settings object.
func (l *Log) mergeWith(other Log) {
l.Level = helpers.MergeWithLogLevel(l.Level, other.Level)
}
// overrideWith overrides fields of the receiver
// settings object with any field set in the other
// settings.
func (l *Log) overrideWith(other Log) {
l.Level = helpers.OverrideWithLogLevel(l.Level, other.Level)
}
func (l *Log) setDefaults() {
l.Level = helpers.DefaultLogLevel(l.Level, logging.LevelInfo)
}
func (l Log) String() string {
return l.toLinesNode().String()
}
func (l Log) toLinesNode() (node *gotree.Node) {
node = gotree.New("Log settings:")
node.Appendf("Log level: %s", l.Level.String())
return node
}

View File

@@ -0,0 +1,320 @@
package settings
import (
"fmt"
"strings"
"github.com/qdm12/gluetun/internal/configuration/settings/helpers"
"github.com/qdm12/gluetun/internal/constants"
"github.com/qdm12/gluetun/internal/openvpn/parse"
"github.com/qdm12/gotree"
)
// OpenVPN contains settings to configure the OpenVPN client.
type OpenVPN struct {
// Version is the OpenVPN version to run.
// It can only be "2.4" or "2.5".
Version string
// User is the OpenVPN authentication username.
// It cannot be an empty string in the internal state
// if OpenVPN is used.
User string
// Password is the OpenVPN authentication password.
// It cannot be an empty string in the internal state
// if OpenVPN is used.
Password string
// ConfFile is a custom OpenVPN configuration file path.
// It can be set to the empty string for it to be ignored.
// It cannot be nil in the internal state.
ConfFile *string
// Ciphers is a list of ciphers to use for OpenVPN,
// different from the ones specified by the VPN
// service provider configuration files.
Ciphers []string
// Auth is an auth algorithm to use in OpenVPN instead
// of the one specified by the VPN service provider.
// It cannot be nil in the internal state.
// It is ignored if it is set to the empty string.
Auth *string
// ClientCrt is the OpenVPN client certificate.
// This is notably used by Cyberghost.
// It can be set to the empty string to be ignored.
// It cannot be nil in the internal state.
ClientCrt *string
// ClientKey is the OpenVPN client key.
// This is used by Cyberghost and VPN Unlimited.
// It can be set to the empty string to be ignored.
// It cannot be nil in the internal state.
ClientKey *string
// PIAEncPreset is the encryption preset for
// Private Internet Access. It can be set to an
// empty string for other providers.
PIAEncPreset *string
// IPv6 is set to true if IPv6 routing should be
// set to be tunnel in OpenVPN, and false otherwise.
// It cannot be nil in the internal state.
IPv6 *bool // TODO automate like with Wireguard
// MSSFix is the value (1 to 10000) to set for the
// mssfix option for OpenVPN. It is ignored if set to 0.
// It cannot be nil in the internal state.
MSSFix *uint16
// Interface is the OpenVPN device interface name.
// It cannot be an empty string in the internal state.
Interface string
// Root is true if OpenVPN is to be run as root,
// and false otherwise. It cannot be nil in the
// internal state.
Root *bool
// ProcUser is the OpenVPN process OS username
// to use. It cannot be nil in the internal state.
// This is set and injected at runtime.
// TODO only use ProcUser and not Root field.
ProcUser string
// Verbosity is the OpenVPN verbosity level from 0 to 6.
// It cannot be nil in the internal state.
Verbosity *int
// Flags is a slice of additional flags to be passed
// to the OpenVPN program.
Flags []string
}
func (o OpenVPN) validate(vpnProvider string) (err error) {
// Validate version
validVersions := []string{constants.Openvpn24, constants.Openvpn25}
if !helpers.IsOneOf(o.Version, validVersions...) {
return fmt.Errorf("%w: %q can only be one of %s",
ErrOpenVPNVersionIsNotValid, o.Version, strings.Join(validVersions, ", "))
}
isCustom := vpnProvider == constants.Custom
if !isCustom && o.User == "" {
return ErrOpenVPNUserIsEmpty
}
if !isCustom && o.Password == "" {
return ErrOpenVPNPasswordIsEmpty
}
// Validate ConfFile
if isCustom {
if *o.ConfFile == "" {
return fmt.Errorf("%w: no file path specified", ErrOpenVPNConfigFile)
}
err := helpers.FileExists(*o.ConfFile)
if err != nil {
return fmt.Errorf("%w: %s", ErrOpenVPNConfigFile, err)
}
}
// Check client certificate
switch vpnProvider {
case
constants.Cyberghost,
constants.VPNUnlimited:
if *o.ClientCrt == "" {
return ErrOpenVPNClientCertMissing
}
}
if *o.ClientCrt != "" {
_, err = parse.ExtractCert([]byte(*o.ClientCrt))
if err != nil {
return fmt.Errorf("%w: %s", ErrOpenVPNClientCertNotValid, err)
}
}
// Check client key
switch vpnProvider {
case
constants.Cyberghost,
constants.VPNUnlimited,
constants.Wevpn:
if *o.ClientKey == "" {
return ErrOpenVPNClientKeyMissing
}
}
if *o.ClientKey != "" {
_, err = parse.ExtractPrivateKey([]byte(*o.ClientKey))
if err != nil {
return fmt.Errorf("%w: %s", ErrOpenVPNClientKeyNotValid, err)
}
}
// Validate MSSFix
const maxMSSFix = 10000
if *o.MSSFix > maxMSSFix {
return fmt.Errorf("%w: %d is over the maximum value of %d",
ErrOpenVPNMSSFixIsTooHigh, *o.MSSFix, maxMSSFix)
}
if !regexpInterfaceName.MatchString(o.Interface) {
return fmt.Errorf("%w: '%s' does not match regex '%s'",
ErrOpenVPNInterfaceNotValid, o.Interface, regexpInterfaceName)
}
// Validate Verbosity
if *o.Verbosity < 0 || *o.Verbosity > 6 {
return fmt.Errorf("%w: %d can only be between 0 and 5",
ErrOpenVPNVerbosityIsOutOfBounds, o.Verbosity)
}
return nil
}
func (o *OpenVPN) copy() (copied OpenVPN) {
return OpenVPN{
Version: o.Version,
User: o.User,
Password: o.Password,
ConfFile: helpers.CopyStringPtr(o.ConfFile),
Ciphers: helpers.CopyStringSlice(o.Ciphers),
Auth: helpers.CopyStringPtr(o.Auth),
ClientCrt: helpers.CopyStringPtr(o.ClientCrt),
ClientKey: helpers.CopyStringPtr(o.ClientKey),
PIAEncPreset: helpers.CopyStringPtr(o.PIAEncPreset),
IPv6: helpers.CopyBoolPtr(o.IPv6),
MSSFix: helpers.CopyUint16Ptr(o.MSSFix),
Interface: o.Interface,
Root: helpers.CopyBoolPtr(o.Root),
ProcUser: o.ProcUser,
Verbosity: helpers.CopyIntPtr(o.Verbosity),
Flags: helpers.CopyStringSlice(o.Flags),
}
}
// mergeWith merges the other settings into any
// unset field of the receiver settings object.
func (o *OpenVPN) mergeWith(other OpenVPN) {
o.Version = helpers.MergeWithString(o.Version, other.Version)
o.User = helpers.MergeWithString(o.User, other.User)
o.Password = helpers.MergeWithString(o.Password, other.Password)
o.ConfFile = helpers.MergeWithStringPtr(o.ConfFile, other.ConfFile)
o.Ciphers = helpers.MergeStringSlices(o.Ciphers, other.Ciphers)
o.Auth = helpers.MergeWithStringPtr(o.Auth, other.Auth)
o.ClientCrt = helpers.MergeWithStringPtr(o.ClientCrt, other.ClientCrt)
o.ClientKey = helpers.MergeWithStringPtr(o.ClientKey, other.ClientKey)
o.PIAEncPreset = helpers.MergeWithStringPtr(o.PIAEncPreset, other.PIAEncPreset)
o.IPv6 = helpers.MergeWithBool(o.IPv6, other.IPv6)
o.MSSFix = helpers.MergeWithUint16(o.MSSFix, other.MSSFix)
o.Interface = helpers.MergeWithString(o.Interface, other.Interface)
o.Root = helpers.MergeWithBool(o.Root, other.Root)
o.ProcUser = helpers.MergeWithString(o.ProcUser, other.ProcUser)
o.Verbosity = helpers.MergeWithInt(o.Verbosity, other.Verbosity)
o.Flags = helpers.MergeStringSlices(o.Flags, other.Flags)
}
// overrideWith overrides fields of the receiver
// settings object with any field set in the other
// settings.
func (o *OpenVPN) overrideWith(other OpenVPN) {
o.Version = helpers.OverrideWithString(o.Version, other.Version)
o.User = helpers.OverrideWithString(o.User, other.User)
o.Password = helpers.OverrideWithString(o.Password, other.Password)
o.ConfFile = helpers.OverrideWithStringPtr(o.ConfFile, other.ConfFile)
o.Ciphers = helpers.OverrideWithStringSlice(o.Ciphers, other.Ciphers)
o.Auth = helpers.OverrideWithStringPtr(o.Auth, other.Auth)
o.ClientCrt = helpers.OverrideWithStringPtr(o.ClientCrt, other.ClientCrt)
o.ClientKey = helpers.OverrideWithStringPtr(o.ClientKey, other.ClientKey)
o.PIAEncPreset = helpers.OverrideWithStringPtr(o.PIAEncPreset, other.PIAEncPreset)
o.IPv6 = helpers.OverrideWithBool(o.IPv6, other.IPv6)
o.MSSFix = helpers.OverrideWithUint16(o.MSSFix, other.MSSFix)
o.Interface = helpers.OverrideWithString(o.Interface, other.Interface)
o.Root = helpers.OverrideWithBool(o.Root, other.Root)
o.ProcUser = helpers.OverrideWithString(o.ProcUser, other.ProcUser)
o.Verbosity = helpers.OverrideWithInt(o.Verbosity, other.Verbosity)
o.Flags = helpers.OverrideWithStringSlice(o.Flags, other.Flags)
}
func (o *OpenVPN) setDefaults(vpnProvider string) {
o.Version = helpers.DefaultString(o.Version, constants.Openvpn25)
if vpnProvider == constants.Mullvad {
o.Password = "m"
}
o.ConfFile = helpers.DefaultStringPtr(o.ConfFile, "")
o.Auth = helpers.DefaultStringPtr(o.Auth, "")
o.ClientCrt = helpers.DefaultStringPtr(o.ClientCrt, "")
o.ClientKey = helpers.DefaultStringPtr(o.ClientKey, "")
var defaultEncPreset string
if vpnProvider == constants.PrivateInternetAccess {
defaultEncPreset = constants.PIAEncryptionPresetStrong
}
o.PIAEncPreset = helpers.DefaultStringPtr(o.PIAEncPreset, defaultEncPreset)
o.IPv6 = helpers.DefaultBool(o.IPv6, false)
o.MSSFix = helpers.DefaultUint16(o.MSSFix, 0)
o.Interface = helpers.DefaultString(o.Interface, "tun0")
o.Root = helpers.DefaultBool(o.Root, true)
o.ProcUser = helpers.DefaultString(o.ProcUser, "root")
o.Verbosity = helpers.DefaultInt(o.Verbosity, 1)
}
func (o OpenVPN) String() string {
return o.toLinesNode().String()
}
func (o OpenVPN) toLinesNode() (node *gotree.Node) {
node = gotree.New("OpenVPN settings:")
node.Appendf("OpenVPN version: %s", o.Version)
node.Appendf("User: %s", helpers.ObfuscatePassword(o.User))
node.Appendf("Password: %s", helpers.ObfuscatePassword(o.Password))
if *o.ConfFile != "" {
node.Appendf("Custom configuration file: %s", *o.ConfFile)
}
if len(o.Ciphers) > 0 {
node.Appendf("Ciphers: %s", o.Ciphers)
}
if *o.Auth != "" {
node.Appendf("Auth: %s", *o.Auth)
}
if *o.ClientCrt != "" {
node.Appendf("Client crt: %s", helpers.ObfuscateData(*o.ClientCrt))
}
if *o.ClientKey != "" {
node.Appendf("Client key: %s", helpers.ObfuscateData(*o.ClientKey))
}
if *o.PIAEncPreset != "" {
node.Appendf("Private Internet Access encryption preset: %s", *o.PIAEncPreset)
}
node.Appendf("Tunnel IPv6: %s", helpers.BoolPtrToYesNo(o.IPv6))
if *o.MSSFix > 0 {
node.Appendf("MSS Fix: %d", *o.MSSFix)
}
if o.Interface != "" {
node.Appendf("Network interface: %s", o.Interface)
}
processUser := "root"
if !*o.Root {
processUser = "some non root user" // TODO
if o.ProcUser != "" {
processUser = o.ProcUser
}
}
node.Appendf("Run OpenVPN as: %s", processUser)
node.Appendf("Verbosity level: %d", *o.Verbosity)
if len(o.Flags) > 0 {
node.Appendf("Flags: %s", o.Flags)
}
return node
}
// WithDefaults is a shorthand using setDefaults.
// It's used in unit tests in other packages.
func (o OpenVPN) WithDefaults(provider string) OpenVPN {
o.setDefaults(provider)
return o
}

View File

@@ -0,0 +1,170 @@
package settings
import (
"fmt"
"github.com/qdm12/gluetun/internal/configuration/settings/helpers"
"github.com/qdm12/gluetun/internal/constants"
"github.com/qdm12/gotree"
)
type OpenVPNSelection struct {
// ConfFile is the custom configuration file path.
// It can be set to an empty string to indicate to
// NOT use a custom configuration file.
// It cannot be nil in the internal state.
ConfFile *string
// TCP is true if the OpenVPN protocol is TCP,
// and false for UDP.
// It cannot be nil in the internal state.
TCP *bool
// CustomPort is the OpenVPN server endpoint port.
// It can be set to 0 to indicate no custom port should
// be used. It cannot be nil in the internal state.
CustomPort *uint16 // HideMyAss, Mullvad, PIA, ProtonVPN, WeVPN, Windscribe
// PIAEncPreset is the encryption preset for
// Private Internet Access. It can be set to an
// empty string for other providers.
PIAEncPreset *string
}
func (o OpenVPNSelection) validate(vpnProvider string) (err error) {
// Validate ConfFile
if confFile := *o.ConfFile; confFile != "" {
err := helpers.FileExists(confFile)
if err != nil {
return fmt.Errorf("%w: %s", ErrOpenVPNConfigFile, err)
}
}
// Validate TCP
if *o.TCP && helpers.IsOneOf(vpnProvider,
constants.Perfectprivacy,
constants.Privado,
constants.Vyprvpn,
) {
return fmt.Errorf("%w: for VPN service provider %s",
ErrOpenVPNTCPNotSupported, vpnProvider)
}
// Validate CustomPort
if *o.CustomPort != 0 {
switch vpnProvider {
// no restriction on port
case constants.Cyberghost, constants.HideMyAss,
constants.PrivateInternetAccess, constants.Privatevpn,
constants.Protonvpn, constants.Torguard:
// no custom port allowed
case constants.Expressvpn, constants.Fastestvpn,
constants.Ipvanish, constants.Nordvpn,
constants.Privado, constants.Purevpn,
constants.Surfshark, constants.VPNUnlimited,
constants.Vyprvpn:
return fmt.Errorf("%w: for VPN service provider %s",
ErrOpenVPNCustomPortNotAllowed, vpnProvider)
default:
var allowedTCP, allowedUDP []uint16
switch vpnProvider {
case constants.Ivpn:
allowedTCP = []uint16{80, 443, 1143}
allowedUDP = []uint16{53, 1194, 2049, 2050}
case constants.Mullvad:
allowedTCP = []uint16{80, 443, 1401}
allowedUDP = []uint16{53, 1194, 1195, 1196, 1197, 1300, 1301, 1302, 1303, 1400}
case constants.Perfectprivacy:
allowedTCP = []uint16{44, 443, 4433}
allowedUDP = []uint16{44, 443, 4433}
case constants.Wevpn:
allowedTCP = []uint16{53, 1195, 1199, 2018}
allowedUDP = []uint16{80, 1194, 1198}
case constants.Windscribe:
allowedTCP = []uint16{21, 22, 80, 123, 143, 443, 587, 1194, 3306, 8080, 54783}
allowedUDP = []uint16{53, 80, 123, 443, 1194, 54783}
}
if *o.TCP && !helpers.Uint16IsOneOf(*o.CustomPort, allowedTCP) {
return fmt.Errorf("%w: %d for VPN service provider %s; %s",
ErrOpenVPNCustomPortNotAllowed, o.CustomPort, vpnProvider,
helpers.PortChoicesOrString(allowedTCP))
} else if !*o.TCP && !helpers.Uint16IsOneOf(*o.CustomPort, allowedUDP) {
return fmt.Errorf("%w: %d for VPN service provider %s; %s",
ErrOpenVPNCustomPortNotAllowed, o.CustomPort, vpnProvider,
helpers.PortChoicesOrString(allowedUDP))
}
}
}
// Validate EncPreset
if vpnProvider == constants.PrivateInternetAccess {
validEncryptionPresets := []string{
constants.PIAEncryptionPresetNone,
constants.PIAEncryptionPresetNormal,
constants.PIAEncryptionPresetStrong,
}
if !helpers.IsOneOf(*o.PIAEncPreset, validEncryptionPresets...) {
return fmt.Errorf("%w: %s; valid presets are %s",
ErrOpenVPNEncryptionPresetNotValid, *o.PIAEncPreset,
helpers.ChoicesOrString(validEncryptionPresets))
}
}
return nil
}
func (o *OpenVPNSelection) copy() (copied OpenVPNSelection) {
return OpenVPNSelection{
ConfFile: helpers.CopyStringPtr(o.ConfFile),
TCP: helpers.CopyBoolPtr(o.TCP),
CustomPort: helpers.CopyUint16Ptr(o.CustomPort),
PIAEncPreset: helpers.CopyStringPtr(o.PIAEncPreset),
}
}
func (o *OpenVPNSelection) mergeWith(other OpenVPNSelection) {
o.ConfFile = helpers.MergeWithStringPtr(o.ConfFile, other.ConfFile)
o.TCP = helpers.MergeWithBool(o.TCP, other.TCP)
o.CustomPort = helpers.MergeWithUint16(o.CustomPort, other.CustomPort)
o.PIAEncPreset = helpers.MergeWithStringPtr(o.PIAEncPreset, other.PIAEncPreset)
}
func (o *OpenVPNSelection) overrideWith(other OpenVPNSelection) {
o.ConfFile = helpers.OverrideWithStringPtr(o.ConfFile, other.ConfFile)
o.TCP = helpers.OverrideWithBool(o.TCP, other.TCP)
o.CustomPort = helpers.OverrideWithUint16(o.CustomPort, other.CustomPort)
o.PIAEncPreset = helpers.OverrideWithStringPtr(o.PIAEncPreset, other.PIAEncPreset)
}
func (o *OpenVPNSelection) setDefaults(vpnProvider string) {
o.ConfFile = helpers.DefaultStringPtr(o.ConfFile, "")
o.TCP = helpers.DefaultBool(o.TCP, false)
o.CustomPort = helpers.DefaultUint16(o.CustomPort, 0)
var defaultEncPreset string
if vpnProvider == constants.PrivateInternetAccess {
defaultEncPreset = constants.PIAEncryptionPresetStrong
}
o.PIAEncPreset = helpers.DefaultStringPtr(o.PIAEncPreset, defaultEncPreset)
}
func (o OpenVPNSelection) String() string {
return o.toLinesNode().String()
}
func (o OpenVPNSelection) toLinesNode() (node *gotree.Node) {
node = gotree.New("OpenVPN server selection settings:")
node.Appendf("Protocol: %s", helpers.TCPPtrToString(o.TCP))
if *o.CustomPort != 0 {
node.Appendf("Custom port: %d", *o.CustomPort)
}
if *o.PIAEncPreset != "" {
node.Appendf("Private Internet Access encryption preset: %s", *o.PIAEncPreset)
}
if *o.ConfFile != "" {
node.Appendf("Custom configuration file: %s", *o.ConfFile)
}
return node
}

View File

@@ -0,0 +1,89 @@
package settings
import (
"fmt"
"path/filepath"
"strings"
"github.com/qdm12/gluetun/internal/configuration/settings/helpers"
"github.com/qdm12/gluetun/internal/constants"
"github.com/qdm12/gotree"
)
// PortForwarding contains settings for port forwarding.
type PortForwarding struct {
// Enabled is true if port forwarding should be activated.
// It cannot be nil for the internal state.
Enabled *bool
// Filepath is the port forwarding status file path
// to use. It can be the empty string to indicate not
// to write to a file. It cannot be nil for the
// internal state
Filepath *string
}
func (p PortForwarding) validate(vpnProvider string) (err error) {
if !*p.Enabled {
return nil
}
// Validate Enabled
validProviders := []string{constants.PrivateInternetAccess}
if !helpers.IsOneOf(vpnProvider, validProviders...) {
return fmt.Errorf("%w: for provider %s, it is only available for %s",
ErrPortForwardingEnabled, vpnProvider, strings.Join(validProviders, ", "))
}
// Validate Filepath
if *p.Filepath != "" { // optional
_, err := filepath.Abs(*p.Filepath)
if err != nil {
return fmt.Errorf("%w: %s", ErrPortForwardingFilepathNotValid, err)
}
}
return nil
}
func (p *PortForwarding) copy() (copied PortForwarding) {
return PortForwarding{
Enabled: helpers.CopyBoolPtr(p.Enabled),
Filepath: helpers.CopyStringPtr(p.Filepath),
}
}
func (p *PortForwarding) mergeWith(other PortForwarding) {
p.Enabled = helpers.MergeWithBool(p.Enabled, other.Enabled)
p.Filepath = helpers.MergeWithStringPtr(p.Filepath, other.Filepath)
}
func (p *PortForwarding) overrideWith(other PortForwarding) {
p.Enabled = helpers.OverrideWithBool(p.Enabled, other.Enabled)
p.Filepath = helpers.OverrideWithStringPtr(p.Filepath, other.Filepath)
}
func (p *PortForwarding) setDefaults() {
p.Enabled = helpers.DefaultBool(p.Enabled, false)
p.Filepath = helpers.DefaultStringPtr(p.Filepath, "/tmp/gluetun/forwarded_port")
}
func (p PortForwarding) String() string {
return p.toLinesNode().String()
}
func (p PortForwarding) toLinesNode() (node *gotree.Node) {
if !*p.Enabled {
return nil
}
node = gotree.New("Automatic port forwarding settings:")
node.Appendf("Enabled: yes")
filepath := *p.Filepath
if filepath == "" {
filepath = "[not set]"
}
node.Appendf("Forwarded port file path: %s", filepath)
return node
}

View File

@@ -0,0 +1,19 @@
package settings
import (
"testing"
"github.com/stretchr/testify/assert"
)
func Test_PortForwarding_String(t *testing.T) {
t.Parallel()
settings := PortForwarding{
Enabled: boolPtr(false),
}
s := settings.String()
assert.Empty(t, s)
}

View File

@@ -0,0 +1,93 @@
package settings
import (
"fmt"
"github.com/qdm12/gluetun/internal/configuration/settings/helpers"
"github.com/qdm12/gluetun/internal/constants"
"github.com/qdm12/gluetun/internal/models"
"github.com/qdm12/gotree"
)
// Provider contains settings specific to a VPN provider.
type Provider struct {
// Name is the VPN service provider name.
// It cannot be nil in the internal state.
Name *string
// ServerSelection is the settings to
// select the VPN server.
ServerSelection ServerSelection
// PortForwarding is the settings about port forwarding.
PortForwarding PortForwarding
}
// TODO v4 remove pointer for receiver (because of Surfshark).
func (p *Provider) validate(vpnType string, allServers models.AllServers) (err error) {
// Validate Name
var validNames []string
if vpnType == constants.OpenVPN {
validNames = constants.AllProviders()
validNames = append(validNames, "pia") // Retro-compatibility
} else { // Wireguard
validNames = []string{
constants.Custom,
constants.Ivpn,
constants.Mullvad,
constants.Windscribe,
}
}
if !helpers.IsOneOf(*p.Name, validNames...) {
return fmt.Errorf("%w: %q can only be one of %s",
ErrVPNProviderNameNotValid, *p.Name, helpers.ChoicesOrString(validNames))
}
err = p.ServerSelection.validate(*p.Name, allServers)
if err != nil {
return fmt.Errorf("server selection settings validation failed: %w", err)
}
err = p.PortForwarding.validate(*p.Name)
if err != nil {
return fmt.Errorf("port forwarding settings validation failed: %w", err)
}
return nil
}
func (p *Provider) copy() (copied Provider) {
return Provider{
Name: helpers.CopyStringPtr(p.Name),
ServerSelection: p.ServerSelection.copy(),
PortForwarding: p.PortForwarding.copy(),
}
}
func (p *Provider) mergeWith(other Provider) {
p.Name = helpers.MergeWithStringPtr(p.Name, other.Name)
p.ServerSelection.mergeWith(other.ServerSelection)
p.PortForwarding.mergeWith(other.PortForwarding)
}
func (p *Provider) overrideWith(other Provider) {
p.Name = helpers.OverrideWithStringPtr(p.Name, other.Name)
p.ServerSelection.overrideWith(other.ServerSelection)
p.PortForwarding.overrideWith(other.PortForwarding)
}
func (p *Provider) setDefaults() {
p.Name = helpers.DefaultStringPtr(p.Name, constants.PrivateInternetAccess)
p.ServerSelection.setDefaults(*p.Name)
p.PortForwarding.setDefaults()
}
func (p Provider) String() string {
return p.toLinesNode().String()
}
func (p Provider) toLinesNode() (node *gotree.Node) {
node = gotree.New("VPN provider settings:")
node.Appendf("Name: %s", *p.Name)
node.AppendNode(p.ServerSelection.toLinesNode())
node.AppendNode(p.PortForwarding.toLinesNode())
return node
}

View File

@@ -0,0 +1,89 @@
package settings
import (
"fmt"
"path/filepath"
"time"
"github.com/qdm12/gluetun/internal/configuration/settings/helpers"
"github.com/qdm12/gotree"
)
// PublicIP contains settings for port forwarding.
type PublicIP struct {
// Period is the period to get the public IP address.
// It can be set to 0 to disable periodic checking.
// It cannot be nil for the internal state.
// TODO change to value and add enabled field
Period *time.Duration
// IPFilepath is the public IP address status file path
// to use. It can be the empty string to indicate not
// to write to a file. It cannot be nil for the
// internal state
IPFilepath *string
}
func (p PublicIP) validate() (err error) {
const minPeriod = 5 * time.Second
if *p.Period < minPeriod {
return fmt.Errorf("%w: %s must be at least %s",
ErrPublicIPPeriodTooShort, p.Period, minPeriod)
}
if *p.IPFilepath != "" { // optional
_, err := filepath.Abs(*p.IPFilepath)
if err != nil {
return fmt.Errorf("%w: %s", ErrPublicIPFilepathNotValid, err)
}
}
return nil
}
func (p *PublicIP) copy() (copied PublicIP) {
return PublicIP{
Period: helpers.CopyDurationPtr(p.Period),
IPFilepath: helpers.CopyStringPtr(p.IPFilepath),
}
}
func (p *PublicIP) mergeWith(other PublicIP) {
p.Period = helpers.MergeWithDuration(p.Period, other.Period)
p.IPFilepath = helpers.MergeWithStringPtr(p.IPFilepath, other.IPFilepath)
}
func (p *PublicIP) overrideWith(other PublicIP) {
p.Period = helpers.OverrideWithDuration(p.Period, other.Period)
p.IPFilepath = helpers.OverrideWithStringPtr(p.IPFilepath, other.IPFilepath)
}
func (p *PublicIP) setDefaults() {
const defaultPeriod = 12 * time.Hour
p.Period = helpers.DefaultDuration(p.Period, defaultPeriod)
p.IPFilepath = helpers.DefaultStringPtr(p.IPFilepath, "/tmp/gluetun/ip")
}
func (p PublicIP) String() string {
return p.toLinesNode().String()
}
func (p PublicIP) toLinesNode() (node *gotree.Node) {
node = gotree.New("Public IP settings:")
if *p.Period == 0 {
node.Appendf("Enabled: no")
return node
}
updatePeriod := "disabled"
if *p.Period > 0 {
updatePeriod = "every " + p.Period.String()
}
node.Appendf("Fetching: %s", updatePeriod)
if *p.IPFilepath != "" {
node.Appendf("IP file path: %s", *p.IPFilepath)
}
return node
}

View File

@@ -0,0 +1,71 @@
package settings
import (
"fmt"
"os"
"github.com/qdm12/gluetun/internal/configuration/settings/helpers"
"github.com/qdm12/gotree"
)
// ControlServer contains settings to customize the control server operation.
type ControlServer struct {
// Port is the listening port to use.
// It can be set to 0 to bind to a random port.
// It cannot be nil in the internal state.
// TODO change to address
Port *uint16
// Log can be true or false to enable logging on requests.
// It cannot be nil in the internal state.
Log *bool
}
func (c ControlServer) validate() (err error) {
uid := os.Getuid()
const maxPrivilegedPort uint16 = 1023
if uid != 0 && *c.Port <= maxPrivilegedPort {
return fmt.Errorf("%w: %d when running with user ID %d",
ErrControlServerPrivilegedPort, *c.Port, uid)
}
return nil
}
func (c *ControlServer) copy() (copied ControlServer) {
return ControlServer{
Port: helpers.CopyUint16Ptr(c.Port),
Log: helpers.CopyBoolPtr(c.Log),
}
}
// mergeWith merges the other settings into any
// unset field of the receiver settings object.
func (c *ControlServer) mergeWith(other ControlServer) {
c.Port = helpers.MergeWithUint16(c.Port, other.Port)
c.Log = helpers.MergeWithBool(c.Log, other.Log)
}
// overrideWith overrides fields of the receiver
// settings object with any field set in the other
// settings.
func (c *ControlServer) overrideWith(other ControlServer) {
c.Port = helpers.MergeWithUint16(c.Port, other.Port)
c.Log = helpers.MergeWithBool(c.Log, other.Log)
}
func (c *ControlServer) setDefaults() {
const defaultPort = 8000
c.Port = helpers.DefaultUint16(c.Port, defaultPort)
c.Log = helpers.DefaultBool(c.Log, true)
}
func (c ControlServer) String() string {
return c.toLinesNode().String()
}
func (c ControlServer) toLinesNode() (node *gotree.Node) {
node = gotree.New("Control server settings:")
node.Appendf("Listening port: %d", *c.Port)
node.Appendf("Logging: %s", helpers.BoolPtrToYesNo(c.Log))
return node
}

View File

@@ -0,0 +1,389 @@
package settings
import (
"fmt"
"net"
"strings"
"github.com/qdm12/gluetun/internal/configuration/settings/helpers"
"github.com/qdm12/gluetun/internal/constants"
"github.com/qdm12/gluetun/internal/models"
"github.com/qdm12/gotree"
)
type ServerSelection struct { //nolint:maligned
// VPN is the VPN type which can be 'openvpn'
// or 'wireguard'. It cannot be the empty string
// in the internal state.
VPN string
// TargetIP is the server endpoint IP address to use.
// It will override any IP address from the picked
// built-in server. It cannot be nil in the internal
// state, and can be set to an empty net.IP{} to indicate
// there is not target IP address to use.
TargetIP net.IP
// Counties is the list of countries to filter VPN servers with.
Countries []string
// Regions is the list of regions to filter VPN servers with.
Regions []string
// Cities is the list of cities to filter VPN servers with.
Cities []string
// ISPs is the list of ISP names to filter VPN servers with.
ISPs []string
// Names is the list of server names to filter VPN servers with.
Names []string
// Numbers is the list of server numbers to filter VPN servers with.
Numbers []uint16
// Hostnames is the list of hostnames to filter VPN servers with.
Hostnames []string
// OwnedOnly is true if only VPN provider owned servers
// should be filtered. This is used with Mullvad.
OwnedOnly *bool
// FreeOnly is true if only free VPN servers
// should be filtered. This is used with ProtonVPN.
FreeOnly *bool
// FreeOnly is true if only free VPN servers
// should be filtered. This is used with ProtonVPN.
StreamOnly *bool
// MultiHopOnly is true if only multihop VPN servers
// should be filtered. This is used with Surfshark.
MultiHopOnly *bool
// OpenVPN contains settings to select OpenVPN servers
// and the final connection.
OpenVPN OpenVPNSelection
// Wireguard contains settings to select Wireguard servers
// and the final connection.
Wireguard WireguardSelection
}
func (ss *ServerSelection) validate(vpnServiceProvider string,
allServers models.AllServers) (err error) {
switch ss.VPN {
case constants.OpenVPN, constants.Wireguard:
default:
return fmt.Errorf("%w: %s", ErrVPNTypeNotValid, ss.VPN)
}
var countryChoices, regionChoices, cityChoices,
ispChoices, nameChoices, hostnameChoices []string
switch vpnServiceProvider {
case constants.Custom:
case constants.Cyberghost:
servers := allServers.GetCyberghost()
countryChoices = constants.CyberghostCountryChoices(servers)
hostnameChoices = constants.CyberghostHostnameChoices(servers)
case constants.Expressvpn:
servers := allServers.GetExpressvpn()
countryChoices = constants.ExpressvpnCountriesChoices(servers)
cityChoices = constants.ExpressvpnCityChoices(servers)
hostnameChoices = constants.ExpressvpnHostnameChoices(servers)
case constants.Fastestvpn:
servers := allServers.GetFastestvpn()
countryChoices = constants.FastestvpnCountriesChoices(servers)
hostnameChoices = constants.FastestvpnHostnameChoices(servers)
case constants.HideMyAss:
servers := allServers.GetHideMyAss()
countryChoices = constants.HideMyAssCountryChoices(servers)
regionChoices = constants.HideMyAssRegionChoices(servers)
cityChoices = constants.HideMyAssCityChoices(servers)
hostnameChoices = constants.HideMyAssHostnameChoices(servers)
case constants.Ipvanish:
servers := allServers.GetIpvanish()
countryChoices = constants.IpvanishCountryChoices(servers)
cityChoices = constants.IpvanishCityChoices(servers)
hostnameChoices = constants.IpvanishHostnameChoices(servers)
case constants.Ivpn:
servers := allServers.GetIvpn()
countryChoices = constants.IvpnCountryChoices(servers)
cityChoices = constants.IvpnCityChoices(servers)
ispChoices = constants.IvpnISPChoices(servers)
hostnameChoices = constants.IvpnHostnameChoices(servers)
case constants.Mullvad:
servers := allServers.GetMullvad()
countryChoices = constants.MullvadCountryChoices(servers)
cityChoices = constants.MullvadCityChoices(servers)
ispChoices = constants.MullvadISPChoices(servers)
hostnameChoices = constants.MullvadHostnameChoices(servers)
case constants.Nordvpn:
servers := allServers.GetNordvpn()
regionChoices = constants.NordvpnRegionChoices(servers)
hostnameChoices = constants.NordvpnHostnameChoices(servers)
case constants.Perfectprivacy:
servers := allServers.GetPerfectprivacy()
cityChoices = constants.PerfectprivacyCityChoices(servers)
case constants.Privado:
servers := allServers.GetPrivado()
countryChoices = constants.PrivadoCountryChoices(servers)
regionChoices = constants.PrivadoRegionChoices(servers)
cityChoices = constants.PrivadoCityChoices(servers)
hostnameChoices = constants.PrivadoHostnameChoices(servers)
case constants.PrivateInternetAccess:
servers := allServers.GetPia()
regionChoices = constants.PIAGeoChoices(servers)
hostnameChoices = constants.PIAHostnameChoices(servers)
nameChoices = constants.PIANameChoices(servers)
case constants.Privatevpn:
servers := allServers.GetPrivatevpn()
countryChoices = constants.PrivatevpnCountryChoices(servers)
cityChoices = constants.PrivatevpnCityChoices(servers)
hostnameChoices = constants.PrivatevpnHostnameChoices(servers)
case constants.Protonvpn:
servers := allServers.GetProtonvpn()
countryChoices = constants.ProtonvpnCountryChoices(servers)
regionChoices = constants.ProtonvpnRegionChoices(servers)
cityChoices = constants.ProtonvpnCityChoices(servers)
nameChoices = constants.ProtonvpnNameChoices(servers)
hostnameChoices = constants.ProtonvpnHostnameChoices(servers)
case constants.Purevpn:
servers := allServers.GetPurevpn()
countryChoices = constants.PurevpnCountryChoices(servers)
regionChoices = constants.PurevpnRegionChoices(servers)
cityChoices = constants.PurevpnCityChoices(servers)
hostnameChoices = constants.PurevpnHostnameChoices(servers)
case constants.Surfshark:
servers := allServers.GetSurfshark()
countryChoices = constants.SurfsharkCountryChoices(servers)
cityChoices = constants.SurfsharkCityChoices(servers)
hostnameChoices = constants.SurfsharkHostnameChoices(servers)
regionChoices = constants.SurfsharkRegionChoices(servers)
// TODO v4 remove
regionChoices = append(regionChoices, constants.SurfsharkRetroLocChoices(servers)...)
if err := helpers.AreAllOneOf(ss.Regions, regionChoices); err != nil {
return fmt.Errorf("%w: %s", ErrRegionNotValid, err)
}
// Retro compatibility
// TODO remove in v4
*ss = surfsharkRetroRegion(*ss)
case constants.Torguard:
servers := allServers.GetTorguard()
countryChoices = constants.TorguardCountryChoices(servers)
cityChoices = constants.TorguardCityChoices(servers)
hostnameChoices = constants.TorguardHostnameChoices(servers)
case constants.VPNUnlimited:
servers := allServers.GetVPNUnlimited()
countryChoices = constants.VPNUnlimitedCountryChoices(servers)
cityChoices = constants.VPNUnlimitedCityChoices(servers)
hostnameChoices = constants.VPNUnlimitedHostnameChoices(servers)
case constants.Vyprvpn:
servers := allServers.GetVyprvpn()
regionChoices = constants.VyprvpnRegionChoices(servers)
case constants.Wevpn:
servers := allServers.GetWevpn()
cityChoices = constants.WevpnCityChoices(servers)
hostnameChoices = constants.WevpnHostnameChoices(servers)
case constants.Windscribe:
servers := allServers.GetWindscribe()
regionChoices = constants.WindscribeRegionChoices(servers)
cityChoices = constants.WindscribeCityChoices(servers)
hostnameChoices = constants.WindscribeHostnameChoices(servers)
default:
return fmt.Errorf("%w: %s", ErrVPNProviderNameNotValid, vpnServiceProvider)
}
err = validateServerFilters(*ss, countryChoices, regionChoices, cityChoices,
ispChoices, nameChoices, hostnameChoices)
if err != nil {
return err // already wrapped error
}
if ss.VPN == constants.OpenVPN {
err = ss.OpenVPN.validate(vpnServiceProvider)
if err != nil {
return fmt.Errorf("OpenVPN server selection settings validation failed: %w", err)
}
} else {
err = ss.Wireguard.validate(vpnServiceProvider)
if err != nil {
return fmt.Errorf("Wireguard server selection settings validation failed: %w", err)
}
}
return nil
}
// validateServerFilters validates filters against the choices given as arguments.
// Set an argument to nil to pass the check for a particular filter.
func validateServerFilters(settings ServerSelection,
countryChoices, regionChoices, cityChoices, ispChoices,
nameChoices, hostnameChoices []string) (err error) {
if countryChoices != nil {
if err := helpers.AreAllOneOf(settings.Countries, countryChoices); err != nil {
return fmt.Errorf("%w: %s", ErrCountryNotValid, err)
}
}
if regionChoices != nil {
if err := helpers.AreAllOneOf(settings.Regions, regionChoices); err != nil {
return fmt.Errorf("%w: %s", ErrRegionNotValid, err)
}
}
if cityChoices != nil {
if err := helpers.AreAllOneOf(settings.Cities, cityChoices); err != nil {
return fmt.Errorf("%w: %s", ErrCityNotValid, err)
}
}
if ispChoices != nil {
if err := helpers.AreAllOneOf(settings.ISPs, ispChoices); err != nil {
return fmt.Errorf("%w: %s", ErrISPNotValid, err)
}
}
if hostnameChoices != nil {
if err := helpers.AreAllOneOf(settings.Hostnames, hostnameChoices); err != nil {
return fmt.Errorf("%w: %s", ErrHostnameNotValid, err)
}
}
if nameChoices != nil {
if err := helpers.AreAllOneOf(settings.Names, nameChoices); err != nil {
return fmt.Errorf("%w: %s", ErrNameNotValid, err)
}
}
return nil
}
func (ss *ServerSelection) copy() (copied ServerSelection) {
return ServerSelection{
VPN: ss.VPN,
TargetIP: helpers.CopyIP(ss.TargetIP),
Countries: helpers.CopyStringSlice(ss.Countries),
Regions: helpers.CopyStringSlice(ss.Regions),
Cities: helpers.CopyStringSlice(ss.Cities),
ISPs: helpers.CopyStringSlice(ss.ISPs),
Hostnames: helpers.CopyStringSlice(ss.Hostnames),
Names: helpers.CopyStringSlice(ss.Names),
Numbers: helpers.CopyUint16Slice(ss.Numbers),
OwnedOnly: helpers.CopyBoolPtr(ss.OwnedOnly),
FreeOnly: helpers.CopyBoolPtr(ss.FreeOnly),
StreamOnly: helpers.CopyBoolPtr(ss.StreamOnly),
MultiHopOnly: helpers.CopyBoolPtr(ss.MultiHopOnly),
OpenVPN: ss.OpenVPN.copy(),
Wireguard: ss.Wireguard.copy(),
}
}
func (ss *ServerSelection) mergeWith(other ServerSelection) {
ss.VPN = helpers.MergeWithString(ss.VPN, other.VPN)
ss.TargetIP = helpers.MergeWithIP(ss.TargetIP, other.TargetIP)
ss.Countries = helpers.MergeStringSlices(ss.Countries, other.Countries)
ss.Regions = helpers.MergeStringSlices(ss.Regions, other.Regions)
ss.Cities = helpers.MergeStringSlices(ss.Cities, other.Cities)
ss.ISPs = helpers.MergeStringSlices(ss.ISPs, other.ISPs)
ss.Hostnames = helpers.MergeStringSlices(ss.Hostnames, other.Hostnames)
ss.Names = helpers.MergeStringSlices(ss.Names, other.Names)
ss.Numbers = helpers.MergeUint16Slices(ss.Numbers, other.Numbers)
ss.OwnedOnly = helpers.MergeWithBool(ss.OwnedOnly, other.OwnedOnly)
ss.FreeOnly = helpers.MergeWithBool(ss.FreeOnly, other.FreeOnly)
ss.StreamOnly = helpers.MergeWithBool(ss.StreamOnly, other.StreamOnly)
ss.MultiHopOnly = helpers.MergeWithBool(ss.MultiHopOnly, other.MultiHopOnly)
ss.OpenVPN.mergeWith(other.OpenVPN)
ss.Wireguard.mergeWith(other.Wireguard)
}
func (ss *ServerSelection) overrideWith(other ServerSelection) {
ss.VPN = helpers.OverrideWithString(ss.VPN, other.VPN)
ss.TargetIP = helpers.OverrideWithIP(ss.TargetIP, other.TargetIP)
ss.Countries = helpers.OverrideWithStringSlice(ss.Countries, other.Countries)
ss.Regions = helpers.OverrideWithStringSlice(ss.Regions, other.Regions)
ss.Cities = helpers.OverrideWithStringSlice(ss.Cities, other.Cities)
ss.ISPs = helpers.OverrideWithStringSlice(ss.ISPs, other.ISPs)
ss.Hostnames = helpers.OverrideWithStringSlice(ss.Hostnames, other.Hostnames)
ss.Names = helpers.OverrideWithStringSlice(ss.Names, other.Names)
ss.Numbers = helpers.OverrideWithUint16Slice(ss.Numbers, other.Numbers)
ss.OwnedOnly = helpers.OverrideWithBool(ss.OwnedOnly, other.OwnedOnly)
ss.FreeOnly = helpers.OverrideWithBool(ss.FreeOnly, other.FreeOnly)
ss.StreamOnly = helpers.OverrideWithBool(ss.StreamOnly, other.StreamOnly)
ss.MultiHopOnly = helpers.OverrideWithBool(ss.MultiHopOnly, other.MultiHopOnly)
ss.OpenVPN.overrideWith(other.OpenVPN)
ss.Wireguard.overrideWith(other.Wireguard)
}
func (ss *ServerSelection) setDefaults(vpnProvider string) {
ss.VPN = helpers.DefaultString(ss.VPN, constants.OpenVPN)
ss.TargetIP = helpers.DefaultIP(ss.TargetIP, net.IP{})
ss.OwnedOnly = helpers.DefaultBool(ss.OwnedOnly, false)
ss.FreeOnly = helpers.DefaultBool(ss.FreeOnly, false)
ss.StreamOnly = helpers.DefaultBool(ss.StreamOnly, false)
ss.MultiHopOnly = helpers.DefaultBool(ss.MultiHopOnly, false)
ss.OpenVPN.setDefaults(vpnProvider)
ss.Wireguard.setDefaults()
}
func (ss ServerSelection) String() string {
return ss.toLinesNode().String()
}
func (ss ServerSelection) toLinesNode() (node *gotree.Node) {
node = gotree.New("Server selection settings:")
node.Appendf("VPN type: %s", ss.VPN)
if len(ss.TargetIP) > 0 {
node.Appendf("Target IP address: %s", ss.TargetIP)
}
if len(ss.Countries) > 0 {
node.Appendf("Countries: %s", strings.Join(ss.Countries, ", "))
}
if len(ss.Regions) > 0 {
node.Appendf("Regions: %s", strings.Join(ss.Regions, ", "))
}
if len(ss.Cities) > 0 {
node.Appendf("Cities: %s", strings.Join(ss.Cities, ", "))
}
if len(ss.ISPs) > 0 {
node.Appendf("ISPs: %s", strings.Join(ss.ISPs, ", "))
}
if len(ss.Names) > 0 {
node.Appendf("Server names: %s", strings.Join(ss.Names, ", "))
}
if len(ss.Numbers) > 0 {
numbersNode := node.Appendf("Server numbers:")
for _, number := range ss.Numbers {
numbersNode.Appendf("%d", number)
}
}
if len(ss.Hostnames) > 0 {
node.Appendf("Hostnames: %s", strings.Join(ss.Hostnames, ", "))
}
if *ss.OwnedOnly {
node.Appendf("Owned only servers: yes")
}
if *ss.FreeOnly {
node.Appendf("Free only servers: yes")
}
if *ss.StreamOnly {
node.Appendf("Stream only servers: yes")
}
if *ss.MultiHopOnly {
node.Appendf("Multi-hop only servers: yes")
}
if ss.VPN == constants.OpenVPN {
node.AppendNode(ss.OpenVPN.toLinesNode())
} else {
node.AppendNode(ss.Wireguard.toLinesNode())
}
return node
}
// WithDefaults is a shorthand using setDefaults.
// It's used in unit tests in other packages.
func (ss ServerSelection) WithDefaults(provider string) ServerSelection {
ss.setDefaults(provider)
return ss
}

View File

@@ -0,0 +1,147 @@
package settings
import (
"fmt"
"github.com/qdm12/gluetun/internal/models"
"github.com/qdm12/gotree"
)
type Settings struct {
ControlServer ControlServer
DNS DNS
Firewall Firewall
Health Health
HTTPProxy HTTPProxy
Log Log
PublicIP PublicIP
Shadowsocks Shadowsocks
System System
Updater Updater
Version Version
VPN VPN
}
// Validate validates all the settings and returns an error
// if one of them is not valid.
// TODO v4 remove pointer for receiver (because of Surfshark).
func (s *Settings) Validate(allServers models.AllServers) (err error) {
nameToValidation := map[string]func() error{
"control server": s.ControlServer.validate,
"dns": s.DNS.validate,
"firewall": s.Firewall.validate,
"health": s.Health.Validate,
"http proxy": s.HTTPProxy.validate,
"log": s.Log.validate,
"public ip check": s.PublicIP.validate,
"shadowsocks": s.Shadowsocks.validate,
"system": s.System.validate,
"updater": s.Updater.Validate,
"version": s.Version.validate,
"VPN": func() error {
return s.VPN.validate(allServers)
},
}
for name, validation := range nameToValidation {
err = validation()
if err != nil {
return fmt.Errorf("failed validating %s settings: %w", name, err)
}
}
return nil
}
func (s *Settings) copy() (copied Settings) {
return Settings{
ControlServer: s.ControlServer.copy(),
DNS: s.DNS.Copy(),
Firewall: s.Firewall.copy(),
Health: s.Health.copy(),
HTTPProxy: s.HTTPProxy.copy(),
Log: s.Log.copy(),
PublicIP: s.PublicIP.copy(),
Shadowsocks: s.Shadowsocks.copy(),
System: s.System.copy(),
Updater: s.Updater.copy(),
Version: s.Version.copy(),
VPN: s.VPN.copy(),
}
}
func (s *Settings) MergeWith(other Settings) {
s.ControlServer.mergeWith(other.ControlServer)
s.DNS.mergeWith(other.DNS)
s.Firewall.mergeWith(other.Firewall)
s.Health.MergeWith(other.Health)
s.HTTPProxy.mergeWith(other.HTTPProxy)
s.Log.mergeWith(other.Log)
s.PublicIP.mergeWith(other.PublicIP)
s.Shadowsocks.mergeWith(other.Shadowsocks)
s.System.mergeWith(other.System)
s.Updater.mergeWith(other.Updater)
s.Version.mergeWith(other.Version)
s.VPN.mergeWith(other.VPN)
}
func (s *Settings) OverrideWith(other Settings,
allServers models.AllServers) (err error) {
patchedSettings := s.copy()
patchedSettings.ControlServer.overrideWith(other.ControlServer)
patchedSettings.DNS.overrideWith(other.DNS)
patchedSettings.Firewall.overrideWith(other.Firewall)
patchedSettings.Health.OverrideWith(other.Health)
patchedSettings.HTTPProxy.overrideWith(other.HTTPProxy)
patchedSettings.Log.overrideWith(other.Log)
patchedSettings.PublicIP.overrideWith(other.PublicIP)
patchedSettings.Shadowsocks.overrideWith(other.Shadowsocks)
patchedSettings.System.overrideWith(other.System)
patchedSettings.Updater.overrideWith(other.Updater)
patchedSettings.Version.overrideWith(other.Version)
patchedSettings.VPN.overrideWith(other.VPN)
err = patchedSettings.Validate(allServers)
if err != nil {
return err
}
*s = patchedSettings
return nil
}
func (s *Settings) SetDefaults() {
s.ControlServer.setDefaults()
s.DNS.setDefaults()
s.Firewall.setDefaults()
s.Health.SetDefaults()
s.HTTPProxy.setDefaults()
s.Log.setDefaults()
s.PublicIP.setDefaults()
s.Shadowsocks.setDefaults()
s.System.setDefaults()
s.Updater.SetDefaults()
s.Version.setDefaults()
s.VPN.setDefaults()
}
func (s Settings) String() string {
return s.toLinesNode().String()
}
func (s Settings) toLinesNode() (node *gotree.Node) {
node = gotree.New("Settings summary:")
node.AppendNode(s.VPN.toLinesNode())
node.AppendNode(s.DNS.toLinesNode())
node.AppendNode(s.Firewall.toLinesNode())
node.AppendNode(s.Log.toLinesNode())
node.AppendNode(s.Health.toLinesNode())
node.AppendNode(s.Shadowsocks.toLinesNode())
node.AppendNode(s.HTTPProxy.toLinesNode())
node.AppendNode(s.ControlServer.toLinesNode())
node.AppendNode(s.System.toLinesNode())
node.AppendNode(s.PublicIP.toLinesNode())
node.AppendNode(s.Updater.toLinesNode())
node.AppendNode(s.Version.toLinesNode())
return node
}

View File

@@ -0,0 +1,101 @@
package settings
import (
"testing"
"github.com/stretchr/testify/assert"
)
func Test_Settings_String(t *testing.T) {
t.Parallel()
withDefaults := func(s Settings) Settings {
s.SetDefaults()
return s
}
testCases := map[string]struct {
settings Settings
s string
}{
"default settings": {
settings: withDefaults(Settings{}),
s: `Settings summary:
├── VPN settings:
| ├── VPN provider settings:
| | ├── Name: private internet access
| | └── Server selection settings:
| | ├── VPN type: openvpn
| | └── OpenVPN server selection settings:
| | ├── Protocol: UDP
| | └── Private Internet Access encryption preset: strong
| └── OpenVPN settings:
| ├── OpenVPN version: 2.5
| ├── User: [not set]
| ├── Password: [not set]
| ├── Private Internet Access encryption preset: strong
| ├── Tunnel IPv6: no
| ├── Network interface: tun0
| ├── Run OpenVPN as: root
| └── Verbosity level: 1
├── DNS settings:
| ├── DNS server address to use: 127.0.0.1
| ├── Keep existing nameserver(s): no
| └── DNS over TLS settings:
| ├── Enabled: yes
| ├── Update period: every 24h0m0s
| ├── Unbound settings:
| | ├── Authoritative servers:
| | | └── Cloudflare
| | ├── Caching: yes
| | ├── IPv6: no
| | ├── Verbosity level: 1
| | ├── Verbosity details level: 0
| | ├── Validation log level: 0
| | ├── System user: root
| | └── Allowed networks:
| | ├── 0.0.0.0/0
| | └── ::/0
| └── DNS filtering settings:
| ├── Block malicious: yes
| ├── Block ads: no
| └── Block surveillance: yes
├── Firewall settings:
| └── Enabled: yes
├── Log settings:
| └── Log level: INFO
├── Health settings:
| ├── Server listening address: 127.0.0.1:9999
| ├── Target address: github.com:443
| └── VPN wait durations:
| ├── Initial duration: 6s
| └── Additional duration: 5s
├── Shadowsocks server settings:
| └── Enabled: no
├── HTTP proxy settings:
| └── Enabled: no
├── Control server settings:
| ├── Listening port: 8000
| └── Logging: yes
├── OS Alpine settings:
| ├── Process UID: 1000
| └── Process GID: 1000
├── Public IP settings:
| ├── Fetching: every 12h0m0s
| └── IP file path: /tmp/gluetun/ip
└── Version settings:
└── Enabled: yes`,
},
}
for name, testCase := range testCases {
testCase := testCase
t.Run(name, func(t *testing.T) {
t.Parallel()
s := testCase.settings.String()
assert.Equal(t, testCase.s, s)
})
}
}

View File

@@ -0,0 +1,68 @@
package settings
import (
"github.com/qdm12/gluetun/internal/configuration/settings/helpers"
"github.com/qdm12/gotree"
"github.com/qdm12/ss-server/pkg/tcpudp"
)
// Shadowsocks contains settings to configure the Shadowsocks server.
type Shadowsocks struct {
// Enabled is true if the server should be running.
// It defaults to false, and cannot be nil in the internal state.
Enabled *bool
// Settings are settings for the TCP+UDP server.
tcpudp.Settings
}
func (s Shadowsocks) validate() (err error) {
return s.Settings.Validate()
}
func (s *Shadowsocks) copy() (copied Shadowsocks) {
return Shadowsocks{
Enabled: helpers.CopyBoolPtr(s.Enabled),
Settings: s.Settings.Copy(),
}
}
// mergeWith merges the other settings into any
// unset field of the receiver settings object.
func (s *Shadowsocks) mergeWith(other Shadowsocks) {
s.Enabled = helpers.MergeWithBool(s.Enabled, other.Enabled)
s.Settings.MergeWith(other.Settings)
}
// overrideWith overrides fields of the receiver
// settings object with any field set in the other
// settings.
func (s *Shadowsocks) overrideWith(other Shadowsocks) {
s.Enabled = helpers.OverrideWithBool(s.Enabled, other.Enabled)
s.Settings.OverrideWith(other.Settings)
}
func (s *Shadowsocks) setDefaults() {
s.Enabled = helpers.DefaultBool(s.Enabled, false)
s.Settings.SetDefaults()
}
func (s Shadowsocks) String() string {
return s.toLinesNode().String()
}
func (s Shadowsocks) toLinesNode() (node *gotree.Node) {
node = gotree.New("Shadowsocks server settings:")
node.Appendf("Enabled: %s", helpers.BoolPtrToYesNo(s.Enabled))
if !*s.Enabled {
return node
}
// TODO have ToLinesNode in qdm12/ss-server
node.Appendf("Listening address: %s", s.Address)
node.Appendf("Cipher: %s", s.CipherName)
node.Appendf("Password: %s", helpers.ObfuscatePassword(*s.Password))
node.Appendf("Log addresses: %s", helpers.BoolPtrToYesNo(s.LogAddresses))
return node
}

View File

@@ -0,0 +1,56 @@
package settings
import (
"strings"
"github.com/qdm12/gluetun/internal/constants"
"github.com/qdm12/gluetun/internal/models"
)
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,61 @@
package settings
import (
"github.com/qdm12/gluetun/internal/configuration/settings/helpers"
"github.com/qdm12/gotree"
)
// System contains settings to configure system related elements.
type System struct {
PUID *uint16
PGID *uint16
Timezone string
}
// Validate validates System settings.
func (s System) validate() (err error) {
return nil
}
func (s *System) copy() (copied System) {
return System{
PUID: helpers.CopyUint16Ptr(s.PUID),
PGID: helpers.CopyUint16Ptr(s.PGID),
Timezone: s.Timezone,
}
}
func (s *System) mergeWith(other System) {
s.PUID = helpers.MergeWithUint16(s.PUID, other.PUID)
s.PGID = helpers.MergeWithUint16(s.PGID, other.PGID)
s.Timezone = helpers.MergeWithString(s.Timezone, other.Timezone)
}
func (s *System) overrideWith(other System) {
s.PUID = helpers.OverrideWithUint16(s.PUID, other.PUID)
s.PGID = helpers.OverrideWithUint16(s.PGID, other.PGID)
s.Timezone = helpers.OverrideWithString(s.Timezone, other.Timezone)
}
func (s *System) setDefaults() {
const defaultID = 1000
s.PUID = helpers.DefaultUint16(s.PUID, defaultID)
s.PGID = helpers.DefaultUint16(s.PGID, defaultID)
}
func (s System) String() string {
return s.toLinesNode().String()
}
func (s System) toLinesNode() (node *gotree.Node) {
node = gotree.New("OS Alpine settings:")
node.Appendf("Process UID: %d", *s.PUID)
node.Appendf("Process GID: %d", *s.PGID)
if s.Timezone != "" {
node.Appendf("Timezone: %s", s.Timezone)
}
return node
}

View File

@@ -0,0 +1,193 @@
package settings
import (
"errors"
"fmt"
"net"
"github.com/qdm12/dns/pkg/provider"
"github.com/qdm12/dns/pkg/unbound"
"github.com/qdm12/gluetun/internal/configuration/settings/helpers"
"github.com/qdm12/gotree"
"inet.af/netaddr"
)
// Unbound is settings for the Unbound program.
type Unbound struct {
Providers []string
Caching *bool
IPv6 *bool
VerbosityLevel *uint8
VerbosityDetailsLevel *uint8
ValidationLogLevel *uint8
Username string
Allowed []netaddr.IPPrefix
}
func (u *Unbound) setDefaults() {
if len(u.Providers) == 0 {
u.Providers = []string{
provider.Cloudflare().String(),
}
}
u.Caching = helpers.DefaultBool(u.Caching, true)
u.IPv6 = helpers.DefaultBool(u.IPv6, false)
const defaultVerbosityLevel = 1
u.VerbosityLevel = helpers.DefaultUint8(u.VerbosityLevel, defaultVerbosityLevel)
const defaultVerbosityDetailsLevel = 0
u.VerbosityDetailsLevel = helpers.DefaultUint8(u.VerbosityDetailsLevel, defaultVerbosityDetailsLevel)
const defaultValidationLogLevel = 0
u.ValidationLogLevel = helpers.DefaultUint8(u.ValidationLogLevel, defaultValidationLogLevel)
if u.Allowed == nil {
u.Allowed = []netaddr.IPPrefix{
netaddr.IPPrefixFrom(netaddr.IPv4(0, 0, 0, 0), 0),
netaddr.IPPrefixFrom(netaddr.IPv6Raw([16]byte{}), 0),
}
}
u.Username = helpers.DefaultString(u.Username, "root")
}
var (
ErrUnboundVerbosityLevelNotValid = errors.New("Unbound verbosity level is not valid")
ErrUnboundVerbosityDetailsLevelNotValid = errors.New("Unbound verbosity details level is not valid")
ErrUnboundValidationLogLevelNotValid = errors.New("Unbound validation log level is not valid")
)
func (u Unbound) validate() (err error) {
for _, s := range u.Providers {
_, err := provider.Parse(s)
if err != nil {
return err
}
}
const maxVerbosityLevel = 5
if *u.VerbosityLevel > maxVerbosityLevel {
return fmt.Errorf("%w: %d must be between 0 and %d",
ErrUnboundVerbosityLevelNotValid,
*u.VerbosityLevel,
maxVerbosityLevel)
}
const maxVerbosityDetailsLevel = 4
if *u.VerbosityDetailsLevel > maxVerbosityDetailsLevel {
return fmt.Errorf("%w: %d must be between 0 and %d",
ErrUnboundVerbosityDetailsLevelNotValid,
*u.VerbosityDetailsLevel,
maxVerbosityDetailsLevel)
}
const maxValidationLogLevel = 2
if *u.ValidationLogLevel > maxValidationLogLevel {
return fmt.Errorf("%w: %d must be between 0 and %d",
ErrUnboundValidationLogLevelNotValid,
*u.ValidationLogLevel, maxValidationLogLevel)
}
return nil
}
func (u Unbound) copy() (copied Unbound) {
return Unbound{
Providers: helpers.CopyStringSlice(u.Providers),
Caching: helpers.CopyBoolPtr(u.Caching),
IPv6: helpers.CopyBoolPtr(u.IPv6),
VerbosityLevel: helpers.CopyUint8Ptr(u.VerbosityLevel),
VerbosityDetailsLevel: helpers.CopyUint8Ptr(u.VerbosityDetailsLevel),
ValidationLogLevel: helpers.CopyUint8Ptr(u.ValidationLogLevel),
Username: u.Username,
Allowed: helpers.CopyIPPrefixSlice(u.Allowed),
}
}
func (u *Unbound) mergeWith(other Unbound) {
u.Providers = helpers.MergeStringSlices(u.Providers, other.Providers)
u.Caching = helpers.MergeWithBool(u.Caching, other.Caching)
u.IPv6 = helpers.MergeWithBool(u.IPv6, other.IPv6)
u.VerbosityLevel = helpers.MergeWithUint8(u.VerbosityLevel, other.VerbosityLevel)
u.VerbosityDetailsLevel = helpers.MergeWithUint8(u.VerbosityDetailsLevel, other.VerbosityDetailsLevel)
u.ValidationLogLevel = helpers.MergeWithUint8(u.ValidationLogLevel, other.ValidationLogLevel)
u.Username = helpers.MergeWithString(u.Username, other.Username)
u.Allowed = helpers.MergeIPPrefixesSlices(u.Allowed, other.Allowed)
}
func (u *Unbound) overrideWith(other Unbound) {
u.Providers = helpers.OverrideWithStringSlice(u.Providers, other.Providers)
u.Caching = helpers.OverrideWithBool(u.Caching, other.Caching)
u.IPv6 = helpers.OverrideWithBool(u.IPv6, other.IPv6)
u.VerbosityLevel = helpers.OverrideWithUint8(u.VerbosityLevel, other.VerbosityLevel)
u.VerbosityDetailsLevel = helpers.OverrideWithUint8(u.VerbosityDetailsLevel, other.VerbosityDetailsLevel)
u.ValidationLogLevel = helpers.OverrideWithUint8(u.ValidationLogLevel, other.ValidationLogLevel)
u.Username = helpers.OverrideWithString(u.Username, other.Username)
u.Allowed = helpers.OverrideWithIPPrefixesSlice(u.Allowed, other.Allowed)
}
func (u Unbound) ToUnboundFormat() (settings unbound.Settings, err error) {
providers := make([]provider.Provider, len(u.Providers))
for i := range providers {
providers[i], err = provider.Parse(u.Providers[i])
if err != nil {
return settings, err
}
}
const port = 53
return unbound.Settings{
ListeningPort: port,
IPv4: true,
Providers: providers,
Caching: *u.Caching,
IPv6: *u.IPv6,
VerbosityLevel: *u.VerbosityLevel,
VerbosityDetailsLevel: *u.VerbosityDetailsLevel,
ValidationLogLevel: *u.ValidationLogLevel,
AccessControl: unbound.AccessControlSettings{
Allowed: u.Allowed,
},
Username: u.Username,
}, nil
}
func (u Unbound) GetFirstPlaintextIPv4() (ipv4 net.IP, err error) {
s := u.Providers[0]
provider, err := provider.Parse(s)
if err != nil {
return nil, err
}
return provider.DNS().IPv4[0], nil
}
func (u Unbound) String() string {
return u.toLinesNode().String()
}
func (u Unbound) toLinesNode() (node *gotree.Node) {
node = gotree.New("Unbound settings:")
authServers := node.Appendf("Authoritative servers:")
for _, provider := range u.Providers {
authServers.Appendf(provider)
}
node.Appendf("Caching: %s", helpers.BoolPtrToYesNo(u.Caching))
node.Appendf("IPv6: %s", helpers.BoolPtrToYesNo(u.IPv6))
node.Appendf("Verbosity level: %d", *u.VerbosityLevel)
node.Appendf("Verbosity details level: %d", *u.VerbosityDetailsLevel)
node.Appendf("Validation log level: %d", *u.ValidationLogLevel)
node.Appendf("System user: %s", u.Username)
allowedNetworks := node.Appendf("Allowed networks:")
for _, network := range u.Allowed {
allowedNetworks.Appendf(network.String())
}
return node
}

View File

@@ -0,0 +1,43 @@
package settings
import (
"encoding/json"
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"inet.af/netaddr"
)
func Test_Unbound_JSON(t *testing.T) {
t.Parallel()
settings := Unbound{
Providers: []string{"cloudflare"},
Caching: boolPtr(true),
IPv6: boolPtr(false),
VerbosityLevel: uint8Ptr(1),
VerbosityDetailsLevel: nil,
ValidationLogLevel: uint8Ptr(0),
Username: "user",
Allowed: []netaddr.IPPrefix{
netaddr.IPPrefixFrom(netaddr.IPv4(0, 0, 0, 0), 0),
netaddr.IPPrefixFrom(netaddr.IPv6Raw([16]byte{}), 0),
},
}
b, err := json.Marshal(settings)
require.NoError(t, err)
const expected = `{"Providers":["cloudflare"],"Caching":true,"IPv6":false,` +
`"VerbosityLevel":1,"VerbosityDetailsLevel":null,"ValidationLogLevel":0,` +
`"Username":"user","Allowed":["0.0.0.0/0","::/0"]}`
assert.Equal(t, expected, string(b))
var resultSettings Unbound
err = json.Unmarshal(b, &resultSettings)
require.NoError(t, err)
assert.Equal(t, settings, resultSettings)
}

View File

@@ -0,0 +1,117 @@
package settings
import (
"fmt"
"net"
"strings"
"time"
"github.com/qdm12/gluetun/internal/configuration/settings/helpers"
"github.com/qdm12/gluetun/internal/constants"
"github.com/qdm12/gotree"
)
// Updater contains settings to configure the VPN
// server information updater.
type Updater struct {
// Period is the period for which the updater
// should run. It can be set to 0 to disable the
// updater. It cannot be nil in the internal state.
// TODO change to value and add Enabled field.
Period *time.Duration
// DNSAddress is the DNS server address to use
// to resolve VPN server hostnames to IP addresses.
// It cannot be nil in the internal state.
DNSAddress net.IP
// Providers is the list of VPN service providers
// to update server information for.
Providers []string
// CLI is to precise the updater is running in CLI
// mode. This is set automatically and cannot be set
// by settings sources. It cannot be nil in the
// internal state.
CLI *bool
}
func (u Updater) Validate() (err error) {
const minPeriod = time.Minute
if *u.Period > 0 && *u.Period < minPeriod {
return fmt.Errorf("%w: %d must be larger than %s",
ErrUpdaterPeriodTooSmall, *u.Period, minPeriod)
}
for i, provider := range u.Providers {
valid := false
for _, validProvider := range constants.AllProviders() {
if validProvider == constants.Custom {
continue
}
if provider == validProvider {
valid = true
break
}
}
if !valid {
return fmt.Errorf("%w: %s at index %d",
ErrVPNProviderNameNotValid, provider, i)
}
}
return nil
}
func (u *Updater) copy() (copied Updater) {
return Updater{
Period: helpers.CopyDurationPtr(u.Period),
DNSAddress: helpers.CopyIP(u.DNSAddress),
Providers: helpers.CopyStringSlice(u.Providers),
CLI: u.CLI,
}
}
// mergeWith merges the other settings into any
// unset field of the receiver settings object.
func (u *Updater) mergeWith(other Updater) {
u.Period = helpers.MergeWithDuration(u.Period, other.Period)
u.DNSAddress = helpers.MergeWithIP(u.DNSAddress, other.DNSAddress)
u.Providers = helpers.MergeStringSlices(u.Providers, other.Providers)
u.CLI = helpers.MergeWithBool(u.CLI, other.CLI)
}
// overrideWith overrides fields of the receiver
// settings object with any field set in the other
// settings.
func (u *Updater) overrideWith(other Updater) {
u.Period = helpers.OverrideWithDuration(u.Period, other.Period)
u.DNSAddress = helpers.OverrideWithIP(u.DNSAddress, other.DNSAddress)
u.Providers = helpers.OverrideWithStringSlice(u.Providers, other.Providers)
u.CLI = helpers.MergeWithBool(u.CLI, other.CLI)
}
func (u *Updater) SetDefaults() {
u.Period = helpers.DefaultDuration(u.Period, 0)
u.DNSAddress = helpers.DefaultIP(u.DNSAddress, net.IPv4(1, 1, 1, 1))
u.CLI = helpers.DefaultBool(u.CLI, false)
}
func (u Updater) String() string {
return u.toLinesNode().String()
}
func (u Updater) toLinesNode() (node *gotree.Node) {
if *u.Period == 0 {
return nil
}
node = gotree.New("Server data updater settings:")
node.Appendf("Update period: %s", *u.Period)
node.Appendf("DNS address: %s", u.DNSAddress)
node.Appendf("Providers to update: %s", strings.Join(u.Providers, ", "))
if *u.CLI {
node.Appendf("CLI mode: enabled")
}
return node
}

View File

@@ -0,0 +1,53 @@
package settings
import (
"github.com/qdm12/gluetun/internal/configuration/settings/helpers"
"github.com/qdm12/gotree"
)
// Version contains settings to configure the version
// information fetcher.
type Version struct {
// Enabled is true if the version information should
// be fetched from Github.
Enabled *bool
}
func (v Version) validate() (err error) {
return nil
}
func (v *Version) copy() (copied Version) {
return Version{
Enabled: helpers.CopyBoolPtr(v.Enabled),
}
}
// mergeWith merges the other settings into any
// unset field of the receiver settings object.
func (v *Version) mergeWith(other Version) {
v.Enabled = helpers.MergeWithBool(v.Enabled, other.Enabled)
}
// overrideWith overrides fields of the receiver
// settings object with any field set in the other
// settings.
func (v *Version) overrideWith(other Version) {
v.Enabled = helpers.OverrideWithBool(v.Enabled, other.Enabled)
}
func (v *Version) setDefaults() {
v.Enabled = helpers.DefaultBool(v.Enabled, true)
}
func (v Version) String() string {
return v.toLinesNode().String()
}
func (v Version) toLinesNode() (node *gotree.Node) {
node = gotree.New("Version settings:")
node.Appendf("Enabled: %s", helpers.BoolPtrToYesNo(v.Enabled))
return node
}

View File

@@ -0,0 +1,98 @@
package settings
import (
"fmt"
"strings"
"github.com/qdm12/gluetun/internal/configuration/settings/helpers"
"github.com/qdm12/gluetun/internal/constants"
"github.com/qdm12/gluetun/internal/models"
"github.com/qdm12/gotree"
)
type VPN struct {
// Type is the VPN type and can only be
// 'openvpn' or 'wireguard'. It cannot be the
// empty string in the internal state.
Type string
Provider Provider
OpenVPN OpenVPN
Wireguard Wireguard
}
// TODO v4 remove pointer for receiver (because of Surfshark).
func (v *VPN) validate(allServers models.AllServers) (err error) {
// Validate Type
validVPNTypes := []string{constants.OpenVPN, constants.Wireguard}
if !helpers.IsOneOf(v.Type, validVPNTypes...) {
return fmt.Errorf("%w: %q and can only be one of %s",
ErrVPNTypeNotValid, v.Type, strings.Join(validVPNTypes, ", "))
}
err = v.Provider.validate(v.Type, allServers)
if err != nil {
return fmt.Errorf("provider settings validation failed: %w", err)
}
if v.Type == constants.OpenVPN {
err := v.OpenVPN.validate(*v.Provider.Name)
if err != nil {
return fmt.Errorf("OpenVPN settings validation failed: %w", err)
}
} else {
err := v.Wireguard.validate(*v.Provider.Name)
if err != nil {
return fmt.Errorf("Wireguard settings validation failed: %w", err)
}
}
return nil
}
func (v *VPN) copy() (copied VPN) {
return VPN{
Type: v.Type,
Provider: v.Provider.copy(),
OpenVPN: v.OpenVPN.copy(),
Wireguard: v.Wireguard.copy(),
}
}
func (v *VPN) mergeWith(other VPN) {
v.Type = helpers.MergeWithString(v.Type, other.Type)
v.Provider.mergeWith(other.Provider)
v.OpenVPN.mergeWith(other.OpenVPN)
v.Wireguard.mergeWith(other.Wireguard)
}
func (v *VPN) overrideWith(other VPN) {
v.Type = helpers.OverrideWithString(v.Type, other.Type)
v.Provider.overrideWith(other.Provider)
v.OpenVPN.overrideWith(other.OpenVPN)
v.Wireguard.overrideWith(other.Wireguard)
}
func (v *VPN) setDefaults() {
v.Type = helpers.DefaultString(v.Type, constants.OpenVPN)
v.Provider.setDefaults()
v.OpenVPN.setDefaults(*v.Provider.Name)
v.Wireguard.setDefaults()
}
func (v VPN) String() string {
return v.toLinesNode().String()
}
func (v VPN) toLinesNode() (node *gotree.Node) {
node = gotree.New("VPN settings:")
node.AppendNode(v.Provider.toLinesNode())
if v.Type == constants.OpenVPN {
node.AppendNode(v.OpenVPN.toLinesNode())
} else {
node.AppendNode(v.Wireguard.toLinesNode())
}
return node
}

View File

@@ -0,0 +1,138 @@
package settings
import (
"fmt"
"net"
"regexp"
"github.com/qdm12/gluetun/internal/configuration/settings/helpers"
"github.com/qdm12/gluetun/internal/constants"
"github.com/qdm12/gotree"
"golang.zx2c4.com/wireguard/wgctrl/wgtypes"
)
// Wireguard contains settings to configure the Wireguard client.
type Wireguard struct {
// PrivateKey is the Wireguard client peer private key.
// It cannot be nil in the internal state.
PrivateKey *string
// PreSharedKey is the Wireguard pre-shared key.
// It can be the empty string to indicate there
// is no pre-shared key.
// It cannot be nil in the internal state.
PreSharedKey *string
// Addresses are the Wireguard interface addresses.
Addresses []net.IPNet
// Interface is the name of the Wireguard interface
// to create. It cannot be the empty string in the
// internal state.
Interface string
}
var regexpInterfaceName = regexp.MustCompile(`^[a-zA-Z0-9_]+$`)
// Validate validates Wireguard settings.
// It should only be ran if the VPN type chosen is Wireguard.
func (w Wireguard) validate(vpnProvider string) (err error) {
if !helpers.IsOneOf(vpnProvider,
constants.Custom,
constants.Ivpn,
constants.Mullvad,
constants.Windscribe,
) {
// do not validate for VPN provider not supporting Wireguard
return nil
}
// Validate PrivateKey
if *w.PrivateKey == "" {
return ErrWireguardPrivateKeyNotSet
}
_, err = wgtypes.ParseKey(*w.PrivateKey)
if err != nil {
return fmt.Errorf("%w: %s", ErrWireguardPrivateKeyNotValid, err)
}
// Validate PreSharedKey
if *w.PreSharedKey != "" { // Note: this is optional
_, err = wgtypes.ParseKey(*w.PreSharedKey)
if err != nil {
return fmt.Errorf("%w: %s", ErrWireguardPreSharedKeyNotValid, err)
}
}
// Validate Addresses
if len(w.Addresses) == 0 {
return ErrWireguardInterfaceAddressNotSet
}
for i, ipNet := range w.Addresses {
if ipNet.IP == nil || ipNet.Mask == nil {
return fmt.Errorf("%w: for address at index %d: %s",
ErrWireguardInterfaceAddressNotSet, i, ipNet.String())
}
}
// Validate interface
if !regexpInterfaceName.MatchString(w.Interface) {
return fmt.Errorf("%w: '%s' does not match regex '%s'",
ErrWireguardInterfaceNotValid, w.Interface, regexpInterfaceName)
}
return nil
}
func (w *Wireguard) copy() (copied Wireguard) {
return Wireguard{
PrivateKey: helpers.CopyStringPtr(w.PrivateKey),
PreSharedKey: helpers.CopyStringPtr(w.PreSharedKey),
Addresses: helpers.CopyIPNetSlice(w.Addresses),
Interface: w.Interface,
}
}
func (w *Wireguard) mergeWith(other Wireguard) {
w.PrivateKey = helpers.MergeWithStringPtr(w.PrivateKey, other.PrivateKey)
w.PreSharedKey = helpers.MergeWithStringPtr(w.PreSharedKey, other.PreSharedKey)
w.Addresses = helpers.MergeIPNetsSlices(w.Addresses, other.Addresses)
w.Interface = helpers.MergeWithString(w.Interface, other.Interface)
}
func (w *Wireguard) overrideWith(other Wireguard) {
w.PrivateKey = helpers.OverrideWithStringPtr(w.PrivateKey, other.PrivateKey)
w.PreSharedKey = helpers.OverrideWithStringPtr(w.PreSharedKey, other.PreSharedKey)
w.Addresses = helpers.OverrideWithIPNetsSlice(w.Addresses, other.Addresses)
w.Interface = helpers.OverrideWithString(w.Interface, other.Interface)
}
func (w *Wireguard) setDefaults() {
w.PrivateKey = helpers.DefaultStringPtr(w.PrivateKey, "")
w.PreSharedKey = helpers.DefaultStringPtr(w.PreSharedKey, "")
w.Interface = helpers.DefaultString(w.Interface, "wg0")
}
func (w Wireguard) String() string {
return w.toLinesNode().String()
}
func (w Wireguard) toLinesNode() (node *gotree.Node) {
node = gotree.New("Wireguard settings:")
if *w.PrivateKey != "" {
s := helpers.ObfuscateWireguardKey(*w.PrivateKey)
node.Appendf("Private key: %s", s)
}
if *w.PreSharedKey != "" {
s := helpers.ObfuscateWireguardKey(*w.PreSharedKey)
node.Appendf("Pre-shared key: %s", s)
}
addressesNode := node.Appendf("Interface addresses:")
for _, address := range w.Addresses {
addressesNode.Appendf(address.String())
}
node.Appendf("Network interface: %s", w.Interface)
return node
}

View File

@@ -0,0 +1,144 @@
package settings
import (
"fmt"
"net"
"github.com/qdm12/gluetun/internal/configuration/settings/helpers"
"github.com/qdm12/gluetun/internal/constants"
"github.com/qdm12/gotree"
"golang.zx2c4.com/wireguard/wgctrl/wgtypes"
)
type WireguardSelection struct {
// EndpointIP is the server endpoint IP address.
// It is only used with VPN providers generating Wireguard
// configurations specific to each server and user.
// To indicate it should not be used, it should be set
// to the empty net.IP{} slice. It can never be nil
// in the internal state.
EndpointIP net.IP
// EndpointPort is a the server port to use for the VPN server.
// It is optional for VPN providers IVPN, Mullvad
// and Windscribe, and compulsory for the others.
// When optional, it can be set to 0 to indicate not use
// a custom endpoint port. It cannot be nil in the internal
// state.
EndpointPort *uint16
// PublicKey is the server public key.
// It is only used with VPN providers generating Wireguard
// configurations specific to each server and user.
PublicKey string
}
// Validate validates WireguardSelection settings.
// It should only be ran if the VPN type chosen is Wireguard.
func (w WireguardSelection) validate(vpnProvider string) (err error) {
// Validate EndpointIP
switch vpnProvider {
case constants.Ivpn, constants.Mullvad, constants.Windscribe: // endpoint IP addresses are baked in
case constants.Custom:
if len(w.EndpointIP) == 0 {
return ErrWireguardEndpointIPNotSet
}
default: // Providers not supporting Wireguard
}
// Validate EndpointPort
switch vpnProvider {
// EndpointPort is required
case constants.Custom:
if *w.EndpointPort == 0 {
return ErrWireguardEndpointPortNotSet
}
case constants.Ivpn, constants.Mullvad, constants.Windscribe:
// EndpointPort is optional and can be 0
if *w.EndpointPort == 0 {
break // no custom endpoint port set
}
if vpnProvider == constants.Mullvad {
break // no restriction on custom endpoint port value
}
var allowed []uint16
switch vpnProvider {
case constants.Ivpn:
allowed = []uint16{2049, 2050, 53, 30587, 41893, 48574, 58237}
case constants.Windscribe:
allowed = []uint16{53, 80, 123, 443, 1194, 65142}
}
if helpers.Uint16IsOneOf(*w.EndpointPort, allowed) {
break
}
return fmt.Errorf("%w: %d for VPN service provider %s; %s",
ErrWireguardEndpointPortNotAllowed, w.EndpointPort, vpnProvider,
helpers.PortChoicesOrString(allowed))
default: // Providers not supporting Wireguard
}
// Validate PublicKey
switch vpnProvider {
case constants.Ivpn, constants.Mullvad, constants.Windscribe: // public keys are baked in
case constants.Custom:
if w.PublicKey == "" {
return ErrWireguardPublicKeyNotSet
}
default: // Providers not supporting Wireguard
}
if w.PublicKey != "" {
_, err := wgtypes.ParseKey(w.PublicKey)
if err != nil {
return fmt.Errorf("%w: %s: %s",
ErrWireguardPublicKeyNotValid, w.PublicKey, err)
}
}
return nil
}
func (w *WireguardSelection) copy() (copied WireguardSelection) {
return WireguardSelection{
EndpointIP: helpers.CopyIP(w.EndpointIP),
EndpointPort: helpers.CopyUint16Ptr(w.EndpointPort),
PublicKey: w.PublicKey,
}
}
func (w *WireguardSelection) mergeWith(other WireguardSelection) {
w.EndpointIP = helpers.MergeWithIP(w.EndpointIP, other.EndpointIP)
w.EndpointPort = helpers.MergeWithUint16(w.EndpointPort, other.EndpointPort)
w.PublicKey = helpers.MergeWithString(w.PublicKey, other.PublicKey)
}
func (w *WireguardSelection) overrideWith(other WireguardSelection) {
w.EndpointIP = helpers.OverrideWithIP(w.EndpointIP, other.EndpointIP)
w.EndpointPort = helpers.OverrideWithUint16(w.EndpointPort, other.EndpointPort)
w.PublicKey = helpers.OverrideWithString(w.PublicKey, other.PublicKey)
}
func (w *WireguardSelection) setDefaults() {
w.EndpointIP = helpers.DefaultIP(w.EndpointIP, net.IP{})
w.EndpointPort = helpers.DefaultUint16(w.EndpointPort, 0)
}
func (w WireguardSelection) String() string {
return w.toLinesNode().String()
}
func (w WireguardSelection) toLinesNode() (node *gotree.Node) {
node = gotree.New("Wireguard selection settings:")
if len(w.EndpointIP) > 0 {
node.Appendf("Endpoint IP address: %s", w.EndpointIP)
}
if *w.EndpointPort != 0 {
node.Appendf("Endpoint port: %d", *w.EndpointPort)
}
if w.PublicKey != "" {
node.Appendf("Server public key: %s", w.PublicKey)
}
return node
}

View File

@@ -0,0 +1,51 @@
package env
import (
"fmt"
"net"
"os"
"github.com/qdm12/gluetun/internal/configuration/settings"
)
func (r *Reader) readDNS() (dns settings.DNS, err error) {
dns.ServerAddress, err = r.readDNSServerAddress()
if err != nil {
return dns, err
}
dns.KeepNameserver, err = envToBoolPtr("DNS_KEEP_NAMESERVER")
if err != nil {
return dns, fmt.Errorf("environment variable DNS_KEEP_NAMESERVER: %w", err)
}
dns.DoT, err = r.readDoT()
if err != nil {
return dns, fmt.Errorf("cannot read DoT settings: %w", err)
}
return dns, nil
}
func (r *Reader) readDNSServerAddress() (address net.IP, err error) {
s := os.Getenv("DNS_PLAINTEXT_ADDRESS")
if s == "" {
return nil, nil
}
address = net.ParseIP(s)
if address == nil {
return nil, fmt.Errorf("environment variable DNS_PLAINTEXT_ADDRESS: %w: %s", ErrIPAddressParse, s)
}
// TODO remove in v4
if !address.Equal(net.IPv4(127, 0, 0, 1)) { //nolint:gomnd
r.warner.Warn("DNS_PLAINTEXT_ADDRESS is set to " + s +
" so the DNS over TLS (DoT) server will not be used." +
" The default value changed to 127.0.0.1 so it uses the internal DoT server." +
" If the DoT server fails to start, the IPv4 address of the first plaintext DNS server" +
" corresponding to the first DoT provider chosen is used.")
}
return address, nil
}

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