mirror of
https://github.com/qdm12/gluetun.git
synced 2026-07-04 17:49:51 +02:00
Compare commits
6 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 7f22fb3276 | |||
| 6909a0c123 | |||
| 3e1f48932a | |||
| 50744852c5 | |||
| 09e52bc685 | |||
| 857fe425ec |
@@ -1,3 +1,2 @@
|
|||||||
FROM ghcr.io/qdm12/godevcontainer:v0.21-alpine
|
FROM ghcr.io/qdm12/godevcontainer:v0.21-alpine
|
||||||
RUN apk add wireguard-tools htop openssl tcpdump iptables
|
RUN apk add wireguard-tools htop openssl
|
||||||
RUN apk add nodejs npm && npm install markdownlint-cli2 --global && apk del npm
|
|
||||||
|
|||||||
@@ -23,8 +23,6 @@
|
|||||||
color: "959a9c"
|
color: "959a9c"
|
||||||
- name: "Closed: ☠️ cannot be done"
|
- name: "Closed: ☠️ cannot be done"
|
||||||
color: "959a9c"
|
color: "959a9c"
|
||||||
- name: "Closed: 🤖🍺 drunk AI"
|
|
||||||
color: "959a9c"
|
|
||||||
|
|
||||||
- name: "Priority: 🚨 Urgent"
|
- name: "Priority: 🚨 Urgent"
|
||||||
color: "03adfc"
|
color: "03adfc"
|
||||||
@@ -128,8 +126,6 @@
|
|||||||
color: "ffc7ea"
|
color: "ffc7ea"
|
||||||
- name: "Category: Firewall ⛓️"
|
- name: "Category: Firewall ⛓️"
|
||||||
color: "ffc7ea"
|
color: "ffc7ea"
|
||||||
- name: "Category: MTU discovery 🔦"
|
|
||||||
color: "ffc7ea"
|
|
||||||
- name: "Category: Routing 🛤️"
|
- name: "Category: Routing 🛤️"
|
||||||
color: "ffc7ea"
|
color: "ffc7ea"
|
||||||
- name: "Category: IPv6 🛰️"
|
- name: "Category: IPv6 🛰️"
|
||||||
|
|||||||
@@ -45,7 +45,6 @@ jobs:
|
|||||||
level: error
|
level: error
|
||||||
exclude: |
|
exclude: |
|
||||||
./internal/storage/servers.json
|
./internal/storage/servers.json
|
||||||
./.golangci.yml
|
|
||||||
*.md
|
*.md
|
||||||
|
|
||||||
- name: Linting
|
- name: Linting
|
||||||
@@ -60,13 +59,10 @@ jobs:
|
|||||||
- name: Run tests in test container
|
- name: Run tests in test container
|
||||||
run: |
|
run: |
|
||||||
touch coverage.txt
|
touch coverage.txt
|
||||||
docker run --rm --cap-add=NET_ADMIN --device /dev/net/tun \
|
docker run --rm --device /dev/net/tun \
|
||||||
-v "$(pwd)/coverage.txt:/tmp/gobuild/coverage.txt" \
|
-v "$(pwd)/coverage.txt:/tmp/gobuild/coverage.txt" \
|
||||||
test-container
|
test-container
|
||||||
|
|
||||||
- name: Verify dev cross platform compatibility
|
|
||||||
run: docker build --target xcompile .
|
|
||||||
|
|
||||||
- name: Build final image
|
- name: Build final image
|
||||||
run: docker build -t final-image .
|
run: docker build -t final-image .
|
||||||
|
|
||||||
@@ -138,7 +134,7 @@ jobs:
|
|||||||
# https://github.com/docker/metadata-action
|
# https://github.com/docker/metadata-action
|
||||||
- name: Extract Docker metadata
|
- name: Extract Docker metadata
|
||||||
id: meta
|
id: meta
|
||||||
uses: docker/metadata-action@v6
|
uses: docker/metadata-action@v5
|
||||||
with:
|
with:
|
||||||
flavor: |
|
flavor: |
|
||||||
latest=${{ github.ref == format('refs/heads/{0}', github.event.repository.default_branch) }}
|
latest=${{ github.ref == format('refs/heads/{0}', github.event.repository.default_branch) }}
|
||||||
@@ -153,15 +149,15 @@ jobs:
|
|||||||
type=semver,pattern=v{{major}},enable=${{ !startsWith(github.ref, 'refs/tags/v0.') }}
|
type=semver,pattern=v{{major}},enable=${{ !startsWith(github.ref, 'refs/tags/v0.') }}
|
||||||
type=raw,value=latest,enable=${{ github.ref == format('refs/heads/{0}', github.event.repository.default_branch) }}
|
type=raw,value=latest,enable=${{ github.ref == format('refs/heads/{0}', github.event.repository.default_branch) }}
|
||||||
|
|
||||||
- uses: docker/setup-qemu-action@v4
|
- uses: docker/setup-qemu-action@v3
|
||||||
- uses: docker/setup-buildx-action@v4
|
- uses: docker/setup-buildx-action@v3
|
||||||
|
|
||||||
- uses: docker/login-action@v4
|
- uses: docker/login-action@v3
|
||||||
with:
|
with:
|
||||||
username: qmcgaw
|
username: qmcgaw
|
||||||
password: ${{ secrets.DOCKERHUB_PASSWORD }}
|
password: ${{ secrets.DOCKERHUB_PASSWORD }}
|
||||||
|
|
||||||
- uses: docker/login-action@v4
|
- uses: docker/login-action@v3
|
||||||
with:
|
with:
|
||||||
registry: ghcr.io
|
registry: ghcr.io
|
||||||
username: qdm12
|
username: qdm12
|
||||||
@@ -169,14 +165,10 @@ jobs:
|
|||||||
|
|
||||||
- name: Short commit
|
- name: Short commit
|
||||||
id: shortcommit
|
id: shortcommit
|
||||||
run: |
|
run: echo "::set-output name=value::$(git rev-parse --short HEAD)"
|
||||||
# Use the PR head SHA if it exists, otherwise fallback to GITHUB_SHA
|
|
||||||
FULL_SHA="${{ github.event.pull_request.head.sha || github.sha }}"
|
|
||||||
SHORT_SHA=
|
|
||||||
echo "value=$(echo $FULL_SHA | cut -c1-7)" >> $GITHUB_OUTPUT
|
|
||||||
|
|
||||||
- name: Build and push final image
|
- name: Build and push final image
|
||||||
uses: docker/build-push-action@v7
|
uses: docker/build-push-action@v6
|
||||||
with:
|
with:
|
||||||
platforms: linux/amd64,linux/386,linux/arm64,linux/arm/v6,linux/arm/v7,linux/ppc64le
|
platforms: linux/amd64,linux/386,linux/arm64,linux/arm/v6,linux/arm/v7,linux/ppc64le
|
||||||
labels: ${{ steps.meta.outputs.labels }}
|
labels: ${{ steps.meta.outputs.labels }}
|
||||||
|
|||||||
@@ -14,12 +14,8 @@ jobs:
|
|||||||
token: ${{ github.token }}
|
token: ${{ github.token }}
|
||||||
issue-number: ${{ github.event.issue.number }}
|
issue-number: ${{ github.event.issue.number }}
|
||||||
body: |
|
body: |
|
||||||
Closed issues are **NOT** monitored, so commenting here will likely NOT be seen.
|
Closed issues are **NOT** monitored, so commenting here is likely to be not seen.
|
||||||
If you think this is *still unresolved* and have **more information** to bring, please either
|
If you think this is *still unresolved* and have **more information** to bring, please create another issue.
|
||||||
re-open this issue or create another issue.
|
|
||||||
|
|
||||||
❤️😠 temporarily help the Gluetun community and fight the AI slop scam website `gluetun[dot]com` by setting `BORINGPOLL_GLUETUNCOM=on` on the latest image.
|
|
||||||
See [the option in the wiki for more information](https://github.com/qdm12/gluetun-wiki/blob/main/setup/options/others.md)
|
|
||||||
|
|
||||||
This is an automated comment setup because @qdm12 is the sole maintainer of this project
|
This is an automated comment setup because @qdm12 is the sole maintainer of this project
|
||||||
which became too popular to monitor closed issues.
|
which became too popular to monitor issues closed.
|
||||||
|
|||||||
@@ -12,6 +12,6 @@ jobs:
|
|||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v6
|
- uses: actions/checkout@v6
|
||||||
- uses: crazy-max/ghaction-github-labeler@v6
|
- uses: crazy-max/ghaction-github-labeler@v5
|
||||||
with:
|
with:
|
||||||
yaml-file: .github/labels.yml
|
yaml-file: .github/labels.yml
|
||||||
|
|||||||
@@ -20,7 +20,7 @@ jobs:
|
|||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v6
|
- uses: actions/checkout@v6
|
||||||
|
|
||||||
- uses: DavidAnson/markdownlint-cli2-action@v22
|
- uses: DavidAnson/markdownlint-cli2-action@v21
|
||||||
with:
|
with:
|
||||||
globs: "**.md"
|
globs: "**.md"
|
||||||
config: .markdownlint-cli2.jsonc
|
config: .markdownlint-cli2.jsonc
|
||||||
@@ -37,7 +37,7 @@ jobs:
|
|||||||
use-quiet-mode: yes
|
use-quiet-mode: yes
|
||||||
config-file: .github/workflows/configs/mlc-config.json
|
config-file: .github/workflows/configs/mlc-config.json
|
||||||
|
|
||||||
- uses: peter-evans/dockerhub-description@v5
|
- uses: peter-evans/dockerhub-description@v4
|
||||||
if: github.repository == 'qdm12/gluetun' && github.event_name == 'push'
|
if: github.repository == 'qdm12/gluetun' && github.event_name == 'push'
|
||||||
with:
|
with:
|
||||||
username: qmcgaw
|
username: qmcgaw
|
||||||
|
|||||||
@@ -1,98 +0,0 @@
|
|||||||
name: Update servers list
|
|
||||||
on:
|
|
||||||
workflow_dispatch:
|
|
||||||
inputs:
|
|
||||||
provider:
|
|
||||||
description: "VPN Provider to update"
|
|
||||||
required: true
|
|
||||||
default: "all"
|
|
||||||
type: choice
|
|
||||||
options:
|
|
||||||
- all
|
|
||||||
- airvpn
|
|
||||||
- cyberghost
|
|
||||||
- expressvpn
|
|
||||||
- fastestvpn
|
|
||||||
- giganews
|
|
||||||
- hidemyass
|
|
||||||
- ipvanish
|
|
||||||
- ivpn
|
|
||||||
- mullvad
|
|
||||||
- nordvpn
|
|
||||||
- perfect privacy
|
|
||||||
- privado
|
|
||||||
- private internet access
|
|
||||||
- privatevpn
|
|
||||||
- protonvpn
|
|
||||||
- purevpn
|
|
||||||
- slickvpn
|
|
||||||
- surfshark
|
|
||||||
- torguard
|
|
||||||
- vpnsecure
|
|
||||||
- vpn unlimited
|
|
||||||
- vyprvpn
|
|
||||||
- windscribe
|
|
||||||
schedule:
|
|
||||||
- cron: "11 3 1 */2 *" # Run at 03:11 on the 1st of every 2nd month
|
|
||||||
jobs:
|
|
||||||
update-servers-list:
|
|
||||||
if: github.repository == 'qdm12/gluetun'
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
permissions:
|
|
||||||
actions: read
|
|
||||||
contents: write
|
|
||||||
pull-requests: write
|
|
||||||
steps:
|
|
||||||
- uses: actions/checkout@v6
|
|
||||||
|
|
||||||
- uses: actions/setup-go@v6
|
|
||||||
with:
|
|
||||||
go-version-file: go.mod
|
|
||||||
|
|
||||||
- name: Update servers list
|
|
||||||
run: |
|
|
||||||
SELECTED_PROVIDER="${{ github.event.inputs.provider || 'all' }}"
|
|
||||||
|
|
||||||
if [ "$SELECTED_PROVIDER" = "all" ]; then
|
|
||||||
FLAGS="-all"
|
|
||||||
else
|
|
||||||
FLAGS="-providers $SELECTED_PROVIDER"
|
|
||||||
fi
|
|
||||||
|
|
||||||
go run ./cmd/gluetun/main.go update $FLAGS \
|
|
||||||
-maintainer \
|
|
||||||
-proton-email "${{ secrets.PROTON_EMAIL }}" \
|
|
||||||
-proton-password "${{ secrets.PROTON_PASSWORD }}"
|
|
||||||
|
|
||||||
- name: Check for changes
|
|
||||||
run: |
|
|
||||||
if git diff --exit-code internal/storage/servers.json >/dev/null; then
|
|
||||||
echo "Error: internal/storage/servers.json was not modified."
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
- name: Check no other file changes
|
|
||||||
run: |
|
|
||||||
if ! git diff --exit-code --quiet ':!internal/storage/servers.json'; then
|
|
||||||
echo "Error: Unexpected changes detected in files other than servers.json"
|
|
||||||
git status --short
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
- name: Create Pull Request
|
|
||||||
id: createpr
|
|
||||||
uses: peter-evans/create-pull-request@v8
|
|
||||||
with:
|
|
||||||
branch-suffix: timestamp
|
|
||||||
branch: bot/update-servers-list
|
|
||||||
base: master
|
|
||||||
delete-branch: true
|
|
||||||
title: "feat(providers/${{ github.event.inputs.provider || 'all' }}): servers data update"
|
|
||||||
body: |
|
|
||||||
This PR was automatically generated by the [Update servers list](${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}) workflow run.
|
|
||||||
|
|
||||||
# - name: Merge Pull Request
|
|
||||||
# env:
|
|
||||||
# GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
|
||||||
# run: |
|
|
||||||
# gh pr merge ${{ steps.createpr.outputs.pull-request-number }} --auto -m -d
|
|
||||||
@@ -1,2 +1 @@
|
|||||||
scratch.txt
|
scratch.txt
|
||||||
.DS_Store
|
|
||||||
|
|||||||
+1
-6
@@ -22,7 +22,6 @@ linters:
|
|||||||
- "^disabled$"
|
- "^disabled$"
|
||||||
# Firewall and routing strings
|
# Firewall and routing strings
|
||||||
- "^(ACCEPT|DROP)$"
|
- "^(ACCEPT|DROP)$"
|
||||||
- "^--append$"
|
|
||||||
- "^--delete$"
|
- "^--delete$"
|
||||||
- "^all$"
|
- "^all$"
|
||||||
- "^(tcp|udp)$"
|
- "^(tcp|udp)$"
|
||||||
@@ -48,7 +47,7 @@ linters:
|
|||||||
path: internal\/server\/.+\.go
|
path: internal\/server\/.+\.go
|
||||||
- linters:
|
- linters:
|
||||||
- ireturn
|
- ireturn
|
||||||
text: returns interface \(golang\.org\/x\/sys\/unix\.Sockaddr\)
|
text: returns interface \(github\.com\/vishvananda\/netlink\.Link\)
|
||||||
- linters:
|
- linters:
|
||||||
- ireturn
|
- ireturn
|
||||||
path: internal\/openvpn\/pkcs8\/descbc\.go
|
path: internal\/openvpn\/pkcs8\/descbc\.go
|
||||||
@@ -60,10 +59,6 @@ linters:
|
|||||||
- linters:
|
- linters:
|
||||||
- lll
|
- lll
|
||||||
source: "^// https://.+$"
|
source: "^// https://.+$"
|
||||||
- linters:
|
|
||||||
- mnd
|
|
||||||
source: "^ cleanups\\.Add.+$"
|
|
||||||
path: internal\/(wireguard|amneziawg)\/run\.go
|
|
||||||
- linters:
|
- linters:
|
||||||
- err113
|
- err113
|
||||||
- mnd
|
- mnd
|
||||||
|
|||||||
@@ -1,195 +0,0 @@
|
|||||||
# AGENTS
|
|
||||||
|
|
||||||
Guidance for coding agents working in this repository.
|
|
||||||
|
|
||||||
## Scope and priorities
|
|
||||||
|
|
||||||
- Keep changes minimal and targeted. Feel free to do light refactors that are relevant to the modifications.
|
|
||||||
- Breaking changes:
|
|
||||||
- Do not introduce breaking usage behavior (cli flags, environment variables, etc.) unless explicitly agreed.
|
|
||||||
- Do not introduce breaking changes for the Go API in the `pkg/` directory.
|
|
||||||
- If a compatibility break seems beneficial, stop and ask for confirmation before implementing it.
|
|
||||||
- Update or add tests when behavior changes.
|
|
||||||
|
|
||||||
## Go coding conventions
|
|
||||||
|
|
||||||
### General guidelines
|
|
||||||
|
|
||||||
- Use explicit, descriptive variable names by default.
|
|
||||||
- Notable bad examples: `req`, `resp`, `cfg`, `v`
|
|
||||||
- Allowed short-name exceptions:
|
|
||||||
- indexes such as `i`, `j`
|
|
||||||
- `ctx` for `context.Context`
|
|
||||||
- `t` for `*testing.T` and `b` for `*testing.B`
|
|
||||||
- `ctrl` for `*gomock.Controller`
|
|
||||||
- `err` for `error`, `errs` for `[]error`
|
|
||||||
- `wg` for `*sync.WaitGroup`
|
|
||||||
- Avoid using global variables except for:
|
|
||||||
- exported sentinel errors that are used outside the package boundaries
|
|
||||||
- regular expressions defined with `regexp.MustCompile`
|
|
||||||
- variables set by the build pipeline, such as `Version` and `BuildDate`
|
|
||||||
- Constants
|
|
||||||
- Prefer defining them inline in a function if it's only used in that function, rather than at the package level.
|
|
||||||
- Each one should be defined right above where it's used, instead of having multiple defined at the same place in a `const ()` block
|
|
||||||
- If one is only used in a single production code function, define it right above it so it's more local for readability.
|
|
||||||
- Do not define constants when constants exist in other packages, for example `http.StatusBadRequest` or `log.LevelDebug`.
|
|
||||||
- Structs
|
|
||||||
- Prefer defining them inline in a function if it's only used in that function, rather than at the package level.
|
|
||||||
- Do not use the short if form, prefer the longer one
|
|
||||||
- Follow modern Go, according to the Go version defined in go.mod. Prefer modern constructs when equivalent:
|
|
||||||
- Example: use `for i := range 5` rather than `for i := 0; i < 5; i++`.
|
|
||||||
- Example: use `new("string")` rather than helper wrappers such as `stringPtr("string")`.
|
|
||||||
- Example: no need to pin variables in for loops when using them in goroutines or subtests.
|
|
||||||
- Use `New(...) *Item` constructor per package. Each package should ideally only have one constructor, although this is not a strict rule. The constructor should return a pointer to the struct, and not an interface.
|
|
||||||
- Always prefer using context-aware functions, for example:
|
|
||||||
- `exec.CommandContext` rather than `exec.Command`
|
|
||||||
- `http.NewRequestWithContext` rather than `http.NewRequest`
|
|
||||||
- Never export a symbol unless absolutely necessary.
|
|
||||||
- Always use the most restrictive builtin types. For example prefer `uint` over `int` if it's only zero or positive. Prefer `uint16` is the max value is 65535.
|
|
||||||
- Prefer using builtin types whenever possible AND do not define single field structs unless necessary
|
|
||||||
- Prefer splitting a code line only when it triggers the `lll` linter, do not split a command or arguments list for each element
|
|
||||||
- Use `netip` types instead of `net` types whenever possible
|
|
||||||
- Use constants instead of variables whenever possible, especially function-local inline constants.
|
|
||||||
- Do not use `time.Sleep`, prefer using a `time.Timer` with a `select` statement also listening on a context cancelation
|
|
||||||
- `panic`:
|
|
||||||
- should only be used when a programming error is encountered and you should NOT return errors for programming errors (such as passing nil objects)
|
|
||||||
- Its counterpart `recover` should not really be used, except for testing a panic in test code (or use `assert.PanicsWithValue`).
|
|
||||||
|
|
||||||
### Directory structure and file naming
|
|
||||||
|
|
||||||
- Executable main packages with a single `main()` function must be in the `cmd` directory.
|
|
||||||
Prefer having top level logic and have a longer `main()` function rather than having an `internal/app` package.
|
|
||||||
- Code lives by default in subpackages within the `internal` directory
|
|
||||||
- Code needing to be imported by external Go modules must be in subpackages within the `pkg` directory
|
|
||||||
- Example code especially using the `pkg` directory must be in `main` packages within the `examples` directory, each with a single `main.go` function.
|
|
||||||
- If AND only if the repository is a Go library and not a Go application, you may have Go files at the root of the project to simplify import paths. Most of the code should still be in subpackages in the `internal` directory.
|
|
||||||
- Interfaces should be defined in `interfaces.go` files for each package. If there are unexported interfaces which need to be mocked, which is rare, they should be defined in `interfaces_local.go` files.
|
|
||||||
- Mock files are
|
|
||||||
- `mocks_generate_test.go` which only contains `//go:generate` directives for generating mocks, and no actual code
|
|
||||||
- `mocks_test.go` which contains the generated mocks from exported interfaces and no other code, and is ignored in coverage reports
|
|
||||||
- `mocks_local_test.go` (rare) which contains the generated mocks from unexported interfaces and no other code, and is ignored in coverage reports
|
|
||||||
- NEVER generate an exported mock in a non test file, prefer re-generating files across packages.
|
|
||||||
- Package naming
|
|
||||||
- Your package name should be the same as the directory containing it, **except for the `main` package**
|
|
||||||
- Use single words for package names
|
|
||||||
- Do not use generic names for package names such as `utils` or `helpers`
|
|
||||||
- Package nesting
|
|
||||||
- Try to avoid nesting packages by default
|
|
||||||
- You can nest packages if you have different implementations for the same interface (e.g. a store interface)
|
|
||||||
- You can nest packages if you start having a lot of Go files (more than 10) and it really does make sense to make subpackages
|
|
||||||
|
|
||||||
### Linting
|
|
||||||
|
|
||||||
The linter is `golangci-lint` with the configuration defined in `.golangci.yml`.
|
|
||||||
|
|
||||||
To exclude code from linting, prefer using, when absolutely necessary, command comments `//nolint:<linter>`.
|
|
||||||
This allows the `nolintlint` linter to detect and report unnecessary `//nolint` comments later.
|
|
||||||
You can notably use `//nolint:lll` and, for good valid reasons, `//nolint:gosec`. Sometimes `//nolint:mnd` when it just doesn't make sense to extract a constant such as `n = n << 4`
|
|
||||||
Always prefer placing `//nolint` comments on the same code line where the error comes from, and not above a code block.
|
|
||||||
|
|
||||||
### Mocking
|
|
||||||
|
|
||||||
Mocking works with the `go.uber.org/mock` library, and the `mockgen` tool.
|
|
||||||
|
|
||||||
- Mocks from exported interfaces are generated using go generate commands in `mocks_generate_test.go` files, and stored in `mocks_test.go` files, using:
|
|
||||||
|
|
||||||
```go
|
|
||||||
//go:generate mockgen -destination=mocks_test.go -package=$GOPACKAGE . InterfaceA,InterfaceB
|
|
||||||
```
|
|
||||||
|
|
||||||
- Mocks from unexported interfaces are generated using go generate commands in `mocks_generate_test.go` files, and stored in `mocks_local_test.go` files. The source file for unexported interfaces is `interfaces_local.go`. The go generate command is similar to:
|
|
||||||
|
|
||||||
```go
|
|
||||||
//go:generate mockgen -destination=mocks_local_test.go -package $GOPACKAGE -source interfaces_local.go
|
|
||||||
```
|
|
||||||
|
|
||||||
- Mocks from external interfaces are generated using go generate commands in `mocks_generate_test.go` files, and stored in `mocks_<package-name>_test.go` files, using:
|
|
||||||
|
|
||||||
```go
|
|
||||||
//go:generate mockgen -destination=mocks_<package-name>_test.go -package $GOPACKAGE module-name InterfaceA,InterfaceB
|
|
||||||
```
|
|
||||||
|
|
||||||
- Generated mocks usage in tests:
|
|
||||||
- Define mocks in the subtest, not in the parent test. You can also have a function returning the mocks as a field of the test case struct, which takes in the subtest `*testing.T` as argument, and call it in the subtest to get the mocks.
|
|
||||||
- **Never** use `gomock.Any()` as argument. Always use concrete, precise arguments. You might need to define a custom GoMock matcher for your argument in some very niche and corner cases.
|
|
||||||
- **Never** use `.AnyTimes()` on mocks. Always define the number of times a certain mock call should be called, with `.Times(3)` for example.
|
|
||||||
- **Always** set the `.Return(...)` on the mock if the function returns something.
|
|
||||||
- Avoid using **mock helpers** functions, prefer a bit of repetition than tight coupling and dependency
|
|
||||||
|
|
||||||
### main.go
|
|
||||||
|
|
||||||
- Make the program OS signal aware, so it attempts a graceful shutdown when interruped. Force quit the program on a second interrupt signal.
|
|
||||||
|
|
||||||
### Formatting
|
|
||||||
|
|
||||||
The Go formatter used is gofumpt.
|
|
||||||
|
|
||||||
### Errors
|
|
||||||
|
|
||||||
- Always prefer wrapping errors with some context with `fmt.Errorf("doing this: %w", err)`
|
|
||||||
- In rare cases, you can just use `return err` notably:
|
|
||||||
- If the function is called **recursively**, since we don't wrap the wrapping multiple times for each recursion
|
|
||||||
- If the current function only statement is the call to another function, for example:
|
|
||||||
|
|
||||||
```go
|
|
||||||
func (s *Struct) Fetch() error {
|
|
||||||
return fetch() // do not wrap the error
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
- When wrapping errors, use verbs ending in "ing" and no "failed to" or "cannot" to avoid redundancy. For example, use `fmt.Errorf("resolving host: %w", err)` rather than `fmt.Errorf("failed to resolve host: %w", err)`.
|
|
||||||
- When wrapping an error, the context should NEVER contain variables injected as arguments in the function returning an error, to avoid repeating the same variable in multiple error messages.
|
|
||||||
- Testing errors:
|
|
||||||
- If the error does not wrap a sentinel error, use `assert.ErrorContains` to check for error messages, rather than `assert.EqualError`, to avoid having to update tests for minor changes in error messages. And use `assert.NoError` to check for no error.
|
|
||||||
- If the error wraps a sentinel error, use `assert.ErrorIs` to check both for the sentinel error or an expected nil error. You can also check the error message with `assert.ErrorContains`
|
|
||||||
|
|
||||||
### User program settings
|
|
||||||
|
|
||||||
- For configuration structs, each field Go zero value (i.e. `0` for `int`, `nil` for `*string`) should be an INVALID value in the user sense. This is used to detect when a field is not set, in order to default it, merge it or override it. For example if `""` is not a valid value, the field should be of type `string`. Conversely, if `""` is a valid value, the field should be of type `*string` to distinguish between "not set" and "set to empty string". Notably, boolean fields are ALWAYS of type `*bool` for this reason, since both `true` and `false` are valid values.
|
|
||||||
- Configuration reading and handling relies on the Go library github.com/qdm12/gosettings please use it whenever appropriate.
|
|
||||||
- Do not wrap errors coming from `reader.Reader` methods, since they already contain the necessary context.
|
|
||||||
- All keys passed to `reader.Reader` methods must be in environment variable format, i.e. uppercase with underscores. These get converted to lowercase and dashes for flags notably.
|
|
||||||
- For each settings structs, define the following methods, which are usually unexported, but can be exported especially for the top level Settings struct, in this order:
|
|
||||||
- `func (s *Settings) setDefaults()` whichs sets defaults (using `gosettings.Default*` functions) on unset fields
|
|
||||||
- If the settings need to be patched at runtime, which is rarely the case, define `func (s *Settings) overrideWith(other Settings)` which overrides the settings with another settings struct, only for fields that are set in the other struct (using `gosettings.OverrideWith` functions).
|
|
||||||
- `func (s Settings) validate() error` which validates the settings, and returns an error if anything is invalid
|
|
||||||
- `func (s *Settings) read(r *reader.Reader) error` which reads the settings from a gosettings/reader.Reader (which can be from multiple sources, such as environment variables, cli flags, config files etc.)
|
|
||||||
- `func (s Settings) String() string` which uses `toLinesNode().String()` to return a string representation of the settings
|
|
||||||
- `func (s Settings) toLinesNode() *gotree.Node` which a github.com/qdm12/gotree `*Node` representing the settings
|
|
||||||
|
|
||||||
### Testing
|
|
||||||
|
|
||||||
- Use the github.com/stretchr/testify library for assertions
|
|
||||||
- Most tests should be table tests with parallel subtests
|
|
||||||
- Prefer map-based table tests of the form `map[string]struct{ ... }`, with the key as the test name.
|
|
||||||
Use underscores in test names, not spaces, to keep `go test` output searchable.
|
|
||||||
- Use `testCases` for the table variable name, and `testCase` for each iterated case value.
|
|
||||||
- Run all tests in parallel:
|
|
||||||
- call `t.Parallel()` in the top-level test
|
|
||||||
- call `t.Parallel()` in each subtest
|
|
||||||
|
|
||||||
### Libraries to use
|
|
||||||
|
|
||||||
- Logging: `github.com/qdm12/log`
|
|
||||||
- Splash information at program start: `github.com/qdm12/gosplash`
|
|
||||||
- Long running services (i.e. health server, http prod server, backup loop etc.): `github.com/qdm12/goservices`
|
|
||||||
- String tree structures: `github.com/qdm12/gotree`
|
|
||||||
|
|
||||||
### Extra rules
|
|
||||||
|
|
||||||
- Do not use `http.DefaultClient`, use a custom `*http.Client` with a fixed timeout and share with dependency injections.
|
|
||||||
- Do not check for injected dependencies being `nil`, prefer to just panic on a nil pointer. By default it's fine to panic if a developer injects a dependency `nil`. `nil` does not mean use a default.
|
|
||||||
|
|
||||||
## Validation checklist
|
|
||||||
|
|
||||||
Run the following before finishing changes:
|
|
||||||
|
|
||||||
1. Go building `go build ./...`
|
|
||||||
1. Go linting `golangci-lint run`
|
|
||||||
1. Go unit tests `go test ./...`
|
|
||||||
1. If a module is added or modified, run `go mod tidy`
|
|
||||||
1. If an interface or mock command is modified, run `go generate -run mockgen ./...`
|
|
||||||
|
|
||||||
If a Markdown file is modified and `markdownlint-cli2` is available, run `markdownlint-cli2 "**/*.md"`
|
|
||||||
|
|
||||||
If a command is unavailable in the current environment, report it clearly and provide the exact command needed once available.
|
|
||||||
+15
-73
@@ -1,5 +1,5 @@
|
|||||||
ARG ALPINE_VERSION=3.23
|
ARG ALPINE_VERSION=3.22
|
||||||
ARG GO_ALPINE_VERSION=3.23
|
ARG GO_ALPINE_VERSION=3.22
|
||||||
ARG GO_VERSION=1.25
|
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
|
||||||
@@ -13,7 +13,7 @@ FROM --platform=${BUILDPLATFORM} ghcr.io/qdm12/binpot:mockgen-${MOCKGEN_VERSION}
|
|||||||
FROM --platform=${BUILDPLATFORM} golang:${GO_VERSION}-alpine${GO_ALPINE_VERSION} AS base
|
FROM --platform=${BUILDPLATFORM} golang:${GO_VERSION}-alpine${GO_ALPINE_VERSION} AS base
|
||||||
COPY --from=xcputranslate /xcputranslate /usr/local/bin/xcputranslate
|
COPY --from=xcputranslate /xcputranslate /usr/local/bin/xcputranslate
|
||||||
# Note: findutils needed to have xargs support `-d` flag for mocks stage.
|
# Note: findutils needed to have xargs support `-d` flag for mocks stage.
|
||||||
RUN apk --update add git g++ findutils iptables
|
RUN apk --update add git g++ findutils
|
||||||
ENV CGO_ENABLED=0
|
ENV CGO_ENABLED=0
|
||||||
COPY --from=golangci-lint /bin /go/bin/golangci-lint
|
COPY --from=golangci-lint /bin /go/bin/golangci-lint
|
||||||
COPY --from=mockgen /bin /go/bin/mockgen
|
COPY --from=mockgen /bin /go/bin/mockgen
|
||||||
@@ -46,10 +46,6 @@ RUN git init && \
|
|||||||
git diff --exit-code && \
|
git diff --exit-code && \
|
||||||
rm -rf .git/
|
rm -rf .git/
|
||||||
|
|
||||||
FROM --platform=${BUILDPLATFORM} base AS xcompile
|
|
||||||
RUN GOOS=darwin go build -o /dev/null ./...
|
|
||||||
RUN GOOS=windows go build -o /dev/null ./...
|
|
||||||
|
|
||||||
FROM --platform=${BUILDPLATFORM} base AS build
|
FROM --platform=${BUILDPLATFORM} base AS build
|
||||||
ARG TARGETPLATFORM
|
ARG TARGETPLATFORM
|
||||||
ARG VERSION=unknown
|
ARG VERSION=unknown
|
||||||
@@ -110,66 +106,8 @@ ENV VPN_SERVICE_PROVIDER=pia \
|
|||||||
WIREGUARD_PERSISTENT_KEEPALIVE_INTERVAL=0 \
|
WIREGUARD_PERSISTENT_KEEPALIVE_INTERVAL=0 \
|
||||||
WIREGUARD_ADDRESSES= \
|
WIREGUARD_ADDRESSES= \
|
||||||
WIREGUARD_ADDRESSES_SECRETFILE=/run/secrets/wireguard_addresses \
|
WIREGUARD_ADDRESSES_SECRETFILE=/run/secrets/wireguard_addresses \
|
||||||
WIREGUARD_MTU= \
|
WIREGUARD_MTU=1320 \
|
||||||
WIREGUARD_IMPLEMENTATION=auto \
|
WIREGUARD_IMPLEMENTATION=auto \
|
||||||
# Amnezia
|
|
||||||
AMNEZIAWG_ENDPOINT_IP= \
|
|
||||||
AMNEZIAWG_ENDPOINT_PORT= \
|
|
||||||
AMNEZIAWG_CONF_SECRETFILE=/run/secrets/wg0.conf \
|
|
||||||
AMNEZIAWG_PRIVATE_KEY= \
|
|
||||||
AMNEZIAWG_PRIVATE_KEY_SECRETFILE=/run/secrets/wireguard_private_key \
|
|
||||||
AMNEZIAWG_PRESHARED_KEY= \
|
|
||||||
AMNEZIAWG_PRESHARED_KEY_SECRETFILE=/run/secrets/wireguard_preshared_key \
|
|
||||||
AMNEZIAWG_PUBLIC_KEY= \
|
|
||||||
AMNEZIAWG_ALLOWED_IPS= \
|
|
||||||
AMNEZIAWG_PERSISTENT_KEEPALIVE_INTERVAL=0 \
|
|
||||||
AMNEZIAWG_ADDRESSES= \
|
|
||||||
AMNEZIAWG_ADDRESSES_SECRETFILE=/run/secrets/wireguard_addresses \
|
|
||||||
AMNEZIAWG_MTU= \
|
|
||||||
AMNEZIAWG_JC=0 \
|
|
||||||
AMNEZIAWG_JMIN=0 \
|
|
||||||
AMNEZIAWG_JMAX=0 \
|
|
||||||
AMNEZIAWG_S1=0 \
|
|
||||||
AMNEZIAWG_S2=0 \
|
|
||||||
AMNEZIAWG_S3=0 \
|
|
||||||
AMNEZIAWG_S4=0 \
|
|
||||||
AMNEZIAWG_H1= \
|
|
||||||
AMNEZIAWG_H2= \
|
|
||||||
AMNEZIAWG_H3= \
|
|
||||||
AMNEZIAWG_H4= \
|
|
||||||
AMNEZIAWG_I1= \
|
|
||||||
AMNEZIAWG_I2= \
|
|
||||||
AMNEZIAWG_I3= \
|
|
||||||
AMNEZIAWG_I4= \
|
|
||||||
AMNEZIAWG_I5= \
|
|
||||||
# Wireguard AmneziaWG userspace obfuscation (requires WIREGUARD_IMPLEMENTATION=amneziawg)
|
|
||||||
AMNEZIAWG_JC=0 \
|
|
||||||
AMNEZIAWG_JMIN=0 \
|
|
||||||
AMNEZIAWG_JMAX=0 \
|
|
||||||
AMNEZIAWG_S1=0 \
|
|
||||||
AMNEZIAWG_S2=0 \
|
|
||||||
AMNEZIAWG_S3=0 \
|
|
||||||
AMNEZIAWG_S4=0 \
|
|
||||||
AMNEZIAWG_H1= \
|
|
||||||
AMNEZIAWG_H2= \
|
|
||||||
AMNEZIAWG_H3= \
|
|
||||||
AMNEZIAWG_H4= \
|
|
||||||
AMNEZIAWG_I1= \
|
|
||||||
AMNEZIAWG_I2= \
|
|
||||||
AMNEZIAWG_I3= \
|
|
||||||
AMNEZIAWG_I4= \
|
|
||||||
AMNEZIAWG_I5= \
|
|
||||||
# VPN server port forwarding
|
|
||||||
VPN_PORT_FORWARDING=off \
|
|
||||||
VPN_PORT_FORWARDING_PROVIDER= \
|
|
||||||
VPN_PORT_FORWARDING_UP_COMMAND= \
|
|
||||||
VPN_PORT_FORWARDING_DOWN_COMMAND= \
|
|
||||||
VPN_PORT_FORWARDING_LISTENING_PORTS=0 \
|
|
||||||
VPN_PORT_FORWARDING_PORTS_COUNT=1 \
|
|
||||||
VPN_PORT_FORWARDING_STATUS_FILE="/tmp/gluetun/forwarded_port" \
|
|
||||||
# PMTUD
|
|
||||||
PMTUD_ICMP_ADDRESSES=1.1.1.1,8.8.8.8 \
|
|
||||||
PMTUD_TCP_ADDRESSES=1.1.1.1:443,8.8.8.8:443,1.1.1.1:53,8.8.8.8:53,[2606:4700:4700::1111]:53,[2001:4860:4860::8888]:53,[2606:4700:4700::1111]:443,[2001:4860:4860::8888]:443 \
|
|
||||||
# VPN server filtering
|
# VPN server filtering
|
||||||
SERVER_REGIONS= \
|
SERVER_REGIONS= \
|
||||||
SERVER_COUNTRIES= \
|
SERVER_COUNTRIES= \
|
||||||
@@ -181,8 +119,14 @@ ENV VPN_SERVICE_PROVIDER=pia \
|
|||||||
OWNED_ONLY=no \
|
OWNED_ONLY=no \
|
||||||
# # Private Internet Access only:
|
# # Private Internet Access only:
|
||||||
PRIVATE_INTERNET_ACCESS_OPENVPN_ENCRYPTION_PRESET= \
|
PRIVATE_INTERNET_ACCESS_OPENVPN_ENCRYPTION_PRESET= \
|
||||||
|
VPN_PORT_FORWARDING=off \
|
||||||
|
VPN_PORT_FORWARDING_LISTENING_PORT=0 \
|
||||||
|
VPN_PORT_FORWARDING_PROVIDER= \
|
||||||
|
VPN_PORT_FORWARDING_STATUS_FILE="/tmp/gluetun/forwarded_port" \
|
||||||
VPN_PORT_FORWARDING_USERNAME= \
|
VPN_PORT_FORWARDING_USERNAME= \
|
||||||
VPN_PORT_FORWARDING_PASSWORD= \
|
VPN_PORT_FORWARDING_PASSWORD= \
|
||||||
|
VPN_PORT_FORWARDING_UP_COMMAND= \
|
||||||
|
VPN_PORT_FORWARDING_DOWN_COMMAND= \
|
||||||
# # Cyberghost only:
|
# # Cyberghost only:
|
||||||
OPENVPN_CERT= \
|
OPENVPN_CERT= \
|
||||||
OPENVPN_KEY= \
|
OPENVPN_KEY= \
|
||||||
@@ -214,9 +158,7 @@ ENV VPN_SERVICE_PROVIDER=pia \
|
|||||||
FIREWALL_VPN_INPUT_PORTS= \
|
FIREWALL_VPN_INPUT_PORTS= \
|
||||||
FIREWALL_INPUT_PORTS= \
|
FIREWALL_INPUT_PORTS= \
|
||||||
FIREWALL_OUTBOUND_SUBNETS= \
|
FIREWALL_OUTBOUND_SUBNETS= \
|
||||||
FIREWALL_IPTABLES_LOG_LEVEL=info \
|
FIREWALL_DEBUG=off \
|
||||||
# IPv6
|
|
||||||
IPV6_CHECK_ADDRESSES=[2001:4860:4860::8888]:53,[2606:4700:4700::1111]:53 \
|
|
||||||
# Logging
|
# Logging
|
||||||
LOG_LEVEL=info \
|
LOG_LEVEL=info \
|
||||||
# Health
|
# Health
|
||||||
@@ -226,9 +168,9 @@ ENV VPN_SERVICE_PROVIDER=pia \
|
|||||||
HEALTH_SMALL_CHECK_TYPE=icmp \
|
HEALTH_SMALL_CHECK_TYPE=icmp \
|
||||||
HEALTH_RESTART_VPN=on \
|
HEALTH_RESTART_VPN=on \
|
||||||
# DNS
|
# DNS
|
||||||
|
DNS_SERVER=on \
|
||||||
DNS_UPSTREAM_RESOLVER_TYPE=DoT \
|
DNS_UPSTREAM_RESOLVER_TYPE=DoT \
|
||||||
# Note: DNS_UPSTREAM_RESOLVERS defaults to cloudflare in code if DNS_UPSTREAM_PLAIN_ADDRESSES is empty
|
DNS_UPSTREAM_RESOLVERS=cloudflare \
|
||||||
DNS_UPSTREAM_RESOLVERS= \
|
|
||||||
DNS_BLOCK_IPS= \
|
DNS_BLOCK_IPS= \
|
||||||
DNS_BLOCK_IP_PREFIXES= \
|
DNS_BLOCK_IP_PREFIXES= \
|
||||||
DNS_CACHING=on \
|
DNS_CACHING=on \
|
||||||
@@ -239,7 +181,8 @@ ENV VPN_SERVICE_PROVIDER=pia \
|
|||||||
DNS_UNBLOCK_HOSTNAMES= \
|
DNS_UNBLOCK_HOSTNAMES= \
|
||||||
DNS_REBINDING_PROTECTION_EXEMPT_HOSTNAMES= \
|
DNS_REBINDING_PROTECTION_EXEMPT_HOSTNAMES= \
|
||||||
DNS_UPDATE_PERIOD=24h \
|
DNS_UPDATE_PERIOD=24h \
|
||||||
DNS_UPSTREAM_PLAIN_ADDRESSES= \
|
DNS_ADDRESS=127.0.0.1 \
|
||||||
|
DNS_KEEP_NAMESERVER=off \
|
||||||
# HTTP proxy
|
# HTTP proxy
|
||||||
HTTPPROXY= \
|
HTTPPROXY= \
|
||||||
HTTPPROXY_LOG=off \
|
HTTPPROXY_LOG=off \
|
||||||
@@ -281,7 +224,6 @@ ENV VPN_SERVICE_PROVIDER=pia \
|
|||||||
PPROF_HTTP_SERVER_ADDRESS=":6060" \
|
PPROF_HTTP_SERVER_ADDRESS=":6060" \
|
||||||
# Extras
|
# Extras
|
||||||
VERSION_INFORMATION=on \
|
VERSION_INFORMATION=on \
|
||||||
BORINGPOLL_GLUETUNCOM=off \
|
|
||||||
TZ= \
|
TZ= \
|
||||||
PUID=1000 \
|
PUID=1000 \
|
||||||
PGID=1000
|
PGID=1000
|
||||||
|
|||||||
@@ -2,8 +2,6 @@
|
|||||||
|
|
||||||
⚠️ This and [gluetun-wiki](https://github.com/qdm12/gluetun-wiki) are the only websites for Gluetun, other websites claiming to be official are scams ⚠️
|
⚠️ This and [gluetun-wiki](https://github.com/qdm12/gluetun-wiki) are the only websites for Gluetun, other websites claiming to be official are scams ⚠️
|
||||||
|
|
||||||
💁 You can optionally set `BORINGPOLL_GLUETUNCOM=on` to... [poll](./internal/boringpoll/boringpoll.go) that **scammy AI slop** website every few minutes so it costs them too much to keep it up. My gentle email reminders to take it down are being grossly ignored 🤷 This would make me very happy and serve this community.
|
|
||||||
|
|
||||||
Lightweight swiss-army-knife-like VPN client to multiple VPN service providers
|
Lightweight swiss-army-knife-like VPN client to multiple VPN service providers
|
||||||
|
|
||||||

|

|
||||||
@@ -59,15 +57,14 @@ Lightweight swiss-army-knife-like VPN client to multiple VPN service providers
|
|||||||
|
|
||||||
## Features
|
## Features
|
||||||
|
|
||||||
- Based on Alpine 3.23 for a small Docker image of 43.1MB
|
- Based on Alpine 3.22 for a small Docker image of 41.1MB
|
||||||
- Supports: **AirVPN**, **Cyberghost**, **ExpressVPN**, **FastestVPN**, **Giganews**, **HideMyAss**, **IPVanish**, **IVPN**, **Mullvad** (Wireguard only), **NordVPN**, **Perfect Privacy**, **Privado**, **Private Internet Access**, **PrivateVPN**, **ProtonVPN**, **PureVPN**, **SlickVPN**, **Surfshark**, **TorGuard**, **VPNSecure.me**, **VPNUnlimited**, **Vyprvpn**, **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**, **Windscribe** servers
|
||||||
- Supports OpenVPN for all providers listed
|
- Supports OpenVPN for all providers listed
|
||||||
- Supports Wireguard both kernelspace and userspace
|
- Supports Wireguard both kernelspace and userspace
|
||||||
- For **AirVPN**, **FastestVPN**, **Ivpn**, **Mullvad**, **NordVPN**, **Perfect privacy**, **ProtonVPN**, **Surfshark** and **Windscribe**
|
- For **AirVPN**, **FastestVPN**, **Ivpn**, **Mullvad**, **NordVPN**, **Perfect privacy**, **ProtonVPN**, **Surfshark** and **Windscribe**
|
||||||
- For **Cyberghost**, **Private Internet Access**, **PrivateVPN**, **PureVPN**, **Torguard**, **VPN Unlimited** and **VyprVPN** using [the custom provider](https://github.com/qdm12/gluetun-wiki/blob/main/setup/providers/custom.md)
|
- For **Cyberghost**, **Private Internet Access**, **PrivateVPN**, **PureVPN**, **Torguard**, **VPN Unlimited** and **VyprVPN** using [the custom provider](https://github.com/qdm12/gluetun-wiki/blob/main/setup/providers/custom.md)
|
||||||
- For custom Wireguard configurations using [the custom provider](https://github.com/qdm12/gluetun-wiki/blob/main/setup/providers/custom.md)
|
- For custom Wireguard configurations using [the custom provider](https://github.com/qdm12/gluetun-wiki/blob/main/setup/providers/custom.md)
|
||||||
- More in progress, see [#134](https://github.com/qdm12/gluetun/issues/134)
|
- More in progress, see [#134](https://github.com/qdm12/gluetun/issues/134)
|
||||||
- Supports AmneziaWG only with the custom provider for now
|
|
||||||
- DNS over TLS baked in with service provider(s) of your choice
|
- DNS over TLS baked in with service provider(s) of your choice
|
||||||
- DNS fine blocking of malicious/ads/surveillance hostnames and IP addresses, with live update every 24 hours
|
- DNS fine blocking of malicious/ads/surveillance hostnames and IP addresses, with live update every 24 hours
|
||||||
- Choose the vpn network protocol, `udp` or `tcp`
|
- Choose the vpn network protocol, `udp` or `tcp`
|
||||||
|
|||||||
@@ -17,7 +17,7 @@ import (
|
|||||||
func ptrTo[T any](v T) *T { return &v }
|
func ptrTo[T any](v T) *T { return &v }
|
||||||
|
|
||||||
func simpleTest(ctx context.Context, env []string, logger Logger) error {
|
func simpleTest(ctx context.Context, env []string, logger Logger) error {
|
||||||
const timeout = 60 * time.Second
|
const timeout = 30 * time.Second
|
||||||
ctx, cancel := context.WithTimeout(ctx, timeout)
|
ctx, cancel := context.WithTimeout(ctx, timeout)
|
||||||
defer cancel()
|
defer cancel()
|
||||||
|
|
||||||
|
|||||||
+31
-64
@@ -6,7 +6,6 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"io/fs"
|
"io/fs"
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/netip"
|
|
||||||
"os"
|
"os"
|
||||||
"os/exec"
|
"os/exec"
|
||||||
"os/signal"
|
"os/signal"
|
||||||
@@ -16,10 +15,7 @@ import (
|
|||||||
_ "time/tzdata"
|
_ "time/tzdata"
|
||||||
|
|
||||||
_ "github.com/breml/rootcerts"
|
_ "github.com/breml/rootcerts"
|
||||||
"github.com/qdm12/dns/v2/pkg/doh"
|
|
||||||
dnsprovider "github.com/qdm12/dns/v2/pkg/provider"
|
|
||||||
"github.com/qdm12/gluetun/internal/alpine"
|
"github.com/qdm12/gluetun/internal/alpine"
|
||||||
"github.com/qdm12/gluetun/internal/boringpoll"
|
|
||||||
"github.com/qdm12/gluetun/internal/cli"
|
"github.com/qdm12/gluetun/internal/cli"
|
||||||
"github.com/qdm12/gluetun/internal/command"
|
"github.com/qdm12/gluetun/internal/command"
|
||||||
"github.com/qdm12/gluetun/internal/configuration/settings"
|
"github.com/qdm12/gluetun/internal/configuration/settings"
|
||||||
@@ -171,7 +167,7 @@ func _main(ctx context.Context, buildInfo models.BuildInformation,
|
|||||||
|
|
||||||
defer fmt.Println(gluetunLogo)
|
defer fmt.Println(gluetunLogo)
|
||||||
|
|
||||||
announcementExp, err := time.Parse(time.RFC3339, "2026-04-30T00:00:00Z")
|
announcementExp, err := time.Parse(time.RFC3339, "2024-12-01T00:00:00Z")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -182,7 +178,7 @@ func _main(ctx context.Context, buildInfo models.BuildInformation,
|
|||||||
Version: buildInfo.Version,
|
Version: buildInfo.Version,
|
||||||
Commit: buildInfo.Commit,
|
Commit: buildInfo.Commit,
|
||||||
Created: buildInfo.Created,
|
Created: buildInfo.Created,
|
||||||
Announcement: "Set BORINGPOLL_GLUETUNCOM=on to help combat AI slop and shutdown that scam website",
|
Announcement: "All control server routes will become private by default after the v3.41.0 release",
|
||||||
AnnounceExp: announcementExp,
|
AnnounceExp: announcementExp,
|
||||||
// Sponsor information
|
// Sponsor information
|
||||||
PaypalUser: "qmcgaw",
|
PaypalUser: "qmcgaw",
|
||||||
@@ -210,6 +206,9 @@ func _main(ctx context.Context, buildInfo models.BuildInformation,
|
|||||||
netLinker.PatchLoggerLevel(logLevel)
|
netLinker.PatchLoggerLevel(logLevel)
|
||||||
|
|
||||||
routingLogger := logger.New(log.SetComponent("routing"))
|
routingLogger := logger.New(log.SetComponent("routing"))
|
||||||
|
if *allSettings.Firewall.Debug { // To remove in v4
|
||||||
|
routingLogger.Patch(log.SetLevel(log.LevelDebug))
|
||||||
|
}
|
||||||
routingConf := routing.New(netLinker, routingLogger)
|
routingConf := routing.New(netLinker, routingLogger)
|
||||||
|
|
||||||
defaultRoutes, err := routingConf.DefaultRoutes()
|
defaultRoutes, err := routingConf.DefaultRoutes()
|
||||||
@@ -222,11 +221,11 @@ func _main(ctx context.Context, buildInfo models.BuildInformation,
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
iptablesLogLevel, _ := log.ParseLevel(allSettings.Firewall.Iptables.LogLevel)
|
|
||||||
iptablesLogger := logger.New(log.SetComponent("iptables"), log.SetLevel(iptablesLogLevel))
|
|
||||||
|
|
||||||
firewallLogger := logger.New(log.SetComponent("firewall"))
|
firewallLogger := logger.New(log.SetComponent("firewall"))
|
||||||
firewallConf, err := firewall.NewConfig(ctx, firewallLogger, iptablesLogger, cmder,
|
if *allSettings.Firewall.Debug { // To remove in v4
|
||||||
|
firewallLogger.Patch(log.SetLevel(log.LevelDebug))
|
||||||
|
}
|
||||||
|
firewallConf, err := firewall.NewConfig(ctx, firewallLogger, cmder,
|
||||||
defaultRoutes, localNetworks)
|
defaultRoutes, localNetworks)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
@@ -237,10 +236,6 @@ func _main(ctx context.Context, buildInfo models.BuildInformation,
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
err = netLinker.FlushConntrack()
|
|
||||||
if err != nil {
|
|
||||||
logger.Warnf("flushing conntrack failed: %s", err)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO run this in a loop or in openvpn to reload from file without restarting
|
// TODO run this in a loop or in openvpn to reload from file without restarting
|
||||||
@@ -250,13 +245,10 @@ func _main(ctx context.Context, buildInfo models.BuildInformation,
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
ipv6SupportLevel, err := netLinker.FindIPv6SupportLevel(ctx,
|
ipv6Supported, err := netLinker.IsIPv6Supported()
|
||||||
allSettings.IPv6.CheckAddresses, firewallConf)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("checking for IPv6 support: %w", err)
|
return fmt.Errorf("checking for IPv6 support: %w", err)
|
||||||
}
|
}
|
||||||
ipv6Supported := ipv6SupportLevel == netlink.IPv6Supported ||
|
|
||||||
ipv6SupportLevel == netlink.IPv6Internet
|
|
||||||
|
|
||||||
err = allSettings.Validate(storage, ipv6Supported, logger)
|
err = allSettings.Validate(storage, ipv6Supported, logger)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -271,7 +263,7 @@ func _main(ctx context.Context, buildInfo models.BuildInformation,
|
|||||||
|
|
||||||
puid, pgid := int(*allSettings.System.PUID), int(*allSettings.System.PGID)
|
puid, pgid := int(*allSettings.System.PUID), int(*allSettings.System.PGID)
|
||||||
|
|
||||||
const clientTimeout = 35 * time.Second
|
const clientTimeout = 15 * time.Second
|
||||||
httpClient := &http.Client{Timeout: clientTimeout}
|
httpClient := &http.Client{Timeout: clientTimeout}
|
||||||
// Create configurators
|
// Create configurators
|
||||||
alpineConf := alpine.New()
|
alpineConf := alpine.New()
|
||||||
@@ -286,7 +278,7 @@ func _main(ctx context.Context, buildInfo models.BuildInformation,
|
|||||||
err = printVersions(ctx, logger, []printVersionElement{
|
err = printVersions(ctx, logger, []printVersionElement{
|
||||||
{name: "Alpine", getVersion: alpineConf.Version},
|
{name: "Alpine", getVersion: alpineConf.Version},
|
||||||
{name: "OpenVPN", getVersion: ovpnVersion},
|
{name: "OpenVPN", getVersion: ovpnVersion},
|
||||||
{name: "Firewall", getVersion: firewallConf.Version},
|
{name: "IPtables", getVersion: firewallConf.Version},
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
@@ -402,7 +394,7 @@ func _main(ctx context.Context, buildInfo models.BuildInformation,
|
|||||||
|
|
||||||
dnsLogger := logger.New(log.SetComponent("dns"))
|
dnsLogger := logger.New(log.SetComponent("dns"))
|
||||||
dnsLooper, err := dns.NewLoop(allSettings.DNS, httpClient,
|
dnsLooper, err := dns.NewLoop(allSettings.DNS, httpClient,
|
||||||
dnsLogger, localNetworksToPrefixes(localNetworks))
|
dnsLogger)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("creating DNS loop: %w", err)
|
return fmt.Errorf("creating DNS loop: %w", err)
|
||||||
}
|
}
|
||||||
@@ -435,31 +427,20 @@ func _main(ctx context.Context, buildInfo models.BuildInformation,
|
|||||||
go healthcheckServer.Run(healthServerCtx, healthServerDone)
|
go healthcheckServer.Run(healthServerCtx, healthServerDone)
|
||||||
healthChecker := healthcheck.NewChecker(healthLogger)
|
healthChecker := healthcheck.NewChecker(healthLogger)
|
||||||
|
|
||||||
// Note: we use a separate DoH dialer for the VPN servers data updater, separate from the
|
|
||||||
// main DNS local server to make sure no request is blocked by filters.
|
|
||||||
dohDialer, err := doh.New(doh.Settings{
|
|
||||||
UpstreamResolvers: []dnsprovider.Provider{dnsprovider.Cloudflare(), dnsprovider.Google()},
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("creating updater DoH dialer: %w", err)
|
|
||||||
}
|
|
||||||
updaterLogger := logger.New(log.SetComponent("updater"))
|
updaterLogger := logger.New(log.SetComponent("updater"))
|
||||||
|
|
||||||
unzipper := unzip.New(httpClient)
|
unzipper := unzip.New(httpClient)
|
||||||
parallelResolver := resolver.NewParallelResolver(dohDialer)
|
parallelResolver := resolver.NewParallelResolver(allSettings.Updater.DNSAddress)
|
||||||
openvpnFileExtractor := extract.New()
|
openvpnFileExtractor := extract.New()
|
||||||
providers := provider.NewProviders(storage, time.Now, updaterLogger,
|
providers := provider.NewProviders(storage, time.Now, updaterLogger,
|
||||||
httpClient, unzipper, parallelResolver, publicIPLooper.Fetcher(),
|
httpClient, unzipper, parallelResolver, publicIPLooper.Fetcher(),
|
||||||
openvpnFileExtractor, allSettings.Updater)
|
openvpnFileExtractor, allSettings.Updater)
|
||||||
|
|
||||||
boringPollLogger := logger.New(log.SetComponent("boring poll"))
|
|
||||||
boringPoll := boringpoll.New(httpClient, boringPollLogger, allSettings.BoringPoll)
|
|
||||||
|
|
||||||
vpnLogger := logger.New(log.SetComponent("vpn"))
|
vpnLogger := logger.New(log.SetComponent("vpn"))
|
||||||
vpnLooper := vpn.NewLoop(allSettings.VPN, ipv6SupportLevel, allSettings.Firewall.VPNInputPorts,
|
vpnLooper := vpn.NewLoop(allSettings.VPN, ipv6Supported, allSettings.Firewall.VPNInputPorts,
|
||||||
providers, storage, boringPoll, allSettings.Health, healthChecker, healthcheckServer,
|
providers, storage, allSettings.Health, healthChecker, healthcheckServer, ovpnConf, netLinker, firewallConf,
|
||||||
ovpnConf, netLinker, firewallConf, routingConf, portForwardLooper, cmder, publicIPLooper,
|
routingConf, portForwardLooper, cmder, publicIPLooper, dnsLooper, vpnLogger, httpClient,
|
||||||
dnsLooper, vpnLogger, httpClient, buildInfo, *allSettings.Version.Enabled)
|
buildInfo, *allSettings.Version.Enabled)
|
||||||
vpnHandler, vpnCtx, vpnDone := goshutdown.NewGoRoutineHandler(
|
vpnHandler, vpnCtx, vpnDone := goshutdown.NewGoRoutineHandler(
|
||||||
"vpn", goroutine.OptionTimeout(time.Second))
|
"vpn", goroutine.OptionTimeout(time.Second))
|
||||||
go vpnLooper.Run(vpnCtx, vpnDone)
|
go vpnLooper.Run(vpnCtx, vpnDone)
|
||||||
@@ -497,7 +478,7 @@ func _main(ctx context.Context, buildInfo models.BuildInformation,
|
|||||||
httpServer, err := server.New(httpServerCtx, allSettings.ControlServer,
|
httpServer, err := server.New(httpServerCtx, allSettings.ControlServer,
|
||||||
logger.New(log.SetComponent("http server")),
|
logger.New(log.SetComponent("http server")),
|
||||||
buildInfo, vpnLooper, portForwardLooper, dnsLooper, updaterLooper, publicIPLooper,
|
buildInfo, vpnLooper, portForwardLooper, dnsLooper, updaterLooper, publicIPLooper,
|
||||||
storage, ipv6SupportLevel.IsSupported())
|
storage, ipv6Supported)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("setting up control server: %w", err)
|
return fmt.Errorf("setting up control server: %w", err)
|
||||||
}
|
}
|
||||||
@@ -567,42 +548,31 @@ func printVersions(ctx context.Context, logger infoer,
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func localNetworksToPrefixes(localNetworks []routing.LocalNetwork) (prefixes []netip.Prefix) {
|
|
||||||
prefixes = make([]netip.Prefix, len(localNetworks))
|
|
||||||
for i, localNetwork := range localNetworks {
|
|
||||||
prefixes[i] = localNetwork.IPNet
|
|
||||||
}
|
|
||||||
return prefixes
|
|
||||||
}
|
|
||||||
|
|
||||||
type netLinker interface {
|
type netLinker interface {
|
||||||
Addresser
|
Addresser
|
||||||
Router
|
Router
|
||||||
Ruler
|
Ruler
|
||||||
Linker
|
Linker
|
||||||
IsWireguardSupported() (ok bool, err error)
|
IsWireguardSupported() bool
|
||||||
FindIPv6SupportLevel(ctx context.Context,
|
IsIPv6Supported() (ok bool, err error)
|
||||||
checkAddresses []netip.AddrPort, firewall netlink.Firewall,
|
|
||||||
) (level netlink.IPv6SupportLevel, err error)
|
|
||||||
FlushConntrack() error
|
|
||||||
PatchLoggerLevel(level log.Level)
|
PatchLoggerLevel(level log.Level)
|
||||||
}
|
}
|
||||||
|
|
||||||
type Addresser interface {
|
type Addresser interface {
|
||||||
AddrList(linkIndex uint32, family uint8) (
|
AddrList(link netlink.Link, family int) (
|
||||||
addresses []netip.Prefix, err error)
|
addresses []netlink.Addr, err error)
|
||||||
AddrReplace(linkIndex uint32, addr netip.Prefix) error
|
AddrReplace(link netlink.Link, addr netlink.Addr) error
|
||||||
}
|
}
|
||||||
|
|
||||||
type Router interface {
|
type Router interface {
|
||||||
RouteList(family uint8) (routes []netlink.Route, err error)
|
RouteList(family int) (routes []netlink.Route, err error)
|
||||||
RouteAdd(route netlink.Route) error
|
RouteAdd(route netlink.Route) error
|
||||||
RouteDel(route netlink.Route) error
|
RouteDel(route netlink.Route) error
|
||||||
RouteReplace(route netlink.Route) error
|
RouteReplace(route netlink.Route) error
|
||||||
}
|
}
|
||||||
|
|
||||||
type Ruler interface {
|
type Ruler interface {
|
||||||
RuleList(family uint8) (rules []netlink.Rule, err error)
|
RuleList(family int) (rules []netlink.Rule, err error)
|
||||||
RuleAdd(rule netlink.Rule) error
|
RuleAdd(rule netlink.Rule) error
|
||||||
RuleDel(rule netlink.Rule) error
|
RuleDel(rule netlink.Rule) error
|
||||||
}
|
}
|
||||||
@@ -610,12 +580,11 @@ type Ruler interface {
|
|||||||
type Linker interface {
|
type Linker interface {
|
||||||
LinkList() (links []netlink.Link, err error)
|
LinkList() (links []netlink.Link, err error)
|
||||||
LinkByName(name string) (link netlink.Link, err error)
|
LinkByName(name string) (link netlink.Link, err error)
|
||||||
LinkByIndex(index uint32) (link netlink.Link, err error)
|
LinkByIndex(index int) (link netlink.Link, err error)
|
||||||
LinkAdd(link netlink.Link) (linkIndex uint32, err error)
|
LinkAdd(link netlink.Link) (linkIndex int, err error)
|
||||||
LinkDel(linkIndex uint32) (err error)
|
LinkDel(link netlink.Link) (err error)
|
||||||
LinkSetUp(linkIndex uint32) (err error)
|
LinkSetUp(link netlink.Link) (linkIndex int, err error)
|
||||||
LinkSetDown(linkIndex uint32) (err error)
|
LinkSetDown(link netlink.Link) (err error)
|
||||||
LinkSetMTU(linkIndex, mtu uint32) error
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type clier interface {
|
type clier interface {
|
||||||
@@ -636,8 +605,6 @@ type RunStarter interface {
|
|||||||
Run(cmd *exec.Cmd) (output string, err error)
|
Run(cmd *exec.Cmd) (output string, err error)
|
||||||
Start(cmd *exec.Cmd) (stdoutLines, stderrLines <-chan string,
|
Start(cmd *exec.Cmd) (stdoutLines, stderrLines <-chan string,
|
||||||
waitError <-chan error, err error)
|
waitError <-chan error, err error)
|
||||||
RunAndLog(ctx context.Context, commandString string,
|
|
||||||
logger command.Logger) (err error)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const gluetunLogo = ` @@@
|
const gluetunLogo = ` @@@
|
||||||
|
|||||||
@@ -4,34 +4,30 @@ go 1.25.0
|
|||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/ProtonMail/go-srp v0.0.7
|
github.com/ProtonMail/go-srp v0.0.7
|
||||||
github.com/amnezia-vpn/amneziawg-go v0.2.16
|
github.com/breml/rootcerts v0.3.3
|
||||||
github.com/breml/rootcerts v0.3.4
|
|
||||||
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/jsimonetti/rtnetlink v1.4.2
|
github.com/klauspost/compress v1.18.1
|
||||||
github.com/klauspost/compress v1.18.4
|
|
||||||
github.com/klauspost/pgzip v1.2.6
|
github.com/klauspost/pgzip v1.2.6
|
||||||
github.com/mdlayher/genetlink v1.3.2
|
|
||||||
github.com/mdlayher/netlink v1.9.0
|
|
||||||
github.com/pelletier/go-toml/v2 v2.2.4
|
github.com/pelletier/go-toml/v2 v2.2.4
|
||||||
github.com/qdm12/dns/v2 v2.0.0-rc9.0.20260421173011-9de8e7fdbe3a
|
github.com/qdm12/dns/v2 v2.0.0-rc10
|
||||||
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.1-0.20260305164749-b713de4fee6c
|
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.11.1
|
github.com/stretchr/testify v1.11.1
|
||||||
github.com/ti-mo/netfilter v0.5.3
|
|
||||||
github.com/ulikunitz/xz v0.5.15
|
github.com/ulikunitz/xz v0.5.15
|
||||||
|
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.51.0
|
golang.org/x/net v0.47.0
|
||||||
golang.org/x/sys v0.42.0
|
golang.org/x/sys v0.38.0
|
||||||
golang.org/x/text v0.35.0
|
golang.org/x/text v0.31.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.1
|
gopkg.in/ini.v1 v1.67.0
|
||||||
)
|
)
|
||||||
|
|
||||||
require (
|
require (
|
||||||
@@ -42,10 +38,13 @@ require (
|
|||||||
github.com/cloudflare/circl v1.6.1 // indirect
|
github.com/cloudflare/circl v1.6.1 // indirect
|
||||||
github.com/cronokirby/saferith v0.33.0 // indirect
|
github.com/cronokirby/saferith v0.33.0 // indirect
|
||||||
github.com/davecgh/go-spew v1.1.1 // indirect
|
github.com/davecgh/go-spew v1.1.1 // indirect
|
||||||
github.com/google/go-cmp v0.7.0 // indirect
|
github.com/google/go-cmp v0.6.0 // indirect
|
||||||
|
github.com/josharian/native v1.1.0 // indirect
|
||||||
github.com/mattn/go-colorable v0.1.13 // indirect
|
github.com/mattn/go-colorable v0.1.13 // indirect
|
||||||
github.com/mattn/go-isatty v0.0.20 // indirect
|
github.com/mattn/go-isatty v0.0.20 // indirect
|
||||||
github.com/mdlayher/socket v0.5.1 // indirect
|
github.com/mdlayher/genetlink v1.3.2 // indirect
|
||||||
|
github.com/mdlayher/netlink v1.7.2 // indirect
|
||||||
|
github.com/mdlayher/socket v0.4.1 // indirect
|
||||||
github.com/miekg/dns v1.1.62 // indirect
|
github.com/miekg/dns v1.1.62 // indirect
|
||||||
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect
|
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect
|
||||||
github.com/pkg/errors v0.9.1 // indirect
|
github.com/pkg/errors v0.9.1 // indirect
|
||||||
@@ -56,10 +55,12 @@ require (
|
|||||||
github.com/prometheus/procfs v0.15.1 // indirect
|
github.com/prometheus/procfs v0.15.1 // indirect
|
||||||
github.com/qdm12/goservices v0.1.1-0.20251104135713-6bee97bd4978 // 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
|
||||||
golang.org/x/crypto v0.48.0 // indirect
|
github.com/vishvananda/netns v0.0.5 // indirect
|
||||||
golang.org/x/mod v0.33.0 // indirect
|
golang.org/x/crypto v0.45.0 // indirect
|
||||||
golang.org/x/sync v0.20.0 // indirect
|
golang.org/x/mod v0.29.0 // indirect
|
||||||
golang.org/x/tools v0.42.0 // indirect
|
golang.org/x/sync v0.18.0 // indirect
|
||||||
|
golang.org/x/time v0.3.0 // indirect
|
||||||
|
golang.org/x/tools v0.38.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
|
||||||
|
|||||||
@@ -6,37 +6,32 @@ github.com/ProtonMail/go-crypto v1.3.0-proton h1:tAQKQRZX/73VmzK6yHSCaRUOvS/3OYS
|
|||||||
github.com/ProtonMail/go-crypto v1.3.0-proton/go.mod h1:9whxjD8Rbs29b4XWbB8irEcE8KHMqaR2e7GWU1R+/PE=
|
github.com/ProtonMail/go-crypto v1.3.0-proton/go.mod h1:9whxjD8Rbs29b4XWbB8irEcE8KHMqaR2e7GWU1R+/PE=
|
||||||
github.com/ProtonMail/go-srp v0.0.7 h1:Sos3Qk+th4tQR64vsxGIxYpN3rdnG9Wf9K4ZloC1JrI=
|
github.com/ProtonMail/go-srp v0.0.7 h1:Sos3Qk+th4tQR64vsxGIxYpN3rdnG9Wf9K4ZloC1JrI=
|
||||||
github.com/ProtonMail/go-srp v0.0.7/go.mod h1:giCp+7qRnMIcCvI6V6U3S1lDDXDQYx2ewJ6F/9wdlJk=
|
github.com/ProtonMail/go-srp v0.0.7/go.mod h1:giCp+7qRnMIcCvI6V6U3S1lDDXDQYx2ewJ6F/9wdlJk=
|
||||||
github.com/amnezia-vpn/amneziawg-go v0.2.16 h1:XY6HOq/xtqH8ZXMncRWkjFs85EKdN10NLNnw23kTpE0=
|
|
||||||
github.com/amnezia-vpn/amneziawg-go v0.2.16/go.mod h1:nRkPpIzjCxMW8pZKXTRkpqAQVlmFJdVOGkeQSC7wbms=
|
|
||||||
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.3.4 h1:9i7WNl/ctd9OEAOaTfLy//Wrlfxq/tRQ7v4okYFN9Ys=
|
github.com/breml/rootcerts v0.3.3 h1://GnaRtQ/9BY2+GtMk2wtWxVdCRysiaPr5/xBwl7NKw=
|
||||||
github.com/breml/rootcerts v0.3.4/go.mod h1:S/PKh+4d1HUn4HQovEB8hPJZO6pUZYrIhmXBhsegfXw=
|
github.com/breml/rootcerts v0.3.3/go.mod h1:S/PKh+4d1HUn4HQovEB8hPJZO6pUZYrIhmXBhsegfXw=
|
||||||
github.com/bwesterb/go-ristretto v1.2.0/go.mod h1:fUIoIZaG73pV5biE2Blr2xEzDoMj7NFEuV9ekS419A0=
|
github.com/bwesterb/go-ristretto v1.2.0/go.mod h1:fUIoIZaG73pV5biE2Blr2xEzDoMj7NFEuV9ekS419A0=
|
||||||
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/cilium/ebpf v0.12.3 h1:8ht6F9MquybnY97at+VDZb3eQQr8ev79RueWeVaEcG4=
|
|
||||||
github.com/cilium/ebpf v0.12.3/go.mod h1:TctK1ivibvI3znr66ljgi4hqOT8EYQjz1KWBfb1UVgM=
|
|
||||||
github.com/cloudflare/circl v1.1.0/go.mod h1:prBCrKB9DV4poKZY1l9zBXg2QJY7mvgRvtMxxK7fi4I=
|
github.com/cloudflare/circl v1.1.0/go.mod h1:prBCrKB9DV4poKZY1l9zBXg2QJY7mvgRvtMxxK7fi4I=
|
||||||
github.com/cloudflare/circl v1.6.1 h1:zqIqSPIndyBh1bjLVVDHMPpVKqp8Su/V+6MeDzzQBQ0=
|
github.com/cloudflare/circl v1.6.1 h1:zqIqSPIndyBh1bjLVVDHMPpVKqp8Su/V+6MeDzzQBQ0=
|
||||||
github.com/cloudflare/circl v1.6.1/go.mod h1:uddAzsPgqdMAYatqJ0lsjX1oECcQLIlRpzZh3pJrofs=
|
github.com/cloudflare/circl v1.6.1/go.mod h1:uddAzsPgqdMAYatqJ0lsjX1oECcQLIlRpzZh3pJrofs=
|
||||||
github.com/cronokirby/saferith v0.33.0 h1:TgoQlfsD4LIwx71+ChfRcIpjkw+RPOapDEVxa+LhwLo=
|
github.com/cronokirby/saferith v0.33.0 h1:TgoQlfsD4LIwx71+ChfRcIpjkw+RPOapDEVxa+LhwLo=
|
||||||
github.com/cronokirby/saferith v0.33.0/go.mod h1:QKJhjoqUtBsXCAVEjw38mFqoi7DebT7kthcD7UzbnoA=
|
github.com/cronokirby/saferith v0.33.0/go.mod h1:QKJhjoqUtBsXCAVEjw38mFqoi7DebT7kthcD7UzbnoA=
|
||||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
|
||||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
github.com/fatih/color v1.18.0 h1:S8gINlzdQ840/4pfAwic/ZE0djQEH3wM94VfqLTZcOM=
|
github.com/fatih/color v1.18.0 h1:S8gINlzdQ840/4pfAwic/ZE0djQEH3wM94VfqLTZcOM=
|
||||||
github.com/fatih/color v1.18.0/go.mod h1:4FelSpRwEGDpQ12mAdzqdOukCy4u8WUtOY6lkT/6HfU=
|
github.com/fatih/color v1.18.0/go.mod h1:4FelSpRwEGDpQ12mAdzqdOukCy4u8WUtOY6lkT/6HfU=
|
||||||
github.com/golang/mock v1.6.0 h1:ErTB+efbowRARo13NNdxyJji2egdxLGQhRaY+DUumQc=
|
github.com/golang/mock v1.6.0 h1:ErTB+efbowRARo13NNdxyJji2egdxLGQhRaY+DUumQc=
|
||||||
github.com/golang/mock v1.6.0/go.mod h1:p6yTPP+5HYm5mzsMV8JkE6ZKdX+/wYM6Hr+LicevLPs=
|
github.com/golang/mock v1.6.0/go.mod h1:p6yTPP+5HYm5mzsMV8JkE6ZKdX+/wYM6Hr+LicevLPs=
|
||||||
github.com/google/btree v1.1.3 h1:CVpQJjYgC4VbzxeGVHfvZrv1ctoYCAI8vbl07Fcxlyg=
|
github.com/google/btree v1.0.1 h1:gK4Kx5IaGY9CD5sPJ36FHiBJ6ZXl0kilRiiCj+jdYp4=
|
||||||
github.com/google/btree v1.1.3/go.mod h1:qOPhT0dTNdNzV6Z/lhRX0YXUafgPLFUh+gZMl761Gm4=
|
github.com/google/btree v1.0.1/go.mod h1:xXMiIv4Fb/0kKde4SpL7qlzvu5cMJDRkFDxJfI9uaxA=
|
||||||
github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
|
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
|
||||||
github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=
|
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
||||||
github.com/jsimonetti/rtnetlink v1.4.2 h1:Df9w9TZ3npHTyDn0Ev9e1uzmN2odmXd0QX+J5GTEn90=
|
github.com/josharian/native v1.1.0 h1:uuaP0hAbW7Y4l0ZRQ6C9zfb7Mg1mbFKry/xzDAfmtLA=
|
||||||
github.com/jsimonetti/rtnetlink v1.4.2/go.mod h1:92s6LJdE+1iOrw+F2/RO7LYI2Qd8pPpFNNUYW06gcoM=
|
github.com/josharian/native v1.1.0/go.mod h1:7X/raswPFr05uY3HiLlYeyQntB6OO7E/d2Cu7qoaN2w=
|
||||||
github.com/klauspost/compress v1.18.4 h1:RPhnKRAQ4Fh8zU2FY/6ZFDwTVTxgJ/EMydqSTzE9a2c=
|
github.com/klauspost/compress v1.18.1 h1:bcSGx7UbpBqMChDtsF28Lw6v/G94LPrrbMbdC3JH2co=
|
||||||
github.com/klauspost/compress v1.18.4/go.mod h1:R0h/fSBs8DE4ENlcrlib3PsXS61voFxhIs2DeRhCvJ4=
|
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=
|
||||||
@@ -50,10 +45,10 @@ github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWE
|
|||||||
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
|
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
|
||||||
github.com/mdlayher/genetlink v1.3.2 h1:KdrNKe+CTu+IbZnm/GVUMXSqBBLqcGpRDa0xkQy56gw=
|
github.com/mdlayher/genetlink v1.3.2 h1:KdrNKe+CTu+IbZnm/GVUMXSqBBLqcGpRDa0xkQy56gw=
|
||||||
github.com/mdlayher/genetlink v1.3.2/go.mod h1:tcC3pkCrPUGIKKsCsp0B3AdaaKuHtaxoJRz3cc+528o=
|
github.com/mdlayher/genetlink v1.3.2/go.mod h1:tcC3pkCrPUGIKKsCsp0B3AdaaKuHtaxoJRz3cc+528o=
|
||||||
github.com/mdlayher/netlink v1.9.0 h1:G8+GLq2x3v4D4MVIqDdNUhTUC7TKiCy/6MDkmItfKco=
|
github.com/mdlayher/netlink v1.7.2 h1:/UtM3ofJap7Vl4QWCPDGXY8d3GIY2UGSDbK+QWmY8/g=
|
||||||
github.com/mdlayher/netlink v1.9.0/go.mod h1:YBnl5BXsCoRuwBjKKlZ+aYmEoq0r12FDA/3JC+94KDg=
|
github.com/mdlayher/netlink v1.7.2/go.mod h1:xraEF7uJbxLhc5fpHL4cPe221LI2bdttWlU+ZGLfQSw=
|
||||||
github.com/mdlayher/socket v0.5.1 h1:VZaqt6RkGkt2OE9l3GcC6nZkqD3xKeQLyfleW/uBcos=
|
github.com/mdlayher/socket v0.4.1 h1:eM9y2/jlbs1M615oshPQOHZzj6R6wMT7bX5NPiQvn2U=
|
||||||
github.com/mdlayher/socket v0.5.1/go.mod h1:TjPLHI1UgwEv5J1B5q0zTZq12A/6H7nKmtTanQE37IQ=
|
github.com/mdlayher/socket v0.4.1/go.mod h1:cAqeGjoufqdxWkD7DkpyS+wcefOtmu5OQ8KuoJGIReA=
|
||||||
github.com/miekg/dns v1.1.62 h1:cN8OuEF1/x5Rq6Np+h1epln8OiyPWV+lROx9LxcGgIQ=
|
github.com/miekg/dns v1.1.62 h1:cN8OuEF1/x5Rq6Np+h1epln8OiyPWV+lROx9LxcGgIQ=
|
||||||
github.com/miekg/dns v1.1.62/go.mod h1:mvDlcItzm+br7MToIKqkglaGhlFMHJ9DTNNWONWXbNQ=
|
github.com/miekg/dns v1.1.62/go.mod h1:mvDlcItzm+br7MToIKqkglaGhlFMHJ9DTNNWONWXbNQ=
|
||||||
github.com/mikioh/ipaddr v0.0.0-20190404000644-d465c8ab6721 h1:RlZweED6sbSArvlE924+mUcZuXKLBHA35U7LN621Bws=
|
github.com/mikioh/ipaddr v0.0.0-20190404000644-d465c8ab6721 h1:RlZweED6sbSArvlE924+mUcZuXKLBHA35U7LN621Bws=
|
||||||
@@ -74,16 +69,16 @@ 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-rc9.0.20260421173011-9de8e7fdbe3a h1:TE157yPQmAbVruH0MWCQzs0vTT/6t96DkoWUXd6PVuc=
|
github.com/qdm12/dns/v2 v2.0.0-rc10 h1:IyeNEYXfhBsaE1dwxx5eAqdAz1HS98dT+8c7xoKODa0=
|
||||||
github.com/qdm12/dns/v2 v2.0.0-rc9.0.20260421173011-9de8e7fdbe3a/go.mod h1:98foWgXJZ+g8gJIuO+fdO+oWpFei5WShMFTeN4Im2lE=
|
github.com/qdm12/dns/v2 v2.0.0-rc10/go.mod h1:98foWgXJZ+g8gJIuO+fdO+oWpFei5WShMFTeN4Im2lE=
|
||||||
github.com/qdm12/goservices v0.1.1-0.20251104135713-6bee97bd4978 h1:TRGpCU1l0lNwtogEUSs5U+RFceYxkAJUmrGabno7J5c=
|
github.com/qdm12/goservices v0.1.1-0.20251104135713-6bee97bd4978 h1:TRGpCU1l0lNwtogEUSs5U+RFceYxkAJUmrGabno7J5c=
|
||||||
github.com/qdm12/goservices v0.1.1-0.20251104135713-6bee97bd4978/go.mod h1:D1Po4CRQLYjccnAR2JsVlN1sBMgQrcNLONbvyuzcdTg=
|
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=
|
||||||
github.com/qdm12/goshutdown v0.3.0/go.mod h1:EqZ46No00kCTZ5qzdd3qIzY6ayhMt24QI8Mh8LVQYmM=
|
github.com/qdm12/goshutdown v0.3.0/go.mod h1:EqZ46No00kCTZ5qzdd3qIzY6ayhMt24QI8Mh8LVQYmM=
|
||||||
github.com/qdm12/gosplash v0.2.1-0.20260305164749-b713de4fee6c h1:l8qz53IqEXRGK0X62gWwipG077Fz5eNM7qe4mUbAr/Q=
|
github.com/qdm12/gosplash v0.2.0 h1:DOxCEizbW6ZG+FgpH2oK1atT6bM8MHL9GZ2ywSS4zZY=
|
||||||
github.com/qdm12/gosplash v0.2.1-0.20260305164749-b713de4fee6c/go.mod h1:vgRg8Skq9+RNp1THecwMI7SGsnIwO/NPMfYenNTgpAc=
|
github.com/qdm12/gosplash v0.2.0/go.mod h1:k+1PzhO0th9cpX4q2Nneu4xTsndXqrM/x7NTIYmJ4jo=
|
||||||
github.com/qdm12/gotree v0.3.0 h1:Q9f4C571EFK7ZEsPkEL2oGZX7I+ZhVxhh1ZSydW+5yI=
|
github.com/qdm12/gotree v0.3.0 h1:Q9f4C571EFK7ZEsPkEL2oGZX7I+ZhVxhh1ZSydW+5yI=
|
||||||
github.com/qdm12/gotree v0.3.0/go.mod h1:iz06uXmRR4Aq9v6tX7mosXStO/yGHxRA1hbyD0UVeYw=
|
github.com/qdm12/gotree v0.3.0/go.mod h1:iz06uXmRR4Aq9v6tX7mosXStO/yGHxRA1hbyD0UVeYw=
|
||||||
github.com/qdm12/log v0.1.0 h1:jYBd/xscHYpblzZAd2kjZp2YmuYHjAAfbTViJWxoPTw=
|
github.com/qdm12/log v0.1.0 h1:jYBd/xscHYpblzZAd2kjZp2YmuYHjAAfbTViJWxoPTw=
|
||||||
@@ -94,39 +89,32 @@ 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/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
|
||||||
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
|
|
||||||
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
|
|
||||||
github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA=
|
|
||||||
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
|
||||||
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
|
|
||||||
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
|
|
||||||
github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U=
|
github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U=
|
||||||
github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=
|
github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=
|
||||||
github.com/ti-mo/netfilter v0.5.3 h1:ikzduvnaUMwre5bhbNwWOd6bjqLMVb33vv0XXbK0xGQ=
|
|
||||||
github.com/ti-mo/netfilter v0.5.3/go.mod h1:08SyBCg6hu1qyQk4s3DjjJKNrm3RTb32nm6AzyT972E=
|
|
||||||
github.com/ulikunitz/xz v0.5.15 h1:9DNdB5s+SgV3bQ2ApL10xRc35ck0DuIX/isZvIk+ubY=
|
github.com/ulikunitz/xz v0.5.15 h1:9DNdB5s+SgV3bQ2ApL10xRc35ck0DuIX/isZvIk+ubY=
|
||||||
github.com/ulikunitz/xz v0.5.15/go.mod h1:nbz6k7qbPmH4IRqmfOplQw/tblSgqTqBwxkY0oWt/14=
|
github.com/ulikunitz/xz v0.5.15/go.mod h1:nbz6k7qbPmH4IRqmfOplQw/tblSgqTqBwxkY0oWt/14=
|
||||||
|
github.com/vishvananda/netlink v1.3.1 h1:3AEMt62VKqz90r0tmNhog0r/PpWKmrEShJU0wJW6bV0=
|
||||||
|
github.com/vishvananda/netlink v1.3.1/go.mod h1:ARtKouGSTGchR8aMwmkzC0qiNPrrWO5JS/XMVl45+b4=
|
||||||
|
github.com/vishvananda/netns v0.0.5 h1:DfiHV+j8bA32MFM7bfEunvT8IAqQ/NzSJHtcmW5zdEY=
|
||||||
|
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=
|
||||||
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
|
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
|
||||||
go.uber.org/atomic v1.11.0 h1:ZvwS0R+56ePWxUNi+Atn9dWONBPp/AUETXlHW0DxSjE=
|
|
||||||
go.uber.org/atomic v1.11.0/go.mod h1:LUxbIzbOniOlMKjJjyPfpl4v+PKK2cNJn91OQbhoJI0=
|
|
||||||
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.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
||||||
golang.org/x/crypto v0.7.0/go.mod h1:pYwdfH91IfpZVANVyUOhSIPZaFoJGxTFbZhFTx+dXZU=
|
golang.org/x/crypto v0.7.0/go.mod h1:pYwdfH91IfpZVANVyUOhSIPZaFoJGxTFbZhFTx+dXZU=
|
||||||
golang.org/x/crypto v0.48.0 h1:/VRzVqiRSggnhY7gNRxPauEQ5Drw9haKdM0jqfcCFts=
|
golang.org/x/crypto v0.45.0 h1:jMBrvKuj23MTlT0bQEOBcAE0mjg8mK9RXFhRH6nyF3Q=
|
||||||
golang.org/x/crypto v0.48.0/go.mod h1:r0kV5h3qnFPlQnBSrULhlsRfryS2pmewsg+XfMgkVos=
|
golang.org/x/crypto v0.45.0/go.mod h1:XTGrrkGJve7CYK7J8PEww4aY7gM3qMCElcJQ8n8JdX4=
|
||||||
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.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
|
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
|
||||||
golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
|
golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
|
||||||
golang.org/x/mod v0.33.0 h1:tHFzIWbBifEmbwtGz65eaWyGiGZatSrT9prnU8DbVL8=
|
golang.org/x/mod v0.29.0 h1:HV8lRxZC4l2cr3Zq1LvtOsi/ThTgWnUk/y64QSs8GwA=
|
||||||
golang.org/x/mod v0.33.0/go.mod h1:swjeQEj+6r7fODbD2cqrnje9PnziFuw4bmLbBZFrQ5w=
|
golang.org/x/mod v0.29.0/go.mod h1:NyhrlYXJ2H4eJiRy/WDBO6HMqZQ6q9nk4JzS3NuCK+w=
|
||||||
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-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
||||||
@@ -134,14 +122,14 @@ golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96b
|
|||||||
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
|
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
|
||||||
golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
|
golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
|
||||||
golang.org/x/net v0.8.0/go.mod h1:QVkue5JL9kW//ek3r6jTKnTFis1tRmNAW2P1shuFdJc=
|
golang.org/x/net v0.8.0/go.mod h1:QVkue5JL9kW//ek3r6jTKnTFis1tRmNAW2P1shuFdJc=
|
||||||
golang.org/x/net v0.51.0 h1:94R/GTO7mt3/4wIKpcR5gkGmRLOuE/2hNGeWq/GBIFo=
|
golang.org/x/net v0.47.0 h1:Mx+4dIFzqraBXUugkia1OOvlD6LemFo1ALMHjrXDOhY=
|
||||||
golang.org/x/net v0.51.0/go.mod h1:aamm+2QF5ogm02fjy5Bb7CQ0WMt1/WVM7FtyaTLlA9Y=
|
golang.org/x/net v0.47.0/go.mod h1:/jNxtkgq5yWUGYkaZGqo27cfGZ1c5Nen03aYrrKpVRU=
|
||||||
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.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
golang.org/x/sync v0.20.0 h1:e0PTpb7pjO8GAtTs2dQ6jYa5BWYlMuX047Dco/pItO4=
|
golang.org/x/sync v0.18.0 h1:kr88TuHDroi+UVf+0hZnirlk8o8T+4MrK6mr60WkH/I=
|
||||||
golang.org/x/sync v0.20.0/go.mod h1:9xrNwdLfx4jkKbNva9FpL6vEN7evnE43NNNJQ2LF3+0=
|
golang.org/x/sync v0.18.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=
|
||||||
@@ -152,10 +140,12 @@ golang.org/x/sys v0.0.0-20211007075335-d3039528d8ac/go.mod h1:oPkhp1MJrh7nUepCBc
|
|||||||
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
|
golang.org/x/sys v0.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.5.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.42.0 h1:omrd2nAlyT5ESRdCLYdm3+fMfNFE/+Rf4bDIQImRJeo=
|
golang.org/x/sys v0.10.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.42.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw=
|
golang.org/x/sys v0.38.0 h1:3yZWxaJjBmCWXqhN1qh02AkOnCQ1poK6oF+a7xWL6Gc=
|
||||||
|
golang.org/x/sys v0.38.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/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
||||||
golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
|
golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
|
||||||
@@ -165,17 +155,17 @@ golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
|||||||
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
|
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
|
||||||
golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
|
golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
|
||||||
golang.org/x/text v0.8.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
|
golang.org/x/text v0.8.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
|
||||||
golang.org/x/text v0.35.0 h1:JOVx6vVDFokkpaq1AEptVzLTpDe9KGpj5tR4/X+ybL8=
|
golang.org/x/text v0.31.0 h1:aC8ghyu4JhP8VojJ2lEHBnochRno1sgL6nEi9WGFGMM=
|
||||||
golang.org/x/text v0.35.0/go.mod h1:khi/HExzZJ2pGnjenulevKNX1W67CUy0AsXcNubPGCA=
|
golang.org/x/text v0.31.0/go.mod h1:tKRAlv61yKIjGGHX/4tP1LTbc13YSec1pxVEWXzfoeM=
|
||||||
golang.org/x/time v0.9.0 h1:EsRrnYcQiGH+5FfbgvV4AP7qEZstoyrHB0DzarOQ4ZY=
|
golang.org/x/time v0.3.0 h1:rg5rLMjNzMS1RkNLzCG38eapWhnYLFYXDXj2gOlr8j4=
|
||||||
golang.org/x/time v0.9.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM=
|
golang.org/x/time v0.3.0/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.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
|
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
|
||||||
golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=
|
golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=
|
||||||
golang.org/x/tools v0.42.0 h1:uNgphsn75Tdz5Ji2q36v/nsFSfR/9BRFvqhGBaJGd5k=
|
golang.org/x/tools v0.38.0 h1:Hx2Xv8hISq8Lm16jvBZ2VQf+RLmbd7wVUsALibYI/IQ=
|
||||||
golang.org/x/tools v0.42.0/go.mod h1:Ma6lCIwGZvHK6XtgbswSoWroEkhugApmsXyrUmBhfr0=
|
golang.org/x/tools v0.38.0/go.mod h1:yEsQ/d/YK8cjh0L6rZlY8tgtlKiBNTL14pGDJPJpYQs=
|
||||||
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=
|
||||||
@@ -190,13 +180,12 @@ google.golang.org/protobuf v1.35.1/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojt
|
|||||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||||
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
|
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
|
||||||
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
|
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
|
||||||
gopkg.in/ini.v1 v1.67.1 h1:tVBILHy0R6e4wkYOn3XmiITt/hEVH4TFMYvAX2Ytz6k=
|
gopkg.in/ini.v1 v1.67.0 h1:Dgnx+6+nfE+IfzjUEISNeydPJh9AXNNsWbGP9KzCsOA=
|
||||||
gopkg.in/ini.v1 v1.67.1/go.mod h1:x/cyOwCgZqOkJoDIJ3c1KNHMo10+nLGAhh+kn3Zizss=
|
gopkg.in/ini.v1 v1.67.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
|
||||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
|
||||||
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||||
gvisor.dev/gvisor v0.0.0-20231202080848-1f7806d17489 h1:ze1vwAdliUAr68RQ5NtufWaXaOg8WUO2OACzEV+TNdE=
|
gvisor.dev/gvisor v0.0.0-20230927004350-cbd86285d259 h1:TbRPT0HtzFP3Cno1zZo7yPzEEnfu8EjLfl6IU9VfqkQ=
|
||||||
gvisor.dev/gvisor v0.0.0-20231202080848-1f7806d17489/go.mod h1:10sU+Uh5KKNv1+2x2A0Gvzt8FjD3ASIhorV3YsauXhk=
|
gvisor.dev/gvisor v0.0.0-20230927004350-cbd86285d259/go.mod h1:AVgIgHMwK63XvmAzWG9vLQ41YnVHN0du0tEC46fI7yY=
|
||||||
kernel.org/pub/linux/libs/security/libcap/cap v1.2.70 h1:QnLPkuDWWbD5C+3DUA2IUXai5TK6w2zff+MAGccqdsw=
|
kernel.org/pub/linux/libs/security/libcap/cap v1.2.70 h1:QnLPkuDWWbD5C+3DUA2IUXai5TK6w2zff+MAGccqdsw=
|
||||||
kernel.org/pub/linux/libs/security/libcap/cap v1.2.70/go.mod h1:/iBwcj9nbLejQitYvUm9caurITQ6WyNHibJk6Q9fiS4=
|
kernel.org/pub/linux/libs/security/libcap/cap v1.2.70/go.mod h1:/iBwcj9nbLejQitYvUm9caurITQ6WyNHibJk6Q9fiS4=
|
||||||
kernel.org/pub/linux/libs/security/libcap/psx v1.2.70 h1:HsB2G/rEQiYyo1bGoQqHZ/Bvd6x1rERQTNdPr1FyWjI=
|
kernel.org/pub/linux/libs/security/libcap/psx v1.2.70 h1:HsB2G/rEQiYyo1bGoQqHZ/Bvd6x1rERQTNdPr1FyWjI=
|
||||||
|
|||||||
@@ -1,22 +0,0 @@
|
|||||||
package amneziawg
|
|
||||||
|
|
||||||
type Amneziawg struct {
|
|
||||||
logger Logger
|
|
||||||
settings Settings
|
|
||||||
netlink NetLinker
|
|
||||||
}
|
|
||||||
|
|
||||||
func New(settings Settings, netlink NetLinker,
|
|
||||||
logger Logger,
|
|
||||||
) (a *Amneziawg, err error) {
|
|
||||||
settings.SetDefaults()
|
|
||||||
if err := settings.Check(); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return &Amneziawg{
|
|
||||||
logger: logger,
|
|
||||||
settings: settings,
|
|
||||||
netlink: netlink,
|
|
||||||
}, nil
|
|
||||||
}
|
|
||||||
@@ -1,86 +0,0 @@
|
|||||||
package amneziawg
|
|
||||||
|
|
||||||
import (
|
|
||||||
"net/netip"
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
"github.com/qdm12/gluetun/internal/wireguard"
|
|
||||||
"github.com/stretchr/testify/assert"
|
|
||||||
"github.com/stretchr/testify/require"
|
|
||||||
"golang.zx2c4.com/wireguard/device"
|
|
||||||
)
|
|
||||||
|
|
||||||
func Test_New(t *testing.T) {
|
|
||||||
t.Parallel()
|
|
||||||
|
|
||||||
const validKeyString = "oMNSf/zJ0pt1ciy+qIRk8Rlyfs9accwuRLnKd85Yl1Q="
|
|
||||||
logger := NewMockLogger(nil)
|
|
||||||
netLinker := NewMockNetLinker(nil)
|
|
||||||
|
|
||||||
testCases := map[string]struct {
|
|
||||||
settings Settings
|
|
||||||
amneziawg *Amneziawg
|
|
||||||
err error
|
|
||||||
}{
|
|
||||||
"bad_settings": {
|
|
||||||
settings: Settings{
|
|
||||||
Wireguard: wireguard.Settings{
|
|
||||||
PrivateKey: "",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
err: wireguard.ErrPrivateKeyMissing,
|
|
||||||
},
|
|
||||||
"minimal valid settings": {
|
|
||||||
settings: Settings{
|
|
||||||
Wireguard: wireguard.Settings{
|
|
||||||
PrivateKey: validKeyString,
|
|
||||||
PublicKey: validKeyString,
|
|
||||||
Endpoint: netip.AddrPortFrom(netip.AddrFrom4([4]byte{1, 2, 3, 4}), 0),
|
|
||||||
Addresses: []netip.Prefix{
|
|
||||||
netip.PrefixFrom(netip.AddrFrom4([4]byte{5, 6, 7, 8}), 32),
|
|
||||||
},
|
|
||||||
FirewallMark: 100,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
amneziawg: &Amneziawg{
|
|
||||||
logger: logger,
|
|
||||||
netlink: netLinker,
|
|
||||||
settings: Settings{
|
|
||||||
Wireguard: wireguard.Settings{
|
|
||||||
InterfaceName: "wg0",
|
|
||||||
PrivateKey: validKeyString,
|
|
||||||
PublicKey: validKeyString,
|
|
||||||
Endpoint: netip.AddrPortFrom(netip.AddrFrom4([4]byte{1, 2, 3, 4}), 51820),
|
|
||||||
Addresses: []netip.Prefix{
|
|
||||||
netip.PrefixFrom(netip.AddrFrom4([4]byte{5, 6, 7, 8}), 32),
|
|
||||||
},
|
|
||||||
AllowedIPs: []netip.Prefix{
|
|
||||||
netip.MustParsePrefix("0.0.0.0/0"),
|
|
||||||
},
|
|
||||||
FirewallMark: 100,
|
|
||||||
MTU: device.DefaultMTU,
|
|
||||||
IPv6: ptrTo(false),
|
|
||||||
Implementation: "auto",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
for name, testCase := range testCases {
|
|
||||||
t.Run(name, func(t *testing.T) {
|
|
||||||
t.Parallel()
|
|
||||||
|
|
||||||
wireguard, err := New(testCase.settings, netLinker, logger)
|
|
||||||
|
|
||||||
if testCase.err != nil {
|
|
||||||
require.Error(t, err)
|
|
||||||
assert.Equal(t, testCase.err.Error(), err.Error())
|
|
||||||
} else {
|
|
||||||
assert.NoError(t, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
assert.Equal(t, testCase.amneziawg, wireguard)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,5 +0,0 @@
|
|||||||
package amneziawg
|
|
||||||
|
|
||||||
func ptrTo[T any](v T) *T {
|
|
||||||
return &v
|
|
||||||
}
|
|
||||||
@@ -1,11 +0,0 @@
|
|||||||
package amneziawg
|
|
||||||
|
|
||||||
//go:generate mockgen -destination=log_mock_test.go -package amneziawg . Logger
|
|
||||||
|
|
||||||
type Logger interface {
|
|
||||||
Debug(s string)
|
|
||||||
Debugf(format string, args ...interface{})
|
|
||||||
Info(s string)
|
|
||||||
Error(s string)
|
|
||||||
Errorf(format string, args ...interface{})
|
|
||||||
}
|
|
||||||
@@ -1,104 +0,0 @@
|
|||||||
// Code generated by MockGen. DO NOT EDIT.
|
|
||||||
// Source: github.com/qdm12/gluetun/internal/amneziawg (interfaces: Logger)
|
|
||||||
|
|
||||||
// Package amneziawg is a generated GoMock package.
|
|
||||||
package amneziawg
|
|
||||||
|
|
||||||
import (
|
|
||||||
reflect "reflect"
|
|
||||||
|
|
||||||
gomock "github.com/golang/mock/gomock"
|
|
||||||
)
|
|
||||||
|
|
||||||
// MockLogger is a mock of Logger interface.
|
|
||||||
type MockLogger struct {
|
|
||||||
ctrl *gomock.Controller
|
|
||||||
recorder *MockLoggerMockRecorder
|
|
||||||
}
|
|
||||||
|
|
||||||
// MockLoggerMockRecorder is the mock recorder for MockLogger.
|
|
||||||
type MockLoggerMockRecorder struct {
|
|
||||||
mock *MockLogger
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewMockLogger creates a new mock instance.
|
|
||||||
func NewMockLogger(ctrl *gomock.Controller) *MockLogger {
|
|
||||||
mock := &MockLogger{ctrl: ctrl}
|
|
||||||
mock.recorder = &MockLoggerMockRecorder{mock}
|
|
||||||
return mock
|
|
||||||
}
|
|
||||||
|
|
||||||
// EXPECT returns an object that allows the caller to indicate expected use.
|
|
||||||
func (m *MockLogger) EXPECT() *MockLoggerMockRecorder {
|
|
||||||
return m.recorder
|
|
||||||
}
|
|
||||||
|
|
||||||
// Debug mocks base method.
|
|
||||||
func (m *MockLogger) Debug(arg0 string) {
|
|
||||||
m.ctrl.T.Helper()
|
|
||||||
m.ctrl.Call(m, "Debug", arg0)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Debug indicates an expected call of Debug.
|
|
||||||
func (mr *MockLoggerMockRecorder) Debug(arg0 interface{}) *gomock.Call {
|
|
||||||
mr.mock.ctrl.T.Helper()
|
|
||||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Debug", reflect.TypeOf((*MockLogger)(nil).Debug), arg0)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Debugf mocks base method.
|
|
||||||
func (m *MockLogger) Debugf(arg0 string, arg1 ...interface{}) {
|
|
||||||
m.ctrl.T.Helper()
|
|
||||||
varargs := []interface{}{arg0}
|
|
||||||
for _, a := range arg1 {
|
|
||||||
varargs = append(varargs, a)
|
|
||||||
}
|
|
||||||
m.ctrl.Call(m, "Debugf", varargs...)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Debugf indicates an expected call of Debugf.
|
|
||||||
func (mr *MockLoggerMockRecorder) Debugf(arg0 interface{}, arg1 ...interface{}) *gomock.Call {
|
|
||||||
mr.mock.ctrl.T.Helper()
|
|
||||||
varargs := append([]interface{}{arg0}, arg1...)
|
|
||||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Debugf", reflect.TypeOf((*MockLogger)(nil).Debugf), varargs...)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Error mocks base method.
|
|
||||||
func (m *MockLogger) Error(arg0 string) {
|
|
||||||
m.ctrl.T.Helper()
|
|
||||||
m.ctrl.Call(m, "Error", arg0)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Error indicates an expected call of Error.
|
|
||||||
func (mr *MockLoggerMockRecorder) Error(arg0 interface{}) *gomock.Call {
|
|
||||||
mr.mock.ctrl.T.Helper()
|
|
||||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Error", reflect.TypeOf((*MockLogger)(nil).Error), arg0)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Errorf mocks base method.
|
|
||||||
func (m *MockLogger) Errorf(arg0 string, arg1 ...interface{}) {
|
|
||||||
m.ctrl.T.Helper()
|
|
||||||
varargs := []interface{}{arg0}
|
|
||||||
for _, a := range arg1 {
|
|
||||||
varargs = append(varargs, a)
|
|
||||||
}
|
|
||||||
m.ctrl.Call(m, "Errorf", varargs...)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Errorf indicates an expected call of Errorf.
|
|
||||||
func (mr *MockLoggerMockRecorder) Errorf(arg0 interface{}, arg1 ...interface{}) *gomock.Call {
|
|
||||||
mr.mock.ctrl.T.Helper()
|
|
||||||
varargs := append([]interface{}{arg0}, arg1...)
|
|
||||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Errorf", reflect.TypeOf((*MockLogger)(nil).Errorf), varargs...)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Info mocks base method.
|
|
||||||
func (m *MockLogger) Info(arg0 string) {
|
|
||||||
m.ctrl.T.Helper()
|
|
||||||
m.ctrl.Call(m, "Info", arg0)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Info indicates an expected call of Info.
|
|
||||||
func (mr *MockLoggerMockRecorder) Info(arg0 interface{}) *gomock.Call {
|
|
||||||
mr.mock.ctrl.T.Helper()
|
|
||||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Info", reflect.TypeOf((*MockLogger)(nil).Info), arg0)
|
|
||||||
}
|
|
||||||
@@ -1,36 +0,0 @@
|
|||||||
package amneziawg
|
|
||||||
|
|
||||||
import (
|
|
||||||
"net/netip"
|
|
||||||
|
|
||||||
"github.com/qdm12/gluetun/internal/netlink"
|
|
||||||
)
|
|
||||||
|
|
||||||
//go:generate mockgen -destination=netlinker_mock_test.go -package amneziawg . NetLinker
|
|
||||||
|
|
||||||
type NetLinker interface {
|
|
||||||
AddrReplace(linkIndex uint32, addr netip.Prefix) error
|
|
||||||
Router
|
|
||||||
Ruler
|
|
||||||
Linker
|
|
||||||
IsWireguardSupported() (ok bool, err error)
|
|
||||||
}
|
|
||||||
|
|
||||||
type Router interface {
|
|
||||||
RouteList(family uint8) (routes []netlink.Route, err error)
|
|
||||||
RouteAdd(route netlink.Route) error
|
|
||||||
}
|
|
||||||
|
|
||||||
type Ruler interface {
|
|
||||||
RuleAdd(rule netlink.Rule) error
|
|
||||||
RuleDel(rule netlink.Rule) error
|
|
||||||
}
|
|
||||||
|
|
||||||
type Linker interface {
|
|
||||||
LinkAdd(link netlink.Link) (linkIndex uint32, err error)
|
|
||||||
LinkList() (links []netlink.Link, err error)
|
|
||||||
LinkByName(name string) (link netlink.Link, err error)
|
|
||||||
LinkSetUp(linkIndex uint32) error
|
|
||||||
LinkSetDown(linkIndex uint32) error
|
|
||||||
LinkDel(linkIndex uint32) error
|
|
||||||
}
|
|
||||||
@@ -1,209 +0,0 @@
|
|||||||
// Code generated by MockGen. DO NOT EDIT.
|
|
||||||
// Source: github.com/qdm12/gluetun/internal/amneziawg (interfaces: NetLinker)
|
|
||||||
|
|
||||||
// Package amneziawg is a generated GoMock package.
|
|
||||||
package amneziawg
|
|
||||||
|
|
||||||
import (
|
|
||||||
netip "net/netip"
|
|
||||||
reflect "reflect"
|
|
||||||
|
|
||||||
gomock "github.com/golang/mock/gomock"
|
|
||||||
netlink "github.com/qdm12/gluetun/internal/netlink"
|
|
||||||
)
|
|
||||||
|
|
||||||
// MockNetLinker is a mock of NetLinker interface.
|
|
||||||
type MockNetLinker struct {
|
|
||||||
ctrl *gomock.Controller
|
|
||||||
recorder *MockNetLinkerMockRecorder
|
|
||||||
}
|
|
||||||
|
|
||||||
// MockNetLinkerMockRecorder is the mock recorder for MockNetLinker.
|
|
||||||
type MockNetLinkerMockRecorder struct {
|
|
||||||
mock *MockNetLinker
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewMockNetLinker creates a new mock instance.
|
|
||||||
func NewMockNetLinker(ctrl *gomock.Controller) *MockNetLinker {
|
|
||||||
mock := &MockNetLinker{ctrl: ctrl}
|
|
||||||
mock.recorder = &MockNetLinkerMockRecorder{mock}
|
|
||||||
return mock
|
|
||||||
}
|
|
||||||
|
|
||||||
// EXPECT returns an object that allows the caller to indicate expected use.
|
|
||||||
func (m *MockNetLinker) EXPECT() *MockNetLinkerMockRecorder {
|
|
||||||
return m.recorder
|
|
||||||
}
|
|
||||||
|
|
||||||
// AddrReplace mocks base method.
|
|
||||||
func (m *MockNetLinker) AddrReplace(arg0 uint32, arg1 netip.Prefix) error {
|
|
||||||
m.ctrl.T.Helper()
|
|
||||||
ret := m.ctrl.Call(m, "AddrReplace", arg0, arg1)
|
|
||||||
ret0, _ := ret[0].(error)
|
|
||||||
return ret0
|
|
||||||
}
|
|
||||||
|
|
||||||
// AddrReplace indicates an expected call of AddrReplace.
|
|
||||||
func (mr *MockNetLinkerMockRecorder) AddrReplace(arg0, arg1 interface{}) *gomock.Call {
|
|
||||||
mr.mock.ctrl.T.Helper()
|
|
||||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "AddrReplace", reflect.TypeOf((*MockNetLinker)(nil).AddrReplace), arg0, arg1)
|
|
||||||
}
|
|
||||||
|
|
||||||
// IsWireguardSupported mocks base method.
|
|
||||||
func (m *MockNetLinker) IsWireguardSupported() (bool, error) {
|
|
||||||
m.ctrl.T.Helper()
|
|
||||||
ret := m.ctrl.Call(m, "IsWireguardSupported")
|
|
||||||
ret0, _ := ret[0].(bool)
|
|
||||||
ret1, _ := ret[1].(error)
|
|
||||||
return ret0, ret1
|
|
||||||
}
|
|
||||||
|
|
||||||
// IsWireguardSupported indicates an expected call of IsWireguardSupported.
|
|
||||||
func (mr *MockNetLinkerMockRecorder) IsWireguardSupported() *gomock.Call {
|
|
||||||
mr.mock.ctrl.T.Helper()
|
|
||||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "IsWireguardSupported", reflect.TypeOf((*MockNetLinker)(nil).IsWireguardSupported))
|
|
||||||
}
|
|
||||||
|
|
||||||
// LinkAdd mocks base method.
|
|
||||||
func (m *MockNetLinker) LinkAdd(arg0 netlink.Link) (uint32, error) {
|
|
||||||
m.ctrl.T.Helper()
|
|
||||||
ret := m.ctrl.Call(m, "LinkAdd", arg0)
|
|
||||||
ret0, _ := ret[0].(uint32)
|
|
||||||
ret1, _ := ret[1].(error)
|
|
||||||
return ret0, ret1
|
|
||||||
}
|
|
||||||
|
|
||||||
// LinkAdd indicates an expected call of LinkAdd.
|
|
||||||
func (mr *MockNetLinkerMockRecorder) LinkAdd(arg0 interface{}) *gomock.Call {
|
|
||||||
mr.mock.ctrl.T.Helper()
|
|
||||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "LinkAdd", reflect.TypeOf((*MockNetLinker)(nil).LinkAdd), arg0)
|
|
||||||
}
|
|
||||||
|
|
||||||
// LinkByName mocks base method.
|
|
||||||
func (m *MockNetLinker) LinkByName(arg0 string) (netlink.Link, error) {
|
|
||||||
m.ctrl.T.Helper()
|
|
||||||
ret := m.ctrl.Call(m, "LinkByName", arg0)
|
|
||||||
ret0, _ := ret[0].(netlink.Link)
|
|
||||||
ret1, _ := ret[1].(error)
|
|
||||||
return ret0, ret1
|
|
||||||
}
|
|
||||||
|
|
||||||
// LinkByName indicates an expected call of LinkByName.
|
|
||||||
func (mr *MockNetLinkerMockRecorder) LinkByName(arg0 interface{}) *gomock.Call {
|
|
||||||
mr.mock.ctrl.T.Helper()
|
|
||||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "LinkByName", reflect.TypeOf((*MockNetLinker)(nil).LinkByName), arg0)
|
|
||||||
}
|
|
||||||
|
|
||||||
// LinkDel mocks base method.
|
|
||||||
func (m *MockNetLinker) LinkDel(arg0 uint32) error {
|
|
||||||
m.ctrl.T.Helper()
|
|
||||||
ret := m.ctrl.Call(m, "LinkDel", arg0)
|
|
||||||
ret0, _ := ret[0].(error)
|
|
||||||
return ret0
|
|
||||||
}
|
|
||||||
|
|
||||||
// LinkDel indicates an expected call of LinkDel.
|
|
||||||
func (mr *MockNetLinkerMockRecorder) LinkDel(arg0 interface{}) *gomock.Call {
|
|
||||||
mr.mock.ctrl.T.Helper()
|
|
||||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "LinkDel", reflect.TypeOf((*MockNetLinker)(nil).LinkDel), arg0)
|
|
||||||
}
|
|
||||||
|
|
||||||
// LinkList mocks base method.
|
|
||||||
func (m *MockNetLinker) LinkList() ([]netlink.Link, error) {
|
|
||||||
m.ctrl.T.Helper()
|
|
||||||
ret := m.ctrl.Call(m, "LinkList")
|
|
||||||
ret0, _ := ret[0].([]netlink.Link)
|
|
||||||
ret1, _ := ret[1].(error)
|
|
||||||
return ret0, ret1
|
|
||||||
}
|
|
||||||
|
|
||||||
// LinkList indicates an expected call of LinkList.
|
|
||||||
func (mr *MockNetLinkerMockRecorder) LinkList() *gomock.Call {
|
|
||||||
mr.mock.ctrl.T.Helper()
|
|
||||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "LinkList", reflect.TypeOf((*MockNetLinker)(nil).LinkList))
|
|
||||||
}
|
|
||||||
|
|
||||||
// LinkSetDown mocks base method.
|
|
||||||
func (m *MockNetLinker) LinkSetDown(arg0 uint32) error {
|
|
||||||
m.ctrl.T.Helper()
|
|
||||||
ret := m.ctrl.Call(m, "LinkSetDown", arg0)
|
|
||||||
ret0, _ := ret[0].(error)
|
|
||||||
return ret0
|
|
||||||
}
|
|
||||||
|
|
||||||
// LinkSetDown indicates an expected call of LinkSetDown.
|
|
||||||
func (mr *MockNetLinkerMockRecorder) LinkSetDown(arg0 interface{}) *gomock.Call {
|
|
||||||
mr.mock.ctrl.T.Helper()
|
|
||||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "LinkSetDown", reflect.TypeOf((*MockNetLinker)(nil).LinkSetDown), arg0)
|
|
||||||
}
|
|
||||||
|
|
||||||
// LinkSetUp mocks base method.
|
|
||||||
func (m *MockNetLinker) LinkSetUp(arg0 uint32) error {
|
|
||||||
m.ctrl.T.Helper()
|
|
||||||
ret := m.ctrl.Call(m, "LinkSetUp", arg0)
|
|
||||||
ret0, _ := ret[0].(error)
|
|
||||||
return ret0
|
|
||||||
}
|
|
||||||
|
|
||||||
// LinkSetUp indicates an expected call of LinkSetUp.
|
|
||||||
func (mr *MockNetLinkerMockRecorder) LinkSetUp(arg0 interface{}) *gomock.Call {
|
|
||||||
mr.mock.ctrl.T.Helper()
|
|
||||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "LinkSetUp", reflect.TypeOf((*MockNetLinker)(nil).LinkSetUp), arg0)
|
|
||||||
}
|
|
||||||
|
|
||||||
// RouteAdd mocks base method.
|
|
||||||
func (m *MockNetLinker) RouteAdd(arg0 netlink.Route) error {
|
|
||||||
m.ctrl.T.Helper()
|
|
||||||
ret := m.ctrl.Call(m, "RouteAdd", arg0)
|
|
||||||
ret0, _ := ret[0].(error)
|
|
||||||
return ret0
|
|
||||||
}
|
|
||||||
|
|
||||||
// RouteAdd indicates an expected call of RouteAdd.
|
|
||||||
func (mr *MockNetLinkerMockRecorder) RouteAdd(arg0 interface{}) *gomock.Call {
|
|
||||||
mr.mock.ctrl.T.Helper()
|
|
||||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "RouteAdd", reflect.TypeOf((*MockNetLinker)(nil).RouteAdd), arg0)
|
|
||||||
}
|
|
||||||
|
|
||||||
// RouteList mocks base method.
|
|
||||||
func (m *MockNetLinker) RouteList(arg0 byte) ([]netlink.Route, error) {
|
|
||||||
m.ctrl.T.Helper()
|
|
||||||
ret := m.ctrl.Call(m, "RouteList", arg0)
|
|
||||||
ret0, _ := ret[0].([]netlink.Route)
|
|
||||||
ret1, _ := ret[1].(error)
|
|
||||||
return ret0, ret1
|
|
||||||
}
|
|
||||||
|
|
||||||
// RouteList indicates an expected call of RouteList.
|
|
||||||
func (mr *MockNetLinkerMockRecorder) RouteList(arg0 interface{}) *gomock.Call {
|
|
||||||
mr.mock.ctrl.T.Helper()
|
|
||||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "RouteList", reflect.TypeOf((*MockNetLinker)(nil).RouteList), arg0)
|
|
||||||
}
|
|
||||||
|
|
||||||
// RuleAdd mocks base method.
|
|
||||||
func (m *MockNetLinker) RuleAdd(arg0 netlink.Rule) error {
|
|
||||||
m.ctrl.T.Helper()
|
|
||||||
ret := m.ctrl.Call(m, "RuleAdd", arg0)
|
|
||||||
ret0, _ := ret[0].(error)
|
|
||||||
return ret0
|
|
||||||
}
|
|
||||||
|
|
||||||
// RuleAdd indicates an expected call of RuleAdd.
|
|
||||||
func (mr *MockNetLinkerMockRecorder) RuleAdd(arg0 interface{}) *gomock.Call {
|
|
||||||
mr.mock.ctrl.T.Helper()
|
|
||||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "RuleAdd", reflect.TypeOf((*MockNetLinker)(nil).RuleAdd), arg0)
|
|
||||||
}
|
|
||||||
|
|
||||||
// RuleDel mocks base method.
|
|
||||||
func (m *MockNetLinker) RuleDel(arg0 netlink.Rule) error {
|
|
||||||
m.ctrl.T.Helper()
|
|
||||||
ret := m.ctrl.Call(m, "RuleDel", arg0)
|
|
||||||
ret0, _ := ret[0].(error)
|
|
||||||
return ret0
|
|
||||||
}
|
|
||||||
|
|
||||||
// RuleDel indicates an expected call of RuleDel.
|
|
||||||
func (mr *MockNetLinkerMockRecorder) RuleDel(arg0 interface{}) *gomock.Call {
|
|
||||||
mr.mock.ctrl.T.Helper()
|
|
||||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "RuleDel", reflect.TypeOf((*MockNetLinker)(nil).RuleDel), arg0)
|
|
||||||
}
|
|
||||||
@@ -1,133 +0,0 @@
|
|||||||
package amneziawg
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"errors"
|
|
||||||
"fmt"
|
|
||||||
"net"
|
|
||||||
|
|
||||||
amneziaconn "github.com/amnezia-vpn/amneziawg-go/conn"
|
|
||||||
amneziadevice "github.com/amnezia-vpn/amneziawg-go/device"
|
|
||||||
amneziatun "github.com/amnezia-vpn/amneziawg-go/tun"
|
|
||||||
"github.com/qdm12/gluetun/internal/cleanup"
|
|
||||||
"github.com/qdm12/gluetun/internal/wireguard"
|
|
||||||
)
|
|
||||||
|
|
||||||
var (
|
|
||||||
errTunNameMismatch = errors.New("TUN device name is mismatching")
|
|
||||||
errDeviceWaited = errors.New("device waited for")
|
|
||||||
)
|
|
||||||
|
|
||||||
// Run runs the amneziawg interface and waits until the context is done, then it cleans up the
|
|
||||||
// interface and returns any error that occurred during setup or waiting. It sends an error to
|
|
||||||
// waitError if any error occurs during setup or waiting, otherwise it sends nil when the context
|
|
||||||
// is done. It sends a signal to ready when the setup is complete and the interface is ready to use.
|
|
||||||
// See https://github.com/amnezia-vpn/amneziawg-go/blob/master/main.go
|
|
||||||
func (a *Amneziawg) Run(ctx context.Context, waitError chan<- error, ready chan<- struct{}) {
|
|
||||||
setup := func(ctx context.Context, cleanups *cleanup.Cleanups) (
|
|
||||||
linkIndex uint32, waitAndCleanup func() error, err error,
|
|
||||||
) {
|
|
||||||
return setupUserspace(ctx, a.settings.Wireguard.InterfaceName,
|
|
||||||
a.netlink, a.settings.Wireguard.MTU, cleanups, a.logger, a.settings)
|
|
||||||
}
|
|
||||||
|
|
||||||
wireguard.Run(ctx, waitError, ready, setup, a.settings.Wireguard, a.netlink, a.logger)
|
|
||||||
}
|
|
||||||
|
|
||||||
func setupUserspace(ctx context.Context,
|
|
||||||
interfaceName string, netLinker NetLinker, mtu uint32,
|
|
||||||
cleanups *cleanup.Cleanups, logger Logger,
|
|
||||||
settings Settings,
|
|
||||||
) (
|
|
||||||
linkIndex uint32, waitAndCleanup func() error, err error,
|
|
||||||
) {
|
|
||||||
tun, err := amneziatun.CreateTUN(interfaceName, int(mtu))
|
|
||||||
if err != nil {
|
|
||||||
return 0, nil, fmt.Errorf("creating TUN device: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
cleanups.Add("closing TUN device", 7, tun.Close)
|
|
||||||
|
|
||||||
tunName, err := tun.Name()
|
|
||||||
if err != nil {
|
|
||||||
return 0, nil, fmt.Errorf("getting created TUN device name: %w", err)
|
|
||||||
} else if tunName != interfaceName {
|
|
||||||
return 0, nil, fmt.Errorf("%w: expected %q and got %q",
|
|
||||||
errTunNameMismatch, interfaceName, tunName)
|
|
||||||
}
|
|
||||||
|
|
||||||
link, err := netLinker.LinkByName(interfaceName)
|
|
||||||
if err != nil {
|
|
||||||
return 0, nil, fmt.Errorf("finding link %s: %w", interfaceName, err)
|
|
||||||
}
|
|
||||||
cleanups.Add("deleting link", 5, func() error {
|
|
||||||
return netLinker.LinkDel(link.Index)
|
|
||||||
})
|
|
||||||
|
|
||||||
bind := amneziaconn.NewDefaultBind()
|
|
||||||
cleanups.Add("closing bind", 7, bind.Close)
|
|
||||||
|
|
||||||
deviceLogger := amneziadevice.Logger{
|
|
||||||
Verbosef: logger.Debugf,
|
|
||||||
Errorf: logger.Errorf,
|
|
||||||
}
|
|
||||||
device := amneziadevice.NewDevice(tun, bind, &deviceLogger)
|
|
||||||
|
|
||||||
cleanups.Add("closing Wireguard device", 6, func() error {
|
|
||||||
device.Close()
|
|
||||||
return nil
|
|
||||||
})
|
|
||||||
|
|
||||||
uapiFile, err := wireguard.UAPIOpen(interfaceName)
|
|
||||||
if err != nil {
|
|
||||||
return 0, nil, fmt.Errorf("opening UAPI socket: %w", err)
|
|
||||||
}
|
|
||||||
cleanups.Add("closing UAPI file", 3, uapiFile.Close)
|
|
||||||
|
|
||||||
uapiListener, err := wireguard.UAPIListen(interfaceName, uapiFile)
|
|
||||||
if err != nil {
|
|
||||||
return 0, nil, fmt.Errorf("listening on UAPI socket: %w", err)
|
|
||||||
}
|
|
||||||
cleanups.Add("closing UAPI listener", 2, uapiListener.Close)
|
|
||||||
|
|
||||||
uapiConfig := settings.uapiConfig()
|
|
||||||
err = device.IpcSet(uapiConfig)
|
|
||||||
if err != nil {
|
|
||||||
return 0, nil, fmt.Errorf("setting amneziawg uapi config: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// acceptAndHandle exits when uapiListener is closed
|
|
||||||
uapiAcceptErrorCh := make(chan error)
|
|
||||||
go acceptAndHandle(uapiListener, device, uapiAcceptErrorCh)
|
|
||||||
waitAndCleanup = func() error {
|
|
||||||
select {
|
|
||||||
case <-ctx.Done():
|
|
||||||
err = ctx.Err()
|
|
||||||
case err = <-uapiAcceptErrorCh:
|
|
||||||
close(uapiAcceptErrorCh)
|
|
||||||
case <-device.Wait():
|
|
||||||
err = errDeviceWaited
|
|
||||||
}
|
|
||||||
|
|
||||||
cleanups.Cleanup(logger)
|
|
||||||
|
|
||||||
<-uapiAcceptErrorCh // wait for acceptAndHandle to exit
|
|
||||||
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
return link.Index, waitAndCleanup, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func acceptAndHandle(uapi net.Listener, device *amneziadevice.Device,
|
|
||||||
uapiAcceptErrorCh chan<- error,
|
|
||||||
) {
|
|
||||||
for { // stopped by uapiFile.Close()
|
|
||||||
conn, err := uapi.Accept()
|
|
||||||
if err != nil {
|
|
||||||
uapiAcceptErrorCh <- err
|
|
||||||
return
|
|
||||||
}
|
|
||||||
go device.IpcHandle(conn)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,69 +0,0 @@
|
|||||||
package amneziawg
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"strings"
|
|
||||||
|
|
||||||
"github.com/qdm12/gluetun/internal/wireguard"
|
|
||||||
)
|
|
||||||
|
|
||||||
type Settings struct {
|
|
||||||
Wireguard wireguard.Settings
|
|
||||||
JunkPacketCount uint16
|
|
||||||
JunkPacketMin uint16
|
|
||||||
JunkPacketMax uint16
|
|
||||||
PaddingS1 uint16
|
|
||||||
PaddingS2 uint16
|
|
||||||
PaddingS3 uint16
|
|
||||||
PaddingS4 uint16
|
|
||||||
HeaderH1 string
|
|
||||||
HeaderH2 string
|
|
||||||
HeaderH3 string
|
|
||||||
HeaderH4 string
|
|
||||||
InitPacketI1 string
|
|
||||||
InitPacketI2 string
|
|
||||||
InitPacketI3 string
|
|
||||||
InitPacketI4 string
|
|
||||||
InitPacketI5 string
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s Settings) uapiConfig() string {
|
|
||||||
uintFields := map[string]uint16{
|
|
||||||
"jc": s.JunkPacketCount,
|
|
||||||
"jmin": s.JunkPacketMin,
|
|
||||||
"jmax": s.JunkPacketMax,
|
|
||||||
"s1": s.PaddingS1,
|
|
||||||
"s2": s.PaddingS2,
|
|
||||||
"s3": s.PaddingS3,
|
|
||||||
"s4": s.PaddingS4,
|
|
||||||
}
|
|
||||||
stringFields := map[string]string{
|
|
||||||
"h1": s.HeaderH1,
|
|
||||||
"h2": s.HeaderH2,
|
|
||||||
"h3": s.HeaderH3,
|
|
||||||
"h4": s.HeaderH4,
|
|
||||||
"i1": s.InitPacketI1,
|
|
||||||
"i2": s.InitPacketI2,
|
|
||||||
"i3": s.InitPacketI3,
|
|
||||||
"i4": s.InitPacketI4,
|
|
||||||
"i5": s.InitPacketI5,
|
|
||||||
}
|
|
||||||
lines := make([]string, 0, len(uintFields)+len(stringFields))
|
|
||||||
|
|
||||||
for key, val := range uintFields {
|
|
||||||
lines = append(lines, fmt.Sprintf("%s=%d", key, val))
|
|
||||||
}
|
|
||||||
|
|
||||||
for key, val := range stringFields {
|
|
||||||
lines = append(lines, key+"="+val)
|
|
||||||
}
|
|
||||||
return strings.Join(lines, "\n")
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *Settings) SetDefaults() {
|
|
||||||
s.Wireguard.SetDefaults()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *Settings) Check() error {
|
|
||||||
return s.Wireguard.Check()
|
|
||||||
}
|
|
||||||
@@ -1,189 +0,0 @@
|
|||||||
package boringpoll
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"fmt"
|
|
||||||
"io"
|
|
||||||
"math/rand"
|
|
||||||
"net/http"
|
|
||||||
"sync"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/qdm12/gluetun/internal/configuration/settings"
|
|
||||||
)
|
|
||||||
|
|
||||||
type BoringPoll struct {
|
|
||||||
// Injected dependencies
|
|
||||||
client *http.Client
|
|
||||||
logger Logger
|
|
||||||
|
|
||||||
// Internal state
|
|
||||||
urlToData map[string]*urlData
|
|
||||||
|
|
||||||
// Internal signals and channels
|
|
||||||
cancel context.CancelFunc
|
|
||||||
done *sync.WaitGroup
|
|
||||||
mutex sync.Mutex
|
|
||||||
}
|
|
||||||
|
|
||||||
type urlData struct{}
|
|
||||||
|
|
||||||
func New(client *http.Client, logger Logger, settings settings.BoringPoll) *BoringPoll {
|
|
||||||
urlToData := make(map[string]*urlData)
|
|
||||||
if *settings.GluetunCom {
|
|
||||||
urlToData["https://gluetun.com/wp-json"] = &urlData{}
|
|
||||||
}
|
|
||||||
return &BoringPoll{
|
|
||||||
client: client,
|
|
||||||
logger: logger,
|
|
||||||
urlToData: urlToData,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (b *BoringPoll) Start() (runError <-chan error, err error) {
|
|
||||||
b.mutex.Lock()
|
|
||||||
defer b.mutex.Unlock()
|
|
||||||
|
|
||||||
if len(b.urlToData) == 0 {
|
|
||||||
return nil, nil //nolint:nilnil
|
|
||||||
}
|
|
||||||
|
|
||||||
const minPeriod = time.Minute
|
|
||||||
const maxPeriod = 5 * time.Minute
|
|
||||||
const logEveryBytes = 100 * 1000 * 1000 // 100 IEC MB
|
|
||||||
|
|
||||||
var ready, done sync.WaitGroup
|
|
||||||
b.done = &done
|
|
||||||
ready.Add(len(b.urlToData))
|
|
||||||
done.Add(len(b.urlToData))
|
|
||||||
ctx, cancel := context.WithCancel(context.Background())
|
|
||||||
b.cancel = cancel
|
|
||||||
for url := range b.urlToData {
|
|
||||||
go func(url string) {
|
|
||||||
defer done.Done()
|
|
||||||
|
|
||||||
b.logger.Infof("running against %s periodically between %s and %s "+
|
|
||||||
"and will log every %s downloaded",
|
|
||||||
url, minPeriod, maxPeriod, byteCountSI(logEveryBytes))
|
|
||||||
totalDownloaded := uint64(0)
|
|
||||||
lastDownloaded := uint64(0)
|
|
||||||
consecutiveFails := 0
|
|
||||||
const maxConsecutiveErrs = 3
|
|
||||||
const coolDownTimeout = time.Hour
|
|
||||||
timer := time.NewTimer(time.Hour)
|
|
||||||
var err error
|
|
||||||
|
|
||||||
ready.Done()
|
|
||||||
for {
|
|
||||||
timeout := minPeriod + time.Duration(rand.Int63n(int64(maxPeriod-minPeriod))) //nolint:gosec
|
|
||||||
if consecutiveFails >= maxConsecutiveErrs {
|
|
||||||
b.logger.Debugf("pausing poll to %s for %s due to %d consecutive errors, last error: %s",
|
|
||||||
url, coolDownTimeout, consecutiveFails, err)
|
|
||||||
timeout = coolDownTimeout
|
|
||||||
}
|
|
||||||
timer.Reset(timeout)
|
|
||||||
select {
|
|
||||||
case <-ctx.Done():
|
|
||||||
timer.Stop()
|
|
||||||
totalDownloaded += lastDownloaded
|
|
||||||
if totalDownloaded > 0 {
|
|
||||||
b.logger.Infof("stopping poll to %s, downloaded %s!", url, byteCountSI(totalDownloaded))
|
|
||||||
}
|
|
||||||
return
|
|
||||||
case <-timer.C:
|
|
||||||
}
|
|
||||||
var n int64
|
|
||||||
n, err = fetchURL(ctx, b.client, url)
|
|
||||||
if err != nil {
|
|
||||||
consecutiveFails++
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
consecutiveFails = 0
|
|
||||||
totalDownloaded += uint64(n) //nolint:gosec
|
|
||||||
lastDownloaded += uint64(n) //nolint:gosec
|
|
||||||
if lastDownloaded >= logEveryBytes {
|
|
||||||
b.logger.Infof("thanks for helping! You have downloaded %s from %s so far!",
|
|
||||||
byteCountSI(totalDownloaded), url)
|
|
||||||
lastDownloaded = 0
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}(url)
|
|
||||||
}
|
|
||||||
return nil, nil //nolint:nilnil
|
|
||||||
}
|
|
||||||
|
|
||||||
func fetchURL(ctx context.Context, client *http.Client, url string) (downloaded int64, err error) {
|
|
||||||
const requestTimeout = 10 * time.Second
|
|
||||||
ctx, cancel := context.WithTimeout(ctx, requestTimeout)
|
|
||||||
defer cancel()
|
|
||||||
request, err := http.NewRequestWithContext(ctx, http.MethodGet, url, nil)
|
|
||||||
if err != nil {
|
|
||||||
cancel()
|
|
||||||
return 0, err
|
|
||||||
}
|
|
||||||
request.Header.Set("Cache-Control", "no-cache, no-store, must-revalidate")
|
|
||||||
request.Header.Set("Pragma", "no-cache")
|
|
||||||
request.Header.Set("Expires", "0")
|
|
||||||
request.Header.Set("User-Agent", getRandomUserAgent())
|
|
||||||
|
|
||||||
response, err := client.Do(request)
|
|
||||||
if err != nil {
|
|
||||||
return 0, err
|
|
||||||
}
|
|
||||||
downloaded, err = io.Copy(io.Discard, response.Body)
|
|
||||||
_ = response.Body.Close()
|
|
||||||
if err != nil {
|
|
||||||
return 0, err
|
|
||||||
}
|
|
||||||
return downloaded, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func getRandomUserAgent() string {
|
|
||||||
//nolint:lll
|
|
||||||
userAgents := [...]string{
|
|
||||||
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/121.0.0.0 Safari/537.36",
|
|
||||||
"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/121.0.0.0 Safari/537.36",
|
|
||||||
"Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/121.0.0.0 Safari/537.36",
|
|
||||||
"Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:122.0) Gecko/20100101 Firefox/122.0",
|
|
||||||
"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/17.2.1 Safari/605.1.15",
|
|
||||||
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Edge/121.0.0.0 Safari/537.36",
|
|
||||||
"Mozilla/5.0 (iPhone; CPU iPhone OS 17_2_1 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/17.2 Mobile/15E148 Safari/604.1",
|
|
||||||
"Mozilla/5.0 (iPad; CPU OS 17_2_1 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/17.2 Mobile/15E148 Safari/604.1",
|
|
||||||
"Mozilla/5.0 (Android 14; Mobile; rv:122.0) Gecko/122.0 Firefox/122.0",
|
|
||||||
"Mozilla/5.0 (Linux; Android 10; K) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/121.0.0.0 Mobile Safari/537.36",
|
|
||||||
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36 OPR/106.0.0.0",
|
|
||||||
"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36 OPR/106.0.0.0",
|
|
||||||
"Mozilla/5.0 (compatible; Googlebot/2.1; +http://www.google.com/bot.html)",
|
|
||||||
"Mozilla/5.0 (compatible; bingbot/2.0; +http://www.bing.com/bingbot.htm)",
|
|
||||||
}
|
|
||||||
return userAgents[rand.Intn(len(userAgents))] //nolint:gosec
|
|
||||||
}
|
|
||||||
|
|
||||||
func (b *BoringPoll) Stop() error {
|
|
||||||
b.mutex.Lock()
|
|
||||||
defer b.mutex.Unlock()
|
|
||||||
|
|
||||||
if b.cancel == nil {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
b.cancel()
|
|
||||||
b.done.Wait()
|
|
||||||
b.cancel = nil
|
|
||||||
b.done = nil
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func byteCountSI(b uint64) string {
|
|
||||||
const unit = 1000
|
|
||||||
if b < unit {
|
|
||||||
return fmt.Sprintf("%dB", b)
|
|
||||||
}
|
|
||||||
|
|
||||||
div, exp := uint64(unit), 0
|
|
||||||
for n := b / unit; n >= unit; n /= unit {
|
|
||||||
div *= unit
|
|
||||||
exp++
|
|
||||||
}
|
|
||||||
|
|
||||||
return fmt.Sprintf("%.1f%cB", float64(b)/float64(div), "kMGTPE"[exp])
|
|
||||||
}
|
|
||||||
@@ -1,6 +0,0 @@
|
|||||||
package boringpoll
|
|
||||||
|
|
||||||
type Logger interface {
|
|
||||||
Infof(format string, args ...any)
|
|
||||||
Debugf(format string, args ...any)
|
|
||||||
}
|
|
||||||
@@ -1,51 +0,0 @@
|
|||||||
package cleanup
|
|
||||||
|
|
||||||
import "sort"
|
|
||||||
|
|
||||||
type Cleanups []cleanup
|
|
||||||
|
|
||||||
type cleanup struct {
|
|
||||||
operation string
|
|
||||||
orderIndex uint
|
|
||||||
cleanup func() error
|
|
||||||
done bool
|
|
||||||
}
|
|
||||||
|
|
||||||
// Add adds a cleanup function to the list of cleanups, with a description of the
|
|
||||||
// operation being cleaned up, and an order index that determines the order in which
|
|
||||||
// the cleanup functions are run. The lower the order index, the earlier the cleanup
|
|
||||||
// function is run.
|
|
||||||
func (c *Cleanups) Add(operation string, orderIndex uint,
|
|
||||||
cleanupFunc func() error,
|
|
||||||
) {
|
|
||||||
closer := cleanup{
|
|
||||||
operation: operation,
|
|
||||||
orderIndex: orderIndex,
|
|
||||||
cleanup: cleanupFunc,
|
|
||||||
}
|
|
||||||
*c = append(*c, closer)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Cleanup runs the cleanup functions in the order of their orderIndex,
|
|
||||||
// and logs any error that occurs during cleanup.
|
|
||||||
// It can also be re-called in case a cleanup fails, and already cleaned up
|
|
||||||
// functions will not be re-run.
|
|
||||||
func (c *Cleanups) Cleanup(logger Logger) {
|
|
||||||
closers := *c
|
|
||||||
|
|
||||||
sort.Slice(closers, func(i, j int) bool {
|
|
||||||
return closers[i].orderIndex < closers[j].orderIndex
|
|
||||||
})
|
|
||||||
|
|
||||||
for i, closer := range closers {
|
|
||||||
if closer.done {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
closers[i].done = true
|
|
||||||
logger.Debug(closer.operation + "...")
|
|
||||||
err := closer.cleanup()
|
|
||||||
if err != nil {
|
|
||||||
logger.Error("failed " + closer.operation + ": " + err.Error())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,57 +0,0 @@
|
|||||||
package cleanup
|
|
||||||
|
|
||||||
import (
|
|
||||||
"errors"
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
"github.com/golang/mock/gomock"
|
|
||||||
"github.com/stretchr/testify/assert"
|
|
||||||
)
|
|
||||||
|
|
||||||
func Test_Cleanups(t *testing.T) {
|
|
||||||
t.Parallel()
|
|
||||||
|
|
||||||
ctrl := gomock.NewController(t)
|
|
||||||
|
|
||||||
var ACloseCalled, BCloseCalled, CCloseCalled bool
|
|
||||||
var (
|
|
||||||
AErr error
|
|
||||||
BErr = errors.New("B failed")
|
|
||||||
CErr = errors.New("C failed")
|
|
||||||
)
|
|
||||||
|
|
||||||
var cleanups Cleanups
|
|
||||||
cleanups.Add("cleaning up A", 5, func() error {
|
|
||||||
ACloseCalled = true
|
|
||||||
return AErr
|
|
||||||
})
|
|
||||||
|
|
||||||
cleanups.Add("cleaning up B", 3, func() error {
|
|
||||||
BCloseCalled = true
|
|
||||||
return BErr
|
|
||||||
})
|
|
||||||
|
|
||||||
cleanups.Add("cleaning up C", 2, func() error {
|
|
||||||
CCloseCalled = true
|
|
||||||
return CErr
|
|
||||||
})
|
|
||||||
|
|
||||||
logger := NewMockLogger(ctrl)
|
|
||||||
prevCall := logger.EXPECT().Debug("cleaning up C...")
|
|
||||||
prevCall = logger.EXPECT().Error("failed cleaning up C: C failed").After(prevCall)
|
|
||||||
prevCall = logger.EXPECT().Debug("cleaning up B...").After(prevCall)
|
|
||||||
prevCall = logger.EXPECT().Error("failed cleaning up B: B failed").After(prevCall)
|
|
||||||
logger.EXPECT().Debug("cleaning up A...").After(prevCall)
|
|
||||||
|
|
||||||
cleanups.Cleanup(logger)
|
|
||||||
|
|
||||||
cleanups.Cleanup(logger) // run twice should not close already closed
|
|
||||||
|
|
||||||
for _, cleanup := range cleanups {
|
|
||||||
assert.True(t, cleanup.done)
|
|
||||||
}
|
|
||||||
|
|
||||||
assert.True(t, ACloseCalled)
|
|
||||||
assert.True(t, BCloseCalled)
|
|
||||||
assert.True(t, CCloseCalled)
|
|
||||||
}
|
|
||||||
@@ -1,6 +0,0 @@
|
|||||||
package cleanup
|
|
||||||
|
|
||||||
type Logger interface {
|
|
||||||
Debug(string)
|
|
||||||
Error(string)
|
|
||||||
}
|
|
||||||
@@ -1,3 +0,0 @@
|
|||||||
package cleanup
|
|
||||||
|
|
||||||
//go:generate mockgen -destination=mocks_test.go -package=$GOPACKAGE . Logger
|
|
||||||
@@ -1,58 +0,0 @@
|
|||||||
// Code generated by MockGen. DO NOT EDIT.
|
|
||||||
// Source: github.com/qdm12/gluetun/internal/cleanup (interfaces: Logger)
|
|
||||||
|
|
||||||
// Package cleanup is a generated GoMock package.
|
|
||||||
package cleanup
|
|
||||||
|
|
||||||
import (
|
|
||||||
reflect "reflect"
|
|
||||||
|
|
||||||
gomock "github.com/golang/mock/gomock"
|
|
||||||
)
|
|
||||||
|
|
||||||
// MockLogger is a mock of Logger interface.
|
|
||||||
type MockLogger struct {
|
|
||||||
ctrl *gomock.Controller
|
|
||||||
recorder *MockLoggerMockRecorder
|
|
||||||
}
|
|
||||||
|
|
||||||
// MockLoggerMockRecorder is the mock recorder for MockLogger.
|
|
||||||
type MockLoggerMockRecorder struct {
|
|
||||||
mock *MockLogger
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewMockLogger creates a new mock instance.
|
|
||||||
func NewMockLogger(ctrl *gomock.Controller) *MockLogger {
|
|
||||||
mock := &MockLogger{ctrl: ctrl}
|
|
||||||
mock.recorder = &MockLoggerMockRecorder{mock}
|
|
||||||
return mock
|
|
||||||
}
|
|
||||||
|
|
||||||
// EXPECT returns an object that allows the caller to indicate expected use.
|
|
||||||
func (m *MockLogger) EXPECT() *MockLoggerMockRecorder {
|
|
||||||
return m.recorder
|
|
||||||
}
|
|
||||||
|
|
||||||
// Debug mocks base method.
|
|
||||||
func (m *MockLogger) Debug(arg0 string) {
|
|
||||||
m.ctrl.T.Helper()
|
|
||||||
m.ctrl.Call(m, "Debug", arg0)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Debug indicates an expected call of Debug.
|
|
||||||
func (mr *MockLoggerMockRecorder) Debug(arg0 interface{}) *gomock.Call {
|
|
||||||
mr.mock.ctrl.T.Helper()
|
|
||||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Debug", reflect.TypeOf((*MockLogger)(nil).Debug), arg0)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Error mocks base method.
|
|
||||||
func (m *MockLogger) Error(arg0 string) {
|
|
||||||
m.ctrl.T.Helper()
|
|
||||||
m.ctrl.Call(m, "Error", arg0)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Error indicates an expected call of Error.
|
|
||||||
func (mr *MockLoggerMockRecorder) Error(arg0 interface{}) *gomock.Call {
|
|
||||||
mr.mock.ctrl.T.Helper()
|
|
||||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Error", reflect.TypeOf((*MockLogger)(nil).Error), arg0)
|
|
||||||
}
|
|
||||||
@@ -1,14 +0,0 @@
|
|||||||
package cli
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"net/netip"
|
|
||||||
)
|
|
||||||
|
|
||||||
type noopFirewall struct{}
|
|
||||||
|
|
||||||
func (f *noopFirewall) AcceptOutput(_ context.Context, _, _ string, _ netip.Addr,
|
|
||||||
_ uint16, _ bool,
|
|
||||||
) (err error) {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
@@ -11,7 +11,6 @@ import (
|
|||||||
"github.com/qdm12/gluetun/internal/configuration/settings"
|
"github.com/qdm12/gluetun/internal/configuration/settings"
|
||||||
"github.com/qdm12/gluetun/internal/constants"
|
"github.com/qdm12/gluetun/internal/constants"
|
||||||
"github.com/qdm12/gluetun/internal/models"
|
"github.com/qdm12/gluetun/internal/models"
|
||||||
"github.com/qdm12/gluetun/internal/netlink"
|
|
||||||
"github.com/qdm12/gluetun/internal/openvpn/extract"
|
"github.com/qdm12/gluetun/internal/openvpn/extract"
|
||||||
"github.com/qdm12/gluetun/internal/provider"
|
"github.com/qdm12/gluetun/internal/provider"
|
||||||
"github.com/qdm12/gluetun/internal/storage"
|
"github.com/qdm12/gluetun/internal/storage"
|
||||||
@@ -41,9 +40,7 @@ type IPFetcher interface {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type IPv6Checker interface {
|
type IPv6Checker interface {
|
||||||
FindIPv6SupportLevel(ctx context.Context,
|
IsIPv6Supported() (supported bool, err error)
|
||||||
checkAddresses []netip.AddrPort, firewall netlink.Firewall,
|
|
||||||
) (level netlink.IPv6SupportLevel, err error)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *CLI) OpenvpnConfig(logger OpenvpnConfigLogger, reader *reader.Reader,
|
func (c *CLI) OpenvpnConfig(logger OpenvpnConfigLogger, reader *reader.Reader,
|
||||||
@@ -61,14 +58,12 @@ func (c *CLI) OpenvpnConfig(logger OpenvpnConfigLogger, reader *reader.Reader,
|
|||||||
}
|
}
|
||||||
allSettings.SetDefaults()
|
allSettings.SetDefaults()
|
||||||
|
|
||||||
ipv6SupportLevel, err := ipv6Checker.FindIPv6SupportLevel(context.Background(),
|
ipv6Supported, err := ipv6Checker.IsIPv6Supported()
|
||||||
allSettings.IPv6.CheckAddresses, &noopFirewall{})
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("checking for IPv6 support: %w", err)
|
return fmt.Errorf("checking for IPv6 support: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
err = allSettings.Validate(storage, ipv6SupportLevel.IsSupported(), logger)
|
if err = allSettings.Validate(storage, ipv6Supported, logger); err != nil {
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("validating settings: %w", err)
|
return fmt.Errorf("validating settings: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -84,13 +79,13 @@ func (c *CLI) OpenvpnConfig(logger OpenvpnConfigLogger, reader *reader.Reader,
|
|||||||
unzipper, parallelResolver, ipFetcher, openvpnFileExtractor, allSettings.Updater)
|
unzipper, parallelResolver, ipFetcher, openvpnFileExtractor, allSettings.Updater)
|
||||||
providerConf := providers.Get(allSettings.VPN.Provider.Name)
|
providerConf := providers.Get(allSettings.VPN.Provider.Name)
|
||||||
connection, err := providerConf.GetConnection(
|
connection, err := providerConf.GetConnection(
|
||||||
allSettings.VPN.Provider.ServerSelection, ipv6SupportLevel == netlink.IPv6Internet)
|
allSettings.VPN.Provider.ServerSelection, ipv6Supported)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
lines := providerConf.OpenVPNConfig(connection,
|
lines := providerConf.OpenVPNConfig(connection,
|
||||||
allSettings.VPN.OpenVPN, ipv6SupportLevel.IsSupported())
|
allSettings.VPN.OpenVPN, ipv6Supported)
|
||||||
|
|
||||||
fmt.Println(strings.Join(lines, "\n"))
|
fmt.Println(strings.Join(lines, "\n"))
|
||||||
return nil
|
return nil
|
||||||
|
|||||||
+3
-20
@@ -10,8 +10,6 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/qdm12/dns/v2/pkg/doh"
|
|
||||||
dnsprovider "github.com/qdm12/dns/v2/pkg/provider"
|
|
||||||
"github.com/qdm12/gluetun/internal/configuration/settings"
|
"github.com/qdm12/gluetun/internal/configuration/settings"
|
||||||
"github.com/qdm12/gluetun/internal/constants"
|
"github.com/qdm12/gluetun/internal/constants"
|
||||||
"github.com/qdm12/gluetun/internal/constants/providers"
|
"github.com/qdm12/gluetun/internal/constants/providers"
|
||||||
@@ -40,12 +38,12 @@ type UpdaterLogger interface {
|
|||||||
func (c *CLI) Update(ctx context.Context, args []string, logger UpdaterLogger) error {
|
func (c *CLI) Update(ctx context.Context, args []string, logger UpdaterLogger) error {
|
||||||
options := settings.Updater{}
|
options := settings.Updater{}
|
||||||
var endUserMode, maintainerMode, updateAll bool
|
var endUserMode, maintainerMode, updateAll bool
|
||||||
var dnsServer, csvProviders, ipToken, protonUsername, protonEmail, protonPassword string
|
var csvProviders, ipToken, protonUsername, protonEmail, protonPassword string
|
||||||
flagSet := flag.NewFlagSet("update", flag.ExitOnError)
|
flagSet := flag.NewFlagSet("update", flag.ExitOnError)
|
||||||
flagSet.BoolVar(&endUserMode, "enduser", false, "Write results to /gluetun/servers.json (for end users)")
|
flagSet.BoolVar(&endUserMode, "enduser", false, "Write results to /gluetun/servers.json (for end users)")
|
||||||
flagSet.BoolVar(&maintainerMode, "maintainer", false,
|
flagSet.BoolVar(&maintainerMode, "maintainer", false,
|
||||||
"Write results to ./internal/storage/servers.json to modify the program (for maintainers)")
|
"Write results to ./internal/storage/servers.json to modify the program (for maintainers)")
|
||||||
flagSet.StringVar(&dnsServer, "dns", "", "no longer used, your DNS will use DoH with Cloudflare and Google")
|
flagSet.StringVar(&options.DNSAddress, "dns", "8.8.8.8", "DNS resolver address to use")
|
||||||
const defaultMinRatio = 0.8
|
const defaultMinRatio = 0.8
|
||||||
flagSet.Float64Var(&options.MinRatio, "minratio", defaultMinRatio,
|
flagSet.Float64Var(&options.MinRatio, "minratio", defaultMinRatio,
|
||||||
"Minimum ratio of servers to find for the update to succeed")
|
"Minimum ratio of servers to find for the update to succeed")
|
||||||
@@ -60,10 +58,6 @@ func (c *CLI) Update(ctx context.Context, args []string, logger UpdaterLogger) e
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
if dnsServer != "" {
|
|
||||||
logger.Warn("The -dns flag is no longer used, your DNS will use DoH with Cloudflare and Google")
|
|
||||||
}
|
|
||||||
|
|
||||||
if !endUserMode && !maintainerMode {
|
if !endUserMode && !maintainerMode {
|
||||||
return fmt.Errorf("%w", ErrModeUnspecified)
|
return fmt.Errorf("%w", ErrModeUnspecified)
|
||||||
}
|
}
|
||||||
@@ -103,21 +97,10 @@ func (c *CLI) Update(ctx context.Context, args []string, logger UpdaterLogger) e
|
|||||||
return fmt.Errorf("creating servers storage: %w", err)
|
return fmt.Errorf("creating servers storage: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
dohSettings := doh.Settings{
|
|
||||||
UpstreamResolvers: []dnsprovider.Provider{
|
|
||||||
dnsprovider.Cloudflare(),
|
|
||||||
dnsprovider.Google(),
|
|
||||||
},
|
|
||||||
}
|
|
||||||
dnsDialer, err := doh.New(dohSettings)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("creating DoH dialer: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
const clientTimeout = 10 * time.Second
|
const clientTimeout = 10 * time.Second
|
||||||
httpClient := &http.Client{Timeout: clientTimeout}
|
httpClient := &http.Client{Timeout: clientTimeout}
|
||||||
unzipper := unzip.New(httpClient)
|
unzipper := unzip.New(httpClient)
|
||||||
parallelResolver := resolver.NewParallelResolver(dnsDialer)
|
parallelResolver := resolver.NewParallelResolver(options.DNSAddress)
|
||||||
nameTokenPairs := []api.NameToken{
|
nameTokenPairs := []api.NameToken{
|
||||||
{Name: string(api.IPInfo), Token: ipToken},
|
{Name: string(api.IPInfo), Token: ipToken},
|
||||||
{Name: string(api.IP2Location)},
|
{Name: string(api.IP2Location)},
|
||||||
|
|||||||
@@ -1,6 +0,0 @@
|
|||||||
package command
|
|
||||||
|
|
||||||
type Logger interface {
|
|
||||||
Info(s string)
|
|
||||||
Error(s string)
|
|
||||||
}
|
|
||||||
+11
-11
@@ -9,13 +9,13 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
errCommandEmpty = errors.New("command is empty")
|
ErrCommandEmpty = errors.New("command is empty")
|
||||||
errSingleQuoteUnterminated = errors.New("unterminated single-quoted string")
|
ErrSingleQuoteUnterminated = errors.New("unterminated single-quoted string")
|
||||||
errDoubleQuoteUnterminated = errors.New("unterminated double-quoted string")
|
ErrDoubleQuoteUnterminated = errors.New("unterminated double-quoted string")
|
||||||
errEscapeUnterminated = errors.New("unterminated backslash-escape")
|
ErrEscapeUnterminated = errors.New("unterminated backslash-escape")
|
||||||
)
|
)
|
||||||
|
|
||||||
// split splits a command string into a slice of arguments.
|
// Split splits a command string into a slice of arguments.
|
||||||
// This is especially important for commands such as:
|
// This is especially important for commands such as:
|
||||||
// /bin/sh -c "echo hello"
|
// /bin/sh -c "echo hello"
|
||||||
// which should be split into: ["/bin/sh", "-c", "echo hello"]
|
// which should be split into: ["/bin/sh", "-c", "echo hello"]
|
||||||
@@ -23,9 +23,9 @@ var (
|
|||||||
// It does not support:
|
// It does not support:
|
||||||
// - the $" quoting style.
|
// - the $" quoting style.
|
||||||
// - expansion (brace, shell or pathname).
|
// - expansion (brace, shell or pathname).
|
||||||
func split(command string) (words []string, err error) {
|
func Split(command string) (words []string, err error) {
|
||||||
if command == "" {
|
if command == "" {
|
||||||
return nil, fmt.Errorf("%w", errCommandEmpty)
|
return nil, fmt.Errorf("%w", ErrCommandEmpty)
|
||||||
}
|
}
|
||||||
|
|
||||||
const bufferSize = 1024
|
const bufferSize = 1024
|
||||||
@@ -42,7 +42,7 @@ func split(command string) (words []string, err error) {
|
|||||||
case character == '\\':
|
case character == '\\':
|
||||||
// Look ahead to eventually skip an escaped newline
|
// Look ahead to eventually skip an escaped newline
|
||||||
if command[startIndex+runeSize:] == "" {
|
if command[startIndex+runeSize:] == "" {
|
||||||
return nil, fmt.Errorf("%w: %q", errEscapeUnterminated, command)
|
return nil, fmt.Errorf("%w: %q", ErrEscapeUnterminated, command)
|
||||||
}
|
}
|
||||||
character, runeSize := utf8.DecodeRuneInString(command[startIndex+runeSize:])
|
character, runeSize := utf8.DecodeRuneInString(command[startIndex+runeSize:])
|
||||||
if character == '\n' {
|
if character == '\n' {
|
||||||
@@ -119,7 +119,7 @@ func handleDoubleQuoted(input string, startIndex int, buffer *bytes.Buffer) (
|
|||||||
startIndex = cursor
|
startIndex = cursor
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return "", 0, fmt.Errorf("%w", errDoubleQuoteUnterminated)
|
return "", 0, fmt.Errorf("%w", ErrDoubleQuoteUnterminated)
|
||||||
}
|
}
|
||||||
|
|
||||||
func handleSingleQuoted(input string, startIndex int, buffer *bytes.Buffer) (
|
func handleSingleQuoted(input string, startIndex int, buffer *bytes.Buffer) (
|
||||||
@@ -127,7 +127,7 @@ func handleSingleQuoted(input string, startIndex int, buffer *bytes.Buffer) (
|
|||||||
) {
|
) {
|
||||||
closingQuoteIndex := strings.IndexRune(input[startIndex:], '\'')
|
closingQuoteIndex := strings.IndexRune(input[startIndex:], '\'')
|
||||||
if closingQuoteIndex == -1 {
|
if closingQuoteIndex == -1 {
|
||||||
return "", 0, fmt.Errorf("%w", errSingleQuoteUnterminated)
|
return "", 0, fmt.Errorf("%w", ErrSingleQuoteUnterminated)
|
||||||
}
|
}
|
||||||
buffer.WriteString(input[startIndex : startIndex+closingQuoteIndex])
|
buffer.WriteString(input[startIndex : startIndex+closingQuoteIndex])
|
||||||
const singleQuoteRuneLength = 1
|
const singleQuoteRuneLength = 1
|
||||||
@@ -139,7 +139,7 @@ func handleEscaped(input string, startIndex int, buffer *bytes.Buffer) (
|
|||||||
word string, newStartIndex int, err error,
|
word string, newStartIndex int, err error,
|
||||||
) {
|
) {
|
||||||
if input[startIndex:] == "" {
|
if input[startIndex:] == "" {
|
||||||
return "", 0, fmt.Errorf("%w", errEscapeUnterminated)
|
return "", 0, fmt.Errorf("%w", ErrEscapeUnterminated)
|
||||||
}
|
}
|
||||||
character, runeLength := utf8.DecodeRuneInString(input[startIndex:])
|
character, runeLength := utf8.DecodeRuneInString(input[startIndex:])
|
||||||
if character != '\n' { // backslash-escaped newline is ignored
|
if character != '\n' { // backslash-escaped newline is ignored
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ import (
|
|||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
)
|
)
|
||||||
|
|
||||||
func Test_split(t *testing.T) {
|
func Test_Split(t *testing.T) {
|
||||||
t.Parallel()
|
t.Parallel()
|
||||||
|
|
||||||
testCases := map[string]struct {
|
testCases := map[string]struct {
|
||||||
@@ -17,7 +17,7 @@ func Test_split(t *testing.T) {
|
|||||||
}{
|
}{
|
||||||
"empty": {
|
"empty": {
|
||||||
command: "",
|
command: "",
|
||||||
errWrapped: errCommandEmpty,
|
errWrapped: ErrCommandEmpty,
|
||||||
errMessage: "command is empty",
|
errMessage: "command is empty",
|
||||||
},
|
},
|
||||||
"concrete_sh_command": {
|
"concrete_sh_command": {
|
||||||
@@ -74,22 +74,22 @@ func Test_split(t *testing.T) {
|
|||||||
},
|
},
|
||||||
"unterminated_single_quote": {
|
"unterminated_single_quote": {
|
||||||
command: "'abc'\\''def",
|
command: "'abc'\\''def",
|
||||||
errWrapped: errSingleQuoteUnterminated,
|
errWrapped: ErrSingleQuoteUnterminated,
|
||||||
errMessage: `splitting word in "'abc'\\''def": unterminated single-quoted string`,
|
errMessage: `splitting word in "'abc'\\''def": unterminated single-quoted string`,
|
||||||
},
|
},
|
||||||
"unterminated_double_quote": {
|
"unterminated_double_quote": {
|
||||||
command: "\"abc'def",
|
command: "\"abc'def",
|
||||||
errWrapped: errDoubleQuoteUnterminated,
|
errWrapped: ErrDoubleQuoteUnterminated,
|
||||||
errMessage: `splitting word in "\"abc'def": unterminated double-quoted string`,
|
errMessage: `splitting word in "\"abc'def": unterminated double-quoted string`,
|
||||||
},
|
},
|
||||||
"unterminated_escape": {
|
"unterminated_escape": {
|
||||||
command: "abc\\",
|
command: "abc\\",
|
||||||
errWrapped: errEscapeUnterminated,
|
errWrapped: ErrEscapeUnterminated,
|
||||||
errMessage: `splitting word in "abc\\": unterminated backslash-escape`,
|
errMessage: `splitting word in "abc\\": unterminated backslash-escape`,
|
||||||
},
|
},
|
||||||
"unterminated_escape_only": {
|
"unterminated_escape_only": {
|
||||||
command: " \\",
|
command: " \\",
|
||||||
errWrapped: errEscapeUnterminated,
|
errWrapped: ErrEscapeUnterminated,
|
||||||
errMessage: `unterminated backslash-escape: " \\"`,
|
errMessage: `unterminated backslash-escape: " \\"`,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
@@ -98,7 +98,7 @@ func Test_split(t *testing.T) {
|
|||||||
t.Run(name, func(t *testing.T) {
|
t.Run(name, func(t *testing.T) {
|
||||||
t.Parallel()
|
t.Parallel()
|
||||||
|
|
||||||
words, err := split(testCase.command)
|
words, err := Split(testCase.command)
|
||||||
|
|
||||||
assert.Equal(t, testCase.words, words)
|
assert.Equal(t, testCase.words, words)
|
||||||
assert.ErrorIs(t, err, testCase.errWrapped)
|
assert.ErrorIs(t, err, testCase.errWrapped)
|
||||||
|
|||||||
@@ -1,48 +0,0 @@
|
|||||||
package command
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"fmt"
|
|
||||||
"os/exec"
|
|
||||||
)
|
|
||||||
|
|
||||||
func (c *Cmder) RunAndLog(ctx context.Context, command string, logger Logger) (err error) {
|
|
||||||
args, err := split(command)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("parsing command: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
cmd := exec.CommandContext(ctx, args[0], args[1:]...) // #nosec G204
|
|
||||||
stdout, stderr, waitError, err := c.Start(cmd)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
streamCtx, streamCancel := context.WithCancel(context.Background())
|
|
||||||
streamDone := make(chan struct{})
|
|
||||||
go streamLines(streamCtx, streamDone, logger, stdout, stderr)
|
|
||||||
|
|
||||||
err = <-waitError
|
|
||||||
streamCancel()
|
|
||||||
<-streamDone
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
func streamLines(ctx context.Context, done chan<- struct{},
|
|
||||||
logger Logger, stdout, stderr <-chan string,
|
|
||||||
) {
|
|
||||||
defer close(done)
|
|
||||||
|
|
||||||
var line string
|
|
||||||
|
|
||||||
for {
|
|
||||||
select {
|
|
||||||
case <-ctx.Done():
|
|
||||||
return
|
|
||||||
case line = <-stdout:
|
|
||||||
logger.Info(line)
|
|
||||||
case line = <-stderr:
|
|
||||||
logger.Error(line)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,243 +0,0 @@
|
|||||||
package settings
|
|
||||||
|
|
||||||
import (
|
|
||||||
"errors"
|
|
||||||
"fmt"
|
|
||||||
"strconv"
|
|
||||||
"strings"
|
|
||||||
|
|
||||||
"github.com/qdm12/gosettings"
|
|
||||||
"github.com/qdm12/gosettings/reader"
|
|
||||||
"github.com/qdm12/gotree"
|
|
||||||
)
|
|
||||||
|
|
||||||
type AmneziaWg struct {
|
|
||||||
// Wireguard contains the configuration for Wireguard, given
|
|
||||||
// AmneziaWg is based on Wireguard
|
|
||||||
Wireguard Wireguard `json:"wireguard"`
|
|
||||||
JunkPacketCount *uint16 `json:"junk_packet_count"`
|
|
||||||
JunkPacketMin *uint16 `json:"junk_packet_min"`
|
|
||||||
JunkPacketMax *uint16 `json:"junk_packet_max"`
|
|
||||||
PaddingS1 *uint16 `json:"padding_s1"`
|
|
||||||
PaddingS2 *uint16 `json:"padding_s2"`
|
|
||||||
PaddingS3 *uint16 `json:"padding_s3"`
|
|
||||||
PaddingS4 *uint16 `json:"padding_s4"`
|
|
||||||
HeaderH1 *string `json:"header_h1"`
|
|
||||||
HeaderH2 *string `json:"header_h2"`
|
|
||||||
HeaderH3 *string `json:"header_h3"`
|
|
||||||
HeaderH4 *string `json:"header_h4"`
|
|
||||||
InitPacketI1 *string `json:"init_packet_i1"`
|
|
||||||
InitPacketI2 *string `json:"init_packet_i2"`
|
|
||||||
InitPacketI3 *string `json:"init_packet_i3"`
|
|
||||||
InitPacketI4 *string `json:"init_packet_i4"`
|
|
||||||
InitPacketI5 *string `json:"init_packet_i5"`
|
|
||||||
}
|
|
||||||
|
|
||||||
func (a *AmneziaWg) read(r *reader.Reader) (err error) {
|
|
||||||
const amneziawg = true
|
|
||||||
err = a.Wireguard.read(r, amneziawg)
|
|
||||||
if err != nil {
|
|
||||||
return err // do not wrap this error
|
|
||||||
}
|
|
||||||
|
|
||||||
uint16Fields := map[string]**uint16{
|
|
||||||
"AMNEZIAWG_JC": &a.JunkPacketCount,
|
|
||||||
"AMNEZIAWG_JMIN": &a.JunkPacketMin,
|
|
||||||
"AMNEZIAWG_JMAX": &a.JunkPacketMax,
|
|
||||||
"AMNEZIAWG_S1": &a.PaddingS1,
|
|
||||||
"AMNEZIAWG_S2": &a.PaddingS2,
|
|
||||||
"AMNEZIAWG_S3": &a.PaddingS3,
|
|
||||||
"AMNEZIAWG_S4": &a.PaddingS4,
|
|
||||||
}
|
|
||||||
for key, dst := range uint16Fields {
|
|
||||||
*dst, err = r.Uint16Ptr(key)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
stringFields := map[string]**string{
|
|
||||||
"AMNEZIAWG_H1": &a.HeaderH1,
|
|
||||||
"AMNEZIAWG_H2": &a.HeaderH2,
|
|
||||||
"AMNEZIAWG_H3": &a.HeaderH3,
|
|
||||||
"AMNEZIAWG_H4": &a.HeaderH4,
|
|
||||||
"AMNEZIAWG_I1": &a.InitPacketI1,
|
|
||||||
"AMNEZIAWG_I2": &a.InitPacketI2,
|
|
||||||
"AMNEZIAWG_I3": &a.InitPacketI3,
|
|
||||||
"AMNEZIAWG_I4": &a.InitPacketI4,
|
|
||||||
"AMNEZIAWG_I5": &a.InitPacketI5,
|
|
||||||
}
|
|
||||||
opt := reader.ForceLowercase(false)
|
|
||||||
for key, dst := range stringFields {
|
|
||||||
*dst = r.Get(key, opt)
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (a AmneziaWg) copy() (copied AmneziaWg) {
|
|
||||||
return AmneziaWg{
|
|
||||||
Wireguard: a.Wireguard.copy(),
|
|
||||||
JunkPacketCount: gosettings.CopyPointer(a.JunkPacketCount),
|
|
||||||
JunkPacketMin: gosettings.CopyPointer(a.JunkPacketMin),
|
|
||||||
JunkPacketMax: gosettings.CopyPointer(a.JunkPacketMax),
|
|
||||||
PaddingS1: gosettings.CopyPointer(a.PaddingS1),
|
|
||||||
PaddingS2: gosettings.CopyPointer(a.PaddingS2),
|
|
||||||
PaddingS3: gosettings.CopyPointer(a.PaddingS3),
|
|
||||||
PaddingS4: gosettings.CopyPointer(a.PaddingS4),
|
|
||||||
HeaderH1: gosettings.CopyPointer(a.HeaderH1),
|
|
||||||
HeaderH2: gosettings.CopyPointer(a.HeaderH2),
|
|
||||||
HeaderH3: gosettings.CopyPointer(a.HeaderH3),
|
|
||||||
HeaderH4: gosettings.CopyPointer(a.HeaderH4),
|
|
||||||
InitPacketI1: gosettings.CopyPointer(a.InitPacketI1),
|
|
||||||
InitPacketI2: gosettings.CopyPointer(a.InitPacketI2),
|
|
||||||
InitPacketI3: gosettings.CopyPointer(a.InitPacketI3),
|
|
||||||
InitPacketI4: gosettings.CopyPointer(a.InitPacketI4),
|
|
||||||
InitPacketI5: gosettings.CopyPointer(a.InitPacketI5),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (a *AmneziaWg) overrideWith(other AmneziaWg) {
|
|
||||||
a.Wireguard.overrideWith(other.Wireguard)
|
|
||||||
a.JunkPacketCount = gosettings.OverrideWithPointer(a.JunkPacketCount, other.JunkPacketCount)
|
|
||||||
a.JunkPacketMin = gosettings.OverrideWithPointer(a.JunkPacketMin, other.JunkPacketMin)
|
|
||||||
a.JunkPacketMax = gosettings.OverrideWithPointer(a.JunkPacketMax, other.JunkPacketMax)
|
|
||||||
a.PaddingS1 = gosettings.OverrideWithPointer(a.PaddingS1, other.PaddingS1)
|
|
||||||
a.PaddingS2 = gosettings.OverrideWithPointer(a.PaddingS2, other.PaddingS2)
|
|
||||||
a.PaddingS3 = gosettings.OverrideWithPointer(a.PaddingS3, other.PaddingS3)
|
|
||||||
a.PaddingS4 = gosettings.OverrideWithPointer(a.PaddingS4, other.PaddingS4)
|
|
||||||
a.HeaderH1 = gosettings.OverrideWithPointer(a.HeaderH1, other.HeaderH1)
|
|
||||||
a.HeaderH2 = gosettings.OverrideWithPointer(a.HeaderH2, other.HeaderH2)
|
|
||||||
a.HeaderH3 = gosettings.OverrideWithPointer(a.HeaderH3, other.HeaderH3)
|
|
||||||
a.HeaderH4 = gosettings.OverrideWithPointer(a.HeaderH4, other.HeaderH4)
|
|
||||||
a.InitPacketI1 = gosettings.OverrideWithPointer(a.InitPacketI1, other.InitPacketI1)
|
|
||||||
a.InitPacketI2 = gosettings.OverrideWithPointer(a.InitPacketI2, other.InitPacketI2)
|
|
||||||
a.InitPacketI3 = gosettings.OverrideWithPointer(a.InitPacketI3, other.InitPacketI3)
|
|
||||||
a.InitPacketI4 = gosettings.OverrideWithPointer(a.InitPacketI4, other.InitPacketI4)
|
|
||||||
a.InitPacketI5 = gosettings.OverrideWithPointer(a.InitPacketI5, other.InitPacketI5)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (a *AmneziaWg) setDefaults(vpnProvider string) {
|
|
||||||
a.Wireguard.setDefaults(vpnProvider)
|
|
||||||
a.Wireguard.Implementation = "userspace" // unused except in logs
|
|
||||||
a.JunkPacketCount = gosettings.DefaultPointer(a.JunkPacketCount, 0)
|
|
||||||
a.JunkPacketMin = gosettings.DefaultPointer(a.JunkPacketMin, 0)
|
|
||||||
a.JunkPacketMax = gosettings.DefaultPointer(a.JunkPacketMax, 0)
|
|
||||||
a.PaddingS1 = gosettings.DefaultPointer(a.PaddingS1, 0)
|
|
||||||
a.PaddingS2 = gosettings.DefaultPointer(a.PaddingS2, 0)
|
|
||||||
a.PaddingS3 = gosettings.DefaultPointer(a.PaddingS3, 0)
|
|
||||||
a.PaddingS4 = gosettings.DefaultPointer(a.PaddingS4, 0)
|
|
||||||
a.HeaderH1 = gosettings.DefaultPointer(a.HeaderH1, "")
|
|
||||||
a.HeaderH2 = gosettings.DefaultPointer(a.HeaderH2, "")
|
|
||||||
a.HeaderH3 = gosettings.DefaultPointer(a.HeaderH3, "")
|
|
||||||
a.HeaderH4 = gosettings.DefaultPointer(a.HeaderH4, "")
|
|
||||||
a.InitPacketI1 = gosettings.DefaultPointer(a.InitPacketI1, "")
|
|
||||||
a.InitPacketI2 = gosettings.DefaultPointer(a.InitPacketI2, "")
|
|
||||||
a.InitPacketI3 = gosettings.DefaultPointer(a.InitPacketI3, "")
|
|
||||||
a.InitPacketI4 = gosettings.DefaultPointer(a.InitPacketI4, "")
|
|
||||||
a.InitPacketI5 = gosettings.DefaultPointer(a.InitPacketI5, "")
|
|
||||||
}
|
|
||||||
|
|
||||||
func (a AmneziaWg) toLinesNode() (node *gotree.Node) {
|
|
||||||
node = gotree.New("AmneziaWG settings:")
|
|
||||||
node.AppendNode(a.Wireguard.toLinesNode())
|
|
||||||
|
|
||||||
uintFields := []struct {
|
|
||||||
key string
|
|
||||||
val *uint16
|
|
||||||
}{
|
|
||||||
{"JC", a.JunkPacketCount},
|
|
||||||
{"JMIN", a.JunkPacketMin},
|
|
||||||
{"JMAX", a.JunkPacketMax},
|
|
||||||
{"S1", a.PaddingS1},
|
|
||||||
{"S2", a.PaddingS2},
|
|
||||||
{"S3", a.PaddingS3},
|
|
||||||
{"S4", a.PaddingS4},
|
|
||||||
}
|
|
||||||
for _, f := range uintFields {
|
|
||||||
node.Appendf("%s: %d", f.key, *f.val)
|
|
||||||
}
|
|
||||||
|
|
||||||
stringFields := []struct {
|
|
||||||
key string
|
|
||||||
val *string
|
|
||||||
}{
|
|
||||||
{"H1", a.HeaderH1},
|
|
||||||
{"H2", a.HeaderH2},
|
|
||||||
{"H3", a.HeaderH3},
|
|
||||||
{"H4", a.HeaderH4},
|
|
||||||
{"I1", a.InitPacketI1},
|
|
||||||
{"I2", a.InitPacketI2},
|
|
||||||
{"I3", a.InitPacketI3},
|
|
||||||
{"I4", a.InitPacketI4},
|
|
||||||
{"I5", a.InitPacketI5},
|
|
||||||
}
|
|
||||||
for _, f := range stringFields {
|
|
||||||
node.Appendf("%s: %s", f.key, *f.val)
|
|
||||||
}
|
|
||||||
|
|
||||||
return node
|
|
||||||
}
|
|
||||||
|
|
||||||
var (
|
|
||||||
ErrAmenziawgImplementationNotValid = errors.New("AmneziaWG implementation is not valid")
|
|
||||||
ErrJunkPacketBounds = errors.New("junk packet minimum must be lower than or equal to maximum")
|
|
||||||
ErrJunkPacketMinMaxNotSet = errors.New("junk packet min and max must be set when junk packet count is set")
|
|
||||||
ErrJunkPacketCountNotSet = errors.New("junk packet count must be set when junk packet min or max is set")
|
|
||||||
ErrHeaderRangeMalformed = errors.New("header range is malformed")
|
|
||||||
)
|
|
||||||
|
|
||||||
func (a AmneziaWg) validate(vpnProvider string, ipv6Supported bool) error {
|
|
||||||
const amneziaWG = true
|
|
||||||
err := a.Wireguard.validate(vpnProvider, ipv6Supported, amneziaWG)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("wireguard settings: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if *a.JunkPacketCount == 0 {
|
|
||||||
if *a.JunkPacketMin != 0 || *a.JunkPacketMax != 0 {
|
|
||||||
return fmt.Errorf("%w: jc=%d and jmin=%d and jmax=%d",
|
|
||||||
ErrJunkPacketCountNotSet, a.JunkPacketCount, *a.JunkPacketMin, *a.JunkPacketMax)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
if *a.JunkPacketMin == 0 || *a.JunkPacketMax == 0 {
|
|
||||||
return fmt.Errorf("%w: jc=%d and jmin=%d and jmax=%d",
|
|
||||||
ErrJunkPacketMinMaxNotSet, a.JunkPacketCount, *a.JunkPacketMin, *a.JunkPacketMax)
|
|
||||||
} else if *a.JunkPacketMin > *a.JunkPacketMax {
|
|
||||||
return fmt.Errorf("%w: jmin=%d and jmax=%d",
|
|
||||||
ErrJunkPacketBounds, *a.JunkPacketMin, *a.JunkPacketMax)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
nameToHeaderRange := map[string]string{
|
|
||||||
"h1": *a.HeaderH1,
|
|
||||||
"h2": *a.HeaderH2,
|
|
||||||
"h3": *a.HeaderH3,
|
|
||||||
"h4": *a.HeaderH4,
|
|
||||||
}
|
|
||||||
for name, headerRange := range nameToHeaderRange {
|
|
||||||
if headerRange == "" {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
fields := strings.Split(headerRange, "-")
|
|
||||||
switch len(fields) {
|
|
||||||
case 1:
|
|
||||||
_, err := strconv.Atoi(fields[0])
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("%w: %s value %s is not a number",
|
|
||||||
ErrHeaderRangeMalformed, name, headerRange)
|
|
||||||
}
|
|
||||||
case 2: //nolint:mnd
|
|
||||||
for _, field := range fields {
|
|
||||||
_, err := strconv.Atoi(field)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("%w: %s value %s is not a valid range",
|
|
||||||
ErrHeaderRangeMalformed, name, headerRange)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
default:
|
|
||||||
return fmt.Errorf("%w: %s value %s must be in the form n or n-m",
|
|
||||||
ErrHeaderRangeMalformed, name, headerRange)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
@@ -1,51 +0,0 @@
|
|||||||
package settings
|
|
||||||
|
|
||||||
import (
|
|
||||||
"github.com/qdm12/gosettings"
|
|
||||||
"github.com/qdm12/gosettings/reader"
|
|
||||||
"github.com/qdm12/gotree"
|
|
||||||
)
|
|
||||||
|
|
||||||
type BoringPoll struct {
|
|
||||||
GluetunCom *bool
|
|
||||||
}
|
|
||||||
|
|
||||||
func (b BoringPoll) validate() error {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (b BoringPoll) Copy() BoringPoll {
|
|
||||||
return BoringPoll{
|
|
||||||
GluetunCom: gosettings.CopyPointer(b.GluetunCom),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (b *BoringPoll) overrideWith(other BoringPoll) {
|
|
||||||
b.GluetunCom = gosettings.OverrideWithPointer(b.GluetunCom, other.GluetunCom)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (b *BoringPoll) setDefaults() {
|
|
||||||
b.GluetunCom = gosettings.DefaultPointer(b.GluetunCom, false)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (b BoringPoll) String() string {
|
|
||||||
return b.toLinesNode().String()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (b BoringPoll) toLinesNode() *gotree.Node {
|
|
||||||
if !*b.GluetunCom {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
node := gotree.New("Boring-poll settings:")
|
|
||||||
node.Append("gluetun.com: on")
|
|
||||||
return node
|
|
||||||
}
|
|
||||||
|
|
||||||
func (b *BoringPoll) read(r *reader.Reader) (err error) {
|
|
||||||
b.GluetunCom, err = r.BoolPtr("BORINGPOLL_GLUETUNCOM")
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
@@ -14,10 +14,6 @@ func readObsolete(r *reader.Reader) (warnings []string) {
|
|||||||
"DOT_VALIDATION_LOGLEVEL": "DOT_VALIDATION_LOGLEVEL is obsolete because DNSSEC validation is not implemented.",
|
"DOT_VALIDATION_LOGLEVEL": "DOT_VALIDATION_LOGLEVEL is obsolete because DNSSEC validation is not implemented.",
|
||||||
"HEALTH_VPN_DURATION_INITIAL": "HEALTH_VPN_DURATION_INITIAL is obsolete",
|
"HEALTH_VPN_DURATION_INITIAL": "HEALTH_VPN_DURATION_INITIAL is obsolete",
|
||||||
"HEALTH_VPN_DURATION_ADDITION": "HEALTH_VPN_DURATION_ADDITION is obsolete",
|
"HEALTH_VPN_DURATION_ADDITION": "HEALTH_VPN_DURATION_ADDITION is obsolete",
|
||||||
"DNS_SERVER": "DNS_SERVER is obsolete because the forwarding server is always enabled.",
|
|
||||||
"DOT": "DOT is obsolete because the forwarding server is always enabled.",
|
|
||||||
"DNS_KEEP_NAMESERVER": "DNS_KEEP_NAMESERVER is obsolete because the forwarding server is always used and " +
|
|
||||||
"forwards local names to private DNS resolvers found in /etc/resolv.conf",
|
|
||||||
}
|
}
|
||||||
sortedKeys := maps.Keys(keyToMessage)
|
sortedKeys := maps.Keys(keyToMessage)
|
||||||
slices.Sort(sortedKeys)
|
slices.Sort(sortedKeys)
|
||||||
|
|||||||
@@ -13,25 +13,20 @@ import (
|
|||||||
"github.com/qdm12/gotree"
|
"github.com/qdm12/gotree"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
|
||||||
DNSUpstreamTypeDot = "dot"
|
|
||||||
DNSUpstreamTypeDoh = "doh"
|
|
||||||
DNSUpstreamTypePlain = "plain"
|
|
||||||
)
|
|
||||||
|
|
||||||
// DNS contains settings to configure DNS.
|
// DNS contains settings to configure DNS.
|
||||||
type DNS struct {
|
type DNS struct {
|
||||||
// UpstreamType can be [DNSUpstreamTypeDot], [DNSUpstreamTypeDoh]
|
// ServerEnabled is true if the server should be running
|
||||||
// or [DNSUpstreamTypePlain]. It defaults to [DNSUpstreamTypeDot].
|
// 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"`
|
UpstreamType string `json:"upstream_type"`
|
||||||
// UpdatePeriod is the period to update DNS block lists.
|
// UpdatePeriod is the period to update DNS block lists.
|
||||||
// It can be set to 0 to disable the update.
|
// It can be set to 0 to disable the update.
|
||||||
// It defaults to 24h and cannot be nil in
|
// It defaults to 24h and cannot be nil in
|
||||||
// the internal state.
|
// the internal state.
|
||||||
UpdatePeriod *time.Duration
|
UpdatePeriod *time.Duration
|
||||||
// Providers is a list of DNS providers.
|
// Providers is a list of DNS providers
|
||||||
// It defaults to ["cloudflare"] and is ignored if the UpstreamType is
|
|
||||||
// [DNSUpstreamTypePlain] and the UpstreamPlainAddresses field is set.
|
|
||||||
Providers []string `json:"providers"`
|
Providers []string `json:"providers"`
|
||||||
// Caching is true if the server should cache
|
// Caching is true if the server should cache
|
||||||
// DNS responses.
|
// DNS responses.
|
||||||
@@ -41,22 +36,32 @@ type DNS struct {
|
|||||||
// Blacklist contains settings to configure the filter
|
// Blacklist contains settings to configure the filter
|
||||||
// block lists.
|
// block lists.
|
||||||
Blacklist DNSBlacklist
|
Blacklist DNSBlacklist
|
||||||
// UpstreamPlainAddresses are the upstream plaintext DNS resolver
|
// ServerAddress is the DNS server to use inside
|
||||||
// addresses to use by the built-in DNS server forwarder.
|
// the Go program and for the system.
|
||||||
// Note, if the upstream type is [dnsUpstreamTypePlain] and this field is set,
|
// It defaults to '127.0.0.1' to be used with the
|
||||||
// the Providers field is ignored.
|
// local server. It cannot be the zero value in the internal
|
||||||
UpstreamPlainAddresses []netip.AddrPort
|
// state.
|
||||||
|
ServerAddress netip.Addr
|
||||||
|
// KeepNameserver is true if the existing DNS server
|
||||||
|
// found in /etc/resolv.conf should be used
|
||||||
|
// Note setting this to true will likely DNS traffic
|
||||||
|
// outside the VPN tunnel since it would go through
|
||||||
|
// the local DNS server of your Docker/Kubernetes
|
||||||
|
// configuration, which is likely not going through the tunnel.
|
||||||
|
// This will also disable the DNS forwarder server and the
|
||||||
|
// `ServerAddress` field will be ignored.
|
||||||
|
// It defaults to false and cannot be nil in the
|
||||||
|
// internal state.
|
||||||
|
KeepNameserver *bool
|
||||||
}
|
}
|
||||||
|
|
||||||
var (
|
var (
|
||||||
ErrDNSUpstreamTypeNotValid = errors.New("DNS upstream type is not valid")
|
ErrDNSUpstreamTypeNotValid = errors.New("DNS upstream type is not valid")
|
||||||
ErrDNSUpdatePeriodTooShort = errors.New("update period is too short")
|
ErrDNSUpdatePeriodTooShort = errors.New("update period is too short")
|
||||||
ErrDNSUpstreamPlainNoIPv6 = errors.New("upstream plain addresses do not contain any IPv6 address")
|
|
||||||
ErrDNSUpstreamPlainNoIPv4 = errors.New("upstream plain addresses do not contain any IPv4 address")
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func (d DNS) validate() (err error) {
|
func (d DNS) validate() (err error) {
|
||||||
if !helpers.IsOneOf(d.UpstreamType, DNSUpstreamTypeDot, DNSUpstreamTypeDoh, DNSUpstreamTypePlain) {
|
if !helpers.IsOneOf(d.UpstreamType, "dot", "doh", "plain") {
|
||||||
return fmt.Errorf("%w: %s", ErrDNSUpstreamTypeNotValid, d.UpstreamType)
|
return fmt.Errorf("%w: %s", ErrDNSUpstreamTypeNotValid, d.UpstreamType)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -66,27 +71,13 @@ func (d DNS) validate() (err error) {
|
|||||||
ErrDNSUpdatePeriodTooShort, *d.UpdatePeriod, minUpdatePeriod)
|
ErrDNSUpdatePeriodTooShort, *d.UpdatePeriod, minUpdatePeriod)
|
||||||
}
|
}
|
||||||
|
|
||||||
if d.UpstreamType == DNSUpstreamTypePlain {
|
providers := provider.NewProviders()
|
||||||
selectedHasPlainIPv4, selectedHasPlainIPv6 := false, false
|
for _, providerName := range d.Providers {
|
||||||
for _, addrPort := range d.UpstreamPlainAddresses {
|
_, err := providers.Get(providerName)
|
||||||
if !selectedHasPlainIPv4 && addrPort.Addr().Is4() {
|
if err != nil {
|
||||||
selectedHasPlainIPv4 = true
|
return err
|
||||||
}
|
|
||||||
if !selectedHasPlainIPv6 && addrPort.Addr().Is6() {
|
|
||||||
selectedHasPlainIPv6 = true
|
|
||||||
}
|
|
||||||
if selectedHasPlainIPv4 && selectedHasPlainIPv6 {
|
|
||||||
break
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
switch {
|
|
||||||
case *d.IPv6 && !selectedHasPlainIPv6:
|
|
||||||
return fmt.Errorf("%w: in %d addresses", ErrDNSUpstreamPlainNoIPv6, len(d.UpstreamPlainAddresses))
|
|
||||||
case !*d.IPv6 && !selectedHasPlainIPv4:
|
|
||||||
return fmt.Errorf("%w: in %d addresses", ErrDNSUpstreamPlainNoIPv4, len(d.UpstreamPlainAddresses))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// Note: all DNS built in providers have both IPv4 and IPv6 addresses for all modes
|
|
||||||
|
|
||||||
err = d.Blacklist.validate()
|
err = d.Blacklist.validate()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -98,13 +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,
|
UpstreamType: d.UpstreamType,
|
||||||
UpdatePeriod: gosettings.CopyPointer(d.UpdatePeriod),
|
UpdatePeriod: gosettings.CopyPointer(d.UpdatePeriod),
|
||||||
Providers: gosettings.CopySlice(d.Providers),
|
Providers: gosettings.CopySlice(d.Providers),
|
||||||
Caching: gosettings.CopyPointer(d.Caching),
|
Caching: gosettings.CopyPointer(d.Caching),
|
||||||
IPv6: gosettings.CopyPointer(d.IPv6),
|
IPv6: gosettings.CopyPointer(d.IPv6),
|
||||||
Blacklist: d.Blacklist.copy(),
|
Blacklist: d.Blacklist.copy(),
|
||||||
UpstreamPlainAddresses: gosettings.CopySlice(d.UpstreamPlainAddresses),
|
ServerAddress: d.ServerAddress,
|
||||||
|
KeepNameserver: gosettings.CopyPointer(d.KeepNameserver),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -112,30 +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.UpstreamType = gosettings.OverrideWithComparable(d.UpstreamType, other.UpstreamType)
|
||||||
d.UpdatePeriod = gosettings.OverrideWithPointer(d.UpdatePeriod, other.UpdatePeriod)
|
d.UpdatePeriod = gosettings.OverrideWithPointer(d.UpdatePeriod, other.UpdatePeriod)
|
||||||
d.Providers = gosettings.OverrideWithSlice(d.Providers, other.Providers)
|
d.Providers = gosettings.OverrideWithSlice(d.Providers, other.Providers)
|
||||||
d.Caching = gosettings.OverrideWithPointer(d.Caching, other.Caching)
|
d.Caching = gosettings.OverrideWithPointer(d.Caching, other.Caching)
|
||||||
d.IPv6 = gosettings.OverrideWithPointer(d.IPv6, other.IPv6)
|
d.IPv6 = gosettings.OverrideWithPointer(d.IPv6, other.IPv6)
|
||||||
d.Blacklist.overrideWith(other.Blacklist)
|
d.Blacklist.overrideWith(other.Blacklist)
|
||||||
d.UpstreamPlainAddresses = gosettings.OverrideWithSlice(d.UpstreamPlainAddresses, other.UpstreamPlainAddresses)
|
d.ServerAddress = gosettings.OverrideWithValidator(d.ServerAddress, other.ServerAddress)
|
||||||
|
d.KeepNameserver = gosettings.OverrideWithPointer(d.KeepNameserver, other.KeepNameserver)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d *DNS) setDefaults() {
|
func (d *DNS) setDefaults() {
|
||||||
d.UpstreamType = gosettings.DefaultComparable(d.UpstreamType, DNSUpstreamTypeDot)
|
d.ServerEnabled = gosettings.DefaultPointer(d.ServerEnabled, true)
|
||||||
|
d.UpstreamType = gosettings.DefaultComparable(d.UpstreamType, "dot")
|
||||||
const defaultUpdatePeriod = 24 * time.Hour
|
const defaultUpdatePeriod = 24 * time.Hour
|
||||||
d.UpdatePeriod = gosettings.DefaultPointer(d.UpdatePeriod, defaultUpdatePeriod)
|
d.UpdatePeriod = gosettings.DefaultPointer(d.UpdatePeriod, defaultUpdatePeriod)
|
||||||
d.UpstreamPlainAddresses = gosettings.DefaultSlice(d.UpstreamPlainAddresses, []netip.AddrPort{})
|
d.Providers = gosettings.DefaultSlice(d.Providers, []string{
|
||||||
d.Providers = gosettings.DefaultSlice(d.Providers, defaultDNSProviders())
|
provider.Cloudflare().Name,
|
||||||
|
})
|
||||||
d.Caching = gosettings.DefaultPointer(d.Caching, true)
|
d.Caching = gosettings.DefaultPointer(d.Caching, true)
|
||||||
d.IPv6 = gosettings.DefaultPointer(d.IPv6, false)
|
d.IPv6 = gosettings.DefaultPointer(d.IPv6, false)
|
||||||
d.Blacklist.setDefaults()
|
d.Blacklist.setDefaults()
|
||||||
|
d.ServerAddress = gosettings.DefaultValidator(d.ServerAddress,
|
||||||
|
netip.AddrFrom4([4]byte{127, 0, 0, 1}))
|
||||||
|
d.KeepNameserver = gosettings.DefaultPointer(d.KeepNameserver, false)
|
||||||
}
|
}
|
||||||
|
|
||||||
func defaultDNSProviders() []string {
|
func (d DNS) GetFirstPlaintextIPv4() (ipv4 netip.Addr) {
|
||||||
return []string{
|
localhost := netip.AddrFrom4([4]byte{127, 0, 0, 1})
|
||||||
provider.Cloudflare().Name,
|
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 {
|
||||||
@@ -144,26 +155,23 @@ func (d DNS) String() string {
|
|||||||
|
|
||||||
func (d DNS) toLinesNode() (node *gotree.Node) {
|
func (d DNS) toLinesNode() (node *gotree.Node) {
|
||||||
node = gotree.New("DNS settings:")
|
node = gotree.New("DNS settings:")
|
||||||
|
node.Appendf("Keep existing nameserver(s): %s", gosettings.BoolToYesNo(d.KeepNameserver))
|
||||||
|
if *d.KeepNameserver {
|
||||||
|
return node
|
||||||
|
}
|
||||||
|
node.Appendf("DNS server address to use: %s", d.ServerAddress)
|
||||||
|
|
||||||
|
node.Appendf("DNS forwarder server enabled: %s", gosettings.BoolToYesNo(d.ServerEnabled))
|
||||||
|
if !*d.ServerEnabled {
|
||||||
|
return node
|
||||||
|
}
|
||||||
|
|
||||||
node.Appendf("Upstream resolver type: %s", d.UpstreamType)
|
node.Appendf("Upstream resolver type: %s", d.UpstreamType)
|
||||||
|
|
||||||
upstreamResolvers := node.Append("Upstream resolvers:")
|
upstreamResolvers := node.Append("Upstream resolvers:")
|
||||||
if len(d.UpstreamPlainAddresses) > 0 {
|
|
||||||
if d.UpstreamType == DNSUpstreamTypePlain {
|
|
||||||
for _, addr := range d.UpstreamPlainAddresses {
|
|
||||||
upstreamResolvers.Append(addr.String())
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
node.Appendf("Upstream plain addresses: ignored because upstream type is not plain")
|
|
||||||
for _, provider := range d.Providers {
|
for _, provider := range d.Providers {
|
||||||
upstreamResolvers.Append(provider)
|
upstreamResolvers.Append(provider)
|
||||||
}
|
}
|
||||||
}
|
|
||||||
} else {
|
|
||||||
for _, provider := range d.Providers {
|
|
||||||
upstreamResolvers.Append(provider)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
node.Appendf("Caching: %s", gosettings.BoolToYesNo(d.Caching))
|
node.Appendf("Caching: %s", gosettings.BoolToYesNo(d.Caching))
|
||||||
node.Appendf("IPv6: %s", gosettings.BoolToYesNo(d.IPv6))
|
node.Appendf("IPv6: %s", gosettings.BoolToYesNo(d.IPv6))
|
||||||
@@ -180,6 +188,11 @@ func (d DNS) toLinesNode() (node *gotree.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.UpstreamType = r.String("DNS_UPSTREAM_RESOLVER_TYPE")
|
||||||
|
|
||||||
d.UpdatePeriod, err = r.DurationPtr("DNS_UPDATE_PERIOD")
|
d.UpdatePeriod, err = r.DurationPtr("DNS_UPDATE_PERIOD")
|
||||||
@@ -204,42 +217,15 @@ func (d *DNS) read(r *reader.Reader) (err error) {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
err = d.readUpstreamPlainAddresses(r)
|
d.ServerAddress, err = r.NetipAddr("DNS_ADDRESS", reader.RetroKeys("DNS_PLAINTEXT_ADDRESS"))
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
d.KeepNameserver, err = r.BoolPtr("DNS_KEEP_NAMESERVER")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d *DNS) readUpstreamPlainAddresses(r *reader.Reader) (err error) {
|
|
||||||
// If DNS_UPSTREAM_PLAIN_ADDRESSES is set, the user must also set DNS_UPSTREAM_TYPE=plain
|
|
||||||
// for these to be used. This is an added safety measure to reduce misunderstandings, and
|
|
||||||
// reduce odd settings overrides.
|
|
||||||
d.UpstreamPlainAddresses, err = r.CSVNetipAddrPorts("DNS_UPSTREAM_PLAIN_ADDRESSES")
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
// Retro-compatibility - remove in v4
|
|
||||||
// If DNS_ADDRESS is set to a non-localhost address, append it to the other
|
|
||||||
// upstream plain addresses, assuming port 53, and force the upstream type to plain
|
|
||||||
// to maintain retro-compatibility behavior.
|
|
||||||
serverAddress, err := r.NetipAddr("DNS_ADDRESS",
|
|
||||||
reader.RetroKeys("DNS_PLAINTEXT_ADDRESS"),
|
|
||||||
reader.IsRetro("DNS_UPSTREAM_PLAIN_ADDRESSES"))
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
} else if !serverAddress.IsValid() {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
isLocalhost := serverAddress.Compare(netip.AddrFrom4([4]byte{127, 0, 0, 1})) == 0
|
|
||||||
if isLocalhost {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
const defaultPlainPort = 53
|
|
||||||
addrPort := netip.AddrPortFrom(serverAddress, defaultPlainPort)
|
|
||||||
d.UpstreamPlainAddresses = append(d.UpstreamPlainAddresses, addrPort)
|
|
||||||
d.UpstreamType = DNSUpstreamTypePlain
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -1,26 +0,0 @@
|
|||||||
package settings
|
|
||||||
|
|
||||||
import (
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
"github.com/qdm12/dns/v2/pkg/provider"
|
|
||||||
"github.com/stretchr/testify/require"
|
|
||||||
)
|
|
||||||
|
|
||||||
func Test_defaultDNSProviders(t *testing.T) {
|
|
||||||
t.Parallel()
|
|
||||||
|
|
||||||
names := defaultDNSProviders()
|
|
||||||
|
|
||||||
found := false
|
|
||||||
providers := provider.NewProviders()
|
|
||||||
for _, name := range names {
|
|
||||||
provider, err := providers.Get(name)
|
|
||||||
require.NoError(t, err)
|
|
||||||
if len(provider.Plain.IPv4) > 0 {
|
|
||||||
found = true
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
require.True(t, found, "no default DNS provider has a plaintext IPv4 address")
|
|
||||||
}
|
|
||||||
@@ -23,9 +23,7 @@ type DNSBlacklist struct {
|
|||||||
AddBlockedIPs []netip.Addr
|
AddBlockedIPs []netip.Addr
|
||||||
AddBlockedIPPrefixes []netip.Prefix
|
AddBlockedIPPrefixes []netip.Prefix
|
||||||
// RebindingProtectionExemptHostnames is a list of hostnames
|
// RebindingProtectionExemptHostnames is a list of hostnames
|
||||||
// exempt from DNS rebinding protection. It can contain parent
|
// exempt from DNS rebinding protection.
|
||||||
// domains which are of the form "*.example.com". Note the wildcard
|
|
||||||
// can only be used at the start of the hostname.
|
|
||||||
RebindingProtectionExemptHostnames []string
|
RebindingProtectionExemptHostnames []string
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -57,9 +55,6 @@ func (b DNSBlacklist) validate() (err error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
for _, host := range b.RebindingProtectionExemptHostnames {
|
for _, host := range b.RebindingProtectionExemptHostnames {
|
||||||
if len(host) > 2 && host[:2] == "*." {
|
|
||||||
host = host[2:]
|
|
||||||
}
|
|
||||||
if !hostRegex.MatchString(host) {
|
if !hostRegex.MatchString(host) {
|
||||||
return fmt.Errorf("%w: %s", ErrRebindingProtectionExemptHostNotValid, host)
|
return fmt.Errorf("%w: %s", ErrRebindingProtectionExemptHostNotValid, host)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -15,7 +15,7 @@ type Firewall struct {
|
|||||||
InputPorts []uint16
|
InputPorts []uint16
|
||||||
OutboundSubnets []netip.Prefix
|
OutboundSubnets []netip.Prefix
|
||||||
Enabled *bool
|
Enabled *bool
|
||||||
Iptables Iptables
|
Debug *bool
|
||||||
}
|
}
|
||||||
|
|
||||||
func (f Firewall) validate() (err error) {
|
func (f Firewall) validate() (err error) {
|
||||||
@@ -33,11 +33,6 @@ func (f Firewall) validate() (err error) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
err = f.Iptables.validate()
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("iptables settings: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -56,7 +51,7 @@ func (f *Firewall) copy() (copied Firewall) {
|
|||||||
InputPorts: gosettings.CopySlice(f.InputPorts),
|
InputPorts: gosettings.CopySlice(f.InputPorts),
|
||||||
OutboundSubnets: gosettings.CopySlice(f.OutboundSubnets),
|
OutboundSubnets: gosettings.CopySlice(f.OutboundSubnets),
|
||||||
Enabled: gosettings.CopyPointer(f.Enabled),
|
Enabled: gosettings.CopyPointer(f.Enabled),
|
||||||
Iptables: f.Iptables.copy(),
|
Debug: gosettings.CopyPointer(f.Debug),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -68,12 +63,12 @@ func (f *Firewall) overrideWith(other Firewall) {
|
|||||||
f.InputPorts = gosettings.OverrideWithSlice(f.InputPorts, other.InputPorts)
|
f.InputPorts = gosettings.OverrideWithSlice(f.InputPorts, other.InputPorts)
|
||||||
f.OutboundSubnets = gosettings.OverrideWithSlice(f.OutboundSubnets, other.OutboundSubnets)
|
f.OutboundSubnets = gosettings.OverrideWithSlice(f.OutboundSubnets, other.OutboundSubnets)
|
||||||
f.Enabled = gosettings.OverrideWithPointer(f.Enabled, other.Enabled)
|
f.Enabled = gosettings.OverrideWithPointer(f.Enabled, other.Enabled)
|
||||||
f.Iptables.overrideWith(other.Iptables)
|
f.Debug = gosettings.OverrideWithPointer(f.Debug, other.Debug)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (f *Firewall) setDefaults(globalLogLevel string) {
|
func (f *Firewall) setDefaults() {
|
||||||
f.Enabled = gosettings.DefaultPointer(f.Enabled, true)
|
f.Enabled = gosettings.DefaultPointer(f.Enabled, true)
|
||||||
f.Iptables.setDefaults(globalLogLevel)
|
f.Debug = gosettings.DefaultPointer(f.Debug, false)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (f Firewall) String() string {
|
func (f Firewall) String() string {
|
||||||
@@ -88,7 +83,9 @@ func (f Firewall) toLinesNode() (node *gotree.Node) {
|
|||||||
return node
|
return node
|
||||||
}
|
}
|
||||||
|
|
||||||
node.AppendNode(f.Iptables.toLinesNode())
|
if *f.Debug {
|
||||||
|
node.Appendf("Debug mode: on")
|
||||||
|
}
|
||||||
|
|
||||||
if len(f.VPNInputPorts) > 0 {
|
if len(f.VPNInputPorts) > 0 {
|
||||||
vpnInputPortsNode := node.Appendf("VPN input ports:")
|
vpnInputPortsNode := node.Appendf("VPN input ports:")
|
||||||
@@ -136,9 +133,9 @@ func (f *Firewall) read(r *reader.Reader) (err error) {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
err = f.Iptables.read(r)
|
f.Debug, err = r.BoolPtr("FIREWALL_DEBUG")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("reading iptables settings: %w", err)
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
|
|||||||
@@ -4,7 +4,6 @@ import (
|
|||||||
"net/netip"
|
"net/netip"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/qdm12/log"
|
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -16,10 +15,7 @@ func Test_Firewall_validate(t *testing.T) {
|
|||||||
errWrapped error
|
errWrapped error
|
||||||
errMessage string
|
errMessage string
|
||||||
}{
|
}{
|
||||||
"empty": {
|
"empty": {},
|
||||||
errWrapped: log.ErrLevelNotRecognized,
|
|
||||||
errMessage: "iptables settings: log level: level is not recognized: ",
|
|
||||||
},
|
|
||||||
"zero_vpn_input_port": {
|
"zero_vpn_input_port": {
|
||||||
firewall: Firewall{
|
firewall: Firewall{
|
||||||
VPNInputPorts: []uint16{0},
|
VPNInputPorts: []uint16{0},
|
||||||
@@ -45,7 +41,6 @@ func Test_Firewall_validate(t *testing.T) {
|
|||||||
},
|
},
|
||||||
"public_outbound_subnet": {
|
"public_outbound_subnet": {
|
||||||
firewall: Firewall{
|
firewall: Firewall{
|
||||||
Iptables: Iptables{LogLevel: log.LevelInfo.String()},
|
|
||||||
OutboundSubnets: []netip.Prefix{
|
OutboundSubnets: []netip.Prefix{
|
||||||
netip.MustParsePrefix("1.2.3.4/32"),
|
netip.MustParsePrefix("1.2.3.4/32"),
|
||||||
},
|
},
|
||||||
@@ -53,7 +48,6 @@ func Test_Firewall_validate(t *testing.T) {
|
|||||||
},
|
},
|
||||||
"valid_settings": {
|
"valid_settings": {
|
||||||
firewall: Firewall{
|
firewall: Firewall{
|
||||||
Iptables: Iptables{LogLevel: log.LevelInfo.String()},
|
|
||||||
VPNInputPorts: []uint16{100, 101},
|
VPNInputPorts: []uint16{100, 101},
|
||||||
InputPorts: []uint16{200, 201},
|
InputPorts: []uint16{200, 201},
|
||||||
OutboundSubnets: []netip.Prefix{
|
OutboundSubnets: []netip.Prefix{
|
||||||
|
|||||||
@@ -1,67 +0,0 @@
|
|||||||
package settings
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
|
|
||||||
"github.com/qdm12/gosettings"
|
|
||||||
"github.com/qdm12/gosettings/reader"
|
|
||||||
"github.com/qdm12/gotree"
|
|
||||||
"github.com/qdm12/log"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Iptables contains settings to customize iptables.
|
|
||||||
type Iptables struct {
|
|
||||||
LogLevel string
|
|
||||||
}
|
|
||||||
|
|
||||||
func (i Iptables) validate() (err error) {
|
|
||||||
_, err = log.ParseLevel(i.LogLevel)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("log level: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (i *Iptables) copy() (copied Iptables) {
|
|
||||||
return Iptables{
|
|
||||||
LogLevel: i.LogLevel,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (i *Iptables) overrideWith(other Iptables) {
|
|
||||||
i.LogLevel = gosettings.OverrideWithComparable(i.LogLevel, other.LogLevel)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (i *Iptables) setDefaults(globalLogLevel string) {
|
|
||||||
defaultLevel := globalLogLevel
|
|
||||||
if defaultLevel == log.LevelDebug.String() {
|
|
||||||
// Given iptables debug logger is quite verbose, we only turn it to debug level
|
|
||||||
// if it is explicitly asked to be at debug level; even if the global logger is
|
|
||||||
// at the debug level, we keep iptables at info level by default.
|
|
||||||
defaultLevel = log.LevelInfo.String()
|
|
||||||
}
|
|
||||||
i.LogLevel = gosettings.DefaultComparable(i.LogLevel, defaultLevel)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (i Iptables) String() string {
|
|
||||||
return i.toLinesNode().String()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (i Iptables) toLinesNode() (node *gotree.Node) {
|
|
||||||
node = gotree.New("Iptables settings:")
|
|
||||||
node.Appendf("Log level: %s", i.LogLevel)
|
|
||||||
return node
|
|
||||||
}
|
|
||||||
|
|
||||||
func (i *Iptables) read(r *reader.Reader) (err error) {
|
|
||||||
debugMode, err := r.BoolPtr("FIREWALL_DEBUG", reader.IsRetro("FIREWALL_IPTABLES_LOG_LEVEL"))
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if debugMode != nil && *debugMode {
|
|
||||||
i.LogLevel = log.LevelDebug.String()
|
|
||||||
}
|
|
||||||
i.LogLevel = r.String("FIREWALL_IPTABLES_LOG_LEVEL")
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
@@ -1,58 +0,0 @@
|
|||||||
package settings
|
|
||||||
|
|
||||||
import (
|
|
||||||
"net/netip"
|
|
||||||
|
|
||||||
"github.com/qdm12/gosettings"
|
|
||||||
"github.com/qdm12/gosettings/reader"
|
|
||||||
"github.com/qdm12/gotree"
|
|
||||||
)
|
|
||||||
|
|
||||||
// IPv6 contains settings regarding IPv6 configuration.
|
|
||||||
type IPv6 struct {
|
|
||||||
// CheckAddresses are the TCP ip:port addresses to dial to check if
|
|
||||||
// IPv6 is supported, in case a default IPv6 route is found.
|
|
||||||
// It defaults to google and cloudflare IPv6 anycast addresses
|
|
||||||
// [2001:4860:4860::8888]:53,[2606:4700:4700::1111]:53
|
|
||||||
CheckAddresses []netip.AddrPort
|
|
||||||
}
|
|
||||||
|
|
||||||
func (i IPv6) validate() (err error) {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (i *IPv6) copy() (copied IPv6) {
|
|
||||||
return IPv6{
|
|
||||||
CheckAddresses: gosettings.CopySlice(i.CheckAddresses),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (i *IPv6) overrideWith(other IPv6) {
|
|
||||||
i.CheckAddresses = gosettings.OverrideWithSlice(i.CheckAddresses, other.CheckAddresses)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (i *IPv6) setDefaults() {
|
|
||||||
defaultCheckAddresses := []netip.AddrPort{
|
|
||||||
netip.MustParseAddrPort("[2001:4860:4860::8888]:53"),
|
|
||||||
netip.MustParseAddrPort("[2606:4700:4700::1111]:53"),
|
|
||||||
}
|
|
||||||
i.CheckAddresses = gosettings.DefaultSlice(i.CheckAddresses, defaultCheckAddresses)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (i IPv6) String() string {
|
|
||||||
return i.toLinesNode().String()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (i IPv6) toLinesNode() (node *gotree.Node) {
|
|
||||||
node = gotree.New("IPv6 settings:")
|
|
||||||
addrsNode := node.Appendf("Check addresses:")
|
|
||||||
for _, addr := range i.CheckAddresses {
|
|
||||||
addrsNode.Append(addr.String())
|
|
||||||
}
|
|
||||||
return node
|
|
||||||
}
|
|
||||||
|
|
||||||
func (i *IPv6) read(r *reader.Reader) (err error) {
|
|
||||||
i.CheckAddresses, err = r.CSVNetipAddrPorts("IPV6_CHECK_ADDRESSES")
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
@@ -60,6 +60,7 @@ func (o OpenVPNSelection) validate(vpnProvider string) (err error) {
|
|||||||
providers.Giganews,
|
providers.Giganews,
|
||||||
providers.Ipvanish,
|
providers.Ipvanish,
|
||||||
providers.Perfectprivacy,
|
providers.Perfectprivacy,
|
||||||
|
providers.Privado,
|
||||||
providers.Vyprvpn,
|
providers.Vyprvpn,
|
||||||
) {
|
) {
|
||||||
return fmt.Errorf("%w: for VPN service provider %s",
|
return fmt.Errorf("%w: for VPN service provider %s",
|
||||||
@@ -74,8 +75,8 @@ func (o OpenVPNSelection) validate(vpnProvider string) (err error) {
|
|||||||
providers.Privatevpn, providers.Torguard:
|
providers.Privatevpn, providers.Torguard:
|
||||||
// no custom port allowed
|
// no custom port allowed
|
||||||
case providers.Expressvpn, providers.Fastestvpn,
|
case providers.Expressvpn, providers.Fastestvpn,
|
||||||
providers.Giganews, providers.Ipvanish,
|
providers.Giganews, providers.Ipvanish, providers.Nordvpn,
|
||||||
providers.Nordvpn, providers.Purevpn,
|
providers.Privado, providers.Purevpn,
|
||||||
providers.Surfshark, providers.VPNSecure,
|
providers.Surfshark, providers.VPNSecure,
|
||||||
providers.VPNUnlimited, providers.Vyprvpn:
|
providers.VPNUnlimited, providers.Vyprvpn:
|
||||||
return fmt.Errorf("%w: for VPN service provider %s",
|
return fmt.Errorf("%w: for VPN service provider %s",
|
||||||
@@ -98,9 +99,6 @@ func (o OpenVPNSelection) validate(vpnProvider string) (err error) {
|
|||||||
case providers.Perfectprivacy:
|
case providers.Perfectprivacy:
|
||||||
allowedTCP = []uint16{44, 443, 4433}
|
allowedTCP = []uint16{44, 443, 4433}
|
||||||
allowedUDP = []uint16{44, 443, 4433}
|
allowedUDP = []uint16{44, 443, 4433}
|
||||||
case providers.Privado:
|
|
||||||
allowedTCP = []uint16{443, 1194, 8080, 8443}
|
|
||||||
allowedUDP = []uint16{443, 1194, 8080, 8443}
|
|
||||||
case providers.PrivateInternetAccess:
|
case providers.PrivateInternetAccess:
|
||||||
allowedTCP = []uint16{80, 110, 443}
|
allowedTCP = []uint16{80, 110, 443}
|
||||||
allowedUDP = []uint16{53, 1194, 1197, 1198, 8080, 9201}
|
allowedUDP = []uint16{53, 1194, 1197, 1198, 8080, 9201}
|
||||||
@@ -132,6 +130,7 @@ func (o OpenVPNSelection) validate(vpnProvider string) (err error) {
|
|||||||
// Validate EncPreset
|
// Validate EncPreset
|
||||||
if vpnProvider == providers.PrivateInternetAccess {
|
if vpnProvider == providers.PrivateInternetAccess {
|
||||||
validEncryptionPresets := []string{
|
validEncryptionPresets := []string{
|
||||||
|
presets.None,
|
||||||
presets.Normal,
|
presets.Normal,
|
||||||
presets.Strong,
|
presets.Strong,
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,111 +0,0 @@
|
|||||||
package settings
|
|
||||||
|
|
||||||
import (
|
|
||||||
"errors"
|
|
||||||
"fmt"
|
|
||||||
"net/netip"
|
|
||||||
|
|
||||||
"github.com/qdm12/gosettings"
|
|
||||||
"github.com/qdm12/gosettings/reader"
|
|
||||||
"github.com/qdm12/gotree"
|
|
||||||
)
|
|
||||||
|
|
||||||
// PMTUD contains settings to configure Path MTU Discovery.
|
|
||||||
type PMTUD struct {
|
|
||||||
// ICMPAddresses is the redundancy list of addresses to use
|
|
||||||
// for ICMP path MTU discovery. Each address MUST handle ICMP
|
|
||||||
// packets for PMTUD to work.
|
|
||||||
// It cannot be nil in the internal state.
|
|
||||||
ICMPAddresses []netip.Addr `json:"icmp_addresses"`
|
|
||||||
// TCPAddresses is the redundancy list of addresses to use
|
|
||||||
// for TCP path MTU discovery. Each address MUST have a listening
|
|
||||||
// TCP server on the port specified.
|
|
||||||
// It cannot be nil in the internal state.
|
|
||||||
TCPAddresses []netip.AddrPort `json:"tcp_addresses"`
|
|
||||||
}
|
|
||||||
|
|
||||||
var (
|
|
||||||
ErrPMTUDICMPAddressNotValid = errors.New("PMTUD ICMP address is not valid")
|
|
||||||
ErrPMTUDTCPAddressNotValid = errors.New("PMTUD TCP address is not valid")
|
|
||||||
)
|
|
||||||
|
|
||||||
// Validate validates PMTUD settings.
|
|
||||||
func (p PMTUD) validate() (err error) {
|
|
||||||
for i, addr := range p.ICMPAddresses {
|
|
||||||
if !addr.IsValid() {
|
|
||||||
return fmt.Errorf("%w: at index %d", ErrPMTUDICMPAddressNotValid, i)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
for i, addr := range p.TCPAddresses {
|
|
||||||
if !addr.IsValid() {
|
|
||||||
return fmt.Errorf("%w: at index %d", ErrPMTUDTCPAddressNotValid, i)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *PMTUD) copy() (copied PMTUD) {
|
|
||||||
return PMTUD{
|
|
||||||
ICMPAddresses: gosettings.CopySlice(p.ICMPAddresses),
|
|
||||||
TCPAddresses: gosettings.CopySlice(p.TCPAddresses),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *PMTUD) overrideWith(other PMTUD) {
|
|
||||||
p.ICMPAddresses = gosettings.OverrideWithSlice(p.ICMPAddresses, other.ICMPAddresses)
|
|
||||||
p.TCPAddresses = gosettings.OverrideWithSlice(p.TCPAddresses, other.TCPAddresses)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *PMTUD) setDefaults() {
|
|
||||||
defaultICMPAddresses := []netip.Addr{
|
|
||||||
netip.AddrFrom4([4]byte{1, 1, 1, 1}),
|
|
||||||
netip.AddrFrom4([4]byte{8, 8, 8, 8}),
|
|
||||||
}
|
|
||||||
p.ICMPAddresses = gosettings.DefaultSlice(p.ICMPAddresses, defaultICMPAddresses)
|
|
||||||
|
|
||||||
const dnsPort, tlsPort = 53, 443
|
|
||||||
defaultTCPAddresses := []netip.AddrPort{
|
|
||||||
netip.AddrPortFrom(netip.AddrFrom4([4]byte{1, 1, 1, 1}), dnsPort),
|
|
||||||
netip.AddrPortFrom(netip.AddrFrom4([4]byte{8, 8, 8, 8}), dnsPort),
|
|
||||||
netip.AddrPortFrom(netip.AddrFrom4([4]byte{1, 1, 1, 1}), tlsPort),
|
|
||||||
netip.AddrPortFrom(netip.AddrFrom4([4]byte{8, 8, 8, 8}), tlsPort),
|
|
||||||
netip.AddrPortFrom(netip.MustParseAddr("2606:4700:4700::1111"), dnsPort),
|
|
||||||
netip.AddrPortFrom(netip.MustParseAddr("2001:4860:4860::8888"), dnsPort),
|
|
||||||
netip.AddrPortFrom(netip.MustParseAddr("2606:4700:4700::1111"), tlsPort),
|
|
||||||
netip.AddrPortFrom(netip.MustParseAddr("2001:4860:4860::8888"), tlsPort),
|
|
||||||
}
|
|
||||||
p.TCPAddresses = gosettings.DefaultSlice(p.TCPAddresses, defaultTCPAddresses)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p PMTUD) String() string {
|
|
||||||
return p.toLinesNode().String()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p PMTUD) toLinesNode() (node *gotree.Node) {
|
|
||||||
node = gotree.New("Path MTU discovery:")
|
|
||||||
|
|
||||||
icmpAddrNode := node.Append("ICMP addresses:")
|
|
||||||
for _, addr := range p.ICMPAddresses {
|
|
||||||
icmpAddrNode.Append(addr.String())
|
|
||||||
}
|
|
||||||
|
|
||||||
tcpAddrNode := node.Append("TCP addresses:")
|
|
||||||
for _, addr := range p.TCPAddresses {
|
|
||||||
tcpAddrNode.Append(addr.String())
|
|
||||||
}
|
|
||||||
return node
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *PMTUD) read(r *reader.Reader) (err error) {
|
|
||||||
p.ICMPAddresses, err = r.CSVNetipAddresses("PMTUD_ICMP_ADDRESSES")
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
p.TCPAddresses, err = r.CSVNetipAddrPorts("PMTUD_TCP_ADDRESSES")
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
@@ -1,10 +1,8 @@
|
|||||||
package settings
|
package settings
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"errors"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"slices"
|
|
||||||
|
|
||||||
"github.com/qdm12/gluetun/internal/constants/providers"
|
"github.com/qdm12/gluetun/internal/constants/providers"
|
||||||
"github.com/qdm12/gosettings"
|
"github.com/qdm12/gosettings"
|
||||||
@@ -39,28 +37,16 @@ type PortForwarding struct {
|
|||||||
// It can be the empty string to indicate to NOT run a command.
|
// It can be the empty string to indicate to NOT run a command.
|
||||||
// It cannot be nil in the internal state.
|
// It cannot be nil in the internal state.
|
||||||
DownCommand *string `json:"down_command"`
|
DownCommand *string `json:"down_command"`
|
||||||
// ListeningPorts are the ports traffic would be redirected to from the
|
// ListeningPort is the port traffic would be redirected to from the
|
||||||
// forwarded ports. The redirection is disabled if it is the slice [0],
|
// forwarded port. The redirection is disabled if it is set to 0, which
|
||||||
// which is its default as well. If set and not [0], its length must match
|
// is its default as well.
|
||||||
// the PortsCount value, such that each forwarded port is redirected to
|
ListeningPort *uint16 `json:"listening_port"`
|
||||||
// the corresponding listening port.
|
|
||||||
ListeningPorts []uint16 `json:"listening_port"`
|
|
||||||
// PortsCount is the number of ports to forward. It is optional for ProtonVPN
|
|
||||||
// and be between 1 and 5. For other providers, it must be set to 1 if port
|
|
||||||
// forwarding is enabled.
|
|
||||||
PortsCount uint16 `json:"ports_count"`
|
|
||||||
// Username is only used for Private Internet Access port forwarding.
|
// Username is only used for Private Internet Access port forwarding.
|
||||||
Username string `json:"username"`
|
Username string `json:"username"`
|
||||||
// Password is only used for Private Internet Access port forwarding.
|
// Password is only used for Private Internet Access port forwarding.
|
||||||
Password string `json:"password"`
|
Password string `json:"password"`
|
||||||
}
|
}
|
||||||
|
|
||||||
var (
|
|
||||||
ErrPortsCountTooHigh = errors.New("ports count too high")
|
|
||||||
ErrListeningPortsLen = errors.New("listening ports length must be equal to ports count")
|
|
||||||
ErrListeningPortZero = errors.New("listening port cannot be 0")
|
|
||||||
)
|
|
||||||
|
|
||||||
func (p PortForwarding) Validate(vpnProvider string) (err error) {
|
func (p PortForwarding) Validate(vpnProvider string) (err error) {
|
||||||
if !*p.Enabled {
|
if !*p.Enabled {
|
||||||
return nil
|
return nil
|
||||||
@@ -89,36 +75,13 @@ func (p PortForwarding) Validate(vpnProvider string) (err error) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
switch providerSelected {
|
if providerSelected == providers.PrivateInternetAccess {
|
||||||
case providers.PrivateInternetAccess:
|
|
||||||
const maxPortsCount = 1
|
|
||||||
switch {
|
switch {
|
||||||
case p.PortsCount > maxPortsCount:
|
|
||||||
return fmt.Errorf("%w: %d > %d", ErrPortsCountTooHigh, p.PortsCount, maxPortsCount)
|
|
||||||
case p.Username == "":
|
case p.Username == "":
|
||||||
return fmt.Errorf("%w", ErrPortForwardingUserEmpty)
|
return fmt.Errorf("%w", ErrPortForwardingUserEmpty)
|
||||||
case p.Password == "":
|
case p.Password == "":
|
||||||
return fmt.Errorf("%w", ErrPortForwardingPasswordEmpty)
|
return fmt.Errorf("%w", ErrPortForwardingPasswordEmpty)
|
||||||
}
|
}
|
||||||
case providers.Protonvpn:
|
|
||||||
const maxPortsCount = 4
|
|
||||||
if p.PortsCount > maxPortsCount {
|
|
||||||
return fmt.Errorf("%w: %d > %d", ErrPortsCountTooHigh, p.PortsCount, maxPortsCount)
|
|
||||||
}
|
|
||||||
default:
|
|
||||||
const maxPortsCount = 1
|
|
||||||
if p.PortsCount > maxPortsCount {
|
|
||||||
return fmt.Errorf("%w: %d > %d", ErrPortsCountTooHigh, p.PortsCount, maxPortsCount)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if !slices.Equal(p.ListeningPorts, []uint16{0}) {
|
|
||||||
switch {
|
|
||||||
case len(p.ListeningPorts) != int(p.PortsCount):
|
|
||||||
return fmt.Errorf("%w: %d != %d", ErrListeningPortsLen, len(p.ListeningPorts), p.PortsCount)
|
|
||||||
case slices.Contains(p.ListeningPorts, 0):
|
|
||||||
return fmt.Errorf("%w: in %v", ErrListeningPortZero, p.ListeningPorts)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
@@ -131,7 +94,7 @@ func (p *PortForwarding) Copy() (copied PortForwarding) {
|
|||||||
Filepath: gosettings.CopyPointer(p.Filepath),
|
Filepath: gosettings.CopyPointer(p.Filepath),
|
||||||
UpCommand: gosettings.CopyPointer(p.UpCommand),
|
UpCommand: gosettings.CopyPointer(p.UpCommand),
|
||||||
DownCommand: gosettings.CopyPointer(p.DownCommand),
|
DownCommand: gosettings.CopyPointer(p.DownCommand),
|
||||||
ListeningPorts: gosettings.CopySlice(p.ListeningPorts),
|
ListeningPort: gosettings.CopyPointer(p.ListeningPort),
|
||||||
Username: p.Username,
|
Username: p.Username,
|
||||||
Password: p.Password,
|
Password: p.Password,
|
||||||
}
|
}
|
||||||
@@ -143,7 +106,7 @@ func (p *PortForwarding) OverrideWith(other PortForwarding) {
|
|||||||
p.Filepath = gosettings.OverrideWithPointer(p.Filepath, other.Filepath)
|
p.Filepath = gosettings.OverrideWithPointer(p.Filepath, other.Filepath)
|
||||||
p.UpCommand = gosettings.OverrideWithPointer(p.UpCommand, other.UpCommand)
|
p.UpCommand = gosettings.OverrideWithPointer(p.UpCommand, other.UpCommand)
|
||||||
p.DownCommand = gosettings.OverrideWithPointer(p.DownCommand, other.DownCommand)
|
p.DownCommand = gosettings.OverrideWithPointer(p.DownCommand, other.DownCommand)
|
||||||
p.ListeningPorts = gosettings.OverrideWithSlice(p.ListeningPorts, other.ListeningPorts)
|
p.ListeningPort = gosettings.OverrideWithPointer(p.ListeningPort, other.ListeningPort)
|
||||||
p.Username = gosettings.OverrideWithComparable(p.Username, other.Username)
|
p.Username = gosettings.OverrideWithComparable(p.Username, other.Username)
|
||||||
p.Password = gosettings.OverrideWithComparable(p.Password, other.Password)
|
p.Password = gosettings.OverrideWithComparable(p.Password, other.Password)
|
||||||
}
|
}
|
||||||
@@ -154,8 +117,7 @@ func (p *PortForwarding) setDefaults() {
|
|||||||
p.Filepath = gosettings.DefaultPointer(p.Filepath, "/tmp/gluetun/forwarded_port")
|
p.Filepath = gosettings.DefaultPointer(p.Filepath, "/tmp/gluetun/forwarded_port")
|
||||||
p.UpCommand = gosettings.DefaultPointer(p.UpCommand, "")
|
p.UpCommand = gosettings.DefaultPointer(p.UpCommand, "")
|
||||||
p.DownCommand = gosettings.DefaultPointer(p.DownCommand, "")
|
p.DownCommand = gosettings.DefaultPointer(p.DownCommand, "")
|
||||||
p.ListeningPorts = gosettings.DefaultSlice(p.ListeningPorts, []uint16{0}) // disabled
|
p.ListeningPort = gosettings.DefaultPointer(p.ListeningPort, 0)
|
||||||
p.PortsCount = gosettings.DefaultComparable(p.PortsCount, 1)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p PortForwarding) String() string {
|
func (p PortForwarding) String() string {
|
||||||
@@ -169,14 +131,11 @@ func (p PortForwarding) toLinesNode() (node *gotree.Node) {
|
|||||||
|
|
||||||
node = gotree.New("Automatic port forwarding settings:")
|
node = gotree.New("Automatic port forwarding settings:")
|
||||||
|
|
||||||
node.Appendf("Number of ports to be forwarded: %d", p.PortsCount)
|
listeningPort := "disabled"
|
||||||
|
if *p.ListeningPort != 0 {
|
||||||
if !slices.Equal(p.ListeningPorts, []uint16{0}) {
|
listeningPort = fmt.Sprintf("%d", *p.ListeningPort)
|
||||||
redirNode := node.Appendf("Redirection for listening ports:")
|
|
||||||
for i, port := range p.ListeningPorts {
|
|
||||||
redirNode.Appendf("Port #%d -> %d", i+1, port)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
node.Appendf("Redirection listening port: %s", listeningPort)
|
||||||
|
|
||||||
if *p.Provider == "" {
|
if *p.Provider == "" {
|
||||||
node.Appendf("Use port forwarding code for current provider")
|
node.Appendf("Use port forwarding code for current provider")
|
||||||
@@ -231,13 +190,7 @@ func (p *PortForwarding) read(r *reader.Reader) (err error) {
|
|||||||
p.DownCommand = r.Get("VPN_PORT_FORWARDING_DOWN_COMMAND",
|
p.DownCommand = r.Get("VPN_PORT_FORWARDING_DOWN_COMMAND",
|
||||||
reader.ForceLowercase(false))
|
reader.ForceLowercase(false))
|
||||||
|
|
||||||
p.ListeningPorts, err = r.CSVUint16("VPN_PORT_FORWARDING_LISTENING_PORTS",
|
p.ListeningPort, err = r.Uint16Ptr("VPN_PORT_FORWARDING_LISTENING_PORT")
|
||||||
reader.RetroKeys("VPN_PORT_FORWARDING_LISTENING_PORT"))
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
p.PortsCount, err = r.Uint16("VPN_PORT_FORWARDING_PORTS_COUNT")
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,8 +2,6 @@ package settings
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"slices"
|
|
||||||
"sort"
|
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/qdm12/gluetun/internal/constants/providers"
|
"github.com/qdm12/gluetun/internal/constants/providers"
|
||||||
@@ -30,18 +28,10 @@ type Provider struct {
|
|||||||
func (p *Provider) validate(vpnType string, filterChoicesGetter FilterChoicesGetter, warner Warner) (err error) {
|
func (p *Provider) validate(vpnType string, filterChoicesGetter FilterChoicesGetter, warner Warner) (err error) {
|
||||||
// Validate Name
|
// Validate Name
|
||||||
var validNames []string
|
var validNames []string
|
||||||
switch vpnType {
|
if vpnType == vpn.OpenVPN {
|
||||||
case vpn.AmneziaWg:
|
|
||||||
validNames = []string{providers.Custom}
|
|
||||||
case vpn.OpenVPN:
|
|
||||||
validNames = providers.AllWithCustom()
|
validNames = providers.AllWithCustom()
|
||||||
validNames = append(validNames, "pia") // Retro-compatibility
|
validNames = append(validNames, "pia") // Retro-compatibility
|
||||||
// Remove Mullvad since it no longer supports OpenVPN as of January 15th, 2026
|
} else { // Wireguard
|
||||||
mullvadIndex := slices.Index(validNames, providers.Mullvad)
|
|
||||||
validNames[mullvadIndex], validNames[len(validNames)-1] = validNames[len(validNames)-1], validNames[mullvadIndex]
|
|
||||||
validNames = validNames[:len(validNames)-1]
|
|
||||||
sort.Strings(validNames)
|
|
||||||
case vpn.Wireguard:
|
|
||||||
validNames = []string{
|
validNames = []string{
|
||||||
providers.Airvpn,
|
providers.Airvpn,
|
||||||
providers.Custom,
|
providers.Custom,
|
||||||
@@ -55,7 +45,7 @@ func (p *Provider) validate(vpnType string, filterChoicesGetter FilterChoicesGet
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
if err = validate.IsOneOf(p.Name, validNames...); err != nil {
|
if err = validate.IsOneOf(p.Name, validNames...); err != nil {
|
||||||
return fmt.Errorf("%w for %s: %w", ErrVPNProviderNameNotValid, vpnType, err)
|
return fmt.Errorf("%w for Wireguard: %w", ErrVPNProviderNameNotValid, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
err = p.ServerSelection.validate(p.Name, filterChoicesGetter, warner)
|
err = p.ServerSelection.validate(p.Name, filterChoicesGetter, warner)
|
||||||
|
|||||||
@@ -87,7 +87,7 @@ func (ss *ServerSelection) validate(vpnServiceProvider string,
|
|||||||
filterChoicesGetter FilterChoicesGetter, warner Warner,
|
filterChoicesGetter FilterChoicesGetter, warner Warner,
|
||||||
) (err error) {
|
) (err error) {
|
||||||
switch ss.VPN {
|
switch ss.VPN {
|
||||||
case vpn.AmneziaWg, vpn.OpenVPN, vpn.Wireguard:
|
case vpn.OpenVPN, vpn.Wireguard:
|
||||||
default:
|
default:
|
||||||
return fmt.Errorf("%w: %s", ErrVPNTypeNotValid, ss.VPN)
|
return fmt.Errorf("%w: %s", ErrVPNTypeNotValid, ss.VPN)
|
||||||
}
|
}
|
||||||
@@ -518,8 +518,7 @@ func (ss *ServerSelection) read(r *reader.Reader,
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
amneziawg := ss.VPN == vpn.AmneziaWg
|
err = ss.Wireguard.read(r)
|
||||||
err = ss.Wireguard.read(r, amneziawg)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ package settings
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"net/netip"
|
||||||
|
|
||||||
"github.com/qdm12/gluetun/internal/configuration/settings/helpers"
|
"github.com/qdm12/gluetun/internal/configuration/settings/helpers"
|
||||||
"github.com/qdm12/gluetun/internal/constants/providers"
|
"github.com/qdm12/gluetun/internal/constants/providers"
|
||||||
@@ -26,9 +27,7 @@ type Settings struct {
|
|||||||
Updater Updater
|
Updater Updater
|
||||||
Version Version
|
Version Version
|
||||||
VPN VPN
|
VPN VPN
|
||||||
IPv6 IPv6
|
|
||||||
Pprof pprof.Settings
|
Pprof pprof.Settings
|
||||||
BoringPoll BoringPoll
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type FilterChoicesGetter interface {
|
type FilterChoicesGetter interface {
|
||||||
@@ -54,12 +53,10 @@ func (s *Settings) Validate(filterChoicesGetter FilterChoicesGetter, ipv6Support
|
|||||||
"system": s.System.validate,
|
"system": s.System.validate,
|
||||||
"updater": s.Updater.Validate,
|
"updater": s.Updater.Validate,
|
||||||
"version": s.Version.validate,
|
"version": s.Version.validate,
|
||||||
"ipv6": s.IPv6.validate,
|
|
||||||
// Pprof validation done in pprof constructor
|
// Pprof validation done in pprof constructor
|
||||||
"VPN": func() error {
|
"VPN": func() error {
|
||||||
return s.VPN.Validate(filterChoicesGetter, ipv6Supported, warner)
|
return s.VPN.Validate(filterChoicesGetter, ipv6Supported, warner)
|
||||||
},
|
},
|
||||||
"boring poll": s.BoringPoll.validate,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
for name, validation := range nameToValidation {
|
for name, validation := range nameToValidation {
|
||||||
@@ -88,8 +85,6 @@ func (s *Settings) copy() (copied Settings) {
|
|||||||
Version: s.Version.copy(),
|
Version: s.Version.copy(),
|
||||||
VPN: s.VPN.Copy(),
|
VPN: s.VPN.Copy(),
|
||||||
Pprof: s.Pprof.Copy(),
|
Pprof: s.Pprof.Copy(),
|
||||||
BoringPoll: s.BoringPoll.Copy(),
|
|
||||||
IPv6: s.IPv6.copy(),
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -111,8 +106,6 @@ func (s *Settings) OverrideWith(other Settings,
|
|||||||
patchedSettings.Version.overrideWith(other.Version)
|
patchedSettings.Version.overrideWith(other.Version)
|
||||||
patchedSettings.VPN.OverrideWith(other.VPN)
|
patchedSettings.VPN.OverrideWith(other.VPN)
|
||||||
patchedSettings.Pprof.OverrideWith(other.Pprof)
|
patchedSettings.Pprof.OverrideWith(other.Pprof)
|
||||||
patchedSettings.BoringPoll.overrideWith(other.BoringPoll)
|
|
||||||
patchedSettings.IPv6.overrideWith(other.IPv6)
|
|
||||||
err = patchedSettings.Validate(filterChoicesGetter, ipv6Supported, warner)
|
err = patchedSettings.Validate(filterChoicesGetter, ipv6Supported, warner)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
@@ -124,12 +117,10 @@ func (s *Settings) OverrideWith(other Settings,
|
|||||||
func (s *Settings) SetDefaults() {
|
func (s *Settings) SetDefaults() {
|
||||||
s.ControlServer.setDefaults()
|
s.ControlServer.setDefaults()
|
||||||
s.DNS.setDefaults()
|
s.DNS.setDefaults()
|
||||||
s.Log.setDefaults()
|
s.Firewall.setDefaults()
|
||||||
s.Firewall.setDefaults(s.Log.Level)
|
|
||||||
s.Health.SetDefaults()
|
s.Health.SetDefaults()
|
||||||
s.HTTPProxy.setDefaults()
|
s.HTTPProxy.setDefaults()
|
||||||
s.Log.setDefaults()
|
s.Log.setDefaults()
|
||||||
s.IPv6.setDefaults()
|
|
||||||
s.PublicIP.setDefaults()
|
s.PublicIP.setDefaults()
|
||||||
s.Shadowsocks.setDefaults()
|
s.Shadowsocks.setDefaults()
|
||||||
s.Storage.setDefaults()
|
s.Storage.setDefaults()
|
||||||
@@ -138,7 +129,6 @@ func (s *Settings) SetDefaults() {
|
|||||||
s.VPN.setDefaults()
|
s.VPN.setDefaults()
|
||||||
s.Updater.SetDefaults(s.VPN.Provider.Name)
|
s.Updater.SetDefaults(s.VPN.Provider.Name)
|
||||||
s.Pprof.SetDefaults()
|
s.Pprof.SetDefaults()
|
||||||
s.BoringPoll.setDefaults()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s Settings) String() string {
|
func (s Settings) String() string {
|
||||||
@@ -152,7 +142,6 @@ func (s Settings) toLinesNode() (node *gotree.Node) {
|
|||||||
node.AppendNode(s.DNS.toLinesNode())
|
node.AppendNode(s.DNS.toLinesNode())
|
||||||
node.AppendNode(s.Firewall.toLinesNode())
|
node.AppendNode(s.Firewall.toLinesNode())
|
||||||
node.AppendNode(s.Log.toLinesNode())
|
node.AppendNode(s.Log.toLinesNode())
|
||||||
node.AppendNode(s.IPv6.toLinesNode())
|
|
||||||
node.AppendNode(s.Health.toLinesNode())
|
node.AppendNode(s.Health.toLinesNode())
|
||||||
node.AppendNode(s.Shadowsocks.toLinesNode())
|
node.AppendNode(s.Shadowsocks.toLinesNode())
|
||||||
node.AppendNode(s.HTTPProxy.toLinesNode())
|
node.AppendNode(s.HTTPProxy.toLinesNode())
|
||||||
@@ -163,7 +152,6 @@ func (s Settings) toLinesNode() (node *gotree.Node) {
|
|||||||
node.AppendNode(s.Updater.toLinesNode())
|
node.AppendNode(s.Updater.toLinesNode())
|
||||||
node.AppendNode(s.Version.toLinesNode())
|
node.AppendNode(s.Version.toLinesNode())
|
||||||
node.AppendNode(s.Pprof.ToLinesNode())
|
node.AppendNode(s.Pprof.ToLinesNode())
|
||||||
node.AppendNode(s.BoringPoll.toLinesNode())
|
|
||||||
|
|
||||||
return node
|
return node
|
||||||
}
|
}
|
||||||
@@ -186,11 +174,13 @@ func (s Settings) Warnings() (warnings []string) {
|
|||||||
"by creating an issue, attaching the new certificate and we will update Gluetun.")
|
"by creating an issue, attaching the new certificate and we will update Gluetun.")
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, upstreamAddress := range s.DNS.UpstreamPlainAddresses {
|
// TODO remove in v4
|
||||||
if upstreamAddress.Addr().IsPrivate() {
|
if s.DNS.ServerAddress.Unmap().Compare(netip.AddrFrom4([4]byte{127, 0, 0, 1})) != 0 {
|
||||||
warnings = append(warnings, "DNS upstream address "+upstreamAddress.String()+" is private: "+
|
warnings = append(warnings, "DNS address is set to "+s.DNS.ServerAddress.String()+
|
||||||
"DNS traffic might leak out of the VPN tunnel to that address.")
|
" so the local forwarding DNS server will not be used."+
|
||||||
}
|
" The default value changed to 127.0.0.1 so it uses the internal DNS server."+
|
||||||
|
" If this server fails to start, the IPv4 address of the first plaintext DNS server"+
|
||||||
|
" corresponding to the first DNS provider chosen is used.")
|
||||||
}
|
}
|
||||||
|
|
||||||
return warnings
|
return warnings
|
||||||
@@ -218,9 +208,7 @@ func (s *Settings) Read(r *reader.Reader, warner Warner) (err error) {
|
|||||||
"updater": s.Updater.read,
|
"updater": s.Updater.read,
|
||||||
"version": s.Version.read,
|
"version": s.Version.read,
|
||||||
"VPN": s.VPN.read,
|
"VPN": s.VPN.read,
|
||||||
"IPv6": s.IPv6.read,
|
|
||||||
"profiling": s.Pprof.Read,
|
"profiling": s.Pprof.Read,
|
||||||
"boring poll": s.BoringPoll.read,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
for name, read := range readFunctions {
|
for name, read := range readFunctions {
|
||||||
|
|||||||
@@ -29,28 +29,18 @@ func Test_Settings_String(t *testing.T) {
|
|||||||
| | └── OpenVPN server selection settings:
|
| | └── OpenVPN server selection settings:
|
||||||
| | ├── Protocol: UDP
|
| | ├── Protocol: UDP
|
||||||
| | └── Private Internet Access encryption preset: strong
|
| | └── Private Internet Access encryption preset: strong
|
||||||
| ├── OpenVPN settings:
|
| └── OpenVPN settings:
|
||||||
| | ├── OpenVPN version: 2.6
|
| ├── OpenVPN version: 2.6
|
||||||
| | ├── User: [not set]
|
| ├── User: [not set]
|
||||||
| | ├── Password: [not set]
|
| ├── Password: [not set]
|
||||||
| | ├── Private Internet Access encryption preset: strong
|
| ├── Private Internet Access encryption preset: strong
|
||||||
| | ├── Network interface: tun0
|
| ├── Network interface: tun0
|
||||||
| | ├── Run OpenVPN as: root
|
| ├── Run OpenVPN as: root
|
||||||
| | └── Verbosity level: 1
|
| └── Verbosity level: 1
|
||||||
| └── Path MTU discovery:
|
|
||||||
| ├── ICMP addresses:
|
|
||||||
| | ├── 1.1.1.1
|
|
||||||
| | └── 8.8.8.8
|
|
||||||
| └── TCP addresses:
|
|
||||||
| ├── 1.1.1.1:53
|
|
||||||
| ├── 8.8.8.8:53
|
|
||||||
| ├── 1.1.1.1:443
|
|
||||||
| ├── 8.8.8.8:443
|
|
||||||
| ├── [2606:4700:4700::1111]:53
|
|
||||||
| ├── [2001:4860:4860::8888]:53
|
|
||||||
| ├── [2606:4700:4700::1111]:443
|
|
||||||
| └── [2001:4860:4860::8888]:443
|
|
||||||
├── DNS settings:
|
├── DNS settings:
|
||||||
|
| ├── Keep existing nameserver(s): no
|
||||||
|
| ├── DNS server address to use: 127.0.0.1
|
||||||
|
| ├── DNS forwarder server enabled: yes
|
||||||
| ├── Upstream resolver type: dot
|
| ├── Upstream resolver type: dot
|
||||||
| ├── Upstream resolvers:
|
| ├── Upstream resolvers:
|
||||||
| | └── Cloudflare
|
| | └── Cloudflare
|
||||||
@@ -62,15 +52,9 @@ func Test_Settings_String(t *testing.T) {
|
|||||||
| ├── Block ads: no
|
| ├── Block ads: no
|
||||||
| └── Block surveillance: yes
|
| └── Block surveillance: yes
|
||||||
├── Firewall settings:
|
├── Firewall settings:
|
||||||
| ├── Enabled: yes
|
| └── Enabled: yes
|
||||||
| └── Iptables settings:
|
|
||||||
| └── Log level: INFO
|
|
||||||
├── Log settings:
|
├── Log settings:
|
||||||
| └── Log level: INFO
|
| └── Log level: INFO
|
||||||
├── IPv6 settings:
|
|
||||||
| └── Check addresses:
|
|
||||||
| ├── [2001:4860:4860::8888]:53
|
|
||||||
| └── [2606:4700:4700::1111]:53
|
|
||||||
├── Health settings:
|
├── Health settings:
|
||||||
| ├── Server listening address: 127.0.0.1:9999
|
| ├── Server listening address: 127.0.0.1:9999
|
||||||
| ├── Target addresses:
|
| ├── Target addresses:
|
||||||
|
|||||||
@@ -21,6 +21,10 @@ type Updater struct {
|
|||||||
// updater. It cannot be nil in the internal state.
|
// updater. It cannot be nil in the internal state.
|
||||||
// TODO change to value and add Enabled field.
|
// TODO change to value and add Enabled field.
|
||||||
Period *time.Duration
|
Period *time.Duration
|
||||||
|
// DNSAddress is the DNS server address to use
|
||||||
|
// to resolve VPN server hostnames to IP addresses.
|
||||||
|
// It cannot be the empty string in the internal state.
|
||||||
|
DNSAddress string
|
||||||
// MinRatio is the minimum ratio of servers to
|
// MinRatio is the minimum ratio of servers to
|
||||||
// find per provider, compared to the total current
|
// find per provider, compared to the total current
|
||||||
// number of servers. It defaults to 0.8.
|
// number of servers. It defaults to 0.8.
|
||||||
@@ -72,6 +76,7 @@ func (u Updater) Validate() (err error) {
|
|||||||
func (u *Updater) copy() (copied Updater) {
|
func (u *Updater) copy() (copied Updater) {
|
||||||
return Updater{
|
return Updater{
|
||||||
Period: gosettings.CopyPointer(u.Period),
|
Period: gosettings.CopyPointer(u.Period),
|
||||||
|
DNSAddress: u.DNSAddress,
|
||||||
MinRatio: u.MinRatio,
|
MinRatio: u.MinRatio,
|
||||||
Providers: gosettings.CopySlice(u.Providers),
|
Providers: gosettings.CopySlice(u.Providers),
|
||||||
ProtonEmail: gosettings.CopyPointer(u.ProtonEmail),
|
ProtonEmail: gosettings.CopyPointer(u.ProtonEmail),
|
||||||
@@ -84,6 +89,7 @@ func (u *Updater) copy() (copied Updater) {
|
|||||||
// settings.
|
// settings.
|
||||||
func (u *Updater) overrideWith(other Updater) {
|
func (u *Updater) overrideWith(other Updater) {
|
||||||
u.Period = gosettings.OverrideWithPointer(u.Period, other.Period)
|
u.Period = gosettings.OverrideWithPointer(u.Period, other.Period)
|
||||||
|
u.DNSAddress = gosettings.OverrideWithComparable(u.DNSAddress, other.DNSAddress)
|
||||||
u.MinRatio = gosettings.OverrideWithComparable(u.MinRatio, other.MinRatio)
|
u.MinRatio = gosettings.OverrideWithComparable(u.MinRatio, other.MinRatio)
|
||||||
u.Providers = gosettings.OverrideWithSlice(u.Providers, other.Providers)
|
u.Providers = gosettings.OverrideWithSlice(u.Providers, other.Providers)
|
||||||
u.ProtonEmail = gosettings.OverrideWithPointer(u.ProtonEmail, other.ProtonEmail)
|
u.ProtonEmail = gosettings.OverrideWithPointer(u.ProtonEmail, other.ProtonEmail)
|
||||||
@@ -92,6 +98,7 @@ func (u *Updater) overrideWith(other Updater) {
|
|||||||
|
|
||||||
func (u *Updater) SetDefaults(vpnProvider string) {
|
func (u *Updater) SetDefaults(vpnProvider string) {
|
||||||
u.Period = gosettings.DefaultPointer(u.Period, 0)
|
u.Period = gosettings.DefaultPointer(u.Period, 0)
|
||||||
|
u.DNSAddress = gosettings.DefaultComparable(u.DNSAddress, "1.1.1.1:53")
|
||||||
|
|
||||||
if u.MinRatio == 0 {
|
if u.MinRatio == 0 {
|
||||||
const defaultMinRatio = 0.8
|
const defaultMinRatio = 0.8
|
||||||
@@ -118,6 +125,7 @@ func (u Updater) toLinesNode() (node *gotree.Node) {
|
|||||||
|
|
||||||
node = gotree.New("Server data updater settings:")
|
node = gotree.New("Server data updater settings:")
|
||||||
node.Appendf("Update period: %s", *u.Period)
|
node.Appendf("Update period: %s", *u.Period)
|
||||||
|
node.Appendf("DNS address: %s", u.DNSAddress)
|
||||||
node.Appendf("Minimum ratio: %.1f", u.MinRatio)
|
node.Appendf("Minimum ratio: %.1f", u.MinRatio)
|
||||||
node.Appendf("Providers to update: %s", strings.Join(u.Providers, ", "))
|
node.Appendf("Providers to update: %s", strings.Join(u.Providers, ", "))
|
||||||
if slices.Contains(u.Providers, providers.Protonvpn) {
|
if slices.Contains(u.Providers, providers.Protonvpn) {
|
||||||
@@ -134,6 +142,11 @@ func (u *Updater) read(r *reader.Reader) (err error) {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
u.DNSAddress, err = readUpdaterDNSAddress()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
u.MinRatio, err = r.Float64("UPDATER_MIN_RATIO")
|
u.MinRatio, err = r.Float64("UPDATER_MIN_RATIO")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
@@ -153,3 +166,12 @@ func (u *Updater) read(r *reader.Reader) (err error) {
|
|||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func readUpdaterDNSAddress() (address string, err error) {
|
||||||
|
// TODO this is currently using Cloudflare in
|
||||||
|
// plaintext to not be blocked by DNS over TLS by default.
|
||||||
|
// If a plaintext address is set in the DNS settings, this one will be used.
|
||||||
|
// use custom future encrypted DNS written in Go without blocking
|
||||||
|
// as it's too much trouble to start another parallel unbound instance for now.
|
||||||
|
return "", nil
|
||||||
|
}
|
||||||
|
|||||||
@@ -16,26 +16,14 @@ type VPN struct {
|
|||||||
// empty string in the internal state.
|
// empty string in the internal state.
|
||||||
Type string `json:"type"`
|
Type string `json:"type"`
|
||||||
Provider Provider `json:"provider"`
|
Provider Provider `json:"provider"`
|
||||||
AmneziaWg AmneziaWg `json:"amneziawg"`
|
|
||||||
OpenVPN OpenVPN `json:"openvpn"`
|
OpenVPN OpenVPN `json:"openvpn"`
|
||||||
Wireguard Wireguard `json:"wireguard"`
|
Wireguard Wireguard `json:"wireguard"`
|
||||||
PMTUD PMTUD `json:"pmtud"`
|
|
||||||
// UpCommand is the command to use when the VPN connection is up.
|
|
||||||
// It can be the empty string to indicate not to run a command.
|
|
||||||
// It cannot be nil in the internal state.
|
|
||||||
UpCommand *string `json:"up_command"`
|
|
||||||
// DownCommand is the command to use after the VPN connection goes down.
|
|
||||||
// It can be the empty string to indicate to NOT run a command.
|
|
||||||
// It cannot be nil in the internal state.
|
|
||||||
DownCommand *string `json:"down_command"`
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Validate validates VPN settings, using the filter choices getter (aka servers data storage),
|
|
||||||
// and if IPv6 is supported or not.
|
|
||||||
// TODO v4 remove pointer for receiver (because of Surfshark).
|
// TODO v4 remove pointer for receiver (because of Surfshark).
|
||||||
func (v *VPN) Validate(filterChoicesGetter FilterChoicesGetter, ipv6Supported bool, warner Warner) (err error) {
|
func (v *VPN) Validate(filterChoicesGetter FilterChoicesGetter, ipv6Supported bool, warner Warner) (err error) {
|
||||||
// Validate Type
|
// Validate Type
|
||||||
validVPNTypes := []string{vpn.AmneziaWg, vpn.OpenVPN, vpn.Wireguard}
|
validVPNTypes := []string{vpn.OpenVPN, vpn.Wireguard}
|
||||||
if err = validate.IsOneOf(v.Type, validVPNTypes...); err != nil {
|
if err = validate.IsOneOf(v.Type, validVPNTypes...); err != nil {
|
||||||
return fmt.Errorf("%w: %w", ErrVPNTypeNotValid, err)
|
return fmt.Errorf("%w: %w", ErrVPNTypeNotValid, err)
|
||||||
}
|
}
|
||||||
@@ -45,30 +33,18 @@ func (v *VPN) Validate(filterChoicesGetter FilterChoicesGetter, ipv6Supported bo
|
|||||||
return fmt.Errorf("provider settings: %w", err)
|
return fmt.Errorf("provider settings: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
switch v.Type {
|
if v.Type == vpn.OpenVPN {
|
||||||
case vpn.AmneziaWg:
|
|
||||||
err = v.AmneziaWg.validate(v.Provider.Name, ipv6Supported)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("AmneziaWG settings: %w", err)
|
|
||||||
}
|
|
||||||
case vpn.OpenVPN:
|
|
||||||
err := v.OpenVPN.validate(v.Provider.Name)
|
err := v.OpenVPN.validate(v.Provider.Name)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("OpenVPN settings: %w", err)
|
return fmt.Errorf("OpenVPN settings: %w", err)
|
||||||
}
|
}
|
||||||
case vpn.Wireguard:
|
} else {
|
||||||
const amneziawg = false
|
err := v.Wireguard.validate(v.Provider.Name, ipv6Supported)
|
||||||
err := v.Wireguard.validate(v.Provider.Name, ipv6Supported, amneziawg)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("Wireguard settings: %w", err)
|
return fmt.Errorf("Wireguard settings: %w", err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
err = v.PMTUD.validate()
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("PMTUD settings: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -76,35 +52,23 @@ func (v *VPN) Copy() (copied VPN) {
|
|||||||
return VPN{
|
return VPN{
|
||||||
Type: v.Type,
|
Type: v.Type,
|
||||||
Provider: v.Provider.copy(),
|
Provider: v.Provider.copy(),
|
||||||
AmneziaWg: v.AmneziaWg.copy(),
|
|
||||||
OpenVPN: v.OpenVPN.copy(),
|
OpenVPN: v.OpenVPN.copy(),
|
||||||
Wireguard: v.Wireguard.copy(),
|
Wireguard: v.Wireguard.copy(),
|
||||||
PMTUD: v.PMTUD.copy(),
|
|
||||||
UpCommand: gosettings.CopyPointer(v.UpCommand),
|
|
||||||
DownCommand: gosettings.CopyPointer(v.DownCommand),
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (v *VPN) OverrideWith(other VPN) {
|
func (v *VPN) OverrideWith(other VPN) {
|
||||||
v.Type = gosettings.OverrideWithComparable(v.Type, other.Type)
|
v.Type = gosettings.OverrideWithComparable(v.Type, other.Type)
|
||||||
v.Provider.overrideWith(other.Provider)
|
v.Provider.overrideWith(other.Provider)
|
||||||
v.AmneziaWg.overrideWith(other.AmneziaWg)
|
|
||||||
v.OpenVPN.overrideWith(other.OpenVPN)
|
v.OpenVPN.overrideWith(other.OpenVPN)
|
||||||
v.Wireguard.overrideWith(other.Wireguard)
|
v.Wireguard.overrideWith(other.Wireguard)
|
||||||
v.PMTUD.overrideWith(other.PMTUD)
|
|
||||||
v.UpCommand = gosettings.OverrideWithPointer(v.UpCommand, other.UpCommand)
|
|
||||||
v.DownCommand = gosettings.OverrideWithPointer(v.DownCommand, other.DownCommand)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (v *VPN) setDefaults() {
|
func (v *VPN) setDefaults() {
|
||||||
v.Type = gosettings.DefaultComparable(v.Type, vpn.OpenVPN)
|
v.Type = gosettings.DefaultComparable(v.Type, vpn.OpenVPN)
|
||||||
v.Provider.setDefaults()
|
v.Provider.setDefaults()
|
||||||
v.AmneziaWg.setDefaults(v.Provider.Name)
|
|
||||||
v.OpenVPN.setDefaults(v.Provider.Name)
|
v.OpenVPN.setDefaults(v.Provider.Name)
|
||||||
v.Wireguard.setDefaults(v.Provider.Name)
|
v.Wireguard.setDefaults(v.Provider.Name)
|
||||||
v.PMTUD.setDefaults()
|
|
||||||
v.UpCommand = gosettings.DefaultPointer(v.UpCommand, "")
|
|
||||||
v.DownCommand = gosettings.DefaultPointer(v.DownCommand, "")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (v VPN) String() string {
|
func (v VPN) String() string {
|
||||||
@@ -116,22 +80,11 @@ func (v VPN) toLinesNode() (node *gotree.Node) {
|
|||||||
|
|
||||||
node.AppendNode(v.Provider.toLinesNode())
|
node.AppendNode(v.Provider.toLinesNode())
|
||||||
|
|
||||||
switch v.Type {
|
if v.Type == vpn.OpenVPN {
|
||||||
case vpn.AmneziaWg:
|
|
||||||
node.AppendNode(v.AmneziaWg.toLinesNode())
|
|
||||||
case vpn.OpenVPN:
|
|
||||||
node.AppendNode(v.OpenVPN.toLinesNode())
|
node.AppendNode(v.OpenVPN.toLinesNode())
|
||||||
case vpn.Wireguard:
|
} else {
|
||||||
node.AppendNode(v.Wireguard.toLinesNode())
|
node.AppendNode(v.Wireguard.toLinesNode())
|
||||||
}
|
}
|
||||||
node.AppendNode(v.PMTUD.toLinesNode())
|
|
||||||
|
|
||||||
if *v.UpCommand != "" {
|
|
||||||
node.Appendf("Up command: %s", *v.UpCommand)
|
|
||||||
}
|
|
||||||
if *v.DownCommand != "" {
|
|
||||||
node.Appendf("Down command: %s", *v.DownCommand)
|
|
||||||
}
|
|
||||||
|
|
||||||
return node
|
return node
|
||||||
}
|
}
|
||||||
@@ -144,30 +97,15 @@ func (v *VPN) read(r *reader.Reader) (err error) {
|
|||||||
return fmt.Errorf("VPN provider: %w", err)
|
return fmt.Errorf("VPN provider: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
err = v.AmneziaWg.read(r)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("AmneziaWG: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
err = v.OpenVPN.read(r)
|
err = v.OpenVPN.read(r)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("OpenVPN: %w", err)
|
return fmt.Errorf("OpenVPN: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
const amneziawg = false
|
err = v.Wireguard.read(r)
|
||||||
err = v.Wireguard.read(r, amneziawg)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("wireguard: %w", err)
|
return fmt.Errorf("wireguard: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
err = v.PMTUD.read(r)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("PMTUD: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
v.UpCommand = r.Get("VPN_UP_COMMAND", reader.ForceLowercase(false))
|
|
||||||
|
|
||||||
v.DownCommand = r.Get("VPN_DOWN_COMMAND", reader.ForceLowercase(false))
|
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/qdm12/gluetun/internal/configuration/settings/helpers"
|
||||||
"github.com/qdm12/gluetun/internal/constants/providers"
|
"github.com/qdm12/gluetun/internal/constants/providers"
|
||||||
"github.com/qdm12/gosettings"
|
"github.com/qdm12/gosettings"
|
||||||
"github.com/qdm12/gosettings/reader"
|
"github.com/qdm12/gosettings/reader"
|
||||||
@@ -37,9 +38,14 @@ type Wireguard struct {
|
|||||||
Interface string `json:"interface"`
|
Interface string `json:"interface"`
|
||||||
PersistentKeepaliveInterval *time.Duration `json:"persistent_keep_alive_interval"`
|
PersistentKeepaliveInterval *time.Duration `json:"persistent_keep_alive_interval"`
|
||||||
// Maximum Transmission Unit (MTU) of the Wireguard interface.
|
// Maximum Transmission Unit (MTU) of the Wireguard interface.
|
||||||
// It cannot be nil in the internal state, and defaults to
|
// It cannot be zero in the internal state, and defaults to
|
||||||
// 0 indicating to use PMTUD.
|
// 1320. Note it is not the wireguard-go MTU default of 1420
|
||||||
MTU *uint32 `json:"mtu"`
|
// because this impacts bandwidth a lot on some VPN providers,
|
||||||
|
// see https://github.com/qdm12/gluetun/issues/1650.
|
||||||
|
// It has been lowered to 1320 following quite a bit of
|
||||||
|
// investigation in the issue:
|
||||||
|
// https://github.com/qdm12/gluetun/issues/2533.
|
||||||
|
MTU uint16 `json:"mtu"`
|
||||||
// Implementation is the Wireguard implementation to use.
|
// Implementation is the Wireguard implementation to use.
|
||||||
// It can be "auto", "userspace" or "kernelspace".
|
// It can be "auto", "userspace" or "kernelspace".
|
||||||
// It defaults to "auto" and cannot be the empty string
|
// It defaults to "auto" and cannot be the empty string
|
||||||
@@ -50,8 +56,23 @@ type Wireguard struct {
|
|||||||
var regexpInterfaceName = regexp.MustCompile(`^[a-zA-Z0-9_]+$`)
|
var regexpInterfaceName = regexp.MustCompile(`^[a-zA-Z0-9_]+$`)
|
||||||
|
|
||||||
// Validate validates Wireguard settings.
|
// Validate validates Wireguard settings.
|
||||||
// It should only be ran if the VPN type chosen is Wireguard or AmneziaWg.
|
// It should only be ran if the VPN type chosen is Wireguard.
|
||||||
func (w Wireguard) validate(vpnProvider string, ipv6Supported, amneziawg bool) (err error) {
|
func (w Wireguard) validate(vpnProvider string, ipv6Supported bool) (err error) {
|
||||||
|
if !helpers.IsOneOf(vpnProvider,
|
||||||
|
providers.Airvpn,
|
||||||
|
providers.Custom,
|
||||||
|
providers.Fastestvpn,
|
||||||
|
providers.Ivpn,
|
||||||
|
providers.Mullvad,
|
||||||
|
providers.Nordvpn,
|
||||||
|
providers.Protonvpn,
|
||||||
|
providers.Surfshark,
|
||||||
|
providers.Windscribe,
|
||||||
|
) {
|
||||||
|
// do not validate for VPN provider not supporting Wireguard
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
// Validate PrivateKey
|
// Validate PrivateKey
|
||||||
if *w.PrivateKey == "" {
|
if *w.PrivateKey == "" {
|
||||||
return fmt.Errorf("%w", ErrWireguardPrivateKeyNotSet)
|
return fmt.Errorf("%w", ErrWireguardPrivateKeyNotSet)
|
||||||
@@ -120,12 +141,10 @@ func (w Wireguard) validate(vpnProvider string, ipv6Supported, amneziawg bool) (
|
|||||||
ErrWireguardInterfaceNotValid, w.Interface, regexpInterfaceName)
|
ErrWireguardInterfaceNotValid, w.Interface, regexpInterfaceName)
|
||||||
}
|
}
|
||||||
|
|
||||||
if !amneziawg { // amneziawg should have its own Implementation field and ignore this one
|
|
||||||
validImplementations := []string{"auto", "userspace", "kernelspace"}
|
validImplementations := []string{"auto", "userspace", "kernelspace"}
|
||||||
if err := validate.IsOneOf(w.Implementation, validImplementations...); err != nil {
|
if err := validate.IsOneOf(w.Implementation, validImplementations...); err != nil {
|
||||||
return fmt.Errorf("%w: %w", ErrWireguardImplementationNotValid, err)
|
return fmt.Errorf("%w: %w", ErrWireguardImplementationNotValid, err)
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
@@ -175,7 +194,8 @@ func (w *Wireguard) setDefaults(vpnProvider string) {
|
|||||||
w.AllowedIPs = gosettings.DefaultSlice(w.AllowedIPs, defaultAllowedIPs)
|
w.AllowedIPs = gosettings.DefaultSlice(w.AllowedIPs, defaultAllowedIPs)
|
||||||
w.PersistentKeepaliveInterval = gosettings.DefaultPointer(w.PersistentKeepaliveInterval, 0)
|
w.PersistentKeepaliveInterval = gosettings.DefaultPointer(w.PersistentKeepaliveInterval, 0)
|
||||||
w.Interface = gosettings.DefaultComparable(w.Interface, "wg0")
|
w.Interface = gosettings.DefaultComparable(w.Interface, "wg0")
|
||||||
w.MTU = gosettings.DefaultPointer(w.MTU, 0)
|
const defaultMTU = 1320
|
||||||
|
w.MTU = gosettings.DefaultComparable(w.MTU, defaultMTU)
|
||||||
w.Implementation = gosettings.DefaultComparable(w.Implementation, "auto")
|
w.Implementation = gosettings.DefaultComparable(w.Implementation, "auto")
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -211,11 +231,7 @@ func (w Wireguard) toLinesNode() (node *gotree.Node) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
interfaceNode := node.Appendf("Network interface: %s", w.Interface)
|
interfaceNode := node.Appendf("Network interface: %s", w.Interface)
|
||||||
if *w.MTU == 0 {
|
interfaceNode.Appendf("MTU: %d", w.MTU)
|
||||||
interfaceNode.Append("MTU: use path MTU discovery")
|
|
||||||
} else {
|
|
||||||
interfaceNode.Appendf("MTU: %d", *w.MTU)
|
|
||||||
}
|
|
||||||
|
|
||||||
if w.Implementation != "auto" {
|
if w.Implementation != "auto" {
|
||||||
node.Appendf("Implementation: %s", w.Implementation)
|
node.Appendf("Implementation: %s", w.Implementation)
|
||||||
@@ -224,21 +240,14 @@ func (w Wireguard) toLinesNode() (node *gotree.Node) {
|
|||||||
return node
|
return node
|
||||||
}
|
}
|
||||||
|
|
||||||
func (w *Wireguard) read(r *reader.Reader, amneziaWG bool) (err error) {
|
func (w *Wireguard) read(r *reader.Reader) (err error) {
|
||||||
prefix := "WIREGUARD"
|
w.PrivateKey = r.Get("WIREGUARD_PRIVATE_KEY", reader.ForceLowercase(false))
|
||||||
if amneziaWG {
|
w.PreSharedKey = r.Get("WIREGUARD_PRESHARED_KEY", reader.ForceLowercase(false))
|
||||||
prefix = "AMNEZIAWG"
|
|
||||||
}
|
|
||||||
w.PrivateKey = r.Get(prefix+"_PRIVATE_KEY", reader.ForceLowercase(false))
|
|
||||||
w.PreSharedKey = r.Get(prefix+"_PRESHARED_KEY", reader.ForceLowercase(false))
|
|
||||||
w.Interface = r.String("VPN_INTERFACE",
|
w.Interface = r.String("VPN_INTERFACE",
|
||||||
reader.RetroKeys(prefix+"_INTERFACE"), reader.ForceLowercase(false))
|
reader.RetroKeys("WIREGUARD_INTERFACE"), reader.ForceLowercase(false))
|
||||||
|
|
||||||
if !amneziaWG {
|
|
||||||
w.Implementation = r.String("WIREGUARD_IMPLEMENTATION")
|
w.Implementation = r.String("WIREGUARD_IMPLEMENTATION")
|
||||||
}
|
|
||||||
|
|
||||||
addressStrings := r.CSV(prefix+"_ADDRESSES", reader.RetroKeys(prefix+"_ADDRESS"))
|
addressStrings := r.CSV("WIREGUARD_ADDRESSES", reader.RetroKeys("WIREGUARD_ADDRESS"))
|
||||||
// WARNING: do not initialize w.Addresses to an empty slice
|
// WARNING: do not initialize w.Addresses to an empty slice
|
||||||
// or the defaults for nordvpn will not work.
|
// or the defaults for nordvpn will not work.
|
||||||
for _, addressString := range addressStrings {
|
for _, addressString := range addressStrings {
|
||||||
@@ -253,19 +262,21 @@ func (w *Wireguard) read(r *reader.Reader, amneziaWG bool) (err error) {
|
|||||||
w.Addresses = append(w.Addresses, address)
|
w.Addresses = append(w.Addresses, address)
|
||||||
}
|
}
|
||||||
|
|
||||||
w.AllowedIPs, err = r.CSVNetipPrefixes(prefix + "_ALLOWED_IPS")
|
w.AllowedIPs, err = r.CSVNetipPrefixes("WIREGUARD_ALLOWED_IPS")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err // already wrapped
|
return err // already wrapped
|
||||||
}
|
}
|
||||||
|
|
||||||
w.PersistentKeepaliveInterval, err = r.DurationPtr(prefix + "_PERSISTENT_KEEPALIVE_INTERVAL")
|
w.PersistentKeepaliveInterval, err = r.DurationPtr("WIREGUARD_PERSISTENT_KEEPALIVE_INTERVAL")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
w.MTU, err = r.Uint32Ptr(prefix + "_MTU")
|
mtuPtr, err := r.Uint16Ptr("WIREGUARD_MTU")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
|
} else if mtuPtr != nil {
|
||||||
|
w.MTU = *mtuPtr
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -152,22 +152,18 @@ func (w WireguardSelection) toLinesNode() (node *gotree.Node) {
|
|||||||
return node
|
return node
|
||||||
}
|
}
|
||||||
|
|
||||||
func (w *WireguardSelection) read(r *reader.Reader, amneziaWG bool) (err error) {
|
func (w *WireguardSelection) read(r *reader.Reader) (err error) {
|
||||||
prefix := "WIREGUARD"
|
w.EndpointIP, err = r.NetipAddr("WIREGUARD_ENDPOINT_IP", reader.RetroKeys("VPN_ENDPOINT_IP"))
|
||||||
if amneziaWG {
|
|
||||||
prefix = "AMNEZIAWG"
|
|
||||||
}
|
|
||||||
w.EndpointIP, err = r.NetipAddr(prefix+"_ENDPOINT_IP", reader.RetroKeys("VPN_ENDPOINT_IP"))
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("%w - note this MUST be an IP address, "+
|
return fmt.Errorf("%w - note this MUST be an IP address, "+
|
||||||
"see https://github.com/qdm12/gluetun/issues/788", err)
|
"see https://github.com/qdm12/gluetun/issues/788", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
w.EndpointPort, err = r.Uint16Ptr(prefix+"_ENDPOINT_PORT", reader.RetroKeys("VPN_ENDPOINT_PORT"))
|
w.EndpointPort, err = r.Uint16Ptr("WIREGUARD_ENDPOINT_PORT", reader.RetroKeys("VPN_ENDPOINT_PORT"))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
w.PublicKey = r.String(prefix+"_PUBLIC_KEY", reader.ForceLowercase(false))
|
w.PublicKey = r.String("WIREGUARD_PUBLIC_KEY", reader.ForceLowercase(false))
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,84 +0,0 @@
|
|||||||
package files
|
|
||||||
|
|
||||||
import (
|
|
||||||
"errors"
|
|
||||||
"fmt"
|
|
||||||
"os"
|
|
||||||
"path/filepath"
|
|
||||||
|
|
||||||
"gopkg.in/ini.v1"
|
|
||||||
)
|
|
||||||
|
|
||||||
func (s *Source) lazyLoadAmneziawgConf() AmneziawgConfig {
|
|
||||||
if s.cached.amneziawgLoaded {
|
|
||||||
return s.cached.amneziawgConf
|
|
||||||
}
|
|
||||||
|
|
||||||
s.cached.amneziawgLoaded = true
|
|
||||||
var err error
|
|
||||||
s.cached.amneziawgConf, err = ParseAmneziawgConf(filepath.Join(s.rootDirectory, "amneziawg", "awg0.conf"))
|
|
||||||
if err != nil {
|
|
||||||
s.warner.Warnf("skipping Amneziawg config: %s", err)
|
|
||||||
}
|
|
||||||
return s.cached.amneziawgConf
|
|
||||||
}
|
|
||||||
|
|
||||||
type AmneziawgConfig struct {
|
|
||||||
Wireguard WireguardConfig
|
|
||||||
Jc *string
|
|
||||||
Jmin *string
|
|
||||||
Jmax *string
|
|
||||||
S1 *string
|
|
||||||
S2 *string
|
|
||||||
S3 *string
|
|
||||||
S4 *string
|
|
||||||
H1 *string
|
|
||||||
H2 *string
|
|
||||||
H3 *string
|
|
||||||
H4 *string
|
|
||||||
I1 *string
|
|
||||||
I2 *string
|
|
||||||
I3 *string
|
|
||||||
I4 *string
|
|
||||||
I5 *string
|
|
||||||
}
|
|
||||||
|
|
||||||
func ParseAmneziawgConf(path string) (config AmneziawgConfig, err error) {
|
|
||||||
iniFile, err := ini.InsensitiveLoad(path)
|
|
||||||
if err != nil {
|
|
||||||
if errors.Is(err, os.ErrNotExist) {
|
|
||||||
return AmneziawgConfig{}, nil
|
|
||||||
}
|
|
||||||
return AmneziawgConfig{}, fmt.Errorf("loading ini from reader: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
config.Wireguard, err = ParseWireguardConf(path)
|
|
||||||
if err != nil {
|
|
||||||
return AmneziawgConfig{}, err
|
|
||||||
}
|
|
||||||
|
|
||||||
interfaceSection, err := iniFile.GetSection("Interface")
|
|
||||||
if err != nil {
|
|
||||||
// can never happen
|
|
||||||
return AmneziawgConfig{}, fmt.Errorf("getting interface section: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
config.Jc = getINIKeyFromSection(interfaceSection, "Jc")
|
|
||||||
config.Jmin = getINIKeyFromSection(interfaceSection, "Jmin")
|
|
||||||
config.Jmax = getINIKeyFromSection(interfaceSection, "Jmax")
|
|
||||||
config.S1 = getINIKeyFromSection(interfaceSection, "S1")
|
|
||||||
config.S2 = getINIKeyFromSection(interfaceSection, "S2")
|
|
||||||
config.S3 = getINIKeyFromSection(interfaceSection, "S3")
|
|
||||||
config.S4 = getINIKeyFromSection(interfaceSection, "S4")
|
|
||||||
config.H1 = getINIKeyFromSection(interfaceSection, "H1")
|
|
||||||
config.H2 = getINIKeyFromSection(interfaceSection, "H2")
|
|
||||||
config.H3 = getINIKeyFromSection(interfaceSection, "H3")
|
|
||||||
config.H4 = getINIKeyFromSection(interfaceSection, "H4")
|
|
||||||
config.I1 = getINIKeyFromSection(interfaceSection, "I1")
|
|
||||||
config.I2 = getINIKeyFromSection(interfaceSection, "I2")
|
|
||||||
config.I3 = getINIKeyFromSection(interfaceSection, "I3")
|
|
||||||
config.I4 = getINIKeyFromSection(interfaceSection, "I4")
|
|
||||||
config.I5 = getINIKeyFromSection(interfaceSection, "I5")
|
|
||||||
|
|
||||||
return config, nil
|
|
||||||
}
|
|
||||||
@@ -1,82 +0,0 @@
|
|||||||
package files
|
|
||||||
|
|
||||||
import (
|
|
||||||
"io/fs"
|
|
||||||
"os"
|
|
||||||
"path/filepath"
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
"github.com/stretchr/testify/assert"
|
|
||||||
"github.com/stretchr/testify/require"
|
|
||||||
)
|
|
||||||
|
|
||||||
func Test_Source_ParseAmneziawgConf(t *testing.T) {
|
|
||||||
t.Parallel()
|
|
||||||
|
|
||||||
t.Run("no_file", func(t *testing.T) {
|
|
||||||
t.Parallel()
|
|
||||||
|
|
||||||
noFile := filepath.Join(t.TempDir(), "doesnotexist")
|
|
||||||
wireguard, err := ParseAmneziawgConf(noFile)
|
|
||||||
assert.Equal(t, AmneziawgConfig{}, wireguard)
|
|
||||||
assert.NoError(t, err)
|
|
||||||
})
|
|
||||||
|
|
||||||
testCases := map[string]struct {
|
|
||||||
fileContent string
|
|
||||||
amneziawg AmneziawgConfig
|
|
||||||
errMessage string
|
|
||||||
}{
|
|
||||||
"ini_load_error": {
|
|
||||||
fileContent: "invalid",
|
|
||||||
errMessage: "loading ini from reader: key-value delimiter not found: invalid",
|
|
||||||
},
|
|
||||||
"empty_file": {
|
|
||||||
errMessage: `getting interface section: section "interface" does not exist`,
|
|
||||||
},
|
|
||||||
"success": {
|
|
||||||
fileContent: `
|
|
||||||
[Interface]
|
|
||||||
PrivateKey = QOlCgyA/Sn/c/+YNTIEohrjm8IZV+OZ2AUFIoX20sk8=
|
|
||||||
Address = 10.38.22.35/32
|
|
||||||
DNS = 193.138.218.74
|
|
||||||
Jc = 4
|
|
||||||
H1 = 721391205
|
|
||||||
I1 = <b 0x1234>
|
|
||||||
|
|
||||||
[Peer]
|
|
||||||
PresharedKey = YJ680VN+dGrdsWNjSFqZ6vvwuiNhbq502ZL3G7Q3o3g=
|
|
||||||
`,
|
|
||||||
amneziawg: AmneziawgConfig{
|
|
||||||
Wireguard: WireguardConfig{
|
|
||||||
PrivateKey: ptrTo("QOlCgyA/Sn/c/+YNTIEohrjm8IZV+OZ2AUFIoX20sk8="),
|
|
||||||
PreSharedKey: ptrTo("YJ680VN+dGrdsWNjSFqZ6vvwuiNhbq502ZL3G7Q3o3g="),
|
|
||||||
Addresses: ptrTo("10.38.22.35/32"),
|
|
||||||
},
|
|
||||||
Jc: ptrTo("4"),
|
|
||||||
H1: ptrTo("721391205"),
|
|
||||||
I1: ptrTo("<b 0x1234>"),
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
for testName, testCase := range testCases {
|
|
||||||
t.Run(testName, func(t *testing.T) {
|
|
||||||
t.Parallel()
|
|
||||||
|
|
||||||
configFile := filepath.Join(t.TempDir(), "awg.conf")
|
|
||||||
const permission = fs.FileMode(0o600)
|
|
||||||
err := os.WriteFile(configFile, []byte(testCase.fileContent), permission)
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
wireguard, err := ParseAmneziawgConf(configFile)
|
|
||||||
|
|
||||||
assert.Equal(t, testCase.amneziawg, wireguard)
|
|
||||||
if testCase.errMessage != "" {
|
|
||||||
assert.EqualError(t, err, testCase.errMessage)
|
|
||||||
} else {
|
|
||||||
assert.NoError(t, err)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -13,8 +13,6 @@ type Source struct {
|
|||||||
cached struct {
|
cached struct {
|
||||||
wireguardLoaded bool
|
wireguardLoaded bool
|
||||||
wireguardConf WireguardConfig
|
wireguardConf WireguardConfig
|
||||||
amneziawgLoaded bool
|
|
||||||
amneziawgConf AmneziawgConfig
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -73,11 +71,6 @@ func (s *Source) Get(key string) (value string, isSet bool) {
|
|||||||
return strPtrToStringIsSet(s.lazyLoadWireguardConf().EndpointPort)
|
return strPtrToStringIsSet(s.lazyLoadWireguardConf().EndpointPort)
|
||||||
}
|
}
|
||||||
|
|
||||||
value, isSet, matched := s.getAmneziawgKey(key)
|
|
||||||
if matched {
|
|
||||||
return value, isSet
|
|
||||||
}
|
|
||||||
|
|
||||||
value, isSet, err := ReadFromFile(path)
|
value, isSet, err := ReadFromFile(path)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
s.warner.Warnf("skipping %s: reading file: %s", path, err)
|
s.warner.Warnf("skipping %s: reading file: %s", path, err)
|
||||||
@@ -85,58 +78,6 @@ func (s *Source) Get(key string) (value string, isSet bool) {
|
|||||||
return value, isSet
|
return value, isSet
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Source) getAmneziawgKey(key string) (value string, isSet, matched bool) {
|
|
||||||
switch key {
|
|
||||||
case "amnezia_private_key":
|
|
||||||
value, isSet = strPtrToStringIsSet(s.lazyLoadAmneziawgConf().Wireguard.PrivateKey)
|
|
||||||
case "amnezia_preshared_key":
|
|
||||||
value, isSet = strPtrToStringIsSet(s.lazyLoadAmneziawgConf().Wireguard.PreSharedKey)
|
|
||||||
case "amnezia_addresses":
|
|
||||||
value, isSet = strPtrToStringIsSet(s.lazyLoadAmneziawgConf().Wireguard.Addresses)
|
|
||||||
case "amnezia_public_key":
|
|
||||||
value, isSet = strPtrToStringIsSet(s.lazyLoadAmneziawgConf().Wireguard.PublicKey)
|
|
||||||
case "amnezia_endpoint_ip":
|
|
||||||
value, isSet = strPtrToStringIsSet(s.lazyLoadAmneziawgConf().Wireguard.EndpointIP)
|
|
||||||
case "amnezia_endpoint_port":
|
|
||||||
value, isSet = strPtrToStringIsSet(s.lazyLoadAmneziawgConf().Wireguard.EndpointPort)
|
|
||||||
case "amnezia_jc":
|
|
||||||
value, isSet = strPtrToStringIsSet(s.lazyLoadAmneziawgConf().Jc)
|
|
||||||
case "amnezia_jmin":
|
|
||||||
value, isSet = strPtrToStringIsSet(s.lazyLoadAmneziawgConf().Jmin)
|
|
||||||
case "amnezia_jmax":
|
|
||||||
value, isSet = strPtrToStringIsSet(s.lazyLoadAmneziawgConf().Jmax)
|
|
||||||
case "amnezia_s1":
|
|
||||||
value, isSet = strPtrToStringIsSet(s.lazyLoadAmneziawgConf().S1)
|
|
||||||
case "amnezia_s2":
|
|
||||||
value, isSet = strPtrToStringIsSet(s.lazyLoadAmneziawgConf().S2)
|
|
||||||
case "amnezia_s3":
|
|
||||||
value, isSet = strPtrToStringIsSet(s.lazyLoadAmneziawgConf().S3)
|
|
||||||
case "amnezia_s4":
|
|
||||||
value, isSet = strPtrToStringIsSet(s.lazyLoadAmneziawgConf().S4)
|
|
||||||
case "amnezia_h1":
|
|
||||||
value, isSet = strPtrToStringIsSet(s.lazyLoadAmneziawgConf().H1)
|
|
||||||
case "amnezia_h2":
|
|
||||||
value, isSet = strPtrToStringIsSet(s.lazyLoadAmneziawgConf().H2)
|
|
||||||
case "amnezia_h3":
|
|
||||||
value, isSet = strPtrToStringIsSet(s.lazyLoadAmneziawgConf().H3)
|
|
||||||
case "amnezia_h4":
|
|
||||||
value, isSet = strPtrToStringIsSet(s.lazyLoadAmneziawgConf().H4)
|
|
||||||
case "amnezia_i1":
|
|
||||||
value, isSet = strPtrToStringIsSet(s.lazyLoadAmneziawgConf().I1)
|
|
||||||
case "amnezia_i2":
|
|
||||||
value, isSet = strPtrToStringIsSet(s.lazyLoadAmneziawgConf().I2)
|
|
||||||
case "amnezia_i3":
|
|
||||||
value, isSet = strPtrToStringIsSet(s.lazyLoadAmneziawgConf().I3)
|
|
||||||
case "amnezia_i4":
|
|
||||||
value, isSet = strPtrToStringIsSet(s.lazyLoadAmneziawgConf().I4)
|
|
||||||
case "amnezia_i5":
|
|
||||||
value, isSet = strPtrToStringIsSet(s.lazyLoadAmneziawgConf().I5)
|
|
||||||
default:
|
|
||||||
return "", false, false
|
|
||||||
}
|
|
||||||
return value, isSet, true
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *Source) KeyTransform(key string) string {
|
func (s *Source) KeyTransform(key string) string {
|
||||||
switch key {
|
switch key {
|
||||||
// TODO v4 remove these irregular cases
|
// TODO v4 remove these irregular cases
|
||||||
|
|||||||
@@ -3,7 +3,6 @@ package files
|
|||||||
import (
|
import (
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"net"
|
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"regexp"
|
"regexp"
|
||||||
@@ -83,15 +82,12 @@ func parseWireguardPeerSection(peerSection *ini.Section) (
|
|||||||
publicKey = getINIKeyFromSection(peerSection, "PublicKey")
|
publicKey = getINIKeyFromSection(peerSection, "PublicKey")
|
||||||
endpoint := getINIKeyFromSection(peerSection, "Endpoint")
|
endpoint := getINIKeyFromSection(peerSection, "Endpoint")
|
||||||
if endpoint != nil {
|
if endpoint != nil {
|
||||||
host, port, err := net.SplitHostPort(*endpoint)
|
parts := strings.Split(*endpoint, ":")
|
||||||
if err == nil {
|
endpointIP = &parts[0]
|
||||||
endpointIP = &host
|
const partsWithPort = 2
|
||||||
// IPv6 hosts contain colons; port is managed by the provider for those
|
if len(parts) >= partsWithPort {
|
||||||
if !strings.Contains(host, ":") {
|
endpointPort = new(string)
|
||||||
endpointPort = &port
|
*endpointPort = strings.Join(parts[1:], ":")
|
||||||
}
|
|
||||||
} else {
|
|
||||||
endpointIP = endpoint
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -179,11 +179,6 @@ Endpoint = 1.2.3.4:51820`,
|
|||||||
endpointIP: ptrTo("1.2.3.4"),
|
endpointIP: ptrTo("1.2.3.4"),
|
||||||
endpointPort: ptrTo("51820"),
|
endpointPort: ptrTo("51820"),
|
||||||
},
|
},
|
||||||
"ipv6_endpoint": {
|
|
||||||
iniData: `[Peer]
|
|
||||||
Endpoint = [2a02:bbbb:aaaa:8075::10]:51820`,
|
|
||||||
endpointIP: ptrTo("2a02:bbbb:aaaa:8075::10"),
|
|
||||||
},
|
|
||||||
}
|
}
|
||||||
|
|
||||||
for testName, testCase := range testCases {
|
for testName, testCase := range testCases {
|
||||||
|
|||||||
@@ -1,27 +0,0 @@
|
|||||||
package secrets
|
|
||||||
|
|
||||||
import (
|
|
||||||
"os"
|
|
||||||
"path/filepath"
|
|
||||||
|
|
||||||
"github.com/qdm12/gluetun/internal/configuration/sources/files"
|
|
||||||
)
|
|
||||||
|
|
||||||
func (s *Source) lazyLoadAmneziawgConf() files.AmneziawgConfig {
|
|
||||||
if s.cached.amneziawgLoaded {
|
|
||||||
return s.cached.amneziawgConf
|
|
||||||
}
|
|
||||||
|
|
||||||
path := os.Getenv("AMNEZIAWG_CONF_SECRETFILE")
|
|
||||||
if path == "" {
|
|
||||||
path = filepath.Join(s.rootDirectory, "amneziawg", "awg0.conf")
|
|
||||||
}
|
|
||||||
|
|
||||||
s.cached.amneziawgLoaded = true
|
|
||||||
var err error
|
|
||||||
s.cached.amneziawgConf, err = files.ParseAmneziawgConf(path)
|
|
||||||
if err != nil {
|
|
||||||
s.warner.Warnf("skipping Amneziawg config: %s", err)
|
|
||||||
}
|
|
||||||
return s.cached.amneziawgConf
|
|
||||||
}
|
|
||||||
@@ -15,8 +15,6 @@ type Source struct {
|
|||||||
cached struct {
|
cached struct {
|
||||||
wireguardLoaded bool
|
wireguardLoaded bool
|
||||||
wireguardConf files.WireguardConfig
|
wireguardConf files.WireguardConfig
|
||||||
amneziawgLoaded bool
|
|
||||||
amneziawgConf files.AmneziawgConfig
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -85,11 +83,6 @@ func (s *Source) Get(key string) (value string, isSet bool) {
|
|||||||
return strPtrToStringIsSet(s.lazyLoadWireguardConf().EndpointPort)
|
return strPtrToStringIsSet(s.lazyLoadWireguardConf().EndpointPort)
|
||||||
}
|
}
|
||||||
|
|
||||||
value, isSet, matched := s.getAmneziaWg(key)
|
|
||||||
if matched {
|
|
||||||
return value, isSet
|
|
||||||
}
|
|
||||||
|
|
||||||
value, isSet, err := files.ReadFromFile(path)
|
value, isSet, err := files.ReadFromFile(path)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
s.warner.Warnf("skipping %s: reading file: %s", path, err)
|
s.warner.Warnf("skipping %s: reading file: %s", path, err)
|
||||||
@@ -111,55 +104,3 @@ func (s *Source) KeyTransform(key string) string {
|
|||||||
return key
|
return key
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Source) getAmneziaWg(key string) (value string, isSet, matched bool) {
|
|
||||||
switch key {
|
|
||||||
case "amneziawg_private_key":
|
|
||||||
value, isSet = strPtrToStringIsSet(s.lazyLoadAmneziawgConf().Wireguard.PrivateKey)
|
|
||||||
case "amneziawg_preshared_key":
|
|
||||||
value, isSet = strPtrToStringIsSet(s.lazyLoadAmneziawgConf().Wireguard.PreSharedKey)
|
|
||||||
case "amneziawg_addresses":
|
|
||||||
value, isSet = strPtrToStringIsSet(s.lazyLoadAmneziawgConf().Wireguard.Addresses)
|
|
||||||
case "amneziawg_public_key":
|
|
||||||
value, isSet = strPtrToStringIsSet(s.lazyLoadAmneziawgConf().Wireguard.PublicKey)
|
|
||||||
case "amneziawg_endpoint_ip":
|
|
||||||
value, isSet = strPtrToStringIsSet(s.lazyLoadAmneziawgConf().Wireguard.EndpointIP)
|
|
||||||
case "amneziawg_endpoint_port":
|
|
||||||
value, isSet = strPtrToStringIsSet(s.lazyLoadAmneziawgConf().Wireguard.EndpointPort)
|
|
||||||
case "amneziawg_jc":
|
|
||||||
value, isSet = strPtrToStringIsSet(s.lazyLoadAmneziawgConf().Jc)
|
|
||||||
case "amneziawg_jmin":
|
|
||||||
value, isSet = strPtrToStringIsSet(s.lazyLoadAmneziawgConf().Jmin)
|
|
||||||
case "amneziawg_jmax":
|
|
||||||
value, isSet = strPtrToStringIsSet(s.lazyLoadAmneziawgConf().Jmax)
|
|
||||||
case "amneziawg_s1":
|
|
||||||
value, isSet = strPtrToStringIsSet(s.lazyLoadAmneziawgConf().S1)
|
|
||||||
case "amneziawg_s2":
|
|
||||||
value, isSet = strPtrToStringIsSet(s.lazyLoadAmneziawgConf().S2)
|
|
||||||
case "amneziawg_s3":
|
|
||||||
value, isSet = strPtrToStringIsSet(s.lazyLoadAmneziawgConf().S3)
|
|
||||||
case "amneziawg_s4":
|
|
||||||
value, isSet = strPtrToStringIsSet(s.lazyLoadAmneziawgConf().S4)
|
|
||||||
case "amneziawg_h1":
|
|
||||||
value, isSet = strPtrToStringIsSet(s.lazyLoadAmneziawgConf().H1)
|
|
||||||
case "amneziawg_h2":
|
|
||||||
value, isSet = strPtrToStringIsSet(s.lazyLoadAmneziawgConf().H2)
|
|
||||||
case "amneziawg_h3":
|
|
||||||
value, isSet = strPtrToStringIsSet(s.lazyLoadAmneziawgConf().H3)
|
|
||||||
case "amneziawg_h4":
|
|
||||||
value, isSet = strPtrToStringIsSet(s.lazyLoadAmneziawgConf().H4)
|
|
||||||
case "amneziawg_i1":
|
|
||||||
value, isSet = strPtrToStringIsSet(s.lazyLoadAmneziawgConf().I1)
|
|
||||||
case "amneziawg_i2":
|
|
||||||
value, isSet = strPtrToStringIsSet(s.lazyLoadAmneziawgConf().I2)
|
|
||||||
case "amneziawg_i3":
|
|
||||||
value, isSet = strPtrToStringIsSet(s.lazyLoadAmneziawgConf().I3)
|
|
||||||
case "amneziawg_i4":
|
|
||||||
value, isSet = strPtrToStringIsSet(s.lazyLoadAmneziawgConf().I4)
|
|
||||||
case "amneziawg_i5":
|
|
||||||
value, isSet = strPtrToStringIsSet(s.lazyLoadAmneziawgConf().I5)
|
|
||||||
default:
|
|
||||||
return "", false, false
|
|
||||||
}
|
|
||||||
return value, isSet, true
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
package vpn
|
package vpn
|
||||||
|
|
||||||
const (
|
const (
|
||||||
AmneziaWg = "amneziawg"
|
|
||||||
OpenVPN = "openvpn"
|
OpenVPN = "openvpn"
|
||||||
Wireguard = "wireguard"
|
Wireguard = "wireguard"
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -1,131 +0,0 @@
|
|||||||
package dns
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"encoding/json"
|
|
||||||
"errors"
|
|
||||||
"fmt"
|
|
||||||
"math"
|
|
||||||
"math/rand/v2"
|
|
||||||
"net/http"
|
|
||||||
"sort"
|
|
||||||
"strings"
|
|
||||||
)
|
|
||||||
|
|
||||||
func leakCheck(ctx context.Context, client *http.Client) (report string, err error) {
|
|
||||||
const sessionLength = 40
|
|
||||||
session := generateRandomString(sessionLength)
|
|
||||||
|
|
||||||
ctx, cancel := context.WithCancel(ctx)
|
|
||||||
defer cancel()
|
|
||||||
type result struct {
|
|
||||||
dnsToCount map[string]uint
|
|
||||||
err error
|
|
||||||
}
|
|
||||||
resultsCh := make(chan result)
|
|
||||||
|
|
||||||
const requestsCount = 5
|
|
||||||
for range requestsCount {
|
|
||||||
go func() {
|
|
||||||
dnsToCount, err := triggerDNSQuery(ctx, client, session)
|
|
||||||
resultsCh <- result{dnsToCount: dnsToCount, err: err}
|
|
||||||
}()
|
|
||||||
}
|
|
||||||
|
|
||||||
dnsToCount := make(map[string]uint)
|
|
||||||
for range requestsCount {
|
|
||||||
result := <-resultsCh
|
|
||||||
if result.err != nil {
|
|
||||||
if err == nil {
|
|
||||||
cancel()
|
|
||||||
err = fmt.Errorf("request failed: %w", result.err)
|
|
||||||
}
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
for dns, count := range result.dnsToCount {
|
|
||||||
dnsToCount[dns] += count
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
|
|
||||||
return formatPercentages(dnsToCount), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func generateRandomString(length uint) string {
|
|
||||||
const charset = "abcdefghijklmnopqrstuvwxyz0123456789"
|
|
||||||
b := make([]byte, length)
|
|
||||||
for i := range b {
|
|
||||||
b[i] = charset[rand.IntN(len(charset))] //nolint:gosec
|
|
||||||
}
|
|
||||||
return string(b)
|
|
||||||
}
|
|
||||||
|
|
||||||
var errIPLeakSessionMismatch = errors.New("ipleak.net session mismatch")
|
|
||||||
|
|
||||||
func triggerDNSQuery(ctx context.Context, client *http.Client, session string) (
|
|
||||||
dnsToCount map[string]uint, err error,
|
|
||||||
) {
|
|
||||||
const randomLength = 12
|
|
||||||
randomPart := generateRandomString(randomLength)
|
|
||||||
url := fmt.Sprintf("https://%s-%s.ipleak.net/dnsdetection/", session, randomPart)
|
|
||||||
request, err := http.NewRequestWithContext(ctx, http.MethodGet, url, nil)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("creating request: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
response, err := client.Do(request)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("performing request: %w", err)
|
|
||||||
}
|
|
||||||
defer response.Body.Close()
|
|
||||||
|
|
||||||
type ipLeakData struct {
|
|
||||||
Session string `json:"session"`
|
|
||||||
IP map[string]uint `json:"ip"`
|
|
||||||
}
|
|
||||||
|
|
||||||
decoder := json.NewDecoder(response.Body)
|
|
||||||
var data ipLeakData
|
|
||||||
err = decoder.Decode(&data)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("decoding response: %w", err)
|
|
||||||
} else if data.Session != session {
|
|
||||||
return nil, fmt.Errorf("%w: expected %s, got %s", errIPLeakSessionMismatch, session, data.Session)
|
|
||||||
}
|
|
||||||
|
|
||||||
return data.IP, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func formatPercentages(data map[string]uint) string {
|
|
||||||
if len(data) == 0 {
|
|
||||||
return ""
|
|
||||||
}
|
|
||||||
|
|
||||||
var total uint
|
|
||||||
keys := make([]string, 0, len(data))
|
|
||||||
for k, v := range data {
|
|
||||||
total += v
|
|
||||||
keys = append(keys, k)
|
|
||||||
}
|
|
||||||
|
|
||||||
sort.Slice(keys, func(i, j int) bool {
|
|
||||||
if data[keys[i]] == data[keys[j]] {
|
|
||||||
return keys[i] < keys[j] // Tie-breaker: alphabetical
|
|
||||||
}
|
|
||||||
return data[keys[i]] > data[keys[j]]
|
|
||||||
})
|
|
||||||
|
|
||||||
results := make([]string, len(keys))
|
|
||||||
for i, key := range keys {
|
|
||||||
var pct float64
|
|
||||||
if total > 0 {
|
|
||||||
pct = math.Ceil((float64(data[key]) / float64(total)) * 100) //nolint:mnd
|
|
||||||
}
|
|
||||||
results[i] = fmt.Sprintf("%s (%.0f%%)", key, pct)
|
|
||||||
}
|
|
||||||
|
|
||||||
return strings.Join(results, ", ")
|
|
||||||
}
|
|
||||||
@@ -1,22 +0,0 @@
|
|||||||
package dns
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"net/http"
|
|
||||||
"testing"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/stretchr/testify/require"
|
|
||||||
)
|
|
||||||
|
|
||||||
func Test_leakCheck(t *testing.T) {
|
|
||||||
t.Parallel()
|
|
||||||
|
|
||||||
const timeout = 10 * time.Second
|
|
||||||
ctx, cancel := context.WithTimeout(t.Context(), timeout)
|
|
||||||
t.Cleanup(cancel)
|
|
||||||
client := http.DefaultClient
|
|
||||||
report, err := leakCheck(ctx, client)
|
|
||||||
require.NoError(t, err)
|
|
||||||
require.NotEmpty(t, report)
|
|
||||||
}
|
|
||||||
@@ -3,8 +3,6 @@ package dns
|
|||||||
type Logger interface {
|
type Logger interface {
|
||||||
Debug(s string)
|
Debug(s string)
|
||||||
Info(s string)
|
Info(s string)
|
||||||
Infof(format string, args ...any)
|
|
||||||
Warn(s string)
|
Warn(s string)
|
||||||
Warnf(format string, args ...any)
|
|
||||||
Error(s string)
|
Error(s string)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -22,7 +22,6 @@ type Loop struct {
|
|||||||
server *server.Server
|
server *server.Server
|
||||||
filter *mapfilter.Filter
|
filter *mapfilter.Filter
|
||||||
localResolvers []netip.Addr
|
localResolvers []netip.Addr
|
||||||
localSubnets []netip.Prefix
|
|
||||||
resolvConf string
|
resolvConf string
|
||||||
client *http.Client
|
client *http.Client
|
||||||
logger Logger
|
logger Logger
|
||||||
@@ -40,7 +39,7 @@ type Loop struct {
|
|||||||
const defaultBackoffTime = 10 * time.Second
|
const defaultBackoffTime = 10 * time.Second
|
||||||
|
|
||||||
func NewLoop(settings settings.DNS,
|
func NewLoop(settings settings.DNS,
|
||||||
client *http.Client, logger Logger, localSubnets []netip.Prefix,
|
client *http.Client, logger Logger,
|
||||||
) (loop *Loop, err error) {
|
) (loop *Loop, err error) {
|
||||||
start := make(chan struct{})
|
start := make(chan struct{})
|
||||||
running := make(chan models.LoopStatus)
|
running := make(chan models.LoopStatus)
|
||||||
@@ -63,7 +62,6 @@ func NewLoop(settings settings.DNS,
|
|||||||
state: state,
|
state: state,
|
||||||
server: nil,
|
server: nil,
|
||||||
filter: filter,
|
filter: filter,
|
||||||
localSubnets: localSubnets,
|
|
||||||
resolvConf: "/etc/resolv.conf",
|
resolvConf: "/etc/resolv.conf",
|
||||||
client: client,
|
client: client,
|
||||||
logger: logger,
|
logger: logger,
|
||||||
|
|||||||
@@ -0,0 +1,37 @@
|
|||||||
|
package dns
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/netip"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/qdm12/dns/v2/pkg/nameserver"
|
||||||
|
)
|
||||||
|
|
||||||
|
func (l *Loop) useUnencryptedDNS(fallback bool) {
|
||||||
|
settings := l.GetSettings()
|
||||||
|
|
||||||
|
targetIP := settings.GetFirstPlaintextIPv4()
|
||||||
|
|
||||||
|
if fallback {
|
||||||
|
l.logger.Info("falling back on plaintext DNS at address " + targetIP.String())
|
||||||
|
} else {
|
||||||
|
l.logger.Info("using plaintext DNS at address " + targetIP.String())
|
||||||
|
}
|
||||||
|
|
||||||
|
const dialTimeout = 3 * time.Second
|
||||||
|
const defaultDNSPort = 53
|
||||||
|
settingsInternalDNS := nameserver.SettingsInternalDNS{
|
||||||
|
AddrPort: netip.AddrPortFrom(targetIP, defaultDNSPort),
|
||||||
|
Timeout: dialTimeout,
|
||||||
|
}
|
||||||
|
nameserver.UseDNSInternally(settingsInternalDNS)
|
||||||
|
|
||||||
|
settingsSystemWide := nameserver.SettingsSystemDNS{
|
||||||
|
IPs: []netip.Addr{targetIP},
|
||||||
|
ResolvPath: l.resolvConf,
|
||||||
|
}
|
||||||
|
err := nameserver.UseDNSSystemWide(settingsSystemWide)
|
||||||
|
if err != nil {
|
||||||
|
l.logger.Error(err.Error())
|
||||||
|
}
|
||||||
|
}
|
||||||
+38
-20
@@ -2,9 +2,9 @@ package dns
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"errors"
|
||||||
|
|
||||||
"github.com/qdm12/dns/v2/pkg/nameserver"
|
"github.com/qdm12/dns/v2/pkg/nameserver"
|
||||||
"github.com/qdm12/gluetun/internal/configuration/settings"
|
|
||||||
"github.com/qdm12/gluetun/internal/constants"
|
"github.com/qdm12/gluetun/internal/constants"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -18,6 +18,15 @@ func (l *Loop) Run(ctx context.Context, done chan<- struct{}) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if *l.GetSettings().KeepNameserver {
|
||||||
|
l.logger.Warn("⚠️⚠️⚠️ keeping the default container nameservers, " +
|
||||||
|
"this will likely leak DNS traffic outside the VPN " +
|
||||||
|
"and go through your container network DNS outside the VPN tunnel!")
|
||||||
|
} else {
|
||||||
|
const fallback = false
|
||||||
|
l.useUnencryptedDNS(fallback)
|
||||||
|
}
|
||||||
|
|
||||||
select {
|
select {
|
||||||
case <-l.start:
|
case <-l.start:
|
||||||
case <-ctx.Done():
|
case <-ctx.Done():
|
||||||
@@ -29,40 +38,39 @@ func (l *Loop) Run(ctx context.Context, done chan<- struct{}) {
|
|||||||
// 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
|
||||||
|
|
||||||
var settings settings.DNS
|
settings := l.GetSettings()
|
||||||
for {
|
for !*settings.KeepNameserver && *settings.ServerEnabled {
|
||||||
settings = l.GetSettings()
|
|
||||||
var err error
|
var err error
|
||||||
runError, err = l.setupServer(ctx, settings)
|
runError, err = l.setupServer(ctx)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
|
l.backoffTime = defaultBackoffTime
|
||||||
|
l.logger.Info("ready")
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
|
|
||||||
l.signalOrSetStatus(constants.Crashed)
|
l.signalOrSetStatus(constants.Crashed)
|
||||||
|
|
||||||
if ctx.Err() != nil {
|
if ctx.Err() != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
l.logAndWait(ctx, err)
|
|
||||||
|
if !errors.Is(err, errUpdateBlockLists) {
|
||||||
|
const fallback = true
|
||||||
|
l.useUnencryptedDNS(fallback)
|
||||||
}
|
}
|
||||||
|
l.logAndWait(ctx, err)
|
||||||
l.backoffTime = defaultBackoffTime
|
settings = l.GetSettings()
|
||||||
l.logger.Infof("ready and using DNS server with %s upstream resolvers", settings.UpstreamType)
|
|
||||||
|
|
||||||
err = l.updateFiles(ctx, settings)
|
|
||||||
if err != nil {
|
|
||||||
l.logger.Warn("downloading block lists failed, skipping: " + err.Error())
|
|
||||||
}
|
}
|
||||||
l.signalOrSetStatus(constants.Running)
|
l.signalOrSetStatus(constants.Running)
|
||||||
|
|
||||||
l.userTrigger = false
|
settings = l.GetSettings()
|
||||||
|
if !*settings.KeepNameserver && !*settings.ServerEnabled {
|
||||||
report, err := leakCheck(ctx, l.client)
|
const fallback = false
|
||||||
if err != nil {
|
l.useUnencryptedDNS(fallback)
|
||||||
l.logger.Warnf("running leak check: %s", err)
|
|
||||||
} else {
|
|
||||||
l.logger.Infof("leak check report: %s", report)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
l.userTrigger = false
|
||||||
|
|
||||||
exitLoop := l.runWait(ctx, runError)
|
exitLoop := l.runWait(ctx, runError)
|
||||||
if exitLoop {
|
if exitLoop {
|
||||||
return
|
return
|
||||||
@@ -74,13 +82,21 @@ func (l *Loop) runWait(ctx context.Context, runError <-chan error) (exitLoop boo
|
|||||||
for {
|
for {
|
||||||
select {
|
select {
|
||||||
case <-ctx.Done():
|
case <-ctx.Done():
|
||||||
|
settings := l.GetSettings()
|
||||||
|
if !*settings.KeepNameserver && *settings.ServerEnabled {
|
||||||
l.stopServer()
|
l.stopServer()
|
||||||
// TODO revert OS and Go nameserver when exiting
|
// TODO revert OS and Go nameserver when exiting
|
||||||
|
}
|
||||||
return true
|
return true
|
||||||
case <-l.stop:
|
case <-l.stop:
|
||||||
l.userTrigger = true
|
l.userTrigger = true
|
||||||
l.logger.Info("stopping")
|
l.logger.Info("stopping")
|
||||||
|
settings := l.GetSettings()
|
||||||
|
if !*settings.KeepNameserver && *settings.ServerEnabled {
|
||||||
|
const fallback = false
|
||||||
|
l.useUnencryptedDNS(fallback)
|
||||||
l.stopServer()
|
l.stopServer()
|
||||||
|
}
|
||||||
l.stopped <- struct{}{}
|
l.stopped <- struct{}{}
|
||||||
case <-l.start:
|
case <-l.start:
|
||||||
l.userTrigger = true
|
l.userTrigger = true
|
||||||
@@ -88,6 +104,8 @@ func (l *Loop) runWait(ctx context.Context, runError <-chan error) (exitLoop boo
|
|||||||
return false
|
return false
|
||||||
case err := <-runError: // unexpected error
|
case err := <-runError: // unexpected error
|
||||||
l.statusManager.SetStatus(constants.Crashed)
|
l.statusManager.SetStatus(constants.Crashed)
|
||||||
|
const fallback = true
|
||||||
|
l.useUnencryptedDNS(fallback)
|
||||||
l.logAndWait(ctx, err)
|
l.logAndWait(ctx, err)
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|||||||
+18
-54
@@ -4,7 +4,6 @@ import (
|
|||||||
"context"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
"net/netip"
|
"net/netip"
|
||||||
"slices"
|
|
||||||
|
|
||||||
"github.com/qdm12/dns/v2/pkg/doh"
|
"github.com/qdm12/dns/v2/pkg/doh"
|
||||||
"github.com/qdm12/dns/v2/pkg/dot"
|
"github.com/qdm12/dns/v2/pkg/dot"
|
||||||
@@ -27,23 +26,31 @@ func (l *Loop) SetSettings(ctx context.Context, settings settings.DNS) (
|
|||||||
return l.state.SetSettings(ctx, settings)
|
return l.state.SetSettings(ctx, settings)
|
||||||
}
|
}
|
||||||
|
|
||||||
func buildServerSettings(userSettings settings.DNS,
|
func buildServerSettings(settings settings.DNS,
|
||||||
filter *mapfilter.Filter, localResolvers []netip.Addr,
|
filter *mapfilter.Filter, localResolvers []netip.Addr,
|
||||||
localSubnets []netip.Prefix, logger Logger) (
|
logger Logger) (
|
||||||
serverSettings server.Settings, err error,
|
serverSettings server.Settings, err error,
|
||||||
) {
|
) {
|
||||||
serverSettings.Logger = logger
|
serverSettings.Logger = logger
|
||||||
|
|
||||||
upstreamResolvers := buildProviders(userSettings, localSubnets, logger)
|
providersData := provider.NewProviders()
|
||||||
|
upstreamResolvers := make([]provider.Provider, len(settings.Providers))
|
||||||
|
for i := range settings.Providers {
|
||||||
|
var err error
|
||||||
|
upstreamResolvers[i], err = providersData.Get(settings.Providers[i])
|
||||||
|
if err != nil {
|
||||||
|
panic(err) // this should already had been checked
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
ipVersion := "ipv4"
|
ipVersion := "ipv4"
|
||||||
if *userSettings.IPv6 {
|
if *settings.IPv6 {
|
||||||
ipVersion = "ipv6"
|
ipVersion = "ipv6"
|
||||||
}
|
}
|
||||||
|
|
||||||
var dialer server.Dialer
|
var dialer server.Dialer
|
||||||
switch userSettings.UpstreamType {
|
switch settings.UpstreamType {
|
||||||
case settings.DNSUpstreamTypeDot:
|
case "dot":
|
||||||
dialerSettings := dot.Settings{
|
dialerSettings := dot.Settings{
|
||||||
UpstreamResolvers: upstreamResolvers,
|
UpstreamResolvers: upstreamResolvers,
|
||||||
IPVersion: ipVersion,
|
IPVersion: ipVersion,
|
||||||
@@ -52,7 +59,7 @@ func buildServerSettings(userSettings settings.DNS,
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return server.Settings{}, fmt.Errorf("creating DNS over TLS dialer: %w", err)
|
return server.Settings{}, fmt.Errorf("creating DNS over TLS dialer: %w", err)
|
||||||
}
|
}
|
||||||
case settings.DNSUpstreamTypeDoh:
|
case "doh":
|
||||||
dialerSettings := doh.Settings{
|
dialerSettings := doh.Settings{
|
||||||
UpstreamResolvers: upstreamResolvers,
|
UpstreamResolvers: upstreamResolvers,
|
||||||
IPVersion: ipVersion,
|
IPVersion: ipVersion,
|
||||||
@@ -61,7 +68,7 @@ func buildServerSettings(userSettings settings.DNS,
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return server.Settings{}, fmt.Errorf("creating DNS over HTTPS dialer: %w", err)
|
return server.Settings{}, fmt.Errorf("creating DNS over HTTPS dialer: %w", err)
|
||||||
}
|
}
|
||||||
case settings.DNSUpstreamTypePlain:
|
case "plain":
|
||||||
dialerSettings := plain.Settings{
|
dialerSettings := plain.Settings{
|
||||||
UpstreamResolvers: upstreamResolvers,
|
UpstreamResolvers: upstreamResolvers,
|
||||||
IPVersion: ipVersion,
|
IPVersion: ipVersion,
|
||||||
@@ -71,11 +78,11 @@ func buildServerSettings(userSettings settings.DNS,
|
|||||||
return server.Settings{}, fmt.Errorf("creating plain DNS dialer: %w", err)
|
return server.Settings{}, fmt.Errorf("creating plain DNS dialer: %w", err)
|
||||||
}
|
}
|
||||||
default:
|
default:
|
||||||
panic("unknown upstream type: " + userSettings.UpstreamType)
|
panic("unknown upstream type: " + settings.UpstreamType)
|
||||||
}
|
}
|
||||||
serverSettings.Dialer = dialer
|
serverSettings.Dialer = dialer
|
||||||
|
|
||||||
if *userSettings.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)
|
||||||
@@ -116,46 +123,3 @@ func buildServerSettings(userSettings settings.DNS,
|
|||||||
|
|
||||||
return serverSettings, nil
|
return serverSettings, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func buildProviders(userSettings settings.DNS, localSubnets []netip.Prefix,
|
|
||||||
logger Logger,
|
|
||||||
) (providers []provider.Provider) {
|
|
||||||
userDefinedPlainAddresses := userSettings.UpstreamType == settings.DNSUpstreamTypePlain &&
|
|
||||||
len(userSettings.UpstreamPlainAddresses) > 0
|
|
||||||
if !userDefinedPlainAddresses {
|
|
||||||
providers = make([]provider.Provider, len(userSettings.Providers))
|
|
||||||
providersData := provider.NewProviders()
|
|
||||||
for i, providerName := range userSettings.Providers {
|
|
||||||
var err error
|
|
||||||
providers[i], err = providersData.Get(providerName)
|
|
||||||
if err != nil {
|
|
||||||
panic(err) // this should already had been checked
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return providers
|
|
||||||
}
|
|
||||||
|
|
||||||
providers = make([]provider.Provider, len(userSettings.UpstreamPlainAddresses))
|
|
||||||
for i, addrPort := range userSettings.UpstreamPlainAddresses {
|
|
||||||
addr := addrPort.Addr()
|
|
||||||
if addr.IsPrivate() && !addr.IsLoopback() &&
|
|
||||||
!slices.ContainsFunc(localSubnets, func(prefix netip.Prefix) bool {
|
|
||||||
return prefix.Contains(addr)
|
|
||||||
}) {
|
|
||||||
logger.Warnf("DNS server address %s is not in local subnets, "+
|
|
||||||
"make sure to specify it in FIREWALL_OUTBOUND_SUBNETS as %s",
|
|
||||||
addr, netip.PrefixFrom(addr, addr.BitLen()))
|
|
||||||
}
|
|
||||||
|
|
||||||
providers[i] = provider.Provider{
|
|
||||||
Name: addrPort.String(),
|
|
||||||
}
|
|
||||||
if addr.Is4() {
|
|
||||||
providers[i].Plain.IPv4 = []netip.AddrPort{addrPort}
|
|
||||||
} else {
|
|
||||||
providers[i].Plain.IPv6 = []netip.AddrPort{addrPort}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return providers
|
|
||||||
}
|
|
||||||
|
|||||||
+22
-9
@@ -2,23 +2,26 @@ package dns
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"net/netip"
|
||||||
|
|
||||||
"github.com/qdm12/dns/v2/pkg/middlewares/filter/update"
|
"github.com/qdm12/dns/v2/pkg/check"
|
||||||
"github.com/qdm12/dns/v2/pkg/nameserver"
|
"github.com/qdm12/dns/v2/pkg/nameserver"
|
||||||
"github.com/qdm12/dns/v2/pkg/server"
|
"github.com/qdm12/dns/v2/pkg/server"
|
||||||
"github.com/qdm12/gluetun/internal/configuration/settings"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func (l *Loop) setupServer(ctx context.Context, settings settings.DNS) (runError <-chan error, err error) {
|
var errUpdateBlockLists = errors.New("cannot update filter block lists")
|
||||||
var updateSettings update.Settings
|
|
||||||
updateSettings.SetRebindingProtectionExempt(settings.Blacklist.RebindingProtectionExemptHostnames)
|
func (l *Loop) setupServer(ctx context.Context) (runError <-chan error, err error) {
|
||||||
err = l.filter.Update(updateSettings)
|
err = l.updateFiles(ctx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("updating filter for rebinding protection: %w", err)
|
return nil, fmt.Errorf("%w: %w", errUpdateBlockLists, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
serverSettings, err := buildServerSettings(settings, l.filter, l.localResolvers, l.localSubnets, l.logger)
|
settings := l.GetSettings()
|
||||||
|
|
||||||
|
serverSettings, err := buildServerSettings(settings, l.filter, l.localResolvers, l.logger)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("building server settings: %w", err)
|
return nil, fmt.Errorf("building server settings: %w", err)
|
||||||
}
|
}
|
||||||
@@ -35,13 +38,23 @@ func (l *Loop) setupServer(ctx context.Context, settings settings.DNS) (runError
|
|||||||
l.server = server
|
l.server = server
|
||||||
|
|
||||||
// use internal DNS server
|
// use internal DNS server
|
||||||
nameserver.UseDNSInternally(nameserver.SettingsInternalDNS{})
|
const defaultDNSPort = 53
|
||||||
|
nameserver.UseDNSInternally(nameserver.SettingsInternalDNS{
|
||||||
|
AddrPort: netip.AddrPortFrom(settings.ServerAddress, defaultDNSPort),
|
||||||
|
})
|
||||||
err = nameserver.UseDNSSystemWide(nameserver.SettingsSystemDNS{
|
err = nameserver.UseDNSSystemWide(nameserver.SettingsSystemDNS{
|
||||||
|
IPs: []netip.Addr{settings.ServerAddress},
|
||||||
ResolvPath: l.resolvConf,
|
ResolvPath: l.resolvConf,
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
l.logger.Error(err.Error())
|
l.logger.Error(err.Error())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
err = check.WaitForDNS(ctx, check.Settings{})
|
||||||
|
if err != nil {
|
||||||
|
l.stopServer()
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
return runError, nil
|
return runError, nil
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -40,6 +40,8 @@ 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.ServerEnabled {
|
||||||
outcome, _ = s.statusApplier.ApplyStatus(ctx, constants.Running)
|
outcome, _ = s.statusApplier.ApplyStatus(ctx, constants.Running)
|
||||||
|
}
|
||||||
return outcome
|
return outcome
|
||||||
}
|
}
|
||||||
|
|||||||
+14
-3
@@ -28,12 +28,23 @@ func (l *Loop) RunRestartTicker(ctx context.Context, done chan<- struct{}) {
|
|||||||
return
|
return
|
||||||
case <-timer.C:
|
case <-timer.C:
|
||||||
lastTick = l.timeNow()
|
lastTick = l.timeNow()
|
||||||
|
|
||||||
|
status := l.GetStatus()
|
||||||
|
if status == constants.Running {
|
||||||
|
if err := l.updateFiles(ctx); err != nil {
|
||||||
|
l.statusManager.SetStatus(constants.Crashed)
|
||||||
|
l.logger.Error(err.Error())
|
||||||
|
l.logger.Warn("skipping DNS server restart due to failed files update")
|
||||||
settings := l.GetSettings()
|
settings := l.GetSettings()
|
||||||
if l.GetStatus() == constants.Running {
|
timer.Reset(*settings.UpdatePeriod)
|
||||||
if err := l.updateFiles(ctx, settings); err != nil {
|
continue
|
||||||
l.logger.Warn("updating block lists failed, skipping: " + err.Error())
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
_, _ = l.statusManager.ApplyStatus(ctx, constants.Stopped)
|
||||||
|
_, _ = l.statusManager.ApplyStatus(ctx, constants.Running)
|
||||||
|
|
||||||
|
settings := l.GetSettings()
|
||||||
timer.Reset(*settings.UpdatePeriod)
|
timer.Reset(*settings.UpdatePeriod)
|
||||||
case <-l.updateTicker:
|
case <-l.updateTicker:
|
||||||
if !timer.Stop() {
|
if !timer.Stop() {
|
||||||
|
|||||||
@@ -6,10 +6,11 @@ import (
|
|||||||
|
|
||||||
"github.com/qdm12/dns/v2/pkg/blockbuilder"
|
"github.com/qdm12/dns/v2/pkg/blockbuilder"
|
||||||
"github.com/qdm12/dns/v2/pkg/middlewares/filter/update"
|
"github.com/qdm12/dns/v2/pkg/middlewares/filter/update"
|
||||||
"github.com/qdm12/gluetun/internal/configuration/settings"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func (l *Loop) updateFiles(ctx context.Context, settings settings.DNS) (err error) {
|
func (l *Loop) updateFiles(ctx context.Context) (err error) {
|
||||||
|
settings := l.GetSettings()
|
||||||
|
|
||||||
l.logger.Info("downloading hostnames and IP block lists")
|
l.logger.Info("downloading hostnames and IP block lists")
|
||||||
blacklistSettings := settings.Blacklist.ToBlockBuilderSettings(l.client)
|
blacklistSettings := settings.Blacklist.ToBlockBuilderSettings(l.client)
|
||||||
|
|
||||||
@@ -36,6 +37,7 @@ func (l *Loop) updateFiles(ctx context.Context, settings settings.DNS) (err erro
|
|||||||
IPPrefixes: result.BlockedIPPrefixes,
|
IPPrefixes: result.BlockedIPPrefixes,
|
||||||
}
|
}
|
||||||
updateSettings.BlockHostnames(result.BlockedHostnames)
|
updateSettings.BlockHostnames(result.BlockedHostnames)
|
||||||
|
updateSettings.SetRebindingProtectionExempt(settings.Blacklist.RebindingProtectionExemptHostnames)
|
||||||
err = l.filter.Update(updateSettings)
|
err = l.filter.Update(updateSettings)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("updating filter: %w", err)
|
return fmt.Errorf("updating filter: %w", err)
|
||||||
|
|||||||
+1
-1
@@ -1,4 +1,4 @@
|
|||||||
package iptables
|
package firewall
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
package iptables
|
package firewall
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
package iptables
|
package firewall
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
@@ -69,8 +69,8 @@ func Test_deleteIPTablesRule(t *testing.T) {
|
|||||||
"invalid_instruction": {
|
"invalid_instruction": {
|
||||||
instruction: "invalid",
|
instruction: "invalid",
|
||||||
errWrapped: ErrIptablesCommandMalformed,
|
errWrapped: ErrIptablesCommandMalformed,
|
||||||
errMessage: "parsing iptables command: parsing \"invalid\": " +
|
errMessage: "parsing iptables command: iptables command is malformed: " +
|
||||||
"iptables command is malformed: flag \"invalid\" requires a value, but got none",
|
"fields count 1 is not even: \"invalid\"",
|
||||||
},
|
},
|
||||||
"list_error": {
|
"list_error": {
|
||||||
instruction: "-t nat --delete PREROUTING -i tun0 -p tcp --dport 43716 -j REDIRECT --to-ports 5678",
|
instruction: "-t nat --delete PREROUTING -i tun0 -p tcp --dport 43716 -j REDIRECT --to-ports 5678",
|
||||||
+60
-34
@@ -22,7 +22,9 @@ func (c *Config) SetEnabled(ctx context.Context, enabled bool) (err error) {
|
|||||||
|
|
||||||
if !enabled {
|
if !enabled {
|
||||||
c.logger.Info("disabling...")
|
c.logger.Info("disabling...")
|
||||||
c.restore(ctx)
|
if err = c.disable(ctx); err != nil {
|
||||||
|
return fmt.Errorf("disabling firewall: %w", err)
|
||||||
|
}
|
||||||
c.enabled = false
|
c.enabled = false
|
||||||
c.logger.Info("disabled successfully")
|
c.logger.Info("disabled successfully")
|
||||||
return nil
|
return nil
|
||||||
@@ -39,37 +41,64 @@ func (c *Config) SetEnabled(ctx context.Context, enabled bool) (err error) {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (c *Config) disable(ctx context.Context) (err error) {
|
||||||
|
if err = c.clearAllRules(ctx); err != nil {
|
||||||
|
return fmt.Errorf("clearing all rules: %w", err)
|
||||||
|
}
|
||||||
|
if err = c.setIPv4AllPolicies(ctx, "ACCEPT"); err != nil {
|
||||||
|
return fmt.Errorf("setting ipv4 policies: %w", err)
|
||||||
|
}
|
||||||
|
if err = c.setIPv6AllPolicies(ctx, "ACCEPT"); err != nil {
|
||||||
|
return fmt.Errorf("setting ipv6 policies: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
const remove = true
|
||||||
|
err = c.redirectPorts(ctx, remove)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("removing port redirections: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// To use in defered call when enabling the firewall.
|
||||||
|
func (c *Config) fallbackToDisabled(ctx context.Context) {
|
||||||
|
if ctx.Err() != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if err := c.disable(ctx); err != nil {
|
||||||
|
c.logger.Error("failed reversing firewall changes: " + err.Error())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func (c *Config) enable(ctx context.Context) (err error) {
|
func (c *Config) enable(ctx context.Context) (err error) {
|
||||||
c.restore, err = c.impl.SaveAndRestore(ctx)
|
touched := false
|
||||||
if err != nil {
|
if err = c.setIPv4AllPolicies(ctx, "DROP"); err != nil {
|
||||||
return fmt.Errorf("saving firewall rules: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
defer func() {
|
|
||||||
if err != nil {
|
|
||||||
c.restore(context.Background())
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
|
|
||||||
if err = c.impl.SetIPv4AllPolicies(ctx, "DROP"); err != nil {
|
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
touched = true
|
||||||
|
|
||||||
if err = c.impl.SetIPv6AllPolicies(ctx, "DROP"); err != nil {
|
if err = c.setIPv6AllPolicies(ctx, "DROP"); err != nil {
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
// Loopback traffic
|
|
||||||
if err = c.impl.AcceptInputThroughInterface(ctx, "lo"); err != nil {
|
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
const remove = false
|
const remove = false
|
||||||
if err = c.impl.AcceptOutputThroughInterface(ctx, "lo", remove); err != nil {
|
|
||||||
|
defer func() {
|
||||||
|
if touched && err != nil {
|
||||||
|
c.fallbackToDisabled(ctx)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
// Loopback traffic
|
||||||
|
if err = c.acceptInputThroughInterface(ctx, "lo", remove); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if err = c.acceptOutputThroughInterface(ctx, "lo", remove); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
if err = c.impl.AcceptEstablishedRelatedTraffic(ctx); err != nil {
|
if err = c.acceptEstablishedRelatedTraffic(ctx, remove); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -79,9 +108,7 @@ func (c *Config) enable(ctx context.Context) (err error) {
|
|||||||
|
|
||||||
localInterfaces := make(map[string]struct{}, len(c.localNetworks))
|
localInterfaces := make(map[string]struct{}, len(c.localNetworks))
|
||||||
for _, network := range c.localNetworks {
|
for _, network := range c.localNetworks {
|
||||||
err = c.impl.AcceptOutputFromIPToSubnet(ctx,
|
if err := c.acceptOutputFromIPToSubnet(ctx, network.InterfaceName, network.IP, network.IPNet, remove); err != nil {
|
||||||
network.InterfaceName, network.IP, network.IPNet, remove)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -90,7 +117,7 @@ func (c *Config) enable(ctx context.Context) (err error) {
|
|||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
localInterfaces[network.InterfaceName] = struct{}{}
|
localInterfaces[network.InterfaceName] = struct{}{}
|
||||||
err = c.impl.AcceptIpv6MulticastOutput(ctx, network.InterfaceName)
|
err = c.acceptIpv6MulticastOutput(ctx, network.InterfaceName, remove)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("accepting IPv6 multicast output: %w", err)
|
return fmt.Errorf("accepting IPv6 multicast output: %w", err)
|
||||||
}
|
}
|
||||||
@@ -103,7 +130,7 @@ func (c *Config) enable(ctx context.Context) (err error) {
|
|||||||
// Allows packets from any IP address to go through eth0 / local network
|
// Allows packets from any IP address to go through eth0 / local network
|
||||||
// to reach Gluetun.
|
// to reach Gluetun.
|
||||||
for _, network := range c.localNetworks {
|
for _, network := range c.localNetworks {
|
||||||
if err := c.impl.AcceptInputToSubnet(ctx, network.InterfaceName, network.IPNet); err != nil {
|
if err := c.acceptInputToSubnet(ctx, network.InterfaceName, network.IPNet, remove); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -112,12 +139,12 @@ func (c *Config) enable(ctx context.Context) (err error) {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
err = c.redirectPorts(ctx)
|
err = c.redirectPorts(ctx, remove)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("redirecting ports: %w", err)
|
return fmt.Errorf("redirecting ports: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := c.impl.RunUserPostRules(ctx, c.customRulesPath); err != nil {
|
if err := c.runUserPostRules(ctx, c.customRulesPath, remove); err != nil {
|
||||||
return fmt.Errorf("running user defined post firewall rules: %w", err)
|
return fmt.Errorf("running user defined post firewall rules: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -137,7 +164,7 @@ func (c *Config) allowVPNIP(ctx context.Context) (err error) {
|
|||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
interfacesSeen[defaultRoute.NetInterface] = struct{}{}
|
interfacesSeen[defaultRoute.NetInterface] = struct{}{}
|
||||||
err = c.impl.AcceptOutputTrafficToVPN(ctx, defaultRoute.NetInterface, c.vpnConnection, remove)
|
err = c.acceptOutputTrafficToVPN(ctx, defaultRoute.NetInterface, c.vpnConnection, remove)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("accepting output traffic through VPN: %w", err)
|
return fmt.Errorf("accepting output traffic through VPN: %w", err)
|
||||||
}
|
}
|
||||||
@@ -159,7 +186,7 @@ func (c *Config) allowOutboundSubnets(ctx context.Context) (err error) {
|
|||||||
firewallUpdated = true
|
firewallUpdated = true
|
||||||
|
|
||||||
const remove = false
|
const remove = false
|
||||||
err := c.impl.AcceptOutputFromIPToSubnet(ctx, defaultRoute.NetInterface,
|
err := c.acceptOutputFromIPToSubnet(ctx, defaultRoute.NetInterface,
|
||||||
defaultRoute.AssignedIP, subnet, remove)
|
defaultRoute.AssignedIP, subnet, remove)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
@@ -177,7 +204,7 @@ func (c *Config) allowInputPorts(ctx context.Context) (err error) {
|
|||||||
for port, netInterfaces := range c.allowedInputPorts {
|
for port, netInterfaces := range c.allowedInputPorts {
|
||||||
for netInterface := range netInterfaces {
|
for netInterface := range netInterfaces {
|
||||||
const remove = false
|
const remove = false
|
||||||
err = c.impl.AcceptInputToPort(ctx, netInterface, port, remove)
|
err = c.acceptInputToPort(ctx, netInterface, port, remove)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("accepting input port %d on interface %s: %w",
|
return fmt.Errorf("accepting input port %d on interface %s: %w",
|
||||||
port, netInterface, err)
|
port, netInterface, err)
|
||||||
@@ -187,10 +214,9 @@ func (c *Config) allowInputPorts(ctx context.Context) (err error) {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Config) redirectPorts(ctx context.Context) (err error) {
|
func (c *Config) redirectPorts(ctx context.Context, remove bool) (err error) {
|
||||||
for _, portRedirection := range c.portRedirections {
|
for _, portRedirection := range c.portRedirections {
|
||||||
const remove = false
|
err = c.redirectPort(ctx, portRedirection.interfaceName, portRedirection.sourcePort,
|
||||||
err = c.impl.RedirectPort(ctx, portRedirection.interfaceName, portRedirection.sourcePort,
|
|
||||||
portRedirection.destinationPort, remove)
|
portRedirection.destinationPort, remove)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
|
|||||||
@@ -2,11 +2,9 @@ package firewall
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"fmt"
|
|
||||||
"net/netip"
|
"net/netip"
|
||||||
"sync"
|
"sync"
|
||||||
|
|
||||||
"github.com/qdm12/gluetun/internal/firewall/iptables"
|
|
||||||
"github.com/qdm12/gluetun/internal/models"
|
"github.com/qdm12/gluetun/internal/models"
|
||||||
"github.com/qdm12/gluetun/internal/routing"
|
"github.com/qdm12/gluetun/internal/routing"
|
||||||
)
|
)
|
||||||
@@ -14,16 +12,18 @@ import (
|
|||||||
type Config struct {
|
type Config struct {
|
||||||
runner CmdRunner
|
runner CmdRunner
|
||||||
logger Logger
|
logger Logger
|
||||||
|
iptablesMutex sync.Mutex
|
||||||
|
ip6tablesMutex sync.Mutex
|
||||||
defaultRoutes []routing.DefaultRoute
|
defaultRoutes []routing.DefaultRoute
|
||||||
localNetworks []routing.LocalNetwork
|
localNetworks []routing.LocalNetwork
|
||||||
|
|
||||||
// Fixed
|
// Fixed state
|
||||||
impl firewallImpl
|
ipTables string
|
||||||
|
ip6Tables string
|
||||||
customRulesPath string
|
customRulesPath string
|
||||||
|
|
||||||
// State
|
// State
|
||||||
enabled bool
|
enabled bool
|
||||||
restore func(context.Context)
|
|
||||||
vpnConnection models.Connection
|
vpnConnection models.Connection
|
||||||
vpnIntf string
|
vpnIntf string
|
||||||
outboundSubnets []netip.Prefix
|
outboundSubnets []netip.Prefix
|
||||||
@@ -34,23 +34,29 @@ type Config struct {
|
|||||||
|
|
||||||
// NewConfig creates a new Config instance and returns an error
|
// NewConfig creates a new Config instance and returns an error
|
||||||
// if no iptables implementation is available.
|
// if no iptables implementation is available.
|
||||||
func NewConfig(ctx context.Context, logger, iptablesLogger Logger,
|
func NewConfig(ctx context.Context, logger Logger,
|
||||||
runner CmdRunner, defaultRoutes []routing.DefaultRoute,
|
runner CmdRunner, defaultRoutes []routing.DefaultRoute,
|
||||||
localNetworks []routing.LocalNetwork,
|
localNetworks []routing.LocalNetwork,
|
||||||
) (config *Config, err error) {
|
) (config *Config, err error) {
|
||||||
impl, err := iptables.New(ctx, runner, iptablesLogger)
|
iptables, err := checkIptablesSupport(ctx, runner, "iptables", "iptables-nft", "iptables-legacy")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("creating iptables firewall: %w", err)
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
ip6tables, err := findIP6tablesSupported(ctx, runner)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
return &Config{
|
return &Config{
|
||||||
runner: runner,
|
runner: runner,
|
||||||
logger: logger,
|
logger: logger,
|
||||||
allowedInputPorts: make(map[uint16]map[string]struct{}),
|
allowedInputPorts: make(map[uint16]map[string]struct{}),
|
||||||
|
ipTables: iptables,
|
||||||
|
ip6Tables: ip6tables,
|
||||||
|
customRulesPath: "/iptables/post-rules.txt",
|
||||||
// Obtained from routing
|
// Obtained from routing
|
||||||
defaultRoutes: defaultRoutes,
|
defaultRoutes: defaultRoutes,
|
||||||
localNetworks: localNetworks,
|
localNetworks: localNetworks,
|
||||||
impl: impl,
|
|
||||||
customRulesPath: "/iptables/post-rules.txt",
|
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,12 +1,6 @@
|
|||||||
package firewall
|
package firewall
|
||||||
|
|
||||||
import (
|
import "os/exec"
|
||||||
"context"
|
|
||||||
"net/netip"
|
|
||||||
"os/exec"
|
|
||||||
|
|
||||||
"github.com/qdm12/gluetun/internal/models"
|
|
||||||
)
|
|
||||||
|
|
||||||
type CmdRunner interface {
|
type CmdRunner interface {
|
||||||
Run(cmd *exec.Cmd) (output string, err error)
|
Run(cmd *exec.Cmd) (output string, err error)
|
||||||
@@ -18,27 +12,3 @@ type Logger interface {
|
|||||||
Warn(s string)
|
Warn(s string)
|
||||||
Error(s string)
|
Error(s string)
|
||||||
}
|
}
|
||||||
|
|
||||||
type firewallImpl interface { //nolint:interfacebloat
|
|
||||||
SaveAndRestore(ctx context.Context) (restore func(context.Context), err error)
|
|
||||||
AcceptEstablishedRelatedTraffic(ctx context.Context) error
|
|
||||||
AcceptInputThroughInterface(ctx context.Context, intf string) error
|
|
||||||
AcceptInputToPort(ctx context.Context, intf string, port uint16, remove bool) error
|
|
||||||
AcceptInputToSubnet(ctx context.Context, intf string, subnet netip.Prefix) error
|
|
||||||
AcceptIpv6MulticastOutput(ctx context.Context, intf string) error
|
|
||||||
AcceptOutput(ctx context.Context, protocol, intf string,
|
|
||||||
ip netip.Addr, port uint16, remove bool) error
|
|
||||||
AcceptOutputFromIPToSubnet(ctx context.Context, intf string, assignedIP netip.Addr,
|
|
||||||
subnet netip.Prefix, remove bool) error
|
|
||||||
AcceptOutputThroughInterface(ctx context.Context, intf string, remove bool) error
|
|
||||||
AcceptOutputTrafficToVPN(ctx context.Context, intf string,
|
|
||||||
connection models.Connection, remove bool) error
|
|
||||||
RedirectPort(ctx context.Context, intf string, sourcePort,
|
|
||||||
destinationPort uint16, remove bool) error
|
|
||||||
RunUserPostRules(ctx context.Context, customRulesPath string) error
|
|
||||||
SetIPv4AllPolicies(ctx context.Context, policy string) error
|
|
||||||
SetIPv6AllPolicies(ctx context.Context, policy string) error
|
|
||||||
TempDropOutputTCPRST(ctx context.Context, src, dst netip.AddrPort, excludeMark int) (
|
|
||||||
revert func(ctx context.Context) error, err error)
|
|
||||||
Version(ctx context.Context) (version string, err error)
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
package iptables
|
package firewall
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
@@ -14,8 +14,8 @@ import (
|
|||||||
func findIP6tablesSupported(ctx context.Context, runner CmdRunner) (
|
func findIP6tablesSupported(ctx context.Context, runner CmdRunner) (
|
||||||
ip6tablesPath string, err error,
|
ip6tablesPath string, err error,
|
||||||
) {
|
) {
|
||||||
ip6tablesPath, err = checkIptablesSupport(ctx, runner, "ip6tables", "ip6tables-legacy")
|
ip6tablesPath, err = checkIptablesSupport(ctx, runner, "ip6tables", "ip6tables-nft", "ip6tables-legacy")
|
||||||
if errors.Is(err, ErrNotSupported) {
|
if errors.Is(err, ErrIPTablesNotSupported) {
|
||||||
return "", nil
|
return "", nil
|
||||||
} else if err != nil {
|
} else if err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
@@ -24,23 +24,8 @@ func findIP6tablesSupported(ctx context.Context, runner CmdRunner) (
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (c *Config) runIP6tablesInstructions(ctx context.Context, instructions []string) error {
|
func (c *Config) runIP6tablesInstructions(ctx context.Context, instructions []string) error {
|
||||||
c.iptablesMutex.Lock() // only one iptables command at once
|
|
||||||
defer c.iptablesMutex.Unlock()
|
|
||||||
|
|
||||||
restore, err := c.saveAndRestoreIPv6(ctx)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
err = c.runIP6tablesInstructionsNoSave(ctx, instructions)
|
|
||||||
if err != nil {
|
|
||||||
restore(ctx)
|
|
||||||
}
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *Config) runIP6tablesInstructionsNoSave(ctx context.Context, instructions []string) error {
|
|
||||||
for _, instruction := range instructions {
|
for _, instruction := range instructions {
|
||||||
if err := c.runIP6tablesInstructionNoSave(ctx, instruction); err != nil {
|
if err := c.runIP6tablesInstruction(ctx, instruction); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -48,24 +33,11 @@ func (c *Config) runIP6tablesInstructionsNoSave(ctx context.Context, instruction
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (c *Config) runIP6tablesInstruction(ctx context.Context, instruction string) error {
|
func (c *Config) runIP6tablesInstruction(ctx context.Context, instruction string) error {
|
||||||
c.iptablesMutex.Lock() // only one iptables command at once
|
|
||||||
defer c.iptablesMutex.Unlock()
|
|
||||||
|
|
||||||
restore, err := c.saveAndRestoreIPv6(ctx)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
err = c.runIP6tablesInstructionNoSave(ctx, instruction)
|
|
||||||
if err != nil {
|
|
||||||
restore(ctx)
|
|
||||||
}
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *Config) runIP6tablesInstructionNoSave(ctx context.Context, instruction string) error {
|
|
||||||
if c.ip6Tables == "" {
|
if c.ip6Tables == "" {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
c.ip6tablesMutex.Lock() // only one ip6tables command at once
|
||||||
|
defer c.ip6tablesMutex.Unlock()
|
||||||
|
|
||||||
if isDeleteMatchInstruction(instruction) {
|
if isDeleteMatchInstruction(instruction) {
|
||||||
return deleteIPTablesRule(ctx, c.ip6Tables, instruction,
|
return deleteIPTablesRule(ctx, c.ip6Tables, instruction,
|
||||||
@@ -84,7 +56,7 @@ func (c *Config) runIP6tablesInstructionNoSave(ctx context.Context, instruction
|
|||||||
|
|
||||||
var ErrPolicyNotValid = errors.New("policy is not valid")
|
var ErrPolicyNotValid = errors.New("policy is not valid")
|
||||||
|
|
||||||
func (c *Config) SetIPv6AllPolicies(ctx context.Context, policy string) error {
|
func (c *Config) setIPv6AllPolicies(ctx context.Context, policy string) error {
|
||||||
switch policy {
|
switch policy {
|
||||||
case "ACCEPT", "DROP":
|
case "ACCEPT", "DROP":
|
||||||
default:
|
default:
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
package iptables
|
package firewall
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
@@ -26,6 +26,22 @@ func appendOrDelete(remove bool) string {
|
|||||||
return "--append"
|
return "--append"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// flipRule changes an append rule in a delete rule or a delete rule into an
|
||||||
|
// append rule.
|
||||||
|
func flipRule(rule string) string {
|
||||||
|
switch {
|
||||||
|
case strings.HasPrefix(rule, "-A"):
|
||||||
|
return strings.Replace(rule, "-A", "-D", 1)
|
||||||
|
case strings.HasPrefix(rule, "--append"):
|
||||||
|
return strings.Replace(rule, "--append", "-D", 1)
|
||||||
|
case strings.HasPrefix(rule, "-D"):
|
||||||
|
return strings.Replace(rule, "-D", "-A", 1)
|
||||||
|
case strings.HasPrefix(rule, "--delete"):
|
||||||
|
return strings.Replace(rule, "--delete", "-A", 1)
|
||||||
|
}
|
||||||
|
return rule
|
||||||
|
}
|
||||||
|
|
||||||
// Version obtains the version of the installed iptables.
|
// Version obtains the version of the installed iptables.
|
||||||
func (c *Config) Version(ctx context.Context) (string, error) {
|
func (c *Config) Version(ctx context.Context) (string, error) {
|
||||||
cmd := exec.CommandContext(ctx, c.ipTables, "--version") //nolint:gosec
|
cmd := exec.CommandContext(ctx, c.ipTables, "--version") //nolint:gosec
|
||||||
@@ -38,28 +54,12 @@ func (c *Config) Version(ctx context.Context) (string, error) {
|
|||||||
if len(words) < minWords {
|
if len(words) < minWords {
|
||||||
return "", fmt.Errorf("%w: %s", ErrIPTablesVersionTooShort, output)
|
return "", fmt.Errorf("%w: %s", ErrIPTablesVersionTooShort, output)
|
||||||
}
|
}
|
||||||
return "iptables " + words[1], nil
|
return words[1], nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Config) runIptablesInstructions(ctx context.Context, instructions []string) error {
|
func (c *Config) runIptablesInstructions(ctx context.Context, instructions []string) error {
|
||||||
c.iptablesMutex.Lock()
|
|
||||||
defer c.iptablesMutex.Unlock()
|
|
||||||
|
|
||||||
restore, err := c.saveAndRestoreIPv4(ctx)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
err = c.runIptablesInstructionsNoSave(ctx, instructions)
|
|
||||||
if err != nil {
|
|
||||||
restore(ctx)
|
|
||||||
}
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *Config) runIptablesInstructionsNoSave(ctx context.Context, instructions []string) error {
|
|
||||||
for _, instruction := range instructions {
|
for _, instruction := range instructions {
|
||||||
if err := c.runIptablesInstructionNoSave(ctx, instruction); err != nil {
|
if err := c.runIptablesInstruction(ctx, instruction); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -70,19 +70,6 @@ func (c *Config) runIptablesInstruction(ctx context.Context, instruction string)
|
|||||||
c.iptablesMutex.Lock() // only one iptables command at once
|
c.iptablesMutex.Lock() // only one iptables command at once
|
||||||
defer c.iptablesMutex.Unlock()
|
defer c.iptablesMutex.Unlock()
|
||||||
|
|
||||||
restore, err := c.saveAndRestoreIPv4(ctx)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
err = c.runIptablesInstructionNoSave(ctx, instruction)
|
|
||||||
if err != nil {
|
|
||||||
restore(ctx)
|
|
||||||
}
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *Config) runIptablesInstructionNoSave(ctx context.Context, instruction string) error {
|
|
||||||
if isDeleteMatchInstruction(instruction) {
|
if isDeleteMatchInstruction(instruction) {
|
||||||
return deleteIPTablesRule(ctx, c.ipTables, instruction,
|
return deleteIPTablesRule(ctx, c.ipTables, instruction,
|
||||||
c.runner, c.logger)
|
c.runner, c.logger)
|
||||||
@@ -98,7 +85,14 @@ func (c *Config) runIptablesInstructionNoSave(ctx context.Context, instruction s
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Config) SetIPv4AllPolicies(ctx context.Context, policy string) error {
|
func (c *Config) clearAllRules(ctx context.Context) error {
|
||||||
|
return c.runMixedIptablesInstructions(ctx, []string{
|
||||||
|
"--flush", // flush all chains
|
||||||
|
"--delete-chain", // delete all chains
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Config) setIPv4AllPolicies(ctx context.Context, policy string) error {
|
||||||
switch policy {
|
switch policy {
|
||||||
case "ACCEPT", "DROP":
|
case "ACCEPT", "DROP":
|
||||||
default:
|
default:
|
||||||
@@ -111,19 +105,22 @@ func (c *Config) SetIPv4AllPolicies(ctx context.Context, policy string) error {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Config) AcceptInputThroughInterface(ctx context.Context, intf string) error {
|
func (c *Config) acceptInputThroughInterface(ctx context.Context, intf string, remove bool) error {
|
||||||
return c.runMixedIptablesInstruction(ctx, fmt.Sprintf(
|
return c.runMixedIptablesInstruction(ctx, fmt.Sprintf(
|
||||||
"--append INPUT -i %s -j ACCEPT", intf))
|
"%s INPUT -i %s -j ACCEPT", appendOrDelete(remove), intf,
|
||||||
|
))
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Config) AcceptInputToSubnet(ctx context.Context, intf string, destination netip.Prefix) error {
|
func (c *Config) acceptInputToSubnet(ctx context.Context, intf string,
|
||||||
|
destination netip.Prefix, remove bool,
|
||||||
|
) error {
|
||||||
interfaceFlag := "-i " + intf
|
interfaceFlag := "-i " + intf
|
||||||
if intf == "*" { // all interfaces
|
if intf == "*" { // all interfaces
|
||||||
interfaceFlag = ""
|
interfaceFlag = ""
|
||||||
}
|
}
|
||||||
|
|
||||||
instruction := fmt.Sprintf("--append INPUT %s -d %s -j ACCEPT",
|
instruction := fmt.Sprintf("%s INPUT %s -d %s -j ACCEPT",
|
||||||
interfaceFlag, destination.String())
|
appendOrDelete(remove), interfaceFlag, destination.String())
|
||||||
|
|
||||||
if destination.Addr().Is4() {
|
if destination.Addr().Is4() {
|
||||||
return c.runIptablesInstruction(ctx, instruction)
|
return c.runIptablesInstruction(ctx, instruction)
|
||||||
@@ -134,23 +131,26 @@ func (c *Config) AcceptInputToSubnet(ctx context.Context, intf string, destinati
|
|||||||
return c.runIP6tablesInstruction(ctx, instruction)
|
return c.runIP6tablesInstruction(ctx, instruction)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Config) AcceptOutputThroughInterface(ctx context.Context, intf string, remove bool) error {
|
func (c *Config) acceptOutputThroughInterface(ctx context.Context, intf string, remove bool) error {
|
||||||
return c.runMixedIptablesInstruction(ctx, fmt.Sprintf(
|
return c.runMixedIptablesInstruction(ctx, fmt.Sprintf(
|
||||||
"%s OUTPUT -o %s -j ACCEPT", appendOrDelete(remove), intf,
|
"%s OUTPUT -o %s -j ACCEPT", appendOrDelete(remove), intf,
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Config) AcceptEstablishedRelatedTraffic(ctx context.Context) error {
|
func (c *Config) acceptEstablishedRelatedTraffic(ctx context.Context, remove bool) error {
|
||||||
return c.runMixedIptablesInstructions(ctx, []string{
|
return c.runMixedIptablesInstructions(ctx, []string{
|
||||||
"--append OUTPUT -m conntrack --ctstate ESTABLISHED,RELATED -j ACCEPT",
|
fmt.Sprintf("%s OUTPUT -m conntrack --ctstate ESTABLISHED,RELATED -j ACCEPT", appendOrDelete(remove)),
|
||||||
"--append INPUT -m conntrack --ctstate ESTABLISHED,RELATED -j ACCEPT",
|
fmt.Sprintf("%s INPUT -m conntrack --ctstate ESTABLISHED,RELATED -j ACCEPT", appendOrDelete(remove)),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Config) AcceptOutputTrafficToVPN(ctx context.Context,
|
func (c *Config) acceptOutputTrafficToVPN(ctx context.Context,
|
||||||
defaultInterface string, connection models.Connection, remove bool,
|
defaultInterface string, connection models.Connection, remove bool,
|
||||||
) error {
|
) error {
|
||||||
protocol := connection.Protocol
|
protocol := connection.Protocol
|
||||||
|
if protocol == "tcp-client" {
|
||||||
|
protocol = "tcp"
|
||||||
|
}
|
||||||
instruction := fmt.Sprintf("%s OUTPUT -d %s -o %s -p %s -m %s --dport %d -j ACCEPT",
|
instruction := fmt.Sprintf("%s OUTPUT -d %s -o %s -p %s -m %s --dport %d -j ACCEPT",
|
||||||
appendOrDelete(remove), connection.IP, defaultInterface, protocol,
|
appendOrDelete(remove), connection.IP, defaultInterface, protocol,
|
||||||
protocol, connection.Port)
|
protocol, connection.Port)
|
||||||
@@ -162,29 +162,8 @@ func (c *Config) AcceptOutputTrafficToVPN(ctx context.Context,
|
|||||||
return c.runIP6tablesInstruction(ctx, instruction)
|
return c.runIP6tablesInstruction(ctx, instruction)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Config) AcceptOutput(ctx context.Context,
|
|
||||||
protocol, intf string, ip netip.Addr, port uint16, remove bool,
|
|
||||||
) error {
|
|
||||||
interfaceFlag := "-o " + intf
|
|
||||||
if intf == "*" { // all interfaces
|
|
||||||
interfaceFlag = ""
|
|
||||||
}
|
|
||||||
|
|
||||||
instruction := fmt.Sprintf("%s OUTPUT -d %s %s -p %s -m %s --dport %d -j ACCEPT",
|
|
||||||
appendOrDelete(remove), ip, interfaceFlag, protocol, protocol, port)
|
|
||||||
if ip.Is4() {
|
|
||||||
return c.runIptablesInstruction(ctx, instruction)
|
|
||||||
} else if c.ip6Tables == "" {
|
|
||||||
return fmt.Errorf("accept output to VPN server: %w", ErrNeedIP6Tables)
|
|
||||||
}
|
|
||||||
return c.runIP6tablesInstruction(ctx, instruction)
|
|
||||||
}
|
|
||||||
|
|
||||||
// AcceptOutputFromIPToSubnet accepts outgoing traffic from sourceIP to destinationSubnet
|
|
||||||
// on the interface intf. If intf is empty, it is set to "*" which means all interfaces.
|
|
||||||
// If remove is true, the rule is removed instead of added.
|
|
||||||
// Thanks to @npawelek.
|
// Thanks to @npawelek.
|
||||||
func (c *Config) AcceptOutputFromIPToSubnet(ctx context.Context,
|
func (c *Config) acceptOutputFromIPToSubnet(ctx context.Context,
|
||||||
intf string, sourceIP netip.Addr, destinationSubnet netip.Prefix, remove bool,
|
intf string, sourceIP netip.Addr, destinationSubnet netip.Prefix, remove bool,
|
||||||
) error {
|
) error {
|
||||||
doIPv4 := sourceIP.Is4() && destinationSubnet.Addr().Is4()
|
doIPv4 := sourceIP.Is4() && destinationSubnet.Addr().Is4()
|
||||||
@@ -205,24 +184,21 @@ func (c *Config) AcceptOutputFromIPToSubnet(ctx context.Context,
|
|||||||
return c.runIP6tablesInstruction(ctx, instruction)
|
return c.runIP6tablesInstruction(ctx, instruction)
|
||||||
}
|
}
|
||||||
|
|
||||||
// AcceptIpv6MulticastOutput accepts outgoing traffic to the IPv6 multicast address
|
// NDP uses multicast address (theres no broadcast in IPv6 like ARP uses in IPv4).
|
||||||
// ff02::1:ff00:0/104, which is used for NDP (Neighbor Discovery Protocol) to resolve
|
func (c *Config) acceptIpv6MulticastOutput(ctx context.Context,
|
||||||
// IPv6 addresses to MAC addresses. If intf is empty, it is set to "*" which means
|
intf string, remove bool,
|
||||||
// all interfaces. If remove is true, the rule is removed instead of added.
|
) error {
|
||||||
func (c *Config) AcceptIpv6MulticastOutput(ctx context.Context, intf string) error {
|
|
||||||
interfaceFlag := "-o " + intf
|
interfaceFlag := "-o " + intf
|
||||||
if intf == "*" { // all interfaces
|
if intf == "*" { // all interfaces
|
||||||
interfaceFlag = ""
|
interfaceFlag = ""
|
||||||
}
|
}
|
||||||
instruction := fmt.Sprintf("--append OUTPUT %s -d ff02::1:ff00:0/104 -j ACCEPT", interfaceFlag)
|
instruction := fmt.Sprintf("%s OUTPUT %s -d ff02::1:ff00:0/104 -j ACCEPT",
|
||||||
|
appendOrDelete(remove), interfaceFlag)
|
||||||
return c.runIP6tablesInstruction(ctx, instruction)
|
return c.runIP6tablesInstruction(ctx, instruction)
|
||||||
}
|
}
|
||||||
|
|
||||||
// AcceptInputToPort accepts incoming traffic on the specified port, for both TCP and UDP
|
// Used for port forwarding, with intf set to tun.
|
||||||
// protocols, on the interface intf. If intf is empty, it is set to "*" which means all interfaces.
|
func (c *Config) acceptInputToPort(ctx context.Context, intf string, port uint16, remove bool) error {
|
||||||
// If remove is true, the rule is removed instead of added. This is used for port forwarding, with
|
|
||||||
// intf set to the VPN tunnel interface.
|
|
||||||
func (c *Config) AcceptInputToPort(ctx context.Context, intf string, port uint16, remove bool) error {
|
|
||||||
interfaceFlag := "-i " + intf
|
interfaceFlag := "-i " + intf
|
||||||
if intf == "*" { // all interfaces
|
if intf == "*" { // all interfaces
|
||||||
interfaceFlag = ""
|
interfaceFlag = ""
|
||||||
@@ -233,12 +209,8 @@ func (c *Config) AcceptInputToPort(ctx context.Context, intf string, port uint16
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
// RedirectPort redirects incoming traffic on the specified source port to the
|
// Used for VPN server side port forwarding, with intf set to the VPN tunnel interface.
|
||||||
// specified destination port, for both TCP and UDP protocols, on the interface intf.
|
func (c *Config) redirectPort(ctx context.Context, intf string,
|
||||||
// If intf is empty, it is set to "*" which means all interfaces. If remove is true,
|
|
||||||
// the redirection is removed instead of added. This is used for VPN server side
|
|
||||||
// port forwarding, with intf set to the VPN tunnel interface.
|
|
||||||
func (c *Config) RedirectPort(ctx context.Context, intf string,
|
|
||||||
sourcePort, destinationPort uint16, remove bool,
|
sourcePort, destinationPort uint16, remove bool,
|
||||||
) (err error) {
|
) (err error) {
|
||||||
interfaceFlag := "-i " + intf
|
interfaceFlag := "-i " + intf
|
||||||
@@ -246,15 +218,7 @@ func (c *Config) RedirectPort(ctx context.Context, intf string,
|
|||||||
interfaceFlag = ""
|
interfaceFlag = ""
|
||||||
}
|
}
|
||||||
|
|
||||||
c.iptablesMutex.Lock()
|
err = c.runIptablesInstructions(ctx, []string{
|
||||||
defer c.iptablesMutex.Unlock()
|
|
||||||
|
|
||||||
restore, err := c.saveAndRestore(ctx)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
err = c.runIptablesInstructionsNoSave(ctx, []string{
|
|
||||||
fmt.Sprintf("-t nat %s PREROUTING %s -p tcp --dport %d -j REDIRECT --to-ports %d",
|
fmt.Sprintf("-t nat %s PREROUTING %s -p tcp --dport %d -j REDIRECT --to-ports %d",
|
||||||
appendOrDelete(remove), interfaceFlag, sourcePort, destinationPort),
|
appendOrDelete(remove), interfaceFlag, sourcePort, destinationPort),
|
||||||
fmt.Sprintf("%s INPUT %s -p tcp -m tcp --dport %d -j ACCEPT",
|
fmt.Sprintf("%s INPUT %s -p tcp -m tcp --dport %d -j ACCEPT",
|
||||||
@@ -265,12 +229,11 @@ func (c *Config) RedirectPort(ctx context.Context, intf string,
|
|||||||
appendOrDelete(remove), interfaceFlag, destinationPort),
|
appendOrDelete(remove), interfaceFlag, destinationPort),
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
restore(ctx)
|
|
||||||
return fmt.Errorf("redirecting IPv4 source port %d to destination port %d on interface %s: %w",
|
return fmt.Errorf("redirecting IPv4 source port %d to destination port %d on interface %s: %w",
|
||||||
sourcePort, destinationPort, intf, err)
|
sourcePort, destinationPort, intf, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
err = c.runIP6tablesInstructionsNoSave(ctx, []string{
|
err = c.runIP6tablesInstructions(ctx, []string{
|
||||||
fmt.Sprintf("-t nat %s PREROUTING %s -p tcp --dport %d -j REDIRECT --to-ports %d",
|
fmt.Sprintf("-t nat %s PREROUTING %s -p tcp --dport %d -j REDIRECT --to-ports %d",
|
||||||
appendOrDelete(remove), interfaceFlag, sourcePort, destinationPort),
|
appendOrDelete(remove), interfaceFlag, sourcePort, destinationPort),
|
||||||
fmt.Sprintf("%s INPUT %s -p tcp -m tcp --dport %d -j ACCEPT",
|
fmt.Sprintf("%s INPUT %s -p tcp -m tcp --dport %d -j ACCEPT",
|
||||||
@@ -281,7 +244,6 @@ func (c *Config) RedirectPort(ctx context.Context, intf string,
|
|||||||
appendOrDelete(remove), interfaceFlag, destinationPort),
|
appendOrDelete(remove), interfaceFlag, destinationPort),
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
restore(ctx) // just in case
|
|
||||||
errMessage := err.Error()
|
errMessage := err.Error()
|
||||||
if strings.Contains(errMessage, "can't initialize ip6tables table `nat': Table does not exist") {
|
if strings.Contains(errMessage, "can't initialize ip6tables table `nat': Table does not exist") {
|
||||||
if !remove {
|
if !remove {
|
||||||
@@ -295,7 +257,7 @@ func (c *Config) RedirectPort(ctx context.Context, intf string,
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Config) RunUserPostRules(ctx context.Context, filepath string) error {
|
func (c *Config) runUserPostRules(ctx context.Context, filepath string, remove bool) error {
|
||||||
file, err := os.OpenFile(filepath, os.O_RDONLY, 0)
|
file, err := os.OpenFile(filepath, os.O_RDONLY, 0)
|
||||||
if os.IsNotExist(err) {
|
if os.IsNotExist(err) {
|
||||||
return nil
|
return nil
|
||||||
@@ -311,15 +273,16 @@ func (c *Config) RunUserPostRules(ctx context.Context, filepath string) error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
lines := strings.Split(string(b), "\n")
|
lines := strings.Split(string(b), "\n")
|
||||||
|
successfulRules := []string{}
|
||||||
c.iptablesMutex.Lock()
|
defer func() {
|
||||||
defer c.iptablesMutex.Unlock()
|
// transaction-like rollback
|
||||||
|
if err == nil || ctx.Err() != nil {
|
||||||
restore, err := c.saveAndRestore(ctx)
|
return
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
}
|
||||||
|
for _, rule := range successfulRules {
|
||||||
|
_ = c.runIptablesInstruction(ctx, flipRule(rule))
|
||||||
|
}
|
||||||
|
}()
|
||||||
for _, line := range lines {
|
for _, line := range lines {
|
||||||
var ipv4 bool
|
var ipv4 bool
|
||||||
var rule string
|
var rule string
|
||||||
@@ -346,18 +309,23 @@ func (c *Config) RunUserPostRules(ctx context.Context, filepath string) error {
|
|||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if remove {
|
||||||
|
rule = flipRule(rule)
|
||||||
|
}
|
||||||
|
|
||||||
switch {
|
switch {
|
||||||
case ipv4:
|
case ipv4:
|
||||||
err = c.runIptablesInstructionNoSave(ctx, rule)
|
err = c.runIptablesInstruction(ctx, rule)
|
||||||
case c.ip6Tables == "":
|
case c.ip6Tables == "":
|
||||||
err = fmt.Errorf("running user ip6tables rule: %w", ErrNeedIP6Tables)
|
err = fmt.Errorf("running user ip6tables rule: %w", ErrNeedIP6Tables)
|
||||||
default: // ipv6
|
default: // ipv6
|
||||||
err = c.runIP6tablesInstructionNoSave(ctx, rule)
|
err = c.runIP6tablesInstruction(ctx, rule)
|
||||||
}
|
}
|
||||||
if err != nil {
|
if err != nil {
|
||||||
restore(ctx)
|
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
successfulRules = append(successfulRules, rule)
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
@@ -1,87 +0,0 @@
|
|||||||
package iptables
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"fmt"
|
|
||||||
"os/exec"
|
|
||||||
"strings"
|
|
||||||
)
|
|
||||||
|
|
||||||
// SaveAndRestore saves the current iptables and ip6tables rules and
|
|
||||||
// returns a restore function that can be called to restore the saved rules.
|
|
||||||
func (c *Config) SaveAndRestore(ctx context.Context) (restore func(context.Context), err error) {
|
|
||||||
c.iptablesMutex.Lock()
|
|
||||||
defer c.iptablesMutex.Unlock()
|
|
||||||
|
|
||||||
return c.saveAndRestore(ctx)
|
|
||||||
}
|
|
||||||
|
|
||||||
// callers MUST always lock both the [Config] iptablesMutex and the ip6tablesMutex
|
|
||||||
// before calling this function. Note the restore function does not interact with mutexes
|
|
||||||
// so the caller must make sure the mutexes are locked when calling the restore function.
|
|
||||||
func (c *Config) saveAndRestore(ctx context.Context) (restore func(context.Context), err error) {
|
|
||||||
restoreIPv4, err := c.saveAndRestoreIPv4(ctx)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
restoreIPv6, err := c.saveAndRestoreIPv6(ctx)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
restore = func(ctx context.Context) {
|
|
||||||
restoreIPv4(ctx)
|
|
||||||
if restoreIPv6 != nil {
|
|
||||||
restoreIPv6(ctx)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return restore, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Callers of saveAndRestoreIPv4 MUST always lock the [Config] iptablesMutex
|
|
||||||
// before calling this function.
|
|
||||||
func (c *Config) saveAndRestoreIPv4(ctx context.Context) (restore func(context.Context), err error) {
|
|
||||||
cmd := exec.CommandContext(ctx, c.ipTables+"-save") //nolint:gosec
|
|
||||||
data, err := c.runner.Run(cmd)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("saving IPv4 iptables: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
restore = func(ctx context.Context) {
|
|
||||||
cmd := exec.CommandContext(ctx, c.ipTables+"-restore") //nolint:gosec
|
|
||||||
cmd.Stdin = strings.NewReader(data)
|
|
||||||
output, err := c.runner.Run(cmd)
|
|
||||||
if err != nil {
|
|
||||||
c.logger.Warn(fmt.Sprintf("restoring IPv4 iptables failed: %s", makeRestoreErrorMessage(err, output, data)))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return restore, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Callers of saveAndRestoreIPv6 MUST always lock the [Config] ip6tablesMutex
|
|
||||||
// before calling this function.
|
|
||||||
func (c *Config) saveAndRestoreIPv6(ctx context.Context) (restore func(context.Context), err error) {
|
|
||||||
if c.ip6Tables == "" {
|
|
||||||
return nil, nil //nolint:nilnil
|
|
||||||
}
|
|
||||||
|
|
||||||
cmd := exec.CommandContext(ctx, c.ip6Tables+"-save") //nolint:gosec
|
|
||||||
data, err := c.runner.Run(cmd)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("saving IPv6 iptables: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
restore = func(ctx context.Context) {
|
|
||||||
cmd = exec.CommandContext(ctx, c.ip6Tables+"-restore") //nolint:gosec
|
|
||||||
cmd.Stdin = strings.NewReader(data)
|
|
||||||
output, err := c.runner.Run(cmd)
|
|
||||||
if err != nil {
|
|
||||||
c.logger.Warn(fmt.Sprintf("restoring IPv6 iptables failed: %s", makeRestoreErrorMessage(err, output, data)))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return restore, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func makeRestoreErrorMessage(err error, output, data string) string {
|
|
||||||
return fmt.Sprintf("%s: %s: restoring from data:\n%s", err, output, data)
|
|
||||||
}
|
|
||||||
@@ -1,35 +0,0 @@
|
|||||||
package iptables
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"sync"
|
|
||||||
)
|
|
||||||
|
|
||||||
type Config struct {
|
|
||||||
runner CmdRunner
|
|
||||||
logger Logger
|
|
||||||
iptablesMutex sync.Mutex
|
|
||||||
|
|
||||||
// Fixed state
|
|
||||||
ipTables string
|
|
||||||
ip6Tables string
|
|
||||||
}
|
|
||||||
|
|
||||||
func New(ctx context.Context, runner CmdRunner, logger Logger) (*Config, error) {
|
|
||||||
iptables, err := checkIptablesSupport(ctx, runner, "iptables", "iptables-nft", "iptables-legacy")
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
ip6tables, err := findIP6tablesSupported(ctx, runner)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return &Config{
|
|
||||||
runner: runner,
|
|
||||||
logger: logger,
|
|
||||||
ipTables: iptables,
|
|
||||||
ip6Tables: ip6tables,
|
|
||||||
}, nil
|
|
||||||
}
|
|
||||||
@@ -1,12 +0,0 @@
|
|||||||
package iptables
|
|
||||||
|
|
||||||
import "os/exec"
|
|
||||||
|
|
||||||
type CmdRunner interface {
|
|
||||||
Run(cmd *exec.Cmd) (output string, err error)
|
|
||||||
}
|
|
||||||
|
|
||||||
type Logger interface {
|
|
||||||
Debug(s string)
|
|
||||||
Warn(s string)
|
|
||||||
}
|
|
||||||
@@ -1,45 +0,0 @@
|
|||||||
package iptables
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
)
|
|
||||||
|
|
||||||
func (c *Config) runMixedIptablesInstructions(ctx context.Context, instructions []string) error {
|
|
||||||
c.iptablesMutex.Lock()
|
|
||||||
defer c.iptablesMutex.Unlock()
|
|
||||||
|
|
||||||
restore, err := c.saveAndRestore(ctx)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, instruction := range instructions {
|
|
||||||
if err := c.runMixedIptablesInstructionNoSave(ctx, instruction); err != nil {
|
|
||||||
restore(ctx)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *Config) runMixedIptablesInstruction(ctx context.Context, instruction string) error {
|
|
||||||
c.iptablesMutex.Lock()
|
|
||||||
defer c.iptablesMutex.Unlock()
|
|
||||||
|
|
||||||
restore, err := c.saveAndRestore(ctx)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
err = c.runMixedIptablesInstructionNoSave(ctx, instruction)
|
|
||||||
if err != nil {
|
|
||||||
restore(ctx)
|
|
||||||
}
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *Config) runMixedIptablesInstructionNoSave(ctx context.Context, instruction string) error {
|
|
||||||
if err := c.runIptablesInstructionNoSave(ctx, instruction); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
return c.runIP6tablesInstructionNoSave(ctx, instruction)
|
|
||||||
}
|
|
||||||
@@ -1,98 +0,0 @@
|
|||||||
package iptables
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"errors"
|
|
||||||
"fmt"
|
|
||||||
"net/netip"
|
|
||||||
"os"
|
|
||||||
)
|
|
||||||
|
|
||||||
type tcpFlags struct {
|
|
||||||
mask []tcpFlag
|
|
||||||
comparison []tcpFlag
|
|
||||||
}
|
|
||||||
|
|
||||||
type tcpFlag uint8
|
|
||||||
|
|
||||||
const (
|
|
||||||
tcpFlagFIN tcpFlag = 1 << iota
|
|
||||||
tcpFlagSYN
|
|
||||||
tcpFlagRST
|
|
||||||
tcpFlagPSH
|
|
||||||
tcpFlagACK
|
|
||||||
tcpFlagURG
|
|
||||||
tcpFlagECE
|
|
||||||
tcpFlagCWR
|
|
||||||
)
|
|
||||||
|
|
||||||
func (f tcpFlag) String() string {
|
|
||||||
switch f {
|
|
||||||
case tcpFlagFIN:
|
|
||||||
return "FIN"
|
|
||||||
case tcpFlagSYN:
|
|
||||||
return "SYN"
|
|
||||||
case tcpFlagRST:
|
|
||||||
return "RST"
|
|
||||||
case tcpFlagPSH:
|
|
||||||
return "PSH"
|
|
||||||
case tcpFlagACK:
|
|
||||||
return "ACK"
|
|
||||||
case tcpFlagURG:
|
|
||||||
return "URG"
|
|
||||||
case tcpFlagECE:
|
|
||||||
return "ECE"
|
|
||||||
case tcpFlagCWR:
|
|
||||||
return "CWR"
|
|
||||||
default:
|
|
||||||
panic(fmt.Sprintf("%s: %d", errTCPFlagUnknown, f))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
var errTCPFlagUnknown = errors.New("unknown TCP flag")
|
|
||||||
|
|
||||||
func parseTCPFlag(s string) (tcpFlag, error) {
|
|
||||||
allFlags := []tcpFlag{
|
|
||||||
tcpFlagFIN, tcpFlagSYN, tcpFlagRST, tcpFlagPSH,
|
|
||||||
tcpFlagACK, tcpFlagURG, tcpFlagECE, tcpFlagCWR,
|
|
||||||
}
|
|
||||||
for _, flag := range allFlags {
|
|
||||||
if s == fmt.Sprintf("%#02x", uint8(flag)) || s == flag.String() {
|
|
||||||
return flag, nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return 0, fmt.Errorf("%w: %s", errTCPFlagUnknown, s)
|
|
||||||
}
|
|
||||||
|
|
||||||
var ErrMarkMatchModuleMissing = errors.New("kernel is missing the mark module libxt_mark.so")
|
|
||||||
|
|
||||||
// TempDropOutputTCPRST temporarily drops outgoing TCP RST packets to the specified address and port,
|
|
||||||
// for any TCP packets not marked with the excludeMark given.
|
|
||||||
// This is necessary for TCP path MTU discovery to work, as the kernel will try to terminate the connection
|
|
||||||
// by sending a TCP RST packet, although we want to handle the connection manually.
|
|
||||||
func (c *Config) TempDropOutputTCPRST(ctx context.Context,
|
|
||||||
src, dst netip.AddrPort, excludeMark int) (
|
|
||||||
revert func(ctx context.Context) error, err error,
|
|
||||||
) {
|
|
||||||
_, err = os.Stat("/usr/lib/xtables/libxt_mark.so")
|
|
||||||
if err != nil && errors.Is(err, os.ErrNotExist) {
|
|
||||||
return nil, fmt.Errorf("%w", ErrMarkMatchModuleMissing)
|
|
||||||
}
|
|
||||||
|
|
||||||
const template = "%s OUTPUT -p tcp -s %s --sport %d -d %s --dport %d " +
|
|
||||||
"--tcp-flags RST RST -m mark ! --mark %d -j DROP" //nolint:dupword
|
|
||||||
instruction := fmt.Sprintf(template, "--append", src.Addr(), src.Port(), dst.Addr(), dst.Port(), excludeMark)
|
|
||||||
revertInstruction := fmt.Sprintf(template, "--delete", src.Addr(), src.Port(), dst.Addr(), dst.Port(), excludeMark)
|
|
||||||
run := c.runIptablesInstruction
|
|
||||||
if dst.Addr().Is6() {
|
|
||||||
run = c.runIP6tablesInstruction
|
|
||||||
}
|
|
||||||
revert = func(ctx context.Context) error {
|
|
||||||
return run(ctx, revertInstruction)
|
|
||||||
}
|
|
||||||
err = run(ctx, instruction)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("running instruction: %w", err)
|
|
||||||
}
|
|
||||||
return revert, nil
|
|
||||||
}
|
|
||||||
@@ -0,0 +1,21 @@
|
|||||||
|
package firewall
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
)
|
||||||
|
|
||||||
|
func (c *Config) runMixedIptablesInstructions(ctx context.Context, instructions []string) error {
|
||||||
|
for _, instruction := range instructions {
|
||||||
|
if err := c.runMixedIptablesInstruction(ctx, instruction); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Config) runMixedIptablesInstruction(ctx context.Context, instruction string) error {
|
||||||
|
if err := c.runIptablesInstruction(ctx, instruction); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return c.runIP6tablesInstruction(ctx, instruction)
|
||||||
|
}
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
package iptables
|
package firewall
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"errors"
|
"errors"
|
||||||
@@ -26,18 +26,10 @@ type chainRule struct {
|
|||||||
inputInterface string // input interface, for example "tun0" or "*""
|
inputInterface string // input interface, for example "tun0" or "*""
|
||||||
outputInterface string // output interface, for example "eth0" or "*""
|
outputInterface string // output interface, for example "eth0" or "*""
|
||||||
source netip.Prefix // source IP CIDR, for example 0.0.0.0/0. Must be valid.
|
source netip.Prefix // source IP CIDR, for example 0.0.0.0/0. Must be valid.
|
||||||
sourcePort uint16 // Not specified if set to zero.
|
|
||||||
destination netip.Prefix // destination IP CIDR, for example 0.0.0.0/0. Must be valid.
|
destination netip.Prefix // destination IP CIDR, for example 0.0.0.0/0. Must be valid.
|
||||||
destinationPort uint16 // Not specified if set to zero.
|
destinationPort uint16 // Not specified if set to zero.
|
||||||
redirPorts []uint16 // Not specified if empty.
|
redirPorts []uint16 // Not specified if empty.
|
||||||
ctstate []string // for example ["RELATED","ESTABLISHED"]. Can be empty.
|
ctstate []string // for example ["RELATED","ESTABLISHED"]. Can be empty.
|
||||||
tcpFlags tcpFlags
|
|
||||||
mark mark
|
|
||||||
}
|
|
||||||
|
|
||||||
type mark struct {
|
|
||||||
invert bool
|
|
||||||
value uint
|
|
||||||
}
|
}
|
||||||
|
|
||||||
var ErrChainListMalformed = errors.New("iptables chain list output is malformed")
|
var ErrChainListMalformed = errors.New("iptables chain list output is malformed")
|
||||||
@@ -249,23 +241,19 @@ func parseChainRuleField(fieldIndex int, field string, rule *chainRule) (err err
|
|||||||
}
|
}
|
||||||
|
|
||||||
func parseChainRuleOptionalFields(optionalFields []string, rule *chainRule) (err error) {
|
func parseChainRuleOptionalFields(optionalFields []string, rule *chainRule) (err error) {
|
||||||
i := 0
|
for i := 0; i < len(optionalFields); i++ {
|
||||||
for i < len(optionalFields) {
|
key := optionalFields[i]
|
||||||
switch optionalFields[i] {
|
switch key {
|
||||||
case "udp":
|
case "tcp", "udp":
|
||||||
i++
|
i++
|
||||||
consumed, err := parseUDPOptional(optionalFields[i:], rule)
|
value := optionalFields[i]
|
||||||
|
value = strings.TrimPrefix(value, "dpt:")
|
||||||
|
const base, bitLength = 10, 16
|
||||||
|
destinationPort, err := strconv.ParseUint(value, base, bitLength)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("parsing UDP optional fields: %w", err)
|
return fmt.Errorf("parsing destination port %q: %w", value, err)
|
||||||
}
|
}
|
||||||
i += consumed
|
rule.destinationPort = uint16(destinationPort)
|
||||||
case "tcp":
|
|
||||||
i++
|
|
||||||
consumed, err := parseTCPOptional(optionalFields[i:], rule)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("parsing TCP optional fields: %w", err)
|
|
||||||
}
|
|
||||||
i += consumed
|
|
||||||
case "redir":
|
case "redir":
|
||||||
i++
|
i++
|
||||||
switch optionalFields[i] {
|
switch optionalFields[i] {
|
||||||
@@ -276,136 +264,20 @@ func parseChainRuleOptionalFields(optionalFields []string, rule *chainRule) (err
|
|||||||
return fmt.Errorf("parsing redirection ports: %w", err)
|
return fmt.Errorf("parsing redirection ports: %w", err)
|
||||||
}
|
}
|
||||||
rule.redirPorts = ports
|
rule.redirPorts = ports
|
||||||
i++
|
|
||||||
default:
|
|
||||||
return fmt.Errorf("%w: unexpected %q after redir",
|
|
||||||
ErrChainRuleMalformed, optionalFields[1])
|
|
||||||
}
|
|
||||||
case "ctstate":
|
|
||||||
i++
|
|
||||||
rule.ctstate = strings.Split(optionalFields[i], ",")
|
|
||||||
i++
|
|
||||||
case "mark":
|
|
||||||
i++
|
|
||||||
mark, consumed, err := parseMark(optionalFields[i:])
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("parsing mark: %w", err)
|
|
||||||
}
|
|
||||||
rule.mark = mark
|
|
||||||
i += consumed
|
|
||||||
default:
|
default:
|
||||||
return fmt.Errorf("%w: unexpected optional field: %s",
|
return fmt.Errorf("%w: unexpected optional field: %s",
|
||||||
ErrChainRuleMalformed, optionalFields[i])
|
ErrChainRuleMalformed, optionalFields[i])
|
||||||
}
|
}
|
||||||
|
case "ctstate":
|
||||||
|
i++
|
||||||
|
rule.ctstate = strings.Split(optionalFields[i], ",")
|
||||||
|
default:
|
||||||
|
return fmt.Errorf("%w: unexpected optional field: %s", ErrChainRuleMalformed, key)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
var errUDPOptionalUnknown = errors.New("unknown UDP optional field")
|
|
||||||
|
|
||||||
func parseUDPOptional(optionalFields []string, rule *chainRule) (consumed int, err error) {
|
|
||||||
for _, value := range optionalFields {
|
|
||||||
if !strings.ContainsRune(value, ':') {
|
|
||||||
// no longer a UDP-associated option
|
|
||||||
return consumed, nil
|
|
||||||
}
|
|
||||||
switch {
|
|
||||||
case strings.HasPrefix(value, "dpt:"):
|
|
||||||
rule.destinationPort, err = parseDestinationPort(value)
|
|
||||||
if err != nil {
|
|
||||||
return 0, fmt.Errorf("parsing destination port: %w", err)
|
|
||||||
}
|
|
||||||
consumed++
|
|
||||||
case strings.HasPrefix(value, "spt:"):
|
|
||||||
rule.sourcePort, err = parseSourcePort(value)
|
|
||||||
if err != nil {
|
|
||||||
return 0, fmt.Errorf("parsing source port: %w", err)
|
|
||||||
}
|
|
||||||
consumed++
|
|
||||||
default:
|
|
||||||
return 0, fmt.Errorf("%w: %s", errUDPOptionalUnknown, value)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return consumed, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
var errTCPOptionalUnknown = errors.New("unknown TCP optional field")
|
|
||||||
|
|
||||||
func parseTCPOptional(optionalFields []string, rule *chainRule) (consumed int, err error) {
|
|
||||||
for _, value := range optionalFields {
|
|
||||||
if !strings.ContainsRune(value, ':') {
|
|
||||||
// no longer a TCP-associated option
|
|
||||||
return consumed, nil
|
|
||||||
}
|
|
||||||
switch {
|
|
||||||
case strings.HasPrefix(value, "dpt:"):
|
|
||||||
rule.destinationPort, err = parseDestinationPort(value)
|
|
||||||
if err != nil {
|
|
||||||
return 0, fmt.Errorf("parsing destination port: %w", err)
|
|
||||||
}
|
|
||||||
consumed++
|
|
||||||
case strings.HasPrefix(value, "spt:"):
|
|
||||||
rule.sourcePort, err = parseSourcePort(value)
|
|
||||||
if err != nil {
|
|
||||||
return 0, fmt.Errorf("parsing source port: %w", err)
|
|
||||||
}
|
|
||||||
consumed++
|
|
||||||
case strings.HasPrefix(value, "flags:"):
|
|
||||||
rule.tcpFlags, err = parseTCPFlags(value)
|
|
||||||
if err != nil {
|
|
||||||
return 0, fmt.Errorf("parsing TCP flags: %w", err)
|
|
||||||
}
|
|
||||||
consumed++
|
|
||||||
default:
|
|
||||||
return 0, fmt.Errorf("%w: %s", errTCPOptionalUnknown, value)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return consumed, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func parseDestinationPort(value string) (port uint16, err error) {
|
|
||||||
value = strings.TrimPrefix(value, "dpt:")
|
|
||||||
return parsePort(value)
|
|
||||||
}
|
|
||||||
|
|
||||||
func parseSourcePort(value string) (port uint16, err error) {
|
|
||||||
value = strings.TrimPrefix(value, "spt:")
|
|
||||||
return parsePort(value)
|
|
||||||
}
|
|
||||||
|
|
||||||
var errTCPFlagsMalformed = errors.New("TCP flags are malformed")
|
|
||||||
|
|
||||||
func parseTCPFlags(value string) (tcpFlags, error) {
|
|
||||||
value = strings.TrimPrefix(value, "flags:")
|
|
||||||
fields := strings.Split(value, "/")
|
|
||||||
const expectedFields = 2
|
|
||||||
if len(fields) != expectedFields {
|
|
||||||
return tcpFlags{}, fmt.Errorf("%w: expected format 'flags:<mask>/<comparison>' in %q",
|
|
||||||
errTCPFlagsMalformed, value)
|
|
||||||
}
|
|
||||||
maskFlags := strings.Split(fields[0], ",")
|
|
||||||
mask := make([]tcpFlag, len(maskFlags))
|
|
||||||
var err error
|
|
||||||
for i, maskFlag := range maskFlags {
|
|
||||||
mask[i], err = parseTCPFlag(maskFlag)
|
|
||||||
if err != nil {
|
|
||||||
return tcpFlags{}, fmt.Errorf("parsing TCP mask flags: %w", err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
comparisonFlags := strings.Split(fields[1], ",")
|
|
||||||
comparison := make([]tcpFlag, len(comparisonFlags))
|
|
||||||
for i, comparisonFlag := range comparisonFlags {
|
|
||||||
comparison[i], err = parseTCPFlag(comparisonFlag)
|
|
||||||
if err != nil {
|
|
||||||
return tcpFlags{}, fmt.Errorf("parsing TCP comparison flags: %w", err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return tcpFlags{
|
|
||||||
mask: mask,
|
|
||||||
comparison: comparison,
|
|
||||||
}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func parsePortsCSV(s string) (ports []uint16, err error) {
|
func parsePortsCSV(s string) (ports []uint16, err error) {
|
||||||
if s == "" {
|
if s == "" {
|
||||||
return nil, nil
|
return nil, nil
|
||||||
@@ -414,40 +286,16 @@ func parsePortsCSV(s string) (ports []uint16, err error) {
|
|||||||
fields := strings.Split(s, ",")
|
fields := strings.Split(s, ",")
|
||||||
ports = make([]uint16, len(fields))
|
ports = make([]uint16, len(fields))
|
||||||
for i, field := range fields {
|
for i, field := range fields {
|
||||||
ports[i], err = parsePort(field)
|
const base, bitLength = 10, 16
|
||||||
|
port, err := strconv.ParseUint(field, base, bitLength)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, fmt.Errorf("parsing port %q: %w", field, err)
|
||||||
}
|
}
|
||||||
|
ports[i] = uint16(port)
|
||||||
}
|
}
|
||||||
return ports, nil
|
return ports, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
var errMarkValueMalformed = errors.New("mark value is malformed")
|
|
||||||
|
|
||||||
func parseMark(optionalFields []string) (m mark, consumed int, err error) {
|
|
||||||
switch optionalFields[consumed] {
|
|
||||||
case "match":
|
|
||||||
consumed++
|
|
||||||
if optionalFields[consumed] == "!" {
|
|
||||||
m.invert = true
|
|
||||||
consumed++
|
|
||||||
}
|
|
||||||
|
|
||||||
const base = 0 // auto-detect
|
|
||||||
const bits = 32
|
|
||||||
value, err := strconv.ParseUint(optionalFields[consumed], base, bits)
|
|
||||||
if err != nil {
|
|
||||||
return mark{}, 0, fmt.Errorf("%w: %s", errMarkValueMalformed, optionalFields[consumed])
|
|
||||||
}
|
|
||||||
m.value = uint(value)
|
|
||||||
consumed++
|
|
||||||
default:
|
|
||||||
return mark{}, 0, fmt.Errorf("%w: unexpected mark mode field: %s",
|
|
||||||
ErrChainRuleMalformed, optionalFields[consumed])
|
|
||||||
}
|
|
||||||
return m, consumed, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
var ErrLineNumberIsZero = errors.New("line number is zero")
|
var ErrLineNumberIsZero = errors.New("line number is zero")
|
||||||
|
|
||||||
func parseLineNumber(s string) (n uint16, err error) {
|
func parseLineNumber(s string) (n uint16, err error) {
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
package iptables
|
package firewall
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"net/netip"
|
"net/netip"
|
||||||
+1
-1
@@ -1,3 +1,3 @@
|
|||||||
package iptables
|
package firewall
|
||||||
|
|
||||||
//go:generate mockgen -destination=mocks_test.go -package $GOPACKAGE . CmdRunner,Logger
|
//go:generate mockgen -destination=mocks_test.go -package $GOPACKAGE . CmdRunner,Logger
|
||||||
@@ -1,8 +1,8 @@
|
|||||||
// Code generated by MockGen. DO NOT EDIT.
|
// Code generated by MockGen. DO NOT EDIT.
|
||||||
// Source: github.com/qdm12/gluetun/internal/firewall/iptables (interfaces: CmdRunner,Logger)
|
// Source: github.com/qdm12/gluetun/internal/firewall (interfaces: CmdRunner,Logger)
|
||||||
|
|
||||||
// Package iptables is a generated GoMock package.
|
// Package firewall is a generated GoMock package.
|
||||||
package iptables
|
package firewall
|
||||||
|
|
||||||
import (
|
import (
|
||||||
exec "os/exec"
|
exec "os/exec"
|
||||||
@@ -84,6 +84,30 @@ func (mr *MockLoggerMockRecorder) Debug(arg0 interface{}) *gomock.Call {
|
|||||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Debug", reflect.TypeOf((*MockLogger)(nil).Debug), arg0)
|
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Debug", reflect.TypeOf((*MockLogger)(nil).Debug), arg0)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Error mocks base method.
|
||||||
|
func (m *MockLogger) Error(arg0 string) {
|
||||||
|
m.ctrl.T.Helper()
|
||||||
|
m.ctrl.Call(m, "Error", arg0)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Error indicates an expected call of Error.
|
||||||
|
func (mr *MockLoggerMockRecorder) Error(arg0 interface{}) *gomock.Call {
|
||||||
|
mr.mock.ctrl.T.Helper()
|
||||||
|
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Error", reflect.TypeOf((*MockLogger)(nil).Error), arg0)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Info mocks base method.
|
||||||
|
func (m *MockLogger) Info(arg0 string) {
|
||||||
|
m.ctrl.T.Helper()
|
||||||
|
m.ctrl.Call(m, "Info", arg0)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Info indicates an expected call of Info.
|
||||||
|
func (mr *MockLoggerMockRecorder) Info(arg0 interface{}) *gomock.Call {
|
||||||
|
mr.mock.ctrl.T.Helper()
|
||||||
|
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Info", reflect.TypeOf((*MockLogger)(nil).Info), arg0)
|
||||||
|
}
|
||||||
|
|
||||||
// Warn mocks base method.
|
// Warn mocks base method.
|
||||||
func (m *MockLogger) Warn(arg0 string) {
|
func (m *MockLogger) Warn(arg0 string) {
|
||||||
m.ctrl.T.Helper()
|
m.ctrl.T.Helper()
|
||||||
@@ -48,7 +48,7 @@ func (c *Config) removeOutboundSubnets(ctx context.Context, subnets []netip.Pref
|
|||||||
}
|
}
|
||||||
|
|
||||||
firewallUpdated = true
|
firewallUpdated = true
|
||||||
err := c.impl.AcceptOutputFromIPToSubnet(ctx, defaultRoute.NetInterface,
|
err := c.acceptOutputFromIPToSubnet(ctx, defaultRoute.NetInterface,
|
||||||
defaultRoute.AssignedIP, subNet, remove)
|
defaultRoute.AssignedIP, subNet, remove)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
c.logger.Error("cannot remove outdated outbound subnet: " + err.Error())
|
c.logger.Error("cannot remove outdated outbound subnet: " + err.Error())
|
||||||
@@ -77,7 +77,7 @@ func (c *Config) addOutboundSubnets(ctx context.Context, subnets []netip.Prefix)
|
|||||||
}
|
}
|
||||||
|
|
||||||
firewallUpdated = true
|
firewallUpdated = true
|
||||||
err := c.impl.AcceptOutputFromIPToSubnet(ctx, defaultRoute.NetInterface,
|
err := c.acceptOutputFromIPToSubnet(ctx, defaultRoute.NetInterface,
|
||||||
defaultRoute.AssignedIP, subnet, remove)
|
defaultRoute.AssignedIP, subnet, remove)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
package iptables
|
package firewall
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"errors"
|
"errors"
|
||||||
@@ -18,13 +18,10 @@ type iptablesInstruction struct {
|
|||||||
inputInterface string // for example "tun0" or "" for any interface.
|
inputInterface string // for example "tun0" or "" for any interface.
|
||||||
outputInterface string // for example "tun0" or "" for any interface.
|
outputInterface string // for example "tun0" or "" for any interface.
|
||||||
source netip.Prefix // if not valid, then it is unspecified.
|
source netip.Prefix // if not valid, then it is unspecified.
|
||||||
sourcePort uint16 // if zero, there is no source port
|
|
||||||
destination netip.Prefix // if not valid, then it is unspecified.
|
destination netip.Prefix // if not valid, then it is unspecified.
|
||||||
destinationPort uint16 // if zero, there is no destination port
|
destinationPort uint16 // if zero, there is no destination port
|
||||||
toPorts []uint16 // if empty, there is no redirection
|
toPorts []uint16 // if empty, there is no redirection
|
||||||
ctstate []string // if empty, there is no ctstate
|
ctstate []string // if empty, there is no ctstate
|
||||||
tcpFlags tcpFlags
|
|
||||||
mark mark
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (i *iptablesInstruction) setDefaults() {
|
func (i *iptablesInstruction) setDefaults() {
|
||||||
@@ -46,8 +43,6 @@ func (i *iptablesInstruction) equalToRule(table, chain string, rule chainRule) (
|
|||||||
return false
|
return false
|
||||||
case i.destinationPort != rule.destinationPort:
|
case i.destinationPort != rule.destinationPort:
|
||||||
return false
|
return false
|
||||||
case i.sourcePort != rule.sourcePort:
|
|
||||||
return false
|
|
||||||
case !slices.Equal(i.toPorts, rule.redirPorts):
|
case !slices.Equal(i.toPorts, rule.redirPorts):
|
||||||
return false
|
return false
|
||||||
case !slices.Equal(i.ctstate, rule.ctstate):
|
case !slices.Equal(i.ctstate, rule.ctstate):
|
||||||
@@ -60,11 +55,6 @@ func (i *iptablesInstruction) equalToRule(table, chain string, rule chainRule) (
|
|||||||
return false
|
return false
|
||||||
case !ipPrefixesEqual(i.destination, rule.destination):
|
case !ipPrefixesEqual(i.destination, rule.destination):
|
||||||
return false
|
return false
|
||||||
case !slices.Equal(i.tcpFlags.mask, rule.tcpFlags.mask) ||
|
|
||||||
!slices.Equal(i.tcpFlags.comparison, rule.tcpFlags.comparison):
|
|
||||||
return false
|
|
||||||
case i.mark != rule.mark:
|
|
||||||
return false
|
|
||||||
default:
|
default:
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
@@ -87,29 +77,26 @@ func parseIptablesInstruction(s string) (instruction iptablesInstruction, err er
|
|||||||
return iptablesInstruction{}, fmt.Errorf("%w: empty instruction", ErrIptablesCommandMalformed)
|
return iptablesInstruction{}, fmt.Errorf("%w: empty instruction", ErrIptablesCommandMalformed)
|
||||||
}
|
}
|
||||||
fields := strings.Fields(s)
|
fields := strings.Fields(s)
|
||||||
|
if len(fields)%2 != 0 {
|
||||||
|
return iptablesInstruction{}, fmt.Errorf("%w: fields count %d is not even: %q",
|
||||||
|
ErrIptablesCommandMalformed, len(fields), s)
|
||||||
|
}
|
||||||
|
|
||||||
i := 0
|
for i := 0; i < len(fields); i += 2 {
|
||||||
for i < len(fields) {
|
key := fields[i]
|
||||||
consumed, err := parseInstructionFlag(fields[i:], &instruction)
|
value := fields[i+1]
|
||||||
|
err = parseInstructionFlag(key, value, &instruction)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return iptablesInstruction{}, fmt.Errorf("parsing %q: %w", s, err)
|
return iptablesInstruction{}, fmt.Errorf("parsing %q: %w", s, err)
|
||||||
}
|
}
|
||||||
i += consumed
|
|
||||||
}
|
}
|
||||||
|
|
||||||
instruction.setDefaults()
|
instruction.setDefaults()
|
||||||
return instruction, nil
|
return instruction, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func parseInstructionFlag(fields []string, instruction *iptablesInstruction) (consumed int, err error) {
|
func parseInstructionFlag(key, value string, instruction *iptablesInstruction) (err error) {
|
||||||
consumed, err = preCheckInstructionFields(fields)
|
switch key {
|
||||||
if err != nil {
|
|
||||||
return 0, err
|
|
||||||
}
|
|
||||||
flag := fields[0]
|
|
||||||
value := fields[1]
|
|
||||||
|
|
||||||
switch flag {
|
|
||||||
case "-t", "--table":
|
case "-t", "--table":
|
||||||
instruction.table = value
|
instruction.table = value
|
||||||
case "-D", "--delete":
|
case "-D", "--delete":
|
||||||
@@ -122,19 +109,7 @@ func parseInstructionFlag(fields []string, instruction *iptablesInstruction) (co
|
|||||||
instruction.target = value
|
instruction.target = value
|
||||||
case "-p", "--protocol":
|
case "-p", "--protocol":
|
||||||
instruction.protocol = value
|
instruction.protocol = value
|
||||||
case "-m", "--match":
|
case "-m", "--match": // ignore match
|
||||||
consumed, err = parseMatchModule(fields, instruction)
|
|
||||||
if err != nil {
|
|
||||||
return 0, fmt.Errorf("parsing match module: %w", err)
|
|
||||||
}
|
|
||||||
case "--mark":
|
|
||||||
const base = 0 // auto-detect
|
|
||||||
const bits = 32
|
|
||||||
value, err := strconv.ParseUint(value, base, bits)
|
|
||||||
if err != nil {
|
|
||||||
return 0, fmt.Errorf("parsing mark value %q: %w", fields[2], err)
|
|
||||||
}
|
|
||||||
instruction.mark.value = uint(value)
|
|
||||||
case "-i", "--in-interface":
|
case "-i", "--in-interface":
|
||||||
instruction.inputInterface = value
|
instruction.inputInterface = value
|
||||||
case "-o", "--out-interface":
|
case "-o", "--out-interface":
|
||||||
@@ -142,61 +117,37 @@ func parseInstructionFlag(fields []string, instruction *iptablesInstruction) (co
|
|||||||
case "-s", "--source":
|
case "-s", "--source":
|
||||||
instruction.source, err = parseIPPrefix(value)
|
instruction.source, err = parseIPPrefix(value)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return 0, fmt.Errorf("parsing source IP CIDR: %w", err)
|
return fmt.Errorf("parsing source IP CIDR: %w", err)
|
||||||
}
|
|
||||||
case "--sport":
|
|
||||||
instruction.sourcePort, err = parsePort(value)
|
|
||||||
if err != nil {
|
|
||||||
return 0, fmt.Errorf("parsing source port: %w", err)
|
|
||||||
}
|
}
|
||||||
case "-d", "--destination":
|
case "-d", "--destination":
|
||||||
instruction.destination, err = parseIPPrefix(value)
|
instruction.destination, err = parseIPPrefix(value)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return 0, fmt.Errorf("parsing destination IP CIDR: %w", err)
|
return fmt.Errorf("parsing destination IP CIDR: %w", err)
|
||||||
}
|
}
|
||||||
case "--dport":
|
case "--dport":
|
||||||
instruction.destinationPort, err = parsePort(value)
|
const base, bitLength = 10, 16
|
||||||
|
destinationPort, err := strconv.ParseUint(value, base, bitLength)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return 0, fmt.Errorf("parsing destination port: %w", err)
|
return fmt.Errorf("parsing destination port: %w", err)
|
||||||
}
|
}
|
||||||
|
instruction.destinationPort = uint16(destinationPort)
|
||||||
case "--ctstate":
|
case "--ctstate":
|
||||||
instruction.ctstate = strings.Split(value, ",")
|
instruction.ctstate = strings.Split(value, ",")
|
||||||
case "--to-ports":
|
case "--to-ports":
|
||||||
instruction.toPorts, err = parseToPorts(value)
|
portStrings := strings.Split(value, ",")
|
||||||
|
instruction.toPorts = make([]uint16, len(portStrings))
|
||||||
|
for i, portString := range portStrings {
|
||||||
|
const base, bitLength = 10, 16
|
||||||
|
port, err := strconv.ParseUint(portString, base, bitLength)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return 0, fmt.Errorf("parsing port redirection: %w", err)
|
return fmt.Errorf("parsing port redirection: %w", err)
|
||||||
}
|
}
|
||||||
case "--tcp-flags":
|
instruction.toPorts[i] = uint16(port)
|
||||||
mask, comparison := value, fields[2]
|
|
||||||
instruction.tcpFlags, err = parseTCPFlags(mask + "/" + comparison)
|
|
||||||
if err != nil {
|
|
||||||
return 0, fmt.Errorf("parsing TCP flags: %w", err)
|
|
||||||
}
|
}
|
||||||
default:
|
default:
|
||||||
return 0, fmt.Errorf("%w: unknown key %q", ErrIptablesCommandMalformed, flag)
|
return fmt.Errorf("%w: unknown key %q", ErrIptablesCommandMalformed, key)
|
||||||
}
|
|
||||||
return consumed, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func preCheckInstructionFields(fields []string) (consumed int, err error) {
|
|
||||||
flag := fields[0]
|
|
||||||
// All flags use one value after the flag, except the following:
|
|
||||||
switch flag {
|
|
||||||
case "--tcp-flags": // -m can have 1 or 2 values
|
|
||||||
const expected = 3
|
|
||||||
if len(fields) < expected {
|
|
||||||
return 0, fmt.Errorf("%w: flag %q requires at least 2 values, but got %s",
|
|
||||||
ErrIptablesCommandMalformed, flag, strings.Join(fields, " "))
|
|
||||||
}
|
|
||||||
return expected, nil
|
|
||||||
default:
|
|
||||||
const expected = 2
|
|
||||||
if len(fields) < expected {
|
|
||||||
return 0, fmt.Errorf("%w: flag %q requires a value, but got none",
|
|
||||||
ErrIptablesCommandMalformed, flag)
|
|
||||||
}
|
|
||||||
return expected, nil
|
|
||||||
}
|
}
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func parseIPPrefix(value string) (prefix netip.Prefix, err error) {
|
func parseIPPrefix(value string) (prefix netip.Prefix, err error) {
|
||||||
@@ -211,52 +162,3 @@ func parseIPPrefix(value string) (prefix netip.Prefix, err error) {
|
|||||||
}
|
}
|
||||||
return netip.PrefixFrom(ip, ip.BitLen()), nil
|
return netip.PrefixFrom(ip, ip.BitLen()), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func parsePort(value string) (port uint16, err error) {
|
|
||||||
const base, bitLength = 10, 16
|
|
||||||
portValue, err := strconv.ParseUint(value, base, bitLength)
|
|
||||||
if err != nil {
|
|
||||||
return 0, err
|
|
||||||
}
|
|
||||||
return uint16(portValue), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func parseMatchModule(fields []string, instruction *iptablesInstruction) (
|
|
||||||
consumed int, err error,
|
|
||||||
) {
|
|
||||||
_ = fields[consumed] // -m or --match flag already detected
|
|
||||||
consumed++
|
|
||||||
switch fields[consumed] {
|
|
||||||
case "tcp", "udp":
|
|
||||||
consumed++
|
|
||||||
// for now ignore the protocol match since it's auto-loaded
|
|
||||||
// when parsing the -p/--protocol flag, and we don't need to
|
|
||||||
// parse it twice.
|
|
||||||
case "mark":
|
|
||||||
consumed++
|
|
||||||
switch fields[consumed] {
|
|
||||||
case "!":
|
|
||||||
consumed++
|
|
||||||
instruction.mark.invert = true
|
|
||||||
default:
|
|
||||||
return consumed, fmt.Errorf("%w: unsupported match mark with value: %s",
|
|
||||||
ErrIptablesCommandMalformed, fields[2])
|
|
||||||
}
|
|
||||||
default:
|
|
||||||
return 0, fmt.Errorf("%w: unknown match value: %s",
|
|
||||||
ErrIptablesCommandMalformed, fields[consumed])
|
|
||||||
}
|
|
||||||
return consumed, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func parseToPorts(value string) (toPorts []uint16, err error) {
|
|
||||||
portStrings := strings.Split(value, ",")
|
|
||||||
toPorts = make([]uint16, len(portStrings))
|
|
||||||
for i, portString := range portStrings {
|
|
||||||
toPorts[i], err = parsePort(portString)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return toPorts, nil
|
|
||||||
}
|
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
package iptables
|
package firewall
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"net/netip"
|
"net/netip"
|
||||||
@@ -23,7 +23,7 @@ func Test_parseIptablesInstruction(t *testing.T) {
|
|||||||
"uneven_fields": {
|
"uneven_fields": {
|
||||||
s: "-A",
|
s: "-A",
|
||||||
errWrapped: ErrIptablesCommandMalformed,
|
errWrapped: ErrIptablesCommandMalformed,
|
||||||
errMessage: "parsing \"-A\": iptables command is malformed: flag \"-A\" requires a value, but got none",
|
errMessage: "iptables command is malformed: fields count 1 is not even: \"-A\"",
|
||||||
},
|
},
|
||||||
"unknown_key": {
|
"unknown_key": {
|
||||||
s: "-x something",
|
s: "-x something",
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user