Compare commits
34 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
db886163c2 | ||
|
|
91f5338db0 | ||
|
|
82a02287ac | ||
|
|
2dc674559e | ||
|
|
38e713fea2 | ||
|
|
2cbb14c36c | ||
|
|
610e88958e | ||
|
|
bb76477467 | ||
|
|
433a799759 | ||
|
|
22965ccce3 | ||
|
|
4257581f55 | ||
|
|
d60d629105 | ||
|
|
3f721b1717 | ||
|
|
97049bfab4 | ||
|
|
84944a87d3 | ||
|
|
fb62910b17 | ||
|
|
1cc0f5fee9 | ||
|
|
6896cf4258 | ||
|
|
188d63c6b8 | ||
|
|
cbc5d466f6 | ||
|
|
aef14a9f6d | ||
|
|
f48392064e | ||
|
|
994bdd0ca7 | ||
|
|
40ed070f21 | ||
|
|
f1e4b9937b | ||
|
|
0423388b52 | ||
|
|
096a9c5fc0 | ||
|
|
7518f74729 | ||
|
|
854401a150 | ||
|
|
a7a7efe9c3 | ||
|
|
31883f9adb | ||
|
|
3b04677f8f | ||
|
|
b5fb2b849a | ||
|
|
0c9f74ffa4 |
5
.devcontainer/.dockerignore
Normal file
5
.devcontainer/.dockerignore
Normal file
@@ -0,0 +1,5 @@
|
||||
.dockerignore
|
||||
devcontainer.json
|
||||
docker-compose.yml
|
||||
Dockerfile
|
||||
README.md
|
||||
1
.devcontainer/Dockerfile
Normal file
1
.devcontainer/Dockerfile
Normal file
@@ -0,0 +1 @@
|
||||
FROM qmcgaw/godevcontainer
|
||||
68
.devcontainer/README.md
Normal file
68
.devcontainer/README.md
Normal 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.
|
||||
@@ -1,5 +1,5 @@
|
||||
{
|
||||
"name": "pia-dev",
|
||||
"name": "gluetun-dev",
|
||||
"dockerComposeFile": [
|
||||
"docker-compose.yml"
|
||||
],
|
||||
@@ -12,27 +12,25 @@
|
||||
"workspaceFolder": "/workspace",
|
||||
"extensions": [
|
||||
"golang.go",
|
||||
"IBM.output-colorizer",
|
||||
"eamodio.gitlens",
|
||||
"mhutchie.git-graph",
|
||||
"eamodio.gitlens", // IDE Git information
|
||||
"davidanson.vscode-markdownlint",
|
||||
"shardulm94.trailing-spaces",
|
||||
"alefragnani.Bookmarks",
|
||||
"Gruntfuggly.todo-tree",
|
||||
"mohsen1.prettify-json",
|
||||
"quicktype.quicktype",
|
||||
"spikespaz.vscode-smoothtype",
|
||||
"stkb.rewrap",
|
||||
"vscode-icons-team.vscode-icons"
|
||||
"ms-azuretools.vscode-docker", // Docker integration and linting
|
||||
"shardulm94.trailing-spaces", // Show trailing spaces
|
||||
"Gruntfuggly.todo-tree", // Highlights TODO comments
|
||||
"bierner.emojisense", // Emoji sense for markdown
|
||||
"stkb.rewrap", // rewrap comments after n characters on one line
|
||||
"vscode-icons-team.vscode-icons", // Better file extension icons
|
||||
"github.vscode-pull-request-github", // Github interaction
|
||||
"redhat.vscode-yaml", // Kubernetes, Drone syntax highlighting
|
||||
"bajdzis.vscode-database", // Supports connections to mysql or postgres, over SSL, socked
|
||||
"IBM.output-colorizer", // Colorize your output/test logs
|
||||
"mohsen1.prettify-json", // Prettify JSON data
|
||||
],
|
||||
"settings": {
|
||||
// General settings
|
||||
"files.eol": "\n",
|
||||
// Docker
|
||||
"remote.extensionKind": {
|
||||
"ms-azuretools.vscode-docker": "workspace"
|
||||
},
|
||||
// Golang general settings
|
||||
"go.useLanguageServer": true,
|
||||
"go.autocompleteUnimportedPackages": true,
|
||||
"go.gotoSymbol.includeImports": true,
|
||||
@@ -43,7 +41,6 @@
|
||||
"usePlaceholders": false
|
||||
},
|
||||
"go.lintTool": "golangci-lint",
|
||||
// Golang on save
|
||||
"go.buildOnSave": "workspace",
|
||||
"go.lintOnSave": "workspace",
|
||||
"go.vetOnSave": "workspace",
|
||||
@@ -53,20 +50,21 @@
|
||||
"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",
|
||||
// "-race"
|
||||
],
|
||||
"go.testTimeout": "600s",
|
||||
"go.testTimeout": "10s",
|
||||
"go.coverOnSingleTest": true,
|
||||
"go.coverOnSingleTestFile": true,
|
||||
"go.coverOnSingleTest": true
|
||||
"go.coverOnTestPackage": true
|
||||
}
|
||||
}
|
||||
@@ -2,14 +2,24 @@ version: "3.7"
|
||||
|
||||
services:
|
||||
vscode:
|
||||
image: qmcgaw/godevcontainer
|
||||
build: .
|
||||
image: godevcontainer
|
||||
volumes:
|
||||
- ../:/workspace
|
||||
# Docker socket to access Docker server
|
||||
- /var/run/docker.sock:/var/run/docker.sock
|
||||
# SSH directory
|
||||
- ~/.ssh:/home/vscode/.ssh
|
||||
- ~/.ssh:/root/.ssh
|
||||
- /var/run/docker.sock:/var/run/docker.sock
|
||||
# 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"
|
||||
|
||||
46
.github/ISSUE_TEMPLATE/bug.md
vendored
46
.github/ISSUE_TEMPLATE/bug.md
vendored
@@ -7,49 +7,31 @@ assignees: qdm12
|
||||
|
||||
---
|
||||
|
||||
**TLDR**: *Describe your issue in a one liner here*
|
||||
**Host OS** (approximate answer is fine too): Ubuntu 18
|
||||
|
||||
1. Is this urgent?
|
||||
**Is this urgent?**: No
|
||||
|
||||
- [ ] Yes
|
||||
- [x] No
|
||||
**What VPN provider are you using**:
|
||||
|
||||
2. What VPN service provider are you using?
|
||||
**What are you using to run your container?**: Docker Compose
|
||||
|
||||
- [x] PIA
|
||||
- [ ] Mullvad
|
||||
- [ ] Windscribe
|
||||
- [ ] Surfshark
|
||||
- [ ] Cyberghost
|
||||
**What is the version of the program** (See the line at the top of your logs)
|
||||
|
||||
3. What's the version of the program?
|
||||
```
|
||||
Running version latest built on 2020-03-13T01:30:06Z (commit d0f678c)
|
||||
```
|
||||
|
||||
**See the line at the top of your logs**
|
||||
**What's the problem** 🤔
|
||||
|
||||
`Running version latest built on 2020-03-13T01:30:06Z (commit d0f678c)`
|
||||
That feature doesn't work
|
||||
|
||||
4. What are you using to run the container?
|
||||
**Share your logs...**
|
||||
|
||||
- [ ] Docker run
|
||||
- [x] Docker Compose
|
||||
- [ ] Kubernetes
|
||||
- [ ] Docker stack
|
||||
- [ ] Docker swarm
|
||||
- [ ] Podman
|
||||
- [ ] Other:
|
||||
|
||||
5. Extra information
|
||||
|
||||
Logs:
|
||||
...*careful to remove i.e. token information with PIA port forwarding*
|
||||
|
||||
```log
|
||||
|
||||
```
|
||||
|
||||
Configuration file:
|
||||
|
||||
```yml
|
||||
PASTE YOUR LOGS
|
||||
IN THERE
|
||||
|
||||
```
|
||||
|
||||
Host OS:
|
||||
|
||||
11
.github/ISSUE_TEMPLATE/feature_request.md
vendored
11
.github/ISSUE_TEMPLATE/feature_request.md
vendored
@@ -1,14 +1,17 @@
|
||||
---
|
||||
name: Feature request
|
||||
about: Suggest a feature to add to this project
|
||||
title: 'Feature request: ...'
|
||||
title: 'Feature request: FILL THIS TEXT!'
|
||||
labels: ":bulb: feature request"
|
||||
assignees: qdm12
|
||||
|
||||
---
|
||||
|
||||
1. What's the feature?
|
||||
**What's the feature?** 🧐
|
||||
|
||||
2. Why do you need this feature?
|
||||
- Support this new feature because that and that
|
||||
|
||||
3. Extra information?
|
||||
**Optional extra information** 🚀
|
||||
|
||||
- I tried `docker run something` and it doesn't work
|
||||
- That [url](https://github.com/qdm12/gluetun) is interesting
|
||||
|
||||
56
.github/ISSUE_TEMPLATE/help.md
vendored
56
.github/ISSUE_TEMPLATE/help.md
vendored
@@ -7,49 +7,47 @@ assignees:
|
||||
|
||||
---
|
||||
|
||||
**TLDR**: *Describe your issue in a one liner here*
|
||||
**Host OS** (approximate answer is fine too): Ubuntu 18
|
||||
|
||||
1. Is this urgent?
|
||||
**Is this urgent?**: No
|
||||
|
||||
- [ ] Yes
|
||||
- [x] No
|
||||
**What VPN provider are you using**:
|
||||
|
||||
2. What VPN service provider are you using?
|
||||
**What is the version of the program** (See the line at the top of your logs)
|
||||
|
||||
- [x] PIA
|
||||
- [ ] Mullvad
|
||||
- [ ] Windscribe
|
||||
- [ ] Surfshark
|
||||
- [ ] Cyberghost
|
||||
```
|
||||
Running version latest built on 2020-03-13T01:30:06Z (commit d0f678c)
|
||||
```
|
||||
|
||||
3. What's the version of the program?
|
||||
**What's the problem** 🤔
|
||||
|
||||
**See the line at the top of your logs**
|
||||
That feature doesn't work
|
||||
|
||||
`Running version latest built on 2020-03-13T01:30:06Z (commit d0f678c)`
|
||||
**Share your logs...**
|
||||
|
||||
4. What are you using to run the container?
|
||||
|
||||
- [ ] Docker run
|
||||
- [x] Docker Compose
|
||||
- [ ] Kubernetes
|
||||
- [ ] Docker stack
|
||||
- [ ] Docker swarm
|
||||
- [ ] Podman
|
||||
- [ ] Other:
|
||||
|
||||
5. Extra information
|
||||
|
||||
Logs:
|
||||
...*careful to remove i.e. token information with PIA port forwarding*
|
||||
|
||||
```log
|
||||
|
||||
PASTE YOUR LOGS
|
||||
IN THERE
|
||||
|
||||
```
|
||||
|
||||
Configuration file:
|
||||
**What are you using to run your container?**: Docker Compose
|
||||
|
||||
Please also share your configuration file:
|
||||
|
||||
```yml
|
||||
|
||||
your .yml
|
||||
content
|
||||
in here
|
||||
```
|
||||
|
||||
Host OS:
|
||||
or
|
||||
|
||||
```sh
|
||||
# your docker
|
||||
# run command
|
||||
# in here
|
||||
```
|
||||
|
||||
96
.github/labels.yml
vendored
96
.github/labels.yml
vendored
@@ -1,51 +1,67 @@
|
||||
- name: ":robot: bot"
|
||||
color: "69cde9"
|
||||
description: ""
|
||||
- name: ":bug: bug"
|
||||
- name: "Bug :bug:"
|
||||
color: "b60205"
|
||||
description: ""
|
||||
- name: ":game_die: dependencies"
|
||||
color: "0366d6"
|
||||
description: ""
|
||||
- name: ":memo: documentation"
|
||||
color: "c5def5"
|
||||
description: ""
|
||||
- name: ":busts_in_silhouette: duplicate"
|
||||
color: "cccccc"
|
||||
description: ""
|
||||
- name: ":sparkles: enhancement"
|
||||
color: "0054ca"
|
||||
description: ""
|
||||
- name: ":bulb: feature request"
|
||||
- name: "Feature request :bulb:"
|
||||
color: "0e8a16"
|
||||
description: ""
|
||||
- name: ":mega: feedback"
|
||||
color: "03a9f4"
|
||||
description: ""
|
||||
- name: ":rocket: future maybe"
|
||||
color: "fef2c0"
|
||||
description: ""
|
||||
- name: ":hatching_chick: good first issue"
|
||||
color: "7057ff"
|
||||
description: ""
|
||||
- name: ":pray: help wanted"
|
||||
- name: "Help wanted :pray:"
|
||||
color: "4caf50"
|
||||
description: ""
|
||||
- name: ":hand: hold"
|
||||
color: "24292f"
|
||||
- name: "Documentation :memo:"
|
||||
color: "c5def5"
|
||||
description: ""
|
||||
- name: ":no_entry_sign: invalid"
|
||||
color: "e6e6e6"
|
||||
description: ""
|
||||
- name: ":interrobang: maybe bug"
|
||||
color: "ff5722"
|
||||
description: ""
|
||||
- name: ":thinking: needs more info"
|
||||
- name: "Needs more info :thinking:"
|
||||
color: "795548"
|
||||
description: ""
|
||||
- name: ":question: question"
|
||||
color: "3f51b5"
|
||||
|
||||
# VPN providers
|
||||
- name: ":cloud: Cyberghost"
|
||||
color: "cfe8d4"
|
||||
description: ""
|
||||
- name: ":coffin: wontfix"
|
||||
color: "ffffff"
|
||||
- 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: ""
|
||||
|
||||
31
.github/workflows/build.yml
vendored
31
.github/workflows/build.yml
vendored
@@ -2,28 +2,15 @@ name: Docker build
|
||||
on:
|
||||
pull_request:
|
||||
branches: [master]
|
||||
paths-ignore:
|
||||
- .devcontainer
|
||||
- .github/ISSUE_TEMPLATE
|
||||
- .github/workflows/buildx-release.yml
|
||||
- .github/workflows/buildx-branch.yml
|
||||
- .github/workflows/buildx-latest.yml
|
||||
- .github/workflows/dockerhub-description.yml
|
||||
- .github/workflows/labels.yml
|
||||
- .github/workflows/misspell.yml
|
||||
- .github/CODEOWNERS
|
||||
- .github/CONTRIBUTING.md
|
||||
- .github/FUNDING.yml
|
||||
- .github/labels.yml
|
||||
- .vscode
|
||||
- cmd/ovpnparser
|
||||
- cmd/resolver
|
||||
- doc
|
||||
- .gitignore
|
||||
- docker-compose.yml
|
||||
- LICENSE
|
||||
- README.md
|
||||
- title.svg
|
||||
paths:
|
||||
- .github/workflows/build.yml
|
||||
- cmd/**
|
||||
- internal/**
|
||||
- .dockerignore
|
||||
- .golangci.yml
|
||||
- Dockerfile
|
||||
- go.mod
|
||||
- go.sum
|
||||
jobs:
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
31
.github/workflows/buildx-branch.yml
vendored
31
.github/workflows/buildx-branch.yml
vendored
@@ -5,28 +5,15 @@ on:
|
||||
- '*'
|
||||
- '*/*'
|
||||
- '!master'
|
||||
paths-ignore:
|
||||
- .devcontainer
|
||||
- .github/ISSUE_TEMPLATE
|
||||
- .github/workflows/build.yml
|
||||
- .github/workflows/buildx-release.yml
|
||||
- .github/workflows/buildx-latest.yml
|
||||
- .github/workflows/dockerhub-description.yml
|
||||
- .github/workflows/labels.yml
|
||||
- .github/workflows/misspell.yml
|
||||
- .github/CODEOWNERS
|
||||
- .github/CONTRIBUTING.md
|
||||
- .github/FUNDING.yml
|
||||
- .github/labels.yml
|
||||
- .vscode
|
||||
- cmd/ovpnparser
|
||||
- cmd/resolver
|
||||
- doc
|
||||
- .gitignore
|
||||
- docker-compose.yml
|
||||
- LICENSE
|
||||
- README.md
|
||||
- title.svg
|
||||
paths:
|
||||
- .github/workflows/buildx-branch.yml
|
||||
- cmd/**
|
||||
- internal/**
|
||||
- .dockerignore
|
||||
- .golangci.yml
|
||||
- Dockerfile
|
||||
- go.mod
|
||||
- go.sum
|
||||
jobs:
|
||||
buildx:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
31
.github/workflows/buildx-latest.yml
vendored
31
.github/workflows/buildx-latest.yml
vendored
@@ -2,28 +2,15 @@ name: Buildx latest
|
||||
on:
|
||||
push:
|
||||
branches: [master]
|
||||
paths-ignore:
|
||||
- .devcontainer
|
||||
- .github/ISSUE_TEMPLATE
|
||||
- .github/workflows/build.yml
|
||||
- .github/workflows/buildx-branch.yml
|
||||
- .github/workflows/buildx-release.yml
|
||||
- .github/workflows/dockerhub-description.yml
|
||||
- .github/workflows/labels.yml
|
||||
- .github/workflows/misspell.yml
|
||||
- .github/CODEOWNERS
|
||||
- .github/CONTRIBUTING.md
|
||||
- .github/FUNDING.yml
|
||||
- .github/labels.yml
|
||||
- .vscode
|
||||
- cmd/ovpnparser
|
||||
- cmd/resolver
|
||||
- doc
|
||||
- .gitignore
|
||||
- docker-compose.yml
|
||||
- LICENSE
|
||||
- README.md
|
||||
- title.svg
|
||||
paths:
|
||||
- .github/workflows/buildx-latest.yml
|
||||
- cmd/**
|
||||
- internal/**
|
||||
- .dockerignore
|
||||
- .golangci.yml
|
||||
- Dockerfile
|
||||
- go.mod
|
||||
- go.sum
|
||||
jobs:
|
||||
buildx:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
31
.github/workflows/buildx-release.yml
vendored
31
.github/workflows/buildx-release.yml
vendored
@@ -2,28 +2,15 @@ name: Buildx release
|
||||
on:
|
||||
release:
|
||||
types: [published]
|
||||
paths-ignore:
|
||||
- .devcontainer
|
||||
- .github/ISSUE_TEMPLATE
|
||||
- .github/workflows/build.yml
|
||||
- .github/workflows/buildx-branch.yml
|
||||
- .github/workflows/buildx-latest.yml
|
||||
- .github/workflows/dockerhub-description.yml
|
||||
- .github/workflows/labels.yml
|
||||
- .github/workflows/misspell.yml
|
||||
- .github/CODEOWNERS
|
||||
- .github/CONTRIBUTING.md
|
||||
- .github/FUNDING.yml
|
||||
- .github/labels.yml
|
||||
- .vscode
|
||||
- cmd/ovpnparser
|
||||
- cmd/resolver
|
||||
- doc
|
||||
- .gitignore
|
||||
- docker-compose.yml
|
||||
- LICENSE
|
||||
- README.md
|
||||
- title.svg
|
||||
paths:
|
||||
- .github/workflows/buildx-release.yml
|
||||
- cmd/**
|
||||
- internal/**
|
||||
- .dockerignore
|
||||
- .golangci.yml
|
||||
- Dockerfile
|
||||
- go.mod
|
||||
- go.sum
|
||||
jobs:
|
||||
buildx:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
6
.github/workflows/labels.yml
vendored
6
.github/workflows/labels.yml
vendored
@@ -1,10 +1,10 @@
|
||||
name: labels
|
||||
on:
|
||||
push:
|
||||
branches: ["master"]
|
||||
branches: [master]
|
||||
paths:
|
||||
- '.github/labels.yml'
|
||||
- '.github/workflows/labels.yml'
|
||||
- .github/labels.yml
|
||||
- .github/workflows/labels.yml
|
||||
jobs:
|
||||
labeler:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
@@ -4,6 +4,13 @@ linters-settings:
|
||||
misspell:
|
||||
locale: US
|
||||
|
||||
issues:
|
||||
exclude-rules:
|
||||
- path: _test\.go
|
||||
linters:
|
||||
- dupl
|
||||
- maligned
|
||||
|
||||
linters:
|
||||
disable-all: true
|
||||
enable:
|
||||
@@ -57,4 +64,4 @@ run:
|
||||
skip-dirs:
|
||||
- .devcontainer
|
||||
- .github
|
||||
- postgres
|
||||
- doc
|
||||
|
||||
32
Dockerfile
32
Dockerfile
@@ -4,7 +4,7 @@ ARG GO_VERSION=1.15
|
||||
FROM golang:${GO_VERSION}-alpine${ALPINE_VERSION} AS builder
|
||||
RUN apk --update add git
|
||||
ENV CGO_ENABLED=0
|
||||
ARG GOLANGCI_LINT_VERSION=v1.31.0
|
||||
ARG GOLANGCI_LINT_VERSION=v1.33.0
|
||||
RUN wget -O- -nv https://raw.githubusercontent.com/golangci/golangci-lint/master/install.sh | sh -s ${GOLANGCI_LINT_VERSION}
|
||||
WORKDIR /tmp/gobuild
|
||||
COPY .golangci.yml .
|
||||
@@ -18,10 +18,10 @@ COPY internal/ ./internal/
|
||||
RUN go test ./...
|
||||
RUN golangci-lint run --timeout=10m
|
||||
RUN go build -trimpath -ldflags="-s -w \
|
||||
-X 'main.version=$VERSION' \
|
||||
-X 'main.buildDate=$BUILD_DATE' \
|
||||
-X 'main.commit=$COMMIT' \
|
||||
" -o entrypoint main.go
|
||||
-X 'main.version=$VERSION' \
|
||||
-X 'main.buildDate=$BUILD_DATE' \
|
||||
-X 'main.commit=$COMMIT' \
|
||||
" -o entrypoint main.go
|
||||
|
||||
FROM alpine:${ALPINE_VERSION}
|
||||
ARG VERSION=unknown
|
||||
@@ -47,7 +47,7 @@ ENV VPNSP=pia \
|
||||
TZ= \
|
||||
UID=1000 \
|
||||
GID=1000 \
|
||||
IP_STATUS_FILE="/tmp/gluetun/ip" \
|
||||
PUBLICIP_FILE="/tmp/gluetun/ip" \
|
||||
# PIA, Windscribe, Surfshark, Cyberghost, Vyprvpn, NordVPN, PureVPN only
|
||||
USER= \
|
||||
PASSWORD= \
|
||||
@@ -58,7 +58,10 @@ ENV VPNSP=pia \
|
||||
PORT_FORWARDING_STATUS_FILE="/tmp/gluetun/forwarded_port" \
|
||||
# Mullvad and PureVPN only
|
||||
COUNTRY= \
|
||||
# Mullvad, PureVPN, Windscribe only
|
||||
CITY= \
|
||||
# Windscribe only
|
||||
HOSTNAME= \
|
||||
# Mullvad only
|
||||
ISP= \
|
||||
OWNED=no \
|
||||
@@ -93,12 +96,12 @@ ENV VPNSP=pia \
|
||||
FIREWALL_INPUT_PORTS= \
|
||||
FIREWALL_OUTBOUND_SUBNETS= \
|
||||
FIREWALL_DEBUG=off \
|
||||
# Tinyproxy
|
||||
TINYPROXY=off \
|
||||
TINYPROXY_LOG=Info \
|
||||
TINYPROXY_PORT=8888 \
|
||||
TINYPROXY_USER= \
|
||||
TINYPROXY_PASSWORD= \
|
||||
# HTTP proxy
|
||||
HTTPPROXY= \
|
||||
HTTPPROXY_LOG=off \
|
||||
HTTPPROXY_PORT=8888 \
|
||||
HTTPPROXY_USER= \
|
||||
HTTPPROXY_PASSWORD= \
|
||||
# Shadowsocks
|
||||
SHADOWSOCKS=off \
|
||||
SHADOWSOCKS_LOG=off \
|
||||
@@ -109,10 +112,9 @@ ENV VPNSP=pia \
|
||||
ENTRYPOINT ["/entrypoint"]
|
||||
EXPOSE 8000/tcp 8888/tcp 8388/tcp 8388/udp
|
||||
HEALTHCHECK --interval=10m --timeout=10s --start-period=30s --retries=2 CMD /entrypoint healthcheck
|
||||
RUN apk add -q --progress --no-cache --update openvpn ca-certificates iptables ip6tables unbound tinyproxy tzdata && \
|
||||
rm -rf /var/cache/apk/* /etc/unbound/* /usr/sbin/unbound-* /etc/tinyproxy/tinyproxy.conf && \
|
||||
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 tinyproxy && \
|
||||
deluser unbound && \
|
||||
mkdir /gluetun
|
||||
# TODO remove once SAN is added to PIA servers certificates, see https://github.com/pia-foss/manual-connections/issues/10
|
||||
|
||||
69
README.md
69
README.md
@@ -1,8 +1,7 @@
|
||||
# Gluetun VPN client
|
||||
|
||||
*Lightweight swiss-knife-like VPN client to tunnel to Private Internet Access,
|
||||
Mullvad, Windscribe, Surfshark Cyberghost, VyprVPN, NordVPN and PureVPN VPN servers, using Go, OpenVPN,
|
||||
iptables, DNS over TLS, ShadowSocks and Tinyproxy*
|
||||
Mullvad, Windscribe, Surfshark Cyberghost, VyprVPN, NordVPN, PureVPN and Privado VPN servers, using Go, OpenVPN, iptables, DNS over TLS, ShadowSocks and an HTTP proxy*
|
||||
|
||||
**ANNOUNCEMENT**: *Github Wiki reworked*
|
||||
|
||||
@@ -29,14 +28,14 @@ iptables, DNS over TLS, ShadowSocks and Tinyproxy*
|
||||
## Features
|
||||
|
||||
- Based on Alpine 3.12 for a small Docker image of 52MB
|
||||
- Supports **Private Internet Access** (new and old), **Mullvad**, **Windscribe**, **Surfshark**, **Cyberghost**, **Vyprvpn**, **NordVPN** and **PureVPN** servers
|
||||
- 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 (Tinyproxy, tunnels TCP)
|
||||
- Built in HTTP proxy (tunnels HTTP and HTTPS through TCP)
|
||||
- [Connect other containers to it](https://github.com/qdm12/gluetun#connect-to-it)
|
||||
- [Connect LAN devices to it](https://github.com/qdm12/gluetun#connect-to-it)
|
||||
- Compatible with amd64, i686 (32 bit), **ARM** 64 bit, ARM 32 bit v6 and v7 🎆
|
||||
@@ -97,8 +96,8 @@ docker run --rm --network=container:gluetun alpine:3.12 wget -qO- https://ipinfo
|
||||
|
||||
| Variable | Default | Choices | Description |
|
||||
| --- | --- | --- | --- |
|
||||
| 🏁 `VPNSP` | `private internet access` | `private internet access`, `private internet access old`, `mullvad`, `windscribe`, `surfshark`, `vyprvpn`, `nordvpn`, `purevpn` | VPN Service Provider |
|
||||
| `IP_STATUS_FILE` | `/tmp/gluetun/ip` | Any filepath | Filepath to store the public IP address assigned |
|
||||
| 🏁 `VPNSP` | `private internet access` | `private internet access`, `mullvad`, `windscribe`, `surfshark`, `vyprvpn`, `nordvpn`, `purevpn`, `privado` | VPN Service Provider |
|
||||
| `PUBLICIP_FILE` | `/tmp/gluetun/ip` | Any filepath | Filepath to store the public IP address assigned |
|
||||
| `PROTOCOL` | `udp` | `udp` or `tcp` | Network protocol to use |
|
||||
| `OPENVPN_VERBOSITY` | `1` | `0` to `6` | Openvpn verbosity level |
|
||||
| `OPENVPN_ROOT` | `no` | `yes` or `no` | Run OpenVPN as root |
|
||||
@@ -135,13 +134,15 @@ docker run --rm --network=container:gluetun alpine:3.12 wget -qO- https://ipinfo
|
||||
|
||||
For **port forwarding**, obtain a port from [here](https://mullvad.net/en/account/#/ports) and add it to `FIREWALL_VPN_INPUT_PORTS`
|
||||
|
||||
- Windscribe
|
||||
- Windscribe (see [this](https://github.com/qdm12/gluetun/blob/master/internal/constants/windscribe.go#L43) for the choices of regions, cities and hostnames)
|
||||
|
||||
| Variable | Default | Choices | Description |
|
||||
| --- | --- | --- | --- |
|
||||
| 🏁 `USER` | | | Your username |
|
||||
| 🏁 `PASSWORD` | | | Your password |
|
||||
| `REGION` | | One of the [Windscribe regions](https://windscribe.com/status) | VPN server region |
|
||||
| `REGION` | | | Comma separated list of regions to choose the VPN server |
|
||||
| `CITY` | | | Comma separated list of cities to choose the VPN server |
|
||||
| `HOSTNAME` | | | Comma separated list of hostnames to choose the VPN server |
|
||||
| `PORT` | | One from the [this list of ports](https://windscribe.com/getconfig/openvpn) | Custom VPN port to use |
|
||||
|
||||
- Surfshark
|
||||
@@ -158,20 +159,15 @@ docker run --rm --network=container:gluetun alpine:3.12 wget -qO- https://ipinfo
|
||||
| --- | --- | --- | --- |
|
||||
| 🏁 `USER` | | | Your username |
|
||||
| 🏁 `PASSWORD` | | | Your password |
|
||||
| 🏁 `CLIENT_KEY` | | | Your device client key content, **see below** |
|
||||
| 🏁 | | | **See additional setup steps below** |
|
||||
| `REGION` | | One of the Cyberghost regions, [Wiki page](https://github.com/qdm12/gluetun/wiki/Cyberghost-Servers) | VPN server country |
|
||||
| `CYBERGHOST_GROUP` | `Premium UDP Europe` | One of the server groups (see above Wiki page) | Server group |
|
||||
|
||||
To specify your client key, you can either:
|
||||
**Additional setup steps**: Bind mount your `client.key` file to `/gluetun/client.key` and your `client.crt` file to `/gluetun/client.crt`. For example, you can use with your `docker run` command:
|
||||
|
||||
- Bind mount it at `/files/client.key`, for example with `-v /yourpath/client.key:/files/client.key:ro`
|
||||
- Convert it to a single line value using:
|
||||
|
||||
```sh
|
||||
docker run -it --rm -v /yourpath/client.key:/files/client.key:ro qmcgaw/private-internet-access clientkey
|
||||
```
|
||||
|
||||
And use the line produced as the value for the environment variable `CLIENT_KEY`.
|
||||
```sh
|
||||
-v /yourpath/client.key:/gluetun/client.key:ro -v /yourpath/client.crt:/gluetun/client.crt:ro
|
||||
```
|
||||
|
||||
- Vyprvpn
|
||||
|
||||
@@ -196,11 +192,20 @@ docker run --rm --network=container:gluetun alpine:3.12 wget -qO- https://ipinfo
|
||||
|
||||
| Variable | Default | Choices | Description |
|
||||
| --- | --- | --- | --- |
|
||||
| 🏁 `USER` | | | Your user ID |
|
||||
| 🏁 `REGION` | | One of the [PureVPN regions](https://support.purevpn.com/vpn-servers) | VPN server region |
|
||||
| 🏁 `USER` | | | Your username |
|
||||
| 🏁 `PASSWORD` | | | Your password |
|
||||
| `REGION` | | One of the [PureVPN regions](https://support.purevpn.com/vpn-servers) | VPN server region |
|
||||
| `COUNTRY` | | One of the [PureVPN countries](https://support.purevpn.com/vpn-servers) | VPN server country |
|
||||
| `CITY` | | One of the [PureVPN cities](https://support.purevpn.com/vpn-servers) | VPN server city |
|
||||
|
||||
- Privado
|
||||
|
||||
| Variable | Default | Choices | Description |
|
||||
| --- | --- | --- | --- |
|
||||
| 🏁 `USER` | | | Your username |
|
||||
| 🏁 `PASSWORD` | | | Your password |
|
||||
| `HOSTNAME` | | [One of the Privado hostname](internal/constants/privado.go#L26), i.e. `ams-001.vpn.privado.io` | VPN server hostname |
|
||||
|
||||
### DNS over TLS
|
||||
|
||||
None of the following values are required.
|
||||
@@ -243,15 +248,16 @@ None of the following values are required.
|
||||
| `SHADOWSOCKS_PASSWORD` | | | Password to use to connect to Shadowsocks |
|
||||
| `SHADOWSOCKS_METHOD` | `chacha20-ietf-poly1305` | `chacha20-ietf-poly1305`, `aes-128-gcm`, `aes-256-gcm` | Method to use for Shadowsocks |
|
||||
|
||||
### Tinyproxy
|
||||
### HTTP proxy
|
||||
|
||||
| Variable | Default | Choices | Description |
|
||||
| --- | --- | --- | --- |
|
||||
| `TINYPROXY` | `off` | `on`, `off` | Enable the internal HTTP proxy tinyproxy |
|
||||
| `TINYPROXY_LOG` | `Info` | `Info`, `Connect`, `Notice`, `Warning`, `Error`, `Critical` | Tinyproxy log level |
|
||||
| `TINYPROXY_PORT` | `8888` | `1024` to `65535` | Internal port number for Tinyproxy to listen on |
|
||||
| `TINYPROXY_USER` | | | Username to use to connect to Tinyproxy |
|
||||
| `TINYPROXY_PASSWORD` | | | Password to use to connect to Tinyproxy |
|
||||
| `HTTPPROXY` | `off` | `on`, `off` | Enable the internal HTTP proxy |
|
||||
| `HTTPPROXY_LOG` | `off` | `on` or `off` | Logs every tunnel requests |
|
||||
| `HTTPPROXY_PORT` | `8888` | `1024` to `65535` | Internal port number for the HTTP proxy to listen on |
|
||||
| `HTTPPROXY_USER` | | | Username to use to connect to the HTTP proxy |
|
||||
| `HTTPPROXY_PASSWORD` | | | Password to use to connect to the HTTP proxy |
|
||||
| `HTTPPROXY_STEALTH` | `off` | `on` or `off` | Stealth mode means HTTP proxy headers are not added to your requests |
|
||||
|
||||
### System
|
||||
|
||||
@@ -295,15 +301,16 @@ There are various ways to achieve this, depending on your use case.
|
||||
Add `network_mode: "container:gluetun"` to your *docker-compose.yml*, provided Gluetun is already running
|
||||
|
||||
</p></details>
|
||||
- <details><summary>Connect LAN devices through the built-in HTTP proxy *Tinyproxy* (i.e. with Chrome, Kodi, etc.)</summary><p>
|
||||
- <details><summary>Connect LAN devices through the built-in HTTP proxy (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.
|
||||
⚠️ You might want to use Shadowsocks instead which tunnels UDP as well as TCP and does not leak your credentials.
|
||||
The HTTP proxy will not encrypt your username and password every time you send a request to the HTTP proxy server.
|
||||
|
||||
1. Setup a HTTP proxy client, such as [SwitchyOmega for Chrome](https://chrome.google.com/webstore/detail/proxy-switchyomega/padekgcemlokbadohgkifijomclgjgif?hl=en)
|
||||
1. Setup an HTTP proxy client, such as [SwitchyOmega for Chrome](https://chrome.google.com/webstore/detail/proxy-switchyomega/padekgcemlokbadohgkifijomclgjgif?hl=en)
|
||||
1. Ensure the Gluetun container is launched with:
|
||||
- port `8888` published `-p 8888:8888/tcp`
|
||||
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
|
||||
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 `HTTPPROXY_USER` and `HTTPPROXY_PASSWORD`. Note that Chrome does not support authentication.
|
||||
1. If you set `HTTPPROXY_LOG` to `on`, more information will be logged in the Docker logs
|
||||
|
||||
</p></details>
|
||||
- <details><summary>Connect LAN devices through the built-in *Shadowsocks* proxy (per app, system wide, etc.)</summary><p>
|
||||
|
||||
@@ -18,7 +18,9 @@ import (
|
||||
"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"
|
||||
@@ -27,7 +29,6 @@ import (
|
||||
"github.com/qdm12/gluetun/internal/settings"
|
||||
"github.com/qdm12/gluetun/internal/shadowsocks"
|
||||
"github.com/qdm12/gluetun/internal/storage"
|
||||
"github.com/qdm12/gluetun/internal/tinyproxy"
|
||||
"github.com/qdm12/gluetun/internal/updater"
|
||||
versionpkg "github.com/qdm12/gluetun/internal/version"
|
||||
"github.com/qdm12/golibs/command"
|
||||
@@ -38,12 +39,16 @@ import (
|
||||
|
||||
//nolint:gochecknoglobals
|
||||
var (
|
||||
buildInfo models.BuildInformation
|
||||
version = "unknown"
|
||||
commit = "unknown"
|
||||
buildDate = "an unknown date"
|
||||
)
|
||||
|
||||
func main() {
|
||||
buildInfo.Version = version
|
||||
buildInfo.Commit = commit
|
||||
buildInfo.BuildDate = buildDate
|
||||
ctx := context.Background()
|
||||
os.Exit(_main(ctx, os.Args))
|
||||
}
|
||||
@@ -83,17 +88,15 @@ func _main(background context.Context, args []string) int { //nolint:gocognit,go
|
||||
dnsConf := dns.NewConfigurator(logger, client, fileManager)
|
||||
routingConf := routing.NewRouting(logger)
|
||||
firewallConf := firewall.NewConfigurator(logger, routingConf, fileManager)
|
||||
tinyProxyConf := tinyproxy.NewConfigurator(fileManager, logger)
|
||||
streamMerger := command.NewStreamMerger()
|
||||
|
||||
paramsReader := params.NewReader(logger, fileManager)
|
||||
fmt.Println(gluetunLogging.Splash(version, commit, buildDate))
|
||||
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,
|
||||
"TinyProxy": tinyProxyConf.Version,
|
||||
"OpenVPN": ovpnConf.Version,
|
||||
"Unbound": dnsConf.Version,
|
||||
"IPtables": firewallConf.Version,
|
||||
})
|
||||
|
||||
allSettings, err := settings.GetAllSettings(paramsReader)
|
||||
@@ -115,7 +118,8 @@ func _main(background context.Context, args []string) int { //nolint:gocognit,go
|
||||
// Should never change
|
||||
uid, gid := allSettings.System.UID, allSettings.System.GID
|
||||
|
||||
err = alpineConf.CreateUser("nonrootuser", uid)
|
||||
const defaultUsername = "nonrootuser"
|
||||
nonRootUsername, err := alpineConf.CreateUser(defaultUsername, uid)
|
||||
if err != nil {
|
||||
logger.Error(err)
|
||||
return 1
|
||||
@@ -125,11 +129,6 @@ func _main(background context.Context, args []string) int { //nolint:gocognit,go
|
||||
logger.Error(err)
|
||||
return 1
|
||||
}
|
||||
err = fileManager.SetOwnership("/etc/tinyproxy", uid, gid)
|
||||
if err != nil {
|
||||
logger.Error(err)
|
||||
return 1
|
||||
}
|
||||
|
||||
if allSettings.Firewall.Debug {
|
||||
firewallConf.SetDebug()
|
||||
@@ -161,6 +160,7 @@ func _main(background context.Context, args []string) int { //nolint:gocognit,go
|
||||
return 1
|
||||
}
|
||||
defer func() {
|
||||
routingConf.SetVerbose(false)
|
||||
if err := routingConf.TearDown(); err != nil {
|
||||
logger.Error(err)
|
||||
}
|
||||
@@ -218,45 +218,41 @@ func _main(background context.Context, args []string) int { //nolint:gocognit,go
|
||||
|
||||
go collectStreamLines(ctx, streamMerger, logger, signalTunnelReady)
|
||||
|
||||
openvpnLooper := openvpn.NewLooper(allSettings.VPNSP, allSettings.OpenVPN, uid, gid, allServers,
|
||||
openvpnLooper := openvpn.NewLooper(allSettings.OpenVPN, nonRootUsername, uid, gid, allServers,
|
||||
ovpnConf, firewallConf, routingConf, logger, httpClient, fileManager, streamMerger, cancel)
|
||||
wg.Add(1)
|
||||
// wait for restartOpenvpn
|
||||
go openvpnLooper.Run(ctx, wg)
|
||||
|
||||
updaterOptions := updater.NewOptions("127.0.0.1")
|
||||
updaterLooper := updater.NewLooper(updaterOptions, allSettings.UpdaterPeriod,
|
||||
allServers, storage, openvpnLooper.SetAllServers, httpClient, logger)
|
||||
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, logger, streamMerger, uid, gid)
|
||||
unboundLooper := dns.NewLooper(dnsConf, allSettings.DNS, logger, streamMerger, nonRootUsername, uid, gid)
|
||||
wg.Add(1)
|
||||
// wait for unboundLooper.Restart or its ticker launched with RunRestartTicker
|
||||
go unboundLooper.Run(ctx, wg, signalDNSReady)
|
||||
|
||||
publicIPLooper := publicip.NewLooper(client, logger, fileManager,
|
||||
allSettings.System.IPStatusFilepath, allSettings.PublicIPPeriod, uid, gid)
|
||||
publicIPLooper := publicip.NewLooper(
|
||||
client, logger, fileManager, allSettings.PublicIP, uid, gid)
|
||||
wg.Add(1)
|
||||
go publicIPLooper.Run(ctx, wg)
|
||||
wg.Add(1)
|
||||
go publicIPLooper.RunRestartTicker(ctx, wg)
|
||||
publicIPLooper.SetPeriod(allSettings.PublicIPPeriod) // call after RunRestartTicker
|
||||
|
||||
tinyproxyLooper := tinyproxy.NewLooper(tinyProxyConf, firewallConf,
|
||||
allSettings.TinyProxy, logger, streamMerger, uid, gid, defaultInterface)
|
||||
restartTinyproxy := tinyproxyLooper.Restart
|
||||
httpProxyLooper := httpproxy.NewLooper(logger, allSettings.HTTPProxy)
|
||||
wg.Add(1)
|
||||
go tinyproxyLooper.Run(ctx, wg)
|
||||
go httpProxyLooper.Run(ctx, wg)
|
||||
|
||||
shadowsocksLooper := shadowsocks.NewLooper(allSettings.ShadowSocks, logger, defaultInterface)
|
||||
restartShadowsocks := shadowsocksLooper.Restart
|
||||
wg.Add(1)
|
||||
go shadowsocksLooper.Run(ctx, wg)
|
||||
|
||||
if allSettings.TinyProxy.Enabled {
|
||||
restartTinyproxy()
|
||||
if allSettings.HTTPProxy.Enabled {
|
||||
httpProxyLooper.Restart()
|
||||
}
|
||||
if allSettings.ShadowSocks.Enabled {
|
||||
restartShadowsocks()
|
||||
@@ -270,7 +266,7 @@ func _main(background context.Context, args []string) int { //nolint:gocognit,go
|
||||
controlServerAddress := fmt.Sprintf("0.0.0.0:%d", allSettings.ControlServer.Port)
|
||||
controlServerLogging := allSettings.ControlServer.Log
|
||||
httpServer := server.New(controlServerAddress, controlServerLogging,
|
||||
logger, openvpnLooper, unboundLooper, updaterLooper)
|
||||
logger, buildInfo, openvpnLooper, unboundLooper, updaterLooper, publicIPLooper)
|
||||
wg.Add(1)
|
||||
go httpServer.Run(ctx, wg)
|
||||
|
||||
@@ -279,8 +275,9 @@ func _main(background context.Context, args []string) int { //nolint:gocognit,go
|
||||
wg.Add(1)
|
||||
go healthcheckServer.Run(ctx, wg)
|
||||
|
||||
// Start openvpn for the first time
|
||||
openvpnLooper.Restart()
|
||||
// Start openvpn for the first time in a blocking call
|
||||
// until openvpn is launched
|
||||
_, _ = openvpnLooper.SetStatus(constants.Running) // TODO option to disable with variable
|
||||
|
||||
signalsCh := make(chan os.Signal, 1)
|
||||
signal.Notify(signalsCh,
|
||||
@@ -296,11 +293,6 @@ func _main(background context.Context, args []string) int { //nolint:gocognit,go
|
||||
case <-ctx.Done():
|
||||
logger.Warn("context canceled, shutting down")
|
||||
}
|
||||
logger.Info("Clearing ip status file %s", allSettings.System.IPStatusFilepath)
|
||||
if err := fileManager.Remove(string(allSettings.System.IPStatusFilepath)); err != nil {
|
||||
logger.Error(err)
|
||||
shutdownErrorsCount++
|
||||
}
|
||||
if allSettings.OpenVPN.Provider.PortForwarding.Enabled {
|
||||
logger.Info("Clearing forwarded port status file %s", allSettings.OpenVPN.Provider.PortForwarding.Filepath)
|
||||
if err := fileManager.Remove(string(allSettings.OpenVPN.Provider.PortForwarding.Filepath)); err != nil {
|
||||
@@ -356,7 +348,7 @@ func printVersions(ctx context.Context, logger logging.Logger,
|
||||
//nolint:lll
|
||||
func collectStreamLines(ctx context.Context, streamMerger command.StreamMerger,
|
||||
logger logging.Logger, signalTunnelReady func()) {
|
||||
// Blocking line merging paramsReader for all programs: openvpn, tinyproxy, unbound and shadowsocks
|
||||
// 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)
|
||||
@@ -404,7 +396,7 @@ func routeReadyEvents(ctx context.Context, wg *sync.WaitGroup, tunnelReadyCh, dn
|
||||
tickerWg.Wait()
|
||||
return
|
||||
case <-tunnelReadyCh: // blocks until openvpn is connected
|
||||
unboundLooper.Restart()
|
||||
_, _ = unboundLooper.SetStatus(constants.Running)
|
||||
restartTickerCancel() // stop previous restart tickers
|
||||
tickerWg.Wait()
|
||||
restartTickerContext, restartTickerCancel = context.WithCancel(ctx)
|
||||
@@ -418,31 +410,21 @@ func routeReadyEvents(ctx context.Context, wg *sync.WaitGroup, tunnelReadyCh, dn
|
||||
logger.Info("VPN routing IP address: %s", vpnDestination)
|
||||
}
|
||||
if portForwardingEnabled {
|
||||
// TODO make instantaneous once v3 go out of service
|
||||
const waitDuration = 5 * time.Second
|
||||
timer := time.NewTimer(waitDuration)
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
if !timer.Stop() {
|
||||
<-timer.C
|
||||
}
|
||||
continue
|
||||
case <-timer.C:
|
||||
// vpnGateway required only for PIA v4
|
||||
vpnGateway, err := routing.VPNLocalGatewayIP()
|
||||
if err != nil {
|
||||
logger.Error(err)
|
||||
}
|
||||
logger.Info("VPN gateway IP address: %s", vpnGateway)
|
||||
startPortForward(vpnGateway)
|
||||
// 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:
|
||||
publicIPLooper.Restart() // TODO do not restart if disabled
|
||||
// Runs the Public IP getter job once
|
||||
_, _ = publicIPLooper.SetStatus(constants.Running)
|
||||
if !versionInformation {
|
||||
break
|
||||
}
|
||||
message, err := versionpkg.GetMessage(ctx, version, commit, httpClient)
|
||||
message, err := versionpkg.GetMessage(ctx, buildInfo, httpClient)
|
||||
if err != nil {
|
||||
logger.Error(err)
|
||||
break
|
||||
|
||||
1720
doc/logo.svg
Normal file
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
BIN
doc/logo_256.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 20 KiB |
@@ -7,7 +7,7 @@ services:
|
||||
- NET_ADMIN
|
||||
network_mode: bridge
|
||||
ports:
|
||||
- 8888:8888/tcp # Tinyproxy
|
||||
- 8888:8888/tcp # HTTP proxy
|
||||
- 8388:8388/tcp # Shadowsocks
|
||||
- 8388:8388/udp # Shadowsocks
|
||||
- 8000:8000/tcp # Built-in HTTP control server
|
||||
|
||||
2
go.mod
2
go.mod
@@ -7,7 +7,7 @@ require (
|
||||
github.com/golang/mock v1.4.4
|
||||
github.com/kyokomi/emoji v2.2.4+incompatible
|
||||
github.com/qdm12/golibs v0.0.0-20201025221346-fe352060c25a
|
||||
github.com/qdm12/ss-server v0.0.0-20200819124651-6428e626ee83
|
||||
github.com/qdm12/ss-server v0.1.0
|
||||
github.com/stretchr/testify v1.6.1
|
||||
github.com/vishvananda/netlink v1.1.0
|
||||
golang.org/x/sys v0.0.0-20201018121011-98379d014ca7
|
||||
|
||||
13
go.sum
13
go.sum
@@ -72,20 +72,16 @@ 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-20201024185935-092412448c2c h1:9EQyDXbeapnPeMeO8Yq7PE6zqYPGkHp/qijNBBTU74c=
|
||||
github.com/qdm12/golibs v0.0.0-20201024185935-092412448c2c/go.mod h1:pikkTN7g7zRuuAnERwqW1yAFq6pYmxrxpjiwGvb0Ysc=
|
||||
github.com/qdm12/golibs v0.0.0-20201025221346-fe352060c25a h1:v0zUA1FWeVkTEd9KyxfehbRVJeFGOqyMY6FHO/Q9ITU=
|
||||
github.com/qdm12/golibs v0.0.0-20201025221346-fe352060c25a/go.mod h1:pikkTN7g7zRuuAnERwqW1yAFq6pYmxrxpjiwGvb0Ysc=
|
||||
github.com/qdm12/ss-server v0.0.0-20200819124651-6428e626ee83 h1:b7sNsgsKxH0mbl9L1hdUp5KSDkZ/1kOQ+iHiBVgFElM=
|
||||
github.com/qdm12/ss-server v0.0.0-20200819124651-6428e626ee83/go.mod h1:ABVUkxubboL3vqBkOwDV9glX1/x7SnYrckBe5d+M/zw=
|
||||
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/riobard/go-bloom v0.0.0-20200614022211-cdc8013cb5b3 h1:f/FNXud6gA3MNr8meMVVGxhp+QBTqY91tM8HjEuMjGg=
|
||||
github.com/riobard/go-bloom v0.0.0-20200614022211-cdc8013cb5b3/go.mod h1:HgjTstvQsPGkxUsCd2KWxErBblirPizecHcpD3ffK+s=
|
||||
github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
|
||||
github.com/stretchr/objx v0.1.0 h1:4G4v2dO3VZwixGIRoQ5Lfboy6nUhCyYzaqnIAPPhYs4=
|
||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
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=
|
||||
@@ -112,19 +108,16 @@ golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKG
|
||||
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/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-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190412213103-97732733099d h1:+R4KGOnez64A81RvjARKc4UT5/tI9ujCIVX+P5KiHuI=
|
||||
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190606203320-7fc4e5ec1444/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20201018121011-98379d014ca7 h1:CNOpL+H7PSxBI7dF/EIUsfOguRSzWp6CQ91yxZE6PG4=
|
||||
golang.org/x/sys v0.0.0-20201018121011-98379d014ca7/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.0 h1:g61tztE5qeGQ89tm6NTjjM9VPIm088od1l6aSorWRWg=
|
||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
|
||||
golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
|
||||
@@ -133,13 +126,11 @@ golang.org/x/tools v0.0.0-20191029041327-9cc4af7d6b2c/go.mod h1:b+2E5dAYhXwXZwtn
|
||||
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/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=
|
||||
|
||||
@@ -7,7 +7,7 @@ import (
|
||||
)
|
||||
|
||||
type Configurator interface {
|
||||
CreateUser(username string, uid int) error
|
||||
CreateUser(username string, uid int) (createdUsername string, err error)
|
||||
}
|
||||
|
||||
type configurator struct {
|
||||
|
||||
@@ -6,34 +6,34 @@ import (
|
||||
)
|
||||
|
||||
// CreateUser creates a user in Alpine with the given UID.
|
||||
func (c *configurator) CreateUser(username string, uid int) error {
|
||||
func (c *configurator) CreateUser(username string, uid int) (createdUsername string, err error) {
|
||||
UIDStr := fmt.Sprintf("%d", uid)
|
||||
u, err := c.lookupUID(UIDStr)
|
||||
_, unknownUID := err.(user.UnknownUserIdError)
|
||||
if err != nil && !unknownUID {
|
||||
return fmt.Errorf("cannot create user: %w", err)
|
||||
return "", fmt.Errorf("cannot create user: %w", err)
|
||||
} else if u != nil {
|
||||
if u.Username == username {
|
||||
return nil
|
||||
return "", nil
|
||||
}
|
||||
return fmt.Errorf("user with ID %d exists with username %q instead of %q", uid, u.Username, username)
|
||||
return u.Username, nil
|
||||
}
|
||||
u, err = c.lookupUser(username)
|
||||
_, unknownUsername := err.(user.UnknownUserError)
|
||||
if err != nil && !unknownUsername {
|
||||
return fmt.Errorf("cannot create user: %w", err)
|
||||
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",
|
||||
return "", fmt.Errorf("cannot create user: user with name %s already exists for ID %s instead of %d",
|
||||
username, u.Uid, uid)
|
||||
}
|
||||
passwd, err := c.fileManager.ReadFile("/etc/passwd")
|
||||
if err != nil {
|
||||
return fmt.Errorf("cannot create user: %w", err)
|
||||
return "", fmt.Errorf("cannot create user: %w", err)
|
||||
}
|
||||
passwd = append(passwd, []byte(fmt.Sprintf("%s:x:%d:::/dev/null:/sbin/nologin\n", username, uid))...)
|
||||
|
||||
if err := c.fileManager.WriteToFile("/etc/passwd", passwd); err != nil {
|
||||
return fmt.Errorf("cannot create user: %w", err)
|
||||
return "", fmt.Errorf("cannot create user: %w", err)
|
||||
}
|
||||
return nil
|
||||
return username, nil
|
||||
}
|
||||
|
||||
@@ -21,7 +21,7 @@ import (
|
||||
|
||||
func ClientKey(args []string) error {
|
||||
flagSet := flag.NewFlagSet("clientkey", flag.ExitOnError)
|
||||
filepath := flagSet.String("path", "/files/client.key", "file path to the client.key file")
|
||||
filepath := flagSet.String("path", string(constants.ClientKey), "file path to the client.key file")
|
||||
if err := flagSet.Parse(args); err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -71,8 +71,7 @@ func OpenvpnConfig() error {
|
||||
lines := providerConf.BuildConf(
|
||||
connection,
|
||||
allSettings.OpenVPN.Verbosity,
|
||||
allSettings.System.UID,
|
||||
allSettings.System.GID,
|
||||
"nonroortuser",
|
||||
allSettings.OpenVPN.Root,
|
||||
allSettings.OpenVPN.Cipher,
|
||||
allSettings.OpenVPN.Auth,
|
||||
@@ -83,7 +82,7 @@ func OpenvpnConfig() error {
|
||||
}
|
||||
|
||||
func Update(args []string) error {
|
||||
options := updater.Options{CLI: true}
|
||||
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)")
|
||||
@@ -93,7 +92,7 @@ func Update(args []string) error {
|
||||
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.PIAold, "piaold", false, "Update Private Internet Access pre-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")
|
||||
|
||||
@@ -6,10 +6,6 @@ func ColorUnbound() *color.Color {
|
||||
return color.New(color.FgCyan)
|
||||
}
|
||||
|
||||
func ColorTinyproxy() *color.Color {
|
||||
return color.New(color.FgHiGreen)
|
||||
}
|
||||
|
||||
func ColorOpenvpn() *color.Color {
|
||||
return color.New(color.FgHiMagenta)
|
||||
}
|
||||
|
||||
@@ -9,8 +9,7 @@ import (
|
||||
|
||||
//nolint:lll
|
||||
const (
|
||||
CyberghostCertificate = "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"
|
||||
CyberghostClientCertificate = "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"
|
||||
CyberghostCertificate = "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"
|
||||
)
|
||||
|
||||
func CyberghostRegionChoices() (choices []string) {
|
||||
|
||||
@@ -97,7 +97,7 @@ func DNSProviderMapping() map[models.DNSProvider]models.DNSProviderData {
|
||||
}
|
||||
}
|
||||
|
||||
// Block lists URLs
|
||||
// Block lists URLs.
|
||||
//nolint:lll
|
||||
const (
|
||||
AdsBlockListHostnamesURL models.URL = "https://raw.githubusercontent.com/qdm12/files/master/ads-hostnames.updated"
|
||||
|
||||
@@ -15,16 +15,18 @@ const (
|
||||
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 v4 servers.
|
||||
// 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"
|
||||
// TinyProxyConf is the filepath to the tinyproxy configuration file.
|
||||
TinyProxyConf models.Filepath = "/etc/tinyproxy/tinyproxy.conf"
|
||||
// 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"
|
||||
)
|
||||
|
||||
@@ -28,185 +28,99 @@ func PIAGeoChoices() (choices []string) {
|
||||
//nolint:lll
|
||||
func PIAServers() []models.PIAServer {
|
||||
return []models.PIAServer{
|
||||
{Region: "AU Melbourne", PortForward: true, OpenvpnUDP: models.PIAServerOpenvpn{CN: "melbourne405", IPs: []net.IP{{103, 2, 198, 108}}}, OpenvpnTCP: models.PIAServerOpenvpn{CN: "melbourne405", IPs: []net.IP{{103, 2, 198, 103}}}},
|
||||
{Region: "AU Perth", PortForward: true, OpenvpnUDP: models.PIAServerOpenvpn{CN: "perth404", IPs: []net.IP{{43, 250, 205, 186}}}, OpenvpnTCP: models.PIAServerOpenvpn{CN: "perth404", IPs: []net.IP{{43, 250, 205, 188}}}},
|
||||
{Region: "AU Sydney", PortForward: true, OpenvpnUDP: models.PIAServerOpenvpn{CN: "sydney405", IPs: []net.IP{{27, 50, 76, 132}}}, OpenvpnTCP: models.PIAServerOpenvpn{CN: "sydney405", IPs: []net.IP{{27, 50, 76, 132}}}},
|
||||
{Region: "Albania", PortForward: true, OpenvpnUDP: models.PIAServerOpenvpn{CN: "tirana401", IPs: []net.IP{{31, 171, 154, 131}}}, OpenvpnTCP: models.PIAServerOpenvpn{CN: "tirana401", IPs: []net.IP{{31, 171, 154, 137}}}},
|
||||
{Region: "Algeria", PortForward: true, OpenvpnUDP: models.PIAServerOpenvpn{CN: "algiers402", IPs: []net.IP{{45, 133, 91, 209}}}, OpenvpnTCP: models.PIAServerOpenvpn{CN: "algiers402", IPs: []net.IP{{45, 133, 91, 227}}}},
|
||||
{Region: "Andorra", PortForward: true, OpenvpnUDP: models.PIAServerOpenvpn{CN: "andorra401", IPs: []net.IP{{45, 139, 49, 232}}}, OpenvpnTCP: models.PIAServerOpenvpn{CN: "andorra401", IPs: []net.IP{{45, 139, 49, 238}}}},
|
||||
{Region: "Argentina", PortForward: true, OpenvpnUDP: models.PIAServerOpenvpn{CN: "buenosaires401", IPs: []net.IP{{190, 106, 134, 92}}}, OpenvpnTCP: models.PIAServerOpenvpn{CN: "buenosaires401", IPs: []net.IP{{190, 106, 134, 89}}}},
|
||||
{Region: "Armenia", PortForward: true, OpenvpnUDP: models.PIAServerOpenvpn{CN: "armenia402", IPs: []net.IP{{45, 139, 50, 229}}}, OpenvpnTCP: models.PIAServerOpenvpn{CN: "armenia402", IPs: []net.IP{{45, 139, 50, 213}}}},
|
||||
{Region: "Austria", PortForward: true, OpenvpnUDP: models.PIAServerOpenvpn{CN: "vienna403", IPs: []net.IP{{156, 146, 60, 104}}}, OpenvpnTCP: models.PIAServerOpenvpn{CN: "vienna403", IPs: []net.IP{{156, 146, 60, 100}}}},
|
||||
{Region: "Bahamas", PortForward: true, OpenvpnUDP: models.PIAServerOpenvpn{CN: "bahamas402", IPs: []net.IP{{45, 132, 143, 206}}}, OpenvpnTCP: models.PIAServerOpenvpn{CN: "bahamas402", IPs: []net.IP{{45, 132, 143, 229}}}},
|
||||
{Region: "Belgium", PortForward: true, OpenvpnUDP: models.PIAServerOpenvpn{CN: "brussels403", IPs: []net.IP{{5, 253, 205, 147}}}, OpenvpnTCP: models.PIAServerOpenvpn{CN: "brussels403", IPs: []net.IP{{5, 253, 205, 153}}}},
|
||||
{Region: "Bosnia and Herzegovina", PortForward: true, OpenvpnUDP: models.PIAServerOpenvpn{CN: "sarajevo401", IPs: []net.IP{{185, 212, 111, 76}}}, OpenvpnTCP: models.PIAServerOpenvpn{CN: "sarajevo401", IPs: []net.IP{{185, 212, 111, 77}}}},
|
||||
{Region: "Brazil", PortForward: true, OpenvpnUDP: models.PIAServerOpenvpn{CN: "saopaolo402", IPs: []net.IP{{188, 241, 177, 56}}}, OpenvpnTCP: models.PIAServerOpenvpn{CN: "saopaolo402", IPs: []net.IP{{188, 241, 177, 51}}}},
|
||||
{Region: "Bulgaria", PortForward: true, OpenvpnUDP: models.PIAServerOpenvpn{CN: "sofia401", IPs: []net.IP{{217, 138, 221, 131}}}, OpenvpnTCP: models.PIAServerOpenvpn{CN: "sofia401", IPs: []net.IP{{217, 138, 221, 133}}}},
|
||||
{Region: "CA Montreal", PortForward: true, OpenvpnUDP: models.PIAServerOpenvpn{CN: "montreal403", IPs: []net.IP{{172, 98, 71, 62}}}, OpenvpnTCP: models.PIAServerOpenvpn{CN: "montreal403", IPs: []net.IP{{172, 98, 71, 59}}}},
|
||||
{Region: "CA Ontario", PortForward: true, OpenvpnUDP: models.PIAServerOpenvpn{CN: "ontario402", IPs: []net.IP{{172, 83, 47, 138}}}, OpenvpnTCP: models.PIAServerOpenvpn{CN: "ontario402", IPs: []net.IP{{172, 83, 47, 196}}}},
|
||||
{Region: "CA Toronto", PortForward: true, OpenvpnUDP: models.PIAServerOpenvpn{CN: "toronto405", IPs: []net.IP{{172, 83, 47, 250}}}, OpenvpnTCP: models.PIAServerOpenvpn{CN: "toronto405", IPs: []net.IP{{172, 83, 47, 251}}}},
|
||||
{Region: "CA Vancouver", PortForward: true, OpenvpnUDP: models.PIAServerOpenvpn{CN: "vancouver407", IPs: []net.IP{{172, 98, 89, 70}}}, OpenvpnTCP: models.PIAServerOpenvpn{CN: "vancouver407", IPs: []net.IP{{172, 98, 89, 18}}}},
|
||||
{Region: "Cambodia", PortForward: true, OpenvpnUDP: models.PIAServerOpenvpn{CN: "cambodia401", IPs: []net.IP{{188, 215, 235, 105}}}, OpenvpnTCP: models.PIAServerOpenvpn{CN: "cambodia401", IPs: []net.IP{{188, 215, 235, 102}}}},
|
||||
{Region: "China", PortForward: true, OpenvpnUDP: models.PIAServerOpenvpn{CN: "china403", IPs: []net.IP{{86, 107, 104, 212}}}, OpenvpnTCP: models.PIAServerOpenvpn{CN: "china403", IPs: []net.IP{{86, 107, 104, 216}}}},
|
||||
{Region: "Cyprus", PortForward: true, OpenvpnUDP: models.PIAServerOpenvpn{CN: "cyprus402", IPs: []net.IP{{45, 132, 137, 220}}}, OpenvpnTCP: models.PIAServerOpenvpn{CN: "cyprus402", IPs: []net.IP{{45, 132, 137, 225}}}},
|
||||
{Region: "Czech Republic", PortForward: true, OpenvpnUDP: models.PIAServerOpenvpn{CN: "prague402", IPs: []net.IP{{212, 102, 39, 148}}}, OpenvpnTCP: models.PIAServerOpenvpn{CN: "prague402", IPs: []net.IP{{212, 102, 39, 149}}}},
|
||||
{Region: "DE Berlin", PortForward: true, OpenvpnUDP: models.PIAServerOpenvpn{CN: "berlin410", IPs: []net.IP{{89, 36, 76, 153}}}, OpenvpnTCP: models.PIAServerOpenvpn{CN: "berlin410", IPs: []net.IP{{89, 36, 76, 149}}}},
|
||||
{Region: "DE Frankfurt", PortForward: true, OpenvpnUDP: models.PIAServerOpenvpn{CN: "frankfurt406", IPs: []net.IP{{212, 102, 57, 96}}}, OpenvpnTCP: models.PIAServerOpenvpn{CN: "frankfurt406", IPs: []net.IP{{212, 102, 57, 106}}}},
|
||||
{Region: "Denmark", PortForward: true, OpenvpnUDP: models.PIAServerOpenvpn{CN: "copenhagen402", IPs: []net.IP{{188, 126, 94, 93}}}, OpenvpnTCP: models.PIAServerOpenvpn{CN: "copenhagen402", IPs: []net.IP{{188, 126, 94, 93}}}},
|
||||
{Region: "Egypt", PortForward: true, OpenvpnUDP: models.PIAServerOpenvpn{CN: "cairo401", IPs: []net.IP{{188, 214, 122, 106}}}, OpenvpnTCP: models.PIAServerOpenvpn{CN: "cairo401", IPs: []net.IP{{188, 214, 122, 104}}}},
|
||||
{Region: "Estonia", PortForward: true, OpenvpnUDP: models.PIAServerOpenvpn{CN: "talinn402", IPs: []net.IP{{95, 153, 31, 73}}}, OpenvpnTCP: models.PIAServerOpenvpn{CN: "talinn402", IPs: []net.IP{{95, 153, 31, 73}}}},
|
||||
{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, 45}}}},
|
||||
{Region: "France", PortForward: true, OpenvpnUDP: models.PIAServerOpenvpn{CN: "paris402", IPs: []net.IP{{156, 146, 63, 159}}}, OpenvpnTCP: models.PIAServerOpenvpn{CN: "paris402", IPs: []net.IP{{156, 146, 63, 159}}}},
|
||||
{Region: "Georgia", PortForward: true, OpenvpnUDP: models.PIAServerOpenvpn{CN: "georgia401", IPs: []net.IP{{45, 132, 138, 245}}}, OpenvpnTCP: models.PIAServerOpenvpn{CN: "georgia401", IPs: []net.IP{{45, 132, 138, 236}}}},
|
||||
{Region: "Greece", PortForward: true, OpenvpnUDP: models.PIAServerOpenvpn{CN: "athens401", IPs: []net.IP{{154, 57, 3, 80}}}, OpenvpnTCP: models.PIAServerOpenvpn{CN: "athens401", IPs: []net.IP{{154, 57, 3, 84}}}},
|
||||
{Region: "Greenland", PortForward: true, OpenvpnUDP: models.PIAServerOpenvpn{CN: "greenland402", IPs: []net.IP{{45, 131, 209, 222}}}, OpenvpnTCP: models.PIAServerOpenvpn{CN: "greenland402", IPs: []net.IP{{45, 131, 209, 208}}}},
|
||||
{Region: "Hong Kong", PortForward: true, OpenvpnUDP: models.PIAServerOpenvpn{CN: "hongkong402", IPs: []net.IP{{86, 107, 104, 234}}}, OpenvpnTCP: models.PIAServerOpenvpn{CN: "hongkong402", IPs: []net.IP{{86, 107, 104, 240}}}},
|
||||
{Region: "Hungary", PortForward: true, OpenvpnUDP: models.PIAServerOpenvpn{CN: "budapest402", IPs: []net.IP{{86, 106, 74, 121}}}, OpenvpnTCP: models.PIAServerOpenvpn{CN: "budapest402", IPs: []net.IP{{86, 106, 74, 125}}}},
|
||||
{Region: "Iceland", PortForward: true, OpenvpnUDP: models.PIAServerOpenvpn{CN: "reykjavik402", IPs: []net.IP{{45, 133, 193, 86}}}, OpenvpnTCP: models.PIAServerOpenvpn{CN: "reykjavik402", IPs: []net.IP{{45, 133, 193, 86}}}},
|
||||
{Region: "India", PortForward: true, OpenvpnUDP: models.PIAServerOpenvpn{CN: "mumbai405", IPs: []net.IP{{45, 120, 139, 97}}}, OpenvpnTCP: models.PIAServerOpenvpn{CN: "mumbai405", IPs: []net.IP{{45, 120, 139, 97}}}},
|
||||
{Region: "Iran", PortForward: true, OpenvpnUDP: models.PIAServerOpenvpn{CN: "iran402", IPs: []net.IP{{45, 131, 4, 219}}}, OpenvpnTCP: models.PIAServerOpenvpn{CN: "iran402", IPs: []net.IP{{45, 131, 4, 218}}}},
|
||||
{Region: "Ireland", PortForward: true, OpenvpnUDP: models.PIAServerOpenvpn{CN: "dublin404", IPs: []net.IP{{193, 56, 252, 28}}}, OpenvpnTCP: models.PIAServerOpenvpn{CN: "dublin404", IPs: []net.IP{{193, 56, 252, 24}}}},
|
||||
{Region: "Isle of Man", PortForward: true, OpenvpnUDP: models.PIAServerOpenvpn{CN: "douglas401", IPs: []net.IP{{45, 132, 140, 236}}}, OpenvpnTCP: models.PIAServerOpenvpn{CN: "douglas401", IPs: []net.IP{{45, 132, 140, 244}}}},
|
||||
{Region: "Israel", PortForward: true, OpenvpnUDP: models.PIAServerOpenvpn{CN: "jerusalem401", IPs: []net.IP{{185, 77, 248, 19}}}, OpenvpnTCP: models.PIAServerOpenvpn{CN: "jerusalem401", IPs: []net.IP{{185, 77, 248, 17}}}},
|
||||
{Region: "Italy", PortForward: true, OpenvpnUDP: models.PIAServerOpenvpn{CN: "milano402", IPs: []net.IP{{156, 146, 41, 20}}}, OpenvpnTCP: models.PIAServerOpenvpn{CN: "milano402", IPs: []net.IP{{156, 146, 41, 42}}}},
|
||||
{Region: "Japan", PortForward: true, OpenvpnUDP: models.PIAServerOpenvpn{CN: "tokyo401", IPs: []net.IP{{156, 146, 34, 135}}}, OpenvpnTCP: models.PIAServerOpenvpn{CN: "tokyo401", IPs: []net.IP{{156, 146, 34, 157}}}},
|
||||
{Region: "Kazakhstan", PortForward: true, OpenvpnUDP: models.PIAServerOpenvpn{CN: "kazakhstan402", IPs: []net.IP{{45, 133, 88, 209}}}, OpenvpnTCP: models.PIAServerOpenvpn{CN: "kazakhstan402", IPs: []net.IP{{45, 133, 88, 229}}}},
|
||||
{Region: "Latvia", PortForward: true, OpenvpnUDP: models.PIAServerOpenvpn{CN: "riga401", IPs: []net.IP{{109, 248, 149, 12}}}, OpenvpnTCP: models.PIAServerOpenvpn{CN: "riga401", IPs: []net.IP{{109, 248, 149, 12}}}},
|
||||
{Region: "Liechtenstein", PortForward: true, OpenvpnUDP: models.PIAServerOpenvpn{CN: "liechtenstein401", IPs: []net.IP{{45, 139, 48, 236}}}, OpenvpnTCP: models.PIAServerOpenvpn{CN: "liechtenstein401", IPs: []net.IP{{45, 139, 48, 242}}}},
|
||||
{Region: "Lithuania", PortForward: true, OpenvpnUDP: models.PIAServerOpenvpn{CN: "vilnius401", IPs: []net.IP{{85, 206, 165, 163}}}, OpenvpnTCP: models.PIAServerOpenvpn{CN: "vilnius401", IPs: []net.IP{{85, 206, 165, 163}}}},
|
||||
{Region: "Luxembourg", PortForward: true, OpenvpnUDP: models.PIAServerOpenvpn{CN: "luxembourg401", IPs: []net.IP{{92, 223, 89, 74}}}, OpenvpnTCP: models.PIAServerOpenvpn{CN: "luxembourg401", IPs: []net.IP{{92, 223, 89, 78}}}},
|
||||
{Region: "Macedonia", PortForward: true, OpenvpnUDP: models.PIAServerOpenvpn{CN: "macedonia401", IPs: []net.IP{{185, 225, 28, 115}}}, OpenvpnTCP: models.PIAServerOpenvpn{CN: "macedonia401", IPs: []net.IP{{185, 225, 28, 115}}}},
|
||||
{Region: "Malta", PortForward: true, OpenvpnUDP: models.PIAServerOpenvpn{CN: "malta401", IPs: []net.IP{{45, 137, 198, 238}}}, OpenvpnTCP: models.PIAServerOpenvpn{CN: "malta401", IPs: []net.IP{{45, 137, 198, 244}}}},
|
||||
{Region: "Mexico", PortForward: true, OpenvpnUDP: models.PIAServerOpenvpn{CN: "mexico403", IPs: []net.IP{{77, 81, 142, 8}}}, OpenvpnTCP: models.PIAServerOpenvpn{CN: "mexico403", IPs: []net.IP{{77, 81, 142, 7}}}},
|
||||
{Region: "Moldova", PortForward: true, OpenvpnUDP: models.PIAServerOpenvpn{CN: "chisinau401", IPs: []net.IP{{178, 175, 129, 43}}}, OpenvpnTCP: models.PIAServerOpenvpn{CN: "chisinau401", IPs: []net.IP{{178, 175, 129, 44}}}},
|
||||
{Region: "Monaco", PortForward: true, OpenvpnUDP: models.PIAServerOpenvpn{CN: "monaco402", IPs: []net.IP{{45, 137, 199, 226}}}, OpenvpnTCP: models.PIAServerOpenvpn{CN: "monaco402", IPs: []net.IP{{45, 137, 199, 218}}}},
|
||||
{Region: "Montenegro", PortForward: true, OpenvpnUDP: models.PIAServerOpenvpn{CN: "montenegro402", IPs: []net.IP{{45, 131, 208, 212}}}, OpenvpnTCP: models.PIAServerOpenvpn{CN: "montenegro402", IPs: []net.IP{{45, 131, 208, 212}}}},
|
||||
{Region: "Morocco", PortForward: true, OpenvpnUDP: models.PIAServerOpenvpn{CN: "morocco401", IPs: []net.IP{{45, 131, 211, 233}}}, OpenvpnTCP: models.PIAServerOpenvpn{CN: "morocco401", IPs: []net.IP{{45, 131, 211, 248}}}},
|
||||
{Region: "Netherlands", PortForward: true, OpenvpnUDP: models.PIAServerOpenvpn{CN: "amsterdam416", IPs: []net.IP{{212, 102, 35, 136}}}, OpenvpnTCP: models.PIAServerOpenvpn{CN: "amsterdam416", IPs: []net.IP{{212, 102, 35, 136}}}},
|
||||
{Region: "New Zealand", PortForward: true, OpenvpnUDP: models.PIAServerOpenvpn{CN: "newzealand403", IPs: []net.IP{{43, 250, 207, 89}}}, OpenvpnTCP: models.PIAServerOpenvpn{CN: "newzealand403", IPs: []net.IP{{43, 250, 207, 94}}}},
|
||||
{Region: "Norway", PortForward: true, OpenvpnUDP: models.PIAServerOpenvpn{CN: "oslo403", IPs: []net.IP{{46, 246, 122, 124}}}, OpenvpnTCP: models.PIAServerOpenvpn{CN: "oslo403", IPs: []net.IP{{46, 246, 122, 99}}}},
|
||||
{Region: "Panama", PortForward: true, OpenvpnUDP: models.PIAServerOpenvpn{CN: "panama401", IPs: []net.IP{{45, 131, 210, 248}}}, OpenvpnTCP: models.PIAServerOpenvpn{CN: "panama401", IPs: []net.IP{{45, 131, 210, 231}}}},
|
||||
{Region: "Philippines", PortForward: true, OpenvpnUDP: models.PIAServerOpenvpn{CN: "philippines401", IPs: []net.IP{{188, 214, 125, 142}}}, OpenvpnTCP: models.PIAServerOpenvpn{CN: "philippines401", IPs: []net.IP{{188, 214, 125, 142}}}},
|
||||
{Region: "Poland", PortForward: true, OpenvpnUDP: models.PIAServerOpenvpn{CN: "warsaw402", IPs: []net.IP{{194, 110, 114, 13}}}, OpenvpnTCP: models.PIAServerOpenvpn{CN: "warsaw402", IPs: []net.IP{{194, 110, 114, 13}}}},
|
||||
{Region: "Portugal", PortForward: true, OpenvpnUDP: models.PIAServerOpenvpn{CN: "lisbon401", IPs: []net.IP{{89, 26, 241, 72}}}, OpenvpnTCP: models.PIAServerOpenvpn{CN: "lisbon401", IPs: []net.IP{{89, 26, 241, 76}}}},
|
||||
{Region: "Qatar", PortForward: true, OpenvpnUDP: models.PIAServerOpenvpn{CN: "qatar401", IPs: []net.IP{{45, 131, 7, 234}}}, OpenvpnTCP: models.PIAServerOpenvpn{CN: "qatar401", IPs: []net.IP{{45, 131, 7, 232}}}},
|
||||
{Region: "Romania", PortForward: true, OpenvpnUDP: models.PIAServerOpenvpn{CN: "romania408", IPs: []net.IP{{143, 244, 54, 93}}}, OpenvpnTCP: models.PIAServerOpenvpn{CN: "romania408", IPs: []net.IP{{143, 244, 54, 92}}}},
|
||||
{Region: "Saudi Arabia", PortForward: true, OpenvpnUDP: models.PIAServerOpenvpn{CN: "saudiarabia401", IPs: []net.IP{{45, 131, 6, 238}}}, OpenvpnTCP: models.PIAServerOpenvpn{CN: "saudiarabia401", IPs: []net.IP{{45, 131, 6, 231}}}},
|
||||
{Region: "Serbia", PortForward: true, OpenvpnUDP: models.PIAServerOpenvpn{CN: "belgrade401", IPs: []net.IP{{37, 120, 193, 254}}}, OpenvpnTCP: models.PIAServerOpenvpn{CN: "belgrade401", IPs: []net.IP{{37, 120, 193, 254}}}},
|
||||
{Region: "Singapore", PortForward: true, OpenvpnUDP: models.PIAServerOpenvpn{CN: "singapore401", IPs: []net.IP{{156, 146, 57, 210}}}, OpenvpnTCP: models.PIAServerOpenvpn{CN: "singapore401", IPs: []net.IP{{156, 146, 57, 190}}}},
|
||||
{Region: "Slovakia", PortForward: true, OpenvpnUDP: models.PIAServerOpenvpn{CN: "bratislava401", IPs: []net.IP{{37, 120, 221, 93}}}, OpenvpnTCP: models.PIAServerOpenvpn{CN: "bratislava401", IPs: []net.IP{{37, 120, 221, 83}}}},
|
||||
{Region: "South Africa", PortForward: true, OpenvpnUDP: models.PIAServerOpenvpn{CN: "johannesburg401", IPs: []net.IP{{154, 16, 93, 46}}}, OpenvpnTCP: models.PIAServerOpenvpn{CN: "johannesburg401", IPs: []net.IP{{154, 16, 93, 44}}}},
|
||||
{Region: "Spain", PortForward: true, OpenvpnUDP: models.PIAServerOpenvpn{CN: "madrid402", IPs: []net.IP{{212, 102, 49, 33}}}, OpenvpnTCP: models.PIAServerOpenvpn{CN: "madrid402", IPs: []net.IP{{212, 102, 49, 29}}}},
|
||||
{Region: "Sri Lanka", PortForward: true, OpenvpnUDP: models.PIAServerOpenvpn{CN: "srilanka402", IPs: []net.IP{{45, 132, 136, 224}}}, OpenvpnTCP: models.PIAServerOpenvpn{CN: "srilanka402", IPs: []net.IP{{45, 132, 136, 216}}}},
|
||||
{Region: "Sweden", PortForward: true, OpenvpnUDP: models.PIAServerOpenvpn{CN: "stockholm404", IPs: []net.IP{{195, 246, 120, 140}}}, OpenvpnTCP: models.PIAServerOpenvpn{CN: "stockholm404", IPs: []net.IP{{195, 246, 120, 116}}}},
|
||||
{Region: "Switzerland", PortForward: true, OpenvpnUDP: models.PIAServerOpenvpn{CN: "zurich404", IPs: []net.IP{{212, 102, 37, 104}}}, OpenvpnTCP: models.PIAServerOpenvpn{CN: "zurich404", IPs: []net.IP{{212, 102, 37, 84}}}},
|
||||
{Region: "Taiwan", PortForward: true, OpenvpnUDP: models.PIAServerOpenvpn{CN: "taiwan401", IPs: []net.IP{{188, 214, 106, 76}}}, OpenvpnTCP: models.PIAServerOpenvpn{CN: "taiwan401", IPs: []net.IP{{188, 214, 106, 71}}}},
|
||||
{Region: "Turkey", PortForward: true, OpenvpnUDP: models.PIAServerOpenvpn{CN: "istanbul401", IPs: []net.IP{{188, 213, 34, 71}}}, OpenvpnTCP: models.PIAServerOpenvpn{CN: "istanbul401", IPs: []net.IP{{188, 213, 34, 76}}}},
|
||||
{Region: "UK London", PortForward: true, OpenvpnUDP: models.PIAServerOpenvpn{CN: "london412", IPs: []net.IP{{37, 235, 96, 109}}}, OpenvpnTCP: models.PIAServerOpenvpn{CN: "london412", IPs: []net.IP{{37, 235, 96, 109}}}},
|
||||
{Region: "UK Manchester", PortForward: true, OpenvpnUDP: models.PIAServerOpenvpn{CN: "manchester460", IPs: []net.IP{{37, 120, 159, 136}}}, OpenvpnTCP: models.PIAServerOpenvpn{CN: "manchester460", IPs: []net.IP{{37, 120, 159, 122}}}},
|
||||
{Region: "UK Southampton", PortForward: true, OpenvpnUDP: models.PIAServerOpenvpn{CN: "southampton401", IPs: []net.IP{{143, 244, 37, 223}}}, OpenvpnTCP: models.PIAServerOpenvpn{CN: "southampton401", IPs: []net.IP{{143, 244, 37, 189}}}},
|
||||
{Region: "US Atlanta", PortForward: false, OpenvpnUDP: models.PIAServerOpenvpn{CN: "atlanta421", IPs: []net.IP{{154, 21, 21, 77}}}, OpenvpnTCP: models.PIAServerOpenvpn{CN: "atlanta421", IPs: []net.IP{{154, 21, 21, 70}}}},
|
||||
{Region: "US California", PortForward: false, OpenvpnUDP: models.PIAServerOpenvpn{CN: "losangeles401", IPs: []net.IP{{37, 235, 107, 62}}}, OpenvpnTCP: models.PIAServerOpenvpn{CN: "losangeles401", IPs: []net.IP{{37, 235, 107, 17}}}},
|
||||
{Region: "US Chicago", PortForward: false, OpenvpnUDP: models.PIAServerOpenvpn{CN: "chicago416", IPs: []net.IP{{154, 21, 114, 12}}}, OpenvpnTCP: models.PIAServerOpenvpn{CN: "chicago416", IPs: []net.IP{{154, 21, 114, 12}}}},
|
||||
{Region: "US Denver", PortForward: false, OpenvpnUDP: models.PIAServerOpenvpn{CN: "denver402", IPs: []net.IP{{70, 39, 126, 157}}}, OpenvpnTCP: models.PIAServerOpenvpn{CN: "denver402", IPs: []net.IP{{70, 39, 126, 175}}}},
|
||||
{Region: "US East", PortForward: false, OpenvpnUDP: models.PIAServerOpenvpn{CN: "newjersey402", IPs: []net.IP{{37, 235, 103, 74}}}, OpenvpnTCP: models.PIAServerOpenvpn{CN: "newjersey402", IPs: []net.IP{{37, 235, 103, 131}}}},
|
||||
{Region: "US Florida", PortForward: false, OpenvpnUDP: models.PIAServerOpenvpn{CN: "miami405", IPs: []net.IP{{37, 235, 98, 169}}}, OpenvpnTCP: models.PIAServerOpenvpn{CN: "miami405", IPs: []net.IP{{37, 235, 98, 188}}}},
|
||||
{Region: "US Houston", PortForward: false, OpenvpnUDP: models.PIAServerOpenvpn{CN: "houston418", IPs: []net.IP{{205, 251, 154, 205}}}, OpenvpnTCP: models.PIAServerOpenvpn{CN: "houston418", IPs: []net.IP{{205, 251, 154, 208}}}},
|
||||
{Region: "US Las Vegas", PortForward: false, OpenvpnUDP: models.PIAServerOpenvpn{CN: "lasvegas402", IPs: []net.IP{{45, 89, 173, 178}}}, OpenvpnTCP: models.PIAServerOpenvpn{CN: "lasvegas402", IPs: []net.IP{{45, 89, 173, 181}}}},
|
||||
{Region: "US New York", PortForward: false, OpenvpnUDP: models.PIAServerOpenvpn{CN: "newyork403", IPs: []net.IP{{156, 146, 54, 108}}}, OpenvpnTCP: models.PIAServerOpenvpn{CN: "newyork403", IPs: []net.IP{{156, 146, 54, 63}}}},
|
||||
{Region: "US Seattle", PortForward: false, OpenvpnUDP: models.PIAServerOpenvpn{CN: "seattle417", IPs: []net.IP{{154, 21, 20, 187}}}, OpenvpnTCP: models.PIAServerOpenvpn{CN: "seattle417", IPs: []net.IP{{154, 21, 20, 169}}}},
|
||||
{Region: "US Silicon Valley", PortForward: false, OpenvpnUDP: models.PIAServerOpenvpn{CN: "siliconvalley401", IPs: []net.IP{{154, 21, 212, 40}}}, OpenvpnTCP: models.PIAServerOpenvpn{CN: "siliconvalley401", IPs: []net.IP{{154, 21, 212, 14}}}},
|
||||
{Region: "US Texas", PortForward: false, OpenvpnUDP: models.PIAServerOpenvpn{CN: "dallas401", IPs: []net.IP{{156, 146, 53, 180}}}, OpenvpnTCP: models.PIAServerOpenvpn{CN: "dallas401", IPs: []net.IP{{156, 146, 53, 186}}}},
|
||||
{Region: "US Washington DC", PortForward: false, OpenvpnUDP: models.PIAServerOpenvpn{CN: "washington412", IPs: []net.IP{{23, 105, 168, 143}}}, OpenvpnTCP: models.PIAServerOpenvpn{CN: "washington412", IPs: []net.IP{{23, 105, 168, 150}}}},
|
||||
{Region: "US West", PortForward: false, OpenvpnUDP: models.PIAServerOpenvpn{CN: "phoenix407", IPs: []net.IP{{184, 170, 241, 67}}}, OpenvpnTCP: models.PIAServerOpenvpn{CN: "phoenix407", IPs: []net.IP{{184, 170, 241, 121}}}},
|
||||
{Region: "Ukraine", PortForward: true, OpenvpnUDP: models.PIAServerOpenvpn{CN: "kiev402", IPs: []net.IP{{62, 149, 20, 23}}}, OpenvpnTCP: models.PIAServerOpenvpn{CN: "kiev402", IPs: []net.IP{{62, 149, 20, 22}}}},
|
||||
{Region: "United Arab Emirates", PortForward: true, OpenvpnUDP: models.PIAServerOpenvpn{CN: "dubai403", IPs: []net.IP{{217, 138, 193, 146}}}, OpenvpnTCP: models.PIAServerOpenvpn{CN: "dubai403", IPs: []net.IP{{217, 138, 193, 148}}}},
|
||||
{Region: "Venezuela", PortForward: true, OpenvpnUDP: models.PIAServerOpenvpn{CN: "venezuela402", IPs: []net.IP{{45, 133, 89, 217}}}, OpenvpnTCP: models.PIAServerOpenvpn{CN: "venezuela402", IPs: []net.IP{{45, 133, 89, 217}}}},
|
||||
{Region: "Vietnam", PortForward: true, OpenvpnUDP: models.PIAServerOpenvpn{CN: "vietnam401", IPs: []net.IP{{188, 214, 152, 76}}}, OpenvpnTCP: models.PIAServerOpenvpn{CN: "vietnam401", IPs: []net.IP{{188, 214, 152, 70}}}},
|
||||
{Region: "AU Melbourne", PortForward: true, OpenvpnUDP: models.PIAServerOpenvpn{CN: "melbourne406", IPs: []net.IP{{27, 50, 74, 148}}}, OpenvpnTCP: models.PIAServerOpenvpn{CN: "melbourne406", IPs: []net.IP{{27, 50, 74, 156}}}},
|
||||
{Region: "AU Perth", PortForward: true, OpenvpnUDP: models.PIAServerOpenvpn{CN: "perth404", IPs: []net.IP{{43, 250, 205, 186}}}, OpenvpnTCP: models.PIAServerOpenvpn{CN: "perth404", IPs: []net.IP{{43, 250, 205, 190}}}},
|
||||
{Region: "AU Sydney", PortForward: true, OpenvpnUDP: models.PIAServerOpenvpn{CN: "sydney403", IPs: []net.IP{{180, 92, 192, 156}}}, OpenvpnTCP: models.PIAServerOpenvpn{CN: "sydney403", IPs: []net.IP{{117, 120, 9, 7}}}},
|
||||
{Region: "Albania", PortForward: true, OpenvpnUDP: models.PIAServerOpenvpn{CN: "tirana401", IPs: []net.IP{{31, 171, 154, 134}}}, OpenvpnTCP: models.PIAServerOpenvpn{CN: "tirana401", IPs: []net.IP{{31, 171, 154, 136}}}},
|
||||
{Region: "Algeria", PortForward: true, OpenvpnUDP: models.PIAServerOpenvpn{CN: "algiers401", IPs: []net.IP{{45, 133, 91, 248}}}, OpenvpnTCP: models.PIAServerOpenvpn{CN: "algiers401", IPs: []net.IP{{45, 133, 91, 234}}}},
|
||||
{Region: "Andorra", PortForward: true, OpenvpnUDP: models.PIAServerOpenvpn{CN: "andorra403", IPs: []net.IP{{188, 241, 82, 4}}}, OpenvpnTCP: models.PIAServerOpenvpn{CN: "andorra403", IPs: []net.IP{{188, 241, 82, 7}}}},
|
||||
{Region: "Argentina", PortForward: true, OpenvpnUDP: models.PIAServerOpenvpn{CN: "buenosaires401", IPs: []net.IP{{190, 106, 134, 86}}}, OpenvpnTCP: models.PIAServerOpenvpn{CN: "buenosaires401", IPs: []net.IP{{190, 106, 134, 87}}}},
|
||||
{Region: "Armenia", PortForward: true, OpenvpnUDP: models.PIAServerOpenvpn{CN: "armenia401", IPs: []net.IP{{45, 139, 50, 244}}}, OpenvpnTCP: models.PIAServerOpenvpn{CN: "armenia401", IPs: []net.IP{{45, 139, 50, 249}}}},
|
||||
{Region: "Austria", PortForward: true, OpenvpnUDP: models.PIAServerOpenvpn{CN: "vienna402", IPs: []net.IP{{156, 146, 60, 46}}}, OpenvpnTCP: models.PIAServerOpenvpn{CN: "vienna402", IPs: []net.IP{{156, 146, 60, 53}}}},
|
||||
{Region: "Bahamas", PortForward: true, OpenvpnUDP: models.PIAServerOpenvpn{CN: "bahamas402", IPs: []net.IP{{45, 132, 143, 226}}}, OpenvpnTCP: models.PIAServerOpenvpn{CN: "bahamas402", IPs: []net.IP{{45, 132, 143, 208}}}},
|
||||
{Region: "Belgium", PortForward: true, OpenvpnUDP: models.PIAServerOpenvpn{CN: "brussels408", IPs: []net.IP{{91, 90, 123, 46}}}, OpenvpnTCP: models.PIAServerOpenvpn{CN: "brussels408", IPs: []net.IP{{91, 90, 123, 46}}}},
|
||||
{Region: "Brazil", PortForward: true, OpenvpnUDP: models.PIAServerOpenvpn{CN: "saopaolo404", IPs: []net.IP{{45, 133, 180, 248}}}, OpenvpnTCP: models.PIAServerOpenvpn{CN: "saopaolo404", IPs: []net.IP{{45, 133, 180, 244}}}},
|
||||
{Region: "Bulgaria", PortForward: true, OpenvpnUDP: models.PIAServerOpenvpn{CN: "sofia403", IPs: []net.IP{{217, 138, 221, 70}}}, OpenvpnTCP: models.PIAServerOpenvpn{CN: "sofia403", IPs: []net.IP{{217, 138, 221, 70}}}},
|
||||
{Region: "CA Montreal", PortForward: true, OpenvpnUDP: models.PIAServerOpenvpn{CN: "montreal410", IPs: []net.IP{{199, 36, 223, 235}}}, OpenvpnTCP: models.PIAServerOpenvpn{CN: "montreal401", IPs: []net.IP{{176, 113, 74, 45}}}},
|
||||
{Region: "CA Ontario", PortForward: true, OpenvpnUDP: models.PIAServerOpenvpn{CN: "ontario403", IPs: []net.IP{{172, 98, 92, 5}}}, OpenvpnTCP: models.PIAServerOpenvpn{CN: "ontario403", IPs: []net.IP{{172, 98, 92, 45}}}},
|
||||
{Region: "CA Toronto", PortForward: true, OpenvpnUDP: models.PIAServerOpenvpn{CN: "toronto405", IPs: []net.IP{{172, 83, 47, 226}}}, OpenvpnTCP: models.PIAServerOpenvpn{CN: "toronto405", IPs: []net.IP{{172, 83, 47, 234}}}},
|
||||
{Region: "CA Vancouver", PortForward: true, OpenvpnUDP: models.PIAServerOpenvpn{CN: "vancouver407", IPs: []net.IP{{172, 98, 89, 18}}}, OpenvpnTCP: models.PIAServerOpenvpn{CN: "vancouver407", IPs: []net.IP{{172, 98, 89, 43}}}},
|
||||
{Region: "Cambodia", PortForward: true, OpenvpnUDP: models.PIAServerOpenvpn{CN: "cambodia402", IPs: []net.IP{{188, 215, 235, 120}}}, OpenvpnTCP: models.PIAServerOpenvpn{CN: "cambodia401", IPs: []net.IP{{188, 215, 235, 100}}}},
|
||||
{Region: "China", PortForward: true, OpenvpnUDP: models.PIAServerOpenvpn{CN: "china404", IPs: []net.IP{{188, 241, 80, 10}}}, OpenvpnTCP: models.PIAServerOpenvpn{CN: "china404", IPs: []net.IP{{188, 241, 80, 10}}}},
|
||||
{Region: "Cyprus", PortForward: true, OpenvpnUDP: models.PIAServerOpenvpn{CN: "cyprus401", IPs: []net.IP{{45, 132, 137, 253}}}, OpenvpnTCP: models.PIAServerOpenvpn{CN: "cyprus401", IPs: []net.IP{{45, 132, 137, 234}}}},
|
||||
{Region: "Czech Republic", PortForward: true, OpenvpnUDP: models.PIAServerOpenvpn{CN: "prague402", IPs: []net.IP{{212, 102, 39, 163}}}, OpenvpnTCP: models.PIAServerOpenvpn{CN: "prague402", IPs: []net.IP{{212, 102, 39, 147}}}},
|
||||
{Region: "DE Berlin", PortForward: true, OpenvpnUDP: models.PIAServerOpenvpn{CN: "berlin421", IPs: []net.IP{{154, 13, 1, 87}}}, OpenvpnTCP: models.PIAServerOpenvpn{CN: "berlin421", IPs: []net.IP{{154, 13, 1, 80}}}},
|
||||
{Region: "DE Frankfurt", PortForward: true, OpenvpnUDP: models.PIAServerOpenvpn{CN: "frankfurt406", IPs: []net.IP{{212, 102, 57, 115}}}, OpenvpnTCP: models.PIAServerOpenvpn{CN: "frankfurt406", IPs: []net.IP{{212, 102, 57, 67}}}},
|
||||
{Region: "Denmark", PortForward: true, OpenvpnUDP: models.PIAServerOpenvpn{CN: "copenhagen402", IPs: []net.IP{{188, 126, 94, 72}}}, OpenvpnTCP: models.PIAServerOpenvpn{CN: "copenhagen402", IPs: []net.IP{{188, 126, 94, 72}}}},
|
||||
{Region: "Egypt", PortForward: true, OpenvpnUDP: models.PIAServerOpenvpn{CN: "cairo402", IPs: []net.IP{{188, 214, 122, 119}}}, 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, 77}}}, OpenvpnTCP: models.PIAServerOpenvpn{CN: "talinn402", IPs: []net.IP{{95, 153, 31, 73}}}},
|
||||
{Region: "Finland", PortForward: true, OpenvpnUDP: models.PIAServerOpenvpn{CN: "helsinki402", IPs: []net.IP{{188, 126, 89, 51}}}, OpenvpnTCP: models.PIAServerOpenvpn{CN: "helsinki402", IPs: []net.IP{{188, 126, 89, 53}}}},
|
||||
{Region: "France", PortForward: true, OpenvpnUDP: models.PIAServerOpenvpn{CN: "paris406", IPs: []net.IP{{143, 244, 57, 136}}}, OpenvpnTCP: models.PIAServerOpenvpn{CN: "paris406", IPs: []net.IP{{143, 244, 57, 184}}}},
|
||||
{Region: "Georgia", PortForward: true, OpenvpnUDP: models.PIAServerOpenvpn{CN: "georgia402", IPs: []net.IP{{45, 132, 138, 208}}}, OpenvpnTCP: models.PIAServerOpenvpn{CN: "georgia402", IPs: []net.IP{{45, 132, 138, 228}}}},
|
||||
{Region: "Greece", PortForward: true, OpenvpnUDP: models.PIAServerOpenvpn{CN: "athens403", IPs: []net.IP{{154, 57, 3, 108}}}, OpenvpnTCP: models.PIAServerOpenvpn{CN: "athens403", IPs: []net.IP{{154, 57, 3, 118}}}},
|
||||
{Region: "Greenland", PortForward: true, OpenvpnUDP: models.PIAServerOpenvpn{CN: "greenland402", IPs: []net.IP{{45, 131, 209, 206}}}, OpenvpnTCP: models.PIAServerOpenvpn{CN: "greenland402", IPs: []net.IP{{45, 131, 209, 226}}}},
|
||||
{Region: "Hong Kong", PortForward: true, OpenvpnUDP: models.PIAServerOpenvpn{CN: "china403", IPs: []net.IP{{86, 107, 104, 217}}}, OpenvpnTCP: models.PIAServerOpenvpn{CN: "china403", IPs: []net.IP{{86, 107, 104, 216}}}},
|
||||
{Region: "Hungary", PortForward: true, OpenvpnUDP: models.PIAServerOpenvpn{CN: "budapest401", IPs: []net.IP{{217, 138, 192, 218}}}, OpenvpnTCP: models.PIAServerOpenvpn{CN: "budapest401", IPs: []net.IP{{217, 138, 192, 220}}}},
|
||||
{Region: "Iceland", PortForward: true, OpenvpnUDP: models.PIAServerOpenvpn{CN: "reykjavik402", IPs: []net.IP{{45, 133, 193, 93}}}, OpenvpnTCP: models.PIAServerOpenvpn{CN: "reykjavik402", IPs: []net.IP{{45, 133, 193, 86}}}},
|
||||
{Region: "India", PortForward: true, OpenvpnUDP: models.PIAServerOpenvpn{CN: "mumbai402", IPs: []net.IP{{45, 120, 139, 128}}}, OpenvpnTCP: models.PIAServerOpenvpn{CN: "mumbai402", IPs: []net.IP{{45, 120, 139, 128}}}},
|
||||
{Region: "Ireland", PortForward: true, OpenvpnUDP: models.PIAServerOpenvpn{CN: "dublin409", IPs: []net.IP{{188, 241, 178, 35}}}, OpenvpnTCP: models.PIAServerOpenvpn{CN: "dublin409", IPs: []net.IP{{188, 241, 178, 40}}}},
|
||||
{Region: "Isle of Man", PortForward: true, OpenvpnUDP: models.PIAServerOpenvpn{CN: "douglas402", IPs: []net.IP{{45, 132, 140, 220}}}, OpenvpnTCP: models.PIAServerOpenvpn{CN: "douglas402", IPs: []net.IP{{45, 132, 140, 214}}}},
|
||||
{Region: "Israel", PortForward: true, OpenvpnUDP: models.PIAServerOpenvpn{CN: "jerusalem402", IPs: []net.IP{{185, 77, 248, 25}}}, OpenvpnTCP: models.PIAServerOpenvpn{CN: "jerusalem402", IPs: []net.IP{{185, 77, 248, 27}}}},
|
||||
{Region: "Italy", PortForward: true, OpenvpnUDP: models.PIAServerOpenvpn{CN: "milano403", IPs: []net.IP{{156, 146, 41, 115}}}, OpenvpnTCP: models.PIAServerOpenvpn{CN: "milano403", IPs: []net.IP{{156, 146, 41, 115}}}},
|
||||
{Region: "Japan", PortForward: true, OpenvpnUDP: models.PIAServerOpenvpn{CN: "tokyo404", IPs: []net.IP{{156, 146, 34, 227}}}, OpenvpnTCP: models.PIAServerOpenvpn{CN: "tokyo402", IPs: []net.IP{{156, 146, 34, 126}}}},
|
||||
{Region: "Kazakhstan", PortForward: true, OpenvpnUDP: models.PIAServerOpenvpn{CN: "kazakhstan402", IPs: []net.IP{{45, 133, 88, 227}}}, OpenvpnTCP: models.PIAServerOpenvpn{CN: "kazakhstan402", IPs: []net.IP{{45, 133, 88, 227}}}},
|
||||
{Region: "Latvia", PortForward: true, OpenvpnUDP: models.PIAServerOpenvpn{CN: "riga401", IPs: []net.IP{{109, 248, 149, 4}}}, OpenvpnTCP: models.PIAServerOpenvpn{CN: "riga401", IPs: []net.IP{{109, 248, 149, 14}}}},
|
||||
{Region: "Liechtenstein", PortForward: true, OpenvpnUDP: models.PIAServerOpenvpn{CN: "liechtenstein402", IPs: []net.IP{{45, 139, 48, 227}}}, OpenvpnTCP: models.PIAServerOpenvpn{CN: "liechtenstein402", IPs: []net.IP{{45, 139, 48, 207}}}},
|
||||
{Region: "Lithuania", PortForward: true, OpenvpnUDP: models.PIAServerOpenvpn{CN: "vilnius401", IPs: []net.IP{{85, 206, 165, 167}}}, OpenvpnTCP: models.PIAServerOpenvpn{CN: "vilnius401", IPs: []net.IP{{85, 206, 165, 167}}}},
|
||||
{Region: "Luxembourg", PortForward: true, OpenvpnUDP: models.PIAServerOpenvpn{CN: "luxembourg403", IPs: []net.IP{{92, 223, 89, 245}}}, OpenvpnTCP: models.PIAServerOpenvpn{CN: "luxembourg403", IPs: []net.IP{{92, 223, 89, 103}}}},
|
||||
{Region: "Macedonia", PortForward: true, OpenvpnUDP: models.PIAServerOpenvpn{CN: "macedonia401", IPs: []net.IP{{185, 225, 28, 122}}}, OpenvpnTCP: models.PIAServerOpenvpn{CN: "macedonia401", IPs: []net.IP{{185, 225, 28, 126}}}},
|
||||
{Region: "Malta", PortForward: true, OpenvpnUDP: models.PIAServerOpenvpn{CN: "malta402", IPs: []net.IP{{45, 137, 198, 219}}}, OpenvpnTCP: models.PIAServerOpenvpn{CN: "malta402", IPs: []net.IP{{45, 137, 198, 208}}}},
|
||||
{Region: "Mexico", PortForward: true, OpenvpnUDP: models.PIAServerOpenvpn{CN: "mexico410", IPs: []net.IP{{77, 81, 142, 114}}}, OpenvpnTCP: models.PIAServerOpenvpn{CN: "mexico410", IPs: []net.IP{{77, 81, 142, 113}}}},
|
||||
{Region: "Moldova", PortForward: true, OpenvpnUDP: models.PIAServerOpenvpn{CN: "chisinau401", IPs: []net.IP{{178, 175, 129, 38}}}, OpenvpnTCP: models.PIAServerOpenvpn{CN: "chisinau401", IPs: []net.IP{{178, 175, 129, 37}}}},
|
||||
{Region: "Monaco", PortForward: true, OpenvpnUDP: models.PIAServerOpenvpn{CN: "monaco402", IPs: []net.IP{{45, 137, 199, 208}}}, OpenvpnTCP: models.PIAServerOpenvpn{CN: "monaco402", IPs: []net.IP{{45, 137, 199, 206}}}},
|
||||
{Region: "Montenegro", PortForward: true, OpenvpnUDP: models.PIAServerOpenvpn{CN: "montenegro401", IPs: []net.IP{{45, 131, 208, 250}}}, OpenvpnTCP: models.PIAServerOpenvpn{CN: "montenegro401", IPs: []net.IP{{45, 131, 208, 249}}}},
|
||||
{Region: "Morocco", PortForward: true, OpenvpnUDP: models.PIAServerOpenvpn{CN: "morocco401", IPs: []net.IP{{45, 131, 211, 233}}}, OpenvpnTCP: models.PIAServerOpenvpn{CN: "morocco401", IPs: []net.IP{{45, 131, 211, 243}}}},
|
||||
{Region: "Netherlands", PortForward: true, OpenvpnUDP: models.PIAServerOpenvpn{CN: "amsterdam402", IPs: []net.IP{{212, 102, 34, 144}}}, OpenvpnTCP: models.PIAServerOpenvpn{CN: "amsterdam402", IPs: []net.IP{{212, 102, 35, 84}}}},
|
||||
{Region: "New Zealand", PortForward: true, OpenvpnUDP: models.PIAServerOpenvpn{CN: "newzealand404", IPs: []net.IP{{43, 250, 207, 25}}}, OpenvpnTCP: models.PIAServerOpenvpn{CN: "newzealand403", IPs: []net.IP{{43, 250, 207, 94}}}},
|
||||
{Region: "Nigeria", PortForward: true, OpenvpnUDP: models.PIAServerOpenvpn{CN: "nigeria401", IPs: []net.IP{{45, 137, 196, 243}}}, OpenvpnTCP: models.PIAServerOpenvpn{CN: "nigeria402", IPs: []net.IP{{45, 137, 196, 217}}}},
|
||||
{Region: "Norway", PortForward: true, OpenvpnUDP: models.PIAServerOpenvpn{CN: "oslo402", IPs: []net.IP{{46, 246, 122, 75}}}, OpenvpnTCP: models.PIAServerOpenvpn{CN: "oslo402", IPs: []net.IP{{46, 246, 122, 88}}}},
|
||||
{Region: "Panama", PortForward: true, OpenvpnUDP: models.PIAServerOpenvpn{CN: "panama402", IPs: []net.IP{{45, 131, 210, 220}}}, OpenvpnTCP: models.PIAServerOpenvpn{CN: "panama402", IPs: []net.IP{{45, 131, 210, 220}}}},
|
||||
{Region: "Philippines", PortForward: true, OpenvpnUDP: models.PIAServerOpenvpn{CN: "philippines402", IPs: []net.IP{{188, 214, 125, 153}}}, OpenvpnTCP: models.PIAServerOpenvpn{CN: "philippines401", IPs: []net.IP{{188, 214, 125, 136}}}},
|
||||
{Region: "Poland", PortForward: true, OpenvpnUDP: models.PIAServerOpenvpn{CN: "warsaw406", IPs: []net.IP{{194, 110, 114, 74}}}, OpenvpnTCP: models.PIAServerOpenvpn{CN: "warsaw406", IPs: []net.IP{{194, 110, 114, 68}}}},
|
||||
{Region: "Portugal", PortForward: true, OpenvpnUDP: models.PIAServerOpenvpn{CN: "lisbon403", IPs: []net.IP{{89, 26, 241, 132}}}, OpenvpnTCP: models.PIAServerOpenvpn{CN: "lisbon403", IPs: []net.IP{{89, 26, 241, 132}}}},
|
||||
{Region: "Qatar", PortForward: true, OpenvpnUDP: models.PIAServerOpenvpn{CN: "qatar402", IPs: []net.IP{{45, 131, 7, 208}}}, OpenvpnTCP: models.PIAServerOpenvpn{CN: "qatar402", IPs: []net.IP{{45, 131, 7, 210}}}},
|
||||
{Region: "Romania", PortForward: true, OpenvpnUDP: models.PIAServerOpenvpn{CN: "romania407", IPs: []net.IP{{143, 244, 54, 136}}}, OpenvpnTCP: models.PIAServerOpenvpn{CN: "romania407", IPs: []net.IP{{143, 244, 54, 170}}}},
|
||||
{Region: "Saudi Arabia", PortForward: true, OpenvpnUDP: models.PIAServerOpenvpn{CN: "saudiarabia401", IPs: []net.IP{{45, 131, 6, 236}}}, OpenvpnTCP: models.PIAServerOpenvpn{CN: "saudiarabia401", IPs: []net.IP{{45, 131, 6, 236}}}},
|
||||
{Region: "Serbia", PortForward: true, OpenvpnUDP: models.PIAServerOpenvpn{CN: "belgrade401", IPs: []net.IP{{37, 120, 193, 246}}}, OpenvpnTCP: models.PIAServerOpenvpn{CN: "belgrade401", IPs: []net.IP{{37, 120, 193, 254}}}},
|
||||
{Region: "Singapore", PortForward: true, OpenvpnUDP: models.PIAServerOpenvpn{CN: "singapore401", IPs: []net.IP{{156, 146, 57, 177}}}, OpenvpnTCP: models.PIAServerOpenvpn{CN: "singapore401", IPs: []net.IP{{156, 146, 57, 223}}}},
|
||||
{Region: "Slovakia", PortForward: true, OpenvpnUDP: models.PIAServerOpenvpn{CN: "bratislava402", IPs: []net.IP{{37, 120, 221, 216}}}, 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, 39}}}, OpenvpnTCP: models.PIAServerOpenvpn{CN: "johannesburg401", IPs: []net.IP{{154, 16, 93, 45}}}},
|
||||
{Region: "Spain", PortForward: true, OpenvpnUDP: models.PIAServerOpenvpn{CN: "madrid402", IPs: []net.IP{{212, 102, 49, 25}}}, OpenvpnTCP: models.PIAServerOpenvpn{CN: "madrid402", IPs: []net.IP{{212, 102, 49, 17}}}},
|
||||
{Region: "Sri Lanka", PortForward: true, OpenvpnUDP: models.PIAServerOpenvpn{CN: "srilanka402", IPs: []net.IP{{45, 132, 136, 210}}}, OpenvpnTCP: models.PIAServerOpenvpn{CN: "srilanka402", IPs: []net.IP{{45, 132, 136, 214}}}},
|
||||
{Region: "Sweden", PortForward: true, OpenvpnUDP: models.PIAServerOpenvpn{CN: "stockholm403", IPs: []net.IP{{195, 246, 120, 94}}}, OpenvpnTCP: models.PIAServerOpenvpn{CN: "stockholm401", IPs: []net.IP{{195, 246, 120, 14}}}},
|
||||
{Region: "Switzerland", PortForward: true, OpenvpnUDP: models.PIAServerOpenvpn{CN: "zurich404", IPs: []net.IP{{212, 102, 37, 73}}}, OpenvpnTCP: models.PIAServerOpenvpn{CN: "zurich408", IPs: []net.IP{{212, 102, 37, 6}}}},
|
||||
{Region: "Taiwan", PortForward: true, OpenvpnUDP: models.PIAServerOpenvpn{CN: "taiwan402", IPs: []net.IP{{188, 214, 106, 93}}}, OpenvpnTCP: models.PIAServerOpenvpn{CN: "taiwan402", IPs: []net.IP{{188, 214, 106, 94}}}},
|
||||
{Region: "Turkey", PortForward: true, OpenvpnUDP: models.PIAServerOpenvpn{CN: "istanbul401", IPs: []net.IP{{188, 213, 34, 70}}}, OpenvpnTCP: models.PIAServerOpenvpn{CN: "istanbul401", IPs: []net.IP{{188, 213, 34, 73}}}},
|
||||
{Region: "UK London", PortForward: true, OpenvpnUDP: models.PIAServerOpenvpn{CN: "london402", IPs: []net.IP{{212, 102, 63, 137}}}, OpenvpnTCP: models.PIAServerOpenvpn{CN: "london402", IPs: []net.IP{{212, 102, 63, 154}}}},
|
||||
{Region: "UK Manchester", PortForward: true, OpenvpnUDP: models.PIAServerOpenvpn{CN: "manchester410", IPs: []net.IP{{194, 37, 96, 40}}}, OpenvpnTCP: models.PIAServerOpenvpn{CN: "manchester410", IPs: []net.IP{{194, 37, 96, 43}}}},
|
||||
{Region: "UK Southampton", PortForward: true, OpenvpnUDP: models.PIAServerOpenvpn{CN: "southampton403", IPs: []net.IP{{143, 244, 37, 113}}}, OpenvpnTCP: models.PIAServerOpenvpn{CN: "southampton403", IPs: []net.IP{{143, 244, 37, 99}}}},
|
||||
{Region: "US Atlanta", PortForward: false, OpenvpnUDP: models.PIAServerOpenvpn{CN: "atlanta424", IPs: []net.IP{{154, 21, 21, 193}}}, OpenvpnTCP: models.PIAServerOpenvpn{CN: "atlanta424", IPs: []net.IP{{154, 21, 21, 175}}}},
|
||||
{Region: "US California", PortForward: false, OpenvpnUDP: models.PIAServerOpenvpn{CN: "losangeles409", IPs: []net.IP{{143, 244, 49, 144}}}, OpenvpnTCP: models.PIAServerOpenvpn{CN: "losangeles409", IPs: []net.IP{{143, 244, 49, 186}}}},
|
||||
{Region: "US Chicago", PortForward: false, OpenvpnUDP: models.PIAServerOpenvpn{CN: "chicago410", IPs: []net.IP{{154, 21, 28, 239}}}, OpenvpnTCP: models.PIAServerOpenvpn{CN: "chicago410", IPs: []net.IP{{154, 21, 28, 247}}}},
|
||||
{Region: "US Denver", PortForward: false, OpenvpnUDP: models.PIAServerOpenvpn{CN: "denver404", IPs: []net.IP{{70, 39, 111, 204}}}, OpenvpnTCP: models.PIAServerOpenvpn{CN: "denver404", IPs: []net.IP{{70, 39, 111, 236}}}},
|
||||
{Region: "US East", PortForward: false, OpenvpnUDP: models.PIAServerOpenvpn{CN: "newjersey405", IPs: []net.IP{{37, 235, 104, 13}}}, OpenvpnTCP: models.PIAServerOpenvpn{CN: "newjersey405", IPs: []net.IP{{37, 235, 104, 17}}}},
|
||||
{Region: "US Florida", PortForward: false, OpenvpnUDP: models.PIAServerOpenvpn{CN: "miami403", IPs: []net.IP{{37, 235, 98, 3}}}, OpenvpnTCP: models.PIAServerOpenvpn{CN: "miami403", IPs: []net.IP{{37, 235, 98, 64}}}},
|
||||
{Region: "US Houston", PortForward: false, OpenvpnUDP: models.PIAServerOpenvpn{CN: "houston406", IPs: []net.IP{{205, 251, 138, 134}}}, OpenvpnTCP: models.PIAServerOpenvpn{CN: "houston406", IPs: []net.IP{{205, 251, 138, 137}}}},
|
||||
{Region: "US Las Vegas", PortForward: false, OpenvpnUDP: models.PIAServerOpenvpn{CN: "lasvegas406", IPs: []net.IP{{82, 102, 31, 187}}}, OpenvpnTCP: models.PIAServerOpenvpn{CN: "lasvegas406", IPs: []net.IP{{82, 102, 31, 180}}}},
|
||||
{Region: "US New York", PortForward: false, OpenvpnUDP: models.PIAServerOpenvpn{CN: "newyork420", IPs: []net.IP{{138, 199, 10, 115}}}, OpenvpnTCP: models.PIAServerOpenvpn{CN: "newyork418", IPs: []net.IP{{156, 146, 58, 217}}}},
|
||||
{Region: "US Seattle", PortForward: false, OpenvpnUDP: models.PIAServerOpenvpn{CN: "seattle413", IPs: []net.IP{{154, 21, 20, 44}}}, OpenvpnTCP: models.PIAServerOpenvpn{CN: "seattle413", IPs: []net.IP{{154, 21, 20, 55}}}},
|
||||
{Region: "US Silicon Valley", PortForward: false, OpenvpnUDP: models.PIAServerOpenvpn{CN: "siliconvalley407", IPs: []net.IP{{154, 21, 212, 209}}}, OpenvpnTCP: models.PIAServerOpenvpn{CN: "siliconvalley407", IPs: []net.IP{{154, 21, 212, 197}}}},
|
||||
{Region: "US Texas", PortForward: false, OpenvpnUDP: models.PIAServerOpenvpn{CN: "dallas418", IPs: []net.IP{{154, 3, 250, 180}}}, OpenvpnTCP: models.PIAServerOpenvpn{CN: "dallas418", IPs: []net.IP{{154, 3, 250, 179}}}},
|
||||
{Region: "US Washington DC", PortForward: false, OpenvpnUDP: models.PIAServerOpenvpn{CN: "washington420", IPs: []net.IP{{70, 32, 6, 69}}}, OpenvpnTCP: models.PIAServerOpenvpn{CN: "washington420", IPs: []net.IP{{70, 32, 6, 68}}}},
|
||||
{Region: "US West", PortForward: false, OpenvpnUDP: models.PIAServerOpenvpn{CN: "phoenix407", IPs: []net.IP{{184, 170, 241, 93}}}, OpenvpnTCP: models.PIAServerOpenvpn{CN: "phoenix407", IPs: []net.IP{{184, 170, 241, 69}}}},
|
||||
{Region: "Ukraine", PortForward: true, OpenvpnUDP: models.PIAServerOpenvpn{CN: "kiev401", IPs: []net.IP{{62, 149, 20, 61}}}, OpenvpnTCP: models.PIAServerOpenvpn{CN: "kiev401", IPs: []net.IP{{62, 149, 20, 58}}}},
|
||||
{Region: "United Arab Emirates", PortForward: true, OpenvpnUDP: models.PIAServerOpenvpn{CN: "dubai403", IPs: []net.IP{{217, 138, 193, 156}}}, OpenvpnTCP: models.PIAServerOpenvpn{CN: "dubai403", IPs: []net.IP{{217, 138, 193, 148}}}},
|
||||
{Region: "Venezuela", PortForward: true, OpenvpnUDP: models.PIAServerOpenvpn{CN: "venezuela401", IPs: []net.IP{{45, 133, 89, 239}}}, OpenvpnTCP: models.PIAServerOpenvpn{CN: "venezuela401", IPs: []net.IP{{45, 133, 89, 246}}}},
|
||||
{Region: "Vietnam", PortForward: true, OpenvpnUDP: models.PIAServerOpenvpn{CN: "vietnam402", IPs: []net.IP{{188, 214, 152, 83}}}, OpenvpnTCP: models.PIAServerOpenvpn{CN: "vietnam402", IPs: []net.IP{{188, 214, 152, 83}}}},
|
||||
}
|
||||
}
|
||||
|
||||
func PIAOldGeoChoices() (choices []string) {
|
||||
servers := PIAOldServers()
|
||||
choices = make([]string, len(servers))
|
||||
for i := range servers {
|
||||
choices[i] = servers[i].Region
|
||||
}
|
||||
return choices
|
||||
}
|
||||
|
||||
//nolint:lll
|
||||
func PIAOldServers() []models.PIAOldServer {
|
||||
return []models.PIAOldServer{
|
||||
{Region: "AU Melbourne", IPs: []net.IP{{27, 50, 82, 131}, {43, 250, 204, 105}, {43, 250, 204, 107}, {43, 250, 204, 109}, {43, 250, 204, 111}, {43, 250, 204, 113}, {43, 250, 204, 115}, {43, 250, 204, 117}, {43, 250, 204, 119}, {43, 250, 204, 123}, {43, 250, 204, 125}}},
|
||||
{Region: "AU Perth", IPs: []net.IP{{43, 250, 205, 59}, {43, 250, 205, 91}, {43, 250, 205, 93}, {43, 250, 205, 95}}},
|
||||
{Region: "AU Sydney", IPs: []net.IP{{27, 50, 68, 23}, {27, 50, 70, 87}, {27, 50, 77, 251}, {27, 50, 81, 117}, {103, 13, 102, 123}, {103, 13, 102, 127}, {118, 127, 60, 51}, {221, 121, 145, 135}, {221, 121, 145, 137}, {221, 121, 145, 145}, {221, 121, 145, 147}, {221, 121, 145, 159}, {221, 121, 146, 203}, {221, 121, 148, 221}, {221, 121, 152, 215}}},
|
||||
{Region: "Albania", IPs: []net.IP{{31, 171, 154, 114}}},
|
||||
{Region: "Argentina", IPs: []net.IP{{190, 106, 134, 100}}},
|
||||
{Region: "Austria", IPs: []net.IP{{89, 187, 168, 6}, {156, 146, 60, 129}}},
|
||||
{Region: "Belgium", IPs: []net.IP{{77, 243, 191, 18}, {77, 243, 191, 19}, {77, 243, 191, 20}, {185, 232, 21, 26}}},
|
||||
{Region: "Bosnia and Herzegovina", IPs: []net.IP{{185, 164, 35, 54}}},
|
||||
{Region: "Bulgaria", IPs: []net.IP{{217, 138, 221, 66}}},
|
||||
{Region: "CA Montreal", IPs: []net.IP{{172, 98, 71, 194}, {199, 36, 223, 130}, {199, 36, 223, 194}}},
|
||||
{Region: "CA Ontario", IPs: []net.IP{{162, 219, 176, 26}, {162, 219, 176, 42}, {184, 75, 208, 2}, {184, 75, 208, 90}, {184, 75, 208, 114}, {184, 75, 208, 122}, {184, 75, 208, 130}, {184, 75, 208, 146}, {184, 75, 208, 170}, {184, 75, 208, 202}, {184, 75, 210, 18}, {184, 75, 210, 98}, {184, 75, 210, 106}, {184, 75, 213, 186}, {184, 75, 213, 218}, {184, 75, 214, 18}, {184, 75, 215, 18}, {184, 75, 215, 26}, {184, 75, 215, 66}, {184, 75, 215, 74}}},
|
||||
{Region: "CA Toronto", IPs: []net.IP{{66, 115, 142, 130}, {66, 115, 145, 199}, {172, 98, 92, 66}, {172, 98, 92, 130}, {172, 98, 92, 194}}},
|
||||
{Region: "CA Vancouver", IPs: []net.IP{{162, 216, 47, 66}, {162, 216, 47, 194}, {172, 98, 89, 130}, {172, 98, 89, 194}}},
|
||||
{Region: "Czech Republic", IPs: []net.IP{{212, 102, 39, 1}}},
|
||||
{Region: "DE Berlin", IPs: []net.IP{{185, 230, 127, 238}, {193, 176, 86, 122}, {193, 176, 86, 123}, {193, 176, 86, 134}, {193, 176, 86, 178}, {194, 36, 108, 6}}},
|
||||
{Region: "DE Frankfurt", IPs: []net.IP{{195, 181, 170, 239}, {195, 181, 170, 240}, {195, 181, 170, 241}, {195, 181, 170, 242}, {195, 181, 170, 243}, {195, 181, 170, 244}, {212, 102, 57, 138}}},
|
||||
{Region: "Denmark", IPs: []net.IP{{188, 126, 94, 34}}},
|
||||
{Region: "Estonia", IPs: []net.IP{{77, 247, 111, 82}, {77, 247, 111, 98}, {77, 247, 111, 114}, {77, 247, 111, 130}}},
|
||||
{Region: "Finland", IPs: []net.IP{{188, 126, 89, 4}, {188, 126, 89, 194}}},
|
||||
{Region: "France", IPs: []net.IP{{156, 146, 63, 1}, {156, 146, 63, 65}}},
|
||||
{Region: "Greece", IPs: []net.IP{{154, 57, 3, 91}, {154, 57, 3, 106}, {154, 57, 3, 145}}},
|
||||
{Region: "Hungary", IPs: []net.IP{{185, 128, 26, 18}, {185, 128, 26, 19}, {185, 128, 26, 20}, {185, 128, 26, 21}, {185, 128, 26, 22}, {185, 128, 26, 23}, {185, 128, 26, 24}, {185, 189, 114, 98}}},
|
||||
{Region: "Iceland", IPs: []net.IP{{45, 133, 193, 50}}},
|
||||
{Region: "India", IPs: []net.IP{{45, 120, 139, 108}, {45, 120, 139, 109}, {150, 242, 12, 155}, {150, 242, 12, 171}, {150, 242, 12, 187}}},
|
||||
{Region: "Ireland", IPs: []net.IP{{193, 56, 252, 210}, {193, 56, 252, 226}, {193, 56, 252, 242}, {193, 56, 252, 250}, {193, 56, 252, 251}, {193, 56, 252, 252}}},
|
||||
{Region: "Israel", IPs: []net.IP{{31, 168, 172, 142}, {31, 168, 172, 143}, {31, 168, 172, 145}, {31, 168, 172, 146}}},
|
||||
{Region: "Italy", IPs: []net.IP{{156, 146, 41, 129}, {156, 146, 41, 193}}},
|
||||
{Region: "Japan", IPs: []net.IP{{156, 146, 34, 1}, {156, 146, 34, 65}}},
|
||||
{Region: "Latvia", IPs: []net.IP{{46, 183, 217, 34}, {46, 183, 218, 130}, {46, 183, 218, 146}}},
|
||||
{Region: "Lithuania", IPs: []net.IP{{85, 206, 165, 96}, {85, 206, 165, 112}, {85, 206, 165, 128}}},
|
||||
{Region: "Luxembourg", IPs: []net.IP{{92, 223, 89, 133}, {92, 223, 89, 134}, {92, 223, 89, 135}, {92, 223, 89, 136}, {92, 223, 89, 137}, {92, 223, 89, 138}, {92, 223, 89, 140}, {92, 223, 89, 142}}},
|
||||
{Region: "Moldova", IPs: []net.IP{{178, 17, 172, 242}, {178, 17, 173, 194}, {178, 175, 128, 34}}},
|
||||
{Region: "Netherlands", IPs: []net.IP{{89, 187, 174, 198}, {212, 102, 35, 101}, {212, 102, 35, 102}, {212, 102, 35, 103}, {212, 102, 35, 104}}},
|
||||
{Region: "New Zealand", IPs: []net.IP{{43, 250, 207, 1}, {43, 250, 207, 3}}},
|
||||
{Region: "North Macedonia", IPs: []net.IP{{185, 225, 28, 130}}},
|
||||
{Region: "Norway", IPs: []net.IP{{46, 246, 122, 34}, {46, 246, 122, 162}}},
|
||||
{Region: "Poland", IPs: []net.IP{{185, 244, 214, 195}, {185, 244, 214, 196}, {185, 244, 214, 197}, {185, 244, 214, 198}, {185, 244, 214, 199}, {185, 244, 214, 200}}},
|
||||
{Region: "Portugal", IPs: []net.IP{{89, 26, 241, 86}, {89, 26, 241, 102}, {89, 26, 241, 130}}},
|
||||
{Region: "Romania", IPs: []net.IP{{86, 105, 25, 69}, {86, 105, 25, 70}, {86, 105, 25, 74}, {86, 105, 25, 75}, {86, 105, 25, 76}, {86, 105, 25, 77}, {86, 105, 25, 78}, {89, 33, 8, 38}, {89, 33, 8, 42}, {93, 115, 7, 70}, {94, 176, 148, 35}, {143, 244, 54, 1}, {185, 45, 12, 126}, {185, 210, 218, 98}, {185, 210, 218, 99}, {185, 210, 218, 100}, {185, 210, 218, 101}, {185, 210, 218, 102}, {185, 210, 218, 105}, {188, 240, 220, 26}}},
|
||||
{Region: "Serbia", IPs: []net.IP{{37, 120, 193, 226}}},
|
||||
{Region: "Singapore", IPs: []net.IP{{156, 146, 56, 193}, {156, 146, 57, 38}, {156, 146, 57, 235}, {156, 146, 57, 244}}},
|
||||
{Region: "Slovakia", IPs: []net.IP{{37, 120, 221, 82}, {37, 120, 221, 98}}},
|
||||
{Region: "South Africa", IPs: []net.IP{{102, 165, 20, 133}}},
|
||||
{Region: "Spain", IPs: []net.IP{{212, 102, 49, 185}, {212, 102, 49, 251}}},
|
||||
{Region: "Sweden", IPs: []net.IP{{46, 246, 3, 254}}},
|
||||
{Region: "Switzerland", IPs: []net.IP{{156, 146, 62, 193}, {212, 102, 36, 1}, {212, 102, 36, 166}, {212, 102, 37, 240}, {212, 102, 37, 241}, {212, 102, 37, 242}, {212, 102, 37, 243}}},
|
||||
{Region: "Turkey", IPs: []net.IP{{185, 195, 79, 34}, {185, 195, 79, 82}}},
|
||||
{Region: "UAE", IPs: []net.IP{{45, 9, 250, 46}}},
|
||||
{Region: "UK London", IPs: []net.IP{{212, 102, 52, 1}}},
|
||||
{Region: "UK Manchester", IPs: []net.IP{{89, 238, 137, 36}, {89, 238, 137, 37}, {89, 238, 137, 38}, {89, 238, 137, 39}, {89, 238, 139, 52}, {89, 238, 139, 53}, {89, 238, 139, 54}, {89, 238, 139, 55}, {89, 238, 139, 56}, {89, 238, 139, 57}, {89, 238, 139, 58}, {89, 249, 67, 220}}},
|
||||
{Region: "UK Southampton", IPs: []net.IP{{143, 244, 36, 58}, {143, 244, 37, 1}, {143, 244, 38, 1}, {143, 244, 38, 60}, {143, 244, 38, 119}}},
|
||||
{Region: "US Atlanta", IPs: []net.IP{{156, 146, 46, 1}, {156, 146, 46, 134}, {156, 146, 46, 198}, {156, 146, 47, 11}}},
|
||||
{Region: "US California", IPs: []net.IP{{37, 235, 108, 208}, {89, 187, 187, 129}, {89, 187, 187, 162}, {91, 207, 175, 194}, {91, 207, 175, 195}, {91, 207, 175, 197}, {91, 207, 175, 198}, {91, 207, 175, 199}, {91, 207, 175, 200}, {91, 207, 175, 205}, {91, 207, 175, 206}, {91, 207, 175, 207}, {91, 207, 175, 209}, {91, 207, 175, 210}, {91, 207, 175, 212}}},
|
||||
{Region: "US Chicago", IPs: []net.IP{{156, 146, 50, 1}, {156, 146, 50, 65}, {156, 146, 50, 134}, {156, 146, 50, 198}, {156, 146, 51, 11}, {212, 102, 58, 113}, {212, 102, 59, 54}, {212, 102, 59, 129}}},
|
||||
{Region: "US Dallas", IPs: []net.IP{{156, 146, 38, 65}, {156, 146, 38, 161}, {156, 146, 39, 1}, {156, 146, 39, 6}, {156, 146, 52, 6}, {156, 146, 52, 70}, {156, 146, 52, 139}, {156, 146, 52, 203}}},
|
||||
{Region: "US Denver", IPs: []net.IP{{70, 39, 77, 130}, {70, 39, 92, 2}, {70, 39, 113, 194}, {174, 128, 225, 2}, {174, 128, 226, 10}, {174, 128, 226, 18}, {174, 128, 227, 2}, {174, 128, 227, 226}, {174, 128, 236, 98}, {174, 128, 242, 234}, {174, 128, 242, 250}, {174, 128, 243, 98}, {174, 128, 244, 74}, {174, 128, 245, 122}, {174, 128, 246, 10}, {199, 115, 98, 146}, {199, 115, 98, 234}, {199, 115, 101, 178}, {199, 115, 101, 186}, {199, 115, 102, 146}}},
|
||||
{Region: "US East", IPs: []net.IP{{156, 146, 58, 202}, {156, 146, 58, 203}, {156, 146, 58, 204}, {156, 146, 58, 205}, {156, 146, 58, 207}, {156, 146, 58, 208}, {156, 146, 58, 209}, {193, 37, 253, 115}, {193, 37, 253, 134}, {194, 59, 251, 8}, {194, 59, 251, 11}, {194, 59, 251, 22}, {194, 59, 251, 28}, {194, 59, 251, 56}, {194, 59, 251, 62}, {194, 59, 251, 69}, {194, 59, 251, 82}, {194, 59, 251, 84}, {194, 59, 251, 91}, {194, 59, 251, 112}}},
|
||||
{Region: "US Florida", IPs: []net.IP{{193, 37, 252, 6}, {193, 37, 252, 7}, {193, 37, 252, 8}, {193, 37, 252, 9}, {193, 37, 252, 10}, {193, 37, 252, 11}, {193, 37, 252, 12}, {193, 37, 252, 14}, {193, 37, 252, 15}, {193, 37, 252, 16}, {193, 37, 252, 17}, {193, 37, 252, 18}, {193, 37, 252, 19}, {193, 37, 252, 20}, {193, 37, 252, 21}, {193, 37, 252, 23}, {193, 37, 252, 24}, {193, 37, 252, 25}, {193, 37, 252, 26}, {193, 37, 252, 27}}},
|
||||
{Region: "US Houston", IPs: []net.IP{{74, 81, 88, 26}, {74, 81, 88, 42}, {74, 81, 88, 66}, {74, 81, 88, 74}, {205, 251, 148, 66}, {205, 251, 148, 90}, {205, 251, 148, 98}, {205, 251, 148, 122}, {205, 251, 148, 130}, {205, 251, 148, 138}, {205, 251, 148, 186}, {205, 251, 150, 146}, {205, 251, 150, 170}}},
|
||||
{Region: "US Las Vegas", IPs: []net.IP{{79, 110, 53, 50}, {79, 110, 53, 66}, {79, 110, 53, 98}, {79, 110, 53, 114}, {79, 110, 53, 130}, {79, 110, 53, 146}, {79, 110, 53, 162}, {79, 110, 53, 178}, {79, 110, 53, 194}, {79, 110, 53, 210}, {162, 251, 236, 7}, {199, 127, 56, 83}, {199, 127, 56, 84}, {199, 127, 56, 87}, {199, 127, 56, 89}, {199, 127, 56, 90}}},
|
||||
{Region: "US New York City", IPs: []net.IP{{156, 146, 36, 225}, {156, 146, 37, 129}, {156, 146, 58, 1}, {156, 146, 58, 134}}},
|
||||
{Region: "US Seattle", IPs: []net.IP{{156, 146, 48, 65}, {156, 146, 48, 135}, {156, 146, 48, 200}, {156, 146, 49, 13}, {212, 102, 46, 129}, {212, 102, 46, 193}, {212, 102, 47, 134}}},
|
||||
{Region: "US Silicon Valley", IPs: []net.IP{{199, 116, 118, 130}, {199, 116, 118, 132}, {199, 116, 118, 134}, {199, 116, 118, 136}, {199, 116, 118, 145}, {199, 116, 118, 148}, {199, 116, 118, 149}, {199, 116, 118, 157}, {199, 116, 118, 166}, {199, 116, 118, 169}, {199, 116, 118, 172}}},
|
||||
{Region: "US Washington DC", IPs: []net.IP{{70, 32, 0, 46}, {70, 32, 0, 51}, {70, 32, 0, 53}, {70, 32, 0, 62}, {70, 32, 0, 64}, {70, 32, 0, 68}, {70, 32, 0, 69}, {70, 32, 0, 72}, {70, 32, 0, 76}, {70, 32, 0, 77}, {70, 32, 0, 106}, {70, 32, 0, 107}, {70, 32, 0, 114}, {70, 32, 0, 116}, {70, 32, 0, 120}, {70, 32, 0, 167}, {70, 32, 0, 168}, {70, 32, 0, 170}, {70, 32, 0, 172}, {70, 32, 0, 173}}},
|
||||
{Region: "US West", IPs: []net.IP{{184, 170, 241, 130}, {184, 170, 241, 194}, {184, 170, 242, 135}, {184, 170, 242, 199}}},
|
||||
{Region: "Ukraine", IPs: []net.IP{{62, 149, 20, 10}, {62, 149, 20, 40}}},
|
||||
}
|
||||
}
|
||||
|
||||
const (
|
||||
PIAPortForwardURL models.URL = "http://209.222.18.222:2000"
|
||||
)
|
||||
|
||||
206
internal/constants/privado.go
Normal file
206
internal/constants/privado.go
Normal file
@@ -0,0 +1,206 @@
|
||||
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
|
||||
}
|
||||
|
||||
//nolint:gomnd
|
||||
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}},
|
||||
}
|
||||
}
|
||||
@@ -23,19 +23,19 @@ func GetAllServers() (allServers models.AllServers) {
|
||||
},
|
||||
Pia: models.PiaServers{
|
||||
Version: 2,
|
||||
Timestamp: 1602531173,
|
||||
Timestamp: 1605392393,
|
||||
Servers: PIAServers(),
|
||||
},
|
||||
PiaOld: models.PiaOldServers{
|
||||
Version: 1,
|
||||
Timestamp: 1602523433,
|
||||
Servers: PIAOldServers(),
|
||||
},
|
||||
Purevpn: models.PurevpnServers{
|
||||
Version: 1,
|
||||
Timestamp: 1599323261,
|
||||
Servers: PurevpnServers(),
|
||||
},
|
||||
Privado: models.PrivadoServers{
|
||||
Version: 2,
|
||||
Timestamp: 1604963273,
|
||||
Servers: PrivadoServers(),
|
||||
},
|
||||
Surfshark: models.SurfsharkServers{
|
||||
Version: 1,
|
||||
Timestamp: 1599957644,
|
||||
@@ -47,8 +47,8 @@ func GetAllServers() (allServers models.AllServers) {
|
||||
Servers: VyprvpnServers(),
|
||||
},
|
||||
Windscribe: models.WindscribeServers{
|
||||
Version: 1,
|
||||
Timestamp: 1599323261,
|
||||
Version: 2,
|
||||
Timestamp: 1604019438,
|
||||
Servers: WindscribeServers(),
|
||||
},
|
||||
}
|
||||
|
||||
@@ -54,10 +54,10 @@ func Test_versions(t *testing.T) {
|
||||
version: allServers.Pia.Version,
|
||||
digest: "f1e01afe",
|
||||
},
|
||||
"Private Internet Access Old": {
|
||||
model: models.PIAOldServer{},
|
||||
version: allServers.PiaOld.Version,
|
||||
digest: "4e25ce4a",
|
||||
"Privado": {
|
||||
model: models.PrivadoServer{},
|
||||
version: allServers.Privado.Version,
|
||||
digest: "1d5aeb23",
|
||||
},
|
||||
"Purevpn": {
|
||||
model: models.PurevpnServer{},
|
||||
@@ -77,7 +77,7 @@ func Test_versions(t *testing.T) {
|
||||
"Windscribe": {
|
||||
model: models.WindscribeServer{},
|
||||
version: allServers.Windscribe.Version,
|
||||
digest: "042bef64",
|
||||
digest: "6e3ca639",
|
||||
},
|
||||
}
|
||||
for name, testCase := range testCases {
|
||||
@@ -133,18 +133,18 @@ func Test_timestamps(t *testing.T) {
|
||||
"Private Internet Access": {
|
||||
servers: allServers.Pia.Servers,
|
||||
timestamp: allServers.Pia.Timestamp,
|
||||
digest: "1571e777",
|
||||
},
|
||||
"Private Internet Access Old": {
|
||||
servers: allServers.PiaOld.Servers,
|
||||
timestamp: allServers.PiaOld.Timestamp,
|
||||
digest: "3566a800",
|
||||
digest: "4a172b0a",
|
||||
},
|
||||
"Purevpn": {
|
||||
servers: allServers.Purevpn.Servers,
|
||||
timestamp: allServers.Purevpn.Timestamp,
|
||||
digest: "cdf9b708",
|
||||
},
|
||||
"Privado": {
|
||||
servers: allServers.Privado.Servers,
|
||||
timestamp: allServers.Privado.Timestamp,
|
||||
digest: "df2a046d",
|
||||
},
|
||||
"Surfshark": {
|
||||
servers: allServers.Surfshark.Servers,
|
||||
timestamp: allServers.Surfshark.Timestamp,
|
||||
@@ -158,7 +158,7 @@ func Test_timestamps(t *testing.T) {
|
||||
"Windscribe": {
|
||||
servers: allServers.Windscribe.Servers,
|
||||
timestamp: allServers.Windscribe.Timestamp,
|
||||
digest: "eacad593",
|
||||
digest: "fd87502e",
|
||||
},
|
||||
}
|
||||
for name, testCase := range testCases {
|
||||
|
||||
@@ -2,9 +2,9 @@ package constants
|
||||
|
||||
const (
|
||||
// Announcement is a message announcement.
|
||||
Announcement = "Port forwarding is working for PIA v4 servers"
|
||||
Announcement = "Support for Privado"
|
||||
// AnnouncementExpiration is the expiration date of the announcement in format yyyy-mm-dd.
|
||||
AnnouncementExpiration = "2020-11-15"
|
||||
AnnouncementExpiration = "2020-11-25"
|
||||
)
|
||||
|
||||
const (
|
||||
|
||||
14
internal/constants/status.go
Normal file
14
internal/constants/status.go
Normal 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"
|
||||
)
|
||||
@@ -1,20 +0,0 @@
|
||||
package constants
|
||||
|
||||
import (
|
||||
"github.com/qdm12/gluetun/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"
|
||||
)
|
||||
@@ -7,8 +7,6 @@ import (
|
||||
const (
|
||||
// PrivateInternetAccess is a VPN provider.
|
||||
PrivateInternetAccess models.VPNProvider = "private internet access"
|
||||
// PrivateInternetAccessOld is the pre summer 2020 PIA provider.
|
||||
PrivateInternetAccessOld models.VPNProvider = "private internet access old"
|
||||
// Mullvad is a VPN provider.
|
||||
Mullvad models.VPNProvider = "mullvad"
|
||||
// Windscribe is a VPN provider.
|
||||
@@ -23,6 +21,8 @@ const (
|
||||
Nordvpn models.VPNProvider = "nordvpn"
|
||||
// PureVPN is a VPN provider.
|
||||
Purevpn models.VPNProvider = "purevpn"
|
||||
// Privado is a VPN provider.
|
||||
Privado models.VPNProvider = "privado"
|
||||
)
|
||||
|
||||
const (
|
||||
|
||||
@@ -21,79 +21,319 @@ func WindscribeRegionChoices() (choices []string) {
|
||||
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", IPs: []net.IP{{31, 171, 152, 179}}},
|
||||
{Region: "Argentina", IPs: []net.IP{{167, 250, 6, 121}, {190, 105, 236, 19}, {190, 105, 236, 32}, {190, 105, 236, 50}}},
|
||||
{Region: "Australia", IPs: []net.IP{{45, 121, 208, 160}, {45, 121, 209, 160}, {45, 121, 210, 208}, {103, 62, 50, 208}, {103, 77, 233, 67}, {103, 77, 234, 211}, {116, 90, 72, 243}, {116, 206, 228, 67}}},
|
||||
{Region: "Austria", IPs: []net.IP{{89, 187, 168, 66}, {217, 64, 127, 11}}},
|
||||
{Region: "Azerbaijan", IPs: []net.IP{{85, 132, 61, 123}}},
|
||||
{Region: "Belgium", IPs: []net.IP{{185, 232, 21, 131}, {194, 187, 251, 147}}},
|
||||
{Region: "Bosnia", IPs: []net.IP{{185, 99, 3, 24}}},
|
||||
{Region: "Brazil", IPs: []net.IP{{177, 54, 144, 68}, {177, 67, 80, 59}, {189, 1, 172, 12}}},
|
||||
{Region: "Bulgaria", IPs: []net.IP{{185, 94, 192, 35}}},
|
||||
{Region: "Canada East", IPs: []net.IP{{23, 154, 160, 177}, {66, 70, 148, 80}, {104, 227, 235, 129}, {104, 254, 92, 11}, {104, 254, 92, 91}, {144, 168, 163, 160}, {144, 168, 163, 193}, {184, 75, 212, 91}, {192, 190, 19, 65}, {192, 190, 19, 97}, {198, 8, 85, 195}, {198, 8, 85, 210}, {199, 204, 208, 158}}},
|
||||
{Region: "Canada West", IPs: []net.IP{{104, 218, 61, 1}, {104, 218, 61, 33}, {162, 221, 207, 95}, {208, 78, 41, 1}, {208, 78, 41, 131}, {208, 78, 41, 163}}},
|
||||
{Region: "Colombia", IPs: []net.IP{{138, 121, 203, 203}, {138, 186, 141, 155}}},
|
||||
{Region: "Croatia", IPs: []net.IP{{85, 10, 56, 252}}},
|
||||
{Region: "Cyprus", IPs: []net.IP{{157, 97, 132, 43}}},
|
||||
{Region: "Czech republic", IPs: []net.IP{{185, 156, 174, 11}, {185, 246, 210, 2}}},
|
||||
{Region: "Denmark", IPs: []net.IP{{134, 90, 149, 147}, {185, 206, 224, 195}}},
|
||||
{Region: "Estonia", IPs: []net.IP{{46, 22, 211, 251}, {196, 196, 216, 131}}},
|
||||
{Region: "Fake antarctica", IPs: []net.IP{{23, 154, 160, 212}, {23, 154, 160, 222}}},
|
||||
{Region: "Finland", IPs: []net.IP{{185, 112, 82, 227}, {194, 34, 133, 82}}},
|
||||
{Region: "France", IPs: []net.IP{{45, 89, 174, 35}, {82, 102, 18, 35}, {84, 17, 42, 2}, {84, 17, 42, 34}, {185, 156, 173, 187}}},
|
||||
{Region: "Germany", IPs: []net.IP{{45, 87, 212, 51}, {89, 249, 65, 19}, {185, 130, 184, 195}, {195, 181, 170, 66}, {195, 181, 175, 98}, {217, 138, 194, 115}}},
|
||||
{Region: "Greece", IPs: []net.IP{{78, 108, 38, 155}, {185, 226, 64, 111}, {188, 123, 126, 146}}},
|
||||
{Region: "Guinea-Bissau", IPs: []net.IP{{149, 56, 10, 82}}},
|
||||
{Region: "Hong kong", IPs: []net.IP{{84, 17, 57, 114}, {103, 10, 197, 99}}},
|
||||
{Region: "Hungary", IPs: []net.IP{{185, 104, 187, 43}}},
|
||||
{Region: "Iceland", IPs: []net.IP{{82, 221, 139, 38}, {185, 165, 170, 2}}},
|
||||
{Region: "India", IPs: []net.IP{{103, 205, 140, 227}, {169, 38, 68, 188}, {169, 38, 72, 12}, {169, 38, 72, 14}}},
|
||||
{Region: "Indonesia", IPs: []net.IP{{45, 127, 134, 91}}},
|
||||
{Region: "Ireland", IPs: []net.IP{{185, 24, 232, 146}, {185, 104, 219, 2}}},
|
||||
{Region: "Israel", IPs: []net.IP{{160, 116, 0, 27}, {185, 191, 205, 139}}},
|
||||
{Region: "Italy", IPs: []net.IP{{37, 120, 135, 83}, {37, 120, 207, 19}, {84, 17, 59, 66}, {87, 101, 94, 195}, {89, 40, 182, 3}}},
|
||||
{Region: "Japan", IPs: []net.IP{{89, 187, 161, 114}, {193, 148, 16, 243}}},
|
||||
{Region: "Latvia", IPs: []net.IP{{85, 254, 72, 23}, {89, 111, 33, 220}}},
|
||||
{Region: "Lithuania", IPs: []net.IP{{85, 206, 163, 225}}},
|
||||
{Region: "Macedonia", IPs: []net.IP{{185, 225, 28, 51}}},
|
||||
{Region: "Madagascar", IPs: []net.IP{{104, 20, 26, 217}, {104, 20, 27, 217}, {172, 67, 17, 175}}},
|
||||
{Region: "Malaysia", IPs: []net.IP{{103, 106, 250, 31}, {103, 212, 69, 232}}},
|
||||
{Region: "Mexico", IPs: []net.IP{{143, 255, 57, 67}, {190, 103, 179, 211}, {190, 103, 179, 217}, {201, 131, 125, 107}}},
|
||||
{Region: "Moldova", IPs: []net.IP{{178, 175, 144, 123}}},
|
||||
{Region: "Netherlands", IPs: []net.IP{{37, 120, 192, 19}, {46, 166, 143, 98}, {72, 11, 157, 35}, {72, 11, 157, 67}, {84, 17, 46, 2}, {185, 212, 171, 131}, {185, 253, 96, 3}}},
|
||||
{Region: "New zealand", IPs: []net.IP{{103, 62, 49, 113}}},
|
||||
{Region: "Norway", IPs: []net.IP{{37, 120, 203, 67}, {185, 206, 225, 131}}},
|
||||
{Region: "Panama", IPs: []net.IP{{138, 186, 142, 203}}},
|
||||
{Region: "Peru", IPs: []net.IP{{190, 120, 229, 139}}},
|
||||
{Region: "Philippines", IPs: []net.IP{{103, 103, 0, 118}, {141, 98, 215, 211}}},
|
||||
{Region: "Poland", IPs: []net.IP{{5, 133, 11, 116}, {84, 17, 55, 98}, {185, 244, 214, 35}}},
|
||||
{Region: "Portugal", IPs: []net.IP{{94, 46, 13, 215}, {185, 15, 21, 66}}},
|
||||
{Region: "Romania", IPs: []net.IP{{89, 46, 103, 147}, {91, 207, 102, 147}}},
|
||||
{Region: "Russia", IPs: []net.IP{{94, 242, 62, 19}, {94, 242, 62, 67}, {95, 213, 193, 195}, {95, 213, 193, 227}, {185, 22, 175, 132}, {188, 124, 42, 99}, {188, 124, 42, 115}}},
|
||||
{Region: "Serbia", IPs: []net.IP{{141, 98, 103, 19}}},
|
||||
{Region: "Singapore", IPs: []net.IP{{82, 102, 25, 131}, {103, 62, 48, 224}, {156, 146, 56, 98}, {156, 146, 56, 111}, {185, 200, 117, 163}}},
|
||||
{Region: "Slovakia", IPs: []net.IP{{185, 245, 85, 3}}},
|
||||
{Region: "South Africa", IPs: []net.IP{{129, 232, 167, 211}, {165, 73, 248, 91}, {197, 242, 157, 235}}},
|
||||
{Region: "South Korea", IPs: []net.IP{{27, 255, 92, 52}, {103, 212, 223, 3}, {218, 232, 76, 179}}},
|
||||
{Region: "Spain", IPs: []net.IP{{37, 120, 142, 227}, {89, 238, 178, 43}, {185, 253, 99, 131}, {217, 138, 218, 99}}},
|
||||
{Region: "Sweden", IPs: []net.IP{{31, 13, 191, 67}, {79, 142, 76, 198}, {195, 181, 166, 129}}},
|
||||
{Region: "Switzerland", IPs: []net.IP{{31, 7, 57, 242}, {37, 120, 213, 163}, {84, 17, 53, 2}, {89, 187, 165, 98}, {185, 156, 175, 179}}},
|
||||
{Region: "Taiwan", IPs: []net.IP{{103, 4, 29, 77}, {185, 189, 160, 12}, {185, 189, 160, 27}, {185, 189, 160, 32}}},
|
||||
{Region: "Thailand", IPs: []net.IP{{27, 254, 130, 221}, {202, 129, 16, 147}, {202, 129, 16, 155}}},
|
||||
{Region: "Tunisia", IPs: []net.IP{{41, 231, 5, 23}}},
|
||||
{Region: "Turkey", IPs: []net.IP{{45, 123, 118, 156}, {45, 123, 119, 11}, {79, 98, 131, 43}, {176, 53, 113, 163}, {185, 125, 33, 227}}},
|
||||
{Region: "US Central", IPs: []net.IP{{67, 212, 238, 196}, {69, 12, 94, 67}, {104, 129, 18, 3}, {104, 129, 18, 131}, {104, 223, 92, 163}, {107, 150, 31, 3}, {107, 150, 31, 67}, {107, 150, 31, 131}, {107, 161, 86, 131}, {107, 182, 234, 240}, {161, 129, 70, 195}, {162, 222, 198, 67}, {172, 241, 26, 78}, {172, 241, 131, 129}, {198, 12, 76, 211}, {198, 54, 128, 116}, {198, 55, 125, 195}, {199, 115, 96, 83}, {204, 44, 112, 67}, {204, 44, 112, 131}, {206, 217, 139, 19}, {206, 217, 139, 195}, {206, 217, 143, 131}}},
|
||||
{Region: "US West", IPs: []net.IP{{23, 83, 130, 166}, {23, 83, 131, 187}, {23, 94, 74, 99}, {37, 120, 147, 163}, {64, 120, 2, 174}, {66, 115, 176, 3}, {82, 102, 30, 67}, {89, 187, 185, 34}, {89, 187, 187, 98}, {104, 129, 3, 67}, {104, 129, 3, 163}, {104, 129, 56, 67}, {104, 129, 56, 131}, {104, 152, 222, 33}, {167, 88, 60, 227}, {167, 88, 60, 243}, {172, 241, 214, 202}, {172, 241, 250, 131}, {172, 255, 125, 141}, {185, 236, 200, 35}, {192, 3, 20, 51}, {198, 12, 116, 195}, {198, 23, 242, 147}, {209, 58, 129, 121}, {212, 103, 49, 67}, {216, 45, 53, 131}, {217, 138, 217, 51}, {217, 138, 217, 211}}},
|
||||
{Region: "Ukraine", IPs: []net.IP{{45, 141, 156, 11}, {45, 141, 156, 50}}},
|
||||
{Region: "United Arab Emirates", IPs: []net.IP{{45, 9, 249, 43}}},
|
||||
{Region: "United Kingdom", IPs: []net.IP{{2, 58, 29, 17}, {2, 58, 29, 145}, {81, 92, 207, 69}, {84, 17, 50, 130}, {89, 44, 201, 99}, {89, 238, 135, 133}, {89, 238, 150, 229}, {185, 212, 168, 133}, {212, 102, 63, 32}, {212, 102, 63, 62}, {217, 138, 254, 51}}},
|
||||
{Region: "Vietnam", IPs: []net.IP{{103, 9, 76, 197}, {103, 9, 79, 186}, {103, 9, 79, 219}}},
|
||||
{Region: "Windflix CA", IPs: []net.IP{{104, 218, 60, 111}, {104, 254, 92, 99}}},
|
||||
{Region: "Windflix JP", IPs: []net.IP{{5, 181, 235, 67}}},
|
||||
{Region: "Windflix UK", IPs: []net.IP{{45, 9, 248, 3}, {81, 92, 200, 85}, {89, 47, 62, 83}}},
|
||||
{Region: "Windflix US", IPs: []net.IP{{38, 132, 101, 211}, {38, 132, 122, 131}, {38, 132, 122, 195}, {77, 81, 136, 99}, {185, 232, 22, 131}, {217, 138, 206, 211}}},
|
||||
{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}},
|
||||
}
|
||||
}
|
||||
|
||||
@@ -14,9 +14,10 @@ import (
|
||||
"github.com/qdm12/golibs/network"
|
||||
)
|
||||
|
||||
func (c *configurator) MakeUnboundConf(ctx context.Context, settings settings.DNS, uid, gid int) (err error) {
|
||||
func (c *configurator) MakeUnboundConf(ctx context.Context, settings settings.DNS,
|
||||
username string, uid, gid int) (err error) {
|
||||
c.logger.Info("generating Unbound configuration")
|
||||
lines, warnings := generateUnboundConf(ctx, settings, c.client, c.logger)
|
||||
lines, warnings := generateUnboundConf(ctx, settings, username, c.client, c.logger)
|
||||
for _, warning := range warnings {
|
||||
c.logger.Warn(warning)
|
||||
}
|
||||
@@ -28,7 +29,7 @@ func (c *configurator) MakeUnboundConf(ctx context.Context, settings settings.DN
|
||||
}
|
||||
|
||||
// MakeUnboundConf generates an Unbound configuration from the user provided settings.
|
||||
func generateUnboundConf(ctx context.Context, settings settings.DNS,
|
||||
func generateUnboundConf(ctx context.Context, settings settings.DNS, username string,
|
||||
client network.Client, logger logging.Logger) (
|
||||
lines []string, warnings []error) {
|
||||
doIPv6 := "no"
|
||||
@@ -66,10 +67,10 @@ func generateUnboundConf(ctx context.Context, settings settings.DNS,
|
||||
// Network
|
||||
"do-ip4": "yes",
|
||||
"do-ip6": doIPv6,
|
||||
"interface": "127.0.0.1",
|
||||
"interface": "0.0.0.0",
|
||||
"port": "53",
|
||||
// Other
|
||||
"username": "\"nonrootuser\"",
|
||||
"username": fmt.Sprintf("%q", username),
|
||||
}
|
||||
|
||||
// Block lists
|
||||
|
||||
@@ -41,7 +41,7 @@ func Test_generateUnboundConf(t *testing.T) {
|
||||
logger := mock_logging.NewMockLogger(mockCtrl)
|
||||
logger.EXPECT().Info("%d hostnames blocked overall", 2).Times(1)
|
||||
logger.EXPECT().Info("%d IP addresses blocked overall", 3).Times(1)
|
||||
lines, warnings := generateUnboundConf(ctx, settings, client, logger)
|
||||
lines, warnings := generateUnboundConf(ctx, settings, "nonrootuser", client, logger)
|
||||
require.Len(t, warnings, 0)
|
||||
expected := `
|
||||
server:
|
||||
@@ -54,7 +54,7 @@ server:
|
||||
harden-referral-path: yes
|
||||
hide-identity: yes
|
||||
hide-version: yes
|
||||
interface: 127.0.0.1
|
||||
interface: 0.0.0.0
|
||||
key-cache-size: 16m
|
||||
key-cache-slabs: 4
|
||||
msg-cache-size: 4m
|
||||
|
||||
@@ -15,7 +15,7 @@ import (
|
||||
type Configurator interface {
|
||||
DownloadRootHints(ctx context.Context, uid, gid int) error
|
||||
DownloadRootKey(ctx context.Context, uid, gid int) error
|
||||
MakeUnboundConf(ctx context.Context, settings settings.DNS, uid, gid int) (err error)
|
||||
MakeUnboundConf(ctx context.Context, settings settings.DNS, username string, uid, gid int) (err error)
|
||||
UseDNSInternally(IP net.IP)
|
||||
UseDNSSystemWide(ip net.IP, keepNameserver bool) error
|
||||
Start(ctx context.Context, logLevel uint8) (stdout io.ReadCloser, waitFn func() error, err error)
|
||||
|
||||
@@ -7,6 +7,7 @@ import (
|
||||
"time"
|
||||
|
||||
"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"
|
||||
@@ -15,80 +16,53 @@ import (
|
||||
type Looper interface {
|
||||
Run(ctx context.Context, wg *sync.WaitGroup, signalDNSReady func())
|
||||
RunRestartTicker(ctx context.Context, wg *sync.WaitGroup)
|
||||
Restart()
|
||||
Start()
|
||||
Stop()
|
||||
GetStatus() (status models.LoopStatus)
|
||||
SetStatus(status models.LoopStatus) (outcome string, err error)
|
||||
GetSettings() (settings settings.DNS)
|
||||
SetSettings(settings settings.DNS)
|
||||
SetSettings(settings settings.DNS) (outcome string)
|
||||
}
|
||||
|
||||
type looper struct {
|
||||
conf Configurator
|
||||
settings settings.DNS
|
||||
settingsMutex sync.RWMutex
|
||||
logger logging.Logger
|
||||
streamMerger command.StreamMerger
|
||||
uid int
|
||||
gid int
|
||||
restart chan struct{}
|
||||
start chan struct{}
|
||||
stop chan struct{}
|
||||
updateTicker chan struct{}
|
||||
timeNow func() time.Time
|
||||
timeSince func(time.Time) time.Duration
|
||||
state state
|
||||
conf Configurator
|
||||
logger logging.Logger
|
||||
streamMerger command.StreamMerger
|
||||
username string
|
||||
uid int
|
||||
gid int
|
||||
loopLock sync.Mutex
|
||||
start chan struct{}
|
||||
running chan models.LoopStatus
|
||||
stop chan struct{}
|
||||
stopped chan struct{}
|
||||
updateTicker chan struct{}
|
||||
timeNow func() time.Time
|
||||
timeSince func(time.Time) time.Duration
|
||||
}
|
||||
|
||||
func NewLooper(conf Configurator, settings settings.DNS, logger logging.Logger,
|
||||
streamMerger command.StreamMerger, uid, gid int) Looper {
|
||||
streamMerger command.StreamMerger, username string, uid, gid int) Looper {
|
||||
return &looper{
|
||||
state: state{
|
||||
status: constants.Stopped,
|
||||
settings: settings,
|
||||
},
|
||||
conf: conf,
|
||||
settings: settings,
|
||||
logger: logger.WithPrefix("dns over tls: "),
|
||||
username: username,
|
||||
uid: uid,
|
||||
gid: gid,
|
||||
streamMerger: streamMerger,
|
||||
restart: make(chan struct{}),
|
||||
start: make(chan struct{}),
|
||||
running: make(chan models.LoopStatus),
|
||||
stop: make(chan struct{}),
|
||||
stopped: make(chan struct{}),
|
||||
updateTicker: make(chan struct{}),
|
||||
timeNow: time.Now,
|
||||
timeSince: time.Since,
|
||||
}
|
||||
}
|
||||
|
||||
func (l *looper) Restart() { l.restart <- struct{}{} }
|
||||
func (l *looper) Start() { l.start <- struct{}{} }
|
||||
func (l *looper) Stop() { l.stop <- struct{}{} }
|
||||
|
||||
func (l *looper) GetSettings() (settings settings.DNS) {
|
||||
l.settingsMutex.RLock()
|
||||
defer l.settingsMutex.RUnlock()
|
||||
return l.settings
|
||||
}
|
||||
|
||||
func (l *looper) SetSettings(settings settings.DNS) {
|
||||
l.settingsMutex.Lock()
|
||||
defer l.settingsMutex.Unlock()
|
||||
updatePeriodDiffers := l.settings.UpdatePeriod != settings.UpdatePeriod
|
||||
l.settings = settings
|
||||
l.settingsMutex.Unlock()
|
||||
if updatePeriodDiffers {
|
||||
l.updateTicker <- struct{}{}
|
||||
}
|
||||
}
|
||||
|
||||
func (l *looper) isEnabled() bool {
|
||||
l.settingsMutex.RLock()
|
||||
defer l.settingsMutex.RUnlock()
|
||||
return l.settings.Enabled
|
||||
}
|
||||
|
||||
func (l *looper) setEnabled(enabled bool) {
|
||||
l.settingsMutex.Lock()
|
||||
defer l.settingsMutex.Unlock()
|
||||
l.settings.Enabled = enabled
|
||||
}
|
||||
|
||||
func (l *looper) logAndWait(ctx context.Context, err error) {
|
||||
l.logger.Warn(err)
|
||||
l.logger.Info("attempting restart in 10 seconds")
|
||||
@@ -103,96 +77,42 @@ func (l *looper) logAndWait(ctx context.Context, err error) {
|
||||
}
|
||||
}
|
||||
|
||||
func (l *looper) waitForFirstStart(ctx context.Context, signalDNSReady func()) {
|
||||
for {
|
||||
select {
|
||||
case <-l.stop:
|
||||
l.setEnabled(false)
|
||||
l.logger.Info("not started yet")
|
||||
case <-l.restart:
|
||||
if l.isEnabled() {
|
||||
return
|
||||
}
|
||||
signalDNSReady()
|
||||
l.logger.Info("not restarting because disabled")
|
||||
case <-l.start:
|
||||
l.setEnabled(true)
|
||||
return
|
||||
case <-ctx.Done():
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (l *looper) waitForSubsequentStart(ctx context.Context, unboundCancel context.CancelFunc) {
|
||||
if l.isEnabled() {
|
||||
return
|
||||
}
|
||||
for {
|
||||
// wait for a signal to re-enable
|
||||
select {
|
||||
case <-l.stop:
|
||||
l.logger.Info("already disabled")
|
||||
case <-l.restart:
|
||||
if !l.isEnabled() {
|
||||
l.logger.Info("not restarting because disabled")
|
||||
} else {
|
||||
return
|
||||
}
|
||||
case <-l.start:
|
||||
l.setEnabled(true)
|
||||
return
|
||||
case <-ctx.Done():
|
||||
unboundCancel()
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (l *looper) Run(ctx context.Context, wg *sync.WaitGroup, signalDNSReady func()) {
|
||||
defer wg.Done()
|
||||
|
||||
const fallback = false
|
||||
l.useUnencryptedDNS(fallback)
|
||||
l.waitForFirstStart(ctx, signalDNSReady)
|
||||
if ctx.Err() != nil {
|
||||
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")
|
||||
|
||||
var unboundCtx context.Context
|
||||
var unboundCancel context.CancelFunc = func() {}
|
||||
var waitError chan error
|
||||
triggeredRestart := false
|
||||
l.setEnabled(true)
|
||||
for ctx.Err() == nil {
|
||||
l.waitForSubsequentStart(ctx, unboundCancel)
|
||||
err := l.updateFiles(ctx)
|
||||
if err == nil {
|
||||
break
|
||||
}
|
||||
l.state.setStatusWithLock(constants.Crashed)
|
||||
l.logAndWait(ctx, err)
|
||||
}
|
||||
|
||||
crashed := false
|
||||
|
||||
for ctx.Err() == nil {
|
||||
settings := l.GetSettings()
|
||||
|
||||
// Setup
|
||||
if err := l.conf.DownloadRootHints(ctx, l.uid, l.gid); err != nil {
|
||||
l.logAndWait(ctx, err)
|
||||
continue
|
||||
}
|
||||
if err := l.conf.DownloadRootKey(ctx, l.uid, l.gid); err != nil {
|
||||
l.logAndWait(ctx, err)
|
||||
continue
|
||||
}
|
||||
if err := l.conf.MakeUnboundConf(ctx, settings, l.uid, l.gid); err != nil {
|
||||
l.logAndWait(ctx, err)
|
||||
continue
|
||||
}
|
||||
|
||||
if triggeredRestart {
|
||||
triggeredRestart = false
|
||||
unboundCancel()
|
||||
<-waitError
|
||||
close(waitError)
|
||||
}
|
||||
unboundCtx, unboundCancel = context.WithCancel(context.Background())
|
||||
unboundCtx, unboundCancel := context.WithCancel(context.Background())
|
||||
stream, waitFn, err := l.conf.Start(unboundCtx, settings.VerbosityDetailsLevel)
|
||||
if err != nil {
|
||||
unboundCancel()
|
||||
if !crashed {
|
||||
l.running <- constants.Crashed
|
||||
}
|
||||
crashed = true
|
||||
const fallback = true
|
||||
l.useUnencryptedDNS(fallback)
|
||||
l.logAndWait(ctx, err)
|
||||
@@ -201,23 +121,37 @@ func (l *looper) Run(ctx context.Context, wg *sync.WaitGroup, signalDNSReady fun
|
||||
|
||||
// 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(); err != nil {
|
||||
if !crashed {
|
||||
l.running <- constants.Crashed
|
||||
crashed = true
|
||||
}
|
||||
unboundCancel()
|
||||
const fallback = true
|
||||
l.useUnencryptedDNS(fallback)
|
||||
l.logAndWait(ctx, err)
|
||||
continue
|
||||
}
|
||||
waitError = make(chan error)
|
||||
|
||||
waitError := make(chan error)
|
||||
go func() {
|
||||
err := waitFn() // blocking
|
||||
waitError <- err
|
||||
}()
|
||||
|
||||
l.logger.Info("DNS over TLS is ready")
|
||||
if !crashed {
|
||||
l.running <- constants.Running
|
||||
crashed = false
|
||||
} else {
|
||||
l.state.setStatusWithLock(constants.Running)
|
||||
}
|
||||
signalDNSReady()
|
||||
|
||||
stayHere := true
|
||||
@@ -229,31 +163,28 @@ func (l *looper) Run(ctx context.Context, wg *sync.WaitGroup, signalDNSReady fun
|
||||
<-waitError
|
||||
close(waitError)
|
||||
return
|
||||
case <-l.restart: // triggered restart
|
||||
l.logger.Info("restarting")
|
||||
// unboundCancel occurs next loop run when the setup is complete
|
||||
triggeredRestart = true
|
||||
stayHere = false
|
||||
case <-l.start:
|
||||
l.logger.Info("already started")
|
||||
case <-l.stop:
|
||||
l.logger.Info("stopping")
|
||||
const fallback = false
|
||||
l.useUnencryptedDNS(fallback)
|
||||
unboundCancel()
|
||||
<-waitError
|
||||
close(waitError)
|
||||
l.setEnabled(false)
|
||||
l.stopped <- struct{}{}
|
||||
case <-l.start:
|
||||
l.logger.Info("starting")
|
||||
stayHere = false
|
||||
case err := <-waitError: // unexpected error
|
||||
close(waitError)
|
||||
unboundCancel()
|
||||
l.state.setStatusWithLock(constants.Crashed)
|
||||
const fallback = true
|
||||
l.useUnencryptedDNS(fallback)
|
||||
l.logAndWait(ctx, err)
|
||||
stayHere = false
|
||||
}
|
||||
}
|
||||
close(waitError)
|
||||
unboundCancel()
|
||||
}
|
||||
unboundCancel()
|
||||
}
|
||||
|
||||
func (l *looper) useUnencryptedDNS(fallback bool) {
|
||||
@@ -279,7 +210,11 @@ func (l *looper) useUnencryptedDNS(fallback bool) {
|
||||
data := constants.DNSProviderMapping()[provider]
|
||||
for _, targetIP = range data.IPs {
|
||||
if targetIP.To4() != nil {
|
||||
l.logger.Info("falling back on plaintext DNS at address %s", targetIP)
|
||||
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)
|
||||
@@ -314,7 +249,20 @@ func (l *looper) RunRestartTicker(ctx context.Context, wg *sync.WaitGroup) {
|
||||
return
|
||||
case <-timer.C:
|
||||
lastTick = l.timeNow()
|
||||
l.restart <- struct{}{}
|
||||
|
||||
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:
|
||||
@@ -337,3 +285,17 @@ func (l *looper) RunRestartTicker(ctx context.Context, wg *sync.WaitGroup) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (l *looper) updateFiles(ctx context.Context) (err error) {
|
||||
if err := l.conf.DownloadRootHints(ctx, l.uid, l.gid); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := l.conf.DownloadRootKey(ctx, l.uid, l.gid); err != nil {
|
||||
return err
|
||||
}
|
||||
settings := l.GetSettings()
|
||||
if err := l.conf.MakeUnboundConf(ctx, settings, l.username, l.uid, l.gid); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
96
internal/dns/state.go
Normal file
96
internal/dns/state.go
Normal file
@@ -0,0 +1,96 @@
|
||||
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
|
||||
if onlyUpdatePeriodChanged {
|
||||
l.updateTicker <- struct{}{}
|
||||
return "update period changed"
|
||||
}
|
||||
_, _ = l.SetStatus(constants.Stopped)
|
||||
outcome, _ = l.SetStatus(constants.Running)
|
||||
return outcome
|
||||
}
|
||||
@@ -94,6 +94,10 @@ func (c *configurator) enable(ctx context.Context) (err error) {
|
||||
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)
|
||||
|
||||
24
internal/httpproxy/accept.go
Normal file
24
internal/httpproxy/accept.go
Normal 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
|
||||
}
|
||||
41
internal/httpproxy/auth.go
Normal file
41
internal/httpproxy/auth.go
Normal 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
|
||||
}
|
||||
64
internal/httpproxy/handler.go
Normal file
64
internal/httpproxy/handler.go
Normal 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",
|
||||
}
|
||||
71
internal/httpproxy/http.go
Normal file
71
internal/httpproxy/http.go
Normal 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)
|
||||
}
|
||||
64
internal/httpproxy/https.go
Normal file
64
internal/httpproxy/https.go
Normal 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()
|
||||
}
|
||||
135
internal/httpproxy/loop.go
Normal file
135
internal/httpproxy/loop.go
Normal file
@@ -0,0 +1,135 @@
|
||||
package httpproxy
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"sync"
|
||||
|
||||
"github.com/qdm12/gluetun/internal/settings"
|
||||
"github.com/qdm12/golibs/logging"
|
||||
)
|
||||
|
||||
type Looper interface {
|
||||
Run(ctx context.Context, wg *sync.WaitGroup)
|
||||
Restart()
|
||||
Start()
|
||||
Stop()
|
||||
GetSettings() (settings settings.HTTPProxy)
|
||||
SetSettings(settings settings.HTTPProxy)
|
||||
}
|
||||
|
||||
type looper struct {
|
||||
settings settings.HTTPProxy
|
||||
settingsMutex sync.RWMutex
|
||||
logger logging.Logger
|
||||
restart chan struct{}
|
||||
start chan struct{}
|
||||
stop chan struct{}
|
||||
}
|
||||
|
||||
func NewLooper(logger logging.Logger, settings settings.HTTPProxy) Looper {
|
||||
return &looper{
|
||||
settings: settings,
|
||||
logger: logger.WithPrefix("http proxy: "),
|
||||
restart: make(chan struct{}),
|
||||
start: make(chan struct{}),
|
||||
stop: make(chan struct{}),
|
||||
}
|
||||
}
|
||||
|
||||
func (l *looper) GetSettings() (settings settings.HTTPProxy) {
|
||||
l.settingsMutex.RLock()
|
||||
defer l.settingsMutex.RUnlock()
|
||||
return l.settings
|
||||
}
|
||||
|
||||
func (l *looper) SetSettings(settings settings.HTTPProxy) {
|
||||
l.settingsMutex.Lock()
|
||||
defer l.settingsMutex.Unlock()
|
||||
l.settings = settings
|
||||
}
|
||||
|
||||
func (l *looper) isEnabled() bool {
|
||||
l.settingsMutex.RLock()
|
||||
defer l.settingsMutex.RUnlock()
|
||||
return l.settings.Enabled
|
||||
}
|
||||
|
||||
func (l *looper) setEnabled(enabled bool) {
|
||||
l.settingsMutex.Lock()
|
||||
defer l.settingsMutex.Unlock()
|
||||
l.settings.Enabled = enabled
|
||||
}
|
||||
|
||||
func (l *looper) Restart() { l.restart <- struct{}{} }
|
||||
func (l *looper) Start() { l.start <- struct{}{} }
|
||||
func (l *looper) Stop() { l.stop <- struct{}{} }
|
||||
|
||||
func (l *looper) Run(ctx context.Context, wg *sync.WaitGroup) {
|
||||
defer wg.Done()
|
||||
waitForStart := true
|
||||
for waitForStart {
|
||||
select {
|
||||
case <-l.stop:
|
||||
l.logger.Info("not started yet")
|
||||
case <-l.start:
|
||||
waitForStart = false
|
||||
case <-l.restart:
|
||||
waitForStart = false
|
||||
case <-ctx.Done():
|
||||
return
|
||||
}
|
||||
}
|
||||
defer l.logger.Warn("loop exited")
|
||||
|
||||
for ctx.Err() == nil {
|
||||
for !l.isEnabled() {
|
||||
// wait for a signal to re-enable
|
||||
select {
|
||||
case <-l.stop:
|
||||
l.logger.Info("already disabled")
|
||||
case <-l.restart:
|
||||
l.setEnabled(true)
|
||||
case <-l.start:
|
||||
l.setEnabled(true)
|
||||
case <-ctx.Done():
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
settings := l.GetSettings()
|
||||
address := fmt.Sprintf("0.0.0.0:%d", settings.Port)
|
||||
|
||||
server := New(ctx, address, l.logger, settings.Stealth, settings.Log, settings.User, settings.Password)
|
||||
|
||||
runCtx, runCancel := context.WithCancel(context.Background())
|
||||
runWg := &sync.WaitGroup{}
|
||||
runWg.Add(1)
|
||||
go server.Run(runCtx, runWg)
|
||||
|
||||
stayHere := true
|
||||
for stayHere {
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
l.logger.Warn("context canceled: exiting loop")
|
||||
runCancel()
|
||||
runWg.Wait()
|
||||
return
|
||||
case <-l.restart: // triggered restart
|
||||
l.logger.Info("restarting")
|
||||
runCancel()
|
||||
runWg.Wait()
|
||||
stayHere = false
|
||||
case <-l.start:
|
||||
l.logger.Info("already started")
|
||||
case <-l.stop:
|
||||
l.logger.Info("stopping")
|
||||
runCancel()
|
||||
runWg.Wait()
|
||||
l.setEnabled(false)
|
||||
stayHere = false
|
||||
}
|
||||
}
|
||||
runCancel() // repetition for linter only
|
||||
}
|
||||
}
|
||||
54
internal/httpproxy/server.go
Normal file
54
internal/httpproxy/server.go
Normal 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)
|
||||
}
|
||||
|
||||
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) {
|
||||
defer wg.Done()
|
||||
server := http.Server{Addr: s.address, Handler: s.handler}
|
||||
go func() {
|
||||
<-ctx.Done()
|
||||
s.logger.Warn("context canceled: exiting loop")
|
||||
defer s.logger.Warn("loop exited")
|
||||
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() != context.Canceled {
|
||||
s.logger.Error(err)
|
||||
}
|
||||
s.internalWG.Wait()
|
||||
}
|
||||
@@ -12,13 +12,9 @@ import (
|
||||
|
||||
//nolint:lll
|
||||
var regularExpressions = struct { //nolint:gochecknoglobals
|
||||
unboundPrefix *regexp.Regexp
|
||||
tinyproxyLoglevel *regexp.Regexp
|
||||
tinyproxyPrefix *regexp.Regexp
|
||||
unboundPrefix *regexp.Regexp
|
||||
}{
|
||||
unboundPrefix: regexp.MustCompile(`unbound: \[[0-9]{10}\] unbound\[[0-9]+:0\] `),
|
||||
tinyproxyLoglevel: regexp.MustCompile(`INFO|CONNECT|NOTICE|WARNING|ERROR|CRITICAL`),
|
||||
tinyproxyPrefix: regexp.MustCompile(`tinyproxy: .+[ ]+(Jan|Feb|Mar|Apr|May|Jun|Jul|Aug|Sep|Oct|Nov|Dec) [0-3][0-9] [0-2][0-9]:[0-5][0-9]:[0-5][0-9] \[[0-9]+\]: `),
|
||||
unboundPrefix: regexp.MustCompile(`unbound: \[[0-9]{10}\] unbound\[[0-9]+:0\] `),
|
||||
}
|
||||
|
||||
func PostProcessLine(s string) (filtered string, level logging.Level) {
|
||||
@@ -48,7 +44,7 @@ func PostProcessLine(s string) (filtered string, level logging.Level) {
|
||||
case s == "openvpn: Initialization Sequence Completed":
|
||||
return color.HiGreenString(s), logging.InfoLevel
|
||||
case s == "openvpn: AUTH: Received control message: AUTH_FAILED":
|
||||
filtered = s + "\n\n (IF YOU ARE USING PIA V4 servers, MAYBE CHECK OUT https://github.com/qdm12/gluetun/issues/265)\n" //nolint:lll
|
||||
filtered = s + "\n\n (IF YOU ARE USING PIA servers, MAYBE CHECK OUT https://github.com/qdm12/gluetun/issues/265)\n" //nolint:lll
|
||||
level = logging.ErrorLevel
|
||||
default:
|
||||
filtered = s
|
||||
@@ -78,21 +74,6 @@ func PostProcessLine(s string) (filtered string, level logging.Level) {
|
||||
filtered = fmt.Sprintf("unbound: %s", filtered)
|
||||
filtered = constants.ColorUnbound().Sprintf(filtered)
|
||||
return filtered, level
|
||||
case strings.HasPrefix(s, "tinyproxy: "):
|
||||
logLevel := regularExpressions.tinyproxyLoglevel.FindString(s)
|
||||
prefix := regularExpressions.tinyproxyPrefix.FindString(s)
|
||||
filtered = fmt.Sprintf("tinyproxy: %s", s[len(prefix):])
|
||||
filtered = constants.ColorTinyproxy().Sprintf(filtered)
|
||||
switch logLevel {
|
||||
case "INFO", "CONNECT", "NOTICE":
|
||||
return filtered, logging.InfoLevel
|
||||
case "WARNING":
|
||||
return filtered, logging.WarnLevel
|
||||
case "ERROR", "CRITICAL":
|
||||
return filtered, logging.ErrorLevel
|
||||
default:
|
||||
return filtered, logging.ErrorLevel
|
||||
}
|
||||
}
|
||||
return s, logging.InfoLevel
|
||||
}
|
||||
|
||||
@@ -36,34 +36,6 @@ func Test_PostProcessLine(t *testing.T) {
|
||||
"unbound: [1594595249] unbound[75:0] BLA: init module 0: validator",
|
||||
"unbound: BLA: init module 0: validator",
|
||||
logging.ErrorLevel},
|
||||
"tinyproxy info": {
|
||||
"tinyproxy: INFO Jul 12 23:07:25 [32]: Reloading config file",
|
||||
"tinyproxy: Reloading config file",
|
||||
logging.InfoLevel},
|
||||
"tinyproxy connect": {
|
||||
"tinyproxy: CONNECT Jul 12 23:07:25 [32]: Reloading config file",
|
||||
"tinyproxy: Reloading config file",
|
||||
logging.InfoLevel},
|
||||
"tinyproxy notice": {
|
||||
"tinyproxy: NOTICE Jul 12 23:07:25 [32]: Reloading config file",
|
||||
"tinyproxy: Reloading config file",
|
||||
logging.InfoLevel},
|
||||
"tinyproxy warning": {
|
||||
"tinyproxy: WARNING Jul 12 23:07:25 [32]: Reloading config file",
|
||||
"tinyproxy: Reloading config file",
|
||||
logging.WarnLevel},
|
||||
"tinyproxy error": {
|
||||
"tinyproxy: ERROR Jul 12 23:07:25 [32]: Reloading config file",
|
||||
"tinyproxy: Reloading config file",
|
||||
logging.ErrorLevel},
|
||||
"tinyproxy critical": {
|
||||
"tinyproxy: CRITICAL Jul 12 23:07:25 [32]: Reloading config file",
|
||||
"tinyproxy: Reloading config file",
|
||||
logging.ErrorLevel},
|
||||
"tinyproxy unknown": {
|
||||
"tinyproxy: BLABLA Jul 12 23:07:25 [32]: Reloading config file",
|
||||
"tinyproxy: Reloading config file",
|
||||
logging.ErrorLevel},
|
||||
"openvpn unknown": {
|
||||
"openvpn: message",
|
||||
"openvpn: message",
|
||||
@@ -90,7 +62,7 @@ func Test_PostProcessLine(t *testing.T) {
|
||||
logging.InfoLevel},
|
||||
"openvpn auth failed": {
|
||||
"openvpn: AUTH: Received control message: AUTH_FAILED",
|
||||
"openvpn: AUTH: Received control message: AUTH_FAILED\n\n (IF YOU ARE USING PIA V4 servers, MAYBE CHECK OUT https://github.com/qdm12/gluetun/issues/265)\n", //nolint:lll
|
||||
"openvpn: AUTH: Received control message: AUTH_FAILED\n\n (IF YOU ARE USING PIA servers, MAYBE CHECK OUT https://github.com/qdm12/gluetun/issues/265)\n", //nolint:lll
|
||||
logging.ErrorLevel},
|
||||
}
|
||||
for name, tc := range tests {
|
||||
|
||||
@@ -7,13 +7,15 @@ import (
|
||||
|
||||
"github.com/kyokomi/emoji"
|
||||
"github.com/qdm12/gluetun/internal/constants"
|
||||
"github.com/qdm12/gluetun/internal/models"
|
||||
)
|
||||
|
||||
// Splash returns the welcome spash message.
|
||||
func Splash(version, commit, buildDate string) string {
|
||||
func Splash(buildInfo models.BuildInformation) string {
|
||||
lines := title()
|
||||
lines = append(lines, "")
|
||||
lines = append(lines, fmt.Sprintf("Running version %s built on %s (commit %s)", version, buildDate, commit))
|
||||
lines = append(lines, fmt.Sprintf("Running version %s built on %s (commit %s)",
|
||||
buildInfo.Version, buildInfo.BuildDate, buildInfo.Commit))
|
||||
lines = append(lines, "")
|
||||
lines = append(lines, announcement()...)
|
||||
lines = append(lines, "")
|
||||
@@ -27,7 +29,7 @@ func title() []string {
|
||||
"================ Gluetun ================",
|
||||
"=========================================",
|
||||
"==== A mix of OpenVPN, DNS over TLS, ====",
|
||||
"======= Shadowsocks and Tinyproxy =======",
|
||||
"======= Shadowsocks and HTTP proxy ======",
|
||||
"========= all glued up with Go ==========",
|
||||
"=========================================",
|
||||
"=========== For tunneling to ============",
|
||||
|
||||
@@ -16,14 +16,18 @@ type (
|
||||
URL string
|
||||
// Filepath is a local filesytem file path.
|
||||
Filepath string
|
||||
// TinyProxyLogLevel is the log level for TinyProxy.
|
||||
TinyProxyLogLevel string
|
||||
// VPNProvider is the name of the VPN provider to be used.
|
||||
VPNProvider string
|
||||
// NetworkProtocol contains the network protocol to be used to communicate with the VPN servers.
|
||||
NetworkProtocol string
|
||||
// Loop status such as stopped or running.
|
||||
LoopStatus string
|
||||
)
|
||||
|
||||
func (ls LoopStatus) String() string {
|
||||
return string(ls)
|
||||
}
|
||||
|
||||
func marshalJSONString(s string) (data []byte, err error) {
|
||||
return []byte(fmt.Sprintf("%q", s)), nil
|
||||
}
|
||||
|
||||
7
internal/models/build.go
Normal file
7
internal/models/build.go
Normal file
@@ -0,0 +1,7 @@
|
||||
package models
|
||||
|
||||
type BuildInformation struct {
|
||||
Version string `json:"version"`
|
||||
Commit string `json:"commit"`
|
||||
BuildDate string `json:"build_date"`
|
||||
}
|
||||
@@ -1,13 +1,17 @@
|
||||
package models
|
||||
|
||||
import "net"
|
||||
import (
|
||||
"net"
|
||||
)
|
||||
|
||||
type OpenVPNConnection struct {
|
||||
IP net.IP
|
||||
Port uint16
|
||||
Protocol NetworkProtocol
|
||||
Hostname string // Privado for tls verification
|
||||
}
|
||||
|
||||
func (o *OpenVPNConnection) Equal(other OpenVPNConnection) bool {
|
||||
return o.IP.Equal(other.IP) && o.Port == other.Port && o.Protocol == other.Protocol
|
||||
return o.IP.Equal(other.IP) && o.Port == other.Port && o.Protocol == other.Protocol &&
|
||||
o.Hostname == other.Hostname
|
||||
}
|
||||
|
||||
@@ -9,15 +9,15 @@ import (
|
||||
// ProviderSettings contains settings specific to a VPN provider.
|
||||
type ProviderSettings struct {
|
||||
Name VPNProvider `json:"name"`
|
||||
ServerSelection ServerSelection `json:"serverSelection"`
|
||||
ExtraConfigOptions ExtraConfigOptions `json:"extraConfig"`
|
||||
PortForwarding PortForwarding `json:"portForwarding"`
|
||||
ServerSelection ServerSelection `json:"server_selection"`
|
||||
ExtraConfigOptions ExtraConfigOptions `json:"extra_config"`
|
||||
PortForwarding PortForwarding `json:"port_forwarding"`
|
||||
}
|
||||
|
||||
type ServerSelection struct {
|
||||
// Common
|
||||
Protocol NetworkProtocol `json:"networkProtocol"`
|
||||
TargetIP net.IP `json:"targetIP,omitempty"`
|
||||
Protocol NetworkProtocol `json:"network_protocol"`
|
||||
TargetIP net.IP `json:"target_ip,omitempty"`
|
||||
|
||||
// Cyberghost, PIA, Surfshark, Windscribe, Vyprvpn, NordVPN
|
||||
Regions []string `json:"regions"`
|
||||
@@ -25,28 +25,29 @@ type ServerSelection struct {
|
||||
// Cyberghost
|
||||
Group string `json:"group"`
|
||||
|
||||
// Mullvad, PureVPN
|
||||
Countries []string `json:"countries"`
|
||||
Cities []string `json:"cities"`
|
||||
Countries []string `json:"countries"` // Mullvad, PureVPN
|
||||
Cities []string `json:"cities"` // Mullvad, PureVPN, Windscribe
|
||||
Hostnames []string `json:"hostnames"` // Windscribe, Privado
|
||||
|
||||
// Mullvad
|
||||
ISPs []string `json:"isps"`
|
||||
Owned bool `json:"owned"`
|
||||
|
||||
// Mullvad, Windscribe
|
||||
CustomPort uint16 `json:"customPort"`
|
||||
CustomPort uint16 `json:"custom_port"`
|
||||
|
||||
// NordVPN
|
||||
Numbers []uint16 `json:"numbers"`
|
||||
|
||||
// PIA
|
||||
EncryptionPreset string `json:"encryptionPreset"`
|
||||
EncryptionPreset string `json:"encryption_preset"`
|
||||
}
|
||||
|
||||
type ExtraConfigOptions struct {
|
||||
ClientKey string `json:"-"` // Cyberghost
|
||||
EncryptionPreset string `json:"encryptionPreset"` // PIA
|
||||
OpenVPNIPv6 bool `json:"openvpnIPv6"` // Mullvad
|
||||
ClientCertificate string `json:"-"` // Cyberghost
|
||||
ClientKey string `json:"-"` // Cyberghost
|
||||
EncryptionPreset string `json:"encryption_preset"` // PIA
|
||||
OpenVPNIPv6 bool `json:"openvpn_ipv6"` // Mullvad
|
||||
}
|
||||
|
||||
// PortForwarding contains settings for port forwarding.
|
||||
@@ -111,7 +112,8 @@ func (p *ProviderSettings) String() string {
|
||||
)
|
||||
case "cyberghost":
|
||||
settingsList = append(settingsList,
|
||||
"ClientKey: [redacted]",
|
||||
"Client key: [redacted]",
|
||||
"Client certificate: [redacted]",
|
||||
"Group: "+p.ServerSelection.Group,
|
||||
"Regions: "+commaJoin(p.ServerSelection.Regions),
|
||||
)
|
||||
@@ -130,6 +132,11 @@ func (p *ProviderSettings) String() string {
|
||||
"Countries: "+commaJoin(p.ServerSelection.Countries),
|
||||
"Cities: "+commaJoin(p.ServerSelection.Cities),
|
||||
)
|
||||
case "privado":
|
||||
settingsList = append(settingsList,
|
||||
"Cities: "+commaJoin(p.ServerSelection.Cities),
|
||||
"Server numbers: "+commaJoin(numbers),
|
||||
)
|
||||
default:
|
||||
settingsList = append(settingsList,
|
||||
"<Missing String method, please implement me!>",
|
||||
|
||||
@@ -28,15 +28,6 @@ func (p *PIAServer) String() string {
|
||||
p.Region, p.PortForward, p.OpenvpnUDP.String(), p.OpenvpnTCP.String())
|
||||
}
|
||||
|
||||
type PIAOldServer struct {
|
||||
IPs []net.IP `json:"ips"`
|
||||
Region string `json:"region"`
|
||||
}
|
||||
|
||||
func (p *PIAOldServer) String() string {
|
||||
return fmt.Sprintf("{Region: %q, IPs: %s}", p.Region, goStringifyIPs(p.IPs))
|
||||
}
|
||||
|
||||
type MullvadServer struct {
|
||||
IPs []net.IP `json:"ips"`
|
||||
IPsV6 []net.IP `json:"ipsv6"`
|
||||
@@ -52,12 +43,15 @@ func (s *MullvadServer) String() string {
|
||||
}
|
||||
|
||||
type WindscribeServer struct {
|
||||
Region string `json:"region"`
|
||||
IPs []net.IP `json:"ips"`
|
||||
Region string `json:"region"`
|
||||
City string `json:"city"`
|
||||
Hostname string `json:"hostname"`
|
||||
IP net.IP `json:"ip"`
|
||||
}
|
||||
|
||||
func (s *WindscribeServer) String() string {
|
||||
return fmt.Sprintf("{Region: %q, IPs: %s}", s.Region, goStringifyIPs(s.IPs))
|
||||
return fmt.Sprintf("{Region: %q, City: %q, Hostname: %q, IP: %s}",
|
||||
s.Region, s.City, s.Hostname, goStringifyIP(s.IP))
|
||||
}
|
||||
|
||||
type SurfsharkServer struct {
|
||||
@@ -113,6 +107,16 @@ func (s *PurevpnServer) String() string {
|
||||
s.Region, s.Country, s.City, goStringifyIPs(s.IPs))
|
||||
}
|
||||
|
||||
type PrivadoServer struct {
|
||||
IP net.IP `json:"ip"`
|
||||
Hostname string `json:"hostname"`
|
||||
}
|
||||
|
||||
func (s *PrivadoServer) String() string {
|
||||
return fmt.Sprintf("{Hostname: %q, IP: %s}",
|
||||
s.Hostname, goStringifyIP(s.IP))
|
||||
}
|
||||
|
||||
func goStringifyIP(ip net.IP) string {
|
||||
s := fmt.Sprintf("%#v", ip)
|
||||
s = strings.TrimSuffix(strings.TrimPrefix(s, "net.IP{"), "}")
|
||||
|
||||
@@ -7,31 +7,6 @@ import (
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func Test_PIAOldServer_String(t *testing.T) {
|
||||
t.Parallel()
|
||||
testCases := map[string]struct {
|
||||
server PIAOldServer
|
||||
s string
|
||||
}{
|
||||
"no ips": {
|
||||
server: PIAOldServer{Region: "a b"},
|
||||
s: `{Region: "a b", IPs: []net.IP{}}`,
|
||||
},
|
||||
"with ips": {
|
||||
server: PIAOldServer{Region: "a b", IPs: []net.IP{{1, 1, 1, 1}, {2, 2, 2, 2}}},
|
||||
s: `{Region: "a b", IPs: []net.IP{{1, 1, 1, 1}, {2, 2, 2, 2}}}`,
|
||||
},
|
||||
}
|
||||
for name, testCase := range testCases {
|
||||
testCase := testCase
|
||||
t.Run(name, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
s := testCase.server.String()
|
||||
assert.Equal(t, testCase.s, s)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func Test_MullvadServer_String(t *testing.T) {
|
||||
t.Parallel()
|
||||
testCases := map[string]struct {
|
||||
|
||||
@@ -5,8 +5,8 @@ type AllServers struct {
|
||||
Cyberghost CyberghostServers `json:"cyberghost"`
|
||||
Mullvad MullvadServers `json:"mullvad"`
|
||||
Nordvpn NordvpnServers `json:"nordvpn"`
|
||||
PiaOld PiaOldServers `json:"piaOld"`
|
||||
Pia PiaServers `json:"pia"`
|
||||
Privado PrivadoServers `json:"privado"`
|
||||
Purevpn PurevpnServers `json:"purevpn"`
|
||||
Surfshark SurfsharkServers `json:"surfshark"`
|
||||
Vyprvpn VyprvpnServers `json:"vyprvpn"`
|
||||
@@ -28,20 +28,20 @@ type NordvpnServers struct {
|
||||
Timestamp int64 `json:"timestamp"`
|
||||
Servers []NordvpnServer `json:"servers"`
|
||||
}
|
||||
type PiaOldServers struct {
|
||||
Version uint16 `json:"version"`
|
||||
Timestamp int64 `json:"timestamp"`
|
||||
Servers []PIAOldServer `json:"servers"`
|
||||
}
|
||||
type PiaServers struct {
|
||||
Version uint16 `json:"version"`
|
||||
Timestamp int64 `json:"timestamp"`
|
||||
Servers []PIAServer `json:"servers"`
|
||||
}
|
||||
type PrivadoServers struct {
|
||||
Version uint16 `json:"version"`
|
||||
Timestamp int64 `json:"timestamp"`
|
||||
Servers []PrivadoServer `json:"servers"`
|
||||
}
|
||||
type PurevpnServers struct {
|
||||
Version uint16 `json:"version"`
|
||||
Timestamp int64 `json:"timestamp"`
|
||||
Servers []PurevpnServer `json:"purevpn"`
|
||||
Servers []PurevpnServer `json:"servers"`
|
||||
}
|
||||
type SurfsharkServers struct {
|
||||
Version uint16 `json:"version"`
|
||||
|
||||
@@ -20,26 +20,22 @@ import (
|
||||
|
||||
type Looper interface {
|
||||
Run(ctx context.Context, wg *sync.WaitGroup)
|
||||
Restart()
|
||||
PortForward(vpnGatewayIP net.IP)
|
||||
GetStatus() (status models.LoopStatus)
|
||||
SetStatus(status models.LoopStatus) (outcome string, err error)
|
||||
GetSettings() (settings settings.OpenVPN)
|
||||
SetSettings(settings settings.OpenVPN)
|
||||
GetPortForwarded() (portForwarded uint16)
|
||||
SetAllServers(allServers models.AllServers)
|
||||
SetSettings(settings settings.OpenVPN) (outcome string)
|
||||
GetServers() (servers models.AllServers)
|
||||
SetServers(servers models.AllServers)
|
||||
GetPortForwarded() (port uint16)
|
||||
PortForward(vpnGatewayIP net.IP)
|
||||
}
|
||||
|
||||
type looper struct {
|
||||
// Variable parameters
|
||||
provider models.VPNProvider
|
||||
settings settings.OpenVPN
|
||||
settingsMutex sync.RWMutex
|
||||
portForwarded uint16
|
||||
portForwardedMutex sync.RWMutex
|
||||
allServers models.AllServers
|
||||
allServersMutex sync.RWMutex
|
||||
state state
|
||||
// Fixed parameters
|
||||
uid int
|
||||
gid int
|
||||
username string
|
||||
uid int
|
||||
gid int
|
||||
// Configurators
|
||||
conf Configurator
|
||||
fw firewall.Configurator
|
||||
@@ -50,22 +46,28 @@ type looper struct {
|
||||
fileManager files.FileManager
|
||||
streamMerger command.StreamMerger
|
||||
cancel context.CancelFunc
|
||||
// Internal channels
|
||||
restart chan struct{}
|
||||
// Internal channels and locks
|
||||
loopLock sync.Mutex
|
||||
running chan models.LoopStatus
|
||||
stop, stopped chan struct{}
|
||||
start chan struct{}
|
||||
portForwardSignals chan net.IP
|
||||
}
|
||||
|
||||
func NewLooper(provider models.VPNProvider, settings settings.OpenVPN,
|
||||
uid, gid int, allServers models.AllServers,
|
||||
func NewLooper(settings settings.OpenVPN,
|
||||
username string, uid, gid int, allServers models.AllServers,
|
||||
conf Configurator, fw firewall.Configurator, routing routing.Routing,
|
||||
logger logging.Logger, client *http.Client, fileManager files.FileManager,
|
||||
streamMerger command.StreamMerger, cancel context.CancelFunc) Looper {
|
||||
return &looper{
|
||||
provider: provider,
|
||||
settings: settings,
|
||||
state: state{
|
||||
status: constants.Stopped,
|
||||
settings: settings,
|
||||
allServers: allServers,
|
||||
},
|
||||
username: username,
|
||||
uid: uid,
|
||||
gid: gid,
|
||||
allServers: allServers,
|
||||
conf: conf,
|
||||
fw: fw,
|
||||
routing: routing,
|
||||
@@ -75,46 +77,29 @@ func NewLooper(provider models.VPNProvider, settings settings.OpenVPN,
|
||||
fileManager: fileManager,
|
||||
streamMerger: streamMerger,
|
||||
cancel: cancel,
|
||||
restart: make(chan struct{}),
|
||||
start: make(chan struct{}),
|
||||
running: make(chan models.LoopStatus),
|
||||
stop: make(chan struct{}),
|
||||
stopped: make(chan struct{}),
|
||||
portForwardSignals: make(chan net.IP),
|
||||
}
|
||||
}
|
||||
|
||||
func (l *looper) Restart() { l.restart <- struct{}{} }
|
||||
func (l *looper) PortForward(vpnGateway net.IP) { l.portForwardSignals <- vpnGateway }
|
||||
|
||||
func (l *looper) GetSettings() (settings settings.OpenVPN) {
|
||||
l.settingsMutex.RLock()
|
||||
defer l.settingsMutex.RUnlock()
|
||||
return l.settings
|
||||
}
|
||||
|
||||
func (l *looper) SetSettings(settings settings.OpenVPN) {
|
||||
l.settingsMutex.Lock()
|
||||
defer l.settingsMutex.Unlock()
|
||||
l.settings = settings
|
||||
}
|
||||
|
||||
func (l *looper) SetAllServers(allServers models.AllServers) {
|
||||
l.allServersMutex.Lock()
|
||||
defer l.allServersMutex.Unlock()
|
||||
l.allServers = allServers
|
||||
}
|
||||
|
||||
func (l *looper) Run(ctx context.Context, wg *sync.WaitGroup) {
|
||||
defer wg.Done()
|
||||
crashed := false
|
||||
select {
|
||||
case <-l.restart:
|
||||
case <-l.start:
|
||||
case <-ctx.Done():
|
||||
return
|
||||
}
|
||||
defer l.logger.Warn("loop exited")
|
||||
|
||||
for ctx.Err() == nil {
|
||||
settings := l.GetSettings()
|
||||
l.allServersMutex.RLock()
|
||||
providerConf := provider.New(l.provider, l.allServers, time.Now)
|
||||
l.allServersMutex.RUnlock()
|
||||
settings, allServers := l.state.getSettingsAndServers()
|
||||
providerConf := provider.New(settings.Provider.Name, allServers, time.Now)
|
||||
connection, err := providerConf.GetOpenVPNConnection(settings.Provider.ServerSelection)
|
||||
if err != nil {
|
||||
l.logger.Error(err)
|
||||
@@ -124,8 +109,7 @@ func (l *looper) Run(ctx context.Context, wg *sync.WaitGroup) {
|
||||
lines := providerConf.BuildConf(
|
||||
connection,
|
||||
settings.Verbosity,
|
||||
l.uid,
|
||||
l.gid,
|
||||
l.username,
|
||||
settings.Root,
|
||||
settings.Cipher,
|
||||
settings.Auth,
|
||||
@@ -155,6 +139,10 @@ func (l *looper) Run(ctx context.Context, wg *sync.WaitGroup) {
|
||||
stream, waitFn, err := l.conf.Start(openvpnCtx)
|
||||
if err != nil {
|
||||
openvpnCancel()
|
||||
if !crashed {
|
||||
l.running <- constants.Crashed
|
||||
crashed = true
|
||||
}
|
||||
l.logAndWait(ctx, err)
|
||||
continue
|
||||
}
|
||||
@@ -179,23 +167,41 @@ func (l *looper) Run(ctx context.Context, wg *sync.WaitGroup) {
|
||||
err := waitFn() // blocking
|
||||
waitError <- err
|
||||
}()
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
l.logger.Warn("context canceled: exiting loop")
|
||||
openvpnCancel()
|
||||
<-waitError
|
||||
close(waitError)
|
||||
return
|
||||
case <-l.restart: // triggered restart
|
||||
l.logger.Info("restarting")
|
||||
openvpnCancel()
|
||||
<-waitError
|
||||
close(waitError)
|
||||
case err := <-waitError: // unexpected error
|
||||
openvpnCancel()
|
||||
close(waitError)
|
||||
l.logAndWait(ctx, err)
|
||||
|
||||
if !crashed {
|
||||
l.running <- constants.Running
|
||||
crashed = false
|
||||
} else {
|
||||
l.state.setStatusWithLock(constants.Running)
|
||||
}
|
||||
|
||||
stayHere := true
|
||||
for stayHere {
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
l.logger.Warn("context canceled: exiting loop")
|
||||
openvpnCancel()
|
||||
<-waitError
|
||||
close(waitError)
|
||||
return
|
||||
case <-l.stop:
|
||||
l.logger.Info("stopping")
|
||||
openvpnCancel()
|
||||
<-waitError
|
||||
l.stopped <- struct{}{}
|
||||
case <-l.start:
|
||||
l.logger.Info("starting")
|
||||
stayHere = false
|
||||
case err := <-waitError: // unexpected error
|
||||
openvpnCancel()
|
||||
l.state.setStatusWithLock(constants.Crashed)
|
||||
l.logAndWait(ctx, err)
|
||||
crashed = true
|
||||
stayHere = false
|
||||
}
|
||||
}
|
||||
close(waitError)
|
||||
openvpnCancel() // just for the linter
|
||||
}
|
||||
}
|
||||
|
||||
@@ -218,24 +224,21 @@ func (l *looper) logAndWait(ctx context.Context, err error) {
|
||||
func (l *looper) portForward(ctx context.Context, wg *sync.WaitGroup,
|
||||
providerConf provider.Provider, client *http.Client, gateway net.IP) {
|
||||
defer wg.Done()
|
||||
settings := l.GetSettings()
|
||||
l.state.portForwardedMu.RLock()
|
||||
settings := l.state.settings
|
||||
l.state.portForwardedMu.RUnlock()
|
||||
if !settings.Provider.PortForwarding.Enabled {
|
||||
return
|
||||
}
|
||||
syncState := func(port uint16) (pfFilepath models.Filepath) {
|
||||
l.portForwardedMutex.Lock()
|
||||
l.portForwarded = port
|
||||
l.portForwardedMutex.Unlock()
|
||||
settings := l.GetSettings()
|
||||
l.state.portForwardedMu.Lock()
|
||||
defer l.state.portForwardedMu.Unlock()
|
||||
l.state.portForwarded = port
|
||||
l.state.settingsMu.RLock()
|
||||
defer l.state.settingsMu.RUnlock()
|
||||
return settings.Provider.PortForwarding.Filepath
|
||||
}
|
||||
providerConf.PortForward(ctx,
|
||||
client, l.fileManager, l.pfLogger,
|
||||
gateway, l.fw, syncState)
|
||||
}
|
||||
|
||||
func (l *looper) GetPortForwarded() (portForwarded uint16) {
|
||||
l.portForwardedMutex.RLock()
|
||||
defer l.portForwardedMutex.RUnlock()
|
||||
return l.portForwarded
|
||||
}
|
||||
|
||||
121
internal/openvpn/state.go
Normal file
121
internal/openvpn/state.go
Normal file
@@ -0,0 +1,121 @@
|
||||
package openvpn
|
||||
|
||||
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.OpenVPN
|
||||
allServers models.AllServers
|
||||
portForwarded uint16
|
||||
statusMu sync.RWMutex
|
||||
settingsMu sync.RWMutex
|
||||
allServersMu sync.RWMutex
|
||||
portForwardedMu sync.RWMutex
|
||||
}
|
||||
|
||||
func (s *state) setStatusWithLock(status models.LoopStatus) {
|
||||
s.statusMu.Lock()
|
||||
defer s.statusMu.Unlock()
|
||||
s.status = status
|
||||
}
|
||||
|
||||
func (s *state) getSettingsAndServers() (settings settings.OpenVPN, allServers models.AllServers) {
|
||||
s.settingsMu.RLock()
|
||||
s.allServersMu.RLock()
|
||||
settings = s.settings
|
||||
allServers = s.allServers
|
||||
s.settingsMu.RLock()
|
||||
s.allServersMu.RLock()
|
||||
return settings, allServers
|
||||
}
|
||||
|
||||
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.OpenVPN) {
|
||||
l.state.settingsMu.RLock()
|
||||
defer l.state.settingsMu.RUnlock()
|
||||
return l.state.settings
|
||||
}
|
||||
|
||||
func (l *looper) SetSettings(settings settings.OpenVPN) (outcome string) {
|
||||
l.state.settingsMu.Lock()
|
||||
settingsUnchanged := reflect.DeepEqual(l.state.settings, settings)
|
||||
if settingsUnchanged {
|
||||
l.state.settingsMu.Unlock()
|
||||
return "settings left unchanged"
|
||||
}
|
||||
l.state.settings = settings
|
||||
_, _ = l.SetStatus(constants.Stopped)
|
||||
outcome, _ = l.SetStatus(constants.Running)
|
||||
return outcome
|
||||
}
|
||||
|
||||
func (l *looper) GetServers() (servers models.AllServers) {
|
||||
l.state.allServersMu.RLock()
|
||||
defer l.state.allServersMu.RUnlock()
|
||||
return l.state.allServers
|
||||
}
|
||||
|
||||
func (l *looper) SetServers(servers models.AllServers) {
|
||||
l.state.allServersMu.Lock()
|
||||
defer l.state.allServersMu.Unlock()
|
||||
l.state.allServers = servers
|
||||
}
|
||||
|
||||
func (l *looper) GetPortForwarded() (port uint16) {
|
||||
l.state.portForwardedMu.RLock()
|
||||
defer l.state.portForwardedMu.RUnlock()
|
||||
return l.state.portForwarded
|
||||
}
|
||||
@@ -1,6 +1,8 @@
|
||||
package params
|
||||
|
||||
import (
|
||||
"encoding/pem"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/qdm12/gluetun/internal/constants"
|
||||
@@ -22,7 +24,7 @@ func (p *reader) GetCyberghostRegions() (regions []string, err error) {
|
||||
}
|
||||
|
||||
// GetCyberghostClientKey obtains the one line client key to use for openvpn from the
|
||||
// environment variable CLIENT_KEY.
|
||||
// environment variable CLIENT_KEY or from the file at /gluetun/client.key.
|
||||
func (p *reader) GetCyberghostClientKey() (clientKey string, err error) {
|
||||
clientKey, err = p.envParams.GetEnv("CLIENT_KEY", libparams.CaseSensitiveValue())
|
||||
if err != nil {
|
||||
@@ -30,12 +32,45 @@ func (p *reader) GetCyberghostClientKey() (clientKey string, err error) {
|
||||
} else if len(clientKey) > 0 {
|
||||
return clientKey, nil
|
||||
}
|
||||
content, err := p.fileManager.ReadFile("/files/client.key")
|
||||
content, err := p.fileManager.ReadFile(string(constants.ClientKey))
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
s := string(content)
|
||||
return extractClientKey(content)
|
||||
}
|
||||
|
||||
func extractClientKey(b []byte) (key string, err error) {
|
||||
pemBlock, _ := pem.Decode(b)
|
||||
if pemBlock == nil {
|
||||
return "", fmt.Errorf("cannot decode PEM block from client key")
|
||||
}
|
||||
parsedBytes := pem.EncodeToMemory(pemBlock)
|
||||
s := string(parsedBytes)
|
||||
s = strings.ReplaceAll(s, "\n", "")
|
||||
s = strings.ReplaceAll(s, "\r", "")
|
||||
s = strings.TrimPrefix(s, "-----BEGIN PRIVATE KEY-----")
|
||||
s = strings.TrimSuffix(s, "-----END PRIVATE KEY-----")
|
||||
return s, nil
|
||||
}
|
||||
|
||||
// GetCyberghostClientCertificate obtains the client certificate to use for openvpn from the
|
||||
// file at /gluetun/client.crt.
|
||||
func (p *reader) GetCyberghostClientCertificate() (clientCertificate string, err error) {
|
||||
content, err := p.fileManager.ReadFile(string(constants.ClientCertificate))
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return extractClientCertificate(content)
|
||||
}
|
||||
|
||||
func extractClientCertificate(b []byte) (certificate string, err error) {
|
||||
pemBlock, _ := pem.Decode(b)
|
||||
if pemBlock == nil {
|
||||
return "", fmt.Errorf("cannot decode PEM block from client certificate")
|
||||
}
|
||||
parsedBytes := pem.EncodeToMemory(pemBlock)
|
||||
s := string(parsedBytes)
|
||||
s = strings.ReplaceAll(s, "\n", "")
|
||||
s = strings.TrimPrefix(s, "-----BEGIN CERTIFICATE-----")
|
||||
s = strings.TrimSuffix(s, "-----END CERTIFICATE-----")
|
||||
return s, nil
|
||||
}
|
||||
|
||||
175
internal/params/cyberghost_test.go
Normal file
175
internal/params/cyberghost_test.go
Normal file
@@ -0,0 +1,175 @@
|
||||
package params
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func Test_extractClientKey(t *testing.T) {
|
||||
t.Parallel()
|
||||
const validPEM = `
|
||||
-----BEGIN PRIVATE KEY-----
|
||||
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
|
||||
zZjrL52saevO25cigVl+hxcnY8DTpbk=
|
||||
-----END PRIVATE KEY-----
|
||||
`
|
||||
const validKeyString = "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" //nolint:lll
|
||||
testCases := map[string]struct {
|
||||
b []byte
|
||||
key string
|
||||
err error
|
||||
}{
|
||||
"no input": {
|
||||
err: fmt.Errorf("cannot decode PEM block from client key"),
|
||||
},
|
||||
"bad input": {
|
||||
b: []byte{1, 2, 3},
|
||||
err: fmt.Errorf("cannot decode PEM block from client key"),
|
||||
},
|
||||
"valid key": {
|
||||
b: []byte(validPEM),
|
||||
key: validKeyString,
|
||||
},
|
||||
}
|
||||
for name, testCase := range testCases {
|
||||
testCase := testCase
|
||||
t.Run(name, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
key, err := extractClientKey(testCase.b)
|
||||
if testCase.err != nil {
|
||||
require.Error(t, err)
|
||||
assert.Equal(t, testCase.err.Error(), err.Error())
|
||||
} else {
|
||||
assert.NoError(t, err)
|
||||
}
|
||||
assert.Equal(t, testCase.key, key)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func Test_extractClientCertificate(t *testing.T) {
|
||||
t.Parallel()
|
||||
const validPEM = `
|
||||
-----BEGIN CERTIFICATE-----
|
||||
MIIGrDCCBJSgAwIBAgIEAdTnfTANBgkqhkiG9w0BAQsFADB7MQswCQYDVQQGEwJS
|
||||
TzESMBAGA1UEBxMJQnVjaGFyZXN0MRgwFgYDVQQKEw9DeWJlckdob3N0IFMuQS4x
|
||||
GzAZBgNVBAMTEkN5YmVyR2hvc3QgUm9vdCBDQTEhMB8GCSqGSIb3DQEJARYSaW5m
|
||||
b0BjeWJlcmdob3N0LnJvMB4XDTIwMDcwNDE1MjkzNloXDTMwMDcwMjE1MjkzNlow
|
||||
fTELMAkGA1UEBhMCUk8xEjAQBgNVBAcMCUJ1Y2hhcmVzdDEYMBYGA1UECgwPQ3li
|
||||
ZXJHaG9zdCBTLkEuMR0wGwYDVQQDDBRjLmoua2xhdmVyQGdtYWlsLmNvbTEhMB8G
|
||||
CSqGSIb3DQEJARYSaW5mb0BjeWJlcmdob3N0LnJvMIICIjANBgkqhkiG9w0BAQEF
|
||||
AAOCAg8AMIICCgKCAgEAobp2NlGUHMNBe08YEOnVG3QJjF3ZaXbRhE/II9rmtgJT
|
||||
NZtDohGChvFlNRsExKzVrKxHCeuJkVffwzQ6fYk4/M1RdYLJUh0UVw3e4WdApw8E
|
||||
7TJZxDYm4SHQNXUvt1Rt5TjslcXxIpDZgrMSc/kHROYEL9tdgdzPZErUJehXyJPh
|
||||
EzIrzmAJh501x7WwKPz9ctSVlItyavqEWFF2vyUa6X9DYmD9mQTz5c+VXNO5DkXm
|
||||
PFBIaEVDnvFtcjGJ56yEvFnWVukL+OUX7ezowrIOFOcp9udjgpeiHq+XvsQ6ER0D
|
||||
Jt25MiEId3NjkxtZ8BitDftTcLN/kt81hWKT7adMVc3kpIZ80cxrwRCttMd7sHAz
|
||||
KI9u7pMxv10eUOsIEY87ewBe3l6KvEnjA+9uIjim6gLLebDIaEH50Ee9PzNJ8fqQ
|
||||
2u54Ab4bt00/H1sUnJ6Ss/+WsQDOK1BsPRKKcnHZntOlHrs2Tu5+txKNU2cOapI8
|
||||
SjVULUNKrRXASbpfWnLUfri/HO742bJb/TjkOJcOxta3hTPFAhaRWBusVlB41XVH
|
||||
euH5DAhugYXeSNK6/6Ul8YvKUNH/7QbxuGIGXfth19Xl4QLI1umyEjZopSlt3tOi
|
||||
O2V1soVNSQCCfxXVoCTMESMLjhkjWdmBDhdy2GTW7S4YoJfqVKiS18rYkN7I4ZMC
|
||||
AwEAAaOCATQwggEwMAkGA1UdEwQCMAAwCwYDVR0PBAQDAgWgMDQGCWCGSAGG+EIB
|
||||
DQQnFiVDeWJlckdob3N0IEdlbmVyYXRlZCBVc2VyIENlcnRpZmljYXRlMBEGCWCG
|
||||
SAGG+EIBAQQEAwIHgDAdBgNVHQ4EFgQULwUtU5s6pL2NN9gPeEnKX0dhwiswga0G
|
||||
A1UdIwSBpTCBooAU6tdK1g/He5qzjeAoM5eHt4in9iWhf6R9MHsxCzAJBgNVBAYT
|
||||
AlJPMRIwEAYDVQQHEwlCdWNoYXJlc3QxGDAWBgNVBAoTD0N5YmVyR2hvc3QgUy5B
|
||||
LjEbMBkGA1UEAxMSQ3liZXJHaG9zdCBSb290IENBMSEwHwYJKoZIhvcNAQkBFhJp
|
||||
bmZvQGN5YmVyZ2hvc3Qucm+CCQCcVButZsQ0uzANBgkqhkiG9w0BAQsFAAOCAgEA
|
||||
ystGIMYhQWaEdTqlnLCytrr8657t+PuidZMNNIaPB3wN2Fi2xKf14DTg03mqxjmP
|
||||
Pb+f+PVNIOV5PdWD4jcQwOP1GEboGV0DFzlRGeAtDcvKwdee4oASJbZq1CETqDao
|
||||
hQTxKEWC+UBk2F36nOaEI6Sab+Mb4cR9//PAwvzOqrXuGF5NuIOX7eFtCMQSgQq6
|
||||
lRRqTQjekm0Dxigx4JA92Jo2qZRwCJ0T3IXBJGL831HCFJbDWv8PV3lsfFb/i2+v
|
||||
r54uywFQVWWp18dYi97gipfuQ4zRg2Ldx5aXSmnhhKpg5ioZvtk043QofF12YORh
|
||||
obElqavRbvvhZvlCouvcuoq9QKi7IPe5SJZkZ1X7ezMesCwBzwFpt6vRUAcslsNF
|
||||
bcYS1iSENlY/PTcDqBhbKuc9yAhq+/aUgaY/8VF5RWVzSRZufbf3BPwOkE4K0Uyb
|
||||
aobO/YX0JOkCacAD+4tdR6YSXNIMMRAOCBQvxbxFXaHzhwhzBAjdsC56FrJKwXvQ
|
||||
rRLU3tF4P0zFMeNTay8uTtUXugDK7EnklLESuYdpUJ8bUMlAUhJBi6UFI9/icMud
|
||||
xXvLRvhnBW9EtKib5JnVFUovcEUt+3EJbyst05nkL4YPjQS4TC9DHdo5SyRAy1Tp
|
||||
iOCYTbretAFZRhh6ycUN5hBeN8GMQxiMreMtDV4PEIQ=
|
||||
-----END CERTIFICATE-----
|
||||
`
|
||||
const validCertificateString = "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" //nolint:lll
|
||||
testCases := map[string]struct {
|
||||
b []byte
|
||||
certificate string
|
||||
err error
|
||||
}{
|
||||
"no input": {
|
||||
err: fmt.Errorf("cannot decode PEM block from client certificate"),
|
||||
},
|
||||
"bad input": {
|
||||
b: []byte{1, 2, 3},
|
||||
err: fmt.Errorf("cannot decode PEM block from client certificate"),
|
||||
},
|
||||
"valid key": {
|
||||
b: []byte(validPEM),
|
||||
certificate: validCertificateString,
|
||||
},
|
||||
}
|
||||
for name, testCase := range testCases {
|
||||
testCase := testCase
|
||||
t.Run(name, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
certificate, err := extractClientCertificate(testCase.b)
|
||||
if testCase.err != nil {
|
||||
require.Error(t, err)
|
||||
assert.Equal(t, testCase.err.Error(), err.Error())
|
||||
} else {
|
||||
assert.NoError(t, err)
|
||||
}
|
||||
assert.Equal(t, testCase.certificate, certificate)
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -130,8 +130,8 @@ func (r *reader) GetDNSOverTLSPrivateAddresses() (privateAddresses []string, err
|
||||
return privateAddresses, nil
|
||||
}
|
||||
|
||||
// GetDNSOverTLSIPv6 obtains if Unbound should resolve ipv6 addresses using ipv6 DNS over TLS
|
||||
// servers from the environment variable DOT_IPV6.
|
||||
// GetDNSOverTLSIPv6 obtains if Unbound should resolve ipv6 addresses using
|
||||
// ipv6 DNS over TLS from the environment variable DOT_IPV6.
|
||||
func (r *reader) GetDNSOverTLSIPv6() (ipv6 bool, err error) {
|
||||
return r.envParams.GetOnOff("DOT_IPV6", libparams.Default("off"))
|
||||
}
|
||||
|
||||
78
internal/params/httpproxy.go
Normal file
78
internal/params/httpproxy.go
Normal file
@@ -0,0 +1,78 @@
|
||||
package params
|
||||
|
||||
import (
|
||||
"strings"
|
||||
|
||||
libparams "github.com/qdm12/golibs/params"
|
||||
)
|
||||
|
||||
// GetHTTPProxy obtains if the HTTP proxy is on from the environment variable
|
||||
// HTTPPROXY, and using PROXY and TINYPROXY as retro-compatibility names.
|
||||
func (r *reader) GetHTTPProxy() (enabled bool, err error) {
|
||||
retroKeysOption := libparams.RetroKeys(
|
||||
[]string{"TINYPROXY", "PROXY"},
|
||||
r.onRetroActive,
|
||||
)
|
||||
return r.envParams.GetOnOff("HTTPPROXY", retroKeysOption, libparams.Default("off"))
|
||||
}
|
||||
|
||||
// GetHTTPProxyLog obtains the if http proxy requests should be logged from
|
||||
// the environment variable HTTPPROXY_LOG, and using PROXY_LOG_LEVEL and
|
||||
// TINYPROXY_LOG as retro-compatibility names.
|
||||
func (r *reader) GetHTTPProxyLog() (log bool, err error) {
|
||||
s, _ := r.envParams.GetEnv("HTTPPROXY_LOG")
|
||||
if len(s) == 0 {
|
||||
s, _ = r.envParams.GetEnv("PROXY_LOG_LEVEL")
|
||||
if len(s) == 0 {
|
||||
s, _ = r.envParams.GetEnv("TINYPROXY_LOG")
|
||||
if len(s) == 0 {
|
||||
return false, nil // default log disabled
|
||||
}
|
||||
}
|
||||
switch strings.ToLower(s) {
|
||||
case "info", "connect", "notice":
|
||||
return true, nil
|
||||
default:
|
||||
return false, nil
|
||||
}
|
||||
}
|
||||
return r.envParams.GetOnOff("HTTPPROXY_LOG", libparams.Default("off"))
|
||||
}
|
||||
|
||||
// GetHTTPProxyPort obtains the HTTP proxy listening port from the environment variable
|
||||
// HTTPPROXY_PORT, and using PROXY_PORT and TINYPROXY_PORT as retro-compatibility names.
|
||||
func (r *reader) GetHTTPProxyPort() (port uint16, err error) {
|
||||
retroKeysOption := libparams.RetroKeys(
|
||||
[]string{"TINYPROXY_PORT", "PROXY_PORT"},
|
||||
r.onRetroActive,
|
||||
)
|
||||
return r.envParams.GetPort("HTTPPROXY_PORT", retroKeysOption, libparams.Default("8888"))
|
||||
}
|
||||
|
||||
// GetHTTPProxyUser obtains the HTTP proxy server user from the environment variable
|
||||
// HTTPPROXY_USER, and using TINYPROXY_USER and PROXY_USER as retro-compatibility names.
|
||||
func (r *reader) GetHTTPProxyUser() (user string, err error) {
|
||||
retroKeysOption := libparams.RetroKeys(
|
||||
[]string{"TINYPROXY_USER", "PROXY_USER"},
|
||||
r.onRetroActive,
|
||||
)
|
||||
return r.envParams.GetEnv("HTTPPROXY_USER",
|
||||
retroKeysOption, libparams.CaseSensitiveValue(), libparams.Unset())
|
||||
}
|
||||
|
||||
// GetHTTPProxyPassword obtains the HTTP proxy server password from the environment variable
|
||||
// HTTPPROXY_PASSWORD, and using TINYPROXY_PASSWORD and PROXY_PASSWORD as retro-compatibility names.
|
||||
func (r *reader) GetHTTPProxyPassword() (password string, err error) {
|
||||
retroKeysOption := libparams.RetroKeys(
|
||||
[]string{"TINYPROXY_PASSWORD", "PROXY_PASSWORD"},
|
||||
r.onRetroActive,
|
||||
)
|
||||
return r.envParams.GetEnv("HTTPPROXY_PASSWORD",
|
||||
retroKeysOption, libparams.CaseSensitiveValue(), libparams.Unset())
|
||||
}
|
||||
|
||||
// GetHTTPProxyStealth obtains the HTTP proxy server stealth mode
|
||||
// from the environment variable HTTPPROXY_STEALTH.
|
||||
func (r *reader) GetHTTPProxyStealth() (stealth bool, err error) {
|
||||
return r.envParams.GetOnOff("HTTPPROXY_STEALTH", libparams.Default("off"))
|
||||
}
|
||||
@@ -37,7 +37,7 @@ type Reader interface {
|
||||
GetUID() (uid int, err error)
|
||||
GetGID() (gid int, err error)
|
||||
GetTimezone() (timezone string, err error)
|
||||
GetIPStatusFilepath() (filepath models.Filepath, err error)
|
||||
GetPublicIPFilepath() (filepath models.Filepath, err error)
|
||||
|
||||
// Firewall getters
|
||||
GetFirewall() (enabled bool, err error)
|
||||
@@ -62,7 +62,6 @@ type Reader interface {
|
||||
GetPortForwardingStatusFilepath() (filepath models.Filepath, err error)
|
||||
GetPIAEncryptionPreset() (preset string, err error)
|
||||
GetPIARegions() (regions []string, err error)
|
||||
GetPIAOldRegions() (regions []string, err error)
|
||||
|
||||
// Mullvad getters
|
||||
GetMullvadCountries() (countries []string, err error)
|
||||
@@ -73,6 +72,8 @@ type Reader interface {
|
||||
|
||||
// Windscribe getters
|
||||
GetWindscribeRegions() (countries []string, err error)
|
||||
GetWindscribeCities() (cities []string, err error)
|
||||
GetWindscribeHostnames() (hostnames []string, err error)
|
||||
GetWindscribePort(protocol models.NetworkProtocol) (port uint16, err error)
|
||||
|
||||
// Surfshark getters
|
||||
@@ -82,6 +83,7 @@ type Reader interface {
|
||||
GetCyberghostGroup() (group string, err error)
|
||||
GetCyberghostRegions() (regions []string, err error)
|
||||
GetCyberghostClientKey() (clientKey string, err error)
|
||||
GetCyberghostClientCertificate() (clientCertificate string, err error)
|
||||
|
||||
// Vyprvpn getters
|
||||
GetVyprvpnRegions() (regions []string, err error)
|
||||
@@ -90,6 +92,9 @@ type Reader interface {
|
||||
GetNordvpnRegions() (regions []string, err error)
|
||||
GetNordvpnNumbers() (numbers []uint16, err error)
|
||||
|
||||
// Privado getters
|
||||
GetPrivadoHostnames() (hostnames []string, err error)
|
||||
|
||||
// PureVPN getters
|
||||
GetPurevpnRegions() (regions []string, err error)
|
||||
GetPurevpnCountries() (countries []string, err error)
|
||||
@@ -102,12 +107,13 @@ type Reader interface {
|
||||
GetShadowSocksPassword() (password string, err error)
|
||||
GetShadowSocksMethod() (method string, err error)
|
||||
|
||||
// Tinyproxy getters
|
||||
GetTinyProxy() (activated bool, err error)
|
||||
GetTinyProxyLog() (models.TinyProxyLogLevel, error)
|
||||
GetTinyProxyPort() (port uint16, err error)
|
||||
GetTinyProxyUser() (user string, err error)
|
||||
GetTinyProxyPassword() (password string, err error)
|
||||
// HTTP proxy getters
|
||||
GetHTTPProxy() (activated bool, err error)
|
||||
GetHTTPProxyLog() (log bool, err error)
|
||||
GetHTTPProxyPort() (port uint16, err error)
|
||||
GetHTTPProxyUser() (user string, err error)
|
||||
GetHTTPProxyPassword() (password string, err error)
|
||||
GetHTTPProxyStealth() (stealth bool, err error)
|
||||
|
||||
// Public IP getters
|
||||
GetPublicIPPeriod() (period time.Duration, err error)
|
||||
@@ -146,9 +152,9 @@ func (r *reader) GetVPNSP() (vpnServiceProvider models.VPNProvider, err error) {
|
||||
s, err := r.envParams.GetValueIfInside(
|
||||
"VPNSP",
|
||||
[]string{
|
||||
"pia", "private internet access", "private internet access old",
|
||||
"pia", "private internet access",
|
||||
"mullvad", "windscribe", "surfshark", "cyberghost",
|
||||
"vyprvpn", "nordvpn", "purevpn",
|
||||
"vyprvpn", "nordvpn", "purevpn", "privado",
|
||||
}, libparams.Default("private internet access"))
|
||||
if s == "pia" {
|
||||
s = "private internet access"
|
||||
|
||||
@@ -63,9 +63,3 @@ func (r *reader) GetPIAEncryptionPreset() (preset string, err error) {
|
||||
func (r *reader) GetPIARegions() (regions []string, err error) {
|
||||
return r.envParams.GetCSVInPossibilities("REGION", constants.PIAGeoChoices())
|
||||
}
|
||||
|
||||
// GetPIAOldRegions obtains the regions for the PIA servers from the
|
||||
// environment variable REGION.
|
||||
func (r *reader) GetPIAOldRegions() (regions []string, err error) {
|
||||
return r.envParams.GetCSVInPossibilities("REGION", constants.PIAOldGeoChoices())
|
||||
}
|
||||
|
||||
11
internal/params/privado.go
Normal file
11
internal/params/privado.go
Normal file
@@ -0,0 +1,11 @@
|
||||
package params
|
||||
|
||||
import (
|
||||
"github.com/qdm12/gluetun/internal/constants"
|
||||
)
|
||||
|
||||
// GetPrivadoHostnames obtains the hostnames for the Privado server from the
|
||||
// environment variable HOSTNAME.
|
||||
func (r *reader) GetPrivadoHostnames() (hosts []string, err error) {
|
||||
return r.envParams.GetCSVInPossibilities("HOSTNAME", constants.PrivadoHostnameChoices())
|
||||
}
|
||||
@@ -3,6 +3,7 @@ package params
|
||||
import (
|
||||
"time"
|
||||
|
||||
"github.com/qdm12/gluetun/internal/models"
|
||||
libparams "github.com/qdm12/golibs/params"
|
||||
)
|
||||
|
||||
@@ -15,3 +16,13 @@ func (r *reader) GetPublicIPPeriod() (period time.Duration, err error) {
|
||||
}
|
||||
return time.ParseDuration(s)
|
||||
}
|
||||
|
||||
// GetPublicIPFilepath obtains the public IP filepath
|
||||
// from the environment variable PUBLICIP_FILE with retro-compatible
|
||||
// environment variable IP_STATUS_FILE.
|
||||
func (r *reader) GetPublicIPFilepath() (filepath models.Filepath, err error) {
|
||||
filepathStr, err := r.envParams.GetPath("PUBLICIP_FILE",
|
||||
libparams.RetroKeys([]string{"IP_STATUS_FILE"}, r.onRetroActive),
|
||||
libparams.Default("/tmp/gluetun/ip"), libparams.CaseSensitiveValue())
|
||||
return models.Filepath(filepathStr), err
|
||||
}
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
package params
|
||||
|
||||
import (
|
||||
"github.com/qdm12/gluetun/internal/models"
|
||||
libparams "github.com/qdm12/golibs/params"
|
||||
)
|
||||
|
||||
@@ -19,11 +18,3 @@ func (r *reader) GetGID() (gid int, err error) {
|
||||
func (r *reader) GetTimezone() (timezone string, err error) {
|
||||
return r.envParams.GetEnv("TZ")
|
||||
}
|
||||
|
||||
// GetIPStatusFilepath obtains the IP status file path
|
||||
// from the environment variable IP_STATUS_FILE.
|
||||
func (r *reader) GetIPStatusFilepath() (filepath models.Filepath, err error) {
|
||||
filepathStr, err := r.envParams.GetPath("IP_STATUS_FILE",
|
||||
libparams.Default("/tmp/gluetun/ip"), libparams.CaseSensitiveValue())
|
||||
return models.Filepath(filepathStr), err
|
||||
}
|
||||
|
||||
@@ -1,120 +0,0 @@
|
||||
package params
|
||||
|
||||
import (
|
||||
"strconv"
|
||||
|
||||
"github.com/qdm12/gluetun/internal/models"
|
||||
libparams "github.com/qdm12/golibs/params"
|
||||
)
|
||||
|
||||
// GetTinyProxy obtains if TinyProxy is on from the environment variable
|
||||
// TINYPROXY, and using PROXY as a retro-compatibility name.
|
||||
func (r *reader) GetTinyProxy() (activated bool, err error) {
|
||||
// Retro-compatibility
|
||||
s, err := r.envParams.GetEnv("PROXY")
|
||||
if err != nil {
|
||||
return false, err
|
||||
} else if len(s) != 0 {
|
||||
r.logger.Warn("You are using the old environment variable PROXY, please consider changing it to TINYPROXY")
|
||||
return r.envParams.GetOnOff("PROXY", libparams.Compulsory())
|
||||
}
|
||||
return r.envParams.GetOnOff("TINYPROXY", libparams.Default("off"))
|
||||
}
|
||||
|
||||
// GetTinyProxyLog obtains the TinyProxy log level from the environment variable
|
||||
// TINYPROXY_LOG, and using PROXY_LOG_LEVEL as a retro-compatibility name.
|
||||
func (r *reader) GetTinyProxyLog() (models.TinyProxyLogLevel, error) {
|
||||
// Retro-compatibility
|
||||
s, err := r.envParams.GetEnv("PROXY_LOG_LEVEL")
|
||||
if err != nil {
|
||||
return models.TinyProxyLogLevel(s), err
|
||||
} else if len(s) != 0 {
|
||||
r.logger.Warn("You are using the old environment variable PROXY_LOG_LEVEL, please consider changing it to TINYPROXY_LOG") //nolint:lll
|
||||
s, err = r.envParams.GetValueIfInside("PROXY_LOG_LEVEL",
|
||||
[]string{"Info", "Connect", "Notice", "Warning", "Error", "Critical"},
|
||||
libparams.Compulsory())
|
||||
return models.TinyProxyLogLevel(s), err
|
||||
}
|
||||
s, err = r.envParams.GetValueIfInside("TINYPROXY_LOG",
|
||||
[]string{"Info", "Connect", "Notice", "Warning", "Error", "Critical"},
|
||||
libparams.Default("Connect"))
|
||||
return models.TinyProxyLogLevel(s), err
|
||||
}
|
||||
|
||||
// GetTinyProxyPort obtains the TinyProxy listening port from the environment variable
|
||||
// TINYPROXY_PORT, and using PROXY_PORT as a retro-compatibility name.
|
||||
func (r *reader) GetTinyProxyPort() (port uint16, err error) {
|
||||
// Retro-compatibility
|
||||
portStr, err := r.envParams.GetEnv("PROXY_PORT")
|
||||
switch {
|
||||
case err != nil:
|
||||
return 0, err
|
||||
case len(portStr) != 0:
|
||||
r.logger.Warn("You are using the old environment variable PROXY_PORT, please consider changing it to TINYPROXY_PORT")
|
||||
default:
|
||||
portStr, err = r.envParams.GetEnv("TINYPROXY_PORT", libparams.Default("8888"))
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
}
|
||||
if err := r.verifier.VerifyPort(portStr); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
portUint64, err := strconv.ParseUint(portStr, 10, 16)
|
||||
return uint16(portUint64), err
|
||||
}
|
||||
|
||||
// GetTinyProxyUser obtains the TinyProxy server user from the environment variable
|
||||
// TINYPROXY_USER, and using PROXY_USER as a retro-compatibility name.
|
||||
func (r *reader) GetTinyProxyUser() (user string, err error) {
|
||||
defer func() {
|
||||
unsetErr := r.unsetEnv("PROXY_USER")
|
||||
if err == nil {
|
||||
err = unsetErr
|
||||
}
|
||||
}()
|
||||
defer func() {
|
||||
unsetErr := r.unsetEnv("TINYPROXY_USER")
|
||||
if err == nil {
|
||||
err = unsetErr
|
||||
}
|
||||
}()
|
||||
// Retro-compatibility
|
||||
user, err = r.envParams.GetEnv("PROXY_USER", libparams.CaseSensitiveValue())
|
||||
if err != nil {
|
||||
return user, err
|
||||
}
|
||||
if len(user) != 0 {
|
||||
r.logger.Warn("You are using the old environment variable PROXY_USER, please consider changing it to TINYPROXY_USER")
|
||||
return user, nil
|
||||
}
|
||||
return r.envParams.GetEnv("TINYPROXY_USER", libparams.CaseSensitiveValue())
|
||||
}
|
||||
|
||||
// GetTinyProxyPassword obtains the TinyProxy server password from the environment variable
|
||||
// TINYPROXY_PASSWORD, and using PROXY_PASSWORD as a retro-compatibility name.
|
||||
func (r *reader) GetTinyProxyPassword() (password string, err error) {
|
||||
defer func() {
|
||||
unsetErr := r.unsetEnv("PROXY_PASSWORD")
|
||||
if err == nil {
|
||||
err = unsetErr
|
||||
}
|
||||
}()
|
||||
defer func() {
|
||||
unsetErr := r.unsetEnv("TINYPROXY_PASSWORD")
|
||||
if err == nil {
|
||||
err = unsetErr
|
||||
}
|
||||
}()
|
||||
|
||||
// Retro-compatibility
|
||||
password, err = r.envParams.GetEnv("PROXY_PASSWORD", libparams.CaseSensitiveValue())
|
||||
if err != nil {
|
||||
return password, err
|
||||
}
|
||||
if len(password) != 0 {
|
||||
r.logger.Warn("You are using the old environment variable PROXY_PASSWORD, please consider changing it to TINYPROXY_PASSWORD") //nolint:lll
|
||||
return password, nil
|
||||
}
|
||||
return r.envParams.GetEnv("TINYPROXY_PASSWORD", libparams.CaseSensitiveValue())
|
||||
}
|
||||
@@ -14,7 +14,19 @@ func (r *reader) GetWindscribeRegions() (regions []string, err error) {
|
||||
return r.envParams.GetCSVInPossibilities("REGION", constants.WindscribeRegionChoices())
|
||||
}
|
||||
|
||||
// GetMullvadPort obtains the port to reach the Mullvad server on from the
|
||||
// GetWindscribeCities obtains the cities for the Windscribe servers from the
|
||||
// environment variable CITY.
|
||||
func (r *reader) GetWindscribeCities() (cities []string, err error) {
|
||||
return r.envParams.GetCSVInPossibilities("CITY", constants.WindscribeCityChoices())
|
||||
}
|
||||
|
||||
// GetWindscribeHostnames obtains the hostnames for the Windscribe servers from the
|
||||
// environment variable HOSTNAME.
|
||||
func (r *reader) GetWindscribeHostnames() (hostnames []string, err error) {
|
||||
return r.envParams.GetCSVInPossibilities("HOSTNAME", constants.WindscribeHostnameChoices())
|
||||
}
|
||||
|
||||
// GetWindscribePort obtains the port to reach the Windscribe server on from the
|
||||
// environment variable PORT.
|
||||
//nolint:gomnd
|
||||
func (r *reader) GetWindscribePort(protocol models.NetworkProtocol) (port uint16, err error) {
|
||||
|
||||
@@ -2,4 +2,5 @@ package provider
|
||||
|
||||
const (
|
||||
aes256cbc = "aes-256-cbc"
|
||||
sha256 = "sha256"
|
||||
)
|
||||
|
||||
@@ -62,13 +62,13 @@ func (c *cyberghost) GetOpenVPNConnection(selection models.ServerSelection) (
|
||||
return pickRandomConnection(connections, c.randSource), nil
|
||||
}
|
||||
|
||||
func (c *cyberghost) BuildConf(connection models.OpenVPNConnection, verbosity,
|
||||
uid, gid int, root bool, cipher, auth string, extras models.ExtraConfigOptions) (lines []string) {
|
||||
func (c *cyberghost) BuildConf(connection models.OpenVPNConnection, verbosity int,
|
||||
username string, root bool, cipher, auth string, extras models.ExtraConfigOptions) (lines []string) {
|
||||
if len(cipher) == 0 {
|
||||
cipher = aes256cbc
|
||||
}
|
||||
if len(auth) == 0 {
|
||||
auth = "SHA256"
|
||||
auth = sha256
|
||||
}
|
||||
lines = []string{
|
||||
"client",
|
||||
@@ -105,7 +105,7 @@ func (c *cyberghost) BuildConf(connection models.OpenVPNConnection, verbosity,
|
||||
lines = append(lines, "ncp-ciphers AES-256-GCM:AES-256-CBC:AES-128-GCM")
|
||||
}
|
||||
if !root {
|
||||
lines = append(lines, "user nonrootuser")
|
||||
lines = append(lines, "user "+username)
|
||||
}
|
||||
lines = append(lines, []string{
|
||||
"<ca>",
|
||||
@@ -117,7 +117,7 @@ func (c *cyberghost) BuildConf(connection models.OpenVPNConnection, verbosity,
|
||||
lines = append(lines, []string{
|
||||
"<cert>",
|
||||
"-----BEGIN CERTIFICATE-----",
|
||||
constants.CyberghostClientCertificate,
|
||||
extras.ClientCertificate,
|
||||
"-----END CERTIFICATE-----",
|
||||
"</cert>",
|
||||
}...)
|
||||
|
||||
@@ -73,7 +73,7 @@ func (m *mullvad) GetOpenVPNConnection(selection models.ServerSelection) (
|
||||
}
|
||||
|
||||
func (m *mullvad) BuildConf(connection models.OpenVPNConnection,
|
||||
verbosity, uid, gid int, root bool, cipher, auth string, extras models.ExtraConfigOptions) (lines []string) {
|
||||
verbosity int, username string, root bool, cipher, auth string, extras models.ExtraConfigOptions) (lines []string) {
|
||||
if len(cipher) == 0 {
|
||||
cipher = aes256cbc
|
||||
}
|
||||
@@ -114,7 +114,7 @@ func (m *mullvad) BuildConf(connection models.OpenVPNConnection,
|
||||
lines = append(lines, `pull-filter ignore "ifconfig-ipv6"`)
|
||||
}
|
||||
if !root {
|
||||
lines = append(lines, "user nonrootuser")
|
||||
lines = append(lines, "user "+username)
|
||||
}
|
||||
lines = append(lines, []string{
|
||||
"<ca>",
|
||||
|
||||
@@ -78,7 +78,7 @@ func (n *nordvpn) GetOpenVPNConnection(selection models.ServerSelection) (
|
||||
return pickRandomConnection(connections, n.randSource), nil
|
||||
}
|
||||
|
||||
func (n *nordvpn) BuildConf(connection models.OpenVPNConnection, verbosity, uid, gid int, root bool,
|
||||
func (n *nordvpn) BuildConf(connection models.OpenVPNConnection, verbosity int, username string, root bool,
|
||||
cipher, auth string, extras models.ExtraConfigOptions) (lines []string) {
|
||||
if len(cipher) == 0 {
|
||||
cipher = aes256cbc
|
||||
@@ -121,7 +121,7 @@ func (n *nordvpn) BuildConf(connection models.OpenVPNConnection, verbosity, uid,
|
||||
fmt.Sprintf("auth %s", auth),
|
||||
}
|
||||
if !root {
|
||||
lines = append(lines, "user nonrootuser")
|
||||
lines = append(lines, "user "+username)
|
||||
}
|
||||
lines = append(lines, []string{
|
||||
"<ca>",
|
||||
|
||||
@@ -1,81 +0,0 @@
|
||||
package provider
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/qdm12/gluetun/internal/constants"
|
||||
"github.com/qdm12/gluetun/internal/models"
|
||||
)
|
||||
|
||||
func buildPIAConf(connection models.OpenVPNConnection, verbosity int, root bool, cipher, auth string,
|
||||
extras models.ExtraConfigOptions) (lines []string) {
|
||||
var X509CRL, certificate string
|
||||
var defaultCipher, defaultAuth string
|
||||
if extras.EncryptionPreset == constants.PIAEncryptionPresetNormal {
|
||||
defaultCipher = "aes-128-cbc"
|
||||
defaultAuth = "sha1"
|
||||
X509CRL = constants.PiaX509CRLNormal
|
||||
certificate = constants.PIACertificateNormal
|
||||
} else { // strong encryption
|
||||
defaultCipher = aes256cbc
|
||||
defaultAuth = "sha256"
|
||||
X509CRL = constants.PiaX509CRLStrong
|
||||
certificate = constants.PIACertificateStrong
|
||||
}
|
||||
if len(cipher) == 0 {
|
||||
cipher = defaultCipher
|
||||
}
|
||||
if len(auth) == 0 {
|
||||
auth = defaultAuth
|
||||
}
|
||||
lines = []string{
|
||||
"client",
|
||||
"dev tun",
|
||||
"nobind",
|
||||
"persist-key",
|
||||
"remote-cert-tls server",
|
||||
|
||||
// PIA specific
|
||||
"ping 300", // Ping every 5 minutes to prevent a timeout error
|
||||
"reneg-sec 0",
|
||||
"compress", // allow PIA server to choose the compression to use
|
||||
|
||||
// Added constant values
|
||||
"auth-nocache",
|
||||
"mute-replay-warnings",
|
||||
"pull-filter ignore \"auth-token\"", // prevent auth failed loops
|
||||
"auth-retry nointeract",
|
||||
"suppress-timestamps",
|
||||
|
||||
// Modified variables
|
||||
fmt.Sprintf("verb %d", verbosity),
|
||||
fmt.Sprintf("auth-user-pass %s", constants.OpenVPNAuthConf),
|
||||
fmt.Sprintf("proto %s", connection.Protocol),
|
||||
fmt.Sprintf("remote %s %d", connection.IP, connection.Port),
|
||||
fmt.Sprintf("cipher %s", cipher),
|
||||
fmt.Sprintf("auth %s", auth),
|
||||
}
|
||||
if strings.HasSuffix(cipher, "-gcm") {
|
||||
lines = append(lines, "ncp-disable")
|
||||
}
|
||||
if !root {
|
||||
lines = append(lines, "user nonrootuser")
|
||||
}
|
||||
lines = append(lines, []string{
|
||||
"<crl-verify>",
|
||||
"-----BEGIN X509 CRL-----",
|
||||
X509CRL,
|
||||
"-----END X509 CRL-----",
|
||||
"</crl-verify>",
|
||||
}...)
|
||||
lines = append(lines, []string{
|
||||
"<ca>",
|
||||
"-----BEGIN CERTIFICATE-----",
|
||||
certificate,
|
||||
"-----END CERTIFICATE-----",
|
||||
"</ca>",
|
||||
"",
|
||||
}...)
|
||||
return lines
|
||||
}
|
||||
@@ -1,156 +0,0 @@
|
||||
package provider
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/hex"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"math/rand"
|
||||
"net"
|
||||
"net/http"
|
||||
|
||||
"github.com/qdm12/gluetun/internal/constants"
|
||||
"github.com/qdm12/gluetun/internal/firewall"
|
||||
"github.com/qdm12/gluetun/internal/models"
|
||||
"github.com/qdm12/golibs/files"
|
||||
"github.com/qdm12/golibs/logging"
|
||||
)
|
||||
|
||||
type piaV3 struct {
|
||||
servers []models.PIAOldServer
|
||||
randSource rand.Source
|
||||
}
|
||||
|
||||
func newPrivateInternetAccessV3(servers []models.PIAOldServer, timeNow timeNowFunc) *piaV3 {
|
||||
return &piaV3{
|
||||
servers: servers,
|
||||
randSource: rand.NewSource(timeNow().UnixNano()),
|
||||
}
|
||||
}
|
||||
|
||||
func (p *piaV3) GetOpenVPNConnection(selection models.ServerSelection) (
|
||||
connection models.OpenVPNConnection, err error) {
|
||||
var port uint16
|
||||
switch selection.Protocol {
|
||||
case constants.TCP:
|
||||
switch selection.EncryptionPreset {
|
||||
case constants.PIAEncryptionPresetNormal:
|
||||
port = 502
|
||||
case constants.PIAEncryptionPresetStrong:
|
||||
port = 501
|
||||
}
|
||||
case constants.UDP:
|
||||
switch selection.EncryptionPreset {
|
||||
case constants.PIAEncryptionPresetNormal:
|
||||
port = 1198
|
||||
case constants.PIAEncryptionPresetStrong:
|
||||
port = 1197
|
||||
}
|
||||
}
|
||||
if port == 0 {
|
||||
return connection, fmt.Errorf(
|
||||
"combination of protocol %q and encryption %q does not yield any port number",
|
||||
selection.Protocol, selection.EncryptionPreset)
|
||||
}
|
||||
|
||||
if selection.TargetIP != nil {
|
||||
return models.OpenVPNConnection{IP: selection.TargetIP, Port: port, Protocol: selection.Protocol}, nil
|
||||
}
|
||||
|
||||
servers := filterPIAOldServers(p.servers, selection.Regions)
|
||||
if len(servers) == 0 {
|
||||
return connection, fmt.Errorf("no server found for regions %s", commaJoin(selection.Regions))
|
||||
}
|
||||
|
||||
var connections []models.OpenVPNConnection
|
||||
for _, server := range servers {
|
||||
for _, IP := range server.IPs {
|
||||
connections = append(connections, models.OpenVPNConnection{IP: IP, Port: port, Protocol: selection.Protocol})
|
||||
}
|
||||
}
|
||||
|
||||
return pickRandomConnection(connections, p.randSource), nil
|
||||
}
|
||||
|
||||
func (p *piaV3) BuildConf(connection models.OpenVPNConnection, verbosity, uid, gid int,
|
||||
root bool, cipher, auth string, extras models.ExtraConfigOptions) (lines []string) {
|
||||
return buildPIAConf(connection, verbosity, root, cipher, auth, extras)
|
||||
}
|
||||
|
||||
func (p *piaV3) PortForward(ctx context.Context, client *http.Client,
|
||||
fileManager files.FileManager, pfLogger logging.Logger, gateway net.IP, fw firewall.Configurator,
|
||||
syncState func(port uint16) (pfFilepath models.Filepath)) {
|
||||
const uuidLength = 32
|
||||
b := make([]byte, uuidLength)
|
||||
n, err := rand.New(p.randSource).Read(b) //nolint:gosec
|
||||
if err != nil {
|
||||
pfLogger.Error(err)
|
||||
return
|
||||
} else if n != uuidLength {
|
||||
pfLogger.Error("only read %d bytes instead of %d", n, uuidLength)
|
||||
return
|
||||
}
|
||||
clientID := hex.EncodeToString(b)
|
||||
url := fmt.Sprintf("%s/?client_id=%s", constants.PIAPortForwardURL, clientID)
|
||||
request, err := http.NewRequestWithContext(ctx, http.MethodGet, url, nil)
|
||||
if err != nil {
|
||||
pfLogger.Error(err)
|
||||
return
|
||||
}
|
||||
response, err := client.Do(request)
|
||||
if err != nil {
|
||||
pfLogger.Error(err)
|
||||
return
|
||||
}
|
||||
defer response.Body.Close()
|
||||
if response.StatusCode != http.StatusOK {
|
||||
pfLogger.Error("%s for %s; does your PIA server support port forwarding?", response.Status, url)
|
||||
return
|
||||
}
|
||||
b, err = ioutil.ReadAll(response.Body)
|
||||
if err != nil {
|
||||
pfLogger.Error(err)
|
||||
return
|
||||
} else if len(b) == 0 {
|
||||
pfLogger.Error("port forwarding is already activated on this connection, has expired, or you are not connected to a PIA region that supports port forwarding") //nolint:lll
|
||||
return
|
||||
}
|
||||
body := struct {
|
||||
Port uint16 `json:"port"`
|
||||
}{}
|
||||
if err := json.Unmarshal(b, &body); err != nil {
|
||||
pfLogger.Error("port forwarding response: %s", err)
|
||||
return
|
||||
}
|
||||
port := body.Port
|
||||
|
||||
filepath := syncState(port)
|
||||
pfLogger.Info("Writing port to %s", filepath)
|
||||
if err := fileManager.WriteToFile(
|
||||
string(filepath), []byte(fmt.Sprintf("%d", port)),
|
||||
files.Permissions(constants.AllReadWritePermissions),
|
||||
); err != nil {
|
||||
pfLogger.Error(err)
|
||||
}
|
||||
|
||||
if err := fw.SetAllowedPort(ctx, port, string(constants.TUN)); err != nil {
|
||||
pfLogger.Error(err)
|
||||
}
|
||||
|
||||
<-ctx.Done()
|
||||
if err := fw.RemoveAllowedPort(ctx, port); err != nil {
|
||||
pfLogger.Error(err)
|
||||
}
|
||||
}
|
||||
|
||||
func filterPIAOldServers(servers []models.PIAOldServer, regions []string) (filtered []models.PIAOldServer) {
|
||||
for _, server := range servers {
|
||||
switch {
|
||||
case filterByPossibilities(server.Region, regions):
|
||||
default:
|
||||
filtered = append(filtered, server)
|
||||
}
|
||||
}
|
||||
return filtered
|
||||
}
|
||||
@@ -23,7 +23,7 @@ import (
|
||||
"github.com/qdm12/golibs/logging"
|
||||
)
|
||||
|
||||
type piaV4 struct {
|
||||
type pia struct {
|
||||
servers []models.PIAServer
|
||||
timeNow timeNowFunc
|
||||
randSource rand.Source
|
||||
@@ -31,15 +31,15 @@ type piaV4 struct {
|
||||
activeProtocol models.NetworkProtocol
|
||||
}
|
||||
|
||||
func newPrivateInternetAccessV4(servers []models.PIAServer, timeNow timeNowFunc) *piaV4 {
|
||||
return &piaV4{
|
||||
func newPrivateInternetAccess(servers []models.PIAServer, timeNow timeNowFunc) *pia {
|
||||
return &pia{
|
||||
servers: servers,
|
||||
timeNow: timeNow,
|
||||
randSource: rand.NewSource(timeNow().UnixNano()),
|
||||
}
|
||||
}
|
||||
|
||||
func (p *piaV4) GetOpenVPNConnection(selection models.ServerSelection) (
|
||||
func (p *pia) GetOpenVPNConnection(selection models.ServerSelection) (
|
||||
connection models.OpenVPNConnection, err error) {
|
||||
var port uint16
|
||||
switch selection.Protocol {
|
||||
@@ -109,13 +109,80 @@ func (p *piaV4) GetOpenVPNConnection(selection models.ServerSelection) (
|
||||
return connection, nil
|
||||
}
|
||||
|
||||
func (p *piaV4) BuildConf(connection models.OpenVPNConnection, verbosity, uid, gid int, root bool,
|
||||
func (p *pia) BuildConf(connection models.OpenVPNConnection, verbosity int, username string, root bool,
|
||||
cipher, auth string, extras models.ExtraConfigOptions) (lines []string) {
|
||||
return buildPIAConf(connection, verbosity, root, cipher, auth, extras)
|
||||
var X509CRL, certificate string
|
||||
var defaultCipher, defaultAuth string
|
||||
if extras.EncryptionPreset == constants.PIAEncryptionPresetNormal {
|
||||
defaultCipher = "aes-128-cbc"
|
||||
defaultAuth = "sha1"
|
||||
X509CRL = constants.PiaX509CRLNormal
|
||||
certificate = constants.PIACertificateNormal
|
||||
} else { // strong encryption
|
||||
defaultCipher = aes256cbc
|
||||
defaultAuth = "sha256"
|
||||
X509CRL = constants.PiaX509CRLStrong
|
||||
certificate = constants.PIACertificateStrong
|
||||
}
|
||||
if len(cipher) == 0 {
|
||||
cipher = defaultCipher
|
||||
}
|
||||
if len(auth) == 0 {
|
||||
auth = defaultAuth
|
||||
}
|
||||
lines = []string{
|
||||
"client",
|
||||
"dev tun",
|
||||
"nobind",
|
||||
"persist-key",
|
||||
"remote-cert-tls server",
|
||||
|
||||
// PIA specific
|
||||
"ping 300", // Ping every 5 minutes to prevent a timeout error
|
||||
"reneg-sec 0",
|
||||
"compress", // allow PIA server to choose the compression to use
|
||||
|
||||
// Added constant values
|
||||
"auth-nocache",
|
||||
"mute-replay-warnings",
|
||||
"pull-filter ignore \"auth-token\"", // prevent auth failed loops
|
||||
"auth-retry nointeract",
|
||||
"suppress-timestamps",
|
||||
|
||||
// Modified variables
|
||||
fmt.Sprintf("verb %d", verbosity),
|
||||
fmt.Sprintf("auth-user-pass %s", constants.OpenVPNAuthConf),
|
||||
fmt.Sprintf("proto %s", connection.Protocol),
|
||||
fmt.Sprintf("remote %s %d", connection.IP, connection.Port),
|
||||
fmt.Sprintf("cipher %s", cipher),
|
||||
fmt.Sprintf("auth %s", auth),
|
||||
}
|
||||
if strings.HasSuffix(cipher, "-gcm") {
|
||||
lines = append(lines, "ncp-disable")
|
||||
}
|
||||
if !root {
|
||||
lines = append(lines, "user "+username)
|
||||
}
|
||||
lines = append(lines, []string{
|
||||
"<crl-verify>",
|
||||
"-----BEGIN X509 CRL-----",
|
||||
X509CRL,
|
||||
"-----END X509 CRL-----",
|
||||
"</crl-verify>",
|
||||
}...)
|
||||
lines = append(lines, []string{
|
||||
"<ca>",
|
||||
"-----BEGIN CERTIFICATE-----",
|
||||
certificate,
|
||||
"-----END CERTIFICATE-----",
|
||||
"</ca>",
|
||||
"",
|
||||
}...)
|
||||
return lines
|
||||
}
|
||||
|
||||
//nolint:gocognit
|
||||
func (p *piaV4) PortForward(ctx context.Context, client *http.Client,
|
||||
func (p *pia) PortForward(ctx context.Context, client *http.Client,
|
||||
fileManager files.FileManager, pfLogger logging.Logger, gateway net.IP, fw firewall.Configurator,
|
||||
syncState func(port uint16) (pfFilepath models.Filepath)) {
|
||||
if !p.activeServer.PortForward {
|
||||
@@ -130,7 +197,7 @@ func (p *piaV4) PortForward(ctx context.Context, client *http.Client,
|
||||
if p.activeProtocol == constants.TCP {
|
||||
commonName = p.activeServer.OpenvpnTCP.CN
|
||||
}
|
||||
client, err := newPIAv4HTTPClient(commonName)
|
||||
client, err := newPIAHTTPClient(commonName)
|
||||
if err != nil {
|
||||
pfLogger.Error("aborting because: %s", err)
|
||||
return
|
||||
@@ -260,7 +327,7 @@ func filterPIAServers(servers []models.PIAServer, regions []string) (filtered []
|
||||
return filtered
|
||||
}
|
||||
|
||||
func newPIAv4HTTPClient(serverName string) (client *http.Client, err error) {
|
||||
func newPIAHTTPClient(serverName string) (client *http.Client, err error) {
|
||||
certificateBytes, err := base64.StdEncoding.DecodeString(constants.PIACertificateStrong)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("cannot decode PIA root certificate: %w", err)
|
||||
|
||||
123
internal/provider/privado.go
Normal file
123
internal/provider/privado.go
Normal file
@@ -0,0 +1,123 @@
|
||||
package provider
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"math/rand"
|
||||
"net"
|
||||
"net/http"
|
||||
|
||||
"github.com/qdm12/gluetun/internal/constants"
|
||||
"github.com/qdm12/gluetun/internal/firewall"
|
||||
"github.com/qdm12/gluetun/internal/models"
|
||||
"github.com/qdm12/golibs/files"
|
||||
"github.com/qdm12/golibs/logging"
|
||||
)
|
||||
|
||||
type privado struct {
|
||||
servers []models.PrivadoServer
|
||||
randSource rand.Source
|
||||
}
|
||||
|
||||
func newPrivado(servers []models.PrivadoServer, timeNow timeNowFunc) *privado {
|
||||
return &privado{
|
||||
servers: servers,
|
||||
randSource: rand.NewSource(timeNow().UnixNano()),
|
||||
}
|
||||
}
|
||||
|
||||
func (s *privado) filterServers(hostnames []string) (servers []models.PrivadoServer) {
|
||||
for _, server := range s.servers {
|
||||
switch {
|
||||
case filterByPossibilities(server.Hostname, hostnames):
|
||||
default:
|
||||
servers = append(servers, server)
|
||||
}
|
||||
}
|
||||
return servers
|
||||
}
|
||||
|
||||
func (s *privado) GetOpenVPNConnection(selection models.ServerSelection) (
|
||||
connection models.OpenVPNConnection, err error) {
|
||||
var port uint16 = 1194
|
||||
switch selection.Protocol {
|
||||
case constants.UDP:
|
||||
default:
|
||||
return connection, fmt.Errorf("protocol %q is not supported by Privado", selection.Protocol)
|
||||
}
|
||||
|
||||
if selection.TargetIP != nil {
|
||||
return models.OpenVPNConnection{IP: selection.TargetIP, Port: port, Protocol: selection.Protocol}, nil
|
||||
}
|
||||
|
||||
servers := s.filterServers(selection.Hostnames)
|
||||
if len(servers) == 0 {
|
||||
return connection, fmt.Errorf("no server found for cities %s and server numbers %v",
|
||||
commaJoin(selection.Cities), selection.Numbers)
|
||||
}
|
||||
|
||||
connections := make([]models.OpenVPNConnection, len(servers))
|
||||
for i := range servers {
|
||||
connection := models.OpenVPNConnection{
|
||||
IP: servers[i].IP,
|
||||
Port: port,
|
||||
Protocol: selection.Protocol,
|
||||
Hostname: servers[i].Hostname,
|
||||
}
|
||||
connections = append(connections, connection)
|
||||
}
|
||||
|
||||
return pickRandomConnection(connections, s.randSource), nil
|
||||
}
|
||||
|
||||
func (s *privado) BuildConf(connection models.OpenVPNConnection, verbosity int, username string, root bool,
|
||||
cipher, auth string, extras models.ExtraConfigOptions) (lines []string) {
|
||||
if len(cipher) == 0 {
|
||||
cipher = aes256cbc
|
||||
}
|
||||
if len(auth) == 0 {
|
||||
auth = sha256
|
||||
}
|
||||
lines = []string{
|
||||
"client",
|
||||
"dev tun",
|
||||
"nobind",
|
||||
"persist-key",
|
||||
|
||||
// Privado specific
|
||||
"tls-cipher TLS-DHE-RSA-WITH-AES-256-CBC-SHA:TLS-DHE-DSS-WITH-AES-256-CBC-SHA:TLS-RSA-WITH-AES-256-CBC-SHA",
|
||||
fmt.Sprintf("verify-x509-name %s name", connection.Hostname),
|
||||
|
||||
// Added constant values
|
||||
"auth-nocache",
|
||||
"mute-replay-warnings",
|
||||
"pull-filter ignore \"auth-token\"", // prevent auth failed loops
|
||||
"auth-retry nointeract",
|
||||
"suppress-timestamps",
|
||||
|
||||
// Modified variables
|
||||
fmt.Sprintf("verb %d", verbosity),
|
||||
fmt.Sprintf("auth-user-pass %s", constants.OpenVPNAuthConf),
|
||||
fmt.Sprintf("proto %s", connection.Protocol),
|
||||
fmt.Sprintf("remote %s %d", connection.IP, connection.Port),
|
||||
fmt.Sprintf("cipher %s", cipher),
|
||||
fmt.Sprintf("auth %s", auth),
|
||||
}
|
||||
if !root {
|
||||
lines = append(lines, "user "+username)
|
||||
}
|
||||
lines = append(lines, []string{
|
||||
"<ca>",
|
||||
"-----BEGIN CERTIFICATE-----",
|
||||
constants.PrivadoCertificate,
|
||||
"-----END CERTIFICATE-----",
|
||||
"</ca>",
|
||||
}...)
|
||||
return lines
|
||||
}
|
||||
|
||||
func (s *privado) PortForward(ctx context.Context, client *http.Client,
|
||||
fileManager files.FileManager, pfLogger logging.Logger, gateway net.IP, fw firewall.Configurator,
|
||||
syncState func(port uint16) (pfFilepath models.Filepath)) {
|
||||
panic("port forwarding is not supported for privado")
|
||||
}
|
||||
@@ -15,7 +15,7 @@ import (
|
||||
// Provider contains methods to read and modify the openvpn configuration to connect as a client.
|
||||
type Provider interface {
|
||||
GetOpenVPNConnection(selection models.ServerSelection) (connection models.OpenVPNConnection, err error)
|
||||
BuildConf(connection models.OpenVPNConnection, verbosity, uid, gid int,
|
||||
BuildConf(connection models.OpenVPNConnection, verbosity int, username string,
|
||||
root bool, cipher, auth string, extras models.ExtraConfigOptions) (lines []string)
|
||||
PortForward(ctx context.Context, client *http.Client,
|
||||
fileManager files.FileManager, pfLogger logging.Logger, gateway net.IP, fw firewall.Configurator,
|
||||
@@ -25,9 +25,7 @@ type Provider interface {
|
||||
func New(provider models.VPNProvider, allServers models.AllServers, timeNow timeNowFunc) Provider {
|
||||
switch provider {
|
||||
case constants.PrivateInternetAccess:
|
||||
return newPrivateInternetAccessV4(allServers.Pia.Servers, timeNow)
|
||||
case constants.PrivateInternetAccessOld:
|
||||
return newPrivateInternetAccessV3(allServers.PiaOld.Servers, timeNow)
|
||||
return newPrivateInternetAccess(allServers.Pia.Servers, timeNow)
|
||||
case constants.Mullvad:
|
||||
return newMullvad(allServers.Mullvad.Servers, timeNow)
|
||||
case constants.Windscribe:
|
||||
@@ -42,6 +40,8 @@ func New(provider models.VPNProvider, allServers models.AllServers, timeNow time
|
||||
return newNordvpn(allServers.Nordvpn.Servers, timeNow)
|
||||
case constants.Purevpn:
|
||||
return newPurevpn(allServers.Purevpn.Servers, timeNow)
|
||||
case constants.Privado:
|
||||
return newPrivado(allServers.Privado.Servers, timeNow)
|
||||
default:
|
||||
return nil // should never occur
|
||||
}
|
||||
|
||||
@@ -72,7 +72,7 @@ func (p *purevpn) GetOpenVPNConnection(selection models.ServerSelection) (
|
||||
return pickRandomConnection(connections, p.randSource), nil
|
||||
}
|
||||
|
||||
func (p *purevpn) BuildConf(connection models.OpenVPNConnection, verbosity, uid, gid int, root bool,
|
||||
func (p *purevpn) BuildConf(connection models.OpenVPNConnection, verbosity int, username string, root bool,
|
||||
cipher, auth string, extras models.ExtraConfigOptions) (lines []string) {
|
||||
if len(cipher) == 0 {
|
||||
cipher = aes256cbc
|
||||
@@ -108,7 +108,7 @@ func (p *purevpn) BuildConf(connection models.OpenVPNConnection, verbosity, uid,
|
||||
fmt.Sprintf("cipher %s", cipher),
|
||||
}
|
||||
if !root {
|
||||
lines = append(lines, "user nonrootuser")
|
||||
lines = append(lines, "user "+username)
|
||||
}
|
||||
lines = append(lines, []string{
|
||||
"<ca>",
|
||||
|
||||
@@ -73,7 +73,7 @@ func (s *surfshark) GetOpenVPNConnection(selection models.ServerSelection) (
|
||||
return pickRandomConnection(connections, s.randSource), nil
|
||||
}
|
||||
|
||||
func (s *surfshark) BuildConf(connection models.OpenVPNConnection, verbosity, uid, gid int, root bool,
|
||||
func (s *surfshark) BuildConf(connection models.OpenVPNConnection, verbosity int, username string, root bool,
|
||||
cipher, auth string, extras models.ExtraConfigOptions) (lines []string) {
|
||||
if len(cipher) == 0 {
|
||||
cipher = aes256cbc
|
||||
@@ -104,6 +104,7 @@ func (s *surfshark) BuildConf(connection models.OpenVPNConnection, verbosity, ui
|
||||
"auth-nocache",
|
||||
"mute-replay-warnings",
|
||||
"pull-filter ignore \"auth-token\"", // prevent auth failed loops
|
||||
"pull-filter ignore \"block-outside-dns\"",
|
||||
"auth-retry nointeract",
|
||||
"suppress-timestamps",
|
||||
|
||||
@@ -116,7 +117,7 @@ func (s *surfshark) BuildConf(connection models.OpenVPNConnection, verbosity, ui
|
||||
fmt.Sprintf("auth %s", auth),
|
||||
}
|
||||
if !root {
|
||||
lines = append(lines, "user nonrootuser")
|
||||
lines = append(lines, "user "+username)
|
||||
}
|
||||
lines = append(lines, []string{
|
||||
"<ca>",
|
||||
|
||||
@@ -69,7 +69,7 @@ func (v *vyprvpn) GetOpenVPNConnection(selection models.ServerSelection) (
|
||||
return pickRandomConnection(connections, v.randSource), nil
|
||||
}
|
||||
|
||||
func (v *vyprvpn) BuildConf(connection models.OpenVPNConnection, verbosity, uid, gid int,
|
||||
func (v *vyprvpn) BuildConf(connection models.OpenVPNConnection, verbosity int, username string,
|
||||
root bool, cipher, auth string, extras models.ExtraConfigOptions) (lines []string) {
|
||||
if len(cipher) == 0 {
|
||||
cipher = aes256cbc
|
||||
@@ -106,7 +106,7 @@ func (v *vyprvpn) BuildConf(connection models.OpenVPNConnection, verbosity, uid,
|
||||
fmt.Sprintf("auth %s", auth),
|
||||
}
|
||||
if !root {
|
||||
lines = append(lines, "user nonrootuser")
|
||||
lines = append(lines, "user "+username)
|
||||
}
|
||||
lines = append(lines, []string{
|
||||
"<ca>",
|
||||
|
||||
@@ -27,11 +27,13 @@ func newWindscribe(servers []models.WindscribeServer, timeNow timeNowFunc) *wind
|
||||
}
|
||||
}
|
||||
|
||||
func (w *windscribe) filterServers(regions []string) (servers []models.WindscribeServer) {
|
||||
func (w *windscribe) filterServers(regions, cities, hostnames []string) (servers []models.WindscribeServer) {
|
||||
for _, server := range w.servers {
|
||||
switch {
|
||||
case
|
||||
filterByPossibilities(server.Region, regions):
|
||||
filterByPossibilities(server.Region, regions),
|
||||
filterByPossibilities(server.City, cities),
|
||||
filterByPossibilities(server.Hostname, hostnames):
|
||||
default:
|
||||
servers = append(servers, server)
|
||||
}
|
||||
@@ -57,22 +59,20 @@ func (w *windscribe) GetOpenVPNConnection(selection models.ServerSelection) (con
|
||||
return models.OpenVPNConnection{IP: selection.TargetIP, Port: port, Protocol: selection.Protocol}, nil
|
||||
}
|
||||
|
||||
servers := w.filterServers(selection.Regions)
|
||||
servers := w.filterServers(selection.Regions, selection.Cities, selection.Hostnames)
|
||||
if len(servers) == 0 {
|
||||
return connection, fmt.Errorf("no server found for region %s", commaJoin(selection.Regions))
|
||||
}
|
||||
|
||||
var connections []models.OpenVPNConnection
|
||||
connections := make([]models.OpenVPNConnection, len(servers))
|
||||
for _, server := range servers {
|
||||
for _, IP := range server.IPs {
|
||||
connections = append(connections, models.OpenVPNConnection{IP: IP, Port: port, Protocol: selection.Protocol})
|
||||
}
|
||||
connections = append(connections, models.OpenVPNConnection{IP: server.IP, Port: port, Protocol: selection.Protocol})
|
||||
}
|
||||
|
||||
return pickRandomConnection(connections, w.randSource), nil
|
||||
}
|
||||
|
||||
func (w *windscribe) BuildConf(connection models.OpenVPNConnection, verbosity, uid, gid int,
|
||||
func (w *windscribe) BuildConf(connection models.OpenVPNConnection, verbosity int, username string,
|
||||
root bool, cipher, auth string, extras models.ExtraConfigOptions) (lines []string) {
|
||||
if len(cipher) == 0 {
|
||||
cipher = aes256cbc
|
||||
@@ -111,7 +111,7 @@ func (w *windscribe) BuildConf(connection models.OpenVPNConnection, verbosity, u
|
||||
lines = append(lines, "ncp-ciphers AES-256-GCM:AES-256-CBC:AES-128-GCM")
|
||||
}
|
||||
if !root {
|
||||
lines = append(lines, "user nonrootuser")
|
||||
lines = append(lines, "user "+username)
|
||||
}
|
||||
lines = append(lines, []string{
|
||||
"<ca>",
|
||||
|
||||
@@ -2,10 +2,13 @@ package publicip
|
||||
|
||||
import (
|
||||
"context"
|
||||
"net"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/qdm12/gluetun/internal/constants"
|
||||
"github.com/qdm12/gluetun/internal/models"
|
||||
"github.com/qdm12/gluetun/internal/settings"
|
||||
"github.com/qdm12/golibs/files"
|
||||
"github.com/qdm12/golibs/logging"
|
||||
"github.com/qdm12/golibs/network"
|
||||
@@ -14,62 +17,57 @@ import (
|
||||
type Looper interface {
|
||||
Run(ctx context.Context, wg *sync.WaitGroup)
|
||||
RunRestartTicker(ctx context.Context, wg *sync.WaitGroup)
|
||||
Restart()
|
||||
Stop()
|
||||
GetPeriod() (period time.Duration)
|
||||
SetPeriod(period time.Duration)
|
||||
GetStatus() (status models.LoopStatus)
|
||||
SetStatus(status models.LoopStatus) (outcome string, err error)
|
||||
GetSettings() (settings settings.PublicIP)
|
||||
SetSettings(settings settings.PublicIP) (outcome string)
|
||||
GetPublicIP() (publicIP net.IP)
|
||||
}
|
||||
|
||||
type looper struct {
|
||||
period time.Duration
|
||||
periodMutex sync.RWMutex
|
||||
getter IPGetter
|
||||
logger logging.Logger
|
||||
fileManager files.FileManager
|
||||
ipStatusFilepath models.Filepath
|
||||
uid int
|
||||
gid int
|
||||
restart chan struct{}
|
||||
stop chan struct{}
|
||||
updateTicker chan struct{}
|
||||
timeNow func() time.Time
|
||||
timeSince func(time.Time) time.Duration
|
||||
state state
|
||||
// Objects
|
||||
getter IPGetter
|
||||
logger logging.Logger
|
||||
fileManager files.FileManager
|
||||
// Fixed settings
|
||||
uid int
|
||||
gid int
|
||||
// Internal channels and locks
|
||||
loopLock sync.Mutex
|
||||
start chan struct{}
|
||||
running chan models.LoopStatus
|
||||
stop chan struct{}
|
||||
stopped chan struct{}
|
||||
updateTicker chan struct{}
|
||||
// Mock functions
|
||||
timeNow func() time.Time
|
||||
timeSince func(time.Time) time.Duration
|
||||
}
|
||||
|
||||
func NewLooper(client network.Client, logger logging.Logger, fileManager files.FileManager,
|
||||
ipStatusFilepath models.Filepath, period time.Duration, uid, gid int) Looper {
|
||||
settings settings.PublicIP, uid, gid int) Looper {
|
||||
return &looper{
|
||||
period: period,
|
||||
getter: NewIPGetter(client),
|
||||
logger: logger.WithPrefix("ip getter: "),
|
||||
fileManager: fileManager,
|
||||
ipStatusFilepath: ipStatusFilepath,
|
||||
uid: uid,
|
||||
gid: gid,
|
||||
restart: make(chan struct{}),
|
||||
stop: make(chan struct{}),
|
||||
updateTicker: make(chan struct{}),
|
||||
timeNow: time.Now,
|
||||
timeSince: time.Since,
|
||||
state: state{
|
||||
status: constants.Stopped,
|
||||
settings: settings,
|
||||
},
|
||||
// Objects
|
||||
getter: NewIPGetter(client),
|
||||
logger: logger.WithPrefix("ip getter: "),
|
||||
fileManager: fileManager,
|
||||
uid: uid,
|
||||
gid: gid,
|
||||
start: make(chan struct{}),
|
||||
running: make(chan models.LoopStatus),
|
||||
stop: make(chan struct{}),
|
||||
stopped: make(chan struct{}),
|
||||
updateTicker: make(chan struct{}),
|
||||
timeNow: time.Now,
|
||||
timeSince: time.Since,
|
||||
}
|
||||
}
|
||||
|
||||
func (l *looper) Restart() { l.restart <- struct{}{} }
|
||||
func (l *looper) Stop() { l.stop <- struct{}{} }
|
||||
|
||||
func (l *looper) GetPeriod() (period time.Duration) {
|
||||
l.periodMutex.RLock()
|
||||
defer l.periodMutex.RUnlock()
|
||||
return l.period
|
||||
}
|
||||
|
||||
func (l *looper) SetPeriod(period time.Duration) {
|
||||
l.periodMutex.Lock()
|
||||
l.period = period
|
||||
l.periodMutex.Unlock()
|
||||
l.updateTicker <- struct{}{}
|
||||
}
|
||||
|
||||
func (l *looper) logAndWait(ctx context.Context, err error) {
|
||||
l.logger.Error(err)
|
||||
const waitTime = 5 * time.Second
|
||||
@@ -86,53 +84,84 @@ func (l *looper) logAndWait(ctx context.Context, err error) {
|
||||
|
||||
func (l *looper) Run(ctx context.Context, wg *sync.WaitGroup) {
|
||||
defer wg.Done()
|
||||
|
||||
crashed := false
|
||||
|
||||
select {
|
||||
case <-l.restart:
|
||||
case <-l.start:
|
||||
case <-ctx.Done():
|
||||
return
|
||||
}
|
||||
defer l.logger.Warn("loop exited")
|
||||
|
||||
enabled := true
|
||||
|
||||
for ctx.Err() == nil {
|
||||
for !enabled {
|
||||
// wait for a signal to re-enable
|
||||
select {
|
||||
case <-l.stop:
|
||||
l.logger.Info("already disabled")
|
||||
case <-l.restart:
|
||||
enabled = true
|
||||
case <-ctx.Done():
|
||||
getCtx, getCancel := context.WithCancel(ctx)
|
||||
defer getCancel()
|
||||
|
||||
ipCh := make(chan net.IP)
|
||||
errorCh := make(chan error)
|
||||
go func() {
|
||||
ip, err := l.getter.Get(getCtx)
|
||||
if err != nil {
|
||||
errorCh <- err
|
||||
return
|
||||
}
|
||||
ipCh <- ip
|
||||
}()
|
||||
|
||||
if !crashed {
|
||||
l.running <- constants.Running
|
||||
crashed = false
|
||||
} else {
|
||||
l.state.setStatusWithLock(constants.Running)
|
||||
}
|
||||
|
||||
// Enabled and has a period set
|
||||
|
||||
ip, err := l.getter.Get(ctx)
|
||||
if err != nil {
|
||||
l.logAndWait(ctx, err)
|
||||
continue
|
||||
}
|
||||
l.logger.Info("Public IP address is %s", ip)
|
||||
const userReadWritePermissions = 0600
|
||||
err = l.fileManager.WriteLinesToFile(
|
||||
string(l.ipStatusFilepath),
|
||||
[]string{ip.String()},
|
||||
files.Ownership(l.uid, l.gid),
|
||||
files.Permissions(userReadWritePermissions))
|
||||
if err != nil {
|
||||
l.logAndWait(ctx, err)
|
||||
continue
|
||||
}
|
||||
select {
|
||||
case <-l.restart: // triggered restart
|
||||
case <-l.stop:
|
||||
enabled = false
|
||||
case <-ctx.Done():
|
||||
return
|
||||
stayHere := true
|
||||
for stayHere {
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
l.logger.Warn("context canceled: exiting loop")
|
||||
getCancel()
|
||||
close(errorCh)
|
||||
filepath := l.GetSettings().IPFilepath
|
||||
l.logger.Info("Removing ip file %s", filepath)
|
||||
if err := l.fileManager.Remove(string(filepath)); err != nil {
|
||||
l.logger.Error(err)
|
||||
}
|
||||
return
|
||||
case <-l.start:
|
||||
l.logger.Info("starting")
|
||||
getCancel()
|
||||
stayHere = false
|
||||
case <-l.stop:
|
||||
l.logger.Info("stopping")
|
||||
getCancel()
|
||||
<-errorCh
|
||||
l.stopped <- struct{}{}
|
||||
case ip := <-ipCh:
|
||||
getCancel()
|
||||
l.state.setPublicIP(ip)
|
||||
l.logger.Info("Public IP address is %s", ip)
|
||||
const userReadWritePermissions = 0600
|
||||
err := l.fileManager.WriteLinesToFile(
|
||||
string(l.state.settings.IPFilepath),
|
||||
[]string{ip.String()},
|
||||
files.Ownership(l.uid, l.gid),
|
||||
files.Permissions(userReadWritePermissions))
|
||||
if err != nil {
|
||||
l.logger.Error(err)
|
||||
}
|
||||
l.state.setStatusWithLock(constants.Completed)
|
||||
case err := <-errorCh:
|
||||
getCancel()
|
||||
close(ipCh)
|
||||
l.state.setStatusWithLock(constants.Crashed)
|
||||
l.logAndWait(ctx, err)
|
||||
crashed = true
|
||||
stayHere = false
|
||||
}
|
||||
}
|
||||
close(errorCh)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -141,10 +170,9 @@ func (l *looper) RunRestartTicker(ctx context.Context, wg *sync.WaitGroup) {
|
||||
timer := time.NewTimer(time.Hour)
|
||||
timer.Stop() // 1 hour, cannot be a race condition
|
||||
timerIsStopped := true
|
||||
period := l.GetPeriod()
|
||||
if period > 0 {
|
||||
timer.Reset(period)
|
||||
if period := l.GetSettings().Period; period > 0 {
|
||||
timerIsStopped = false
|
||||
timer.Reset(period)
|
||||
}
|
||||
lastTick := time.Unix(0, 0)
|
||||
for {
|
||||
@@ -156,14 +184,14 @@ func (l *looper) RunRestartTicker(ctx context.Context, wg *sync.WaitGroup) {
|
||||
return
|
||||
case <-timer.C:
|
||||
lastTick = l.timeNow()
|
||||
l.restart <- struct{}{}
|
||||
timer.Reset(l.GetPeriod())
|
||||
l.start <- struct{}{}
|
||||
timer.Reset(l.GetSettings().Period)
|
||||
case <-l.updateTicker:
|
||||
if !timer.Stop() {
|
||||
if !timerIsStopped && !timer.Stop() {
|
||||
<-timer.C
|
||||
}
|
||||
timerIsStopped = true
|
||||
period := l.GetPeriod()
|
||||
period := l.GetSettings().Period
|
||||
if period == 0 {
|
||||
continue
|
||||
}
|
||||
|
||||
110
internal/publicip/state.go
Normal file
110
internal/publicip/state.go
Normal file
@@ -0,0 +1,110 @@
|
||||
package publicip
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net"
|
||||
"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.PublicIP
|
||||
ip net.IP
|
||||
statusMu sync.RWMutex
|
||||
settingsMu sync.RWMutex
|
||||
ipMu 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.PublicIP) {
|
||||
l.state.settingsMu.RLock()
|
||||
defer l.state.settingsMu.RUnlock()
|
||||
return l.state.settings
|
||||
}
|
||||
|
||||
func (l *looper) SetSettings(settings settings.PublicIP) (outcome string) {
|
||||
l.state.settingsMu.Lock()
|
||||
defer l.state.settingsMu.Unlock()
|
||||
settingsUnchanged := reflect.DeepEqual(settings, l.state.settings)
|
||||
if settingsUnchanged {
|
||||
return "settings left unchanged"
|
||||
}
|
||||
periodChanged := l.state.settings.Period != settings.Period
|
||||
l.state.settings = settings
|
||||
if periodChanged {
|
||||
l.updateTicker <- struct{}{}
|
||||
// TODO blocking
|
||||
}
|
||||
return "settings updated"
|
||||
}
|
||||
|
||||
func (l *looper) GetPublicIP() (publicIP net.IP) {
|
||||
l.state.ipMu.RLock()
|
||||
defer l.state.ipMu.RUnlock()
|
||||
publicIP = make(net.IP, len(l.state.ip))
|
||||
copy(publicIP, l.state.ip)
|
||||
return publicIP
|
||||
}
|
||||
|
||||
func (s *state) setPublicIP(publicIP net.IP) {
|
||||
s.ipMu.Lock()
|
||||
defer s.ipMu.Unlock()
|
||||
s.ip = make(net.IP, len(publicIP))
|
||||
copy(s.ip, publicIP)
|
||||
}
|
||||
@@ -128,7 +128,7 @@ func (r *routing) VPNDestinationIP() (ip net.IP, err error) {
|
||||
for _, route := range routes {
|
||||
if route.LinkIndex == defaultLinkIndex &&
|
||||
route.Dst != nil &&
|
||||
!ipIsPrivate(route.Dst.IP) &&
|
||||
!IPIsPrivate(route.Dst.IP) &&
|
||||
bytes.Equal(route.Dst.Mask, net.IPMask{255, 255, 255, 255}) {
|
||||
return route.Dst.IP, nil
|
||||
}
|
||||
@@ -156,7 +156,7 @@ func (r *routing) VPNLocalGatewayIP() (ip net.IP, err error) {
|
||||
return nil, fmt.Errorf("cannot find VPN local gateway IP address from ip routes")
|
||||
}
|
||||
|
||||
func ipIsPrivate(ip net.IP) bool {
|
||||
func IPIsPrivate(ip net.IP) bool {
|
||||
if ip.IsLoopback() || ip.IsLinkLocalUnicast() || ip.IsLinkLocalMulticast() {
|
||||
return true
|
||||
}
|
||||
|
||||
76
internal/server/dns.go
Normal file
76
internal/server/dns.go
Normal file
@@ -0,0 +1,76 @@
|
||||
//nolint:dupl
|
||||
package server
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"net/http"
|
||||
"strings"
|
||||
|
||||
"github.com/qdm12/gluetun/internal/dns"
|
||||
"github.com/qdm12/golibs/logging"
|
||||
)
|
||||
|
||||
func newDNSHandler(looper dns.Looper, logger logging.Logger) http.Handler {
|
||||
return &dnsHandler{
|
||||
looper: looper,
|
||||
logger: logger,
|
||||
}
|
||||
}
|
||||
|
||||
type dnsHandler struct {
|
||||
looper dns.Looper
|
||||
logger logging.Logger
|
||||
}
|
||||
|
||||
func (h *dnsHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||
r.RequestURI = strings.TrimPrefix(r.RequestURI, "/dns")
|
||||
switch r.RequestURI {
|
||||
case "/status": //nolint:goconst
|
||||
switch r.Method {
|
||||
case http.MethodGet:
|
||||
h.getStatus(w)
|
||||
case http.MethodPut:
|
||||
h.setStatus(w, r)
|
||||
default:
|
||||
http.Error(w, "", http.StatusNotFound)
|
||||
}
|
||||
default:
|
||||
http.Error(w, "", http.StatusNotFound)
|
||||
}
|
||||
}
|
||||
|
||||
func (h *dnsHandler) getStatus(w http.ResponseWriter) {
|
||||
status := h.looper.GetStatus()
|
||||
encoder := json.NewEncoder(w)
|
||||
data := statusWrapper{Status: string(status)}
|
||||
if err := encoder.Encode(data); err != nil {
|
||||
h.logger.Warn(err)
|
||||
w.WriteHeader(http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
func (h *dnsHandler) setStatus(w http.ResponseWriter, r *http.Request) {
|
||||
decoder := json.NewDecoder(r.Body)
|
||||
var data statusWrapper
|
||||
if err := decoder.Decode(&data); err != nil {
|
||||
http.Error(w, err.Error(), http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
status, err := data.getStatus()
|
||||
if err != nil {
|
||||
http.Error(w, err.Error(), http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
outcome, err := h.looper.SetStatus(status)
|
||||
if err != nil {
|
||||
http.Error(w, err.Error(), http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
encoder := json.NewEncoder(w)
|
||||
if err := encoder.Encode(outcomeWrapper{Outcome: outcome}); err != nil {
|
||||
h.logger.Warn(err)
|
||||
http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
}
|
||||
52
internal/server/handler.go
Normal file
52
internal/server/handler.go
Normal file
@@ -0,0 +1,52 @@
|
||||
package server
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"strings"
|
||||
|
||||
"github.com/qdm12/gluetun/internal/dns"
|
||||
"github.com/qdm12/gluetun/internal/models"
|
||||
"github.com/qdm12/gluetun/internal/openvpn"
|
||||
"github.com/qdm12/gluetun/internal/publicip"
|
||||
"github.com/qdm12/gluetun/internal/updater"
|
||||
"github.com/qdm12/golibs/logging"
|
||||
)
|
||||
|
||||
func newHandler(logger logging.Logger, logging bool,
|
||||
buildInfo models.BuildInformation,
|
||||
openvpnLooper openvpn.Looper,
|
||||
unboundLooper dns.Looper,
|
||||
updaterLooper updater.Looper,
|
||||
publicIPLooper publicip.Looper,
|
||||
) http.Handler {
|
||||
handler := &handler{}
|
||||
|
||||
openvpn := newOpenvpnHandler(openvpnLooper, logger)
|
||||
dns := newDNSHandler(unboundLooper, logger)
|
||||
updater := newUpdaterHandler(updaterLooper, logger)
|
||||
publicip := newPublicIPHandler(publicIPLooper, logger)
|
||||
|
||||
handler.v0 = newHandlerV0(logger, openvpnLooper, unboundLooper, updaterLooper)
|
||||
handler.v1 = newHandlerV1(logger, buildInfo, openvpn, dns, updater, publicip)
|
||||
|
||||
handlerWithLog := withLogMiddleware(handler, logger, logging)
|
||||
handler.setLogEnabled = handlerWithLog.setEnabled
|
||||
|
||||
return handlerWithLog
|
||||
}
|
||||
|
||||
type handler struct {
|
||||
v0 http.Handler
|
||||
v1 http.Handler
|
||||
setLogEnabled func(enabled bool)
|
||||
}
|
||||
|
||||
func (h *handler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||
r.RequestURI = strings.TrimSuffix(r.RequestURI, "/")
|
||||
if !strings.HasPrefix(r.RequestURI, "/v1/") && r.RequestURI != "/v1" {
|
||||
h.v0.ServeHTTP(w, r)
|
||||
return
|
||||
}
|
||||
r.RequestURI = strings.TrimPrefix(r.RequestURI, "/v1")
|
||||
h.v1.ServeHTTP(w, r)
|
||||
}
|
||||
69
internal/server/handlerv0.go
Normal file
69
internal/server/handlerv0.go
Normal file
@@ -0,0 +1,69 @@
|
||||
package server
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
"github.com/qdm12/gluetun/internal/constants"
|
||||
"github.com/qdm12/gluetun/internal/dns"
|
||||
"github.com/qdm12/gluetun/internal/openvpn"
|
||||
"github.com/qdm12/gluetun/internal/updater"
|
||||
"github.com/qdm12/golibs/logging"
|
||||
)
|
||||
|
||||
func newHandlerV0(logger logging.Logger,
|
||||
openvpn openvpn.Looper, dns dns.Looper, updater updater.Looper) http.Handler {
|
||||
return &handlerV0{
|
||||
logger: logger,
|
||||
openvpn: openvpn,
|
||||
dns: dns,
|
||||
updater: updater,
|
||||
}
|
||||
}
|
||||
|
||||
type handlerV0 struct {
|
||||
logger logging.Logger
|
||||
openvpn openvpn.Looper
|
||||
dns dns.Looper
|
||||
updater updater.Looper
|
||||
}
|
||||
|
||||
func (h *handlerV0) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||
if r.Method != http.MethodGet {
|
||||
http.Error(w, "unversioned API: only supports GET method", http.StatusNotFound)
|
||||
return
|
||||
}
|
||||
switch r.RequestURI {
|
||||
case "/version":
|
||||
http.Redirect(w, r, "/v1/version", http.StatusPermanentRedirect)
|
||||
case "/openvpn/actions/restart":
|
||||
outcome, _ := h.openvpn.SetStatus(constants.Stopped)
|
||||
h.logger.Info("openvpn: %s", outcome)
|
||||
outcome, _ = h.openvpn.SetStatus(constants.Running)
|
||||
h.logger.Info("openvpn: %s", outcome)
|
||||
if _, err := w.Write([]byte("openvpn restarted, please consider using the /v1/ API in the future.")); err != nil {
|
||||
h.logger.Warn(err)
|
||||
}
|
||||
case "/unbound/actions/restart":
|
||||
outcome, _ := h.dns.SetStatus(constants.Stopped)
|
||||
h.logger.Info("dns: %s", outcome)
|
||||
outcome, _ = h.dns.SetStatus(constants.Running)
|
||||
h.logger.Info("dns: %s", outcome)
|
||||
if _, err := w.Write([]byte("dns restarted, please consider using the /v1/ API in the future.")); err != nil {
|
||||
h.logger.Warn(err)
|
||||
}
|
||||
case "/openvpn/portforwarded":
|
||||
http.Redirect(w, r, "/v1/openvpn/portforwarded", http.StatusPermanentRedirect)
|
||||
case "/openvpn/settings":
|
||||
http.Redirect(w, r, "/v1/openvpn/settings", http.StatusPermanentRedirect)
|
||||
case "/updater/restart":
|
||||
outcome, _ := h.updater.SetStatus(constants.Stopped)
|
||||
h.logger.Info("updater: %s", outcome)
|
||||
outcome, _ = h.updater.SetStatus(constants.Running)
|
||||
h.logger.Info("updater: %s", outcome)
|
||||
if _, err := w.Write([]byte("updater restarted, please consider using the /v1/ API in the future.")); err != nil {
|
||||
h.logger.Warn(err)
|
||||
}
|
||||
default:
|
||||
http.Error(w, "unversioned API: requested URI not found", http.StatusNotFound)
|
||||
}
|
||||
}
|
||||
62
internal/server/handlerv1.go
Normal file
62
internal/server/handlerv1.go
Normal file
@@ -0,0 +1,62 @@
|
||||
package server
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"strings"
|
||||
|
||||
"github.com/qdm12/gluetun/internal/models"
|
||||
"github.com/qdm12/golibs/logging"
|
||||
)
|
||||
|
||||
func newHandlerV1(logger logging.Logger, buildInfo models.BuildInformation,
|
||||
openvpn, dns, updater, publicip http.Handler) http.Handler {
|
||||
return &handlerV1{
|
||||
logger: logger,
|
||||
buildInfo: buildInfo,
|
||||
openvpn: openvpn,
|
||||
dns: dns,
|
||||
updater: updater,
|
||||
publicip: publicip,
|
||||
}
|
||||
}
|
||||
|
||||
type handlerV1 struct {
|
||||
logger logging.Logger
|
||||
buildInfo models.BuildInformation
|
||||
openvpn http.Handler
|
||||
dns http.Handler
|
||||
updater http.Handler
|
||||
publicip http.Handler
|
||||
}
|
||||
|
||||
func (h *handlerV1) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||
switch {
|
||||
case r.RequestURI == "/version" && r.Method == http.MethodGet:
|
||||
h.getVersion(w)
|
||||
case strings.HasPrefix(r.RequestURI, "/openvpn"):
|
||||
h.openvpn.ServeHTTP(w, r)
|
||||
case strings.HasPrefix(r.RequestURI, "/dns"):
|
||||
h.dns.ServeHTTP(w, r)
|
||||
case strings.HasPrefix(r.RequestURI, "/updater"):
|
||||
h.updater.ServeHTTP(w, r)
|
||||
case strings.HasPrefix(r.RequestURI, "/publicip"):
|
||||
h.publicip.ServeHTTP(w, r)
|
||||
default:
|
||||
errString := fmt.Sprintf("%s %s not found", r.Method, r.RequestURI)
|
||||
http.Error(w, errString, http.StatusNotFound)
|
||||
}
|
||||
}
|
||||
|
||||
func (h *handlerV1) getVersion(w http.ResponseWriter) {
|
||||
data, err := json.Marshal(h.buildInfo)
|
||||
if err != nil {
|
||||
h.logger.Warn(err)
|
||||
w.WriteHeader(http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
if _, err := w.Write(data); err != nil {
|
||||
h.logger.Warn(err)
|
||||
}
|
||||
}
|
||||
75
internal/server/log.go
Normal file
75
internal/server/log.go
Normal file
@@ -0,0 +1,75 @@
|
||||
package server
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/qdm12/golibs/logging"
|
||||
)
|
||||
|
||||
func withLogMiddleware(childHandler http.Handler, logger logging.Logger, enabled bool) *logMiddleware {
|
||||
return &logMiddleware{
|
||||
childHandler: childHandler,
|
||||
logger: logger,
|
||||
timeNow: time.Now,
|
||||
enabled: enabled,
|
||||
}
|
||||
}
|
||||
|
||||
type logMiddleware struct {
|
||||
childHandler http.Handler
|
||||
logger logging.Logger
|
||||
timeNow func() time.Time
|
||||
enabled bool
|
||||
enabledMu sync.RWMutex
|
||||
}
|
||||
|
||||
func (m *logMiddleware) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||
if !m.isEnabled() {
|
||||
m.childHandler.ServeHTTP(w, r)
|
||||
return
|
||||
}
|
||||
tStart := m.timeNow()
|
||||
statefulWriter := &statefulResponseWriter{httpWriter: w}
|
||||
m.childHandler.ServeHTTP(statefulWriter, r)
|
||||
duration := m.timeNow().Sub(tStart)
|
||||
m.logger.Info("%d %s %s wrote %dB to %s in %s",
|
||||
statefulWriter.statusCode, r.Method, r.RequestURI, statefulWriter.length, r.RemoteAddr, duration)
|
||||
}
|
||||
|
||||
func (m *logMiddleware) setEnabled(enabled bool) {
|
||||
m.enabledMu.Lock()
|
||||
defer m.enabledMu.Unlock()
|
||||
m.enabled = enabled
|
||||
}
|
||||
|
||||
func (m *logMiddleware) isEnabled() (enabled bool) {
|
||||
m.enabledMu.RLock()
|
||||
defer m.enabledMu.RUnlock()
|
||||
return m.enabled
|
||||
}
|
||||
|
||||
type statefulResponseWriter struct {
|
||||
httpWriter http.ResponseWriter
|
||||
statusCode int
|
||||
length int
|
||||
}
|
||||
|
||||
func (w *statefulResponseWriter) Write(b []byte) (n int, err error) {
|
||||
n, err = w.httpWriter.Write(b)
|
||||
if w.statusCode == 0 {
|
||||
w.statusCode = http.StatusOK
|
||||
}
|
||||
w.length += n
|
||||
return n, err
|
||||
}
|
||||
|
||||
func (w *statefulResponseWriter) WriteHeader(statusCode int) {
|
||||
w.statusCode = statusCode
|
||||
w.httpWriter.WriteHeader(statusCode)
|
||||
}
|
||||
|
||||
func (w *statefulResponseWriter) Header() http.Header {
|
||||
return w.httpWriter.Header()
|
||||
}
|
||||
@@ -3,34 +3,110 @@ package server
|
||||
import (
|
||||
"encoding/json"
|
||||
"net/http"
|
||||
"strings"
|
||||
|
||||
"github.com/qdm12/gluetun/internal/openvpn"
|
||||
"github.com/qdm12/golibs/logging"
|
||||
)
|
||||
|
||||
func (s *server) handleGetPortForwarded(w http.ResponseWriter) {
|
||||
port := s.openvpnLooper.GetPortForwarded()
|
||||
data, err := json.Marshal(struct {
|
||||
Port uint16 `json:"port"`
|
||||
}{port})
|
||||
if err != nil {
|
||||
s.logger.Warn(err)
|
||||
w.WriteHeader(http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
if _, err := w.Write(data); err != nil {
|
||||
s.logger.Warn(err)
|
||||
w.WriteHeader(http.StatusInternalServerError)
|
||||
func newOpenvpnHandler(looper openvpn.Looper, logger logging.Logger) http.Handler {
|
||||
return &openvpnHandler{
|
||||
looper: looper,
|
||||
logger: logger,
|
||||
}
|
||||
}
|
||||
|
||||
func (s *server) handleGetOpenvpnSettings(w http.ResponseWriter) {
|
||||
settings := s.openvpnLooper.GetSettings()
|
||||
data, err := json.Marshal(settings)
|
||||
if err != nil {
|
||||
s.logger.Warn(err)
|
||||
type openvpnHandler struct {
|
||||
looper openvpn.Looper
|
||||
logger logging.Logger
|
||||
}
|
||||
|
||||
func (h *openvpnHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||
r.RequestURI = strings.TrimPrefix(r.RequestURI, "/openvpn")
|
||||
switch r.RequestURI {
|
||||
case "/status":
|
||||
switch r.Method {
|
||||
case http.MethodGet:
|
||||
h.getStatus(w)
|
||||
case http.MethodPut:
|
||||
h.setStatus(w, r)
|
||||
default:
|
||||
http.Error(w, "", http.StatusNotFound)
|
||||
}
|
||||
case "/settings":
|
||||
switch r.Method {
|
||||
case http.MethodGet:
|
||||
h.getSettings(w)
|
||||
default:
|
||||
http.Error(w, "", http.StatusNotFound)
|
||||
}
|
||||
case "/portforwarded":
|
||||
switch r.Method {
|
||||
case http.MethodGet:
|
||||
h.getPortForwarded(w)
|
||||
default:
|
||||
http.Error(w, "", http.StatusNotFound)
|
||||
}
|
||||
default:
|
||||
http.Error(w, "", http.StatusNotFound)
|
||||
}
|
||||
}
|
||||
|
||||
func (h *openvpnHandler) getStatus(w http.ResponseWriter) {
|
||||
status := h.looper.GetStatus()
|
||||
encoder := json.NewEncoder(w)
|
||||
data := statusWrapper{Status: string(status)}
|
||||
if err := encoder.Encode(data); err != nil {
|
||||
h.logger.Warn(err)
|
||||
w.WriteHeader(http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
if _, err := w.Write(data); err != nil {
|
||||
s.logger.Warn(err)
|
||||
w.WriteHeader(http.StatusInternalServerError)
|
||||
}
|
||||
|
||||
func (h *openvpnHandler) setStatus(w http.ResponseWriter, r *http.Request) { //nolint:dupl
|
||||
decoder := json.NewDecoder(r.Body)
|
||||
var data statusWrapper
|
||||
if err := decoder.Decode(&data); err != nil {
|
||||
http.Error(w, err.Error(), http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
status, err := data.getStatus()
|
||||
if err != nil {
|
||||
http.Error(w, err.Error(), http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
outcome, err := h.looper.SetStatus(status)
|
||||
if err != nil {
|
||||
http.Error(w, err.Error(), http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
encoder := json.NewEncoder(w)
|
||||
if err := encoder.Encode(outcomeWrapper{Outcome: outcome}); err != nil {
|
||||
h.logger.Warn(err)
|
||||
http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
func (h *openvpnHandler) getSettings(w http.ResponseWriter) {
|
||||
settings := h.looper.GetSettings()
|
||||
settings.User = "redacted"
|
||||
settings.Password = "redacted"
|
||||
encoder := json.NewEncoder(w)
|
||||
if err := encoder.Encode(settings); err != nil {
|
||||
h.logger.Warn(err)
|
||||
w.WriteHeader(http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
func (h *openvpnHandler) getPortForwarded(w http.ResponseWriter) {
|
||||
port := h.looper.GetPortForwarded()
|
||||
encoder := json.NewEncoder(w)
|
||||
data := portWrapper{Port: port}
|
||||
if err := encoder.Encode(data); err != nil {
|
||||
h.logger.Warn(err)
|
||||
w.WriteHeader(http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
55
internal/server/publicip.go
Normal file
55
internal/server/publicip.go
Normal file
@@ -0,0 +1,55 @@
|
||||
//nolint:dupl
|
||||
package server
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"net/http"
|
||||
"strings"
|
||||
|
||||
"github.com/qdm12/gluetun/internal/publicip"
|
||||
"github.com/qdm12/golibs/logging"
|
||||
)
|
||||
|
||||
func newPublicIPHandler(
|
||||
looper publicip.Looper,
|
||||
logger logging.Logger) http.Handler {
|
||||
return &publicIPHandler{
|
||||
looper: looper,
|
||||
logger: logger,
|
||||
}
|
||||
}
|
||||
|
||||
type publicIPHandler struct {
|
||||
looper publicip.Looper
|
||||
logger logging.Logger
|
||||
}
|
||||
|
||||
func (h *publicIPHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||
r.RequestURI = strings.TrimPrefix(r.RequestURI, "/publicip")
|
||||
switch r.RequestURI {
|
||||
case "/ip":
|
||||
switch r.Method {
|
||||
case http.MethodGet:
|
||||
h.getPublicIP(w)
|
||||
default:
|
||||
http.Error(w, "", http.StatusNotFound)
|
||||
}
|
||||
default:
|
||||
http.Error(w, "", http.StatusNotFound)
|
||||
}
|
||||
}
|
||||
|
||||
type publicIPWrapper struct {
|
||||
PublicIP string `json:"public_ip"`
|
||||
}
|
||||
|
||||
func (h *publicIPHandler) getPublicIP(w http.ResponseWriter) {
|
||||
publicIP := h.looper.GetPublicIP()
|
||||
encoder := json.NewEncoder(w)
|
||||
data := publicIPWrapper{PublicIP: publicIP.String()}
|
||||
if err := encoder.Encode(data); err != nil {
|
||||
h.logger.Warn(err)
|
||||
w.WriteHeader(http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
}
|
||||
@@ -2,14 +2,14 @@ package server
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"net"
|
||||
"net/http"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/qdm12/gluetun/internal/dns"
|
||||
"github.com/qdm12/gluetun/internal/models"
|
||||
"github.com/qdm12/gluetun/internal/openvpn"
|
||||
"github.com/qdm12/gluetun/internal/publicip"
|
||||
"github.com/qdm12/gluetun/internal/updater"
|
||||
"github.com/qdm12/golibs/logging"
|
||||
)
|
||||
@@ -19,30 +19,27 @@ type Server interface {
|
||||
}
|
||||
|
||||
type server struct {
|
||||
address string
|
||||
logging bool
|
||||
logger logging.Logger
|
||||
openvpnLooper openvpn.Looper
|
||||
unboundLooper dns.Looper
|
||||
updaterLooper updater.Looper
|
||||
lookupIP func(host string) ([]net.IP, error)
|
||||
address string
|
||||
logger logging.Logger
|
||||
handler http.Handler
|
||||
}
|
||||
|
||||
func New(address string, logging bool, logger logging.Logger,
|
||||
openvpnLooper openvpn.Looper, unboundLooper dns.Looper, updaterLooper updater.Looper) Server {
|
||||
buildInfo models.BuildInformation,
|
||||
openvpnLooper openvpn.Looper, unboundLooper dns.Looper,
|
||||
updaterLooper updater.Looper, publicIPLooper publicip.Looper) Server {
|
||||
serverLogger := logger.WithPrefix("http server: ")
|
||||
handler := newHandler(serverLogger, logging, buildInfo,
|
||||
openvpnLooper, unboundLooper, updaterLooper, publicIPLooper)
|
||||
return &server{
|
||||
address: address,
|
||||
logging: logging,
|
||||
logger: logger.WithPrefix("http server: "),
|
||||
openvpnLooper: openvpnLooper,
|
||||
unboundLooper: unboundLooper,
|
||||
updaterLooper: updaterLooper,
|
||||
lookupIP: net.LookupIP,
|
||||
address: address,
|
||||
logger: serverLogger,
|
||||
handler: handler,
|
||||
}
|
||||
}
|
||||
|
||||
func (s *server) Run(ctx context.Context, wg *sync.WaitGroup) {
|
||||
server := http.Server{Addr: s.address, Handler: s.makeHandler()}
|
||||
server := http.Server{Addr: s.address, Handler: s.handler}
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
<-ctx.Done()
|
||||
@@ -61,39 +58,3 @@ func (s *server) Run(ctx context.Context, wg *sync.WaitGroup) {
|
||||
s.logger.Error(err)
|
||||
}
|
||||
}
|
||||
|
||||
func (s *server) makeHandler() http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
s.logger.Info("HTTP %s %s", r.Method, r.RequestURI)
|
||||
switch r.Method {
|
||||
case http.MethodGet:
|
||||
switch r.RequestURI {
|
||||
case "/openvpn/actions/restart":
|
||||
s.openvpnLooper.Restart()
|
||||
w.WriteHeader(http.StatusOK)
|
||||
case "/unbound/actions/restart":
|
||||
s.unboundLooper.Restart()
|
||||
w.WriteHeader(http.StatusOK)
|
||||
case "/openvpn/portforwarded":
|
||||
s.handleGetPortForwarded(w)
|
||||
case "/openvpn/settings":
|
||||
s.handleGetOpenvpnSettings(w)
|
||||
case "/updater/restart":
|
||||
s.updaterLooper.Restart()
|
||||
w.WriteHeader(http.StatusOK)
|
||||
default:
|
||||
routeDoesNotExist(s.logger, w, r)
|
||||
}
|
||||
default:
|
||||
routeDoesNotExist(s.logger, w, r)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func routeDoesNotExist(logger logging.Logger, w http.ResponseWriter, r *http.Request) {
|
||||
w.WriteHeader(http.StatusBadRequest)
|
||||
_, err := w.Write([]byte(fmt.Sprintf("Nothing here for %s %s", r.Method, r.RequestURI)))
|
||||
if err != nil {
|
||||
logger.Error(err)
|
||||
}
|
||||
}
|
||||
|
||||
78
internal/server/updater.go
Normal file
78
internal/server/updater.go
Normal file
@@ -0,0 +1,78 @@
|
||||
//nolint:dupl
|
||||
package server
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"net/http"
|
||||
"strings"
|
||||
|
||||
"github.com/qdm12/gluetun/internal/updater"
|
||||
"github.com/qdm12/golibs/logging"
|
||||
)
|
||||
|
||||
func newUpdaterHandler(
|
||||
looper updater.Looper,
|
||||
logger logging.Logger) http.Handler {
|
||||
return &updaterHandler{
|
||||
looper: looper,
|
||||
logger: logger,
|
||||
}
|
||||
}
|
||||
|
||||
type updaterHandler struct {
|
||||
looper updater.Looper
|
||||
logger logging.Logger
|
||||
}
|
||||
|
||||
func (h *updaterHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||
r.RequestURI = strings.TrimPrefix(r.RequestURI, "/updater")
|
||||
switch r.RequestURI {
|
||||
case "/status":
|
||||
switch r.Method {
|
||||
case http.MethodGet:
|
||||
h.getStatus(w)
|
||||
case http.MethodPut:
|
||||
h.setStatus(w, r)
|
||||
default:
|
||||
http.Error(w, "", http.StatusNotFound)
|
||||
}
|
||||
default:
|
||||
http.Error(w, "", http.StatusNotFound)
|
||||
}
|
||||
}
|
||||
|
||||
func (h *updaterHandler) getStatus(w http.ResponseWriter) {
|
||||
status := h.looper.GetStatus()
|
||||
encoder := json.NewEncoder(w)
|
||||
data := statusWrapper{Status: string(status)}
|
||||
if err := encoder.Encode(data); err != nil {
|
||||
h.logger.Warn(err)
|
||||
w.WriteHeader(http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
func (h *updaterHandler) setStatus(w http.ResponseWriter, r *http.Request) {
|
||||
decoder := json.NewDecoder(r.Body)
|
||||
var data statusWrapper
|
||||
if err := decoder.Decode(&data); err != nil {
|
||||
http.Error(w, err.Error(), http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
status, err := data.getStatus()
|
||||
if err != nil {
|
||||
http.Error(w, err.Error(), http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
outcome, err := h.looper.SetStatus(status)
|
||||
if err != nil {
|
||||
http.Error(w, err.Error(), http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
encoder := json.NewEncoder(w)
|
||||
if err := encoder.Encode(outcomeWrapper{Outcome: outcome}); err != nil {
|
||||
h.logger.Warn(err)
|
||||
http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user