Compare commits
102 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
1750c5beba | ||
|
|
69713f34b2 | ||
|
|
55801597c6 | ||
|
|
ff3cc98d46 | ||
|
|
79489796ae | ||
|
|
8e495494fd | ||
|
|
1abb716bb6 | ||
|
|
3f012dd7a3 | ||
|
|
bf6bab7963 | ||
|
|
9db10f56ef | ||
|
|
3b91e351b7 | ||
|
|
657937d272 | ||
|
|
d294fbab15 | ||
|
|
cfbf5624e1 | ||
|
|
c833e9a1a8 | ||
|
|
f1b261163b | ||
|
|
4553240601 | ||
|
|
007a4536c7 | ||
|
|
31cf5d4a5a | ||
|
|
3e3bd05c79 | ||
|
|
20deaf2950 | ||
|
|
680aef62ee | ||
|
|
f5eb4887a7 | ||
|
|
dc3452c5b7 | ||
|
|
a67efd1ad1 | ||
|
|
5dcbe79fa8 | ||
|
|
574ac9a603 | ||
|
|
6871444728 | ||
|
|
f4db7e3e53 | ||
|
|
da92b6bfb9 | ||
|
|
d713782fe1 | ||
|
|
02cde5f50b | ||
|
|
c5a7a83d3a | ||
|
|
6655a1a5e6 | ||
|
|
b8cb181070 | ||
|
|
a56471fe73 | ||
|
|
8c769812ae | ||
|
|
f7a842e4ee | ||
|
|
23c0334f68 | ||
|
|
e2ee7a0408 | ||
|
|
8f862b3df7 | ||
|
|
ae1f91a997 | ||
|
|
d4fb76770f | ||
|
|
ea28c791e6 | ||
|
|
251555f859 | ||
|
|
fa7bda7ee4 | ||
|
|
f385c4203a | ||
|
|
1e4243dedb | ||
|
|
5f78ee7b79 | ||
|
|
c6eb5c1785 | ||
|
|
11338b6382 | ||
|
|
6f3a074e00 | ||
|
|
e827079604 | ||
|
|
cf66db8d4b | ||
|
|
25acbf8501 | ||
|
|
e4c7a887d2 | ||
|
|
fb8a615660 | ||
|
|
1d9d49f406 | ||
|
|
0069b59ffe | ||
|
|
d4ba1b1e09 | ||
|
|
3a20b84f3a | ||
|
|
d52fc777ac | ||
|
|
5753a428d8 | ||
|
|
85afef5775 | ||
|
|
b4fc24995c | ||
|
|
5917bb10e4 | ||
|
|
258e150ebf | ||
|
|
96f2b2b617 | ||
|
|
d556db079b | ||
|
|
a811a82329 | ||
|
|
d17a0dae1f | ||
|
|
ef40f2f91b | ||
|
|
a921f9848c | ||
|
|
95ba3261fd | ||
|
|
fe81eb65c2 | ||
|
|
8428714cf5 | ||
|
|
bedf613cff | ||
|
|
e643ce5b99 | ||
|
|
cb64302294 | ||
|
|
8d5f2fec09 | ||
|
|
60e98235ca | ||
|
|
f55fb4055f | ||
|
|
da4e410bb7 | ||
|
|
cdd1f87437 | ||
|
|
7058373916 | ||
|
|
8dd38fd182 | ||
|
|
73479bab26 | ||
|
|
f5366c33bc | ||
|
|
db886163c2 | ||
|
|
91f5338db0 | ||
|
|
82a02287ac | ||
|
|
2dc674559e | ||
|
|
38e713fea2 | ||
|
|
2cbb14c36c | ||
|
|
610e88958e | ||
|
|
bb76477467 | ||
|
|
433a799759 | ||
|
|
22965ccce3 | ||
|
|
4257581f55 | ||
|
|
d60d629105 | ||
|
|
3f721b1717 | ||
|
|
97049bfab4 |
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"
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
.devcontainer
|
||||
.git
|
||||
.github
|
||||
.vscode
|
||||
cmd
|
||||
!cmd/gluetun
|
||||
doc
|
||||
|
||||
2
.github/CONTRIBUTING.md
vendored
2
.github/CONTRIBUTING.md
vendored
@@ -7,7 +7,7 @@ Contributions are [released](https://help.github.com/articles/github-terms-of-se
|
||||
1. [Fork](https://github.com/qdm12/gluetun/fork) and clone the repository
|
||||
1. Create a new branch `git checkout -b my-branch-name`
|
||||
1. Modify the code
|
||||
1. Ensure the docker build succeeds `docker build .`
|
||||
1. Ensure the docker build succeeds `docker build .` (you might need `export DOCKER_BUILDKIT=1`)
|
||||
1. Commit your modifications
|
||||
1. Push to your fork and [submit a pull request](https://github.com/qdm12/gluetun/compare)
|
||||
|
||||
|
||||
2
.github/ISSUE_TEMPLATE/bug.md
vendored
2
.github/ISSUE_TEMPLATE/bug.md
vendored
@@ -1,7 +1,7 @@
|
||||
---
|
||||
name: Bug
|
||||
about: Report a bug
|
||||
title: 'Bug: ...'
|
||||
title: 'Bug: FILL THIS TEXT!'
|
||||
labels: ":bug: bug"
|
||||
assignees: qdm12
|
||||
|
||||
|
||||
2
.github/ISSUE_TEMPLATE/help.md
vendored
2
.github/ISSUE_TEMPLATE/help.md
vendored
@@ -1,7 +1,7 @@
|
||||
---
|
||||
name: Help
|
||||
about: Ask for help
|
||||
title: 'Help: ...'
|
||||
title: 'Help: FILL THIS TEXT!'
|
||||
labels: ":pray: help wanted"
|
||||
assignees:
|
||||
|
||||
|
||||
7
.github/dependabot.yml
vendored
Normal file
7
.github/dependabot.yml
vendored
Normal file
@@ -0,0 +1,7 @@
|
||||
version: 2
|
||||
updates:
|
||||
# Maintain dependencies for GitHub Actions
|
||||
- package-ecosystem: "github-actions"
|
||||
directory: "/"
|
||||
schedule:
|
||||
interval: "daily"
|
||||
52
.github/workflows/branch.yml
vendored
Normal file
52
.github/workflows/branch.yml
vendored
Normal file
@@ -0,0 +1,52 @@
|
||||
name: branch
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- "*"
|
||||
- "!master"
|
||||
paths:
|
||||
- .github/workflows/branch.yml
|
||||
- cmd/**
|
||||
- internal/**
|
||||
- .dockerignore
|
||||
- .golangci.yml
|
||||
- Dockerfile
|
||||
- go.mod
|
||||
- go.sum
|
||||
|
||||
jobs:
|
||||
test:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- env:
|
||||
DOCKER_BUILDKIT: "1"
|
||||
run: docker build --target test .
|
||||
|
||||
lint:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- env:
|
||||
DOCKER_BUILDKIT: "1"
|
||||
run: docker build --target lint .
|
||||
|
||||
docker:
|
||||
runs-on: ubuntu-latest
|
||||
needs: [test, lint]
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- uses: docker/setup-qemu-action@v1
|
||||
- uses: docker/setup-buildx-action@v1
|
||||
- name: Dockerhub login
|
||||
run: echo ${{ secrets.DOCKERHUB_PASSWORD }} | docker login -u qmcgaw --password-stdin 2>&1
|
||||
- name: Docker build
|
||||
run: |
|
||||
docker buildx build \
|
||||
--platform=linux/amd64,linux/386,linux/arm64,linux/arm/v6,linux/arm/v7,linux/s390x,linux/ppc64le \
|
||||
--build-arg BUILD_DATE=`date -u +"%Y-%m-%dT%H:%M:%SZ"` \
|
||||
--build-arg COMMIT=`git rev-parse --short HEAD` \
|
||||
--build-arg VERSION="branch-${GITHUB_REF##*/}" \
|
||||
-t qmcgaw/gluetun:branch-${GITHUB_REF##*/} \
|
||||
--push \
|
||||
.
|
||||
34
.github/workflows/build.yml
vendored
34
.github/workflows/build.yml
vendored
@@ -1,34 +0,0 @@
|
||||
name: Docker build
|
||||
on:
|
||||
pull_request:
|
||||
branches: [master]
|
||||
paths-ignore:
|
||||
- .devcontainer
|
||||
- .github/ISSUE_TEMPLATE
|
||||
- .github/workflows/buildx-release.yml
|
||||
- .github/workflows/buildx-branch.yml
|
||||
- .github/workflows/buildx-latest.yml
|
||||
- .github/workflows/dockerhub-description.yml
|
||||
- .github/workflows/labels.yml
|
||||
- .github/workflows/misspell.yml
|
||||
- .github/CODEOWNERS
|
||||
- .github/CONTRIBUTING.md
|
||||
- .github/FUNDING.yml
|
||||
- .github/labels.yml
|
||||
- .vscode
|
||||
- cmd/ovpnparser
|
||||
- cmd/resolver
|
||||
- doc
|
||||
- .gitignore
|
||||
- docker-compose.yml
|
||||
- LICENSE
|
||||
- README.md
|
||||
- title.svg
|
||||
jobs:
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v2
|
||||
- name: Build image
|
||||
run: docker build .
|
||||
50
.github/workflows/buildx-branch.yml
vendored
50
.github/workflows/buildx-branch.yml
vendored
@@ -1,50 +0,0 @@
|
||||
name: Buildx branch
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- '*'
|
||||
- '*/*'
|
||||
- '!master'
|
||||
paths-ignore:
|
||||
- .devcontainer
|
||||
- .github/ISSUE_TEMPLATE
|
||||
- .github/workflows/build.yml
|
||||
- .github/workflows/buildx-release.yml
|
||||
- .github/workflows/buildx-latest.yml
|
||||
- .github/workflows/dockerhub-description.yml
|
||||
- .github/workflows/labels.yml
|
||||
- .github/workflows/misspell.yml
|
||||
- .github/CODEOWNERS
|
||||
- .github/CONTRIBUTING.md
|
||||
- .github/FUNDING.yml
|
||||
- .github/labels.yml
|
||||
- .vscode
|
||||
- cmd/ovpnparser
|
||||
- cmd/resolver
|
||||
- doc
|
||||
- .gitignore
|
||||
- docker-compose.yml
|
||||
- LICENSE
|
||||
- README.md
|
||||
- title.svg
|
||||
jobs:
|
||||
buildx:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- name: Buildx setup
|
||||
uses: crazy-max/ghaction-docker-buildx@v1
|
||||
- name: Dockerhub login
|
||||
run: echo ${{ secrets.DOCKERHUB_PASSWORD }} | docker login -u qmcgaw --password-stdin 2>&1
|
||||
- name: Run Buildx
|
||||
run: |
|
||||
docker buildx build \
|
||||
--progress plain \
|
||||
--platform=linux/amd64 \
|
||||
--build-arg BUILD_DATE=`date -u +"%Y-%m-%dT%H:%M:%SZ"` \
|
||||
--build-arg COMMIT=`git rev-parse --short HEAD` \
|
||||
--build-arg VERSION=${GITHUB_REF##*/} \
|
||||
-t qmcgaw/private-internet-access:${GITHUB_REF##*/} \
|
||||
--push \
|
||||
.
|
||||
- run: curl -X POST https://hooks.microbadger.com/images/qmcgaw/private-internet-access/tQFy7AxtSUNANPe6aoVChYdsI_I= || exit 0
|
||||
47
.github/workflows/buildx-latest.yml
vendored
47
.github/workflows/buildx-latest.yml
vendored
@@ -1,47 +0,0 @@
|
||||
name: Buildx latest
|
||||
on:
|
||||
push:
|
||||
branches: [master]
|
||||
paths-ignore:
|
||||
- .devcontainer
|
||||
- .github/ISSUE_TEMPLATE
|
||||
- .github/workflows/build.yml
|
||||
- .github/workflows/buildx-branch.yml
|
||||
- .github/workflows/buildx-release.yml
|
||||
- .github/workflows/dockerhub-description.yml
|
||||
- .github/workflows/labels.yml
|
||||
- .github/workflows/misspell.yml
|
||||
- .github/CODEOWNERS
|
||||
- .github/CONTRIBUTING.md
|
||||
- .github/FUNDING.yml
|
||||
- .github/labels.yml
|
||||
- .vscode
|
||||
- cmd/ovpnparser
|
||||
- cmd/resolver
|
||||
- doc
|
||||
- .gitignore
|
||||
- docker-compose.yml
|
||||
- LICENSE
|
||||
- README.md
|
||||
- title.svg
|
||||
jobs:
|
||||
buildx:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- name: Buildx setup
|
||||
uses: crazy-max/ghaction-docker-buildx@v1
|
||||
- name: Dockerhub login
|
||||
run: echo ${{ secrets.DOCKERHUB_PASSWORD }} | docker login -u qmcgaw --password-stdin 2>&1
|
||||
- name: Run Buildx
|
||||
run: |
|
||||
docker buildx build \
|
||||
--progress plain \
|
||||
--platform=linux/amd64,linux/386,linux/arm64,linux/arm/v7,linux/arm/v6 \
|
||||
--build-arg BUILD_DATE=`date -u +"%Y-%m-%dT%H:%M:%SZ"` \
|
||||
--build-arg COMMIT=`git rev-parse --short HEAD` \
|
||||
--build-arg VERSION=latest \
|
||||
-t qmcgaw/private-internet-access:latest \
|
||||
--push \
|
||||
.
|
||||
- run: curl -X POST https://hooks.microbadger.com/images/qmcgaw/private-internet-access/tQFy7AxtSUNANPe6aoVChYdsI_I= || exit 0
|
||||
47
.github/workflows/buildx-release.yml
vendored
47
.github/workflows/buildx-release.yml
vendored
@@ -1,47 +0,0 @@
|
||||
name: Buildx release
|
||||
on:
|
||||
release:
|
||||
types: [published]
|
||||
paths-ignore:
|
||||
- .devcontainer
|
||||
- .github/ISSUE_TEMPLATE
|
||||
- .github/workflows/build.yml
|
||||
- .github/workflows/buildx-branch.yml
|
||||
- .github/workflows/buildx-latest.yml
|
||||
- .github/workflows/dockerhub-description.yml
|
||||
- .github/workflows/labels.yml
|
||||
- .github/workflows/misspell.yml
|
||||
- .github/CODEOWNERS
|
||||
- .github/CONTRIBUTING.md
|
||||
- .github/FUNDING.yml
|
||||
- .github/labels.yml
|
||||
- .vscode
|
||||
- cmd/ovpnparser
|
||||
- cmd/resolver
|
||||
- doc
|
||||
- .gitignore
|
||||
- docker-compose.yml
|
||||
- LICENSE
|
||||
- README.md
|
||||
- title.svg
|
||||
jobs:
|
||||
buildx:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- name: Buildx setup
|
||||
uses: crazy-max/ghaction-docker-buildx@v1
|
||||
- name: Dockerhub login
|
||||
run: echo ${{ secrets.DOCKERHUB_PASSWORD }} | docker login -u qmcgaw --password-stdin 2>&1
|
||||
- name: Run Buildx
|
||||
run: |
|
||||
docker buildx build \
|
||||
--progress plain \
|
||||
--platform=linux/amd64,linux/386,linux/arm64,linux/arm/v7,linux/arm/v6 \
|
||||
--build-arg BUILD_DATE=`date -u +"%Y-%m-%dT%H:%M:%SZ"` \
|
||||
--build-arg COMMIT=`git rev-parse --short HEAD` \
|
||||
--build-arg VERSION=${GITHUB_REF##*/} \
|
||||
-t qmcgaw/private-internet-access:${GITHUB_REF##*/} \
|
||||
--push \
|
||||
.
|
||||
- run: curl -X POST https://hooks.microbadger.com/images/qmcgaw/private-internet-access/tQFy7AxtSUNANPe6aoVChYdsI_I= || exit 0
|
||||
12
.github/workflows/dockerhub-description.yml
vendored
12
.github/workflows/dockerhub-description.yml
vendored
@@ -12,8 +12,10 @@ jobs:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v2
|
||||
- name: Docker Hub Description
|
||||
uses: peter-evans/dockerhub-description@v2.1.0
|
||||
env:
|
||||
DOCKERHUB_USERNAME: qmcgaw
|
||||
DOCKERHUB_PASSWORD: ${{ secrets.DOCKERHUB_PASSWORD }}
|
||||
DOCKERHUB_REPOSITORY: qmcgaw/private-internet-access
|
||||
uses: peter-evans/dockerhub-description@v2.4.1
|
||||
with:
|
||||
username: qmcgaw
|
||||
password: ${{ secrets.DOCKERHUB_PASSWORD }}
|
||||
repository: qmcgaw/gluetun
|
||||
short-description: Lightweight Swiss-knife VPN client to connect to several VPN providers
|
||||
readme-filepath: README.md
|
||||
|
||||
8
.github/workflows/labels.yml
vendored
8
.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
|
||||
@@ -13,6 +13,6 @@ jobs:
|
||||
uses: actions/checkout@v2
|
||||
- name: Labeler
|
||||
if: success()
|
||||
uses: crazy-max/ghaction-github-labeler@v1
|
||||
uses: crazy-max/ghaction-github-labeler@v3.1.1
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
53
.github/workflows/latest.yml
vendored
Normal file
53
.github/workflows/latest.yml
vendored
Normal file
@@ -0,0 +1,53 @@
|
||||
name: latest
|
||||
on:
|
||||
push:
|
||||
branches: [master]
|
||||
paths:
|
||||
- .github/workflows/latest.yml
|
||||
- cmd/**
|
||||
- internal/**
|
||||
- .dockerignore
|
||||
- .golangci.yml
|
||||
- Dockerfile
|
||||
- go.mod
|
||||
- go.sum
|
||||
|
||||
jobs:
|
||||
test:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- env:
|
||||
DOCKER_BUILDKIT: "1"
|
||||
run: docker build --target test .
|
||||
|
||||
lint:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- env:
|
||||
DOCKER_BUILDKIT: "1"
|
||||
run: docker build --target lint .
|
||||
|
||||
docker:
|
||||
runs-on: ubuntu-latest
|
||||
needs: [test, lint]
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- uses: docker/setup-qemu-action@v1
|
||||
- uses: docker/setup-buildx-action@v1
|
||||
- name: Dockerhub login
|
||||
run: echo ${{ secrets.DOCKERHUB_PASSWORD }} | docker login -u qmcgaw --password-stdin 2>&1
|
||||
- name: Docker buildx
|
||||
run: |
|
||||
docker buildx build \
|
||||
--platform=linux/amd64,linux/386,linux/arm64,linux/arm/v6,linux/arm/v7,linux/s390x,linux/ppc64le \
|
||||
--build-arg BUILD_DATE=`date -u +"%Y-%m-%dT%H:%M:%SZ"` \
|
||||
--build-arg COMMIT=`git rev-parse --short HEAD` \
|
||||
--build-arg VERSION=latest \
|
||||
-t qmcgaw/private-internet-access:latest \
|
||||
-t qmcgaw/gluetun:latest \
|
||||
--push \
|
||||
.
|
||||
- run: curl -X POST https://hooks.microbadger.com/images/qmcgaw/private-internet-access/tQFy7AxtSUNANPe6aoVChYdsI_I=
|
||||
continue-on-error: true
|
||||
40
.github/workflows/pr.yml
vendored
Normal file
40
.github/workflows/pr.yml
vendored
Normal file
@@ -0,0 +1,40 @@
|
||||
name: pull request
|
||||
on:
|
||||
pull_request:
|
||||
branches: [master]
|
||||
paths:
|
||||
- .github/workflows/pr.yml
|
||||
- cmd/**
|
||||
- internal/**
|
||||
- .dockerignore
|
||||
- .golangci.yml
|
||||
- Dockerfile
|
||||
- go.mod
|
||||
- go.sum
|
||||
|
||||
jobs:
|
||||
test:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- env:
|
||||
DOCKER_BUILDKIT: "1"
|
||||
run: docker build --target test .
|
||||
|
||||
lint:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- env:
|
||||
DOCKER_BUILDKIT: "1"
|
||||
run: docker build --target lint .
|
||||
|
||||
docker:
|
||||
runs-on: ubuntu-latest
|
||||
needs: [test, lint]
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- name: Docker build
|
||||
env:
|
||||
DOCKER_BUILDKIT: "1"
|
||||
run: docker build .
|
||||
51
.github/workflows/release.yml
vendored
Normal file
51
.github/workflows/release.yml
vendored
Normal file
@@ -0,0 +1,51 @@
|
||||
name: release
|
||||
on:
|
||||
release:
|
||||
types: [published]
|
||||
paths:
|
||||
- .github/workflows/release.yml
|
||||
- cmd/**
|
||||
- internal/**
|
||||
- .dockerignore
|
||||
- .golangci.yml
|
||||
- Dockerfile
|
||||
- go.mod
|
||||
- go.sum
|
||||
|
||||
jobs:
|
||||
test:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- env:
|
||||
DOCKER_BUILDKIT: "1"
|
||||
run: docker build --target test .
|
||||
|
||||
lint:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- env:
|
||||
DOCKER_BUILDKIT: "1"
|
||||
run: docker build --target lint .
|
||||
|
||||
docker:
|
||||
runs-on: ubuntu-latest
|
||||
needs: [test, lint]
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- uses: docker/setup-qemu-action@v1
|
||||
- uses: docker/setup-buildx-action@v1
|
||||
- name: Dockerhub login
|
||||
run: echo ${{ secrets.DOCKERHUB_PASSWORD }} | docker login -u qmcgaw --password-stdin 2>&1
|
||||
- name: Docker buildx
|
||||
run: |
|
||||
docker buildx build \
|
||||
--platform=linux/amd64,linux/386,linux/arm64,linux/arm/v6,linux/arm/v7,linux/s390x,linux/ppc64le \
|
||||
--build-arg BUILD_DATE=`date -u +"%Y-%m-%dT%H:%M:%SZ"` \
|
||||
--build-arg COMMIT=`git rev-parse --short HEAD` \
|
||||
--build-arg VERSION=${GITHUB_REF##*/} \
|
||||
-t qmcgaw/private-internet-access:${GITHUB_REF##*/} \
|
||||
-t qmcgaw/gluetun:${GITHUB_REF##*/} \
|
||||
--push \
|
||||
.
|
||||
@@ -10,7 +10,10 @@ issues:
|
||||
linters:
|
||||
- dupl
|
||||
- maligned
|
||||
|
||||
- path: internal/unix/constants\.go
|
||||
linters:
|
||||
- golint
|
||||
text: don't use ALL_CAPS in Go names; use CamelCase
|
||||
linters:
|
||||
disable-all: true
|
||||
enable:
|
||||
|
||||
9
.vscode/extensions.json
vendored
9
.vscode/extensions.json
vendored
@@ -1,9 +0,0 @@
|
||||
{
|
||||
"recommendations": [
|
||||
"shardulm94.trailing-spaces",
|
||||
"ms-azuretools.vscode-docker",
|
||||
"davidanson.vscode-markdownlint",
|
||||
"IBM.output-colorizer",
|
||||
"golang.go"
|
||||
]
|
||||
}
|
||||
91
.vscode/settings.json
vendored
91
.vscode/settings.json
vendored
@@ -1,91 +0,0 @@
|
||||
{
|
||||
// General settings
|
||||
"files.eol": "\n",
|
||||
// Docker
|
||||
"remote.extensionKind": {
|
||||
"ms-azuretools.vscode-docker": "workspace"
|
||||
},
|
||||
// Golang general settings
|
||||
"go.useLanguageServer": true,
|
||||
"go.autocompleteUnimportedPackages": true,
|
||||
"go.gotoSymbol.includeImports": true,
|
||||
"go.gotoSymbol.includeGoroot": true,
|
||||
"gopls": {
|
||||
"completeUnimported": true,
|
||||
"deepCompletion": true,
|
||||
"usePlaceholders": false
|
||||
},
|
||||
"go.lintTool": "golangci-lint",
|
||||
"go.lintFlags": [
|
||||
"--fast",
|
||||
"--enable",
|
||||
"rowserrcheck",
|
||||
"--enable",
|
||||
"bodyclose",
|
||||
"--enable",
|
||||
"dogsled",
|
||||
"--enable",
|
||||
"dupl",
|
||||
"--enable",
|
||||
"gochecknoglobals",
|
||||
"--enable",
|
||||
"gochecknoinits",
|
||||
"--enable",
|
||||
"gocognit",
|
||||
"--enable",
|
||||
"goconst",
|
||||
"--enable",
|
||||
"gocritic",
|
||||
"--enable",
|
||||
"gocyclo",
|
||||
"--enable",
|
||||
"goimports",
|
||||
"--enable",
|
||||
"golint",
|
||||
"--enable",
|
||||
"gosec",
|
||||
"--enable",
|
||||
"interfacer",
|
||||
"--enable",
|
||||
"maligned",
|
||||
"--enable",
|
||||
"misspell",
|
||||
"--enable",
|
||||
"nakedret",
|
||||
"--enable",
|
||||
"prealloc",
|
||||
"--enable",
|
||||
"scopelint",
|
||||
"--enable",
|
||||
"unconvert",
|
||||
"--enable",
|
||||
"unparam",
|
||||
"--enable",
|
||||
"whitespace"
|
||||
],
|
||||
// Golang on save
|
||||
"go.buildOnSave": "workspace",
|
||||
"go.lintOnSave": "workspace",
|
||||
"go.vetOnSave": "workspace",
|
||||
"editor.formatOnSave": true,
|
||||
"[go]": {
|
||||
"editor.codeActionsOnSave": {
|
||||
"source.organizeImports": true
|
||||
}
|
||||
},
|
||||
// Golang testing
|
||||
"go.toolsEnvVars": {
|
||||
"GOFLAGS": "-tags="
|
||||
},
|
||||
"gopls.env": {
|
||||
"GOFLAGS": "-tags="
|
||||
},
|
||||
"go.testEnvVars": {},
|
||||
"go.testFlags": [
|
||||
"-v",
|
||||
// "-race"
|
||||
],
|
||||
"go.testTimeout": "600s",
|
||||
"go.coverOnSingleTestFile": true,
|
||||
"go.coverOnSingleTest": true
|
||||
}
|
||||
66
Dockerfile
66
Dockerfile
@@ -1,27 +1,42 @@
|
||||
ARG ALPINE_VERSION=3.12
|
||||
ARG GO_VERSION=1.15
|
||||
|
||||
FROM golang:${GO_VERSION}-alpine${ALPINE_VERSION} AS builder
|
||||
FROM --platform=$BUILDPLATFORM golang:${GO_VERSION}-alpine${ALPINE_VERSION} AS base
|
||||
RUN apk --update add git
|
||||
ENV CGO_ENABLED=0
|
||||
ARG GOLANGCI_LINT_VERSION=v1.31.0
|
||||
RUN wget -O- -nv https://raw.githubusercontent.com/golangci/golangci-lint/master/install.sh | sh -s ${GOLANGCI_LINT_VERSION}
|
||||
WORKDIR /tmp/gobuild
|
||||
COPY .golangci.yml .
|
||||
COPY go.mod go.sum ./
|
||||
RUN go mod download
|
||||
COPY cmd/ ./cmd/
|
||||
COPY internal/ ./internal/
|
||||
|
||||
FROM --platform=$BUILDPLATFORM base AS test
|
||||
ENV CGO_ENABLED=1
|
||||
RUN apk --update add g++
|
||||
RUN go test -race ./...
|
||||
|
||||
FROM --platform=$BUILDPLATFORM base AS lint
|
||||
ARG GOLANGCI_LINT_VERSION=v1.34.1
|
||||
RUN wget -O- -nv https://raw.githubusercontent.com/golangci/golangci-lint/master/install.sh | \
|
||||
sh -s -- -b /usr/local/bin ${GOLANGCI_LINT_VERSION}
|
||||
COPY .golangci.yml ./
|
||||
RUN golangci-lint run --timeout=10m
|
||||
|
||||
FROM --platform=$BUILDPLATFORM base AS build
|
||||
COPY --from=qmcgaw/xcputranslate /xcputranslate /usr/local/bin/xcputranslate
|
||||
ARG TARGETPLATFORM
|
||||
ARG VERSION=unknown
|
||||
ARG BUILD_DATE="an unknown date"
|
||||
ARG COMMIT=unknown
|
||||
COPY cmd/gluetun/main.go .
|
||||
COPY cmd/ ./cmd/
|
||||
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
|
||||
RUN GOARCH="$(echo ${TARGETPLATFORM} | xcputranslate -field arch)" \
|
||||
GOARM="$(echo ${TARGETPLATFORM} | xcputranslate -field arm)" \
|
||||
go build -trimpath -ldflags="-s -w \
|
||||
-X 'main.version=$VERSION' \
|
||||
-X 'main.buildDate=$BUILD_DATE' \
|
||||
-X 'main.commit=$COMMIT' \
|
||||
" -o entrypoint cmd/gluetun/main.go
|
||||
|
||||
FROM alpine:${ALPINE_VERSION}
|
||||
ARG VERSION=unknown
|
||||
@@ -35,8 +50,8 @@ LABEL \
|
||||
org.opencontainers.image.url="https://github.com/qdm12/gluetun" \
|
||||
org.opencontainers.image.documentation="https://github.com/qdm12/gluetun" \
|
||||
org.opencontainers.image.source="https://github.com/qdm12/gluetun" \
|
||||
org.opencontainers.image.title="VPN client for PIA, Mullvad, Windscribe, Surfshark and Cyberghost" \
|
||||
org.opencontainers.image.description="VPN client to tunnel to PIA, Mullvad, Windscribe, Surfshark and Cyberghost servers using OpenVPN, IPtables, DNS over TLS and Alpine Linux"
|
||||
org.opencontainers.image.title="VPN swiss-knife like client for multiple VPN providers" \
|
||||
org.opencontainers.image.description="VPN swiss-knife like client to tunnel to multiple VPN servers using OpenVPN, IPtables, DNS over TLS, Shadowsocks, an HTTP proxy and Alpine Linux"
|
||||
ENV VPNSP=pia \
|
||||
VERSION_INFORMATION=on \
|
||||
PROTOCOL=udp \
|
||||
@@ -45,12 +60,14 @@ ENV VPNSP=pia \
|
||||
OPENVPN_TARGET_IP= \
|
||||
OPENVPN_IPV6=off \
|
||||
TZ= \
|
||||
UID=1000 \
|
||||
GID=1000 \
|
||||
IP_STATUS_FILE="/tmp/gluetun/ip" \
|
||||
PUID= \
|
||||
PGID= \
|
||||
PUBLICIP_FILE="/tmp/gluetun/ip" \
|
||||
# PIA, Windscribe, Surfshark, Cyberghost, Vyprvpn, NordVPN, PureVPN only
|
||||
USER= \
|
||||
PASSWORD= \
|
||||
OPENVPN_USER= \
|
||||
OPENVPN_PASSWORD= \
|
||||
USER_SECRETFILE=/run/secrets/openvpn_user \
|
||||
PASSWORD_SECRETFILE=/run/secrets/openvpn_password \
|
||||
REGION= \
|
||||
# PIA only
|
||||
PIA_ENCRYPTION=strong \
|
||||
@@ -61,7 +78,7 @@ ENV VPNSP=pia \
|
||||
# Mullvad, PureVPN, Windscribe only
|
||||
CITY= \
|
||||
# Windscribe only
|
||||
HOSTNAME= \
|
||||
SERVER_HOSTNAME= \
|
||||
# Mullvad only
|
||||
ISP= \
|
||||
OWNED=no \
|
||||
@@ -69,6 +86,8 @@ ENV VPNSP=pia \
|
||||
PORT= \
|
||||
# Cyberghost only
|
||||
CYBERGHOST_GROUP="Premium UDP Europe" \
|
||||
OPENVPN_CLIENTCRT_SECRETFILE=/run/secrets/openvpn_clientcrt \
|
||||
OPENVPN_CLIENTKEY_SECRETFILE=/run/secrets/openvpn_clientkey \
|
||||
# NordVPN only
|
||||
SERVER_NUMBER= \
|
||||
# Openvpn
|
||||
@@ -102,16 +121,19 @@ ENV VPNSP=pia \
|
||||
HTTPPROXY_PORT=8888 \
|
||||
HTTPPROXY_USER= \
|
||||
HTTPPROXY_PASSWORD= \
|
||||
HTTPPROXY_USER_SECRETFILE=/run/secrets/httpproxy_user \
|
||||
HTTPPROXY_PASSWORD_SECRETFILE=/run/secrets/httpproxy_password \
|
||||
# Shadowsocks
|
||||
SHADOWSOCKS=off \
|
||||
SHADOWSOCKS_LOG=off \
|
||||
SHADOWSOCKS_PORT=8388 \
|
||||
SHADOWSOCKS_PASSWORD= \
|
||||
SHADOWSOCKS_PASSWORD_SECRETFILE=/run/secrets/shadowsocks_password \
|
||||
SHADOWSOCKS_METHOD=chacha20-ietf-poly1305 \
|
||||
UPDATER_PERIOD=0
|
||||
ENTRYPOINT ["/entrypoint"]
|
||||
EXPOSE 8000/tcp 8888/tcp 8388/tcp 8388/udp
|
||||
HEALTHCHECK --interval=10m --timeout=10s --start-period=30s --retries=2 CMD /entrypoint healthcheck
|
||||
HEALTHCHECK --interval=5s --timeout=5s --start-period=10s --retries=1 CMD /entrypoint healthcheck
|
||||
RUN apk add -q --progress --no-cache --update openvpn ca-certificates iptables ip6tables unbound tzdata && \
|
||||
rm -rf /var/cache/apk/* /etc/unbound/* /usr/sbin/unbound-* && \
|
||||
deluser openvpn && \
|
||||
@@ -119,4 +141,4 @@ RUN apk add -q --progress --no-cache --update openvpn ca-certificates iptables i
|
||||
mkdir /gluetun
|
||||
# TODO remove once SAN is added to PIA servers certificates, see https://github.com/pia-foss/manual-connections/issues/10
|
||||
ENV GODEBUG=x509ignoreCN=0
|
||||
COPY --from=builder /tmp/gobuild/entrypoint /entrypoint
|
||||
COPY --from=build /tmp/gobuild/entrypoint /entrypoint
|
||||
|
||||
407
README.md
407
README.md
@@ -3,27 +3,36 @@
|
||||
*Lightweight swiss-knife-like VPN client to tunnel to Private Internet Access,
|
||||
Mullvad, Windscribe, Surfshark Cyberghost, VyprVPN, NordVPN, PureVPN and Privado VPN servers, using Go, OpenVPN, iptables, DNS over TLS, ShadowSocks and an HTTP proxy*
|
||||
|
||||
**ANNOUNCEMENT**: *Github Wiki reworked*
|
||||
**ANNOUNCEMENT**: *New Docker image name `qmcgaw/gluetun`*
|
||||
|
||||
<img height="250" src="https://raw.githubusercontent.com/qdm12/gluetun/master/title.svg?sanitize=true">
|
||||
|
||||
[](https://github.com/qdm12/gluetun/actions?query=workflow%3A%22Buildx+latest%22)
|
||||
[](https://hub.docker.com/r/qmcgaw/gluetun/tags?page=1&ordering=last_updated)
|
||||
[](https://hub.docker.com/r/qmcgaw/gluetun/tags)
|
||||
|
||||
[](https://hub.docker.com/r/qmcgaw/private-internet-access)
|
||||
[](https://hub.docker.com/r/qmcgaw/private-internet-access)
|
||||
[](https://hub.docker.com/r/qmcgaw/gluetun)
|
||||
|
||||
[](https://github.com/qdm12/gluetun/issues)
|
||||
[](https://github.com/qdm12/gluetun/issues)
|
||||
[](https://github.com/qdm12/gluetun/issues)
|
||||

|
||||

|
||||

|
||||
|
||||
[](https://microbadger.com/images/qmcgaw/private-internet-access)
|
||||
[](https://microbadger.com/images/qmcgaw/private-internet-access)
|
||||
[](https://join.slack.com/t/qdm12/shared_invite/enQtOTE0NjcxNTM1ODc5LTYyZmVlOTM3MGI4ZWU0YmJkMjUxNmQ4ODQ2OTAwYzMxMTlhY2Q1MWQyOWUyNjc2ODliNjFjMDUxNWNmNzk5MDk)
|
||||

|
||||
[](https://github.com/qdm12/gluetun/commits)
|
||||
|
||||
## Videos
|
||||
[](https://github.com/qdm12/gluetun)
|
||||
|
||||
1. [**Introduction**](https://youtu.be/3jIbU6J2Hs0)
|
||||
1. [**Connect a container**](https://youtu.be/mH7J_2JKNK0)
|
||||
1. [**Connect LAN devices**](https://youtu.be/qvjrM15Y0uk)
|
||||
## Quick links
|
||||
|
||||
- Problem or suggestion?
|
||||
- [Start a discussion](https://github.com/qdm12/gluetun/discussions)
|
||||
- [Create an issue](https://github.com/qdm12/gluetun/issues)
|
||||
- [Check the Wiki](https://github.com/qdm12/gluetun/wiki)
|
||||
- [Join the Slack channel](https://join.slack.com/t/qdm12/shared_invite/enQtOTE0NjcxNTM1ODc5LTYyZmVlOTM3MGI4ZWU0YmJkMjUxNmQ4ODQ2OTAwYzMxMTlhY2Q1MWQyOWUyNjc2ODliNjFjMDUxNWNmNzk5MDk)
|
||||
- Happy?
|
||||
- Sponsor me on [github.com/sponsors/qdm12](https://github.com/sponsors/qdm12)
|
||||
- Donate to [paypal.me/qmcgaw](https://www.paypal.me/qmcgaw)
|
||||
- Drop me [an email](mailto:quentin.mcgaw@gmail.com)
|
||||
|
||||
## Features
|
||||
|
||||
@@ -36,9 +45,9 @@ Mullvad, Windscribe, Surfshark Cyberghost, VyprVPN, NordVPN, PureVPN and Privado
|
||||
- Built in firewall kill switch to allow traffic only with needed the VPN servers and LAN devices
|
||||
- Built in Shadowsocks proxy (protocol based on SOCKS5 with an encryption layer, tunnels TCP+UDP)
|
||||
- Built in HTTP proxy (tunnels HTTP and HTTPS through TCP)
|
||||
- [Connect other containers to it](https://github.com/qdm12/gluetun#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 🎆
|
||||
- [Connect other containers to it](https://github.com/qdm12/gluetun/wiki/Connect-to-gluetun)
|
||||
- [Connect LAN devices to it](https://github.com/qdm12/gluetun/wiki/Connect-to-gluetun)
|
||||
- Compatible with amd64, i686 (32 bit), **ARM** 64 bit, ARM 32 bit v6 and v7, and even s390x as well as ppc64le 🎆
|
||||
- VPN server side port forwarding for Private Internet Access and Vyprvpn
|
||||
- Possibility of split horizon DNS by selecting multiple DNS over TLS providers
|
||||
- Subprograms all drop root privileges once launched
|
||||
@@ -54,352 +63,70 @@ Mullvad, Windscribe, Surfshark Cyberghost, VyprVPN, NordVPN, PureVPN and Privado
|
||||
```bash
|
||||
docker run -d --name gluetun --cap-add=NET_ADMIN \
|
||||
-e VPNSP="private internet access" -e REGION="CA Montreal" \
|
||||
-e USER=js89ds7 -e PASSWORD=8fd9s239G \
|
||||
-e OPENVPN_USER=js89ds7 -e OPENVPN_PASSWORD=8fd9s239G \
|
||||
-v /yourpath:/gluetun \
|
||||
qmcgaw/private-internet-access
|
||||
qmcgaw/gluetun
|
||||
```
|
||||
|
||||
or use [docker-compose.yml](https://github.com/qdm12/gluetun/blob/master/docker-compose.yml) with:
|
||||
|
||||
```bash
|
||||
echo "your openvpn username" > openvpn_user
|
||||
echo "your openvpn password" > openvpn_password
|
||||
docker-compose up -d
|
||||
```
|
||||
|
||||
Note that you can:
|
||||
You should probably check the many [environment variables](https://github.com/qdm12/gluetun/wiki/Environment-variables) available to adapt the container to your needs.
|
||||
|
||||
- Change the many [environment variables](#environment-variables) available
|
||||
- Use `-p 8888:8888/tcp` to access the HTTP web proxy
|
||||
- Use `-p 8388:8388/tcp -p 8388:8388/udp` to access the Shadowsocks proxy
|
||||
- Use `-p 8000:8000/tcp` to access the [HTTP control server](#HTTP-control-server) built-in
|
||||
## Further setup
|
||||
|
||||
**If you encounter an issue with the tun device not being available, see [the FAQ](https://github.com/qdm12/gluetun/blob/master/doc/faq.md#how-to-fix-openvpn-failing-to-start)**
|
||||
The following points are all optional but should give you insights on all the possibilities with this container.
|
||||
|
||||
1. You can update the image with `docker pull qmcgaw/private-internet-access:latest`. See the [wiki](https://github.com/qdm12/gluetun/wiki/Common-issues#use-a-release-tag) for more information on other tags available.
|
||||
- Use [Docker secrets](https://github.com/qdm12/gluetun/wiki/Docker-secrets) to read your credentials instead of environment variables
|
||||
- [Test your setup](https://github.com/qdm12/gluetun/wiki/Test-your-setup)
|
||||
- [How to connect other containers and devices to Gluetun](https://github.com/qdm12/gluetun/wiki/Connect-to-gluetun)
|
||||
- [VPN server side port forwarding](https://github.com/qdm12/gluetun/wiki/Port-forwarding)
|
||||
- [HTTP control server](https://github.com/qdm12/gluetun/wiki/HTTP-Control-server) to automate things, restart Openvpn etc.
|
||||
- Update the image with `docker pull qmcgaw/gluetun:latest`. See this [Wiki document](https://github.com/qdm12/gluetun/wiki/Docker-image-tags) for Docker tags available.
|
||||
|
||||
## Testing
|
||||
## Development
|
||||
|
||||
Check the VPN IP address matches your expectations
|
||||
|
||||
```sh
|
||||
docker run --rm --network=container:gluetun alpine:3.12 wget -qO- https://ipinfo.io
|
||||
```
|
||||
|
||||
▶ [Testing Wiki page](https://github.com/qdm12/gluetun/wiki/Testing-the-setup)
|
||||
|
||||
## Environment variables
|
||||
|
||||
**TLDR**; only set the 🏁 marked environment variables to get started.
|
||||
|
||||
💡 For all server filtering options such as `REGION`, you can have multiple values separated by a comma, i.e. `Germany,Singapore`
|
||||
|
||||
### VPN
|
||||
|
||||
| Variable | Default | Choices | Description |
|
||||
| --- | --- | --- | --- |
|
||||
| 🏁 `VPNSP` | `private internet access` | `private internet access`, `mullvad`, `windscribe`, `surfshark`, `vyprvpn`, `nordvpn`, `purevpn`, `privado` | VPN Service Provider |
|
||||
| `IP_STATUS_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 |
|
||||
| `OPENVPN_TARGET_IP` | | Valid IP address | Specify a target VPN IP address to use |
|
||||
| `OPENVPN_CIPHER` | | i.e. `aes-256-gcm` | Specify a custom cipher to use. It will also set `ncp-disable` if using AES GCM for PIA |
|
||||
| `OPENVPN_AUTH` | | i.e. `sha256` | Specify a custom auth algorithm to use |
|
||||
| `OPENVPN_IPV6` | `off` | `on`, `off` | Enable tunneling of IPv6 (only for Mullvad) |
|
||||
|
||||
*For all providers below, server location parameters are all optional. By default a random server is picked using the filter settings provided.*
|
||||
|
||||
- Private Internet Access
|
||||
|
||||
| Variable | Default | Choices | Description |
|
||||
| --- | --- | --- | --- |
|
||||
| 🏁 `USER` | | | Your username |
|
||||
| 🏁 `PASSWORD` | | | Your password |
|
||||
| `REGION` | | One of the [PIA regions](https://www.privateinternetaccess.com/pages/network/) | VPN server region |
|
||||
| `PIA_ENCRYPTION` | `strong` | `normal`, `strong` | Encryption preset |
|
||||
| `PORT_FORWARDING` | `off` | `on`, `off` | Enable port forwarding on the VPN server |
|
||||
| `PORT_FORWARDING_STATUS_FILE` | `/tmp/gluetun/forwarded_port` | Any filepath | Filepath to store the forwarded port number |
|
||||
|
||||
- Mullvad
|
||||
|
||||
| Variable | Default | Choices | Description |
|
||||
| --- | --- | --- | --- |
|
||||
| 🏁 `USER` | | | Your user ID |
|
||||
| `COUNTRY` | | One of the [Mullvad countries](https://mullvad.net/en/servers/#openvpn) | VPN server country |
|
||||
| `CITY` | | One of the [Mullvad cities](https://mullvad.net/en/servers/#openvpn) | VPN server city |
|
||||
| `ISP` | | One of the [Mullvad ISP](https://mullvad.net/en/servers/#openvpn) | VPN server ISP |
|
||||
| `PORT` | | `80`, `443` or `1401` for TCP; `53`, `1194`, `1195`, `1196`, `1197`, `1300`, `1301`, `1302`, `1303` or `1400` for UDP. Defaults to TCP `443` and UDP `1194` | Custom VPN port to use |
|
||||
| `OWNED` | `no` | `yes` or `no` | If the VPN server is owned by Mullvad |
|
||||
|
||||
💡 [Mullvad IPv6 Wiki page](https://github.com/qdm12/gluetun/wiki/Mullvad-IPv6)
|
||||
|
||||
For **port forwarding**, obtain a port from [here](https://mullvad.net/en/account/#/ports) and add it to `FIREWALL_VPN_INPUT_PORTS`
|
||||
|
||||
- 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` | | | 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
|
||||
|
||||
| Variable | Default | Choices | Description |
|
||||
| --- | --- | --- | --- |
|
||||
| 🏁 `USER` | | | Your **service** username, found at the bottom of the [manual setup page](https://account.surfshark.com/setup/manual) |
|
||||
| 🏁 `PASSWORD` | | | Your **service** password |
|
||||
| `REGION` | | One of the [Surfshark regions](https://github.com/qdm12/gluetun/wiki/Surfshark-Servers) | VPN server region |
|
||||
|
||||
- Cyberghost
|
||||
|
||||
| Variable | Default | Choices | Description |
|
||||
| --- | --- | --- | --- |
|
||||
| 🏁 `USER` | | | Your username |
|
||||
| 🏁 `PASSWORD` | | | Your password |
|
||||
| 🏁 | | | **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 |
|
||||
|
||||
**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:
|
||||
|
||||
```sh
|
||||
-v /yourpath/client.key:/gluetun/client.key:ro -v /yourpath/client.crt:/gluetun/client.crt:ro
|
||||
```
|
||||
|
||||
- Vyprvpn
|
||||
|
||||
| Variable | Default | Choices | Description |
|
||||
| --- | --- | --- | --- |
|
||||
| 🏁 `USER` | | | Your username |
|
||||
| 🏁 `PASSWORD` | | | Your password |
|
||||
| `REGION` | | One of the [VyprVPN regions](https://www.vyprvpn.com/server-locations) | VPN server region |
|
||||
|
||||
For **port forwarding**, add a port you want to be accessible to `FIREWALL_VPN_INPUT_PORTS`
|
||||
|
||||
- NordVPN
|
||||
|
||||
| Variable | Default | Choices | Description |
|
||||
| --- | --- | --- | --- |
|
||||
| 🏁 `USER` | | | Your username |
|
||||
| 🏁 `PASSWORD` | | | Your password |
|
||||
| `REGION` | | One of the NordVPN server country, i.e. `Switzerland` | VPN server country |
|
||||
| `SERVER_NUMBER` | | Server integer number | Optional server number. For example `251` for `Italy #251` |
|
||||
|
||||
- PureVPN
|
||||
|
||||
| Variable | Default | Choices | Description |
|
||||
| --- | --- | --- | --- |
|
||||
| 🏁 `USER` | | | Your user ID |
|
||||
| 🏁 `REGION` | | One of the [PureVPN regions](https://support.purevpn.com/vpn-servers) | VPN server region |
|
||||
| `COUNTRY` | | One of the [PureVPN countries](https://support.purevpn.com/vpn-servers) | VPN server country |
|
||||
| `CITY` | | One of the [PureVPN cities](https://support.purevpn.com/vpn-servers) | VPN server city |
|
||||
|
||||
- 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.
|
||||
|
||||
| Variable | Default | Choices | Description |
|
||||
| --- | --- | --- | --- |
|
||||
| `DOT` | `on` | `on`, `off` | Activate DNS over TLS with Unbound |
|
||||
| `DOT_PROVIDERS` | `cloudflare` | `cloudflare`, `google`, `quad9`, `quadrant`, `cleanbrowsing`, `securedns`, `libredns` | Comma delimited list of DNS over TLS providers |
|
||||
| `DOT_CACHING` | `on` | `on`, `off` | Unbound caching |
|
||||
| `DOT_IPV6` | `off` | `on`, `off` | DNS IPv6 resolution |
|
||||
| `DOT_PRIVATE_ADDRESS` | All private CIDRs ranges | | Comma separated list of CIDRs or single IP addresses Unbound won't resolve to. Note that the default setting prevents DNS rebinding |
|
||||
| `DOT_VERBOSITY` | `1` | `0` to `5` | Unbound verbosity level |
|
||||
| `DOT_VERBOSITY_DETAILS` | `0` | `0` to `4` | Unbound details verbosity level |
|
||||
| `DOT_VALIDATION_LOGLEVEL` | `0` | `0` to `2` | Unbound validation log level |
|
||||
| `DNS_UPDATE_PERIOD` | `24h` | i.e. `0`, `30s`, `5m`, `24h` | Period to update block lists and cryptographic files and restart Unbound. Set to `0` to deactivate updates |
|
||||
| `BLOCK_MALICIOUS` | `on` | `on`, `off` | Block malicious hostnames and IPs with Unbound |
|
||||
| `BLOCK_SURVEILLANCE` | `off` | `on`, `off` | Block surveillance hostnames and IPs with Unbound |
|
||||
| `BLOCK_ADS` | `off` | `on`, `off` | Block ads hostnames and IPs with Unbound |
|
||||
| `UNBLOCK` | |i.e. `domain1.com,x.domain2.co.uk` | Comma separated list of domain names to leave unblocked with Unbound |
|
||||
| `DNS_PLAINTEXT_ADDRESS` | `1.1.1.1` | Any IP address | IP address to use as DNS resolver if `DOT` is `off` |
|
||||
| `DNS_KEEP_NAMESERVER` | `off` | `on` or `off` | Keep the nameservers in /etc/resolv.conf untouched, but disabled DNS blocking features |
|
||||
|
||||
### Firewall and routing
|
||||
|
||||
| Variable | Default | Choices | Description |
|
||||
| --- | --- | --- | --- |
|
||||
| `FIREWALL` | `on` | `on` or `off` | Turn on or off the container built-in firewall. You should use it for **debugging purposes** only. |
|
||||
| `FIREWALL_VPN_INPUT_PORTS` | | i.e. `1000,8080` | Comma separated list of ports to allow from the VPN server side (useful for **vyprvpn** port forwarding) |
|
||||
| `FIREWALL_INPUT_PORTS` | | i.e. `1000,8000` | Comma separated list of ports to allow through the default interface. This seems needed for Kubernetes sidecars. |
|
||||
| `FIREWALL_DEBUG` | `off` | `on` or `off` | Prints every firewall related command. You should use it for **debugging purposes** only. |
|
||||
| `FIREWALL_OUTBOUND_SUBNETS` | | i.e. `192.168.1.0/24,192.168.10.121,10.0.0.5/28` | Comma separated subnets that Gluetun and the containers sharing its network stack are allowed to access. This involves firewall and routing modifications. |
|
||||
|
||||
### Shadowsocks
|
||||
|
||||
| Variable | Default | Choices | Description |
|
||||
| --- | --- | --- | --- |
|
||||
| `SHADOWSOCKS` | `off` | `on`, `off` | Enable the internal Shadowsocks proxy |
|
||||
| `SHADOWSOCKS_LOG` | `off` | `on`, `off` | Enable logging |
|
||||
| `SHADOWSOCKS_PORT` | `8388` | `1024` to `65535` | Internal port number for Shadowsocks to listen on |
|
||||
| `SHADOWSOCKS_PASSWORD` | | | Password to use to connect to Shadowsocks |
|
||||
| `SHADOWSOCKS_METHOD` | `chacha20-ietf-poly1305` | `chacha20-ietf-poly1305`, `aes-128-gcm`, `aes-256-gcm` | Method to use for Shadowsocks |
|
||||
|
||||
### HTTP proxy
|
||||
|
||||
| Variable | Default | Choices | Description |
|
||||
| --- | --- | --- | --- |
|
||||
| `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
|
||||
|
||||
| Variable | Default | Choices | Description |
|
||||
| --- | --- | --- | --- |
|
||||
| `TZ` | | i.e. `Europe/London` | Specify a timezone to use to have correct log times |
|
||||
| `UID` | `1000` | | User ID to run as non root and for ownership of files written |
|
||||
| `GID` | `1000` | | Group ID to run as non root and for ownership of files written |
|
||||
|
||||
### HTTP Control server
|
||||
|
||||
| Variable | Default | Choices | Description |
|
||||
| --- | --- | --- | --- |
|
||||
| `HTTP_CONTROL_SERVER_PORT` | `8000` | `1` to `65535` | Listening port for the HTTP control server |
|
||||
| `HTTP_CONTROL_SERVER_LOG` | `on` | `on` or `off` | Enable logging of HTTP requests |
|
||||
|
||||
### Other
|
||||
|
||||
| Variable | Default | Choices | Description |
|
||||
| --- | --- | --- | --- |
|
||||
| `PUBLICIP_PERIOD` | `12h` | Valid duration | Period to check for public IP address. Set to `0` to disable. |
|
||||
| `VERSION_INFORMATION` | `on` | `on`, `off` | Logs a message indicating if a newer version is available once the VPN is connected |
|
||||
| `UPDATER_PERIOD` | `0` | Valid duration string such as `24h` | Period to update all VPN servers information in memory and to /gluetun/servers.json. Set to `0` to disable. This does a burst of DNS over TLS requests, which may be blocked if you set `BLOCK_MALICIOUS=on` for example. |
|
||||
|
||||
## Connect to it
|
||||
|
||||
There are various ways to achieve this, depending on your use case.
|
||||
|
||||
- <details><summary>Connect containers in the same docker-compose.yml as Gluetun</summary><p>
|
||||
|
||||
Add `network_mode: "service:gluetun"` to your *docker-compose.yml* (no need for `depends_on`)
|
||||
|
||||
</p></details>
|
||||
- <details><summary>Connect other containers to Gluetun</summary><p>
|
||||
|
||||
Add `--network=container:gluetun` when launching the container, provided Gluetun is already running
|
||||
|
||||
</p></details>
|
||||
- <details><summary>Connect containers from another docker-compose.yml</summary><p>
|
||||
|
||||
Add `network_mode: "container:gluetun"` to your *docker-compose.yml*, provided Gluetun is already running
|
||||
|
||||
</p></details>
|
||||
- <details><summary>Connect LAN devices through the built-in HTTP proxy (i.e. with Chrome, Kodi, etc.)</summary><p>
|
||||
|
||||
⚠️ 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 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 `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>
|
||||
|
||||
1. Setup a Shadowsocks proxy client, there is a list of [ShadowSocks clients for **all platforms**](https://shadowsocks.org/en/download/clients.html)
|
||||
- **note** some clients do not tunnel UDP so your DNS queries will be done locally and not through Gluetun and its built in DNS over TLS
|
||||
- Clients that support such UDP tunneling are, as far as I know:
|
||||
- iOS: Potatso Lite
|
||||
- OSX: ShadowsocksX
|
||||
- Android: Shadowsocks by Max Lv
|
||||
1. Ensure the Gluetun container is launched with:
|
||||
- port `8388` published `-p 8388:8388/tcp -p 8388:8388/udp`
|
||||
1. With your Shadowsocks proxy client
|
||||
- Enter the Docker host (i.e. `192.168.1.10`) as the server IP
|
||||
- Enter port TCP (and UDP, if available) `8388` as the server port
|
||||
- Use the password you have set with `SHADOWSOCKS_PASSWORD`
|
||||
- Choose the encryption method/algorithm to the method you specified in `SHADOWSOCKS_METHOD`
|
||||
1. If you set `SHADOWSOCKS_LOG` to `on`, (a lot) more information will be logged in the Docker logs
|
||||
|
||||
</p></details>
|
||||
- <details><summary>Access ports of containers connected to Gluetun</summary><p>
|
||||
|
||||
In example, to access port `8000` of container `xyz` and `9000` of container `abc` connected to Gluetun,
|
||||
publish ports `8000` and `9000` for the Gluetun container and access them as you would with any other container
|
||||
|
||||
</p></details>
|
||||
- <details><summary>Access ports of containers connected to Gluetun, all in the same docker-compose.yml</summary><p>
|
||||
|
||||
In example, to access port `8000` of container `xyz` and `9000` of container `abc` connected to Gluetun, publish port `8000` and `9000` for the Gluetun container.
|
||||
The docker-compose.yml file would look like:
|
||||
|
||||
```yml
|
||||
version: '3.7'
|
||||
services:
|
||||
gluetun:
|
||||
image: qmcgaw/private-internet-access
|
||||
container_name: gluetun
|
||||
cap_add:
|
||||
- NET_ADMIN
|
||||
environment:
|
||||
- USER=js89ds7
|
||||
- PASSWORD=8fd9s239G
|
||||
ports:
|
||||
- 8000:8000/tcp
|
||||
- 9000:9000/tcp
|
||||
abc:
|
||||
image: abc
|
||||
container_name: abc
|
||||
network_mode: "service:gluetun"
|
||||
xyz:
|
||||
image: xyz
|
||||
container_name: xyz
|
||||
network_mode: "service:gluetun"
|
||||
```
|
||||
|
||||
</p></details>
|
||||
|
||||
## Private Internet Access port forwarding
|
||||
|
||||
When `PORT_FORWARDING=on`, a port will be forwarded on the VPN server side and written to the file specified by `PORT_FORWARDING_STATUS_FILE=/tmp/gluetun/forwarded_port`.
|
||||
It can be useful to mount this file as a volume to read it from other containers, for example to configure a torrenting client.
|
||||
|
||||
For `VPNSP=private internet access` (default), you will keep the same forwarded port for 60 days as long as you bind mount the `/gluetun` directory.
|
||||
|
||||
You can also use the HTTP control server (see below) to get the port forwarded.
|
||||
|
||||
## HTTP control server
|
||||
|
||||
[Wiki page](https://github.com/qdm12/gluetun/wiki/HTTP-Control-server)
|
||||
|
||||
## Development and contributing
|
||||
|
||||
- Contribute with code: start with [this Wiki page](https://github.com/qdm12/gluetun/wiki/Developement-setup)
|
||||
- [The list of existing contributors 👍](https://github.com/qdm12/gluetun/blob/master/.github/CONTRIBUTING.md#Contributors)
|
||||
- [Github workflows](https://github.com/qdm12/gluetun/actions) to know what's building
|
||||
- 💻 [Contribute with code](https://github.com/qdm12/gluetun/wiki/Development) ([existing contributors 👍](https://github.com/qdm12/gluetun/blob/master/.github/CONTRIBUTING.md#Contributors))
|
||||
- [List of issues and feature requests](https://github.com/qdm12/gluetun/issues)
|
||||
- [Kanban board](https://github.com/qdm12/gluetun/projects/1)
|
||||
|
||||
## License
|
||||
|
||||
This repository is under an [MIT license](https://github.com/qdm12/gluetun/master/license)
|
||||
[](https://github.com/qdm12/gluetun/master/LICENSE)
|
||||
|
||||
## Support
|
||||
|
||||
Sponsor me on [Github](https://github.com/sponsors/qdm12), donate to [paypal.me/qmcgaw](https://www.paypal.me/qmcgaw) or subscribe to a VPN provider through one of my affiliate links:
|
||||
- Sponsor me on [Github](https://github.com/sponsors/qdm12) or donate to [paypal.me/qmcgaw](https://www.paypal.me/qmcgaw)
|
||||
|
||||
[](https://github.com/sponsors/qdm12)
|
||||
[](https://www.paypal.me/qmcgaw)
|
||||
[](https://github.com/sponsors/qdm12)
|
||||
[](https://www.paypal.me/qmcgaw)
|
||||
|
||||
[](https://windscribe.com/?affid=mh7nyafu)
|
||||
- Contribute to the issues and discussions on Github
|
||||
- Many thanks to @Frepke, @Ralph521, G. Mendez, M. Otmar Weber, J. Perez, A. Cooper and **others** for supporting me financially 🥇👍
|
||||
|
||||
Feel also free to have a look at [the Kanban board](https://github.com/qdm12/gluetun/projects/1) and [contribute](#Development-and-contributing) to the code or the issues discussion.
|
||||
## Metadata
|
||||
|
||||
Many thanks to @Frepke, @Ralph521, G. Mendez, M. Otmar Weber, J. Perez and A. Cooper for supporting me financially 🥇👍
|
||||
[](https://github.com/qdm12/gluetun/commits)
|
||||
[](https://github.com/qdm12/gluetun/pulls?q=is%3Apr+is%3Aclosed)
|
||||
|
||||
[](https://github.com/qdm12/gluetun/issues)
|
||||
[](https://github.com/qdm12/gluetun/issues?q=is%3Aissue+is%3Aclosed)
|
||||
|
||||

|
||||

|
||||

|
||||

|
||||

|
||||
|
||||

|
||||

|
||||
|
||||
[](https://hub.docker.com/r/qmcgaw/gluetun)
|
||||
|
||||

|
||||

|
||||
|
||||
@@ -5,13 +5,14 @@ import (
|
||||
"fmt"
|
||||
"net"
|
||||
"net/http"
|
||||
"os"
|
||||
nativeos "os"
|
||||
"os/signal"
|
||||
"strings"
|
||||
"sync"
|
||||
"syscall"
|
||||
"time"
|
||||
|
||||
"github.com/qdm12/dns/pkg/unbound"
|
||||
"github.com/qdm12/gluetun/internal/alpine"
|
||||
"github.com/qdm12/gluetun/internal/cli"
|
||||
"github.com/qdm12/gluetun/internal/constants"
|
||||
@@ -29,68 +30,120 @@ import (
|
||||
"github.com/qdm12/gluetun/internal/settings"
|
||||
"github.com/qdm12/gluetun/internal/shadowsocks"
|
||||
"github.com/qdm12/gluetun/internal/storage"
|
||||
"github.com/qdm12/gluetun/internal/unix"
|
||||
"github.com/qdm12/gluetun/internal/updater"
|
||||
versionpkg "github.com/qdm12/gluetun/internal/version"
|
||||
"github.com/qdm12/golibs/command"
|
||||
"github.com/qdm12/golibs/files"
|
||||
"github.com/qdm12/golibs/logging"
|
||||
"github.com/qdm12/golibs/network"
|
||||
"github.com/qdm12/golibs/os"
|
||||
"github.com/qdm12/golibs/os/user"
|
||||
"github.com/qdm12/updated/pkg/dnscrypto"
|
||||
)
|
||||
|
||||
//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
|
||||
buildInfo := models.BuildInformation{
|
||||
Version: version,
|
||||
Commit: commit,
|
||||
BuildDate: buildDate,
|
||||
}
|
||||
|
||||
ctx := context.Background()
|
||||
os.Exit(_main(ctx, os.Args))
|
||||
ctx, cancel := context.WithCancel(ctx)
|
||||
|
||||
logger, err := logging.NewLogger(logging.ConsoleEncoding, logging.InfoLevel)
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
nativeos.Exit(1)
|
||||
}
|
||||
|
||||
args := nativeos.Args
|
||||
os := os.New()
|
||||
osUser := user.New()
|
||||
unix := unix.New()
|
||||
cli := cli.New()
|
||||
|
||||
errorCh := make(chan error)
|
||||
go func() {
|
||||
errorCh <- _main(ctx, buildInfo, args, logger, os, osUser, unix, cli)
|
||||
}()
|
||||
|
||||
signalsCh := make(chan nativeos.Signal, 1)
|
||||
signal.Notify(signalsCh,
|
||||
syscall.SIGINT,
|
||||
syscall.SIGTERM,
|
||||
nativeos.Interrupt,
|
||||
)
|
||||
|
||||
select {
|
||||
case signal := <-signalsCh:
|
||||
logger.Warn("Caught OS signal %s, shutting down", signal)
|
||||
case err := <-errorCh:
|
||||
close(errorCh)
|
||||
if err == nil { // expected exit such as healthcheck
|
||||
nativeos.Exit(0)
|
||||
}
|
||||
logger.Error(err)
|
||||
}
|
||||
|
||||
cancel()
|
||||
|
||||
const shutdownGracePeriod = 5 * time.Second
|
||||
timer := time.NewTimer(shutdownGracePeriod)
|
||||
select {
|
||||
case <-errorCh:
|
||||
if !timer.Stop() {
|
||||
<-timer.C
|
||||
}
|
||||
logger.Info("Shutdown successful")
|
||||
case <-timer.C:
|
||||
logger.Warn("Shutdown timed out")
|
||||
}
|
||||
|
||||
nativeos.Exit(1)
|
||||
}
|
||||
|
||||
func _main(background context.Context, args []string) int { //nolint:gocognit,gocyclo
|
||||
//nolint:gocognit,gocyclo
|
||||
func _main(background context.Context, buildInfo models.BuildInformation,
|
||||
args []string, logger logging.Logger, os os.OS, osUser user.OSUser, unix unix.Unix,
|
||||
cli cli.CLI) error {
|
||||
if len(args) > 1 { // cli operation
|
||||
var err error
|
||||
switch args[1] {
|
||||
case "healthcheck":
|
||||
err = cli.HealthCheck(background)
|
||||
return cli.HealthCheck(background)
|
||||
case "clientkey":
|
||||
err = cli.ClientKey(args[2:])
|
||||
return cli.ClientKey(args[2:], os.OpenFile)
|
||||
case "openvpnconfig":
|
||||
err = cli.OpenvpnConfig()
|
||||
return cli.OpenvpnConfig(os)
|
||||
case "update":
|
||||
err = cli.Update(args[2:])
|
||||
return cli.Update(args[2:], os)
|
||||
default:
|
||||
err = fmt.Errorf("command %q is unknown", args[1])
|
||||
return fmt.Errorf("command %q is unknown", args[1])
|
||||
}
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
return 1
|
||||
}
|
||||
return 0
|
||||
}
|
||||
ctx, cancel := context.WithCancel(background)
|
||||
defer cancel()
|
||||
logger := createLogger()
|
||||
|
||||
const clientTimeout = 15 * time.Second
|
||||
httpClient := &http.Client{Timeout: clientTimeout}
|
||||
client := network.NewClient(clientTimeout)
|
||||
// Create configurators
|
||||
fileManager := files.NewFileManager()
|
||||
alpineConf := alpine.NewConfigurator(fileManager)
|
||||
ovpnConf := openvpn.NewConfigurator(logger, fileManager)
|
||||
dnsConf := dns.NewConfigurator(logger, client, fileManager)
|
||||
alpineConf := alpine.NewConfigurator(os.OpenFile, osUser)
|
||||
ovpnConf := openvpn.NewConfigurator(logger, os, unix)
|
||||
dnsCrypto := dnscrypto.New(httpClient, "", "")
|
||||
const cacertsPath = "/etc/ssl/certs/ca-certificates.crt"
|
||||
dnsConf := unbound.NewConfigurator(logger, os.OpenFile, dnsCrypto,
|
||||
"/etc/unbound", "/usr/sbin/unbound", cacertsPath)
|
||||
routingConf := routing.NewRouting(logger)
|
||||
firewallConf := firewall.NewConfigurator(logger, routingConf, fileManager)
|
||||
firewallConf := firewall.NewConfigurator(logger, routingConf, os.OpenFile)
|
||||
streamMerger := command.NewStreamMerger()
|
||||
|
||||
paramsReader := params.NewReader(logger, fileManager)
|
||||
paramsReader := params.NewReader(logger, os)
|
||||
fmt.Println(gluetunLogging.Splash(buildInfo))
|
||||
|
||||
printVersions(ctx, logger, map[string]func(ctx context.Context) (string, error){
|
||||
@@ -101,32 +154,38 @@ func _main(background context.Context, args []string) int { //nolint:gocognit,go
|
||||
|
||||
allSettings, err := settings.GetAllSettings(paramsReader)
|
||||
if err != nil {
|
||||
logger.Error(err)
|
||||
return 1
|
||||
return err
|
||||
}
|
||||
logger.Info(allSettings.String())
|
||||
|
||||
if err := os.MkdirAll("/tmp/gluetun", 0644); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := os.MkdirAll("/gluetun", 0644); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// TODO run this in a loop or in openvpn to reload from file without restarting
|
||||
storage := storage.New(logger)
|
||||
const updateServerFile = true
|
||||
allServers, err := storage.SyncServers(constants.GetAllServers(), updateServerFile)
|
||||
storage := storage.New(logger, os, constants.ServersData)
|
||||
allServers, err := storage.SyncServers(constants.GetAllServers())
|
||||
if err != nil {
|
||||
logger.Error(err)
|
||||
return 1
|
||||
return err
|
||||
}
|
||||
|
||||
// Should never change
|
||||
uid, gid := allSettings.System.UID, allSettings.System.GID
|
||||
puid, pgid := allSettings.System.PUID, allSettings.System.PGID
|
||||
|
||||
err = alpineConf.CreateUser("nonrootuser", uid)
|
||||
const defaultUsername = "nonrootuser"
|
||||
nonRootUsername, err := alpineConf.CreateUser(defaultUsername, puid)
|
||||
if err != nil {
|
||||
logger.Error(err)
|
||||
return 1
|
||||
return err
|
||||
}
|
||||
err = fileManager.SetOwnership("/etc/unbound", uid, gid)
|
||||
if err != nil {
|
||||
logger.Error(err)
|
||||
return 1
|
||||
if nonRootUsername != defaultUsername {
|
||||
logger.Info("using existing username %s corresponding to user id %d", nonRootUsername, puid)
|
||||
}
|
||||
|
||||
if err := os.Chown("/etc/unbound", puid, pgid); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if allSettings.Firewall.Debug {
|
||||
@@ -136,27 +195,23 @@ func _main(background context.Context, args []string) int { //nolint:gocognit,go
|
||||
|
||||
defaultInterface, defaultGateway, err := routingConf.DefaultRoute()
|
||||
if err != nil {
|
||||
logger.Error(err)
|
||||
return 1
|
||||
return err
|
||||
}
|
||||
|
||||
localSubnet, err := routingConf.LocalSubnet()
|
||||
if err != nil {
|
||||
logger.Error(err)
|
||||
return 1
|
||||
return err
|
||||
}
|
||||
|
||||
defaultIP, err := routingConf.DefaultIP()
|
||||
if err != nil {
|
||||
logger.Error(err)
|
||||
return 1
|
||||
return err
|
||||
}
|
||||
|
||||
firewallConf.SetNetworkInformation(defaultInterface, defaultGateway, localSubnet, defaultIP)
|
||||
|
||||
if err := routingConf.Setup(); err != nil {
|
||||
logger.Error(err)
|
||||
return 1
|
||||
return err
|
||||
}
|
||||
defer func() {
|
||||
routingConf.SetVerbose(false)
|
||||
@@ -166,108 +221,93 @@ func _main(background context.Context, args []string) int { //nolint:gocognit,go
|
||||
}()
|
||||
|
||||
if err := firewallConf.SetOutboundSubnets(ctx, allSettings.Firewall.OutboundSubnets); err != nil {
|
||||
logger.Error(err)
|
||||
return 1
|
||||
return err
|
||||
}
|
||||
if err := routingConf.SetOutboundRoutes(allSettings.Firewall.OutboundSubnets); err != nil {
|
||||
logger.Error(err)
|
||||
return 1
|
||||
return err
|
||||
}
|
||||
|
||||
if err := ovpnConf.CheckTUN(); err != nil {
|
||||
logger.Warn(err)
|
||||
err = ovpnConf.CreateTUN()
|
||||
if err != nil {
|
||||
logger.Error(err)
|
||||
return 1
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
tunnelReadyCh, dnsReadyCh := make(chan struct{}), make(chan struct{})
|
||||
signalTunnelReady := func() { tunnelReadyCh <- struct{}{} }
|
||||
signalDNSReady := func() { dnsReadyCh <- struct{}{} }
|
||||
tunnelReadyCh := make(chan struct{})
|
||||
dnsReadyCh := make(chan struct{})
|
||||
defer close(tunnelReadyCh)
|
||||
defer close(dnsReadyCh)
|
||||
|
||||
if allSettings.Firewall.Enabled {
|
||||
err := firewallConf.SetEnabled(ctx, true) // disabled by default
|
||||
if err != nil {
|
||||
logger.Error(err)
|
||||
return 1
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
for _, vpnPort := range allSettings.Firewall.VPNInputPorts {
|
||||
err = firewallConf.SetAllowedPort(ctx, vpnPort, string(constants.TUN))
|
||||
if err != nil {
|
||||
logger.Error(err)
|
||||
return 1
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
for _, port := range allSettings.Firewall.InputPorts {
|
||||
err = firewallConf.SetAllowedPort(ctx, port, defaultInterface)
|
||||
if err != nil {
|
||||
logger.Error(err)
|
||||
return 1
|
||||
return err
|
||||
}
|
||||
} // TODO move inside firewall?
|
||||
|
||||
wg := &sync.WaitGroup{}
|
||||
|
||||
go collectStreamLines(ctx, streamMerger, logger, signalTunnelReady)
|
||||
wg.Add(1)
|
||||
go collectStreamLines(ctx, wg, streamMerger, logger, tunnelReadyCh)
|
||||
|
||||
openvpnLooper := openvpn.NewLooper(allSettings.VPNSP, allSettings.OpenVPN, uid, gid, allServers,
|
||||
ovpnConf, firewallConf, routingConf, logger, httpClient, fileManager, streamMerger, cancel)
|
||||
openvpnLooper := openvpn.NewLooper(allSettings.OpenVPN, nonRootUsername, puid, pgid, allServers,
|
||||
ovpnConf, firewallConf, routingConf, logger, httpClient, os.OpenFile, streamMerger, cancel)
|
||||
wg.Add(1)
|
||||
// wait for restartOpenvpn
|
||||
go openvpnLooper.Run(ctx, wg)
|
||||
|
||||
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, httpClient,
|
||||
logger, streamMerger, nonRootUsername, puid, pgid)
|
||||
wg.Add(1)
|
||||
// wait for unboundLooper.Restart or its ticker launched with RunRestartTicker
|
||||
go unboundLooper.Run(ctx, wg, signalDNSReady)
|
||||
go unboundLooper.Run(ctx, wg, dnsReadyCh)
|
||||
|
||||
publicIPLooper := publicip.NewLooper(client, logger, fileManager,
|
||||
allSettings.System.IPStatusFilepath, allSettings.PublicIPPeriod, uid, gid)
|
||||
publicIPLooper := publicip.NewLooper(
|
||||
httpClient, logger, allSettings.PublicIP, puid, pgid, os)
|
||||
wg.Add(1)
|
||||
go publicIPLooper.Run(ctx, wg)
|
||||
wg.Add(1)
|
||||
go publicIPLooper.RunRestartTicker(ctx, wg)
|
||||
publicIPLooper.SetPeriod(allSettings.PublicIPPeriod) // call after RunRestartTicker
|
||||
|
||||
httpProxyLooper := httpproxy.NewLooper(logger, allSettings.HTTPProxy)
|
||||
wg.Add(1)
|
||||
go httpProxyLooper.Run(ctx, wg)
|
||||
|
||||
shadowsocksLooper := shadowsocks.NewLooper(allSettings.ShadowSocks, logger, defaultInterface)
|
||||
restartShadowsocks := shadowsocksLooper.Restart
|
||||
shadowsocksLooper := shadowsocks.NewLooper(allSettings.ShadowSocks, logger)
|
||||
wg.Add(1)
|
||||
go shadowsocksLooper.Run(ctx, wg)
|
||||
|
||||
if allSettings.HTTPProxy.Enabled {
|
||||
httpProxyLooper.Restart()
|
||||
}
|
||||
if allSettings.ShadowSocks.Enabled {
|
||||
restartShadowsocks()
|
||||
}
|
||||
|
||||
wg.Add(1)
|
||||
go routeReadyEvents(ctx, wg, tunnelReadyCh, dnsReadyCh,
|
||||
go routeReadyEvents(ctx, wg, buildInfo, tunnelReadyCh, dnsReadyCh,
|
||||
unboundLooper, updaterLooper, publicIPLooper, routingConf, logger, httpClient,
|
||||
allSettings.VersionInformation, allSettings.OpenVPN.Provider.PortForwarding.Enabled, openvpnLooper.PortForward,
|
||||
)
|
||||
controlServerAddress := fmt.Sprintf("0.0.0.0:%d", allSettings.ControlServer.Port)
|
||||
controlServerLogging := allSettings.ControlServer.Log
|
||||
httpServer := server.New(controlServerAddress, controlServerLogging,
|
||||
logger, buildInfo, openvpnLooper, unboundLooper, updaterLooper)
|
||||
logger, buildInfo, openvpnLooper, unboundLooper, updaterLooper, publicIPLooper)
|
||||
wg.Add(1)
|
||||
go httpServer.Run(ctx, wg)
|
||||
|
||||
@@ -276,63 +316,22 @@ 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
|
||||
|
||||
<-ctx.Done()
|
||||
|
||||
signalsCh := make(chan os.Signal, 1)
|
||||
signal.Notify(signalsCh,
|
||||
syscall.SIGINT,
|
||||
syscall.SIGTERM,
|
||||
os.Interrupt,
|
||||
)
|
||||
shutdownErrorsCount := 0
|
||||
select {
|
||||
case signal := <-signalsCh:
|
||||
logger.Warn("Caught OS signal %s, shutting down", signal)
|
||||
cancel()
|
||||
case <-ctx.Done():
|
||||
logger.Warn("context canceled, shutting down")
|
||||
}
|
||||
logger.Info("Clearing ip status file %s", allSettings.System.IPStatusFilepath)
|
||||
if err := fileManager.Remove(string(allSettings.System.IPStatusFilepath)); err != nil {
|
||||
logger.Error(err)
|
||||
shutdownErrorsCount++
|
||||
}
|
||||
if allSettings.OpenVPN.Provider.PortForwarding.Enabled {
|
||||
logger.Info("Clearing forwarded port status file %s", allSettings.OpenVPN.Provider.PortForwarding.Filepath)
|
||||
if err := fileManager.Remove(string(allSettings.OpenVPN.Provider.PortForwarding.Filepath)); err != nil {
|
||||
if err := os.Remove(string(allSettings.OpenVPN.Provider.PortForwarding.Filepath)); err != nil {
|
||||
logger.Error(err)
|
||||
shutdownErrorsCount++
|
||||
}
|
||||
}
|
||||
const shutdownGracePeriod = 5 * time.Second
|
||||
waiting, waited := context.WithTimeout(context.Background(), shutdownGracePeriod)
|
||||
go func() {
|
||||
defer waited()
|
||||
wg.Wait()
|
||||
}()
|
||||
<-waiting.Done()
|
||||
if waiting.Err() == context.DeadlineExceeded {
|
||||
if shutdownErrorsCount > 0 {
|
||||
logger.Warn("Shutdown had %d errors", shutdownErrorsCount)
|
||||
}
|
||||
logger.Warn("Shutdown timed out")
|
||||
return 1
|
||||
}
|
||||
if shutdownErrorsCount > 0 {
|
||||
logger.Warn("Shutdown had %d errors")
|
||||
return 1
|
||||
}
|
||||
logger.Info("Shutdown successful")
|
||||
return 0
|
||||
}
|
||||
|
||||
func createLogger() logging.Logger {
|
||||
logger, err := logging.NewLogger(logging.ConsoleEncoding, logging.InfoLevel)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return logger
|
||||
wg.Wait()
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func printVersions(ctx context.Context, logger logging.Logger,
|
||||
@@ -350,9 +349,10 @@ func printVersions(ctx context.Context, logger logging.Logger,
|
||||
}
|
||||
}
|
||||
|
||||
//nolint:lll
|
||||
func collectStreamLines(ctx context.Context, streamMerger command.StreamMerger,
|
||||
logger logging.Logger, signalTunnelReady func()) {
|
||||
func collectStreamLines(ctx context.Context, wg *sync.WaitGroup,
|
||||
streamMerger command.StreamMerger,
|
||||
logger logging.Logger, tunnelReadyCh chan<- struct{}) {
|
||||
defer wg.Done()
|
||||
// Blocking line merging paramsReader for openvpn and unbound
|
||||
logger.Info("Launching standard output merger")
|
||||
streamMerger.CollectLines(ctx, func(line string) {
|
||||
@@ -372,10 +372,10 @@ func collectStreamLines(ctx context.Context, streamMerger command.StreamMerger,
|
||||
}
|
||||
switch {
|
||||
case strings.Contains(line, "Initialization Sequence Completed"):
|
||||
signalTunnelReady()
|
||||
case strings.Contains(line, "TLS Error: TLS key negotiation failed to occur within 60 seconds (check your network connectivity)"):
|
||||
tunnelReadyCh <- struct{}{}
|
||||
case strings.Contains(line, "TLS Error: TLS key negotiation failed to occur within 60 seconds (check your network connectivity)"): //nolint:lll
|
||||
logger.Warn("This means that either...")
|
||||
logger.Warn("1. The VPN server IP address you are trying to connect to is no longer valid, see https://github.com/qdm12/gluetun/wiki/Update-servers-information")
|
||||
logger.Warn("1. The VPN server IP address you are trying to connect to is no longer valid, see https://github.com/qdm12/gluetun/wiki/Update-servers-information") //nolint:lll
|
||||
logger.Warn("2. The VPN server crashed, try changing region")
|
||||
logger.Warn("3. Your Internet connection is not working, ensure it works")
|
||||
logger.Warn("Feel free to create an issue at https://github.com/qdm12/gluetun/issues/new/choose")
|
||||
@@ -385,7 +385,8 @@ func collectStreamLines(ctx context.Context, streamMerger command.StreamMerger,
|
||||
})
|
||||
}
|
||||
|
||||
func routeReadyEvents(ctx context.Context, wg *sync.WaitGroup, tunnelReadyCh, dnsReadyCh <-chan struct{},
|
||||
func routeReadyEvents(ctx context.Context, wg *sync.WaitGroup, buildInfo models.BuildInformation,
|
||||
tunnelReadyCh, dnsReadyCh <-chan struct{},
|
||||
unboundLooper dns.Looper, updaterLooper updater.Looper, publicIPLooper publicip.Looper,
|
||||
routing routing.Routing, logger logging.Logger, httpClient *http.Client,
|
||||
versionInformation, portForwardingEnabled bool, startPortForward func(vpnGateway net.IP)) {
|
||||
@@ -401,7 +402,9 @@ func routeReadyEvents(ctx context.Context, wg *sync.WaitGroup, tunnelReadyCh, dn
|
||||
tickerWg.Wait()
|
||||
return
|
||||
case <-tunnelReadyCh: // blocks until openvpn is connected
|
||||
unboundLooper.Restart()
|
||||
if unboundLooper.GetSettings().Enabled {
|
||||
_, _ = unboundLooper.SetStatus(constants.Running)
|
||||
}
|
||||
restartTickerCancel() // stop previous restart tickers
|
||||
tickerWg.Wait()
|
||||
restartTickerContext, restartTickerCancel = context.WithCancel(ctx)
|
||||
@@ -424,7 +427,8 @@ func routeReadyEvents(ctx context.Context, wg *sync.WaitGroup, tunnelReadyCh, dn
|
||||
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
|
||||
}
|
||||
|
||||
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 |
@@ -1,7 +1,7 @@
|
||||
version: "3.7"
|
||||
services:
|
||||
gluetun:
|
||||
image: qmcgaw/private-internet-access
|
||||
image: qmcgaw/gluetun
|
||||
container_name: gluetun
|
||||
cap_add:
|
||||
- NET_ADMIN
|
||||
@@ -14,25 +14,18 @@ services:
|
||||
# command:
|
||||
volumes:
|
||||
- /yourpath:/gluetun
|
||||
secrets:
|
||||
- openvpn_user
|
||||
- openvpn_password
|
||||
environment:
|
||||
# More variables are available, see the readme table
|
||||
- VPNSP=private internet access
|
||||
|
||||
# Timezone for accurate logs times
|
||||
- TZ=
|
||||
|
||||
# All VPN providers
|
||||
- USER=js89ds7
|
||||
|
||||
# All VPN providers but Mullvad
|
||||
- PASSWORD=8fd9s239G
|
||||
|
||||
# Cyberghost only
|
||||
- CLIENT_KEY=
|
||||
|
||||
# All VPN providers but Mullvad
|
||||
- REGION=Austria
|
||||
|
||||
# Mullvad only
|
||||
- COUNTRY=Sweden
|
||||
restart: always
|
||||
|
||||
secrets:
|
||||
openvpn_user:
|
||||
file: ./openvpn_user
|
||||
openvpn_password:
|
||||
file: ./openvpn_password
|
||||
|
||||
8
go.mod
8
go.mod
@@ -3,12 +3,14 @@ module github.com/qdm12/gluetun
|
||||
go 1.15
|
||||
|
||||
require (
|
||||
github.com/fatih/color v1.9.0
|
||||
github.com/fatih/color v1.10.0
|
||||
github.com/golang/mock v1.4.4
|
||||
github.com/kyokomi/emoji v2.2.4+incompatible
|
||||
github.com/qdm12/golibs v0.0.0-20201025221346-fe352060c25a
|
||||
github.com/qdm12/dns v1.4.0-rc4
|
||||
github.com/qdm12/golibs v0.0.0-20210102020307-17bc97def973
|
||||
github.com/qdm12/ss-server v0.1.0
|
||||
github.com/qdm12/updated v0.0.0-20210102005021-dd457d77f94a
|
||||
github.com/stretchr/testify v1.6.1
|
||||
github.com/vishvananda/netlink v1.1.0
|
||||
golang.org/x/sys v0.0.0-20201018121011-98379d014ca7
|
||||
golang.org/x/sys v0.0.0-20201223074533-0d417f636930
|
||||
)
|
||||
|
||||
56
go.sum
56
go.sum
@@ -4,14 +4,23 @@ github.com/PuerkitoBio/purell v1.1.0 h1:rmGxhojJlM0tuKtfdvliR84CFHljx9ag64t2xmVk
|
||||
github.com/PuerkitoBio/purell v1.1.0/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0=
|
||||
github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578 h1:d+Bc7a5rLufV/sSk/8dngufqelfh6jnri85riMAaF/M=
|
||||
github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578/go.mod h1:uGdkoq3SwY9Y+13GIhn11/XLaGBb4BfwItxLd5jeuXE=
|
||||
github.com/alcortesm/tgz v0.0.0-20161220082320-9c5fe88206d7/go.mod h1:6zEj6s6u/ghQa61ZWa/C2Aw3RkjiTBOix7dkqa1VLIs=
|
||||
github.com/anmitsu/go-shlex v0.0.0-20161002113705-648efa622239/go.mod h1:2FmKhYUyUczH0OGQWaF5ceTx0UBShxjsH6f8oGKYe2c=
|
||||
github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5/go.mod h1:wHh0iHkYZB8zMSxRWpUBQtwG5a7fFgvEO+odwuTv2gs=
|
||||
github.com/asaskevich/govalidator v0.0.0-20180720115003-f9ffefc3facf h1:eg0MeVzsP1G42dRafH3vf+al2vQIJU0YHX+1Tw87oco=
|
||||
github.com/asaskevich/govalidator v0.0.0-20180720115003-f9ffefc3facf/go.mod h1:lB+ZfQJz7igIIfQNfa7Ml4HSf2uFQQRzpGGRXenZAgY=
|
||||
github.com/creack/pty v1.1.7/go.mod h1:lj5s0c3V2DBrqTV7llrYr5NG6My20zk30Fl46Y7DoTY=
|
||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/docker/go-units v0.3.3/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk=
|
||||
github.com/emirpasic/gods v1.12.0/go.mod h1:YfzfFFoVP/catgzJb4IKIqXjX78Ha8FMSDh3ymbK86o=
|
||||
github.com/fatih/color v1.9.0 h1:8xPHl4/q1VyqGIPif1F+1V3Y3lSmrq01EabUW3CoW5s=
|
||||
github.com/fatih/color v1.9.0/go.mod h1:eQcE1qtQxscV5RaZvpXrrb8Drkc3/DdQ+uUYCNjL+zU=
|
||||
github.com/fatih/color v1.10.0 h1:s36xzo75JdqLaaWoiEHk767eHiwo0598uUxyfiPkDsg=
|
||||
github.com/fatih/color v1.10.0/go.mod h1:ELkj/draVOlAH/xkhN6mQ50Qd0MPOk5AAr3maGEBuJM=
|
||||
github.com/flynn/go-shlex v0.0.0-20150515145356-3f9db97f8568/go.mod h1:xEzjJPgXI435gkrCt3MPfRiAkVrwSbHsst4LCFVfpJc=
|
||||
github.com/gliderlabs/ssh v0.2.2/go.mod h1:U7qILu1NlMHj9FlMhZLlkCdDnU1DBEAqr0aevW3Awn0=
|
||||
github.com/globalsign/mgo v0.0.0-20180905125535-1ca0a4f7cbcb h1:D4uzjWwKYQ5XnAvUbuvHW93esHg7F8N/OYeBBcJoTr0=
|
||||
github.com/globalsign/mgo v0.0.0-20180905125535-1ca0a4f7cbcb/go.mod h1:xkRDCp4j0OGD1HRkm4kmhM+pmpv3AKq5SU7GMg4oO/Q=
|
||||
github.com/go-openapi/analysis v0.0.0-20180825180245-b006789cd277/go.mod h1:k70tL6pCuVxPJOHXQ+wIac1FUrvNkHolPie/cLEU6hI=
|
||||
@@ -42,14 +51,19 @@ github.com/golang/mock v1.4.3/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt
|
||||
github.com/golang/mock v1.4.4 h1:l75CXGRSwbaYNpl/Z2X1XIIAMSCquvXgpVZDhwEIJsc=
|
||||
github.com/golang/mock v1.4.4/go.mod h1:l3mdAwkq5BuhzHwde/uurv3sEJeZMXNpwsxVWU71h+4=
|
||||
github.com/gomodule/redigo v2.0.0+incompatible/go.mod h1:B4C85qUVwatsJoIUNIfCRsp7qO0iAmpGFZ4EELWSbC4=
|
||||
github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
|
||||
github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI=
|
||||
github.com/google/uuid v1.0.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
github.com/gotify/go-api-client/v2 v2.0.4 h1:0w8skCr8aLBDKaQDg31LKKHUGF7rt7zdRpR+6cqIAlE=
|
||||
github.com/gotify/go-api-client/v2 v2.0.4/go.mod h1:VKiah/UK20bXsr0JObE1eBVLW44zbBouzjuri9iwjFU=
|
||||
github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99/go.mod h1:1lJo3i6rXxKeerYnT8Nvf0QmHCRC1n8sfWVwXF2Frvo=
|
||||
github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI=
|
||||
github.com/kevinburke/ssh_config v0.0.0-20190725054713-01f96b0aa0cd/go.mod h1:CT57kijsi8u/K/BOFA39wgDQJ9CxiF4nAY/ojJ6r6mM=
|
||||
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
|
||||
github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI=
|
||||
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
|
||||
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
|
||||
github.com/kr/pty v1.1.8/go.mod h1:O1sed60cT9XZ5uDucP5qwvh+TE3NnUj51EiZO/lmSfw=
|
||||
github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=
|
||||
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
|
||||
github.com/kyokomi/emoji v2.2.4+incompatible h1:np0woGKwx9LiHAQmwZx79Oc0rHpNw3o+3evou4BEPv4=
|
||||
@@ -58,28 +72,43 @@ github.com/mailru/easyjson v0.0.0-20180823135443-60711f1a8329 h1:2gxZ0XQIU/5z3Z3
|
||||
github.com/mailru/easyjson v0.0.0-20180823135443-60711f1a8329/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc=
|
||||
github.com/mattn/go-colorable v0.1.4 h1:snbPLB8fVfU9iwbbo30TPtbLRzwWu6aJS6Xh4eaaviA=
|
||||
github.com/mattn/go-colorable v0.1.4/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE=
|
||||
github.com/mattn/go-colorable v0.1.8 h1:c1ghPdyEDarC70ftn0y+A/Ee++9zz8ljHG1b13eJ0s8=
|
||||
github.com/mattn/go-colorable v0.1.8/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc=
|
||||
github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s=
|
||||
github.com/mattn/go-isatty v0.0.11 h1:FxPOTFNqGkuDUGi3H/qkUbQO4ZiBa2brKq5r0l8TGeM=
|
||||
github.com/mattn/go-isatty v0.0.11/go.mod h1:PhnuNfih5lzO57/f3n+odYbM4JtupLOxQOAqxQCu2WE=
|
||||
github.com/mattn/go-isatty v0.0.12 h1:wuysRhFDzyxgEmMf5xjvJ2M9dZoWAXNNr5LSBS7uHXY=
|
||||
github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU=
|
||||
github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
|
||||
github.com/mitchellh/mapstructure v1.1.2 h1:fmNYVwqnSfB9mZU6OS2O6GsXM+wcskZDuKQzvN1EDeE=
|
||||
github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
|
||||
github.com/mr-tron/base58 v1.1.3 h1:v+sk57XuaCKGXpWtVBX8YJzO7hMGx4Aajh4TQbdEFdc=
|
||||
github.com/mr-tron/base58 v1.1.3/go.mod h1:BinMc/sQntlIE1frQmRFPUoPA1Zkr8VRgBdjWI2mNwc=
|
||||
github.com/pborman/uuid v1.2.0/go.mod h1:X/NO0urCmaxf9VXbdlT7C2Yzkj2IKimNn4k+gtPdI/k=
|
||||
github.com/pelletier/go-buffruneio v0.2.0/go.mod h1:JkE26KsDizTr40EUHkXVtNPvgGtbSNq5BcowyYOWdKo=
|
||||
github.com/phayes/permbits v0.0.0-20190612203442-39d7c581d2ee h1:P6U24L02WMfj9ymZTxl7CxS73JC99x3ukk+DBkgQGQs=
|
||||
github.com/phayes/permbits v0.0.0-20190612203442-39d7c581d2ee/go.mod h1:3uODdxMgOaPYeWU7RzZLxVtJHZ/x1f/iHkBZuKJDzuY=
|
||||
github.com/pkg/errors v0.8.1 h1:iURUrRGxPUNPdy5/HRSm+Yj6okJ6UtLINN0Q9M4+h3I=
|
||||
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/qdm12/golibs v0.0.0-20201025221346-fe352060c25a h1:v0zUA1FWeVkTEd9KyxfehbRVJeFGOqyMY6FHO/Q9ITU=
|
||||
github.com/qdm12/golibs v0.0.0-20201025221346-fe352060c25a/go.mod h1:pikkTN7g7zRuuAnERwqW1yAFq6pYmxrxpjiwGvb0Ysc=
|
||||
github.com/qdm12/dns v1.4.0-rc4 h1:pCcFMqismbktPQX7yrtdmJZq30Y05JBfWRTXY1ZLVFw=
|
||||
github.com/qdm12/dns v1.4.0-rc4/go.mod h1:JhUKBhuDRYBUQ2XwW/jbeWx/qS0sSJjIFjGTCFGP5I8=
|
||||
github.com/qdm12/golibs v0.0.0-20201227203847-2fd99ffdfdba/go.mod h1:pikkTN7g7zRuuAnERwqW1yAFq6pYmxrxpjiwGvb0Ysc=
|
||||
github.com/qdm12/golibs v0.0.0-20210102020307-17bc97def973 h1:5YeJALmDjvg2wSi6XB8MpQQekbT/eBnwGahJrh01HHQ=
|
||||
github.com/qdm12/golibs v0.0.0-20210102020307-17bc97def973/go.mod h1:pikkTN7g7zRuuAnERwqW1yAFq6pYmxrxpjiwGvb0Ysc=
|
||||
github.com/qdm12/ss-server v0.1.0 h1:WV9MkHCDEWRwe4WpnYFeR/zcZAxYoTbfntLDnw9AQ50=
|
||||
github.com/qdm12/ss-server v0.1.0/go.mod h1:ABVUkxubboL3vqBkOwDV9glX1/x7SnYrckBe5d+M/zw=
|
||||
github.com/qdm12/updated v0.0.0-20210102005021-dd457d77f94a h1:gkyP+gMEeBgMgyRYGrVNcoy6cL1065IvXsyfB6xboIc=
|
||||
github.com/qdm12/updated v0.0.0-20210102005021-dd457d77f94a/go.mod h1:bbJGxEYCnsA8WU4vBcXYU6mOoHyzdP458FIKP4mfLJM=
|
||||
github.com/riobard/go-bloom v0.0.0-20200614022211-cdc8013cb5b3 h1:f/FNXud6gA3MNr8meMVVGxhp+QBTqY91tM8HjEuMjGg=
|
||||
github.com/riobard/go-bloom v0.0.0-20200614022211-cdc8013cb5b3/go.mod h1:HgjTstvQsPGkxUsCd2KWxErBblirPizecHcpD3ffK+s=
|
||||
github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
|
||||
github.com/sergi/go-diff v1.0.0/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAmXWZgo=
|
||||
github.com/src-d/gcfg v1.4.0/go.mod h1:p/UMsR43ujA89BJY9duynAwIpvqEujIH/jFlfL7jWoI=
|
||||
github.com/stretchr/objx v0.1.0 h1:4G4v2dO3VZwixGIRoQ5Lfboy6nUhCyYzaqnIAPPhYs4=
|
||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/stretchr/objx v0.2.0/go.mod h1:qt09Ya8vawLte6SNmTgCsAVtYtaKzEcn8ATUoHMkEqE=
|
||||
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
|
||||
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
|
||||
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
|
||||
@@ -89,6 +118,8 @@ github.com/vishvananda/netlink v1.1.0 h1:1iyaYNBLmP6L0220aDnYQpo1QEV4t4hJ+xEEhhJ
|
||||
github.com/vishvananda/netlink v1.1.0/go.mod h1:cTgwzPIzzgDAYoQrMm0EdrjRUBkTqKYppBueQtXaqoE=
|
||||
github.com/vishvananda/netns v0.0.0-20191106174202-0a2b9b5464df h1:OviZH7qLw/7ZovXvuNyL3XQl8UFofeikI1NW1Gypu7k=
|
||||
github.com/vishvananda/netns v0.0.0-20191106174202-0a2b9b5464df/go.mod h1:JP3t17pCcGlemwknint6hfoeCVQrEMVwxRLRjXpq+BU=
|
||||
github.com/xanzy/ssh-agent v0.2.1/go.mod h1:mLlQY/MoOhWBj+gOGMQkOeiEvkx+8pJSI+0Bx9h2kr4=
|
||||
github.com/yl2chen/cidranger v1.0.2/go.mod h1:9U1yz7WPYDwf0vpNWFaeRh0bjwz5RVgRy/9UEQfHl0g=
|
||||
go.uber.org/atomic v1.5.0 h1:OI5t8sDa1Or+q8AeE+yKeB/SDYioSHAgcVljj9JIETY=
|
||||
go.uber.org/atomic v1.5.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ=
|
||||
go.uber.org/multierr v1.3.0 h1:sFPn2GLc3poCkfrpIXGhBD2X0CMIo4Q/zSULXrj/+uc=
|
||||
@@ -97,11 +128,15 @@ go.uber.org/tools v0.0.0-20190618225709-2cfd321de3ee h1:0mgffUl7nfd+FpvXMVz4IDEa
|
||||
go.uber.org/tools v0.0.0-20190618225709-2cfd321de3ee/go.mod h1:vJERXedbb3MVM5f9Ejo0C68/HhF8uaILCdgjnY+goOA=
|
||||
go.uber.org/zap v1.13.0 h1:nR6NoDBgAf67s68NhaXbsojM+2gxp3S1hWkHDl27pVU=
|
||||
go.uber.org/zap v1.13.0/go.mod h1:zwrFLgMcdUuIBviXEYEH1YKNaOBnKXsx2IPda5bBwHM=
|
||||
golang.org/x/crypto v0.0.0-20190219172222-a4c6cb3142f2/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
|
||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||
golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||
golang.org/x/crypto v0.0.0-20190701094942-4def268fd1a4/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||
golang.org/x/crypto v0.0.0-20200117160349-530e935923ad/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||
golang.org/x/crypto v0.0.0-20200728195943-123391ffb6de h1:ikNHVSjEfnvz6sxdSPCaPt572qowuyMDMJLLm3Db3ig=
|
||||
golang.org/x/crypto v0.0.0-20200728195943-123391ffb6de/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||
golang.org/x/crypto v0.0.0-20201221181555-eec23a3978ad h1:DN0cp81fZ3njFcrLCytUHRSUkqBjfTo4Tx9RJTWs0EY=
|
||||
golang.org/x/crypto v0.0.0-20201221181555-eec23a3978ad/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I=
|
||||
golang.org/x/lint v0.0.0-20190930215403-16217165b5de h1:5hukYrvBGR8/eNkX5mdUezrA6JiaEZDtJb9Ei+1LlBs=
|
||||
golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
|
||||
golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc=
|
||||
@@ -109,19 +144,28 @@ golang.org/x/net v0.0.0-20181005035420-146acd28ed58/go.mod h1:mL1N/T3taQHkDXs73r
|
||||
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20190724013045-ca1201d0de80/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/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-20190221075227-b4e8571b14e0/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190606203320-7fc4e5ec1444/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20201018121011-98379d014ca7 h1:CNOpL+H7PSxBI7dF/EIUsfOguRSzWp6CQ91yxZE6PG4=
|
||||
golang.org/x/sys v0.0.0-20201018121011-98379d014ca7/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20201223074533-0d417f636930 h1:vRgIt+nup/B/BwIS0g2oC0haq0iqbV3ZA+u6+0TlNCo=
|
||||
golang.org/x/sys v0.0.0-20201223074533-0d417f636930/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw=
|
||||
golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
|
||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
|
||||
golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
|
||||
golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
|
||||
golang.org/x/tools v0.0.0-20190729092621-ff9f1409240a/go.mod h1:jcCCGcm9btYwXyDqrUWc6MKQKKGJCWEQ3AfLSRIbEuI=
|
||||
golang.org/x/tools v0.0.0-20191029041327-9cc4af7d6b2c/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.0.0-20191029190741-b9c20aec41a5 h1:hKsoRgsbwY1NafxrwTs+k64bikrLBkAgPir1TNCj3Zs=
|
||||
golang.org/x/tools v0.0.0-20191029190741-b9c20aec41a5/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
@@ -130,6 +174,10 @@ gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8
|
||||
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY=
|
||||
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
|
||||
gopkg.in/src-d/go-billy.v4 v4.3.2/go.mod h1:nDjArDMp+XMs1aFAESLRjfGSgfvoYN0hDfzEk0GjC98=
|
||||
gopkg.in/src-d/go-git-fixtures.v3 v3.5.0/go.mod h1:dLBcvytrw/TYZsNTWCnkNF2DSIlzWYqTe3rJR56Ac7g=
|
||||
gopkg.in/src-d/go-git.v4 v4.13.1/go.mod h1:nx5NYcxdKxq5fpltdHnPa2Exj4Sx0EclMWZQbYDu2z8=
|
||||
gopkg.in/warnings.v0 v0.1.2/go.mod h1:jksf8JmL6Qr/oQM2OXTHunEvvTAsrWBLb6OOjuVWRNI=
|
||||
gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo=
|
||||
|
||||
@@ -1,25 +1,22 @@
|
||||
package alpine
|
||||
|
||||
import (
|
||||
"os/user"
|
||||
|
||||
"github.com/qdm12/golibs/files"
|
||||
"github.com/qdm12/golibs/os"
|
||||
"github.com/qdm12/golibs/os/user"
|
||||
)
|
||||
|
||||
type Configurator interface {
|
||||
CreateUser(username string, uid int) error
|
||||
CreateUser(username string, uid int) (createdUsername string, err error)
|
||||
}
|
||||
|
||||
type configurator struct {
|
||||
fileManager files.FileManager
|
||||
lookupUID func(uid string) (*user.User, error)
|
||||
lookupUser func(username string) (*user.User, error)
|
||||
openFile os.OpenFileFunc
|
||||
osUser user.OSUser
|
||||
}
|
||||
|
||||
func NewConfigurator(fileManager files.FileManager) Configurator {
|
||||
func NewConfigurator(openFile os.OpenFileFunc, osUser user.OSUser) Configurator {
|
||||
return &configurator{
|
||||
fileManager: fileManager,
|
||||
lookupUID: user.LookupId,
|
||||
lookupUser: user.Lookup,
|
||||
openFile: openFile,
|
||||
osUser: osUser,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,38 +2,40 @@ package alpine
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"os/user"
|
||||
)
|
||||
|
||||
// CreateUser creates a user in Alpine with the given UID.
|
||||
func (c *configurator) CreateUser(username string, uid int) error {
|
||||
func (c *configurator) CreateUser(username string, uid int) (createdUsername string, err error) {
|
||||
UIDStr := fmt.Sprintf("%d", uid)
|
||||
u, err := c.lookupUID(UIDStr)
|
||||
u, err := c.osUser.LookupID(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)
|
||||
u, err = c.osUser.Lookup(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")
|
||||
file, err := c.openFile("/etc/passwd", os.O_APPEND|os.O_WRONLY, 0644)
|
||||
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)
|
||||
s := fmt.Sprintf("%s:x:%d:::/dev/null:/sbin/nologin\n", username, uid)
|
||||
_, err = file.WriteString(s)
|
||||
if err != nil {
|
||||
_ = file.Close()
|
||||
return "", err
|
||||
}
|
||||
return nil
|
||||
return username, file.Close()
|
||||
}
|
||||
|
||||
@@ -2,131 +2,19 @@ package cli
|
||||
|
||||
import (
|
||||
"context"
|
||||
"flag"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/qdm12/gluetun/internal/constants"
|
||||
"github.com/qdm12/gluetun/internal/healthcheck"
|
||||
"github.com/qdm12/gluetun/internal/params"
|
||||
"github.com/qdm12/gluetun/internal/provider"
|
||||
"github.com/qdm12/gluetun/internal/settings"
|
||||
"github.com/qdm12/gluetun/internal/storage"
|
||||
"github.com/qdm12/gluetun/internal/updater"
|
||||
"github.com/qdm12/golibs/files"
|
||||
"github.com/qdm12/golibs/logging"
|
||||
"github.com/qdm12/golibs/os"
|
||||
)
|
||||
|
||||
func ClientKey(args []string) error {
|
||||
flagSet := flag.NewFlagSet("clientkey", flag.ExitOnError)
|
||||
filepath := flagSet.String("path", string(constants.ClientKey), "file path to the client.key file")
|
||||
if err := flagSet.Parse(args); err != nil {
|
||||
return err
|
||||
}
|
||||
fileManager := files.NewFileManager()
|
||||
data, err := fileManager.ReadFile(*filepath)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
s := string(data)
|
||||
s = strings.ReplaceAll(s, "\n", "")
|
||||
s = strings.ReplaceAll(s, "\r", "")
|
||||
s = strings.TrimPrefix(s, "-----BEGIN PRIVATE KEY-----")
|
||||
s = strings.TrimSuffix(s, "-----END PRIVATE KEY-----")
|
||||
fmt.Println(s)
|
||||
return nil
|
||||
type CLI interface {
|
||||
ClientKey(args []string, openFile os.OpenFileFunc) error
|
||||
HealthCheck(ctx context.Context) error
|
||||
OpenvpnConfig(os os.OS) error
|
||||
Update(args []string, os os.OS) error
|
||||
}
|
||||
|
||||
func HealthCheck(ctx context.Context) error {
|
||||
const timeout = 3 * time.Second
|
||||
httpClient := &http.Client{Timeout: timeout}
|
||||
healthchecker := healthcheck.NewChecker(httpClient)
|
||||
ctx, cancel := context.WithTimeout(ctx, timeout)
|
||||
defer cancel()
|
||||
const url = "http://" + constants.HealthcheckAddress
|
||||
return healthchecker.Check(ctx, url)
|
||||
}
|
||||
type cli struct{}
|
||||
|
||||
func OpenvpnConfig() error {
|
||||
logger, err := logging.NewLogger(logging.ConsoleEncoding, logging.InfoLevel)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
paramsReader := params.NewReader(logger, files.NewFileManager())
|
||||
allSettings, err := settings.GetAllSettings(paramsReader)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
allServers, err := storage.New(logger).SyncServers(constants.GetAllServers(), false)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
providerConf := provider.New(allSettings.OpenVPN.Provider.Name, allServers, time.Now)
|
||||
connection, err := providerConf.GetOpenVPNConnection(allSettings.OpenVPN.Provider.ServerSelection)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
lines := providerConf.BuildConf(
|
||||
connection,
|
||||
allSettings.OpenVPN.Verbosity,
|
||||
allSettings.System.UID,
|
||||
allSettings.System.GID,
|
||||
allSettings.OpenVPN.Root,
|
||||
allSettings.OpenVPN.Cipher,
|
||||
allSettings.OpenVPN.Auth,
|
||||
allSettings.OpenVPN.Provider.ExtraConfigOptions,
|
||||
)
|
||||
fmt.Println(strings.Join(lines, "\n"))
|
||||
return nil
|
||||
}
|
||||
|
||||
func Update(args []string) error {
|
||||
options := updater.Options{CLI: true}
|
||||
var flushToFile bool
|
||||
flagSet := flag.NewFlagSet("update", flag.ExitOnError)
|
||||
flagSet.BoolVar(&flushToFile, "file", false, "Write results to /gluetun/servers.json (for end users)")
|
||||
flagSet.BoolVar(&options.Stdout, "stdout", false, "Write results to console to modify the program (for maintainers)")
|
||||
flagSet.StringVar(&options.DNSAddress, "dns", "1.1.1.1", "DNS resolver address to use")
|
||||
flagSet.BoolVar(&options.Cyberghost, "cyberghost", false, "Update Cyberghost servers")
|
||||
flagSet.BoolVar(&options.Mullvad, "mullvad", false, "Update Mullvad servers")
|
||||
flagSet.BoolVar(&options.Nordvpn, "nordvpn", false, "Update Nordvpn servers")
|
||||
flagSet.BoolVar(&options.PIA, "pia", false, "Update Private Internet Access post-summer 2020 servers")
|
||||
flagSet.BoolVar(&options.Privado, "privado", false, "Update Privado servers")
|
||||
flagSet.BoolVar(&options.Purevpn, "purevpn", false, "Update Purevpn servers")
|
||||
flagSet.BoolVar(&options.Surfshark, "surfshark", false, "Update Surfshark servers")
|
||||
flagSet.BoolVar(&options.Vyprvpn, "vyprvpn", false, "Update Vyprvpn servers")
|
||||
flagSet.BoolVar(&options.Windscribe, "windscribe", false, "Update Windscribe servers")
|
||||
if err := flagSet.Parse(args); err != nil {
|
||||
return err
|
||||
}
|
||||
logger, err := logging.NewLogger(logging.ConsoleEncoding, logging.InfoLevel)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if !flushToFile && !options.Stdout {
|
||||
return fmt.Errorf("at least one of -file or -stdout must be specified")
|
||||
}
|
||||
ctx := context.Background()
|
||||
const clientTimeout = 10 * time.Second
|
||||
httpClient := &http.Client{Timeout: clientTimeout}
|
||||
storage := storage.New(logger)
|
||||
const writeSync = false
|
||||
currentServers, err := storage.SyncServers(constants.GetAllServers(), writeSync)
|
||||
if err != nil {
|
||||
return fmt.Errorf("cannot update servers: %w", err)
|
||||
}
|
||||
updater := updater.New(options, httpClient, currentServers, logger)
|
||||
allServers, err := updater.UpdateServers(ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if flushToFile {
|
||||
if err := storage.FlushToFile(allServers); err != nil {
|
||||
return fmt.Errorf("cannot update servers: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
func New() CLI {
|
||||
return &cli{}
|
||||
}
|
||||
|
||||
41
internal/cli/clientkey.go
Normal file
41
internal/cli/clientkey.go
Normal file
@@ -0,0 +1,41 @@
|
||||
package cli
|
||||
|
||||
import (
|
||||
"flag"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"strings"
|
||||
|
||||
"github.com/qdm12/gluetun/internal/constants"
|
||||
"github.com/qdm12/golibs/os"
|
||||
)
|
||||
|
||||
func (c *cli) ClientKey(args []string, openFile os.OpenFileFunc) error {
|
||||
flagSet := flag.NewFlagSet("clientkey", flag.ExitOnError)
|
||||
filepath := flagSet.String("path", string(constants.ClientKey), "file path to the client.key file")
|
||||
if err := flagSet.Parse(args); err != nil {
|
||||
return err
|
||||
}
|
||||
file, err := openFile(*filepath, os.O_RDONLY, 0)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
data, err := ioutil.ReadAll(file)
|
||||
if err != nil {
|
||||
_ = file.Close()
|
||||
return err
|
||||
}
|
||||
if err := file.Close(); err != nil {
|
||||
return err
|
||||
}
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
s := string(data)
|
||||
s = strings.ReplaceAll(s, "\n", "")
|
||||
s = strings.ReplaceAll(s, "\r", "")
|
||||
s = strings.TrimPrefix(s, "-----BEGIN PRIVATE KEY-----")
|
||||
s = strings.TrimSuffix(s, "-----END PRIVATE KEY-----")
|
||||
fmt.Println(s)
|
||||
return nil
|
||||
}
|
||||
20
internal/cli/healthcheck.go
Normal file
20
internal/cli/healthcheck.go
Normal file
@@ -0,0 +1,20 @@
|
||||
package cli
|
||||
|
||||
import (
|
||||
"context"
|
||||
"net/http"
|
||||
"time"
|
||||
|
||||
"github.com/qdm12/gluetun/internal/constants"
|
||||
"github.com/qdm12/gluetun/internal/healthcheck"
|
||||
)
|
||||
|
||||
func (c *cli) HealthCheck(ctx context.Context) error {
|
||||
const timeout = 10 * time.Second
|
||||
httpClient := &http.Client{Timeout: timeout}
|
||||
healthchecker := healthcheck.NewChecker(httpClient)
|
||||
ctx, cancel := context.WithTimeout(ctx, timeout)
|
||||
defer cancel()
|
||||
const url = "http://" + constants.HealthcheckAddress
|
||||
return healthchecker.Check(ctx, url)
|
||||
}
|
||||
48
internal/cli/openvpnconfig.go
Normal file
48
internal/cli/openvpnconfig.go
Normal file
@@ -0,0 +1,48 @@
|
||||
package cli
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/qdm12/gluetun/internal/constants"
|
||||
"github.com/qdm12/gluetun/internal/params"
|
||||
"github.com/qdm12/gluetun/internal/provider"
|
||||
"github.com/qdm12/gluetun/internal/settings"
|
||||
"github.com/qdm12/gluetun/internal/storage"
|
||||
"github.com/qdm12/golibs/logging"
|
||||
"github.com/qdm12/golibs/os"
|
||||
)
|
||||
|
||||
func (c *cli) OpenvpnConfig(os os.OS) error {
|
||||
logger, err := logging.NewLogger(logging.ConsoleEncoding, logging.InfoLevel)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
paramsReader := params.NewReader(logger, os)
|
||||
allSettings, err := settings.GetAllSettings(paramsReader)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
allServers, err := storage.New(logger, os, constants.ServersData).
|
||||
SyncServers(constants.GetAllServers())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
providerConf := provider.New(allSettings.OpenVPN.Provider.Name, allServers, time.Now)
|
||||
connection, err := providerConf.GetOpenVPNConnection(allSettings.OpenVPN.Provider.ServerSelection)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
lines := providerConf.BuildConf(
|
||||
connection,
|
||||
allSettings.OpenVPN.Verbosity,
|
||||
"nonroortuser",
|
||||
allSettings.OpenVPN.Root,
|
||||
allSettings.OpenVPN.Cipher,
|
||||
allSettings.OpenVPN.Auth,
|
||||
allSettings.OpenVPN.Provider.ExtraConfigOptions,
|
||||
)
|
||||
fmt.Println(strings.Join(lines, "\n"))
|
||||
return nil
|
||||
}
|
||||
64
internal/cli/update.go
Normal file
64
internal/cli/update.go
Normal file
@@ -0,0 +1,64 @@
|
||||
package cli
|
||||
|
||||
import (
|
||||
"context"
|
||||
"flag"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"time"
|
||||
|
||||
"github.com/qdm12/gluetun/internal/constants"
|
||||
"github.com/qdm12/gluetun/internal/settings"
|
||||
"github.com/qdm12/gluetun/internal/storage"
|
||||
"github.com/qdm12/gluetun/internal/updater"
|
||||
"github.com/qdm12/golibs/logging"
|
||||
"github.com/qdm12/golibs/os"
|
||||
)
|
||||
|
||||
func (c *cli) Update(args []string, os os.OS) error {
|
||||
options := settings.Updater{CLI: true}
|
||||
var flushToFile bool
|
||||
flagSet := flag.NewFlagSet("update", flag.ExitOnError)
|
||||
flagSet.BoolVar(&flushToFile, "file", false, "Write results to /gluetun/servers.json (for end users)")
|
||||
flagSet.BoolVar(&options.Stdout, "stdout", false, "Write results to console to modify the program (for maintainers)")
|
||||
flagSet.StringVar(&options.DNSAddress, "dns", "1.1.1.1", "DNS resolver address to use")
|
||||
flagSet.BoolVar(&options.Cyberghost, "cyberghost", false, "Update Cyberghost servers")
|
||||
flagSet.BoolVar(&options.Mullvad, "mullvad", false, "Update Mullvad servers")
|
||||
flagSet.BoolVar(&options.Nordvpn, "nordvpn", false, "Update Nordvpn servers")
|
||||
flagSet.BoolVar(&options.PIA, "pia", false, "Update Private Internet Access post-summer 2020 servers")
|
||||
flagSet.BoolVar(&options.Privado, "privado", false, "Update Privado servers")
|
||||
flagSet.BoolVar(&options.Purevpn, "purevpn", false, "Update Purevpn servers")
|
||||
flagSet.BoolVar(&options.Surfshark, "surfshark", false, "Update Surfshark servers")
|
||||
flagSet.BoolVar(&options.Vyprvpn, "vyprvpn", false, "Update Vyprvpn servers")
|
||||
flagSet.BoolVar(&options.Windscribe, "windscribe", false, "Update Windscribe servers")
|
||||
if err := flagSet.Parse(args); err != nil {
|
||||
return err
|
||||
}
|
||||
logger, err := logging.NewLogger(logging.ConsoleEncoding, logging.InfoLevel)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if !flushToFile && !options.Stdout {
|
||||
return fmt.Errorf("at least one of -file or -stdout must be specified")
|
||||
}
|
||||
ctx := context.Background()
|
||||
const clientTimeout = 10 * time.Second
|
||||
httpClient := &http.Client{Timeout: clientTimeout}
|
||||
storage := storage.New(logger, os, constants.ServersData)
|
||||
currentServers, err := storage.SyncServers(constants.GetAllServers())
|
||||
if err != nil {
|
||||
return fmt.Errorf("cannot update servers: %w", err)
|
||||
}
|
||||
updater := updater.New(options, httpClient, currentServers, logger)
|
||||
allServers, err := updater.UpdateServers(ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if flushToFile {
|
||||
if err := storage.FlushToFile(allServers); err != nil {
|
||||
return fmt.Errorf("cannot update servers: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
@@ -1,116 +0,0 @@
|
||||
package constants
|
||||
|
||||
import (
|
||||
"net"
|
||||
|
||||
"github.com/qdm12/gluetun/internal/models"
|
||||
)
|
||||
|
||||
const (
|
||||
// Cloudflare is a DNS over TLS provider.
|
||||
Cloudflare models.DNSProvider = "cloudflare"
|
||||
// Google is a DNS over TLS provider.
|
||||
Google models.DNSProvider = "google"
|
||||
// Quad9 is a DNS over TLS provider.
|
||||
Quad9 models.DNSProvider = "quad9"
|
||||
// Quadrant is a DNS over TLS provider.
|
||||
Quadrant models.DNSProvider = "quadrant"
|
||||
// CleanBrowsing is a DNS over TLS provider.
|
||||
CleanBrowsing models.DNSProvider = "cleanbrowsing"
|
||||
// SecureDNS is a DNS over TLS provider.
|
||||
SecureDNS models.DNSProvider = "securedns"
|
||||
// LibreDNS is a DNS over TLS provider.
|
||||
LibreDNS models.DNSProvider = "libredns"
|
||||
)
|
||||
|
||||
// DNSProviderMapping returns a constant mapping of dns provider name
|
||||
// to their data such as IP addresses or TLS host name.
|
||||
func DNSProviderMapping() map[models.DNSProvider]models.DNSProviderData {
|
||||
return map[models.DNSProvider]models.DNSProviderData{
|
||||
Cloudflare: {
|
||||
IPs: []net.IP{
|
||||
{1, 1, 1, 1},
|
||||
{1, 0, 0, 1},
|
||||
{0x26, 0x6, 0x47, 0x0, 0x47, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x11, 0x11},
|
||||
{0x26, 0x6, 0x47, 0x0, 0x47, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x10, 0x01},
|
||||
},
|
||||
SupportsTLS: true,
|
||||
SupportsIPv6: true,
|
||||
Host: models.DNSHost("cloudflare-dns.com"),
|
||||
},
|
||||
Google: {
|
||||
IPs: []net.IP{
|
||||
{8, 8, 8, 8},
|
||||
{8, 8, 4, 4},
|
||||
{0x20, 0x1, 0x48, 0x60, 0x48, 0x60, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x88, 0x88},
|
||||
{0x20, 0x1, 0x48, 0x60, 0x48, 0x60, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x88, 0x44},
|
||||
},
|
||||
SupportsTLS: true,
|
||||
SupportsIPv6: true,
|
||||
Host: models.DNSHost("dns.google"),
|
||||
},
|
||||
Quad9: {
|
||||
IPs: []net.IP{
|
||||
{9, 9, 9, 9},
|
||||
{149, 112, 112, 112},
|
||||
{0x26, 0x20, 0x0, 0xfe, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xfe},
|
||||
{0x26, 0x20, 0x0, 0xfe, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x9},
|
||||
},
|
||||
SupportsTLS: true,
|
||||
SupportsIPv6: true,
|
||||
Host: models.DNSHost("dns.quad9.net"),
|
||||
},
|
||||
Quadrant: {
|
||||
IPs: []net.IP{
|
||||
{12, 159, 2, 159},
|
||||
{0x20, 0x1, 0x18, 0x90, 0x14, 0xc, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1, 0x59},
|
||||
},
|
||||
SupportsTLS: true,
|
||||
SupportsIPv6: true,
|
||||
Host: models.DNSHost("dns-tls.qis.io"),
|
||||
},
|
||||
CleanBrowsing: {
|
||||
IPs: []net.IP{
|
||||
{185, 228, 168, 9},
|
||||
{185, 228, 169, 9},
|
||||
{0x2a, 0xd, 0x2a, 0x0, 0x0, 0x1, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x2},
|
||||
{0x2a, 0xd, 0x2a, 0x0, 0x0, 0x2, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x2},
|
||||
},
|
||||
SupportsTLS: true,
|
||||
SupportsIPv6: true,
|
||||
Host: models.DNSHost("security-filter-dns.cleanbrowsing.org"),
|
||||
},
|
||||
SecureDNS: {
|
||||
IPs: []net.IP{
|
||||
{146, 185, 167, 43},
|
||||
{0x2a, 0x3, 0xb0, 0xc0, 0x0, 0x0, 0x10, 0x10, 0x0, 0x0, 0x0, 0x0, 0xe, 0x9a, 0x30, 0x1},
|
||||
},
|
||||
SupportsTLS: true,
|
||||
SupportsIPv6: true,
|
||||
Host: models.DNSHost("dot.securedns.eu"),
|
||||
},
|
||||
LibreDNS: {
|
||||
IPs: []net.IP{{116, 203, 115, 192}},
|
||||
SupportsTLS: true,
|
||||
Host: models.DNSHost("dot.libredns.gr"),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// Block lists URLs
|
||||
//nolint:lll
|
||||
const (
|
||||
AdsBlockListHostnamesURL models.URL = "https://raw.githubusercontent.com/qdm12/files/master/ads-hostnames.updated"
|
||||
AdsBlockListIPsURL models.URL = "https://raw.githubusercontent.com/qdm12/files/master/ads-ips.updated"
|
||||
MaliciousBlockListHostnamesURL models.URL = "https://raw.githubusercontent.com/qdm12/files/master/malicious-hostnames.updated"
|
||||
MaliciousBlockListIPsURL models.URL = "https://raw.githubusercontent.com/qdm12/files/master/malicious-ips.updated"
|
||||
SurveillanceBlockListHostnamesURL models.URL = "https://raw.githubusercontent.com/qdm12/files/master/surveillance-hostnames.updated"
|
||||
SurveillanceBlockListIPsURL models.URL = "https://raw.githubusercontent.com/qdm12/files/master/surveillance-ips.updated"
|
||||
)
|
||||
|
||||
// DNS certificates to fetch.
|
||||
// TODO obtain from source directly, see qdm12/updated).
|
||||
const (
|
||||
NamedRootURL models.URL = "https://raw.githubusercontent.com/qdm12/files/master/named.root.updated"
|
||||
RootKeyURL models.URL = "https://raw.githubusercontent.com/qdm12/files/master/root.key.updated"
|
||||
)
|
||||
@@ -29,4 +29,6 @@ const (
|
||||
ClientKey models.Filepath = "/gluetun/client.key"
|
||||
// Client certificate filepath, used by Cyberghost.
|
||||
ClientCertificate models.Filepath = "/gluetun/client.crt"
|
||||
// Servers information filepath.
|
||||
ServersData = "/gluetun/servers.json"
|
||||
)
|
||||
|
||||
@@ -1,8 +0,0 @@
|
||||
package constants
|
||||
|
||||
import "os"
|
||||
|
||||
const (
|
||||
UserReadPermission os.FileMode = 0400
|
||||
AllReadWritePermissions os.FileMode = 0666
|
||||
)
|
||||
@@ -28,99 +28,104 @@ func PIAGeoChoices() (choices []string) {
|
||||
//nolint:lll
|
||||
func PIAServers() []models.PIAServer {
|
||||
return []models.PIAServer{
|
||||
{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}}}},
|
||||
{Region: "AU Melbourne", PortForward: true, OpenvpnUDP: models.PIAServerOpenvpn{CN: "melbourne404", IPs: []net.IP{{103, 2, 198, 93}}}, OpenvpnTCP: models.PIAServerOpenvpn{CN: "melbourne404", IPs: []net.IP{{103, 2, 198, 85}}}},
|
||||
{Region: "AU Perth", PortForward: true, OpenvpnUDP: models.PIAServerOpenvpn{CN: "perth403", IPs: []net.IP{{43, 250, 205, 156}}}, OpenvpnTCP: models.PIAServerOpenvpn{CN: "perth403", IPs: []net.IP{{43, 250, 205, 153}}}},
|
||||
{Region: "AU Sydney", PortForward: true, OpenvpnUDP: models.PIAServerOpenvpn{CN: "sydney408", IPs: []net.IP{{117, 120, 10, 56}}}, OpenvpnTCP: models.PIAServerOpenvpn{CN: "sydney408", IPs: []net.IP{{117, 120, 10, 48}}}},
|
||||
{Region: "Albania", PortForward: true, OpenvpnUDP: models.PIAServerOpenvpn{CN: "tirana402", IPs: []net.IP{{31, 171, 154, 115}}}, OpenvpnTCP: models.PIAServerOpenvpn{CN: "tirana402", IPs: []net.IP{{31, 171, 154, 118}}}},
|
||||
{Region: "Algeria", PortForward: true, OpenvpnUDP: models.PIAServerOpenvpn{CN: "algiers404", IPs: []net.IP{{176, 125, 228, 19}}}, OpenvpnTCP: models.PIAServerOpenvpn{CN: "algiers404", IPs: []net.IP{{176, 125, 228, 26}}}},
|
||||
{Region: "Andorra", PortForward: true, OpenvpnUDP: models.PIAServerOpenvpn{CN: "andorra405", IPs: []net.IP{{188, 241, 82, 40}}}, OpenvpnTCP: models.PIAServerOpenvpn{CN: "andorra405", IPs: []net.IP{{188, 241, 82, 37}}}},
|
||||
{Region: "Argentina", PortForward: true, OpenvpnUDP: models.PIAServerOpenvpn{CN: "buenosaires401", IPs: []net.IP{{190, 106, 134, 95}}}, OpenvpnTCP: models.PIAServerOpenvpn{CN: "buenosaires401", IPs: []net.IP{{190, 106, 134, 85}}}},
|
||||
{Region: "Armenia", PortForward: true, OpenvpnUDP: models.PIAServerOpenvpn{CN: "armenia403", IPs: []net.IP{{185, 253, 160, 3}}}, OpenvpnTCP: models.PIAServerOpenvpn{CN: "armenia403", IPs: []net.IP{{185, 253, 160, 9}}}},
|
||||
{Region: "Austria", PortForward: true, OpenvpnUDP: models.PIAServerOpenvpn{CN: "vienna403", IPs: []net.IP{{156, 146, 60, 108}}}, OpenvpnTCP: models.PIAServerOpenvpn{CN: "vienna403", IPs: []net.IP{{156, 146, 60, 110}}}},
|
||||
{Region: "Bahamas", PortForward: true, OpenvpnUDP: models.PIAServerOpenvpn{CN: "bahamas404", IPs: []net.IP{{95, 181, 238, 9}}}, OpenvpnTCP: models.PIAServerOpenvpn{CN: "bahamas404", IPs: []net.IP{{95, 181, 238, 9}}}},
|
||||
{Region: "Bangladesh", PortForward: true, OpenvpnUDP: models.PIAServerOpenvpn{CN: "bangladesh403", IPs: []net.IP{{84, 252, 93, 7}}}, OpenvpnTCP: models.PIAServerOpenvpn{CN: "bangladesh403", IPs: []net.IP{{84, 252, 93, 7}}}},
|
||||
{Region: "Belgium", PortForward: true, OpenvpnUDP: models.PIAServerOpenvpn{CN: "brussels404", IPs: []net.IP{{37, 120, 143, 156}}}, OpenvpnTCP: models.PIAServerOpenvpn{CN: "brussels404", IPs: []net.IP{{37, 120, 143, 156}}}},
|
||||
{Region: "Bosnia and Herzegovina", PortForward: true, OpenvpnUDP: models.PIAServerOpenvpn{CN: "sarajevo402", IPs: []net.IP{{185, 212, 111, 4}}}, OpenvpnTCP: models.PIAServerOpenvpn{CN: "sarajevo402", IPs: []net.IP{{185, 212, 111, 71}}}},
|
||||
{Region: "Brazil", PortForward: true, OpenvpnUDP: models.PIAServerOpenvpn{CN: "saopaolo401", IPs: []net.IP{{45, 133, 180, 236}}}, OpenvpnTCP: models.PIAServerOpenvpn{CN: "saopaolo401", IPs: []net.IP{{45, 133, 180, 227}}}},
|
||||
{Region: "Bulgaria", PortForward: true, OpenvpnUDP: models.PIAServerOpenvpn{CN: "sofia402", IPs: []net.IP{{217, 138, 221, 89}}}, OpenvpnTCP: models.PIAServerOpenvpn{CN: "sofia402", IPs: []net.IP{{217, 138, 221, 89}}}},
|
||||
{Region: "CA Montreal", PortForward: true, OpenvpnUDP: models.PIAServerOpenvpn{CN: "montreal410", IPs: []net.IP{{199, 36, 223, 210}}}, OpenvpnTCP: models.PIAServerOpenvpn{CN: "montreal410", IPs: []net.IP{{199, 36, 223, 210}}}},
|
||||
{Region: "CA Ontario", PortForward: true, OpenvpnUDP: models.PIAServerOpenvpn{CN: "ontario408", IPs: []net.IP{{172, 98, 92, 87}}}, OpenvpnTCP: models.PIAServerOpenvpn{CN: "ontario408", IPs: []net.IP{{172, 98, 92, 80}}}},
|
||||
{Region: "CA Toronto", PortForward: true, OpenvpnUDP: models.PIAServerOpenvpn{CN: "toronto402", IPs: []net.IP{{66, 115, 142, 58}}}, OpenvpnTCP: models.PIAServerOpenvpn{CN: "toronto402", IPs: []net.IP{{66, 115, 142, 58}}}},
|
||||
{Region: "CA Vancouver", PortForward: true, OpenvpnUDP: models.PIAServerOpenvpn{CN: "vancouver411", IPs: []net.IP{{208, 78, 42, 168}}}, OpenvpnTCP: models.PIAServerOpenvpn{CN: "vancouver411", IPs: []net.IP{{208, 78, 42, 161}}}},
|
||||
{Region: "Cambodia", PortForward: true, OpenvpnUDP: models.PIAServerOpenvpn{CN: "cambodia401", IPs: []net.IP{{188, 215, 235, 109}}}, OpenvpnTCP: models.PIAServerOpenvpn{CN: "cambodia401", IPs: []net.IP{{188, 215, 235, 110}}}},
|
||||
{Region: "China", PortForward: true, OpenvpnUDP: models.PIAServerOpenvpn{CN: "china404", IPs: []net.IP{{188, 241, 80, 9}}}, OpenvpnTCP: models.PIAServerOpenvpn{CN: "china404", IPs: []net.IP{{188, 241, 80, 4}}}},
|
||||
{Region: "Cyprus", PortForward: true, OpenvpnUDP: models.PIAServerOpenvpn{CN: "cyprus403", IPs: []net.IP{{185, 253, 162, 8}}}, OpenvpnTCP: models.PIAServerOpenvpn{CN: "cyprus403", IPs: []net.IP{{185, 253, 162, 14}}}},
|
||||
{Region: "Czech Republic", PortForward: true, OpenvpnUDP: models.PIAServerOpenvpn{CN: "prague405", IPs: []net.IP{{143, 244, 59, 168}}}, OpenvpnTCP: models.PIAServerOpenvpn{CN: "prague405", IPs: []net.IP{{143, 244, 59, 154}}}},
|
||||
{Region: "DE Berlin", PortForward: true, OpenvpnUDP: models.PIAServerOpenvpn{CN: "berlin425", IPs: []net.IP{{154, 13, 1, 148}}}, OpenvpnTCP: models.PIAServerOpenvpn{CN: "berlin425", IPs: []net.IP{{154, 13, 1, 146}}}},
|
||||
{Region: "DE Frankfurt", PortForward: true, OpenvpnUDP: models.PIAServerOpenvpn{CN: "frankfurt440", IPs: []net.IP{{185, 216, 33, 165}}}, OpenvpnTCP: models.PIAServerOpenvpn{CN: "frankfurt440", IPs: []net.IP{{185, 216, 33, 166}}}},
|
||||
{Region: "Denmark", PortForward: true, OpenvpnUDP: models.PIAServerOpenvpn{CN: "copenhagen404", IPs: []net.IP{{188, 126, 94, 190}}}, OpenvpnTCP: models.PIAServerOpenvpn{CN: "copenhagen404", IPs: []net.IP{{188, 126, 94, 165}}}},
|
||||
{Region: "Egypt", PortForward: true, OpenvpnUDP: models.PIAServerOpenvpn{CN: "cairo402", IPs: []net.IP{{188, 214, 122, 126}}}, OpenvpnTCP: models.PIAServerOpenvpn{CN: "cairo402", IPs: []net.IP{{188, 214, 122, 123}}}},
|
||||
{Region: "Estonia", PortForward: true, OpenvpnUDP: models.PIAServerOpenvpn{CN: "talinn402", IPs: []net.IP{{95, 153, 31, 68}}}, OpenvpnTCP: models.PIAServerOpenvpn{CN: "talinn402", IPs: []net.IP{{95, 153, 31, 78}}}},
|
||||
{Region: "Finland", PortForward: true, OpenvpnUDP: models.PIAServerOpenvpn{CN: "helsinki402", IPs: []net.IP{{188, 126, 89, 45}}}, OpenvpnTCP: models.PIAServerOpenvpn{CN: "helsinki402", IPs: []net.IP{{188, 126, 89, 35}}}},
|
||||
{Region: "France", PortForward: true, OpenvpnUDP: models.PIAServerOpenvpn{CN: "paris406", IPs: []net.IP{{143, 244, 57, 169}}}, OpenvpnTCP: models.PIAServerOpenvpn{CN: "paris406", IPs: []net.IP{{143, 244, 57, 169}}}},
|
||||
{Region: "Georgia", PortForward: true, OpenvpnUDP: models.PIAServerOpenvpn{CN: "georgia403", IPs: []net.IP{{95, 181, 236, 8}}}, OpenvpnTCP: models.PIAServerOpenvpn{CN: "georgia403", IPs: []net.IP{{95, 181, 236, 10}}}},
|
||||
{Region: "Greece", PortForward: true, OpenvpnUDP: models.PIAServerOpenvpn{CN: "athens401", IPs: []net.IP{{154, 57, 3, 87}}}, OpenvpnTCP: models.PIAServerOpenvpn{CN: "athens401", IPs: []net.IP{{154, 57, 3, 85}}}},
|
||||
{Region: "Greenland", PortForward: true, OpenvpnUDP: models.PIAServerOpenvpn{CN: "greenland404", IPs: []net.IP{{91, 90, 120, 149}}}, OpenvpnTCP: models.PIAServerOpenvpn{CN: "greenland404", IPs: []net.IP{{91, 90, 120, 147}}}},
|
||||
{Region: "Hong Kong", PortForward: true, OpenvpnUDP: models.PIAServerOpenvpn{CN: "china403", IPs: []net.IP{{86, 107, 104, 213}}}, OpenvpnTCP: models.PIAServerOpenvpn{CN: "china403", IPs: []net.IP{{86, 107, 104, 219}}}},
|
||||
{Region: "Hungary", PortForward: true, OpenvpnUDP: models.PIAServerOpenvpn{CN: "budapest402", IPs: []net.IP{{86, 106, 74, 115}}}, OpenvpnTCP: models.PIAServerOpenvpn{CN: "budapest402", IPs: []net.IP{{86, 106, 74, 117}}}},
|
||||
{Region: "Iceland", PortForward: true, OpenvpnUDP: models.PIAServerOpenvpn{CN: "reykjavik402", IPs: []net.IP{{45, 133, 193, 83}}}, OpenvpnTCP: models.PIAServerOpenvpn{CN: "reykjavik402", IPs: []net.IP{{45, 133, 193, 88}}}},
|
||||
{Region: "India", PortForward: true, OpenvpnUDP: models.PIAServerOpenvpn{CN: "mumbai402", IPs: []net.IP{{45, 120, 139, 138}}}, OpenvpnTCP: models.PIAServerOpenvpn{CN: "mumbai402", IPs: []net.IP{{45, 120, 139, 127}}}},
|
||||
{Region: "Ireland", PortForward: true, OpenvpnUDP: models.PIAServerOpenvpn{CN: "dublin411", IPs: []net.IP{{188, 241, 178, 23}}}, OpenvpnTCP: models.PIAServerOpenvpn{CN: "dublin411", IPs: []net.IP{{188, 241, 178, 30}}}},
|
||||
{Region: "Isle of Man", PortForward: true, OpenvpnUDP: models.PIAServerOpenvpn{CN: "douglas403", IPs: []net.IP{{91, 90, 124, 7}}}, OpenvpnTCP: models.PIAServerOpenvpn{CN: "douglas403", IPs: []net.IP{{91, 90, 124, 18}}}},
|
||||
{Region: "Israel", PortForward: true, OpenvpnUDP: models.PIAServerOpenvpn{CN: "jerusalem407", IPs: []net.IP{{185, 77, 248, 91}}}, OpenvpnTCP: models.PIAServerOpenvpn{CN: "jerusalem407", IPs: []net.IP{{185, 77, 248, 91}}}},
|
||||
{Region: "Italy", PortForward: true, OpenvpnUDP: models.PIAServerOpenvpn{CN: "milano403", IPs: []net.IP{{156, 146, 41, 74}}}, OpenvpnTCP: models.PIAServerOpenvpn{CN: "milano403", IPs: []net.IP{{156, 146, 41, 84}}}},
|
||||
{Region: "Japan", PortForward: true, OpenvpnUDP: models.PIAServerOpenvpn{CN: "tokyo401", IPs: []net.IP{{156, 146, 34, 130}}}, OpenvpnTCP: models.PIAServerOpenvpn{CN: "tokyo401", IPs: []net.IP{{156, 146, 34, 130}}}},
|
||||
{Region: "Kazakhstan", PortForward: true, OpenvpnUDP: models.PIAServerOpenvpn{CN: "kazakhstan403", IPs: []net.IP{{62, 133, 47, 13}}}, OpenvpnTCP: models.PIAServerOpenvpn{CN: "kazakhstan403", IPs: []net.IP{{62, 133, 47, 5}}}},
|
||||
{Region: "Latvia", PortForward: true, OpenvpnUDP: models.PIAServerOpenvpn{CN: "riga401", IPs: []net.IP{{109, 248, 149, 5}}}, OpenvpnTCP: models.PIAServerOpenvpn{CN: "riga401", IPs: []net.IP{{109, 248, 149, 8}}}},
|
||||
{Region: "Liechtenstein", PortForward: true, OpenvpnUDP: models.PIAServerOpenvpn{CN: "liechtenstein403", IPs: []net.IP{{91, 90, 122, 7}}}, OpenvpnTCP: models.PIAServerOpenvpn{CN: "liechtenstein403", IPs: []net.IP{{91, 90, 122, 18}}}},
|
||||
{Region: "Lithuania", PortForward: true, OpenvpnUDP: models.PIAServerOpenvpn{CN: "vilnius403", IPs: []net.IP{{85, 206, 165, 118}}}, OpenvpnTCP: models.PIAServerOpenvpn{CN: "vilnius403", IPs: []net.IP{{85, 206, 165, 116}}}},
|
||||
{Region: "Luxembourg", PortForward: true, OpenvpnUDP: models.PIAServerOpenvpn{CN: "luxembourg407", IPs: []net.IP{{5, 253, 204, 150}}}, OpenvpnTCP: models.PIAServerOpenvpn{CN: "luxembourg407", IPs: []net.IP{{5, 253, 204, 155}}}},
|
||||
{Region: "Macao", PortForward: true, OpenvpnUDP: models.PIAServerOpenvpn{CN: "macau403", IPs: []net.IP{{84, 252, 92, 6}}}, OpenvpnTCP: models.PIAServerOpenvpn{CN: "macau403", IPs: []net.IP{{84, 252, 92, 15}}}},
|
||||
{Region: "Macedonia", PortForward: true, OpenvpnUDP: models.PIAServerOpenvpn{CN: "macedonia401", IPs: []net.IP{{185, 225, 28, 120}}}, OpenvpnTCP: models.PIAServerOpenvpn{CN: "macedonia401", IPs: []net.IP{{185, 225, 28, 120}}}},
|
||||
{Region: "Malta", PortForward: true, OpenvpnUDP: models.PIAServerOpenvpn{CN: "malta403", IPs: []net.IP{{176, 125, 230, 13}}}, OpenvpnTCP: models.PIAServerOpenvpn{CN: "malta403", IPs: []net.IP{{176, 125, 230, 7}}}},
|
||||
{Region: "Mexico", PortForward: true, OpenvpnUDP: models.PIAServerOpenvpn{CN: "mexico403", IPs: []net.IP{{77, 81, 142, 14}}}, OpenvpnTCP: models.PIAServerOpenvpn{CN: "mexico403", IPs: []net.IP{{77, 81, 142, 13}}}},
|
||||
{Region: "Moldova", PortForward: true, OpenvpnUDP: models.PIAServerOpenvpn{CN: "chisinau401", IPs: []net.IP{{178, 175, 129, 44}}}, OpenvpnTCP: models.PIAServerOpenvpn{CN: "chisinau401", IPs: []net.IP{{178, 175, 129, 46}}}},
|
||||
{Region: "Monaco", PortForward: true, OpenvpnUDP: models.PIAServerOpenvpn{CN: "monaco403", IPs: []net.IP{{95, 181, 233, 8}}}, OpenvpnTCP: models.PIAServerOpenvpn{CN: "monaco403", IPs: []net.IP{{95, 181, 233, 5}}}},
|
||||
{Region: "Mongolia", PortForward: true, OpenvpnUDP: models.PIAServerOpenvpn{CN: "mongolia403", IPs: []net.IP{{185, 253, 163, 15}}}, OpenvpnTCP: models.PIAServerOpenvpn{CN: "mongolia403", IPs: []net.IP{{185, 253, 163, 5}}}},
|
||||
{Region: "Montenegro", PortForward: true, OpenvpnUDP: models.PIAServerOpenvpn{CN: "montenegro403", IPs: []net.IP{{176, 125, 229, 14}}}, OpenvpnTCP: models.PIAServerOpenvpn{CN: "montenegro403", IPs: []net.IP{{176, 125, 229, 4}}}},
|
||||
{Region: "Morocco", PortForward: true, OpenvpnUDP: models.PIAServerOpenvpn{CN: "morocco403", IPs: []net.IP{{95, 181, 232, 4}}}, OpenvpnTCP: models.PIAServerOpenvpn{CN: "morocco403", IPs: []net.IP{{95, 181, 232, 8}}}},
|
||||
{Region: "Netherlands", PortForward: true, OpenvpnUDP: models.PIAServerOpenvpn{CN: "amsterdam412", IPs: []net.IP{{143, 244, 41, 196}}}, OpenvpnTCP: models.PIAServerOpenvpn{CN: "amsterdam412", IPs: []net.IP{{143, 244, 41, 196}}}},
|
||||
{Region: "New Zealand", PortForward: true, OpenvpnUDP: models.PIAServerOpenvpn{CN: "newzealand403", IPs: []net.IP{{43, 250, 207, 90}}}, OpenvpnTCP: models.PIAServerOpenvpn{CN: "newzealand403", IPs: []net.IP{{43, 250, 207, 84}}}},
|
||||
{Region: "Nigeria", PortForward: true, OpenvpnUDP: models.PIAServerOpenvpn{CN: "nigeria403", IPs: []net.IP{{102, 165, 25, 86}}}, OpenvpnTCP: models.PIAServerOpenvpn{CN: "nigeria403", IPs: []net.IP{{102, 165, 25, 85}}}},
|
||||
{Region: "Norway", PortForward: true, OpenvpnUDP: models.PIAServerOpenvpn{CN: "oslo401", IPs: []net.IP{{46, 246, 122, 37}}}, OpenvpnTCP: models.PIAServerOpenvpn{CN: "oslo401", IPs: []net.IP{{46, 246, 122, 60}}}},
|
||||
{Region: "Panama", PortForward: true, OpenvpnUDP: models.PIAServerOpenvpn{CN: "panama404", IPs: []net.IP{{91, 90, 126, 25}}}, OpenvpnTCP: models.PIAServerOpenvpn{CN: "panama404", IPs: []net.IP{{91, 90, 126, 28}}}},
|
||||
{Region: "Philippines", PortForward: true, OpenvpnUDP: models.PIAServerOpenvpn{CN: "philippines401", IPs: []net.IP{{188, 214, 125, 140}}}, OpenvpnTCP: models.PIAServerOpenvpn{CN: "philippines401", IPs: []net.IP{{188, 214, 125, 137}}}},
|
||||
{Region: "Poland", PortForward: true, OpenvpnUDP: models.PIAServerOpenvpn{CN: "warsaw409", IPs: []net.IP{{194, 110, 114, 119}}}, OpenvpnTCP: models.PIAServerOpenvpn{CN: "warsaw409", IPs: []net.IP{{194, 110, 114, 118}}}},
|
||||
{Region: "Portugal", PortForward: true, OpenvpnUDP: models.PIAServerOpenvpn{CN: "lisbon402", IPs: []net.IP{{89, 26, 241, 87}}}, OpenvpnTCP: models.PIAServerOpenvpn{CN: "lisbon402", IPs: []net.IP{{89, 26, 241, 88}}}},
|
||||
{Region: "Qatar", PortForward: true, OpenvpnUDP: models.PIAServerOpenvpn{CN: "qatar403", IPs: []net.IP{{95, 181, 234, 9}}}, OpenvpnTCP: models.PIAServerOpenvpn{CN: "qatar403", IPs: []net.IP{{95, 181, 234, 8}}}},
|
||||
{Region: "Romania", PortForward: true, OpenvpnUDP: models.PIAServerOpenvpn{CN: "romania408", IPs: []net.IP{{143, 244, 54, 117}}}, OpenvpnTCP: models.PIAServerOpenvpn{CN: "romania408", IPs: []net.IP{{143, 244, 54, 116}}}},
|
||||
{Region: "Saudi Arabia", PortForward: true, OpenvpnUDP: models.PIAServerOpenvpn{CN: "saudiarabia403", IPs: []net.IP{{95, 181, 235, 8}}}, OpenvpnTCP: models.PIAServerOpenvpn{CN: "saudiarabia403", IPs: []net.IP{{95, 181, 235, 4}}}},
|
||||
{Region: "Serbia", PortForward: true, OpenvpnUDP: models.PIAServerOpenvpn{CN: "belgrade401", IPs: []net.IP{{37, 120, 193, 250}}}, OpenvpnTCP: models.PIAServerOpenvpn{CN: "belgrade401", IPs: []net.IP{{37, 120, 193, 249}}}},
|
||||
{Region: "Singapore", PortForward: true, OpenvpnUDP: models.PIAServerOpenvpn{CN: "singapore401", IPs: []net.IP{{156, 146, 57, 213}}}, OpenvpnTCP: models.PIAServerOpenvpn{CN: "singapore401", IPs: []net.IP{{156, 146, 57, 197}}}},
|
||||
{Region: "Slovakia", PortForward: true, OpenvpnUDP: models.PIAServerOpenvpn{CN: "bratislava402", IPs: []net.IP{{37, 120, 221, 213}}}, OpenvpnTCP: models.PIAServerOpenvpn{CN: "bratislava402", IPs: []net.IP{{37, 120, 221, 218}}}},
|
||||
{Region: "South Africa", PortForward: true, OpenvpnUDP: models.PIAServerOpenvpn{CN: "johannesburg401", IPs: []net.IP{{154, 16, 93, 33}}}, OpenvpnTCP: models.PIAServerOpenvpn{CN: "johannesburg401", IPs: []net.IP{{154, 16, 93, 46}}}},
|
||||
{Region: "Spain", PortForward: true, OpenvpnUDP: models.PIAServerOpenvpn{CN: "madrid401", IPs: []net.IP{{212, 102, 49, 68}}}, OpenvpnTCP: models.PIAServerOpenvpn{CN: "madrid401", IPs: []net.IP{{212, 102, 49, 68}}}},
|
||||
{Region: "Sri Lanka", PortForward: true, OpenvpnUDP: models.PIAServerOpenvpn{CN: "srilanka403", IPs: []net.IP{{95, 181, 239, 8}}}, OpenvpnTCP: models.PIAServerOpenvpn{CN: "srilanka403", IPs: []net.IP{{95, 181, 239, 13}}}},
|
||||
{Region: "Sweden", PortForward: true, OpenvpnUDP: models.PIAServerOpenvpn{CN: "stockholm401", IPs: []net.IP{{195, 246, 120, 4}}}, OpenvpnTCP: models.PIAServerOpenvpn{CN: "stockholm401", IPs: []net.IP{{195, 246, 120, 39}}}},
|
||||
{Region: "Switzerland", PortForward: true, OpenvpnUDP: models.PIAServerOpenvpn{CN: "zurich407", IPs: []net.IP{{156, 146, 62, 194}}}, OpenvpnTCP: models.PIAServerOpenvpn{CN: "zurich407", IPs: []net.IP{{156, 146, 62, 194}}}},
|
||||
{Region: "Taiwan", PortForward: true, OpenvpnUDP: models.PIAServerOpenvpn{CN: "taiwan401", IPs: []net.IP{{188, 214, 106, 74}}}, OpenvpnTCP: models.PIAServerOpenvpn{CN: "taiwan401", IPs: []net.IP{{188, 214, 106, 69}}}},
|
||||
{Region: "Turkey", PortForward: true, OpenvpnUDP: models.PIAServerOpenvpn{CN: "istanbul402", IPs: []net.IP{{188, 213, 34, 88}}}, OpenvpnTCP: models.PIAServerOpenvpn{CN: "istanbul402", IPs: []net.IP{{188, 213, 34, 83}}}},
|
||||
{Region: "UK London", PortForward: true, OpenvpnUDP: models.PIAServerOpenvpn{CN: "london405", IPs: []net.IP{{212, 102, 53, 15}}}, OpenvpnTCP: models.PIAServerOpenvpn{CN: "london405", IPs: []net.IP{{212, 102, 53, 60}}}},
|
||||
{Region: "UK London-2", PortForward: true, OpenvpnUDP: models.PIAServerOpenvpn{CN: "london420", IPs: []net.IP{{37, 235, 96, 200}}}, OpenvpnTCP: models.PIAServerOpenvpn{CN: "london420", IPs: []net.IP{{37, 235, 96, 206}}}},
|
||||
{Region: "UK Manchester", PortForward: true, OpenvpnUDP: models.PIAServerOpenvpn{CN: "manchester414", IPs: []net.IP{{194, 37, 96, 194}}}, OpenvpnTCP: models.PIAServerOpenvpn{CN: "manchester414", IPs: []net.IP{{194, 37, 96, 197}}}},
|
||||
{Region: "UK Southampton", PortForward: true, OpenvpnUDP: models.PIAServerOpenvpn{CN: "southampton401", IPs: []net.IP{{143, 244, 37, 244}}}, OpenvpnTCP: models.PIAServerOpenvpn{CN: "southampton401", IPs: []net.IP{{143, 244, 37, 194}}}},
|
||||
{Region: "US Atlanta", PortForward: false, OpenvpnUDP: models.PIAServerOpenvpn{CN: "atlanta417", IPs: []net.IP{{154, 21, 22, 216}}}, OpenvpnTCP: models.PIAServerOpenvpn{CN: "atlanta417", IPs: []net.IP{{154, 21, 22, 216}}}},
|
||||
{Region: "US California", PortForward: false, OpenvpnUDP: models.PIAServerOpenvpn{CN: "losangeles401", IPs: []net.IP{{143, 244, 48, 15}}}, OpenvpnTCP: models.PIAServerOpenvpn{CN: "losangeles401", IPs: []net.IP{{143, 244, 48, 17}}}},
|
||||
{Region: "US Chicago", PortForward: false, OpenvpnUDP: models.PIAServerOpenvpn{CN: "chicago413", IPs: []net.IP{{154, 21, 23, 125}}}, OpenvpnTCP: models.PIAServerOpenvpn{CN: "chicago413", IPs: []net.IP{{154, 21, 23, 137}}}},
|
||||
{Region: "US Denver", PortForward: false, OpenvpnUDP: models.PIAServerOpenvpn{CN: "denver410", IPs: []net.IP{{174, 128, 227, 24}}}, OpenvpnTCP: models.PIAServerOpenvpn{CN: "denver410", IPs: []net.IP{{174, 128, 227, 24}}}},
|
||||
{Region: "US East", PortForward: false, OpenvpnUDP: models.PIAServerOpenvpn{CN: "newjersey405", IPs: []net.IP{{143, 244, 46, 65}}}, OpenvpnTCP: models.PIAServerOpenvpn{CN: "newjersey405", IPs: []net.IP{{143, 244, 46, 115}}}},
|
||||
{Region: "US Florida", PortForward: false, OpenvpnUDP: models.PIAServerOpenvpn{CN: "miami407", IPs: []net.IP{{156, 146, 42, 78}}}, OpenvpnTCP: models.PIAServerOpenvpn{CN: "miami407", IPs: []net.IP{{156, 146, 42, 113}}}},
|
||||
{Region: "US Houston", PortForward: false, OpenvpnUDP: models.PIAServerOpenvpn{CN: "houston413", IPs: []net.IP{{205, 251, 142, 20}}}, OpenvpnTCP: models.PIAServerOpenvpn{CN: "houston413", IPs: []net.IP{{205, 251, 142, 20}}}},
|
||||
{Region: "US Las Vegas", PortForward: false, OpenvpnUDP: models.PIAServerOpenvpn{CN: "lasvegas426", IPs: []net.IP{{196, 53, 64, 175}}}, OpenvpnTCP: models.PIAServerOpenvpn{CN: "lasvegas426", IPs: []net.IP{{196, 53, 64, 189}}}},
|
||||
{Region: "US New York", PortForward: false, OpenvpnUDP: models.PIAServerOpenvpn{CN: "newyork404", IPs: []net.IP{{143, 244, 44, 227}}}, OpenvpnTCP: models.PIAServerOpenvpn{CN: "newyork404", IPs: []net.IP{{143, 244, 44, 208}}}},
|
||||
{Region: "US Seattle", PortForward: false, OpenvpnUDP: models.PIAServerOpenvpn{CN: "seattle422", IPs: []net.IP{{156, 146, 48, 204}}}, OpenvpnTCP: models.PIAServerOpenvpn{CN: "seattle422", IPs: []net.IP{{156, 146, 48, 239}}}},
|
||||
{Region: "US Silicon Valley", PortForward: false, OpenvpnUDP: models.PIAServerOpenvpn{CN: "siliconvalley420", IPs: []net.IP{{66, 115, 165, 93}}}, OpenvpnTCP: models.PIAServerOpenvpn{CN: "siliconvalley420", IPs: []net.IP{{66, 115, 165, 80}}}},
|
||||
{Region: "US Texas", PortForward: false, OpenvpnUDP: models.PIAServerOpenvpn{CN: "dallas415", IPs: []net.IP{{154, 3, 250, 16}}}, OpenvpnTCP: models.PIAServerOpenvpn{CN: "dallas415", IPs: []net.IP{{154, 3, 250, 24}}}},
|
||||
{Region: "US Washington DC", PortForward: false, OpenvpnUDP: models.PIAServerOpenvpn{CN: "washington452", IPs: []net.IP{{91, 149, 244, 110}}}, OpenvpnTCP: models.PIAServerOpenvpn{CN: "washington452", IPs: []net.IP{{91, 149, 244, 114}}}},
|
||||
{Region: "US West", PortForward: false, OpenvpnUDP: models.PIAServerOpenvpn{CN: "phoenix413", IPs: []net.IP{{184, 170, 241, 169}}}, OpenvpnTCP: models.PIAServerOpenvpn{CN: "phoenix413", IPs: []net.IP{{184, 170, 241, 169}}}},
|
||||
{Region: "Ukraine", PortForward: true, OpenvpnUDP: models.PIAServerOpenvpn{CN: "kiev403", IPs: []net.IP{{62, 149, 20, 7}}}, OpenvpnTCP: models.PIAServerOpenvpn{CN: "kiev403", IPs: []net.IP{{62, 149, 20, 4}}}},
|
||||
{Region: "United Arab Emirates", PortForward: true, OpenvpnUDP: models.PIAServerOpenvpn{CN: "dubai403", IPs: []net.IP{{217, 138, 193, 153}}}, OpenvpnTCP: models.PIAServerOpenvpn{CN: "dubai403", IPs: []net.IP{{217, 138, 193, 150}}}},
|
||||
{Region: "Venezuela", PortForward: true, OpenvpnUDP: models.PIAServerOpenvpn{CN: "venezuela403", IPs: []net.IP{{95, 181, 237, 3}}}, OpenvpnTCP: models.PIAServerOpenvpn{CN: "venezuela403", IPs: []net.IP{{95, 181, 237, 9}}}},
|
||||
{Region: "Vietnam", PortForward: true, OpenvpnUDP: models.PIAServerOpenvpn{CN: "vietnam401", IPs: []net.IP{{188, 214, 152, 67}}}, OpenvpnTCP: models.PIAServerOpenvpn{CN: "vietnam401", IPs: []net.IP{{188, 214, 152, 67}}}},
|
||||
}
|
||||
}
|
||||
|
||||
@@ -20,7 +20,6 @@ func PrivadoHostnameChoices() (choices []string) {
|
||||
return choices
|
||||
}
|
||||
|
||||
//nolint:gomnd
|
||||
func PrivadoServers() []models.PrivadoServer {
|
||||
return []models.PrivadoServer{
|
||||
{Hostname: "akl-001.vpn.privado.io", IP: net.IP{23, 254, 104, 114}},
|
||||
|
||||
@@ -44,155 +44,46 @@ func PurevpnCityChoices() (choices []string) {
|
||||
//nolint:lll
|
||||
func PurevpnServers() []models.PurevpnServer {
|
||||
return []models.PurevpnServer{
|
||||
{Region: "Africa", Country: "Algeria", City: "Algiers", IPs: []net.IP{{172, 94, 64, 2}}},
|
||||
{Region: "Africa", Country: "Angola", City: "Benguela", IPs: []net.IP{{45, 115, 26, 2}}},
|
||||
{Region: "Africa", Country: "Cape Verde", City: "Praia", IPs: []net.IP{{45, 74, 25, 2}}},
|
||||
{Region: "Africa", Country: "Egypt", City: "Cairo", IPs: []net.IP{{192, 198, 120, 122}}},
|
||||
{Region: "Africa", Country: "Ethiopia", City: "Addis Ababa", IPs: []net.IP{{104, 250, 178, 4}}},
|
||||
{Region: "Africa", Country: "Ghana", City: "Accra", IPs: []net.IP{{196, 251, 67, 4}}},
|
||||
{Region: "Africa", Country: "Kenya", City: "Mombasa", IPs: []net.IP{{102, 135, 0, 2}}},
|
||||
{Region: "Africa", Country: "Madagascar", City: "Antananarivo", IPs: []net.IP{{206, 123, 156, 131}}},
|
||||
{Region: "Africa", Country: "Mauritania", City: "Nouakchott", IPs: []net.IP{{206, 123, 158, 63}}},
|
||||
{Region: "Africa", Country: "Mauritius", City: "Port Louis", IPs: []net.IP{{104, 250, 181, 4}}},
|
||||
{Region: "Africa", Country: "Morocco", City: "Rabat", IPs: []net.IP{{104, 243, 250, 126}}},
|
||||
{Region: "Africa", Country: "Niger", City: "Niamey", IPs: []net.IP{{206, 123, 157, 131}}},
|
||||
{Region: "Africa", Country: "Nigeria", City: "Suleja", IPs: []net.IP{{102, 165, 25, 38}}},
|
||||
{Region: "Africa", Country: "Senegal", City: "Dakar", IPs: []net.IP{{206, 123, 158, 131}}},
|
||||
{Region: "Africa", Country: "Seychelles", City: "Victoria", IPs: []net.IP{{172, 111, 128, 126}}},
|
||||
{Region: "Africa", Country: "South Africa", City: "Johannesburg", IPs: []net.IP{{45, 74, 45, 2}}},
|
||||
{Region: "Africa", Country: "Tanzania", City: "Dar Es Salaam", IPs: []net.IP{{102, 135, 0, 2}}},
|
||||
{Region: "Africa", Country: "Tunisia", City: "Tunis", IPs: []net.IP{{206, 123, 159, 4}}},
|
||||
{Region: "Asia", Country: "Afghanistan", City: "Kabul", IPs: []net.IP{{172, 111, 208, 2}}},
|
||||
{Region: "Asia", Country: "Armenia", City: "Singapore", IPs: []net.IP{{37, 120, 208, 147}}},
|
||||
{Region: "Asia", Country: "Azerbaijan", City: "Baku", IPs: []net.IP{{104, 250, 177, 4}}},
|
||||
{Region: "Asia", Country: "Bangladesh", City: "Dhaka", IPs: []net.IP{{206, 123, 154, 190}}},
|
||||
{Region: "Asia", Country: "Brunei Darussalam", City: "Bandar Seri Begawan", IPs: []net.IP{{36, 255, 98, 2}}},
|
||||
{Region: "Asia", Country: "Cambodia", City: "Phnom Penh", IPs: []net.IP{{104, 250, 176, 122}}},
|
||||
{Region: "Asia", Country: "India", City: "Chennai", IPs: []net.IP{{129, 227, 107, 242}}},
|
||||
{Region: "Asia", Country: "Indonesia", City: "Jakarta", IPs: []net.IP{{103, 55, 9, 2}}},
|
||||
{Region: "Asia", Country: "Japan", City: "Tokyo", IPs: []net.IP{{172, 94, 56, 2}}},
|
||||
{Region: "Asia", Country: "Kazakhstan", City: "Almaty", IPs: []net.IP{{206, 123, 152, 4}}},
|
||||
{Region: "Asia", Country: "Korea, South", City: "Seoul", IPs: []net.IP{{45, 115, 25, 1}}},
|
||||
{Region: "Asia", Country: "Kyrgyzstan", City: "Bishkek", IPs: []net.IP{{206, 123, 151, 131}}},
|
||||
{Region: "Asia", Country: "Laos", City: "Vientiane", IPs: []net.IP{{206, 123, 153, 4}}},
|
||||
{Region: "Asia", Country: "Macao", City: "Beyrouth", IPs: []net.IP{{104, 243, 240, 121}}},
|
||||
{Region: "Asia", Country: "Malaysia", City: "Johor Baharu", IPs: []net.IP{{103, 28, 90, 54}, {103, 28, 90, 55}, {103, 28, 90, 71}, {103, 28, 90, 72}, {103, 117, 20, 21}, {103, 117, 20, 163}, {103, 117, 20, 164}, {103, 117, 20, 201}}},
|
||||
{Region: "Asia", Country: "Malaysia", City: "Kuala Lumpur", IPs: []net.IP{{104, 250, 160, 4}}},
|
||||
{Region: "Asia", Country: "Mongolia", City: "Ulaanbaatar", IPs: []net.IP{{206, 123, 153, 131}}},
|
||||
{Region: "Asia", Country: "Pakistan", City: "Islamabad", IPs: []net.IP{{104, 250, 187, 3}}},
|
||||
{Region: "Asia", Country: "Papua New Guinea", City: "Port Moresby", IPs: []net.IP{{206, 123, 155, 131}}},
|
||||
{Region: "Asia", Country: "Philippines", City: "Manila", IPs: []net.IP{{129, 227, 119, 84}}},
|
||||
{Region: "Asia", Country: "Sri Lanka", City: "Colombo", IPs: []net.IP{{206, 123, 154, 4}}},
|
||||
{Region: "Asia", Country: "Taiwan", City: "Taipei", IPs: []net.IP{{128, 1, 155, 178}}},
|
||||
{Region: "Asia", Country: "Tajikistan", City: "Dushanbe", IPs: []net.IP{{206, 123, 151, 4}}},
|
||||
{Region: "Asia", Country: "Thailand", City: "Bangkok", IPs: []net.IP{{104, 37, 6, 4}}},
|
||||
{Region: "Asia", Country: "Turkey", City: "Istanbul", IPs: []net.IP{{185, 220, 58, 3}}},
|
||||
{Region: "Asia", Country: "Turkmenistan", City: "Ashgabat", IPs: []net.IP{{206, 123, 152, 131}}},
|
||||
{Region: "Asia", Country: "Uzbekistan", City: "Tashkent", IPs: []net.IP{{206, 123, 150, 131}}},
|
||||
{Region: "Asia", Country: "Vietnam", City: "Hanoi", IPs: []net.IP{{192, 253, 249, 132}}},
|
||||
{Region: "Europe", Country: "Albania", City: "Tirane", IPs: []net.IP{{46, 243, 224, 2}}},
|
||||
{Region: "Europe", Country: "Armenia", City: "Yerevan", IPs: []net.IP{{172, 94, 35, 4}}},
|
||||
{Region: "Europe", Country: "Austria", City: "Vienna", IPs: []net.IP{{172, 94, 109, 2}}},
|
||||
{Region: "Europe", Country: "Belgium", City: "Brussels", IPs: []net.IP{{185, 210, 217, 147}}},
|
||||
{Region: "Europe", Country: "Bosnia and Herzegovina", City: "Sarajevo", IPs: []net.IP{{104, 250, 169, 122}}},
|
||||
{Region: "Europe", Country: "Bulgaria", City: "Sofia", IPs: []net.IP{{217, 138, 221, 114}}},
|
||||
{Region: "Europe", Country: "Croatia", City: "Zagreb", IPs: []net.IP{{104, 250, 163, 2}}},
|
||||
{Region: "Europe", Country: "Cyprus", City: "Nicosia", IPs: []net.IP{{188, 72, 119, 4}}},
|
||||
{Region: "Europe", Country: "Denmark", City: "Copenhagen", IPs: []net.IP{{89, 45, 7, 5}}},
|
||||
{Region: "Europe", Country: "Estonia", City: "Tallinn", IPs: []net.IP{{185, 166, 87, 2}, {188, 72, 111, 4}}},
|
||||
{Region: "Europe", Country: "France", City: "Paris", IPs: []net.IP{{172, 94, 53, 2}, {172, 111, 219, 2}}},
|
||||
{Region: "Europe", Country: "Georgia", City: "Tbilisi", IPs: []net.IP{{141, 101, 156, 2}}},
|
||||
{Region: "Europe", Country: "Germany", City: "Frankfurt", IPs: []net.IP{{172, 94, 8, 4}}},
|
||||
{Region: "Europe", Country: "Germany", City: "Munich", IPs: []net.IP{{172, 94, 8, 4}}},
|
||||
{Region: "Europe", Country: "Germany", City: "Nuremberg", IPs: []net.IP{{172, 94, 125, 2}}},
|
||||
{Region: "Europe", Country: "Greece", City: "Thessaloniki", IPs: []net.IP{{172, 94, 109, 2}}},
|
||||
{Region: "Europe", Country: "Hungary", City: "Budapest", IPs: []net.IP{{172, 111, 129, 2}, {188, 72, 125, 126}}},
|
||||
{Region: "Europe", Country: "Iceland", City: "Reykjavik", IPs: []net.IP{{192, 253, 250, 1}}},
|
||||
{Region: "Europe", Country: "Ireland", City: "Dublin", IPs: []net.IP{{185, 210, 217, 147}}},
|
||||
{Region: "Europe", Country: "Isle of Man", City: "Onchan", IPs: []net.IP{{46, 243, 144, 2}}},
|
||||
{Region: "Europe", Country: "Italy", City: "Milano", IPs: []net.IP{{45, 9, 251, 2}}},
|
||||
{Region: "Europe", Country: "Latvia", City: "RIGA", IPs: []net.IP{{185, 118, 76, 5}}},
|
||||
{Region: "Europe", Country: "Liechtenstein", City: "Vaduz", IPs: []net.IP{{104, 250, 164, 4}}},
|
||||
{Region: "Europe", Country: "Lithuania", City: "Vilnius", IPs: []net.IP{{188, 72, 116, 3}}},
|
||||
{Region: "Europe", Country: "Luxembourg", City: "Luxembourg", IPs: []net.IP{{188, 72, 114, 2}}},
|
||||
{Region: "Europe", Country: "Malta", City: "Sliema", IPs: []net.IP{{46, 243, 241, 4}}},
|
||||
{Region: "Europe", Country: "Monaco", City: "Monaco", IPs: []net.IP{{104, 250, 168, 132}}},
|
||||
{Region: "Europe", Country: "Montenegro", City: "Podgorica", IPs: []net.IP{{104, 250, 165, 121}}},
|
||||
{Region: "Europe", Country: "Netherlands", City: "Amsterdam", IPs: []net.IP{{92, 119, 179, 195}}},
|
||||
{Region: "Europe", Country: "Norway", City: "Oslo", IPs: []net.IP{{82, 102, 22, 211}}},
|
||||
{Region: "Europe", Country: "Poland", City: "Warsaw", IPs: []net.IP{{5, 253, 206, 251}}},
|
||||
{Region: "Europe", Country: "Portugal", City: "Lisbon", IPs: []net.IP{{45, 74, 10, 1}}},
|
||||
{Region: "Europe", Country: "Romania", City: "Bucharest", IPs: []net.IP{{192, 253, 253, 2}}},
|
||||
{Region: "Europe", Country: "Serbia", City: "Niš", IPs: []net.IP{{104, 250, 166, 2}}},
|
||||
{Region: "Europe", Country: "Slovakia", City: "Bratislava", IPs: []net.IP{{188, 72, 112, 3}}},
|
||||
{Region: "Europe", Country: "Slovenia", City: "Ljubljana", IPs: []net.IP{{104, 243, 246, 129}}},
|
||||
{Region: "Europe", Country: "Spain", City: "Barcelona", IPs: []net.IP{{185, 230, 124, 147}}},
|
||||
{Region: "Europe", Country: "Sweden", City: "Stockholm", IPs: []net.IP{{45, 74, 46, 2}}},
|
||||
{Region: "Europe", Country: "Switzerland", City: "Zurich", IPs: []net.IP{{172, 111, 217, 2}}},
|
||||
{Region: "Europe", Country: "United Kingdom", City: "Gosport", IPs: []net.IP{{45, 74, 0, 2}, {45, 74, 62, 2}}},
|
||||
{Region: "Europe", Country: "United Kingdom", City: "London", IPs: []net.IP{{45, 74, 0, 2}, {45, 74, 62, 2}}},
|
||||
{Region: "Europe", Country: "United Kingdom", City: "Maidenhead", IPs: []net.IP{{172, 111, 183, 2}}},
|
||||
{Region: "Europe", Country: "United Kingdom", City: "Manchester", IPs: []net.IP{{172, 111, 183, 2}}},
|
||||
{Region: "Middle East", Country: "Bahrain", City: "Manama", IPs: []net.IP{{46, 243, 150, 4}}},
|
||||
{Region: "Middle East", Country: "Jordan", City: "Amman", IPs: []net.IP{{172, 111, 152, 3}}},
|
||||
{Region: "Middle East", Country: "Kuwait", City: "Kuwait", IPs: []net.IP{{206, 123, 146, 4}}},
|
||||
{Region: "Middle East", Country: "Oman", City: "Salalah", IPs: []net.IP{{46, 243, 148, 125}}},
|
||||
{Region: "Middle East", Country: "Qatar", City: "Doha", IPs: []net.IP{{46, 243, 147, 2}}},
|
||||
{Region: "Middle East", Country: "Saudi Arabia", City: "Jeddah", IPs: []net.IP{{45, 74, 1, 4}}},
|
||||
{Region: "Middle East", Country: "United Arab Emirates", City: "Dubai", IPs: []net.IP{{104, 37, 6, 4}}},
|
||||
{Region: "North America", Country: "Aruba", City: "Oranjestad", IPs: []net.IP{{104, 243, 246, 129}}},
|
||||
{Region: "North America", Country: "Barbados", City: "Bridgetown", IPs: []net.IP{{172, 94, 97, 2}}},
|
||||
{Region: "North America", Country: "Belize", City: "Belmopan", IPs: []net.IP{{104, 243, 241, 4}}},
|
||||
{Region: "North America", Country: "Bermuda", City: "Hamilton", IPs: []net.IP{{172, 94, 76, 2}}},
|
||||
{Region: "North America", Country: "Canada", City: "Montreal", IPs: []net.IP{{172, 94, 7, 2}}},
|
||||
{Region: "North America", Country: "Canada", City: "Toronto", IPs: []net.IP{{172, 94, 7, 2}}},
|
||||
{Region: "North America", Country: "Canada", City: "Vancouver", IPs: []net.IP{{107, 181, 177, 42}}},
|
||||
{Region: "North America", Country: "Cayman Islands", City: "George Town", IPs: []net.IP{{172, 94, 113, 2}}},
|
||||
{Region: "North America", Country: "Costa Rica", City: "San Jose", IPs: []net.IP{{104, 243, 245, 1}}},
|
||||
{Region: "North America", Country: "Dominica", City: "Roseau", IPs: []net.IP{{45, 74, 22, 2}}},
|
||||
{Region: "North America", Country: "Dominican Republic", City: "Santo Domingo", IPs: []net.IP{{45, 74, 23, 129}}},
|
||||
{Region: "North America", Country: "El Salvador", City: "San Salvador", IPs: []net.IP{{45, 74, 17, 129}}},
|
||||
{Region: "North America", Country: "Grenada", City: "St George's", IPs: []net.IP{{45, 74, 21, 129}}},
|
||||
{Region: "North America", Country: "Guatemala", City: "Guatemala", IPs: []net.IP{{45, 74, 17, 2}}},
|
||||
{Region: "North America", Country: "Haiti", City: "PORT-AU-PRINCE", IPs: []net.IP{{45, 74, 24, 2}}},
|
||||
{Region: "North America", Country: "Honduras", City: "TEGUCIGALPA", IPs: []net.IP{{45, 74, 18, 2}}},
|
||||
{Region: "North America", Country: "Jamaica", City: "Kingston", IPs: []net.IP{{104, 250, 182, 126}}},
|
||||
{Region: "North America", Country: "Mexico", City: "Mexico City", IPs: []net.IP{{104, 243, 243, 131}}},
|
||||
{Region: "North America", Country: "Montserrat", City: "plymouth", IPs: []net.IP{{45, 74, 26, 190}}},
|
||||
{Region: "North America", Country: "Puerto Rico", City: "San Juan", IPs: []net.IP{{104, 37, 2, 2}}},
|
||||
{Region: "North America", Country: "Saint Lucia", City: "Castries", IPs: []net.IP{{45, 74, 23, 2}}},
|
||||
{Region: "North America", Country: "The Bahamas", City: "Freeport", IPs: []net.IP{{104, 243, 242, 2}}},
|
||||
{Region: "North America", Country: "Trinidad and Tobago", City: "Port of Spain", IPs: []net.IP{{45, 74, 21, 2}}},
|
||||
{Region: "North America", Country: "Turks and Caicos Islands", City: "Balfour Town", IPs: []net.IP{{45, 74, 24, 129}}},
|
||||
{Region: "North America", Country: "United States", City: "Ashburn", IPs: []net.IP{{46, 243, 249, 2}}},
|
||||
{Region: "North America", Country: "United States", City: "Chicago", IPs: []net.IP{{46, 243, 249, 4}}},
|
||||
{Region: "North America", Country: "United States", City: "Columbus", IPs: []net.IP{{172, 94, 115, 2}}},
|
||||
{Region: "North America", Country: "United States", City: "Georgia", IPs: []net.IP{{141, 101, 168, 4}}},
|
||||
{Region: "North America", Country: "United States", City: "Houston", IPs: []net.IP{{172, 94, 1, 4}}},
|
||||
{Region: "North America", Country: "United States", City: "Los Angeles", IPs: []net.IP{{141, 101, 169, 4}}},
|
||||
{Region: "North America", Country: "United States", City: "Miami", IPs: []net.IP{{5, 254, 79, 114}}},
|
||||
{Region: "North America", Country: "United States", City: "New Jersey", IPs: []net.IP{{172, 94, 1, 4}}},
|
||||
{Region: "North America", Country: "United States", City: "New York", IPs: []net.IP{{172, 94, 1, 4}}},
|
||||
{Region: "North America", Country: "United States", City: "Phoenix", IPs: []net.IP{{172, 94, 26, 4}}},
|
||||
{Region: "North America", Country: "United States", City: "Salt Lake City", IPs: []net.IP{{141, 101, 168, 4}}},
|
||||
{Region: "North America", Country: "United States", City: "San Francisco", IPs: []net.IP{{172, 94, 1, 4}}},
|
||||
{Region: "North America", Country: "United States", City: "Seattle", IPs: []net.IP{{172, 94, 86, 2}}},
|
||||
{Region: "North America", Country: "United States", City: "Washington, D.C.", IPs: []net.IP{{141, 101, 169, 4}}},
|
||||
{Region: "Oceania", Country: "Australia", City: "Brisbane", IPs: []net.IP{{172, 111, 236, 2}}},
|
||||
{Region: "Oceania", Country: "Australia", City: "Melbourne", IPs: []net.IP{{118, 127, 62, 2}}},
|
||||
{Region: "Oceania", Country: "Australia", City: "Sydney", IPs: []net.IP{{192, 253, 241, 2}}},
|
||||
{Region: "Oceania", Country: "New Zealand", City: "Auckland", IPs: []net.IP{{43, 228, 156, 4}}},
|
||||
{Region: "South America", Country: "Argentina", City: "Buenos Aires", IPs: []net.IP{{104, 243, 244, 1}}},
|
||||
{Region: "South America", Country: "Bolivia", City: "Sucre", IPs: []net.IP{{172, 94, 77, 2}}},
|
||||
{Region: "South America", Country: "Brazil", City: "Sao Paulo", IPs: []net.IP{{104, 243, 244, 2}}},
|
||||
{Region: "South America", Country: "British Virgin Island", City: "Road Town", IPs: []net.IP{{104, 250, 184, 130}}},
|
||||
{Region: "South America", Country: "Chile", City: "Santiago", IPs: []net.IP{{191, 96, 183, 251}}},
|
||||
{Region: "South America", Country: "Colombia", City: "Bogota", IPs: []net.IP{{172, 111, 132, 1}}},
|
||||
{Region: "South America", Country: "Ecuador", City: "Quito", IPs: []net.IP{{104, 250, 180, 126}}},
|
||||
{Region: "South America", Country: "Guyana", City: "Georgetown", IPs: []net.IP{{45, 74, 20, 129}}},
|
||||
{Region: "South America", Country: "Panama", City: "Panama City", IPs: []net.IP{{104, 243, 243, 131}}},
|
||||
{Region: "South America", Country: "Paraguay", City: "Asuncion", IPs: []net.IP{{45, 74, 19, 129}}},
|
||||
{Region: "South America", Country: "Peru", City: "Lima", IPs: []net.IP{{172, 111, 131, 1}}},
|
||||
{Region: "South America", Country: "Suriname", City: "Paramaribo", IPs: []net.IP{{45, 74, 20, 4}}},
|
||||
{Country: "Australia", Region: "New South Wales", City: "Sydney", IPs: []net.IP{{192, 253, 241, 4}, {43, 245, 161, 85}}},
|
||||
{Country: "Australia", Region: "Western Australia", City: "Perth", IPs: []net.IP{{172, 94, 123, 4}}},
|
||||
{Country: "Austria", Region: "Lower Austria", City: "Langenzersdorf", IPs: []net.IP{{172, 94, 109, 4}}},
|
||||
{Country: "Austria", Region: "Vienna", City: "Vienna", IPs: []net.IP{{217, 64, 127, 252}}},
|
||||
{Country: "Belgium", Region: "Flanders", City: "Zaventem", IPs: []net.IP{{172, 111, 223, 4}}},
|
||||
{Country: "Bulgaria", Region: "Sofia-Capital", City: "Sofia", IPs: []net.IP{{217, 138, 221, 120}}},
|
||||
{Country: "Canada", Region: "Alberta", City: "Calgary", IPs: []net.IP{{172, 94, 34, 4}}},
|
||||
{Country: "Canada", Region: "Ontario", City: "Toronto", IPs: []net.IP{{104, 200, 138, 196}}},
|
||||
{Country: "France", Region: "Île-de-France", City: "Paris", IPs: []net.IP{{89, 40, 183, 178}}},
|
||||
{Country: "Germany", Region: "Hesse", City: "Frankfurt am Main", IPs: []net.IP{{188, 72, 84, 4}}},
|
||||
{Country: "Greece", Region: "Central Macedonia", City: "Thessaloníki", IPs: []net.IP{{178, 21, 169, 244}}},
|
||||
{Country: "Hong Kong", Region: "Central and Western", City: "Central", IPs: []net.IP{{141, 101, 168, 4}, {141, 101, 168, 4}}},
|
||||
{Country: "Hong Kong", Region: "Central and Western", City: "Hong Kong", IPs: []net.IP{{43, 226, 231, 6}, {172, 111, 168, 4}}},
|
||||
{Country: "India", Region: "Tamil Nadu", City: "Chennai", IPs: []net.IP{{129, 227, 107, 242}}},
|
||||
{Country: "Italy", Region: "Trentino-Alto Adige", City: "Trento", IPs: []net.IP{{172, 111, 173, 3}}},
|
||||
{Country: "Japan", Region: "Ōsaka", City: "Osaka", IPs: []net.IP{{172, 94, 56, 4}}},
|
||||
{Country: "Malaysia", Region: "Kuala Lumpur", City: "Kuala Lumpur", IPs: []net.IP{{103, 55, 10, 133}}},
|
||||
{Country: "Netherlands", Region: "North Holland", City: "Amsterdam", IPs: []net.IP{{5, 254, 73, 172}}},
|
||||
{Country: "Norway", Region: "Oslo", City: "Oslo", IPs: []net.IP{{82, 102, 22, 212}}},
|
||||
{Country: "Panama", Region: "Panamá", City: "Panamá", IPs: []net.IP{{104, 243, 243, 131}}},
|
||||
{Country: "Philippines", Region: "Metro Manila", City: "Quezon City", IPs: []net.IP{{129, 227, 119, 84}}},
|
||||
{Country: "Poland", Region: "Mazovia", City: "Warsaw", IPs: []net.IP{{5, 253, 206, 251}}},
|
||||
{Country: "Portugal", Region: "Lisbon", City: "Lisbon", IPs: []net.IP{{5, 154, 174, 3}}},
|
||||
{Country: "Russian Federation", Region: "Moscow", City: "Moscow", IPs: []net.IP{{46, 243, 220, 2}, {206, 123, 139, 4}}},
|
||||
{Country: "Singapore", Region: "Singapore", City: "Singapore", IPs: []net.IP{{37, 120, 208, 147}}},
|
||||
{Country: "South Africa", Region: "Gauteng", City: "Johannesburg", IPs: []net.IP{{102, 165, 3, 33}}},
|
||||
{Country: "Spain", Region: "Madrid", City: "Madrid", IPs: []net.IP{{217, 138, 218, 210}}},
|
||||
{Country: "Sweden", Region: "Stockholm", City: "Stockholm", IPs: []net.IP{{86, 106, 103, 139}}},
|
||||
{Country: "Switzerland", Region: "Zurich", City: "Zürich", IPs: []net.IP{{45, 12, 222, 103}}},
|
||||
{Country: "Taiwan", Region: "Taiwan", City: "Taipei", IPs: []net.IP{{128, 1, 155, 178}}},
|
||||
{Country: "United Arab Emirates", Region: "Dubai", City: "Dubai", IPs: []net.IP{{104, 37, 6, 4}}},
|
||||
{Country: "United Kingdom", Region: "England", City: "Birmingham", IPs: []net.IP{{188, 72, 89, 4}}},
|
||||
{Country: "United Kingdom", Region: "England", City: "London", IPs: []net.IP{{45, 141, 154, 71}, {45, 141, 154, 71}}},
|
||||
{Country: "United States", Region: "California", City: "Los Angeles", IPs: []net.IP{{172, 111, 147, 4}}},
|
||||
{Country: "United States", Region: "California", City: "South San Francisco", IPs: []net.IP{{141, 101, 166, 4}, {141, 101, 166, 4}, {141, 101, 166, 4}}},
|
||||
{Country: "United States", Region: "Florida", City: "Miami", IPs: []net.IP{{5, 254, 79, 115}}},
|
||||
{Country: "United States", Region: "Massachusetts", City: "Newton", IPs: []net.IP{{104, 243, 244, 2}}},
|
||||
{Country: "United States", Region: "Michigan", City: "Ypsilanti", IPs: []net.IP{{172, 111, 149, 4}}},
|
||||
{Country: "United States", Region: "Texas", City: "Dallas", IPs: []net.IP{{208, 84, 155, 100}}},
|
||||
{Country: "United States", Region: "Utah", City: "Salt Lake City", IPs: []net.IP{{45, 74, 52, 4}, {45, 74, 52, 4}, {45, 74, 52, 4}, {45, 74, 52, 4}, {45, 74, 52, 4}, {45, 74, 52, 4}}},
|
||||
{Country: "Vietnam", Region: "Hanoi", City: "Cầu Giấy", IPs: []net.IP{{192, 253, 249, 132}}},
|
||||
}
|
||||
}
|
||||
|
||||
@@ -23,12 +23,12 @@ func GetAllServers() (allServers models.AllServers) {
|
||||
},
|
||||
Pia: models.PiaServers{
|
||||
Version: 2,
|
||||
Timestamp: 1605392393,
|
||||
Timestamp: 1609343591,
|
||||
Servers: PIAServers(),
|
||||
},
|
||||
Purevpn: models.PurevpnServers{
|
||||
Version: 1,
|
||||
Timestamp: 1599323261,
|
||||
Timestamp: 1609448478,
|
||||
Servers: PurevpnServers(),
|
||||
},
|
||||
Privado: models.PrivadoServers{
|
||||
|
||||
@@ -62,7 +62,7 @@ func Test_versions(t *testing.T) {
|
||||
"Purevpn": {
|
||||
model: models.PurevpnServer{},
|
||||
version: allServers.Purevpn.Version,
|
||||
digest: "cc1a2219",
|
||||
digest: "ada45379",
|
||||
},
|
||||
"Surfshark": {
|
||||
model: models.SurfsharkServer{},
|
||||
@@ -133,12 +133,12 @@ func Test_timestamps(t *testing.T) {
|
||||
"Private Internet Access": {
|
||||
servers: allServers.Pia.Servers,
|
||||
timestamp: allServers.Pia.Timestamp,
|
||||
digest: "4a172b0a",
|
||||
digest: "d797f112",
|
||||
},
|
||||
"Purevpn": {
|
||||
servers: allServers.Purevpn.Servers,
|
||||
timestamp: allServers.Purevpn.Timestamp,
|
||||
digest: "cdf9b708",
|
||||
digest: "8abe18d4",
|
||||
},
|
||||
"Privado": {
|
||||
servers: allServers.Privado.Servers,
|
||||
|
||||
@@ -2,9 +2,9 @@ package constants
|
||||
|
||||
const (
|
||||
// Announcement is a message announcement.
|
||||
Announcement = "Support for Privado"
|
||||
Announcement = "New Docker image qmcgaw/gluetun"
|
||||
// AnnouncementExpiration is the expiration date of the announcement in format yyyy-mm-dd.
|
||||
AnnouncementExpiration = "2020-11-25"
|
||||
AnnouncementExpiration = "2021-01-20"
|
||||
)
|
||||
|
||||
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,43 +0,0 @@
|
||||
package dns
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"io"
|
||||
"strings"
|
||||
|
||||
"github.com/qdm12/gluetun/internal/constants"
|
||||
)
|
||||
|
||||
func (c *configurator) Start(ctx context.Context, verbosityDetailsLevel uint8) (
|
||||
stdout io.ReadCloser, waitFn func() error, err error) {
|
||||
c.logger.Info("starting unbound")
|
||||
args := []string{"-d", "-c", string(constants.UnboundConf)}
|
||||
if verbosityDetailsLevel > 0 {
|
||||
args = append(args, "-"+strings.Repeat("v", int(verbosityDetailsLevel)))
|
||||
}
|
||||
// Only logs to stderr
|
||||
_, stdout, waitFn, err = c.commander.Start(ctx, "unbound", args...)
|
||||
return stdout, waitFn, err
|
||||
}
|
||||
|
||||
func (c *configurator) Version(ctx context.Context) (version string, err error) {
|
||||
output, err := c.commander.Run(ctx, "unbound", "-V")
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("unbound version: %w", err)
|
||||
}
|
||||
for _, line := range strings.Split(output, "\n") {
|
||||
if strings.Contains(line, "Version ") {
|
||||
words := strings.Fields(line)
|
||||
const minWords = 2
|
||||
if len(words) < minWords {
|
||||
continue
|
||||
}
|
||||
version = words[1]
|
||||
}
|
||||
}
|
||||
if version == "" {
|
||||
return "", fmt.Errorf("unbound version was not found in %q", output)
|
||||
}
|
||||
return version, nil
|
||||
}
|
||||
@@ -1,72 +0,0 @@
|
||||
package dns
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"testing"
|
||||
|
||||
"github.com/golang/mock/gomock"
|
||||
"github.com/qdm12/gluetun/internal/constants"
|
||||
"github.com/qdm12/golibs/command/mock_command"
|
||||
"github.com/qdm12/golibs/logging/mock_logging"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func Test_Start(t *testing.T) {
|
||||
t.Parallel()
|
||||
mockCtrl := gomock.NewController(t)
|
||||
defer mockCtrl.Finish()
|
||||
logger := mock_logging.NewMockLogger(mockCtrl)
|
||||
logger.EXPECT().Info("starting unbound").Times(1)
|
||||
commander := mock_command.NewMockCommander(mockCtrl)
|
||||
commander.EXPECT().Start(context.Background(), "unbound", "-d", "-c", string(constants.UnboundConf), "-vv").
|
||||
Return(nil, nil, nil, nil).Times(1)
|
||||
c := &configurator{commander: commander, logger: logger}
|
||||
stdout, waitFn, err := c.Start(context.Background(), 2)
|
||||
assert.Nil(t, stdout)
|
||||
assert.Nil(t, waitFn)
|
||||
assert.NoError(t, err)
|
||||
}
|
||||
|
||||
func Test_Version(t *testing.T) {
|
||||
t.Parallel()
|
||||
tests := map[string]struct {
|
||||
runOutput string
|
||||
runErr error
|
||||
version string
|
||||
err error
|
||||
}{
|
||||
"no data": {
|
||||
err: fmt.Errorf(`unbound version was not found in ""`),
|
||||
},
|
||||
"2 lines with version": {
|
||||
runOutput: "Version \nVersion 1.0-a hello\n",
|
||||
version: "1.0-a",
|
||||
},
|
||||
"run error": {
|
||||
runErr: fmt.Errorf("error"),
|
||||
err: fmt.Errorf("unbound version: error"),
|
||||
},
|
||||
}
|
||||
for name, tc := range tests {
|
||||
tc := tc
|
||||
t.Run(name, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
mockCtrl := gomock.NewController(t)
|
||||
defer mockCtrl.Finish()
|
||||
commander := mock_command.NewMockCommander(mockCtrl)
|
||||
commander.EXPECT().Run(context.Background(), "unbound", "-V").
|
||||
Return(tc.runOutput, tc.runErr).Times(1)
|
||||
c := &configurator{commander: commander}
|
||||
version, err := c.Version(context.Background())
|
||||
if tc.err != nil {
|
||||
require.Error(t, err)
|
||||
assert.Equal(t, tc.err.Error(), err.Error())
|
||||
} else {
|
||||
assert.NoError(t, err)
|
||||
}
|
||||
assert.Equal(t, tc.version, version)
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -1,292 +0,0 @@
|
||||
package dns
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"sort"
|
||||
"strings"
|
||||
|
||||
"github.com/qdm12/gluetun/internal/constants"
|
||||
"github.com/qdm12/gluetun/internal/settings"
|
||||
"github.com/qdm12/golibs/files"
|
||||
"github.com/qdm12/golibs/logging"
|
||||
"github.com/qdm12/golibs/network"
|
||||
)
|
||||
|
||||
func (c *configurator) MakeUnboundConf(ctx context.Context, settings settings.DNS, uid, gid int) (err error) {
|
||||
c.logger.Info("generating Unbound configuration")
|
||||
lines, warnings := generateUnboundConf(ctx, settings, c.client, c.logger)
|
||||
for _, warning := range warnings {
|
||||
c.logger.Warn(warning)
|
||||
}
|
||||
return c.fileManager.WriteLinesToFile(
|
||||
string(constants.UnboundConf),
|
||||
lines,
|
||||
files.Ownership(uid, gid),
|
||||
files.Permissions(constants.UserReadPermission))
|
||||
}
|
||||
|
||||
// MakeUnboundConf generates an Unbound configuration from the user provided settings.
|
||||
func generateUnboundConf(ctx context.Context, settings settings.DNS,
|
||||
client network.Client, logger logging.Logger) (
|
||||
lines []string, warnings []error) {
|
||||
doIPv6 := "no"
|
||||
if settings.IPv6 {
|
||||
doIPv6 = "yes"
|
||||
}
|
||||
serverSection := map[string]string{
|
||||
// Logging
|
||||
"verbosity": fmt.Sprintf("%d", settings.VerbosityLevel),
|
||||
"val-log-level": fmt.Sprintf("%d", settings.ValidationLogLevel),
|
||||
"use-syslog": "no",
|
||||
// Performance
|
||||
"num-threads": "1",
|
||||
"prefetch": "yes",
|
||||
"prefetch-key": "yes",
|
||||
"key-cache-size": "16m",
|
||||
"key-cache-slabs": "4",
|
||||
"msg-cache-size": "4m",
|
||||
"msg-cache-slabs": "4",
|
||||
"rrset-cache-size": "4m",
|
||||
"rrset-cache-slabs": "4",
|
||||
"cache-min-ttl": "3600",
|
||||
"cache-max-ttl": "9000",
|
||||
// Privacy
|
||||
"rrset-roundrobin": "yes",
|
||||
"hide-identity": "yes",
|
||||
"hide-version": "yes",
|
||||
// Security
|
||||
"tls-cert-bundle": fmt.Sprintf("%q", constants.CACertificates),
|
||||
"root-hints": fmt.Sprintf("%q", constants.RootHints),
|
||||
"trust-anchor-file": fmt.Sprintf("%q", constants.RootKey),
|
||||
"harden-below-nxdomain": "yes",
|
||||
"harden-referral-path": "yes",
|
||||
"harden-algo-downgrade": "yes",
|
||||
// Network
|
||||
"do-ip4": "yes",
|
||||
"do-ip6": doIPv6,
|
||||
"interface": "0.0.0.0",
|
||||
"port": "53",
|
||||
// Other
|
||||
"username": "\"nonrootuser\"",
|
||||
}
|
||||
|
||||
// Block lists
|
||||
hostnamesLines, ipsLines, warnings := buildBlocked(ctx, client,
|
||||
settings.BlockMalicious, settings.BlockAds, settings.BlockSurveillance,
|
||||
settings.AllowedHostnames, settings.PrivateAddresses,
|
||||
)
|
||||
logger.Info("%d hostnames blocked overall", len(hostnamesLines))
|
||||
logger.Info("%d IP addresses blocked overall", len(ipsLines))
|
||||
sort.Slice(hostnamesLines, func(i, j int) bool { // for unit tests really
|
||||
return hostnamesLines[i] < hostnamesLines[j]
|
||||
})
|
||||
sort.Slice(ipsLines, func(i, j int) bool { // for unit tests really
|
||||
return ipsLines[i] < ipsLines[j]
|
||||
})
|
||||
|
||||
// Server
|
||||
lines = append(lines, "server:")
|
||||
serverLines := make([]string, len(serverSection))
|
||||
i := 0
|
||||
for k, v := range serverSection {
|
||||
serverLines[i] = " " + k + ": " + v
|
||||
i++
|
||||
}
|
||||
sort.Slice(serverLines, func(i, j int) bool {
|
||||
return serverLines[i] < serverLines[j]
|
||||
})
|
||||
lines = append(lines, serverLines...)
|
||||
lines = append(lines, hostnamesLines...)
|
||||
lines = append(lines, ipsLines...)
|
||||
|
||||
// Forward zone
|
||||
lines = append(lines, "forward-zone:")
|
||||
forwardZoneSection := map[string]string{
|
||||
"name": "\".\"",
|
||||
"forward-tls-upstream": "yes",
|
||||
}
|
||||
if settings.Caching {
|
||||
forwardZoneSection["forward-no-cache"] = "no"
|
||||
} else {
|
||||
forwardZoneSection["forward-no-cache"] = "yes"
|
||||
}
|
||||
forwardZoneLines := make([]string, len(forwardZoneSection))
|
||||
i = 0
|
||||
for k, v := range forwardZoneSection {
|
||||
forwardZoneLines[i] = " " + k + ": " + v
|
||||
i++
|
||||
}
|
||||
sort.Slice(forwardZoneLines, func(i, j int) bool {
|
||||
return forwardZoneLines[i] < forwardZoneLines[j]
|
||||
})
|
||||
for _, provider := range settings.Providers {
|
||||
providerData := constants.DNSProviderMapping()[provider]
|
||||
for _, IP := range providerData.IPs {
|
||||
forwardZoneLines = append(forwardZoneLines,
|
||||
fmt.Sprintf(" forward-addr: %s@853#%s", IP, providerData.Host))
|
||||
}
|
||||
}
|
||||
lines = append(lines, forwardZoneLines...)
|
||||
return lines, warnings
|
||||
}
|
||||
|
||||
func buildBlocked(ctx context.Context, client network.Client, blockMalicious, blockAds, blockSurveillance bool,
|
||||
allowedHostnames, privateAddresses []string) (hostnamesLines, ipsLines []string, errs []error) {
|
||||
chHostnames := make(chan []string)
|
||||
chIPs := make(chan []string)
|
||||
chErrors := make(chan []error)
|
||||
go func() {
|
||||
lines, errs := buildBlockedHostnames(ctx, client, blockMalicious, blockAds, blockSurveillance, allowedHostnames)
|
||||
chHostnames <- lines
|
||||
chErrors <- errs
|
||||
}()
|
||||
go func() {
|
||||
lines, errs := buildBlockedIPs(ctx, client, blockMalicious, blockAds, blockSurveillance, privateAddresses)
|
||||
chIPs <- lines
|
||||
chErrors <- errs
|
||||
}()
|
||||
n := 2
|
||||
for n > 0 {
|
||||
select {
|
||||
case lines := <-chHostnames:
|
||||
hostnamesLines = append(hostnamesLines, lines...)
|
||||
case lines := <-chIPs:
|
||||
ipsLines = append(ipsLines, lines...)
|
||||
case routineErrs := <-chErrors:
|
||||
errs = append(errs, routineErrs...)
|
||||
n--
|
||||
}
|
||||
}
|
||||
return hostnamesLines, ipsLines, errs
|
||||
}
|
||||
|
||||
func getList(ctx context.Context, client network.Client, url string) (results []string, err error) {
|
||||
content, status, err := client.Get(ctx, url)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
} else if status != http.StatusOK {
|
||||
return nil, fmt.Errorf("HTTP status code is %d and not 200", status)
|
||||
}
|
||||
results = strings.Split(string(content), "\n")
|
||||
|
||||
// remove empty lines
|
||||
last := len(results) - 1
|
||||
for i := range results {
|
||||
if len(results[i]) == 0 {
|
||||
results[i] = results[last]
|
||||
last--
|
||||
}
|
||||
}
|
||||
results = results[:last+1]
|
||||
|
||||
if len(results) == 0 {
|
||||
return nil, nil
|
||||
}
|
||||
return results, nil
|
||||
}
|
||||
|
||||
func buildBlockedHostnames(ctx context.Context, client network.Client, blockMalicious, blockAds, blockSurveillance bool,
|
||||
allowedHostnames []string) (lines []string, errs []error) {
|
||||
chResults := make(chan []string)
|
||||
chError := make(chan error)
|
||||
listsLeftToFetch := 0
|
||||
if blockMalicious {
|
||||
listsLeftToFetch++
|
||||
go func() {
|
||||
results, err := getList(ctx, client, string(constants.MaliciousBlockListHostnamesURL))
|
||||
chResults <- results
|
||||
chError <- err
|
||||
}()
|
||||
}
|
||||
if blockAds {
|
||||
listsLeftToFetch++
|
||||
go func() {
|
||||
results, err := getList(ctx, client, string(constants.AdsBlockListHostnamesURL))
|
||||
chResults <- results
|
||||
chError <- err
|
||||
}()
|
||||
}
|
||||
if blockSurveillance {
|
||||
listsLeftToFetch++
|
||||
go func() {
|
||||
results, err := getList(ctx, client, string(constants.SurveillanceBlockListHostnamesURL))
|
||||
chResults <- results
|
||||
chError <- err
|
||||
}()
|
||||
}
|
||||
uniqueResults := make(map[string]struct{})
|
||||
for listsLeftToFetch > 0 {
|
||||
select {
|
||||
case results := <-chResults:
|
||||
for _, result := range results {
|
||||
uniqueResults[result] = struct{}{}
|
||||
}
|
||||
case err := <-chError:
|
||||
listsLeftToFetch--
|
||||
if err != nil {
|
||||
errs = append(errs, err)
|
||||
}
|
||||
}
|
||||
}
|
||||
for _, allowedHostname := range allowedHostnames {
|
||||
delete(uniqueResults, allowedHostname)
|
||||
}
|
||||
for result := range uniqueResults {
|
||||
lines = append(lines, " local-zone: \""+result+"\" static")
|
||||
}
|
||||
return lines, errs
|
||||
}
|
||||
|
||||
func buildBlockedIPs(ctx context.Context, client network.Client, blockMalicious, blockAds, blockSurveillance bool,
|
||||
privateAddresses []string) (lines []string, errs []error) {
|
||||
chResults := make(chan []string)
|
||||
chError := make(chan error)
|
||||
listsLeftToFetch := 0
|
||||
if blockMalicious {
|
||||
listsLeftToFetch++
|
||||
go func() {
|
||||
results, err := getList(ctx, client, string(constants.MaliciousBlockListIPsURL))
|
||||
chResults <- results
|
||||
chError <- err
|
||||
}()
|
||||
}
|
||||
if blockAds {
|
||||
listsLeftToFetch++
|
||||
go func() {
|
||||
results, err := getList(ctx, client, string(constants.AdsBlockListIPsURL))
|
||||
chResults <- results
|
||||
chError <- err
|
||||
}()
|
||||
}
|
||||
if blockSurveillance {
|
||||
listsLeftToFetch++
|
||||
go func() {
|
||||
results, err := getList(ctx, client, string(constants.SurveillanceBlockListIPsURL))
|
||||
chResults <- results
|
||||
chError <- err
|
||||
}()
|
||||
}
|
||||
uniqueResults := make(map[string]struct{})
|
||||
for listsLeftToFetch > 0 {
|
||||
select {
|
||||
case results := <-chResults:
|
||||
for _, result := range results {
|
||||
uniqueResults[result] = struct{}{}
|
||||
}
|
||||
case err := <-chError:
|
||||
listsLeftToFetch--
|
||||
if err != nil {
|
||||
errs = append(errs, err)
|
||||
}
|
||||
}
|
||||
}
|
||||
for _, privateAddress := range privateAddresses {
|
||||
uniqueResults[privateAddress] = struct{}{}
|
||||
}
|
||||
for result := range uniqueResults {
|
||||
lines = append(lines, " private-address: "+result)
|
||||
}
|
||||
return lines, errs
|
||||
}
|
||||
@@ -1,539 +0,0 @@
|
||||
package dns
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/golang/mock/gomock"
|
||||
"github.com/qdm12/gluetun/internal/constants"
|
||||
"github.com/qdm12/gluetun/internal/models"
|
||||
"github.com/qdm12/gluetun/internal/settings"
|
||||
"github.com/qdm12/golibs/logging/mock_logging"
|
||||
"github.com/qdm12/golibs/network/mock_network"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func Test_generateUnboundConf(t *testing.T) {
|
||||
t.Parallel()
|
||||
settings := settings.DNS{
|
||||
Providers: []models.DNSProvider{constants.Cloudflare, constants.Quad9},
|
||||
AllowedHostnames: []string{"a"},
|
||||
PrivateAddresses: []string{"9.9.9.9"},
|
||||
BlockMalicious: true,
|
||||
BlockSurveillance: false,
|
||||
BlockAds: false,
|
||||
VerbosityLevel: 2,
|
||||
ValidationLogLevel: 3,
|
||||
Caching: true,
|
||||
IPv6: true,
|
||||
}
|
||||
mockCtrl := gomock.NewController(t)
|
||||
defer mockCtrl.Finish()
|
||||
ctx := context.Background()
|
||||
client := mock_network.NewMockClient(mockCtrl)
|
||||
client.EXPECT().Get(ctx, string(constants.MaliciousBlockListHostnamesURL)).
|
||||
Return([]byte("b\na\nc"), 200, nil).Times(1)
|
||||
client.EXPECT().Get(ctx, string(constants.MaliciousBlockListIPsURL)).
|
||||
Return([]byte("c\nd\n"), 200, nil).Times(1)
|
||||
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)
|
||||
require.Len(t, warnings, 0)
|
||||
expected := `
|
||||
server:
|
||||
cache-max-ttl: 9000
|
||||
cache-min-ttl: 3600
|
||||
do-ip4: yes
|
||||
do-ip6: yes
|
||||
harden-algo-downgrade: yes
|
||||
harden-below-nxdomain: yes
|
||||
harden-referral-path: yes
|
||||
hide-identity: yes
|
||||
hide-version: yes
|
||||
interface: 0.0.0.0
|
||||
key-cache-size: 16m
|
||||
key-cache-slabs: 4
|
||||
msg-cache-size: 4m
|
||||
msg-cache-slabs: 4
|
||||
num-threads: 1
|
||||
port: 53
|
||||
prefetch-key: yes
|
||||
prefetch: yes
|
||||
root-hints: "/etc/unbound/root.hints"
|
||||
rrset-cache-size: 4m
|
||||
rrset-cache-slabs: 4
|
||||
rrset-roundrobin: yes
|
||||
tls-cert-bundle: "/etc/ssl/certs/ca-certificates.crt"
|
||||
trust-anchor-file: "/etc/unbound/root.key"
|
||||
use-syslog: no
|
||||
username: "nonrootuser"
|
||||
val-log-level: 3
|
||||
verbosity: 2
|
||||
local-zone: "b" static
|
||||
local-zone: "c" static
|
||||
private-address: 9.9.9.9
|
||||
private-address: c
|
||||
private-address: d
|
||||
forward-zone:
|
||||
forward-no-cache: no
|
||||
forward-tls-upstream: yes
|
||||
name: "."
|
||||
forward-addr: 1.1.1.1@853#cloudflare-dns.com
|
||||
forward-addr: 1.0.0.1@853#cloudflare-dns.com
|
||||
forward-addr: 2606:4700:4700::1111@853#cloudflare-dns.com
|
||||
forward-addr: 2606:4700:4700::1001@853#cloudflare-dns.com
|
||||
forward-addr: 9.9.9.9@853#dns.quad9.net
|
||||
forward-addr: 149.112.112.112@853#dns.quad9.net
|
||||
forward-addr: 2620:fe::fe@853#dns.quad9.net
|
||||
forward-addr: 2620:fe::9@853#dns.quad9.net`
|
||||
assert.Equal(t, expected, "\n"+strings.Join(lines, "\n"))
|
||||
}
|
||||
|
||||
func Test_buildBlocked(t *testing.T) {
|
||||
t.Parallel()
|
||||
type blockParams struct {
|
||||
blocked bool
|
||||
content []byte
|
||||
clientErr error
|
||||
}
|
||||
tests := map[string]struct {
|
||||
malicious blockParams
|
||||
ads blockParams
|
||||
surveillance blockParams
|
||||
allowedHostnames []string
|
||||
privateAddresses []string
|
||||
hostnamesLines []string
|
||||
ipsLines []string
|
||||
errsString []string
|
||||
}{
|
||||
"none blocked": {},
|
||||
"all blocked without lists": {
|
||||
malicious: blockParams{
|
||||
blocked: true,
|
||||
},
|
||||
ads: blockParams{
|
||||
blocked: true,
|
||||
},
|
||||
surveillance: blockParams{
|
||||
blocked: true,
|
||||
},
|
||||
},
|
||||
"all blocked with lists": {
|
||||
malicious: blockParams{
|
||||
blocked: true,
|
||||
content: []byte("malicious"),
|
||||
},
|
||||
ads: blockParams{
|
||||
blocked: true,
|
||||
content: []byte("ads"),
|
||||
},
|
||||
surveillance: blockParams{
|
||||
blocked: true,
|
||||
content: []byte("surveillance"),
|
||||
},
|
||||
hostnamesLines: []string{
|
||||
" local-zone: \"ads\" static",
|
||||
" local-zone: \"malicious\" static",
|
||||
" local-zone: \"surveillance\" static"},
|
||||
ipsLines: []string{
|
||||
" private-address: ads",
|
||||
" private-address: malicious",
|
||||
" private-address: surveillance"},
|
||||
},
|
||||
"all blocked with allowed hostnames": {
|
||||
malicious: blockParams{
|
||||
blocked: true,
|
||||
content: []byte("malicious"),
|
||||
},
|
||||
ads: blockParams{
|
||||
blocked: true,
|
||||
content: []byte("ads"),
|
||||
},
|
||||
surveillance: blockParams{
|
||||
blocked: true,
|
||||
content: []byte("surveillance"),
|
||||
},
|
||||
allowedHostnames: []string{"ads"},
|
||||
hostnamesLines: []string{
|
||||
" local-zone: \"malicious\" static",
|
||||
" local-zone: \"surveillance\" static"},
|
||||
ipsLines: []string{
|
||||
" private-address: ads",
|
||||
" private-address: malicious",
|
||||
" private-address: surveillance"},
|
||||
},
|
||||
"all blocked with private addresses": {
|
||||
malicious: blockParams{
|
||||
blocked: true,
|
||||
content: []byte("malicious"),
|
||||
},
|
||||
ads: blockParams{
|
||||
blocked: true,
|
||||
content: []byte("ads"),
|
||||
},
|
||||
surveillance: blockParams{
|
||||
blocked: true,
|
||||
content: []byte("surveillance"),
|
||||
},
|
||||
privateAddresses: []string{"ads", "192.100.1.5"},
|
||||
hostnamesLines: []string{
|
||||
" local-zone: \"ads\" static",
|
||||
" local-zone: \"malicious\" static",
|
||||
" local-zone: \"surveillance\" static"},
|
||||
ipsLines: []string{
|
||||
" private-address: 192.100.1.5",
|
||||
" private-address: ads",
|
||||
" private-address: malicious",
|
||||
" private-address: surveillance"},
|
||||
},
|
||||
"all blocked with lists and one error": {
|
||||
malicious: blockParams{
|
||||
blocked: true,
|
||||
content: []byte("malicious"),
|
||||
},
|
||||
ads: blockParams{
|
||||
blocked: true,
|
||||
content: []byte("ads"),
|
||||
clientErr: fmt.Errorf("ads error"),
|
||||
},
|
||||
surveillance: blockParams{
|
||||
blocked: true,
|
||||
content: []byte("surveillance"),
|
||||
},
|
||||
hostnamesLines: []string{
|
||||
" local-zone: \"malicious\" static",
|
||||
" local-zone: \"surveillance\" static"},
|
||||
ipsLines: []string{
|
||||
" private-address: malicious",
|
||||
" private-address: surveillance"},
|
||||
errsString: []string{"ads error", "ads error"},
|
||||
},
|
||||
"all blocked with errors": {
|
||||
malicious: blockParams{
|
||||
blocked: true,
|
||||
clientErr: fmt.Errorf("malicious"),
|
||||
},
|
||||
ads: blockParams{
|
||||
blocked: true,
|
||||
clientErr: fmt.Errorf("ads"),
|
||||
},
|
||||
surveillance: blockParams{
|
||||
blocked: true,
|
||||
clientErr: fmt.Errorf("surveillance"),
|
||||
},
|
||||
errsString: []string{"malicious", "malicious", "ads", "ads", "surveillance", "surveillance"},
|
||||
},
|
||||
}
|
||||
for name, tc := range tests {
|
||||
tc := tc
|
||||
t.Run(name, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
mockCtrl := gomock.NewController(t)
|
||||
defer mockCtrl.Finish()
|
||||
ctx := context.Background()
|
||||
client := mock_network.NewMockClient(mockCtrl)
|
||||
if tc.malicious.blocked {
|
||||
client.EXPECT().Get(ctx, string(constants.MaliciousBlockListHostnamesURL)).
|
||||
Return(tc.malicious.content, 200, tc.malicious.clientErr).Times(1)
|
||||
client.EXPECT().Get(ctx, string(constants.MaliciousBlockListIPsURL)).
|
||||
Return(tc.malicious.content, 200, tc.malicious.clientErr).Times(1)
|
||||
}
|
||||
if tc.ads.blocked {
|
||||
client.EXPECT().Get(ctx, string(constants.AdsBlockListHostnamesURL)).
|
||||
Return(tc.ads.content, 200, tc.ads.clientErr).Times(1)
|
||||
client.EXPECT().Get(ctx, string(constants.AdsBlockListIPsURL)).
|
||||
Return(tc.ads.content, 200, tc.ads.clientErr).Times(1)
|
||||
}
|
||||
if tc.surveillance.blocked {
|
||||
client.EXPECT().Get(ctx, string(constants.SurveillanceBlockListHostnamesURL)).
|
||||
Return(tc.surveillance.content, 200, tc.surveillance.clientErr).Times(1)
|
||||
client.EXPECT().Get(ctx, string(constants.SurveillanceBlockListIPsURL)).
|
||||
Return(tc.surveillance.content, 200, tc.surveillance.clientErr).Times(1)
|
||||
}
|
||||
hostnamesLines, ipsLines, errs := buildBlocked(ctx, client,
|
||||
tc.malicious.blocked, tc.ads.blocked, tc.surveillance.blocked,
|
||||
tc.allowedHostnames, tc.privateAddresses)
|
||||
var errsString []string
|
||||
for _, err := range errs {
|
||||
errsString = append(errsString, err.Error())
|
||||
}
|
||||
assert.ElementsMatch(t, tc.errsString, errsString)
|
||||
assert.ElementsMatch(t, tc.hostnamesLines, hostnamesLines)
|
||||
assert.ElementsMatch(t, tc.ipsLines, ipsLines)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func Test_getList(t *testing.T) {
|
||||
t.Parallel()
|
||||
tests := map[string]struct {
|
||||
content []byte
|
||||
status int
|
||||
clientErr error
|
||||
results []string
|
||||
err error
|
||||
}{
|
||||
"no result": {nil, 200, nil, nil, nil},
|
||||
"bad status": {nil, 500, nil, nil, fmt.Errorf("HTTP status code is 500 and not 200")},
|
||||
"network error": {nil, 200, fmt.Errorf("error"), nil, fmt.Errorf("error")},
|
||||
"results": {[]byte("a\nb\nc\n"), 200, nil, []string{"a", "b", "c"}, nil},
|
||||
}
|
||||
for name, tc := range tests {
|
||||
tc := tc
|
||||
t.Run(name, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
mockCtrl := gomock.NewController(t)
|
||||
defer mockCtrl.Finish()
|
||||
ctx := context.Background()
|
||||
client := mock_network.NewMockClient(mockCtrl)
|
||||
client.EXPECT().Get(ctx, "irrelevant_url").
|
||||
Return(tc.content, tc.status, tc.clientErr).Times(1)
|
||||
results, err := getList(ctx, client, "irrelevant_url")
|
||||
if tc.err != nil {
|
||||
require.Error(t, err)
|
||||
assert.Equal(t, tc.err.Error(), err.Error())
|
||||
} else {
|
||||
assert.NoError(t, err)
|
||||
}
|
||||
assert.Equal(t, tc.results, results)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func Test_buildBlockedHostnames(t *testing.T) {
|
||||
t.Parallel()
|
||||
type blockParams struct {
|
||||
blocked bool
|
||||
content []byte
|
||||
clientErr error
|
||||
}
|
||||
tests := map[string]struct {
|
||||
malicious blockParams
|
||||
ads blockParams
|
||||
surveillance blockParams
|
||||
allowedHostnames []string
|
||||
lines []string
|
||||
errsString []string
|
||||
}{
|
||||
"nothing blocked": {
|
||||
lines: nil,
|
||||
errsString: nil,
|
||||
},
|
||||
"only malicious blocked": {
|
||||
malicious: blockParams{
|
||||
blocked: true,
|
||||
content: []byte("site_a\nsite_b"),
|
||||
clientErr: nil,
|
||||
},
|
||||
lines: []string{
|
||||
" local-zone: \"site_a\" static",
|
||||
" local-zone: \"site_b\" static"},
|
||||
errsString: nil,
|
||||
},
|
||||
"all blocked with some duplicates": {
|
||||
malicious: blockParams{
|
||||
blocked: true,
|
||||
content: []byte("site_a\nsite_b"),
|
||||
},
|
||||
ads: blockParams{
|
||||
blocked: true,
|
||||
content: []byte("site_a\nsite_c"),
|
||||
},
|
||||
surveillance: blockParams{
|
||||
blocked: true,
|
||||
content: []byte("site_c\nsite_a"),
|
||||
},
|
||||
lines: []string{
|
||||
" local-zone: \"site_a\" static",
|
||||
" local-zone: \"site_b\" static",
|
||||
" local-zone: \"site_c\" static"},
|
||||
errsString: nil,
|
||||
},
|
||||
"all blocked with one errored": {
|
||||
malicious: blockParams{
|
||||
blocked: true,
|
||||
content: []byte("site_a\nsite_b"),
|
||||
},
|
||||
ads: blockParams{
|
||||
blocked: true,
|
||||
content: []byte("site_a\nsite_c"),
|
||||
},
|
||||
surveillance: blockParams{
|
||||
blocked: true,
|
||||
clientErr: fmt.Errorf("surveillance error"),
|
||||
},
|
||||
lines: []string{
|
||||
" local-zone: \"site_a\" static",
|
||||
" local-zone: \"site_b\" static",
|
||||
" local-zone: \"site_c\" static"},
|
||||
errsString: []string{"surveillance error"},
|
||||
},
|
||||
"blocked with allowed hostnames": {
|
||||
malicious: blockParams{
|
||||
blocked: true,
|
||||
content: []byte("site_a\nsite_b"),
|
||||
},
|
||||
ads: blockParams{
|
||||
blocked: true,
|
||||
content: []byte("site_c\nsite_d"),
|
||||
},
|
||||
allowedHostnames: []string{"site_b", "site_c"},
|
||||
lines: []string{
|
||||
" local-zone: \"site_a\" static",
|
||||
" local-zone: \"site_d\" static"},
|
||||
},
|
||||
}
|
||||
for name, tc := range tests { //nolint:dupl
|
||||
tc := tc
|
||||
t.Run(name, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
mockCtrl := gomock.NewController(t)
|
||||
defer mockCtrl.Finish()
|
||||
ctx := context.Background()
|
||||
client := mock_network.NewMockClient(mockCtrl)
|
||||
if tc.malicious.blocked {
|
||||
client.EXPECT().Get(ctx, string(constants.MaliciousBlockListHostnamesURL)).
|
||||
Return(tc.malicious.content, 200, tc.malicious.clientErr).Times(1)
|
||||
}
|
||||
if tc.ads.blocked {
|
||||
client.EXPECT().Get(ctx, string(constants.AdsBlockListHostnamesURL)).
|
||||
Return(tc.ads.content, 200, tc.ads.clientErr).Times(1)
|
||||
}
|
||||
if tc.surveillance.blocked {
|
||||
client.EXPECT().Get(ctx, string(constants.SurveillanceBlockListHostnamesURL)).
|
||||
Return(tc.surveillance.content, 200, tc.surveillance.clientErr).Times(1)
|
||||
}
|
||||
lines, errs := buildBlockedHostnames(ctx, client,
|
||||
tc.malicious.blocked, tc.ads.blocked,
|
||||
tc.surveillance.blocked, tc.allowedHostnames)
|
||||
var errsString []string
|
||||
for _, err := range errs {
|
||||
errsString = append(errsString, err.Error())
|
||||
}
|
||||
assert.ElementsMatch(t, tc.errsString, errsString)
|
||||
assert.ElementsMatch(t, tc.lines, lines)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func Test_buildBlockedIPs(t *testing.T) {
|
||||
t.Parallel()
|
||||
type blockParams struct {
|
||||
blocked bool
|
||||
content []byte
|
||||
clientErr error
|
||||
}
|
||||
tests := map[string]struct {
|
||||
malicious blockParams
|
||||
ads blockParams
|
||||
surveillance blockParams
|
||||
privateAddresses []string
|
||||
lines []string
|
||||
errsString []string
|
||||
}{
|
||||
"nothing blocked": {
|
||||
lines: nil,
|
||||
errsString: nil,
|
||||
},
|
||||
"only malicious blocked": {
|
||||
malicious: blockParams{
|
||||
blocked: true,
|
||||
content: []byte("site_a\nsite_b"),
|
||||
clientErr: nil,
|
||||
},
|
||||
lines: []string{
|
||||
" private-address: site_a",
|
||||
" private-address: site_b"},
|
||||
errsString: nil,
|
||||
},
|
||||
"all blocked with some duplicates": {
|
||||
malicious: blockParams{
|
||||
blocked: true,
|
||||
content: []byte("site_a\nsite_b"),
|
||||
},
|
||||
ads: blockParams{
|
||||
blocked: true,
|
||||
content: []byte("site_a\nsite_c"),
|
||||
},
|
||||
surveillance: blockParams{
|
||||
blocked: true,
|
||||
content: []byte("site_c\nsite_a"),
|
||||
},
|
||||
lines: []string{
|
||||
" private-address: site_a",
|
||||
" private-address: site_b",
|
||||
" private-address: site_c"},
|
||||
errsString: nil,
|
||||
},
|
||||
"all blocked with one errored": {
|
||||
malicious: blockParams{
|
||||
blocked: true,
|
||||
content: []byte("site_a\nsite_b"),
|
||||
},
|
||||
ads: blockParams{
|
||||
blocked: true,
|
||||
content: []byte("site_a\nsite_c"),
|
||||
},
|
||||
surveillance: blockParams{
|
||||
blocked: true,
|
||||
clientErr: fmt.Errorf("surveillance error"),
|
||||
},
|
||||
lines: []string{
|
||||
" private-address: site_a",
|
||||
" private-address: site_b",
|
||||
" private-address: site_c"},
|
||||
errsString: []string{"surveillance error"},
|
||||
},
|
||||
"blocked with private addresses": {
|
||||
malicious: blockParams{
|
||||
blocked: true,
|
||||
content: []byte("site_a\nsite_b"),
|
||||
},
|
||||
ads: blockParams{
|
||||
blocked: true,
|
||||
content: []byte("site_c"),
|
||||
},
|
||||
privateAddresses: []string{"site_c", "site_d"},
|
||||
lines: []string{
|
||||
" private-address: site_a",
|
||||
" private-address: site_b",
|
||||
" private-address: site_c",
|
||||
" private-address: site_d"},
|
||||
},
|
||||
}
|
||||
for name, tc := range tests { //nolint:dupl
|
||||
tc := tc
|
||||
t.Run(name, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
mockCtrl := gomock.NewController(t)
|
||||
defer mockCtrl.Finish()
|
||||
ctx := context.Background()
|
||||
client := mock_network.NewMockClient(mockCtrl)
|
||||
if tc.malicious.blocked {
|
||||
client.EXPECT().Get(ctx, string(constants.MaliciousBlockListIPsURL)).
|
||||
Return(tc.malicious.content, 200, tc.malicious.clientErr).Times(1)
|
||||
}
|
||||
if tc.ads.blocked {
|
||||
client.EXPECT().Get(ctx, string(constants.AdsBlockListIPsURL)).
|
||||
Return(tc.ads.content, 200, tc.ads.clientErr).Times(1)
|
||||
}
|
||||
if tc.surveillance.blocked {
|
||||
client.EXPECT().Get(ctx, string(constants.SurveillanceBlockListIPsURL)).
|
||||
Return(tc.surveillance.content, 200, tc.surveillance.clientErr).Times(1)
|
||||
}
|
||||
lines, errs := buildBlockedIPs(ctx, client,
|
||||
tc.malicious.blocked, tc.ads.blocked,
|
||||
tc.surveillance.blocked, tc.privateAddresses)
|
||||
var errsString []string
|
||||
for _, err := range errs {
|
||||
errsString = append(errsString, err.Error())
|
||||
}
|
||||
assert.ElementsMatch(t, tc.errsString, errsString)
|
||||
assert.ElementsMatch(t, tc.lines, lines)
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -1,42 +0,0 @@
|
||||
package dns
|
||||
|
||||
import (
|
||||
"context"
|
||||
"io"
|
||||
"net"
|
||||
|
||||
"github.com/qdm12/gluetun/internal/settings"
|
||||
"github.com/qdm12/golibs/command"
|
||||
"github.com/qdm12/golibs/files"
|
||||
"github.com/qdm12/golibs/logging"
|
||||
"github.com/qdm12/golibs/network"
|
||||
)
|
||||
|
||||
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)
|
||||
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)
|
||||
WaitForUnbound() (err error)
|
||||
Version(ctx context.Context) (version string, err error)
|
||||
}
|
||||
|
||||
type configurator struct {
|
||||
logger logging.Logger
|
||||
client network.Client
|
||||
fileManager files.FileManager
|
||||
commander command.Commander
|
||||
lookupIP func(host string) ([]net.IP, error)
|
||||
}
|
||||
|
||||
func NewConfigurator(logger logging.Logger, client network.Client, fileManager files.FileManager) Configurator {
|
||||
return &configurator{
|
||||
logger: logger.WithPrefix("dns configurator: "),
|
||||
client: client,
|
||||
fileManager: fileManager,
|
||||
commander: command.NewCommander(),
|
||||
lookupIP: net.LookupIP,
|
||||
}
|
||||
}
|
||||
@@ -2,98 +2,82 @@ package dns
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"net"
|
||||
"net/http"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/qdm12/dns/pkg/unbound"
|
||||
"github.com/qdm12/gluetun/internal/constants"
|
||||
"github.com/qdm12/gluetun/internal/models"
|
||||
"github.com/qdm12/gluetun/internal/settings"
|
||||
"github.com/qdm12/golibs/command"
|
||||
"github.com/qdm12/golibs/logging"
|
||||
)
|
||||
|
||||
type Looper interface {
|
||||
Run(ctx context.Context, wg *sync.WaitGroup, signalDNSReady func())
|
||||
Run(ctx context.Context, wg *sync.WaitGroup, dnsReadyCh chan<- struct{})
|
||||
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 unbound.Configurator
|
||||
client *http.Client
|
||||
logger logging.Logger
|
||||
streamMerger command.StreamMerger
|
||||
username string
|
||||
puid int
|
||||
pgid int
|
||||
loopLock sync.Mutex
|
||||
start chan struct{}
|
||||
running chan models.LoopStatus
|
||||
stop chan struct{}
|
||||
stopped chan struct{}
|
||||
updateTicker chan struct{}
|
||||
backoffTime time.Duration
|
||||
timeNow func() time.Time
|
||||
timeSince func(time.Time) time.Duration
|
||||
}
|
||||
|
||||
func NewLooper(conf Configurator, settings settings.DNS, logger logging.Logger,
|
||||
streamMerger command.StreamMerger, uid, gid int) Looper {
|
||||
const defaultBackoffTime = 10 * time.Second
|
||||
|
||||
func NewLooper(conf unbound.Configurator, settings settings.DNS, client *http.Client,
|
||||
logger logging.Logger, streamMerger command.StreamMerger,
|
||||
username string, puid, pgid int) Looper {
|
||||
return &looper{
|
||||
state: state{
|
||||
status: constants.Stopped,
|
||||
settings: settings,
|
||||
},
|
||||
conf: conf,
|
||||
settings: settings,
|
||||
client: client,
|
||||
logger: logger.WithPrefix("dns over tls: "),
|
||||
uid: uid,
|
||||
gid: gid,
|
||||
username: username,
|
||||
puid: puid,
|
||||
pgid: pgid,
|
||||
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{}),
|
||||
backoffTime: defaultBackoffTime,
|
||||
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")
|
||||
const waitDuration = 10 * time.Second
|
||||
timer := time.NewTimer(waitDuration)
|
||||
l.logger.Info("attempting restart in %s", l.backoffTime)
|
||||
timer := time.NewTimer(l.backoffTime)
|
||||
l.backoffTime *= 2
|
||||
select {
|
||||
case <-timer.C:
|
||||
case <-ctx.Done():
|
||||
@@ -103,122 +87,51 @@ 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()) {
|
||||
func (l *looper) Run(ctx context.Context, wg *sync.WaitGroup, dnsReadyCh chan<- struct{}) {
|
||||
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)
|
||||
crashed := false
|
||||
l.backoffTime = defaultBackoffTime
|
||||
|
||||
for ctx.Err() == nil {
|
||||
l.waitForSubsequentStart(ctx, unboundCancel)
|
||||
// Upper scope variables for Unbound only
|
||||
var unboundCancel context.CancelFunc = func() {}
|
||||
waitError := make(chan error)
|
||||
|
||||
settings := l.GetSettings()
|
||||
|
||||
// Setup
|
||||
if err := l.conf.DownloadRootHints(ctx, l.uid, l.gid); err != nil {
|
||||
l.logAndWait(ctx, err)
|
||||
continue
|
||||
for l.GetSettings().Enabled {
|
||||
if ctx.Err() != nil {
|
||||
l.logger.Warn("context canceled: exiting loop")
|
||||
return
|
||||
}
|
||||
var err error
|
||||
unboundCancel, err = l.setupUnbound(ctx, crashed, waitError)
|
||||
if err != nil {
|
||||
if !errors.Is(err, errUpdateFiles) {
|
||||
const fallback = true
|
||||
l.useUnencryptedDNS(fallback)
|
||||
}
|
||||
l.logAndWait(ctx, err)
|
||||
continue
|
||||
}
|
||||
break
|
||||
}
|
||||
if 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())
|
||||
stream, waitFn, err := l.conf.Start(unboundCtx, settings.VerbosityDetailsLevel)
|
||||
if err != nil {
|
||||
unboundCancel()
|
||||
const fallback = true
|
||||
if !l.GetSettings().Enabled {
|
||||
const fallback = false
|
||||
l.useUnencryptedDNS(fallback)
|
||||
l.logAndWait(ctx, err)
|
||||
continue
|
||||
}
|
||||
|
||||
// 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 {
|
||||
unboundCancel()
|
||||
const fallback = true
|
||||
l.useUnencryptedDNS(fallback)
|
||||
l.logAndWait(ctx, err)
|
||||
continue
|
||||
}
|
||||
waitError = make(chan error)
|
||||
go func() {
|
||||
err := waitFn() // blocking
|
||||
waitError <- err
|
||||
}()
|
||||
l.logger.Info("DNS over TLS is ready")
|
||||
signalDNSReady()
|
||||
dnsReadyCh <- struct{}{}
|
||||
|
||||
stayHere := true
|
||||
for stayHere {
|
||||
@@ -229,31 +142,84 @@ 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()
|
||||
}
|
||||
|
||||
var errUpdateFiles = errors.New("cannot update files")
|
||||
|
||||
// Returning cancel == nil signals we want to re-run setupUnbound
|
||||
// Returning err == errUpdateFiles signals we should not fall back
|
||||
// on the plaintext DNS as DOT is still up and running.
|
||||
func (l *looper) setupUnbound(ctx context.Context,
|
||||
previousCrashed bool, waitError chan<- error) (cancel context.CancelFunc, err error) {
|
||||
err = l.updateFiles(ctx)
|
||||
if err != nil {
|
||||
l.state.setStatusWithLock(constants.Crashed)
|
||||
return nil, errUpdateFiles
|
||||
}
|
||||
|
||||
settings := l.GetSettings()
|
||||
|
||||
unboundCtx, cancel := context.WithCancel(context.Background())
|
||||
stream, waitFn, err := l.conf.Start(unboundCtx, settings.Unbound.VerbosityDetailsLevel)
|
||||
if err != nil {
|
||||
cancel()
|
||||
if !previousCrashed {
|
||||
l.running <- constants.Crashed
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Started successfully
|
||||
go l.streamMerger.Merge(unboundCtx, stream, command.MergeName("unbound"))
|
||||
|
||||
l.conf.UseDNSInternally(net.IP{127, 0, 0, 1}) // use Unbound
|
||||
if err := l.conf.UseDNSSystemWide(net.IP{127, 0, 0, 1}, settings.KeepNameserver); err != nil { // use Unbound
|
||||
l.logger.Error(err)
|
||||
}
|
||||
|
||||
if err := l.conf.WaitForUnbound(ctx); err != nil {
|
||||
if !previousCrashed {
|
||||
l.running <- constants.Crashed
|
||||
}
|
||||
cancel()
|
||||
return nil, err
|
||||
}
|
||||
|
||||
go func() {
|
||||
err := waitFn() // blocking
|
||||
waitError <- err
|
||||
}()
|
||||
|
||||
l.logger.Info("ready")
|
||||
if !previousCrashed {
|
||||
l.running <- constants.Running
|
||||
} else {
|
||||
l.backoffTime = defaultBackoffTime
|
||||
l.state.setStatusWithLock(constants.Running)
|
||||
}
|
||||
return cancel, nil
|
||||
}
|
||||
|
||||
func (l *looper) useUnencryptedDNS(fallback bool) {
|
||||
@@ -275,11 +241,15 @@ func (l *looper) useUnencryptedDNS(fallback bool) {
|
||||
}
|
||||
|
||||
// Try with any IPv4 address from the providers chosen
|
||||
for _, provider := range settings.Providers {
|
||||
data := constants.DNSProviderMapping()[provider]
|
||||
for _, provider := range settings.Unbound.Providers {
|
||||
data, _ := unbound.GetProviderData(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)
|
||||
@@ -290,7 +260,7 @@ func (l *looper) useUnencryptedDNS(fallback bool) {
|
||||
}
|
||||
|
||||
// No IPv4 address found
|
||||
l.logger.Error("no ipv4 DNS address found for providers %s", settings.Providers)
|
||||
l.logger.Error("no ipv4 DNS address found for providers %s", settings.Unbound.Providers)
|
||||
}
|
||||
|
||||
func (l *looper) RunRestartTicker(ctx context.Context, wg *sync.WaitGroup) {
|
||||
@@ -314,7 +284,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 +320,27 @@ func (l *looper) RunRestartTicker(ctx context.Context, wg *sync.WaitGroup) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (l *looper) updateFiles(ctx context.Context) (err error) {
|
||||
l.logger.Info("downloading DNS over TLS cryptographic files")
|
||||
if err := l.conf.SetupFiles(ctx); err != nil {
|
||||
return err
|
||||
}
|
||||
settings := l.GetSettings()
|
||||
|
||||
l.logger.Info("downloading hostnames and IP block lists")
|
||||
hostnameLines, ipLines, errs := l.conf.BuildBlocked(ctx, l.client,
|
||||
settings.BlockMalicious, settings.BlockAds, settings.BlockSurveillance,
|
||||
settings.Unbound.BlockedHostnames, settings.Unbound.BlockedIPs,
|
||||
settings.Unbound.AllowedHostnames)
|
||||
for _, err := range errs {
|
||||
l.logger.Warn(err)
|
||||
}
|
||||
|
||||
if err := l.conf.MakeUnboundConf(
|
||||
settings.Unbound, hostnameLines, ipLines,
|
||||
l.username, l.puid, l.pgid); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -1,49 +0,0 @@
|
||||
package dns
|
||||
|
||||
import (
|
||||
"context"
|
||||
"net"
|
||||
"strings"
|
||||
|
||||
"github.com/qdm12/gluetun/internal/constants"
|
||||
)
|
||||
|
||||
// UseDNSInternally is to change the Go program DNS only.
|
||||
func (c *configurator) UseDNSInternally(ip net.IP) {
|
||||
c.logger.Info("using DNS address %s internally", ip.String())
|
||||
net.DefaultResolver = &net.Resolver{
|
||||
PreferGo: true,
|
||||
Dial: func(ctx context.Context, network, address string) (net.Conn, error) {
|
||||
d := net.Dialer{}
|
||||
return d.DialContext(ctx, "udp", net.JoinHostPort(ip.String(), "53"))
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// UseDNSSystemWide changes the nameserver to use for DNS system wide.
|
||||
func (c *configurator) UseDNSSystemWide(ip net.IP, keepNameserver bool) error {
|
||||
c.logger.Info("using DNS address %s system wide", ip.String())
|
||||
data, err := c.fileManager.ReadFile(string(constants.ResolvConf))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
s := strings.TrimSuffix(string(data), "\n")
|
||||
lines := strings.Split(s, "\n")
|
||||
if len(lines) == 1 && lines[0] == "" {
|
||||
lines = nil
|
||||
}
|
||||
found := false
|
||||
if !keepNameserver { // default
|
||||
for i := range lines {
|
||||
if strings.HasPrefix(lines[i], "nameserver ") {
|
||||
lines[i] = "nameserver " + ip.String()
|
||||
found = true
|
||||
}
|
||||
}
|
||||
}
|
||||
if !found {
|
||||
lines = append(lines, "nameserver "+ip.String())
|
||||
}
|
||||
data = []byte(strings.Join(lines, "\n"))
|
||||
return c.fileManager.WriteToFile(string(constants.ResolvConf), data)
|
||||
}
|
||||
@@ -1,74 +0,0 @@
|
||||
package dns
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net"
|
||||
"testing"
|
||||
|
||||
"github.com/golang/mock/gomock"
|
||||
"github.com/qdm12/gluetun/internal/constants"
|
||||
"github.com/qdm12/golibs/files/mock_files"
|
||||
"github.com/qdm12/golibs/logging/mock_logging"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func Test_UseDNSSystemWide(t *testing.T) {
|
||||
t.Parallel()
|
||||
tests := map[string]struct {
|
||||
data []byte
|
||||
writtenData []byte
|
||||
readErr error
|
||||
writeErr error
|
||||
err error
|
||||
}{
|
||||
"no data": {
|
||||
writtenData: []byte("nameserver 127.0.0.1"),
|
||||
},
|
||||
"read error": {
|
||||
readErr: fmt.Errorf("error"),
|
||||
err: fmt.Errorf("error"),
|
||||
},
|
||||
"write error": {
|
||||
writtenData: []byte("nameserver 127.0.0.1"),
|
||||
writeErr: fmt.Errorf("error"),
|
||||
err: fmt.Errorf("error"),
|
||||
},
|
||||
"lines without nameserver": {
|
||||
data: []byte("abc\ndef\n"),
|
||||
writtenData: []byte("abc\ndef\nnameserver 127.0.0.1"),
|
||||
},
|
||||
"lines with nameserver": {
|
||||
data: []byte("abc\nnameserver abc def\ndef\n"),
|
||||
writtenData: []byte("abc\nnameserver 127.0.0.1\ndef"),
|
||||
},
|
||||
}
|
||||
for name, tc := range tests {
|
||||
tc := tc
|
||||
t.Run(name, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
mockCtrl := gomock.NewController(t)
|
||||
defer mockCtrl.Finish()
|
||||
fileManager := mock_files.NewMockFileManager(mockCtrl)
|
||||
fileManager.EXPECT().ReadFile(string(constants.ResolvConf)).
|
||||
Return(tc.data, tc.readErr).Times(1)
|
||||
if tc.readErr == nil {
|
||||
fileManager.EXPECT().WriteToFile(string(constants.ResolvConf), tc.writtenData).
|
||||
Return(tc.writeErr).Times(1)
|
||||
}
|
||||
logger := mock_logging.NewMockLogger(mockCtrl)
|
||||
logger.EXPECT().Info("using DNS address %s system wide", "127.0.0.1").Times(1)
|
||||
c := &configurator{
|
||||
fileManager: fileManager,
|
||||
logger: logger,
|
||||
}
|
||||
err := c.UseDNSSystemWide(net.IP{127, 0, 0, 1}, false)
|
||||
if tc.err != nil {
|
||||
require.Error(t, err)
|
||||
assert.Equal(t, tc.err.Error(), err.Error())
|
||||
} else {
|
||||
assert.NoError(t, err)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -1,40 +0,0 @@
|
||||
package dns
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"net/http"
|
||||
|
||||
"github.com/qdm12/gluetun/internal/constants"
|
||||
"github.com/qdm12/golibs/files"
|
||||
)
|
||||
|
||||
func (c *configurator) DownloadRootHints(ctx context.Context, uid, gid int) error {
|
||||
c.logger.Info("downloading root hints from %s", constants.NamedRootURL)
|
||||
content, status, err := c.client.Get(ctx, string(constants.NamedRootURL))
|
||||
if err != nil {
|
||||
return err
|
||||
} else if status != http.StatusOK {
|
||||
return fmt.Errorf("HTTP status code is %d for %s", status, constants.NamedRootURL)
|
||||
}
|
||||
return c.fileManager.WriteToFile(
|
||||
string(constants.RootHints),
|
||||
content,
|
||||
files.Ownership(uid, gid),
|
||||
files.Permissions(constants.UserReadPermission))
|
||||
}
|
||||
|
||||
func (c *configurator) DownloadRootKey(ctx context.Context, uid, gid int) error {
|
||||
c.logger.Info("downloading root key from %s", constants.RootKeyURL)
|
||||
content, status, err := c.client.Get(ctx, string(constants.RootKeyURL))
|
||||
if err != nil {
|
||||
return err
|
||||
} else if status != http.StatusOK {
|
||||
return fmt.Errorf("HTTP status code is %d for %s", status, constants.RootKeyURL)
|
||||
}
|
||||
return c.fileManager.WriteToFile(
|
||||
string(constants.RootKey),
|
||||
content,
|
||||
files.Ownership(uid, gid),
|
||||
files.Permissions(constants.UserReadPermission))
|
||||
}
|
||||
@@ -1,143 +0,0 @@
|
||||
package dns
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"testing"
|
||||
|
||||
"github.com/golang/mock/gomock"
|
||||
"github.com/qdm12/gluetun/internal/constants"
|
||||
"github.com/qdm12/golibs/files"
|
||||
"github.com/qdm12/golibs/files/mock_files"
|
||||
"github.com/qdm12/golibs/logging/mock_logging"
|
||||
"github.com/qdm12/golibs/network/mock_network"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func Test_DownloadRootHints(t *testing.T) { //nolint:dupl
|
||||
t.Parallel()
|
||||
tests := map[string]struct {
|
||||
content []byte
|
||||
status int
|
||||
clientErr error
|
||||
writeErr error
|
||||
err error
|
||||
}{
|
||||
"no data": {
|
||||
status: http.StatusOK,
|
||||
},
|
||||
"bad status": {
|
||||
status: http.StatusBadRequest,
|
||||
err: fmt.Errorf("HTTP status code is 400 for https://raw.githubusercontent.com/qdm12/files/master/named.root.updated"), //nolint:lll
|
||||
},
|
||||
"client error": {
|
||||
clientErr: fmt.Errorf("error"),
|
||||
err: fmt.Errorf("error"),
|
||||
},
|
||||
"write error": {
|
||||
status: http.StatusOK,
|
||||
writeErr: fmt.Errorf("error"),
|
||||
err: fmt.Errorf("error"),
|
||||
},
|
||||
"data": {
|
||||
content: []byte("content"),
|
||||
status: http.StatusOK,
|
||||
},
|
||||
}
|
||||
for name, tc := range tests {
|
||||
tc := tc
|
||||
t.Run(name, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
mockCtrl := gomock.NewController(t)
|
||||
defer mockCtrl.Finish()
|
||||
ctx := context.Background()
|
||||
logger := mock_logging.NewMockLogger(mockCtrl)
|
||||
logger.EXPECT().Info("downloading root hints from %s", constants.NamedRootURL).Times(1)
|
||||
client := mock_network.NewMockClient(mockCtrl)
|
||||
client.EXPECT().Get(ctx, string(constants.NamedRootURL)).
|
||||
Return(tc.content, tc.status, tc.clientErr).Times(1)
|
||||
fileManager := mock_files.NewMockFileManager(mockCtrl)
|
||||
if tc.clientErr == nil && tc.status == http.StatusOK {
|
||||
fileManager.EXPECT().WriteToFile(
|
||||
string(constants.RootHints),
|
||||
tc.content,
|
||||
gomock.AssignableToTypeOf(files.Ownership(0, 0)),
|
||||
gomock.AssignableToTypeOf(files.Ownership(0, 0))).
|
||||
Return(tc.writeErr).Times(1)
|
||||
}
|
||||
c := &configurator{logger: logger, client: client, fileManager: fileManager}
|
||||
err := c.DownloadRootHints(ctx, 1000, 1000)
|
||||
if tc.err != nil {
|
||||
require.Error(t, err)
|
||||
assert.Equal(t, tc.err.Error(), err.Error())
|
||||
} else {
|
||||
assert.NoError(t, err)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func Test_DownloadRootKey(t *testing.T) { //nolint:dupl
|
||||
t.Parallel()
|
||||
tests := map[string]struct {
|
||||
content []byte
|
||||
status int
|
||||
clientErr error
|
||||
writeErr error
|
||||
err error
|
||||
}{
|
||||
"no data": {
|
||||
status: http.StatusOK,
|
||||
},
|
||||
"bad status": {
|
||||
status: http.StatusBadRequest,
|
||||
err: fmt.Errorf("HTTP status code is 400 for https://raw.githubusercontent.com/qdm12/files/master/root.key.updated"), //nolint:lll
|
||||
},
|
||||
"client error": {
|
||||
clientErr: fmt.Errorf("error"),
|
||||
err: fmt.Errorf("error"),
|
||||
},
|
||||
"write error": {
|
||||
status: http.StatusOK,
|
||||
writeErr: fmt.Errorf("error"),
|
||||
err: fmt.Errorf("error"),
|
||||
},
|
||||
"data": {
|
||||
content: []byte("content"),
|
||||
status: http.StatusOK,
|
||||
},
|
||||
}
|
||||
for name, tc := range tests {
|
||||
tc := tc
|
||||
t.Run(name, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
mockCtrl := gomock.NewController(t)
|
||||
defer mockCtrl.Finish()
|
||||
ctx := context.Background()
|
||||
logger := mock_logging.NewMockLogger(mockCtrl)
|
||||
logger.EXPECT().Info("downloading root key from %s", constants.RootKeyURL).Times(1)
|
||||
client := mock_network.NewMockClient(mockCtrl)
|
||||
client.EXPECT().Get(ctx, string(constants.RootKeyURL)).
|
||||
Return(tc.content, tc.status, tc.clientErr).Times(1)
|
||||
fileManager := mock_files.NewMockFileManager(mockCtrl)
|
||||
if tc.clientErr == nil && tc.status == http.StatusOK {
|
||||
fileManager.EXPECT().WriteToFile(
|
||||
string(constants.RootKey),
|
||||
tc.content,
|
||||
gomock.AssignableToTypeOf(files.Ownership(0, 0)),
|
||||
gomock.AssignableToTypeOf(files.Ownership(0, 0)),
|
||||
).Return(tc.writeErr).Times(1)
|
||||
}
|
||||
c := &configurator{logger: logger, client: client, fileManager: fileManager}
|
||||
err := c.DownloadRootKey(ctx, 1000, 1001)
|
||||
if tc.err != nil {
|
||||
require.Error(t, err)
|
||||
assert.Equal(t, tc.err.Error(), err.Error())
|
||||
} else {
|
||||
assert.NoError(t, err)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
99
internal/dns/state.go
Normal file
99
internal/dns/state.go
Normal file
@@ -0,0 +1,99 @@
|
||||
package dns
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"reflect"
|
||||
"sync"
|
||||
|
||||
"github.com/qdm12/gluetun/internal/constants"
|
||||
"github.com/qdm12/gluetun/internal/models"
|
||||
"github.com/qdm12/gluetun/internal/settings"
|
||||
)
|
||||
|
||||
type state struct {
|
||||
status models.LoopStatus
|
||||
settings settings.DNS
|
||||
statusMu sync.RWMutex
|
||||
settingsMu sync.RWMutex
|
||||
}
|
||||
|
||||
func (s *state) setStatusWithLock(status models.LoopStatus) {
|
||||
s.statusMu.Lock()
|
||||
defer s.statusMu.Unlock()
|
||||
s.status = status
|
||||
}
|
||||
|
||||
func (l *looper) GetStatus() (status models.LoopStatus) {
|
||||
l.state.statusMu.RLock()
|
||||
defer l.state.statusMu.RUnlock()
|
||||
return l.state.status
|
||||
}
|
||||
|
||||
func (l *looper) SetStatus(status models.LoopStatus) (outcome string, err error) {
|
||||
l.state.statusMu.Lock()
|
||||
defer l.state.statusMu.Unlock()
|
||||
existingStatus := l.state.status
|
||||
|
||||
switch status {
|
||||
case constants.Running:
|
||||
switch existingStatus {
|
||||
case constants.Starting, constants.Running, constants.Stopping, constants.Crashed:
|
||||
return fmt.Sprintf("already %s", existingStatus), nil
|
||||
}
|
||||
l.loopLock.Lock()
|
||||
defer l.loopLock.Unlock()
|
||||
l.state.status = constants.Starting
|
||||
l.state.statusMu.Unlock()
|
||||
l.start <- struct{}{}
|
||||
newStatus := <-l.running
|
||||
l.state.statusMu.Lock()
|
||||
l.state.status = newStatus
|
||||
return newStatus.String(), nil
|
||||
case constants.Stopped:
|
||||
switch existingStatus {
|
||||
case constants.Starting, constants.Stopping, constants.Stopped, constants.Crashed:
|
||||
return fmt.Sprintf("already %s", existingStatus), nil
|
||||
}
|
||||
l.loopLock.Lock()
|
||||
defer l.loopLock.Unlock()
|
||||
l.state.status = constants.Stopping
|
||||
l.state.statusMu.Unlock()
|
||||
l.stop <- struct{}{}
|
||||
<-l.stopped
|
||||
l.state.statusMu.Lock()
|
||||
l.state.status = constants.Stopped
|
||||
return status.String(), nil
|
||||
default:
|
||||
return "", fmt.Errorf("status %q can only be %q or %q",
|
||||
status, constants.Running, constants.Stopped)
|
||||
}
|
||||
}
|
||||
|
||||
func (l *looper) GetSettings() (settings settings.DNS) {
|
||||
l.state.settingsMu.RLock()
|
||||
defer l.state.settingsMu.RUnlock()
|
||||
return l.state.settings
|
||||
}
|
||||
|
||||
func (l *looper) SetSettings(settings settings.DNS) (outcome string) {
|
||||
l.state.settingsMu.Lock()
|
||||
settingsUnchanged := reflect.DeepEqual(l.state.settings, settings)
|
||||
if settingsUnchanged {
|
||||
l.state.settingsMu.Unlock()
|
||||
return "settings left unchanged"
|
||||
}
|
||||
tempSettings := l.state.settings
|
||||
tempSettings.UpdatePeriod = settings.UpdatePeriod
|
||||
onlyUpdatePeriodChanged := reflect.DeepEqual(tempSettings, settings)
|
||||
l.state.settings = settings
|
||||
l.state.settingsMu.Unlock()
|
||||
if onlyUpdatePeriodChanged {
|
||||
l.updateTicker <- struct{}{}
|
||||
return "update period changed"
|
||||
}
|
||||
_, _ = l.SetStatus(constants.Stopped)
|
||||
if settings.Enabled {
|
||||
outcome, _ = l.SetStatus(constants.Running)
|
||||
}
|
||||
return outcome
|
||||
}
|
||||
@@ -1,28 +0,0 @@
|
||||
package dns
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"time"
|
||||
)
|
||||
|
||||
func (c *configurator) WaitForUnbound() (err error) {
|
||||
const hostToResolve = "github.com"
|
||||
waitDurations := [...]time.Duration{
|
||||
300 * time.Millisecond,
|
||||
100 * time.Millisecond,
|
||||
300 * time.Millisecond,
|
||||
500 * time.Millisecond,
|
||||
time.Second,
|
||||
2 * time.Second,
|
||||
}
|
||||
maxTries := len(waitDurations)
|
||||
for i, waitDuration := range waitDurations {
|
||||
time.Sleep(waitDuration)
|
||||
_, err := c.lookupIP(hostToResolve)
|
||||
if err == nil {
|
||||
return nil
|
||||
}
|
||||
c.logger.Warn("could not resolve %s (try %d of %d): %s", hostToResolve, i+1, maxTries, err)
|
||||
}
|
||||
return fmt.Errorf("Unbound does not seem to be working after %d tries", maxTries)
|
||||
}
|
||||
@@ -8,8 +8,8 @@ import (
|
||||
"github.com/qdm12/gluetun/internal/models"
|
||||
"github.com/qdm12/gluetun/internal/routing"
|
||||
"github.com/qdm12/golibs/command"
|
||||
"github.com/qdm12/golibs/files"
|
||||
"github.com/qdm12/golibs/logging"
|
||||
"github.com/qdm12/golibs/os"
|
||||
)
|
||||
|
||||
// Configurator allows to change firewall rules and modify network routes.
|
||||
@@ -29,7 +29,7 @@ type configurator struct { //nolint:maligned
|
||||
commander command.Commander
|
||||
logger logging.Logger
|
||||
routing routing.Routing
|
||||
fileManager files.FileManager // for custom iptables rules
|
||||
openFile os.OpenFileFunc // for custom iptables rules
|
||||
iptablesMutex sync.Mutex
|
||||
debug bool
|
||||
defaultInterface string
|
||||
@@ -47,12 +47,12 @@ type configurator struct { //nolint:maligned
|
||||
}
|
||||
|
||||
// NewConfigurator creates a new Configurator instance.
|
||||
func NewConfigurator(logger logging.Logger, routing routing.Routing, fileManager files.FileManager) Configurator {
|
||||
func NewConfigurator(logger logging.Logger, routing routing.Routing, openFile os.OpenFileFunc) Configurator {
|
||||
return &configurator{
|
||||
commander: command.NewCommander(),
|
||||
logger: logger.WithPrefix("firewall: "),
|
||||
routing: routing,
|
||||
fileManager: fileManager,
|
||||
openFile: openFile,
|
||||
allowedInputPorts: make(map[uint16]string),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,7 +3,9 @@ package firewall
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"net"
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
"github.com/qdm12/gluetun/internal/models"
|
||||
@@ -150,14 +152,18 @@ func (c *configurator) acceptInputToPort(ctx context.Context, intf string, port
|
||||
}
|
||||
|
||||
func (c *configurator) runUserPostRules(ctx context.Context, filepath string, remove bool) error {
|
||||
exists, err := c.fileManager.FileExists(filepath)
|
||||
if err != nil {
|
||||
return err
|
||||
} else if !exists {
|
||||
file, err := c.openFile(filepath, os.O_RDONLY, 0)
|
||||
if os.IsNotExist(err) {
|
||||
return nil
|
||||
} else if err != nil {
|
||||
return err
|
||||
}
|
||||
b, err := c.fileManager.ReadFile(filepath)
|
||||
b, err := ioutil.ReadAll(file)
|
||||
if err != nil {
|
||||
_ = file.Close()
|
||||
return err
|
||||
}
|
||||
if err := file.Close(); err != nil {
|
||||
return err
|
||||
}
|
||||
lines := strings.Split(string(b), "\n")
|
||||
|
||||
@@ -1,21 +1,25 @@
|
||||
package healthcheck
|
||||
|
||||
import (
|
||||
"net"
|
||||
"errors"
|
||||
"net/http"
|
||||
"sync"
|
||||
|
||||
"github.com/qdm12/golibs/logging"
|
||||
)
|
||||
|
||||
type handler struct {
|
||||
logger logging.Logger
|
||||
resolver *net.Resolver
|
||||
logger logging.Logger
|
||||
healthErr error
|
||||
healthErrMu sync.RWMutex
|
||||
}
|
||||
|
||||
func newHandler(logger logging.Logger, resolver *net.Resolver) http.Handler {
|
||||
var errHealthcheckNotRunYet = errors.New("healthcheck did not run yet")
|
||||
|
||||
func newHandler(logger logging.Logger) *handler {
|
||||
return &handler{
|
||||
logger: logger,
|
||||
resolver: resolver,
|
||||
logger: logger,
|
||||
healthErr: errHealthcheckNotRunYet,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -24,11 +28,22 @@ func (h *handler) ServeHTTP(responseWriter http.ResponseWriter, request *http.Re
|
||||
http.Error(responseWriter, "method not supported for healthcheck", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
err := healthCheck(request.Context(), h.resolver)
|
||||
if err != nil {
|
||||
if err := h.getErr(); err != nil {
|
||||
h.logger.Error(err)
|
||||
http.Error(responseWriter, err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
responseWriter.WriteHeader(http.StatusOK)
|
||||
}
|
||||
|
||||
func (h *handler) setErr(err error) {
|
||||
h.healthErrMu.Lock()
|
||||
defer h.healthErrMu.Unlock()
|
||||
h.healthErr = err
|
||||
}
|
||||
|
||||
func (h *handler) getErr() (err error) {
|
||||
h.healthErrMu.RLock()
|
||||
defer h.healthErrMu.RUnlock()
|
||||
return h.healthErr
|
||||
}
|
||||
|
||||
@@ -2,8 +2,53 @@ package healthcheck
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"net"
|
||||
"sync"
|
||||
"time"
|
||||
)
|
||||
|
||||
func (s *server) runHealthcheckLoop(ctx context.Context, wg *sync.WaitGroup) {
|
||||
defer wg.Done()
|
||||
for {
|
||||
previousErr := s.handler.getErr()
|
||||
|
||||
err := healthCheck(ctx, s.resolver)
|
||||
s.handler.setErr(err)
|
||||
|
||||
if previousErr != nil && err == nil {
|
||||
s.logger.Info("passed")
|
||||
}
|
||||
|
||||
if err != nil { // try again after 1 second
|
||||
timer := time.NewTimer(time.Second)
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
if !timer.Stop() {
|
||||
<-timer.C
|
||||
}
|
||||
return
|
||||
case <-timer.C:
|
||||
}
|
||||
continue
|
||||
}
|
||||
// Success, check again in 10 minutes
|
||||
const period = 10 * time.Minute
|
||||
timer := time.NewTimer(period)
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
if !timer.Stop() {
|
||||
<-timer.C
|
||||
}
|
||||
return
|
||||
case <-timer.C:
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var (
|
||||
errNoIPResolved = errors.New("no IP address resolved")
|
||||
)
|
||||
|
||||
func healthCheck(ctx context.Context, resolver *net.Resolver) (err error) {
|
||||
@@ -12,9 +57,9 @@ func healthCheck(ctx context.Context, resolver *net.Resolver) (err error) {
|
||||
ips, err := resolver.LookupIP(ctx, "ip", domainToResolve)
|
||||
switch {
|
||||
case err != nil:
|
||||
return fmt.Errorf("cannot resolve github.com: %s", err)
|
||||
return err
|
||||
case len(ips) == 0:
|
||||
return fmt.Errorf("resolved no IP addresses for %s", domainToResolve)
|
||||
return fmt.Errorf("%w for %s", errNoIPResolved, domainToResolve)
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -16,26 +16,36 @@ type Server interface {
|
||||
}
|
||||
|
||||
type server struct {
|
||||
address string
|
||||
logger logging.Logger
|
||||
handler http.Handler
|
||||
address string
|
||||
logger logging.Logger
|
||||
handler *handler
|
||||
resolver *net.Resolver
|
||||
}
|
||||
|
||||
func NewServer(address string, logger logging.Logger) Server {
|
||||
healthcheckLogger := logger.WithPrefix("healthcheck: ")
|
||||
return &server{
|
||||
address: address,
|
||||
logger: logger.WithPrefix("healthcheck: "),
|
||||
handler: newHandler(logger, &net.Resolver{}),
|
||||
address: address,
|
||||
logger: healthcheckLogger,
|
||||
handler: newHandler(healthcheckLogger),
|
||||
resolver: net.DefaultResolver,
|
||||
}
|
||||
}
|
||||
|
||||
func (s *server) Run(ctx context.Context, wg *sync.WaitGroup) {
|
||||
defer wg.Done()
|
||||
|
||||
internalWg := &sync.WaitGroup{}
|
||||
internalWg.Add(1)
|
||||
go s.runHealthcheckLoop(ctx, internalWg)
|
||||
|
||||
server := http.Server{
|
||||
Addr: s.address,
|
||||
Handler: s.handler,
|
||||
}
|
||||
internalWg.Add(1)
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
defer internalWg.Done()
|
||||
<-ctx.Done()
|
||||
s.logger.Warn("context canceled: shutting down server")
|
||||
defer s.logger.Warn("server shut down")
|
||||
@@ -46,9 +56,12 @@ func (s *server) Run(ctx context.Context, wg *sync.WaitGroup) {
|
||||
s.logger.Error("failed shutting down: %s", err)
|
||||
}
|
||||
}()
|
||||
|
||||
s.logger.Info("listening on %s", s.address)
|
||||
err := server.ListenAndServe()
|
||||
if err != nil && !errors.Is(ctx.Err(), context.Canceled) {
|
||||
s.logger.Error(err)
|
||||
}
|
||||
|
||||
internalWg.Wait()
|
||||
}
|
||||
|
||||
@@ -4,108 +4,90 @@ import (
|
||||
"context"
|
||||
"fmt"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/qdm12/gluetun/internal/constants"
|
||||
"github.com/qdm12/gluetun/internal/models"
|
||||
"github.com/qdm12/gluetun/internal/settings"
|
||||
"github.com/qdm12/golibs/logging"
|
||||
)
|
||||
|
||||
type Looper interface {
|
||||
Run(ctx context.Context, wg *sync.WaitGroup)
|
||||
Restart()
|
||||
Start()
|
||||
Stop()
|
||||
SetStatus(status models.LoopStatus) (outcome string, err error)
|
||||
GetStatus() (status models.LoopStatus)
|
||||
GetSettings() (settings settings.HTTPProxy)
|
||||
SetSettings(settings settings.HTTPProxy)
|
||||
SetSettings(settings settings.HTTPProxy) (outcome string)
|
||||
}
|
||||
|
||||
type looper struct {
|
||||
settings settings.HTTPProxy
|
||||
settingsMutex sync.RWMutex
|
||||
logger logging.Logger
|
||||
restart chan struct{}
|
||||
state state
|
||||
// Other objects
|
||||
logger logging.Logger
|
||||
// Internal channels and locks
|
||||
loopLock sync.Mutex
|
||||
running chan models.LoopStatus
|
||||
stop, stopped chan struct{}
|
||||
start chan struct{}
|
||||
stop chan struct{}
|
||||
backoffTime time.Duration
|
||||
}
|
||||
|
||||
const defaultBackoffTime = 10 * time.Second
|
||||
|
||||
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{}),
|
||||
state: state{
|
||||
status: constants.Stopped,
|
||||
settings: settings,
|
||||
},
|
||||
logger: logger.WithPrefix("http proxy: "),
|
||||
start: make(chan struct{}),
|
||||
running: make(chan models.LoopStatus),
|
||||
stop: make(chan struct{}),
|
||||
stopped: make(chan struct{}),
|
||||
backoffTime: defaultBackoffTime,
|
||||
}
|
||||
}
|
||||
|
||||
func (l *looper) 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
|
||||
}
|
||||
|
||||
crashed := false
|
||||
|
||||
if l.GetSettings().Enabled {
|
||||
go func() {
|
||||
_, _ = l.SetStatus(constants.Running)
|
||||
}()
|
||||
}
|
||||
|
||||
select {
|
||||
case <-l.start:
|
||||
case <-ctx.Done():
|
||||
return
|
||||
}
|
||||
|
||||
defer l.logger.Warn("loop exited")
|
||||
|
||||
for ctx.Err() == nil {
|
||||
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
|
||||
}
|
||||
}
|
||||
runCtx, runCancel := context.WithCancel(ctx)
|
||||
|
||||
settings := l.GetSettings()
|
||||
address := fmt.Sprintf("0.0.0.0:%d", settings.Port)
|
||||
address := fmt.Sprintf(":%d", settings.Port)
|
||||
server := New(runCtx, address, l.logger, settings.Stealth, settings.Log, settings.User, settings.Password)
|
||||
|
||||
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)
|
||||
errorCh := make(chan error)
|
||||
go server.Run(runCtx, runWg, errorCh)
|
||||
|
||||
// TODO stable timer, check Shadowsocks
|
||||
if !crashed {
|
||||
l.running <- constants.Running
|
||||
crashed = false
|
||||
} else {
|
||||
l.backoffTime = defaultBackoffTime
|
||||
l.state.setStatusWithLock(constants.Running)
|
||||
}
|
||||
|
||||
stayHere := true
|
||||
for stayHere {
|
||||
@@ -115,21 +97,38 @@ func (l *looper) Run(ctx context.Context, wg *sync.WaitGroup) {
|
||||
runCancel()
|
||||
runWg.Wait()
|
||||
return
|
||||
case <-l.restart: // triggered restart
|
||||
l.logger.Info("restarting")
|
||||
case <-l.start:
|
||||
l.logger.Info("starting")
|
||||
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)
|
||||
l.stopped <- struct{}{}
|
||||
case err := <-errorCh:
|
||||
runWg.Wait()
|
||||
l.state.setStatusWithLock(constants.Crashed)
|
||||
l.logAndWait(ctx, err)
|
||||
crashed = true
|
||||
stayHere = false
|
||||
}
|
||||
}
|
||||
runCancel() // repetition for linter only
|
||||
}
|
||||
}
|
||||
|
||||
func (l *looper) logAndWait(ctx context.Context, err error) {
|
||||
l.logger.Error(err)
|
||||
l.logger.Info("retrying in %s", l.backoffTime)
|
||||
timer := time.NewTimer(l.backoffTime)
|
||||
l.backoffTime *= 2
|
||||
select {
|
||||
case <-timer.C:
|
||||
case <-ctx.Done():
|
||||
if !timer.Stop() {
|
||||
<-timer.C
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,7 +10,7 @@ import (
|
||||
)
|
||||
|
||||
type Server interface {
|
||||
Run(ctx context.Context, wg *sync.WaitGroup)
|
||||
Run(ctx context.Context, wg *sync.WaitGroup, errorCh chan<- error)
|
||||
}
|
||||
|
||||
type server struct {
|
||||
@@ -31,13 +31,13 @@ func New(ctx context.Context, address string, logger logging.Logger,
|
||||
}
|
||||
}
|
||||
|
||||
func (s *server) Run(ctx context.Context, wg *sync.WaitGroup) {
|
||||
func (s *server) Run(ctx context.Context, wg *sync.WaitGroup, errorCh chan<- error) {
|
||||
defer wg.Done()
|
||||
server := http.Server{Addr: s.address, Handler: s.handler}
|
||||
go func() {
|
||||
<-ctx.Done()
|
||||
s.logger.Warn("context canceled: exiting loop")
|
||||
defer s.logger.Warn("loop exited")
|
||||
s.logger.Warn("shutting down server")
|
||||
defer s.logger.Warn("server shut down")
|
||||
const shutdownGraceDuration = 2 * time.Second
|
||||
shutdownCtx, cancel := context.WithTimeout(context.Background(), shutdownGraceDuration)
|
||||
defer cancel()
|
||||
@@ -47,8 +47,8 @@ func (s *server) Run(ctx context.Context, wg *sync.WaitGroup) {
|
||||
}()
|
||||
s.logger.Info("listening on %s", s.address)
|
||||
err := server.ListenAndServe()
|
||||
if err != nil && ctx.Err() != context.Canceled {
|
||||
s.logger.Error(err)
|
||||
if err != nil && ctx.Err() == nil {
|
||||
errorCh <- err
|
||||
}
|
||||
s.internalWG.Wait()
|
||||
}
|
||||
|
||||
101
internal/httpproxy/state.go
Normal file
101
internal/httpproxy/state.go
Normal file
@@ -0,0 +1,101 @@
|
||||
package httpproxy
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"reflect"
|
||||
"sync"
|
||||
|
||||
"github.com/qdm12/gluetun/internal/constants"
|
||||
"github.com/qdm12/gluetun/internal/models"
|
||||
"github.com/qdm12/gluetun/internal/settings"
|
||||
)
|
||||
|
||||
type state struct {
|
||||
status models.LoopStatus
|
||||
settings settings.HTTPProxy
|
||||
statusMu sync.RWMutex
|
||||
settingsMu sync.RWMutex
|
||||
}
|
||||
|
||||
func (s *state) setStatusWithLock(status models.LoopStatus) {
|
||||
s.statusMu.Lock()
|
||||
defer s.statusMu.Unlock()
|
||||
s.status = status
|
||||
}
|
||||
|
||||
func (l *looper) GetStatus() (status models.LoopStatus) {
|
||||
l.state.statusMu.RLock()
|
||||
defer l.state.statusMu.RUnlock()
|
||||
return l.state.status
|
||||
}
|
||||
|
||||
func (l *looper) SetStatus(status models.LoopStatus) (outcome string, err error) {
|
||||
l.state.statusMu.Lock()
|
||||
defer l.state.statusMu.Unlock()
|
||||
existingStatus := l.state.status
|
||||
|
||||
switch status {
|
||||
case constants.Running:
|
||||
switch existingStatus {
|
||||
case constants.Starting, constants.Running, constants.Stopping, constants.Crashed:
|
||||
return fmt.Sprintf("already %s", existingStatus), nil
|
||||
}
|
||||
l.loopLock.Lock()
|
||||
defer l.loopLock.Unlock()
|
||||
l.state.status = constants.Starting
|
||||
l.state.statusMu.Unlock()
|
||||
l.start <- struct{}{}
|
||||
newStatus := <-l.running
|
||||
l.state.statusMu.Lock()
|
||||
l.state.status = newStatus
|
||||
return newStatus.String(), nil
|
||||
case constants.Stopped:
|
||||
switch existingStatus {
|
||||
case constants.Stopped, constants.Stopping, constants.Starting, constants.Crashed:
|
||||
return fmt.Sprintf("already %s", existingStatus), nil
|
||||
}
|
||||
l.loopLock.Lock()
|
||||
defer l.loopLock.Unlock()
|
||||
l.state.status = constants.Stopping
|
||||
l.state.statusMu.Unlock()
|
||||
l.stop <- struct{}{}
|
||||
<-l.stopped
|
||||
l.state.statusMu.Lock()
|
||||
l.state.status = status
|
||||
return status.String(), nil
|
||||
default:
|
||||
return "", fmt.Errorf("status %q can only be %q or %q",
|
||||
status, constants.Running, constants.Stopped)
|
||||
}
|
||||
}
|
||||
|
||||
func (l *looper) GetSettings() (settings settings.HTTPProxy) {
|
||||
l.state.settingsMu.RLock()
|
||||
defer l.state.settingsMu.RUnlock()
|
||||
return l.state.settings
|
||||
}
|
||||
|
||||
func (l *looper) SetSettings(settings settings.HTTPProxy) (outcome string) {
|
||||
l.state.settingsMu.Lock()
|
||||
settingsUnchanged := reflect.DeepEqual(settings, l.state.settings)
|
||||
if settingsUnchanged {
|
||||
l.state.settingsMu.Unlock()
|
||||
return "settings left unchanged"
|
||||
}
|
||||
newEnabled := settings.Enabled
|
||||
previousEnabled := l.state.settings.Enabled
|
||||
l.state.settings = settings
|
||||
l.state.settingsMu.Unlock()
|
||||
// Either restart or set changed status
|
||||
switch {
|
||||
case !newEnabled && !previousEnabled:
|
||||
case newEnabled && previousEnabled:
|
||||
_, _ = l.SetStatus(constants.Stopped)
|
||||
_, _ = l.SetStatus(constants.Running)
|
||||
case newEnabled && !previousEnabled:
|
||||
_, _ = l.SetStatus(constants.Running)
|
||||
case !newEnabled && previousEnabled:
|
||||
_, _ = l.SetStatus(constants.Stopped)
|
||||
}
|
||||
return "settings updated"
|
||||
}
|
||||
@@ -10,11 +10,10 @@ import (
|
||||
"github.com/qdm12/golibs/logging"
|
||||
)
|
||||
|
||||
//nolint:lll
|
||||
var regularExpressions = struct { //nolint:gochecknoglobals
|
||||
unboundPrefix *regexp.Regexp
|
||||
}{
|
||||
unboundPrefix: regexp.MustCompile(`unbound: \[[0-9]{10}\] unbound\[[0-9]+:0\] `),
|
||||
unboundPrefix: regexp.MustCompile(`unbound: \[[0-9]{10}\] unbound\[[0-9]+:[0|1]\] `),
|
||||
}
|
||||
|
||||
func PostProcessLine(s string) (filtered string, level logging.Level) {
|
||||
|
||||
@@ -8,8 +8,6 @@ import (
|
||||
type (
|
||||
// VPNDevice is the device name used to tunnel using Openvpn.
|
||||
VPNDevice string
|
||||
// DNSProvider is a DNS over TLS server provider name.
|
||||
DNSProvider string
|
||||
// DNSHost is the DNS host to use for TLS validation.
|
||||
DNSHost string
|
||||
// URL is an HTTP(s) URL address.
|
||||
@@ -20,8 +18,14 @@ type (
|
||||
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
|
||||
}
|
||||
|
||||
@@ -3,5 +3,5 @@ package models
|
||||
type BuildInformation struct {
|
||||
Version string `json:"version"`
|
||||
Commit string `json:"commit"`
|
||||
BuildDate string `json:"buildDate"`
|
||||
BuildDate string `json:"build_date"`
|
||||
}
|
||||
|
||||
@@ -4,8 +4,9 @@ import "net"
|
||||
|
||||
// DNSProviderData contains information for a DNS provider.
|
||||
type DNSProviderData struct {
|
||||
IPs []net.IP
|
||||
SupportsTLS bool
|
||||
SupportsIPv6 bool
|
||||
Host DNSHost
|
||||
IPs []net.IP
|
||||
SupportsTLS bool
|
||||
SupportsIPv6 bool
|
||||
SupportsDNSSec bool
|
||||
Host DNSHost
|
||||
}
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
package models
|
||||
|
||||
import "net"
|
||||
import (
|
||||
"net"
|
||||
)
|
||||
|
||||
type OpenVPNConnection struct {
|
||||
IP net.IP
|
||||
|
||||
@@ -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"`
|
||||
@@ -34,20 +34,20 @@ type ServerSelection struct {
|
||||
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 {
|
||||
ClientCertificate string `json:"-"` // Cyberghost
|
||||
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.
|
||||
@@ -134,8 +134,7 @@ func (p *ProviderSettings) String() string {
|
||||
)
|
||||
case "privado":
|
||||
settingsList = append(settingsList,
|
||||
"Cities: "+commaJoin(p.ServerSelection.Cities),
|
||||
"Server numbers: "+commaJoin(numbers),
|
||||
"Hostnames: "+commaJoin(p.ServerSelection.Hostnames),
|
||||
)
|
||||
default:
|
||||
settingsList = append(settingsList,
|
||||
|
||||
@@ -96,15 +96,15 @@ func (s *NordvpnServer) String() string {
|
||||
}
|
||||
|
||||
type PurevpnServer struct {
|
||||
Region string `json:"region"`
|
||||
Country string `json:"country"`
|
||||
Region string `json:"region"`
|
||||
City string `json:"city"`
|
||||
IPs []net.IP `json:"ips"`
|
||||
}
|
||||
|
||||
func (s *PurevpnServer) String() string {
|
||||
return fmt.Sprintf("{Region: %q, Country: %q, City: %q, IPs: %s}",
|
||||
s.Region, s.Country, s.City, goStringifyIPs(s.IPs))
|
||||
return fmt.Sprintf("{Country: %q, Region: %q, City: %q, IPs: %s}",
|
||||
s.Country, s.Region, s.City, goStringifyIPs(s.IPs))
|
||||
}
|
||||
|
||||
type PrivadoServer struct {
|
||||
|
||||
@@ -1,31 +1,68 @@
|
||||
package openvpn
|
||||
|
||||
import (
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
"github.com/qdm12/gluetun/internal/constants"
|
||||
"github.com/qdm12/golibs/files"
|
||||
)
|
||||
|
||||
// WriteAuthFile writes the OpenVPN auth file to disk with the right permissions.
|
||||
func (c *configurator) WriteAuthFile(user, password string, uid, gid int) error {
|
||||
exists, err := c.fileManager.FileExists(string(constants.OpenVPNAuthConf))
|
||||
if err != nil {
|
||||
func (c *configurator) WriteAuthFile(user, password string, puid, pgid int) error {
|
||||
const filepath = string(constants.OpenVPNAuthConf)
|
||||
file, err := c.os.OpenFile(filepath, os.O_RDONLY, 0)
|
||||
|
||||
if err != nil && !os.IsNotExist(err) {
|
||||
return err
|
||||
} else if exists {
|
||||
data, err := c.fileManager.ReadFile(string(constants.OpenVPNAuthConf))
|
||||
}
|
||||
|
||||
if os.IsNotExist(err) {
|
||||
file, err = c.os.OpenFile(filepath, os.O_WRONLY|os.O_CREATE, 0400)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
lines := strings.Split(string(data), "\n")
|
||||
if len(lines) > 1 && lines[0] == user && lines[1] == password {
|
||||
return nil
|
||||
_, err = file.WriteString(user + "\n" + password)
|
||||
if err != nil {
|
||||
_ = file.Close()
|
||||
return err
|
||||
}
|
||||
c.logger.Info("username and password changed", constants.OpenVPNAuthConf)
|
||||
err = file.Chown(puid, pgid)
|
||||
if err != nil {
|
||||
_ = file.Close()
|
||||
return err
|
||||
}
|
||||
return file.Close()
|
||||
}
|
||||
return c.fileManager.WriteLinesToFile(
|
||||
string(constants.OpenVPNAuthConf),
|
||||
[]string{user, password},
|
||||
files.Ownership(uid, gid),
|
||||
files.Permissions(constants.UserReadPermission))
|
||||
|
||||
data, err := ioutil.ReadAll(file)
|
||||
if err != nil {
|
||||
_ = file.Close()
|
||||
return err
|
||||
}
|
||||
if err := file.Close(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
lines := strings.Split(string(data), "\n")
|
||||
if len(lines) > 1 && lines[0] == user && lines[1] == password {
|
||||
return nil
|
||||
}
|
||||
|
||||
c.logger.Info("username and password changed in %s", constants.OpenVPNAuthConf)
|
||||
file, err = c.os.OpenFile(filepath, os.O_TRUNC|os.O_WRONLY, 0400)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
_, err = file.WriteString(user + "\n" + password)
|
||||
if err != nil {
|
||||
_ = file.Close()
|
||||
return err
|
||||
}
|
||||
err = file.Chown(puid, pgid)
|
||||
if err != nil {
|
||||
_ = file.Close()
|
||||
return err
|
||||
}
|
||||
return file.Close()
|
||||
}
|
||||
|
||||
@@ -4,6 +4,7 @@ import (
|
||||
"context"
|
||||
"net"
|
||||
"net/http"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
@@ -14,32 +15,28 @@ import (
|
||||
"github.com/qdm12/gluetun/internal/routing"
|
||||
"github.com/qdm12/gluetun/internal/settings"
|
||||
"github.com/qdm12/golibs/command"
|
||||
"github.com/qdm12/golibs/files"
|
||||
"github.com/qdm12/golibs/logging"
|
||||
"github.com/qdm12/golibs/os"
|
||||
)
|
||||
|
||||
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
|
||||
puid int
|
||||
pgid int
|
||||
// Configurators
|
||||
conf Configurator
|
||||
fw firewall.Configurator
|
||||
@@ -47,105 +44,108 @@ type looper struct {
|
||||
// Other objects
|
||||
logger, pfLogger logging.Logger
|
||||
client *http.Client
|
||||
fileManager files.FileManager
|
||||
openFile os.OpenFileFunc
|
||||
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
|
||||
crashed bool
|
||||
backoffTime time.Duration
|
||||
}
|
||||
|
||||
func NewLooper(provider models.VPNProvider, settings settings.OpenVPN,
|
||||
uid, gid int, allServers models.AllServers,
|
||||
const defaultBackoffTime = 15 * time.Second
|
||||
|
||||
func NewLooper(settings settings.OpenVPN,
|
||||
username string, puid, pgid int, allServers models.AllServers,
|
||||
conf Configurator, fw firewall.Configurator, routing routing.Routing,
|
||||
logger logging.Logger, client *http.Client, fileManager files.FileManager,
|
||||
logger logging.Logger, client *http.Client, openFile os.OpenFileFunc,
|
||||
streamMerger command.StreamMerger, cancel context.CancelFunc) Looper {
|
||||
return &looper{
|
||||
provider: provider,
|
||||
settings: settings,
|
||||
uid: uid,
|
||||
gid: gid,
|
||||
allServers: allServers,
|
||||
state: state{
|
||||
status: constants.Stopped,
|
||||
settings: settings,
|
||||
allServers: allServers,
|
||||
},
|
||||
username: username,
|
||||
puid: puid,
|
||||
pgid: pgid,
|
||||
conf: conf,
|
||||
fw: fw,
|
||||
routing: routing,
|
||||
logger: logger.WithPrefix("openvpn: "),
|
||||
pfLogger: logger.WithPrefix("port forwarding: "),
|
||||
client: client,
|
||||
fileManager: fileManager,
|
||||
openFile: openFile,
|
||||
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),
|
||||
backoffTime: defaultBackoffTime,
|
||||
}
|
||||
}
|
||||
|
||||
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) signalCrashedStatus() {
|
||||
if !l.crashed {
|
||||
l.crashed = true
|
||||
l.running <- constants.Crashed
|
||||
}
|
||||
}
|
||||
|
||||
func (l *looper) Run(ctx context.Context, wg *sync.WaitGroup) {
|
||||
defer wg.Done()
|
||||
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)
|
||||
l.signalCrashedStatus()
|
||||
l.cancel()
|
||||
return
|
||||
}
|
||||
lines := providerConf.BuildConf(
|
||||
connection,
|
||||
settings.Verbosity,
|
||||
l.uid,
|
||||
l.gid,
|
||||
l.username,
|
||||
settings.Root,
|
||||
settings.Cipher,
|
||||
settings.Auth,
|
||||
settings.Provider.ExtraConfigOptions,
|
||||
)
|
||||
if err := l.fileManager.WriteLinesToFile(string(constants.OpenVPNConf), lines,
|
||||
files.Ownership(l.uid, l.gid), files.Permissions(constants.UserReadPermission)); err != nil {
|
||||
|
||||
if err := writeOpenvpnConf(lines, l.openFile); err != nil {
|
||||
l.logger.Error(err)
|
||||
l.signalCrashedStatus()
|
||||
l.cancel()
|
||||
return
|
||||
}
|
||||
|
||||
if err := l.conf.WriteAuthFile(settings.User, settings.Password, l.uid, l.gid); err != nil {
|
||||
if err := l.conf.WriteAuthFile(settings.User, settings.Password, l.puid, l.pgid); err != nil {
|
||||
l.logger.Error(err)
|
||||
l.signalCrashedStatus()
|
||||
l.cancel()
|
||||
return
|
||||
}
|
||||
|
||||
if err := l.fw.SetVPNConnection(ctx, connection); err != nil {
|
||||
l.logger.Error(err)
|
||||
l.signalCrashedStatus()
|
||||
l.cancel()
|
||||
return
|
||||
}
|
||||
@@ -155,6 +155,7 @@ func (l *looper) Run(ctx context.Context, wg *sync.WaitGroup) {
|
||||
stream, waitFn, err := l.conf.Start(openvpnCtx)
|
||||
if err != nil {
|
||||
openvpnCancel()
|
||||
l.signalCrashedStatus()
|
||||
l.logAndWait(ctx, err)
|
||||
continue
|
||||
}
|
||||
@@ -179,31 +180,50 @@ 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 l.crashed {
|
||||
l.crashed = false
|
||||
l.backoffTime = defaultBackoffTime
|
||||
l.state.setStatusWithLock(constants.Running)
|
||||
} else {
|
||||
l.running <- 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)
|
||||
l.crashed = true
|
||||
stayHere = false
|
||||
}
|
||||
}
|
||||
close(waitError)
|
||||
openvpnCancel() // just for the linter
|
||||
}
|
||||
}
|
||||
|
||||
func (l *looper) logAndWait(ctx context.Context, err error) {
|
||||
l.logger.Error(err)
|
||||
const waitTime = 30 * time.Second
|
||||
l.logger.Info("retrying in %s", waitTime)
|
||||
timer := time.NewTimer(waitTime)
|
||||
l.logger.Info("retrying in %s", l.backoffTime)
|
||||
timer := time.NewTimer(l.backoffTime)
|
||||
l.backoffTime *= 2
|
||||
select {
|
||||
case <-timer.C:
|
||||
case <-ctx.Done():
|
||||
@@ -218,24 +238,37 @@ 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,
|
||||
client, l.openFile, l.pfLogger,
|
||||
gateway, l.fw, syncState)
|
||||
}
|
||||
|
||||
func (l *looper) GetPortForwarded() (portForwarded uint16) {
|
||||
l.portForwardedMutex.RLock()
|
||||
defer l.portForwardedMutex.RUnlock()
|
||||
return l.portForwarded
|
||||
func writeOpenvpnConf(lines []string, openFile os.OpenFileFunc) error {
|
||||
const filepath = string(constants.OpenVPNConf)
|
||||
file, err := openFile(filepath, os.O_CREATE|os.O_TRUNC|os.O_WRONLY, 0644)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
_, err = file.WriteString(strings.Join(lines, "\n"))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if err := file.Close(); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -3,38 +3,33 @@ package openvpn
|
||||
import (
|
||||
"context"
|
||||
"io"
|
||||
"os"
|
||||
|
||||
"github.com/qdm12/gluetun/internal/unix"
|
||||
"github.com/qdm12/golibs/command"
|
||||
"github.com/qdm12/golibs/files"
|
||||
"github.com/qdm12/golibs/logging"
|
||||
"golang.org/x/sys/unix"
|
||||
"github.com/qdm12/golibs/os"
|
||||
)
|
||||
|
||||
type Configurator interface {
|
||||
Version(ctx context.Context) (string, error)
|
||||
WriteAuthFile(user, password string, uid, gid int) error
|
||||
WriteAuthFile(user, password string, puid, pgid int) error
|
||||
CheckTUN() error
|
||||
CreateTUN() error
|
||||
Start(ctx context.Context) (stdout io.ReadCloser, waitFn func() error, err error)
|
||||
}
|
||||
|
||||
type configurator struct {
|
||||
fileManager files.FileManager
|
||||
logger logging.Logger
|
||||
commander command.Commander
|
||||
openFile func(name string, flag int, perm os.FileMode) (*os.File, error)
|
||||
mkDev func(major uint32, minor uint32) uint64
|
||||
mkNod func(path string, mode uint32, dev int) error
|
||||
logger logging.Logger
|
||||
commander command.Commander
|
||||
os os.OS
|
||||
unix unix.Unix
|
||||
}
|
||||
|
||||
func NewConfigurator(logger logging.Logger, fileManager files.FileManager) Configurator {
|
||||
func NewConfigurator(logger logging.Logger, os os.OS, unix unix.Unix) Configurator {
|
||||
return &configurator{
|
||||
fileManager: fileManager,
|
||||
logger: logger.WithPrefix("openvpn configurator: "),
|
||||
commander: command.NewCommander(),
|
||||
openFile: os.OpenFile,
|
||||
mkDev: unix.Mkdev,
|
||||
mkNod: unix.Mknod,
|
||||
logger: logger.WithPrefix("openvpn configurator: "),
|
||||
commander: command.NewCommander(),
|
||||
os: os,
|
||||
unix: unix,
|
||||
}
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
@@ -5,13 +5,13 @@ import (
|
||||
"os"
|
||||
|
||||
"github.com/qdm12/gluetun/internal/constants"
|
||||
"golang.org/x/sys/unix"
|
||||
"github.com/qdm12/gluetun/internal/unix"
|
||||
)
|
||||
|
||||
// CheckTUN checks the tunnel device is present and accessible.
|
||||
func (c *configurator) CheckTUN() error {
|
||||
c.logger.Info("checking for device %s", constants.TunnelDevice)
|
||||
f, err := c.openFile(string(constants.TunnelDevice), os.O_RDWR, 0)
|
||||
f, err := c.os.OpenFile(string(constants.TunnelDevice), os.O_RDWR, 0)
|
||||
if err != nil {
|
||||
return fmt.Errorf("TUN device is not available: %w", err)
|
||||
}
|
||||
@@ -23,19 +23,29 @@ func (c *configurator) CheckTUN() error {
|
||||
|
||||
func (c *configurator) CreateTUN() error {
|
||||
c.logger.Info("creating %s", constants.TunnelDevice)
|
||||
if err := c.fileManager.CreateDir("/dev/net"); err != nil {
|
||||
if err := c.os.MkdirAll("/dev/net", 0751); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
const (
|
||||
major = 10
|
||||
minor = 200
|
||||
)
|
||||
dev := c.mkDev(major, minor)
|
||||
if err := c.mkNod(string(constants.TunnelDevice), unix.S_IFCHR, int(dev)); err != nil {
|
||||
dev := c.unix.Mkdev(major, minor)
|
||||
if err := c.unix.Mknod(string(constants.TunnelDevice), unix.S_IFCHR, int(dev)); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := c.fileManager.SetUserPermissions(string(constants.TunnelDevice), 0666); err != nil {
|
||||
|
||||
const filepath = string(constants.TunnelDevice)
|
||||
file, err := c.os.OpenFile(filepath, os.O_WRONLY, 0666)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
const readWriteAllPerms os.FileMode = 0666
|
||||
if err := file.Chmod(readWriteAllPerms); err != nil {
|
||||
_ = file.Close()
|
||||
return err
|
||||
}
|
||||
|
||||
return file.Close()
|
||||
}
|
||||
|
||||
@@ -23,20 +23,15 @@ func (p *reader) GetCyberghostRegions() (regions []string, err error) {
|
||||
return p.envParams.GetCSVInPossibilities("REGION", constants.CyberghostRegionChoices())
|
||||
}
|
||||
|
||||
// GetCyberghostClientKey obtains the one line client key to use for openvpn from the
|
||||
// environment variable CLIENT_KEY or from the file at /gluetun/client.key.
|
||||
// GetCyberghostClientKey obtains the client key to use for openvpn
|
||||
// from the secret file /run/secrets/openvpn_clientkey or from the file
|
||||
// /gluetun/client.key.
|
||||
func (p *reader) GetCyberghostClientKey() (clientKey string, err error) {
|
||||
clientKey, err = p.envParams.GetEnv("CLIENT_KEY", libparams.CaseSensitiveValue())
|
||||
if err != nil {
|
||||
return "", err
|
||||
} else if len(clientKey) > 0 {
|
||||
return clientKey, nil
|
||||
}
|
||||
content, err := p.fileManager.ReadFile(string(constants.ClientKey))
|
||||
b, err := p.getFromFileOrSecretFile("OPENVPN_CLIENTKEY", string(constants.ClientKey))
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return extractClientKey(content)
|
||||
return extractClientKey(b)
|
||||
}
|
||||
|
||||
func extractClientKey(b []byte) (key string, err error) {
|
||||
@@ -52,14 +47,15 @@ func extractClientKey(b []byte) (key string, err error) {
|
||||
return s, nil
|
||||
}
|
||||
|
||||
// GetCyberghostClientCertificate obtains the client certificate to use for openvpn from the
|
||||
// file at /gluetun/client.crt.
|
||||
// GetCyberghostClientCertificate obtains the client certificate to use for openvpn
|
||||
// from the secret file /run/secrets/openvpn_clientcrt or from the file
|
||||
// /gluetun/client.crt.
|
||||
func (p *reader) GetCyberghostClientCertificate() (clientCertificate string, err error) {
|
||||
content, err := p.fileManager.ReadFile(string(constants.ClientCertificate))
|
||||
b, err := p.getFromFileOrSecretFile("OPENVPN_CLIENTCRT", string(constants.ClientCertificate))
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return extractClientCertificate(content)
|
||||
return extractClientCertificate(b)
|
||||
}
|
||||
|
||||
func extractClientCertificate(b []byte) (certificate string, err error) {
|
||||
|
||||
@@ -6,8 +6,7 @@ import (
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/qdm12/gluetun/internal/constants"
|
||||
"github.com/qdm12/gluetun/internal/models"
|
||||
dns "github.com/qdm12/dns/pkg/unbound"
|
||||
libparams "github.com/qdm12/golibs/params"
|
||||
)
|
||||
|
||||
@@ -19,21 +18,17 @@ func (r *reader) GetDNSOverTLS() (DNSOverTLS bool, err error) { //nolint:gocriti
|
||||
|
||||
// GetDNSOverTLSProviders obtains the DNS over TLS providers to use
|
||||
// from the environment variable DOT_PROVIDERS.
|
||||
func (r *reader) GetDNSOverTLSProviders() (providers []models.DNSProvider, err error) {
|
||||
func (r *reader) GetDNSOverTLSProviders() (providers []string, err error) {
|
||||
s, err := r.envParams.GetEnv("DOT_PROVIDERS", libparams.Default("cloudflare"))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
for _, word := range strings.Split(s, ",") {
|
||||
provider := models.DNSProvider(word)
|
||||
switch provider {
|
||||
case constants.Cloudflare, constants.Google, constants.Quad9,
|
||||
constants.Quadrant, constants.CleanBrowsing, constants.SecureDNS,
|
||||
constants.LibreDNS:
|
||||
providers = append(providers, provider)
|
||||
default:
|
||||
for _, provider := range strings.Split(s, ",") {
|
||||
_, ok := dns.GetProviderData(provider)
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("DNS over TLS provider %q is not valid", provider)
|
||||
}
|
||||
providers = append(providers, provider)
|
||||
}
|
||||
return providers, nil
|
||||
}
|
||||
@@ -130,8 +125,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"))
|
||||
}
|
||||
|
||||
@@ -49,26 +49,28 @@ func (r *reader) GetHTTPProxyPort() (port uint16, err error) {
|
||||
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.
|
||||
// GetHTTPProxyUser obtains the HTTP proxy server user.
|
||||
// It first tries to use the HTTPPROXY_USER environment variable (easier for the end user)
|
||||
// and then tries to read from the secret file httpproxy_user if nothing was found.
|
||||
func (r *reader) GetHTTPProxyUser() (user string, err error) {
|
||||
retroKeysOption := libparams.RetroKeys(
|
||||
const compulsory = false
|
||||
return r.getFromEnvOrSecretFile(
|
||||
"HTTPPROXY_USER",
|
||||
compulsory,
|
||||
[]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.
|
||||
// GetHTTPProxyPassword obtains the HTTP proxy server password.
|
||||
// It first tries to use the HTTPPROXY_PASSWORD environment variable (easier for the end user)
|
||||
// and then tries to read from the secret file httpproxy_password if nothing was found.
|
||||
func (r *reader) GetHTTPProxyPassword() (password string, err error) {
|
||||
retroKeysOption := libparams.RetroKeys(
|
||||
const compulsory = false
|
||||
return r.getFromEnvOrSecretFile(
|
||||
"HTTPPROXY_USER",
|
||||
compulsory,
|
||||
[]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
|
||||
|
||||
@@ -9,29 +9,19 @@ import (
|
||||
)
|
||||
|
||||
// GetUser obtains the user to use to connect to the VPN servers.
|
||||
func (r *reader) GetUser() (s string, err error) {
|
||||
defer func() {
|
||||
unsetenvErr := r.unsetEnv("USER")
|
||||
if err == nil {
|
||||
err = unsetenvErr
|
||||
}
|
||||
}()
|
||||
return r.envParams.GetEnv("USER", libparams.CaseSensitiveValue(), libparams.Compulsory())
|
||||
// It first tries to use the OPENVPN_USER environment variable (easier for the end user)
|
||||
// and then tries to read from the secret file openvpn_user if nothing was found.
|
||||
func (r *reader) GetUser() (user string, err error) {
|
||||
const compulsory = true
|
||||
return r.getFromEnvOrSecretFile("OPENVPN_USER", compulsory, []string{"USER"})
|
||||
}
|
||||
|
||||
// GetPassword obtains the password to use to connect to the VPN servers.
|
||||
func (r *reader) GetPassword(required bool) (s string, err error) {
|
||||
defer func() {
|
||||
unsetenvErr := r.unsetEnv("PASSWORD")
|
||||
if err == nil {
|
||||
err = unsetenvErr
|
||||
}
|
||||
}()
|
||||
options := []libparams.GetEnvSetter{libparams.CaseSensitiveValue()}
|
||||
if required {
|
||||
options = append(options, libparams.Compulsory())
|
||||
}
|
||||
return r.envParams.GetEnv("PASSWORD", options...)
|
||||
// It first tries to use the OPENVPN_PASSWORD environment variable (easier for the end user)
|
||||
// and then tries to read from the secret file openvpn_password if nothing was found.
|
||||
func (r *reader) GetPassword() (s string, err error) {
|
||||
const compulsory = true
|
||||
return r.getFromEnvOrSecretFile("OPENVPN_PASSWORD", compulsory, []string{"PASSWORD"})
|
||||
}
|
||||
|
||||
// GetNetworkProtocol obtains the network protocol to use to connect to the
|
||||
|
||||
@@ -2,12 +2,11 @@ package params
|
||||
|
||||
import (
|
||||
"net"
|
||||
"os"
|
||||
"time"
|
||||
|
||||
"github.com/qdm12/gluetun/internal/models"
|
||||
"github.com/qdm12/golibs/files"
|
||||
"github.com/qdm12/golibs/logging"
|
||||
"github.com/qdm12/golibs/os"
|
||||
libparams "github.com/qdm12/golibs/params"
|
||||
"github.com/qdm12/golibs/verification"
|
||||
)
|
||||
@@ -18,7 +17,7 @@ type Reader interface {
|
||||
|
||||
// DNS over TLS getters
|
||||
GetDNSOverTLS() (DNSOverTLS bool, err error)
|
||||
GetDNSOverTLSProviders() (providers []models.DNSProvider, err error)
|
||||
GetDNSOverTLSProviders() (providers []string, err error)
|
||||
GetDNSOverTLSCaching() (caching bool, err error)
|
||||
GetDNSOverTLSVerbosity() (verbosityLevel uint8, err error)
|
||||
GetDNSOverTLSVerbosityDetails() (verbosityDetailsLevel uint8, err error)
|
||||
@@ -34,10 +33,10 @@ type Reader interface {
|
||||
GetDNSKeepNameserver() (on bool, err error)
|
||||
|
||||
// System
|
||||
GetUID() (uid int, err error)
|
||||
GetGID() (gid int, err error)
|
||||
GetPUID() (puid int, err error)
|
||||
GetPGID() (pgid 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)
|
||||
@@ -48,7 +47,7 @@ type Reader interface {
|
||||
|
||||
// VPN getters
|
||||
GetUser() (s string, err error)
|
||||
GetPassword(required bool) (s string, err error)
|
||||
GetPassword() (s string, err error)
|
||||
GetNetworkProtocol() (protocol models.NetworkProtocol, err error)
|
||||
GetOpenVPNVerbosity() (verbosity int, err error)
|
||||
GetOpenVPNRoot() (root bool, err error)
|
||||
@@ -128,22 +127,20 @@ type Reader interface {
|
||||
}
|
||||
|
||||
type reader struct {
|
||||
envParams libparams.EnvParams
|
||||
logger logging.Logger
|
||||
verifier verification.Verifier
|
||||
unsetEnv func(key string) error
|
||||
fileManager files.FileManager
|
||||
envParams libparams.EnvParams
|
||||
logger logging.Logger
|
||||
verifier verification.Verifier
|
||||
os os.OS
|
||||
}
|
||||
|
||||
// Newreader returns a paramsReadeer object to read parameters from
|
||||
// environment variables.
|
||||
func NewReader(logger logging.Logger, fileManager files.FileManager) Reader {
|
||||
func NewReader(logger logging.Logger, os os.OS) Reader {
|
||||
return &reader{
|
||||
envParams: libparams.NewEnvParams(),
|
||||
logger: logger,
|
||||
verifier: verification.NewVerifier(),
|
||||
unsetEnv: os.Unsetenv,
|
||||
fileManager: fileManager,
|
||||
envParams: libparams.NewEnvParams(),
|
||||
logger: logger,
|
||||
verifier: verification.NewVerifier(),
|
||||
os: os,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -2,10 +2,13 @@ package params
|
||||
|
||||
import (
|
||||
"github.com/qdm12/gluetun/internal/constants"
|
||||
libparams "github.com/qdm12/golibs/params"
|
||||
)
|
||||
|
||||
// GetPrivadoHostnames obtains the hostnames for the Privado server from the
|
||||
// environment variable HOSTNAME.
|
||||
// environment variable SERVER_HOSTNAME.
|
||||
func (r *reader) GetPrivadoHostnames() (hosts []string, err error) {
|
||||
return r.envParams.GetCSVInPossibilities("HOSTNAME", constants.PrivadoHostnameChoices())
|
||||
return r.envParams.GetCSVInPossibilities("SERVER_HOSTNAME",
|
||||
constants.PrivadoHostnameChoices(),
|
||||
libparams.RetroKeys([]string{"HOSTNAME"}, r.onRetroActive))
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
109
internal/params/secrets.go
Normal file
109
internal/params/secrets.go
Normal file
@@ -0,0 +1,109 @@
|
||||
package params
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"strings"
|
||||
|
||||
"github.com/qdm12/golibs/os"
|
||||
libparams "github.com/qdm12/golibs/params"
|
||||
)
|
||||
|
||||
var (
|
||||
ErrGetSecretFilepath = errors.New("cannot get secret file path from env")
|
||||
ErrReadSecretFile = errors.New("cannot read secret file")
|
||||
ErrSecretFileIsEmpty = errors.New("secret file is empty")
|
||||
ErrReadNonSecretFile = errors.New("cannot read non secret file")
|
||||
ErrFilesDoNotExist = errors.New("files do not exist")
|
||||
)
|
||||
|
||||
func (r *reader) getFromEnvOrSecretFile(envKey string, compulsory bool, retroKeys []string) (value string, err error) {
|
||||
envOptions := []libparams.GetEnvSetter{
|
||||
libparams.Compulsory(), // to fallback on file reading
|
||||
libparams.CaseSensitiveValue(),
|
||||
libparams.Unset(),
|
||||
libparams.RetroKeys(retroKeys, r.onRetroActive),
|
||||
}
|
||||
value, envErr := r.envParams.GetEnv(envKey, envOptions...)
|
||||
if envErr == nil {
|
||||
return value, nil
|
||||
}
|
||||
|
||||
defaultSecretFile := "/run/secrets/" + strings.ToLower(envKey)
|
||||
filepath, err := r.envParams.GetEnv(envKey+"_SECRETFILE",
|
||||
libparams.CaseSensitiveValue(),
|
||||
libparams.Default(defaultSecretFile),
|
||||
)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("%w: %s", ErrGetSecretFilepath, err)
|
||||
}
|
||||
|
||||
file, fileErr := r.os.OpenFile(filepath, os.O_RDONLY, 0)
|
||||
if os.IsNotExist(fileErr) {
|
||||
if compulsory {
|
||||
return "", envErr
|
||||
}
|
||||
return "", nil
|
||||
} else if fileErr != nil {
|
||||
return "", fmt.Errorf("%w: %s", ErrReadSecretFile, fileErr)
|
||||
}
|
||||
|
||||
b, err := ioutil.ReadAll(file)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("%w: %s", ErrReadSecretFile, err)
|
||||
}
|
||||
|
||||
value = string(b)
|
||||
value = strings.TrimSuffix(value, "\n")
|
||||
if compulsory && len(value) == 0 {
|
||||
return "", ErrSecretFileIsEmpty
|
||||
}
|
||||
|
||||
return value, nil
|
||||
}
|
||||
|
||||
// Tries to read from the secret file then the non secret file.
|
||||
func (r *reader) getFromFileOrSecretFile(secretName, filepath string) (
|
||||
b []byte, err error) {
|
||||
defaultSecretFile := "/run/secrets/" + strings.ToLower(secretName)
|
||||
secretFilepath, err := r.envParams.GetEnv(strings.ToUpper(secretName)+"_SECRETFILE",
|
||||
libparams.CaseSensitiveValue(),
|
||||
libparams.Default(defaultSecretFile),
|
||||
)
|
||||
if err != nil {
|
||||
return b, fmt.Errorf("%w: %s", ErrGetSecretFilepath, err)
|
||||
}
|
||||
|
||||
b, err = readFromFile(r.os.OpenFile, secretFilepath)
|
||||
if err != nil && !os.IsNotExist(err) {
|
||||
return b, fmt.Errorf("%w: %s", ErrReadSecretFile, err)
|
||||
} else if err == nil {
|
||||
return b, nil
|
||||
}
|
||||
|
||||
// Secret file does not exist, try the non secret file
|
||||
b, err = readFromFile(r.os.OpenFile, filepath)
|
||||
if err != nil && !os.IsNotExist(err) {
|
||||
return nil, fmt.Errorf("%w: %s", ErrReadSecretFile, err)
|
||||
} else if err == nil {
|
||||
return b, nil
|
||||
}
|
||||
return nil, fmt.Errorf("%w: %s and %s", ErrFilesDoNotExist, secretFilepath, filepath)
|
||||
}
|
||||
|
||||
func readFromFile(openFile os.OpenFileFunc, filepath string) (b []byte, err error) {
|
||||
file, err := openFile(filepath, os.O_RDONLY, 0)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
b, err = ioutil.ReadAll(file)
|
||||
if err != nil {
|
||||
_ = file.Close()
|
||||
return nil, err
|
||||
}
|
||||
if err := file.Close(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return b, nil
|
||||
}
|
||||
@@ -32,16 +32,12 @@ func (r *reader) GetShadowSocksPort() (port uint16, err error) {
|
||||
return uint16(portUint64), err
|
||||
}
|
||||
|
||||
// GetShadowSocksPassword obtains the ShadowSocks server password from the environment variable
|
||||
// SHADOWSOCKS_PASSWORD.
|
||||
// GetShadowSocksPassword obtains the ShadowSocks server password.
|
||||
// It first tries to use the SHADOWSOCKS_PASSWORD environment variable (easier for the end user)
|
||||
// and then tries to read from the secret file shadowsocks_password if nothing was found.
|
||||
func (r *reader) GetShadowSocksPassword() (password string, err error) {
|
||||
defer func() {
|
||||
unsetErr := r.unsetEnv("SHADOWSOCKS_PASSWORD")
|
||||
if err == nil {
|
||||
err = unsetErr
|
||||
}
|
||||
}()
|
||||
return r.envParams.GetEnv("SHADOWSOCKS_PASSWORD", libparams.CaseSensitiveValue())
|
||||
const compulsory = false
|
||||
return r.getFromEnvOrSecretFile("SHADOWSOCKS_PASSWORD", compulsory, nil)
|
||||
}
|
||||
|
||||
// GetShadowSocksMethod obtains the ShadowSocks method to use from the environment variable
|
||||
|
||||
@@ -1,29 +1,26 @@
|
||||
package params
|
||||
|
||||
import (
|
||||
"github.com/qdm12/gluetun/internal/models"
|
||||
libparams "github.com/qdm12/golibs/params"
|
||||
)
|
||||
|
||||
// GetUID obtains the user ID to use from the environment variable UID.
|
||||
func (r *reader) GetUID() (uid int, err error) {
|
||||
return r.envParams.GetEnvIntRange("UID", 0, 65535, libparams.Default("1000"))
|
||||
// GetPUID obtains the user ID to use from the environment variable PUID
|
||||
// with retro compatible variable UID.
|
||||
func (r *reader) GetPUID() (ppuid int, err error) {
|
||||
return r.envParams.GetEnvIntRange("PUID", 0, 65535,
|
||||
libparams.Default("1000"),
|
||||
libparams.RetroKeys([]string{"UID"}, r.onRetroActive))
|
||||
}
|
||||
|
||||
// GetGID obtains the group ID to use from the environment variable GID.
|
||||
func (r *reader) GetGID() (gid int, err error) {
|
||||
return r.envParams.GetEnvIntRange("GID", 0, 65535, libparams.Default("1000"))
|
||||
// GetGID obtains the group ID to use from the environment variable PGID
|
||||
// with retro compatible variable PGID.
|
||||
func (r *reader) GetPGID() (pgid int, err error) {
|
||||
return r.envParams.GetEnvIntRange("PGID", 0, 65535,
|
||||
libparams.Default("1000"),
|
||||
libparams.RetroKeys([]string{"GID"}, r.onRetroActive))
|
||||
}
|
||||
|
||||
// GetTZ obtains the timezone from the environment variable TZ.
|
||||
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
|
||||
}
|
||||
|
||||
@@ -21,9 +21,12 @@ func (r *reader) GetWindscribeCities() (cities []string, err error) {
|
||||
}
|
||||
|
||||
// GetWindscribeHostnames obtains the hostnames for the Windscribe servers from the
|
||||
// environment variable HOSTNAME.
|
||||
// environment variable SERVER_HOSTNAME.
|
||||
func (r *reader) GetWindscribeHostnames() (hostnames []string, err error) {
|
||||
return r.envParams.GetCSVInPossibilities("HOSTNAME", constants.WindscribeHostnameChoices())
|
||||
return r.envParams.GetCSVInPossibilities("SERVER_HOSTNAME",
|
||||
constants.WindscribeHostnameChoices(),
|
||||
libparams.RetroKeys([]string{"HOSTNAME"}, r.onRetroActive),
|
||||
)
|
||||
}
|
||||
|
||||
// GetWindscribePort obtains the port to reach the Windscribe server on from the
|
||||
|
||||
@@ -11,8 +11,8 @@ import (
|
||||
"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"
|
||||
"github.com/qdm12/golibs/os"
|
||||
)
|
||||
|
||||
type cyberghost struct {
|
||||
@@ -62,8 +62,8 @@ 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
|
||||
}
|
||||
@@ -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>",
|
||||
@@ -133,7 +133,7 @@ func (c *cyberghost) BuildConf(connection models.OpenVPNConnection, verbosity,
|
||||
}
|
||||
|
||||
func (c *cyberghost) PortForward(ctx context.Context, client *http.Client,
|
||||
fileManager files.FileManager, pfLogger logging.Logger, gateway net.IP, fw firewall.Configurator,
|
||||
openFile os.OpenFileFunc, pfLogger logging.Logger, gateway net.IP, fw firewall.Configurator,
|
||||
syncState func(port uint16) (pfFilepath models.Filepath)) {
|
||||
panic("port forwarding is not supported for cyberghost")
|
||||
}
|
||||
|
||||
@@ -10,8 +10,8 @@ import (
|
||||
"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"
|
||||
"github.com/qdm12/golibs/os"
|
||||
)
|
||||
|
||||
type mullvad struct {
|
||||
@@ -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>",
|
||||
@@ -128,7 +128,7 @@ func (m *mullvad) BuildConf(connection models.OpenVPNConnection,
|
||||
}
|
||||
|
||||
func (m *mullvad) PortForward(ctx context.Context, client *http.Client,
|
||||
fileManager files.FileManager, pfLogger logging.Logger, gateway net.IP, fw firewall.Configurator,
|
||||
openFile os.OpenFileFunc, pfLogger logging.Logger, gateway net.IP, fw firewall.Configurator,
|
||||
syncState func(port uint16) (pfFilepath models.Filepath)) {
|
||||
panic("port forwarding is not supported for mullvad")
|
||||
}
|
||||
|
||||
@@ -10,8 +10,8 @@ import (
|
||||
"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"
|
||||
"github.com/qdm12/golibs/os"
|
||||
)
|
||||
|
||||
type nordvpn struct {
|
||||
@@ -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
|
||||
@@ -115,13 +115,13 @@ func (n *nordvpn) BuildConf(connection models.OpenVPNConnection, verbosity, uid,
|
||||
// Modified variables
|
||||
fmt.Sprintf("verb %d", verbosity),
|
||||
fmt.Sprintf("auth-user-pass %s", constants.OpenVPNAuthConf),
|
||||
fmt.Sprintf("proto %s", string(connection.Protocol)),
|
||||
fmt.Sprintf("proto %s", connection.Protocol),
|
||||
fmt.Sprintf("remote %s %d", connection.IP.String(), connection.Port),
|
||||
fmt.Sprintf("cipher %s", cipher),
|
||||
fmt.Sprintf("auth %s", auth),
|
||||
}
|
||||
if !root {
|
||||
lines = append(lines, "user nonrootuser")
|
||||
lines = append(lines, "user "+username)
|
||||
}
|
||||
lines = append(lines, []string{
|
||||
"<ca>",
|
||||
@@ -142,7 +142,7 @@ func (n *nordvpn) BuildConf(connection models.OpenVPNConnection, verbosity, uid,
|
||||
}
|
||||
|
||||
func (n *nordvpn) PortForward(ctx context.Context, client *http.Client,
|
||||
fileManager files.FileManager, pfLogger logging.Logger, gateway net.IP, fw firewall.Configurator,
|
||||
openFile os.OpenFileFunc, pfLogger logging.Logger, gateway net.IP, fw firewall.Configurator,
|
||||
syncState func(port uint16) (pfFilepath models.Filepath)) {
|
||||
panic("port forwarding is not supported for nordvpn")
|
||||
}
|
||||
|
||||
@@ -19,8 +19,8 @@ import (
|
||||
"github.com/qdm12/gluetun/internal/firewall"
|
||||
gluetunLog "github.com/qdm12/gluetun/internal/logging"
|
||||
"github.com/qdm12/gluetun/internal/models"
|
||||
"github.com/qdm12/golibs/files"
|
||||
"github.com/qdm12/golibs/logging"
|
||||
"github.com/qdm12/golibs/os"
|
||||
)
|
||||
|
||||
type pia struct {
|
||||
@@ -109,7 +109,7 @@ func (p *pia) GetOpenVPNConnection(selection models.ServerSelection) (
|
||||
return connection, nil
|
||||
}
|
||||
|
||||
func (p *pia) 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) {
|
||||
var X509CRL, certificate string
|
||||
var defaultCipher, defaultAuth string
|
||||
@@ -161,7 +161,7 @@ func (p *pia) BuildConf(connection models.OpenVPNConnection, verbosity, uid, gid
|
||||
lines = append(lines, "ncp-disable")
|
||||
}
|
||||
if !root {
|
||||
lines = append(lines, "user nonrootuser")
|
||||
lines = append(lines, "user "+username)
|
||||
}
|
||||
lines = append(lines, []string{
|
||||
"<crl-verify>",
|
||||
@@ -183,7 +183,7 @@ func (p *pia) BuildConf(connection models.OpenVPNConnection, verbosity, uid, gid
|
||||
|
||||
//nolint:gocognit
|
||||
func (p *pia) PortForward(ctx context.Context, client *http.Client,
|
||||
fileManager files.FileManager, pfLogger logging.Logger, gateway net.IP, fw firewall.Configurator,
|
||||
openFile os.OpenFileFunc, pfLogger logging.Logger, gateway net.IP, fw firewall.Configurator,
|
||||
syncState func(port uint16) (pfFilepath models.Filepath)) {
|
||||
if !p.activeServer.PortForward {
|
||||
pfLogger.Error("The server %s does not support port forwarding", p.activeServer.Region)
|
||||
@@ -203,7 +203,7 @@ func (p *pia) PortForward(ctx context.Context, client *http.Client,
|
||||
return
|
||||
}
|
||||
defer pfLogger.Warn("loop exited")
|
||||
data, err := readPIAPortForwardData(fileManager)
|
||||
data, err := readPIAPortForwardData(openFile)
|
||||
if err != nil {
|
||||
pfLogger.Error(err)
|
||||
}
|
||||
@@ -222,7 +222,7 @@ func (p *pia) PortForward(ctx context.Context, client *http.Client,
|
||||
|
||||
if !dataFound || expired {
|
||||
tryUntilSuccessful(ctx, pfLogger, func() error {
|
||||
data, err = refreshPIAPortForwardData(ctx, client, gateway, fileManager)
|
||||
data, err = refreshPIAPortForwardData(ctx, client, gateway, openFile)
|
||||
return err
|
||||
})
|
||||
if ctx.Err() != nil {
|
||||
@@ -240,12 +240,9 @@ func (p *pia) PortForward(ctx context.Context, client *http.Client,
|
||||
return
|
||||
}
|
||||
|
||||
filepath := syncState(data.Port)
|
||||
filepath := string(syncState(data.Port))
|
||||
pfLogger.Info("Writing port to %s", filepath)
|
||||
if err := fileManager.WriteToFile(
|
||||
string(filepath), []byte(fmt.Sprintf("%d", data.Port)),
|
||||
files.Permissions(constants.AllReadWritePermissions),
|
||||
); err != nil {
|
||||
if err := writePortForwardedToFile(openFile, filepath, data.Port); err != nil {
|
||||
pfLogger.Error(err)
|
||||
}
|
||||
|
||||
@@ -281,7 +278,7 @@ func (p *pia) PortForward(ctx context.Context, client *http.Client,
|
||||
pfLogger.Warn("Forward port has expired on %s, getting another one", data.Expiration.Format(time.RFC1123))
|
||||
oldPort := data.Port
|
||||
for {
|
||||
data, err = refreshPIAPortForwardData(ctx, client, gateway, fileManager)
|
||||
data, err = refreshPIAPortForwardData(ctx, client, gateway, openFile)
|
||||
if err != nil {
|
||||
pfLogger.Error(err)
|
||||
continue
|
||||
@@ -298,10 +295,7 @@ func (p *pia) PortForward(ctx context.Context, client *http.Client,
|
||||
}
|
||||
filepath := syncState(data.Port)
|
||||
pfLogger.Info("Writing port to %s", filepath)
|
||||
if err := fileManager.WriteToFile(
|
||||
string(filepath), []byte(fmt.Sprintf("%d", data.Port)),
|
||||
files.Permissions(constants.AllReadWritePermissions),
|
||||
); err != nil {
|
||||
if err := writePortForwardedToFile(openFile, string(filepath), data.Port); err != nil {
|
||||
pfLogger.Error(err)
|
||||
}
|
||||
if err := bindPIAPort(ctx, client, gateway, data); err != nil {
|
||||
@@ -365,8 +359,8 @@ func newPIAHTTPClient(serverName string) (client *http.Client, err error) {
|
||||
}
|
||||
|
||||
func refreshPIAPortForwardData(ctx context.Context, client *http.Client,
|
||||
gateway net.IP, fileManager files.FileManager) (data piaPortForwardData, err error) {
|
||||
data.Token, err = fetchPIAToken(ctx, fileManager, client)
|
||||
gateway net.IP, openFile os.OpenFileFunc) (data piaPortForwardData, err error) {
|
||||
data.Token, err = fetchPIAToken(ctx, openFile, client)
|
||||
if err != nil {
|
||||
return data, fmt.Errorf("cannot obtain token: %w", err)
|
||||
}
|
||||
@@ -374,7 +368,7 @@ func refreshPIAPortForwardData(ctx context.Context, client *http.Client,
|
||||
if err != nil {
|
||||
return data, fmt.Errorf("cannot obtain port forwarding data: %w", err)
|
||||
}
|
||||
if err := writePIAPortForwardData(fileManager, data); err != nil {
|
||||
if err := writePIAPortForwardData(openFile, data); err != nil {
|
||||
return data, fmt.Errorf("cannot persist port forwarding information to file: %w", err)
|
||||
}
|
||||
return data, nil
|
||||
@@ -393,34 +387,39 @@ type piaPortForwardData struct {
|
||||
Expiration time.Time `json:"expires_at"`
|
||||
}
|
||||
|
||||
func readPIAPortForwardData(fileManager files.FileManager) (data piaPortForwardData, err error) {
|
||||
func readPIAPortForwardData(openFile os.OpenFileFunc) (data piaPortForwardData, err error) {
|
||||
const filepath = string(constants.PIAPortForward)
|
||||
exists, err := fileManager.FileExists(filepath)
|
||||
if err != nil {
|
||||
return data, err
|
||||
} else if !exists {
|
||||
file, err := openFile(filepath, os.O_RDONLY, 0)
|
||||
if os.IsNotExist(err) {
|
||||
return data, nil
|
||||
} else if err != nil {
|
||||
return data, err
|
||||
}
|
||||
b, err := fileManager.ReadFile(filepath)
|
||||
|
||||
decoder := json.NewDecoder(file)
|
||||
err = decoder.Decode(&data)
|
||||
if err != nil {
|
||||
_ = file.Close()
|
||||
return data, err
|
||||
}
|
||||
if err := json.Unmarshal(b, &data); err != nil {
|
||||
return data, err
|
||||
}
|
||||
return data, nil
|
||||
return data, file.Close()
|
||||
}
|
||||
|
||||
func writePIAPortForwardData(fileManager files.FileManager, data piaPortForwardData) (err error) {
|
||||
b, err := json.Marshal(&data)
|
||||
if err != nil {
|
||||
return fmt.Errorf("cannot encode data: %w", err)
|
||||
}
|
||||
err = fileManager.WriteToFile(string(constants.PIAPortForward), b)
|
||||
func writePIAPortForwardData(openFile os.OpenFileFunc, data piaPortForwardData) (err error) {
|
||||
const filepath = string(constants.PIAPortForward)
|
||||
file, err := openFile(filepath,
|
||||
os.O_CREATE|os.O_TRUNC|os.O_WRONLY,
|
||||
0644)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
encoder := json.NewEncoder(file)
|
||||
err = encoder.Encode(data)
|
||||
if err != nil {
|
||||
_ = file.Close()
|
||||
return err
|
||||
}
|
||||
return file.Close()
|
||||
}
|
||||
|
||||
func unpackPIAPayload(payload string) (port uint16, token string, expiration time.Time, err error) {
|
||||
@@ -449,8 +448,9 @@ func packPIAPayload(port uint16, token string, expiration time.Time) (payload st
|
||||
return payload, nil
|
||||
}
|
||||
|
||||
func fetchPIAToken(ctx context.Context, fileManager files.FileManager, client *http.Client) (token string, err error) {
|
||||
username, password, err := getOpenvpnCredentials(fileManager)
|
||||
func fetchPIAToken(ctx context.Context, openFile os.OpenFileFunc,
|
||||
client *http.Client) (token string, err error) {
|
||||
username, password, err := getOpenvpnCredentials(openFile)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("cannot get Openvpn credentials: %w", err)
|
||||
}
|
||||
@@ -469,19 +469,18 @@ func fetchPIAToken(ctx context.Context, fileManager files.FileManager, client *h
|
||||
return "", err
|
||||
}
|
||||
defer response.Body.Close()
|
||||
b, err := ioutil.ReadAll(response.Body)
|
||||
if response.StatusCode != http.StatusOK {
|
||||
b, _ := ioutil.ReadAll(response.Body)
|
||||
shortenMessage := string(b)
|
||||
shortenMessage = strings.ReplaceAll(shortenMessage, "\n", "")
|
||||
shortenMessage = strings.ReplaceAll(shortenMessage, " ", " ")
|
||||
return "", fmt.Errorf("%s: response received: %q", response.Status, shortenMessage)
|
||||
} else if err != nil {
|
||||
return "", err
|
||||
}
|
||||
decoder := json.NewDecoder(response.Body)
|
||||
var result struct {
|
||||
Token string `json:"token"`
|
||||
}
|
||||
if err := json.Unmarshal(b, &result); err != nil {
|
||||
if err := decoder.Decode(&result); err != nil {
|
||||
return "", err
|
||||
} else if len(result.Token) == 0 {
|
||||
return "", fmt.Errorf("token is empty")
|
||||
@@ -489,10 +488,19 @@ func fetchPIAToken(ctx context.Context, fileManager files.FileManager, client *h
|
||||
return result.Token, nil
|
||||
}
|
||||
|
||||
func getOpenvpnCredentials(fileManager files.FileManager) (username, password string, err error) {
|
||||
authData, err := fileManager.ReadFile(string(constants.OpenVPNAuthConf))
|
||||
func getOpenvpnCredentials(openFile os.OpenFileFunc) (username, password string, err error) {
|
||||
const filepath = string(constants.OpenVPNAuthConf)
|
||||
file, err := openFile(filepath, os.O_RDONLY, 0)
|
||||
if err != nil {
|
||||
return "", "", fmt.Errorf("cannot read openvpn auth file: %w", err)
|
||||
return "", "", fmt.Errorf("cannot read openvpn auth file: %s", err)
|
||||
}
|
||||
authData, err := ioutil.ReadAll(file)
|
||||
if err != nil {
|
||||
_ = file.Close()
|
||||
return "", "", fmt.Errorf("cannot read openvpn auth file: %s", err)
|
||||
}
|
||||
if err := file.Close(); err != nil {
|
||||
return "", "", err
|
||||
}
|
||||
lines := strings.Split(string(authData), "\n")
|
||||
const minLines = 2
|
||||
@@ -525,16 +533,13 @@ func fetchPIAPortForwardData(ctx context.Context, client *http.Client, gateway n
|
||||
if response.StatusCode != http.StatusOK {
|
||||
return 0, "", expiration, fmt.Errorf("cannot obtain signature: %s", response.Status)
|
||||
}
|
||||
b, err := ioutil.ReadAll(response.Body)
|
||||
if err != nil {
|
||||
return 0, "", expiration, fmt.Errorf("cannot obtain signature: %w", err)
|
||||
}
|
||||
decoder := json.NewDecoder(response.Body)
|
||||
var data struct {
|
||||
Status string `json:"status"`
|
||||
Payload string `json:"payload"`
|
||||
Signature string `json:"signature"`
|
||||
}
|
||||
if err := json.Unmarshal(b, &data); err != nil {
|
||||
if err := decoder.Decode(&data); err != nil {
|
||||
return 0, "", expiration, fmt.Errorf("cannot decode received data: %w", err)
|
||||
} else if data.Status != "OK" {
|
||||
return 0, "", expiration, fmt.Errorf("response received from PIA has status %s", data.Status)
|
||||
@@ -571,18 +576,30 @@ func bindPIAPort(ctx context.Context, client *http.Client, gateway net.IP, data
|
||||
if response.StatusCode != http.StatusOK {
|
||||
return fmt.Errorf("cannot bind port: %s", response.Status)
|
||||
}
|
||||
b, err := ioutil.ReadAll(response.Body)
|
||||
if err != nil {
|
||||
return fmt.Errorf("cannot bind port: %w", err)
|
||||
}
|
||||
|
||||
decoder := json.NewDecoder(response.Body)
|
||||
var responseData struct {
|
||||
Status string `json:"status"`
|
||||
Message string `json:"message"`
|
||||
}
|
||||
if err := json.Unmarshal(b, &responseData); err != nil {
|
||||
if err := decoder.Decode(&responseData); err != nil {
|
||||
return fmt.Errorf("cannot bind port: %w", err)
|
||||
} else if responseData.Status != "OK" {
|
||||
return fmt.Errorf("response received from PIA: %s (%s)", responseData.Status, responseData.Message)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func writePortForwardedToFile(openFile os.OpenFileFunc,
|
||||
filepath string, port uint16) (err error) {
|
||||
file, err := openFile(filepath, os.O_CREATE|os.O_TRUNC|os.O_WRONLY, 0644)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
_, err = file.Write([]byte(fmt.Sprintf("%d", port)))
|
||||
if err != nil {
|
||||
_ = file.Close()
|
||||
return err
|
||||
}
|
||||
return file.Close()
|
||||
}
|
||||
|
||||
@@ -10,8 +10,8 @@ import (
|
||||
"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"
|
||||
"github.com/qdm12/golibs/os"
|
||||
)
|
||||
|
||||
type privado struct {
|
||||
@@ -64,13 +64,13 @@ func (s *privado) GetOpenVPNConnection(selection models.ServerSelection) (
|
||||
Protocol: selection.Protocol,
|
||||
Hostname: servers[i].Hostname,
|
||||
}
|
||||
connections = append(connections, connection)
|
||||
connections[i] = connection
|
||||
}
|
||||
|
||||
return pickRandomConnection(connections, s.randSource), nil
|
||||
}
|
||||
|
||||
func (s *privado) BuildConf(connection models.OpenVPNConnection, verbosity, uid, gid int, root bool,
|
||||
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
|
||||
@@ -104,7 +104,7 @@ func (s *privado) 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>",
|
||||
@@ -117,7 +117,7 @@ func (s *privado) BuildConf(connection models.OpenVPNConnection, verbosity, uid,
|
||||
}
|
||||
|
||||
func (s *privado) PortForward(ctx context.Context, client *http.Client,
|
||||
fileManager files.FileManager, pfLogger logging.Logger, gateway net.IP, fw firewall.Configurator,
|
||||
openFile os.OpenFileFunc, pfLogger logging.Logger, gateway net.IP, fw firewall.Configurator,
|
||||
syncState func(port uint16) (pfFilepath models.Filepath)) {
|
||||
panic("port forwarding is not supported for privado")
|
||||
}
|
||||
|
||||
@@ -8,17 +8,17 @@ import (
|
||||
"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"
|
||||
"github.com/qdm12/golibs/os"
|
||||
)
|
||||
|
||||
// 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,
|
||||
openFile os.OpenFileFunc, pfLogger logging.Logger, gateway net.IP, fw firewall.Configurator,
|
||||
syncState func(port uint16) (pfFilepath models.Filepath))
|
||||
}
|
||||
|
||||
|
||||
@@ -10,8 +10,8 @@ import (
|
||||
"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"
|
||||
"github.com/qdm12/golibs/os"
|
||||
)
|
||||
|
||||
type purevpn struct {
|
||||
@@ -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
|
||||
@@ -103,12 +103,12 @@ func (p *purevpn) BuildConf(connection models.OpenVPNConnection, verbosity, uid,
|
||||
// Modified variables
|
||||
fmt.Sprintf("verb %d", verbosity),
|
||||
fmt.Sprintf("auth-user-pass %s", constants.OpenVPNAuthConf),
|
||||
fmt.Sprintf("proto %s", string(connection.Protocol)),
|
||||
fmt.Sprintf("proto %s", connection.Protocol),
|
||||
fmt.Sprintf("remote %s %d", connection.IP.String(), connection.Port),
|
||||
fmt.Sprintf("cipher %s", cipher),
|
||||
}
|
||||
if !root {
|
||||
lines = append(lines, "user nonrootuser")
|
||||
lines = append(lines, "user "+username)
|
||||
}
|
||||
lines = append(lines, []string{
|
||||
"<ca>",
|
||||
@@ -150,7 +150,7 @@ func (p *purevpn) BuildConf(connection models.OpenVPNConnection, verbosity, uid,
|
||||
}
|
||||
|
||||
func (p *purevpn) PortForward(ctx context.Context, client *http.Client,
|
||||
fileManager files.FileManager, pfLogger logging.Logger, gateway net.IP, fw firewall.Configurator,
|
||||
openFile os.OpenFileFunc, pfLogger logging.Logger, gateway net.IP, fw firewall.Configurator,
|
||||
syncState func(port uint16) (pfFilepath models.Filepath)) {
|
||||
panic("port forwarding is not supported for purevpn")
|
||||
}
|
||||
|
||||
@@ -10,8 +10,8 @@ import (
|
||||
"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"
|
||||
"github.com/qdm12/golibs/os"
|
||||
)
|
||||
|
||||
type surfshark struct {
|
||||
@@ -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>",
|
||||
@@ -137,7 +138,7 @@ func (s *surfshark) BuildConf(connection models.OpenVPNConnection, verbosity, ui
|
||||
}
|
||||
|
||||
func (s *surfshark) PortForward(ctx context.Context, client *http.Client,
|
||||
fileManager files.FileManager, pfLogger logging.Logger, gateway net.IP, fw firewall.Configurator,
|
||||
openFile os.OpenFileFunc, pfLogger logging.Logger, gateway net.IP, fw firewall.Configurator,
|
||||
syncState func(port uint16) (pfFilepath models.Filepath)) {
|
||||
panic("port forwarding is not supported for surfshark")
|
||||
}
|
||||
|
||||
@@ -10,8 +10,8 @@ import (
|
||||
"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"
|
||||
"github.com/qdm12/golibs/os"
|
||||
)
|
||||
|
||||
type vyprvpn struct {
|
||||
@@ -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>",
|
||||
@@ -119,7 +119,7 @@ func (v *vyprvpn) BuildConf(connection models.OpenVPNConnection, verbosity, uid,
|
||||
}
|
||||
|
||||
func (v *vyprvpn) PortForward(ctx context.Context, client *http.Client,
|
||||
fileManager files.FileManager, pfLogger logging.Logger, gateway net.IP, fw firewall.Configurator,
|
||||
openFile os.OpenFileFunc, pfLogger logging.Logger, gateway net.IP, fw firewall.Configurator,
|
||||
syncState func(port uint16) (pfFilepath models.Filepath)) {
|
||||
panic("port forwarding is not supported for vyprvpn")
|
||||
}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user