Merge branch 'master' into pmtu
This commit is contained in:
37
.github/ISSUE_TEMPLATE/provider.md
vendored
37
.github/ISSUE_TEMPLATE/provider.md
vendored
@@ -6,12 +6,35 @@ labels: ":bulb: New provider"
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
One of the following is required:
|
Important notes:
|
||||||
|
|
||||||
- Publicly accessible URL to a zip file containing the Openvpn configuration files
|
- There is no need to support both OpenVPN and Wireguard for a provider, but it's better to support both if possible
|
||||||
- Publicly accessible URL to a structured (JSON etc.) list of servers **and attach** an example Openvpn configuration file for both TCP and UDP
|
- We do **not** implement authentication to access servers information behind a login. This is way too time consuming unfortunately
|
||||||
|
- If it's not possible to support a provider natively, you can still use the [the custom provider](https://github.com/qdm12/gluetun-wiki/blob/main/setup/providers/custom.md)
|
||||||
|
|
||||||
|
## For Wireguard
|
||||||
|
|
||||||
|
Wireguard can be natively supported ONLY if:
|
||||||
|
|
||||||
|
- the `PrivateKey` field value is the same across all servers for one user account
|
||||||
|
- the `Address` field value is:
|
||||||
|
- can be found in a structured (JSON etc.) list of servers publicly available; OR
|
||||||
|
- the same across all servers for one user account
|
||||||
|
- the `PublicKey` field value is:
|
||||||
|
- can be found in a structured (JSON etc.) list of servers publicly available; OR
|
||||||
|
- the same across all servers for one user account
|
||||||
|
- the `Endpoint` field value:
|
||||||
|
- can be found in a structured (JSON etc.) list of servers publicly available
|
||||||
|
- can be determined using a pattern, for example using country codes in hostnames
|
||||||
|
|
||||||
|
If any of these conditions are not met, Wireguard cannot be natively supported or there is no advantage compared to using a custom Wireguard configuration file.
|
||||||
|
|
||||||
|
If **all** of these conditions are met, please provide an answer for each of them.
|
||||||
|
|
||||||
|
## For OpenVPN
|
||||||
|
|
||||||
|
OpenVPN can be natively supported ONLY if one of the following can be provided, by preference in this order:
|
||||||
|
|
||||||
|
- Publicly accessible URL to a structured (JSON etc.) list of servers **and attach** an example Openvpn configuration file for both TCP and UDP; OR
|
||||||
|
- Publicly accessible URL to a zip file containing the Openvpn configuration files; OR
|
||||||
- Publicly accessible URL to the list of servers **and attach** an example Openvpn configuration file for both TCP and UDP
|
- Publicly accessible URL to the list of servers **and attach** an example Openvpn configuration file for both TCP and UDP
|
||||||
|
|
||||||
If the list of servers requires to login **or** is hidden behind an interactive configurator,
|
|
||||||
you can only use a custom Openvpn configuration file.
|
|
||||||
[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.
|
|
||||||
|
|||||||
12
.github/pull_request_template.md
vendored
Normal file
12
.github/pull_request_template.md
vendored
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
# Description
|
||||||
|
|
||||||
|
<!-- Please describe the reason for the changes being proposed. -->
|
||||||
|
|
||||||
|
# Issue
|
||||||
|
|
||||||
|
<!-- Please link to the issue(s) this change relates to. -->
|
||||||
|
|
||||||
|
# Assertions
|
||||||
|
|
||||||
|
* [ ] I am aware that we do not accept manual changes to the servers.json file <!-- If this is your goal, please consult https://github.com/qdm12/gluetun-wiki/blob/main/setup/servers.md#update-using-the-command-line -->
|
||||||
|
* [ ] I am aware that any changes to settings should be reflected in the [wiki](https://github.com/qdm12/gluetun-wiki/)
|
||||||
35
.github/workflows/ci.yml
vendored
35
.github/workflows/ci.yml
vendored
@@ -66,6 +66,33 @@ jobs:
|
|||||||
- name: Build final image
|
- name: Build final image
|
||||||
run: docker build -t final-image .
|
run: docker build -t final-image .
|
||||||
|
|
||||||
|
verify-private:
|
||||||
|
if: |
|
||||||
|
github.repository == 'qdm12/gluetun' &&
|
||||||
|
(
|
||||||
|
github.event_name == 'push' ||
|
||||||
|
github.event_name == 'release' ||
|
||||||
|
(github.event_name == 'pull_request' && github.event.pull_request.head.repo.full_name == github.repository && github.actor != 'dependabot[bot]')
|
||||||
|
)
|
||||||
|
needs: [verify]
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
environment: secrets
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v5
|
||||||
|
|
||||||
|
- run: docker build -t qmcgaw/gluetun .
|
||||||
|
|
||||||
|
- name: Setup Go for CI utility
|
||||||
|
uses: actions/setup-go@v6
|
||||||
|
with:
|
||||||
|
go-version-file: ci/go.mod
|
||||||
|
|
||||||
|
- name: Build utility
|
||||||
|
run: go build -C ./ci -o runner ./cmd/main.go
|
||||||
|
|
||||||
|
- name: Run Gluetun container with Mullvad configuration
|
||||||
|
run: echo -e "${{ secrets.MULLVAD_WIREGUARD_PRIVATE_KEY }}\n${{ secrets.MULLVAD_WIREGUARD_ADDRESS }}" | ./ci/runner mullvad
|
||||||
|
|
||||||
codeql:
|
codeql:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
permissions:
|
permissions:
|
||||||
@@ -74,14 +101,14 @@ jobs:
|
|||||||
security-events: write
|
security-events: write
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v5
|
- uses: actions/checkout@v5
|
||||||
- uses: actions/setup-go@v5
|
- uses: actions/setup-go@v6
|
||||||
with:
|
with:
|
||||||
go-version-file: go.mod
|
go-version-file: go.mod
|
||||||
- uses: github/codeql-action/init@v3
|
- uses: github/codeql-action/init@v4
|
||||||
with:
|
with:
|
||||||
languages: go
|
languages: go
|
||||||
- uses: github/codeql-action/autobuild@v3
|
- uses: github/codeql-action/autobuild@v4
|
||||||
- uses: github/codeql-action/analyze@v3
|
- uses: github/codeql-action/analyze@v4
|
||||||
|
|
||||||
publish:
|
publish:
|
||||||
if: |
|
if: |
|
||||||
|
|||||||
2
.github/workflows/closed-issue.yml
vendored
2
.github/workflows/closed-issue.yml
vendored
@@ -9,7 +9,7 @@ jobs:
|
|||||||
issues: write
|
issues: write
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- uses: peter-evans/create-or-update-comment@v4
|
- uses: peter-evans/create-or-update-comment@v5
|
||||||
with:
|
with:
|
||||||
token: ${{ github.token }}
|
token: ${{ github.token }}
|
||||||
issue-number: ${{ github.event.issue.number }}
|
issue-number: ${{ github.event.issue.number }}
|
||||||
|
|||||||
3
.github/workflows/configs/mlc-config.json
vendored
3
.github/workflows/configs/mlc-config.json
vendored
@@ -8,6 +8,7 @@
|
|||||||
"retryOn429": false,
|
"retryOn429": false,
|
||||||
"fallbackRetryDelay": "30s",
|
"fallbackRetryDelay": "30s",
|
||||||
"aliveStatusCodes": [
|
"aliveStatusCodes": [
|
||||||
200
|
200,
|
||||||
|
429
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
2
.github/workflows/markdown.yml
vendored
2
.github/workflows/markdown.yml
vendored
@@ -23,7 +23,7 @@ jobs:
|
|||||||
- uses: DavidAnson/markdownlint-cli2-action@v20
|
- uses: DavidAnson/markdownlint-cli2-action@v20
|
||||||
with:
|
with:
|
||||||
globs: "**.md"
|
globs: "**.md"
|
||||||
config: .markdownlint.json
|
config: .markdownlint-cli2.jsonc
|
||||||
|
|
||||||
- uses: reviewdog/action-misspell@v1
|
- uses: reviewdog/action-misspell@v1
|
||||||
with:
|
with:
|
||||||
|
|||||||
2
.github/workflows/opened-issue.yml
vendored
2
.github/workflows/opened-issue.yml
vendored
@@ -9,7 +9,7 @@ jobs:
|
|||||||
issues: write
|
issues: write
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- uses: peter-evans/create-or-update-comment@v4
|
- uses: peter-evans/create-or-update-comment@v5
|
||||||
with:
|
with:
|
||||||
token: ${{ github.token }}
|
token: ${{ github.token }}
|
||||||
issue-number: ${{ github.event.issue.number }}
|
issue-number: ${{ github.event.issue.number }}
|
||||||
|
|||||||
@@ -56,6 +56,10 @@ linters:
|
|||||||
- revive
|
- revive
|
||||||
path: internal\/provider\/(common|utils)\/.+\.go
|
path: internal\/provider\/(common|utils)\/.+\.go
|
||||||
text: "var-naming: avoid (bad|meaningless) package names"
|
text: "var-naming: avoid (bad|meaningless) package names"
|
||||||
|
- linters:
|
||||||
|
- err113
|
||||||
|
- mnd
|
||||||
|
path: ci\/.+\.go
|
||||||
|
|
||||||
paths:
|
paths:
|
||||||
- third_party$
|
- third_party$
|
||||||
|
|||||||
9
.markdownlint-cli2.jsonc
Normal file
9
.markdownlint-cli2.jsonc
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
{
|
||||||
|
"config": {
|
||||||
|
"default": true,
|
||||||
|
"MD013": false,
|
||||||
|
},
|
||||||
|
"ignores": [
|
||||||
|
".github/pull_request_template.md"
|
||||||
|
]
|
||||||
|
}
|
||||||
@@ -1,3 +0,0 @@
|
|||||||
{
|
|
||||||
"MD013": false
|
|
||||||
}
|
|
||||||
25
Dockerfile
25
Dockerfile
@@ -1,6 +1,6 @@
|
|||||||
ARG ALPINE_VERSION=3.20
|
ARG ALPINE_VERSION=3.22
|
||||||
ARG GO_ALPINE_VERSION=3.20
|
ARG GO_ALPINE_VERSION=3.22
|
||||||
ARG GO_VERSION=1.23
|
ARG GO_VERSION=1.25
|
||||||
ARG XCPUTRANSLATE_VERSION=v0.9.0
|
ARG XCPUTRANSLATE_VERSION=v0.9.0
|
||||||
ARG GOLANGCI_LINT_VERSION=v2.4.0
|
ARG GOLANGCI_LINT_VERSION=v2.4.0
|
||||||
ARG MOCKGEN_VERSION=v1.6.0
|
ARG MOCKGEN_VERSION=v1.6.0
|
||||||
@@ -164,17 +164,20 @@ ENV VPN_SERVICE_PROVIDER=pia \
|
|||||||
# Health
|
# Health
|
||||||
HEALTH_SERVER_ADDRESS=127.0.0.1:9999 \
|
HEALTH_SERVER_ADDRESS=127.0.0.1:9999 \
|
||||||
HEALTH_TARGET_ADDRESS=cloudflare.com:443 \
|
HEALTH_TARGET_ADDRESS=cloudflare.com:443 \
|
||||||
HEALTH_ICMP_TARGET_IP=0.0.0.0 \
|
HEALTH_ICMP_TARGET_IP=1.1.1.1 \
|
||||||
# DNS over TLS
|
HEALTH_RESTART_VPN=on \
|
||||||
DOT=on \
|
# DNS
|
||||||
DOT_PROVIDERS=cloudflare \
|
DNS_SERVER=on \
|
||||||
DOT_PRIVATE_ADDRESS=127.0.0.1/8,10.0.0.0/8,172.16.0.0/12,192.168.0.0/16,169.254.0.0/16,::1/128,fc00::/7,fe80::/10,::ffff:7f00:1/104,::ffff:a00:0/104,::ffff:a9fe:0/112,::ffff:ac10:0/108,::ffff:c0a8:0/112 \
|
DNS_UPSTREAM_RESOLVER_TYPE=DoT \
|
||||||
DOT_CACHING=on \
|
DNS_UPSTREAM_RESOLVERS=cloudflare \
|
||||||
DOT_IPV6=off \
|
DNS_BLOCK_IPS= \
|
||||||
|
DNS_BLOCK_IP_PREFIXES=127.0.0.1/8,10.0.0.0/8,172.16.0.0/12,192.168.0.0/16,169.254.0.0/16,::1/128,fc00::/7,fe80::/10,::ffff:7f00:1/104,::ffff:a00:0/104,::ffff:a9fe:0/112,::ffff:ac10:0/108,::ffff:c0a8:0/112 \
|
||||||
|
DNS_CACHING=on \
|
||||||
|
DNS_UPSTREAM_IPV6=off \
|
||||||
BLOCK_MALICIOUS=on \
|
BLOCK_MALICIOUS=on \
|
||||||
BLOCK_SURVEILLANCE=off \
|
BLOCK_SURVEILLANCE=off \
|
||||||
BLOCK_ADS=off \
|
BLOCK_ADS=off \
|
||||||
UNBLOCK= \
|
DNS_UNBLOCK_HOSTNAMES= \
|
||||||
DNS_UPDATE_PERIOD=24h \
|
DNS_UPDATE_PERIOD=24h \
|
||||||
DNS_ADDRESS=127.0.0.1 \
|
DNS_ADDRESS=127.0.0.1 \
|
||||||
DNS_KEEP_NAMESERVER=off \
|
DNS_KEEP_NAMESERVER=off \
|
||||||
|
|||||||
@@ -26,7 +26,6 @@ Lightweight swiss-army-knife-like VPN client to multiple VPN service providers
|
|||||||
[](https://github.com/qdm12/gluetun/issues)
|
[](https://github.com/qdm12/gluetun/issues)
|
||||||
[](https://github.com/qdm12/gluetun/issues?q=is%3Aissue+is%3Aclosed)
|
[](https://github.com/qdm12/gluetun/issues?q=is%3Aissue+is%3Aclosed)
|
||||||
|
|
||||||
[](https://github.com/qdm12/gluetun)
|
|
||||||

|

|
||||||

|

|
||||||

|

|
||||||
@@ -56,7 +55,7 @@ Lightweight swiss-army-knife-like VPN client to multiple VPN service providers
|
|||||||
|
|
||||||
## Features
|
## Features
|
||||||
|
|
||||||
- Based on Alpine 3.20 for a small Docker image of 35.6MB
|
- Based on Alpine 3.22 for a small Docker image of 41.1MB
|
||||||
- Supports: **AirVPN**, **Cyberghost**, **ExpressVPN**, **FastestVPN**, **Giganews**, **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**, **Giganews**, **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
|
||||||
|
|||||||
33
ci/cmd/main.go
Normal file
33
ci/cmd/main.go
Normal file
@@ -0,0 +1,33 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
"os/signal"
|
||||||
|
|
||||||
|
"github.com/qdm12/gluetun/ci/internal"
|
||||||
|
)
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
if len(os.Args) < 2 {
|
||||||
|
fmt.Println("Usage: " + os.Args[0] + " <command>")
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx, stop := signal.NotifyContext(context.Background(), os.Interrupt)
|
||||||
|
|
||||||
|
var err error
|
||||||
|
switch os.Args[1] {
|
||||||
|
case "mullvad":
|
||||||
|
err = internal.MullvadTest(ctx)
|
||||||
|
default:
|
||||||
|
err = fmt.Errorf("unknown command: %s", os.Args[1])
|
||||||
|
}
|
||||||
|
stop()
|
||||||
|
if err != nil {
|
||||||
|
fmt.Println("❌", err)
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
fmt.Println("✅ Test completed successfully.")
|
||||||
|
}
|
||||||
36
ci/go.mod
Normal file
36
ci/go.mod
Normal file
@@ -0,0 +1,36 @@
|
|||||||
|
module github.com/qdm12/gluetun/ci
|
||||||
|
|
||||||
|
go 1.25.0
|
||||||
|
|
||||||
|
require (
|
||||||
|
github.com/docker/docker v28.5.1+incompatible
|
||||||
|
github.com/opencontainers/image-spec v1.1.1
|
||||||
|
)
|
||||||
|
|
||||||
|
require (
|
||||||
|
github.com/Microsoft/go-winio v0.6.2 // indirect
|
||||||
|
github.com/containerd/errdefs v1.0.0 // indirect
|
||||||
|
github.com/containerd/errdefs/pkg v0.3.0 // indirect
|
||||||
|
github.com/containerd/log v0.1.0 // indirect
|
||||||
|
github.com/distribution/reference v0.6.0 // indirect
|
||||||
|
github.com/docker/go-connections v0.6.0 // indirect
|
||||||
|
github.com/docker/go-units v0.5.0 // indirect
|
||||||
|
github.com/felixge/httpsnoop v1.0.4 // indirect
|
||||||
|
github.com/go-logr/logr v1.4.3 // indirect
|
||||||
|
github.com/go-logr/stdr v1.2.2 // indirect
|
||||||
|
github.com/moby/docker-image-spec v1.3.1 // indirect
|
||||||
|
github.com/moby/sys/atomicwriter v0.1.0 // indirect
|
||||||
|
github.com/moby/term v0.5.2 // indirect
|
||||||
|
github.com/morikuni/aec v1.0.0 // indirect
|
||||||
|
github.com/opencontainers/go-digest v1.0.0 // indirect
|
||||||
|
github.com/pkg/errors v0.9.1 // indirect
|
||||||
|
go.opentelemetry.io/auto/sdk v1.1.0 // indirect
|
||||||
|
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.63.0 // indirect
|
||||||
|
go.opentelemetry.io/otel v1.38.0 // indirect
|
||||||
|
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.38.0 // indirect
|
||||||
|
go.opentelemetry.io/otel/metric v1.38.0 // indirect
|
||||||
|
go.opentelemetry.io/otel/trace v1.38.0 // indirect
|
||||||
|
golang.org/x/sys v0.35.0 // indirect
|
||||||
|
golang.org/x/time v0.14.0 // indirect
|
||||||
|
gotest.tools/v3 v3.5.2 // indirect
|
||||||
|
)
|
||||||
97
ci/go.sum
Normal file
97
ci/go.sum
Normal file
@@ -0,0 +1,97 @@
|
|||||||
|
github.com/Azure/go-ansiterm v0.0.0-20250102033503-faa5f7b0171c h1:udKWzYgxTojEKWjV8V+WSxDXJ4NFATAsZjh8iIbsQIg=
|
||||||
|
github.com/Azure/go-ansiterm v0.0.0-20250102033503-faa5f7b0171c/go.mod h1:xomTg63KZ2rFqZQzSB4Vz2SUXa1BpHTVz9L5PTmPC4E=
|
||||||
|
github.com/Microsoft/go-winio v0.6.2 h1:F2VQgta7ecxGYO8k3ZZz3RS8fVIXVxONVUPlNERoyfY=
|
||||||
|
github.com/Microsoft/go-winio v0.6.2/go.mod h1:yd8OoFMLzJbo9gZq8j5qaps8bJ9aShtEA8Ipt1oGCvU=
|
||||||
|
github.com/cenkalti/backoff/v5 v5.0.3 h1:ZN+IMa753KfX5hd8vVaMixjnqRZ3y8CuJKRKj1xcsSM=
|
||||||
|
github.com/cenkalti/backoff/v5 v5.0.3/go.mod h1:rkhZdG3JZukswDf7f0cwqPNk4K0sa+F97BxZthm/crw=
|
||||||
|
github.com/containerd/errdefs v1.0.0 h1:tg5yIfIlQIrxYtu9ajqY42W3lpS19XqdxRQeEwYG8PI=
|
||||||
|
github.com/containerd/errdefs v1.0.0/go.mod h1:+YBYIdtsnF4Iw6nWZhJcqGSg/dwvV7tyJ/kCkyJ2k+M=
|
||||||
|
github.com/containerd/errdefs/pkg v0.3.0 h1:9IKJ06FvyNlexW690DXuQNx2KA2cUJXx151Xdx3ZPPE=
|
||||||
|
github.com/containerd/errdefs/pkg v0.3.0/go.mod h1:NJw6s9HwNuRhnjJhM7pylWwMyAkmCQvQ4GpJHEqRLVk=
|
||||||
|
github.com/containerd/log v0.1.0 h1:TCJt7ioM2cr/tfR8GPbGf9/VRAX8D2B4PjzCpfX540I=
|
||||||
|
github.com/containerd/log v0.1.0/go.mod h1:VRRf09a7mHDIRezVKTRCrOq78v577GXq3bSa3EhrzVo=
|
||||||
|
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||||
|
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
|
github.com/distribution/reference v0.6.0 h1:0IXCQ5g4/QMHHkarYzh5l+u8T3t73zM5QvfrDyIgxBk=
|
||||||
|
github.com/distribution/reference v0.6.0/go.mod h1:BbU0aIcezP1/5jX/8MP0YiH4SdvB5Y4f/wlDRiLyi3E=
|
||||||
|
github.com/docker/docker v28.5.1+incompatible h1:Bm8DchhSD2J6PsFzxC35TZo4TLGR2PdW/E69rU45NhM=
|
||||||
|
github.com/docker/docker v28.5.1+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk=
|
||||||
|
github.com/docker/go-connections v0.6.0 h1:LlMG9azAe1TqfR7sO+NJttz1gy6KO7VJBh+pMmjSD94=
|
||||||
|
github.com/docker/go-connections v0.6.0/go.mod h1:AahvXYshr6JgfUJGdDCs2b5EZG/vmaMAntpSFH5BFKE=
|
||||||
|
github.com/docker/go-units v0.5.0 h1:69rxXcBk27SvSaaxTtLh/8llcHD8vYHT7WSdRZ/jvr4=
|
||||||
|
github.com/docker/go-units v0.5.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk=
|
||||||
|
github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg=
|
||||||
|
github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U=
|
||||||
|
github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
|
||||||
|
github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI=
|
||||||
|
github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
|
||||||
|
github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag=
|
||||||
|
github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE=
|
||||||
|
github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
|
||||||
|
github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=
|
||||||
|
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
|
||||||
|
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||||
|
github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.2 h1:8Tjv8EJ+pM1xP8mK6egEbD1OgnVTyacbefKhmbLhIhU=
|
||||||
|
github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.2/go.mod h1:pkJQ2tZHJ0aFOVEEot6oZmaVEZcRme73eIFmhiVuRWs=
|
||||||
|
github.com/moby/docker-image-spec v1.3.1 h1:jMKff3w6PgbfSa69GfNg+zN/XLhfXJGnEx3Nl2EsFP0=
|
||||||
|
github.com/moby/docker-image-spec v1.3.1/go.mod h1:eKmb5VW8vQEh/BAr2yvVNvuiJuY6UIocYsFu/DxxRpo=
|
||||||
|
github.com/moby/sys/atomicwriter v0.1.0 h1:kw5D/EqkBwsBFi0ss9v1VG3wIkVhzGvLklJ+w3A14Sw=
|
||||||
|
github.com/moby/sys/atomicwriter v0.1.0/go.mod h1:Ul8oqv2ZMNHOceF643P6FKPXeCmYtlQMvpizfsSoaWs=
|
||||||
|
github.com/moby/sys/sequential v0.6.0 h1:qrx7XFUd/5DxtqcoH1h438hF5TmOvzC/lspjy7zgvCU=
|
||||||
|
github.com/moby/sys/sequential v0.6.0/go.mod h1:uyv8EUTrca5PnDsdMGXhZe6CCe8U/UiTWd+lL+7b/Ko=
|
||||||
|
github.com/moby/term v0.5.2 h1:6qk3FJAFDs6i/q3W/pQ97SX192qKfZgGjCQqfCJkgzQ=
|
||||||
|
github.com/moby/term v0.5.2/go.mod h1:d3djjFCrjnB+fl8NJux+EJzu0msscUP+f8it8hPkFLc=
|
||||||
|
github.com/morikuni/aec v1.0.0 h1:nP9CBfwrvYnBRgY6qfDQkygYDmYwOilePFkwzv4dU8A=
|
||||||
|
github.com/morikuni/aec v1.0.0/go.mod h1:BbKIizmSmc5MMPqRYbxO4ZU0S0+P200+tUnFx7PXmsc=
|
||||||
|
github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U=
|
||||||
|
github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM=
|
||||||
|
github.com/opencontainers/image-spec v1.1.1 h1:y0fUlFfIZhPF1W537XOLg0/fcx6zcHCJwooC2xJA040=
|
||||||
|
github.com/opencontainers/image-spec v1.1.1/go.mod h1:qpqAh3Dmcf36wStyyWU+kCeDgrGnAve2nCC8+7h8Q0M=
|
||||||
|
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
|
||||||
|
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||||
|
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||||
|
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||||
|
github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ=
|
||||||
|
github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ=
|
||||||
|
github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U=
|
||||||
|
github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=
|
||||||
|
go.opentelemetry.io/auto/sdk v1.1.0 h1:cH53jehLUN6UFLY71z+NDOiNJqDdPRaXzTel0sJySYA=
|
||||||
|
go.opentelemetry.io/auto/sdk v1.1.0/go.mod h1:3wSPjt5PWp2RhlCcmmOial7AvC4DQqZb7a7wCow3W8A=
|
||||||
|
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.63.0 h1:RbKq8BG0FI8OiXhBfcRtqqHcZcka+gU3cskNuf05R18=
|
||||||
|
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.63.0/go.mod h1:h06DGIukJOevXaj/xrNjhi/2098RZzcLTbc0jDAUbsg=
|
||||||
|
go.opentelemetry.io/otel v1.38.0 h1:RkfdswUDRimDg0m2Az18RKOsnI8UDzppJAtj01/Ymk8=
|
||||||
|
go.opentelemetry.io/otel v1.38.0/go.mod h1:zcmtmQ1+YmQM9wrNsTGV/q/uyusom3P8RxwExxkZhjM=
|
||||||
|
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.38.0 h1:GqRJVj7UmLjCVyVJ3ZFLdPRmhDUp2zFmQe3RHIOsw24=
|
||||||
|
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.38.0/go.mod h1:ri3aaHSmCTVYu2AWv44YMauwAQc0aqI9gHKIcSbI1pU=
|
||||||
|
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.38.0 h1:aTL7F04bJHUlztTsNGJ2l+6he8c+y/b//eR0jjjemT4=
|
||||||
|
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.38.0/go.mod h1:kldtb7jDTeol0l3ewcmd8SDvx3EmIE7lyvqbasU3QC4=
|
||||||
|
go.opentelemetry.io/otel/metric v1.38.0 h1:Kl6lzIYGAh5M159u9NgiRkmoMKjvbsKtYRwgfrA6WpA=
|
||||||
|
go.opentelemetry.io/otel/metric v1.38.0/go.mod h1:kB5n/QoRM8YwmUahxvI3bO34eVtQf2i4utNVLr9gEmI=
|
||||||
|
go.opentelemetry.io/otel/sdk v1.38.0 h1:l48sr5YbNf2hpCUj/FoGhW9yDkl+Ma+LrVl8qaM5b+E=
|
||||||
|
go.opentelemetry.io/otel/sdk v1.38.0/go.mod h1:ghmNdGlVemJI3+ZB5iDEuk4bWA3GkTpW+DOoZMYBVVg=
|
||||||
|
go.opentelemetry.io/otel/sdk/metric v1.38.0 h1:aSH66iL0aZqo//xXzQLYozmWrXxyFkBJ6qT5wthqPoM=
|
||||||
|
go.opentelemetry.io/otel/sdk/metric v1.38.0/go.mod h1:dg9PBnW9XdQ1Hd6ZnRz689CbtrUp0wMMs9iPcgT9EZA=
|
||||||
|
go.opentelemetry.io/otel/trace v1.38.0 h1:Fxk5bKrDZJUH+AMyyIXGcFAPah0oRcT+LuNtJrmcNLE=
|
||||||
|
go.opentelemetry.io/otel/trace v1.38.0/go.mod h1:j1P9ivuFsTceSWe1oY+EeW3sc+Pp42sO++GHkg4wwhs=
|
||||||
|
go.opentelemetry.io/proto/otlp v1.7.1 h1:gTOMpGDb0WTBOP8JaO72iL3auEZhVmAQg4ipjOVAtj4=
|
||||||
|
go.opentelemetry.io/proto/otlp v1.7.1/go.mod h1:b2rVh6rfI/s2pHWNlB7ILJcRALpcNDzKhACevjI+ZnE=
|
||||||
|
golang.org/x/net v0.43.0 h1:lat02VYK2j4aLzMzecihNvTlJNQUq316m2Mr9rnM6YE=
|
||||||
|
golang.org/x/net v0.43.0/go.mod h1:vhO1fvI4dGsIjh73sWfUVjj3N7CA9WkKJNQm2svM6Jg=
|
||||||
|
golang.org/x/sys v0.35.0 h1:vz1N37gP5bs89s7He8XuIYXpyY0+QlsKmzipCbUtyxI=
|
||||||
|
golang.org/x/sys v0.35.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
|
||||||
|
golang.org/x/text v0.28.0 h1:rhazDwis8INMIwQ4tpjLDzUhx6RlXqZNPEM0huQojng=
|
||||||
|
golang.org/x/text v0.28.0/go.mod h1:U8nCwOR8jO/marOQ0QbDiOngZVEBB7MAiitBuMjXiNU=
|
||||||
|
golang.org/x/time v0.14.0 h1:MRx4UaLrDotUKUdCIqzPC48t1Y9hANFKIRpNx+Te8PI=
|
||||||
|
golang.org/x/time v0.14.0/go.mod h1:eL/Oa2bBBK0TkX57Fyni+NgnyQQN4LitPmob2Hjnqw4=
|
||||||
|
google.golang.org/genproto/googleapis/api v0.0.0-20250825161204-c5933d9347a5 h1:BIRfGDEjiHRrk0QKZe3Xv2ieMhtgRGeLcZQ0mIVn4EY=
|
||||||
|
google.golang.org/genproto/googleapis/api v0.0.0-20250825161204-c5933d9347a5/go.mod h1:j3QtIyytwqGr1JUDtYXwtMXWPKsEa5LtzIFN1Wn5WvE=
|
||||||
|
google.golang.org/genproto/googleapis/rpc v0.0.0-20250825161204-c5933d9347a5 h1:eaY8u2EuxbRv7c3NiGK0/NedzVsCcV6hDuU5qPX5EGE=
|
||||||
|
google.golang.org/genproto/googleapis/rpc v0.0.0-20250825161204-c5933d9347a5/go.mod h1:M4/wBTSeyLxupu3W3tJtOgB14jILAS/XWPSSa3TAlJc=
|
||||||
|
google.golang.org/grpc v1.75.0 h1:+TW+dqTd2Biwe6KKfhE5JpiYIBWq865PhKGSXiivqt4=
|
||||||
|
google.golang.org/grpc v1.75.0/go.mod h1:JtPAzKiq4v1xcAB2hydNlWI2RnF85XXcV0mhKXr2ecQ=
|
||||||
|
google.golang.org/protobuf v1.36.8 h1:xHScyCOEuuwZEc6UtSOvPbAT4zRh0xcNRYekJwfqyMc=
|
||||||
|
google.golang.org/protobuf v1.36.8/go.mod h1:fuxRtAxBytpl4zzqUh6/eyUujkJdNiuEkXntxiD/uRU=
|
||||||
|
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||||
|
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||||
|
gotest.tools/v3 v3.5.2 h1:7koQfIKdy+I8UTetycgUqXWSDwpgv193Ka+qRsmBY8Q=
|
||||||
|
gotest.tools/v3 v3.5.2/go.mod h1:LtdLGcnqToBH83WByAAi/wiwSFCArdFIUV/xxN4pcjA=
|
||||||
193
ci/internal/mullvad.go
Normal file
193
ci/internal/mullvad.go
Normal file
@@ -0,0 +1,193 @@
|
|||||||
|
package internal
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bufio"
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"os"
|
||||||
|
"regexp"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/docker/docker/api/types/container"
|
||||||
|
"github.com/docker/docker/api/types/network"
|
||||||
|
"github.com/docker/docker/client"
|
||||||
|
v1 "github.com/opencontainers/image-spec/specs-go/v1"
|
||||||
|
)
|
||||||
|
|
||||||
|
func MullvadTest(ctx context.Context) error {
|
||||||
|
secrets, err := readSecrets(ctx)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("reading secrets: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
const timeout = 15 * time.Second
|
||||||
|
ctx, cancel := context.WithTimeout(ctx, timeout)
|
||||||
|
defer cancel()
|
||||||
|
|
||||||
|
client, err := client.NewClientWithOpts(client.FromEnv, client.WithAPIVersionNegotiation())
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("creating Docker client: %w", err)
|
||||||
|
}
|
||||||
|
defer client.Close()
|
||||||
|
|
||||||
|
config := &container.Config{
|
||||||
|
Image: "qmcgaw/gluetun",
|
||||||
|
StopTimeout: ptrTo(3),
|
||||||
|
Env: []string{
|
||||||
|
"VPN_SERVICE_PROVIDER=mullvad",
|
||||||
|
"VPN_TYPE=wireguard",
|
||||||
|
"LOG_LEVEL=debug",
|
||||||
|
"SERVER_COUNTRIES=USA",
|
||||||
|
"WIREGUARD_PRIVATE_KEY=" + secrets.mullvadWireguardPrivateKey,
|
||||||
|
"WIREGUARD_ADDRESSES=" + secrets.mullvadWireguardAddress,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
hostConfig := &container.HostConfig{
|
||||||
|
AutoRemove: true,
|
||||||
|
CapAdd: []string{"NET_ADMIN", "NET_RAW"},
|
||||||
|
}
|
||||||
|
networkConfig := (*network.NetworkingConfig)(nil)
|
||||||
|
platform := (*v1.Platform)(nil)
|
||||||
|
const containerName = "" // auto-generated name
|
||||||
|
|
||||||
|
response, err := client.ContainerCreate(ctx, config, hostConfig, networkConfig, platform, containerName)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("creating container: %w", err)
|
||||||
|
}
|
||||||
|
for _, warning := range response.Warnings {
|
||||||
|
fmt.Println("Warning during container creation:", warning)
|
||||||
|
}
|
||||||
|
containerID := response.ID
|
||||||
|
defer stopContainer(client, containerID)
|
||||||
|
|
||||||
|
beforeStartTime := time.Now()
|
||||||
|
|
||||||
|
err = client.ContainerStart(ctx, containerID, container.StartOptions{})
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("starting container: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return waitForLogLine(ctx, client, containerID, beforeStartTime)
|
||||||
|
}
|
||||||
|
|
||||||
|
func ptrTo[T any](v T) *T { return &v }
|
||||||
|
|
||||||
|
type secrets struct {
|
||||||
|
mullvadWireguardPrivateKey string
|
||||||
|
mullvadWireguardAddress string
|
||||||
|
}
|
||||||
|
|
||||||
|
func readSecrets(ctx context.Context) (secrets, error) {
|
||||||
|
expectedSecrets := [...]string{
|
||||||
|
"Mullvad Wireguard private key",
|
||||||
|
"Mullvad Wireguard address",
|
||||||
|
}
|
||||||
|
|
||||||
|
scanner := bufio.NewScanner(os.Stdin)
|
||||||
|
lines := make([]string, 0, len(expectedSecrets))
|
||||||
|
|
||||||
|
for i := range expectedSecrets {
|
||||||
|
fmt.Println("🤫 reading", expectedSecrets[i], "from Stdin...")
|
||||||
|
if !scanner.Scan() {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
lines = append(lines, strings.TrimSpace(scanner.Text()))
|
||||||
|
if ctx.Err() != nil {
|
||||||
|
return secrets{}, ctx.Err()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := scanner.Err(); err != nil {
|
||||||
|
return secrets{}, fmt.Errorf("reading secrets from stdin: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(lines) < len(expectedSecrets) {
|
||||||
|
return secrets{}, fmt.Errorf("expected %d secrets via Stdin, but only received %d",
|
||||||
|
len(expectedSecrets), len(lines))
|
||||||
|
}
|
||||||
|
for i, line := range lines {
|
||||||
|
if line == "" {
|
||||||
|
return secrets{}, fmt.Errorf("secret on line %d/%d was empty", i+1, len(lines))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return secrets{
|
||||||
|
mullvadWireguardPrivateKey: lines[0],
|
||||||
|
mullvadWireguardAddress: lines[1],
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func stopContainer(client *client.Client, containerID string) {
|
||||||
|
const stopTimeout = 5 * time.Second // must be higher than 3s, see above [container.Config]'s StopTimeout field
|
||||||
|
stopCtx, stopCancel := context.WithTimeout(context.Background(), stopTimeout)
|
||||||
|
defer stopCancel()
|
||||||
|
|
||||||
|
err := client.ContainerStop(stopCtx, containerID, container.StopOptions{})
|
||||||
|
if err != nil {
|
||||||
|
fmt.Println("failed to stop container:", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var successRegexp = regexp.MustCompile(`^.+Public IP address is .+$`)
|
||||||
|
|
||||||
|
func waitForLogLine(ctx context.Context, client *client.Client, containerID string,
|
||||||
|
beforeStartTime time.Time,
|
||||||
|
) error {
|
||||||
|
logOptions := container.LogsOptions{
|
||||||
|
ShowStdout: true,
|
||||||
|
Follow: true,
|
||||||
|
Since: beforeStartTime.Format(time.RFC3339Nano),
|
||||||
|
}
|
||||||
|
|
||||||
|
reader, err := client.ContainerLogs(ctx, containerID, logOptions)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("error getting container logs: %w", err)
|
||||||
|
}
|
||||||
|
defer reader.Close()
|
||||||
|
|
||||||
|
var linesSeen []string
|
||||||
|
scanner := bufio.NewScanner(reader)
|
||||||
|
for ctx.Err() == nil {
|
||||||
|
if scanner.Scan() {
|
||||||
|
line := scanner.Text()
|
||||||
|
if len(line) > 8 { // remove Docker log prefix
|
||||||
|
line = line[8:]
|
||||||
|
}
|
||||||
|
linesSeen = append(linesSeen, line)
|
||||||
|
if successRegexp.MatchString(line) {
|
||||||
|
fmt.Println("✅ Success line logged")
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
err := scanner.Err()
|
||||||
|
if err != nil && err != io.EOF {
|
||||||
|
logSeenLines(linesSeen)
|
||||||
|
return fmt.Errorf("reading log stream: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// The scanner is either done or cannot read because of EOF
|
||||||
|
fmt.Println("The log scanner stopped")
|
||||||
|
logSeenLines(linesSeen)
|
||||||
|
|
||||||
|
// Check if the container is still running
|
||||||
|
inspect, err := client.ContainerInspect(ctx, containerID)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("inspecting container: %w", err)
|
||||||
|
}
|
||||||
|
if !inspect.State.Running {
|
||||||
|
return fmt.Errorf("container stopped unexpectedly while waiting for log line. Exit code: %d", inspect.State.ExitCode)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return ctx.Err()
|
||||||
|
}
|
||||||
|
|
||||||
|
func logSeenLines(lines []string) {
|
||||||
|
fmt.Println("Logs seen so far:")
|
||||||
|
for _, line := range lines {
|
||||||
|
fmt.Println(" " + line)
|
||||||
|
}
|
||||||
|
}
|
||||||
34
go.mod
34
go.mod
@@ -1,29 +1,29 @@
|
|||||||
module github.com/qdm12/gluetun
|
module github.com/qdm12/gluetun
|
||||||
|
|
||||||
go 1.23
|
go 1.25.0
|
||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/breml/rootcerts v0.2.20
|
github.com/breml/rootcerts v0.3.3
|
||||||
github.com/fatih/color v1.18.0
|
github.com/fatih/color v1.18.0
|
||||||
github.com/golang/mock v1.6.0
|
github.com/golang/mock v1.6.0
|
||||||
github.com/klauspost/compress v1.17.11
|
github.com/klauspost/compress v1.18.1
|
||||||
github.com/klauspost/pgzip v1.2.6
|
github.com/klauspost/pgzip v1.2.6
|
||||||
github.com/pelletier/go-toml/v2 v2.2.3
|
github.com/pelletier/go-toml/v2 v2.2.4
|
||||||
github.com/qdm12/dns/v2 v2.0.0-rc8
|
github.com/qdm12/dns/v2 v2.0.0-rc9
|
||||||
github.com/qdm12/gosettings v0.4.4
|
github.com/qdm12/gosettings v0.4.4
|
||||||
github.com/qdm12/goshutdown v0.3.0
|
github.com/qdm12/goshutdown v0.3.0
|
||||||
github.com/qdm12/gosplash v0.2.0
|
github.com/qdm12/gosplash v0.2.0
|
||||||
github.com/qdm12/gotree v0.3.0
|
github.com/qdm12/gotree v0.3.0
|
||||||
github.com/qdm12/log v0.1.0
|
github.com/qdm12/log v0.1.0
|
||||||
github.com/qdm12/ss-server v0.6.0
|
github.com/qdm12/ss-server v0.6.0
|
||||||
github.com/stretchr/testify v1.10.0
|
github.com/stretchr/testify v1.11.1
|
||||||
github.com/ulikunitz/xz v0.5.11
|
github.com/ulikunitz/xz v0.5.15
|
||||||
github.com/vishvananda/netlink v1.2.1
|
github.com/vishvananda/netlink v1.3.1
|
||||||
github.com/youmark/pkcs8 v0.0.0-20201027041543-1326539a0a0a
|
github.com/youmark/pkcs8 v0.0.0-20201027041543-1326539a0a0a
|
||||||
golang.org/x/exp v0.0.0-20241009180824-f66d83c29e7c
|
golang.org/x/exp v0.0.0-20241009180824-f66d83c29e7c
|
||||||
golang.org/x/net v0.34.0
|
golang.org/x/net v0.46.0
|
||||||
golang.org/x/sys v0.29.0
|
golang.org/x/sys v0.37.0
|
||||||
golang.org/x/text v0.21.0
|
golang.org/x/text v0.30.0
|
||||||
golang.zx2c4.com/wireguard v0.0.0-20231211153847-12269c276173
|
golang.zx2c4.com/wireguard v0.0.0-20231211153847-12269c276173
|
||||||
golang.zx2c4.com/wireguard/wgctrl v0.0.0-20230429144221-925a1e7659e6
|
golang.zx2c4.com/wireguard/wgctrl v0.0.0-20230429144221-925a1e7659e6
|
||||||
gopkg.in/ini.v1 v1.67.0
|
gopkg.in/ini.v1 v1.67.0
|
||||||
@@ -47,13 +47,13 @@ require (
|
|||||||
github.com/prometheus/client_model v0.6.1 // indirect
|
github.com/prometheus/client_model v0.6.1 // indirect
|
||||||
github.com/prometheus/common v0.60.1 // indirect
|
github.com/prometheus/common v0.60.1 // indirect
|
||||||
github.com/prometheus/procfs v0.15.1 // indirect
|
github.com/prometheus/procfs v0.15.1 // indirect
|
||||||
github.com/qdm12/goservices v0.1.0 // indirect
|
github.com/qdm12/goservices v0.1.1-0.20251104135713-6bee97bd4978 // indirect
|
||||||
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.4 // indirect
|
github.com/vishvananda/netns v0.0.5 // indirect
|
||||||
golang.org/x/crypto v0.32.0 // indirect
|
golang.org/x/crypto v0.43.0 // indirect
|
||||||
golang.org/x/mod v0.21.0 // indirect
|
golang.org/x/mod v0.28.0 // indirect
|
||||||
golang.org/x/sync v0.10.0 // indirect
|
golang.org/x/sync v0.17.0 // indirect
|
||||||
golang.org/x/tools v0.26.0 // indirect
|
golang.org/x/tools v0.37.0 // indirect
|
||||||
golang.zx2c4.com/wintun v0.0.0-20230126152724-0fa3db229ce2 // indirect
|
golang.zx2c4.com/wintun v0.0.0-20230126152724-0fa3db229ce2 // indirect
|
||||||
google.golang.org/protobuf v1.35.1 // indirect
|
google.golang.org/protobuf v1.35.1 // indirect
|
||||||
gopkg.in/yaml.v3 v3.0.1 // indirect
|
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||||
|
|||||||
64
go.sum
64
go.sum
@@ -1,7 +1,7 @@
|
|||||||
github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=
|
github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=
|
||||||
github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=
|
github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=
|
||||||
github.com/breml/rootcerts v0.2.20 h1:koth1lShwiiDp3VOX6/4qKEZ87S7HgDKsnDr47XEIq0=
|
github.com/breml/rootcerts v0.3.3 h1://GnaRtQ/9BY2+GtMk2wtWxVdCRysiaPr5/xBwl7NKw=
|
||||||
github.com/breml/rootcerts v0.2.20/go.mod h1:S/PKh+4d1HUn4HQovEB8hPJZO6pUZYrIhmXBhsegfXw=
|
github.com/breml/rootcerts v0.3.3/go.mod h1:S/PKh+4d1HUn4HQovEB8hPJZO6pUZYrIhmXBhsegfXw=
|
||||||
github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs=
|
github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs=
|
||||||
github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
|
github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
|
||||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||||
@@ -16,8 +16,8 @@ github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
|
|||||||
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
||||||
github.com/josharian/native v1.1.0 h1:uuaP0hAbW7Y4l0ZRQ6C9zfb7Mg1mbFKry/xzDAfmtLA=
|
github.com/josharian/native v1.1.0 h1:uuaP0hAbW7Y4l0ZRQ6C9zfb7Mg1mbFKry/xzDAfmtLA=
|
||||||
github.com/josharian/native v1.1.0/go.mod h1:7X/raswPFr05uY3HiLlYeyQntB6OO7E/d2Cu7qoaN2w=
|
github.com/josharian/native v1.1.0/go.mod h1:7X/raswPFr05uY3HiLlYeyQntB6OO7E/d2Cu7qoaN2w=
|
||||||
github.com/klauspost/compress v1.17.11 h1:In6xLpyWOi1+C7tXUUWv2ot1QvBjxevKAaI6IXrJmUc=
|
github.com/klauspost/compress v1.18.1 h1:bcSGx7UbpBqMChDtsF28Lw6v/G94LPrrbMbdC3JH2co=
|
||||||
github.com/klauspost/compress v1.17.11/go.mod h1:pMDklpSncoRMuLFrf1W9Ss9KT+0rH90U12bZKk7uwG0=
|
github.com/klauspost/compress v1.18.1/go.mod h1:ZQFFVG+MdnR0P+l6wpXgIL4NTtwiKIdBnrBd8Nrxr+0=
|
||||||
github.com/klauspost/pgzip v1.2.6 h1:8RXeL5crjEUFnR2/Sn6GJNWtSQ3Dk8pq4CL3jvdDyjU=
|
github.com/klauspost/pgzip v1.2.6 h1:8RXeL5crjEUFnR2/Sn6GJNWtSQ3Dk8pq4CL3jvdDyjU=
|
||||||
github.com/klauspost/pgzip v1.2.6/go.mod h1:Ch1tH69qFZu15pkjo5kYi6mth2Zzwzt50oCQKQE9RUs=
|
github.com/klauspost/pgzip v1.2.6/go.mod h1:Ch1tH69qFZu15pkjo5kYi6mth2Zzwzt50oCQKQE9RUs=
|
||||||
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
|
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
|
||||||
@@ -41,8 +41,8 @@ github.com/mikioh/ipaddr v0.0.0-20190404000644-d465c8ab6721 h1:RlZweED6sbSArvlE9
|
|||||||
github.com/mikioh/ipaddr v0.0.0-20190404000644-d465c8ab6721/go.mod h1:Ickgr2WtCLZ2MDGd4Gr0geeCH5HybhRJbonOgQpvSxc=
|
github.com/mikioh/ipaddr v0.0.0-20190404000644-d465c8ab6721/go.mod h1:Ickgr2WtCLZ2MDGd4Gr0geeCH5HybhRJbonOgQpvSxc=
|
||||||
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA=
|
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA=
|
||||||
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ=
|
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ=
|
||||||
github.com/pelletier/go-toml/v2 v2.2.3 h1:YmeHyLY8mFWbdkNWwpr+qIL2bEqT0o95WSdkNHvL12M=
|
github.com/pelletier/go-toml/v2 v2.2.4 h1:mye9XuhQ6gvn5h28+VilKrrPoQVanw5PMw/TB0t5Ec4=
|
||||||
github.com/pelletier/go-toml/v2 v2.2.3/go.mod h1:MfCQTFTvCcUyyvvwm1+G6H/jORL20Xlb6rzQu9GuUkc=
|
github.com/pelletier/go-toml/v2 v2.2.4/go.mod h1:2gIqNv+qfxSVS7cM2xJQKtLSTLUE9V8t9Stt+h56mCY=
|
||||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||||
github.com/prometheus/client_golang v1.20.5 h1:cxppBPuYhUnsO6yo/aoRol4L7q7UFfdm+bR9r+8l63Y=
|
github.com/prometheus/client_golang v1.20.5 h1:cxppBPuYhUnsO6yo/aoRol4L7q7UFfdm+bR9r+8l63Y=
|
||||||
@@ -53,10 +53,10 @@ github.com/prometheus/common v0.60.1 h1:FUas6GcOw66yB/73KC+BOZoFJmbo/1pojoILArPA
|
|||||||
github.com/prometheus/common v0.60.1/go.mod h1:h0LYf1R1deLSKtD4Vdg8gy4RuOvENW2J/h19V5NADQw=
|
github.com/prometheus/common v0.60.1/go.mod h1:h0LYf1R1deLSKtD4Vdg8gy4RuOvENW2J/h19V5NADQw=
|
||||||
github.com/prometheus/procfs v0.15.1 h1:YagwOFzUgYfKKHX6Dr+sHT7km/hxC76UB0learggepc=
|
github.com/prometheus/procfs v0.15.1 h1:YagwOFzUgYfKKHX6Dr+sHT7km/hxC76UB0learggepc=
|
||||||
github.com/prometheus/procfs v0.15.1/go.mod h1:fB45yRUv8NstnjriLhBQLuOUt+WW4BsoGhij/e3PBqk=
|
github.com/prometheus/procfs v0.15.1/go.mod h1:fB45yRUv8NstnjriLhBQLuOUt+WW4BsoGhij/e3PBqk=
|
||||||
github.com/qdm12/dns/v2 v2.0.0-rc8 h1:kbgKPkbT+79nScfuZ0ZcVhksTGo8IUqQ8TTQGnQlZ18=
|
github.com/qdm12/dns/v2 v2.0.0-rc9 h1:qDzRkHr6993jknNB/ZOCnZOyIG6bsZcl2MIfdeUd0kI=
|
||||||
github.com/qdm12/dns/v2 v2.0.0-rc8/go.mod h1:VaF02KWEL7xNV4oKfG4N9nEv/kR6bqyIcBReCV5NJhw=
|
github.com/qdm12/dns/v2 v2.0.0-rc9/go.mod h1:98foWgXJZ+g8gJIuO+fdO+oWpFei5WShMFTeN4Im2lE=
|
||||||
github.com/qdm12/goservices v0.1.0 h1:9sODefm/yuIGS7ynCkEnNlMTAYn9GzPhtcK4F69JWvc=
|
github.com/qdm12/goservices v0.1.1-0.20251104135713-6bee97bd4978 h1:TRGpCU1l0lNwtogEUSs5U+RFceYxkAJUmrGabno7J5c=
|
||||||
github.com/qdm12/goservices v0.1.0/go.mod h1:/JOFsAnHFiSjyoXxa5FlfX903h20K5u/3rLzCjYVMck=
|
github.com/qdm12/goservices v0.1.1-0.20251104135713-6bee97bd4978/go.mod h1:D1Po4CRQLYjccnAR2JsVlN1sBMgQrcNLONbvyuzcdTg=
|
||||||
github.com/qdm12/gosettings v0.4.4 h1:SM6tOZDf6k8qbjWU8KWyBF4mWIixfsKCfh9DGRLHlj4=
|
github.com/qdm12/gosettings v0.4.4 h1:SM6tOZDf6k8qbjWU8KWyBF4mWIixfsKCfh9DGRLHlj4=
|
||||||
github.com/qdm12/gosettings v0.4.4/go.mod h1:CPrt2YC4UsURTrslmhxocVhMCW03lIrqdH2hzIf5prg=
|
github.com/qdm12/gosettings v0.4.4/go.mod h1:CPrt2YC4UsURTrslmhxocVhMCW03lIrqdH2hzIf5prg=
|
||||||
github.com/qdm12/goshutdown v0.3.0 h1:pqBpJkdwlZlfTEx4QHtS8u8CXx6pG0fVo6S1N0MpSEM=
|
github.com/qdm12/goshutdown v0.3.0 h1:pqBpJkdwlZlfTEx4QHtS8u8CXx6pG0fVo6S1N0MpSEM=
|
||||||
@@ -73,36 +73,36 @@ github.com/riobard/go-bloom v0.0.0-20200614022211-cdc8013cb5b3 h1:f/FNXud6gA3MNr
|
|||||||
github.com/riobard/go-bloom v0.0.0-20200614022211-cdc8013cb5b3/go.mod h1:HgjTstvQsPGkxUsCd2KWxErBblirPizecHcpD3ffK+s=
|
github.com/riobard/go-bloom v0.0.0-20200614022211-cdc8013cb5b3/go.mod h1:HgjTstvQsPGkxUsCd2KWxErBblirPizecHcpD3ffK+s=
|
||||||
github.com/rogpeppe/go-internal v1.13.1 h1:KvO1DLK/DRN07sQ1LQKScxyZJuNnedQ5/wKSR38lUII=
|
github.com/rogpeppe/go-internal v1.13.1 h1:KvO1DLK/DRN07sQ1LQKScxyZJuNnedQ5/wKSR38lUII=
|
||||||
github.com/rogpeppe/go-internal v1.13.1/go.mod h1:uMEvuHeurkdAXX61udpOXGD/AzZDWNMNyH2VO9fmH0o=
|
github.com/rogpeppe/go-internal v1.13.1/go.mod h1:uMEvuHeurkdAXX61udpOXGD/AzZDWNMNyH2VO9fmH0o=
|
||||||
github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA=
|
github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U=
|
||||||
github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
|
github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=
|
||||||
github.com/ulikunitz/xz v0.5.11 h1:kpFauv27b6ynzBNT/Xy+1k+fK4WswhN/6PN5WhFAGw8=
|
github.com/ulikunitz/xz v0.5.15 h1:9DNdB5s+SgV3bQ2ApL10xRc35ck0DuIX/isZvIk+ubY=
|
||||||
github.com/ulikunitz/xz v0.5.11/go.mod h1:nbz6k7qbPmH4IRqmfOplQw/tblSgqTqBwxkY0oWt/14=
|
github.com/ulikunitz/xz v0.5.15/go.mod h1:nbz6k7qbPmH4IRqmfOplQw/tblSgqTqBwxkY0oWt/14=
|
||||||
github.com/vishvananda/netlink v1.2.1 h1:pfLv/qlJUwOTPvtWREA7c3PI4u81YkqZw1DYhI2HmLA=
|
github.com/vishvananda/netlink v1.3.1 h1:3AEMt62VKqz90r0tmNhog0r/PpWKmrEShJU0wJW6bV0=
|
||||||
github.com/vishvananda/netlink v1.2.1/go.mod h1:i6NetklAujEcC6fK0JPjT8qSwWyO0HLn4UKG+hGqeJs=
|
github.com/vishvananda/netlink v1.3.1/go.mod h1:ARtKouGSTGchR8aMwmkzC0qiNPrrWO5JS/XMVl45+b4=
|
||||||
github.com/vishvananda/netns v0.0.4 h1:Oeaw1EM2JMxD51g9uhtC0D7erkIjgmj8+JZc26m1YX8=
|
github.com/vishvananda/netns v0.0.5 h1:DfiHV+j8bA32MFM7bfEunvT8IAqQ/NzSJHtcmW5zdEY=
|
||||||
github.com/vishvananda/netns v0.0.4/go.mod h1:SpkAiCQRtJ6TvvxPnOSyH3BMl6unz3xZlaprSwhNNJM=
|
github.com/vishvananda/netns v0.0.5/go.mod h1:SpkAiCQRtJ6TvvxPnOSyH3BMl6unz3xZlaprSwhNNJM=
|
||||||
github.com/youmark/pkcs8 v0.0.0-20201027041543-1326539a0a0a h1:fZHgsYlfvtyqToslyjUt3VOPF4J7aK/3MPcK7xp3PDk=
|
github.com/youmark/pkcs8 v0.0.0-20201027041543-1326539a0a0a h1:fZHgsYlfvtyqToslyjUt3VOPF4J7aK/3MPcK7xp3PDk=
|
||||||
github.com/youmark/pkcs8 v0.0.0-20201027041543-1326539a0a0a/go.mod h1:ul22v+Nro/R083muKhosV54bj5niojjWZvU8xrevuH4=
|
github.com/youmark/pkcs8 v0.0.0-20201027041543-1326539a0a0a/go.mod h1:ul22v+Nro/R083muKhosV54bj5niojjWZvU8xrevuH4=
|
||||||
github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
|
github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
|
||||||
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-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||||
golang.org/x/crypto v0.0.0-20200302210943-78000ba7a073/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
golang.org/x/crypto v0.0.0-20200302210943-78000ba7a073/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||||
golang.org/x/crypto v0.32.0 h1:euUpcYgM8WcP71gNpTqQCn6rC2t6ULUPiOzfWaXVVfc=
|
golang.org/x/crypto v0.43.0 h1:dduJYIi3A3KOfdGOHX8AVZ/jGiyPa3IbBozJ5kNuE04=
|
||||||
golang.org/x/crypto v0.32.0/go.mod h1:ZnnJkOaASj8g0AjIduWNlq2NRxL0PlBrbKVyZ6V/Ugc=
|
golang.org/x/crypto v0.43.0/go.mod h1:BFbav4mRNlXJL4wNeejLpWxB7wMbc79PdRGhWKncxR0=
|
||||||
golang.org/x/exp v0.0.0-20241009180824-f66d83c29e7c h1:7dEasQXItcW1xKJ2+gg5VOiBnqWrJc+rq0DPKyvvdbY=
|
golang.org/x/exp v0.0.0-20241009180824-f66d83c29e7c h1:7dEasQXItcW1xKJ2+gg5VOiBnqWrJc+rq0DPKyvvdbY=
|
||||||
golang.org/x/exp v0.0.0-20241009180824-f66d83c29e7c/go.mod h1:NQtJDoLvd6faHhE7m4T/1IY708gDefGGjR/iUW8yQQ8=
|
golang.org/x/exp v0.0.0-20241009180824-f66d83c29e7c/go.mod h1:NQtJDoLvd6faHhE7m4T/1IY708gDefGGjR/iUW8yQQ8=
|
||||||
golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||||
golang.org/x/mod v0.21.0 h1:vvrHzRwRfVKSiLrG+d4FMl/Qi4ukBCE6kZlTUkDYRT0=
|
golang.org/x/mod v0.28.0 h1:gQBtGhjxykdjY9YhZpSlZIsbnaE2+PgjfLWUQTnoZ1U=
|
||||||
golang.org/x/mod v0.21.0/go.mod h1:6SkKJ3Xj0I0BrPOZoBy3bdMptDDU9oJrpohJ3eWZ1fY=
|
golang.org/x/mod v0.28.0/go.mod h1:yfB/L0NOf/kmEbXjzCPOx1iK1fRutOydrCMsqRhEBxI=
|
||||||
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||||
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||||
golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM=
|
golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM=
|
||||||
golang.org/x/net v0.34.0 h1:Mb7Mrk043xzHgnRM88suvJFwzVrRfHEHJEl5/71CKw0=
|
golang.org/x/net v0.46.0 h1:giFlY12I07fugqwPuWJi68oOnpfqFnJIJzaIIm2JVV4=
|
||||||
golang.org/x/net v0.34.0/go.mod h1:di0qlW3YNM5oh6GqDGQr92MyTozJPmybPK4Ev/Gm31k=
|
golang.org/x/net v0.46.0/go.mod h1:Q9BGdFy1y4nkUwiLvT5qtyhAnEHgnQ/zd8PfU6nc210=
|
||||||
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-20210220032951-036812b2e83c/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.10.0 h1:3NQrjDixjgGwUOCaF8w2+VYHv0Ve/vGYSbdkTa98gmQ=
|
golang.org/x/sync v0.17.0 h1:l60nONMj9l5drqw6jlhIELNv9I0A4OFgRsG9k2oT9Ug=
|
||||||
golang.org/x/sync v0.10.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
|
golang.org/x/sync v0.17.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI=
|
||||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||||
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
@@ -112,20 +112,20 @@ golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBc
|
|||||||
golang.org/x/sys v0.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.10.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.10.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.29.0 h1:TPYlXGxvx1MGTn2GiZDhnjPA9wZzZeGKHHmKhHYvgaU=
|
golang.org/x/sys v0.37.0 h1:fdNQudmxPjkdUTPnLn5mdQv7Zwvbvpaxqs831goi9kQ=
|
||||||
golang.org/x/sys v0.29.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
golang.org/x/sys v0.37.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
|
||||||
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/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||||
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||||
golang.org/x/text v0.21.0 h1:zyQAAkrwaneQ066sspRyJaG9VNi/YJ1NfzcGB3hZ/qo=
|
golang.org/x/text v0.30.0 h1:yznKA/E9zq54KzlzBEAWn1NXSQ8DIp/NYMy88xJjl4k=
|
||||||
golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ=
|
golang.org/x/text v0.30.0/go.mod h1:yDdHFIX9t+tORqspjENWgzaCVXgk0yYnYuSZ8UzzBVM=
|
||||||
golang.org/x/time v0.0.0-20220210224613-90d013bbcef8 h1:vVKdlvoWBphwdxWKrFZEuM0kGgGLxUOYcY4U/2Vjg44=
|
golang.org/x/time v0.0.0-20220210224613-90d013bbcef8 h1:vVKdlvoWBphwdxWKrFZEuM0kGgGLxUOYcY4U/2Vjg44=
|
||||||
golang.org/x/time v0.0.0-20220210224613-90d013bbcef8/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
golang.org/x/time v0.0.0-20220210224613-90d013bbcef8/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-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||||
golang.org/x/tools v0.1.1/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
|
golang.org/x/tools v0.1.1/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
|
||||||
golang.org/x/tools v0.26.0 h1:v/60pFQmzmT9ExmjDv2gGIfi3OqfKoEP6I5+umXlbnQ=
|
golang.org/x/tools v0.37.0 h1:DVSRzp7FwePZW356yEAChSdNcQo6Nsp+fex1SUW09lE=
|
||||||
golang.org/x/tools v0.26.0/go.mod h1:TPVVj70c7JJ3WCazhD8OdXcZg/og+b9+tH/KxylGwH0=
|
golang.org/x/tools v0.37.0/go.mod h1:MBN5QPQtLMHVdvsbtarmTNukZDdgwdwlO5qGacAzF0w=
|
||||||
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||||
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||||
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||||
|
|||||||
@@ -1,9 +1,13 @@
|
|||||||
package settings
|
package settings
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"net/netip"
|
"net/netip"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/qdm12/dns/v2/pkg/provider"
|
||||||
|
"github.com/qdm12/gluetun/internal/configuration/settings/helpers"
|
||||||
"github.com/qdm12/gosettings"
|
"github.com/qdm12/gosettings"
|
||||||
"github.com/qdm12/gosettings/reader"
|
"github.com/qdm12/gosettings/reader"
|
||||||
"github.com/qdm12/gotree"
|
"github.com/qdm12/gotree"
|
||||||
@@ -11,10 +15,31 @@ import (
|
|||||||
|
|
||||||
// DNS contains settings to configure DNS.
|
// DNS contains settings to configure DNS.
|
||||||
type DNS struct {
|
type DNS struct {
|
||||||
|
// ServerEnabled is true if the server should be running
|
||||||
|
// and used. It defaults to true, and cannot be nil
|
||||||
|
// in the internal state.
|
||||||
|
ServerEnabled *bool
|
||||||
|
// UpstreamType can be dot or plain, and defaults to dot.
|
||||||
|
UpstreamType string `json:"upstream_type"`
|
||||||
|
// UpdatePeriod is the period to update DNS block lists.
|
||||||
|
// It can be set to 0 to disable the update.
|
||||||
|
// It defaults to 24h and cannot be nil in
|
||||||
|
// the internal state.
|
||||||
|
UpdatePeriod *time.Duration
|
||||||
|
// Providers is a list of DNS providers
|
||||||
|
Providers []string `json:"providers"`
|
||||||
|
// Caching is true if the server should cache
|
||||||
|
// DNS responses.
|
||||||
|
Caching *bool `json:"caching"`
|
||||||
|
// IPv6 is true if the server should connect over IPv6.
|
||||||
|
IPv6 *bool `json:"ipv6"`
|
||||||
|
// Blacklist contains settings to configure the filter
|
||||||
|
// block lists.
|
||||||
|
Blacklist DNSBlacklist
|
||||||
// ServerAddress is the DNS server to use inside
|
// ServerAddress is the DNS server to use inside
|
||||||
// the Go program and for the system.
|
// the Go program and for the system.
|
||||||
// It defaults to '127.0.0.1' to be used with the
|
// It defaults to '127.0.0.1' to be used with the
|
||||||
// DoT server. It cannot be the zero value in the internal
|
// local server. It cannot be the zero value in the internal
|
||||||
// state.
|
// state.
|
||||||
ServerAddress netip.Addr
|
ServerAddress netip.Addr
|
||||||
// KeepNameserver is true if the existing DNS server
|
// KeepNameserver is true if the existing DNS server
|
||||||
@@ -23,20 +48,40 @@ type DNS struct {
|
|||||||
// outside the VPN tunnel since it would go through
|
// outside the VPN tunnel since it would go through
|
||||||
// the local DNS server of your Docker/Kubernetes
|
// the local DNS server of your Docker/Kubernetes
|
||||||
// configuration, which is likely not going through the tunnel.
|
// configuration, which is likely not going through the tunnel.
|
||||||
// This will also disable the DNS over TLS server and the
|
// This will also disable the DNS forwarder server and the
|
||||||
// `ServerAddress` field will be ignored.
|
// `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
|
||||||
// DOT contains settings to configure the DoT
|
|
||||||
// server.
|
|
||||||
DoT DoT
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var (
|
||||||
|
ErrDNSUpstreamTypeNotValid = errors.New("DNS upstream type is not valid")
|
||||||
|
ErrDNSUpdatePeriodTooShort = errors.New("update period is too short")
|
||||||
|
)
|
||||||
|
|
||||||
func (d DNS) validate() (err error) {
|
func (d DNS) validate() (err error) {
|
||||||
err = d.DoT.validate()
|
if !helpers.IsOneOf(d.UpstreamType, "dot", "doh", "plain") {
|
||||||
|
return fmt.Errorf("%w: %s", ErrDNSUpstreamTypeNotValid, d.UpstreamType)
|
||||||
|
}
|
||||||
|
|
||||||
|
const minUpdatePeriod = 30 * time.Second
|
||||||
|
if *d.UpdatePeriod != 0 && *d.UpdatePeriod < minUpdatePeriod {
|
||||||
|
return fmt.Errorf("%w: %s must be bigger than %s",
|
||||||
|
ErrDNSUpdatePeriodTooShort, *d.UpdatePeriod, minUpdatePeriod)
|
||||||
|
}
|
||||||
|
|
||||||
|
providers := provider.NewProviders()
|
||||||
|
for _, providerName := range d.Providers {
|
||||||
|
_, err := providers.Get(providerName)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
err = d.Blacklist.validate()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("validating DoT settings: %w", err)
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
@@ -44,9 +89,15 @@ func (d DNS) validate() (err error) {
|
|||||||
|
|
||||||
func (d *DNS) Copy() (copied DNS) {
|
func (d *DNS) Copy() (copied DNS) {
|
||||||
return DNS{
|
return DNS{
|
||||||
|
ServerEnabled: gosettings.CopyPointer(d.ServerEnabled),
|
||||||
|
UpstreamType: d.UpstreamType,
|
||||||
|
UpdatePeriod: gosettings.CopyPointer(d.UpdatePeriod),
|
||||||
|
Providers: gosettings.CopySlice(d.Providers),
|
||||||
|
Caching: gosettings.CopyPointer(d.Caching),
|
||||||
|
IPv6: gosettings.CopyPointer(d.IPv6),
|
||||||
|
Blacklist: d.Blacklist.copy(),
|
||||||
ServerAddress: d.ServerAddress,
|
ServerAddress: d.ServerAddress,
|
||||||
KeepNameserver: gosettings.CopyPointer(d.KeepNameserver),
|
KeepNameserver: gosettings.CopyPointer(d.KeepNameserver),
|
||||||
DoT: d.DoT.copy(),
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -54,16 +105,48 @@ func (d *DNS) Copy() (copied DNS) {
|
|||||||
// settings object with any field set in the other
|
// settings object with any field set in the other
|
||||||
// settings.
|
// settings.
|
||||||
func (d *DNS) overrideWith(other DNS) {
|
func (d *DNS) overrideWith(other DNS) {
|
||||||
|
d.ServerEnabled = gosettings.OverrideWithPointer(d.ServerEnabled, other.ServerEnabled)
|
||||||
|
d.UpstreamType = gosettings.OverrideWithComparable(d.UpstreamType, other.UpstreamType)
|
||||||
|
d.UpdatePeriod = gosettings.OverrideWithPointer(d.UpdatePeriod, other.UpdatePeriod)
|
||||||
|
d.Providers = gosettings.OverrideWithSlice(d.Providers, other.Providers)
|
||||||
|
d.Caching = gosettings.OverrideWithPointer(d.Caching, other.Caching)
|
||||||
|
d.IPv6 = gosettings.OverrideWithPointer(d.IPv6, other.IPv6)
|
||||||
|
d.Blacklist.overrideWith(other.Blacklist)
|
||||||
d.ServerAddress = gosettings.OverrideWithValidator(d.ServerAddress, other.ServerAddress)
|
d.ServerAddress = gosettings.OverrideWithValidator(d.ServerAddress, other.ServerAddress)
|
||||||
d.KeepNameserver = gosettings.OverrideWithPointer(d.KeepNameserver, other.KeepNameserver)
|
d.KeepNameserver = gosettings.OverrideWithPointer(d.KeepNameserver, other.KeepNameserver)
|
||||||
d.DoT.overrideWith(other.DoT)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d *DNS) setDefaults() {
|
func (d *DNS) setDefaults() {
|
||||||
localhost := netip.AddrFrom4([4]byte{127, 0, 0, 1})
|
d.ServerEnabled = gosettings.DefaultPointer(d.ServerEnabled, true)
|
||||||
d.ServerAddress = gosettings.DefaultValidator(d.ServerAddress, localhost)
|
d.UpstreamType = gosettings.DefaultComparable(d.UpstreamType, "dot")
|
||||||
|
const defaultUpdatePeriod = 24 * time.Hour
|
||||||
|
d.UpdatePeriod = gosettings.DefaultPointer(d.UpdatePeriod, defaultUpdatePeriod)
|
||||||
|
d.Providers = gosettings.DefaultSlice(d.Providers, []string{
|
||||||
|
provider.Cloudflare().Name,
|
||||||
|
})
|
||||||
|
d.Caching = gosettings.DefaultPointer(d.Caching, true)
|
||||||
|
d.IPv6 = gosettings.DefaultPointer(d.IPv6, false)
|
||||||
|
d.Blacklist.setDefaults()
|
||||||
|
d.ServerAddress = gosettings.DefaultValidator(d.ServerAddress,
|
||||||
|
netip.AddrFrom4([4]byte{127, 0, 0, 1}))
|
||||||
d.KeepNameserver = gosettings.DefaultPointer(d.KeepNameserver, false)
|
d.KeepNameserver = gosettings.DefaultPointer(d.KeepNameserver, false)
|
||||||
d.DoT.setDefaults()
|
}
|
||||||
|
|
||||||
|
func (d DNS) GetFirstPlaintextIPv4() (ipv4 netip.Addr) {
|
||||||
|
localhost := netip.AddrFrom4([4]byte{127, 0, 0, 1})
|
||||||
|
if d.ServerAddress.Compare(localhost) != 0 && d.ServerAddress.Is4() {
|
||||||
|
return d.ServerAddress
|
||||||
|
}
|
||||||
|
|
||||||
|
providers := provider.NewProviders()
|
||||||
|
provider, err := providers.Get(d.Providers[0])
|
||||||
|
if err != nil {
|
||||||
|
// Settings should be validated before calling this function,
|
||||||
|
// so an error happening here is a programming error.
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return provider.Plain.IPv4[0].Addr()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d DNS) String() string {
|
func (d DNS) String() string {
|
||||||
@@ -77,11 +160,63 @@ func (d DNS) toLinesNode() (node *gotree.Node) {
|
|||||||
return node
|
return node
|
||||||
}
|
}
|
||||||
node.Appendf("DNS server address to use: %s", d.ServerAddress)
|
node.Appendf("DNS server address to use: %s", d.ServerAddress)
|
||||||
node.AppendNode(d.DoT.toLinesNode())
|
|
||||||
|
node.Appendf("DNS forwarder server enabled: %s", gosettings.BoolToYesNo(d.ServerEnabled))
|
||||||
|
if !*d.ServerEnabled {
|
||||||
|
return node
|
||||||
|
}
|
||||||
|
|
||||||
|
node.Appendf("Upstream resolver type: %s", d.UpstreamType)
|
||||||
|
|
||||||
|
upstreamResolvers := node.Append("Upstream resolvers:")
|
||||||
|
for _, provider := range d.Providers {
|
||||||
|
upstreamResolvers.Append(provider)
|
||||||
|
}
|
||||||
|
|
||||||
|
node.Appendf("Caching: %s", gosettings.BoolToYesNo(d.Caching))
|
||||||
|
node.Appendf("IPv6: %s", gosettings.BoolToYesNo(d.IPv6))
|
||||||
|
|
||||||
|
update := "disabled"
|
||||||
|
if *d.UpdatePeriod > 0 {
|
||||||
|
update = "every " + d.UpdatePeriod.String()
|
||||||
|
}
|
||||||
|
node.Appendf("Update period: %s", update)
|
||||||
|
|
||||||
|
node.AppendNode(d.Blacklist.toLinesNode())
|
||||||
|
|
||||||
return node
|
return node
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d *DNS) read(r *reader.Reader) (err error) {
|
func (d *DNS) read(r *reader.Reader) (err error) {
|
||||||
|
d.ServerEnabled, err = r.BoolPtr("DNS_SERVER", reader.RetroKeys("DOT"))
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
d.UpstreamType = r.String("DNS_UPSTREAM_RESOLVER_TYPE")
|
||||||
|
|
||||||
|
d.UpdatePeriod, err = r.DurationPtr("DNS_UPDATE_PERIOD")
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
d.Providers = r.CSV("DNS_UPSTREAM_RESOLVERS", reader.RetroKeys("DOT_PROVIDERS"))
|
||||||
|
|
||||||
|
d.Caching, err = r.BoolPtr("DNS_CACHING", reader.RetroKeys("DOT_CACHING"))
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
d.IPv6, err = r.BoolPtr("DNS_UPSTREAM_IPV6", reader.RetroKeys("DOT_IPV6"))
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
err = d.Blacklist.read(r)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
d.ServerAddress, err = r.NetipAddr("DNS_ADDRESS", reader.RetroKeys("DNS_PLAINTEXT_ADDRESS"))
|
d.ServerAddress, err = r.NetipAddr("DNS_ADDRESS", reader.RetroKeys("DNS_PLAINTEXT_ADDRESS"))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
@@ -92,10 +227,5 @@ func (d *DNS) read(r *reader.Reader) (err error) {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
err = d.DoT.read(r)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("DNS over TLS settings: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -149,23 +149,45 @@ func (b *DNSBlacklist) read(r *reader.Reader) (err error) {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
b.AddBlockedIPs, b.AddBlockedIPPrefixes,
|
b.AddBlockedIPs, b.AddBlockedIPPrefixes, err = readDNSBlockedIPs(r)
|
||||||
err = readDoTPrivateAddresses(r) // TODO v4 split in 2
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
b.AllowedHosts = r.CSV("UNBLOCK") // TODO v4 change name
|
b.AllowedHosts = r.CSV("DNS_UNBLOCK_HOSTNAMES", reader.RetroKeys("UNBLOCK"))
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
var ErrPrivateAddressNotValid = errors.New("private address is not a valid IP or CIDR range")
|
func readDNSBlockedIPs(r *reader.Reader) (ips []netip.Addr,
|
||||||
|
|
||||||
func readDoTPrivateAddresses(reader *reader.Reader) (ips []netip.Addr,
|
|
||||||
ipPrefixes []netip.Prefix, err error,
|
ipPrefixes []netip.Prefix, err error,
|
||||||
) {
|
) {
|
||||||
privateAddresses := reader.CSV("DOT_PRIVATE_ADDRESS")
|
ips, err = r.CSVNetipAddresses("DNS_BLOCK_IPS")
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
ipPrefixes, err = r.CSVNetipPrefixes("DNS_BLOCK_IP_PREFIXES")
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO v4 remove this block below
|
||||||
|
privateIPs, privateIPPrefixes, err := readDNSPrivateAddresses(r)
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
ips = append(ips, privateIPs...)
|
||||||
|
ipPrefixes = append(ipPrefixes, privateIPPrefixes...)
|
||||||
|
|
||||||
|
return ips, ipPrefixes, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
var ErrPrivateAddressNotValid = errors.New("private address is not a valid IP or CIDR range")
|
||||||
|
|
||||||
|
func readDNSPrivateAddresses(r *reader.Reader) (ips []netip.Addr,
|
||||||
|
ipPrefixes []netip.Prefix, err error,
|
||||||
|
) {
|
||||||
|
privateAddresses := r.CSV("DOT_PRIVATE_ADDRESS", reader.IsRetro("DNS_BLOCK_IP_PREFIXES"))
|
||||||
if len(privateAddresses) == 0 {
|
if len(privateAddresses) == 0 {
|
||||||
return nil, nil, nil
|
return nil, nil, nil
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,170 +0,0 @@
|
|||||||
package settings
|
|
||||||
|
|
||||||
import (
|
|
||||||
"errors"
|
|
||||||
"fmt"
|
|
||||||
"net/netip"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/qdm12/dns/v2/pkg/provider"
|
|
||||||
"github.com/qdm12/gosettings"
|
|
||||||
"github.com/qdm12/gosettings/reader"
|
|
||||||
"github.com/qdm12/gotree"
|
|
||||||
)
|
|
||||||
|
|
||||||
// DoT contains settings to configure the DoT server.
|
|
||||||
type DoT struct {
|
|
||||||
// Enabled is true if the DoT server should be running
|
|
||||||
// and used. It defaults to true, and cannot be nil
|
|
||||||
// in the internal state.
|
|
||||||
Enabled *bool
|
|
||||||
// UpdatePeriod is the period to update DNS block lists.
|
|
||||||
// It can be set to 0 to disable the update.
|
|
||||||
// It defaults to 24h and cannot be nil in
|
|
||||||
// the internal state.
|
|
||||||
UpdatePeriod *time.Duration
|
|
||||||
// Providers is a list of DNS over TLS providers
|
|
||||||
Providers []string `json:"providers"`
|
|
||||||
// Caching is true if the DoT server should cache
|
|
||||||
// DNS responses.
|
|
||||||
Caching *bool `json:"caching"`
|
|
||||||
// IPv6 is true if the DoT server should connect over IPv6.
|
|
||||||
IPv6 *bool `json:"ipv6"`
|
|
||||||
// Blacklist contains settings to configure the filter
|
|
||||||
// block lists.
|
|
||||||
Blacklist DNSBlacklist
|
|
||||||
}
|
|
||||||
|
|
||||||
var ErrDoTUpdatePeriodTooShort = errors.New("update period is too short")
|
|
||||||
|
|
||||||
func (d DoT) validate() (err error) {
|
|
||||||
const minUpdatePeriod = 30 * time.Second
|
|
||||||
if *d.UpdatePeriod != 0 && *d.UpdatePeriod < minUpdatePeriod {
|
|
||||||
return fmt.Errorf("%w: %s must be bigger than %s",
|
|
||||||
ErrDoTUpdatePeriodTooShort, *d.UpdatePeriod, minUpdatePeriod)
|
|
||||||
}
|
|
||||||
|
|
||||||
providers := provider.NewProviders()
|
|
||||||
for _, providerName := range d.Providers {
|
|
||||||
_, err := providers.Get(providerName)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
err = d.Blacklist.validate()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (d *DoT) copy() (copied DoT) {
|
|
||||||
return DoT{
|
|
||||||
Enabled: gosettings.CopyPointer(d.Enabled),
|
|
||||||
UpdatePeriod: gosettings.CopyPointer(d.UpdatePeriod),
|
|
||||||
Providers: gosettings.CopySlice(d.Providers),
|
|
||||||
Caching: gosettings.CopyPointer(d.Caching),
|
|
||||||
IPv6: gosettings.CopyPointer(d.IPv6),
|
|
||||||
Blacklist: d.Blacklist.copy(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// overrideWith overrides fields of the receiver
|
|
||||||
// settings object with any field set in the other
|
|
||||||
// settings.
|
|
||||||
func (d *DoT) overrideWith(other DoT) {
|
|
||||||
d.Enabled = gosettings.OverrideWithPointer(d.Enabled, other.Enabled)
|
|
||||||
d.UpdatePeriod = gosettings.OverrideWithPointer(d.UpdatePeriod, other.UpdatePeriod)
|
|
||||||
d.Providers = gosettings.OverrideWithSlice(d.Providers, other.Providers)
|
|
||||||
d.Caching = gosettings.OverrideWithPointer(d.Caching, other.Caching)
|
|
||||||
d.IPv6 = gosettings.OverrideWithPointer(d.IPv6, other.IPv6)
|
|
||||||
d.Blacklist.overrideWith(other.Blacklist)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (d *DoT) setDefaults() {
|
|
||||||
d.Enabled = gosettings.DefaultPointer(d.Enabled, true)
|
|
||||||
const defaultUpdatePeriod = 24 * time.Hour
|
|
||||||
d.UpdatePeriod = gosettings.DefaultPointer(d.UpdatePeriod, defaultUpdatePeriod)
|
|
||||||
d.Providers = gosettings.DefaultSlice(d.Providers, []string{
|
|
||||||
provider.Cloudflare().Name,
|
|
||||||
})
|
|
||||||
d.Caching = gosettings.DefaultPointer(d.Caching, true)
|
|
||||||
d.IPv6 = gosettings.DefaultPointer(d.IPv6, false)
|
|
||||||
d.Blacklist.setDefaults()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (d DoT) GetFirstPlaintextIPv4() (ipv4 netip.Addr) {
|
|
||||||
providers := provider.NewProviders()
|
|
||||||
provider, err := providers.Get(d.Providers[0])
|
|
||||||
if err != nil {
|
|
||||||
// Settings should be validated before calling this function,
|
|
||||||
// so an error happening here is a programming error.
|
|
||||||
panic(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
return provider.DoT.IPv4[0].Addr()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (d DoT) String() string {
|
|
||||||
return d.toLinesNode().String()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (d DoT) toLinesNode() (node *gotree.Node) {
|
|
||||||
node = gotree.New("DNS over TLS settings:")
|
|
||||||
|
|
||||||
node.Appendf("Enabled: %s", gosettings.BoolToYesNo(d.Enabled))
|
|
||||||
if !*d.Enabled {
|
|
||||||
return node
|
|
||||||
}
|
|
||||||
|
|
||||||
update := "disabled"
|
|
||||||
if *d.UpdatePeriod > 0 {
|
|
||||||
update = "every " + d.UpdatePeriod.String()
|
|
||||||
}
|
|
||||||
node.Appendf("Update period: %s", update)
|
|
||||||
|
|
||||||
upstreamResolvers := node.Append("Upstream resolvers:")
|
|
||||||
for _, provider := range d.Providers {
|
|
||||||
upstreamResolvers.Append(provider)
|
|
||||||
}
|
|
||||||
|
|
||||||
node.Appendf("Caching: %s", gosettings.BoolToYesNo(d.Caching))
|
|
||||||
node.Appendf("IPv6: %s", gosettings.BoolToYesNo(d.IPv6))
|
|
||||||
|
|
||||||
node.AppendNode(d.Blacklist.toLinesNode())
|
|
||||||
|
|
||||||
return node
|
|
||||||
}
|
|
||||||
|
|
||||||
func (d *DoT) read(reader *reader.Reader) (err error) {
|
|
||||||
d.Enabled, err = reader.BoolPtr("DOT")
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
d.UpdatePeriod, err = reader.DurationPtr("DNS_UPDATE_PERIOD")
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
d.Providers = reader.CSV("DOT_PROVIDERS")
|
|
||||||
|
|
||||||
d.Caching, err = reader.BoolPtr("DOT_CACHING")
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
d.IPv6, err = reader.BoolPtr("DOT_IPV6")
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
err = d.Blacklist.read(reader)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
@@ -29,9 +29,12 @@ type Health struct {
|
|||||||
// It cannot be the empty string in the internal state.
|
// It cannot be the empty string in the internal state.
|
||||||
TargetAddress string
|
TargetAddress string
|
||||||
// ICMPTargetIP is the IP address to use for ICMP echo requests
|
// ICMPTargetIP is the IP address to use for ICMP echo requests
|
||||||
// in the health checker. It can be set to an unspecified address
|
// in the health checker. It can be set to an unspecified address (0.0.0.0)
|
||||||
// such that the VPN server IP is used, which is also the default behavior.
|
// such that the VPN server IP is used, which is also the default behavior.
|
||||||
ICMPTargetIP netip.Addr
|
ICMPTargetIP netip.Addr
|
||||||
|
// RestartVPN indicates whether to restart the VPN connection
|
||||||
|
// when the healthcheck fails.
|
||||||
|
RestartVPN *bool
|
||||||
}
|
}
|
||||||
|
|
||||||
func (h Health) Validate() (err error) {
|
func (h Health) Validate() (err error) {
|
||||||
@@ -50,6 +53,7 @@ func (h *Health) copy() (copied Health) {
|
|||||||
ReadTimeout: h.ReadTimeout,
|
ReadTimeout: h.ReadTimeout,
|
||||||
TargetAddress: h.TargetAddress,
|
TargetAddress: h.TargetAddress,
|
||||||
ICMPTargetIP: h.ICMPTargetIP,
|
ICMPTargetIP: h.ICMPTargetIP,
|
||||||
|
RestartVPN: gosettings.CopyPointer(h.RestartVPN),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -62,6 +66,7 @@ func (h *Health) OverrideWith(other Health) {
|
|||||||
h.ReadTimeout = gosettings.OverrideWithComparable(h.ReadTimeout, other.ReadTimeout)
|
h.ReadTimeout = gosettings.OverrideWithComparable(h.ReadTimeout, other.ReadTimeout)
|
||||||
h.TargetAddress = gosettings.OverrideWithComparable(h.TargetAddress, other.TargetAddress)
|
h.TargetAddress = gosettings.OverrideWithComparable(h.TargetAddress, other.TargetAddress)
|
||||||
h.ICMPTargetIP = gosettings.OverrideWithComparable(h.ICMPTargetIP, other.ICMPTargetIP)
|
h.ICMPTargetIP = gosettings.OverrideWithComparable(h.ICMPTargetIP, other.ICMPTargetIP)
|
||||||
|
h.RestartVPN = gosettings.OverrideWithPointer(h.RestartVPN, other.RestartVPN)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (h *Health) SetDefaults() {
|
func (h *Health) SetDefaults() {
|
||||||
@@ -72,6 +77,7 @@ func (h *Health) SetDefaults() {
|
|||||||
h.ReadTimeout = gosettings.DefaultComparable(h.ReadTimeout, defaultReadTimeout)
|
h.ReadTimeout = gosettings.DefaultComparable(h.ReadTimeout, defaultReadTimeout)
|
||||||
h.TargetAddress = gosettings.DefaultComparable(h.TargetAddress, "cloudflare.com:443")
|
h.TargetAddress = gosettings.DefaultComparable(h.TargetAddress, "cloudflare.com:443")
|
||||||
h.ICMPTargetIP = gosettings.DefaultComparable(h.ICMPTargetIP, netip.IPv4Unspecified()) // use the VPN server IP
|
h.ICMPTargetIP = gosettings.DefaultComparable(h.ICMPTargetIP, netip.IPv4Unspecified()) // use the VPN server IP
|
||||||
|
h.RestartVPN = gosettings.DefaultPointer(h.RestartVPN, true)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (h Health) String() string {
|
func (h Health) String() string {
|
||||||
@@ -87,6 +93,7 @@ func (h Health) toLinesNode() (node *gotree.Node) {
|
|||||||
icmpTarget = h.ICMPTargetIP.String()
|
icmpTarget = h.ICMPTargetIP.String()
|
||||||
}
|
}
|
||||||
node.Appendf("ICMP target IP: %s", icmpTarget)
|
node.Appendf("ICMP target IP: %s", icmpTarget)
|
||||||
|
node.Appendf("Restart VPN on healthcheck failure: %s", gosettings.BoolToYesNo(h.RestartVPN))
|
||||||
return node
|
return node
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -98,5 +105,9 @@ func (h *Health) Read(r *reader.Reader) (err error) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
h.RestartVPN, err = r.BoolPtr("HEALTH_RESTART_VPN")
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -177,10 +177,10 @@ func (s Settings) Warnings() (warnings []string) {
|
|||||||
// TODO remove in v4
|
// TODO remove in v4
|
||||||
if s.DNS.ServerAddress.Unmap().Compare(netip.AddrFrom4([4]byte{127, 0, 0, 1})) != 0 {
|
if s.DNS.ServerAddress.Unmap().Compare(netip.AddrFrom4([4]byte{127, 0, 0, 1})) != 0 {
|
||||||
warnings = append(warnings, "DNS address is set to "+s.DNS.ServerAddress.String()+
|
warnings = append(warnings, "DNS address is set to "+s.DNS.ServerAddress.String()+
|
||||||
" so the DNS over TLS (DoT) server will not be used."+
|
" so the local forwarding DNS server will not be used."+
|
||||||
" The default value changed to 127.0.0.1 so it uses the internal DoT serves."+
|
" The default value changed to 127.0.0.1 so it uses the internal DNS server."+
|
||||||
" If the DoT server fails to start, the IPv4 address of the first plaintext DNS server"+
|
" If this server fails to start, the IPv4 address of the first plaintext DNS server"+
|
||||||
" corresponding to the first DoT provider chosen is used.")
|
" corresponding to the first DNS provider chosen is used.")
|
||||||
}
|
}
|
||||||
|
|
||||||
return warnings
|
return warnings
|
||||||
|
|||||||
@@ -40,17 +40,17 @@ func Test_Settings_String(t *testing.T) {
|
|||||||
├── DNS settings:
|
├── DNS settings:
|
||||||
| ├── Keep existing nameserver(s): no
|
| ├── Keep existing nameserver(s): no
|
||||||
| ├── DNS server address to use: 127.0.0.1
|
| ├── DNS server address to use: 127.0.0.1
|
||||||
| └── DNS over TLS settings:
|
| ├── DNS forwarder server enabled: yes
|
||||||
| ├── Enabled: yes
|
| ├── Upstream resolver type: dot
|
||||||
| ├── Update period: every 24h0m0s
|
| ├── Upstream resolvers:
|
||||||
| ├── Upstream resolvers:
|
| | └── Cloudflare
|
||||||
| | └── Cloudflare
|
| ├── Caching: yes
|
||||||
| ├── Caching: yes
|
| ├── IPv6: no
|
||||||
| ├── IPv6: no
|
| ├── Update period: every 24h0m0s
|
||||||
| └── DNS filtering settings:
|
| └── DNS filtering settings:
|
||||||
| ├── Block malicious: yes
|
| ├── Block malicious: yes
|
||||||
| ├── Block ads: no
|
| ├── Block ads: no
|
||||||
| └── Block surveillance: yes
|
| └── Block surveillance: yes
|
||||||
├── Firewall settings:
|
├── Firewall settings:
|
||||||
| └── Enabled: yes
|
| └── Enabled: yes
|
||||||
├── Log settings:
|
├── Log settings:
|
||||||
@@ -58,7 +58,8 @@ func Test_Settings_String(t *testing.T) {
|
|||||||
├── Health settings:
|
├── Health settings:
|
||||||
| ├── Server listening address: 127.0.0.1:9999
|
| ├── Server listening address: 127.0.0.1:9999
|
||||||
| ├── Target address: cloudflare.com:443
|
| ├── Target address: cloudflare.com:443
|
||||||
| └── ICMP target IP: VPN server IP
|
| ├── ICMP target IP: VPN server IP
|
||||||
|
| └── Restart VPN on healthcheck failure: yes
|
||||||
├── Shadowsocks server settings:
|
├── Shadowsocks server settings:
|
||||||
| └── Enabled: no
|
| └── Enabled: no
|
||||||
├── HTTP proxy settings:
|
├── HTTP proxy settings:
|
||||||
|
|||||||
@@ -10,15 +10,7 @@ import (
|
|||||||
func (l *Loop) useUnencryptedDNS(fallback bool) {
|
func (l *Loop) useUnencryptedDNS(fallback bool) {
|
||||||
settings := l.GetSettings()
|
settings := l.GetSettings()
|
||||||
|
|
||||||
// Try with user provided plaintext ip address
|
targetIP := settings.GetFirstPlaintextIPv4()
|
||||||
// if it's not 127.0.0.1 (default for DoT), otherwise
|
|
||||||
// use the first DoT provider ipv4 address found.
|
|
||||||
var targetIP netip.Addr
|
|
||||||
if settings.ServerAddress.Compare(netip.AddrFrom4([4]byte{127, 0, 0, 1})) != 0 {
|
|
||||||
targetIP = settings.ServerAddress
|
|
||||||
} else {
|
|
||||||
targetIP = settings.DoT.GetFirstPlaintextIPv4()
|
|
||||||
}
|
|
||||||
|
|
||||||
if fallback {
|
if fallback {
|
||||||
l.logger.Info("falling back on plaintext DNS at address " + targetIP.String())
|
l.logger.Info("falling back on plaintext DNS at address " + targetIP.String())
|
||||||
@@ -27,14 +19,15 @@ func (l *Loop) useUnencryptedDNS(fallback bool) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const dialTimeout = 3 * time.Second
|
const dialTimeout = 3 * time.Second
|
||||||
|
const defaultDNSPort = 53
|
||||||
settingsInternalDNS := nameserver.SettingsInternalDNS{
|
settingsInternalDNS := nameserver.SettingsInternalDNS{
|
||||||
IP: targetIP,
|
AddrPort: netip.AddrPortFrom(targetIP, defaultDNSPort),
|
||||||
Timeout: dialTimeout,
|
Timeout: dialTimeout,
|
||||||
}
|
}
|
||||||
nameserver.UseDNSInternally(settingsInternalDNS)
|
nameserver.UseDNSInternally(settingsInternalDNS)
|
||||||
|
|
||||||
settingsSystemWide := nameserver.SettingsSystemDNS{
|
settingsSystemWide := nameserver.SettingsSystemDNS{
|
||||||
IP: targetIP,
|
IPs: []netip.Addr{targetIP},
|
||||||
ResolvPath: l.resolvConf,
|
ResolvPath: l.resolvConf,
|
||||||
}
|
}
|
||||||
err := nameserver.UseDNSSystemWide(settingsSystemWide)
|
err := nameserver.UseDNSSystemWide(settingsSystemWide)
|
||||||
|
|||||||
@@ -26,12 +26,12 @@ func (l *Loop) Run(ctx context.Context, done chan<- struct{}) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
for ctx.Err() == nil {
|
for ctx.Err() == nil {
|
||||||
// Upper scope variables for the DNS over TLS server only
|
// Upper scope variables for the DNS forwarder server only
|
||||||
// Their values are to be used if DOT=off
|
// Their values are to be used if DOT=off
|
||||||
var runError <-chan error
|
var runError <-chan error
|
||||||
|
|
||||||
settings := l.GetSettings()
|
settings := l.GetSettings()
|
||||||
for !*settings.KeepNameserver && *settings.DoT.Enabled {
|
for !*settings.KeepNameserver && *settings.ServerEnabled {
|
||||||
var err error
|
var err error
|
||||||
runError, err = l.setupServer(ctx)
|
runError, err = l.setupServer(ctx)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
@@ -56,7 +56,7 @@ func (l *Loop) Run(ctx context.Context, done chan<- struct{}) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
settings = l.GetSettings()
|
settings = l.GetSettings()
|
||||||
if !*settings.KeepNameserver && !*settings.DoT.Enabled {
|
if !*settings.KeepNameserver && !*settings.ServerEnabled {
|
||||||
const fallback = false
|
const fallback = false
|
||||||
l.useUnencryptedDNS(fallback)
|
l.useUnencryptedDNS(fallback)
|
||||||
}
|
}
|
||||||
@@ -101,6 +101,6 @@ func (l *Loop) runWait(ctx context.Context, runError <-chan error) (exitLoop boo
|
|||||||
func (l *Loop) stopServer() {
|
func (l *Loop) stopServer() {
|
||||||
stopErr := l.server.Stop()
|
stopErr := l.server.Stop()
|
||||||
if stopErr != nil {
|
if stopErr != nil {
|
||||||
l.logger.Error("stopping DoT server: " + stopErr.Error())
|
l.logger.Error("stopping server: " + stopErr.Error())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,11 +4,13 @@ import (
|
|||||||
"context"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
|
||||||
|
"github.com/qdm12/dns/v2/pkg/doh"
|
||||||
"github.com/qdm12/dns/v2/pkg/dot"
|
"github.com/qdm12/dns/v2/pkg/dot"
|
||||||
cachemiddleware "github.com/qdm12/dns/v2/pkg/middlewares/cache"
|
cachemiddleware "github.com/qdm12/dns/v2/pkg/middlewares/cache"
|
||||||
"github.com/qdm12/dns/v2/pkg/middlewares/cache/lru"
|
"github.com/qdm12/dns/v2/pkg/middlewares/cache/lru"
|
||||||
filtermiddleware "github.com/qdm12/dns/v2/pkg/middlewares/filter"
|
filtermiddleware "github.com/qdm12/dns/v2/pkg/middlewares/filter"
|
||||||
"github.com/qdm12/dns/v2/pkg/middlewares/filter/mapfilter"
|
"github.com/qdm12/dns/v2/pkg/middlewares/filter/mapfilter"
|
||||||
|
"github.com/qdm12/dns/v2/pkg/plain"
|
||||||
"github.com/qdm12/dns/v2/pkg/provider"
|
"github.com/qdm12/dns/v2/pkg/provider"
|
||||||
"github.com/qdm12/dns/v2/pkg/server"
|
"github.com/qdm12/dns/v2/pkg/server"
|
||||||
"github.com/qdm12/gluetun/internal/configuration/settings"
|
"github.com/qdm12/gluetun/internal/configuration/settings"
|
||||||
@@ -22,33 +24,62 @@ func (l *Loop) SetSettings(ctx context.Context, settings settings.DNS) (
|
|||||||
return l.state.SetSettings(ctx, settings)
|
return l.state.SetSettings(ctx, settings)
|
||||||
}
|
}
|
||||||
|
|
||||||
func buildDoTSettings(settings settings.DNS,
|
func buildServerSettings(settings settings.DNS,
|
||||||
filter *mapfilter.Filter, logger Logger) (
|
filter *mapfilter.Filter, logger Logger) (
|
||||||
serverSettings server.Settings, err error,
|
serverSettings server.Settings, err error,
|
||||||
) {
|
) {
|
||||||
serverSettings.Logger = logger
|
serverSettings.Logger = logger
|
||||||
|
|
||||||
var dotSettings dot.Settings
|
|
||||||
providersData := provider.NewProviders()
|
providersData := provider.NewProviders()
|
||||||
dotSettings.UpstreamResolvers = make([]provider.Provider, len(settings.DoT.Providers))
|
upstreamResolvers := make([]provider.Provider, len(settings.Providers))
|
||||||
for i := range settings.DoT.Providers {
|
for i := range settings.Providers {
|
||||||
var err error
|
var err error
|
||||||
dotSettings.UpstreamResolvers[i], err = providersData.Get(settings.DoT.Providers[i])
|
upstreamResolvers[i], err = providersData.Get(settings.Providers[i])
|
||||||
if err != nil {
|
if err != nil {
|
||||||
panic(err) // this should already had been checked
|
panic(err) // this should already had been checked
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
dotSettings.IPVersion = "ipv4"
|
|
||||||
if *settings.DoT.IPv6 {
|
ipVersion := "ipv4"
|
||||||
dotSettings.IPVersion = "ipv6"
|
if *settings.IPv6 {
|
||||||
|
ipVersion = "ipv6"
|
||||||
}
|
}
|
||||||
|
|
||||||
serverSettings.Dialer, err = dot.New(dotSettings)
|
var dialer server.Dialer
|
||||||
if err != nil {
|
switch settings.UpstreamType {
|
||||||
return server.Settings{}, fmt.Errorf("creating DNS over TLS dialer: %w", err)
|
case "dot":
|
||||||
|
dialerSettings := dot.Settings{
|
||||||
|
UpstreamResolvers: upstreamResolvers,
|
||||||
|
IPVersion: ipVersion,
|
||||||
|
}
|
||||||
|
dialer, err = dot.New(dialerSettings)
|
||||||
|
if err != nil {
|
||||||
|
return server.Settings{}, fmt.Errorf("creating DNS over TLS dialer: %w", err)
|
||||||
|
}
|
||||||
|
case "doh":
|
||||||
|
dialerSettings := doh.Settings{
|
||||||
|
UpstreamResolvers: upstreamResolvers,
|
||||||
|
IPVersion: ipVersion,
|
||||||
|
}
|
||||||
|
dialer, err = doh.New(dialerSettings)
|
||||||
|
if err != nil {
|
||||||
|
return server.Settings{}, fmt.Errorf("creating DNS over HTTPS dialer: %w", err)
|
||||||
|
}
|
||||||
|
case "plain":
|
||||||
|
dialerSettings := plain.Settings{
|
||||||
|
UpstreamResolvers: upstreamResolvers,
|
||||||
|
IPVersion: ipVersion,
|
||||||
|
}
|
||||||
|
dialer, err = plain.New(dialerSettings)
|
||||||
|
if err != nil {
|
||||||
|
return server.Settings{}, fmt.Errorf("creating plain DNS dialer: %w", err)
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
panic("unknown upstream type: " + settings.UpstreamType)
|
||||||
}
|
}
|
||||||
|
serverSettings.Dialer = dialer
|
||||||
|
|
||||||
if *settings.DoT.Caching {
|
if *settings.Caching {
|
||||||
lruCache, err := lru.New(lru.Settings{})
|
lruCache, err := lru.New(lru.Settings{})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return server.Settings{}, fmt.Errorf("creating LRU cache: %w", err)
|
return server.Settings{}, fmt.Errorf("creating LRU cache: %w", err)
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import (
|
|||||||
"context"
|
"context"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"net/netip"
|
||||||
|
|
||||||
"github.com/qdm12/dns/v2/pkg/check"
|
"github.com/qdm12/dns/v2/pkg/check"
|
||||||
"github.com/qdm12/dns/v2/pkg/nameserver"
|
"github.com/qdm12/dns/v2/pkg/nameserver"
|
||||||
@@ -20,14 +21,14 @@ func (l *Loop) setupServer(ctx context.Context) (runError <-chan error, err erro
|
|||||||
|
|
||||||
settings := l.GetSettings()
|
settings := l.GetSettings()
|
||||||
|
|
||||||
dotSettings, err := buildDoTSettings(settings, l.filter, l.logger)
|
serverSettings, err := buildServerSettings(settings, l.filter, l.logger)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("building DoT settings: %w", err)
|
return nil, fmt.Errorf("building server settings: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
server, err := server.New(dotSettings)
|
server, err := server.New(serverSettings)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("creating DoT server: %w", err)
|
return nil, fmt.Errorf("creating server: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
runError, err = server.Start(ctx)
|
runError, err = server.Start(ctx)
|
||||||
@@ -37,11 +38,12 @@ func (l *Loop) setupServer(ctx context.Context) (runError <-chan error, err erro
|
|||||||
l.server = server
|
l.server = server
|
||||||
|
|
||||||
// use internal DNS server
|
// use internal DNS server
|
||||||
|
const defaultDNSPort = 53
|
||||||
nameserver.UseDNSInternally(nameserver.SettingsInternalDNS{
|
nameserver.UseDNSInternally(nameserver.SettingsInternalDNS{
|
||||||
IP: settings.ServerAddress,
|
AddrPort: netip.AddrPortFrom(settings.ServerAddress, defaultDNSPort),
|
||||||
})
|
})
|
||||||
err = nameserver.UseDNSSystemWide(nameserver.SettingsSystemDNS{
|
err = nameserver.UseDNSSystemWide(nameserver.SettingsSystemDNS{
|
||||||
IP: settings.ServerAddress,
|
IPs: []netip.Addr{settings.ServerAddress},
|
||||||
ResolvPath: l.resolvConf,
|
ResolvPath: l.resolvConf,
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|||||||
@@ -27,7 +27,7 @@ func (s *State) SetSettings(ctx context.Context, settings settings.DNS) (
|
|||||||
|
|
||||||
// Check for only update period change
|
// Check for only update period change
|
||||||
tempSettings := s.settings.Copy()
|
tempSettings := s.settings.Copy()
|
||||||
*tempSettings.DoT.UpdatePeriod = *settings.DoT.UpdatePeriod
|
*tempSettings.UpdatePeriod = *settings.UpdatePeriod
|
||||||
onlyUpdatePeriodChanged := reflect.DeepEqual(tempSettings, settings)
|
onlyUpdatePeriodChanged := reflect.DeepEqual(tempSettings, settings)
|
||||||
|
|
||||||
s.settings = settings
|
s.settings = settings
|
||||||
@@ -40,7 +40,7 @@ func (s *State) SetSettings(ctx context.Context, settings settings.DNS) (
|
|||||||
|
|
||||||
// Restart
|
// Restart
|
||||||
_, _ = s.statusApplier.ApplyStatus(ctx, constants.Stopped)
|
_, _ = s.statusApplier.ApplyStatus(ctx, constants.Stopped)
|
||||||
if *settings.DoT.Enabled {
|
if *settings.ServerEnabled {
|
||||||
outcome, _ = s.statusApplier.ApplyStatus(ctx, constants.Running)
|
outcome, _ = s.statusApplier.ApplyStatus(ctx, constants.Running)
|
||||||
}
|
}
|
||||||
return outcome
|
return outcome
|
||||||
|
|||||||
@@ -14,7 +14,7 @@ func (l *Loop) RunRestartTicker(ctx context.Context, done chan<- struct{}) {
|
|||||||
timer.Stop()
|
timer.Stop()
|
||||||
timerIsStopped := true
|
timerIsStopped := true
|
||||||
settings := l.GetSettings()
|
settings := l.GetSettings()
|
||||||
if period := *settings.DoT.UpdatePeriod; period > 0 {
|
if period := *settings.UpdatePeriod; period > 0 {
|
||||||
timer.Reset(period)
|
timer.Reset(period)
|
||||||
timerIsStopped = false
|
timerIsStopped = false
|
||||||
}
|
}
|
||||||
@@ -43,14 +43,14 @@ func (l *Loop) RunRestartTicker(ctx context.Context, done chan<- struct{}) {
|
|||||||
_, _ = l.statusManager.ApplyStatus(ctx, constants.Running)
|
_, _ = l.statusManager.ApplyStatus(ctx, constants.Running)
|
||||||
|
|
||||||
settings := l.GetSettings()
|
settings := l.GetSettings()
|
||||||
timer.Reset(*settings.DoT.UpdatePeriod)
|
timer.Reset(*settings.UpdatePeriod)
|
||||||
case <-l.updateTicker:
|
case <-l.updateTicker:
|
||||||
if !timer.Stop() {
|
if !timer.Stop() {
|
||||||
<-timer.C
|
<-timer.C
|
||||||
}
|
}
|
||||||
timerIsStopped = true
|
timerIsStopped = true
|
||||||
settings := l.GetSettings()
|
settings := l.GetSettings()
|
||||||
newUpdatePeriod := *settings.DoT.UpdatePeriod
|
newUpdatePeriod := *settings.UpdatePeriod
|
||||||
if newUpdatePeriod == 0 {
|
if newUpdatePeriod == 0 {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ func (l *Loop) updateFiles(ctx context.Context) (err error) {
|
|||||||
settings := l.GetSettings()
|
settings := l.GetSettings()
|
||||||
|
|
||||||
l.logger.Info("downloading hostnames and IP block lists")
|
l.logger.Info("downloading hostnames and IP block lists")
|
||||||
blacklistSettings := settings.DoT.Blacklist.ToBlockBuilderSettings(l.client)
|
blacklistSettings := settings.Blacklist.ToBlockBuilderSettings(l.client)
|
||||||
|
|
||||||
blockBuilder, err := blockbuilder.New(blacklistSettings)
|
blockBuilder, err := blockbuilder.New(blacklistSettings)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|||||||
@@ -323,12 +323,12 @@ var ErrProtocolUnknown = errors.New("unknown protocol")
|
|||||||
|
|
||||||
func parseProtocol(s string) (protocol string, err error) {
|
func parseProtocol(s string) (protocol string, err error) {
|
||||||
switch s {
|
switch s {
|
||||||
case "0":
|
case "0", "all":
|
||||||
case "1":
|
case "1", "icmp":
|
||||||
protocol = "icmp"
|
protocol = "icmp"
|
||||||
case "6":
|
case "6", "tcp":
|
||||||
protocol = "tcp"
|
protocol = "tcp"
|
||||||
case "17":
|
case "17", "udp":
|
||||||
protocol = "udp"
|
protocol = "udp"
|
||||||
default:
|
default:
|
||||||
return "", fmt.Errorf("%w: %s", ErrProtocolUnknown, s)
|
return "", fmt.Errorf("%w: %s", ErrProtocolUnknown, s)
|
||||||
|
|||||||
@@ -58,6 +58,7 @@ num pkts bytes target prot opt in out source destinati
|
|||||||
2 0 0 ACCEPT 6 -- tun0 * 0.0.0.0/0 0.0.0.0/0 tcp dpt:55405
|
2 0 0 ACCEPT 6 -- tun0 * 0.0.0.0/0 0.0.0.0/0 tcp dpt:55405
|
||||||
3 0 0 ACCEPT 1 -- tun0 * 0.0.0.0/0 0.0.0.0/0
|
3 0 0 ACCEPT 1 -- tun0 * 0.0.0.0/0 0.0.0.0/0
|
||||||
4 0 0 DROP 0 -- tun0 * 1.2.3.4 0.0.0.0/0
|
4 0 0 DROP 0 -- tun0 * 1.2.3.4 0.0.0.0/0
|
||||||
|
5 0 0 ACCEPT all -- tun0 * 1.2.3.4 0.0.0.0/0
|
||||||
`,
|
`,
|
||||||
table: chain{
|
table: chain{
|
||||||
name: "INPUT",
|
name: "INPUT",
|
||||||
@@ -111,6 +112,17 @@ num pkts bytes target prot opt in out source destinati
|
|||||||
source: netip.MustParsePrefix("1.2.3.4/32"),
|
source: netip.MustParsePrefix("1.2.3.4/32"),
|
||||||
destination: netip.MustParsePrefix("0.0.0.0/0"),
|
destination: netip.MustParsePrefix("0.0.0.0/0"),
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
lineNumber: 5,
|
||||||
|
packets: 0,
|
||||||
|
bytes: 0,
|
||||||
|
target: "ACCEPT",
|
||||||
|
protocol: "",
|
||||||
|
inputInterface: "tun0",
|
||||||
|
outputInterface: "*",
|
||||||
|
source: netip.MustParsePrefix("1.2.3.4/32"),
|
||||||
|
destination: netip.MustParsePrefix("0.0.0.0/0"),
|
||||||
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -25,6 +25,7 @@ type Checker struct {
|
|||||||
configMutex sync.Mutex
|
configMutex sync.Mutex
|
||||||
|
|
||||||
icmpNotPermitted bool
|
icmpNotPermitted bool
|
||||||
|
smallCheckName string
|
||||||
|
|
||||||
// Internal periodic service signals
|
// Internal periodic service signals
|
||||||
stop context.CancelFunc
|
stop context.CancelFunc
|
||||||
@@ -58,8 +59,9 @@ func (c *Checker) SetConfig(tlsDialAddr string, icmpTarget netip.Addr) {
|
|||||||
// and, on success, starts the periodic checks in a separate goroutine:
|
// and, on success, starts the periodic checks in a separate goroutine:
|
||||||
// - a "small" ICMP echo check every 15 seconds
|
// - a "small" ICMP echo check every 15 seconds
|
||||||
// - a "full" TCP+TLS check every 5 minutes
|
// - a "full" TCP+TLS check every 5 minutes
|
||||||
// It returns a channel `runError` that receives an error if one of the periodic checks fail.
|
// It returns a channel `runError` that receives an error (nil or not) when a periodic check is performed.
|
||||||
// It returns an error if the initial TCP+TLS check fails.
|
// It returns an error if the initial TCP+TLS check fails.
|
||||||
|
// The Checker has to be ultimately stopped by calling [Checker.Stop].
|
||||||
func (c *Checker) Start(ctx context.Context) (runError <-chan error, err error) {
|
func (c *Checker) Start(ctx context.Context) (runError <-chan error, err error) {
|
||||||
if c.tlsDialAddr == "" || c.icmpTarget.IsUnspecified() {
|
if c.tlsDialAddr == "" || c.icmpTarget.IsUnspecified() {
|
||||||
panic("call Checker.SetConfig with non empty values before Checker.Start")
|
panic("call Checker.SetConfig with non empty values before Checker.Start")
|
||||||
@@ -81,7 +83,8 @@ func (c *Checker) Start(ctx context.Context) (runError <-chan error, err error)
|
|||||||
c.stop = cancel
|
c.stop = cancel
|
||||||
done := make(chan struct{})
|
done := make(chan struct{})
|
||||||
c.done = done
|
c.done = done
|
||||||
const smallCheckPeriod = 15 * time.Second
|
c.smallCheckName = "ICMP echo"
|
||||||
|
const smallCheckPeriod = time.Minute
|
||||||
smallCheckTimer := time.NewTimer(smallCheckPeriod)
|
smallCheckTimer := time.NewTimer(smallCheckPeriod)
|
||||||
const fullCheckPeriod = 5 * time.Minute
|
const fullCheckPeriod = 5 * time.Minute
|
||||||
fullCheckTimer := time.NewTimer(fullCheckPeriod)
|
fullCheckTimer := time.NewTimer(fullCheckPeriod)
|
||||||
@@ -99,16 +102,16 @@ func (c *Checker) Start(ctx context.Context) (runError <-chan error, err error)
|
|||||||
case <-smallCheckTimer.C:
|
case <-smallCheckTimer.C:
|
||||||
err := c.smallPeriodicCheck(ctx)
|
err := c.smallPeriodicCheck(ctx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
runErrorCh <- fmt.Errorf("periodic small check: %w", err)
|
err = fmt.Errorf("small periodic check: %w", err)
|
||||||
return
|
|
||||||
}
|
}
|
||||||
|
runErrorCh <- err
|
||||||
smallCheckTimer.Reset(smallCheckPeriod)
|
smallCheckTimer.Reset(smallCheckPeriod)
|
||||||
case <-fullCheckTimer.C:
|
case <-fullCheckTimer.C:
|
||||||
err := c.fullPeriodicCheck(ctx)
|
err := c.fullPeriodicCheck(ctx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
runErrorCh <- fmt.Errorf("periodic full check: %w", err)
|
err = fmt.Errorf("full periodic check: %w", err)
|
||||||
return
|
|
||||||
}
|
}
|
||||||
|
runErrorCh <- err
|
||||||
fullCheckTimer.Reset(fullCheckPeriod)
|
fullCheckTimer.Reset(fullCheckPeriod)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -129,8 +132,8 @@ func (c *Checker) smallPeriodicCheck(ctx context.Context) error {
|
|||||||
ip := c.icmpTarget
|
ip := c.icmpTarget
|
||||||
c.configMutex.Unlock()
|
c.configMutex.Unlock()
|
||||||
const maxTries = 3
|
const maxTries = 3
|
||||||
const timeout = 3 * time.Second
|
const timeout = 10 * time.Second
|
||||||
const extraTryTime = time.Second // 1s added for each subsequent retry
|
const extraTryTime = 10 * time.Second // 10s added for each subsequent retry
|
||||||
check := func(ctx context.Context) error {
|
check := func(ctx context.Context) error {
|
||||||
if c.icmpNotPermitted {
|
if c.icmpNotPermitted {
|
||||||
return c.dnsClient.Check(ctx)
|
return c.dnsClient.Check(ctx)
|
||||||
@@ -138,20 +141,21 @@ func (c *Checker) smallPeriodicCheck(ctx context.Context) error {
|
|||||||
err := c.echoer.Echo(ctx, ip)
|
err := c.echoer.Echo(ctx, ip)
|
||||||
if errors.Is(err, icmp.ErrNotPermitted) {
|
if errors.Is(err, icmp.ErrNotPermitted) {
|
||||||
c.icmpNotPermitted = true
|
c.icmpNotPermitted = true
|
||||||
c.logger.Warnf("%s; permanently falling back to plaintext DNS checks.", err)
|
c.smallCheckName = "plain DNS over UDP"
|
||||||
|
c.logger.Infof("%s; permanently falling back to %s checks.", c.smallCheckName, err)
|
||||||
return c.dnsClient.Check(ctx)
|
return c.dnsClient.Check(ctx)
|
||||||
}
|
}
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
return withRetries(ctx, maxTries, timeout, extraTryTime, c.logger, "ICMP echo", check)
|
return withRetries(ctx, maxTries, timeout, extraTryTime, c.logger, c.smallCheckName, check)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Checker) fullPeriodicCheck(ctx context.Context) error {
|
func (c *Checker) fullPeriodicCheck(ctx context.Context) error {
|
||||||
const maxTries = 2
|
const maxTries = 2
|
||||||
// 10s timeout in case the connection is under stress
|
// 20s timeout in case the connection is under stress
|
||||||
// See https://github.com/qdm12/gluetun/issues/2270
|
// See https://github.com/qdm12/gluetun/issues/2270
|
||||||
const timeout = 10 * time.Second
|
const timeout = 20 * time.Second
|
||||||
const extraTryTime = 3 * time.Second // 3s added for each subsequent retry
|
const extraTryTime = 10 * time.Second // 10s added for each subsequent retry
|
||||||
check := func(ctx context.Context) error {
|
check := func(ctx context.Context) error {
|
||||||
return tcpTLSCheck(ctx, c.dialer, c.tlsDialAddr)
|
return tcpTLSCheck(ctx, c.dialer, c.tlsDialAddr)
|
||||||
}
|
}
|
||||||
@@ -215,9 +219,10 @@ func makeAddressToDial(address string) (addressToDial string, err error) {
|
|||||||
var ErrAllCheckTriesFailed = errors.New("all check tries failed")
|
var ErrAllCheckTriesFailed = errors.New("all check tries failed")
|
||||||
|
|
||||||
func withRetries(ctx context.Context, maxTries uint, tryTimeout, extraTryTime time.Duration,
|
func withRetries(ctx context.Context, maxTries uint, tryTimeout, extraTryTime time.Duration,
|
||||||
warner Logger, checkName string, check func(ctx context.Context) error,
|
logger Logger, checkName string, check func(ctx context.Context) error,
|
||||||
) error {
|
) error {
|
||||||
try := uint(0)
|
try := uint(0)
|
||||||
|
var errs []error
|
||||||
for {
|
for {
|
||||||
timeout := tryTimeout + time.Duration(try)*extraTryTime //nolint:gosec
|
timeout := tryTimeout + time.Duration(try)*extraTryTime //nolint:gosec
|
||||||
checkCtx, cancel := context.WithTimeout(ctx, timeout)
|
checkCtx, cancel := context.WithTimeout(ctx, timeout)
|
||||||
@@ -227,13 +232,19 @@ func withRetries(ctx context.Context, maxTries uint, tryTimeout, extraTryTime ti
|
|||||||
case err == nil:
|
case err == nil:
|
||||||
return nil
|
return nil
|
||||||
case ctx.Err() != nil:
|
case ctx.Err() != nil:
|
||||||
return fmt.Errorf("%s context error: %w", checkName, ctx.Err())
|
return fmt.Errorf("%s: %w", checkName, ctx.Err())
|
||||||
default:
|
|
||||||
warner.Warnf("%s attempt %d/%d failed: %v", checkName, try+1, maxTries, err)
|
|
||||||
try++
|
|
||||||
if try == maxTries {
|
|
||||||
return fmt.Errorf("%w: %s: after %d attempts", ErrAllCheckTriesFailed, checkName, maxTries)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
logger.Debugf("%s attempt %d/%d failed: %s", checkName, try+1, maxTries, err)
|
||||||
|
errs = append(errs, err)
|
||||||
|
try++
|
||||||
|
if try < maxTries {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
errStrings := make([]string, len(errs))
|
||||||
|
for i, err := range errs {
|
||||||
|
errStrings[i] = fmt.Sprintf("attempt %d: %s", i+1, err.Error())
|
||||||
|
}
|
||||||
|
return fmt.Errorf("%w: after %d %s attempts (%s)",
|
||||||
|
ErrAllCheckTriesFailed, maxTries, checkName, strings.Join(errStrings, "; "))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -31,7 +31,7 @@ func Test_Checker_fullcheck(t *testing.T) {
|
|||||||
err := checker.fullPeriodicCheck(canceledCtx)
|
err := checker.fullPeriodicCheck(canceledCtx)
|
||||||
|
|
||||||
require.Error(t, err)
|
require.Error(t, err)
|
||||||
assert.EqualError(t, err, "TCP+TLS dial context error: context canceled")
|
assert.EqualError(t, err, "TCP+TLS dial: context canceled")
|
||||||
})
|
})
|
||||||
|
|
||||||
t.Run("dial localhost:0", func(t *testing.T) {
|
t.Run("dial localhost:0", func(t *testing.T) {
|
||||||
|
|||||||
@@ -5,33 +5,60 @@ import (
|
|||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"net"
|
"net"
|
||||||
|
"net/netip"
|
||||||
|
|
||||||
|
"github.com/qdm12/dns/v2/pkg/provider"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Client is a simple plaintext UDP DNS client, to be used for healthchecks.
|
// Client is a simple plaintext UDP DNS client, to be used for healthchecks.
|
||||||
// Note the client connects to a DNS server only over UDP on port 53,
|
// Note the client connects to a DNS server only over UDP on port 53,
|
||||||
// because we don't want to use DoT or DoH and impact the TCP connections
|
// because we don't want to use DoT or DoH and impact the TCP connections
|
||||||
// when running a healthcheck.
|
// when running a healthcheck.
|
||||||
type Client struct{}
|
type Client struct {
|
||||||
|
serverAddrs []netip.AddrPort
|
||||||
|
dnsIPIndex int
|
||||||
|
}
|
||||||
|
|
||||||
func New() *Client {
|
func New() *Client {
|
||||||
return &Client{}
|
return &Client{
|
||||||
|
serverAddrs: concatAddrPorts([][]netip.AddrPort{
|
||||||
|
provider.Cloudflare().Plain.IPv4,
|
||||||
|
provider.Google().Plain.IPv4,
|
||||||
|
provider.Quad9().Plain.IPv4,
|
||||||
|
provider.OpenDNS().Plain.IPv4,
|
||||||
|
provider.LibreDNS().Plain.IPv4,
|
||||||
|
provider.Quadrant().Plain.IPv4,
|
||||||
|
provider.CiraProtected().Plain.IPv4,
|
||||||
|
}),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func concatAddrPorts(addrs [][]netip.AddrPort) []netip.AddrPort {
|
||||||
|
var result []netip.AddrPort
|
||||||
|
for _, addrList := range addrs {
|
||||||
|
result = append(result, addrList...)
|
||||||
|
}
|
||||||
|
return result
|
||||||
}
|
}
|
||||||
|
|
||||||
var ErrLookupNoIPs = errors.New("no IPs found from DNS lookup")
|
var ErrLookupNoIPs = errors.New("no IPs found from DNS lookup")
|
||||||
|
|
||||||
func (c *Client) Check(ctx context.Context) error {
|
func (c *Client) Check(ctx context.Context) error {
|
||||||
|
dnsAddr := c.serverAddrs[c.dnsIPIndex].Addr()
|
||||||
resolver := &net.Resolver{
|
resolver := &net.Resolver{
|
||||||
PreferGo: true,
|
PreferGo: true,
|
||||||
Dial: func(ctx context.Context, _, _ string) (net.Conn, error) {
|
Dial: func(ctx context.Context, _, _ string) (net.Conn, error) {
|
||||||
dialer := net.Dialer{}
|
dialer := net.Dialer{}
|
||||||
return dialer.DialContext(ctx, "udp", "1.1.1.1:53")
|
return dialer.DialContext(ctx, "udp", dnsAddr.String())
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
ips, err := resolver.LookupIP(ctx, "ip", "github.com")
|
ips, err := resolver.LookupIP(ctx, "ip", "github.com")
|
||||||
switch {
|
switch {
|
||||||
case err != nil:
|
case err != nil:
|
||||||
|
c.dnsIPIndex = (c.dnsIPIndex + 1) % len(c.serverAddrs)
|
||||||
return err
|
return err
|
||||||
case len(ips) == 0:
|
case len(ips) == 0:
|
||||||
|
c.dnsIPIndex = (c.dnsIPIndex + 1) % len(c.serverAddrs)
|
||||||
return fmt.Errorf("%w", ErrLookupNoIPs)
|
return fmt.Errorf("%w", ErrLookupNoIPs)
|
||||||
default:
|
default:
|
||||||
return nil
|
return nil
|
||||||
|
|||||||
@@ -153,7 +153,7 @@ func receiveEchoReply(conn net.PacketConn, id int, buffer []byte, ipVersion stri
|
|||||||
// Note we need to read the whole packet in one call to ReadFrom, so the buffer
|
// Note we need to read the whole packet in one call to ReadFrom, so the buffer
|
||||||
// must be large enough to read the entire reply packet. See:
|
// must be large enough to read the entire reply packet. See:
|
||||||
// https://groups.google.com/g/golang-nuts/c/5dy2Q4nPs08/m/KmuSQAGEtG4J
|
// https://groups.google.com/g/golang-nuts/c/5dy2Q4nPs08/m/KmuSQAGEtG4J
|
||||||
bytesRead, _, err := conn.ReadFrom(buffer)
|
bytesRead, returnAddr, err := conn.ReadFrom(buffer)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("reading from ICMP connection: %w", err)
|
return nil, fmt.Errorf("reading from ICMP connection: %w", err)
|
||||||
}
|
}
|
||||||
@@ -168,23 +168,26 @@ func receiveEchoReply(conn net.PacketConn, id int, buffer []byte, ipVersion stri
|
|||||||
switch body := message.Body.(type) {
|
switch body := message.Body.(type) {
|
||||||
case *icmp.Echo:
|
case *icmp.Echo:
|
||||||
if id != body.ID {
|
if id != body.ID {
|
||||||
logger.Warnf("ignoring ICMP echo reply mismatching expected id %d (id: %d, type: %d, code: %d, length: %d)",
|
logger.Warnf("ignoring ICMP echo reply mismatching expected id %d "+
|
||||||
id, body.ID, message.Type, message.Code, len(packetBytes))
|
"(id: %d, type: %d, code: %d, length: %d, return address %s)",
|
||||||
|
id, body.ID, message.Type, message.Code, len(packetBytes), returnAddr)
|
||||||
continue // not the ID we are looking for
|
continue // not the ID we are looking for
|
||||||
}
|
}
|
||||||
return body.Data, nil
|
return body.Data, nil
|
||||||
case *icmp.DstUnreach:
|
case *icmp.DstUnreach:
|
||||||
logger.Debugf("ignoring ICMP destination unreachable message (type: 3, code: %d, expected-id %d)", message.Code, id)
|
logger.Debugf("ignoring ICMP destination unreachable message (type: 3, code: %d, return address %s, expected-id %d)",
|
||||||
|
message.Code, returnAddr, id)
|
||||||
// See https://github.com/qdm12/gluetun/pull/2923#issuecomment-3377532249
|
// See https://github.com/qdm12/gluetun/pull/2923#issuecomment-3377532249
|
||||||
// on why we ignore this message. If it is actually unreachable, the timeout on waiting for
|
// on why we ignore this message. If it is actually unreachable, the timeout on waiting for
|
||||||
// the echo reply will do instead of returning an error error.
|
// the echo reply will do instead of returning an error error.
|
||||||
continue
|
continue
|
||||||
case *icmp.TimeExceeded:
|
case *icmp.TimeExceeded:
|
||||||
logger.Debugf("ignoring ICMP time exceeded message (type: 11, code: %d, expected-id %d)", message.Code, id)
|
logger.Debugf("ignoring ICMP time exceeded message (type: 11, code: %d, return address %s, expected-id %d)",
|
||||||
|
message.Code, returnAddr, id)
|
||||||
continue
|
continue
|
||||||
default:
|
default:
|
||||||
return nil, fmt.Errorf("%w: %T (type %d, code %d, expected-id %d)",
|
return nil, fmt.Errorf("%w: %T (type %d, code %d, return address %s, expected-id %d)",
|
||||||
ErrICMPBodyUnsupported, body, message.Type, message.Code, id)
|
ErrICMPBodyUnsupported, body, message.Type, message.Code, returnAddr, id)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ package healthcheck
|
|||||||
type Logger interface {
|
type Logger interface {
|
||||||
Debugf(format string, args ...any)
|
Debugf(format string, args ...any)
|
||||||
Info(s string)
|
Info(s string)
|
||||||
|
Infof(format string, args ...any)
|
||||||
Warnf(format string, args ...any)
|
Warnf(format string, args ...any)
|
||||||
Error(s string)
|
Error(s string)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,7 +3,6 @@ package service
|
|||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
"os"
|
|
||||||
"time"
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -61,10 +60,10 @@ func (s *Service) cleanup() (err error) {
|
|||||||
s.ports = nil
|
s.ports = nil
|
||||||
|
|
||||||
filepath := s.settings.Filepath
|
filepath := s.settings.Filepath
|
||||||
s.logger.Info("removing port file " + filepath)
|
s.logger.Info("clearing port file " + filepath)
|
||||||
err = os.Remove(filepath)
|
err = s.writePortForwardedFile(nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("removing port file: %w", err)
|
return fmt.Errorf("clearing port file: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
|
|||||||
@@ -15,12 +15,12 @@ type Provider struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func New(storage common.Storage, randSource rand.Source,
|
func New(storage common.Storage, randSource rand.Source,
|
||||||
parallelResolver common.ParallelResolver,
|
updaterWarner common.Warner, parallelResolver common.ParallelResolver,
|
||||||
) *Provider {
|
) *Provider {
|
||||||
return &Provider{
|
return &Provider{
|
||||||
storage: storage,
|
storage: storage,
|
||||||
randSource: randSource,
|
randSource: randSource,
|
||||||
Fetcher: updater.New(parallelResolver),
|
Fetcher: updater.New(parallelResolver, updaterWarner),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -16,7 +16,10 @@ func (u *Updater) FetchServers(ctx context.Context, minServers int) (
|
|||||||
|
|
||||||
possibleHosts := possibleServers.hostsSlice()
|
possibleHosts := possibleServers.hostsSlice()
|
||||||
resolveSettings := parallelResolverSettings(possibleHosts)
|
resolveSettings := parallelResolverSettings(possibleHosts)
|
||||||
hostToIPs, _, err := u.parallelResolver.Resolve(ctx, resolveSettings)
|
hostToIPs, warnings, err := u.parallelResolver.Resolve(ctx, resolveSettings)
|
||||||
|
for _, warning := range warnings {
|
||||||
|
u.warner.Warn(warning)
|
||||||
|
}
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,10 +6,12 @@ import (
|
|||||||
|
|
||||||
type Updater struct {
|
type Updater struct {
|
||||||
parallelResolver common.ParallelResolver
|
parallelResolver common.ParallelResolver
|
||||||
|
warner common.Warner
|
||||||
}
|
}
|
||||||
|
|
||||||
func New(parallelResolver common.ParallelResolver) *Updater {
|
func New(parallelResolver common.ParallelResolver, warner common.Warner) *Updater {
|
||||||
return &Updater{
|
return &Updater{
|
||||||
parallelResolver: parallelResolver,
|
parallelResolver: parallelResolver,
|
||||||
|
warner: warner,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -62,7 +62,7 @@ func NewProviders(storage Storage, timeNow func() time.Time,
|
|||||||
providerNameToProvider := map[string]Provider{
|
providerNameToProvider := map[string]Provider{
|
||||||
providers.Airvpn: airvpn.New(storage, randSource, client),
|
providers.Airvpn: airvpn.New(storage, randSource, client),
|
||||||
providers.Custom: custom.New(extractor),
|
providers.Custom: custom.New(extractor),
|
||||||
providers.Cyberghost: cyberghost.New(storage, randSource, parallelResolver),
|
providers.Cyberghost: cyberghost.New(storage, randSource, updaterWarner, parallelResolver),
|
||||||
providers.Expressvpn: expressvpn.New(storage, randSource, unzipper, updaterWarner, parallelResolver),
|
providers.Expressvpn: expressvpn.New(storage, randSource, unzipper, updaterWarner, parallelResolver),
|
||||||
providers.Fastestvpn: fastestvpn.New(storage, randSource, client, updaterWarner, parallelResolver),
|
providers.Fastestvpn: fastestvpn.New(storage, randSource, client, updaterWarner, parallelResolver),
|
||||||
providers.Giganews: giganews.New(storage, randSource, unzipper, updaterWarner, parallelResolver),
|
providers.Giganews: giganews.New(storage, randSource, unzipper, updaterWarner, parallelResolver),
|
||||||
|
|||||||
@@ -114,6 +114,11 @@ func (l *Loop) run(runCtx context.Context, runDone chan<- struct{},
|
|||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if !*l.settings.Enabled {
|
||||||
|
singleRunResult <- nil
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
result, err := l.fetcher.FetchInfo(singleRunCtx, netip.Addr{})
|
result, err := l.fetcher.FetchInfo(singleRunCtx, netip.Addr{})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
err = fmt.Errorf("fetching information: %w", err)
|
err = fmt.Errorf("fetching information: %w", err)
|
||||||
|
|||||||
@@ -15,6 +15,7 @@ type infoWarner interface {
|
|||||||
|
|
||||||
type infoer interface {
|
type infoer interface {
|
||||||
Info(s string)
|
Info(s string)
|
||||||
|
Infof(format string, args ...any)
|
||||||
}
|
}
|
||||||
|
|
||||||
type warner interface {
|
type warner interface {
|
||||||
|
|||||||
@@ -13,9 +13,6 @@ import (
|
|||||||
func Read(filepath string) (settings Settings, err error) {
|
func Read(filepath string) (settings Settings, err error) {
|
||||||
file, err := os.Open(filepath)
|
file, err := os.Open(filepath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if errors.Is(err, os.ErrNotExist) {
|
|
||||||
return Settings{}, nil
|
|
||||||
}
|
|
||||||
return settings, fmt.Errorf("opening file: %w", err)
|
return settings, fmt.Errorf("opening file: %w", err)
|
||||||
}
|
}
|
||||||
decoder := toml.NewDecoder(file)
|
decoder := toml.NewDecoder(file)
|
||||||
|
|||||||
@@ -2,7 +2,9 @@ package server
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"os"
|
||||||
|
|
||||||
"github.com/qdm12/gluetun/internal/httpserver"
|
"github.com/qdm12/gluetun/internal/httpserver"
|
||||||
"github.com/qdm12/gluetun/internal/models"
|
"github.com/qdm12/gluetun/internal/models"
|
||||||
@@ -17,8 +19,12 @@ func New(ctx context.Context, address string, logEnabled bool, logger Logger,
|
|||||||
server *httpserver.Server, err error,
|
server *httpserver.Server, err error,
|
||||||
) {
|
) {
|
||||||
authSettings, err := auth.Read(authConfigPath)
|
authSettings, err := auth.Read(authConfigPath)
|
||||||
if err != nil {
|
switch {
|
||||||
|
case errors.Is(err, os.ErrNotExist): // no auth file present
|
||||||
|
case err != nil:
|
||||||
return nil, fmt.Errorf("reading auth settings: %w", err)
|
return nil, fmt.Errorf("reading auth settings: %w", err)
|
||||||
|
default:
|
||||||
|
logger.Infof("read %d roles from authentication file", len(authSettings.Roles))
|
||||||
}
|
}
|
||||||
authSettings.SetDefaults()
|
authSettings.SetDefaults()
|
||||||
err = authSettings.Validate()
|
err = authSettings.Validate()
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -57,14 +57,14 @@ func (l *Loop) Run(ctx context.Context, done chan<- struct{}) {
|
|||||||
password: settings.Provider.PortForwarding.Password,
|
password: settings.Provider.PortForwarding.Password,
|
||||||
}
|
}
|
||||||
|
|
||||||
openvpnCtx, openvpnCancel := context.WithCancel(context.Background())
|
vpnCtx, vpnCancel := context.WithCancel(context.Background())
|
||||||
waitError := make(chan error)
|
waitError := make(chan error)
|
||||||
tunnelReady := make(chan struct{})
|
tunnelReady := make(chan struct{})
|
||||||
|
|
||||||
go vpnRunner.Run(openvpnCtx, waitError, tunnelReady)
|
go vpnRunner.Run(vpnCtx, waitError, tunnelReady)
|
||||||
|
|
||||||
if err := l.waitForError(ctx, waitError); err != nil {
|
if err := l.waitForError(ctx, waitError); err != nil {
|
||||||
openvpnCancel()
|
vpnCancel()
|
||||||
l.crashed(ctx, err)
|
l.crashed(ctx, err)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
@@ -76,10 +76,10 @@ func (l *Loop) Run(ctx context.Context, done chan<- struct{}) {
|
|||||||
for stayHere {
|
for stayHere {
|
||||||
select {
|
select {
|
||||||
case <-tunnelReady:
|
case <-tunnelReady:
|
||||||
go l.onTunnelUp(openvpnCtx, ctx, tunnelUpData)
|
go l.onTunnelUp(vpnCtx, ctx, tunnelUpData)
|
||||||
case <-ctx.Done():
|
case <-ctx.Done():
|
||||||
l.cleanup()
|
l.cleanup()
|
||||||
openvpnCancel()
|
vpnCancel()
|
||||||
<-waitError
|
<-waitError
|
||||||
close(waitError)
|
close(waitError)
|
||||||
return
|
return
|
||||||
@@ -87,7 +87,7 @@ func (l *Loop) Run(ctx context.Context, done chan<- struct{}) {
|
|||||||
l.userTrigger = true
|
l.userTrigger = true
|
||||||
l.logger.Info("stopping")
|
l.logger.Info("stopping")
|
||||||
l.cleanup()
|
l.cleanup()
|
||||||
openvpnCancel()
|
vpnCancel()
|
||||||
<-waitError
|
<-waitError
|
||||||
// do not close waitError or the waitError
|
// do not close waitError or the waitError
|
||||||
// select case will trigger
|
// select case will trigger
|
||||||
@@ -100,7 +100,7 @@ func (l *Loop) Run(ctx context.Context, done chan<- struct{}) {
|
|||||||
l.statusManager.Lock() // prevent SetStatus from running in parallel
|
l.statusManager.Lock() // prevent SetStatus from running in parallel
|
||||||
|
|
||||||
l.cleanup()
|
l.cleanup()
|
||||||
openvpnCancel()
|
vpnCancel()
|
||||||
l.statusManager.SetStatus(constants.Crashed)
|
l.statusManager.SetStatus(constants.Crashed)
|
||||||
l.logAndWait(ctx, err)
|
l.logAndWait(ctx, err)
|
||||||
stayHere = false
|
stayHere = false
|
||||||
@@ -108,6 +108,6 @@ func (l *Loop) Run(ctx context.Context, done chan<- struct{}) {
|
|||||||
l.statusManager.Unlock()
|
l.statusManager.Unlock()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
openvpnCancel()
|
vpnCancel()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -53,9 +53,6 @@ func (l *Loop) onTunnelUp(ctx, loopCtx context.Context, data tunnelUpData) {
|
|||||||
l.restartVPN(loopCtx, err)
|
l.restartVPN(loopCtx, err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
defer func() {
|
|
||||||
_ = l.healthChecker.Stop()
|
|
||||||
}()
|
|
||||||
|
|
||||||
mtuLogger := l.logger.New(log.SetComponent("MTU discovery"))
|
mtuLogger := l.logger.New(log.SetComponent("MTU discovery"))
|
||||||
err = updateToMaxMTU(ctx, data.vpnIntf, data.vpnType,
|
err = updateToMaxMTU(ctx, data.vpnIntf, data.vpnType,
|
||||||
@@ -64,7 +61,7 @@ func (l *Loop) onTunnelUp(ctx, loopCtx context.Context, data tunnelUpData) {
|
|||||||
mtuLogger.Error(err.Error())
|
mtuLogger.Error(err.Error())
|
||||||
}
|
}
|
||||||
|
|
||||||
if *l.dnsLooper.GetSettings().DoT.Enabled {
|
if *l.dnsLooper.GetSettings().ServerEnabled {
|
||||||
_, _ = l.dnsLooper.ApplyStatus(ctx, constants.Running)
|
_, _ = l.dnsLooper.ApplyStatus(ctx, constants.Running)
|
||||||
} else {
|
} else {
|
||||||
err := check.WaitForDNS(ctx, check.Settings{})
|
err := check.WaitForDNS(ctx, check.Settings{})
|
||||||
@@ -93,13 +90,33 @@ func (l *Loop) onTunnelUp(ctx, loopCtx context.Context, data tunnelUpData) {
|
|||||||
l.logger.Error(err.Error())
|
l.logger.Error(err.Error())
|
||||||
}
|
}
|
||||||
|
|
||||||
select {
|
l.collectHealthErrors(ctx, loopCtx, healthErrCh)
|
||||||
case <-ctx.Done():
|
}
|
||||||
case healthErr := <-healthErrCh:
|
|
||||||
l.healthServer.SetError(healthErr)
|
func (l *Loop) collectHealthErrors(ctx, loopCtx context.Context, healthErrCh <-chan error) {
|
||||||
// Note this restart call must be done in a separate goroutine
|
var previousHealthErr error
|
||||||
// from the VPN loop goroutine.
|
for {
|
||||||
l.restartVPN(loopCtx, healthErr)
|
select {
|
||||||
|
case <-ctx.Done():
|
||||||
|
_ = l.healthChecker.Stop()
|
||||||
|
return
|
||||||
|
case healthErr := <-healthErrCh:
|
||||||
|
l.healthServer.SetError(healthErr)
|
||||||
|
if healthErr != nil {
|
||||||
|
if *l.healthSettings.RestartVPN {
|
||||||
|
// Note this restart call must be done in a separate goroutine
|
||||||
|
// from the VPN loop goroutine.
|
||||||
|
_ = l.healthChecker.Stop()
|
||||||
|
l.restartVPN(loopCtx, healthErr)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
l.logger.Warnf("healthcheck failed: %s", healthErr)
|
||||||
|
l.logger.Info("👉 See https://github.com/qdm12/gluetun-wiki/blob/main/faq/healthcheck.md")
|
||||||
|
} else if previousHealthErr != nil {
|
||||||
|
l.logger.Info("healthcheck passed successfully after previous failure(s)")
|
||||||
|
}
|
||||||
|
previousHealthErr = healthErr
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -32,9 +32,14 @@ func (w *Wireguard) addRoutes(link netlink.Link, destinations []netip.Prefix,
|
|||||||
func (w *Wireguard) addRoute(link netlink.Link, dst netip.Prefix,
|
func (w *Wireguard) addRoute(link netlink.Link, dst netip.Prefix,
|
||||||
firewallMark uint32,
|
firewallMark uint32,
|
||||||
) (err error) {
|
) (err error) {
|
||||||
|
family := netlink.FamilyV4
|
||||||
|
if dst.Addr().Is6() {
|
||||||
|
family = netlink.FamilyV6
|
||||||
|
}
|
||||||
route := netlink.Route{
|
route := netlink.Route{
|
||||||
LinkIndex: link.Index,
|
LinkIndex: link.Index,
|
||||||
Dst: dst,
|
Dst: dst,
|
||||||
|
Family: family,
|
||||||
Table: int(firewallMark),
|
Table: int(firewallMark),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -37,6 +37,7 @@ func Test_Wireguard_addRoute(t *testing.T) {
|
|||||||
expectedRoute: netlink.Route{
|
expectedRoute: netlink.Route{
|
||||||
LinkIndex: linkIndex,
|
LinkIndex: linkIndex,
|
||||||
Dst: ipPrefix,
|
Dst: ipPrefix,
|
||||||
|
Family: netlink.FamilyV4,
|
||||||
Table: firewallMark,
|
Table: firewallMark,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@@ -49,6 +50,7 @@ func Test_Wireguard_addRoute(t *testing.T) {
|
|||||||
expectedRoute: netlink.Route{
|
expectedRoute: netlink.Route{
|
||||||
LinkIndex: linkIndex,
|
LinkIndex: linkIndex,
|
||||||
Dst: ipPrefix,
|
Dst: ipPrefix,
|
||||||
|
Family: netlink.FamilyV4,
|
||||||
Table: firewallMark,
|
Table: firewallMark,
|
||||||
},
|
},
|
||||||
routeAddErr: errDummy,
|
routeAddErr: errDummy,
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
# Maintenance
|
# Maintenance
|
||||||
|
|
||||||
- Rename `UNBLOCK` to `DNS_HOSTNAMES_UNBLOCKED`
|
|
||||||
- Change `Run` methods to `Start`+`Stop`, returning channels rather than injecting them
|
- Change `Run` methods to `Start`+`Stop`, returning channels rather than injecting them
|
||||||
- Go 1.18
|
- Go 1.18
|
||||||
- gofumpt
|
- gofumpt
|
||||||
|
|||||||
Reference in New Issue
Block a user