mirror of
https://github.com/qdm12/gluetun.git
synced 2026-07-22 18:36:27 +02:00
Compare commits
41 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 3fa3d3e4a5 | |||
| 08e0403d2b | |||
| 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 | |||
| 7f9cd0fd0c | |||
| 1a25f7377a | |||
| 691dc3b0bf | |||
| 5fed7f70ed | |||
| eb9916f0ac | |||
| 2210a0e9ad | |||
| f8a677a424 |
@@ -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
|
||||
|
||||
@@ -4,12 +4,12 @@ Contributions are [released](https://help.github.com/articles/github-terms-of-se
|
||||
|
||||
## Submitting a pull request
|
||||
|
||||
1. [Fork](https://github.com/qdm12/gluetun/fork) and clone the repository
|
||||
1. [Fork](https://github.com/passteque/gluetun/fork) and clone the repository
|
||||
1. Create a new branch `git checkout -b my-branch-name`
|
||||
1. Modify the code
|
||||
1. Ensure the docker build succeeds `docker build .` (you might need `export DOCKER_BUILDKIT=1`)
|
||||
1. Commit your modifications
|
||||
1. Push to your fork and [submit a pull request](https://github.com/qdm12/gluetun/compare)
|
||||
1. Push to your fork and [submit a pull request](https://github.com/passteque/gluetun/compare)
|
||||
|
||||
## Resources
|
||||
|
||||
|
||||
@@ -4,8 +4,8 @@ contact_links:
|
||||
url: https://github.com/qdm12/gluetun-wiki/issues/new/choose
|
||||
about: Please create an issue on the gluetun-wiki repository.
|
||||
- name: Configuration help?
|
||||
url: https://github.com/qdm12/gluetun/discussions/new/choose
|
||||
url: https://github.com/passteque/gluetun/discussions/new/choose
|
||||
about: Please create a Github discussion.
|
||||
- name: Unraid template issue
|
||||
url: https://github.com/qdm12/gluetun/discussions/550
|
||||
url: https://github.com/passteque/gluetun/discussions/550
|
||||
about: Please read the relevant Github discussion.
|
||||
|
||||
+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"
|
||||
|
||||
@@ -140,6 +140,8 @@
|
||||
color: "ffc7ea"
|
||||
- name: "Category: Shadowsocks 🔁"
|
||||
color: "ffc7ea"
|
||||
- name: "Category: Socks5 proxy 🔁"
|
||||
color: "ffc7ea"
|
||||
- name: "Category: control server ⚙️"
|
||||
color: "ffc7ea"
|
||||
- name: "Category: kernel 🧠"
|
||||
|
||||
@@ -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
-13
@@ -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
|
||||
|
||||
@@ -92,13 +95,13 @@ jobs:
|
||||
|
||||
verify-private:
|
||||
if: |
|
||||
github.repository == 'qdm12/gluetun' &&
|
||||
github.repository == 'passteque/gluetun' &&
|
||||
(
|
||||
github.event_name == 'push' ||
|
||||
github.event_name == 'release' ||
|
||||
(github.event_name == 'pull_request' && github.event.pull_request.head.repo.full_name == github.repository && github.actor != 'dependabot[bot]')
|
||||
)
|
||||
needs: [ verify ]
|
||||
needs: [verify]
|
||||
runs-on: ubuntu-latest
|
||||
environment: secrets
|
||||
steps:
|
||||
@@ -120,7 +123,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 +133,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 +146,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:
|
||||
@@ -163,18 +169,19 @@ jobs:
|
||||
|
||||
publish:
|
||||
if: |
|
||||
github.repository == 'qdm12/gluetun' &&
|
||||
github.repository == 'passteque/gluetun' &&
|
||||
(
|
||||
github.event_name == 'push' ||
|
||||
github.event_name == 'release' ||
|
||||
(github.event_name == 'pull_request' && github.event.pull_request.head.repo.full_name == github.repository && github.actor != 'dependabot[bot]')
|
||||
)
|
||||
needs: [ verify, verify-private, codeql ]
|
||||
needs: [verify, verify-private, codeql]
|
||||
permissions:
|
||||
actions: read
|
||||
contents: read
|
||||
packages: write
|
||||
runs-on: ubuntu-latest
|
||||
environment: secrets
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
|
||||
@@ -209,7 +216,7 @@ jobs:
|
||||
with:
|
||||
registry: ghcr.io
|
||||
username: qdm12
|
||||
password: ${{ github.token }}
|
||||
password: ${{ secrets.GHCR_PAT }}
|
||||
|
||||
- name: Short commit
|
||||
id: shortcommit
|
||||
|
||||
@@ -11,12 +11,17 @@ 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
|
||||
permissions:
|
||||
actions: read
|
||||
contents: read
|
||||
environment: secrets
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
|
||||
@@ -38,7 +43,7 @@ jobs:
|
||||
config-file: .github/workflows/configs/mlc-config.json
|
||||
|
||||
- uses: peter-evans/dockerhub-description@v5
|
||||
if: github.repository == 'qdm12/gluetun' && github.event_name == 'push'
|
||||
if: github.repository == 'passteque/gluetun' && github.event_name == 'push'
|
||||
with:
|
||||
username: qmcgaw
|
||||
password: ${{ secrets.DOCKERHUB_PASSWORD }}
|
||||
|
||||
@@ -1,98 +0,0 @@
|
||||
name: Update servers list
|
||||
on:
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
provider:
|
||||
description: "VPN Provider to update"
|
||||
required: true
|
||||
default: "all"
|
||||
type: choice
|
||||
options:
|
||||
- all
|
||||
- airvpn
|
||||
- cyberghost
|
||||
- expressvpn
|
||||
- fastestvpn
|
||||
- giganews
|
||||
- hidemyass
|
||||
- ipvanish
|
||||
- ivpn
|
||||
- mullvad
|
||||
- nordvpn
|
||||
- perfect privacy
|
||||
- privado
|
||||
- private internet access
|
||||
- privatevpn
|
||||
- protonvpn
|
||||
- purevpn
|
||||
- slickvpn
|
||||
- surfshark
|
||||
- torguard
|
||||
- vpnsecure
|
||||
- vpn unlimited
|
||||
- vyprvpn
|
||||
- windscribe
|
||||
schedule:
|
||||
- cron: "11 3 1 */2 *" # Run at 03:11 on the 1st of every 2nd month
|
||||
jobs:
|
||||
update-servers-list:
|
||||
if: github.repository == 'qdm12/gluetun'
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
actions: read
|
||||
contents: write
|
||||
pull-requests: write
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
|
||||
- uses: actions/setup-go@v6
|
||||
with:
|
||||
go-version-file: go.mod
|
||||
|
||||
- name: Update servers list
|
||||
run: |
|
||||
SELECTED_PROVIDER="${{ github.event.inputs.provider || 'all' }}"
|
||||
|
||||
if [ "$SELECTED_PROVIDER" = "all" ]; then
|
||||
FLAGS="-all"
|
||||
else
|
||||
FLAGS="-providers $SELECTED_PROVIDER"
|
||||
fi
|
||||
|
||||
go run ./cmd/gluetun/main.go update $FLAGS \
|
||||
-maintainer \
|
||||
-proton-email "${{ secrets.PROTON_EMAIL }}" \
|
||||
-proton-password "${{ secrets.PROTON_PASSWORD }}"
|
||||
|
||||
- name: Check for changes
|
||||
run: |
|
||||
if git diff --exit-code internal/storage/servers.json >/dev/null; then
|
||||
echo "Error: internal/storage/servers.json was not modified."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
- name: Check no other file changes
|
||||
run: |
|
||||
if ! git diff --exit-code --quiet ':!internal/storage/servers.json'; then
|
||||
echo "Error: Unexpected changes detected in files other than servers.json"
|
||||
git status --short
|
||||
exit 1
|
||||
fi
|
||||
|
||||
- name: Create Pull Request
|
||||
id: createpr
|
||||
uses: peter-evans/create-pull-request@v8
|
||||
with:
|
||||
branch-suffix: timestamp
|
||||
branch: bot/update-servers-list
|
||||
base: master
|
||||
delete-branch: true
|
||||
title: "feat(providers/${{ github.event.inputs.provider || 'all' }}): servers data update"
|
||||
body: |
|
||||
This PR was automatically generated by the [Update servers list](${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}) workflow run.
|
||||
|
||||
# - name: Merge Pull Request
|
||||
# env:
|
||||
# GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
# run: |
|
||||
# gh pr merge ${{ steps.createpr.outputs.pull-request-number }} --auto -m -d
|
||||
@@ -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"
|
||||
},
|
||||
|
||||
+9
-5
@@ -72,9 +72,9 @@ LABEL \
|
||||
org.opencontainers.image.created=$CREATED \
|
||||
org.opencontainers.image.version=$VERSION \
|
||||
org.opencontainers.image.revision=$COMMIT \
|
||||
org.opencontainers.image.url="https://github.com/qdm12/gluetun" \
|
||||
org.opencontainers.image.documentation="https://github.com/qdm12/gluetun" \
|
||||
org.opencontainers.image.source="https://github.com/qdm12/gluetun" \
|
||||
org.opencontainers.image.url="https://github.com/passteque/gluetun" \
|
||||
org.opencontainers.image.documentation="https://github.com/passteque/gluetun" \
|
||||
org.opencontainers.image.source="https://github.com/passteque/gluetun" \
|
||||
org.opencontainers.image.title="VPN swiss-knife like client for multiple VPN providers" \
|
||||
org.opencontainers.image.description="VPN swiss-knife like client to tunnel to multiple VPN servers using OpenVPN, IPtables, DNS over TLS, Shadowsocks, an HTTP proxy and Alpine Linux"
|
||||
ENV VPN_SERVICE_PROVIDER=pia \
|
||||
@@ -218,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= \
|
||||
@@ -240,6 +239,11 @@ ENV VPN_SERVICE_PROVIDER=pia \
|
||||
SHADOWSOCKS_PASSWORD= \
|
||||
SHADOWSOCKS_PASSWORD_SECRETFILE=/run/secrets/shadowsocks_password \
|
||||
SHADOWSOCKS_CIPHER=chacha20-ietf-poly1305 \
|
||||
# Socks5
|
||||
SOCKS5_ENABLED=off \
|
||||
SOCKS5_LISTENING_ADDRESS=":1080" \
|
||||
SOCKS5_USER= \
|
||||
SOCKS5_PASSWORD= \
|
||||
# Control server
|
||||
HTTP_CONTROL_SERVER_LOG=on \
|
||||
HTTP_CONTROL_SERVER_ADDRESS=":8000" \
|
||||
@@ -271,7 +275,7 @@ ENV VPN_SERVICE_PROVIDER=pia \
|
||||
PUID=1000 \
|
||||
PGID=1000
|
||||
ENTRYPOINT ["/gluetun-entrypoint"]
|
||||
EXPOSE 8000/tcp 8888/tcp 8388/tcp 8388/udp
|
||||
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 && \
|
||||
|
||||
@@ -6,9 +6,9 @@ Lightweight swiss-army-knife-like VPN client to multiple VPN service providers
|
||||
|
||||
🗯️ this repository will be migrated to [github.com/passteque/gluetun](https://github.com/passteque/gluetun) on 2026-05-21, which is a Github organization under my sole control, so don't get alarmed if you get redirected in the coming days 😉 Reason being migrating Github sponsors to the Open source collective due to my personal situation, basically annoying paperwork. On the plus side, it will be more transparent and funds donated will only be used for the project. The Docker image names will remain the same.
|
||||
|
||||

|
||||

|
||||
|
||||
[](https://github.com/qdm12/gluetun/actions/workflows/ci.yml)
|
||||
[](https://github.com/passteque/gluetun/actions/workflows/ci.yml)
|
||||
|
||||
[](https://hub.docker.com/r/qmcgaw/gluetun)
|
||||
[](https://hub.docker.com/r/qmcgaw/gluetun)
|
||||
@@ -16,23 +16,23 @@ Lightweight swiss-army-knife-like VPN client to multiple VPN service providers
|
||||
[](https://hub.docker.com/r/qmcgaw/gluetun)
|
||||
[](https://hub.docker.com/r/qmcgaw/gluetun)
|
||||
|
||||

|
||||

|
||||

|
||||
[](https://hub.docker.com/r/qmcgaw/gluetun/tags?page=1&ordering=last_updated)
|
||||

|
||||

|
||||

|
||||

|
||||
|
||||
[](https://hub.docker.com/r/qmcgaw/gluetun/tags)
|
||||
|
||||
[](https://github.com/qdm12/gluetun/commits/master)
|
||||
[](https://github.com/qdm12/gluetun/graphs/contributors)
|
||||
[](https://github.com/qdm12/gluetun/pulls?q=is%3Apr+is%3Aclosed)
|
||||
[](https://github.com/qdm12/gluetun/issues)
|
||||
[](https://github.com/qdm12/gluetun/issues?q=is%3Aissue+is%3Aclosed)
|
||||
[](https://github.com/passteque/gluetun/commits/master)
|
||||
[](https://github.com/passteque/gluetun/graphs/contributors)
|
||||
[](https://github.com/passteque/gluetun/pulls?q=is%3Apr+is%3Aclosed)
|
||||
[](https://github.com/passteque/gluetun/issues)
|
||||
[](https://github.com/passteque/gluetun/issues?q=is%3Aissue+is%3Aclosed)
|
||||
|
||||

|
||||

|
||||

|
||||

|
||||

|
||||

|
||||
|
||||

|
||||
|
||||
@@ -42,10 +42,10 @@ Lightweight swiss-army-knife-like VPN client to multiple VPN service providers
|
||||
- [Features](#features)
|
||||
- Problem?
|
||||
- Check the Wiki [common errors](https://github.com/qdm12/gluetun-wiki/tree/main/errors) and [faq](https://github.com/qdm12/gluetun-wiki/tree/main/faq)
|
||||
- [Start a discussion](https://github.com/qdm12/gluetun/discussions)
|
||||
- [Fix the Unraid template](https://github.com/qdm12/gluetun/discussions/550)
|
||||
- [Start a discussion](https://github.com/passteque/gluetun/discussions)
|
||||
- [Fix the Unraid template](https://github.com/passteque/gluetun/discussions/550)
|
||||
- Suggestion?
|
||||
- [Create an issue](https://github.com/qdm12/gluetun/issues)
|
||||
- [Create an issue](https://github.com/passteque/gluetun/issues)
|
||||
- Happy?
|
||||
- Sponsor me on [github.com/sponsors/qdm12](https://github.com/sponsors/qdm12)
|
||||
- Donate to [paypal.me/qmcgaw](https://www.paypal.me/qmcgaw)
|
||||
@@ -66,13 +66,14 @@ Lightweight swiss-army-knife-like VPN client to multiple VPN service providers
|
||||
- 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/qdm12/gluetun/issues/134)
|
||||
- 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+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)
|
||||
@@ -130,8 +131,8 @@ services:
|
||||
|
||||
## Fun graphs
|
||||
|
||||
[](https://www.star-history.com/#qdm12/gluetun&type=date&legend=top-left)
|
||||
[](https://www.star-history.com/#passteque/gluetun&type=date&legend=top-left)
|
||||
|
||||
## License
|
||||
|
||||
[](https://github.com/qdm12/gluetun/blob/master/LICENSE)
|
||||
[](https://github.com/passteque/gluetun/blob/master/LICENSE)
|
||||
|
||||
+16
-1
@@ -41,6 +41,7 @@ import (
|
||||
"github.com/qdm12/gluetun/internal/routing"
|
||||
"github.com/qdm12/gluetun/internal/server"
|
||||
"github.com/qdm12/gluetun/internal/shadowsocks"
|
||||
"github.com/qdm12/gluetun/internal/socks5"
|
||||
"github.com/qdm12/gluetun/internal/storage"
|
||||
updater "github.com/qdm12/gluetun/internal/updater/loop"
|
||||
"github.com/qdm12/gluetun/internal/updater/resolver"
|
||||
@@ -411,6 +412,18 @@ func _main(ctx context.Context, buildInfo models.BuildInformation,
|
||||
return fmt.Errorf("starting public ip loop: %w", err)
|
||||
}
|
||||
|
||||
socks5Loop := socks5.NewLoop(socks5.Settings{
|
||||
Enabled: *allSettings.Socks5.Enabled,
|
||||
Username: *allSettings.Socks5.Username,
|
||||
Password: *allSettings.Socks5.Password,
|
||||
Address: allSettings.Socks5.ListeningAddress,
|
||||
Logger: logger.New(log.SetComponent("socks5")),
|
||||
})
|
||||
socks5RunError, err := socks5Loop.Start(ctx)
|
||||
if err != nil {
|
||||
return fmt.Errorf("starting SOCKS5 server loop: %w", err)
|
||||
}
|
||||
|
||||
healthLogger := logger.New(log.SetComponent("healthcheck"))
|
||||
healthcheckServer := healthcheck.NewServer(allSettings.Health, healthLogger)
|
||||
healthServerHandler, healthServerCtx, healthServerDone := goshutdown.NewGoRoutineHandler(
|
||||
@@ -506,7 +519,7 @@ func _main(ctx context.Context, buildInfo models.BuildInformation,
|
||||
String() string
|
||||
Stop() error
|
||||
}{
|
||||
portForwardLooper, publicIPLooper,
|
||||
portForwardLooper, publicIPLooper, socks5Loop,
|
||||
}
|
||||
for _, stopper := range stoppers {
|
||||
err := stopper.Stop()
|
||||
@@ -518,6 +531,8 @@ func _main(ctx context.Context, buildInfo models.BuildInformation,
|
||||
logger.Errorf("port forwarding loop crashed: %s", err)
|
||||
case err := <-publicIPRunError:
|
||||
logger.Errorf("public IP loop crashed: %s", err)
|
||||
case err := <-socks5RunError:
|
||||
logger.Errorf("SOCKS5 server loop crashed: %s", err)
|
||||
}
|
||||
|
||||
return orderHandler.Shutdown(context.Background())
|
||||
|
||||
@@ -4,18 +4,19 @@ 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.19
|
||||
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.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
|
||||
github.com/qdm12/gosplash v0.2.1-0.20260305164749-b713de4fee6c
|
||||
@@ -26,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
|
||||
@@ -46,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
|
||||
@@ -55,12 +55,12 @@ require (
|
||||
github.com/prometheus/client_model v0.6.1 // indirect
|
||||
github.com/prometheus/common v0.60.1 // indirect
|
||||
github.com/prometheus/procfs v0.15.1 // indirect
|
||||
github.com/qdm12/goservices v0.1.1-0.20251104135713-6bee97bd4978 // 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.19 h1:l3rOmrA4o5z38kpgnA5iSk1yOm7Cv3AafIi4vxpSEV0=
|
||||
github.com/amnezia-vpn/amneziawg-go v0.2.19/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=
|
||||
@@ -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) {
|
||||
|
||||
+18
-21
@@ -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,
|
||||
@@ -21,7 +22,6 @@ func (c *Cmder) Start(cmd *exec.Cmd) (
|
||||
func start(cmd execCmd) (stdoutLines, stderrLines <-chan string,
|
||||
waitError <-chan error, startErr error,
|
||||
) {
|
||||
stop := make(chan struct{})
|
||||
stdoutReady := make(chan struct{})
|
||||
stdoutLinesCh := make(chan string)
|
||||
stdoutDone := make(chan struct{})
|
||||
@@ -33,43 +33,47 @@ func start(cmd execCmd) (stdoutLines, stderrLines <-chan string,
|
||||
if err != nil {
|
||||
return nil, nil, nil, err
|
||||
}
|
||||
go streamToChannel(stdoutReady, stop, stdoutDone, stdout, stdoutLinesCh)
|
||||
go streamToChannel(stdoutReady, stdoutDone, stdout, stdoutLinesCh)
|
||||
|
||||
stderr, err := cmd.StderrPipe()
|
||||
if err != nil {
|
||||
_ = stdout.Close()
|
||||
close(stop)
|
||||
<-stdoutDone
|
||||
close(stdoutLinesCh)
|
||||
return nil, nil, nil, err
|
||||
}
|
||||
go streamToChannel(stderrReady, stop, stderrDone, stderr, stderrLinesCh)
|
||||
go streamToChannel(stderrReady, stderrDone, stderr, stderrLinesCh)
|
||||
|
||||
err = cmd.Start()
|
||||
if err != nil {
|
||||
_ = stdout.Close()
|
||||
_ = stderr.Close()
|
||||
close(stop)
|
||||
<-stdoutDone
|
||||
close(stdoutLinesCh)
|
||||
_ = stderr.Close()
|
||||
<-stderrDone
|
||||
close(stderrLinesCh)
|
||||
return nil, nil, nil, err
|
||||
}
|
||||
|
||||
waitErrorCh := make(chan error)
|
||||
go func() {
|
||||
err := cmd.Wait()
|
||||
_ = stdout.Close()
|
||||
_ = stderr.Close()
|
||||
close(stop)
|
||||
<-stdoutDone
|
||||
close(stdoutLinesCh)
|
||||
_ = stdout.Close()
|
||||
<-stderrDone
|
||||
close(stderrLinesCh)
|
||||
_ = stderr.Close()
|
||||
waitErrorCh <- err
|
||||
}()
|
||||
|
||||
<-stdoutReady
|
||||
<-stderrReady
|
||||
|
||||
return stdoutLinesCh, stderrLinesCh, waitErrorCh, nil
|
||||
}
|
||||
|
||||
func streamToChannel(ready chan<- struct{},
|
||||
stop <-chan struct{}, done chan<- struct{},
|
||||
func streamToChannel(ready chan<- struct{}, done chan<- struct{},
|
||||
stream io.Reader, lines chan<- string,
|
||||
) {
|
||||
defer close(done)
|
||||
@@ -89,12 +93,5 @@ func streamToChannel(ready chan<- struct{},
|
||||
if err == nil || errors.Is(err, os.ErrClosed) {
|
||||
return
|
||||
}
|
||||
|
||||
// ignore the error if it is stopped.
|
||||
select {
|
||||
case <-stop:
|
||||
return
|
||||
default:
|
||||
lines <- "stream error: " + err.Error()
|
||||
}
|
||||
lines <- "stream error: " + err.Error()
|
||||
}
|
||||
|
||||
@@ -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,30 +89,48 @@ func Test_start(t *testing.T) {
|
||||
|
||||
require.NoError(t, err)
|
||||
|
||||
var stdoutIndex, stderrIndex int
|
||||
|
||||
done := false
|
||||
for !done {
|
||||
select {
|
||||
case line := <-stdoutLines:
|
||||
assert.Equal(t, testCase.stdout[stdoutIndex], line)
|
||||
stdoutIndex++
|
||||
case line := <-stderrLines:
|
||||
assert.Equal(t, testCase.stderr[stderrIndex], line)
|
||||
stderrIndex++
|
||||
case err := <-waitError:
|
||||
if testCase.waitErr != nil {
|
||||
require.Error(t, err)
|
||||
assert.Equal(t, testCase.waitErr.Error(), err.Error())
|
||||
} else {
|
||||
assert.NoError(t, err)
|
||||
}
|
||||
done = true
|
||||
}
|
||||
}
|
||||
|
||||
assert.Equal(t, len(testCase.stdout), stdoutIndex)
|
||||
assert.Equal(t, len(testCase.stderr), stderrIndex)
|
||||
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, ok := <-stdoutLines:
|
||||
if !ok {
|
||||
stdoutLines = nil
|
||||
continue
|
||||
}
|
||||
assert.Equal(t, expectedStdout[stdoutIndex], line)
|
||||
stdoutIndex++
|
||||
case line, ok := <-stderrLines:
|
||||
if !ok {
|
||||
stderrLines = nil
|
||||
continue
|
||||
}
|
||||
assert.Equal(t, expectedStderr[stderrIndex], line)
|
||||
stderrIndex++
|
||||
case err := <-waitError:
|
||||
if expectedWaitErr != nil {
|
||||
require.Error(t, err)
|
||||
assert.Equal(t, expectedWaitErr.Error(), err.Error())
|
||||
} else {
|
||||
assert.NoError(t, err)
|
||||
}
|
||||
done = true
|
||||
}
|
||||
}
|
||||
|
||||
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:
|
||||
logger.Info(line)
|
||||
case line = <-stderr:
|
||||
logger.Error(line)
|
||||
case line, ok := <-stdout:
|
||||
if ok {
|
||||
logger.Info(line)
|
||||
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)
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -20,6 +20,7 @@ type Settings struct {
|
||||
HTTPProxy HTTPProxy
|
||||
Log Log
|
||||
PublicIP PublicIP
|
||||
Socks5 Socks5
|
||||
Shadowsocks Shadowsocks
|
||||
Storage Storage
|
||||
System System
|
||||
@@ -49,6 +50,7 @@ func (s *Settings) Validate(filterChoicesGetter FilterChoicesGetter, ipv6Support
|
||||
"http proxy": s.HTTPProxy.validate,
|
||||
"log": s.Log.validate,
|
||||
"public ip check": s.PublicIP.validate,
|
||||
"socks5": s.Socks5.validate,
|
||||
"shadowsocks": s.Shadowsocks.validate,
|
||||
"storage": s.Storage.validate,
|
||||
"system": s.System.validate,
|
||||
@@ -81,6 +83,7 @@ func (s *Settings) copy() (copied Settings) {
|
||||
HTTPProxy: s.HTTPProxy.copy(),
|
||||
Log: s.Log.copy(),
|
||||
PublicIP: s.PublicIP.copy(),
|
||||
Socks5: s.Socks5.copy(),
|
||||
Shadowsocks: s.Shadowsocks.copy(),
|
||||
Storage: s.Storage.copy(),
|
||||
System: s.System.copy(),
|
||||
@@ -104,6 +107,7 @@ func (s *Settings) OverrideWith(other Settings,
|
||||
patchedSettings.HTTPProxy.overrideWith(other.HTTPProxy)
|
||||
patchedSettings.Log.overrideWith(other.Log)
|
||||
patchedSettings.PublicIP.overrideWith(other.PublicIP)
|
||||
patchedSettings.Socks5.overrideWith(other.Socks5)
|
||||
patchedSettings.Shadowsocks.overrideWith(other.Shadowsocks)
|
||||
patchedSettings.Storage.overrideWith(other.Storage)
|
||||
patchedSettings.System.overrideWith(other.System)
|
||||
@@ -131,6 +135,7 @@ func (s *Settings) SetDefaults() {
|
||||
s.Log.setDefaults()
|
||||
s.IPv6.setDefaults()
|
||||
s.PublicIP.setDefaults()
|
||||
s.Socks5.setDefaults()
|
||||
s.Shadowsocks.setDefaults()
|
||||
s.Storage.SetDefaults()
|
||||
s.System.setDefaults()
|
||||
@@ -154,6 +159,7 @@ func (s Settings) toLinesNode() (node *gotree.Node) {
|
||||
node.AppendNode(s.Log.toLinesNode())
|
||||
node.AppendNode(s.IPv6.toLinesNode())
|
||||
node.AppendNode(s.Health.toLinesNode())
|
||||
node.AppendNode(s.Socks5.toLinesNode())
|
||||
node.AppendNode(s.Shadowsocks.toLinesNode())
|
||||
node.AppendNode(s.HTTPProxy.toLinesNode())
|
||||
node.AppendNode(s.ControlServer.toLinesNode())
|
||||
@@ -212,6 +218,7 @@ func (s *Settings) Read(r *reader.Reader, warner Warner) (err error) {
|
||||
"public ip": func(r *reader.Reader) error {
|
||||
return s.PublicIP.read(r, warner)
|
||||
},
|
||||
"socks5": s.Socks5.read,
|
||||
"shadowsocks": s.Shadowsocks.read,
|
||||
"storage": s.Storage.Read,
|
||||
"system": s.System.read,
|
||||
|
||||
@@ -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:
|
||||
@@ -81,6 +80,8 @@ func Test_Settings_String(t *testing.T) {
|
||||
| | ├── 1.1.1.1
|
||||
| | └── 8.8.8.8
|
||||
| └── Restart VPN on healthcheck failure: yes
|
||||
├── SOCKS5 proxy server settings:
|
||||
| └── Enabled: no
|
||||
├── Shadowsocks server settings:
|
||||
| └── Enabled: no
|
||||
├── HTTP proxy settings:
|
||||
|
||||
@@ -0,0 +1,91 @@
|
||||
package settings
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"os"
|
||||
|
||||
"github.com/qdm12/gosettings"
|
||||
"github.com/qdm12/gosettings/reader"
|
||||
"github.com/qdm12/gosettings/validate"
|
||||
"github.com/qdm12/gotree"
|
||||
)
|
||||
|
||||
// Socks5 contains settings to configure the Socks5 proxy server.
|
||||
type Socks5 struct {
|
||||
Enabled *bool
|
||||
ListeningAddress string
|
||||
Username *string
|
||||
Password *string
|
||||
}
|
||||
|
||||
func (s Socks5) validate() (err error) {
|
||||
err = validate.ListeningAddress(s.ListeningAddress, os.Getuid())
|
||||
if err != nil {
|
||||
return fmt.Errorf("server listening address is not valid: %w", err)
|
||||
}
|
||||
|
||||
switch {
|
||||
case *s.Username != "" && *s.Password == "":
|
||||
return errors.New("password must be set if username is set")
|
||||
case *s.Username == "" && *s.Password != "":
|
||||
return errors.New("username must be set if password is set")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *Socks5) copy() (copied Socks5) {
|
||||
return Socks5{
|
||||
Enabled: gosettings.CopyPointer(s.Enabled),
|
||||
ListeningAddress: s.ListeningAddress,
|
||||
Username: gosettings.CopyPointer(s.Username),
|
||||
Password: gosettings.CopyPointer(s.Password),
|
||||
}
|
||||
}
|
||||
|
||||
func (s *Socks5) overrideWith(other Socks5) {
|
||||
s.Enabled = gosettings.OverrideWithPointer(s.Enabled, other.Enabled)
|
||||
s.ListeningAddress = gosettings.OverrideWithComparable(s.ListeningAddress, other.ListeningAddress)
|
||||
s.Username = gosettings.OverrideWithPointer(s.Username, other.Username)
|
||||
s.Password = gosettings.OverrideWithPointer(s.Password, other.Password)
|
||||
}
|
||||
|
||||
func (s *Socks5) setDefaults() {
|
||||
s.Enabled = gosettings.DefaultPointer(s.Enabled, false)
|
||||
s.ListeningAddress = gosettings.DefaultComparable(s.ListeningAddress, ":1080")
|
||||
s.Username = gosettings.DefaultPointer(s.Username, "")
|
||||
s.Password = gosettings.DefaultPointer(s.Password, "")
|
||||
}
|
||||
|
||||
func (s Socks5) String() string {
|
||||
return s.toLinesNode().String()
|
||||
}
|
||||
|
||||
func (s Socks5) toLinesNode() (node *gotree.Node) {
|
||||
node = gotree.New("SOCKS5 proxy server settings:")
|
||||
node.Appendf("Enabled: %s", gosettings.BoolToYesNo(s.Enabled))
|
||||
if !*s.Enabled {
|
||||
return node
|
||||
}
|
||||
|
||||
node.Appendf("Listening address: %s", s.ListeningAddress)
|
||||
if *s.Username != "" || *s.Password != "" {
|
||||
node.Appendf("Username: %s", *s.Username)
|
||||
node.Appendf("Password: %s", gosettings.ObfuscateKey(*s.Password))
|
||||
}
|
||||
return node
|
||||
}
|
||||
|
||||
func (s *Socks5) read(r *reader.Reader) (err error) {
|
||||
s.Enabled, err = r.BoolPtr("SOCKS5_ENABLED")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
s.ListeningAddress = r.String("SOCKS5_LISTENING_ADDRESS")
|
||||
s.Username = r.Get("SOCKS5_USER", reader.ForceLowercase(false))
|
||||
s.Password = r.Get("SOCKS5_PASSWORD", reader.ForceLowercase(false))
|
||||
|
||||
return nil
|
||||
}
|
||||
@@ -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) {
|
||||
|
||||
@@ -278,7 +278,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 +285,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 {
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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():
|
||||
return
|
||||
case line = <-stdout:
|
||||
case line = <-stderr:
|
||||
errLine = true
|
||||
case line, ok = <-stdout:
|
||||
if ok {
|
||||
break
|
||||
}
|
||||
if stderr == nil {
|
||||
return
|
||||
}
|
||||
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) {
|
||||
|
||||
@@ -2,6 +2,10 @@ package service
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"maps"
|
||||
"slices"
|
||||
"sort"
|
||||
"strconv"
|
||||
"strings"
|
||||
)
|
||||
|
||||
@@ -20,3 +24,28 @@ func portsToString(ports []uint16) (s string) {
|
||||
" and " + portStrings[len(portStrings)-1]
|
||||
}
|
||||
}
|
||||
|
||||
func portPairsToString(internalToExternalPort map[uint16]uint16) (s string) {
|
||||
switch len(internalToExternalPort) {
|
||||
case 0:
|
||||
return "no port forwarded"
|
||||
case 1:
|
||||
internal := slices.Collect(maps.Keys(internalToExternalPort))[0]
|
||||
return "port forwarded is " + portPairToString(internal, internalToExternalPort[internal])
|
||||
default:
|
||||
portStrings := make([]string, 0, len(internalToExternalPort))
|
||||
for internal, external := range internalToExternalPort {
|
||||
portStrings = append(portStrings, portPairToString(internal, external))
|
||||
}
|
||||
sort.StringSlice(portStrings).Sort()
|
||||
return "ports forwarded are " + strings.Join(portStrings[:len(portStrings)-1], ", ") +
|
||||
" and " + portStrings[len(portStrings)-1]
|
||||
}
|
||||
}
|
||||
|
||||
func portPairToString(internal, external uint16) string {
|
||||
if internal == external {
|
||||
return strconv.FormatUint(uint64(external), 10)
|
||||
}
|
||||
return fmt.Sprintf("%d (internal port %d)", external, internal)
|
||||
}
|
||||
|
||||
@@ -40,3 +40,42 @@ func Test_portsToString(t *testing.T) {
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func Test_externalInternalPortsToString(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
testCases := map[string]struct {
|
||||
internalToExternalPort map[uint16]uint16
|
||||
s string
|
||||
}{
|
||||
"no_port": {
|
||||
s: "no port forwarded",
|
||||
},
|
||||
"one_port": {
|
||||
internalToExternalPort: map[uint16]uint16{123: 123},
|
||||
s: "port forwarded is 123",
|
||||
},
|
||||
"two_ports": {
|
||||
internalToExternalPort: map[uint16]uint16{123: 123, 456: 456},
|
||||
s: "ports forwarded are 123 and 456",
|
||||
},
|
||||
"two_ports_different_internal_external": {
|
||||
internalToExternalPort: map[uint16]uint16{123: 124, 456: 457},
|
||||
s: "ports forwarded are 124 (internal port 123) and 457 (internal port 456)",
|
||||
},
|
||||
"three_ports": {
|
||||
internalToExternalPort: map[uint16]uint16{123: 123, 456: 456, 789: 789},
|
||||
s: "ports forwarded are 123, 456 and 789",
|
||||
},
|
||||
}
|
||||
|
||||
for name, testCase := range testCases {
|
||||
t.Run(name, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
s := portPairsToString(testCase.internalToExternalPort)
|
||||
|
||||
assert.Equal(t, testCase.s, s)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
@@ -89,20 +89,14 @@ func (s *Service) Start(ctx context.Context) (runError <-chan error, err error)
|
||||
}
|
||||
|
||||
func (s *Service) onNewPorts(ctx context.Context, internalToExternalPorts map[uint16]uint16) (err error) {
|
||||
autoRedirectionNeeded := false
|
||||
s.logger.Info(portPairsToString(internalToExternalPorts))
|
||||
|
||||
externalPorts := slices.Collect(maps.Values(internalToExternalPorts))
|
||||
externalToInternalPorts := make(map[uint16]uint16, len(internalToExternalPorts))
|
||||
for internal, external := range internalToExternalPorts {
|
||||
externalToInternalPorts[external] = internal
|
||||
if internal != external {
|
||||
autoRedirectionNeeded = true
|
||||
}
|
||||
}
|
||||
|
||||
externalPorts := slices.Collect(maps.Keys(externalToInternalPorts))
|
||||
slices.Sort(externalPorts)
|
||||
|
||||
s.logger.Info(portsToString(externalPorts))
|
||||
|
||||
userRedirectionEnabled := !slices.Equal(s.settings.ListeningPorts, []uint16{0})
|
||||
for i, port := range externalPorts {
|
||||
internalPort := externalToInternalPorts[port]
|
||||
@@ -111,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) {
|
||||
|
||||
@@ -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)
|
||||
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)
|
||||
}
|
||||
_, _, err := addPortMappingTCPUDP(ctx, client, logger, objects.Gateway, internalPort, externalPort, lifetime)
|
||||
if err != nil {
|
||||
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))
|
||||
}
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -1,5 +1,10 @@
|
||||
// Code generated by MockGen. DO NOT EDIT.
|
||||
// Source: github.com/qdm12/gluetun/internal/routing (interfaces: NetLinker)
|
||||
//
|
||||
// Generated by this command:
|
||||
//
|
||||
// mockgen -destination=mocks_test.go -package=routing . NetLinker
|
||||
//
|
||||
|
||||
// Package routing is a generated GoMock package.
|
||||
package routing
|
||||
@@ -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,91 +42,91 @@ func (m *MockNetLinker) EXPECT() *MockNetLinkerMockRecorder {
|
||||
}
|
||||
|
||||
// AddrList mocks base method.
|
||||
func (m *MockNetLinker) AddrList(arg0 uint32, arg1 byte) ([]netip.Prefix, error) {
|
||||
func (m *MockNetLinker) AddrList(linkIndex uint32, family uint8) ([]netip.Prefix, error) {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "AddrList", arg0, arg1)
|
||||
ret := m.ctrl.Call(m, "AddrList", linkIndex, family)
|
||||
ret0, _ := ret[0].([]netip.Prefix)
|
||||
ret1, _ := ret[1].(error)
|
||||
return ret0, ret1
|
||||
}
|
||||
|
||||
// AddrList indicates an expected call of AddrList.
|
||||
func (mr *MockNetLinkerMockRecorder) AddrList(arg0, arg1 interface{}) *gomock.Call {
|
||||
func (mr *MockNetLinkerMockRecorder) AddrList(linkIndex, family any) *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "AddrList", reflect.TypeOf((*MockNetLinker)(nil).AddrList), arg0, arg1)
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "AddrList", reflect.TypeOf((*MockNetLinker)(nil).AddrList), linkIndex, family)
|
||||
}
|
||||
|
||||
// AddrReplace mocks base method.
|
||||
func (m *MockNetLinker) AddrReplace(arg0 uint32, arg1 netip.Prefix) error {
|
||||
func (m *MockNetLinker) AddrReplace(linkIndex uint32, prefix netip.Prefix) error {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "AddrReplace", arg0, arg1)
|
||||
ret := m.ctrl.Call(m, "AddrReplace", linkIndex, prefix)
|
||||
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, prefix 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, prefix)
|
||||
}
|
||||
|
||||
// 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)
|
||||
}
|
||||
|
||||
// LinkByIndex mocks base method.
|
||||
func (m *MockNetLinker) LinkByIndex(arg0 uint32) (netlink.Link, error) {
|
||||
func (m *MockNetLinker) LinkByIndex(index uint32) (netlink.Link, error) {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "LinkByIndex", arg0)
|
||||
ret := m.ctrl.Call(m, "LinkByIndex", index)
|
||||
ret0, _ := ret[0].(netlink.Link)
|
||||
ret1, _ := ret[1].(error)
|
||||
return ret0, ret1
|
||||
}
|
||||
|
||||
// LinkByIndex indicates an expected call of LinkByIndex.
|
||||
func (mr *MockNetLinkerMockRecorder) LinkByIndex(arg0 interface{}) *gomock.Call {
|
||||
func (mr *MockNetLinkerMockRecorder) LinkByIndex(index any) *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "LinkByIndex", reflect.TypeOf((*MockNetLinker)(nil).LinkByIndex), arg0)
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "LinkByIndex", reflect.TypeOf((*MockNetLinker)(nil).LinkByIndex), index)
|
||||
}
|
||||
|
||||
// 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(index uint32) error {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "LinkDel", arg0)
|
||||
ret := m.ctrl.Call(m, "LinkDel", index)
|
||||
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(index 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), index)
|
||||
}
|
||||
|
||||
// LinkList mocks base method.
|
||||
@@ -139,129 +145,129 @@ func (mr *MockNetLinkerMockRecorder) LinkList() *gomock.Call {
|
||||
}
|
||||
|
||||
// LinkSetDown mocks base method.
|
||||
func (m *MockNetLinker) LinkSetDown(arg0 uint32) error {
|
||||
func (m *MockNetLinker) LinkSetDown(index uint32) error {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "LinkSetDown", arg0)
|
||||
ret := m.ctrl.Call(m, "LinkSetDown", index)
|
||||
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(index 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), index)
|
||||
}
|
||||
|
||||
// LinkSetUp mocks base method.
|
||||
func (m *MockNetLinker) LinkSetUp(arg0 uint32) error {
|
||||
func (m *MockNetLinker) LinkSetUp(index uint32) error {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "LinkSetUp", arg0)
|
||||
ret := m.ctrl.Call(m, "LinkSetUp", index)
|
||||
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(index 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), index)
|
||||
}
|
||||
|
||||
// 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)
|
||||
}
|
||||
|
||||
// RouteDel mocks base method.
|
||||
func (m *MockNetLinker) RouteDel(arg0 netlink.Route) error {
|
||||
func (m *MockNetLinker) RouteDel(route netlink.Route) error {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "RouteDel", arg0)
|
||||
ret := m.ctrl.Call(m, "RouteDel", route)
|
||||
ret0, _ := ret[0].(error)
|
||||
return ret0
|
||||
}
|
||||
|
||||
// RouteDel indicates an expected call of RouteDel.
|
||||
func (mr *MockNetLinkerMockRecorder) RouteDel(arg0 interface{}) *gomock.Call {
|
||||
func (mr *MockNetLinkerMockRecorder) RouteDel(route any) *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "RouteDel", reflect.TypeOf((*MockNetLinker)(nil).RouteDel), arg0)
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "RouteDel", reflect.TypeOf((*MockNetLinker)(nil).RouteDel), 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)
|
||||
}
|
||||
|
||||
// RouteReplace mocks base method.
|
||||
func (m *MockNetLinker) RouteReplace(arg0 netlink.Route) error {
|
||||
func (m *MockNetLinker) RouteReplace(route netlink.Route) error {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "RouteReplace", arg0)
|
||||
ret := m.ctrl.Call(m, "RouteReplace", route)
|
||||
ret0, _ := ret[0].(error)
|
||||
return ret0
|
||||
}
|
||||
|
||||
// RouteReplace indicates an expected call of RouteReplace.
|
||||
func (mr *MockNetLinkerMockRecorder) RouteReplace(arg0 interface{}) *gomock.Call {
|
||||
func (mr *MockNetLinkerMockRecorder) RouteReplace(route any) *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "RouteReplace", reflect.TypeOf((*MockNetLinker)(nil).RouteReplace), arg0)
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "RouteReplace", reflect.TypeOf((*MockNetLinker)(nil).RouteReplace), route)
|
||||
}
|
||||
|
||||
// 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)
|
||||
}
|
||||
|
||||
// RuleList mocks base method.
|
||||
func (m *MockNetLinker) RuleList(arg0 byte) ([]netlink.Rule, error) {
|
||||
func (m *MockNetLinker) RuleList(family uint8) ([]netlink.Rule, error) {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "RuleList", arg0)
|
||||
ret := m.ctrl.Call(m, "RuleList", family)
|
||||
ret0, _ := ret[0].([]netlink.Rule)
|
||||
ret1, _ := ret[1].(error)
|
||||
return ret0, ret1
|
||||
}
|
||||
|
||||
// RuleList indicates an expected call of RuleList.
|
||||
func (mr *MockNetLinkerMockRecorder) RuleList(arg0 interface{}) *gomock.Call {
|
||||
func (mr *MockNetLinkerMockRecorder) RuleList(family any) *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "RuleList", reflect.TypeOf((*MockNetLinker)(nil).RuleList), arg0)
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "RuleList", reflect.TypeOf((*MockNetLinker)(nil).RuleList), family)
|
||||
}
|
||||
|
||||
@@ -5,10 +5,10 @@ import (
|
||||
"net/netip"
|
||||
"testing"
|
||||
|
||||
"github.com/golang/mock/gomock"
|
||||
"github.com/qdm12/gluetun/internal/netlink"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
"go.uber.org/mock/gomock"
|
||||
)
|
||||
|
||||
func makeNetipPrefix(n byte) netip.Prefix {
|
||||
|
||||
@@ -8,9 +8,9 @@ import (
|
||||
"net/url"
|
||||
"testing"
|
||||
|
||||
"github.com/golang/mock/gomock"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
"go.uber.org/mock/gomock"
|
||||
)
|
||||
|
||||
func Test_authHandler_ServeHTTP(t *testing.T) {
|
||||
|
||||
@@ -1,5 +1,10 @@
|
||||
// Code generated by MockGen. DO NOT EDIT.
|
||||
// Source: github.com/qdm12/gluetun/internal/server/middlewares/auth (interfaces: DebugLogger)
|
||||
//
|
||||
// Generated by this command:
|
||||
//
|
||||
// mockgen -destination=mocks_test.go -package=auth . DebugLogger
|
||||
//
|
||||
|
||||
// Package auth is a generated GoMock package.
|
||||
package auth
|
||||
@@ -7,13 +12,14 @@ package auth
|
||||
import (
|
||||
reflect "reflect"
|
||||
|
||||
gomock "github.com/golang/mock/gomock"
|
||||
gomock "go.uber.org/mock/gomock"
|
||||
)
|
||||
|
||||
// MockDebugLogger is a mock of DebugLogger interface.
|
||||
type MockDebugLogger struct {
|
||||
ctrl *gomock.Controller
|
||||
recorder *MockDebugLoggerMockRecorder
|
||||
isgomock struct{}
|
||||
}
|
||||
|
||||
// MockDebugLoggerMockRecorder is the mock recorder for MockDebugLogger.
|
||||
@@ -34,35 +40,35 @@ func (m *MockDebugLogger) EXPECT() *MockDebugLoggerMockRecorder {
|
||||
}
|
||||
|
||||
// Debugf mocks base method.
|
||||
func (m *MockDebugLogger) Debugf(arg0 string, arg1 ...interface{}) {
|
||||
func (m *MockDebugLogger) 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 *MockDebugLoggerMockRecorder) Debugf(arg0 interface{}, arg1 ...interface{}) *gomock.Call {
|
||||
func (mr *MockDebugLoggerMockRecorder) 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((*MockDebugLogger)(nil).Debugf), varargs...)
|
||||
}
|
||||
|
||||
// Warnf mocks base method.
|
||||
func (m *MockDebugLogger) Warnf(arg0 string, arg1 ...interface{}) {
|
||||
func (m *MockDebugLogger) Warnf(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, "Warnf", varargs...)
|
||||
}
|
||||
|
||||
// Warnf indicates an expected call of Warnf.
|
||||
func (mr *MockDebugLoggerMockRecorder) Warnf(arg0 interface{}, arg1 ...interface{}) *gomock.Call {
|
||||
func (mr *MockDebugLoggerMockRecorder) Warnf(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, "Warnf", reflect.TypeOf((*MockDebugLogger)(nil).Warnf), varargs...)
|
||||
}
|
||||
|
||||
@@ -0,0 +1,83 @@
|
||||
package socks5
|
||||
|
||||
import "fmt"
|
||||
|
||||
// See https://datatracker.ietf.org/doc/html/rfc1928#section-3
|
||||
type authMethod byte
|
||||
|
||||
const (
|
||||
authNotRequired authMethod = 0
|
||||
authGssapi authMethod = 1
|
||||
authUsernamePassword authMethod = 2
|
||||
authNotAcceptable authMethod = 255
|
||||
)
|
||||
|
||||
func (a authMethod) String() string {
|
||||
switch a {
|
||||
case authNotRequired:
|
||||
return "no authentication required"
|
||||
case authGssapi:
|
||||
return "GSSAPI"
|
||||
case authUsernamePassword:
|
||||
return "username/password"
|
||||
case authNotAcceptable:
|
||||
return "no acceptable methods"
|
||||
default:
|
||||
return fmt.Sprintf("unknown method (%d)", a)
|
||||
}
|
||||
}
|
||||
|
||||
// Subnegotiation version
|
||||
// See https://datatracker.ietf.org/doc/html/rfc1929#section-2
|
||||
const (
|
||||
authUsernamePasswordSubNegotiation1 byte = 1
|
||||
)
|
||||
|
||||
// SOCKS versions.
|
||||
const (
|
||||
socks5Version byte = 5
|
||||
)
|
||||
|
||||
// See https://datatracker.ietf.org/doc/html/rfc1928#section-4
|
||||
type cmdType byte
|
||||
|
||||
const (
|
||||
connect cmdType = 1
|
||||
udpAssociate cmdType = 3
|
||||
)
|
||||
|
||||
func (c cmdType) String() string {
|
||||
switch c {
|
||||
case connect:
|
||||
return "connect"
|
||||
case udpAssociate:
|
||||
return "UDP associate"
|
||||
default:
|
||||
return fmt.Sprintf("unknown command (%d)", c)
|
||||
}
|
||||
}
|
||||
|
||||
// See https://datatracker.ietf.org/doc/html/rfc1928#section-4 and
|
||||
// https://datatracker.ietf.org/doc/html/rfc1928#section-5
|
||||
type addrType byte
|
||||
|
||||
const (
|
||||
ipv4 addrType = 1
|
||||
domainName addrType = 3
|
||||
ipv6 addrType = 4
|
||||
)
|
||||
|
||||
// See https://datatracker.ietf.org/doc/html/rfc1928#section-6
|
||||
type replyCode byte
|
||||
|
||||
const (
|
||||
succeeded replyCode = iota
|
||||
generalServerFailure
|
||||
connectionNotAllowedByRuleset
|
||||
networkUnreachable
|
||||
hostUnreachable
|
||||
connectionRefused
|
||||
ttlExpired
|
||||
commandNotSupported
|
||||
addressTypeNotSupported
|
||||
)
|
||||
@@ -0,0 +1,6 @@
|
||||
package socks5
|
||||
|
||||
type Logger interface {
|
||||
Infof(format string, a ...interface{})
|
||||
Warnf(format string, a ...interface{})
|
||||
}
|
||||
@@ -0,0 +1,106 @@
|
||||
package socks5
|
||||
|
||||
import (
|
||||
"context"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/qdm12/goservices"
|
||||
)
|
||||
|
||||
type Loop struct {
|
||||
settings Settings
|
||||
|
||||
mutex sync.Mutex
|
||||
runCancel context.CancelFunc
|
||||
runDone <-chan error
|
||||
}
|
||||
|
||||
func NewLoop(settings Settings) *Loop {
|
||||
return &Loop{
|
||||
settings: settings,
|
||||
}
|
||||
}
|
||||
|
||||
func (l *Loop) String() string {
|
||||
return "SOCKS5 server loop"
|
||||
}
|
||||
|
||||
func (l *Loop) Start(_ context.Context) (runError <-chan error, err error) {
|
||||
l.mutex.Lock()
|
||||
defer l.mutex.Unlock()
|
||||
|
||||
var runCtx context.Context
|
||||
runCtx, l.runCancel = context.WithCancel(context.Background())
|
||||
|
||||
runDone := make(chan error)
|
||||
l.runDone = runDone
|
||||
|
||||
go run(runCtx, runDone, l.settings)
|
||||
|
||||
return nil, nil //nolint:nilnil
|
||||
}
|
||||
|
||||
func run(ctx context.Context, done chan<- error, settings Settings) {
|
||||
defer close(done)
|
||||
logger := settings.Logger
|
||||
|
||||
for ctx.Err() == nil {
|
||||
var server goservices.Service
|
||||
if settings.Enabled {
|
||||
server = newServer(settings)
|
||||
} else {
|
||||
server = new(noopService)
|
||||
}
|
||||
|
||||
errorCh, err := server.Start(ctx)
|
||||
if err != nil {
|
||||
logger.Warnf("failed starting SOCKS5 server: %s", err)
|
||||
waitBeforeRetry(ctx)
|
||||
continue
|
||||
}
|
||||
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
done <- server.Stop()
|
||||
return
|
||||
case err := <-errorCh:
|
||||
if ctx.Err() != nil {
|
||||
return
|
||||
}
|
||||
logger.Warnf("SOCKS5 server crashed: %s", err)
|
||||
waitBeforeRetry(ctx)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (l *Loop) Stop() (err error) {
|
||||
l.mutex.Lock()
|
||||
defer l.mutex.Unlock()
|
||||
|
||||
l.runCancel()
|
||||
return <-l.runDone
|
||||
}
|
||||
|
||||
func waitBeforeRetry(ctx context.Context) {
|
||||
const retryDelay = 10 * time.Second
|
||||
timer := time.NewTimer(retryDelay)
|
||||
select {
|
||||
case <-timer.C:
|
||||
case <-ctx.Done():
|
||||
}
|
||||
}
|
||||
|
||||
type noopService struct{}
|
||||
|
||||
func (s noopService) Start(_ context.Context) (runErr <-chan error, err error) {
|
||||
return nil, nil //nolint:nilnil
|
||||
}
|
||||
|
||||
func (s noopService) Stop() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s noopService) String() string {
|
||||
return "noop service"
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
package socks5
|
||||
|
||||
//go:generate mockgen -destination=mocks_test.go -package=$GOPACKAGE . Logger
|
||||
@@ -0,0 +1,74 @@
|
||||
// Code generated by MockGen. DO NOT EDIT.
|
||||
// Source: github.com/qdm12/gluetun/internal/socks5 (interfaces: Logger)
|
||||
//
|
||||
// Generated by this command:
|
||||
//
|
||||
// mockgen -destination=mocks_test.go -package=socks5 . Logger
|
||||
//
|
||||
|
||||
// Package socks5 is a generated GoMock package.
|
||||
package socks5
|
||||
|
||||
import (
|
||||
reflect "reflect"
|
||||
|
||||
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.
|
||||
type MockLoggerMockRecorder struct {
|
||||
mock *MockLogger
|
||||
}
|
||||
|
||||
// NewMockLogger creates a new mock instance.
|
||||
func NewMockLogger(ctrl *gomock.Controller) *MockLogger {
|
||||
mock := &MockLogger{ctrl: ctrl}
|
||||
mock.recorder = &MockLoggerMockRecorder{mock}
|
||||
return mock
|
||||
}
|
||||
|
||||
// EXPECT returns an object that allows the caller to indicate expected use.
|
||||
func (m *MockLogger) EXPECT() *MockLoggerMockRecorder {
|
||||
return m.recorder
|
||||
}
|
||||
|
||||
// Infof mocks base method.
|
||||
func (m *MockLogger) Infof(format string, a ...any) {
|
||||
m.ctrl.T.Helper()
|
||||
varargs := []any{format}
|
||||
for _, a_2 := range a {
|
||||
varargs = append(varargs, a_2)
|
||||
}
|
||||
m.ctrl.Call(m, "Infof", varargs...)
|
||||
}
|
||||
|
||||
// Infof indicates an expected call of Infof.
|
||||
func (mr *MockLoggerMockRecorder) Infof(format any, a ...any) *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
varargs := append([]any{format}, a...)
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Infof", reflect.TypeOf((*MockLogger)(nil).Infof), varargs...)
|
||||
}
|
||||
|
||||
// Warnf mocks base method.
|
||||
func (m *MockLogger) Warnf(format string, a ...any) {
|
||||
m.ctrl.T.Helper()
|
||||
varargs := []any{format}
|
||||
for _, a_2 := range a {
|
||||
varargs = append(varargs, a_2)
|
||||
}
|
||||
m.ctrl.Call(m, "Warnf", varargs...)
|
||||
}
|
||||
|
||||
// Warnf indicates an expected call of Warnf.
|
||||
func (mr *MockLoggerMockRecorder) Warnf(format any, a ...any) *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
varargs := append([]any{format}, a...)
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Warnf", reflect.TypeOf((*MockLogger)(nil).Warnf), varargs...)
|
||||
}
|
||||
@@ -0,0 +1,109 @@
|
||||
package socks5
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"net"
|
||||
"net/netip"
|
||||
)
|
||||
|
||||
// See https://datatracker.ietf.org/doc/html/rfc1928#section-6
|
||||
func (c *socksConn) encodeFailedResponse(writer io.Writer, socksVersion byte, reply replyCode) {
|
||||
_, err := writer.Write([]byte{
|
||||
socksVersion,
|
||||
byte(reply),
|
||||
0, // RSV byte
|
||||
// The RFC requires a full response frame even for failures.
|
||||
// Use IPv4 address type with zeroed address and port.
|
||||
byte(ipv4), // ATYP
|
||||
0, 0, 0, 0, // BND.ADDR (zeroed)
|
||||
0, 0, // BND.PORT (zeroed)
|
||||
})
|
||||
if err != nil {
|
||||
c.logger.Warnf("failed writing failed response: %s", err)
|
||||
}
|
||||
}
|
||||
|
||||
// See https://datatracker.ietf.org/doc/html/rfc1928#section-6
|
||||
func (c *socksConn) encodeSuccessResponse(writer io.Writer, socksVersion byte,
|
||||
reply replyCode, bindAddrType addrType, bindAddress string,
|
||||
bindPort uint16,
|
||||
) (err error) {
|
||||
bindData, err := encodeBindData(bindAddrType, bindAddress, bindPort)
|
||||
if err != nil {
|
||||
return fmt.Errorf("encoding bind data: %w", err)
|
||||
}
|
||||
|
||||
const initialPacketLength = 3
|
||||
capacity := initialPacketLength + len(bindData)
|
||||
packet := make([]byte, initialPacketLength, capacity)
|
||||
packet[0] = socksVersion
|
||||
packet[1] = byte(reply)
|
||||
packet[2] = 0 // RSV byte
|
||||
packet = append(packet, bindData...)
|
||||
|
||||
_, err = writer.Write(packet)
|
||||
if err != nil {
|
||||
return fmt.Errorf("writing packet: %w", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
var (
|
||||
ErrIPVersionUnexpected = errors.New("ip version is unexpected")
|
||||
ErrDomainNameTooLong = errors.New("domain name is too long")
|
||||
)
|
||||
|
||||
func encodeBindData(addrType addrType, address string, port uint16) (
|
||||
data []byte, err error,
|
||||
) {
|
||||
capacity := bindDataLength(addrType, address)
|
||||
data = make([]byte, 0, capacity)
|
||||
|
||||
data = append(data, byte(addrType))
|
||||
switch addrType {
|
||||
case ipv4, ipv6:
|
||||
ip, err := netip.ParseAddr(address)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("parsing IP address: %w", err)
|
||||
}
|
||||
|
||||
switch {
|
||||
case addrType == ipv4 && !ip.Is4():
|
||||
return nil, fmt.Errorf("%w: expected IPv4 for %s", ErrIPVersionUnexpected, ip)
|
||||
case addrType == ipv6 && !ip.Is6():
|
||||
return nil, fmt.Errorf("%w: expected IPv6 for %s", ErrIPVersionUnexpected, ip)
|
||||
}
|
||||
data = append(data, ip.AsSlice()...)
|
||||
case domainName:
|
||||
const maxDomainNameLength = 255
|
||||
if len(address) > maxDomainNameLength {
|
||||
return nil, fmt.Errorf("%w: %s", ErrDomainNameTooLong, address)
|
||||
}
|
||||
data = append(data, byte(len(address)))
|
||||
data = append(data, []byte(address)...)
|
||||
default:
|
||||
panic(fmt.Sprintf("unsupported address type %d", addrType))
|
||||
}
|
||||
data = binary.BigEndian.AppendUint16(data, port)
|
||||
return data, nil
|
||||
}
|
||||
|
||||
func bindDataLength(addrType addrType, address string) (maxLength uint) {
|
||||
maxLength++ // address type
|
||||
switch addrType {
|
||||
case ipv4:
|
||||
maxLength += net.IPv4len
|
||||
case domainName:
|
||||
maxLength++ // domain name length
|
||||
maxLength += uint(len([]byte(address)))
|
||||
case ipv6:
|
||||
maxLength += net.IPv6len
|
||||
default:
|
||||
panic("unsupported address type: " + fmt.Sprint(addrType))
|
||||
}
|
||||
maxLength += 2 // port
|
||||
return maxLength
|
||||
}
|
||||
@@ -0,0 +1,162 @@
|
||||
package socks5
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"net"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
)
|
||||
|
||||
type server struct {
|
||||
username string
|
||||
password string
|
||||
address string
|
||||
logger Logger
|
||||
|
||||
// internal fields
|
||||
tcpListener net.Listener
|
||||
udpRouter *udpRouter
|
||||
listening atomic.Bool
|
||||
socksConnCtx context.Context //nolint:containedctx
|
||||
socksConnCancel context.CancelFunc
|
||||
done <-chan error
|
||||
stopCh chan<- struct{}
|
||||
}
|
||||
|
||||
func newServer(settings Settings) *server {
|
||||
return &server{
|
||||
username: settings.Username,
|
||||
password: settings.Password,
|
||||
address: settings.Address,
|
||||
logger: settings.Logger,
|
||||
}
|
||||
}
|
||||
|
||||
func (s *server) String() string {
|
||||
return "SOCKS5 server"
|
||||
}
|
||||
|
||||
func (s *server) Start(ctx context.Context) (runErr <-chan error, err error) {
|
||||
s.socksConnCtx, s.socksConnCancel = context.WithCancel(context.Background())
|
||||
config := &net.ListenConfig{}
|
||||
s.tcpListener, err = config.Listen(ctx, "tcp", s.address)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("TCP listening on %s: %w", s.address, err)
|
||||
}
|
||||
|
||||
s.udpRouter, err = newUDPRouter(ctx, s.address, s.logger)
|
||||
if err != nil {
|
||||
_ = s.tcpListener.Close()
|
||||
return nil, fmt.Errorf("creating UDP router: %w", err)
|
||||
}
|
||||
s.listening.Store(true)
|
||||
s.logger.Infof("SOCKS5 TCP server listening on %s", s.tcpListener.Addr())
|
||||
s.logger.Infof("SOCKS5 UDP server listening on %s", s.udpRouter.localAddress())
|
||||
|
||||
ready := make(chan struct{})
|
||||
runErrCh := make(chan error)
|
||||
runErr = runErrCh
|
||||
done := make(chan error)
|
||||
s.done = done
|
||||
stop := make(chan struct{})
|
||||
s.stopCh = stop
|
||||
go s.runServer(ready, runErrCh, stop, done)
|
||||
select {
|
||||
case <-ready:
|
||||
case <-ctx.Done():
|
||||
_ = s.Stop()
|
||||
return nil, fmt.Errorf("starting server: %w", ctx.Err())
|
||||
}
|
||||
return runErr, nil
|
||||
}
|
||||
|
||||
func (s *server) runServer(ready chan<- struct{},
|
||||
runErrCh chan<- error, stop <-chan struct{}, done chan<- error,
|
||||
) {
|
||||
close(ready)
|
||||
defer close(done)
|
||||
|
||||
udpErrCh := make(chan error)
|
||||
go func() {
|
||||
udpErrCh <- s.udpRouter.run(s.socksConnCtx)
|
||||
}()
|
||||
|
||||
tcpErrCh := make(chan error)
|
||||
go func() {
|
||||
var wg sync.WaitGroup
|
||||
defer wg.Wait()
|
||||
|
||||
dialer := &net.Dialer{}
|
||||
for {
|
||||
connection, err := s.tcpListener.Accept()
|
||||
if err != nil {
|
||||
s.socksConnCancel() // stop ongoing TCP socks connections - no impact on UDP
|
||||
tcpErrCh <- fmt.Errorf("accepting connection: %w", err)
|
||||
return
|
||||
}
|
||||
wg.Go(func() {
|
||||
connection := connection // capture loop variable
|
||||
socksConn := &socksConn{
|
||||
dialer: dialer,
|
||||
username: s.username,
|
||||
password: s.password,
|
||||
clientConn: connection,
|
||||
udpRouter: s.udpRouter,
|
||||
logger: s.logger,
|
||||
}
|
||||
err := socksConn.run(s.socksConnCtx)
|
||||
if err != nil {
|
||||
s.logger.Infof("running socks connection: %s", err)
|
||||
}
|
||||
})
|
||||
}
|
||||
}()
|
||||
|
||||
select {
|
||||
case <-stop:
|
||||
s.listening.Store(false)
|
||||
var errs []error
|
||||
err := s.tcpListener.Close()
|
||||
if err != nil {
|
||||
errs = append(errs, fmt.Errorf("closing TCP listener: %w", err))
|
||||
}
|
||||
// stop ongoing TCP socks connections. This impacts the udpRouter run error when it is being closed.
|
||||
s.socksConnCancel()
|
||||
<-tcpErrCh // wait for TCP server to stop
|
||||
err = s.udpRouter.close()
|
||||
if err != nil {
|
||||
errs = append(errs, fmt.Errorf("closing UDP router: %w", err))
|
||||
}
|
||||
<-udpErrCh // wait for UDP router to stop
|
||||
if len(errs) > 0 {
|
||||
// Only write to the done channel if the [server.Stop] method is waiting to read from it
|
||||
done <- errors.Join(errs...)
|
||||
}
|
||||
// If no error, the done channel is closed so the error is effectively `nil`
|
||||
// Note: do NOT write an error the runError channel, since we are stopping the server gracefully.
|
||||
case err := <-udpErrCh:
|
||||
_ = s.tcpListener.Close() // stop accepting new TCP connections
|
||||
s.socksConnCancel() // stop ongoing TCP socks connections
|
||||
<-tcpErrCh // wait for TCP server to stop
|
||||
runErrCh <- fmt.Errorf("running UDP router: %w", err)
|
||||
case err := <-tcpErrCh:
|
||||
s.socksConnCancel()
|
||||
_ = s.udpRouter.close() // stop UDP router
|
||||
<-udpErrCh // wait for UDP router to stop
|
||||
runErrCh <- fmt.Errorf("running TCP server: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
func (s *server) Stop() (err error) {
|
||||
close(s.stopCh)
|
||||
return <-s.done
|
||||
}
|
||||
|
||||
func (s *server) listeningAddress() net.Addr {
|
||||
if s.listening.Load() {
|
||||
return s.tcpListener.Addr()
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -0,0 +1,112 @@
|
||||
//go:build integration
|
||||
|
||||
package socks5
|
||||
|
||||
import (
|
||||
"math/rand/v2"
|
||||
"net"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/miekg/dns"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func Test_Server_UDPResolution(t *testing.T) {
|
||||
t.Parallel()
|
||||
ctx := t.Context()
|
||||
|
||||
server := newServer(Settings{
|
||||
Address: "127.0.0.1:0",
|
||||
Logger: noopLogger{},
|
||||
})
|
||||
runErr, err := server.Start(ctx)
|
||||
require.NoError(t, err, "starting SOCKS5 server")
|
||||
|
||||
const timeout = 3 * time.Second
|
||||
|
||||
// Connect to the SOCKS5 server via TCP to negotiate UDP associate
|
||||
dialer := &net.Dialer{Timeout: timeout}
|
||||
tcpConn, err := dialer.DialContext(ctx, "tcp", server.listeningAddress().String())
|
||||
require.NoError(t, err, "tcp connecting to SOCKS5 server")
|
||||
t.Cleanup(func() { tcpConn.Close() })
|
||||
|
||||
negotiateSOCKS5(t, tcpConn, "", "")
|
||||
|
||||
// UDP Associate Command: [VERSION (5), CMD (3 = UDP ASSOC), RSV (0), ATYP (1 = IPv4), ADDR (0.0.0.0), PORT (0)]
|
||||
_, err = tcpConn.Write([]byte{5, 3, 0, 1, 0, 0, 0, 0, 0, 0})
|
||||
require.NoError(t, err, "sending UDP ASSOC request")
|
||||
|
||||
relayAddressString, err := readSOCKS5ResponseAddress(t, tcpConn)
|
||||
require.NoError(t, err, "reading UDP ASSOC reply")
|
||||
relayAddress, err := net.ResolveUDPAddr("udp", relayAddressString)
|
||||
require.NoError(t, err, "resolving udp relay address")
|
||||
|
||||
// Dial the relay using IPv4 so source IP family matches the control connection.
|
||||
udpConn, err := net.DialUDP("udp4", nil, relayAddress)
|
||||
require.NoError(t, err, "dialing UDP relay")
|
||||
t.Cleanup(func() { _ = udpConn.Close() })
|
||||
|
||||
queryID := uint16(rand.Uint32()) //nolint:gosec
|
||||
dnsRequest := &dns.Msg{
|
||||
MsgHdr: dns.MsgHdr{
|
||||
Id: queryID,
|
||||
RecursionDesired: true,
|
||||
},
|
||||
Question: []dns.Question{{
|
||||
Name: dns.Fqdn("github.com"),
|
||||
Qtype: dns.TypeA,
|
||||
Qclass: dns.ClassINET,
|
||||
}},
|
||||
}
|
||||
dnsQuery, err := dnsRequest.Pack()
|
||||
require.NoError(t, err)
|
||||
|
||||
// Encapsulate DNS payload into SOCKS5 UDP Request Header
|
||||
// [RSV (0,0), FRAG (0), ATYP (1 = IPv4), DST.ADDR (1.1.1.1), DST.PORT (53)]
|
||||
packet := append([]byte{0, 0, 0, 1, 1, 1, 1, 1, 0, 53}, dnsQuery...)
|
||||
|
||||
// Send encapsulated packet to the proxy's UDP relay address
|
||||
_, err = udpConn.Write(packet)
|
||||
require.NoError(t, err, "sending UDP packet to relay")
|
||||
|
||||
// Read response from the proxy relay
|
||||
err = udpConn.SetReadDeadline(time.Now().Add(timeout))
|
||||
require.NoError(t, err, "setting read deadline on UDP connection")
|
||||
buffer := make([]byte, 2048)
|
||||
n, err := udpConn.Read(buffer)
|
||||
require.NoError(t, err, "receiving UDP response from relay")
|
||||
const minimumHeaderSize = 10
|
||||
require.GreaterOrEqual(t, n, minimumHeaderSize, "received UDP packet too short to contain valid SOCKS5 header")
|
||||
|
||||
// Verify header layout and slice out the raw DNS response
|
||||
// Header format: RSV(2) FRAG(1) ATYP(1) DST.ADDR(variable) DST.PORT(2)
|
||||
atyp := buffer[3]
|
||||
var headerSize int
|
||||
switch atyp {
|
||||
case 1: // IPv4
|
||||
headerSize = 10
|
||||
case 3: // Domain name
|
||||
headerSize = 4 + 1 + int(buffer[4]) + 2
|
||||
case 4: // IPv6
|
||||
headerSize = 22
|
||||
default:
|
||||
t.Fatalf("Unknown ATYP in SOCKS5 UDP header: %d", atyp)
|
||||
}
|
||||
|
||||
dnsResponse := new(dns.Msg)
|
||||
err = dnsResponse.Unpack(buffer[headerSize:n])
|
||||
require.NoError(t, err, "unpacking DNS response from SOCKS5 UDP packet")
|
||||
|
||||
assert.Equal(t, queryID, dnsResponse.Id, "DNS response ID should match query ID")
|
||||
|
||||
select {
|
||||
case err := <-runErr:
|
||||
require.NoError(t, err, "SOCKS5 server run error")
|
||||
default:
|
||||
}
|
||||
|
||||
err = server.Stop()
|
||||
require.NoError(t, err, "stopping SOCKS5 server")
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
package socks5
|
||||
|
||||
type Settings struct {
|
||||
Enabled bool
|
||||
Username string
|
||||
Password string
|
||||
Address string
|
||||
Logger Logger
|
||||
}
|
||||
@@ -0,0 +1,538 @@
|
||||
package socks5
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/binary"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"net"
|
||||
"net/netip"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
)
|
||||
|
||||
var (
|
||||
errNoMethodIdentifiers = errors.New("no method identifiers")
|
||||
errNoValidMethodIdentifier = errors.New("no valid method identifier")
|
||||
)
|
||||
|
||||
type socksConn struct {
|
||||
// Injected fields
|
||||
dialer *net.Dialer
|
||||
username string
|
||||
password string
|
||||
clientConn net.Conn
|
||||
udpRouter *udpRouter
|
||||
logger Logger
|
||||
}
|
||||
|
||||
func (c *socksConn) closeClientConn(ctxErr error) {
|
||||
err := c.clientConn.Close()
|
||||
if err != nil && ctxErr == nil {
|
||||
c.logger.Warnf("closing client connection: %s", err)
|
||||
}
|
||||
}
|
||||
|
||||
func (c *socksConn) run(ctx context.Context) error {
|
||||
// Monitoring context cancellation to close the connection and stop
|
||||
// reading operations on clientConn.
|
||||
done := make(chan struct{})
|
||||
ctxWatcherDone := make(chan struct{})
|
||||
go func() {
|
||||
defer close(ctxWatcherDone)
|
||||
select {
|
||||
case <-done:
|
||||
case <-ctx.Done():
|
||||
// unblock read operations
|
||||
c.closeClientConn(ctx.Err())
|
||||
}
|
||||
}()
|
||||
defer func() {
|
||||
close(done)
|
||||
<-ctxWatcherDone
|
||||
}()
|
||||
|
||||
authMethod := authNotRequired
|
||||
if c.username != "" || c.password != "" {
|
||||
authMethod = authUsernamePassword
|
||||
}
|
||||
|
||||
err := verifyFirstNegotiation(c.clientConn, authMethod)
|
||||
if err != nil {
|
||||
replyMethod := authMethod
|
||||
if errors.Is(err, errNoMethodIdentifiers) || errors.Is(err, errNoValidMethodIdentifier) {
|
||||
replyMethod = authNotAcceptable
|
||||
}
|
||||
_, writeErr := c.clientConn.Write([]byte{socks5Version, byte(replyMethod)})
|
||||
if writeErr != nil {
|
||||
c.logger.Warnf("failed writing first negotiation reply: %s", writeErr)
|
||||
}
|
||||
c.closeClientConn(ctx.Err())
|
||||
return fmt.Errorf("verifying first negotiation: %w", err)
|
||||
}
|
||||
|
||||
_, err = c.clientConn.Write([]byte{socks5Version, byte(authMethod)})
|
||||
if err != nil {
|
||||
c.closeClientConn(ctx.Err())
|
||||
return fmt.Errorf("writing first negotiation reply: %w", err)
|
||||
}
|
||||
|
||||
switch authMethod {
|
||||
case authNotRequired, authNotAcceptable:
|
||||
case authGssapi:
|
||||
panic("not implemented")
|
||||
case authUsernamePassword:
|
||||
// See https://datatracker.ietf.org/doc/html/rfc1929#section-2
|
||||
err = usernamePasswordSubnegotiate(c.clientConn, c.username, c.password)
|
||||
if err != nil {
|
||||
// If the server returns a `failure' (STATUS value other than X'00') status,
|
||||
// it MUST close the connection.
|
||||
c.closeClientConn(ctx.Err())
|
||||
return fmt.Errorf("subnegotiating username and password: %w", err)
|
||||
}
|
||||
default:
|
||||
panic(fmt.Sprintf("unimplemented auth method %d", authMethod))
|
||||
}
|
||||
|
||||
err = c.handleRequest(ctx)
|
||||
c.closeClientConn(ctx.Err())
|
||||
if err != nil {
|
||||
return fmt.Errorf("handling request: %w", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *socksConn) handleRequest(ctx context.Context) error {
|
||||
const socksVersion = socks5Version
|
||||
request, err := decodeRequest(c.clientConn, socksVersion)
|
||||
if err != nil {
|
||||
c.encodeFailedResponse(c.clientConn, socksVersion, generalServerFailure)
|
||||
return err
|
||||
}
|
||||
|
||||
switch request.command {
|
||||
case connect:
|
||||
err = c.handleConnectRequest(ctx, socksVersion, request)
|
||||
if err != nil {
|
||||
return fmt.Errorf("handling %s request: %w", request.command, err)
|
||||
}
|
||||
return nil
|
||||
case udpAssociate:
|
||||
err = c.handleUDPAssociateRequest(ctx, socksVersion, request)
|
||||
if err != nil {
|
||||
return fmt.Errorf("handling %s request: %w", request.command, err)
|
||||
}
|
||||
return nil
|
||||
default:
|
||||
c.encodeFailedResponse(c.clientConn, socksVersion, commandNotSupported)
|
||||
return fmt.Errorf("command %s is not supported", request.command)
|
||||
}
|
||||
}
|
||||
|
||||
func (c *socksConn) handleConnectRequest(ctx context.Context,
|
||||
socksVersion byte, request request,
|
||||
) error {
|
||||
destinationAddress := net.JoinHostPort(request.destination, fmt.Sprint(request.port))
|
||||
destinationConn, err := c.dialer.DialContext(ctx, "tcp", destinationAddress)
|
||||
if err != nil {
|
||||
c.encodeFailedResponse(c.clientConn, socksVersion, generalServerFailure)
|
||||
return err
|
||||
}
|
||||
defer destinationConn.Close()
|
||||
|
||||
destinationServerAddress := destinationConn.LocalAddr().String()
|
||||
destinationAddr, destinationPortStr, err := net.SplitHostPort(destinationServerAddress)
|
||||
if err != nil {
|
||||
return fmt.Errorf("splitting destination address: %w", err)
|
||||
}
|
||||
destinationPort, err := strconv.ParseUint(destinationPortStr, 10, 16)
|
||||
if err != nil {
|
||||
return fmt.Errorf("port is malformed: %q", destinationPortStr)
|
||||
}
|
||||
|
||||
var bindAddrType addrType
|
||||
if ip := net.ParseIP(destinationAddr); ip != nil {
|
||||
if ip.To4() != nil {
|
||||
bindAddrType = ipv4
|
||||
} else {
|
||||
bindAddrType = ipv6
|
||||
}
|
||||
} else {
|
||||
bindAddrType = domainName
|
||||
}
|
||||
|
||||
err = c.encodeSuccessResponse(c.clientConn, socksVersion, succeeded, bindAddrType,
|
||||
destinationAddr, uint16(destinationPort))
|
||||
if err != nil {
|
||||
c.encodeFailedResponse(c.clientConn, socksVersion, generalServerFailure)
|
||||
return fmt.Errorf("writing successful %s response: %w", request.command, err)
|
||||
}
|
||||
|
||||
const capacity = 2 // if one goroutine fails, we don't want to leak the other one
|
||||
errc := make(chan error, capacity)
|
||||
go func() {
|
||||
_, err := io.Copy(c.clientConn, destinationConn)
|
||||
if err != nil {
|
||||
err = fmt.Errorf("from backend to client: %w", err)
|
||||
}
|
||||
errc <- err
|
||||
}()
|
||||
go func() {
|
||||
_, err := io.Copy(destinationConn, c.clientConn)
|
||||
if err != nil {
|
||||
err = fmt.Errorf("from client to backend: %w", err)
|
||||
}
|
||||
errc <- err
|
||||
}()
|
||||
select {
|
||||
case err := <-errc:
|
||||
return err
|
||||
case <-ctx.Done():
|
||||
_ = destinationConn.Close()
|
||||
_ = c.clientConn.Close()
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
func (c *socksConn) handleUDPAssociateRequest(ctx context.Context,
|
||||
socksVersion byte, request request,
|
||||
) error {
|
||||
expectedAddrPort, err := udpAssociateExpectedClientEndpoint(request)
|
||||
if err != nil {
|
||||
c.encodeFailedResponse(c.clientConn, socksVersion, addressTypeNotSupported)
|
||||
return fmt.Errorf("deriving expected client address and port from request: %w", err)
|
||||
}
|
||||
|
||||
bindAddress, bindPort, bindAddrType, err := c.udpAssociationAddresses()
|
||||
if err != nil {
|
||||
c.encodeFailedResponse(c.clientConn, socksVersion, generalServerFailure)
|
||||
return fmt.Errorf("getting udp association addresses: %w", err)
|
||||
}
|
||||
|
||||
association, err := c.udpRouter.registerAssociation(c.clientConn, expectedAddrPort)
|
||||
if err != nil {
|
||||
c.encodeFailedResponse(c.clientConn, socksVersion, generalServerFailure)
|
||||
return fmt.Errorf("registering udp association: %w", err)
|
||||
}
|
||||
defer c.udpRouter.unregisterAssociation(association)
|
||||
|
||||
err = c.encodeSuccessResponse(c.clientConn, socksVersion, succeeded,
|
||||
bindAddrType, bindAddress, bindPort)
|
||||
if err != nil {
|
||||
c.encodeFailedResponse(c.clientConn, socksVersion, generalServerFailure)
|
||||
return fmt.Errorf("writing successful %s response: %w", udpAssociate, err)
|
||||
}
|
||||
|
||||
associationCtx, associationCancel := context.WithCancel(ctx)
|
||||
defer associationCancel()
|
||||
|
||||
var wg sync.WaitGroup
|
||||
|
||||
wg.Go(func() {
|
||||
c.udpRouter.runAssociationHandler(associationCtx, association)
|
||||
})
|
||||
|
||||
wg.Go(func() {
|
||||
_, _ = io.Copy(io.Discard, c.clientConn)
|
||||
associationCancel()
|
||||
})
|
||||
<-associationCtx.Done()
|
||||
wg.Wait()
|
||||
return nil
|
||||
}
|
||||
|
||||
func udpAssociateExpectedClientEndpoint(request request) (expectedAddrPort netip.AddrPort, err error) {
|
||||
switch request.addressType {
|
||||
case ipv4, ipv6:
|
||||
expectedClientAddress, parseErr := netip.ParseAddr(request.destination)
|
||||
if parseErr != nil {
|
||||
return netip.AddrPort{}, fmt.Errorf("parsing destination address: %w", parseErr)
|
||||
}
|
||||
expectedClientAddress = expectedClientAddress.Unmap()
|
||||
if !expectedClientAddress.IsUnspecified() {
|
||||
return netip.AddrPortFrom(expectedClientAddress, request.port), nil
|
||||
}
|
||||
return netip.AddrPortFrom(netip.Addr{}, request.port), nil
|
||||
case domainName:
|
||||
// For UDP associate, client endpoint matching is based on observed UDP source
|
||||
// address/port. A hostname is not directly matchable at this stage, so we
|
||||
// ignore the domain name request destination entirely.
|
||||
return netip.AddrPortFrom(netip.Addr{}, request.port), nil
|
||||
default:
|
||||
return netip.AddrPort{}, fmt.Errorf("address type %d is not supported", request.addressType)
|
||||
}
|
||||
}
|
||||
|
||||
func (c *socksConn) udpAssociationAddresses() (bindAddress string,
|
||||
bindPort uint16, bindAddrType addrType, err error,
|
||||
) {
|
||||
localAddress := c.udpRouter.localAddress().String()
|
||||
host, portString, err := net.SplitHostPort(localAddress)
|
||||
if err != nil {
|
||||
return "", 0, 0, fmt.Errorf("splitting local address: %w", err)
|
||||
}
|
||||
port, err := strconv.ParseUint(portString, 10, 16)
|
||||
if err != nil {
|
||||
return "", 0, 0, fmt.Errorf("parsing local port: %w", err)
|
||||
}
|
||||
bindAddress = host
|
||||
bindPort = uint16(port)
|
||||
if isUnspecifiedIPAddress(bindAddress) {
|
||||
controlLocalAddress := c.clientConn.LocalAddr().String()
|
||||
controlLocalHost, _, splitErr := net.SplitHostPort(controlLocalAddress)
|
||||
if splitErr != nil {
|
||||
return "", 0, 0, fmt.Errorf("splitting control connection local address: %w", splitErr)
|
||||
}
|
||||
bindAddress = controlLocalHost
|
||||
}
|
||||
|
||||
ipAddress := net.ParseIP(bindAddress)
|
||||
if ipAddress == nil {
|
||||
bindAddrType = domainName
|
||||
return bindAddress, bindPort, bindAddrType, nil
|
||||
}
|
||||
|
||||
if ipAddress.To4() != nil {
|
||||
bindAddrType = ipv4
|
||||
} else {
|
||||
bindAddrType = ipv6
|
||||
}
|
||||
|
||||
return bindAddress, bindPort, bindAddrType, nil
|
||||
}
|
||||
|
||||
func isUnspecifiedIPAddress(address string) bool {
|
||||
ipAddress, err := netip.ParseAddr(address)
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
return ipAddress.IsUnspecified()
|
||||
}
|
||||
|
||||
func decodeUDPDatagram(packet []byte) (destination string, payload []byte, err error) {
|
||||
const minimumPacketLength = 4
|
||||
if len(packet) < minimumPacketLength {
|
||||
return "", nil, fmt.Errorf("packet is too short: %d", len(packet))
|
||||
}
|
||||
if packet[0] != 0 || packet[1] != 0 {
|
||||
return "", nil, fmt.Errorf("reserved bytes are invalid: %x %x", packet[0], packet[1])
|
||||
}
|
||||
if packet[2] != 0 {
|
||||
return "", nil, fmt.Errorf("fragmentation is not supported")
|
||||
}
|
||||
|
||||
offset := 3
|
||||
addressType := addrType(packet[offset])
|
||||
offset++
|
||||
|
||||
switch addressType {
|
||||
case ipv4:
|
||||
const ipv4Length = 4
|
||||
if len(packet) < offset+ipv4Length+2 {
|
||||
return "", nil, fmt.Errorf("packet is too short for IPv4 address")
|
||||
}
|
||||
var ip [ipv4Length]byte
|
||||
copy(ip[:], packet[offset:offset+ipv4Length])
|
||||
destination = netip.AddrFrom4(ip).String()
|
||||
offset += ipv4Length
|
||||
case ipv6:
|
||||
const ipv6Length = 16
|
||||
if len(packet) < offset+ipv6Length+2 {
|
||||
return "", nil, fmt.Errorf("packet is too short for IPv6 address")
|
||||
}
|
||||
var ip [ipv6Length]byte
|
||||
copy(ip[:], packet[offset:offset+ipv6Length])
|
||||
destination = netip.AddrFrom16(ip).String()
|
||||
offset += ipv6Length
|
||||
case domainName:
|
||||
if len(packet) < offset+1 {
|
||||
return "", nil, fmt.Errorf("packet is too short for domain name length")
|
||||
}
|
||||
domainNameLength := int(packet[offset])
|
||||
offset++
|
||||
if len(packet) < offset+domainNameLength+2 {
|
||||
return "", nil, fmt.Errorf("packet is too short for domain name")
|
||||
}
|
||||
destination = string(packet[offset : offset+domainNameLength])
|
||||
offset += domainNameLength
|
||||
default:
|
||||
return "", nil, fmt.Errorf("address type is not supported: %d", addressType)
|
||||
}
|
||||
|
||||
port := binary.BigEndian.Uint16(packet[offset : offset+2])
|
||||
destination = net.JoinHostPort(destination, fmt.Sprint(port))
|
||||
offset += 2
|
||||
payload = packet[offset:]
|
||||
|
||||
return destination, payload, nil
|
||||
}
|
||||
|
||||
func encodeUDPDatagramToBuffer(writer io.Writer, sourceAddrPort netip.AddrPort,
|
||||
payload []byte,
|
||||
) error {
|
||||
address := sourceAddrPort.Addr()
|
||||
if !address.IsValid() {
|
||||
return errors.New("source address is not valid")
|
||||
}
|
||||
|
||||
err := writeUDPDatagramSourceAddress(writer, address)
|
||||
if err != nil {
|
||||
return fmt.Errorf("writing source address: %w", err)
|
||||
}
|
||||
|
||||
var portBytes [2]byte
|
||||
binary.BigEndian.PutUint16(portBytes[:], sourceAddrPort.Port())
|
||||
_, err = writer.Write(portBytes[:])
|
||||
if err != nil {
|
||||
return fmt.Errorf("writing destination port: %w", err)
|
||||
}
|
||||
|
||||
_, err = writer.Write(payload)
|
||||
if err != nil {
|
||||
return fmt.Errorf("writing payload: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func writeUDPDatagramSourceAddress(writer io.Writer, address netip.Addr) error {
|
||||
var addrType addrType
|
||||
var addressBytes []byte
|
||||
switch {
|
||||
case address.Is4():
|
||||
addrType = ipv4
|
||||
array := address.As4()
|
||||
addressBytes = array[:]
|
||||
case address.Is6():
|
||||
addrType = ipv6
|
||||
array := address.As16()
|
||||
addressBytes = array[:]
|
||||
default:
|
||||
return fmt.Errorf("address type is not supported: %v", address)
|
||||
}
|
||||
|
||||
_, err := writer.Write([]byte{0, 0, 0, byte(addrType)})
|
||||
if err != nil {
|
||||
return fmt.Errorf("writing header: %w", err)
|
||||
}
|
||||
_, err = writer.Write(addressBytes)
|
||||
if err != nil {
|
||||
return fmt.Errorf("writing IP address: %w", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// See https://datatracker.ietf.org/doc/html/rfc1928#section-3
|
||||
func verifyFirstNegotiation(reader io.Reader, requiredMethod authMethod) error {
|
||||
const headerLength = 2 // version + nMethods bytes
|
||||
header := make([]byte, headerLength)
|
||||
_, err := io.ReadFull(reader, header)
|
||||
if err != nil {
|
||||
return fmt.Errorf("reading header: %w", err)
|
||||
}
|
||||
|
||||
if header[0] != socks5Version {
|
||||
return fmt.Errorf("version is not supported: %d", header[0])
|
||||
}
|
||||
|
||||
nMethods := header[1]
|
||||
if nMethods == 0 {
|
||||
return fmt.Errorf("%w", errNoMethodIdentifiers)
|
||||
}
|
||||
|
||||
methodIdentifiers := make([]byte, nMethods)
|
||||
_, err = io.ReadFull(reader, methodIdentifiers)
|
||||
if err != nil {
|
||||
return fmt.Errorf("reading method identifiers: %w", err)
|
||||
}
|
||||
for _, methodIdentifier := range methodIdentifiers {
|
||||
if methodIdentifier == byte(requiredMethod) {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
return makeNoAcceptableMethodError(requiredMethod, methodIdentifiers)
|
||||
}
|
||||
|
||||
func makeNoAcceptableMethodError(requiredAuthMethod authMethod, methodIdentifiers []byte) error {
|
||||
methodNames := make([]string, len(methodIdentifiers))
|
||||
for i, methodIdentifier := range methodIdentifiers {
|
||||
methodNames[i] = fmt.Sprintf("%q", authMethod(methodIdentifier))
|
||||
}
|
||||
|
||||
return fmt.Errorf("%w: none of %s matches %s",
|
||||
errNoValidMethodIdentifier, strings.Join(methodNames, ", "),
|
||||
requiredAuthMethod)
|
||||
}
|
||||
|
||||
// See https://datatracker.ietf.org/doc/html/rfc1928#section-4
|
||||
type request struct {
|
||||
command cmdType
|
||||
destination string
|
||||
port uint16
|
||||
addressType addrType
|
||||
}
|
||||
|
||||
func decodeRequest(reader io.Reader, expectedVersion byte) (req request, err error) {
|
||||
const headerLength = 4
|
||||
header := [headerLength]byte{}
|
||||
_, err = io.ReadFull(reader, header[:])
|
||||
if err != nil {
|
||||
return request{}, fmt.Errorf("reading header: %w", err)
|
||||
}
|
||||
|
||||
version := header[0]
|
||||
switch {
|
||||
case version != expectedVersion:
|
||||
return request{}, fmt.Errorf("version is not supported: expected %d and got %d",
|
||||
expectedVersion, version)
|
||||
case header[2] != 0:
|
||||
return request{}, fmt.Errorf("reserved header byte must be 0 but got %d", header[2])
|
||||
}
|
||||
|
||||
req.command = cmdType(header[1])
|
||||
// header[2] is RSV byte
|
||||
req.addressType = addrType(header[3])
|
||||
|
||||
switch req.addressType {
|
||||
case ipv4:
|
||||
var ip [4]byte
|
||||
_, err = io.ReadFull(reader, ip[:])
|
||||
if err != nil {
|
||||
return request{}, fmt.Errorf("reading IPv4 address: %w", err)
|
||||
}
|
||||
req.destination = netip.AddrFrom4(ip).String()
|
||||
case ipv6:
|
||||
var ip [16]byte
|
||||
_, err = io.ReadFull(reader, ip[:])
|
||||
if err != nil {
|
||||
return request{}, fmt.Errorf("reading IPv6 address: %w", err)
|
||||
}
|
||||
req.destination = netip.AddrFrom16(ip).String()
|
||||
case domainName:
|
||||
var header [1]byte
|
||||
_, err = io.ReadFull(reader, header[:])
|
||||
if err != nil {
|
||||
return request{}, fmt.Errorf("reading domain name header: %w", err)
|
||||
}
|
||||
domainName := make([]byte, header[0])
|
||||
_, err = io.ReadFull(reader, domainName)
|
||||
if err != nil {
|
||||
return request{}, fmt.Errorf("reading domain name bytes: %w", err)
|
||||
}
|
||||
req.destination = string(domainName)
|
||||
default:
|
||||
return request{}, fmt.Errorf("address type is not supported: %d", req.addressType)
|
||||
}
|
||||
|
||||
var portBytes [2]byte
|
||||
_, err = io.ReadFull(reader, portBytes[:])
|
||||
if err != nil {
|
||||
return request{}, fmt.Errorf("reading port: %w", err)
|
||||
}
|
||||
req.port = binary.BigEndian.Uint16(portBytes[:])
|
||||
|
||||
return req, nil
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,370 @@
|
||||
package socks5
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"net"
|
||||
"net/netip"
|
||||
"sync"
|
||||
)
|
||||
|
||||
type udpAssociation struct {
|
||||
id uint64
|
||||
clientAddrPort netip.AddrPort
|
||||
expectedAddrPort netip.AddrPort
|
||||
controlConnAddr netip.Addr
|
||||
packetCh chan *bytes.Buffer
|
||||
}
|
||||
|
||||
type udpRouter struct {
|
||||
logger Logger
|
||||
|
||||
listener net.PacketConn
|
||||
mutex sync.Mutex
|
||||
bufferPool sync.Pool
|
||||
nextAssociationID uint64
|
||||
clientAddrPortToAssociation map[netip.AddrPort]udpAssociation
|
||||
clientIPToPendingAssociations map[netip.Addr][]udpAssociation
|
||||
associationIDToClientAddrPort map[uint64]netip.AddrPort
|
||||
}
|
||||
|
||||
const (
|
||||
maxUDPPacketLength = 65535
|
||||
maxSOCKS5UDPDatagramOverhead = 3 + 1 + 16 + 2
|
||||
pooledUDPPacketBufferCapacity = maxUDPPacketLength + maxSOCKS5UDPDatagramOverhead
|
||||
)
|
||||
|
||||
func newUDPRouter(ctx context.Context, address string, logger Logger) (router *udpRouter, err error) {
|
||||
config := &net.ListenConfig{}
|
||||
listener, err := config.ListenPacket(ctx, "udp", address)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("UDP listening: %w", err)
|
||||
}
|
||||
|
||||
return &udpRouter{
|
||||
logger: logger,
|
||||
listener: listener,
|
||||
bufferPool: sync.Pool{
|
||||
New: func() any {
|
||||
return bytes.NewBuffer(make([]byte, 0, pooledUDPPacketBufferCapacity))
|
||||
},
|
||||
},
|
||||
nextAssociationID: 1,
|
||||
clientAddrPortToAssociation: make(map[netip.AddrPort]udpAssociation),
|
||||
clientIPToPendingAssociations: make(map[netip.Addr][]udpAssociation),
|
||||
associationIDToClientAddrPort: make(map[uint64]netip.AddrPort),
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (r *udpRouter) localAddress() net.Addr {
|
||||
return r.listener.LocalAddr()
|
||||
}
|
||||
|
||||
func (r *udpRouter) close() error {
|
||||
return r.listener.Close()
|
||||
}
|
||||
|
||||
func (r *udpRouter) registerAssociation(controlConn net.Conn, expectedAddrPort netip.AddrPort) (udpAssociation, error) {
|
||||
controlConnAddrPort, err := netip.ParseAddrPort(controlConn.RemoteAddr().String())
|
||||
if err != nil {
|
||||
return udpAssociation{}, fmt.Errorf("parsing control connection address: %w", err)
|
||||
}
|
||||
controlConnAddr := controlConnAddrPort.Addr().Unmap()
|
||||
|
||||
r.mutex.Lock()
|
||||
defer r.mutex.Unlock()
|
||||
|
||||
const udpPacketChannelBuffer = 64
|
||||
associationID := r.nextAssociationID
|
||||
r.nextAssociationID++
|
||||
|
||||
association := udpAssociation{
|
||||
id: associationID,
|
||||
expectedAddrPort: expectedAddrPort,
|
||||
controlConnAddr: controlConnAddr,
|
||||
packetCh: make(chan *bytes.Buffer, udpPacketChannelBuffer),
|
||||
}
|
||||
|
||||
if expectedAddrPort.Addr().IsValid() && expectedAddrPort.Port() != 0 {
|
||||
association.clientAddrPort = expectedAddrPort
|
||||
r.clientAddrPortToAssociation[association.clientAddrPort] = association
|
||||
r.associationIDToClientAddrPort[association.id] = association.clientAddrPort
|
||||
return association, nil
|
||||
}
|
||||
|
||||
pendingAssociations := r.clientIPToPendingAssociations[controlConnAddr]
|
||||
pendingAssociations = append(pendingAssociations, association)
|
||||
r.clientIPToPendingAssociations[controlConnAddr] = pendingAssociations
|
||||
|
||||
return association, nil
|
||||
}
|
||||
|
||||
func (r *udpRouter) unregisterAssociation(association udpAssociation) {
|
||||
r.mutex.Lock()
|
||||
defer r.mutex.Unlock()
|
||||
|
||||
clientAddrPort, hasClientAddress := r.associationIDToClientAddrPort[association.id]
|
||||
if hasClientAddress {
|
||||
delete(r.associationIDToClientAddrPort, association.id)
|
||||
delete(r.clientAddrPortToAssociation, clientAddrPort)
|
||||
}
|
||||
|
||||
pendingAssociations := r.clientIPToPendingAssociations[association.controlConnAddr]
|
||||
for i, pendingAssociation := range pendingAssociations {
|
||||
if pendingAssociation.id == association.id {
|
||||
pendingAssociations = append(pendingAssociations[:i], pendingAssociations[i+1:]...)
|
||||
break
|
||||
}
|
||||
}
|
||||
if len(pendingAssociations) == 0 {
|
||||
delete(r.clientIPToPendingAssociations, association.controlConnAddr)
|
||||
} else {
|
||||
r.clientIPToPendingAssociations[association.controlConnAddr] = pendingAssociations
|
||||
}
|
||||
}
|
||||
|
||||
func (r *udpRouter) run(ctx context.Context) error {
|
||||
packetBuffer := make([]byte, maxUDPPacketLength)
|
||||
|
||||
for {
|
||||
packetLength, sourceAddress, err := r.listener.ReadFrom(packetBuffer)
|
||||
if err != nil {
|
||||
if ctx.Err() != nil && errors.Is(err, net.ErrClosed) {
|
||||
return nil
|
||||
}
|
||||
return fmt.Errorf("reading UDP packet: %w", err)
|
||||
}
|
||||
|
||||
sourceAddrPort, err := netAddrToNetipAddrPort(sourceAddress)
|
||||
if err != nil {
|
||||
r.logger.Warnf("parsing source address: %s", err)
|
||||
continue
|
||||
}
|
||||
buffer := r.bufferPool.Get().(*bytes.Buffer) //nolint:forcetypeassert
|
||||
buffer.Reset()
|
||||
_, err = buffer.Write(packetBuffer[:packetLength])
|
||||
if err != nil {
|
||||
r.bufferPool.Put(buffer)
|
||||
r.logger.Warnf("buffering packet: %s", err)
|
||||
continue
|
||||
}
|
||||
err = r.routePacket(sourceAddrPort, buffer)
|
||||
if err != nil {
|
||||
r.logger.Warnf("failed routing UDP packet: %s", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (r *udpRouter) routePacket(sourceAddrPort netip.AddrPort, packet *bytes.Buffer) error {
|
||||
r.mutex.Lock()
|
||||
association, packetFromClient := r.findClientAssociation(sourceAddrPort)
|
||||
r.mutex.Unlock()
|
||||
|
||||
if !packetFromClient {
|
||||
r.bufferPool.Put(packet)
|
||||
return nil
|
||||
}
|
||||
|
||||
select {
|
||||
case association.packetCh <- packet:
|
||||
return nil
|
||||
default:
|
||||
r.bufferPool.Put(packet)
|
||||
return errors.New("association packet queue full")
|
||||
}
|
||||
}
|
||||
|
||||
func (r *udpRouter) findClientAssociation(sourceAddrPort netip.AddrPort) (
|
||||
association udpAssociation, ok bool,
|
||||
) {
|
||||
association, ok = r.clientAddrPortToAssociation[sourceAddrPort]
|
||||
if ok {
|
||||
return association, true
|
||||
}
|
||||
sourceAddr := sourceAddrPort.Addr()
|
||||
|
||||
pendingAssociations := r.clientIPToPendingAssociations[sourceAddr]
|
||||
if len(pendingAssociations) == 0 {
|
||||
return udpAssociation{}, false
|
||||
}
|
||||
|
||||
index := -1
|
||||
for i, pendingAssociation := range pendingAssociations {
|
||||
if matchesExpectedClientEndpoint(pendingAssociation, sourceAddrPort) {
|
||||
association = pendingAssociation
|
||||
index = i
|
||||
break
|
||||
}
|
||||
}
|
||||
if index == -1 {
|
||||
return udpAssociation{}, false
|
||||
}
|
||||
|
||||
r.clientIPToPendingAssociations[sourceAddr] = append(pendingAssociations[:index], pendingAssociations[index+1:]...)
|
||||
if len(r.clientIPToPendingAssociations[sourceAddr]) == 0 {
|
||||
delete(r.clientIPToPendingAssociations, sourceAddr)
|
||||
}
|
||||
|
||||
association.clientAddrPort = sourceAddrPort
|
||||
r.clientAddrPortToAssociation[sourceAddrPort] = association
|
||||
r.associationIDToClientAddrPort[association.id] = sourceAddrPort
|
||||
|
||||
return association, true
|
||||
}
|
||||
|
||||
func matchesExpectedClientEndpoint(association udpAssociation, sourceAddrPort netip.AddrPort) bool {
|
||||
switch {
|
||||
case association.expectedAddrPort.Addr().IsValid() && sourceAddrPort.Addr() != association.expectedAddrPort.Addr():
|
||||
return false
|
||||
case association.expectedAddrPort.Port() != 0 && sourceAddrPort.Port() != association.expectedAddrPort.Port():
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
func (r *udpRouter) clientAddrPortForAssociation(associationID uint64) (
|
||||
clientAddrPort netip.AddrPort, ok bool,
|
||||
) {
|
||||
r.mutex.Lock()
|
||||
defer r.mutex.Unlock()
|
||||
|
||||
clientAddrPort, ok = r.associationIDToClientAddrPort[associationID]
|
||||
return clientAddrPort, ok
|
||||
}
|
||||
|
||||
func (r *udpRouter) runAssociationHandler(ctx context.Context, association udpAssociation) {
|
||||
config := &net.ListenConfig{}
|
||||
socket, err := config.ListenPacket(ctx, "udp", ":0")
|
||||
if err != nil {
|
||||
r.logger.Warnf("creating per-association UDP socket: %s", err)
|
||||
return
|
||||
}
|
||||
defer socket.Close()
|
||||
|
||||
go closeSocketOnContextDone(ctx, socket)
|
||||
|
||||
packetBuffer := make([]byte, maxUDPPacketLength)
|
||||
|
||||
forwardDoneCh := make(chan struct{})
|
||||
go r.forwardClientPackets(ctx, socket, association.packetCh, forwardDoneCh)
|
||||
|
||||
for {
|
||||
packetLength, sourceAddress, err := socket.ReadFrom(packetBuffer)
|
||||
if err != nil {
|
||||
if ctx.Err() != nil || errors.Is(err, net.ErrClosed) {
|
||||
<-forwardDoneCh
|
||||
return
|
||||
}
|
||||
r.logger.Warnf("reading from per-association UDP socket: %s", err)
|
||||
continue
|
||||
}
|
||||
|
||||
sourceAddrPort, err := netAddrToNetipAddrPort(sourceAddress)
|
||||
if err != nil {
|
||||
r.logger.Warnf("parsing source address from destination: %s", err)
|
||||
continue
|
||||
}
|
||||
|
||||
buffer := r.bufferPool.Get().(*bytes.Buffer) //nolint:forcetypeassert
|
||||
buffer.Reset()
|
||||
err = encodeUDPDatagramToBuffer(buffer, sourceAddrPort, packetBuffer[:packetLength])
|
||||
if err != nil {
|
||||
r.bufferPool.Put(buffer)
|
||||
r.logger.Warnf("encoding response datagram: %s", err)
|
||||
continue
|
||||
}
|
||||
|
||||
clientAddrPort, found := r.clientAddrPortForAssociation(association.id)
|
||||
if !found {
|
||||
r.bufferPool.Put(buffer)
|
||||
r.logger.Warnf("client address not found for association id %d", association.id)
|
||||
continue
|
||||
}
|
||||
|
||||
clientUDPAddress := &net.UDPAddr{
|
||||
IP: clientAddrPort.Addr().AsSlice(),
|
||||
Port: int(clientAddrPort.Port()),
|
||||
}
|
||||
_, err = r.listener.WriteTo(buffer.Bytes(), clientUDPAddress)
|
||||
r.bufferPool.Put(buffer)
|
||||
if err != nil {
|
||||
r.logger.Warnf("writing response to client: %s", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func closeSocketOnContextDone(ctx context.Context, socket net.PacketConn) {
|
||||
<-ctx.Done()
|
||||
_ = socket.Close()
|
||||
}
|
||||
|
||||
func (r *udpRouter) forwardClientPackets(ctx context.Context, socket net.PacketConn,
|
||||
packetCh <-chan *bytes.Buffer, done chan<- struct{},
|
||||
) {
|
||||
defer close(done)
|
||||
|
||||
for {
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
return
|
||||
case buffer, ok := <-packetCh:
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
|
||||
err := r.writeClientPacketToDestination(ctx, socket, buffer)
|
||||
r.bufferPool.Put(buffer)
|
||||
if err != nil {
|
||||
r.logger.Warnf("forwarding client packet to destination: %s", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (r *udpRouter) writeClientPacketToDestination(ctx context.Context,
|
||||
socket net.PacketConn, packet *bytes.Buffer,
|
||||
) error {
|
||||
destination, payload, err := decodeUDPDatagram(packet.Bytes())
|
||||
if err != nil {
|
||||
return fmt.Errorf("decoding UDP datagram: %w", err)
|
||||
}
|
||||
|
||||
host, portStr, err := net.SplitHostPort(destination)
|
||||
if err != nil {
|
||||
return fmt.Errorf("splitting destination host and port: %w", err)
|
||||
}
|
||||
|
||||
if _, err := netip.ParseAddr(host); err != nil { // domain name
|
||||
addrs, err := net.DefaultResolver.LookupHost(ctx, host)
|
||||
if err != nil {
|
||||
return fmt.Errorf("resolving destination host: %w", err)
|
||||
}
|
||||
if len(addrs) == 0 {
|
||||
return fmt.Errorf("resolving destination host: no addresses found for %q", host)
|
||||
}
|
||||
|
||||
destination = net.JoinHostPort(addrs[0], portStr)
|
||||
}
|
||||
|
||||
resolvedDestinationUDPAddress, err := net.ResolveUDPAddr("udp", destination)
|
||||
if err != nil {
|
||||
return fmt.Errorf("resolving destination UDP address: %w", err)
|
||||
}
|
||||
|
||||
_, err = socket.WriteTo(payload, resolvedDestinationUDPAddress)
|
||||
if err != nil && ctx.Err() == nil {
|
||||
return fmt.Errorf("writing payload to destination: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func netAddrToNetipAddrPort(addr net.Addr) (netip.AddrPort, error) {
|
||||
addrPort, err := netip.ParseAddrPort(addr.String())
|
||||
if err != nil {
|
||||
return netip.AddrPort{}, fmt.Errorf("parsing address: %w", err)
|
||||
}
|
||||
return netip.AddrPortFrom(addrPort.Addr().Unmap(), addrPort.Port()), nil
|
||||
}
|
||||
@@ -0,0 +1,164 @@
|
||||
//go:build integration
|
||||
|
||||
package socks5
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"math/rand/v2"
|
||||
"net"
|
||||
"net/netip"
|
||||
"strconv"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/miekg/dns"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
"go.uber.org/mock/gomock"
|
||||
)
|
||||
|
||||
func Test_udpRouter_ResolveGithubFromCloudflareDNS(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
ctx := t.Context()
|
||||
var cancel context.CancelFunc
|
||||
deadline, hasDeadline := ctx.Deadline()
|
||||
if hasDeadline {
|
||||
const deadlineBuffer = 500 * time.Millisecond
|
||||
deadline = deadline.Add(-deadlineBuffer)
|
||||
} else {
|
||||
const defaultTimeout = 10 * time.Second
|
||||
deadline = time.Now().Add(defaultTimeout)
|
||||
}
|
||||
ctx, cancel = context.WithDeadline(ctx, deadline)
|
||||
|
||||
ctrl := gomock.NewController(t)
|
||||
logger := NewMockLogger(ctrl)
|
||||
|
||||
router, err := newUDPRouter(ctx, "127.0.0.1:0", logger)
|
||||
require.NoError(t, err)
|
||||
|
||||
routerRunErrCh := make(chan error)
|
||||
go func() {
|
||||
routerRunErrCh <- router.run(ctx)
|
||||
}()
|
||||
|
||||
t.Cleanup(func() {
|
||||
cancel()
|
||||
err := router.close()
|
||||
assert.NoError(t, err, "closing router")
|
||||
runErr := <-routerRunErrCh
|
||||
assert.NoError(t, runErr)
|
||||
})
|
||||
|
||||
controlListener, err := (&net.ListenConfig{}).Listen(ctx, "tcp", "127.0.0.1:0")
|
||||
require.NoError(t, err)
|
||||
t.Cleanup(func() {
|
||||
err := controlListener.Close()
|
||||
assert.NoError(t, err, "closing control listener")
|
||||
})
|
||||
|
||||
acceptedConnCh := make(chan net.Conn)
|
||||
go func() {
|
||||
acceptedConn, acceptErr := controlListener.Accept()
|
||||
assert.NoError(t, acceptErr, "accepting control connection")
|
||||
if acceptErr != nil {
|
||||
return
|
||||
}
|
||||
acceptedConnCh <- acceptedConn
|
||||
}()
|
||||
|
||||
clientControlConn, err := (&net.Dialer{}).DialContext(ctx, "tcp", controlListener.Addr().String())
|
||||
require.NoError(t, err)
|
||||
t.Cleanup(func() {
|
||||
err = clientControlConn.Close()
|
||||
assert.NoError(t, err, "closing client control connection")
|
||||
})
|
||||
|
||||
serverControlConn := <-acceptedConnCh
|
||||
t.Cleanup(func() {
|
||||
err := serverControlConn.Close()
|
||||
assert.NoError(t, err, "closing server control connection")
|
||||
})
|
||||
|
||||
association, err := router.registerAssociation(serverControlConn, netip.AddrPort{})
|
||||
require.NoError(t, err)
|
||||
t.Cleanup(func() {
|
||||
router.unregisterAssociation(association)
|
||||
})
|
||||
|
||||
associationCtx, associationCancel := context.WithCancel(ctx)
|
||||
handlerDoneCh := make(chan struct{})
|
||||
go func() {
|
||||
router.runAssociationHandler(associationCtx, association)
|
||||
close(handlerDoneCh)
|
||||
}()
|
||||
t.Cleanup(func() {
|
||||
associationCancel()
|
||||
<-handlerDoneCh
|
||||
})
|
||||
|
||||
udpRouterAddress, err := net.ResolveUDPAddr("udp", router.localAddress().String())
|
||||
require.NoError(t, err)
|
||||
|
||||
clientUDPConn, err := net.DialUDP("udp", nil, udpRouterAddress)
|
||||
require.NoError(t, err)
|
||||
t.Cleanup(func() {
|
||||
err := clientUDPConn.Close()
|
||||
assert.NoError(t, err, "closing client UDP connection")
|
||||
})
|
||||
|
||||
queryID := uint16(rand.Uint32()) //nolint:gosec
|
||||
dnsRequest := &dns.Msg{
|
||||
MsgHdr: dns.MsgHdr{
|
||||
Id: queryID,
|
||||
RecursionDesired: true,
|
||||
},
|
||||
Question: []dns.Question{{
|
||||
Name: dns.Fqdn("github.com"),
|
||||
Qtype: dns.TypeA,
|
||||
Qclass: dns.ClassINET,
|
||||
}},
|
||||
}
|
||||
dnsQuery, err := dnsRequest.Pack()
|
||||
require.NoError(t, err)
|
||||
|
||||
targetAddrPort := netip.MustParseAddrPort("1.1.1.1:53")
|
||||
socksDatagramBuffer := bytes.NewBuffer(nil)
|
||||
err = encodeUDPDatagramToBuffer(socksDatagramBuffer, targetAddrPort, dnsQuery)
|
||||
require.NoError(t, err)
|
||||
socksDatagram := socksDatagramBuffer.Bytes()
|
||||
|
||||
err = clientUDPConn.SetDeadline(deadline)
|
||||
require.NoError(t, err)
|
||||
|
||||
_, err = clientUDPConn.Write(socksDatagram)
|
||||
require.NoError(t, err)
|
||||
|
||||
responseBuffer := make([]byte, maxUDPPacketLength)
|
||||
responseLength, err := clientUDPConn.Read(responseBuffer)
|
||||
require.NoError(t, err)
|
||||
|
||||
responseDestination, responsePayload, err := decodeUDPDatagram(responseBuffer[:responseLength])
|
||||
require.NoError(t, err)
|
||||
|
||||
responseHost, responsePortString, err := net.SplitHostPort(responseDestination)
|
||||
require.NoError(t, err)
|
||||
responsePort, err := strconv.ParseUint(responsePortString, 10, 16)
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, uint64(53), responsePort)
|
||||
assert.NotEmpty(t, responseHost)
|
||||
|
||||
dnsResponse := new(dns.Msg)
|
||||
err = dnsResponse.Unpack(responsePayload)
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, queryID, dnsResponse.Id)
|
||||
assert.True(t, dnsResponse.Response)
|
||||
assert.Equal(t, dns.RcodeSuccess, dnsResponse.Rcode)
|
||||
require.NotEmpty(t, dnsResponse.Question)
|
||||
assert.Equal(t, dns.Fqdn("github.com"), dnsResponse.Question[0].Name)
|
||||
assert.Equal(t, dns.TypeA, dnsResponse.Question[0].Qtype)
|
||||
assert.NotEmpty(t, dnsResponse.Answer)
|
||||
require.NoError(t, err)
|
||||
}
|
||||
@@ -0,0 +1,62 @@
|
||||
package socks5
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
)
|
||||
|
||||
// See https://datatracker.ietf.org/doc/html/rfc1929#section-2
|
||||
func usernamePasswordSubnegotiate(conn io.ReadWriter, username, password string) (err error) {
|
||||
status := byte(1)
|
||||
const defaultVersion = byte(1)
|
||||
|
||||
const headerLength = 2
|
||||
var header [headerLength]byte
|
||||
_, err = io.ReadFull(conn, header[:])
|
||||
if err != nil {
|
||||
_, _ = conn.Write([]byte{defaultVersion, status})
|
||||
return fmt.Errorf("reading header: %w", err)
|
||||
}
|
||||
|
||||
if header[0] != authUsernamePasswordSubNegotiation1 {
|
||||
_, _ = conn.Write([]byte{defaultVersion, status})
|
||||
return fmt.Errorf("subnegotiation version not supported: %d", header[0])
|
||||
}
|
||||
version := header[0]
|
||||
|
||||
usernameBytes := make([]byte, header[1])
|
||||
_, err = io.ReadFull(conn, usernameBytes)
|
||||
if err != nil {
|
||||
_, _ = conn.Write([]byte{version, status})
|
||||
return fmt.Errorf("reading username bytes: %w", err)
|
||||
} else if username != string(usernameBytes) {
|
||||
_, _ = conn.Write([]byte{version, status})
|
||||
return fmt.Errorf("username received is not valid")
|
||||
}
|
||||
|
||||
const passwordHeaderLength = 1
|
||||
passwordHeader := make([]byte, passwordHeaderLength)
|
||||
_, err = io.ReadFull(conn, passwordHeader)
|
||||
if err != nil {
|
||||
_, _ = conn.Write([]byte{version, status})
|
||||
return fmt.Errorf("reading password length: %w", err)
|
||||
}
|
||||
|
||||
passwordBytes := make([]byte, passwordHeader[0])
|
||||
_, err = io.ReadFull(conn, passwordBytes)
|
||||
if err != nil {
|
||||
_, _ = conn.Write([]byte{version, status})
|
||||
return fmt.Errorf("reading password bytes: %w", err)
|
||||
} else if password != string(passwordBytes) {
|
||||
_, _ = conn.Write([]byte{version, status})
|
||||
return fmt.Errorf("password not valid for username %q", string(usernameBytes))
|
||||
}
|
||||
|
||||
status = 0
|
||||
_, err = conn.Write([]byte{version, status})
|
||||
if err != nil {
|
||||
return fmt.Errorf("writing success status: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
@@ -1,32 +1,23 @@
|
||||
package storage
|
||||
|
||||
import (
|
||||
"embed"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"path"
|
||||
"path/filepath"
|
||||
|
||||
serversmodule "github.com/qdm12/gluetun-servers/pkg/servers"
|
||||
"github.com/qdm12/gluetun/internal/constants/providers"
|
||||
"github.com/qdm12/gluetun/internal/models"
|
||||
)
|
||||
|
||||
//go:embed servers.json
|
||||
var allServersEmbedFS embed.FS
|
||||
|
||||
func parseHardcodedServers() (allServers models.AllServers) {
|
||||
f, err := allServersEmbedFS.Open("servers.json")
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
defer f.Close() // no-op
|
||||
decoder := json.NewDecoder(f)
|
||||
err = decoder.Decode(&allServers)
|
||||
if err != nil {
|
||||
panic("decoding servers.json: " + err.Error())
|
||||
}
|
||||
allProviders := providers.All()
|
||||
|
||||
for provider, metadata := range allServers.ProviderToServers {
|
||||
filename := path.Base(metadata.Filepath)
|
||||
const version = 1
|
||||
allServers.ProviderToServers = make(map[string]models.Servers, len(allProviders))
|
||||
allServers.Version = version
|
||||
for _, provider := range allProviders {
|
||||
filename := provider + ".json"
|
||||
providerFile, err := serversmodule.Files.Open(filename)
|
||||
if err != nil {
|
||||
panic(fmt.Sprintf("reading embedded provider file %s for %s: %s", filename, provider, err))
|
||||
@@ -44,7 +35,8 @@ func parseHardcodedServers() (allServers models.AllServers) {
|
||||
filename, provider))
|
||||
}
|
||||
|
||||
providerServers.Filepath = metadata.Filepath // inherit filepath from servers.json
|
||||
const serversPath = "/gluetun/servers/"
|
||||
providerServers.Filepath = filepath.Join(serversPath, filename)
|
||||
allServers.ProviderToServers[provider] = providerServers
|
||||
}
|
||||
|
||||
|
||||
@@ -3,6 +3,5 @@ package storage
|
||||
import "fmt"
|
||||
|
||||
func panicOnProviderMissingHardcoded(provider string) {
|
||||
panic(fmt.Sprintf("provider %s not found in hardcoded servers map; "+
|
||||
"did you add the provider key in the embedded servers.json?", provider))
|
||||
panic(fmt.Sprintf("provider %s not found in hardcoded servers map", provider))
|
||||
}
|
||||
|
||||
@@ -1,5 +1,10 @@
|
||||
// Code generated by MockGen. DO NOT EDIT.
|
||||
// Source: github.com/qdm12/gluetun/internal/storage (interfaces: Logger)
|
||||
//
|
||||
// Generated by this command:
|
||||
//
|
||||
// mockgen -destination=mocks_test.go -package storage . Logger
|
||||
//
|
||||
|
||||
// Package storage is a generated GoMock package.
|
||||
package storage
|
||||
@@ -7,13 +12,14 @@ package storage
|
||||
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,42 +40,42 @@ func (m *MockLogger) EXPECT() *MockLoggerMockRecorder {
|
||||
}
|
||||
|
||||
// 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)
|
||||
}
|
||||
|
||||
// Infof mocks base method.
|
||||
func (m *MockLogger) Infof(arg0 string, arg1 ...interface{}) {
|
||||
func (m *MockLogger) Infof(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, "Infof", varargs...)
|
||||
}
|
||||
|
||||
// Infof indicates an expected call of Infof.
|
||||
func (mr *MockLoggerMockRecorder) Infof(arg0 interface{}, arg1 ...interface{}) *gomock.Call {
|
||||
func (mr *MockLoggerMockRecorder) Infof(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, "Infof", reflect.TypeOf((*MockLogger)(nil).Infof), varargs...)
|
||||
}
|
||||
|
||||
// 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)
|
||||
}
|
||||
|
||||
@@ -4,11 +4,11 @@ import (
|
||||
"fmt"
|
||||
"testing"
|
||||
|
||||
"github.com/golang/mock/gomock"
|
||||
"github.com/qdm12/gluetun/internal/constants/providers"
|
||||
"github.com/qdm12/gluetun/internal/models"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
"go.uber.org/mock/gomock"
|
||||
)
|
||||
|
||||
func populateProviderToVersion(providerToVersion map[string]uint16) map[string]uint16 {
|
||||
@@ -152,8 +152,7 @@ func Test_extractServersFromBytes(t *testing.T) {
|
||||
allProviders[0]: 1,
|
||||
// Missing provider allProviders[1]
|
||||
}
|
||||
expectedPanicValue := fmt.Sprintf("provider %s not found in hardcoded servers map; "+
|
||||
"did you add the provider key in the embedded servers.json?", allProviders[1])
|
||||
expectedPanicValue := fmt.Sprintf("provider %s not found in hardcoded servers map", allProviders[1])
|
||||
assert.PanicsWithValue(t, expectedPanicValue, func() {
|
||||
_, _ = s.extractServersFromBytes(b, hardcodedVersions)
|
||||
})
|
||||
|
||||
@@ -1,72 +0,0 @@
|
||||
{
|
||||
"version": 1,
|
||||
"airvpn": {
|
||||
"filepath": "/gluetun/servers/airvpn.json"
|
||||
},
|
||||
"cyberghost": {
|
||||
"filepath": "/gluetun/servers/cyberghost.json"
|
||||
},
|
||||
"expressvpn": {
|
||||
"filepath": "/gluetun/servers/expressvpn.json"
|
||||
},
|
||||
"fastestvpn": {
|
||||
"filepath": "/gluetun/servers/fastestvpn.json"
|
||||
},
|
||||
"giganews": {
|
||||
"filepath": "/gluetun/servers/giganews.json"
|
||||
},
|
||||
"hidemyass": {
|
||||
"filepath": "/gluetun/servers/hidemyass.json"
|
||||
},
|
||||
"ipvanish": {
|
||||
"filepath": "/gluetun/servers/ipvanish.json"
|
||||
},
|
||||
"ivpn": {
|
||||
"filepath": "/gluetun/servers/ivpn.json"
|
||||
},
|
||||
"mullvad": {
|
||||
"filepath": "/gluetun/servers/mullvad.json"
|
||||
},
|
||||
"nordvpn": {
|
||||
"filepath": "/gluetun/servers/nordvpn.json"
|
||||
},
|
||||
"perfect privacy": {
|
||||
"filepath": "/gluetun/servers/perfect privacy.json"
|
||||
},
|
||||
"privado": {
|
||||
"filepath": "/gluetun/servers/privado.json"
|
||||
},
|
||||
"private internet access": {
|
||||
"filepath": "/gluetun/servers/private internet access.json"
|
||||
},
|
||||
"privatevpn": {
|
||||
"filepath": "/gluetun/servers/privatevpn.json"
|
||||
},
|
||||
"protonvpn": {
|
||||
"filepath": "/gluetun/servers/protonvpn.json"
|
||||
},
|
||||
"purevpn": {
|
||||
"filepath": "/gluetun/servers/purevpn.json"
|
||||
},
|
||||
"slickvpn": {
|
||||
"filepath": "/gluetun/servers/slickvpn.json"
|
||||
},
|
||||
"surfshark": {
|
||||
"filepath": "/gluetun/servers/surfshark.json"
|
||||
},
|
||||
"torguard": {
|
||||
"filepath": "/gluetun/servers/torguard.json"
|
||||
},
|
||||
"vpn unlimited": {
|
||||
"filepath": "/gluetun/servers/vpn unlimited.json"
|
||||
},
|
||||
"vpnsecure": {
|
||||
"filepath": "/gluetun/servers/vpnsecure.json"
|
||||
},
|
||||
"vyprvpn": {
|
||||
"filepath": "/gluetun/servers/vyprvpn.json"
|
||||
},
|
||||
"windscribe": {
|
||||
"filepath": "/gluetun/servers/windscribe.json"
|
||||
}
|
||||
}
|
||||
@@ -31,7 +31,7 @@ func getGithubReleases(ctx context.Context, client *http.Client) (releases []git
|
||||
ctx, cancel := context.WithTimeout(ctx, timeout)
|
||||
defer cancel()
|
||||
|
||||
const url = "https://api.github.com/repos/qdm12/gluetun/releases"
|
||||
const url = "https://api.github.com/repos/passteque/gluetun/releases"
|
||||
request, err := http.NewRequestWithContext(ctx, http.MethodGet, url, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@@ -62,7 +62,7 @@ func getGithubCommits(ctx context.Context, client *http.Client) (commits []githu
|
||||
ctx, cancel := context.WithTimeout(ctx, timeout)
|
||||
defer cancel()
|
||||
|
||||
const url = "https://api.github.com/repos/qdm12/gluetun/commits"
|
||||
const url = "https://api.github.com/repos/passteque/gluetun/commits"
|
||||
request, err := http.NewRequestWithContext(ctx, http.MethodGet, url, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
|
||||
@@ -5,9 +5,9 @@ import (
|
||||
"net/netip"
|
||||
"testing"
|
||||
|
||||
"github.com/golang/mock/gomock"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
"go.uber.org/mock/gomock"
|
||||
)
|
||||
|
||||
func Test_AddAddresses(t *testing.T) {
|
||||
|
||||
@@ -1,5 +1,10 @@
|
||||
// Code generated by MockGen. DO NOT EDIT.
|
||||
// Source: github.com/qdm12/gluetun/internal/wireguard (interfaces: Logger)
|
||||
//
|
||||
// Generated by this command:
|
||||
//
|
||||
// mockgen -destination=log_mock_test.go -package wireguard . Logger
|
||||
//
|
||||
|
||||
// Package wireguard is a generated GoMock package.
|
||||
package wireguard
|
||||
@@ -7,13 +12,14 @@ package wireguard
|
||||
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)
|
||||
}
|
||||
|
||||
@@ -3,7 +3,7 @@ package wireguard
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/golang/mock/gomock"
|
||||
"go.uber.org/mock/gomock"
|
||||
)
|
||||
|
||||
func Test_makeDeviceLogger(t *testing.T) {
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user