Compare commits
103 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c826707d42 | ||
|
|
8a17cd87c3 | ||
|
|
f8da1e79bc | ||
|
|
cfc29d6a6b | ||
|
|
5467652b8b | ||
|
|
daa63c276d | ||
|
|
ab96acdc5b | ||
|
|
6e108706a1 | ||
|
|
4a6c229504 | ||
|
|
ed3a72790a | ||
|
|
4bf5777f23 | ||
|
|
f0f9bdb883 | ||
|
|
4984d90b5a | ||
|
|
b5e648d13a | ||
|
|
f71a1b083b | ||
|
|
75fd869625 | ||
|
|
657b4b787f | ||
|
|
32d6453918 | ||
|
|
c326b616b4 | ||
|
|
d5376629df | ||
|
|
3e825d7a08 | ||
|
|
059b12883f | ||
|
|
74aa509644 | ||
|
|
4105f74ce1 | ||
|
|
8318be3159 | ||
|
|
de196490db | ||
|
|
ab7d1ccf3d | ||
|
|
ed49a7a7c0 | ||
|
|
135832d985 | ||
|
|
1adbd9f692 | ||
|
|
26e1c92841 | ||
|
|
3c5b3514fb | ||
|
|
f884293f6e | ||
|
|
c67bd1aa2a | ||
|
|
77ace9377d | ||
|
|
6e676209ff | ||
|
|
80917d58b2 | ||
|
|
fa49f13f19 | ||
|
|
1fcabd152f | ||
|
|
385879c297 | ||
|
|
e0515cb458 | ||
|
|
1c43a1d55b | ||
|
|
6c639fcf7f | ||
|
|
ec1f252528 | ||
|
|
ee413f59a2 | ||
|
|
d4df87286e | ||
|
|
a194906bdd | ||
|
|
9b00763a69 | ||
|
|
4d627bb7b1 | ||
|
|
dc8fc5f81f | ||
|
|
b787e12e25 | ||
|
|
f96448947f | ||
|
|
e64e5af4c3 | ||
|
|
aa6dc786a4 | ||
|
|
84300db7c1 | ||
|
|
2ac0f35060 | ||
|
|
1a865f56d5 | ||
|
|
0406de399d | ||
|
|
71201411f4 | ||
|
|
c435bbb32c | ||
|
|
4cbfea41f2 | ||
|
|
f9c9ad34f7 | ||
|
|
4ea474b896 | ||
|
|
6aa4a93665 | ||
|
|
ea25a0ff89 | ||
|
|
659da67ed5 | ||
|
|
ffc6d2e593 | ||
|
|
03ce08e23d | ||
|
|
3449e7a0e1 | ||
|
|
c0062fb807 | ||
|
|
1ac031e78c | ||
|
|
e556871e8b | ||
|
|
082a38b769 | ||
|
|
39ae57f49d | ||
|
|
9024912e17 | ||
|
|
eecfb3952f | ||
|
|
0ebfe534d3 | ||
|
|
c5cc240a6c | ||
|
|
1a5a0148ea | ||
|
|
abe2aceb18 | ||
|
|
fa541b8fc2 | ||
|
|
a681d38dfb | ||
|
|
a7b96e3f4d | ||
|
|
04ef92edab | ||
|
|
919b55c3aa | ||
|
|
9c0f187a12 | ||
|
|
075a1e2a80 | ||
|
|
f31a846cda | ||
|
|
9bef46db77 | ||
|
|
d83217f7ac | ||
|
|
1cd2fec796 | ||
|
|
235f24ee5b | ||
|
|
2e34c6009e | ||
|
|
c0eb2f2315 | ||
|
|
8ad16cdc12 | ||
|
|
fae6544431 | ||
|
|
f8a41b2133 | ||
|
|
ff9b56d6d8 | ||
|
|
99d5a591b9 | ||
|
|
fbe252a9b6 | ||
|
|
76a92b90e3 | ||
|
|
2873b06275 | ||
|
|
9cdd6294d2 |
@@ -47,7 +47,7 @@ You can customize **settings** and **extensions** in the [devcontainer.json](dev
|
|||||||
|
|
||||||
### Entrypoint script
|
### Entrypoint script
|
||||||
|
|
||||||
You can bind mount a shell script to `/root/.welcome.sh` to replace the [current welcome script](shell/.welcome.sh).
|
You can bind mount a shell script to `/root/.welcome.sh` to replace the [current welcome script](https://github.com/qdm12/godevcontainer/blob/master/shell/.welcome.sh).
|
||||||
|
|
||||||
### Publish a port
|
### Publish a port
|
||||||
|
|
||||||
|
|||||||
2
.github/CONTRIBUTING.md
vendored
2
.github/CONTRIBUTING.md
vendored
@@ -13,6 +13,6 @@ Contributions are [released](https://help.github.com/articles/github-terms-of-se
|
|||||||
|
|
||||||
## Resources
|
## Resources
|
||||||
|
|
||||||
- [Gluetun guide on development](https://github.com/qdm12/gluetun/wiki/Development)
|
- [Gluetun guide on development](https://github.com/qdm12/gluetun-wiki/blob/main/contributing/development.md)
|
||||||
- [Using Pull Requests](https://help.github.com/articles/about-pull-requests/)
|
- [Using Pull Requests](https://help.github.com/articles/about-pull-requests/)
|
||||||
- [How to Contribute to Open Source](https://opensource.guide/how-to-contribute/)
|
- [How to Contribute to Open Source](https://opensource.guide/how-to-contribute/)
|
||||||
|
|||||||
12
.github/ISSUE_TEMPLATE/bug.yml
vendored
12
.github/ISSUE_TEMPLATE/bug.yml
vendored
@@ -7,13 +7,18 @@ body:
|
|||||||
attributes:
|
attributes:
|
||||||
value: |
|
value: |
|
||||||
Thanks for taking the time to fill out this bug report!
|
Thanks for taking the time to fill out this bug report!
|
||||||
|
|
||||||
|
⚠️ Your issue will be instantly closed as not planned WITHOUT explanation if:
|
||||||
|
- you do not fill out **the title of the issue** ☝️
|
||||||
|
- you do not provide the **Gluetun version** as requested below
|
||||||
|
- you provide **less than 10 lines of logs** as requested below
|
||||||
- type: dropdown
|
- type: dropdown
|
||||||
id: urgent
|
id: urgent
|
||||||
attributes:
|
attributes:
|
||||||
label: Is this urgent?
|
label: Is this urgent?
|
||||||
description: |
|
description: |
|
||||||
Is this a critical bug, or do you need this fixed urgently?
|
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.
|
If this is urgent, note you can use one of the [image tags available](https://github.com/qdm12/gluetun-wiki/blob/main/setup/docker-image-tags.md) if that can help.
|
||||||
options:
|
options:
|
||||||
- "No"
|
- "No"
|
||||||
- "Yes"
|
- "Yes"
|
||||||
@@ -75,6 +80,7 @@ body:
|
|||||||
- Portainer
|
- Portainer
|
||||||
- Kubernetes
|
- Kubernetes
|
||||||
- Podman
|
- Podman
|
||||||
|
- Unraid
|
||||||
- Other
|
- Other
|
||||||
validations:
|
validations:
|
||||||
required: true
|
required: true
|
||||||
@@ -84,7 +90,7 @@ body:
|
|||||||
label: What is the version of Gluetun
|
label: What is the version of Gluetun
|
||||||
description: |
|
description: |
|
||||||
Copy paste the version line at the top of your logs.
|
Copy paste the version line at the top of your logs.
|
||||||
It should be in the form `Running version latest built on 2020-03-13T01:30:06Z (commit d0f678c)`.
|
It MUST be in the form `Running version latest built on 2020-03-13T01:30:06Z (commit d0f678c)`.
|
||||||
validations:
|
validations:
|
||||||
required: true
|
required: true
|
||||||
- type: textarea
|
- type: textarea
|
||||||
@@ -97,7 +103,7 @@ body:
|
|||||||
- type: textarea
|
- type: textarea
|
||||||
id: logs
|
id: logs
|
||||||
attributes:
|
attributes:
|
||||||
label: Share your logs
|
label: Share your logs (at least 10 lines)
|
||||||
description: No sensitive information is logged out except when running with `LOG_LEVEL=debug`.
|
description: No sensitive information is logged out except when running with `LOG_LEVEL=debug`.
|
||||||
render: plain text
|
render: plain text
|
||||||
validations:
|
validations:
|
||||||
|
|||||||
3
.github/ISSUE_TEMPLATE/config.yml
vendored
3
.github/ISSUE_TEMPLATE/config.yml
vendored
@@ -1,4 +1,7 @@
|
|||||||
contact_links:
|
contact_links:
|
||||||
|
- name: Report a Wiki issue
|
||||||
|
url: https://github.com/qdm12/gluetun-wiki/issues/new
|
||||||
|
about: Please create an issue on the gluetun-wiki repository.
|
||||||
- name: Configuration help?
|
- name: Configuration help?
|
||||||
url: https://github.com/qdm12/gluetun/discussions/new
|
url: https://github.com/qdm12/gluetun/discussions/new
|
||||||
about: Please create a Github discussion.
|
about: Please create a Github discussion.
|
||||||
|
|||||||
2
.github/ISSUE_TEMPLATE/provider.md
vendored
2
.github/ISSUE_TEMPLATE/provider.md
vendored
@@ -14,4 +14,4 @@ One of the following is required:
|
|||||||
|
|
||||||
If the list of servers requires to login **or** is hidden behind an interactive configurator,
|
If the list of servers requires to login **or** is hidden behind an interactive configurator,
|
||||||
you can only use a custom Openvpn configuration file.
|
you can only use a custom Openvpn configuration file.
|
||||||
[The Wiki](https://github.com/qdm12/gluetun/wiki/Openvpn-file) describes how to do so.
|
[The Wiki's OpenVPN configuration file page](https://github.com/qdm12/gluetun-wiki/blob/main/setup/openvpn-configuration-file.md) describes how to do so.
|
||||||
|
|||||||
18
.github/ISSUE_TEMPLATE/wiki issue.yml
vendored
18
.github/ISSUE_TEMPLATE/wiki issue.yml
vendored
@@ -1,18 +0,0 @@
|
|||||||
name: Wiki issue
|
|
||||||
description: Report a Wiki issue
|
|
||||||
title: "Wiki issue: "
|
|
||||||
labels: ["📄 Wiki issue"]
|
|
||||||
body:
|
|
||||||
- type: input
|
|
||||||
id: url
|
|
||||||
attributes:
|
|
||||||
label: "URL to the Wiki page"
|
|
||||||
placeholder: "https://github.com/qdm12/gluetun/wiki/OpenVPN-options"
|
|
||||||
validations:
|
|
||||||
required: true
|
|
||||||
- type: textarea
|
|
||||||
id: description
|
|
||||||
attributes:
|
|
||||||
label: "What's the issue?"
|
|
||||||
validations:
|
|
||||||
required: true
|
|
||||||
25
.github/workflows/ci.yml
vendored
25
.github/workflows/ci.yml
vendored
@@ -37,7 +37,7 @@ jobs:
|
|||||||
env:
|
env:
|
||||||
DOCKER_BUILDKIT: "1"
|
DOCKER_BUILDKIT: "1"
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v3
|
- uses: actions/checkout@v4
|
||||||
|
|
||||||
- uses: reviewdog/action-misspell@v1
|
- uses: reviewdog/action-misspell@v1
|
||||||
with:
|
with:
|
||||||
@@ -45,6 +45,7 @@ jobs:
|
|||||||
level: error
|
level: error
|
||||||
exclude: |
|
exclude: |
|
||||||
./internal/storage/servers.json
|
./internal/storage/servers.json
|
||||||
|
*.md
|
||||||
|
|
||||||
- name: Linting
|
- name: Linting
|
||||||
run: docker build --target lint .
|
run: docker build --target lint .
|
||||||
@@ -72,12 +73,12 @@ jobs:
|
|||||||
contents: read
|
contents: read
|
||||||
security-events: write
|
security-events: write
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v3
|
- uses: actions/checkout@v4
|
||||||
- uses: github/codeql-action/init@v2
|
- uses: github/codeql-action/init@v3
|
||||||
with:
|
with:
|
||||||
languages: go
|
languages: go
|
||||||
- uses: github/codeql-action/autobuild@v2
|
- uses: github/codeql-action/autobuild@v3
|
||||||
- uses: github/codeql-action/analyze@v2
|
- uses: github/codeql-action/analyze@v3
|
||||||
|
|
||||||
publish:
|
publish:
|
||||||
if: |
|
if: |
|
||||||
@@ -94,13 +95,13 @@ jobs:
|
|||||||
packages: write
|
packages: write
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v3
|
- uses: actions/checkout@v4
|
||||||
|
|
||||||
# extract metadata (tags, labels) for Docker
|
# extract metadata (tags, labels) for Docker
|
||||||
# https://github.com/docker/metadata-action
|
# https://github.com/docker/metadata-action
|
||||||
- name: Extract Docker metadata
|
- name: Extract Docker metadata
|
||||||
id: meta
|
id: meta
|
||||||
uses: docker/metadata-action@v4
|
uses: docker/metadata-action@v5
|
||||||
with:
|
with:
|
||||||
flavor: |
|
flavor: |
|
||||||
latest=${{ github.ref == format('refs/heads/{0}', github.event.repository.default_branch) }}
|
latest=${{ github.ref == format('refs/heads/{0}', github.event.repository.default_branch) }}
|
||||||
@@ -115,15 +116,15 @@ jobs:
|
|||||||
type=semver,pattern=v{{major}},enable=${{ !startsWith(github.ref, 'refs/tags/v0.') }}
|
type=semver,pattern=v{{major}},enable=${{ !startsWith(github.ref, 'refs/tags/v0.') }}
|
||||||
type=raw,value=latest,enable=${{ github.ref == format('refs/heads/{0}', github.event.repository.default_branch) }}
|
type=raw,value=latest,enable=${{ github.ref == format('refs/heads/{0}', github.event.repository.default_branch) }}
|
||||||
|
|
||||||
- uses: docker/setup-qemu-action@v2
|
- uses: docker/setup-qemu-action@v3
|
||||||
- uses: docker/setup-buildx-action@v2
|
- uses: docker/setup-buildx-action@v3
|
||||||
|
|
||||||
- uses: docker/login-action@v2
|
- uses: docker/login-action@v3
|
||||||
with:
|
with:
|
||||||
username: qmcgaw
|
username: qmcgaw
|
||||||
password: ${{ secrets.DOCKERHUB_PASSWORD }}
|
password: ${{ secrets.DOCKERHUB_PASSWORD }}
|
||||||
|
|
||||||
- uses: docker/login-action@v2
|
- uses: docker/login-action@v3
|
||||||
with:
|
with:
|
||||||
registry: ghcr.io
|
registry: ghcr.io
|
||||||
username: qdm12
|
username: qdm12
|
||||||
@@ -134,7 +135,7 @@ jobs:
|
|||||||
run: echo "::set-output name=value::$(git rev-parse --short HEAD)"
|
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@v4.1.1
|
uses: docker/build-push-action@v5.1.0
|
||||||
with:
|
with:
|
||||||
platforms: linux/amd64,linux/386,linux/arm64,linux/arm/v6,linux/arm/v7,linux/ppc64le
|
platforms: linux/amd64,linux/386,linux/arm64,linux/arm/v6,linux/arm/v7,linux/ppc64le
|
||||||
labels: ${{ steps.meta.outputs.labels }}
|
labels: ${{ steps.meta.outputs.labels }}
|
||||||
|
|||||||
25
.github/workflows/dockerhub-description.yml
vendored
25
.github/workflows/dockerhub-description.yml
vendored
@@ -1,25 +0,0 @@
|
|||||||
name: Docker Hub description
|
|
||||||
on:
|
|
||||||
push:
|
|
||||||
branches:
|
|
||||||
- master
|
|
||||||
paths:
|
|
||||||
- README.md
|
|
||||||
- .github/workflows/dockerhub-description.yml
|
|
||||||
jobs:
|
|
||||||
docker-hub-description:
|
|
||||||
if: github.repository == 'qdm12/gluetun'
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
permissions:
|
|
||||||
actions: read
|
|
||||||
contents: read
|
|
||||||
steps:
|
|
||||||
- uses: actions/checkout@v3
|
|
||||||
|
|
||||||
- uses: peter-evans/dockerhub-description@v3
|
|
||||||
with:
|
|
||||||
username: qmcgaw
|
|
||||||
password: ${{ secrets.DOCKERHUB_PASSWORD }}
|
|
||||||
repository: qmcgaw/gluetun
|
|
||||||
short-description: Lightweight Swiss-knife VPN client to connect to several VPN providers
|
|
||||||
readme-filepath: README.md
|
|
||||||
4
.github/workflows/labels.yml
vendored
4
.github/workflows/labels.yml
vendored
@@ -11,7 +11,7 @@ jobs:
|
|||||||
issues: write
|
issues: write
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v3
|
- uses: actions/checkout@v4
|
||||||
- uses: crazy-max/ghaction-github-labeler@v4
|
- uses: crazy-max/ghaction-github-labeler@v5
|
||||||
with:
|
with:
|
||||||
yaml-file: .github/labels.yml
|
yaml-file: .github/labels.yml
|
||||||
|
|||||||
21
.github/workflows/markdown-skip.yml
vendored
Normal file
21
.github/workflows/markdown-skip.yml
vendored
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
name: Markdown
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches:
|
||||||
|
- master
|
||||||
|
paths-ignore:
|
||||||
|
- "**.md"
|
||||||
|
- .github/workflows/markdown.yml
|
||||||
|
pull_request:
|
||||||
|
paths-ignore:
|
||||||
|
- "**.md"
|
||||||
|
- .github/workflows/markdown.yml
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
markdown:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
permissions:
|
||||||
|
actions: read
|
||||||
|
steps:
|
||||||
|
- name: No trigger path triggered for required markdown workflow.
|
||||||
|
run: exit 0
|
||||||
46
.github/workflows/markdown.yml
vendored
Normal file
46
.github/workflows/markdown.yml
vendored
Normal file
@@ -0,0 +1,46 @@
|
|||||||
|
name: Markdown
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches:
|
||||||
|
- master
|
||||||
|
paths:
|
||||||
|
- "**.md"
|
||||||
|
- .github/workflows/markdown.yml
|
||||||
|
pull_request:
|
||||||
|
paths:
|
||||||
|
- "**.md"
|
||||||
|
- .github/workflows/markdown.yml
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
markdown:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
permissions:
|
||||||
|
actions: read
|
||||||
|
contents: read
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
|
||||||
|
- uses: DavidAnson/markdownlint-cli2-action@v14
|
||||||
|
with:
|
||||||
|
globs: "**.md"
|
||||||
|
config: .markdownlint.json
|
||||||
|
|
||||||
|
- uses: reviewdog/action-misspell@v1
|
||||||
|
with:
|
||||||
|
locale: "US"
|
||||||
|
level: error
|
||||||
|
pattern: |
|
||||||
|
*.md
|
||||||
|
|
||||||
|
- uses: gaurav-nelson/github-action-markdown-link-check@v1
|
||||||
|
with:
|
||||||
|
use-quiet-mode: yes
|
||||||
|
|
||||||
|
- uses: peter-evans/dockerhub-description@v3
|
||||||
|
if: github.repository == 'qdm12/gluetun' && github.event_name == 'push'
|
||||||
|
with:
|
||||||
|
username: qmcgaw
|
||||||
|
password: ${{ secrets.DOCKERHUB_PASSWORD }}
|
||||||
|
repository: qmcgaw/gluetun
|
||||||
|
short-description: Lightweight Swiss-knife VPN client to connect to several VPN providers
|
||||||
|
readme-filepath: README.md
|
||||||
3
.markdownlint.json
Normal file
3
.markdownlint.json
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
{
|
||||||
|
"MD013": false
|
||||||
|
}
|
||||||
11
Dockerfile
11
Dockerfile
@@ -1,8 +1,8 @@
|
|||||||
ARG ALPINE_VERSION=3.18
|
ARG ALPINE_VERSION=3.18
|
||||||
ARG GO_ALPINE_VERSION=3.18
|
ARG GO_ALPINE_VERSION=3.18
|
||||||
ARG GO_VERSION=1.20
|
ARG GO_VERSION=1.21
|
||||||
ARG XCPUTRANSLATE_VERSION=v0.6.0
|
ARG XCPUTRANSLATE_VERSION=v0.6.0
|
||||||
ARG GOLANGCI_LINT_VERSION=v1.53.2
|
ARG GOLANGCI_LINT_VERSION=v1.54.1
|
||||||
ARG MOCKGEN_VERSION=v1.6.0
|
ARG MOCKGEN_VERSION=v1.6.0
|
||||||
ARG BUILDPLATFORM=linux/amd64
|
ARG BUILDPLATFORM=linux/amd64
|
||||||
|
|
||||||
@@ -90,12 +90,13 @@ ENV VPN_SERVICE_PROVIDER=pia \
|
|||||||
OPENVPN_FLAGS= \
|
OPENVPN_FLAGS= \
|
||||||
OPENVPN_CIPHERS= \
|
OPENVPN_CIPHERS= \
|
||||||
OPENVPN_AUTH= \
|
OPENVPN_AUTH= \
|
||||||
OPENVPN_PROCESS_USER= \
|
OPENVPN_PROCESS_USER=root \
|
||||||
OPENVPN_CUSTOM_CONFIG= \
|
OPENVPN_CUSTOM_CONFIG= \
|
||||||
# Wireguard
|
# Wireguard
|
||||||
WIREGUARD_PRIVATE_KEY= \
|
WIREGUARD_PRIVATE_KEY= \
|
||||||
WIREGUARD_PRESHARED_KEY= \
|
WIREGUARD_PRESHARED_KEY= \
|
||||||
WIREGUARD_PUBLIC_KEY= \
|
WIREGUARD_PUBLIC_KEY= \
|
||||||
|
WIREGUARD_ALLOWED_IPS= \
|
||||||
WIREGUARD_ADDRESSES= \
|
WIREGUARD_ADDRESSES= \
|
||||||
WIREGUARD_MTU=1400 \
|
WIREGUARD_MTU=1400 \
|
||||||
WIREGUARD_IMPLEMENTATION=auto \
|
WIREGUARD_IMPLEMENTATION=auto \
|
||||||
@@ -110,6 +111,8 @@ ENV VPN_SERVICE_PROVIDER=pia \
|
|||||||
# # Private Internet Access only:
|
# # Private Internet Access only:
|
||||||
PRIVATE_INTERNET_ACCESS_OPENVPN_ENCRYPTION_PRESET= \
|
PRIVATE_INTERNET_ACCESS_OPENVPN_ENCRYPTION_PRESET= \
|
||||||
VPN_PORT_FORWARDING=off \
|
VPN_PORT_FORWARDING=off \
|
||||||
|
VPN_PORT_FORWARDING_LISTENING_PORT=0 \
|
||||||
|
VPN_PORT_FORWARDING_PROVIDER= \
|
||||||
VPN_PORT_FORWARDING_STATUS_FILE="/tmp/gluetun/forwarded_port" \
|
VPN_PORT_FORWARDING_STATUS_FILE="/tmp/gluetun/forwarded_port" \
|
||||||
# # Cyberghost only:
|
# # Cyberghost only:
|
||||||
OPENVPN_CERT= \
|
OPENVPN_CERT= \
|
||||||
@@ -165,6 +168,7 @@ ENV VPN_SERVICE_PROVIDER=pia \
|
|||||||
HTTPPROXY= \
|
HTTPPROXY= \
|
||||||
HTTPPROXY_LOG=off \
|
HTTPPROXY_LOG=off \
|
||||||
HTTPPROXY_LISTENING_ADDRESS=":8888" \
|
HTTPPROXY_LISTENING_ADDRESS=":8888" \
|
||||||
|
HTTPPROXY_STEALTH=off \
|
||||||
HTTPPROXY_USER= \
|
HTTPPROXY_USER= \
|
||||||
HTTPPROXY_PASSWORD= \
|
HTTPPROXY_PASSWORD= \
|
||||||
HTTPPROXY_USER_SECRETFILE=/run/secrets/httpproxy_user \
|
HTTPPROXY_USER_SECRETFILE=/run/secrets/httpproxy_user \
|
||||||
@@ -177,6 +181,7 @@ ENV VPN_SERVICE_PROVIDER=pia \
|
|||||||
SHADOWSOCKS_PASSWORD_SECRETFILE=/run/secrets/shadowsocks_password \
|
SHADOWSOCKS_PASSWORD_SECRETFILE=/run/secrets/shadowsocks_password \
|
||||||
SHADOWSOCKS_CIPHER=chacha20-ietf-poly1305 \
|
SHADOWSOCKS_CIPHER=chacha20-ietf-poly1305 \
|
||||||
# Control server
|
# Control server
|
||||||
|
HTTP_CONTROL_SERVER_LOG=on \
|
||||||
HTTP_CONTROL_SERVER_ADDRESS=":8000" \
|
HTTP_CONTROL_SERVER_ADDRESS=":8000" \
|
||||||
# Server data updater
|
# Server data updater
|
||||||
UPDATER_PERIOD=0 \
|
UPDATER_PERIOD=0 \
|
||||||
|
|||||||
32
README.md
32
README.md
@@ -38,17 +38,16 @@ Lightweight swiss-knife-like VPN client to multiple VPN service providers
|
|||||||
- [Setup](#setup)
|
- [Setup](#setup)
|
||||||
- [Features](#features)
|
- [Features](#features)
|
||||||
- Problem?
|
- Problem?
|
||||||
- [Check the Wiki](https://github.com/qdm12/gluetun/wiki)
|
- Check the Wiki [common errors](https://github.com/qdm12/gluetun-wiki/tree/main/errors) and [faq](https://github.com/qdm12/gluetun-wiki/tree/main/faq)
|
||||||
- [Start a discussion](https://github.com/qdm12/gluetun/discussions)
|
- [Start a discussion](https://github.com/qdm12/gluetun/discussions)
|
||||||
- [Fix the Unraid template](https://github.com/qdm12/gluetun/discussions/550)
|
- [Fix the Unraid template](https://github.com/qdm12/gluetun/discussions/550)
|
||||||
- Suggestion?
|
- Suggestion?
|
||||||
- [Create an issue](https://github.com/qdm12/gluetun/issues)
|
- [Create an issue](https://github.com/qdm12/gluetun/issues)
|
||||||
- [Join the Slack channel](https://join.slack.com/t/qdm12/shared_invite/enQtOTE0NjcxNTM1ODc5LTYyZmVlOTM3MGI4ZWU0YmJkMjUxNmQ4ODQ2OTAwYzMxMTlhY2Q1MWQyOWUyNjc2ODliNjFjMDUxNWNmNzk5MDk)
|
|
||||||
- Happy?
|
- Happy?
|
||||||
- Sponsor me on [github.com/sponsors/qdm12](https://github.com/sponsors/qdm12)
|
- Sponsor me on [github.com/sponsors/qdm12](https://github.com/sponsors/qdm12)
|
||||||
- Donate to [paypal.me/qmcgaw](https://www.paypal.me/qmcgaw)
|
- Donate to [paypal.me/qmcgaw](https://www.paypal.me/qmcgaw)
|
||||||
- Drop me [an email](mailto:quentin.mcgaw@gmail.com)
|
- Drop me [an email](mailto:quentin.mcgaw@gmail.com)
|
||||||
- **Want to add a VPN provider?** check [Development](https://github.com/qdm12/gluetun/wiki/Development) and [Add a provider](https://github.com/qdm12/gluetun/wiki/Add-a-provider)
|
- **Want to add a VPN provider?** check [the development page](https://github.com/qdm12/gluetun-wiki/blob/main/contributing/development.md) and [add a provider page](https://github.com/qdm12/gluetun-wiki/blob/main/contributing/add-a-provider.md)
|
||||||
- Video:
|
- Video:
|
||||||
|
|
||||||
[](https://youtu.be/0F6I03LQcI4)
|
[](https://youtu.be/0F6I03LQcI4)
|
||||||
@@ -61,9 +60,9 @@ Lightweight swiss-knife-like VPN client to multiple VPN service providers
|
|||||||
- Supports: **AirVPN**, **Cyberghost**, **ExpressVPN**, **FastestVPN**, **HideMyAss**, **IPVanish**, **IVPN**, **Mullvad**, **NordVPN**, **Perfect Privacy**, **Privado**, **Private Internet Access**, **PrivateVPN**, **ProtonVPN**, **PureVPN**, **SlickVPN**, **Surfshark**, **TorGuard**, **VPNSecure.me**, **VPNUnlimited**, **Vyprvpn**, **WeVPN**, **Windscribe** servers
|
- Supports: **AirVPN**, **Cyberghost**, **ExpressVPN**, **FastestVPN**, **HideMyAss**, **IPVanish**, **IVPN**, **Mullvad**, **NordVPN**, **Perfect Privacy**, **Privado**, **Private Internet Access**, **PrivateVPN**, **ProtonVPN**, **PureVPN**, **SlickVPN**, **Surfshark**, **TorGuard**, **VPNSecure.me**, **VPNUnlimited**, **Vyprvpn**, **WeVPN**, **Windscribe** servers
|
||||||
- Supports OpenVPN for all providers listed
|
- Supports OpenVPN for all providers listed
|
||||||
- Supports Wireguard both kernelspace and userspace
|
- Supports Wireguard both kernelspace and userspace
|
||||||
- For **Mullvad**, **Ivpn**, **Surfshark** and **Windscribe**
|
- For **AirVPN**, **Ivpn**, **Mullvad**, **NordVPN**, **Surfshark** and **Windscribe**
|
||||||
- For **ProtonVPN**, **PureVPN**, **Torguard**, **VPN Unlimited** and **WeVPN** using [the custom provider](https://github.com/qdm12/gluetun/wiki/Custom-provider)
|
- For **ProtonVPN**, **PureVPN**, **Torguard**, **VPN Unlimited** and **WeVPN** using [the custom provider](https://github.com/qdm12/gluetun-wiki/blob/main/setup/providers/custom.md)
|
||||||
- For custom Wireguard configurations 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/blob/main/setup/providers/custom.md)
|
||||||
- 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
|
||||||
@@ -71,10 +70,10 @@ Lightweight swiss-knife-like VPN client to multiple VPN service providers
|
|||||||
- Built in firewall kill switch to allow traffic only with needed the VPN servers and LAN devices
|
- Built in firewall kill switch to allow traffic only with needed the VPN servers and LAN devices
|
||||||
- Built in Shadowsocks proxy (protocol based on SOCKS5 with an encryption layer, tunnels TCP+UDP)
|
- Built in Shadowsocks proxy (protocol based on SOCKS5 with an encryption layer, tunnels TCP+UDP)
|
||||||
- Built in HTTP proxy (tunnels HTTP and HTTPS through TCP)
|
- Built in HTTP proxy (tunnels HTTP and HTTPS through TCP)
|
||||||
- [Connect other containers to it](https://github.com/qdm12/gluetun/wiki/Connect-a-container-to-gluetun)
|
- [Connect other containers to it](https://github.com/qdm12/gluetun-wiki/blob/main/setup/connect-a-container-to-gluetun.md)
|
||||||
- [Connect LAN devices to it](https://github.com/qdm12/gluetun/wiki/Connect-a-LAN-device-to-gluetun)
|
- [Connect LAN devices to it](https://github.com/qdm12/gluetun-wiki/blob/main/setup/connect-a-lan-device-to-gluetun.md)
|
||||||
- Compatible with amd64, i686 (32 bit), **ARM** 64 bit, ARM 32 bit v6 and v7, and even ppc64le 🎆
|
- Compatible with amd64, i686 (32 bit), **ARM** 64 bit, ARM 32 bit v6 and v7, and even ppc64le 🎆
|
||||||
- [Custom VPN server side port forwarding for Private Internet Access](https://github.com/qdm12/gluetun/wiki/Private-internet-access#vpn-server-port-forwarding)
|
- [Custom VPN server side port forwarding for Private Internet Access](https://github.com/qdm12/gluetun-wiki/blob/main/setup/providers/private-internet-access.md#vpn-server-port-forwarding)
|
||||||
- Possibility of split horizon DNS by selecting multiple DNS over TLS providers
|
- Possibility of split horizon DNS by selecting multiple DNS over TLS providers
|
||||||
- Unbound subprogram drops root privileges once launched
|
- Unbound subprogram drops root privileges once launched
|
||||||
- Can work as a Kubernetes sidecar container, thanks @rorph
|
- Can work as a Kubernetes sidecar container, thanks @rorph
|
||||||
@@ -83,9 +82,9 @@ Lightweight swiss-knife-like VPN client to multiple VPN service providers
|
|||||||
|
|
||||||
🎉 There are now instructions specific to each VPN provider with examples to help you get started as quickly as possible!
|
🎉 There are now instructions specific to each VPN provider with examples to help you get started as quickly as possible!
|
||||||
|
|
||||||
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.yml&title=Wiki+issue%3A+)
|
[🐛 Found a bug in the Wiki?!](https://github.com/qdm12/gluetun-wiki/issues/new)
|
||||||
|
|
||||||
Here's a docker-compose.yml for the laziest:
|
Here's a docker-compose.yml for the laziest:
|
||||||
|
|
||||||
@@ -95,7 +94,8 @@ services:
|
|||||||
gluetun:
|
gluetun:
|
||||||
image: qmcgaw/gluetun
|
image: qmcgaw/gluetun
|
||||||
# container_name: gluetun
|
# container_name: gluetun
|
||||||
# line above must be uncommented to allow external containers to connect. See https://github.com/qdm12/gluetun/wiki/Connect-a-container-to-gluetun#external-container-to-gluetun
|
# line above must be uncommented to allow external containers to connect.
|
||||||
|
# See https://github.com/qdm12/gluetun-wiki/blob/main/setup/connect-a-container-to-gluetun.md#external-container-to-gluetun
|
||||||
cap_add:
|
cap_add:
|
||||||
- NET_ADMIN
|
- NET_ADMIN
|
||||||
devices:
|
devices:
|
||||||
@@ -107,7 +107,7 @@ services:
|
|||||||
volumes:
|
volumes:
|
||||||
- /yourpath:/gluetun
|
- /yourpath:/gluetun
|
||||||
environment:
|
environment:
|
||||||
# See https://github.com/qdm12/gluetun/wiki
|
# See https://github.com/qdm12/gluetun-wiki/tree/main/setup#setup
|
||||||
- VPN_SERVICE_PROVIDER=ivpn
|
- VPN_SERVICE_PROVIDER=ivpn
|
||||||
- VPN_TYPE=openvpn
|
- VPN_TYPE=openvpn
|
||||||
# OpenVPN:
|
# OpenVPN:
|
||||||
@@ -118,13 +118,13 @@ services:
|
|||||||
# - WIREGUARD_ADDRESSES=10.64.222.21/32
|
# - WIREGUARD_ADDRESSES=10.64.222.21/32
|
||||||
# Timezone for accurate log times
|
# Timezone for accurate log times
|
||||||
- TZ=
|
- TZ=
|
||||||
# Server list updater. See https://github.com/qdm12/gluetun/wiki/Updating-Servers#periodic-update
|
# Server list updater
|
||||||
|
# See https://github.com/qdm12/gluetun-wiki/blob/main/setup/servers.md#update-the-vpn-servers-list
|
||||||
- UPDATER_PERIOD=
|
- UPDATER_PERIOD=
|
||||||
- UPDATER_VPN_SERVICE_PROVIDERS=
|
|
||||||
```
|
```
|
||||||
|
|
||||||
🆕 Image also available as `ghcr.io/qdm12/gluetun`
|
🆕 Image also available as `ghcr.io/qdm12/gluetun`
|
||||||
|
|
||||||
## License
|
## License
|
||||||
|
|
||||||
[](https://github.com/qdm12/gluetun/master/LICENSE)
|
[](https://github.com/qdm12/gluetun/blob/master/LICENSE)
|
||||||
|
|||||||
@@ -82,10 +82,10 @@ func main() {
|
|||||||
cli := cli.New()
|
cli := cli.New()
|
||||||
cmder := command.NewCmder()
|
cmder := command.NewCmder()
|
||||||
|
|
||||||
envReader := env.New(logger)
|
|
||||||
filesReader := files.New()
|
|
||||||
secretsReader := secrets.New()
|
secretsReader := secrets.New()
|
||||||
muxReader := mux.New(envReader, filesReader, secretsReader)
|
filesReader := files.New()
|
||||||
|
envReader := env.New(logger)
|
||||||
|
muxReader := mux.New(secretsReader, filesReader, envReader)
|
||||||
|
|
||||||
errorCh := make(chan error)
|
errorCh := make(chan error)
|
||||||
go func() {
|
go func() {
|
||||||
@@ -159,7 +159,7 @@ func _main(ctx context.Context, buildInfo models.BuildInformation,
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
announcementExp, err := time.Parse(time.RFC3339, "2021-02-15T00:00:00Z")
|
announcementExp, err := time.Parse(time.RFC3339, "2023-07-01T00:00:00Z")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -170,7 +170,7 @@ func _main(ctx context.Context, buildInfo models.BuildInformation,
|
|||||||
Version: buildInfo.Version,
|
Version: buildInfo.Version,
|
||||||
Commit: buildInfo.Commit,
|
Commit: buildInfo.Commit,
|
||||||
BuildDate: buildInfo.Created,
|
BuildDate: buildInfo.Created,
|
||||||
Announcement: "Large settings parsing refactoring merged on 2022-01-06, please report any issue!",
|
Announcement: "Wiki moved to https://github.com/qdm12/gluetun-wiki",
|
||||||
AnnounceExp: announcementExp,
|
AnnounceExp: announcementExp,
|
||||||
// Sponsor information
|
// Sponsor information
|
||||||
PaypalUser: "qmcgaw",
|
PaypalUser: "qmcgaw",
|
||||||
@@ -376,12 +376,13 @@ func _main(ctx context.Context, buildInfo models.BuildInformation,
|
|||||||
|
|
||||||
portForwardLogger := logger.New(log.SetComponent("port forwarding"))
|
portForwardLogger := logger.New(log.SetComponent("port forwarding"))
|
||||||
portForwardLooper := portforward.NewLoop(allSettings.VPN.Provider.PortForwarding,
|
portForwardLooper := portforward.NewLoop(allSettings.VPN.Provider.PortForwarding,
|
||||||
httpClient, firewallConf, portForwardLogger, puid, pgid)
|
routingConf, httpClient, firewallConf, portForwardLogger, puid, pgid)
|
||||||
portForwardHandler, portForwardCtx, portForwardDone := goshutdown.NewGoRoutineHandler(
|
portForwardRunError, err := portForwardLooper.Start(ctx)
|
||||||
"port forwarding", goroutine.OptionTimeout(time.Second))
|
if err != nil {
|
||||||
go portForwardLooper.Run(portForwardCtx, portForwardDone)
|
return fmt.Errorf("starting port forwarding loop: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
unboundLogger := logger.New(log.SetComponent("dns over tls"))
|
unboundLogger := logger.New(log.SetComponent("dns"))
|
||||||
unboundLooper := dns.NewLoop(dnsConf, allSettings.DNS, httpClient,
|
unboundLooper := dns.NewLoop(dnsConf, allSettings.DNS, httpClient,
|
||||||
unboundLogger)
|
unboundLogger)
|
||||||
dnsHandler, dnsCtx, dnsDone := goshutdown.NewGoRoutineHandler(
|
dnsHandler, dnsCtx, dnsDone := goshutdown.NewGoRoutineHandler(
|
||||||
@@ -399,15 +400,10 @@ func _main(ctx context.Context, buildInfo models.BuildInformation,
|
|||||||
publicIPLooper := publicip.NewLoop(ipFetcher,
|
publicIPLooper := publicip.NewLoop(ipFetcher,
|
||||||
logger.New(log.SetComponent("ip getter")),
|
logger.New(log.SetComponent("ip getter")),
|
||||||
allSettings.PublicIP, puid, pgid)
|
allSettings.PublicIP, puid, pgid)
|
||||||
pubIPHandler, pubIPCtx, pubIPDone := goshutdown.NewGoRoutineHandler(
|
publicIPRunError, err := publicIPLooper.Start(ctx)
|
||||||
"public IP", goroutine.OptionTimeout(defaultShutdownTimeout))
|
if err != nil {
|
||||||
go publicIPLooper.Run(pubIPCtx, pubIPDone)
|
return fmt.Errorf("starting public ip loop: %w", err)
|
||||||
otherGroupHandler.Add(pubIPHandler)
|
}
|
||||||
|
|
||||||
pubIPTickerHandler, pubIPTickerCtx, pubIPTickerDone := goshutdown.NewGoRoutineHandler(
|
|
||||||
"public IP", goroutine.OptionTimeout(defaultShutdownTimeout))
|
|
||||||
go publicIPLooper.RunRestartTicker(pubIPTickerCtx, pubIPTickerDone)
|
|
||||||
tickersGroupHandler.Add(pubIPTickerHandler)
|
|
||||||
|
|
||||||
updaterLogger := logger.New(log.SetComponent("updater"))
|
updaterLogger := logger.New(log.SetComponent("updater"))
|
||||||
|
|
||||||
@@ -481,13 +477,31 @@ func _main(ctx context.Context, buildInfo models.BuildInformation,
|
|||||||
order.OptionOnSuccess(defaultShutdownOnSuccess),
|
order.OptionOnSuccess(defaultShutdownOnSuccess),
|
||||||
order.OptionOnFailure(defaultShutdownOnFailure))
|
order.OptionOnFailure(defaultShutdownOnFailure))
|
||||||
orderHandler.Append(controlGroupHandler, tickersGroupHandler, healthServerHandler,
|
orderHandler.Append(controlGroupHandler, tickersGroupHandler, healthServerHandler,
|
||||||
vpnHandler, portForwardHandler, otherGroupHandler)
|
vpnHandler, otherGroupHandler)
|
||||||
|
|
||||||
// Start VPN for the first time in a blocking call
|
// Start VPN for the first time in a blocking call
|
||||||
// until the VPN is launched
|
// until the VPN is launched
|
||||||
_, _ = vpnLooper.ApplyStatus(ctx, constants.Running) // TODO option to disable with variable
|
_, _ = vpnLooper.ApplyStatus(ctx, constants.Running) // TODO option to disable with variable
|
||||||
|
|
||||||
<-ctx.Done()
|
select {
|
||||||
|
case <-ctx.Done():
|
||||||
|
stoppers := []interface {
|
||||||
|
String() string
|
||||||
|
Stop() error
|
||||||
|
}{
|
||||||
|
portForwardLooper, publicIPLooper,
|
||||||
|
}
|
||||||
|
for _, stopper := range stoppers {
|
||||||
|
err := stopper.Stop()
|
||||||
|
if err != nil {
|
||||||
|
logger.Error(fmt.Sprintf("stopping %s: %s", stopper, err))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
case err := <-portForwardRunError:
|
||||||
|
logger.Errorf("port forwarding loop crashed: %s", err)
|
||||||
|
case err := <-publicIPRunError:
|
||||||
|
logger.Errorf("public IP loop crashed: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
return orderHandler.Shutdown(context.Background())
|
return orderHandler.Shutdown(context.Background())
|
||||||
}
|
}
|
||||||
|
|||||||
26
go.mod
26
go.mod
@@ -1,29 +1,33 @@
|
|||||||
module github.com/qdm12/gluetun
|
module github.com/qdm12/gluetun
|
||||||
|
|
||||||
go 1.20
|
go 1.21
|
||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/breml/rootcerts v0.2.11
|
github.com/breml/rootcerts v0.2.14
|
||||||
github.com/fatih/color v1.15.0
|
github.com/fatih/color v1.16.0
|
||||||
github.com/golang/mock v1.6.0
|
github.com/golang/mock v1.6.0
|
||||||
|
github.com/klauspost/compress v1.17.4
|
||||||
|
github.com/klauspost/pgzip v1.2.6
|
||||||
github.com/qdm12/dns v1.11.0
|
github.com/qdm12/dns v1.11.0
|
||||||
github.com/qdm12/golibs v0.0.0-20210822203818-5c568b0777b6
|
github.com/qdm12/golibs v0.0.0-20210822203818-5c568b0777b6
|
||||||
github.com/qdm12/gosettings v0.3.0-rc13
|
github.com/qdm12/gosettings v0.4.0-rc1
|
||||||
github.com/qdm12/goshutdown v0.3.0
|
github.com/qdm12/goshutdown v0.3.0
|
||||||
github.com/qdm12/gosplash v0.1.0
|
github.com/qdm12/gosplash v0.1.0
|
||||||
github.com/qdm12/gotree v0.2.0
|
github.com/qdm12/gotree v0.2.0
|
||||||
github.com/qdm12/govalid v0.2.0-rc1
|
github.com/qdm12/govalid v0.2.0-rc1
|
||||||
github.com/qdm12/log v0.1.0
|
github.com/qdm12/log v0.1.0
|
||||||
github.com/qdm12/ss-server v0.5.0-rc1
|
github.com/qdm12/ss-server v0.5.0
|
||||||
github.com/qdm12/updated v0.0.0-20210603204757-205acfe6937e
|
github.com/qdm12/updated v0.0.0-20210603204757-205acfe6937e
|
||||||
github.com/stretchr/testify v1.8.4
|
github.com/stretchr/testify v1.8.4
|
||||||
|
github.com/ulikunitz/xz v0.5.11
|
||||||
github.com/vishvananda/netlink v1.2.1-beta.2
|
github.com/vishvananda/netlink v1.2.1-beta.2
|
||||||
github.com/youmark/pkcs8 v0.0.0-20201027041543-1326539a0a0a
|
github.com/youmark/pkcs8 v0.0.0-20201027041543-1326539a0a0a
|
||||||
golang.org/x/net v0.10.0
|
golang.org/x/net v0.19.0
|
||||||
golang.org/x/sys v0.8.0
|
golang.org/x/sys v0.15.0
|
||||||
golang.org/x/text v0.10.0
|
golang.org/x/text v0.14.0
|
||||||
golang.zx2c4.com/wireguard v0.0.0-20230325221338-052af4a8072b
|
golang.zx2c4.com/wireguard v0.0.0-20230325221338-052af4a8072b
|
||||||
golang.zx2c4.com/wireguard/wgctrl v0.0.0-20230215201556-9c5414ab4bde
|
golang.zx2c4.com/wireguard/wgctrl v0.0.0-20230215201556-9c5414ab4bde
|
||||||
|
gopkg.in/ini.v1 v1.67.0
|
||||||
inet.af/netaddr v0.0.0-20220811202034-502d2d690317
|
inet.af/netaddr v0.0.0-20220811202034-502d2d690317
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -32,7 +36,7 @@ require (
|
|||||||
github.com/google/go-cmp v0.5.9 // indirect
|
github.com/google/go-cmp v0.5.9 // indirect
|
||||||
github.com/josharian/native v1.0.0 // indirect
|
github.com/josharian/native v1.0.0 // indirect
|
||||||
github.com/mattn/go-colorable v0.1.13 // indirect
|
github.com/mattn/go-colorable v0.1.13 // indirect
|
||||||
github.com/mattn/go-isatty v0.0.17 // indirect
|
github.com/mattn/go-isatty v0.0.20 // indirect
|
||||||
github.com/mdlayher/genetlink v1.2.0 // indirect
|
github.com/mdlayher/genetlink v1.2.0 // indirect
|
||||||
github.com/mdlayher/netlink v1.6.2 // indirect
|
github.com/mdlayher/netlink v1.6.2 // indirect
|
||||||
github.com/mdlayher/socket v0.2.3 // indirect
|
github.com/mdlayher/socket v0.2.3 // indirect
|
||||||
@@ -42,8 +46,8 @@ require (
|
|||||||
github.com/riobard/go-bloom v0.0.0-20200614022211-cdc8013cb5b3 // indirect
|
github.com/riobard/go-bloom v0.0.0-20200614022211-cdc8013cb5b3 // indirect
|
||||||
github.com/vishvananda/netns v0.0.0-20200728191858-db3c7e526aae // indirect
|
github.com/vishvananda/netns v0.0.0-20200728191858-db3c7e526aae // indirect
|
||||||
go4.org/intern v0.0.0-20211027215823-ae77deb06f29 // indirect
|
go4.org/intern v0.0.0-20211027215823-ae77deb06f29 // indirect
|
||||||
go4.org/unsafe/assume-no-moving-gc v0.0.0-20230221090011-e4bae7ad2296 // indirect
|
go4.org/unsafe/assume-no-moving-gc v0.0.0-20230525183740-e7c30c78aeb2 // indirect
|
||||||
golang.org/x/crypto v0.9.0 // indirect
|
golang.org/x/crypto v0.17.0 // indirect
|
||||||
golang.org/x/exp v0.0.0-20230522175609-2e198f4a06a1 // indirect
|
golang.org/x/exp v0.0.0-20230522175609-2e198f4a06a1 // indirect
|
||||||
golang.org/x/sync v0.1.0 // indirect
|
golang.org/x/sync v0.1.0 // indirect
|
||||||
golang.zx2c4.com/wintun v0.0.0-20230126152724-0fa3db229ce2 // indirect
|
golang.zx2c4.com/wintun v0.0.0-20230126152724-0fa3db229ce2 // indirect
|
||||||
|
|||||||
53
go.sum
53
go.sum
@@ -4,8 +4,8 @@ github.com/alcortesm/tgz v0.0.0-20161220082320-9c5fe88206d7/go.mod h1:6zEj6s6u/g
|
|||||||
github.com/anmitsu/go-shlex v0.0.0-20161002113705-648efa622239/go.mod h1:2FmKhYUyUczH0OGQWaF5ceTx0UBShxjsH6f8oGKYe2c=
|
github.com/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.2.11 h1:njUAtoyZ6HUXPAPk63tGz0BEZk1/6gyfqK5fTzksHkM=
|
github.com/breml/rootcerts v0.2.14 h1:Bu0Ullru+/GTr/S582LCzP1P57WgncIEFylXkBBXgEI=
|
||||||
github.com/breml/rootcerts v0.2.11/go.mod h1:S/PKh+4d1HUn4HQovEB8hPJZO6pUZYrIhmXBhsegfXw=
|
github.com/breml/rootcerts v0.2.14/go.mod h1:S/PKh+4d1HUn4HQovEB8hPJZO6pUZYrIhmXBhsegfXw=
|
||||||
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=
|
||||||
@@ -14,8 +14,8 @@ github.com/docker/go-units v0.3.3/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDD
|
|||||||
github.com/dvyukov/go-fuzz v0.0.0-20210103155950-6a8e9d1f2415/go.mod h1:11Gm+ccJnvAhCNLlf5+cS9KjtbaD5I5zaZpFMsTHWTw=
|
github.com/dvyukov/go-fuzz v0.0.0-20210103155950-6a8e9d1f2415/go.mod h1:11Gm+ccJnvAhCNLlf5+cS9KjtbaD5I5zaZpFMsTHWTw=
|
||||||
github.com/emirpasic/gods v1.12.0/go.mod h1:YfzfFFoVP/catgzJb4IKIqXjX78Ha8FMSDh3ymbK86o=
|
github.com/emirpasic/gods v1.12.0/go.mod h1:YfzfFFoVP/catgzJb4IKIqXjX78Ha8FMSDh3ymbK86o=
|
||||||
github.com/fatih/color v1.12.0/go.mod h1:ELkj/draVOlAH/xkhN6mQ50Qd0MPOk5AAr3maGEBuJM=
|
github.com/fatih/color v1.12.0/go.mod h1:ELkj/draVOlAH/xkhN6mQ50Qd0MPOk5AAr3maGEBuJM=
|
||||||
github.com/fatih/color v1.15.0 h1:kOqh6YHBtK8aywxGerMG2Eq3H6Qgoqeo13Bk2Mv/nBs=
|
github.com/fatih/color v1.16.0 h1:zmkK9Ngbjj+K0yRhTVONQh1p/HknKYSlNT+vZCzyokM=
|
||||||
github.com/fatih/color v1.15.0/go.mod h1:0h5ZqXfHYED7Bhv2ZJamyIOUej9KtShiJESRwBDUSsw=
|
github.com/fatih/color v1.16.0/go.mod h1:fL2Sau1YI5c0pdGEVCbKQbLXB6edEj1ZgiY4NijnWvE=
|
||||||
github.com/flynn/go-shlex v0.0.0-20150515145356-3f9db97f8568/go.mod h1:xEzjJPgXI435gkrCt3MPfRiAkVrwSbHsst4LCFVfpJc=
|
github.com/flynn/go-shlex v0.0.0-20150515145356-3f9db97f8568/go.mod h1:xEzjJPgXI435gkrCt3MPfRiAkVrwSbHsst4LCFVfpJc=
|
||||||
github.com/gliderlabs/ssh v0.2.2/go.mod h1:U7qILu1NlMHj9FlMhZLlkCdDnU1DBEAqr0aevW3Awn0=
|
github.com/gliderlabs/ssh v0.2.2/go.mod h1:U7qILu1NlMHj9FlMhZLlkCdDnU1DBEAqr0aevW3Awn0=
|
||||||
github.com/globalsign/mgo v0.0.0-20180905125535-1ca0a4f7cbcb/go.mod h1:xkRDCp4j0OGD1HRkm4kmhM+pmpv3AKq5SU7GMg4oO/Q=
|
github.com/globalsign/mgo v0.0.0-20180905125535-1ca0a4f7cbcb/go.mod h1:xkRDCp4j0OGD1HRkm4kmhM+pmpv3AKq5SU7GMg4oO/Q=
|
||||||
@@ -37,6 +37,7 @@ github.com/golang/mock v1.6.0 h1:ErTB+efbowRARo13NNdxyJji2egdxLGQhRaY+DUumQc=
|
|||||||
github.com/golang/mock v1.6.0/go.mod h1:p6yTPP+5HYm5mzsMV8JkE6ZKdX+/wYM6Hr+LicevLPs=
|
github.com/golang/mock v1.6.0/go.mod h1:p6yTPP+5HYm5mzsMV8JkE6ZKdX+/wYM6Hr+LicevLPs=
|
||||||
github.com/gomodule/redigo v2.0.0+incompatible/go.mod h1:B4C85qUVwatsJoIUNIfCRsp7qO0iAmpGFZ4EELWSbC4=
|
github.com/gomodule/redigo v2.0.0+incompatible/go.mod h1:B4C85qUVwatsJoIUNIfCRsp7qO0iAmpGFZ4EELWSbC4=
|
||||||
github.com/google/btree v1.0.1 h1:gK4Kx5IaGY9CD5sPJ36FHiBJ6ZXl0kilRiiCj+jdYp4=
|
github.com/google/btree v1.0.1 h1:gK4Kx5IaGY9CD5sPJ36FHiBJ6ZXl0kilRiiCj+jdYp4=
|
||||||
|
github.com/google/btree v1.0.1/go.mod h1:xXMiIv4Fb/0kKde4SpL7qlzvu5cMJDRkFDxJfI9uaxA=
|
||||||
github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
|
github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
|
||||||
github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||||
github.com/google/go-cmp v0.5.7/go.mod h1:n+brtR0CgQNWTVd5ZUFpTBC8YFBDLK/h/bpaJ8/DtOE=
|
github.com/google/go-cmp v0.5.7/go.mod h1:n+brtR0CgQNWTVd5ZUFpTBC8YFBDLK/h/bpaJ8/DtOE=
|
||||||
@@ -49,6 +50,10 @@ github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJS
|
|||||||
github.com/josharian/native v1.0.0 h1:Ts/E8zCSEsG17dUqv7joXJFybuMLjQfWE04tsBODTxk=
|
github.com/josharian/native v1.0.0 h1:Ts/E8zCSEsG17dUqv7joXJFybuMLjQfWE04tsBODTxk=
|
||||||
github.com/josharian/native v1.0.0/go.mod h1:7X/raswPFr05uY3HiLlYeyQntB6OO7E/d2Cu7qoaN2w=
|
github.com/josharian/native v1.0.0/go.mod h1:7X/raswPFr05uY3HiLlYeyQntB6OO7E/d2Cu7qoaN2w=
|
||||||
github.com/kevinburke/ssh_config v0.0.0-20190725054713-01f96b0aa0cd/go.mod h1:CT57kijsi8u/K/BOFA39wgDQJ9CxiF4nAY/ojJ6r6mM=
|
github.com/kevinburke/ssh_config v0.0.0-20190725054713-01f96b0aa0cd/go.mod h1:CT57kijsi8u/K/BOFA39wgDQJ9CxiF4nAY/ojJ6r6mM=
|
||||||
|
github.com/klauspost/compress v1.17.4 h1:Ej5ixsIri7BrIjBkRZLTo6ghwrEtHFk7ijlczPW4fZ4=
|
||||||
|
github.com/klauspost/compress v1.17.4/go.mod h1:/dCuZOvVtNoHsyb+cuJD3itjs3NbnF6KH9zAO4BDxPM=
|
||||||
|
github.com/klauspost/pgzip v1.2.6 h1:8RXeL5crjEUFnR2/Sn6GJNWtSQ3Dk8pq4CL3jvdDyjU=
|
||||||
|
github.com/klauspost/pgzip v1.2.6/go.mod h1:Ch1tH69qFZu15pkjo5kYi6mth2Zzwzt50oCQKQE9RUs=
|
||||||
github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI=
|
github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI=
|
||||||
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
|
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
|
||||||
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
|
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
|
||||||
@@ -62,8 +67,8 @@ github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxec
|
|||||||
github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg=
|
github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg=
|
||||||
github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU=
|
github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU=
|
||||||
github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
|
github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
|
||||||
github.com/mattn/go-isatty v0.0.17 h1:BTarxUcIeDqL27Mc+vyvdWYSL28zpIhv3RoTdsLMPng=
|
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
|
||||||
github.com/mattn/go-isatty v0.0.17/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
|
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
|
||||||
github.com/mdlayher/genetlink v1.2.0 h1:4yrIkRV5Wfk1WfpWTcoOlGmsWgQj3OtQN9ZsbrE+XtU=
|
github.com/mdlayher/genetlink v1.2.0 h1:4yrIkRV5Wfk1WfpWTcoOlGmsWgQj3OtQN9ZsbrE+XtU=
|
||||||
github.com/mdlayher/genetlink v1.2.0/go.mod h1:ra5LDov2KrUCZJiAtEvXXZBxGMInICMXIwshlJ+qRxQ=
|
github.com/mdlayher/genetlink v1.2.0/go.mod h1:ra5LDov2KrUCZJiAtEvXXZBxGMInICMXIwshlJ+qRxQ=
|
||||||
github.com/mdlayher/netlink v1.6.0/go.mod h1:0o3PlBmGst1xve7wQ7j/hwpNaFaH4qCRyWCdcZk8/vA=
|
github.com/mdlayher/netlink v1.6.0/go.mod h1:0o3PlBmGst1xve7wQ7j/hwpNaFaH4qCRyWCdcZk8/vA=
|
||||||
@@ -75,6 +80,7 @@ github.com/mdlayher/socket v0.2.3/go.mod h1:bz12/FozYNH/VbvC3q7TRIK/Y6dH1kCKsXaU
|
|||||||
github.com/miekg/dns v1.1.40 h1:pyyPFfGMnciYUk/mXpKkVmeMQjfXqt3FAJ2hy7tPiLA=
|
github.com/miekg/dns v1.1.40 h1:pyyPFfGMnciYUk/mXpKkVmeMQjfXqt3FAJ2hy7tPiLA=
|
||||||
github.com/miekg/dns v1.1.40/go.mod h1:KNUDUusw/aVsxyTYZM1oqvCicbwhgbNgztCETuNZ7xM=
|
github.com/miekg/dns v1.1.40/go.mod h1:KNUDUusw/aVsxyTYZM1oqvCicbwhgbNgztCETuNZ7xM=
|
||||||
github.com/mikioh/ipaddr v0.0.0-20190404000644-d465c8ab6721 h1:RlZweED6sbSArvlE924+mUcZuXKLBHA35U7LN621Bws=
|
github.com/mikioh/ipaddr v0.0.0-20190404000644-d465c8ab6721 h1:RlZweED6sbSArvlE924+mUcZuXKLBHA35U7LN621Bws=
|
||||||
|
github.com/mikioh/ipaddr v0.0.0-20190404000644-d465c8ab6721/go.mod h1:Ickgr2WtCLZ2MDGd4Gr0geeCH5HybhRJbonOgQpvSxc=
|
||||||
github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
|
github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
|
||||||
github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
|
github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
|
||||||
github.com/mr-tron/base58 v1.2.0 h1:T/HDJBh4ZCPbU39/+c3rRvE0uKBQlU27+QI8LJ4t64o=
|
github.com/mr-tron/base58 v1.2.0 h1:T/HDJBh4ZCPbU39/+c3rRvE0uKBQlU27+QI8LJ4t64o=
|
||||||
@@ -91,8 +97,8 @@ github.com/qdm12/golibs v0.0.0-20210603202746-e5494e9c2ebb/go.mod h1:15RBzkun0i8
|
|||||||
github.com/qdm12/golibs v0.0.0-20210723175634-a75ca7fd74c2/go.mod h1:6aRbg4Z/bTbm9JfxsGXfWKHi7zsOvPfUTK1S5HuAFKg=
|
github.com/qdm12/golibs v0.0.0-20210723175634-a75ca7fd74c2/go.mod h1:6aRbg4Z/bTbm9JfxsGXfWKHi7zsOvPfUTK1S5HuAFKg=
|
||||||
github.com/qdm12/golibs v0.0.0-20210822203818-5c568b0777b6 h1:bge5AL7cjHJMPz+5IOz5yF01q/l8No6+lIEBieA8gMg=
|
github.com/qdm12/golibs v0.0.0-20210822203818-5c568b0777b6 h1:bge5AL7cjHJMPz+5IOz5yF01q/l8No6+lIEBieA8gMg=
|
||||||
github.com/qdm12/golibs v0.0.0-20210822203818-5c568b0777b6/go.mod h1:6aRbg4Z/bTbm9JfxsGXfWKHi7zsOvPfUTK1S5HuAFKg=
|
github.com/qdm12/golibs v0.0.0-20210822203818-5c568b0777b6/go.mod h1:6aRbg4Z/bTbm9JfxsGXfWKHi7zsOvPfUTK1S5HuAFKg=
|
||||||
github.com/qdm12/gosettings v0.3.0-rc13 h1:fag+/hFPBUcNk3a5ifUbwNS2VgXFpxindkl8mQNk76U=
|
github.com/qdm12/gosettings v0.4.0-rc1 h1:UYA92yyeDPbmZysIuG65yrpZVPtdIoRmtEHft/AyI38=
|
||||||
github.com/qdm12/gosettings v0.3.0-rc13/go.mod h1:JRV3opOpHvnKlIA29lKQMdYw1WSMVMfHYLLHPHol5ME=
|
github.com/qdm12/gosettings v0.4.0-rc1/go.mod h1:JRV3opOpHvnKlIA29lKQMdYw1WSMVMfHYLLHPHol5ME=
|
||||||
github.com/qdm12/goshutdown v0.3.0 h1:pqBpJkdwlZlfTEx4QHtS8u8CXx6pG0fVo6S1N0MpSEM=
|
github.com/qdm12/goshutdown v0.3.0 h1:pqBpJkdwlZlfTEx4QHtS8u8CXx6pG0fVo6S1N0MpSEM=
|
||||||
github.com/qdm12/goshutdown v0.3.0/go.mod h1:EqZ46No00kCTZ5qzdd3qIzY6ayhMt24QI8Mh8LVQYmM=
|
github.com/qdm12/goshutdown v0.3.0/go.mod h1:EqZ46No00kCTZ5qzdd3qIzY6ayhMt24QI8Mh8LVQYmM=
|
||||||
github.com/qdm12/gosplash v0.1.0 h1:Sfl+zIjFZFP7b0iqf2l5UkmEY97XBnaKkH3FNY6Gf7g=
|
github.com/qdm12/gosplash v0.1.0 h1:Sfl+zIjFZFP7b0iqf2l5UkmEY97XBnaKkH3FNY6Gf7g=
|
||||||
@@ -103,8 +109,8 @@ github.com/qdm12/govalid v0.2.0-rc1 h1:4iYQvU4ibrASgzelsEgZX4JyKX3UTB/DcHObzQ7BX
|
|||||||
github.com/qdm12/govalid v0.2.0-rc1/go.mod h1:/uWzVWMuS71wmbsVnlUxpQiy6EAXqm8eQ2RbyA72roQ=
|
github.com/qdm12/govalid v0.2.0-rc1/go.mod h1:/uWzVWMuS71wmbsVnlUxpQiy6EAXqm8eQ2RbyA72roQ=
|
||||||
github.com/qdm12/log v0.1.0 h1:jYBd/xscHYpblzZAd2kjZp2YmuYHjAAfbTViJWxoPTw=
|
github.com/qdm12/log v0.1.0 h1:jYBd/xscHYpblzZAd2kjZp2YmuYHjAAfbTViJWxoPTw=
|
||||||
github.com/qdm12/log v0.1.0/go.mod h1:Vchi5M8uBvHfPNIblN4mjXn/oSbiWguQIbsgF1zdQPI=
|
github.com/qdm12/log v0.1.0/go.mod h1:Vchi5M8uBvHfPNIblN4mjXn/oSbiWguQIbsgF1zdQPI=
|
||||||
github.com/qdm12/ss-server v0.5.0-rc1 h1:2rJEhDnUUc9AKtvyVu+CrnJwvdEjMaB1zFRQvTUlDPw=
|
github.com/qdm12/ss-server v0.5.0 h1:ARAqJayohDM51BmJ/R5Yplkpo+Qxgp7xizBF1HWd7uQ=
|
||||||
github.com/qdm12/ss-server v0.5.0-rc1/go.mod h1:IoFYGpVpxfIB/dMTr0PnSegdhV1gEfZLS9Tr1Qn8uRg=
|
github.com/qdm12/ss-server v0.5.0/go.mod h1:eFd8PL/uy0ZvJ4KeSUzToruJctVQoYqXk+LRy9vcOiI=
|
||||||
github.com/qdm12/updated v0.0.0-20210603204757-205acfe6937e h1:4q+uFLawkaQRq3yARYLsjJPZd2wYwxn4g6G/5v0xW1g=
|
github.com/qdm12/updated v0.0.0-20210603204757-205acfe6937e h1:4q+uFLawkaQRq3yARYLsjJPZd2wYwxn4g6G/5v0xW1g=
|
||||||
github.com/qdm12/updated v0.0.0-20210603204757-205acfe6937e/go.mod h1:UvJRGkZ9XL3/D7e7JiTTVLm1F3Cymd3/gFpD6frEpBo=
|
github.com/qdm12/updated v0.0.0-20210603204757-205acfe6937e/go.mod h1:UvJRGkZ9XL3/D7e7JiTTVLm1F3Cymd3/gFpD6frEpBo=
|
||||||
github.com/riobard/go-bloom v0.0.0-20200614022211-cdc8013cb5b3 h1:f/FNXud6gA3MNr8meMVVGxhp+QBTqY91tM8HjEuMjGg=
|
github.com/riobard/go-bloom v0.0.0-20200614022211-cdc8013cb5b3 h1:f/FNXud6gA3MNr8meMVVGxhp+QBTqY91tM8HjEuMjGg=
|
||||||
@@ -119,6 +125,8 @@ github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81P
|
|||||||
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||||
github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk=
|
github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk=
|
||||||
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
|
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
|
||||||
|
github.com/ulikunitz/xz v0.5.11 h1:kpFauv27b6ynzBNT/Xy+1k+fK4WswhN/6PN5WhFAGw8=
|
||||||
|
github.com/ulikunitz/xz v0.5.11/go.mod h1:nbz6k7qbPmH4IRqmfOplQw/tblSgqTqBwxkY0oWt/14=
|
||||||
github.com/vishvananda/netlink v1.2.1-beta.2 h1:Llsql0lnQEbHj0I1OuKyp8otXp0r3q0mPkuhwHfStVs=
|
github.com/vishvananda/netlink v1.2.1-beta.2 h1:Llsql0lnQEbHj0I1OuKyp8otXp0r3q0mPkuhwHfStVs=
|
||||||
github.com/vishvananda/netlink v1.2.1-beta.2/go.mod h1:twkDnbuQxJYemMlGd4JFIcuhgX83tXhKS2B/PRMpOho=
|
github.com/vishvananda/netlink v1.2.1-beta.2/go.mod h1:twkDnbuQxJYemMlGd4JFIcuhgX83tXhKS2B/PRMpOho=
|
||||||
github.com/vishvananda/netns v0.0.0-20200728191858-db3c7e526aae h1:4hwBBUfQCFe3Cym0ZtKyq7L16eZUtYKs+BaHDN6mAns=
|
github.com/vishvananda/netns v0.0.0-20200728191858-db3c7e526aae h1:4hwBBUfQCFe3Cym0ZtKyq7L16eZUtYKs+BaHDN6mAns=
|
||||||
@@ -136,8 +144,8 @@ go4.org/unsafe/assume-no-moving-gc v0.0.0-20201222175341-b30ae309168e/go.mod h1:
|
|||||||
go4.org/unsafe/assume-no-moving-gc v0.0.0-20201222180813-1025295fd063/go.mod h1:FftLjUGFEDu5k8lt0ddY+HcrH/qU/0qk+H8j9/nTl3E=
|
go4.org/unsafe/assume-no-moving-gc v0.0.0-20201222180813-1025295fd063/go.mod h1:FftLjUGFEDu5k8lt0ddY+HcrH/qU/0qk+H8j9/nTl3E=
|
||||||
go4.org/unsafe/assume-no-moving-gc v0.0.0-20211027215541-db492cf91b37/go.mod h1:FftLjUGFEDu5k8lt0ddY+HcrH/qU/0qk+H8j9/nTl3E=
|
go4.org/unsafe/assume-no-moving-gc v0.0.0-20211027215541-db492cf91b37/go.mod h1:FftLjUGFEDu5k8lt0ddY+HcrH/qU/0qk+H8j9/nTl3E=
|
||||||
go4.org/unsafe/assume-no-moving-gc v0.0.0-20220617031537-928513b29760/go.mod h1:FftLjUGFEDu5k8lt0ddY+HcrH/qU/0qk+H8j9/nTl3E=
|
go4.org/unsafe/assume-no-moving-gc v0.0.0-20220617031537-928513b29760/go.mod h1:FftLjUGFEDu5k8lt0ddY+HcrH/qU/0qk+H8j9/nTl3E=
|
||||||
go4.org/unsafe/assume-no-moving-gc v0.0.0-20230221090011-e4bae7ad2296 h1:QJ/xcIANMLApehfgPCHnfK1hZiaMmbaTVmPv7DAoTbo=
|
go4.org/unsafe/assume-no-moving-gc v0.0.0-20230525183740-e7c30c78aeb2 h1:WJhcL4p+YeDxmZWg141nRm7XC8IDmhz7lk5GpadO1Sg=
|
||||||
go4.org/unsafe/assume-no-moving-gc v0.0.0-20230221090011-e4bae7ad2296/go.mod h1:FftLjUGFEDu5k8lt0ddY+HcrH/qU/0qk+H8j9/nTl3E=
|
go4.org/unsafe/assume-no-moving-gc v0.0.0-20230525183740-e7c30c78aeb2/go.mod h1:FftLjUGFEDu5k8lt0ddY+HcrH/qU/0qk+H8j9/nTl3E=
|
||||||
golang.org/x/crypto v0.0.0-20190219172222-a4c6cb3142f2/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
|
golang.org/x/crypto v0.0.0-20190219172222-a4c6cb3142f2/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
|
||||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||||
golang.org/x/crypto v0.0.0-20190701094942-4def268fd1a4/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
golang.org/x/crypto v0.0.0-20190701094942-4def268fd1a4/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||||
@@ -146,8 +154,8 @@ golang.org/x/crypto v0.0.0-20200302210943-78000ba7a073/go.mod h1:LzIPMQfyMNhhGPh
|
|||||||
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||||
golang.org/x/crypto v0.0.0-20210513164829-c07d793c2f9a/go.mod h1:P+XmwS30IXTQdn5tA2iutPOUgjI07+tq3H3K9MVA1s8=
|
golang.org/x/crypto v0.0.0-20210513164829-c07d793c2f9a/go.mod h1:P+XmwS30IXTQdn5tA2iutPOUgjI07+tq3H3K9MVA1s8=
|
||||||
golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
||||||
golang.org/x/crypto v0.9.0 h1:LF6fAI+IutBocDJ2OT0Q1g8plpYljMZ4+lty+dsqw3g=
|
golang.org/x/crypto v0.17.0 h1:r8bRNjWL3GshPW3gkd+RpvzWrZAwPS49OmTGZ/uhM4k=
|
||||||
golang.org/x/crypto v0.9.0/go.mod h1:yrmDGqONDYtNj3tH8X9dzUun2m2lzPa9ngI6/RUPGR0=
|
golang.org/x/crypto v0.17.0/go.mod h1:gCAAfMLgwOJRpTjQ2zCCt2OcSfYMTeZVSRtQlPC7Nq4=
|
||||||
golang.org/x/exp v0.0.0-20230522175609-2e198f4a06a1 h1:k/i9J1pBpvlfR+9QsetwPyERsqu1GIbi967PQMq3Ivc=
|
golang.org/x/exp v0.0.0-20230522175609-2e198f4a06a1 h1:k/i9J1pBpvlfR+9QsetwPyERsqu1GIbi967PQMq3Ivc=
|
||||||
golang.org/x/exp v0.0.0-20230522175609-2e198f4a06a1/go.mod h1:V1LtkGg67GoY2N1AnLN78QLrzxkLyJw7RJb1gzOOz9w=
|
golang.org/x/exp v0.0.0-20230522175609-2e198f4a06a1/go.mod h1:V1LtkGg67GoY2N1AnLN78QLrzxkLyJw7RJb1gzOOz9w=
|
||||||
golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
|
golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
|
||||||
@@ -165,8 +173,8 @@ golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96b
|
|||||||
golang.org/x/net v0.0.0-20210928044308-7d9f5e0b762b/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
golang.org/x/net v0.0.0-20210928044308-7d9f5e0b762b/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
||||||
golang.org/x/net v0.0.0-20220127200216-cd36cc0744dd/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
|
golang.org/x/net v0.0.0-20220127200216-cd36cc0744dd/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
|
||||||
golang.org/x/net v0.0.0-20220923203811-8be639271d50/go.mod h1:YDH+HFinaLZZlnHAfSS6ZXJJ9M9t4Dl22yv3iI2vPwk=
|
golang.org/x/net v0.0.0-20220923203811-8be639271d50/go.mod h1:YDH+HFinaLZZlnHAfSS6ZXJJ9M9t4Dl22yv3iI2vPwk=
|
||||||
golang.org/x/net v0.10.0 h1:X2//UzNDwYmtCLn7To6G58Wr6f5ahEAQgKNzv9Y951M=
|
golang.org/x/net v0.19.0 h1:zTwKpTd2XuCqf8huc7Fo2iSy+4RHPd10s4KzeTnVr1c=
|
||||||
golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg=
|
golang.org/x/net v0.19.0/go.mod h1:CfAk/cbD4CthTvqiEl8NpboMuiuOYsAr/7NOjZJtv1U=
|
||||||
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
@@ -195,8 +203,9 @@ golang.org/x/sys v0.0.0-20220128215802-99c3d69c2c27/go.mod h1:oPkhp1MJrh7nUepCBc
|
|||||||
golang.org/x/sys v0.0.0-20220319134239-a9b59b0215f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.0.0-20220319134239-a9b59b0215f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.0.0-20220728004956-3c1f35247d10/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.0.0-20220728004956-3c1f35247d10/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.8.0 h1:EBmGv8NaZBZTWvrbjNoL6HVt+IVy3QDQpJs7VRIw3tU=
|
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.15.0 h1:h48lPFYpsTvQJZF4EKyI4aLHaev3CxivZmv7yZig9pc=
|
||||||
|
golang.org/x/sys v0.15.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||||
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
||||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||||
@@ -204,9 +213,10 @@ golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
|
|||||||
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||||
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||||
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
|
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
|
||||||
golang.org/x/text v0.10.0 h1:UpjohKhiEgNc0CSauXmwYftY1+LlaC75SJwh0SgCX58=
|
golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ=
|
||||||
golang.org/x/text v0.10.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
|
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
|
||||||
golang.org/x/time v0.0.0-20191024005414-555d28b269f0 h1:/5xXl8Y5W96D+TtHSlonuFqGHIWVuyCkGJLwGh9JJFs=
|
golang.org/x/time v0.0.0-20191024005414-555d28b269f0 h1:/5xXl8Y5W96D+TtHSlonuFqGHIWVuyCkGJLwGh9JJFs=
|
||||||
|
golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||||
golang.org/x/tools v0.0.0-20190729092621-ff9f1409240a/go.mod h1:jcCCGcm9btYwXyDqrUWc6MKQKKGJCWEQ3AfLSRIbEuI=
|
golang.org/x/tools v0.0.0-20190729092621-ff9f1409240a/go.mod h1:jcCCGcm9btYwXyDqrUWc6MKQKKGJCWEQ3AfLSRIbEuI=
|
||||||
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||||
@@ -226,6 +236,8 @@ golang.zx2c4.com/wireguard/wgctrl v0.0.0-20230215201556-9c5414ab4bde/go.mod h1:m
|
|||||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||||
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY=
|
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY=
|
||||||
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||||
|
gopkg.in/ini.v1 v1.67.0 h1:Dgnx+6+nfE+IfzjUEISNeydPJh9AXNNsWbGP9KzCsOA=
|
||||||
|
gopkg.in/ini.v1 v1.67.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
|
||||||
gopkg.in/src-d/go-billy.v4 v4.3.2/go.mod h1:nDjArDMp+XMs1aFAESLRjfGSgfvoYN0hDfzEk0GjC98=
|
gopkg.in/src-d/go-billy.v4 v4.3.2/go.mod h1:nDjArDMp+XMs1aFAESLRjfGSgfvoYN0hDfzEk0GjC98=
|
||||||
gopkg.in/src-d/go-git-fixtures.v3 v3.5.0/go.mod h1:dLBcvytrw/TYZsNTWCnkNF2DSIlzWYqTe3rJR56Ac7g=
|
gopkg.in/src-d/go-git-fixtures.v3 v3.5.0/go.mod h1:dLBcvytrw/TYZsNTWCnkNF2DSIlzWYqTe3rJR56Ac7g=
|
||||||
gopkg.in/src-d/go-git.v4 v4.13.1/go.mod h1:nx5NYcxdKxq5fpltdHnPa2Exj4Sx0EclMWZQbYDu2z8=
|
gopkg.in/src-d/go-git.v4 v4.13.1/go.mod h1:nx5NYcxdKxq5fpltdHnPa2Exj4Sx0EclMWZQbYDu2z8=
|
||||||
@@ -236,6 +248,7 @@ gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C
|
|||||||
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||||
gvisor.dev/gvisor v0.0.0-20221203005347-703fd9b7fbc0 h1:Wobr37noukisGxpKo5jAsLREcpj61RxrWYzD8uwveOY=
|
gvisor.dev/gvisor v0.0.0-20221203005347-703fd9b7fbc0 h1:Wobr37noukisGxpKo5jAsLREcpj61RxrWYzD8uwveOY=
|
||||||
|
gvisor.dev/gvisor v0.0.0-20221203005347-703fd9b7fbc0/go.mod h1:Dn5idtptoW1dIos9U6A2rpebLs/MtTwFacjKb8jLdQA=
|
||||||
inet.af/netaddr v0.0.0-20210511181906-37180328850c/go.mod h1:z0nx+Dh+7N7CC8V5ayHtHGpZpxLQZZxkIaaz6HN65Ls=
|
inet.af/netaddr v0.0.0-20210511181906-37180328850c/go.mod h1:z0nx+Dh+7N7CC8V5ayHtHGpZpxLQZZxkIaaz6HN65Ls=
|
||||||
inet.af/netaddr v0.0.0-20220811202034-502d2d690317 h1:U2fwK6P2EqmopP/hFLTOAjWTki0qgd4GMJn5X8wOleU=
|
inet.af/netaddr v0.0.0-20220811202034-502d2d690317 h1:U2fwK6P2EqmopP/hFLTOAjWTki0qgd4GMJn5X8wOleU=
|
||||||
inet.af/netaddr v0.0.0-20220811202034-502d2d690317/go.mod h1:OIezDfdzOgFhuw4HuWapWq2e9l0H9tK4F1j+ETRtF3k=
|
inet.af/netaddr v0.0.0-20220811202034-502d2d690317/go.mod h1:OIezDfdzOgFhuw4HuWapWq2e9l0H9tK4F1j+ETRtF3k=
|
||||||
|
|||||||
@@ -33,15 +33,20 @@ func addProviderFlag(flagSet *flag.FlagSet, providerToFormat map[string]*bool,
|
|||||||
func (c *CLI) FormatServers(args []string) error {
|
func (c *CLI) FormatServers(args []string) error {
|
||||||
var format, output string
|
var format, output string
|
||||||
allProviders := providers.All()
|
allProviders := providers.All()
|
||||||
|
allProviderFlags := make([]string, len(allProviders))
|
||||||
|
for i, provider := range allProviders {
|
||||||
|
allProviderFlags[i] = strings.ReplaceAll(provider, " ", "-")
|
||||||
|
}
|
||||||
|
|
||||||
providersToFormat := make(map[string]*bool, len(allProviders))
|
providersToFormat := make(map[string]*bool, len(allProviders))
|
||||||
for _, provider := range allProviders {
|
for _, provider := range allProviderFlags {
|
||||||
providersToFormat[provider] = new(bool)
|
providersToFormat[provider] = new(bool)
|
||||||
}
|
}
|
||||||
flagSet := flag.NewFlagSet("markdown", flag.ExitOnError)
|
flagSet := flag.NewFlagSet("format-servers", 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'")
|
||||||
flagSet.StringVar(&output, "output", "/dev/stdout", "Output file to write the formatted data to")
|
flagSet.StringVar(&output, "output", "/dev/stdout", "Output file to write the formatted data to")
|
||||||
titleCaser := cases.Title(language.English)
|
titleCaser := cases.Title(language.English)
|
||||||
for _, provider := range allProviders {
|
for _, provider := range allProviderFlags {
|
||||||
addProviderFlag(flagSet, providersToFormat, provider, titleCaser)
|
addProviderFlag(flagSet, providersToFormat, provider, titleCaser)
|
||||||
}
|
}
|
||||||
if err := flagSet.Parse(args); err != nil {
|
if err := flagSet.Parse(args); err != nil {
|
||||||
@@ -68,7 +73,13 @@ func (c *CLI) FormatServers(args []string) error {
|
|||||||
ErrMultipleProvidersToFormat, len(providers),
|
ErrMultipleProvidersToFormat, len(providers),
|
||||||
strings.Join(providers, ", "))
|
strings.Join(providers, ", "))
|
||||||
}
|
}
|
||||||
providerToFormat := providers[0]
|
|
||||||
|
var providerToFormat string
|
||||||
|
for _, providerToFormat = range allProviders {
|
||||||
|
if strings.ReplaceAll(providerToFormat, " ", "-") == providers[0] {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
logger := newNoopLogger()
|
logger := newNoopLogger()
|
||||||
storage, err := storage.New(logger, constants.ServersData)
|
storage, err := storage.New(logger, constants.ServersData)
|
||||||
|
|||||||
@@ -16,10 +16,14 @@ type DNS struct {
|
|||||||
// DoT server. It cannot be the zero value in the internal
|
// DoT server. It cannot be the zero value in the internal
|
||||||
// state.
|
// state.
|
||||||
ServerAddress netip.Addr
|
ServerAddress netip.Addr
|
||||||
// KeepNameserver is true if the Docker DNS server
|
// KeepNameserver is true if the existing DNS server
|
||||||
// found in /etc/resolv.conf should be kept.
|
// found in /etc/resolv.conf should be used
|
||||||
// Note settings this to true will go around the
|
// Note setting this to true will likely DNS traffic
|
||||||
// DoT server blocking.
|
// outside the VPN tunnel since it would go through
|
||||||
|
// the local DNS server of your Docker/Kubernetes
|
||||||
|
// configuration, which is likely not going through the tunnel.
|
||||||
|
// This will also disable the DNS over TLS server and the
|
||||||
|
// `ServerAddress` field will be ignored.
|
||||||
// It defaults to false and cannot be nil in the
|
// It defaults to false and cannot be nil in the
|
||||||
// internal state.
|
// internal state.
|
||||||
KeepNameserver *bool
|
KeepNameserver *bool
|
||||||
@@ -75,8 +79,11 @@ func (d DNS) String() string {
|
|||||||
|
|
||||||
func (d DNS) toLinesNode() (node *gotree.Node) {
|
func (d DNS) toLinesNode() (node *gotree.Node) {
|
||||||
node = gotree.New("DNS settings:")
|
node = gotree.New("DNS settings:")
|
||||||
node.Appendf("DNS server address to use: %s", d.ServerAddress)
|
|
||||||
node.Appendf("Keep existing nameserver(s): %s", gosettings.BoolToYesNo(d.KeepNameserver))
|
node.Appendf("Keep existing nameserver(s): %s", gosettings.BoolToYesNo(d.KeepNameserver))
|
||||||
|
if *d.KeepNameserver {
|
||||||
|
return node
|
||||||
|
}
|
||||||
|
node.Appendf("DNS server address to use: %s", d.ServerAddress)
|
||||||
node.AppendNode(d.DoT.toLinesNode())
|
node.AppendNode(d.DoT.toLinesNode())
|
||||||
return node
|
return node
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -100,7 +100,7 @@ func (d DoT) toLinesNode() (node *gotree.Node) {
|
|||||||
return node
|
return node
|
||||||
}
|
}
|
||||||
|
|
||||||
update := "disabled"
|
update := "disabled" //nolint:goconst
|
||||||
if *d.UpdatePeriod > 0 {
|
if *d.UpdatePeriod > 0 {
|
||||||
update = "every " + d.UpdatePeriod.String()
|
update = "every " + d.UpdatePeriod.String()
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -34,6 +34,8 @@ var (
|
|||||||
ErrUpdaterPeriodTooSmall = errors.New("VPN server data updater period is too small")
|
ErrUpdaterPeriodTooSmall = errors.New("VPN server data updater period is too small")
|
||||||
ErrVPNProviderNameNotValid = errors.New("VPN provider name is not valid")
|
ErrVPNProviderNameNotValid = errors.New("VPN provider name is not valid")
|
||||||
ErrVPNTypeNotValid = errors.New("VPN type is not valid")
|
ErrVPNTypeNotValid = errors.New("VPN type is not valid")
|
||||||
|
ErrWireguardAllowedIPNotSet = errors.New("allowed IP is not set")
|
||||||
|
ErrWireguardAllowedIPsNotSet = errors.New("allowed IPs is not set")
|
||||||
ErrWireguardEndpointIPNotSet = errors.New("endpoint IP is not set")
|
ErrWireguardEndpointIPNotSet = errors.New("endpoint IP is not set")
|
||||||
ErrWireguardEndpointPortNotAllowed = errors.New("endpoint port is not allowed")
|
ErrWireguardEndpointPortNotAllowed = errors.New("endpoint port is not allowed")
|
||||||
ErrWireguardEndpointPortNotSet = errors.New("endpoint port is not set")
|
ErrWireguardEndpointPortNotSet = errors.New("endpoint port is not set")
|
||||||
|
|||||||
@@ -56,7 +56,7 @@ func (o OpenVPNSelection) validate(vpnProvider string) (err error) {
|
|||||||
if *o.CustomPort != 0 {
|
if *o.CustomPort != 0 {
|
||||||
switch vpnProvider {
|
switch vpnProvider {
|
||||||
// no restriction on port
|
// no restriction on port
|
||||||
case providers.Cyberghost, providers.HideMyAss,
|
case providers.Custom, providers.Cyberghost, providers.HideMyAss,
|
||||||
providers.Privatevpn, providers.Torguard:
|
providers.Privatevpn, providers.Torguard:
|
||||||
// no custom port allowed
|
// no custom port allowed
|
||||||
case providers.Expressvpn, providers.Fastestvpn,
|
case providers.Expressvpn, providers.Fastestvpn,
|
||||||
@@ -99,6 +99,8 @@ func (o OpenVPNSelection) validate(vpnProvider string) (err error) {
|
|||||||
case providers.Windscribe:
|
case providers.Windscribe:
|
||||||
allowedTCP = []uint16{21, 22, 80, 123, 143, 443, 587, 1194, 3306, 8080, 54783}
|
allowedTCP = []uint16{21, 22, 80, 123, 143, 443, 587, 1194, 3306, 8080, 54783}
|
||||||
allowedUDP = []uint16{53, 80, 123, 443, 1194, 54783}
|
allowedUDP = []uint16{53, 80, 123, 443, 1194, 54783}
|
||||||
|
default:
|
||||||
|
panic(fmt.Sprintf("VPN provider %s has no registered allowed ports", vpnProvider))
|
||||||
}
|
}
|
||||||
|
|
||||||
allowedPorts := allowedUDP
|
allowedPorts := allowedUDP
|
||||||
|
|||||||
@@ -15,21 +15,40 @@ type PortForwarding struct {
|
|||||||
// Enabled is true if port forwarding should be activated.
|
// Enabled is true if port forwarding should be activated.
|
||||||
// It cannot be nil for the internal state.
|
// It cannot be nil for the internal state.
|
||||||
Enabled *bool `json:"enabled"`
|
Enabled *bool `json:"enabled"`
|
||||||
|
// Provider is set to specify which custom port forwarding code
|
||||||
|
// should be used. This is especially necessary for the custom
|
||||||
|
// provider using Wireguard for a provider where Wireguard is not
|
||||||
|
// natively supported but custom port forwading code is available.
|
||||||
|
// It defaults to the empty string, meaning the current provider
|
||||||
|
// should be the one used for port forwarding.
|
||||||
|
// It cannot be nil for the internal state.
|
||||||
|
Provider *string `json:"provider"`
|
||||||
// Filepath is the port forwarding status file path
|
// Filepath is the port forwarding status file path
|
||||||
// to use. It can be the empty string to indicate not
|
// to use. It can be the empty string to indicate not
|
||||||
// to write to a file. It cannot be nil for the
|
// to write to a file. It cannot be nil for the
|
||||||
// internal state
|
// internal state
|
||||||
Filepath *string `json:"status_file_path"`
|
Filepath *string `json:"status_file_path"`
|
||||||
|
// ListeningPort is the port traffic would be redirected to from the
|
||||||
|
// forwarded port. The redirection is disabled if it is set to 0, which
|
||||||
|
// is its default as well.
|
||||||
|
ListeningPort *uint16 `json:"listening_port"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p PortForwarding) validate(vpnProvider string) (err error) {
|
func (p PortForwarding) Validate(vpnProvider string) (err error) {
|
||||||
if !*p.Enabled {
|
if !*p.Enabled {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Validate Enabled
|
// Validate current provider or custom provider specified
|
||||||
validProviders := []string{providers.PrivateInternetAccess}
|
providerSelected := vpnProvider
|
||||||
if err = validate.IsOneOf(vpnProvider, validProviders...); err != nil {
|
if *p.Provider != "" {
|
||||||
|
providerSelected = *p.Provider
|
||||||
|
}
|
||||||
|
validProviders := []string{
|
||||||
|
providers.PrivateInternetAccess,
|
||||||
|
providers.Protonvpn,
|
||||||
|
}
|
||||||
|
if err = validate.IsOneOf(providerSelected, validProviders...); err != nil {
|
||||||
return fmt.Errorf("%w: %w", ErrPortForwardingEnabled, err)
|
return fmt.Errorf("%w: %w", ErrPortForwardingEnabled, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -44,26 +63,34 @@ func (p PortForwarding) validate(vpnProvider string) (err error) {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *PortForwarding) copy() (copied PortForwarding) {
|
func (p *PortForwarding) Copy() (copied PortForwarding) {
|
||||||
return PortForwarding{
|
return PortForwarding{
|
||||||
Enabled: gosettings.CopyPointer(p.Enabled),
|
Enabled: gosettings.CopyPointer(p.Enabled),
|
||||||
Filepath: gosettings.CopyPointer(p.Filepath),
|
Provider: gosettings.CopyPointer(p.Provider),
|
||||||
|
Filepath: gosettings.CopyPointer(p.Filepath),
|
||||||
|
ListeningPort: gosettings.CopyPointer(p.ListeningPort),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *PortForwarding) mergeWith(other PortForwarding) {
|
func (p *PortForwarding) mergeWith(other PortForwarding) {
|
||||||
p.Enabled = gosettings.MergeWithPointer(p.Enabled, other.Enabled)
|
p.Enabled = gosettings.MergeWithPointer(p.Enabled, other.Enabled)
|
||||||
|
p.Provider = gosettings.MergeWithPointer(p.Provider, other.Provider)
|
||||||
p.Filepath = gosettings.MergeWithPointer(p.Filepath, other.Filepath)
|
p.Filepath = gosettings.MergeWithPointer(p.Filepath, other.Filepath)
|
||||||
|
p.ListeningPort = gosettings.MergeWithPointer(p.ListeningPort, other.ListeningPort)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *PortForwarding) overrideWith(other PortForwarding) {
|
func (p *PortForwarding) OverrideWith(other PortForwarding) {
|
||||||
p.Enabled = gosettings.OverrideWithPointer(p.Enabled, other.Enabled)
|
p.Enabled = gosettings.OverrideWithPointer(p.Enabled, other.Enabled)
|
||||||
|
p.Provider = gosettings.OverrideWithPointer(p.Provider, other.Provider)
|
||||||
p.Filepath = gosettings.OverrideWithPointer(p.Filepath, other.Filepath)
|
p.Filepath = gosettings.OverrideWithPointer(p.Filepath, other.Filepath)
|
||||||
|
p.ListeningPort = gosettings.OverrideWithPointer(p.ListeningPort, other.ListeningPort)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *PortForwarding) setDefaults() {
|
func (p *PortForwarding) setDefaults() {
|
||||||
p.Enabled = gosettings.DefaultPointer(p.Enabled, false)
|
p.Enabled = gosettings.DefaultPointer(p.Enabled, false)
|
||||||
|
p.Provider = gosettings.DefaultPointer(p.Provider, "")
|
||||||
p.Filepath = gosettings.DefaultPointer(p.Filepath, "/tmp/gluetun/forwarded_port")
|
p.Filepath = gosettings.DefaultPointer(p.Filepath, "/tmp/gluetun/forwarded_port")
|
||||||
|
p.ListeningPort = gosettings.DefaultPointer(p.ListeningPort, 0)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p PortForwarding) String() string {
|
func (p PortForwarding) String() string {
|
||||||
@@ -76,7 +103,18 @@ func (p PortForwarding) toLinesNode() (node *gotree.Node) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
node = gotree.New("Automatic port forwarding settings:")
|
node = gotree.New("Automatic port forwarding settings:")
|
||||||
node.Appendf("Enabled: yes")
|
|
||||||
|
listeningPort := "disabled"
|
||||||
|
if *p.ListeningPort != 0 {
|
||||||
|
listeningPort = fmt.Sprintf("%d", *p.ListeningPort)
|
||||||
|
}
|
||||||
|
node.Appendf("Redirection listening port: %s", listeningPort)
|
||||||
|
|
||||||
|
if *p.Provider == "" {
|
||||||
|
node.Appendf("Use port forwarding code for current provider")
|
||||||
|
} else {
|
||||||
|
node.Appendf("Use code for provider: %s", *p.Provider)
|
||||||
|
}
|
||||||
|
|
||||||
filepath := *p.Filepath
|
filepath := *p.Filepath
|
||||||
if filepath == "" {
|
if filepath == "" {
|
||||||
|
|||||||
@@ -49,7 +49,7 @@ func (p *Provider) validate(vpnType string, storage Storage) (err error) {
|
|||||||
return fmt.Errorf("server selection: %w", err)
|
return fmt.Errorf("server selection: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
err = p.PortForwarding.validate(*p.Name)
|
err = p.PortForwarding.Validate(*p.Name)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("port forwarding: %w", err)
|
return fmt.Errorf("port forwarding: %w", err)
|
||||||
}
|
}
|
||||||
@@ -61,7 +61,7 @@ func (p *Provider) copy() (copied Provider) {
|
|||||||
return Provider{
|
return Provider{
|
||||||
Name: gosettings.CopyPointer(p.Name),
|
Name: gosettings.CopyPointer(p.Name),
|
||||||
ServerSelection: p.ServerSelection.copy(),
|
ServerSelection: p.ServerSelection.copy(),
|
||||||
PortForwarding: p.PortForwarding.copy(),
|
PortForwarding: p.PortForwarding.Copy(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -74,7 +74,7 @@ func (p *Provider) mergeWith(other Provider) {
|
|||||||
func (p *Provider) overrideWith(other Provider) {
|
func (p *Provider) overrideWith(other Provider) {
|
||||||
p.Name = gosettings.OverrideWithPointer(p.Name, other.Name)
|
p.Name = gosettings.OverrideWithPointer(p.Name, other.Name)
|
||||||
p.ServerSelection.overrideWith(other.ServerSelection)
|
p.ServerSelection.overrideWith(other.ServerSelection)
|
||||||
p.PortForwarding.overrideWith(other.PortForwarding)
|
p.PortForwarding.OverrideWith(other.PortForwarding)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *Provider) setDefaults() {
|
func (p *Provider) setDefaults() {
|
||||||
|
|||||||
@@ -23,6 +23,20 @@ type PublicIP struct {
|
|||||||
IPFilepath *string
|
IPFilepath *string
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// UpdateWith deep copies the receiving settings, overrides the copy with
|
||||||
|
// fields set in the partialUpdate argument, validates the new settings
|
||||||
|
// and returns them if they are valid, or returns an error otherwise.
|
||||||
|
// In all cases, the receiving settings are unmodified.
|
||||||
|
func (p PublicIP) UpdateWith(partialUpdate PublicIP) (updatedSettings PublicIP, err error) {
|
||||||
|
updatedSettings = p.copy()
|
||||||
|
updatedSettings.overrideWith(partialUpdate)
|
||||||
|
err = updatedSettings.validate()
|
||||||
|
if err != nil {
|
||||||
|
return updatedSettings, fmt.Errorf("validating updated settings: %w", err)
|
||||||
|
}
|
||||||
|
return updatedSettings, nil
|
||||||
|
}
|
||||||
|
|
||||||
func (p PublicIP) validate() (err error) {
|
func (p PublicIP) validate() (err error) {
|
||||||
const minPeriod = 5 * time.Second
|
const minPeriod = 5 * time.Second
|
||||||
if *p.Period < minPeriod {
|
if *p.Period < minPeriod {
|
||||||
|
|||||||
@@ -38,8 +38,8 @@ func Test_Settings_String(t *testing.T) {
|
|||||||
| ├── Run OpenVPN as: root
|
| ├── Run OpenVPN as: root
|
||||||
| └── Verbosity level: 1
|
| └── Verbosity level: 1
|
||||||
├── DNS settings:
|
├── DNS settings:
|
||||||
| ├── DNS server address to use: 127.0.0.1
|
|
||||||
| ├── Keep existing nameserver(s): no
|
| ├── Keep existing nameserver(s): no
|
||||||
|
| ├── DNS server address to use: 127.0.0.1
|
||||||
| └── DNS over TLS settings:
|
| └── DNS over TLS settings:
|
||||||
| ├── Enabled: yes
|
| ├── Enabled: yes
|
||||||
| ├── Update period: every 24h0m0s
|
| ├── Update period: every 24h0m0s
|
||||||
|
|||||||
@@ -25,6 +25,10 @@ type Wireguard struct {
|
|||||||
PreSharedKey *string `json:"pre_shared_key"`
|
PreSharedKey *string `json:"pre_shared_key"`
|
||||||
// Addresses are the Wireguard interface addresses.
|
// Addresses are the Wireguard interface addresses.
|
||||||
Addresses []netip.Prefix `json:"addresses"`
|
Addresses []netip.Prefix `json:"addresses"`
|
||||||
|
// AllowedIPs are the Wireguard allowed IPs.
|
||||||
|
// If left unset, they default to "0.0.0.0/0"
|
||||||
|
// and, if IPv6 is supported, "::0".
|
||||||
|
AllowedIPs []netip.Prefix `json:"allowed_ips"`
|
||||||
// Interface is the name of the Wireguard interface
|
// Interface is the name of the Wireguard interface
|
||||||
// to create. It cannot be the empty string in the
|
// to create. It cannot be the empty string in the
|
||||||
// internal state.
|
// internal state.
|
||||||
@@ -66,7 +70,12 @@ func (w Wireguard) validate(vpnProvider string, ipv6Supported bool) (err error)
|
|||||||
}
|
}
|
||||||
_, err = wgtypes.ParseKey(*w.PrivateKey)
|
_, err = wgtypes.ParseKey(*w.PrivateKey)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("private key is not valid: %w", err)
|
err = fmt.Errorf("private key is not valid: %w", err)
|
||||||
|
if vpnProvider == providers.Nordvpn &&
|
||||||
|
err.Error() == "wgtypes: incorrect key size: 48" {
|
||||||
|
err = fmt.Errorf("%w - you might be using your access token instead of the Wireguard private key", err)
|
||||||
|
}
|
||||||
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
if vpnProvider == providers.Airvpn {
|
if vpnProvider == providers.Airvpn {
|
||||||
@@ -89,13 +98,26 @@ func (w Wireguard) validate(vpnProvider string, ipv6Supported bool) (err error)
|
|||||||
}
|
}
|
||||||
for i, ipNet := range w.Addresses {
|
for i, ipNet := range w.Addresses {
|
||||||
if !ipNet.IsValid() {
|
if !ipNet.IsValid() {
|
||||||
return fmt.Errorf("%w: for address at index %d: %s",
|
return fmt.Errorf("%w: for address at index %d",
|
||||||
ErrWireguardInterfaceAddressNotSet, i, ipNet.String())
|
ErrWireguardInterfaceAddressNotSet, i)
|
||||||
}
|
}
|
||||||
|
|
||||||
if !ipv6Supported && ipNet.Addr().Is6() {
|
if !ipv6Supported && ipNet.Addr().Is6() {
|
||||||
return fmt.Errorf("%w: address %s",
|
return fmt.Errorf("%w: address %s",
|
||||||
ErrWireguardInterfaceAddressIPv6, ipNet)
|
ErrWireguardInterfaceAddressIPv6, ipNet.String())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Validate AllowedIPs
|
||||||
|
// WARNING: do not check for IPv6 networks in the allowed IPs,
|
||||||
|
// the wireguard code will take care to ignore it.
|
||||||
|
if len(w.AllowedIPs) == 0 {
|
||||||
|
return fmt.Errorf("%w", ErrWireguardAllowedIPsNotSet)
|
||||||
|
}
|
||||||
|
for i, allowedIP := range w.AllowedIPs {
|
||||||
|
if !allowedIP.IsValid() {
|
||||||
|
return fmt.Errorf("%w: for allowed ip %d of %d",
|
||||||
|
ErrWireguardAllowedIPNotSet, i+1, len(w.AllowedIPs))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -118,6 +140,7 @@ func (w *Wireguard) copy() (copied Wireguard) {
|
|||||||
PrivateKey: gosettings.CopyPointer(w.PrivateKey),
|
PrivateKey: gosettings.CopyPointer(w.PrivateKey),
|
||||||
PreSharedKey: gosettings.CopyPointer(w.PreSharedKey),
|
PreSharedKey: gosettings.CopyPointer(w.PreSharedKey),
|
||||||
Addresses: gosettings.CopySlice(w.Addresses),
|
Addresses: gosettings.CopySlice(w.Addresses),
|
||||||
|
AllowedIPs: gosettings.CopySlice(w.AllowedIPs),
|
||||||
Interface: w.Interface,
|
Interface: w.Interface,
|
||||||
MTU: w.MTU,
|
MTU: w.MTU,
|
||||||
Implementation: w.Implementation,
|
Implementation: w.Implementation,
|
||||||
@@ -128,6 +151,7 @@ func (w *Wireguard) mergeWith(other Wireguard) {
|
|||||||
w.PrivateKey = gosettings.MergeWithPointer(w.PrivateKey, other.PrivateKey)
|
w.PrivateKey = gosettings.MergeWithPointer(w.PrivateKey, other.PrivateKey)
|
||||||
w.PreSharedKey = gosettings.MergeWithPointer(w.PreSharedKey, other.PreSharedKey)
|
w.PreSharedKey = gosettings.MergeWithPointer(w.PreSharedKey, other.PreSharedKey)
|
||||||
w.Addresses = gosettings.MergeWithSlice(w.Addresses, other.Addresses)
|
w.Addresses = gosettings.MergeWithSlice(w.Addresses, other.Addresses)
|
||||||
|
w.AllowedIPs = gosettings.MergeWithSlice(w.AllowedIPs, other.AllowedIPs)
|
||||||
w.Interface = gosettings.MergeWithString(w.Interface, other.Interface)
|
w.Interface = gosettings.MergeWithString(w.Interface, other.Interface)
|
||||||
w.MTU = gosettings.MergeWithNumber(w.MTU, other.MTU)
|
w.MTU = gosettings.MergeWithNumber(w.MTU, other.MTU)
|
||||||
w.Implementation = gosettings.MergeWithString(w.Implementation, other.Implementation)
|
w.Implementation = gosettings.MergeWithString(w.Implementation, other.Implementation)
|
||||||
@@ -137,6 +161,7 @@ func (w *Wireguard) overrideWith(other Wireguard) {
|
|||||||
w.PrivateKey = gosettings.OverrideWithPointer(w.PrivateKey, other.PrivateKey)
|
w.PrivateKey = gosettings.OverrideWithPointer(w.PrivateKey, other.PrivateKey)
|
||||||
w.PreSharedKey = gosettings.OverrideWithPointer(w.PreSharedKey, other.PreSharedKey)
|
w.PreSharedKey = gosettings.OverrideWithPointer(w.PreSharedKey, other.PreSharedKey)
|
||||||
w.Addresses = gosettings.OverrideWithSlice(w.Addresses, other.Addresses)
|
w.Addresses = gosettings.OverrideWithSlice(w.Addresses, other.Addresses)
|
||||||
|
w.AllowedIPs = gosettings.OverrideWithSlice(w.AllowedIPs, other.AllowedIPs)
|
||||||
w.Interface = gosettings.OverrideWithString(w.Interface, other.Interface)
|
w.Interface = gosettings.OverrideWithString(w.Interface, other.Interface)
|
||||||
w.MTU = gosettings.OverrideWithNumber(w.MTU, other.MTU)
|
w.MTU = gosettings.OverrideWithNumber(w.MTU, other.MTU)
|
||||||
w.Implementation = gosettings.OverrideWithString(w.Implementation, other.Implementation)
|
w.Implementation = gosettings.OverrideWithString(w.Implementation, other.Implementation)
|
||||||
@@ -150,6 +175,11 @@ func (w *Wireguard) setDefaults(vpnProvider string) {
|
|||||||
defaultNordVPNPrefix := netip.PrefixFrom(defaultNordVPNAddress, defaultNordVPNAddress.BitLen())
|
defaultNordVPNPrefix := netip.PrefixFrom(defaultNordVPNAddress, defaultNordVPNAddress.BitLen())
|
||||||
w.Addresses = gosettings.DefaultSlice(w.Addresses, []netip.Prefix{defaultNordVPNPrefix})
|
w.Addresses = gosettings.DefaultSlice(w.Addresses, []netip.Prefix{defaultNordVPNPrefix})
|
||||||
}
|
}
|
||||||
|
defaultAllowedIPs := []netip.Prefix{
|
||||||
|
netip.PrefixFrom(netip.IPv4Unspecified(), 0),
|
||||||
|
netip.PrefixFrom(netip.IPv6Unspecified(), 0),
|
||||||
|
}
|
||||||
|
w.AllowedIPs = gosettings.DefaultSlice(w.AllowedIPs, defaultAllowedIPs)
|
||||||
w.Interface = gosettings.DefaultString(w.Interface, "wg0")
|
w.Interface = gosettings.DefaultString(w.Interface, "wg0")
|
||||||
const defaultMTU = 1400
|
const defaultMTU = 1400
|
||||||
w.MTU = gosettings.DefaultNumber(w.MTU, defaultMTU)
|
w.MTU = gosettings.DefaultNumber(w.MTU, defaultMTU)
|
||||||
@@ -178,6 +208,11 @@ func (w Wireguard) toLinesNode() (node *gotree.Node) {
|
|||||||
addressesNode.Appendf(address.String())
|
addressesNode.Appendf(address.String())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
allowedIPsNode := node.Appendf("Allowed IPs:")
|
||||||
|
for _, allowedIP := range w.AllowedIPs {
|
||||||
|
allowedIPsNode.Appendf(allowedIP.String())
|
||||||
|
}
|
||||||
|
|
||||||
interfaceNode := node.Appendf("Network interface: %s", w.Interface)
|
interfaceNode := node.Appendf("Network interface: %s", w.Interface)
|
||||||
interfaceNode.Appendf("MTU: %d", w.MTU)
|
interfaceNode.Appendf("MTU: %d", w.MTU)
|
||||||
|
|
||||||
|
|||||||
@@ -16,7 +16,7 @@ type WireguardSelection struct {
|
|||||||
// It is only used with VPN providers generating Wireguard
|
// It is only used with VPN providers generating Wireguard
|
||||||
// configurations specific to each server and user.
|
// configurations specific to each server and user.
|
||||||
// To indicate it should not be used, it should be set
|
// To indicate it should not be used, it should be set
|
||||||
// to netaddr.IPv4Unspecified(). It can never be the zero value
|
// to netip.IPv4Unspecified(). It can never be the zero value
|
||||||
// in the internal state.
|
// in the internal state.
|
||||||
EndpointIP netip.Addr `json:"endpoint_ip"`
|
EndpointIP netip.Addr `json:"endpoint_ip"`
|
||||||
// EndpointPort is a the server port to use for the VPN server.
|
// EndpointPort is a the server port to use for the VPN server.
|
||||||
|
|||||||
@@ -16,6 +16,8 @@ func (s *Source) readPortForward() (
|
|||||||
return portForwarding, err
|
return portForwarding, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
portForwarding.Provider = s.env.Get("VPN_PORT_FORWARDING_PROVIDER")
|
||||||
|
|
||||||
portForwarding.Filepath = s.env.Get("VPN_PORT_FORWARDING_STATUS_FILE",
|
portForwarding.Filepath = s.env.Get("VPN_PORT_FORWARDING_STATUS_FILE",
|
||||||
env.ForceLowercase(false),
|
env.ForceLowercase(false),
|
||||||
env.RetroKeys(
|
env.RetroKeys(
|
||||||
@@ -23,5 +25,10 @@ func (s *Source) readPortForward() (
|
|||||||
"PRIVATE_INTERNET_ACCESS_VPN_PORT_FORWARDING_STATUS_FILE",
|
"PRIVATE_INTERNET_ACCESS_VPN_PORT_FORWARDING_STATUS_FILE",
|
||||||
))
|
))
|
||||||
|
|
||||||
|
portForwarding.ListeningPort, err = s.env.Uint16Ptr("VPN_PORT_FORWARDING_LISTENING_PORT")
|
||||||
|
if err != nil {
|
||||||
|
return portForwarding, err
|
||||||
|
}
|
||||||
|
|
||||||
return portForwarding, nil
|
return portForwarding, nil
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -19,6 +19,10 @@ func (s *Source) readWireguard() (wireguard settings.Wireguard, err error) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return wireguard, err // already wrapped
|
return wireguard, err // already wrapped
|
||||||
}
|
}
|
||||||
|
wireguard.AllowedIPs, err = s.env.CSVNetipPrefixes("WIREGUARD_ALLOWED_IPS")
|
||||||
|
if err != nil {
|
||||||
|
return wireguard, err // already wrapped
|
||||||
|
}
|
||||||
mtuPtr, err := s.env.Uint16Ptr("WIREGUARD_MTU")
|
mtuPtr, err := s.env.Uint16Ptr("WIREGUARD_MTU")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return wireguard, err
|
return wireguard, err
|
||||||
|
|||||||
3
internal/configuration/sources/files/helpers_test.go
Normal file
3
internal/configuration/sources/files/helpers_test.go
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
package files
|
||||||
|
|
||||||
|
func ptrTo[T any](x T) *T { return &x }
|
||||||
16
internal/configuration/sources/files/provider.go
Normal file
16
internal/configuration/sources/files/provider.go
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
package files
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"github.com/qdm12/gluetun/internal/configuration/settings"
|
||||||
|
)
|
||||||
|
|
||||||
|
func (s *Source) readProvider() (provider settings.Provider, err error) {
|
||||||
|
provider.ServerSelection, err = s.readServerSelection()
|
||||||
|
if err != nil {
|
||||||
|
return provider, fmt.Errorf("server selection: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return provider, nil
|
||||||
|
}
|
||||||
@@ -4,10 +4,15 @@ import (
|
|||||||
"github.com/qdm12/gluetun/internal/configuration/settings"
|
"github.com/qdm12/gluetun/internal/configuration/settings"
|
||||||
)
|
)
|
||||||
|
|
||||||
type Source struct{}
|
type Source struct {
|
||||||
|
wireguardConfigPath string
|
||||||
|
}
|
||||||
|
|
||||||
func New() *Source {
|
func New() *Source {
|
||||||
return &Source{}
|
const wireguardConfigPath = "/gluetun/wireguard/wg0.conf"
|
||||||
|
return &Source{
|
||||||
|
wireguardConfigPath: wireguardConfigPath,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Source) String() string { return "files" }
|
func (s *Source) String() string { return "files" }
|
||||||
|
|||||||
16
internal/configuration/sources/files/serverselection.go
Normal file
16
internal/configuration/sources/files/serverselection.go
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
package files
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"github.com/qdm12/gluetun/internal/configuration/settings"
|
||||||
|
)
|
||||||
|
|
||||||
|
func (s *Source) readServerSelection() (selection settings.ServerSelection, err error) {
|
||||||
|
selection.Wireguard, err = s.readWireguardSelection()
|
||||||
|
if err != nil {
|
||||||
|
return selection, fmt.Errorf("wireguard: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return selection, nil
|
||||||
|
}
|
||||||
@@ -7,10 +7,20 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
func (s *Source) readVPN() (vpn settings.VPN, err error) {
|
func (s *Source) readVPN() (vpn settings.VPN, err error) {
|
||||||
|
vpn.Provider, err = s.readProvider()
|
||||||
|
if err != nil {
|
||||||
|
return vpn, fmt.Errorf("provider: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
vpn.OpenVPN, err = s.readOpenVPN()
|
vpn.OpenVPN, err = s.readOpenVPN()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return vpn, fmt.Errorf("OpenVPN: %w", err)
|
return vpn, fmt.Errorf("OpenVPN: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
vpn.Wireguard, err = s.readWireguard()
|
||||||
|
if err != nil {
|
||||||
|
return vpn, fmt.Errorf("wireguard: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
return vpn, nil
|
return vpn, nil
|
||||||
}
|
}
|
||||||
|
|||||||
120
internal/configuration/sources/files/wireguard.go
Normal file
120
internal/configuration/sources/files/wireguard.go
Normal file
@@ -0,0 +1,120 @@
|
|||||||
|
package files
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"net/netip"
|
||||||
|
"regexp"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/qdm12/gluetun/internal/configuration/settings"
|
||||||
|
"golang.zx2c4.com/wireguard/wgctrl/wgtypes"
|
||||||
|
"gopkg.in/ini.v1"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
regexINISectionNotExist = regexp.MustCompile(`^section ".+" does not exist$`)
|
||||||
|
regexINIKeyNotExist = regexp.MustCompile(`key ".*" not exists$`)
|
||||||
|
)
|
||||||
|
|
||||||
|
func (s *Source) readWireguard() (wireguard settings.Wireguard, err error) {
|
||||||
|
fileStringPtr, err := ReadFromFile(s.wireguardConfigPath)
|
||||||
|
if err != nil {
|
||||||
|
return wireguard, fmt.Errorf("reading file: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if fileStringPtr == nil {
|
||||||
|
return wireguard, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
rawData := []byte(*fileStringPtr)
|
||||||
|
iniFile, err := ini.Load(rawData)
|
||||||
|
if err != nil {
|
||||||
|
return wireguard, fmt.Errorf("loading ini from reader: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
interfaceSection, err := iniFile.GetSection("Interface")
|
||||||
|
if err == nil {
|
||||||
|
err = parseWireguardInterfaceSection(interfaceSection, &wireguard)
|
||||||
|
if err != nil {
|
||||||
|
return wireguard, fmt.Errorf("parsing interface section: %w", err)
|
||||||
|
}
|
||||||
|
} else if !regexINISectionNotExist.MatchString(err.Error()) {
|
||||||
|
// can never happen
|
||||||
|
return wireguard, fmt.Errorf("getting interface section: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
peerSection, err := iniFile.GetSection("Peer")
|
||||||
|
if err == nil {
|
||||||
|
wireguard.PreSharedKey, err = parseINIWireguardKey(peerSection, "PresharedKey")
|
||||||
|
if err != nil {
|
||||||
|
return wireguard, fmt.Errorf("parsing peer section: %w", err)
|
||||||
|
}
|
||||||
|
} else if !regexINISectionNotExist.MatchString(err.Error()) {
|
||||||
|
// can never happen
|
||||||
|
return wireguard, fmt.Errorf("getting peer section: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return wireguard, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func parseWireguardInterfaceSection(interfaceSection *ini.Section,
|
||||||
|
wireguard *settings.Wireguard) (err error) {
|
||||||
|
wireguard.PrivateKey, err = parseINIWireguardKey(interfaceSection, "PrivateKey")
|
||||||
|
if err != nil {
|
||||||
|
return err // error is already wrapped correctly
|
||||||
|
}
|
||||||
|
|
||||||
|
wireguard.Addresses, err = parseINIWireguardAddress(interfaceSection)
|
||||||
|
if err != nil {
|
||||||
|
return err // error is already wrapped correctly
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func parseINIWireguardKey(section *ini.Section, keyName string) (
|
||||||
|
key *string, err error) {
|
||||||
|
iniKey, err := section.GetKey(keyName)
|
||||||
|
if err != nil {
|
||||||
|
if regexINIKeyNotExist.MatchString(err.Error()) {
|
||||||
|
return nil, nil //nolint:nilnil
|
||||||
|
}
|
||||||
|
// can never happen
|
||||||
|
return nil, fmt.Errorf("getting %s key: %w", keyName, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
key = new(string)
|
||||||
|
*key = iniKey.String()
|
||||||
|
_, err = wgtypes.ParseKey(*key)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("parsing %s: %s: %w", keyName, *key, err)
|
||||||
|
}
|
||||||
|
return key, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func parseINIWireguardAddress(section *ini.Section) (
|
||||||
|
addresses []netip.Prefix, err error) {
|
||||||
|
addressKey, err := section.GetKey("Address")
|
||||||
|
if err != nil {
|
||||||
|
if regexINIKeyNotExist.MatchString(err.Error()) {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
// can never happen
|
||||||
|
return nil, fmt.Errorf("getting Address key: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
addressStrings := strings.Split(addressKey.String(), ",")
|
||||||
|
addresses = make([]netip.Prefix, len(addressStrings))
|
||||||
|
for i, addressString := range addressStrings {
|
||||||
|
addressString = strings.TrimSpace(addressString)
|
||||||
|
if !strings.ContainsRune(addressString, '/') {
|
||||||
|
addressString += "/32"
|
||||||
|
}
|
||||||
|
addresses[i], err = netip.ParsePrefix(addressString)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("parsing address: %w", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return addresses, nil
|
||||||
|
}
|
||||||
263
internal/configuration/sources/files/wireguard_test.go
Normal file
263
internal/configuration/sources/files/wireguard_test.go
Normal file
@@ -0,0 +1,263 @@
|
|||||||
|
package files
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/netip"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/qdm12/gluetun/internal/configuration/settings"
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
"gopkg.in/ini.v1"
|
||||||
|
)
|
||||||
|
|
||||||
|
func Test_Source_readWireguard(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
t.Run("fail reading from file", func(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
dirPath := t.TempDir()
|
||||||
|
source := &Source{
|
||||||
|
wireguardConfigPath: dirPath,
|
||||||
|
}
|
||||||
|
wireguard, err := source.readWireguard()
|
||||||
|
assert.Equal(t, settings.Wireguard{}, wireguard)
|
||||||
|
assert.Error(t, err)
|
||||||
|
assert.Regexp(t, `reading file: read .+: is a directory`, err.Error())
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("no file", func(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
noFile := filepath.Join(t.TempDir(), "doesnotexist")
|
||||||
|
source := &Source{
|
||||||
|
wireguardConfigPath: noFile,
|
||||||
|
}
|
||||||
|
wireguard, err := source.readWireguard()
|
||||||
|
assert.Equal(t, settings.Wireguard{}, wireguard)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
})
|
||||||
|
|
||||||
|
testCases := map[string]struct {
|
||||||
|
fileContent string
|
||||||
|
wireguard settings.Wireguard
|
||||||
|
errMessage string
|
||||||
|
}{
|
||||||
|
"ini load error": {
|
||||||
|
fileContent: "invalid",
|
||||||
|
errMessage: "loading ini from reader: key-value delimiter not found: invalid",
|
||||||
|
},
|
||||||
|
"empty file": {},
|
||||||
|
"interface section parsing error": {
|
||||||
|
fileContent: `
|
||||||
|
[Interface]
|
||||||
|
PrivateKey = x
|
||||||
|
`,
|
||||||
|
errMessage: "parsing interface section: parsing PrivateKey: " +
|
||||||
|
"x: wgtypes: failed to parse base64-encoded key: " +
|
||||||
|
"illegal base64 data at input byte 0",
|
||||||
|
},
|
||||||
|
"success": {
|
||||||
|
fileContent: `
|
||||||
|
[Interface]
|
||||||
|
PrivateKey = QOlCgyA/Sn/c/+YNTIEohrjm8IZV+OZ2AUFIoX20sk8=
|
||||||
|
Address = 10.38.22.35/32
|
||||||
|
DNS = 193.138.218.74
|
||||||
|
|
||||||
|
[Peer]
|
||||||
|
PresharedKey = YJ680VN+dGrdsWNjSFqZ6vvwuiNhbq502ZL3G7Q3o3g=
|
||||||
|
`,
|
||||||
|
wireguard: settings.Wireguard{
|
||||||
|
PrivateKey: ptrTo("QOlCgyA/Sn/c/+YNTIEohrjm8IZV+OZ2AUFIoX20sk8="),
|
||||||
|
PreSharedKey: ptrTo("YJ680VN+dGrdsWNjSFqZ6vvwuiNhbq502ZL3G7Q3o3g="),
|
||||||
|
Addresses: []netip.Prefix{
|
||||||
|
netip.PrefixFrom(netip.AddrFrom4([4]byte{10, 38, 22, 35}), 32),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for testName, testCase := range testCases {
|
||||||
|
testCase := testCase
|
||||||
|
t.Run(testName, func(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
configFile := filepath.Join(t.TempDir(), "wg.conf")
|
||||||
|
err := os.WriteFile(configFile, []byte(testCase.fileContent), 0600)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
source := &Source{
|
||||||
|
wireguardConfigPath: configFile,
|
||||||
|
}
|
||||||
|
|
||||||
|
wireguard, err := source.readWireguard()
|
||||||
|
|
||||||
|
assert.Equal(t, testCase.wireguard, wireguard)
|
||||||
|
if testCase.errMessage != "" {
|
||||||
|
assert.EqualError(t, err, testCase.errMessage)
|
||||||
|
} else {
|
||||||
|
assert.NoError(t, err)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func Test_parseWireguardInterfaceSection(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
testCases := map[string]struct {
|
||||||
|
iniData string
|
||||||
|
wireguard settings.Wireguard
|
||||||
|
errMessage string
|
||||||
|
}{
|
||||||
|
"private key error": {
|
||||||
|
iniData: `[Interface]
|
||||||
|
PrivateKey = x`,
|
||||||
|
errMessage: "parsing PrivateKey: x: " +
|
||||||
|
"wgtypes: failed to parse base64-encoded key: " +
|
||||||
|
"illegal base64 data at input byte 0",
|
||||||
|
},
|
||||||
|
"address error": {
|
||||||
|
iniData: `[Interface]
|
||||||
|
Address = x
|
||||||
|
`,
|
||||||
|
errMessage: "parsing address: netip.ParsePrefix(\"x/32\"): ParseAddr(\"x\"): unable to parse IP",
|
||||||
|
},
|
||||||
|
"success": {
|
||||||
|
iniData: `
|
||||||
|
[Interface]
|
||||||
|
PrivateKey = QOlCgyA/Sn/c/+YNTIEohrjm8IZV+OZ2AUFIoX20sk8=
|
||||||
|
Address = 10.38.22.35/32
|
||||||
|
`,
|
||||||
|
wireguard: settings.Wireguard{
|
||||||
|
PrivateKey: ptrTo("QOlCgyA/Sn/c/+YNTIEohrjm8IZV+OZ2AUFIoX20sk8="),
|
||||||
|
Addresses: []netip.Prefix{
|
||||||
|
netip.PrefixFrom(netip.AddrFrom4([4]byte{10, 38, 22, 35}), 32),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for testName, testCase := range testCases {
|
||||||
|
testCase := testCase
|
||||||
|
t.Run(testName, func(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
iniFile, err := ini.Load([]byte(testCase.iniData))
|
||||||
|
require.NoError(t, err)
|
||||||
|
iniSection, err := iniFile.GetSection("Interface")
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
var wireguard settings.Wireguard
|
||||||
|
err = parseWireguardInterfaceSection(iniSection, &wireguard)
|
||||||
|
|
||||||
|
assert.Equal(t, testCase.wireguard, wireguard)
|
||||||
|
if testCase.errMessage != "" {
|
||||||
|
assert.EqualError(t, err, testCase.errMessage)
|
||||||
|
} else {
|
||||||
|
assert.NoError(t, err)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func Test_parseINIWireguardKey(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
testCases := map[string]struct {
|
||||||
|
fileContent string
|
||||||
|
keyName string
|
||||||
|
key *string
|
||||||
|
errMessage string
|
||||||
|
}{
|
||||||
|
"key does not exist": {
|
||||||
|
fileContent: `[Interface]`,
|
||||||
|
keyName: "PrivateKey",
|
||||||
|
},
|
||||||
|
"bad Wireguard key": {
|
||||||
|
fileContent: `[Interface]
|
||||||
|
PrivateKey = x`,
|
||||||
|
keyName: "PrivateKey",
|
||||||
|
errMessage: "parsing PrivateKey: x: " +
|
||||||
|
"wgtypes: failed to parse base64-encoded key: " +
|
||||||
|
"illegal base64 data at input byte 0",
|
||||||
|
},
|
||||||
|
"success": {
|
||||||
|
fileContent: `[Interface]
|
||||||
|
PrivateKey = QOlCgyA/Sn/c/+YNTIEohrjm8IZV+OZ2AUFIoX20sk8=`,
|
||||||
|
keyName: "PrivateKey",
|
||||||
|
key: ptrTo("QOlCgyA/Sn/c/+YNTIEohrjm8IZV+OZ2AUFIoX20sk8="),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for testName, testCase := range testCases {
|
||||||
|
testCase := testCase
|
||||||
|
t.Run(testName, func(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
iniFile, err := ini.Load([]byte(testCase.fileContent))
|
||||||
|
require.NoError(t, err)
|
||||||
|
iniSection, err := iniFile.GetSection("Interface")
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
key, err := parseINIWireguardKey(iniSection, testCase.keyName)
|
||||||
|
|
||||||
|
assert.Equal(t, testCase.key, key)
|
||||||
|
if testCase.errMessage != "" {
|
||||||
|
assert.EqualError(t, err, testCase.errMessage)
|
||||||
|
} else {
|
||||||
|
assert.NoError(t, err)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func Test_parseINIWireguardAddress(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
testCases := map[string]struct {
|
||||||
|
fileContent string
|
||||||
|
addresses []netip.Prefix
|
||||||
|
errMessage string
|
||||||
|
}{
|
||||||
|
"key does not exist": {
|
||||||
|
fileContent: `[Interface]`,
|
||||||
|
},
|
||||||
|
"bad address": {
|
||||||
|
fileContent: `[Interface]
|
||||||
|
Address = x`,
|
||||||
|
errMessage: "parsing address: netip.ParsePrefix(\"x/32\"): ParseAddr(\"x\"): unable to parse IP",
|
||||||
|
},
|
||||||
|
"success": {
|
||||||
|
fileContent: `[Interface]
|
||||||
|
Address = 1.2.3.4/32, 5.6.7.8/32`,
|
||||||
|
addresses: []netip.Prefix{
|
||||||
|
netip.PrefixFrom(netip.AddrFrom4([4]byte{1, 2, 3, 4}), 32),
|
||||||
|
netip.PrefixFrom(netip.AddrFrom4([4]byte{5, 6, 7, 8}), 32),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for testName, testCase := range testCases {
|
||||||
|
testCase := testCase
|
||||||
|
t.Run(testName, func(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
iniFile, err := ini.Load([]byte(testCase.fileContent))
|
||||||
|
require.NoError(t, err)
|
||||||
|
iniSection, err := iniFile.GetSection("Interface")
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
addresses, err := parseINIWireguardAddress(iniSection)
|
||||||
|
|
||||||
|
assert.Equal(t, testCase.addresses, addresses)
|
||||||
|
if testCase.errMessage != "" {
|
||||||
|
assert.EqualError(t, err, testCase.errMessage)
|
||||||
|
} else {
|
||||||
|
assert.NoError(t, err)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
83
internal/configuration/sources/files/wireguardselection.go
Normal file
83
internal/configuration/sources/files/wireguardselection.go
Normal file
@@ -0,0 +1,83 @@
|
|||||||
|
package files
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"net"
|
||||||
|
"net/netip"
|
||||||
|
|
||||||
|
"github.com/qdm12/gluetun/internal/configuration/settings"
|
||||||
|
"github.com/qdm12/govalid/port"
|
||||||
|
"gopkg.in/ini.v1"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
ErrEndpointHostNotIP = errors.New("endpoint host is not an IP")
|
||||||
|
)
|
||||||
|
|
||||||
|
func (s *Source) readWireguardSelection() (selection settings.WireguardSelection, err error) {
|
||||||
|
fileStringPtr, err := ReadFromFile(s.wireguardConfigPath)
|
||||||
|
if err != nil {
|
||||||
|
return selection, fmt.Errorf("reading file: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if fileStringPtr == nil {
|
||||||
|
return selection, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
rawData := []byte(*fileStringPtr)
|
||||||
|
iniFile, err := ini.Load(rawData)
|
||||||
|
if err != nil {
|
||||||
|
return selection, fmt.Errorf("loading ini from reader: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
peerSection, err := iniFile.GetSection("Peer")
|
||||||
|
if err == nil {
|
||||||
|
err = parseWireguardPeerSection(peerSection, &selection)
|
||||||
|
if err != nil {
|
||||||
|
return selection, fmt.Errorf("parsing peer section: %w", err)
|
||||||
|
}
|
||||||
|
} else if !regexINISectionNotExist.MatchString(err.Error()) {
|
||||||
|
// can never happen
|
||||||
|
return selection, fmt.Errorf("getting peer section: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return selection, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func parseWireguardPeerSection(peerSection *ini.Section,
|
||||||
|
selection *settings.WireguardSelection) (err error) {
|
||||||
|
publicKeyPtr, err := parseINIWireguardKey(peerSection, "PublicKey")
|
||||||
|
if err != nil {
|
||||||
|
return err // error is already wrapped correctly
|
||||||
|
} else if publicKeyPtr != nil {
|
||||||
|
selection.PublicKey = *publicKeyPtr
|
||||||
|
}
|
||||||
|
|
||||||
|
endpointKey, err := peerSection.GetKey("Endpoint")
|
||||||
|
if err == nil {
|
||||||
|
endpoint := endpointKey.String()
|
||||||
|
host, portString, err := net.SplitHostPort(endpoint)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("splitting endpoint: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
ip, err := netip.ParseAddr(host)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("%w: %w", ErrEndpointHostNotIP, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
endpointPort, err := port.Validate(portString)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("port from Endpoint key: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
selection.EndpointIP = ip
|
||||||
|
selection.EndpointPort = &endpointPort
|
||||||
|
} else if !regexINIKeyNotExist.MatchString(err.Error()) {
|
||||||
|
// can never happen
|
||||||
|
return fmt.Errorf("getting endpoint key: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
181
internal/configuration/sources/files/wireguardselection_test.go
Normal file
181
internal/configuration/sources/files/wireguardselection_test.go
Normal file
@@ -0,0 +1,181 @@
|
|||||||
|
package files
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/netip"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/qdm12/gluetun/internal/configuration/settings"
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
"gopkg.in/ini.v1"
|
||||||
|
)
|
||||||
|
|
||||||
|
func uint16Ptr(n uint16) *uint16 { return &n }
|
||||||
|
|
||||||
|
func Test_Source_readWireguardSelection(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
t.Run("fail reading from file", func(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
dirPath := t.TempDir()
|
||||||
|
source := &Source{
|
||||||
|
wireguardConfigPath: dirPath,
|
||||||
|
}
|
||||||
|
wireguard, err := source.readWireguardSelection()
|
||||||
|
assert.Equal(t, settings.WireguardSelection{}, wireguard)
|
||||||
|
assert.Error(t, err)
|
||||||
|
assert.Regexp(t, `reading file: read .+: is a directory`, err.Error())
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("no file", func(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
noFile := filepath.Join(t.TempDir(), "doesnotexist")
|
||||||
|
source := &Source{
|
||||||
|
wireguardConfigPath: noFile,
|
||||||
|
}
|
||||||
|
wireguard, err := source.readWireguardSelection()
|
||||||
|
assert.Equal(t, settings.WireguardSelection{}, wireguard)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
})
|
||||||
|
|
||||||
|
testCases := map[string]struct {
|
||||||
|
fileContent string
|
||||||
|
selection settings.WireguardSelection
|
||||||
|
errMessage string
|
||||||
|
}{
|
||||||
|
"ini load error": {
|
||||||
|
fileContent: "invalid",
|
||||||
|
errMessage: "loading ini from reader: key-value delimiter not found: invalid",
|
||||||
|
},
|
||||||
|
"empty file": {},
|
||||||
|
"peer section parsing error": {
|
||||||
|
fileContent: `
|
||||||
|
[Peer]
|
||||||
|
PublicKey = x
|
||||||
|
`,
|
||||||
|
errMessage: "parsing peer section: parsing PublicKey: " +
|
||||||
|
"x: wgtypes: failed to parse base64-encoded key: " +
|
||||||
|
"illegal base64 data at input byte 0",
|
||||||
|
},
|
||||||
|
"success": {
|
||||||
|
fileContent: `
|
||||||
|
[Peer]
|
||||||
|
PublicKey = QOlCgyA/Sn/c/+YNTIEohrjm8IZV+OZ2AUFIoX20sk8=
|
||||||
|
Endpoint = 1.2.3.4:51820
|
||||||
|
`,
|
||||||
|
selection: settings.WireguardSelection{
|
||||||
|
PublicKey: "QOlCgyA/Sn/c/+YNTIEohrjm8IZV+OZ2AUFIoX20sk8=",
|
||||||
|
EndpointIP: netip.AddrFrom4([4]byte{1, 2, 3, 4}),
|
||||||
|
EndpointPort: uint16Ptr(51820),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for testName, testCase := range testCases {
|
||||||
|
testCase := testCase
|
||||||
|
t.Run(testName, func(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
configFile := filepath.Join(t.TempDir(), "wg.conf")
|
||||||
|
err := os.WriteFile(configFile, []byte(testCase.fileContent), 0600)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
source := &Source{
|
||||||
|
wireguardConfigPath: configFile,
|
||||||
|
}
|
||||||
|
|
||||||
|
wireguard, err := source.readWireguardSelection()
|
||||||
|
|
||||||
|
assert.Equal(t, testCase.selection, wireguard)
|
||||||
|
if testCase.errMessage != "" {
|
||||||
|
assert.EqualError(t, err, testCase.errMessage)
|
||||||
|
} else {
|
||||||
|
assert.NoError(t, err)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func Test_parseWireguardPeerSection(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
testCases := map[string]struct {
|
||||||
|
iniData string
|
||||||
|
selection settings.WireguardSelection
|
||||||
|
errMessage string
|
||||||
|
}{
|
||||||
|
"public key error": {
|
||||||
|
iniData: `[Peer]
|
||||||
|
PublicKey = x`,
|
||||||
|
errMessage: "parsing PublicKey: x: " +
|
||||||
|
"wgtypes: failed to parse base64-encoded key: " +
|
||||||
|
"illegal base64 data at input byte 0",
|
||||||
|
},
|
||||||
|
"public key set": {
|
||||||
|
iniData: `[Peer]
|
||||||
|
PublicKey = QOlCgyA/Sn/c/+YNTIEohrjm8IZV+OZ2AUFIoX20sk8=`,
|
||||||
|
selection: settings.WireguardSelection{
|
||||||
|
PublicKey: "QOlCgyA/Sn/c/+YNTIEohrjm8IZV+OZ2AUFIoX20sk8=",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"missing port in endpoint": {
|
||||||
|
iniData: `[Peer]
|
||||||
|
Endpoint = x`,
|
||||||
|
errMessage: "splitting endpoint: address x: missing port in address",
|
||||||
|
},
|
||||||
|
"endpoint host is not IP": {
|
||||||
|
iniData: `[Peer]
|
||||||
|
Endpoint = website.com:51820`,
|
||||||
|
errMessage: "endpoint host is not an IP: ParseAddr(\"website.com\"): unexpected character (at \"website.com\")",
|
||||||
|
},
|
||||||
|
"endpoint port is not valid": {
|
||||||
|
iniData: `[Peer]
|
||||||
|
Endpoint = 1.2.3.4:518299`,
|
||||||
|
errMessage: "port from Endpoint key: port cannot be higher than 65535: 518299",
|
||||||
|
},
|
||||||
|
"valid endpoint": {
|
||||||
|
iniData: `[Peer]
|
||||||
|
Endpoint = 1.2.3.4:51820`,
|
||||||
|
selection: settings.WireguardSelection{
|
||||||
|
EndpointIP: netip.AddrFrom4([4]byte{1, 2, 3, 4}),
|
||||||
|
EndpointPort: uint16Ptr(51820),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"all set": {
|
||||||
|
iniData: `[Peer]
|
||||||
|
PublicKey = QOlCgyA/Sn/c/+YNTIEohrjm8IZV+OZ2AUFIoX20sk8=
|
||||||
|
Endpoint = 1.2.3.4:51820`,
|
||||||
|
selection: settings.WireguardSelection{
|
||||||
|
PublicKey: "QOlCgyA/Sn/c/+YNTIEohrjm8IZV+OZ2AUFIoX20sk8=",
|
||||||
|
EndpointIP: netip.AddrFrom4([4]byte{1, 2, 3, 4}),
|
||||||
|
EndpointPort: uint16Ptr(51820),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for testName, testCase := range testCases {
|
||||||
|
testCase := testCase
|
||||||
|
t.Run(testName, func(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
iniFile, err := ini.Load([]byte(testCase.iniData))
|
||||||
|
require.NoError(t, err)
|
||||||
|
iniSection, err := iniFile.GetSection("Peer")
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
var selection settings.WireguardSelection
|
||||||
|
err = parseWireguardPeerSection(iniSection, &selection)
|
||||||
|
|
||||||
|
assert.Equal(t, testCase.selection, selection)
|
||||||
|
if testCase.errMessage != "" {
|
||||||
|
assert.EqualError(t, err, testCase.errMessage)
|
||||||
|
} else {
|
||||||
|
assert.NoError(t, err)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -19,7 +19,8 @@ func (l *Loop) useUnencryptedDNS(fallback bool) {
|
|||||||
l.logger.Info("using plaintext DNS at address " + targetIP.String())
|
l.logger.Info("using plaintext DNS at address " + targetIP.String())
|
||||||
}
|
}
|
||||||
nameserver.UseDNSInternally(targetIP.AsSlice())
|
nameserver.UseDNSInternally(targetIP.AsSlice())
|
||||||
err := nameserver.UseDNSSystemWide(l.resolvConf, targetIP.AsSlice(), *settings.KeepNameserver)
|
const keepNameserver = false
|
||||||
|
err := nameserver.UseDNSSystemWide(l.resolvConf, targetIP.AsSlice(), keepNameserver)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
l.logger.Error(err.Error())
|
l.logger.Error(err.Error())
|
||||||
}
|
}
|
||||||
@@ -39,7 +40,8 @@ func (l *Loop) useUnencryptedDNS(fallback bool) {
|
|||||||
l.logger.Info("using plaintext DNS at address " + targetIP.String())
|
l.logger.Info("using plaintext DNS at address " + targetIP.String())
|
||||||
}
|
}
|
||||||
nameserver.UseDNSInternally(targetIP.AsSlice())
|
nameserver.UseDNSInternally(targetIP.AsSlice())
|
||||||
err = nameserver.UseDNSSystemWide(l.resolvConf, targetIP.AsSlice(), *settings.KeepNameserver)
|
const keepNameserver = false
|
||||||
|
err = nameserver.UseDNSSystemWide(l.resolvConf, targetIP.AsSlice(), keepNameserver)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
l.logger.Error(err.Error())
|
l.logger.Error(err.Error())
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,9 +10,14 @@ import (
|
|||||||
func (l *Loop) Run(ctx context.Context, done chan<- struct{}) {
|
func (l *Loop) Run(ctx context.Context, done chan<- struct{}) {
|
||||||
defer close(done)
|
defer close(done)
|
||||||
|
|
||||||
const fallback = false
|
if *l.GetSettings().KeepNameserver {
|
||||||
l.useUnencryptedDNS(fallback) // TODO remove? Use default DNS by default for Docker resolution?
|
l.logger.Warn("⚠️⚠️⚠️ keeping the default container nameservers, " +
|
||||||
// TODO this one is kept if DNS_KEEP_NAMESERVER=on and should be replaced
|
"this will likely leak DNS traffic outside the VPN " +
|
||||||
|
"and go through your container network DNS outside the VPN tunnel!")
|
||||||
|
} else {
|
||||||
|
const fallback = false
|
||||||
|
l.useUnencryptedDNS(fallback)
|
||||||
|
}
|
||||||
|
|
||||||
select {
|
select {
|
||||||
case <-l.start:
|
case <-l.start:
|
||||||
@@ -27,7 +32,8 @@ func (l *Loop) Run(ctx context.Context, done chan<- struct{}) {
|
|||||||
unboundCancel := func() { waitError <- nil }
|
unboundCancel := func() { waitError <- nil }
|
||||||
closeStreams := func() {}
|
closeStreams := func() {}
|
||||||
|
|
||||||
for *l.GetSettings().DoT.Enabled {
|
settings := l.GetSettings()
|
||||||
|
for !*settings.KeepNameserver && *settings.DoT.Enabled {
|
||||||
var err error
|
var err error
|
||||||
unboundCancel, waitError, closeStreams, err = l.setupUnbound(ctx)
|
unboundCancel, waitError, closeStreams, err = l.setupUnbound(ctx)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
@@ -50,7 +56,8 @@ func (l *Loop) Run(ctx context.Context, done chan<- struct{}) {
|
|||||||
l.logAndWait(ctx, err)
|
l.logAndWait(ctx, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if !*l.GetSettings().DoT.Enabled {
|
settings = l.GetSettings()
|
||||||
|
if !*settings.KeepNameserver && !*settings.DoT.Enabled {
|
||||||
const fallback = false
|
const fallback = false
|
||||||
l.useUnencryptedDNS(fallback)
|
l.useUnencryptedDNS(fallback)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,6 +3,8 @@ package firewall
|
|||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
|
||||||
|
"github.com/qdm12/gluetun/internal/netlink"
|
||||||
)
|
)
|
||||||
|
|
||||||
func (c *Config) SetEnabled(ctx context.Context, enabled bool) (err error) {
|
func (c *Config) SetEnabled(ctx context.Context, enabled bool) (err error) {
|
||||||
@@ -49,6 +51,13 @@ func (c *Config) disable(ctx context.Context) (err error) {
|
|||||||
if err = c.setIPv6AllPolicies(ctx, "ACCEPT"); err != nil {
|
if err = c.setIPv6AllPolicies(ctx, "ACCEPT"); err != nil {
|
||||||
return fmt.Errorf("setting ipv6 policies: %w", err)
|
return fmt.Errorf("setting ipv6 policies: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const remove = true
|
||||||
|
err = c.redirectPorts(ctx, remove)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("removing port redirections: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -122,6 +131,11 @@ func (c *Config) enable(ctx context.Context) (err error) {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
err = c.redirectPorts(ctx, remove)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("redirecting ports: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
if err := c.runUserPostRules(ctx, c.customRulesPath, remove); err != nil {
|
if err := c.runUserPostRules(ctx, c.customRulesPath, remove); err != nil {
|
||||||
return fmt.Errorf("running user defined post firewall rules: %w", err)
|
return fmt.Errorf("running user defined post firewall rules: %w", err)
|
||||||
}
|
}
|
||||||
@@ -147,7 +161,16 @@ func (c *Config) allowVPNIP(ctx context.Context) (err error) {
|
|||||||
|
|
||||||
func (c *Config) allowOutboundSubnets(ctx context.Context) (err error) {
|
func (c *Config) allowOutboundSubnets(ctx context.Context) (err error) {
|
||||||
for _, subnet := range c.outboundSubnets {
|
for _, subnet := range c.outboundSubnets {
|
||||||
|
subnetIsIPv6 := subnet.Addr().Is6()
|
||||||
|
firewallUpdated := false
|
||||||
for _, defaultRoute := range c.defaultRoutes {
|
for _, defaultRoute := range c.defaultRoutes {
|
||||||
|
defaultRouteIsIPv6 := defaultRoute.Family == netlink.FamilyV6
|
||||||
|
ipFamilyMatch := subnetIsIPv6 == defaultRouteIsIPv6
|
||||||
|
if !ipFamilyMatch {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
firewallUpdated = true
|
||||||
|
|
||||||
const remove = false
|
const remove = false
|
||||||
err := c.acceptOutputFromIPToSubnet(ctx, defaultRoute.NetInterface,
|
err := c.acceptOutputFromIPToSubnet(ctx, defaultRoute.NetInterface,
|
||||||
defaultRoute.AssignedIP, subnet, remove)
|
defaultRoute.AssignedIP, subnet, remove)
|
||||||
@@ -155,6 +178,11 @@ func (c *Config) allowOutboundSubnets(ctx context.Context) (err error) {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if !firewallUpdated {
|
||||||
|
c.logger.Info(fmt.Sprintf("ignoring subnet %s which has "+
|
||||||
|
"no default route matching its family", subnet))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
@@ -172,3 +200,14 @@ func (c *Config) allowInputPorts(ctx context.Context) (err error) {
|
|||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (c *Config) redirectPorts(ctx context.Context, remove bool) (err error) {
|
||||||
|
for _, portRedirection := range c.portRedirections {
|
||||||
|
err = c.redirectPort(ctx, portRedirection.interfaceName, portRedirection.sourcePort,
|
||||||
|
portRedirection.destinationPort, remove)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|||||||
@@ -29,6 +29,7 @@ type Config struct { //nolint:maligned
|
|||||||
vpnIntf string
|
vpnIntf string
|
||||||
outboundSubnets []netip.Prefix
|
outboundSubnets []netip.Prefix
|
||||||
allowedInputPorts map[uint16]map[string]struct{} // port to interfaces set mapping
|
allowedInputPorts map[uint16]map[string]struct{} // port to interfaces set mapping
|
||||||
|
portRedirections portRedirections
|
||||||
stateMutex sync.Mutex
|
stateMutex sync.Mutex
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -142,9 +142,13 @@ func (c *Config) acceptEstablishedRelatedTraffic(ctx context.Context, remove boo
|
|||||||
|
|
||||||
func (c *Config) acceptOutputTrafficToVPN(ctx context.Context,
|
func (c *Config) acceptOutputTrafficToVPN(ctx context.Context,
|
||||||
defaultInterface string, connection models.Connection, remove bool) error {
|
defaultInterface string, connection models.Connection, remove bool) error {
|
||||||
|
protocol := connection.Protocol
|
||||||
|
if protocol == "tcp-client" {
|
||||||
|
protocol = "tcp"
|
||||||
|
}
|
||||||
instruction := fmt.Sprintf("%s OUTPUT -d %s -o %s -p %s -m %s --dport %d -j ACCEPT",
|
instruction := fmt.Sprintf("%s OUTPUT -d %s -o %s -p %s -m %s --dport %d -j ACCEPT",
|
||||||
appendOrDelete(remove), connection.IP, defaultInterface, connection.Protocol,
|
appendOrDelete(remove), connection.IP, defaultInterface, protocol,
|
||||||
connection.Protocol, connection.Port)
|
protocol, connection.Port)
|
||||||
if connection.IP.Is4() {
|
if connection.IP.Is4() {
|
||||||
return c.runIptablesInstruction(ctx, instruction)
|
return c.runIptablesInstruction(ctx, instruction)
|
||||||
} else if c.ip6Tables == "" {
|
} else if c.ip6Tables == "" {
|
||||||
@@ -198,6 +202,38 @@ func (c *Config) acceptInputToPort(ctx context.Context, intf string, port uint16
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Used for VPN server side port forwarding, with intf set to the VPN tunnel interface.
|
||||||
|
func (c *Config) redirectPort(ctx context.Context, intf string,
|
||||||
|
sourcePort, destinationPort uint16, remove bool) (err error) {
|
||||||
|
interfaceFlag := "-i " + intf
|
||||||
|
if intf == "*" { // all interfaces
|
||||||
|
interfaceFlag = ""
|
||||||
|
}
|
||||||
|
|
||||||
|
err = c.runIptablesInstructions(ctx, []string{
|
||||||
|
fmt.Sprintf("-t nat %s PREROUTING %s -d 127.0.0.1 -p tcp --dport %d -j REDIRECT --to-ports %d",
|
||||||
|
appendOrDelete(remove), interfaceFlag, sourcePort, destinationPort),
|
||||||
|
fmt.Sprintf("-t nat %s PREROUTING %s -d 127.0.0.1 -p udp --dport %d -j REDIRECT --to-ports %d",
|
||||||
|
appendOrDelete(remove), interfaceFlag, sourcePort, destinationPort),
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("redirecting IPv4 source port %d to destination port %d on interface %s: %w",
|
||||||
|
sourcePort, destinationPort, intf, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
err = c.runIP6tablesInstructions(ctx, []string{
|
||||||
|
fmt.Sprintf("-t nat %s PREROUTING %s -d ::1 -p tcp --dport %d -j REDIRECT --to-ports %d",
|
||||||
|
appendOrDelete(remove), interfaceFlag, sourcePort, destinationPort),
|
||||||
|
fmt.Sprintf("-t nat %s PREROUTING %s -d ::1 -p udp --dport %d -j REDIRECT --to-ports %d",
|
||||||
|
appendOrDelete(remove), interfaceFlag, sourcePort, destinationPort),
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("redirecting IPv6 source port %d to destination port %d on interface %s: %w",
|
||||||
|
sourcePort, destinationPort, intf, err)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
func (c *Config) runUserPostRules(ctx context.Context, filepath string, remove bool) error {
|
func (c *Config) runUserPostRules(ctx context.Context, filepath string, remove bool) error {
|
||||||
file, err := os.OpenFile(filepath, os.O_RDONLY, 0)
|
file, err := os.OpenFile(filepath, os.O_RDONLY, 0)
|
||||||
if os.IsNotExist(err) {
|
if os.IsNotExist(err) {
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"net/netip"
|
"net/netip"
|
||||||
|
|
||||||
|
"github.com/qdm12/gluetun/internal/netlink"
|
||||||
"github.com/qdm12/gluetun/internal/subnet"
|
"github.com/qdm12/gluetun/internal/subnet"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -37,7 +38,16 @@ func (c *Config) SetOutboundSubnets(ctx context.Context, subnets []netip.Prefix)
|
|||||||
func (c *Config) removeOutboundSubnets(ctx context.Context, subnets []netip.Prefix) {
|
func (c *Config) removeOutboundSubnets(ctx context.Context, subnets []netip.Prefix) {
|
||||||
const remove = true
|
const remove = true
|
||||||
for _, subNet := range subnets {
|
for _, subNet := range subnets {
|
||||||
|
subnetIsIPv6 := subNet.Addr().Is6()
|
||||||
|
firewallUpdated := false
|
||||||
for _, defaultRoute := range c.defaultRoutes {
|
for _, defaultRoute := range c.defaultRoutes {
|
||||||
|
defaultRouteIsIPv6 := defaultRoute.Family == netlink.FamilyV6
|
||||||
|
ipFamilyMatch := subnetIsIPv6 == defaultRouteIsIPv6
|
||||||
|
if !ipFamilyMatch {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
firewallUpdated = true
|
||||||
err := c.acceptOutputFromIPToSubnet(ctx, defaultRoute.NetInterface,
|
err := c.acceptOutputFromIPToSubnet(ctx, defaultRoute.NetInterface,
|
||||||
defaultRoute.AssignedIP, subNet, remove)
|
defaultRoute.AssignedIP, subNet, remove)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -45,6 +55,12 @@ func (c *Config) removeOutboundSubnets(ctx context.Context, subnets []netip.Pref
|
|||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if !firewallUpdated {
|
||||||
|
c.logger.Info(fmt.Sprintf("ignoring subnet %s which has "+
|
||||||
|
"no default route matching its family", subNet))
|
||||||
|
continue
|
||||||
|
}
|
||||||
c.outboundSubnets = subnet.RemoveSubnetFromSubnets(c.outboundSubnets, subNet)
|
c.outboundSubnets = subnet.RemoveSubnetFromSubnets(c.outboundSubnets, subNet)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -52,13 +68,28 @@ func (c *Config) removeOutboundSubnets(ctx context.Context, subnets []netip.Pref
|
|||||||
func (c *Config) addOutboundSubnets(ctx context.Context, subnets []netip.Prefix) error {
|
func (c *Config) addOutboundSubnets(ctx context.Context, subnets []netip.Prefix) error {
|
||||||
const remove = false
|
const remove = false
|
||||||
for _, subnet := range subnets {
|
for _, subnet := range subnets {
|
||||||
|
subnetIsIPv6 := subnet.Addr().Is6()
|
||||||
|
firewallUpdated := false
|
||||||
for _, defaultRoute := range c.defaultRoutes {
|
for _, defaultRoute := range c.defaultRoutes {
|
||||||
|
defaultRouteIsIPv6 := defaultRoute.Family == netlink.FamilyV6
|
||||||
|
ipFamilyMatch := subnetIsIPv6 == defaultRouteIsIPv6
|
||||||
|
if !ipFamilyMatch {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
firewallUpdated = true
|
||||||
err := c.acceptOutputFromIPToSubnet(ctx, defaultRoute.NetInterface,
|
err := c.acceptOutputFromIPToSubnet(ctx, defaultRoute.NetInterface,
|
||||||
defaultRoute.AssignedIP, subnet, remove)
|
defaultRoute.AssignedIP, subnet, remove)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if !firewallUpdated {
|
||||||
|
c.logger.Info(fmt.Sprintf("ignoring subnet %s which has "+
|
||||||
|
"no default route matching its family", subnet))
|
||||||
|
continue
|
||||||
|
}
|
||||||
c.outboundSubnets = append(c.outboundSubnets, subnet)
|
c.outboundSubnets = append(c.outboundSubnets, subnet)
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
|
|||||||
119
internal/firewall/redirect.go
Normal file
119
internal/firewall/redirect.go
Normal file
@@ -0,0 +1,119 @@
|
|||||||
|
package firewall
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
)
|
||||||
|
|
||||||
|
// RedirectPort redirects a source port to a destination port on the interface
|
||||||
|
// intf. If intf is empty, it is set to "*" which means all interfaces.
|
||||||
|
// If a redirection for the source port given already exists, it is removed first.
|
||||||
|
// If the destination port is zero, the redirection for the source port is removed
|
||||||
|
// and no new redirection is added.
|
||||||
|
func (c *Config) RedirectPort(ctx context.Context, intf string, sourcePort,
|
||||||
|
destinationPort uint16) (err error) {
|
||||||
|
c.stateMutex.Lock()
|
||||||
|
defer c.stateMutex.Unlock()
|
||||||
|
|
||||||
|
if sourcePort == 0 {
|
||||||
|
panic("source port cannot be 0")
|
||||||
|
}
|
||||||
|
|
||||||
|
newRedirection := portRedirection{
|
||||||
|
interfaceName: intf,
|
||||||
|
sourcePort: sourcePort,
|
||||||
|
destinationPort: destinationPort,
|
||||||
|
}
|
||||||
|
|
||||||
|
if !c.enabled {
|
||||||
|
c.logger.Info("firewall disabled, only updating redirected ports internal state")
|
||||||
|
if destinationPort == 0 {
|
||||||
|
c.portRedirections.remove(intf, sourcePort)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
exists, conflict := c.portRedirections.check(newRedirection)
|
||||||
|
switch {
|
||||||
|
case exists:
|
||||||
|
return nil
|
||||||
|
case conflict != nil:
|
||||||
|
c.portRedirections.remove(conflict.interfaceName,
|
||||||
|
conflict.sourcePort)
|
||||||
|
}
|
||||||
|
c.portRedirections.append(newRedirection)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
exists, conflict := c.portRedirections.check(newRedirection)
|
||||||
|
switch {
|
||||||
|
case exists:
|
||||||
|
return nil
|
||||||
|
case conflict != nil:
|
||||||
|
const remove = true
|
||||||
|
err = c.redirectPort(ctx, conflict.interfaceName, conflict.sourcePort,
|
||||||
|
conflict.destinationPort, remove)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("removing conflicting redirection: %w", err)
|
||||||
|
}
|
||||||
|
c.portRedirections.remove(conflict.interfaceName,
|
||||||
|
conflict.sourcePort)
|
||||||
|
}
|
||||||
|
|
||||||
|
const remove = false
|
||||||
|
err = c.redirectPort(ctx, intf, sourcePort, destinationPort, remove)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("redirecting port: %w", err)
|
||||||
|
}
|
||||||
|
c.portRedirections.append(newRedirection)
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type portRedirection struct {
|
||||||
|
interfaceName string
|
||||||
|
sourcePort uint16
|
||||||
|
destinationPort uint16
|
||||||
|
}
|
||||||
|
|
||||||
|
type portRedirections []portRedirection
|
||||||
|
|
||||||
|
func (p *portRedirections) remove(intf string, sourcePort uint16) {
|
||||||
|
slice := *p
|
||||||
|
for i, redirection := range slice {
|
||||||
|
interfaceMatch := intf == "" || intf == redirection.interfaceName
|
||||||
|
if redirection.sourcePort == sourcePort && interfaceMatch {
|
||||||
|
// Remove redirection - note: order does not matter
|
||||||
|
slice[i] = slice[len(slice)-1]
|
||||||
|
slice = slice[:len(slice)-1]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
*p = slice
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *portRedirections) check(dryRun portRedirection) (alreadyExists bool,
|
||||||
|
conflict *portRedirection) {
|
||||||
|
slice := *p
|
||||||
|
for _, redirection := range slice {
|
||||||
|
interfaceMatch := redirection.interfaceName == "" ||
|
||||||
|
redirection.interfaceName == dryRun.interfaceName
|
||||||
|
|
||||||
|
if redirection.sourcePort == dryRun.sourcePort &&
|
||||||
|
redirection.destinationPort == dryRun.destinationPort &&
|
||||||
|
interfaceMatch {
|
||||||
|
return true, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
if redirection.sourcePort == dryRun.sourcePort &&
|
||||||
|
interfaceMatch {
|
||||||
|
// Source port has a redirection already for the same interface or all interfaces
|
||||||
|
return false, &redirection
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// append should be called after running `check` to avoid rule conflicts.
|
||||||
|
func (p *portRedirections) append(newRedirection portRedirection) {
|
||||||
|
slice := *p
|
||||||
|
slice = append(slice, newRedirection)
|
||||||
|
*p = slice
|
||||||
|
}
|
||||||
@@ -16,7 +16,7 @@ type vpnHealth struct {
|
|||||||
func (s *Server) onUnhealthyVPN(ctx context.Context) {
|
func (s *Server) onUnhealthyVPN(ctx context.Context) {
|
||||||
s.logger.Info("program has been unhealthy for " +
|
s.logger.Info("program has been unhealthy for " +
|
||||||
s.vpn.healthyWait.String() + ": restarting VPN " +
|
s.vpn.healthyWait.String() + ": restarting VPN " +
|
||||||
"(see https://github.com/qdm12/gluetun/wiki/Healthcheck)")
|
"(see https://github.com/qdm12/gluetun-wiki/blob/main/faq/healthcheck.md)")
|
||||||
_, _ = s.vpn.loop.ApplyStatus(ctx, constants.Stopped)
|
_, _ = s.vpn.loop.ApplyStatus(ctx, constants.Stopped)
|
||||||
_, _ = s.vpn.loop.ApplyStatus(ctx, constants.Running)
|
_, _ = s.vpn.loop.ApplyStatus(ctx, constants.Running)
|
||||||
s.vpn.healthyWait += *s.config.VPN.Addition
|
s.vpn.healthyWait += *s.config.VPN.Addition
|
||||||
|
|||||||
207
internal/mod/info.go
Normal file
207
internal/mod/info.go
Normal file
@@ -0,0 +1,207 @@
|
|||||||
|
package mod
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bufio"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
"path"
|
||||||
|
"path/filepath"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"golang.org/x/sys/unix"
|
||||||
|
)
|
||||||
|
|
||||||
|
type state uint8
|
||||||
|
|
||||||
|
const (
|
||||||
|
unloaded state = iota
|
||||||
|
loading
|
||||||
|
loaded
|
||||||
|
builtin
|
||||||
|
)
|
||||||
|
|
||||||
|
type moduleInfo struct {
|
||||||
|
state state
|
||||||
|
dependencyPaths []string
|
||||||
|
}
|
||||||
|
|
||||||
|
var (
|
||||||
|
ErrModulesDirectoryNotFound = errors.New("modules directory not found")
|
||||||
|
)
|
||||||
|
|
||||||
|
func getModulesInfo() (modulesInfo map[string]moduleInfo, err error) {
|
||||||
|
var utsName unix.Utsname
|
||||||
|
err = unix.Uname(&utsName)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("getting unix uname release: %w", err)
|
||||||
|
}
|
||||||
|
release := unix.ByteSliceToString(utsName.Release[:])
|
||||||
|
release = strings.TrimSpace(release)
|
||||||
|
|
||||||
|
modulePaths := []string{
|
||||||
|
filepath.Join("/lib/modules", release),
|
||||||
|
filepath.Join("/usr/lib/modules", release),
|
||||||
|
}
|
||||||
|
|
||||||
|
var modulesPath string
|
||||||
|
var found bool
|
||||||
|
for _, modulesPath = range modulePaths {
|
||||||
|
info, err := os.Stat(modulesPath)
|
||||||
|
if err == nil && info.IsDir() {
|
||||||
|
found = true
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if !found {
|
||||||
|
return nil, fmt.Errorf("%w: %s are not valid existing directories"+
|
||||||
|
"; have you bind mounted the /lib/modules directory?",
|
||||||
|
ErrModulesDirectoryNotFound, strings.Join(modulePaths, ", "))
|
||||||
|
}
|
||||||
|
|
||||||
|
dependencyFilepath := filepath.Join(modulesPath, "modules.dep")
|
||||||
|
dependencyFile, err := os.Open(dependencyFilepath)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("opening dependency file: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
modulesInfo = make(map[string]moduleInfo)
|
||||||
|
scanner := bufio.NewScanner(dependencyFile)
|
||||||
|
for scanner.Scan() {
|
||||||
|
line := scanner.Text()
|
||||||
|
parts := strings.Split(line, ":")
|
||||||
|
path := filepath.Join(modulesPath, strings.TrimSpace(parts[0]))
|
||||||
|
dependenciesString := strings.TrimSpace(parts[1])
|
||||||
|
|
||||||
|
if dependenciesString == "" {
|
||||||
|
modulesInfo[path] = moduleInfo{}
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
dependencyNames := strings.Split(dependenciesString, " ")
|
||||||
|
dependencies := make([]string, len(dependencyNames))
|
||||||
|
for i := range dependencyNames {
|
||||||
|
dependencies[i] = filepath.Join(modulesPath, dependencyNames[i])
|
||||||
|
}
|
||||||
|
modulesInfo[path] = moduleInfo{dependencyPaths: dependencies}
|
||||||
|
}
|
||||||
|
|
||||||
|
err = scanner.Err()
|
||||||
|
if err != nil {
|
||||||
|
_ = dependencyFile.Close()
|
||||||
|
return nil, fmt.Errorf("modules dependency file scanning: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
err = dependencyFile.Close()
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("closing dependency file: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
err = getBuiltinModules(modulesPath, modulesInfo)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("getting builtin modules: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
err = getLoadedModules(modulesInfo)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("getting loaded modules: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return modulesInfo, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func getBuiltinModules(modulesDirPath string, modulesInfo map[string]moduleInfo) error {
|
||||||
|
file, err := os.Open(filepath.Join(modulesDirPath, "modules.builtin"))
|
||||||
|
if err != nil {
|
||||||
|
if os.IsNotExist(err) {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return fmt.Errorf("opening builtin modules file: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
scanner := bufio.NewScanner(file)
|
||||||
|
for scanner.Scan() {
|
||||||
|
txt := scanner.Text()
|
||||||
|
path := filepath.Join(modulesDirPath, strings.TrimSpace(txt))
|
||||||
|
info := modulesInfo[path]
|
||||||
|
info.state = builtin
|
||||||
|
modulesInfo[path] = info
|
||||||
|
}
|
||||||
|
|
||||||
|
err = scanner.Err()
|
||||||
|
if err != nil {
|
||||||
|
_ = file.Close()
|
||||||
|
return fmt.Errorf("scanning builtin modules file: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
err = file.Close()
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("closing builtin modules file: %w", err)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func getLoadedModules(modulesInfo map[string]moduleInfo) (err error) {
|
||||||
|
file, err := os.Open("/proc/modules")
|
||||||
|
if err != nil {
|
||||||
|
// File cannot be opened, so assume no module is loaded
|
||||||
|
return nil //nolint:nilerr
|
||||||
|
}
|
||||||
|
|
||||||
|
scanner := bufio.NewScanner(file)
|
||||||
|
for scanner.Scan() {
|
||||||
|
parts := strings.Split(scanner.Text(), " ")
|
||||||
|
name := parts[0]
|
||||||
|
path, err := findModulePath(name, modulesInfo)
|
||||||
|
if err != nil {
|
||||||
|
_ = file.Close()
|
||||||
|
return fmt.Errorf("finding module path: %w", err)
|
||||||
|
}
|
||||||
|
info := modulesInfo[path]
|
||||||
|
info.state = loaded
|
||||||
|
modulesInfo[path] = info
|
||||||
|
}
|
||||||
|
|
||||||
|
err = scanner.Err()
|
||||||
|
if err != nil {
|
||||||
|
_ = file.Close()
|
||||||
|
return fmt.Errorf("scanning modules: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
err = file.Close()
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("closing process modules file: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
var (
|
||||||
|
ErrModulePathNotFound = errors.New("module path not found")
|
||||||
|
)
|
||||||
|
|
||||||
|
func findModulePath(moduleName string, modulesInfo map[string]moduleInfo) (modulePath string, err error) {
|
||||||
|
// Kernel module names can have underscores or hyphens in their names,
|
||||||
|
// but only one or the other in one particular name.
|
||||||
|
nameHyphensOnly := strings.ReplaceAll(moduleName, "_", "-")
|
||||||
|
nameUnderscoresOnly := strings.ReplaceAll(moduleName, "-", "_")
|
||||||
|
|
||||||
|
validModuleExtensions := []string{".ko", ".ko.gz", ".ko.xz", ".ko.zst"}
|
||||||
|
const nameVariants = 2
|
||||||
|
validFilenames := make(map[string]struct{}, nameVariants*len(validModuleExtensions))
|
||||||
|
for _, ext := range validModuleExtensions {
|
||||||
|
validFilenames[nameHyphensOnly+ext] = struct{}{}
|
||||||
|
validFilenames[nameUnderscoresOnly+ext] = struct{}{}
|
||||||
|
}
|
||||||
|
|
||||||
|
for modulePath := range modulesInfo {
|
||||||
|
moduleFileName := path.Base(modulePath)
|
||||||
|
_, valid := validFilenames[moduleFileName]
|
||||||
|
if valid {
|
||||||
|
return modulePath, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return "", fmt.Errorf("%w: for %q", ErrModulePathNotFound, moduleName)
|
||||||
|
}
|
||||||
115
internal/mod/load.go
Normal file
115
internal/mod/load.go
Normal file
@@ -0,0 +1,115 @@
|
|||||||
|
package mod
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/klauspost/compress/zstd"
|
||||||
|
"github.com/klauspost/pgzip"
|
||||||
|
"github.com/ulikunitz/xz"
|
||||||
|
"golang.org/x/sys/unix"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
ErrModuleInfoNotFound = errors.New("module info not found")
|
||||||
|
ErrCircularDependency = errors.New("circular dependency")
|
||||||
|
)
|
||||||
|
|
||||||
|
func initDependencies(path string, modulesInfo map[string]moduleInfo) (err error) {
|
||||||
|
info, ok := modulesInfo[path]
|
||||||
|
if !ok {
|
||||||
|
return fmt.Errorf("%w: %s", ErrModuleInfoNotFound, path)
|
||||||
|
}
|
||||||
|
|
||||||
|
switch info.state {
|
||||||
|
case unloaded:
|
||||||
|
case loaded, builtin:
|
||||||
|
return nil
|
||||||
|
case loading:
|
||||||
|
return fmt.Errorf("%w: %s is already in the loading state",
|
||||||
|
ErrCircularDependency, path)
|
||||||
|
}
|
||||||
|
|
||||||
|
info.state = loading
|
||||||
|
modulesInfo[path] = info
|
||||||
|
|
||||||
|
for _, dependencyPath := range info.dependencyPaths {
|
||||||
|
err = initDependencies(dependencyPath, modulesInfo)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("init dependencies for %s: %w", path, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
err = initModule(path)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("loading module: %w", err)
|
||||||
|
}
|
||||||
|
info.state = loaded
|
||||||
|
modulesInfo[path] = info
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func initModule(path string) (err error) {
|
||||||
|
file, err := os.Open(path)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("opening module file: %w", err)
|
||||||
|
}
|
||||||
|
defer func() {
|
||||||
|
_ = file.Close()
|
||||||
|
}()
|
||||||
|
|
||||||
|
var reader io.Reader
|
||||||
|
switch filepath.Ext(file.Name()) {
|
||||||
|
case ".xz":
|
||||||
|
reader, err = xz.NewReader(file)
|
||||||
|
case ".gz":
|
||||||
|
reader, err = pgzip.NewReader(file)
|
||||||
|
case ".zst":
|
||||||
|
reader, err = zstd.NewReader(file)
|
||||||
|
default:
|
||||||
|
const moduleParams = ""
|
||||||
|
const flags = 0
|
||||||
|
err = unix.FinitModule(int(file.Fd()), moduleParams, flags)
|
||||||
|
switch {
|
||||||
|
case err == nil, err == unix.EEXIST: //nolint:goerr113
|
||||||
|
return nil
|
||||||
|
case err != unix.ENOSYS: //nolint:goerr113
|
||||||
|
if strings.HasSuffix(err.Error(), "operation not permitted") {
|
||||||
|
err = fmt.Errorf("%w; did you set the SYS_MODULE capability to your container?", err)
|
||||||
|
}
|
||||||
|
return fmt.Errorf("finit module %s: %w", path, err)
|
||||||
|
case flags != 0:
|
||||||
|
return err // unix.ENOSYS error
|
||||||
|
default: // Fall back to init_module(2).
|
||||||
|
reader = file
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("reading from %s: %w", path, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
image, err := io.ReadAll(reader)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("reading module image from %s: %w", path, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
err = file.Close()
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("closing module file %s: %w", path, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
const params = ""
|
||||||
|
err = unix.InitModule(image, params)
|
||||||
|
switch err {
|
||||||
|
case nil, unix.EEXIST:
|
||||||
|
return nil
|
||||||
|
default:
|
||||||
|
return fmt.Errorf("init module read from %s: %w", path, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
37
internal/mod/probe.go
Normal file
37
internal/mod/probe.go
Normal file
@@ -0,0 +1,37 @@
|
|||||||
|
package mod
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Probe loads the given kernel module and its dependencies.
|
||||||
|
func Probe(moduleName string) error {
|
||||||
|
modulesInfo, err := getModulesInfo()
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("getting modules information: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
modulePath, err := findModulePath(moduleName, modulesInfo)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("finding module path: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
info := modulesInfo[modulePath]
|
||||||
|
if info.state == builtin || info.state == loaded {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
info.state = loading
|
||||||
|
for _, dependencyModulePath := range info.dependencyPaths {
|
||||||
|
err = initDependencies(dependencyModulePath, modulesInfo)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("init dependencies: %w", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
err = initModule(modulePath)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("init module: %w", err)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
@@ -126,7 +126,7 @@ func getMarkdownHeaders(vpnProvider string) (headers []string) {
|
|||||||
case providers.Privado:
|
case providers.Privado:
|
||||||
return []string{countryHeader, regionHeader, cityHeader, hostnameHeader}
|
return []string{countryHeader, regionHeader, cityHeader, hostnameHeader}
|
||||||
case providers.PrivateInternetAccess:
|
case providers.PrivateInternetAccess:
|
||||||
return []string{regionHeader, hostnameHeader, tcpHeader, udpHeader, portForwardHeader}
|
return []string{regionHeader, hostnameHeader, nameHeader, tcpHeader, udpHeader, portForwardHeader}
|
||||||
case providers.Privatevpn:
|
case providers.Privatevpn:
|
||||||
return []string{countryHeader, cityHeader, hostnameHeader}
|
return []string{countryHeader, cityHeader, hostnameHeader}
|
||||||
case providers.Protonvpn:
|
case providers.Protonvpn:
|
||||||
|
|||||||
94
internal/natpmp/checks.go
Normal file
94
internal/natpmp/checks.go
Normal file
@@ -0,0 +1,94 @@
|
|||||||
|
package natpmp
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/binary"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
ErrRequestSizeTooSmall = errors.New("message size is too small")
|
||||||
|
)
|
||||||
|
|
||||||
|
func checkRequest(request []byte) (err error) {
|
||||||
|
const minMessageSize = 2 // version number + operation code
|
||||||
|
if len(request) < minMessageSize {
|
||||||
|
return fmt.Errorf("%w: need at least %d bytes and got %d byte(s)",
|
||||||
|
ErrRequestSizeTooSmall, minMessageSize, len(request))
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
var (
|
||||||
|
ErrResponseSizeTooSmall = errors.New("response size is too small")
|
||||||
|
ErrResponseSizeUnexpected = errors.New("response size is unexpected")
|
||||||
|
ErrProtocolVersionUnknown = errors.New("protocol version is unknown")
|
||||||
|
ErrOperationCodeUnexpected = errors.New("operation code is unexpected")
|
||||||
|
)
|
||||||
|
|
||||||
|
func checkResponse(response []byte, expectedOperationCode byte,
|
||||||
|
expectedResponseSize uint) (err error) {
|
||||||
|
const minResponseSize = 4
|
||||||
|
if len(response) < minResponseSize {
|
||||||
|
return fmt.Errorf("%w: need at least %d bytes and got %d byte(s)",
|
||||||
|
ErrResponseSizeTooSmall, minResponseSize, len(response))
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(response) != int(expectedResponseSize) {
|
||||||
|
return fmt.Errorf("%w: expected %d bytes and got %d byte(s)",
|
||||||
|
ErrResponseSizeUnexpected, expectedResponseSize, len(response))
|
||||||
|
}
|
||||||
|
|
||||||
|
protocolVersion := response[0]
|
||||||
|
if protocolVersion != 0 {
|
||||||
|
return fmt.Errorf("%w: %d", ErrProtocolVersionUnknown, protocolVersion)
|
||||||
|
}
|
||||||
|
|
||||||
|
operationCode := response[1]
|
||||||
|
if operationCode != expectedOperationCode {
|
||||||
|
return fmt.Errorf("%w: expected 0x%x and got 0x%x",
|
||||||
|
ErrOperationCodeUnexpected, expectedOperationCode, operationCode)
|
||||||
|
}
|
||||||
|
|
||||||
|
resultCode := binary.BigEndian.Uint16(response[2:4])
|
||||||
|
err = checkResultCode(resultCode)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("result code: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
var (
|
||||||
|
ErrVersionNotSupported = errors.New("version is not supported")
|
||||||
|
ErrNotAuthorized = errors.New("not authorized")
|
||||||
|
ErrNetworkFailure = errors.New("network failure")
|
||||||
|
ErrOutOfResources = errors.New("out of resources")
|
||||||
|
ErrOperationCodeNotSupported = errors.New("operation code is not supported")
|
||||||
|
ErrResultCodeUnknown = errors.New("result code is unknown")
|
||||||
|
)
|
||||||
|
|
||||||
|
// checkResultCode checks the result code and returns an error
|
||||||
|
// if the result code is not a success (0).
|
||||||
|
// See https://www.ietf.org/rfc/rfc6886.html#section-3.5
|
||||||
|
//
|
||||||
|
//nolint:gomnd
|
||||||
|
func checkResultCode(resultCode uint16) (err error) {
|
||||||
|
switch resultCode {
|
||||||
|
case 0:
|
||||||
|
return nil
|
||||||
|
case 1:
|
||||||
|
return fmt.Errorf("%w", ErrVersionNotSupported)
|
||||||
|
case 2:
|
||||||
|
return fmt.Errorf("%w", ErrNotAuthorized)
|
||||||
|
case 3:
|
||||||
|
return fmt.Errorf("%w", ErrNetworkFailure)
|
||||||
|
case 4:
|
||||||
|
return fmt.Errorf("%w", ErrOutOfResources)
|
||||||
|
case 5:
|
||||||
|
return fmt.Errorf("%w", ErrOperationCodeNotSupported)
|
||||||
|
default:
|
||||||
|
return fmt.Errorf("%w: %d", ErrResultCodeUnknown, resultCode)
|
||||||
|
}
|
||||||
|
}
|
||||||
161
internal/natpmp/checks_test.go
Normal file
161
internal/natpmp/checks_test.go
Normal file
@@ -0,0 +1,161 @@
|
|||||||
|
package natpmp
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
)
|
||||||
|
|
||||||
|
func Test_checkRequest(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
testCases := map[string]struct {
|
||||||
|
request []byte
|
||||||
|
err error
|
||||||
|
errMessage string
|
||||||
|
}{
|
||||||
|
"too_short": {
|
||||||
|
request: []byte{1},
|
||||||
|
err: ErrRequestSizeTooSmall,
|
||||||
|
errMessage: "message size is too small: need at least 2 bytes and got 1 byte(s)",
|
||||||
|
},
|
||||||
|
"success": {
|
||||||
|
request: []byte{0, 0},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for name, testCase := range testCases {
|
||||||
|
testCase := testCase
|
||||||
|
t.Run(name, func(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
err := checkRequest(testCase.request)
|
||||||
|
|
||||||
|
assert.ErrorIs(t, err, testCase.err)
|
||||||
|
if testCase.err != nil {
|
||||||
|
assert.EqualError(t, err, testCase.errMessage)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func Test_checkResponse(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
testCases := map[string]struct {
|
||||||
|
response []byte
|
||||||
|
expectedOperationCode byte
|
||||||
|
expectedResponseSize uint
|
||||||
|
err error
|
||||||
|
errMessage string
|
||||||
|
}{
|
||||||
|
"too_short": {
|
||||||
|
response: []byte{1},
|
||||||
|
err: ErrResponseSizeTooSmall,
|
||||||
|
errMessage: "response size is too small: need at least 4 bytes and got 1 byte(s)",
|
||||||
|
},
|
||||||
|
"size_mismatch": {
|
||||||
|
response: []byte{0, 0, 0, 0},
|
||||||
|
expectedResponseSize: 5,
|
||||||
|
err: ErrResponseSizeUnexpected,
|
||||||
|
errMessage: "response size is unexpected: expected 5 bytes and got 4 byte(s)",
|
||||||
|
},
|
||||||
|
"protocol_unknown": {
|
||||||
|
response: []byte{1, 0, 0, 0},
|
||||||
|
expectedResponseSize: 4,
|
||||||
|
err: ErrProtocolVersionUnknown,
|
||||||
|
errMessage: "protocol version is unknown: 1",
|
||||||
|
},
|
||||||
|
"operation_code_unexpected": {
|
||||||
|
response: []byte{0, 2, 0, 0},
|
||||||
|
expectedOperationCode: 1,
|
||||||
|
expectedResponseSize: 4,
|
||||||
|
err: ErrOperationCodeUnexpected,
|
||||||
|
errMessage: "operation code is unexpected: expected 0x1 and got 0x2",
|
||||||
|
},
|
||||||
|
"result_code_failure": {
|
||||||
|
response: []byte{0, 1, 0, 1},
|
||||||
|
expectedOperationCode: 1,
|
||||||
|
expectedResponseSize: 4,
|
||||||
|
err: ErrVersionNotSupported,
|
||||||
|
errMessage: "result code: version is not supported",
|
||||||
|
},
|
||||||
|
"success": {
|
||||||
|
response: []byte{0, 1, 0, 0},
|
||||||
|
expectedOperationCode: 1,
|
||||||
|
expectedResponseSize: 4,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for name, testCase := range testCases {
|
||||||
|
testCase := testCase
|
||||||
|
t.Run(name, func(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
err := checkResponse(testCase.response,
|
||||||
|
testCase.expectedOperationCode,
|
||||||
|
testCase.expectedResponseSize)
|
||||||
|
|
||||||
|
assert.ErrorIs(t, err, testCase.err)
|
||||||
|
if testCase.err != nil {
|
||||||
|
assert.EqualError(t, err, testCase.errMessage)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func Test_checkResultCode(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
testCases := map[string]struct {
|
||||||
|
resultCode uint16
|
||||||
|
err error
|
||||||
|
errMessage string
|
||||||
|
}{
|
||||||
|
"success": {},
|
||||||
|
"version_unsupported": {
|
||||||
|
resultCode: 1,
|
||||||
|
err: ErrVersionNotSupported,
|
||||||
|
errMessage: "version is not supported",
|
||||||
|
},
|
||||||
|
"not_authorized": {
|
||||||
|
resultCode: 2,
|
||||||
|
err: ErrNotAuthorized,
|
||||||
|
errMessage: "not authorized",
|
||||||
|
},
|
||||||
|
"network_failure": {
|
||||||
|
resultCode: 3,
|
||||||
|
err: ErrNetworkFailure,
|
||||||
|
errMessage: "network failure",
|
||||||
|
},
|
||||||
|
"out_of_resources": {
|
||||||
|
resultCode: 4,
|
||||||
|
err: ErrOutOfResources,
|
||||||
|
errMessage: "out of resources",
|
||||||
|
},
|
||||||
|
"unsupported_operation_code": {
|
||||||
|
resultCode: 5,
|
||||||
|
err: ErrOperationCodeNotSupported,
|
||||||
|
errMessage: "operation code is not supported",
|
||||||
|
},
|
||||||
|
"unknown": {
|
||||||
|
resultCode: 6,
|
||||||
|
err: ErrResultCodeUnknown,
|
||||||
|
errMessage: "result code is unknown: 6",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for name, testCase := range testCases {
|
||||||
|
testCase := testCase
|
||||||
|
t.Run(name, func(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
err := checkResultCode(testCase.resultCode)
|
||||||
|
|
||||||
|
assert.ErrorIs(t, err, testCase.err)
|
||||||
|
if testCase.err != nil {
|
||||||
|
assert.EqualError(t, err, testCase.errMessage)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
28
internal/natpmp/externaladdress.go
Normal file
28
internal/natpmp/externaladdress.go
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
package natpmp
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"encoding/binary"
|
||||||
|
"fmt"
|
||||||
|
"net/netip"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
// ExternalAddress fetches the duration since the start of epoch and the external
|
||||||
|
// IPv4 address of the gateway.
|
||||||
|
// See https://www.ietf.org/rfc/rfc6886.html#section-3.2
|
||||||
|
func (c *Client) ExternalAddress(ctx context.Context, gateway netip.Addr) (
|
||||||
|
durationSinceStartOfEpoch time.Duration,
|
||||||
|
externalIPv4Address netip.Addr, err error) {
|
||||||
|
request := []byte{0, 0} // version 0, operationCode 0
|
||||||
|
const responseSize = 12
|
||||||
|
response, err := c.rpc(ctx, gateway, request, responseSize)
|
||||||
|
if err != nil {
|
||||||
|
return 0, externalIPv4Address, fmt.Errorf("executing remote procedure call: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
secondsSinceStartOfEpoch := binary.BigEndian.Uint32(response[4:8])
|
||||||
|
durationSinceStartOfEpoch = time.Duration(secondsSinceStartOfEpoch) * time.Second
|
||||||
|
externalIPv4Address = netip.AddrFrom4([4]byte{response[8], response[9], response[10], response[11]})
|
||||||
|
return durationSinceStartOfEpoch, externalIPv4Address, nil
|
||||||
|
}
|
||||||
71
internal/natpmp/externaladdress_test.go
Normal file
71
internal/natpmp/externaladdress_test.go
Normal file
@@ -0,0 +1,71 @@
|
|||||||
|
package natpmp
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"net/netip"
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
)
|
||||||
|
|
||||||
|
func Test_Client_ExternalAddress(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
canceledCtx, cancel := context.WithCancel(context.Background())
|
||||||
|
cancel()
|
||||||
|
|
||||||
|
testCases := map[string]struct {
|
||||||
|
ctx context.Context
|
||||||
|
gateway netip.Addr
|
||||||
|
initialConnDuration time.Duration
|
||||||
|
exchanges []udpExchange
|
||||||
|
durationSinceStartOfEpoch time.Duration
|
||||||
|
externalIPv4Address netip.Addr
|
||||||
|
err error
|
||||||
|
errMessage string
|
||||||
|
}{
|
||||||
|
"failure": {
|
||||||
|
ctx: canceledCtx,
|
||||||
|
gateway: netip.AddrFrom4([4]byte{127, 0, 0, 1}),
|
||||||
|
initialConnDuration: initialConnectionDuration,
|
||||||
|
err: context.Canceled,
|
||||||
|
errMessage: "executing remote procedure call: reading from udp connection: context canceled",
|
||||||
|
},
|
||||||
|
"success": {
|
||||||
|
ctx: context.Background(),
|
||||||
|
gateway: netip.AddrFrom4([4]byte{127, 0, 0, 1}),
|
||||||
|
initialConnDuration: initialConnectionDuration,
|
||||||
|
exchanges: []udpExchange{{
|
||||||
|
request: []byte{0, 0},
|
||||||
|
response: []byte{0x0, 0x80, 0x0, 0x0, 0x0, 0x13, 0xf2, 0x4f, 0x49, 0x8c, 0x36, 0x9a},
|
||||||
|
}},
|
||||||
|
durationSinceStartOfEpoch: time.Duration(0x13f24f) * time.Second,
|
||||||
|
externalIPv4Address: netip.AddrFrom4([4]byte{0x49, 0x8c, 0x36, 0x9a}),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for name, testCase := range testCases {
|
||||||
|
testCase := testCase
|
||||||
|
t.Run(name, func(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
remoteAddress := launchUDPServer(t, testCase.exchanges)
|
||||||
|
|
||||||
|
client := Client{
|
||||||
|
serverPort: uint16(remoteAddress.Port),
|
||||||
|
initialConnectionDuration: testCase.initialConnDuration,
|
||||||
|
maxRetries: 1,
|
||||||
|
}
|
||||||
|
|
||||||
|
durationSinceStartOfEpoch, externalIPv4Address, err :=
|
||||||
|
client.ExternalAddress(testCase.ctx, testCase.gateway)
|
||||||
|
assert.ErrorIs(t, err, testCase.err)
|
||||||
|
if testCase.err != nil {
|
||||||
|
assert.EqualError(t, err, testCase.errMessage)
|
||||||
|
}
|
||||||
|
assert.Equal(t, testCase.durationSinceStartOfEpoch, durationSinceStartOfEpoch)
|
||||||
|
assert.Equal(t, testCase.externalIPv4Address, externalIPv4Address)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
103
internal/natpmp/helpers_test.go
Normal file
103
internal/natpmp/helpers_test.go
Normal file
@@ -0,0 +1,103 @@
|
|||||||
|
package natpmp
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"net"
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
)
|
||||||
|
|
||||||
|
// enough for slow machines for local UDP server.
|
||||||
|
const initialConnectionDuration = 3 * time.Second
|
||||||
|
|
||||||
|
type udpExchange struct {
|
||||||
|
request []byte
|
||||||
|
response []byte
|
||||||
|
close bool // to trigger a client error
|
||||||
|
}
|
||||||
|
|
||||||
|
// launchUDPServer launches an UDP server which will expect
|
||||||
|
// the requests precised in each of the given exchanges,
|
||||||
|
// and respond the given corresponding response.
|
||||||
|
// The server shuts down gracefully at the end of the test.
|
||||||
|
// The remote address (127.0.0.1:port) is returned, where
|
||||||
|
// port is dynamically assigned by the OS so calling tests
|
||||||
|
// can run in parallel.
|
||||||
|
func launchUDPServer(t *testing.T, exchanges []udpExchange) (
|
||||||
|
remoteAddress *net.UDPAddr) {
|
||||||
|
t.Helper()
|
||||||
|
|
||||||
|
conn, err := net.ListenUDP("udp", nil)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
listeningAddress, ok := conn.LocalAddr().(*net.UDPAddr)
|
||||||
|
require.True(t, ok, "listening address is not UDP")
|
||||||
|
remoteAddress = &net.UDPAddr{
|
||||||
|
IP: net.IPv4(127, 0, 0, 1),
|
||||||
|
Port: listeningAddress.Port,
|
||||||
|
}
|
||||||
|
|
||||||
|
done := make(chan struct{})
|
||||||
|
t.Cleanup(func() {
|
||||||
|
err := conn.Close()
|
||||||
|
if !errors.Is(err, net.ErrClosed) {
|
||||||
|
assert.NoError(t, err)
|
||||||
|
}
|
||||||
|
<-done
|
||||||
|
})
|
||||||
|
|
||||||
|
var maxBufferSize int
|
||||||
|
for _, exchange := range exchanges {
|
||||||
|
if len(exchange.request) > maxBufferSize {
|
||||||
|
maxBufferSize = len(exchange.request)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
buffer := make([]byte, maxBufferSize)
|
||||||
|
|
||||||
|
ready := make(chan struct{})
|
||||||
|
go func() {
|
||||||
|
defer close(done)
|
||||||
|
close(ready)
|
||||||
|
for _, exchange := range exchanges {
|
||||||
|
n, clientAddress, err := conn.ReadFromUDP(buffer)
|
||||||
|
if errors.Is(err, net.ErrClosed) {
|
||||||
|
t.Error("at least one exchange is missing")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
assert.Equal(t, len(exchange.request), n,
|
||||||
|
"request message size is unexpected")
|
||||||
|
if n > 0 {
|
||||||
|
assert.Equal(t, exchange.request, buffer[:n],
|
||||||
|
"request message is unexpected")
|
||||||
|
}
|
||||||
|
|
||||||
|
if exchange.close {
|
||||||
|
err = conn.Close()
|
||||||
|
if !errors.Is(err, net.ErrClosed) {
|
||||||
|
// connection might be already closed by client production code
|
||||||
|
assert.NoError(t, err)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err = conn.WriteToUDP(exchange.response, clientAddress)
|
||||||
|
require.NoError(t, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
err := conn.Close()
|
||||||
|
if !errors.Is(err, net.ErrClosed) {
|
||||||
|
// The connection closing can be raced by the test
|
||||||
|
// cleanup function defined above.
|
||||||
|
assert.NoError(t, err)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
<-ready
|
||||||
|
|
||||||
|
return remoteAddress
|
||||||
|
}
|
||||||
26
internal/natpmp/natpmp.go
Normal file
26
internal/natpmp/natpmp.go
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
package natpmp
|
||||||
|
|
||||||
|
import (
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Client is a NAT-PMP protocol client.
|
||||||
|
type Client struct {
|
||||||
|
serverPort uint16
|
||||||
|
initialConnectionDuration time.Duration
|
||||||
|
maxRetries uint
|
||||||
|
}
|
||||||
|
|
||||||
|
// New creates a new NAT-PMP client.
|
||||||
|
func New() (client *Client) {
|
||||||
|
const natpmpPort = 5351
|
||||||
|
|
||||||
|
// Parameters described in https://www.ietf.org/rfc/rfc6886.html#section-3.1
|
||||||
|
const initialConnectionDuration = 250 * time.Millisecond
|
||||||
|
const maxTries = 9 // 64 seconds
|
||||||
|
return &Client{
|
||||||
|
serverPort: natpmpPort,
|
||||||
|
initialConnectionDuration: initialConnectionDuration,
|
||||||
|
maxRetries: maxTries,
|
||||||
|
}
|
||||||
|
}
|
||||||
20
internal/natpmp/natpmp_test.go
Normal file
20
internal/natpmp/natpmp_test.go
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
package natpmp
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
)
|
||||||
|
|
||||||
|
func Test_New(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
expectedClient := &Client{
|
||||||
|
serverPort: 5351,
|
||||||
|
initialConnectionDuration: 250 * time.Millisecond,
|
||||||
|
maxRetries: 9,
|
||||||
|
}
|
||||||
|
client := New()
|
||||||
|
assert.Equal(t, expectedClient, client)
|
||||||
|
}
|
||||||
60
internal/natpmp/portmapping.go
Normal file
60
internal/natpmp/portmapping.go
Normal file
@@ -0,0 +1,60 @@
|
|||||||
|
package natpmp
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"encoding/binary"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"net/netip"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
ErrNetworkProtocolUnknown = errors.New("network protocol is unknown")
|
||||||
|
ErrLifetimeTooLong = errors.New("lifetime is too long")
|
||||||
|
)
|
||||||
|
|
||||||
|
// Add or delete a port mapping. To delete a mapping, set both the
|
||||||
|
// requestedExternalPort and lifetime to 0.
|
||||||
|
// See https://www.ietf.org/rfc/rfc6886.html#section-3.3
|
||||||
|
func (c *Client) AddPortMapping(ctx context.Context, gateway netip.Addr,
|
||||||
|
protocol string, internalPort, requestedExternalPort uint16,
|
||||||
|
lifetime time.Duration) (durationSinceStartOfEpoch time.Duration,
|
||||||
|
assignedInternalPort, assignedExternalPort uint16, assignedLifetime time.Duration,
|
||||||
|
err error) {
|
||||||
|
lifetimeSecondsFloat := lifetime.Seconds()
|
||||||
|
const maxLifetimeSeconds = uint64(^uint32(0))
|
||||||
|
if uint64(lifetimeSecondsFloat) > maxLifetimeSeconds {
|
||||||
|
return 0, 0, 0, 0, fmt.Errorf("%w: %d seconds must at most %d seconds",
|
||||||
|
ErrLifetimeTooLong, uint64(lifetimeSecondsFloat), maxLifetimeSeconds)
|
||||||
|
}
|
||||||
|
const messageSize = 12
|
||||||
|
message := make([]byte, messageSize)
|
||||||
|
message[0] = 0 // Version 0
|
||||||
|
switch protocol {
|
||||||
|
case "udp":
|
||||||
|
message[1] = 1 // operationCode 1
|
||||||
|
case "tcp":
|
||||||
|
message[1] = 2 // operationCode 2
|
||||||
|
default:
|
||||||
|
return 0, 0, 0, 0, fmt.Errorf("%w: %s", ErrNetworkProtocolUnknown, protocol)
|
||||||
|
}
|
||||||
|
// [2:3] are reserved.
|
||||||
|
binary.BigEndian.PutUint16(message[4:6], internalPort)
|
||||||
|
binary.BigEndian.PutUint16(message[6:8], requestedExternalPort)
|
||||||
|
binary.BigEndian.PutUint32(message[8:12], uint32(lifetimeSecondsFloat))
|
||||||
|
|
||||||
|
const responseSize = 16
|
||||||
|
response, err := c.rpc(ctx, gateway, message, responseSize)
|
||||||
|
if err != nil {
|
||||||
|
return 0, 0, 0, 0, fmt.Errorf("executing remote procedure call: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
secondsSinceStartOfEpoch := binary.BigEndian.Uint32(response[4:8])
|
||||||
|
durationSinceStartOfEpoch = time.Duration(secondsSinceStartOfEpoch) * time.Second
|
||||||
|
assignedInternalPort = binary.BigEndian.Uint16(response[8:10])
|
||||||
|
assignedExternalPort = binary.BigEndian.Uint16(response[10:12])
|
||||||
|
lifetimeInSeconds := binary.BigEndian.Uint32(response[12:16])
|
||||||
|
assignedLifetime = time.Duration(lifetimeInSeconds) * time.Second
|
||||||
|
return durationSinceStartOfEpoch, assignedInternalPort, assignedExternalPort, assignedLifetime, nil
|
||||||
|
}
|
||||||
149
internal/natpmp/portmapping_test.go
Normal file
149
internal/natpmp/portmapping_test.go
Normal file
@@ -0,0 +1,149 @@
|
|||||||
|
package natpmp
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"net/netip"
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
)
|
||||||
|
|
||||||
|
func Test_Client_AddPortMapping(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
testCases := map[string]struct {
|
||||||
|
ctx context.Context
|
||||||
|
gateway netip.Addr
|
||||||
|
protocol string
|
||||||
|
internalPort uint16
|
||||||
|
requestedExternalPort uint16
|
||||||
|
lifetime time.Duration
|
||||||
|
initialConnectionDuration time.Duration
|
||||||
|
exchanges []udpExchange
|
||||||
|
durationSinceStartOfEpoch time.Duration
|
||||||
|
assignedInternalPort uint16
|
||||||
|
assignedExternalPort uint16
|
||||||
|
assignedLifetime time.Duration
|
||||||
|
err error
|
||||||
|
errMessage string
|
||||||
|
}{
|
||||||
|
"lifetime_too_long": {
|
||||||
|
lifetime: time.Duration(uint64(^uint32(0))+1) * time.Second,
|
||||||
|
err: ErrLifetimeTooLong,
|
||||||
|
errMessage: "lifetime is too long: 4294967296 seconds must at most 4294967295 seconds",
|
||||||
|
},
|
||||||
|
"protocol_unknown": {
|
||||||
|
lifetime: time.Second,
|
||||||
|
protocol: "xyz",
|
||||||
|
err: ErrNetworkProtocolUnknown,
|
||||||
|
errMessage: "network protocol is unknown: xyz",
|
||||||
|
},
|
||||||
|
"rpc_error": {
|
||||||
|
ctx: context.Background(),
|
||||||
|
gateway: netip.AddrFrom4([4]byte{127, 0, 0, 1}),
|
||||||
|
protocol: "udp",
|
||||||
|
internalPort: 123,
|
||||||
|
requestedExternalPort: 456,
|
||||||
|
lifetime: 1200 * time.Second,
|
||||||
|
initialConnectionDuration: time.Millisecond,
|
||||||
|
exchanges: []udpExchange{{close: true}},
|
||||||
|
err: ErrConnectionTimeout,
|
||||||
|
errMessage: "executing remote procedure call: connection timeout: after 1ms",
|
||||||
|
},
|
||||||
|
"add_udp": {
|
||||||
|
ctx: context.Background(),
|
||||||
|
gateway: netip.AddrFrom4([4]byte{127, 0, 0, 1}),
|
||||||
|
protocol: "udp",
|
||||||
|
internalPort: 123,
|
||||||
|
requestedExternalPort: 456,
|
||||||
|
lifetime: 1200 * time.Second,
|
||||||
|
initialConnectionDuration: initialConnectionDuration,
|
||||||
|
exchanges: []udpExchange{{
|
||||||
|
request: []byte{0x0, 0x1, 0x0, 0x0, 0x0, 0x7b, 0x1, 0xc8, 0x0, 0x0, 0x4, 0xb0},
|
||||||
|
response: []byte{0x0, 0x81, 0x0, 0x0, 0x0, 0x13, 0xfe, 0xff, 0x0, 0x7b, 0x1, 0xc8, 0x0, 0x0, 0x4, 0xb0},
|
||||||
|
}},
|
||||||
|
durationSinceStartOfEpoch: 0x13feff * time.Second,
|
||||||
|
assignedInternalPort: 0x7b,
|
||||||
|
assignedExternalPort: 0x1c8,
|
||||||
|
assignedLifetime: 0x4b0 * time.Second,
|
||||||
|
},
|
||||||
|
"add_tcp": {
|
||||||
|
ctx: context.Background(),
|
||||||
|
gateway: netip.AddrFrom4([4]byte{127, 0, 0, 1}),
|
||||||
|
protocol: "tcp",
|
||||||
|
internalPort: 123,
|
||||||
|
requestedExternalPort: 456,
|
||||||
|
lifetime: 1200 * time.Second,
|
||||||
|
initialConnectionDuration: initialConnectionDuration,
|
||||||
|
exchanges: []udpExchange{{
|
||||||
|
request: []byte{0x0, 0x2, 0x0, 0x0, 0x0, 0x7b, 0x1, 0xc8, 0x0, 0x0, 0x4, 0xb0},
|
||||||
|
response: []byte{0x0, 0x82, 0x0, 0x0, 0x0, 0x14, 0x3, 0x21, 0x0, 0x7b, 0x1, 0xc8, 0x0, 0x0, 0x4, 0xb0},
|
||||||
|
}},
|
||||||
|
durationSinceStartOfEpoch: 0x140321 * time.Second,
|
||||||
|
assignedInternalPort: 0x7b,
|
||||||
|
assignedExternalPort: 0x1c8,
|
||||||
|
assignedLifetime: 0x4b0 * time.Second,
|
||||||
|
},
|
||||||
|
"remove_udp": {
|
||||||
|
ctx: context.Background(),
|
||||||
|
gateway: netip.AddrFrom4([4]byte{127, 0, 0, 1}),
|
||||||
|
protocol: "udp",
|
||||||
|
internalPort: 123,
|
||||||
|
initialConnectionDuration: initialConnectionDuration,
|
||||||
|
exchanges: []udpExchange{{
|
||||||
|
request: []byte{0x0, 0x1, 0x0, 0x0, 0x0, 0x7b, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0},
|
||||||
|
response: []byte{0x0, 0x81, 0x0, 0x0, 0x0, 0x14, 0x3, 0xd5, 0x0, 0x7b, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0},
|
||||||
|
}},
|
||||||
|
durationSinceStartOfEpoch: 0x1403d5 * time.Second,
|
||||||
|
assignedInternalPort: 0x7b,
|
||||||
|
},
|
||||||
|
"remove_tcp": {
|
||||||
|
ctx: context.Background(),
|
||||||
|
gateway: netip.AddrFrom4([4]byte{127, 0, 0, 1}),
|
||||||
|
protocol: "tcp",
|
||||||
|
internalPort: 123,
|
||||||
|
initialConnectionDuration: initialConnectionDuration,
|
||||||
|
exchanges: []udpExchange{{
|
||||||
|
request: []byte{0x0, 0x2, 0x0, 0x0, 0x0, 0x7b, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0},
|
||||||
|
response: []byte{0x0, 0x82, 0x0, 0x0, 0x0, 0x14, 0x4, 0x96, 0x0, 0x7b, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0},
|
||||||
|
}},
|
||||||
|
durationSinceStartOfEpoch: 0x140496 * time.Second,
|
||||||
|
assignedInternalPort: 0x7b,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for name, testCase := range testCases {
|
||||||
|
testCase := testCase
|
||||||
|
t.Run(name, func(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
remoteAddress := launchUDPServer(t, testCase.exchanges)
|
||||||
|
|
||||||
|
client := Client{
|
||||||
|
serverPort: uint16(remoteAddress.Port),
|
||||||
|
initialConnectionDuration: testCase.initialConnectionDuration,
|
||||||
|
maxRetries: 1,
|
||||||
|
}
|
||||||
|
|
||||||
|
durationSinceStartOfEpoch, assignedInternalPort,
|
||||||
|
assignedExternalPort, assignedLifetime, err :=
|
||||||
|
client.AddPortMapping(testCase.ctx, testCase.gateway,
|
||||||
|
testCase.protocol, testCase.internalPort,
|
||||||
|
testCase.requestedExternalPort, testCase.lifetime)
|
||||||
|
|
||||||
|
assert.Equal(t, testCase.durationSinceStartOfEpoch, durationSinceStartOfEpoch)
|
||||||
|
assert.Equal(t, testCase.assignedInternalPort, assignedInternalPort)
|
||||||
|
assert.Equal(t, testCase.assignedExternalPort, assignedExternalPort)
|
||||||
|
assert.Equal(t, testCase.assignedLifetime, assignedLifetime)
|
||||||
|
if testCase.errMessage != "" {
|
||||||
|
if testCase.err != nil {
|
||||||
|
assert.ErrorIs(t, err, testCase.err)
|
||||||
|
}
|
||||||
|
assert.Regexp(t, "^"+testCase.errMessage+"$", err.Error())
|
||||||
|
} else {
|
||||||
|
assert.NoError(t, err)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
123
internal/natpmp/rpc.go
Normal file
123
internal/natpmp/rpc.go
Normal file
@@ -0,0 +1,123 @@
|
|||||||
|
package natpmp
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"net"
|
||||||
|
"net/netip"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
ErrGatewayIPUnspecified = errors.New("gateway IP is unspecified")
|
||||||
|
ErrConnectionTimeout = errors.New("connection timeout")
|
||||||
|
)
|
||||||
|
|
||||||
|
func (c *Client) rpc(ctx context.Context, gateway netip.Addr,
|
||||||
|
request []byte, responseSize uint) (
|
||||||
|
response []byte, err error) {
|
||||||
|
if gateway.IsUnspecified() || !gateway.IsValid() {
|
||||||
|
return nil, fmt.Errorf("%w", ErrGatewayIPUnspecified)
|
||||||
|
}
|
||||||
|
|
||||||
|
err = checkRequest(request)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("checking request: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
gatewayAddress := &net.UDPAddr{
|
||||||
|
IP: gateway.AsSlice(),
|
||||||
|
Port: int(c.serverPort),
|
||||||
|
}
|
||||||
|
|
||||||
|
connection, err := net.DialUDP("udp", nil, gatewayAddress)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("dialing udp: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx, cancel := context.WithCancel(ctx)
|
||||||
|
endGoroutineDone := make(chan struct{})
|
||||||
|
defer func() {
|
||||||
|
cancel()
|
||||||
|
<-endGoroutineDone
|
||||||
|
}()
|
||||||
|
go func() {
|
||||||
|
defer close(endGoroutineDone)
|
||||||
|
// Context is canceled either by the parent context or
|
||||||
|
// when this function returns.
|
||||||
|
<-ctx.Done()
|
||||||
|
closeErr := connection.Close()
|
||||||
|
if closeErr == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if err == nil {
|
||||||
|
err = fmt.Errorf("closing connection: %w", closeErr)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
err = fmt.Errorf("%w; closing connection: %w", err, closeErr)
|
||||||
|
}()
|
||||||
|
|
||||||
|
const maxResponseSize = 16
|
||||||
|
response = make([]byte, maxResponseSize)
|
||||||
|
|
||||||
|
// Connection duration doubles on every network error
|
||||||
|
// Note it does not double if the source IP mismatches the gateway IP.
|
||||||
|
connectionDuration := c.initialConnectionDuration
|
||||||
|
|
||||||
|
var totalRetryDuration time.Duration
|
||||||
|
|
||||||
|
var retryCount uint
|
||||||
|
for retryCount = 0; retryCount < c.maxRetries; retryCount++ {
|
||||||
|
deadline := time.Now().Add(connectionDuration)
|
||||||
|
err = connection.SetDeadline(deadline)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("setting connection deadline: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err = connection.Write(request)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("writing to connection: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
bytesRead, receivedRemoteAddress, err := connection.ReadFromUDP(response)
|
||||||
|
if err != nil {
|
||||||
|
if ctx.Err() != nil {
|
||||||
|
return nil, fmt.Errorf("reading from udp connection: %w", ctx.Err())
|
||||||
|
}
|
||||||
|
var netErr net.Error
|
||||||
|
if errors.As(err, &netErr) && netErr.Timeout() {
|
||||||
|
totalRetryDuration += connectionDuration
|
||||||
|
connectionDuration *= 2
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
return nil, fmt.Errorf("reading from udp connection: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if !receivedRemoteAddress.IP.Equal(gatewayAddress.IP) {
|
||||||
|
// Upon receiving a response packet, the client MUST check the source IP
|
||||||
|
// address, and silently discard the packet if the address is not the
|
||||||
|
// address of the gateway to which the request was sent.
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
response = response[:bytesRead]
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
if retryCount == c.maxRetries {
|
||||||
|
return nil, fmt.Errorf("%w: after %s",
|
||||||
|
ErrConnectionTimeout, totalRetryDuration)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Opcodes between 0 and 127 are client requests. Opcodes from 128 to
|
||||||
|
// 255 are corresponding server responses.
|
||||||
|
const operationCodeMask = 128
|
||||||
|
expectedOperationCode := request[1] | operationCodeMask
|
||||||
|
err = checkResponse(response, expectedOperationCode, responseSize)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("checking response: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return response, nil
|
||||||
|
}
|
||||||
166
internal/natpmp/rpc_test.go
Normal file
166
internal/natpmp/rpc_test.go
Normal file
@@ -0,0 +1,166 @@
|
|||||||
|
package natpmp
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"net/netip"
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
)
|
||||||
|
|
||||||
|
func Test_Client_rpc(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
testCases := map[string]struct {
|
||||||
|
ctx context.Context
|
||||||
|
gateway netip.Addr
|
||||||
|
request []byte
|
||||||
|
responseSize uint
|
||||||
|
initialConnectionDuration time.Duration
|
||||||
|
exchanges []udpExchange
|
||||||
|
expectedResponse []byte
|
||||||
|
err error
|
||||||
|
errMessage string
|
||||||
|
}{
|
||||||
|
"gateway_ip_unspecified": {
|
||||||
|
gateway: netip.IPv6Unspecified(),
|
||||||
|
request: []byte{0, 0},
|
||||||
|
err: ErrGatewayIPUnspecified,
|
||||||
|
errMessage: "gateway IP is unspecified",
|
||||||
|
},
|
||||||
|
"request_too_small": {
|
||||||
|
gateway: netip.AddrFrom4([4]byte{127, 0, 0, 1}),
|
||||||
|
request: []byte{0},
|
||||||
|
initialConnectionDuration: time.Nanosecond, // doesn't matter
|
||||||
|
err: ErrRequestSizeTooSmall,
|
||||||
|
errMessage: `checking request: message size is too small: ` +
|
||||||
|
`need at least 2 bytes and got 1 byte\(s\)`,
|
||||||
|
},
|
||||||
|
"write_error": {
|
||||||
|
ctx: context.Background(),
|
||||||
|
gateway: netip.AddrFrom4([4]byte{127, 0, 0, 1}),
|
||||||
|
request: []byte{0, 0},
|
||||||
|
errMessage: `writing to connection: write udp ` +
|
||||||
|
`127.0.0.1:[1-9][0-9]{0,4}->127.0.0.1:[1-9][0-9]{0,4}: ` +
|
||||||
|
`i/o timeout`,
|
||||||
|
},
|
||||||
|
"call_error": {
|
||||||
|
ctx: context.Background(),
|
||||||
|
gateway: netip.AddrFrom4([4]byte{127, 0, 0, 1}),
|
||||||
|
request: []byte{0, 1},
|
||||||
|
initialConnectionDuration: time.Millisecond,
|
||||||
|
exchanges: []udpExchange{
|
||||||
|
{request: []byte{0, 1}, close: true},
|
||||||
|
},
|
||||||
|
err: ErrConnectionTimeout,
|
||||||
|
errMessage: "connection timeout: after 1ms",
|
||||||
|
},
|
||||||
|
"response_too_small": {
|
||||||
|
ctx: context.Background(),
|
||||||
|
gateway: netip.AddrFrom4([4]byte{127, 0, 0, 1}),
|
||||||
|
request: []byte{0, 0},
|
||||||
|
initialConnectionDuration: initialConnectionDuration,
|
||||||
|
exchanges: []udpExchange{{
|
||||||
|
request: []byte{0, 0},
|
||||||
|
response: []byte{1},
|
||||||
|
}},
|
||||||
|
err: ErrResponseSizeTooSmall,
|
||||||
|
errMessage: `checking response: response size is too small: ` +
|
||||||
|
`need at least 4 bytes and got 1 byte\(s\)`,
|
||||||
|
},
|
||||||
|
"unexpected_response_size": {
|
||||||
|
ctx: context.Background(),
|
||||||
|
gateway: netip.AddrFrom4([4]byte{127, 0, 0, 1}),
|
||||||
|
request: []byte{0x0, 0x2, 0x0, 0x0, 0x0, 0x7b, 0x1, 0xc8, 0x0, 0x0, 0x4, 0xb0},
|
||||||
|
responseSize: 5,
|
||||||
|
initialConnectionDuration: initialConnectionDuration,
|
||||||
|
exchanges: []udpExchange{{
|
||||||
|
request: []byte{0x0, 0x2, 0x0, 0x0, 0x0, 0x7b, 0x1, 0xc8, 0x0, 0x0, 0x4, 0xb0},
|
||||||
|
response: []byte{0, 1, 2, 3}, // size 4
|
||||||
|
}},
|
||||||
|
err: ErrResponseSizeUnexpected,
|
||||||
|
errMessage: `checking response: response size is unexpected: ` +
|
||||||
|
`expected 5 bytes and got 4 byte\(s\)`,
|
||||||
|
},
|
||||||
|
"unknown_protocol_version": {
|
||||||
|
ctx: context.Background(),
|
||||||
|
gateway: netip.AddrFrom4([4]byte{127, 0, 0, 1}),
|
||||||
|
request: []byte{0x0, 0x2, 0x0, 0x0, 0x0, 0x7b, 0x1, 0xc8, 0x0, 0x0, 0x4, 0xb0},
|
||||||
|
responseSize: 16,
|
||||||
|
initialConnectionDuration: initialConnectionDuration,
|
||||||
|
exchanges: []udpExchange{{
|
||||||
|
request: []byte{0x0, 0x2, 0x0, 0x0, 0x0, 0x7b, 0x1, 0xc8, 0x0, 0x0, 0x4, 0xb0},
|
||||||
|
response: []byte{0x1, 0x82, 0x0, 0x0, 0x0, 0x14, 0x4, 0x96, 0x0, 0x7b, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0},
|
||||||
|
}},
|
||||||
|
err: ErrProtocolVersionUnknown,
|
||||||
|
errMessage: "checking response: protocol version is unknown: 1",
|
||||||
|
},
|
||||||
|
"unexpected_operation_code": {
|
||||||
|
ctx: context.Background(),
|
||||||
|
gateway: netip.AddrFrom4([4]byte{127, 0, 0, 1}),
|
||||||
|
request: []byte{0x0, 0x2, 0x0, 0x0, 0x0, 0x7b, 0x1, 0xc8, 0x0, 0x0, 0x4, 0xb0},
|
||||||
|
responseSize: 16,
|
||||||
|
initialConnectionDuration: initialConnectionDuration,
|
||||||
|
exchanges: []udpExchange{{
|
||||||
|
request: []byte{0x0, 0x2, 0x0, 0x0, 0x0, 0x7b, 0x1, 0xc8, 0x0, 0x0, 0x4, 0xb0},
|
||||||
|
response: []byte{0x0, 0x88, 0x0, 0x0, 0x0, 0x14, 0x4, 0x96, 0x0, 0x7b, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0},
|
||||||
|
}},
|
||||||
|
err: ErrOperationCodeUnexpected,
|
||||||
|
errMessage: "checking response: operation code is unexpected: expected 0x82 and got 0x88",
|
||||||
|
},
|
||||||
|
"failure_result_code": {
|
||||||
|
ctx: context.Background(),
|
||||||
|
gateway: netip.AddrFrom4([4]byte{127, 0, 0, 1}),
|
||||||
|
request: []byte{0x0, 0x2, 0x0, 0x0, 0x0, 0x7b, 0x1, 0xc8, 0x0, 0x0, 0x4, 0xb0},
|
||||||
|
responseSize: 16,
|
||||||
|
initialConnectionDuration: initialConnectionDuration,
|
||||||
|
exchanges: []udpExchange{{
|
||||||
|
request: []byte{0x0, 0x2, 0x0, 0x0, 0x0, 0x7b, 0x1, 0xc8, 0x0, 0x0, 0x4, 0xb0},
|
||||||
|
response: []byte{0x0, 0x82, 0x0, 0x11, 0x0, 0x14, 0x4, 0x96, 0x0, 0x7b, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0},
|
||||||
|
}},
|
||||||
|
err: ErrResultCodeUnknown,
|
||||||
|
errMessage: "checking response: result code: result code is unknown: 17",
|
||||||
|
},
|
||||||
|
"success": {
|
||||||
|
ctx: context.Background(),
|
||||||
|
gateway: netip.AddrFrom4([4]byte{127, 0, 0, 1}),
|
||||||
|
request: []byte{0x0, 0x2, 0x0, 0x0, 0x0, 0x7b, 0x1, 0xc8, 0x0, 0x0, 0x4, 0xb0},
|
||||||
|
responseSize: 16,
|
||||||
|
initialConnectionDuration: initialConnectionDuration,
|
||||||
|
exchanges: []udpExchange{{
|
||||||
|
request: []byte{0x0, 0x2, 0x0, 0x0, 0x0, 0x7b, 0x1, 0xc8, 0x0, 0x0, 0x4, 0xb0},
|
||||||
|
response: []byte{0x0, 0x82, 0x0, 0x0, 0x0, 0x0, 0x4, 0x96, 0x0, 0x7b, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0},
|
||||||
|
}},
|
||||||
|
expectedResponse: []byte{0x0, 0x82, 0x0, 0x0, 0x0, 0x0, 0x4, 0x96, 0x0, 0x7b, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for name, testCase := range testCases {
|
||||||
|
testCase := testCase
|
||||||
|
t.Run(name, func(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
remoteAddress := launchUDPServer(t, testCase.exchanges)
|
||||||
|
|
||||||
|
client := Client{
|
||||||
|
serverPort: uint16(remoteAddress.Port),
|
||||||
|
initialConnectionDuration: testCase.initialConnectionDuration,
|
||||||
|
maxRetries: 1,
|
||||||
|
}
|
||||||
|
|
||||||
|
response, err := client.rpc(testCase.ctx, testCase.gateway,
|
||||||
|
testCase.request, testCase.responseSize)
|
||||||
|
|
||||||
|
if testCase.errMessage != "" {
|
||||||
|
if testCase.err != nil {
|
||||||
|
assert.ErrorIs(t, err, testCase.err)
|
||||||
|
}
|
||||||
|
assert.Regexp(t, "^"+testCase.errMessage+"$", err.Error())
|
||||||
|
} else {
|
||||||
|
assert.NoError(t, err)
|
||||||
|
}
|
||||||
|
assert.Equal(t, testCase.expectedResponse, response)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -5,10 +5,42 @@ package netlink
|
|||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
|
||||||
|
"github.com/qdm12/gluetun/internal/mod"
|
||||||
"github.com/vishvananda/netlink"
|
"github.com/vishvananda/netlink"
|
||||||
)
|
)
|
||||||
|
|
||||||
func (n *NetLink) IsWireguardSupported() (ok bool, err error) {
|
func (n *NetLink) IsWireguardSupported() (ok bool, err error) {
|
||||||
|
// Check for Wireguard family without loading the wireguard module.
|
||||||
|
// Some kernels have the wireguard module built-in, and don't have a
|
||||||
|
// modules directory, such as WSL2 kernels.
|
||||||
|
ok, err = hasWireguardFamily()
|
||||||
|
if err != nil {
|
||||||
|
return false, fmt.Errorf("checking for wireguard family: %w", err)
|
||||||
|
}
|
||||||
|
if ok {
|
||||||
|
return true, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Try loading the wireguard module, since some systems do not load
|
||||||
|
// it after a boot. If this fails, wireguard is assumed to not be supported.
|
||||||
|
n.debugLogger.Debugf("wireguard family not found, trying to load wireguard kernel module")
|
||||||
|
err = mod.Probe("wireguard")
|
||||||
|
if err != nil {
|
||||||
|
n.debugLogger.Debugf("failed loading wireguard kernel module: %s", err)
|
||||||
|
return false, nil
|
||||||
|
}
|
||||||
|
n.debugLogger.Debugf("wireguard kernel module loaded successfully")
|
||||||
|
|
||||||
|
// Re-check if the Wireguard family is now available, after loading
|
||||||
|
// the wireguard kernel module.
|
||||||
|
ok, err = hasWireguardFamily()
|
||||||
|
if err != nil {
|
||||||
|
return false, fmt.Errorf("checking for wireguard family: %w", err)
|
||||||
|
}
|
||||||
|
return ok, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func hasWireguardFamily() (ok bool, err error) {
|
||||||
families, err := netlink.GenlFamilyList()
|
families, err := netlink.GenlFamilyList()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return false, fmt.Errorf("listing gen 1 families: %w", err)
|
return false, fmt.Errorf("listing gen 1 families: %w", err)
|
||||||
|
|||||||
@@ -40,7 +40,7 @@ func extractDataFromLines(lines []string) (
|
|||||||
|
|
||||||
if connection.Port == 0 {
|
if connection.Port == 0 {
|
||||||
connection.Port = 1194
|
connection.Port = 1194
|
||||||
if connection.Protocol == constants.TCP {
|
if strings.HasPrefix(connection.Protocol, "tcp") {
|
||||||
connection.Port = 443
|
connection.Port = 443
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -81,7 +81,7 @@ func extractProto(line string) (protocol string, err error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
switch fields[1] {
|
switch fields[1] {
|
||||||
case "tcp", "tcp4", "tcp6", "udp", "udp4", "udp6":
|
case "tcp", "tcp4", "tcp6", "tcp-client", "udp", "udp4", "udp6":
|
||||||
default:
|
default:
|
||||||
return "", fmt.Errorf("%w: %s", errProtocolNotSupported, fields[1])
|
return "", fmt.Errorf("%w: %s", errProtocolNotSupported, fields[1])
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -104,6 +104,10 @@ func Test_extractDataFromLine(t *testing.T) {
|
|||||||
line: "proto tcp",
|
line: "proto tcp",
|
||||||
protocol: constants.TCP,
|
protocol: constants.TCP,
|
||||||
},
|
},
|
||||||
|
"tcp-client": {
|
||||||
|
line: "proto tcp-client",
|
||||||
|
protocol: "tcp-client",
|
||||||
|
},
|
||||||
"extract remote error": {
|
"extract remote error": {
|
||||||
line: "remote bad",
|
line: "remote bad",
|
||||||
isErr: errHostNotIP,
|
isErr: errHostNotIP,
|
||||||
|
|||||||
@@ -52,7 +52,7 @@ Your credentials might be wrong 🤨
|
|||||||
That error usually happens because either:
|
That error usually happens because either:
|
||||||
|
|
||||||
1. The VPN server IP address you are trying to connect to is no longer valid 🔌
|
1. The VPN server IP address you are trying to connect to is no longer valid 🔌
|
||||||
Update your server information using https://github.com/qdm12/gluetun/wiki/Updating-Servers
|
Check out https://github.com/qdm12/gluetun-wiki/blob/main/setup/servers.md#update-the-vpn-servers-list
|
||||||
|
|
||||||
2. The VPN server crashed 💥, try changing your VPN servers filtering options such as SERVER_REGIONS
|
2. The VPN server crashed 💥, try changing your VPN servers filtering options such as SERVER_REGIONS
|
||||||
|
|
||||||
|
|||||||
@@ -52,7 +52,7 @@ func Test_processLogLine(t *testing.T) {
|
|||||||
That error usually happens because either:
|
That error usually happens because either:
|
||||||
|
|
||||||
1. The VPN server IP address you are trying to connect to is no longer valid 🔌
|
1. The VPN server IP address you are trying to connect to is no longer valid 🔌
|
||||||
Update your server information using https://github.com/qdm12/gluetun/wiki/Updating-Servers
|
Check out https://github.com/qdm12/gluetun-wiki/blob/main/setup/servers.md#update-the-vpn-servers-list
|
||||||
|
|
||||||
2. The VPN server crashed 💥, try changing your VPN servers filtering options such as SERVER_REGIONS
|
2. The VPN server crashed 💥, try changing your VPN servers filtering options such as SERVER_REGIONS
|
||||||
|
|
||||||
|
|||||||
@@ -1,32 +0,0 @@
|
|||||||
package portforward
|
|
||||||
|
|
||||||
import "context"
|
|
||||||
|
|
||||||
// firewallBlockPort obtains the state port thread safely and blocks
|
|
||||||
// it in the firewall if it is not the zero value (0).
|
|
||||||
func (l *Loop) firewallBlockPort(ctx context.Context) {
|
|
||||||
port := l.state.GetPortForwarded()
|
|
||||||
if port == 0 {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
err := l.portAllower.RemoveAllowedPort(ctx, port)
|
|
||||||
if err != nil {
|
|
||||||
l.logger.Error("cannot block previous port in firewall: " + err.Error())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// firewallAllowPort obtains the state port thread safely and allows
|
|
||||||
// it in the firewall if it is not the zero value (0).
|
|
||||||
func (l *Loop) firewallAllowPort(ctx context.Context) {
|
|
||||||
port := l.state.GetPortForwarded()
|
|
||||||
if port == 0 {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
startData := l.state.GetStartData()
|
|
||||||
err := l.portAllower.SetAllowedPort(ctx, port, startData.Interface)
|
|
||||||
if err != nil {
|
|
||||||
l.logger.Error("cannot allow port: " + err.Error())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,37 +0,0 @@
|
|||||||
package portforward
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"os"
|
|
||||||
)
|
|
||||||
|
|
||||||
func (l *Loop) removePortForwardedFile() {
|
|
||||||
filepath := *l.state.GetSettings().Filepath
|
|
||||||
l.logger.Info("removing port file " + filepath)
|
|
||||||
if err := os.Remove(filepath); err != nil {
|
|
||||||
l.logger.Error(err.Error())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (l *Loop) writePortForwardedFile(port uint16) {
|
|
||||||
filepath := *l.state.GetSettings().Filepath
|
|
||||||
l.logger.Info("writing port file " + filepath)
|
|
||||||
if err := writePortForwardedToFile(filepath, port, l.puid, l.pgid); err != nil {
|
|
||||||
l.logger.Error("writing port forwarded to file: " + err.Error())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func writePortForwardedToFile(filepath string, port uint16, uid, gid int) (err error) {
|
|
||||||
const perms = os.FileMode(0644)
|
|
||||||
err = os.WriteFile(filepath, []byte(fmt.Sprint(port)), perms)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("writing file: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
err = os.Chown(filepath, uid, gid)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("chowning file: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
@@ -1,5 +0,0 @@
|
|||||||
package portforward
|
|
||||||
|
|
||||||
func (l *Loop) GetPortForwarded() (port uint16) {
|
|
||||||
return l.state.GetPortForwarded()
|
|
||||||
}
|
|
||||||
@@ -1,22 +0,0 @@
|
|||||||
package portforward
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"time"
|
|
||||||
)
|
|
||||||
|
|
||||||
func (l *Loop) logAndWait(ctx context.Context, err error) {
|
|
||||||
if err != nil {
|
|
||||||
l.logger.Error(err.Error())
|
|
||||||
}
|
|
||||||
l.logger.Info("retrying in " + l.backoffTime.String())
|
|
||||||
timer := time.NewTimer(l.backoffTime)
|
|
||||||
l.backoffTime *= 2
|
|
||||||
select {
|
|
||||||
case <-timer.C:
|
|
||||||
case <-ctx.Done():
|
|
||||||
if !timer.Stop() {
|
|
||||||
<-timer.C
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -2,9 +2,29 @@ package portforward
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"net/netip"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
type Service interface {
|
||||||
|
Start(ctx context.Context) (runError <-chan error, err error)
|
||||||
|
Stop() (err error)
|
||||||
|
GetPortForwarded() (port uint16)
|
||||||
|
}
|
||||||
|
|
||||||
|
type Routing interface {
|
||||||
|
VPNLocalGatewayIP(vpnInterface string) (gateway netip.Addr, err error)
|
||||||
|
}
|
||||||
|
|
||||||
type PortAllower interface {
|
type PortAllower interface {
|
||||||
SetAllowedPort(ctx context.Context, port uint16, intf string) (err error)
|
SetAllowedPort(ctx context.Context, port uint16, intf string) (err error)
|
||||||
RemoveAllowedPort(ctx context.Context, port uint16) (err error)
|
RemoveAllowedPort(ctx context.Context, port uint16) (err error)
|
||||||
|
RedirectPort(ctx context.Context, intf string, sourcePort,
|
||||||
|
destinationPort uint16) (err error)
|
||||||
|
}
|
||||||
|
|
||||||
|
type Logger interface {
|
||||||
|
Debug(s string)
|
||||||
|
Info(s string)
|
||||||
|
Warn(s string)
|
||||||
|
Error(s string)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +0,0 @@
|
|||||||
package portforward
|
|
||||||
|
|
||||||
type Logger interface {
|
|
||||||
Info(s string)
|
|
||||||
Warn(s string)
|
|
||||||
Error(s string)
|
|
||||||
}
|
|
||||||
@@ -1,64 +1,162 @@
|
|||||||
package portforward
|
package portforward
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
"net/http"
|
"net/http"
|
||||||
"sync"
|
"sync"
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/qdm12/gluetun/internal/configuration/settings"
|
"github.com/qdm12/gluetun/internal/configuration/settings"
|
||||||
"github.com/qdm12/gluetun/internal/constants"
|
"github.com/qdm12/gluetun/internal/portforward/service"
|
||||||
"github.com/qdm12/gluetun/internal/loopstate"
|
|
||||||
"github.com/qdm12/gluetun/internal/models"
|
|
||||||
"github.com/qdm12/gluetun/internal/portforward/state"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
type Loop struct {
|
type Loop struct {
|
||||||
statusManager *loopstate.State
|
// State
|
||||||
state *state.State
|
settings Settings
|
||||||
// Fixed parameters
|
settingsMutex sync.RWMutex
|
||||||
puid int
|
service Service
|
||||||
pgid int
|
// Fixed injected objets
|
||||||
// Objects
|
routing Routing
|
||||||
client *http.Client
|
client *http.Client
|
||||||
portAllower PortAllower
|
portAllower PortAllower
|
||||||
logger Logger
|
logger Logger
|
||||||
|
// Fixed parameters
|
||||||
|
uid, gid int
|
||||||
// Internal channels and locks
|
// Internal channels and locks
|
||||||
start chan struct{}
|
// runCtx is used to detect when the loop has exited
|
||||||
running chan models.LoopStatus
|
// when performing an update
|
||||||
stop chan struct{}
|
runCtx context.Context //nolint:containedctx
|
||||||
stopped chan struct{}
|
runCancel context.CancelFunc
|
||||||
startMu sync.Mutex
|
runDone <-chan struct{}
|
||||||
backoffTime time.Duration
|
updateTrigger chan<- Settings
|
||||||
userTrigger bool
|
updatedResult <-chan error
|
||||||
}
|
}
|
||||||
|
|
||||||
const defaultBackoffTime = 5 * time.Second
|
func NewLoop(settings settings.PortForwarding, routing Routing,
|
||||||
|
|
||||||
func NewLoop(settings settings.PortForwarding,
|
|
||||||
client *http.Client, portAllower PortAllower,
|
client *http.Client, portAllower PortAllower,
|
||||||
logger Logger, puid, pgid int) *Loop {
|
logger Logger, uid, gid int) *Loop {
|
||||||
start := make(chan struct{})
|
|
||||||
running := make(chan models.LoopStatus)
|
|
||||||
stop := make(chan struct{})
|
|
||||||
stopped := make(chan struct{})
|
|
||||||
|
|
||||||
statusManager := loopstate.New(constants.Stopped, start, running, stop, stopped)
|
|
||||||
state := state.New(statusManager, settings)
|
|
||||||
|
|
||||||
return &Loop{
|
return &Loop{
|
||||||
statusManager: statusManager,
|
settings: Settings{
|
||||||
state: state,
|
VPNIsUp: ptrTo(false),
|
||||||
puid: puid,
|
Service: service.Settings{
|
||||||
pgid: pgid,
|
Enabled: settings.Enabled,
|
||||||
// Objects
|
Filepath: *settings.Filepath,
|
||||||
|
ListeningPort: *settings.ListeningPort,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
routing: routing,
|
||||||
client: client,
|
client: client,
|
||||||
portAllower: portAllower,
|
portAllower: portAllower,
|
||||||
logger: logger,
|
logger: logger,
|
||||||
start: start,
|
uid: uid,
|
||||||
running: running,
|
gid: gid,
|
||||||
stop: stop,
|
|
||||||
stopped: stopped,
|
|
||||||
userTrigger: true,
|
|
||||||
backoffTime: defaultBackoffTime,
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (l *Loop) String() string {
|
||||||
|
return "port forwarding loop"
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *Loop) Start(_ context.Context) (runError <-chan error, _ error) {
|
||||||
|
l.runCtx, l.runCancel = context.WithCancel(context.Background())
|
||||||
|
runDone := make(chan struct{})
|
||||||
|
l.runDone = runDone
|
||||||
|
|
||||||
|
updateTrigger := make(chan Settings)
|
||||||
|
l.updateTrigger = updateTrigger
|
||||||
|
updateResult := make(chan error)
|
||||||
|
l.updatedResult = updateResult
|
||||||
|
runErrorCh := make(chan error)
|
||||||
|
|
||||||
|
go l.run(l.runCtx, runDone, runErrorCh, updateTrigger, updateResult)
|
||||||
|
|
||||||
|
return runErrorCh, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *Loop) run(runCtx context.Context, runDone chan<- struct{},
|
||||||
|
runErrorCh chan<- error, updateTrigger <-chan Settings,
|
||||||
|
updateResult chan<- error) {
|
||||||
|
defer close(runDone)
|
||||||
|
|
||||||
|
var serviceRunError <-chan error
|
||||||
|
for {
|
||||||
|
updateReceived := false
|
||||||
|
select {
|
||||||
|
case <-runCtx.Done():
|
||||||
|
// Stop call takes care of stopping the service
|
||||||
|
return
|
||||||
|
case partialUpdate := <-updateTrigger:
|
||||||
|
updatedSettings, err := l.settings.updateWith(partialUpdate, *l.settings.VPNIsUp)
|
||||||
|
if err != nil {
|
||||||
|
updateResult <- err
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
updateReceived = true
|
||||||
|
l.settingsMutex.Lock()
|
||||||
|
l.settings = updatedSettings
|
||||||
|
l.settingsMutex.Unlock()
|
||||||
|
case err := <-serviceRunError:
|
||||||
|
l.logger.Error(err.Error())
|
||||||
|
}
|
||||||
|
|
||||||
|
firstRun := serviceRunError == nil
|
||||||
|
if !firstRun {
|
||||||
|
err := l.service.Stop()
|
||||||
|
if err != nil {
|
||||||
|
runErrorCh <- fmt.Errorf("stopping previous service: %w", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
serviceSettings := l.settings.Service.Copy()
|
||||||
|
// Only enable port forward if the VPN tunnel is up
|
||||||
|
*serviceSettings.Enabled = *serviceSettings.Enabled && *l.settings.VPNIsUp
|
||||||
|
|
||||||
|
l.service = service.New(serviceSettings, l.routing, l.client,
|
||||||
|
l.portAllower, l.logger, l.uid, l.gid)
|
||||||
|
|
||||||
|
var err error
|
||||||
|
serviceRunError, err = l.service.Start(runCtx)
|
||||||
|
if updateReceived {
|
||||||
|
// Signal to the Update call that the service has started
|
||||||
|
// and if it failed to start.
|
||||||
|
updateResult <- err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *Loop) UpdateWith(partialUpdate Settings) (err error) {
|
||||||
|
select {
|
||||||
|
case l.updateTrigger <- partialUpdate:
|
||||||
|
select {
|
||||||
|
case err = <-l.updatedResult:
|
||||||
|
return err
|
||||||
|
case <-l.runCtx.Done():
|
||||||
|
return l.runCtx.Err()
|
||||||
|
}
|
||||||
|
case <-l.runCtx.Done():
|
||||||
|
// loop has been stopped, no update can be done
|
||||||
|
return l.runCtx.Err()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *Loop) Stop() (err error) {
|
||||||
|
l.runCancel()
|
||||||
|
<-l.runDone
|
||||||
|
|
||||||
|
if l.service != nil {
|
||||||
|
return l.service.Stop()
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *Loop) GetPortForwarded() (port uint16) {
|
||||||
|
if l.service == nil {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
return l.service.GetPortForwarded()
|
||||||
|
}
|
||||||
|
|
||||||
|
func ptrTo[T any](value T) *T {
|
||||||
|
return &value
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,98 +0,0 @@
|
|||||||
package portforward
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"strconv"
|
|
||||||
|
|
||||||
"github.com/qdm12/gluetun/internal/constants"
|
|
||||||
)
|
|
||||||
|
|
||||||
func (l *Loop) Run(ctx context.Context, done chan<- struct{}) {
|
|
||||||
defer close(done)
|
|
||||||
|
|
||||||
select {
|
|
||||||
case <-l.start: // l.state.SetStartData called beforehand
|
|
||||||
case <-ctx.Done():
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
for ctx.Err() == nil {
|
|
||||||
pfCtx, pfCancel := context.WithCancel(ctx)
|
|
||||||
|
|
||||||
portCh := make(chan uint16)
|
|
||||||
errorCh := make(chan error)
|
|
||||||
|
|
||||||
startData := l.state.GetStartData()
|
|
||||||
|
|
||||||
go func(ctx context.Context, startData StartData) {
|
|
||||||
port, err := startData.PortForwarder.PortForward(ctx, l.client, l.logger,
|
|
||||||
startData.Gateway, startData.ServerName)
|
|
||||||
if err != nil {
|
|
||||||
errorCh <- err
|
|
||||||
return
|
|
||||||
}
|
|
||||||
portCh <- port
|
|
||||||
|
|
||||||
// Infinite loop
|
|
||||||
err = startData.PortForwarder.KeepPortForward(ctx,
|
|
||||||
startData.Gateway, startData.ServerName)
|
|
||||||
errorCh <- err
|
|
||||||
}(pfCtx, startData)
|
|
||||||
|
|
||||||
if l.userTrigger {
|
|
||||||
l.userTrigger = false
|
|
||||||
l.running <- constants.Running
|
|
||||||
} else { // crash
|
|
||||||
l.backoffTime = defaultBackoffTime
|
|
||||||
l.statusManager.SetStatus(constants.Running)
|
|
||||||
}
|
|
||||||
|
|
||||||
stayHere := true
|
|
||||||
stopped := false
|
|
||||||
for stayHere {
|
|
||||||
select {
|
|
||||||
case <-ctx.Done():
|
|
||||||
pfCancel()
|
|
||||||
if stopped {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
<-errorCh
|
|
||||||
close(errorCh)
|
|
||||||
close(portCh)
|
|
||||||
l.removePortForwardedFile()
|
|
||||||
l.firewallBlockPort(ctx)
|
|
||||||
l.state.SetPortForwarded(0)
|
|
||||||
return
|
|
||||||
case <-l.start:
|
|
||||||
l.userTrigger = true
|
|
||||||
l.logger.Info("starting")
|
|
||||||
pfCancel()
|
|
||||||
stayHere = false
|
|
||||||
case <-l.stop:
|
|
||||||
l.userTrigger = true
|
|
||||||
l.logger.Info("stopping")
|
|
||||||
pfCancel()
|
|
||||||
<-errorCh
|
|
||||||
l.removePortForwardedFile()
|
|
||||||
l.firewallBlockPort(ctx)
|
|
||||||
l.state.SetPortForwarded(0)
|
|
||||||
l.stopped <- struct{}{}
|
|
||||||
stopped = true
|
|
||||||
case port := <-portCh:
|
|
||||||
l.logger.Info("port forwarded is " + strconv.Itoa(int(port)))
|
|
||||||
l.firewallBlockPort(ctx)
|
|
||||||
l.state.SetPortForwarded(port)
|
|
||||||
l.firewallAllowPort(ctx)
|
|
||||||
l.writePortForwardedFile(port)
|
|
||||||
case err := <-errorCh:
|
|
||||||
pfCancel()
|
|
||||||
close(errorCh)
|
|
||||||
close(portCh)
|
|
||||||
l.statusManager.SetStatus(constants.Crashed)
|
|
||||||
l.logAndWait(ctx, err)
|
|
||||||
stayHere = false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
pfCancel() // for linting
|
|
||||||
}
|
|
||||||
}
|
|
||||||
23
internal/portforward/service/fs.go
Normal file
23
internal/portforward/service/fs.go
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
package service
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
)
|
||||||
|
|
||||||
|
func (s *Service) writePortForwardedFile(port uint16) (err error) {
|
||||||
|
filepath := s.settings.Filepath
|
||||||
|
s.logger.Info("writing port file " + filepath)
|
||||||
|
const perms = os.FileMode(0644)
|
||||||
|
err = os.WriteFile(filepath, []byte(fmt.Sprint(port)), perms)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("writing file: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
err = os.Chown(filepath, s.puid, s.pgid)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("chowning file: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
33
internal/portforward/service/interfaces.go
Normal file
33
internal/portforward/service/interfaces.go
Normal file
@@ -0,0 +1,33 @@
|
|||||||
|
package service
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"net/netip"
|
||||||
|
|
||||||
|
"github.com/qdm12/gluetun/internal/provider/utils"
|
||||||
|
)
|
||||||
|
|
||||||
|
type PortAllower interface {
|
||||||
|
SetAllowedPort(ctx context.Context, port uint16, intf string) (err error)
|
||||||
|
RemoveAllowedPort(ctx context.Context, port uint16) (err error)
|
||||||
|
RedirectPort(ctx context.Context, intf string, sourcePort,
|
||||||
|
destinationPort uint16) (err error)
|
||||||
|
}
|
||||||
|
|
||||||
|
type Routing interface {
|
||||||
|
VPNLocalGatewayIP(vpnInterface string) (gateway netip.Addr, err error)
|
||||||
|
}
|
||||||
|
|
||||||
|
type Logger interface {
|
||||||
|
Debug(s string)
|
||||||
|
Info(s string)
|
||||||
|
Warn(s string)
|
||||||
|
Error(s string)
|
||||||
|
}
|
||||||
|
|
||||||
|
type PortForwarder interface {
|
||||||
|
Name() string
|
||||||
|
PortForward(ctx context.Context, objects utils.PortForwardObjects) (
|
||||||
|
port uint16, err error)
|
||||||
|
KeepPortForward(ctx context.Context, objects utils.PortForwardObjects) (err error)
|
||||||
|
}
|
||||||
47
internal/portforward/service/service.go
Normal file
47
internal/portforward/service/service.go
Normal file
@@ -0,0 +1,47 @@
|
|||||||
|
package service
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"net/http"
|
||||||
|
"sync"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Service struct {
|
||||||
|
// State
|
||||||
|
portMutex sync.RWMutex
|
||||||
|
port uint16
|
||||||
|
// Fixed parameters
|
||||||
|
settings Settings
|
||||||
|
puid int
|
||||||
|
pgid int
|
||||||
|
// Fixed injected objets
|
||||||
|
routing Routing
|
||||||
|
client *http.Client
|
||||||
|
portAllower PortAllower
|
||||||
|
logger Logger
|
||||||
|
// Internal channels and locks
|
||||||
|
startStopMutex sync.Mutex
|
||||||
|
keepPortCancel context.CancelFunc
|
||||||
|
keepPortDoneCh <-chan struct{}
|
||||||
|
}
|
||||||
|
|
||||||
|
func New(settings Settings, routing Routing, client *http.Client,
|
||||||
|
portAllower PortAllower, logger Logger, puid, pgid int) *Service {
|
||||||
|
return &Service{
|
||||||
|
// Fixed parameters
|
||||||
|
settings: settings,
|
||||||
|
puid: puid,
|
||||||
|
pgid: pgid,
|
||||||
|
// Fixed injected objets
|
||||||
|
routing: routing,
|
||||||
|
client: client,
|
||||||
|
portAllower: portAllower,
|
||||||
|
logger: logger,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Service) GetPortForwarded() (port uint16) {
|
||||||
|
s.portMutex.RLock()
|
||||||
|
defer s.portMutex.RUnlock()
|
||||||
|
return s.port
|
||||||
|
}
|
||||||
68
internal/portforward/service/settings.go
Normal file
68
internal/portforward/service/settings.go
Normal file
@@ -0,0 +1,68 @@
|
|||||||
|
package service
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"github.com/qdm12/gluetun/internal/constants/providers"
|
||||||
|
"github.com/qdm12/gosettings"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Settings struct {
|
||||||
|
Enabled *bool
|
||||||
|
PortForwarder PortForwarder
|
||||||
|
Filepath string
|
||||||
|
Interface string // needed for PIA and ProtonVPN, tun0 for example
|
||||||
|
ServerName string // needed for PIA
|
||||||
|
ListeningPort uint16
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s Settings) Copy() (copied Settings) {
|
||||||
|
copied.Enabled = gosettings.CopyPointer(s.Enabled)
|
||||||
|
copied.PortForwarder = s.PortForwarder
|
||||||
|
copied.Filepath = s.Filepath
|
||||||
|
copied.Interface = s.Interface
|
||||||
|
copied.ServerName = s.ServerName
|
||||||
|
copied.ListeningPort = s.ListeningPort
|
||||||
|
return copied
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Settings) OverrideWith(update Settings) {
|
||||||
|
s.Enabled = gosettings.OverrideWithPointer(s.Enabled, update.Enabled)
|
||||||
|
s.PortForwarder = gosettings.OverrideWithInterface(s.PortForwarder, update.PortForwarder)
|
||||||
|
s.Filepath = gosettings.OverrideWithString(s.Filepath, update.Filepath)
|
||||||
|
s.Interface = gosettings.OverrideWithString(s.Interface, update.Interface)
|
||||||
|
s.ServerName = gosettings.OverrideWithString(s.ServerName, update.ServerName)
|
||||||
|
s.ListeningPort = gosettings.OverrideWithNumber(s.ListeningPort, update.ListeningPort)
|
||||||
|
}
|
||||||
|
|
||||||
|
var (
|
||||||
|
ErrPortForwarderNotSet = errors.New("port forwarder not set")
|
||||||
|
ErrServerNameNotSet = errors.New("server name not set")
|
||||||
|
ErrFilepathNotSet = errors.New("file path not set")
|
||||||
|
ErrInterfaceNotSet = errors.New("interface not set")
|
||||||
|
)
|
||||||
|
|
||||||
|
func (s *Settings) Validate(forStartup bool) (err error) {
|
||||||
|
// Minimal validation
|
||||||
|
if s.Filepath == "" {
|
||||||
|
return fmt.Errorf("%w", ErrFilepathNotSet)
|
||||||
|
}
|
||||||
|
|
||||||
|
if !forStartup {
|
||||||
|
// No additional validation needed if the service
|
||||||
|
// is not to be started with the given settings.
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Startup validation requires additional fields set.
|
||||||
|
switch {
|
||||||
|
case s.PortForwarder == nil:
|
||||||
|
return fmt.Errorf("%w", ErrPortForwarderNotSet)
|
||||||
|
case s.Interface == "":
|
||||||
|
return fmt.Errorf("%w", ErrInterfaceNotSet)
|
||||||
|
case s.PortForwarder.Name() == providers.PrivateInternetAccess && s.ServerName == "":
|
||||||
|
return fmt.Errorf("%w", ErrServerNameNotSet)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
81
internal/portforward/service/start.go
Normal file
81
internal/portforward/service/start.go
Normal file
@@ -0,0 +1,81 @@
|
|||||||
|
package service
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"github.com/qdm12/gluetun/internal/provider/utils"
|
||||||
|
)
|
||||||
|
|
||||||
|
func (s *Service) Start(ctx context.Context) (runError <-chan error, err error) {
|
||||||
|
s.startStopMutex.Lock()
|
||||||
|
defer s.startStopMutex.Unlock()
|
||||||
|
|
||||||
|
if !*s.settings.Enabled {
|
||||||
|
return nil, nil //nolint:nilnil
|
||||||
|
}
|
||||||
|
|
||||||
|
s.logger.Info("starting")
|
||||||
|
|
||||||
|
gateway, err := s.routing.VPNLocalGatewayIP(s.settings.Interface)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("getting VPN local gateway IP: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
obj := utils.PortForwardObjects{
|
||||||
|
Logger: s.logger,
|
||||||
|
Gateway: gateway,
|
||||||
|
Client: s.client,
|
||||||
|
ServerName: s.settings.ServerName,
|
||||||
|
}
|
||||||
|
port, err := s.settings.PortForwarder.PortForward(ctx, obj)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("port forwarding for the first time: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
s.logger.Info("port forwarded is " + fmt.Sprint(int(port)))
|
||||||
|
|
||||||
|
err = s.portAllower.SetAllowedPort(ctx, port, s.settings.Interface)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("allowing port in firewall: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if s.settings.ListeningPort != 0 {
|
||||||
|
err = s.portAllower.RedirectPort(ctx, s.settings.Interface, port, s.settings.ListeningPort)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("redirecting port in firewall: %w", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
err = s.writePortForwardedFile(port)
|
||||||
|
if err != nil {
|
||||||
|
_ = s.cleanup()
|
||||||
|
return nil, fmt.Errorf("writing port file: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
s.portMutex.Lock()
|
||||||
|
s.port = port
|
||||||
|
s.portMutex.Unlock()
|
||||||
|
|
||||||
|
keepPortCtx, keepPortCancel := context.WithCancel(context.Background())
|
||||||
|
s.keepPortCancel = keepPortCancel
|
||||||
|
runErrorCh := make(chan error)
|
||||||
|
keepPortDoneCh := make(chan struct{})
|
||||||
|
s.keepPortDoneCh = keepPortDoneCh
|
||||||
|
|
||||||
|
go func(ctx context.Context, portForwarder PortForwarder,
|
||||||
|
obj utils.PortForwardObjects, runError chan<- error, doneCh chan<- struct{}) {
|
||||||
|
defer close(doneCh)
|
||||||
|
err = portForwarder.KeepPortForward(ctx, obj)
|
||||||
|
crashed := ctx.Err() == nil
|
||||||
|
if !crashed { // stopped by Stop call
|
||||||
|
return
|
||||||
|
}
|
||||||
|
s.startStopMutex.Lock()
|
||||||
|
defer s.startStopMutex.Unlock()
|
||||||
|
_ = s.cleanup()
|
||||||
|
runError <- err
|
||||||
|
}(keepPortCtx, s.settings.PortForwarder, obj, runErrorCh, keepPortDoneCh)
|
||||||
|
|
||||||
|
return runErrorCh, nil
|
||||||
|
}
|
||||||
57
internal/portforward/service/stop.go
Normal file
57
internal/portforward/service/stop.go
Normal file
@@ -0,0 +1,57 @@
|
|||||||
|
package service
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
)
|
||||||
|
|
||||||
|
func (s *Service) Stop() (err error) {
|
||||||
|
s.startStopMutex.Lock()
|
||||||
|
defer s.startStopMutex.Unlock()
|
||||||
|
|
||||||
|
s.portMutex.RLock()
|
||||||
|
serviceNotRunning := s.port == 0
|
||||||
|
s.portMutex.RUnlock()
|
||||||
|
if serviceNotRunning {
|
||||||
|
// TODO replace with goservices.ErrAlreadyStopped
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
s.logger.Info("stopping")
|
||||||
|
|
||||||
|
s.keepPortCancel()
|
||||||
|
<-s.keepPortDoneCh
|
||||||
|
|
||||||
|
return s.cleanup()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Service) cleanup() (err error) {
|
||||||
|
s.portMutex.Lock()
|
||||||
|
defer s.portMutex.Unlock()
|
||||||
|
|
||||||
|
err = s.portAllower.RemoveAllowedPort(context.Background(), s.port)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("blocking previous port in firewall: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if s.settings.ListeningPort != 0 {
|
||||||
|
ctx := context.Background()
|
||||||
|
const listeningPort = 0 // 0 to clear the redirection
|
||||||
|
err = s.portAllower.RedirectPort(ctx, s.settings.Interface, s.port, listeningPort)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("removing previous port redirection in firewall: %w", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
s.port = 0
|
||||||
|
|
||||||
|
filepath := s.settings.Filepath
|
||||||
|
s.logger.Info("removing port file " + filepath)
|
||||||
|
err = os.Remove(filepath)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("removing port file: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
@@ -1,16 +1,44 @@
|
|||||||
package portforward
|
package portforward
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"github.com/qdm12/gluetun/internal/portforward/service"
|
||||||
|
"github.com/qdm12/gosettings"
|
||||||
"github.com/qdm12/gluetun/internal/configuration/settings"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func (l *Loop) GetSettings() (settings settings.PortForwarding) {
|
type Settings struct {
|
||||||
return l.state.GetSettings()
|
// VPNIsUp can be optionally set to signal the loop
|
||||||
|
// the VPN is up (true) or down (false). If left to nil,
|
||||||
|
// it is assumed the VPN is in the same previous state.
|
||||||
|
VPNIsUp *bool
|
||||||
|
Service service.Settings
|
||||||
}
|
}
|
||||||
|
|
||||||
func (l *Loop) SetSettings(ctx context.Context, settings settings.PortForwarding) (
|
// updateWith deep copies the receiving settings, overrides the copy with
|
||||||
outcome string) {
|
// fields set in the partialUpdate argument, validates the new settings
|
||||||
return l.state.SetSettings(ctx, settings)
|
// and returns them if they are valid, or returns an error otherwise.
|
||||||
|
// In all cases, the receiving settings are unmodified.
|
||||||
|
func (s Settings) updateWith(partialUpdate Settings,
|
||||||
|
forStartup bool) (updated Settings, err error) {
|
||||||
|
updated = s.copy()
|
||||||
|
updated.overrideWith(partialUpdate)
|
||||||
|
err = updated.validate(forStartup)
|
||||||
|
if err != nil {
|
||||||
|
return updated, err
|
||||||
|
}
|
||||||
|
return updated, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s Settings) copy() (copied Settings) {
|
||||||
|
copied.VPNIsUp = gosettings.CopyPointer(s.VPNIsUp)
|
||||||
|
copied.Service = s.Service.Copy()
|
||||||
|
return copied
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Settings) overrideWith(update Settings) {
|
||||||
|
s.VPNIsUp = gosettings.OverrideWithPointer(s.VPNIsUp, update.VPNIsUp)
|
||||||
|
s.Service.OverrideWith(update.Service)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s Settings) validate(forStartup bool) (err error) {
|
||||||
|
return s.Service.Validate(forStartup)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,17 +0,0 @@
|
|||||||
package state
|
|
||||||
|
|
||||||
// GetPortForwarded is used by the control HTTP server
|
|
||||||
// to obtain the port currently forwarded.
|
|
||||||
func (s *State) GetPortForwarded() (port uint16) {
|
|
||||||
s.portForwardedMu.RLock()
|
|
||||||
defer s.portForwardedMu.RUnlock()
|
|
||||||
return s.portForwarded
|
|
||||||
}
|
|
||||||
|
|
||||||
// SetPortForwarded is only used from within the OpenVPN loop
|
|
||||||
// to set the port forwarded.
|
|
||||||
func (s *State) SetPortForwarded(port uint16) {
|
|
||||||
s.portForwardedMu.Lock()
|
|
||||||
defer s.portForwardedMu.Unlock()
|
|
||||||
s.portForwarded = port
|
|
||||||
}
|
|
||||||
@@ -1,49 +0,0 @@
|
|||||||
package state
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"os"
|
|
||||||
"reflect"
|
|
||||||
|
|
||||||
"github.com/qdm12/gluetun/internal/configuration/settings"
|
|
||||||
"github.com/qdm12/gluetun/internal/constants"
|
|
||||||
)
|
|
||||||
|
|
||||||
func (s *State) GetSettings() (settings settings.PortForwarding) {
|
|
||||||
s.settingsMu.RLock()
|
|
||||||
defer s.settingsMu.RUnlock()
|
|
||||||
return s.settings
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *State) SetSettings(ctx context.Context, settings settings.PortForwarding) (
|
|
||||||
outcome string) {
|
|
||||||
s.settingsMu.Lock()
|
|
||||||
|
|
||||||
settingsUnchanged := reflect.DeepEqual(s.settings, settings)
|
|
||||||
if settingsUnchanged {
|
|
||||||
s.settingsMu.Unlock()
|
|
||||||
return "settings left unchanged"
|
|
||||||
}
|
|
||||||
|
|
||||||
if s.settings.Filepath != settings.Filepath {
|
|
||||||
_ = os.Rename(*s.settings.Filepath, *settings.Filepath)
|
|
||||||
}
|
|
||||||
|
|
||||||
newEnabled := *settings.Enabled
|
|
||||||
previousEnabled := *s.settings.Enabled
|
|
||||||
|
|
||||||
s.settings = settings
|
|
||||||
s.settingsMu.Unlock()
|
|
||||||
|
|
||||||
switch {
|
|
||||||
case !newEnabled && !previousEnabled:
|
|
||||||
case newEnabled && previousEnabled:
|
|
||||||
// no need to restart for now since we os.Rename the file here.
|
|
||||||
case newEnabled && !previousEnabled:
|
|
||||||
_, _ = s.statusApplier.ApplyStatus(ctx, constants.Running)
|
|
||||||
case !newEnabled && previousEnabled:
|
|
||||||
_, _ = s.statusApplier.ApplyStatus(ctx, constants.Stopped)
|
|
||||||
}
|
|
||||||
|
|
||||||
return "settings updated"
|
|
||||||
}
|
|
||||||
@@ -1,26 +0,0 @@
|
|||||||
package state
|
|
||||||
|
|
||||||
import (
|
|
||||||
"net/netip"
|
|
||||||
|
|
||||||
"github.com/qdm12/gluetun/internal/provider"
|
|
||||||
)
|
|
||||||
|
|
||||||
type StartData struct {
|
|
||||||
PortForwarder provider.PortForwarder
|
|
||||||
Gateway netip.Addr // needed for PIA
|
|
||||||
ServerName string // needed for PIA
|
|
||||||
Interface string // tun0 for example
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *State) GetStartData() (startData StartData) {
|
|
||||||
s.startDataMu.RLock()
|
|
||||||
defer s.startDataMu.RUnlock()
|
|
||||||
return s.startData
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *State) SetStartData(startData StartData) {
|
|
||||||
s.startDataMu.Lock()
|
|
||||||
defer s.startDataMu.Unlock()
|
|
||||||
s.startData = startData
|
|
||||||
}
|
|
||||||
@@ -1,35 +0,0 @@
|
|||||||
package state
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"sync"
|
|
||||||
|
|
||||||
"github.com/qdm12/gluetun/internal/configuration/settings"
|
|
||||||
"github.com/qdm12/gluetun/internal/models"
|
|
||||||
)
|
|
||||||
|
|
||||||
func New(statusApplier StatusApplier,
|
|
||||||
settings settings.PortForwarding) *State {
|
|
||||||
return &State{
|
|
||||||
statusApplier: statusApplier,
|
|
||||||
settings: settings,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
type State struct {
|
|
||||||
statusApplier StatusApplier
|
|
||||||
|
|
||||||
settings settings.PortForwarding
|
|
||||||
settingsMu sync.RWMutex
|
|
||||||
|
|
||||||
portForwarded uint16
|
|
||||||
portForwardedMu sync.RWMutex
|
|
||||||
|
|
||||||
startData StartData
|
|
||||||
startDataMu sync.RWMutex
|
|
||||||
}
|
|
||||||
|
|
||||||
type StatusApplier interface {
|
|
||||||
ApplyStatus(ctx context.Context, status models.LoopStatus) (
|
|
||||||
outcome string, err error)
|
|
||||||
}
|
|
||||||
@@ -1,27 +0,0 @@
|
|||||||
package portforward
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
|
|
||||||
"github.com/qdm12/gluetun/internal/constants"
|
|
||||||
"github.com/qdm12/gluetun/internal/models"
|
|
||||||
"github.com/qdm12/gluetun/internal/portforward/state"
|
|
||||||
)
|
|
||||||
|
|
||||||
func (l *Loop) GetStatus() (status models.LoopStatus) {
|
|
||||||
return l.statusManager.GetStatus()
|
|
||||||
}
|
|
||||||
|
|
||||||
type StartData = state.StartData
|
|
||||||
|
|
||||||
func (l *Loop) Start(ctx context.Context, data StartData) (
|
|
||||||
outcome string, err error) {
|
|
||||||
l.startMu.Lock()
|
|
||||||
defer l.startMu.Unlock()
|
|
||||||
l.state.SetStartData(data)
|
|
||||||
return l.statusManager.ApplyStatus(ctx, constants.Running)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (l *Loop) Stop(ctx context.Context) (outcome string, err error) {
|
|
||||||
return l.statusManager.ApplyStatus(ctx, constants.Stopped)
|
|
||||||
}
|
|
||||||
@@ -15,8 +15,8 @@ func (p *Provider) OpenVPNConfig(connection models.Connection,
|
|||||||
AuthUserPass: true,
|
AuthUserPass: true,
|
||||||
RemoteCertTLS: true,
|
RemoteCertTLS: true,
|
||||||
Auth: openvpn.SHA512,
|
Auth: openvpn.SHA512,
|
||||||
CA: "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", //nolint:lll
|
CAs: []string{"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"}, //nolint:lll
|
||||||
TLSCrypt: "a3a7d8f4e778d279d9076a8d47a9aa0c6054aed5752ddefa5efac1ea982740f1ffcabadf0d3709dae18c1fad61e14f72a8eb7cb931ed0209e67c1d325353a657a1198ef649f1c23861a2a19f2c6b27aa5e43be761e0c71e9c2e8d33b75af289effb1b1e4ec603d865f74e2b4348ff631c5c81202d90003ed263dca4022aa9861520e00cc26e40fa171b9985a2763ccb4c63560b7e6b0f897978fb25a2d5889cd6d46a29509fa09830aff18d6e81a8dc1a0182402e3039c3316180e618705ca35f2763f8a62ca5983d145faa2276532ae5e18459a0b729dc67f41b928e592b39467ec3d79c70205595718b1bce56ca4ff58e692ce09c8282d2770d2bf5c217c06", //nolint:lll
|
TLSCrypt: "a3a7d8f4e778d279d9076a8d47a9aa0c6054aed5752ddefa5efac1ea982740f1ffcabadf0d3709dae18c1fad61e14f72a8eb7cb931ed0209e67c1d325353a657a1198ef649f1c23861a2a19f2c6b27aa5e43be761e0c71e9c2e8d33b75af289effb1b1e4ec603d865f74e2b4348ff631c5c81202d90003ed263dca4022aa9861520e00cc26e40fa171b9985a2763ccb4c63560b7e6b0f897978fb25a2d5889cd6d46a29509fa09830aff18d6e81a8dc1a0182402e3039c3316180e618705ca35f2763f8a62ca5983d145faa2276532ae5e18459a0b729dc67f41b928e592b39467ec3d79c70205595718b1bce56ca4ff58e692ce09c8282d2770d2bf5c217c06", //nolint:lll
|
||||||
ExtraLines: []string{
|
ExtraLines: []string{
|
||||||
"comp-lzo no", // Explicitly disable compression
|
"comp-lzo no", // Explicitly disable compression
|
||||||
"push-peer-info",
|
"push-peer-info",
|
||||||
|
|||||||
@@ -7,23 +7,20 @@ import (
|
|||||||
"github.com/qdm12/gluetun/internal/constants/providers"
|
"github.com/qdm12/gluetun/internal/constants/providers"
|
||||||
"github.com/qdm12/gluetun/internal/provider/airvpn/updater"
|
"github.com/qdm12/gluetun/internal/provider/airvpn/updater"
|
||||||
"github.com/qdm12/gluetun/internal/provider/common"
|
"github.com/qdm12/gluetun/internal/provider/common"
|
||||||
"github.com/qdm12/gluetun/internal/provider/utils"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
type Provider struct {
|
type Provider struct {
|
||||||
storage common.Storage
|
storage common.Storage
|
||||||
randSource rand.Source
|
randSource rand.Source
|
||||||
utils.NoPortForwarder
|
|
||||||
common.Fetcher
|
common.Fetcher
|
||||||
}
|
}
|
||||||
|
|
||||||
func New(storage common.Storage, randSource rand.Source,
|
func New(storage common.Storage, randSource rand.Source,
|
||||||
client *http.Client) *Provider {
|
client *http.Client) *Provider {
|
||||||
return &Provider{
|
return &Provider{
|
||||||
storage: storage,
|
storage: storage,
|
||||||
randSource: randSource,
|
randSource: randSource,
|
||||||
NoPortForwarder: utils.NewNoPortForwarding(providers.Example),
|
Fetcher: updater.New(client),
|
||||||
Fetcher: updater.New(client),
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -8,15 +8,13 @@ import (
|
|||||||
|
|
||||||
type Provider struct {
|
type Provider struct {
|
||||||
extractor Extractor
|
extractor Extractor
|
||||||
utils.NoPortForwarder
|
|
||||||
common.Fetcher
|
common.Fetcher
|
||||||
}
|
}
|
||||||
|
|
||||||
func New(extractor Extractor) *Provider {
|
func New(extractor Extractor) *Provider {
|
||||||
return &Provider{
|
return &Provider{
|
||||||
extractor: extractor,
|
extractor: extractor,
|
||||||
NoPortForwarder: utils.NewNoPortForwarding(providers.Custom),
|
Fetcher: utils.NewNoFetcher(providers.Custom),
|
||||||
Fetcher: utils.NewNoFetcher(providers.Custom),
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -20,7 +20,7 @@ func (p *Provider) OpenVPNConfig(connection models.Connection,
|
|||||||
},
|
},
|
||||||
Auth: openvpn.SHA256,
|
Auth: openvpn.SHA256,
|
||||||
Ping: 10,
|
Ping: 10,
|
||||||
CA: "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", //nolint:lll
|
CAs: []string{"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"}, //nolint:lll
|
||||||
}
|
}
|
||||||
return utils.OpenVPNConfig(providerSettings, connection, settings, ipv6Supported)
|
return utils.OpenVPNConfig(providerSettings, connection, settings, ipv6Supported)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,23 +6,20 @@ import (
|
|||||||
"github.com/qdm12/gluetun/internal/constants/providers"
|
"github.com/qdm12/gluetun/internal/constants/providers"
|
||||||
"github.com/qdm12/gluetun/internal/provider/common"
|
"github.com/qdm12/gluetun/internal/provider/common"
|
||||||
"github.com/qdm12/gluetun/internal/provider/cyberghost/updater"
|
"github.com/qdm12/gluetun/internal/provider/cyberghost/updater"
|
||||||
"github.com/qdm12/gluetun/internal/provider/utils"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
type Provider struct {
|
type Provider struct {
|
||||||
storage common.Storage
|
storage common.Storage
|
||||||
randSource rand.Source
|
randSource rand.Source
|
||||||
utils.NoPortForwarder
|
|
||||||
common.Fetcher
|
common.Fetcher
|
||||||
}
|
}
|
||||||
|
|
||||||
func New(storage common.Storage, randSource rand.Source,
|
func New(storage common.Storage, randSource rand.Source,
|
||||||
parallelResolver common.ParallelResolver) *Provider {
|
parallelResolver common.ParallelResolver) *Provider {
|
||||||
return &Provider{
|
return &Provider{
|
||||||
storage: storage,
|
storage: storage,
|
||||||
randSource: randSource,
|
randSource: randSource,
|
||||||
NoPortForwarder: utils.NewNoPortForwarding(providers.Cyberghost),
|
Fetcher: updater.New(parallelResolver),
|
||||||
Fetcher: updater.New(parallelResolver),
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -19,8 +19,8 @@ func (p *Provider) OpenVPNConfig(connection models.Connection,
|
|||||||
},
|
},
|
||||||
Ping: 5,
|
Ping: 5,
|
||||||
RemoteCertTLS: true,
|
RemoteCertTLS: true,
|
||||||
CA: "MIIDZzCCAk+gAwIBAgIUVwHEFE6geihigDSNkBppm2Zamx0wDQYJKoZIhvcNAQELBQAwQzELMAkGA1UEBhMCQ0ExDzANBgNVBAgMBlF1ZWJlYzERMA8GA1UEBwwITW9udHJlYWwxEDAOBgNVBAoMB0dsdWV0dW4wHhcNMjIwNzAxMTY1MzE5WhcNMjcwNjMwMTY1MzE5WjBDMQswCQYDVQQGEwJDQTEPMA0GA1UECAwGUXVlYmVjMREwDwYDVQQHDAhNb250cmVhbDEQMA4GA1UECgwHR2x1ZXR1bjCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBALmJRhTUr+87NFkHL2PWjIz7efHqQgrWuDQt8oOBHvl0Hm72N+ckO+5Q0zG4XtqlpBjFjGUSjfNUWSrRztbXlMmzDcjHKjYHUPepJpoF100fK2q3XPiFRl6sEXzYeOdFgpaTdmGHS6DL9aWeCoYA/k6NV8YqHXujr14gOYOAWG6cRimpTJf8DtEDcxtp1w6fOEoN0b5PvV7dcpLiva8LYyZKPvFYBzlc5BZxOLvq6bvhQm54R6zoHFpaKOf7FeqhxI6KOQu4IPwU12YBlOP5CbkMAQ1cWWVQ4pnh0Hwh71Sjm848jS/OcammNzsp4xWaKt/pzcix3fpJt/MDP/9fxA8CAwEAAaNTMFEwHQYDVR0OBBYEFCIQ9l28Yy1/3qJvFarXjhKdG9tVMB8GA1UdIwQYMBaAFCIQ9l28Yy1/3qJvFarXjhKdG9tVMA8GA1UdEwEB/wQFMAMBAf8wDQYJKoZIhvcNAQELBQADggEBAKLPmLTppXYTTOOxHhTMyHI0oTl7ID2PQfJsref+jDshB3hib98BC17b9ESpLnwx7ugg17NRl7RYutxjuVw/CK/gwAnTMg3D3mdAnKkMRr3UxnD89KprLIpf7WQCmyJaxalsD5jjgl3kuGM7jf2FJNxQz5RrXBGlQHa465ouov+Rp5v/K5Umyt6wsCZXEbOF0SdUhEGU3nxVbFsoPimNYSHHwc29USnQmyW1O/drFDoTcOK4GdHFEVkrHQgqwU8ay1fYGYfIUDhsV/5AAWgQC41r9FWr+VQgyJC94qmDg0c46RE123dL/YifVUl2DKuJ0aOY+OkSgwknKZ+FQd+8d6k=", //nolint:lll
|
CAs: []string{"MIIDZzCCAk+gAwIBAgIUVwHEFE6geihigDSNkBppm2Zamx0wDQYJKoZIhvcNAQELBQAwQzELMAkGA1UEBhMCQ0ExDzANBgNVBAgMBlF1ZWJlYzERMA8GA1UEBwwITW9udHJlYWwxEDAOBgNVBAoMB0dsdWV0dW4wHhcNMjIwNzAxMTY1MzE5WhcNMjcwNjMwMTY1MzE5WjBDMQswCQYDVQQGEwJDQTEPMA0GA1UECAwGUXVlYmVjMREwDwYDVQQHDAhNb250cmVhbDEQMA4GA1UECgwHR2x1ZXR1bjCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBALmJRhTUr+87NFkHL2PWjIz7efHqQgrWuDQt8oOBHvl0Hm72N+ckO+5Q0zG4XtqlpBjFjGUSjfNUWSrRztbXlMmzDcjHKjYHUPepJpoF100fK2q3XPiFRl6sEXzYeOdFgpaTdmGHS6DL9aWeCoYA/k6NV8YqHXujr14gOYOAWG6cRimpTJf8DtEDcxtp1w6fOEoN0b5PvV7dcpLiva8LYyZKPvFYBzlc5BZxOLvq6bvhQm54R6zoHFpaKOf7FeqhxI6KOQu4IPwU12YBlOP5CbkMAQ1cWWVQ4pnh0Hwh71Sjm848jS/OcammNzsp4xWaKt/pzcix3fpJt/MDP/9fxA8CAwEAAaNTMFEwHQYDVR0OBBYEFCIQ9l28Yy1/3qJvFarXjhKdG9tVMB8GA1UdIwQYMBaAFCIQ9l28Yy1/3qJvFarXjhKdG9tVMA8GA1UdEwEB/wQFMAMBAf8wDQYJKoZIhvcNAQELBQADggEBAKLPmLTppXYTTOOxHhTMyHI0oTl7ID2PQfJsref+jDshB3hib98BC17b9ESpLnwx7ugg17NRl7RYutxjuVw/CK/gwAnTMg3D3mdAnKkMRr3UxnD89KprLIpf7WQCmyJaxalsD5jjgl3kuGM7jf2FJNxQz5RrXBGlQHa465ouov+Rp5v/K5Umyt6wsCZXEbOF0SdUhEGU3nxVbFsoPimNYSHHwc29USnQmyW1O/drFDoTcOK4GdHFEVkrHQgqwU8ay1fYGYfIUDhsV/5AAWgQC41r9FWr+VQgyJC94qmDg0c46RE123dL/YifVUl2DKuJ0aOY+OkSgwknKZ+FQd+8d6k="}, //nolint:lll
|
||||||
TLSAuth: "bc470c93ff9f5602a8abb27dee84a52814d10f20490ad23c47d5d82120c1bf859e93d0696b455d4a1b8d55d40c2685c41ca1d0aef29a3efd27274c4ef09020a3978fe45784b335da6df2d12db97bbb838416515f2a96f04715fd28949c6fe296a925cfada3f8b8928ed7fc963c1563272f5cf46e5e1d9c845d7703ca881497b7e6564a9d1dea9358adffd435295479f47d5298fabf5359613ff5992cb57ff081a04dfb81a26513a6b44a9b5490ad265f8a02384832a59cc3e075ad545461060b7bcab49bac815163cb80983dd51d5b1fd76170ffd904d8291071e96efc3fb777856c717b148d08a510f5687b8a8285dcffe737b98916dd15ef6235dee4266d3b", //nolint:lll
|
TLSAuth: "bc470c93ff9f5602a8abb27dee84a52814d10f20490ad23c47d5d82120c1bf859e93d0696b455d4a1b8d55d40c2685c41ca1d0aef29a3efd27274c4ef09020a3978fe45784b335da6df2d12db97bbb838416515f2a96f04715fd28949c6fe296a925cfada3f8b8928ed7fc963c1563272f5cf46e5e1d9c845d7703ca881497b7e6564a9d1dea9358adffd435295479f47d5298fabf5359613ff5992cb57ff081a04dfb81a26513a6b44a9b5490ad265f8a02384832a59cc3e075ad545461060b7bcab49bac815163cb80983dd51d5b1fd76170ffd904d8291071e96efc3fb777856c717b148d08a510f5687b8a8285dcffe737b98916dd15ef6235dee4266d3b", //nolint:lll
|
||||||
}
|
}
|
||||||
return utils.OpenVPNConfig(providerSettings, connection, settings, ipv6Supported)
|
return utils.OpenVPNConfig(providerSettings, connection, settings, ipv6Supported)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,13 +7,11 @@ import (
|
|||||||
"github.com/qdm12/gluetun/internal/constants/providers"
|
"github.com/qdm12/gluetun/internal/constants/providers"
|
||||||
"github.com/qdm12/gluetun/internal/provider/common"
|
"github.com/qdm12/gluetun/internal/provider/common"
|
||||||
"github.com/qdm12/gluetun/internal/provider/example/updater"
|
"github.com/qdm12/gluetun/internal/provider/example/updater"
|
||||||
"github.com/qdm12/gluetun/internal/provider/utils"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
type Provider struct {
|
type Provider struct {
|
||||||
storage common.Storage
|
storage common.Storage
|
||||||
randSource rand.Source
|
randSource rand.Source
|
||||||
utils.NoPortForwarder
|
|
||||||
common.Fetcher
|
common.Fetcher
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -22,10 +20,9 @@ func New(storage common.Storage, randSource rand.Source,
|
|||||||
updaterWarner common.Warner, client *http.Client,
|
updaterWarner common.Warner, client *http.Client,
|
||||||
unzipper common.Unzipper, parallelResolver common.ParallelResolver) *Provider {
|
unzipper common.Unzipper, parallelResolver common.ParallelResolver) *Provider {
|
||||||
return &Provider{
|
return &Provider{
|
||||||
storage: storage,
|
storage: storage,
|
||||||
randSource: randSource,
|
randSource: randSource,
|
||||||
NoPortForwarder: utils.NewNoPortForwarding(providers.Example),
|
Fetcher: updater.New(updaterWarner, unzipper, client, parallelResolver),
|
||||||
Fetcher: updater.New(updaterWarner, unzipper, client, parallelResolver),
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -17,10 +17,10 @@ func (p *Provider) OpenVPNConfig(connection models.Connection,
|
|||||||
openvpn.AES256gcm, openvpn.AES256cbc, openvpn.AES128gcm,
|
openvpn.AES256gcm, openvpn.AES256cbc, openvpn.AES128gcm,
|
||||||
},
|
},
|
||||||
Auth: openvpn.SHA512,
|
Auth: openvpn.SHA512,
|
||||||
CA: "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", //nolint:lll
|
CAs: []string{"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"}, //nolint:lll
|
||||||
Cert: "MIIDTjCCAregAwIBAgIDKzZvMA0GCSqGSIb3DQEBCwUAMIGFMQswCQYDVQQGEwJVUzELMAkGA1UECBMCQ0ExFTATBgNVBAcTDFNhbkZyYW5jaXNjbzEVMBMGA1UEChMMRm9ydC1GdW5zdG9uMRgwFgYDVQQDEw9Gb3J0LUZ1bnN0b24gQ0ExITAfBgkqhkiG9w0BCQEWEm1lQG15aG9zdC5teWRvbWFpbjAgFw0xNjExMDMwMzA2MThaGA8yMDY2MTEwMzAzMDYxOFowgYoxCzAJBgNVBAYTAlZHMQwwCgYDVQQIDANCVkkxEzARBgNVBAoMCkV4cHJlc3NWUE4xEzARBgNVBAsMCkV4cHJlc3NWUE4xHDAaBgNVBAMME2V4cHJlc3N2cG5fY3VzdG9tZXIxJTAjBgkqhkiG9w0BCQEWFnN1cHBvcnRAZXhwcmVzc3Zwbi5jb20wggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCrOYt/KOi2uMDGev3pXg8j1SO4J/4EVWDF7vJcKr2jrZlqD/zuAFx2W1YWvwumPO6PKH4PU9621aNdiumaUkv/RplCfznnnxqobhJuTE2oA+rS1bOq+9OhHwF9jgNXNVk+XX4d0toST5uGE6Z3OdmPBur8o5AlCf78PDSAwpFOw5HrgLqOEU4hTweC1/czX2VsvsHv22HRI6JMZgP8gGQii/p9iukqfaJvGdPciL5p1QRBUQIi8P8pNvEp1pVIpxYj7/LOUqb2DxFvgmp2v1IQ0Yu88SWsFk84+xAYHzfkLyS31Sqj5uLRBnJqx3fIlOihQ50GI72fwPMwo+OippvVAgMBAAGjPzA9MAwGA1UdEwEB/wQCMAAwDgYDVR0PAQH/BAQDAgSwMB0GA1UdDgQWBBSkBM1TCX9kBgFsv2RmOzudMXa9njANBgkqhkiG9w0BAQsFAAOBgQA+2e4b+33zFmA+1ZQ46kWkfiB+fEeDyMwMLeYYyDS2d8mZhNZKdOw7dy4Ifz9Vqzp4aKuQ6j61c6k1UaQQL0tskqWVzslSFvs9NZyUAJLLdGUc5TT2MiLwiXQwd4UvH6bGeePdhvB4+ZbW7VMD7TE8hZhjhAL4F6yAP1EQvg3LDA==", //nolint:lll
|
Cert: "MIIDTjCCAregAwIBAgIDKzZvMA0GCSqGSIb3DQEBCwUAMIGFMQswCQYDVQQGEwJVUzELMAkGA1UECBMCQ0ExFTATBgNVBAcTDFNhbkZyYW5jaXNjbzEVMBMGA1UEChMMRm9ydC1GdW5zdG9uMRgwFgYDVQQDEw9Gb3J0LUZ1bnN0b24gQ0ExITAfBgkqhkiG9w0BCQEWEm1lQG15aG9zdC5teWRvbWFpbjAgFw0xNjExMDMwMzA2MThaGA8yMDY2MTEwMzAzMDYxOFowgYoxCzAJBgNVBAYTAlZHMQwwCgYDVQQIDANCVkkxEzARBgNVBAoMCkV4cHJlc3NWUE4xEzARBgNVBAsMCkV4cHJlc3NWUE4xHDAaBgNVBAMME2V4cHJlc3N2cG5fY3VzdG9tZXIxJTAjBgkqhkiG9w0BCQEWFnN1cHBvcnRAZXhwcmVzc3Zwbi5jb20wggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCrOYt/KOi2uMDGev3pXg8j1SO4J/4EVWDF7vJcKr2jrZlqD/zuAFx2W1YWvwumPO6PKH4PU9621aNdiumaUkv/RplCfznnnxqobhJuTE2oA+rS1bOq+9OhHwF9jgNXNVk+XX4d0toST5uGE6Z3OdmPBur8o5AlCf78PDSAwpFOw5HrgLqOEU4hTweC1/czX2VsvsHv22HRI6JMZgP8gGQii/p9iukqfaJvGdPciL5p1QRBUQIi8P8pNvEp1pVIpxYj7/LOUqb2DxFvgmp2v1IQ0Yu88SWsFk84+xAYHzfkLyS31Sqj5uLRBnJqx3fIlOihQ50GI72fwPMwo+OippvVAgMBAAGjPzA9MAwGA1UdEwEB/wQCMAAwDgYDVR0PAQH/BAQDAgSwMB0GA1UdDgQWBBSkBM1TCX9kBgFsv2RmOzudMXa9njANBgkqhkiG9w0BAQsFAAOBgQA+2e4b+33zFmA+1ZQ46kWkfiB+fEeDyMwMLeYYyDS2d8mZhNZKdOw7dy4Ifz9Vqzp4aKuQ6j61c6k1UaQQL0tskqWVzslSFvs9NZyUAJLLdGUc5TT2MiLwiXQwd4UvH6bGeePdhvB4+ZbW7VMD7TE8hZhjhAL4F6yAP1EQvg3LDA==", //nolint:lll
|
||||||
RSAKey: "MIIEpAIBAAKCAQEAqzmLfyjotrjAxnr96V4PI9UjuCf+BFVgxe7yXCq9o62Zag/87gBcdltWFr8Lpjzujyh+D1PettWjXYrpmlJL/0aZQn85558aqG4SbkxNqAPq0tWzqvvToR8BfY4DVzVZPl1+HdLaEk+bhhOmdznZjwbq/KOQJQn+/Dw0gMKRTsOR64C6jhFOIU8Hgtf3M19lbL7B79th0SOiTGYD/IBkIov6fYrpKn2ibxnT3Ii+adUEQVECIvD/KTbxKdaVSKcWI+/yzlKm9g8Rb4Jqdr9SENGLvPElrBZPOPsQGB835C8kt9Uqo+bi0QZyasd3yJTooUOdBiO9n8DzMKPjoqab1QIDAQABAoIBAHgsekC0SKi+AOcNOZqJ3pxqophE0V7fQX2KWGXhxZnUZMFxGTc936deMYzjZ1y0lUa6x8cgOUcfqHol3hDmw9oWBckLHGv5Wi9umdb6DOLoZO62+FQATSdfaJ9jheq2Ub2YxsRN0apaXzB6KDKz0oM0+sZ4Udn9Kw6DfuIELRIWwEx4w0v3gKW7YLC4Jkc4AwLkPK03xEA/qImfkCmaMPLhrgVQt+IFfP8bXzL7CCC04rNU/IS8pyjex+iUolnQZlbXntF7Bm4V2mz0827ZVqrgAb/hEQRlsTW3rRkVh+rrdoUE7BCZRTFmRCbLoShjN6XuSf4sAus8ch4UEN12gN0CgYEA4o/tSvij1iPaXLmt4KOEuxqmSGB8MLKhFde8lBbNdrDgxiIH9bH7khKx15XRTX0qLDbs8b2/UJygZG0Aa1kIBqZTXTgeMAuxPRTesALJPdqQ/ROnbJcdFkI7gllrAG8VB0fH4wTRsRd0vWEB6YlCdE107u6LEsLAHxOj9Q5819cCgYEAwXjx9RkQ2qITBx5Ewib8YsltA0n3cmRomPicLlsnKV5DfvyCLpFIsZ1h3f9dUpfxRLwzp8wcoLiq9cCoOGdu1udw/yBTqmhaXWhUK/g77f9Ze2ZB1OEhuyKLYJ1vW/h/Z/a1aPCMxZqsDTPCePsuO8Cez5gqs8LjM3W7EyzRxDMCgYEAvhHrDFt975fSiLoJgo0MPIAGAnBXn+8sLwv3m/FpW+rWF8LTFK/Fku12H5wDpNOdvswxijkauIE+GiJMGMLvdcyx4WHECaC1h73reJRNykOEIZ0Md5BrCZJ1JEzp9Mo8RQhWTEFtvfkkqgApP4g0pSeaMx0StaGG1kt+4IbP+68CgYBrZdQKlquAck/Vt7u7eyDHRcE5/ilaWtqlb/xizz7h++3D5C/v4b5UumTFcyg+3RGVclPKZcfOgDSGzzeSd/hTW46iUTOgeOUQzQVMkzPRXdoyYgVRQtgSpY5xR3O1vjAbahwx8LZ0SvQPMBhYSDbV/Isr+fBacWjl/AipEEwxeQKBgQDdrAEnVlOFoCLw4sUjsPoxkLjhTAgI7CYk5NNxX67Rnj0tp+Y49+sGUhl5sCGfMKkLShiON5P2oxZa+B0aPtQjsdnsFPa1uaZkK4c++SS6AetzYRpVDLmLp7/1CulE0z3O0sBekpwiuaqLJ9ZccC81g4+2j8j6c50rIAct3hxIxw==", //nolint:lll
|
RSAKey: "MIIEpAIBAAKCAQEAqzmLfyjotrjAxnr96V4PI9UjuCf+BFVgxe7yXCq9o62Zag/87gBcdltWFr8Lpjzujyh+D1PettWjXYrpmlJL/0aZQn85558aqG4SbkxNqAPq0tWzqvvToR8BfY4DVzVZPl1+HdLaEk+bhhOmdznZjwbq/KOQJQn+/Dw0gMKRTsOR64C6jhFOIU8Hgtf3M19lbL7B79th0SOiTGYD/IBkIov6fYrpKn2ibxnT3Ii+adUEQVECIvD/KTbxKdaVSKcWI+/yzlKm9g8Rb4Jqdr9SENGLvPElrBZPOPsQGB835C8kt9Uqo+bi0QZyasd3yJTooUOdBiO9n8DzMKPjoqab1QIDAQABAoIBAHgsekC0SKi+AOcNOZqJ3pxqophE0V7fQX2KWGXhxZnUZMFxGTc936deMYzjZ1y0lUa6x8cgOUcfqHol3hDmw9oWBckLHGv5Wi9umdb6DOLoZO62+FQATSdfaJ9jheq2Ub2YxsRN0apaXzB6KDKz0oM0+sZ4Udn9Kw6DfuIELRIWwEx4w0v3gKW7YLC4Jkc4AwLkPK03xEA/qImfkCmaMPLhrgVQt+IFfP8bXzL7CCC04rNU/IS8pyjex+iUolnQZlbXntF7Bm4V2mz0827ZVqrgAb/hEQRlsTW3rRkVh+rrdoUE7BCZRTFmRCbLoShjN6XuSf4sAus8ch4UEN12gN0CgYEA4o/tSvij1iPaXLmt4KOEuxqmSGB8MLKhFde8lBbNdrDgxiIH9bH7khKx15XRTX0qLDbs8b2/UJygZG0Aa1kIBqZTXTgeMAuxPRTesALJPdqQ/ROnbJcdFkI7gllrAG8VB0fH4wTRsRd0vWEB6YlCdE107u6LEsLAHxOj9Q5819cCgYEAwXjx9RkQ2qITBx5Ewib8YsltA0n3cmRomPicLlsnKV5DfvyCLpFIsZ1h3f9dUpfxRLwzp8wcoLiq9cCoOGdu1udw/yBTqmhaXWhUK/g77f9Ze2ZB1OEhuyKLYJ1vW/h/Z/a1aPCMxZqsDTPCePsuO8Cez5gqs8LjM3W7EyzRxDMCgYEAvhHrDFt975fSiLoJgo0MPIAGAnBXn+8sLwv3m/FpW+rWF8LTFK/Fku12H5wDpNOdvswxijkauIE+GiJMGMLvdcyx4WHECaC1h73reJRNykOEIZ0Md5BrCZJ1JEzp9Mo8RQhWTEFtvfkkqgApP4g0pSeaMx0StaGG1kt+4IbP+68CgYBrZdQKlquAck/Vt7u7eyDHRcE5/ilaWtqlb/xizz7h++3D5C/v4b5UumTFcyg+3RGVclPKZcfOgDSGzzeSd/hTW46iUTOgeOUQzQVMkzPRXdoyYgVRQtgSpY5xR3O1vjAbahwx8LZ0SvQPMBhYSDbV/Isr+fBacWjl/AipEEwxeQKBgQDdrAEnVlOFoCLw4sUjsPoxkLjhTAgI7CYk5NNxX67Rnj0tp+Y49+sGUhl5sCGfMKkLShiON5P2oxZa+B0aPtQjsdnsFPa1uaZkK4c++SS6AetzYRpVDLmLp7/1CulE0z3O0sBekpwiuaqLJ9ZccC81g4+2j8j6c50rIAct3hxIxw==", //nolint:lll
|
||||||
TLSAuth: "48d9999bd71095b10649c7cb471c1051b1afdece597cea06909b99303a18c67401597b12c04a787e98cdb619ee960d90a0165529dc650f3a5c6fbe77c91c137dcf55d863fcbe314df5f0b45dbe974d9bde33ef5b4803c3985531c6c23ca6906d6cd028efc8585d1b9e71003566bd7891b9cc9212bcba510109922eed87f5c8e66d8e59cbd82575261f02777372b2cd4ca5214c4a6513ff26dd568f574fd40d6cd450fc788160ff68434ce2bf6afb00e710a3198538f14c4d45d84ab42637872e778a6b35a124e700920879f1d003ba93dccdb953cdf32bea03f365760b0ed8002098d4ce20d045b45a83a8432cc737677aed27125592a7148d25c87fdbe0a3f6", //nolint:lll
|
TLSAuth: "48d9999bd71095b10649c7cb471c1051b1afdece597cea06909b99303a18c67401597b12c04a787e98cdb619ee960d90a0165529dc650f3a5c6fbe77c91c137dcf55d863fcbe314df5f0b45dbe974d9bde33ef5b4803c3985531c6c23ca6906d6cd028efc8585d1b9e71003566bd7891b9cc9212bcba510109922eed87f5c8e66d8e59cbd82575261f02777372b2cd4ca5214c4a6513ff26dd568f574fd40d6cd450fc788160ff68434ce2bf6afb00e710a3198538f14c4d45d84ab42637872e778a6b35a124e700920879f1d003ba93dccdb953cdf32bea03f365760b0ed8002098d4ce20d045b45a83a8432cc737677aed27125592a7148d25c87fdbe0a3f6", //nolint:lll
|
||||||
MssFix: 1200,
|
MssFix: 1200,
|
||||||
FastIO: true,
|
FastIO: true,
|
||||||
Fragment: 1300,
|
Fragment: 1300,
|
||||||
|
|||||||
@@ -6,13 +6,11 @@ import (
|
|||||||
"github.com/qdm12/gluetun/internal/constants/providers"
|
"github.com/qdm12/gluetun/internal/constants/providers"
|
||||||
"github.com/qdm12/gluetun/internal/provider/common"
|
"github.com/qdm12/gluetun/internal/provider/common"
|
||||||
"github.com/qdm12/gluetun/internal/provider/expressvpn/updater"
|
"github.com/qdm12/gluetun/internal/provider/expressvpn/updater"
|
||||||
"github.com/qdm12/gluetun/internal/provider/utils"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
type Provider struct {
|
type Provider struct {
|
||||||
storage common.Storage
|
storage common.Storage
|
||||||
randSource rand.Source
|
randSource rand.Source
|
||||||
utils.NoPortForwarder
|
|
||||||
common.Fetcher
|
common.Fetcher
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -20,10 +18,9 @@ func New(storage common.Storage, randSource rand.Source,
|
|||||||
unzipper common.Unzipper, updaterWarner common.Warner,
|
unzipper common.Unzipper, updaterWarner common.Warner,
|
||||||
parallelResolver common.ParallelResolver) *Provider {
|
parallelResolver common.ParallelResolver) *Provider {
|
||||||
return &Provider{
|
return &Provider{
|
||||||
storage: storage,
|
storage: storage,
|
||||||
randSource: randSource,
|
randSource: randSource,
|
||||||
NoPortForwarder: utils.NewNoPortForwarding(providers.Expressvpn),
|
Fetcher: updater.New(unzipper, updaterWarner, parallelResolver),
|
||||||
Fetcher: updater.New(unzipper, updaterWarner, parallelResolver),
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -21,8 +21,8 @@ func (p *Provider) OpenVPNConfig(connection models.Connection,
|
|||||||
AuthToken: true,
|
AuthToken: true,
|
||||||
KeyDirection: "1",
|
KeyDirection: "1",
|
||||||
RenegDisabled: true,
|
RenegDisabled: true,
|
||||||
CA: "MIIFQjCCAyqgAwIBAgIIUfxepT+rr8owDQYJKoZIhvcNAQEMBQAwPzELMAkGA1UEBhMCS1kxEzARBgNVBAoTCkZhc3Rlc3RWUE4xGzAZBgNVBAMTEkZhc3Rlc3RWUE4gUm9vdCBDQTAeFw0xNzA5MTYwMDAxNDZaFw0yNzA5MTQwMDAxNDZaMD8xCzAJBgNVBAYTAktZMRMwEQYDVQQKEwpGYXN0ZXN0VlBOMRswGQYDVQQDExJGYXN0ZXN0VlBOIFJvb3QgQ0EwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQC1Xj+WfPTozFynFqc+c3CVrggIllaXEl5bY5VgFynXkqCTM6lSrfC4pNjGXUbqWe6RnGJbM4/6kUn+lQDjFSQV1rzP2eDS8+r5+X2WXh4AoeNRUWhvSG+HiHD/B2EFK+Nd5BRSdUjpKWAtsCmT2bBt7nT0jN1OdeNrLJeyF8siAqv/oQzKznF9aIe/N01b2M8ZOFTzoXi2fZAckgGWui8NB/lzkVIJqSkAPRL8qiJLuRCPVOX1PFD8vV//R8/QumtfbcYBMo6vCk2HmWdrh5OQHPxb3KJtbtG+Z1j8x6HGEAe17djYepBiRMyCEQvYgfD6tvFylc4IquhqE9yaP60PJod5TxpWnRQ6HIGSeBm+S+rYSMalTZ8+pUqOOA+IQCYpfpx6EKIJL/VsW2C7cXdvudxDhXPI5lR/QidCb9Ohq3WkfxXaYwzrngdg2avmNqId9R4KESuM9GoHW0dszfyBCh5wYfeaffMElfDam3B92NUwyhZwtIiv623WVXY9PPz+EDjSJsIAu2Vi1vdJyA4nD4k9Lwmx/1zTc/UaYVLsiBqL2WdfvFTeoWoV+dNxQXSEPhB8gwi8x4O4lZW0cwVy/6fa8KMY8gZbcbSTr7U5bRERfW8l+jY+mYKQ/M/ccgpxaHiw1/+4LWfbJQ7VhJJrTyN0C36FQzY1URkSXg+53wIDAQABo0IwQDAPBgNVHRMBAf8EBTADAQH/MA4GA1UdDwEB/wQEAwIBBjAdBgNVHQ4EFgQUmVEL4x6xdCqiqu2OBLs27EA8xGYwDQYJKoZIhvcNAQEMBQADggIBABCpITvO1+R4T9v2+onHiFxU5JjtCZ0zkXqRCMp/Z0UIYbeo1p07pZCPAUjBfGPCkAaR++OiG9sysALdJf8Y6HQKcyuAcWUqQnaIhoZ2JcAP7EKq7uCqsMhcYZD/j3O/3RPtSW5UOx6ItDU+Ua0t9Edho9whNw0VQXmo1JjYoP3FzPjuKoDWTSO1q5eYlZfwcTcs55O2shNkFafPg/6cCm5j6v9nyHrM3sk4LjkrBPUXVx2m/aoz219t8O9Ha9/CdMKXsPO/8gTUzpgnzSgPnGnBmi5xr1nspVN8X4E2f3D+DKqBim3YgslD68NcuFQvJ0/BxZzWVbrr+QXoyzaiCgXuogpIDc2bB6oRXqFnHNz36d4QJmJdWdSaijiS/peQ6EOPgOZ1GuObLWlDCBZLNeQ+N6QaiJxVO4XUj/s22i1IRtwdz84TRHrbWiIpEymsqmb/Ep5r4xV5d6+791axclfOTH7tQrY/SbPtTJI4OEgNekI8YfadQifpelF82MsFFEZuaQn0lj+fvLGtE/zKh3OdLTxRc5TAgBB+0T81+JQosygNr2aFFG0hxar1eyw/gLeG8H+7Ie50pyPvXO4OgB6Key8rSExpilQXlvAT1qX0qS3/K1i/9QkSE9ftIPT6vtwLV2sVQzfyanI4IZgWC6ryhvNLsRn0NFnQclor0+aq", //nolint:lll //nolint:lll
|
CAs: []string{"MIIFQjCCAyqgAwIBAgIIUfxepT+rr8owDQYJKoZIhvcNAQEMBQAwPzELMAkGA1UEBhMCS1kxEzARBgNVBAoTCkZhc3Rlc3RWUE4xGzAZBgNVBAMTEkZhc3Rlc3RWUE4gUm9vdCBDQTAeFw0xNzA5MTYwMDAxNDZaFw0yNzA5MTQwMDAxNDZaMD8xCzAJBgNVBAYTAktZMRMwEQYDVQQKEwpGYXN0ZXN0VlBOMRswGQYDVQQDExJGYXN0ZXN0VlBOIFJvb3QgQ0EwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQC1Xj+WfPTozFynFqc+c3CVrggIllaXEl5bY5VgFynXkqCTM6lSrfC4pNjGXUbqWe6RnGJbM4/6kUn+lQDjFSQV1rzP2eDS8+r5+X2WXh4AoeNRUWhvSG+HiHD/B2EFK+Nd5BRSdUjpKWAtsCmT2bBt7nT0jN1OdeNrLJeyF8siAqv/oQzKznF9aIe/N01b2M8ZOFTzoXi2fZAckgGWui8NB/lzkVIJqSkAPRL8qiJLuRCPVOX1PFD8vV//R8/QumtfbcYBMo6vCk2HmWdrh5OQHPxb3KJtbtG+Z1j8x6HGEAe17djYepBiRMyCEQvYgfD6tvFylc4IquhqE9yaP60PJod5TxpWnRQ6HIGSeBm+S+rYSMalTZ8+pUqOOA+IQCYpfpx6EKIJL/VsW2C7cXdvudxDhXPI5lR/QidCb9Ohq3WkfxXaYwzrngdg2avmNqId9R4KESuM9GoHW0dszfyBCh5wYfeaffMElfDam3B92NUwyhZwtIiv623WVXY9PPz+EDjSJsIAu2Vi1vdJyA4nD4k9Lwmx/1zTc/UaYVLsiBqL2WdfvFTeoWoV+dNxQXSEPhB8gwi8x4O4lZW0cwVy/6fa8KMY8gZbcbSTr7U5bRERfW8l+jY+mYKQ/M/ccgpxaHiw1/+4LWfbJQ7VhJJrTyN0C36FQzY1URkSXg+53wIDAQABo0IwQDAPBgNVHRMBAf8EBTADAQH/MA4GA1UdDwEB/wQEAwIBBjAdBgNVHQ4EFgQUmVEL4x6xdCqiqu2OBLs27EA8xGYwDQYJKoZIhvcNAQEMBQADggIBABCpITvO1+R4T9v2+onHiFxU5JjtCZ0zkXqRCMp/Z0UIYbeo1p07pZCPAUjBfGPCkAaR++OiG9sysALdJf8Y6HQKcyuAcWUqQnaIhoZ2JcAP7EKq7uCqsMhcYZD/j3O/3RPtSW5UOx6ItDU+Ua0t9Edho9whNw0VQXmo1JjYoP3FzPjuKoDWTSO1q5eYlZfwcTcs55O2shNkFafPg/6cCm5j6v9nyHrM3sk4LjkrBPUXVx2m/aoz219t8O9Ha9/CdMKXsPO/8gTUzpgnzSgPnGnBmi5xr1nspVN8X4E2f3D+DKqBim3YgslD68NcuFQvJ0/BxZzWVbrr+QXoyzaiCgXuogpIDc2bB6oRXqFnHNz36d4QJmJdWdSaijiS/peQ6EOPgOZ1GuObLWlDCBZLNeQ+N6QaiJxVO4XUj/s22i1IRtwdz84TRHrbWiIpEymsqmb/Ep5r4xV5d6+791axclfOTH7tQrY/SbPtTJI4OEgNekI8YfadQifpelF82MsFFEZuaQn0lj+fvLGtE/zKh3OdLTxRc5TAgBB+0T81+JQosygNr2aFFG0hxar1eyw/gLeG8H+7Ie50pyPvXO4OgB6Key8rSExpilQXlvAT1qX0qS3/K1i/9QkSE9ftIPT6vtwLV2sVQzfyanI4IZgWC6ryhvNLsRn0NFnQclor0+aq"}, //nolint:lll
|
||||||
TLSAuth: "697fe793b32cb5091d30f2326d5d124a9412e93d0a44ef7361395d76528fcbfc82c3859dccea70a93cfa8fae409709bff75f844cf5ff0c237f426d0c20969233db0e706edb6bdf195ec3dc11b3f76bc807a77e74662d9a800c8cd1144ebb67b7f0d3f1281d1baf522bfe03b7c3f963b1364fc0769400e413b61ca7b43ab19fac9e0f77e41efd4bda7fd77b1de2d7d7855cbbe3e620cecceac72c21a825b243e651f44d90e290e09c3ad650de8fca99c858bc7caad584bc69b11e5c9fd9381c69c505ec487a65912c672d83ed0113b5a74ddfbd3ab33b3683cec593557520a72c4d6cce46111f56f3396cc3ce7183edce553c68ea0796cf6c4375fad00aaa2a42", //nolint:lll
|
TLSAuth: "697fe793b32cb5091d30f2326d5d124a9412e93d0a44ef7361395d76528fcbfc82c3859dccea70a93cfa8fae409709bff75f844cf5ff0c237f426d0c20969233db0e706edb6bdf195ec3dc11b3f76bc807a77e74662d9a800c8cd1144ebb67b7f0d3f1281d1baf522bfe03b7c3f963b1364fc0769400e413b61ca7b43ab19fac9e0f77e41efd4bda7fd77b1de2d7d7855cbbe3e620cecceac72c21a825b243e651f44d90e290e09c3ad650de8fca99c858bc7caad584bc69b11e5c9fd9381c69c505ec487a65912c672d83ed0113b5a74ddfbd3ab33b3683cec593557520a72c4d6cce46111f56f3396cc3ce7183edce553c68ea0796cf6c4375fad00aaa2a42", //nolint:lll
|
||||||
UDPLines: []string{
|
UDPLines: []string{
|
||||||
"tun-mtu 1500",
|
"tun-mtu 1500",
|
||||||
"tun-mtu-extra 32",
|
"tun-mtu-extra 32",
|
||||||
|
|||||||
@@ -6,13 +6,11 @@ import (
|
|||||||
"github.com/qdm12/gluetun/internal/constants/providers"
|
"github.com/qdm12/gluetun/internal/constants/providers"
|
||||||
"github.com/qdm12/gluetun/internal/provider/common"
|
"github.com/qdm12/gluetun/internal/provider/common"
|
||||||
"github.com/qdm12/gluetun/internal/provider/fastestvpn/updater"
|
"github.com/qdm12/gluetun/internal/provider/fastestvpn/updater"
|
||||||
"github.com/qdm12/gluetun/internal/provider/utils"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
type Provider struct {
|
type Provider struct {
|
||||||
storage common.Storage
|
storage common.Storage
|
||||||
randSource rand.Source
|
randSource rand.Source
|
||||||
utils.NoPortForwarder
|
|
||||||
common.Fetcher
|
common.Fetcher
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -20,10 +18,9 @@ func New(storage common.Storage, randSource rand.Source,
|
|||||||
unzipper common.Unzipper, updaterWarner common.Warner,
|
unzipper common.Unzipper, updaterWarner common.Warner,
|
||||||
parallelResolver common.ParallelResolver) *Provider {
|
parallelResolver common.ParallelResolver) *Provider {
|
||||||
return &Provider{
|
return &Provider{
|
||||||
storage: storage,
|
storage: storage,
|
||||||
randSource: randSource,
|
randSource: randSource,
|
||||||
NoPortForwarder: utils.NewNoPortForwarding(providers.Fastestvpn),
|
Fetcher: updater.New(unzipper, updaterWarner, parallelResolver),
|
||||||
Fetcher: updater.New(unzipper, updaterWarner, parallelResolver),
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -16,9 +16,9 @@ func (p *Provider) OpenVPNConfig(connection models.Connection,
|
|||||||
openvpn.AES256cbc,
|
openvpn.AES256cbc,
|
||||||
},
|
},
|
||||||
RemoteCertTLS: true,
|
RemoteCertTLS: true,
|
||||||
CA: "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", //nolint:lll
|
CAs: []string{"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"}, //nolint:lll
|
||||||
Cert: "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", //nolint:lll
|
Cert: "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", //nolint:lll
|
||||||
RSAKey: "MIIEpAIBAAKCAQEA5XY3ERJYWs/YIeBoybivNlu+M32rJs+CAZsh7BnnetTxytI4ngsMRoqXETuis8udp2hsqEHsglLR9tlk9C8yCuKhxbkpdrXFWdISmUq5sa7/wqg/zJF1AZm5Jy0oHNyTHfG6XW61I/h9IN5dmcR9YLir8DVDBNllbtt0z+DnvOhYJOqC30ENahWkTmNKl1cT7EBrR5slddiBJleAb08z77pwsD310e6jWTBySsBcPy+xu/Jj2QgVil/3mstZZDI+noFzs3SkTFBkha/lNTP7NODBQ6m39iaJxz6ZR1xE3v7XU0H5WnpZIcQ2+kmu5Krk2y1GYMKL+9oaotXFPz9v+QIDAQABAoIBAQCcMcssOMOiFWc3MC3EWo4SP4MKQ9n0Uj5Z34LI151FdJyehlj54+VYQ1Cv71tCbjED2sZUBoP69mtsT/EzcsjqtfiOwgrifrs2+BOm+0HKHKiGlcbP9peiHkT10PxEITWXpYtJvGlbcfOjIxqt6B28cBjCK09ShrVQL9ylAKBearRRUacszppntMNTMtN/uG48ZR9Wm+xAczImdG6CrG5sLI/++JwM5PDChLvn5JgMGyOfQZdjNe1oSOVLmqFeG5uu/FS4oMon9+HtfjHJr4ZgA1yQ2wQh3GvEjlP8zwHxEpRJYbxpj6ZbjHZJ2HLX/Gcd9/cXiN8+fQ2zPIYQyG9dAoGBAPUUmt2nJNvl7gj0GbZZ3XR9o+hvj7bJ74W2NhMrw6kjrrzHTAUQd1sBQS8szAQCLqf2ou1aw9AMMBdsLAHydXxvbH7IBAla7rKr23iethtSfjhTNSgQLJHVZlNHfp3hzNtCQZ7j0qVjrteNotrdVF7kKPHDXAK00ICy6SPNjvrXAoGBAO+vdnO15jLeZbbi3lQNS4r8oCadyqyX7ouKE6MtKNhiPsNPGqHKiGcKs/+QylVgYvSmm7TgpsCAiEYeLSPT+Yq3y7HtwVpULlpfAhEJXmvn/6hGpOizx1WNGWhw7nHPWPDzf+jqCGzHdhK0aEZR3MZZQ+U+uKfGiJ8vrvgB7eGvAoGAWxxp5nU48rcsIw/8bxpBhgkfYk33M5EnBqKSv9XJS5wEXhIJZOiWNrLktNEGl4boKXE7aNoRacreJhcE1UR6AOS7hPZ+6atwiePyF4mJUeb9HZtxa493wk9/Vv6BR9il++1Jz/QKX4oLef8hyBP4Rb60qgxirG7kBLR+j9zfhskCgYEAzA5y5xIeuIIU0H4XUDG9dcebxSSjbwsuYIgeLdb9pjMGQhsvjjyyoh8/nT20tLkJpkXN3FFCRjNnUWLRhWYrVkkh1wqWiYOPrwqh5MU4KN/sDWSPcznTY+drkTpMFoKzsvdrl2zf3VR3FneXKv742bkXj601Ykko+XWMHcLutisCgYBSq8IrsjzfaTQiTGI9a7WWsvzK92bq7Abnfq7swAXWcJd/bnjTQKLrrvt2bmwNvlWKAb3c69BFMn0X4t4PuN0iJQ39D6aQAEaM7HwWAmjf5TbodbmgbGxdsUB4xcCIQQ1mvTkigXWrCg0YAD2GZSoaslXAAVv6nR5qWEIa0Hx9GA==", //nolint:lll
|
RSAKey: "MIIEpAIBAAKCAQEA5XY3ERJYWs/YIeBoybivNlu+M32rJs+CAZsh7BnnetTxytI4ngsMRoqXETuis8udp2hsqEHsglLR9tlk9C8yCuKhxbkpdrXFWdISmUq5sa7/wqg/zJF1AZm5Jy0oHNyTHfG6XW61I/h9IN5dmcR9YLir8DVDBNllbtt0z+DnvOhYJOqC30ENahWkTmNKl1cT7EBrR5slddiBJleAb08z77pwsD310e6jWTBySsBcPy+xu/Jj2QgVil/3mstZZDI+noFzs3SkTFBkha/lNTP7NODBQ6m39iaJxz6ZR1xE3v7XU0H5WnpZIcQ2+kmu5Krk2y1GYMKL+9oaotXFPz9v+QIDAQABAoIBAQCcMcssOMOiFWc3MC3EWo4SP4MKQ9n0Uj5Z34LI151FdJyehlj54+VYQ1Cv71tCbjED2sZUBoP69mtsT/EzcsjqtfiOwgrifrs2+BOm+0HKHKiGlcbP9peiHkT10PxEITWXpYtJvGlbcfOjIxqt6B28cBjCK09ShrVQL9ylAKBearRRUacszppntMNTMtN/uG48ZR9Wm+xAczImdG6CrG5sLI/++JwM5PDChLvn5JgMGyOfQZdjNe1oSOVLmqFeG5uu/FS4oMon9+HtfjHJr4ZgA1yQ2wQh3GvEjlP8zwHxEpRJYbxpj6ZbjHZJ2HLX/Gcd9/cXiN8+fQ2zPIYQyG9dAoGBAPUUmt2nJNvl7gj0GbZZ3XR9o+hvj7bJ74W2NhMrw6kjrrzHTAUQd1sBQS8szAQCLqf2ou1aw9AMMBdsLAHydXxvbH7IBAla7rKr23iethtSfjhTNSgQLJHVZlNHfp3hzNtCQZ7j0qVjrteNotrdVF7kKPHDXAK00ICy6SPNjvrXAoGBAO+vdnO15jLeZbbi3lQNS4r8oCadyqyX7ouKE6MtKNhiPsNPGqHKiGcKs/+QylVgYvSmm7TgpsCAiEYeLSPT+Yq3y7HtwVpULlpfAhEJXmvn/6hGpOizx1WNGWhw7nHPWPDzf+jqCGzHdhK0aEZR3MZZQ+U+uKfGiJ8vrvgB7eGvAoGAWxxp5nU48rcsIw/8bxpBhgkfYk33M5EnBqKSv9XJS5wEXhIJZOiWNrLktNEGl4boKXE7aNoRacreJhcE1UR6AOS7hPZ+6atwiePyF4mJUeb9HZtxa493wk9/Vv6BR9il++1Jz/QKX4oLef8hyBP4Rb60qgxirG7kBLR+j9zfhskCgYEAzA5y5xIeuIIU0H4XUDG9dcebxSSjbwsuYIgeLdb9pjMGQhsvjjyyoh8/nT20tLkJpkXN3FFCRjNnUWLRhWYrVkkh1wqWiYOPrwqh5MU4KN/sDWSPcznTY+drkTpMFoKzsvdrl2zf3VR3FneXKv742bkXj601Ykko+XWMHcLutisCgYBSq8IrsjzfaTQiTGI9a7WWsvzK92bq7Abnfq7swAXWcJd/bnjTQKLrrvt2bmwNvlWKAb3c69BFMn0X4t4PuN0iJQ39D6aQAEaM7HwWAmjf5TbodbmgbGxdsUB4xcCIQQ1mvTkigXWrCg0YAD2GZSoaslXAAVv6nR5qWEIa0Hx9GA==", //nolint:lll
|
||||||
Ping: 5,
|
Ping: 5,
|
||||||
}
|
}
|
||||||
return utils.OpenVPNConfig(providerSettings, connection, settings, ipv6Supported)
|
return utils.OpenVPNConfig(providerSettings, connection, settings, ipv6Supported)
|
||||||
|
|||||||
@@ -7,13 +7,11 @@ import (
|
|||||||
"github.com/qdm12/gluetun/internal/constants/providers"
|
"github.com/qdm12/gluetun/internal/constants/providers"
|
||||||
"github.com/qdm12/gluetun/internal/provider/common"
|
"github.com/qdm12/gluetun/internal/provider/common"
|
||||||
"github.com/qdm12/gluetun/internal/provider/hidemyass/updater"
|
"github.com/qdm12/gluetun/internal/provider/hidemyass/updater"
|
||||||
"github.com/qdm12/gluetun/internal/provider/utils"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
type Provider struct {
|
type Provider struct {
|
||||||
storage common.Storage
|
storage common.Storage
|
||||||
randSource rand.Source
|
randSource rand.Source
|
||||||
utils.NoPortForwarder
|
|
||||||
common.Fetcher
|
common.Fetcher
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -21,10 +19,9 @@ func New(storage common.Storage, randSource rand.Source,
|
|||||||
client *http.Client, updaterWarner common.Warner,
|
client *http.Client, updaterWarner common.Warner,
|
||||||
parallelResolver common.ParallelResolver) *Provider {
|
parallelResolver common.ParallelResolver) *Provider {
|
||||||
return &Provider{
|
return &Provider{
|
||||||
storage: storage,
|
storage: storage,
|
||||||
randSource: randSource,
|
randSource: randSource,
|
||||||
NoPortForwarder: utils.NewNoPortForwarding(providers.HideMyAss),
|
Fetcher: updater.New(client, updaterWarner, parallelResolver),
|
||||||
Fetcher: updater.New(client, updaterWarner, parallelResolver),
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user