mirror of
https://github.com/qdm12/gluetun.git
synced 2026-07-20 09:26:25 +02:00
Compare commits
34 Commits
09c47838f1
..
master
| Author | SHA1 | Date | |
|---|---|---|---|
| 78f6076c83 | |||
| 03d0838356 | |||
| e7b304d0b4 | |||
| 93cc5a4b2c | |||
| a17591dcdb | |||
| c31c566282 | |||
| 496458ca4e | |||
| 52a41cb891 | |||
| 6c76273ef6 | |||
| 366062dc12 | |||
| 8abb05567c | |||
| a53a0267e4 | |||
| 4e986c8af7 | |||
| 6d84462f00 | |||
| acab89b91a | |||
| 48c1f2bf6a | |||
| c599e7fd2c | |||
| ff6e45fae0 | |||
| 17f24343d6 | |||
| ebbc630b31 | |||
| 39ac8b3432 | |||
| f65ee3dcb1 | |||
| 7e8d81b161 | |||
| 21e868c89c | |||
| 2e20e2df66 | |||
| 6f5f518d1d | |||
| 1998e0d04f | |||
| 14f30bc641 | |||
| f89e55b8ff | |||
| 7ad6af0947 | |||
| d3e089ccd7 | |||
| 3eebbf65a8 | |||
| a1ef736b0f | |||
| 46edfe49e3 |
@@ -1,3 +1,3 @@
|
||||
FROM ghcr.io/qdm12/godevcontainer:v0.21-alpine
|
||||
FROM ghcr.io/qdm12/godevcontainer:v0.22-alpine
|
||||
RUN apk add wireguard-tools htop openssl tcpdump iptables
|
||||
RUN apk add nodejs npm && npm install markdownlint-cli2 --global && apk del npm
|
||||
|
||||
@@ -56,7 +56,6 @@ body:
|
||||
- IVPN
|
||||
- Mullvad
|
||||
- NordVPN
|
||||
- OVPN
|
||||
- Privado
|
||||
- Private Internet Access
|
||||
- PrivateVPN
|
||||
|
||||
+31
-5
@@ -4,12 +4,38 @@ updates:
|
||||
- package-ecosystem: "github-actions"
|
||||
directory: "/"
|
||||
schedule:
|
||||
interval: "daily"
|
||||
interval: "weekly"
|
||||
- package-ecosystem: docker
|
||||
directory: /
|
||||
schedule:
|
||||
interval: "daily"
|
||||
- package-ecosystem: gomod
|
||||
directory: /
|
||||
interval: "weekly"
|
||||
- # Servers data dependency that should be updated as soon as
|
||||
# possible when a new version is released, to have the latest
|
||||
# servers available
|
||||
package-ecosystem: "gomod"
|
||||
directory: "/"
|
||||
schedule:
|
||||
interval: "daily"
|
||||
interval: "weekly"
|
||||
ignore:
|
||||
- # In particular avoid amneziawg-go which have v1.x.y versions available
|
||||
# on the Go modules proxy, but are not in the Github repository tags
|
||||
# and are not the latest releases either. Most likely a mistake from the
|
||||
# maintainers, which is persisted on the Go proxy.
|
||||
dependency-name: "github.com/amnezia-vpn/amneziawg-go"
|
||||
versions: ["1.x"]
|
||||
groups:
|
||||
low-importance:
|
||||
patterns:
|
||||
- "github.com/breml/rootcerts"
|
||||
- "github.com/fatih/color"
|
||||
- "github.com/golang/mock"
|
||||
- "github.com/klauspost/compress"
|
||||
- "github.com/klauspost/pgzip"
|
||||
- "github.com/pelletier/go-toml/v2"
|
||||
- "github.com/qdm12/goshutdown"
|
||||
- "github.com/qdm12/gosplash"
|
||||
- "github.com/qdm12/gotree"
|
||||
- "github.com/qdm12/log"
|
||||
- "github.com/stretchr/testify"
|
||||
- "github.com/ulikunitz/xz"
|
||||
- "gopkg.in/ini.v1"
|
||||
|
||||
@@ -64,8 +64,6 @@
|
||||
color: "cfe8d4"
|
||||
- name: "☁️ NordVPN"
|
||||
color: "cfe8d4"
|
||||
- name: "☁️ OVPN"
|
||||
color: "cfe8d4"
|
||||
- name: "☁️ Perfect Privacy"
|
||||
color: "cfe8d4"
|
||||
- name: "☁️ PIA"
|
||||
|
||||
@@ -8,5 +8,4 @@
|
||||
|
||||
# Assertions
|
||||
|
||||
* [ ] I am aware that we do not accept manual changes to the servers.json file <!-- If this is your goal, please consult https://github.com/qdm12/gluetun-wiki/blob/main/setup/servers.md#update-using-the-command-line -->
|
||||
* [ ] I am aware that any changes to settings should be reflected in the [wiki](https://github.com/qdm12/gluetun-wiki/)
|
||||
|
||||
+20
-10
@@ -28,6 +28,10 @@ on:
|
||||
- go.mod
|
||||
- go.sum
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}
|
||||
cancel-in-progress: true
|
||||
|
||||
jobs:
|
||||
verify:
|
||||
runs-on: ubuntu-latest
|
||||
@@ -44,7 +48,6 @@ jobs:
|
||||
locale: "US"
|
||||
level: error
|
||||
exclude: |
|
||||
./internal/storage/servers.json
|
||||
./.golangci.yml
|
||||
*.md
|
||||
|
||||
@@ -64,6 +67,10 @@ jobs:
|
||||
-v "$(pwd)/coverage.txt:/tmp/gobuild/coverage.txt" \
|
||||
test-container
|
||||
|
||||
- name: Run integration tests in test container
|
||||
run: |
|
||||
docker run --rm --entrypoint go test-container test -tags=integration ./internal/restrictednet
|
||||
|
||||
- name: Verify dev cross platform compatibility
|
||||
run: docker build --target xcompile .
|
||||
|
||||
@@ -98,7 +105,7 @@ jobs:
|
||||
github.event_name == 'release' ||
|
||||
(github.event_name == 'pull_request' && github.event.pull_request.head.repo.full_name == github.repository && github.actor != 'dependabot[bot]')
|
||||
)
|
||||
needs: [ verify ]
|
||||
needs: [verify]
|
||||
runs-on: ubuntu-latest
|
||||
environment: secrets
|
||||
steps:
|
||||
@@ -120,7 +127,8 @@ jobs:
|
||||
|
||||
- name: Run Gluetun container with ProtonVPN Wireguard and port forwarding
|
||||
configuration
|
||||
run: echo -e "${{ secrets.PROTONVPN_WIREGUARD_PRIVATE_KEY }}" | ./ci/runner
|
||||
run:
|
||||
echo -e "${{ secrets.PROTONVPN_WIREGUARD_PRIVATE_KEY }}" | ./ci/runner
|
||||
protonvpn-wireguard-port-forwarding
|
||||
|
||||
- name: Run Gluetun container with ProtonVPN OpenVPN and port forwarding
|
||||
@@ -129,11 +137,12 @@ jobs:
|
||||
secrets.PROTONVPN_OPENVPN_PASSWORD }}" | ./ci/runner
|
||||
protonvpn-openvpn-port-forwarding
|
||||
|
||||
- name: Run Gluetun container with Private Internet Access OpenVPN and port
|
||||
forwarding configuration
|
||||
run: echo -e "${{ secrets.PRIVATEINTERNETACCESS_OPENVPN_USER }}\n${{
|
||||
secrets.PRIVATEINTERNETACCESS_OPENVPN_PASSWORD }}" | ./ci/runner
|
||||
private-internet-access-openvpn-port-forwarding
|
||||
# - name:
|
||||
# Run Gluetun container with Private Internet Access OpenVPN and port
|
||||
# forwarding configuration
|
||||
# run: echo -e "${{ secrets.PRIVATEINTERNETACCESS_OPENVPN_USER }}\n${{
|
||||
# secrets.PRIVATEINTERNETACCESS_OPENVPN_PASSWORD }}" | ./ci/runner
|
||||
# private-internet-access-openvpn-port-forwarding
|
||||
|
||||
- name: Run Gluetun container with AirVPN Wireguard configuration
|
||||
run: echo -e "${{ secrets.AIRVPN_WIREGUARD_PRIVATE_KEY }}\n${{
|
||||
@@ -141,7 +150,8 @@ jobs:
|
||||
secrets.AIRVPN_WIREGUARD_ADDRESSES }}" | ./ci/runner airvpn-wireguard
|
||||
|
||||
- name: Run Gluetun container with AirVPN OpenVPN configuration
|
||||
run: echo -e "${{ secrets.AIRVPN_OPENVPN_KEY }}\n${{ secrets.AIRVPN_OPENVPN_CERT
|
||||
run:
|
||||
echo -e "${{ secrets.AIRVPN_OPENVPN_KEY }}\n${{ secrets.AIRVPN_OPENVPN_CERT
|
||||
}}" | ./ci/runner airvpn-openvpn
|
||||
|
||||
codeql:
|
||||
@@ -169,7 +179,7 @@ jobs:
|
||||
github.event_name == 'release' ||
|
||||
(github.event_name == 'pull_request' && github.event.pull_request.head.repo.full_name == github.repository && github.actor != 'dependabot[bot]')
|
||||
)
|
||||
needs: [ verify, verify-private, codeql ]
|
||||
needs: [verify, verify-private, codeql]
|
||||
permissions:
|
||||
actions: read
|
||||
contents: read
|
||||
|
||||
@@ -11,6 +11,10 @@ on:
|
||||
- "**.md"
|
||||
- .github/workflows/markdown.yml
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}
|
||||
cancel-in-progress: true
|
||||
|
||||
jobs:
|
||||
markdown:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
@@ -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 == 'passteque/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
|
||||
@@ -12,6 +12,10 @@ formatters:
|
||||
- builtin$
|
||||
- examples$
|
||||
|
||||
run:
|
||||
build-tags:
|
||||
- integration
|
||||
|
||||
linters:
|
||||
settings:
|
||||
misspell:
|
||||
|
||||
Vendored
+1
-1
@@ -3,7 +3,7 @@
|
||||
// to develop this project.
|
||||
"files.eol": "\n",
|
||||
"editor.formatOnSave": true,
|
||||
"go.buildTags": "linux",
|
||||
"go.buildTags": "linux,integration",
|
||||
"go.toolsEnvVars": {
|
||||
"CGO_ENABLED": "0"
|
||||
},
|
||||
|
||||
@@ -50,6 +50,7 @@ Guidance for coding agents working in this repository.
|
||||
- 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.
|
||||
- Prefer using pure functions over methods when possible. Especially if the method does not need any fields from the receiving struct, it should be a pure function.
|
||||
- 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)
|
||||
@@ -115,6 +116,7 @@ Mocking works with the `go.uber.org/mock` library, and the `mockgen` tool.
|
||||
- **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
|
||||
- Always define the gomock controller `ctrl` in the subtest and not in the parent test, or a subtest mock failing will crash all the other subtests.
|
||||
|
||||
### main.go
|
||||
|
||||
@@ -127,6 +129,7 @@ The Go formatter used is gofumpt.
|
||||
### Errors
|
||||
|
||||
- Always prefer wrapping errors with some context with `fmt.Errorf("doing this: %w", err)`
|
||||
- Use `errors.New("error message")` when creating a 'bottom' constant string error without additional context, instead of `fmt.Errorf`
|
||||
- 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:
|
||||
@@ -179,6 +182,8 @@ The Go formatter used is gofumpt.
|
||||
|
||||
- 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.
|
||||
- Prefer using a `switch { case ...}` statement over multiple consecutive `if` statements to have shorter code.
|
||||
- Prefer using `[...]T` instead of `[]T` when the length is fixed and known at compile time, to avoid unnecessary allocations.
|
||||
|
||||
## Validation checklist
|
||||
|
||||
|
||||
+3
-6
@@ -3,7 +3,7 @@ ARG GO_ALPINE_VERSION=3.23
|
||||
ARG GO_VERSION=1.25
|
||||
ARG XCPUTRANSLATE_VERSION=v0.9.0
|
||||
ARG GOLANGCI_LINT_VERSION=v2.4.0
|
||||
ARG MOCKGEN_VERSION=v1.6.0
|
||||
ARG MOCKGEN_VERSION=v0.6.0
|
||||
ARG BUILDPLATFORM=linux/amd64
|
||||
|
||||
FROM --platform=${BUILDPLATFORM} ghcr.io/qdm12/xcputranslate:${XCPUTRANSLATE_VERSION} AS xcputranslate
|
||||
@@ -186,14 +186,12 @@ ENV VPN_SERVICE_PROVIDER=pia \
|
||||
# # ProtonVPN only:
|
||||
SECURE_CORE_ONLY= \
|
||||
TOR_ONLY= \
|
||||
# # Surfshark and ovpn only:
|
||||
# # Surfshark only:
|
||||
MULTIHOP_ONLY= \
|
||||
# # VPN Secure only:
|
||||
PREMIUM_ONLY= \
|
||||
# # PIA and ProtonVPN only:
|
||||
PORT_FORWARD_ONLY= \
|
||||
# # Ovpn only:
|
||||
SERVER_DEDICATED=no \
|
||||
# Firewall
|
||||
FIREWALL_ENABLED_DISABLING_IT_SHOOTS_YOU_IN_YOUR_FOOT=on \
|
||||
FIREWALL_VPN_INPUT_PORTS= \
|
||||
@@ -220,7 +218,6 @@ ENV VPN_SERVICE_PROVIDER=pia \
|
||||
DNS_CACHING=on \
|
||||
DNS_UPSTREAM_IPV6=off \
|
||||
BLOCK_MALICIOUS=on \
|
||||
BLOCK_SURVEILLANCE=off \
|
||||
BLOCK_ADS=off \
|
||||
DNS_UNBLOCK_HOSTNAMES= \
|
||||
DNS_REBINDING_PROTECTION_EXEMPT_HOSTNAMES= \
|
||||
@@ -278,7 +275,7 @@ ENV VPN_SERVICE_PROVIDER=pia \
|
||||
PUID=1000 \
|
||||
PGID=1000
|
||||
ENTRYPOINT ["/gluetun-entrypoint"]
|
||||
EXPOSE 8000/tcp 8888/tcp 8388/tcp 8388/udp 1080/tcp
|
||||
EXPOSE 8000/tcp 8888/tcp 8388/tcp 8388/udp 1080/tcp 1080/udp
|
||||
HEALTHCHECK --interval=5s --timeout=5s --start-period=10s --retries=3 CMD /gluetun-entrypoint healthcheck
|
||||
ARG TARGETPLATFORM
|
||||
RUN apk add --no-cache --update -l wget && \
|
||||
|
||||
@@ -60,20 +60,20 @@ Lightweight swiss-army-knife-like VPN client to multiple VPN service providers
|
||||
## Features
|
||||
|
||||
- Based on Alpine 3.23 for a small Docker image of 43.1MB
|
||||
- Supports: **AirVPN**, **Cyberghost**, **ExpressVPN**, **FastestVPN**, **Giganews**, **HideMyAss**, **IPVanish**, **IVPN**, **Mullvad** (Wireguard only), **NordVPN**, **Ovpn**, **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** (Wireguard only), **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 Wireguard both kernelspace and userspace
|
||||
- For **AirVPN**, **FastestVPN**, **Ivpn**, **Mullvad**, **NordVPN**, **Ovpn**, **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 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/passteque/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 fine blocking of malicious/ads/surveillance hostnames and IP addresses, with live update every 24 hours
|
||||
- DNS fine blocking of malicious/ads hostnames and IP addresses, with live update every 24 hours
|
||||
- Choose the vpn network protocol, `udp` or `tcp`
|
||||
- Built in firewall kill switch to allow traffic only with needed the VPN servers and LAN devices
|
||||
- Built in Shadowsocks proxy server (protocol based on SOCKS5 with an encryption layer, tunnels TCP+UDP)
|
||||
- Built in Socks5 proxy server (tunnels TCP) - partial credits to @angelakis and @adjscent
|
||||
- Built in Socks5 proxy server (tunnels TCP+UDP) - partial credits to @angelakis and @adjscent
|
||||
- Built in HTTP proxy (tunnels HTTP and HTTPS through TCP)
|
||||
- [Connect other containers to it](https://github.com/qdm12/gluetun-wiki/blob/main/setup/connect-a-container-to-gluetun.md)
|
||||
- [Connect LAN devices to it](https://github.com/qdm12/gluetun-wiki/blob/main/setup/connect-a-lan-device-to-gluetun.md)
|
||||
|
||||
@@ -4,18 +4,18 @@ go 1.25.0
|
||||
|
||||
require (
|
||||
github.com/ProtonMail/go-srp v0.0.7
|
||||
github.com/amnezia-vpn/amneziawg-go v0.2.16
|
||||
github.com/amnezia-vpn/amneziawg-go v0.2.18
|
||||
github.com/breml/rootcerts v0.3.4
|
||||
github.com/fatih/color v1.18.0
|
||||
github.com/golang/mock v1.6.0
|
||||
github.com/jsimonetti/rtnetlink v1.4.2
|
||||
github.com/klauspost/compress v1.18.4
|
||||
github.com/klauspost/pgzip v1.2.6
|
||||
github.com/mdlayher/genetlink v1.3.2
|
||||
github.com/mdlayher/genetlink v1.4.0
|
||||
github.com/mdlayher/netlink v1.9.0
|
||||
github.com/miekg/dns v1.1.62
|
||||
github.com/pelletier/go-toml/v2 v2.2.4
|
||||
github.com/qdm12/dns/v2 v2.0.0-rc9.0.20260421173011-9de8e7fdbe3a
|
||||
github.com/qdm12/gluetun-servers v0.1.1-0.20260522005421-14277e92ce82
|
||||
github.com/qdm12/gluetun-servers v0.1.0
|
||||
github.com/qdm12/goservices v0.1.1-0.20251104135713-6bee97bd4978
|
||||
github.com/qdm12/gosettings v0.4.4
|
||||
github.com/qdm12/goshutdown v0.3.0
|
||||
@@ -27,10 +27,10 @@ require (
|
||||
github.com/ti-mo/netfilter v0.5.3
|
||||
github.com/ulikunitz/xz v0.5.15
|
||||
github.com/youmark/pkcs8 v0.0.0-20201027041543-1326539a0a0a
|
||||
golang.org/x/exp v0.0.0-20241009180824-f66d83c29e7c
|
||||
golang.org/x/net v0.51.0
|
||||
golang.org/x/sys v0.42.0
|
||||
golang.org/x/text v0.35.0
|
||||
go.uber.org/mock v0.6.0
|
||||
golang.org/x/net v0.55.0
|
||||
golang.org/x/sys v0.45.0
|
||||
golang.org/x/text v0.38.0
|
||||
golang.zx2c4.com/wireguard v0.0.0-20231211153847-12269c276173
|
||||
golang.zx2c4.com/wireguard/wgctrl v0.0.0-20230429144221-925a1e7659e6
|
||||
gopkg.in/ini.v1 v1.67.1
|
||||
@@ -47,8 +47,7 @@ require (
|
||||
github.com/google/go-cmp v0.7.0 // indirect
|
||||
github.com/mattn/go-colorable v0.1.13 // indirect
|
||||
github.com/mattn/go-isatty v0.0.20 // indirect
|
||||
github.com/mdlayher/socket v0.5.1 // indirect
|
||||
github.com/miekg/dns v1.1.62 // indirect
|
||||
github.com/mdlayher/socket v0.6.0 // indirect
|
||||
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect
|
||||
github.com/pkg/errors v0.9.1 // indirect
|
||||
github.com/pmezard/go-difflib v1.0.0 // indirect
|
||||
@@ -57,10 +56,11 @@ require (
|
||||
github.com/prometheus/common v0.60.1 // indirect
|
||||
github.com/prometheus/procfs v0.15.1 // indirect
|
||||
github.com/riobard/go-bloom v0.0.0-20200614022211-cdc8013cb5b3 // indirect
|
||||
golang.org/x/crypto v0.48.0 // indirect
|
||||
golang.org/x/mod v0.33.0 // indirect
|
||||
golang.org/x/sync v0.20.0 // indirect
|
||||
golang.org/x/tools v0.42.0 // indirect
|
||||
golang.org/x/crypto v0.51.0 // indirect
|
||||
golang.org/x/exp v0.0.0-20241009180824-f66d83c29e7c // indirect
|
||||
golang.org/x/mod v0.36.0 // indirect
|
||||
golang.org/x/sync v0.21.0 // indirect
|
||||
golang.org/x/tools v0.45.0 // indirect
|
||||
golang.zx2c4.com/wintun v0.0.0-20230126152724-0fa3db229ce2 // indirect
|
||||
google.golang.org/protobuf v1.35.1 // indirect
|
||||
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||
|
||||
@@ -6,8 +6,8 @@ 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-srp v0.0.7 h1:Sos3Qk+th4tQR64vsxGIxYpN3rdnG9Wf9K4ZloC1JrI=
|
||||
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/amnezia-vpn/amneziawg-go v0.2.18 h1:pUn7/P8qdGmHd6JmE3bCQXPblZs3vruWR98nLODQLJg=
|
||||
github.com/amnezia-vpn/amneziawg-go v0.2.18/go.mod h1:aMgOk9MuX0xI7b5TKAYp8pLM54RlXcOPzDvYw3YEO5A=
|
||||
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/breml/rootcerts v0.3.4 h1:9i7WNl/ctd9OEAOaTfLy//Wrlfxq/tRQ7v4okYFN9Ys=
|
||||
@@ -48,12 +48,12 @@ github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovk
|
||||
github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
|
||||
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
|
||||
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/go.mod h1:tcC3pkCrPUGIKKsCsp0B3AdaaKuHtaxoJRz3cc+528o=
|
||||
github.com/mdlayher/genetlink v1.4.0 h1:f/Xs7Y2T+GyX9b3dbiUhnLE9InGs5F9RxJ2JwBMl71o=
|
||||
github.com/mdlayher/genetlink v1.4.0/go.mod h1:d1hrKr8fwZU2JkcAtQUAzeTrI7nbgQSl+5k1cC0biSA=
|
||||
github.com/mdlayher/netlink v1.9.0 h1:G8+GLq2x3v4D4MVIqDdNUhTUC7TKiCy/6MDkmItfKco=
|
||||
github.com/mdlayher/netlink v1.9.0/go.mod h1:YBnl5BXsCoRuwBjKKlZ+aYmEoq0r12FDA/3JC+94KDg=
|
||||
github.com/mdlayher/socket v0.5.1 h1:VZaqt6RkGkt2OE9l3GcC6nZkqD3xKeQLyfleW/uBcos=
|
||||
github.com/mdlayher/socket v0.5.1/go.mod h1:TjPLHI1UgwEv5J1B5q0zTZq12A/6H7nKmtTanQE37IQ=
|
||||
github.com/mdlayher/socket v0.6.0 h1:ScZPaAGyO1icQnbFrhPM8mnXyMu9qukC1K4ZoM2IQKU=
|
||||
github.com/mdlayher/socket v0.6.0/go.mod h1:q7vozUAnxSqnjHc12Fik5yUKIzfZ8ITCfMkhOtE9z18=
|
||||
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/mikioh/ipaddr v0.0.0-20190404000644-d465c8ab6721 h1:RlZweED6sbSArvlE924+mUcZuXKLBHA35U7LN621Bws=
|
||||
@@ -76,8 +76,8 @@ github.com/prometheus/procfs v0.15.1 h1:YagwOFzUgYfKKHX6Dr+sHT7km/hxC76UB0leargg
|
||||
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-rc9.0.20260421173011-9de8e7fdbe3a/go.mod h1:98foWgXJZ+g8gJIuO+fdO+oWpFei5WShMFTeN4Im2lE=
|
||||
github.com/qdm12/gluetun-servers v0.1.1-0.20260522005421-14277e92ce82 h1:tE44IEW7o9yPQaO8HBeoO9RxtTTxqhboIypegrQlVt8=
|
||||
github.com/qdm12/gluetun-servers v0.1.1-0.20260522005421-14277e92ce82/go.mod h1:acttuyHyoFDu6GTbf3kAV+QXeiX8oJeh0MBic67/9z8=
|
||||
github.com/qdm12/gluetun-servers v0.1.0 h1:w9JLghKZwI0Gzpp9p5rNANgEYUUZ1dxdxsG6NKIojaY=
|
||||
github.com/qdm12/gluetun-servers v0.1.0/go.mod h1:acttuyHyoFDu6GTbf3kAV+QXeiX8oJeh0MBic67/9z8=
|
||||
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/gosettings v0.4.4 h1:SM6tOZDf6k8qbjWU8KWyBF4mWIixfsKCfh9DGRLHlj4=
|
||||
@@ -111,44 +111,39 @@ 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/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/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
|
||||
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=
|
||||
go.uber.org/mock v0.6.0 h1:hyF9dfmbgIX5EfOdasqLsWD6xqpNZlXblLB/Dbnwv3Y=
|
||||
go.uber.org/mock v0.6.0/go.mod h1:KiVJ4BqZJaMj4svdfmHM0AUx4NJYO8ZNpPnZn1Z+BBU=
|
||||
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-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.7.0/go.mod h1:pYwdfH91IfpZVANVyUOhSIPZaFoJGxTFbZhFTx+dXZU=
|
||||
golang.org/x/crypto v0.48.0 h1:/VRzVqiRSggnhY7gNRxPauEQ5Drw9haKdM0jqfcCFts=
|
||||
golang.org/x/crypto v0.48.0/go.mod h1:r0kV5h3qnFPlQnBSrULhlsRfryS2pmewsg+XfMgkVos=
|
||||
golang.org/x/crypto v0.51.0 h1:IBPXwPfKxY7cWQZ38ZCIRPI50YLeevDLlLnyC5wRGTI=
|
||||
golang.org/x/crypto v0.51.0/go.mod h1:8AdwkbraGNABw2kOX6YFPs3WM22XqI4EXEd8g+x7Oc8=
|
||||
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/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.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
|
||||
golang.org/x/mod v0.33.0 h1:tHFzIWbBifEmbwtGz65eaWyGiGZatSrT9prnU8DbVL8=
|
||||
golang.org/x/mod v0.33.0/go.mod h1:swjeQEj+6r7fODbD2cqrnje9PnziFuw4bmLbBZFrQ5w=
|
||||
golang.org/x/mod v0.36.0 h1:JJjpVx6myfUsUdAzZuOSTTmRE0PfZeNWzzvKrP7amb4=
|
||||
golang.org/x/mod v0.36.0/go.mod h1:moc6ELqsWcOw5Ef3xVprK5ul/MvtVvkIXLziUOICjUQ=
|
||||
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-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
||||
golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM=
|
||||
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.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.51.0/go.mod h1:aamm+2QF5ogm02fjy5Bb7CQ0WMt1/WVM7FtyaTLlA9Y=
|
||||
golang.org/x/net v0.55.0 h1:bcvxaJn3e1U6InsFWt1JUq1aSjnRxLzT2rtD2KfkDF8=
|
||||
golang.org/x/net v0.55.0/go.mod h1:L5U2KuzuOe1lY7Z+aWVIKK6qEeJXnXV9yzGA+WCHJww=
|
||||
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-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.20.0 h1:e0PTpb7pjO8GAtTs2dQ6jYa5BWYlMuX047Dco/pItO4=
|
||||
golang.org/x/sync v0.20.0/go.mod h1:9xrNwdLfx4jkKbNva9FpL6vEN7evnE43NNNJQ2LF3+0=
|
||||
golang.org/x/sync v0.21.0 h1:HLII4xRRTtCRkxYp4HNFF0Js/Og6q2i++KXbg0gHCwM=
|
||||
golang.org/x/sync v0.21.0/go.mod h1:9xrNwdLfx4jkKbNva9FpL6vEN7evnE43NNNJQ2LF3+0=
|
||||
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-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20211007075335-d3039528d8ac/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
@@ -156,8 +151,8 @@ golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBc
|
||||
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/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.42.0 h1:omrd2nAlyT5ESRdCLYdm3+fMfNFE/+Rf4bDIQImRJeo=
|
||||
golang.org/x/sys v0.42.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw=
|
||||
golang.org/x/sys v0.45.0 h1:dO4czNzziLiiXplLQgBCEpCvXQ3dnkn0SdaZSYdQ+FY=
|
||||
golang.org/x/sys v0.45.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw=
|
||||
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.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
|
||||
@@ -167,20 +162,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.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.35.0 h1:JOVx6vVDFokkpaq1AEptVzLTpDe9KGpj5tR4/X+ybL8=
|
||||
golang.org/x/text v0.35.0/go.mod h1:khi/HExzZJ2pGnjenulevKNX1W67CUy0AsXcNubPGCA=
|
||||
golang.org/x/text v0.38.0 h1:sXmwo9DwP3OK9EZ7PqAdaooSGozfl/3a6/xJcbzPRhE=
|
||||
golang.org/x/text v0.38.0/go.mod h1:YXZt3QhHUKYT53r2lLKFIVi6Ao1jdzrTR/KQ09qyxF4=
|
||||
golang.org/x/time v0.9.0 h1:EsRrnYcQiGH+5FfbgvV4AP7qEZstoyrHB0DzarOQ4ZY=
|
||||
golang.org/x/time v0.9.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM=
|
||||
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.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.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=
|
||||
golang.org/x/tools v0.42.0 h1:uNgphsn75Tdz5Ji2q36v/nsFSfR/9BRFvqhGBaJGd5k=
|
||||
golang.org/x/tools v0.42.0/go.mod h1:Ma6lCIwGZvHK6XtgbswSoWroEkhugApmsXyrUmBhfr0=
|
||||
golang.org/x/tools v0.45.0 h1:18qN3FAooORvApf5XjCXgsuayZOEtXf6JK18I3+ONa8=
|
||||
golang.org/x/tools v0.45.0/go.mod h1:LuUGqqaXcXMEFEruIVJVm5mgDD8vww/z/SR1gQ4uE/0=
|
||||
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-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.zx2c4.com/wintun v0.0.0-20230126152724-0fa3db229ce2 h1:B82qJJgjvYKsXS9jeunTOisW56dUokqW/FOteYJJ/yg=
|
||||
golang.zx2c4.com/wintun v0.0.0-20230126152724-0fa3db229ce2/go.mod h1:deeaetjYA+DHMHg+sMSMI58GrEteJUUzzw7en6TJQcI=
|
||||
golang.zx2c4.com/wireguard v0.0.0-20231211153847-12269c276173 h1:/jFs0duh4rdb8uIfPMv78iAJGcPKDeqAFnaLBropIC4=
|
||||
|
||||
@@ -1,5 +1,10 @@
|
||||
// Code generated by MockGen. DO NOT EDIT.
|
||||
// Source: github.com/qdm12/gluetun/internal/amneziawg (interfaces: Logger)
|
||||
//
|
||||
// Generated by this command:
|
||||
//
|
||||
// mockgen -destination=log_mock_test.go -package amneziawg . Logger
|
||||
//
|
||||
|
||||
// Package amneziawg is a generated GoMock package.
|
||||
package amneziawg
|
||||
@@ -7,13 +12,14 @@ package amneziawg
|
||||
import (
|
||||
reflect "reflect"
|
||||
|
||||
gomock "github.com/golang/mock/gomock"
|
||||
gomock "go.uber.org/mock/gomock"
|
||||
)
|
||||
|
||||
// MockLogger is a mock of Logger interface.
|
||||
type MockLogger struct {
|
||||
ctrl *gomock.Controller
|
||||
recorder *MockLoggerMockRecorder
|
||||
isgomock struct{}
|
||||
}
|
||||
|
||||
// MockLoggerMockRecorder is the mock recorder for MockLogger.
|
||||
@@ -34,71 +40,71 @@ func (m *MockLogger) EXPECT() *MockLoggerMockRecorder {
|
||||
}
|
||||
|
||||
// Debug mocks base method.
|
||||
func (m *MockLogger) Debug(arg0 string) {
|
||||
func (m *MockLogger) Debug(s string) {
|
||||
m.ctrl.T.Helper()
|
||||
m.ctrl.Call(m, "Debug", arg0)
|
||||
m.ctrl.Call(m, "Debug", s)
|
||||
}
|
||||
|
||||
// Debug indicates an expected call of Debug.
|
||||
func (mr *MockLoggerMockRecorder) Debug(arg0 interface{}) *gomock.Call {
|
||||
func (mr *MockLoggerMockRecorder) Debug(s any) *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
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), s)
|
||||
}
|
||||
|
||||
// Debugf mocks base method.
|
||||
func (m *MockLogger) Debugf(arg0 string, arg1 ...interface{}) {
|
||||
func (m *MockLogger) Debugf(format string, args ...any) {
|
||||
m.ctrl.T.Helper()
|
||||
varargs := []interface{}{arg0}
|
||||
for _, a := range arg1 {
|
||||
varargs := []any{format}
|
||||
for _, a := range args {
|
||||
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 {
|
||||
func (mr *MockLoggerMockRecorder) Debugf(format any, args ...any) *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
varargs := append([]interface{}{arg0}, arg1...)
|
||||
varargs := append([]any{format}, args...)
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Debugf", reflect.TypeOf((*MockLogger)(nil).Debugf), varargs...)
|
||||
}
|
||||
|
||||
// Error mocks base method.
|
||||
func (m *MockLogger) Error(arg0 string) {
|
||||
func (m *MockLogger) Error(s string) {
|
||||
m.ctrl.T.Helper()
|
||||
m.ctrl.Call(m, "Error", arg0)
|
||||
m.ctrl.Call(m, "Error", s)
|
||||
}
|
||||
|
||||
// Error indicates an expected call of Error.
|
||||
func (mr *MockLoggerMockRecorder) Error(arg0 interface{}) *gomock.Call {
|
||||
func (mr *MockLoggerMockRecorder) Error(s any) *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Error", reflect.TypeOf((*MockLogger)(nil).Error), arg0)
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Error", reflect.TypeOf((*MockLogger)(nil).Error), s)
|
||||
}
|
||||
|
||||
// Errorf mocks base method.
|
||||
func (m *MockLogger) Errorf(arg0 string, arg1 ...interface{}) {
|
||||
func (m *MockLogger) Errorf(format string, args ...any) {
|
||||
m.ctrl.T.Helper()
|
||||
varargs := []interface{}{arg0}
|
||||
for _, a := range arg1 {
|
||||
varargs := []any{format}
|
||||
for _, a := range args {
|
||||
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 {
|
||||
func (mr *MockLoggerMockRecorder) Errorf(format any, args ...any) *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
varargs := append([]interface{}{arg0}, arg1...)
|
||||
varargs := append([]any{format}, args...)
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Errorf", reflect.TypeOf((*MockLogger)(nil).Errorf), varargs...)
|
||||
}
|
||||
|
||||
// Info mocks base method.
|
||||
func (m *MockLogger) Info(arg0 string) {
|
||||
func (m *MockLogger) Info(s string) {
|
||||
m.ctrl.T.Helper()
|
||||
m.ctrl.Call(m, "Info", arg0)
|
||||
m.ctrl.Call(m, "Info", s)
|
||||
}
|
||||
|
||||
// Info indicates an expected call of Info.
|
||||
func (mr *MockLoggerMockRecorder) Info(arg0 interface{}) *gomock.Call {
|
||||
func (mr *MockLoggerMockRecorder) Info(s any) *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Info", reflect.TypeOf((*MockLogger)(nil).Info), arg0)
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Info", reflect.TypeOf((*MockLogger)(nil).Info), s)
|
||||
}
|
||||
|
||||
@@ -1,5 +1,10 @@
|
||||
// Code generated by MockGen. DO NOT EDIT.
|
||||
// Source: github.com/qdm12/gluetun/internal/amneziawg (interfaces: NetLinker)
|
||||
//
|
||||
// Generated by this command:
|
||||
//
|
||||
// mockgen -destination=netlinker_mock_test.go -package amneziawg . NetLinker
|
||||
//
|
||||
|
||||
// Package amneziawg is a generated GoMock package.
|
||||
package amneziawg
|
||||
@@ -8,14 +13,15 @@ import (
|
||||
netip "net/netip"
|
||||
reflect "reflect"
|
||||
|
||||
gomock "github.com/golang/mock/gomock"
|
||||
netlink "github.com/qdm12/gluetun/internal/netlink"
|
||||
gomock "go.uber.org/mock/gomock"
|
||||
)
|
||||
|
||||
// MockNetLinker is a mock of NetLinker interface.
|
||||
type MockNetLinker struct {
|
||||
ctrl *gomock.Controller
|
||||
recorder *MockNetLinkerMockRecorder
|
||||
isgomock struct{}
|
||||
}
|
||||
|
||||
// MockNetLinkerMockRecorder is the mock recorder for MockNetLinker.
|
||||
@@ -36,17 +42,17 @@ func (m *MockNetLinker) EXPECT() *MockNetLinkerMockRecorder {
|
||||
}
|
||||
|
||||
// AddrReplace mocks base method.
|
||||
func (m *MockNetLinker) AddrReplace(arg0 uint32, arg1 netip.Prefix) error {
|
||||
func (m *MockNetLinker) AddrReplace(linkIndex uint32, addr netip.Prefix) error {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "AddrReplace", arg0, arg1)
|
||||
ret := m.ctrl.Call(m, "AddrReplace", linkIndex, addr)
|
||||
ret0, _ := ret[0].(error)
|
||||
return ret0
|
||||
}
|
||||
|
||||
// AddrReplace indicates an expected call of AddrReplace.
|
||||
func (mr *MockNetLinkerMockRecorder) AddrReplace(arg0, arg1 interface{}) *gomock.Call {
|
||||
func (mr *MockNetLinkerMockRecorder) AddrReplace(linkIndex, addr any) *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "AddrReplace", reflect.TypeOf((*MockNetLinker)(nil).AddrReplace), arg0, arg1)
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "AddrReplace", reflect.TypeOf((*MockNetLinker)(nil).AddrReplace), linkIndex, addr)
|
||||
}
|
||||
|
||||
// IsWireguardSupported mocks base method.
|
||||
@@ -65,47 +71,47 @@ func (mr *MockNetLinkerMockRecorder) IsWireguardSupported() *gomock.Call {
|
||||
}
|
||||
|
||||
// LinkAdd mocks base method.
|
||||
func (m *MockNetLinker) LinkAdd(arg0 netlink.Link) (uint32, error) {
|
||||
func (m *MockNetLinker) LinkAdd(link netlink.Link) (uint32, error) {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "LinkAdd", arg0)
|
||||
ret := m.ctrl.Call(m, "LinkAdd", link)
|
||||
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 {
|
||||
func (mr *MockNetLinkerMockRecorder) LinkAdd(link any) *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "LinkAdd", reflect.TypeOf((*MockNetLinker)(nil).LinkAdd), arg0)
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "LinkAdd", reflect.TypeOf((*MockNetLinker)(nil).LinkAdd), link)
|
||||
}
|
||||
|
||||
// LinkByName mocks base method.
|
||||
func (m *MockNetLinker) LinkByName(arg0 string) (netlink.Link, error) {
|
||||
func (m *MockNetLinker) LinkByName(name string) (netlink.Link, error) {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "LinkByName", arg0)
|
||||
ret := m.ctrl.Call(m, "LinkByName", name)
|
||||
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 {
|
||||
func (mr *MockNetLinkerMockRecorder) LinkByName(name any) *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "LinkByName", reflect.TypeOf((*MockNetLinker)(nil).LinkByName), arg0)
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "LinkByName", reflect.TypeOf((*MockNetLinker)(nil).LinkByName), name)
|
||||
}
|
||||
|
||||
// LinkDel mocks base method.
|
||||
func (m *MockNetLinker) LinkDel(arg0 uint32) error {
|
||||
func (m *MockNetLinker) LinkDel(linkIndex uint32) error {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "LinkDel", arg0)
|
||||
ret := m.ctrl.Call(m, "LinkDel", linkIndex)
|
||||
ret0, _ := ret[0].(error)
|
||||
return ret0
|
||||
}
|
||||
|
||||
// LinkDel indicates an expected call of LinkDel.
|
||||
func (mr *MockNetLinkerMockRecorder) LinkDel(arg0 interface{}) *gomock.Call {
|
||||
func (mr *MockNetLinkerMockRecorder) LinkDel(linkIndex any) *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "LinkDel", reflect.TypeOf((*MockNetLinker)(nil).LinkDel), arg0)
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "LinkDel", reflect.TypeOf((*MockNetLinker)(nil).LinkDel), linkIndex)
|
||||
}
|
||||
|
||||
// LinkList mocks base method.
|
||||
@@ -124,86 +130,86 @@ func (mr *MockNetLinkerMockRecorder) LinkList() *gomock.Call {
|
||||
}
|
||||
|
||||
// LinkSetDown mocks base method.
|
||||
func (m *MockNetLinker) LinkSetDown(arg0 uint32) error {
|
||||
func (m *MockNetLinker) LinkSetDown(linkIndex uint32) error {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "LinkSetDown", arg0)
|
||||
ret := m.ctrl.Call(m, "LinkSetDown", linkIndex)
|
||||
ret0, _ := ret[0].(error)
|
||||
return ret0
|
||||
}
|
||||
|
||||
// LinkSetDown indicates an expected call of LinkSetDown.
|
||||
func (mr *MockNetLinkerMockRecorder) LinkSetDown(arg0 interface{}) *gomock.Call {
|
||||
func (mr *MockNetLinkerMockRecorder) LinkSetDown(linkIndex any) *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "LinkSetDown", reflect.TypeOf((*MockNetLinker)(nil).LinkSetDown), arg0)
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "LinkSetDown", reflect.TypeOf((*MockNetLinker)(nil).LinkSetDown), linkIndex)
|
||||
}
|
||||
|
||||
// LinkSetUp mocks base method.
|
||||
func (m *MockNetLinker) LinkSetUp(arg0 uint32) error {
|
||||
func (m *MockNetLinker) LinkSetUp(linkIndex uint32) error {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "LinkSetUp", arg0)
|
||||
ret := m.ctrl.Call(m, "LinkSetUp", linkIndex)
|
||||
ret0, _ := ret[0].(error)
|
||||
return ret0
|
||||
}
|
||||
|
||||
// LinkSetUp indicates an expected call of LinkSetUp.
|
||||
func (mr *MockNetLinkerMockRecorder) LinkSetUp(arg0 interface{}) *gomock.Call {
|
||||
func (mr *MockNetLinkerMockRecorder) LinkSetUp(linkIndex any) *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "LinkSetUp", reflect.TypeOf((*MockNetLinker)(nil).LinkSetUp), arg0)
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "LinkSetUp", reflect.TypeOf((*MockNetLinker)(nil).LinkSetUp), linkIndex)
|
||||
}
|
||||
|
||||
// RouteAdd mocks base method.
|
||||
func (m *MockNetLinker) RouteAdd(arg0 netlink.Route) error {
|
||||
func (m *MockNetLinker) RouteAdd(route netlink.Route) error {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "RouteAdd", arg0)
|
||||
ret := m.ctrl.Call(m, "RouteAdd", route)
|
||||
ret0, _ := ret[0].(error)
|
||||
return ret0
|
||||
}
|
||||
|
||||
// RouteAdd indicates an expected call of RouteAdd.
|
||||
func (mr *MockNetLinkerMockRecorder) RouteAdd(arg0 interface{}) *gomock.Call {
|
||||
func (mr *MockNetLinkerMockRecorder) RouteAdd(route any) *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "RouteAdd", reflect.TypeOf((*MockNetLinker)(nil).RouteAdd), arg0)
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "RouteAdd", reflect.TypeOf((*MockNetLinker)(nil).RouteAdd), route)
|
||||
}
|
||||
|
||||
// RouteList mocks base method.
|
||||
func (m *MockNetLinker) RouteList(arg0 byte) ([]netlink.Route, error) {
|
||||
func (m *MockNetLinker) RouteList(family uint8) ([]netlink.Route, error) {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "RouteList", arg0)
|
||||
ret := m.ctrl.Call(m, "RouteList", family)
|
||||
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 {
|
||||
func (mr *MockNetLinkerMockRecorder) RouteList(family any) *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "RouteList", reflect.TypeOf((*MockNetLinker)(nil).RouteList), arg0)
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "RouteList", reflect.TypeOf((*MockNetLinker)(nil).RouteList), family)
|
||||
}
|
||||
|
||||
// RuleAdd mocks base method.
|
||||
func (m *MockNetLinker) RuleAdd(arg0 netlink.Rule) error {
|
||||
func (m *MockNetLinker) RuleAdd(rule netlink.Rule) error {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "RuleAdd", arg0)
|
||||
ret := m.ctrl.Call(m, "RuleAdd", rule)
|
||||
ret0, _ := ret[0].(error)
|
||||
return ret0
|
||||
}
|
||||
|
||||
// RuleAdd indicates an expected call of RuleAdd.
|
||||
func (mr *MockNetLinkerMockRecorder) RuleAdd(arg0 interface{}) *gomock.Call {
|
||||
func (mr *MockNetLinkerMockRecorder) RuleAdd(rule any) *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "RuleAdd", reflect.TypeOf((*MockNetLinker)(nil).RuleAdd), arg0)
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "RuleAdd", reflect.TypeOf((*MockNetLinker)(nil).RuleAdd), rule)
|
||||
}
|
||||
|
||||
// RuleDel mocks base method.
|
||||
func (m *MockNetLinker) RuleDel(arg0 netlink.Rule) error {
|
||||
func (m *MockNetLinker) RuleDel(rule netlink.Rule) error {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "RuleDel", arg0)
|
||||
ret := m.ctrl.Call(m, "RuleDel", rule)
|
||||
ret0, _ := ret[0].(error)
|
||||
return ret0
|
||||
}
|
||||
|
||||
// RuleDel indicates an expected call of RuleDel.
|
||||
func (mr *MockNetLinkerMockRecorder) RuleDel(arg0 interface{}) *gomock.Call {
|
||||
func (mr *MockNetLinkerMockRecorder) RuleDel(rule any) *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "RuleDel", reflect.TypeOf((*MockNetLinker)(nil).RuleDel), arg0)
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "RuleDel", reflect.TypeOf((*MockNetLinker)(nil).RuleDel), rule)
|
||||
}
|
||||
|
||||
@@ -4,8 +4,8 @@ import (
|
||||
"errors"
|
||||
"testing"
|
||||
|
||||
"github.com/golang/mock/gomock"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"go.uber.org/mock/gomock"
|
||||
)
|
||||
|
||||
func Test_Cleanups(t *testing.T) {
|
||||
|
||||
@@ -1,5 +1,10 @@
|
||||
// Code generated by MockGen. DO NOT EDIT.
|
||||
// Source: github.com/qdm12/gluetun/internal/cleanup (interfaces: Logger)
|
||||
//
|
||||
// Generated by this command:
|
||||
//
|
||||
// mockgen -destination=mocks_test.go -package=cleanup . Logger
|
||||
//
|
||||
|
||||
// Package cleanup is a generated GoMock package.
|
||||
package cleanup
|
||||
@@ -7,13 +12,14 @@ package cleanup
|
||||
import (
|
||||
reflect "reflect"
|
||||
|
||||
gomock "github.com/golang/mock/gomock"
|
||||
gomock "go.uber.org/mock/gomock"
|
||||
)
|
||||
|
||||
// MockLogger is a mock of Logger interface.
|
||||
type MockLogger struct {
|
||||
ctrl *gomock.Controller
|
||||
recorder *MockLoggerMockRecorder
|
||||
isgomock struct{}
|
||||
}
|
||||
|
||||
// MockLoggerMockRecorder is the mock recorder for MockLogger.
|
||||
@@ -40,7 +46,7 @@ func (m *MockLogger) Debug(arg0 string) {
|
||||
}
|
||||
|
||||
// Debug indicates an expected call of Debug.
|
||||
func (mr *MockLoggerMockRecorder) Debug(arg0 interface{}) *gomock.Call {
|
||||
func (mr *MockLoggerMockRecorder) Debug(arg0 any) *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Debug", reflect.TypeOf((*MockLogger)(nil).Debug), arg0)
|
||||
}
|
||||
@@ -52,7 +58,7 @@ func (m *MockLogger) Error(arg0 string) {
|
||||
}
|
||||
|
||||
// Error indicates an expected call of Error.
|
||||
func (mr *MockLoggerMockRecorder) Error(arg0 interface{}) *gomock.Call {
|
||||
func (mr *MockLoggerMockRecorder) Error(arg0 any) *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Error", reflect.TypeOf((*MockLogger)(nil).Error), arg0)
|
||||
}
|
||||
|
||||
@@ -5,6 +5,7 @@ import (
|
||||
"errors"
|
||||
"flag"
|
||||
"fmt"
|
||||
"net"
|
||||
"net/http"
|
||||
"slices"
|
||||
"strings"
|
||||
@@ -104,6 +105,10 @@ func (c *CLI) Update(ctx context.Context, args []string, logger UpdaterLogger) e
|
||||
if err != nil {
|
||||
return fmt.Errorf("creating DoH dialer: %w", err)
|
||||
}
|
||||
net.DefaultResolver = &net.Resolver{
|
||||
PreferGo: true,
|
||||
Dial: dnsDialer.Dial,
|
||||
}
|
||||
|
||||
const clientTimeout = 10 * time.Second
|
||||
httpClient := &http.Client{Timeout: clientTimeout}
|
||||
|
||||
@@ -1,5 +1,10 @@
|
||||
// Code generated by MockGen. DO NOT EDIT.
|
||||
// Source: interfaces_local.go
|
||||
//
|
||||
// Generated by this command:
|
||||
//
|
||||
// mockgen -destination=mocks_local_test.go -package=command -source=interfaces_local.go
|
||||
//
|
||||
|
||||
// Package command is a generated GoMock package.
|
||||
package command
|
||||
@@ -8,13 +13,14 @@ import (
|
||||
io "io"
|
||||
reflect "reflect"
|
||||
|
||||
gomock "github.com/golang/mock/gomock"
|
||||
gomock "go.uber.org/mock/gomock"
|
||||
)
|
||||
|
||||
// MockexecCmd is a mock of execCmd interface.
|
||||
type MockexecCmd struct {
|
||||
ctrl *gomock.Controller
|
||||
recorder *MockexecCmdMockRecorder
|
||||
isgomock struct{}
|
||||
}
|
||||
|
||||
// MockexecCmdMockRecorder is the mock recorder for MockexecCmd.
|
||||
|
||||
@@ -4,9 +4,9 @@ import (
|
||||
"errors"
|
||||
"testing"
|
||||
|
||||
gomock "github.com/golang/mock/gomock"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
gomock "go.uber.org/mock/gomock"
|
||||
)
|
||||
|
||||
func Test_run(t *testing.T) {
|
||||
|
||||
@@ -9,8 +9,9 @@ import (
|
||||
)
|
||||
|
||||
// Start launches a command and streams stdout and stderr to channels.
|
||||
// All the channels returned are ready only and won't be closed
|
||||
// if the command fails later.
|
||||
// stdoutLines and stderrLines channels will be closed when there is no more
|
||||
// output to read, in order for the caller to catch all lines even after the
|
||||
// command has finished. The waitError channel returned will never be closed.
|
||||
func (c *Cmder) Start(cmd *exec.Cmd) (
|
||||
stdoutLines, stderrLines <-chan string,
|
||||
waitError <-chan error, startErr error,
|
||||
@@ -38,6 +39,7 @@ func start(cmd execCmd) (stdoutLines, stderrLines <-chan string,
|
||||
if err != nil {
|
||||
_ = stdout.Close()
|
||||
<-stdoutDone
|
||||
close(stdoutLinesCh)
|
||||
return nil, nil, nil, err
|
||||
}
|
||||
go streamToChannel(stderrReady, stderrDone, stderr, stderrLinesCh)
|
||||
@@ -45,9 +47,11 @@ func start(cmd execCmd) (stdoutLines, stderrLines <-chan string,
|
||||
err = cmd.Start()
|
||||
if err != nil {
|
||||
_ = stdout.Close()
|
||||
_ = stderr.Close()
|
||||
<-stdoutDone
|
||||
close(stdoutLinesCh)
|
||||
_ = stderr.Close()
|
||||
<-stderrDone
|
||||
close(stderrLinesCh)
|
||||
return nil, nil, nil, err
|
||||
}
|
||||
|
||||
@@ -55,8 +59,10 @@ func start(cmd execCmd) (stdoutLines, stderrLines <-chan string,
|
||||
go func() {
|
||||
err := cmd.Wait()
|
||||
<-stdoutDone
|
||||
<-stderrDone
|
||||
close(stdoutLinesCh)
|
||||
_ = stdout.Close()
|
||||
<-stderrDone
|
||||
close(stderrLinesCh)
|
||||
_ = stderr.Close()
|
||||
waitErrorCh <- err
|
||||
}()
|
||||
|
||||
@@ -7,9 +7,9 @@ import (
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
gomock "github.com/golang/mock/gomock"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
gomock "go.uber.org/mock/gomock"
|
||||
)
|
||||
|
||||
func linesToReadCloser(lines []string) io.ReadCloser {
|
||||
@@ -89,21 +89,41 @@ func Test_start(t *testing.T) {
|
||||
|
||||
require.NoError(t, err)
|
||||
|
||||
var stdoutIndex, stderrIndex int
|
||||
collectAndCheckChannels(t, stdoutLines, stderrLines, waitError,
|
||||
testCase.stdout, testCase.stderr, testCase.waitErr)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func collectAndCheckChannels(t *testing.T, stdoutLines, stderrLines <-chan string,
|
||||
waitError <-chan error, expectedStdout, expectedStderr []string, expectedWaitErr error,
|
||||
) {
|
||||
t.Helper()
|
||||
|
||||
stdoutIndex := 0
|
||||
stderrIndex := 0
|
||||
|
||||
done := false
|
||||
for !done {
|
||||
select {
|
||||
case line := <-stdoutLines:
|
||||
assert.Equal(t, testCase.stdout[stdoutIndex], line)
|
||||
case line, ok := <-stdoutLines:
|
||||
if !ok {
|
||||
stdoutLines = nil
|
||||
continue
|
||||
}
|
||||
assert.Equal(t, expectedStdout[stdoutIndex], line)
|
||||
stdoutIndex++
|
||||
case line := <-stderrLines:
|
||||
assert.Equal(t, testCase.stderr[stderrIndex], line)
|
||||
case line, ok := <-stderrLines:
|
||||
if !ok {
|
||||
stderrLines = nil
|
||||
continue
|
||||
}
|
||||
assert.Equal(t, expectedStderr[stderrIndex], line)
|
||||
stderrIndex++
|
||||
case err := <-waitError:
|
||||
if testCase.waitErr != nil {
|
||||
if expectedWaitErr != nil {
|
||||
require.Error(t, err)
|
||||
assert.Equal(t, testCase.waitErr.Error(), err.Error())
|
||||
assert.Equal(t, expectedWaitErr.Error(), err.Error())
|
||||
} else {
|
||||
assert.NoError(t, err)
|
||||
}
|
||||
@@ -111,8 +131,6 @@ func Test_start(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
assert.Equal(t, len(testCase.stdout), stdoutIndex)
|
||||
assert.Equal(t, len(testCase.stderr), stderrIndex)
|
||||
})
|
||||
}
|
||||
assert.Equal(t, len(expectedStdout), stdoutIndex)
|
||||
assert.Equal(t, len(expectedStderr), stderrIndex)
|
||||
}
|
||||
|
||||
@@ -18,31 +18,39 @@ func (c *Cmder) RunAndLog(ctx context.Context, command string, logger Logger) (e
|
||||
return err
|
||||
}
|
||||
|
||||
streamCtx, streamCancel := context.WithCancel(context.Background())
|
||||
streamDone := make(chan struct{})
|
||||
go streamLines(streamCtx, streamDone, logger, stdout, stderr)
|
||||
go streamLines(streamDone, logger, stdout, stderr)
|
||||
|
||||
err = <-waitError
|
||||
streamCancel()
|
||||
<-streamDone
|
||||
return err
|
||||
}
|
||||
|
||||
func streamLines(ctx context.Context, done chan<- struct{},
|
||||
logger Logger, stdout, stderr <-chan string,
|
||||
func streamLines(done chan<- struct{}, logger Logger,
|
||||
stdout, stderr <-chan string,
|
||||
) {
|
||||
defer close(done)
|
||||
|
||||
var line string
|
||||
|
||||
for {
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
return
|
||||
case line = <-stdout:
|
||||
case line, ok := <-stdout:
|
||||
if ok {
|
||||
logger.Info(line)
|
||||
case line = <-stderr:
|
||||
break
|
||||
}
|
||||
if stderr == nil {
|
||||
return
|
||||
}
|
||||
stdout = nil
|
||||
case line, ok := <-stderr:
|
||||
if ok {
|
||||
logger.Error(line)
|
||||
break
|
||||
}
|
||||
if stdout == nil {
|
||||
return
|
||||
}
|
||||
stderr = nil
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
package settings
|
||||
|
||||
import (
|
||||
"maps"
|
||||
"slices"
|
||||
|
||||
"github.com/qdm12/gosettings/reader"
|
||||
"golang.org/x/exp/maps"
|
||||
)
|
||||
|
||||
func readObsolete(r *reader.Reader) (warnings []string) {
|
||||
@@ -16,8 +16,9 @@ func readObsolete(r *reader.Reader) (warnings []string) {
|
||||
"HEALTH_VPN_DURATION_ADDITION": "HEALTH_VPN_DURATION_ADDITION is obsolete",
|
||||
"DNS_KEEP_NAMESERVER": "DNS_KEEP_NAMESERVER is obsolete because you should use the built-in server which now " +
|
||||
"forwards local names to private DNS resolvers found in /etc/resolv.conf at container start",
|
||||
"BLOCK_SURVEILLANCE": "BLOCK_SURVEILLANCE is obsolete because its DNS block lists are not longer maintained",
|
||||
}
|
||||
sortedKeys := maps.Keys(keyToMessage)
|
||||
sortedKeys := slices.Collect(maps.Keys(keyToMessage))
|
||||
slices.Sort(sortedKeys)
|
||||
warnings = make([]string, 0, len(keyToMessage))
|
||||
for _, key := range sortedKeys {
|
||||
|
||||
@@ -16,7 +16,6 @@ import (
|
||||
type DNSBlacklist struct {
|
||||
BlockMalicious *bool
|
||||
BlockAds *bool
|
||||
BlockSurveillance *bool
|
||||
AllowedHosts []string
|
||||
AddBlockedHosts []string
|
||||
AddBlockedIPs []netip.Addr
|
||||
@@ -31,7 +30,6 @@ type DNSBlacklist struct {
|
||||
func (b *DNSBlacklist) setDefaults() {
|
||||
b.BlockMalicious = gosettings.DefaultPointer(b.BlockMalicious, true)
|
||||
b.BlockAds = gosettings.DefaultPointer(b.BlockAds, false)
|
||||
b.BlockSurveillance = gosettings.DefaultPointer(b.BlockSurveillance, true)
|
||||
}
|
||||
|
||||
var hostRegex = regexp.MustCompile(`^([a-zA-Z0-9]|[a-zA-Z0-9_][a-zA-Z0-9\-_]{0,61}[a-zA-Z0-9_])(\.([a-zA-Z0-9]|[a-zA-Z0-9_][a-zA-Z0-9\-_]{0,61}[a-zA-Z0-9]))*$`) //nolint:lll
|
||||
@@ -65,7 +63,6 @@ func (b DNSBlacklist) copy() (copied DNSBlacklist) {
|
||||
return DNSBlacklist{
|
||||
BlockMalicious: gosettings.CopyPointer(b.BlockMalicious),
|
||||
BlockAds: gosettings.CopyPointer(b.BlockAds),
|
||||
BlockSurveillance: gosettings.CopyPointer(b.BlockSurveillance),
|
||||
AllowedHosts: gosettings.CopySlice(b.AllowedHosts),
|
||||
AddBlockedHosts: gosettings.CopySlice(b.AddBlockedHosts),
|
||||
AddBlockedIPs: gosettings.CopySlice(b.AddBlockedIPs),
|
||||
@@ -77,7 +74,6 @@ func (b DNSBlacklist) copy() (copied DNSBlacklist) {
|
||||
func (b *DNSBlacklist) overrideWith(other DNSBlacklist) {
|
||||
b.BlockMalicious = gosettings.OverrideWithPointer(b.BlockMalicious, other.BlockMalicious)
|
||||
b.BlockAds = gosettings.OverrideWithPointer(b.BlockAds, other.BlockAds)
|
||||
b.BlockSurveillance = gosettings.OverrideWithPointer(b.BlockSurveillance, other.BlockSurveillance)
|
||||
b.AllowedHosts = gosettings.OverrideWithSlice(b.AllowedHosts, other.AllowedHosts)
|
||||
b.AddBlockedHosts = gosettings.OverrideWithSlice(b.AddBlockedHosts, other.AddBlockedHosts)
|
||||
b.AddBlockedIPs = gosettings.OverrideWithSlice(b.AddBlockedIPs, other.AddBlockedIPs)
|
||||
@@ -93,7 +89,6 @@ func (b DNSBlacklist) ToBlockBuilderSettings(client *http.Client) (
|
||||
Client: client,
|
||||
BlockMalicious: b.BlockMalicious,
|
||||
BlockAds: b.BlockAds,
|
||||
BlockSurveillance: b.BlockSurveillance,
|
||||
AllowedHosts: b.AllowedHosts,
|
||||
AddBlockedHosts: b.AddBlockedHosts,
|
||||
AddBlockedIPs: b.AddBlockedIPs,
|
||||
@@ -110,7 +105,6 @@ func (b DNSBlacklist) toLinesNode() (node *gotree.Node) {
|
||||
|
||||
node.Appendf("Block malicious: %s", gosettings.BoolToYesNo(b.BlockMalicious))
|
||||
node.Appendf("Block ads: %s", gosettings.BoolToYesNo(b.BlockAds))
|
||||
node.Appendf("Block surveillance: %s", gosettings.BoolToYesNo(b.BlockSurveillance))
|
||||
|
||||
if len(b.AllowedHosts) > 0 {
|
||||
allowedHostsNode := node.Append("Allowed hosts:")
|
||||
@@ -156,12 +150,6 @@ func (b *DNSBlacklist) read(r *reader.Reader) (err error) {
|
||||
return err
|
||||
}
|
||||
|
||||
b.BlockSurveillance, err = r.BoolPtr("BLOCK_SURVEILLANCE",
|
||||
reader.RetroKeys("BLOCK_NSA"))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
b.BlockAds, err = r.BoolPtr("BLOCK_ADS")
|
||||
if err != nil {
|
||||
return err
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
package settings
|
||||
|
||||
import gomock "github.com/golang/mock/gomock"
|
||||
import gomock "go.uber.org/mock/gomock"
|
||||
|
||||
type sourceKeyValue struct {
|
||||
key string
|
||||
|
||||
@@ -1,5 +1,10 @@
|
||||
// Code generated by MockGen. DO NOT EDIT.
|
||||
// Source: github.com/qdm12/gosettings/reader (interfaces: Source)
|
||||
//
|
||||
// Generated by this command:
|
||||
//
|
||||
// mockgen -destination=mocks_reader_test.go -package=settings github.com/qdm12/gosettings/reader Source
|
||||
//
|
||||
|
||||
// Package settings is a generated GoMock package.
|
||||
package settings
|
||||
@@ -7,13 +12,14 @@ package settings
|
||||
import (
|
||||
reflect "reflect"
|
||||
|
||||
gomock "github.com/golang/mock/gomock"
|
||||
gomock "go.uber.org/mock/gomock"
|
||||
)
|
||||
|
||||
// MockSource is a mock of Source interface.
|
||||
type MockSource struct {
|
||||
ctrl *gomock.Controller
|
||||
recorder *MockSourceMockRecorder
|
||||
isgomock struct{}
|
||||
}
|
||||
|
||||
// MockSourceMockRecorder is the mock recorder for MockSource.
|
||||
@@ -34,32 +40,32 @@ func (m *MockSource) EXPECT() *MockSourceMockRecorder {
|
||||
}
|
||||
|
||||
// Get mocks base method.
|
||||
func (m *MockSource) Get(arg0 string) (string, bool) {
|
||||
func (m *MockSource) Get(key string) (string, bool) {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "Get", arg0)
|
||||
ret := m.ctrl.Call(m, "Get", key)
|
||||
ret0, _ := ret[0].(string)
|
||||
ret1, _ := ret[1].(bool)
|
||||
return ret0, ret1
|
||||
}
|
||||
|
||||
// Get indicates an expected call of Get.
|
||||
func (mr *MockSourceMockRecorder) Get(arg0 interface{}) *gomock.Call {
|
||||
func (mr *MockSourceMockRecorder) Get(key any) *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Get", reflect.TypeOf((*MockSource)(nil).Get), arg0)
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Get", reflect.TypeOf((*MockSource)(nil).Get), key)
|
||||
}
|
||||
|
||||
// KeyTransform mocks base method.
|
||||
func (m *MockSource) KeyTransform(arg0 string) string {
|
||||
func (m *MockSource) KeyTransform(key string) string {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "KeyTransform", arg0)
|
||||
ret := m.ctrl.Call(m, "KeyTransform", key)
|
||||
ret0, _ := ret[0].(string)
|
||||
return ret0
|
||||
}
|
||||
|
||||
// KeyTransform indicates an expected call of KeyTransform.
|
||||
func (mr *MockSourceMockRecorder) KeyTransform(arg0 interface{}) *gomock.Call {
|
||||
func (mr *MockSourceMockRecorder) KeyTransform(key any) *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "KeyTransform", reflect.TypeOf((*MockSource)(nil).KeyTransform), arg0)
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "KeyTransform", reflect.TypeOf((*MockSource)(nil).KeyTransform), key)
|
||||
}
|
||||
|
||||
// String mocks base method.
|
||||
|
||||
@@ -1,5 +1,10 @@
|
||||
// Code generated by MockGen. DO NOT EDIT.
|
||||
// Source: github.com/qdm12/gluetun/internal/configuration/settings (interfaces: Warner)
|
||||
//
|
||||
// Generated by this command:
|
||||
//
|
||||
// mockgen -destination=mocks_test.go -package=settings . Warner
|
||||
//
|
||||
|
||||
// Package settings is a generated GoMock package.
|
||||
package settings
|
||||
@@ -7,13 +12,14 @@ package settings
|
||||
import (
|
||||
reflect "reflect"
|
||||
|
||||
gomock "github.com/golang/mock/gomock"
|
||||
gomock "go.uber.org/mock/gomock"
|
||||
)
|
||||
|
||||
// MockWarner is a mock of Warner interface.
|
||||
type MockWarner struct {
|
||||
ctrl *gomock.Controller
|
||||
recorder *MockWarnerMockRecorder
|
||||
isgomock struct{}
|
||||
}
|
||||
|
||||
// MockWarnerMockRecorder is the mock recorder for MockWarner.
|
||||
@@ -34,13 +40,13 @@ func (m *MockWarner) EXPECT() *MockWarnerMockRecorder {
|
||||
}
|
||||
|
||||
// Warn mocks base method.
|
||||
func (m *MockWarner) Warn(arg0 string) {
|
||||
func (m *MockWarner) Warn(message string) {
|
||||
m.ctrl.T.Helper()
|
||||
m.ctrl.Call(m, "Warn", arg0)
|
||||
m.ctrl.Call(m, "Warn", message)
|
||||
}
|
||||
|
||||
// Warn indicates an expected call of Warn.
|
||||
func (mr *MockWarnerMockRecorder) Warn(arg0 interface{}) *gomock.Call {
|
||||
func (mr *MockWarnerMockRecorder) Warn(message any) *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Warn", reflect.TypeOf((*MockWarner)(nil).Warn), arg0)
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Warn", reflect.TypeOf((*MockWarner)(nil).Warn), message)
|
||||
}
|
||||
|
||||
@@ -70,7 +70,7 @@ func (o OpenVPNSelection) validate(vpnProvider string) (err error) {
|
||||
switch vpnProvider {
|
||||
// no restriction on port
|
||||
case providers.Custom, providers.Cyberghost, providers.HideMyAss,
|
||||
providers.Ovpn, providers.Privatevpn, providers.Torguard:
|
||||
providers.Privatevpn, providers.Torguard:
|
||||
// no custom port allowed
|
||||
case providers.Expressvpn, providers.Fastestvpn,
|
||||
providers.Giganews, providers.Ipvanish,
|
||||
|
||||
@@ -95,7 +95,7 @@ func (p PortForwarding) Validate(vpnProvider string) (err error) {
|
||||
return errors.New("port forwarding password is empty")
|
||||
}
|
||||
case providers.Protonvpn:
|
||||
const maxPortsCount = 4
|
||||
const maxPortsCount = 5
|
||||
if p.PortsCount > maxPortsCount {
|
||||
return fmt.Errorf("ports count too high: %d > %d", p.PortsCount, maxPortsCount)
|
||||
}
|
||||
|
||||
@@ -49,7 +49,6 @@ func (p *Provider) validate(vpnType string, filterChoicesGetter FilterChoicesGet
|
||||
providers.Ivpn,
|
||||
providers.Mullvad,
|
||||
providers.Nordvpn,
|
||||
providers.Ovpn,
|
||||
providers.Protonvpn,
|
||||
providers.Surfshark,
|
||||
providers.Windscribe,
|
||||
|
||||
@@ -3,9 +3,9 @@ package settings
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/golang/mock/gomock"
|
||||
"github.com/qdm12/gosettings/reader"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"go.uber.org/mock/gomock"
|
||||
)
|
||||
|
||||
func Test_PublicIP_read(t *testing.T) {
|
||||
|
||||
@@ -63,9 +63,6 @@ type ServerSelection struct {
|
||||
// TorOnly is true if VPN servers without tor should
|
||||
// be filtered. This is used with ProtonVPN.
|
||||
TorOnly *bool `json:"tor_only"`
|
||||
// Dedicated is true if dedicated VPN servers should be chosen only.
|
||||
// This is used with OVPN.
|
||||
Dedicated *bool `json:"dedicated"`
|
||||
// OpenVPN contains settings to select OpenVPN servers
|
||||
// and the final connection.
|
||||
OpenVPN OpenVPNSelection `json:"openvpn"`
|
||||
@@ -275,8 +272,6 @@ func validateFeatureFilters(settings ServerSelection, vpnServiceProvider string)
|
||||
return errors.New("secure core only filter is not supported")
|
||||
case *settings.TorOnly && vpnServiceProvider != providers.Protonvpn:
|
||||
return errors.New("tor only filter is not supported")
|
||||
case *settings.Dedicated && vpnServiceProvider != providers.Ovpn:
|
||||
return errors.New("dedicated filter is not supported")
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
@@ -301,7 +296,6 @@ func (ss *ServerSelection) copy() (copied ServerSelection) {
|
||||
TorOnly: gosettings.CopyPointer(ss.TorOnly),
|
||||
PortForwardOnly: gosettings.CopyPointer(ss.PortForwardOnly),
|
||||
MultiHopOnly: gosettings.CopyPointer(ss.MultiHopOnly),
|
||||
Dedicated: gosettings.CopyPointer(ss.Dedicated),
|
||||
OpenVPN: ss.OpenVPN.copy(),
|
||||
Wireguard: ss.Wireguard.copy(),
|
||||
}
|
||||
@@ -325,7 +319,6 @@ func (ss *ServerSelection) overrideWith(other ServerSelection) {
|
||||
ss.TorOnly = gosettings.OverrideWithPointer(ss.TorOnly, other.TorOnly)
|
||||
ss.MultiHopOnly = gosettings.OverrideWithPointer(ss.MultiHopOnly, other.MultiHopOnly)
|
||||
ss.PortForwardOnly = gosettings.OverrideWithPointer(ss.PortForwardOnly, other.PortForwardOnly)
|
||||
ss.Dedicated = gosettings.OverrideWithPointer(ss.Dedicated, other.Dedicated)
|
||||
ss.OpenVPN.overrideWith(other.OpenVPN)
|
||||
ss.Wireguard.overrideWith(other.Wireguard)
|
||||
}
|
||||
@@ -342,7 +335,6 @@ func (ss *ServerSelection) setDefaults(vpnProvider string, portForwardingEnabled
|
||||
defaultPortForwardOnly := portForwardingEnabled &&
|
||||
helpers.IsOneOf(vpnProvider, providers.PrivateInternetAccess, providers.Protonvpn)
|
||||
ss.PortForwardOnly = gosettings.DefaultPointer(ss.PortForwardOnly, defaultPortForwardOnly)
|
||||
ss.Dedicated = gosettings.DefaultPointer(ss.Dedicated, false)
|
||||
ss.OpenVPN.setDefaults(vpnProvider)
|
||||
ss.Wireguard.setDefaults()
|
||||
}
|
||||
@@ -418,10 +410,6 @@ func (ss ServerSelection) toLinesNode() (node *gotree.Node) {
|
||||
node.Appendf("Multi-hop only servers: yes")
|
||||
}
|
||||
|
||||
if *ss.Dedicated {
|
||||
node.Appendf("Dedicated servers: yes")
|
||||
}
|
||||
|
||||
if *ss.PortForwardOnly {
|
||||
node.Appendf("Port forwarding only servers: yes")
|
||||
}
|
||||
@@ -513,12 +501,6 @@ func (ss *ServerSelection) read(r *reader.Reader,
|
||||
return err
|
||||
}
|
||||
|
||||
// Ovpn only
|
||||
ss.Dedicated, err = r.BoolPtr("SERVER_DEDICATED")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = ss.OpenVPN.read(r)
|
||||
if err != nil {
|
||||
return err
|
||||
|
||||
@@ -59,8 +59,7 @@ func Test_Settings_String(t *testing.T) {
|
||||
| ├── Update period: every 24h0m0s
|
||||
| └── DNS filtering settings:
|
||||
| ├── Block malicious: yes
|
||||
| ├── Block ads: no
|
||||
| └── Block surveillance: yes
|
||||
| └── Block ads: no
|
||||
├── Firewall settings:
|
||||
| ├── Enabled: yes
|
||||
| └── Iptables settings:
|
||||
|
||||
@@ -5,7 +5,6 @@ import (
|
||||
"fmt"
|
||||
"net/netip"
|
||||
|
||||
"github.com/qdm12/gluetun/internal/configuration/settings/helpers"
|
||||
"github.com/qdm12/gluetun/internal/constants/providers"
|
||||
"github.com/qdm12/gosettings"
|
||||
"github.com/qdm12/gosettings/reader"
|
||||
@@ -23,7 +22,7 @@ type WireguardSelection struct {
|
||||
// It can never be the zero value in the internal state.
|
||||
EndpointIP netip.Addr `json:"endpoint_ip"`
|
||||
// EndpointPort is a the server port to use for the VPN server.
|
||||
// It is optional for VPN providers IVPN, Mullvad, Ovpn, Surfshark
|
||||
// It is optional for VPN providers IVPN, Mullvad, Surfshark
|
||||
// and Windscribe, and compulsory for the others.
|
||||
// When optional, it can be set to 0 to indicate not use
|
||||
// a custom endpoint port. It cannot be nil in the internal
|
||||
@@ -41,9 +40,8 @@ func (w WireguardSelection) validate(vpnProvider string) (err error) {
|
||||
// Validate EndpointIP
|
||||
switch vpnProvider {
|
||||
case providers.Airvpn, providers.Fastestvpn, providers.Ivpn,
|
||||
providers.Mullvad, providers.Nordvpn, providers.Ovpn,
|
||||
providers.Protonvpn, providers.Surfshark,
|
||||
providers.Windscribe:
|
||||
providers.Mullvad, providers.Nordvpn, providers.Protonvpn,
|
||||
providers.Surfshark, providers.Windscribe:
|
||||
// endpoint IP addresses are baked in
|
||||
case providers.Custom:
|
||||
if !w.EndpointIP.IsValid() || w.EndpointIP.IsUnspecified() {
|
||||
@@ -65,16 +63,12 @@ func (w WireguardSelection) validate(vpnProvider string) (err error) {
|
||||
if *w.EndpointPort != 0 {
|
||||
return errors.New("endpoint port is set")
|
||||
}
|
||||
case providers.Airvpn, providers.Ivpn, providers.Mullvad,
|
||||
providers.Ovpn, providers.Windscribe:
|
||||
case providers.Airvpn, providers.Ivpn, providers.Mullvad, providers.Windscribe:
|
||||
// EndpointPort is optional and can be 0
|
||||
if *w.EndpointPort == 0 {
|
||||
break // no custom endpoint port set
|
||||
}
|
||||
if helpers.IsOneOf(vpnProvider,
|
||||
providers.Mullvad,
|
||||
providers.Ovpn,
|
||||
) {
|
||||
if vpnProvider == providers.Mullvad {
|
||||
break // no restriction on custom endpoint port value
|
||||
}
|
||||
var allowed []uint16
|
||||
@@ -98,7 +92,7 @@ func (w WireguardSelection) validate(vpnProvider string) (err error) {
|
||||
// Validate PublicKey
|
||||
switch vpnProvider {
|
||||
case providers.Fastestvpn, providers.Ivpn, providers.Mullvad,
|
||||
providers.Ovpn, providers.Surfshark, providers.Windscribe:
|
||||
providers.Surfshark, providers.Windscribe:
|
||||
// public keys are baked in
|
||||
case providers.Custom:
|
||||
if w.PublicKey == "" {
|
||||
|
||||
@@ -15,7 +15,6 @@ const (
|
||||
Ivpn = "ivpn"
|
||||
Mullvad = "mullvad"
|
||||
Nordvpn = "nordvpn"
|
||||
Ovpn = "ovpn"
|
||||
Perfectprivacy = "perfect privacy"
|
||||
Privado = "privado"
|
||||
PrivateInternetAccess = "private internet access"
|
||||
@@ -44,7 +43,6 @@ func All() []string {
|
||||
Ivpn,
|
||||
Mullvad,
|
||||
Nordvpn,
|
||||
Ovpn,
|
||||
Perfectprivacy,
|
||||
Privado,
|
||||
PrivateInternetAccess,
|
||||
|
||||
@@ -28,6 +28,8 @@ type firewallImpl interface { //nolint:interfacebloat
|
||||
AcceptIpv6MulticastOutput(ctx context.Context, intf string) error
|
||||
AcceptOutput(ctx context.Context, protocol, intf string,
|
||||
ip netip.Addr, port uint16, remove bool) error
|
||||
AcceptOutputFromIPPortToIPPort(ctx context.Context, protocol, intf string,
|
||||
source, destination netip.AddrPort, 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
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
package iptables
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"context"
|
||||
"fmt"
|
||||
"os/exec"
|
||||
@@ -97,25 +96,24 @@ func saveData(ctx context.Context, binary string) (data string, err error) {
|
||||
}
|
||||
return "", fmt.Errorf("running %s-save: %w", binary, err)
|
||||
}
|
||||
err = checkData(string(output))
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("checking saved data: %w", err)
|
||||
}
|
||||
return string(output), nil
|
||||
return filterData(output)
|
||||
}
|
||||
|
||||
func checkData(data string) error {
|
||||
scanner := bufio.NewScanner(strings.NewReader(data))
|
||||
i := 0
|
||||
for scanner.Scan() {
|
||||
line := scanner.Text()
|
||||
if strings.HasPrefix(line, "[unsupported") {
|
||||
return fmt.Errorf("unsupported revision marker found in line %d: %s", i+1, line)
|
||||
func filterData(cmdOutput []byte) (filtered string, err error) {
|
||||
lines := strings.Split(string(cmdOutput), "\n")
|
||||
filteredLines := make([]string, 0, len(lines))
|
||||
for _, line := range lines {
|
||||
switch {
|
||||
case strings.HasPrefix(line, ":DOCKER_OUTPUT"),
|
||||
strings.HasPrefix(line, ":DOCKER_POSTROUTING"),
|
||||
strings.HasPrefix(line, "-A DOCKER_OUTPUT"),
|
||||
strings.HasPrefix(line, "-A DOCKER_POSTROUTING"):
|
||||
// Do not touch (aka save and restore) NAT rules added by Docker
|
||||
continue
|
||||
case strings.Contains(line, "[unsupported revision]"):
|
||||
return "", fmt.Errorf("mismatch container iptables-save and kernel: %s", line)
|
||||
}
|
||||
i++
|
||||
filteredLines = append(filteredLines, line)
|
||||
}
|
||||
if scanner.Err() != nil {
|
||||
return fmt.Errorf("scanning data: %w", scanner.Err())
|
||||
}
|
||||
return nil
|
||||
return strings.Join(filteredLines, "\n"), nil
|
||||
}
|
||||
|
||||
@@ -5,7 +5,7 @@ import (
|
||||
"os/exec"
|
||||
"regexp"
|
||||
|
||||
"github.com/golang/mock/gomock"
|
||||
"go.uber.org/mock/gomock"
|
||||
)
|
||||
|
||||
var _ gomock.Matcher = (*cmdMatcher)(nil)
|
||||
|
||||
@@ -5,8 +5,8 @@ import (
|
||||
"errors"
|
||||
"testing"
|
||||
|
||||
"github.com/golang/mock/gomock"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"go.uber.org/mock/gomock"
|
||||
)
|
||||
|
||||
func Test_isDeleteMatchInstruction(t *testing.T) {
|
||||
|
||||
@@ -2,6 +2,7 @@ package iptables
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/netip"
|
||||
@@ -177,6 +178,29 @@ func (c *Config) AcceptOutput(ctx context.Context,
|
||||
return c.runIP6tablesInstruction(ctx, instruction)
|
||||
}
|
||||
|
||||
func (c *Config) AcceptOutputFromIPPortToIPPort(ctx context.Context,
|
||||
protocol, intf string, source, destination netip.AddrPort, remove bool,
|
||||
) error {
|
||||
if source.Addr().BitLen() != destination.Addr().BitLen() {
|
||||
return errors.New("source and destination address families do not match")
|
||||
}
|
||||
|
||||
interfaceFlag := "-o " + intf
|
||||
if intf == "*" { // all interfaces
|
||||
interfaceFlag = ""
|
||||
}
|
||||
|
||||
instruction := fmt.Sprintf("%s OUTPUT %s -s %s -d %s -p %s -m %s --sport %d --dport %d -j ACCEPT",
|
||||
appendOrDelete(remove), interfaceFlag, source.Addr(), destination.Addr(),
|
||||
protocol, protocol, source.Port(), destination.Port())
|
||||
if destination.Addr().Is4() {
|
||||
return c.runIptablesInstruction(ctx, instruction)
|
||||
} else if c.ip6Tables == "" {
|
||||
return fmt.Errorf("accept output from %s to %s: %s", source, destination, needIP6Tables)
|
||||
}
|
||||
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.
|
||||
@@ -278,7 +302,6 @@ func (c *Config) RedirectPort(ctx context.Context, intf string,
|
||||
appendOrDelete(remove), interfaceFlag, destinationPort),
|
||||
})
|
||||
if err != nil {
|
||||
restore(ctx) // just in case
|
||||
errMessage := err.Error()
|
||||
if strings.Contains(errMessage, "can't initialize ip6tables table `nat': Table does not exist") {
|
||||
if !remove {
|
||||
@@ -286,6 +309,7 @@ func (c *Config) RedirectPort(ctx context.Context, intf string,
|
||||
}
|
||||
return nil
|
||||
}
|
||||
restore(ctx)
|
||||
return fmt.Errorf("redirecting IPv6 source port %d to destination port %d on interface %s: %w",
|
||||
sourcePort, destinationPort, intf, err)
|
||||
}
|
||||
|
||||
@@ -1,5 +1,10 @@
|
||||
// Code generated by MockGen. DO NOT EDIT.
|
||||
// Source: github.com/qdm12/gluetun/internal/firewall/iptables (interfaces: CmdRunner,Logger)
|
||||
//
|
||||
// Generated by this command:
|
||||
//
|
||||
// mockgen -destination=mocks_test.go -package iptables . CmdRunner,Logger
|
||||
//
|
||||
|
||||
// Package iptables is a generated GoMock package.
|
||||
package iptables
|
||||
@@ -8,13 +13,14 @@ import (
|
||||
exec "os/exec"
|
||||
reflect "reflect"
|
||||
|
||||
gomock "github.com/golang/mock/gomock"
|
||||
gomock "go.uber.org/mock/gomock"
|
||||
)
|
||||
|
||||
// MockCmdRunner is a mock of CmdRunner interface.
|
||||
type MockCmdRunner struct {
|
||||
ctrl *gomock.Controller
|
||||
recorder *MockCmdRunnerMockRecorder
|
||||
isgomock struct{}
|
||||
}
|
||||
|
||||
// MockCmdRunnerMockRecorder is the mock recorder for MockCmdRunner.
|
||||
@@ -35,24 +41,25 @@ func (m *MockCmdRunner) EXPECT() *MockCmdRunnerMockRecorder {
|
||||
}
|
||||
|
||||
// Run mocks base method.
|
||||
func (m *MockCmdRunner) Run(arg0 *exec.Cmd) (string, error) {
|
||||
func (m *MockCmdRunner) Run(cmd *exec.Cmd) (string, error) {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "Run", arg0)
|
||||
ret := m.ctrl.Call(m, "Run", cmd)
|
||||
ret0, _ := ret[0].(string)
|
||||
ret1, _ := ret[1].(error)
|
||||
return ret0, ret1
|
||||
}
|
||||
|
||||
// Run indicates an expected call of Run.
|
||||
func (mr *MockCmdRunnerMockRecorder) Run(arg0 interface{}) *gomock.Call {
|
||||
func (mr *MockCmdRunnerMockRecorder) Run(cmd any) *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Run", reflect.TypeOf((*MockCmdRunner)(nil).Run), arg0)
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Run", reflect.TypeOf((*MockCmdRunner)(nil).Run), cmd)
|
||||
}
|
||||
|
||||
// MockLogger is a mock of Logger interface.
|
||||
type MockLogger struct {
|
||||
ctrl *gomock.Controller
|
||||
recorder *MockLoggerMockRecorder
|
||||
isgomock struct{}
|
||||
}
|
||||
|
||||
// MockLoggerMockRecorder is the mock recorder for MockLogger.
|
||||
@@ -73,25 +80,25 @@ func (m *MockLogger) EXPECT() *MockLoggerMockRecorder {
|
||||
}
|
||||
|
||||
// Debug mocks base method.
|
||||
func (m *MockLogger) Debug(arg0 string) {
|
||||
func (m *MockLogger) Debug(s string) {
|
||||
m.ctrl.T.Helper()
|
||||
m.ctrl.Call(m, "Debug", arg0)
|
||||
m.ctrl.Call(m, "Debug", s)
|
||||
}
|
||||
|
||||
// Debug indicates an expected call of Debug.
|
||||
func (mr *MockLoggerMockRecorder) Debug(arg0 interface{}) *gomock.Call {
|
||||
func (mr *MockLoggerMockRecorder) Debug(s any) *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
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), s)
|
||||
}
|
||||
|
||||
// Warn mocks base method.
|
||||
func (m *MockLogger) Warn(arg0 string) {
|
||||
func (m *MockLogger) Warn(s string) {
|
||||
m.ctrl.T.Helper()
|
||||
m.ctrl.Call(m, "Warn", arg0)
|
||||
m.ctrl.Call(m, "Warn", s)
|
||||
}
|
||||
|
||||
// Warn indicates an expected call of Warn.
|
||||
func (mr *MockLoggerMockRecorder) Warn(arg0 interface{}) *gomock.Call {
|
||||
func (mr *MockLoggerMockRecorder) Warn(s any) *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Warn", reflect.TypeOf((*MockLogger)(nil).Warn), arg0)
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Warn", reflect.TypeOf((*MockLogger)(nil).Warn), s)
|
||||
}
|
||||
|
||||
@@ -5,8 +5,8 @@ import (
|
||||
"errors"
|
||||
"testing"
|
||||
|
||||
"github.com/golang/mock/gomock"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"go.uber.org/mock/gomock"
|
||||
)
|
||||
|
||||
func newAppendTestRuleMatcher(path string) *cmdMatcher {
|
||||
|
||||
@@ -25,3 +25,10 @@ func (c *Config) AcceptOutput(ctx context.Context, protocol, intf string,
|
||||
) error {
|
||||
return c.impl.AcceptOutput(ctx, protocol, intf, ip, port, remove)
|
||||
}
|
||||
|
||||
func (c *Config) AcceptOutputFromIPPortToIPPort(ctx context.Context,
|
||||
protocol, intf string, source, destination netip.AddrPort, remove bool,
|
||||
) error {
|
||||
return c.impl.AcceptOutputFromIPPortToIPPort(ctx, protocol, intf,
|
||||
source, destination, remove)
|
||||
}
|
||||
|
||||
@@ -3,7 +3,7 @@ package httpserver
|
||||
import (
|
||||
"regexp"
|
||||
|
||||
gomock "github.com/golang/mock/gomock"
|
||||
gomock "go.uber.org/mock/gomock"
|
||||
)
|
||||
|
||||
var _ Logger = (*testLogger)(nil)
|
||||
|
||||
@@ -1,5 +1,10 @@
|
||||
// Code generated by MockGen. DO NOT EDIT.
|
||||
// Source: github.com/qdm12/gluetun/internal/httpserver (interfaces: Logger)
|
||||
//
|
||||
// Generated by this command:
|
||||
//
|
||||
// mockgen -destination=logger_mock_test.go -package httpserver . Logger
|
||||
//
|
||||
|
||||
// Package httpserver is a generated GoMock package.
|
||||
package httpserver
|
||||
@@ -7,13 +12,14 @@ package httpserver
|
||||
import (
|
||||
reflect "reflect"
|
||||
|
||||
gomock "github.com/golang/mock/gomock"
|
||||
gomock "go.uber.org/mock/gomock"
|
||||
)
|
||||
|
||||
// MockLogger is a mock of Logger interface.
|
||||
type MockLogger struct {
|
||||
ctrl *gomock.Controller
|
||||
recorder *MockLoggerMockRecorder
|
||||
isgomock struct{}
|
||||
}
|
||||
|
||||
// MockLoggerMockRecorder is the mock recorder for MockLogger.
|
||||
@@ -34,37 +40,37 @@ func (m *MockLogger) EXPECT() *MockLoggerMockRecorder {
|
||||
}
|
||||
|
||||
// Error mocks base method.
|
||||
func (m *MockLogger) Error(arg0 string) {
|
||||
func (m *MockLogger) Error(msg string) {
|
||||
m.ctrl.T.Helper()
|
||||
m.ctrl.Call(m, "Error", arg0)
|
||||
m.ctrl.Call(m, "Error", msg)
|
||||
}
|
||||
|
||||
// Error indicates an expected call of Error.
|
||||
func (mr *MockLoggerMockRecorder) Error(arg0 interface{}) *gomock.Call {
|
||||
func (mr *MockLoggerMockRecorder) Error(msg any) *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Error", reflect.TypeOf((*MockLogger)(nil).Error), arg0)
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Error", reflect.TypeOf((*MockLogger)(nil).Error), msg)
|
||||
}
|
||||
|
||||
// Info mocks base method.
|
||||
func (m *MockLogger) Info(arg0 string) {
|
||||
func (m *MockLogger) Info(msg string) {
|
||||
m.ctrl.T.Helper()
|
||||
m.ctrl.Call(m, "Info", arg0)
|
||||
m.ctrl.Call(m, "Info", msg)
|
||||
}
|
||||
|
||||
// Info indicates an expected call of Info.
|
||||
func (mr *MockLoggerMockRecorder) Info(arg0 interface{}) *gomock.Call {
|
||||
func (mr *MockLoggerMockRecorder) Info(msg any) *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Info", reflect.TypeOf((*MockLogger)(nil).Info), arg0)
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Info", reflect.TypeOf((*MockLogger)(nil).Info), msg)
|
||||
}
|
||||
|
||||
// Warn mocks base method.
|
||||
func (m *MockLogger) Warn(arg0 string) {
|
||||
func (m *MockLogger) Warn(msg string) {
|
||||
m.ctrl.T.Helper()
|
||||
m.ctrl.Call(m, "Warn", arg0)
|
||||
m.ctrl.Call(m, "Warn", msg)
|
||||
}
|
||||
|
||||
// Warn indicates an expected call of Warn.
|
||||
func (mr *MockLoggerMockRecorder) Warn(arg0 interface{}) *gomock.Call {
|
||||
func (mr *MockLoggerMockRecorder) Warn(msg any) *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Warn", reflect.TypeOf((*MockLogger)(nil).Warn), arg0)
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Warn", reflect.TypeOf((*MockLogger)(nil).Warn), msg)
|
||||
}
|
||||
|
||||
@@ -6,8 +6,8 @@ import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
gomock "github.com/golang/mock/gomock"
|
||||
"github.com/stretchr/testify/assert"
|
||||
gomock "go.uber.org/mock/gomock"
|
||||
)
|
||||
|
||||
func Test_Server_Run_success(t *testing.T) {
|
||||
|
||||
@@ -34,11 +34,8 @@ type Server struct {
|
||||
SecureCore bool `json:"secure_core,omitempty"`
|
||||
Tor bool `json:"tor,omitempty"`
|
||||
PortForward bool `json:"port_forward,omitempty"`
|
||||
Dedicated bool `json:"dedicated,omitempty"`
|
||||
Keep bool `json:"keep,omitempty"`
|
||||
IPs []netip.Addr `json:"ips,omitempty"`
|
||||
PortsTCP []uint16 `json:"ports_tcp,omitempty"`
|
||||
PortsUDP []uint16 `json:"ports_udp,omitempty"`
|
||||
}
|
||||
|
||||
func (s *Server) HasMinimumInformation() (err error) {
|
||||
|
||||
@@ -9,9 +9,9 @@ import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
gomock "github.com/golang/mock/gomock"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
gomock "go.uber.org/mock/gomock"
|
||||
)
|
||||
|
||||
func isIPv6LocallySupported() bool {
|
||||
|
||||
@@ -1,5 +1,10 @@
|
||||
// Code generated by MockGen. DO NOT EDIT.
|
||||
// Source: github.com/qdm12/gluetun/internal/netlink (interfaces: Firewall)
|
||||
//
|
||||
// Generated by this command:
|
||||
//
|
||||
// mockgen -destination=mocks_test.go -package=netlink . Firewall
|
||||
//
|
||||
|
||||
// Package netlink is a generated GoMock package.
|
||||
package netlink
|
||||
@@ -9,13 +14,14 @@ import (
|
||||
netip "net/netip"
|
||||
reflect "reflect"
|
||||
|
||||
gomock "github.com/golang/mock/gomock"
|
||||
gomock "go.uber.org/mock/gomock"
|
||||
)
|
||||
|
||||
// MockFirewall is a mock of Firewall interface.
|
||||
type MockFirewall struct {
|
||||
ctrl *gomock.Controller
|
||||
recorder *MockFirewallMockRecorder
|
||||
isgomock struct{}
|
||||
}
|
||||
|
||||
// MockFirewallMockRecorder is the mock recorder for MockFirewall.
|
||||
@@ -36,15 +42,15 @@ func (m *MockFirewall) EXPECT() *MockFirewallMockRecorder {
|
||||
}
|
||||
|
||||
// AcceptOutput mocks base method.
|
||||
func (m *MockFirewall) AcceptOutput(arg0 context.Context, arg1, arg2 string, arg3 netip.Addr, arg4 uint16, arg5 bool) error {
|
||||
func (m *MockFirewall) AcceptOutput(ctx context.Context, protocol, intf string, ip netip.Addr, port uint16, remove bool) error {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "AcceptOutput", arg0, arg1, arg2, arg3, arg4, arg5)
|
||||
ret := m.ctrl.Call(m, "AcceptOutput", ctx, protocol, intf, ip, port, remove)
|
||||
ret0, _ := ret[0].(error)
|
||||
return ret0
|
||||
}
|
||||
|
||||
// AcceptOutput indicates an expected call of AcceptOutput.
|
||||
func (mr *MockFirewallMockRecorder) AcceptOutput(arg0, arg1, arg2, arg3, arg4, arg5 interface{}) *gomock.Call {
|
||||
func (mr *MockFirewallMockRecorder) AcceptOutput(ctx, protocol, intf, ip, port, remove any) *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "AcceptOutput", reflect.TypeOf((*MockFirewall)(nil).AcceptOutput), arg0, arg1, arg2, arg3, arg4, arg5)
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "AcceptOutput", reflect.TypeOf((*MockFirewall)(nil).AcceptOutput), ctx, protocol, intf, ip, port, remove)
|
||||
}
|
||||
|
||||
@@ -29,19 +29,16 @@ func (r *Runner) Run(ctx context.Context, errCh chan<- error, ready chan<- struc
|
||||
return
|
||||
}
|
||||
|
||||
streamCtx, streamCancel := context.WithCancel(context.Background())
|
||||
streamDone := make(chan struct{})
|
||||
go streamLines(streamCtx, streamDone, r.logger,
|
||||
go streamLines(streamDone, r.logger,
|
||||
stdoutLines, stderrLines, ready)
|
||||
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
<-waitError
|
||||
streamCancel()
|
||||
<-streamDone
|
||||
errCh <- ctx.Err()
|
||||
case err := <-waitError:
|
||||
streamCancel()
|
||||
<-streamDone
|
||||
errCh <- err
|
||||
}
|
||||
|
||||
@@ -1,26 +1,37 @@
|
||||
package openvpn
|
||||
|
||||
import (
|
||||
"context"
|
||||
"strings"
|
||||
)
|
||||
|
||||
func streamLines(ctx context.Context, done chan<- struct{},
|
||||
func streamLines(done chan<- struct{},
|
||||
logger Logger, stdout, stderr <-chan string,
|
||||
tunnelReady chan<- struct{},
|
||||
) {
|
||||
defer close(done)
|
||||
|
||||
var line string
|
||||
|
||||
for {
|
||||
var line string
|
||||
var ok bool
|
||||
errLine := false
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
case line, ok = <-stdout:
|
||||
if ok {
|
||||
break
|
||||
}
|
||||
if stderr == nil {
|
||||
return
|
||||
case line = <-stdout:
|
||||
case line = <-stderr:
|
||||
}
|
||||
stdout = nil
|
||||
case line, ok = <-stderr:
|
||||
if ok {
|
||||
errLine = true
|
||||
break
|
||||
}
|
||||
if stdout == nil {
|
||||
return
|
||||
}
|
||||
stderr = nil
|
||||
}
|
||||
line, level := processLogLine(line)
|
||||
if line == "" {
|
||||
|
||||
@@ -1,5 +1,10 @@
|
||||
// Code generated by MockGen. DO NOT EDIT.
|
||||
// Source: github.com/qdm12/gluetun/internal/pmtud/tcp (interfaces: Logger)
|
||||
//
|
||||
// Generated by this command:
|
||||
//
|
||||
// mockgen -destination=mocks_test.go -package=tcp . Logger
|
||||
//
|
||||
|
||||
// Package tcp is a generated GoMock package.
|
||||
package tcp
|
||||
@@ -7,13 +12,14 @@ package tcp
|
||||
import (
|
||||
reflect "reflect"
|
||||
|
||||
gomock "github.com/golang/mock/gomock"
|
||||
gomock "go.uber.org/mock/gomock"
|
||||
)
|
||||
|
||||
// MockLogger is a mock of Logger interface.
|
||||
type MockLogger struct {
|
||||
ctrl *gomock.Controller
|
||||
recorder *MockLoggerMockRecorder
|
||||
isgomock struct{}
|
||||
}
|
||||
|
||||
// MockLoggerMockRecorder is the mock recorder for MockLogger.
|
||||
@@ -34,47 +40,47 @@ func (m *MockLogger) EXPECT() *MockLoggerMockRecorder {
|
||||
}
|
||||
|
||||
// Debug mocks base method.
|
||||
func (m *MockLogger) Debug(arg0 string) {
|
||||
func (m *MockLogger) Debug(msg string) {
|
||||
m.ctrl.T.Helper()
|
||||
m.ctrl.Call(m, "Debug", arg0)
|
||||
m.ctrl.Call(m, "Debug", msg)
|
||||
}
|
||||
|
||||
// Debug indicates an expected call of Debug.
|
||||
func (mr *MockLoggerMockRecorder) Debug(arg0 interface{}) *gomock.Call {
|
||||
func (mr *MockLoggerMockRecorder) Debug(msg any) *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
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), msg)
|
||||
}
|
||||
|
||||
// Debugf mocks base method.
|
||||
func (m *MockLogger) Debugf(arg0 string, arg1 ...interface{}) {
|
||||
func (m *MockLogger) Debugf(msg string, args ...any) {
|
||||
m.ctrl.T.Helper()
|
||||
varargs := []interface{}{arg0}
|
||||
for _, a := range arg1 {
|
||||
varargs := []any{msg}
|
||||
for _, a := range args {
|
||||
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 {
|
||||
func (mr *MockLoggerMockRecorder) Debugf(msg any, args ...any) *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
varargs := append([]interface{}{arg0}, arg1...)
|
||||
varargs := append([]any{msg}, args...)
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Debugf", reflect.TypeOf((*MockLogger)(nil).Debugf), varargs...)
|
||||
}
|
||||
|
||||
// Warnf mocks base method.
|
||||
func (m *MockLogger) Warnf(arg0 string, arg1 ...interface{}) {
|
||||
func (m *MockLogger) Warnf(msg string, args ...any) {
|
||||
m.ctrl.T.Helper()
|
||||
varargs := []interface{}{arg0}
|
||||
for _, a := range arg1 {
|
||||
varargs := []any{msg}
|
||||
for _, a := range args {
|
||||
varargs = append(varargs, a)
|
||||
}
|
||||
m.ctrl.Call(m, "Warnf", varargs...)
|
||||
}
|
||||
|
||||
// Warnf indicates an expected call of Warnf.
|
||||
func (mr *MockLoggerMockRecorder) Warnf(arg0 interface{}, arg1 ...interface{}) *gomock.Call {
|
||||
func (mr *MockLoggerMockRecorder) Warnf(msg any, args ...any) *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
varargs := append([]interface{}{arg0}, arg1...)
|
||||
varargs := append([]any{msg}, args...)
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Warnf", reflect.TypeOf((*MockLogger)(nil).Warnf), varargs...)
|
||||
}
|
||||
|
||||
@@ -8,11 +8,11 @@ import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
gomock "github.com/golang/mock/gomock"
|
||||
"github.com/qdm12/gluetun/internal/netlink"
|
||||
"github.com/qdm12/gluetun/internal/pmtud/constants"
|
||||
"github.com/qdm12/gluetun/internal/pmtud/ip"
|
||||
"github.com/stretchr/testify/require"
|
||||
gomock "go.uber.org/mock/gomock"
|
||||
)
|
||||
|
||||
func Test_runTest(t *testing.T) {
|
||||
|
||||
@@ -15,7 +15,11 @@ func runCommand(ctx context.Context, cmder Cmder, logger Logger,
|
||||
}
|
||||
portsString := strings.Join(portStrings, ",")
|
||||
commandString := strings.ReplaceAll(commandTemplate, "{{PORTS}}", portsString)
|
||||
commandString = strings.ReplaceAll(commandString, "{{PORT}}", portStrings[0])
|
||||
var firstPort string
|
||||
if len(portStrings) > 0 {
|
||||
firstPort = portStrings[0]
|
||||
}
|
||||
commandString = strings.ReplaceAll(commandString, "{{PORT}}", firstPort)
|
||||
commandString = strings.ReplaceAll(commandString, "{{VPN_INTERFACE}}", vpnInterface)
|
||||
return cmder.RunAndLog(ctx, commandString, logger)
|
||||
}
|
||||
|
||||
@@ -6,9 +6,9 @@ import (
|
||||
"context"
|
||||
"testing"
|
||||
|
||||
gomock "github.com/golang/mock/gomock"
|
||||
"github.com/qdm12/gluetun/internal/command"
|
||||
"github.com/stretchr/testify/require"
|
||||
gomock "go.uber.org/mock/gomock"
|
||||
)
|
||||
|
||||
func Test_Service_runCommand(t *testing.T) {
|
||||
|
||||
@@ -1,5 +1,10 @@
|
||||
// Code generated by MockGen. DO NOT EDIT.
|
||||
// Source: github.com/qdm12/gluetun/internal/portforward/service (interfaces: Logger)
|
||||
//
|
||||
// Generated by this command:
|
||||
//
|
||||
// mockgen -destination=mocks_test.go -package=service . Logger
|
||||
//
|
||||
|
||||
// Package service is a generated GoMock package.
|
||||
package service
|
||||
@@ -7,13 +12,14 @@ package service
|
||||
import (
|
||||
reflect "reflect"
|
||||
|
||||
gomock "github.com/golang/mock/gomock"
|
||||
gomock "go.uber.org/mock/gomock"
|
||||
)
|
||||
|
||||
// MockLogger is a mock of Logger interface.
|
||||
type MockLogger struct {
|
||||
ctrl *gomock.Controller
|
||||
recorder *MockLoggerMockRecorder
|
||||
isgomock struct{}
|
||||
}
|
||||
|
||||
// MockLoggerMockRecorder is the mock recorder for MockLogger.
|
||||
@@ -34,49 +40,49 @@ func (m *MockLogger) EXPECT() *MockLoggerMockRecorder {
|
||||
}
|
||||
|
||||
// Debug mocks base method.
|
||||
func (m *MockLogger) Debug(arg0 string) {
|
||||
func (m *MockLogger) Debug(s string) {
|
||||
m.ctrl.T.Helper()
|
||||
m.ctrl.Call(m, "Debug", arg0)
|
||||
m.ctrl.Call(m, "Debug", s)
|
||||
}
|
||||
|
||||
// Debug indicates an expected call of Debug.
|
||||
func (mr *MockLoggerMockRecorder) Debug(arg0 interface{}) *gomock.Call {
|
||||
func (mr *MockLoggerMockRecorder) Debug(s any) *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
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), s)
|
||||
}
|
||||
|
||||
// Error mocks base method.
|
||||
func (m *MockLogger) Error(arg0 string) {
|
||||
func (m *MockLogger) Error(s string) {
|
||||
m.ctrl.T.Helper()
|
||||
m.ctrl.Call(m, "Error", arg0)
|
||||
m.ctrl.Call(m, "Error", s)
|
||||
}
|
||||
|
||||
// Error indicates an expected call of Error.
|
||||
func (mr *MockLoggerMockRecorder) Error(arg0 interface{}) *gomock.Call {
|
||||
func (mr *MockLoggerMockRecorder) Error(s any) *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Error", reflect.TypeOf((*MockLogger)(nil).Error), arg0)
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Error", reflect.TypeOf((*MockLogger)(nil).Error), s)
|
||||
}
|
||||
|
||||
// Info mocks base method.
|
||||
func (m *MockLogger) Info(arg0 string) {
|
||||
func (m *MockLogger) Info(s string) {
|
||||
m.ctrl.T.Helper()
|
||||
m.ctrl.Call(m, "Info", arg0)
|
||||
m.ctrl.Call(m, "Info", s)
|
||||
}
|
||||
|
||||
// Info indicates an expected call of Info.
|
||||
func (mr *MockLoggerMockRecorder) Info(arg0 interface{}) *gomock.Call {
|
||||
func (mr *MockLoggerMockRecorder) Info(s any) *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Info", reflect.TypeOf((*MockLogger)(nil).Info), arg0)
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Info", reflect.TypeOf((*MockLogger)(nil).Info), s)
|
||||
}
|
||||
|
||||
// Warn mocks base method.
|
||||
func (m *MockLogger) Warn(arg0 string) {
|
||||
func (m *MockLogger) Warn(s string) {
|
||||
m.ctrl.T.Helper()
|
||||
m.ctrl.Call(m, "Warn", arg0)
|
||||
m.ctrl.Call(m, "Warn", s)
|
||||
}
|
||||
|
||||
// Warn indicates an expected call of Warn.
|
||||
func (mr *MockLoggerMockRecorder) Warn(arg0 interface{}) *gomock.Call {
|
||||
func (mr *MockLoggerMockRecorder) Warn(s any) *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Warn", reflect.TypeOf((*MockLogger)(nil).Warn), arg0)
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Warn", reflect.TypeOf((*MockLogger)(nil).Warn), s)
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@ package service
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"slices"
|
||||
@@ -59,6 +60,10 @@ func (s *Service) SetPortsForwarded(ctx context.Context, ports []uint16) (err er
|
||||
s.portMutex.Lock()
|
||||
defer s.portMutex.Unlock()
|
||||
|
||||
if s.settings.PortForwarder != nil {
|
||||
return errors.New("setting port forwarded at runtime is not supported with internally running port forwarding code")
|
||||
}
|
||||
|
||||
slices.Sort(ports)
|
||||
if slices.Equal(s.ports, ports) {
|
||||
return nil
|
||||
@@ -78,7 +83,5 @@ func (s *Service) SetPortsForwarded(ctx context.Context, ports []uint16) (err er
|
||||
return fmt.Errorf("handling new ports: %w", err)
|
||||
}
|
||||
|
||||
s.logger.Info("updated: " + portsToString(s.ports))
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -88,7 +88,7 @@ func (s *Settings) Validate(forStartup bool) (err error) {
|
||||
return errors.New("password not set")
|
||||
}
|
||||
case providers.Protonvpn:
|
||||
const maxPortsCount = 4
|
||||
const maxPortsCount = 5
|
||||
if s.PortsCount > maxPortsCount {
|
||||
return fmt.Errorf("ports count too high: %d > %d", s.PortsCount, maxPortsCount)
|
||||
}
|
||||
|
||||
@@ -92,13 +92,9 @@ func (s *Service) onNewPorts(ctx context.Context, internalToExternalPorts map[ui
|
||||
s.logger.Info(portPairsToString(internalToExternalPorts))
|
||||
|
||||
externalPorts := slices.Collect(maps.Values(internalToExternalPorts))
|
||||
autoRedirectionNeeded := false
|
||||
externalToInternalPorts := make(map[uint16]uint16, len(internalToExternalPorts))
|
||||
for internal, external := range internalToExternalPorts {
|
||||
externalToInternalPorts[external] = internal
|
||||
if internal != external {
|
||||
autoRedirectionNeeded = true
|
||||
}
|
||||
}
|
||||
slices.Sort(externalPorts)
|
||||
userRedirectionEnabled := !slices.Equal(s.settings.ListeningPorts, []uint16{0})
|
||||
@@ -109,23 +105,21 @@ func (s *Service) onNewPorts(ctx context.Context, internalToExternalPorts map[ui
|
||||
return fmt.Errorf("allowing port in firewall: %w", err)
|
||||
}
|
||||
|
||||
var sourcePort, destinationPort uint16
|
||||
var destinationPort uint16
|
||||
switch {
|
||||
case userRedirectionEnabled: // precedence over auto redirection
|
||||
sourcePort = externalToInternalPorts[port]
|
||||
destinationPort = s.settings.ListeningPorts[i]
|
||||
case autoRedirectionNeeded:
|
||||
sourcePort = externalToInternalPorts[port]
|
||||
case port != internalPort: // auto redirection needed, source and destination ports differ
|
||||
destinationPort = port
|
||||
default:
|
||||
// No redirection needed, source and destination ports are the same.
|
||||
continue
|
||||
}
|
||||
|
||||
err = s.portAllower.RedirectPort(ctx, s.settings.Interface, sourcePort, destinationPort)
|
||||
err = s.portAllower.RedirectPort(ctx, s.settings.Interface, internalPort, destinationPort)
|
||||
if err != nil {
|
||||
return fmt.Errorf("redirecting port %d to %d in firewall: %w",
|
||||
sourcePort, destinationPort, err)
|
||||
internalPort, destinationPort, err)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -3,7 +3,7 @@ package pprof
|
||||
import (
|
||||
"regexp"
|
||||
|
||||
gomock "github.com/golang/mock/gomock"
|
||||
gomock "go.uber.org/mock/gomock"
|
||||
)
|
||||
|
||||
func boolPtr(b bool) *bool { return &b }
|
||||
|
||||
@@ -1,5 +1,10 @@
|
||||
// Code generated by MockGen. DO NOT EDIT.
|
||||
// Source: github.com/qdm12/gluetun/internal/httpserver (interfaces: Logger)
|
||||
//
|
||||
// Generated by this command:
|
||||
//
|
||||
// mockgen -destination=logger_mock_test.go -package pprof github.com/qdm12/gluetun/internal/httpserver Logger
|
||||
//
|
||||
|
||||
// Package pprof is a generated GoMock package.
|
||||
package pprof
|
||||
@@ -7,13 +12,14 @@ package pprof
|
||||
import (
|
||||
reflect "reflect"
|
||||
|
||||
gomock "github.com/golang/mock/gomock"
|
||||
gomock "go.uber.org/mock/gomock"
|
||||
)
|
||||
|
||||
// MockLogger is a mock of Logger interface.
|
||||
type MockLogger struct {
|
||||
ctrl *gomock.Controller
|
||||
recorder *MockLoggerMockRecorder
|
||||
isgomock struct{}
|
||||
}
|
||||
|
||||
// MockLoggerMockRecorder is the mock recorder for MockLogger.
|
||||
@@ -34,37 +40,37 @@ func (m *MockLogger) EXPECT() *MockLoggerMockRecorder {
|
||||
}
|
||||
|
||||
// Error mocks base method.
|
||||
func (m *MockLogger) Error(arg0 string) {
|
||||
func (m *MockLogger) Error(msg string) {
|
||||
m.ctrl.T.Helper()
|
||||
m.ctrl.Call(m, "Error", arg0)
|
||||
m.ctrl.Call(m, "Error", msg)
|
||||
}
|
||||
|
||||
// Error indicates an expected call of Error.
|
||||
func (mr *MockLoggerMockRecorder) Error(arg0 interface{}) *gomock.Call {
|
||||
func (mr *MockLoggerMockRecorder) Error(msg any) *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Error", reflect.TypeOf((*MockLogger)(nil).Error), arg0)
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Error", reflect.TypeOf((*MockLogger)(nil).Error), msg)
|
||||
}
|
||||
|
||||
// Info mocks base method.
|
||||
func (m *MockLogger) Info(arg0 string) {
|
||||
func (m *MockLogger) Info(msg string) {
|
||||
m.ctrl.T.Helper()
|
||||
m.ctrl.Call(m, "Info", arg0)
|
||||
m.ctrl.Call(m, "Info", msg)
|
||||
}
|
||||
|
||||
// Info indicates an expected call of Info.
|
||||
func (mr *MockLoggerMockRecorder) Info(arg0 interface{}) *gomock.Call {
|
||||
func (mr *MockLoggerMockRecorder) Info(msg any) *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Info", reflect.TypeOf((*MockLogger)(nil).Info), arg0)
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Info", reflect.TypeOf((*MockLogger)(nil).Info), msg)
|
||||
}
|
||||
|
||||
// Warn mocks base method.
|
||||
func (m *MockLogger) Warn(arg0 string) {
|
||||
func (m *MockLogger) Warn(msg string) {
|
||||
m.ctrl.T.Helper()
|
||||
m.ctrl.Call(m, "Warn", arg0)
|
||||
m.ctrl.Call(m, "Warn", msg)
|
||||
}
|
||||
|
||||
// Warn indicates an expected call of Warn.
|
||||
func (mr *MockLoggerMockRecorder) Warn(arg0 interface{}) *gomock.Call {
|
||||
func (mr *MockLoggerMockRecorder) Warn(msg any) *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Warn", reflect.TypeOf((*MockLogger)(nil).Warn), arg0)
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Warn", reflect.TypeOf((*MockLogger)(nil).Warn), msg)
|
||||
}
|
||||
|
||||
@@ -7,10 +7,10 @@ import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/golang/mock/gomock"
|
||||
"github.com/qdm12/gluetun/internal/httpserver"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
"go.uber.org/mock/gomock"
|
||||
)
|
||||
|
||||
//go:generate mockgen -destination=logger_mock_test.go -package $GOPACKAGE github.com/qdm12/gluetun/internal/httpserver Logger
|
||||
|
||||
@@ -1,5 +1,10 @@
|
||||
// Code generated by MockGen. DO NOT EDIT.
|
||||
// Source: github.com/qdm12/gluetun/internal/provider/common (interfaces: ParallelResolver,Storage,Unzipper,Warner)
|
||||
//
|
||||
// Generated by this command:
|
||||
//
|
||||
// mockgen -destination=mocks.go -package common . ParallelResolver,Storage,Unzipper,Warner
|
||||
//
|
||||
|
||||
// Package common is a generated GoMock package.
|
||||
package common
|
||||
@@ -9,16 +14,17 @@ import (
|
||||
netip "net/netip"
|
||||
reflect "reflect"
|
||||
|
||||
gomock "github.com/golang/mock/gomock"
|
||||
settings "github.com/qdm12/gluetun/internal/configuration/settings"
|
||||
models "github.com/qdm12/gluetun/internal/models"
|
||||
resolver "github.com/qdm12/gluetun/internal/updater/resolver"
|
||||
gomock "go.uber.org/mock/gomock"
|
||||
)
|
||||
|
||||
// MockParallelResolver is a mock of ParallelResolver interface.
|
||||
type MockParallelResolver struct {
|
||||
ctrl *gomock.Controller
|
||||
recorder *MockParallelResolverMockRecorder
|
||||
isgomock struct{}
|
||||
}
|
||||
|
||||
// MockParallelResolverMockRecorder is the mock recorder for MockParallelResolver.
|
||||
@@ -39,9 +45,9 @@ func (m *MockParallelResolver) EXPECT() *MockParallelResolverMockRecorder {
|
||||
}
|
||||
|
||||
// Resolve mocks base method.
|
||||
func (m *MockParallelResolver) Resolve(arg0 context.Context, arg1 resolver.ParallelSettings) (map[string][]netip.Addr, []string, error) {
|
||||
func (m *MockParallelResolver) Resolve(ctx context.Context, arg1 resolver.ParallelSettings) (map[string][]netip.Addr, []string, error) {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "Resolve", arg0, arg1)
|
||||
ret := m.ctrl.Call(m, "Resolve", ctx, arg1)
|
||||
ret0, _ := ret[0].(map[string][]netip.Addr)
|
||||
ret1, _ := ret[1].([]string)
|
||||
ret2, _ := ret[2].(error)
|
||||
@@ -49,15 +55,16 @@ func (m *MockParallelResolver) Resolve(arg0 context.Context, arg1 resolver.Paral
|
||||
}
|
||||
|
||||
// Resolve indicates an expected call of Resolve.
|
||||
func (mr *MockParallelResolverMockRecorder) Resolve(arg0, arg1 interface{}) *gomock.Call {
|
||||
func (mr *MockParallelResolverMockRecorder) Resolve(ctx, arg1 any) *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Resolve", reflect.TypeOf((*MockParallelResolver)(nil).Resolve), arg0, arg1)
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Resolve", reflect.TypeOf((*MockParallelResolver)(nil).Resolve), ctx, arg1)
|
||||
}
|
||||
|
||||
// MockStorage is a mock of Storage interface.
|
||||
type MockStorage struct {
|
||||
ctrl *gomock.Controller
|
||||
recorder *MockStorageMockRecorder
|
||||
isgomock struct{}
|
||||
}
|
||||
|
||||
// MockStorageMockRecorder is the mock recorder for MockStorage.
|
||||
@@ -78,24 +85,25 @@ func (m *MockStorage) EXPECT() *MockStorageMockRecorder {
|
||||
}
|
||||
|
||||
// FilterServers mocks base method.
|
||||
func (m *MockStorage) FilterServers(arg0 string, arg1 settings.ServerSelection) ([]models.Server, error) {
|
||||
func (m *MockStorage) FilterServers(provider string, selection settings.ServerSelection) ([]models.Server, error) {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "FilterServers", arg0, arg1)
|
||||
ret := m.ctrl.Call(m, "FilterServers", provider, selection)
|
||||
ret0, _ := ret[0].([]models.Server)
|
||||
ret1, _ := ret[1].(error)
|
||||
return ret0, ret1
|
||||
}
|
||||
|
||||
// FilterServers indicates an expected call of FilterServers.
|
||||
func (mr *MockStorageMockRecorder) FilterServers(arg0, arg1 interface{}) *gomock.Call {
|
||||
func (mr *MockStorageMockRecorder) FilterServers(provider, selection any) *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "FilterServers", reflect.TypeOf((*MockStorage)(nil).FilterServers), arg0, arg1)
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "FilterServers", reflect.TypeOf((*MockStorage)(nil).FilterServers), provider, selection)
|
||||
}
|
||||
|
||||
// MockUnzipper is a mock of Unzipper interface.
|
||||
type MockUnzipper struct {
|
||||
ctrl *gomock.Controller
|
||||
recorder *MockUnzipperMockRecorder
|
||||
isgomock struct{}
|
||||
}
|
||||
|
||||
// MockUnzipperMockRecorder is the mock recorder for MockUnzipper.
|
||||
@@ -116,24 +124,25 @@ func (m *MockUnzipper) EXPECT() *MockUnzipperMockRecorder {
|
||||
}
|
||||
|
||||
// FetchAndExtract mocks base method.
|
||||
func (m *MockUnzipper) FetchAndExtract(arg0 context.Context, arg1 string) (map[string][]byte, error) {
|
||||
func (m *MockUnzipper) FetchAndExtract(ctx context.Context, url string) (map[string][]byte, error) {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "FetchAndExtract", arg0, arg1)
|
||||
ret := m.ctrl.Call(m, "FetchAndExtract", ctx, url)
|
||||
ret0, _ := ret[0].(map[string][]byte)
|
||||
ret1, _ := ret[1].(error)
|
||||
return ret0, ret1
|
||||
}
|
||||
|
||||
// FetchAndExtract indicates an expected call of FetchAndExtract.
|
||||
func (mr *MockUnzipperMockRecorder) FetchAndExtract(arg0, arg1 interface{}) *gomock.Call {
|
||||
func (mr *MockUnzipperMockRecorder) FetchAndExtract(ctx, url any) *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "FetchAndExtract", reflect.TypeOf((*MockUnzipper)(nil).FetchAndExtract), arg0, arg1)
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "FetchAndExtract", reflect.TypeOf((*MockUnzipper)(nil).FetchAndExtract), ctx, url)
|
||||
}
|
||||
|
||||
// MockWarner is a mock of Warner interface.
|
||||
type MockWarner struct {
|
||||
ctrl *gomock.Controller
|
||||
recorder *MockWarnerMockRecorder
|
||||
isgomock struct{}
|
||||
}
|
||||
|
||||
// MockWarnerMockRecorder is the mock recorder for MockWarner.
|
||||
@@ -154,13 +163,13 @@ func (m *MockWarner) EXPECT() *MockWarnerMockRecorder {
|
||||
}
|
||||
|
||||
// Warn mocks base method.
|
||||
func (m *MockWarner) Warn(arg0 string) {
|
||||
func (m *MockWarner) Warn(s string) {
|
||||
m.ctrl.T.Helper()
|
||||
m.ctrl.Call(m, "Warn", arg0)
|
||||
m.ctrl.Call(m, "Warn", s)
|
||||
}
|
||||
|
||||
// Warn indicates an expected call of Warn.
|
||||
func (mr *MockWarnerMockRecorder) Warn(arg0 interface{}) *gomock.Call {
|
||||
func (mr *MockWarnerMockRecorder) Warn(s any) *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Warn", reflect.TypeOf((*MockWarner)(nil).Warn), arg0)
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Warn", reflect.TypeOf((*MockWarner)(nil).Warn), s)
|
||||
}
|
||||
|
||||
@@ -6,6 +6,7 @@ import (
|
||||
"strings"
|
||||
|
||||
"github.com/qdm12/gluetun/internal/configuration/settings"
|
||||
"github.com/qdm12/gluetun/internal/constants"
|
||||
"github.com/qdm12/gluetun/internal/constants/openvpn"
|
||||
"github.com/qdm12/gluetun/internal/models"
|
||||
"github.com/qdm12/gluetun/internal/provider/utils"
|
||||
@@ -65,7 +66,11 @@ func modifyConfig(lines []string, connection models.Connection,
|
||||
}
|
||||
|
||||
// Add values
|
||||
modified = append(modified, "proto "+connection.Protocol)
|
||||
protocol := connection.Protocol
|
||||
if protocol == constants.TCP {
|
||||
protocol = "tcp-client"
|
||||
}
|
||||
modified = append(modified, "proto "+protocol)
|
||||
modified = append(modified, fmt.Sprintf("remote %s %d", connection.IP, connection.Port))
|
||||
modified = append(modified, "dev "+settings.Interface)
|
||||
modified = append(modified, "mute-replay-warnings")
|
||||
|
||||
@@ -5,7 +5,6 @@ import (
|
||||
"net/netip"
|
||||
"testing"
|
||||
|
||||
"github.com/golang/mock/gomock"
|
||||
"github.com/qdm12/gluetun/internal/configuration/settings"
|
||||
"github.com/qdm12/gluetun/internal/constants"
|
||||
"github.com/qdm12/gluetun/internal/constants/providers"
|
||||
@@ -13,6 +12,7 @@ import (
|
||||
"github.com/qdm12/gluetun/internal/models"
|
||||
"github.com/qdm12/gluetun/internal/provider/common"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"go.uber.org/mock/gomock"
|
||||
)
|
||||
|
||||
func Test_Provider_GetConnection(t *testing.T) {
|
||||
|
||||
@@ -7,13 +7,13 @@ import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/golang/mock/gomock"
|
||||
"github.com/qdm12/gluetun/internal/constants/vpn"
|
||||
"github.com/qdm12/gluetun/internal/models"
|
||||
"github.com/qdm12/gluetun/internal/provider/common"
|
||||
"github.com/qdm12/gluetun/internal/updater/resolver"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
"go.uber.org/mock/gomock"
|
||||
)
|
||||
|
||||
func Test_Updater_GetServers(t *testing.T) {
|
||||
|
||||
@@ -6,7 +6,6 @@ import (
|
||||
"net/netip"
|
||||
"testing"
|
||||
|
||||
"github.com/golang/mock/gomock"
|
||||
"github.com/qdm12/gluetun/internal/configuration/settings"
|
||||
"github.com/qdm12/gluetun/internal/constants"
|
||||
"github.com/qdm12/gluetun/internal/constants/providers"
|
||||
@@ -14,6 +13,7 @@ import (
|
||||
"github.com/qdm12/gluetun/internal/models"
|
||||
"github.com/qdm12/gluetun/internal/provider/common"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"go.uber.org/mock/gomock"
|
||||
)
|
||||
|
||||
func Test_Provider_GetConnection(t *testing.T) {
|
||||
|
||||
@@ -10,13 +10,13 @@ import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/golang/mock/gomock"
|
||||
"github.com/qdm12/gluetun/internal/constants/vpn"
|
||||
"github.com/qdm12/gluetun/internal/models"
|
||||
"github.com/qdm12/gluetun/internal/provider/common"
|
||||
"github.com/qdm12/gluetun/internal/updater/resolver"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
"go.uber.org/mock/gomock"
|
||||
)
|
||||
|
||||
func Test_Updater_GetServers(t *testing.T) {
|
||||
|
||||
@@ -6,7 +6,6 @@ import (
|
||||
"net/netip"
|
||||
"testing"
|
||||
|
||||
"github.com/golang/mock/gomock"
|
||||
"github.com/qdm12/gluetun/internal/configuration/settings"
|
||||
"github.com/qdm12/gluetun/internal/constants"
|
||||
"github.com/qdm12/gluetun/internal/constants/providers"
|
||||
@@ -14,6 +13,7 @@ import (
|
||||
"github.com/qdm12/gluetun/internal/models"
|
||||
"github.com/qdm12/gluetun/internal/provider/common"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"go.uber.org/mock/gomock"
|
||||
)
|
||||
|
||||
func Test_Provider_GetConnection(t *testing.T) {
|
||||
|
||||
@@ -1,15 +0,0 @@
|
||||
package ovpn
|
||||
|
||||
import (
|
||||
"github.com/qdm12/gluetun/internal/configuration/settings"
|
||||
"github.com/qdm12/gluetun/internal/models"
|
||||
"github.com/qdm12/gluetun/internal/provider/utils"
|
||||
)
|
||||
|
||||
func (p *Provider) GetConnection(selection settings.ServerSelection, ipv6Supported bool) (
|
||||
connection models.Connection, err error,
|
||||
) {
|
||||
defaults := utils.NewConnectionDefaults(443, 1194, 9929) //nolint:mnd
|
||||
return utils.GetConnection(p.Name(),
|
||||
p.storage, selection, defaults, ipv6Supported, p.connPicker)
|
||||
}
|
||||
@@ -1,126 +0,0 @@
|
||||
package ovpn
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"net/http"
|
||||
"net/netip"
|
||||
"testing"
|
||||
|
||||
"github.com/golang/mock/gomock"
|
||||
"github.com/qdm12/gluetun/internal/configuration/settings"
|
||||
"github.com/qdm12/gluetun/internal/constants"
|
||||
"github.com/qdm12/gluetun/internal/constants/providers"
|
||||
"github.com/qdm12/gluetun/internal/constants/vpn"
|
||||
"github.com/qdm12/gluetun/internal/models"
|
||||
"github.com/qdm12/gluetun/internal/provider/common"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func Test_Provider_GetConnection(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
const provider = providers.Ovpn
|
||||
|
||||
errTest := errors.New("test error")
|
||||
|
||||
testCases := map[string]struct {
|
||||
filteredServers []models.Server
|
||||
storageErr error
|
||||
selection settings.ServerSelection
|
||||
ipv6Supported bool
|
||||
connection models.Connection
|
||||
errWrapped error
|
||||
errMessage string
|
||||
}{
|
||||
"error": {
|
||||
storageErr: errTest,
|
||||
errWrapped: errTest,
|
||||
errMessage: "filtering servers: test error",
|
||||
},
|
||||
"default_openvpn_tcp_port": {
|
||||
filteredServers: []models.Server{
|
||||
{IPs: []netip.Addr{netip.AddrFrom4([4]byte{1, 1, 1, 1})}},
|
||||
},
|
||||
selection: settings.ServerSelection{
|
||||
OpenVPN: settings.OpenVPNSelection{
|
||||
Protocol: constants.TCP,
|
||||
},
|
||||
}.WithDefaults(provider),
|
||||
connection: models.Connection{
|
||||
Type: vpn.OpenVPN,
|
||||
IP: netip.AddrFrom4([4]byte{1, 1, 1, 1}),
|
||||
Port: 443,
|
||||
Protocol: constants.TCP,
|
||||
},
|
||||
},
|
||||
"default_openvpn_udp_port": {
|
||||
filteredServers: []models.Server{
|
||||
{IPs: []netip.Addr{netip.AddrFrom4([4]byte{1, 1, 1, 1})}},
|
||||
},
|
||||
selection: settings.ServerSelection{
|
||||
OpenVPN: settings.OpenVPNSelection{
|
||||
Protocol: constants.UDP,
|
||||
},
|
||||
}.WithDefaults(provider),
|
||||
connection: models.Connection{
|
||||
Type: vpn.OpenVPN,
|
||||
IP: netip.AddrFrom4([4]byte{1, 1, 1, 1}),
|
||||
Port: 1194,
|
||||
Protocol: constants.UDP,
|
||||
},
|
||||
},
|
||||
"default_wireguard_port": {
|
||||
filteredServers: []models.Server{
|
||||
{IPs: []netip.Addr{netip.AddrFrom4([4]byte{1, 1, 1, 1})}, WgPubKey: "x"},
|
||||
},
|
||||
selection: settings.ServerSelection{
|
||||
VPN: vpn.Wireguard,
|
||||
}.WithDefaults(provider),
|
||||
connection: models.Connection{
|
||||
Type: vpn.Wireguard,
|
||||
IP: netip.AddrFrom4([4]byte{1, 1, 1, 1}),
|
||||
Port: 9929,
|
||||
Protocol: constants.UDP,
|
||||
PubKey: "x",
|
||||
},
|
||||
},
|
||||
"default_multihop_port": {
|
||||
filteredServers: []models.Server{
|
||||
{IPs: []netip.Addr{netip.AddrFrom4([4]byte{1, 1, 1, 1})}, WgPubKey: "x", PortsUDP: []uint16{30044}},
|
||||
},
|
||||
selection: settings.ServerSelection{
|
||||
VPN: vpn.Wireguard,
|
||||
}.WithDefaults(provider),
|
||||
connection: models.Connection{
|
||||
Type: vpn.Wireguard,
|
||||
IP: netip.AddrFrom4([4]byte{1, 1, 1, 1}),
|
||||
Port: 30044,
|
||||
Protocol: constants.UDP,
|
||||
PubKey: "x",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for name, testCase := range testCases {
|
||||
t.Run(name, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
ctrl := gomock.NewController(t)
|
||||
|
||||
storage := common.NewMockStorage(ctrl)
|
||||
storage.EXPECT().FilterServers(provider, testCase.selection).
|
||||
Return(testCase.filteredServers, testCase.storageErr)
|
||||
|
||||
client := (*http.Client)(nil)
|
||||
provider := New(storage, client)
|
||||
|
||||
connection, err := provider.GetConnection(testCase.selection, testCase.ipv6Supported)
|
||||
|
||||
assert.ErrorIs(t, err, testCase.errWrapped)
|
||||
if testCase.errWrapped != nil {
|
||||
assert.EqualError(t, err, testCase.errMessage)
|
||||
}
|
||||
|
||||
assert.Equal(t, testCase.connection, connection)
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -1,38 +0,0 @@
|
||||
package ovpn
|
||||
|
||||
import (
|
||||
"strings"
|
||||
|
||||
"github.com/qdm12/gluetun/internal/configuration/settings"
|
||||
"github.com/qdm12/gluetun/internal/constants/openvpn"
|
||||
"github.com/qdm12/gluetun/internal/models"
|
||||
"github.com/qdm12/gluetun/internal/provider/utils"
|
||||
)
|
||||
|
||||
func (p *Provider) OpenVPNConfig(connection models.Connection,
|
||||
settings settings.OpenVPN, ipv6Supported bool,
|
||||
) (lines []string) {
|
||||
providerSettings := utils.OpenVPNProviderSettings{
|
||||
AuthUserPass: true,
|
||||
RemoteCertTLS: true,
|
||||
Ciphers: []string{
|
||||
openvpn.AES256gcm,
|
||||
openvpn.AES256cbc,
|
||||
openvpn.AES128gcm,
|
||||
openvpn.Chacha20Poly1305,
|
||||
},
|
||||
CAs: []string{
|
||||
"MIIEfTCCA2WgAwIBAgIJAK2aIWqpLj1/MA0GCSqGSIb3DQEBBQUAMIGFMQswCQYDVQQGEwJTRTESMBAGA1UECBMJU3RvY2tob2xtMRIwEAYDVQQHEwlTdG9ja2hvbG0xHDAaBgNVBAsTE0Zpcm1hIERhdmlkIFdpYmVyZ2gxEzARBgNVBAMTCm92cG4uc2UgY2ExGzAZBgkqhkiG9w0BCQEWDGluZm9Ab3Zwbi5zZTAeFw0xNDA4MTcxODIxMjlaFw0zNDA4MTIxODIxMjlaMIGFMQswCQYDVQQGEwJTRTESMBAGA1UECBMJU3RvY2tob2xtMRIwEAYDVQQHEwlTdG9ja2hvbG0xHDAaBgNVBAsTE0Zpcm1hIERhdmlkIFdpYmVyZ2gxEzARBgNVBAMTCm92cG4uc2UgY2ExGzAZBgkqhkiG9w0BCQEWDGluZm9Ab3Zwbi5zZTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAMR+aP4GTuZwurZuOA2NYzMfqKyZi/TJcLEPlGTB/b4CWA9bTd8f0pHPrDAZsXIEayxxB58BIFNDNiybnbO15JN/QwlsqmA+aZX6mCSkScs/rRwasM6LDo8iGx+KmYEqAgzziONGbCMnlO+OaarXte7LhZ9X6Z/bryu4xq/i1v3raak13kXsrogtu4iDzxqJE/QhbNOi0yhCdlm5RYQjmlKGdPB9pNTgcakVI4HcngRYMzBlrGin0YkvWCdpx5FrDNeld7BSWrJMNYyvd+buaid0Fu1T9/P/Srj/8AiabKoaDyiGFbZdTnGfK+04lWRvwAmvazpqbUt5Omw634jJDuMCAwEAAaOB7TCB6jAdBgNVHQ4EFgQUEvJcHHcTiDtu7bAyZw+xaqg+xdIwgboGA1UdIwSBsjCBr4AUEvJcHHcTiDtu7bAyZw+xaqg+xdKhgYukgYgwgYUxCzAJBgNVBAYTAlNFMRIwEAYDVQQIEwlTdG9ja2hvbG0xEjAQBgNVBAcTCVN0b2NraG9sbTEcMBoGA1UECxMTRmlybWEgRGF2aWQgV2liZXJnaDETMBEGA1UEAxMKb3Zwbi5zZSBjYTEbMBkGCSqGSIb3DQEJARYMaW5mb0BvdnBuLnNlggkArZohaqkuPX8wDAYDVR0TBAUwAwEB/zANBgkqhkiG9w0BAQUFAAOCAQEAJmID6OyBJbV7ayPPgquojF+FICuDdOfGVKP828cyISxcbVA04VpD0QLYVb0k9pFUx0NbgX2SvRTiFhP7LcyS1HV9s+XLCb2WItPPsrdRTwtqU2n3TlCEzWA3WOcOCtT6JSkv1eelmx1JnP0gYJrDvDvRYBFctwWhtE0bineSQkZwN6980zkknADLAiHpeZSu/AMx7CGTwA6SmoFvpNBmHXDcfe/9ZqbbYfUfyPNe+0JbMrcv1elKi+6wlEkHFaEBphiZwGEbOX1CjUMcQFgW/cIp3n50Eiyx6ktuqimhyb59P4Nw8gqH452tTtE4MM/brA5y0Q0WFBRBojfZIbGWWQ==", //nolint:lll
|
||||
},
|
||||
TLSAuth: "81782767e4d59c4464cc5d1896f1cf6015017d53ac62e2e3b94b889e00b2c69ddc01944fe1c6d895b4d80540502eb71910b8d785c9efa9e3182343532adffe1cfbb7bb6eae39c502da2748edf0fb89b8a20b0a1085cc1f06135037881bc0c4ad8f2c0f4f72d2ab466fb54af3d8264c5fddeb0f21aa0ca41863678f5fc4c44de4ca0926b36dfddc42c6f2fabd1694bdc8215b2d223b9c21dc6734c2c778093187afb8c33403b228b9af68b540c284f6d183bcc88bd41d47bd717996e499ce1cbbfa768a9723c19c58314c4d19cfed82e543ee92e73d38ad26d4fbec231c0f9f3b30773a5c87792e9bc7c34e8d7611002ebedd044e48a0f1f96527bfdcc940aa09", //nolint:lll
|
||||
KeyDirection: "1",
|
||||
}
|
||||
|
||||
if strings.HasSuffix(connection.Hostname, "singapore.ovpn.com") {
|
||||
providerSettings.TLSCrypt = providerSettings.TLSAuth
|
||||
providerSettings.TLSAuth = ""
|
||||
providerSettings.KeyDirection = ""
|
||||
}
|
||||
|
||||
return utils.OpenVPNConfig(providerSettings, connection, settings, ipv6Supported)
|
||||
}
|
||||
@@ -1,28 +0,0 @@
|
||||
package ovpn
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
"github.com/qdm12/gluetun/internal/constants/providers"
|
||||
"github.com/qdm12/gluetun/internal/provider/common"
|
||||
"github.com/qdm12/gluetun/internal/provider/ovpn/updater"
|
||||
"github.com/qdm12/gluetun/internal/provider/utils"
|
||||
)
|
||||
|
||||
type Provider struct {
|
||||
storage common.Storage
|
||||
connPicker *utils.ConnectionPicker
|
||||
common.Fetcher
|
||||
}
|
||||
|
||||
func New(storage common.Storage, client *http.Client) *Provider {
|
||||
return &Provider{
|
||||
storage: storage,
|
||||
connPicker: utils.NewConnectionPicker(),
|
||||
Fetcher: updater.New(client),
|
||||
}
|
||||
}
|
||||
|
||||
func (p *Provider) Name() string {
|
||||
return providers.Ovpn
|
||||
}
|
||||
@@ -1,153 +0,0 @@
|
||||
package updater
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"net/netip"
|
||||
"strings"
|
||||
)
|
||||
|
||||
type apiData struct {
|
||||
Success bool `json:"success"`
|
||||
DataCenters []apiDataCenter `json:"datacenters"`
|
||||
}
|
||||
|
||||
type apiDataCenter struct {
|
||||
City string `json:"city"`
|
||||
CountryName string `json:"country_name"`
|
||||
Servers []apiServer `json:"servers"`
|
||||
}
|
||||
|
||||
type apiServer struct {
|
||||
IP netip.Addr `json:"ip"`
|
||||
Ptr string `json:"ptr"` // hostname
|
||||
Online bool `json:"online"`
|
||||
// PublicKey is for the Standard Shared Entry Point
|
||||
PublicKey string `json:"public_key"`
|
||||
// PublicKeyIPv4 is for the Public / Dedicated IP Entry Point
|
||||
PublicKeyIPv4 string `json:"public_key_ipv4"`
|
||||
WireguardPorts []uint16 `json:"wireguard_ports"`
|
||||
MultiHopOpenvpnPort uint16 `json:"multihop_openvpn_port"`
|
||||
MultiHopWireguardPort uint16 `json:"multihop_wireguard_port"`
|
||||
}
|
||||
|
||||
func fetchAPI(ctx context.Context, client *http.Client) (
|
||||
data apiData, err error,
|
||||
) {
|
||||
const url = "https://www.ovpn.com/v2/api/client/entry"
|
||||
|
||||
request, err := http.NewRequestWithContext(ctx, http.MethodGet, url, nil)
|
||||
if err != nil {
|
||||
return data, err
|
||||
}
|
||||
|
||||
response, err := client.Do(request)
|
||||
if err != nil {
|
||||
return data, err
|
||||
}
|
||||
|
||||
if response.StatusCode != http.StatusOK {
|
||||
_ = response.Body.Close()
|
||||
return data, fmt.Errorf("HTTP response status code is not OK: %d %s",
|
||||
response.StatusCode, response.Status)
|
||||
}
|
||||
|
||||
decoder := json.NewDecoder(response.Body)
|
||||
err = decoder.Decode(&data)
|
||||
if err != nil {
|
||||
_ = response.Body.Close()
|
||||
return data, fmt.Errorf("decoding response body: %w", err)
|
||||
}
|
||||
|
||||
err = response.Body.Close()
|
||||
if err != nil {
|
||||
return data, fmt.Errorf("closing response body: %w", err)
|
||||
}
|
||||
|
||||
return data, nil
|
||||
}
|
||||
|
||||
func (a *apiDataCenter) validate() (err error) {
|
||||
conditionalErrors := []conditionalError{
|
||||
{err: "city is not set", condition: a.City == ""},
|
||||
{err: "country name is not set", condition: a.CountryName == ""},
|
||||
{err: "servers array is not set", condition: len(a.Servers) == 0},
|
||||
}
|
||||
err = collectErrors(conditionalErrors)
|
||||
if err != nil {
|
||||
var dataCenterSetFields []string
|
||||
if a.CountryName != "" {
|
||||
dataCenterSetFields = append(dataCenterSetFields, a.CountryName)
|
||||
}
|
||||
if a.City != "" {
|
||||
dataCenterSetFields = append(dataCenterSetFields, a.City)
|
||||
}
|
||||
if len(dataCenterSetFields) == 0 {
|
||||
return err
|
||||
}
|
||||
return fmt.Errorf("data center %s: %w",
|
||||
strings.Join(dataCenterSetFields, ", "), err)
|
||||
}
|
||||
|
||||
for i, server := range a.Servers {
|
||||
err = server.validate()
|
||||
if err != nil {
|
||||
return fmt.Errorf("datacenter %s, %s: server %d of %d: %w",
|
||||
a.CountryName, a.City, i+1, len(a.Servers), err)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (a *apiServer) validate() (err error) {
|
||||
const defaultWireguardPort = 9929
|
||||
conditionalErrors := []conditionalError{
|
||||
{err: "ip address is not set", condition: !a.IP.IsValid()},
|
||||
{err: "hostname field is not set", condition: a.Ptr == ""},
|
||||
{err: "public key field is not set", condition: a.PublicKey == ""},
|
||||
{err: "public key IPv4 field is not set", condition: a.PublicKeyIPv4 == ""},
|
||||
{err: "wireguard ports array is not set", condition: len(a.WireguardPorts) == 0},
|
||||
{
|
||||
err: "wireguard port is not the default 9929",
|
||||
condition: len(a.WireguardPorts) != 1 || a.WireguardPorts[0] != defaultWireguardPort,
|
||||
},
|
||||
{err: "multihop OpenVPN port is not set", condition: a.MultiHopOpenvpnPort == 0},
|
||||
{err: "multihop WireGuard port is not set", condition: a.MultiHopWireguardPort == 0},
|
||||
}
|
||||
err = collectErrors(conditionalErrors)
|
||||
switch {
|
||||
case err == nil:
|
||||
return nil
|
||||
case a.Ptr != "":
|
||||
return fmt.Errorf("server %s: %w", a.Ptr, err)
|
||||
case a.IP.IsValid():
|
||||
return fmt.Errorf("server %s: %w", a.IP.String(), err)
|
||||
default:
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
type conditionalError struct {
|
||||
err string
|
||||
condition bool
|
||||
}
|
||||
|
||||
func collectErrors(conditionalErrors []conditionalError) (err error) {
|
||||
errs := make([]string, 0, len(conditionalErrors))
|
||||
for _, conditionalError := range conditionalErrors {
|
||||
if !conditionalError.condition {
|
||||
continue
|
||||
}
|
||||
errs = append(errs, conditionalError.err)
|
||||
}
|
||||
|
||||
if len(errs) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
return errors.New(strings.Join(errs, "; "))
|
||||
}
|
||||
@@ -1,118 +0,0 @@
|
||||
package updater
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"io"
|
||||
"net/http"
|
||||
"net/netip"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func Test_fetchAPI(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
testCases := map[string]struct {
|
||||
responseStatus int
|
||||
responseBody io.ReadCloser
|
||||
data apiData
|
||||
err error
|
||||
}{
|
||||
"http response status not ok": {
|
||||
responseStatus: http.StatusNoContent,
|
||||
err: errors.New("HTTP response status code is not OK: 204 No Content"),
|
||||
},
|
||||
"nil body": {
|
||||
responseStatus: http.StatusOK,
|
||||
err: errors.New("decoding response body: EOF"),
|
||||
},
|
||||
"no server": {
|
||||
responseStatus: http.StatusOK,
|
||||
responseBody: io.NopCloser(strings.NewReader(`{}`)),
|
||||
},
|
||||
"success": {
|
||||
responseStatus: http.StatusOK,
|
||||
responseBody: io.NopCloser(strings.NewReader(`{
|
||||
"success": true,
|
||||
"datacenters": [
|
||||
{
|
||||
"slug": "vienna",
|
||||
"city": "Vienna",
|
||||
"country": "AT",
|
||||
"country_name": "Austria",
|
||||
"pools": [
|
||||
"pool-1.prd.at.vienna.ovpn.com"
|
||||
],
|
||||
"ping_address": "37.120.212.227",
|
||||
"servers": [
|
||||
{
|
||||
"ip": "37.120.212.227",
|
||||
"ptr": "vpn44.prd.vienna.ovpn.com",
|
||||
"name": "VPN44 - Vienna",
|
||||
"online": true,
|
||||
"load": 8,
|
||||
"public_key": "r83LIc0Q2F8s3dY9x5y17Yz8wTADJc7giW1t5eSmoXc=",
|
||||
"public_key_ipv4": "wFbSRyjSXBmkjJodlqz7DoYn3WNDPYFUIXyIUS2QU2A=",
|
||||
"wireguard_ports": [
|
||||
9929
|
||||
],
|
||||
"multihop_openvpn_port": 20044,
|
||||
"multihop_wireguard_port": 30044
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}`)),
|
||||
data: apiData{
|
||||
Success: true,
|
||||
DataCenters: []apiDataCenter{
|
||||
{CountryName: "Austria", City: "Vienna", Servers: []apiServer{
|
||||
{
|
||||
IP: netip.MustParseAddr("37.120.212.227"),
|
||||
Ptr: "vpn44.prd.vienna.ovpn.com",
|
||||
Online: true,
|
||||
PublicKey: "r83LIc0Q2F8s3dY9x5y17Yz8wTADJc7giW1t5eSmoXc=",
|
||||
PublicKeyIPv4: "wFbSRyjSXBmkjJodlqz7DoYn3WNDPYFUIXyIUS2QU2A=",
|
||||
WireguardPorts: []uint16{9929},
|
||||
MultiHopOpenvpnPort: 20044,
|
||||
MultiHopWireguardPort: 30044,
|
||||
},
|
||||
}},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
for name, testCase := range testCases {
|
||||
t.Run(name, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
ctx := context.Background()
|
||||
|
||||
client := &http.Client{
|
||||
Transport: roundTripFunc(func(r *http.Request) (*http.Response, error) {
|
||||
assert.Equal(t, http.MethodGet, r.Method)
|
||||
assert.Equal(t, r.URL.String(), "https://www.ovpn.com/v2/api/client/entry")
|
||||
return &http.Response{
|
||||
StatusCode: testCase.responseStatus,
|
||||
Status: http.StatusText(testCase.responseStatus),
|
||||
Body: testCase.responseBody,
|
||||
}, nil
|
||||
}),
|
||||
}
|
||||
|
||||
data, err := fetchAPI(ctx, client)
|
||||
|
||||
assert.Equal(t, testCase.data, data)
|
||||
if testCase.err != nil {
|
||||
require.Error(t, err)
|
||||
assert.Equal(t, testCase.err.Error(), err.Error())
|
||||
} else {
|
||||
assert.NoError(t, err)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -1,9 +0,0 @@
|
||||
package updater
|
||||
|
||||
import "net/http"
|
||||
|
||||
type roundTripFunc func(r *http.Request) (*http.Response, error)
|
||||
|
||||
func (f roundTripFunc) RoundTrip(r *http.Request) (*http.Response, error) {
|
||||
return f(r)
|
||||
}
|
||||
@@ -1,82 +0,0 @@
|
||||
package updater
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"net/netip"
|
||||
"sort"
|
||||
|
||||
"github.com/qdm12/gluetun/internal/constants/vpn"
|
||||
"github.com/qdm12/gluetun/internal/models"
|
||||
"github.com/qdm12/gluetun/internal/provider/common"
|
||||
)
|
||||
|
||||
func (u *Updater) FetchServers(ctx context.Context, minServers int) (
|
||||
servers []models.Server, err error,
|
||||
) {
|
||||
data, err := fetchAPI(ctx, u.client)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("fetching API: %w", err)
|
||||
} else if !data.Success {
|
||||
return nil, errors.New("response success field is false")
|
||||
}
|
||||
|
||||
for dataCenterIndex, dataCenter := range data.DataCenters {
|
||||
err = dataCenter.validate()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("validating data center %d of %d: %w",
|
||||
dataCenterIndex+1, len(data.DataCenters), err)
|
||||
}
|
||||
|
||||
for _, apiServer := range dataCenter.Servers {
|
||||
if !apiServer.Online {
|
||||
continue
|
||||
}
|
||||
|
||||
baseServer := models.Server{
|
||||
Country: dataCenter.CountryName,
|
||||
City: dataCenter.City,
|
||||
Hostname: apiServer.Ptr,
|
||||
IPs: []netip.Addr{apiServer.IP},
|
||||
}
|
||||
openVPNServer := baseServer
|
||||
openVPNServer.VPN = vpn.OpenVPN
|
||||
openVPNServer.TCP = true
|
||||
openVPNServer.UDP = true
|
||||
multiHopOpenVPNServer := openVPNServer
|
||||
multiHopOpenVPNServer.MultiHop = true
|
||||
multiHopOpenVPNServer.PortsTCP = []uint16{apiServer.MultiHopOpenvpnPort}
|
||||
multiHopOpenVPNServer.PortsUDP = []uint16{apiServer.MultiHopOpenvpnPort}
|
||||
servers = append(servers, openVPNServer, multiHopOpenVPNServer)
|
||||
|
||||
wireguardServer := baseServer
|
||||
wireguardServer.VPN = vpn.Wireguard
|
||||
wireguardServer.WgPubKey = apiServer.PublicKey
|
||||
multiHopWireguardServer := wireguardServer
|
||||
multiHopWireguardServer.MultiHop = true
|
||||
multiHopWireguardServer.PortsUDP = []uint16{apiServer.MultiHopWireguardPort}
|
||||
dedicatedWireguardServer := wireguardServer
|
||||
dedicatedWireguardServer.WgPubKey = apiServer.PublicKeyIPv4
|
||||
dedicatedWireguardServer.Dedicated = true
|
||||
dedicatedMultiHopWireguardServer := multiHopWireguardServer
|
||||
dedicatedMultiHopWireguardServer.WgPubKey = apiServer.PublicKeyIPv4
|
||||
dedicatedMultiHopWireguardServer.Dedicated = true
|
||||
servers = append(servers,
|
||||
wireguardServer,
|
||||
multiHopWireguardServer,
|
||||
dedicatedWireguardServer,
|
||||
dedicatedMultiHopWireguardServer,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
if len(servers) < minServers {
|
||||
return nil, fmt.Errorf("%w: %d and expected at least %d",
|
||||
common.ErrNotEnoughServers, len(servers), minServers)
|
||||
}
|
||||
|
||||
sort.Sort(models.SortableServers(servers))
|
||||
|
||||
return servers, nil
|
||||
}
|
||||
@@ -1,228 +0,0 @@
|
||||
package updater
|
||||
|
||||
import (
|
||||
"context"
|
||||
"io"
|
||||
"net/http"
|
||||
"net/netip"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/qdm12/gluetun/internal/constants/vpn"
|
||||
"github.com/qdm12/gluetun/internal/models"
|
||||
"github.com/qdm12/gluetun/internal/provider/common"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func Test_Updater_FetchServers(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
testCases := map[string]struct {
|
||||
// Inputs
|
||||
minServers int
|
||||
|
||||
// From API
|
||||
responseStatus int
|
||||
responseBody string
|
||||
|
||||
// Output
|
||||
servers []models.Server
|
||||
errWrapped error
|
||||
errMessage string
|
||||
}{
|
||||
"http_response_error": {
|
||||
responseStatus: http.StatusNoContent,
|
||||
errMessage: "fetching API: HTTP response status code is not OK: 204 No Content",
|
||||
},
|
||||
"success_field_false": {
|
||||
responseStatus: http.StatusOK,
|
||||
responseBody: `{"success": false}`,
|
||||
errMessage: "response success field is false",
|
||||
},
|
||||
"validation_failed": {
|
||||
responseStatus: http.StatusOK,
|
||||
responseBody: `{
|
||||
"success": true,
|
||||
"datacenters": [
|
||||
{
|
||||
"city": "Vienna",
|
||||
"servers": [
|
||||
{}
|
||||
]
|
||||
}
|
||||
]
|
||||
}`,
|
||||
errMessage: "validating data center 1 of 1: data center Vienna: country name is not set",
|
||||
},
|
||||
"not_enough_servers": {
|
||||
minServers: 7,
|
||||
responseStatus: http.StatusOK,
|
||||
responseBody: `{
|
||||
"success": true,
|
||||
"datacenters": [
|
||||
{
|
||||
"city": "Vienna",
|
||||
"country_name": "Austria",
|
||||
"servers": [
|
||||
{
|
||||
"ip": "37.120.212.227",
|
||||
"ptr": "vpn44.prd.vienna.ovpn.com",
|
||||
"online": true,
|
||||
"public_key": "r83LIc0Q2F8s3dY9x5y17Yz8wTADJc7giW1t5eSmoXc=",
|
||||
"public_key_ipv4": "wFbSRyjSXBmkjJodlqz7DoYn3WNDPYFUIXyIUS2QU2A=",
|
||||
"wireguard_ports": [9929],
|
||||
"multihop_openvpn_port": 20044,
|
||||
"multihop_wireguard_port": 30044
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}`,
|
||||
errWrapped: common.ErrNotEnoughServers,
|
||||
// Wireguard + dedicated Wireguard + Wireguard multi-hop +
|
||||
// dedicated Wireguard multi-hop + OpenVPN + OpenVPN multi-hop
|
||||
errMessage: "not enough servers found: 6 and expected at least 7",
|
||||
},
|
||||
"success": {
|
||||
minServers: 4,
|
||||
responseBody: `{
|
||||
"success": true,
|
||||
"datacenters": [
|
||||
{
|
||||
"slug": "vienna",
|
||||
"city": "Vienna",
|
||||
"country": "AT",
|
||||
"country_name": "Austria",
|
||||
"pools": [
|
||||
"pool-1.prd.at.vienna.ovpn.com"
|
||||
],
|
||||
"ping_address": "37.120.212.227",
|
||||
"servers": [
|
||||
{
|
||||
"ip": "37.120.212.227",
|
||||
"ptr": "vpn44.prd.vienna.ovpn.com",
|
||||
"name": "VPN44 - Vienna",
|
||||
"online": true,
|
||||
"load": 8,
|
||||
"public_key": "r83LIc0Q2F8s3dY9x5y17Yz8wTADJc7giW1t5eSmoXc=",
|
||||
"public_key_ipv4": "wFbSRyjSXBmkjJodlqz7DoYn3WNDPYFUIXyIUS2QU2A=",
|
||||
"wireguard_ports": [
|
||||
9929
|
||||
],
|
||||
"multihop_openvpn_port": 20044,
|
||||
"multihop_wireguard_port": 30044
|
||||
},
|
||||
{
|
||||
"ip": "37.120.212.228",
|
||||
"ptr": "vpn45.prd.vienna.ovpn.com",
|
||||
"online": false,
|
||||
"public_key": "r93LIc0Q2F8s3dY9x5y17Yz8wTADJc7giW1t5eSmoXc=",
|
||||
"public_key_ipv4": "wGbSRyjSXBmkjJodlqz7DoYn3WNDPYFUIXyIUS2QU2A=",
|
||||
"wireguard_ports": [9929],
|
||||
"multihop_openvpn_port": 20045,
|
||||
"multihop_wireguard_port": 30045
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}`,
|
||||
responseStatus: http.StatusOK,
|
||||
servers: []models.Server{
|
||||
{
|
||||
Country: "Austria",
|
||||
City: "Vienna",
|
||||
Hostname: "vpn44.prd.vienna.ovpn.com",
|
||||
IPs: []netip.Addr{netip.MustParseAddr("37.120.212.227")},
|
||||
VPN: vpn.OpenVPN,
|
||||
UDP: true,
|
||||
TCP: true,
|
||||
},
|
||||
{
|
||||
Country: "Austria",
|
||||
City: "Vienna",
|
||||
Hostname: "vpn44.prd.vienna.ovpn.com",
|
||||
IPs: []netip.Addr{netip.MustParseAddr("37.120.212.227")},
|
||||
VPN: vpn.OpenVPN,
|
||||
UDP: true,
|
||||
TCP: true,
|
||||
MultiHop: true,
|
||||
PortsTCP: []uint16{20044},
|
||||
PortsUDP: []uint16{20044},
|
||||
},
|
||||
{
|
||||
Country: "Austria",
|
||||
City: "Vienna",
|
||||
Hostname: "vpn44.prd.vienna.ovpn.com",
|
||||
IPs: []netip.Addr{netip.MustParseAddr("37.120.212.227")},
|
||||
VPN: vpn.Wireguard,
|
||||
WgPubKey: "r83LIc0Q2F8s3dY9x5y17Yz8wTADJc7giW1t5eSmoXc=",
|
||||
},
|
||||
{
|
||||
Country: "Austria",
|
||||
City: "Vienna",
|
||||
Hostname: "vpn44.prd.vienna.ovpn.com",
|
||||
IPs: []netip.Addr{netip.MustParseAddr("37.120.212.227")},
|
||||
VPN: vpn.Wireguard,
|
||||
WgPubKey: "r83LIc0Q2F8s3dY9x5y17Yz8wTADJc7giW1t5eSmoXc=",
|
||||
MultiHop: true,
|
||||
PortsUDP: []uint16{30044},
|
||||
},
|
||||
{
|
||||
Country: "Austria",
|
||||
City: "Vienna",
|
||||
Hostname: "vpn44.prd.vienna.ovpn.com",
|
||||
IPs: []netip.Addr{netip.MustParseAddr("37.120.212.227")},
|
||||
VPN: vpn.Wireguard,
|
||||
WgPubKey: "wFbSRyjSXBmkjJodlqz7DoYn3WNDPYFUIXyIUS2QU2A=",
|
||||
Dedicated: true,
|
||||
},
|
||||
{
|
||||
Country: "Austria",
|
||||
City: "Vienna",
|
||||
Hostname: "vpn44.prd.vienna.ovpn.com",
|
||||
IPs: []netip.Addr{netip.MustParseAddr("37.120.212.227")},
|
||||
VPN: vpn.Wireguard,
|
||||
WgPubKey: "wFbSRyjSXBmkjJodlqz7DoYn3WNDPYFUIXyIUS2QU2A=",
|
||||
MultiHop: true,
|
||||
Dedicated: true,
|
||||
PortsUDP: []uint16{30044},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
for name, testCase := range testCases {
|
||||
t.Run(name, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
ctx := context.Background()
|
||||
|
||||
client := &http.Client{
|
||||
Transport: roundTripFunc(func(r *http.Request) (*http.Response, error) {
|
||||
assert.Equal(t, http.MethodGet, r.Method)
|
||||
assert.Equal(t, r.URL.String(), "https://www.ovpn.com/v2/api/client/entry")
|
||||
return &http.Response{
|
||||
StatusCode: testCase.responseStatus,
|
||||
Status: http.StatusText(testCase.responseStatus),
|
||||
Body: io.NopCloser(strings.NewReader(testCase.responseBody)),
|
||||
}, nil
|
||||
}),
|
||||
}
|
||||
|
||||
updater := &Updater{
|
||||
client: client,
|
||||
}
|
||||
|
||||
servers, err := updater.FetchServers(ctx, testCase.minServers)
|
||||
|
||||
assert.Equal(t, testCase.servers, servers)
|
||||
if testCase.errMessage == "" {
|
||||
assert.NoError(t, err)
|
||||
} else {
|
||||
assert.Contains(t, err.Error(), testCase.errMessage)
|
||||
}
|
||||
if testCase.errWrapped != nil {
|
||||
assert.ErrorIs(t, err, testCase.errWrapped)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -1,15 +0,0 @@
|
||||
package updater
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
)
|
||||
|
||||
type Updater struct {
|
||||
client *http.Client
|
||||
}
|
||||
|
||||
func New(client *http.Client) *Updater {
|
||||
return &Updater{
|
||||
client: client,
|
||||
}
|
||||
}
|
||||
@@ -5,6 +5,7 @@ import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"maps"
|
||||
"net/netip"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
@@ -12,14 +13,14 @@ import (
|
||||
"github.com/qdm12/gluetun/internal/provider/utils"
|
||||
)
|
||||
|
||||
const nonSymmetricPortStart uint16 = 56789
|
||||
|
||||
// PortForward obtains a VPN server side port forwarded from ProtonVPN gateway.
|
||||
func (p *Provider) PortForward(ctx context.Context, objects utils.PortForwardObjects) (
|
||||
internalToExternalPorts map[uint16]uint16, err error,
|
||||
) {
|
||||
if !objects.CanPortForward {
|
||||
return nil, errors.New("server does not support port forwarding")
|
||||
} else if objects.PortsCount == 0 {
|
||||
return nil, nil //nolint:nilnil
|
||||
}
|
||||
|
||||
client := natpmp.New()
|
||||
@@ -39,38 +40,75 @@ func (p *Provider) PortForward(ctx context.Context, objects utils.PortForwardObj
|
||||
logger := objects.Logger
|
||||
|
||||
logger.Debug("gateway external IPv4 address is " + externalIPv4Address.String())
|
||||
const externalPort = 0
|
||||
const lifetime = 60 * time.Second
|
||||
|
||||
p.internalToExternalPorts = make(map[uint16]uint16, objects.PortsCount)
|
||||
for i := range objects.PortsCount {
|
||||
internalPort := nonSymmetricPortStart + i
|
||||
protoToInternalPort := map[string]uint16{
|
||||
"udp": 0,
|
||||
"tcp": 0,
|
||||
}
|
||||
protoToExternalPort := maps.Clone(protoToInternalPort)
|
||||
for protocol := range protoToExternalPort {
|
||||
_, assignedInternalPort, assignedExternalPort, assignedLifetime, err := client.AddPortMapping(
|
||||
ctx, objects.Gateway, protocol, internalPort, externalPort, lifetime)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("adding %d/%d %s port mapping: %w",
|
||||
i+1, objects.PortsCount, strings.ToUpper(protocol), err)
|
||||
}
|
||||
checkLifetime(logger, strings.ToUpper(protocol), lifetime, assignedLifetime)
|
||||
checkInternalPort(logger, internalPort, assignedInternalPort)
|
||||
protoToInternalPort[protocol] = assignedInternalPort
|
||||
protoToExternalPort[protocol] = assignedExternalPort
|
||||
}
|
||||
const lifetime = 60 * time.Second
|
||||
|
||||
checkInternalPorts(logger, protoToInternalPort["udp"], protoToInternalPort["tcp"])
|
||||
checkExternalPorts(logger, protoToExternalPort["udp"], protoToExternalPort["tcp"])
|
||||
p.internalToExternalPorts[protoToInternalPort["tcp"]] = protoToExternalPort["tcp"]
|
||||
// Only one port can be a symmetric mapping
|
||||
const internalPort, externalPort = 0, 1
|
||||
_, assignedExternalPort, err := addPortMappingTCPUDP(ctx,
|
||||
client, logger, objects.Gateway, internalPort, externalPort, lifetime)
|
||||
// Note the returned assignedInternalPort is always 0 in this case
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("adding first port mapping: %w", err)
|
||||
}
|
||||
p.internalToExternalPorts[assignedExternalPort] = assignedExternalPort
|
||||
|
||||
// Extra ports must be non-symmetric, meaning that the internal port is
|
||||
// different from the external port.
|
||||
const nonSymmetricPortStart = uint16(56789)
|
||||
nonSymmetricPortStartMinusOne := nonSymmetricPortStart - 1
|
||||
if _, ok := p.internalToExternalPorts[nonSymmetricPortStart]; ok {
|
||||
nonSymmetricPortStartMinusOne++
|
||||
}
|
||||
for i := uint16(1); i < objects.PortsCount; i++ {
|
||||
internalPort := nonSymmetricPortStartMinusOne + i
|
||||
const externalPort = 0
|
||||
assignedInternalPort, assignedExternalPort, err := addPortMappingTCPUDP(ctx,
|
||||
client, logger, objects.Gateway, internalPort, externalPort, lifetime)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("adding %d/%d port mapping: %w", i+1, objects.PortsCount, err)
|
||||
}
|
||||
p.internalToExternalPorts[assignedInternalPort] = assignedExternalPort
|
||||
}
|
||||
|
||||
return maps.Clone(p.internalToExternalPorts), nil
|
||||
}
|
||||
|
||||
func addPortMappingTCPUDP(ctx context.Context, client *natpmp.Client, logger utils.Logger,
|
||||
gateway netip.Addr, internalPort, externalPort uint16, lifetime time.Duration,
|
||||
) (assignedInternalPort, assignedExternalPort uint16, err error) {
|
||||
var assignedLifetime time.Duration
|
||||
protocolToExternalPort := map[string]uint16{
|
||||
"tcp": 0,
|
||||
"udp": 0,
|
||||
}
|
||||
for _, protocol := range [...]string{"udp", "tcp"} {
|
||||
protocolStr := strings.ToUpper(protocol)
|
||||
_, assignedInternalPort, assignedExternalPort, assignedLifetime, err = client.AddPortMapping(
|
||||
ctx, gateway, protocol, internalPort, externalPort, lifetime)
|
||||
if err != nil {
|
||||
return 0, 0, fmt.Errorf("adding %s port mapping: %w", protocolStr, err)
|
||||
}
|
||||
protocolToExternalPort[protocol] = assignedExternalPort
|
||||
checkLifetime(logger, protocolStr, lifetime, assignedLifetime)
|
||||
if internalPort != assignedInternalPort {
|
||||
return 0, 0, fmt.Errorf("%s internal port requested as %d but received %d",
|
||||
protocolStr, internalPort, assignedInternalPort)
|
||||
} else if externalPort != 0 && externalPort != 1 && externalPort != assignedExternalPort {
|
||||
return 0, 0, fmt.Errorf("%s external port requested as %d but received %d",
|
||||
protocolStr, externalPort, assignedExternalPort)
|
||||
}
|
||||
}
|
||||
|
||||
if protocolToExternalPort["tcp"] != protocolToExternalPort["udp"] {
|
||||
return 0, 0, fmt.Errorf("TCP and UDP external ports differ: %d and %d",
|
||||
protocolToExternalPort["tcp"], protocolToExternalPort["udp"])
|
||||
}
|
||||
|
||||
return assignedInternalPort, assignedExternalPort, nil
|
||||
}
|
||||
|
||||
func checkLifetime(logger utils.Logger, protocol string,
|
||||
requested, actual time.Duration,
|
||||
) {
|
||||
@@ -81,27 +119,6 @@ func checkLifetime(logger utils.Logger, protocol string,
|
||||
}
|
||||
}
|
||||
|
||||
func checkInternalPort(logger utils.Logger, sent, received uint16) {
|
||||
if sent != received {
|
||||
logger.Warn(fmt.Sprintf("internal port assigned %d differs from requested internal port %d",
|
||||
sent, received))
|
||||
}
|
||||
}
|
||||
|
||||
func checkInternalPorts(logger utils.Logger, udpPort, tcpPort uint16) {
|
||||
if udpPort != tcpPort {
|
||||
logger.Warn(fmt.Sprintf("UDP internal port %d differs from TCP internal port %d",
|
||||
udpPort, tcpPort))
|
||||
}
|
||||
}
|
||||
|
||||
func checkExternalPorts(logger utils.Logger, udpPort, tcpPort uint16) {
|
||||
if udpPort != tcpPort {
|
||||
logger.Warn(fmt.Sprintf("UDP external port %d differs from TCP external port %d",
|
||||
udpPort, tcpPort))
|
||||
}
|
||||
}
|
||||
|
||||
func (p *Provider) KeepPortForward(ctx context.Context,
|
||||
objects utils.PortForwardObjects,
|
||||
) (err error) {
|
||||
@@ -117,22 +134,12 @@ func (p *Provider) KeepPortForward(ctx context.Context,
|
||||
}
|
||||
|
||||
objects.Logger.Debug("refreshing forwarded ports since 45 seconds have elapsed")
|
||||
networkProtocols := [...]string{"udp", "tcp"}
|
||||
const lifetime = 60 * time.Second
|
||||
for internalPort, externalPort := range p.internalToExternalPorts {
|
||||
for _, networkProtocol := range networkProtocols {
|
||||
_, assignedInternalPort, assignedExternalPort, assignedLiftetime, err := client.AddPortMapping(
|
||||
ctx, objects.Gateway, networkProtocol, internalPort, externalPort, lifetime)
|
||||
_, _, err := addPortMappingTCPUDP(ctx, client, logger, objects.Gateway, internalPort, externalPort, lifetime)
|
||||
if err != nil {
|
||||
return fmt.Errorf("adding port mapping: %w", err)
|
||||
}
|
||||
checkLifetime(logger, networkProtocol, lifetime, assignedLiftetime)
|
||||
if externalPort != assignedExternalPort {
|
||||
return fmt.Errorf("external port changed from %d to %d", externalPort, assignedExternalPort)
|
||||
} else if internalPort != assignedInternalPort {
|
||||
return fmt.Errorf("internal port changed from %d (for external port %d) to %d",
|
||||
internalPort, externalPort, assignedInternalPort)
|
||||
}
|
||||
return fmt.Errorf("refreshing port mapping for internal port %d and external port %d: %w",
|
||||
internalPort, externalPort, err)
|
||||
}
|
||||
objects.Logger.Debug(fmt.Sprintf("port forwarded %d maintained", externalPort))
|
||||
}
|
||||
|
||||
@@ -20,7 +20,6 @@ import (
|
||||
"github.com/qdm12/gluetun/internal/provider/ivpn"
|
||||
"github.com/qdm12/gluetun/internal/provider/mullvad"
|
||||
"github.com/qdm12/gluetun/internal/provider/nordvpn"
|
||||
"github.com/qdm12/gluetun/internal/provider/ovpn"
|
||||
"github.com/qdm12/gluetun/internal/provider/perfectprivacy"
|
||||
"github.com/qdm12/gluetun/internal/provider/privado"
|
||||
"github.com/qdm12/gluetun/internal/provider/privateinternetaccess"
|
||||
@@ -68,7 +67,6 @@ func NewProviders(storage Storage, timeNow func() time.Time,
|
||||
providers.Ivpn: ivpn.New(storage, client, updaterWarner, parallelResolver),
|
||||
providers.Mullvad: mullvad.New(storage, client),
|
||||
providers.Nordvpn: nordvpn.New(storage, client, updaterWarner),
|
||||
providers.Ovpn: ovpn.New(storage, client),
|
||||
providers.Perfectprivacy: perfectprivacy.New(storage, unzipper, updaterWarner),
|
||||
providers.Privado: privado.New(storage, client, updaterWarner),
|
||||
providers.PrivateInternetAccess: privateinternetaccess.New(storage, timeNow, client),
|
||||
|
||||
@@ -52,6 +52,8 @@ func GetConnection(provider string,
|
||||
})
|
||||
|
||||
protocol := getProtocol(selection)
|
||||
port := getPort(selection, defaults.OpenVPNTCPPort,
|
||||
defaults.OpenVPNUDPPort, defaults.WireguardPort)
|
||||
|
||||
connections := make([]models.Connection, 0, len(servers))
|
||||
for _, server := range servers {
|
||||
@@ -67,9 +69,6 @@ func GetConnection(provider string,
|
||||
hostname = server.OvpnX509
|
||||
}
|
||||
|
||||
port := getPort(selection, server, defaults.OpenVPNTCPPort,
|
||||
defaults.OpenVPNUDPPort, defaults.WireguardPort)
|
||||
|
||||
connection := models.Connection{
|
||||
Type: selection.VPN,
|
||||
IP: ip,
|
||||
|
||||
@@ -6,7 +6,6 @@ import (
|
||||
"net/netip"
|
||||
"testing"
|
||||
|
||||
"github.com/golang/mock/gomock"
|
||||
"github.com/qdm12/gluetun/internal/configuration/settings"
|
||||
"github.com/qdm12/gluetun/internal/constants"
|
||||
"github.com/qdm12/gluetun/internal/constants/providers"
|
||||
@@ -14,6 +13,7 @@ import (
|
||||
"github.com/qdm12/gluetun/internal/models"
|
||||
"github.com/qdm12/gluetun/internal/provider/common"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"go.uber.org/mock/gomock"
|
||||
)
|
||||
|
||||
func Test_GetConnection(t *testing.T) {
|
||||
|
||||
@@ -6,44 +6,29 @@ import (
|
||||
"github.com/qdm12/gluetun/internal/configuration/settings"
|
||||
"github.com/qdm12/gluetun/internal/constants"
|
||||
"github.com/qdm12/gluetun/internal/constants/vpn"
|
||||
"github.com/qdm12/gluetun/internal/models"
|
||||
)
|
||||
|
||||
func getPort(selection settings.ServerSelection, server models.Server,
|
||||
func getPort(selection settings.ServerSelection,
|
||||
defaultOpenVPNTCP, defaultOpenVPNUDP, defaultWireguard uint16,
|
||||
) (port uint16) {
|
||||
switch selection.VPN {
|
||||
case vpn.Wireguard:
|
||||
customPort := *selection.Wireguard.EndpointPort
|
||||
if customPort > 0 {
|
||||
// Note: servers filtering ensures the custom port is within the
|
||||
// server ports defined if any is set.
|
||||
return customPort
|
||||
}
|
||||
|
||||
if len(server.PortsUDP) > 0 {
|
||||
defaultWireguard = server.PortsUDP[0]
|
||||
}
|
||||
checkDefined("Wireguard", defaultWireguard)
|
||||
return defaultWireguard
|
||||
default: // OpenVPN
|
||||
customPort := *selection.OpenVPN.CustomPort
|
||||
if customPort > 0 {
|
||||
// Note: servers filtering ensures the custom port is within the
|
||||
// server ports defined if any is set.
|
||||
return customPort
|
||||
}
|
||||
if selection.OpenVPN.Protocol == constants.TCP {
|
||||
if len(server.PortsTCP) > 0 {
|
||||
defaultOpenVPNTCP = server.PortsTCP[0]
|
||||
}
|
||||
checkDefined("OpenVPN TCP", defaultOpenVPNTCP)
|
||||
return defaultOpenVPNTCP
|
||||
}
|
||||
|
||||
if len(server.PortsUDP) > 0 {
|
||||
defaultOpenVPNUDP = server.PortsUDP[0]
|
||||
}
|
||||
checkDefined("OpenVPN UDP", defaultOpenVPNUDP)
|
||||
return defaultOpenVPNUDP
|
||||
}
|
||||
|
||||
@@ -6,7 +6,6 @@ import (
|
||||
"github.com/qdm12/gluetun/internal/configuration/settings"
|
||||
"github.com/qdm12/gluetun/internal/constants"
|
||||
"github.com/qdm12/gluetun/internal/constants/vpn"
|
||||
"github.com/qdm12/gluetun/internal/models"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
@@ -23,7 +22,6 @@ func Test_GetPort(t *testing.T) {
|
||||
|
||||
testCases := map[string]struct {
|
||||
selection settings.ServerSelection
|
||||
server models.Server
|
||||
defaultOpenVPNTCP uint16
|
||||
defaultOpenVPNUDP uint16
|
||||
defaultWireguard uint16
|
||||
@@ -50,20 +48,6 @@ func Test_GetPort(t *testing.T) {
|
||||
defaultWireguard: defaultWireguard,
|
||||
port: defaultOpenVPNUDP,
|
||||
},
|
||||
"OpenVPN_server_port_udp": {
|
||||
selection: settings.ServerSelection{
|
||||
VPN: vpn.OpenVPN,
|
||||
OpenVPN: settings.OpenVPNSelection{
|
||||
CustomPort: uint16Ptr(0),
|
||||
Protocol: constants.UDP,
|
||||
},
|
||||
},
|
||||
server: models.Server{
|
||||
PortsUDP: []uint16{1234},
|
||||
},
|
||||
defaultOpenVPNUDP: defaultOpenVPNUDP,
|
||||
port: 1234,
|
||||
},
|
||||
"OpenVPN UDP no default port defined": {
|
||||
selection: settings.ServerSelection{
|
||||
VPN: vpn.OpenVPN,
|
||||
@@ -104,20 +88,6 @@ func Test_GetPort(t *testing.T) {
|
||||
},
|
||||
port: 1234,
|
||||
},
|
||||
"OpenVPN_server_port_tcp": {
|
||||
selection: settings.ServerSelection{
|
||||
VPN: vpn.OpenVPN,
|
||||
OpenVPN: settings.OpenVPNSelection{
|
||||
CustomPort: uint16Ptr(0),
|
||||
Protocol: constants.TCP,
|
||||
},
|
||||
},
|
||||
server: models.Server{
|
||||
PortsTCP: []uint16{1234},
|
||||
},
|
||||
defaultOpenVPNTCP: defaultOpenVPNTCP,
|
||||
port: 1234,
|
||||
},
|
||||
"Wireguard": {
|
||||
selection: settings.ServerSelection{
|
||||
VPN: vpn.Wireguard,
|
||||
@@ -135,19 +105,6 @@ func Test_GetPort(t *testing.T) {
|
||||
defaultWireguard: defaultWireguard,
|
||||
port: 1234,
|
||||
},
|
||||
"Wireguard_server_port": {
|
||||
selection: settings.ServerSelection{
|
||||
VPN: vpn.Wireguard,
|
||||
Wireguard: settings.WireguardSelection{
|
||||
EndpointPort: uint16Ptr(0),
|
||||
},
|
||||
},
|
||||
server: models.Server{
|
||||
PortsUDP: []uint16{1234},
|
||||
},
|
||||
defaultWireguard: defaultWireguard,
|
||||
port: 1234,
|
||||
},
|
||||
"Wireguard no default port defined": {
|
||||
selection: settings.ServerSelection{
|
||||
VPN: vpn.Wireguard,
|
||||
@@ -163,7 +120,6 @@ func Test_GetPort(t *testing.T) {
|
||||
if testCase.panics != "" {
|
||||
assert.PanicsWithValue(t, testCase.panics, func() {
|
||||
_ = getPort(testCase.selection,
|
||||
testCase.server,
|
||||
testCase.defaultOpenVPNTCP,
|
||||
testCase.defaultOpenVPNUDP,
|
||||
testCase.defaultWireguard)
|
||||
@@ -172,7 +128,6 @@ func Test_GetPort(t *testing.T) {
|
||||
}
|
||||
|
||||
port := getPort(testCase.selection,
|
||||
testCase.server,
|
||||
testCase.defaultOpenVPNTCP,
|
||||
testCase.defaultOpenVPNUDP,
|
||||
testCase.defaultWireguard)
|
||||
|
||||
@@ -7,10 +7,10 @@ import (
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/golang/mock/gomock"
|
||||
"github.com/qdm12/gluetun/internal/models"
|
||||
"github.com/qdm12/gluetun/internal/provider/common"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"go.uber.org/mock/gomock"
|
||||
"golang.org/x/net/html"
|
||||
)
|
||||
|
||||
|
||||
@@ -6,7 +6,6 @@ import (
|
||||
"net/netip"
|
||||
"testing"
|
||||
|
||||
"github.com/golang/mock/gomock"
|
||||
"github.com/qdm12/gluetun/internal/configuration/settings"
|
||||
"github.com/qdm12/gluetun/internal/constants"
|
||||
"github.com/qdm12/gluetun/internal/constants/providers"
|
||||
@@ -14,6 +13,7 @@ import (
|
||||
"github.com/qdm12/gluetun/internal/models"
|
||||
"github.com/qdm12/gluetun/internal/provider/common"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"go.uber.org/mock/gomock"
|
||||
)
|
||||
|
||||
func Test_Provider_GetConnection(t *testing.T) {
|
||||
|
||||
@@ -0,0 +1,82 @@
|
||||
package restrictednet
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"net"
|
||||
"net/http"
|
||||
"net/netip"
|
||||
"strconv"
|
||||
|
||||
"github.com/qdm12/dns/v2/pkg/provider"
|
||||
)
|
||||
|
||||
// Client is a client for making restricted network requests,
|
||||
// such as opening temporary firewall rules for HTTPS connections.
|
||||
// It is not meant to be high performance, although it can be used for
|
||||
// multiple requests and concurrently.
|
||||
type Client struct {
|
||||
outboundInterface string
|
||||
ipv6Supported bool
|
||||
firewall Firewall
|
||||
dohServers []provider.DoHServer
|
||||
}
|
||||
|
||||
func New(settings Settings) *Client {
|
||||
if err := settings.validate(); err != nil {
|
||||
panic(fmt.Sprintf("invalid settings: %v", err)) // programming error
|
||||
}
|
||||
dohServers := make([]provider.DoHServer, len(settings.UpstreamResolvers))
|
||||
for i, upstreamResolver := range settings.UpstreamResolvers {
|
||||
dohServers[i] = upstreamResolver.DoH
|
||||
}
|
||||
|
||||
return &Client{
|
||||
outboundInterface: settings.DefaultInterface,
|
||||
ipv6Supported: *settings.IPv6Supported,
|
||||
firewall: settings.Firewall,
|
||||
dohServers: dohServers,
|
||||
}
|
||||
}
|
||||
|
||||
// OpenHTTPSByHostname opens an https connection through the firewall,
|
||||
// to the hostname which in the format `host:port`. The returned cleanup
|
||||
// function must be called to remove the temporary firewall rule and close connections.
|
||||
// It first resolves the domain in hostname using DNS over HTTPS and then opens
|
||||
// the restricted HTTPS connection to the resolved IP.
|
||||
func (c *Client) OpenHTTPSByHostname(ctx context.Context, hostname string) (
|
||||
httpClient *http.Client, cleanup func() error, err error,
|
||||
) {
|
||||
host, portStr, err := net.SplitHostPort(hostname)
|
||||
if err != nil {
|
||||
return nil, nil, fmt.Errorf("splitting host and port: %w", err)
|
||||
}
|
||||
resolvedIPs, err := c.ResolveName(ctx, host)
|
||||
if err != nil {
|
||||
return nil, nil, fmt.Errorf("resolving name: %w", err)
|
||||
} else if len(resolvedIPs) == 0 {
|
||||
return nil, nil, fmt.Errorf("no IP address found for name %q", host)
|
||||
}
|
||||
|
||||
portUint, err := strconv.ParseUint(portStr, 10, 16)
|
||||
if err != nil {
|
||||
return nil, nil, fmt.Errorf("parsing port: %w", err)
|
||||
} else if portUint == 0 {
|
||||
return nil, nil, errors.New("destination port cannot be 0")
|
||||
}
|
||||
port := uint16(portUint)
|
||||
|
||||
errs := make([]error, 0, len(resolvedIPs))
|
||||
for _, ip := range resolvedIPs {
|
||||
addrPort := netip.AddrPortFrom(ip, port)
|
||||
httpClient, cleanup, err := c.OpenHTTPS(ctx, host, addrPort)
|
||||
if err != nil {
|
||||
errs = append(errs, fmt.Errorf("for %s: %w", ip, err))
|
||||
continue
|
||||
}
|
||||
return httpClient, cleanup, nil
|
||||
}
|
||||
|
||||
return nil, nil, fmt.Errorf("opening HTTPS to %s: %w", hostname, errors.Join(errs...))
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
//go:build integration
|
||||
|
||||
package restrictednet
|
||||
|
||||
func ptrTo[T any](value T) *T {
|
||||
return &value
|
||||
}
|
||||
@@ -0,0 +1,202 @@
|
||||
package restrictednet
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/tls"
|
||||
"errors"
|
||||
"fmt"
|
||||
"net"
|
||||
"net/http"
|
||||
"net/netip"
|
||||
"os"
|
||||
"time"
|
||||
|
||||
"github.com/jsimonetti/rtnetlink"
|
||||
"github.com/qdm12/gluetun/internal/pmtud/constants"
|
||||
)
|
||||
|
||||
// OpenHTTPS opens temporary restrictive firewall output for one HTTPS destination.
|
||||
// The returned [*http.Client] must be used sequentially only, and each request must
|
||||
// have its response body fully read/discarded and then closed.
|
||||
// The returned cleanup function must be called to remove the temporary firewall rule and close connections.
|
||||
func (c *Client) OpenHTTPS(ctx context.Context, destinationTLSName string, destinationAddrPort netip.AddrPort,
|
||||
) (httpClient *http.Client, cleanup func() error, err error) {
|
||||
fd, sourceAddrPort, err := bindSourceConnection(destinationAddrPort.Addr())
|
||||
if err != nil {
|
||||
return nil, nil, fmt.Errorf("binding source port: %w", err)
|
||||
}
|
||||
|
||||
const remove = false
|
||||
err = c.firewall.AcceptOutputFromIPPortToIPPort(ctx, "tcp", c.outboundInterface,
|
||||
sourceAddrPort, destinationAddrPort, remove)
|
||||
if err != nil {
|
||||
closeFD(fd)
|
||||
return nil, nil, fmt.Errorf("allowing output traffic through firewall: %w", err)
|
||||
}
|
||||
|
||||
connection, err := connectSourceConnection(ctx, fd, destinationAddrPort)
|
||||
if err != nil {
|
||||
const remove = true
|
||||
_ = c.firewall.AcceptOutputFromIPPortToIPPort(context.Background(), "tcp", c.outboundInterface,
|
||||
sourceAddrPort, destinationAddrPort, remove)
|
||||
return nil, nil, fmt.Errorf("connecting source socket: %w", err)
|
||||
}
|
||||
|
||||
dial := makeDial(connection, destinationTLSName)
|
||||
httpClient = newHTTPSClient(destinationTLSName, dial)
|
||||
cleanup = func() error {
|
||||
var errs []error
|
||||
httpClient.CloseIdleConnections()
|
||||
err := connection.Close()
|
||||
if err != nil && !errors.Is(err, net.ErrClosed) {
|
||||
errs = append(errs, fmt.Errorf("closing connection: %w", err))
|
||||
}
|
||||
const remove = true
|
||||
err = c.firewall.AcceptOutputFromIPPortToIPPort(context.Background(), "tcp", c.outboundInterface,
|
||||
sourceAddrPort, destinationAddrPort, remove)
|
||||
if err != nil {
|
||||
errs = append(errs, fmt.Errorf("removing output traffic rule: %w", err))
|
||||
}
|
||||
if len(errs) > 0 {
|
||||
return errors.Join(errs...)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
return httpClient, cleanup, nil
|
||||
}
|
||||
|
||||
type dialFunc func(ctx context.Context, network, address string) (net.Conn, error)
|
||||
|
||||
func newHTTPSClient(destinationTLSName string, dial dialFunc) *http.Client {
|
||||
const timeout = 5 * time.Second
|
||||
transport := &http.Transport{
|
||||
MaxIdleConns: 1,
|
||||
MaxIdleConnsPerHost: 1,
|
||||
MaxConnsPerHost: 1,
|
||||
TLSClientConfig: &tls.Config{
|
||||
MinVersion: tls.VersionTLS12,
|
||||
ServerName: destinationTLSName,
|
||||
},
|
||||
DialContext: dial,
|
||||
}
|
||||
return &http.Client{
|
||||
Timeout: timeout,
|
||||
Transport: transport,
|
||||
}
|
||||
}
|
||||
|
||||
func makeDial(connection net.Conn, tlsName string) dialFunc {
|
||||
_, destinationPort, err := net.SplitHostPort(connection.RemoteAddr().String())
|
||||
if err != nil {
|
||||
panic(err) // connection remote address should always be in the form "host:port"
|
||||
}
|
||||
expectedAddress := net.JoinHostPort(tlsName, destinationPort)
|
||||
used := false
|
||||
return func(_ context.Context, network, address string) (net.Conn, error) {
|
||||
if used {
|
||||
return nil, errors.New("dial function called more than once")
|
||||
}
|
||||
used = true
|
||||
switch network {
|
||||
case "tcp", "tcp4", "tcp6":
|
||||
default:
|
||||
return nil, fmt.Errorf("unexpected dial network %q", network)
|
||||
}
|
||||
if address != expectedAddress {
|
||||
return nil, fmt.Errorf("unexpected dial address %q (expected %q)", address, expectedAddress)
|
||||
}
|
||||
return connection, nil
|
||||
}
|
||||
}
|
||||
|
||||
func bindSourceConnection(destinationIP netip.Addr) (fd int, sourceAddr netip.AddrPort, err error) {
|
||||
sourceIP, err := sourceIPForDestination(destinationIP)
|
||||
if err != nil {
|
||||
return 0, netip.AddrPort{}, fmt.Errorf("finding source IP: %w", err)
|
||||
}
|
||||
|
||||
family := constants.AF_INET
|
||||
if sourceIP.Is6() {
|
||||
family = constants.AF_INET6
|
||||
}
|
||||
|
||||
fd, err = newTCPSockStream(family)
|
||||
if err != nil {
|
||||
return 0, netip.AddrPort{}, fmt.Errorf("creating socket: %w", err)
|
||||
}
|
||||
|
||||
bindAddrPort := netip.AddrPortFrom(sourceIP, 0)
|
||||
err = bindFD(fd, bindAddrPort)
|
||||
if err != nil {
|
||||
closeFD(fd)
|
||||
return 0, netip.AddrPort{}, fmt.Errorf("binding socket: %w", err)
|
||||
}
|
||||
|
||||
sourceAddr, err = fdToSourceAddr(fd)
|
||||
if err != nil {
|
||||
closeFD(fd)
|
||||
return 0, netip.AddrPort{}, fmt.Errorf("getting source address: %w", err)
|
||||
}
|
||||
|
||||
return fd, sourceAddr, nil
|
||||
}
|
||||
|
||||
func connectSourceConnection(ctx context.Context, fd int, destinationAddrPort netip.AddrPort) (
|
||||
connection net.Conn, err error,
|
||||
) {
|
||||
err = connectFD(ctx, fd, destinationAddrPort)
|
||||
if err != nil {
|
||||
closeFD(fd)
|
||||
return nil, fmt.Errorf("connecting socket: %w", err)
|
||||
}
|
||||
|
||||
file := os.NewFile(uintptr(fd), "")
|
||||
if file == nil {
|
||||
closeFD(fd)
|
||||
return nil, fmt.Errorf("creating socket file for destination %s", destinationAddrPort)
|
||||
}
|
||||
defer file.Close()
|
||||
|
||||
connection, err = net.FileConn(file)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("wrapping socket connection: %w", err)
|
||||
}
|
||||
|
||||
return connection, nil
|
||||
}
|
||||
|
||||
func sourceIPForDestination(destinationIP netip.Addr) (srcIP netip.Addr, err error) {
|
||||
conn, err := rtnetlink.Dial(nil)
|
||||
if err != nil {
|
||||
return netip.Addr{}, err
|
||||
}
|
||||
defer conn.Close()
|
||||
|
||||
family := uint8(constants.AF_INET)
|
||||
if destinationIP.Is6() {
|
||||
family = constants.AF_INET6
|
||||
}
|
||||
|
||||
requestMessage := &rtnetlink.RouteMessage{
|
||||
Family: family,
|
||||
Attributes: rtnetlink.RouteAttributes{
|
||||
Dst: destinationIP.AsSlice(),
|
||||
},
|
||||
}
|
||||
messages, err := conn.Route.Get(requestMessage)
|
||||
if err != nil {
|
||||
return netip.Addr{}, fmt.Errorf("getting routes to %s: %w", destinationIP, err)
|
||||
}
|
||||
|
||||
for _, message := range messages {
|
||||
if message.Attributes.Src == nil {
|
||||
continue
|
||||
}
|
||||
if message.Attributes.Src.To4() == nil {
|
||||
return netip.AddrFrom16([16]byte(message.Attributes.Src)), nil
|
||||
}
|
||||
return netip.AddrFrom4([4]byte(message.Attributes.Src)), nil
|
||||
}
|
||||
|
||||
return netip.Addr{}, fmt.Errorf("no route to %s", destinationIP)
|
||||
}
|
||||
@@ -0,0 +1,117 @@
|
||||
//go:build integration
|
||||
|
||||
package restrictednet
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"net/netip"
|
||||
"testing"
|
||||
|
||||
"github.com/qdm12/dns/v2/pkg/provider"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
"go.uber.org/mock/gomock"
|
||||
)
|
||||
|
||||
type listenAddrPortMatcher struct {
|
||||
expected netip.AddrPort
|
||||
}
|
||||
|
||||
func (m listenAddrPortMatcher) Matches(x any) bool {
|
||||
ip, ok := x.(netip.AddrPort)
|
||||
if !ok {
|
||||
return false
|
||||
}
|
||||
if m.expected.IsValid() {
|
||||
return ip == m.expected
|
||||
}
|
||||
return ip.IsValid() && ip.Addr().IsValid() && ip.Port() > 0
|
||||
}
|
||||
|
||||
func (m listenAddrPortMatcher) String() string {
|
||||
if m.expected.IsValid() {
|
||||
return "is the same as " + m.expected.String()
|
||||
}
|
||||
return "is a valid netip.AddrPort with a valid IP and non-zero port"
|
||||
}
|
||||
|
||||
type destinationAddrPortMatcher struct {
|
||||
expected netip.AddrPort
|
||||
}
|
||||
|
||||
func (m destinationAddrPortMatcher) Matches(x any) bool {
|
||||
ip, ok := x.(netip.AddrPort)
|
||||
if !ok {
|
||||
return false
|
||||
}
|
||||
if m.expected.IsValid() {
|
||||
return ip == m.expected
|
||||
}
|
||||
return ip.IsValid() && ip.Port() == m.expected.Port()
|
||||
}
|
||||
|
||||
func (m destinationAddrPortMatcher) String() string {
|
||||
if m.expected.IsValid() {
|
||||
return "is the same as " + m.expected.String()
|
||||
}
|
||||
return "matches the port " + fmt.Sprint(m.expected.Port())
|
||||
}
|
||||
|
||||
func Test_Client_OpenHTTPS(t *testing.T) {
|
||||
t.Parallel()
|
||||
ctx := t.Context()
|
||||
ctrl := gomock.NewController(t)
|
||||
|
||||
const destinationTLSName = "one.one.one.one"
|
||||
destinationAddrPort := netip.AddrPortFrom(netip.AddrFrom4([4]byte{1, 1, 1, 1}), 443)
|
||||
|
||||
firewall := NewMockFirewall(ctrl)
|
||||
sourceMatcher := listenAddrPortMatcher{}
|
||||
firewall.EXPECT().AcceptOutputFromIPPortToIPPort(
|
||||
ctx, "tcp", "eth0", sourceMatcher, destinationAddrPort, false,
|
||||
).DoAndReturn(func(_ context.Context,
|
||||
_, _ string, source, _ netip.AddrPort, _ bool,
|
||||
) error {
|
||||
sourceMatcher.expected = source
|
||||
return nil
|
||||
})
|
||||
firewall.EXPECT().AcceptOutputFromIPPortToIPPort(
|
||||
context.Background(), "tcp", "eth0", sourceMatcher, destinationAddrPort, true,
|
||||
).Return(nil)
|
||||
|
||||
const ipv6Supported = false
|
||||
upstreamResolvers := []provider.Provider{provider.Google()}
|
||||
settings := Settings{
|
||||
Firewall: firewall,
|
||||
DefaultInterface: "eth0",
|
||||
IPv6Supported: ptrTo(ipv6Supported),
|
||||
UpstreamResolvers: upstreamResolvers,
|
||||
}
|
||||
client := New(settings)
|
||||
|
||||
httpClient, cleanup, err := client.OpenHTTPS(ctx, destinationTLSName, destinationAddrPort)
|
||||
require.NoError(t, err)
|
||||
require.NotNil(t, httpClient)
|
||||
require.NotNil(t, cleanup)
|
||||
|
||||
const requests = 2
|
||||
|
||||
for range requests {
|
||||
request, err := http.NewRequestWithContext(ctx, http.MethodGet, "https://"+destinationTLSName, nil)
|
||||
require.NoError(t, err)
|
||||
|
||||
response, err := httpClient.Do(request)
|
||||
require.NoError(t, err)
|
||||
_, err = io.Copy(io.Discard, response.Body)
|
||||
require.NoError(t, err)
|
||||
err = response.Body.Close()
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, http.StatusOK, response.StatusCode)
|
||||
}
|
||||
|
||||
err = cleanup()
|
||||
require.NoError(t, err)
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
package restrictednet
|
||||
|
||||
import (
|
||||
"context"
|
||||
"net/netip"
|
||||
)
|
||||
|
||||
type Firewall interface {
|
||||
AcceptOutputFromIPPortToIPPort(ctx context.Context,
|
||||
protocol, intf string, source, destination netip.AddrPort, remove bool,
|
||||
) error
|
||||
}
|
||||
@@ -0,0 +1,121 @@
|
||||
//go:build linux
|
||||
|
||||
package restrictednet
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"net/netip"
|
||||
"time"
|
||||
|
||||
"golang.org/x/sys/unix"
|
||||
)
|
||||
|
||||
func closeFD(fd int) {
|
||||
unix.Close(fd)
|
||||
}
|
||||
|
||||
func newTCPSockStream(family int) (fd int, err error) {
|
||||
fd, err = unix.Socket(family, unix.SOCK_STREAM, unix.IPPROTO_TCP)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
err = unix.SetNonblock(fd, true)
|
||||
if err != nil {
|
||||
_ = unix.Close(fd)
|
||||
return 0, err
|
||||
}
|
||||
return fd, nil
|
||||
}
|
||||
|
||||
func bindFD(fd int, address netip.AddrPort) error {
|
||||
bindAddr := makeSockAddr(address)
|
||||
return unix.Bind(fd, bindAddr)
|
||||
}
|
||||
|
||||
func connectFD(ctx context.Context, fd int, destination netip.AddrPort) error {
|
||||
err := unix.Connect(fd, makeSockAddr(destination))
|
||||
switch {
|
||||
case err == nil:
|
||||
return nil
|
||||
case !errors.Is(err, unix.EINPROGRESS):
|
||||
return err
|
||||
}
|
||||
|
||||
for {
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
return ctx.Err()
|
||||
default:
|
||||
bitsIndex := fd / 64 //nolint:mnd
|
||||
if bitsIndex >= len(unix.FdSet{}.Bits) {
|
||||
return fmt.Errorf("fd %d exceeds unix.Select FdSet capacity", fd)
|
||||
}
|
||||
wset := &unix.FdSet{}
|
||||
wset.Bits[bitsIndex] |= 1 << (uint64(fd) % 64) //nolint:gosec,mnd
|
||||
eset := &unix.FdSet{}
|
||||
eset.Bits[bitsIndex] |= 1 << (uint64(fd) % 64) //nolint:gosec,mnd
|
||||
const selectTimeout = 50 * time.Millisecond
|
||||
timeval := unix.NsecToTimeval(int64(selectTimeout))
|
||||
|
||||
// Wait for the FD to become writable or hit an error state
|
||||
n, err := unix.Select(fd+1, nil, wset, eset, &timeval)
|
||||
if err != nil {
|
||||
if errors.Is(err, unix.EINTR) {
|
||||
continue // Syscall interrupted, try again
|
||||
}
|
||||
return fmt.Errorf("select error: %w", err)
|
||||
} else if n == 0 {
|
||||
continue // no status change yet
|
||||
}
|
||||
|
||||
// Check if the socket encountered an error
|
||||
n, err = unix.GetsockoptInt(fd, unix.SOL_SOCKET, unix.SO_ERROR)
|
||||
if err != nil {
|
||||
return fmt.Errorf("getsockopt error: %w", err)
|
||||
} else if n != 0 {
|
||||
return fmt.Errorf("connect failed asynchronously: %w", unix.Errno(n))
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func fdToSourceAddr(fd int) (sourceAddrPort netip.AddrPort, err error) {
|
||||
sockAddr, err := unix.Getsockname(fd)
|
||||
if err != nil {
|
||||
return netip.AddrPort{}, fmt.Errorf("getting sockname: %w", err)
|
||||
}
|
||||
|
||||
sourceAddrPort, err = sockAddrToAddrPort(sockAddr)
|
||||
if err != nil {
|
||||
return netip.AddrPort{}, err
|
||||
}
|
||||
return sourceAddrPort, nil
|
||||
}
|
||||
|
||||
func makeSockAddr(addressPort netip.AddrPort) unix.Sockaddr {
|
||||
if addressPort.Addr().Is4() {
|
||||
return &unix.SockaddrInet4{
|
||||
Port: int(addressPort.Port()),
|
||||
Addr: addressPort.Addr().As4(),
|
||||
}
|
||||
}
|
||||
return &unix.SockaddrInet6{
|
||||
Port: int(addressPort.Port()),
|
||||
Addr: addressPort.Addr().As16(),
|
||||
}
|
||||
}
|
||||
|
||||
func sockAddrToAddrPort(sockAddr unix.Sockaddr) (addrPort netip.AddrPort, err error) {
|
||||
switch typedSockAddr := sockAddr.(type) {
|
||||
case *unix.SockaddrInet4:
|
||||
return netip.AddrPortFrom(netip.AddrFrom4(typedSockAddr.Addr), uint16(typedSockAddr.Port)), nil //nolint:gosec
|
||||
case *unix.SockaddrInet6:
|
||||
return netip.AddrPortFrom(netip.AddrFrom16(typedSockAddr.Addr), uint16(typedSockAddr.Port)), nil //nolint:gosec
|
||||
default:
|
||||
return netip.AddrPort{}, fmt.Errorf("unexpected socket address type %T", typedSockAddr)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
package restrictednet
|
||||
|
||||
//go:generate mockgen -destination=mocks_test.go -package=$GOPACKAGE . Firewall
|
||||
@@ -0,0 +1,56 @@
|
||||
// Code generated by MockGen. DO NOT EDIT.
|
||||
// Source: github.com/qdm12/gluetun/internal/restrictednet (interfaces: Firewall)
|
||||
//
|
||||
// Generated by this command:
|
||||
//
|
||||
// mockgen -destination=mocks_test.go -package=restrictednet . Firewall
|
||||
//
|
||||
|
||||
// Package restrictednet is a generated GoMock package.
|
||||
package restrictednet
|
||||
|
||||
import (
|
||||
context "context"
|
||||
netip "net/netip"
|
||||
reflect "reflect"
|
||||
|
||||
gomock "go.uber.org/mock/gomock"
|
||||
)
|
||||
|
||||
// MockFirewall is a mock of Firewall interface.
|
||||
type MockFirewall struct {
|
||||
ctrl *gomock.Controller
|
||||
recorder *MockFirewallMockRecorder
|
||||
isgomock struct{}
|
||||
}
|
||||
|
||||
// MockFirewallMockRecorder is the mock recorder for MockFirewall.
|
||||
type MockFirewallMockRecorder struct {
|
||||
mock *MockFirewall
|
||||
}
|
||||
|
||||
// NewMockFirewall creates a new mock instance.
|
||||
func NewMockFirewall(ctrl *gomock.Controller) *MockFirewall {
|
||||
mock := &MockFirewall{ctrl: ctrl}
|
||||
mock.recorder = &MockFirewallMockRecorder{mock}
|
||||
return mock
|
||||
}
|
||||
|
||||
// EXPECT returns an object that allows the caller to indicate expected use.
|
||||
func (m *MockFirewall) EXPECT() *MockFirewallMockRecorder {
|
||||
return m.recorder
|
||||
}
|
||||
|
||||
// AcceptOutputFromIPPortToIPPort mocks base method.
|
||||
func (m *MockFirewall) AcceptOutputFromIPPortToIPPort(ctx context.Context, protocol, intf string, source, destination netip.AddrPort, remove bool) error {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "AcceptOutputFromIPPortToIPPort", ctx, protocol, intf, source, destination, remove)
|
||||
ret0, _ := ret[0].(error)
|
||||
return ret0
|
||||
}
|
||||
|
||||
// AcceptOutputFromIPPortToIPPort indicates an expected call of AcceptOutputFromIPPortToIPPort.
|
||||
func (mr *MockFirewallMockRecorder) AcceptOutputFromIPPortToIPPort(ctx, protocol, intf, source, destination, remove any) *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "AcceptOutputFromIPPortToIPPort", reflect.TypeOf((*MockFirewall)(nil).AcceptOutputFromIPPortToIPPort), ctx, protocol, intf, source, destination, remove)
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
//go:build !linux
|
||||
|
||||
package restrictednet
|
||||
|
||||
import (
|
||||
"context"
|
||||
"net/netip"
|
||||
)
|
||||
|
||||
func closeFD(fd int) {
|
||||
panic("not implemented")
|
||||
}
|
||||
|
||||
func newTCPSockStream(family int) (fd int, err error) {
|
||||
panic("not implemented")
|
||||
}
|
||||
|
||||
func bindFD(fd int, address netip.AddrPort) error {
|
||||
panic("not implemented")
|
||||
}
|
||||
|
||||
func connectFD(ctx context.Context, fd int, destination netip.AddrPort) error {
|
||||
panic("not implemented")
|
||||
}
|
||||
|
||||
func fdToSourceAddr(fd int) (sourceAddrPort netip.AddrPort, err error) {
|
||||
panic("not implemented")
|
||||
}
|
||||
@@ -0,0 +1,201 @@
|
||||
package restrictednet
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"net/netip"
|
||||
"net/url"
|
||||
"strconv"
|
||||
|
||||
"github.com/miekg/dns"
|
||||
)
|
||||
|
||||
// ResolveName resolves the given host name to IP addresses using DoH servers,
|
||||
// while opening temporary restrictive firewall rules for HTTPS traffic to DoH servers.
|
||||
// The host must be a single well-formed domain name, without port or path.
|
||||
func (c *Client) ResolveName(ctx context.Context, host string) (
|
||||
resolvedAddresses []netip.Addr, err error,
|
||||
) {
|
||||
const maxTypes = 2
|
||||
questionTypes := make([]uint16, 0, maxTypes)
|
||||
if c.ipv6Supported {
|
||||
questionTypes = append(questionTypes, dns.TypeAAAA)
|
||||
}
|
||||
questionTypes = append(questionTypes, dns.TypeA)
|
||||
|
||||
var addresses []netip.Addr
|
||||
errs := make([]error, 0, len(questionTypes))
|
||||
for _, questionType := range questionTypes {
|
||||
answerAddresses, err := c.resolveOneQuestionType(ctx, host, questionType)
|
||||
if err != nil {
|
||||
errs = append(errs, err)
|
||||
continue
|
||||
}
|
||||
addresses = append(addresses, answerAddresses...)
|
||||
}
|
||||
|
||||
switch {
|
||||
case len(addresses) > 0:
|
||||
return addresses, nil
|
||||
case len(errs) == 0:
|
||||
return nil, nil // no address found
|
||||
default: // errors
|
||||
return nil, fmt.Errorf("resolving host %q: %w", host, errors.Join(errs...))
|
||||
}
|
||||
}
|
||||
|
||||
func (c *Client) resolveOneQuestionType(ctx context.Context,
|
||||
host string, questionType uint16,
|
||||
) (addresses []netip.Addr, err error) {
|
||||
queryMessage := &dns.Msg{}
|
||||
queryMessage.SetQuestion(dns.Fqdn(host), questionType)
|
||||
queryWire, err := queryMessage.Pack()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("packing DNS query: %w", err)
|
||||
}
|
||||
|
||||
// Try every DoH server and every of each of their IP until we get a non-empty
|
||||
// successful response.
|
||||
errs := make([]error, 0)
|
||||
for _, dohServer := range c.dohServers {
|
||||
dohURL, err := url.Parse(dohServer.URL)
|
||||
if err != nil {
|
||||
errs = append(errs,
|
||||
fmt.Errorf("parsing DoH server URL %s: %w", dohServer.URL, err))
|
||||
continue
|
||||
}
|
||||
|
||||
dohServerIPs := make([]netip.Addr, 0, len(dohServer.IPv4)+len(dohServer.IPv6))
|
||||
if c.ipv6Supported {
|
||||
// Prefer IPv6 addresses if IPv6 is supported
|
||||
dohServerIPs = append(dohServerIPs, dohServer.IPv6...)
|
||||
}
|
||||
dohServerIPs = append(dohServerIPs, dohServer.IPv4...)
|
||||
|
||||
for _, dohServerIP := range dohServerIPs {
|
||||
const defaultDoHPort uint16 = 443
|
||||
port := defaultDoHPort
|
||||
if portStr := dohURL.Port(); portStr != "" {
|
||||
port, err = parseDestinationPort(portStr)
|
||||
if err != nil {
|
||||
errs = append(errs, fmt.Errorf("parsing DoH server port: %w", err))
|
||||
continue
|
||||
}
|
||||
}
|
||||
dohServerAddrPort := netip.AddrPortFrom(dohServerIP, port)
|
||||
responseMessage, err := c.doHQuery(ctx, queryWire, dohURL, dohServerAddrPort)
|
||||
switch {
|
||||
case err != nil:
|
||||
errs = append(errs, fmt.Errorf("querying DoH server %q (%s): %w",
|
||||
dohServer.URL, dohServerAddrPort, err))
|
||||
continue
|
||||
case responseMessage.Rcode != dns.RcodeSuccess:
|
||||
errs = append(errs, fmt.Errorf("querying DoH server %q (%s): DNS rcode %s",
|
||||
dohServer.URL, dohServerAddrPort, dns.RcodeToString[responseMessage.Rcode]))
|
||||
continue
|
||||
}
|
||||
addresses := answersToNetipAddrs(responseMessage)
|
||||
if len(addresses) == 0 {
|
||||
continue
|
||||
}
|
||||
return addresses, nil
|
||||
}
|
||||
}
|
||||
|
||||
if len(errs) == 0 {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
return nil, fmt.Errorf("resolving %s %s: %w",
|
||||
dns.TypeToString[questionType], host, errors.Join(errs...))
|
||||
}
|
||||
|
||||
func (c *Client) doHQuery(ctx context.Context, queryWire []byte,
|
||||
dohURL *url.URL, dohServerAddrPort netip.AddrPort,
|
||||
) (responseMessage *dns.Msg, err error) {
|
||||
httpClient, cleanup, err := c.OpenHTTPS(ctx, dohURL.Hostname(), dohServerAddrPort)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("opening https connection: %w", err)
|
||||
}
|
||||
defer func() {
|
||||
closeErr := cleanup()
|
||||
if err == nil && closeErr != nil {
|
||||
err = fmt.Errorf("cleaning up https connection: %w", closeErr)
|
||||
}
|
||||
}()
|
||||
|
||||
requestBody := bytes.NewReader(queryWire)
|
||||
request, err := http.NewRequestWithContext(ctx, http.MethodPost, dohURL.String(), requestBody)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("creating request: %w", err)
|
||||
}
|
||||
request.Header.Set("Content-Type", "application/dns-message")
|
||||
request.Header.Set("Accept", "application/dns-message")
|
||||
|
||||
response, err := httpClient.Do(request)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
responseData, err := io.ReadAll(response.Body)
|
||||
if err != nil {
|
||||
_ = response.Body.Close()
|
||||
return nil, fmt.Errorf("reading response body: %w", err)
|
||||
}
|
||||
|
||||
err = response.Body.Close()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("closing response body: %w", err)
|
||||
}
|
||||
|
||||
if response.StatusCode != http.StatusOK {
|
||||
return nil, fmt.Errorf("response status code is %s (data length %d)",
|
||||
response.Status, len(responseData))
|
||||
}
|
||||
|
||||
responseMessage = new(dns.Msg)
|
||||
err = responseMessage.Unpack(responseData)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("parsing DoH response: %w", err)
|
||||
}
|
||||
|
||||
return responseMessage, nil
|
||||
}
|
||||
|
||||
func answersToNetipAddrs(message *dns.Msg) (addresses []netip.Addr) {
|
||||
if message == nil {
|
||||
return nil
|
||||
}
|
||||
addresses = make([]netip.Addr, 0, len(message.Answer))
|
||||
for _, answer := range message.Answer {
|
||||
switch record := answer.(type) {
|
||||
case *dns.A:
|
||||
address, ok := netip.AddrFromSlice(record.A)
|
||||
if ok {
|
||||
addresses = append(addresses, address.Unmap())
|
||||
}
|
||||
case *dns.AAAA:
|
||||
address, ok := netip.AddrFromSlice(record.AAAA)
|
||||
if ok {
|
||||
addresses = append(addresses, address)
|
||||
}
|
||||
}
|
||||
}
|
||||
return addresses
|
||||
}
|
||||
|
||||
func parseDestinationPort(portStr string) (port uint16, err error) {
|
||||
portUint, err := strconv.ParseUint(portStr, 10, 16)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
if portUint == 0 {
|
||||
return 0, errors.New("port cannot be 0")
|
||||
}
|
||||
return uint16(portUint), nil
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user