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" "vscode"
], ],
"shutdownAction": "stopCompose", "shutdownAction": "stopCompose",
"postCreateCommand": "source ~/.windows.sh && go mod download && go mod tidy", "postCreateCommand": "~/.windows.sh && go mod download && go mod tidy",
"workspaceFolder": "/workspace", "workspaceFolder": "/workspace",
"extensions": [ "extensions": [
"golang.go", "golang.go",

View File

@@ -11,7 +11,9 @@ body:
id: urgent id: urgent
attributes: attributes:
label: Is this urgent? 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: options:
- "No" - "No"
- "Yes" - "Yes"
@@ -38,6 +40,7 @@ body:
attributes: attributes:
label: VPN service provider label: VPN service provider
options: options:
- Custom
- Cyberghost - Cyberghost
- ExpressVPN - ExpressVPN
- FastestVPN - FastestVPN

11
.github/labels.yml vendored
View File

@@ -14,6 +14,14 @@
color: "795548" color: "795548"
description: "" description: ""
# Priority
- name: "🚨 Urgent"
color: "d5232f"
description: ""
- name: "💤 Low priority"
color: "4285f4"
description: ""
# VPN providers # VPN providers
- name: ":cloud: Cyberghost" - name: ":cloud: Cyberghost"
color: "cfe8d4" color: "cfe8d4"
@@ -39,6 +47,9 @@
- name: ":cloud: NordVPN" - name: ":cloud: NordVPN"
color: "cfe8d4" color: "cfe8d4"
description: "" description: ""
- name: ":cloud: Perfect Privacy"
color: "cfe8d4"
description: ""
- name: ":cloud: PIA" - name: ":cloud: PIA"
color: "cfe8d4" color: "cfe8d4"
description: "" description: ""

View File

@@ -1,6 +1,24 @@
name: CI name: CI
on: on:
release:
types:
- published
push: push:
branches:
- master
paths:
- .github/workflows/ci.yml
- cmd/**
- internal/**
- pkg/**
- .dockerignore
- .golangci.yml
- Dockerfile
- go.mod
- go.sum
pull_request:
branches:
- master
paths: paths:
- .github/workflows/ci.yml - .github/workflows/ci.yml
- cmd/** - cmd/**
@@ -14,11 +32,17 @@ on:
jobs: jobs:
verify: 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 runs-on: ubuntu-latest
env: env:
DOCKER_BUILDKIT: "1" DOCKER_BUILDKIT: "1"
steps: steps:
- uses: actions/checkout@v2.3.4 - uses: actions/checkout@v2.4.0
- name: Linting - name: Linting
run: docker build --target lint . run: docker build --target lint .
@@ -44,18 +68,23 @@ jobs:
- name: Build final image - name: Build final image
run: docker build -t final-image . run: docker build -t final-image .
- name: Image security analysis # - name: Image security analysis
uses: snyk/actions/docker@master # uses: snyk/actions/docker@master
env: # env:
SNYK_TOKEN: ${{ secrets.SNYK_TOKEN }} # SNYK_TOKEN: ${{ secrets.SNYK_TOKEN }}
with: # with:
image: final-image # image: final-image
publish: 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] needs: [verify]
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- uses: actions/checkout@v2.3.4 - uses: actions/checkout@v2.4.0
- uses: docker/setup-qemu-action@v1 - uses: docker/setup-qemu-action@v1
- uses: docker/setup-buildx-action@v1 - uses: docker/setup-buildx-action@v1
@@ -65,35 +94,51 @@ jobs:
username: qmcgaw username: qmcgaw
password: ${{ secrets.DOCKERHUB_PASSWORD }} password: ${{ secrets.DOCKERHUB_PASSWORD }}
- name: Set variables - name: Check for semver tag
id: vars id: semvercheck
env:
EVENT_NAME: ${{ github.event_name }}
run: | run: |
BRANCH=${GITHUB_REF#refs/heads/} if [[ ${{ github.ref }} =~ ^refs/tags/v[0-9]+\.[0-9]+\.[0-9]+$ ]]; then
TAG=${GITHUB_REF#refs/tags/} MATCH=true
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
else else
echo ::set-output name=version::$BRANCH MATCH=false
echo ::set-output name=platforms::linux/amd64,linux/386,linux/arm64,linux/arm/v6,linux/arm/v7,linux/ppc64le
fi 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 - name: Build and push final image
uses: docker/build-push-action@v2.6.1 uses: docker/build-push-action@v2.7.0
with: 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: | build-args: |
CREATED=${{ steps.vars.outputs.created }} CREATED=${{ fromJSON(steps.meta.outputs.json).labels['org.opencontainers.image.created'] }}
COMMIT=${{ steps.vars.outputs.commit }} COMMIT=${{ steps.shortcommit.outputs.value }}
VERSION=${{ steps.vars.outputs.version }} VERSION=${{ fromJSON(steps.meta.outputs.json).labels['org.opencontainers.image.version'] }}
tags: | tags: ${{ steps.meta.outputs.tags }}
qmcgaw/gluetun:${{ steps.vars.outputs.version }}
qmcgaw/private-internet-access:${{ steps.vars.outputs.version }}
push: true 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 runs-on: ubuntu-latest
steps: steps:
- name: Checkout - name: Checkout
uses: actions/checkout@v2.3.4 uses: actions/checkout@v2.4.0
- name: Docker Hub Description - name: Docker Hub Description
uses: peter-evans/dockerhub-description@v2 uses: peter-evans/dockerhub-description@v2
with: 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: labeler:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- uses: actions/checkout@v2.3.4 - uses: actions/checkout@v2.4.0
- uses: crazy-max/ghaction-github-labeler@v3 - uses: crazy-max/ghaction-github-labeler@v3
with: with:
yaml-file: .github/labels.yml yaml-file: .github/labels.yml

View File

@@ -8,7 +8,7 @@ jobs:
misspell: misspell:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- uses: actions/checkout@v2.3.4 - uses: actions/checkout@v2.4.0
- uses: reviewdog/action-misspell@v1 - uses: reviewdog/action-misspell@v1
with: with:
locale: "US" locale: "US"

View File

@@ -31,8 +31,11 @@ linters:
enable: enable:
# - cyclop # - cyclop
# - errorlint # - errorlint
# - ireturn
# - varnamelen
# - wrapcheck # - wrapcheck
- asciicheck - asciicheck
- bidichk
- bodyclose - bodyclose
- dogsled - dogsled
- dupl - dupl
@@ -56,7 +59,7 @@ linters:
- gomoddirectives - gomoddirectives
- goprintffuncname - goprintffuncname
- gosec - gosec
- importas - ifshort
- importas - importas
- lll - lll
- makezero - makezero
@@ -64,6 +67,7 @@ linters:
- nakedret - nakedret
- nestif - nestif
- nilerr - nilerr
- nilnil
- noctx - noctx
- nolintlint - nolintlint
- prealloc - prealloc
@@ -73,6 +77,7 @@ linters:
- revive - revive
- rowserrcheck - rowserrcheck
- sqlclosecheck - sqlclosecheck
- tenv
- thelper - thelper
- tparallel - tparallel
- unconvert - unconvert

View File

@@ -2,7 +2,7 @@ ARG ALPINE_VERSION=3.14
ARG GO_ALPINE_VERSION=3.14 ARG GO_ALPINE_VERSION=3.14
ARG GO_VERSION=1.17 ARG GO_VERSION=1.17
ARG XCPUTRANSLATE_VERSION=v0.6.0 ARG XCPUTRANSLATE_VERSION=v0.6.0
ARG GOLANGCI_LINT_VERSION=v1.42.1 ARG GOLANGCI_LINT_VERSION=v1.43.0
ARG BUILDPLATFORM=linux/amd64 ARG BUILDPLATFORM=linux/amd64
FROM --platform=${BUILDPLATFORM} qmcgaw/xcputranslate:${XCPUTRANSLATE_VERSION} AS xcputranslate 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 && \ mv /usr/sbin/openvpn /usr/sbin/openvpn2.4 && \
apk del openvpn && \ apk del openvpn && \
apk add --no-cache --update openvpn ca-certificates iptables ip6tables unbound tzdata && \ 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 && \ 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 openvpn && \
deluser unbound && \ deluser unbound && \

View File

@@ -1,7 +1,7 @@
# Gluetun VPN client # Gluetun VPN client
*Lightweight swiss-knife-like VPN client to tunnel to Cyberghost, ExpressVPN, FastestVPN, *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 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* 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 ## Features
- Based on Alpine 3.14 for a small Docker image of 31MB - Based on Alpine 3.14 for a small Docker image of 33MB
- Supports: **Cyberghost**, **ExpressVPN**, **FastestVPN**, **HideMyAss**, **IPVanish**, **IVPN**, **Mullvad**, **NordVPN**, **Privado**, **Private Internet Access**, **PrivateVPN**, **ProtonVPN**, **PureVPN**, **Surfshark**, **TorGuard**, **VPNUnlimited**, **Vyprvpn**, **WeVPN**, **Windscribe** servers - 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 OpenVPN for all providers listed
- Supports Wireguard - Supports Wireguard
- For **Mullvad**, **Ivpn** and **Windscribe** - 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 **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/Environment-variables#custom) - 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) - 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 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 - 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)! 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: 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 // TODO run this in a loop or in openvpn to reload from file without restarting
storageLogger := logger.NewChild(logging.Settings{Prefix: "storage: "}) storageLogger := logger.NewChild(logging.Settings{Prefix: "storage: "})
storage, err := storage.New(storageLogger, constants.ServersData) 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, dnsConf := unbound.NewConfigurator(nil, cmder, dnsCrypto,
"/etc/unbound", "/usr/sbin/unbound", cacertsPath) "/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{ err = printVersions(ctx, logger, []printVersionElement{
{name: "Alpine", getVersion: alpineConf.Version}, {name: "Alpine", getVersion: alpineConf.Version},
{name: "OpenVPN 2.4", getVersion: ovpnConf.Version24}, {name: "OpenVPN 2.4", getVersion: ovpnConf.Version24},

2
go.mod
View File

@@ -3,7 +3,7 @@ module github.com/qdm12/gluetun
go 1.17 go 1.17
require ( require (
github.com/breml/rootcerts v0.1.0 github.com/breml/rootcerts v0.2.0
github.com/fatih/color v1.13.0 github.com/fatih/color v1.13.0
github.com/go-ping/ping v0.0.0-20210911151512-381826476871 github.com/go-ping/ping v0.0.0-20210911151512-381826476871
github.com/golang/mock v1.6.0 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/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/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/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.2.0 h1:bBIgVe8bS0Ec+orgWpZ/GRYt3a0O8yoW+g2kSBY2aLE=
github.com/breml/rootcerts v0.1.0/go.mod h1:24FDtzYMpqIeYC7QzaE8VPRQaFZU5TIUDlyk8qwjD88= github.com/breml/rootcerts v0.2.0/go.mod h1:24FDtzYMpqIeYC7QzaE8VPRQaFZU5TIUDlyk8qwjD88=
github.com/creack/pty v1.1.7/go.mod h1:lj5s0c3V2DBrqTV7llrYr5NG6My20zk30Fl46Y7DoTY= github.com/creack/pty v1.1.7/go.mod h1:lj5s0c3V2DBrqTV7llrYr5NG6My20zk30Fl46Y7DoTY=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=

View File

@@ -26,7 +26,7 @@ var (
func (c *CLI) FormatServers(args []string) error { func (c *CLI) FormatServers(args []string) error {
var format, output string var format, output string
var cyberghost, expressvpn, fastestvpn, hideMyAss, ipvanish, ivpn, mullvad, 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 torguard, vpnUnlimited, vyprvpn, wevpn, windscribe bool
flagSet := flag.NewFlagSet("markdown", flag.ExitOnError) flagSet := flag.NewFlagSet("markdown", flag.ExitOnError)
flagSet.StringVar(&format, "format", "markdown", "Format to use which can be: 'markdown'") 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(&ivpn, "ivpn", false, "Format IVPN servers")
flagSet.BoolVar(&mullvad, "mullvad", false, "Format Mullvad servers") flagSet.BoolVar(&mullvad, "mullvad", false, "Format Mullvad servers")
flagSet.BoolVar(&nordvpn, "nordvpn", false, "Format Nordvpn 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(&pia, "pia", false, "Format Private Internet Access servers")
flagSet.BoolVar(&privado, "privado", false, "Format Privado servers") flagSet.BoolVar(&privado, "privado", false, "Format Privado servers")
flagSet.BoolVar(&privatevpn, "privatevpn", false, "Format Private VPN 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() formatted = currentServers.Mullvad.ToMarkdown()
case nordvpn: case nordvpn:
formatted = currentServers.Nordvpn.ToMarkdown() formatted = currentServers.Nordvpn.ToMarkdown()
case perfectPrivacy:
formatted = currentServers.Perfectprivacy.ToMarkdown()
case pia: case pia:
formatted = currentServers.Pia.ToMarkdown() formatted = currentServers.Pia.ToMarkdown()
case privado: case privado:

View File

@@ -18,7 +18,7 @@ import (
) )
var ( 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") ErrNewStorage = errors.New("cannot create storage")
ErrUpdateServerInformation = errors.New("cannot update server information") ErrUpdateServerInformation = errors.New("cannot update server information")
ErrWriteToFile = errors.New("cannot write updated information to file") 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.Ivpn, "ivpn", false, "Update IVPN servers")
flagSet.BoolVar(&options.Mullvad, "mullvad", false, "Update Mullvad servers") flagSet.BoolVar(&options.Mullvad, "mullvad", false, "Update Mullvad servers")
flagSet.BoolVar(&options.Nordvpn, "nordvpn", false, "Update Nordvpn 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.PIA, "pia", false, "Update Private Internet Access post-summer 2020 servers")
flagSet.BoolVar(&options.Privado, "privado", false, "Update Privado servers") flagSet.BoolVar(&options.Privado, "privado", false, "Update Privado servers")
flagSet.BoolVar(&options.Privatevpn, "privatevpn", false, "Update Private VPN servers") flagSet.BoolVar(&options.Privatevpn, "privatevpn", false, "Update Private VPN servers")

View File

@@ -19,7 +19,7 @@ type OpenVPN struct {
Flags []string `json:"flags"` Flags []string `json:"flags"`
MSSFix uint16 `json:"mssfix"` MSSFix uint16 `json:"mssfix"`
Root bool `json:"run_as_root"` Root bool `json:"run_as_root"`
Cipher string `json:"cipher"` Ciphers []string `json:"ciphers"`
Auth string `json:"auth"` Auth string `json:"auth"`
ConfFile string `json:"conf_file"` ConfFile string `json:"conf_file"`
Version string `json:"version"` Version string `json:"version"`
@@ -52,8 +52,8 @@ func (settings *OpenVPN) lines() (lines []string) {
lines = append(lines, indent+lastIndent+"Run as root: enabled") lines = append(lines, indent+lastIndent+"Run as root: enabled")
} }
if len(settings.Cipher) > 0 { if len(settings.Ciphers) > 0 {
lines = append(lines, indent+lastIndent+"Custom cipher: "+settings.Cipher) lines = append(lines, indent+lastIndent+"Custom ciphers: "+commaJoin(settings.Ciphers))
} }
if len(settings.Auth) > 0 { if len(settings.Auth) > 0 {
lines = append(lines, indent+lastIndent+"Custom auth algorithm: "+settings.Auth) 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.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 { if err != nil {
return fmt.Errorf("environment variable OPENVPN_ROOT: %w", err) 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 { if err != nil {
return fmt.Errorf("environment variable OPENVPN_CIPHER: %w", err) return fmt.Errorf("environment variable OPENVPN_CIPHER: %w", err)
} }

View File

@@ -13,6 +13,7 @@ func Test_OpenVPN_JSON(t *testing.T) {
in := OpenVPN{ in := OpenVPN{
Root: true, Root: true,
Flags: []string{}, Flags: []string{},
Ciphers: []string{},
} }
data, err := json.MarshalIndent(in, "", " ") data, err := json.MarshalIndent(in, "", " ")
require.NoError(t, err) require.NoError(t, err)
@@ -23,7 +24,7 @@ func Test_OpenVPN_JSON(t *testing.T) {
"flags": [], "flags": [],
"mssfix": 0, "mssfix": 0,
"run_as_root": true, "run_as_root": true,
"cipher": "", "ciphers": [],
"auth": "", "auth": "",
"conf_file": "", "conf_file": "",
"version": "", "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) err = settings.readMullvad(r)
case constants.Nordvpn: case constants.Nordvpn:
err = settings.readNordvpn(r) err = settings.readNordvpn(r)
case constants.Perfectprivacy:
err = settings.readPerfectPrivacy(r)
case constants.Privado: case constants.Privado:
err = settings.readPrivado(r) err = settings.readPrivado(r)
case constants.PrivateInternetAccess: case constants.PrivateInternetAccess:
@@ -108,7 +110,7 @@ func (settings *Provider) readVPNServiceProvider(r reader, vpnType string) (err
constants.Custom, constants.Custom,
"cyberghost", constants.Expressvpn, "fastestvpn", "hidemyass", "ipvanish", "cyberghost", constants.Expressvpn, "fastestvpn", "hidemyass", "ipvanish",
"ivpn", "mullvad", "nordvpn", "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", "purevpn", "surfshark", "torguard", constants.VPNUnlimited, "vyprvpn",
constants.Wevpn, "windscribe"} constants.Wevpn, "windscribe"}
case constants.Wireguard: case constants.Wireguard:

View File

@@ -168,6 +168,21 @@ func Test_Provider_lines(t *testing.T) {
" |--Protocol: udp", " |--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": { "privado": {
settings: Provider{ settings: Provider{
Name: constants.Privado, Name: constants.Privado,

View File

@@ -17,7 +17,8 @@ type ServerSelection struct { //nolint:maligned
// Expressvpn, Fastestvpn, HideMyAss, IPVanish, IVPN, Mullvad, PrivateVPN, Protonvpn, PureVPN, VPNUnlimited // Expressvpn, Fastestvpn, HideMyAss, IPVanish, IVPN, Mullvad, PrivateVPN, Protonvpn, PureVPN, VPNUnlimited
Countries []string `json:"countries"` 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"` Cities []string `json:"cities"`
// Expressvpn, Fastestvpn, HideMyAss, IPVanish, IVPN, PrivateVPN, Windscribe, Privado, Protonvpn, VPNUnlimited, WeVPN // Expressvpn, Fastestvpn, HideMyAss, IPVanish, IVPN, PrivateVPN, Windscribe, Privado, Protonvpn, VPNUnlimited, WeVPN
Hostnames []string `json:"hostnames"` Hostnames []string `json:"hostnames"`
@@ -137,7 +138,7 @@ func (settings *OpenVPNSelection) readProtocolAndPort(r reader) (err error) {
settings.CustomPort, err = readOpenVPNCustomPort(r, openvpnPortValidation{allAllowed: true}) settings.CustomPort, err = readOpenVPNCustomPort(r, openvpnPortValidation{allAllowed: true})
if err != nil { if err != nil {
return fmt.Errorf("environment variable PORT: %w", err) return err
} }
return nil return nil

View File

@@ -19,6 +19,7 @@ type Updater struct {
Ivpn bool `json:"ivpn"` Ivpn bool `json:"ivpn"`
Mullvad bool `json:"mullvad"` Mullvad bool `json:"mullvad"`
Nordvpn bool `json:"nordvpn"` Nordvpn bool `json:"nordvpn"`
Perfectprivacy bool `json:"perfectprivacy"`
PIA bool `json:"pia"` PIA bool `json:"pia"`
Privado bool `json:"privado"` Privado bool `json:"privado"`
Privatevpn bool `json:"privatevpn"` Privatevpn bool `json:"privatevpn"`
@@ -57,6 +58,7 @@ func (settings *Updater) EnableAll() {
settings.Ivpn = true settings.Ivpn = true
settings.Mullvad = true settings.Mullvad = true
settings.Nordvpn = true settings.Nordvpn = true
settings.Perfectprivacy = true
settings.Privado = true settings.Privado = true
settings.PIA = true settings.PIA = true
settings.Privado = 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 //nolint:lll
const ( 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==" 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) { func PrivatevpnCountryChoices(servers []models.PrivatevpnServer) (choices []string) {

View File

@@ -25,6 +25,8 @@ const (
Mullvad = "mullvad" Mullvad = "mullvad"
// Nordvpn is a VPN provider. // Nordvpn is a VPN provider.
Nordvpn = "nordvpn" Nordvpn = "nordvpn"
// Perfectprivacy is a VPN provider.
Perfectprivacy = "perfect privacy"
// Privado is a VPN provider. // Privado is a VPN provider.
Privado = "privado" Privado = "privado"
// PrivateInternetAccess is a VPN provider. // PrivateInternetAccess is a VPN provider.

View File

@@ -3,6 +3,7 @@ package dns
import ( import (
"context" "context"
"errors" "errors"
"fmt"
"net" "net"
"github.com/qdm12/dns/pkg/check" "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) { cancel context.CancelFunc, waitError chan error, closeStreams func(), err error) {
err = l.updateFiles(ctx) err = l.updateFiles(ctx)
if err != nil { if err != nil {
return nil, nil, nil, errUpdateFiles return nil, nil, nil,
fmt.Errorf("%w: %s", errUpdateFiles, err)
} }
settings := l.GetSettings() 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) response, err := h.client.Do(request)
if err != nil { if err != nil {
http.Error(responseWriter, "server error", http.StatusInternalServerError) http.Error(responseWriter, "server error", http.StatusInternalServerError)
h.logger.Warn("cannot request " + request.URL.String() + h.logger.Warn("cannot process request for client " + request.RemoteAddr + ": " + err.Error())
" for client " + request.RemoteAddr + ": " + err.Error())
return return
} }
defer response.Body.Close() defer response.Body.Close()

View File

@@ -14,6 +14,7 @@ func (a AllServers) GetCopy() (servers AllServers) {
servers.Ivpn.Servers = a.GetIvpn() servers.Ivpn.Servers = a.GetIvpn()
servers.Mullvad.Servers = a.GetMullvad() servers.Mullvad.Servers = a.GetMullvad()
servers.Nordvpn.Servers = a.GetNordvpn() servers.Nordvpn.Servers = a.GetNordvpn()
servers.Perfectprivacy.Servers = a.GetPerfectprivacy()
servers.Privado.Servers = a.GetPrivado() servers.Privado.Servers = a.GetPrivado()
servers.Pia.Servers = a.GetPia() servers.Pia.Servers = a.GetPia()
servers.Privatevpn.Servers = a.GetPrivatevpn() servers.Privatevpn.Servers = a.GetPrivatevpn()
@@ -124,6 +125,18 @@ func (a *AllServers) GetNordvpn() (servers []NordvpnServer) {
return servers 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) { func (a *AllServers) GetPia() (servers []PIAServer) {
if a.Pia.Servers == nil { if a.Pia.Servers == nil {
return nil return nil
@@ -168,7 +181,7 @@ func (a *AllServers) GetProtonvpn() (servers []ProtonvpnServer) {
for i, serverToCopy := range a.Protonvpn.Servers { for i, serverToCopy := range a.Protonvpn.Servers {
servers[i] = serverToCopy servers[i] = serverToCopy
servers[i].EntryIP = copyIP(serverToCopy.EntryIP) servers[i].EntryIP = copyIP(serverToCopy.EntryIP)
servers[i].ExitIP = copyIP(serverToCopy.ExitIP) servers[i].ExitIPs = copyIPs(serverToCopy.ExitIPs)
} }
return servers return servers
} }

View File

@@ -51,6 +51,11 @@ func Test_AllServers_GetCopy(t *testing.T) {
IP: net.IP{1, 2, 3, 4}, IP: net.IP{1, 2, 3, 4},
}}, }},
}, },
Perfectprivacy: PerfectprivacyServers{
Servers: []PerfectprivacyServer{{
IPs: []net.IP{{1, 2, 3, 4}},
}},
},
Privado: PrivadoServers{ Privado: PrivadoServers{
Servers: []PrivadoServer{{ Servers: []PrivadoServer{{
IP: net.IP{1, 2, 3, 4}, IP: net.IP{1, 2, 3, 4},
@@ -69,7 +74,7 @@ func Test_AllServers_GetCopy(t *testing.T) {
Protonvpn: ProtonvpnServers{ Protonvpn: ProtonvpnServers{
Servers: []ProtonvpnServer{{ Servers: []ProtonvpnServer{{
EntryIP: net.IP{1, 2, 3, 4}, EntryIP: net.IP{1, 2, 3, 4},
ExitIP: net.IP{1, 2, 3, 4}, ExitIPs: []net.IP{{1, 2, 3, 4}},
}}, }},
}, },
Purevpn: PurevpnServers{ Purevpn: PurevpnServers{

View File

@@ -9,7 +9,7 @@ func boolToMarkdown(b bool) string {
if b { if b {
return "✅" return "✅"
} }
return "" return ""
} }
func markdownTableHeading(legendFields ...string) (markdown string) { func markdownTableHeading(legendFields ...string) (markdown string) {
@@ -141,6 +141,19 @@ func (s *PrivadoServer) ToMarkdown() (markdown string) {
s.Country, s.Region, s.City, s.Hostname) 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) { func (s *PiaServers) ToMarkdown() (markdown string) {
markdown = markdownTableHeading("Region", "Hostname", "TCP", "UDP") markdown = markdownTableHeading("Region", "Hostname", "TCP", "UDP")
for _, server := range s.Servers { for _, server := range s.Servers {

View File

@@ -19,8 +19,8 @@ func Test_CyberghostServers_ToMarkdown(t *testing.T) {
markdown := servers.ToMarkdown() markdown := servers.ToMarkdown()
const expected = "| Country | Hostname | TCP | UDP |\n" + const expected = "| Country | Hostname | TCP | UDP |\n" +
"| --- | --- | --- | --- |\n" + "| --- | --- | --- | --- |\n" +
"| a | `xa` | | ✅ |\n" + "| a | `xa` | | ✅ |\n" +
"| b | `xb` | ✅ | |\n" "| b | `xb` | ✅ | |\n"
assert.Equal(t, expected, markdown) assert.Equal(t, expected, markdown)
} }
@@ -38,8 +38,8 @@ func Test_FastestvpnServers_ToMarkdown(t *testing.T) {
markdown := servers.ToMarkdown() markdown := servers.ToMarkdown()
const expected = "| Country | Hostname | TCP | UDP |\n" + const expected = "| Country | Hostname | TCP | UDP |\n" +
"| --- | --- | --- | --- |\n" + "| --- | --- | --- | --- |\n" +
"| a | `xa` | ✅ | |\n" + "| a | `xa` | ✅ | |\n" +
"| b | `xb` | | ✅ |\n" "| b | `xb` | | ✅ |\n"
assert.Equal(t, expected, markdown) assert.Equal(t, expected, markdown)
} }

View File

@@ -81,6 +81,13 @@ type NordvpnServer struct { //nolint:maligned
UDP bool `json:"udp"` 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 { type PrivadoServer struct {
Country string `json:"country"` Country string `json:"country"`
Region string `json:"region"` Region string `json:"region"`
@@ -113,7 +120,7 @@ type ProtonvpnServer struct {
Name string `json:"name"` Name string `json:"name"`
Hostname string `json:"hostname"` Hostname string `json:"hostname"`
EntryIP net.IP `json:"entry_ip"` EntryIP net.IP `json:"entry_ip"`
ExitIP net.IP `json:"exit_ip"` // TODO verify it matches with public IP once connected ExitIPs []net.IP `json:"exit_ip"` // TODO verify it matches with public IP once connected
} }
type PurevpnServer struct { type PurevpnServer struct {

View File

@@ -9,6 +9,7 @@ type AllServers struct {
Ipvanish IpvanishServers `json:"ipvanish"` Ipvanish IpvanishServers `json:"ipvanish"`
Ivpn IvpnServers `json:"ivpn"` Ivpn IvpnServers `json:"ivpn"`
Mullvad MullvadServers `json:"mullvad"` Mullvad MullvadServers `json:"mullvad"`
Perfectprivacy PerfectprivacyServers `json:"perfectprivacy"`
Nordvpn NordvpnServers `json:"nordvpn"` Nordvpn NordvpnServers `json:"nordvpn"`
Privado PrivadoServers `json:"privado"` Privado PrivadoServers `json:"privado"`
Pia PiaServers `json:"pia"` Pia PiaServers `json:"pia"`
@@ -32,6 +33,7 @@ func (a *AllServers) Count() int {
len(a.Ivpn.Servers) + len(a.Ivpn.Servers) +
len(a.Mullvad.Servers) + len(a.Mullvad.Servers) +
len(a.Nordvpn.Servers) + len(a.Nordvpn.Servers) +
len(a.Perfectprivacy.Servers) +
len(a.Privado.Servers) + len(a.Privado.Servers) +
len(a.Pia.Servers) + len(a.Pia.Servers) +
len(a.Privatevpn.Servers) + len(a.Privatevpn.Servers) +
@@ -85,6 +87,11 @@ type NordvpnServers struct {
Timestamp int64 `json:"timestamp"` Timestamp int64 `json:"timestamp"`
Servers []NordvpnServer `json:"servers"` Servers []NordvpnServer `json:"servers"`
} }
type PerfectprivacyServers struct {
Version uint16 `json:"version"`
Timestamp int64 `json:"timestamp"`
Servers []PerfectprivacyServer `json:"servers"`
}
type PrivadoServers struct { type PrivadoServers struct {
Version uint16 `json:"version"` Version uint16 `json:"version"`
Timestamp int64 `json:"timestamp"` Timestamp int64 `json:"timestamp"`

View File

@@ -11,8 +11,6 @@ var (
) )
func extractPEM(b []byte, name string) (encodedData string, err error) { func extractPEM(b []byte, name string) (encodedData string, err error) {
name = strings.ToUpper(name) // certificate -> CERTIFICATE
pemBlock, _ := pem.Decode(b) pemBlock, _ := pem.Decode(b)
if pemBlock == nil { if pemBlock == nil {
return "", errPEMDecode return "", errPEMDecode

View File

@@ -25,7 +25,7 @@ func Test_extractPEM(t *testing.T) {
err: errors.New("cannot decode PEM encoded block"), err: errors.New("cannot decode PEM encoded block"),
}, },
"valid data": { "valid data": {
name: "certificate", name: "CERTIFICATE",
b: []byte(validCertPEM), b: []byte(validCertPEM),
encodedData: validCertData, encodedData: validCertData,
}, },

View File

@@ -15,7 +15,7 @@ var ErrVersionUnknown = errors.New("OpenVPN version is unknown")
const ( const (
binOpenvpn24 = "openvpn2.4" binOpenvpn24 = "openvpn2.4"
binOpenvpn25 = "openvpn" binOpenvpn25 = "openvpn2.5"
) )
func start(ctx context.Context, starter command.Starter, version string, flags []string) ( 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, "remote "),
strings.HasPrefix(line, "dev "), strings.HasPrefix(line, "dev "),
// Remove values eventually modified // Remove values eventually modified
settings.Cipher != "" && hasPrefixOneOf(line, len(settings.Ciphers) > 0 && hasPrefixOneOf(line,
"cipher ", "data-ciphers ", "data-ciphers-fallback "), "cipher ", "ncp-ciphers ", "data-ciphers ", "data-ciphers-fallback "),
settings.Auth != "" && strings.HasPrefix(line, "auth "), settings.Auth != "" && strings.HasPrefix(line, "auth "),
settings.MSSFix > 0 && strings.HasPrefix(line, "mssfix "), settings.MSSFix > 0 && strings.HasPrefix(line, "mssfix "),
!settings.IPv6 && hasPrefixOneOf(line, "tun-ipv6", !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, "auth-user-pass "+constants.OpenVPNAuthConf)
} }
modified = append(modified, "verb "+strconv.Itoa(settings.Verbosity)) modified = append(modified, "verb "+strconv.Itoa(settings.Verbosity))
if settings.Cipher != "" { if len(settings.Ciphers) > 0 {
modified = append(modified, utils.CipherLines(settings.Cipher, settings.Version)...) modified = append(modified, utils.CipherLines(settings.Ciphers, settings.Version)...)
} }
if settings.Auth != "" { if settings.Auth != "" {
modified = append(modified, "auth "+settings.Auth) modified = append(modified, "auth "+settings.Auth)

View File

@@ -32,7 +32,7 @@ func Test_modifyConfig(t *testing.T) {
}, },
settings: configuration.OpenVPN{ settings: configuration.OpenVPN{
User: "user", User: "user",
Cipher: "cipher", Ciphers: []string{"cipher"},
Auth: "auth", Auth: "auth",
MSSFix: 1000, MSSFix: 1000,
ProcUser: "procuser", ProcUser: "procuser",

View File

@@ -2,7 +2,6 @@ package cyberghost
import ( import (
"strconv" "strconv"
"strings"
"github.com/qdm12/gluetun/internal/configuration" "github.com/qdm12/gluetun/internal/configuration"
"github.com/qdm12/gluetun/internal/constants" "github.com/qdm12/gluetun/internal/constants"
@@ -12,8 +11,12 @@ import (
func (c *Cyberghost) BuildConf(connection models.Connection, func (c *Cyberghost) BuildConf(connection models.Connection,
settings configuration.OpenVPN) (lines []string, err error) { settings configuration.OpenVPN) (lines []string, err error) {
if settings.Cipher == "" { if len(settings.Ciphers) == 0 {
settings.Cipher = constants.AES128gcm settings.Ciphers = []string{
constants.AES256gcm,
constants.AES256cbc,
constants.AES128gcm,
}
} }
if settings.Auth == "" { if settings.Auth == "" {
@@ -45,16 +48,12 @@ func (c *Cyberghost) BuildConf(connection models.Connection,
connection.OpenVPNRemoteLine(), 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 { if connection.Protocol == constants.UDP {
lines = append(lines, "explicit-exit-notify") 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 { if !settings.Root {
lines = append(lines, "user "+settings.ProcUser) lines = append(lines, "user "+settings.ProcUser)
lines = append(lines, "persist-tun") lines = append(lines, "persist-tun")

View File

@@ -11,8 +11,8 @@ import (
func (p *Provider) BuildConf(connection models.Connection, func (p *Provider) BuildConf(connection models.Connection,
settings configuration.OpenVPN) (lines []string, err error) { settings configuration.OpenVPN) (lines []string, err error) {
if settings.Cipher == "" { if len(settings.Ciphers) == 0 {
settings.Cipher = constants.AES256cbc settings.Ciphers = []string{constants.AES256cbc}
} }
if settings.Auth == "" { if settings.Auth == "" {
settings.Auth = constants.SHA512 settings.Auth = constants.SHA512
@@ -53,7 +53,7 @@ func (p *Provider) BuildConf(connection models.Connection,
connection.OpenVPNRemoteLine(), 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 { if connection.Protocol == constants.UDP {
lines = append(lines, "explicit-exit-notify") lines = append(lines, "explicit-exit-notify")

View File

@@ -11,8 +11,8 @@ import (
func (f *Fastestvpn) BuildConf(connection models.Connection, func (f *Fastestvpn) BuildConf(connection models.Connection,
settings configuration.OpenVPN) (lines []string, err error) { settings configuration.OpenVPN) (lines []string, err error) {
if settings.Cipher == "" { if len(settings.Ciphers) == 0 {
settings.Cipher = constants.AES256cbc settings.Ciphers = []string{constants.AES256cbc}
} }
if settings.Auth == "" { if settings.Auth == "" {
settings.Auth = constants.SHA256 settings.Auth = constants.SHA256
@@ -49,7 +49,7 @@ func (f *Fastestvpn) BuildConf(connection models.Connection,
connection.OpenVPNRemoteLine(), 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 { if connection.Protocol == constants.UDP {
lines = append(lines, "explicit-exit-notify") lines = append(lines, "explicit-exit-notify")

View File

@@ -11,8 +11,8 @@ import (
func (h *HideMyAss) BuildConf(connection models.Connection, func (h *HideMyAss) BuildConf(connection models.Connection,
settings configuration.OpenVPN) (lines []string, err error) { settings configuration.OpenVPN) (lines []string, err error) {
if settings.Cipher == "" { if len(settings.Ciphers) == 0 {
settings.Cipher = constants.AES256cbc settings.Ciphers = []string{constants.AES256cbc}
} }
lines = []string{ lines = []string{
@@ -39,7 +39,7 @@ func (h *HideMyAss) BuildConf(connection models.Connection,
connection.OpenVPNRemoteLine(), connection.OpenVPNRemoteLine(),
} }
lines = append(lines, utils.CipherLines(settings.Cipher, settings.Version)...) lines = append(lines, utils.CipherLines(settings.Ciphers, settings.Version)...)
if settings.Auth != "" { if settings.Auth != "" {
lines = append(lines, "auth "+settings.Auth) lines = append(lines, "auth "+settings.Auth)

View File

@@ -11,8 +11,8 @@ import (
func (i *Ipvanish) BuildConf(connection models.Connection, func (i *Ipvanish) BuildConf(connection models.Connection,
settings configuration.OpenVPN) (lines []string, err error) { settings configuration.OpenVPN) (lines []string, err error) {
if settings.Cipher == "" { if len(settings.Ciphers) == 0 {
settings.Cipher = constants.AES256cbc settings.Ciphers = []string{constants.AES256cbc}
} }
if settings.Auth == "" { if settings.Auth == "" {
settings.Auth = constants.SHA256 settings.Auth = constants.SHA256
@@ -43,7 +43,7 @@ func (i *Ipvanish) BuildConf(connection models.Connection,
connection.OpenVPNRemoteLine(), connection.OpenVPNRemoteLine(),
} }
lines = append(lines, utils.CipherLines(settings.Cipher, settings.Version)...) lines = append(lines, utils.CipherLines(settings.Ciphers, settings.Version)...)
if settings.MSSFix > 0 { if settings.MSSFix > 0 {
lines = append(lines, "mssfix "+strconv.Itoa(int(settings.MSSFix))) lines = append(lines, "mssfix "+strconv.Itoa(int(settings.MSSFix)))

View File

@@ -12,8 +12,8 @@ import (
func (i *Ivpn) BuildConf(connection models.Connection, func (i *Ivpn) BuildConf(connection models.Connection,
settings configuration.OpenVPN) (lines []string, err error) { settings configuration.OpenVPN) (lines []string, err error) {
if settings.Cipher == "" { if len(settings.Ciphers) == 0 {
settings.Cipher = constants.AES256cbc settings.Ciphers = []string{constants.AES256cbc}
} }
namePrefix := strings.Split(connection.Hostname, ".")[0] namePrefix := strings.Split(connection.Hostname, ".")[0]
@@ -45,7 +45,7 @@ func (i *Ivpn) BuildConf(connection models.Connection,
connection.OpenVPNRemoteLine(), connection.OpenVPNRemoteLine(),
} }
lines = append(lines, utils.CipherLines(settings.Cipher, settings.Version)...) lines = append(lines, utils.CipherLines(settings.Ciphers, settings.Version)...)
if settings.Auth != "" { if settings.Auth != "" {
lines = append(lines, "auth "+settings.Auth) lines = append(lines, "auth "+settings.Auth)

View File

@@ -11,8 +11,8 @@ import (
func (m *Mullvad) BuildConf(connection models.Connection, func (m *Mullvad) BuildConf(connection models.Connection,
settings configuration.OpenVPN) (lines []string, err error) { settings configuration.OpenVPN) (lines []string, err error) {
if settings.Cipher == "" { if len(settings.Ciphers) == 0 {
settings.Cipher = constants.AES256cbc settings.Ciphers = []string{constants.AES256cbc, constants.AES128gcm}
} }
lines = []string{ lines = []string{
@@ -42,7 +42,7 @@ func (m *Mullvad) BuildConf(connection models.Connection,
connection.OpenVPNRemoteLine(), connection.OpenVPNRemoteLine(),
} }
lines = append(lines, utils.CipherLines(settings.Cipher, settings.Version)...) lines = append(lines, utils.CipherLines(settings.Ciphers, settings.Version)...)
if settings.Auth != "" { if settings.Auth != "" {
lines = append(lines, "auth "+settings.Auth) lines = append(lines, "auth "+settings.Auth)

View File

@@ -11,8 +11,8 @@ import (
func (n *Nordvpn) BuildConf(connection models.Connection, func (n *Nordvpn) BuildConf(connection models.Connection,
settings configuration.OpenVPN) (lines []string, err error) { settings configuration.OpenVPN) (lines []string, err error) {
if settings.Cipher == "" { if len(settings.Ciphers) == 0 {
settings.Cipher = constants.AES256cbc settings.Ciphers = []string{constants.AES256cbc}
} }
if settings.Auth == "" { if settings.Auth == "" {
@@ -39,6 +39,7 @@ func (n *Nordvpn) BuildConf(connection models.Connection,
"key-direction 1", "key-direction 1",
"auth-user-pass " + constants.OpenVPNAuthConf, "auth-user-pass " + constants.OpenVPNAuthConf,
"auth " + settings.Auth, "auth " + settings.Auth,
"comp-lzo", // Required, NordVPN does not work without it
// Added constant values // Added constant values
"auth-nocache", "auth-nocache",
@@ -52,7 +53,7 @@ func (n *Nordvpn) BuildConf(connection models.Connection,
connection.OpenVPNRemoteLine(), 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 { if connection.Protocol == constants.UDP {
lines = append(lines, "fast-io") 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, func (p *Privado) BuildConf(connection models.Connection,
settings configuration.OpenVPN) (lines []string, err error) { settings configuration.OpenVPN) (lines []string, err error) {
if settings.Cipher == "" { if len(settings.Ciphers) == 0 {
settings.Cipher = constants.AES256cbc settings.Ciphers = []string{constants.AES256cbc}
} }
if settings.Auth == "" { if settings.Auth == "" {
@@ -45,7 +45,7 @@ func (p *Privado) BuildConf(connection models.Connection,
connection.OpenVPNRemoteLine(), connection.OpenVPNRemoteLine(),
} }
lines = append(lines, utils.CipherLines(settings.Cipher, settings.Version)...) lines = append(lines, utils.CipherLines(settings.Ciphers, settings.Version)...)
if !settings.Root { if !settings.Root {
lines = append(lines, "user "+settings.ProcUser) lines = append(lines, "user "+settings.ProcUser)

View File

@@ -30,8 +30,8 @@ func (p *PIA) BuildConf(connection models.Connection,
certificate = constants.PIACertificateStrong certificate = constants.PIACertificateStrong
} }
if settings.Cipher == "" { if len(settings.Ciphers) == 0 {
settings.Cipher = defaultCipher settings.Ciphers = []string{defaultCipher}
} }
if settings.Auth == "" { if settings.Auth == "" {
@@ -62,8 +62,8 @@ func (p *PIA) BuildConf(connection models.Connection,
connection.OpenVPNRemoteLine(), connection.OpenVPNRemoteLine(),
} }
if settings.Cipher != "" { if len(settings.Ciphers) > 0 {
lines = append(lines, utils.CipherLines(settings.Cipher, settings.Version)...) lines = append(lines, utils.CipherLines(settings.Ciphers, settings.Version)...)
} }
if settings.Auth != "" { if settings.Auth != "" {

View File

@@ -15,6 +15,9 @@ func (p *Privatevpn) GetConnection(selection configuration.ServerSelection) (
protocol = constants.TCP protocol = constants.TCP
port = 443 port = 443
} }
if selection.OpenVPN.CustomPort > 0 {
port = selection.OpenVPN.CustomPort
}
servers, err := p.filterServers(selection) servers, err := p.filterServers(selection)
if err != nil { if err != nil {

View File

@@ -11,8 +11,8 @@ import (
func (p *Privatevpn) BuildConf(connection models.Connection, func (p *Privatevpn) BuildConf(connection models.Connection,
settings configuration.OpenVPN) (lines []string, err error) { settings configuration.OpenVPN) (lines []string, err error) {
if settings.Cipher == "" { if len(settings.Ciphers) == 0 {
settings.Cipher = constants.AES128gcm settings.Ciphers = []string{constants.AES128gcm}
} }
if settings.Auth == "" { if settings.Auth == "" {
@@ -43,7 +43,7 @@ func (p *Privatevpn) BuildConf(connection models.Connection,
connection.OpenVPNRemoteLine(), 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 { if connection.Protocol == constants.UDP {
lines = append(lines, "key-direction 1") lines = append(lines, "key-direction 1")
@@ -67,7 +67,7 @@ func (p *Privatevpn) BuildConf(connection models.Connection,
lines = append(lines, utils.WrapOpenvpnCA( lines = append(lines, utils.WrapOpenvpnCA(
constants.PrivatevpnCertificate)...) constants.PrivatevpnCertificate)...)
lines = append(lines, utils.WrapOpenvpnTLSCrypt( lines = append(lines, utils.WrapOpenvpnTLSAuth(
constants.PrivatevpnOpenvpnStaticKeyV1)...) constants.PrivatevpnOpenvpnStaticKeyV1)...)
lines = append(lines, "") lines = append(lines, "")

View File

@@ -11,8 +11,8 @@ import (
func (p *Protonvpn) BuildConf(connection models.Connection, func (p *Protonvpn) BuildConf(connection models.Connection,
settings configuration.OpenVPN) (lines []string, err error) { settings configuration.OpenVPN) (lines []string, err error) {
if settings.Cipher == "" { if len(settings.Ciphers) == 0 {
settings.Cipher = constants.AES256cbc settings.Ciphers = []string{constants.AES256cbc}
} }
if settings.Auth == "" { if settings.Auth == "" {
@@ -52,7 +52,7 @@ func (p *Protonvpn) BuildConf(connection models.Connection,
connection.OpenVPNRemoteLine(), 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 { if connection.Protocol == constants.UDP {
lines = append(lines, "fast-io") 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/ivpn"
"github.com/qdm12/gluetun/internal/provider/mullvad" "github.com/qdm12/gluetun/internal/provider/mullvad"
"github.com/qdm12/gluetun/internal/provider/nordvpn" "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/privado"
"github.com/qdm12/gluetun/internal/provider/privateinternetaccess" "github.com/qdm12/gluetun/internal/provider/privateinternetaccess"
"github.com/qdm12/gluetun/internal/provider/privatevpn" "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) return mullvad.New(allServers.Mullvad.Servers, randSource)
case constants.Nordvpn: case constants.Nordvpn:
return nordvpn.New(allServers.Nordvpn.Servers, randSource) return nordvpn.New(allServers.Nordvpn.Servers, randSource)
case constants.Perfectprivacy:
return perfectprivacy.New(allServers.Perfectprivacy.Servers, randSource)
case constants.Privado: case constants.Privado:
return privado.New(allServers.Privado.Servers, randSource) return privado.New(allServers.Privado.Servers, randSource)
case constants.PrivateInternetAccess: case constants.PrivateInternetAccess:

View File

@@ -11,8 +11,8 @@ import (
func (p *Purevpn) BuildConf(connection models.Connection, func (p *Purevpn) BuildConf(connection models.Connection,
settings configuration.OpenVPN) (lines []string, err error) { settings configuration.OpenVPN) (lines []string, err error) {
if settings.Cipher == "" { if len(settings.Ciphers) == 0 {
settings.Cipher = constants.AES256gcm settings.Ciphers = []string{constants.AES256gcm}
} }
lines = []string{ lines = []string{
@@ -40,7 +40,7 @@ func (p *Purevpn) BuildConf(connection models.Connection,
connection.OpenVPNRemoteLine(), 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 { if connection.Protocol == constants.UDP {
lines = append(lines, "explicit-exit-notify") lines = append(lines, "explicit-exit-notify")

View File

@@ -11,8 +11,8 @@ import (
func (s *Surfshark) BuildConf(connection models.Connection, func (s *Surfshark) BuildConf(connection models.Connection,
settings configuration.OpenVPN) (lines []string, err error) { settings configuration.OpenVPN) (lines []string, err error) {
if settings.Cipher == "" { if len(settings.Ciphers) == 0 {
settings.Cipher = constants.AES256gcm settings.Ciphers = []string{constants.AES256gcm}
} }
if settings.Auth == "" { if settings.Auth == "" {
@@ -53,7 +53,7 @@ func (s *Surfshark) BuildConf(connection models.Connection,
connection.OpenVPNRemoteLine(), 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 { if connection.Protocol == constants.UDP {
lines = append(lines, "explicit-exit-notify") lines = append(lines, "explicit-exit-notify")

View File

@@ -11,8 +11,8 @@ import (
func (t *Torguard) BuildConf(connection models.Connection, func (t *Torguard) BuildConf(connection models.Connection,
settings configuration.OpenVPN) (lines []string, err error) { settings configuration.OpenVPN) (lines []string, err error) {
if settings.Cipher == "" { if len(settings.Ciphers) == 0 {
settings.Cipher = constants.AES256gcm settings.Ciphers = []string{constants.AES256gcm}
} }
if settings.Auth == "" { if settings.Auth == "" {
@@ -55,7 +55,7 @@ func (t *Torguard) BuildConf(connection models.Connection,
connection.OpenVPNRemoteLine(), connection.OpenVPNRemoteLine(),
} }
lines = append(lines, utils.CipherLines(settings.Cipher, settings.Version)...) lines = append(lines, utils.CipherLines(settings.Ciphers, settings.Version)...)
if !settings.Root { if !settings.Root {
lines = append(lines, "user "+settings.ProcUser) lines = append(lines, "user "+settings.ProcUser)

View File

@@ -1,17 +1,22 @@
package utils package utils
import ( import (
"strings"
"github.com/qdm12/gluetun/internal/constants" "github.com/qdm12/gluetun/internal/constants"
) )
func CipherLines(cipher, version string) (lines []string) { func CipherLines(ciphers []string, version string) (lines []string) {
switch version { switch version {
case constants.Openvpn24: case constants.Openvpn24:
return []string{"cipher " + cipher} return []string{
"cipher " + ciphers[0],
"ncp-ciphers " + strings.Join(ciphers, ":"),
}
default: // 2.5 and above default: // 2.5 and above
return []string{ return []string{
"data-ciphers-fallback " + cipher, "data-ciphers-fallback " + ciphers[0],
"data-ciphers " + cipher, "data-ciphers " + strings.Join(ciphers, ":"),
} }
} }
} }

View File

@@ -9,24 +9,31 @@ import (
func Test_CipherLines(t *testing.T) { func Test_CipherLines(t *testing.T) {
t.Parallel() t.Parallel()
testCases := map[string]struct { testCases := map[string]struct {
ciphers []string
version string version string
lines []string lines []string
}{ }{
"empty version": { "empty version": {
ciphers: []string{"AES"},
lines: []string{ lines: []string{
"data-ciphers-fallback AES", "data-ciphers-fallback AES",
"data-ciphers AES", "data-ciphers AES",
}, },
}, },
"2.4": { "2.4": {
ciphers: []string{"AES", "CBC"},
version: "2.4", version: "2.4",
lines: []string{"cipher AES"}, lines: []string{
"cipher AES",
"ncp-ciphers AES:CBC",
},
}, },
"2.5": { "2.5": {
ciphers: []string{"AES", "CBC"},
version: "2.5", version: "2.5",
lines: []string{ lines: []string{
"data-ciphers-fallback AES", "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.Run(name, func(t *testing.T) {
t.Parallel() t.Parallel()
const cipher = "AES" lines := CipherLines(testCase.ciphers, testCase.version)
lines := CipherLines(cipher, testCase.version)
assert.Equal(t, testCase.lines, lines) assert.Equal(t, testCase.lines, lines)
}) })

View File

@@ -36,8 +36,8 @@ func (p *Provider) BuildConf(connection models.Connection,
connection.OpenVPNRemoteLine(), connection.OpenVPNRemoteLine(),
} }
if settings.Cipher != "" { if len(settings.Ciphers) > 0 {
lines = append(lines, utils.CipherLines(settings.Cipher, settings.Version)...) lines = append(lines, utils.CipherLines(settings.Ciphers, settings.Version)...)
} }
if settings.Auth != "" { if settings.Auth != "" {

View File

@@ -11,8 +11,8 @@ import (
func (v *Vyprvpn) BuildConf(connection models.Connection, func (v *Vyprvpn) BuildConf(connection models.Connection,
settings configuration.OpenVPN) (lines []string, err error) { settings configuration.OpenVPN) (lines []string, err error) {
if settings.Cipher == "" { if len(settings.Ciphers) == 0 {
settings.Cipher = constants.AES256cbc settings.Ciphers = []string{constants.AES256cbc}
} }
if settings.Auth == "" { 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 "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-user-pass " + constants.OpenVPNAuthConf,
"auth " + settings.Auth, "auth " + settings.Auth,
"comp-lzo",
// Added constant values // Added constant values
"auth-nocache", "auth-nocache",
@@ -46,7 +47,7 @@ func (v *Vyprvpn) BuildConf(connection models.Connection,
connection.OpenVPNRemoteLine(), 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 { if connection.Protocol == constants.UDP {
lines = append(lines, "explicit-exit-notify") lines = append(lines, "explicit-exit-notify")

View File

@@ -11,8 +11,8 @@ import (
func (w *Wevpn) BuildConf(connection models.Connection, func (w *Wevpn) BuildConf(connection models.Connection,
settings configuration.OpenVPN) (lines []string, err error) { settings configuration.OpenVPN) (lines []string, err error) {
if settings.Cipher == "" { if len(settings.Ciphers) == 0 {
settings.Cipher = constants.AES256gcm settings.Ciphers = []string{constants.AES256gcm}
} }
if settings.Auth == "" { if settings.Auth == "" {
@@ -50,7 +50,7 @@ func (w *Wevpn) BuildConf(connection models.Connection,
lines = append(lines, "explicit-exit-notify") 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 { if !settings.Root {
lines = append(lines, "user "+settings.ProcUser) lines = append(lines, "user "+settings.ProcUser)

View File

@@ -2,7 +2,6 @@ package windscribe
import ( import (
"strconv" "strconv"
"strings"
"github.com/qdm12/gluetun/internal/configuration" "github.com/qdm12/gluetun/internal/configuration"
"github.com/qdm12/gluetun/internal/constants" "github.com/qdm12/gluetun/internal/constants"
@@ -12,8 +11,12 @@ import (
func (w *Windscribe) BuildConf(connection models.Connection, func (w *Windscribe) BuildConf(connection models.Connection,
settings configuration.OpenVPN) (lines []string, err error) { settings configuration.OpenVPN) (lines []string, err error) {
if settings.Cipher == "" { if len(settings.Ciphers) == 0 {
settings.Cipher = constants.AES256gcm settings.Ciphers = []string{
constants.AES256gcm,
constants.AES256cbc,
constants.AES128gcm,
}
} }
if settings.Auth == "" { if settings.Auth == "" {
@@ -48,11 +51,7 @@ func (w *Windscribe) BuildConf(connection models.Connection,
connection.OpenVPNRemoteLine(), connection.OpenVPNRemoteLine(),
} }
lines = append(lines, utils.CipherLines(settings.Cipher, settings.Version)...) lines = append(lines, utils.CipherLines(settings.Ciphers, settings.Version)...)
if strings.HasSuffix(settings.Cipher, "-gcm") {
lines = append(lines, "ncp-ciphers AES-256-GCM:AES-256-CBC:AES-128-GCM")
}
if connection.Protocol == constants.UDP { if connection.Protocol == constants.UDP {
lines = append(lines, "explicit-exit-notify") 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:8245",
"http://ip1.dynupdate.no-ip.com", "http://ip1.dynupdate.no-ip.com",
"https://api.ipify.org", "https://api.ipify.org",
"https://diagnostic.opendns.com/myip",
"https://domains.google.com/checkip", "https://domains.google.com/checkip",
"https://ifconfig.io/ip", "https://ifconfig.io/ip",
"https://ipinfo.io/ip", "https://ipinfo.io/ip",

View File

@@ -8,7 +8,7 @@ import (
) )
//go:embed servers.json //go:embed servers.json
var allServersEmbedFS embed.FS //nolint:gochecknoglobals var allServersEmbedFS embed.FS
func parseHardcodedServers() (allServers models.AllServers, err error) { func parseHardcodedServers() (allServers models.AllServers, err error) {
f, err := allServersEmbedFS.Open("servers.json") f, err := allServersEmbedFS.Open("servers.json")

View File

@@ -88,6 +88,11 @@ func Test_versions(t *testing.T) {
version: allServers.Nordvpn.Version, version: allServers.Nordvpn.Version,
digest: "a8043704", digest: "a8043704",
}, },
"Perfect privacy": {
model: models.PerfectprivacyServer{},
version: allServers.Perfectprivacy.Version,
digest: "233f0dd4",
},
"Privado": { "Privado": {
model: models.PrivadoServer{}, model: models.PrivadoServer{},
version: allServers.Privado.Version, version: allServers.Privado.Version,
@@ -106,7 +111,7 @@ func Test_versions(t *testing.T) {
"Protonvpn": { "Protonvpn": {
model: models.ProtonvpnServer{}, model: models.ProtonvpnServer{},
version: allServers.Protonvpn.Version, version: allServers.Protonvpn.Version,
digest: "b964085b", digest: "4cb74c3a",
}, },
"Purevpn": { "Purevpn": {
model: models.PurevpnServer{}, 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" "github.com/qdm12/gluetun/internal/models"
) )
func (s *Storage) logVersionDiff(provider string, diff int) { func (s *Storage) logVersionDiff(provider string, hardcodedVersion, persistedVersion uint16) {
diffString := strconv.Itoa(diff) message := provider + " servers from file discarded because they have version " +
strconv.Itoa(int(persistedVersion)) +
message := provider + " servers from file discarded because they are " + " and hardcoded servers have version " +
diffString + " version" strconv.Itoa(int(hardcodedVersion))
if diff > 1 {
message += "s"
}
message += " behind"
s.logger.Info(message) s.logger.Info(message)
} }
@@ -41,6 +37,7 @@ func (s *Storage) mergeServers(hardcoded, persisted models.AllServers) models.Al
Ivpn: s.mergeIvpn(hardcoded.Ivpn, persisted.Ivpn), Ivpn: s.mergeIvpn(hardcoded.Ivpn, persisted.Ivpn),
Mullvad: s.mergeMullvad(hardcoded.Mullvad, persisted.Mullvad), Mullvad: s.mergeMullvad(hardcoded.Mullvad, persisted.Mullvad),
Nordvpn: s.mergeNordVPN(hardcoded.Nordvpn, persisted.Nordvpn), Nordvpn: s.mergeNordVPN(hardcoded.Nordvpn, persisted.Nordvpn),
Perfectprivacy: s.mergePerfectprivacy(hardcoded.Perfectprivacy, persisted.Perfectprivacy),
Privado: s.mergePrivado(hardcoded.Privado, persisted.Privado), Privado: s.mergePrivado(hardcoded.Privado, persisted.Privado),
Pia: s.mergePIA(hardcoded.Pia, persisted.Pia), Pia: s.mergePIA(hardcoded.Pia, persisted.Pia),
Privatevpn: s.mergePrivatevpn(hardcoded.Privatevpn, persisted.Privatevpn), Privatevpn: s.mergePrivatevpn(hardcoded.Privatevpn, persisted.Privatevpn),
@@ -60,12 +57,6 @@ func (s *Storage) mergeCyberghost(hardcoded, persisted models.CyberghostServers)
return hardcoded 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) s.logTimeDiff("Cyberghost", persisted.Timestamp, hardcoded.Timestamp)
return persisted return persisted
} }
@@ -74,11 +65,7 @@ func (s *Storage) mergeExpressvpn(hardcoded, persisted models.ExpressvpnServers)
if persisted.Timestamp <= hardcoded.Timestamp { if persisted.Timestamp <= hardcoded.Timestamp {
return hardcoded 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) s.logTimeDiff("ExpressVPN", persisted.Timestamp, hardcoded.Timestamp)
return persisted return persisted
} }
@@ -87,11 +74,7 @@ func (s *Storage) mergeFastestvpn(hardcoded, persisted models.FastestvpnServers)
if persisted.Timestamp <= hardcoded.Timestamp { if persisted.Timestamp <= hardcoded.Timestamp {
return hardcoded 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) s.logTimeDiff("FastestVPN", persisted.Timestamp, hardcoded.Timestamp)
return persisted return persisted
} }
@@ -100,11 +83,7 @@ func (s *Storage) mergeHideMyAss(hardcoded, persisted models.HideMyAssServers) m
if persisted.Timestamp <= hardcoded.Timestamp { if persisted.Timestamp <= hardcoded.Timestamp {
return hardcoded 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) s.logTimeDiff("HideMyAss", persisted.Timestamp, hardcoded.Timestamp)
return persisted return persisted
} }
@@ -113,11 +92,7 @@ func (s *Storage) mergeIpvanish(hardcoded, persisted models.IpvanishServers) mod
if persisted.Timestamp <= hardcoded.Timestamp { if persisted.Timestamp <= hardcoded.Timestamp {
return hardcoded 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) s.logTimeDiff("Ipvanish", persisted.Timestamp, hardcoded.Timestamp)
return persisted return persisted
} }
@@ -126,11 +101,7 @@ func (s *Storage) mergeIvpn(hardcoded, persisted models.IvpnServers) models.Ivpn
if persisted.Timestamp <= hardcoded.Timestamp { if persisted.Timestamp <= hardcoded.Timestamp {
return hardcoded 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) s.logTimeDiff("Ivpn", persisted.Timestamp, hardcoded.Timestamp)
return persisted return persisted
} }
@@ -140,12 +111,6 @@ func (s *Storage) mergeMullvad(hardcoded, persisted models.MullvadServers) model
return hardcoded 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) s.logTimeDiff("Mullvad", persisted.Timestamp, hardcoded.Timestamp)
return persisted return persisted
} }
@@ -155,13 +120,16 @@ func (s *Storage) mergeNordVPN(hardcoded, persisted models.NordvpnServers) model
return hardcoded return hardcoded
} }
versionDiff := int(hardcoded.Version) - int(persisted.Version) s.logTimeDiff("NordVPN", persisted.Timestamp, hardcoded.Timestamp)
if versionDiff > 0 { return persisted
s.logVersionDiff("NordVPN", versionDiff) }
func (s *Storage) mergePerfectprivacy(hardcoded, persisted models.PerfectprivacyServers) models.PerfectprivacyServers {
if persisted.Timestamp <= hardcoded.Timestamp {
return hardcoded return hardcoded
} }
s.logTimeDiff("NordVPN", persisted.Timestamp, hardcoded.Timestamp) s.logTimeDiff("Perfect Privacy", persisted.Timestamp, hardcoded.Timestamp)
return persisted return persisted
} }
@@ -169,11 +137,6 @@ func (s *Storage) mergePrivado(hardcoded, persisted models.PrivadoServers) model
if persisted.Timestamp <= hardcoded.Timestamp { if persisted.Timestamp <= hardcoded.Timestamp {
return hardcoded 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) s.logTimeDiff("Privado", persisted.Timestamp, hardcoded.Timestamp)
return persisted return persisted
@@ -183,11 +146,6 @@ func (s *Storage) mergePIA(hardcoded, persisted models.PiaServers) models.PiaSer
if persisted.Timestamp <= hardcoded.Timestamp { if persisted.Timestamp <= hardcoded.Timestamp {
return hardcoded 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) s.logTimeDiff("Private Internet Access", persisted.Timestamp, hardcoded.Timestamp)
return persisted return persisted
@@ -197,11 +155,6 @@ func (s *Storage) mergePrivatevpn(hardcoded, persisted models.PrivatevpnServers)
if persisted.Timestamp <= hardcoded.Timestamp { if persisted.Timestamp <= hardcoded.Timestamp {
return hardcoded 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) s.logTimeDiff("PrivateVPN", persisted.Timestamp, hardcoded.Timestamp)
return persisted return persisted
@@ -211,11 +164,6 @@ func (s *Storage) mergeProtonvpn(hardcoded, persisted models.ProtonvpnServers) m
if persisted.Timestamp <= hardcoded.Timestamp { if persisted.Timestamp <= hardcoded.Timestamp {
return hardcoded 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) s.logTimeDiff("ProtonVPN", persisted.Timestamp, hardcoded.Timestamp)
return persisted return persisted
@@ -226,12 +174,6 @@ func (s *Storage) mergePureVPN(hardcoded, persisted models.PurevpnServers) model
return hardcoded 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) s.logTimeDiff("PureVPN", persisted.Timestamp, hardcoded.Timestamp)
return persisted return persisted
} }
@@ -241,12 +183,6 @@ func (s *Storage) mergeSurfshark(hardcoded, persisted models.SurfsharkServers) m
return hardcoded 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) s.logTimeDiff("Surfshark", persisted.Timestamp, hardcoded.Timestamp)
return persisted return persisted
} }
@@ -255,11 +191,6 @@ func (s *Storage) mergeTorguard(hardcoded, persisted models.TorguardServers) mod
if persisted.Timestamp <= hardcoded.Timestamp { if persisted.Timestamp <= hardcoded.Timestamp {
return hardcoded 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) s.logTimeDiff("Torguard", persisted.Timestamp, hardcoded.Timestamp)
return persisted return persisted
@@ -269,11 +200,6 @@ func (s *Storage) mergeVPNUnlimited(hardcoded, persisted models.VPNUnlimitedServ
if persisted.Timestamp <= hardcoded.Timestamp { if persisted.Timestamp <= hardcoded.Timestamp {
return hardcoded 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) s.logTimeDiff("VPN Unlimited", persisted.Timestamp, hardcoded.Timestamp)
return persisted return persisted
@@ -284,12 +210,6 @@ func (s *Storage) mergeVyprvpn(hardcoded, persisted models.VyprvpnServers) model
return hardcoded 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) s.logTimeDiff("VyprVPN", persisted.Timestamp, hardcoded.Timestamp)
return persisted return persisted
} }
@@ -299,12 +219,6 @@ func (s *Storage) mergeWevpn(hardcoded, persisted models.WevpnServers) models.We
return hardcoded 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) s.logTimeDiff("WeVPN", persisted.Timestamp, hardcoded.Timestamp)
return persisted return persisted
} }
@@ -314,12 +228,6 @@ func (s *Storage) mergeWindscribe(hardcoded, persisted models.WindscribeServers)
return hardcoded 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) s.logTimeDiff("Windscribe", persisted.Timestamp, hardcoded.Timestamp)
return persisted return persisted
} }

View File

@@ -3,26 +3,291 @@ package storage
import ( import (
"encoding/json" "encoding/json"
"errors" "errors"
"fmt"
"io" "io"
"os" "os"
"github.com/qdm12/gluetun/internal/models" "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) file, err := os.Open(filepath)
if os.IsNotExist(err) { if os.IsNotExist(err) {
return servers, nil return servers, nil
} else if err != nil { } else if err != nil {
return servers, err return servers, err
} }
decoder := json.NewDecoder(file)
if err := decoder.Decode(&servers); err != nil { b, err := io.ReadAll(file)
_ = file.Close() if err != nil {
if errors.Is(err, io.EOF) {
return servers, nil
}
return servers, err 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" "github.com/qdm12/gluetun/internal/models"
) )
//go:generate mockgen -destination=infoerrorer_mock_test.go -package $GOPACKAGE . InfoErrorer
type Storage struct { type Storage struct {
mergedServers models.AllServers mergedServers models.AllServers
hardcodedServers models.AllServers hardcodedServers models.AllServers

View File

@@ -22,6 +22,7 @@ func countServers(allServers models.AllServers) int {
len(allServers.Ivpn.Servers) + len(allServers.Ivpn.Servers) +
len(allServers.Mullvad.Servers) + len(allServers.Mullvad.Servers) +
len(allServers.Nordvpn.Servers) + len(allServers.Nordvpn.Servers) +
len(allServers.Perfectprivacy.Servers) +
len(allServers.Privado.Servers) + len(allServers.Privado.Servers) +
len(allServers.Pia.Servers) + len(allServers.Pia.Servers) +
len(allServers.Privatevpn.Servers) + len(allServers.Privatevpn.Servers) +
@@ -36,7 +37,7 @@ func countServers(allServers models.AllServers) int {
} }
func (s *Storage) SyncServers() (err error) { func (s *Storage) SyncServers() (err error) {
serversOnFile, err := readFromFile(s.filepath) serversOnFile, err := s.readFromFile(s.filepath, s.hardcodedServers)
if err != nil { if err != nil {
return fmt.Errorf("%w: %s", ErrCannotReadFile, err) return fmt.Errorf("%w: %s", ErrCannotReadFile, err)
} }

View File

@@ -4,6 +4,7 @@ import (
"errors" "errors"
"fmt" "fmt"
"net" "net"
"sort"
"strings" "strings"
) )
@@ -53,20 +54,38 @@ func ExtractHost(b []byte) (host, warning string, err error) {
return hosts[0], warning, nil 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) { func ExtractIP(b []byte) (ip net.IP, warning string, err error) {
const ( ips, err := ExtractIPs(b)
rejectIP = false if err != nil {
rejectDomain = true return nil, "", err
) }
ips := extractRemoteHosts(b, rejectIP, rejectDomain)
if len(ips) == 0 { if len(ips) > 1 {
return nil, "", ErrNoRemoteIP
} else if len(ips) > 1 {
warning = fmt.Sprintf( warning = fmt.Sprintf(
"only using the first IP address %s and discarding %d other hosts", "only using the first IP address %s and discarding %d other hosts",
ips[0], len(ips)-1) 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) { 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/ivpn"
"github.com/qdm12/gluetun/internal/updater/providers/mullvad" "github.com/qdm12/gluetun/internal/updater/providers/mullvad"
"github.com/qdm12/gluetun/internal/updater/providers/nordvpn" "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/pia"
"github.com/qdm12/gluetun/internal/updater/providers/privado" "github.com/qdm12/gluetun/internal/updater/providers/privado"
"github.com/qdm12/gluetun/internal/updater/providers/privatevpn" "github.com/qdm12/gluetun/internal/updater/providers/privatevpn"
@@ -190,6 +191,27 @@ func (u *updater) updateNordvpn(ctx context.Context) (err error) {
return nil 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) { func (u *updater) updatePIA(ctx context.Context) (err error) {
minServers := getMinServers(len(u.servers.Pia.Servers)) minServers := getMinServers(len(u.servers.Pia.Servers))
servers, err := pia.GetServers(ctx, u.client, minServers) 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) ErrNotEnoughServers, count, minServers)
} }
servers = make([]models.ProtonvpnServer, 0, count) ipToServer := make(ipToServer, count)
for _, logicalServer := range data.LogicalServers { for _, logicalServer := range data.LogicalServers {
region := getStringValue(logicalServer.Region)
city := getStringValue(logicalServer.City)
name := logicalServer.Name
for _, physicalServer := range logicalServer.Servers { for _, physicalServer := range logicalServer.Servers {
server, warning, err := makeServer( if physicalServer.Status == 0 { // disabled so skip server
physicalServer, logicalServer, countryCodes) 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 != "" { if warning != "" {
warnings = append(warnings, warning) warnings = append(warnings, warning)
} }
if err != nil { ipToServer.add(country, region, city, name, hostname, entryIP, exitIP)
warnings = append(warnings, err.Error())
continue
}
servers = append(servers, server)
} }
} }
if len(servers) < minServers { if len(ipToServer) < minServers {
return nil, warnings, fmt.Errorf("%w: %d and expected at least %d", 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) sortServers(servers)
return servers, warnings, nil 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 { func getStringValue(ptr *string) string {
if ptr == nil { if ptr == nil {
return "" return ""

View File

@@ -16,7 +16,8 @@ func zipExtractAll(zipBytes []byte) (contents map[string][]byte, err error) {
contents = map[string][]byte{} contents = map[string][]byte{}
for _, zf := range r.File { for _, zf := range r.File {
fileName := filepath.Base(zf.Name) fileName := filepath.Base(zf.Name)
if !strings.HasSuffix(fileName, ".ovpn") { if !strings.HasSuffix(fileName, ".ovpn") &&
!strings.HasSuffix(fileName, ".conf") {
continue continue
} }
f, err := zf.Open() f, err := zf.Open()

View File

@@ -14,8 +14,6 @@ var (
func (u *unzipper) FetchAndExtract(ctx context.Context, url string) ( func (u *unzipper) FetchAndExtract(ctx context.Context, url string) (
contents map[string][]byte, err error) { contents map[string][]byte, err error) {
contents = make(map[string][]byte)
request, err := http.NewRequestWithContext(ctx, http.MethodGet, url, nil) request, err := http.NewRequestWithContext(ctx, http.MethodGet, url, nil)
if err != nil { if err != nil {
return nil, err return nil, err
@@ -40,13 +38,5 @@ func (u *unzipper) FetchAndExtract(ctx context.Context, url string) (
return nil, err return nil, err
} }
newContents, err := zipExtractAll(b) return zipExtractAll(b)
if err != nil {
return nil, err
}
for fileName, content := range newContents {
contents[fileName] = content
}
return contents, nil
} }

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 { if u.options.Privado {
u.logger.Info("updating Privado servers...") u.logger.Info("updating Privado servers...")
if err := u.updatePrivado(ctx); err != nil { if err := u.updatePrivado(ctx); err != nil {

View File

@@ -3,6 +3,8 @@ package version
import ( import (
"context" "context"
"encoding/json" "encoding/json"
"errors"
"fmt"
"net/http" "net/http"
"time" "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) { func getGithubReleases(ctx context.Context, client *http.Client) (releases []githubRelease, err error) {
const url = "https://api.github.com/repos/qdm12/gluetun/releases" const url = "https://api.github.com/repos/qdm12/gluetun/releases"
request, err := http.NewRequestWithContext(ctx, http.MethodGet, url, nil) request, err := http.NewRequestWithContext(ctx, http.MethodGet, url, nil)
if err != nil { if err != nil {
return nil, err return nil, err
} }
response, err := client.Do(request) response, err := client.Do(request)
if err != nil { if err != nil {
return nil, err return nil, err
} }
defer response.Body.Close() defer response.Body.Close()
if response.StatusCode != http.StatusOK {
return nil, fmt.Errorf("%w: %s", errHTTPStatusCode, response.Status)
}
decoder := json.NewDecoder(response.Body) decoder := json.NewDecoder(response.Body)
if err := decoder.Decode(&releases); err != nil { if err := decoder.Decode(&releases); err != nil {
return nil, err return nil, err

View File

@@ -5,8 +5,6 @@
- Remove duplicate `/gluetun` directory creation - Remove duplicate `/gluetun` directory creation
- Remove firewall shadowsocks input port? - Remove firewall shadowsocks input port?
- Remove `script-security` option - Remove `script-security` option
- `ncp-ciphers` to `data-ciphers`
- Remove `ncp-disable`
## Uniformization ## Uniformization
@@ -40,6 +38,7 @@
- `WIREGUARD_ADDRESS` to `WIREGUARD_ADDRESSES` - `WIREGUARD_ADDRESS` to `WIREGUARD_ADDRESSES`
- `VPNSP` to `VPN_SERVICE_PROVIDER` - `VPNSP` to `VPN_SERVICE_PROVIDER`
- Rename `REGION` (etc.) to `SERVER_REGIONS` - Rename `REGION` (etc.) to `SERVER_REGIONS`
- `OPENVPN_CIPHER` to `OPENVPN_CIPHERS`
- Split servers.json and compress it - Split servers.json and compress it
## Gluetun V4 ## Gluetun V4