Compare commits

..

44 Commits

Author SHA1 Message Date
Quentin McGaw
b9a9319cb4 fix(ci): trigger CI on published releases 2021-11-27 12:09:33 +00:00
Quentin McGaw
77e4317135 chore(dev): fix devcontainer post create command 2021-11-27 12:01:07 +00:00
dependabot[bot]
b10d97e53a Chore(deps): Bump github.com/breml/rootcerts from 0.1.1 to 0.2.0 (#722)
Bumps [github.com/breml/rootcerts](https://github.com/breml/rootcerts) from 0.1.1 to 0.2.0.
- [Release notes](https://github.com/breml/rootcerts/releases)
- [Commits](https://github.com/breml/rootcerts/compare/v0.1.1...v0.2.0)

---
updated-dependencies:
- dependency-name: github.com/breml/rootcerts
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2021-11-27 06:58:32 -05:00
dependabot[bot]
648a4c04d7 Build(deps): Bump actions/checkout from 2.3.4 to 2.4.0 (#705)
Bumps [actions/checkout](https://github.com/actions/checkout) from 2.3.4 to 2.4.0.
- [Release notes](https://github.com/actions/checkout/releases)
- [Changelog](https://github.com/actions/checkout/blob/main/CHANGELOG.md)
- [Commits](https://github.com/actions/checkout/compare/v2.3.4...v2.4.0)

---
updated-dependencies:
- dependency-name: actions/checkout
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2021-11-27 06:58:16 -05:00
Quentin McGaw
3ca674dca7 feat(windscribe): update server information 2021-11-18 22:00:05 +00:00
Quentin McGaw
fa97fd496e feat(torguard): update server information 2021-11-18 09:35:30 +00:00
Quentin McGaw
c76a7ee8da chore(dot): add error description for update files failure 2021-11-18 08:12:07 +00:00
Quentin McGaw
80f6b78332 chore(config): fix bad error wrapping 2021-11-17 22:32:33 +00:00
Quentin McGaw
8dc54a7c44 feat(privatevpn): support OPENVPN_PORT 2021-11-17 22:32:18 +00:00
Quentin McGaw
8f080c537b fix(privatevpn): openvpn configuration values 2021-11-17 22:26:18 +00:00
Quentin McGaw
427cf86f44 chore(ci) disable snyk analysis for false positive 2021-11-12 23:39:01 +00:00
Quentin McGaw
2d244c08e7 Fix: 2 low vulnerability busybox issues 2021-11-12 23:04:42 +00:00
Quentin McGaw
82c0f523aa fix: openvpn at /usr/sbin/openvpn2.5
- Fix operation on QNAP devices
- Refer to #157
2021-11-12 22:48:19 +00:00
Quentin McGaw
c07a0b0ada chore(lint): add bidichk, ifshort, nilnil and tenv 2021-11-08 22:41:20 +00:00
Quentin McGaw
e4c306c0ee chore(linter): update golangci-lint to v1.43.0 2021-11-07 21:26:31 +00:00
Quentin McGaw
6ffb94f819 fix(updater): cli error message 2021-11-07 21:25:10 +00:00
Quentin McGaw
142238e8b7 feat(protonvpn): update built-in servers data 2021-11-07 21:24:57 +00:00
Quentin McGaw (desktop)
678e23c7d6 Change: run OpenVPN as root to clean routes on exit 2021-11-01 22:51:03 +00:00
Quentin McGaw (desktop)
0abcebe1d8 Feat: update NordVPN server information 2021-11-01 22:48:36 +00:00
Quentin McGaw (desktop)
f398af1169 Fix: check github http status code for version 2021-10-29 01:37:45 +00:00
Quentin McGaw (desktop)
afbea415e3 Maint: replace with for markdown generated tables 2021-10-25 22:38:59 +00:00
Quentin McGaw (desktop)
225bd5d25b Fix: CI to use short commits 2021-10-21 13:46:50 +00:00
Quentin McGaw (desktop)
3651cc6161 Maint: CI image tags rework 2021-10-16 14:58:11 +00:00
Quentin McGaw (desktop)
dc674014ff Fix: vyprvpn: openvpn comp-lzo option 2021-10-14 19:55:48 +00:00
Quentin McGaw (desktop)
0e0e03949d Docs: add urgent and low priority labels 2021-10-14 16:37:09 +00:00
Quentin McGaw (desktop)
f5bf5c236a Hotfix: CI if condition 2021-10-14 16:32:43 +00:00
dependabot[bot]
94480ecabb Maint: bump docker/build-push-action from 2.6.1 to 2.7.0 (#664) 2021-10-14 09:31:08 -07:00
dependabot[bot]
31ef9b1d45 Maint: bump github.com/breml/rootcerts from 0.1.0 to 0.1.1 (#668) 2021-10-14 09:30:21 -07:00
Quentin McGaw (desktop)
bf76132fd4 Maint: fix dependabot CI trigger 2021-10-14 16:29:22 +00:00
Quentin McGaw (desktop)
8cc2983318 Fix: NordVPN: Re-add comp-lzo option 2021-10-13 00:04:51 +00:00
Quentin McGaw (desktop)
caeca18ed7 Hotfix: ci workflow 2021-10-06 18:54:44 +00:00
dependabot[bot]
50febb41ff Maint: bump docker/build-push-action from 2.6.1 to 2.7.0 (#583) 2021-10-06 11:47:06 -07:00
Quentin McGaw (desktop)
79293e067c Doc: fix readme Wiki links 2021-10-06 17:47:08 +00:00
Quentin McGaw (desktop)
f45be80591 Maint: CI changes
- Only trigger on push and PR to master
- Do not push images for branches
- Add fork only workflow
- Add dependabot only workflow
- Do not trigger ci workflow from forked/dependabot PRs
2021-10-06 14:23:01 +00:00
Quentin McGaw (desktop)
d405ba8dca Feat: run OpenVPN without root 2021-10-05 21:33:15 +00:00
Quentin McGaw (desktop)
ca975b1c01 Feat: multiple OpenVPN ciphers for negotiation
- Perfect privacy to accept AES-256-CBC and AES-256-GCM
- Cyberghost default cipher set to AES-256-GCM
- `OPENVPN_CIPHER` accept comma separated cipher values
- Use `ncp-ciphers` for OpenVPN 2.4
2021-10-05 20:36:23 +00:00
Quentin McGaw
e0e3ca3832 Feat: Perfect privacy support (#606) 2021-10-05 10:44:15 -07:00
Quentin McGaw (desktop)
e7c952cbf7 Maint: remove opendns.com due to bad x509 cert 2021-09-30 16:01:35 +00:00
Quentin McGaw (desktop)
85ad2dd39a Maint: simplify warning logging in http proxy 2021-09-30 16:01:02 +00:00
Quentin McGaw (desktop)
0c4f0ec17b Doc: add ref to image tags in bug issue template 2021-09-30 15:34:21 +00:00
Quentin McGaw (desktop)
5ad4136955 Maint: move splash at start of program 2021-09-30 15:28:24 +00:00
Quentin McGaw (desktop)
a432de95a9 Maint: deduplicate ProtonVPN servers by entry IP 2021-09-30 15:23:18 +00:00
Quentin McGaw (desktop)
1d25a0e18c Fix: server data version diff when reading file 2021-09-30 15:22:57 +00:00
Quentin McGaw (desktop)
29fd95685f Doc: add custom provider option for bug issue template 2021-09-29 20:41:38 +00:00
90 changed files with 10315 additions and 15186 deletions

View File

@@ -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",

View File

@@ -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
View File

@@ -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: ""

View File

@@ -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
View 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 .

View File

@@ -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
View 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 .

View File

@@ -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

View File

@@ -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"

View File

@@ -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

View File

@@ -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 && \

View File

@@ -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:

View File

@@ -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
View File

@@ -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
View File

@@ -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=

View File

@@ -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:

View File

@@ -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")

View File

@@ -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)
}

View File

@@ -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": "",

View 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
}

View File

@@ -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:

View File

@@ -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,

View File

@@ -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

View File

@@ -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

View 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)
}

View File

@@ -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) {

View File

@@ -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.

View File

@@ -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()

View File

@@ -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()

View File

@@ -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
}

View File

@@ -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{

View File

@@ -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 {

View File

@@ -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)
}

View File

@@ -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 {

View File

@@ -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"`

View File

@@ -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

View File

@@ -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,
},

View File

@@ -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) (

View File

@@ -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)

View File

@@ -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",

View File

@@ -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")

View File

@@ -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")

View File

@@ -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")

View File

@@ -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)

View File

@@ -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)))

View File

@@ -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)

View File

@@ -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)

View File

@@ -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")

View 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)
}

View 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
}

View 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
}

View 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),
}
}

View File

@@ -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)

View File

@@ -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 != "" {

View File

@@ -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 {

View File

@@ -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, "")

View File

@@ -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")

View File

@@ -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:

View File

@@ -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")

View File

@@ -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")

View File

@@ -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)

View File

@@ -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, ":"),
}
}
}

View File

@@ -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)
})

View File

@@ -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 != "" {

View File

@@ -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")

View File

@@ -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)

View File

@@ -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")

View File

@@ -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",

View File

@@ -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")

View File

@@ -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{},

View 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)
}

View File

@@ -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
}

View File

@@ -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"`
}

View 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

View File

@@ -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

View File

@@ -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)
}

View File

@@ -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) {

View File

@@ -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)

View 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
}

View 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
}

View 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
}

View 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
})
}

View 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
}

View File

@@ -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 ""

View File

@@ -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()

View File

@@ -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)
}

View File

@@ -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 {

View File

@@ -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

View File

@@ -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