Compare commits

..

458 Commits
v2 ... v3.11.1

Author SHA1 Message Date
Quentin McGaw
1750c5beba Fix: DNS_KEEP_NAMESERVER 2021-01-06 21:54:23 +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
Quentin McGaw
78323f0a33 Update PIA IP addresses, fixes #215 2020-08-08 16:27:51 +00:00
Quentin McGaw
cd60fe4406 Add PIA US Dallas region, refers to #212 2020-07-28 02:40:38 +00:00
Quentin McGaw
a2a9410053 Fix #212 2020-07-28 00:31:19 +00:00
Quentin McGaw
f95f6201b1 Rename repo to Gluetun, refers to #112 2020-07-26 12:07:06 +00:00
Quentin McGaw
90e5742211 Reduce readme size 2020-07-25 11:55:35 -04:00
Quentin McGaw
8f547500d0 Purevpn support (#208)
Fixes #192
2020-07-25 11:19:45 -04:00
Quentin McGaw
0811b8b099 Server filtering fixes for Mullvad and Nordvpn 2020-07-23 02:16:12 +00:00
Quentin McGaw
c5c53a2ff8 FatalOnError fixes 2020-07-23 02:15:37 +00:00
Quentin McGaw
0ce129b63d Make all variables behave like server filters 2020-07-23 01:48:18 +00:00
Quentin McGaw
fec1249293 Uniformize server selection filtering 2020-07-23 01:46:28 +00:00
Quentin McGaw
a5c35455d1 Update PIA IP addresses 2020-07-20 02:32:02 +00:00
Quentin McGaw
28e0abc922 FIREWALL_VPN_INPUT_PORTS variable, fixes #196 2020-07-20 02:07:13 +00:00
Quentin McGaw
a13be8f45e Firewall simplifications
- Only a map of allowed input port to interface
- port forwarded is in the map of allowed input ports
- port forwarded has the interface tun0 in this map
- Always allow tcp and udp for allowed input ports
- Port forward state is in openvpn looper only
- Shadowsocks input port allowed on default interface only
- Tinyproxy input port allowed on default interface only
2020-07-20 00:39:59 +00:00
Quentin McGaw
85bd4f2e8d Get default route and local subnet only at start 2020-07-20 00:35:53 +00:00
Quentin McGaw
4baf0420d6 Openvpn get settings http route 2020-07-19 14:26:24 +00:00
Quentin McGaw
29f74df450 Fix #202 2020-07-19 14:22:23 +00:00
Quentin McGaw
fab9939b26 Simplify DNS loop a bit and fixes #199 2020-07-17 01:16:49 +00:00
Quentin McGaw
b4a4e441c1 Fix #199 when ticker period is 0 2020-07-16 12:00:25 +00:00
Quentin McGaw
e8526141be Fix issues in readme 2020-07-16 01:52:09 +00:00
Quentin McGaw
9abb630692 Get and set settings for DNS 2020-07-16 01:45:05 +00:00
Quentin McGaw
9b92ece5a1 Fix race condition for public ip loop 2020-07-16 01:44:48 +00:00
Quentin McGaw
87a3e54044 Set and get settings for openvpn 2020-07-16 01:26:37 +00:00
Quentin McGaw
76b730e2a6 Improve openvpn logging
- Show Initialization Sequence completed in green
- Show all other openvpn logs in the openvpn color
2020-07-16 01:20:47 +00:00
Quentin McGaw
51af8d1ab0 PUBLICIP_PERIOD environment variable 2020-07-16 01:12:54 +00:00
Quentin McGaw
002ffacd35 Shadowsocks get and set settings 2020-07-16 00:05:00 +00:00
Quentin McGaw
404cee9371 Tinyproxy set and get settings 2020-07-16 00:05:00 +00:00
Quentin McGaw
f89e7aa8dc Update readme list of VPN providers supported 2020-07-16 00:04:58 +00:00
Quentin McGaw
a0312ec916 Shadowsocks and Tinyproxy Start and Stop 2020-07-16 00:04:15 +00:00
Quentin McGaw
83cf59b93e Start and Stop for dns over tls 2020-07-16 00:04:15 +00:00
Quentin McGaw
ad5de13c25 Logging filtering for openvpn process 2020-07-16 00:04:14 +00:00
Quentin McGaw
1281026850 Nordvpn support (#189), fix #178 2020-07-15 18:14:45 -04:00
Quentin McGaw
616ba0c538 Replace explicit channels with functions 2020-07-15 01:34:46 +00:00
Quentin McGaw
8c7c8f7d5a Fix Cyberghost port, fixes #179 and #104 2020-07-14 23:08:57 +00:00
Quentin McGaw
78877483e9 Fix #198 2020-07-14 22:31:15 +00:00
Quentin McGaw
de7f12d958 Gluetun git history video 😉 2020-07-14 00:38:55 +00:00
Quentin McGaw
7e7312459d Cyberghost: read client key as file, see #179 2020-07-14 00:17:31 +00:00
Quentin McGaw
e3a677c22b Add openvpnconfig command, for #179 2020-07-13 23:43:26 +00:00
Quentin McGaw
2f955e0190 Simplified provider object creation 2020-07-13 23:34:03 +00:00
Quentin McGaw
618441b008 Vyprvpn support, fix #181 (#193) 2020-07-13 08:04:35 -04:00
Quentin McGaw
4a7d341c57 Fixing extra subnets firewall rules
- Fix #194
- Fix #190
- Refers to #188
2020-07-13 02:17:49 +00:00
Quentin McGaw
95ad58687d Remove duplicate firewall rule for tun0 2020-07-13 02:15:32 +00:00
Quentin McGaw
0fc69e068e FIREWALL_DEBUG variable, refers to #190, #194 2020-07-13 02:14:56 +00:00
Quentin McGaw
7252ac722c Logging improvements (#195)
- Fix (and test) filtering of lines
- Filter out shadowsocks cannot resolve error
- Change tinyproxy color
- Deduct logging level according to message content
2020-07-12 21:19:44 -04:00
Quentin McGaw
4cd6b33044 Add missing subnets setup, fixes #190
- Also setup subnet routes when firewall is disabled
2020-07-12 21:21:41 +00:00
Quentin McGaw
0731b1cb82 Remove horrible firewall debug 1 printing 2020-07-12 19:22:48 +00:00
Quentin McGaw
07efea612b Minor changes
- Remove unneeded string conversions
- Remove resolve-retry infinite openvpn configuration (unneeded)
- Add cover on single tests and single test files for Vscode
2020-07-12 19:15:05 +00:00
Quentin McGaw
6afa4f69a0 Fix routing reading issues
- Detect VPN gateway properly
- Fix local subnet detection, refers to #188
- Split LocalSubnet from DefaultRoute (2 different routes actually)
2020-07-12 19:05:48 +00:00
Quentin McGaw
2acf627918 Fixes #191 2020-07-12 15:21:32 +00:00
Quentin McGaw
4eb7c4ac36 Support for username and password changes 2020-07-12 14:55:03 +00:00
Quentin McGaw
b4c838e6ab Changes to cyberghost openvpn configuration
- Comment out redirect-gateway def1
- Add explicit-exit-notify 2 to ensure disconnection server side
- Remove ping-exit instructions
- Remove resolv-retry infinite (unneeded)
2020-07-12 14:47:37 +00:00
Quentin McGaw
8b096af04e DNS_KEEP_NAMESERVER variable, refers to #188 2020-07-11 23:51:53 +00:00
Quentin McGaw
78b63174ce Update VPN server IP addresses
- PIA updated
- Surfshark updated
- Windscribe updated (US East and Slovenia removed)
- Capital letters fixed for regions of Windscribe
- Resolver program updated to match latest format for constants
2020-07-11 22:02:25 +00:00
Quentin McGaw
11fca08028 Port forwarded firewall fix 2020-07-11 21:30:26 +00:00
Quentin McGaw
515e72a0ed Update versions in readme 2020-07-11 21:10:41 +00:00
Quentin McGaw
2f9d1f09d3 Minor changes
- Remove https://ip4.ddnss.de/meineip.php
- Logs when falling back to plaintext DNS
2020-07-11 21:04:46 +00:00
Quentin McGaw
b1596bc7e4 Firewall refactoring
- Ability to enable and disable rules in various loops
- Simplified code overall
- Port forwarding moved into openvpn loop
- Route addition and removal improved
2020-07-11 21:03:55 +00:00
Quentin McGaw
ccf11990f1 Fix several async issues
- race conditions between ctx.Done and waitError channel
- Sleep for retry cancels on cancelation of context
- Stops the any loop at the start if the context was canceled
- Mentions when loops exit
- Wait for errors on triggered loop restarts
2020-07-11 20:59:30 +00:00
Quentin McGaw
1ac06ee4a8 Fatal waits for subprocesses to complete 2020-07-09 22:04:04 +00:00
Quentin McGaw
dc1c7eab81 Fix #187 (silly me #2) 2020-07-09 11:56:43 +00:00
Quentin McGaw
5bf471767d Remove unneeded waiter object 2020-07-08 23:43:03 +00:00
Quentin McGaw
3d25db1bed Waits properly for all subprocess to exit 2020-07-08 23:42:54 +00:00
Quentin McGaw
99e386abc8 Using a waitgroup to wait for all programs to exit 2020-07-08 23:36:02 +00:00
Quentin McGaw
8669748289 Shadowsocks loop 2020-07-08 23:29:40 +00:00
Quentin McGaw
a39d885e34 Minor fixes
- Not logging program name twice for wait errors
- Wait for tinyproxy to exit
2020-07-08 23:29:22 +00:00
Quentin McGaw
7d36993450 Tinyproxy run loop 2020-07-08 23:20:33 +00:00
Quentin McGaw
0d53461706 Quick fix: uid and gid for openvpn 2020-07-08 22:51:27 +00:00
Quentin McGaw
758f316816 Small constants changes
- ifconfig.me/ip added, see #186
- Remove trailing space for a Surfshark region
2020-07-08 22:47:48 +00:00
Quentin McGaw
ad73a027f3 Gets public IP every hour 2020-07-08 22:47:12 +00:00
Quentin McGaw
2c96f91043 Merge branch 'master' of github.com:qdm12/private-internet-access-docker 2020-07-08 22:33:40 +00:00
Quentin McGaw
53b7fafc49 Public IP loop, fixes #186
- Uses common http client with 15 seconds timeout
- Repeats on fail after 5 seconds sleep time
2020-07-08 22:33:28 +00:00
Quentin McGaw
7450ffce2b uid and gid variables (no change at runtime) 2020-07-08 22:13:59 +00:00
Quentin McGaw
765f06e5a8 Write auth file in openvpn loop 2020-07-08 22:11:23 +00:00
Rick van Hattem
e304b4a829 Updated readme to match the Dockerfile (#185) 2020-07-08 09:19:56 -04:00
Quentin McGaw
3ae4523280 Merge branch 'master' of github.com:qdm12/private-internet-access-docker 2020-07-08 13:14:50 +00:00
Quentin McGaw
7a136db085 Simplified loop mechanism for openvpn and dns
- Refers to #91
- http control server starts without waiting for unbound and/or openvpn
- Trying to get rid of waiter and use channels directly
- Simpler main.go
- More robust logic overall
2020-07-08 13:14:39 +00:00
Rick van Hattem
e809e178b9 Added Surfshark documentation for authentication (#182) 2020-07-07 21:14:05 -04:00
Rick van Hattem
dd529a48fa Stripped extraneous whitespace (#180) 2020-07-07 17:33:35 -04:00
Rick van Hattem
2c6eae4e90 Set Surfshark ping-restart to valid value (#183)
No idea why this was here, but thanks for fixing it 🥇
2020-07-07 17:30:06 -04:00
Quentin McGaw
18e99d07d0 Move provider settings in openvpn settings 2020-07-05 20:05:38 +00:00
Quentin McGaw
a4b0e0ff86 Fix provider settings logging 2020-07-05 20:05:15 +00:00
Quentin McGaw
7e36fbbd00 Refers to #179 (Cyberghost)
- Fix <crt> -> <cert>
- Fix certificates (ca.crt and client.crt)
2020-07-05 16:20:40 +00:00
Quentin McGaw
d228216d1c Fix #179 2020-07-04 17:48:22 +00:00
Quentin McGaw
c9368e352c DNS_PLAINTEXT_ADDRESS, fixes #176 2020-06-26 14:40:46 +00:00
Quentin McGaw
d947d9fe30 Rename dev extension ms-vscode.go to golang.go 2020-06-26 14:37:11 +00:00
Quentin McGaw
613ded51ab Upgrade to Alpine 3.12 2020-06-26 14:36:31 +00:00
Quentin McGaw
3b43b7c2f6 Add script security 2 to run vpn provider scripts
Fix Bug: ... #176
2020-06-21 20:21:13 -04:00
Quentin McGaw
cdbb7bf771 Fix #175 2020-06-18 18:05:51 +00:00
Quentin McGaw
5a6cf0fe3a Remove firewall nat chain clearing, refers to #171 2020-06-16 12:20:33 +00:00
Quentin McGaw
082a5bdf51 Fix http control server not working when DOT=off 2020-06-16 00:11:22 +00:00
Quentin McGaw
7369808b84 Refactor (#174)
- Goal was to simplify main.go complexity
- Use common structures and interfaces for all vpn providers
- Moved files around
- Removed some alias models
2020-06-13 14:08:29 -04:00
Quentin McGaw
4f502abcf8 Cyberghost support (#168)
* Host finder CLI for cyberghost
* Resolver program updated with Cyberghost data
* Gluetun cli clientkey subcommand
2020-06-13 10:43:47 -04:00
Quentin McGaw
bdcadf09ec Fix: Shadowsocks nameserver when DOT=off 2020-06-13 13:57:26 +00:00
Quentin McGaw
8cae369186 Add FIREWALL variable, refers to #171 2020-06-12 17:11:21 +00:00
Quentin McGaw
a3d75f3d8b Replace p with r for params reader (lint issue) 2020-06-12 17:07:32 +00:00
Quentin McGaw
1a06d01ae2 Fix #172 kernel module for Synology 2020-06-10 01:34:20 +00:00
Quentin McGaw
634cef2bb2 Only resolve github.com for healthcheck, fix #170 2020-06-10 01:25:10 +00:00
Quentin McGaw
6107f5c4ab Fix #173 2020-06-10 01:16:58 +00:00
Quentin McGaw
6ae9dc5c2c Update list of donators in readme 👍 2020-06-08 08:31:18 -04:00
Quentin McGaw
ea3a173e3b Remove persist-tun, fix #171
- Now openvpn tries other vpn ip addresses available on restart
- In case of  a bad ip address, it will try other random ones
2020-06-08 11:08:07 +00:00
Quentin McGaw
69217f61a1 Update PIA servers IP addresses, refers to #171 2020-06-08 11:06:11 +00:00
Quentin McGaw
e33a6a8503 Fix #170 2020-06-05 19:32:12 -04:00
Quentin McGaw
0fb065eb61 Fix bad comparison in healthcheck 2020-06-05 12:46:44 +00:00
Quentin McGaw
f6a2aac475 Fix #170 2020-06-04 22:13:49 +00:00
Quentin McGaw
900fa261d8 Better healthcheck (#169), fixes #133
* Changed healthcheck to get and compare IP address
* Change default healthcheck frequency and retries
2020-06-03 21:52:44 -04:00
Quentin McGaw
cfb4dd84bc Replace pia with gluetun wherever possible
- in Readme documentation
- Changed splash title string
- Changed Dockerfile labels
- Changed commands and docker-compose service & container name
2020-06-03 02:11:35 +00:00
Quentin McGaw
4f72f60a3e .dockerignore updated 2020-06-02 23:12:04 +00:00
Quentin McGaw
f262ee6454 Resolver cli changes
- Max of 10 simultaneous goroutines to avoid being throttled by DNS
- All template formatting moved to formatLine function
- resolveRepeat is synchronous to avoid being throttled by DNS
2020-06-02 23:10:04 +00:00
Quentin McGaw
20a3327815 Minor changes
- PORT is for Mullvad but also Windscribe (docker-compose, README.md)
- Windscribe configurator does not need lookupIP anymore
2020-06-02 23:07:29 +00:00
Quentin McGaw
3ab1298b1f Main function improved
- More explicit cli operation
- Using ctx and os.Args injected for eventual later testing
- Returning exit code
- Cli code moved to cli package
2020-06-02 23:03:18 +00:00
Quentin McGaw
a7739b6f5d Add multi hop regions and ips for Surfshark 2020-05-31 19:50:28 +00:00
Quentin McGaw
263368af89 Remove cipher and auth restrictive checks 2020-05-29 12:01:45 +00:00
Quentin McGaw
96e57d2c32 Surfshark (#167) 2020-05-29 07:24:27 -04:00
Quentin McGaw
85a93bdd34 Remove timestamps from program logs
- Using suppress-timestamps for openvpn
- Using custom regexp for tinyproxy, shadowsocks and unbound
- Time is now only shown once per line
- Log level of subprogram is preserverd
2020-05-29 11:17:14 +00:00
Quentin McGaw
cc80d224c2 Review current openvpn configuration
- tls-client unneeded if client is specified
- Moved settings around in config file
2020-05-29 10:29:07 +00:00
Quentin McGaw
c85cca7fdc Workflow adjustments:
- Paths ignore for all docker build
- Remove security workflow (CVEs should be fixed by Alpine)
2020-05-29 10:20:45 +00:00
Quentin McGaw
3f6d3d7c2a Openvpn files parser tool binary 2020-05-29 10:13:42 +00:00
Quentin McGaw
09a0ba1228 DNS Resolver tool binary 2020-05-29 10:13:29 +00:00
Quentin McGaw
6b81ed6bde Unneeded Docker init, Go entrypoint does it 2020-05-29 10:12:38 +00:00
Quentin McGaw
64e447b262 Minor code and comments changes 2020-05-29 00:09:49 +00:00
Quentin McGaw
d0926111e0 Env variables cleanup in Docker config files 2020-05-29 00:03:10 +00:00
Quentin McGaw
aac4298f69 Moved main.go to cmd/gluetun/main.go 2020-05-28 23:59:35 +00:00
Quentin McGaw
f4018d3411 Fix PIA region case sensitivity, refers to #163 2020-05-28 01:07:32 +00:00
Quentin McGaw
0710199409 Updated IP addresses for Windscribe and PIA
- Updated test functions to resolve subdomains
- IP addresses are in increasing order
- One line per region/subdomain
2020-05-27 00:52:52 +00:00
Quentin McGaw
43c15b3e68 Removed double message in port forwarding logging 2020-05-18 13:39:01 +00:00
Quentin McGaw
ab223a5e06 User specified iptables rules (#161) 2020-05-18 09:37:34 -04:00
Quentin McGaw
fd5e7af3ff Improve environment variables table 2020-05-17 18:34:57 -04:00
Quentin McGaw
886d4ad1a9 Update readme for new wiki 2020-05-17 17:54:37 -04:00
Quentin McGaw
40a72b6189 Update golangci-lint to 1.27.0 2020-05-17 17:49:40 -04:00
Quentin McGaw
5eb1859f41 Fix #156 falls back to dns on ipv4 only 2020-05-09 00:54:49 +00:00
Quentin McGaw
b45fa026dd Improved openvpn run loop 2020-05-09 00:43:09 +00:00
Quentin McGaw
da739a0c3d Fix waitForAll context for graceful exits 2020-05-07 12:58:37 +00:00
Quentin McGaw
0dc400b540 Fix Unbound run loop logic
- Plain DNS is used only for the first resolving of github.com to obtain block lists and crypto files required by Unbound
- DNS over TLS is used at all time by the system and the Go program thereafter, even between periodic restarts
- Downtime during a periodic update is < 1 second
- On an Unbound start or unexpected exit error, the container falls back on the unencrypted version of the DNS in order to try restarting Unbound
2020-05-07 12:56:49 +00:00
Quentin McGaw
d12668d57f Fix logic to disable DNS periodic update 2020-05-05 22:02:23 +00:00
Quentin McGaw
c39affeb12 GolangCi-lint 1.
26.0
2020-05-05 18:01:12 +00:00
Quentin McGaw
d73765a5f5 DNS_UPDATE_PERIOD environment variable 2020-05-05 18:00:56 +00:00
Quentin McGaw
37282c014b Removed greetings workflow (not working on forks) 2020-05-04 23:05:03 +00:00
Quentin McGaw
adeccf8548 ip6tables package, refers to #153 2020-05-04 12:27:15 +00:00
Quentin McGaw
a97cbcc4e4 Refers to #153 2020-05-04 12:24:34 +00:00
Quentin McGaw
89187b6b86 Fix #142 2020-05-03 16:28:59 +00:00
Quentin McGaw
754bab9763 Unbound restart logic
- Update files and restart unbound every 24hours
- HTTP route to force update & restart of Unbound
- Fix #151
2020-05-02 17:08:15 +00:00
Quentin McGaw
0d7f6dab1a Remove unneeded functionNotSet in server 2020-05-02 17:04:01 +00:00
Quentin McGaw
507374ca4e Improve openvpn run loop logs 2020-05-02 17:03:11 +00:00
Quentin McGaw
318c3c9032 Control server announcement 2020-05-02 17:02:39 +00:00
Quentin McGaw
c068484fa0 Initial DNS IP is ipv4/6 depending on DOT_IPV6 2020-05-02 15:41:28 +00:00
Quentin McGaw
7cd35737ba Defaults DOT_IPV6 to off 2020-05-02 15:40:40 +00:00
Quentin McGaw
0247a1ff01 Refers to #142 2020-05-02 14:59:22 +00:00
Quentin McGaw
363fabc810 Reduced main.go code complexity 2020-05-02 14:48:18 +00:00
Quentin McGaw
6049b10209 Update firewall documentation 2020-05-02 13:13:19 +00:00
Quentin McGaw
bc05ff34fd Launch DNS over TLS after tunneling
- No data is downloaded before tunneling
- Fixes #127
2020-05-02 13:11:41 +00:00
Quentin McGaw
8e77842f1e FatalOnError cancels parent context 2020-05-02 13:05:09 +00:00
Quentin McGaw
41168f88cd Improved connected signaling mechanism 2020-05-01 03:15:49 +00:00
Quentin McGaw
88ad10d429 PIA and Windscribe hardcoded IP addresses
- Allows to not need to resolve subdomains at start before tunneling
- Allows for Unbound to be configured and started after tunneling
- Refers to #127
2020-05-01 03:14:16 +00:00
Quentin McGaw
f4cd1896c9 Go HTTP control server with restart openvpn route
- Fix #147
- Dockerfile updated
- Documentation updated
- Using contexts to restart openvpn
- Code foundation for more http routes
2020-04-30 23:41:57 +00:00
Quentin McGaw
944e6a107b Logs wait function errors as soon as they happen 2020-04-30 23:39:28 +00:00
Quentin McGaw
b6135d2476 Support consoles without /dev/stdout 2020-04-30 23:34:35 +00:00
Quentin McGaw
c9b6e79792 Clear ip status file on termination 2020-04-30 12:55:07 +00:00
Quentin McGaw
94255aaa38 Better onConnected logic
- First port forward after 5 seconds
- Public IP obtained ASAP
- Logging in main only
- Allow port forward firewall with 1 second timeout local context
2020-04-30 12:54:48 +00:00
Quentin McGaw
ac706bd156 Wait for openvpn to exit on program termination 2020-04-30 11:01:22 +00:00
Quentin McGaw
d864a9f580 Refixes #148 2020-04-30 10:48:26 +00:00
Quentin McGaw
a32318d246 Improve issue templates 2020-04-30 10:38:31 +00:00
Quentin McGaw
45a7a5b9e2 Fix build 2020-04-30 00:46:29 +00:00
Quentin McGaw
9af2a7a640 Retry port forwarding after 5 seconds, fix #148 2020-04-30 00:02:27 +00:00
Quentin McGaw
eb62ad06db Better context handling (prep for restart logic) 2020-04-29 23:59:23 +00:00
Quentin McGaw
a033637e85 Better exit handling 2020-04-29 01:27:42 +00:00
Quentin McGaw
b0ea739c20 Restarts openvpn on failure without Docker restart (#144) 2020-04-28 21:22:18 -04:00
Quentin McGaw
352af84977 Workflows adjustments (#124)
* reviewdog/action-misspell@master
* Improve issue templates
2020-04-28 08:48:06 -04:00
Quentin McGaw
eb149ee040 Fix bad links in FAQ 2020-04-28 08:43:21 -04:00
Quentin McGaw
9b3166a2e2 Add @Frepke to sponsors in readme 2020-04-28 08:43:13 -04:00
Quentin McGaw
e94f4283e1 Port forwards 1 second after openvpn connects 2020-04-27 12:10:36 +00:00
Quentin McGaw
ef0959a15e Update Golangci-lint to v1.25.0 2020-04-26 13:28:26 +00:00
Quentin McGaw
36424c08ac Better checks for user provided private addresses 2020-04-26 13:28:14 +00:00
Quentin McGaw
97ea5f63b8 Removes port forward status file at exit 2020-04-19 20:45:34 +00:00
Quentin McGaw
88c9d3d687 Waits 300ms before first DNS resolution try 2020-04-19 20:44:33 +00:00
Quentin McGaw
f1569dac05 Each stream uses a different color, fixes #136 2020-04-19 20:40:31 +00:00
Quentin McGaw
4cb32ef9dc Reliably wait for all processes to exit 2020-04-19 20:10:48 +00:00
Quentin McGaw
e805d42197 Updated dependencies 2020-04-19 18:13:48 +00:00
Quentin McGaw
cbd11bfdf2 Thanks @Ralph521 ! 2020-04-15 12:33:50 +00:00
Quentin McGaw
422bd8d428 Log stderr stream from shadowsocks 2020-04-14 12:22:14 +00:00
Quentin McGaw
58459f0336 PIA_ENCRYPTION in readme, thanks @Frepke 2020-04-14 07:43:14 -04:00
Quentin McGaw
6f6e227b94 Add section for sponsors, donations in readme 2020-04-13 00:32:57 +00:00
Quentin McGaw
e015cd4a27 Windscribe affiliate link, refers to #109 2020-04-13 00:21:41 +00:00
Quentin McGaw
768147095f Golangcilint in build pipeline and fix lint errors
- Fix bad permissions bits for files
- VPNSP is 'private internet access' instead of 'pia' (retro compatible)
- Check errors of deferred unsetEnv functions in params package
-  Other lint errors fixing and code simplifications
2020-04-12 20:05:28 +00:00
Quentin McGaw
8f6b6306d6 Formatting of files (goimport) 2020-04-12 20:01:33 +00:00
Quentin McGaw
fb4c9b8a58 Vscode workspace settings 2020-04-12 19:53:50 +00:00
Quentin McGaw
3d7cfb125a Using WithPrefix for loggers 2020-04-12 19:07:19 +00:00
Quentin McGaw
d42de99879 Updated golibs and using gomock+mockgen for tests 2020-04-12 18:09:46 +00:00
Quentin McGaw
68203c221d Refactored documentation for Docker hub workflow
- Readme size lowered to 18KB
- Documents created in doc/ directory
- faq.md, firewall.md, development.md
- Title svg as absolute path for Docker hub
- Re-enabled Docker hub description workflow
2020-04-12 13:42:41 +00:00
Quentin McGaw
3ac3e5022c IP_STATUS_FILE and routing improvements (#130)
- Obtains VPN public IP address from routing table
- Logs and writes VPN Public IP address to `/ip` as soon as VPN is up
- Obtain port forward, logs it and writes it as soon as VPN is up
- Routing fully refactored and tested
- Routing reads from `/proc/net/route`
- Routing mutates the routes using `ip route ...`
2020-04-12 08:55:13 -04:00
Quentin McGaw
da8391e9ae Using %s instead of %w for format print 2020-04-12 02:42:32 +00:00
Quentin McGaw
ebdf241888 Show DNS lookup error, refers to #127 2020-04-09 13:17:55 +00:00
Quentin McGaw
60cec716b2 Clears port forward status file at exit, fix #125 2020-04-09 12:11:36 +00:00
Quentin McGaw
e7a475a303 Stops on port forwarding error, fixes #120 2020-03-31 12:03:26 +00:00
Quentin McGaw
67588e0072 Merge branch 'master' of github.com:qdm12/private-internet-access-docker 2020-03-30 12:01:45 +00:00
Quentin McGaw
bfa3d749ac Fix test 2020-03-30 12:01:35 +00:00
Quentin McGaw
7e79d9696f TZ setting shown at start (#119) 2020-03-30 07:58:40 -04:00
Quentin McGaw
f251c6aa4d Using UID and GID given for Tinyproxy, fixes #118 2020-03-30 11:56:38 +00:00
Quentin McGaw
d2117cd043 Improved environment variables table 2020-03-30 00:48:54 +00:00
Quentin McGaw
0235df74a0 SHADOWSOCKS_METHOD environment variable (#117) 2020-03-29 20:06:27 -04:00
Quentin McGaw
e5adccd9c5 Custom UID and GID for subprocesses and files written (#116) Fix #116
- Environment variables `UID` and `GID`, both defaulting to `1000`
- All subprocesses (openvpn, tinyproxy, etc.) run using the UID and GID given
- All files are written with an ownership for the UID and GID given
- Port forwarded file has also ownership for UID, GID and read permission only
2020-03-29 19:52:49 -04:00
Quentin McGaw
76cea56864 Windscribe support (#114) 2020-03-29 16:42:06 -04:00
Quentin McGaw
643745d33e OPENVPN_AUTH variable, refers to #94 2020-03-29 16:22:21 -04:00
Quentin McGaw
3d6a580102 Workflow minor fixes
- Renamed bad name in PR docker build
- Removed escaped \n in greetings
- Fixed up misspell action
2020-03-29 11:30:37 -04:00
Quentin McGaw
d4a1828c1d Issue templates updated 2020-03-29 11:29:59 -04:00
Quentin McGaw
bdf96d864e Check custom cipher value for each vpn provider 2020-03-27 01:10:54 +00:00
Quentin McGaw
15a549be11 OPENVPN_CIPHER variable (#100), refers to #94 and #59 2020-03-26 20:29:32 -04:00
Quentin McGaw
d534f92432 Workflows changes
- Simple Docker build for PRs
- Only run buildx for latest tag on pushes to master branch
2020-03-26 20:17:10 -04:00
Quentin McGaw
d0c61662b5 Revise workflows (#99) 2020-03-26 08:35:48 -04:00
Quentin McGaw
98b076e2cb Rename ENCRYPTION to PIA_ENCRYPTION (#98) 2020-03-26 08:11:50 -04:00
github-actions[bot]
0b997fe6c8 Typos fixes (#95)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2020-03-25 18:28:28 -04:00
Quentin McGaw
b0c0bd6364 Updated readme build status badge 2020-03-25 18:23:09 -04:00
Quentin McGaw
c61a418430 Docker build with workflows instead of Travis CI 2020-03-25 18:22:02 -04:00
Quentin McGaw
e6bbaa2ba6 Security analysis workflow 2020-03-25 18:21:36 -04:00
Quentin McGaw
17ccf98c75 Docker hub description sync workflow 2020-03-25 18:21:26 -04:00
Quentin McGaw
4db67c70b8 Misspell workflow 2020-03-25 18:21:14 -04:00
Quentin McGaw
3250a20ffc Issue templates 2020-03-25 18:21:01 -04:00
Quentin McGaw
6c12fdff2b Labels and greetings Github workflows 2020-03-25 18:20:52 -04:00
Quentin McGaw
f033204844 Contribution doc updated
- Codeowners file
- Contributing doc file
- Readme dev and contributing updated
2020-03-25 18:19:57 -04:00
Quentin McGaw
e334cf6c5f Minor changes 2020-03-23 20:37:56 +00:00
Quentin McGaw
9435db8e1e Fix #90 add env variable OPENVPN_TARGET_IP 2020-03-18 23:49:40 +00:00
Quentin McGaw
d2b361b998 Fix #81, new env variable OPENVPN_ROOT 2020-03-18 23:05:47 +00:00
Quentin McGaw (desktop)
9d786bf338 Update to Go 1.14 2020-03-18 01:04:44 +00:00
Quentin McGaw (desktop)
3339455a97 Simpler readme svg title 2020-03-18 01:04:33 +00:00
Quentin McGaw (desktop)
0eb2e5a120 Removed unecessary tun-ipv6 from pia ovpn config 2020-03-05 01:03:33 +00:00
Quentin McGaw (desktop)
d0f678c315 Add DNS over TLS ipv6 upstream servers, see #88 2020-03-05 00:54:33 +00:00
Quentin McGaw (desktop)
0c48d2d5a0 DOT_IPV6 environment variable added, refers to #88 2020-03-05 00:51:04 +00:00
Quentin McGaw (desktop)
47a197be48 Add ipv6 documentation, refers to #86 2020-03-05 00:01:04 +00:00
Quentin McGaw (desktop)
28edae383b Changed openvpn Mullvad settings, refers to #86 2020-03-04 23:52:41 +00:00
Quentin McGaw (desktop)
939b58c457 Ortograph! Renamed annoucement to announcement 2020-03-04 23:47:21 +00:00
Quentin McGaw
fa0272d5ad Support Mullvad over openvpn (#85)
Additional changes:
- Allow empty value for PIA region
- Most settings are lowercased
- `OPENVPN_VERBOSITY` environment variable
- openvpn also tunnels IPv6, and unbound supports ipv6
- auth kept only on disk, not in memory
- readme reworked
- CI script fixed and improved
- Added v2 Docker tag
- Shadowsocks log defaults to `off`
2020-02-29 21:05:20 -05:00
Quentin McGaw (desktop)
839c6f05dd Upgraded golibs 2020-03-01 02:03:57 +00:00
Quentin McGaw (desktop)
9ada201b82 Fixed and improved CI script 2020-02-25 11:47:08 +00:00
Quentin McGaw (desktop)
dd0170afb1 Annoucement expiration time parsing 2020-02-25 11:46:52 +00:00
Quentin McGaw (desktop)
9239e840c4 Fixed CI script for pull requests 2020-02-23 19:11:03 -05:00
Quentin McGaw (desktop)
96713b26cb README: contributors section 2020-02-22 17:56:04 +00:00
Quentin McGaw (desktop)
3ad60349db Applied JeordyR's changes 2020-02-22 17:55:49 +00:00
Quentin McGaw (desktop)
5ee4e2fde0 Shadowsocks log defaults to off 2020-02-22 17:43:17 +00:00
Quentin McGaw (desktop)
ce4fd8bc68 TODOs are in a collapsible (most users don't care) 2020-02-22 17:35:50 +00:00
Quentin McGaw (desktop)
90fc12a941 Added v2 Docker tag 2020-02-22 17:34:41 +00:00
Quentin McGaw (desktop)
16995e1d93 Changed CI script 2020-02-22 17:32:05 +00:00
Quentin McGaw (desktop)
9669938703 Port forwarding section reworked 2020-02-22 17:15:42 +00:00
Quentin McGaw (desktop)
ac60cf8ab8 Minor changes 2020-02-22 17:07:06 +00:00
Quentin McGaw (desktop)
f5a32e690f README: Updated and refactored features section 2020-02-22 17:06:35 +00:00
Quentin McGaw (desktop)
4e622a92a5 README: Rework requirements 2020-02-22 17:05:52 +00:00
Quentin McGaw (desktop)
d1412f43fd Changed announcement 2020-02-22 17:04:50 +00:00
Quentin McGaw (desktop)
1b3a135920 Reworked readme's FAQ 2020-02-22 16:35:26 +00:00
Quentin McGaw (desktop)
53db4813fa Allow PIA server to choose compression 2020-02-22 16:34:28 +00:00
Quentin McGaw (desktop)
2f09ed9069 tls-client openvpn client instruction added 2020-02-22 16:34:05 +00:00
Quentin McGaw (desktop)
9202d6c15f Do not keep auth in memory, only on disk 2020-02-22 16:33:37 +00:00
Quentin McGaw (desktop)
023f1c7e8e Tunnels IPv6 2020-02-22 16:32:59 +00:00
Quentin McGaw (desktop)
1aebe1a4c1 Simplified GetUser and GetPassword using libparams 2020-02-22 15:53:50 +00:00
Quentin McGaw (desktop)
f45f40eee1 Added OPENVPN_VERBOSITY environment variable 2020-02-22 15:48:09 +00:00
Quentin McGaw (desktop)
ab5d60754f Read some values with case sensitivity 2020-02-22 15:43:33 +00:00
Quentin McGaw (desktop)
83e8bb780a Fix reading region without case sensitivity 2020-02-22 13:52:33 +00:00
Quentin McGaw (desktop)
888d8bbf87 Lowercased all Mullvad server constants 2020-02-16 22:39:05 +00:00
Quentin McGaw (desktop)
fbf04677f1 Minor fixes 2020-02-16 22:27:49 +00:00
Quentin McGaw (desktop)
2051aa1b04 Wire up everything for Mullvad and PIA in main.go 2020-02-16 22:16:11 +00:00
Quentin McGaw (desktop)
fc88ee135d Added Mullvad settings setup 2020-02-16 22:15:52 +00:00
Quentin McGaw (desktop)
a6f9a1a3d1 Fix GetVPNSP 2020-02-16 22:15:06 +00:00
Quentin McGaw (desktop)
f181ff0005 Added Mullvad environment variables and getters 2020-02-16 20:30:29 +00:00
Quentin McGaw (desktop)
71dcf23013 Allow empty value for PIA region 2020-02-16 20:29:37 +00:00
Quentin McGaw (desktop)
95ee3b4276 Moved GetUser and GetPassword to openvpn params getters 2020-02-16 19:55:01 +00:00
Quentin McGaw (desktop)
c42d13f14f Added Mullvad configuration builder 2020-02-16 19:51:08 +00:00
Quentin McGaw (desktop)
ce11745f6f Using struct for VPN connection settings 2020-02-16 19:50:21 +00:00
Quentin McGaw (desktop)
f6b91bd74f Added Mullvad constants and lists 2020-02-16 17:34:04 +00:00
Quentin McGaw (desktop)
5c69ddc05f Added Mullvad server model 2020-02-16 17:33:52 +00:00
238 changed files with 23185 additions and 4326 deletions

View File

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

1
.devcontainer/Dockerfile Normal file
View File

@@ -0,0 +1 @@
FROM qmcgaw/godevcontainer

68
.devcontainer/README.md Normal file
View File

@@ -0,0 +1,68 @@
# 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.
## 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,34 +1,36 @@
{
"name": "pia-dev",
"dockerComposeFile": ["docker-compose.yml"],
"name": "gluetun-dev",
"dockerComposeFile": [
"docker-compose.yml"
],
"service": "vscode",
"runServices": ["vscode"],
"runServices": [
"vscode"
],
"shutdownAction": "stopCompose",
// "postCreateCommand": "go mod download",
"postCreateCommand": "go mod download",
"workspaceFolder": "/workspace",
"extensions": [
"ms-vscode.go",
"IBM.output-colorizer",
"eamodio.gitlens",
"mhutchie.git-graph",
"golang.go",
"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
"go.useLanguageServer": true,
"go.autocompleteUnimportedPackages": true,
"go.gotoSymbol.includeImports": true,
@@ -38,25 +40,31 @@
"deepCompletion": true,
"usePlaceholders": false
},
// Golang on save
"go.buildOnSave": "package",
"go.lintOnSave": "package",
"go.vetOnSave": "package",
"go.lintTool": "golangci-lint",
"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.testFlags": ["-v"],
"go.testTimeout": "600s"
"go.testFlags": [
"-v",
// "-race"
],
"go.testTimeout": "10s",
"go.coverOnSingleTest": true,
"go.coverOnSingleTestFile": true,
"go.coverOnTestPackage": true
}
}

View File

@@ -2,14 +2,24 @@ version: "3.7"
services:
vscode:
image: qmcgaw/godevcontainer
build: .
image: godevcontainer
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
# SSH directory
- ~/.ssh:/home/vscode/.ssh
- ~/.ssh:/root/.ssh
# Git config
- ~/.gitconfig:/home/districter/.gitconfig
- ~/.gitconfig:/root/.gitconfig
environment:
- TZ=
cap_add:
# For debugging with dlv
- SYS_PTRACE
security_opt:
# For debugging with dlv
- seccomp:unconfined
entrypoint: zsh -c "while sleep 1000; do :; done"

View File

@@ -1,10 +1,10 @@
.devcontainer
.git
.vscode
readme
.gitignore
.travis.yml
ci.sh
.github
cmd
!cmd/gluetun
doc
docker-compose.yml
LICENSE
README.md
Dockerfile
title.svg

1
.github/CODEOWNERS vendored Normal file
View File

@@ -0,0 +1 @@
@qdm12

29
.github/CONTRIBUTING.md vendored Normal file
View File

@@ -0,0 +1,29 @@
# Contributing
Contributions are [released](https://help.github.com/articles/github-terms-of-service/#6-contributions-under-repository-license) to the public under the [open source license of this project](../LICENSE).
## Submitting a pull request
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 .` (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
- [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)

37
.github/ISSUE_TEMPLATE/bug.md vendored Normal file
View File

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

View File

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

53
.github/ISSUE_TEMPLATE/help.md vendored Normal file
View File

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

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

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

67
.github/labels.yml vendored Normal file
View File

@@ -0,0 +1,67 @@
- name: "Bug :bug:"
color: "b60205"
description: ""
- name: "Feature request :bulb:"
color: "0e8a16"
description: ""
- name: "Help wanted :pray:"
color: "4caf50"
description: ""
- name: "Documentation :memo:"
color: "c5def5"
description: ""
- name: "Needs more info :thinking:"
color: "795548"
description: ""
# VPN providers
- name: ":cloud: Cyberghost"
color: "cfe8d4"
description: ""
- name: ":cloud: Mullvad"
color: "cfe8d4"
description: ""
- name: ":cloud: NordVPN"
color: "cfe8d4"
description: ""
- name: ":cloud: PIA"
color: "cfe8d4"
description: ""
- name: ":cloud: Privado"
color: "cfe8d4"
description: ""
- name: ":cloud: PureVPN"
color: "cfe8d4"
description: ""
- name: ":cloud: Surfshark"
color: "cfe8d4"
description: ""
- name: ":cloud: Vyprvpn"
color: "cfe8d4"
description: ""
- name: ":cloud: Windscribe"
color: "cfe8d4"
description: ""
# Problem category
- name: "Openvpn"
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: ""

52
.github/workflows/branch.yml vendored Normal file
View File

@@ -0,0 +1,52 @@
name: branch
on:
push:
branches:
- "*"
- "!master"
paths:
- .github/workflows/branch.yml
- cmd/**
- internal/**
- .dockerignore
- .golangci.yml
- Dockerfile
- go.mod
- go.sum
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- env:
DOCKER_BUILDKIT: "1"
run: docker build --target test .
lint:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- env:
DOCKER_BUILDKIT: "1"
run: docker build --target lint .
docker:
runs-on: ubuntu-latest
needs: [test, lint]
steps:
- uses: actions/checkout@v2
- uses: docker/setup-qemu-action@v1
- uses: docker/setup-buildx-action@v1
- name: Dockerhub login
run: echo ${{ secrets.DOCKERHUB_PASSWORD }} | docker login -u qmcgaw --password-stdin 2>&1
- name: Docker build
run: |
docker buildx build \
--platform=linux/amd64,linux/386,linux/arm64,linux/arm/v6,linux/arm/v7,linux/s390x,linux/ppc64le \
--build-arg BUILD_DATE=`date -u +"%Y-%m-%dT%H:%M:%SZ"` \
--build-arg COMMIT=`git rev-parse --short HEAD` \
--build-arg VERSION="branch-${GITHUB_REF##*/}" \
-t qmcgaw/gluetun:branch-${GITHUB_REF##*/} \
--push \
.

View File

@@ -0,0 +1,21 @@
name: Docker Hub description
on:
push:
branches: [master]
paths:
- README.md
- .github/workflows/dockerhub-description.yml
jobs:
dockerHubDescription:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v2
- name: Docker Hub Description
uses: peter-evans/dockerhub-description@v2.4.1
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

18
.github/workflows/labels.yml vendored Normal file
View File

@@ -0,0 +1,18 @@
name: labels
on:
push:
branches: [master]
paths:
- .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@v3.1.1
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}

53
.github/workflows/latest.yml vendored Normal file
View File

@@ -0,0 +1,53 @@
name: latest
on:
push:
branches: [master]
paths:
- .github/workflows/latest.yml
- cmd/**
- internal/**
- .dockerignore
- .golangci.yml
- Dockerfile
- go.mod
- go.sum
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- env:
DOCKER_BUILDKIT: "1"
run: docker build --target test .
lint:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- env:
DOCKER_BUILDKIT: "1"
run: docker build --target lint .
docker:
runs-on: ubuntu-latest
needs: [test, lint]
steps:
- uses: actions/checkout@v2
- uses: docker/setup-qemu-action@v1
- uses: docker/setup-buildx-action@v1
- name: Dockerhub login
run: echo ${{ secrets.DOCKERHUB_PASSWORD }} | docker login -u qmcgaw --password-stdin 2>&1
- name: Docker buildx
run: |
docker buildx build \
--platform=linux/amd64,linux/386,linux/arm64,linux/arm/v6,linux/arm/v7,linux/s390x,linux/ppc64le \
--build-arg BUILD_DATE=`date -u +"%Y-%m-%dT%H:%M:%SZ"` \
--build-arg COMMIT=`git rev-parse --short HEAD` \
--build-arg VERSION=latest \
-t qmcgaw/private-internet-access:latest \
-t qmcgaw/gluetun:latest \
--push \
.
- run: curl -X POST https://hooks.microbadger.com/images/qmcgaw/private-internet-access/tQFy7AxtSUNANPe6aoVChYdsI_I=
continue-on-error: true

16
.github/workflows/misspell.yml vendored Normal file
View File

@@ -0,0 +1,16 @@
name: Misspells
on:
pull_request:
branches: [master]
push:
branches: [master]
jobs:
misspell:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- uses: reviewdog/action-misspell@master
with:
github_token: ${{ secrets.GITHUB_TOKEN }}
locale: "US"
level: error

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

@@ -0,0 +1,40 @@
name: pull request
on:
pull_request:
branches: [master]
paths:
- .github/workflows/pr.yml
- cmd/**
- internal/**
- .dockerignore
- .golangci.yml
- Dockerfile
- go.mod
- go.sum
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- env:
DOCKER_BUILDKIT: "1"
run: docker build --target test .
lint:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- env:
DOCKER_BUILDKIT: "1"
run: docker build --target lint .
docker:
runs-on: ubuntu-latest
needs: [test, lint]
steps:
- uses: actions/checkout@v2
- name: Docker build
env:
DOCKER_BUILDKIT: "1"
run: docker build .

51
.github/workflows/release.yml vendored Normal file
View File

@@ -0,0 +1,51 @@
name: release
on:
release:
types: [published]
paths:
- .github/workflows/release.yml
- cmd/**
- internal/**
- .dockerignore
- .golangci.yml
- Dockerfile
- go.mod
- go.sum
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- env:
DOCKER_BUILDKIT: "1"
run: docker build --target test .
lint:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- env:
DOCKER_BUILDKIT: "1"
run: docker build --target lint .
docker:
runs-on: ubuntu-latest
needs: [test, lint]
steps:
- uses: actions/checkout@v2
- uses: docker/setup-qemu-action@v1
- uses: docker/setup-buildx-action@v1
- name: Dockerhub login
run: echo ${{ secrets.DOCKERHUB_PASSWORD }} | docker login -u qmcgaw --password-stdin 2>&1
- name: Docker buildx
run: |
docker buildx build \
--platform=linux/amd64,linux/386,linux/arm64,linux/arm/v6,linux/arm/v7,linux/s390x,linux/ppc64le \
--build-arg BUILD_DATE=`date -u +"%Y-%m-%dT%H:%M:%SZ"` \
--build-arg COMMIT=`git rev-parse --short HEAD` \
--build-arg VERSION=${GITHUB_REF##*/} \
-t qmcgaw/private-internet-access:${GITHUB_REF##*/} \
-t qmcgaw/gluetun:${GITHUB_REF##*/} \
--push \
.

70
.golangci.yml Normal file
View File

@@ -0,0 +1,70 @@
linters-settings:
maligned:
suggest-new: true
misspell:
locale: US
issues:
exclude-rules:
- path: _test\.go
linters:
- dupl
- maligned
- path: internal/unix/constants\.go
linters:
- golint
text: don't use ALL_CAPS in Go names; use CamelCase
linters:
disable-all: true
enable:
- asciicheck
- bodyclose
- deadcode
- dogsled
- dupl
- errcheck
- exhaustive
- exportloopref
- gci
- gochecknoglobals
- gochecknoinits
- gocognit
- goconst
- gocritic
- gocyclo
- godot
- goheader
- goimports
- golint
- gomnd
- goprintffuncname
- gosec
- gosimple
- govet
- ineffassign
- interfacer
- lll
- maligned
- misspell
- nakedret
- nestif
- noctx
- nolintlint
- prealloc
- rowserrcheck
- scopelint
- sqlclosecheck
- staticcheck
- structcheck
- typecheck
- unconvert
- unparam
- unused
- varcheck
- whitespace
run:
skip-dirs:
- .devcontainer
- .github
- doc

View File

@@ -1,20 +0,0 @@
dist: xenial
sudo: required
git:
quiet: true
depth: 1
env:
global:
- DOCKER_REPO=qmcgaw/private-internet-access
before_install:
- curl -fsSL https://get.docker.com | sh
- echo '{"experimental":"enabled"}' | sudo tee /etc/docker/daemon.json
- mkdir -p $HOME/.docker
- echo '{"experimental":"enabled"}' | sudo tee $HOME/.docker/config.json
- sudo service docker start
install:
- docker run --rm --privileged multiarch/qemu-user-static --reset -p yes
- docker buildx create --name xbuilder --use
script: bash ci.sh
after_success:
- curl -X POST https://hooks.microbadger.com/images/$DOCKER_REPO/tQFy7AxtSUNANPe6aoVChYdsI_I= || exit 0

View File

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

View File

@@ -1,71 +1,144 @@
ARG ALPINE_VERSION=3.11
ARG GO_VERSION=1.13.7
FROM golang:${GO_VERSION}-alpine${ALPINE_VERSION} AS builder
RUN apk --update add git
WORKDIR /tmp/gobuild
ENV CGO_ENABLED=0
COPY go.mod go.sum ./
RUN go mod download 2>&1
COPY internal/ ./internal/
COPY cmd/main.go .
RUN go test ./...
RUN go build -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/private-internet-access-docker" \
org.opencontainers.image.documentation="https://github.com/qdm12/private-internet-access-docker" \
org.opencontainers.image.source="https://github.com/qdm12/private-internet-access-docker" \
org.opencontainers.image.title="PIA client" \
org.opencontainers.image.description="VPN client to tunnel to private internet access servers using OpenVPN, IPtables, DNS over TLS and Alpine Linux"
ENV USER= \
PASSWORD= \
ENCRYPTION=strong \
PROTOCOL=udp \
REGION="CA Montreal" \
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 \
BLOCK_MALICIOUS=on \
BLOCK_SURVEILLANCE=off \
BLOCK_ADS=off \
UNBLOCK= \
EXTRA_SUBNETS= \
PORT_FORWARDING=off \
PORT_FORWARDING_STATUS_FILE="/forwarded_port" \
TINYPROXY=off \
TINYPROXY_LOG=Info \
TINYPROXY_PORT=8888 \
TINYPROXY_USER= \
TINYPROXY_PASSWORD= \
SHADOWSOCKS=off \
SHADOWSOCKS_LOG=on \
SHADOWSOCKS_PORT=8388 \
SHADOWSOCKS_PASSWORD= \
TZ=
ENTRYPOINT /entrypoint
EXPOSE 8888/tcp 8388/tcp 8388/udp
HEALTHCHECK --interval=3m --timeout=3s --start-period=20s --retries=1 CMD /entrypoint healthcheck
RUN apk add -q --progress --no-cache --update openvpn ca-certificates iptables unbound tinyproxy tzdata && \
echo "http://dl-cdn.alpinelinux.org/alpine/edge/testing" >> /etc/apk/repositories && \
apk add -q --progress --no-cache --update shadowsocks-libev && \
rm -rf /*.zip /var/cache/apk/* /etc/unbound/* /usr/sbin/unbound-anchor /usr/sbin/unbound-checkconf /usr/sbin/unbound-control /usr/sbin/unbound-control-setup /usr/sbin/unbound-host /etc/tinyproxy/tinyproxy.conf && \
adduser nonrootuser -D -H --uid 1000 && \
chown nonrootuser -R /etc/unbound /etc/tinyproxy && \
chmod 700 /etc/unbound /etc/tinyproxy
COPY --from=builder --chown=1000:1000 /tmp/gobuild/entrypoint /entrypoint
ARG ALPINE_VERSION=3.12
ARG GO_VERSION=1.15
FROM --platform=$BUILDPLATFORM golang:${GO_VERSION}-alpine${ALPINE_VERSION} AS base
RUN apk --update add git
ENV CGO_ENABLED=0
WORKDIR /tmp/gobuild
COPY go.mod go.sum ./
RUN go mod download
COPY cmd/ ./cmd/
COPY internal/ ./internal/
FROM --platform=$BUILDPLATFORM base AS test
ENV CGO_ENABLED=1
RUN apk --update add g++
RUN go test -race ./...
FROM --platform=$BUILDPLATFORM base AS lint
ARG GOLANGCI_LINT_VERSION=v1.34.1
RUN wget -O- -nv https://raw.githubusercontent.com/golangci/golangci-lint/master/install.sh | \
sh -s -- -b /usr/local/bin ${GOLANGCI_LINT_VERSION}
COPY .golangci.yml ./
RUN golangci-lint run --timeout=10m
FROM --platform=$BUILDPLATFORM base AS build
COPY --from=qmcgaw/xcputranslate /xcputranslate /usr/local/bin/xcputranslate
ARG TARGETPLATFORM
ARG VERSION=unknown
ARG BUILD_DATE="an unknown date"
ARG COMMIT=unknown
COPY cmd/ ./cmd/
COPY internal/ ./internal/
RUN GOARCH="$(echo ${TARGETPLATFORM} | xcputranslate -field arch)" \
GOARM="$(echo ${TARGETPLATFORM} | xcputranslate -field arm)" \
go build -trimpath -ldflags="-s -w \
-X 'main.version=$VERSION' \
-X 'main.buildDate=$BUILD_DATE' \
-X 'main.commit=$COMMIT' \
" -o entrypoint cmd/gluetun/main.go
FROM alpine:${ALPINE_VERSION}
ARG VERSION=unknown
ARG BUILD_DATE="an unknown date"
ARG COMMIT=unknown
LABEL \
org.opencontainers.image.authors="quentin.mcgaw@gmail.com" \
org.opencontainers.image.created=$BUILD_DATE \
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 \
VERSION_INFORMATION=on \
PROTOCOL=udp \
OPENVPN_VERBOSITY=1 \
OPENVPN_ROOT=no \
OPENVPN_TARGET_IP= \
OPENVPN_IPV6=off \
TZ= \
PUID= \
PGID= \
PUBLICIP_FILE="/tmp/gluetun/ip" \
# PIA, Windscribe, Surfshark, Cyberghost, Vyprvpn, NordVPN, PureVPN only
OPENVPN_USER= \
OPENVPN_PASSWORD= \
USER_SECRETFILE=/run/secrets/openvpn_user \
PASSWORD_SECRETFILE=/run/secrets/openvpn_password \
REGION= \
# PIA only
PIA_ENCRYPTION=strong \
PORT_FORWARDING=off \
PORT_FORWARDING_STATUS_FILE="/tmp/gluetun/forwarded_port" \
# Mullvad and PureVPN only
COUNTRY= \
# Mullvad, PureVPN, Windscribe only
CITY= \
# Windscribe only
SERVER_HOSTNAME= \
# Mullvad only
ISP= \
OWNED=no \
# Mullvad and Windscribe only
PORT= \
# Cyberghost only
CYBERGHOST_GROUP="Premium UDP Europe" \
OPENVPN_CLIENTCRT_SECRETFILE=/run/secrets/openvpn_clientcrt \
OPENVPN_CLIENTKEY_SECRETFILE=/run/secrets/openvpn_clientkey \
# 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 \
FIREWALL_VPN_INPUT_PORTS= \
FIREWALL_INPUT_PORTS= \
FIREWALL_OUTBOUND_SUBNETS= \
FIREWALL_DEBUG=off \
# HTTP proxy
HTTPPROXY= \
HTTPPROXY_LOG=off \
HTTPPROXY_PORT=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_PORT=8388 \
SHADOWSOCKS_PASSWORD= \
SHADOWSOCKS_PASSWORD_SECRETFILE=/run/secrets/shadowsocks_password \
SHADOWSOCKS_METHOD=chacha20-ietf-poly1305 \
UPDATER_PERIOD=0
ENTRYPOINT ["/entrypoint"]
EXPOSE 8000/tcp 8888/tcp 8388/tcp 8388/udp
HEALTHCHECK --interval=5s --timeout=5s --start-period=10s --retries=1 CMD /entrypoint healthcheck
RUN apk add -q --progress --no-cache --update openvpn ca-certificates iptables ip6tables unbound tzdata && \
rm -rf /var/cache/apk/* /etc/unbound/* /usr/sbin/unbound-* && \
deluser openvpn && \
deluser unbound && \
mkdir /gluetun
# TODO remove once SAN is added to PIA servers certificates, see https://github.com/pia-foss/manual-connections/issues/10
ENV GODEBUG=x509ignoreCN=0
COPY --from=build /tmp/gobuild/entrypoint /entrypoint

402
README.md
View File

@@ -1,340 +1,132 @@
# Private Internet Access Client
# Gluetun VPN client
*Lightweight swiss-knife-like VPN client to tunnel to private internet access servers, using OpenVPN, iptables, DNS over TLS, ShadowSocks, Tinyproxy and more*
*Lightweight swiss-knife-like VPN client to tunnel to Private Internet Access,
Mullvad, Windscribe, Surfshark Cyberghost, VyprVPN, NordVPN, PureVPN and Privado VPN servers, using Go, OpenVPN, iptables, DNS over TLS, ShadowSocks and an HTTP proxy*
**ANNOUCEMENT**: *Total rewrite in Go: see the new features [below](#Features)* (in case something break use the image with tag `:old`)
**ANNOUNCEMENT**: *New Docker image name `qmcgaw/gluetun`*
<a href="https://hub.docker.com/r/qmcgaw/private-internet-access">
<img width="100%" height="320" src="https://raw.githubusercontent.com/qdm12/private-internet-access-docker/master/title.svg?sanitize=true">
</a>
<img height="250" src="https://raw.githubusercontent.com/qdm12/gluetun/master/title.svg?sanitize=true">
[![Size](https://img.shields.io/docker/image-size/qmcgaw/gluetun?sort=semver&label=Last%20released%20image)](https://hub.docker.com/r/qmcgaw/gluetun/tags?page=1&ordering=last_updated)
[![Size](https://img.shields.io/docker/image-size/qmcgaw/gluetun/latest?label=Latest%20image)](https://hub.docker.com/r/qmcgaw/gluetun/tags)
[![Build Status](https://travis-ci.org/qdm12/private-internet-access-docker.svg?branch=master)](https://travis-ci.org/qdm12/private-internet-access-docker)
[![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)
[![Docker Pulls](https://img.shields.io/docker/pulls/qmcgaw/gluetun.svg)](https://hub.docker.com/r/qmcgaw/gluetun)
[![GitHub last commit](https://img.shields.io/github/last-commit/qdm12/private-internet-access-docker.svg)](https://github.com/qdm12/private-internet-access-docker/issues)
[![GitHub commit activity](https://img.shields.io/github/commit-activity/y/qdm12/private-internet-access-docker.svg)](https://github.com/qdm12/private-internet-access-docker/issues)
[![GitHub issues](https://img.shields.io/github/issues/qdm12/private-internet-access-docker.svg)](https://github.com/qdm12/private-internet-access-docker/issues)
![Last release](https://img.shields.io/github/release/qdm12/gluetun?label=Last%20release)
![Last Docker tag](https://img.shields.io/docker/v/qmcgaw/gluetun?sort=semver&label=Last%20Docker%20tag)
![GitHub Release Date](https://img.shields.io/github/release-date/qdm12/gluetun?label=Last%20release%20date)
[![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)
![Commits since release](https://img.shields.io/github/commits-since/qdm12/gluetun/latest?sort=semver)
[![GitHub last commit](https://img.shields.io/github/last-commit/qdm12/gluetun.svg)](https://github.com/qdm12/gluetun/commits)
<details><summary>Click to show base components</summary><p>
[![Lines of code](https://img.shields.io/tokei/lines/github/qdm12/gluetun)](https://github.com/qdm12/gluetun)
- [Alpine 3.11](https://alpinelinux.org) for a tiny image (37MB of packages, 6.7MB of Go binary and 5.6MB for Alpine)
- [OpenVPN 2.4.8](https://pkgs.alpinelinux.org/package/v3.11/main/x86_64/openvpn) to tunnel to PIA servers
- [IPtables 1.8.3](https://pkgs.alpinelinux.org/package/v3.11/main/x86_64/iptables) enforces the container to communicate only through the VPN or with other containers in its virtual network (acts as a killswitch)
- [Unbound 1.9.6](https://pkgs.alpinelinux.org/package/v3.11/main/x86_64/unbound) configured with Cloudflare's [1.1.1.1](https://1.1.1.1) DNS over TLS (configurable with 5 different providers)
- [Files and blocking lists built periodically](https://github.com/qdm12/updated/tree/master/files) used with Unbound (see `BLOCK_MALICIOUS`, `BLOCK_SURVEILLANCE` and `BLOCK_ADS` environment variables)
- [TinyProxy 1.10.0](https://pkgs.alpinelinux.org/package/v3.11/main/x86_64/tinyproxy)
- [Shadowsocks 3.3.4](https://pkgs.alpinelinux.org/package/edge/testing/x86/shadowsocks-libev)
## Quick links
</p></details>
- Problem or suggestion?
- [Start a discussion](https://github.com/qdm12/gluetun/discussions)
- [Create an issue](https://github.com/qdm12/gluetun/issues)
- [Check the Wiki](https://github.com/qdm12/gluetun/wiki)
- [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)
## Features
- **New features**
- Choice to block ads, malicious and surveillance at the DNS level
- All program output streams are merged (openvpn, unbound, shadowsocks, tinyproxy, etc.)
- Choice of DNS over TLS provider(s)
- Possibility of split horizon DNS by selecting multiple DNS over TLS providers
- Download block lists and cryptographic files at start instead of at build time
- Can work as a Kubernetes sidecar container, thanks @rorph
- Pick a random region if no region is given, thanks @rorph
- <details><summary>Configure everything with environment variables</summary><p>
- [Destination region](https://www.privateinternetaccess.com/pages/network)
- Internet protocol
- Level of encryption
- PIA Username and password
- DNS over TLS
- DNS blocking: ads, malicious, surveillance
- Internal firewall
- Socks5 proxy
- Web HTTP proxy
</p></details>
- Connect
- [Other containers to it](https://github.com/qdm12/private-internet-access-docker#connect-to-it)
- [LAN devices to it](https://github.com/qdm12/private-internet-access-docker#connect-to-it)
- Killswitch using *iptables* to allow traffic only with needed PIA servers and LAN devices
- Port forwarding
- Compatible with amd64, i686 (32 bit), **ARM** 64 bit, ARM 32 bit v6 and v7, ppc64le and even that s390x 🎆
- Sub programs drop root privileges once launched: Openvpn, Unbound, Shadowsocks, Tinyproxy
- Based on Alpine 3.12 for a small Docker image of 52MB
- Supports **Private Internet Access**, **Mullvad**, **Windscribe**, **Surfshark**, **Cyberghost**, **Vyprvpn**, **NordVPN**, **PureVPN** and **Privado** servers
- Supports Openvpn only for now
- DNS over TLS baked in with service provider(s) of your choice
- DNS fine blocking of malicious/ads/surveillance hostnames and IP addresses, with live update every 24 hours
- Choose the vpn network protocol, `udp` or `tcp`
- Built in firewall kill switch to allow traffic only with needed the VPN servers and LAN devices
- Built in Shadowsocks proxy (protocol based on SOCKS5 with an encryption layer, tunnels TCP+UDP)
- Built in HTTP proxy (tunnels HTTP and HTTPS through TCP)
- [Connect other containers to it](https://github.com/qdm12/gluetun/wiki/Connect-to-gluetun)
- [Connect LAN devices to it](https://github.com/qdm12/gluetun/wiki/Connect-to-gluetun)
- Compatible with amd64, i686 (32 bit), **ARM** 64 bit, ARM 32 bit v6 and v7, and even s390x as well as ppc64le 🎆
- VPN server side port forwarding for Private Internet Access and Vyprvpn
- 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
- Can work as a Kubernetes sidecar container, thanks @rorph
## Setup
1. <details><summary>Requirements</summary><p>
- A Private Internet Access **username** and **password** - [Sign up](https://www.privateinternetaccess.com/pages/buy-vpn/)
- Docker API 1.25 to support `init`
- If you use Docker Compose, docker-compose >= 1.22.0, to support `init: true`
- <details><summary>External firewall requirements, if you have one</summary><p>
- At start only
- Allow outbound TCP 443 to github.com and privateinternetaccess.com
- If `DOT=on`, allow outbound TCP 853 to 1.1.1.1 to allow Unbound to resolve the PIA domain name.
- If `DOT=off`, allow outbound UDP 53 to your DNS provider to resolve the PIA domain name.
- For UDP strong encryption, allow outbound UDP 1197 to the corresponding VPN server IPs
- For UDP normal encryption, allow outbound UDP 1198 to the corresponding VPN server IPs
- For TCP strong encryption, allow outbound TCP 501 to the corresponding VPN server IPs
- For TCP normal encryption, allow outbound TCP 502 to the corresponding VPN server IPs
- If `SHADOWSOCKS=on`, allow inbound TCP 8388 and UDP 8388 from your LAN
- If `TINYPROXY=on`, allow inbound TCP 8888 from your LAN
</p></details>
</p></details>
1. On some devices you may need to setup your tunnel kernel module on your host with `insmod /lib/modules/tun.ko` or `modprobe tun`
- [Synology users Wiki page](https://github.com/qdm12/gluetun/wiki/Synology-setup)
1. Launch the container with:
```bash
docker run -d --init --name=pia --cap-add=NET_ADMIN \
-e REGION="CA Montreal" -e USER=js89ds7 -e PASSWORD=8fd9s239G \
qmcgaw/private-internet-access
docker run -d --name gluetun --cap-add=NET_ADMIN \
-e VPNSP="private internet access" -e REGION="CA Montreal" \
-e OPENVPN_USER=js89ds7 -e OPENVPN_PASSWORD=8fd9s239G \
-v /yourpath:/gluetun \
qmcgaw/gluetun
```
or use [docker-compose.yml](https://github.com/qdm12/private-internet-access-docker/blob/master/docker-compose.yml) with:
or use [docker-compose.yml](https://github.com/qdm12/gluetun/blob/master/docker-compose.yml) with:
```bash
echo "your openvpn username" > openvpn_user
echo "your openvpn password" > openvpn_password
docker-compose up -d
```
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)
- Use `-p 8388:8388/tcp -p 8388:8388/udp` to access the SOCKS5 proxy (and put your LAN in `EXTRA_SUBNETS` environment variable)
- Pass additional arguments to *openvpn* using Docker's command function (commands after the image name)
1. You can update the image with `docker pull qmcgaw/private-internet-access:latest`. There are also docker tags available:
- `qmcgaw/private-internet-access:v1` linked to the [v1 release](https://github.com/qdm12/private-internet-access-docker/releases/tag/v1.0)
You should probably check the many [environment variables](https://github.com/qdm12/gluetun/wiki/Environment-variables) available to adapt the container to your needs.
## Testing
## Further setup
Check the PIA IP address matches your expectations
The following points are all optional but should give you insights on all the possibilities with this container.
```sh
docker run --rm --network=container:pia alpine:3.10 wget -qO- https://ipinfo.io
```
## Environment variables
| Environment variable | Default | Description |
| --- | --- | --- |
| `REGION` | `CA Montreal` | One of the [PIA regions](https://www.privateinternetaccess.com/pages/network/) |
| `PROTOCOL` | `udp` | `tcp` or `udp` |
| `ENCRYPTION` | `strong` | `normal` or `strong` |
| `USER` | | Your PIA username |
| `PASSWORD` | | Your PIA password |
| `DOT` | `on` | `on` or `off`, to activate DNS over TLS to 1.1.1.1 |
| `DOT_PROVIDERS` | `cloudflare` | Comma delimited list of DNS over TLS providers from `cloudflare`, `google`, `quad9`, `quadrant`, `cleanbrowsing`, `securedns`, `libredns` |
| `DOT_CACHING` | `on` | Unbound caching feature, `on` or `off` |
| `DOT_PRIVATE_ADDRESS` | All IPv4 and IPv6 CIDRs private ranges | Comma separated list of CIDRs or single IP addresses. Note that the default setting prevents DNS rebinding |
| `DOT_VERBOSITY` | `1` | Unbound verbosity level from `0` to `5` (full debug) |
| `DOT_VERBOSITY_DETAILS` | `0` | Unbound details verbosity level from `0` to `4` |
| `DOT_VALIDATION_LOGLEVEL` | `0` | Unbound validation log level from `0` to `2` |
| `BLOCK_MALICIOUS` | `on` | `on` or `off`, blocks malicious hostnames and IPs |
| `BLOCK_SURVEILLANCE` | `off` | `on` or `off`, blocks surveillance hostnames and IPs |
| `BLOCK_ADS` | `off` | `on` or `off`, blocks ads hostnames and IPs |
| `UNBLOCK` | | comma separated string (i.e. `web.com,web2.ca`) to unblock hostnames |
| `EXTRA_SUBNETS` | | comma separated subnets allowed in the container firewall (i.e. `192.168.1.0/24,192.168.10.121,10.0.0.5/28`) |
| `PORT_FORWARDING` | `off` | Set to `on` to forward a port on PIA server |
| `PORT_FORWARDING_STATUS_FILE` | `/forwarded_port` | File path to store the forwarded port number |
| `TINYPROXY` | `off` | `on` or `off`, to enable the internal HTTP proxy tinyproxy |
| `TINYPROXY_LOG` | `Info` | `Info`, `Connect`, `Notice`, `Warning`, `Error` or `Critical` |
| `TINYPROXY_PORT` | `8888` | `1024` to `65535` internal port for HTTP proxy |
| `TINYPROXY_USER` | | Username to use to connect to the HTTP proxy |
| `TINYPROXY_PASSWORD` | | Passsword to use to connect to the HTTP proxy |
| `SHADOWSOCKS` | `off` | `on` or `off`, to enable the internal SOCKS5 proxy Shadowsocks |
| `SHADOWSOCKS_LOG` | `on` | `on` or `off` to enable logging for Shadowsocks |
| `SHADOWSOCKS_PORT` | `8388` | `1024` to `65535` internal port for SOCKS5 proxy |
| `SHADOWSOCKS_PASSWORD` | | Passsword to use to connect to the SOCKS5 proxy |
| `TZ` | | Specify a timezone to use i.e. `Europe/London` |
## 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 PIA</summary><p>
Add `network_mode: "service:pia"` to your *docker-compose.yml* (no need for `depends_on`)
</p></details>
- <details><summary>Connect other containers to PIA</summary><p>
Add `--network=container:pia` when launching the container, provided PIA is already running
</p></details>
- <details><summary>Connect containers from another docker-compose.yml</summary><p>
Add `network_mode: "container:pia"` to your *docker-compose.yml*, provided PIA 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 PIA 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 PIA 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 PIA 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 `chacha20-ietf-poly1305`
1. If you set `SHADOWSOCKS_LOG` to `on`, more information will be logged in the Docker logs
</p></details>
- <details><summary>Access ports of containers connected to PIA</summary><p>
In example, to access port `8000` of container `xyz` and `9000` of container `abc` connected to PIA,
publish ports `8000` and `9000` for the PIA container and access them as you would with any other container
</p></details>
- <details><summary>Access ports of containers connected to PIA, 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 PIA, publish port `8000` and `9000` for the PIA container.
The docker-compose.yml file would look like:
```yml
version: '3.7'
services:
pia:
image: qmcgaw/private-internet-access
container_name: pia
init: true
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:pia"
xyz:
image: xyz
container_name: xyz
network_mode: "service:pia"
```
</p></details>
## Port forwarding
By setting `PORT_FORWARDING` environment variable to `on`, the forwarded port will be read and written to the file specified in `PORT_FORWARDING_STATUS_FILE` (by default, this is set to `/forwarded_port`). If the location for this file does not exist, it will be created automatically.
You can mount this file as a volume to read it from other containers.
Note that not all regions support port forwarding.
## For the paranoids
- You can review the code which consists in:
- [Dockerfile](https://github.com/qdm12/private-internet-access-docker/blob/master/Dockerfile)
- [main.go](https://github.com/qdm12/private-internet-access-docker/blob/master/cmd/main.go), the main code entrypoint
- [internal package](https://github.com/qdm12/private-internet-access-docker/blob/master/internal)
- [github.com/qdm12/golibs](https://github.com/qdm12/golibs) dependency
- [github.com/qdm12/files](https://github.com/qdm12/files) for files downloaded at start (DNS root hints, block lists, etc.)
- Build the image yourself:
```bash
docker build -t qmcgaw/private-internet-access https://github.com/qdm12/private-internet-access-docker.git
```
- The download and parsing of all needed files is done at start (openvpn config files, Unbound files, block lists, etc.)
- Use `-e ENCRYPTION=strong -e BLOCK_MALICIOUS=on`
- You can test DNSSEC using [internet.nl/connection](https://www.internet.nl/connection/)
- Check DNS leak tests with [https://www.dnsleaktest.com](https://www.dnsleaktest.com)
- DNS Leaks tests might not work because of [this](https://github.com/qdm12/cloudflare-dns-server#verify-dns-connection) (*TLDR*: DNS server is a local caching intermediary)
## Troubleshooting
- If openvpn fails to start, you may need to:
- Install the tun kernel module on your host with `insmod /lib/modules/tun.ko` or `modprobe tun`
- Add `--device=/dev/net/tun` to your docker run command (equivalent for docker-compose, kubernetes, etc.)
- Fallback to a previous Docker image tags:
- `v1` tag, stable shell scripting based (no support)
- `old` tag, latest shell scripting version (no support)
- `v2`... waiting for `latest` to become more stable
- Fallback to a precise previous version
1. Clone the repository on your machine
```sh
git clone https://github.com/qdm12/private-internet-access-docker.git pia
cd pia
```
1. Look up which commit you want to go back to [here](https://github.com/qdm12/private-internet-access-docker/commits/master), i.e. `942cc7d4d10545b6f5f89c907b7dd1dbc39368e0`
1. Revert to this commit locally
```sh
git reset --hard 942cc7d4d10545b6f5f89c907b7dd1dbc39368e0
```
1. Build the Docker image
```sh
docker build -t qmcgaw/private-internet-access .
```
- Use [Docker secrets](https://github.com/qdm12/gluetun/wiki/Docker-secrets) to read your credentials instead of environment variables
- [Test your setup](https://github.com/qdm12/gluetun/wiki/Test-your-setup)
- [How to connect other containers and devices to Gluetun](https://github.com/qdm12/gluetun/wiki/Connect-to-gluetun)
- [VPN server side port forwarding](https://github.com/qdm12/gluetun/wiki/Port-forwarding)
- [HTTP control server](https://github.com/qdm12/gluetun/wiki/HTTP-Control-server) to automate things, restart Openvpn etc.
- Update the image with `docker pull qmcgaw/gluetun:latest`. See this [Wiki document](https://github.com/qdm12/gluetun/wiki/Docker-image-tags) for Docker tags available.
## Development
### Using VSCode and Docker
1. Install [Docker](https://docs.docker.com/install)
- On Windows, share a drive with Docker Desktop and have the project on that partition
1. With [Visual Studio Code](https://code.visualstudio.com/download), install the [remote containers extension](https://marketplace.visualstudio.com/items?itemName=ms-vscode-remote.remote-containers)
1. In Visual Studio Code, press on `F1` and select `Remote-Containers: Open Folder in Container...`
1. Your dev environment is ready to go!... and it's running in a container :+1:
## TODOs
- Support other VPN providers
- Mullvad
- Windscribe
- Gotify support for notificactions
- Periodic update of malicious block lists with Unbound restart
- Improve healthcheck
- Check IP address belongs to selected region
- Check for DNS provider somehow if this is even possible
- Support for other VPN protocols
- Wireguard (wireguard-go)
- Show new versions/commits at start
- Colors & emojis
- Setup
- Logging streams
- More unit tests
- Write in Go
- DNS over TLS to replace Unbound
- HTTP proxy to replace tinyproxy
- use [go-Shadowsocks2](https://github.com/shadowsocks/go-shadowsocks2)
- DNS over HTTPS, maybe use [github.com/likexian/doh-go](https://github.com/likexian/doh-go)
- use [iptables-go](https://github.com/coreos/go-iptables) to replace iptables
- wireguard-go
- Openvpn to replace openvpn
- 💻 [Contribute with code](https://github.com/qdm12/gluetun/wiki/Development) ([existing contributors 👍](https://github.com/qdm12/gluetun/blob/master/.github/CONTRIBUTING.md#Contributors))
- [List of issues and feature requests](https://github.com/qdm12/gluetun/issues)
- [Kanban board](https://github.com/qdm12/gluetun/projects/1)
## License
This repository is under an [MIT license](https://github.com/qdm12/private-internet-access-docker/master/license)
[![MIT](https://img.shields.io/github/license/qdm12/gluetun)](https://github.com/qdm12/gluetun/master/LICENSE)
## Support
- Sponsor me on [Github](https://github.com/sponsors/qdm12) or donate to [paypal.me/qmcgaw](https://www.paypal.me/qmcgaw)
[![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)
- Contribute to the issues and discussions on Github
- Many thanks to @Frepke, @Ralph521, G. Mendez, M. Otmar Weber, J. Perez, A. Cooper and **others** for supporting me financially 🥇👍
## Metadata
[![GitHub commit activity](https://img.shields.io/github/commit-activity/y/qdm12/gluetun.svg)](https://github.com/qdm12/gluetun/commits)
[![GitHub closed PRs](https://img.shields.io/github/issues-pr-closed/qdm12/gluetun.svg)](https://github.com/qdm12/gluetun/pulls?q=is%3Apr+is%3Aclosed)
[![GitHub issues](https://img.shields.io/github/issues/qdm12/gluetun.svg)](https://github.com/qdm12/gluetun/issues)
[![GitHub closed issues](https://img.shields.io/github/issues-closed/qdm12/gluetun.svg)](https://github.com/qdm12/gluetun/issues?q=is%3Aissue+is%3Aclosed)
![Visitors count](https://visitor-badge.laobi.icu/badge?page_id=gluetun.readme)
![GitHub stars](https://img.shields.io/github/stars/qdm12/gluetun?style=social)
![GitHub watchers](https://img.shields.io/github/watchers/qdm12/gluetun?style=social)
![Contributors](https://img.shields.io/github/contributors/qdm12/gluetun)
![GitHub forks](https://img.shields.io/github/forks/qdm12/gluetun?style=social)
![Code size](https://img.shields.io/github/languages/code-size/qdm12/gluetun)
![GitHub repo size](https://img.shields.io/github/repo-size/qdm12/gluetun)
[![dockeri.co](https://dockeri.co/image/qmcgaw/gluetun)](https://hub.docker.com/r/qmcgaw/gluetun)
![Docker Layers for latest](https://img.shields.io/microbadger/layers/qmcgaw/gluetun/latest?label=Docker%20image%20layers)
![Go version](https://img.shields.io/github/go-mod/go-version/qdm12/gluetun)

21
ci.sh
View File

@@ -1,21 +0,0 @@
#!/bin/bash
if [ "$TRAVIS_PULL_REQUEST" = "true" ] || [ "$TRAVIS_BRANCH" != "master" ]; then
docker buildx build \
--progress plain \
--platform=linux/amd64,linux/386,linux/arm64,linux/arm/v7,linux/arm/v6,linux/ppc64le,linux/s390x \
.
exit $?
fi
echo $DOCKER_PASSWORD | docker login -u qmcgaw --password-stdin &> /dev/null
TAG="${TRAVIS_TAG:-latest}"
echo "Building Docker images for \"$DOCKER_REPO:$TAG\""
docker buildx build \
--progress plain \
--platform=linux/amd64,linux/386,linux/arm64,linux/arm/v7,linux/arm/v6,linux/ppc64le,linux/s390x \
--build-arg BUILD_DATE=`date -u +"%Y-%m-%dT%H:%M:%SZ"` \
--build-arg VCS_REF=`git rev-parse --short HEAD` \
--build-arg VERSION=$TAG \
-t $DOCKER_REPO:$TAG \
--push \
.

443
cmd/gluetun/main.go Normal file
View File

@@ -0,0 +1,443 @@
package main
import (
"context"
"fmt"
"net"
"net/http"
nativeos "os"
"os/signal"
"strings"
"sync"
"syscall"
"time"
"github.com/qdm12/dns/pkg/unbound"
"github.com/qdm12/gluetun/internal/alpine"
"github.com/qdm12/gluetun/internal/cli"
"github.com/qdm12/gluetun/internal/constants"
"github.com/qdm12/gluetun/internal/dns"
"github.com/qdm12/gluetun/internal/firewall"
"github.com/qdm12/gluetun/internal/healthcheck"
"github.com/qdm12/gluetun/internal/httpproxy"
gluetunLogging "github.com/qdm12/gluetun/internal/logging"
"github.com/qdm12/gluetun/internal/models"
"github.com/qdm12/gluetun/internal/openvpn"
"github.com/qdm12/gluetun/internal/params"
"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/storage"
"github.com/qdm12/gluetun/internal/unix"
"github.com/qdm12/gluetun/internal/updater"
versionpkg "github.com/qdm12/gluetun/internal/version"
"github.com/qdm12/golibs/command"
"github.com/qdm12/golibs/logging"
"github.com/qdm12/golibs/os"
"github.com/qdm12/golibs/os/user"
"github.com/qdm12/updated/pkg/dnscrypto"
)
//nolint:gochecknoglobals
var (
version = "unknown"
commit = "unknown"
buildDate = "an unknown date"
)
func main() {
buildInfo := models.BuildInformation{
Version: version,
Commit: commit,
BuildDate: buildDate,
}
ctx := context.Background()
ctx, cancel := context.WithCancel(ctx)
logger, err := logging.NewLogger(logging.ConsoleEncoding, logging.InfoLevel)
if err != nil {
fmt.Println(err)
nativeos.Exit(1)
}
args := nativeos.Args
os := os.New()
osUser := user.New()
unix := unix.New()
cli := cli.New()
errorCh := make(chan error)
go func() {
errorCh <- _main(ctx, buildInfo, args, logger, os, osUser, unix, cli)
}()
signalsCh := make(chan nativeos.Signal, 1)
signal.Notify(signalsCh,
syscall.SIGINT,
syscall.SIGTERM,
nativeos.Interrupt,
)
select {
case signal := <-signalsCh:
logger.Warn("Caught OS signal %s, shutting down", signal)
case err := <-errorCh:
close(errorCh)
if err == nil { // expected exit such as healthcheck
nativeos.Exit(0)
}
logger.Error(err)
}
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")
}
nativeos.Exit(1)
}
//nolint:gocognit,gocyclo
func _main(background context.Context, buildInfo models.BuildInformation,
args []string, logger logging.Logger, os os.OS, osUser user.OSUser, unix unix.Unix,
cli cli.CLI) error {
if len(args) > 1 { // cli operation
switch args[1] {
case "healthcheck":
return cli.HealthCheck(background)
case "clientkey":
return cli.ClientKey(args[2:], os.OpenFile)
case "openvpnconfig":
return cli.OpenvpnConfig(os)
case "update":
return cli.Update(args[2:], os)
default:
return fmt.Errorf("command %q is unknown", args[1])
}
}
ctx, cancel := context.WithCancel(background)
defer cancel()
const clientTimeout = 15 * time.Second
httpClient := &http.Client{Timeout: clientTimeout}
// Create configurators
alpineConf := alpine.NewConfigurator(os.OpenFile, osUser)
ovpnConf := openvpn.NewConfigurator(logger, os, unix)
dnsCrypto := dnscrypto.New(httpClient, "", "")
const cacertsPath = "/etc/ssl/certs/ca-certificates.crt"
dnsConf := unbound.NewConfigurator(logger, os.OpenFile, dnsCrypto,
"/etc/unbound", "/usr/sbin/unbound", cacertsPath)
routingConf := routing.NewRouting(logger)
firewallConf := firewall.NewConfigurator(logger, routingConf, os.OpenFile)
streamMerger := command.NewStreamMerger()
paramsReader := params.NewReader(logger, os)
fmt.Println(gluetunLogging.Splash(buildInfo))
printVersions(ctx, logger, map[string]func(ctx context.Context) (string, error){
"OpenVPN": ovpnConf.Version,
"Unbound": dnsConf.Version,
"IPtables": firewallConf.Version,
})
allSettings, err := settings.GetAllSettings(paramsReader)
if err != nil {
return err
}
logger.Info(allSettings.String())
if err := os.MkdirAll("/tmp/gluetun", 0644); err != nil {
return err
}
if err := os.MkdirAll("/gluetun", 0644); err != nil {
return err
}
// TODO run this in a loop or in openvpn to reload from file without restarting
storage := storage.New(logger, os, constants.ServersData)
allServers, err := storage.SyncServers(constants.GetAllServers())
if err != nil {
return err
}
// Should never change
puid, pgid := allSettings.System.PUID, allSettings.System.PGID
const defaultUsername = "nonrootuser"
nonRootUsername, err := alpineConf.CreateUser(defaultUsername, puid)
if err != nil {
return err
}
if nonRootUsername != defaultUsername {
logger.Info("using existing username %s corresponding to user id %d", nonRootUsername, puid)
}
if err := os.Chown("/etc/unbound", puid, pgid); err != nil {
return err
}
if allSettings.Firewall.Debug {
firewallConf.SetDebug()
routingConf.SetDebug()
}
defaultInterface, defaultGateway, err := routingConf.DefaultRoute()
if err != nil {
return err
}
localSubnet, err := routingConf.LocalSubnet()
if err != nil {
return err
}
defaultIP, err := routingConf.DefaultIP()
if err != nil {
return err
}
firewallConf.SetNetworkInformation(defaultInterface, defaultGateway, localSubnet, defaultIP)
if err := routingConf.Setup(); err != nil {
return err
}
defer func() {
routingConf.SetVerbose(false)
if err := routingConf.TearDown(); err != nil {
logger.Error(err)
}
}()
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 := ovpnConf.CheckTUN(); err != nil {
logger.Warn(err)
err = ovpnConf.CreateTUN()
if err != nil {
return err
}
}
tunnelReadyCh := make(chan struct{})
dnsReadyCh := make(chan struct{})
defer close(tunnelReadyCh)
defer close(dnsReadyCh)
if allSettings.Firewall.Enabled {
err := firewallConf.SetEnabled(ctx, true) // disabled by default
if err != nil {
return err
}
}
for _, vpnPort := range allSettings.Firewall.VPNInputPorts {
err = firewallConf.SetAllowedPort(ctx, vpnPort, string(constants.TUN))
if err != nil {
return err
}
}
for _, port := range allSettings.Firewall.InputPorts {
err = firewallConf.SetAllowedPort(ctx, port, defaultInterface)
if err != nil {
return err
}
} // TODO move inside firewall?
wg := &sync.WaitGroup{}
wg.Add(1)
go collectStreamLines(ctx, wg, streamMerger, logger, tunnelReadyCh)
openvpnLooper := openvpn.NewLooper(allSettings.OpenVPN, nonRootUsername, puid, pgid, allServers,
ovpnConf, firewallConf, routingConf, logger, httpClient, os.OpenFile, streamMerger, cancel)
wg.Add(1)
// wait for restartOpenvpn
go openvpnLooper.Run(ctx, wg)
updaterLooper := updater.NewLooper(allSettings.Updater,
allServers, storage, openvpnLooper.SetServers, httpClient, logger)
wg.Add(1)
// wait for updaterLooper.Restart() or its ticket launched with RunRestartTicker
go updaterLooper.Run(ctx, wg)
unboundLooper := dns.NewLooper(dnsConf, allSettings.DNS, httpClient,
logger, streamMerger, nonRootUsername, puid, pgid)
wg.Add(1)
// wait for unboundLooper.Restart or its ticker launched with RunRestartTicker
go unboundLooper.Run(ctx, wg, dnsReadyCh)
publicIPLooper := publicip.NewLooper(
httpClient, logger, allSettings.PublicIP, puid, pgid, os)
wg.Add(1)
go publicIPLooper.Run(ctx, wg)
wg.Add(1)
go publicIPLooper.RunRestartTicker(ctx, wg)
httpProxyLooper := httpproxy.NewLooper(logger, allSettings.HTTPProxy)
wg.Add(1)
go httpProxyLooper.Run(ctx, wg)
shadowsocksLooper := shadowsocks.NewLooper(allSettings.ShadowSocks, logger)
wg.Add(1)
go shadowsocksLooper.Run(ctx, wg)
wg.Add(1)
go routeReadyEvents(ctx, wg, buildInfo, tunnelReadyCh, dnsReadyCh,
unboundLooper, updaterLooper, publicIPLooper, routingConf, logger, httpClient,
allSettings.VersionInformation, allSettings.OpenVPN.Provider.PortForwarding.Enabled, openvpnLooper.PortForward,
)
controlServerAddress := fmt.Sprintf("0.0.0.0:%d", allSettings.ControlServer.Port)
controlServerLogging := allSettings.ControlServer.Log
httpServer := server.New(controlServerAddress, controlServerLogging,
logger, buildInfo, openvpnLooper, unboundLooper, updaterLooper, publicIPLooper)
wg.Add(1)
go httpServer.Run(ctx, wg)
healthcheckServer := healthcheck.NewServer(
constants.HealthcheckAddress, logger)
wg.Add(1)
go healthcheckServer.Run(ctx, wg)
// Start openvpn for the first time in a blocking call
// until openvpn is launched
_, _ = openvpnLooper.SetStatus(constants.Running) // TODO option to disable with variable
<-ctx.Done()
if allSettings.OpenVPN.Provider.PortForwarding.Enabled {
logger.Info("Clearing forwarded port status file %s", allSettings.OpenVPN.Provider.PortForwarding.Filepath)
if err := os.Remove(string(allSettings.OpenVPN.Provider.PortForwarding.Filepath)); err != nil {
logger.Error(err)
}
}
wg.Wait()
return nil
}
func printVersions(ctx context.Context, logger logging.Logger,
versionFunctions map[string]func(ctx context.Context) (string, error)) {
const timeout = 5 * time.Second
ctx, cancel := context.WithTimeout(ctx, timeout)
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, wg *sync.WaitGroup,
streamMerger command.StreamMerger,
logger logging.Logger, tunnelReadyCh chan<- struct{}) {
defer wg.Done()
// Blocking line merging paramsReader for openvpn and unbound
logger.Info("Launching standard output merger")
streamMerger.CollectLines(ctx, func(line string) {
line, level := gluetunLogging.PostProcessLine(line)
if line == "" {
return
}
switch level {
case logging.DebugLevel:
logger.Debug(line)
case logging.InfoLevel:
logger.Info(line)
case logging.WarnLevel:
logger.Warn(line)
case logging.ErrorLevel:
logger.Error(line)
}
switch {
case strings.Contains(line, "Initialization Sequence Completed"):
tunnelReadyCh <- struct{}{}
case strings.Contains(line, "TLS Error: TLS key negotiation failed to occur within 60 seconds (check your network connectivity)"): //nolint:lll
logger.Warn("This means that either...")
logger.Warn("1. The VPN server IP address you are trying to connect to is no longer valid, see https://github.com/qdm12/gluetun/wiki/Update-servers-information") //nolint:lll
logger.Warn("2. The VPN server crashed, try changing region")
logger.Warn("3. Your Internet connection is not working, ensure it works")
logger.Warn("Feel free to create an issue at https://github.com/qdm12/gluetun/issues/new/choose")
}
}, func(err error) {
logger.Warn(err)
})
}
func routeReadyEvents(ctx context.Context, wg *sync.WaitGroup, buildInfo models.BuildInformation,
tunnelReadyCh, dnsReadyCh <-chan struct{},
unboundLooper dns.Looper, updaterLooper updater.Looper, publicIPLooper publicip.Looper,
routing routing.Routing, logger logging.Logger, httpClient *http.Client,
versionInformation, portForwardingEnabled bool, startPortForward func(vpnGateway net.IP)) {
defer wg.Done()
tickerWg := &sync.WaitGroup{}
// for linters only
var restartTickerContext context.Context
var restartTickerCancel context.CancelFunc = func() {}
for {
select {
case <-ctx.Done():
restartTickerCancel() // for linters only
tickerWg.Wait()
return
case <-tunnelReadyCh: // blocks until openvpn is connected
if unboundLooper.GetSettings().Enabled {
_, _ = unboundLooper.SetStatus(constants.Running)
}
restartTickerCancel() // stop previous restart tickers
tickerWg.Wait()
restartTickerContext, restartTickerCancel = context.WithCancel(ctx)
tickerWg.Add(2) //nolint:gomnd
go unboundLooper.RunRestartTicker(restartTickerContext, tickerWg)
go updaterLooper.RunRestartTicker(restartTickerContext, tickerWg)
vpnDestination, err := routing.VPNDestinationIP()
if err != nil {
logger.Warn(err)
} else {
logger.Info("VPN routing IP address: %s", vpnDestination)
}
if portForwardingEnabled {
// vpnGateway required only for PIA
vpnGateway, err := routing.VPNLocalGatewayIP()
if err != nil {
logger.Error(err)
}
logger.Info("VPN gateway IP address: %s", vpnGateway)
startPortForward(vpnGateway)
}
case <-dnsReadyCh:
// Runs the Public IP getter job once
_, _ = publicIPLooper.SetStatus(constants.Running)
if !versionInformation {
break
}
message, err := versionpkg.GetMessage(ctx, buildInfo, httpClient)
if err != nil {
logger.Error(err)
break
}
logger.Info(message)
}
}
}

View File

@@ -1,190 +0,0 @@
package main
import (
"context"
"fmt"
"net"
"os"
"time"
"github.com/qdm12/golibs/command"
"github.com/qdm12/golibs/files"
libhealthcheck "github.com/qdm12/golibs/healthcheck"
"github.com/qdm12/golibs/logging"
"github.com/qdm12/golibs/network"
"github.com/qdm12/golibs/signals"
"github.com/qdm12/private-internet-access-docker/internal/constants"
"github.com/qdm12/private-internet-access-docker/internal/dns"
"github.com/qdm12/private-internet-access-docker/internal/env"
"github.com/qdm12/private-internet-access-docker/internal/firewall"
"github.com/qdm12/private-internet-access-docker/internal/healthcheck"
"github.com/qdm12/private-internet-access-docker/internal/openvpn"
"github.com/qdm12/private-internet-access-docker/internal/params"
"github.com/qdm12/private-internet-access-docker/internal/pia"
"github.com/qdm12/private-internet-access-docker/internal/settings"
"github.com/qdm12/private-internet-access-docker/internal/shadowsocks"
"github.com/qdm12/private-internet-access-docker/internal/splash"
"github.com/qdm12/private-internet-access-docker/internal/tinyproxy"
)
const (
uid, gid = 1000, 1000
)
func main() {
logger, err := logging.NewLogger(logging.ConsoleEncoding, logging.InfoLevel, -1)
if err != nil {
panic(err)
}
if libhealthcheck.Mode(os.Args) {
if err := healthcheck.HealthCheck(); err != nil {
fmt.Println(err)
os.Exit(1)
}
os.Exit(0)
}
paramsReader := params.NewParamsReader(logger)
fmt.Println(splash.Splash(paramsReader))
e := env.New(logger)
client := network.NewClient(15 * time.Second)
// Create configurators
fileManager := files.NewFileManager()
ovpnConf := openvpn.NewConfigurator(logger, fileManager)
dnsConf := dns.NewConfigurator(logger, client, fileManager)
firewallConf := firewall.NewConfigurator(logger, fileManager)
piaConf := pia.NewConfigurator(client, fileManager, firewallConf, logger)
tinyProxyConf := tinyproxy.NewConfigurator(fileManager, logger)
shadowsocksConf := shadowsocks.NewConfigurator(fileManager, logger)
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
streamMerger := command.NewStreamMerger(ctx)
e.PrintVersion("OpenVPN", ovpnConf.Version)
e.PrintVersion("Unbound", dnsConf.Version)
e.PrintVersion("IPtables", firewallConf.Version)
e.PrintVersion("TinyProxy", tinyProxyConf.Version)
e.PrintVersion("ShadowSocks", shadowsocksConf.Version)
allSettings, err := settings.GetAllSettings(paramsReader)
e.FatalOnError(err)
logger.Info(allSettings.String())
if err := ovpnConf.CheckTUN(); err != nil {
logger.Warn(err)
err = ovpnConf.CreateTUN()
e.FatalOnError(err)
}
err = ovpnConf.WriteAuthFile(allSettings.PIA.User, allSettings.PIA.Password, uid, gid)
e.FatalOnError(err)
// Temporarily reset chain policies allowing Kubernetes sidecar to
// successfully restart the container. Without this, the existing rules will
// pre-exist, preventing the nslookup of the PIA region address. These will
// simply be redundant at Docker runtime as they will already be set this way
// Thanks to @npawelek https://github.com/npawelek
err = firewallConf.AcceptAll()
e.FatalOnError(err)
go func() {
// Blocking line merging reader for all programs: openvpn, tinyproxy, unbound and shadowsocks
logger.Info("Launching standard output merger")
err = streamMerger.CollectLines(func(line string) { logger.Info(line) })
e.FatalOnError(err)
}()
if allSettings.DNS.Enabled {
initialDNSToUse := constants.DNSProviderMapping()[allSettings.DNS.Providers[0]]
dnsConf.UseDNSInternally(initialDNSToUse.IPs[0])
err = dnsConf.DownloadRootHints(uid, gid)
e.FatalOnError(err)
err = dnsConf.DownloadRootKey(uid, gid)
e.FatalOnError(err)
err = dnsConf.MakeUnboundConf(allSettings.DNS, uid, gid)
e.FatalOnError(err)
stream, waitFn, err := dnsConf.Start(allSettings.DNS.VerbosityDetailsLevel)
e.FatalOnError(err)
go func() {
e.FatalOnError(waitFn())
}()
go streamMerger.Merge("unbound", stream)
dnsConf.UseDNSInternally(net.IP{127, 0, 0, 1}) // use Unbound
err = dnsConf.UseDNSSystemWide(net.IP{127, 0, 0, 1}) // use Unbound
e.FatalOnError(err)
err = dnsConf.WaitForUnbound()
e.FatalOnError(err)
}
VPNIPs, port, err := piaConf.BuildConf(allSettings.PIA.Region, allSettings.OpenVPN.NetworkProtocol, allSettings.PIA.Encryption, uid, gid)
e.FatalOnError(err)
defaultInterface, defaultGateway, defaultSubnet, err := firewallConf.GetDefaultRoute()
e.FatalOnError(err)
err = firewallConf.AddRoutesVia(allSettings.Firewall.AllowedSubnets, defaultGateway, defaultInterface)
e.FatalOnError(err)
err = firewallConf.Clear()
e.FatalOnError(err)
err = firewallConf.BlockAll()
e.FatalOnError(err)
err = firewallConf.CreateGeneralRules()
e.FatalOnError(err)
err = firewallConf.CreateVPNRules(constants.TUN, VPNIPs, defaultInterface, port, allSettings.OpenVPN.NetworkProtocol)
e.FatalOnError(err)
err = firewallConf.CreateLocalSubnetsRules(defaultSubnet, allSettings.Firewall.AllowedSubnets, defaultInterface)
e.FatalOnError(err)
if allSettings.TinyProxy.Enabled {
err = tinyProxyConf.MakeConf(allSettings.TinyProxy.LogLevel, allSettings.TinyProxy.Port, allSettings.TinyProxy.User, allSettings.TinyProxy.Password, uid, gid)
e.FatalOnError(err)
err = firewallConf.AllowAnyIncomingOnPort(allSettings.TinyProxy.Port)
e.FatalOnError(err)
stream, waitFn, err := tinyProxyConf.Start()
e.FatalOnError(err)
go func() {
if err := waitFn(); err != nil {
logger.Error(err)
}
}()
go streamMerger.Merge("tinyproxy", stream)
}
if allSettings.ShadowSocks.Enabled {
err = shadowsocksConf.MakeConf(allSettings.ShadowSocks.Port, allSettings.ShadowSocks.Password, uid, gid)
e.FatalOnError(err)
err = firewallConf.AllowAnyIncomingOnPort(allSettings.ShadowSocks.Port)
e.FatalOnError(err)
stream, waitFn, err := shadowsocksConf.Start("0.0.0.0", allSettings.ShadowSocks.Port, allSettings.ShadowSocks.Password, allSettings.ShadowSocks.Log)
e.FatalOnError(err)
go func() {
if err := waitFn(); err != nil {
logger.Error(err)
}
}()
go streamMerger.Merge("shadowsocks", stream)
}
if allSettings.PIA.PortForwarding.Enabled {
time.AfterFunc(10*time.Second, func() {
port, err := piaConf.GetPortForward()
if err != nil {
logger.Error("port forwarding:", err)
}
if err := piaConf.WritePortForward(allSettings.PIA.PortForwarding.Filepath, port); err != nil {
logger.Error("port forwarding:", err)
}
if err := piaConf.AllowPortForwardFirewall(constants.TUN, port); err != nil {
logger.Error("port forwarding:", err)
}
})
}
stream, waitFn, err := ovpnConf.Start()
e.FatalOnError(err)
go streamMerger.Merge("openvpn", stream)
go signals.WaitForExit(func(signal string) int {
logger.Warn("Caught OS signal %s, shutting down", signal)
time.Sleep(100 * time.Millisecond) // wait for other processes to exit
return 0
})
e.FatalOnError(waitFn())
}

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

BIN
doc/paypal.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.6 KiB

BIN
doc/sponsors.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

BIN
doc/windscribe.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

View File

@@ -1,37 +1,31 @@
version: "3.7"
services:
pia:
build: https://github.com/qdm12/private-internet-access-docker.git
image: qmcgaw/private-internet-access
container_name: pia
gluetun:
image: qmcgaw/gluetun
container_name: gluetun
cap_add:
- NET_ADMIN
network_mode: bridge
init: true
ports:
- 8888:8888/tcp
- 8388:8388/tcp
- 8388:8388/udp
- 8888:8888/tcp # HTTP proxy
- 8388:8388/tcp # Shadowsocks
- 8388:8388/udp # Shadowsocks
- 8000:8000/tcp # Built-in HTTP control server
# command:
volumes:
- /yourpath:/gluetun
secrets:
- openvpn_user
- openvpn_password
environment:
- USER=js89ds7
- PASSWORD=8fd9s239G
- ENCRYPTION=strong
- PROTOCOL=udp
- REGION=CA Montreal
- DOT=on
- DOT_PROVIDERS=cloudflare
- BLOCK_MALICIOUS=on
- BLOCK_SURVEILLANCE=off
- BLOCK_ADS=off
- UNBLOCK=
- EXTRA_SUBNETS=
- TINYPROXY=off
- TINYPROXY_LOG=Info
- TINYPROXY_USER=
- TINYPROXY_PASSWORD=
- SHADOWSOCKS=off
- SHADOWSOCKS_LOG=on
- SHADOWSOCKS_PORT=8388
- SHADOWSOCKS_PASSWORD=
# More variables are available, see the readme table
- VPNSP=private internet access
# Timezone for accurate logs times
- TZ=
restart: always
secrets:
openvpn_user:
file: ./openvpn_user
openvpn_password:
file: ./openvpn_password

18
go.mod
View File

@@ -1,10 +1,16 @@
module github.com/qdm12/private-internet-access-docker
module github.com/qdm12/gluetun
go 1.13
go 1.15
require (
github.com/kyokomi/emoji v2.1.0+incompatible
github.com/qdm12/golibs v0.0.0-20200208153322-66b2eb719e21
github.com/stretchr/testify v1.4.0
golang.org/x/sys v0.0.0-20190412213103-97732733099d
github.com/fatih/color v1.10.0
github.com/golang/mock v1.4.4
github.com/kyokomi/emoji v2.2.4+incompatible
github.com/qdm12/dns v1.4.0-rc4
github.com/qdm12/golibs v0.0.0-20210102020307-17bc97def973
github.com/qdm12/ss-server v0.1.0
github.com/qdm12/updated v0.0.0-20210102005021-dd457d77f94a
github.com/stretchr/testify v1.6.1
github.com/vishvananda/netlink v1.1.0
golang.org/x/sys v0.0.0-20201223074533-0d417f636930
)

95
go.sum
View File

@@ -4,12 +4,23 @@ github.com/PuerkitoBio/purell v1.1.0 h1:rmGxhojJlM0tuKtfdvliR84CFHljx9ag64t2xmVk
github.com/PuerkitoBio/purell v1.1.0/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0=
github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578 h1:d+Bc7a5rLufV/sSk/8dngufqelfh6jnri85riMAaF/M=
github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578/go.mod h1:uGdkoq3SwY9Y+13GIhn11/XLaGBb4BfwItxLd5jeuXE=
github.com/alcortesm/tgz v0.0.0-20161220082320-9c5fe88206d7/go.mod h1:6zEj6s6u/ghQa61ZWa/C2Aw3RkjiTBOix7dkqa1VLIs=
github.com/anmitsu/go-shlex v0.0.0-20161002113705-648efa622239/go.mod h1:2FmKhYUyUczH0OGQWaF5ceTx0UBShxjsH6f8oGKYe2c=
github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5/go.mod h1:wHh0iHkYZB8zMSxRWpUBQtwG5a7fFgvEO+odwuTv2gs=
github.com/asaskevich/govalidator v0.0.0-20180720115003-f9ffefc3facf h1:eg0MeVzsP1G42dRafH3vf+al2vQIJU0YHX+1Tw87oco=
github.com/asaskevich/govalidator v0.0.0-20180720115003-f9ffefc3facf/go.mod h1:lB+ZfQJz7igIIfQNfa7Ml4HSf2uFQQRzpGGRXenZAgY=
github.com/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/emirpasic/gods v1.12.0/go.mod h1:YfzfFFoVP/catgzJb4IKIqXjX78Ha8FMSDh3ymbK86o=
github.com/fatih/color v1.9.0 h1:8xPHl4/q1VyqGIPif1F+1V3Y3lSmrq01EabUW3CoW5s=
github.com/fatih/color v1.9.0/go.mod h1:eQcE1qtQxscV5RaZvpXrrb8Drkc3/DdQ+uUYCNjL+zU=
github.com/fatih/color v1.10.0 h1:s36xzo75JdqLaaWoiEHk767eHiwo0598uUxyfiPkDsg=
github.com/fatih/color v1.10.0/go.mod h1:ELkj/draVOlAH/xkhN6mQ50Qd0MPOk5AAr3maGEBuJM=
github.com/flynn/go-shlex v0.0.0-20150515145356-3f9db97f8568/go.mod h1:xEzjJPgXI435gkrCt3MPfRiAkVrwSbHsst4LCFVfpJc=
github.com/gliderlabs/ssh v0.2.2/go.mod h1:U7qILu1NlMHj9FlMhZLlkCdDnU1DBEAqr0aevW3Awn0=
github.com/globalsign/mgo v0.0.0-20180905125535-1ca0a4f7cbcb h1:D4uzjWwKYQ5XnAvUbuvHW93esHg7F8N/OYeBBcJoTr0=
github.com/globalsign/mgo v0.0.0-20180905125535-1ca0a4f7cbcb/go.mod h1:xkRDCp4j0OGD1HRkm4kmhM+pmpv3AKq5SU7GMg4oO/Q=
github.com/go-openapi/analysis v0.0.0-20180825180245-b006789cd277/go.mod h1:k70tL6pCuVxPJOHXQ+wIac1FUrvNkHolPie/cLEU6hI=
@@ -35,41 +46,80 @@ github.com/go-openapi/swag v0.17.0 h1:iqrgMg7Q7SvtbWLlltPrkMs0UBJI6oTSs79JFRUi88
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/gomodule/redigo v2.0.0+incompatible/go.mod h1:B4C85qUVwatsJoIUNIfCRsp7qO0iAmpGFZ4EELWSbC4=
github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI=
github.com/google/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/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/kevinburke/ssh_config v0.0.0-20190725054713-01f96b0aa0cd/go.mod h1:CT57kijsi8u/K/BOFA39wgDQJ9CxiF4nAY/ojJ6r6mM=
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI=
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
github.com/kr/pty v1.1.8/go.mod h1:O1sed60cT9XZ5uDucP5qwvh+TE3NnUj51EiZO/lmSfw=
github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
github.com/kyokomi/emoji v2.1.0+incompatible h1:+DYU2RgpI6OHG4oQkM5KlqD3Wd3UPEsX8jamTo1Mp6o=
github.com/kyokomi/emoji v2.1.0+incompatible/go.mod h1:mZ6aGCD7yk8j6QY6KICwnZ2pxoszVseX1DNoGtU2tBA=
github.com/kyokomi/emoji v2.2.4+incompatible h1:np0woGKwx9LiHAQmwZx79Oc0rHpNw3o+3evou4BEPv4=
github.com/kyokomi/emoji v2.2.4+incompatible/go.mod h1:mZ6aGCD7yk8j6QY6KICwnZ2pxoszVseX1DNoGtU2tBA=
github.com/mailru/easyjson v0.0.0-20180823135443-60711f1a8329 h1:2gxZ0XQIU/5z3Z3bUBu+FXuk2pFbkN6tcwi/pjyaDic=
github.com/mailru/easyjson v0.0.0-20180823135443-60711f1a8329/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc=
github.com/mattn/go-colorable v0.1.4 h1:snbPLB8fVfU9iwbbo30TPtbLRzwWu6aJS6Xh4eaaviA=
github.com/mattn/go-colorable v0.1.4/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE=
github.com/mattn/go-colorable v0.1.8 h1:c1ghPdyEDarC70ftn0y+A/Ee++9zz8ljHG1b13eJ0s8=
github.com/mattn/go-colorable v0.1.8/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc=
github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s=
github.com/mattn/go-isatty v0.0.11 h1:FxPOTFNqGkuDUGi3H/qkUbQO4ZiBa2brKq5r0l8TGeM=
github.com/mattn/go-isatty v0.0.11/go.mod h1:PhnuNfih5lzO57/f3n+odYbM4JtupLOxQOAqxQCu2WE=
github.com/mattn/go-isatty v0.0.12 h1:wuysRhFDzyxgEmMf5xjvJ2M9dZoWAXNNr5LSBS7uHXY=
github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU=
github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
github.com/mitchellh/mapstructure v1.1.2 h1:fmNYVwqnSfB9mZU6OS2O6GsXM+wcskZDuKQzvN1EDeE=
github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
github.com/mr-tron/base58 v1.1.3 h1:v+sk57XuaCKGXpWtVBX8YJzO7hMGx4Aajh4TQbdEFdc=
github.com/mr-tron/base58 v1.1.3/go.mod h1:BinMc/sQntlIE1frQmRFPUoPA1Zkr8VRgBdjWI2mNwc=
github.com/pborman/uuid v1.2.0/go.mod h1:X/NO0urCmaxf9VXbdlT7C2Yzkj2IKimNn4k+gtPdI/k=
github.com/pelletier/go-buffruneio v0.2.0/go.mod h1:JkE26KsDizTr40EUHkXVtNPvgGtbSNq5BcowyYOWdKo=
github.com/phayes/permbits v0.0.0-20190612203442-39d7c581d2ee h1:P6U24L02WMfj9ymZTxl7CxS73JC99x3ukk+DBkgQGQs=
github.com/phayes/permbits v0.0.0-20190612203442-39d7c581d2ee/go.mod h1:3uODdxMgOaPYeWU7RzZLxVtJHZ/x1f/iHkBZuKJDzuY=
github.com/pkg/errors v0.8.1 h1:iURUrRGxPUNPdy5/HRSm+Yj6okJ6UtLINN0Q9M4+h3I=
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/qdm12/golibs v0.0.0-20200208153322-66b2eb719e21 h1:Nza/Ar6tPYhDzkiNzbaJZHl4+GUXTqbtjGXuWenkqpQ=
github.com/qdm12/golibs v0.0.0-20200208153322-66b2eb719e21/go.mod h1:YULaFjj6VGmhjak6f35sUWwEleHUmngN5IQ3kdvd6XE=
github.com/qdm12/dns v1.4.0-rc4 h1:pCcFMqismbktPQX7yrtdmJZq30Y05JBfWRTXY1ZLVFw=
github.com/qdm12/dns v1.4.0-rc4/go.mod h1:JhUKBhuDRYBUQ2XwW/jbeWx/qS0sSJjIFjGTCFGP5I8=
github.com/qdm12/golibs v0.0.0-20201227203847-2fd99ffdfdba/go.mod h1:pikkTN7g7zRuuAnERwqW1yAFq6pYmxrxpjiwGvb0Ysc=
github.com/qdm12/golibs v0.0.0-20210102020307-17bc97def973 h1:5YeJALmDjvg2wSi6XB8MpQQekbT/eBnwGahJrh01HHQ=
github.com/qdm12/golibs v0.0.0-20210102020307-17bc97def973/go.mod h1:pikkTN7g7zRuuAnERwqW1yAFq6pYmxrxpjiwGvb0Ysc=
github.com/qdm12/ss-server v0.1.0 h1:WV9MkHCDEWRwe4WpnYFeR/zcZAxYoTbfntLDnw9AQ50=
github.com/qdm12/ss-server v0.1.0/go.mod h1:ABVUkxubboL3vqBkOwDV9glX1/x7SnYrckBe5d+M/zw=
github.com/qdm12/updated v0.0.0-20210102005021-dd457d77f94a h1:gkyP+gMEeBgMgyRYGrVNcoy6cL1065IvXsyfB6xboIc=
github.com/qdm12/updated v0.0.0-20210102005021-dd457d77f94a/go.mod h1:bbJGxEYCnsA8WU4vBcXYU6mOoHyzdP458FIKP4mfLJM=
github.com/riobard/go-bloom v0.0.0-20200614022211-cdc8013cb5b3 h1:f/FNXud6gA3MNr8meMVVGxhp+QBTqY91tM8HjEuMjGg=
github.com/riobard/go-bloom v0.0.0-20200614022211-cdc8013cb5b3/go.mod h1:HgjTstvQsPGkxUsCd2KWxErBblirPizecHcpD3ffK+s=
github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
github.com/sergi/go-diff v1.0.0/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAmXWZgo=
github.com/src-d/gcfg v1.4.0/go.mod h1:p/UMsR43ujA89BJY9duynAwIpvqEujIH/jFlfL7jWoI=
github.com/stretchr/objx v0.1.0 h1:4G4v2dO3VZwixGIRoQ5Lfboy6nUhCyYzaqnIAPPhYs4=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.2.0/go.mod h1:qt09Ya8vawLte6SNmTgCsAVtYtaKzEcn8ATUoHMkEqE=
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/stretchr/testify v1.4.0 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=
github.com/vishvananda/netlink v1.1.0 h1:1iyaYNBLmP6L0220aDnYQpo1QEV4t4hJ+xEEhhJH8j0=
github.com/vishvananda/netlink v1.1.0/go.mod h1:cTgwzPIzzgDAYoQrMm0EdrjRUBkTqKYppBueQtXaqoE=
github.com/vishvananda/netns v0.0.0-20191106174202-0a2b9b5464df h1:OviZH7qLw/7ZovXvuNyL3XQl8UFofeikI1NW1Gypu7k=
github.com/vishvananda/netns v0.0.0-20191106174202-0a2b9b5464df/go.mod h1:JP3t17pCcGlemwknint6hfoeCVQrEMVwxRLRjXpq+BU=
github.com/xanzy/ssh-agent v0.2.1/go.mod h1:mLlQY/MoOhWBj+gOGMQkOeiEvkx+8pJSI+0Bx9h2kr4=
github.com/yl2chen/cidranger v1.0.2/go.mod h1:9U1yz7WPYDwf0vpNWFaeRh0bjwz5RVgRy/9UEQfHl0g=
go.uber.org/atomic v1.5.0 h1:OI5t8sDa1Or+q8AeE+yKeB/SDYioSHAgcVljj9JIETY=
go.uber.org/atomic v1.5.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ=
go.uber.org/multierr v1.3.0 h1:sFPn2GLc3poCkfrpIXGhBD2X0CMIo4Q/zSULXrj/+uc=
@@ -78,36 +128,61 @@ go.uber.org/tools v0.0.0-20190618225709-2cfd321de3ee h1:0mgffUl7nfd+FpvXMVz4IDEa
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=
golang.org/x/crypto v0.0.0-20190219172222-a4c6cb3142f2/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20190701094942-4def268fd1a4/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20200117160349-530e935923ad/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20200728195943-123391ffb6de h1:ikNHVSjEfnvz6sxdSPCaPt572qowuyMDMJLLm3Db3ig=
golang.org/x/crypto v0.0.0-20200728195943-123391ffb6de/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20201221181555-eec23a3978ad h1:DN0cp81fZ3njFcrLCytUHRSUkqBjfTo4Tx9RJTWs0EY=
golang.org/x/crypto v0.0.0-20201221181555-eec23a3978ad/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I=
golang.org/x/lint v0.0.0-20190930215403-16217165b5de h1:5hukYrvBGR8/eNkX5mdUezrA6JiaEZDtJb9Ei+1LlBs=
golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc=
golang.org/x/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/sync v0.0.0-20190423024810-112230192c58/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-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-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/text v0.3.0 h1:g61tztE5qeGQ89tm6NTjjM9VPIm088od1l6aSorWRWg=
golang.org/x/sys v0.0.0-20190606203320-7fc4e5ec1444/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201223074533-0d417f636930 h1:vRgIt+nup/B/BwIS0g2oC0haq0iqbV3ZA+u6+0TlNCo=
golang.org/x/sys v0.0.0-20201223074533-0d417f636930/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw=
golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
golang.org/x/tools v0.0.0-20190729092621-ff9f1409240a/go.mod h1:jcCCGcm9btYwXyDqrUWc6MKQKKGJCWEQ3AfLSRIbEuI=
golang.org/x/tools v0.0.0-20191029041327-9cc4af7d6b2c/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191029190741-b9c20aec41a5 h1:hKsoRgsbwY1NafxrwTs+k64bikrLBkAgPir1TNCj3Zs=
golang.org/x/tools v0.0.0-20191029190741-b9c20aec41a5/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
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=

22
internal/alpine/alpine.go Normal file
View File

@@ -0,0 +1,22 @@
package alpine
import (
"github.com/qdm12/golibs/os"
"github.com/qdm12/golibs/os/user"
)
type Configurator interface {
CreateUser(username string, uid int) (createdUsername string, err error)
}
type configurator struct {
openFile os.OpenFileFunc
osUser user.OSUser
}
func NewConfigurator(openFile os.OpenFileFunc, osUser user.OSUser) Configurator {
return &configurator{
openFile: openFile,
osUser: osUser,
}
}

41
internal/alpine/users.go Normal file
View File

@@ -0,0 +1,41 @@
package alpine
import (
"fmt"
"os"
"os/user"
)
// CreateUser creates a user in Alpine with the given UID.
func (c *configurator) CreateUser(username string, uid int) (createdUsername string, err error) {
UIDStr := fmt.Sprintf("%d", uid)
u, err := c.osUser.LookupID(UIDStr)
_, 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 u.Username, nil
}
u, err = c.osUser.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)
}
file, err := c.openFile("/etc/passwd", os.O_APPEND|os.O_WRONLY, 0644)
if err != nil {
return "", fmt.Errorf("cannot create user: %w", 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()
}

20
internal/cli/cli.go Normal file
View File

@@ -0,0 +1,20 @@
package cli
import (
"context"
"github.com/qdm12/golibs/os"
)
type CLI interface {
ClientKey(args []string, openFile os.OpenFileFunc) error
HealthCheck(ctx context.Context) error
OpenvpnConfig(os os.OS) error
Update(args []string, os os.OS) error
}
type cli struct{}
func New() CLI {
return &cli{}
}

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

@@ -0,0 +1,41 @@
package cli
import (
"flag"
"fmt"
"io/ioutil"
"strings"
"github.com/qdm12/gluetun/internal/constants"
"github.com/qdm12/golibs/os"
)
func (c *cli) ClientKey(args []string, openFile os.OpenFileFunc) error {
flagSet := flag.NewFlagSet("clientkey", flag.ExitOnError)
filepath := flagSet.String("path", string(constants.ClientKey), "file path to the client.key file")
if err := flagSet.Parse(args); err != nil {
return err
}
file, err := openFile(*filepath, os.O_RDONLY, 0)
if err != nil {
return err
}
data, err := ioutil.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,20 @@
package cli
import (
"context"
"net/http"
"time"
"github.com/qdm12/gluetun/internal/constants"
"github.com/qdm12/gluetun/internal/healthcheck"
)
func (c *cli) HealthCheck(ctx context.Context) error {
const timeout = 10 * time.Second
httpClient := &http.Client{Timeout: timeout}
healthchecker := healthcheck.NewChecker(httpClient)
ctx, cancel := context.WithTimeout(ctx, timeout)
defer cancel()
const url = "http://" + constants.HealthcheckAddress
return healthchecker.Check(ctx, url)
}

View File

@@ -0,0 +1,48 @@
package cli
import (
"fmt"
"strings"
"time"
"github.com/qdm12/gluetun/internal/constants"
"github.com/qdm12/gluetun/internal/params"
"github.com/qdm12/gluetun/internal/provider"
"github.com/qdm12/gluetun/internal/settings"
"github.com/qdm12/gluetun/internal/storage"
"github.com/qdm12/golibs/logging"
"github.com/qdm12/golibs/os"
)
func (c *cli) OpenvpnConfig(os os.OS) error {
logger, err := logging.NewLogger(logging.ConsoleEncoding, logging.InfoLevel)
if err != nil {
return err
}
paramsReader := params.NewReader(logger, os)
allSettings, err := settings.GetAllSettings(paramsReader)
if err != nil {
return err
}
allServers, err := storage.New(logger, os, constants.ServersData).
SyncServers(constants.GetAllServers())
if err != nil {
return err
}
providerConf := provider.New(allSettings.OpenVPN.Provider.Name, allServers, time.Now)
connection, err := providerConf.GetOpenVPNConnection(allSettings.OpenVPN.Provider.ServerSelection)
if err != nil {
return err
}
lines := providerConf.BuildConf(
connection,
allSettings.OpenVPN.Verbosity,
"nonroortuser",
allSettings.OpenVPN.Root,
allSettings.OpenVPN.Cipher,
allSettings.OpenVPN.Auth,
allSettings.OpenVPN.Provider.ExtraConfigOptions,
)
fmt.Println(strings.Join(lines, "\n"))
return nil
}

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

@@ -0,0 +1,64 @@
package cli
import (
"context"
"flag"
"fmt"
"net/http"
"time"
"github.com/qdm12/gluetun/internal/constants"
"github.com/qdm12/gluetun/internal/settings"
"github.com/qdm12/gluetun/internal/storage"
"github.com/qdm12/gluetun/internal/updater"
"github.com/qdm12/golibs/logging"
"github.com/qdm12/golibs/os"
)
func (c *cli) Update(args []string, os os.OS) error {
options := settings.Updater{CLI: true}
var flushToFile bool
flagSet := flag.NewFlagSet("update", flag.ExitOnError)
flagSet.BoolVar(&flushToFile, "file", false, "Write results to /gluetun/servers.json (for end users)")
flagSet.BoolVar(&options.Stdout, "stdout", false, "Write results to console to modify the program (for maintainers)")
flagSet.StringVar(&options.DNSAddress, "dns", "1.1.1.1", "DNS resolver address to use")
flagSet.BoolVar(&options.Cyberghost, "cyberghost", false, "Update Cyberghost servers")
flagSet.BoolVar(&options.Mullvad, "mullvad", false, "Update Mullvad servers")
flagSet.BoolVar(&options.Nordvpn, "nordvpn", false, "Update Nordvpn servers")
flagSet.BoolVar(&options.PIA, "pia", false, "Update Private Internet Access post-summer 2020 servers")
flagSet.BoolVar(&options.Privado, "privado", false, "Update Privado servers")
flagSet.BoolVar(&options.Purevpn, "purevpn", false, "Update Purevpn servers")
flagSet.BoolVar(&options.Surfshark, "surfshark", false, "Update Surfshark servers")
flagSet.BoolVar(&options.Vyprvpn, "vyprvpn", false, "Update Vyprvpn servers")
flagSet.BoolVar(&options.Windscribe, "windscribe", false, "Update Windscribe servers")
if err := flagSet.Parse(args); err != nil {
return err
}
logger, err := logging.NewLogger(logging.ConsoleEncoding, logging.InfoLevel)
if err != nil {
return err
}
if !flushToFile && !options.Stdout {
return fmt.Errorf("at least one of -file or -stdout must be specified")
}
ctx := context.Background()
const clientTimeout = 10 * time.Second
httpClient := &http.Client{Timeout: clientTimeout}
storage := storage.New(logger, os, constants.ServersData)
currentServers, err := storage.SyncServers(constants.GetAllServers())
if err != nil {
return fmt.Errorf("cannot update servers: %w", err)
}
updater := updater.New(options, httpClient, currentServers, logger)
allServers, err := updater.UpdateServers(ctx)
if err != nil {
return err
}
if flushToFile {
if err := storage.FlushToFile(allServers); err != nil {
return fmt.Errorf("cannot update servers: %w", err)
}
}
return nil
}

View File

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

View File

@@ -0,0 +1,11 @@
package constants
import "github.com/fatih/color"
func ColorUnbound() *color.Color {
return color.New(color.FgCyan)
}
func ColorOpenvpn() *color.Color {
return color.New(color.FgHiMagenta)
}

View File

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

View File

@@ -1,83 +0,0 @@
package constants
import (
"net"
"github.com/qdm12/private-internet-access-docker/internal/models"
)
const (
// Cloudflare is a DNS over TLS provider
Cloudflare models.DNSProvider = "cloudflare"
// Google is a DNS over TLS provider
Google models.DNSProvider = "google"
// Quad9 is a DNS over TLS provider
Quad9 models.DNSProvider = "quad9"
// Quadrant is a DNS over TLS provider
Quadrant models.DNSProvider = "quadrant"
// CleanBrowsing is a DNS over TLS provider
CleanBrowsing models.DNSProvider = "cleanbrowsing"
// SecureDNS is a DNS over TLS provider
SecureDNS models.DNSProvider = "securedns"
// LibreDNS is a DNS over TLS provider
LibreDNS models.DNSProvider = "libredns"
)
// DNSProviderMapping returns a constant mapping of dns provider name
// to their data such as IP addresses or TLS host name.
func DNSProviderMapping() map[models.DNSProvider]models.DNSProviderData {
return map[models.DNSProvider]models.DNSProviderData{
Cloudflare: models.DNSProviderData{
IPs: []net.IP{{1, 1, 1, 1}, {1, 0, 0, 1}},
SupportsTLS: true,
Host: models.DNSHost("cloudflare-dns.com"),
},
Google: models.DNSProviderData{
IPs: []net.IP{{8, 8, 8, 8}, {8, 8, 4, 4}},
SupportsTLS: true,
Host: models.DNSHost("dns.google"),
},
Quad9: models.DNSProviderData{
IPs: []net.IP{{9, 9, 9, 9}, {149, 112, 112, 112}},
SupportsTLS: true,
Host: models.DNSHost("dns.quad9.net"),
},
Quadrant: models.DNSProviderData{
IPs: []net.IP{{12, 159, 2, 159}},
SupportsTLS: true,
Host: models.DNSHost("dns-tls.qis.io"),
},
CleanBrowsing: models.DNSProviderData{
IPs: []net.IP{{185, 228, 168, 9}, {185, 228, 169, 9}},
SupportsTLS: true,
Host: models.DNSHost("security-filter-dns.cleanbrowsing.org"),
},
SecureDNS: models.DNSProviderData{
IPs: []net.IP{{146, 185, 167, 43}},
SupportsTLS: true,
Host: models.DNSHost("dot.securedns.eu"),
},
LibreDNS: models.DNSProviderData{
IPs: []net.IP{{116, 203, 115, 192}},
SupportsTLS: true,
Host: models.DNSHost("dot.libredns.gr"),
},
}
}
// Block lists URLs
const (
AdsBlockListHostnamesURL models.URL = "https://raw.githubusercontent.com/qdm12/files/master/ads-hostnames.updated"
AdsBlockListIPsURL models.URL = "https://raw.githubusercontent.com/qdm12/files/master/ads-ips.updated"
MaliciousBlockListHostnamesURL models.URL = "https://raw.githubusercontent.com/qdm12/files/master/malicious-hostnames.updated"
MaliciousBlockListIPsURL models.URL = "https://raw.githubusercontent.com/qdm12/files/master/malicious-ips.updated"
SurveillanceBlockListHostnamesURL models.URL = "https://raw.githubusercontent.com/qdm12/files/master/surveillance-hostnames.updated"
SurveillanceBlockListIPsURL models.URL = "https://raw.githubusercontent.com/qdm12/files/master/surveillance-ips.updated"
)
// DNS certificates to fetch
// TODO obtain from source directly, see qdm12/updated)
const (
NamedRootURL models.URL = "https://raw.githubusercontent.com/qdm12/files/master/named.root.updated"
RootKeyURL models.URL = "https://raw.githubusercontent.com/qdm12/files/master/root.key.updated"
)

View File

@@ -0,0 +1,142 @@
package constants
import (
"net"
"sort"
"github.com/qdm12/gluetun/internal/models"
)
//nolint:lll
const (
MullvadCertificate = "MIIGIzCCBAugAwIBAgIJAK6BqXN9GHI0MA0GCSqGSIb3DQEBCwUAMIGfMQswCQYDVQQGEwJTRTERMA8GA1UECAwIR290YWxhbmQxEzARBgNVBAcMCkdvdGhlbmJ1cmcxFDASBgNVBAoMC0FtYWdpY29tIEFCMRAwDgYDVQQLDAdNdWxsdmFkMRswGQYDVQQDDBJNdWxsdmFkIFJvb3QgQ0EgdjIxIzAhBgkqhkiG9w0BCQEWFHNlY3VyaXR5QG11bGx2YWQubmV0MB4XDTE4MTEwMjExMTYxMVoXDTI4MTAzMDExMTYxMVowgZ8xCzAJBgNVBAYTAlNFMREwDwYDVQQIDAhHb3RhbGFuZDETMBEGA1UEBwwKR290aGVuYnVyZzEUMBIGA1UECgwLQW1hZ2ljb20gQUIxEDAOBgNVBAsMB011bGx2YWQxGzAZBgNVBAMMEk11bGx2YWQgUm9vdCBDQSB2MjEjMCEGCSqGSIb3DQEJARYUc2VjdXJpdHlAbXVsbHZhZC5uZXQwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQCifDn75E/Zdx1qsy31rMEzuvbTXqZVZp4bjWbmcyyXqvnayRUHHoovG+lzc+HDL3HJV+kjxKpCMkEVWwjY159lJbQbm8kkYntBBREdzRRjjJpTb6haf/NXeOtQJ9aVlCc4dM66bEmyAoXkzXVZTQJ8h2FE55KVxHi5Sdy4XC5zm0wPa4DPDokNp1qm3A9Xicq3HsflLbMZRCAGuI+Jek6caHqiKjTHtujn6Gfxv2WsZ7SjerUAk+mvBo2sfKmB7octxG7yAOFFg7YsWL0AxddBWqgq5R/1WDJ9d1Cwun9WGRRQ1TLvzF1yABUerjjKrk89RCzYISwsKcgJPscaDqZgO6RIruY/xjuTtrnZSv+FXs+Woxf87P+QgQd76LC0MstTnys+AfTMuMPOLy9fMfEzs3LP0Nz6v5yjhX8ff7+3UUI3IcMxCvyxdTPClY5IvFdW7CCmmLNzakmx5GCItBWg/EIg1K1SG0jU9F8vlNZUqLKz42hWy/xB5C4QYQQ9ILdu4araPnrXnmd1D1QKVwKQ1DpWhNbpBDfE776/4xXD/tGM5O0TImp1NXul8wYsDi8g+e0pxNgY3Pahnj1yfG75Yw82spZanUH0QSNoMVMWnmV2hXGsWqypRq0pH8mPeLzeKa82gzsAZsouRD1k8wFlYA4z9HQFxqfcntTqXuwQcQIDAQABo2AwXjAdBgNVHQ4EFgQUfaEyaBpGNzsqttiSMETq+X/GJ0YwHwYDVR0jBBgwFoAUfaEyaBpGNzsqttiSMETq+X/GJ0YwCwYDVR0PBAQDAgEGMA8GA1UdEwEB/wQFMAMBAf8wDQYJKoZIhvcNAQELBQADggIBADH5izxu4V8Javal8EA4DxZxIHUsWCg5cuopB28PsyJYpyKipsBoI8+RXqbtrLLue4WQfNPZHLXlKi+A3GTrLdlnenYzXVipPd+n3vRZyofaB3Jtb03nirVWGa8FG21Xy/f4rPqwcW54lxrnnh0SA0hwuZ+b2yAWESBXPxrzVQdTWCqoFI6/aRnN8RyZn0LqRYoW7WDtKpLmfyvshBmmu4PCYSh/SYiFHgR9fsWzVcxdySDsmX8wXowuFfp8V9sFhD4TsebAaplaICOuLUgj+Yin5QzgB0F9Ci3Zh6oWwl64SL/OxxQLpzMWzr0lrWsQrS3PgC4+6JC4IpTXX5eUqfSvHPtbRKK0yLnd9hYgvZUBvvZvUFR/3/fW+mpBHbZJBu9+/1uux46M4rJ2FeaJUf9PhYCPuUj63yu0Grn0DreVKK1SkD5V6qXN0TmoxYyguhfsIPCpI1VsdaSWuNjJ+a/HIlKIU8vKp5iN/+6ZTPAg9Q7s3Ji+vfx/AhFtQyTpIYNszVzNZyobvkiMUlK+eUKGlHVQp73y6MmGIlbBbyzpEoedNU4uFu57mw4fYGHqYZmYqFaiNQv4tVrGkg6p+Ypyu1zOfIHF7eqlAOu/SyRTvZkt9VtSVEOVH7nDIGdrCC9U/g1Lqk8Td00Oj8xesyKzsG214Xd8m7/7GmJ7nXe5"
)
func MullvadCountryChoices() (choices []string) {
uniqueChoices := map[string]struct{}{}
for _, server := range MullvadServers() {
uniqueChoices[server.Country] = struct{}{}
}
for choice := range uniqueChoices {
choices = append(choices, choice)
}
sort.Slice(choices, func(i, j int) bool {
return choices[i] < choices[j]
})
return choices
}
func MullvadCityChoices() (choices []string) {
uniqueChoices := map[string]struct{}{}
for _, server := range MullvadServers() {
uniqueChoices[server.City] = struct{}{}
}
for choice := range uniqueChoices {
choices = append(choices, choice)
}
sort.Slice(choices, func(i, j int) bool {
return choices[i] < choices[j]
})
return choices
}
func MullvadISPChoices() (choices []string) {
uniqueChoices := map[string]struct{}{}
for _, server := range MullvadServers() {
uniqueChoices[server.ISP] = struct{}{}
}
for choice := range uniqueChoices {
choices = append(choices, choice)
}
sort.Slice(choices, func(i, j int) bool {
return choices[i] < choices[j]
})
return choices
}
//nolint:dupl,lll
func MullvadServers() []models.MullvadServer {
return []models.MullvadServer{
{Country: "Albania", City: "Tirana", ISP: "iRegister", Owned: false, IPs: []net.IP{{31, 171, 154, 210}}, IPsV6: []net.IP{{0x2a, 0x4, 0x27, 0xc0, 0xff, 0xff, 0xff, 0xff, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1f}}},
{Country: "Australia", City: "Adelaide", ISP: "Intergrid", Owned: false, IPs: []net.IP{{116, 206, 231, 58}}, IPsV6: []net.IP{{0x24, 0x7, 0xa0, 0x80, 0x50, 0x0, 0x0, 0x6, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1f}}},
{Country: "Australia", City: "Brisbane", ISP: "Intergrid", Owned: false, IPs: []net.IP{{43, 245, 160, 162}}, IPsV6: []net.IP{{0x24, 0x7, 0xa0, 0x80, 0x20, 0x0, 0x0, 0xa, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1f}}},
{Country: "Australia", City: "Canberra", ISP: "Intergrid", Owned: false, IPs: []net.IP{{116, 206, 229, 98}}, IPsV6: []net.IP{{0x24, 0x7, 0xa0, 0x80, 0x40, 0x0, 0x0, 0x5, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1f}}},
{Country: "Australia", City: "Melbourne", ISP: "Intergrid", Owned: false, IPs: []net.IP{{116, 206, 228, 202}, {116, 206, 228, 242}, {116, 206, 230, 98}}, IPsV6: []net.IP{{0x24, 0x7, 0xa0, 0x80, 0x30, 0x0, 0x0, 0x5, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x2f}, {0x24, 0x7, 0xa0, 0x80, 0x30, 0x0, 0x0, 0x6, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x3f}, {0x24, 0x7, 0xa0, 0x80, 0x30, 0x0, 0x0, 0x7, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x4f}}},
{Country: "Australia", City: "Perth", ISP: "Intergrid", Owned: false, IPs: []net.IP{{103, 77, 235, 66}}, IPsV6: []net.IP{{0x24, 0x0, 0xfa, 0x80, 0x0, 0x5, 0x0, 0x8, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1f}}},
{Country: "Australia", City: "Sydney", ISP: "Intergrid", Owned: false, IPs: []net.IP{{43, 245, 162, 130}, {103, 77, 232, 130}, {103, 77, 232, 146}}, IPsV6: []net.IP{{0x24, 0x0, 0xfa, 0x80, 0x0, 0x1, 0x0, 0x10, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1f}, {0x24, 0x0, 0xfa, 0x80, 0x0, 0x1, 0x0, 0x12, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x3f}, {0x24, 0x0, 0xfa, 0x80, 0x0, 0x1, 0x0, 0x15, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x2f}}},
{Country: "Australia", City: "Sydney", ISP: "M247", Owned: false, IPs: []net.IP{{89, 44, 10, 18}, {89, 44, 10, 34}, {89, 44, 10, 50}, {89, 44, 10, 194}}, IPsV6: []net.IP{{0x20, 0x1, 0xa, 0xc8, 0x0, 0x84, 0x0, 0x28, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1f}, {0x20, 0x1, 0xa, 0xc8, 0x0, 0x84, 0x0, 0x29, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x2f}, {0x20, 0x1, 0xa, 0xc8, 0x0, 0x84, 0x0, 0x30, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x3f}, {0x20, 0x1, 0xa, 0xc8, 0x0, 0x84, 0x0, 0x38, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x4f}}},
{Country: "Austria", City: "Vienna", ISP: "M247", Owned: false, IPs: []net.IP{{5, 253, 207, 34}, {86, 107, 21, 210}, {86, 107, 21, 226}, {86, 107, 21, 242}}, IPsV6: []net.IP{{0x20, 0x1, 0xa, 0xc8, 0x0, 0x29, 0x0, 0x39, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x10, 0x2f}, {0x20, 0x1, 0xa, 0xc8, 0x0, 0x29, 0x0, 0x5a, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x10, 0x1f}, {0x20, 0x1, 0xa, 0xc8, 0x0, 0x29, 0x0, 0x5b, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x10, 0x3f}, {0x20, 0x1, 0xa, 0xc8, 0x0, 0x29, 0x0, 0x5c, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x10, 0x4f}}},
{Country: "Belgium", City: "Brussels", ISP: "M247", Owned: false, IPs: []net.IP{{37, 120, 143, 138}, {37, 120, 218, 138}, {37, 120, 218, 146}, {91, 207, 57, 50}}, IPsV6: []net.IP{{0x20, 0x1, 0xa, 0xc8, 0x0, 0x27, 0x0, 0xd, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x2f}, {0x20, 0x1, 0xa, 0xc8, 0x0, 0x27, 0x0, 0x32, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x3f}, {0x20, 0x1, 0xa, 0xc8, 0x0, 0x27, 0x0, 0x54, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x4f}, {0x20, 0x1, 0xa, 0xc8, 0x0, 0x27, 0x0, 0x55, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x5f}}},
{Country: "Brazil", City: "Sao Paulo", ISP: "Heficed", Owned: false, IPs: []net.IP{{191, 101, 62, 178}}, IPsV6: []net.IP{{0x28, 0x3, 0x0, 0x80, 0x80, 0x3, 0x80, 0x4, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x2f}}},
{Country: "Brazil", City: "Sao Paulo", ISP: "Qnax", Owned: false, IPs: []net.IP{{177, 67, 80, 186}}, IPsV6: []net.IP{{0x28, 0x4, 0x53, 0x64, 0x21, 0x0, 0x0, 0x6, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1f}}},
{Country: "Bulgaria", City: "Sofia", ISP: "M247", Owned: false, IPs: []net.IP{{37, 120, 152, 114}, {37, 120, 152, 146}}, IPsV6: []net.IP{{0x20, 0x1, 0xa, 0xc8, 0x0, 0x30, 0x0, 0x19, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1f}, {0x20, 0x1, 0xa, 0xc8, 0x0, 0x30, 0x0, 0x1a, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1f}}},
{Country: "Canada", City: "Montreal", ISP: "M247", Owned: false, IPs: []net.IP{{89, 36, 78, 18}, {89, 36, 78, 34}, {89, 36, 78, 50}, {89, 36, 78, 66}, {89, 36, 78, 82}, {89, 36, 78, 98}, {89, 36, 78, 114}, {89, 36, 78, 130}}, IPsV6: []net.IP{{0x2a, 0xd, 0x56, 0x0, 0x0, 0x9, 0x0, 0xb6, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x4f}, {0x2a, 0xd, 0x56, 0x0, 0x0, 0x9, 0x0, 0xb7, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x5f}, {0x2a, 0xd, 0x56, 0x0, 0x0, 0x9, 0x0, 0xb8, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x6f}, {0x2a, 0xd, 0x56, 0x0, 0x0, 0x9, 0x0, 0xb9, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x7f}, {0x2a, 0xd, 0x56, 0x0, 0x0, 0x9, 0x0, 0xba, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x8f}, {0x2a, 0xd, 0x56, 0x0, 0x0, 0x9, 0x0, 0xc8, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1f}, {0x2a, 0xd, 0x56, 0x0, 0x0, 0x9, 0x0, 0xc9, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x2f}, {0x2a, 0xd, 0x56, 0x0, 0x0, 0x9, 0x1, 0x61, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x3f}}},
{Country: "Canada", City: "Toronto", ISP: "Amanah", Owned: false, IPs: []net.IP{{162, 219, 176, 250}}, IPsV6: []net.IP{{0x26, 0x6, 0x60, 0x80, 0x10, 0x2, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x2f}}},
{Country: "Canada", City: "Toronto", ISP: "Tzulo", Owned: false, IPs: []net.IP{{198, 54, 132, 34}, {198, 54, 132, 50}, {198, 54, 132, 66}}, IPsV6: []net.IP{{0x26, 0x7, 0x90, 0x0, 0x60, 0x0, 0x0, 0x12, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1f}, {0x26, 0x7, 0x90, 0x0, 0x60, 0x0, 0x0, 0x13, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x2f}, {0x26, 0x7, 0x90, 0x0, 0x60, 0x0, 0x0, 0x14, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x3f}}},
{Country: "Canada", City: "Vancouver", ISP: "100TB", Owned: false, IPs: []net.IP{{172, 83, 40, 34}, {172, 83, 40, 38}}, IPsV6: []net.IP{{0x26, 0x7, 0xf7, 0xa0, 0x0, 0xd, 0x0, 0x1, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x2f}, {0x26, 0x7, 0xf7, 0xa0, 0x0, 0xd, 0x0, 0x2, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1f}}},
{Country: "Canada", City: "Vancouver", ISP: "Esecuredata", Owned: false, IPs: []net.IP{{71, 19, 248, 240}, {71, 19, 249, 81}}, IPsV6: []net.IP{{0x26, 0x5, 0x0, 0x80, 0x0, 0x18, 0x0, 0x2, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x4}, {0x26, 0x5, 0x0, 0x80, 0x0, 0x19, 0x0, 0x2, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x5}}},
{Country: "Czech Republic", City: "Prague", ISP: "M247", Owned: false, IPs: []net.IP{{185, 156, 174, 146}, {185, 156, 174, 170}, {185, 216, 35, 242}, {217, 138, 199, 74}, {217, 138, 199, 82}}, IPsV6: []net.IP{{0x20, 0x1, 0xa, 0xc8, 0x0, 0x33, 0x0, 0x7, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1f}, {0x20, 0x1, 0xa, 0xc8, 0x0, 0x33, 0x0, 0xb, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x3f}, {0x20, 0x1, 0xa, 0xc8, 0x0, 0x33, 0x0, 0x14, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x2}, {0x20, 0x1, 0xa, 0xc8, 0x0, 0x33, 0x0, 0x3f, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x4f}, {0x20, 0x1, 0xa, 0xc8, 0x0, 0x33, 0x0, 0x40, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x5f}}},
{Country: "Denmark", City: "Copenhagen", ISP: "31173", Owned: true, IPs: []net.IP{{45, 129, 56, 81}, {141, 98, 254, 71}}, IPsV6: []net.IP{{0x2a, 0x3, 0x1b, 0x20, 0x0, 0x8, 0xf0, 0x11, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1f}, {0x2a, 0x3, 0x1b, 0x20, 0x0, 0x8, 0xf0, 0x11, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x2f}}},
{Country: "Denmark", City: "Copenhagen", ISP: "Asergo", Owned: false, IPs: []net.IP{{82, 103, 140, 213}}, IPsV6: []net.IP{{0x2a, 0x0, 0x90, 0x80, 0x0, 0x1, 0x9, 0x8c, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1f}}},
{Country: "Denmark", City: "Copenhagen", ISP: "Blix", Owned: false, IPs: []net.IP{{134, 90, 149, 138}}, IPsV6: []net.IP{{0x2a, 0x2, 0xed, 0x1, 0x41, 0x20, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1f}}},
{Country: "Denmark", City: "Copenhagen", ISP: "M247", Owned: false, IPs: []net.IP{{89, 45, 7, 130}, {89, 45, 7, 146}}, IPsV6: []net.IP{{0x20, 0x1, 0xa, 0xc8, 0x0, 0x37, 0x0, 0x5b, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x10, 0x3f}, {0x20, 0x1, 0xa, 0xc8, 0x0, 0x37, 0x0, 0x5c, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x10, 0x4f}}},
{Country: "Finland", City: "Helsinki", ISP: "Creanova", Owned: true, IPs: []net.IP{{185, 204, 1, 171}, {185, 204, 1, 172}, {185, 204, 1, 173}, {185, 204, 1, 174}, {185, 204, 1, 175}, {185, 204, 1, 176}, {185, 212, 149, 201}}, IPsV6: []net.IP{{0x2a, 0xc, 0xf0, 0x40, 0x0, 0x0, 0x27, 0x90, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1f}, {0x2a, 0xc, 0xf0, 0x40, 0x0, 0x0, 0x27, 0x90, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x2f}, {0x2a, 0xc, 0xf0, 0x40, 0x0, 0x0, 0x27, 0x90, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x3f}, {0x2a, 0xc, 0xf0, 0x40, 0x0, 0x0, 0x27, 0x90, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x4f}, {0x2a, 0xc, 0xf0, 0x40, 0x0, 0x0, 0x27, 0x90, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x5f}, {0x2a, 0xc, 0xf0, 0x40, 0x0, 0x0, 0x27, 0x90, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x6f}, {0x2a, 0xc, 0xf0, 0x40, 0x0, 0x0, 0x27, 0x90, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x7f}}},
{Country: "France", City: "Paris", ISP: "31173", Owned: true, IPs: []net.IP{{193, 32, 126, 81}, {193, 32, 126, 82}, {193, 32, 126, 83}, {193, 32, 126, 84}}, IPsV6: []net.IP{{0x2a, 0x3, 0x1b, 0x20, 0x0, 0x9, 0xf0, 0x11, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1f}, {0x2a, 0x3, 0x1b, 0x20, 0x0, 0x9, 0xf0, 0x11, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x2f}, {0x2a, 0x3, 0x1b, 0x20, 0x0, 0x9, 0xf0, 0x11, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x3f}, {0x2a, 0x3, 0x1b, 0x20, 0x0, 0x9, 0xf0, 0x11, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x4f}}},
{Country: "France", City: "Paris", ISP: "M247", Owned: false, IPs: []net.IP{{89, 44, 9, 19}, {89, 44, 9, 35}, {194, 110, 113, 3}}, IPsV6: []net.IP{{0x20, 0x1, 0xa, 0xc8, 0x0, 0x25, 0x0, 0xd0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1f}, {0x20, 0x1, 0xa, 0xc8, 0x0, 0x25, 0x0, 0xd1, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x2f}, {0x20, 0x1, 0xa, 0xc8, 0x0, 0x25, 0x0, 0xd2, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x3f}}},
{Country: "Germany", City: "Frankfurt", ISP: "31173", Owned: true, IPs: []net.IP{{185, 213, 155, 131}, {185, 213, 155, 132}, {185, 213, 155, 133}, {185, 213, 155, 134}, {185, 213, 155, 135}, {185, 213, 155, 136}, {185, 213, 155, 137}, {185, 213, 155, 138}, {185, 213, 155, 139}, {185, 213, 155, 140}}, IPsV6: []net.IP{{0x2a, 0x3, 0x1b, 0x20, 0x0, 0x6, 0xf0, 0x11, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1f}, {0x2a, 0x3, 0x1b, 0x20, 0x0, 0x6, 0xf0, 0x11, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x2f}, {0x2a, 0x3, 0x1b, 0x20, 0x0, 0x6, 0xf0, 0x11, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x3f}, {0x2a, 0x3, 0x1b, 0x20, 0x0, 0x6, 0xf0, 0x11, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x4f}, {0x2a, 0x3, 0x1b, 0x20, 0x0, 0x6, 0xf0, 0x11, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x5f}, {0x2a, 0x3, 0x1b, 0x20, 0x0, 0x6, 0xf0, 0x11, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x6f}, {0x2a, 0x3, 0x1b, 0x20, 0x0, 0x6, 0xf0, 0x11, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x7f}, {0x2a, 0x3, 0x1b, 0x20, 0x0, 0x6, 0xf0, 0x11, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x8f}, {0x2a, 0x3, 0x1b, 0x20, 0x0, 0x6, 0xf0, 0x11, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x9f}, {0x2a, 0x3, 0x1b, 0x20, 0x0, 0x6, 0xf0, 0x11, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1, 0xf}}},
{Country: "Germany", City: "Frankfurt", ISP: "M247", Owned: false, IPs: []net.IP{{193, 27, 14, 2}, {193, 27, 14, 18}, {193, 27, 14, 34}, {193, 27, 14, 50}}, IPsV6: []net.IP{{0x20, 0x1, 0xa, 0xc8, 0x0, 0x20, 0x3, 0x1, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x20, 0x1f}, {0x20, 0x1, 0xa, 0xc8, 0x0, 0x20, 0x3, 0x2, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x20, 0x2f}, {0x20, 0x1, 0xa, 0xc8, 0x0, 0x20, 0x3, 0x3, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x20, 0x3f}, {0x20, 0x1, 0xa, 0xc8, 0x0, 0x20, 0x3, 0x4, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x20, 0x4f}}},
{Country: "Greece", City: "Athens", ISP: "aweb", Owned: false, IPs: []net.IP{{185, 226, 67, 168}}, IPsV6: []net.IP{{0x2a, 0xc, 0x5e, 0x0, 0x0, 0x0, 0x0, 0x8, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1f}}},
{Country: "Hong Kong", City: "Hong Kong", ISP: "Leaseweb", Owned: false, IPs: []net.IP{{209, 58, 184, 146}, {209, 58, 185, 53}, {209, 58, 185, 186}}, IPsV6: []net.IP{{0x20, 0x1, 0xd, 0xf1, 0x8, 0x1, 0xa0, 0x3, 0x0, 0x4, 0x0, 0x0, 0x0, 0x0, 0x0, 0x2f}, {0x20, 0x1, 0xd, 0xf1, 0x8, 0x1, 0xa0, 0x3, 0x0, 0x5, 0x0, 0x0, 0x0, 0x0, 0x0, 0x3f}, {0x20, 0x1, 0xd, 0xf1, 0x8, 0x1, 0xa0, 0x5, 0x5, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1f}}},
{Country: "Hong Kong", City: "Hong Kong", ISP: "M247", Owned: false, IPs: []net.IP{{89, 45, 6, 50}, {89, 45, 6, 66}}, IPsV6: []net.IP{{0x20, 0x1, 0xa, 0xc8, 0x0, 0x92, 0x0, 0x2, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x2f}, {0x20, 0x1, 0xa, 0xc8, 0x0, 0x92, 0x0, 0x9, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1f}}},
{Country: "Hungary", City: "Budapest", ISP: "M247", Owned: false, IPs: []net.IP{{86, 106, 74, 34}, {86, 106, 74, 50}}, IPsV6: []net.IP{{0x20, 0x1, 0xa, 0xc8, 0x0, 0x26, 0x0, 0xab, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x3f}, {0x20, 0x1, 0xa, 0xc8, 0x0, 0x26, 0x0, 0xac, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x4f}}},
{Country: "Ireland", City: "Dublin", ISP: "M247", Owned: false, IPs: []net.IP{{217, 138, 222, 82}, {217, 138, 222, 90}}, IPsV6: []net.IP{{0x20, 0x1, 0xa, 0xc8, 0x0, 0x88, 0x0, 0x5a, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1f}, {0x20, 0x1, 0xa, 0xc8, 0x0, 0x88, 0x0, 0x5b, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x2f}}},
{Country: "Israel", City: "Tel Aviv", ISP: "HQServ", Owned: false, IPs: []net.IP{{185, 191, 207, 210}}, IPsV6: []net.IP{{0x2a, 0xa, 0x1d, 0xc4, 0x0, 0x1, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1f}}},
{Country: "Italy", City: "Milan", ISP: "M247", Owned: false, IPs: []net.IP{{89, 40, 182, 146}, {89, 40, 182, 210}, {192, 145, 127, 98}, {192, 145, 127, 114}}, IPsV6: []net.IP{{0x20, 0x1, 0xa, 0xc8, 0x0, 0x24, 0x0, 0x76, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1f}, {0x20, 0x1, 0xa, 0xc8, 0x0, 0x24, 0x0, 0x77, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x2f}, {0x20, 0x1, 0xa, 0xc8, 0x0, 0x24, 0x0, 0x78, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x3f}, {0x20, 0x1, 0xa, 0xc8, 0x0, 0x24, 0x0, 0x79, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x4f}}},
{Country: "Japan", City: "Tokyo", ISP: "M247", Owned: false, IPs: []net.IP{{217, 138, 252, 50}, {217, 138, 252, 162}, {217, 138, 252, 178}, {217, 138, 252, 194}, {217, 138, 252, 210}}, IPsV6: []net.IP{{0x20, 0x1, 0xa, 0xc8, 0x0, 0x40, 0x0, 0xb1, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1f}, {0x20, 0x1, 0xa, 0xc8, 0x0, 0x40, 0x0, 0xb2, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x2f}, {0x20, 0x1, 0xa, 0xc8, 0x0, 0x40, 0x0, 0xb3, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x3f}, {0x20, 0x1, 0xa, 0xc8, 0x0, 0x40, 0x0, 0xb4, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x4f}, {0x20, 0x1, 0xa, 0xc8, 0x0, 0x40, 0x0, 0xb5, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x5f}}},
{Country: "Latvia", City: "Riga", ISP: "Makonix", Owned: false, IPs: []net.IP{{31, 170, 22, 2}}, IPsV6: []net.IP{{0x2a, 0x0, 0xc, 0x68, 0x0, 0x0, 0xcb, 0xcf, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1f}}},
{Country: "Luxembourg", City: "Luxembourg", ISP: "Evoluso", Owned: false, IPs: []net.IP{{92, 223, 89, 182}}, IPsV6: []net.IP{{0x2a, 0x3, 0x90, 0xc0, 0x0, 0x83, 0x29, 0x53, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x2f}}},
{Country: "Moldova", City: "Chisinau", ISP: "Trabia", Owned: false, IPs: []net.IP{{178, 175, 142, 194}}, IPsV6: []net.IP{{0x2a, 0x0, 0x1d, 0xc0, 0x29, 0x25, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1f}}},
{Country: "Netherlands", City: "Amsterdam", ISP: "31173", Owned: true, IPs: []net.IP{{185, 65, 134, 131}, {185, 65, 134, 132}, {185, 65, 134, 133}, {185, 65, 134, 134}, {185, 65, 134, 135}, {185, 65, 134, 136}, {185, 65, 134, 139}, {185, 65, 134, 140}, {185, 65, 134, 141}, {185, 65, 134, 142}, {185, 65, 134, 143}, {185, 65, 134, 144}, {185, 65, 134, 145}, {185, 65, 134, 146}, {185, 65, 134, 147}, {185, 65, 134, 148}}, IPsV6: []net.IP{{0x2a, 0x3, 0x1b, 0x20, 0x0, 0x3, 0xf0, 0x11, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1f}, {0x2a, 0x3, 0x1b, 0x20, 0x0, 0x3, 0xf0, 0x11, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x2f}, {0x2a, 0x3, 0x1b, 0x20, 0x0, 0x3, 0xf0, 0x11, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x3f}, {0x2a, 0x3, 0x1b, 0x20, 0x0, 0x3, 0xf0, 0x11, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x4f}, {0x2a, 0x3, 0x1b, 0x20, 0x0, 0x3, 0xf0, 0x11, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x5f}, {0x2a, 0x3, 0x1b, 0x20, 0x0, 0x3, 0xf0, 0x11, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x6f}, {0x2a, 0x3, 0x1b, 0x20, 0x0, 0x3, 0xf0, 0x11, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x9f}, {0x2a, 0x3, 0x1b, 0x20, 0x0, 0x3, 0xf0, 0x11, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1, 0xf}, {0x2a, 0x3, 0x1b, 0x20, 0x0, 0x3, 0xf0, 0x11, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1, 0x1f}, {0x2a, 0x3, 0x1b, 0x20, 0x0, 0x3, 0xf0, 0x11, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1, 0x2f}, {0x2a, 0x3, 0x1b, 0x20, 0x0, 0x3, 0xf0, 0x11, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1, 0x3f}, {0x2a, 0x3, 0x1b, 0x20, 0x0, 0x3, 0xf0, 0x11, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1, 0x4f}, {0x2a, 0x3, 0x1b, 0x20, 0x0, 0x3, 0xf0, 0x11, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1, 0x5f}, {0x2a, 0x3, 0x1b, 0x20, 0x0, 0x3, 0xf0, 0x11, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1, 0x6f}, {0x2a, 0x3, 0x1b, 0x20, 0x0, 0x3, 0xf0, 0x11, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1, 0x7f}, {0x2a, 0x3, 0x1b, 0x20, 0x0, 0x3, 0xf0, 0x11, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1, 0x8f}}},
{Country: "New Zealand", City: "Auckland", ISP: "Intergrid", Owned: false, IPs: []net.IP{{103, 231, 91, 114}}, IPsV6: []net.IP{{0x24, 0x0, 0xfa, 0x80, 0x0, 0x4, 0x0, 0x9, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1f}}},
{Country: "Norway", City: "Oslo", ISP: "Blix", Owned: true, IPs: []net.IP{{91, 90, 44, 11}, {91, 90, 44, 12}, {91, 90, 44, 13}, {91, 90, 44, 14}, {91, 90, 44, 15}, {91, 90, 44, 16}, {91, 90, 44, 17}, {91, 90, 44, 18}}, IPsV6: []net.IP{{0x2a, 0x2, 0x20, 0xc8, 0x41, 0x24, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1f}, {0x2a, 0x2, 0x20, 0xc8, 0x41, 0x24, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x2f}, {0x2a, 0x2, 0x20, 0xc8, 0x41, 0x24, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x3f}, {0x2a, 0x2, 0x20, 0xc8, 0x41, 0x24, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x4f}, {0x2a, 0x2, 0x20, 0xc8, 0x41, 0x24, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x5f}, {0x2a, 0x2, 0x20, 0xc8, 0x41, 0x24, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x6f}, {0x2a, 0x2, 0x20, 0xc8, 0x41, 0x24, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x7f}, {0x2a, 0x2, 0x20, 0xc8, 0x41, 0x24, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x8f}}},
{Country: "Poland", City: "Warsaw", ISP: "M247", Owned: false, IPs: []net.IP{{37, 120, 156, 162}, {37, 120, 211, 186}, {37, 120, 211, 194}, {37, 120, 211, 202}, {185, 244, 214, 210}, {185, 244, 214, 215}}, IPsV6: []net.IP{{0x2a, 0xd, 0x56, 0x0, 0x0, 0x13, 0x0, 0x6, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x2f}, {0x2a, 0xd, 0x56, 0x0, 0x0, 0x13, 0x0, 0x7, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x3f}, {0x2a, 0xd, 0x56, 0x0, 0x0, 0x13, 0x0, 0x39, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x4f}, {0x2a, 0xd, 0x56, 0x0, 0x0, 0x13, 0x0, 0x3a, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x5f}, {0x2a, 0xd, 0x56, 0x0, 0x0, 0x13, 0x0, 0x3b, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x6f}, {0x2a, 0xd, 0x56, 0x0, 0x0, 0x13, 0xb, 0xb1, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1f}}},
{Country: "Romania", City: "Bucharest", ISP: "M247", Owned: false, IPs: []net.IP{{185, 163, 110, 66}, {185, 163, 110, 82}, {185, 163, 110, 98}, {185, 163, 110, 114}}, IPsV6: []net.IP{{0x2a, 0x4, 0x9d, 0xc0, 0x0, 0x0, 0x0, 0x90, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x10, 0x1f}, {0x2a, 0x4, 0x9d, 0xc0, 0x0, 0x0, 0x0, 0x91, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x10, 0x2f}, {0x2a, 0x4, 0x9d, 0xc0, 0x0, 0x0, 0x0, 0x92, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x10, 0x3f}, {0x2a, 0x4, 0x9d, 0xc0, 0x0, 0x0, 0x0, 0x93, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x10, 0x4f}}},
{Country: "Serbia", City: "Belgrade", ISP: "M247", Owned: false, IPs: []net.IP{{89, 38, 224, 98}, {89, 38, 224, 114}}, IPsV6: []net.IP{{0x20, 0x1, 0xa, 0xc8, 0x0, 0x7d, 0x0, 0x10, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x2f}, {0x20, 0x1, 0xa, 0xc8, 0x0, 0x7d, 0x0, 0x11, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x3f}}},
{Country: "Serbia", City: "Nis", ISP: "ninet", Owned: false, IPs: []net.IP{{176, 104, 107, 118}}, IPsV6: []net.IP{{0x2a, 0x6, 0x1, 0x85, 0x0, 0x1, 0x0, 0x2, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1f}}},
{Country: "Singapore", City: "Singapore", ISP: "M247", Owned: false, IPs: []net.IP{{89, 38, 225, 34}, {94, 198, 43, 2}, {94, 198, 43, 18}}, IPsV6: []net.IP{{0x2a, 0xa, 0xb6, 0x40, 0x0, 0x1, 0x0, 0x54, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1f}, {0x2a, 0xa, 0xb6, 0x40, 0x0, 0x1, 0x0, 0x55, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x2f}, {0x2a, 0xa, 0xb6, 0x40, 0x0, 0x1, 0x0, 0x56, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x2f}}},
{Country: "Spain", City: "Madrid", ISP: "M247", Owned: false, IPs: []net.IP{{45, 152, 183, 26}, {45, 152, 183, 42}, {89, 238, 178, 34}, {89, 238, 178, 74}, {195, 206, 107, 146}}, IPsV6: []net.IP{{0x20, 0x1, 0xa, 0xc8, 0x0, 0x23, 0x0, 0x6, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1f}, {0x20, 0x1, 0xa, 0xc8, 0x0, 0x23, 0x0, 0x12, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xf2}, {0x20, 0x1, 0xa, 0xc8, 0x0, 0x23, 0x0, 0x2a, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x3f}, {0x20, 0x1, 0xa, 0xc8, 0x0, 0x23, 0x0, 0x58, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x4f}, {0x20, 0x1, 0xa, 0xc8, 0x0, 0x23, 0x0, 0x59, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x5f}}},
{Country: "Sweden", City: "Gothenburg", ISP: "31173", Owned: true, IPs: []net.IP{{185, 213, 154, 131}, {185, 213, 154, 132}, {185, 213, 154, 133}, {185, 213, 154, 134}, {185, 213, 154, 135}, {185, 213, 154, 136}, {185, 213, 154, 137}, {185, 213, 154, 138}, {185, 213, 154, 139}, {185, 213, 154, 140}, {185, 213, 154, 141}, {185, 213, 154, 142}}, IPsV6: []net.IP{{0x2a, 0x3, 0x1b, 0x20, 0x0, 0x5, 0xf0, 0x11, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x8f}, {0x2a, 0x3, 0x1b, 0x20, 0x0, 0x5, 0xf0, 0x11, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x9f}, {0x2a, 0x3, 0x1b, 0x20, 0x0, 0x5, 0xf0, 0x11, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1, 0xf}, {0x2a, 0x3, 0x1b, 0x20, 0x0, 0x5, 0xf0, 0x11, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1, 0x1f}, {0x2a, 0x3, 0x1b, 0x20, 0x0, 0x5, 0xf0, 0x11, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1, 0x2f}, {0x2a, 0x3, 0x1b, 0x20, 0x0, 0x5, 0xf0, 0x11, 0x0, 0x31, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1f}, {0x2a, 0x3, 0x1b, 0x20, 0x0, 0x5, 0xf0, 0x11, 0x0, 0x31, 0x0, 0x0, 0x0, 0x0, 0x0, 0x2f}, {0x2a, 0x3, 0x1b, 0x20, 0x0, 0x5, 0xf0, 0x11, 0x0, 0x31, 0x0, 0x0, 0x0, 0x0, 0x0, 0x3f}, {0x2a, 0x3, 0x1b, 0x20, 0x0, 0x5, 0xf0, 0x11, 0x0, 0x31, 0x0, 0x0, 0x0, 0x0, 0x0, 0x4f}, {0x2a, 0x3, 0x1b, 0x20, 0x0, 0x5, 0xf0, 0x11, 0x0, 0x31, 0x0, 0x0, 0x0, 0x0, 0x0, 0x5f}, {0x2a, 0x3, 0x1b, 0x20, 0x0, 0x5, 0xf0, 0x11, 0x0, 0x31, 0x0, 0x0, 0x0, 0x0, 0x0, 0x6f}, {0x2a, 0x3, 0x1b, 0x20, 0x0, 0x5, 0xf0, 0x11, 0x0, 0x31, 0x0, 0x0, 0x0, 0x0, 0x0, 0x7f}}},
{Country: "Sweden", City: "Helsingborg", ISP: "31173", Owned: true, IPs: []net.IP{{185, 213, 152, 131}, {185, 213, 152, 132}}, IPsV6: []net.IP{{0x2a, 0x3, 0x1b, 0x20, 0x0, 0x2, 0xf7, 0x2, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1f}, {0x2a, 0x3, 0x1b, 0x20, 0x0, 0x2, 0xf7, 0x2, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x2f}}},
{Country: "Sweden", City: "Malmö", ISP: "31173", Owned: true, IPs: []net.IP{{45, 83, 220, 87}, {45, 83, 220, 88}, {45, 83, 220, 89}, {45, 83, 220, 90}, {45, 83, 220, 91}, {45, 83, 220, 92}, {45, 83, 220, 93}, {141, 98, 255, 83}, {141, 98, 255, 84}, {141, 98, 255, 85}, {141, 98, 255, 86}, {141, 98, 255, 87}, {141, 98, 255, 88}, {141, 98, 255, 89}, {141, 98, 255, 90}, {141, 98, 255, 91}, {141, 98, 255, 92}, {141, 98, 255, 93}, {141, 98, 255, 94}, {193, 138, 218, 131}, {193, 138, 218, 132}, {193, 138, 218, 133}, {193, 138, 218, 134}, {193, 138, 218, 135}, {193, 138, 218, 136}, {193, 138, 218, 137}}, IPsV6: []net.IP{{0x2a, 0x3, 0x1b, 0x20, 0x0, 0x1, 0xe0, 0x11, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x7f}, {0x2a, 0x3, 0x1b, 0x20, 0x0, 0x1, 0xe0, 0x11, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x8f}, {0x2a, 0x3, 0x1b, 0x20, 0x0, 0x1, 0xe0, 0x11, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x9f}, {0x2a, 0x3, 0x1b, 0x20, 0x0, 0x1, 0xe0, 0x11, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1, 0xf}, {0x2a, 0x3, 0x1b, 0x20, 0x0, 0x1, 0xe0, 0x11, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1, 0x1f}, {0x2a, 0x3, 0x1b, 0x20, 0x0, 0x1, 0xe0, 0x11, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1, 0x2f}, {0x2a, 0x3, 0x1b, 0x20, 0x0, 0x1, 0xe0, 0x11, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1, 0x3f}, {0x2a, 0x3, 0x1b, 0x20, 0x0, 0x1, 0xf4, 0x10, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1, 0x3f}, {0x2a, 0x3, 0x1b, 0x20, 0x0, 0x1, 0xf4, 0x10, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1, 0x4f}, {0x2a, 0x3, 0x1b, 0x20, 0x0, 0x1, 0xf4, 0x10, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1, 0x5f}, {0x2a, 0x3, 0x1b, 0x20, 0x0, 0x1, 0xf4, 0x10, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1, 0x6f}, {0x2a, 0x3, 0x1b, 0x20, 0x0, 0x1, 0xf4, 0x10, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1, 0x7f}, {0x2a, 0x3, 0x1b, 0x20, 0x0, 0x1, 0xf4, 0x10, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1, 0x8f}, {0x2a, 0x3, 0x1b, 0x20, 0x0, 0x1, 0xf4, 0x10, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1, 0x9f}, {0x2a, 0x3, 0x1b, 0x20, 0x0, 0x1, 0xf4, 0x10, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x2, 0xf}, {0x2a, 0x3, 0x1b, 0x20, 0x0, 0x1, 0xf4, 0x10, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x2, 0x1f}, {0x2a, 0x3, 0x1b, 0x20, 0x0, 0x1, 0xf4, 0x10, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x2, 0x2f}, {0x2a, 0x3, 0x1b, 0x20, 0x0, 0x1, 0xf4, 0x10, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x2, 0x3f}, {0x2a, 0x3, 0x1b, 0x20, 0x0, 0x1, 0xf4, 0x10, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x2, 0x4f}, {0x2a, 0x3, 0x1b, 0x20, 0x0, 0x1, 0xf4, 0x10, 0x0, 0x40, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1f}, {0x2a, 0x3, 0x1b, 0x20, 0x0, 0x1, 0xf4, 0x10, 0x0, 0x40, 0x0, 0x0, 0x0, 0x0, 0x0, 0x2f}, {0x2a, 0x3, 0x1b, 0x20, 0x0, 0x1, 0xf4, 0x10, 0x0, 0x40, 0x0, 0x0, 0x0, 0x0, 0x0, 0x3f}, {0x2a, 0x3, 0x1b, 0x20, 0x0, 0x1, 0xf4, 0x10, 0x0, 0x40, 0x0, 0x0, 0x0, 0x0, 0x0, 0x4f}, {0x2a, 0x3, 0x1b, 0x20, 0x0, 0x1, 0xf4, 0x10, 0x0, 0x40, 0x0, 0x0, 0x0, 0x0, 0x0, 0x5f}, {0x2a, 0x3, 0x1b, 0x20, 0x0, 0x1, 0xf4, 0x10, 0x0, 0x40, 0x0, 0x0, 0x0, 0x0, 0x0, 0x6f}, {0x2a, 0x3, 0x1b, 0x20, 0x0, 0x1, 0xf4, 0x10, 0x0, 0x40, 0x0, 0x0, 0x0, 0x0, 0x0, 0x7f}}},
{Country: "Sweden", City: "Stockholm", ISP: "31173", Owned: true, IPs: []net.IP{{185, 65, 135, 136}, {185, 65, 135, 137}, {185, 65, 135, 138}, {185, 65, 135, 139}, {185, 65, 135, 140}, {185, 65, 135, 141}, {185, 65, 135, 142}, {185, 65, 135, 143}, {185, 65, 135, 144}, {185, 65, 135, 145}, {185, 65, 135, 146}, {185, 65, 135, 147}, {185, 65, 135, 148}, {185, 65, 135, 149}, {185, 65, 135, 150}, {185, 65, 135, 151}, {185, 65, 135, 152}, {185, 65, 135, 153}, {185, 65, 135, 154}}, IPsV6: []net.IP{{0x2a, 0x3, 0x1b, 0x20, 0x0, 0x4, 0xf0, 0x11, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x6f}, {0x2a, 0x3, 0x1b, 0x20, 0x0, 0x4, 0xf0, 0x11, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x7f}, {0x2a, 0x3, 0x1b, 0x20, 0x0, 0x4, 0xf0, 0x11, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x8f}, {0x2a, 0x3, 0x1b, 0x20, 0x0, 0x4, 0xf0, 0x11, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x9f}, {0x2a, 0x3, 0x1b, 0x20, 0x0, 0x4, 0xf0, 0x11, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1, 0xf}, {0x2a, 0x3, 0x1b, 0x20, 0x0, 0x4, 0xf0, 0x11, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1, 0x1f}, {0x2a, 0x3, 0x1b, 0x20, 0x0, 0x4, 0xf0, 0x11, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1, 0x2f}, {0x2a, 0x3, 0x1b, 0x20, 0x0, 0x4, 0xf0, 0x11, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1, 0x3f}, {0x2a, 0x3, 0x1b, 0x20, 0x0, 0x4, 0xf0, 0x11, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1, 0x4e}, {0x2a, 0x3, 0x1b, 0x20, 0x0, 0x4, 0xf0, 0x11, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1, 0x5f}, {0x2a, 0x3, 0x1b, 0x20, 0x0, 0x4, 0xf0, 0x11, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1, 0x6f}, {0x2a, 0x3, 0x1b, 0x20, 0x0, 0x4, 0xf0, 0x11, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1, 0x7f}, {0x2a, 0x3, 0x1b, 0x20, 0x0, 0x4, 0xf0, 0x11, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1, 0x8f}, {0x2a, 0x3, 0x1b, 0x20, 0x0, 0x4, 0xf0, 0x11, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1, 0x9f}, {0x2a, 0x3, 0x1b, 0x20, 0x0, 0x4, 0xf0, 0x11, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x2, 0xf}, {0x2a, 0x3, 0x1b, 0x20, 0x0, 0x4, 0xf0, 0x11, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x2, 0x1f}, {0x2a, 0x3, 0x1b, 0x20, 0x0, 0x4, 0xf0, 0x11, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x2, 0x2f}, {0x2a, 0x3, 0x1b, 0x20, 0x0, 0x4, 0xf0, 0x11, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x2, 0x3f}, {0x2a, 0x3, 0x1b, 0x20, 0x0, 0x4, 0xf0, 0x11, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x2, 0x4f}}},
{Country: "Switzerland", City: "Zurich", ISP: "31173", Owned: true, IPs: []net.IP{{193, 32, 127, 81}, {193, 32, 127, 82}, {193, 32, 127, 83}, {193, 32, 127, 84}}, IPsV6: []net.IP{{0x2a, 0x3, 0x1b, 0x20, 0x0, 0xa, 0xf0, 0x11, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1f}, {0x2a, 0x3, 0x1b, 0x20, 0x0, 0xa, 0xf0, 0x11, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x2f}, {0x2a, 0x3, 0x1b, 0x20, 0x0, 0xa, 0xf0, 0x11, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x3f}, {0x2a, 0x3, 0x1b, 0x20, 0x0, 0xa, 0xf0, 0x11, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x4f}}},
{Country: "Switzerland", City: "Zurich", ISP: "M247", Owned: false, IPs: []net.IP{{91, 193, 4, 2}, {91, 193, 4, 18}, {91, 193, 4, 34}, {91, 193, 4, 50}, {91, 193, 4, 66}}, IPsV6: []net.IP{{0x20, 0x1, 0xa, 0xc8, 0x0, 0x28, 0x0, 0x84, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x2f}, {0x20, 0x1, 0xa, 0xc8, 0x0, 0x28, 0x0, 0x85, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x3f}, {0x20, 0x1, 0xa, 0xc8, 0x0, 0x28, 0x0, 0x86, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x4f}, {0x20, 0x1, 0xa, 0xc8, 0x0, 0x28, 0x0, 0x87, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x5f}, {0x20, 0x1, 0xa, 0xc8, 0x0, 0x28, 0x0, 0x97, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1f}}},
{Country: "Switzerland", City: "Zurich", ISP: "PrivateLayer", Owned: false, IPs: []net.IP{{179, 43, 128, 170}}, IPsV6: []net.IP{{0x2a, 0x2, 0x29, 0xb8, 0xdc, 0x1, 0x5, 0x97, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1f}}},
{Country: "UK", City: "London", ISP: "31173", Owned: true, IPs: []net.IP{{141, 98, 252, 131}, {141, 98, 252, 132}, {141, 98, 252, 133}, {141, 98, 252, 138}, {141, 98, 252, 139}, {141, 98, 252, 140}, {185, 195, 232, 84}, {185, 195, 232, 85}, {185, 195, 232, 86}}, IPsV6: []net.IP{{0x2a, 0x3, 0x1b, 0x20, 0x0, 0x7, 0xf0, 0x11, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1f}, {0x2a, 0x3, 0x1b, 0x20, 0x0, 0x7, 0xf0, 0x11, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x2f}, {0x2a, 0x3, 0x1b, 0x20, 0x0, 0x7, 0xf0, 0x11, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x3f}, {0x2a, 0x3, 0x1b, 0x20, 0x0, 0x7, 0xf0, 0x11, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x4f}, {0x2a, 0x3, 0x1b, 0x20, 0x0, 0x7, 0xf0, 0x11, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x5f}, {0x2a, 0x3, 0x1b, 0x20, 0x0, 0x7, 0xf0, 0x11, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x6f}, {0x2a, 0x3, 0x1b, 0x20, 0x0, 0x7, 0xf0, 0x11, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x8f}, {0x2a, 0x3, 0x1b, 0x20, 0x0, 0x7, 0xf0, 0x11, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x9f}, {0x2a, 0x3, 0x1b, 0x20, 0x0, 0x7, 0xf0, 0x11, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1, 0xf}}},
{Country: "UK", City: "London", ISP: "M247", Owned: false, IPs: []net.IP{{45, 87, 215, 50}, {185, 200, 118, 178}}, IPsV6: []net.IP{{0x20, 0x1, 0xa, 0xc8, 0x0, 0x31, 0x2, 0x35, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1f}, {0x20, 0x1, 0xa, 0xc8, 0x0, 0x31, 0x2, 0x36, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x2f}}},
{Country: "UK", City: "Manchester", ISP: "M247", Owned: false, IPs: []net.IP{{37, 120, 159, 164}, {89, 238, 132, 36}, {194, 37, 96, 180}, {217, 151, 98, 68}}, IPsV6: []net.IP{{0x20, 0x1, 0xa, 0xc8, 0x0, 0x21, 0x0, 0x3, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x6f}, {0x20, 0x1, 0xa, 0xc8, 0x0, 0x21, 0x0, 0x1b, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x2f}, {0x20, 0x1, 0xa, 0xc8, 0x0, 0x21, 0x0, 0x34, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x3f}, {0x20, 0x1, 0xa, 0xc8, 0x0, 0x21, 0x0, 0x45, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1f}}},
{Country: "USA", City: "Atlanta GA", ISP: "100TB", Owned: false, IPs: []net.IP{{66, 115, 180, 227}, {66, 115, 180, 228}, {66, 115, 180, 229}, {66, 115, 180, 230}, {107, 152, 108, 62}}, IPsV6: []net.IP{{0x26, 0x7, 0xf7, 0xa0, 0x0, 0x1, 0x0, 0xd, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x2f}, {0x26, 0x7, 0xf7, 0xa0, 0x0, 0x1, 0x0, 0xd, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x3f}, {0x26, 0x7, 0xf7, 0xa0, 0x0, 0x1, 0x0, 0xd, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x4f}, {0x26, 0x7, 0xf7, 0xa0, 0x0, 0x1, 0x0, 0xd, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x5f}, {0x26, 0x7, 0xf7, 0xa0, 0x0, 0x6, 0x0, 0x5, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1, 0xf}}},
{Country: "USA", City: "Atlanta GA", ISP: "Quadranet", Owned: false, IPs: []net.IP{{104, 129, 24, 242}}, IPsV6: []net.IP{{0x26, 0x7, 0xfc, 0xd0, 0xaa, 0x80, 0x13, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x2f}}},
{Country: "USA", City: "Chicago IL", ISP: "Quadranet", Owned: false, IPs: []net.IP{{104, 129, 31, 26}}, IPsV6: []net.IP{{0x26, 0x7, 0xfc, 0xd0, 0xbb, 0x80, 0x4, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1f}}},
{Country: "USA", City: "Chicago IL", ISP: "Tzulo", Owned: false, IPs: []net.IP{{68, 235, 43, 10}, {68, 235, 43, 18}, {68, 235, 43, 26}, {68, 235, 43, 34}, {68, 235, 43, 42}, {68, 235, 43, 50}, {68, 235, 43, 58}, {68, 235, 43, 66}, {68, 235, 43, 74}, {68, 235, 43, 122}}, IPsV6: []net.IP{{0x26, 0x7, 0x90, 0x0, 0x0, 0x0, 0x0, 0x51, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x2f}, {0x26, 0x7, 0x90, 0x0, 0x0, 0x0, 0x0, 0x52, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x3f}, {0x26, 0x7, 0x90, 0x0, 0x0, 0x0, 0x0, 0x53, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x4f}, {0x26, 0x7, 0x90, 0x0, 0x0, 0x0, 0x0, 0x54, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x5f}, {0x26, 0x7, 0x90, 0x0, 0x0, 0x0, 0x0, 0x55, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x6f}, {0x26, 0x7, 0x90, 0x0, 0x0, 0x0, 0x0, 0x56, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x7f}, {0x26, 0x7, 0x90, 0x0, 0x0, 0x0, 0x0, 0x57, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x8f}, {0x26, 0x7, 0x90, 0x0, 0x0, 0x0, 0x0, 0x58, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x9f}, {0x26, 0x7, 0x90, 0x0, 0x0, 0x0, 0x0, 0x59, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1, 0xf}, {0x26, 0x7, 0x90, 0x0, 0x0, 0x0, 0x0, 0x65, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1f}}},
{Country: "USA", City: "Dallas TX", ISP: "100TB", Owned: false, IPs: []net.IP{{174, 127, 113, 3}, {174, 127, 113, 4}, {174, 127, 113, 5}, {174, 127, 113, 6}, {174, 127, 113, 7}}, IPsV6: []net.IP{{0x26, 0x6, 0x2e, 0x0, 0x80, 0x7, 0x0, 0x1, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1f}, {0x26, 0x6, 0x2e, 0x0, 0x80, 0x7, 0x0, 0x1, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x2f}, {0x26, 0x6, 0x2e, 0x0, 0x80, 0x7, 0x0, 0x1, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x3f}, {0x26, 0x6, 0x2e, 0x0, 0x80, 0x7, 0x0, 0x1, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x4f}, {0x26, 0x6, 0x2e, 0x0, 0x80, 0x7, 0x0, 0x1, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x5f}}},
{Country: "USA", City: "Dallas TX", ISP: "M247", Owned: false, IPs: []net.IP{{193, 27, 13, 34}, {193, 27, 13, 50}, {193, 27, 13, 66}, {193, 27, 13, 82}, {193, 27, 13, 178}}, IPsV6: []net.IP{{0x20, 0x1, 0xa, 0xc8, 0x0, 0x9a, 0x0, 0x1, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x2f}, {0x20, 0x1, 0xa, 0xc8, 0x0, 0x9a, 0x0, 0x2, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x3f}, {0x20, 0x1, 0xa, 0xc8, 0x0, 0x9a, 0x0, 0x3, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x4f}, {0x20, 0x1, 0xa, 0xc8, 0x0, 0x9a, 0x0, 0x4, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x5f}, {0x20, 0x1, 0xa, 0xc8, 0x0, 0x9a, 0x0, 0x10, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1f}}},
{Country: "USA", City: "Dallas TX", ISP: "Quadranet", Owned: false, IPs: []net.IP{{96, 44, 145, 18}, {96, 44, 147, 130}}, IPsV6: []net.IP{{0x26, 0x7, 0xfc, 0xd0, 0xda, 0x80, 0x18, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x8}, {0x26, 0x7, 0xfc, 0xd0, 0xda, 0x80, 0x18, 0x1, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x9}}},
{Country: "USA", City: "Denver CO", ISP: "Tzulo", Owned: false, IPs: []net.IP{{198, 54, 128, 66}, {198, 54, 128, 74}, {198, 54, 128, 106}}, IPsV6: []net.IP{{0x26, 0x7, 0x90, 0x0, 0x20, 0x0, 0x0, 0x17, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x2f}, {0x26, 0x7, 0x90, 0x0, 0x20, 0x0, 0x0, 0x18, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1f}, {0x26, 0x7, 0x90, 0x0, 0x20, 0x0, 0x0, 0x22, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x3f}}},
{Country: "USA", City: "Los Angeles CA", ISP: "100TB", Owned: false, IPs: []net.IP{{104, 200, 152, 66}, {107, 181, 168, 130}}, IPsV6: []net.IP{{0x26, 0x7, 0xf7, 0xa0, 0x0, 0x3, 0x0, 0x3, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x8f}, {0x26, 0x7, 0xf7, 0xa0, 0x0, 0x3, 0x0, 0x6, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x7f}}},
{Country: "USA", City: "Los Angeles CA", ISP: "M247", Owned: false, IPs: []net.IP{{89, 46, 114, 15}, {89, 46, 114, 28}, {89, 46, 114, 41}, {89, 46, 114, 54}, {89, 46, 114, 67}, {89, 46, 114, 80}, {89, 46, 114, 93}, {89, 46, 114, 106}, {89, 46, 114, 119}, {89, 46, 114, 132}, {89, 46, 114, 145}, {89, 46, 114, 158}, {89, 46, 114, 171}, {89, 46, 114, 184}, {89, 46, 114, 197}, {89, 46, 114, 210}, {89, 46, 114, 223}}, IPsV6: []net.IP{{0x2a, 0xd, 0x56, 0x0, 0x0, 0x8, 0x0, 0xe, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1f}, {0x2a, 0xd, 0x56, 0x0, 0x0, 0x8, 0x0, 0xe, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x2f}, {0x2a, 0xd, 0x56, 0x0, 0x0, 0x8, 0x0, 0xe, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x3f}, {0x2a, 0xd, 0x56, 0x0, 0x0, 0x8, 0x0, 0xe, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x4f}, {0x2a, 0xd, 0x56, 0x0, 0x0, 0x8, 0x0, 0xe, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x5f}, {0x2a, 0xd, 0x56, 0x0, 0x0, 0x8, 0x0, 0xe, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x6f}, {0x2a, 0xd, 0x56, 0x0, 0x0, 0x8, 0x0, 0xe, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x7f}, {0x2a, 0xd, 0x56, 0x0, 0x0, 0x8, 0x0, 0xe, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x8f}, {0x2a, 0xd, 0x56, 0x0, 0x0, 0x8, 0x0, 0xe, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x9f}, {0x2a, 0xd, 0x56, 0x0, 0x0, 0x8, 0x0, 0xe, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1, 0xf}, {0x2a, 0xd, 0x56, 0x0, 0x0, 0x8, 0x0, 0xe, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1, 0x1f}, {0x2a, 0xd, 0x56, 0x0, 0x0, 0x8, 0x0, 0xe, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1, 0x2f}, {0x2a, 0xd, 0x56, 0x0, 0x0, 0x8, 0x0, 0xe, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1, 0x3f}, {0x2a, 0xd, 0x56, 0x0, 0x0, 0x8, 0x0, 0xe, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1, 0x4f}, {0x2a, 0xd, 0x56, 0x0, 0x0, 0x8, 0x0, 0xe, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1, 0x5f}, {0x2a, 0xd, 0x56, 0x0, 0x0, 0x8, 0x0, 0xe, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1, 0x6f}, {0x2a, 0xd, 0x56, 0x0, 0x0, 0x8, 0x0, 0xe, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1, 0x7f}}},
{Country: "USA", City: "Los Angeles CA", ISP: "Tzulo", Owned: false, IPs: []net.IP{{198, 54, 129, 74}, {198, 54, 129, 82}}, IPsV6: []net.IP{{0x26, 0x7, 0x90, 0x0, 0x30, 0x0, 0x0, 0x17, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x2f}, {0x26, 0x7, 0x90, 0x0, 0x30, 0x0, 0x0, 0x18, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1f}}},
{Country: "USA", City: "Miami FL", ISP: "M247", Owned: false, IPs: []net.IP{{94, 198, 42, 50}, {94, 198, 42, 66}, {94, 198, 42, 82}, {94, 198, 42, 98}, {193, 27, 12, 2}, {193, 27, 12, 18}}, IPsV6: []net.IP{{0x2a, 0xd, 0x56, 0x0, 0x0, 0x6, 0x0, 0x33, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x3f}, {0x2a, 0xd, 0x56, 0x0, 0x0, 0x6, 0x0, 0x34, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x4f}, {0x2a, 0xd, 0x56, 0x0, 0x0, 0x6, 0x0, 0x35, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x5f}, {0x2a, 0xd, 0x56, 0x0, 0x0, 0x6, 0x0, 0x36, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x6f}, {0x2a, 0xd, 0x56, 0x0, 0x0, 0x6, 0xa, 0xd6, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x2f}, {0x2a, 0xd, 0x56, 0x0, 0x0, 0x6, 0xa, 0xd7, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1f}}},
{Country: "USA", City: "New York NY", ISP: "100TB", Owned: false, IPs: []net.IP{{107, 182, 226, 206}, {107, 182, 226, 218}}, IPsV6: []net.IP{{0x26, 0x6, 0x2e, 0x0, 0x80, 0x3, 0x0, 0x13, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x6f}, {0x26, 0x6, 0x2e, 0x0, 0x80, 0x3, 0x0, 0x13, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x7f}}},
{Country: "USA", City: "New York NY", ISP: "M247", Owned: false, IPs: []net.IP{{86, 106, 121, 15}, {86, 106, 121, 28}, {86, 106, 121, 41}, {86, 106, 121, 54}, {86, 106, 121, 67}, {86, 106, 121, 80}, {86, 106, 121, 93}, {86, 106, 121, 106}, {86, 106, 121, 119}, {86, 106, 121, 132}, {89, 46, 62, 15}, {89, 46, 62, 28}, {89, 46, 62, 41}, {89, 46, 62, 54}, {89, 46, 62, 67}, {89, 46, 62, 80}, {89, 46, 62, 93}, {89, 46, 62, 106}, {89, 46, 62, 119}, {89, 46, 62, 132}}, IPsV6: []net.IP{{0x2a, 0xd, 0x56, 0x0, 0x0, 0x24, 0xa, 0x71, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1f}, {0x2a, 0xd, 0x56, 0x0, 0x0, 0x24, 0xa, 0x72, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x2f}, {0x2a, 0xd, 0x56, 0x0, 0x0, 0x24, 0xa, 0x73, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x3f}, {0x2a, 0xd, 0x56, 0x0, 0x0, 0x24, 0xa, 0x74, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x4f}, {0x2a, 0xd, 0x56, 0x0, 0x0, 0x24, 0xa, 0x75, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x5f}, {0x2a, 0xd, 0x56, 0x0, 0x0, 0x24, 0xa, 0x76, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x6f}, {0x2a, 0xd, 0x56, 0x0, 0x0, 0x24, 0xa, 0x77, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x7f}, {0x2a, 0xd, 0x56, 0x0, 0x0, 0x24, 0xa, 0x78, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x8f}, {0x2a, 0xd, 0x56, 0x0, 0x0, 0x24, 0xa, 0x79, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x9f}, {0x2a, 0xd, 0x56, 0x0, 0x0, 0x24, 0xa, 0x7a, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1, 0xf}, {0x2a, 0xd, 0x56, 0x0, 0x0, 0x24, 0xa, 0x99, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1, 0x1f}, {0x2a, 0xd, 0x56, 0x0, 0x0, 0x24, 0xa, 0x9a, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1, 0x2f}, {0x2a, 0xd, 0x56, 0x0, 0x0, 0x24, 0xa, 0x9b, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1, 0x3f}, {0x2a, 0xd, 0x56, 0x0, 0x0, 0x24, 0xa, 0x9c, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1, 0x4f}, {0x2a, 0xd, 0x56, 0x0, 0x0, 0x24, 0xa, 0x9d, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1, 0x5f}, {0x2a, 0xd, 0x56, 0x0, 0x0, 0x24, 0xa, 0x9e, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1, 0x6f}, {0x2a, 0xd, 0x56, 0x0, 0x0, 0x24, 0xa, 0x9f, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1, 0x7f}, {0x2a, 0xd, 0x56, 0x0, 0x0, 0x24, 0xa, 0xa1, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1, 0x8f}, {0x2a, 0xd, 0x56, 0x0, 0x0, 0x24, 0xa, 0xa2, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1, 0x9f}, {0x2a, 0xd, 0x56, 0x0, 0x0, 0x24, 0xa, 0xa3, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x2, 0xf}}},
{Country: "USA", City: "Phoenix AZ", ISP: "100TB", Owned: false, IPs: []net.IP{{107, 152, 99, 86}}, IPsV6: []net.IP{{0x26, 0x7, 0xf7, 0xa0, 0x0, 0x5, 0x0, 0x4, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x4f}}},
{Country: "USA", City: "Phoenix AZ", ISP: "Tzulo", Owned: false, IPs: []net.IP{{198, 54, 133, 34}, {198, 54, 133, 50}, {198, 54, 133, 66}}, IPsV6: []net.IP{{0x26, 0x7, 0x90, 0x0, 0x70, 0x0, 0x0, 0x12, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x10, 0x1f}, {0x26, 0x7, 0x90, 0x0, 0x70, 0x0, 0x0, 0x13, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x10, 0x2f}, {0x26, 0x7, 0x90, 0x0, 0x70, 0x0, 0x0, 0x14, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x10, 0x3f}}},
{Country: "USA", City: "Raleigh NC", ISP: "Tzulo", Owned: false, IPs: []net.IP{{198, 54, 130, 34}, {198, 54, 130, 50}, {198, 54, 130, 66}}, IPsV6: []net.IP{{0x26, 0x7, 0x90, 0x0, 0x40, 0x0, 0x0, 0x12, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x10, 0x1f}, {0x26, 0x7, 0x90, 0x0, 0x40, 0x0, 0x0, 0x13, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x10, 0x2f}, {0x26, 0x7, 0x90, 0x0, 0x40, 0x0, 0x0, 0x14, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x10, 0x3f}}},
{Country: "USA", City: "Salt Lake City UT", ISP: "100TB", Owned: false, IPs: []net.IP{{69, 4, 234, 132}, {69, 4, 234, 133}, {69, 4, 234, 134}, {69, 4, 234, 135}, {69, 4, 234, 136}, {69, 4, 234, 137}}, IPsV6: []net.IP{{0x26, 0x6, 0x2e, 0x0, 0x0, 0x0, 0x0, 0xb9, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1f}, {0x26, 0x6, 0x2e, 0x0, 0x0, 0x0, 0x0, 0xb9, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x2f}, {0x26, 0x6, 0x2e, 0x0, 0x0, 0x0, 0x0, 0xb9, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x3f}, {0x26, 0x6, 0x2e, 0x0, 0x0, 0x0, 0x0, 0xb9, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x4f}, {0x26, 0x6, 0x2e, 0x0, 0x0, 0x0, 0x0, 0xb9, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x5f}, {0x26, 0x6, 0x2e, 0x0, 0x0, 0x0, 0x0, 0xb9, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x6f}}},
{Country: "USA", City: "San Jose CA", ISP: "Tzulo", Owned: false, IPs: []net.IP{{198, 54, 134, 34}, {198, 54, 134, 50}, {198, 54, 134, 66}}, IPsV6: []net.IP{{0x26, 0x7, 0x90, 0x0, 0x80, 0x0, 0x0, 0x12, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1f}, {0x26, 0x7, 0x90, 0x0, 0x80, 0x0, 0x0, 0x13, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x2f}, {0x26, 0x7, 0x90, 0x0, 0x80, 0x0, 0x0, 0x14, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x3f}}},
{Country: "USA", City: "Seattle WA", ISP: "100TB", Owned: false, IPs: []net.IP{{104, 200, 129, 42}, {104, 200, 129, 110}, {104, 200, 129, 150}}, IPsV6: []net.IP{{0x26, 0x7, 0xf7, 0xa0, 0x0, 0xc, 0x0, 0x2, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x5}, {0x26, 0x7, 0xf7, 0xa0, 0x0, 0xc, 0x0, 0x3, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x3f}, {0x26, 0x7, 0xf7, 0xa0, 0x0, 0xc, 0x0, 0x5, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x2f}}},
{Country: "USA", City: "Seattle WA", ISP: "Tzulo", Owned: false, IPs: []net.IP{{198, 54, 131, 34}, {198, 54, 131, 50}, {198, 54, 131, 66}}, IPsV6: []net.IP{{0x26, 0x7, 0x90, 0x0, 0x50, 0x0, 0x0, 0x12, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x10, 0x1f}, {0x26, 0x7, 0x90, 0x0, 0x50, 0x0, 0x0, 0x13, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x10, 0x2f}, {0x26, 0x7, 0x90, 0x0, 0x50, 0x0, 0x0, 0x14, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x10, 0x3f}}},
{Country: "USA", City: "Secaucus NJ", ISP: "Quadranet", Owned: false, IPs: []net.IP{{23, 226, 131, 130}, {23, 226, 131, 154}}, IPsV6: []net.IP{{0x26, 0x7, 0xfc, 0xd0, 0xcc, 0xc0, 0x1d, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1f}, {0x26, 0x7, 0xfc, 0xd0, 0xcc, 0xc0, 0x1d, 0x1, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x2f}}},
{Country: "United Arab Emirates", City: "Dubai", ISP: "M247", Owned: false, IPs: []net.IP{{45, 9, 249, 34}}, IPsV6: []net.IP{{0x20, 0x1, 0xa, 0xc8, 0x0, 0x81, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1f}}},
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -1,7 +1,7 @@
package constants
import (
"github.com/qdm12/private-internet-access-docker/internal/models"
"github.com/qdm12/gluetun/internal/models"
)
const (

View File

@@ -1,30 +1,34 @@
package constants
import (
"github.com/qdm12/private-internet-access-docker/internal/models"
)
const (
// UnboundConf is the file path to the Unbound configuration file
UnboundConf models.Filepath = "/etc/unbound/unbound.conf"
// ResolvConf is the file path to the system resolv.conf file
ResolvConf models.Filepath = "/etc/resolv.conf"
// CACertificates is the file path to the CA certificates file
CACertificates models.Filepath = "/etc/ssl/certs/ca-certificates.crt"
// OpenVPNAuthConf is the file path to the OpenVPN auth file
OpenVPNAuthConf models.Filepath = "/etc/openvpn/auth.conf"
// OpenVPNConf is the file path to the OpenVPN client configuration file
OpenVPNConf models.Filepath = "/etc/openvpn/target.ovpn"
// TunnelDevice is the file path to tun device
TunnelDevice models.Filepath = "/dev/net/tun"
// NetRoute is the path to the file containing information on the network route
NetRoute models.Filepath = "/proc/net/route"
// TinyProxyConf is the filepath to the tinyproxy configuration file
TinyProxyConf models.Filepath = "/etc/tinyproxy/tinyproxy.conf"
// ShadowsocksConf is the filepath to the shadowsocks configuration file
ShadowsocksConf models.Filepath = "/etc/shadowsocks.json"
// RootHints is the filepath to the root.hints file used by Unbound
RootHints models.Filepath = "/etc/unbound/root.hints"
// RootKey is the filepath to the root.key file used by Unbound
RootKey models.Filepath = "/etc/unbound/root.key"
)
package constants
import (
"github.com/qdm12/gluetun/internal/models"
)
const (
// UnboundConf is the file path to the Unbound configuration file.
UnboundConf models.Filepath = "/etc/unbound/unbound.conf"
// ResolvConf is the file path to the system resolv.conf file.
ResolvConf models.Filepath = "/etc/resolv.conf"
// CACertificates is the file path to the CA certificates file.
CACertificates models.Filepath = "/etc/ssl/certs/ca-certificates.crt"
// OpenVPNAuthConf is the file path to the OpenVPN auth file.
OpenVPNAuthConf models.Filepath = "/etc/openvpn/auth.conf"
// OpenVPNConf is the file path to the OpenVPN client configuration file.
OpenVPNConf models.Filepath = "/etc/openvpn/target.ovpn"
// PIAPortForward is the file path to the port forwarding JSON information for PIA servers.
PIAPortForward models.Filepath = "/gluetun/piaportforward.json"
// TunnelDevice is the file path to tun device.
TunnelDevice models.Filepath = "/dev/net/tun"
// NetRoute is the path to the file containing information on the network route.
NetRoute models.Filepath = "/proc/net/route"
// RootHints is the filepath to the root.hints file used by Unbound.
RootHints models.Filepath = "/etc/unbound/root.hints"
// RootKey is the filepath to the root.key file used by Unbound.
RootKey models.Filepath = "/etc/unbound/root.key"
// Client key filepath, used by Cyberghost.
ClientKey models.Filepath = "/gluetun/client.key"
// Client certificate filepath, used by Cyberghost.
ClientCertificate models.Filepath = "/gluetun/client.crt"
// Servers information filepath.
ServersData = "/gluetun/servers.json"
)

View File

@@ -1,90 +1,131 @@
package constants
import (
"fmt"
"strings"
"github.com/qdm12/private-internet-access-docker/internal/models"
)
const (
// PIAEncryptionNormal is the normal level of encryption for communication with PIA servers
PIAEncryptionNormal models.PIAEncryption = "normal"
// PIAEncryptionStrong is the strong level of encryption for communication with PIA servers
PIAEncryptionStrong models.PIAEncryption = "strong"
)
const (
PIAX509CRL_NORMAL = "MIICWDCCAUAwDQYJKoZIhvcNAQENBQAwgegxCzAJBgNVBAYTAlVTMQswCQYDVQQIEwJDQTETMBEGA1UEBxMKTG9zQW5nZWxlczEgMB4GA1UEChMXUHJpdmF0ZSBJbnRlcm5ldCBBY2Nlc3MxIDAeBgNVBAsTF1ByaXZhdGUgSW50ZXJuZXQgQWNjZXNzMSAwHgYDVQQDExdQcml2YXRlIEludGVybmV0IEFjY2VzczEgMB4GA1UEKRMXUHJpdmF0ZSBJbnRlcm5ldCBBY2Nlc3MxLzAtBgkqhkiG9w0BCQEWIHNlY3VyZUBwcml2YXRlaW50ZXJuZXRhY2Nlc3MuY29tFw0xNjA3MDgxOTAwNDZaFw0zNjA3MDMxOTAwNDZaMCYwEQIBARcMMTYwNzA4MTkwMDQ2MBECAQYXDDE2MDcwODE5MDA0NjANBgkqhkiG9w0BAQ0FAAOCAQEAQZo9X97ci8EcPYu/uK2HB152OZbeZCINmYyluLDOdcSvg6B5jI+ffKN3laDvczsG6CxmY3jNyc79XVpEYUnq4rT3FfveW1+Ralf+Vf38HdpwB8EWB4hZlQ205+21CALLvZvR8HcPxC9KEnev1mU46wkTiov0EKc+EdRxkj5yMgv0V2Reze7AP+NQ9ykvDScH4eYCsmufNpIjBLhpLE2cuZZXBLcPhuRzVoU3l7A9lvzG9mjA5YijHJGHNjlWFqyrn1CfYS6koa4TGEPngBoAziWRbDGdhEgJABHrpoaFYaL61zqyMR6jC0K2ps9qyZAN74LEBedEfK7tBOzWMwr58A=="
PIAX509CRL_STRONG = "MIIDWDCCAUAwDQYJKoZIhvcNAQENBQAwgegxCzAJBgNVBAYTAlVTMQswCQYDVQQIEwJDQTETMBEGA1UEBxMKTG9zQW5nZWxlczEgMB4GA1UEChMXUHJpdmF0ZSBJbnRlcm5ldCBBY2Nlc3MxIDAeBgNVBAsTF1ByaXZhdGUgSW50ZXJuZXQgQWNjZXNzMSAwHgYDVQQDExdQcml2YXRlIEludGVybmV0IEFjY2VzczEgMB4GA1UEKRMXUHJpdmF0ZSBJbnRlcm5ldCBBY2Nlc3MxLzAtBgkqhkiG9w0BCQEWIHNlY3VyZUBwcml2YXRlaW50ZXJuZXRhY2Nlc3MuY29tFw0xNjA3MDgxOTAwNDZaFw0zNjA3MDMxOTAwNDZaMCYwEQIBARcMMTYwNzA4MTkwMDQ2MBECAQYXDDE2MDcwODE5MDA0NjANBgkqhkiG9w0BAQ0FAAOCAgEAppFfEpGsasjB1QgJcosGpzbf2kfRhM84o2TlqY1ua+Gi5TMdKydA3LJcNTjlI9a0TYAJfeRX5IkpoglSUuHuJgXhP3nEvX10mjXDpcu/YvM8TdE5JV2+EGqZ80kFtBeOq94WcpiVKFTR4fO+VkOK9zwspFfb1cNs9rHvgJ1QMkRUF8PpLN6AkntHY0+6DnigtSaKqldqjKTDTv2OeH3nPoh80SGrt0oCOmYKfWTJGpggMGKvIdvU3vH9+EuILZKKIskt+1dwdfA5Bkz1GLmiQG7+9ZZBQUjBG9Dos4hfX/rwJ3eU8oUIm4WoTz9rb71SOEuUUjP5NPy9HNx2vx+cVvLsTF4ZDZaUztW9o9JmIURDtbeyqxuHN3prlPWB6aj73IIm2dsDQvs3XXwRIxs8NwLbJ6CyEuvEOVCskdM8rdADWx1J0lRNlOJ0Z8ieLLEmYAA834VN1SboB6wJIAPxQU3rcBhXqO9y8aa2oRMg8NxZ5gr+PnKVMqag1x0IxbIgLxtkXQvxXxQHEMSODzvcOfK/nBRBsqTj30P+R87sU8titOoxNeRnBDRNhdEy/QGAqGh62ShPpQUCJdnKRiRTjnil9hMQHevoSuFKeEMO30FQL7BZyo37GFU+q1WPCplVZgCP9hC8Rn5K2+f6KLFo5bhtowSmu+GY1yZtg+RTtsA="
PIACertificate_NORMAL = "MIIFqzCCBJOgAwIBAgIJAKZ7D5Yv87qDMA0GCSqGSIb3DQEBDQUAMIHoMQswCQYDVQQGEwJVUzELMAkGA1UECBMCQ0ExEzARBgNVBAcTCkxvc0FuZ2VsZXMxIDAeBgNVBAoTF1ByaXZhdGUgSW50ZXJuZXQgQWNjZXNzMSAwHgYDVQQLExdQcml2YXRlIEludGVybmV0IEFjY2VzczEgMB4GA1UEAxMXUHJpdmF0ZSBJbnRlcm5ldCBBY2Nlc3MxIDAeBgNVBCkTF1ByaXZhdGUgSW50ZXJuZXQgQWNjZXNzMS8wLQYJKoZIhvcNAQkBFiBzZWN1cmVAcHJpdmF0ZWludGVybmV0YWNjZXNzLmNvbTAeFw0xNDA0MTcxNzM1MThaFw0zNDA0MTIxNzM1MThaMIHoMQswCQYDVQQGEwJVUzELMAkGA1UECBMCQ0ExEzARBgNVBAcTCkxvc0FuZ2VsZXMxIDAeBgNVBAoTF1ByaXZhdGUgSW50ZXJuZXQgQWNjZXNzMSAwHgYDVQQLExdQcml2YXRlIEludGVybmV0IEFjY2VzczEgMB4GA1UEAxMXUHJpdmF0ZSBJbnRlcm5ldCBBY2Nlc3MxIDAeBgNVBCkTF1ByaXZhdGUgSW50ZXJuZXQgQWNjZXNzMS8wLQYJKoZIhvcNAQkBFiBzZWN1cmVAcHJpdmF0ZWludGVybmV0YWNjZXNzLmNvbTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAPXDL1L9tX6DGf36liA7UBTy5I869z0UVo3lImfOs/GSiFKPtInlesP65577nd7UNzzXlH/P/CnFPdBWlLp5ze3HRBCc/Avgr5CdMRkEsySL5GHBZsx6w2cayQ2EcRhVTwWpcdldeNO+pPr9rIgPrtXqT4SWViTQRBeGM8CDxAyTopTsobjSiYZCF9Ta1gunl0G/8Vfp+SXfYCC+ZzWvP+L1pFhPRqzQQ8k+wMZIovObK1s+nlwPaLyayzw9a8sUnvWB/5rGPdIYnQWPgoNlLN9HpSmsAcw2z8DXI9pIxbr74cb3/HSfuYGOLkRqrOk6h4RCOfuWoTrZup1uEOn+fw8CAwEAAaOCAVQwggFQMB0GA1UdDgQWBBQv63nQ/pJAt5tLy8VJcbHe22ZOsjCCAR8GA1UdIwSCARYwggESgBQv63nQ/pJAt5tLy8VJcbHe22ZOsqGB7qSB6zCB6DELMAkGA1UEBhMCVVMxCzAJBgNVBAgTAkNBMRMwEQYDVQQHEwpMb3NBbmdlbGVzMSAwHgYDVQQKExdQcml2YXRlIEludGVybmV0IEFjY2VzczEgMB4GA1UECxMXUHJpdmF0ZSBJbnRlcm5ldCBBY2Nlc3MxIDAeBgNVBAMTF1ByaXZhdGUgSW50ZXJuZXQgQWNjZXNzMSAwHgYDVQQpExdQcml2YXRlIEludGVybmV0IEFjY2VzczEvMC0GCSqGSIb3DQEJARYgc2VjdXJlQHByaXZhdGVpbnRlcm5ldGFjY2Vzcy5jb22CCQCmew+WL/O6gzAMBgNVHRMEBTADAQH/MA0GCSqGSIb3DQEBDQUAA4IBAQAna5PgrtxfwTumD4+3/SYvwoD66cB8IcK//h1mCzAduU8KgUXocLx7QgJWo9lnZ8xUryXvWab2usg4fqk7FPi00bED4f4qVQFVfGfPZIH9QQ7/48bPM9RyfzImZWUCenK37pdw4Bvgoys2rHLHbGen7f28knT2j/cbMxd78tQc20TIObGjo8+ISTRclSTRBtyCGohseKYpTS9himFERpUgNtefvYHbn70mIOzfOJFTVqfrptf9jXa9N8Mpy3ayfodz1wiqdteqFXkTYoSDctgKMiZ6GdocK9nMroQipIQtpnwd4yBDWIyC6Bvlkrq5TQUtYDQ8z9v+DMO6iwyIDRiU"
PIACertificate_STRONG = "MIIHqzCCBZOgAwIBAgIJAJ0u+vODZJntMA0GCSqGSIb3DQEBDQUAMIHoMQswCQYDVQQGEwJVUzELMAkGA1UECBMCQ0ExEzARBgNVBAcTCkxvc0FuZ2VsZXMxIDAeBgNVBAoTF1ByaXZhdGUgSW50ZXJuZXQgQWNjZXNzMSAwHgYDVQQLExdQcml2YXRlIEludGVybmV0IEFjY2VzczEgMB4GA1UEAxMXUHJpdmF0ZSBJbnRlcm5ldCBBY2Nlc3MxIDAeBgNVBCkTF1ByaXZhdGUgSW50ZXJuZXQgQWNjZXNzMS8wLQYJKoZIhvcNAQkBFiBzZWN1cmVAcHJpdmF0ZWludGVybmV0YWNjZXNzLmNvbTAeFw0xNDA0MTcxNzQwMzNaFw0zNDA0MTIxNzQwMzNaMIHoMQswCQYDVQQGEwJVUzELMAkGA1UECBMCQ0ExEzARBgNVBAcTCkxvc0FuZ2VsZXMxIDAeBgNVBAoTF1ByaXZhdGUgSW50ZXJuZXQgQWNjZXNzMSAwHgYDVQQLExdQcml2YXRlIEludGVybmV0IEFjY2VzczEgMB4GA1UEAxMXUHJpdmF0ZSBJbnRlcm5ldCBBY2Nlc3MxIDAeBgNVBCkTF1ByaXZhdGUgSW50ZXJuZXQgQWNjZXNzMS8wLQYJKoZIhvcNAQkBFiBzZWN1cmVAcHJpdmF0ZWludGVybmV0YWNjZXNzLmNvbTCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBALVkhjumaqBbL8aSgj6xbX1QPTfTd1qHsAZd2B97m8Vw31c/2yQgZNf5qZY0+jOIHULNDe4R9TIvyBEbvnAg/OkPw8n/+ScgYOeH876VUXzjLDBnDb8DLr/+w9oVsuDeFJ9KV2UFM1OYX0SnkHnrYAN2QLF98ESK4NCSU01h5zkcgmQ+qKSfA9Ny0/UpsKPBFqsQ25NvjDWFhCpeqCHKUJ4Be27CDbSl7lAkBuHMPHJs8f8xPgAbHRXZOxVCpayZ2SNDfCwsnGWpWFoMGvdMbygngCn6jA/W1VSFOlRlfLuuGe7QFfDwA0jaLCxuWt/BgZylp7tAzYKR8lnWmtUCPm4+BtjyVDYtDCiGBD9Z4P13RFWvJHw5aapx/5W/CuvVyI7pKwvc2IT+KPxCUhH1XI8ca5RN3C9NoPJJf6qpg4g0rJH3aaWkoMRrYvQ+5PXXYUzjtRHImghRGd/ydERYoAZXuGSbPkm9Y/p2X8unLcW+F0xpJD98+ZI+tzSsI99Zs5wijSUGYr9/j18KHFTMQ8n+1jauc5bCCegN27dPeKXNSZ5riXFL2XX6BkY68y58UaNzmeGMiUL9BOV1iV+PMb7B7PYs7oFLjAhh0EdyvfHkrh/ZV9BEhtFa7yXp8XR0J6vz1YV9R6DYJmLjOEbhU8N0gc3tZm4Qz39lIIG6w3FDAgMBAAGjggFUMIIBUDAdBgNVHQ4EFgQUrsRtyWJftjpdRM0+925Y6Cl08SUwggEfBgNVHSMEggEWMIIBEoAUrsRtyWJftjpdRM0+925Y6Cl08SWhge6kgeswgegxCzAJBgNVBAYTAlVTMQswCQYDVQQIEwJDQTETMBEGA1UEBxMKTG9zQW5nZWxlczEgMB4GA1UEChMXUHJpdmF0ZSBJbnRlcm5ldCBBY2Nlc3MxIDAeBgNVBAsTF1ByaXZhdGUgSW50ZXJuZXQgQWNjZXNzMSAwHgYDVQQDExdQcml2YXRlIEludGVybmV0IEFjY2VzczEgMB4GA1UEKRMXUHJpdmF0ZSBJbnRlcm5ldCBBY2Nlc3MxLzAtBgkqhkiG9w0BCQEWIHNlY3VyZUBwcml2YXRlaW50ZXJuZXRhY2Nlc3MuY29tggkAnS7684Nkme0wDAYDVR0TBAUwAwEB/zANBgkqhkiG9w0BAQ0FAAOCAgEAJsfhsPk3r8kLXLxY+v+vHzbr4ufNtqnL9/1Uuf8NrsCtpXAoyZ0YqfbkWx3NHTZ7OE9ZRhdMP/RqHQE1p4N4Sa1nZKhTKasV6KhHDqSCt/dvEm89xWm2MVA7nyzQxVlHa9AkcBaemcXEiyT19XdpiXOP4Vhs+J1R5m8zQOxZlV1GtF9vsXmJqWZpOVPmZ8f35BCsYPvv4yMewnrtAC8PFEK/bOPeYcKN50bol22QYaZuLfpkHfNiFTnfMh8sl/ablPyNY7DUNiP5DRcMdIwmfGQxR5WEQoHL3yPJ42LkB5zs6jIm26DGNXfwura/mi105+ENH1CaROtRYwkiHb08U6qLXXJz80mWJkT90nr8Asj35xN2cUppg74nG3YVav/38P48T56hG1NHbYF5uOCske19F6wi9maUoto/3vEr0rnXJUp2KODmKdvBI7co245lHBABWikk8VfejQSlCtDBXn644ZMtAdoxKNfR2WTFVEwJiyd1Fzx0yujuiXDROLhISLQDRjVVAvawrAtLZWYK31bY7KlezPlQnl/D9Asxe85l8jO5+0LdJ6VyOs/Hd4w52alDW/MFySDZSfQHMTIc30hLBJ8OnCEIvluVQQ2UQvoW+no177N9L2Y+M9TcTA62ZyMXShHQGeh20rb4kK8f+iFX8NxtdHVSkxMEFSfDDyQ="
)
func PIAGeoChoices() []string {
return []string{"AU Melbourne", "AU Perth", "AU Sydney", "Austria", "Belgium", "CA Montreal", "CA Toronto", "CA Vancouver", "Czech Republic", "DE Berlin", "DE Frankfurt", "Denmark", "Finland", "France", "Hong Kong", "Hungary", "India", "Ireland", "Israel", "Italy", "Japan", "Luxembourg", "Mexico", "Netherlands", "New Zealand", "Norway", "Poland", "Romania", "Singapore", "Spain", "Sweden", "Switzerland", "UAE", "UK London", "UK Manchester", "UK Southampton", "US Atlanta", "US California", "US Chicago", "US Denver", "US East", "US Florida", "US Houston", "US Las Vegas", "US New York City", "US Seattle", "US Silicon Valley", "US Texas", "US Washington DC", "US West"}
}
func PIAGeoToSubdomainMapping(region models.PIARegion) (subdomain string, err error) {
mapping := map[models.PIARegion]string{
models.PIARegion("AU Melbourne"): "au-melbourne",
models.PIARegion("AU Perth"): "au-perth",
models.PIARegion("AU Sydney"): "au-sydney",
models.PIARegion("Austria"): "austria",
models.PIARegion("Belgium"): "belgium",
models.PIARegion("CA Montreal"): "ca-montreal",
models.PIARegion("CA Toronto"): "ca-toronto",
models.PIARegion("CA Vancouver"): "ca-vancouver",
models.PIARegion("Czech Republic"): "czech",
models.PIARegion("DE Berlin"): "de-berlin",
models.PIARegion("DE Frankfurt"): "de-frankfurt",
models.PIARegion("Denmark"): "denmark",
models.PIARegion("Finland"): "fi",
models.PIARegion("France"): "france",
models.PIARegion("Hong Kong"): "hk",
models.PIARegion("Hungary"): "hungary",
models.PIARegion("India"): "in",
models.PIARegion("Ireland"): "ireland",
models.PIARegion("Israel"): "israel",
models.PIARegion("Italy"): "italy",
models.PIARegion("Japan"): "japan",
models.PIARegion("Luxembourg"): "lu",
models.PIARegion("Mexico"): "mexico",
models.PIARegion("Netherlands"): "nl",
models.PIARegion("New Zealand"): "nz",
models.PIARegion("Norway"): "no",
models.PIARegion("Poland"): "poland",
models.PIARegion("Romania"): "ro",
models.PIARegion("Singapore"): "sg",
models.PIARegion("Spain"): "spain",
models.PIARegion("Sweden"): "sweden",
models.PIARegion("Switzerland"): "swiss",
models.PIARegion("UAE"): "ae",
models.PIARegion("UK London"): "uk-london",
models.PIARegion("UK Manchester"): "uk-manchester",
models.PIARegion("UK Southampton"): "uk-southampton",
models.PIARegion("US Atlanta"): "us-atlanta",
models.PIARegion("US California"): "us-california",
models.PIARegion("US Chicago"): "us-chicago",
models.PIARegion("US Denver"): "us-denver",
models.PIARegion("US East"): "us-east",
models.PIARegion("US Florida"): "us-florida",
models.PIARegion("US Houston"): "us-houston",
models.PIARegion("US Las Vegas"): "us-lasvegas",
models.PIARegion("US New York City"): "us-newyorkcity",
models.PIARegion("US Seattle"): "us-seattle",
models.PIARegion("US Silicon Valley"): "us-siliconvalley",
models.PIARegion("US Texas"): "us-texas",
models.PIARegion("US Washington DC"): "us-washingtondc",
models.PIARegion("US West"): "us-west",
}
subdomain, ok := mapping[region]
if !ok {
return "", fmt.Errorf("PIA region %q does not exist and can only be one of ", strings.Join(PIAGeoChoices(), ","))
}
return subdomain, nil
}
const (
PIAPortForwardURL models.URL = "http://209.222.18.222:2000"
)
package constants
import (
"net"
"github.com/qdm12/gluetun/internal/models"
)
//nolint:lll
const (
PIAEncryptionPresetNormal = "normal"
PIAEncryptionPresetStrong = "strong"
PiaX509CRLNormal = "MIICWDCCAUAwDQYJKoZIhvcNAQENBQAwgegxCzAJBgNVBAYTAlVTMQswCQYDVQQIEwJDQTETMBEGA1UEBxMKTG9zQW5nZWxlczEgMB4GA1UEChMXUHJpdmF0ZSBJbnRlcm5ldCBBY2Nlc3MxIDAeBgNVBAsTF1ByaXZhdGUgSW50ZXJuZXQgQWNjZXNzMSAwHgYDVQQDExdQcml2YXRlIEludGVybmV0IEFjY2VzczEgMB4GA1UEKRMXUHJpdmF0ZSBJbnRlcm5ldCBBY2Nlc3MxLzAtBgkqhkiG9w0BCQEWIHNlY3VyZUBwcml2YXRlaW50ZXJuZXRhY2Nlc3MuY29tFw0xNjA3MDgxOTAwNDZaFw0zNjA3MDMxOTAwNDZaMCYwEQIBARcMMTYwNzA4MTkwMDQ2MBECAQYXDDE2MDcwODE5MDA0NjANBgkqhkiG9w0BAQ0FAAOCAQEAQZo9X97ci8EcPYu/uK2HB152OZbeZCINmYyluLDOdcSvg6B5jI+ffKN3laDvczsG6CxmY3jNyc79XVpEYUnq4rT3FfveW1+Ralf+Vf38HdpwB8EWB4hZlQ205+21CALLvZvR8HcPxC9KEnev1mU46wkTiov0EKc+EdRxkj5yMgv0V2Reze7AP+NQ9ykvDScH4eYCsmufNpIjBLhpLE2cuZZXBLcPhuRzVoU3l7A9lvzG9mjA5YijHJGHNjlWFqyrn1CfYS6koa4TGEPngBoAziWRbDGdhEgJABHrpoaFYaL61zqyMR6jC0K2ps9qyZAN74LEBedEfK7tBOzWMwr58A=="
PiaX509CRLStrong = "MIIDWDCCAUAwDQYJKoZIhvcNAQENBQAwgegxCzAJBgNVBAYTAlVTMQswCQYDVQQIEwJDQTETMBEGA1UEBxMKTG9zQW5nZWxlczEgMB4GA1UEChMXUHJpdmF0ZSBJbnRlcm5ldCBBY2Nlc3MxIDAeBgNVBAsTF1ByaXZhdGUgSW50ZXJuZXQgQWNjZXNzMSAwHgYDVQQDExdQcml2YXRlIEludGVybmV0IEFjY2VzczEgMB4GA1UEKRMXUHJpdmF0ZSBJbnRlcm5ldCBBY2Nlc3MxLzAtBgkqhkiG9w0BCQEWIHNlY3VyZUBwcml2YXRlaW50ZXJuZXRhY2Nlc3MuY29tFw0xNjA3MDgxOTAwNDZaFw0zNjA3MDMxOTAwNDZaMCYwEQIBARcMMTYwNzA4MTkwMDQ2MBECAQYXDDE2MDcwODE5MDA0NjANBgkqhkiG9w0BAQ0FAAOCAgEAppFfEpGsasjB1QgJcosGpzbf2kfRhM84o2TlqY1ua+Gi5TMdKydA3LJcNTjlI9a0TYAJfeRX5IkpoglSUuHuJgXhP3nEvX10mjXDpcu/YvM8TdE5JV2+EGqZ80kFtBeOq94WcpiVKFTR4fO+VkOK9zwspFfb1cNs9rHvgJ1QMkRUF8PpLN6AkntHY0+6DnigtSaKqldqjKTDTv2OeH3nPoh80SGrt0oCOmYKfWTJGpggMGKvIdvU3vH9+EuILZKKIskt+1dwdfA5Bkz1GLmiQG7+9ZZBQUjBG9Dos4hfX/rwJ3eU8oUIm4WoTz9rb71SOEuUUjP5NPy9HNx2vx+cVvLsTF4ZDZaUztW9o9JmIURDtbeyqxuHN3prlPWB6aj73IIm2dsDQvs3XXwRIxs8NwLbJ6CyEuvEOVCskdM8rdADWx1J0lRNlOJ0Z8ieLLEmYAA834VN1SboB6wJIAPxQU3rcBhXqO9y8aa2oRMg8NxZ5gr+PnKVMqag1x0IxbIgLxtkXQvxXxQHEMSODzvcOfK/nBRBsqTj30P+R87sU8titOoxNeRnBDRNhdEy/QGAqGh62ShPpQUCJdnKRiRTjnil9hMQHevoSuFKeEMO30FQL7BZyo37GFU+q1WPCplVZgCP9hC8Rn5K2+f6KLFo5bhtowSmu+GY1yZtg+RTtsA="
PIACertificateNormal = "MIIFqzCCBJOgAwIBAgIJAKZ7D5Yv87qDMA0GCSqGSIb3DQEBDQUAMIHoMQswCQYDVQQGEwJVUzELMAkGA1UECBMCQ0ExEzARBgNVBAcTCkxvc0FuZ2VsZXMxIDAeBgNVBAoTF1ByaXZhdGUgSW50ZXJuZXQgQWNjZXNzMSAwHgYDVQQLExdQcml2YXRlIEludGVybmV0IEFjY2VzczEgMB4GA1UEAxMXUHJpdmF0ZSBJbnRlcm5ldCBBY2Nlc3MxIDAeBgNVBCkTF1ByaXZhdGUgSW50ZXJuZXQgQWNjZXNzMS8wLQYJKoZIhvcNAQkBFiBzZWN1cmVAcHJpdmF0ZWludGVybmV0YWNjZXNzLmNvbTAeFw0xNDA0MTcxNzM1MThaFw0zNDA0MTIxNzM1MThaMIHoMQswCQYDVQQGEwJVUzELMAkGA1UECBMCQ0ExEzARBgNVBAcTCkxvc0FuZ2VsZXMxIDAeBgNVBAoTF1ByaXZhdGUgSW50ZXJuZXQgQWNjZXNzMSAwHgYDVQQLExdQcml2YXRlIEludGVybmV0IEFjY2VzczEgMB4GA1UEAxMXUHJpdmF0ZSBJbnRlcm5ldCBBY2Nlc3MxIDAeBgNVBCkTF1ByaXZhdGUgSW50ZXJuZXQgQWNjZXNzMS8wLQYJKoZIhvcNAQkBFiBzZWN1cmVAcHJpdmF0ZWludGVybmV0YWNjZXNzLmNvbTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAPXDL1L9tX6DGf36liA7UBTy5I869z0UVo3lImfOs/GSiFKPtInlesP65577nd7UNzzXlH/P/CnFPdBWlLp5ze3HRBCc/Avgr5CdMRkEsySL5GHBZsx6w2cayQ2EcRhVTwWpcdldeNO+pPr9rIgPrtXqT4SWViTQRBeGM8CDxAyTopTsobjSiYZCF9Ta1gunl0G/8Vfp+SXfYCC+ZzWvP+L1pFhPRqzQQ8k+wMZIovObK1s+nlwPaLyayzw9a8sUnvWB/5rGPdIYnQWPgoNlLN9HpSmsAcw2z8DXI9pIxbr74cb3/HSfuYGOLkRqrOk6h4RCOfuWoTrZup1uEOn+fw8CAwEAAaOCAVQwggFQMB0GA1UdDgQWBBQv63nQ/pJAt5tLy8VJcbHe22ZOsjCCAR8GA1UdIwSCARYwggESgBQv63nQ/pJAt5tLy8VJcbHe22ZOsqGB7qSB6zCB6DELMAkGA1UEBhMCVVMxCzAJBgNVBAgTAkNBMRMwEQYDVQQHEwpMb3NBbmdlbGVzMSAwHgYDVQQKExdQcml2YXRlIEludGVybmV0IEFjY2VzczEgMB4GA1UECxMXUHJpdmF0ZSBJbnRlcm5ldCBBY2Nlc3MxIDAeBgNVBAMTF1ByaXZhdGUgSW50ZXJuZXQgQWNjZXNzMSAwHgYDVQQpExdQcml2YXRlIEludGVybmV0IEFjY2VzczEvMC0GCSqGSIb3DQEJARYgc2VjdXJlQHByaXZhdGVpbnRlcm5ldGFjY2Vzcy5jb22CCQCmew+WL/O6gzAMBgNVHRMEBTADAQH/MA0GCSqGSIb3DQEBDQUAA4IBAQAna5PgrtxfwTumD4+3/SYvwoD66cB8IcK//h1mCzAduU8KgUXocLx7QgJWo9lnZ8xUryXvWab2usg4fqk7FPi00bED4f4qVQFVfGfPZIH9QQ7/48bPM9RyfzImZWUCenK37pdw4Bvgoys2rHLHbGen7f28knT2j/cbMxd78tQc20TIObGjo8+ISTRclSTRBtyCGohseKYpTS9himFERpUgNtefvYHbn70mIOzfOJFTVqfrptf9jXa9N8Mpy3ayfodz1wiqdteqFXkTYoSDctgKMiZ6GdocK9nMroQipIQtpnwd4yBDWIyC6Bvlkrq5TQUtYDQ8z9v+DMO6iwyIDRiU"
PIACertificateStrong = "MIIHqzCCBZOgAwIBAgIJAJ0u+vODZJntMA0GCSqGSIb3DQEBDQUAMIHoMQswCQYDVQQGEwJVUzELMAkGA1UECBMCQ0ExEzARBgNVBAcTCkxvc0FuZ2VsZXMxIDAeBgNVBAoTF1ByaXZhdGUgSW50ZXJuZXQgQWNjZXNzMSAwHgYDVQQLExdQcml2YXRlIEludGVybmV0IEFjY2VzczEgMB4GA1UEAxMXUHJpdmF0ZSBJbnRlcm5ldCBBY2Nlc3MxIDAeBgNVBCkTF1ByaXZhdGUgSW50ZXJuZXQgQWNjZXNzMS8wLQYJKoZIhvcNAQkBFiBzZWN1cmVAcHJpdmF0ZWludGVybmV0YWNjZXNzLmNvbTAeFw0xNDA0MTcxNzQwMzNaFw0zNDA0MTIxNzQwMzNaMIHoMQswCQYDVQQGEwJVUzELMAkGA1UECBMCQ0ExEzARBgNVBAcTCkxvc0FuZ2VsZXMxIDAeBgNVBAoTF1ByaXZhdGUgSW50ZXJuZXQgQWNjZXNzMSAwHgYDVQQLExdQcml2YXRlIEludGVybmV0IEFjY2VzczEgMB4GA1UEAxMXUHJpdmF0ZSBJbnRlcm5ldCBBY2Nlc3MxIDAeBgNVBCkTF1ByaXZhdGUgSW50ZXJuZXQgQWNjZXNzMS8wLQYJKoZIhvcNAQkBFiBzZWN1cmVAcHJpdmF0ZWludGVybmV0YWNjZXNzLmNvbTCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBALVkhjumaqBbL8aSgj6xbX1QPTfTd1qHsAZd2B97m8Vw31c/2yQgZNf5qZY0+jOIHULNDe4R9TIvyBEbvnAg/OkPw8n/+ScgYOeH876VUXzjLDBnDb8DLr/+w9oVsuDeFJ9KV2UFM1OYX0SnkHnrYAN2QLF98ESK4NCSU01h5zkcgmQ+qKSfA9Ny0/UpsKPBFqsQ25NvjDWFhCpeqCHKUJ4Be27CDbSl7lAkBuHMPHJs8f8xPgAbHRXZOxVCpayZ2SNDfCwsnGWpWFoMGvdMbygngCn6jA/W1VSFOlRlfLuuGe7QFfDwA0jaLCxuWt/BgZylp7tAzYKR8lnWmtUCPm4+BtjyVDYtDCiGBD9Z4P13RFWvJHw5aapx/5W/CuvVyI7pKwvc2IT+KPxCUhH1XI8ca5RN3C9NoPJJf6qpg4g0rJH3aaWkoMRrYvQ+5PXXYUzjtRHImghRGd/ydERYoAZXuGSbPkm9Y/p2X8unLcW+F0xpJD98+ZI+tzSsI99Zs5wijSUGYr9/j18KHFTMQ8n+1jauc5bCCegN27dPeKXNSZ5riXFL2XX6BkY68y58UaNzmeGMiUL9BOV1iV+PMb7B7PYs7oFLjAhh0EdyvfHkrh/ZV9BEhtFa7yXp8XR0J6vz1YV9R6DYJmLjOEbhU8N0gc3tZm4Qz39lIIG6w3FDAgMBAAGjggFUMIIBUDAdBgNVHQ4EFgQUrsRtyWJftjpdRM0+925Y6Cl08SUwggEfBgNVHSMEggEWMIIBEoAUrsRtyWJftjpdRM0+925Y6Cl08SWhge6kgeswgegxCzAJBgNVBAYTAlVTMQswCQYDVQQIEwJDQTETMBEGA1UEBxMKTG9zQW5nZWxlczEgMB4GA1UEChMXUHJpdmF0ZSBJbnRlcm5ldCBBY2Nlc3MxIDAeBgNVBAsTF1ByaXZhdGUgSW50ZXJuZXQgQWNjZXNzMSAwHgYDVQQDExdQcml2YXRlIEludGVybmV0IEFjY2VzczEgMB4GA1UEKRMXUHJpdmF0ZSBJbnRlcm5ldCBBY2Nlc3MxLzAtBgkqhkiG9w0BCQEWIHNlY3VyZUBwcml2YXRlaW50ZXJuZXRhY2Nlc3MuY29tggkAnS7684Nkme0wDAYDVR0TBAUwAwEB/zANBgkqhkiG9w0BAQ0FAAOCAgEAJsfhsPk3r8kLXLxY+v+vHzbr4ufNtqnL9/1Uuf8NrsCtpXAoyZ0YqfbkWx3NHTZ7OE9ZRhdMP/RqHQE1p4N4Sa1nZKhTKasV6KhHDqSCt/dvEm89xWm2MVA7nyzQxVlHa9AkcBaemcXEiyT19XdpiXOP4Vhs+J1R5m8zQOxZlV1GtF9vsXmJqWZpOVPmZ8f35BCsYPvv4yMewnrtAC8PFEK/bOPeYcKN50bol22QYaZuLfpkHfNiFTnfMh8sl/ablPyNY7DUNiP5DRcMdIwmfGQxR5WEQoHL3yPJ42LkB5zs6jIm26DGNXfwura/mi105+ENH1CaROtRYwkiHb08U6qLXXJz80mWJkT90nr8Asj35xN2cUppg74nG3YVav/38P48T56hG1NHbYF5uOCske19F6wi9maUoto/3vEr0rnXJUp2KODmKdvBI7co245lHBABWikk8VfejQSlCtDBXn644ZMtAdoxKNfR2WTFVEwJiyd1Fzx0yujuiXDROLhISLQDRjVVAvawrAtLZWYK31bY7KlezPlQnl/D9Asxe85l8jO5+0LdJ6VyOs/Hd4w52alDW/MFySDZSfQHMTIc30hLBJ8OnCEIvluVQQ2UQvoW+no177N9L2Y+M9TcTA62ZyMXShHQGeh20rb4kK8f+iFX8NxtdHVSkxMEFSfDDyQ="
)
func PIAGeoChoices() (choices []string) {
servers := PIAServers()
choices = make([]string, len(servers))
for i := range servers {
choices[i] = servers[i].Region
}
return choices
}
//nolint:lll
func PIAServers() []models.PIAServer {
return []models.PIAServer{
{Region: "AU Melbourne", PortForward: true, OpenvpnUDP: models.PIAServerOpenvpn{CN: "melbourne404", IPs: []net.IP{{103, 2, 198, 93}}}, OpenvpnTCP: models.PIAServerOpenvpn{CN: "melbourne404", IPs: []net.IP{{103, 2, 198, 85}}}},
{Region: "AU Perth", PortForward: true, OpenvpnUDP: models.PIAServerOpenvpn{CN: "perth403", IPs: []net.IP{{43, 250, 205, 156}}}, OpenvpnTCP: models.PIAServerOpenvpn{CN: "perth403", IPs: []net.IP{{43, 250, 205, 153}}}},
{Region: "AU Sydney", PortForward: true, OpenvpnUDP: models.PIAServerOpenvpn{CN: "sydney408", IPs: []net.IP{{117, 120, 10, 56}}}, OpenvpnTCP: models.PIAServerOpenvpn{CN: "sydney408", IPs: []net.IP{{117, 120, 10, 48}}}},
{Region: "Albania", PortForward: true, OpenvpnUDP: models.PIAServerOpenvpn{CN: "tirana402", IPs: []net.IP{{31, 171, 154, 115}}}, OpenvpnTCP: models.PIAServerOpenvpn{CN: "tirana402", IPs: []net.IP{{31, 171, 154, 118}}}},
{Region: "Algeria", PortForward: true, OpenvpnUDP: models.PIAServerOpenvpn{CN: "algiers404", IPs: []net.IP{{176, 125, 228, 19}}}, OpenvpnTCP: models.PIAServerOpenvpn{CN: "algiers404", IPs: []net.IP{{176, 125, 228, 26}}}},
{Region: "Andorra", PortForward: true, OpenvpnUDP: models.PIAServerOpenvpn{CN: "andorra405", IPs: []net.IP{{188, 241, 82, 40}}}, OpenvpnTCP: models.PIAServerOpenvpn{CN: "andorra405", IPs: []net.IP{{188, 241, 82, 37}}}},
{Region: "Argentina", PortForward: true, OpenvpnUDP: models.PIAServerOpenvpn{CN: "buenosaires401", IPs: []net.IP{{190, 106, 134, 95}}}, OpenvpnTCP: models.PIAServerOpenvpn{CN: "buenosaires401", IPs: []net.IP{{190, 106, 134, 85}}}},
{Region: "Armenia", PortForward: true, OpenvpnUDP: models.PIAServerOpenvpn{CN: "armenia403", IPs: []net.IP{{185, 253, 160, 3}}}, OpenvpnTCP: models.PIAServerOpenvpn{CN: "armenia403", IPs: []net.IP{{185, 253, 160, 9}}}},
{Region: "Austria", PortForward: true, OpenvpnUDP: models.PIAServerOpenvpn{CN: "vienna403", IPs: []net.IP{{156, 146, 60, 108}}}, OpenvpnTCP: models.PIAServerOpenvpn{CN: "vienna403", IPs: []net.IP{{156, 146, 60, 110}}}},
{Region: "Bahamas", PortForward: true, OpenvpnUDP: models.PIAServerOpenvpn{CN: "bahamas404", IPs: []net.IP{{95, 181, 238, 9}}}, OpenvpnTCP: models.PIAServerOpenvpn{CN: "bahamas404", IPs: []net.IP{{95, 181, 238, 9}}}},
{Region: "Bangladesh", PortForward: true, OpenvpnUDP: models.PIAServerOpenvpn{CN: "bangladesh403", IPs: []net.IP{{84, 252, 93, 7}}}, OpenvpnTCP: models.PIAServerOpenvpn{CN: "bangladesh403", IPs: []net.IP{{84, 252, 93, 7}}}},
{Region: "Belgium", PortForward: true, OpenvpnUDP: models.PIAServerOpenvpn{CN: "brussels404", IPs: []net.IP{{37, 120, 143, 156}}}, OpenvpnTCP: models.PIAServerOpenvpn{CN: "brussels404", IPs: []net.IP{{37, 120, 143, 156}}}},
{Region: "Bosnia and Herzegovina", PortForward: true, OpenvpnUDP: models.PIAServerOpenvpn{CN: "sarajevo402", IPs: []net.IP{{185, 212, 111, 4}}}, OpenvpnTCP: models.PIAServerOpenvpn{CN: "sarajevo402", IPs: []net.IP{{185, 212, 111, 71}}}},
{Region: "Brazil", PortForward: true, OpenvpnUDP: models.PIAServerOpenvpn{CN: "saopaolo401", IPs: []net.IP{{45, 133, 180, 236}}}, OpenvpnTCP: models.PIAServerOpenvpn{CN: "saopaolo401", IPs: []net.IP{{45, 133, 180, 227}}}},
{Region: "Bulgaria", PortForward: true, OpenvpnUDP: models.PIAServerOpenvpn{CN: "sofia402", IPs: []net.IP{{217, 138, 221, 89}}}, OpenvpnTCP: models.PIAServerOpenvpn{CN: "sofia402", IPs: []net.IP{{217, 138, 221, 89}}}},
{Region: "CA Montreal", PortForward: true, OpenvpnUDP: models.PIAServerOpenvpn{CN: "montreal410", IPs: []net.IP{{199, 36, 223, 210}}}, OpenvpnTCP: models.PIAServerOpenvpn{CN: "montreal410", IPs: []net.IP{{199, 36, 223, 210}}}},
{Region: "CA Ontario", PortForward: true, OpenvpnUDP: models.PIAServerOpenvpn{CN: "ontario408", IPs: []net.IP{{172, 98, 92, 87}}}, OpenvpnTCP: models.PIAServerOpenvpn{CN: "ontario408", IPs: []net.IP{{172, 98, 92, 80}}}},
{Region: "CA Toronto", PortForward: true, OpenvpnUDP: models.PIAServerOpenvpn{CN: "toronto402", IPs: []net.IP{{66, 115, 142, 58}}}, OpenvpnTCP: models.PIAServerOpenvpn{CN: "toronto402", IPs: []net.IP{{66, 115, 142, 58}}}},
{Region: "CA Vancouver", PortForward: true, OpenvpnUDP: models.PIAServerOpenvpn{CN: "vancouver411", IPs: []net.IP{{208, 78, 42, 168}}}, OpenvpnTCP: models.PIAServerOpenvpn{CN: "vancouver411", IPs: []net.IP{{208, 78, 42, 161}}}},
{Region: "Cambodia", PortForward: true, OpenvpnUDP: models.PIAServerOpenvpn{CN: "cambodia401", IPs: []net.IP{{188, 215, 235, 109}}}, OpenvpnTCP: models.PIAServerOpenvpn{CN: "cambodia401", IPs: []net.IP{{188, 215, 235, 110}}}},
{Region: "China", PortForward: true, OpenvpnUDP: models.PIAServerOpenvpn{CN: "china404", IPs: []net.IP{{188, 241, 80, 9}}}, OpenvpnTCP: models.PIAServerOpenvpn{CN: "china404", IPs: []net.IP{{188, 241, 80, 4}}}},
{Region: "Cyprus", PortForward: true, OpenvpnUDP: models.PIAServerOpenvpn{CN: "cyprus403", IPs: []net.IP{{185, 253, 162, 8}}}, OpenvpnTCP: models.PIAServerOpenvpn{CN: "cyprus403", IPs: []net.IP{{185, 253, 162, 14}}}},
{Region: "Czech Republic", PortForward: true, OpenvpnUDP: models.PIAServerOpenvpn{CN: "prague405", IPs: []net.IP{{143, 244, 59, 168}}}, OpenvpnTCP: models.PIAServerOpenvpn{CN: "prague405", IPs: []net.IP{{143, 244, 59, 154}}}},
{Region: "DE Berlin", PortForward: true, OpenvpnUDP: models.PIAServerOpenvpn{CN: "berlin425", IPs: []net.IP{{154, 13, 1, 148}}}, OpenvpnTCP: models.PIAServerOpenvpn{CN: "berlin425", IPs: []net.IP{{154, 13, 1, 146}}}},
{Region: "DE Frankfurt", PortForward: true, OpenvpnUDP: models.PIAServerOpenvpn{CN: "frankfurt440", IPs: []net.IP{{185, 216, 33, 165}}}, OpenvpnTCP: models.PIAServerOpenvpn{CN: "frankfurt440", IPs: []net.IP{{185, 216, 33, 166}}}},
{Region: "Denmark", PortForward: true, OpenvpnUDP: models.PIAServerOpenvpn{CN: "copenhagen404", IPs: []net.IP{{188, 126, 94, 190}}}, OpenvpnTCP: models.PIAServerOpenvpn{CN: "copenhagen404", IPs: []net.IP{{188, 126, 94, 165}}}},
{Region: "Egypt", PortForward: true, OpenvpnUDP: models.PIAServerOpenvpn{CN: "cairo402", IPs: []net.IP{{188, 214, 122, 126}}}, OpenvpnTCP: models.PIAServerOpenvpn{CN: "cairo402", IPs: []net.IP{{188, 214, 122, 123}}}},
{Region: "Estonia", PortForward: true, OpenvpnUDP: models.PIAServerOpenvpn{CN: "talinn402", IPs: []net.IP{{95, 153, 31, 68}}}, OpenvpnTCP: models.PIAServerOpenvpn{CN: "talinn402", IPs: []net.IP{{95, 153, 31, 78}}}},
{Region: "Finland", PortForward: true, OpenvpnUDP: models.PIAServerOpenvpn{CN: "helsinki402", IPs: []net.IP{{188, 126, 89, 45}}}, OpenvpnTCP: models.PIAServerOpenvpn{CN: "helsinki402", IPs: []net.IP{{188, 126, 89, 35}}}},
{Region: "France", PortForward: true, OpenvpnUDP: models.PIAServerOpenvpn{CN: "paris406", IPs: []net.IP{{143, 244, 57, 169}}}, OpenvpnTCP: models.PIAServerOpenvpn{CN: "paris406", IPs: []net.IP{{143, 244, 57, 169}}}},
{Region: "Georgia", PortForward: true, OpenvpnUDP: models.PIAServerOpenvpn{CN: "georgia403", IPs: []net.IP{{95, 181, 236, 8}}}, OpenvpnTCP: models.PIAServerOpenvpn{CN: "georgia403", IPs: []net.IP{{95, 181, 236, 10}}}},
{Region: "Greece", PortForward: true, OpenvpnUDP: models.PIAServerOpenvpn{CN: "athens401", IPs: []net.IP{{154, 57, 3, 87}}}, OpenvpnTCP: models.PIAServerOpenvpn{CN: "athens401", IPs: []net.IP{{154, 57, 3, 85}}}},
{Region: "Greenland", PortForward: true, OpenvpnUDP: models.PIAServerOpenvpn{CN: "greenland404", IPs: []net.IP{{91, 90, 120, 149}}}, OpenvpnTCP: models.PIAServerOpenvpn{CN: "greenland404", IPs: []net.IP{{91, 90, 120, 147}}}},
{Region: "Hong Kong", PortForward: true, OpenvpnUDP: models.PIAServerOpenvpn{CN: "china403", IPs: []net.IP{{86, 107, 104, 213}}}, OpenvpnTCP: models.PIAServerOpenvpn{CN: "china403", IPs: []net.IP{{86, 107, 104, 219}}}},
{Region: "Hungary", PortForward: true, OpenvpnUDP: models.PIAServerOpenvpn{CN: "budapest402", IPs: []net.IP{{86, 106, 74, 115}}}, OpenvpnTCP: models.PIAServerOpenvpn{CN: "budapest402", IPs: []net.IP{{86, 106, 74, 117}}}},
{Region: "Iceland", PortForward: true, OpenvpnUDP: models.PIAServerOpenvpn{CN: "reykjavik402", IPs: []net.IP{{45, 133, 193, 83}}}, OpenvpnTCP: models.PIAServerOpenvpn{CN: "reykjavik402", IPs: []net.IP{{45, 133, 193, 88}}}},
{Region: "India", PortForward: true, OpenvpnUDP: models.PIAServerOpenvpn{CN: "mumbai402", IPs: []net.IP{{45, 120, 139, 138}}}, OpenvpnTCP: models.PIAServerOpenvpn{CN: "mumbai402", IPs: []net.IP{{45, 120, 139, 127}}}},
{Region: "Ireland", PortForward: true, OpenvpnUDP: models.PIAServerOpenvpn{CN: "dublin411", IPs: []net.IP{{188, 241, 178, 23}}}, OpenvpnTCP: models.PIAServerOpenvpn{CN: "dublin411", IPs: []net.IP{{188, 241, 178, 30}}}},
{Region: "Isle of Man", PortForward: true, OpenvpnUDP: models.PIAServerOpenvpn{CN: "douglas403", IPs: []net.IP{{91, 90, 124, 7}}}, OpenvpnTCP: models.PIAServerOpenvpn{CN: "douglas403", IPs: []net.IP{{91, 90, 124, 18}}}},
{Region: "Israel", PortForward: true, OpenvpnUDP: models.PIAServerOpenvpn{CN: "jerusalem407", IPs: []net.IP{{185, 77, 248, 91}}}, OpenvpnTCP: models.PIAServerOpenvpn{CN: "jerusalem407", IPs: []net.IP{{185, 77, 248, 91}}}},
{Region: "Italy", PortForward: true, OpenvpnUDP: models.PIAServerOpenvpn{CN: "milano403", IPs: []net.IP{{156, 146, 41, 74}}}, OpenvpnTCP: models.PIAServerOpenvpn{CN: "milano403", IPs: []net.IP{{156, 146, 41, 84}}}},
{Region: "Japan", PortForward: true, OpenvpnUDP: models.PIAServerOpenvpn{CN: "tokyo401", IPs: []net.IP{{156, 146, 34, 130}}}, OpenvpnTCP: models.PIAServerOpenvpn{CN: "tokyo401", IPs: []net.IP{{156, 146, 34, 130}}}},
{Region: "Kazakhstan", PortForward: true, OpenvpnUDP: models.PIAServerOpenvpn{CN: "kazakhstan403", IPs: []net.IP{{62, 133, 47, 13}}}, OpenvpnTCP: models.PIAServerOpenvpn{CN: "kazakhstan403", IPs: []net.IP{{62, 133, 47, 5}}}},
{Region: "Latvia", PortForward: true, OpenvpnUDP: models.PIAServerOpenvpn{CN: "riga401", IPs: []net.IP{{109, 248, 149, 5}}}, OpenvpnTCP: models.PIAServerOpenvpn{CN: "riga401", IPs: []net.IP{{109, 248, 149, 8}}}},
{Region: "Liechtenstein", PortForward: true, OpenvpnUDP: models.PIAServerOpenvpn{CN: "liechtenstein403", IPs: []net.IP{{91, 90, 122, 7}}}, OpenvpnTCP: models.PIAServerOpenvpn{CN: "liechtenstein403", IPs: []net.IP{{91, 90, 122, 18}}}},
{Region: "Lithuania", PortForward: true, OpenvpnUDP: models.PIAServerOpenvpn{CN: "vilnius403", IPs: []net.IP{{85, 206, 165, 118}}}, OpenvpnTCP: models.PIAServerOpenvpn{CN: "vilnius403", IPs: []net.IP{{85, 206, 165, 116}}}},
{Region: "Luxembourg", PortForward: true, OpenvpnUDP: models.PIAServerOpenvpn{CN: "luxembourg407", IPs: []net.IP{{5, 253, 204, 150}}}, OpenvpnTCP: models.PIAServerOpenvpn{CN: "luxembourg407", IPs: []net.IP{{5, 253, 204, 155}}}},
{Region: "Macao", PortForward: true, OpenvpnUDP: models.PIAServerOpenvpn{CN: "macau403", IPs: []net.IP{{84, 252, 92, 6}}}, OpenvpnTCP: models.PIAServerOpenvpn{CN: "macau403", IPs: []net.IP{{84, 252, 92, 15}}}},
{Region: "Macedonia", PortForward: true, OpenvpnUDP: models.PIAServerOpenvpn{CN: "macedonia401", IPs: []net.IP{{185, 225, 28, 120}}}, OpenvpnTCP: models.PIAServerOpenvpn{CN: "macedonia401", IPs: []net.IP{{185, 225, 28, 120}}}},
{Region: "Malta", PortForward: true, OpenvpnUDP: models.PIAServerOpenvpn{CN: "malta403", IPs: []net.IP{{176, 125, 230, 13}}}, OpenvpnTCP: models.PIAServerOpenvpn{CN: "malta403", IPs: []net.IP{{176, 125, 230, 7}}}},
{Region: "Mexico", PortForward: true, OpenvpnUDP: models.PIAServerOpenvpn{CN: "mexico403", IPs: []net.IP{{77, 81, 142, 14}}}, OpenvpnTCP: models.PIAServerOpenvpn{CN: "mexico403", IPs: []net.IP{{77, 81, 142, 13}}}},
{Region: "Moldova", PortForward: true, OpenvpnUDP: models.PIAServerOpenvpn{CN: "chisinau401", IPs: []net.IP{{178, 175, 129, 44}}}, OpenvpnTCP: models.PIAServerOpenvpn{CN: "chisinau401", IPs: []net.IP{{178, 175, 129, 46}}}},
{Region: "Monaco", PortForward: true, OpenvpnUDP: models.PIAServerOpenvpn{CN: "monaco403", IPs: []net.IP{{95, 181, 233, 8}}}, OpenvpnTCP: models.PIAServerOpenvpn{CN: "monaco403", IPs: []net.IP{{95, 181, 233, 5}}}},
{Region: "Mongolia", PortForward: true, OpenvpnUDP: models.PIAServerOpenvpn{CN: "mongolia403", IPs: []net.IP{{185, 253, 163, 15}}}, OpenvpnTCP: models.PIAServerOpenvpn{CN: "mongolia403", IPs: []net.IP{{185, 253, 163, 5}}}},
{Region: "Montenegro", PortForward: true, OpenvpnUDP: models.PIAServerOpenvpn{CN: "montenegro403", IPs: []net.IP{{176, 125, 229, 14}}}, OpenvpnTCP: models.PIAServerOpenvpn{CN: "montenegro403", IPs: []net.IP{{176, 125, 229, 4}}}},
{Region: "Morocco", PortForward: true, OpenvpnUDP: models.PIAServerOpenvpn{CN: "morocco403", IPs: []net.IP{{95, 181, 232, 4}}}, OpenvpnTCP: models.PIAServerOpenvpn{CN: "morocco403", IPs: []net.IP{{95, 181, 232, 8}}}},
{Region: "Netherlands", PortForward: true, OpenvpnUDP: models.PIAServerOpenvpn{CN: "amsterdam412", IPs: []net.IP{{143, 244, 41, 196}}}, OpenvpnTCP: models.PIAServerOpenvpn{CN: "amsterdam412", IPs: []net.IP{{143, 244, 41, 196}}}},
{Region: "New Zealand", PortForward: true, OpenvpnUDP: models.PIAServerOpenvpn{CN: "newzealand403", IPs: []net.IP{{43, 250, 207, 90}}}, OpenvpnTCP: models.PIAServerOpenvpn{CN: "newzealand403", IPs: []net.IP{{43, 250, 207, 84}}}},
{Region: "Nigeria", PortForward: true, OpenvpnUDP: models.PIAServerOpenvpn{CN: "nigeria403", IPs: []net.IP{{102, 165, 25, 86}}}, OpenvpnTCP: models.PIAServerOpenvpn{CN: "nigeria403", IPs: []net.IP{{102, 165, 25, 85}}}},
{Region: "Norway", PortForward: true, OpenvpnUDP: models.PIAServerOpenvpn{CN: "oslo401", IPs: []net.IP{{46, 246, 122, 37}}}, OpenvpnTCP: models.PIAServerOpenvpn{CN: "oslo401", IPs: []net.IP{{46, 246, 122, 60}}}},
{Region: "Panama", PortForward: true, OpenvpnUDP: models.PIAServerOpenvpn{CN: "panama404", IPs: []net.IP{{91, 90, 126, 25}}}, OpenvpnTCP: models.PIAServerOpenvpn{CN: "panama404", IPs: []net.IP{{91, 90, 126, 28}}}},
{Region: "Philippines", PortForward: true, OpenvpnUDP: models.PIAServerOpenvpn{CN: "philippines401", IPs: []net.IP{{188, 214, 125, 140}}}, OpenvpnTCP: models.PIAServerOpenvpn{CN: "philippines401", IPs: []net.IP{{188, 214, 125, 137}}}},
{Region: "Poland", PortForward: true, OpenvpnUDP: models.PIAServerOpenvpn{CN: "warsaw409", IPs: []net.IP{{194, 110, 114, 119}}}, OpenvpnTCP: models.PIAServerOpenvpn{CN: "warsaw409", IPs: []net.IP{{194, 110, 114, 118}}}},
{Region: "Portugal", PortForward: true, OpenvpnUDP: models.PIAServerOpenvpn{CN: "lisbon402", IPs: []net.IP{{89, 26, 241, 87}}}, OpenvpnTCP: models.PIAServerOpenvpn{CN: "lisbon402", IPs: []net.IP{{89, 26, 241, 88}}}},
{Region: "Qatar", PortForward: true, OpenvpnUDP: models.PIAServerOpenvpn{CN: "qatar403", IPs: []net.IP{{95, 181, 234, 9}}}, OpenvpnTCP: models.PIAServerOpenvpn{CN: "qatar403", IPs: []net.IP{{95, 181, 234, 8}}}},
{Region: "Romania", PortForward: true, OpenvpnUDP: models.PIAServerOpenvpn{CN: "romania408", IPs: []net.IP{{143, 244, 54, 117}}}, OpenvpnTCP: models.PIAServerOpenvpn{CN: "romania408", IPs: []net.IP{{143, 244, 54, 116}}}},
{Region: "Saudi Arabia", PortForward: true, OpenvpnUDP: models.PIAServerOpenvpn{CN: "saudiarabia403", IPs: []net.IP{{95, 181, 235, 8}}}, OpenvpnTCP: models.PIAServerOpenvpn{CN: "saudiarabia403", IPs: []net.IP{{95, 181, 235, 4}}}},
{Region: "Serbia", PortForward: true, OpenvpnUDP: models.PIAServerOpenvpn{CN: "belgrade401", IPs: []net.IP{{37, 120, 193, 250}}}, OpenvpnTCP: models.PIAServerOpenvpn{CN: "belgrade401", IPs: []net.IP{{37, 120, 193, 249}}}},
{Region: "Singapore", PortForward: true, OpenvpnUDP: models.PIAServerOpenvpn{CN: "singapore401", IPs: []net.IP{{156, 146, 57, 213}}}, OpenvpnTCP: models.PIAServerOpenvpn{CN: "singapore401", IPs: []net.IP{{156, 146, 57, 197}}}},
{Region: "Slovakia", PortForward: true, OpenvpnUDP: models.PIAServerOpenvpn{CN: "bratislava402", IPs: []net.IP{{37, 120, 221, 213}}}, OpenvpnTCP: models.PIAServerOpenvpn{CN: "bratislava402", IPs: []net.IP{{37, 120, 221, 218}}}},
{Region: "South Africa", PortForward: true, OpenvpnUDP: models.PIAServerOpenvpn{CN: "johannesburg401", IPs: []net.IP{{154, 16, 93, 33}}}, OpenvpnTCP: models.PIAServerOpenvpn{CN: "johannesburg401", IPs: []net.IP{{154, 16, 93, 46}}}},
{Region: "Spain", PortForward: true, OpenvpnUDP: models.PIAServerOpenvpn{CN: "madrid401", IPs: []net.IP{{212, 102, 49, 68}}}, OpenvpnTCP: models.PIAServerOpenvpn{CN: "madrid401", IPs: []net.IP{{212, 102, 49, 68}}}},
{Region: "Sri Lanka", PortForward: true, OpenvpnUDP: models.PIAServerOpenvpn{CN: "srilanka403", IPs: []net.IP{{95, 181, 239, 8}}}, OpenvpnTCP: models.PIAServerOpenvpn{CN: "srilanka403", IPs: []net.IP{{95, 181, 239, 13}}}},
{Region: "Sweden", PortForward: true, OpenvpnUDP: models.PIAServerOpenvpn{CN: "stockholm401", IPs: []net.IP{{195, 246, 120, 4}}}, OpenvpnTCP: models.PIAServerOpenvpn{CN: "stockholm401", IPs: []net.IP{{195, 246, 120, 39}}}},
{Region: "Switzerland", PortForward: true, OpenvpnUDP: models.PIAServerOpenvpn{CN: "zurich407", IPs: []net.IP{{156, 146, 62, 194}}}, OpenvpnTCP: models.PIAServerOpenvpn{CN: "zurich407", IPs: []net.IP{{156, 146, 62, 194}}}},
{Region: "Taiwan", PortForward: true, OpenvpnUDP: models.PIAServerOpenvpn{CN: "taiwan401", IPs: []net.IP{{188, 214, 106, 74}}}, OpenvpnTCP: models.PIAServerOpenvpn{CN: "taiwan401", IPs: []net.IP{{188, 214, 106, 69}}}},
{Region: "Turkey", PortForward: true, OpenvpnUDP: models.PIAServerOpenvpn{CN: "istanbul402", IPs: []net.IP{{188, 213, 34, 88}}}, OpenvpnTCP: models.PIAServerOpenvpn{CN: "istanbul402", IPs: []net.IP{{188, 213, 34, 83}}}},
{Region: "UK London", PortForward: true, OpenvpnUDP: models.PIAServerOpenvpn{CN: "london405", IPs: []net.IP{{212, 102, 53, 15}}}, OpenvpnTCP: models.PIAServerOpenvpn{CN: "london405", IPs: []net.IP{{212, 102, 53, 60}}}},
{Region: "UK London-2", PortForward: true, OpenvpnUDP: models.PIAServerOpenvpn{CN: "london420", IPs: []net.IP{{37, 235, 96, 200}}}, OpenvpnTCP: models.PIAServerOpenvpn{CN: "london420", IPs: []net.IP{{37, 235, 96, 206}}}},
{Region: "UK Manchester", PortForward: true, OpenvpnUDP: models.PIAServerOpenvpn{CN: "manchester414", IPs: []net.IP{{194, 37, 96, 194}}}, OpenvpnTCP: models.PIAServerOpenvpn{CN: "manchester414", IPs: []net.IP{{194, 37, 96, 197}}}},
{Region: "UK Southampton", PortForward: true, OpenvpnUDP: models.PIAServerOpenvpn{CN: "southampton401", IPs: []net.IP{{143, 244, 37, 244}}}, OpenvpnTCP: models.PIAServerOpenvpn{CN: "southampton401", IPs: []net.IP{{143, 244, 37, 194}}}},
{Region: "US Atlanta", PortForward: false, OpenvpnUDP: models.PIAServerOpenvpn{CN: "atlanta417", IPs: []net.IP{{154, 21, 22, 216}}}, OpenvpnTCP: models.PIAServerOpenvpn{CN: "atlanta417", IPs: []net.IP{{154, 21, 22, 216}}}},
{Region: "US California", PortForward: false, OpenvpnUDP: models.PIAServerOpenvpn{CN: "losangeles401", IPs: []net.IP{{143, 244, 48, 15}}}, OpenvpnTCP: models.PIAServerOpenvpn{CN: "losangeles401", IPs: []net.IP{{143, 244, 48, 17}}}},
{Region: "US Chicago", PortForward: false, OpenvpnUDP: models.PIAServerOpenvpn{CN: "chicago413", IPs: []net.IP{{154, 21, 23, 125}}}, OpenvpnTCP: models.PIAServerOpenvpn{CN: "chicago413", IPs: []net.IP{{154, 21, 23, 137}}}},
{Region: "US Denver", PortForward: false, OpenvpnUDP: models.PIAServerOpenvpn{CN: "denver410", IPs: []net.IP{{174, 128, 227, 24}}}, OpenvpnTCP: models.PIAServerOpenvpn{CN: "denver410", IPs: []net.IP{{174, 128, 227, 24}}}},
{Region: "US East", PortForward: false, OpenvpnUDP: models.PIAServerOpenvpn{CN: "newjersey405", IPs: []net.IP{{143, 244, 46, 65}}}, OpenvpnTCP: models.PIAServerOpenvpn{CN: "newjersey405", IPs: []net.IP{{143, 244, 46, 115}}}},
{Region: "US Florida", PortForward: false, OpenvpnUDP: models.PIAServerOpenvpn{CN: "miami407", IPs: []net.IP{{156, 146, 42, 78}}}, OpenvpnTCP: models.PIAServerOpenvpn{CN: "miami407", IPs: []net.IP{{156, 146, 42, 113}}}},
{Region: "US Houston", PortForward: false, OpenvpnUDP: models.PIAServerOpenvpn{CN: "houston413", IPs: []net.IP{{205, 251, 142, 20}}}, OpenvpnTCP: models.PIAServerOpenvpn{CN: "houston413", IPs: []net.IP{{205, 251, 142, 20}}}},
{Region: "US Las Vegas", PortForward: false, OpenvpnUDP: models.PIAServerOpenvpn{CN: "lasvegas426", IPs: []net.IP{{196, 53, 64, 175}}}, OpenvpnTCP: models.PIAServerOpenvpn{CN: "lasvegas426", IPs: []net.IP{{196, 53, 64, 189}}}},
{Region: "US New York", PortForward: false, OpenvpnUDP: models.PIAServerOpenvpn{CN: "newyork404", IPs: []net.IP{{143, 244, 44, 227}}}, OpenvpnTCP: models.PIAServerOpenvpn{CN: "newyork404", IPs: []net.IP{{143, 244, 44, 208}}}},
{Region: "US Seattle", PortForward: false, OpenvpnUDP: models.PIAServerOpenvpn{CN: "seattle422", IPs: []net.IP{{156, 146, 48, 204}}}, OpenvpnTCP: models.PIAServerOpenvpn{CN: "seattle422", IPs: []net.IP{{156, 146, 48, 239}}}},
{Region: "US Silicon Valley", PortForward: false, OpenvpnUDP: models.PIAServerOpenvpn{CN: "siliconvalley420", IPs: []net.IP{{66, 115, 165, 93}}}, OpenvpnTCP: models.PIAServerOpenvpn{CN: "siliconvalley420", IPs: []net.IP{{66, 115, 165, 80}}}},
{Region: "US Texas", PortForward: false, OpenvpnUDP: models.PIAServerOpenvpn{CN: "dallas415", IPs: []net.IP{{154, 3, 250, 16}}}, OpenvpnTCP: models.PIAServerOpenvpn{CN: "dallas415", IPs: []net.IP{{154, 3, 250, 24}}}},
{Region: "US Washington DC", PortForward: false, OpenvpnUDP: models.PIAServerOpenvpn{CN: "washington452", IPs: []net.IP{{91, 149, 244, 110}}}, OpenvpnTCP: models.PIAServerOpenvpn{CN: "washington452", IPs: []net.IP{{91, 149, 244, 114}}}},
{Region: "US West", PortForward: false, OpenvpnUDP: models.PIAServerOpenvpn{CN: "phoenix413", IPs: []net.IP{{184, 170, 241, 169}}}, OpenvpnTCP: models.PIAServerOpenvpn{CN: "phoenix413", IPs: []net.IP{{184, 170, 241, 169}}}},
{Region: "Ukraine", PortForward: true, OpenvpnUDP: models.PIAServerOpenvpn{CN: "kiev403", IPs: []net.IP{{62, 149, 20, 7}}}, OpenvpnTCP: models.PIAServerOpenvpn{CN: "kiev403", IPs: []net.IP{{62, 149, 20, 4}}}},
{Region: "United Arab Emirates", PortForward: true, OpenvpnUDP: models.PIAServerOpenvpn{CN: "dubai403", IPs: []net.IP{{217, 138, 193, 153}}}, OpenvpnTCP: models.PIAServerOpenvpn{CN: "dubai403", IPs: []net.IP{{217, 138, 193, 150}}}},
{Region: "Venezuela", PortForward: true, OpenvpnUDP: models.PIAServerOpenvpn{CN: "venezuela403", IPs: []net.IP{{95, 181, 237, 3}}}, OpenvpnTCP: models.PIAServerOpenvpn{CN: "venezuela403", IPs: []net.IP{{95, 181, 237, 9}}}},
{Region: "Vietnam", PortForward: true, OpenvpnUDP: models.PIAServerOpenvpn{CN: "vietnam401", IPs: []net.IP{{188, 214, 152, 67}}}, OpenvpnTCP: models.PIAServerOpenvpn{CN: "vietnam401", IPs: []net.IP{{188, 214, 152, 67}}}},
}
}

View File

@@ -0,0 +1,205 @@
package constants
import (
"net"
"github.com/qdm12/gluetun/internal/models"
)
//nolint:lll
const (
PrivadoCertificate = "MIIFKDCCAxCgAwIBAgIJAMtrmqZxIV/OMA0GCSqGSIb3DQEBDQUAMBIxEDAOBgNVBAMMB1ByaXZhZG8wHhcNMjAwMTA4MjEyODQ1WhcNMzUwMTA5MjEyODQ1WjASMRAwDgYDVQQDDAdQcml2YWRvMIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAxPwOgiwNJzZTnKIXwAB0TSu/Lu2qt2U2I8obtQjwhi/7OrfmbmYykSdro70al2XPhnwAGGdCxW6LDnp0UN/IOhD11mgBPo14f5CLkBQjSJ6VN5miPbvK746LsNZl9H8rQGvDuPo4CG9BfPZMiDRGlsMxij/jztzgT1gmuxQ7WHfFRcNzBas1dHa9hV/d3TU6/t47x4SE/ljdcCtJiu7Zn6ODKQoys3mB7Luz2ngqUJWvkqsg+E4+3eJ0M8Hlbn5TPaRJBID7DAdYo6Vs6xGCYr981ThFcmoIQ10js10yANrrfGAzd03b3TnLAgko0uQMHjliMZL6L8sWOPHxyxJI0us88SFh4UgcFyRHKHPKux7w24SxAlZUYoUcTHp9VjG5XvDKYxzgV2RdM4ulBGbQRQ3y3/CyddsyQYMvA55Ets0LfPaBvDIcct70iXijGsdvlX1du3ArGpG7Vaje/RU4nbbGT6HYRdt5YyZfof288ukMOSj20nVcmS+c/4tqsxSerRb1aq5LOi1IemSkTMeC5gCbexk+L1vl7NT/58sxjGmu5bXwnvev/lIItfi2AlITrfUSEv19iDMKkeshwn/+sFJBMWYyluP+yJ56yR+MWoXvLlSWphLDTqq19yx3BZn0P1tgbXoR0g8PTdJFcz8z3RIb7myVLYulV1oGG/3rka0CAwEAAaOBgDB+MB0GA1UdDgQWBBTFtJkZCVDuDAD6k5bJzefjJdO3DTBCBgNVHSMEOzA5gBTFtJkZCVDuDAD6k5bJzefjJdO3DaEWpBQwEjEQMA4GA1UEAwwHUHJpdmFkb4IJAMtrmqZxIV/OMAwGA1UdEwQFMAMBAf8wCwYDVR0PBAQDAgEGMA0GCSqGSIb3DQEBDQUAA4ICAQB7MUSXMeBb9wlSv4sUaT1JHEwE26nlBw+TKmezfuPU5pBlY0LYr6qQZY95DHqsRJ7ByUzGUrGo17dNGXlcuNc6TAaQQEDRPo6y+LVh2TWMk15TUMI+MkqryJtCret7xGvDigKYMJgBy58HN3RAVr1B7cL9youwzLgc2Y/NcFKvnQJKeiIYAJ7g0CcnJiQvgZTS7xdwkEBXfsngmUCIG320DLPEL+Ze0HiUrxwWljMRya6i40AeH3Zu2i532xX1wV5+cjA4RJWIKg6ri/Q54iFGtZrA9/nc6y9uoQHkmz8cGyVUmJxFzMrrIICVqUtVRxLhkTMe4UzwRWTBeGgtW4tS0yq1QonAKfOyjgRw/CeY55D2UGvnAFZdTadtYXS4Alu2P9zdwoEk3fzHiVmDjqfJVr5wz9383aABUFrPI3nz6ed/Z6LZflKh1k+DUDEp8NxU4klUULWsSOKoa5zGX51G8cdHxwQLImXvtGuN5eSR8jCTgxFZhdps/xes4KkyfIz9FMYG748M+uOTgKITf4zdJ9BAyiQaOufVQZ8WjhWzWk9YHec9VqPkzpWNGkVjiRI5ewuXwZzZ164tMv2hikBXSuUCnFz37/ZNwGlDi0oBdDszCk2GxccdFHHaCSmpjU5MrdJ+5IhtTKGeTx+US2hTIVHQFIO99DmacxSYvLNcSQ=="
)
func PrivadoHostnameChoices() (choices []string) {
servers := PrivadoServers()
choices = make([]string, len(servers))
for i := range servers {
choices[i] = servers[i].Hostname
}
return choices
}
func PrivadoServers() []models.PrivadoServer {
return []models.PrivadoServer{
{Hostname: "akl-001.vpn.privado.io", IP: net.IP{23, 254, 104, 114}},
{Hostname: "akl-002.vpn.privado.io", IP: net.IP{23, 254, 104, 120}},
{Hostname: "akl-003.vpn.privado.io", IP: net.IP{23, 254, 104, 51}},
{Hostname: "ams-001.vpn.privado.io", IP: net.IP{91, 148, 224, 10}},
{Hostname: "ams-002.vpn.privado.io", IP: net.IP{91, 148, 224, 20}},
{Hostname: "ams-003.vpn.privado.io", IP: net.IP{91, 148, 224, 30}},
{Hostname: "ams-004.vpn.privado.io", IP: net.IP{91, 148, 224, 40}},
{Hostname: "ams-005.vpn.privado.io", IP: net.IP{91, 148, 224, 50}},
{Hostname: "ams-006.vpn.privado.io", IP: net.IP{91, 148, 224, 60}},
{Hostname: "ams-007.vpn.privado.io", IP: net.IP{91, 148, 224, 70}},
{Hostname: "ams-008.vpn.privado.io", IP: net.IP{91, 148, 224, 80}},
{Hostname: "ams-009.vpn.privado.io", IP: net.IP{91, 148, 228, 10}},
{Hostname: "ams-010.vpn.privado.io", IP: net.IP{91, 148, 228, 20}},
{Hostname: "ams-011.vpn.privado.io", IP: net.IP{91, 148, 228, 30}},
{Hostname: "ams-012.vpn.privado.io", IP: net.IP{91, 148, 228, 40}},
{Hostname: "ams-013.vpn.privado.io", IP: net.IP{91, 148, 228, 50}},
{Hostname: "ams-014.vpn.privado.io", IP: net.IP{91, 148, 228, 60}},
{Hostname: "ams-015.vpn.privado.io", IP: net.IP{91, 148, 228, 70}},
{Hostname: "ams-016.vpn.privado.io", IP: net.IP{91, 148, 228, 80}},
{Hostname: "arn-001.vpn.privado.io", IP: net.IP{86, 106, 103, 67}},
{Hostname: "arn-002.vpn.privado.io", IP: net.IP{86, 106, 103, 74}},
{Hostname: "arn-003.vpn.privado.io", IP: net.IP{86, 106, 103, 81}},
{Hostname: "ath-001.vpn.privado.io", IP: net.IP{188, 123, 126, 61}},
{Hostname: "ath-002.vpn.privado.io", IP: net.IP{188, 123, 126, 64}},
{Hostname: "ath-003.vpn.privado.io", IP: net.IP{188, 123, 126, 68}},
{Hostname: "ath-004.vpn.privado.io", IP: net.IP{188, 123, 126, 72}},
{Hostname: "beg-001.vpn.privado.io", IP: net.IP{89, 38, 224, 19}},
{Hostname: "beg-002.vpn.privado.io", IP: net.IP{89, 38, 224, 25}},
{Hostname: "bkk-001.vpn.privado.io", IP: net.IP{119, 59, 111, 3}},
{Hostname: "bkk-002.vpn.privado.io", IP: net.IP{119, 59, 111, 11}},
{Hostname: "bom-001.vpn.privado.io", IP: net.IP{103, 26, 204, 61}},
{Hostname: "bom-002.vpn.privado.io", IP: net.IP{103, 26, 204, 70}},
{Hostname: "bru-001.vpn.privado.io", IP: net.IP{217, 138, 211, 163}},
{Hostname: "bru-002.vpn.privado.io", IP: net.IP{217, 138, 211, 170}},
{Hostname: "bru-003.vpn.privado.io", IP: net.IP{217, 138, 211, 177}},
{Hostname: "bru-004.vpn.privado.io", IP: net.IP{217, 138, 211, 184}},
{Hostname: "bts-001.vpn.privado.io", IP: net.IP{37, 120, 221, 227}},
{Hostname: "bts-002.vpn.privado.io", IP: net.IP{37, 120, 221, 233}},
{Hostname: "bud-001.vpn.privado.io", IP: net.IP{185, 128, 26, 194}},
{Hostname: "bud-002.vpn.privado.io", IP: net.IP{185, 128, 26, 200}},
{Hostname: "cdg-001.vpn.privado.io", IP: net.IP{89, 40, 183, 99}},
{Hostname: "cdg-002.vpn.privado.io", IP: net.IP{89, 40, 183, 106}},
{Hostname: "cdg-003.vpn.privado.io", IP: net.IP{89, 40, 183, 113}},
{Hostname: "cdg-004.vpn.privado.io", IP: net.IP{89, 40, 183, 120}},
{Hostname: "cph-001.vpn.privado.io", IP: net.IP{2, 58, 46, 35}},
{Hostname: "cph-002.vpn.privado.io", IP: net.IP{2, 58, 46, 42}},
{Hostname: "cph-003.vpn.privado.io", IP: net.IP{2, 58, 46, 49}},
{Hostname: "cph-004.vpn.privado.io", IP: net.IP{2, 58, 46, 56}},
{Hostname: "dca-001.vpn.privado.io", IP: net.IP{85, 12, 61, 10}},
{Hostname: "dca-002.vpn.privado.io", IP: net.IP{85, 12, 61, 20}},
{Hostname: "dca-003.vpn.privado.io", IP: net.IP{85, 12, 61, 30}},
{Hostname: "dca-004.vpn.privado.io", IP: net.IP{85, 12, 61, 40}},
{Hostname: "dca-005.vpn.privado.io", IP: net.IP{85, 12, 61, 50}},
{Hostname: "dca-006.vpn.privado.io", IP: net.IP{85, 12, 61, 60}},
{Hostname: "dca-007.vpn.privado.io", IP: net.IP{85, 12, 61, 70}},
{Hostname: "dca-008.vpn.privado.io", IP: net.IP{85, 12, 61, 80}},
{Hostname: "dca-013.vpn.privado.io", IP: net.IP{185, 247, 68, 3}},
{Hostname: "dca-014.vpn.privado.io", IP: net.IP{185, 247, 68, 10}},
{Hostname: "dca-015.vpn.privado.io", IP: net.IP{185, 247, 68, 17}},
{Hostname: "dca-016.vpn.privado.io", IP: net.IP{185, 247, 68, 24}},
{Hostname: "dfw-001.vpn.privado.io", IP: net.IP{23, 105, 32, 243}},
{Hostname: "dfw-002.vpn.privado.io", IP: net.IP{23, 105, 32, 244}},
{Hostname: "dub-001.vpn.privado.io", IP: net.IP{84, 247, 48, 227}},
{Hostname: "dub-002.vpn.privado.io", IP: net.IP{84, 247, 48, 234}},
{Hostname: "dub-003.vpn.privado.io", IP: net.IP{84, 247, 48, 241}},
{Hostname: "dub-004.vpn.privado.io", IP: net.IP{84, 247, 48, 248}},
{Hostname: "eze-001.vpn.privado.io", IP: net.IP{168, 205, 93, 211}},
{Hostname: "eze-002.vpn.privado.io", IP: net.IP{168, 205, 93, 217}},
{Hostname: "fra-001.vpn.privado.io", IP: net.IP{91, 148, 232, 10}},
{Hostname: "fra-002.vpn.privado.io", IP: net.IP{91, 148, 232, 20}},
{Hostname: "fra-003.vpn.privado.io", IP: net.IP{91, 148, 232, 30}},
{Hostname: "fra-004.vpn.privado.io", IP: net.IP{91, 148, 232, 40}},
{Hostname: "fra-005.vpn.privado.io", IP: net.IP{91, 148, 233, 7}},
{Hostname: "fra-006.vpn.privado.io", IP: net.IP{91, 148, 233, 8}},
{Hostname: "fra-007.vpn.privado.io", IP: net.IP{91, 148, 233, 9}},
{Hostname: "fra-008.vpn.privado.io", IP: net.IP{91, 148, 233, 10}},
{Hostname: "gru-001.vpn.privado.io", IP: net.IP{177, 54, 145, 193}},
{Hostname: "gru-002.vpn.privado.io", IP: net.IP{177, 54, 145, 197}},
{Hostname: "hel-001.vpn.privado.io", IP: net.IP{194, 34, 134, 219}},
{Hostname: "hel-002.vpn.privado.io", IP: net.IP{194, 34, 134, 227}},
{Hostname: "hkg-001.vpn.privado.io", IP: net.IP{209, 58, 185, 88}},
{Hostname: "hkg-002.vpn.privado.io", IP: net.IP{209, 58, 185, 97}},
{Hostname: "hkg-003.vpn.privado.io", IP: net.IP{209, 58, 185, 108}},
{Hostname: "hkg-004.vpn.privado.io", IP: net.IP{209, 58, 185, 120}},
{Hostname: "icn-001.vpn.privado.io", IP: net.IP{169, 56, 73, 146}},
{Hostname: "icn-002.vpn.privado.io", IP: net.IP{169, 56, 73, 153}},
{Hostname: "iev-001.vpn.privado.io", IP: net.IP{176, 103, 52, 40}},
{Hostname: "iev-002.vpn.privado.io", IP: net.IP{176, 103, 53, 40}},
{Hostname: "ist-001.vpn.privado.io", IP: net.IP{185, 84, 183, 3}},
{Hostname: "ist-002.vpn.privado.io", IP: net.IP{185, 84, 183, 4}},
{Hostname: "jfk-001.vpn.privado.io", IP: net.IP{217, 138, 208, 99}},
{Hostname: "jfk-002.vpn.privado.io", IP: net.IP{217, 138, 208, 106}},
{Hostname: "jfk-003.vpn.privado.io", IP: net.IP{217, 138, 208, 113}},
{Hostname: "jfk-004.vpn.privado.io", IP: net.IP{217, 138, 208, 120}},
{Hostname: "jnb-001.vpn.privado.io", IP: net.IP{172, 107, 93, 131}},
{Hostname: "jnb-002.vpn.privado.io", IP: net.IP{172, 107, 93, 137}},
{Hostname: "lax-009.vpn.privado.io", IP: net.IP{45, 152, 182, 227}},
{Hostname: "lax-010.vpn.privado.io", IP: net.IP{45, 152, 182, 234}},
{Hostname: "lax-011.vpn.privado.io", IP: net.IP{45, 152, 182, 241}},
{Hostname: "lax-012.vpn.privado.io", IP: net.IP{45, 152, 182, 248}},
{Hostname: "lis-001.vpn.privado.io", IP: net.IP{89, 26, 243, 153}},
{Hostname: "lis-002.vpn.privado.io", IP: net.IP{89, 26, 243, 154}},
{Hostname: "lon-001.vpn.privado.io", IP: net.IP{217, 138, 195, 163}},
{Hostname: "lon-002.vpn.privado.io", IP: net.IP{217, 138, 195, 170}},
{Hostname: "lon-003.vpn.privado.io", IP: net.IP{217, 138, 195, 177}},
{Hostname: "lon-004.vpn.privado.io", IP: net.IP{217, 138, 195, 184}},
{Hostname: "mad-001.vpn.privado.io", IP: net.IP{217, 138, 218, 131}},
{Hostname: "man-001.vpn.privado.io", IP: net.IP{217, 138, 196, 131}},
{Hostname: "man-002.vpn.privado.io", IP: net.IP{217, 138, 196, 138}},
{Hostname: "man-003.vpn.privado.io", IP: net.IP{217, 138, 196, 145}},
{Hostname: "man-004.vpn.privado.io", IP: net.IP{217, 138, 196, 152}},
{Hostname: "mex-001.vpn.privado.io", IP: net.IP{169, 57, 96, 52}},
{Hostname: "mex-002.vpn.privado.io", IP: net.IP{169, 57, 96, 57}},
{Hostname: "mia-001.vpn.privado.io", IP: net.IP{86, 106, 87, 131}},
{Hostname: "mia-002.vpn.privado.io", IP: net.IP{86, 106, 87, 138}},
{Hostname: "mia-003.vpn.privado.io", IP: net.IP{86, 106, 87, 145}},
{Hostname: "mia-004.vpn.privado.io", IP: net.IP{86, 106, 87, 152}},
{Hostname: "mxp-001.vpn.privado.io", IP: net.IP{89, 40, 182, 195}},
{Hostname: "mxp-002.vpn.privado.io", IP: net.IP{89, 40, 182, 201}},
{Hostname: "nrt-001.vpn.privado.io", IP: net.IP{217, 138, 252, 3}},
{Hostname: "nrt-002.vpn.privado.io", IP: net.IP{217, 138, 252, 10}},
{Hostname: "nrt-003.vpn.privado.io", IP: net.IP{217, 138, 252, 17}},
{Hostname: "nrt-004.vpn.privado.io", IP: net.IP{217, 138, 252, 24}},
{Hostname: "ord-001.vpn.privado.io", IP: net.IP{23, 108, 95, 129}},
{Hostname: "ord-002.vpn.privado.io", IP: net.IP{23, 108, 95, 167}},
{Hostname: "osl-001.vpn.privado.io", IP: net.IP{84, 247, 50, 115}},
{Hostname: "osl-002.vpn.privado.io", IP: net.IP{84, 247, 50, 119}},
{Hostname: "osl-003.vpn.privado.io", IP: net.IP{84, 247, 50, 123}},
{Hostname: "otp-001.vpn.privado.io", IP: net.IP{89, 46, 102, 179}},
{Hostname: "otp-002.vpn.privado.io", IP: net.IP{89, 46, 102, 185}},
{Hostname: "phx-001.vpn.privado.io", IP: net.IP{91, 148, 236, 10}},
{Hostname: "phx-002.vpn.privado.io", IP: net.IP{91, 148, 236, 20}},
{Hostname: "phx-003.vpn.privado.io", IP: net.IP{91, 148, 236, 30}},
{Hostname: "phx-004.vpn.privado.io", IP: net.IP{91, 148, 236, 40}},
{Hostname: "phx-005.vpn.privado.io", IP: net.IP{91, 148, 236, 50}},
{Hostname: "phx-006.vpn.privado.io", IP: net.IP{91, 148, 236, 60}},
{Hostname: "phx-007.vpn.privado.io", IP: net.IP{91, 148, 236, 70}},
{Hostname: "phx-008.vpn.privado.io", IP: net.IP{91, 148, 236, 80}},
{Hostname: "prg-001.vpn.privado.io", IP: net.IP{185, 216, 35, 99}},
{Hostname: "prg-002.vpn.privado.io", IP: net.IP{185, 216, 35, 105}},
{Hostname: "rix-001.vpn.privado.io", IP: net.IP{109, 248, 149, 35}},
{Hostname: "rix-002.vpn.privado.io", IP: net.IP{109, 248, 149, 40}},
{Hostname: "rkv-001.vpn.privado.io", IP: net.IP{82, 221, 131, 78}},
{Hostname: "rkv-002.vpn.privado.io", IP: net.IP{82, 221, 131, 127}},
{Hostname: "sea-001.vpn.privado.io", IP: net.IP{23, 81, 208, 96}},
{Hostname: "sea-002.vpn.privado.io", IP: net.IP{23, 81, 208, 104}},
{Hostname: "sin-001.vpn.privado.io", IP: net.IP{92, 119, 178, 131}},
{Hostname: "sin-002.vpn.privado.io", IP: net.IP{92, 119, 178, 138}},
{Hostname: "sin-003.vpn.privado.io", IP: net.IP{92, 119, 178, 145}},
{Hostname: "sin-004.vpn.privado.io", IP: net.IP{92, 119, 178, 152}},
{Hostname: "sof-001.vpn.privado.io", IP: net.IP{217, 138, 221, 163}},
{Hostname: "sof-002.vpn.privado.io", IP: net.IP{217, 138, 221, 169}},
{Hostname: "stl-001.vpn.privado.io", IP: net.IP{148, 72, 170, 145}},
{Hostname: "stl-002.vpn.privado.io", IP: net.IP{148, 72, 172, 82}},
{Hostname: "syd-001.vpn.privado.io", IP: net.IP{93, 115, 35, 35}},
{Hostname: "syd-002.vpn.privado.io", IP: net.IP{93, 115, 35, 42}},
{Hostname: "syd-003.vpn.privado.io", IP: net.IP{93, 115, 35, 49}},
{Hostname: "syd-004.vpn.privado.io", IP: net.IP{93, 115, 35, 56}},
{Hostname: "vie-001.vpn.privado.io", IP: net.IP{5, 253, 207, 227}},
{Hostname: "vie-002.vpn.privado.io", IP: net.IP{5, 253, 207, 234}},
{Hostname: "vie-003.vpn.privado.io", IP: net.IP{5, 253, 207, 241}},
{Hostname: "vie-004.vpn.privado.io", IP: net.IP{5, 253, 207, 248}},
{Hostname: "vno-001.vpn.privado.io", IP: net.IP{185, 64, 104, 176}},
{Hostname: "vno-002.vpn.privado.io", IP: net.IP{185, 64, 104, 180}},
{Hostname: "waw-001.vpn.privado.io", IP: net.IP{217, 138, 209, 163}},
{Hostname: "waw-002.vpn.privado.io", IP: net.IP{217, 138, 209, 164}},
{Hostname: "waw-003.vpn.privado.io", IP: net.IP{217, 138, 209, 165}},
{Hostname: "waw-004.vpn.privado.io", IP: net.IP{217, 138, 209, 166}},
{Hostname: "yul-001.vpn.privado.io", IP: net.IP{217, 138, 213, 67}},
{Hostname: "yul-002.vpn.privado.io", IP: net.IP{217, 138, 213, 74}},
{Hostname: "yul-003.vpn.privado.io", IP: net.IP{217, 138, 213, 81}},
{Hostname: "yul-004.vpn.privado.io", IP: net.IP{217, 138, 213, 88}},
{Hostname: "yvr-001.vpn.privado.io", IP: net.IP{71, 19, 248, 57}},
{Hostname: "yvr-002.vpn.privado.io", IP: net.IP{71, 19, 248, 113}},
{Hostname: "yyz-003.vpn.privado.io", IP: net.IP{199, 189, 27, 19}},
{Hostname: "zrh-001.vpn.privado.io", IP: net.IP{185, 156, 175, 195}},
{Hostname: "zrh-002.vpn.privado.io", IP: net.IP{185, 156, 175, 202}},
{Hostname: "zrh-003.vpn.privado.io", IP: net.IP{185, 156, 175, 209}},
{Hostname: "zrh-004.vpn.privado.io", IP: net.IP{185, 156, 175, 216}},
}
}

View File

@@ -0,0 +1,89 @@
package constants
import (
"net"
"github.com/qdm12/gluetun/internal/models"
)
//nolint:lll
const (
PurevpnCertificateAuthority = "MIIE6DCCA9CgAwIBAgIJAMjXFoeo5uSlMA0GCSqGSIb3DQEBCwUAMIGoMQswCQYDVQQGEwJISzEQMA4GA1UECBMHQ2VudHJhbDELMAkGA1UEBxMCSEsxGDAWBgNVBAoTD1NlY3VyZS1TZXJ2ZXJDQTELMAkGA1UECxMCSVQxGDAWBgNVBAMTD1NlY3VyZS1TZXJ2ZXJDQTEYMBYGA1UEKRMPU2VjdXJlLVNlcnZlckNBMR8wHQYJKoZIhvcNAQkBFhBtYWlsQGhvc3QuZG9tYWluMB4XDTE2MDExNTE1MzQwOVoXDTI2MDExMjE1MzQwOVowgagxCzAJBgNVBAYTAkhLMRAwDgYDVQQIEwdDZW50cmFsMQswCQYDVQQHEwJISzEYMBYGA1UEChMPU2VjdXJlLVNlcnZlckNBMQswCQYDVQQLEwJJVDEYMBYGA1UEAxMPU2VjdXJlLVNlcnZlckNBMRgwFgYDVQQpEw9TZWN1cmUtU2VydmVyQ0ExHzAdBgkqhkiG9w0BCQEWEG1haWxAaG9zdC5kb21haW4wggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDluufhyLlyvXzPUL16kAWAdivl1roQv3QHbuRshyKacf/1Er1JqEbtW3Mx9Fvr/u27qU2W8lQI6DaJhU2BfijPe/KHkib55mvHzIVvoexxya26nk79F2c+d9PnuuMdThWQO3El5a/i2AASnM7T7piIBT2WRZW2i8RbfJaTT7G7LP7OpMKIV1qyBg/cWoO7cIWQW4jmzqrNryIkF0AzStLN1DxvnQZwgXBGv0CwuAkfQuNSLu0PQgPp0PhdukNZFllv5D29IhPr0Z+kwPtrAgPQo+lHlOBHBMUpDT4XChTPeAvMaUSBsqmonAE8UUHEabWrqYN/kWNHCNkYXMkiVmK1AgMBAAGjggERMIIBDTAdBgNVHQ4EFgQU456ijsFrYnzHBShLAPpOUqQ+Z2cwgd0GA1UdIwSB1TCB0oAU456ijsFrYnzHBShLAPpOUqQ+Z2ehga6kgaswgagxCzAJBgNVBAYTAkhLMRAwDgYDVQQIEwdDZW50cmFsMQswCQYDVQQHEwJISzEYMBYGA1UEChMPU2VjdXJlLVNlcnZlckNBMQswCQYDVQQLEwJJVDEYMBYGA1UEAxMPU2VjdXJlLVNlcnZlckNBMRgwFgYDVQQpEw9TZWN1cmUtU2VydmVyQ0ExHzAdBgkqhkiG9w0BCQEWEG1haWxAaG9zdC5kb21haW6CCQDI1xaHqObkpTAMBgNVHRMEBTADAQH/MA0GCSqGSIb3DQEBCwUAA4IBAQCvga2HMwOtUxWH/inL2qk24KX2pxLg939JNhqoyNrUpbDHag5xPQYXUmUpKrNJZ0z+o/ZnNUPHydTSXE7Z7E45J0GDN5E7g4pakndKnDLSjp03NgGsCGW+cXnz6UBPM5FStFvGdDeModeSUyoS9fjk+mYROvmiy5EiVDP91sKGcPLR7Ym0M7zl2aaqV7bb98HmMoBOxpeZQinof67nKrCsgz/xjktWFgcmPl4/PQSsmqQD0fTtWxGuRX+FzwvF2OCMCAJgp1RqJNlk2g50/kBIoJVPPCfjDFeDU5zGaWGSQ9+z1L6/z7VXdjUiHL0ouOcHwbiS4ZjTr9nMn6WdAHU2"
PurevpnCertificate = "MIIEnzCCA4egAwIBAgIBAzANBgkqhkiG9w0BAQsFADCBqDELMAkGA1UEBhMCSEsxEDAOBgNVBAgTB0NlbnRyYWwxCzAJBgNVBAcTAkhLMRgwFgYDVQQKEw9TZWN1cmUtU2VydmVyQ0ExCzAJBgNVBAsTAklUMRgwFgYDVQQDEw9TZWN1cmUtU2VydmVyQ0ExGDAWBgNVBCkTD1NlY3VyZS1TZXJ2ZXJDQTEfMB0GCSqGSIb3DQEJARYQbWFpbEBob3N0LmRvbWFpbjAeFw0xNjAxMTUxNjE1MzhaFw0yNjAxMTIxNjE1MzhaMIGdMQswCQYDVQQGEwJISzEQMA4GA1UECBMHQ2VudHJhbDELMAkGA1UEBxMCSEsxFjAUBgNVBAoTDVNlY3VyZS1DbGllbnQxCzAJBgNVBAsTAklUMRYwFAYDVQQDEw1TZWN1cmUtQ2xpZW50MREwDwYDVQQpEwhjaGFuZ2VtZTEfMB0GCSqGSIb3DQEJARYQbWFpbEBob3N0LmRvbWFpbjCBnzANBgkqhkiG9w0BAQEFAAOBjQAwgYkCgYEAxsnyn4v6xxDPnuDaYS0b9M1N8nxgg7OBPBlK+FWRxdTQ8yxt5U5CZGm7riVp7fya2J2iPZIgmHQEv/KbxztsHAVlYSfYYlalrnhEL3bDP2tY+N43AwB1k5BrPq2s1pPLT2XG951drDKG4PUuFHUP1sHzW5oQlfVCmxgIMAP8OYkCAwEAAaOCAV8wggFbMAkGA1UdEwQCMAAwLQYJYIZIAYb4QgENBCAWHkVhc3ktUlNBIEdlbmVyYXRlZCBDZXJ0aWZpY2F0ZTAdBgNVHQ4EFgQU9MwUnUDbQKKZKjoeieD2OD5NlAEwgd0GA1UdIwSB1TCB0oAU456ijsFrYnzHBShLAPpOUqQ+Z2ehga6kgaswgagxCzAJBgNVBAYTAkhLMRAwDgYDVQQIEwdDZW50cmFsMQswCQYDVQQHEwJISzEYMBYGA1UEChMPU2VjdXJlLVNlcnZlckNBMQswCQYDVQQLEwJJVDEYMBYGA1UEAxMPU2VjdXJlLVNlcnZlckNBMRgwFgYDVQQpEw9TZWN1cmUtU2VydmVyQ0ExHzAdBgkqhkiG9w0BCQEWEG1haWxAaG9zdC5kb21haW6CCQDI1xaHqObkpTATBgNVHSUEDDAKBggrBgEFBQcDAjALBgNVHQ8EBAMCB4AwDQYJKoZIhvcNAQELBQADggEBAFyFo2VUX/UFixsdPdK9/Yt6mkCWc+XS1xbapGXXb9U1d+h1iBCIV9odUHgNCXWpz1hR5Uu/OCzaZ0asLE4IFMZlQmJs8sMT0c1tfPPGW45vxbL0lhqnQ8PNcBH7huNK7VFjUh4szXRKmaQPaM4S91R3L4CaNfVeHfAg7mN2m9Zn5Gto1Q1/CFMGKu2hxwGEw5p+X1czBWEvg/O09ckx/ggkkI1NcZsNiYQ+6Pz8DdGGX3+05YwLZu94+O6iIMrzxl/il0eK83g3YPbsOrASARvw6w/8sOnJCK5eOacl21oww875KisnYdWjHB1FiI+VzQ1/gyoDsL5kPTJVuu2CoG8="
PurevpnKey = "MIICdgIBADANBgkqhkiG9w0BAQEFAASCAmAwggJcAgEAAoGBAMbJ8p+L+scQz57g2mEtG/TNTfJ8YIOzgTwZSvhVkcXU0PMsbeVOQmRpu64lae38mtidoj2SIJh0BL/ym8c7bBwFZWEn2GJWpa54RC92wz9rWPjeNwMAdZOQaz6trNaTy09lxvedXawyhuD1LhR1D9bB81uaEJX1QpsYCDAD/DmJAgMBAAECgYEAvTHbDupE5U0krUvHzBEIuHblptGlcfNYHoDcD3oxYR3pOGeiuElBexv+mgHVzcFLBrsQfJUlHLPfCWi3xmjRvDQcr7N7U1u7NIzazy/PpRBaKolMRiM1KMYi2DG0i4ZONwFT8bvNHOIrZzCLY54KDrqOn55OzC70WYjWh4t5evkCQQDkkzZUAeskBC9+JP/zLps8jhwfoLBWGw/zbC9ePDmX0N8MTZdcUpg6KUTf1wbkLUyVtIRjS2ao6qu1jWG6K0x3AkEA3qPWyaWQWCynhNDqu2U1cPb2kh5AJip+gqxO3emikAdajsSxeoyEC2AfyBITbeB1tvCUZH17J4i/0+OFTEQp/wJAb/zEOGJ8PzghwK8GC7JA8mk51DEZVAaMSRovFv9wxDXcoh191AjPdmdzzCuAv9iF1i8MUc3GbWoUWK39PIYsPwJAWh63sqfx5b8tj/WBDpnJKBDPfhYAoXJSA1L8GZeY1fQkE+ZKcPCwAmrGcpXeh3t0Krj3WDXyw+32uC5Apr5wwQJAPZwOOReaC4YNfBPZN9BdHvVjOYGGUffpI+X+hWpLRnQFJteAi+eqwyk0Oi0SkJB+a7jcerK2d7q7xhec5WHlng=="
PurevpnOpenvpnStaticKeyV1 = "e30af995f56d07426d9ba1f824730521d4283db4b4d0cdda9c6e8759a3799dcb7939b6a5989160c9660de0f6125cbb1f585b41c074b2fe88ecfcf17eab9a33be1352379cdf74952b588fb161a93e13df9135b2b29038231e02d657a6225705e6868ccb0c384ed11614690a1894bfbeb274cebf1fe9c2329bdd5c8a40fe8820624d2ea7540cd79ab76892db51fc371a3ac5fc9573afecb3fffe3281e61d72e91579d9b03d8cbf7909b3aebf4d90850321ee6b7d0a7846d15c27d8290e031e951e19438a4654663cad975e138f5bc5af89c737ad822f27e19057731f41e1e254cc9c95b7175c622422cde9f1f2cfd3510add94498b4d7133d3729dd214a16b27fb"
)
func PurevpnRegionChoices() (choices []string) {
servers := PurevpnServers()
choices = make([]string, len(servers))
for i := range servers {
choices[i] = servers[i].Region
}
return choices
}
func PurevpnCountryChoices() (choices []string) {
servers := PurevpnServers()
choices = make([]string, len(servers))
for i := range servers {
choices[i] = servers[i].Country
}
return choices
}
func PurevpnCityChoices() (choices []string) {
servers := PurevpnServers()
choices = make([]string, len(servers))
for i := range servers {
choices[i] = servers[i].City
}
return choices
}
//nolint:lll
func PurevpnServers() []models.PurevpnServer {
return []models.PurevpnServer{
{Country: "Australia", Region: "New South Wales", City: "Sydney", IPs: []net.IP{{192, 253, 241, 4}, {43, 245, 161, 85}}},
{Country: "Australia", Region: "Western Australia", City: "Perth", IPs: []net.IP{{172, 94, 123, 4}}},
{Country: "Austria", Region: "Lower Austria", City: "Langenzersdorf", IPs: []net.IP{{172, 94, 109, 4}}},
{Country: "Austria", Region: "Vienna", City: "Vienna", IPs: []net.IP{{217, 64, 127, 252}}},
{Country: "Belgium", Region: "Flanders", City: "Zaventem", IPs: []net.IP{{172, 111, 223, 4}}},
{Country: "Bulgaria", Region: "Sofia-Capital", City: "Sofia", IPs: []net.IP{{217, 138, 221, 120}}},
{Country: "Canada", Region: "Alberta", City: "Calgary", IPs: []net.IP{{172, 94, 34, 4}}},
{Country: "Canada", Region: "Ontario", City: "Toronto", IPs: []net.IP{{104, 200, 138, 196}}},
{Country: "France", Region: "Île-de-France", City: "Paris", IPs: []net.IP{{89, 40, 183, 178}}},
{Country: "Germany", Region: "Hesse", City: "Frankfurt am Main", IPs: []net.IP{{188, 72, 84, 4}}},
{Country: "Greece", Region: "Central Macedonia", City: "Thessaloníki", IPs: []net.IP{{178, 21, 169, 244}}},
{Country: "Hong Kong", Region: "Central and Western", City: "Central", IPs: []net.IP{{141, 101, 168, 4}, {141, 101, 168, 4}}},
{Country: "Hong Kong", Region: "Central and Western", City: "Hong Kong", IPs: []net.IP{{43, 226, 231, 6}, {172, 111, 168, 4}}},
{Country: "India", Region: "Tamil Nadu", City: "Chennai", IPs: []net.IP{{129, 227, 107, 242}}},
{Country: "Italy", Region: "Trentino-Alto Adige", City: "Trento", IPs: []net.IP{{172, 111, 173, 3}}},
{Country: "Japan", Region: "Ōsaka", City: "Osaka", IPs: []net.IP{{172, 94, 56, 4}}},
{Country: "Malaysia", Region: "Kuala Lumpur", City: "Kuala Lumpur", IPs: []net.IP{{103, 55, 10, 133}}},
{Country: "Netherlands", Region: "North Holland", City: "Amsterdam", IPs: []net.IP{{5, 254, 73, 172}}},
{Country: "Norway", Region: "Oslo", City: "Oslo", IPs: []net.IP{{82, 102, 22, 212}}},
{Country: "Panama", Region: "Panamá", City: "Panamá", IPs: []net.IP{{104, 243, 243, 131}}},
{Country: "Philippines", Region: "Metro Manila", City: "Quezon City", IPs: []net.IP{{129, 227, 119, 84}}},
{Country: "Poland", Region: "Mazovia", City: "Warsaw", IPs: []net.IP{{5, 253, 206, 251}}},
{Country: "Portugal", Region: "Lisbon", City: "Lisbon", IPs: []net.IP{{5, 154, 174, 3}}},
{Country: "Russian Federation", Region: "Moscow", City: "Moscow", IPs: []net.IP{{46, 243, 220, 2}, {206, 123, 139, 4}}},
{Country: "Singapore", Region: "Singapore", City: "Singapore", IPs: []net.IP{{37, 120, 208, 147}}},
{Country: "South Africa", Region: "Gauteng", City: "Johannesburg", IPs: []net.IP{{102, 165, 3, 33}}},
{Country: "Spain", Region: "Madrid", City: "Madrid", IPs: []net.IP{{217, 138, 218, 210}}},
{Country: "Sweden", Region: "Stockholm", City: "Stockholm", IPs: []net.IP{{86, 106, 103, 139}}},
{Country: "Switzerland", Region: "Zurich", City: "Zürich", IPs: []net.IP{{45, 12, 222, 103}}},
{Country: "Taiwan", Region: "Taiwan", City: "Taipei", IPs: []net.IP{{128, 1, 155, 178}}},
{Country: "United Arab Emirates", Region: "Dubai", City: "Dubai", IPs: []net.IP{{104, 37, 6, 4}}},
{Country: "United Kingdom", Region: "England", City: "Birmingham", IPs: []net.IP{{188, 72, 89, 4}}},
{Country: "United Kingdom", Region: "England", City: "London", IPs: []net.IP{{45, 141, 154, 71}, {45, 141, 154, 71}}},
{Country: "United States", Region: "California", City: "Los Angeles", IPs: []net.IP{{172, 111, 147, 4}}},
{Country: "United States", Region: "California", City: "South San Francisco", IPs: []net.IP{{141, 101, 166, 4}, {141, 101, 166, 4}, {141, 101, 166, 4}}},
{Country: "United States", Region: "Florida", City: "Miami", IPs: []net.IP{{5, 254, 79, 115}}},
{Country: "United States", Region: "Massachusetts", City: "Newton", IPs: []net.IP{{104, 243, 244, 2}}},
{Country: "United States", Region: "Michigan", City: "Ypsilanti", IPs: []net.IP{{172, 111, 149, 4}}},
{Country: "United States", Region: "Texas", City: "Dallas", IPs: []net.IP{{208, 84, 155, 100}}},
{Country: "United States", Region: "Utah", City: "Salt Lake City", IPs: []net.IP{{45, 74, 52, 4}, {45, 74, 52, 4}, {45, 74, 52, 4}, {45, 74, 52, 4}, {45, 74, 52, 4}, {45, 74, 52, 4}}},
{Country: "Vietnam", Region: "Hanoi", City: "Cầu Giấy", IPs: []net.IP{{192, 253, 249, 132}}},
}
}

View File

@@ -0,0 +1,55 @@
package constants
import "github.com/qdm12/gluetun/internal/models"
func GetAllServers() (allServers models.AllServers) {
//nolint:gomnd
return models.AllServers{
Version: 1, // used for migration of the top level scheme
Cyberghost: models.CyberghostServers{
Version: 1, // model version
Timestamp: 1599323261, // latest takes precedence
Servers: CyberghostServers(),
},
Mullvad: models.MullvadServers{
Version: 1,
Timestamp: 1603660367,
Servers: MullvadServers(),
},
Nordvpn: models.NordvpnServers{
Version: 1,
Timestamp: 1599323261,
Servers: NordvpnServers(),
},
Pia: models.PiaServers{
Version: 2,
Timestamp: 1609343591,
Servers: PIAServers(),
},
Purevpn: models.PurevpnServers{
Version: 1,
Timestamp: 1609448478,
Servers: PurevpnServers(),
},
Privado: models.PrivadoServers{
Version: 2,
Timestamp: 1604963273,
Servers: PrivadoServers(),
},
Surfshark: models.SurfsharkServers{
Version: 1,
Timestamp: 1599957644,
Servers: SurfsharkServers(),
},
Vyprvpn: models.VyprvpnServers{
Version: 1,
Timestamp: 1599323261,
Servers: VyprvpnServers(),
},
Windscribe: models.WindscribeServers{
Version: 2,
Timestamp: 1604019438,
Servers: WindscribeServers(),
},
}
}

View File

@@ -0,0 +1,174 @@
package constants
import (
"crypto/sha256"
"encoding/hex"
"encoding/json"
"fmt"
"testing"
"github.com/qdm12/gluetun/internal/models"
"github.com/stretchr/testify/assert"
)
func digestServerModelVersion(t *testing.T, server interface{}, version uint16) string {
bytes, err := json.Marshal(server)
if err != nil {
t.Fatal(err)
}
bytes = append(bytes, []byte(fmt.Sprintf("%d", version))...)
arr := sha256.Sum256(bytes)
hexString := hex.EncodeToString(arr[:])
if len(hexString) > 8 {
hexString = hexString[:8]
}
return hexString
}
func Test_versions(t *testing.T) {
t.Parallel()
allServers := GetAllServers()
const format = "you forgot to update the version for %s"
testCases := map[string]struct {
model interface{}
version uint16
digest string
}{
"Cyberghost": {
model: models.CyberghostServer{},
version: allServers.Cyberghost.Version,
digest: "fd6242bb",
},
"Mullvad": {
model: models.MullvadServer{},
version: allServers.Mullvad.Version,
digest: "665e9dc1",
},
"Nordvpn": {
model: models.NordvpnServer{},
version: allServers.Nordvpn.Version,
digest: "040de8d0",
},
"Private Internet Access": {
model: models.PIAServer{},
version: allServers.Pia.Version,
digest: "f1e01afe",
},
"Privado": {
model: models.PrivadoServer{},
version: allServers.Privado.Version,
digest: "1d5aeb23",
},
"Purevpn": {
model: models.PurevpnServer{},
version: allServers.Purevpn.Version,
digest: "ada45379",
},
"Surfshark": {
model: models.SurfsharkServer{},
version: allServers.Surfshark.Version,
digest: "042bef64",
},
"Vyprvpn": {
model: models.VyprvpnServer{},
version: allServers.Vyprvpn.Version,
digest: "042bef64",
},
"Windscribe": {
model: models.WindscribeServer{},
version: allServers.Windscribe.Version,
digest: "6e3ca639",
},
}
for name, testCase := range testCases {
name := name
testCase := testCase
t.Run(name, func(t *testing.T) {
t.Parallel()
digest := digestServerModelVersion(t, testCase.model, testCase.version)
failureMessage := fmt.Sprintf(format, name)
assert.Equal(t, testCase.digest, digest, failureMessage)
})
}
}
func digestServersTimestamp(t *testing.T, servers interface{}, timestamp int64) string {
bytes, err := json.Marshal(servers)
if err != nil {
t.Fatal(err)
}
bytes = append(bytes, []byte(fmt.Sprintf("%d", timestamp))...)
arr := sha256.Sum256(bytes)
hexString := hex.EncodeToString(arr[:])
if len(hexString) > 8 {
hexString = hexString[:8]
}
return hexString
}
func Test_timestamps(t *testing.T) {
t.Parallel()
allServers := GetAllServers()
const format = "you forgot to update the timestamp for %s"
testCases := map[string]struct {
servers interface{}
timestamp int64
digest string
}{
"Cyberghost": {
servers: allServers.Cyberghost.Servers,
timestamp: allServers.Cyberghost.Timestamp,
digest: "160631de",
},
"Mullvad": {
servers: allServers.Mullvad.Servers,
timestamp: allServers.Mullvad.Timestamp,
digest: "93859652",
},
"Nordvpn": {
servers: allServers.Nordvpn.Servers,
timestamp: allServers.Nordvpn.Timestamp,
digest: "9fc9a579",
},
"Private Internet Access": {
servers: allServers.Pia.Servers,
timestamp: allServers.Pia.Timestamp,
digest: "d797f112",
},
"Purevpn": {
servers: allServers.Purevpn.Servers,
timestamp: allServers.Purevpn.Timestamp,
digest: "8abe18d4",
},
"Privado": {
servers: allServers.Privado.Servers,
timestamp: allServers.Privado.Timestamp,
digest: "df2a046d",
},
"Surfshark": {
servers: allServers.Surfshark.Servers,
timestamp: allServers.Surfshark.Timestamp,
digest: "79484811",
},
"Vyprvpn": {
servers: allServers.Vyprvpn.Servers,
timestamp: allServers.Vyprvpn.Timestamp,
digest: "1992457c",
},
"Windscribe": {
servers: allServers.Windscribe.Servers,
timestamp: allServers.Windscribe.Timestamp,
digest: "fd87502e",
},
}
for name, testCase := range testCases {
name := name
testCase := testCase
t.Run(name, func(t *testing.T) {
t.Parallel()
digest := digestServersTimestamp(t, testCase.servers, testCase.timestamp)
failureMessage := fmt.Sprintf(format, name)
assert.Equal(t, testCase.digest, digest, failureMessage)
})
}
}

View File

@@ -1,13 +1,13 @@
package constants
const (
// Annoucement is a message annoucement
Annoucement = "Total rewrite in Go with many new features"
// AnnoucementExpiration is the expiration time of the annoucement in unix timestamp
AnnoucementExpiration = 1582761600
// Announcement is a message announcement.
Announcement = "New Docker image qmcgaw/gluetun"
// AnnouncementExpiration is the expiration date of the announcement in format yyyy-mm-dd.
AnnouncementExpiration = "2021-01-20"
)
const (
// IssueLink is the link for users to use to create issues
IssueLink = "https://github.com/qdm12/private-internet-access-docker/issues/new"
// IssueLink is the link for users to use to create issues.
IssueLink = "https://github.com/qdm12/gluetun/issues/new"
)

View File

@@ -0,0 +1,17 @@
package constants
import (
"testing"
"time"
"github.com/stretchr/testify/assert"
)
func Test_AnnouncementExpiration(t *testing.T) {
t.Parallel()
if len(AnnouncementExpiration) == 0 {
return
}
_, err := time.Parse("2006-01-02", AnnouncementExpiration)
assert.NoError(t, err)
}

View File

@@ -0,0 +1,14 @@
package constants
import (
"github.com/qdm12/gluetun/internal/models"
)
const (
Starting models.LoopStatus = "starting"
Running models.LoopStatus = "running"
Stopping models.LoopStatus = "stopping"
Stopped models.LoopStatus = "stopped"
Crashed models.LoopStatus = "crashed"
Completed models.LoopStatus = "completed"
)

View File

@@ -0,0 +1,185 @@
package constants
import (
"net"
"github.com/qdm12/gluetun/internal/models"
)
//nolint:lll
const (
SurfsharkCertificate = "MIIFTTCCAzWgAwIBAgIJAMs9S3fqwv+mMA0GCSqGSIb3DQEBCwUAMD0xCzAJBgNVBAYTAlZHMRIwEAYDVQQKDAlTdXJmc2hhcmsxGjAYBgNVBAMMEVN1cmZzaGFyayBSb290IENBMB4XDTE4MDMxNDA4NTkyM1oXDTI4MDMxMTA4NTkyM1owPTELMAkGA1UEBhMCVkcxEjAQBgNVBAoMCVN1cmZzaGFyazEaMBgGA1UEAwwRU3VyZnNoYXJrIFJvb3QgQ0EwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQDEGMNj0aisM63oSkmVJyZPaYX7aPsZtzsxo6m6p5Wta3MGASoryRsBuRaH6VVa0fwbI1nw5ubyxkuaNa4v3zHVwuSq6F1p8S811+1YP1av+jqDcMyojH0ujZSHIcb/i5LtaHNXBQ3qN48Cc7sqBnTIIFpmb5HthQ/4pW+a82b1guM5dZHsh7q+LKQDIGmvtMtO1+NEnmj81BApFayiaD1ggvwDI4x7o/Y3ksfWSCHnqXGyqzSFLh8QuQrTmWUm84YHGFxoI1/8AKdIyVoB6BjcaMKtKs/pbctk6vkzmYf0XmGovDKPQF6MwUekchLjB5gSBNnptSQ9kNgnTLqi0OpSwI6ixX52Ksva6UM8P01ZIhWZ6ua/T/tArgODy5JZMW+pQ1A6L0b7egIeghpwKnPRG+5CzgO0J5UE6gv000mqbmC3CbiS8xi2xuNgruAyY2hUOoV9/BuBev8ttE5ZCsJH3YlG6NtbZ9hPc61GiBSx8NJnX5QHyCnfic/X87eST/amZsZCAOJ5v4EPSaKrItt+HrEFWZQIq4fJmHJNNbYvWzCE08AL+5/6Z+lxb/Bm3dapx2zdit3x2e+miGHekuiE8lQWD0rXD4+T+nDRi3X+kyt8Ex/8qRiUfrisrSHFzVMRungIMGdO9O/zCINFrb7wahm4PqU2f12Z9TRCOTXciQIDAQABo1AwTjAdBgNVHQ4EFgQUYRpbQwyDahLMN3F2ony3+UqOYOgwHwYDVR0jBBgwFoAUYRpbQwyDahLMN3F2ony3+UqOYOgwDAYDVR0TBAUwAwEB/zANBgkqhkiG9w0BAQsFAAOCAgEAn9zV7F/XVnFNZhHFrt0ZS1Yqz+qM9CojLmiyblMFh0p7t+Hh+VKVgMwrz0LwDH4UsOosXA28eJPmech6/bjfymkoXISy/NUSTFpUChGO9RabGGxJsT4dugOw9MPaIVZffny4qYOc/rXDXDSfF2b+303lLPI43y9qoe0oyZ1vtk/UKG75FkWfFUogGNbpOkuz+et5Y0aIEiyg0yh6/l5Q5h8+yom0HZnREHhqieGbkaGKLkyu7zQ4D4tRK/mBhd8nv+09GtPEG+D5LPbabFVxKjBMP4Vp24WuSUOqcGSsURHevawPVBfgmsxf1UCjelaIwngdh6WfNCRXa5QQPQTKubQvkvXONCDdhmdXQccnRX1nJWhPYi0onffvjsWUfztRypsKzX4dvM9k7xnIcGSGEnCC4RCgt1UiZIj7frcCMssbA6vJ9naM0s7JF7N3VKeHJtqe1OCRHMYnWUZt9vrqX6IoIHlZCoLlv39wFW9QNxelcAOCVbD+19MZ0ZXt7LitjIqe7yF5WxDQN4xru087FzQ4Hfj7eH1SNLLyKZkA1eecjmRoi/OoqAt7afSnwtQLtMUc2bQDg6rHt5C0e4dCLqP/9PGZTSJiwmtRHJ/N5qYWIh9ju83APvLm/AGBTR2pXmj9G3KdVOkpIC7L35dI623cSEC3Q3UZutsEm/UplsM="
SurfsharkOpenvpnStaticKeyV1 = "b02cb1d7c6fee5d4f89b8de72b51a8d0c7b282631d6fc19be1df6ebae9e2779e6d9f097058a31c97f57f0c35526a44ae09a01d1284b50b954d9246725a1ead1ff224a102ed9ab3da0152a15525643b2eee226c37041dc55539d475183b889a10e18bb94f079a4a49888da566b99783460ece01daaf93548beea6c827d9674897e7279ff1a19cb092659e8c1860fbad0db4ad0ad5732f1af4655dbd66214e552f04ed8fd0104e1d4bf99c249ac229ce169d9ba22068c6c0ab742424760911d4636aafb4b85f0c952a9ce4275bc821391aa65fcd0d2394f006e3fba0fd34c4bc4ab260f4b45dec3285875589c97d3087c9134d3a3aa2f904512e85aa2dc2202498"
)
func SurfsharkRegionChoices() (choices []string) {
servers := SurfsharkServers()
choices = make([]string, len(servers))
for i := range servers {
choices[i] = servers[i].Region
}
return choices
}
func SurfsharkServers() []models.SurfsharkServer {
return []models.SurfsharkServer{
{Region: "Albania", IPs: []net.IP{{31, 171, 152, 197}, {31, 171, 154, 147}}},
{Region: "Argentina Buenos Aires", IPs: []net.IP{{91, 206, 168, 13}, {91, 206, 168, 24}}},
{Region: "Australia Adelaide", IPs: []net.IP{{45, 248, 79, 67}, {45, 248, 79, 69}}},
{Region: "Australia Brisbane", IPs: []net.IP{{144, 48, 39, 107}, {144, 48, 39, 131}}},
{Region: "Australia Melbourne", IPs: []net.IP{{103, 192, 80, 141}, {144, 48, 38, 141}}},
{Region: "Australia Perth", IPs: []net.IP{{45, 248, 78, 45}, {124, 150, 139, 27}}},
{Region: "Australia Sydney", IPs: []net.IP{{45, 125, 247, 195}, {180, 149, 228, 117}}},
{Region: "Australia US", IPs: []net.IP{{45, 76, 117, 108}}},
{Region: "Austria", IPs: []net.IP{{5, 253, 207, 83}, {37, 120, 212, 131}}},
{Region: "Azerbaijan", IPs: []net.IP{{94, 20, 21, 85}, {94, 20, 21, 87}}},
{Region: "Belgium", IPs: []net.IP{{5, 253, 205, 181}, {5, 253, 205, 213}}},
{Region: "Bosnia and Herzegovina", IPs: []net.IP{{185, 99, 3, 7}, {185, 212, 111, 41}}},
{Region: "Brazil", IPs: []net.IP{{191, 96, 73, 214}, {191, 96, 73, 216}}},
{Region: "Bulgaria", IPs: []net.IP{{37, 120, 152, 37}}},
{Region: "Canada Montreal", IPs: []net.IP{{172, 98, 82, 85}, {198, 8, 85, 19}}},
{Region: "Canada Toronto", IPs: []net.IP{{68, 71, 244, 200}, {104, 200, 138, 163}}},
{Region: "Canada Toronto mp001", IPs: []net.IP{{138, 197, 151, 26}}},
{Region: "Canada US", IPs: []net.IP{{159, 203, 57, 80}}},
{Region: "Canada Vancouver", IPs: []net.IP{{66, 115, 147, 79}, {66, 115, 147, 87}}},
{Region: "Chile", IPs: []net.IP{{31, 169, 121, 16}}},
{Region: "Colombia", IPs: []net.IP{{45, 129, 32, 8}}},
{Region: "Costa Rica", IPs: []net.IP{{176, 227, 241, 19}, {176, 227, 241, 21}}},
{Region: "Croatia", IPs: []net.IP{{89, 164, 99, 109}, {89, 164, 99, 111}}},
{Region: "Cyprus", IPs: []net.IP{{195, 47, 194, 34}}},
{Region: "Czech Republic", IPs: []net.IP{{185, 152, 64, 151}, {185, 152, 64, 178}}},
{Region: "Denmark", IPs: []net.IP{{37, 120, 194, 115}, {95, 174, 65, 71}}},
{Region: "Estonia", IPs: []net.IP{{165, 231, 163, 23}, {185, 174, 159, 69}}},
{Region: "Finland", IPs: []net.IP{{196, 244, 191, 179}, {196, 244, 191, 181}}},
{Region: "France Bordeaux", IPs: []net.IP{{185, 108, 106, 67}, {185, 108, 106, 150}}},
{Region: "France Marseilles", IPs: []net.IP{{185, 166, 84, 53}, {185, 166, 84, 75}}},
{Region: "France Paris", IPs: []net.IP{{45, 83, 90, 181}, {45, 89, 174, 103}}},
{Region: "France Sweden", IPs: []net.IP{{199, 247, 8, 20}}},
{Region: "Germany Berlin", IPs: []net.IP{{152, 89, 163, 19}, {217, 138, 216, 243}}},
{Region: "Germany Frankfurt am Main", IPs: []net.IP{{185, 158, 135, 36}, {185, 220, 70, 83}}},
{Region: "Germany Frankfurt am Main st001", IPs: []net.IP{{45, 87, 212, 179}}},
{Region: "Germany Frankfurt am Main st002", IPs: []net.IP{{45, 87, 212, 181}}},
{Region: "Germany Frankfurt am Main st003", IPs: []net.IP{{45, 87, 212, 183}}},
{Region: "Germany Frankfurt mp001", IPs: []net.IP{{46, 101, 189, 14}}},
{Region: "Germany Munich", IPs: []net.IP{{178, 238, 231, 51}, {178, 238, 231, 55}}},
{Region: "Germany Nuremberg", IPs: []net.IP{{62, 171, 149, 162}, {62, 171, 151, 182}}},
{Region: "Germany Singapour", IPs: []net.IP{{159, 89, 14, 157}}},
{Region: "Germany UK", IPs: []net.IP{{46, 101, 250, 73}}},
{Region: "Greece", IPs: []net.IP{{194, 150, 167, 34}, {194, 150, 167, 40}}},
{Region: "Hong Kong", IPs: []net.IP{{84, 17, 57, 73}, {212, 102, 42, 201}}},
{Region: "Hungary", IPs: []net.IP{{37, 120, 144, 151}, {37, 120, 144, 213}}},
{Region: "Iceland", IPs: []net.IP{{82, 221, 128, 166}, {82, 221, 143, 243}}},
{Region: "India Chennai", IPs: []net.IP{{103, 108, 117, 118}, {103, 108, 117, 151}}},
{Region: "India Indore", IPs: []net.IP{{103, 39, 132, 189}, {137, 59, 52, 109}}},
{Region: "India Mumbai", IPs: []net.IP{{103, 221, 233, 88}, {165, 231, 253, 147}}},
{Region: "India UK", IPs: []net.IP{{134, 209, 148, 122}}},
{Region: "Indonesia", IPs: []net.IP{{103, 120, 66, 214}, {103, 120, 66, 216}}},
{Region: "Ireland", IPs: []net.IP{{185, 108, 128, 159}, {185, 108, 128, 181}}},
{Region: "Israel", IPs: []net.IP{{87, 239, 255, 109}}},
{Region: "Italy Milan", IPs: []net.IP{{84, 17, 58, 148}, {185, 128, 27, 37}}},
{Region: "Italy Rome", IPs: []net.IP{{82, 102, 26, 61}, {82, 102, 26, 115}}},
{Region: "Japan Tokyo", IPs: []net.IP{{45, 87, 213, 5}, {103, 208, 221, 227}}},
{Region: "Japan Tokyo st001", IPs: []net.IP{{45, 87, 213, 19}}},
{Region: "Japan Tokyo st002", IPs: []net.IP{{45, 87, 213, 21}}},
{Region: "Japan Tokyo st003", IPs: []net.IP{{45, 87, 213, 23}}},
{Region: "Japan Tokyo st004", IPs: []net.IP{{217, 138, 212, 19}}},
{Region: "Japan Tokyo st005", IPs: []net.IP{{217, 138, 212, 21}}},
{Region: "Japan Tokyo st006", IPs: []net.IP{{82, 102, 28, 123}}},
{Region: "Japan Tokyo st007", IPs: []net.IP{{82, 102, 28, 125}}},
{Region: "Kazakhstan", IPs: []net.IP{{95, 57, 207, 200}}},
{Region: "Korea", IPs: []net.IP{{61, 14, 210, 239}, {61, 97, 243, 112}}},
{Region: "Latvia", IPs: []net.IP{{188, 92, 78, 140}, {188, 92, 78, 142}}},
{Region: "Libya", IPs: []net.IP{{41, 208, 72, 157}, {41, 208, 72, 204}}},
{Region: "Luxembourg", IPs: []net.IP{{185, 153, 151, 60}, {185, 153, 151, 83}}},
{Region: "Malaysia", IPs: []net.IP{{42, 0, 30, 152}, {42, 0, 30, 209}}},
{Region: "Mexico City Mexico", IPs: []net.IP{{194, 41, 112, 9}, {194, 41, 112, 19}}},
{Region: "Moldova", IPs: []net.IP{{178, 175, 128, 235}, {178, 175, 128, 237}}},
{Region: "Netherlands Amsterdam", IPs: []net.IP{{89, 46, 223, 74}, {89, 46, 223, 217}}},
{Region: "Netherlands Amsterdam mp001", IPs: []net.IP{{188, 166, 43, 117}}},
{Region: "Netherlands Amsterdam st001", IPs: []net.IP{{81, 19, 209, 51}}},
{Region: "Netherlands US", IPs: []net.IP{{188, 166, 98, 91}}},
{Region: "New Zealand", IPs: []net.IP{{180, 149, 231, 67}}},
{Region: "Nigeria", IPs: []net.IP{{102, 165, 23, 6}, {102, 165, 23, 42}}},
{Region: "North Macedonia", IPs: []net.IP{{185, 225, 28, 93}, {185, 225, 28, 101}}},
{Region: "Norway", IPs: []net.IP{{45, 12, 223, 213}, {84, 247, 50, 69}}},
{Region: "Paraguay", IPs: []net.IP{{181, 40, 18, 56}, {186, 16, 32, 163}}},
{Region: "Philippines", IPs: []net.IP{{45, 134, 224, 10}, {45, 134, 224, 20}}},
{Region: "Poland Gdansk", IPs: []net.IP{{5, 187, 53, 51}}},
{Region: "Poland Warsaw", IPs: []net.IP{{185, 246, 208, 77}, {185, 246, 208, 105}}},
{Region: "Portugal Lisbon", IPs: []net.IP{{5, 154, 174, 26}, {5, 154, 174, 173}}},
{Region: "Portugal Loule", IPs: []net.IP{{176, 61, 146, 111}, {176, 61, 146, 123}}},
{Region: "Portugal Porto", IPs: []net.IP{{194, 39, 127, 242}}},
{Region: "Romania", IPs: []net.IP{{86, 106, 137, 147}, {86, 106, 137, 149}}},
{Region: "Russia Moscow", IPs: []net.IP{{213, 183, 56, 145}, {213, 183, 56, 160}}},
{Region: "Russia St. Petersburg", IPs: []net.IP{{213, 183, 54, 23}, {213, 183, 54, 165}}},
{Region: "Serbia", IPs: []net.IP{{37, 120, 193, 53}, {152, 89, 160, 211}}},
{Region: "Singapore", IPs: []net.IP{{89, 187, 162, 186}, {89, 187, 163, 210}}},
{Region: "Singapore Hong Kong", IPs: []net.IP{{206, 189, 83, 129}}},
{Region: "Singapore Netherlands", IPs: []net.IP{{104, 248, 148, 18}}},
{Region: "Singapore in", IPs: []net.IP{{128, 199, 193, 35}}},
{Region: "Singapore mp001", IPs: []net.IP{{206, 189, 94, 229}}},
{Region: "Singapore st001", IPs: []net.IP{{217, 138, 201, 91}}},
{Region: "Singapore st002", IPs: []net.IP{{217, 138, 201, 93}}},
{Region: "Singapore st003", IPs: []net.IP{{84, 247, 49, 19}}},
{Region: "Singapore st004", IPs: []net.IP{{84, 247, 49, 21}}},
{Region: "Slovekia", IPs: []net.IP{{193, 37, 255, 39}, {193, 37, 255, 41}}},
{Region: "Slovenia", IPs: []net.IP{{195, 158, 249, 38}, {195, 158, 249, 42}}},
{Region: "South Africa", IPs: []net.IP{{154, 127, 49, 226}, {154, 127, 49, 232}}},
{Region: "Spain Barcelona", IPs: []net.IP{{185, 188, 61, 15}, {185, 188, 61, 25}}},
{Region: "Spain Madrid", IPs: []net.IP{{188, 208, 141, 18}, {188, 208, 141, 20}}},
{Region: "Spain Valencia", IPs: []net.IP{{185, 153, 150, 48}, {196, 196, 150, 71}}},
{Region: "Sweden", IPs: []net.IP{{45, 83, 91, 149}, {185, 76, 9, 41}}},
{Region: "Switzerland", IPs: []net.IP{{84, 17, 53, 219}, {84, 17, 53, 221}}},
{Region: "Taiwan", IPs: []net.IP{{2, 58, 241, 5}, {2, 58, 241, 43}}},
{Region: "Thailand", IPs: []net.IP{{45, 64, 186, 132}, {45, 64, 186, 163}}},
{Region: "Turkey", IPs: []net.IP{{185, 195, 79, 5}, {185, 195, 79, 19}}},
{Region: "Turkey Istanbul", IPs: []net.IP{{107, 150, 95, 155}, {107, 150, 95, 157}}},
{Region: "UK France", IPs: []net.IP{{188, 166, 168, 247}}},
{Region: "UK Germany", IPs: []net.IP{{45, 77, 58, 16}}},
{Region: "UK Glasgow", IPs: []net.IP{{185, 108, 105, 157}}},
{Region: "UK London", IPs: []net.IP{{178, 239, 166, 218}, {185, 44, 78, 164}}},
{Region: "UK London mp001", IPs: []net.IP{{206, 189, 119, 92}}},
{Region: "UK London st001", IPs: []net.IP{{217, 146, 82, 83}}},
{Region: "UK London st002", IPs: []net.IP{{185, 134, 22, 80}}},
{Region: "UK London st003", IPs: []net.IP{{185, 134, 22, 92}}},
{Region: "UK London st004", IPs: []net.IP{{185, 44, 76, 186}}},
{Region: "UK London st005", IPs: []net.IP{{185, 44, 76, 188}}},
{Region: "UK Manchester", IPs: []net.IP{{193, 148, 17, 83}, {217, 138, 196, 91}}},
{Region: "US Atlanta", IPs: []net.IP{{66, 115, 166, 147}, {66, 115, 166, 151}}},
{Region: "US Bend", IPs: []net.IP{{45, 43, 14, 95}}},
{Region: "US Boston", IPs: []net.IP{{173, 237, 207, 13}, {199, 217, 107, 20}}},
{Region: "US Buffalo", IPs: []net.IP{{107, 174, 20, 130}}},
{Region: "US Charlotte", IPs: []net.IP{{66, 11, 124, 136}, {192, 154, 254, 135}}},
{Region: "US Chicago", IPs: []net.IP{{74, 119, 146, 197}, {89, 187, 182, 173}}},
{Region: "US Dallas", IPs: []net.IP{{66, 115, 177, 133}, {66, 115, 177, 158}}},
{Region: "US Denver", IPs: []net.IP{{212, 102, 44, 76}, {212, 102, 44, 98}}},
{Region: "US Gahanna", IPs: []net.IP{{104, 244, 209, 99}, {104, 244, 211, 171}}},
{Region: "US Houston", IPs: []net.IP{{104, 148, 30, 53}, {199, 10, 64, 115}}},
{Region: "US Kansas City", IPs: []net.IP{{173, 208, 202, 59}, {173, 208, 202, 61}}},
{Region: "US Las Vegas", IPs: []net.IP{{89, 187, 187, 149}, {185, 242, 5, 215}}},
{Region: "US Latham", IPs: []net.IP{{45, 43, 19, 74}, {45, 43, 19, 92}}},
{Region: "US Los Angeles", IPs: []net.IP{{38, 95, 110, 73}, {192, 111, 134, 202}}},
{Region: "US Maryland", IPs: []net.IP{{23, 82, 8, 173}, {23, 105, 163, 94}}},
{Region: "US Miami", IPs: []net.IP{{89, 187, 173, 250}, {172, 83, 42, 143}}},
{Region: "US Netherlands", IPs: []net.IP{{142, 93, 58, 71}}},
{Region: "US New York City", IPs: []net.IP{{84, 17, 35, 78}, {89, 187, 178, 92}}},
{Region: "US New York City mp001", IPs: []net.IP{{45, 55, 60, 159}}},
{Region: "US New York City st001", IPs: []net.IP{{92, 119, 177, 19}}},
{Region: "US New York City st002", IPs: []net.IP{{92, 119, 177, 21}}},
{Region: "US New York City st003", IPs: []net.IP{{92, 119, 177, 23}}},
{Region: "US New York City st004", IPs: []net.IP{{193, 148, 18, 51}}},
{Region: "US New York City st005", IPs: []net.IP{{193, 148, 18, 53}}},
{Region: "US Orlando", IPs: []net.IP{{198, 147, 22, 135}, {198, 147, 22, 213}}},
{Region: "US Phoenix", IPs: []net.IP{{184, 170, 240, 179}, {199, 58, 187, 3}}},
{Region: "US Portugal", IPs: []net.IP{{142, 93, 81, 242}}},
{Region: "US Saint Louis", IPs: []net.IP{{148, 72, 174, 43}, {148, 72, 174, 51}}},
{Region: "US Salt Lake City", IPs: []net.IP{{104, 200, 131, 165}, {104, 200, 131, 249}}},
{Region: "US San Francisco", IPs: []net.IP{{107, 181, 166, 39}, {107, 181, 166, 83}}},
{Region: "US San Francisco mp001", IPs: []net.IP{{165, 232, 53, 25}}},
{Region: "US Seatle", IPs: []net.IP{{199, 229, 250, 163}}},
{Region: "US Tampa", IPs: []net.IP{{209, 216, 92, 197}, {209, 216, 92, 205}}},
{Region: "Ukraine", IPs: []net.IP{{45, 9, 238, 23}, {45, 9, 238, 30}}},
{Region: "United Arab Emirates", IPs: []net.IP{{45, 9, 250, 99}, {45, 9, 250, 103}}},
{Region: "Vietnam", IPs: []net.IP{{202, 143, 110, 29}, {202, 143, 110, 32}}},
}
}

View File

@@ -1,20 +0,0 @@
package constants
import (
"github.com/qdm12/private-internet-access-docker/internal/models"
)
const (
// TinyProxyInfoLevel is the info log level for TinyProxy
TinyProxyInfoLevel models.TinyProxyLogLevel = "Info"
// TinyProxyConnectLevel is the info log level for TinyProxy
TinyProxyConnectLevel models.TinyProxyLogLevel = "Connect"
// TinyProxyNoticeLevel is the info log level for TinyProxy
TinyProxyNoticeLevel models.TinyProxyLogLevel = "Notice"
// TinyProxyWarnLevel is the warning log level for TinyProxy
TinyProxyWarnLevel models.TinyProxyLogLevel = "Warning"
// TinyProxyErrorLevel is the error log level for TinyProxy
TinyProxyErrorLevel models.TinyProxyLogLevel = "Error"
// TinyProxyCriticalLevel is the critical log level for TinyProxy
TinyProxyCriticalLevel models.TinyProxyLogLevel = "Critical"
)

View File

@@ -1,21 +1,33 @@
package constants
import (
"github.com/qdm12/private-internet-access-docker/internal/models"
)
const (
// PrivateInternetAccess is a VPN provider
PrivateInternetAccess models.VPNProvider = "private internet access"
// Mullvad is a VPN provider
Mullvad models.VPNProvider = "mullvad"
// Windscribe is a VPN provider
Windscribe models.VPNProvider = "windscribe"
)
const (
// TCP is a network protocol (reliable and slower than UDP)
TCP models.NetworkProtocol = "tcp"
// UDP is a network protocol (unreliable and faster than TCP)
UDP models.NetworkProtocol = "udp"
)
package constants
import (
"github.com/qdm12/gluetun/internal/models"
)
const (
// PrivateInternetAccess is a VPN provider.
PrivateInternetAccess models.VPNProvider = "private internet access"
// Mullvad is a VPN provider.
Mullvad models.VPNProvider = "mullvad"
// Windscribe is a VPN provider.
Windscribe models.VPNProvider = "windscribe"
// Surfshark is a VPN provider.
Surfshark models.VPNProvider = "surfshark"
// Cyberghost is a VPN provider.
Cyberghost models.VPNProvider = "cyberghost"
// Vyprvpn is a VPN provider.
Vyprvpn models.VPNProvider = "vyprvpn"
// NordVPN is a VPN provider.
Nordvpn models.VPNProvider = "nordvpn"
// PureVPN is a VPN provider.
Purevpn models.VPNProvider = "purevpn"
// Privado is a VPN provider.
Privado models.VPNProvider = "privado"
)
const (
// TCP is a network protocol (reliable and slower than UDP).
TCP models.NetworkProtocol = "tcp"
// UDP is a network protocol (unreliable and faster than TCP).
UDP models.NetworkProtocol = "udp"
)

View File

@@ -0,0 +1,100 @@
package constants
import (
"net"
"github.com/qdm12/gluetun/internal/models"
)
//nolint:lll
const (
VyprvpnCertificate = "MIIGDjCCA/agAwIBAgIJAL2ON5xbane/MA0GCSqGSIb3DQEBDQUAMIGTMQswCQYDVQQGEwJDSDEQMA4GA1UECAwHTHVjZXJuZTEPMA0GA1UEBwwGTWVnZ2VuMRkwFwYDVQQKDBBHb2xkZW4gRnJvZyBHbWJIMSEwHwYDVQQDDBhHb2xkZW4gRnJvZyBHbWJIIFJvb3QgQ0ExIzAhBgkqhkiG9w0BCQEWFGFkbWluQGdvbGRlbmZyb2cuY29tMB4XDTE5MTAxNzIwMTQxMFoXDTM5MTAxMjIwMTQxMFowgZMxCzAJBgNVBAYTAkNIMRAwDgYDVQQIDAdMdWNlcm5lMQ8wDQYDVQQHDAZNZWdnZW4xGTAXBgNVBAoMEEdvbGRlbiBGcm9nIEdtYkgxITAfBgNVBAMMGEdvbGRlbiBGcm9nIEdtYkggUm9vdCBDQTEjMCEGCSqGSIb3DQEJARYUYWRtaW5AZ29sZGVuZnJvZy5jb20wggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQCtuddaZrpWZ+nUuJpG+ohTquO3XZtq6d4U0E2oiPeIiwm+WWLY49G+GNJb5aVrlrBojaykCAc2sU6NeUlpg3zuqrDqLcz7PAE4OdNiOdrLBF1o9ZHrcITDZN304eAY5nbyHx5V6x/QoDVCi4g+5OVTA+tZjpcl4wRIpgknWznO73IKCJ6YckpLn1BsFrVCb2ehHYZLg7Js58FzMySIxBmtkuPeHQXL61DFHh3cTFcMxqJjzh7EGsWRyXfbAaBGYnT+TZwzpLXXt8oBGpNXG8YBDrPdK0A+lzMnJ4nS0rgHDSRF0brx+QYk/6CgM510uFzB7zytw9UTD3/5TvKlCUmTGGgI84DbJ3DEvjxbgiQnJXCUZKKYSHwrK79Y4Qn+lXu4Bu0ZTCJBje0GUVMTPAvBCeDvzSe0iRcVSNMJVM68d4kD1PpSY/zWfCz5hiOjHWuXinaoZ0JJqRF8kGbJsbDlDYDtVvh/Cd4aWN6Q/2XLpszBsG5i8sdkS37nzkdlRwNEIZwsKfcXwdTOlDinR1LUG68LmzJAwfNE47xbrZUsdGGfG+HSPsrqFFiLGe7Y4e2+a7vGdSY9qR9PAzyx0ijCCrYzZDIsb2dwjLctUx6a3LNV8cpfhKX+s6tfMldGufPI7byHT1Ybf0NtMS1d1RjD6IbqedXQdCKtaw68kTX//wIDAQABo2MwYTAdBgNVHQ4EFgQU2EbQvBd1r/EADr2jCPMXsH7zEXEwHwYDVR0jBBgwFoAU2EbQvBd1r/EADr2jCPMXsH7zEXEwDwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMCAYYwDQYJKoZIhvcNAQENBQADggIBAAViCPieIronV+9asjZyo5oSZSNWUkWRYdezjezsf49+fwT12iRgnkSEQeoj5caqcOfNm/eRpN4G7jhhCcxy9RGF+GurIlZ4v0mChZbx1jcxqr9/3/Z2TqvHALyWngBYDv6pv1iWcd9a4+QL9kj1Tlp8vUDIcHMtDQkEHnkhC+MnjyrdsdNE5wjlLljjFR2Qy5a6/kWwZ1JQVYof1J1EzY6mU7YLMHOdjfmeci5i0vg8+9kGMsc/7Wm69L1BeqpDB3ZEAgmOtda2jwOevJ4sABmRoSThFp4DeMcxb62HW1zZCCpgzWv/33+pZdPvnZHSz7RGoxH4Ln7eBf3oo2PMlu7wCsid3HUdgkRf2Og1RJIrFfEjb7jga1JbKX2Qo/FH3txzdUimKiDRv3ccFmEOqjndUG6hP+7/EsI43oCPYOvZR+u5GdOkhYrDGZlvjXeJ1CpQxTR/EX+Vt7F8YG+i2LkO7lhPLb+LzgPAxVPCcEMHruuUlE1BYxxzRMOW4X4kjHvJjZGISxa9lgTY3e0mnoQNQVBHKfzI2vGLwvcrFcCIrVxeEbj2dryfByyhZlrNPFbXyf7P4OSfk+fVh6Is1IF1wksfLY/6gWvcmXB8JwmKFDa9s5NfzXnzP3VMrNUWXN3G8Eee6qzKKTDsJ70OrgAx9j9a+dMLfe1vP5t6GQj5"
)
func VyprvpnRegionChoices() (choices []string) {
servers := VyprvpnServers()
choices = make([]string, len(servers))
for i := range servers {
choices[i] = servers[i].Region
}
return choices
}
func VyprvpnServers() []models.VyprvpnServer {
return []models.VyprvpnServer{
{Region: "Algeria", IPs: []net.IP{{209, 99, 75, 20}}},
{Region: "Argentina", IPs: []net.IP{{209, 99, 109, 19}}},
{Region: "Australia Melbourne", IPs: []net.IP{{209, 99, 117, 19}}},
{Region: "Australia Perth", IPs: []net.IP{{209, 99, 1, 19}}},
{Region: "Australia Sydney", IPs: []net.IP{{209, 99, 117, 18}}},
{Region: "Austria", IPs: []net.IP{{128, 90, 96, 18}}},
{Region: "Bahrain", IPs: []net.IP{{209, 99, 115, 19}}},
{Region: "Belgium", IPs: []net.IP{{128, 90, 96, 20}}},
{Region: "Brazil", IPs: []net.IP{{209, 99, 109, 20}}},
{Region: "Bulgaria", IPs: []net.IP{{128, 90, 96, 22}}},
{Region: "Canada", IPs: []net.IP{{209, 99, 21, 18}}},
{Region: "Columbia", IPs: []net.IP{{209, 99, 109, 21}}},
{Region: "Costa Rica", IPs: []net.IP{{209, 99, 109, 22}}},
{Region: "Czech Republic", IPs: []net.IP{{128, 90, 96, 24}}},
{Region: "Denmark", IPs: []net.IP{{128, 90, 96, 28}}},
{Region: "Dubai", IPs: []net.IP{{128, 90, 45, 104}}},
{Region: "Egypt", IPs: []net.IP{{128, 90, 228, 43}}},
{Region: "El Salvador", IPs: []net.IP{{209, 99, 61, 20}}},
{Region: "Finland", IPs: []net.IP{{128, 90, 96, 32}}},
{Region: "France", IPs: []net.IP{{128, 90, 96, 34}}},
{Region: "Germany", IPs: []net.IP{{128, 90, 96, 26}}},
{Region: "Greece", IPs: []net.IP{{128, 90, 228, 59}}},
{Region: "Hong Kong", IPs: []net.IP{{128, 90, 227, 18}}},
{Region: "Iceland", IPs: []net.IP{{209, 99, 22, 20}}},
{Region: "India", IPs: []net.IP{{209, 99, 115, 20}}},
{Region: "Indonesia", IPs: []net.IP{{209, 99, 1, 20}}},
{Region: "Ireland", IPs: []net.IP{{209, 99, 22, 19}}},
{Region: "Israel", IPs: []net.IP{{128, 90, 228, 20}}},
{Region: "Italy", IPs: []net.IP{{128, 90, 96, 36}}},
{Region: "Japan", IPs: []net.IP{{209, 99, 113, 18}}},
{Region: "Latvia", IPs: []net.IP{{128, 90, 96, 44}}},
{Region: "Liechtenstein", IPs: []net.IP{{128, 90, 96, 38}}},
{Region: "Lithuania", IPs: []net.IP{{128, 90, 96, 40}}},
{Region: "Luxembourg", IPs: []net.IP{{128, 90, 96, 42}}},
{Region: "Macao", IPs: []net.IP{{128, 90, 227, 36}}},
{Region: "Malaysia", IPs: []net.IP{{209, 99, 1, 21}}},
{Region: "Maldives", IPs: []net.IP{{209, 99, 1, 26}}},
{Region: "Marshall Islands", IPs: []net.IP{{209, 99, 1, 25}}},
{Region: "Mexico", IPs: []net.IP{{209, 99, 61, 19}}},
{Region: "Netherlands", IPs: []net.IP{{128, 90, 96, 16}}},
{Region: "New Zealand", IPs: []net.IP{{209, 99, 117, 20}}},
{Region: "Norway", IPs: []net.IP{{128, 90, 96, 46}}},
{Region: "Pakistan", IPs: []net.IP{{128, 90, 228, 67}}},
{Region: "Panama", IPs: []net.IP{{209, 99, 109, 23}}},
{Region: "Philippines", IPs: []net.IP{{209, 99, 1, 22}}},
{Region: "Poland", IPs: []net.IP{{128, 90, 96, 48}}},
{Region: "Portugal", IPs: []net.IP{{128, 90, 96, 50}}},
{Region: "Qatar", IPs: []net.IP{{209, 99, 115, 21}}},
{Region: "Romania", IPs: []net.IP{{128, 90, 96, 52}}},
{Region: "Russia", IPs: []net.IP{{128, 90, 96, 54}}},
{Region: "Saudi Arabia", IPs: []net.IP{{209, 99, 115, 22}}},
{Region: "Singapore", IPs: []net.IP{{209, 99, 1, 18}}},
{Region: "Slovakia", IPs: []net.IP{{128, 90, 96, 60}}},
{Region: "Slovenia", IPs: []net.IP{{128, 90, 96, 58}}},
{Region: "South Korea", IPs: []net.IP{{209, 99, 113, 19}}},
{Region: "Spain", IPs: []net.IP{{128, 90, 96, 30}}},
{Region: "Sweden", IPs: []net.IP{{128, 90, 96, 56}}},
{Region: "Switzerland", IPs: []net.IP{{209, 99, 60, 18}}},
{Region: "Taiwan", IPs: []net.IP{{128, 90, 227, 27}}},
{Region: "Thailand", IPs: []net.IP{{209, 99, 1, 23}}},
{Region: "Turkey", IPs: []net.IP{{128, 90, 96, 62}}},
{Region: "USA Austin", IPs: []net.IP{{209, 99, 61, 18}}},
{Region: "USA Chicago", IPs: []net.IP{{209, 99, 93, 18}}},
{Region: "USA Los Angeles", IPs: []net.IP{{209, 99, 67, 18}}},
{Region: "USA Miami", IPs: []net.IP{{209, 99, 109, 18}}},
{Region: "USA New York", IPs: []net.IP{{209, 99, 63, 18}}},
{Region: "USA San Francisco", IPs: []net.IP{{209, 99, 95, 18}}},
{Region: "USA Seattle", IPs: []net.IP{{209, 99, 94, 18}}},
{Region: "USA Washington", IPs: []net.IP{{209, 99, 62, 18}}},
{Region: "USA Washington DC", IPs: []net.IP{{209, 99, 62, 18}}},
{Region: "Ukraine", IPs: []net.IP{{128, 90, 96, 64}}},
{Region: "United Kingdom", IPs: []net.IP{{209, 99, 22, 18}}},
{Region: "Uruguay", IPs: []net.IP{{209, 99, 61, 21}}},
{Region: "Vietnam", IPs: []net.IP{{209, 99, 1, 24}}},
}
}

View File

@@ -0,0 +1,339 @@
package constants
import (
"net"
"github.com/qdm12/gluetun/internal/models"
)
//nolint:lll
const (
WindscribeCertificate = "MIIF3DCCA8SgAwIBAgIJAMsOivWTmu9fMA0GCSqGSIb3DQEBCwUAMHsxCzAJBgNVBAYTAkNBMQswCQYDVQQIDAJPTjEQMA4GA1UEBwwHVG9yb250bzEbMBkGA1UECgwSV2luZHNjcmliZSBMaW1pdGVkMRMwEQYDVQQLDApPcGVyYXRpb25zMRswGQYDVQQDDBJXaW5kc2NyaWJlIE5vZGUgQ0EwHhcNMTYwMzA5MDMyNjIwWhcNNDAxMDI5MDMyNjIwWjB7MQswCQYDVQQGEwJDQTELMAkGA1UECAwCT04xEDAOBgNVBAcMB1Rvcm9udG8xGzAZBgNVBAoMEldpbmRzY3JpYmUgTGltaXRlZDETMBEGA1UECwwKT3BlcmF0aW9uczEbMBkGA1UEAwwSV2luZHNjcmliZSBOb2RlIENBMIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAruBtLR1Vufd71LeQEqChgHS4AQJ0fSRner0gmZPEr2TL5uWboOEWXFFoEUTthF+P/N8yy3xRZ8HhG/zKlmJ1xw+7KZRbTADD6shJPj3/uvTIO80sU+9LmsyKSWuPhQ1NkgNA7rrMTfz9eHJ2MVDs4XCpYWyX9iuAQrHSY6aPq+4TpCbUgprkM3Gwjh9RSt9IoDoc4CF2bWSaVepUcL9yz/SXLPzFx2OT9rFrDhL3ryHRzJQ/tA+VD8A7lo8bhOcDqiXgEFmVOZNMLw+r167Qq1Ck7X86yr2mnW/6HK2gJOvY0/SPKukfGJAiYZKdG+fe4ekyYcAVhDfPJg7rF9wUqPwUzejJyAs1K18JwX94Y8fnD6vQobjpC3qfHtwQP7Uj2AcI6QC8ytWDegV6UIkHXAMXBQSX5suSQoE11deG32cy7nyp5vhgy31rTyNoopqlcCAhPm6k0jVVQbvXhLcpTSL8iCCoMdrP28i/xsfvktBAkl5giHMdK6hxqWgPI+Bx9uPIhRp3fJ2z8AgFm8g1ARB2ZzQ+OZZ2RUIkJuUKhi2kUhgKSAQ+eF89aoqDjp/J1miZqGRzt4DovSZfQOeL01RkKHEibAPYCfgHG2ZSwoLoeaxE2vNZiX4dpXiOQYTOIXOwEPZzPvfTQf9T4Kxvx3jzQnt3PzjlMCqKk3Aipm8CAwEAAaNjMGEwHQYDVR0OBBYEFEH2v9F2z938Ebngsj9RkVSSgs45MB8GA1UdIwQYMBaAFEH2v9F2z938Ebngsj9RkVSSgs45MA8GA1UdEwEB/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgGGMA0GCSqGSIb3DQEBCwUAA4ICAQAgI6NgYkVo5rB6yKStgHjjZsINsgEvoMuHwkM0YaV22XtKNiHdsiOmY/PGCRemFobTEHk5XHcvcOTWv/D1qVf8fI21WAoNQVH7h8KEsr4uMGKCB6Lu8l6xALXRMjo1xb6JKBWXwIAzUu691rUD2exT1E+A5t+xw+gzqV8rWTMIoUaH7O1EKjN6ryGW71Khiik8/ETrP3YT32ZbS2P902iMKw9rpmuS0wWhnO5k/iO/6YNA1ZMV5JG5oZvZQYEDk7enLD9HvqazofMuy/Sz/n62ZCDdQsnabzxl04wwv5Y3JZbV/6bOM520GgdJEoDxviY05ax2Mz05otyBzrAVjFw9RZt/Ls8ATifu9BusZ2ootvscdIuE3x+ZCl5lvANcFEnvgGw0qpCeASLpsfxwq1dRgIn7BOiTauFv4eoeFAQvCD+l+EKGWKu3M2y19DgYX94N2+Xs2bwChroaO5e4iFemMLMuWKZvYgnqS9OAtRSYWbNX/wliiPz7u13yj+qSWgMfu8WPYNQlMZJXuGWUvKLEXCUExlu7/o8D4HpsVs30E0pUdaqN0vExB1KegxPWWrmLcYnPG3knXpkC3ZBZ5P/el/2eyhZRy9ydiITF8gM3L08E8aeqvzZMw2FDSmousydIzlXgeS5VuEf+lUFA2h8oZYGQgrLt+ot8MbLhJlkp4Q=="
WindscribeOpenvpnStaticKeyV1 = "5801926a57ac2ce27e3dfd1dd6ef82042d82bd4f3f0021296f57734f6f1ea714a6623845541c4b0c3dea0a050fe6746cb66dfab14cda27e5ae09d7c155aa554f399fa4a863f0e8c1af787e5c602a801d3a2ec41e395a978d56729457fe6102d7d9e9119aa83643210b33c678f9d4109e3154ac9c759e490cb309b319cf708cae83ddadc3060a7a26564d1a24411cd552fe6620ea16b755697a4fc5e6e9d0cfc0c5c4a1874685429046a424c026db672e4c2c492898052ba59128d46200b40f880027a8b6610a4d559bdc9346d33a0a6b08e75c7fd43192b162bfd0aef0c716b31584827693f676f9a5047123466f0654eade34972586b31c6ce7e395f4b478cb"
)
func WindscribeRegionChoices() (choices []string) {
servers := WindscribeServers()
choices = make([]string, len(servers))
for i := range servers {
choices[i] = servers[i].Region
}
return choices
}
func WindscribeCityChoices() (choices []string) {
servers := WindscribeServers()
choices = make([]string, len(servers))
for i := range servers {
choices[i] = servers[i].City
}
return choices
}
func WindscribeHostnameChoices() (choices []string) {
servers := WindscribeServers()
choices = make([]string, len(servers))
for i := range servers {
choices[i] = servers[i].Hostname
}
return choices
}
//nolint:lll
func WindscribeServers() []models.WindscribeServer {
return []models.WindscribeServer{
{Region: "Albania", City: "Tirana", Hostname: "al-002.whiskergalaxy.com", IP: net.IP{31, 171, 152, 179}},
{Region: "Argentina", City: "Buenos Aires", Hostname: "ar-001.whiskergalaxy.com", IP: net.IP{200, 85, 152, 110}},
{Region: "Argentina", City: "Buenos Aires", Hostname: "ar-003.whiskergalaxy.com", IP: net.IP{167, 250, 6, 121}},
{Region: "Argentina", City: "Buenos Aires", Hostname: "ar-004.whiskergalaxy.com", IP: net.IP{190, 105, 236, 50}},
{Region: "Argentina", City: "Buenos Aires", Hostname: "ar-005.whiskergalaxy.com", IP: net.IP{190, 105, 236, 32}},
{Region: "Argentina", City: "Buenos Aires", Hostname: "ar-006.whiskergalaxy.com", IP: net.IP{190, 105, 236, 19}},
{Region: "Australia", City: "Adelaide ", Hostname: "au-011.whiskergalaxy.com", IP: net.IP{103, 108, 92, 83}},
{Region: "Australia", City: "Adelaide", Hostname: "au-008.whiskergalaxy.com", IP: net.IP{116, 90, 72, 243}},
{Region: "Australia", City: "Brisbane", Hostname: "au-007.whiskergalaxy.com", IP: net.IP{103, 62, 50, 208}},
{Region: "Australia", City: "Brisbane", Hostname: "au-014.whiskergalaxy.com", IP: net.IP{43, 245, 160, 35}},
{Region: "Australia", City: "Canberra", Hostname: "au-010.whiskergalaxy.com", IP: net.IP{116, 206, 229, 131}},
{Region: "Australia", City: "Melbourne ", Hostname: "au-005.whiskergalaxy.com", IP: net.IP{45, 121, 209, 160}},
{Region: "Australia", City: "Melbourne ", Hostname: "au-013.whiskergalaxy.com", IP: net.IP{116, 206, 228, 67}},
{Region: "Australia", City: "Perth", Hostname: "au-004.whiskergalaxy.com", IP: net.IP{45, 121, 208, 160}},
{Region: "Australia", City: "Perth", Hostname: "au-012.whiskergalaxy.com", IP: net.IP{103, 77, 234, 211}},
{Region: "Australia", City: "Sydney", Hostname: "au-009.whiskergalaxy.com", IP: net.IP{103, 77, 233, 67}},
{Region: "Australia", City: "Sydney", Hostname: "au-015.whiskergalaxy.com", IP: net.IP{103, 1, 213, 211}},
{Region: "Austria", City: "Vienna", Hostname: "at-001.whiskergalaxy.com", IP: net.IP{217, 64, 127, 11}},
{Region: "Austria", City: "Vienna", Hostname: "at-002.whiskergalaxy.com", IP: net.IP{89, 187, 168, 66}},
{Region: "Belgium", City: "Brussels", Hostname: "be-001.whiskergalaxy.com", IP: net.IP{194, 187, 251, 147}},
{Region: "Belgium", City: "Brussels", Hostname: "be-002.whiskergalaxy.com", IP: net.IP{185, 232, 21, 131}},
{Region: "Brazil", City: "Sao Paulo", Hostname: "br-004.whiskergalaxy.com", IP: net.IP{177, 67, 80, 59}},
{Region: "Brazil", City: "Sao Paulo", Hostname: "br-005.whiskergalaxy.com", IP: net.IP{177, 54, 157, 178}},
{Region: "Brazil", City: "Sao Paulo", Hostname: "br-006.whiskergalaxy.com", IP: net.IP{177, 54, 148, 247}},
{Region: "Bulgaria", City: "Sofia", Hostname: "bg-001.whiskergalaxy.com", IP: net.IP{185, 94, 192, 35}},
{Region: "Canada East", City: "Halifax", Hostname: "ca-029.whiskergalaxy.com", IP: net.IP{199, 204, 208, 158}},
{Region: "Canada East", City: "Montreal", Hostname: "ca-004.whiskergalaxy.com", IP: net.IP{66, 70, 148, 80}},
{Region: "Canada East", City: "Montreal", Hostname: "ca-027.whiskergalaxy.com", IP: net.IP{144, 168, 163, 160}},
{Region: "Canada East", City: "Montreal", Hostname: "ca-028.whiskergalaxy.com", IP: net.IP{144, 168, 163, 193}},
{Region: "Canada East", City: "Montreal", Hostname: "ca-032.whiskergalaxy.com", IP: net.IP{104, 227, 235, 129}},
{Region: "Canada East", City: "Montreal", Hostname: "ca-033.whiskergalaxy.com", IP: net.IP{198, 8, 85, 195}},
{Region: "Canada East", City: "Montreal", Hostname: "ca-034.whiskergalaxy.com", IP: net.IP{198, 8, 85, 210}},
{Region: "Canada East", City: "Toronto", Hostname: "ca-002.whiskergalaxy.com", IP: net.IP{104, 254, 92, 11}},
{Region: "Canada East", City: "Toronto", Hostname: "ca-009.whiskergalaxy.com", IP: net.IP{104, 254, 92, 91}},
{Region: "Canada East", City: "Toronto", Hostname: "ca-017.whiskergalaxy.com", IP: net.IP{184, 75, 212, 91}},
{Region: "Canada East", City: "Toronto", Hostname: "ca-025.whiskergalaxy.com", IP: net.IP{192, 190, 19, 65}},
{Region: "Canada East", City: "Toronto", Hostname: "ca-026.whiskergalaxy.com", IP: net.IP{192, 190, 19, 97}},
{Region: "Canada East", City: "Toronto", Hostname: "ca-030.whiskergalaxy.com", IP: net.IP{23, 154, 160, 177}},
{Region: "Canada West", City: "Vancouver", Hostname: "ca-west-005.whiskergalaxy.com", IP: net.IP{162, 221, 207, 95}},
{Region: "Canada West", City: "Vancouver", Hostname: "ca-west-011.whiskergalaxy.com", IP: net.IP{104, 218, 61, 1}},
{Region: "Canada West", City: "Vancouver", Hostname: "ca-west-012.whiskergalaxy.com", IP: net.IP{104, 218, 61, 33}},
{Region: "Canada West", City: "Vancouver", Hostname: "ca-west-016.whiskergalaxy.com", IP: net.IP{208, 78, 41, 1}},
{Region: "Canada West", City: "Vancouver", Hostname: "ca-west-017.whiskergalaxy.com", IP: net.IP{208, 78, 41, 131}},
{Region: "Canada West", City: "Vancouver", Hostname: "ca-west-019.whiskergalaxy.com", IP: net.IP{208, 78, 41, 163}},
{Region: "Colombia", City: "Bogota", Hostname: "co-001.whiskergalaxy.com", IP: net.IP{138, 121, 203, 203}},
{Region: "Colombia", City: "Bogota", Hostname: "co-002.whiskergalaxy.com", IP: net.IP{138, 186, 141, 155}},
{Region: "Croatia", City: "Zagreb", Hostname: "hr-002.whiskergalaxy.com", IP: net.IP{85, 10, 56, 129}},
{Region: "Cyprus", City: "Nicosia", Hostname: "cy-001.whiskergalaxy.com", IP: net.IP{157, 97, 132, 43}},
{Region: "Czech Republic", City: "Prague ", Hostname: "cz-002.whiskergalaxy.com", IP: net.IP{185, 246, 210, 2}},
{Region: "Czech Republic", City: "Prague", Hostname: "cz-001.whiskergalaxy.com", IP: net.IP{185, 156, 174, 11}},
{Region: "Denmark", City: "Copenhagen", Hostname: "dk-001.whiskergalaxy.com", IP: net.IP{185, 206, 224, 195}},
{Region: "Denmark", City: "Copenhagen", Hostname: "dk-002.whiskergalaxy.com", IP: net.IP{134, 90, 149, 147}},
{Region: "Estonia", City: "Tallinn", Hostname: "ee-001.whiskergalaxy.com", IP: net.IP{46, 22, 211, 251}},
{Region: "Estonia", City: "Tallinn", Hostname: "ee-002.whiskergalaxy.com", IP: net.IP{196, 196, 216, 131}},
{Region: "Fake Antarctica", City: "Troll", Hostname: "aq-001.whiskergalaxy.com", IP: net.IP{23, 154, 160, 212}},
{Region: "Fake Antarctica", City: "Troll", Hostname: "aq-002.whiskergalaxy.com", IP: net.IP{23, 154, 160, 222}},
{Region: "Finland", City: "Helsinki", Hostname: "fi-002.whiskergalaxy.com", IP: net.IP{185, 112, 82, 227}},
{Region: "Finland", City: "Helsinki", Hostname: "fi-003.whiskergalaxy.com", IP: net.IP{194, 34, 133, 82}},
{Region: "Finland", City: "Helsinki", Hostname: "fi-004.whiskergalaxy.com", IP: net.IP{196, 244, 192, 51}},
{Region: "France", City: "Paris", Hostname: "fr-004.whiskergalaxy.com", IP: net.IP{185, 156, 173, 187}},
{Region: "France", City: "Paris", Hostname: "fr-005.whiskergalaxy.com", IP: net.IP{82, 102, 18, 35}},
{Region: "France", City: "Paris", Hostname: "fr-008.whiskergalaxy.com", IP: net.IP{84, 17, 42, 34}},
{Region: "France", City: "Paris", Hostname: "fr-009.whiskergalaxy.com", IP: net.IP{84, 17, 42, 2}},
{Region: "France", City: "Paris", Hostname: "fr-011.whiskergalaxy.com", IP: net.IP{45, 89, 174, 35}},
{Region: "Germany", City: "Frankfurt", Hostname: "de-003.whiskergalaxy.com", IP: net.IP{89, 249, 65, 19}},
{Region: "Germany", City: "Frankfurt", Hostname: "de-006.whiskergalaxy.com", IP: net.IP{185, 130, 184, 195}},
{Region: "Germany", City: "Frankfurt", Hostname: "de-009.whiskergalaxy.com", IP: net.IP{195, 181, 170, 66}},
{Region: "Germany", City: "Frankfurt", Hostname: "de-010.whiskergalaxy.com", IP: net.IP{195, 181, 175, 98}},
{Region: "Germany", City: "Frankfurt", Hostname: "de-011.whiskergalaxy.com", IP: net.IP{217, 138, 194, 115}},
{Region: "Germany", City: "Frankfurt", Hostname: "de-012.whiskergalaxy.com", IP: net.IP{45, 87, 212, 51}},
{Region: "Greece", City: "Athens", Hostname: "gr-002.whiskergalaxy.com", IP: net.IP{78, 108, 38, 155}},
{Region: "Greece", City: "Athens", Hostname: "gr-004.whiskergalaxy.com", IP: net.IP{185, 226, 64, 111}},
{Region: "Greece", City: "Athens", Hostname: "gr-005.whiskergalaxy.com", IP: net.IP{188, 123, 126, 146}},
{Region: "Hong Kong", City: "Hong Kong", Hostname: "hk-005.whiskergalaxy.com", IP: net.IP{103, 10, 197, 99}},
{Region: "Hong Kong", City: "Hong Kong", Hostname: "hk-006.whiskergalaxy.com", IP: net.IP{84, 17, 57, 114}},
{Region: "Hungary", City: "Budapest", Hostname: "hu-001.whiskergalaxy.com", IP: net.IP{185, 104, 187, 43}},
{Region: "Iceland", City: "Reykjavik", Hostname: "is-001.whiskergalaxy.com", IP: net.IP{82, 221, 139, 38}},
{Region: "Iceland", City: "Reykjavik", Hostname: "is-002.whiskergalaxy.com", IP: net.IP{185, 165, 170, 2}},
{Region: "India", City: "Chennai", Hostname: "in-005.whiskergalaxy.com", IP: net.IP{169, 38, 68, 188}},
{Region: "India", City: "Chennai", Hostname: "in-006.whiskergalaxy.com", IP: net.IP{169, 38, 72, 14}},
{Region: "India", City: "Chennai", Hostname: "in-007.whiskergalaxy.com", IP: net.IP{169, 38, 72, 12}},
{Region: "India", City: "Mumbai", Hostname: "in-009.whiskergalaxy.com", IP: net.IP{165, 231, 253, 211}},
{Region: "India", City: "New Delhi", Hostname: "in-008.whiskergalaxy.com", IP: net.IP{103, 205, 140, 227}},
{Region: "Indonesia", City: "Jakarta", Hostname: "id-002.whiskergalaxy.com", IP: net.IP{45, 127, 134, 91}},
{Region: "Ireland", City: "Dublin", Hostname: "ie-001.whiskergalaxy.com", IP: net.IP{185, 24, 232, 146}},
{Region: "Ireland", City: "Dublin", Hostname: "ie-002.whiskergalaxy.com", IP: net.IP{185, 104, 219, 2}},
{Region: "Ireland", City: "Dublin", Hostname: "ie-003.whiskergalaxy.com", IP: net.IP{23, 92, 127, 35}},
{Region: "Israel", City: "Ashdod", Hostname: "il-002.whiskergalaxy.com", IP: net.IP{185, 191, 205, 139}},
{Region: "Israel", City: "Jerusalem", Hostname: "il-001.whiskergalaxy.com", IP: net.IP{160, 116, 0, 27}},
{Region: "Italy", City: "Milan", Hostname: "it-001.whiskergalaxy.com", IP: net.IP{37, 120, 135, 83}},
{Region: "Italy", City: "Milan", Hostname: "it-004.whiskergalaxy.com", IP: net.IP{84, 17, 59, 66}},
{Region: "Italy", City: "Milan", Hostname: "it-005.whiskergalaxy.com", IP: net.IP{89, 40, 182, 3}},
{Region: "Italy", City: "Rome", Hostname: "it-003.whiskergalaxy.com", IP: net.IP{87, 101, 94, 195}},
{Region: "Italy", City: "Rome", Hostname: "it-006.whiskergalaxy.com", IP: net.IP{37, 120, 207, 19}},
{Region: "Japan", City: "Tokyo", Hostname: "jp-004.whiskergalaxy.com", IP: net.IP{193, 148, 16, 243}},
{Region: "Japan", City: "Tokyo", Hostname: "jp-005.whiskergalaxy.com", IP: net.IP{89, 187, 161, 114}},
{Region: "Latvia", City: "Riga", Hostname: "lv-003.whiskergalaxy.com", IP: net.IP{85, 254, 72, 23}},
{Region: "Latvia", City: "Riga", Hostname: "lv-004.whiskergalaxy.com", IP: net.IP{89, 111, 33, 220}},
{Region: "Lithuania", City: "Siauliai", Hostname: "lt-003.whiskergalaxy.com", IP: net.IP{85, 206, 163, 225}},
{Region: "Malaysia", City: "Kuala Lumpur", Hostname: "my-001.whiskergalaxy.com", IP: net.IP{103, 106, 250, 31}},
{Region: "Malaysia", City: "Kuala Lumpur", Hostname: "my-003.whiskergalaxy.com", IP: net.IP{103, 212, 69, 232}},
{Region: "Mexico", City: "Guadalajara", Hostname: "mx-007.whiskergalaxy.com", IP: net.IP{201, 131, 125, 107}},
{Region: "Mexico", City: "Guadalajara", Hostname: "mx-008.whiskergalaxy.com", IP: net.IP{143, 255, 57, 67}},
{Region: "Mexico", City: "Mexico City", Hostname: "mx-009.whiskergalaxy.com", IP: net.IP{190, 103, 179, 211}},
{Region: "Mexico", City: "Mexico City", Hostname: "mx-010.whiskergalaxy.com", IP: net.IP{190, 103, 179, 217}},
{Region: "Moldova", City: "Chisinau", Hostname: "md-002.whiskergalaxy.com", IP: net.IP{178, 175, 144, 123}},
{Region: "Netherlands", City: "Amsterdam", Hostname: "nl-001.whiskergalaxy.com", IP: net.IP{46, 166, 143, 98}},
{Region: "Netherlands", City: "Amsterdam", Hostname: "nl-005.whiskergalaxy.com", IP: net.IP{185, 212, 171, 131}},
{Region: "Netherlands", City: "Amsterdam", Hostname: "nl-008.whiskergalaxy.com", IP: net.IP{185, 253, 96, 3}},
{Region: "Netherlands", City: "Amsterdam", Hostname: "nl-011.whiskergalaxy.com", IP: net.IP{84, 17, 46, 2}},
{Region: "Netherlands", City: "Amsterdam", Hostname: "nl-012.whiskergalaxy.com", IP: net.IP{37, 120, 192, 19}},
{Region: "Netherlands", City: "Amsterdam", Hostname: "nl-013.whiskergalaxy.com", IP: net.IP{72, 11, 157, 67}},
{Region: "Netherlands", City: "Amsterdam", Hostname: "nl-014.whiskergalaxy.com", IP: net.IP{72, 11, 157, 35}},
{Region: "Netherlands", City: "Amsterdam", Hostname: "nl-015.whiskergalaxy.com", IP: net.IP{109, 201, 130, 2}},
{Region: "New Zealand", City: "Auckland ", Hostname: "nz-003.whiskergalaxy.com", IP: net.IP{103, 108, 94, 163}},
{Region: "New Zealand", City: "Auckland", Hostname: "nz-002.whiskergalaxy.com", IP: net.IP{103, 62, 49, 113}},
{Region: "North Macedonia", City: "Skopje", Hostname: "mk-001.whiskergalaxy.com", IP: net.IP{185, 225, 28, 51}},
{Region: "Norway", City: "Oslo", Hostname: "no-003.whiskergalaxy.com", IP: net.IP{185, 206, 225, 131}},
{Region: "Norway", City: "Oslo", Hostname: "no-006.whiskergalaxy.com", IP: net.IP{37, 120, 203, 67}},
{Region: "Panama", City: "Panama City", Hostname: "pa-001.whiskergalaxy.com", IP: net.IP{138, 186, 142, 203}},
{Region: "Peru", City: "Lima", Hostname: "pe-002.whiskergalaxy.com", IP: net.IP{190, 120, 229, 139}},
{Region: "Philippines", City: "Manila", Hostname: "ph-003.whiskergalaxy.com", IP: net.IP{141, 98, 215, 211}},
{Region: "Philippines", City: "San Antonio", Hostname: "ph-002.whiskergalaxy.com", IP: net.IP{103, 103, 0, 118}},
{Region: "Poland", City: "Warsaw", Hostname: "pl-002.whiskergalaxy.com", IP: net.IP{185, 244, 214, 35}},
{Region: "Poland", City: "Warsaw", Hostname: "pl-004.whiskergalaxy.com", IP: net.IP{84, 17, 55, 98}},
{Region: "Poland", City: "Warsaw", Hostname: "pl-005.whiskergalaxy.com", IP: net.IP{5, 133, 11, 116}},
{Region: "Portugal", City: "Lisbon", Hostname: "pt-002.whiskergalaxy.com", IP: net.IP{94, 46, 13, 215}},
{Region: "Portugal", City: "Lisbon", Hostname: "pt-003.whiskergalaxy.com", IP: net.IP{185, 15, 21, 66}},
{Region: "Romania", City: "Bucharest", Hostname: "ro-006.whiskergalaxy.com", IP: net.IP{89, 46, 103, 147}},
{Region: "Romania", City: "Bucharest", Hostname: "ro-008.whiskergalaxy.com", IP: net.IP{91, 207, 102, 147}},
{Region: "Russia", City: "Moscow", Hostname: "ru-010.whiskergalaxy.com", IP: net.IP{95, 213, 193, 227}},
{Region: "Russia", City: "Moscow", Hostname: "ru-011.whiskergalaxy.com", IP: net.IP{95, 213, 193, 195}},
{Region: "Russia", City: "Saint Petersburg", Hostname: "ru-008.whiskergalaxy.com", IP: net.IP{94, 242, 62, 19}},
{Region: "Russia", City: "Saint Petersburg", Hostname: "ru-009.whiskergalaxy.com", IP: net.IP{94, 242, 62, 67}},
{Region: "Russia", City: "Saint Petersburg", Hostname: "ru-012.whiskergalaxy.com", IP: net.IP{188, 124, 42, 115}},
{Region: "Russia", City: "Saint Petersburg", Hostname: "ru-013.whiskergalaxy.com", IP: net.IP{188, 124, 42, 99}},
{Region: "Serbia", City: "Belgrade", Hostname: "rs-003.whiskergalaxy.com", IP: net.IP{141, 98, 103, 19}},
{Region: "Singapore", City: "Singapore", Hostname: "sg-003.whiskergalaxy.com", IP: net.IP{185, 200, 117, 163}},
{Region: "Singapore", City: "Singapore", Hostname: "sg-004.whiskergalaxy.com", IP: net.IP{82, 102, 25, 131}},
{Region: "Singapore", City: "Singapore", Hostname: "sg-005.whiskergalaxy.com", IP: net.IP{103, 62, 48, 224}},
{Region: "Singapore", City: "Singapore", Hostname: "sg-006.whiskergalaxy.com", IP: net.IP{156, 146, 56, 98}},
{Region: "Singapore", City: "Singapore", Hostname: "sg-007.whiskergalaxy.com", IP: net.IP{156, 146, 56, 111}},
{Region: "Slovakia", City: "Bratislava", Hostname: "sk-001.whiskergalaxy.com", IP: net.IP{185, 245, 85, 3}},
{Region: "South Africa", City: "Johannesburg", Hostname: "za-001.whiskergalaxy.com", IP: net.IP{197, 242, 157, 235}},
{Region: "South Africa", City: "Johannesburg", Hostname: "za-002.whiskergalaxy.com", IP: net.IP{129, 232, 167, 211}},
{Region: "South Africa", City: "Johannesburg", Hostname: "za-003.whiskergalaxy.com", IP: net.IP{197, 242, 156, 53}},
{Region: "South Africa", City: "Johannesburg", Hostname: "za-004.whiskergalaxy.com", IP: net.IP{165, 73, 248, 91}},
{Region: "South Korea", City: "Seoul", Hostname: "kr-001.whiskergalaxy.com", IP: net.IP{103, 212, 223, 3}},
{Region: "South Korea", City: "Seoul", Hostname: "kr-002.whiskergalaxy.com", IP: net.IP{218, 232, 76, 179}},
{Region: "South Korea", City: "Seoul", Hostname: "kr-005.whiskergalaxy.com", IP: net.IP{45, 133, 194, 235}},
{Region: "Spain", City: "Barcelona", Hostname: "es-004.whiskergalaxy.com", IP: net.IP{37, 120, 142, 227}},
{Region: "Spain", City: "Madrid", Hostname: "es-002.whiskergalaxy.com", IP: net.IP{89, 238, 178, 43}},
{Region: "Spain", City: "Madrid", Hostname: "es-003.whiskergalaxy.com", IP: net.IP{217, 138, 218, 99}},
{Region: "Sweden", City: "Stockholm", Hostname: "se-001.whiskergalaxy.com", IP: net.IP{31, 13, 191, 67}},
{Region: "Sweden", City: "Stockholm", Hostname: "se-002.whiskergalaxy.com", IP: net.IP{79, 142, 76, 198}},
{Region: "Sweden", City: "Stockholm", Hostname: "se-003.whiskergalaxy.com", IP: net.IP{195, 181, 166, 129}},
{Region: "Switzerland", City: "Zurich", Hostname: "ch-001.whiskergalaxy.com", IP: net.IP{31, 7, 57, 242}},
{Region: "Switzerland", City: "Zurich", Hostname: "ch-003.whiskergalaxy.com", IP: net.IP{185, 156, 175, 179}},
{Region: "Switzerland", City: "Zurich", Hostname: "ch-005.whiskergalaxy.com", IP: net.IP{89, 187, 165, 98}},
{Region: "Switzerland", City: "Zurich", Hostname: "ch-006.whiskergalaxy.com", IP: net.IP{84, 17, 53, 2}},
{Region: "Switzerland", City: "Zurich", Hostname: "ch-008.whiskergalaxy.com", IP: net.IP{37, 120, 213, 163}},
{Region: "Taiwan", City: "Taipei", Hostname: "tw-008.whiskergalaxy.com", IP: net.IP{103, 4, 29, 77}},
{Region: "Taiwan", City: "Taipei", Hostname: "tw-009.whiskergalaxy.com", IP: net.IP{185, 189, 160, 12}},
{Region: "Taiwan", City: "Taipei", Hostname: "tw-010.whiskergalaxy.com", IP: net.IP{185, 189, 160, 27}},
{Region: "Taiwan", City: "Taipei", Hostname: "tw-011.whiskergalaxy.com", IP: net.IP{185, 189, 160, 32}},
{Region: "Thailand", City: "Bangkok", Hostname: "th-003.whiskergalaxy.com", IP: net.IP{27, 254, 130, 221}},
{Region: "Thailand", City: "Bangkok", Hostname: "th-005.whiskergalaxy.com", IP: net.IP{202, 129, 16, 147}},
{Region: "Thailand", City: "Bangkok", Hostname: "th-006.whiskergalaxy.com", IP: net.IP{202, 129, 16, 155}},
{Region: "Tunisia", City: "Tunis", Hostname: "tn-001.whiskergalaxy.com", IP: net.IP{41, 231, 5, 23}},
{Region: "Turkey", City: "Bursa", Hostname: "tr-001.whiskergalaxy.com", IP: net.IP{45, 123, 118, 156}},
{Region: "Turkey", City: "Istanbul", Hostname: "tr-004.whiskergalaxy.com", IP: net.IP{45, 123, 119, 11}},
{Region: "Turkey", City: "Istanbul", Hostname: "tr-006.whiskergalaxy.com", IP: net.IP{185, 125, 33, 227}},
{Region: "Turkey", City: "Istanbul", Hostname: "tr-009.whiskergalaxy.com", IP: net.IP{79, 98, 131, 43}},
{Region: "Turkey", City: "Istanbul", Hostname: "tr-011.whiskergalaxy.com", IP: net.IP{176, 53, 113, 163}},
{Region: "US Central", City: "Atlanta", Hostname: "us-central-016.whiskergalaxy.com", IP: net.IP{104, 129, 18, 3}},
{Region: "US Central", City: "Atlanta", Hostname: "us-central-020.whiskergalaxy.com", IP: net.IP{104, 129, 18, 131}},
{Region: "US Central", City: "Atlanta", Hostname: "us-central-034.whiskergalaxy.com", IP: net.IP{161, 129, 70, 195}},
{Region: "US Central", City: "Atlanta", Hostname: "us-central-046.whiskergalaxy.com", IP: net.IP{198, 12, 76, 211}},
{Region: "US Central", City: "Atlanta", Hostname: "us-central-049.whiskergalaxy.com", IP: net.IP{107, 150, 31, 3}},
{Region: "US Central", City: "Atlanta", Hostname: "us-central-050.whiskergalaxy.com", IP: net.IP{107, 150, 31, 67}},
{Region: "US Central", City: "Atlanta", Hostname: "us-central-051.whiskergalaxy.com", IP: net.IP{162, 222, 198, 67}},
{Region: "US Central", City: "Atlanta", Hostname: "us-central-054.whiskergalaxy.com", IP: net.IP{104, 223, 92, 163}},
{Region: "US Central", City: "Atlanta", Hostname: "us-central-056.whiskergalaxy.com", IP: net.IP{206, 217, 143, 131}},
{Region: "US Central", City: "Dallas", Hostname: "us-central-014.whiskergalaxy.com", IP: net.IP{69, 12, 94, 67}},
{Region: "US Central", City: "Dallas", Hostname: "us-central-029.whiskergalaxy.com", IP: net.IP{198, 55, 125, 195}},
{Region: "US Central", City: "Dallas", Hostname: "us-central-036.whiskergalaxy.com", IP: net.IP{204, 44, 112, 67}},
{Region: "US Central", City: "Dallas", Hostname: "us-central-037.whiskergalaxy.com", IP: net.IP{204, 44, 112, 131}},
{Region: "US Central", City: "Dallas", Hostname: "us-central-044.whiskergalaxy.com", IP: net.IP{206, 217, 139, 195}},
{Region: "US Central", City: "Dallas", Hostname: "us-central-045.whiskergalaxy.com", IP: net.IP{172, 241, 131, 129}},
{Region: "US Central", City: "Dallas", Hostname: "us-central-055.whiskergalaxy.com", IP: net.IP{206, 217, 139, 19}},
{Region: "US Central", City: "Dallas", Hostname: "us-central-057.whiskergalaxy.com", IP: net.IP{172, 241, 26, 78}},
{Region: "US Central", City: "Dallas", Hostname: "us-central-060.whiskergalaxy.com", IP: net.IP{198, 55, 126, 131}},
{Region: "US Central", City: "Denver", Hostname: "us-central-043.whiskergalaxy.com", IP: net.IP{199, 115, 96, 83}},
{Region: "US Central", City: "Denver", Hostname: "us-central-058.whiskergalaxy.com", IP: net.IP{198, 54, 128, 116}},
{Region: "US Central", City: "Denver", Hostname: "us-central-062.whiskergalaxy.com", IP: net.IP{174, 128, 251, 147}},
{Region: "US Central", City: "Kansas City", Hostname: "us-central-063.whiskergalaxy.com", IP: net.IP{38, 146, 5, 51}},
{Region: "US Central", City: "Salt Lake City", Hostname: "us-central-047.whiskergalaxy.com", IP: net.IP{107, 182, 234, 240}},
{Region: "US Central", City: "Salt Lake City", Hostname: "us-central-052.whiskergalaxy.com", IP: net.IP{67, 212, 238, 196}},
{Region: "US East", City: "Boston", Hostname: "us-east-039.whiskergalaxy.com", IP: net.IP{199, 217, 104, 227}},
{Region: "US East", City: "Boston", Hostname: "us-east-051.whiskergalaxy.com", IP: net.IP{199, 217, 105, 227}},
{Region: "US East", City: "Buffalo", Hostname: "us-east-045.whiskergalaxy.com", IP: net.IP{104, 168, 34, 147}},
{Region: "US East", City: "Buffalo", Hostname: "us-east-065.whiskergalaxy.com", IP: net.IP{198, 12, 64, 35}},
{Region: "US East", City: "Charlotte", Hostname: "us-east-040.whiskergalaxy.com", IP: net.IP{67, 21, 32, 145}},
{Region: "US East", City: "Chicago", Hostname: "us-east-015.whiskergalaxy.com", IP: net.IP{68, 235, 50, 227}},
{Region: "US East", City: "Chicago", Hostname: "us-east-019.whiskergalaxy.com", IP: net.IP{23, 226, 141, 195}},
{Region: "US East", City: "Chicago", Hostname: "us-east-022.whiskergalaxy.com", IP: net.IP{167, 160, 172, 3}},
{Region: "US East", City: "Chicago", Hostname: "us-east-047.whiskergalaxy.com", IP: net.IP{23, 83, 91, 170}},
{Region: "US East", City: "Chicago", Hostname: "us-east-053.whiskergalaxy.com", IP: net.IP{107, 150, 29, 131}},
{Region: "US East", City: "Chicago", Hostname: "us-east-069.whiskergalaxy.com", IP: net.IP{68, 235, 35, 172}},
{Region: "US East", City: "Chicago", Hostname: "us-east-071.whiskergalaxy.com", IP: net.IP{68, 235, 35, 12}},
{Region: "US East", City: "Chicago", Hostname: "us-east-077.whiskergalaxy.com", IP: net.IP{68, 235, 43, 204}},
{Region: "US East", City: "Cleveland", Hostname: "us-east-078.whiskergalaxy.com", IP: net.IP{38, 101, 74, 19}},
{Region: "US East", City: "Columbus", Hostname: "us-east-059.whiskergalaxy.com", IP: net.IP{67, 219, 146, 67}},
{Region: "US East", City: "Detroit", Hostname: "us-east-079.whiskergalaxy.com", IP: net.IP{104, 244, 210, 51}},
{Region: "US East", City: "Miami", Hostname: "us-east-006.whiskergalaxy.com", IP: net.IP{173, 44, 36, 67}},
{Region: "US East", City: "Miami", Hostname: "us-east-012.whiskergalaxy.com", IP: net.IP{45, 87, 214, 35}},
{Region: "US East", City: "Miami", Hostname: "us-east-028.whiskergalaxy.com", IP: net.IP{104, 223, 127, 195}},
{Region: "US East", City: "Miami", Hostname: "us-east-049.whiskergalaxy.com", IP: net.IP{23, 82, 136, 93}},
{Region: "US East", City: "Miami", Hostname: "us-east-067.whiskergalaxy.com", IP: net.IP{86, 106, 87, 83}},
{Region: "US East", City: "New Jersey", Hostname: "us-east-020.whiskergalaxy.com", IP: net.IP{162, 222, 195, 67}},
{Region: "US East", City: "New Jersey", Hostname: "us-east-054.whiskergalaxy.com", IP: net.IP{167, 160, 167, 195}},
{Region: "US East", City: "New York", Hostname: "us-east-013.whiskergalaxy.com", IP: net.IP{185, 232, 22, 195}},
{Region: "US East", City: "New York", Hostname: "us-east-046.whiskergalaxy.com", IP: net.IP{206, 217, 129, 227}},
{Region: "US East", City: "New York", Hostname: "us-east-050.whiskergalaxy.com", IP: net.IP{173, 208, 45, 33}},
{Region: "US East", City: "New York", Hostname: "us-east-064.whiskergalaxy.com", IP: net.IP{206, 217, 128, 3}},
{Region: "US East", City: "New York", Hostname: "us-east-068.whiskergalaxy.com", IP: net.IP{142, 234, 200, 176}},
{Region: "US East", City: "New York", Hostname: "us-east-073.whiskergalaxy.com", IP: net.IP{217, 138, 255, 163}},
{Region: "US East", City: "New York", Hostname: "us-east-074.whiskergalaxy.com", IP: net.IP{217, 138, 255, 179}},
{Region: "US East", City: "Orlando", Hostname: "us-east-052.whiskergalaxy.com", IP: net.IP{198, 147, 22, 225}},
{Region: "US East", City: "Philadelphia", Hostname: "us-east-060.whiskergalaxy.com", IP: net.IP{76, 72, 175, 99}},
{Region: "US East", City: "Philadelphia", Hostname: "us-east-061.whiskergalaxy.com", IP: net.IP{156, 96, 59, 102}},
{Region: "US East", City: "Washington DC", Hostname: "us-east-048.whiskergalaxy.com", IP: net.IP{23, 82, 8, 143}},
{Region: "US East", City: "Washington DC", Hostname: "us-east-055.whiskergalaxy.com", IP: net.IP{23, 105, 170, 139}},
{Region: "US East", City: "Washington DC", Hostname: "us-east-057.whiskergalaxy.com", IP: net.IP{23, 105, 170, 130}},
{Region: "US East", City: "Washington DC", Hostname: "us-east-058.whiskergalaxy.com", IP: net.IP{23, 105, 170, 151}},
{Region: "US West", City: "Bend", Hostname: "us-west-038.whiskergalaxy.com", IP: net.IP{104, 152, 222, 33}},
{Region: "US West", City: "Las Vegas", Hostname: "us-west-018.whiskergalaxy.com", IP: net.IP{82, 102, 30, 67}},
{Region: "US West", City: "Las Vegas", Hostname: "us-west-030.whiskergalaxy.com", IP: net.IP{37, 120, 147, 163}},
{Region: "US West", City: "Los Angeles", Hostname: "us-west-004.whiskergalaxy.com", IP: net.IP{185, 236, 200, 35}},
{Region: "US West", City: "Los Angeles", Hostname: "us-west-015.whiskergalaxy.com", IP: net.IP{216, 45, 53, 131}},
{Region: "US West", City: "Los Angeles", Hostname: "us-west-027.whiskergalaxy.com", IP: net.IP{212, 103, 49, 67}},
{Region: "US West", City: "Los Angeles", Hostname: "us-west-040.whiskergalaxy.com", IP: net.IP{89, 187, 185, 34}},
{Region: "US West", City: "Los Angeles", Hostname: "us-west-044.whiskergalaxy.com", IP: net.IP{192, 3, 20, 51}},
{Region: "US West", City: "Los Angeles", Hostname: "us-west-047.whiskergalaxy.com", IP: net.IP{172, 241, 214, 202}},
{Region: "US West", City: "Los Angeles", Hostname: "us-west-055.whiskergalaxy.com", IP: net.IP{104, 129, 3, 67}},
{Region: "US West", City: "Los Angeles", Hostname: "us-west-059.whiskergalaxy.com", IP: net.IP{104, 129, 3, 163}},
{Region: "US West", City: "Los Angeles", Hostname: "us-west-060.whiskergalaxy.com", IP: net.IP{217, 138, 217, 51}},
{Region: "US West", City: "Los Angeles", Hostname: "us-west-063.whiskergalaxy.com", IP: net.IP{198, 23, 242, 147}},
{Region: "US West", City: "Los Angeles", Hostname: "us-west-065.whiskergalaxy.com", IP: net.IP{217, 138, 217, 211}},
{Region: "US West", City: "Los Angeles", Hostname: "us-west-066.whiskergalaxy.com", IP: net.IP{89, 187, 187, 98}},
{Region: "US West", City: "Phoenix", Hostname: "us-west-046.whiskergalaxy.com", IP: net.IP{23, 83, 130, 166}},
{Region: "US West", City: "Phoenix", Hostname: "us-west-061.whiskergalaxy.com", IP: net.IP{23, 83, 131, 187}},
{Region: "US West", City: "San Francisco", Hostname: "us-west-048.whiskergalaxy.com", IP: net.IP{172, 241, 250, 131}},
{Region: "US West", City: "San Francisco", Hostname: "us-west-053.whiskergalaxy.com", IP: net.IP{209, 58, 129, 121}},
{Region: "US West", City: "San Francisco", Hostname: "us-west-054.whiskergalaxy.com", IP: net.IP{172, 255, 125, 141}},
{Region: "US West", City: "San Jose", Hostname: "us-west-052.whiskergalaxy.com", IP: net.IP{66, 115, 176, 3}},
{Region: "US West", City: "Santa Clara", Hostname: "us-west-050.whiskergalaxy.com", IP: net.IP{167, 88, 60, 227}},
{Region: "US West", City: "Santa Clara", Hostname: "us-west-051.whiskergalaxy.com", IP: net.IP{167, 88, 60, 243}},
{Region: "US West", City: "Seattle", Hostname: "us-west-043.whiskergalaxy.com", IP: net.IP{23, 94, 74, 99}},
{Region: "US West", City: "Seattle", Hostname: "us-west-045.whiskergalaxy.com", IP: net.IP{64, 120, 2, 174}},
{Region: "US West", City: "Seattle", Hostname: "us-west-056.whiskergalaxy.com", IP: net.IP{104, 129, 56, 67}},
{Region: "US West", City: "Seattle", Hostname: "us-west-057.whiskergalaxy.com", IP: net.IP{104, 129, 56, 131}},
{Region: "US West", City: "Seattle", Hostname: "us-west-062.whiskergalaxy.com", IP: net.IP{198, 12, 116, 195}},
{Region: "Ukraine", City: "Kyiv", Hostname: "ua-006.whiskergalaxy.com", IP: net.IP{45, 141, 156, 11}},
{Region: "Ukraine", City: "Kyiv", Hostname: "ua-007.whiskergalaxy.com", IP: net.IP{45, 141, 156, 50}},
{Region: "United Arab Emirates", City: "Dubai", Hostname: "ae-001.whiskergalaxy.com", IP: net.IP{45, 9, 249, 43}},
{Region: "United Kingdom", City: "Edinburgh", Hostname: "uk-026.whiskergalaxy.com", IP: net.IP{193, 36, 118, 243}},
{Region: "United Kingdom", City: "London", Hostname: "uk-007.whiskergalaxy.com", IP: net.IP{185, 212, 168, 133}},
{Region: "United Kingdom", City: "London", Hostname: "uk-013.whiskergalaxy.com", IP: net.IP{89, 238, 150, 229}},
{Region: "United Kingdom", City: "London", Hostname: "uk-014.whiskergalaxy.com", IP: net.IP{2, 58, 29, 145}},
{Region: "United Kingdom", City: "London", Hostname: "uk-015.whiskergalaxy.com", IP: net.IP{2, 58, 29, 17}},
{Region: "United Kingdom", City: "London", Hostname: "uk-017.whiskergalaxy.com", IP: net.IP{84, 17, 50, 130}},
{Region: "United Kingdom", City: "London", Hostname: "uk-021.whiskergalaxy.com", IP: net.IP{212, 102, 63, 32}},
{Region: "United Kingdom", City: "London", Hostname: "uk-022.whiskergalaxy.com", IP: net.IP{212, 102, 63, 62}},
{Region: "United Kingdom", City: "London", Hostname: "uk-024.whiskergalaxy.com", IP: net.IP{217, 138, 254, 51}},
{Region: "United Kingdom", City: "Manchester", Hostname: "uk-008.whiskergalaxy.com", IP: net.IP{81, 92, 207, 69}},
{Region: "United Kingdom", City: "Manchester", Hostname: "uk-010.whiskergalaxy.com", IP: net.IP{89, 238, 135, 133}},
{Region: "United Kingdom", City: "Manchester", Hostname: "uk-025.whiskergalaxy.com", IP: net.IP{89, 44, 201, 99}},
{Region: "Vietnam", City: "Hanoi", Hostname: "vn-001.whiskergalaxy.com", IP: net.IP{103, 9, 76, 197}},
{Region: "Vietnam", City: "Hanoi", Hostname: "vn-002.whiskergalaxy.com", IP: net.IP{103, 9, 79, 186}},
{Region: "Vietnam", City: "Hanoi", Hostname: "vn-003.whiskergalaxy.com", IP: net.IP{103, 9, 79, 219}},
{Region: "WINDFLIX CA", City: "Toronto", Hostname: "wf-ca-003.whiskergalaxy.com", IP: net.IP{104, 218, 60, 111}},
{Region: "WINDFLIX CA", City: "Toronto", Hostname: "wf-ca-004.whiskergalaxy.com", IP: net.IP{104, 254, 92, 99}},
{Region: "WINDFLIX JP", City: "Tokyo", Hostname: "wf-jp-002.whiskergalaxy.com", IP: net.IP{5, 181, 235, 67}},
{Region: "WINDFLIX UK", City: "London", Hostname: "wf-uk-001.whiskergalaxy.com", IP: net.IP{45, 9, 248, 3}},
{Region: "WINDFLIX UK", City: "London", Hostname: "wf-uk-006.whiskergalaxy.com", IP: net.IP{81, 92, 200, 85}},
{Region: "WINDFLIX UK", City: "London", Hostname: "wf-uk-007.whiskergalaxy.com", IP: net.IP{89, 47, 62, 83}},
{Region: "WINDFLIX US", City: "New York", Hostname: "wf-us-010.whiskergalaxy.com", IP: net.IP{38, 132, 122, 195}},
{Region: "WINDFLIX US", City: "New York", Hostname: "wf-us-011.whiskergalaxy.com", IP: net.IP{38, 132, 122, 131}},
{Region: "WINDFLIX US", City: "New York", Hostname: "wf-us-012.whiskergalaxy.com", IP: net.IP{185, 232, 22, 131}},
{Region: "WINDFLIX US", City: "New York", Hostname: "wf-us-013.whiskergalaxy.com", IP: net.IP{217, 138, 206, 211}},
{Region: "WINDFLIX US", City: "New York", Hostname: "wf-us-014.whiskergalaxy.com", IP: net.IP{77, 81, 136, 99}},
{Region: "WINDFLIX US", City: "New York", Hostname: "wf-us-015.whiskergalaxy.com", IP: net.IP{38, 132, 101, 211}},
}
}

View File

@@ -1,40 +0,0 @@
package dns
import (
"fmt"
"io"
"strings"
"github.com/qdm12/private-internet-access-docker/internal/constants"
)
func (c *configurator) Start(verbosityDetailsLevel uint8) (stdout io.ReadCloser, waitFn func() error, err error) {
c.logger.Info("%s: starting unbound", logPrefix)
args := []string{"-d", "-c", string(constants.UnboundConf)}
if verbosityDetailsLevel > 0 {
args = append(args, "-"+strings.Repeat("v", int(verbosityDetailsLevel)))
}
// Only logs to stderr
_, stdout, waitFn, err = c.commander.Start("unbound", args...)
return stdout, waitFn, err
}
func (c *configurator) Version() (version string, err error) {
output, err := c.commander.Run("unbound", "-V")
if err != nil {
return "", fmt.Errorf("unbound version: %w", err)
}
for _, line := range strings.Split(output, "\n") {
if strings.Contains(line, "Version ") {
words := strings.Fields(line)
if len(words) < 2 {
continue
}
version = words[1]
}
}
if version == "" {
return "", fmt.Errorf("unbound version was not found in %q", output)
}
return version, nil
}

View File

@@ -1,70 +0,0 @@
package dns
import (
"fmt"
"testing"
commandMocks "github.com/qdm12/golibs/command/mocks"
loggingMocks "github.com/qdm12/golibs/logging/mocks"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"github.com/qdm12/private-internet-access-docker/internal/constants"
)
func Test_Start(t *testing.T) {
t.Parallel()
logger := &loggingMocks.Logger{}
logger.On("Info", "%s: starting unbound", logPrefix).Once()
commander := &commandMocks.Commander{}
commander.On("Start", "unbound", "-d", "-c", string(constants.UnboundConf), "-vv").
Return(nil, nil, nil, nil).Once()
c := &configurator{commander: commander, logger: logger}
stdout, waitFn, err := c.Start(2)
assert.Nil(t, stdout)
assert.Nil(t, waitFn)
assert.NoError(t, err)
logger.AssertExpectations(t)
commander.AssertExpectations(t)
}
func Test_Version(t *testing.T) {
t.Parallel()
tests := map[string]struct {
runOutput string
runErr error
version string
err error
}{
"no data": {
err: fmt.Errorf(`unbound version was not found in ""`),
},
"2 lines with version": {
runOutput: "Version \nVersion 1.0-a hello\n",
version: "1.0-a",
},
"run error": {
runErr: fmt.Errorf("error"),
err: fmt.Errorf("unbound version: error"),
},
}
for name, tc := range tests {
tc := tc
t.Run(name, func(t *testing.T) {
t.Parallel()
commander := &commandMocks.Commander{}
commander.On("Run", "unbound", "-V").
Return(tc.runOutput, tc.runErr).Once()
c := &configurator{commander: commander}
version, err := c.Version()
if tc.err != nil {
require.Error(t, err)
assert.Equal(t, tc.err.Error(), err.Error())
} else {
assert.NoError(t, err)
}
assert.Equal(t, tc.version, version)
commander.AssertExpectations(t)
})
}
}

View File

@@ -1,288 +0,0 @@
package dns
import (
"fmt"
"sort"
"strings"
"github.com/qdm12/golibs/files"
"github.com/qdm12/golibs/logging"
"github.com/qdm12/golibs/network"
"github.com/qdm12/private-internet-access-docker/internal/constants"
"github.com/qdm12/private-internet-access-docker/internal/settings"
)
func (c *configurator) MakeUnboundConf(settings settings.DNS, uid, gid int) (err error) {
c.logger.Info("%s: generating Unbound configuration", logPrefix)
lines, warnings, err := generateUnboundConf(settings, c.client, c.logger)
for _, warning := range warnings {
c.logger.Warn(warning)
}
if err != nil {
return err
}
return c.fileManager.WriteLinesToFile(
string(constants.UnboundConf),
lines,
files.Ownership(uid, gid),
files.Permissions(0400))
}
// MakeUnboundConf generates an Unbound configuration from the user provided settings
func generateUnboundConf(settings settings.DNS, client network.Client, logger logging.Logger) (lines []string, warnings []error, err error) {
serverSection := map[string]string{
// Logging
"verbosity": fmt.Sprintf("%d", settings.VerbosityLevel),
"val-log-level": fmt.Sprintf("%d", settings.ValidationLogLevel),
"use-syslog": "no",
// Performance
"num-threads": "1",
"prefetch": "yes",
"prefetch-key": "yes",
"key-cache-size": "16m",
"key-cache-slabs": "4",
"msg-cache-size": "4m",
"msg-cache-slabs": "4",
"rrset-cache-size": "4m",
"rrset-cache-slabs": "4",
"cache-min-ttl": "3600",
"cache-max-ttl": "9000",
// Privacy
"rrset-roundrobin": "yes",
"hide-identity": "yes",
"hide-version": "yes",
// Security
"tls-cert-bundle": fmt.Sprintf("%q", constants.CACertificates),
"root-hints": fmt.Sprintf("%q", constants.RootHints),
"trust-anchor-file": fmt.Sprintf("%q", constants.RootKey),
"harden-below-nxdomain": "yes",
"harden-referral-path": "yes",
"harden-algo-downgrade": "yes",
// Network
"do-ip4": "yes",
"do-ip6": "no",
"interface": "127.0.0.1",
"port": "53",
// Other
"username": "\"nonrootuser\"",
}
// Block lists
hostnamesLines, ipsLines, warnings := buildBlocked(client,
settings.BlockMalicious, settings.BlockAds, settings.BlockSurveillance,
settings.AllowedHostnames, settings.PrivateAddresses,
)
logger.Info("%s: %d hostnames blocked overall", logPrefix, len(hostnamesLines))
logger.Info("%s: %d IP addresses blocked overall", logPrefix, len(ipsLines))
sort.Slice(hostnamesLines, func(i, j int) bool { // for unit tests really
return hostnamesLines[i] < hostnamesLines[j]
})
sort.Slice(ipsLines, func(i, j int) bool { // for unit tests really
return ipsLines[i] < ipsLines[j]
})
// Server
lines = append(lines, "server:")
var serverLines []string
for k, v := range serverSection {
serverLines = append(serverLines, " "+k+": "+v)
}
sort.Slice(serverLines, func(i, j int) bool {
return serverLines[i] < serverLines[j]
})
lines = append(lines, serverLines...)
lines = append(lines, hostnamesLines...)
lines = append(lines, ipsLines...)
// Forward zone
lines = append(lines, "forward-zone:")
forwardZoneSection := map[string]string{
"name": "\".\"",
"forward-tls-upstream": "yes",
}
if settings.Caching {
forwardZoneSection["forward-no-cache"] = "no"
} else {
forwardZoneSection["forward-no-cache"] = "yes"
}
var forwardZoneLines []string
for k, v := range forwardZoneSection {
forwardZoneLines = append(forwardZoneLines, " "+k+": "+v)
}
sort.Slice(forwardZoneLines, func(i, j int) bool {
return forwardZoneLines[i] < forwardZoneLines[j]
})
for _, provider := range settings.Providers {
providerData, ok := constants.DNSProviderMapping()[provider]
if !ok {
return nil, warnings, fmt.Errorf("DNS provider %q does not have associated data", provider)
} else if !providerData.SupportsTLS {
return nil, warnings, fmt.Errorf("DNS provider %q does not support DNS over TLS", provider)
}
for _, IP := range providerData.IPs {
forwardZoneLines = append(forwardZoneLines,
fmt.Sprintf(" forward-addr: %s@853#%s", IP.String(), providerData.Host))
}
}
lines = append(lines, forwardZoneLines...)
return lines, warnings, nil
}
func buildBlocked(client network.Client, blockMalicious, blockAds, blockSurveillance bool,
allowedHostnames, privateAddresses []string) (hostnamesLines, ipsLines []string, errs []error) {
chHostnames := make(chan []string)
chIPs := make(chan []string)
chErrors := make(chan []error)
go func() {
lines, errs := buildBlockedHostnames(client, blockMalicious, blockAds, blockSurveillance, allowedHostnames)
chHostnames <- lines
chErrors <- errs
}()
go func() {
lines, errs := buildBlockedIPs(client, blockMalicious, blockAds, blockSurveillance, privateAddresses)
chIPs <- lines
chErrors <- errs
}()
n := 2
for n > 0 {
select {
case lines := <-chHostnames:
hostnamesLines = append(hostnamesLines, lines...)
case lines := <-chIPs:
ipsLines = append(ipsLines, lines...)
case routineErrs := <-chErrors:
errs = append(errs, routineErrs...)
n--
}
}
return hostnamesLines, ipsLines, errs
}
func getList(client network.Client, URL string) (results []string, err error) {
content, status, err := client.GetContent(URL)
if err != nil {
return nil, err
} else if status != 200 {
return nil, fmt.Errorf("HTTP status code is %d and not 200", status)
}
results = strings.Split(string(content), "\n")
// remove empty lines
last := len(results) - 1
for i := range results {
if len(results[i]) == 0 {
results[i] = results[last]
last--
}
}
results = results[:last+1]
if len(results) == 0 {
return nil, nil
}
return results, nil
}
func buildBlockedHostnames(client network.Client, blockMalicious, blockAds, blockSurveillance bool,
allowedHostnames []string) (lines []string, errs []error) {
chResults := make(chan []string)
chError := make(chan error)
listsLeftToFetch := 0
if blockMalicious {
listsLeftToFetch++
go func() {
results, err := getList(client, string(constants.MaliciousBlockListHostnamesURL))
chResults <- results
chError <- err
}()
}
if blockAds {
listsLeftToFetch++
go func() {
results, err := getList(client, string(constants.AdsBlockListHostnamesURL))
chResults <- results
chError <- err
}()
}
if blockSurveillance {
listsLeftToFetch++
go func() {
results, err := getList(client, string(constants.SurveillanceBlockListHostnamesURL))
chResults <- results
chError <- err
}()
}
uniqueResults := make(map[string]struct{})
for listsLeftToFetch > 0 {
select {
case results := <-chResults:
for _, result := range results {
uniqueResults[result] = struct{}{}
}
case err := <-chError:
listsLeftToFetch--
if err != nil {
errs = append(errs, err)
}
}
}
for _, allowedHostname := range allowedHostnames {
delete(uniqueResults, allowedHostname)
}
for result := range uniqueResults {
lines = append(lines, " local-zone: \""+result+"\" static")
}
return lines, errs
}
func buildBlockedIPs(client network.Client, blockMalicious, blockAds, blockSurveillance bool,
privateAddresses []string) (lines []string, errs []error) {
chResults := make(chan []string)
chError := make(chan error)
listsLeftToFetch := 0
if blockMalicious {
listsLeftToFetch++
go func() {
results, err := getList(client, string(constants.MaliciousBlockListIPsURL))
chResults <- results
chError <- err
}()
}
if blockAds {
listsLeftToFetch++
go func() {
results, err := getList(client, string(constants.AdsBlockListIPsURL))
chResults <- results
chError <- err
}()
}
if blockSurveillance {
listsLeftToFetch++
go func() {
results, err := getList(client, string(constants.SurveillanceBlockListIPsURL))
chResults <- results
chError <- err
}()
}
uniqueResults := make(map[string]struct{})
for listsLeftToFetch > 0 {
select {
case results := <-chResults:
for _, result := range results {
uniqueResults[result] = struct{}{}
}
case err := <-chError:
listsLeftToFetch--
if err != nil {
errs = append(errs, err)
}
}
}
for _, privateAddress := range privateAddresses {
uniqueResults[privateAddress] = struct{}{}
}
for result := range uniqueResults {
lines = append(lines, " private-address: "+result)
}
return lines, errs
}

View File

@@ -1,520 +0,0 @@
package dns
import (
"fmt"
"strings"
"testing"
"github.com/qdm12/golibs/logging"
"github.com/qdm12/golibs/network/mocks"
"github.com/qdm12/private-internet-access-docker/internal/constants"
"github.com/qdm12/private-internet-access-docker/internal/models"
"github.com/qdm12/private-internet-access-docker/internal/settings"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func Test_generateUnboundConf(t *testing.T) {
t.Parallel()
settings := settings.DNS{
Providers: []models.DNSProvider{constants.Cloudflare, constants.Quad9},
AllowedHostnames: []string{"a"},
PrivateAddresses: []string{"9.9.9.9"},
BlockMalicious: true,
BlockSurveillance: false,
BlockAds: false,
VerbosityLevel: 2,
ValidationLogLevel: 3,
Caching: true,
}
client := &mocks.Client{}
client.On("GetContent", string(constants.MaliciousBlockListHostnamesURL)).
Return([]byte("b\na\nc"), 200, nil).Once()
client.On("GetContent", string(constants.MaliciousBlockListIPsURL)).
Return([]byte("c\nd\n"), 200, nil).Once()
emptyLogger, err := logging.NewEmptyLogger()
require.NoError(t, err)
lines, warnings, err := generateUnboundConf(settings, client, emptyLogger)
require.Len(t, warnings, 0)
require.NoError(t, err)
client.AssertExpectations(t)
expected := `
server:
cache-max-ttl: 9000
cache-min-ttl: 3600
do-ip4: yes
do-ip6: no
harden-algo-downgrade: yes
harden-below-nxdomain: yes
harden-referral-path: yes
hide-identity: yes
hide-version: yes
interface: 127.0.0.1
key-cache-size: 16m
key-cache-slabs: 4
msg-cache-size: 4m
msg-cache-slabs: 4
num-threads: 1
port: 53
prefetch-key: yes
prefetch: yes
root-hints: "/etc/unbound/root.hints"
rrset-cache-size: 4m
rrset-cache-slabs: 4
rrset-roundrobin: yes
tls-cert-bundle: "/etc/ssl/certs/ca-certificates.crt"
trust-anchor-file: "/etc/unbound/root.key"
use-syslog: no
username: "nonrootuser"
val-log-level: 3
verbosity: 2
local-zone: "b" static
local-zone: "c" static
private-address: 9.9.9.9
private-address: c
private-address: d
forward-zone:
forward-no-cache: no
forward-tls-upstream: yes
name: "."
forward-addr: 1.1.1.1@853#cloudflare-dns.com
forward-addr: 1.0.0.1@853#cloudflare-dns.com
forward-addr: 9.9.9.9@853#dns.quad9.net
forward-addr: 149.112.112.112@853#dns.quad9.net`
assert.Equal(t, expected, "\n"+strings.Join(lines, "\n"))
}
func Test_buildBlocked(t *testing.T) {
t.Parallel()
type blockParams struct {
blocked bool
content []byte
clientErr error
}
tests := map[string]struct {
malicious blockParams
ads blockParams
surveillance blockParams
allowedHostnames []string
privateAddresses []string
hostnamesLines []string
ipsLines []string
errsString []string
}{
"none blocked": {},
"all blocked without lists": {
malicious: blockParams{
blocked: true,
},
ads: blockParams{
blocked: true,
},
surveillance: blockParams{
blocked: true,
},
},
"all blocked with lists": {
malicious: blockParams{
blocked: true,
content: []byte("malicious"),
},
ads: blockParams{
blocked: true,
content: []byte("ads"),
},
surveillance: blockParams{
blocked: true,
content: []byte("surveillance"),
},
hostnamesLines: []string{
" local-zone: \"ads\" static",
" local-zone: \"malicious\" static",
" local-zone: \"surveillance\" static"},
ipsLines: []string{
" private-address: ads",
" private-address: malicious",
" private-address: surveillance"},
},
"all blocked with allowed hostnames": {
malicious: blockParams{
blocked: true,
content: []byte("malicious"),
},
ads: blockParams{
blocked: true,
content: []byte("ads"),
},
surveillance: blockParams{
blocked: true,
content: []byte("surveillance"),
},
allowedHostnames: []string{"ads"},
hostnamesLines: []string{
" local-zone: \"malicious\" static",
" local-zone: \"surveillance\" static"},
ipsLines: []string{
" private-address: ads",
" private-address: malicious",
" private-address: surveillance"},
},
"all blocked with private addresses": {
malicious: blockParams{
blocked: true,
content: []byte("malicious"),
},
ads: blockParams{
blocked: true,
content: []byte("ads"),
},
surveillance: blockParams{
blocked: true,
content: []byte("surveillance"),
},
privateAddresses: []string{"ads", "192.100.1.5"},
hostnamesLines: []string{
" local-zone: \"ads\" static",
" local-zone: \"malicious\" static",
" local-zone: \"surveillance\" static"},
ipsLines: []string{
" private-address: 192.100.1.5",
" private-address: ads",
" private-address: malicious",
" private-address: surveillance"},
},
"all blocked with lists and one error": {
malicious: blockParams{
blocked: true,
content: []byte("malicious"),
},
ads: blockParams{
blocked: true,
content: []byte("ads"),
clientErr: fmt.Errorf("ads error"),
},
surveillance: blockParams{
blocked: true,
content: []byte("surveillance"),
},
hostnamesLines: []string{
" local-zone: \"malicious\" static",
" local-zone: \"surveillance\" static"},
ipsLines: []string{
" private-address: malicious",
" private-address: surveillance"},
errsString: []string{"ads error", "ads error"},
},
"all blocked with errors": {
malicious: blockParams{
blocked: true,
clientErr: fmt.Errorf("malicious"),
},
ads: blockParams{
blocked: true,
clientErr: fmt.Errorf("ads"),
},
surveillance: blockParams{
blocked: true,
clientErr: fmt.Errorf("surveillance"),
},
errsString: []string{"malicious", "malicious", "ads", "ads", "surveillance", "surveillance"},
},
}
for name, tc := range tests {
tc := tc
t.Run(name, func(t *testing.T) {
t.Parallel()
client := &mocks.Client{}
if tc.malicious.blocked {
client.On("GetContent", string(constants.MaliciousBlockListHostnamesURL)).
Return(tc.malicious.content, 200, tc.malicious.clientErr).Once()
client.On("GetContent", string(constants.MaliciousBlockListIPsURL)).
Return(tc.malicious.content, 200, tc.malicious.clientErr).Once()
}
if tc.ads.blocked {
client.On("GetContent", string(constants.AdsBlockListHostnamesURL)).
Return(tc.ads.content, 200, tc.ads.clientErr).Once()
client.On("GetContent", string(constants.AdsBlockListIPsURL)).
Return(tc.ads.content, 200, tc.ads.clientErr).Once()
}
if tc.surveillance.blocked {
client.On("GetContent", string(constants.SurveillanceBlockListHostnamesURL)).
Return(tc.surveillance.content, 200, tc.surveillance.clientErr).Once()
client.On("GetContent", string(constants.SurveillanceBlockListIPsURL)).
Return(tc.surveillance.content, 200, tc.surveillance.clientErr).Once()
}
hostnamesLines, ipsLines, errs := buildBlocked(client, tc.malicious.blocked, tc.ads.blocked, tc.surveillance.blocked,
tc.allowedHostnames, tc.privateAddresses)
var errsString []string
for _, err := range errs {
errsString = append(errsString, err.Error())
}
assert.ElementsMatch(t, tc.errsString, errsString)
assert.ElementsMatch(t, tc.hostnamesLines, hostnamesLines)
assert.ElementsMatch(t, tc.ipsLines, ipsLines)
client.AssertExpectations(t)
})
}
}
func Test_getList(t *testing.T) {
t.Parallel()
tests := map[string]struct {
content []byte
status int
clientErr error
results []string
err error
}{
"no result": {nil, 200, nil, nil, nil},
"bad status": {nil, 500, nil, nil, fmt.Errorf("HTTP status code is 500 and not 200")},
"network error": {nil, 200, fmt.Errorf("error"), nil, fmt.Errorf("error")},
"results": {[]byte("a\nb\nc\n"), 200, nil, []string{"a", "b", "c"}, nil},
}
for name, tc := range tests {
tc := tc
t.Run(name, func(t *testing.T) {
t.Parallel()
client := &mocks.Client{}
client.On("GetContent", "irrelevant_url").Return(
tc.content, tc.status, tc.clientErr,
).Once()
results, err := getList(client, "irrelevant_url")
if tc.err != nil {
require.Error(t, err)
assert.Equal(t, tc.err.Error(), err.Error())
} else {
assert.NoError(t, err)
}
assert.Equal(t, tc.results, results)
client.AssertExpectations(t)
})
}
}
func Test_buildBlockedHostnames(t *testing.T) {
t.Parallel()
type blockParams struct {
blocked bool
content []byte
clientErr error
}
tests := map[string]struct {
malicious blockParams
ads blockParams
surveillance blockParams
allowedHostnames []string
lines []string
errsString []string
}{
"nothing blocked": {
lines: nil,
errsString: nil,
},
"only malicious blocked": {
malicious: blockParams{
blocked: true,
content: []byte("site_a\nsite_b"),
clientErr: nil,
},
lines: []string{
" local-zone: \"site_a\" static",
" local-zone: \"site_b\" static"},
errsString: nil,
},
"all blocked with some duplicates": {
malicious: blockParams{
blocked: true,
content: []byte("site_a\nsite_b"),
},
ads: blockParams{
blocked: true,
content: []byte("site_a\nsite_c"),
},
surveillance: blockParams{
blocked: true,
content: []byte("site_c\nsite_a"),
},
lines: []string{
" local-zone: \"site_a\" static",
" local-zone: \"site_b\" static",
" local-zone: \"site_c\" static"},
errsString: nil,
},
"all blocked with one errored": {
malicious: blockParams{
blocked: true,
content: []byte("site_a\nsite_b"),
},
ads: blockParams{
blocked: true,
content: []byte("site_a\nsite_c"),
},
surveillance: blockParams{
blocked: true,
clientErr: fmt.Errorf("surveillance error"),
},
lines: []string{
" local-zone: \"site_a\" static",
" local-zone: \"site_b\" static",
" local-zone: \"site_c\" static"},
errsString: []string{"surveillance error"},
},
"blocked with allowed hostnames": {
malicious: blockParams{
blocked: true,
content: []byte("site_a\nsite_b"),
},
ads: blockParams{
blocked: true,
content: []byte("site_c\nsite_d"),
},
allowedHostnames: []string{"site_b", "site_c"},
lines: []string{
" local-zone: \"site_a\" static",
" local-zone: \"site_d\" static"},
},
}
for name, tc := range tests {
tc := tc
t.Run(name, func(t *testing.T) {
t.Parallel()
client := &mocks.Client{}
if tc.malicious.blocked {
client.On("GetContent", string(constants.MaliciousBlockListHostnamesURL)).
Return(tc.malicious.content, 200, tc.malicious.clientErr).Once()
}
if tc.ads.blocked {
client.On("GetContent", string(constants.AdsBlockListHostnamesURL)).
Return(tc.ads.content, 200, tc.ads.clientErr).Once()
}
if tc.surveillance.blocked {
client.On("GetContent", string(constants.SurveillanceBlockListHostnamesURL)).
Return(tc.surveillance.content, 200, tc.surveillance.clientErr).Once()
}
lines, errs := buildBlockedHostnames(client,
tc.malicious.blocked, tc.ads.blocked, tc.surveillance.blocked, tc.allowedHostnames)
var errsString []string
for _, err := range errs {
errsString = append(errsString, err.Error())
}
assert.ElementsMatch(t, tc.errsString, errsString)
assert.ElementsMatch(t, tc.lines, lines)
client.AssertExpectations(t)
})
}
}
func Test_buildBlockedIPs(t *testing.T) {
t.Parallel()
type blockParams struct {
blocked bool
content []byte
clientErr error
}
tests := map[string]struct {
malicious blockParams
ads blockParams
surveillance blockParams
privateAddresses []string
lines []string
errsString []string
}{
"nothing blocked": {
lines: nil,
errsString: nil,
},
"only malicious blocked": {
malicious: blockParams{
blocked: true,
content: []byte("site_a\nsite_b"),
clientErr: nil,
},
lines: []string{
" private-address: site_a",
" private-address: site_b"},
errsString: nil,
},
"all blocked with some duplicates": {
malicious: blockParams{
blocked: true,
content: []byte("site_a\nsite_b"),
},
ads: blockParams{
blocked: true,
content: []byte("site_a\nsite_c"),
},
surveillance: blockParams{
blocked: true,
content: []byte("site_c\nsite_a"),
},
lines: []string{
" private-address: site_a",
" private-address: site_b",
" private-address: site_c"},
errsString: nil,
},
"all blocked with one errored": {
malicious: blockParams{
blocked: true,
content: []byte("site_a\nsite_b"),
},
ads: blockParams{
blocked: true,
content: []byte("site_a\nsite_c"),
},
surveillance: blockParams{
blocked: true,
clientErr: fmt.Errorf("surveillance error"),
},
lines: []string{
" private-address: site_a",
" private-address: site_b",
" private-address: site_c"},
errsString: []string{"surveillance error"},
},
"blocked with private addresses": {
malicious: blockParams{
blocked: true,
content: []byte("site_a\nsite_b"),
},
ads: blockParams{
blocked: true,
content: []byte("site_c"),
},
privateAddresses: []string{"site_c", "site_d"},
lines: []string{
" private-address: site_a",
" private-address: site_b",
" private-address: site_c",
" private-address: site_d"},
},
}
for name, tc := range tests {
tc := tc
t.Run(name, func(t *testing.T) {
t.Parallel()
client := &mocks.Client{}
if tc.malicious.blocked {
client.On("GetContent", string(constants.MaliciousBlockListIPsURL)).
Return(tc.malicious.content, 200, tc.malicious.clientErr).Once()
}
if tc.ads.blocked {
client.On("GetContent", string(constants.AdsBlockListIPsURL)).
Return(tc.ads.content, 200, tc.ads.clientErr).Once()
}
if tc.surveillance.blocked {
client.On("GetContent", string(constants.SurveillanceBlockListIPsURL)).
Return(tc.surveillance.content, 200, tc.surveillance.clientErr).Once()
}
lines, errs := buildBlockedIPs(client,
tc.malicious.blocked, tc.ads.blocked, tc.surveillance.blocked, tc.privateAddresses)
var errsString []string
for _, err := range errs {
errsString = append(errsString, err.Error())
}
assert.ElementsMatch(t, tc.errsString, errsString)
assert.ElementsMatch(t, tc.lines, lines)
client.AssertExpectations(t)
})
}
}

View File

@@ -1,43 +0,0 @@
package dns
import (
"io"
"net"
"github.com/qdm12/golibs/command"
"github.com/qdm12/golibs/files"
"github.com/qdm12/golibs/logging"
"github.com/qdm12/golibs/network"
"github.com/qdm12/private-internet-access-docker/internal/settings"
)
const logPrefix = "dns configurator"
type Configurator interface {
DownloadRootHints(uid, gid int) error
DownloadRootKey(uid, gid int) error
MakeUnboundConf(settings settings.DNS, uid, gid int) (err error)
UseDNSInternally(IP net.IP)
UseDNSSystemWide(IP net.IP) error
Start(logLevel uint8) (stdout io.ReadCloser, waitFn func() error, err error)
WaitForUnbound() (err error)
Version() (version string, err error)
}
type configurator struct {
logger logging.Logger
client network.Client
fileManager files.FileManager
commander command.Commander
lookupIP func(host string) ([]net.IP, error)
}
func NewConfigurator(logger logging.Logger, client network.Client, fileManager files.FileManager) Configurator {
return &configurator{
logger: logger,
client: client,
fileManager: fileManager,
commander: command.NewCommander(),
lookupIP: net.LookupIP,
}
}

346
internal/dns/loop.go Normal file
View File

@@ -0,0 +1,346 @@
package dns
import (
"context"
"errors"
"net"
"net/http"
"sync"
"time"
"github.com/qdm12/dns/pkg/unbound"
"github.com/qdm12/gluetun/internal/constants"
"github.com/qdm12/gluetun/internal/models"
"github.com/qdm12/gluetun/internal/settings"
"github.com/qdm12/golibs/command"
"github.com/qdm12/golibs/logging"
)
type Looper interface {
Run(ctx context.Context, wg *sync.WaitGroup, dnsReadyCh chan<- struct{})
RunRestartTicker(ctx context.Context, wg *sync.WaitGroup)
GetStatus() (status models.LoopStatus)
SetStatus(status models.LoopStatus) (outcome string, err error)
GetSettings() (settings settings.DNS)
SetSettings(settings settings.DNS) (outcome string)
}
type looper struct {
state state
conf unbound.Configurator
client *http.Client
logger logging.Logger
streamMerger command.StreamMerger
username string
puid int
pgid int
loopLock sync.Mutex
start chan struct{}
running chan models.LoopStatus
stop chan struct{}
stopped chan struct{}
updateTicker chan struct{}
backoffTime time.Duration
timeNow func() time.Time
timeSince func(time.Time) time.Duration
}
const defaultBackoffTime = 10 * time.Second
func NewLooper(conf unbound.Configurator, settings settings.DNS, client *http.Client,
logger logging.Logger, streamMerger command.StreamMerger,
username string, puid, pgid int) Looper {
return &looper{
state: state{
status: constants.Stopped,
settings: settings,
},
conf: conf,
client: client,
logger: logger.WithPrefix("dns over tls: "),
username: username,
puid: puid,
pgid: pgid,
streamMerger: streamMerger,
start: make(chan struct{}),
running: make(chan models.LoopStatus),
stop: make(chan struct{}),
stopped: make(chan struct{}),
updateTicker: make(chan struct{}),
backoffTime: defaultBackoffTime,
timeNow: time.Now,
timeSince: time.Since,
}
}
func (l *looper) logAndWait(ctx context.Context, err error) {
l.logger.Warn(err)
l.logger.Info("attempting restart in %s", l.backoffTime)
timer := time.NewTimer(l.backoffTime)
l.backoffTime *= 2
select {
case <-timer.C:
case <-ctx.Done():
if !timer.Stop() {
<-timer.C
}
}
}
func (l *looper) Run(ctx context.Context, wg *sync.WaitGroup, dnsReadyCh chan<- struct{}) {
defer wg.Done()
const fallback = false
l.useUnencryptedDNS(fallback) // TODO remove? Use default DNS by default for Docker resolution?
select {
case <-l.start:
case <-ctx.Done():
return
}
defer l.logger.Warn("loop exited")
crashed := false
l.backoffTime = defaultBackoffTime
for ctx.Err() == nil {
// Upper scope variables for Unbound only
var unboundCancel context.CancelFunc = func() {}
waitError := make(chan error)
for l.GetSettings().Enabled {
if ctx.Err() != nil {
l.logger.Warn("context canceled: exiting loop")
return
}
var err error
unboundCancel, err = l.setupUnbound(ctx, crashed, waitError)
if err != nil {
if !errors.Is(err, errUpdateFiles) {
const fallback = true
l.useUnencryptedDNS(fallback)
}
l.logAndWait(ctx, err)
continue
}
break
}
if !l.GetSettings().Enabled {
const fallback = false
l.useUnencryptedDNS(fallback)
}
dnsReadyCh <- struct{}{}
stayHere := true
for stayHere {
select {
case <-ctx.Done():
l.logger.Warn("context canceled: exiting loop")
unboundCancel()
<-waitError
close(waitError)
return
case <-l.stop:
l.logger.Info("stopping")
const fallback = false
l.useUnencryptedDNS(fallback)
unboundCancel()
<-waitError
l.stopped <- struct{}{}
case <-l.start:
l.logger.Info("starting")
stayHere = false
case err := <-waitError: // unexpected error
unboundCancel()
l.state.setStatusWithLock(constants.Crashed)
const fallback = true
l.useUnencryptedDNS(fallback)
l.logAndWait(ctx, err)
stayHere = false
}
}
close(waitError)
unboundCancel()
}
}
var errUpdateFiles = errors.New("cannot update files")
// Returning cancel == nil signals we want to re-run setupUnbound
// Returning err == errUpdateFiles signals we should not fall back
// on the plaintext DNS as DOT is still up and running.
func (l *looper) setupUnbound(ctx context.Context,
previousCrashed bool, waitError chan<- error) (cancel context.CancelFunc, err error) {
err = l.updateFiles(ctx)
if err != nil {
l.state.setStatusWithLock(constants.Crashed)
return nil, errUpdateFiles
}
settings := l.GetSettings()
unboundCtx, cancel := context.WithCancel(context.Background())
stream, waitFn, err := l.conf.Start(unboundCtx, settings.Unbound.VerbosityDetailsLevel)
if err != nil {
cancel()
if !previousCrashed {
l.running <- constants.Crashed
}
return nil, err
}
// Started successfully
go l.streamMerger.Merge(unboundCtx, stream, command.MergeName("unbound"))
l.conf.UseDNSInternally(net.IP{127, 0, 0, 1}) // use Unbound
if err := l.conf.UseDNSSystemWide(net.IP{127, 0, 0, 1}, settings.KeepNameserver); err != nil { // use Unbound
l.logger.Error(err)
}
if err := l.conf.WaitForUnbound(ctx); err != nil {
if !previousCrashed {
l.running <- constants.Crashed
}
cancel()
return nil, err
}
go func() {
err := waitFn() // blocking
waitError <- err
}()
l.logger.Info("ready")
if !previousCrashed {
l.running <- constants.Running
} else {
l.backoffTime = defaultBackoffTime
l.state.setStatusWithLock(constants.Running)
}
return cancel, nil
}
func (l *looper) useUnencryptedDNS(fallback bool) {
settings := l.GetSettings()
// Try with user provided plaintext ip address
targetIP := settings.PlaintextAddress
if targetIP != nil {
if fallback {
l.logger.Info("falling back on plaintext DNS at address %s", targetIP)
} else {
l.logger.Info("using plaintext DNS at address %s", targetIP)
}
l.conf.UseDNSInternally(targetIP)
if err := l.conf.UseDNSSystemWide(targetIP, settings.KeepNameserver); err != nil {
l.logger.Error(err)
}
return
}
// Try with any IPv4 address from the providers chosen
for _, provider := range settings.Unbound.Providers {
data, _ := unbound.GetProviderData(provider)
for _, targetIP = range data.IPs {
if targetIP.To4() != nil {
if fallback {
l.logger.Info("falling back on plaintext DNS at address %s", targetIP)
} else {
l.logger.Info("using plaintext DNS at address %s", targetIP)
}
l.conf.UseDNSInternally(targetIP)
if err := l.conf.UseDNSSystemWide(targetIP, settings.KeepNameserver); err != nil {
l.logger.Error(err)
}
return
}
}
}
// No IPv4 address found
l.logger.Error("no ipv4 DNS address found for providers %s", settings.Unbound.Providers)
}
func (l *looper) RunRestartTicker(ctx context.Context, wg *sync.WaitGroup) {
defer wg.Done()
// Timer that acts as a ticker
timer := time.NewTimer(time.Hour)
timer.Stop()
timerIsStopped := true
settings := l.GetSettings()
if settings.UpdatePeriod > 0 {
timer.Reset(settings.UpdatePeriod)
timerIsStopped = false
}
lastTick := time.Unix(0, 0)
for {
select {
case <-ctx.Done():
if !timerIsStopped && !timer.Stop() {
<-timer.C
}
return
case <-timer.C:
lastTick = l.timeNow()
status := l.GetStatus()
if status == constants.Running {
if err := l.updateFiles(ctx); err != nil {
l.state.setStatusWithLock(constants.Crashed)
l.logger.Error(err)
l.logger.Warn("skipping Unbound restart due to failed files update")
continue
}
}
_, _ = l.SetStatus(constants.Stopped)
_, _ = l.SetStatus(constants.Running)
settings := l.GetSettings()
timer.Reset(settings.UpdatePeriod)
case <-l.updateTicker:
if !timer.Stop() {
<-timer.C
}
timerIsStopped = true
settings := l.GetSettings()
newUpdatePeriod := settings.UpdatePeriod
if newUpdatePeriod == 0 {
continue
}
var waited time.Duration
if lastTick.UnixNano() != 0 {
waited = l.timeSince(lastTick)
}
leftToWait := newUpdatePeriod - waited
timer.Reset(leftToWait)
timerIsStopped = false
}
}
}
func (l *looper) updateFiles(ctx context.Context) (err error) {
l.logger.Info("downloading DNS over TLS cryptographic files")
if err := l.conf.SetupFiles(ctx); err != nil {
return err
}
settings := l.GetSettings()
l.logger.Info("downloading hostnames and IP block lists")
hostnameLines, ipLines, errs := l.conf.BuildBlocked(ctx, l.client,
settings.BlockMalicious, settings.BlockAds, settings.BlockSurveillance,
settings.Unbound.BlockedHostnames, settings.Unbound.BlockedIPs,
settings.Unbound.AllowedHostnames)
for _, err := range errs {
l.logger.Warn(err)
}
if err := l.conf.MakeUnboundConf(
settings.Unbound, hostnameLines, ipLines,
l.username, l.puid, l.pgid); err != nil {
return err
}
return nil
}

View File

@@ -1,47 +0,0 @@
package dns
import (
"context"
"net"
"strings"
"github.com/qdm12/private-internet-access-docker/internal/constants"
)
// UseDNSInternally is to change the Go program DNS only
func (c *configurator) UseDNSInternally(IP net.IP) {
c.logger.Info("%s: using DNS address %s internally", logPrefix, IP.String())
net.DefaultResolver = &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.String(), "53"))
},
}
}
// UseDNSSystemWide changes the nameserver to use for DNS system wide
func (c *configurator) UseDNSSystemWide(IP net.IP) error {
c.logger.Info("%s: using DNS address %s system wide", logPrefix, IP.String())
data, err := c.fileManager.ReadFile(string(constants.ResolvConf))
if err != nil {
return err
}
s := strings.TrimSuffix(string(data), "\n")
lines := strings.Split(s, "\n")
if len(lines) == 1 && lines[0] == "" {
lines = nil
}
found := false
for i := range lines {
if strings.HasPrefix(lines[i], "nameserver ") {
lines[i] = "nameserver " + IP.String()
found = true
}
}
if !found {
lines = append(lines, "nameserver "+IP.String())
}
data = []byte(strings.Join(lines, "\n"))
return c.fileManager.WriteToFile(string(constants.ResolvConf), data)
}

View File

@@ -1,73 +0,0 @@
package dns
import (
"fmt"
"net"
"testing"
filesmocks "github.com/qdm12/golibs/files/mocks"
loggingmocks "github.com/qdm12/golibs/logging/mocks"
"github.com/qdm12/private-internet-access-docker/internal/constants"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func Test_UseDNSSystemWide(t *testing.T) {
t.Parallel()
tests := map[string]struct {
data []byte
writtenData []byte
readErr error
writeErr error
err error
}{
"no data": {
writtenData: []byte("nameserver 127.0.0.1"),
},
"read error": {
readErr: fmt.Errorf("error"),
err: fmt.Errorf("error"),
},
"write error": {
writtenData: []byte("nameserver 127.0.0.1"),
writeErr: fmt.Errorf("error"),
err: fmt.Errorf("error"),
},
"lines without nameserver": {
data: []byte("abc\ndef\n"),
writtenData: []byte("abc\ndef\nnameserver 127.0.0.1"),
},
"lines with nameserver": {
data: []byte("abc\nnameserver abc def\ndef\n"),
writtenData: []byte("abc\nnameserver 127.0.0.1\ndef"),
},
}
for name, tc := range tests {
tc := tc
t.Run(name, func(t *testing.T) {
t.Parallel()
fileManager := &filesmocks.FileManager{}
fileManager.On("ReadFile", string(constants.ResolvConf)).
Return(tc.data, tc.readErr).Once()
if tc.readErr == nil {
fileManager.On("WriteToFile", string(constants.ResolvConf), tc.writtenData).
Return(tc.writeErr).Once()
}
logger := &loggingmocks.Logger{}
logger.On("Info", "%s: using DNS address %s system wide", logPrefix, "127.0.0.1").Once()
c := &configurator{
fileManager: fileManager,
logger: logger,
}
err := c.UseDNSSystemWide(net.IP{127, 0, 0, 1})
if tc.err != nil {
require.Error(t, err)
assert.Equal(t, tc.err.Error(), err.Error())
} else {
assert.NoError(t, err)
}
fileManager.AssertExpectations(t)
logger.AssertExpectations(t)
})
}
}

View File

@@ -1,38 +0,0 @@
package dns
import (
"fmt"
"github.com/qdm12/golibs/files"
"github.com/qdm12/private-internet-access-docker/internal/constants"
)
func (c *configurator) DownloadRootHints(uid, gid int) error {
c.logger.Info("%s: downloading root hints from %s", logPrefix, constants.NamedRootURL)
content, status, err := c.client.GetContent(string(constants.NamedRootURL))
if err != nil {
return err
} else if status != 200 {
return fmt.Errorf("HTTP status code is %d for %s", status, constants.NamedRootURL)
}
return c.fileManager.WriteToFile(
string(constants.RootHints),
content,
files.Ownership(uid, gid),
files.Permissions(0400))
}
func (c *configurator) DownloadRootKey(uid, gid int) error {
c.logger.Info("%s: downloading root key from %s", logPrefix, constants.RootKeyURL)
content, status, err := c.client.GetContent(string(constants.RootKeyURL))
if err != nil {
return err
} else if status != 200 {
return fmt.Errorf("HTTP status code is %d for %s", status, constants.RootKeyURL)
}
return c.fileManager.WriteToFile(
string(constants.RootKey),
content,
files.Ownership(uid, gid),
files.Permissions(0400))
}

View File

@@ -1,144 +0,0 @@
package dns
import (
"fmt"
"net/http"
"testing"
filesMocks "github.com/qdm12/golibs/files/mocks"
loggingMocks "github.com/qdm12/golibs/logging/mocks"
networkMocks "github.com/qdm12/golibs/network/mocks"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/mock"
"github.com/stretchr/testify/require"
"github.com/qdm12/private-internet-access-docker/internal/constants"
)
func Test_DownloadRootHints(t *testing.T) {
t.Parallel()
tests := map[string]struct {
content []byte
status int
clientErr error
writeErr error
err error
}{
"no data": {
status: http.StatusOK,
},
"bad status": {
status: http.StatusBadRequest,
err: fmt.Errorf("HTTP status code is 400 for https://raw.githubusercontent.com/qdm12/files/master/named.root.updated"),
},
"client error": {
clientErr: fmt.Errorf("error"),
err: fmt.Errorf("error"),
},
"write error": {
status: http.StatusOK,
writeErr: fmt.Errorf("error"),
err: fmt.Errorf("error"),
},
"data": {
content: []byte("content"),
status: http.StatusOK,
},
}
for name, tc := range tests {
tc := tc
t.Run(name, func(t *testing.T) {
t.Parallel()
logger := &loggingMocks.Logger{}
logger.On("Info", "%s: downloading root hints from %s", logPrefix, constants.NamedRootURL).Once()
client := &networkMocks.Client{}
client.On("GetContent", string(constants.NamedRootURL)).
Return(tc.content, tc.status, tc.clientErr).Once()
fileManager := &filesMocks.FileManager{}
if tc.clientErr == nil && tc.status == http.StatusOK {
fileManager.On(
"WriteToFile",
string(constants.RootHints),
tc.content,
mock.AnythingOfType("files.WriteOptionSetter"),
mock.AnythingOfType("files.WriteOptionSetter")).
Return(tc.writeErr).Once()
}
c := &configurator{logger: logger, client: client, fileManager: fileManager}
err := c.DownloadRootHints(1000, 1000)
if tc.err != nil {
require.Error(t, err)
assert.Equal(t, tc.err.Error(), err.Error())
} else {
assert.NoError(t, err)
}
logger.AssertExpectations(t)
client.AssertExpectations(t)
fileManager.AssertExpectations(t)
})
}
}
func Test_DownloadRootKey(t *testing.T) {
t.Parallel()
tests := map[string]struct {
content []byte
status int
clientErr error
writeErr error
err error
}{
"no data": {
status: http.StatusOK,
},
"bad status": {
status: http.StatusBadRequest,
err: fmt.Errorf("HTTP status code is 400 for https://raw.githubusercontent.com/qdm12/files/master/root.key.updated"),
},
"client error": {
clientErr: fmt.Errorf("error"),
err: fmt.Errorf("error"),
},
"write error": {
status: http.StatusOK,
writeErr: fmt.Errorf("error"),
err: fmt.Errorf("error"),
},
"data": {
content: []byte("content"),
status: http.StatusOK,
},
}
for name, tc := range tests {
tc := tc
t.Run(name, func(t *testing.T) {
t.Parallel()
logger := &loggingMocks.Logger{}
logger.On("Info", "%s: downloading root key from %s", logPrefix, constants.RootKeyURL).Once()
client := &networkMocks.Client{}
client.On("GetContent", string(constants.RootKeyURL)).
Return(tc.content, tc.status, tc.clientErr).Once()
fileManager := &filesMocks.FileManager{}
if tc.clientErr == nil && tc.status == http.StatusOK {
fileManager.On(
"WriteToFile",
string(constants.RootKey),
tc.content,
mock.AnythingOfType("files.WriteOptionSetter"),
mock.AnythingOfType("files.WriteOptionSetter"),
).Return(tc.writeErr).Once()
}
c := &configurator{logger: logger, client: client, fileManager: fileManager}
err := c.DownloadRootKey(1000, 1001)
if tc.err != nil {
require.Error(t, err)
assert.Equal(t, tc.err.Error(), err.Error())
} else {
assert.NoError(t, err)
}
logger.AssertExpectations(t)
client.AssertExpectations(t)
fileManager.AssertExpectations(t)
})
}
}

99
internal/dns/state.go Normal file
View File

@@ -0,0 +1,99 @@
package dns
import (
"fmt"
"reflect"
"sync"
"github.com/qdm12/gluetun/internal/constants"
"github.com/qdm12/gluetun/internal/models"
"github.com/qdm12/gluetun/internal/settings"
)
type state struct {
status models.LoopStatus
settings settings.DNS
statusMu sync.RWMutex
settingsMu sync.RWMutex
}
func (s *state) setStatusWithLock(status models.LoopStatus) {
s.statusMu.Lock()
defer s.statusMu.Unlock()
s.status = status
}
func (l *looper) GetStatus() (status models.LoopStatus) {
l.state.statusMu.RLock()
defer l.state.statusMu.RUnlock()
return l.state.status
}
func (l *looper) SetStatus(status models.LoopStatus) (outcome string, err error) {
l.state.statusMu.Lock()
defer l.state.statusMu.Unlock()
existingStatus := l.state.status
switch status {
case constants.Running:
switch existingStatus {
case constants.Starting, constants.Running, constants.Stopping, constants.Crashed:
return fmt.Sprintf("already %s", existingStatus), nil
}
l.loopLock.Lock()
defer l.loopLock.Unlock()
l.state.status = constants.Starting
l.state.statusMu.Unlock()
l.start <- struct{}{}
newStatus := <-l.running
l.state.statusMu.Lock()
l.state.status = newStatus
return newStatus.String(), nil
case constants.Stopped:
switch existingStatus {
case constants.Starting, constants.Stopping, constants.Stopped, constants.Crashed:
return fmt.Sprintf("already %s", existingStatus), nil
}
l.loopLock.Lock()
defer l.loopLock.Unlock()
l.state.status = constants.Stopping
l.state.statusMu.Unlock()
l.stop <- struct{}{}
<-l.stopped
l.state.statusMu.Lock()
l.state.status = constants.Stopped
return status.String(), nil
default:
return "", fmt.Errorf("status %q can only be %q or %q",
status, constants.Running, constants.Stopped)
}
}
func (l *looper) GetSettings() (settings settings.DNS) {
l.state.settingsMu.RLock()
defer l.state.settingsMu.RUnlock()
return l.state.settings
}
func (l *looper) SetSettings(settings settings.DNS) (outcome string) {
l.state.settingsMu.Lock()
settingsUnchanged := reflect.DeepEqual(l.state.settings, settings)
if settingsUnchanged {
l.state.settingsMu.Unlock()
return "settings left unchanged"
}
tempSettings := l.state.settings
tempSettings.UpdatePeriod = settings.UpdatePeriod
onlyUpdatePeriodChanged := reflect.DeepEqual(tempSettings, settings)
l.state.settings = settings
l.state.settingsMu.Unlock()
if onlyUpdatePeriodChanged {
l.updateTicker <- struct{}{}
return "update period changed"
}
_, _ = l.SetStatus(constants.Stopped)
if settings.Enabled {
outcome, _ = l.SetStatus(constants.Running)
}
return outcome
}

View File

@@ -1,20 +0,0 @@
package dns
import (
"fmt"
"time"
)
func (c *configurator) WaitForUnbound() (err error) {
const maxTries = 10
const hostToResolve = "github.com"
for try := 1; try <= maxTries; try++ {
_, err := c.lookupIP(hostToResolve)
if err == nil {
return nil
}
c.logger.Warn("could not resolve %s (try %d of %d)", hostToResolve, try, maxTries)
time.Sleep(time.Duration(maxTries * 50 * time.Millisecond))
}
return fmt.Errorf("Unbound does not seem to be working after %d tries", maxTries)
}

40
internal/env/env.go vendored
View File

@@ -1,40 +0,0 @@
package env
import (
"os"
"github.com/qdm12/golibs/logging"
)
type Env interface {
FatalOnError(err error)
PrintVersion(program string, commandFn func() (string, error))
}
type env struct {
logger logging.Logger
osExit func(n int)
}
func New(logger logging.Logger) Env {
return &env{
logger: logger,
osExit: os.Exit,
}
}
func (e *env) FatalOnError(err error) {
if err != nil {
e.logger.Error(err)
e.osExit(1)
}
}
func (e *env) PrintVersion(program string, commandFn func() (string, error)) {
version, err := commandFn()
if err != nil {
e.logger.Error(err)
} else {
e.logger.Info("%s version: %s", program, version)
}
}

View File

@@ -1,90 +0,0 @@
package env
import (
"fmt"
"testing"
"github.com/qdm12/golibs/logging/mocks"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/mock"
)
func Test_FatalOnError(t *testing.T) {
t.Parallel()
tests := map[string]struct {
err error
}{
"nil": {},
"err": {fmt.Errorf("error")},
}
for name, tc := range tests {
tc := tc
t.Run(name, func(t *testing.T) {
t.Parallel()
var logged string
var exitCode int
logger := &mocks.Logger{}
if tc.err != nil {
logger.On("Error", tc.err).
Run(func(args mock.Arguments) {
err := args.Get(0).(error)
logged = err.Error()
}).Once()
}
osExit := func(n int) { exitCode = n }
e := &env{logger, osExit}
e.FatalOnError(tc.err)
if tc.err != nil {
assert.Equal(t, logged, tc.err.Error())
assert.Equal(t, exitCode, 1)
} else {
assert.Empty(t, logged)
assert.Zero(t, exitCode)
}
})
}
}
func Test_PrintVersion(t *testing.T) {
t.Parallel()
tests := map[string]struct {
program string
commandVersion string
commandErr error
}{
"no data": {},
"data": {"binu", "2.3-5", nil},
"error": {"binu", "", fmt.Errorf("error")},
}
for name, tc := range tests {
tc := tc
t.Run(name, func(t *testing.T) {
t.Parallel()
var logged string
logger := &mocks.Logger{}
if tc.commandErr != nil {
logger.On("Error", tc.commandErr).
Run(func(args mock.Arguments) {
err := args.Get(0).(error)
logged = err.Error()
}).Once()
} else {
logger.On("Info", "%s version: %s", tc.program, tc.commandVersion).
Run(func(args mock.Arguments) {
format := args.Get(0).(string)
program := args.Get(1).(string)
version := args.Get(2).(string)
logged = fmt.Sprintf(format, program, version)
}).Once()
}
e := &env{logger: logger}
commandFn := func() (string, error) { return tc.commandVersion, tc.commandErr }
e.PrintVersion(tc.program, commandFn)
if tc.commandErr != nil {
assert.Equal(t, logged, tc.commandErr.Error())
} else {
assert.Equal(t, logged, fmt.Sprintf("%s version: %s", tc.program, tc.commandVersion))
}
})
}
}

124
internal/firewall/enable.go Normal file
View File

@@ -0,0 +1,124 @@
package firewall
import (
"context"
"fmt"
"github.com/qdm12/gluetun/internal/constants"
)
func (c *configurator) SetEnabled(ctx context.Context, enabled bool) (err error) {
c.stateMutex.Lock()
defer c.stateMutex.Unlock()
if enabled == c.enabled {
if enabled {
c.logger.Info("already enabled")
} else {
c.logger.Info("already disabled")
}
return nil
}
if !enabled {
c.logger.Info("disabling...")
if err = c.disable(ctx); err != nil {
return err
}
c.enabled = false
c.logger.Info("disabled successfully")
return nil
}
c.logger.Info("enabling...")
if err := c.enable(ctx); err != nil {
return err
}
c.enabled = true
c.logger.Info("enabled successfully")
return nil
}
func (c *configurator) disable(ctx context.Context) (err error) {
if err = c.clearAllRules(ctx); err != nil {
return fmt.Errorf("cannot disable firewall: %w", err)
}
if err = c.setAllPolicies(ctx, "ACCEPT"); err != nil {
return fmt.Errorf("cannot disable firewall: %w", err)
}
return nil
}
// To use in defered call when enabling the firewall.
func (c *configurator) fallbackToDisabled(ctx context.Context) {
if ctx.Err() != nil {
return
}
if err := c.SetEnabled(ctx, false); err != nil {
c.logger.Error(err)
}
}
func (c *configurator) enable(ctx context.Context) (err error) {
if err = c.setAllPolicies(ctx, "DROP"); err != nil {
return fmt.Errorf("cannot enable firewall: %w", err)
}
const remove = false
defer func() {
if err != nil {
c.fallbackToDisabled(ctx)
}
}()
// Loopback traffic
if err = c.acceptInputThroughInterface(ctx, "lo", remove); err != nil {
return fmt.Errorf("cannot enable firewall: %w", err)
}
if err = c.acceptOutputThroughInterface(ctx, "lo", remove); err != nil {
return fmt.Errorf("cannot enable firewall: %w", err)
}
if err = c.acceptEstablishedRelatedTraffic(ctx, remove); err != nil {
return fmt.Errorf("cannot enable firewall: %w", err)
}
if c.vpnConnection.IP != nil {
if err = c.acceptOutputTrafficToVPN(ctx, c.defaultInterface, c.vpnConnection, remove); err != nil {
return fmt.Errorf("cannot enable firewall: %w", err)
}
}
if err = c.acceptOutputThroughInterface(ctx, string(constants.TUN), remove); err != nil {
return fmt.Errorf("cannot enable firewall: %w", err)
}
if err := c.acceptOutputFromIPToSubnet(ctx, c.defaultInterface, c.localIP, c.localSubnet, remove); err != nil {
return fmt.Errorf("cannot enable firewall: %w", err)
}
for _, subnet := range c.outboundSubnets {
if err := c.acceptOutputFromIPToSubnet(ctx, c.defaultInterface, c.localIP, subnet, remove); err != nil {
return fmt.Errorf("cannot enable firewall: %w", err)
}
}
// Allows packets from any IP address to go through eth0 / local network
// to reach Gluetun.
if err := c.acceptInputToSubnet(ctx, c.defaultInterface, c.localSubnet, remove); err != nil {
return fmt.Errorf("cannot enable firewall: %w", err)
}
for port, intf := range c.allowedInputPorts {
if err := c.acceptInputToPort(ctx, intf, port, remove); err != nil {
return fmt.Errorf("cannot enable firewall: %w", err)
}
}
if err := c.runUserPostRules(ctx, "/iptables/post-rules.txt", remove); err != nil {
return fmt.Errorf("cannot enable firewall: %w", err)
}
return nil
}

View File

@@ -1,43 +1,72 @@
package firewall
import (
"context"
"net"
"sync"
"github.com/qdm12/gluetun/internal/models"
"github.com/qdm12/gluetun/internal/routing"
"github.com/qdm12/golibs/command"
"github.com/qdm12/golibs/files"
"github.com/qdm12/golibs/logging"
"github.com/qdm12/private-internet-access-docker/internal/models"
"github.com/qdm12/golibs/os"
)
const logPrefix = "firewall configurator"
// Configurator allows to change firewall rules and modify network routes
// Configurator allows to change firewall rules and modify network routes.
type Configurator interface {
Version() (string, error)
AcceptAll() error
Clear() error
BlockAll() error
CreateGeneralRules() error
CreateVPNRules(dev models.VPNDevice, serverIPs []net.IP, defaultInterface string,
port uint16, protocol models.NetworkProtocol) error
CreateLocalSubnetsRules(subnet net.IPNet, extraSubnets []net.IPNet, defaultInterface string) error
AddRoutesVia(subnets []net.IPNet, defaultGateway net.IP, defaultInterface string) error
GetDefaultRoute() (defaultInterface string, defaultGateway net.IP, defaultSubnet net.IPNet, err error)
AllowInputTrafficOnPort(device models.VPNDevice, port uint16) error
AllowAnyIncomingOnPort(port uint16) error
Version(ctx context.Context) (string, error)
SetEnabled(ctx context.Context, enabled bool) (err error)
SetVPNConnection(ctx context.Context, connection models.OpenVPNConnection) (err error)
SetAllowedPort(ctx context.Context, port uint16, intf string) (err error)
SetOutboundSubnets(ctx context.Context, subnets []net.IPNet) (err error)
RemoveAllowedPort(ctx context.Context, port uint16) (err error)
SetDebug()
// SetNetworkInformation is meant to be called only once
SetNetworkInformation(defaultInterface string, defaultGateway net.IP, localSubnet net.IPNet, localIP net.IP)
}
type configurator struct {
commander command.Commander
logger logging.Logger
fileManager files.FileManager
type configurator struct { //nolint:maligned
commander command.Commander
logger logging.Logger
routing routing.Routing
openFile os.OpenFileFunc // for custom iptables rules
iptablesMutex sync.Mutex
debug bool
defaultInterface string
defaultGateway net.IP
localSubnet net.IPNet
localIP net.IP
networkInfoMutex sync.Mutex
// State
enabled bool
vpnConnection models.OpenVPNConnection
outboundSubnets []net.IPNet
allowedInputPorts map[uint16]string // port to interface mapping
stateMutex sync.Mutex
}
// NewConfigurator creates a new Configurator instance
func NewConfigurator(logger logging.Logger, fileManager files.FileManager) Configurator {
// NewConfigurator creates a new Configurator instance.
func NewConfigurator(logger logging.Logger, routing routing.Routing, openFile os.OpenFileFunc) Configurator {
return &configurator{
commander: command.NewCommander(),
logger: logger,
fileManager: fileManager,
commander: command.NewCommander(),
logger: logger.WithPrefix("firewall: "),
routing: routing,
openFile: openFile,
allowedInputPorts: make(map[uint16]string),
}
}
func (c *configurator) SetDebug() {
c.debug = true
}
func (c *configurator) SetNetworkInformation(
defaultInterface string, defaultGateway net.IP, localSubnet net.IPNet, localIP net.IP) {
c.networkInfoMutex.Lock()
defer c.networkInfoMutex.Unlock()
c.defaultInterface = defaultInterface
c.defaultGateway = defaultGateway
c.localSubnet = localSubnet
c.localIP = localIP
}

View File

@@ -1,138 +1,194 @@
package firewall
import (
"context"
"fmt"
"io/ioutil"
"net"
"os"
"strings"
"github.com/qdm12/private-internet-access-docker/internal/models"
"github.com/qdm12/gluetun/internal/models"
)
// Version obtains the version of the installed iptables
func (c *configurator) Version() (string, error) {
output, err := c.commander.Run("iptables", "--version")
func appendOrDelete(remove bool) string {
if remove {
return "--delete"
}
return "--append"
}
// flipRule changes an append rule in a delete rule or a delete rule into an
// append rule.
func flipRule(rule string) string {
switch {
case strings.HasPrefix(rule, "-A"):
return strings.Replace(rule, "-A", "-D", 1)
case strings.HasPrefix(rule, "--append"):
return strings.Replace(rule, "--append", "-D", 1)
case strings.HasPrefix(rule, "-D"):
return strings.Replace(rule, "-D", "-A", 1)
case strings.HasPrefix(rule, "--delete"):
return strings.Replace(rule, "--delete", "-A", 1)
}
return rule
}
// Version obtains the version of the installed iptables.
func (c *configurator) Version(ctx context.Context) (string, error) {
output, err := c.commander.Run(ctx, "iptables", "--version")
if err != nil {
return "", err
}
words := strings.Fields(output)
if len(words) < 2 {
const minWords = 2
if len(words) < minWords {
return "", fmt.Errorf("iptables --version: output is too short: %q", output)
}
return words[1], nil
}
func (c *configurator) runIptablesInstructions(instructions []string) error {
func (c *configurator) runIptablesInstructions(ctx context.Context, instructions []string) error {
for _, instruction := range instructions {
if err := c.runIptablesInstruction(instruction); err != nil {
if err := c.runIptablesInstruction(ctx, instruction); err != nil {
return err
}
}
return nil
}
func (c *configurator) runIptablesInstruction(instruction string) error {
func (c *configurator) runIptablesInstruction(ctx context.Context, instruction string) error {
c.iptablesMutex.Lock() // only one iptables command at once
defer c.iptablesMutex.Unlock()
if c.debug {
fmt.Printf("iptables %s\n", instruction)
}
flags := strings.Fields(instruction)
if output, err := c.commander.Run("iptables", flags...); err != nil {
return fmt.Errorf("failed executing %q: %s: %w", instruction, output, err)
if output, err := c.commander.Run(ctx, "iptables", flags...); err != nil {
return fmt.Errorf("failed executing \"iptables %s\": %s: %w", instruction, output, err)
}
return nil
}
func (c *configurator) Clear() error {
c.logger.Info("%s: clearing all rules", logPrefix)
return c.runIptablesInstructions([]string{
"--flush",
"--delete-chain",
"-t nat --flush",
"-t nat --delete-chain",
func (c *configurator) clearAllRules(ctx context.Context) error {
return c.runIptablesInstructions(ctx, []string{
"--flush", // flush all chains
"--delete-chain", // delete all chains
})
}
func (c *configurator) AcceptAll() error {
c.logger.Info("%s: accepting all traffic", logPrefix)
return c.runIptablesInstructions([]string{
"-P INPUT ACCEPT",
"-P OUTPUT ACCEPT",
"-P FORWARD ACCEPT",
})
}
func (c *configurator) BlockAll() error {
c.logger.Info("%s: blocking all traffic", logPrefix)
return c.runIptablesInstructions([]string{
"-P INPUT DROP",
"-F OUTPUT",
"-P OUTPUT DROP",
"-P FORWARD DROP",
})
}
func (c *configurator) CreateGeneralRules() error {
c.logger.Info("%s: creating general rules", logPrefix)
return c.runIptablesInstructions([]string{
"-A OUTPUT -m conntrack --ctstate ESTABLISHED,RELATED -j ACCEPT",
"-A INPUT -m conntrack --ctstate ESTABLISHED,RELATED -j ACCEPT",
"-A OUTPUT -o lo -j ACCEPT",
"-A INPUT -i lo -j ACCEPT",
})
}
func (c *configurator) CreateVPNRules(dev models.VPNDevice, serverIPs []net.IP,
defaultInterface string, port uint16, protocol models.NetworkProtocol) error {
for _, serverIP := range serverIPs {
c.logger.Info("%s: allowing output traffic to VPN server %s through %s on port %s %d",
logPrefix, serverIP, defaultInterface, protocol, port)
if err := c.runIptablesInstruction(
fmt.Sprintf("-A OUTPUT -d %s -o %s -p %s -m %s --dport %d -j ACCEPT",
serverIP, defaultInterface, protocol, protocol, port)); err != nil {
return err
}
func (c *configurator) setAllPolicies(ctx context.Context, policy string) error {
switch policy {
case "ACCEPT", "DROP":
default:
return fmt.Errorf("policy %q not recognized", policy)
}
if err := c.runIptablesInstruction(fmt.Sprintf("-A OUTPUT -o %s -j ACCEPT", dev)); err != nil {
return c.runIptablesInstructions(ctx, []string{
fmt.Sprintf("--policy INPUT %s", policy),
fmt.Sprintf("--policy OUTPUT %s", policy),
fmt.Sprintf("--policy FORWARD %s", policy),
})
}
func (c *configurator) acceptInputThroughInterface(ctx context.Context, intf string, remove bool) error {
return c.runIptablesInstruction(ctx, fmt.Sprintf(
"%s INPUT -i %s -j ACCEPT", appendOrDelete(remove), intf,
))
}
func (c *configurator) acceptInputToSubnet(ctx context.Context, intf string, destination net.IPNet, remove bool) error {
interfaceFlag := "-i " + intf
if intf == "*" { // all interfaces
interfaceFlag = ""
}
return c.runIptablesInstruction(ctx, fmt.Sprintf(
"%s INPUT %s -d %s -j ACCEPT", appendOrDelete(remove), interfaceFlag, destination.String(),
))
}
func (c *configurator) acceptOutputThroughInterface(ctx context.Context, intf string, remove bool) error {
return c.runIptablesInstruction(ctx, fmt.Sprintf(
"%s OUTPUT -o %s -j ACCEPT", appendOrDelete(remove), intf,
))
}
func (c *configurator) acceptEstablishedRelatedTraffic(ctx context.Context, remove bool) error {
return c.runIptablesInstructions(ctx, []string{
fmt.Sprintf("%s OUTPUT -m conntrack --ctstate ESTABLISHED,RELATED -j ACCEPT", appendOrDelete(remove)),
fmt.Sprintf("%s INPUT -m conntrack --ctstate ESTABLISHED,RELATED -j ACCEPT", appendOrDelete(remove)),
})
}
func (c *configurator) acceptOutputTrafficToVPN(ctx context.Context,
defaultInterface string, connection models.OpenVPNConnection, remove bool) error {
return c.runIptablesInstruction(ctx,
fmt.Sprintf("%s OUTPUT -d %s -o %s -p %s -m %s --dport %d -j ACCEPT",
appendOrDelete(remove), connection.IP, defaultInterface, connection.Protocol, connection.Protocol, connection.Port))
}
// Thanks to @npawelek.
func (c *configurator) acceptOutputFromIPToSubnet(ctx context.Context,
intf string, sourceIP net.IP, destinationSubnet net.IPNet, remove bool) error {
interfaceFlag := "-o " + intf
if intf == "*" { // all interfaces
interfaceFlag = ""
}
return c.runIptablesInstruction(ctx, fmt.Sprintf(
"%s OUTPUT %s -s %s -d %s -j ACCEPT",
appendOrDelete(remove), interfaceFlag, sourceIP.String(), destinationSubnet.String(),
))
}
// Used for port forwarding, with intf set to tun.
func (c *configurator) acceptInputToPort(ctx context.Context, intf string, port uint16, remove bool) error {
interfaceFlag := "-i " + intf
if intf == "*" { // all interfaces
interfaceFlag = ""
}
return c.runIptablesInstructions(ctx, []string{
fmt.Sprintf("%s INPUT %s -p tcp --dport %d -j ACCEPT", appendOrDelete(remove), interfaceFlag, port),
fmt.Sprintf("%s INPUT %s -p udp --dport %d -j ACCEPT", appendOrDelete(remove), interfaceFlag, port),
})
}
func (c *configurator) runUserPostRules(ctx context.Context, filepath string, remove bool) error {
file, err := c.openFile(filepath, os.O_RDONLY, 0)
if os.IsNotExist(err) {
return nil
} else if err != nil {
return err
}
return nil
}
func (c *configurator) CreateLocalSubnetsRules(subnet net.IPNet, extraSubnets []net.IPNet, defaultInterface string) error {
subnetStr := subnet.String()
c.logger.Info("%s: accepting input and output traffic for %s", logPrefix, subnetStr)
if err := c.runIptablesInstructions([]string{
fmt.Sprintf("-A INPUT -s %s -d %s -j ACCEPT", subnetStr, subnetStr),
fmt.Sprintf("-A OUTPUT -s %s -d %s -j ACCEPT", subnetStr, subnetStr),
}); err != nil {
b, err := ioutil.ReadAll(file)
if err != nil {
_ = file.Close()
return err
}
for _, extraSubnet := range extraSubnets {
extraSubnetStr := extraSubnet.String()
c.logger.Info("%s: accepting input traffic through %s from %s to %s", logPrefix, defaultInterface, extraSubnetStr, subnetStr)
if err := c.runIptablesInstruction(
fmt.Sprintf("-A INPUT -i %s -s %s -d %s -j ACCEPT", defaultInterface, extraSubnetStr, subnetStr)); err != nil {
return err
if err := file.Close(); err != nil {
return err
}
lines := strings.Split(string(b), "\n")
successfulRules := []string{}
defer func() {
// transaction-like rollback
if err == nil || ctx.Err() != nil {
return
}
// Thanks to @npawelek
c.logger.Info("%s: accepting output traffic through %s from %s to %s", logPrefix, defaultInterface, subnetStr, extraSubnetStr)
if err := c.runIptablesInstruction(
fmt.Sprintf("-A OUTPUT -o %s -s %s -d %s -j ACCEPT", defaultInterface, subnetStr, extraSubnetStr)); err != nil {
return err
for _, rule := range successfulRules {
_ = c.runIptablesInstruction(ctx, flipRule(rule))
}
}()
for _, line := range lines {
if !strings.HasPrefix(line, "iptables ") {
continue
}
rule := strings.TrimPrefix(line, "iptables ")
if remove {
rule = flipRule(rule)
}
if err = c.runIptablesInstruction(ctx, rule); err != nil {
return fmt.Errorf("cannot run custom rule: %w", err)
}
successfulRules = append(successfulRules, rule)
}
return nil
}
// Used for port forwarding
func (c *configurator) AllowInputTrafficOnPort(device models.VPNDevice, port uint16) error {
c.logger.Info("%s: accepting input traffic through %s on port %d", logPrefix, device, port)
return c.runIptablesInstructions([]string{
fmt.Sprintf("-A INPUT -i %s -p tcp --dport %d -j ACCEPT", device, port),
fmt.Sprintf("-A INPUT -i %s -p udp --dport %d -j ACCEPT", device, port),
})
}
func (c *configurator) AllowAnyIncomingOnPort(port uint16) error {
c.logger.Info("%s: accepting any input traffic on port %d", logPrefix, port)
return c.runIptablesInstructions([]string{
fmt.Sprintf("-A INPUT -p tcp --dport %d -j ACCEPT", port),
fmt.Sprintf("-A INPUT -p udp --dport %d -j ACCEPT", port),
})
}

View File

@@ -0,0 +1,56 @@
package firewall
import (
"context"
"fmt"
"net"
)
func (c *configurator) SetOutboundSubnets(ctx context.Context, subnets []net.IPNet) (err error) {
c.stateMutex.Lock()
defer c.stateMutex.Unlock()
if !c.enabled {
c.logger.Info("firewall disabled, only updating allowed subnets internal list")
c.outboundSubnets = make([]net.IPNet, len(subnets))
copy(c.outboundSubnets, subnets)
return nil
}
c.logger.Info("setting allowed subnets through firewall...")
subnetsToAdd := findSubnetsToAdd(c.outboundSubnets, subnets)
subnetsToRemove := findSubnetsToRemove(c.outboundSubnets, subnets)
if len(subnetsToAdd) == 0 && len(subnetsToRemove) == 0 {
return nil
}
c.removeOutboundSubnets(ctx, subnetsToRemove)
if err := c.addOutboundSubnets(ctx, subnetsToAdd); err != nil {
return fmt.Errorf("cannot set allowed subnets through firewall: %w", err)
}
return nil
}
func (c *configurator) removeOutboundSubnets(ctx context.Context, subnets []net.IPNet) {
const remove = true
for _, subnet := range subnets {
if err := c.acceptOutputFromIPToSubnet(ctx, c.defaultInterface, c.localIP, subnet, remove); err != nil {
c.logger.Error("cannot remove outdated outbound subnet through firewall: %s", err)
continue
}
c.outboundSubnets = removeSubnetFromSubnets(c.outboundSubnets, subnet)
}
}
func (c *configurator) addOutboundSubnets(ctx context.Context, subnets []net.IPNet) error {
const remove = false
for _, subnet := range subnets {
if err := c.acceptOutputFromIPToSubnet(ctx, c.defaultInterface, c.localIP, subnet, remove); err != nil {
return fmt.Errorf("cannot add allowed subnet through firewall: %w", err)
}
c.outboundSubnets = append(c.outboundSubnets, subnet)
}
return nil
}

View File

@@ -0,0 +1,71 @@
package firewall
import (
"context"
"fmt"
)
func (c *configurator) SetAllowedPort(ctx context.Context, port uint16, intf string) (err error) {
c.stateMutex.Lock()
defer c.stateMutex.Unlock()
if port == 0 {
return nil
}
if !c.enabled {
c.logger.Info("firewall disabled, only updating allowed ports internal state")
c.allowedInputPorts[port] = intf
return nil
}
c.logger.Info("setting allowed input port %d through interface %s...", port, intf)
if existingIntf, ok := c.allowedInputPorts[port]; ok {
if intf == existingIntf {
return nil
}
const remove = true
if err := c.acceptInputToPort(ctx, existingIntf, port, remove); err != nil {
return fmt.Errorf("cannot remove old allowed port %d through interface %s: %w", port, existingIntf, err)
}
}
const remove = false
if err := c.acceptInputToPort(ctx, intf, port, remove); err != nil {
return fmt.Errorf("cannot set allowed port %d through interface %s: %w", port, intf, err)
}
c.allowedInputPorts[port] = intf
return nil
}
func (c *configurator) RemoveAllowedPort(ctx context.Context, port uint16) (err error) {
c.stateMutex.Lock()
defer c.stateMutex.Unlock()
if port == 0 {
return nil
}
if !c.enabled {
c.logger.Info("firewall disabled, only updating allowed ports internal list")
delete(c.allowedInputPorts, port)
return nil
}
c.logger.Info("removing allowed port %d through firewall...", port)
intf, ok := c.allowedInputPorts[port]
if !ok {
return nil
}
const remove = true
if err := c.acceptInputToPort(ctx, intf, port, remove); err != nil {
return fmt.Errorf("cannot remove allowed port %d through interface %s: %w", port, intf, err)
}
delete(c.allowedInputPorts, port)
return nil
}

View File

@@ -1,88 +0,0 @@
package firewall
import (
"encoding/hex"
"net"
"fmt"
"strings"
"github.com/qdm12/private-internet-access-docker/internal/constants"
)
func (c *configurator) AddRoutesVia(subnets []net.IPNet, defaultGateway net.IP, defaultInterface string) error {
for _, subnet := range subnets {
subnetStr := subnet.String()
output, err := c.commander.Run("ip", "route", "show", subnetStr)
if err != nil {
return fmt.Errorf("cannot read route %s: %s: %w", subnetStr, output, err)
} else if len(output) > 0 { // thanks to @npawelek https://github.com/npawelek
continue // already exists
// TODO remove it instead and continue execution below
}
c.logger.Info("%s: adding %s as route via %s", logPrefix, subnetStr, defaultInterface)
output, err = c.commander.Run("ip", "route", "add", subnetStr, "via", defaultGateway.String(), "dev", defaultInterface)
if err != nil {
return fmt.Errorf("cannot add route for %s via %s %s %s: %s: %w", subnetStr, defaultGateway.String(), "dev", defaultInterface, output, err)
}
}
return nil
}
func (c *configurator) GetDefaultRoute() (defaultInterface string, defaultGateway net.IP, defaultSubnet net.IPNet, err error) {
c.logger.Info("%s: detecting default network route", logPrefix)
data, err := c.fileManager.ReadFile(string(constants.NetRoute))
if err != nil {
return "", nil, defaultSubnet, err
}
// Verify number of lines and fields
lines := strings.Split(string(data), "\n")
if len(lines) < 3 {
return "", nil, defaultSubnet, fmt.Errorf("not enough lines (%d) found in %s", len(lines), constants.NetRoute)
}
fieldsLine1 := strings.Fields(lines[1])
if len(fieldsLine1) < 3 {
return "", nil, defaultSubnet, fmt.Errorf("not enough fields in %q", lines[1])
}
fieldsLine2 := strings.Fields(lines[2])
if len(fieldsLine2) < 8 {
return "", nil, defaultSubnet, fmt.Errorf("not enough fields in %q", lines[2])
}
// get information
defaultInterface = fieldsLine1[0]
defaultGateway, err = reversedHexToIPv4(fieldsLine1[2])
if err != nil {
return "", nil, defaultSubnet, err
}
netNumber, err := reversedHexToIPv4(fieldsLine2[1])
if err != nil {
return "", nil, defaultSubnet, err
}
netMask, err := hexToIPv4Mask(fieldsLine2[7])
if err != nil {
return "", nil, defaultSubnet, err
}
subnet := net.IPNet{IP: netNumber, Mask: netMask}
c.logger.Info("%s: default route found: interface %s, gateway %s, subnet %s", logPrefix, defaultInterface, defaultGateway.String(), subnet.String())
return defaultInterface, defaultGateway, subnet, nil
}
func reversedHexToIPv4(reversedHex string) (IP net.IP, err error) {
bytes, err := hex.DecodeString(reversedHex)
if err != nil {
return nil, fmt.Errorf("cannot parse reversed IP hex %q: %s", reversedHex, err)
} else if len(bytes) != 4 {
return nil, fmt.Errorf("hex string contains %d bytes instead of 4", len(bytes))
}
return []byte{bytes[3], bytes[2], bytes[1], bytes[0]}, nil
}
func hexToIPv4Mask(hexString string) (mask net.IPMask, err error) {
bytes, err := hex.DecodeString(hexString)
if err != nil {
return nil, fmt.Errorf("cannot parse hex mask %q: %s", hexString, err)
} else if len(bytes) != 4 {
return nil, fmt.Errorf("hex string contains %d bytes instead of 4", len(bytes))
}
return []byte{bytes[3], bytes[2], bytes[1], bytes[0]}, nil
}

View File

@@ -1,171 +0,0 @@
package firewall
import (
"fmt"
"net"
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
filesmocks "github.com/qdm12/golibs/files/mocks"
loggingmocks "github.com/qdm12/golibs/logging/mocks"
"github.com/qdm12/private-internet-access-docker/internal/constants"
)
func Test_getDefaultRoute(t *testing.T) {
t.Parallel()
tests := map[string]struct {
data []byte
readErr error
defaultInterface string
defaultGateway net.IP
defaultSubnet net.IPNet
err error
}{
"no data": {
err: fmt.Errorf("not enough lines (1) found in %s", constants.NetRoute)},
"read error": {
readErr: fmt.Errorf("error"),
err: fmt.Errorf("error")},
"not enough fields line 1": {
data: []byte(`Iface Destination Gateway Flags RefCnt Use Metric Mask MTU Window IRTT
eth0 00000000
eth0 000011AC 00000000 0001 0 0 0 0000FFFF 0 0 0`),
err: fmt.Errorf("not enough fields in \"eth0 00000000\"")},
"not enough fields line 2": {
data: []byte(`Iface Destination Gateway Flags RefCnt Use Metric Mask MTU Window IRTT
eth0 00000000 010011AC 0003 0 0 0 00000000 0 0 0
eth0 000011AC 00000000 0001 0 0 0`),
err: fmt.Errorf("not enough fields in \"eth0 000011AC 00000000 0001 0 0 0\"")},
"bad gateway": {
data: []byte(`Iface Destination Gateway Flags RefCnt Use Metric Mask MTU Window IRTT
eth0 00000000 x 0003 0 0 0 00000000 0 0 0
eth0 000011AC 00000000 0001 0 0 0 0000FFFF 0 0 0`),
err: fmt.Errorf("cannot parse reversed IP hex \"x\": encoding/hex: invalid byte: U+0078 'x'")},
"bad net number": {
data: []byte(`Iface Destination Gateway Flags RefCnt Use Metric Mask MTU Window IRTT
eth0 00000000 010011AC 0003 0 0 0 00000000 0 0 0
eth0 x 00000000 0001 0 0 0 0000FFFF 0 0 0`),
err: fmt.Errorf("cannot parse reversed IP hex \"x\": encoding/hex: invalid byte: U+0078 'x'")},
"bad net mask": {
data: []byte(`Iface Destination Gateway Flags RefCnt Use Metric Mask MTU Window IRTT
eth0 00000000 010011AC 0003 0 0 0 00000000 0 0 0
eth0 000011AC 00000000 0001 0 0 0 x 0 0 0`),
err: fmt.Errorf("cannot parse hex mask \"x\": encoding/hex: invalid byte: U+0078 'x'")},
"success": {
data: []byte(`Iface Destination Gateway Flags RefCnt Use Metric Mask MTU Window IRTT
eth0 00000000 010011AC 0003 0 0 0 00000000 0 0 0
eth0 000011AC 00000000 0001 0 0 0 0000FFFF 0 0 0`),
defaultInterface: "eth0",
defaultGateway: net.IP{0xac, 0x11, 0x0, 0x1},
defaultSubnet: net.IPNet{
IP: net.IP{0xac, 0x11, 0x0, 0x0},
Mask: net.IPMask{0xff, 0xff, 0x0, 0x0},
}},
}
for name, tc := range tests {
tc := tc
t.Run(name, func(t *testing.T) {
t.Parallel()
fileManager := &filesmocks.FileManager{}
fileManager.On("ReadFile", string(constants.NetRoute)).
Return(tc.data, tc.readErr).Once()
logger := &loggingmocks.Logger{}
logger.On("Info", "%s: detecting default network route", logPrefix).Once()
if tc.err == nil {
logger.On("Info", "%s: default route found: interface %s, gateway %s, subnet %s",
logPrefix, tc.defaultInterface, tc.defaultGateway.String(), tc.defaultSubnet.String()).Once()
}
c := &configurator{logger: logger, fileManager: fileManager}
defaultInterface, defaultGateway, defaultSubnet, err := c.GetDefaultRoute()
if tc.err != nil {
require.Error(t, err)
assert.Equal(t, tc.err.Error(), err.Error())
} else {
assert.NoError(t, err)
}
assert.Equal(t, tc.defaultInterface, defaultInterface)
assert.Equal(t, tc.defaultGateway, defaultGateway)
assert.Equal(t, tc.defaultSubnet, defaultSubnet)
fileManager.AssertExpectations(t)
logger.AssertExpectations(t)
})
}
}
func Test_reversedHexToIPv4(t *testing.T) {
t.Parallel()
tests := map[string]struct {
reversedHex string
IP net.IP
err error
}{
"empty hex": {
err: fmt.Errorf("hex string contains 0 bytes instead of 4")},
"bad hex": {
reversedHex: "x",
err: fmt.Errorf("cannot parse reversed IP hex \"x\": encoding/hex: invalid byte: U+0078 'x'")},
"3 bytes hex": {
reversedHex: "9abcde",
err: fmt.Errorf("hex string contains 3 bytes instead of 4")},
"correct hex": {
reversedHex: "010011AC",
IP: []byte{0xac, 0x11, 0x0, 0x1},
err: nil},
"correct hex 2": {
reversedHex: "000011AC",
IP: []byte{0xac, 0x11, 0x0, 0x0},
err: nil},
}
for name, tc := range tests {
tc := tc
t.Run(name, func(t *testing.T) {
t.Parallel()
IP, err := reversedHexToIPv4(tc.reversedHex)
if tc.err != nil {
require.Error(t, err)
assert.Equal(t, tc.err.Error(), err.Error())
} else {
assert.NoError(t, err)
}
assert.Equal(t, tc.IP, IP)
})
}
}
func Test_hexMaskToDecMask(t *testing.T) {
t.Parallel()
tests := map[string]struct {
hexString string
mask net.IPMask
err error
}{
"empty hex": {
err: fmt.Errorf("hex string contains 0 bytes instead of 4")},
"bad hex": {
hexString: "x",
err: fmt.Errorf("cannot parse hex mask \"x\": encoding/hex: invalid byte: U+0078 'x'")},
"3 bytes hex": {
hexString: "9abcde",
err: fmt.Errorf("hex string contains 3 bytes instead of 4")},
"16": {
hexString: "0000FFFF",
mask: []byte{0xff, 0xff, 0x0, 0x0},
err: nil},
}
for name, tc := range tests {
tc := tc
t.Run(name, func(t *testing.T) {
t.Parallel()
mask, err := hexToIPv4Mask(tc.hexString)
if tc.err != nil {
require.Error(t, err)
assert.Equal(t, tc.err.Error(), err.Error())
} else {
assert.NoError(t, err)
}
assert.Equal(t, tc.mask, mask)
})
}
}

View File

@@ -0,0 +1,53 @@
package firewall
import (
"net"
)
func findSubnetsToAdd(oldSubnets, newSubnets []net.IPNet) (subnetsToAdd []net.IPNet) {
for _, newSubnet := range newSubnets {
found := false
for _, oldSubnet := range oldSubnets {
if subnetsAreEqual(oldSubnet, newSubnet) {
found = true
break
}
}
if !found {
subnetsToAdd = append(subnetsToAdd, newSubnet)
}
}
return subnetsToAdd
}
func findSubnetsToRemove(oldSubnets, newSubnets []net.IPNet) (subnetsToRemove []net.IPNet) {
for _, oldSubnet := range oldSubnets {
found := false
for _, newSubnet := range newSubnets {
if subnetsAreEqual(oldSubnet, newSubnet) {
found = true
break
}
}
if !found {
subnetsToRemove = append(subnetsToRemove, oldSubnet)
}
}
return subnetsToRemove
}
func subnetsAreEqual(a, b net.IPNet) bool {
return a.IP.Equal(b.IP) && a.Mask.String() == b.Mask.String()
}
func removeSubnetFromSubnets(subnets []net.IPNet, subnet net.IPNet) []net.IPNet {
L := len(subnets)
for i := range subnets {
if subnetsAreEqual(subnet, subnets[i]) {
subnets[i] = subnets[L-1]
subnets = subnets[:L-1]
break
}
}
return subnets
}

39
internal/firewall/vpn.go Normal file
View File

@@ -0,0 +1,39 @@
package firewall
import (
"context"
"fmt"
"github.com/qdm12/gluetun/internal/models"
)
func (c *configurator) SetVPNConnection(ctx context.Context, connection models.OpenVPNConnection) (err error) {
c.stateMutex.Lock()
defer c.stateMutex.Unlock()
if !c.enabled {
c.logger.Info("firewall disabled, only updating internal VPN connection")
c.vpnConnection = connection
return nil
}
c.logger.Info("setting VPN connection through firewall...")
if c.vpnConnection.Equal(connection) {
return nil
}
remove := true
if c.vpnConnection.IP != nil {
if err := c.acceptOutputTrafficToVPN(ctx, c.defaultInterface, c.vpnConnection, remove); err != nil {
c.logger.Error("cannot remove outdated VPN connection through firewall: %s", err)
}
}
c.vpnConnection = models.OpenVPNConnection{}
remove = false
if err := c.acceptOutputTrafficToVPN(ctx, c.defaultInterface, connection, remove); err != nil {
return fmt.Errorf("cannot set VPN connection through firewall: %w", err)
}
c.vpnConnection = connection
return nil
}

View File

@@ -0,0 +1,42 @@
package healthcheck
import (
"context"
"fmt"
"io/ioutil"
"net/http"
)
type Checker interface {
Check(ctx context.Context, url string) error
}
type checker struct {
httpClient *http.Client
}
func NewChecker(httpClient *http.Client) Checker {
return &checker{
httpClient: httpClient,
}
}
func (h *checker) Check(ctx context.Context, url string) error {
request, err := http.NewRequestWithContext(ctx, http.MethodGet, url, nil)
if err != nil {
return err
}
response, err := h.httpClient.Do(request)
if err != nil {
return err
}
defer response.Body.Close()
if response.StatusCode == http.StatusOK {
return nil
}
b, err := ioutil.ReadAll(response.Body)
if err != nil {
return err
}
return fmt.Errorf("%s: %s", response.Status, string(b))
}

View File

@@ -0,0 +1,49 @@
package healthcheck
import (
"errors"
"net/http"
"sync"
"github.com/qdm12/golibs/logging"
)
type handler struct {
logger logging.Logger
healthErr error
healthErrMu sync.RWMutex
}
var errHealthcheckNotRunYet = errors.New("healthcheck did not run yet")
func newHandler(logger logging.Logger) *handler {
return &handler{
logger: logger,
healthErr: errHealthcheckNotRunYet,
}
}
func (h *handler) ServeHTTP(responseWriter http.ResponseWriter, request *http.Request) {
if request.Method != http.MethodGet {
http.Error(responseWriter, "method not supported for healthcheck", http.StatusBadRequest)
return
}
if err := h.getErr(); err != nil {
h.logger.Error(err)
http.Error(responseWriter, err.Error(), http.StatusInternalServerError)
return
}
responseWriter.WriteHeader(http.StatusOK)
}
func (h *handler) setErr(err error) {
h.healthErrMu.Lock()
defer h.healthErrMu.Unlock()
h.healthErr = err
}
func (h *handler) getErr() (err error) {
h.healthErrMu.RLock()
defer h.healthErrMu.RUnlock()
return h.healthErr
}

View File

@@ -0,0 +1,66 @@
package healthcheck
import (
"context"
"errors"
"fmt"
"net"
"sync"
"time"
)
func (s *server) runHealthcheckLoop(ctx context.Context, wg *sync.WaitGroup) {
defer wg.Done()
for {
previousErr := s.handler.getErr()
err := healthCheck(ctx, s.resolver)
s.handler.setErr(err)
if previousErr != nil && err == nil {
s.logger.Info("passed")
}
if err != nil { // try again after 1 second
timer := time.NewTimer(time.Second)
select {
case <-ctx.Done():
if !timer.Stop() {
<-timer.C
}
return
case <-timer.C:
}
continue
}
// Success, check again in 10 minutes
const period = 10 * time.Minute
timer := time.NewTimer(period)
select {
case <-ctx.Done():
if !timer.Stop() {
<-timer.C
}
return
case <-timer.C:
}
}
}
var (
errNoIPResolved = errors.New("no IP address resolved")
)
func healthCheck(ctx context.Context, resolver *net.Resolver) (err error) {
// TODO use mullvad API if current provider is Mullvad
const domainToResolve = "github.com"
ips, err := resolver.LookupIP(ctx, "ip", domainToResolve)
switch {
case err != nil:
return err
case len(ips) == 0:
return fmt.Errorf("%w for %s", errNoIPResolved, domainToResolve)
default:
return nil
}
}

View File

@@ -1,24 +0,0 @@
package healthcheck
import (
"fmt"
"strings"
"time"
"github.com/qdm12/golibs/network"
)
func HealthCheck() error {
// DNS, HTTP and HTTPs check on github.com
connectivty := network.NewConnectivity(3 * time.Second)
errs := connectivty.Checks("github.com")
if len(errs) > 0 {
var errsStr []string
for _, err := range errs {
errsStr = append(errsStr, err.Error())
}
return fmt.Errorf("Multiple errors: %s", strings.Join(errsStr, "; "))
}
// TODO check IP address is in the right region
return nil
}

View File

@@ -0,0 +1,67 @@
package healthcheck
import (
"context"
"errors"
"net"
"net/http"
"sync"
"time"
"github.com/qdm12/golibs/logging"
)
type Server interface {
Run(ctx context.Context, wg *sync.WaitGroup)
}
type server struct {
address string
logger logging.Logger
handler *handler
resolver *net.Resolver
}
func NewServer(address string, logger logging.Logger) Server {
healthcheckLogger := logger.WithPrefix("healthcheck: ")
return &server{
address: address,
logger: healthcheckLogger,
handler: newHandler(healthcheckLogger),
resolver: net.DefaultResolver,
}
}
func (s *server) Run(ctx context.Context, wg *sync.WaitGroup) {
defer wg.Done()
internalWg := &sync.WaitGroup{}
internalWg.Add(1)
go s.runHealthcheckLoop(ctx, internalWg)
server := http.Server{
Addr: s.address,
Handler: s.handler,
}
internalWg.Add(1)
go func() {
defer internalWg.Done()
<-ctx.Done()
s.logger.Warn("context canceled: shutting down server")
defer s.logger.Warn("server shut down")
const shutdownGraceDuration = 2 * time.Second
shutdownCtx, cancel := context.WithTimeout(context.Background(), shutdownGraceDuration)
defer cancel()
if err := server.Shutdown(shutdownCtx); err != nil {
s.logger.Error("failed shutting down: %s", err)
}
}()
s.logger.Info("listening on %s", s.address)
err := server.ListenAndServe()
if err != nil && !errors.Is(ctx.Err(), context.Canceled) {
s.logger.Error(err)
}
internalWg.Wait()
}

View File

@@ -0,0 +1,24 @@
package httpproxy
import (
"fmt"
"net/http"
)
func (h *handler) isAccepted(responseWriter http.ResponseWriter, request *http.Request) bool {
// Not compatible with HTTP < 1.0 or HTTP >= 2.0 (see https://github.com/golang/go/issues/14797#issuecomment-196103814)
const (
minimalMajorVersion = 1
minimalMinorVersion = 0
maximumMajorVersion = 2
maximumMinorVersion = 0
)
if !request.ProtoAtLeast(minimalMajorVersion, minimalMinorVersion) ||
request.ProtoAtLeast(maximumMajorVersion, maximumMinorVersion) {
message := fmt.Sprintf("http version not supported: %s", request.Proto)
h.logger.Info("%s, from %s", message, request.RemoteAddr)
http.Error(responseWriter, message, http.StatusBadRequest)
return false
}
return true
}

View File

@@ -0,0 +1,41 @@
package httpproxy
import (
"encoding/base64"
"net/http"
"strings"
)
func (h *handler) isAuthorized(responseWriter http.ResponseWriter, request *http.Request) (authorized bool) {
if len(h.username) == 0 || (request.Method != "CONNECT" && !request.URL.IsAbs()) {
return true
}
basicAuth := request.Header.Get("Proxy-Authorization")
if len(basicAuth) == 0 {
h.logger.Info("Proxy-Authorization header not found from %s", request.RemoteAddr)
responseWriter.Header().Set("Proxy-Authenticate", `Basic realm="Access to Gluetun over HTTP"`)
responseWriter.WriteHeader(http.StatusProxyAuthRequired)
return false
}
b64UsernamePassword := strings.TrimPrefix(basicAuth, "Basic ")
b, err := base64.StdEncoding.DecodeString(b64UsernamePassword)
if err != nil {
h.logger.Info("Cannot decode Proxy-Authorization header value from %s: %s",
request.RemoteAddr, err.Error())
responseWriter.WriteHeader(http.StatusUnauthorized)
return false
}
usernamePassword := strings.Split(string(b), ":")
const expectedFields = 2
if len(usernamePassword) != expectedFields {
responseWriter.WriteHeader(http.StatusBadRequest)
return false
}
if h.username != usernamePassword[0] || h.password != usernamePassword[1] {
h.logger.Info("Username or password mismatch from %s", request.RemoteAddr)
h.logger.Debug("username provided %q and password provided %q", usernamePassword[0], usernamePassword[1])
responseWriter.WriteHeader(http.StatusUnauthorized)
return false
}
return true
}

View File

@@ -0,0 +1,64 @@
package httpproxy
import (
"context"
"net/http"
"sync"
"time"
"github.com/qdm12/golibs/logging"
)
func newHandler(ctx context.Context, wg *sync.WaitGroup, logger logging.Logger,
stealth, verbose bool, username, password string) http.Handler {
const httpTimeout = 24 * time.Hour
return &handler{
ctx: ctx,
wg: wg,
client: &http.Client{Timeout: httpTimeout},
logger: logger,
verbose: verbose,
stealth: stealth,
username: username,
password: password,
}
}
type handler struct {
ctx context.Context
wg *sync.WaitGroup
client *http.Client
logger logging.Logger
verbose, stealth bool
username, password string
}
func (h *handler) ServeHTTP(responseWriter http.ResponseWriter, request *http.Request) {
if !h.isAccepted(responseWriter, request) {
return
}
if !h.isAuthorized(responseWriter, request) {
return
}
request.Header.Del("Proxy-Connection")
request.Header.Del("Proxy-Authenticate")
request.Header.Del("Proxy-Authorization")
switch request.Method {
case http.MethodConnect:
h.handleHTTPS(responseWriter, request)
default:
h.handleHTTP(responseWriter, request)
}
}
// http://www.w3.org/Protocols/rfc2616/rfc2616-sec13.html
var hopHeaders = [...]string{ //nolint:gochecknoglobals
"Connection",
"Keep-Alive",
"Proxy-Authenticate",
"Proxy-Authorization",
"Te", // canonicalized version of "TE"
"Trailers",
"Transfer-Encoding",
"Upgrade",
}

View File

@@ -0,0 +1,71 @@
package httpproxy
import (
"fmt"
"io"
"net"
"net/http"
"strings"
)
func (h *handler) handleHTTP(responseWriter http.ResponseWriter, request *http.Request) {
switch request.URL.Scheme {
case "http", "https":
default:
h.logger.Warn("Unsupported scheme %q", request.URL.Scheme)
http.Error(responseWriter, "unsupported scheme", http.StatusBadRequest)
return
}
request = request.WithContext(h.ctx)
request.RequestURI = ""
for _, key := range hopHeaders {
request.Header.Del(key)
}
if !h.stealth {
setForwardedHeaders(request)
}
response, err := h.client.Do(request)
if err != nil {
http.Error(responseWriter, "server error", http.StatusInternalServerError)
h.logger.Warn("cannot request %s for client %q: %s",
request.URL, request.RemoteAddr, err)
return
}
defer response.Body.Close()
if h.verbose {
h.logger.Info("%s %s %s %s", request.RemoteAddr, response.Status, request.Method, request.URL)
}
for _, key := range hopHeaders {
response.Header.Del(key)
}
targetHeaderPtr := responseWriter.Header()
for key, values := range response.Header {
for _, value := range values {
targetHeaderPtr.Add(key, value)
}
}
responseWriter.WriteHeader(response.StatusCode)
if _, err := io.Copy(responseWriter, response.Body); err != nil {
h.logger.Error("%s %s: body copy error: %s", request.RemoteAddr, request.URL, err)
}
}
func setForwardedHeaders(request *http.Request) {
clientIP, _, err := net.SplitHostPort(request.RemoteAddr)
if err != nil {
return
}
// keep existing proxy headers
if prior, ok := request.Header["X-Forwarded-For"]; ok {
clientIP = fmt.Sprintf("%s,%s", strings.Join(prior, ", "), clientIP)
}
request.Header.Set("X-Forwarded-For", clientIP)
}

View File

@@ -0,0 +1,64 @@
package httpproxy
import (
"context"
"io"
"net"
"net/http"
"sync"
)
func (h *handler) handleHTTPS(responseWriter http.ResponseWriter, request *http.Request) {
dialer := net.Dialer{}
destinationConn, err := dialer.DialContext(h.ctx, "tcp", request.Host)
if err != nil {
http.Error(responseWriter, err.Error(), http.StatusServiceUnavailable)
return
}
responseWriter.WriteHeader(http.StatusOK)
hijacker, ok := responseWriter.(http.Hijacker)
if !ok {
http.Error(responseWriter, "Hijacking not supported", http.StatusInternalServerError)
return
}
clientConnection, _, err := hijacker.Hijack()
if err != nil {
h.logger.Warn(err)
http.Error(responseWriter, err.Error(), http.StatusServiceUnavailable)
if err := destinationConn.Close(); err != nil {
h.logger.Error("closing destination connection: %s", err)
}
return
}
if h.verbose {
h.logger.Info("%s <-> %s", request.RemoteAddr, request.Host)
}
h.wg.Add(1)
ctx, cancel := context.WithCancel(h.ctx)
const transferGoroutines = 2
wg := &sync.WaitGroup{}
wg.Add(transferGoroutines)
go func() { // trigger cleanup when done
wg.Wait()
cancel()
}()
go func() { // cleanup
<-ctx.Done()
destinationConn.Close()
clientConnection.Close()
h.wg.Done()
}()
go transfer(destinationConn, clientConnection, wg)
go transfer(clientConnection, destinationConn, wg)
}
func transfer(destination io.WriteCloser, source io.ReadCloser, wg *sync.WaitGroup) {
_, _ = io.Copy(destination, source)
_ = source.Close()
_ = destination.Close()
wg.Done()
}

134
internal/httpproxy/loop.go Normal file
View File

@@ -0,0 +1,134 @@
package httpproxy
import (
"context"
"fmt"
"sync"
"time"
"github.com/qdm12/gluetun/internal/constants"
"github.com/qdm12/gluetun/internal/models"
"github.com/qdm12/gluetun/internal/settings"
"github.com/qdm12/golibs/logging"
)
type Looper interface {
Run(ctx context.Context, wg *sync.WaitGroup)
SetStatus(status models.LoopStatus) (outcome string, err error)
GetStatus() (status models.LoopStatus)
GetSettings() (settings settings.HTTPProxy)
SetSettings(settings settings.HTTPProxy) (outcome string)
}
type looper struct {
state state
// Other objects
logger logging.Logger
// Internal channels and locks
loopLock sync.Mutex
running chan models.LoopStatus
stop, stopped chan struct{}
start chan struct{}
backoffTime time.Duration
}
const defaultBackoffTime = 10 * time.Second
func NewLooper(logger logging.Logger, settings settings.HTTPProxy) Looper {
return &looper{
state: state{
status: constants.Stopped,
settings: settings,
},
logger: logger.WithPrefix("http proxy: "),
start: make(chan struct{}),
running: make(chan models.LoopStatus),
stop: make(chan struct{}),
stopped: make(chan struct{}),
backoffTime: defaultBackoffTime,
}
}
func (l *looper) Run(ctx context.Context, wg *sync.WaitGroup) {
defer wg.Done()
crashed := false
if l.GetSettings().Enabled {
go func() {
_, _ = l.SetStatus(constants.Running)
}()
}
select {
case <-l.start:
case <-ctx.Done():
return
}
defer l.logger.Warn("loop exited")
for ctx.Err() == nil {
runCtx, runCancel := context.WithCancel(ctx)
settings := l.GetSettings()
address := fmt.Sprintf(":%d", settings.Port)
server := New(runCtx, address, l.logger, settings.Stealth, settings.Log, settings.User, settings.Password)
runWg := &sync.WaitGroup{}
runWg.Add(1)
errorCh := make(chan error)
go server.Run(runCtx, runWg, errorCh)
// TODO stable timer, check Shadowsocks
if !crashed {
l.running <- constants.Running
crashed = false
} else {
l.backoffTime = defaultBackoffTime
l.state.setStatusWithLock(constants.Running)
}
stayHere := true
for stayHere {
select {
case <-ctx.Done():
l.logger.Warn("context canceled: exiting loop")
runCancel()
runWg.Wait()
return
case <-l.start:
l.logger.Info("starting")
runCancel()
runWg.Wait()
stayHere = false
case <-l.stop:
l.logger.Info("stopping")
runCancel()
runWg.Wait()
l.stopped <- struct{}{}
case err := <-errorCh:
runWg.Wait()
l.state.setStatusWithLock(constants.Crashed)
l.logAndWait(ctx, err)
crashed = true
stayHere = false
}
}
runCancel() // repetition for linter only
}
}
func (l *looper) logAndWait(ctx context.Context, err error) {
l.logger.Error(err)
l.logger.Info("retrying in %s", l.backoffTime)
timer := time.NewTimer(l.backoffTime)
l.backoffTime *= 2
select {
case <-timer.C:
case <-ctx.Done():
if !timer.Stop() {
<-timer.C
}
}
}

View File

@@ -0,0 +1,54 @@
package httpproxy
import (
"context"
"net/http"
"sync"
"time"
"github.com/qdm12/golibs/logging"
)
type Server interface {
Run(ctx context.Context, wg *sync.WaitGroup, errorCh chan<- error)
}
type server struct {
address string
handler http.Handler
logger logging.Logger
internalWG *sync.WaitGroup
}
func New(ctx context.Context, address string, logger logging.Logger,
stealth, verbose bool, username, password string) Server {
wg := &sync.WaitGroup{}
return &server{
address: address,
handler: newHandler(ctx, wg, logger, stealth, verbose, username, password),
logger: logger,
internalWG: wg,
}
}
func (s *server) Run(ctx context.Context, wg *sync.WaitGroup, errorCh chan<- error) {
defer wg.Done()
server := http.Server{Addr: s.address, Handler: s.handler}
go func() {
<-ctx.Done()
s.logger.Warn("shutting down server")
defer s.logger.Warn("server shut down")
const shutdownGraceDuration = 2 * time.Second
shutdownCtx, cancel := context.WithTimeout(context.Background(), shutdownGraceDuration)
defer cancel()
if err := server.Shutdown(shutdownCtx); err != nil {
s.logger.Error("failed shutting down: %s", err)
}
}()
s.logger.Info("listening on %s", s.address)
err := server.ListenAndServe()
if err != nil && ctx.Err() == nil {
errorCh <- err
}
s.internalWG.Wait()
}

101
internal/httpproxy/state.go Normal file
View File

@@ -0,0 +1,101 @@
package httpproxy
import (
"fmt"
"reflect"
"sync"
"github.com/qdm12/gluetun/internal/constants"
"github.com/qdm12/gluetun/internal/models"
"github.com/qdm12/gluetun/internal/settings"
)
type state struct {
status models.LoopStatus
settings settings.HTTPProxy
statusMu sync.RWMutex
settingsMu sync.RWMutex
}
func (s *state) setStatusWithLock(status models.LoopStatus) {
s.statusMu.Lock()
defer s.statusMu.Unlock()
s.status = status
}
func (l *looper) GetStatus() (status models.LoopStatus) {
l.state.statusMu.RLock()
defer l.state.statusMu.RUnlock()
return l.state.status
}
func (l *looper) SetStatus(status models.LoopStatus) (outcome string, err error) {
l.state.statusMu.Lock()
defer l.state.statusMu.Unlock()
existingStatus := l.state.status
switch status {
case constants.Running:
switch existingStatus {
case constants.Starting, constants.Running, constants.Stopping, constants.Crashed:
return fmt.Sprintf("already %s", existingStatus), nil
}
l.loopLock.Lock()
defer l.loopLock.Unlock()
l.state.status = constants.Starting
l.state.statusMu.Unlock()
l.start <- struct{}{}
newStatus := <-l.running
l.state.statusMu.Lock()
l.state.status = newStatus
return newStatus.String(), nil
case constants.Stopped:
switch existingStatus {
case constants.Stopped, constants.Stopping, constants.Starting, constants.Crashed:
return fmt.Sprintf("already %s", existingStatus), nil
}
l.loopLock.Lock()
defer l.loopLock.Unlock()
l.state.status = constants.Stopping
l.state.statusMu.Unlock()
l.stop <- struct{}{}
<-l.stopped
l.state.statusMu.Lock()
l.state.status = status
return status.String(), nil
default:
return "", fmt.Errorf("status %q can only be %q or %q",
status, constants.Running, constants.Stopped)
}
}
func (l *looper) GetSettings() (settings settings.HTTPProxy) {
l.state.settingsMu.RLock()
defer l.state.settingsMu.RUnlock()
return l.state.settings
}
func (l *looper) SetSettings(settings settings.HTTPProxy) (outcome string) {
l.state.settingsMu.Lock()
settingsUnchanged := reflect.DeepEqual(settings, l.state.settings)
if settingsUnchanged {
l.state.settingsMu.Unlock()
return "settings left unchanged"
}
newEnabled := settings.Enabled
previousEnabled := l.state.settings.Enabled
l.state.settings = settings
l.state.settingsMu.Unlock()
// Either restart or set changed status
switch {
case !newEnabled && !previousEnabled:
case newEnabled && previousEnabled:
_, _ = l.SetStatus(constants.Stopped)
_, _ = l.SetStatus(constants.Running)
case newEnabled && !previousEnabled:
_, _ = l.SetStatus(constants.Running)
case !newEnabled && previousEnabled:
_, _ = l.SetStatus(constants.Stopped)
}
return "settings updated"
}

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