Compare commits
103 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
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 | ||
|
|
84944a87d3 | ||
|
|
fb62910b17 |
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": [
|
"dockerComposeFile": [
|
||||||
"docker-compose.yml"
|
"docker-compose.yml"
|
||||||
],
|
],
|
||||||
@@ -12,27 +12,25 @@
|
|||||||
"workspaceFolder": "/workspace",
|
"workspaceFolder": "/workspace",
|
||||||
"extensions": [
|
"extensions": [
|
||||||
"golang.go",
|
"golang.go",
|
||||||
"IBM.output-colorizer",
|
"eamodio.gitlens", // IDE Git information
|
||||||
"eamodio.gitlens",
|
|
||||||
"mhutchie.git-graph",
|
|
||||||
"davidanson.vscode-markdownlint",
|
"davidanson.vscode-markdownlint",
|
||||||
"shardulm94.trailing-spaces",
|
"ms-azuretools.vscode-docker", // Docker integration and linting
|
||||||
"alefragnani.Bookmarks",
|
"shardulm94.trailing-spaces", // Show trailing spaces
|
||||||
"Gruntfuggly.todo-tree",
|
"Gruntfuggly.todo-tree", // Highlights TODO comments
|
||||||
"mohsen1.prettify-json",
|
"bierner.emojisense", // Emoji sense for markdown
|
||||||
"quicktype.quicktype",
|
"stkb.rewrap", // rewrap comments after n characters on one line
|
||||||
"spikespaz.vscode-smoothtype",
|
"vscode-icons-team.vscode-icons", // Better file extension icons
|
||||||
"stkb.rewrap",
|
"github.vscode-pull-request-github", // Github interaction
|
||||||
"vscode-icons-team.vscode-icons"
|
"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": {
|
"settings": {
|
||||||
// General settings
|
|
||||||
"files.eol": "\n",
|
"files.eol": "\n",
|
||||||
// Docker
|
|
||||||
"remote.extensionKind": {
|
"remote.extensionKind": {
|
||||||
"ms-azuretools.vscode-docker": "workspace"
|
"ms-azuretools.vscode-docker": "workspace"
|
||||||
},
|
},
|
||||||
// Golang general settings
|
|
||||||
"go.useLanguageServer": true,
|
"go.useLanguageServer": true,
|
||||||
"go.autocompleteUnimportedPackages": true,
|
"go.autocompleteUnimportedPackages": true,
|
||||||
"go.gotoSymbol.includeImports": true,
|
"go.gotoSymbol.includeImports": true,
|
||||||
@@ -43,7 +41,6 @@
|
|||||||
"usePlaceholders": false
|
"usePlaceholders": false
|
||||||
},
|
},
|
||||||
"go.lintTool": "golangci-lint",
|
"go.lintTool": "golangci-lint",
|
||||||
// Golang on save
|
|
||||||
"go.buildOnSave": "workspace",
|
"go.buildOnSave": "workspace",
|
||||||
"go.lintOnSave": "workspace",
|
"go.lintOnSave": "workspace",
|
||||||
"go.vetOnSave": "workspace",
|
"go.vetOnSave": "workspace",
|
||||||
@@ -53,20 +50,21 @@
|
|||||||
"source.organizeImports": true
|
"source.organizeImports": true
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
// Golang testing
|
|
||||||
"go.toolsEnvVars": {
|
"go.toolsEnvVars": {
|
||||||
"GOFLAGS": "-tags=integration"
|
"GOFLAGS": "-tags=",
|
||||||
|
// "CGO_ENABLED": 1 // for the race detector
|
||||||
},
|
},
|
||||||
"gopls.env": {
|
"gopls.env": {
|
||||||
"GOFLAGS": "-tags=integration"
|
"GOFLAGS": "-tags="
|
||||||
},
|
},
|
||||||
"go.testEnvVars": {},
|
"go.testEnvVars": {},
|
||||||
"go.testFlags": [
|
"go.testFlags": [
|
||||||
"-v",
|
"-v",
|
||||||
// "-race"
|
// "-race"
|
||||||
],
|
],
|
||||||
"go.testTimeout": "600s",
|
"go.testTimeout": "10s",
|
||||||
|
"go.coverOnSingleTest": true,
|
||||||
"go.coverOnSingleTestFile": true,
|
"go.coverOnSingleTestFile": true,
|
||||||
"go.coverOnSingleTest": true
|
"go.coverOnTestPackage": true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -2,14 +2,24 @@ version: "3.7"
|
|||||||
|
|
||||||
services:
|
services:
|
||||||
vscode:
|
vscode:
|
||||||
image: qmcgaw/godevcontainer
|
build: .
|
||||||
|
image: godevcontainer
|
||||||
volumes:
|
volumes:
|
||||||
- ../:/workspace
|
- ../:/workspace
|
||||||
|
# Docker socket to access Docker server
|
||||||
|
- /var/run/docker.sock:/var/run/docker.sock
|
||||||
|
# SSH directory
|
||||||
- ~/.ssh:/home/vscode/.ssh
|
- ~/.ssh:/home/vscode/.ssh
|
||||||
- ~/.ssh:/root/.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:
|
cap_add:
|
||||||
|
# For debugging with dlv
|
||||||
- SYS_PTRACE
|
- SYS_PTRACE
|
||||||
security_opt:
|
security_opt:
|
||||||
|
# For debugging with dlv
|
||||||
- seccomp:unconfined
|
- seccomp:unconfined
|
||||||
entrypoint: zsh -c "while sleep 1000; do :; done"
|
entrypoint: zsh -c "while sleep 1000; do :; done"
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
.devcontainer
|
.devcontainer
|
||||||
.git
|
.git
|
||||||
.github
|
.github
|
||||||
.vscode
|
|
||||||
cmd
|
cmd
|
||||||
!cmd/gluetun
|
!cmd/gluetun
|
||||||
doc
|
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. [Fork](https://github.com/qdm12/gluetun/fork) and clone the repository
|
||||||
1. Create a new branch `git checkout -b my-branch-name`
|
1. Create a new branch `git checkout -b my-branch-name`
|
||||||
1. Modify the code
|
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. Commit your modifications
|
||||||
1. Push to your fork and [submit a pull request](https://github.com/qdm12/gluetun/compare)
|
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
|
name: Bug
|
||||||
about: Report a bug
|
about: Report a bug
|
||||||
title: 'Bug: ...'
|
title: 'Bug: FILL THIS TEXT!'
|
||||||
labels: ":bug: bug"
|
labels: ":bug: bug"
|
||||||
assignees: qdm12
|
assignees: qdm12
|
||||||
|
|
||||||
|
|||||||
2
.github/ISSUE_TEMPLATE/help.md
vendored
2
.github/ISSUE_TEMPLATE/help.md
vendored
@@ -1,7 +1,7 @@
|
|||||||
---
|
---
|
||||||
name: Help
|
name: Help
|
||||||
about: Ask for help
|
about: Ask for help
|
||||||
title: 'Help: ...'
|
title: 'Help: FILL THIS TEXT!'
|
||||||
labels: ":pray: help wanted"
|
labels: ":pray: help wanted"
|
||||||
assignees:
|
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
|
- name: Checkout
|
||||||
uses: actions/checkout@v2
|
uses: actions/checkout@v2
|
||||||
- name: Docker Hub Description
|
- name: Docker Hub Description
|
||||||
uses: peter-evans/dockerhub-description@v2.1.0
|
uses: peter-evans/dockerhub-description@v2.4.1
|
||||||
env:
|
with:
|
||||||
DOCKERHUB_USERNAME: qmcgaw
|
username: qmcgaw
|
||||||
DOCKERHUB_PASSWORD: ${{ secrets.DOCKERHUB_PASSWORD }}
|
password: ${{ secrets.DOCKERHUB_PASSWORD }}
|
||||||
DOCKERHUB_REPOSITORY: qmcgaw/private-internet-access
|
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
|
name: labels
|
||||||
on:
|
on:
|
||||||
push:
|
push:
|
||||||
branches: ["master"]
|
branches: [master]
|
||||||
paths:
|
paths:
|
||||||
- '.github/labels.yml'
|
- .github/labels.yml
|
||||||
- '.github/workflows/labels.yml'
|
- .github/workflows/labels.yml
|
||||||
jobs:
|
jobs:
|
||||||
labeler:
|
labeler:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
@@ -13,6 +13,6 @@ jobs:
|
|||||||
uses: actions/checkout@v2
|
uses: actions/checkout@v2
|
||||||
- name: Labeler
|
- name: Labeler
|
||||||
if: success()
|
if: success()
|
||||||
uses: crazy-max/ghaction-github-labeler@v1
|
uses: crazy-max/ghaction-github-labeler@v3.1.1
|
||||||
env:
|
env:
|
||||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
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:
|
linters:
|
||||||
- dupl
|
- dupl
|
||||||
- maligned
|
- maligned
|
||||||
|
- path: internal/unix/constants\.go
|
||||||
|
linters:
|
||||||
|
- golint
|
||||||
|
text: don't use ALL_CAPS in Go names; use CamelCase
|
||||||
linters:
|
linters:
|
||||||
disable-all: true
|
disable-all: true
|
||||||
enable:
|
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
|
|
||||||
}
|
|
||||||
60
Dockerfile
60
Dockerfile
@@ -1,27 +1,42 @@
|
|||||||
ARG ALPINE_VERSION=3.12
|
ARG ALPINE_VERSION=3.12
|
||||||
ARG GO_VERSION=1.15
|
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
|
RUN apk --update add git
|
||||||
ENV CGO_ENABLED=0
|
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
|
WORKDIR /tmp/gobuild
|
||||||
COPY .golangci.yml .
|
|
||||||
COPY go.mod go.sum ./
|
COPY go.mod go.sum ./
|
||||||
RUN go mod download
|
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 VERSION=unknown
|
||||||
ARG BUILD_DATE="an unknown date"
|
ARG BUILD_DATE="an unknown date"
|
||||||
ARG COMMIT=unknown
|
ARG COMMIT=unknown
|
||||||
COPY cmd/gluetun/main.go .
|
COPY cmd/ ./cmd/
|
||||||
COPY internal/ ./internal/
|
COPY internal/ ./internal/
|
||||||
RUN go test ./...
|
RUN GOARCH="$(echo ${TARGETPLATFORM} | xcputranslate -field arch)" \
|
||||||
RUN golangci-lint run --timeout=10m
|
GOARM="$(echo ${TARGETPLATFORM} | xcputranslate -field arm)" \
|
||||||
RUN go build -trimpath -ldflags="-s -w \
|
go build -trimpath -ldflags="-s -w \
|
||||||
-X 'main.version=$VERSION' \
|
-X 'main.version=$VERSION' \
|
||||||
-X 'main.buildDate=$BUILD_DATE' \
|
-X 'main.buildDate=$BUILD_DATE' \
|
||||||
-X 'main.commit=$COMMIT' \
|
-X 'main.commit=$COMMIT' \
|
||||||
" -o entrypoint main.go
|
" -o entrypoint cmd/gluetun/main.go
|
||||||
|
|
||||||
FROM alpine:${ALPINE_VERSION}
|
FROM alpine:${ALPINE_VERSION}
|
||||||
ARG VERSION=unknown
|
ARG VERSION=unknown
|
||||||
@@ -35,8 +50,8 @@ LABEL \
|
|||||||
org.opencontainers.image.url="https://github.com/qdm12/gluetun" \
|
org.opencontainers.image.url="https://github.com/qdm12/gluetun" \
|
||||||
org.opencontainers.image.documentation="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.source="https://github.com/qdm12/gluetun" \
|
||||||
org.opencontainers.image.title="VPN client for PIA, Mullvad, Windscribe, Surfshark and Cyberghost" \
|
org.opencontainers.image.title="VPN swiss-knife like client for multiple VPN providers" \
|
||||||
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.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 \
|
ENV VPNSP=pia \
|
||||||
VERSION_INFORMATION=on \
|
VERSION_INFORMATION=on \
|
||||||
PROTOCOL=udp \
|
PROTOCOL=udp \
|
||||||
@@ -45,12 +60,14 @@ ENV VPNSP=pia \
|
|||||||
OPENVPN_TARGET_IP= \
|
OPENVPN_TARGET_IP= \
|
||||||
OPENVPN_IPV6=off \
|
OPENVPN_IPV6=off \
|
||||||
TZ= \
|
TZ= \
|
||||||
UID=1000 \
|
PUID= \
|
||||||
GID=1000 \
|
PGID= \
|
||||||
IP_STATUS_FILE="/tmp/gluetun/ip" \
|
PUBLICIP_FILE="/tmp/gluetun/ip" \
|
||||||
# PIA, Windscribe, Surfshark, Cyberghost, Vyprvpn, NordVPN, PureVPN only
|
# PIA, Windscribe, Surfshark, Cyberghost, Vyprvpn, NordVPN, PureVPN only
|
||||||
USER= \
|
OPENVPN_USER= \
|
||||||
PASSWORD= \
|
OPENVPN_PASSWORD= \
|
||||||
|
USER_SECRETFILE=/run/secrets/openvpn_user \
|
||||||
|
PASSWORD_SECRETFILE=/run/secrets/openvpn_password \
|
||||||
REGION= \
|
REGION= \
|
||||||
# PIA only
|
# PIA only
|
||||||
PIA_ENCRYPTION=strong \
|
PIA_ENCRYPTION=strong \
|
||||||
@@ -61,7 +78,7 @@ ENV VPNSP=pia \
|
|||||||
# Mullvad, PureVPN, Windscribe only
|
# Mullvad, PureVPN, Windscribe only
|
||||||
CITY= \
|
CITY= \
|
||||||
# Windscribe only
|
# Windscribe only
|
||||||
HOSTNAME= \
|
SERVER_HOSTNAME= \
|
||||||
# Mullvad only
|
# Mullvad only
|
||||||
ISP= \
|
ISP= \
|
||||||
OWNED=no \
|
OWNED=no \
|
||||||
@@ -69,6 +86,8 @@ ENV VPNSP=pia \
|
|||||||
PORT= \
|
PORT= \
|
||||||
# Cyberghost only
|
# Cyberghost only
|
||||||
CYBERGHOST_GROUP="Premium UDP Europe" \
|
CYBERGHOST_GROUP="Premium UDP Europe" \
|
||||||
|
OPENVPN_CLIENTCRT_SECRETFILE=/run/secrets/openvpn_clientcrt \
|
||||||
|
OPENVPN_CLIENTKEY_SECRETFILE=/run/secrets/openvpn_clientkey \
|
||||||
# NordVPN only
|
# NordVPN only
|
||||||
SERVER_NUMBER= \
|
SERVER_NUMBER= \
|
||||||
# Openvpn
|
# Openvpn
|
||||||
@@ -102,16 +121,19 @@ ENV VPNSP=pia \
|
|||||||
HTTPPROXY_PORT=8888 \
|
HTTPPROXY_PORT=8888 \
|
||||||
HTTPPROXY_USER= \
|
HTTPPROXY_USER= \
|
||||||
HTTPPROXY_PASSWORD= \
|
HTTPPROXY_PASSWORD= \
|
||||||
|
HTTPPROXY_USER_SECRETFILE=/run/secrets/httpproxy_user \
|
||||||
|
HTTPPROXY_PASSWORD_SECRETFILE=/run/secrets/httpproxy_password \
|
||||||
# Shadowsocks
|
# Shadowsocks
|
||||||
SHADOWSOCKS=off \
|
SHADOWSOCKS=off \
|
||||||
SHADOWSOCKS_LOG=off \
|
SHADOWSOCKS_LOG=off \
|
||||||
SHADOWSOCKS_PORT=8388 \
|
SHADOWSOCKS_PORT=8388 \
|
||||||
SHADOWSOCKS_PASSWORD= \
|
SHADOWSOCKS_PASSWORD= \
|
||||||
|
SHADOWSOCKS_PASSWORD_SECRETFILE=/run/secrets/shadowsocks_password \
|
||||||
SHADOWSOCKS_METHOD=chacha20-ietf-poly1305 \
|
SHADOWSOCKS_METHOD=chacha20-ietf-poly1305 \
|
||||||
UPDATER_PERIOD=0
|
UPDATER_PERIOD=0
|
||||||
ENTRYPOINT ["/entrypoint"]
|
ENTRYPOINT ["/entrypoint"]
|
||||||
EXPOSE 8000/tcp 8888/tcp 8388/tcp 8388/udp
|
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 && \
|
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-* && \
|
rm -rf /var/cache/apk/* /etc/unbound/* /usr/sbin/unbound-* && \
|
||||||
deluser openvpn && \
|
deluser openvpn && \
|
||||||
@@ -119,4 +141,4 @@ RUN apk add -q --progress --no-cache --update openvpn ca-certificates iptables i
|
|||||||
mkdir /gluetun
|
mkdir /gluetun
|
||||||
# TODO remove once SAN is added to PIA servers certificates, see https://github.com/pia-foss/manual-connections/issues/10
|
# TODO remove once SAN is added to PIA servers certificates, see https://github.com/pia-foss/manual-connections/issues/10
|
||||||
ENV GODEBUG=x509ignoreCN=0
|
ENV GODEBUG=x509ignoreCN=0
|
||||||
COPY --from=builder /tmp/gobuild/entrypoint /entrypoint
|
COPY --from=build /tmp/gobuild/entrypoint /entrypoint
|
||||||
|
|||||||
403
README.md
403
README.md
@@ -3,27 +3,36 @@
|
|||||||
*Lightweight swiss-knife-like VPN client to tunnel to Private Internet Access,
|
*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*
|
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">
|
<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/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://github.com/qdm12/gluetun/commits)
|
||||||
[](https://join.slack.com/t/qdm12/shared_invite/enQtOTE0NjcxNTM1ODc5LTYyZmVlOTM3MGI4ZWU0YmJkMjUxNmQ4ODQ2OTAwYzMxMTlhY2Q1MWQyOWUyNjc2ODliNjFjMDUxNWNmNzk5MDk)
|
|
||||||
|
|
||||||
## Videos
|
[](https://github.com/qdm12/gluetun)
|
||||||
|
|
||||||
1. [**Introduction**](https://youtu.be/3jIbU6J2Hs0)
|
## Quick links
|
||||||
1. [**Connect a container**](https://youtu.be/mH7J_2JKNK0)
|
|
||||||
1. [**Connect LAN devices**](https://youtu.be/qvjrM15Y0uk)
|
- 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
|
## 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 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 Shadowsocks proxy (protocol based on SOCKS5 with an encryption layer, tunnels TCP+UDP)
|
||||||
- Built in HTTP proxy (tunnels HTTP and HTTPS through TCP)
|
- Built in HTTP proxy (tunnels HTTP and HTTPS through TCP)
|
||||||
- [Connect other containers to it](https://github.com/qdm12/gluetun#connect-to-it)
|
- [Connect other containers to it](https://github.com/qdm12/gluetun/wiki/Connect-to-gluetun)
|
||||||
- [Connect LAN devices to it](https://github.com/qdm12/gluetun#connect-to-it)
|
- [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 🎆
|
- 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
|
- VPN server side port forwarding for Private Internet Access and Vyprvpn
|
||||||
- Possibility of split horizon DNS by selecting multiple DNS over TLS providers
|
- Possibility of split horizon DNS by selecting multiple DNS over TLS providers
|
||||||
- Subprograms all drop root privileges once launched
|
- Subprograms all drop root privileges once launched
|
||||||
@@ -54,352 +63,70 @@ Mullvad, Windscribe, Surfshark Cyberghost, VyprVPN, NordVPN, PureVPN and Privado
|
|||||||
```bash
|
```bash
|
||||||
docker run -d --name gluetun --cap-add=NET_ADMIN \
|
docker run -d --name gluetun --cap-add=NET_ADMIN \
|
||||||
-e VPNSP="private internet access" -e REGION="CA Montreal" \
|
-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 \
|
-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:
|
or use [docker-compose.yml](https://github.com/qdm12/gluetun/blob/master/docker-compose.yml) with:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
|
echo "your openvpn username" > openvpn_user
|
||||||
|
echo "your openvpn password" > openvpn_password
|
||||||
docker-compose up -d
|
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
|
## Further setup
|
||||||
- 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
|
|
||||||
|
|
||||||
**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
|
- 💻 [Contribute with code](https://github.com/qdm12/gluetun/wiki/Development) ([existing contributors 👍](https://github.com/qdm12/gluetun/blob/master/.github/CONTRIBUTING.md#Contributors))
|
||||||
|
|
||||||
```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
|
|
||||||
- [List of issues and feature requests](https://github.com/qdm12/gluetun/issues)
|
- [List of issues and feature requests](https://github.com/qdm12/gluetun/issues)
|
||||||
|
- [Kanban board](https://github.com/qdm12/gluetun/projects/1)
|
||||||
|
|
||||||
## License
|
## License
|
||||||
|
|
||||||
This repository is under an [MIT license](https://github.com/qdm12/gluetun/master/license)
|
[](https://github.com/qdm12/gluetun/master/LICENSE)
|
||||||
|
|
||||||
## Support
|
## 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://github.com/sponsors/qdm12)
|
||||||
[](https://www.paypal.me/qmcgaw)
|
[](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"
|
"fmt"
|
||||||
"net"
|
"net"
|
||||||
"net/http"
|
"net/http"
|
||||||
"os"
|
nativeos "os"
|
||||||
"os/signal"
|
"os/signal"
|
||||||
"strings"
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
"syscall"
|
"syscall"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/qdm12/dns/pkg/unbound"
|
||||||
"github.com/qdm12/gluetun/internal/alpine"
|
"github.com/qdm12/gluetun/internal/alpine"
|
||||||
"github.com/qdm12/gluetun/internal/cli"
|
"github.com/qdm12/gluetun/internal/cli"
|
||||||
"github.com/qdm12/gluetun/internal/constants"
|
"github.com/qdm12/gluetun/internal/constants"
|
||||||
@@ -29,68 +30,120 @@ import (
|
|||||||
"github.com/qdm12/gluetun/internal/settings"
|
"github.com/qdm12/gluetun/internal/settings"
|
||||||
"github.com/qdm12/gluetun/internal/shadowsocks"
|
"github.com/qdm12/gluetun/internal/shadowsocks"
|
||||||
"github.com/qdm12/gluetun/internal/storage"
|
"github.com/qdm12/gluetun/internal/storage"
|
||||||
|
"github.com/qdm12/gluetun/internal/unix"
|
||||||
"github.com/qdm12/gluetun/internal/updater"
|
"github.com/qdm12/gluetun/internal/updater"
|
||||||
versionpkg "github.com/qdm12/gluetun/internal/version"
|
versionpkg "github.com/qdm12/gluetun/internal/version"
|
||||||
"github.com/qdm12/golibs/command"
|
"github.com/qdm12/golibs/command"
|
||||||
"github.com/qdm12/golibs/files"
|
|
||||||
"github.com/qdm12/golibs/logging"
|
"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
|
//nolint:gochecknoglobals
|
||||||
var (
|
var (
|
||||||
buildInfo models.BuildInformation
|
|
||||||
version = "unknown"
|
version = "unknown"
|
||||||
commit = "unknown"
|
commit = "unknown"
|
||||||
buildDate = "an unknown date"
|
buildDate = "an unknown date"
|
||||||
)
|
)
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
buildInfo.Version = version
|
buildInfo := models.BuildInformation{
|
||||||
buildInfo.Commit = commit
|
Version: version,
|
||||||
buildInfo.BuildDate = buildDate
|
Commit: commit,
|
||||||
ctx := context.Background()
|
BuildDate: buildDate,
|
||||||
os.Exit(_main(ctx, os.Args))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func _main(background context.Context, args []string) int { //nolint:gocognit,gocyclo
|
ctx := context.Background()
|
||||||
if len(args) > 1 { // cli operation
|
ctx, cancel := context.WithCancel(ctx)
|
||||||
var err error
|
|
||||||
switch args[1] {
|
logger, err := logging.NewLogger(logging.ConsoleEncoding, logging.InfoLevel)
|
||||||
case "healthcheck":
|
|
||||||
err = cli.HealthCheck(background)
|
|
||||||
case "clientkey":
|
|
||||||
err = cli.ClientKey(args[2:])
|
|
||||||
case "openvpnconfig":
|
|
||||||
err = cli.OpenvpnConfig()
|
|
||||||
case "update":
|
|
||||||
err = cli.Update(args[2:])
|
|
||||||
default:
|
|
||||||
err = fmt.Errorf("command %q is unknown", args[1])
|
|
||||||
}
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Println(err)
|
fmt.Println(err)
|
||||||
return 1
|
nativeos.Exit(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
args := nativeos.Args
|
||||||
|
os := os.New()
|
||||||
|
osUser := user.New()
|
||||||
|
unix := unix.New()
|
||||||
|
cli := cli.New()
|
||||||
|
|
||||||
|
errorCh := make(chan error)
|
||||||
|
go func() {
|
||||||
|
errorCh <- _main(ctx, buildInfo, args, logger, os, osUser, unix, cli)
|
||||||
|
}()
|
||||||
|
|
||||||
|
signalsCh := make(chan nativeos.Signal, 1)
|
||||||
|
signal.Notify(signalsCh,
|
||||||
|
syscall.SIGINT,
|
||||||
|
syscall.SIGTERM,
|
||||||
|
nativeos.Interrupt,
|
||||||
|
)
|
||||||
|
|
||||||
|
select {
|
||||||
|
case signal := <-signalsCh:
|
||||||
|
logger.Warn("Caught OS signal %s, shutting down", signal)
|
||||||
|
case err := <-errorCh:
|
||||||
|
close(errorCh)
|
||||||
|
if err == nil { // expected exit such as healthcheck
|
||||||
|
nativeos.Exit(0)
|
||||||
|
}
|
||||||
|
logger.Error(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
cancel()
|
||||||
|
|
||||||
|
const shutdownGracePeriod = 5 * time.Second
|
||||||
|
timer := time.NewTimer(shutdownGracePeriod)
|
||||||
|
select {
|
||||||
|
case <-errorCh:
|
||||||
|
if !timer.Stop() {
|
||||||
|
<-timer.C
|
||||||
|
}
|
||||||
|
logger.Info("Shutdown successful")
|
||||||
|
case <-timer.C:
|
||||||
|
logger.Warn("Shutdown timed out")
|
||||||
|
}
|
||||||
|
|
||||||
|
nativeos.Exit(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
//nolint:gocognit,gocyclo
|
||||||
|
func _main(background context.Context, buildInfo models.BuildInformation,
|
||||||
|
args []string, logger logging.Logger, os os.OS, osUser user.OSUser, unix unix.Unix,
|
||||||
|
cli cli.CLI) error {
|
||||||
|
if len(args) > 1 { // cli operation
|
||||||
|
switch args[1] {
|
||||||
|
case "healthcheck":
|
||||||
|
return cli.HealthCheck(background)
|
||||||
|
case "clientkey":
|
||||||
|
return cli.ClientKey(args[2:], os.OpenFile)
|
||||||
|
case "openvpnconfig":
|
||||||
|
return cli.OpenvpnConfig(os)
|
||||||
|
case "update":
|
||||||
|
return cli.Update(args[2:], os)
|
||||||
|
default:
|
||||||
|
return fmt.Errorf("command %q is unknown", args[1])
|
||||||
}
|
}
|
||||||
return 0
|
|
||||||
}
|
}
|
||||||
ctx, cancel := context.WithCancel(background)
|
ctx, cancel := context.WithCancel(background)
|
||||||
defer cancel()
|
defer cancel()
|
||||||
logger := createLogger()
|
|
||||||
|
|
||||||
const clientTimeout = 15 * time.Second
|
const clientTimeout = 15 * time.Second
|
||||||
httpClient := &http.Client{Timeout: clientTimeout}
|
httpClient := &http.Client{Timeout: clientTimeout}
|
||||||
client := network.NewClient(clientTimeout)
|
|
||||||
// Create configurators
|
// Create configurators
|
||||||
fileManager := files.NewFileManager()
|
alpineConf := alpine.NewConfigurator(os.OpenFile, osUser)
|
||||||
alpineConf := alpine.NewConfigurator(fileManager)
|
ovpnConf := openvpn.NewConfigurator(logger, os, unix)
|
||||||
ovpnConf := openvpn.NewConfigurator(logger, fileManager)
|
dnsCrypto := dnscrypto.New(httpClient, "", "")
|
||||||
dnsConf := dns.NewConfigurator(logger, client, fileManager)
|
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)
|
routingConf := routing.NewRouting(logger)
|
||||||
firewallConf := firewall.NewConfigurator(logger, routingConf, fileManager)
|
firewallConf := firewall.NewConfigurator(logger, routingConf, os.OpenFile)
|
||||||
streamMerger := command.NewStreamMerger()
|
streamMerger := command.NewStreamMerger()
|
||||||
|
|
||||||
paramsReader := params.NewReader(logger, fileManager)
|
paramsReader := params.NewReader(logger, os)
|
||||||
fmt.Println(gluetunLogging.Splash(buildInfo))
|
fmt.Println(gluetunLogging.Splash(buildInfo))
|
||||||
|
|
||||||
printVersions(ctx, logger, map[string]func(ctx context.Context) (string, error){
|
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)
|
allSettings, err := settings.GetAllSettings(paramsReader)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logger.Error(err)
|
return err
|
||||||
return 1
|
|
||||||
}
|
}
|
||||||
logger.Info(allSettings.String())
|
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
|
// TODO run this in a loop or in openvpn to reload from file without restarting
|
||||||
storage := storage.New(logger)
|
storage := storage.New(logger, os, constants.ServersData)
|
||||||
const updateServerFile = true
|
allServers, err := storage.SyncServers(constants.GetAllServers())
|
||||||
allServers, err := storage.SyncServers(constants.GetAllServers(), updateServerFile)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logger.Error(err)
|
return err
|
||||||
return 1
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Should never change
|
// 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 {
|
if err != nil {
|
||||||
logger.Error(err)
|
return err
|
||||||
return 1
|
|
||||||
}
|
}
|
||||||
err = fileManager.SetOwnership("/etc/unbound", uid, gid)
|
if nonRootUsername != defaultUsername {
|
||||||
if err != nil {
|
logger.Info("using existing username %s corresponding to user id %d", nonRootUsername, puid)
|
||||||
logger.Error(err)
|
}
|
||||||
return 1
|
|
||||||
|
if err := os.Chown("/etc/unbound", puid, pgid); err != nil {
|
||||||
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
if allSettings.Firewall.Debug {
|
if allSettings.Firewall.Debug {
|
||||||
@@ -136,27 +195,23 @@ func _main(background context.Context, args []string) int { //nolint:gocognit,go
|
|||||||
|
|
||||||
defaultInterface, defaultGateway, err := routingConf.DefaultRoute()
|
defaultInterface, defaultGateway, err := routingConf.DefaultRoute()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logger.Error(err)
|
return err
|
||||||
return 1
|
|
||||||
}
|
}
|
||||||
|
|
||||||
localSubnet, err := routingConf.LocalSubnet()
|
localSubnet, err := routingConf.LocalSubnet()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logger.Error(err)
|
return err
|
||||||
return 1
|
|
||||||
}
|
}
|
||||||
|
|
||||||
defaultIP, err := routingConf.DefaultIP()
|
defaultIP, err := routingConf.DefaultIP()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logger.Error(err)
|
return err
|
||||||
return 1
|
|
||||||
}
|
}
|
||||||
|
|
||||||
firewallConf.SetNetworkInformation(defaultInterface, defaultGateway, localSubnet, defaultIP)
|
firewallConf.SetNetworkInformation(defaultInterface, defaultGateway, localSubnet, defaultIP)
|
||||||
|
|
||||||
if err := routingConf.Setup(); err != nil {
|
if err := routingConf.Setup(); err != nil {
|
||||||
logger.Error(err)
|
return err
|
||||||
return 1
|
|
||||||
}
|
}
|
||||||
defer func() {
|
defer func() {
|
||||||
routingConf.SetVerbose(false)
|
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 {
|
if err := firewallConf.SetOutboundSubnets(ctx, allSettings.Firewall.OutboundSubnets); err != nil {
|
||||||
logger.Error(err)
|
return err
|
||||||
return 1
|
|
||||||
}
|
}
|
||||||
if err := routingConf.SetOutboundRoutes(allSettings.Firewall.OutboundSubnets); err != nil {
|
if err := routingConf.SetOutboundRoutes(allSettings.Firewall.OutboundSubnets); err != nil {
|
||||||
logger.Error(err)
|
return err
|
||||||
return 1
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := ovpnConf.CheckTUN(); err != nil {
|
if err := ovpnConf.CheckTUN(); err != nil {
|
||||||
logger.Warn(err)
|
logger.Warn(err)
|
||||||
err = ovpnConf.CreateTUN()
|
err = ovpnConf.CreateTUN()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logger.Error(err)
|
return err
|
||||||
return 1
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
tunnelReadyCh, dnsReadyCh := make(chan struct{}), make(chan struct{})
|
tunnelReadyCh := make(chan struct{})
|
||||||
signalTunnelReady := func() { tunnelReadyCh <- struct{}{} }
|
dnsReadyCh := make(chan struct{})
|
||||||
signalDNSReady := func() { dnsReadyCh <- struct{}{} }
|
|
||||||
defer close(tunnelReadyCh)
|
defer close(tunnelReadyCh)
|
||||||
defer close(dnsReadyCh)
|
defer close(dnsReadyCh)
|
||||||
|
|
||||||
if allSettings.Firewall.Enabled {
|
if allSettings.Firewall.Enabled {
|
||||||
err := firewallConf.SetEnabled(ctx, true) // disabled by default
|
err := firewallConf.SetEnabled(ctx, true) // disabled by default
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logger.Error(err)
|
return err
|
||||||
return 1
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, vpnPort := range allSettings.Firewall.VPNInputPorts {
|
for _, vpnPort := range allSettings.Firewall.VPNInputPorts {
|
||||||
err = firewallConf.SetAllowedPort(ctx, vpnPort, string(constants.TUN))
|
err = firewallConf.SetAllowedPort(ctx, vpnPort, string(constants.TUN))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logger.Error(err)
|
return err
|
||||||
return 1
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, port := range allSettings.Firewall.InputPorts {
|
for _, port := range allSettings.Firewall.InputPorts {
|
||||||
err = firewallConf.SetAllowedPort(ctx, port, defaultInterface)
|
err = firewallConf.SetAllowedPort(ctx, port, defaultInterface)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logger.Error(err)
|
return err
|
||||||
return 1
|
|
||||||
}
|
}
|
||||||
} // TODO move inside firewall?
|
} // TODO move inside firewall?
|
||||||
|
|
||||||
wg := &sync.WaitGroup{}
|
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,
|
openvpnLooper := openvpn.NewLooper(allSettings.OpenVPN, nonRootUsername, puid, pgid, allServers,
|
||||||
ovpnConf, firewallConf, routingConf, logger, httpClient, fileManager, streamMerger, cancel)
|
ovpnConf, firewallConf, routingConf, logger, httpClient, os.OpenFile, streamMerger, cancel)
|
||||||
wg.Add(1)
|
wg.Add(1)
|
||||||
// wait for restartOpenvpn
|
// wait for restartOpenvpn
|
||||||
go openvpnLooper.Run(ctx, wg)
|
go openvpnLooper.Run(ctx, wg)
|
||||||
|
|
||||||
updaterOptions := updater.NewOptions("127.0.0.1")
|
updaterLooper := updater.NewLooper(allSettings.Updater,
|
||||||
updaterLooper := updater.NewLooper(updaterOptions, allSettings.UpdaterPeriod,
|
allServers, storage, openvpnLooper.SetServers, httpClient, logger)
|
||||||
allServers, storage, openvpnLooper.SetAllServers, httpClient, logger)
|
|
||||||
wg.Add(1)
|
wg.Add(1)
|
||||||
// wait for updaterLooper.Restart() or its ticket launched with RunRestartTicker
|
// wait for updaterLooper.Restart() or its ticket launched with RunRestartTicker
|
||||||
go updaterLooper.Run(ctx, wg)
|
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)
|
wg.Add(1)
|
||||||
// wait for unboundLooper.Restart or its ticker launched with RunRestartTicker
|
// 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,
|
publicIPLooper := publicip.NewLooper(
|
||||||
allSettings.System.IPStatusFilepath, allSettings.PublicIPPeriod, uid, gid)
|
httpClient, logger, allSettings.PublicIP, puid, pgid, os)
|
||||||
wg.Add(1)
|
wg.Add(1)
|
||||||
go publicIPLooper.Run(ctx, wg)
|
go publicIPLooper.Run(ctx, wg)
|
||||||
wg.Add(1)
|
wg.Add(1)
|
||||||
go publicIPLooper.RunRestartTicker(ctx, wg)
|
go publicIPLooper.RunRestartTicker(ctx, wg)
|
||||||
publicIPLooper.SetPeriod(allSettings.PublicIPPeriod) // call after RunRestartTicker
|
|
||||||
|
|
||||||
httpProxyLooper := httpproxy.NewLooper(httpClient, logger, allSettings.HTTPProxy)
|
httpProxyLooper := httpproxy.NewLooper(logger, allSettings.HTTPProxy)
|
||||||
wg.Add(1)
|
wg.Add(1)
|
||||||
go httpProxyLooper.Run(ctx, wg)
|
go httpProxyLooper.Run(ctx, wg)
|
||||||
|
|
||||||
shadowsocksLooper := shadowsocks.NewLooper(allSettings.ShadowSocks, logger, defaultInterface)
|
shadowsocksLooper := shadowsocks.NewLooper(allSettings.ShadowSocks, logger)
|
||||||
restartShadowsocks := shadowsocksLooper.Restart
|
|
||||||
wg.Add(1)
|
wg.Add(1)
|
||||||
go shadowsocksLooper.Run(ctx, wg)
|
go shadowsocksLooper.Run(ctx, wg)
|
||||||
|
|
||||||
if allSettings.HTTPProxy.Enabled {
|
|
||||||
httpProxyLooper.Restart()
|
|
||||||
}
|
|
||||||
if allSettings.ShadowSocks.Enabled {
|
|
||||||
restartShadowsocks()
|
|
||||||
}
|
|
||||||
|
|
||||||
wg.Add(1)
|
wg.Add(1)
|
||||||
go routeReadyEvents(ctx, wg, tunnelReadyCh, dnsReadyCh,
|
go routeReadyEvents(ctx, wg, buildInfo, tunnelReadyCh, dnsReadyCh,
|
||||||
unboundLooper, updaterLooper, publicIPLooper, routingConf, logger, httpClient,
|
unboundLooper, updaterLooper, publicIPLooper, routingConf, logger, httpClient,
|
||||||
allSettings.VersionInformation, allSettings.OpenVPN.Provider.PortForwarding.Enabled, openvpnLooper.PortForward,
|
allSettings.VersionInformation, allSettings.OpenVPN.Provider.PortForwarding.Enabled, openvpnLooper.PortForward,
|
||||||
)
|
)
|
||||||
controlServerAddress := fmt.Sprintf("0.0.0.0:%d", allSettings.ControlServer.Port)
|
controlServerAddress := fmt.Sprintf("0.0.0.0:%d", allSettings.ControlServer.Port)
|
||||||
controlServerLogging := allSettings.ControlServer.Log
|
controlServerLogging := allSettings.ControlServer.Log
|
||||||
httpServer := server.New(controlServerAddress, controlServerLogging,
|
httpServer := server.New(controlServerAddress, controlServerLogging,
|
||||||
logger, buildInfo, openvpnLooper, unboundLooper, updaterLooper)
|
logger, buildInfo, openvpnLooper, unboundLooper, updaterLooper, publicIPLooper)
|
||||||
wg.Add(1)
|
wg.Add(1)
|
||||||
go httpServer.Run(ctx, wg)
|
go httpServer.Run(ctx, wg)
|
||||||
|
|
||||||
@@ -276,63 +316,22 @@ func _main(background context.Context, args []string) int { //nolint:gocognit,go
|
|||||||
wg.Add(1)
|
wg.Add(1)
|
||||||
go healthcheckServer.Run(ctx, wg)
|
go healthcheckServer.Run(ctx, wg)
|
||||||
|
|
||||||
// Start openvpn for the first time
|
// Start openvpn for the first time in a blocking call
|
||||||
openvpnLooper.Restart()
|
// 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 {
|
if allSettings.OpenVPN.Provider.PortForwarding.Enabled {
|
||||||
logger.Info("Clearing forwarded port status file %s", allSettings.OpenVPN.Provider.PortForwarding.Filepath)
|
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)
|
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 {
|
wg.Wait()
|
||||||
logger, err := logging.NewLogger(logging.ConsoleEncoding, logging.InfoLevel)
|
|
||||||
if err != nil {
|
return nil
|
||||||
panic(err)
|
|
||||||
}
|
|
||||||
return logger
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func printVersions(ctx context.Context, logger logging.Logger,
|
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, wg *sync.WaitGroup,
|
||||||
func collectStreamLines(ctx context.Context, streamMerger command.StreamMerger,
|
streamMerger command.StreamMerger,
|
||||||
logger logging.Logger, signalTunnelReady func()) {
|
logger logging.Logger, tunnelReadyCh chan<- struct{}) {
|
||||||
|
defer wg.Done()
|
||||||
// Blocking line merging paramsReader for openvpn and unbound
|
// Blocking line merging paramsReader for openvpn and unbound
|
||||||
logger.Info("Launching standard output merger")
|
logger.Info("Launching standard output merger")
|
||||||
streamMerger.CollectLines(ctx, func(line string) {
|
streamMerger.CollectLines(ctx, func(line string) {
|
||||||
@@ -372,10 +372,10 @@ func collectStreamLines(ctx context.Context, streamMerger command.StreamMerger,
|
|||||||
}
|
}
|
||||||
switch {
|
switch {
|
||||||
case strings.Contains(line, "Initialization Sequence Completed"):
|
case strings.Contains(line, "Initialization Sequence Completed"):
|
||||||
signalTunnelReady()
|
tunnelReadyCh <- struct{}{}
|
||||||
case strings.Contains(line, "TLS Error: TLS key negotiation failed to occur within 60 seconds (check your network connectivity)"):
|
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("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("2. The VPN server crashed, try changing region")
|
||||||
logger.Warn("3. Your Internet connection is not working, ensure it works")
|
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")
|
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,
|
unboundLooper dns.Looper, updaterLooper updater.Looper, publicIPLooper publicip.Looper,
|
||||||
routing routing.Routing, logger logging.Logger, httpClient *http.Client,
|
routing routing.Routing, logger logging.Logger, httpClient *http.Client,
|
||||||
versionInformation, portForwardingEnabled bool, startPortForward func(vpnGateway net.IP)) {
|
versionInformation, portForwardingEnabled bool, startPortForward func(vpnGateway net.IP)) {
|
||||||
@@ -401,7 +402,9 @@ func routeReadyEvents(ctx context.Context, wg *sync.WaitGroup, tunnelReadyCh, dn
|
|||||||
tickerWg.Wait()
|
tickerWg.Wait()
|
||||||
return
|
return
|
||||||
case <-tunnelReadyCh: // blocks until openvpn is connected
|
case <-tunnelReadyCh: // blocks until openvpn is connected
|
||||||
unboundLooper.Restart()
|
if unboundLooper.GetSettings().Enabled {
|
||||||
|
_, _ = unboundLooper.SetStatus(constants.Running)
|
||||||
|
}
|
||||||
restartTickerCancel() // stop previous restart tickers
|
restartTickerCancel() // stop previous restart tickers
|
||||||
tickerWg.Wait()
|
tickerWg.Wait()
|
||||||
restartTickerContext, restartTickerCancel = context.WithCancel(ctx)
|
restartTickerContext, restartTickerCancel = context.WithCancel(ctx)
|
||||||
@@ -424,7 +427,8 @@ func routeReadyEvents(ctx context.Context, wg *sync.WaitGroup, tunnelReadyCh, dn
|
|||||||
startPortForward(vpnGateway)
|
startPortForward(vpnGateway)
|
||||||
}
|
}
|
||||||
case <-dnsReadyCh:
|
case <-dnsReadyCh:
|
||||||
publicIPLooper.Restart() // TODO do not restart if disabled
|
// Runs the Public IP getter job once
|
||||||
|
_, _ = publicIPLooper.SetStatus(constants.Running)
|
||||||
if !versionInformation {
|
if !versionInformation {
|
||||||
break
|
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"
|
version: "3.7"
|
||||||
services:
|
services:
|
||||||
gluetun:
|
gluetun:
|
||||||
image: qmcgaw/private-internet-access
|
image: qmcgaw/gluetun
|
||||||
container_name: gluetun
|
container_name: gluetun
|
||||||
cap_add:
|
cap_add:
|
||||||
- NET_ADMIN
|
- NET_ADMIN
|
||||||
@@ -14,25 +14,18 @@ services:
|
|||||||
# command:
|
# command:
|
||||||
volumes:
|
volumes:
|
||||||
- /yourpath:/gluetun
|
- /yourpath:/gluetun
|
||||||
|
secrets:
|
||||||
|
- openvpn_user
|
||||||
|
- openvpn_password
|
||||||
environment:
|
environment:
|
||||||
# More variables are available, see the readme table
|
# More variables are available, see the readme table
|
||||||
- VPNSP=private internet access
|
- VPNSP=private internet access
|
||||||
|
|
||||||
# Timezone for accurate logs times
|
# Timezone for accurate logs times
|
||||||
- TZ=
|
- 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
|
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
|
go 1.15
|
||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/fatih/color v1.9.0
|
github.com/fatih/color v1.10.0
|
||||||
github.com/golang/mock v1.4.4
|
github.com/golang/mock v1.4.4
|
||||||
github.com/kyokomi/emoji v2.2.4+incompatible
|
github.com/kyokomi/emoji v2.2.4+incompatible
|
||||||
github.com/qdm12/golibs v0.0.0-20201025221346-fe352060c25a
|
github.com/qdm12/dns v1.4.0-rc3
|
||||||
|
github.com/qdm12/golibs v0.0.0-20210102020307-17bc97def973
|
||||||
github.com/qdm12/ss-server v0.1.0
|
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/stretchr/testify v1.6.1
|
||||||
github.com/vishvananda/netlink v1.1.0
|
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/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 h1:d+Bc7a5rLufV/sSk/8dngufqelfh6jnri85riMAaF/M=
|
||||||
github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578/go.mod h1:uGdkoq3SwY9Y+13GIhn11/XLaGBb4BfwItxLd5jeuXE=
|
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 h1:eg0MeVzsP1G42dRafH3vf+al2vQIJU0YHX+1Tw87oco=
|
||||||
github.com/asaskevich/govalidator v0.0.0-20180720115003-f9ffefc3facf/go.mod h1:lB+ZfQJz7igIIfQNfa7Ml4HSf2uFQQRzpGGRXenZAgY=
|
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.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 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
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/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 h1:8xPHl4/q1VyqGIPif1F+1V3Y3lSmrq01EabUW3CoW5s=
|
||||||
github.com/fatih/color v1.9.0/go.mod h1:eQcE1qtQxscV5RaZvpXrrb8Drkc3/DdQ+uUYCNjL+zU=
|
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 h1:D4uzjWwKYQ5XnAvUbuvHW93esHg7F8N/OYeBBcJoTr0=
|
||||||
github.com/globalsign/mgo v0.0.0-20180905125535-1ca0a4f7cbcb/go.mod h1:xkRDCp4j0OGD1HRkm4kmhM+pmpv3AKq5SU7GMg4oO/Q=
|
github.com/globalsign/mgo v0.0.0-20180905125535-1ca0a4f7cbcb/go.mod h1:xkRDCp4j0OGD1HRkm4kmhM+pmpv3AKq5SU7GMg4oO/Q=
|
||||||
github.com/go-openapi/analysis v0.0.0-20180825180245-b006789cd277/go.mod h1:k70tL6pCuVxPJOHXQ+wIac1FUrvNkHolPie/cLEU6hI=
|
github.com/go-openapi/analysis v0.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 h1:l75CXGRSwbaYNpl/Z2X1XIIAMSCquvXgpVZDhwEIJsc=
|
||||||
github.com/golang/mock v1.4.4/go.mod h1:l3mdAwkq5BuhzHwde/uurv3sEJeZMXNpwsxVWU71h+4=
|
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/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/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI=
|
||||||
github.com/google/uuid v1.0.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
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 h1:0w8skCr8aLBDKaQDg31LKKHUGF7rt7zdRpR+6cqIAlE=
|
||||||
github.com/gotify/go-api-client/v2 v2.0.4/go.mod h1:VKiah/UK20bXsr0JObE1eBVLW44zbBouzjuri9iwjFU=
|
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/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 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI=
|
||||||
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
|
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.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 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=
|
||||||
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
|
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
|
||||||
github.com/kyokomi/emoji v2.2.4+incompatible h1:np0woGKwx9LiHAQmwZx79Oc0rHpNw3o+3evou4BEPv4=
|
github.com/kyokomi/emoji v2.2.4+incompatible 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/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 h1:snbPLB8fVfU9iwbbo30TPtbLRzwWu6aJS6Xh4eaaviA=
|
||||||
github.com/mattn/go-colorable v0.1.4/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE=
|
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.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 h1:FxPOTFNqGkuDUGi3H/qkUbQO4ZiBa2brKq5r0l8TGeM=
|
||||||
github.com/mattn/go-isatty v0.0.11/go.mod h1:PhnuNfih5lzO57/f3n+odYbM4JtupLOxQOAqxQCu2WE=
|
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 h1:fmNYVwqnSfB9mZU6OS2O6GsXM+wcskZDuKQzvN1EDeE=
|
||||||
github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
|
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 h1:v+sk57XuaCKGXpWtVBX8YJzO7hMGx4Aajh4TQbdEFdc=
|
||||||
github.com/mr-tron/base58 v1.1.3/go.mod h1:BinMc/sQntlIE1frQmRFPUoPA1Zkr8VRgBdjWI2mNwc=
|
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/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 h1:P6U24L02WMfj9ymZTxl7CxS73JC99x3ukk+DBkgQGQs=
|
||||||
github.com/phayes/permbits v0.0.0-20190612203442-39d7c581d2ee/go.mod h1:3uODdxMgOaPYeWU7RzZLxVtJHZ/x1f/iHkBZuKJDzuY=
|
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 h1:iURUrRGxPUNPdy5/HRSm+Yj6okJ6UtLINN0Q9M4+h3I=
|
||||||
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
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 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
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/dns v1.4.0-rc3 h1:pbzeygQtX1ElaAYPj0Dn9XictYZgNyJc1xS7bDMyQ6Y=
|
||||||
github.com/qdm12/golibs v0.0.0-20201025221346-fe352060c25a/go.mod h1:pikkTN7g7zRuuAnERwqW1yAFq6pYmxrxpjiwGvb0Ysc=
|
github.com/qdm12/dns v1.4.0-rc3/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 h1:WV9MkHCDEWRwe4WpnYFeR/zcZAxYoTbfntLDnw9AQ50=
|
||||||
github.com/qdm12/ss-server v0.1.0/go.mod h1:ABVUkxubboL3vqBkOwDV9glX1/x7SnYrckBe5d+M/zw=
|
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 h1:f/FNXud6gA3MNr8meMVVGxhp+QBTqY91tM8HjEuMjGg=
|
||||||
github.com/riobard/go-bloom v0.0.0-20200614022211-cdc8013cb5b3/go.mod h1:HgjTstvQsPGkxUsCd2KWxErBblirPizecHcpD3ffK+s=
|
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/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.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.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
|
||||||
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
|
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
|
||||||
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
|
github.com/stretchr/testify v1.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/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 h1:OviZH7qLw/7ZovXvuNyL3XQl8UFofeikI1NW1Gypu7k=
|
||||||
github.com/vishvananda/netns v0.0.0-20191106174202-0a2b9b5464df/go.mod h1:JP3t17pCcGlemwknint6hfoeCVQrEMVwxRLRjXpq+BU=
|
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 h1:OI5t8sDa1Or+q8AeE+yKeB/SDYioSHAgcVljj9JIETY=
|
||||||
go.uber.org/atomic v1.5.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ=
|
go.uber.org/atomic v1.5.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ=
|
||||||
go.uber.org/multierr v1.3.0 h1:sFPn2GLc3poCkfrpIXGhBD2X0CMIo4Q/zSULXrj/+uc=
|
go.uber.org/multierr v1.3.0 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/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 h1:nR6NoDBgAf67s68NhaXbsojM+2gxp3S1hWkHDl27pVU=
|
||||||
go.uber.org/zap v1.13.0/go.mod h1:zwrFLgMcdUuIBviXEYEH1YKNaOBnKXsx2IPda5bBwHM=
|
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-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-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-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 h1:ikNHVSjEfnvz6sxdSPCaPt572qowuyMDMJLLm3Db3ig=
|
||||||
golang.org/x/crypto v0.0.0-20200728195943-123391ffb6de/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
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 h1:5hukYrvBGR8/eNkX5mdUezrA6JiaEZDtJb9Ei+1LlBs=
|
||||||
golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
|
golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
|
||||||
golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc=
|
golang.org/x/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-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-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-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/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-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-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-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-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-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-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20201018121011-98379d014ca7/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.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.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-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-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-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-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 h1:hKsoRgsbwY1NafxrwTs+k64bikrLBkAgPir1TNCj3Zs=
|
||||||
golang.org/x/tools v0.0.0-20191029190741-b9c20aec41a5/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
golang.org/x/tools v0.0.0-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 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY=
|
||||||
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
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/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.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||||
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo=
|
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo=
|
||||||
|
|||||||
@@ -1,25 +1,22 @@
|
|||||||
package alpine
|
package alpine
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"os/user"
|
"github.com/qdm12/golibs/os"
|
||||||
|
"github.com/qdm12/golibs/os/user"
|
||||||
"github.com/qdm12/golibs/files"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
type Configurator interface {
|
type Configurator interface {
|
||||||
CreateUser(username string, uid int) error
|
CreateUser(username string, uid int) (createdUsername string, err error)
|
||||||
}
|
}
|
||||||
|
|
||||||
type configurator struct {
|
type configurator struct {
|
||||||
fileManager files.FileManager
|
openFile os.OpenFileFunc
|
||||||
lookupUID func(uid string) (*user.User, error)
|
osUser user.OSUser
|
||||||
lookupUser func(username string) (*user.User, error)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewConfigurator(fileManager files.FileManager) Configurator {
|
func NewConfigurator(openFile os.OpenFileFunc, osUser user.OSUser) Configurator {
|
||||||
return &configurator{
|
return &configurator{
|
||||||
fileManager: fileManager,
|
openFile: openFile,
|
||||||
lookupUID: user.LookupId,
|
osUser: osUser,
|
||||||
lookupUser: user.Lookup,
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,38 +2,40 @@ package alpine
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"os"
|
||||||
"os/user"
|
"os/user"
|
||||||
)
|
)
|
||||||
|
|
||||||
// CreateUser creates a user in Alpine with the given UID.
|
// 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)
|
UIDStr := fmt.Sprintf("%d", uid)
|
||||||
u, err := c.lookupUID(UIDStr)
|
u, err := c.osUser.LookupID(UIDStr)
|
||||||
_, unknownUID := err.(user.UnknownUserIdError)
|
_, unknownUID := err.(user.UnknownUserIdError)
|
||||||
if err != nil && !unknownUID {
|
if err != nil && !unknownUID {
|
||||||
return fmt.Errorf("cannot create user: %w", err)
|
return "", fmt.Errorf("cannot create user: %w", err)
|
||||||
} else if u != nil {
|
} else if u != nil {
|
||||||
if u.Username == username {
|
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)
|
_, unknownUsername := err.(user.UnknownUserError)
|
||||||
if err != nil && !unknownUsername {
|
if err != nil && !unknownUsername {
|
||||||
return fmt.Errorf("cannot create user: %w", err)
|
return "", fmt.Errorf("cannot create user: %w", err)
|
||||||
} else if u != nil {
|
} 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)
|
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 {
|
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))...)
|
s := fmt.Sprintf("%s:x:%d:::/dev/null:/sbin/nologin\n", username, uid)
|
||||||
|
_, err = file.WriteString(s)
|
||||||
if err := c.fileManager.WriteToFile("/etc/passwd", passwd); err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("cannot create user: %w", err)
|
_ = file.Close()
|
||||||
|
return "", err
|
||||||
}
|
}
|
||||||
return nil
|
return username, file.Close()
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,131 +2,19 @@ package cli
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"flag"
|
|
||||||
"fmt"
|
|
||||||
"net/http"
|
|
||||||
"strings"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/qdm12/gluetun/internal/constants"
|
"github.com/qdm12/golibs/os"
|
||||||
"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"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func ClientKey(args []string) error {
|
type CLI interface {
|
||||||
flagSet := flag.NewFlagSet("clientkey", flag.ExitOnError)
|
ClientKey(args []string, openFile os.OpenFileFunc) error
|
||||||
filepath := flagSet.String("path", string(constants.ClientKey), "file path to the client.key file")
|
HealthCheck(ctx context.Context) error
|
||||||
if err := flagSet.Parse(args); err != nil {
|
OpenvpnConfig(os os.OS) error
|
||||||
return err
|
Update(args []string, os os.OS) error
|
||||||
}
|
|
||||||
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
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func HealthCheck(ctx context.Context) error {
|
type cli struct{}
|
||||||
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)
|
|
||||||
}
|
|
||||||
|
|
||||||
func OpenvpnConfig() error {
|
func New() CLI {
|
||||||
logger, err := logging.NewLogger(logging.ConsoleEncoding, logging.InfoLevel)
|
return &cli{}
|
||||||
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
|
|
||||||
}
|
}
|
||||||
|
|||||||
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"
|
ClientKey models.Filepath = "/gluetun/client.key"
|
||||||
// Client certificate filepath, used by Cyberghost.
|
// Client certificate filepath, used by Cyberghost.
|
||||||
ClientCertificate models.Filepath = "/gluetun/client.crt"
|
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
|
//nolint:lll
|
||||||
func PIAServers() []models.PIAServer {
|
func PIAServers() []models.PIAServer {
|
||||||
return []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 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: "perth404", IPs: []net.IP{{43, 250, 205, 186}}}, OpenvpnTCP: models.PIAServerOpenvpn{CN: "perth404", IPs: []net.IP{{43, 250, 205, 190}}}},
|
{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: "sydney403", IPs: []net.IP{{180, 92, 192, 156}}}, OpenvpnTCP: models.PIAServerOpenvpn{CN: "sydney403", IPs: []net.IP{{117, 120, 9, 7}}}},
|
{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: "tirana401", IPs: []net.IP{{31, 171, 154, 134}}}, OpenvpnTCP: models.PIAServerOpenvpn{CN: "tirana401", IPs: []net.IP{{31, 171, 154, 136}}}},
|
{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: "algiers401", IPs: []net.IP{{45, 133, 91, 248}}}, OpenvpnTCP: models.PIAServerOpenvpn{CN: "algiers401", IPs: []net.IP{{45, 133, 91, 234}}}},
|
{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: "andorra403", IPs: []net.IP{{188, 241, 82, 4}}}, OpenvpnTCP: models.PIAServerOpenvpn{CN: "andorra403", IPs: []net.IP{{188, 241, 82, 7}}}},
|
{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, 86}}}, OpenvpnTCP: models.PIAServerOpenvpn{CN: "buenosaires401", IPs: []net.IP{{190, 106, 134, 87}}}},
|
{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: "armenia401", IPs: []net.IP{{45, 139, 50, 244}}}, OpenvpnTCP: models.PIAServerOpenvpn{CN: "armenia401", IPs: []net.IP{{45, 139, 50, 249}}}},
|
{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: "vienna402", IPs: []net.IP{{156, 146, 60, 46}}}, OpenvpnTCP: models.PIAServerOpenvpn{CN: "vienna402", IPs: []net.IP{{156, 146, 60, 53}}}},
|
{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: "bahamas402", IPs: []net.IP{{45, 132, 143, 226}}}, OpenvpnTCP: models.PIAServerOpenvpn{CN: "bahamas402", IPs: []net.IP{{45, 132, 143, 208}}}},
|
{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: "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: "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: "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: "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: "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: "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: "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: "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: "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: "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 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 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 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: "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: "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: "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: "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: "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: "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: "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: "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: "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: "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: "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: "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: "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: "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: "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: "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: "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: "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: "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: "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: "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: "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: "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: "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: "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: "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: "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: "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: "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: "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: "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: "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: "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: "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: "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: "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: "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: "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: "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: "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: "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: "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: "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: "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: "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: "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: "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: "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: "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: "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: "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: "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: "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: "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: "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: "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: "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: "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: "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: "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: "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: "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: "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: "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: "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: "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: "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: "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: "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: "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: "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: "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: "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: "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: "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: "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: "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: "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: "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: "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: "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: "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: "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: "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: "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: "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: "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: "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: "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: "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: "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: "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: "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: "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: "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: "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: "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: "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: "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: "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: "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: "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: "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: "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: "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: "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: "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: "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: "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: "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: "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: "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: "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: "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: "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: "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: "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: "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: "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: "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: "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: "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: "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: "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: "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: "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: "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: "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: "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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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: "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: "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: "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: "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: "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: "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: "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: "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: "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
|
return choices
|
||||||
}
|
}
|
||||||
|
|
||||||
//nolint:gomnd
|
|
||||||
func PrivadoServers() []models.PrivadoServer {
|
func PrivadoServers() []models.PrivadoServer {
|
||||||
return []models.PrivadoServer{
|
return []models.PrivadoServer{
|
||||||
{Hostname: "akl-001.vpn.privado.io", IP: net.IP{23, 254, 104, 114}},
|
{Hostname: "akl-001.vpn.privado.io", IP: net.IP{23, 254, 104, 114}},
|
||||||
|
|||||||
@@ -44,155 +44,46 @@ func PurevpnCityChoices() (choices []string) {
|
|||||||
//nolint:lll
|
//nolint:lll
|
||||||
func PurevpnServers() []models.PurevpnServer {
|
func PurevpnServers() []models.PurevpnServer {
|
||||||
return []models.PurevpnServer{
|
return []models.PurevpnServer{
|
||||||
{Region: "Africa", Country: "Algeria", City: "Algiers", IPs: []net.IP{{172, 94, 64, 2}}},
|
{Country: "Australia", Region: "New South Wales", City: "Sydney", IPs: []net.IP{{192, 253, 241, 4}, {43, 245, 161, 85}}},
|
||||||
{Region: "Africa", Country: "Angola", City: "Benguela", IPs: []net.IP{{45, 115, 26, 2}}},
|
{Country: "Australia", Region: "Western Australia", City: "Perth", IPs: []net.IP{{172, 94, 123, 4}}},
|
||||||
{Region: "Africa", Country: "Cape Verde", City: "Praia", IPs: []net.IP{{45, 74, 25, 2}}},
|
{Country: "Austria", Region: "Lower Austria", City: "Langenzersdorf", IPs: []net.IP{{172, 94, 109, 4}}},
|
||||||
{Region: "Africa", Country: "Egypt", City: "Cairo", IPs: []net.IP{{192, 198, 120, 122}}},
|
{Country: "Austria", Region: "Vienna", City: "Vienna", IPs: []net.IP{{217, 64, 127, 252}}},
|
||||||
{Region: "Africa", Country: "Ethiopia", City: "Addis Ababa", IPs: []net.IP{{104, 250, 178, 4}}},
|
{Country: "Belgium", Region: "Flanders", City: "Zaventem", IPs: []net.IP{{172, 111, 223, 4}}},
|
||||||
{Region: "Africa", Country: "Ghana", City: "Accra", IPs: []net.IP{{196, 251, 67, 4}}},
|
{Country: "Bulgaria", Region: "Sofia-Capital", City: "Sofia", IPs: []net.IP{{217, 138, 221, 120}}},
|
||||||
{Region: "Africa", Country: "Kenya", City: "Mombasa", IPs: []net.IP{{102, 135, 0, 2}}},
|
{Country: "Canada", Region: "Alberta", City: "Calgary", IPs: []net.IP{{172, 94, 34, 4}}},
|
||||||
{Region: "Africa", Country: "Madagascar", City: "Antananarivo", IPs: []net.IP{{206, 123, 156, 131}}},
|
{Country: "Canada", Region: "Ontario", City: "Toronto", IPs: []net.IP{{104, 200, 138, 196}}},
|
||||||
{Region: "Africa", Country: "Mauritania", City: "Nouakchott", IPs: []net.IP{{206, 123, 158, 63}}},
|
{Country: "France", Region: "Île-de-France", City: "Paris", IPs: []net.IP{{89, 40, 183, 178}}},
|
||||||
{Region: "Africa", Country: "Mauritius", City: "Port Louis", IPs: []net.IP{{104, 250, 181, 4}}},
|
{Country: "Germany", Region: "Hesse", City: "Frankfurt am Main", IPs: []net.IP{{188, 72, 84, 4}}},
|
||||||
{Region: "Africa", Country: "Morocco", City: "Rabat", IPs: []net.IP{{104, 243, 250, 126}}},
|
{Country: "Greece", Region: "Central Macedonia", City: "Thessaloníki", IPs: []net.IP{{178, 21, 169, 244}}},
|
||||||
{Region: "Africa", Country: "Niger", City: "Niamey", IPs: []net.IP{{206, 123, 157, 131}}},
|
{Country: "Hong Kong", Region: "Central and Western", City: "Central", IPs: []net.IP{{141, 101, 168, 4}, {141, 101, 168, 4}}},
|
||||||
{Region: "Africa", Country: "Nigeria", City: "Suleja", IPs: []net.IP{{102, 165, 25, 38}}},
|
{Country: "Hong Kong", Region: "Central and Western", City: "Hong Kong", IPs: []net.IP{{43, 226, 231, 6}, {172, 111, 168, 4}}},
|
||||||
{Region: "Africa", Country: "Senegal", City: "Dakar", IPs: []net.IP{{206, 123, 158, 131}}},
|
{Country: "India", Region: "Tamil Nadu", City: "Chennai", IPs: []net.IP{{129, 227, 107, 242}}},
|
||||||
{Region: "Africa", Country: "Seychelles", City: "Victoria", IPs: []net.IP{{172, 111, 128, 126}}},
|
{Country: "Italy", Region: "Trentino-Alto Adige", City: "Trento", IPs: []net.IP{{172, 111, 173, 3}}},
|
||||||
{Region: "Africa", Country: "South Africa", City: "Johannesburg", IPs: []net.IP{{45, 74, 45, 2}}},
|
{Country: "Japan", Region: "Ōsaka", City: "Osaka", IPs: []net.IP{{172, 94, 56, 4}}},
|
||||||
{Region: "Africa", Country: "Tanzania", City: "Dar Es Salaam", IPs: []net.IP{{102, 135, 0, 2}}},
|
{Country: "Malaysia", Region: "Kuala Lumpur", City: "Kuala Lumpur", IPs: []net.IP{{103, 55, 10, 133}}},
|
||||||
{Region: "Africa", Country: "Tunisia", City: "Tunis", IPs: []net.IP{{206, 123, 159, 4}}},
|
{Country: "Netherlands", Region: "North Holland", City: "Amsterdam", IPs: []net.IP{{5, 254, 73, 172}}},
|
||||||
{Region: "Asia", Country: "Afghanistan", City: "Kabul", IPs: []net.IP{{172, 111, 208, 2}}},
|
{Country: "Norway", Region: "Oslo", City: "Oslo", IPs: []net.IP{{82, 102, 22, 212}}},
|
||||||
{Region: "Asia", Country: "Armenia", City: "Singapore", IPs: []net.IP{{37, 120, 208, 147}}},
|
{Country: "Panama", Region: "Panamá", City: "Panamá", IPs: []net.IP{{104, 243, 243, 131}}},
|
||||||
{Region: "Asia", Country: "Azerbaijan", City: "Baku", IPs: []net.IP{{104, 250, 177, 4}}},
|
{Country: "Philippines", Region: "Metro Manila", City: "Quezon City", IPs: []net.IP{{129, 227, 119, 84}}},
|
||||||
{Region: "Asia", Country: "Bangladesh", City: "Dhaka", IPs: []net.IP{{206, 123, 154, 190}}},
|
{Country: "Poland", Region: "Mazovia", City: "Warsaw", IPs: []net.IP{{5, 253, 206, 251}}},
|
||||||
{Region: "Asia", Country: "Brunei Darussalam", City: "Bandar Seri Begawan", IPs: []net.IP{{36, 255, 98, 2}}},
|
{Country: "Portugal", Region: "Lisbon", City: "Lisbon", IPs: []net.IP{{5, 154, 174, 3}}},
|
||||||
{Region: "Asia", Country: "Cambodia", City: "Phnom Penh", IPs: []net.IP{{104, 250, 176, 122}}},
|
{Country: "Russian Federation", Region: "Moscow", City: "Moscow", IPs: []net.IP{{46, 243, 220, 2}, {206, 123, 139, 4}}},
|
||||||
{Region: "Asia", Country: "India", City: "Chennai", IPs: []net.IP{{129, 227, 107, 242}}},
|
{Country: "Singapore", Region: "Singapore", City: "Singapore", IPs: []net.IP{{37, 120, 208, 147}}},
|
||||||
{Region: "Asia", Country: "Indonesia", City: "Jakarta", IPs: []net.IP{{103, 55, 9, 2}}},
|
{Country: "South Africa", Region: "Gauteng", City: "Johannesburg", IPs: []net.IP{{102, 165, 3, 33}}},
|
||||||
{Region: "Asia", Country: "Japan", City: "Tokyo", IPs: []net.IP{{172, 94, 56, 2}}},
|
{Country: "Spain", Region: "Madrid", City: "Madrid", IPs: []net.IP{{217, 138, 218, 210}}},
|
||||||
{Region: "Asia", Country: "Kazakhstan", City: "Almaty", IPs: []net.IP{{206, 123, 152, 4}}},
|
{Country: "Sweden", Region: "Stockholm", City: "Stockholm", IPs: []net.IP{{86, 106, 103, 139}}},
|
||||||
{Region: "Asia", Country: "Korea, South", City: "Seoul", IPs: []net.IP{{45, 115, 25, 1}}},
|
{Country: "Switzerland", Region: "Zurich", City: "Zürich", IPs: []net.IP{{45, 12, 222, 103}}},
|
||||||
{Region: "Asia", Country: "Kyrgyzstan", City: "Bishkek", IPs: []net.IP{{206, 123, 151, 131}}},
|
{Country: "Taiwan", Region: "Taiwan", City: "Taipei", IPs: []net.IP{{128, 1, 155, 178}}},
|
||||||
{Region: "Asia", Country: "Laos", City: "Vientiane", IPs: []net.IP{{206, 123, 153, 4}}},
|
{Country: "United Arab Emirates", Region: "Dubai", City: "Dubai", IPs: []net.IP{{104, 37, 6, 4}}},
|
||||||
{Region: "Asia", Country: "Macao", City: "Beyrouth", IPs: []net.IP{{104, 243, 240, 121}}},
|
{Country: "United Kingdom", Region: "England", City: "Birmingham", IPs: []net.IP{{188, 72, 89, 4}}},
|
||||||
{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}}},
|
{Country: "United Kingdom", Region: "England", City: "London", IPs: []net.IP{{45, 141, 154, 71}, {45, 141, 154, 71}}},
|
||||||
{Region: "Asia", Country: "Malaysia", City: "Kuala Lumpur", IPs: []net.IP{{104, 250, 160, 4}}},
|
{Country: "United States", Region: "California", City: "Los Angeles", IPs: []net.IP{{172, 111, 147, 4}}},
|
||||||
{Region: "Asia", Country: "Mongolia", City: "Ulaanbaatar", IPs: []net.IP{{206, 123, 153, 131}}},
|
{Country: "United States", Region: "California", City: "South San Francisco", IPs: []net.IP{{141, 101, 166, 4}, {141, 101, 166, 4}, {141, 101, 166, 4}}},
|
||||||
{Region: "Asia", Country: "Pakistan", City: "Islamabad", IPs: []net.IP{{104, 250, 187, 3}}},
|
{Country: "United States", Region: "Florida", City: "Miami", IPs: []net.IP{{5, 254, 79, 115}}},
|
||||||
{Region: "Asia", Country: "Papua New Guinea", City: "Port Moresby", IPs: []net.IP{{206, 123, 155, 131}}},
|
{Country: "United States", Region: "Massachusetts", City: "Newton", IPs: []net.IP{{104, 243, 244, 2}}},
|
||||||
{Region: "Asia", Country: "Philippines", City: "Manila", IPs: []net.IP{{129, 227, 119, 84}}},
|
{Country: "United States", Region: "Michigan", City: "Ypsilanti", IPs: []net.IP{{172, 111, 149, 4}}},
|
||||||
{Region: "Asia", Country: "Sri Lanka", City: "Colombo", IPs: []net.IP{{206, 123, 154, 4}}},
|
{Country: "United States", Region: "Texas", City: "Dallas", IPs: []net.IP{{208, 84, 155, 100}}},
|
||||||
{Region: "Asia", Country: "Taiwan", City: "Taipei", IPs: []net.IP{{128, 1, 155, 178}}},
|
{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}}},
|
||||||
{Region: "Asia", Country: "Tajikistan", City: "Dushanbe", IPs: []net.IP{{206, 123, 151, 4}}},
|
{Country: "Vietnam", Region: "Hanoi", City: "Cầu Giấy", IPs: []net.IP{{192, 253, 249, 132}}},
|
||||||
{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}}},
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -23,12 +23,12 @@ func GetAllServers() (allServers models.AllServers) {
|
|||||||
},
|
},
|
||||||
Pia: models.PiaServers{
|
Pia: models.PiaServers{
|
||||||
Version: 2,
|
Version: 2,
|
||||||
Timestamp: 1605392393,
|
Timestamp: 1609343591,
|
||||||
Servers: PIAServers(),
|
Servers: PIAServers(),
|
||||||
},
|
},
|
||||||
Purevpn: models.PurevpnServers{
|
Purevpn: models.PurevpnServers{
|
||||||
Version: 1,
|
Version: 1,
|
||||||
Timestamp: 1599323261,
|
Timestamp: 1609448478,
|
||||||
Servers: PurevpnServers(),
|
Servers: PurevpnServers(),
|
||||||
},
|
},
|
||||||
Privado: models.PrivadoServers{
|
Privado: models.PrivadoServers{
|
||||||
|
|||||||
@@ -62,7 +62,7 @@ func Test_versions(t *testing.T) {
|
|||||||
"Purevpn": {
|
"Purevpn": {
|
||||||
model: models.PurevpnServer{},
|
model: models.PurevpnServer{},
|
||||||
version: allServers.Purevpn.Version,
|
version: allServers.Purevpn.Version,
|
||||||
digest: "cc1a2219",
|
digest: "ada45379",
|
||||||
},
|
},
|
||||||
"Surfshark": {
|
"Surfshark": {
|
||||||
model: models.SurfsharkServer{},
|
model: models.SurfsharkServer{},
|
||||||
@@ -133,12 +133,12 @@ func Test_timestamps(t *testing.T) {
|
|||||||
"Private Internet Access": {
|
"Private Internet Access": {
|
||||||
servers: allServers.Pia.Servers,
|
servers: allServers.Pia.Servers,
|
||||||
timestamp: allServers.Pia.Timestamp,
|
timestamp: allServers.Pia.Timestamp,
|
||||||
digest: "4a172b0a",
|
digest: "d797f112",
|
||||||
},
|
},
|
||||||
"Purevpn": {
|
"Purevpn": {
|
||||||
servers: allServers.Purevpn.Servers,
|
servers: allServers.Purevpn.Servers,
|
||||||
timestamp: allServers.Purevpn.Timestamp,
|
timestamp: allServers.Purevpn.Timestamp,
|
||||||
digest: "cdf9b708",
|
digest: "8abe18d4",
|
||||||
},
|
},
|
||||||
"Privado": {
|
"Privado": {
|
||||||
servers: allServers.Privado.Servers,
|
servers: allServers.Privado.Servers,
|
||||||
|
|||||||
@@ -2,9 +2,9 @@ package constants
|
|||||||
|
|
||||||
const (
|
const (
|
||||||
// Announcement is a message announcement.
|
// 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 is the expiration date of the announcement in format yyyy-mm-dd.
|
||||||
AnnouncementExpiration = "2020-11-25"
|
AnnouncementExpiration = "2021-01-20"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
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 (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"errors"
|
||||||
"net"
|
"net"
|
||||||
|
"net/http"
|
||||||
"sync"
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/qdm12/dns/pkg/unbound"
|
||||||
"github.com/qdm12/gluetun/internal/constants"
|
"github.com/qdm12/gluetun/internal/constants"
|
||||||
|
"github.com/qdm12/gluetun/internal/models"
|
||||||
"github.com/qdm12/gluetun/internal/settings"
|
"github.com/qdm12/gluetun/internal/settings"
|
||||||
"github.com/qdm12/golibs/command"
|
"github.com/qdm12/golibs/command"
|
||||||
"github.com/qdm12/golibs/logging"
|
"github.com/qdm12/golibs/logging"
|
||||||
)
|
)
|
||||||
|
|
||||||
type Looper interface {
|
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)
|
RunRestartTicker(ctx context.Context, wg *sync.WaitGroup)
|
||||||
Restart()
|
GetStatus() (status models.LoopStatus)
|
||||||
Start()
|
SetStatus(status models.LoopStatus) (outcome string, err error)
|
||||||
Stop()
|
|
||||||
GetSettings() (settings settings.DNS)
|
GetSettings() (settings settings.DNS)
|
||||||
SetSettings(settings settings.DNS)
|
SetSettings(settings settings.DNS) (outcome string)
|
||||||
}
|
}
|
||||||
|
|
||||||
type looper struct {
|
type looper struct {
|
||||||
conf Configurator
|
state state
|
||||||
settings settings.DNS
|
conf unbound.Configurator
|
||||||
settingsMutex sync.RWMutex
|
client *http.Client
|
||||||
logger logging.Logger
|
logger logging.Logger
|
||||||
streamMerger command.StreamMerger
|
streamMerger command.StreamMerger
|
||||||
uid int
|
username string
|
||||||
gid int
|
puid int
|
||||||
restart chan struct{}
|
pgid int
|
||||||
|
loopLock sync.Mutex
|
||||||
start chan struct{}
|
start chan struct{}
|
||||||
|
running chan models.LoopStatus
|
||||||
stop chan struct{}
|
stop chan struct{}
|
||||||
|
stopped chan struct{}
|
||||||
updateTicker chan struct{}
|
updateTicker chan struct{}
|
||||||
|
backoffTime time.Duration
|
||||||
timeNow func() time.Time
|
timeNow func() time.Time
|
||||||
timeSince func(time.Time) time.Duration
|
timeSince func(time.Time) time.Duration
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewLooper(conf Configurator, settings settings.DNS, logger logging.Logger,
|
const defaultBackoffTime = 10 * time.Second
|
||||||
streamMerger command.StreamMerger, uid, gid int) Looper {
|
|
||||||
|
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{
|
return &looper{
|
||||||
conf: conf,
|
state: state{
|
||||||
|
status: constants.Stopped,
|
||||||
settings: settings,
|
settings: settings,
|
||||||
|
},
|
||||||
|
conf: conf,
|
||||||
|
client: client,
|
||||||
logger: logger.WithPrefix("dns over tls: "),
|
logger: logger.WithPrefix("dns over tls: "),
|
||||||
uid: uid,
|
username: username,
|
||||||
gid: gid,
|
puid: puid,
|
||||||
|
pgid: pgid,
|
||||||
streamMerger: streamMerger,
|
streamMerger: streamMerger,
|
||||||
restart: make(chan struct{}),
|
|
||||||
start: make(chan struct{}),
|
start: make(chan struct{}),
|
||||||
|
running: make(chan models.LoopStatus),
|
||||||
stop: make(chan struct{}),
|
stop: make(chan struct{}),
|
||||||
|
stopped: make(chan struct{}),
|
||||||
updateTicker: make(chan struct{}),
|
updateTicker: make(chan struct{}),
|
||||||
|
backoffTime: defaultBackoffTime,
|
||||||
timeNow: time.Now,
|
timeNow: time.Now,
|
||||||
timeSince: time.Since,
|
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) {
|
func (l *looper) logAndWait(ctx context.Context, err error) {
|
||||||
l.logger.Warn(err)
|
l.logger.Warn(err)
|
||||||
l.logger.Info("attempting restart in 10 seconds")
|
l.logger.Info("attempting restart in %s", l.backoffTime)
|
||||||
const waitDuration = 10 * time.Second
|
timer := time.NewTimer(l.backoffTime)
|
||||||
timer := time.NewTimer(waitDuration)
|
l.backoffTime *= 2
|
||||||
select {
|
select {
|
||||||
case <-timer.C:
|
case <-timer.C:
|
||||||
case <-ctx.Done():
|
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()) {
|
func (l *looper) Run(ctx context.Context, wg *sync.WaitGroup, dnsReadyCh chan<- struct{}) {
|
||||||
for {
|
|
||||||
select {
|
|
||||||
case <-l.stop:
|
|
||||||
l.setEnabled(false)
|
|
||||||
l.logger.Info("not started yet")
|
|
||||||
case <-l.restart:
|
|
||||||
if l.isEnabled() {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
signalDNSReady()
|
|
||||||
l.logger.Info("not restarting because disabled")
|
|
||||||
case <-l.start:
|
|
||||||
l.setEnabled(true)
|
|
||||||
return
|
|
||||||
case <-ctx.Done():
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (l *looper) waitForSubsequentStart(ctx context.Context, unboundCancel context.CancelFunc) {
|
|
||||||
if l.isEnabled() {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
for {
|
|
||||||
// wait for a signal to re-enable
|
|
||||||
select {
|
|
||||||
case <-l.stop:
|
|
||||||
l.logger.Info("already disabled")
|
|
||||||
case <-l.restart:
|
|
||||||
if !l.isEnabled() {
|
|
||||||
l.logger.Info("not restarting because disabled")
|
|
||||||
} else {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
case <-l.start:
|
|
||||||
l.setEnabled(true)
|
|
||||||
return
|
|
||||||
case <-ctx.Done():
|
|
||||||
unboundCancel()
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (l *looper) Run(ctx context.Context, wg *sync.WaitGroup, signalDNSReady func()) {
|
|
||||||
defer wg.Done()
|
defer wg.Done()
|
||||||
|
|
||||||
const fallback = false
|
const fallback = false
|
||||||
l.useUnencryptedDNS(fallback)
|
l.useUnencryptedDNS(fallback) // TODO remove? Use default DNS by default for Docker resolution?
|
||||||
l.waitForFirstStart(ctx, signalDNSReady)
|
|
||||||
if ctx.Err() != nil {
|
select {
|
||||||
|
case <-l.start:
|
||||||
|
case <-ctx.Done():
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
defer l.logger.Warn("loop exited")
|
defer l.logger.Warn("loop exited")
|
||||||
|
|
||||||
var unboundCtx context.Context
|
crashed := false
|
||||||
var unboundCancel context.CancelFunc = func() {}
|
l.backoffTime = defaultBackoffTime
|
||||||
var waitError chan error
|
|
||||||
triggeredRestart := false
|
|
||||||
l.setEnabled(true)
|
|
||||||
for ctx.Err() == nil {
|
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()
|
for l.GetSettings().Enabled {
|
||||||
|
if ctx.Err() != nil {
|
||||||
// Setup
|
l.logger.Warn("context canceled: exiting loop")
|
||||||
if err := l.conf.DownloadRootHints(ctx, l.uid, l.gid); err != nil {
|
return
|
||||||
l.logAndWait(ctx, err)
|
|
||||||
continue
|
|
||||||
}
|
}
|
||||||
if err := l.conf.DownloadRootKey(ctx, l.uid, l.gid); err != nil {
|
var err error
|
||||||
l.logAndWait(ctx, err)
|
unboundCancel, err = l.setupUnbound(ctx, crashed, waitError)
|
||||||
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 {
|
if err != nil {
|
||||||
unboundCancel()
|
if !errors.Is(err, errUpdateFiles) {
|
||||||
const fallback = true
|
const fallback = true
|
||||||
l.useUnencryptedDNS(fallback)
|
l.useUnencryptedDNS(fallback)
|
||||||
|
}
|
||||||
l.logAndWait(ctx, err)
|
l.logAndWait(ctx, err)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
break
|
||||||
|
}
|
||||||
|
if !l.GetSettings().Enabled {
|
||||||
|
const fallback = false
|
||||||
|
l.useUnencryptedDNS(fallback)
|
||||||
|
}
|
||||||
|
|
||||||
// Started successfully
|
dnsReadyCh <- struct{}{}
|
||||||
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()
|
|
||||||
|
|
||||||
stayHere := true
|
stayHere := true
|
||||||
for stayHere {
|
for stayHere {
|
||||||
@@ -229,32 +142,85 @@ func (l *looper) Run(ctx context.Context, wg *sync.WaitGroup, signalDNSReady fun
|
|||||||
<-waitError
|
<-waitError
|
||||||
close(waitError)
|
close(waitError)
|
||||||
return
|
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:
|
case <-l.stop:
|
||||||
l.logger.Info("stopping")
|
l.logger.Info("stopping")
|
||||||
|
const fallback = false
|
||||||
|
l.useUnencryptedDNS(fallback)
|
||||||
unboundCancel()
|
unboundCancel()
|
||||||
<-waitError
|
<-waitError
|
||||||
close(waitError)
|
l.stopped <- struct{}{}
|
||||||
l.setEnabled(false)
|
case <-l.start:
|
||||||
|
l.logger.Info("starting")
|
||||||
stayHere = false
|
stayHere = false
|
||||||
case err := <-waitError: // unexpected error
|
case err := <-waitError: // unexpected error
|
||||||
close(waitError)
|
|
||||||
unboundCancel()
|
unboundCancel()
|
||||||
|
l.state.setStatusWithLock(constants.Crashed)
|
||||||
const fallback = true
|
const fallback = true
|
||||||
l.useUnencryptedDNS(fallback)
|
l.useUnencryptedDNS(fallback)
|
||||||
l.logAndWait(ctx, err)
|
l.logAndWait(ctx, err)
|
||||||
stayHere = false
|
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) {
|
func (l *looper) useUnencryptedDNS(fallback bool) {
|
||||||
settings := l.GetSettings()
|
settings := l.GetSettings()
|
||||||
@@ -275,11 +241,15 @@ func (l *looper) useUnencryptedDNS(fallback bool) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Try with any IPv4 address from the providers chosen
|
// Try with any IPv4 address from the providers chosen
|
||||||
for _, provider := range settings.Providers {
|
for _, provider := range settings.Unbound.Providers {
|
||||||
data := constants.DNSProviderMapping()[provider]
|
data, _ := unbound.GetProviderData(provider)
|
||||||
for _, targetIP = range data.IPs {
|
for _, targetIP = range data.IPs {
|
||||||
if targetIP.To4() != nil {
|
if targetIP.To4() != nil {
|
||||||
|
if fallback {
|
||||||
l.logger.Info("falling back on plaintext DNS at address %s", targetIP)
|
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)
|
l.conf.UseDNSInternally(targetIP)
|
||||||
if err := l.conf.UseDNSSystemWide(targetIP, settings.KeepNameserver); err != nil {
|
if err := l.conf.UseDNSSystemWide(targetIP, settings.KeepNameserver); err != nil {
|
||||||
l.logger.Error(err)
|
l.logger.Error(err)
|
||||||
@@ -290,7 +260,7 @@ func (l *looper) useUnencryptedDNS(fallback bool) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// No IPv4 address found
|
// 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) {
|
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
|
return
|
||||||
case <-timer.C:
|
case <-timer.C:
|
||||||
lastTick = l.timeNow()
|
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()
|
settings := l.GetSettings()
|
||||||
timer.Reset(settings.UpdatePeriod)
|
timer.Reset(settings.UpdatePeriod)
|
||||||
case <-l.updateTicker:
|
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/models"
|
||||||
"github.com/qdm12/gluetun/internal/routing"
|
"github.com/qdm12/gluetun/internal/routing"
|
||||||
"github.com/qdm12/golibs/command"
|
"github.com/qdm12/golibs/command"
|
||||||
"github.com/qdm12/golibs/files"
|
|
||||||
"github.com/qdm12/golibs/logging"
|
"github.com/qdm12/golibs/logging"
|
||||||
|
"github.com/qdm12/golibs/os"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Configurator allows to change firewall rules and modify network routes.
|
// Configurator allows to change firewall rules and modify network routes.
|
||||||
@@ -29,7 +29,7 @@ type configurator struct { //nolint:maligned
|
|||||||
commander command.Commander
|
commander command.Commander
|
||||||
logger logging.Logger
|
logger logging.Logger
|
||||||
routing routing.Routing
|
routing routing.Routing
|
||||||
fileManager files.FileManager // for custom iptables rules
|
openFile os.OpenFileFunc // for custom iptables rules
|
||||||
iptablesMutex sync.Mutex
|
iptablesMutex sync.Mutex
|
||||||
debug bool
|
debug bool
|
||||||
defaultInterface string
|
defaultInterface string
|
||||||
@@ -47,12 +47,12 @@ type configurator struct { //nolint:maligned
|
|||||||
}
|
}
|
||||||
|
|
||||||
// NewConfigurator creates a new Configurator instance.
|
// 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{
|
return &configurator{
|
||||||
commander: command.NewCommander(),
|
commander: command.NewCommander(),
|
||||||
logger: logger.WithPrefix("firewall: "),
|
logger: logger.WithPrefix("firewall: "),
|
||||||
routing: routing,
|
routing: routing,
|
||||||
fileManager: fileManager,
|
openFile: openFile,
|
||||||
allowedInputPorts: make(map[uint16]string),
|
allowedInputPorts: make(map[uint16]string),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,7 +3,9 @@ package firewall
|
|||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"io/ioutil"
|
||||||
"net"
|
"net"
|
||||||
|
"os"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/qdm12/gluetun/internal/models"
|
"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 {
|
func (c *configurator) runUserPostRules(ctx context.Context, filepath string, remove bool) error {
|
||||||
exists, err := c.fileManager.FileExists(filepath)
|
file, err := c.openFile(filepath, os.O_RDONLY, 0)
|
||||||
if err != nil {
|
if os.IsNotExist(err) {
|
||||||
return err
|
|
||||||
} else if !exists {
|
|
||||||
return nil
|
return nil
|
||||||
|
} else if err != nil {
|
||||||
|
return err
|
||||||
}
|
}
|
||||||
b, err := c.fileManager.ReadFile(filepath)
|
b, err := ioutil.ReadAll(file)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
_ = file.Close()
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if err := file.Close(); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
lines := strings.Split(string(b), "\n")
|
lines := strings.Split(string(b), "\n")
|
||||||
|
|||||||
@@ -1,21 +1,25 @@
|
|||||||
package healthcheck
|
package healthcheck
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"net"
|
"errors"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
"sync"
|
||||||
|
|
||||||
"github.com/qdm12/golibs/logging"
|
"github.com/qdm12/golibs/logging"
|
||||||
)
|
)
|
||||||
|
|
||||||
type handler struct {
|
type handler struct {
|
||||||
logger logging.Logger
|
logger logging.Logger
|
||||||
resolver *net.Resolver
|
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{
|
return &handler{
|
||||||
logger: logger,
|
logger: logger,
|
||||||
resolver: resolver,
|
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)
|
http.Error(responseWriter, "method not supported for healthcheck", http.StatusBadRequest)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
err := healthCheck(request.Context(), h.resolver)
|
if err := h.getErr(); err != nil {
|
||||||
if err != nil {
|
|
||||||
h.logger.Error(err)
|
h.logger.Error(err)
|
||||||
http.Error(responseWriter, err.Error(), http.StatusInternalServerError)
|
http.Error(responseWriter, err.Error(), http.StatusInternalServerError)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
responseWriter.WriteHeader(http.StatusOK)
|
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 (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"net"
|
"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) {
|
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)
|
ips, err := resolver.LookupIP(ctx, "ip", domainToResolve)
|
||||||
switch {
|
switch {
|
||||||
case err != nil:
|
case err != nil:
|
||||||
return fmt.Errorf("cannot resolve github.com: %s", err)
|
return err
|
||||||
case len(ips) == 0:
|
case len(ips) == 0:
|
||||||
return fmt.Errorf("resolved no IP addresses for %s", domainToResolve)
|
return fmt.Errorf("%w for %s", errNoIPResolved, domainToResolve)
|
||||||
default:
|
default:
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -18,24 +18,34 @@ type Server interface {
|
|||||||
type server struct {
|
type server struct {
|
||||||
address string
|
address string
|
||||||
logger logging.Logger
|
logger logging.Logger
|
||||||
handler http.Handler
|
handler *handler
|
||||||
|
resolver *net.Resolver
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewServer(address string, logger logging.Logger) Server {
|
func NewServer(address string, logger logging.Logger) Server {
|
||||||
|
healthcheckLogger := logger.WithPrefix("healthcheck: ")
|
||||||
return &server{
|
return &server{
|
||||||
address: address,
|
address: address,
|
||||||
logger: logger.WithPrefix("healthcheck: "),
|
logger: healthcheckLogger,
|
||||||
handler: newHandler(logger, &net.Resolver{}),
|
handler: newHandler(healthcheckLogger),
|
||||||
|
resolver: net.DefaultResolver,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *server) Run(ctx context.Context, wg *sync.WaitGroup) {
|
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{
|
server := http.Server{
|
||||||
Addr: s.address,
|
Addr: s.address,
|
||||||
Handler: s.handler,
|
Handler: s.handler,
|
||||||
}
|
}
|
||||||
|
internalWg.Add(1)
|
||||||
go func() {
|
go func() {
|
||||||
defer wg.Done()
|
defer internalWg.Done()
|
||||||
<-ctx.Done()
|
<-ctx.Done()
|
||||||
s.logger.Warn("context canceled: shutting down server")
|
s.logger.Warn("context canceled: shutting down server")
|
||||||
defer s.logger.Warn("server shut down")
|
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.Error("failed shutting down: %s", err)
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
|
|
||||||
s.logger.Info("listening on %s", s.address)
|
s.logger.Info("listening on %s", s.address)
|
||||||
err := server.ListenAndServe()
|
err := server.ListenAndServe()
|
||||||
if err != nil && !errors.Is(ctx.Err(), context.Canceled) {
|
if err != nil && !errors.Is(ctx.Err(), context.Canceled) {
|
||||||
s.logger.Error(err)
|
s.logger.Error(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
internalWg.Wait()
|
||||||
}
|
}
|
||||||
|
|||||||
24
internal/httpproxy/accept.go
Normal file
24
internal/httpproxy/accept.go
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
package httpproxy
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"net/http"
|
||||||
|
)
|
||||||
|
|
||||||
|
func (h *handler) isAccepted(responseWriter http.ResponseWriter, request *http.Request) bool {
|
||||||
|
// Not compatible with HTTP < 1.0 or HTTP >= 2.0 (see https://github.com/golang/go/issues/14797#issuecomment-196103814)
|
||||||
|
const (
|
||||||
|
minimalMajorVersion = 1
|
||||||
|
minimalMinorVersion = 0
|
||||||
|
maximumMajorVersion = 2
|
||||||
|
maximumMinorVersion = 0
|
||||||
|
)
|
||||||
|
if !request.ProtoAtLeast(minimalMajorVersion, minimalMinorVersion) ||
|
||||||
|
request.ProtoAtLeast(maximumMajorVersion, maximumMinorVersion) {
|
||||||
|
message := fmt.Sprintf("http version not supported: %s", request.Proto)
|
||||||
|
h.logger.Info("%s, from %s", message, request.RemoteAddr)
|
||||||
|
http.Error(responseWriter, message, http.StatusBadRequest)
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
@@ -6,10 +6,13 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
)
|
)
|
||||||
|
|
||||||
func isAuthorized(responseWriter http.ResponseWriter, request *http.Request,
|
func (h *handler) isAuthorized(responseWriter http.ResponseWriter, request *http.Request) (authorized bool) {
|
||||||
username, password string) (authorized bool) {
|
if len(h.username) == 0 || (request.Method != "CONNECT" && !request.URL.IsAbs()) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
basicAuth := request.Header.Get("Proxy-Authorization")
|
basicAuth := request.Header.Get("Proxy-Authorization")
|
||||||
if len(basicAuth) == 0 {
|
if len(basicAuth) == 0 {
|
||||||
|
h.logger.Info("Proxy-Authorization header not found from %s", request.RemoteAddr)
|
||||||
responseWriter.Header().Set("Proxy-Authenticate", `Basic realm="Access to Gluetun over HTTP"`)
|
responseWriter.Header().Set("Proxy-Authenticate", `Basic realm="Access to Gluetun over HTTP"`)
|
||||||
responseWriter.WriteHeader(http.StatusProxyAuthRequired)
|
responseWriter.WriteHeader(http.StatusProxyAuthRequired)
|
||||||
return false
|
return false
|
||||||
@@ -17,6 +20,8 @@ func isAuthorized(responseWriter http.ResponseWriter, request *http.Request,
|
|||||||
b64UsernamePassword := strings.TrimPrefix(basicAuth, "Basic ")
|
b64UsernamePassword := strings.TrimPrefix(basicAuth, "Basic ")
|
||||||
b, err := base64.StdEncoding.DecodeString(b64UsernamePassword)
|
b, err := base64.StdEncoding.DecodeString(b64UsernamePassword)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
h.logger.Info("Cannot decode Proxy-Authorization header value from %s: %s",
|
||||||
|
request.RemoteAddr, err.Error())
|
||||||
responseWriter.WriteHeader(http.StatusUnauthorized)
|
responseWriter.WriteHeader(http.StatusUnauthorized)
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
@@ -26,7 +31,9 @@ func isAuthorized(responseWriter http.ResponseWriter, request *http.Request,
|
|||||||
responseWriter.WriteHeader(http.StatusBadRequest)
|
responseWriter.WriteHeader(http.StatusBadRequest)
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
if username != usernamePassword[0] && password != usernamePassword[1] {
|
if h.username != usernamePassword[0] || h.password != usernamePassword[1] {
|
||||||
|
h.logger.Info("Username or password mismatch from %s", request.RemoteAddr)
|
||||||
|
h.logger.Debug("username provided %q and password provided %q", usernamePassword[0], usernamePassword[1])
|
||||||
responseWriter.WriteHeader(http.StatusUnauthorized)
|
responseWriter.WriteHeader(http.StatusUnauthorized)
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,16 +9,14 @@ import (
|
|||||||
"github.com/qdm12/golibs/logging"
|
"github.com/qdm12/golibs/logging"
|
||||||
)
|
)
|
||||||
|
|
||||||
func newHandler(ctx context.Context, wg *sync.WaitGroup,
|
func newHandler(ctx context.Context, wg *sync.WaitGroup, logger logging.Logger,
|
||||||
client *http.Client, logger logging.Logger,
|
|
||||||
stealth, verbose bool, username, password string) http.Handler {
|
stealth, verbose bool, username, password string) http.Handler {
|
||||||
const relayTimeout = 10 * time.Second
|
const httpTimeout = 24 * time.Hour
|
||||||
return &handler{
|
return &handler{
|
||||||
ctx: ctx,
|
ctx: ctx,
|
||||||
wg: wg,
|
wg: wg,
|
||||||
client: client,
|
client: &http.Client{Timeout: httpTimeout},
|
||||||
logger: logger,
|
logger: logger,
|
||||||
relayTimeout: relayTimeout,
|
|
||||||
verbose: verbose,
|
verbose: verbose,
|
||||||
stealth: stealth,
|
stealth: stealth,
|
||||||
username: username,
|
username: username,
|
||||||
@@ -31,16 +29,20 @@ type handler struct {
|
|||||||
wg *sync.WaitGroup
|
wg *sync.WaitGroup
|
||||||
client *http.Client
|
client *http.Client
|
||||||
logger logging.Logger
|
logger logging.Logger
|
||||||
relayTimeout time.Duration
|
|
||||||
verbose, stealth bool
|
verbose, stealth bool
|
||||||
username, password string
|
username, password string
|
||||||
}
|
}
|
||||||
|
|
||||||
func (h *handler) ServeHTTP(responseWriter http.ResponseWriter, request *http.Request) {
|
func (h *handler) ServeHTTP(responseWriter http.ResponseWriter, request *http.Request) {
|
||||||
if len(h.username) > 0 && !isAuthorized(responseWriter, request, h.username, h.password) {
|
if !h.isAccepted(responseWriter, request) {
|
||||||
h.logger.Info("%s unauthorized", request.RemoteAddr)
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
if !h.isAuthorized(responseWriter, request) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
request.Header.Del("Proxy-Connection")
|
||||||
|
request.Header.Del("Proxy-Authenticate")
|
||||||
|
request.Header.Del("Proxy-Authorization")
|
||||||
switch request.Method {
|
switch request.Method {
|
||||||
case http.MethodConnect:
|
case http.MethodConnect:
|
||||||
h.handleHTTPS(responseWriter, request)
|
h.handleHTTPS(responseWriter, request)
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
package httpproxy
|
package httpproxy
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"net"
|
"net"
|
||||||
@@ -18,9 +17,7 @@ func (h *handler) handleHTTP(responseWriter http.ResponseWriter, request *http.R
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
ctx, cancel := context.WithTimeout(h.ctx, h.relayTimeout)
|
request = request.WithContext(h.ctx)
|
||||||
defer cancel()
|
|
||||||
request = request.WithContext(ctx)
|
|
||||||
|
|
||||||
request.RequestURI = ""
|
request.RequestURI = ""
|
||||||
|
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
func (h *handler) handleHTTPS(responseWriter http.ResponseWriter, request *http.Request) {
|
func (h *handler) handleHTTPS(responseWriter http.ResponseWriter, request *http.Request) {
|
||||||
dialer := net.Dialer{Timeout: h.relayTimeout}
|
dialer := net.Dialer{}
|
||||||
destinationConn, err := dialer.DialContext(h.ctx, "tcp", request.Host)
|
destinationConn, err := dialer.DialContext(h.ctx, "tcp", request.Host)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
http.Error(responseWriter, err.Error(), http.StatusServiceUnavailable)
|
http.Error(responseWriter, err.Error(), http.StatusServiceUnavailable)
|
||||||
|
|||||||
@@ -3,113 +3,91 @@ package httpproxy
|
|||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
"net/http"
|
|
||||||
"sync"
|
"sync"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/qdm12/gluetun/internal/constants"
|
||||||
|
"github.com/qdm12/gluetun/internal/models"
|
||||||
"github.com/qdm12/gluetun/internal/settings"
|
"github.com/qdm12/gluetun/internal/settings"
|
||||||
"github.com/qdm12/golibs/logging"
|
"github.com/qdm12/golibs/logging"
|
||||||
)
|
)
|
||||||
|
|
||||||
type Looper interface {
|
type Looper interface {
|
||||||
Run(ctx context.Context, wg *sync.WaitGroup)
|
Run(ctx context.Context, wg *sync.WaitGroup)
|
||||||
Restart()
|
SetStatus(status models.LoopStatus) (outcome string, err error)
|
||||||
Start()
|
GetStatus() (status models.LoopStatus)
|
||||||
Stop()
|
|
||||||
GetSettings() (settings settings.HTTPProxy)
|
GetSettings() (settings settings.HTTPProxy)
|
||||||
SetSettings(settings settings.HTTPProxy)
|
SetSettings(settings settings.HTTPProxy) (outcome string)
|
||||||
}
|
}
|
||||||
|
|
||||||
type looper struct {
|
type looper struct {
|
||||||
client *http.Client
|
state state
|
||||||
settings settings.HTTPProxy
|
// Other objects
|
||||||
settingsMutex sync.RWMutex
|
|
||||||
logger logging.Logger
|
logger logging.Logger
|
||||||
restart chan struct{}
|
// Internal channels and locks
|
||||||
|
loopLock sync.Mutex
|
||||||
|
running chan models.LoopStatus
|
||||||
|
stop, stopped chan struct{}
|
||||||
start chan struct{}
|
start chan struct{}
|
||||||
stop chan struct{}
|
backoffTime time.Duration
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewLooper(client *http.Client, logger logging.Logger,
|
const defaultBackoffTime = 10 * time.Second
|
||||||
settings settings.HTTPProxy) Looper {
|
|
||||||
|
func NewLooper(logger logging.Logger, settings settings.HTTPProxy) Looper {
|
||||||
return &looper{
|
return &looper{
|
||||||
client: client,
|
state: state{
|
||||||
|
status: constants.Stopped,
|
||||||
settings: settings,
|
settings: settings,
|
||||||
|
},
|
||||||
logger: logger.WithPrefix("http proxy: "),
|
logger: logger.WithPrefix("http proxy: "),
|
||||||
restart: make(chan struct{}),
|
|
||||||
start: make(chan struct{}),
|
start: make(chan struct{}),
|
||||||
|
running: make(chan models.LoopStatus),
|
||||||
stop: make(chan struct{}),
|
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) {
|
func (l *looper) Run(ctx context.Context, wg *sync.WaitGroup) {
|
||||||
defer wg.Done()
|
defer wg.Done()
|
||||||
waitForStart := true
|
|
||||||
for waitForStart {
|
crashed := false
|
||||||
|
|
||||||
|
if l.GetSettings().Enabled {
|
||||||
|
go func() {
|
||||||
|
_, _ = l.SetStatus(constants.Running)
|
||||||
|
}()
|
||||||
|
}
|
||||||
|
|
||||||
select {
|
select {
|
||||||
case <-l.stop:
|
|
||||||
l.logger.Info("not started yet")
|
|
||||||
case <-l.start:
|
case <-l.start:
|
||||||
waitForStart = false
|
|
||||||
case <-l.restart:
|
|
||||||
waitForStart = false
|
|
||||||
case <-ctx.Done():
|
case <-ctx.Done():
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
|
||||||
defer l.logger.Warn("loop exited")
|
defer l.logger.Warn("loop exited")
|
||||||
|
|
||||||
for ctx.Err() == nil {
|
for ctx.Err() == nil {
|
||||||
for !l.isEnabled() {
|
runCtx, runCancel := context.WithCancel(ctx)
|
||||||
// wait for a signal to re-enable
|
|
||||||
select {
|
|
||||||
case <-l.stop:
|
|
||||||
l.logger.Info("already disabled")
|
|
||||||
case <-l.restart:
|
|
||||||
l.setEnabled(true)
|
|
||||||
case <-l.start:
|
|
||||||
l.setEnabled(true)
|
|
||||||
case <-ctx.Done():
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
settings := l.GetSettings()
|
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, l.client, settings.Stealth, settings.Log, settings.User, settings.Password)
|
|
||||||
|
|
||||||
runCtx, runCancel := context.WithCancel(context.Background())
|
|
||||||
runWg := &sync.WaitGroup{}
|
runWg := &sync.WaitGroup{}
|
||||||
runWg.Add(1)
|
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
|
stayHere := true
|
||||||
for stayHere {
|
for stayHere {
|
||||||
@@ -119,21 +97,38 @@ func (l *looper) Run(ctx context.Context, wg *sync.WaitGroup) {
|
|||||||
runCancel()
|
runCancel()
|
||||||
runWg.Wait()
|
runWg.Wait()
|
||||||
return
|
return
|
||||||
case <-l.restart: // triggered restart
|
case <-l.start:
|
||||||
l.logger.Info("restarting")
|
l.logger.Info("starting")
|
||||||
runCancel()
|
runCancel()
|
||||||
runWg.Wait()
|
runWg.Wait()
|
||||||
stayHere = false
|
stayHere = false
|
||||||
case <-l.start:
|
|
||||||
l.logger.Info("already started")
|
|
||||||
case <-l.stop:
|
case <-l.stop:
|
||||||
l.logger.Info("stopping")
|
l.logger.Info("stopping")
|
||||||
runCancel()
|
runCancel()
|
||||||
runWg.Wait()
|
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
|
stayHere = false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
runCancel() // repetition for linter only
|
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 {
|
type Server interface {
|
||||||
Run(ctx context.Context, wg *sync.WaitGroup)
|
Run(ctx context.Context, wg *sync.WaitGroup, errorCh chan<- error)
|
||||||
}
|
}
|
||||||
|
|
||||||
type server struct {
|
type server struct {
|
||||||
@@ -20,25 +20,24 @@ type server struct {
|
|||||||
internalWG *sync.WaitGroup
|
internalWG *sync.WaitGroup
|
||||||
}
|
}
|
||||||
|
|
||||||
func New(ctx context.Context, address string,
|
func New(ctx context.Context, address string, logger logging.Logger,
|
||||||
logger logging.Logger, client *http.Client,
|
|
||||||
stealth, verbose bool, username, password string) Server {
|
stealth, verbose bool, username, password string) Server {
|
||||||
wg := &sync.WaitGroup{}
|
wg := &sync.WaitGroup{}
|
||||||
return &server{
|
return &server{
|
||||||
address: address,
|
address: address,
|
||||||
handler: newHandler(ctx, wg, client, logger, stealth, verbose, username, password),
|
handler: newHandler(ctx, wg, logger, stealth, verbose, username, password),
|
||||||
logger: logger,
|
logger: logger,
|
||||||
internalWG: wg,
|
internalWG: wg,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
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()
|
defer wg.Done()
|
||||||
server := http.Server{Addr: s.address, Handler: s.handler}
|
server := http.Server{Addr: s.address, Handler: s.handler}
|
||||||
go func() {
|
go func() {
|
||||||
<-ctx.Done()
|
<-ctx.Done()
|
||||||
s.logger.Warn("context canceled: exiting loop")
|
s.logger.Warn("shutting down server")
|
||||||
defer s.logger.Warn("loop exited")
|
defer s.logger.Warn("server shut down")
|
||||||
const shutdownGraceDuration = 2 * time.Second
|
const shutdownGraceDuration = 2 * time.Second
|
||||||
shutdownCtx, cancel := context.WithTimeout(context.Background(), shutdownGraceDuration)
|
shutdownCtx, cancel := context.WithTimeout(context.Background(), shutdownGraceDuration)
|
||||||
defer cancel()
|
defer cancel()
|
||||||
@@ -48,8 +47,8 @@ func (s *server) Run(ctx context.Context, wg *sync.WaitGroup) {
|
|||||||
}()
|
}()
|
||||||
s.logger.Info("listening on %s", s.address)
|
s.logger.Info("listening on %s", s.address)
|
||||||
err := server.ListenAndServe()
|
err := server.ListenAndServe()
|
||||||
if err != nil && ctx.Err() != context.Canceled {
|
if err != nil && ctx.Err() == nil {
|
||||||
s.logger.Error(err)
|
errorCh <- err
|
||||||
}
|
}
|
||||||
s.internalWG.Wait()
|
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"
|
"github.com/qdm12/golibs/logging"
|
||||||
)
|
)
|
||||||
|
|
||||||
//nolint:lll
|
|
||||||
var regularExpressions = struct { //nolint:gochecknoglobals
|
var regularExpressions = struct { //nolint:gochecknoglobals
|
||||||
unboundPrefix *regexp.Regexp
|
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) {
|
func PostProcessLine(s string) (filtered string, level logging.Level) {
|
||||||
|
|||||||
@@ -8,8 +8,6 @@ import (
|
|||||||
type (
|
type (
|
||||||
// VPNDevice is the device name used to tunnel using Openvpn.
|
// VPNDevice is the device name used to tunnel using Openvpn.
|
||||||
VPNDevice string
|
VPNDevice string
|
||||||
// DNSProvider is a DNS over TLS server provider name.
|
|
||||||
DNSProvider string
|
|
||||||
// DNSHost is the DNS host to use for TLS validation.
|
// DNSHost is the DNS host to use for TLS validation.
|
||||||
DNSHost string
|
DNSHost string
|
||||||
// URL is an HTTP(s) URL address.
|
// URL is an HTTP(s) URL address.
|
||||||
@@ -20,8 +18,14 @@ type (
|
|||||||
VPNProvider string
|
VPNProvider string
|
||||||
// NetworkProtocol contains the network protocol to be used to communicate with the VPN servers.
|
// NetworkProtocol contains the network protocol to be used to communicate with the VPN servers.
|
||||||
NetworkProtocol string
|
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) {
|
func marshalJSONString(s string) (data []byte, err error) {
|
||||||
return []byte(fmt.Sprintf("%q", s)), nil
|
return []byte(fmt.Sprintf("%q", s)), nil
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,5 +3,5 @@ package models
|
|||||||
type BuildInformation struct {
|
type BuildInformation struct {
|
||||||
Version string `json:"version"`
|
Version string `json:"version"`
|
||||||
Commit string `json:"commit"`
|
Commit string `json:"commit"`
|
||||||
BuildDate string `json:"buildDate"`
|
BuildDate string `json:"build_date"`
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,5 +7,6 @@ type DNSProviderData struct {
|
|||||||
IPs []net.IP
|
IPs []net.IP
|
||||||
SupportsTLS bool
|
SupportsTLS bool
|
||||||
SupportsIPv6 bool
|
SupportsIPv6 bool
|
||||||
|
SupportsDNSSec bool
|
||||||
Host DNSHost
|
Host DNSHost
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,8 @@
|
|||||||
package models
|
package models
|
||||||
|
|
||||||
import "net"
|
import (
|
||||||
|
"net"
|
||||||
|
)
|
||||||
|
|
||||||
type OpenVPNConnection struct {
|
type OpenVPNConnection struct {
|
||||||
IP net.IP
|
IP net.IP
|
||||||
|
|||||||
@@ -9,15 +9,15 @@ import (
|
|||||||
// ProviderSettings contains settings specific to a VPN provider.
|
// ProviderSettings contains settings specific to a VPN provider.
|
||||||
type ProviderSettings struct {
|
type ProviderSettings struct {
|
||||||
Name VPNProvider `json:"name"`
|
Name VPNProvider `json:"name"`
|
||||||
ServerSelection ServerSelection `json:"serverSelection"`
|
ServerSelection ServerSelection `json:"server_selection"`
|
||||||
ExtraConfigOptions ExtraConfigOptions `json:"extraConfig"`
|
ExtraConfigOptions ExtraConfigOptions `json:"extra_config"`
|
||||||
PortForwarding PortForwarding `json:"portForwarding"`
|
PortForwarding PortForwarding `json:"port_forwarding"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type ServerSelection struct {
|
type ServerSelection struct {
|
||||||
// Common
|
// Common
|
||||||
Protocol NetworkProtocol `json:"networkProtocol"`
|
Protocol NetworkProtocol `json:"network_protocol"`
|
||||||
TargetIP net.IP `json:"targetIP,omitempty"`
|
TargetIP net.IP `json:"target_ip,omitempty"`
|
||||||
|
|
||||||
// Cyberghost, PIA, Surfshark, Windscribe, Vyprvpn, NordVPN
|
// Cyberghost, PIA, Surfshark, Windscribe, Vyprvpn, NordVPN
|
||||||
Regions []string `json:"regions"`
|
Regions []string `json:"regions"`
|
||||||
@@ -34,20 +34,20 @@ type ServerSelection struct {
|
|||||||
Owned bool `json:"owned"`
|
Owned bool `json:"owned"`
|
||||||
|
|
||||||
// Mullvad, Windscribe
|
// Mullvad, Windscribe
|
||||||
CustomPort uint16 `json:"customPort"`
|
CustomPort uint16 `json:"custom_port"`
|
||||||
|
|
||||||
// NordVPN
|
// NordVPN
|
||||||
Numbers []uint16 `json:"numbers"`
|
Numbers []uint16 `json:"numbers"`
|
||||||
|
|
||||||
// PIA
|
// PIA
|
||||||
EncryptionPreset string `json:"encryptionPreset"`
|
EncryptionPreset string `json:"encryption_preset"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type ExtraConfigOptions struct {
|
type ExtraConfigOptions struct {
|
||||||
ClientCertificate string `json:"-"` // Cyberghost
|
ClientCertificate string `json:"-"` // Cyberghost
|
||||||
ClientKey string `json:"-"` // Cyberghost
|
ClientKey string `json:"-"` // Cyberghost
|
||||||
EncryptionPreset string `json:"encryptionPreset"` // PIA
|
EncryptionPreset string `json:"encryption_preset"` // PIA
|
||||||
OpenVPNIPv6 bool `json:"openvpnIPv6"` // Mullvad
|
OpenVPNIPv6 bool `json:"openvpn_ipv6"` // Mullvad
|
||||||
}
|
}
|
||||||
|
|
||||||
// PortForwarding contains settings for port forwarding.
|
// PortForwarding contains settings for port forwarding.
|
||||||
@@ -134,8 +134,7 @@ func (p *ProviderSettings) String() string {
|
|||||||
)
|
)
|
||||||
case "privado":
|
case "privado":
|
||||||
settingsList = append(settingsList,
|
settingsList = append(settingsList,
|
||||||
"Cities: "+commaJoin(p.ServerSelection.Cities),
|
"Hostnames: "+commaJoin(p.ServerSelection.Hostnames),
|
||||||
"Server numbers: "+commaJoin(numbers),
|
|
||||||
)
|
)
|
||||||
default:
|
default:
|
||||||
settingsList = append(settingsList,
|
settingsList = append(settingsList,
|
||||||
|
|||||||
@@ -96,15 +96,15 @@ func (s *NordvpnServer) String() string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type PurevpnServer struct {
|
type PurevpnServer struct {
|
||||||
Region string `json:"region"`
|
|
||||||
Country string `json:"country"`
|
Country string `json:"country"`
|
||||||
|
Region string `json:"region"`
|
||||||
City string `json:"city"`
|
City string `json:"city"`
|
||||||
IPs []net.IP `json:"ips"`
|
IPs []net.IP `json:"ips"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *PurevpnServer) String() string {
|
func (s *PurevpnServer) String() string {
|
||||||
return fmt.Sprintf("{Region: %q, Country: %q, City: %q, IPs: %s}",
|
return fmt.Sprintf("{Country: %q, Region: %q, City: %q, IPs: %s}",
|
||||||
s.Region, s.Country, s.City, goStringifyIPs(s.IPs))
|
s.Country, s.Region, s.City, goStringifyIPs(s.IPs))
|
||||||
}
|
}
|
||||||
|
|
||||||
type PrivadoServer struct {
|
type PrivadoServer struct {
|
||||||
|
|||||||
@@ -1,31 +1,68 @@
|
|||||||
package openvpn
|
package openvpn
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"io/ioutil"
|
||||||
|
"os"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/qdm12/gluetun/internal/constants"
|
"github.com/qdm12/gluetun/internal/constants"
|
||||||
"github.com/qdm12/golibs/files"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// WriteAuthFile writes the OpenVPN auth file to disk with the right permissions.
|
// WriteAuthFile writes the OpenVPN auth file to disk with the right permissions.
|
||||||
func (c *configurator) WriteAuthFile(user, password string, uid, gid int) error {
|
func (c *configurator) WriteAuthFile(user, password string, puid, pgid int) error {
|
||||||
exists, err := c.fileManager.FileExists(string(constants.OpenVPNAuthConf))
|
const filepath = string(constants.OpenVPNAuthConf)
|
||||||
if err != nil {
|
file, err := c.os.OpenFile(filepath, os.O_RDONLY, 0)
|
||||||
|
|
||||||
|
if err != nil && !os.IsNotExist(err) {
|
||||||
return 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 {
|
if err != nil {
|
||||||
return err
|
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()
|
||||||
|
}
|
||||||
|
|
||||||
|
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")
|
lines := strings.Split(string(data), "\n")
|
||||||
if len(lines) > 1 && lines[0] == user && lines[1] == password {
|
if len(lines) > 1 && lines[0] == user && lines[1] == password {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
c.logger.Info("username and password changed", constants.OpenVPNAuthConf)
|
|
||||||
|
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
|
||||||
}
|
}
|
||||||
return c.fileManager.WriteLinesToFile(
|
_, err = file.WriteString(user + "\n" + password)
|
||||||
string(constants.OpenVPNAuthConf),
|
if err != nil {
|
||||||
[]string{user, password},
|
_ = file.Close()
|
||||||
files.Ownership(uid, gid),
|
return err
|
||||||
files.Permissions(constants.UserReadPermission))
|
}
|
||||||
|
err = file.Chown(puid, pgid)
|
||||||
|
if err != nil {
|
||||||
|
_ = file.Close()
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return file.Close()
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import (
|
|||||||
"context"
|
"context"
|
||||||
"net"
|
"net"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
@@ -14,32 +15,28 @@ import (
|
|||||||
"github.com/qdm12/gluetun/internal/routing"
|
"github.com/qdm12/gluetun/internal/routing"
|
||||||
"github.com/qdm12/gluetun/internal/settings"
|
"github.com/qdm12/gluetun/internal/settings"
|
||||||
"github.com/qdm12/golibs/command"
|
"github.com/qdm12/golibs/command"
|
||||||
"github.com/qdm12/golibs/files"
|
|
||||||
"github.com/qdm12/golibs/logging"
|
"github.com/qdm12/golibs/logging"
|
||||||
|
"github.com/qdm12/golibs/os"
|
||||||
)
|
)
|
||||||
|
|
||||||
type Looper interface {
|
type Looper interface {
|
||||||
Run(ctx context.Context, wg *sync.WaitGroup)
|
Run(ctx context.Context, wg *sync.WaitGroup)
|
||||||
Restart()
|
GetStatus() (status models.LoopStatus)
|
||||||
PortForward(vpnGatewayIP net.IP)
|
SetStatus(status models.LoopStatus) (outcome string, err error)
|
||||||
GetSettings() (settings settings.OpenVPN)
|
GetSettings() (settings settings.OpenVPN)
|
||||||
SetSettings(settings settings.OpenVPN)
|
SetSettings(settings settings.OpenVPN) (outcome string)
|
||||||
GetPortForwarded() (portForwarded uint16)
|
GetServers() (servers models.AllServers)
|
||||||
SetAllServers(allServers models.AllServers)
|
SetServers(servers models.AllServers)
|
||||||
|
GetPortForwarded() (port uint16)
|
||||||
|
PortForward(vpnGatewayIP net.IP)
|
||||||
}
|
}
|
||||||
|
|
||||||
type looper struct {
|
type looper struct {
|
||||||
// Variable parameters
|
state state
|
||||||
provider models.VPNProvider
|
|
||||||
settings settings.OpenVPN
|
|
||||||
settingsMutex sync.RWMutex
|
|
||||||
portForwarded uint16
|
|
||||||
portForwardedMutex sync.RWMutex
|
|
||||||
allServers models.AllServers
|
|
||||||
allServersMutex sync.RWMutex
|
|
||||||
// Fixed parameters
|
// Fixed parameters
|
||||||
uid int
|
username string
|
||||||
gid int
|
puid int
|
||||||
|
pgid int
|
||||||
// Configurators
|
// Configurators
|
||||||
conf Configurator
|
conf Configurator
|
||||||
fw firewall.Configurator
|
fw firewall.Configurator
|
||||||
@@ -47,105 +44,108 @@ type looper struct {
|
|||||||
// Other objects
|
// Other objects
|
||||||
logger, pfLogger logging.Logger
|
logger, pfLogger logging.Logger
|
||||||
client *http.Client
|
client *http.Client
|
||||||
fileManager files.FileManager
|
openFile os.OpenFileFunc
|
||||||
streamMerger command.StreamMerger
|
streamMerger command.StreamMerger
|
||||||
cancel context.CancelFunc
|
cancel context.CancelFunc
|
||||||
// Internal channels
|
// Internal channels and locks
|
||||||
restart chan struct{}
|
loopLock sync.Mutex
|
||||||
|
running chan models.LoopStatus
|
||||||
|
stop, stopped chan struct{}
|
||||||
|
start chan struct{}
|
||||||
portForwardSignals chan net.IP
|
portForwardSignals chan net.IP
|
||||||
|
crashed bool
|
||||||
|
backoffTime time.Duration
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewLooper(provider models.VPNProvider, settings settings.OpenVPN,
|
const defaultBackoffTime = 15 * time.Second
|
||||||
uid, gid int, allServers models.AllServers,
|
|
||||||
|
func NewLooper(settings settings.OpenVPN,
|
||||||
|
username string, puid, pgid int, allServers models.AllServers,
|
||||||
conf Configurator, fw firewall.Configurator, routing routing.Routing,
|
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 {
|
streamMerger command.StreamMerger, cancel context.CancelFunc) Looper {
|
||||||
return &looper{
|
return &looper{
|
||||||
provider: provider,
|
state: state{
|
||||||
|
status: constants.Stopped,
|
||||||
settings: settings,
|
settings: settings,
|
||||||
uid: uid,
|
|
||||||
gid: gid,
|
|
||||||
allServers: allServers,
|
allServers: allServers,
|
||||||
|
},
|
||||||
|
username: username,
|
||||||
|
puid: puid,
|
||||||
|
pgid: pgid,
|
||||||
conf: conf,
|
conf: conf,
|
||||||
fw: fw,
|
fw: fw,
|
||||||
routing: routing,
|
routing: routing,
|
||||||
logger: logger.WithPrefix("openvpn: "),
|
logger: logger.WithPrefix("openvpn: "),
|
||||||
pfLogger: logger.WithPrefix("port forwarding: "),
|
pfLogger: logger.WithPrefix("port forwarding: "),
|
||||||
client: client,
|
client: client,
|
||||||
fileManager: fileManager,
|
openFile: openFile,
|
||||||
streamMerger: streamMerger,
|
streamMerger: streamMerger,
|
||||||
cancel: cancel,
|
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),
|
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) PortForward(vpnGateway net.IP) { l.portForwardSignals <- vpnGateway }
|
||||||
|
|
||||||
func (l *looper) GetSettings() (settings settings.OpenVPN) {
|
func (l *looper) signalCrashedStatus() {
|
||||||
l.settingsMutex.RLock()
|
if !l.crashed {
|
||||||
defer l.settingsMutex.RUnlock()
|
l.crashed = true
|
||||||
return l.settings
|
l.running <- constants.Crashed
|
||||||
}
|
}
|
||||||
|
|
||||||
func (l *looper) SetSettings(settings settings.OpenVPN) {
|
|
||||||
l.settingsMutex.Lock()
|
|
||||||
defer l.settingsMutex.Unlock()
|
|
||||||
l.settings = settings
|
|
||||||
}
|
|
||||||
|
|
||||||
func (l *looper) SetAllServers(allServers models.AllServers) {
|
|
||||||
l.allServersMutex.Lock()
|
|
||||||
defer l.allServersMutex.Unlock()
|
|
||||||
l.allServers = allServers
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (l *looper) Run(ctx context.Context, wg *sync.WaitGroup) {
|
func (l *looper) Run(ctx context.Context, wg *sync.WaitGroup) {
|
||||||
defer wg.Done()
|
defer wg.Done()
|
||||||
select {
|
select {
|
||||||
case <-l.restart:
|
case <-l.start:
|
||||||
case <-ctx.Done():
|
case <-ctx.Done():
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
defer l.logger.Warn("loop exited")
|
defer l.logger.Warn("loop exited")
|
||||||
|
|
||||||
for ctx.Err() == nil {
|
for ctx.Err() == nil {
|
||||||
settings := l.GetSettings()
|
settings, allServers := l.state.getSettingsAndServers()
|
||||||
l.allServersMutex.RLock()
|
providerConf := provider.New(settings.Provider.Name, allServers, time.Now)
|
||||||
providerConf := provider.New(l.provider, l.allServers, time.Now)
|
|
||||||
l.allServersMutex.RUnlock()
|
|
||||||
connection, err := providerConf.GetOpenVPNConnection(settings.Provider.ServerSelection)
|
connection, err := providerConf.GetOpenVPNConnection(settings.Provider.ServerSelection)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
l.logger.Error(err)
|
l.logger.Error(err)
|
||||||
|
l.signalCrashedStatus()
|
||||||
l.cancel()
|
l.cancel()
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
lines := providerConf.BuildConf(
|
lines := providerConf.BuildConf(
|
||||||
connection,
|
connection,
|
||||||
settings.Verbosity,
|
settings.Verbosity,
|
||||||
l.uid,
|
l.username,
|
||||||
l.gid,
|
|
||||||
settings.Root,
|
settings.Root,
|
||||||
settings.Cipher,
|
settings.Cipher,
|
||||||
settings.Auth,
|
settings.Auth,
|
||||||
settings.Provider.ExtraConfigOptions,
|
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.logger.Error(err)
|
||||||
|
l.signalCrashedStatus()
|
||||||
l.cancel()
|
l.cancel()
|
||||||
return
|
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.logger.Error(err)
|
||||||
|
l.signalCrashedStatus()
|
||||||
l.cancel()
|
l.cancel()
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := l.fw.SetVPNConnection(ctx, connection); err != nil {
|
if err := l.fw.SetVPNConnection(ctx, connection); err != nil {
|
||||||
l.logger.Error(err)
|
l.logger.Error(err)
|
||||||
|
l.signalCrashedStatus()
|
||||||
l.cancel()
|
l.cancel()
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -155,6 +155,7 @@ func (l *looper) Run(ctx context.Context, wg *sync.WaitGroup) {
|
|||||||
stream, waitFn, err := l.conf.Start(openvpnCtx)
|
stream, waitFn, err := l.conf.Start(openvpnCtx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
openvpnCancel()
|
openvpnCancel()
|
||||||
|
l.signalCrashedStatus()
|
||||||
l.logAndWait(ctx, err)
|
l.logAndWait(ctx, err)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
@@ -179,6 +180,17 @@ func (l *looper) Run(ctx context.Context, wg *sync.WaitGroup) {
|
|||||||
err := waitFn() // blocking
|
err := waitFn() // blocking
|
||||||
waitError <- err
|
waitError <- 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 {
|
select {
|
||||||
case <-ctx.Done():
|
case <-ctx.Done():
|
||||||
l.logger.Warn("context canceled: exiting loop")
|
l.logger.Warn("context canceled: exiting loop")
|
||||||
@@ -186,24 +198,32 @@ func (l *looper) Run(ctx context.Context, wg *sync.WaitGroup) {
|
|||||||
<-waitError
|
<-waitError
|
||||||
close(waitError)
|
close(waitError)
|
||||||
return
|
return
|
||||||
case <-l.restart: // triggered restart
|
case <-l.stop:
|
||||||
l.logger.Info("restarting")
|
l.logger.Info("stopping")
|
||||||
openvpnCancel()
|
openvpnCancel()
|
||||||
<-waitError
|
<-waitError
|
||||||
close(waitError)
|
l.stopped <- struct{}{}
|
||||||
|
case <-l.start:
|
||||||
|
l.logger.Info("starting")
|
||||||
|
stayHere = false
|
||||||
case err := <-waitError: // unexpected error
|
case err := <-waitError: // unexpected error
|
||||||
openvpnCancel()
|
openvpnCancel()
|
||||||
close(waitError)
|
l.state.setStatusWithLock(constants.Crashed)
|
||||||
l.logAndWait(ctx, err)
|
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) {
|
func (l *looper) logAndWait(ctx context.Context, err error) {
|
||||||
l.logger.Error(err)
|
l.logger.Error(err)
|
||||||
const waitTime = 30 * time.Second
|
l.logger.Info("retrying in %s", l.backoffTime)
|
||||||
l.logger.Info("retrying in %s", waitTime)
|
timer := time.NewTimer(l.backoffTime)
|
||||||
timer := time.NewTimer(waitTime)
|
l.backoffTime *= 2
|
||||||
select {
|
select {
|
||||||
case <-timer.C:
|
case <-timer.C:
|
||||||
case <-ctx.Done():
|
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,
|
func (l *looper) portForward(ctx context.Context, wg *sync.WaitGroup,
|
||||||
providerConf provider.Provider, client *http.Client, gateway net.IP) {
|
providerConf provider.Provider, client *http.Client, gateway net.IP) {
|
||||||
defer wg.Done()
|
defer wg.Done()
|
||||||
settings := l.GetSettings()
|
l.state.portForwardedMu.RLock()
|
||||||
|
settings := l.state.settings
|
||||||
|
l.state.portForwardedMu.RUnlock()
|
||||||
if !settings.Provider.PortForwarding.Enabled {
|
if !settings.Provider.PortForwarding.Enabled {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
syncState := func(port uint16) (pfFilepath models.Filepath) {
|
syncState := func(port uint16) (pfFilepath models.Filepath) {
|
||||||
l.portForwardedMutex.Lock()
|
l.state.portForwardedMu.Lock()
|
||||||
l.portForwarded = port
|
defer l.state.portForwardedMu.Unlock()
|
||||||
l.portForwardedMutex.Unlock()
|
l.state.portForwarded = port
|
||||||
settings := l.GetSettings()
|
l.state.settingsMu.RLock()
|
||||||
|
defer l.state.settingsMu.RUnlock()
|
||||||
return settings.Provider.PortForwarding.Filepath
|
return settings.Provider.PortForwarding.Filepath
|
||||||
}
|
}
|
||||||
providerConf.PortForward(ctx,
|
providerConf.PortForward(ctx,
|
||||||
client, l.fileManager, l.pfLogger,
|
client, l.openFile, l.pfLogger,
|
||||||
gateway, l.fw, syncState)
|
gateway, l.fw, syncState)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (l *looper) GetPortForwarded() (portForwarded uint16) {
|
func writeOpenvpnConf(lines []string, openFile os.OpenFileFunc) error {
|
||||||
l.portForwardedMutex.RLock()
|
const filepath = string(constants.OpenVPNConf)
|
||||||
defer l.portForwardedMutex.RUnlock()
|
file, err := openFile(filepath, os.O_CREATE|os.O_TRUNC|os.O_WRONLY, 0644)
|
||||||
return l.portForwarded
|
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 (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"io"
|
"io"
|
||||||
"os"
|
|
||||||
|
|
||||||
|
"github.com/qdm12/gluetun/internal/unix"
|
||||||
"github.com/qdm12/golibs/command"
|
"github.com/qdm12/golibs/command"
|
||||||
"github.com/qdm12/golibs/files"
|
|
||||||
"github.com/qdm12/golibs/logging"
|
"github.com/qdm12/golibs/logging"
|
||||||
"golang.org/x/sys/unix"
|
"github.com/qdm12/golibs/os"
|
||||||
)
|
)
|
||||||
|
|
||||||
type Configurator interface {
|
type Configurator interface {
|
||||||
Version(ctx context.Context) (string, error)
|
Version(ctx context.Context) (string, error)
|
||||||
WriteAuthFile(user, password string, uid, gid int) error
|
WriteAuthFile(user, password string, puid, pgid int) error
|
||||||
CheckTUN() error
|
CheckTUN() error
|
||||||
CreateTUN() error
|
CreateTUN() error
|
||||||
Start(ctx context.Context) (stdout io.ReadCloser, waitFn func() error, err error)
|
Start(ctx context.Context) (stdout io.ReadCloser, waitFn func() error, err error)
|
||||||
}
|
}
|
||||||
|
|
||||||
type configurator struct {
|
type configurator struct {
|
||||||
fileManager files.FileManager
|
|
||||||
logger logging.Logger
|
logger logging.Logger
|
||||||
commander command.Commander
|
commander command.Commander
|
||||||
openFile func(name string, flag int, perm os.FileMode) (*os.File, error)
|
os os.OS
|
||||||
mkDev func(major uint32, minor uint32) uint64
|
unix unix.Unix
|
||||||
mkNod func(path string, mode uint32, dev int) error
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewConfigurator(logger logging.Logger, fileManager files.FileManager) Configurator {
|
func NewConfigurator(logger logging.Logger, os os.OS, unix unix.Unix) Configurator {
|
||||||
return &configurator{
|
return &configurator{
|
||||||
fileManager: fileManager,
|
|
||||||
logger: logger.WithPrefix("openvpn configurator: "),
|
logger: logger.WithPrefix("openvpn configurator: "),
|
||||||
commander: command.NewCommander(),
|
commander: command.NewCommander(),
|
||||||
openFile: os.OpenFile,
|
os: os,
|
||||||
mkDev: unix.Mkdev,
|
unix: unix,
|
||||||
mkNod: unix.Mknod,
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
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"
|
"os"
|
||||||
|
|
||||||
"github.com/qdm12/gluetun/internal/constants"
|
"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.
|
// CheckTUN checks the tunnel device is present and accessible.
|
||||||
func (c *configurator) CheckTUN() error {
|
func (c *configurator) CheckTUN() error {
|
||||||
c.logger.Info("checking for device %s", constants.TunnelDevice)
|
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 {
|
if err != nil {
|
||||||
return fmt.Errorf("TUN device is not available: %w", err)
|
return fmt.Errorf("TUN device is not available: %w", err)
|
||||||
}
|
}
|
||||||
@@ -23,19 +23,29 @@ func (c *configurator) CheckTUN() error {
|
|||||||
|
|
||||||
func (c *configurator) CreateTUN() error {
|
func (c *configurator) CreateTUN() error {
|
||||||
c.logger.Info("creating %s", constants.TunnelDevice)
|
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
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
const (
|
const (
|
||||||
major = 10
|
major = 10
|
||||||
minor = 200
|
minor = 200
|
||||||
)
|
)
|
||||||
dev := c.mkDev(major, minor)
|
dev := c.unix.Mkdev(major, minor)
|
||||||
if err := c.mkNod(string(constants.TunnelDevice), unix.S_IFCHR, int(dev)); err != nil {
|
if err := c.unix.Mknod(string(constants.TunnelDevice), unix.S_IFCHR, int(dev)); err != nil {
|
||||||
return err
|
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 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())
|
return p.envParams.GetCSVInPossibilities("REGION", constants.CyberghostRegionChoices())
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetCyberghostClientKey obtains the one line client key to use for openvpn from the
|
// GetCyberghostClientKey obtains the client key to use for openvpn
|
||||||
// environment variable CLIENT_KEY or from the file at /gluetun/client.key.
|
// from the secret file /run/secrets/openvpn_clientkey or from the file
|
||||||
|
// /gluetun/client.key.
|
||||||
func (p *reader) GetCyberghostClientKey() (clientKey string, err error) {
|
func (p *reader) GetCyberghostClientKey() (clientKey string, err error) {
|
||||||
clientKey, err = p.envParams.GetEnv("CLIENT_KEY", libparams.CaseSensitiveValue())
|
b, err := p.getFromFileOrSecretFile("OPENVPN_CLIENTKEY", string(constants.ClientKey))
|
||||||
if err != nil {
|
|
||||||
return "", err
|
|
||||||
} else if len(clientKey) > 0 {
|
|
||||||
return clientKey, nil
|
|
||||||
}
|
|
||||||
content, err := p.fileManager.ReadFile(string(constants.ClientKey))
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
return extractClientKey(content)
|
return extractClientKey(b)
|
||||||
}
|
}
|
||||||
|
|
||||||
func extractClientKey(b []byte) (key string, err error) {
|
func extractClientKey(b []byte) (key string, err error) {
|
||||||
@@ -52,14 +47,15 @@ func extractClientKey(b []byte) (key string, err error) {
|
|||||||
return s, nil
|
return s, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetCyberghostClientCertificate obtains the client certificate to use for openvpn from the
|
// GetCyberghostClientCertificate obtains the client certificate to use for openvpn
|
||||||
// file at /gluetun/client.crt.
|
// from the secret file /run/secrets/openvpn_clientcrt or from the file
|
||||||
|
// /gluetun/client.crt.
|
||||||
func (p *reader) GetCyberghostClientCertificate() (clientCertificate string, err error) {
|
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 {
|
if err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
return extractClientCertificate(content)
|
return extractClientCertificate(b)
|
||||||
}
|
}
|
||||||
|
|
||||||
func extractClientCertificate(b []byte) (certificate string, err error) {
|
func extractClientCertificate(b []byte) (certificate string, err error) {
|
||||||
|
|||||||
@@ -6,8 +6,7 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/qdm12/gluetun/internal/constants"
|
dns "github.com/qdm12/dns/pkg/unbound"
|
||||||
"github.com/qdm12/gluetun/internal/models"
|
|
||||||
libparams "github.com/qdm12/golibs/params"
|
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
|
// GetDNSOverTLSProviders obtains the DNS over TLS providers to use
|
||||||
// from the environment variable DOT_PROVIDERS.
|
// 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"))
|
s, err := r.envParams.GetEnv("DOT_PROVIDERS", libparams.Default("cloudflare"))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
for _, word := range strings.Split(s, ",") {
|
for _, provider := range strings.Split(s, ",") {
|
||||||
provider := models.DNSProvider(word)
|
_, ok := dns.GetProviderData(provider)
|
||||||
switch provider {
|
if !ok {
|
||||||
case constants.Cloudflare, constants.Google, constants.Quad9,
|
|
||||||
constants.Quadrant, constants.CleanBrowsing, constants.SecureDNS,
|
|
||||||
constants.LibreDNS:
|
|
||||||
providers = append(providers, provider)
|
|
||||||
default:
|
|
||||||
return nil, fmt.Errorf("DNS over TLS provider %q is not valid", provider)
|
return nil, fmt.Errorf("DNS over TLS provider %q is not valid", provider)
|
||||||
}
|
}
|
||||||
|
providers = append(providers, provider)
|
||||||
}
|
}
|
||||||
return providers, nil
|
return providers, nil
|
||||||
}
|
}
|
||||||
@@ -130,8 +125,8 @@ func (r *reader) GetDNSOverTLSPrivateAddresses() (privateAddresses []string, err
|
|||||||
return privateAddresses, nil
|
return privateAddresses, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetDNSOverTLSIPv6 obtains if Unbound should resolve ipv6 addresses using ipv6 DNS over TLS
|
// GetDNSOverTLSIPv6 obtains if Unbound should resolve ipv6 addresses using
|
||||||
// servers from the environment variable DOT_IPV6.
|
// ipv6 DNS over TLS from the environment variable DOT_IPV6.
|
||||||
func (r *reader) GetDNSOverTLSIPv6() (ipv6 bool, err error) {
|
func (r *reader) GetDNSOverTLSIPv6() (ipv6 bool, err error) {
|
||||||
return r.envParams.GetOnOff("DOT_IPV6", libparams.Default("off"))
|
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"))
|
return r.envParams.GetPort("HTTPPROXY_PORT", retroKeysOption, libparams.Default("8888"))
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetHTTPProxyUser obtains the HTTP proxy server user from the environment variable
|
// GetHTTPProxyUser obtains the HTTP proxy server user.
|
||||||
// HTTPPROXY_USER, and using TINYPROXY_USER and PROXY_USER as retro-compatibility names.
|
// 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) {
|
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"},
|
[]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
|
// GetHTTPProxyPassword obtains the HTTP proxy server password.
|
||||||
// HTTPPROXY_PASSWORD, and using TINYPROXY_PASSWORD and PROXY_PASSWORD as retro-compatibility names.
|
// 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) {
|
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"},
|
[]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
|
// 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.
|
// GetUser obtains the user to use to connect to the VPN servers.
|
||||||
func (r *reader) GetUser() (s string, err error) {
|
// It first tries to use the OPENVPN_USER environment variable (easier for the end user)
|
||||||
defer func() {
|
// and then tries to read from the secret file openvpn_user if nothing was found.
|
||||||
unsetenvErr := r.unsetEnv("USER")
|
func (r *reader) GetUser() (user string, err error) {
|
||||||
if err == nil {
|
const compulsory = true
|
||||||
err = unsetenvErr
|
return r.getFromEnvOrSecretFile("OPENVPN_USER", compulsory, []string{"USER"})
|
||||||
}
|
|
||||||
}()
|
|
||||||
return r.envParams.GetEnv("USER", libparams.CaseSensitiveValue(), libparams.Compulsory())
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetPassword obtains the password to use to connect to the VPN servers.
|
// GetPassword obtains the password to use to connect to the VPN servers.
|
||||||
func (r *reader) GetPassword(required bool) (s string, err error) {
|
// It first tries to use the OPENVPN_PASSWORD environment variable (easier for the end user)
|
||||||
defer func() {
|
// and then tries to read from the secret file openvpn_password if nothing was found.
|
||||||
unsetenvErr := r.unsetEnv("PASSWORD")
|
func (r *reader) GetPassword() (s string, err error) {
|
||||||
if err == nil {
|
const compulsory = true
|
||||||
err = unsetenvErr
|
return r.getFromEnvOrSecretFile("OPENVPN_PASSWORD", compulsory, []string{"PASSWORD"})
|
||||||
}
|
|
||||||
}()
|
|
||||||
options := []libparams.GetEnvSetter{libparams.CaseSensitiveValue()}
|
|
||||||
if required {
|
|
||||||
options = append(options, libparams.Compulsory())
|
|
||||||
}
|
|
||||||
return r.envParams.GetEnv("PASSWORD", options...)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetNetworkProtocol obtains the network protocol to use to connect to the
|
// GetNetworkProtocol obtains the network protocol to use to connect to the
|
||||||
|
|||||||
@@ -2,12 +2,11 @@ package params
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"net"
|
"net"
|
||||||
"os"
|
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/qdm12/gluetun/internal/models"
|
"github.com/qdm12/gluetun/internal/models"
|
||||||
"github.com/qdm12/golibs/files"
|
|
||||||
"github.com/qdm12/golibs/logging"
|
"github.com/qdm12/golibs/logging"
|
||||||
|
"github.com/qdm12/golibs/os"
|
||||||
libparams "github.com/qdm12/golibs/params"
|
libparams "github.com/qdm12/golibs/params"
|
||||||
"github.com/qdm12/golibs/verification"
|
"github.com/qdm12/golibs/verification"
|
||||||
)
|
)
|
||||||
@@ -18,7 +17,7 @@ type Reader interface {
|
|||||||
|
|
||||||
// DNS over TLS getters
|
// DNS over TLS getters
|
||||||
GetDNSOverTLS() (DNSOverTLS bool, err error)
|
GetDNSOverTLS() (DNSOverTLS bool, err error)
|
||||||
GetDNSOverTLSProviders() (providers []models.DNSProvider, err error)
|
GetDNSOverTLSProviders() (providers []string, err error)
|
||||||
GetDNSOverTLSCaching() (caching bool, err error)
|
GetDNSOverTLSCaching() (caching bool, err error)
|
||||||
GetDNSOverTLSVerbosity() (verbosityLevel uint8, err error)
|
GetDNSOverTLSVerbosity() (verbosityLevel uint8, err error)
|
||||||
GetDNSOverTLSVerbosityDetails() (verbosityDetailsLevel uint8, err error)
|
GetDNSOverTLSVerbosityDetails() (verbosityDetailsLevel uint8, err error)
|
||||||
@@ -34,10 +33,10 @@ type Reader interface {
|
|||||||
GetDNSKeepNameserver() (on bool, err error)
|
GetDNSKeepNameserver() (on bool, err error)
|
||||||
|
|
||||||
// System
|
// System
|
||||||
GetUID() (uid int, err error)
|
GetPUID() (puid int, err error)
|
||||||
GetGID() (gid int, err error)
|
GetPGID() (pgid int, err error)
|
||||||
GetTimezone() (timezone string, err error)
|
GetTimezone() (timezone string, err error)
|
||||||
GetIPStatusFilepath() (filepath models.Filepath, err error)
|
GetPublicIPFilepath() (filepath models.Filepath, err error)
|
||||||
|
|
||||||
// Firewall getters
|
// Firewall getters
|
||||||
GetFirewall() (enabled bool, err error)
|
GetFirewall() (enabled bool, err error)
|
||||||
@@ -48,7 +47,7 @@ type Reader interface {
|
|||||||
|
|
||||||
// VPN getters
|
// VPN getters
|
||||||
GetUser() (s string, err error)
|
GetUser() (s string, err error)
|
||||||
GetPassword(required bool) (s string, err error)
|
GetPassword() (s string, err error)
|
||||||
GetNetworkProtocol() (protocol models.NetworkProtocol, err error)
|
GetNetworkProtocol() (protocol models.NetworkProtocol, err error)
|
||||||
GetOpenVPNVerbosity() (verbosity int, err error)
|
GetOpenVPNVerbosity() (verbosity int, err error)
|
||||||
GetOpenVPNRoot() (root bool, err error)
|
GetOpenVPNRoot() (root bool, err error)
|
||||||
@@ -131,19 +130,17 @@ type reader struct {
|
|||||||
envParams libparams.EnvParams
|
envParams libparams.EnvParams
|
||||||
logger logging.Logger
|
logger logging.Logger
|
||||||
verifier verification.Verifier
|
verifier verification.Verifier
|
||||||
unsetEnv func(key string) error
|
os os.OS
|
||||||
fileManager files.FileManager
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Newreader returns a paramsReadeer object to read parameters from
|
// Newreader returns a paramsReadeer object to read parameters from
|
||||||
// environment variables.
|
// environment variables.
|
||||||
func NewReader(logger logging.Logger, fileManager files.FileManager) Reader {
|
func NewReader(logger logging.Logger, os os.OS) Reader {
|
||||||
return &reader{
|
return &reader{
|
||||||
envParams: libparams.NewEnvParams(),
|
envParams: libparams.NewEnvParams(),
|
||||||
logger: logger,
|
logger: logger,
|
||||||
verifier: verification.NewVerifier(),
|
verifier: verification.NewVerifier(),
|
||||||
unsetEnv: os.Unsetenv,
|
os: os,
|
||||||
fileManager: fileManager,
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -2,10 +2,13 @@ package params
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/qdm12/gluetun/internal/constants"
|
"github.com/qdm12/gluetun/internal/constants"
|
||||||
|
libparams "github.com/qdm12/golibs/params"
|
||||||
)
|
)
|
||||||
|
|
||||||
// GetPrivadoHostnames obtains the hostnames for the Privado server from the
|
// 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) {
|
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 (
|
import (
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/qdm12/gluetun/internal/models"
|
||||||
libparams "github.com/qdm12/golibs/params"
|
libparams "github.com/qdm12/golibs/params"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -15,3 +16,13 @@ func (r *reader) GetPublicIPPeriod() (period time.Duration, err error) {
|
|||||||
}
|
}
|
||||||
return time.ParseDuration(s)
|
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
|
return uint16(portUint64), err
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetShadowSocksPassword obtains the ShadowSocks server password from the environment variable
|
// GetShadowSocksPassword obtains the ShadowSocks server password.
|
||||||
// SHADOWSOCKS_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) {
|
func (r *reader) GetShadowSocksPassword() (password string, err error) {
|
||||||
defer func() {
|
const compulsory = false
|
||||||
unsetErr := r.unsetEnv("SHADOWSOCKS_PASSWORD")
|
return r.getFromEnvOrSecretFile("SHADOWSOCKS_PASSWORD", compulsory, nil)
|
||||||
if err == nil {
|
|
||||||
err = unsetErr
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
return r.envParams.GetEnv("SHADOWSOCKS_PASSWORD", libparams.CaseSensitiveValue())
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetShadowSocksMethod obtains the ShadowSocks method to use from the environment variable
|
// GetShadowSocksMethod obtains the ShadowSocks method to use from the environment variable
|
||||||
|
|||||||
@@ -1,29 +1,26 @@
|
|||||||
package params
|
package params
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/qdm12/gluetun/internal/models"
|
|
||||||
libparams "github.com/qdm12/golibs/params"
|
libparams "github.com/qdm12/golibs/params"
|
||||||
)
|
)
|
||||||
|
|
||||||
// GetUID obtains the user ID to use from the environment variable UID.
|
// GetPUID obtains the user ID to use from the environment variable PUID
|
||||||
func (r *reader) GetUID() (uid int, err error) {
|
// with retro compatible variable UID.
|
||||||
return r.envParams.GetEnvIntRange("UID", 0, 65535, libparams.Default("1000"))
|
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.
|
// GetGID obtains the group ID to use from the environment variable PGID
|
||||||
func (r *reader) GetGID() (gid int, err error) {
|
// with retro compatible variable PGID.
|
||||||
return r.envParams.GetEnvIntRange("GID", 0, 65535, libparams.Default("1000"))
|
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.
|
// GetTZ obtains the timezone from the environment variable TZ.
|
||||||
func (r *reader) GetTimezone() (timezone string, err error) {
|
func (r *reader) GetTimezone() (timezone string, err error) {
|
||||||
return r.envParams.GetEnv("TZ")
|
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
|
// 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) {
|
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
|
// 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/constants"
|
||||||
"github.com/qdm12/gluetun/internal/firewall"
|
"github.com/qdm12/gluetun/internal/firewall"
|
||||||
"github.com/qdm12/gluetun/internal/models"
|
"github.com/qdm12/gluetun/internal/models"
|
||||||
"github.com/qdm12/golibs/files"
|
|
||||||
"github.com/qdm12/golibs/logging"
|
"github.com/qdm12/golibs/logging"
|
||||||
|
"github.com/qdm12/golibs/os"
|
||||||
)
|
)
|
||||||
|
|
||||||
type cyberghost struct {
|
type cyberghost struct {
|
||||||
@@ -62,8 +62,8 @@ func (c *cyberghost) GetOpenVPNConnection(selection models.ServerSelection) (
|
|||||||
return pickRandomConnection(connections, c.randSource), nil
|
return pickRandomConnection(connections, c.randSource), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *cyberghost) BuildConf(connection models.OpenVPNConnection, verbosity,
|
func (c *cyberghost) BuildConf(connection models.OpenVPNConnection, verbosity int,
|
||||||
uid, gid int, root bool, cipher, auth string, extras models.ExtraConfigOptions) (lines []string) {
|
username string, root bool, cipher, auth string, extras models.ExtraConfigOptions) (lines []string) {
|
||||||
if len(cipher) == 0 {
|
if len(cipher) == 0 {
|
||||||
cipher = aes256cbc
|
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")
|
lines = append(lines, "ncp-ciphers AES-256-GCM:AES-256-CBC:AES-128-GCM")
|
||||||
}
|
}
|
||||||
if !root {
|
if !root {
|
||||||
lines = append(lines, "user nonrootuser")
|
lines = append(lines, "user "+username)
|
||||||
}
|
}
|
||||||
lines = append(lines, []string{
|
lines = append(lines, []string{
|
||||||
"<ca>",
|
"<ca>",
|
||||||
@@ -133,7 +133,7 @@ func (c *cyberghost) BuildConf(connection models.OpenVPNConnection, verbosity,
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (c *cyberghost) PortForward(ctx context.Context, client *http.Client,
|
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)) {
|
syncState func(port uint16) (pfFilepath models.Filepath)) {
|
||||||
panic("port forwarding is not supported for cyberghost")
|
panic("port forwarding is not supported for cyberghost")
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,8 +10,8 @@ import (
|
|||||||
"github.com/qdm12/gluetun/internal/constants"
|
"github.com/qdm12/gluetun/internal/constants"
|
||||||
"github.com/qdm12/gluetun/internal/firewall"
|
"github.com/qdm12/gluetun/internal/firewall"
|
||||||
"github.com/qdm12/gluetun/internal/models"
|
"github.com/qdm12/gluetun/internal/models"
|
||||||
"github.com/qdm12/golibs/files"
|
|
||||||
"github.com/qdm12/golibs/logging"
|
"github.com/qdm12/golibs/logging"
|
||||||
|
"github.com/qdm12/golibs/os"
|
||||||
)
|
)
|
||||||
|
|
||||||
type mullvad struct {
|
type mullvad struct {
|
||||||
@@ -73,7 +73,7 @@ func (m *mullvad) GetOpenVPNConnection(selection models.ServerSelection) (
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (m *mullvad) BuildConf(connection models.OpenVPNConnection,
|
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 {
|
if len(cipher) == 0 {
|
||||||
cipher = aes256cbc
|
cipher = aes256cbc
|
||||||
}
|
}
|
||||||
@@ -114,7 +114,7 @@ func (m *mullvad) BuildConf(connection models.OpenVPNConnection,
|
|||||||
lines = append(lines, `pull-filter ignore "ifconfig-ipv6"`)
|
lines = append(lines, `pull-filter ignore "ifconfig-ipv6"`)
|
||||||
}
|
}
|
||||||
if !root {
|
if !root {
|
||||||
lines = append(lines, "user nonrootuser")
|
lines = append(lines, "user "+username)
|
||||||
}
|
}
|
||||||
lines = append(lines, []string{
|
lines = append(lines, []string{
|
||||||
"<ca>",
|
"<ca>",
|
||||||
@@ -128,7 +128,7 @@ func (m *mullvad) BuildConf(connection models.OpenVPNConnection,
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (m *mullvad) PortForward(ctx context.Context, client *http.Client,
|
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)) {
|
syncState func(port uint16) (pfFilepath models.Filepath)) {
|
||||||
panic("port forwarding is not supported for mullvad")
|
panic("port forwarding is not supported for mullvad")
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,8 +10,8 @@ import (
|
|||||||
"github.com/qdm12/gluetun/internal/constants"
|
"github.com/qdm12/gluetun/internal/constants"
|
||||||
"github.com/qdm12/gluetun/internal/firewall"
|
"github.com/qdm12/gluetun/internal/firewall"
|
||||||
"github.com/qdm12/gluetun/internal/models"
|
"github.com/qdm12/gluetun/internal/models"
|
||||||
"github.com/qdm12/golibs/files"
|
|
||||||
"github.com/qdm12/golibs/logging"
|
"github.com/qdm12/golibs/logging"
|
||||||
|
"github.com/qdm12/golibs/os"
|
||||||
)
|
)
|
||||||
|
|
||||||
type nordvpn struct {
|
type nordvpn struct {
|
||||||
@@ -78,7 +78,7 @@ func (n *nordvpn) GetOpenVPNConnection(selection models.ServerSelection) (
|
|||||||
return pickRandomConnection(connections, n.randSource), nil
|
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) {
|
cipher, auth string, extras models.ExtraConfigOptions) (lines []string) {
|
||||||
if len(cipher) == 0 {
|
if len(cipher) == 0 {
|
||||||
cipher = aes256cbc
|
cipher = aes256cbc
|
||||||
@@ -115,13 +115,13 @@ func (n *nordvpn) BuildConf(connection models.OpenVPNConnection, verbosity, uid,
|
|||||||
// Modified variables
|
// Modified variables
|
||||||
fmt.Sprintf("verb %d", verbosity),
|
fmt.Sprintf("verb %d", verbosity),
|
||||||
fmt.Sprintf("auth-user-pass %s", constants.OpenVPNAuthConf),
|
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("remote %s %d", connection.IP.String(), connection.Port),
|
||||||
fmt.Sprintf("cipher %s", cipher),
|
fmt.Sprintf("cipher %s", cipher),
|
||||||
fmt.Sprintf("auth %s", auth),
|
fmt.Sprintf("auth %s", auth),
|
||||||
}
|
}
|
||||||
if !root {
|
if !root {
|
||||||
lines = append(lines, "user nonrootuser")
|
lines = append(lines, "user "+username)
|
||||||
}
|
}
|
||||||
lines = append(lines, []string{
|
lines = append(lines, []string{
|
||||||
"<ca>",
|
"<ca>",
|
||||||
@@ -142,7 +142,7 @@ func (n *nordvpn) BuildConf(connection models.OpenVPNConnection, verbosity, uid,
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (n *nordvpn) PortForward(ctx context.Context, client *http.Client,
|
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)) {
|
syncState func(port uint16) (pfFilepath models.Filepath)) {
|
||||||
panic("port forwarding is not supported for nordvpn")
|
panic("port forwarding is not supported for nordvpn")
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -19,8 +19,8 @@ import (
|
|||||||
"github.com/qdm12/gluetun/internal/firewall"
|
"github.com/qdm12/gluetun/internal/firewall"
|
||||||
gluetunLog "github.com/qdm12/gluetun/internal/logging"
|
gluetunLog "github.com/qdm12/gluetun/internal/logging"
|
||||||
"github.com/qdm12/gluetun/internal/models"
|
"github.com/qdm12/gluetun/internal/models"
|
||||||
"github.com/qdm12/golibs/files"
|
|
||||||
"github.com/qdm12/golibs/logging"
|
"github.com/qdm12/golibs/logging"
|
||||||
|
"github.com/qdm12/golibs/os"
|
||||||
)
|
)
|
||||||
|
|
||||||
type pia struct {
|
type pia struct {
|
||||||
@@ -109,7 +109,7 @@ func (p *pia) GetOpenVPNConnection(selection models.ServerSelection) (
|
|||||||
return connection, nil
|
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) {
|
cipher, auth string, extras models.ExtraConfigOptions) (lines []string) {
|
||||||
var X509CRL, certificate string
|
var X509CRL, certificate string
|
||||||
var defaultCipher, defaultAuth string
|
var defaultCipher, defaultAuth string
|
||||||
@@ -161,7 +161,7 @@ func (p *pia) BuildConf(connection models.OpenVPNConnection, verbosity, uid, gid
|
|||||||
lines = append(lines, "ncp-disable")
|
lines = append(lines, "ncp-disable")
|
||||||
}
|
}
|
||||||
if !root {
|
if !root {
|
||||||
lines = append(lines, "user nonrootuser")
|
lines = append(lines, "user "+username)
|
||||||
}
|
}
|
||||||
lines = append(lines, []string{
|
lines = append(lines, []string{
|
||||||
"<crl-verify>",
|
"<crl-verify>",
|
||||||
@@ -183,7 +183,7 @@ func (p *pia) BuildConf(connection models.OpenVPNConnection, verbosity, uid, gid
|
|||||||
|
|
||||||
//nolint:gocognit
|
//nolint:gocognit
|
||||||
func (p *pia) PortForward(ctx context.Context, client *http.Client,
|
func (p *pia) PortForward(ctx context.Context, client *http.Client,
|
||||||
fileManager files.FileManager, pfLogger logging.Logger, gateway net.IP, fw firewall.Configurator,
|
openFile os.OpenFileFunc, pfLogger logging.Logger, gateway net.IP, fw firewall.Configurator,
|
||||||
syncState func(port uint16) (pfFilepath models.Filepath)) {
|
syncState func(port uint16) (pfFilepath models.Filepath)) {
|
||||||
if !p.activeServer.PortForward {
|
if !p.activeServer.PortForward {
|
||||||
pfLogger.Error("The server %s does not support port forwarding", p.activeServer.Region)
|
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
|
return
|
||||||
}
|
}
|
||||||
defer pfLogger.Warn("loop exited")
|
defer pfLogger.Warn("loop exited")
|
||||||
data, err := readPIAPortForwardData(fileManager)
|
data, err := readPIAPortForwardData(openFile)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
pfLogger.Error(err)
|
pfLogger.Error(err)
|
||||||
}
|
}
|
||||||
@@ -222,7 +222,7 @@ func (p *pia) PortForward(ctx context.Context, client *http.Client,
|
|||||||
|
|
||||||
if !dataFound || expired {
|
if !dataFound || expired {
|
||||||
tryUntilSuccessful(ctx, pfLogger, func() error {
|
tryUntilSuccessful(ctx, pfLogger, func() error {
|
||||||
data, err = refreshPIAPortForwardData(ctx, client, gateway, fileManager)
|
data, err = refreshPIAPortForwardData(ctx, client, gateway, openFile)
|
||||||
return err
|
return err
|
||||||
})
|
})
|
||||||
if ctx.Err() != nil {
|
if ctx.Err() != nil {
|
||||||
@@ -240,12 +240,9 @@ func (p *pia) PortForward(ctx context.Context, client *http.Client,
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
filepath := syncState(data.Port)
|
filepath := string(syncState(data.Port))
|
||||||
pfLogger.Info("Writing port to %s", filepath)
|
pfLogger.Info("Writing port to %s", filepath)
|
||||||
if err := fileManager.WriteToFile(
|
if err := writePortForwardedToFile(openFile, filepath, data.Port); err != nil {
|
||||||
string(filepath), []byte(fmt.Sprintf("%d", data.Port)),
|
|
||||||
files.Permissions(constants.AllReadWritePermissions),
|
|
||||||
); err != nil {
|
|
||||||
pfLogger.Error(err)
|
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))
|
pfLogger.Warn("Forward port has expired on %s, getting another one", data.Expiration.Format(time.RFC1123))
|
||||||
oldPort := data.Port
|
oldPort := data.Port
|
||||||
for {
|
for {
|
||||||
data, err = refreshPIAPortForwardData(ctx, client, gateway, fileManager)
|
data, err = refreshPIAPortForwardData(ctx, client, gateway, openFile)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
pfLogger.Error(err)
|
pfLogger.Error(err)
|
||||||
continue
|
continue
|
||||||
@@ -298,10 +295,7 @@ func (p *pia) PortForward(ctx context.Context, client *http.Client,
|
|||||||
}
|
}
|
||||||
filepath := syncState(data.Port)
|
filepath := syncState(data.Port)
|
||||||
pfLogger.Info("Writing port to %s", filepath)
|
pfLogger.Info("Writing port to %s", filepath)
|
||||||
if err := fileManager.WriteToFile(
|
if err := writePortForwardedToFile(openFile, string(filepath), data.Port); err != nil {
|
||||||
string(filepath), []byte(fmt.Sprintf("%d", data.Port)),
|
|
||||||
files.Permissions(constants.AllReadWritePermissions),
|
|
||||||
); err != nil {
|
|
||||||
pfLogger.Error(err)
|
pfLogger.Error(err)
|
||||||
}
|
}
|
||||||
if err := bindPIAPort(ctx, client, gateway, data); err != nil {
|
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,
|
func refreshPIAPortForwardData(ctx context.Context, client *http.Client,
|
||||||
gateway net.IP, fileManager files.FileManager) (data piaPortForwardData, err error) {
|
gateway net.IP, openFile os.OpenFileFunc) (data piaPortForwardData, err error) {
|
||||||
data.Token, err = fetchPIAToken(ctx, fileManager, client)
|
data.Token, err = fetchPIAToken(ctx, openFile, client)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return data, fmt.Errorf("cannot obtain token: %w", err)
|
return data, fmt.Errorf("cannot obtain token: %w", err)
|
||||||
}
|
}
|
||||||
@@ -374,7 +368,7 @@ func refreshPIAPortForwardData(ctx context.Context, client *http.Client,
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return data, fmt.Errorf("cannot obtain port forwarding data: %w", err)
|
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, fmt.Errorf("cannot persist port forwarding information to file: %w", err)
|
||||||
}
|
}
|
||||||
return data, nil
|
return data, nil
|
||||||
@@ -393,34 +387,39 @@ type piaPortForwardData struct {
|
|||||||
Expiration time.Time `json:"expires_at"`
|
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)
|
const filepath = string(constants.PIAPortForward)
|
||||||
exists, err := fileManager.FileExists(filepath)
|
file, err := openFile(filepath, os.O_RDONLY, 0)
|
||||||
if err != nil {
|
if os.IsNotExist(err) {
|
||||||
return data, err
|
|
||||||
} else if !exists {
|
|
||||||
return data, nil
|
return data, nil
|
||||||
}
|
} else if err != nil {
|
||||||
b, err := fileManager.ReadFile(filepath)
|
|
||||||
if err != nil {
|
|
||||||
return data, err
|
return data, err
|
||||||
}
|
}
|
||||||
if err := json.Unmarshal(b, &data); err != nil {
|
|
||||||
return data, err
|
|
||||||
}
|
|
||||||
return data, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func writePIAPortForwardData(fileManager files.FileManager, data piaPortForwardData) (err error) {
|
decoder := json.NewDecoder(file)
|
||||||
b, err := json.Marshal(&data)
|
err = decoder.Decode(&data)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("cannot encode data: %w", err)
|
_ = file.Close()
|
||||||
|
return data, err
|
||||||
}
|
}
|
||||||
err = fileManager.WriteToFile(string(constants.PIAPortForward), b)
|
return data, file.Close()
|
||||||
|
}
|
||||||
|
|
||||||
|
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 {
|
if err != nil {
|
||||||
return err
|
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) {
|
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
|
return payload, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func fetchPIAToken(ctx context.Context, fileManager files.FileManager, client *http.Client) (token string, err error) {
|
func fetchPIAToken(ctx context.Context, openFile os.OpenFileFunc,
|
||||||
username, password, err := getOpenvpnCredentials(fileManager)
|
client *http.Client) (token string, err error) {
|
||||||
|
username, password, err := getOpenvpnCredentials(openFile)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", fmt.Errorf("cannot get Openvpn credentials: %w", err)
|
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
|
return "", err
|
||||||
}
|
}
|
||||||
defer response.Body.Close()
|
defer response.Body.Close()
|
||||||
b, err := ioutil.ReadAll(response.Body)
|
|
||||||
if response.StatusCode != http.StatusOK {
|
if response.StatusCode != http.StatusOK {
|
||||||
|
b, _ := ioutil.ReadAll(response.Body)
|
||||||
shortenMessage := string(b)
|
shortenMessage := string(b)
|
||||||
shortenMessage = strings.ReplaceAll(shortenMessage, "\n", "")
|
shortenMessage = strings.ReplaceAll(shortenMessage, "\n", "")
|
||||||
shortenMessage = strings.ReplaceAll(shortenMessage, " ", " ")
|
shortenMessage = strings.ReplaceAll(shortenMessage, " ", " ")
|
||||||
return "", fmt.Errorf("%s: response received: %q", response.Status, 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 {
|
var result struct {
|
||||||
Token string `json:"token"`
|
Token string `json:"token"`
|
||||||
}
|
}
|
||||||
if err := json.Unmarshal(b, &result); err != nil {
|
if err := decoder.Decode(&result); err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
} else if len(result.Token) == 0 {
|
} else if len(result.Token) == 0 {
|
||||||
return "", fmt.Errorf("token is empty")
|
return "", fmt.Errorf("token is empty")
|
||||||
@@ -489,10 +488,19 @@ func fetchPIAToken(ctx context.Context, fileManager files.FileManager, client *h
|
|||||||
return result.Token, nil
|
return result.Token, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func getOpenvpnCredentials(fileManager files.FileManager) (username, password string, err error) {
|
func getOpenvpnCredentials(openFile os.OpenFileFunc) (username, password string, err error) {
|
||||||
authData, err := fileManager.ReadFile(string(constants.OpenVPNAuthConf))
|
const filepath = string(constants.OpenVPNAuthConf)
|
||||||
|
file, err := openFile(filepath, os.O_RDONLY, 0)
|
||||||
if err != nil {
|
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")
|
lines := strings.Split(string(authData), "\n")
|
||||||
const minLines = 2
|
const minLines = 2
|
||||||
@@ -525,16 +533,13 @@ func fetchPIAPortForwardData(ctx context.Context, client *http.Client, gateway n
|
|||||||
if response.StatusCode != http.StatusOK {
|
if response.StatusCode != http.StatusOK {
|
||||||
return 0, "", expiration, fmt.Errorf("cannot obtain signature: %s", response.Status)
|
return 0, "", expiration, fmt.Errorf("cannot obtain signature: %s", response.Status)
|
||||||
}
|
}
|
||||||
b, err := ioutil.ReadAll(response.Body)
|
decoder := json.NewDecoder(response.Body)
|
||||||
if err != nil {
|
|
||||||
return 0, "", expiration, fmt.Errorf("cannot obtain signature: %w", err)
|
|
||||||
}
|
|
||||||
var data struct {
|
var data struct {
|
||||||
Status string `json:"status"`
|
Status string `json:"status"`
|
||||||
Payload string `json:"payload"`
|
Payload string `json:"payload"`
|
||||||
Signature string `json:"signature"`
|
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)
|
return 0, "", expiration, fmt.Errorf("cannot decode received data: %w", err)
|
||||||
} else if data.Status != "OK" {
|
} else if data.Status != "OK" {
|
||||||
return 0, "", expiration, fmt.Errorf("response received from PIA has status %s", data.Status)
|
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 {
|
if response.StatusCode != http.StatusOK {
|
||||||
return fmt.Errorf("cannot bind port: %s", response.Status)
|
return fmt.Errorf("cannot bind port: %s", response.Status)
|
||||||
}
|
}
|
||||||
b, err := ioutil.ReadAll(response.Body)
|
|
||||||
if err != nil {
|
decoder := json.NewDecoder(response.Body)
|
||||||
return fmt.Errorf("cannot bind port: %w", err)
|
|
||||||
}
|
|
||||||
var responseData struct {
|
var responseData struct {
|
||||||
Status string `json:"status"`
|
Status string `json:"status"`
|
||||||
Message string `json:"message"`
|
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)
|
return fmt.Errorf("cannot bind port: %w", err)
|
||||||
} else if responseData.Status != "OK" {
|
} else if responseData.Status != "OK" {
|
||||||
return fmt.Errorf("response received from PIA: %s (%s)", responseData.Status, responseData.Message)
|
return fmt.Errorf("response received from PIA: %s (%s)", responseData.Status, responseData.Message)
|
||||||
}
|
}
|
||||||
return nil
|
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()
|
||||||
|
}
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user