Compare commits
44 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
b9a9319cb4 | ||
|
|
77e4317135 | ||
|
|
b10d97e53a | ||
|
|
648a4c04d7 | ||
|
|
3ca674dca7 | ||
|
|
fa97fd496e | ||
|
|
c76a7ee8da | ||
|
|
80f6b78332 | ||
|
|
8dc54a7c44 | ||
|
|
8f080c537b | ||
|
|
427cf86f44 | ||
|
|
2d244c08e7 | ||
|
|
82c0f523aa | ||
|
|
c07a0b0ada | ||
|
|
e4c306c0ee | ||
|
|
6ffb94f819 | ||
|
|
142238e8b7 | ||
|
|
678e23c7d6 | ||
|
|
0abcebe1d8 | ||
|
|
f398af1169 | ||
|
|
afbea415e3 | ||
|
|
225bd5d25b | ||
|
|
3651cc6161 | ||
|
|
dc674014ff | ||
|
|
0e0e03949d | ||
|
|
f5bf5c236a | ||
|
|
94480ecabb | ||
|
|
31ef9b1d45 | ||
|
|
bf76132fd4 | ||
|
|
8cc2983318 | ||
|
|
caeca18ed7 | ||
|
|
50febb41ff | ||
|
|
79293e067c | ||
|
|
f45be80591 | ||
|
|
d405ba8dca | ||
|
|
ca975b1c01 | ||
|
|
e0e3ca3832 | ||
|
|
e7c952cbf7 | ||
|
|
85ad2dd39a | ||
|
|
0c4f0ec17b | ||
|
|
5ad4136955 | ||
|
|
a432de95a9 | ||
|
|
1d25a0e18c | ||
|
|
29fd95685f |
@@ -8,7 +8,7 @@
|
||||
"vscode"
|
||||
],
|
||||
"shutdownAction": "stopCompose",
|
||||
"postCreateCommand": "source ~/.windows.sh && go mod download && go mod tidy",
|
||||
"postCreateCommand": "~/.windows.sh && go mod download && go mod tidy",
|
||||
"workspaceFolder": "/workspace",
|
||||
"extensions": [
|
||||
"golang.go",
|
||||
|
||||
5
.github/ISSUE_TEMPLATE/bug.yml
vendored
5
.github/ISSUE_TEMPLATE/bug.yml
vendored
@@ -11,7 +11,9 @@ body:
|
||||
id: urgent
|
||||
attributes:
|
||||
label: Is this urgent?
|
||||
description: Is this a critical bug, or do you need this fixed urgently?
|
||||
description: |
|
||||
Is this a critical bug, or do you need this fixed urgently?
|
||||
If this is urgent, note you can use one of the [image tags available](https://github.com/qdm12/gluetun/wiki/Docker-image-tags) if that can help.
|
||||
options:
|
||||
- "No"
|
||||
- "Yes"
|
||||
@@ -38,6 +40,7 @@ body:
|
||||
attributes:
|
||||
label: VPN service provider
|
||||
options:
|
||||
- Custom
|
||||
- Cyberghost
|
||||
- ExpressVPN
|
||||
- FastestVPN
|
||||
|
||||
11
.github/labels.yml
vendored
11
.github/labels.yml
vendored
@@ -14,6 +14,14 @@
|
||||
color: "795548"
|
||||
description: ""
|
||||
|
||||
# Priority
|
||||
- name: "🚨 Urgent"
|
||||
color: "d5232f"
|
||||
description: ""
|
||||
- name: "💤 Low priority"
|
||||
color: "4285f4"
|
||||
description: ""
|
||||
|
||||
# VPN providers
|
||||
- name: ":cloud: Cyberghost"
|
||||
color: "cfe8d4"
|
||||
@@ -39,6 +47,9 @@
|
||||
- name: ":cloud: NordVPN"
|
||||
color: "cfe8d4"
|
||||
description: ""
|
||||
- name: ":cloud: Perfect Privacy"
|
||||
color: "cfe8d4"
|
||||
description: ""
|
||||
- name: ":cloud: PIA"
|
||||
color: "cfe8d4"
|
||||
description: ""
|
||||
|
||||
109
.github/workflows/ci.yml
vendored
109
.github/workflows/ci.yml
vendored
@@ -1,6 +1,24 @@
|
||||
name: CI
|
||||
on:
|
||||
release:
|
||||
types:
|
||||
- published
|
||||
push:
|
||||
branches:
|
||||
- master
|
||||
paths:
|
||||
- .github/workflows/ci.yml
|
||||
- cmd/**
|
||||
- internal/**
|
||||
- pkg/**
|
||||
- .dockerignore
|
||||
- .golangci.yml
|
||||
- Dockerfile
|
||||
- go.mod
|
||||
- go.sum
|
||||
pull_request:
|
||||
branches:
|
||||
- master
|
||||
paths:
|
||||
- .github/workflows/ci.yml
|
||||
- cmd/**
|
||||
@@ -14,11 +32,17 @@ on:
|
||||
|
||||
jobs:
|
||||
verify:
|
||||
# Only run if it's a push event or if it's a PR from this repository, and it is not dependabot.
|
||||
if: |
|
||||
github.actor != 'dependabot[bot]' &&
|
||||
(github.event_name == 'push' ||
|
||||
github.event_name == 'release' ||
|
||||
(github.event_name == 'pull_request' && github.event.pull_request.head.repo.full_name == github.repository))
|
||||
runs-on: ubuntu-latest
|
||||
env:
|
||||
DOCKER_BUILDKIT: "1"
|
||||
steps:
|
||||
- uses: actions/checkout@v2.3.4
|
||||
- uses: actions/checkout@v2.4.0
|
||||
|
||||
- name: Linting
|
||||
run: docker build --target lint .
|
||||
@@ -44,18 +68,23 @@ jobs:
|
||||
- name: Build final image
|
||||
run: docker build -t final-image .
|
||||
|
||||
- name: Image security analysis
|
||||
uses: snyk/actions/docker@master
|
||||
env:
|
||||
SNYK_TOKEN: ${{ secrets.SNYK_TOKEN }}
|
||||
with:
|
||||
image: final-image
|
||||
# - name: Image security analysis
|
||||
# uses: snyk/actions/docker@master
|
||||
# env:
|
||||
# SNYK_TOKEN: ${{ secrets.SNYK_TOKEN }}
|
||||
# with:
|
||||
# image: final-image
|
||||
|
||||
publish:
|
||||
# Only run if it's a push event or if it's a PR from this repository
|
||||
if: |
|
||||
github.event_name == 'push' ||
|
||||
github.event_name == 'release' ||
|
||||
(github.event_name == 'pull_request' && github.event.pull_request.head.repo.full_name == github.repository)
|
||||
needs: [verify]
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v2.3.4
|
||||
- uses: actions/checkout@v2.4.0
|
||||
|
||||
- uses: docker/setup-qemu-action@v1
|
||||
- uses: docker/setup-buildx-action@v1
|
||||
@@ -65,35 +94,51 @@ jobs:
|
||||
username: qmcgaw
|
||||
password: ${{ secrets.DOCKERHUB_PASSWORD }}
|
||||
|
||||
- name: Set variables
|
||||
id: vars
|
||||
env:
|
||||
EVENT_NAME: ${{ github.event_name }}
|
||||
- name: Check for semver tag
|
||||
id: semvercheck
|
||||
run: |
|
||||
BRANCH=${GITHUB_REF#refs/heads/}
|
||||
TAG=${GITHUB_REF#refs/tags/}
|
||||
echo ::set-output name=commit::$(git rev-parse --short HEAD)
|
||||
echo ::set-output name=created::$(date -u +%Y-%m-%dT%H:%M:%SZ)
|
||||
if [ "$TAG" != "$GITHUB_REF" ]; then
|
||||
echo ::set-output name=version::$TAG
|
||||
echo ::set-output name=platforms::linux/amd64,linux/386,linux/arm64,linux/arm/v6,linux/arm/v7,linux/ppc64le
|
||||
elif [ "$BRANCH" = "master" ]; then
|
||||
echo ::set-output name=version::latest
|
||||
echo ::set-output name=platforms::linux/amd64,linux/386,linux/arm64,linux/arm/v6,linux/arm/v7,linux/ppc64le
|
||||
if [[ ${{ github.ref }} =~ ^refs/tags/v[0-9]+\.[0-9]+\.[0-9]+$ ]]; then
|
||||
MATCH=true
|
||||
else
|
||||
echo ::set-output name=version::$BRANCH
|
||||
echo ::set-output name=platforms::linux/amd64,linux/386,linux/arm64,linux/arm/v6,linux/arm/v7,linux/ppc64le
|
||||
MATCH=false
|
||||
fi
|
||||
if [[ ! ${{ github.ref }} =~ ^refs/tags/v0\. ]]; then
|
||||
MATCH=$MATCH_nonzero
|
||||
fi
|
||||
echo ::set-output name=match::$MATCH
|
||||
|
||||
# extract metadata (tags, labels) for Docker
|
||||
# https://github.com/docker/metadata-action
|
||||
- name: Extract Docker metadata
|
||||
id: meta
|
||||
uses: docker/metadata-action@v3
|
||||
with:
|
||||
flavor: |
|
||||
latest=${{ github.ref == 'refs/heads/master' }}
|
||||
images: |
|
||||
qmcgaw/gluetun
|
||||
qmcgaw/private-internet-access
|
||||
tags: |
|
||||
type=ref,event=branch,enable=${{ github.ref != 'refs/heads/master' }}
|
||||
type=ref,event=pr
|
||||
type=ref,event=tag,enable=${{ !startsWith(steps.semvercheck.outputs.match, 'true') }}
|
||||
type=semver,pattern=v{{major}}.{{minor}}.{{patch}},enable=${{ startsWith(steps.semvercheck.outputs.match, 'true') }}
|
||||
type=semver,pattern=v{{major}}.{{minor}},enable=${{ startsWith(steps.semvercheck.outputs.match, 'true') }}
|
||||
type=semver,pattern=v{{major}},enable=${{ startsWith(steps.semvercheck.outputs.match, 'true_nonzero') }}
|
||||
type=raw,value=latest,enable=${{ !startsWith(steps.semvercheck.outputs.match, 'true') }}
|
||||
|
||||
- name: Short commit
|
||||
id: shortcommit
|
||||
run: echo "::set-output name=value::$(git rev-parse --short HEAD)"
|
||||
|
||||
- name: Build and push final image
|
||||
uses: docker/build-push-action@v2.6.1
|
||||
uses: docker/build-push-action@v2.7.0
|
||||
with:
|
||||
platforms: ${{ steps.vars.outputs.platforms }}
|
||||
platforms: linux/amd64,linux/386,linux/arm64,linux/arm/v6,linux/arm/v7,linux/ppc64le
|
||||
labels: ${{ steps.meta.outputs.labels }}
|
||||
build-args: |
|
||||
CREATED=${{ steps.vars.outputs.created }}
|
||||
COMMIT=${{ steps.vars.outputs.commit }}
|
||||
VERSION=${{ steps.vars.outputs.version }}
|
||||
tags: |
|
||||
qmcgaw/gluetun:${{ steps.vars.outputs.version }}
|
||||
qmcgaw/private-internet-access:${{ steps.vars.outputs.version }}
|
||||
CREATED=${{ fromJSON(steps.meta.outputs.json).labels['org.opencontainers.image.created'] }}
|
||||
COMMIT=${{ steps.shortcommit.outputs.value }}
|
||||
VERSION=${{ fromJSON(steps.meta.outputs.json).labels['org.opencontainers.image.version'] }}
|
||||
tags: ${{ steps.meta.outputs.tags }}
|
||||
push: true
|
||||
|
||||
37
.github/workflows/dependabot.yml
vendored
Normal file
37
.github/workflows/dependabot.yml
vendored
Normal file
@@ -0,0 +1,37 @@
|
||||
name: Dependabot
|
||||
on:
|
||||
pull_request:
|
||||
branches:
|
||||
- master
|
||||
paths:
|
||||
- .github/workflows/dependabot.yml
|
||||
- cmd/**
|
||||
- internal/**
|
||||
- pkg/**
|
||||
- .dockerignore
|
||||
- .golangci.yml
|
||||
- Dockerfile
|
||||
- go.mod
|
||||
- go.sum
|
||||
|
||||
jobs:
|
||||
verify:
|
||||
if: ${{ github.actor == 'dependabot[bot]' }}
|
||||
runs-on: ubuntu-latest
|
||||
env:
|
||||
DOCKER_BUILDKIT: "1"
|
||||
steps:
|
||||
- uses: actions/checkout@v2.4.0
|
||||
|
||||
- name: Build test image
|
||||
run: docker build --target test -t test-container .
|
||||
|
||||
- name: Run tests in test container
|
||||
run: |
|
||||
touch coverage.txt
|
||||
docker run --rm \
|
||||
-v "$(pwd)/coverage.txt:/tmp/gobuild/coverage.txt" \
|
||||
test-container
|
||||
|
||||
- name: Build final image
|
||||
run: docker build -t final-image .
|
||||
2
.github/workflows/dockerhub-description.yml
vendored
2
.github/workflows/dockerhub-description.yml
vendored
@@ -10,7 +10,7 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v2.3.4
|
||||
uses: actions/checkout@v2.4.0
|
||||
- name: Docker Hub Description
|
||||
uses: peter-evans/dockerhub-description@v2
|
||||
with:
|
||||
|
||||
40
.github/workflows/fork.yml
vendored
Normal file
40
.github/workflows/fork.yml
vendored
Normal file
@@ -0,0 +1,40 @@
|
||||
name: Fork
|
||||
on:
|
||||
pull_request:
|
||||
branches:
|
||||
- master
|
||||
paths:
|
||||
- .github/workflows/fork.yml
|
||||
- cmd/**
|
||||
- internal/**
|
||||
- pkg/**
|
||||
- .dockerignore
|
||||
- .golangci.yml
|
||||
- Dockerfile
|
||||
- go.mod
|
||||
- go.sum
|
||||
|
||||
jobs:
|
||||
verify:
|
||||
if: github.event.pull_request.head.repo.full_name != github.repository && github.actor != 'dependabot[bot]'
|
||||
runs-on: ubuntu-latest
|
||||
env:
|
||||
DOCKER_BUILDKIT: "1"
|
||||
steps:
|
||||
- uses: actions/checkout@v2.4.0
|
||||
|
||||
- name: Linting
|
||||
run: docker build --target lint .
|
||||
|
||||
- name: Build test image
|
||||
run: docker build --target test -t test-container .
|
||||
|
||||
- name: Run tests in test container
|
||||
run: |
|
||||
touch coverage.txt
|
||||
docker run --rm \
|
||||
-v "$(pwd)/coverage.txt:/tmp/gobuild/coverage.txt" \
|
||||
test-container
|
||||
|
||||
- name: Build final image
|
||||
run: docker build -t final-image .
|
||||
2
.github/workflows/labels.yml
vendored
2
.github/workflows/labels.yml
vendored
@@ -9,7 +9,7 @@ jobs:
|
||||
labeler:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v2.3.4
|
||||
- uses: actions/checkout@v2.4.0
|
||||
- uses: crazy-max/ghaction-github-labeler@v3
|
||||
with:
|
||||
yaml-file: .github/labels.yml
|
||||
|
||||
2
.github/workflows/misspell.yml
vendored
2
.github/workflows/misspell.yml
vendored
@@ -8,7 +8,7 @@ jobs:
|
||||
misspell:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v2.3.4
|
||||
- uses: actions/checkout@v2.4.0
|
||||
- uses: reviewdog/action-misspell@v1
|
||||
with:
|
||||
locale: "US"
|
||||
|
||||
@@ -31,8 +31,11 @@ linters:
|
||||
enable:
|
||||
# - cyclop
|
||||
# - errorlint
|
||||
# - ireturn
|
||||
# - varnamelen
|
||||
# - wrapcheck
|
||||
- asciicheck
|
||||
- bidichk
|
||||
- bodyclose
|
||||
- dogsled
|
||||
- dupl
|
||||
@@ -56,7 +59,7 @@ linters:
|
||||
- gomoddirectives
|
||||
- goprintffuncname
|
||||
- gosec
|
||||
- importas
|
||||
- ifshort
|
||||
- importas
|
||||
- lll
|
||||
- makezero
|
||||
@@ -64,6 +67,7 @@ linters:
|
||||
- nakedret
|
||||
- nestif
|
||||
- nilerr
|
||||
- nilnil
|
||||
- noctx
|
||||
- nolintlint
|
||||
- prealloc
|
||||
@@ -73,6 +77,7 @@ linters:
|
||||
- revive
|
||||
- rowserrcheck
|
||||
- sqlclosecheck
|
||||
- tenv
|
||||
- thelper
|
||||
- tparallel
|
||||
- unconvert
|
||||
|
||||
@@ -2,7 +2,7 @@ ARG ALPINE_VERSION=3.14
|
||||
ARG GO_ALPINE_VERSION=3.14
|
||||
ARG GO_VERSION=1.17
|
||||
ARG XCPUTRANSLATE_VERSION=v0.6.0
|
||||
ARG GOLANGCI_LINT_VERSION=v1.42.1
|
||||
ARG GOLANGCI_LINT_VERSION=v1.43.0
|
||||
ARG BUILDPLATFORM=linux/amd64
|
||||
|
||||
FROM --platform=${BUILDPLATFORM} qmcgaw/xcputranslate:${XCPUTRANSLATE_VERSION} AS xcputranslate
|
||||
@@ -179,6 +179,9 @@ RUN apk add --no-cache --update -l apk-tools && \
|
||||
mv /usr/sbin/openvpn /usr/sbin/openvpn2.4 && \
|
||||
apk del openvpn && \
|
||||
apk add --no-cache --update openvpn ca-certificates iptables ip6tables unbound tzdata && \
|
||||
mv /usr/sbin/openvpn /usr/sbin/openvpn2.5 && \
|
||||
# Fix vulnerability issue
|
||||
apk add --no-cache --update busybox && \
|
||||
rm -rf /var/cache/apk/* /etc/unbound/* /usr/sbin/unbound-* /etc/openvpn/*.sh /usr/lib/openvpn/plugins/openvpn-plugin-down-root.so && \
|
||||
deluser openvpn && \
|
||||
deluser unbound && \
|
||||
|
||||
12
README.md
12
README.md
@@ -1,7 +1,7 @@
|
||||
# Gluetun VPN client
|
||||
|
||||
*Lightweight swiss-knife-like VPN client to tunnel to Cyberghost, ExpressVPN, FastestVPN,
|
||||
HideMyAss, IPVanish, IVPN, Mullvad, NordVPN, Privado, Private Internet Access, PrivateVPN,
|
||||
HideMyAss, IPVanish, IVPN, Mullvad, NordVPN, Perfect Privacy, Privado, Private Internet Access, PrivateVPN,
|
||||
ProtonVPN, PureVPN, Surfshark, TorGuard, VPNUnlimited, VyprVPN, WeVPN and Windscribe VPN servers
|
||||
using Go, OpenVPN or Wireguard, iptables, DNS over TLS, ShadowSocks and an HTTP proxy*
|
||||
|
||||
@@ -61,13 +61,13 @@ using Go, OpenVPN or Wireguard, iptables, DNS over TLS, ShadowSocks and an HTTP
|
||||
|
||||
## Features
|
||||
|
||||
- Based on Alpine 3.14 for a small Docker image of 31MB
|
||||
- Supports: **Cyberghost**, **ExpressVPN**, **FastestVPN**, **HideMyAss**, **IPVanish**, **IVPN**, **Mullvad**, **NordVPN**, **Privado**, **Private Internet Access**, **PrivateVPN**, **ProtonVPN**, **PureVPN**, **Surfshark**, **TorGuard**, **VPNUnlimited**, **Vyprvpn**, **WeVPN**, **Windscribe** servers
|
||||
- Based on Alpine 3.14 for a small Docker image of 33MB
|
||||
- Supports: **Cyberghost**, **ExpressVPN**, **FastestVPN**, **HideMyAss**, **IPVanish**, **IVPN**, **Mullvad**, **NordVPN**, **Perfect Privacy**, **Privado**, **Private Internet Access**, **PrivateVPN**, **ProtonVPN**, **PureVPN**, **Surfshark**, **TorGuard**, **VPNUnlimited**, **Vyprvpn**, **WeVPN**, **Windscribe** servers
|
||||
- Supports OpenVPN for all providers listed
|
||||
- Supports Wireguard
|
||||
- For **Mullvad**, **Ivpn** and **Windscribe**
|
||||
- For **Torguard**, **VPN Unlimited** and **WeVPN** using [the custom provider](https://github.com/qdm12/gluetun/wiki/Environment-variables#custom)
|
||||
- For custom Wireguard configurations using [the custom provider](https://github.com/qdm12/gluetun/wiki/Environment-variables#custom)
|
||||
- For **Torguard**, **VPN Unlimited** and **WeVPN** using [the custom provider](https://github.com/qdm12/gluetun/wiki/Custom-provider)
|
||||
- For custom Wireguard configurations using [the custom provider](https://github.com/qdm12/gluetun/wiki/Custom-provider)
|
||||
- More in progress, see [#134](https://github.com/qdm12/gluetun/issues/134)
|
||||
- DNS over TLS baked in with service provider(s) of your choice
|
||||
- DNS fine blocking of malicious/ads/surveillance hostnames and IP addresses, with live update every 24 hours
|
||||
@@ -89,7 +89,7 @@ using Go, OpenVPN or Wireguard, iptables, DNS over TLS, ShadowSocks and an HTTP
|
||||
|
||||
Go to the [Wiki](https://github.com/qdm12/gluetun/wiki)!
|
||||
|
||||
[🐛 Found a bug in the Wiki?!](https://github.com/qdm12/gluetun/issues/new?assignees=&labels=%F0%9F%93%84+Wiki+issue&template=wiki+issue.md&title=Wiki+issue%3A+)
|
||||
[🐛 Found a bug in the Wiki?!](https://github.com/qdm12/gluetun/issues/new?assignees=&labels=%F0%9F%93%84+Wiki+issue&template=wiki+issue.yml&title=Wiki+issue%3A+)
|
||||
|
||||
Here's a docker-compose.yml for the laziest:
|
||||
|
||||
|
||||
@@ -142,6 +142,27 @@ func _main(ctx context.Context, buildInfo models.BuildInformation,
|
||||
}
|
||||
}
|
||||
|
||||
announcementExp, err := time.Parse(time.RFC3339, "2021-10-02T00:00:00Z")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
splashSettings := gosplash.Settings{
|
||||
User: "qdm12",
|
||||
Repository: "gluetun",
|
||||
Emails: []string{"quentin.mcgaw@gmail.com"},
|
||||
Version: buildInfo.Version,
|
||||
Commit: buildInfo.Commit,
|
||||
BuildDate: buildInfo.Created,
|
||||
Announcement: "Wireguard is now supported for Mullvad, IVPN and Windscribe!",
|
||||
AnnounceExp: announcementExp,
|
||||
// Sponsor information
|
||||
PaypalUser: "qmcgaw",
|
||||
GithubSponsor: "qdm12",
|
||||
}
|
||||
for _, line := range gosplash.MakeLines(splashSettings) {
|
||||
fmt.Println(line)
|
||||
}
|
||||
|
||||
// TODO run this in a loop or in openvpn to reload from file without restarting
|
||||
storageLogger := logger.NewChild(logging.Settings{Prefix: "storage: "})
|
||||
storage, err := storage.New(storageLogger, constants.ServersData)
|
||||
@@ -172,27 +193,6 @@ func _main(ctx context.Context, buildInfo models.BuildInformation,
|
||||
dnsConf := unbound.NewConfigurator(nil, cmder, dnsCrypto,
|
||||
"/etc/unbound", "/usr/sbin/unbound", cacertsPath)
|
||||
|
||||
announcementExp, err := time.Parse(time.RFC3339, "2021-10-02T00:00:00Z")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
splashSettings := gosplash.Settings{
|
||||
User: "qdm12",
|
||||
Repository: "gluetun",
|
||||
Emails: []string{"quentin.mcgaw@gmail.com"},
|
||||
Version: buildInfo.Version,
|
||||
Commit: buildInfo.Commit,
|
||||
BuildDate: buildInfo.Created,
|
||||
Announcement: "Wireguard is now supported for Mullvad, IVPN and Windscribe!",
|
||||
AnnounceExp: announcementExp,
|
||||
// Sponsor information
|
||||
PaypalUser: "qmcgaw",
|
||||
GithubSponsor: "qdm12",
|
||||
}
|
||||
for _, line := range gosplash.MakeLines(splashSettings) {
|
||||
fmt.Println(line)
|
||||
}
|
||||
|
||||
err = printVersions(ctx, logger, []printVersionElement{
|
||||
{name: "Alpine", getVersion: alpineConf.Version},
|
||||
{name: "OpenVPN 2.4", getVersion: ovpnConf.Version24},
|
||||
|
||||
2
go.mod
2
go.mod
@@ -3,7 +3,7 @@ module github.com/qdm12/gluetun
|
||||
go 1.17
|
||||
|
||||
require (
|
||||
github.com/breml/rootcerts v0.1.0
|
||||
github.com/breml/rootcerts v0.2.0
|
||||
github.com/fatih/color v1.13.0
|
||||
github.com/go-ping/ping v0.0.0-20210911151512-381826476871
|
||||
github.com/golang/mock v1.6.0
|
||||
|
||||
4
go.sum
4
go.sum
@@ -4,8 +4,8 @@ github.com/alcortesm/tgz v0.0.0-20161220082320-9c5fe88206d7/go.mod h1:6zEj6s6u/g
|
||||
github.com/anmitsu/go-shlex v0.0.0-20161002113705-648efa622239/go.mod h1:2FmKhYUyUczH0OGQWaF5ceTx0UBShxjsH6f8oGKYe2c=
|
||||
github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5/go.mod h1:wHh0iHkYZB8zMSxRWpUBQtwG5a7fFgvEO+odwuTv2gs=
|
||||
github.com/asaskevich/govalidator v0.0.0-20180720115003-f9ffefc3facf/go.mod h1:lB+ZfQJz7igIIfQNfa7Ml4HSf2uFQQRzpGGRXenZAgY=
|
||||
github.com/breml/rootcerts v0.1.0 h1:jf/F4MHeXBi7FVAHrPq+/MBmPlGXTbUAyR977bZ0j4E=
|
||||
github.com/breml/rootcerts v0.1.0/go.mod h1:24FDtzYMpqIeYC7QzaE8VPRQaFZU5TIUDlyk8qwjD88=
|
||||
github.com/breml/rootcerts v0.2.0 h1:bBIgVe8bS0Ec+orgWpZ/GRYt3a0O8yoW+g2kSBY2aLE=
|
||||
github.com/breml/rootcerts v0.2.0/go.mod h1:24FDtzYMpqIeYC7QzaE8VPRQaFZU5TIUDlyk8qwjD88=
|
||||
github.com/creack/pty v1.1.7/go.mod h1:lj5s0c3V2DBrqTV7llrYr5NG6My20zk30Fl46Y7DoTY=
|
||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||
|
||||
@@ -26,7 +26,7 @@ var (
|
||||
func (c *CLI) FormatServers(args []string) error {
|
||||
var format, output string
|
||||
var cyberghost, expressvpn, fastestvpn, hideMyAss, ipvanish, ivpn, mullvad,
|
||||
nordvpn, pia, privado, privatevpn, protonvpn, purevpn, surfshark,
|
||||
nordvpn, perfectPrivacy, pia, privado, privatevpn, protonvpn, purevpn, surfshark,
|
||||
torguard, vpnUnlimited, vyprvpn, wevpn, windscribe bool
|
||||
flagSet := flag.NewFlagSet("markdown", flag.ExitOnError)
|
||||
flagSet.StringVar(&format, "format", "markdown", "Format to use which can be: 'markdown'")
|
||||
@@ -39,6 +39,7 @@ func (c *CLI) FormatServers(args []string) error {
|
||||
flagSet.BoolVar(&ivpn, "ivpn", false, "Format IVPN servers")
|
||||
flagSet.BoolVar(&mullvad, "mullvad", false, "Format Mullvad servers")
|
||||
flagSet.BoolVar(&nordvpn, "nordvpn", false, "Format Nordvpn servers")
|
||||
flagSet.BoolVar(&perfectPrivacy, "perfectprivacy", false, "Format Perfect Privacy servers")
|
||||
flagSet.BoolVar(&pia, "pia", false, "Format Private Internet Access servers")
|
||||
flagSet.BoolVar(&privado, "privado", false, "Format Privado servers")
|
||||
flagSet.BoolVar(&privatevpn, "privatevpn", false, "Format Private VPN servers")
|
||||
@@ -83,6 +84,8 @@ func (c *CLI) FormatServers(args []string) error {
|
||||
formatted = currentServers.Mullvad.ToMarkdown()
|
||||
case nordvpn:
|
||||
formatted = currentServers.Nordvpn.ToMarkdown()
|
||||
case perfectPrivacy:
|
||||
formatted = currentServers.Perfectprivacy.ToMarkdown()
|
||||
case pia:
|
||||
formatted = currentServers.Pia.ToMarkdown()
|
||||
case privado:
|
||||
|
||||
@@ -18,7 +18,7 @@ import (
|
||||
)
|
||||
|
||||
var (
|
||||
ErrModeUnspecified = errors.New("at least one of -enduser or -maintainers must be specified")
|
||||
ErrModeUnspecified = errors.New("at least one of -enduser or -maintainer must be specified")
|
||||
ErrNewStorage = errors.New("cannot create storage")
|
||||
ErrUpdateServerInformation = errors.New("cannot update server information")
|
||||
ErrWriteToFile = errors.New("cannot write updated information to file")
|
||||
@@ -51,6 +51,7 @@ func (c *CLI) Update(ctx context.Context, args []string, logger UpdaterLogger) e
|
||||
flagSet.BoolVar(&options.Ivpn, "ivpn", false, "Update IVPN servers")
|
||||
flagSet.BoolVar(&options.Mullvad, "mullvad", false, "Update Mullvad servers")
|
||||
flagSet.BoolVar(&options.Nordvpn, "nordvpn", false, "Update Nordvpn servers")
|
||||
flagSet.BoolVar(&options.Perfectprivacy, "perfectprivacy", false, "Update Perfect Privacy 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.Privatevpn, "privatevpn", false, "Update Private VPN servers")
|
||||
|
||||
@@ -19,7 +19,7 @@ type OpenVPN struct {
|
||||
Flags []string `json:"flags"`
|
||||
MSSFix uint16 `json:"mssfix"`
|
||||
Root bool `json:"run_as_root"`
|
||||
Cipher string `json:"cipher"`
|
||||
Ciphers []string `json:"ciphers"`
|
||||
Auth string `json:"auth"`
|
||||
ConfFile string `json:"conf_file"`
|
||||
Version string `json:"version"`
|
||||
@@ -52,8 +52,8 @@ func (settings *OpenVPN) lines() (lines []string) {
|
||||
lines = append(lines, indent+lastIndent+"Run as root: enabled")
|
||||
}
|
||||
|
||||
if len(settings.Cipher) > 0 {
|
||||
lines = append(lines, indent+lastIndent+"Custom cipher: "+settings.Cipher)
|
||||
if len(settings.Ciphers) > 0 {
|
||||
lines = append(lines, indent+lastIndent+"Custom ciphers: "+commaJoin(settings.Ciphers))
|
||||
}
|
||||
if len(settings.Auth) > 0 {
|
||||
lines = append(lines, indent+lastIndent+"Custom auth algorithm: "+settings.Auth)
|
||||
@@ -127,12 +127,12 @@ func (settings *OpenVPN) read(r reader, serviceProvider string) (err error) {
|
||||
settings.Flags = strings.Fields(flagsStr)
|
||||
}
|
||||
|
||||
settings.Root, err = r.env.YesNo("OPENVPN_ROOT", params.Default("yes"))
|
||||
settings.Root, err = r.env.YesNo("OPENVPN_ROOT", params.Default("no"))
|
||||
if err != nil {
|
||||
return fmt.Errorf("environment variable OPENVPN_ROOT: %w", err)
|
||||
}
|
||||
|
||||
settings.Cipher, err = r.env.Get("OPENVPN_CIPHER")
|
||||
settings.Ciphers, err = r.env.CSV("OPENVPN_CIPHER")
|
||||
if err != nil {
|
||||
return fmt.Errorf("environment variable OPENVPN_CIPHER: %w", err)
|
||||
}
|
||||
|
||||
@@ -11,8 +11,9 @@ import (
|
||||
func Test_OpenVPN_JSON(t *testing.T) {
|
||||
t.Parallel()
|
||||
in := OpenVPN{
|
||||
Root: true,
|
||||
Flags: []string{},
|
||||
Root: true,
|
||||
Flags: []string{},
|
||||
Ciphers: []string{},
|
||||
}
|
||||
data, err := json.MarshalIndent(in, "", " ")
|
||||
require.NoError(t, err)
|
||||
@@ -23,7 +24,7 @@ func Test_OpenVPN_JSON(t *testing.T) {
|
||||
"flags": [],
|
||||
"mssfix": 0,
|
||||
"run_as_root": true,
|
||||
"cipher": "",
|
||||
"ciphers": [],
|
||||
"auth": "",
|
||||
"conf_file": "",
|
||||
"version": "",
|
||||
|
||||
43
internal/configuration/perfectprivacy.go
Normal file
43
internal/configuration/perfectprivacy.go
Normal file
@@ -0,0 +1,43 @@
|
||||
package configuration
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/qdm12/gluetun/internal/constants"
|
||||
)
|
||||
|
||||
func (settings *Provider) readPerfectPrivacy(r reader) (err error) {
|
||||
settings.Name = constants.Perfectprivacy
|
||||
servers := r.servers.GetPerfectprivacy()
|
||||
|
||||
settings.ServerSelection.TargetIP, err = readTargetIP(r.env)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
settings.ServerSelection.Cities, err = r.env.CSVInside("CITY", constants.PerfectprivacyCityChoices(servers))
|
||||
if err != nil {
|
||||
return fmt.Errorf("environment variable CITY: %w", err)
|
||||
}
|
||||
|
||||
return settings.ServerSelection.OpenVPN.readPerfectPrivacy(r)
|
||||
}
|
||||
|
||||
func (settings *OpenVPNSelection) readPerfectPrivacy(r reader) (err error) {
|
||||
settings.TCP, err = readOpenVPNProtocol(r)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
portValidation := openvpnPortValidation{
|
||||
tcp: settings.TCP,
|
||||
allowedTCP: []uint16{44, 443, 4433},
|
||||
allowedUDP: []uint16{44, 443, 4433},
|
||||
}
|
||||
settings.CustomPort, err = readOpenVPNCustomPort(r, portValidation)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
@@ -67,6 +67,8 @@ func (settings *Provider) read(r reader, vpnType string) error {
|
||||
err = settings.readMullvad(r)
|
||||
case constants.Nordvpn:
|
||||
err = settings.readNordvpn(r)
|
||||
case constants.Perfectprivacy:
|
||||
err = settings.readPerfectPrivacy(r)
|
||||
case constants.Privado:
|
||||
err = settings.readPrivado(r)
|
||||
case constants.PrivateInternetAccess:
|
||||
@@ -108,7 +110,7 @@ func (settings *Provider) readVPNServiceProvider(r reader, vpnType string) (err
|
||||
constants.Custom,
|
||||
"cyberghost", constants.Expressvpn, "fastestvpn", "hidemyass", "ipvanish",
|
||||
"ivpn", "mullvad", "nordvpn",
|
||||
"privado", "pia", "private internet access", "privatevpn", "protonvpn",
|
||||
constants.Perfectprivacy, "privado", "pia", "private internet access", "privatevpn", "protonvpn",
|
||||
"purevpn", "surfshark", "torguard", constants.VPNUnlimited, "vyprvpn",
|
||||
constants.Wevpn, "windscribe"}
|
||||
case constants.Wireguard:
|
||||
|
||||
@@ -168,6 +168,21 @@ func Test_Provider_lines(t *testing.T) {
|
||||
" |--Protocol: udp",
|
||||
},
|
||||
},
|
||||
"perfectprivacy": {
|
||||
settings: Provider{
|
||||
Name: constants.Perfectprivacy,
|
||||
ServerSelection: ServerSelection{
|
||||
VPN: constants.OpenVPN,
|
||||
Cities: []string{"a", "b"},
|
||||
},
|
||||
},
|
||||
lines: []string{
|
||||
"|--Perfect Privacy settings:",
|
||||
" |--Cities: a, b",
|
||||
" |--OpenVPN selection:",
|
||||
" |--Protocol: udp",
|
||||
},
|
||||
},
|
||||
"privado": {
|
||||
settings: Provider{
|
||||
Name: constants.Privado,
|
||||
|
||||
@@ -17,7 +17,8 @@ type ServerSelection struct { //nolint:maligned
|
||||
|
||||
// Expressvpn, Fastestvpn, HideMyAss, IPVanish, IVPN, Mullvad, PrivateVPN, Protonvpn, PureVPN, VPNUnlimited
|
||||
Countries []string `json:"countries"`
|
||||
// Expressvpn, HideMyAss, IPVanish, IVPN, Mullvad, PrivateVPN, Protonvpn, PureVPN, VPNUnlimited, WeVPN, Windscribe
|
||||
// Expressvpn, HideMyAss, IPVanish, IVPN, Mullvad, Perfectprivacy, PrivateVPN, Protonvpn,
|
||||
// PureVPN, VPNUnlimited, WeVPN, Windscribe
|
||||
Cities []string `json:"cities"`
|
||||
// Expressvpn, Fastestvpn, HideMyAss, IPVanish, IVPN, PrivateVPN, Windscribe, Privado, Protonvpn, VPNUnlimited, WeVPN
|
||||
Hostnames []string `json:"hostnames"`
|
||||
@@ -137,7 +138,7 @@ func (settings *OpenVPNSelection) readProtocolAndPort(r reader) (err error) {
|
||||
|
||||
settings.CustomPort, err = readOpenVPNCustomPort(r, openvpnPortValidation{allAllowed: true})
|
||||
if err != nil {
|
||||
return fmt.Errorf("environment variable PORT: %w", err)
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
|
||||
@@ -9,27 +9,28 @@ import (
|
||||
)
|
||||
|
||||
type Updater struct {
|
||||
Period time.Duration `json:"period"`
|
||||
DNSAddress string `json:"dns_address"`
|
||||
Cyberghost bool `json:"cyberghost"`
|
||||
Expressvpn bool `json:"expressvpn"`
|
||||
Fastestvpn bool `json:"fastestvpn"`
|
||||
HideMyAss bool `json:"hidemyass"`
|
||||
Ipvanish bool `json:"ipvanish"`
|
||||
Ivpn bool `json:"ivpn"`
|
||||
Mullvad bool `json:"mullvad"`
|
||||
Nordvpn bool `json:"nordvpn"`
|
||||
PIA bool `json:"pia"`
|
||||
Privado bool `json:"privado"`
|
||||
Privatevpn bool `json:"privatevpn"`
|
||||
Protonvpn bool `json:"protonvpn"`
|
||||
Purevpn bool `json:"purevpn"`
|
||||
Surfshark bool `json:"surfshark"`
|
||||
Torguard bool `json:"torguard"`
|
||||
VPNUnlimited bool `json:"vpnunlimited"`
|
||||
Vyprvpn bool `json:"vyprvpn"`
|
||||
Wevpn bool `json:"wevpn"`
|
||||
Windscribe bool `json:"windscribe"`
|
||||
Period time.Duration `json:"period"`
|
||||
DNSAddress string `json:"dns_address"`
|
||||
Cyberghost bool `json:"cyberghost"`
|
||||
Expressvpn bool `json:"expressvpn"`
|
||||
Fastestvpn bool `json:"fastestvpn"`
|
||||
HideMyAss bool `json:"hidemyass"`
|
||||
Ipvanish bool `json:"ipvanish"`
|
||||
Ivpn bool `json:"ivpn"`
|
||||
Mullvad bool `json:"mullvad"`
|
||||
Nordvpn bool `json:"nordvpn"`
|
||||
Perfectprivacy bool `json:"perfectprivacy"`
|
||||
PIA bool `json:"pia"`
|
||||
Privado bool `json:"privado"`
|
||||
Privatevpn bool `json:"privatevpn"`
|
||||
Protonvpn bool `json:"protonvpn"`
|
||||
Purevpn bool `json:"purevpn"`
|
||||
Surfshark bool `json:"surfshark"`
|
||||
Torguard bool `json:"torguard"`
|
||||
VPNUnlimited bool `json:"vpnunlimited"`
|
||||
Vyprvpn bool `json:"vyprvpn"`
|
||||
Wevpn bool `json:"wevpn"`
|
||||
Windscribe bool `json:"windscribe"`
|
||||
// The two below should be used in CLI mode only
|
||||
CLI bool `json:"-"`
|
||||
}
|
||||
@@ -57,6 +58,7 @@ func (settings *Updater) EnableAll() {
|
||||
settings.Ivpn = true
|
||||
settings.Mullvad = true
|
||||
settings.Nordvpn = true
|
||||
settings.Perfectprivacy = true
|
||||
settings.Privado = true
|
||||
settings.PIA = true
|
||||
settings.Privado = true
|
||||
|
||||
21
internal/constants/perfectprivacy.go
Normal file
21
internal/constants/perfectprivacy.go
Normal file
@@ -0,0 +1,21 @@
|
||||
package constants
|
||||
|
||||
import (
|
||||
"github.com/qdm12/gluetun/internal/models"
|
||||
)
|
||||
|
||||
//nolint:lll
|
||||
const (
|
||||
PerfectprivacyCA = "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"
|
||||
PerfectprivacyCert = "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"
|
||||
PerfectprivacyPrivateKey = "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"
|
||||
PerfectprivacyTLSCryptOpenvpnStaticKeyV1 = "d10a8e2641f5834f6c5e04a6ee9a798553d338fa2836ef2a91057c1f6174a3a12b36f16d1110b20e42ae94d3bd579213e9c3770be6c74804348dddba876945a5a3ab7660f9436f85f331641f6efc81315f0d12b2766a9f15c10a53cf9ba32dc80f03b5f15a6cc6987bda795dbe83443ec81f3d5e161cd47fab6b1f125b3adeee1eae33370d018594e0ff6b25b815228d27371b32c82a95f4929d3abb5fa36e57bf1f42353542568fbb8233f4645f05820275f79570cb8bbcf8010fc5d20f07d031a8227d45daf7349e34158c91a3d4e5add19cfa02f683f87609f6525fa0594016d11abf2de649f83ad54edd3e74e032e34b1bca685b8499916826d9aee11c13"
|
||||
)
|
||||
|
||||
func PerfectprivacyCityChoices(servers []models.PerfectprivacyServer) (choices []string) {
|
||||
choices = make([]string, len(servers))
|
||||
for i := range servers {
|
||||
choices[i] = servers[i].City
|
||||
}
|
||||
return makeUnique(choices)
|
||||
}
|
||||
@@ -5,7 +5,7 @@ import "github.com/qdm12/gluetun/internal/models"
|
||||
//nolint:lll
|
||||
const (
|
||||
PrivatevpnCertificate = "MIIErTCCA5WgAwIBAgIJAPp3HmtYGCIOMA0GCSqGSIb3DQEBCwUAMIGVMQswCQYDVQQGEwJTRTELMAkGA1UECBMCQ0ExEjAQBgNVBAcTCVN0b2NraG9sbTETMBEGA1UEChMKUHJpdmF0ZVZQTjEWMBQGA1UEAxMNUHJpdmF0ZVZQTiBDQTETMBEGA1UEKRMKUHJpdmF0ZVZQTjEjMCEGCSqGSIb3DQEJARYUc3VwcG9ydEBwcml2YXR2cG4uc2UwHhcNMTcwNTI0MjAxNTM3WhcNMjcwNTIyMjAxNTM3WjCBlTELMAkGA1UEBhMCU0UxCzAJBgNVBAgTAkNBMRIwEAYDVQQHEwlTdG9ja2hvbG0xEzARBgNVBAoTClByaXZhdGVWUE4xFjAUBgNVBAMTDVByaXZhdGVWUE4gQ0ExEzARBgNVBCkTClByaXZhdGVWUE4xIzAhBgkqhkiG9w0BCQEWFHN1cHBvcnRAcHJpdmF0dnBuLnNlMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAwjqTWbKk85WN8nd1TaBgBnBHceQWosp8mMHr4xWMTLagWRcq2Modfy7RPnBo9kyn5j/ZZwL/21gLWJbxidurGyZZdEV9Wb5KQl3DUNxa19kwAbkkEchdES61e99MjmQlWq4vGPXAHjEuDxOZ906AXglCyAvQoXcYW0mNm9yybWllVp1aBrCaZQrNYr7eoFvolqJXdQQ3FFsTBCYa5bHJcKQLBfsiqdJ/BAxhNkQtcmWNSgLy16qoxQpCsxNCxAcYnasuL4rwOP+RazBkJTPXA/2neCJC5rt+sXR9CSfiXdJGwMpYso5m31ZEd7JL2+is0FeAZ6ETrKMnEZMsTpTkdwIDAQABo4H9MIH6MB0GA1UdDgQWBBRCkBlC94zCY6VNncMnK36JxT7bazCBygYDVR0jBIHCMIG/gBRCkBlC94zCY6VNncMnK36JxT7ba6GBm6SBmDCBlTELMAkGA1UEBhMCU0UxCzAJBgNVBAgTAkNBMRIwEAYDVQQHEwlTdG9ja2hvbG0xEzARBgNVBAoTClByaXZhdGVWUE4xFjAUBgNVBAMTDVByaXZhdGVWUE4gQ0ExEzARBgNVBCkTClByaXZhdGVWUE4xIzAhBgkqhkiG9w0BCQEWFHN1cHBvcnRAcHJpdmF0dnBuLnNlggkA+ncea1gYIg4wDAYDVR0TBAUwAwEB/zANBgkqhkiG9w0BAQsFAAOCAQEAayugvExKDHar7t1zyYn99Vt1NMf46J8x4Dt9TNjBml5mR9nKvWmreMUuuOhLaO8Da466KGdXeDFNLcBYZd/J2iTawE6/3fmrML9H2sa+k/+E4uU5nQ84ZGOwCinCkMalVjM8EZ0/H2RZvLAVUnvPuUz2JfJhmiRkbeE75fVuqpAm9qdE+/7lg3oICYzxa6BJPxT+Imdjy3Q/FWdsXqX6aallhohPAZlMZgZL4eXECnV8rAfzyjOJggkMDZQt3Flc0Y4iDMfzrEhSOWMkNFBFwjK0F/dnhsX+fPX6GGRpUZgZcCt/hWvypqc05/SnrdKM/vV/jV/yZe0NVzY7S8Ur5g=="
|
||||
PrivatevpnOpenvpnStaticKeyV1 = "a49082f082ca89d6a6bb4ecc7c047c6d428a1d3c8254a95206d38a61d7fbe65984214cd7d56eacc5a60803bffd677fa7294d4bfe555036339312de2dfb1335bd9d5fd94b04bba3a15fc5192aeb02fb6d8dd2ca831fad7509be5eefa8d1eaa689dc586c831a23b589c512662652ecf1bb3a4a673816aba434a04f6857b8c2f8bb265bfe48a7b8112539729d2f7d9734a720e1035188118c73fef1824d0237d5579ca382d703b4bb252acaedc753b12199f00154d3769efbcf85ef5ad6ee755cbeaa944cb98e7654286df54c793a8443f5363078e3da548ba0beed079df633283cefb256f6a4bcfc4ab2c4affc24955c1864d5458e84a7c210d0d186269e55dcf6"
|
||||
PrivatevpnOpenvpnStaticKeyV1 = "f035a3acaeffb5aedb5bc920bca26ca7ac701da88249008e03563eba6af6d2625ac8ba1e5e0921f76be004c24ae4fd43e42caf0f84269ad44d8d4c14ba45b1386f251c7330d8cc56afd16d516835645651ef7e87a723ac78ae0d49da5b2f2d78ceafcff7a6367d0712628a6547e5fc8fef93c87f7bcd6107c7b1ae68396e944aadae50111d01a5d0c67223d667bdbf1bf434bdef03644ecc5386e102724eef3872f66547eb66dc0fea8286069cb082a41c89083b28fe9f4cec25d48017f26c4fd85b25ddf2ae5448dd2bccf3eef2aacf42ef1e88c3248c689423d0b05a641e9e79dd6b9b5c40f0cc21ffdc891b9eee951477b537261cb56a958a4f490d961ecb"
|
||||
)
|
||||
|
||||
func PrivatevpnCountryChoices(servers []models.PrivatevpnServer) (choices []string) {
|
||||
|
||||
@@ -25,6 +25,8 @@ const (
|
||||
Mullvad = "mullvad"
|
||||
// Nordvpn is a VPN provider.
|
||||
Nordvpn = "nordvpn"
|
||||
// Perfectprivacy is a VPN provider.
|
||||
Perfectprivacy = "perfect privacy"
|
||||
// Privado is a VPN provider.
|
||||
Privado = "privado"
|
||||
// PrivateInternetAccess is a VPN provider.
|
||||
|
||||
@@ -3,6 +3,7 @@ package dns
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"net"
|
||||
|
||||
"github.com/qdm12/dns/pkg/check"
|
||||
@@ -18,7 +19,8 @@ func (l *Loop) setupUnbound(ctx context.Context) (
|
||||
cancel context.CancelFunc, waitError chan error, closeStreams func(), err error) {
|
||||
err = l.updateFiles(ctx)
|
||||
if err != nil {
|
||||
return nil, nil, nil, errUpdateFiles
|
||||
return nil, nil, nil,
|
||||
fmt.Errorf("%w: %s", errUpdateFiles, err)
|
||||
}
|
||||
|
||||
settings := l.GetSettings()
|
||||
|
||||
@@ -32,8 +32,7 @@ func (h *handler) handleHTTP(responseWriter http.ResponseWriter, request *http.R
|
||||
response, err := h.client.Do(request)
|
||||
if err != nil {
|
||||
http.Error(responseWriter, "server error", http.StatusInternalServerError)
|
||||
h.logger.Warn("cannot request " + request.URL.String() +
|
||||
" for client " + request.RemoteAddr + ": " + err.Error())
|
||||
h.logger.Warn("cannot process request for client " + request.RemoteAddr + ": " + err.Error())
|
||||
return
|
||||
}
|
||||
defer response.Body.Close()
|
||||
|
||||
@@ -14,6 +14,7 @@ func (a AllServers) GetCopy() (servers AllServers) {
|
||||
servers.Ivpn.Servers = a.GetIvpn()
|
||||
servers.Mullvad.Servers = a.GetMullvad()
|
||||
servers.Nordvpn.Servers = a.GetNordvpn()
|
||||
servers.Perfectprivacy.Servers = a.GetPerfectprivacy()
|
||||
servers.Privado.Servers = a.GetPrivado()
|
||||
servers.Pia.Servers = a.GetPia()
|
||||
servers.Privatevpn.Servers = a.GetPrivatevpn()
|
||||
@@ -124,6 +125,18 @@ func (a *AllServers) GetNordvpn() (servers []NordvpnServer) {
|
||||
return servers
|
||||
}
|
||||
|
||||
func (a *AllServers) GetPerfectprivacy() (servers []PerfectprivacyServer) {
|
||||
if a.Perfectprivacy.Servers == nil {
|
||||
return nil
|
||||
}
|
||||
servers = make([]PerfectprivacyServer, len(a.Perfectprivacy.Servers))
|
||||
for i, serverToCopy := range a.Perfectprivacy.Servers {
|
||||
servers[i] = serverToCopy
|
||||
servers[i].IPs = copyIPs(serverToCopy.IPs)
|
||||
}
|
||||
return servers
|
||||
}
|
||||
|
||||
func (a *AllServers) GetPia() (servers []PIAServer) {
|
||||
if a.Pia.Servers == nil {
|
||||
return nil
|
||||
@@ -168,7 +181,7 @@ func (a *AllServers) GetProtonvpn() (servers []ProtonvpnServer) {
|
||||
for i, serverToCopy := range a.Protonvpn.Servers {
|
||||
servers[i] = serverToCopy
|
||||
servers[i].EntryIP = copyIP(serverToCopy.EntryIP)
|
||||
servers[i].ExitIP = copyIP(serverToCopy.ExitIP)
|
||||
servers[i].ExitIPs = copyIPs(serverToCopy.ExitIPs)
|
||||
}
|
||||
return servers
|
||||
}
|
||||
|
||||
@@ -51,6 +51,11 @@ func Test_AllServers_GetCopy(t *testing.T) {
|
||||
IP: net.IP{1, 2, 3, 4},
|
||||
}},
|
||||
},
|
||||
Perfectprivacy: PerfectprivacyServers{
|
||||
Servers: []PerfectprivacyServer{{
|
||||
IPs: []net.IP{{1, 2, 3, 4}},
|
||||
}},
|
||||
},
|
||||
Privado: PrivadoServers{
|
||||
Servers: []PrivadoServer{{
|
||||
IP: net.IP{1, 2, 3, 4},
|
||||
@@ -69,7 +74,7 @@ func Test_AllServers_GetCopy(t *testing.T) {
|
||||
Protonvpn: ProtonvpnServers{
|
||||
Servers: []ProtonvpnServer{{
|
||||
EntryIP: net.IP{1, 2, 3, 4},
|
||||
ExitIP: net.IP{1, 2, 3, 4},
|
||||
ExitIPs: []net.IP{{1, 2, 3, 4}},
|
||||
}},
|
||||
},
|
||||
Purevpn: PurevpnServers{
|
||||
|
||||
@@ -9,7 +9,7 @@ func boolToMarkdown(b bool) string {
|
||||
if b {
|
||||
return "✅"
|
||||
}
|
||||
return "❎"
|
||||
return "❌"
|
||||
}
|
||||
|
||||
func markdownTableHeading(legendFields ...string) (markdown string) {
|
||||
@@ -141,6 +141,19 @@ func (s *PrivadoServer) ToMarkdown() (markdown string) {
|
||||
s.Country, s.Region, s.City, s.Hostname)
|
||||
}
|
||||
|
||||
func (s *PerfectprivacyServers) ToMarkdown() (markdown string) {
|
||||
markdown = markdownTableHeading("City", "TCP", "UDP")
|
||||
for _, server := range s.Servers {
|
||||
markdown += server.ToMarkdown() + "\n"
|
||||
}
|
||||
return markdown
|
||||
}
|
||||
|
||||
func (s *PerfectprivacyServer) ToMarkdown() (markdown string) {
|
||||
return fmt.Sprintf("| %s | %s | %s |",
|
||||
s.City, boolToMarkdown(s.TCP), boolToMarkdown(s.UDP))
|
||||
}
|
||||
|
||||
func (s *PiaServers) ToMarkdown() (markdown string) {
|
||||
markdown = markdownTableHeading("Region", "Hostname", "TCP", "UDP")
|
||||
for _, server := range s.Servers {
|
||||
|
||||
@@ -19,8 +19,8 @@ func Test_CyberghostServers_ToMarkdown(t *testing.T) {
|
||||
markdown := servers.ToMarkdown()
|
||||
const expected = "| Country | Hostname | TCP | UDP |\n" +
|
||||
"| --- | --- | --- | --- |\n" +
|
||||
"| a | `xa` | ❎ | ✅ |\n" +
|
||||
"| b | `xb` | ✅ | ❎ |\n"
|
||||
"| a | `xa` | ❌ | ✅ |\n" +
|
||||
"| b | `xb` | ✅ | ❌ |\n"
|
||||
|
||||
assert.Equal(t, expected, markdown)
|
||||
}
|
||||
@@ -38,8 +38,8 @@ func Test_FastestvpnServers_ToMarkdown(t *testing.T) {
|
||||
markdown := servers.ToMarkdown()
|
||||
const expected = "| Country | Hostname | TCP | UDP |\n" +
|
||||
"| --- | --- | --- | --- |\n" +
|
||||
"| a | `xa` | ✅ | ❎ |\n" +
|
||||
"| b | `xb` | ❎ | ✅ |\n"
|
||||
"| a | `xa` | ✅ | ❌ |\n" +
|
||||
"| b | `xb` | ❌ | ✅ |\n"
|
||||
|
||||
assert.Equal(t, expected, markdown)
|
||||
}
|
||||
|
||||
@@ -81,6 +81,13 @@ type NordvpnServer struct { //nolint:maligned
|
||||
UDP bool `json:"udp"`
|
||||
}
|
||||
|
||||
type PerfectprivacyServer struct {
|
||||
City string `json:"city"` // primary key
|
||||
IPs []net.IP `json:"ips"`
|
||||
TCP bool `json:"tcp"`
|
||||
UDP bool `json:"udp"`
|
||||
}
|
||||
|
||||
type PrivadoServer struct {
|
||||
Country string `json:"country"`
|
||||
Region string `json:"region"`
|
||||
@@ -107,13 +114,13 @@ type PrivatevpnServer struct {
|
||||
}
|
||||
|
||||
type ProtonvpnServer struct {
|
||||
Country string `json:"country"`
|
||||
Region string `json:"region"`
|
||||
City string `json:"city"`
|
||||
Name string `json:"name"`
|
||||
Hostname string `json:"hostname"`
|
||||
EntryIP net.IP `json:"entry_ip"`
|
||||
ExitIP net.IP `json:"exit_ip"` // TODO verify it matches with public IP once connected
|
||||
Country string `json:"country"`
|
||||
Region string `json:"region"`
|
||||
City string `json:"city"`
|
||||
Name string `json:"name"`
|
||||
Hostname string `json:"hostname"`
|
||||
EntryIP net.IP `json:"entry_ip"`
|
||||
ExitIPs []net.IP `json:"exit_ip"` // TODO verify it matches with public IP once connected
|
||||
}
|
||||
|
||||
type PurevpnServer struct {
|
||||
|
||||
@@ -1,26 +1,27 @@
|
||||
package models
|
||||
|
||||
type AllServers struct {
|
||||
Version uint16 `json:"version"` // used for migration of the top level scheme
|
||||
Cyberghost CyberghostServers `json:"cyberghost"`
|
||||
Expressvpn ExpressvpnServers `json:"expressvpn"`
|
||||
Fastestvpn FastestvpnServers `json:"fastestvpn"`
|
||||
HideMyAss HideMyAssServers `json:"hidemyass"`
|
||||
Ipvanish IpvanishServers `json:"ipvanish"`
|
||||
Ivpn IvpnServers `json:"ivpn"`
|
||||
Mullvad MullvadServers `json:"mullvad"`
|
||||
Nordvpn NordvpnServers `json:"nordvpn"`
|
||||
Privado PrivadoServers `json:"privado"`
|
||||
Pia PiaServers `json:"pia"`
|
||||
Privatevpn PrivatevpnServers `json:"privatevpn"`
|
||||
Protonvpn ProtonvpnServers `json:"protonvpn"`
|
||||
Purevpn PurevpnServers `json:"purevpn"`
|
||||
Surfshark SurfsharkServers `json:"surfshark"`
|
||||
Torguard TorguardServers `json:"torguard"`
|
||||
VPNUnlimited VPNUnlimitedServers `json:"vpnunlimited"`
|
||||
Vyprvpn VyprvpnServers `json:"vyprvpn"`
|
||||
Wevpn WevpnServers `json:"wevpn"`
|
||||
Windscribe WindscribeServers `json:"windscribe"`
|
||||
Version uint16 `json:"version"` // used for migration of the top level scheme
|
||||
Cyberghost CyberghostServers `json:"cyberghost"`
|
||||
Expressvpn ExpressvpnServers `json:"expressvpn"`
|
||||
Fastestvpn FastestvpnServers `json:"fastestvpn"`
|
||||
HideMyAss HideMyAssServers `json:"hidemyass"`
|
||||
Ipvanish IpvanishServers `json:"ipvanish"`
|
||||
Ivpn IvpnServers `json:"ivpn"`
|
||||
Mullvad MullvadServers `json:"mullvad"`
|
||||
Perfectprivacy PerfectprivacyServers `json:"perfectprivacy"`
|
||||
Nordvpn NordvpnServers `json:"nordvpn"`
|
||||
Privado PrivadoServers `json:"privado"`
|
||||
Pia PiaServers `json:"pia"`
|
||||
Privatevpn PrivatevpnServers `json:"privatevpn"`
|
||||
Protonvpn ProtonvpnServers `json:"protonvpn"`
|
||||
Purevpn PurevpnServers `json:"purevpn"`
|
||||
Surfshark SurfsharkServers `json:"surfshark"`
|
||||
Torguard TorguardServers `json:"torguard"`
|
||||
VPNUnlimited VPNUnlimitedServers `json:"vpnunlimited"`
|
||||
Vyprvpn VyprvpnServers `json:"vyprvpn"`
|
||||
Wevpn WevpnServers `json:"wevpn"`
|
||||
Windscribe WindscribeServers `json:"windscribe"`
|
||||
}
|
||||
|
||||
func (a *AllServers) Count() int {
|
||||
@@ -32,6 +33,7 @@ func (a *AllServers) Count() int {
|
||||
len(a.Ivpn.Servers) +
|
||||
len(a.Mullvad.Servers) +
|
||||
len(a.Nordvpn.Servers) +
|
||||
len(a.Perfectprivacy.Servers) +
|
||||
len(a.Privado.Servers) +
|
||||
len(a.Pia.Servers) +
|
||||
len(a.Privatevpn.Servers) +
|
||||
@@ -85,6 +87,11 @@ type NordvpnServers struct {
|
||||
Timestamp int64 `json:"timestamp"`
|
||||
Servers []NordvpnServer `json:"servers"`
|
||||
}
|
||||
type PerfectprivacyServers struct {
|
||||
Version uint16 `json:"version"`
|
||||
Timestamp int64 `json:"timestamp"`
|
||||
Servers []PerfectprivacyServer `json:"servers"`
|
||||
}
|
||||
type PrivadoServers struct {
|
||||
Version uint16 `json:"version"`
|
||||
Timestamp int64 `json:"timestamp"`
|
||||
|
||||
@@ -11,8 +11,6 @@ var (
|
||||
)
|
||||
|
||||
func extractPEM(b []byte, name string) (encodedData string, err error) {
|
||||
name = strings.ToUpper(name) // certificate -> CERTIFICATE
|
||||
|
||||
pemBlock, _ := pem.Decode(b)
|
||||
if pemBlock == nil {
|
||||
return "", errPEMDecode
|
||||
|
||||
@@ -25,7 +25,7 @@ func Test_extractPEM(t *testing.T) {
|
||||
err: errors.New("cannot decode PEM encoded block"),
|
||||
},
|
||||
"valid data": {
|
||||
name: "certificate",
|
||||
name: "CERTIFICATE",
|
||||
b: []byte(validCertPEM),
|
||||
encodedData: validCertData,
|
||||
},
|
||||
|
||||
@@ -15,7 +15,7 @@ var ErrVersionUnknown = errors.New("OpenVPN version is unknown")
|
||||
|
||||
const (
|
||||
binOpenvpn24 = "openvpn2.4"
|
||||
binOpenvpn25 = "openvpn"
|
||||
binOpenvpn25 = "openvpn2.5"
|
||||
)
|
||||
|
||||
func start(ctx context.Context, starter command.Starter, version string, flags []string) (
|
||||
|
||||
@@ -50,8 +50,8 @@ func modifyConfig(lines []string, connection models.Connection,
|
||||
strings.HasPrefix(line, "remote "),
|
||||
strings.HasPrefix(line, "dev "),
|
||||
// Remove values eventually modified
|
||||
settings.Cipher != "" && hasPrefixOneOf(line,
|
||||
"cipher ", "data-ciphers ", "data-ciphers-fallback "),
|
||||
len(settings.Ciphers) > 0 && hasPrefixOneOf(line,
|
||||
"cipher ", "ncp-ciphers ", "data-ciphers ", "data-ciphers-fallback "),
|
||||
settings.Auth != "" && strings.HasPrefix(line, "auth "),
|
||||
settings.MSSFix > 0 && strings.HasPrefix(line, "mssfix "),
|
||||
!settings.IPv6 && hasPrefixOneOf(line, "tun-ipv6",
|
||||
@@ -75,8 +75,8 @@ func modifyConfig(lines []string, connection models.Connection,
|
||||
modified = append(modified, "auth-user-pass "+constants.OpenVPNAuthConf)
|
||||
}
|
||||
modified = append(modified, "verb "+strconv.Itoa(settings.Verbosity))
|
||||
if settings.Cipher != "" {
|
||||
modified = append(modified, utils.CipherLines(settings.Cipher, settings.Version)...)
|
||||
if len(settings.Ciphers) > 0 {
|
||||
modified = append(modified, utils.CipherLines(settings.Ciphers, settings.Version)...)
|
||||
}
|
||||
if settings.Auth != "" {
|
||||
modified = append(modified, "auth "+settings.Auth)
|
||||
|
||||
@@ -32,7 +32,7 @@ func Test_modifyConfig(t *testing.T) {
|
||||
},
|
||||
settings: configuration.OpenVPN{
|
||||
User: "user",
|
||||
Cipher: "cipher",
|
||||
Ciphers: []string{"cipher"},
|
||||
Auth: "auth",
|
||||
MSSFix: 1000,
|
||||
ProcUser: "procuser",
|
||||
|
||||
@@ -2,7 +2,6 @@ package cyberghost
|
||||
|
||||
import (
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/qdm12/gluetun/internal/configuration"
|
||||
"github.com/qdm12/gluetun/internal/constants"
|
||||
@@ -12,8 +11,12 @@ import (
|
||||
|
||||
func (c *Cyberghost) BuildConf(connection models.Connection,
|
||||
settings configuration.OpenVPN) (lines []string, err error) {
|
||||
if settings.Cipher == "" {
|
||||
settings.Cipher = constants.AES128gcm
|
||||
if len(settings.Ciphers) == 0 {
|
||||
settings.Ciphers = []string{
|
||||
constants.AES256gcm,
|
||||
constants.AES256cbc,
|
||||
constants.AES128gcm,
|
||||
}
|
||||
}
|
||||
|
||||
if settings.Auth == "" {
|
||||
@@ -45,16 +48,12 @@ func (c *Cyberghost) BuildConf(connection models.Connection,
|
||||
connection.OpenVPNRemoteLine(),
|
||||
}
|
||||
|
||||
lines = append(lines, utils.CipherLines(settings.Cipher, settings.Version)...)
|
||||
lines = append(lines, utils.CipherLines(settings.Ciphers, settings.Version)...)
|
||||
|
||||
if connection.Protocol == constants.UDP {
|
||||
lines = append(lines, "explicit-exit-notify")
|
||||
}
|
||||
|
||||
if strings.HasSuffix(settings.Cipher, "-gcm") {
|
||||
lines = append(lines, "ncp-ciphers AES-256-GCM:AES-256-CBC:AES-128-GCM")
|
||||
}
|
||||
|
||||
if !settings.Root {
|
||||
lines = append(lines, "user "+settings.ProcUser)
|
||||
lines = append(lines, "persist-tun")
|
||||
|
||||
@@ -11,8 +11,8 @@ import (
|
||||
|
||||
func (p *Provider) BuildConf(connection models.Connection,
|
||||
settings configuration.OpenVPN) (lines []string, err error) {
|
||||
if settings.Cipher == "" {
|
||||
settings.Cipher = constants.AES256cbc
|
||||
if len(settings.Ciphers) == 0 {
|
||||
settings.Ciphers = []string{constants.AES256cbc}
|
||||
}
|
||||
if settings.Auth == "" {
|
||||
settings.Auth = constants.SHA512
|
||||
@@ -53,7 +53,7 @@ func (p *Provider) BuildConf(connection models.Connection,
|
||||
connection.OpenVPNRemoteLine(),
|
||||
}
|
||||
|
||||
lines = append(lines, utils.CipherLines(settings.Cipher, settings.Version)...)
|
||||
lines = append(lines, utils.CipherLines(settings.Ciphers, settings.Version)...)
|
||||
|
||||
if connection.Protocol == constants.UDP {
|
||||
lines = append(lines, "explicit-exit-notify")
|
||||
|
||||
@@ -11,8 +11,8 @@ import (
|
||||
|
||||
func (f *Fastestvpn) BuildConf(connection models.Connection,
|
||||
settings configuration.OpenVPN) (lines []string, err error) {
|
||||
if settings.Cipher == "" {
|
||||
settings.Cipher = constants.AES256cbc
|
||||
if len(settings.Ciphers) == 0 {
|
||||
settings.Ciphers = []string{constants.AES256cbc}
|
||||
}
|
||||
if settings.Auth == "" {
|
||||
settings.Auth = constants.SHA256
|
||||
@@ -49,7 +49,7 @@ func (f *Fastestvpn) BuildConf(connection models.Connection,
|
||||
connection.OpenVPNRemoteLine(),
|
||||
}
|
||||
|
||||
lines = append(lines, utils.CipherLines(settings.Cipher, settings.Version)...)
|
||||
lines = append(lines, utils.CipherLines(settings.Ciphers, settings.Version)...)
|
||||
|
||||
if connection.Protocol == constants.UDP {
|
||||
lines = append(lines, "explicit-exit-notify")
|
||||
|
||||
@@ -11,8 +11,8 @@ import (
|
||||
|
||||
func (h *HideMyAss) BuildConf(connection models.Connection,
|
||||
settings configuration.OpenVPN) (lines []string, err error) {
|
||||
if settings.Cipher == "" {
|
||||
settings.Cipher = constants.AES256cbc
|
||||
if len(settings.Ciphers) == 0 {
|
||||
settings.Ciphers = []string{constants.AES256cbc}
|
||||
}
|
||||
|
||||
lines = []string{
|
||||
@@ -39,7 +39,7 @@ func (h *HideMyAss) BuildConf(connection models.Connection,
|
||||
connection.OpenVPNRemoteLine(),
|
||||
}
|
||||
|
||||
lines = append(lines, utils.CipherLines(settings.Cipher, settings.Version)...)
|
||||
lines = append(lines, utils.CipherLines(settings.Ciphers, settings.Version)...)
|
||||
|
||||
if settings.Auth != "" {
|
||||
lines = append(lines, "auth "+settings.Auth)
|
||||
|
||||
@@ -11,8 +11,8 @@ import (
|
||||
|
||||
func (i *Ipvanish) BuildConf(connection models.Connection,
|
||||
settings configuration.OpenVPN) (lines []string, err error) {
|
||||
if settings.Cipher == "" {
|
||||
settings.Cipher = constants.AES256cbc
|
||||
if len(settings.Ciphers) == 0 {
|
||||
settings.Ciphers = []string{constants.AES256cbc}
|
||||
}
|
||||
if settings.Auth == "" {
|
||||
settings.Auth = constants.SHA256
|
||||
@@ -43,7 +43,7 @@ func (i *Ipvanish) BuildConf(connection models.Connection,
|
||||
connection.OpenVPNRemoteLine(),
|
||||
}
|
||||
|
||||
lines = append(lines, utils.CipherLines(settings.Cipher, settings.Version)...)
|
||||
lines = append(lines, utils.CipherLines(settings.Ciphers, settings.Version)...)
|
||||
|
||||
if settings.MSSFix > 0 {
|
||||
lines = append(lines, "mssfix "+strconv.Itoa(int(settings.MSSFix)))
|
||||
|
||||
@@ -12,8 +12,8 @@ import (
|
||||
|
||||
func (i *Ivpn) BuildConf(connection models.Connection,
|
||||
settings configuration.OpenVPN) (lines []string, err error) {
|
||||
if settings.Cipher == "" {
|
||||
settings.Cipher = constants.AES256cbc
|
||||
if len(settings.Ciphers) == 0 {
|
||||
settings.Ciphers = []string{constants.AES256cbc}
|
||||
}
|
||||
|
||||
namePrefix := strings.Split(connection.Hostname, ".")[0]
|
||||
@@ -45,7 +45,7 @@ func (i *Ivpn) BuildConf(connection models.Connection,
|
||||
connection.OpenVPNRemoteLine(),
|
||||
}
|
||||
|
||||
lines = append(lines, utils.CipherLines(settings.Cipher, settings.Version)...)
|
||||
lines = append(lines, utils.CipherLines(settings.Ciphers, settings.Version)...)
|
||||
|
||||
if settings.Auth != "" {
|
||||
lines = append(lines, "auth "+settings.Auth)
|
||||
|
||||
@@ -11,8 +11,8 @@ import (
|
||||
|
||||
func (m *Mullvad) BuildConf(connection models.Connection,
|
||||
settings configuration.OpenVPN) (lines []string, err error) {
|
||||
if settings.Cipher == "" {
|
||||
settings.Cipher = constants.AES256cbc
|
||||
if len(settings.Ciphers) == 0 {
|
||||
settings.Ciphers = []string{constants.AES256cbc, constants.AES128gcm}
|
||||
}
|
||||
|
||||
lines = []string{
|
||||
@@ -42,7 +42,7 @@ func (m *Mullvad) BuildConf(connection models.Connection,
|
||||
connection.OpenVPNRemoteLine(),
|
||||
}
|
||||
|
||||
lines = append(lines, utils.CipherLines(settings.Cipher, settings.Version)...)
|
||||
lines = append(lines, utils.CipherLines(settings.Ciphers, settings.Version)...)
|
||||
|
||||
if settings.Auth != "" {
|
||||
lines = append(lines, "auth "+settings.Auth)
|
||||
|
||||
@@ -11,8 +11,8 @@ import (
|
||||
|
||||
func (n *Nordvpn) BuildConf(connection models.Connection,
|
||||
settings configuration.OpenVPN) (lines []string, err error) {
|
||||
if settings.Cipher == "" {
|
||||
settings.Cipher = constants.AES256cbc
|
||||
if len(settings.Ciphers) == 0 {
|
||||
settings.Ciphers = []string{constants.AES256cbc}
|
||||
}
|
||||
|
||||
if settings.Auth == "" {
|
||||
@@ -39,6 +39,7 @@ func (n *Nordvpn) BuildConf(connection models.Connection,
|
||||
"key-direction 1",
|
||||
"auth-user-pass " + constants.OpenVPNAuthConf,
|
||||
"auth " + settings.Auth,
|
||||
"comp-lzo", // Required, NordVPN does not work without it
|
||||
|
||||
// Added constant values
|
||||
"auth-nocache",
|
||||
@@ -52,7 +53,7 @@ func (n *Nordvpn) BuildConf(connection models.Connection,
|
||||
connection.OpenVPNRemoteLine(),
|
||||
}
|
||||
|
||||
lines = append(lines, utils.CipherLines(settings.Cipher, settings.Version)...)
|
||||
lines = append(lines, utils.CipherLines(settings.Ciphers, settings.Version)...)
|
||||
|
||||
if connection.Protocol == constants.UDP {
|
||||
lines = append(lines, "fast-io")
|
||||
|
||||
37
internal/provider/perfectprivacy/connection.go
Normal file
37
internal/provider/perfectprivacy/connection.go
Normal file
@@ -0,0 +1,37 @@
|
||||
package perfectprivacy
|
||||
|
||||
import (
|
||||
"github.com/qdm12/gluetun/internal/configuration"
|
||||
"github.com/qdm12/gluetun/internal/models"
|
||||
"github.com/qdm12/gluetun/internal/provider/utils"
|
||||
)
|
||||
|
||||
func (p *Perfectprivacy) GetConnection(selection configuration.ServerSelection) (
|
||||
connection models.Connection, err error) {
|
||||
const defaultPort uint16 = 443
|
||||
port := defaultPort
|
||||
if selection.OpenVPN.CustomPort > 0 {
|
||||
port = selection.OpenVPN.CustomPort
|
||||
}
|
||||
protocol := utils.GetProtocol(selection)
|
||||
|
||||
servers, err := p.filterServers(selection)
|
||||
if err != nil {
|
||||
return connection, err
|
||||
}
|
||||
|
||||
var connections []models.Connection
|
||||
for _, server := range servers {
|
||||
for _, IP := range server.IPs {
|
||||
connection := models.Connection{
|
||||
Type: selection.VPN,
|
||||
IP: IP,
|
||||
Port: port,
|
||||
Protocol: protocol,
|
||||
}
|
||||
connections = append(connections, connection)
|
||||
}
|
||||
}
|
||||
|
||||
return utils.PickConnection(connections, selection, p.randSource)
|
||||
}
|
||||
26
internal/provider/perfectprivacy/filter.go
Normal file
26
internal/provider/perfectprivacy/filter.go
Normal file
@@ -0,0 +1,26 @@
|
||||
package perfectprivacy
|
||||
|
||||
import (
|
||||
"github.com/qdm12/gluetun/internal/configuration"
|
||||
"github.com/qdm12/gluetun/internal/models"
|
||||
"github.com/qdm12/gluetun/internal/provider/utils"
|
||||
)
|
||||
|
||||
func (p *Perfectprivacy) filterServers(selection configuration.ServerSelection) (
|
||||
servers []models.PerfectprivacyServer, err error) {
|
||||
for _, server := range p.servers {
|
||||
switch {
|
||||
case
|
||||
utils.FilterByPossibilities(server.City, selection.Cities),
|
||||
utils.FilterByProtocol(selection, server.TCP, server.UDP):
|
||||
default:
|
||||
servers = append(servers, server)
|
||||
}
|
||||
}
|
||||
|
||||
if len(servers) == 0 {
|
||||
return nil, utils.NoServerFoundError(selection)
|
||||
}
|
||||
|
||||
return servers, nil
|
||||
}
|
||||
88
internal/provider/perfectprivacy/openvpnconf.go
Normal file
88
internal/provider/perfectprivacy/openvpnconf.go
Normal file
@@ -0,0 +1,88 @@
|
||||
package perfectprivacy
|
||||
|
||||
import (
|
||||
"strconv"
|
||||
|
||||
"github.com/qdm12/gluetun/internal/configuration"
|
||||
"github.com/qdm12/gluetun/internal/constants"
|
||||
"github.com/qdm12/gluetun/internal/models"
|
||||
"github.com/qdm12/gluetun/internal/provider/utils"
|
||||
)
|
||||
|
||||
func (p *Perfectprivacy) BuildConf(connection models.Connection,
|
||||
settings configuration.OpenVPN) (lines []string, err error) {
|
||||
if len(settings.Ciphers) == 0 {
|
||||
settings.Ciphers = []string{constants.AES256cbc, constants.AES256gcm}
|
||||
}
|
||||
|
||||
if settings.Auth == "" {
|
||||
settings.Auth = constants.SHA512
|
||||
}
|
||||
|
||||
if settings.MSSFix == 0 {
|
||||
settings.MSSFix = 1450
|
||||
}
|
||||
|
||||
lines = []string{
|
||||
"client",
|
||||
"nobind",
|
||||
"tls-exit",
|
||||
"dev " + settings.Interface,
|
||||
"verb " + strconv.Itoa(settings.Verbosity),
|
||||
|
||||
// Perfect Privacy specific
|
||||
"ping 5",
|
||||
"tun-mtu 1500",
|
||||
"tun-mtu-extra 32",
|
||||
"mssfix " + strconv.Itoa(int(settings.MSSFix)),
|
||||
"reneg-sec 3600",
|
||||
"key-direction 1",
|
||||
"tls-cipher TLS_CHACHA20_POLY1305_SHA256:TLS-DHE-RSA-WITH-AES-256-GCM-SHA384:TLS-DHE-RSA-WITH-AES-256-CBC-SHA:TLS-DHE-RSA-WITH-AES-128-GCM-SHA256:TLS-DHE-RSA-WITH-AES-128-CBC-SHA:TLS_AES_256_GCM_SHA384:TLS-RSA-WITH-AES-256-CBC-SHA", //nolint:lll
|
||||
"auth-user-pass " + constants.OpenVPNAuthConf,
|
||||
"auth " + settings.Auth,
|
||||
|
||||
// Added constant values
|
||||
"auth-nocache",
|
||||
"mute-replay-warnings",
|
||||
"pull-filter ignore \"auth-token\"", // prevent auth failed loops
|
||||
"auth-retry nointeract",
|
||||
"suppress-timestamps",
|
||||
|
||||
// Modified variables
|
||||
connection.OpenVPNProtoLine(),
|
||||
connection.OpenVPNRemoteLine(),
|
||||
}
|
||||
|
||||
lines = append(lines, utils.CipherLines(settings.Ciphers, settings.Version)...)
|
||||
|
||||
if connection.Protocol == constants.UDP {
|
||||
lines = append(lines, "explicit-exit-notify")
|
||||
}
|
||||
|
||||
if !settings.Root {
|
||||
lines = append(lines, "user "+settings.ProcUser)
|
||||
lines = append(lines, "persist-tun")
|
||||
lines = append(lines, "persist-key")
|
||||
}
|
||||
|
||||
if !settings.IPv6 {
|
||||
lines = append(lines, `pull-filter ignore "route-ipv6"`)
|
||||
lines = append(lines, `pull-filter ignore "ifconfig-ipv6"`)
|
||||
// Perfect Privacy specific IPv6
|
||||
lines = append(lines, "redirect-gateway def1")
|
||||
lines = append(lines, `pull-filter ignore "redirect-gateway def1 ipv6"`)
|
||||
}
|
||||
|
||||
lines = append(lines, utils.WrapOpenvpnCA(
|
||||
constants.PerfectprivacyCA)...)
|
||||
lines = append(lines, utils.WrapOpenvpnCert(
|
||||
constants.PerfectprivacyCert)...)
|
||||
lines = append(lines, utils.WrapOpenvpnKey(
|
||||
constants.PerfectprivacyPrivateKey)...)
|
||||
lines = append(lines, utils.WrapOpenvpnTLSCrypt(
|
||||
constants.PerfectprivacyTLSCryptOpenvpnStaticKeyV1)...)
|
||||
|
||||
lines = append(lines, "")
|
||||
|
||||
return lines, nil
|
||||
}
|
||||
23
internal/provider/perfectprivacy/provider.go
Normal file
23
internal/provider/perfectprivacy/provider.go
Normal file
@@ -0,0 +1,23 @@
|
||||
package perfectprivacy
|
||||
|
||||
import (
|
||||
"math/rand"
|
||||
|
||||
"github.com/qdm12/gluetun/internal/constants"
|
||||
"github.com/qdm12/gluetun/internal/models"
|
||||
"github.com/qdm12/gluetun/internal/provider/utils"
|
||||
)
|
||||
|
||||
type Perfectprivacy struct {
|
||||
servers []models.PerfectprivacyServer
|
||||
randSource rand.Source
|
||||
utils.NoPortForwarder
|
||||
}
|
||||
|
||||
func New(servers []models.PerfectprivacyServer, randSource rand.Source) *Perfectprivacy {
|
||||
return &Perfectprivacy{
|
||||
servers: servers,
|
||||
randSource: randSource,
|
||||
NoPortForwarder: utils.NewNoPortForwarding(constants.Perfectprivacy),
|
||||
}
|
||||
}
|
||||
@@ -11,8 +11,8 @@ import (
|
||||
|
||||
func (p *Privado) BuildConf(connection models.Connection,
|
||||
settings configuration.OpenVPN) (lines []string, err error) {
|
||||
if settings.Cipher == "" {
|
||||
settings.Cipher = constants.AES256cbc
|
||||
if len(settings.Ciphers) == 0 {
|
||||
settings.Ciphers = []string{constants.AES256cbc}
|
||||
}
|
||||
|
||||
if settings.Auth == "" {
|
||||
@@ -45,7 +45,7 @@ func (p *Privado) BuildConf(connection models.Connection,
|
||||
connection.OpenVPNRemoteLine(),
|
||||
}
|
||||
|
||||
lines = append(lines, utils.CipherLines(settings.Cipher, settings.Version)...)
|
||||
lines = append(lines, utils.CipherLines(settings.Ciphers, settings.Version)...)
|
||||
|
||||
if !settings.Root {
|
||||
lines = append(lines, "user "+settings.ProcUser)
|
||||
|
||||
@@ -30,8 +30,8 @@ func (p *PIA) BuildConf(connection models.Connection,
|
||||
certificate = constants.PIACertificateStrong
|
||||
}
|
||||
|
||||
if settings.Cipher == "" {
|
||||
settings.Cipher = defaultCipher
|
||||
if len(settings.Ciphers) == 0 {
|
||||
settings.Ciphers = []string{defaultCipher}
|
||||
}
|
||||
|
||||
if settings.Auth == "" {
|
||||
@@ -62,8 +62,8 @@ func (p *PIA) BuildConf(connection models.Connection,
|
||||
connection.OpenVPNRemoteLine(),
|
||||
}
|
||||
|
||||
if settings.Cipher != "" {
|
||||
lines = append(lines, utils.CipherLines(settings.Cipher, settings.Version)...)
|
||||
if len(settings.Ciphers) > 0 {
|
||||
lines = append(lines, utils.CipherLines(settings.Ciphers, settings.Version)...)
|
||||
}
|
||||
|
||||
if settings.Auth != "" {
|
||||
|
||||
@@ -15,6 +15,9 @@ func (p *Privatevpn) GetConnection(selection configuration.ServerSelection) (
|
||||
protocol = constants.TCP
|
||||
port = 443
|
||||
}
|
||||
if selection.OpenVPN.CustomPort > 0 {
|
||||
port = selection.OpenVPN.CustomPort
|
||||
}
|
||||
|
||||
servers, err := p.filterServers(selection)
|
||||
if err != nil {
|
||||
|
||||
@@ -11,8 +11,8 @@ import (
|
||||
|
||||
func (p *Privatevpn) BuildConf(connection models.Connection,
|
||||
settings configuration.OpenVPN) (lines []string, err error) {
|
||||
if settings.Cipher == "" {
|
||||
settings.Cipher = constants.AES128gcm
|
||||
if len(settings.Ciphers) == 0 {
|
||||
settings.Ciphers = []string{constants.AES128gcm}
|
||||
}
|
||||
|
||||
if settings.Auth == "" {
|
||||
@@ -43,7 +43,7 @@ func (p *Privatevpn) BuildConf(connection models.Connection,
|
||||
connection.OpenVPNRemoteLine(),
|
||||
}
|
||||
|
||||
lines = append(lines, utils.CipherLines(settings.Cipher, settings.Version)...)
|
||||
lines = append(lines, utils.CipherLines(settings.Ciphers, settings.Version)...)
|
||||
|
||||
if connection.Protocol == constants.UDP {
|
||||
lines = append(lines, "key-direction 1")
|
||||
@@ -67,7 +67,7 @@ func (p *Privatevpn) BuildConf(connection models.Connection,
|
||||
|
||||
lines = append(lines, utils.WrapOpenvpnCA(
|
||||
constants.PrivatevpnCertificate)...)
|
||||
lines = append(lines, utils.WrapOpenvpnTLSCrypt(
|
||||
lines = append(lines, utils.WrapOpenvpnTLSAuth(
|
||||
constants.PrivatevpnOpenvpnStaticKeyV1)...)
|
||||
|
||||
lines = append(lines, "")
|
||||
|
||||
@@ -11,8 +11,8 @@ import (
|
||||
|
||||
func (p *Protonvpn) BuildConf(connection models.Connection,
|
||||
settings configuration.OpenVPN) (lines []string, err error) {
|
||||
if settings.Cipher == "" {
|
||||
settings.Cipher = constants.AES256cbc
|
||||
if len(settings.Ciphers) == 0 {
|
||||
settings.Ciphers = []string{constants.AES256cbc}
|
||||
}
|
||||
|
||||
if settings.Auth == "" {
|
||||
@@ -52,7 +52,7 @@ func (p *Protonvpn) BuildConf(connection models.Connection,
|
||||
connection.OpenVPNRemoteLine(),
|
||||
}
|
||||
|
||||
lines = append(lines, utils.CipherLines(settings.Cipher, settings.Version)...)
|
||||
lines = append(lines, utils.CipherLines(settings.Ciphers, settings.Version)...)
|
||||
|
||||
if connection.Protocol == constants.UDP {
|
||||
lines = append(lines, "fast-io")
|
||||
|
||||
@@ -20,6 +20,7 @@ import (
|
||||
"github.com/qdm12/gluetun/internal/provider/ivpn"
|
||||
"github.com/qdm12/gluetun/internal/provider/mullvad"
|
||||
"github.com/qdm12/gluetun/internal/provider/nordvpn"
|
||||
"github.com/qdm12/gluetun/internal/provider/perfectprivacy"
|
||||
"github.com/qdm12/gluetun/internal/provider/privado"
|
||||
"github.com/qdm12/gluetun/internal/provider/privateinternetaccess"
|
||||
"github.com/qdm12/gluetun/internal/provider/privatevpn"
|
||||
@@ -70,6 +71,8 @@ func New(provider string, allServers models.AllServers, timeNow func() time.Time
|
||||
return mullvad.New(allServers.Mullvad.Servers, randSource)
|
||||
case constants.Nordvpn:
|
||||
return nordvpn.New(allServers.Nordvpn.Servers, randSource)
|
||||
case constants.Perfectprivacy:
|
||||
return perfectprivacy.New(allServers.Perfectprivacy.Servers, randSource)
|
||||
case constants.Privado:
|
||||
return privado.New(allServers.Privado.Servers, randSource)
|
||||
case constants.PrivateInternetAccess:
|
||||
|
||||
@@ -11,8 +11,8 @@ import (
|
||||
|
||||
func (p *Purevpn) BuildConf(connection models.Connection,
|
||||
settings configuration.OpenVPN) (lines []string, err error) {
|
||||
if settings.Cipher == "" {
|
||||
settings.Cipher = constants.AES256gcm
|
||||
if len(settings.Ciphers) == 0 {
|
||||
settings.Ciphers = []string{constants.AES256gcm}
|
||||
}
|
||||
|
||||
lines = []string{
|
||||
@@ -40,7 +40,7 @@ func (p *Purevpn) BuildConf(connection models.Connection,
|
||||
connection.OpenVPNRemoteLine(),
|
||||
}
|
||||
|
||||
lines = append(lines, utils.CipherLines(settings.Cipher, settings.Version)...)
|
||||
lines = append(lines, utils.CipherLines(settings.Ciphers, settings.Version)...)
|
||||
|
||||
if connection.Protocol == constants.UDP {
|
||||
lines = append(lines, "explicit-exit-notify")
|
||||
|
||||
@@ -11,8 +11,8 @@ import (
|
||||
|
||||
func (s *Surfshark) BuildConf(connection models.Connection,
|
||||
settings configuration.OpenVPN) (lines []string, err error) {
|
||||
if settings.Cipher == "" {
|
||||
settings.Cipher = constants.AES256gcm
|
||||
if len(settings.Ciphers) == 0 {
|
||||
settings.Ciphers = []string{constants.AES256gcm}
|
||||
}
|
||||
|
||||
if settings.Auth == "" {
|
||||
@@ -53,7 +53,7 @@ func (s *Surfshark) BuildConf(connection models.Connection,
|
||||
connection.OpenVPNRemoteLine(),
|
||||
}
|
||||
|
||||
lines = append(lines, utils.CipherLines(settings.Cipher, settings.Version)...)
|
||||
lines = append(lines, utils.CipherLines(settings.Ciphers, settings.Version)...)
|
||||
|
||||
if connection.Protocol == constants.UDP {
|
||||
lines = append(lines, "explicit-exit-notify")
|
||||
|
||||
@@ -11,8 +11,8 @@ import (
|
||||
|
||||
func (t *Torguard) BuildConf(connection models.Connection,
|
||||
settings configuration.OpenVPN) (lines []string, err error) {
|
||||
if settings.Cipher == "" {
|
||||
settings.Cipher = constants.AES256gcm
|
||||
if len(settings.Ciphers) == 0 {
|
||||
settings.Ciphers = []string{constants.AES256gcm}
|
||||
}
|
||||
|
||||
if settings.Auth == "" {
|
||||
@@ -55,7 +55,7 @@ func (t *Torguard) BuildConf(connection models.Connection,
|
||||
connection.OpenVPNRemoteLine(),
|
||||
}
|
||||
|
||||
lines = append(lines, utils.CipherLines(settings.Cipher, settings.Version)...)
|
||||
lines = append(lines, utils.CipherLines(settings.Ciphers, settings.Version)...)
|
||||
|
||||
if !settings.Root {
|
||||
lines = append(lines, "user "+settings.ProcUser)
|
||||
|
||||
@@ -1,17 +1,22 @@
|
||||
package utils
|
||||
|
||||
import (
|
||||
"strings"
|
||||
|
||||
"github.com/qdm12/gluetun/internal/constants"
|
||||
)
|
||||
|
||||
func CipherLines(cipher, version string) (lines []string) {
|
||||
func CipherLines(ciphers []string, version string) (lines []string) {
|
||||
switch version {
|
||||
case constants.Openvpn24:
|
||||
return []string{"cipher " + cipher}
|
||||
return []string{
|
||||
"cipher " + ciphers[0],
|
||||
"ncp-ciphers " + strings.Join(ciphers, ":"),
|
||||
}
|
||||
default: // 2.5 and above
|
||||
return []string{
|
||||
"data-ciphers-fallback " + cipher,
|
||||
"data-ciphers " + cipher,
|
||||
"data-ciphers-fallback " + ciphers[0],
|
||||
"data-ciphers " + strings.Join(ciphers, ":"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,24 +9,31 @@ import (
|
||||
func Test_CipherLines(t *testing.T) {
|
||||
t.Parallel()
|
||||
testCases := map[string]struct {
|
||||
ciphers []string
|
||||
version string
|
||||
lines []string
|
||||
}{
|
||||
"empty version": {
|
||||
ciphers: []string{"AES"},
|
||||
lines: []string{
|
||||
"data-ciphers-fallback AES",
|
||||
"data-ciphers AES",
|
||||
},
|
||||
},
|
||||
"2.4": {
|
||||
ciphers: []string{"AES", "CBC"},
|
||||
version: "2.4",
|
||||
lines: []string{"cipher AES"},
|
||||
lines: []string{
|
||||
"cipher AES",
|
||||
"ncp-ciphers AES:CBC",
|
||||
},
|
||||
},
|
||||
"2.5": {
|
||||
ciphers: []string{"AES", "CBC"},
|
||||
version: "2.5",
|
||||
lines: []string{
|
||||
"data-ciphers-fallback AES",
|
||||
"data-ciphers AES",
|
||||
"data-ciphers AES:CBC",
|
||||
},
|
||||
},
|
||||
}
|
||||
@@ -35,9 +42,7 @@ func Test_CipherLines(t *testing.T) {
|
||||
t.Run(name, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
const cipher = "AES"
|
||||
|
||||
lines := CipherLines(cipher, testCase.version)
|
||||
lines := CipherLines(testCase.ciphers, testCase.version)
|
||||
|
||||
assert.Equal(t, testCase.lines, lines)
|
||||
})
|
||||
|
||||
@@ -36,8 +36,8 @@ func (p *Provider) BuildConf(connection models.Connection,
|
||||
connection.OpenVPNRemoteLine(),
|
||||
}
|
||||
|
||||
if settings.Cipher != "" {
|
||||
lines = append(lines, utils.CipherLines(settings.Cipher, settings.Version)...)
|
||||
if len(settings.Ciphers) > 0 {
|
||||
lines = append(lines, utils.CipherLines(settings.Ciphers, settings.Version)...)
|
||||
}
|
||||
|
||||
if settings.Auth != "" {
|
||||
|
||||
@@ -11,8 +11,8 @@ import (
|
||||
|
||||
func (v *Vyprvpn) BuildConf(connection models.Connection,
|
||||
settings configuration.OpenVPN) (lines []string, err error) {
|
||||
if settings.Cipher == "" {
|
||||
settings.Cipher = constants.AES256cbc
|
||||
if len(settings.Ciphers) == 0 {
|
||||
settings.Ciphers = []string{constants.AES256cbc}
|
||||
}
|
||||
|
||||
if settings.Auth == "" {
|
||||
@@ -33,6 +33,7 @@ func (v *Vyprvpn) BuildConf(connection models.Connection,
|
||||
"tls-cipher TLS-ECDHE-RSA-WITH-AES-256-GCM-SHA384:TLS-DHE-RSA-WITH-AES-256-CBC-SHA256:TLS-DHE-RSA-WITH-AES-256-CBC-SHA", //nolint:lll
|
||||
"auth-user-pass " + constants.OpenVPNAuthConf,
|
||||
"auth " + settings.Auth,
|
||||
"comp-lzo",
|
||||
|
||||
// Added constant values
|
||||
"auth-nocache",
|
||||
@@ -46,7 +47,7 @@ func (v *Vyprvpn) BuildConf(connection models.Connection,
|
||||
connection.OpenVPNRemoteLine(),
|
||||
}
|
||||
|
||||
lines = append(lines, utils.CipherLines(settings.Cipher, settings.Version)...)
|
||||
lines = append(lines, utils.CipherLines(settings.Ciphers, settings.Version)...)
|
||||
|
||||
if connection.Protocol == constants.UDP {
|
||||
lines = append(lines, "explicit-exit-notify")
|
||||
|
||||
@@ -11,8 +11,8 @@ import (
|
||||
|
||||
func (w *Wevpn) BuildConf(connection models.Connection,
|
||||
settings configuration.OpenVPN) (lines []string, err error) {
|
||||
if settings.Cipher == "" {
|
||||
settings.Cipher = constants.AES256gcm
|
||||
if len(settings.Ciphers) == 0 {
|
||||
settings.Ciphers = []string{constants.AES256gcm}
|
||||
}
|
||||
|
||||
if settings.Auth == "" {
|
||||
@@ -50,7 +50,7 @@ func (w *Wevpn) BuildConf(connection models.Connection,
|
||||
lines = append(lines, "explicit-exit-notify")
|
||||
}
|
||||
|
||||
lines = append(lines, utils.CipherLines(settings.Cipher, settings.Version)...)
|
||||
lines = append(lines, utils.CipherLines(settings.Ciphers, settings.Version)...)
|
||||
|
||||
if !settings.Root {
|
||||
lines = append(lines, "user "+settings.ProcUser)
|
||||
|
||||
@@ -2,7 +2,6 @@ package windscribe
|
||||
|
||||
import (
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/qdm12/gluetun/internal/configuration"
|
||||
"github.com/qdm12/gluetun/internal/constants"
|
||||
@@ -12,8 +11,12 @@ import (
|
||||
|
||||
func (w *Windscribe) BuildConf(connection models.Connection,
|
||||
settings configuration.OpenVPN) (lines []string, err error) {
|
||||
if settings.Cipher == "" {
|
||||
settings.Cipher = constants.AES256gcm
|
||||
if len(settings.Ciphers) == 0 {
|
||||
settings.Ciphers = []string{
|
||||
constants.AES256gcm,
|
||||
constants.AES256cbc,
|
||||
constants.AES128gcm,
|
||||
}
|
||||
}
|
||||
|
||||
if settings.Auth == "" {
|
||||
@@ -48,11 +51,7 @@ func (w *Windscribe) BuildConf(connection models.Connection,
|
||||
connection.OpenVPNRemoteLine(),
|
||||
}
|
||||
|
||||
lines = append(lines, utils.CipherLines(settings.Cipher, settings.Version)...)
|
||||
|
||||
if strings.HasSuffix(settings.Cipher, "-gcm") {
|
||||
lines = append(lines, "ncp-ciphers AES-256-GCM:AES-256-CBC:AES-128-GCM")
|
||||
}
|
||||
lines = append(lines, utils.CipherLines(settings.Ciphers, settings.Version)...)
|
||||
|
||||
if connection.Protocol == constants.UDP {
|
||||
lines = append(lines, "explicit-exit-notify")
|
||||
|
||||
@@ -38,7 +38,6 @@ func (f *Fetch) FetchPublicIP(ctx context.Context) (ip net.IP, err error) {
|
||||
"http://ip1.dynupdate.no-ip.com:8245",
|
||||
"http://ip1.dynupdate.no-ip.com",
|
||||
"https://api.ipify.org",
|
||||
"https://diagnostic.opendns.com/myip",
|
||||
"https://domains.google.com/checkip",
|
||||
"https://ifconfig.io/ip",
|
||||
"https://ipinfo.io/ip",
|
||||
|
||||
@@ -8,7 +8,7 @@ import (
|
||||
)
|
||||
|
||||
//go:embed servers.json
|
||||
var allServersEmbedFS embed.FS //nolint:gochecknoglobals
|
||||
var allServersEmbedFS embed.FS
|
||||
|
||||
func parseHardcodedServers() (allServers models.AllServers, err error) {
|
||||
f, err := allServersEmbedFS.Open("servers.json")
|
||||
|
||||
@@ -88,6 +88,11 @@ func Test_versions(t *testing.T) {
|
||||
version: allServers.Nordvpn.Version,
|
||||
digest: "a8043704",
|
||||
},
|
||||
"Perfect privacy": {
|
||||
model: models.PerfectprivacyServer{},
|
||||
version: allServers.Perfectprivacy.Version,
|
||||
digest: "233f0dd4",
|
||||
},
|
||||
"Privado": {
|
||||
model: models.PrivadoServer{},
|
||||
version: allServers.Privado.Version,
|
||||
@@ -106,7 +111,7 @@ func Test_versions(t *testing.T) {
|
||||
"Protonvpn": {
|
||||
model: models.ProtonvpnServer{},
|
||||
version: allServers.Protonvpn.Version,
|
||||
digest: "b964085b",
|
||||
digest: "4cb74c3a",
|
||||
},
|
||||
"Purevpn": {
|
||||
model: models.PurevpnServer{},
|
||||
|
||||
46
internal/storage/infoerrorer_mock_test.go
Normal file
46
internal/storage/infoerrorer_mock_test.go
Normal file
@@ -0,0 +1,46 @@
|
||||
// Code generated by MockGen. DO NOT EDIT.
|
||||
// Source: github.com/qdm12/gluetun/internal/storage (interfaces: InfoErrorer)
|
||||
|
||||
// Package storage is a generated GoMock package.
|
||||
package storage
|
||||
|
||||
import (
|
||||
reflect "reflect"
|
||||
|
||||
gomock "github.com/golang/mock/gomock"
|
||||
)
|
||||
|
||||
// MockInfoErrorer is a mock of InfoErrorer interface.
|
||||
type MockInfoErrorer struct {
|
||||
ctrl *gomock.Controller
|
||||
recorder *MockInfoErrorerMockRecorder
|
||||
}
|
||||
|
||||
// MockInfoErrorerMockRecorder is the mock recorder for MockInfoErrorer.
|
||||
type MockInfoErrorerMockRecorder struct {
|
||||
mock *MockInfoErrorer
|
||||
}
|
||||
|
||||
// NewMockInfoErrorer creates a new mock instance.
|
||||
func NewMockInfoErrorer(ctrl *gomock.Controller) *MockInfoErrorer {
|
||||
mock := &MockInfoErrorer{ctrl: ctrl}
|
||||
mock.recorder = &MockInfoErrorerMockRecorder{mock}
|
||||
return mock
|
||||
}
|
||||
|
||||
// EXPECT returns an object that allows the caller to indicate expected use.
|
||||
func (m *MockInfoErrorer) EXPECT() *MockInfoErrorerMockRecorder {
|
||||
return m.recorder
|
||||
}
|
||||
|
||||
// Info mocks base method.
|
||||
func (m *MockInfoErrorer) Info(arg0 string) {
|
||||
m.ctrl.T.Helper()
|
||||
m.ctrl.Call(m, "Info", arg0)
|
||||
}
|
||||
|
||||
// Info indicates an expected call of Info.
|
||||
func (mr *MockInfoErrorerMockRecorder) Info(arg0 interface{}) *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Info", reflect.TypeOf((*MockInfoErrorer)(nil).Info), arg0)
|
||||
}
|
||||
@@ -7,15 +7,11 @@ import (
|
||||
"github.com/qdm12/gluetun/internal/models"
|
||||
)
|
||||
|
||||
func (s *Storage) logVersionDiff(provider string, diff int) {
|
||||
diffString := strconv.Itoa(diff)
|
||||
|
||||
message := provider + " servers from file discarded because they are " +
|
||||
diffString + " version"
|
||||
if diff > 1 {
|
||||
message += "s"
|
||||
}
|
||||
message += " behind"
|
||||
func (s *Storage) logVersionDiff(provider string, hardcodedVersion, persistedVersion uint16) {
|
||||
message := provider + " servers from file discarded because they have version " +
|
||||
strconv.Itoa(int(persistedVersion)) +
|
||||
" and hardcoded servers have version " +
|
||||
strconv.Itoa(int(hardcodedVersion))
|
||||
s.logger.Info(message)
|
||||
}
|
||||
|
||||
@@ -32,26 +28,27 @@ func (s *Storage) logTimeDiff(provider string, persistedUnix, hardcodedUnix int6
|
||||
|
||||
func (s *Storage) mergeServers(hardcoded, persisted models.AllServers) models.AllServers {
|
||||
return models.AllServers{
|
||||
Version: hardcoded.Version,
|
||||
Cyberghost: s.mergeCyberghost(hardcoded.Cyberghost, persisted.Cyberghost),
|
||||
Expressvpn: s.mergeExpressvpn(hardcoded.Expressvpn, persisted.Expressvpn),
|
||||
Fastestvpn: s.mergeFastestvpn(hardcoded.Fastestvpn, persisted.Fastestvpn),
|
||||
HideMyAss: s.mergeHideMyAss(hardcoded.HideMyAss, persisted.HideMyAss),
|
||||
Ipvanish: s.mergeIpvanish(hardcoded.Ipvanish, persisted.Ipvanish),
|
||||
Ivpn: s.mergeIvpn(hardcoded.Ivpn, persisted.Ivpn),
|
||||
Mullvad: s.mergeMullvad(hardcoded.Mullvad, persisted.Mullvad),
|
||||
Nordvpn: s.mergeNordVPN(hardcoded.Nordvpn, persisted.Nordvpn),
|
||||
Privado: s.mergePrivado(hardcoded.Privado, persisted.Privado),
|
||||
Pia: s.mergePIA(hardcoded.Pia, persisted.Pia),
|
||||
Privatevpn: s.mergePrivatevpn(hardcoded.Privatevpn, persisted.Privatevpn),
|
||||
Protonvpn: s.mergeProtonvpn(hardcoded.Protonvpn, persisted.Protonvpn),
|
||||
Purevpn: s.mergePureVPN(hardcoded.Purevpn, persisted.Purevpn),
|
||||
Surfshark: s.mergeSurfshark(hardcoded.Surfshark, persisted.Surfshark),
|
||||
Torguard: s.mergeTorguard(hardcoded.Torguard, persisted.Torguard),
|
||||
VPNUnlimited: s.mergeVPNUnlimited(hardcoded.VPNUnlimited, persisted.VPNUnlimited),
|
||||
Vyprvpn: s.mergeVyprvpn(hardcoded.Vyprvpn, persisted.Vyprvpn),
|
||||
Wevpn: s.mergeWevpn(hardcoded.Wevpn, persisted.Wevpn),
|
||||
Windscribe: s.mergeWindscribe(hardcoded.Windscribe, persisted.Windscribe),
|
||||
Version: hardcoded.Version,
|
||||
Cyberghost: s.mergeCyberghost(hardcoded.Cyberghost, persisted.Cyberghost),
|
||||
Expressvpn: s.mergeExpressvpn(hardcoded.Expressvpn, persisted.Expressvpn),
|
||||
Fastestvpn: s.mergeFastestvpn(hardcoded.Fastestvpn, persisted.Fastestvpn),
|
||||
HideMyAss: s.mergeHideMyAss(hardcoded.HideMyAss, persisted.HideMyAss),
|
||||
Ipvanish: s.mergeIpvanish(hardcoded.Ipvanish, persisted.Ipvanish),
|
||||
Ivpn: s.mergeIvpn(hardcoded.Ivpn, persisted.Ivpn),
|
||||
Mullvad: s.mergeMullvad(hardcoded.Mullvad, persisted.Mullvad),
|
||||
Nordvpn: s.mergeNordVPN(hardcoded.Nordvpn, persisted.Nordvpn),
|
||||
Perfectprivacy: s.mergePerfectprivacy(hardcoded.Perfectprivacy, persisted.Perfectprivacy),
|
||||
Privado: s.mergePrivado(hardcoded.Privado, persisted.Privado),
|
||||
Pia: s.mergePIA(hardcoded.Pia, persisted.Pia),
|
||||
Privatevpn: s.mergePrivatevpn(hardcoded.Privatevpn, persisted.Privatevpn),
|
||||
Protonvpn: s.mergeProtonvpn(hardcoded.Protonvpn, persisted.Protonvpn),
|
||||
Purevpn: s.mergePureVPN(hardcoded.Purevpn, persisted.Purevpn),
|
||||
Surfshark: s.mergeSurfshark(hardcoded.Surfshark, persisted.Surfshark),
|
||||
Torguard: s.mergeTorguard(hardcoded.Torguard, persisted.Torguard),
|
||||
VPNUnlimited: s.mergeVPNUnlimited(hardcoded.VPNUnlimited, persisted.VPNUnlimited),
|
||||
Vyprvpn: s.mergeVyprvpn(hardcoded.Vyprvpn, persisted.Vyprvpn),
|
||||
Wevpn: s.mergeWevpn(hardcoded.Wevpn, persisted.Wevpn),
|
||||
Windscribe: s.mergeWindscribe(hardcoded.Windscribe, persisted.Windscribe),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -60,12 +57,6 @@ func (s *Storage) mergeCyberghost(hardcoded, persisted models.CyberghostServers)
|
||||
return hardcoded
|
||||
}
|
||||
|
||||
versionDiff := int(hardcoded.Version) - int(persisted.Version)
|
||||
if versionDiff > 0 {
|
||||
s.logVersionDiff("Cyberghost", versionDiff)
|
||||
return hardcoded
|
||||
}
|
||||
|
||||
s.logTimeDiff("Cyberghost", persisted.Timestamp, hardcoded.Timestamp)
|
||||
return persisted
|
||||
}
|
||||
@@ -74,11 +65,7 @@ func (s *Storage) mergeExpressvpn(hardcoded, persisted models.ExpressvpnServers)
|
||||
if persisted.Timestamp <= hardcoded.Timestamp {
|
||||
return hardcoded
|
||||
}
|
||||
versionDiff := int(hardcoded.Version) - int(persisted.Version)
|
||||
if versionDiff > 0 {
|
||||
s.logVersionDiff("ExpressVPN", versionDiff)
|
||||
return hardcoded
|
||||
}
|
||||
|
||||
s.logTimeDiff("ExpressVPN", persisted.Timestamp, hardcoded.Timestamp)
|
||||
return persisted
|
||||
}
|
||||
@@ -87,11 +74,7 @@ func (s *Storage) mergeFastestvpn(hardcoded, persisted models.FastestvpnServers)
|
||||
if persisted.Timestamp <= hardcoded.Timestamp {
|
||||
return hardcoded
|
||||
}
|
||||
versionDiff := int(hardcoded.Version) - int(persisted.Version)
|
||||
if versionDiff > 0 {
|
||||
s.logVersionDiff("FastestVPN", versionDiff)
|
||||
return hardcoded
|
||||
}
|
||||
|
||||
s.logTimeDiff("FastestVPN", persisted.Timestamp, hardcoded.Timestamp)
|
||||
return persisted
|
||||
}
|
||||
@@ -100,11 +83,7 @@ func (s *Storage) mergeHideMyAss(hardcoded, persisted models.HideMyAssServers) m
|
||||
if persisted.Timestamp <= hardcoded.Timestamp {
|
||||
return hardcoded
|
||||
}
|
||||
versionDiff := int(hardcoded.Version) - int(persisted.Version)
|
||||
if versionDiff > 0 {
|
||||
s.logVersionDiff("HideMyAss", versionDiff)
|
||||
return hardcoded
|
||||
}
|
||||
|
||||
s.logTimeDiff("HideMyAss", persisted.Timestamp, hardcoded.Timestamp)
|
||||
return persisted
|
||||
}
|
||||
@@ -113,11 +92,7 @@ func (s *Storage) mergeIpvanish(hardcoded, persisted models.IpvanishServers) mod
|
||||
if persisted.Timestamp <= hardcoded.Timestamp {
|
||||
return hardcoded
|
||||
}
|
||||
versionDiff := int(hardcoded.Version) - int(persisted.Version)
|
||||
if versionDiff > 0 {
|
||||
s.logVersionDiff("Ipvanish", versionDiff)
|
||||
return hardcoded
|
||||
}
|
||||
|
||||
s.logTimeDiff("Ipvanish", persisted.Timestamp, hardcoded.Timestamp)
|
||||
return persisted
|
||||
}
|
||||
@@ -126,11 +101,7 @@ func (s *Storage) mergeIvpn(hardcoded, persisted models.IvpnServers) models.Ivpn
|
||||
if persisted.Timestamp <= hardcoded.Timestamp {
|
||||
return hardcoded
|
||||
}
|
||||
versionDiff := int(hardcoded.Version) - int(persisted.Version)
|
||||
if versionDiff > 0 {
|
||||
s.logVersionDiff("Ivpn", versionDiff)
|
||||
return hardcoded
|
||||
}
|
||||
|
||||
s.logTimeDiff("Ivpn", persisted.Timestamp, hardcoded.Timestamp)
|
||||
return persisted
|
||||
}
|
||||
@@ -140,12 +111,6 @@ func (s *Storage) mergeMullvad(hardcoded, persisted models.MullvadServers) model
|
||||
return hardcoded
|
||||
}
|
||||
|
||||
versionDiff := int(hardcoded.Version) - int(persisted.Version)
|
||||
if versionDiff > 0 {
|
||||
s.logVersionDiff("Mullvad", versionDiff)
|
||||
return hardcoded
|
||||
}
|
||||
|
||||
s.logTimeDiff("Mullvad", persisted.Timestamp, hardcoded.Timestamp)
|
||||
return persisted
|
||||
}
|
||||
@@ -155,13 +120,16 @@ func (s *Storage) mergeNordVPN(hardcoded, persisted models.NordvpnServers) model
|
||||
return hardcoded
|
||||
}
|
||||
|
||||
versionDiff := int(hardcoded.Version) - int(persisted.Version)
|
||||
if versionDiff > 0 {
|
||||
s.logVersionDiff("NordVPN", versionDiff)
|
||||
s.logTimeDiff("NordVPN", persisted.Timestamp, hardcoded.Timestamp)
|
||||
return persisted
|
||||
}
|
||||
|
||||
func (s *Storage) mergePerfectprivacy(hardcoded, persisted models.PerfectprivacyServers) models.PerfectprivacyServers {
|
||||
if persisted.Timestamp <= hardcoded.Timestamp {
|
||||
return hardcoded
|
||||
}
|
||||
|
||||
s.logTimeDiff("NordVPN", persisted.Timestamp, hardcoded.Timestamp)
|
||||
s.logTimeDiff("Perfect Privacy", persisted.Timestamp, hardcoded.Timestamp)
|
||||
return persisted
|
||||
}
|
||||
|
||||
@@ -169,11 +137,6 @@ func (s *Storage) mergePrivado(hardcoded, persisted models.PrivadoServers) model
|
||||
if persisted.Timestamp <= hardcoded.Timestamp {
|
||||
return hardcoded
|
||||
}
|
||||
versionDiff := int(hardcoded.Version) - int(persisted.Version)
|
||||
if versionDiff > 0 {
|
||||
s.logVersionDiff("Privado", versionDiff)
|
||||
return hardcoded
|
||||
}
|
||||
|
||||
s.logTimeDiff("Privado", persisted.Timestamp, hardcoded.Timestamp)
|
||||
return persisted
|
||||
@@ -183,11 +146,6 @@ func (s *Storage) mergePIA(hardcoded, persisted models.PiaServers) models.PiaSer
|
||||
if persisted.Timestamp <= hardcoded.Timestamp {
|
||||
return hardcoded
|
||||
}
|
||||
versionDiff := int(hardcoded.Version) - int(persisted.Version)
|
||||
if versionDiff > 0 {
|
||||
s.logVersionDiff("Private Internet Access", versionDiff)
|
||||
return hardcoded
|
||||
}
|
||||
|
||||
s.logTimeDiff("Private Internet Access", persisted.Timestamp, hardcoded.Timestamp)
|
||||
return persisted
|
||||
@@ -197,11 +155,6 @@ func (s *Storage) mergePrivatevpn(hardcoded, persisted models.PrivatevpnServers)
|
||||
if persisted.Timestamp <= hardcoded.Timestamp {
|
||||
return hardcoded
|
||||
}
|
||||
versionDiff := int(hardcoded.Version) - int(persisted.Version)
|
||||
if versionDiff > 0 {
|
||||
s.logVersionDiff("PrivateVPN", versionDiff)
|
||||
return hardcoded
|
||||
}
|
||||
|
||||
s.logTimeDiff("PrivateVPN", persisted.Timestamp, hardcoded.Timestamp)
|
||||
return persisted
|
||||
@@ -211,11 +164,6 @@ func (s *Storage) mergeProtonvpn(hardcoded, persisted models.ProtonvpnServers) m
|
||||
if persisted.Timestamp <= hardcoded.Timestamp {
|
||||
return hardcoded
|
||||
}
|
||||
versionDiff := int(hardcoded.Version) - int(persisted.Version)
|
||||
if versionDiff > 0 {
|
||||
s.logVersionDiff("ProtonVPN", versionDiff)
|
||||
return hardcoded
|
||||
}
|
||||
|
||||
s.logTimeDiff("ProtonVPN", persisted.Timestamp, hardcoded.Timestamp)
|
||||
return persisted
|
||||
@@ -226,12 +174,6 @@ func (s *Storage) mergePureVPN(hardcoded, persisted models.PurevpnServers) model
|
||||
return hardcoded
|
||||
}
|
||||
|
||||
versionDiff := int(hardcoded.Version) - int(persisted.Version)
|
||||
if versionDiff > 0 {
|
||||
s.logVersionDiff("PureVPN", versionDiff)
|
||||
return hardcoded
|
||||
}
|
||||
|
||||
s.logTimeDiff("PureVPN", persisted.Timestamp, hardcoded.Timestamp)
|
||||
return persisted
|
||||
}
|
||||
@@ -241,12 +183,6 @@ func (s *Storage) mergeSurfshark(hardcoded, persisted models.SurfsharkServers) m
|
||||
return hardcoded
|
||||
}
|
||||
|
||||
versionDiff := int(hardcoded.Version) - int(persisted.Version)
|
||||
if versionDiff > 0 {
|
||||
s.logVersionDiff("Surfshark", versionDiff)
|
||||
return hardcoded
|
||||
}
|
||||
|
||||
s.logTimeDiff("Surfshark", persisted.Timestamp, hardcoded.Timestamp)
|
||||
return persisted
|
||||
}
|
||||
@@ -255,11 +191,6 @@ func (s *Storage) mergeTorguard(hardcoded, persisted models.TorguardServers) mod
|
||||
if persisted.Timestamp <= hardcoded.Timestamp {
|
||||
return hardcoded
|
||||
}
|
||||
versionDiff := int(hardcoded.Version) - int(persisted.Version)
|
||||
if versionDiff > 0 {
|
||||
s.logVersionDiff("Torguard", versionDiff)
|
||||
return hardcoded
|
||||
}
|
||||
|
||||
s.logTimeDiff("Torguard", persisted.Timestamp, hardcoded.Timestamp)
|
||||
return persisted
|
||||
@@ -269,11 +200,6 @@ func (s *Storage) mergeVPNUnlimited(hardcoded, persisted models.VPNUnlimitedServ
|
||||
if persisted.Timestamp <= hardcoded.Timestamp {
|
||||
return hardcoded
|
||||
}
|
||||
versionDiff := int(hardcoded.Version) - int(persisted.Version)
|
||||
if versionDiff > 0 {
|
||||
s.logVersionDiff("VPN Unlimited", versionDiff)
|
||||
return hardcoded
|
||||
}
|
||||
|
||||
s.logTimeDiff("VPN Unlimited", persisted.Timestamp, hardcoded.Timestamp)
|
||||
return persisted
|
||||
@@ -284,12 +210,6 @@ func (s *Storage) mergeVyprvpn(hardcoded, persisted models.VyprvpnServers) model
|
||||
return hardcoded
|
||||
}
|
||||
|
||||
versionDiff := int(hardcoded.Version) - int(persisted.Version)
|
||||
if versionDiff > 0 {
|
||||
s.logVersionDiff("VyprVPN", versionDiff)
|
||||
return hardcoded
|
||||
}
|
||||
|
||||
s.logTimeDiff("VyprVPN", persisted.Timestamp, hardcoded.Timestamp)
|
||||
return persisted
|
||||
}
|
||||
@@ -299,12 +219,6 @@ func (s *Storage) mergeWevpn(hardcoded, persisted models.WevpnServers) models.We
|
||||
return hardcoded
|
||||
}
|
||||
|
||||
versionDiff := int(hardcoded.Version) - int(persisted.Version)
|
||||
if versionDiff > 0 {
|
||||
s.logVersionDiff("WeVPN", versionDiff)
|
||||
return hardcoded
|
||||
}
|
||||
|
||||
s.logTimeDiff("WeVPN", persisted.Timestamp, hardcoded.Timestamp)
|
||||
return persisted
|
||||
}
|
||||
@@ -314,12 +228,6 @@ func (s *Storage) mergeWindscribe(hardcoded, persisted models.WindscribeServers)
|
||||
return hardcoded
|
||||
}
|
||||
|
||||
versionDiff := int(hardcoded.Version) - int(persisted.Version)
|
||||
if versionDiff > 0 {
|
||||
s.logVersionDiff("Windscribe", versionDiff)
|
||||
return hardcoded
|
||||
}
|
||||
|
||||
s.logTimeDiff("Windscribe", persisted.Timestamp, hardcoded.Timestamp)
|
||||
return persisted
|
||||
}
|
||||
|
||||
@@ -3,26 +3,291 @@ package storage
|
||||
import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
|
||||
"github.com/qdm12/gluetun/internal/models"
|
||||
)
|
||||
|
||||
func readFromFile(filepath string) (servers models.AllServers, err error) {
|
||||
// readFromFile reads the servers from server.json.
|
||||
// It only reads servers that have the same version as the hardcoded servers version
|
||||
// to avoid JSON unmarshaling errors.
|
||||
func (s *Storage) readFromFile(filepath string, hardcoded models.AllServers) (
|
||||
servers models.AllServers, err error) {
|
||||
file, err := os.Open(filepath)
|
||||
if os.IsNotExist(err) {
|
||||
return servers, nil
|
||||
} else if err != nil {
|
||||
return servers, err
|
||||
}
|
||||
decoder := json.NewDecoder(file)
|
||||
if err := decoder.Decode(&servers); err != nil {
|
||||
_ = file.Close()
|
||||
if errors.Is(err, io.EOF) {
|
||||
return servers, nil
|
||||
}
|
||||
|
||||
b, err := io.ReadAll(file)
|
||||
if err != nil {
|
||||
return servers, err
|
||||
}
|
||||
return servers, file.Close()
|
||||
|
||||
if err := file.Close(); err != nil {
|
||||
return servers, err
|
||||
}
|
||||
|
||||
return s.extractServersFromBytes(b, hardcoded)
|
||||
}
|
||||
|
||||
var (
|
||||
errDecodeVersions = errors.New("cannot decode versions")
|
||||
errDecodeServers = errors.New("cannot decode servers")
|
||||
errDecodeProvider = errors.New("cannot decode servers for provider")
|
||||
)
|
||||
|
||||
func (s *Storage) extractServersFromBytes(b []byte, hardcoded models.AllServers) ( //nolint:gocognit,gocyclo
|
||||
servers models.AllServers, err error) {
|
||||
var versions allVersions
|
||||
if err := json.Unmarshal(b, &versions); err != nil {
|
||||
return servers, fmt.Errorf("%w: %s", errDecodeVersions, err)
|
||||
}
|
||||
|
||||
var rawMessages allJSONRawMessages
|
||||
if err := json.Unmarshal(b, &rawMessages); err != nil {
|
||||
return servers, fmt.Errorf("%w: %s", errDecodeServers, err)
|
||||
}
|
||||
|
||||
// TODO simplify with generics in Go 1.18
|
||||
|
||||
if hardcoded.Cyberghost.Version != versions.Cyberghost.Version {
|
||||
s.logVersionDiff("Cyberghost", hardcoded.Cyberghost.Version, versions.Cyberghost.Version)
|
||||
} else {
|
||||
err = json.Unmarshal(rawMessages.Cyberghost, &servers.Cyberghost)
|
||||
if err != nil {
|
||||
return servers, fmt.Errorf("%w: %s: %s", errDecodeProvider, "Cyberghost", err)
|
||||
}
|
||||
}
|
||||
|
||||
if hardcoded.Expressvpn.Version != versions.Expressvpn.Version {
|
||||
s.logVersionDiff("Expressvpn", hardcoded.Expressvpn.Version, versions.Expressvpn.Version)
|
||||
} else {
|
||||
err = json.Unmarshal(rawMessages.Expressvpn, &servers.Expressvpn)
|
||||
if err != nil {
|
||||
return servers, fmt.Errorf("%w: %s: %s", errDecodeProvider, "Expressvpn", err)
|
||||
}
|
||||
}
|
||||
|
||||
if hardcoded.Fastestvpn.Version != versions.Fastestvpn.Version {
|
||||
s.logVersionDiff("Fastestvpn", hardcoded.Fastestvpn.Version, versions.Fastestvpn.Version)
|
||||
} else {
|
||||
err = json.Unmarshal(rawMessages.Fastestvpn, &servers.Fastestvpn)
|
||||
if err != nil {
|
||||
return servers, fmt.Errorf("%w: %s: %s", errDecodeProvider, "Fastestvpn", err)
|
||||
}
|
||||
}
|
||||
|
||||
if hardcoded.HideMyAss.Version != versions.HideMyAss.Version {
|
||||
s.logVersionDiff("HideMyAss", hardcoded.HideMyAss.Version, versions.HideMyAss.Version)
|
||||
} else {
|
||||
err = json.Unmarshal(rawMessages.HideMyAss, &servers.HideMyAss)
|
||||
if err != nil {
|
||||
return servers, fmt.Errorf("%w: %s: %s", errDecodeProvider, "HideMyAss", err)
|
||||
}
|
||||
}
|
||||
|
||||
if hardcoded.Ipvanish.Version != versions.Ipvanish.Version {
|
||||
s.logVersionDiff("Ipvanish", hardcoded.Ipvanish.Version, versions.Ipvanish.Version)
|
||||
} else {
|
||||
err = json.Unmarshal(rawMessages.Ipvanish, &servers.Ipvanish)
|
||||
if err != nil {
|
||||
return servers, fmt.Errorf("%w: %s: %s", errDecodeProvider, "Ipvanish", err)
|
||||
}
|
||||
}
|
||||
|
||||
if hardcoded.Ivpn.Version != versions.Ivpn.Version {
|
||||
s.logVersionDiff("Ivpn", hardcoded.Ivpn.Version, versions.Ivpn.Version)
|
||||
} else {
|
||||
err = json.Unmarshal(rawMessages.Ivpn, &servers.Ivpn)
|
||||
if err != nil {
|
||||
return servers, fmt.Errorf("%w: %s: %s", errDecodeProvider, "Ivpn", err)
|
||||
}
|
||||
}
|
||||
|
||||
if hardcoded.Mullvad.Version != versions.Mullvad.Version {
|
||||
s.logVersionDiff("Mullvad", hardcoded.Mullvad.Version, versions.Mullvad.Version)
|
||||
} else {
|
||||
err = json.Unmarshal(rawMessages.Mullvad, &servers.Mullvad)
|
||||
if err != nil {
|
||||
return servers, fmt.Errorf("%w: %s: %s", errDecodeProvider, "Mullvad", err)
|
||||
}
|
||||
}
|
||||
|
||||
if hardcoded.Nordvpn.Version != versions.Nordvpn.Version {
|
||||
s.logVersionDiff("Nordvpn", hardcoded.Nordvpn.Version, versions.Nordvpn.Version)
|
||||
} else {
|
||||
err = json.Unmarshal(rawMessages.Nordvpn, &servers.Nordvpn)
|
||||
if err != nil {
|
||||
return servers, fmt.Errorf("%w: %s: %s", errDecodeProvider, "Nordvpn", err)
|
||||
}
|
||||
}
|
||||
|
||||
if hardcoded.Perfectprivacy.Version != versions.Perfectprivacy.Version {
|
||||
s.logVersionDiff("Perfect Privacy", hardcoded.Perfectprivacy.Version, versions.Perfectprivacy.Version)
|
||||
} else {
|
||||
err = json.Unmarshal(rawMessages.Perfectprivacy, &servers.Perfectprivacy)
|
||||
if err != nil {
|
||||
return servers, fmt.Errorf("%w: %s: %s", errDecodeProvider, "Perfect Privacy", err)
|
||||
}
|
||||
}
|
||||
|
||||
if hardcoded.Privado.Version != versions.Privado.Version {
|
||||
s.logVersionDiff("Privado", hardcoded.Privado.Version, versions.Privado.Version)
|
||||
} else {
|
||||
err = json.Unmarshal(rawMessages.Privado, &servers.Privado)
|
||||
if err != nil {
|
||||
return servers, fmt.Errorf("%w: %s: %s", errDecodeProvider, "Privado", err)
|
||||
}
|
||||
}
|
||||
|
||||
if hardcoded.Pia.Version != versions.Pia.Version {
|
||||
s.logVersionDiff("Pia", hardcoded.Pia.Version, versions.Pia.Version)
|
||||
} else {
|
||||
err = json.Unmarshal(rawMessages.Pia, &servers.Pia)
|
||||
if err != nil {
|
||||
return servers, fmt.Errorf("%w: %s: %s", errDecodeProvider, "Pia", err)
|
||||
}
|
||||
}
|
||||
|
||||
if hardcoded.Privatevpn.Version != versions.Privatevpn.Version {
|
||||
s.logVersionDiff("Privatevpn", hardcoded.Privatevpn.Version, versions.Privatevpn.Version)
|
||||
} else {
|
||||
err = json.Unmarshal(rawMessages.Privatevpn, &servers.Privatevpn)
|
||||
if err != nil {
|
||||
return servers, fmt.Errorf("%w: %s: %s", errDecodeProvider, "Privatevpn", err)
|
||||
}
|
||||
}
|
||||
|
||||
if hardcoded.Protonvpn.Version != versions.Protonvpn.Version {
|
||||
s.logVersionDiff("Protonvpn", hardcoded.Protonvpn.Version, versions.Protonvpn.Version)
|
||||
} else {
|
||||
err = json.Unmarshal(rawMessages.Protonvpn, &servers.Protonvpn)
|
||||
if err != nil {
|
||||
return servers, fmt.Errorf("%w: %s: %s", errDecodeProvider, "Protonvpn", err)
|
||||
}
|
||||
}
|
||||
|
||||
if hardcoded.Purevpn.Version != versions.Purevpn.Version {
|
||||
s.logVersionDiff("Purevpn", hardcoded.Purevpn.Version, versions.Purevpn.Version)
|
||||
} else {
|
||||
err = json.Unmarshal(rawMessages.Purevpn, &servers.Purevpn)
|
||||
if err != nil {
|
||||
return servers, fmt.Errorf("%w: %s: %s", errDecodeProvider, "Purevpn", err)
|
||||
}
|
||||
}
|
||||
|
||||
if hardcoded.Surfshark.Version != versions.Surfshark.Version {
|
||||
s.logVersionDiff("Surfshark", hardcoded.Surfshark.Version, versions.Surfshark.Version)
|
||||
} else {
|
||||
err = json.Unmarshal(rawMessages.Surfshark, &servers.Surfshark)
|
||||
if err != nil {
|
||||
return servers, fmt.Errorf("%w: %s: %s", errDecodeProvider, "Surfshark", err)
|
||||
}
|
||||
}
|
||||
|
||||
if hardcoded.Torguard.Version != versions.Torguard.Version {
|
||||
s.logVersionDiff("Torguard", hardcoded.Torguard.Version, versions.Torguard.Version)
|
||||
} else {
|
||||
err = json.Unmarshal(rawMessages.Torguard, &servers.Torguard)
|
||||
if err != nil {
|
||||
return servers, fmt.Errorf("%w: %s: %s", errDecodeProvider, "Torguard", err)
|
||||
}
|
||||
}
|
||||
|
||||
if hardcoded.VPNUnlimited.Version != versions.VPNUnlimited.Version {
|
||||
s.logVersionDiff("VPNUnlimited", hardcoded.VPNUnlimited.Version, versions.VPNUnlimited.Version)
|
||||
} else {
|
||||
err = json.Unmarshal(rawMessages.VPNUnlimited, &servers.VPNUnlimited)
|
||||
if err != nil {
|
||||
return servers, fmt.Errorf("%w: %s: %s", errDecodeProvider, "VPNUnlimited", err)
|
||||
}
|
||||
}
|
||||
|
||||
if hardcoded.Vyprvpn.Version != versions.Vyprvpn.Version {
|
||||
s.logVersionDiff("Vyprvpn", hardcoded.Vyprvpn.Version, versions.Vyprvpn.Version)
|
||||
} else {
|
||||
err = json.Unmarshal(rawMessages.Vyprvpn, &servers.Vyprvpn)
|
||||
if err != nil {
|
||||
return servers, fmt.Errorf("%w: %s: %s", errDecodeProvider, "Vyprvpn", err)
|
||||
}
|
||||
}
|
||||
|
||||
if hardcoded.Wevpn.Version != versions.Wevpn.Version {
|
||||
s.logVersionDiff("Wevpn", hardcoded.Wevpn.Version, versions.Wevpn.Version)
|
||||
} else {
|
||||
err = json.Unmarshal(rawMessages.Wevpn, &servers.Wevpn)
|
||||
if err != nil {
|
||||
return servers, fmt.Errorf("%w: %s: %s", errDecodeProvider, "Wevpn", err)
|
||||
}
|
||||
}
|
||||
|
||||
if hardcoded.Windscribe.Version != versions.Windscribe.Version {
|
||||
s.logVersionDiff("Windscribe", hardcoded.Windscribe.Version, versions.Windscribe.Version)
|
||||
} else {
|
||||
err = json.Unmarshal(rawMessages.Windscribe, &servers.Windscribe)
|
||||
if err != nil {
|
||||
return servers, fmt.Errorf("%w: %s: %s", errDecodeProvider, "Windscribe", err)
|
||||
}
|
||||
}
|
||||
|
||||
return servers, nil
|
||||
}
|
||||
|
||||
// allVersions is a subset of models.AllServers structure used to track
|
||||
// versions to avoid unmarshaling errors.
|
||||
type allVersions struct {
|
||||
Version uint16 `json:"version"` // used for migration of the top level scheme
|
||||
Cyberghost serverVersion `json:"cyberghost"`
|
||||
Expressvpn serverVersion `json:"expressvpn"`
|
||||
Fastestvpn serverVersion `json:"fastestvpn"`
|
||||
HideMyAss serverVersion `json:"hidemyass"`
|
||||
Ipvanish serverVersion `json:"ipvanish"`
|
||||
Ivpn serverVersion `json:"ivpn"`
|
||||
Mullvad serverVersion `json:"mullvad"`
|
||||
Nordvpn serverVersion `json:"nordvpn"`
|
||||
Perfectprivacy serverVersion `json:"perfectprivacy"`
|
||||
Privado serverVersion `json:"privado"`
|
||||
Pia serverVersion `json:"pia"`
|
||||
Privatevpn serverVersion `json:"privatevpn"`
|
||||
Protonvpn serverVersion `json:"protonvpn"`
|
||||
Purevpn serverVersion `json:"purevpn"`
|
||||
Surfshark serverVersion `json:"surfshark"`
|
||||
Torguard serverVersion `json:"torguard"`
|
||||
VPNUnlimited serverVersion `json:"vpnunlimited"`
|
||||
Vyprvpn serverVersion `json:"vyprvpn"`
|
||||
Wevpn serverVersion `json:"wevpn"`
|
||||
Windscribe serverVersion `json:"windscribe"`
|
||||
}
|
||||
|
||||
type serverVersion struct {
|
||||
Version uint16 `json:"version"`
|
||||
}
|
||||
|
||||
// allJSONRawMessages is to delay decoding of each provider servers.
|
||||
type allJSONRawMessages struct {
|
||||
Version uint16 `json:"version"` // used for migration of the top level scheme
|
||||
Cyberghost json.RawMessage `json:"cyberghost"`
|
||||
Expressvpn json.RawMessage `json:"expressvpn"`
|
||||
Fastestvpn json.RawMessage `json:"fastestvpn"`
|
||||
HideMyAss json.RawMessage `json:"hidemyass"`
|
||||
Ipvanish json.RawMessage `json:"ipvanish"`
|
||||
Ivpn json.RawMessage `json:"ivpn"`
|
||||
Mullvad json.RawMessage `json:"mullvad"`
|
||||
Nordvpn json.RawMessage `json:"nordvpn"`
|
||||
Perfectprivacy json.RawMessage `json:"perfectprivacy"`
|
||||
Privado json.RawMessage `json:"privado"`
|
||||
Pia json.RawMessage `json:"pia"`
|
||||
Privatevpn json.RawMessage `json:"privatevpn"`
|
||||
Protonvpn json.RawMessage `json:"protonvpn"`
|
||||
Purevpn json.RawMessage `json:"purevpn"`
|
||||
Surfshark json.RawMessage `json:"surfshark"`
|
||||
Torguard json.RawMessage `json:"torguard"`
|
||||
VPNUnlimited json.RawMessage `json:"vpnunlimited"`
|
||||
Vyprvpn json.RawMessage `json:"vyprvpn"`
|
||||
Wevpn json.RawMessage `json:"wevpn"`
|
||||
Windscribe json.RawMessage `json:"windscribe"`
|
||||
}
|
||||
|
||||
174
internal/storage/read_test.go
Normal file
174
internal/storage/read_test.go
Normal file
@@ -0,0 +1,174 @@
|
||||
package storage
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"testing"
|
||||
|
||||
gomock "github.com/golang/mock/gomock"
|
||||
"github.com/qdm12/gluetun/internal/models"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func Test_extractServersFromBytes(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
testCases := map[string]struct {
|
||||
b []byte
|
||||
hardcoded models.AllServers
|
||||
logged []string
|
||||
persisted models.AllServers
|
||||
err error
|
||||
}{
|
||||
"no data": {
|
||||
err: errors.New("cannot decode versions: unexpected end of JSON input"),
|
||||
},
|
||||
"empty JSON": {
|
||||
b: []byte("{}"),
|
||||
err: errors.New("cannot decode servers for provider: Cyberghost: unexpected end of JSON input"),
|
||||
},
|
||||
"different versions": {
|
||||
b: []byte(`{}`),
|
||||
hardcoded: models.AllServers{
|
||||
Cyberghost: models.CyberghostServers{Version: 1},
|
||||
Expressvpn: models.ExpressvpnServers{Version: 1},
|
||||
Fastestvpn: models.FastestvpnServers{Version: 1},
|
||||
HideMyAss: models.HideMyAssServers{Version: 1},
|
||||
Ipvanish: models.IpvanishServers{Version: 1},
|
||||
Ivpn: models.IvpnServers{Version: 1},
|
||||
Mullvad: models.MullvadServers{Version: 1},
|
||||
Nordvpn: models.NordvpnServers{Version: 1},
|
||||
Perfectprivacy: models.PerfectprivacyServers{Version: 1},
|
||||
Privado: models.PrivadoServers{Version: 1},
|
||||
Pia: models.PiaServers{Version: 1},
|
||||
Privatevpn: models.PrivatevpnServers{Version: 1},
|
||||
Protonvpn: models.ProtonvpnServers{Version: 1},
|
||||
Purevpn: models.PurevpnServers{Version: 1},
|
||||
Surfshark: models.SurfsharkServers{Version: 1},
|
||||
Torguard: models.TorguardServers{Version: 1},
|
||||
VPNUnlimited: models.VPNUnlimitedServers{Version: 1},
|
||||
Vyprvpn: models.VyprvpnServers{Version: 1},
|
||||
Wevpn: models.WevpnServers{Version: 1},
|
||||
Windscribe: models.WindscribeServers{Version: 1},
|
||||
},
|
||||
logged: []string{
|
||||
"Cyberghost servers from file discarded because they have version 0 and hardcoded servers have version 1",
|
||||
"Expressvpn servers from file discarded because they have version 0 and hardcoded servers have version 1",
|
||||
"Fastestvpn servers from file discarded because they have version 0 and hardcoded servers have version 1",
|
||||
"HideMyAss servers from file discarded because they have version 0 and hardcoded servers have version 1",
|
||||
"Ipvanish servers from file discarded because they have version 0 and hardcoded servers have version 1",
|
||||
"Ivpn servers from file discarded because they have version 0 and hardcoded servers have version 1",
|
||||
"Mullvad servers from file discarded because they have version 0 and hardcoded servers have version 1",
|
||||
"Nordvpn servers from file discarded because they have version 0 and hardcoded servers have version 1",
|
||||
"Perfect Privacy servers from file discarded because they have version 0 and hardcoded servers have version 1",
|
||||
"Privado servers from file discarded because they have version 0 and hardcoded servers have version 1",
|
||||
"Pia servers from file discarded because they have version 0 and hardcoded servers have version 1",
|
||||
"Privatevpn servers from file discarded because they have version 0 and hardcoded servers have version 1",
|
||||
"Protonvpn servers from file discarded because they have version 0 and hardcoded servers have version 1",
|
||||
"Purevpn servers from file discarded because they have version 0 and hardcoded servers have version 1",
|
||||
"Surfshark servers from file discarded because they have version 0 and hardcoded servers have version 1",
|
||||
"Torguard servers from file discarded because they have version 0 and hardcoded servers have version 1",
|
||||
"VPNUnlimited servers from file discarded because they have version 0 and hardcoded servers have version 1",
|
||||
"Vyprvpn servers from file discarded because they have version 0 and hardcoded servers have version 1",
|
||||
"Wevpn servers from file discarded because they have version 0 and hardcoded servers have version 1",
|
||||
"Windscribe servers from file discarded because they have version 0 and hardcoded servers have version 1",
|
||||
},
|
||||
},
|
||||
"same versions": {
|
||||
b: []byte(`{
|
||||
"cyberghost": {"version": 1, "timestamp": 1},
|
||||
"expressvpn": {"version": 1, "timestamp": 1},
|
||||
"fastestvpn": {"version": 1, "timestamp": 1},
|
||||
"hidemyass": {"version": 1, "timestamp": 1},
|
||||
"ipvanish": {"version": 1, "timestamp": 1},
|
||||
"ivpn": {"version": 1, "timestamp": 1},
|
||||
"mullvad": {"version": 1, "timestamp": 1},
|
||||
"nordvpn": {"version": 1, "timestamp": 1},
|
||||
"perfectprivacy": {"version": 1, "timestamp": 1},
|
||||
"privado": {"version": 1, "timestamp": 1},
|
||||
"pia": {"version": 1, "timestamp": 1},
|
||||
"privatevpn": {"version": 1, "timestamp": 1},
|
||||
"protonvpn": {"version": 1, "timestamp": 1},
|
||||
"purevpn": {"version": 1, "timestamp": 1},
|
||||
"surfshark": {"version": 1, "timestamp": 1},
|
||||
"torguard": {"version": 1, "timestamp": 1},
|
||||
"vpnunlimited": {"version": 1, "timestamp": 1},
|
||||
"vyprvpn": {"version": 1, "timestamp": 1},
|
||||
"wevpn": {"version": 1, "timestamp": 1},
|
||||
"windscribe": {"version": 1, "timestamp": 1}
|
||||
}`),
|
||||
hardcoded: models.AllServers{
|
||||
Cyberghost: models.CyberghostServers{Version: 1},
|
||||
Expressvpn: models.ExpressvpnServers{Version: 1},
|
||||
Fastestvpn: models.FastestvpnServers{Version: 1},
|
||||
HideMyAss: models.HideMyAssServers{Version: 1},
|
||||
Ipvanish: models.IpvanishServers{Version: 1},
|
||||
Ivpn: models.IvpnServers{Version: 1},
|
||||
Mullvad: models.MullvadServers{Version: 1},
|
||||
Nordvpn: models.NordvpnServers{Version: 1},
|
||||
Perfectprivacy: models.PerfectprivacyServers{Version: 1},
|
||||
Privado: models.PrivadoServers{Version: 1},
|
||||
Pia: models.PiaServers{Version: 1},
|
||||
Privatevpn: models.PrivatevpnServers{Version: 1},
|
||||
Protonvpn: models.ProtonvpnServers{Version: 1},
|
||||
Purevpn: models.PurevpnServers{Version: 1},
|
||||
Surfshark: models.SurfsharkServers{Version: 1},
|
||||
Torguard: models.TorguardServers{Version: 1},
|
||||
VPNUnlimited: models.VPNUnlimitedServers{Version: 1},
|
||||
Vyprvpn: models.VyprvpnServers{Version: 1},
|
||||
Wevpn: models.WevpnServers{Version: 1},
|
||||
Windscribe: models.WindscribeServers{Version: 1},
|
||||
},
|
||||
persisted: models.AllServers{
|
||||
Cyberghost: models.CyberghostServers{Version: 1, Timestamp: 1},
|
||||
Expressvpn: models.ExpressvpnServers{Version: 1, Timestamp: 1},
|
||||
Fastestvpn: models.FastestvpnServers{Version: 1, Timestamp: 1},
|
||||
HideMyAss: models.HideMyAssServers{Version: 1, Timestamp: 1},
|
||||
Ipvanish: models.IpvanishServers{Version: 1, Timestamp: 1},
|
||||
Ivpn: models.IvpnServers{Version: 1, Timestamp: 1},
|
||||
Mullvad: models.MullvadServers{Version: 1, Timestamp: 1},
|
||||
Nordvpn: models.NordvpnServers{Version: 1, Timestamp: 1},
|
||||
Perfectprivacy: models.PerfectprivacyServers{Version: 1, Timestamp: 1},
|
||||
Privado: models.PrivadoServers{Version: 1, Timestamp: 1},
|
||||
Pia: models.PiaServers{Version: 1, Timestamp: 1},
|
||||
Privatevpn: models.PrivatevpnServers{Version: 1, Timestamp: 1},
|
||||
Protonvpn: models.ProtonvpnServers{Version: 1, Timestamp: 1},
|
||||
Purevpn: models.PurevpnServers{Version: 1, Timestamp: 1},
|
||||
Surfshark: models.SurfsharkServers{Version: 1, Timestamp: 1},
|
||||
Torguard: models.TorguardServers{Version: 1, Timestamp: 1},
|
||||
VPNUnlimited: models.VPNUnlimitedServers{Version: 1, Timestamp: 1},
|
||||
Vyprvpn: models.VyprvpnServers{Version: 1, Timestamp: 1},
|
||||
Wevpn: models.WevpnServers{Version: 1, Timestamp: 1},
|
||||
Windscribe: models.WindscribeServers{Version: 1, Timestamp: 1},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for name, testCase := range testCases {
|
||||
testCase := testCase
|
||||
t.Run(name, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
ctrl := gomock.NewController(t)
|
||||
|
||||
logger := NewMockInfoErrorer(ctrl)
|
||||
for _, logged := range testCase.logged {
|
||||
logger.EXPECT().Info(logged)
|
||||
}
|
||||
|
||||
s := &Storage{
|
||||
logger: logger,
|
||||
}
|
||||
|
||||
servers, err := s.extractServersFromBytes(testCase.b, testCase.hardcoded)
|
||||
|
||||
if testCase.err != nil {
|
||||
require.Error(t, err)
|
||||
assert.Equal(t, testCase.err.Error(), err.Error())
|
||||
} else {
|
||||
assert.NoError(t, err)
|
||||
}
|
||||
|
||||
assert.Equal(t, testCase.persisted, servers)
|
||||
})
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -5,6 +5,8 @@ import (
|
||||
"github.com/qdm12/gluetun/internal/models"
|
||||
)
|
||||
|
||||
//go:generate mockgen -destination=infoerrorer_mock_test.go -package $GOPACKAGE . InfoErrorer
|
||||
|
||||
type Storage struct {
|
||||
mergedServers models.AllServers
|
||||
hardcodedServers models.AllServers
|
||||
|
||||
@@ -22,6 +22,7 @@ func countServers(allServers models.AllServers) int {
|
||||
len(allServers.Ivpn.Servers) +
|
||||
len(allServers.Mullvad.Servers) +
|
||||
len(allServers.Nordvpn.Servers) +
|
||||
len(allServers.Perfectprivacy.Servers) +
|
||||
len(allServers.Privado.Servers) +
|
||||
len(allServers.Pia.Servers) +
|
||||
len(allServers.Privatevpn.Servers) +
|
||||
@@ -36,7 +37,7 @@ func countServers(allServers models.AllServers) int {
|
||||
}
|
||||
|
||||
func (s *Storage) SyncServers() (err error) {
|
||||
serversOnFile, err := readFromFile(s.filepath)
|
||||
serversOnFile, err := s.readFromFile(s.filepath, s.hardcodedServers)
|
||||
if err != nil {
|
||||
return fmt.Errorf("%w: %s", ErrCannotReadFile, err)
|
||||
}
|
||||
|
||||
@@ -4,6 +4,7 @@ import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"net"
|
||||
"sort"
|
||||
"strings"
|
||||
)
|
||||
|
||||
@@ -53,20 +54,38 @@ func ExtractHost(b []byte) (host, warning string, err error) {
|
||||
return hosts[0], warning, nil
|
||||
}
|
||||
|
||||
func ExtractIPs(b []byte) (ips []net.IP, err error) {
|
||||
const rejectIP, rejectDomain = false, true
|
||||
ipStrings := extractRemoteHosts(b, rejectIP, rejectDomain)
|
||||
if len(ipStrings) == 0 {
|
||||
return nil, ErrNoRemoteIP
|
||||
}
|
||||
|
||||
sort.Slice(ipStrings, func(i, j int) bool {
|
||||
return ipStrings[i] < ipStrings[j]
|
||||
})
|
||||
|
||||
ips = make([]net.IP, len(ipStrings))
|
||||
for i := range ipStrings {
|
||||
ips[i] = net.ParseIP(ipStrings[i])
|
||||
}
|
||||
|
||||
return ips, nil
|
||||
}
|
||||
|
||||
func ExtractIP(b []byte) (ip net.IP, warning string, err error) {
|
||||
const (
|
||||
rejectIP = false
|
||||
rejectDomain = true
|
||||
)
|
||||
ips := extractRemoteHosts(b, rejectIP, rejectDomain)
|
||||
if len(ips) == 0 {
|
||||
return nil, "", ErrNoRemoteIP
|
||||
} else if len(ips) > 1 {
|
||||
ips, err := ExtractIPs(b)
|
||||
if err != nil {
|
||||
return nil, "", err
|
||||
}
|
||||
|
||||
if len(ips) > 1 {
|
||||
warning = fmt.Sprintf(
|
||||
"only using the first IP address %s and discarding %d other hosts",
|
||||
ips[0], len(ips)-1)
|
||||
}
|
||||
return net.ParseIP(ips[0]), warning, nil
|
||||
|
||||
return ips[0], warning, nil
|
||||
}
|
||||
|
||||
func extractRemoteHosts(content []byte, rejectIP, rejectDomain bool) (hosts []string) {
|
||||
|
||||
@@ -14,6 +14,7 @@ import (
|
||||
"github.com/qdm12/gluetun/internal/updater/providers/ivpn"
|
||||
"github.com/qdm12/gluetun/internal/updater/providers/mullvad"
|
||||
"github.com/qdm12/gluetun/internal/updater/providers/nordvpn"
|
||||
"github.com/qdm12/gluetun/internal/updater/providers/perfectprivacy"
|
||||
"github.com/qdm12/gluetun/internal/updater/providers/pia"
|
||||
"github.com/qdm12/gluetun/internal/updater/providers/privado"
|
||||
"github.com/qdm12/gluetun/internal/updater/providers/privatevpn"
|
||||
@@ -190,6 +191,27 @@ func (u *updater) updateNordvpn(ctx context.Context) (err error) {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (u *updater) updatePerfectprivacy(ctx context.Context) (err error) {
|
||||
minServers := getMinServers(len(u.servers.Perfectprivacy.Servers))
|
||||
servers, warnings, err := perfectprivacy.GetServers(ctx, u.unzipper, minServers)
|
||||
if u.options.CLI {
|
||||
for _, warning := range warnings {
|
||||
u.logger.Warn(constants.Perfectprivacy + ": " + warning)
|
||||
}
|
||||
}
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if reflect.DeepEqual(u.servers.Perfectprivacy.Servers, servers) {
|
||||
return nil
|
||||
}
|
||||
|
||||
u.servers.Perfectprivacy.Timestamp = u.timeNow().Unix()
|
||||
u.servers.Perfectprivacy.Servers = servers
|
||||
return nil
|
||||
}
|
||||
|
||||
func (u *updater) updatePIA(ctx context.Context) (err error) {
|
||||
minServers := getMinServers(len(u.servers.Pia.Servers))
|
||||
servers, err := pia.GetServers(ctx, u.client, minServers)
|
||||
|
||||
45
internal/updater/providers/perfectprivacy/citytoserver.go
Normal file
45
internal/updater/providers/perfectprivacy/citytoserver.go
Normal file
@@ -0,0 +1,45 @@
|
||||
package perfectprivacy
|
||||
|
||||
import (
|
||||
"net"
|
||||
|
||||
"github.com/qdm12/gluetun/internal/models"
|
||||
)
|
||||
|
||||
type cityToServer map[string]models.PerfectprivacyServer
|
||||
|
||||
func (cts cityToServer) add(city string, ips []net.IP) {
|
||||
server, ok := cts[city]
|
||||
if !ok {
|
||||
server.City = city
|
||||
server.IPs = ips
|
||||
server.TCP = true
|
||||
server.UDP = true
|
||||
} else {
|
||||
// Do not insert duplicate IP addresses
|
||||
existingIPs := make(map[string]struct{}, len(server.IPs))
|
||||
for _, ip := range server.IPs {
|
||||
existingIPs[ip.String()] = struct{}{}
|
||||
}
|
||||
|
||||
for _, ip := range ips {
|
||||
ipString := ip.String()
|
||||
_, ok := existingIPs[ipString]
|
||||
if ok {
|
||||
continue
|
||||
}
|
||||
existingIPs[ipString] = struct{}{}
|
||||
server.IPs = append(server.IPs, ip)
|
||||
}
|
||||
}
|
||||
|
||||
cts[city] = server
|
||||
}
|
||||
|
||||
func (cts cityToServer) toServersSlice() (servers []models.PerfectprivacyServer) {
|
||||
servers = make([]models.PerfectprivacyServer, 0, len(cts))
|
||||
for _, server := range cts {
|
||||
servers = append(servers, server)
|
||||
}
|
||||
return servers
|
||||
}
|
||||
20
internal/updater/providers/perfectprivacy/filename.go
Normal file
20
internal/updater/providers/perfectprivacy/filename.go
Normal file
@@ -0,0 +1,20 @@
|
||||
package perfectprivacy
|
||||
|
||||
import (
|
||||
"strings"
|
||||
"unicode"
|
||||
)
|
||||
|
||||
func parseFilename(fileName string) (city string) {
|
||||
const suffix = ".conf"
|
||||
s := strings.TrimSuffix(fileName, suffix)
|
||||
|
||||
for i, r := range s {
|
||||
if unicode.IsDigit(r) {
|
||||
s = s[:i]
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
return s
|
||||
}
|
||||
73
internal/updater/providers/perfectprivacy/servers.go
Normal file
73
internal/updater/providers/perfectprivacy/servers.go
Normal file
@@ -0,0 +1,73 @@
|
||||
package perfectprivacy
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"net/url"
|
||||
"strings"
|
||||
|
||||
"github.com/qdm12/gluetun/internal/models"
|
||||
"github.com/qdm12/gluetun/internal/updater/openvpn"
|
||||
"github.com/qdm12/gluetun/internal/updater/unzip"
|
||||
)
|
||||
|
||||
var ErrNotEnoughServers = errors.New("not enough servers found")
|
||||
|
||||
func GetServers(ctx context.Context, unzipper unzip.Unzipper, minServers int) (
|
||||
servers []models.PerfectprivacyServer, warnings []string, err error) {
|
||||
zipURL := url.URL{
|
||||
Scheme: "https",
|
||||
Host: "www.perfect-privacy.com",
|
||||
Path: "/downloads/openvpn/get",
|
||||
}
|
||||
values := make(url.Values)
|
||||
values.Set("system", "linux")
|
||||
values.Set("scope", "server")
|
||||
values.Set("filetype", "zip")
|
||||
values.Set("protocol", "udp") // all support both TCP and UDP
|
||||
zipURL.RawQuery = values.Encode()
|
||||
|
||||
contents, err := unzipper.FetchAndExtract(ctx, zipURL.String())
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
cts := make(cityToServer)
|
||||
|
||||
for fileName, content := range contents {
|
||||
err := addServerFromOvpn(cts, fileName, content)
|
||||
if err != nil {
|
||||
warnings = append(warnings, fileName+": "+err.Error())
|
||||
}
|
||||
}
|
||||
|
||||
if len(cts) < minServers {
|
||||
return nil, warnings, fmt.Errorf("%w: %d and expected at least %d",
|
||||
ErrNotEnoughServers, len(cts), minServers)
|
||||
}
|
||||
|
||||
servers = cts.toServersSlice()
|
||||
|
||||
sortServers(servers)
|
||||
|
||||
return servers, warnings, nil
|
||||
}
|
||||
|
||||
func addServerFromOvpn(cts cityToServer,
|
||||
fileName string, content []byte) (err error) {
|
||||
if !strings.HasSuffix(fileName, ".conf") {
|
||||
return nil // not an OpenVPN file
|
||||
}
|
||||
|
||||
ips, err := openvpn.ExtractIPs(content)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
city := parseFilename(fileName)
|
||||
|
||||
cts.add(city, ips)
|
||||
|
||||
return nil
|
||||
}
|
||||
13
internal/updater/providers/perfectprivacy/sort.go
Normal file
13
internal/updater/providers/perfectprivacy/sort.go
Normal file
@@ -0,0 +1,13 @@
|
||||
package perfectprivacy
|
||||
|
||||
import (
|
||||
"sort"
|
||||
|
||||
"github.com/qdm12/gluetun/internal/models"
|
||||
)
|
||||
|
||||
func sortServers(servers []models.PerfectprivacyServer) {
|
||||
sort.Slice(servers, func(i, j int) bool {
|
||||
return servers[i].City < servers[j].City
|
||||
})
|
||||
}
|
||||
37
internal/updater/providers/protonvpn/iptoserver.go
Normal file
37
internal/updater/providers/protonvpn/iptoserver.go
Normal file
@@ -0,0 +1,37 @@
|
||||
package protonvpn
|
||||
|
||||
import (
|
||||
"net"
|
||||
|
||||
"github.com/qdm12/gluetun/internal/models"
|
||||
)
|
||||
|
||||
type ipToServer map[string]models.ProtonvpnServer
|
||||
|
||||
func (its ipToServer) add(country, region, city, name, hostname string,
|
||||
entryIP, exitIP net.IP) {
|
||||
key := entryIP.String()
|
||||
|
||||
server, ok := its[key]
|
||||
if !ok {
|
||||
server.Country = country
|
||||
server.Region = region
|
||||
server.City = city
|
||||
server.Name = name
|
||||
server.Hostname = hostname
|
||||
server.EntryIP = entryIP
|
||||
server.ExitIPs = []net.IP{exitIP}
|
||||
} else {
|
||||
server.ExitIPs = append(server.ExitIPs, exitIP)
|
||||
}
|
||||
|
||||
its[key] = server
|
||||
}
|
||||
|
||||
func (its ipToServer) toServersSlice() (servers []models.ProtonvpnServer) {
|
||||
servers = make([]models.ProtonvpnServer, 0, len(its))
|
||||
for _, server := range its {
|
||||
servers = append(servers, server)
|
||||
}
|
||||
return servers
|
||||
}
|
||||
@@ -33,63 +33,46 @@ func GetServers(ctx context.Context, client *http.Client, minServers int) (
|
||||
ErrNotEnoughServers, count, minServers)
|
||||
}
|
||||
|
||||
servers = make([]models.ProtonvpnServer, 0, count)
|
||||
ipToServer := make(ipToServer, count)
|
||||
for _, logicalServer := range data.LogicalServers {
|
||||
region := getStringValue(logicalServer.Region)
|
||||
city := getStringValue(logicalServer.City)
|
||||
name := logicalServer.Name
|
||||
for _, physicalServer := range logicalServer.Servers {
|
||||
server, warning, err := makeServer(
|
||||
physicalServer, logicalServer, countryCodes)
|
||||
if physicalServer.Status == 0 { // disabled so skip server
|
||||
warnings = append(warnings,
|
||||
"ignoring server "+physicalServer.Domain+" with status 0")
|
||||
continue
|
||||
}
|
||||
|
||||
hostname := physicalServer.Domain
|
||||
entryIP := physicalServer.EntryIP
|
||||
exitIP := physicalServer.ExitIP
|
||||
|
||||
// Note: for multi-hop use the server name or hostname
|
||||
// instead of the country
|
||||
countryCode := logicalServer.ExitCountry
|
||||
country, warning := codeToCountry(countryCode, countryCodes)
|
||||
if warning != "" {
|
||||
warnings = append(warnings, warning)
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
warnings = append(warnings, err.Error())
|
||||
continue
|
||||
}
|
||||
|
||||
servers = append(servers, server)
|
||||
ipToServer.add(country, region, city, name, hostname, entryIP, exitIP)
|
||||
}
|
||||
}
|
||||
|
||||
if len(servers) < minServers {
|
||||
if len(ipToServer) < minServers {
|
||||
return nil, warnings, fmt.Errorf("%w: %d and expected at least %d",
|
||||
ErrNotEnoughServers, len(servers), minServers)
|
||||
ErrNotEnoughServers, len(ipToServer), minServers)
|
||||
}
|
||||
|
||||
servers = ipToServer.toServersSlice()
|
||||
|
||||
sortServers(servers)
|
||||
|
||||
return servers, warnings, nil
|
||||
}
|
||||
|
||||
var errServerStatusZero = errors.New("ignoring server with status 0")
|
||||
|
||||
func makeServer(physical physicalServer, logical logicalServer,
|
||||
countryCodes map[string]string) (server models.ProtonvpnServer,
|
||||
warning string, err error) {
|
||||
if physical.Status == 0 {
|
||||
return server, "", fmt.Errorf("%w: %s",
|
||||
errServerStatusZero, physical.Domain)
|
||||
}
|
||||
|
||||
countryCode := logical.ExitCountry
|
||||
country, warning := codeToCountry(countryCode, countryCodes)
|
||||
|
||||
server = models.ProtonvpnServer{
|
||||
// Note: for multi-hop use the server name or hostname
|
||||
// instead of the country
|
||||
Country: country,
|
||||
Region: getStringValue(logical.Region),
|
||||
City: getStringValue(logical.City),
|
||||
Name: logical.Name,
|
||||
Hostname: physical.Domain,
|
||||
EntryIP: physical.EntryIP,
|
||||
ExitIP: physical.ExitIP,
|
||||
}
|
||||
|
||||
return server, warning, nil
|
||||
}
|
||||
|
||||
func getStringValue(ptr *string) string {
|
||||
if ptr == nil {
|
||||
return ""
|
||||
|
||||
@@ -16,7 +16,8 @@ func zipExtractAll(zipBytes []byte) (contents map[string][]byte, err error) {
|
||||
contents = map[string][]byte{}
|
||||
for _, zf := range r.File {
|
||||
fileName := filepath.Base(zf.Name)
|
||||
if !strings.HasSuffix(fileName, ".ovpn") {
|
||||
if !strings.HasSuffix(fileName, ".ovpn") &&
|
||||
!strings.HasSuffix(fileName, ".conf") {
|
||||
continue
|
||||
}
|
||||
f, err := zf.Open()
|
||||
|
||||
@@ -14,8 +14,6 @@ var (
|
||||
|
||||
func (u *unzipper) FetchAndExtract(ctx context.Context, url string) (
|
||||
contents map[string][]byte, err error) {
|
||||
contents = make(map[string][]byte)
|
||||
|
||||
request, err := http.NewRequestWithContext(ctx, http.MethodGet, url, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@@ -40,13 +38,5 @@ func (u *unzipper) FetchAndExtract(ctx context.Context, url string) (
|
||||
return nil, err
|
||||
}
|
||||
|
||||
newContents, err := zipExtractAll(b)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
for fileName, content := range newContents {
|
||||
contents[fileName] = content
|
||||
}
|
||||
|
||||
return contents, nil
|
||||
return zipExtractAll(b)
|
||||
}
|
||||
|
||||
@@ -132,6 +132,16 @@ func (u *updater) UpdateServers(ctx context.Context) (allServers models.AllServe
|
||||
}
|
||||
}
|
||||
|
||||
if u.options.Perfectprivacy {
|
||||
u.logger.Info("updating " + constants.Perfectprivacy + " servers...")
|
||||
if err := u.updatePerfectprivacy(ctx); err != nil {
|
||||
if ctxErr := ctx.Err(); ctxErr != nil {
|
||||
return allServers, ctxErr
|
||||
}
|
||||
u.logger.Error(err.Error())
|
||||
}
|
||||
}
|
||||
|
||||
if u.options.Privado {
|
||||
u.logger.Info("updating Privado servers...")
|
||||
if err := u.updatePrivado(ctx); err != nil {
|
||||
|
||||
@@ -3,6 +3,8 @@ package version
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"time"
|
||||
)
|
||||
@@ -23,17 +25,25 @@ type githubCommit struct {
|
||||
}
|
||||
}
|
||||
|
||||
var errHTTPStatusCode = errors.New("bad response HTTP status code")
|
||||
|
||||
func getGithubReleases(ctx context.Context, client *http.Client) (releases []githubRelease, err error) {
|
||||
const url = "https://api.github.com/repos/qdm12/gluetun/releases"
|
||||
request, err := http.NewRequestWithContext(ctx, http.MethodGet, url, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
response, err := client.Do(request)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer response.Body.Close()
|
||||
|
||||
if response.StatusCode != http.StatusOK {
|
||||
return nil, fmt.Errorf("%w: %s", errHTTPStatusCode, response.Status)
|
||||
}
|
||||
|
||||
decoder := json.NewDecoder(response.Body)
|
||||
if err := decoder.Decode(&releases); err != nil {
|
||||
return nil, err
|
||||
|
||||
@@ -5,8 +5,6 @@
|
||||
- Remove duplicate `/gluetun` directory creation
|
||||
- Remove firewall shadowsocks input port?
|
||||
- Remove `script-security` option
|
||||
- `ncp-ciphers` to `data-ciphers`
|
||||
- Remove `ncp-disable`
|
||||
|
||||
## Uniformization
|
||||
|
||||
@@ -40,6 +38,7 @@
|
||||
- `WIREGUARD_ADDRESS` to `WIREGUARD_ADDRESSES`
|
||||
- `VPNSP` to `VPN_SERVICE_PROVIDER`
|
||||
- Rename `REGION` (etc.) to `SERVER_REGIONS`
|
||||
- `OPENVPN_CIPHER` to `OPENVPN_CIPHERS`
|
||||
- Split servers.json and compress it
|
||||
|
||||
## Gluetun V4
|
||||
|
||||
Reference in New Issue
Block a user