mirror of
https://github.com/qdm12/gluetun.git
synced 2026-08-01 09:53:20 +02:00
Compare commits
54 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 3d1e20c555 | |||
| 44143aa3ba | |||
| 56e5cad66b | |||
| c0073c9567 | |||
| cccd7c4c1b | |||
| 4ddf7c9a57 | |||
| a4df514e4b | |||
| 35847bb3b2 | |||
| d36d0f84d7 | |||
| e68b70ff8b | |||
| 73c3ce23da | |||
| 6857210429 | |||
| 0f7f35d6dc | |||
| ec71f1881b | |||
| 44f2df6f87 | |||
| 650c61e93c | |||
| 2f98539848 | |||
| 5c02e2106c | |||
| e6815054d9 | |||
| 9fe34266f0 | |||
| cdfd644c04 | |||
| 1c722b0f80 | |||
| 2e204e11b6 | |||
| c745f61156 | |||
| 4301196c21 | |||
| ef48826e48 | |||
| 3fca8b3dfe | |||
| 8ab97bb4b4 | |||
| 8269fad3b2 | |||
| 16870d6385 | |||
| d41081f4b3 | |||
| 5fbe277f9c | |||
| c5b20a3b06 | |||
| 1b871496ea | |||
| 05795ad2d1 | |||
| f9a86f48aa | |||
| 3a39236fa8 | |||
| 998ae46e97 | |||
| c4e65f8cc7 | |||
| a20795df9e | |||
| 78929cd6e1 | |||
| 75c2404179 | |||
| bcbc8c41f2 | |||
| 3da0c21c82 | |||
| 009b8e45b7 | |||
| 05dc21ed61 | |||
| 270dce9acb | |||
| 23cf3231f0 | |||
| 7f22fb3276 | |||
| 6909a0c123 | |||
| 3e1f48932a | |||
| 50744852c5 | |||
| 09e52bc685 | |||
| 857fe425ec |
@@ -1,2 +1,2 @@
|
|||||||
FROM ghcr.io/qdm12/godevcontainer:v0.21-alpine
|
FROM ghcr.io/qdm12/godevcontainer:v0.21-alpine
|
||||||
RUN apk add wireguard-tools htop openssl tcpdump iptables nftables
|
RUN apk add wireguard-tools htop openssl
|
||||||
|
|||||||
@@ -45,7 +45,6 @@ jobs:
|
|||||||
level: error
|
level: error
|
||||||
exclude: |
|
exclude: |
|
||||||
./internal/storage/servers.json
|
./internal/storage/servers.json
|
||||||
./golangci.yml
|
|
||||||
*.md
|
*.md
|
||||||
|
|
||||||
- name: Linting
|
- name: Linting
|
||||||
@@ -60,19 +59,16 @@ jobs:
|
|||||||
- name: Run tests in test container
|
- name: Run tests in test container
|
||||||
run: |
|
run: |
|
||||||
touch coverage.txt
|
touch coverage.txt
|
||||||
docker run --rm --cap-add=NET_ADMIN --device /dev/net/tun \
|
docker run --rm --device /dev/net/tun \
|
||||||
-v "$(pwd)/coverage.txt:/tmp/gobuild/coverage.txt" \
|
-v "$(pwd)/coverage.txt:/tmp/gobuild/coverage.txt" \
|
||||||
test-container
|
test-container
|
||||||
|
|
||||||
- name: Verify dev cross platform compatibility
|
|
||||||
run: docker build --target xcompile .
|
|
||||||
|
|
||||||
- name: Build final image
|
- name: Build final image
|
||||||
run: docker build -t final-image .
|
run: docker build -t final-image .
|
||||||
|
|
||||||
verify-private:
|
verify-private:
|
||||||
if: |
|
if: |
|
||||||
github.repository == 'qdm12/gluetun' &&
|
github.repository == 'passteque/gluetun' &&
|
||||||
(
|
(
|
||||||
github.event_name == 'push' ||
|
github.event_name == 'push' ||
|
||||||
github.event_name == 'release' ||
|
github.event_name == 'release' ||
|
||||||
@@ -119,7 +115,7 @@ jobs:
|
|||||||
|
|
||||||
publish:
|
publish:
|
||||||
if: |
|
if: |
|
||||||
github.repository == 'qdm12/gluetun' &&
|
github.repository == 'passteque/gluetun' &&
|
||||||
(
|
(
|
||||||
github.event_name == 'push' ||
|
github.event_name == 'push' ||
|
||||||
github.event_name == 'release' ||
|
github.event_name == 'release' ||
|
||||||
@@ -131,6 +127,7 @@ jobs:
|
|||||||
contents: read
|
contents: read
|
||||||
packages: write
|
packages: write
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
|
environment: secrets
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v6
|
- uses: actions/checkout@v6
|
||||||
|
|
||||||
@@ -165,11 +162,15 @@ jobs:
|
|||||||
with:
|
with:
|
||||||
registry: ghcr.io
|
registry: ghcr.io
|
||||||
username: qdm12
|
username: qdm12
|
||||||
password: ${{ github.token }}
|
password: ${{ secrets.GHCR_PAT }}
|
||||||
|
|
||||||
- name: Short commit
|
- name: Short commit
|
||||||
id: shortcommit
|
id: shortcommit
|
||||||
run: echo "::set-output name=value::$(git rev-parse --short HEAD)"
|
run: |
|
||||||
|
# Use the PR head SHA if it exists, otherwise fallback to GITHUB_SHA
|
||||||
|
FULL_SHA="${{ github.event.pull_request.head.sha || github.sha }}"
|
||||||
|
SHORT_SHA=
|
||||||
|
echo "value=$(echo $FULL_SHA | cut -c1-7)" >> $GITHUB_OUTPUT
|
||||||
|
|
||||||
- name: Build and push final image
|
- name: Build and push final image
|
||||||
uses: docker/build-push-action@v6
|
uses: docker/build-push-action@v6
|
||||||
|
|||||||
@@ -38,7 +38,7 @@ jobs:
|
|||||||
config-file: .github/workflows/configs/mlc-config.json
|
config-file: .github/workflows/configs/mlc-config.json
|
||||||
|
|
||||||
- uses: peter-evans/dockerhub-description@v4
|
- uses: peter-evans/dockerhub-description@v4
|
||||||
if: github.repository == 'qdm12/gluetun' && github.event_name == 'push'
|
if: github.repository == 'passteque/gluetun' && github.event_name == 'push'
|
||||||
with:
|
with:
|
||||||
username: qmcgaw
|
username: qmcgaw
|
||||||
password: ${{ secrets.DOCKERHUB_PASSWORD }}
|
password: ${{ secrets.DOCKERHUB_PASSWORD }}
|
||||||
|
|||||||
+1
-2
@@ -22,7 +22,6 @@ linters:
|
|||||||
- "^disabled$"
|
- "^disabled$"
|
||||||
# Firewall and routing strings
|
# Firewall and routing strings
|
||||||
- "^(ACCEPT|DROP)$"
|
- "^(ACCEPT|DROP)$"
|
||||||
- "^--append$"
|
|
||||||
- "^--delete$"
|
- "^--delete$"
|
||||||
- "^all$"
|
- "^all$"
|
||||||
- "^(tcp|udp)$"
|
- "^(tcp|udp)$"
|
||||||
@@ -48,7 +47,7 @@ linters:
|
|||||||
path: internal\/server\/.+\.go
|
path: internal\/server\/.+\.go
|
||||||
- linters:
|
- linters:
|
||||||
- ireturn
|
- ireturn
|
||||||
text: returns interface \(golang\.org\/x\/sys\/unix\.Sockaddr\)
|
text: returns interface \(github\.com\/vishvananda\/netlink\.Link\)
|
||||||
- linters:
|
- linters:
|
||||||
- ireturn
|
- ireturn
|
||||||
path: internal\/openvpn\/pkcs8\/descbc\.go
|
path: internal\/openvpn\/pkcs8\/descbc\.go
|
||||||
|
|||||||
+2
-9
@@ -13,7 +13,7 @@ FROM --platform=${BUILDPLATFORM} ghcr.io/qdm12/binpot:mockgen-${MOCKGEN_VERSION}
|
|||||||
FROM --platform=${BUILDPLATFORM} golang:${GO_VERSION}-alpine${GO_ALPINE_VERSION} AS base
|
FROM --platform=${BUILDPLATFORM} golang:${GO_VERSION}-alpine${GO_ALPINE_VERSION} AS base
|
||||||
COPY --from=xcputranslate /xcputranslate /usr/local/bin/xcputranslate
|
COPY --from=xcputranslate /xcputranslate /usr/local/bin/xcputranslate
|
||||||
# Note: findutils needed to have xargs support `-d` flag for mocks stage.
|
# Note: findutils needed to have xargs support `-d` flag for mocks stage.
|
||||||
RUN apk --update add git g++ findutils iptables
|
RUN apk --update add git g++ findutils
|
||||||
ENV CGO_ENABLED=0
|
ENV CGO_ENABLED=0
|
||||||
COPY --from=golangci-lint /bin /go/bin/golangci-lint
|
COPY --from=golangci-lint /bin /go/bin/golangci-lint
|
||||||
COPY --from=mockgen /bin /go/bin/mockgen
|
COPY --from=mockgen /bin /go/bin/mockgen
|
||||||
@@ -46,10 +46,6 @@ RUN git init && \
|
|||||||
git diff --exit-code && \
|
git diff --exit-code && \
|
||||||
rm -rf .git/
|
rm -rf .git/
|
||||||
|
|
||||||
FROM --platform=${BUILDPLATFORM} base AS xcompile
|
|
||||||
RUN GOOS=darwin go build -o /dev/null ./...
|
|
||||||
RUN GOOS=windows go build -o /dev/null ./...
|
|
||||||
|
|
||||||
FROM --platform=${BUILDPLATFORM} base AS build
|
FROM --platform=${BUILDPLATFORM} base AS build
|
||||||
ARG TARGETPLATFORM
|
ARG TARGETPLATFORM
|
||||||
ARG VERSION=unknown
|
ARG VERSION=unknown
|
||||||
@@ -110,11 +106,8 @@ ENV VPN_SERVICE_PROVIDER=pia \
|
|||||||
WIREGUARD_PERSISTENT_KEEPALIVE_INTERVAL=0 \
|
WIREGUARD_PERSISTENT_KEEPALIVE_INTERVAL=0 \
|
||||||
WIREGUARD_ADDRESSES= \
|
WIREGUARD_ADDRESSES= \
|
||||||
WIREGUARD_ADDRESSES_SECRETFILE=/run/secrets/wireguard_addresses \
|
WIREGUARD_ADDRESSES_SECRETFILE=/run/secrets/wireguard_addresses \
|
||||||
WIREGUARD_MTU= \
|
WIREGUARD_MTU=1320 \
|
||||||
WIREGUARD_IMPLEMENTATION=auto \
|
WIREGUARD_IMPLEMENTATION=auto \
|
||||||
# PMTUD
|
|
||||||
PMTUD_ICMP_ADDRESSES=1.1.1.1,8.8.8.8 \
|
|
||||||
PMTUD_TCP_ADDRESSES=1.1.1.1:443,8.8.8.8:443,1.1.1.1:53,8.8.8.8:53,[2606:4700:4700::1111]:53,[2001:4860:4860::8888]:53,[2606:4700:4700::1111]:443,[2001:4860:4860::8888]:443 \
|
|
||||||
# VPN server filtering
|
# VPN server filtering
|
||||||
SERVER_REGIONS= \
|
SERVER_REGIONS= \
|
||||||
SERVER_COUNTRIES= \
|
SERVER_COUNTRIES= \
|
||||||
|
|||||||
@@ -58,7 +58,7 @@ Lightweight swiss-army-knife-like VPN client to multiple VPN service providers
|
|||||||
## Features
|
## Features
|
||||||
|
|
||||||
- Based on Alpine 3.22 for a small Docker image of 41.1MB
|
- Based on Alpine 3.22 for a small Docker image of 41.1MB
|
||||||
- Supports: **AirVPN**, **Cyberghost**, **ExpressVPN**, **FastestVPN**, **Giganews**, **HideMyAss**, **IPVanish**, **IVPN**, **Mullvad** (Wireguard only), **NordVPN**, **Perfect Privacy**, **Privado**, **Private Internet Access**, **PrivateVPN**, **ProtonVPN**, **PureVPN**, **SlickVPN**, **Surfshark**, **TorGuard**, **VPNSecure.me**, **VPNUnlimited**, **Vyprvpn**, **Windscribe** servers
|
- Supports: **AirVPN**, **Cyberghost**, **ExpressVPN**, **FastestVPN**, **Giganews**, **HideMyAss**, **IPVanish**, **IVPN**, **Mullvad**, **NordVPN**, **Perfect Privacy**, **Privado**, **Private Internet Access**, **PrivateVPN**, **ProtonVPN**, **PureVPN**, **SlickVPN**, **Surfshark**, **TorGuard**, **VPNSecure.me**, **VPNUnlimited**, **Vyprvpn**, **Windscribe** servers
|
||||||
- Supports OpenVPN for all providers listed
|
- Supports OpenVPN for all providers listed
|
||||||
- Supports Wireguard both kernelspace and userspace
|
- Supports Wireguard both kernelspace and userspace
|
||||||
- For **AirVPN**, **FastestVPN**, **Ivpn**, **Mullvad**, **NordVPN**, **Perfect privacy**, **ProtonVPN**, **Surfshark** and **Windscribe**
|
- For **AirVPN**, **FastestVPN**, **Ivpn**, **Mullvad**, **NordVPN**, **Perfect privacy**, **ProtonVPN**, **Surfshark** and **Windscribe**
|
||||||
|
|||||||
+27
-44
@@ -6,7 +6,6 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"io/fs"
|
"io/fs"
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/netip"
|
|
||||||
"os"
|
"os"
|
||||||
"os/exec"
|
"os/exec"
|
||||||
"os/signal"
|
"os/signal"
|
||||||
@@ -16,6 +15,8 @@ import (
|
|||||||
_ "time/tzdata"
|
_ "time/tzdata"
|
||||||
|
|
||||||
_ "github.com/breml/rootcerts"
|
_ "github.com/breml/rootcerts"
|
||||||
|
"github.com/qdm12/dns/v2/pkg/doh"
|
||||||
|
dnsprovider "github.com/qdm12/dns/v2/pkg/provider"
|
||||||
"github.com/qdm12/gluetun/internal/alpine"
|
"github.com/qdm12/gluetun/internal/alpine"
|
||||||
"github.com/qdm12/gluetun/internal/cli"
|
"github.com/qdm12/gluetun/internal/cli"
|
||||||
"github.com/qdm12/gluetun/internal/command"
|
"github.com/qdm12/gluetun/internal/command"
|
||||||
@@ -40,7 +41,6 @@ import (
|
|||||||
"github.com/qdm12/gluetun/internal/server"
|
"github.com/qdm12/gluetun/internal/server"
|
||||||
"github.com/qdm12/gluetun/internal/shadowsocks"
|
"github.com/qdm12/gluetun/internal/shadowsocks"
|
||||||
"github.com/qdm12/gluetun/internal/storage"
|
"github.com/qdm12/gluetun/internal/storage"
|
||||||
"github.com/qdm12/gluetun/internal/tun"
|
|
||||||
updater "github.com/qdm12/gluetun/internal/updater/loop"
|
updater "github.com/qdm12/gluetun/internal/updater/loop"
|
||||||
"github.com/qdm12/gluetun/internal/updater/resolver"
|
"github.com/qdm12/gluetun/internal/updater/resolver"
|
||||||
"github.com/qdm12/gluetun/internal/updater/unzip"
|
"github.com/qdm12/gluetun/internal/updater/unzip"
|
||||||
@@ -77,7 +77,6 @@ func main() {
|
|||||||
logger := log.New(log.SetLevel(log.LevelInfo))
|
logger := log.New(log.SetLevel(log.LevelInfo))
|
||||||
|
|
||||||
args := os.Args
|
args := os.Args
|
||||||
tun := tun.New()
|
|
||||||
netLinkDebugLogger := logger.New(log.SetComponent("netlink"))
|
netLinkDebugLogger := logger.New(log.SetComponent("netlink"))
|
||||||
netLinker := netlink.New(netLinkDebugLogger)
|
netLinker := netlink.New(netLinkDebugLogger)
|
||||||
cli := cli.New()
|
cli := cli.New()
|
||||||
@@ -97,7 +96,7 @@ func main() {
|
|||||||
|
|
||||||
errorCh := make(chan error)
|
errorCh := make(chan error)
|
||||||
go func() {
|
go func() {
|
||||||
errorCh <- _main(ctx, buildInfo, args, logger, reader, tun, netLinker, cmder, cli)
|
errorCh <- _main(ctx, buildInfo, args, logger, reader, netLinker, cmder, cli)
|
||||||
}()
|
}()
|
||||||
|
|
||||||
// Wait for OS signal or run error
|
// Wait for OS signal or run error
|
||||||
@@ -144,7 +143,7 @@ var errCommandUnknown = errors.New("command is unknown")
|
|||||||
//nolint:gocognit,gocyclo,maintidx
|
//nolint:gocognit,gocyclo,maintidx
|
||||||
func _main(ctx context.Context, buildInfo models.BuildInformation,
|
func _main(ctx context.Context, buildInfo models.BuildInformation,
|
||||||
args []string, logger log.LoggerInterface, reader *reader.Reader,
|
args []string, logger log.LoggerInterface, reader *reader.Reader,
|
||||||
tun Tun, netLinker netLinker, cmder RunStarter,
|
netLinker netLinker, cmder RunStarter,
|
||||||
cli clier,
|
cli clier,
|
||||||
) error {
|
) error {
|
||||||
if len(args) > 1 { // cli operation
|
if len(args) > 1 { // cli operation
|
||||||
@@ -168,7 +167,7 @@ func _main(ctx context.Context, buildInfo models.BuildInformation,
|
|||||||
|
|
||||||
defer fmt.Println(gluetunLogo)
|
defer fmt.Println(gluetunLogo)
|
||||||
|
|
||||||
announcementExp, err := time.Parse(time.RFC3339, "2026-04-01T00:00:00Z")
|
announcementExp, err := time.Parse(time.RFC3339, "2024-12-01T00:00:00Z")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -179,7 +178,7 @@ func _main(ctx context.Context, buildInfo models.BuildInformation,
|
|||||||
Version: buildInfo.Version,
|
Version: buildInfo.Version,
|
||||||
Commit: buildInfo.Commit,
|
Commit: buildInfo.Commit,
|
||||||
Created: buildInfo.Created,
|
Created: buildInfo.Created,
|
||||||
Announcement: "All control server routes are now private by default",
|
Announcement: "All control server routes will become private by default after the v3.41.0 release",
|
||||||
AnnounceExp: announcementExp,
|
AnnounceExp: announcementExp,
|
||||||
// Sponsor information
|
// Sponsor information
|
||||||
PaypalUser: "qmcgaw",
|
PaypalUser: "qmcgaw",
|
||||||
@@ -237,10 +236,6 @@ func _main(ctx context.Context, buildInfo models.BuildInformation,
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
err = netLinker.FlushConntrack()
|
|
||||||
if err != nil {
|
|
||||||
logger.Warnf("flushing conntrack failed: %s", err)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO run this in a loop or in openvpn to reload from file without restarting
|
// TODO run this in a loop or in openvpn to reload from file without restarting
|
||||||
@@ -283,7 +278,7 @@ func _main(ctx context.Context, buildInfo models.BuildInformation,
|
|||||||
err = printVersions(ctx, logger, []printVersionElement{
|
err = printVersions(ctx, logger, []printVersionElement{
|
||||||
{name: "Alpine", getVersion: alpineConf.Version},
|
{name: "Alpine", getVersion: alpineConf.Version},
|
||||||
{name: "OpenVPN", getVersion: ovpnVersion},
|
{name: "OpenVPN", getVersion: ovpnVersion},
|
||||||
{name: "Firewall", getVersion: firewallConf.Version},
|
{name: "IPtables", getVersion: firewallConf.Version},
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
@@ -340,19 +335,6 @@ func _main(ctx context.Context, buildInfo models.BuildInformation,
|
|||||||
return fmt.Errorf("adding local rules: %w", err)
|
return fmt.Errorf("adding local rules: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
const tunDevice = "/dev/net/tun"
|
|
||||||
err = tun.Check(tunDevice)
|
|
||||||
if err != nil {
|
|
||||||
if !errors.Is(err, os.ErrNotExist) {
|
|
||||||
return fmt.Errorf("checking TUN device: %w (see the Wiki errors/tun page)", err)
|
|
||||||
}
|
|
||||||
logger.Info(err.Error() + "; creating it...")
|
|
||||||
err = tun.Create(tunDevice)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("creating tun device: %w", err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, port := range allSettings.Firewall.InputPorts {
|
for _, port := range allSettings.Firewall.InputPorts {
|
||||||
for _, defaultRoute := range defaultRoutes {
|
for _, defaultRoute := range defaultRoutes {
|
||||||
err = firewallConf.SetAllowedPort(ctx, port, defaultRoute.NetInterface)
|
err = firewallConf.SetAllowedPort(ctx, port, defaultRoute.NetInterface)
|
||||||
@@ -432,10 +414,18 @@ func _main(ctx context.Context, buildInfo models.BuildInformation,
|
|||||||
go healthcheckServer.Run(healthServerCtx, healthServerDone)
|
go healthcheckServer.Run(healthServerCtx, healthServerDone)
|
||||||
healthChecker := healthcheck.NewChecker(healthLogger)
|
healthChecker := healthcheck.NewChecker(healthLogger)
|
||||||
|
|
||||||
|
// Note: we use a separate DoH dialer for the VPN servers data updater, separate from the
|
||||||
|
// main DNS local server to make sure no request is blocked by filters.
|
||||||
|
dohDialer, err := doh.New(doh.Settings{
|
||||||
|
UpstreamResolvers: []dnsprovider.Provider{dnsprovider.Cloudflare(), dnsprovider.Google()},
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("creating updater DoH dialer: %w", err)
|
||||||
|
}
|
||||||
updaterLogger := logger.New(log.SetComponent("updater"))
|
updaterLogger := logger.New(log.SetComponent("updater"))
|
||||||
|
|
||||||
unzipper := unzip.New(httpClient)
|
unzipper := unzip.New(httpClient)
|
||||||
parallelResolver := resolver.NewParallelResolver(allSettings.Updater.DNSAddress)
|
parallelResolver := resolver.NewParallelResolver(dohDialer)
|
||||||
openvpnFileExtractor := extract.New()
|
openvpnFileExtractor := extract.New()
|
||||||
providers := provider.NewProviders(storage, time.Now, updaterLogger,
|
providers := provider.NewProviders(storage, time.Now, updaterLogger,
|
||||||
httpClient, unzipper, parallelResolver, publicIPLooper.Fetcher(),
|
httpClient, unzipper, parallelResolver, publicIPLooper.Fetcher(),
|
||||||
@@ -558,27 +548,26 @@ type netLinker interface {
|
|||||||
Router
|
Router
|
||||||
Ruler
|
Ruler
|
||||||
Linker
|
Linker
|
||||||
IsWireguardSupported() (ok bool, err error)
|
IsWireguardSupported() bool
|
||||||
IsIPv6Supported() (ok bool, err error)
|
IsIPv6Supported() (ok bool, err error)
|
||||||
FlushConntrack() error
|
|
||||||
PatchLoggerLevel(level log.Level)
|
PatchLoggerLevel(level log.Level)
|
||||||
}
|
}
|
||||||
|
|
||||||
type Addresser interface {
|
type Addresser interface {
|
||||||
AddrList(linkIndex uint32, family uint8) (
|
AddrList(link netlink.Link, family int) (
|
||||||
addresses []netip.Prefix, err error)
|
addresses []netlink.Addr, err error)
|
||||||
AddrReplace(linkIndex uint32, addr netip.Prefix) error
|
AddrReplace(link netlink.Link, addr netlink.Addr) error
|
||||||
}
|
}
|
||||||
|
|
||||||
type Router interface {
|
type Router interface {
|
||||||
RouteList(family uint8) (routes []netlink.Route, err error)
|
RouteList(family int) (routes []netlink.Route, err error)
|
||||||
RouteAdd(route netlink.Route) error
|
RouteAdd(route netlink.Route) error
|
||||||
RouteDel(route netlink.Route) error
|
RouteDel(route netlink.Route) error
|
||||||
RouteReplace(route netlink.Route) error
|
RouteReplace(route netlink.Route) error
|
||||||
}
|
}
|
||||||
|
|
||||||
type Ruler interface {
|
type Ruler interface {
|
||||||
RuleList(family uint8) (rules []netlink.Rule, err error)
|
RuleList(family int) (rules []netlink.Rule, err error)
|
||||||
RuleAdd(rule netlink.Rule) error
|
RuleAdd(rule netlink.Rule) error
|
||||||
RuleDel(rule netlink.Rule) error
|
RuleDel(rule netlink.Rule) error
|
||||||
}
|
}
|
||||||
@@ -586,12 +575,11 @@ type Ruler interface {
|
|||||||
type Linker interface {
|
type Linker interface {
|
||||||
LinkList() (links []netlink.Link, err error)
|
LinkList() (links []netlink.Link, err error)
|
||||||
LinkByName(name string) (link netlink.Link, err error)
|
LinkByName(name string) (link netlink.Link, err error)
|
||||||
LinkByIndex(index uint32) (link netlink.Link, err error)
|
LinkByIndex(index int) (link netlink.Link, err error)
|
||||||
LinkAdd(link netlink.Link) (linkIndex uint32, err error)
|
LinkAdd(link netlink.Link) (linkIndex int, err error)
|
||||||
LinkDel(linkIndex uint32) (err error)
|
LinkDel(link netlink.Link) (err error)
|
||||||
LinkSetUp(linkIndex uint32) (err error)
|
LinkSetUp(link netlink.Link) (linkIndex int, err error)
|
||||||
LinkSetDown(linkIndex uint32) (err error)
|
LinkSetDown(link netlink.Link) (err error)
|
||||||
LinkSetMTU(linkIndex, mtu uint32) error
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type clier interface {
|
type clier interface {
|
||||||
@@ -603,11 +591,6 @@ type clier interface {
|
|||||||
GenKey(args []string) error
|
GenKey(args []string) error
|
||||||
}
|
}
|
||||||
|
|
||||||
type Tun interface {
|
|
||||||
Check(tunDevice string) error
|
|
||||||
Create(tunDevice string) error
|
|
||||||
}
|
|
||||||
|
|
||||||
type RunStarter interface {
|
type RunStarter interface {
|
||||||
Run(cmd *exec.Cmd) (output string, err error)
|
Run(cmd *exec.Cmd) (output string, err error)
|
||||||
Start(cmd *exec.Cmd) (stdoutLines, stderrLines <-chan string,
|
Start(cmd *exec.Cmd) (stdoutLines, stderrLines <-chan string,
|
||||||
|
|||||||
@@ -4,17 +4,13 @@ go 1.25.0
|
|||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/ProtonMail/go-srp v0.0.7
|
github.com/ProtonMail/go-srp v0.0.7
|
||||||
github.com/breml/rootcerts v0.3.4
|
github.com/breml/rootcerts v0.3.3
|
||||||
github.com/fatih/color v1.18.0
|
github.com/fatih/color v1.18.0
|
||||||
github.com/golang/mock v1.6.0
|
github.com/golang/mock v1.6.0
|
||||||
github.com/google/nftables v0.3.0
|
|
||||||
github.com/jsimonetti/rtnetlink v1.4.2
|
|
||||||
github.com/klauspost/compress v1.18.1
|
github.com/klauspost/compress v1.18.1
|
||||||
github.com/klauspost/pgzip v1.2.6
|
github.com/klauspost/pgzip v1.2.6
|
||||||
github.com/mdlayher/genetlink v1.3.2
|
|
||||||
github.com/mdlayher/netlink v1.7.3-0.20250113171957-fbb4dce95f42
|
|
||||||
github.com/pelletier/go-toml/v2 v2.2.4
|
github.com/pelletier/go-toml/v2 v2.2.4
|
||||||
github.com/qdm12/dns/v2 v2.0.0-rc9.0.20260216151239-36b3306f2205
|
github.com/qdm12/dns/v2 v2.0.0-rc9.0.20260421173011-9de8e7fdbe3a
|
||||||
github.com/qdm12/gosettings v0.4.4
|
github.com/qdm12/gosettings v0.4.4
|
||||||
github.com/qdm12/goshutdown v0.3.0
|
github.com/qdm12/goshutdown v0.3.0
|
||||||
github.com/qdm12/gosplash v0.2.0
|
github.com/qdm12/gosplash v0.2.0
|
||||||
@@ -22,13 +18,13 @@ require (
|
|||||||
github.com/qdm12/log v0.1.0
|
github.com/qdm12/log v0.1.0
|
||||||
github.com/qdm12/ss-server v0.6.0
|
github.com/qdm12/ss-server v0.6.0
|
||||||
github.com/stretchr/testify v1.11.1
|
github.com/stretchr/testify v1.11.1
|
||||||
github.com/ti-mo/netfilter v0.5.3
|
|
||||||
github.com/ulikunitz/xz v0.5.15
|
github.com/ulikunitz/xz v0.5.15
|
||||||
|
github.com/vishvananda/netlink v1.3.1
|
||||||
github.com/youmark/pkcs8 v0.0.0-20201027041543-1326539a0a0a
|
github.com/youmark/pkcs8 v0.0.0-20201027041543-1326539a0a0a
|
||||||
golang.org/x/exp v0.0.0-20241009180824-f66d83c29e7c
|
golang.org/x/exp v0.0.0-20241009180824-f66d83c29e7c
|
||||||
golang.org/x/net v0.49.0
|
golang.org/x/net v0.47.0
|
||||||
golang.org/x/sys v0.40.0
|
golang.org/x/sys v0.38.0
|
||||||
golang.org/x/text v0.33.0
|
golang.org/x/text v0.31.0
|
||||||
golang.zx2c4.com/wireguard v0.0.0-20231211153847-12269c276173
|
golang.zx2c4.com/wireguard v0.0.0-20231211153847-12269c276173
|
||||||
golang.zx2c4.com/wireguard/wgctrl v0.0.0-20230429144221-925a1e7659e6
|
golang.zx2c4.com/wireguard/wgctrl v0.0.0-20230429144221-925a1e7659e6
|
||||||
gopkg.in/ini.v1 v1.67.0
|
gopkg.in/ini.v1 v1.67.0
|
||||||
@@ -42,10 +38,13 @@ require (
|
|||||||
github.com/cloudflare/circl v1.6.1 // indirect
|
github.com/cloudflare/circl v1.6.1 // indirect
|
||||||
github.com/cronokirby/saferith v0.33.0 // indirect
|
github.com/cronokirby/saferith v0.33.0 // indirect
|
||||||
github.com/davecgh/go-spew v1.1.1 // indirect
|
github.com/davecgh/go-spew v1.1.1 // indirect
|
||||||
github.com/google/go-cmp v0.7.0 // indirect
|
github.com/google/go-cmp v0.6.0 // indirect
|
||||||
|
github.com/josharian/native v1.1.0 // indirect
|
||||||
github.com/mattn/go-colorable v0.1.13 // indirect
|
github.com/mattn/go-colorable v0.1.13 // indirect
|
||||||
github.com/mattn/go-isatty v0.0.20 // indirect
|
github.com/mattn/go-isatty v0.0.20 // indirect
|
||||||
github.com/mdlayher/socket v0.5.1 // indirect
|
github.com/mdlayher/genetlink v1.3.2 // indirect
|
||||||
|
github.com/mdlayher/netlink v1.7.2 // indirect
|
||||||
|
github.com/mdlayher/socket v0.4.1 // indirect
|
||||||
github.com/miekg/dns v1.1.62 // indirect
|
github.com/miekg/dns v1.1.62 // indirect
|
||||||
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect
|
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect
|
||||||
github.com/pkg/errors v0.9.1 // indirect
|
github.com/pkg/errors v0.9.1 // indirect
|
||||||
@@ -56,11 +55,12 @@ require (
|
|||||||
github.com/prometheus/procfs v0.15.1 // indirect
|
github.com/prometheus/procfs v0.15.1 // indirect
|
||||||
github.com/qdm12/goservices v0.1.1-0.20251104135713-6bee97bd4978 // indirect
|
github.com/qdm12/goservices v0.1.1-0.20251104135713-6bee97bd4978 // indirect
|
||||||
github.com/riobard/go-bloom v0.0.0-20200614022211-cdc8013cb5b3 // indirect
|
github.com/riobard/go-bloom v0.0.0-20200614022211-cdc8013cb5b3 // indirect
|
||||||
golang.org/x/crypto v0.47.0 // indirect
|
github.com/vishvananda/netns v0.0.5 // indirect
|
||||||
golang.org/x/mod v0.31.0 // indirect
|
golang.org/x/crypto v0.45.0 // indirect
|
||||||
golang.org/x/sync v0.19.0 // indirect
|
golang.org/x/mod v0.29.0 // indirect
|
||||||
|
golang.org/x/sync v0.18.0 // indirect
|
||||||
golang.org/x/time v0.3.0 // indirect
|
golang.org/x/time v0.3.0 // indirect
|
||||||
golang.org/x/tools v0.40.0 // indirect
|
golang.org/x/tools v0.38.0 // indirect
|
||||||
golang.zx2c4.com/wintun v0.0.0-20230126152724-0fa3db229ce2 // indirect
|
golang.zx2c4.com/wintun v0.0.0-20230126152724-0fa3db229ce2 // indirect
|
||||||
google.golang.org/protobuf v1.35.1 // indirect
|
google.golang.org/protobuf v1.35.1 // indirect
|
||||||
gopkg.in/yaml.v3 v3.0.1 // indirect
|
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||||
|
|||||||
@@ -8,13 +8,11 @@ github.com/ProtonMail/go-srp v0.0.7 h1:Sos3Qk+th4tQR64vsxGIxYpN3rdnG9Wf9K4ZloC1J
|
|||||||
github.com/ProtonMail/go-srp v0.0.7/go.mod h1:giCp+7qRnMIcCvI6V6U3S1lDDXDQYx2ewJ6F/9wdlJk=
|
github.com/ProtonMail/go-srp v0.0.7/go.mod h1:giCp+7qRnMIcCvI6V6U3S1lDDXDQYx2ewJ6F/9wdlJk=
|
||||||
github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=
|
github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=
|
||||||
github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=
|
github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=
|
||||||
github.com/breml/rootcerts v0.3.4 h1:9i7WNl/ctd9OEAOaTfLy//Wrlfxq/tRQ7v4okYFN9Ys=
|
github.com/breml/rootcerts v0.3.3 h1://GnaRtQ/9BY2+GtMk2wtWxVdCRysiaPr5/xBwl7NKw=
|
||||||
github.com/breml/rootcerts v0.3.4/go.mod h1:S/PKh+4d1HUn4HQovEB8hPJZO6pUZYrIhmXBhsegfXw=
|
github.com/breml/rootcerts v0.3.3/go.mod h1:S/PKh+4d1HUn4HQovEB8hPJZO6pUZYrIhmXBhsegfXw=
|
||||||
github.com/bwesterb/go-ristretto v1.2.0/go.mod h1:fUIoIZaG73pV5biE2Blr2xEzDoMj7NFEuV9ekS419A0=
|
github.com/bwesterb/go-ristretto v1.2.0/go.mod h1:fUIoIZaG73pV5biE2Blr2xEzDoMj7NFEuV9ekS419A0=
|
||||||
github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs=
|
github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs=
|
||||||
github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
|
github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
|
||||||
github.com/cilium/ebpf v0.12.3 h1:8ht6F9MquybnY97at+VDZb3eQQr8ev79RueWeVaEcG4=
|
|
||||||
github.com/cilium/ebpf v0.12.3/go.mod h1:TctK1ivibvI3znr66ljgi4hqOT8EYQjz1KWBfb1UVgM=
|
|
||||||
github.com/cloudflare/circl v1.1.0/go.mod h1:prBCrKB9DV4poKZY1l9zBXg2QJY7mvgRvtMxxK7fi4I=
|
github.com/cloudflare/circl v1.1.0/go.mod h1:prBCrKB9DV4poKZY1l9zBXg2QJY7mvgRvtMxxK7fi4I=
|
||||||
github.com/cloudflare/circl v1.6.1 h1:zqIqSPIndyBh1bjLVVDHMPpVKqp8Su/V+6MeDzzQBQ0=
|
github.com/cloudflare/circl v1.6.1 h1:zqIqSPIndyBh1bjLVVDHMPpVKqp8Su/V+6MeDzzQBQ0=
|
||||||
github.com/cloudflare/circl v1.6.1/go.mod h1:uddAzsPgqdMAYatqJ0lsjX1oECcQLIlRpzZh3pJrofs=
|
github.com/cloudflare/circl v1.6.1/go.mod h1:uddAzsPgqdMAYatqJ0lsjX1oECcQLIlRpzZh3pJrofs=
|
||||||
@@ -28,12 +26,10 @@ github.com/golang/mock v1.6.0 h1:ErTB+efbowRARo13NNdxyJji2egdxLGQhRaY+DUumQc=
|
|||||||
github.com/golang/mock v1.6.0/go.mod h1:p6yTPP+5HYm5mzsMV8JkE6ZKdX+/wYM6Hr+LicevLPs=
|
github.com/golang/mock v1.6.0/go.mod h1:p6yTPP+5HYm5mzsMV8JkE6ZKdX+/wYM6Hr+LicevLPs=
|
||||||
github.com/google/btree v1.0.1 h1:gK4Kx5IaGY9CD5sPJ36FHiBJ6ZXl0kilRiiCj+jdYp4=
|
github.com/google/btree v1.0.1 h1:gK4Kx5IaGY9CD5sPJ36FHiBJ6ZXl0kilRiiCj+jdYp4=
|
||||||
github.com/google/btree v1.0.1/go.mod h1:xXMiIv4Fb/0kKde4SpL7qlzvu5cMJDRkFDxJfI9uaxA=
|
github.com/google/btree v1.0.1/go.mod h1:xXMiIv4Fb/0kKde4SpL7qlzvu5cMJDRkFDxJfI9uaxA=
|
||||||
github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
|
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
|
||||||
github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=
|
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
||||||
github.com/google/nftables v0.3.0 h1:bkyZ0cbpVeMHXOrtlFc8ISmfVqq5gPJukoYieyVmITg=
|
github.com/josharian/native v1.1.0 h1:uuaP0hAbW7Y4l0ZRQ6C9zfb7Mg1mbFKry/xzDAfmtLA=
|
||||||
github.com/google/nftables v0.3.0/go.mod h1:BCp9FsrbF1Fn/Yu6CLUc9GGZFw/+hsxfluNXXmxBfRM=
|
github.com/josharian/native v1.1.0/go.mod h1:7X/raswPFr05uY3HiLlYeyQntB6OO7E/d2Cu7qoaN2w=
|
||||||
github.com/jsimonetti/rtnetlink v1.4.2 h1:Df9w9TZ3npHTyDn0Ev9e1uzmN2odmXd0QX+J5GTEn90=
|
|
||||||
github.com/jsimonetti/rtnetlink v1.4.2/go.mod h1:92s6LJdE+1iOrw+F2/RO7LYI2Qd8pPpFNNUYW06gcoM=
|
|
||||||
github.com/klauspost/compress v1.18.1 h1:bcSGx7UbpBqMChDtsF28Lw6v/G94LPrrbMbdC3JH2co=
|
github.com/klauspost/compress v1.18.1 h1:bcSGx7UbpBqMChDtsF28Lw6v/G94LPrrbMbdC3JH2co=
|
||||||
github.com/klauspost/compress v1.18.1/go.mod h1:ZQFFVG+MdnR0P+l6wpXgIL4NTtwiKIdBnrBd8Nrxr+0=
|
github.com/klauspost/compress v1.18.1/go.mod h1:ZQFFVG+MdnR0P+l6wpXgIL4NTtwiKIdBnrBd8Nrxr+0=
|
||||||
github.com/klauspost/pgzip v1.2.6 h1:8RXeL5crjEUFnR2/Sn6GJNWtSQ3Dk8pq4CL3jvdDyjU=
|
github.com/klauspost/pgzip v1.2.6 h1:8RXeL5crjEUFnR2/Sn6GJNWtSQ3Dk8pq4CL3jvdDyjU=
|
||||||
@@ -49,10 +45,10 @@ github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWE
|
|||||||
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
|
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
|
||||||
github.com/mdlayher/genetlink v1.3.2 h1:KdrNKe+CTu+IbZnm/GVUMXSqBBLqcGpRDa0xkQy56gw=
|
github.com/mdlayher/genetlink v1.3.2 h1:KdrNKe+CTu+IbZnm/GVUMXSqBBLqcGpRDa0xkQy56gw=
|
||||||
github.com/mdlayher/genetlink v1.3.2/go.mod h1:tcC3pkCrPUGIKKsCsp0B3AdaaKuHtaxoJRz3cc+528o=
|
github.com/mdlayher/genetlink v1.3.2/go.mod h1:tcC3pkCrPUGIKKsCsp0B3AdaaKuHtaxoJRz3cc+528o=
|
||||||
github.com/mdlayher/netlink v1.7.3-0.20250113171957-fbb4dce95f42 h1:A1Cq6Ysb0GM0tpKMbdCXCIfBclan4oHk1Jb+Hrejirg=
|
github.com/mdlayher/netlink v1.7.2 h1:/UtM3ofJap7Vl4QWCPDGXY8d3GIY2UGSDbK+QWmY8/g=
|
||||||
github.com/mdlayher/netlink v1.7.3-0.20250113171957-fbb4dce95f42/go.mod h1:BB4YCPDOzfy7FniQ/lxuYQ3dgmM2cZumHbK8RpTjN2o=
|
github.com/mdlayher/netlink v1.7.2/go.mod h1:xraEF7uJbxLhc5fpHL4cPe221LI2bdttWlU+ZGLfQSw=
|
||||||
github.com/mdlayher/socket v0.5.1 h1:VZaqt6RkGkt2OE9l3GcC6nZkqD3xKeQLyfleW/uBcos=
|
github.com/mdlayher/socket v0.4.1 h1:eM9y2/jlbs1M615oshPQOHZzj6R6wMT7bX5NPiQvn2U=
|
||||||
github.com/mdlayher/socket v0.5.1/go.mod h1:TjPLHI1UgwEv5J1B5q0zTZq12A/6H7nKmtTanQE37IQ=
|
github.com/mdlayher/socket v0.4.1/go.mod h1:cAqeGjoufqdxWkD7DkpyS+wcefOtmu5OQ8KuoJGIReA=
|
||||||
github.com/miekg/dns v1.1.62 h1:cN8OuEF1/x5Rq6Np+h1epln8OiyPWV+lROx9LxcGgIQ=
|
github.com/miekg/dns v1.1.62 h1:cN8OuEF1/x5Rq6Np+h1epln8OiyPWV+lROx9LxcGgIQ=
|
||||||
github.com/miekg/dns v1.1.62/go.mod h1:mvDlcItzm+br7MToIKqkglaGhlFMHJ9DTNNWONWXbNQ=
|
github.com/miekg/dns v1.1.62/go.mod h1:mvDlcItzm+br7MToIKqkglaGhlFMHJ9DTNNWONWXbNQ=
|
||||||
github.com/mikioh/ipaddr v0.0.0-20190404000644-d465c8ab6721 h1:RlZweED6sbSArvlE924+mUcZuXKLBHA35U7LN621Bws=
|
github.com/mikioh/ipaddr v0.0.0-20190404000644-d465c8ab6721 h1:RlZweED6sbSArvlE924+mUcZuXKLBHA35U7LN621Bws=
|
||||||
@@ -73,8 +69,8 @@ github.com/prometheus/common v0.60.1 h1:FUas6GcOw66yB/73KC+BOZoFJmbo/1pojoILArPA
|
|||||||
github.com/prometheus/common v0.60.1/go.mod h1:h0LYf1R1deLSKtD4Vdg8gy4RuOvENW2J/h19V5NADQw=
|
github.com/prometheus/common v0.60.1/go.mod h1:h0LYf1R1deLSKtD4Vdg8gy4RuOvENW2J/h19V5NADQw=
|
||||||
github.com/prometheus/procfs v0.15.1 h1:YagwOFzUgYfKKHX6Dr+sHT7km/hxC76UB0learggepc=
|
github.com/prometheus/procfs v0.15.1 h1:YagwOFzUgYfKKHX6Dr+sHT7km/hxC76UB0learggepc=
|
||||||
github.com/prometheus/procfs v0.15.1/go.mod h1:fB45yRUv8NstnjriLhBQLuOUt+WW4BsoGhij/e3PBqk=
|
github.com/prometheus/procfs v0.15.1/go.mod h1:fB45yRUv8NstnjriLhBQLuOUt+WW4BsoGhij/e3PBqk=
|
||||||
github.com/qdm12/dns/v2 v2.0.0-rc9.0.20260216151239-36b3306f2205 h1:0ycKUDQ50cYb2QpeyGcEnvVs9HJmC9jsb/XZNC1z28c=
|
github.com/qdm12/dns/v2 v2.0.0-rc9.0.20260421173011-9de8e7fdbe3a h1:TE157yPQmAbVruH0MWCQzs0vTT/6t96DkoWUXd6PVuc=
|
||||||
github.com/qdm12/dns/v2 v2.0.0-rc9.0.20260216151239-36b3306f2205/go.mod h1:98foWgXJZ+g8gJIuO+fdO+oWpFei5WShMFTeN4Im2lE=
|
github.com/qdm12/dns/v2 v2.0.0-rc9.0.20260421173011-9de8e7fdbe3a/go.mod h1:98foWgXJZ+g8gJIuO+fdO+oWpFei5WShMFTeN4Im2lE=
|
||||||
github.com/qdm12/goservices v0.1.1-0.20251104135713-6bee97bd4978 h1:TRGpCU1l0lNwtogEUSs5U+RFceYxkAJUmrGabno7J5c=
|
github.com/qdm12/goservices v0.1.1-0.20251104135713-6bee97bd4978 h1:TRGpCU1l0lNwtogEUSs5U+RFceYxkAJUmrGabno7J5c=
|
||||||
github.com/qdm12/goservices v0.1.1-0.20251104135713-6bee97bd4978/go.mod h1:D1Po4CRQLYjccnAR2JsVlN1sBMgQrcNLONbvyuzcdTg=
|
github.com/qdm12/goservices v0.1.1-0.20251104135713-6bee97bd4978/go.mod h1:D1Po4CRQLYjccnAR2JsVlN1sBMgQrcNLONbvyuzcdTg=
|
||||||
github.com/qdm12/gosettings v0.4.4 h1:SM6tOZDf6k8qbjWU8KWyBF4mWIixfsKCfh9DGRLHlj4=
|
github.com/qdm12/gosettings v0.4.4 h1:SM6tOZDf6k8qbjWU8KWyBF4mWIixfsKCfh9DGRLHlj4=
|
||||||
@@ -95,12 +91,12 @@ github.com/rogpeppe/go-internal v1.13.1 h1:KvO1DLK/DRN07sQ1LQKScxyZJuNnedQ5/wKSR
|
|||||||
github.com/rogpeppe/go-internal v1.13.1/go.mod h1:uMEvuHeurkdAXX61udpOXGD/AzZDWNMNyH2VO9fmH0o=
|
github.com/rogpeppe/go-internal v1.13.1/go.mod h1:uMEvuHeurkdAXX61udpOXGD/AzZDWNMNyH2VO9fmH0o=
|
||||||
github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U=
|
github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U=
|
||||||
github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=
|
github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=
|
||||||
github.com/ti-mo/netfilter v0.5.3 h1:ikzduvnaUMwre5bhbNwWOd6bjqLMVb33vv0XXbK0xGQ=
|
|
||||||
github.com/ti-mo/netfilter v0.5.3/go.mod h1:08SyBCg6hu1qyQk4s3DjjJKNrm3RTb32nm6AzyT972E=
|
|
||||||
github.com/ulikunitz/xz v0.5.15 h1:9DNdB5s+SgV3bQ2ApL10xRc35ck0DuIX/isZvIk+ubY=
|
github.com/ulikunitz/xz v0.5.15 h1:9DNdB5s+SgV3bQ2ApL10xRc35ck0DuIX/isZvIk+ubY=
|
||||||
github.com/ulikunitz/xz v0.5.15/go.mod h1:nbz6k7qbPmH4IRqmfOplQw/tblSgqTqBwxkY0oWt/14=
|
github.com/ulikunitz/xz v0.5.15/go.mod h1:nbz6k7qbPmH4IRqmfOplQw/tblSgqTqBwxkY0oWt/14=
|
||||||
github.com/vishvananda/netns v0.0.4 h1:Oeaw1EM2JMxD51g9uhtC0D7erkIjgmj8+JZc26m1YX8=
|
github.com/vishvananda/netlink v1.3.1 h1:3AEMt62VKqz90r0tmNhog0r/PpWKmrEShJU0wJW6bV0=
|
||||||
github.com/vishvananda/netns v0.0.4/go.mod h1:SpkAiCQRtJ6TvvxPnOSyH3BMl6unz3xZlaprSwhNNJM=
|
github.com/vishvananda/netlink v1.3.1/go.mod h1:ARtKouGSTGchR8aMwmkzC0qiNPrrWO5JS/XMVl45+b4=
|
||||||
|
github.com/vishvananda/netns v0.0.5 h1:DfiHV+j8bA32MFM7bfEunvT8IAqQ/NzSJHtcmW5zdEY=
|
||||||
|
github.com/vishvananda/netns v0.0.5/go.mod h1:SpkAiCQRtJ6TvvxPnOSyH3BMl6unz3xZlaprSwhNNJM=
|
||||||
github.com/youmark/pkcs8 v0.0.0-20201027041543-1326539a0a0a h1:fZHgsYlfvtyqToslyjUt3VOPF4J7aK/3MPcK7xp3PDk=
|
github.com/youmark/pkcs8 v0.0.0-20201027041543-1326539a0a0a h1:fZHgsYlfvtyqToslyjUt3VOPF4J7aK/3MPcK7xp3PDk=
|
||||||
github.com/youmark/pkcs8 v0.0.0-20201027041543-1326539a0a0a/go.mod h1:ul22v+Nro/R083muKhosV54bj5niojjWZvU8xrevuH4=
|
github.com/youmark/pkcs8 v0.0.0-20201027041543-1326539a0a0a/go.mod h1:ul22v+Nro/R083muKhosV54bj5niojjWZvU8xrevuH4=
|
||||||
github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
|
github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
|
||||||
@@ -110,15 +106,15 @@ golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8U
|
|||||||
golang.org/x/crypto v0.0.0-20200302210943-78000ba7a073/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
golang.org/x/crypto v0.0.0-20200302210943-78000ba7a073/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||||
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
||||||
golang.org/x/crypto v0.7.0/go.mod h1:pYwdfH91IfpZVANVyUOhSIPZaFoJGxTFbZhFTx+dXZU=
|
golang.org/x/crypto v0.7.0/go.mod h1:pYwdfH91IfpZVANVyUOhSIPZaFoJGxTFbZhFTx+dXZU=
|
||||||
golang.org/x/crypto v0.47.0 h1:V6e3FRj+n4dbpw86FJ8Fv7XVOql7TEwpHapKoMJ/GO8=
|
golang.org/x/crypto v0.45.0 h1:jMBrvKuj23MTlT0bQEOBcAE0mjg8mK9RXFhRH6nyF3Q=
|
||||||
golang.org/x/crypto v0.47.0/go.mod h1:ff3Y9VzzKbwSSEzWqJsJVBnWmRwRSHt/6Op5n9bQc4A=
|
golang.org/x/crypto v0.45.0/go.mod h1:XTGrrkGJve7CYK7J8PEww4aY7gM3qMCElcJQ8n8JdX4=
|
||||||
golang.org/x/exp v0.0.0-20241009180824-f66d83c29e7c h1:7dEasQXItcW1xKJ2+gg5VOiBnqWrJc+rq0DPKyvvdbY=
|
golang.org/x/exp v0.0.0-20241009180824-f66d83c29e7c h1:7dEasQXItcW1xKJ2+gg5VOiBnqWrJc+rq0DPKyvvdbY=
|
||||||
golang.org/x/exp v0.0.0-20241009180824-f66d83c29e7c/go.mod h1:NQtJDoLvd6faHhE7m4T/1IY708gDefGGjR/iUW8yQQ8=
|
golang.org/x/exp v0.0.0-20241009180824-f66d83c29e7c/go.mod h1:NQtJDoLvd6faHhE7m4T/1IY708gDefGGjR/iUW8yQQ8=
|
||||||
golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||||
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
|
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
|
||||||
golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
|
golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
|
||||||
golang.org/x/mod v0.31.0 h1:HaW9xtz0+kOcWKwli0ZXy79Ix+UW/vOfmWI5QVd2tgI=
|
golang.org/x/mod v0.29.0 h1:HV8lRxZC4l2cr3Zq1LvtOsi/ThTgWnUk/y64QSs8GwA=
|
||||||
golang.org/x/mod v0.31.0/go.mod h1:43JraMp9cGx1Rx3AqioxrbrhNsLl2l/iNAvuBkrezpg=
|
golang.org/x/mod v0.29.0/go.mod h1:NyhrlYXJ2H4eJiRy/WDBO6HMqZQ6q9nk4JzS3NuCK+w=
|
||||||
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||||
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||||
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
||||||
@@ -126,14 +122,14 @@ golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96b
|
|||||||
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
|
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
|
||||||
golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
|
golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
|
||||||
golang.org/x/net v0.8.0/go.mod h1:QVkue5JL9kW//ek3r6jTKnTFis1tRmNAW2P1shuFdJc=
|
golang.org/x/net v0.8.0/go.mod h1:QVkue5JL9kW//ek3r6jTKnTFis1tRmNAW2P1shuFdJc=
|
||||||
golang.org/x/net v0.49.0 h1:eeHFmOGUTtaaPSGNmjBKpbng9MulQsJURQUAfUwY++o=
|
golang.org/x/net v0.47.0 h1:Mx+4dIFzqraBXUugkia1OOvlD6LemFo1ALMHjrXDOhY=
|
||||||
golang.org/x/net v0.49.0/go.mod h1:/ysNB2EvaqvesRkuLAyjI1ycPZlQHM3q01F02UY/MV8=
|
golang.org/x/net v0.47.0/go.mod h1:/jNxtkgq5yWUGYkaZGqo27cfGZ1c5Nen03aYrrKpVRU=
|
||||||
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
golang.org/x/sync v0.19.0 h1:vV+1eWNmZ5geRlYjzm2adRgW2/mcpevXNg50YZtPCE4=
|
golang.org/x/sync v0.18.0 h1:kr88TuHDroi+UVf+0hZnirlk8o8T+4MrK6mr60WkH/I=
|
||||||
golang.org/x/sync v0.19.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI=
|
golang.org/x/sync v0.18.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI=
|
||||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||||
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
@@ -144,10 +140,12 @@ golang.org/x/sys v0.0.0-20211007075335-d3039528d8ac/go.mod h1:oPkhp1MJrh7nUepCBc
|
|||||||
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
|
golang.org/x/sys v0.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.40.0 h1:DBZZqJ2Rkml6QMQsZywtnjnnGvHza6BTfYFWY9kjEWQ=
|
golang.org/x/sys v0.10.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.40.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
|
golang.org/x/sys v0.38.0 h1:3yZWxaJjBmCWXqhN1qh02AkOnCQ1poK6oF+a7xWL6Gc=
|
||||||
|
golang.org/x/sys v0.38.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
|
||||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||||
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
||||||
golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
|
golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
|
||||||
@@ -157,8 +155,8 @@ golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
|||||||
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
|
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
|
||||||
golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
|
golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
|
||||||
golang.org/x/text v0.8.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
|
golang.org/x/text v0.8.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
|
||||||
golang.org/x/text v0.33.0 h1:B3njUFyqtHDUI5jMn1YIr5B0IE2U0qck04r6d4KPAxE=
|
golang.org/x/text v0.31.0 h1:aC8ghyu4JhP8VojJ2lEHBnochRno1sgL6nEi9WGFGMM=
|
||||||
golang.org/x/text v0.33.0/go.mod h1:LuMebE6+rBincTi9+xWTY8TztLzKHc/9C1uBCG27+q8=
|
golang.org/x/text v0.31.0/go.mod h1:tKRAlv61yKIjGGHX/4tP1LTbc13YSec1pxVEWXzfoeM=
|
||||||
golang.org/x/time v0.3.0 h1:rg5rLMjNzMS1RkNLzCG38eapWhnYLFYXDXj2gOlr8j4=
|
golang.org/x/time v0.3.0 h1:rg5rLMjNzMS1RkNLzCG38eapWhnYLFYXDXj2gOlr8j4=
|
||||||
golang.org/x/time v0.3.0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
golang.org/x/time v0.3.0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||||
@@ -166,8 +164,8 @@ golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtn
|
|||||||
golang.org/x/tools v0.1.1/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
|
golang.org/x/tools v0.1.1/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
|
||||||
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
|
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
|
||||||
golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=
|
golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=
|
||||||
golang.org/x/tools v0.40.0 h1:yLkxfA+Qnul4cs9QA3KnlFu0lVmd8JJfoq+E41uSutA=
|
golang.org/x/tools v0.38.0 h1:Hx2Xv8hISq8Lm16jvBZ2VQf+RLmbd7wVUsALibYI/IQ=
|
||||||
golang.org/x/tools v0.40.0/go.mod h1:Ik/tzLRlbscWpqqMRjyWYDisX8bG13FrdXp3o4Sr9lc=
|
golang.org/x/tools v0.38.0/go.mod h1:yEsQ/d/YK8cjh0L6rZlY8tgtlKiBNTL14pGDJPJpYQs=
|
||||||
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||||
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||||
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||||
|
|||||||
+20
-3
@@ -10,6 +10,8 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/qdm12/dns/v2/pkg/doh"
|
||||||
|
dnsprovider "github.com/qdm12/dns/v2/pkg/provider"
|
||||||
"github.com/qdm12/gluetun/internal/configuration/settings"
|
"github.com/qdm12/gluetun/internal/configuration/settings"
|
||||||
"github.com/qdm12/gluetun/internal/constants"
|
"github.com/qdm12/gluetun/internal/constants"
|
||||||
"github.com/qdm12/gluetun/internal/constants/providers"
|
"github.com/qdm12/gluetun/internal/constants/providers"
|
||||||
@@ -38,12 +40,12 @@ type UpdaterLogger interface {
|
|||||||
func (c *CLI) Update(ctx context.Context, args []string, logger UpdaterLogger) error {
|
func (c *CLI) Update(ctx context.Context, args []string, logger UpdaterLogger) error {
|
||||||
options := settings.Updater{}
|
options := settings.Updater{}
|
||||||
var endUserMode, maintainerMode, updateAll bool
|
var endUserMode, maintainerMode, updateAll bool
|
||||||
var csvProviders, ipToken, protonUsername, protonEmail, protonPassword string
|
var dnsServer, csvProviders, ipToken, protonUsername, protonEmail, protonPassword string
|
||||||
flagSet := flag.NewFlagSet("update", flag.ExitOnError)
|
flagSet := flag.NewFlagSet("update", flag.ExitOnError)
|
||||||
flagSet.BoolVar(&endUserMode, "enduser", false, "Write results to /gluetun/servers.json (for end users)")
|
flagSet.BoolVar(&endUserMode, "enduser", false, "Write results to /gluetun/servers.json (for end users)")
|
||||||
flagSet.BoolVar(&maintainerMode, "maintainer", false,
|
flagSet.BoolVar(&maintainerMode, "maintainer", false,
|
||||||
"Write results to ./internal/storage/servers.json to modify the program (for maintainers)")
|
"Write results to ./internal/storage/servers.json to modify the program (for maintainers)")
|
||||||
flagSet.StringVar(&options.DNSAddress, "dns", "8.8.8.8", "DNS resolver address to use")
|
flagSet.StringVar(&dnsServer, "dns", "", "no longer used, your DNS will use DoH with Cloudflare and Google")
|
||||||
const defaultMinRatio = 0.8
|
const defaultMinRatio = 0.8
|
||||||
flagSet.Float64Var(&options.MinRatio, "minratio", defaultMinRatio,
|
flagSet.Float64Var(&options.MinRatio, "minratio", defaultMinRatio,
|
||||||
"Minimum ratio of servers to find for the update to succeed")
|
"Minimum ratio of servers to find for the update to succeed")
|
||||||
@@ -58,6 +60,10 @@ func (c *CLI) Update(ctx context.Context, args []string, logger UpdaterLogger) e
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if dnsServer != "" {
|
||||||
|
logger.Warn("The -dns flag is no longer used, your DNS will use DoH with Cloudflare and Google")
|
||||||
|
}
|
||||||
|
|
||||||
if !endUserMode && !maintainerMode {
|
if !endUserMode && !maintainerMode {
|
||||||
return fmt.Errorf("%w", ErrModeUnspecified)
|
return fmt.Errorf("%w", ErrModeUnspecified)
|
||||||
}
|
}
|
||||||
@@ -97,10 +103,21 @@ func (c *CLI) Update(ctx context.Context, args []string, logger UpdaterLogger) e
|
|||||||
return fmt.Errorf("creating servers storage: %w", err)
|
return fmt.Errorf("creating servers storage: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
dohSettings := doh.Settings{
|
||||||
|
UpstreamResolvers: []dnsprovider.Provider{
|
||||||
|
dnsprovider.Cloudflare(),
|
||||||
|
dnsprovider.Google(),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
dnsDialer, err := doh.New(dohSettings)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("creating DoH dialer: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
const clientTimeout = 10 * time.Second
|
const clientTimeout = 10 * time.Second
|
||||||
httpClient := &http.Client{Timeout: clientTimeout}
|
httpClient := &http.Client{Timeout: clientTimeout}
|
||||||
unzipper := unzip.New(httpClient)
|
unzipper := unzip.New(httpClient)
|
||||||
parallelResolver := resolver.NewParallelResolver(options.DNSAddress)
|
parallelResolver := resolver.NewParallelResolver(dnsDialer)
|
||||||
nameTokenPairs := []api.NameToken{
|
nameTokenPairs := []api.NameToken{
|
||||||
{Name: string(api.IPInfo), Token: ipToken},
|
{Name: string(api.IPInfo), Token: ipToken},
|
||||||
{Name: string(api.IP2Location)},
|
{Name: string(api.IP2Location)},
|
||||||
|
|||||||
+17
-20
@@ -9,8 +9,9 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
// Start launches a command and streams stdout and stderr to channels.
|
// Start launches a command and streams stdout and stderr to channels.
|
||||||
// All the channels returned are ready only and won't be closed
|
// stdoutLines and stderrLines channels will be closed when there is no more
|
||||||
// if the command fails later.
|
// 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) (
|
func (c *Cmder) Start(cmd *exec.Cmd) (
|
||||||
stdoutLines, stderrLines <-chan string,
|
stdoutLines, stderrLines <-chan string,
|
||||||
waitError <-chan error, startErr error,
|
waitError <-chan error, startErr error,
|
||||||
@@ -21,7 +22,6 @@ func (c *Cmder) Start(cmd *exec.Cmd) (
|
|||||||
func start(cmd execCmd) (stdoutLines, stderrLines <-chan string,
|
func start(cmd execCmd) (stdoutLines, stderrLines <-chan string,
|
||||||
waitError <-chan error, startErr error,
|
waitError <-chan error, startErr error,
|
||||||
) {
|
) {
|
||||||
stop := make(chan struct{})
|
|
||||||
stdoutReady := make(chan struct{})
|
stdoutReady := make(chan struct{})
|
||||||
stdoutLinesCh := make(chan string)
|
stdoutLinesCh := make(chan string)
|
||||||
stdoutDone := make(chan struct{})
|
stdoutDone := make(chan struct{})
|
||||||
@@ -33,43 +33,47 @@ func start(cmd execCmd) (stdoutLines, stderrLines <-chan string,
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, nil, nil, err
|
return nil, nil, nil, err
|
||||||
}
|
}
|
||||||
go streamToChannel(stdoutReady, stop, stdoutDone, stdout, stdoutLinesCh)
|
go streamToChannel(stdoutReady, stdoutDone, stdout, stdoutLinesCh)
|
||||||
|
|
||||||
stderr, err := cmd.StderrPipe()
|
stderr, err := cmd.StderrPipe()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
_ = stdout.Close()
|
_ = stdout.Close()
|
||||||
close(stop)
|
|
||||||
<-stdoutDone
|
<-stdoutDone
|
||||||
|
close(stdoutLinesCh)
|
||||||
return nil, nil, nil, err
|
return nil, nil, nil, err
|
||||||
}
|
}
|
||||||
go streamToChannel(stderrReady, stop, stderrDone, stderr, stderrLinesCh)
|
go streamToChannel(stderrReady, stderrDone, stderr, stderrLinesCh)
|
||||||
|
|
||||||
err = cmd.Start()
|
err = cmd.Start()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
_ = stdout.Close()
|
_ = stdout.Close()
|
||||||
_ = stderr.Close()
|
|
||||||
close(stop)
|
|
||||||
<-stdoutDone
|
<-stdoutDone
|
||||||
|
close(stdoutLinesCh)
|
||||||
|
_ = stderr.Close()
|
||||||
<-stderrDone
|
<-stderrDone
|
||||||
|
close(stderrLinesCh)
|
||||||
return nil, nil, nil, err
|
return nil, nil, nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
waitErrorCh := make(chan error)
|
waitErrorCh := make(chan error)
|
||||||
go func() {
|
go func() {
|
||||||
err := cmd.Wait()
|
err := cmd.Wait()
|
||||||
_ = stdout.Close()
|
|
||||||
_ = stderr.Close()
|
|
||||||
close(stop)
|
|
||||||
<-stdoutDone
|
<-stdoutDone
|
||||||
|
close(stdoutLinesCh)
|
||||||
|
_ = stdout.Close()
|
||||||
<-stderrDone
|
<-stderrDone
|
||||||
|
close(stderrLinesCh)
|
||||||
|
_ = stderr.Close()
|
||||||
waitErrorCh <- err
|
waitErrorCh <- err
|
||||||
}()
|
}()
|
||||||
|
|
||||||
|
<-stdoutReady
|
||||||
|
<-stderrReady
|
||||||
|
|
||||||
return stdoutLinesCh, stderrLinesCh, waitErrorCh, nil
|
return stdoutLinesCh, stderrLinesCh, waitErrorCh, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func streamToChannel(ready chan<- struct{},
|
func streamToChannel(ready chan<- struct{}, done chan<- struct{},
|
||||||
stop <-chan struct{}, done chan<- struct{},
|
|
||||||
stream io.Reader, lines chan<- string,
|
stream io.Reader, lines chan<- string,
|
||||||
) {
|
) {
|
||||||
defer close(done)
|
defer close(done)
|
||||||
@@ -89,12 +93,5 @@ func streamToChannel(ready chan<- struct{},
|
|||||||
if err == nil || errors.Is(err, os.ErrClosed) {
|
if err == nil || errors.Is(err, os.ErrClosed) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// ignore the error if it is stopped.
|
|
||||||
select {
|
|
||||||
case <-stop:
|
|
||||||
return
|
|
||||||
default:
|
|
||||||
lines <- "stream error: " + err.Error()
|
lines <- "stream error: " + err.Error()
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|||||||
@@ -89,21 +89,41 @@ func Test_start(t *testing.T) {
|
|||||||
|
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
var stdoutIndex, stderrIndex int
|
collectAndCheckChannels(t, stdoutLines, stderrLines, waitError,
|
||||||
|
testCase.stdout, testCase.stderr, testCase.waitErr)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func collectAndCheckChannels(t *testing.T, stdoutLines, stderrLines <-chan string,
|
||||||
|
waitError <-chan error, expectedStdout, expectedStderr []string, expectedWaitErr error,
|
||||||
|
) {
|
||||||
|
t.Helper()
|
||||||
|
|
||||||
|
stdoutIndex := 0
|
||||||
|
stderrIndex := 0
|
||||||
|
|
||||||
done := false
|
done := false
|
||||||
for !done {
|
for !done {
|
||||||
select {
|
select {
|
||||||
case line := <-stdoutLines:
|
case line, ok := <-stdoutLines:
|
||||||
assert.Equal(t, testCase.stdout[stdoutIndex], line)
|
if !ok {
|
||||||
|
stdoutLines = nil
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
assert.Equal(t, expectedStdout[stdoutIndex], line)
|
||||||
stdoutIndex++
|
stdoutIndex++
|
||||||
case line := <-stderrLines:
|
case line, ok := <-stderrLines:
|
||||||
assert.Equal(t, testCase.stderr[stderrIndex], line)
|
if !ok {
|
||||||
|
stderrLines = nil
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
assert.Equal(t, expectedStderr[stderrIndex], line)
|
||||||
stderrIndex++
|
stderrIndex++
|
||||||
case err := <-waitError:
|
case err := <-waitError:
|
||||||
if testCase.waitErr != nil {
|
if expectedWaitErr != nil {
|
||||||
require.Error(t, err)
|
require.Error(t, err)
|
||||||
assert.Equal(t, testCase.waitErr.Error(), err.Error())
|
assert.Equal(t, expectedWaitErr.Error(), err.Error())
|
||||||
} else {
|
} else {
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
}
|
}
|
||||||
@@ -111,8 +131,6 @@ func Test_start(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
assert.Equal(t, len(testCase.stdout), stdoutIndex)
|
assert.Equal(t, len(expectedStdout), stdoutIndex)
|
||||||
assert.Equal(t, len(testCase.stderr), stderrIndex)
|
assert.Equal(t, len(expectedStderr), stderrIndex)
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -23,9 +23,7 @@ type DNSBlacklist struct {
|
|||||||
AddBlockedIPs []netip.Addr
|
AddBlockedIPs []netip.Addr
|
||||||
AddBlockedIPPrefixes []netip.Prefix
|
AddBlockedIPPrefixes []netip.Prefix
|
||||||
// RebindingProtectionExemptHostnames is a list of hostnames
|
// RebindingProtectionExemptHostnames is a list of hostnames
|
||||||
// exempt from DNS rebinding protection. It can contain parent
|
// exempt from DNS rebinding protection.
|
||||||
// domains which are of the form "*.example.com". Note the wildcard
|
|
||||||
// can only be used at the start of the hostname.
|
|
||||||
RebindingProtectionExemptHostnames []string
|
RebindingProtectionExemptHostnames []string
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -57,9 +55,6 @@ func (b DNSBlacklist) validate() (err error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
for _, host := range b.RebindingProtectionExemptHostnames {
|
for _, host := range b.RebindingProtectionExemptHostnames {
|
||||||
if len(host) > 2 && host[:2] == "*." {
|
|
||||||
host = host[2:]
|
|
||||||
}
|
|
||||||
if !hostRegex.MatchString(host) {
|
if !hostRegex.MatchString(host) {
|
||||||
return fmt.Errorf("%w: %s", ErrRebindingProtectionExemptHostNotValid, host)
|
return fmt.Errorf("%w: %s", ErrRebindingProtectionExemptHostNotValid, host)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -60,7 +60,6 @@ func (o OpenVPNSelection) validate(vpnProvider string) (err error) {
|
|||||||
providers.Giganews,
|
providers.Giganews,
|
||||||
providers.Ipvanish,
|
providers.Ipvanish,
|
||||||
providers.Perfectprivacy,
|
providers.Perfectprivacy,
|
||||||
providers.Privado,
|
|
||||||
providers.Vyprvpn,
|
providers.Vyprvpn,
|
||||||
) {
|
) {
|
||||||
return fmt.Errorf("%w: for VPN service provider %s",
|
return fmt.Errorf("%w: for VPN service provider %s",
|
||||||
@@ -75,8 +74,8 @@ func (o OpenVPNSelection) validate(vpnProvider string) (err error) {
|
|||||||
providers.Privatevpn, providers.Torguard:
|
providers.Privatevpn, providers.Torguard:
|
||||||
// no custom port allowed
|
// no custom port allowed
|
||||||
case providers.Expressvpn, providers.Fastestvpn,
|
case providers.Expressvpn, providers.Fastestvpn,
|
||||||
providers.Giganews, providers.Ipvanish, providers.Nordvpn,
|
providers.Giganews, providers.Ipvanish,
|
||||||
providers.Privado, providers.Purevpn,
|
providers.Nordvpn, providers.Purevpn,
|
||||||
providers.Surfshark, providers.VPNSecure,
|
providers.Surfshark, providers.VPNSecure,
|
||||||
providers.VPNUnlimited, providers.Vyprvpn:
|
providers.VPNUnlimited, providers.Vyprvpn:
|
||||||
return fmt.Errorf("%w: for VPN service provider %s",
|
return fmt.Errorf("%w: for VPN service provider %s",
|
||||||
@@ -99,8 +98,11 @@ func (o OpenVPNSelection) validate(vpnProvider string) (err error) {
|
|||||||
case providers.Perfectprivacy:
|
case providers.Perfectprivacy:
|
||||||
allowedTCP = []uint16{44, 443, 4433}
|
allowedTCP = []uint16{44, 443, 4433}
|
||||||
allowedUDP = []uint16{44, 443, 4433}
|
allowedUDP = []uint16{44, 443, 4433}
|
||||||
|
case providers.Privado:
|
||||||
|
allowedTCP = []uint16{443, 1194, 8080, 8443}
|
||||||
|
allowedUDP = []uint16{443, 1194, 8080, 8443}
|
||||||
case providers.PrivateInternetAccess:
|
case providers.PrivateInternetAccess:
|
||||||
allowedTCP = []uint16{80, 110, 443}
|
allowedTCP = []uint16{80, 110, 443, 501, 502, 8443}
|
||||||
allowedUDP = []uint16{53, 1194, 1197, 1198, 8080, 9201}
|
allowedUDP = []uint16{53, 1194, 1197, 1198, 8080, 9201}
|
||||||
case providers.Protonvpn:
|
case providers.Protonvpn:
|
||||||
allowedTCP = []uint16{443, 5995, 8443}
|
allowedTCP = []uint16{443, 5995, 8443}
|
||||||
@@ -130,7 +132,6 @@ func (o OpenVPNSelection) validate(vpnProvider string) (err error) {
|
|||||||
// Validate EncPreset
|
// Validate EncPreset
|
||||||
if vpnProvider == providers.PrivateInternetAccess {
|
if vpnProvider == providers.PrivateInternetAccess {
|
||||||
validEncryptionPresets := []string{
|
validEncryptionPresets := []string{
|
||||||
presets.None,
|
|
||||||
presets.Normal,
|
presets.Normal,
|
||||||
presets.Strong,
|
presets.Strong,
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,111 +0,0 @@
|
|||||||
package settings
|
|
||||||
|
|
||||||
import (
|
|
||||||
"errors"
|
|
||||||
"fmt"
|
|
||||||
"net/netip"
|
|
||||||
|
|
||||||
"github.com/qdm12/gosettings"
|
|
||||||
"github.com/qdm12/gosettings/reader"
|
|
||||||
"github.com/qdm12/gotree"
|
|
||||||
)
|
|
||||||
|
|
||||||
// PMTUD contains settings to configure Path MTU Discovery.
|
|
||||||
type PMTUD struct {
|
|
||||||
// ICMPAddresses is the redundancy list of addresses to use
|
|
||||||
// for ICMP path MTU discovery. Each address MUST handle ICMP
|
|
||||||
// packets for PMTUD to work.
|
|
||||||
// It cannot be nil in the internal state.
|
|
||||||
ICMPAddresses []netip.Addr `json:"icmp_addresses"`
|
|
||||||
// TCPAddresses is the redundancy list of addresses to use
|
|
||||||
// for TCP path MTU discovery. Each address MUST have a listening
|
|
||||||
// TCP server on the port specified.
|
|
||||||
// It cannot be nil in the internal state.
|
|
||||||
TCPAddresses []netip.AddrPort `json:"tcp_addresses"`
|
|
||||||
}
|
|
||||||
|
|
||||||
var (
|
|
||||||
ErrPMTUDICMPAddressNotValid = errors.New("PMTUD ICMP address is not valid")
|
|
||||||
ErrPMTUDTCPAddressNotValid = errors.New("PMTUD TCP address is not valid")
|
|
||||||
)
|
|
||||||
|
|
||||||
// Validate validates PMTUD settings.
|
|
||||||
func (p PMTUD) validate() (err error) {
|
|
||||||
for i, addr := range p.ICMPAddresses {
|
|
||||||
if !addr.IsValid() {
|
|
||||||
return fmt.Errorf("%w: at index %d", ErrPMTUDICMPAddressNotValid, i)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
for i, addr := range p.TCPAddresses {
|
|
||||||
if !addr.IsValid() {
|
|
||||||
return fmt.Errorf("%w: at index %d", ErrPMTUDTCPAddressNotValid, i)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *PMTUD) copy() (copied PMTUD) {
|
|
||||||
return PMTUD{
|
|
||||||
ICMPAddresses: gosettings.CopySlice(p.ICMPAddresses),
|
|
||||||
TCPAddresses: gosettings.CopySlice(p.TCPAddresses),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *PMTUD) overrideWith(other PMTUD) {
|
|
||||||
p.ICMPAddresses = gosettings.OverrideWithSlice(p.ICMPAddresses, other.ICMPAddresses)
|
|
||||||
p.TCPAddresses = gosettings.OverrideWithSlice(p.TCPAddresses, other.TCPAddresses)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *PMTUD) setDefaults() {
|
|
||||||
defaultICMPAddresses := []netip.Addr{
|
|
||||||
netip.AddrFrom4([4]byte{1, 1, 1, 1}),
|
|
||||||
netip.AddrFrom4([4]byte{8, 8, 8, 8}),
|
|
||||||
}
|
|
||||||
p.ICMPAddresses = gosettings.DefaultSlice(p.ICMPAddresses, defaultICMPAddresses)
|
|
||||||
|
|
||||||
const dnsPort, tlsPort = 53, 443
|
|
||||||
defaultTCPAddresses := []netip.AddrPort{
|
|
||||||
netip.AddrPortFrom(netip.AddrFrom4([4]byte{1, 1, 1, 1}), dnsPort),
|
|
||||||
netip.AddrPortFrom(netip.AddrFrom4([4]byte{8, 8, 8, 8}), dnsPort),
|
|
||||||
netip.AddrPortFrom(netip.AddrFrom4([4]byte{1, 1, 1, 1}), tlsPort),
|
|
||||||
netip.AddrPortFrom(netip.AddrFrom4([4]byte{8, 8, 8, 8}), tlsPort),
|
|
||||||
netip.AddrPortFrom(netip.MustParseAddr("2606:4700:4700::1111"), dnsPort),
|
|
||||||
netip.AddrPortFrom(netip.MustParseAddr("2001:4860:4860::8888"), dnsPort),
|
|
||||||
netip.AddrPortFrom(netip.MustParseAddr("2606:4700:4700::1111"), tlsPort),
|
|
||||||
netip.AddrPortFrom(netip.MustParseAddr("2001:4860:4860::8888"), tlsPort),
|
|
||||||
}
|
|
||||||
p.TCPAddresses = gosettings.DefaultSlice(p.TCPAddresses, defaultTCPAddresses)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p PMTUD) String() string {
|
|
||||||
return p.toLinesNode().String()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p PMTUD) toLinesNode() (node *gotree.Node) {
|
|
||||||
node = gotree.New("Path MTU discovery:")
|
|
||||||
|
|
||||||
icmpAddrNode := node.Append("ICMP addresses:")
|
|
||||||
for _, addr := range p.ICMPAddresses {
|
|
||||||
icmpAddrNode.Append(addr.String())
|
|
||||||
}
|
|
||||||
|
|
||||||
tcpAddrNode := node.Append("TCP addresses:")
|
|
||||||
for _, addr := range p.TCPAddresses {
|
|
||||||
tcpAddrNode.Append(addr.String())
|
|
||||||
}
|
|
||||||
return node
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *PMTUD) read(r *reader.Reader) (err error) {
|
|
||||||
p.ICMPAddresses, err = r.CSVNetipAddresses("PMTUD_ICMP_ADDRESSES")
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
p.TCPAddresses, err = r.CSVNetipAddrPorts("PMTUD_TCP_ADDRESSES")
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
@@ -2,8 +2,6 @@ package settings
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"slices"
|
|
||||||
"sort"
|
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/qdm12/gluetun/internal/constants/providers"
|
"github.com/qdm12/gluetun/internal/constants/providers"
|
||||||
@@ -33,11 +31,6 @@ func (p *Provider) validate(vpnType string, filterChoicesGetter FilterChoicesGet
|
|||||||
if vpnType == vpn.OpenVPN {
|
if vpnType == vpn.OpenVPN {
|
||||||
validNames = providers.AllWithCustom()
|
validNames = providers.AllWithCustom()
|
||||||
validNames = append(validNames, "pia") // Retro-compatibility
|
validNames = append(validNames, "pia") // Retro-compatibility
|
||||||
// Remove Mullvad since it no longer supports OpenVPN as of January 15th, 2026
|
|
||||||
mullvadIndex := slices.Index(validNames, providers.Mullvad)
|
|
||||||
validNames[mullvadIndex], validNames[len(validNames)-1] = validNames[len(validNames)-1], validNames[mullvadIndex]
|
|
||||||
validNames = validNames[:len(validNames)-1]
|
|
||||||
sort.Strings(validNames)
|
|
||||||
} else { // Wireguard
|
} else { // Wireguard
|
||||||
validNames = []string{
|
validNames = []string{
|
||||||
providers.Airvpn,
|
providers.Airvpn,
|
||||||
|
|||||||
@@ -29,27 +29,14 @@ func Test_Settings_String(t *testing.T) {
|
|||||||
| | └── OpenVPN server selection settings:
|
| | └── OpenVPN server selection settings:
|
||||||
| | ├── Protocol: UDP
|
| | ├── Protocol: UDP
|
||||||
| | └── Private Internet Access encryption preset: strong
|
| | └── Private Internet Access encryption preset: strong
|
||||||
| ├── OpenVPN settings:
|
| └── OpenVPN settings:
|
||||||
| | ├── OpenVPN version: 2.6
|
| ├── OpenVPN version: 2.6
|
||||||
| | ├── User: [not set]
|
| ├── User: [not set]
|
||||||
| | ├── Password: [not set]
|
| ├── Password: [not set]
|
||||||
| | ├── Private Internet Access encryption preset: strong
|
| ├── Private Internet Access encryption preset: strong
|
||||||
| | ├── Network interface: tun0
|
| ├── Network interface: tun0
|
||||||
| | ├── Run OpenVPN as: root
|
| ├── Run OpenVPN as: root
|
||||||
| | └── Verbosity level: 1
|
| └── Verbosity level: 1
|
||||||
| └── Path MTU discovery:
|
|
||||||
| ├── ICMP addresses:
|
|
||||||
| | ├── 1.1.1.1
|
|
||||||
| | └── 8.8.8.8
|
|
||||||
| └── TCP addresses:
|
|
||||||
| ├── 1.1.1.1:53
|
|
||||||
| ├── 8.8.8.8:53
|
|
||||||
| ├── 1.1.1.1:443
|
|
||||||
| ├── 8.8.8.8:443
|
|
||||||
| ├── [2606:4700:4700::1111]:53
|
|
||||||
| ├── [2001:4860:4860::8888]:53
|
|
||||||
| ├── [2606:4700:4700::1111]:443
|
|
||||||
| └── [2001:4860:4860::8888]:443
|
|
||||||
├── DNS settings:
|
├── DNS settings:
|
||||||
| ├── Keep existing nameserver(s): no
|
| ├── Keep existing nameserver(s): no
|
||||||
| ├── DNS server address to use: 127.0.0.1
|
| ├── DNS server address to use: 127.0.0.1
|
||||||
|
|||||||
@@ -21,10 +21,6 @@ type Updater struct {
|
|||||||
// updater. It cannot be nil in the internal state.
|
// updater. It cannot be nil in the internal state.
|
||||||
// TODO change to value and add Enabled field.
|
// TODO change to value and add Enabled field.
|
||||||
Period *time.Duration
|
Period *time.Duration
|
||||||
// DNSAddress is the DNS server address to use
|
|
||||||
// to resolve VPN server hostnames to IP addresses.
|
|
||||||
// It cannot be the empty string in the internal state.
|
|
||||||
DNSAddress string
|
|
||||||
// MinRatio is the minimum ratio of servers to
|
// MinRatio is the minimum ratio of servers to
|
||||||
// find per provider, compared to the total current
|
// find per provider, compared to the total current
|
||||||
// number of servers. It defaults to 0.8.
|
// number of servers. It defaults to 0.8.
|
||||||
@@ -76,7 +72,6 @@ func (u Updater) Validate() (err error) {
|
|||||||
func (u *Updater) copy() (copied Updater) {
|
func (u *Updater) copy() (copied Updater) {
|
||||||
return Updater{
|
return Updater{
|
||||||
Period: gosettings.CopyPointer(u.Period),
|
Period: gosettings.CopyPointer(u.Period),
|
||||||
DNSAddress: u.DNSAddress,
|
|
||||||
MinRatio: u.MinRatio,
|
MinRatio: u.MinRatio,
|
||||||
Providers: gosettings.CopySlice(u.Providers),
|
Providers: gosettings.CopySlice(u.Providers),
|
||||||
ProtonEmail: gosettings.CopyPointer(u.ProtonEmail),
|
ProtonEmail: gosettings.CopyPointer(u.ProtonEmail),
|
||||||
@@ -89,7 +84,6 @@ func (u *Updater) copy() (copied Updater) {
|
|||||||
// settings.
|
// settings.
|
||||||
func (u *Updater) overrideWith(other Updater) {
|
func (u *Updater) overrideWith(other Updater) {
|
||||||
u.Period = gosettings.OverrideWithPointer(u.Period, other.Period)
|
u.Period = gosettings.OverrideWithPointer(u.Period, other.Period)
|
||||||
u.DNSAddress = gosettings.OverrideWithComparable(u.DNSAddress, other.DNSAddress)
|
|
||||||
u.MinRatio = gosettings.OverrideWithComparable(u.MinRatio, other.MinRatio)
|
u.MinRatio = gosettings.OverrideWithComparable(u.MinRatio, other.MinRatio)
|
||||||
u.Providers = gosettings.OverrideWithSlice(u.Providers, other.Providers)
|
u.Providers = gosettings.OverrideWithSlice(u.Providers, other.Providers)
|
||||||
u.ProtonEmail = gosettings.OverrideWithPointer(u.ProtonEmail, other.ProtonEmail)
|
u.ProtonEmail = gosettings.OverrideWithPointer(u.ProtonEmail, other.ProtonEmail)
|
||||||
@@ -98,7 +92,6 @@ func (u *Updater) overrideWith(other Updater) {
|
|||||||
|
|
||||||
func (u *Updater) SetDefaults(vpnProvider string) {
|
func (u *Updater) SetDefaults(vpnProvider string) {
|
||||||
u.Period = gosettings.DefaultPointer(u.Period, 0)
|
u.Period = gosettings.DefaultPointer(u.Period, 0)
|
||||||
u.DNSAddress = gosettings.DefaultComparable(u.DNSAddress, "1.1.1.1:53")
|
|
||||||
|
|
||||||
if u.MinRatio == 0 {
|
if u.MinRatio == 0 {
|
||||||
const defaultMinRatio = 0.8
|
const defaultMinRatio = 0.8
|
||||||
@@ -125,7 +118,6 @@ func (u Updater) toLinesNode() (node *gotree.Node) {
|
|||||||
|
|
||||||
node = gotree.New("Server data updater settings:")
|
node = gotree.New("Server data updater settings:")
|
||||||
node.Appendf("Update period: %s", *u.Period)
|
node.Appendf("Update period: %s", *u.Period)
|
||||||
node.Appendf("DNS address: %s", u.DNSAddress)
|
|
||||||
node.Appendf("Minimum ratio: %.1f", u.MinRatio)
|
node.Appendf("Minimum ratio: %.1f", u.MinRatio)
|
||||||
node.Appendf("Providers to update: %s", strings.Join(u.Providers, ", "))
|
node.Appendf("Providers to update: %s", strings.Join(u.Providers, ", "))
|
||||||
if slices.Contains(u.Providers, providers.Protonvpn) {
|
if slices.Contains(u.Providers, providers.Protonvpn) {
|
||||||
@@ -142,11 +134,6 @@ func (u *Updater) read(r *reader.Reader) (err error) {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
u.DNSAddress, err = readUpdaterDNSAddress()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
u.MinRatio, err = r.Float64("UPDATER_MIN_RATIO")
|
u.MinRatio, err = r.Float64("UPDATER_MIN_RATIO")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
@@ -166,12 +153,3 @@ func (u *Updater) read(r *reader.Reader) (err error) {
|
|||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func readUpdaterDNSAddress() (address string, err error) {
|
|
||||||
// TODO this is currently using Cloudflare in
|
|
||||||
// plaintext to not be blocked by DNS over TLS by default.
|
|
||||||
// If a plaintext address is set in the DNS settings, this one will be used.
|
|
||||||
// use custom future encrypted DNS written in Go without blocking
|
|
||||||
// as it's too much trouble to start another parallel unbound instance for now.
|
|
||||||
return "", nil
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -18,7 +18,6 @@ type VPN struct {
|
|||||||
Provider Provider `json:"provider"`
|
Provider Provider `json:"provider"`
|
||||||
OpenVPN OpenVPN `json:"openvpn"`
|
OpenVPN OpenVPN `json:"openvpn"`
|
||||||
Wireguard Wireguard `json:"wireguard"`
|
Wireguard Wireguard `json:"wireguard"`
|
||||||
PMTUD PMTUD `json:"pmtud"`
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO v4 remove pointer for receiver (because of Surfshark).
|
// TODO v4 remove pointer for receiver (because of Surfshark).
|
||||||
@@ -46,11 +45,6 @@ func (v *VPN) Validate(filterChoicesGetter FilterChoicesGetter, ipv6Supported bo
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
err = v.PMTUD.validate()
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("PMTUD settings: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -60,7 +54,6 @@ func (v *VPN) Copy() (copied VPN) {
|
|||||||
Provider: v.Provider.copy(),
|
Provider: v.Provider.copy(),
|
||||||
OpenVPN: v.OpenVPN.copy(),
|
OpenVPN: v.OpenVPN.copy(),
|
||||||
Wireguard: v.Wireguard.copy(),
|
Wireguard: v.Wireguard.copy(),
|
||||||
PMTUD: v.PMTUD.copy(),
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -69,7 +62,6 @@ func (v *VPN) OverrideWith(other VPN) {
|
|||||||
v.Provider.overrideWith(other.Provider)
|
v.Provider.overrideWith(other.Provider)
|
||||||
v.OpenVPN.overrideWith(other.OpenVPN)
|
v.OpenVPN.overrideWith(other.OpenVPN)
|
||||||
v.Wireguard.overrideWith(other.Wireguard)
|
v.Wireguard.overrideWith(other.Wireguard)
|
||||||
v.PMTUD.overrideWith(other.PMTUD)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (v *VPN) setDefaults() {
|
func (v *VPN) setDefaults() {
|
||||||
@@ -77,7 +69,6 @@ func (v *VPN) setDefaults() {
|
|||||||
v.Provider.setDefaults()
|
v.Provider.setDefaults()
|
||||||
v.OpenVPN.setDefaults(v.Provider.Name)
|
v.OpenVPN.setDefaults(v.Provider.Name)
|
||||||
v.Wireguard.setDefaults(v.Provider.Name)
|
v.Wireguard.setDefaults(v.Provider.Name)
|
||||||
v.PMTUD.setDefaults()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (v VPN) String() string {
|
func (v VPN) String() string {
|
||||||
@@ -94,7 +85,6 @@ func (v VPN) toLinesNode() (node *gotree.Node) {
|
|||||||
} else {
|
} else {
|
||||||
node.AppendNode(v.Wireguard.toLinesNode())
|
node.AppendNode(v.Wireguard.toLinesNode())
|
||||||
}
|
}
|
||||||
node.AppendNode(v.PMTUD.toLinesNode())
|
|
||||||
|
|
||||||
return node
|
return node
|
||||||
}
|
}
|
||||||
@@ -117,10 +107,5 @@ func (v *VPN) read(r *reader.Reader) (err error) {
|
|||||||
return fmt.Errorf("wireguard: %w", err)
|
return fmt.Errorf("wireguard: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
err = v.PMTUD.read(r)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("PMTUD: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -38,9 +38,14 @@ type Wireguard struct {
|
|||||||
Interface string `json:"interface"`
|
Interface string `json:"interface"`
|
||||||
PersistentKeepaliveInterval *time.Duration `json:"persistent_keep_alive_interval"`
|
PersistentKeepaliveInterval *time.Duration `json:"persistent_keep_alive_interval"`
|
||||||
// Maximum Transmission Unit (MTU) of the Wireguard interface.
|
// Maximum Transmission Unit (MTU) of the Wireguard interface.
|
||||||
// It cannot be nil in the internal state, and defaults to
|
// It cannot be zero in the internal state, and defaults to
|
||||||
// 0 indicating to use PMTUD.
|
// 1320. Note it is not the wireguard-go MTU default of 1420
|
||||||
MTU *uint32 `json:"mtu"`
|
// because this impacts bandwidth a lot on some VPN providers,
|
||||||
|
// see https://github.com/qdm12/gluetun/issues/1650.
|
||||||
|
// It has been lowered to 1320 following quite a bit of
|
||||||
|
// investigation in the issue:
|
||||||
|
// https://github.com/qdm12/gluetun/issues/2533.
|
||||||
|
MTU uint16 `json:"mtu"`
|
||||||
// Implementation is the Wireguard implementation to use.
|
// Implementation is the Wireguard implementation to use.
|
||||||
// It can be "auto", "userspace" or "kernelspace".
|
// It can be "auto", "userspace" or "kernelspace".
|
||||||
// It defaults to "auto" and cannot be the empty string
|
// It defaults to "auto" and cannot be the empty string
|
||||||
@@ -189,7 +194,8 @@ func (w *Wireguard) setDefaults(vpnProvider string) {
|
|||||||
w.AllowedIPs = gosettings.DefaultSlice(w.AllowedIPs, defaultAllowedIPs)
|
w.AllowedIPs = gosettings.DefaultSlice(w.AllowedIPs, defaultAllowedIPs)
|
||||||
w.PersistentKeepaliveInterval = gosettings.DefaultPointer(w.PersistentKeepaliveInterval, 0)
|
w.PersistentKeepaliveInterval = gosettings.DefaultPointer(w.PersistentKeepaliveInterval, 0)
|
||||||
w.Interface = gosettings.DefaultComparable(w.Interface, "wg0")
|
w.Interface = gosettings.DefaultComparable(w.Interface, "wg0")
|
||||||
w.MTU = gosettings.DefaultPointer(w.MTU, 0)
|
const defaultMTU = 1320
|
||||||
|
w.MTU = gosettings.DefaultComparable(w.MTU, defaultMTU)
|
||||||
w.Implementation = gosettings.DefaultComparable(w.Implementation, "auto")
|
w.Implementation = gosettings.DefaultComparable(w.Implementation, "auto")
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -225,11 +231,7 @@ func (w Wireguard) toLinesNode() (node *gotree.Node) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
interfaceNode := node.Appendf("Network interface: %s", w.Interface)
|
interfaceNode := node.Appendf("Network interface: %s", w.Interface)
|
||||||
if *w.MTU == 0 {
|
interfaceNode.Appendf("MTU: %d", w.MTU)
|
||||||
interfaceNode.Append("MTU: use path MTU discovery")
|
|
||||||
} else {
|
|
||||||
interfaceNode.Appendf("MTU: %d", *w.MTU)
|
|
||||||
}
|
|
||||||
|
|
||||||
if w.Implementation != "auto" {
|
if w.Implementation != "auto" {
|
||||||
node.Appendf("Implementation: %s", w.Implementation)
|
node.Appendf("Implementation: %s", w.Implementation)
|
||||||
@@ -249,10 +251,12 @@ func (w *Wireguard) read(r *reader.Reader) (err error) {
|
|||||||
// WARNING: do not initialize w.Addresses to an empty slice
|
// WARNING: do not initialize w.Addresses to an empty slice
|
||||||
// or the defaults for nordvpn will not work.
|
// or the defaults for nordvpn will not work.
|
||||||
for _, addressString := range addressStrings {
|
for _, addressString := range addressStrings {
|
||||||
if !strings.ContainsRune(addressString, '/') {
|
addressString = strings.TrimSpace(addressString)
|
||||||
|
if addressString == "" {
|
||||||
|
continue
|
||||||
|
} else if !strings.ContainsRune(addressString, '/') {
|
||||||
addressString += "/32"
|
addressString += "/32"
|
||||||
}
|
}
|
||||||
addressString = strings.TrimSpace(addressString)
|
|
||||||
address, err := netip.ParsePrefix(addressString)
|
address, err := netip.ParsePrefix(addressString)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("parsing address: %w", err)
|
return fmt.Errorf("parsing address: %w", err)
|
||||||
@@ -270,9 +274,11 @@ func (w *Wireguard) read(r *reader.Reader) (err error) {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
w.MTU, err = r.Uint32Ptr("WIREGUARD_MTU")
|
mtuPtr, err := r.Uint16Ptr("WIREGUARD_MTU")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
|
} else if mtuPtr != nil {
|
||||||
|
w.MTU = *mtuPtr
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,10 +3,10 @@ package files
|
|||||||
import (
|
import (
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"net"
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"regexp"
|
"regexp"
|
||||||
"strings"
|
|
||||||
|
|
||||||
"gopkg.in/ini.v1"
|
"gopkg.in/ini.v1"
|
||||||
)
|
)
|
||||||
@@ -82,12 +82,12 @@ func parseWireguardPeerSection(peerSection *ini.Section) (
|
|||||||
publicKey = getINIKeyFromSection(peerSection, "PublicKey")
|
publicKey = getINIKeyFromSection(peerSection, "PublicKey")
|
||||||
endpoint := getINIKeyFromSection(peerSection, "Endpoint")
|
endpoint := getINIKeyFromSection(peerSection, "Endpoint")
|
||||||
if endpoint != nil {
|
if endpoint != nil {
|
||||||
parts := strings.Split(*endpoint, ":")
|
host, port, err := net.SplitHostPort(*endpoint)
|
||||||
endpointIP = &parts[0]
|
if err == nil {
|
||||||
const partsWithPort = 2
|
endpointIP = &host
|
||||||
if len(parts) >= partsWithPort {
|
endpointPort = &port
|
||||||
endpointPort = new(string)
|
} else {
|
||||||
*endpointPort = strings.Join(parts[1:], ":")
|
endpointIP = endpoint
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -179,6 +179,12 @@ Endpoint = 1.2.3.4:51820`,
|
|||||||
endpointIP: ptrTo("1.2.3.4"),
|
endpointIP: ptrTo("1.2.3.4"),
|
||||||
endpointPort: ptrTo("51820"),
|
endpointPort: ptrTo("51820"),
|
||||||
},
|
},
|
||||||
|
"ipv6_endpoint": {
|
||||||
|
iniData: `[Peer]
|
||||||
|
Endpoint = [2a02:bbbb:aaaa:8075::10]:51820`,
|
||||||
|
endpointIP: ptrTo("2a02:bbbb:aaaa:8075::10"),
|
||||||
|
endpointPort: ptrTo("51820"),
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
for testName, testCase := range testCases {
|
for testName, testCase := range testCases {
|
||||||
|
|||||||
+1
-6
@@ -43,12 +43,7 @@ func (l *Loop) Run(ctx context.Context, done chan<- struct{}) {
|
|||||||
runError, err = l.setupServer(ctx)
|
runError, err = l.setupServer(ctx)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
l.backoffTime = defaultBackoffTime
|
l.backoffTime = defaultBackoffTime
|
||||||
l.logger.Info("ready and using DNS server at address " + settings.ServerAddress.String())
|
l.logger.Info("ready")
|
||||||
|
|
||||||
err = l.updateFiles(ctx, settings)
|
|
||||||
if err != nil {
|
|
||||||
l.logger.Warn("downloading block lists failed, skipping: " + err.Error())
|
|
||||||
}
|
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -20,6 +20,11 @@ func (l *Loop) setupServer(ctx context.Context) (runError <-chan error, err erro
|
|||||||
return nil, fmt.Errorf("updating filter for rebinding protection: %w", err)
|
return nil, fmt.Errorf("updating filter for rebinding protection: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
err = l.updateFiles(ctx, settings)
|
||||||
|
if err != nil {
|
||||||
|
l.logger.Warn("downloading block lists failed, skipping: " + err.Error())
|
||||||
|
}
|
||||||
|
|
||||||
serverSettings, err := buildServerSettings(settings, l.filter, l.localResolvers, l.logger)
|
serverSettings, err := buildServerSettings(settings, l.filter, l.localResolvers, l.logger)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("building server settings: %w", err)
|
return nil, fmt.Errorf("building server settings: %w", err)
|
||||||
|
|||||||
+13
-2
@@ -28,12 +28,23 @@ func (l *Loop) RunRestartTicker(ctx context.Context, done chan<- struct{}) {
|
|||||||
return
|
return
|
||||||
case <-timer.C:
|
case <-timer.C:
|
||||||
lastTick = l.timeNow()
|
lastTick = l.timeNow()
|
||||||
|
|
||||||
|
status := l.GetStatus()
|
||||||
settings := l.GetSettings()
|
settings := l.GetSettings()
|
||||||
if l.GetStatus() == constants.Running {
|
if status == constants.Running {
|
||||||
if err := l.updateFiles(ctx, settings); err != nil {
|
if err := l.updateFiles(ctx, settings); err != nil {
|
||||||
l.logger.Warn("updating block lists failed, skipping: " + err.Error())
|
l.statusManager.SetStatus(constants.Crashed)
|
||||||
|
l.logger.Error(err.Error())
|
||||||
|
l.logger.Warn("skipping DNS server restart due to failed files update")
|
||||||
|
settings := l.GetSettings()
|
||||||
|
timer.Reset(*settings.UpdatePeriod)
|
||||||
|
continue
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
_, _ = l.statusManager.ApplyStatus(ctx, constants.Stopped)
|
||||||
|
_, _ = l.statusManager.ApplyStatus(ctx, constants.Running)
|
||||||
|
|
||||||
timer.Reset(*settings.UpdatePeriod)
|
timer.Reset(*settings.UpdatePeriod)
|
||||||
case <-l.updateTicker:
|
case <-l.updateTicker:
|
||||||
if !timer.Stop() {
|
if !timer.Stop() {
|
||||||
|
|||||||
+1
-1
@@ -1,4 +1,4 @@
|
|||||||
package iptables
|
package firewall
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
package iptables
|
package firewall
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
package iptables
|
package firewall
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
@@ -69,8 +69,8 @@ func Test_deleteIPTablesRule(t *testing.T) {
|
|||||||
"invalid_instruction": {
|
"invalid_instruction": {
|
||||||
instruction: "invalid",
|
instruction: "invalid",
|
||||||
errWrapped: ErrIptablesCommandMalformed,
|
errWrapped: ErrIptablesCommandMalformed,
|
||||||
errMessage: "parsing iptables command: parsing \"invalid\": " +
|
errMessage: "parsing iptables command: iptables command is malformed: " +
|
||||||
"iptables command is malformed: flag \"invalid\" requires a value, but got none",
|
"fields count 1 is not even: \"invalid\"",
|
||||||
},
|
},
|
||||||
"list_error": {
|
"list_error": {
|
||||||
instruction: "-t nat --delete PREROUTING -i tun0 -p tcp --dport 43716 -j REDIRECT --to-ports 5678",
|
instruction: "-t nat --delete PREROUTING -i tun0 -p tcp --dport 43716 -j REDIRECT --to-ports 5678",
|
||||||
+60
-30
@@ -22,7 +22,9 @@ func (c *Config) SetEnabled(ctx context.Context, enabled bool) (err error) {
|
|||||||
|
|
||||||
if !enabled {
|
if !enabled {
|
||||||
c.logger.Info("disabling...")
|
c.logger.Info("disabling...")
|
||||||
c.restore(ctx)
|
if err = c.disable(ctx); err != nil {
|
||||||
|
return fmt.Errorf("disabling firewall: %w", err)
|
||||||
|
}
|
||||||
c.enabled = false
|
c.enabled = false
|
||||||
c.logger.Info("disabled successfully")
|
c.logger.Info("disabled successfully")
|
||||||
return nil
|
return nil
|
||||||
@@ -39,33 +41,64 @@ func (c *Config) SetEnabled(ctx context.Context, enabled bool) (err error) {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (c *Config) disable(ctx context.Context) (err error) {
|
||||||
|
if err = c.clearAllRules(ctx); err != nil {
|
||||||
|
return fmt.Errorf("clearing all rules: %w", err)
|
||||||
|
}
|
||||||
|
if err = c.setIPv4AllPolicies(ctx, "ACCEPT"); err != nil {
|
||||||
|
return fmt.Errorf("setting ipv4 policies: %w", err)
|
||||||
|
}
|
||||||
|
if err = c.setIPv6AllPolicies(ctx, "ACCEPT"); err != nil {
|
||||||
|
return fmt.Errorf("setting ipv6 policies: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
const remove = true
|
||||||
|
err = c.redirectPorts(ctx, remove)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("removing port redirections: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// To use in defered call when enabling the firewall.
|
||||||
|
func (c *Config) fallbackToDisabled(ctx context.Context) {
|
||||||
|
if ctx.Err() != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if err := c.disable(ctx); err != nil {
|
||||||
|
c.logger.Error("failed reversing firewall changes: " + err.Error())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func (c *Config) enable(ctx context.Context) (err error) {
|
func (c *Config) enable(ctx context.Context) (err error) {
|
||||||
c.restore, err = c.impl.SaveAndRestore(ctx)
|
touched := false
|
||||||
if err != nil {
|
if err = c.setIPv4AllPolicies(ctx, "DROP"); err != nil {
|
||||||
return fmt.Errorf("saving firewall rules: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
defer func() {
|
|
||||||
if err != nil {
|
|
||||||
c.restore(context.Background())
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
|
|
||||||
if err = c.impl.SetBaseChainsPolicy(ctx, "DROP"); err != nil {
|
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
touched = true
|
||||||
|
|
||||||
// Loopback traffic
|
if err = c.setIPv6AllPolicies(ctx, "DROP"); err != nil {
|
||||||
if err = c.impl.AcceptInputThroughInterface(ctx, "lo"); err != nil {
|
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
const remove = false
|
const remove = false
|
||||||
if err = c.impl.AcceptOutputThroughInterface(ctx, "lo", remove); err != nil {
|
|
||||||
|
defer func() {
|
||||||
|
if touched && err != nil {
|
||||||
|
c.fallbackToDisabled(ctx)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
// Loopback traffic
|
||||||
|
if err = c.acceptInputThroughInterface(ctx, "lo", remove); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if err = c.acceptOutputThroughInterface(ctx, "lo", remove); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
if err = c.impl.AcceptEstablishedRelatedTraffic(ctx); err != nil {
|
if err = c.acceptEstablishedRelatedTraffic(ctx, remove); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -75,9 +108,7 @@ func (c *Config) enable(ctx context.Context) (err error) {
|
|||||||
|
|
||||||
localInterfaces := make(map[string]struct{}, len(c.localNetworks))
|
localInterfaces := make(map[string]struct{}, len(c.localNetworks))
|
||||||
for _, network := range c.localNetworks {
|
for _, network := range c.localNetworks {
|
||||||
err = c.impl.AcceptOutputFromIPToSubnet(ctx,
|
if err := c.acceptOutputFromIPToSubnet(ctx, network.InterfaceName, network.IP, network.IPNet, remove); err != nil {
|
||||||
network.InterfaceName, network.IP, network.IPNet, remove)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -86,7 +117,7 @@ func (c *Config) enable(ctx context.Context) (err error) {
|
|||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
localInterfaces[network.InterfaceName] = struct{}{}
|
localInterfaces[network.InterfaceName] = struct{}{}
|
||||||
err = c.impl.AcceptIpv6MulticastOutput(ctx, network.InterfaceName)
|
err = c.acceptIpv6MulticastOutput(ctx, network.InterfaceName, remove)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("accepting IPv6 multicast output: %w", err)
|
return fmt.Errorf("accepting IPv6 multicast output: %w", err)
|
||||||
}
|
}
|
||||||
@@ -99,7 +130,7 @@ func (c *Config) enable(ctx context.Context) (err error) {
|
|||||||
// Allows packets from any IP address to go through eth0 / local network
|
// Allows packets from any IP address to go through eth0 / local network
|
||||||
// to reach Gluetun.
|
// to reach Gluetun.
|
||||||
for _, network := range c.localNetworks {
|
for _, network := range c.localNetworks {
|
||||||
if err := c.impl.AcceptInputToSubnet(ctx, network.InterfaceName, network.IPNet); err != nil {
|
if err := c.acceptInputToSubnet(ctx, network.InterfaceName, network.IPNet, remove); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -108,12 +139,12 @@ func (c *Config) enable(ctx context.Context) (err error) {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
err = c.redirectPorts(ctx)
|
err = c.redirectPorts(ctx, remove)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("redirecting ports: %w", err)
|
return fmt.Errorf("redirecting ports: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := c.impl.RunUserPostRules(ctx, c.customRulesPath); err != nil {
|
if err := c.runUserPostRules(ctx, c.customRulesPath, remove); err != nil {
|
||||||
return fmt.Errorf("running user defined post firewall rules: %w", err)
|
return fmt.Errorf("running user defined post firewall rules: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -133,7 +164,7 @@ func (c *Config) allowVPNIP(ctx context.Context) (err error) {
|
|||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
interfacesSeen[defaultRoute.NetInterface] = struct{}{}
|
interfacesSeen[defaultRoute.NetInterface] = struct{}{}
|
||||||
err = c.impl.AcceptOutputTrafficToVPN(ctx, defaultRoute.NetInterface, c.vpnConnection, remove)
|
err = c.acceptOutputTrafficToVPN(ctx, defaultRoute.NetInterface, c.vpnConnection, remove)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("accepting output traffic through VPN: %w", err)
|
return fmt.Errorf("accepting output traffic through VPN: %w", err)
|
||||||
}
|
}
|
||||||
@@ -155,7 +186,7 @@ func (c *Config) allowOutboundSubnets(ctx context.Context) (err error) {
|
|||||||
firewallUpdated = true
|
firewallUpdated = true
|
||||||
|
|
||||||
const remove = false
|
const remove = false
|
||||||
err := c.impl.AcceptOutputFromIPToSubnet(ctx, defaultRoute.NetInterface,
|
err := c.acceptOutputFromIPToSubnet(ctx, defaultRoute.NetInterface,
|
||||||
defaultRoute.AssignedIP, subnet, remove)
|
defaultRoute.AssignedIP, subnet, remove)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
@@ -173,7 +204,7 @@ func (c *Config) allowInputPorts(ctx context.Context) (err error) {
|
|||||||
for port, netInterfaces := range c.allowedInputPorts {
|
for port, netInterfaces := range c.allowedInputPorts {
|
||||||
for netInterface := range netInterfaces {
|
for netInterface := range netInterfaces {
|
||||||
const remove = false
|
const remove = false
|
||||||
err = c.impl.AcceptInputToPort(ctx, netInterface, port, remove)
|
err = c.acceptInputToPort(ctx, netInterface, port, remove)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("accepting input port %d on interface %s: %w",
|
return fmt.Errorf("accepting input port %d on interface %s: %w",
|
||||||
port, netInterface, err)
|
port, netInterface, err)
|
||||||
@@ -183,10 +214,9 @@ func (c *Config) allowInputPorts(ctx context.Context) (err error) {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Config) redirectPorts(ctx context.Context) (err error) {
|
func (c *Config) redirectPorts(ctx context.Context, remove bool) (err error) {
|
||||||
for _, portRedirection := range c.portRedirections {
|
for _, portRedirection := range c.portRedirections {
|
||||||
const remove = false
|
err = c.redirectPort(ctx, portRedirection.interfaceName, portRedirection.sourcePort,
|
||||||
err = c.impl.RedirectPort(ctx, portRedirection.interfaceName, portRedirection.sourcePort,
|
|
||||||
portRedirection.destinationPort, remove)
|
portRedirection.destinationPort, remove)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
|
|||||||
@@ -2,11 +2,9 @@ package firewall
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"fmt"
|
|
||||||
"net/netip"
|
"net/netip"
|
||||||
"sync"
|
"sync"
|
||||||
|
|
||||||
"github.com/qdm12/gluetun/internal/firewall/iptables"
|
|
||||||
"github.com/qdm12/gluetun/internal/models"
|
"github.com/qdm12/gluetun/internal/models"
|
||||||
"github.com/qdm12/gluetun/internal/routing"
|
"github.com/qdm12/gluetun/internal/routing"
|
||||||
)
|
)
|
||||||
@@ -14,16 +12,17 @@ import (
|
|||||||
type Config struct {
|
type Config struct {
|
||||||
runner CmdRunner
|
runner CmdRunner
|
||||||
logger Logger
|
logger Logger
|
||||||
|
iptablesMutex sync.Mutex
|
||||||
defaultRoutes []routing.DefaultRoute
|
defaultRoutes []routing.DefaultRoute
|
||||||
localNetworks []routing.LocalNetwork
|
localNetworks []routing.LocalNetwork
|
||||||
|
|
||||||
// Fixed
|
// Fixed state
|
||||||
impl firewallImpl
|
ipTables string
|
||||||
|
ip6Tables string
|
||||||
customRulesPath string
|
customRulesPath string
|
||||||
|
|
||||||
// State
|
// State
|
||||||
enabled bool
|
enabled bool
|
||||||
restore func(context.Context)
|
|
||||||
vpnConnection models.Connection
|
vpnConnection models.Connection
|
||||||
vpnIntf string
|
vpnIntf string
|
||||||
outboundSubnets []netip.Prefix
|
outboundSubnets []netip.Prefix
|
||||||
@@ -38,19 +37,25 @@ func NewConfig(ctx context.Context, logger Logger,
|
|||||||
runner CmdRunner, defaultRoutes []routing.DefaultRoute,
|
runner CmdRunner, defaultRoutes []routing.DefaultRoute,
|
||||||
localNetworks []routing.LocalNetwork,
|
localNetworks []routing.LocalNetwork,
|
||||||
) (config *Config, err error) {
|
) (config *Config, err error) {
|
||||||
impl, err := iptables.New(ctx, runner, logger)
|
iptables, err := checkIptablesSupport(ctx, runner, "iptables", "iptables-nft", "iptables-legacy")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("creating iptables firewall: %w", err)
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
ip6tables, err := findIP6tablesSupported(ctx, runner)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
return &Config{
|
return &Config{
|
||||||
runner: runner,
|
runner: runner,
|
||||||
logger: logger,
|
logger: logger,
|
||||||
allowedInputPorts: make(map[uint16]map[string]struct{}),
|
allowedInputPorts: make(map[uint16]map[string]struct{}),
|
||||||
|
ipTables: iptables,
|
||||||
|
ip6Tables: ip6tables,
|
||||||
|
customRulesPath: "/iptables/post-rules.txt",
|
||||||
// Obtained from routing
|
// Obtained from routing
|
||||||
defaultRoutes: defaultRoutes,
|
defaultRoutes: defaultRoutes,
|
||||||
localNetworks: localNetworks,
|
localNetworks: localNetworks,
|
||||||
impl: impl,
|
|
||||||
customRulesPath: "/iptables/post-rules.txt",
|
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,12 +1,6 @@
|
|||||||
package firewall
|
package firewall
|
||||||
|
|
||||||
import (
|
import "os/exec"
|
||||||
"context"
|
|
||||||
"net/netip"
|
|
||||||
"os/exec"
|
|
||||||
|
|
||||||
"github.com/qdm12/gluetun/internal/models"
|
|
||||||
)
|
|
||||||
|
|
||||||
type CmdRunner interface {
|
type CmdRunner interface {
|
||||||
Run(cmd *exec.Cmd) (output string, err error)
|
Run(cmd *exec.Cmd) (output string, err error)
|
||||||
@@ -18,24 +12,3 @@ type Logger interface {
|
|||||||
Warn(s string)
|
Warn(s string)
|
||||||
Error(s string)
|
Error(s string)
|
||||||
}
|
}
|
||||||
|
|
||||||
type firewallImpl interface { //nolint:interfacebloat
|
|
||||||
SaveAndRestore(ctx context.Context) (restore func(context.Context), err error)
|
|
||||||
AcceptEstablishedRelatedTraffic(ctx context.Context) error
|
|
||||||
AcceptInputThroughInterface(ctx context.Context, intf string) error
|
|
||||||
AcceptInputToPort(ctx context.Context, intf string, port uint16, remove bool) error
|
|
||||||
AcceptInputToSubnet(ctx context.Context, intf string, subnet netip.Prefix) error
|
|
||||||
AcceptIpv6MulticastOutput(ctx context.Context, intf string) error
|
|
||||||
AcceptOutputFromIPToSubnet(ctx context.Context, intf string, assignedIP netip.Addr,
|
|
||||||
subnet netip.Prefix, remove bool) error
|
|
||||||
AcceptOutputThroughInterface(ctx context.Context, intf string, remove bool) error
|
|
||||||
AcceptOutputTrafficToVPN(ctx context.Context, intf string,
|
|
||||||
connection models.Connection, remove bool) error
|
|
||||||
RedirectPort(ctx context.Context, intf string, sourcePort,
|
|
||||||
destinationPort uint16, remove bool) error
|
|
||||||
RunUserPostRules(ctx context.Context, customRulesPath string) error
|
|
||||||
SetBaseChainsPolicy(ctx context.Context, policy string) error
|
|
||||||
TempDropOutputTCPRST(ctx context.Context, src, dst netip.AddrPort, excludeMark int) (
|
|
||||||
revert func(ctx context.Context) error, err error)
|
|
||||||
Version(ctx context.Context) (version string, err error)
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
package iptables
|
package firewall
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
@@ -14,8 +14,8 @@ import (
|
|||||||
func findIP6tablesSupported(ctx context.Context, runner CmdRunner) (
|
func findIP6tablesSupported(ctx context.Context, runner CmdRunner) (
|
||||||
ip6tablesPath string, err error,
|
ip6tablesPath string, err error,
|
||||||
) {
|
) {
|
||||||
ip6tablesPath, err = checkIptablesSupport(ctx, runner, "ip6tables", "ip6tables-legacy")
|
ip6tablesPath, err = checkIptablesSupport(ctx, runner, "ip6tables", "ip6tables-nft", "ip6tables-legacy")
|
||||||
if errors.Is(err, ErrNotSupported) {
|
if errors.Is(err, ErrIPTablesNotSupported) {
|
||||||
return "", nil
|
return "", nil
|
||||||
} else if err != nil {
|
} else if err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
@@ -24,23 +24,8 @@ func findIP6tablesSupported(ctx context.Context, runner CmdRunner) (
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (c *Config) runIP6tablesInstructions(ctx context.Context, instructions []string) error {
|
func (c *Config) runIP6tablesInstructions(ctx context.Context, instructions []string) error {
|
||||||
c.ip6tablesMutex.Lock() // only one ip6tables command at once
|
|
||||||
defer c.ip6tablesMutex.Unlock()
|
|
||||||
|
|
||||||
restore, err := c.saveAndRestoreIPv6(ctx)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
err = c.runIP6tablesInstructionsNoSave(ctx, instructions)
|
|
||||||
if err != nil {
|
|
||||||
restore(ctx)
|
|
||||||
}
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *Config) runIP6tablesInstructionsNoSave(ctx context.Context, instructions []string) error {
|
|
||||||
for _, instruction := range instructions {
|
for _, instruction := range instructions {
|
||||||
if err := c.runIP6tablesInstructionNoSave(ctx, instruction); err != nil {
|
if err := c.runIP6tablesInstruction(ctx, instruction); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -48,24 +33,11 @@ func (c *Config) runIP6tablesInstructionsNoSave(ctx context.Context, instruction
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (c *Config) runIP6tablesInstruction(ctx context.Context, instruction string) error {
|
func (c *Config) runIP6tablesInstruction(ctx context.Context, instruction string) error {
|
||||||
c.ip6tablesMutex.Lock() // only one ip6tables command at once
|
|
||||||
defer c.ip6tablesMutex.Unlock()
|
|
||||||
|
|
||||||
restore, err := c.saveAndRestoreIPv6(ctx)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
err = c.runIP6tablesInstructionNoSave(ctx, instruction)
|
|
||||||
if err != nil {
|
|
||||||
restore(ctx)
|
|
||||||
}
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *Config) runIP6tablesInstructionNoSave(ctx context.Context, instruction string) error {
|
|
||||||
if c.ip6Tables == "" {
|
if c.ip6Tables == "" {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
c.iptablesMutex.Lock() // only one ip6tables command at once
|
||||||
|
defer c.iptablesMutex.Unlock()
|
||||||
|
|
||||||
if isDeleteMatchInstruction(instruction) {
|
if isDeleteMatchInstruction(instruction) {
|
||||||
return deleteIPTablesRule(ctx, c.ip6Tables, instruction,
|
return deleteIPTablesRule(ctx, c.ip6Tables, instruction,
|
||||||
@@ -81,3 +53,18 @@ func (c *Config) runIP6tablesInstructionNoSave(ctx context.Context, instruction
|
|||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var ErrPolicyNotValid = errors.New("policy is not valid")
|
||||||
|
|
||||||
|
func (c *Config) setIPv6AllPolicies(ctx context.Context, policy string) error {
|
||||||
|
switch policy {
|
||||||
|
case "ACCEPT", "DROP":
|
||||||
|
default:
|
||||||
|
return fmt.Errorf("%w: %s", ErrPolicyNotValid, policy)
|
||||||
|
}
|
||||||
|
return c.runIP6tablesInstructions(ctx, []string{
|
||||||
|
"--policy INPUT " + policy,
|
||||||
|
"--policy OUTPUT " + policy,
|
||||||
|
"--policy FORWARD " + policy,
|
||||||
|
})
|
||||||
|
}
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
package iptables
|
package firewall
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
@@ -26,6 +26,22 @@ func appendOrDelete(remove bool) string {
|
|||||||
return "--append"
|
return "--append"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// flipRule changes an append rule in a delete rule or a delete rule into an
|
||||||
|
// append rule.
|
||||||
|
func flipRule(rule string) string {
|
||||||
|
switch {
|
||||||
|
case strings.HasPrefix(rule, "-A"):
|
||||||
|
return strings.Replace(rule, "-A", "-D", 1)
|
||||||
|
case strings.HasPrefix(rule, "--append"):
|
||||||
|
return strings.Replace(rule, "--append", "-D", 1)
|
||||||
|
case strings.HasPrefix(rule, "-D"):
|
||||||
|
return strings.Replace(rule, "-D", "-A", 1)
|
||||||
|
case strings.HasPrefix(rule, "--delete"):
|
||||||
|
return strings.Replace(rule, "--delete", "-A", 1)
|
||||||
|
}
|
||||||
|
return rule
|
||||||
|
}
|
||||||
|
|
||||||
// Version obtains the version of the installed iptables.
|
// Version obtains the version of the installed iptables.
|
||||||
func (c *Config) Version(ctx context.Context) (string, error) {
|
func (c *Config) Version(ctx context.Context) (string, error) {
|
||||||
cmd := exec.CommandContext(ctx, c.ipTables, "--version") //nolint:gosec
|
cmd := exec.CommandContext(ctx, c.ipTables, "--version") //nolint:gosec
|
||||||
@@ -38,28 +54,12 @@ func (c *Config) Version(ctx context.Context) (string, error) {
|
|||||||
if len(words) < minWords {
|
if len(words) < minWords {
|
||||||
return "", fmt.Errorf("%w: %s", ErrIPTablesVersionTooShort, output)
|
return "", fmt.Errorf("%w: %s", ErrIPTablesVersionTooShort, output)
|
||||||
}
|
}
|
||||||
return "iptables " + words[1], nil
|
return words[1], nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Config) runIptablesInstructions(ctx context.Context, instructions []string) error {
|
func (c *Config) runIptablesInstructions(ctx context.Context, instructions []string) error {
|
||||||
c.iptablesMutex.Lock()
|
|
||||||
defer c.iptablesMutex.Unlock()
|
|
||||||
|
|
||||||
restore, err := c.saveAndRestoreIPv4(ctx)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
err = c.runIptablesInstructionsNoSave(ctx, instructions)
|
|
||||||
if err != nil {
|
|
||||||
restore(ctx)
|
|
||||||
}
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *Config) runIptablesInstructionsNoSave(ctx context.Context, instructions []string) error {
|
|
||||||
for _, instruction := range instructions {
|
for _, instruction := range instructions {
|
||||||
if err := c.runIptablesInstructionNoSave(ctx, instruction); err != nil {
|
if err := c.runIptablesInstruction(ctx, instruction); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -70,19 +70,6 @@ func (c *Config) runIptablesInstruction(ctx context.Context, instruction string)
|
|||||||
c.iptablesMutex.Lock() // only one iptables command at once
|
c.iptablesMutex.Lock() // only one iptables command at once
|
||||||
defer c.iptablesMutex.Unlock()
|
defer c.iptablesMutex.Unlock()
|
||||||
|
|
||||||
restore, err := c.saveAndRestoreIPv4(ctx)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
err = c.runIptablesInstructionNoSave(ctx, instruction)
|
|
||||||
if err != nil {
|
|
||||||
restore(ctx)
|
|
||||||
}
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *Config) runIptablesInstructionNoSave(ctx context.Context, instruction string) error {
|
|
||||||
if isDeleteMatchInstruction(instruction) {
|
if isDeleteMatchInstruction(instruction) {
|
||||||
return deleteIPTablesRule(ctx, c.ipTables, instruction,
|
return deleteIPTablesRule(ctx, c.ipTables, instruction,
|
||||||
c.runner, c.logger)
|
c.runner, c.logger)
|
||||||
@@ -98,33 +85,42 @@ func (c *Config) runIptablesInstructionNoSave(ctx context.Context, instruction s
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Config) SetBaseChainsPolicy(ctx context.Context, policy string) error {
|
func (c *Config) clearAllRules(ctx context.Context) error {
|
||||||
policy = strings.ToUpper(policy)
|
return c.runMixedIptablesInstructions(ctx, []string{
|
||||||
|
"--flush", // flush all chains
|
||||||
|
"--delete-chain", // delete all chains
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Config) setIPv4AllPolicies(ctx context.Context, policy string) error {
|
||||||
switch policy {
|
switch policy {
|
||||||
case "ACCEPT", "DROP":
|
case "ACCEPT", "DROP":
|
||||||
default:
|
default:
|
||||||
return fmt.Errorf("%w: %s", ErrPolicyUnknown, policy)
|
return fmt.Errorf("%w: %s", ErrPolicyUnknown, policy)
|
||||||
}
|
}
|
||||||
return c.runMixedIptablesInstructions(ctx, []string{
|
return c.runIptablesInstructions(ctx, []string{
|
||||||
"--policy INPUT " + policy,
|
"--policy INPUT " + policy,
|
||||||
"--policy OUTPUT " + policy,
|
"--policy OUTPUT " + policy,
|
||||||
"--policy FORWARD " + policy,
|
"--policy FORWARD " + policy,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Config) AcceptInputThroughInterface(ctx context.Context, intf string) error {
|
func (c *Config) acceptInputThroughInterface(ctx context.Context, intf string, remove bool) error {
|
||||||
return c.runMixedIptablesInstruction(ctx, fmt.Sprintf(
|
return c.runMixedIptablesInstruction(ctx, fmt.Sprintf(
|
||||||
"--append INPUT -i %s -j ACCEPT", intf))
|
"%s INPUT -i %s -j ACCEPT", appendOrDelete(remove), intf,
|
||||||
|
))
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Config) AcceptInputToSubnet(ctx context.Context, intf string, destination netip.Prefix) error {
|
func (c *Config) acceptInputToSubnet(ctx context.Context, intf string,
|
||||||
|
destination netip.Prefix, remove bool,
|
||||||
|
) error {
|
||||||
interfaceFlag := "-i " + intf
|
interfaceFlag := "-i " + intf
|
||||||
if intf == "*" { // all interfaces
|
if intf == "*" { // all interfaces
|
||||||
interfaceFlag = ""
|
interfaceFlag = ""
|
||||||
}
|
}
|
||||||
|
|
||||||
instruction := fmt.Sprintf("--append INPUT %s -d %s -j ACCEPT",
|
instruction := fmt.Sprintf("%s INPUT %s -d %s -j ACCEPT",
|
||||||
interfaceFlag, destination.String())
|
appendOrDelete(remove), interfaceFlag, destination.String())
|
||||||
|
|
||||||
if destination.Addr().Is4() {
|
if destination.Addr().Is4() {
|
||||||
return c.runIptablesInstruction(ctx, instruction)
|
return c.runIptablesInstruction(ctx, instruction)
|
||||||
@@ -135,26 +131,23 @@ func (c *Config) AcceptInputToSubnet(ctx context.Context, intf string, destinati
|
|||||||
return c.runIP6tablesInstruction(ctx, instruction)
|
return c.runIP6tablesInstruction(ctx, instruction)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Config) AcceptOutputThroughInterface(ctx context.Context, intf string, remove bool) error {
|
func (c *Config) acceptOutputThroughInterface(ctx context.Context, intf string, remove bool) error {
|
||||||
return c.runMixedIptablesInstruction(ctx, fmt.Sprintf(
|
return c.runMixedIptablesInstruction(ctx, fmt.Sprintf(
|
||||||
"%s OUTPUT -o %s -j ACCEPT", appendOrDelete(remove), intf,
|
"%s OUTPUT -o %s -j ACCEPT", appendOrDelete(remove), intf,
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Config) AcceptEstablishedRelatedTraffic(ctx context.Context) error {
|
func (c *Config) acceptEstablishedRelatedTraffic(ctx context.Context, remove bool) error {
|
||||||
return c.runMixedIptablesInstructions(ctx, []string{
|
return c.runMixedIptablesInstructions(ctx, []string{
|
||||||
"--append OUTPUT -m conntrack --ctstate ESTABLISHED,RELATED -j ACCEPT",
|
fmt.Sprintf("%s OUTPUT -m conntrack --ctstate ESTABLISHED,RELATED -j ACCEPT", appendOrDelete(remove)),
|
||||||
"--append INPUT -m conntrack --ctstate ESTABLISHED,RELATED -j ACCEPT",
|
fmt.Sprintf("%s INPUT -m conntrack --ctstate ESTABLISHED,RELATED -j ACCEPT", appendOrDelete(remove)),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Config) AcceptOutputTrafficToVPN(ctx context.Context,
|
func (c *Config) acceptOutputTrafficToVPN(ctx context.Context,
|
||||||
defaultInterface string, connection models.Connection, remove bool,
|
defaultInterface string, connection models.Connection, remove bool,
|
||||||
) error {
|
) error {
|
||||||
protocol := connection.Protocol
|
protocol := connection.Protocol
|
||||||
if protocol == "tcp-client" {
|
|
||||||
protocol = "tcp"
|
|
||||||
}
|
|
||||||
instruction := fmt.Sprintf("%s OUTPUT -d %s -o %s -p %s -m %s --dport %d -j ACCEPT",
|
instruction := fmt.Sprintf("%s OUTPUT -d %s -o %s -p %s -m %s --dport %d -j ACCEPT",
|
||||||
appendOrDelete(remove), connection.IP, defaultInterface, protocol,
|
appendOrDelete(remove), connection.IP, defaultInterface, protocol,
|
||||||
protocol, connection.Port)
|
protocol, connection.Port)
|
||||||
@@ -166,11 +159,8 @@ func (c *Config) AcceptOutputTrafficToVPN(ctx context.Context,
|
|||||||
return c.runIP6tablesInstruction(ctx, instruction)
|
return c.runIP6tablesInstruction(ctx, instruction)
|
||||||
}
|
}
|
||||||
|
|
||||||
// AcceptOutputFromIPToSubnet accepts outgoing traffic from sourceIP to destinationSubnet
|
|
||||||
// on the interface intf. If intf is empty, it is set to "*" which means all interfaces.
|
|
||||||
// If remove is true, the rule is removed instead of added.
|
|
||||||
// Thanks to @npawelek.
|
// Thanks to @npawelek.
|
||||||
func (c *Config) AcceptOutputFromIPToSubnet(ctx context.Context,
|
func (c *Config) acceptOutputFromIPToSubnet(ctx context.Context,
|
||||||
intf string, sourceIP netip.Addr, destinationSubnet netip.Prefix, remove bool,
|
intf string, sourceIP netip.Addr, destinationSubnet netip.Prefix, remove bool,
|
||||||
) error {
|
) error {
|
||||||
doIPv4 := sourceIP.Is4() && destinationSubnet.Addr().Is4()
|
doIPv4 := sourceIP.Is4() && destinationSubnet.Addr().Is4()
|
||||||
@@ -191,24 +181,21 @@ func (c *Config) AcceptOutputFromIPToSubnet(ctx context.Context,
|
|||||||
return c.runIP6tablesInstruction(ctx, instruction)
|
return c.runIP6tablesInstruction(ctx, instruction)
|
||||||
}
|
}
|
||||||
|
|
||||||
// AcceptIpv6MulticastOutput accepts outgoing traffic to the IPv6 multicast address
|
// NDP uses multicast address (theres no broadcast in IPv6 like ARP uses in IPv4).
|
||||||
// ff02::1:ff00:0/104, which is used for NDP (Neighbor Discovery Protocol) to resolve
|
func (c *Config) acceptIpv6MulticastOutput(ctx context.Context,
|
||||||
// IPv6 addresses to MAC addresses. If intf is empty, it is set to "*" which means
|
intf string, remove bool,
|
||||||
// all interfaces. If remove is true, the rule is removed instead of added.
|
) error {
|
||||||
func (c *Config) AcceptIpv6MulticastOutput(ctx context.Context, intf string) error {
|
|
||||||
interfaceFlag := "-o " + intf
|
interfaceFlag := "-o " + intf
|
||||||
if intf == "*" { // all interfaces
|
if intf == "*" { // all interfaces
|
||||||
interfaceFlag = ""
|
interfaceFlag = ""
|
||||||
}
|
}
|
||||||
instruction := fmt.Sprintf("--append OUTPUT %s -d ff02::1:ff00:0/104 -j ACCEPT", interfaceFlag)
|
instruction := fmt.Sprintf("%s OUTPUT %s -d ff02::1:ff00:0/104 -j ACCEPT",
|
||||||
|
appendOrDelete(remove), interfaceFlag)
|
||||||
return c.runIP6tablesInstruction(ctx, instruction)
|
return c.runIP6tablesInstruction(ctx, instruction)
|
||||||
}
|
}
|
||||||
|
|
||||||
// AcceptInputToPort accepts incoming traffic on the specified port, for both TCP and UDP
|
// Used for port forwarding, with intf set to tun.
|
||||||
// protocols, on the interface intf. If intf is empty, it is set to "*" which means all interfaces.
|
func (c *Config) acceptInputToPort(ctx context.Context, intf string, port uint16, remove bool) error {
|
||||||
// If remove is true, the rule is removed instead of added. This is used for port forwarding, with
|
|
||||||
// intf set to the VPN tunnel interface.
|
|
||||||
func (c *Config) AcceptInputToPort(ctx context.Context, intf string, port uint16, remove bool) error {
|
|
||||||
interfaceFlag := "-i " + intf
|
interfaceFlag := "-i " + intf
|
||||||
if intf == "*" { // all interfaces
|
if intf == "*" { // all interfaces
|
||||||
interfaceFlag = ""
|
interfaceFlag = ""
|
||||||
@@ -219,12 +206,8 @@ func (c *Config) AcceptInputToPort(ctx context.Context, intf string, port uint16
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
// RedirectPort redirects incoming traffic on the specified source port to the
|
// Used for VPN server side port forwarding, with intf set to the VPN tunnel interface.
|
||||||
// specified destination port, for both TCP and UDP protocols, on the interface intf.
|
func (c *Config) redirectPort(ctx context.Context, intf string,
|
||||||
// If intf is empty, it is set to "*" which means all interfaces. If remove is true,
|
|
||||||
// the redirection is removed instead of added. This is used for VPN server side
|
|
||||||
// port forwarding, with intf set to the VPN tunnel interface.
|
|
||||||
func (c *Config) RedirectPort(ctx context.Context, intf string,
|
|
||||||
sourcePort, destinationPort uint16, remove bool,
|
sourcePort, destinationPort uint16, remove bool,
|
||||||
) (err error) {
|
) (err error) {
|
||||||
interfaceFlag := "-i " + intf
|
interfaceFlag := "-i " + intf
|
||||||
@@ -233,16 +216,9 @@ func (c *Config) RedirectPort(ctx context.Context, intf string,
|
|||||||
}
|
}
|
||||||
|
|
||||||
c.iptablesMutex.Lock()
|
c.iptablesMutex.Lock()
|
||||||
c.ip6tablesMutex.Lock()
|
|
||||||
defer c.iptablesMutex.Unlock()
|
defer c.iptablesMutex.Unlock()
|
||||||
defer c.ip6tablesMutex.Unlock()
|
|
||||||
|
|
||||||
restore, err := c.saveAndRestore(ctx)
|
err = c.runIptablesInstructions(ctx, []string{
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
err = c.runIptablesInstructionsNoSave(ctx, []string{
|
|
||||||
fmt.Sprintf("-t nat %s PREROUTING %s -p tcp --dport %d -j REDIRECT --to-ports %d",
|
fmt.Sprintf("-t nat %s PREROUTING %s -p tcp --dport %d -j REDIRECT --to-ports %d",
|
||||||
appendOrDelete(remove), interfaceFlag, sourcePort, destinationPort),
|
appendOrDelete(remove), interfaceFlag, sourcePort, destinationPort),
|
||||||
fmt.Sprintf("%s INPUT %s -p tcp -m tcp --dport %d -j ACCEPT",
|
fmt.Sprintf("%s INPUT %s -p tcp -m tcp --dport %d -j ACCEPT",
|
||||||
@@ -253,12 +229,11 @@ func (c *Config) RedirectPort(ctx context.Context, intf string,
|
|||||||
appendOrDelete(remove), interfaceFlag, destinationPort),
|
appendOrDelete(remove), interfaceFlag, destinationPort),
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
restore(ctx)
|
|
||||||
return fmt.Errorf("redirecting IPv4 source port %d to destination port %d on interface %s: %w",
|
return fmt.Errorf("redirecting IPv4 source port %d to destination port %d on interface %s: %w",
|
||||||
sourcePort, destinationPort, intf, err)
|
sourcePort, destinationPort, intf, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
err = c.runIP6tablesInstructionsNoSave(ctx, []string{
|
err = c.runIP6tablesInstructions(ctx, []string{
|
||||||
fmt.Sprintf("-t nat %s PREROUTING %s -p tcp --dport %d -j REDIRECT --to-ports %d",
|
fmt.Sprintf("-t nat %s PREROUTING %s -p tcp --dport %d -j REDIRECT --to-ports %d",
|
||||||
appendOrDelete(remove), interfaceFlag, sourcePort, destinationPort),
|
appendOrDelete(remove), interfaceFlag, sourcePort, destinationPort),
|
||||||
fmt.Sprintf("%s INPUT %s -p tcp -m tcp --dport %d -j ACCEPT",
|
fmt.Sprintf("%s INPUT %s -p tcp -m tcp --dport %d -j ACCEPT",
|
||||||
@@ -269,7 +244,6 @@ func (c *Config) RedirectPort(ctx context.Context, intf string,
|
|||||||
appendOrDelete(remove), interfaceFlag, destinationPort),
|
appendOrDelete(remove), interfaceFlag, destinationPort),
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
restore(ctx) // just in case
|
|
||||||
errMessage := err.Error()
|
errMessage := err.Error()
|
||||||
if strings.Contains(errMessage, "can't initialize ip6tables table `nat': Table does not exist") {
|
if strings.Contains(errMessage, "can't initialize ip6tables table `nat': Table does not exist") {
|
||||||
if !remove {
|
if !remove {
|
||||||
@@ -283,7 +257,7 @@ func (c *Config) RedirectPort(ctx context.Context, intf string,
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Config) RunUserPostRules(ctx context.Context, filepath string) error {
|
func (c *Config) runUserPostRules(ctx context.Context, filepath string, remove bool) error {
|
||||||
file, err := os.OpenFile(filepath, os.O_RDONLY, 0)
|
file, err := os.OpenFile(filepath, os.O_RDONLY, 0)
|
||||||
if os.IsNotExist(err) {
|
if os.IsNotExist(err) {
|
||||||
return nil
|
return nil
|
||||||
@@ -299,17 +273,16 @@ func (c *Config) RunUserPostRules(ctx context.Context, filepath string) error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
lines := strings.Split(string(b), "\n")
|
lines := strings.Split(string(b), "\n")
|
||||||
|
successfulRules := []string{}
|
||||||
c.iptablesMutex.Lock()
|
defer func() {
|
||||||
c.ip6tablesMutex.Lock()
|
// transaction-like rollback
|
||||||
defer c.iptablesMutex.Unlock()
|
if err == nil || ctx.Err() != nil {
|
||||||
defer c.ip6tablesMutex.Unlock()
|
return
|
||||||
|
|
||||||
restore, err := c.saveAndRestore(ctx)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
}
|
||||||
|
for _, rule := range successfulRules {
|
||||||
|
_ = c.runIptablesInstruction(ctx, flipRule(rule))
|
||||||
|
}
|
||||||
|
}()
|
||||||
for _, line := range lines {
|
for _, line := range lines {
|
||||||
var ipv4 bool
|
var ipv4 bool
|
||||||
var rule string
|
var rule string
|
||||||
@@ -336,18 +309,23 @@ func (c *Config) RunUserPostRules(ctx context.Context, filepath string) error {
|
|||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if remove {
|
||||||
|
rule = flipRule(rule)
|
||||||
|
}
|
||||||
|
|
||||||
switch {
|
switch {
|
||||||
case ipv4:
|
case ipv4:
|
||||||
err = c.runIptablesInstructionNoSave(ctx, rule)
|
err = c.runIptablesInstruction(ctx, rule)
|
||||||
case c.ip6Tables == "":
|
case c.ip6Tables == "":
|
||||||
err = fmt.Errorf("running user ip6tables rule: %w", ErrNeedIP6Tables)
|
err = fmt.Errorf("running user ip6tables rule: %w", ErrNeedIP6Tables)
|
||||||
default: // ipv6
|
default: // ipv6
|
||||||
err = c.runIP6tablesInstructionNoSave(ctx, rule)
|
err = c.runIP6tablesInstruction(ctx, rule)
|
||||||
}
|
}
|
||||||
if err != nil {
|
if err != nil {
|
||||||
restore(ctx)
|
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
successfulRules = append(successfulRules, rule)
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
@@ -1,85 +0,0 @@
|
|||||||
package iptables
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"fmt"
|
|
||||||
"os/exec"
|
|
||||||
"strings"
|
|
||||||
)
|
|
||||||
|
|
||||||
// SaveAndRestore saves the current iptables and ip6tables rules and
|
|
||||||
// returns a restore function that can be called to restore the saved rules.
|
|
||||||
func (c *Config) SaveAndRestore(ctx context.Context) (restore func(context.Context), err error) {
|
|
||||||
c.iptablesMutex.Lock()
|
|
||||||
c.ip6tablesMutex.Lock()
|
|
||||||
defer c.iptablesMutex.Unlock()
|
|
||||||
defer c.ip6tablesMutex.Unlock()
|
|
||||||
|
|
||||||
return c.saveAndRestore(ctx)
|
|
||||||
}
|
|
||||||
|
|
||||||
// callers MUST always lock both the [Config] iptablesMutex and the ip6tablesMutex
|
|
||||||
// before calling this function. Note the restore function does not interact with mutexes
|
|
||||||
// so the caller must make sure the mutexes are locked when calling the restore function.
|
|
||||||
func (c *Config) saveAndRestore(ctx context.Context) (restore func(context.Context), err error) {
|
|
||||||
restoreIPv4, err := c.saveAndRestoreIPv4(ctx)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
restoreIPv6, err := c.saveAndRestoreIPv6(ctx)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
restore = func(ctx context.Context) {
|
|
||||||
restoreIPv4(ctx)
|
|
||||||
if restoreIPv6 != nil {
|
|
||||||
restoreIPv6(ctx)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return restore, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Callers of saveAndRestoreIPv4 MUST always lock the [Config] iptablesMutex
|
|
||||||
// before calling this function.
|
|
||||||
func (c *Config) saveAndRestoreIPv4(ctx context.Context) (restore func(context.Context), err error) {
|
|
||||||
cmd := exec.CommandContext(ctx, c.ipTables+"-save") //nolint:gosec
|
|
||||||
data, err := c.runner.Run(cmd)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("saving IPv4 iptables: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
restore = func(ctx context.Context) {
|
|
||||||
cmd := exec.CommandContext(ctx, c.ipTables+"-restore") //nolint:gosec
|
|
||||||
cmd.Stdin = strings.NewReader(data)
|
|
||||||
output, err := c.runner.Run(cmd)
|
|
||||||
if err != nil {
|
|
||||||
c.logger.Warn(fmt.Sprintf("restoring IPv4 iptables failed: %v: %s", err, output))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return restore, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Callers of saveAndRestoreIPv6 MUST always lock the [Config] ip6tablesMutex
|
|
||||||
// before calling this function.
|
|
||||||
func (c *Config) saveAndRestoreIPv6(ctx context.Context) (restore func(context.Context), err error) {
|
|
||||||
if c.ip6Tables == "" {
|
|
||||||
return nil, nil //nolint:nilnil
|
|
||||||
}
|
|
||||||
|
|
||||||
cmd := exec.CommandContext(ctx, c.ip6Tables+"-save") //nolint:gosec
|
|
||||||
data, err := c.runner.Run(cmd)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("saving IPv6 iptables: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
restore = func(ctx context.Context) {
|
|
||||||
cmd = exec.CommandContext(ctx, c.ip6Tables+"-restore") //nolint:gosec
|
|
||||||
cmd.Stdin = strings.NewReader(data)
|
|
||||||
output, err := c.runner.Run(cmd)
|
|
||||||
if err != nil {
|
|
||||||
c.logger.Warn(fmt.Sprintf("restoring IPv6 iptables failed: %v: %s", err, output))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return restore, nil
|
|
||||||
}
|
|
||||||
@@ -1,51 +0,0 @@
|
|||||||
package iptables
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"sync"
|
|
||||||
|
|
||||||
"github.com/qdm12/gluetun/internal/mod"
|
|
||||||
)
|
|
||||||
|
|
||||||
type Config struct {
|
|
||||||
runner CmdRunner
|
|
||||||
logger Logger
|
|
||||||
iptablesMutex sync.Mutex
|
|
||||||
ip6tablesMutex sync.Mutex
|
|
||||||
|
|
||||||
// Fixed state
|
|
||||||
ipTables string
|
|
||||||
ip6Tables string
|
|
||||||
nftables bool
|
|
||||||
xtMark bool
|
|
||||||
}
|
|
||||||
|
|
||||||
func New(ctx context.Context, runner CmdRunner, logger Logger) (*Config, error) {
|
|
||||||
iptables, err := checkIptablesSupport(ctx, runner, "iptables", "iptables-nft", "iptables-legacy")
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
ip6tables, err := findIP6tablesSupported(ctx, runner)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
modules := map[string]bool{
|
|
||||||
"xt_mark": false,
|
|
||||||
"nf_tables": false,
|
|
||||||
}
|
|
||||||
for module := range modules {
|
|
||||||
err := mod.Probe(module)
|
|
||||||
modules[module] = err == nil
|
|
||||||
}
|
|
||||||
|
|
||||||
return &Config{
|
|
||||||
runner: runner,
|
|
||||||
logger: logger,
|
|
||||||
ipTables: iptables,
|
|
||||||
ip6Tables: ip6tables,
|
|
||||||
nftables: modules["nf_tables"],
|
|
||||||
xtMark: modules["xt_mark"],
|
|
||||||
}, nil
|
|
||||||
}
|
|
||||||
@@ -1,14 +0,0 @@
|
|||||||
package iptables
|
|
||||||
|
|
||||||
import "os/exec"
|
|
||||||
|
|
||||||
type CmdRunner interface {
|
|
||||||
Run(cmd *exec.Cmd) (output string, err error)
|
|
||||||
}
|
|
||||||
|
|
||||||
type Logger interface {
|
|
||||||
Debug(s string)
|
|
||||||
Info(s string)
|
|
||||||
Warn(s string)
|
|
||||||
Error(s string)
|
|
||||||
}
|
|
||||||
@@ -1,49 +0,0 @@
|
|||||||
package iptables
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
)
|
|
||||||
|
|
||||||
func (c *Config) runMixedIptablesInstructions(ctx context.Context, instructions []string) error {
|
|
||||||
c.iptablesMutex.Lock()
|
|
||||||
c.ip6tablesMutex.Lock()
|
|
||||||
defer c.iptablesMutex.Unlock()
|
|
||||||
defer c.ip6tablesMutex.Unlock()
|
|
||||||
|
|
||||||
restore, err := c.saveAndRestore(ctx)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, instruction := range instructions {
|
|
||||||
if err := c.runMixedIptablesInstructionNoSave(ctx, instruction); err != nil {
|
|
||||||
restore(ctx)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *Config) runMixedIptablesInstruction(ctx context.Context, instruction string) error {
|
|
||||||
c.iptablesMutex.Lock()
|
|
||||||
c.ip6tablesMutex.Lock()
|
|
||||||
defer c.iptablesMutex.Unlock()
|
|
||||||
defer c.ip6tablesMutex.Unlock()
|
|
||||||
|
|
||||||
restore, err := c.saveAndRestore(ctx)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
err = c.runMixedIptablesInstructionNoSave(ctx, instruction)
|
|
||||||
if err != nil {
|
|
||||||
restore(ctx)
|
|
||||||
}
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *Config) runMixedIptablesInstructionNoSave(ctx context.Context, instruction string) error {
|
|
||||||
if err := c.runIptablesInstructionNoSave(ctx, instruction); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
return c.runIP6tablesInstructionNoSave(ctx, instruction)
|
|
||||||
}
|
|
||||||
@@ -1,96 +0,0 @@
|
|||||||
package iptables
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"errors"
|
|
||||||
"fmt"
|
|
||||||
"net/netip"
|
|
||||||
)
|
|
||||||
|
|
||||||
type tcpFlags struct {
|
|
||||||
mask []tcpFlag
|
|
||||||
comparison []tcpFlag
|
|
||||||
}
|
|
||||||
|
|
||||||
type tcpFlag uint8
|
|
||||||
|
|
||||||
const (
|
|
||||||
tcpFlagFIN tcpFlag = 1 << iota
|
|
||||||
tcpFlagSYN
|
|
||||||
tcpFlagRST
|
|
||||||
tcpFlagPSH
|
|
||||||
tcpFlagACK
|
|
||||||
tcpFlagURG
|
|
||||||
tcpFlagECE
|
|
||||||
tcpFlagCWR
|
|
||||||
)
|
|
||||||
|
|
||||||
func (f tcpFlag) String() string {
|
|
||||||
switch f {
|
|
||||||
case tcpFlagFIN:
|
|
||||||
return "FIN"
|
|
||||||
case tcpFlagSYN:
|
|
||||||
return "SYN"
|
|
||||||
case tcpFlagRST:
|
|
||||||
return "RST"
|
|
||||||
case tcpFlagPSH:
|
|
||||||
return "PSH"
|
|
||||||
case tcpFlagACK:
|
|
||||||
return "ACK"
|
|
||||||
case tcpFlagURG:
|
|
||||||
return "URG"
|
|
||||||
case tcpFlagECE:
|
|
||||||
return "ECE"
|
|
||||||
case tcpFlagCWR:
|
|
||||||
return "CWR"
|
|
||||||
default:
|
|
||||||
panic(fmt.Sprintf("%s: %d", errTCPFlagUnknown, f))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
var errTCPFlagUnknown = errors.New("unknown TCP flag")
|
|
||||||
|
|
||||||
func parseTCPFlag(s string) (tcpFlag, error) {
|
|
||||||
allFlags := []tcpFlag{
|
|
||||||
tcpFlagFIN, tcpFlagSYN, tcpFlagRST, tcpFlagPSH,
|
|
||||||
tcpFlagACK, tcpFlagURG, tcpFlagECE, tcpFlagCWR,
|
|
||||||
}
|
|
||||||
for _, flag := range allFlags {
|
|
||||||
if s == fmt.Sprintf("%#02x", uint8(flag)) || s == flag.String() {
|
|
||||||
return flag, nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return 0, fmt.Errorf("%w: %s", errTCPFlagUnknown, s)
|
|
||||||
}
|
|
||||||
|
|
||||||
var ErrMarkMatchModuleMissing = errors.New("kernel is missing the mark module libxt_mark.so")
|
|
||||||
|
|
||||||
// TempDropOutputTCPRST temporarily drops outgoing TCP RST packets to the specified address and port,
|
|
||||||
// for any TCP packets not marked with the excludeMark given.
|
|
||||||
// This is necessary for TCP path MTU discovery to work, as the kernel will try to terminate the connection
|
|
||||||
// by sending a TCP RST packet, although we want to handle the connection manually.
|
|
||||||
func (c *Config) TempDropOutputTCPRST(ctx context.Context,
|
|
||||||
src, dst netip.AddrPort, excludeMark int) (
|
|
||||||
revert func(ctx context.Context) error, err error,
|
|
||||||
) {
|
|
||||||
if !c.nftables && !c.xtMark {
|
|
||||||
return nil, fmt.Errorf("%w", ErrMarkMatchModuleMissing)
|
|
||||||
}
|
|
||||||
|
|
||||||
const template = "%s OUTPUT -p tcp -s %s --sport %d -d %s --dport %d " +
|
|
||||||
"--tcp-flags RST RST -m mark ! --mark %d -j DROP" //nolint:dupword
|
|
||||||
instruction := fmt.Sprintf(template, "--append", src.Addr(), src.Port(), dst.Addr(), dst.Port(), excludeMark)
|
|
||||||
revertInstruction := fmt.Sprintf(template, "--delete", src.Addr(), src.Port(), dst.Addr(), dst.Port(), excludeMark)
|
|
||||||
run := c.runIptablesInstruction
|
|
||||||
if dst.Addr().Is6() {
|
|
||||||
run = c.runIP6tablesInstruction
|
|
||||||
}
|
|
||||||
revert = func(ctx context.Context) error {
|
|
||||||
return run(ctx, revertInstruction)
|
|
||||||
}
|
|
||||||
err = run(ctx, instruction)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("running instruction: %w", err)
|
|
||||||
}
|
|
||||||
return revert, nil
|
|
||||||
}
|
|
||||||
@@ -0,0 +1,21 @@
|
|||||||
|
package firewall
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
)
|
||||||
|
|
||||||
|
func (c *Config) runMixedIptablesInstructions(ctx context.Context, instructions []string) error {
|
||||||
|
for _, instruction := range instructions {
|
||||||
|
if err := c.runMixedIptablesInstruction(ctx, instruction); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Config) runMixedIptablesInstruction(ctx context.Context, instruction string) error {
|
||||||
|
if err := c.runIptablesInstruction(ctx, instruction); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return c.runIP6tablesInstruction(ctx, instruction)
|
||||||
|
}
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
package iptables
|
package firewall
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"errors"
|
"errors"
|
||||||
@@ -26,18 +26,10 @@ type chainRule struct {
|
|||||||
inputInterface string // input interface, for example "tun0" or "*""
|
inputInterface string // input interface, for example "tun0" or "*""
|
||||||
outputInterface string // output interface, for example "eth0" or "*""
|
outputInterface string // output interface, for example "eth0" or "*""
|
||||||
source netip.Prefix // source IP CIDR, for example 0.0.0.0/0. Must be valid.
|
source netip.Prefix // source IP CIDR, for example 0.0.0.0/0. Must be valid.
|
||||||
sourcePort uint16 // Not specified if set to zero.
|
|
||||||
destination netip.Prefix // destination IP CIDR, for example 0.0.0.0/0. Must be valid.
|
destination netip.Prefix // destination IP CIDR, for example 0.0.0.0/0. Must be valid.
|
||||||
destinationPort uint16 // Not specified if set to zero.
|
destinationPort uint16 // Not specified if set to zero.
|
||||||
redirPorts []uint16 // Not specified if empty.
|
redirPorts []uint16 // Not specified if empty.
|
||||||
ctstate []string // for example ["RELATED","ESTABLISHED"]. Can be empty.
|
ctstate []string // for example ["RELATED","ESTABLISHED"]. Can be empty.
|
||||||
tcpFlags tcpFlags
|
|
||||||
mark mark
|
|
||||||
}
|
|
||||||
|
|
||||||
type mark struct {
|
|
||||||
invert bool
|
|
||||||
value uint
|
|
||||||
}
|
}
|
||||||
|
|
||||||
var ErrChainListMalformed = errors.New("iptables chain list output is malformed")
|
var ErrChainListMalformed = errors.New("iptables chain list output is malformed")
|
||||||
@@ -249,23 +241,19 @@ func parseChainRuleField(fieldIndex int, field string, rule *chainRule) (err err
|
|||||||
}
|
}
|
||||||
|
|
||||||
func parseChainRuleOptionalFields(optionalFields []string, rule *chainRule) (err error) {
|
func parseChainRuleOptionalFields(optionalFields []string, rule *chainRule) (err error) {
|
||||||
i := 0
|
for i := 0; i < len(optionalFields); i++ {
|
||||||
for i < len(optionalFields) {
|
key := optionalFields[i]
|
||||||
switch optionalFields[i] {
|
switch key {
|
||||||
case "udp":
|
case "tcp", "udp":
|
||||||
i++
|
i++
|
||||||
consumed, err := parseUDPOptional(optionalFields[i:], rule)
|
value := optionalFields[i]
|
||||||
|
value = strings.TrimPrefix(value, "dpt:")
|
||||||
|
const base, bitLength = 10, 16
|
||||||
|
destinationPort, err := strconv.ParseUint(value, base, bitLength)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("parsing UDP optional fields: %w", err)
|
return fmt.Errorf("parsing destination port %q: %w", value, err)
|
||||||
}
|
}
|
||||||
i += consumed
|
rule.destinationPort = uint16(destinationPort)
|
||||||
case "tcp":
|
|
||||||
i++
|
|
||||||
consumed, err := parseTCPOptional(optionalFields[i:], rule)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("parsing TCP optional fields: %w", err)
|
|
||||||
}
|
|
||||||
i += consumed
|
|
||||||
case "redir":
|
case "redir":
|
||||||
i++
|
i++
|
||||||
switch optionalFields[i] {
|
switch optionalFields[i] {
|
||||||
@@ -276,136 +264,20 @@ func parseChainRuleOptionalFields(optionalFields []string, rule *chainRule) (err
|
|||||||
return fmt.Errorf("parsing redirection ports: %w", err)
|
return fmt.Errorf("parsing redirection ports: %w", err)
|
||||||
}
|
}
|
||||||
rule.redirPorts = ports
|
rule.redirPorts = ports
|
||||||
i++
|
|
||||||
default:
|
|
||||||
return fmt.Errorf("%w: unexpected %q after redir",
|
|
||||||
ErrChainRuleMalformed, optionalFields[1])
|
|
||||||
}
|
|
||||||
case "ctstate":
|
|
||||||
i++
|
|
||||||
rule.ctstate = strings.Split(optionalFields[i], ",")
|
|
||||||
i++
|
|
||||||
case "mark":
|
|
||||||
i++
|
|
||||||
mark, consumed, err := parseMark(optionalFields[i:])
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("parsing mark: %w", err)
|
|
||||||
}
|
|
||||||
rule.mark = mark
|
|
||||||
i += consumed
|
|
||||||
default:
|
default:
|
||||||
return fmt.Errorf("%w: unexpected optional field: %s",
|
return fmt.Errorf("%w: unexpected optional field: %s",
|
||||||
ErrChainRuleMalformed, optionalFields[i])
|
ErrChainRuleMalformed, optionalFields[i])
|
||||||
}
|
}
|
||||||
|
case "ctstate":
|
||||||
|
i++
|
||||||
|
rule.ctstate = strings.Split(optionalFields[i], ",")
|
||||||
|
default:
|
||||||
|
return fmt.Errorf("%w: unexpected optional field: %s", ErrChainRuleMalformed, key)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
var errUDPOptionalUnknown = errors.New("unknown UDP optional field")
|
|
||||||
|
|
||||||
func parseUDPOptional(optionalFields []string, rule *chainRule) (consumed int, err error) {
|
|
||||||
for _, value := range optionalFields {
|
|
||||||
if !strings.ContainsRune(value, ':') {
|
|
||||||
// no longer a UDP-associated option
|
|
||||||
return consumed, nil
|
|
||||||
}
|
|
||||||
switch {
|
|
||||||
case strings.HasPrefix(value, "dpt:"):
|
|
||||||
rule.destinationPort, err = parseDestinationPort(value)
|
|
||||||
if err != nil {
|
|
||||||
return 0, fmt.Errorf("parsing destination port: %w", err)
|
|
||||||
}
|
|
||||||
consumed++
|
|
||||||
case strings.HasPrefix(value, "spt:"):
|
|
||||||
rule.sourcePort, err = parseSourcePort(value)
|
|
||||||
if err != nil {
|
|
||||||
return 0, fmt.Errorf("parsing source port: %w", err)
|
|
||||||
}
|
|
||||||
consumed++
|
|
||||||
default:
|
|
||||||
return 0, fmt.Errorf("%w: %s", errUDPOptionalUnknown, value)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return consumed, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
var errTCPOptionalUnknown = errors.New("unknown TCP optional field")
|
|
||||||
|
|
||||||
func parseTCPOptional(optionalFields []string, rule *chainRule) (consumed int, err error) {
|
|
||||||
for _, value := range optionalFields {
|
|
||||||
if !strings.ContainsRune(value, ':') {
|
|
||||||
// no longer a TCP-associated option
|
|
||||||
return consumed, nil
|
|
||||||
}
|
|
||||||
switch {
|
|
||||||
case strings.HasPrefix(value, "dpt:"):
|
|
||||||
rule.destinationPort, err = parseDestinationPort(value)
|
|
||||||
if err != nil {
|
|
||||||
return 0, fmt.Errorf("parsing destination port: %w", err)
|
|
||||||
}
|
|
||||||
consumed++
|
|
||||||
case strings.HasPrefix(value, "spt:"):
|
|
||||||
rule.sourcePort, err = parseSourcePort(value)
|
|
||||||
if err != nil {
|
|
||||||
return 0, fmt.Errorf("parsing source port: %w", err)
|
|
||||||
}
|
|
||||||
consumed++
|
|
||||||
case strings.HasPrefix(value, "flags:"):
|
|
||||||
rule.tcpFlags, err = parseTCPFlags(value)
|
|
||||||
if err != nil {
|
|
||||||
return 0, fmt.Errorf("parsing TCP flags: %w", err)
|
|
||||||
}
|
|
||||||
consumed++
|
|
||||||
default:
|
|
||||||
return 0, fmt.Errorf("%w: %s", errTCPOptionalUnknown, value)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return consumed, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func parseDestinationPort(value string) (port uint16, err error) {
|
|
||||||
value = strings.TrimPrefix(value, "dpt:")
|
|
||||||
return parsePort(value)
|
|
||||||
}
|
|
||||||
|
|
||||||
func parseSourcePort(value string) (port uint16, err error) {
|
|
||||||
value = strings.TrimPrefix(value, "spt:")
|
|
||||||
return parsePort(value)
|
|
||||||
}
|
|
||||||
|
|
||||||
var errTCPFlagsMalformed = errors.New("TCP flags are malformed")
|
|
||||||
|
|
||||||
func parseTCPFlags(value string) (tcpFlags, error) {
|
|
||||||
value = strings.TrimPrefix(value, "flags:")
|
|
||||||
fields := strings.Split(value, "/")
|
|
||||||
const expectedFields = 2
|
|
||||||
if len(fields) != expectedFields {
|
|
||||||
return tcpFlags{}, fmt.Errorf("%w: expected format 'flags:<mask>/<comparison>' in %q",
|
|
||||||
errTCPFlagsMalformed, value)
|
|
||||||
}
|
|
||||||
maskFlags := strings.Split(fields[0], ",")
|
|
||||||
mask := make([]tcpFlag, len(maskFlags))
|
|
||||||
var err error
|
|
||||||
for i, maskFlag := range maskFlags {
|
|
||||||
mask[i], err = parseTCPFlag(maskFlag)
|
|
||||||
if err != nil {
|
|
||||||
return tcpFlags{}, fmt.Errorf("parsing TCP mask flags: %w", err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
comparisonFlags := strings.Split(fields[1], ",")
|
|
||||||
comparison := make([]tcpFlag, len(comparisonFlags))
|
|
||||||
for i, comparisonFlag := range comparisonFlags {
|
|
||||||
comparison[i], err = parseTCPFlag(comparisonFlag)
|
|
||||||
if err != nil {
|
|
||||||
return tcpFlags{}, fmt.Errorf("parsing TCP comparison flags: %w", err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return tcpFlags{
|
|
||||||
mask: mask,
|
|
||||||
comparison: comparison,
|
|
||||||
}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func parsePortsCSV(s string) (ports []uint16, err error) {
|
func parsePortsCSV(s string) (ports []uint16, err error) {
|
||||||
if s == "" {
|
if s == "" {
|
||||||
return nil, nil
|
return nil, nil
|
||||||
@@ -414,40 +286,16 @@ func parsePortsCSV(s string) (ports []uint16, err error) {
|
|||||||
fields := strings.Split(s, ",")
|
fields := strings.Split(s, ",")
|
||||||
ports = make([]uint16, len(fields))
|
ports = make([]uint16, len(fields))
|
||||||
for i, field := range fields {
|
for i, field := range fields {
|
||||||
ports[i], err = parsePort(field)
|
const base, bitLength = 10, 16
|
||||||
|
port, err := strconv.ParseUint(field, base, bitLength)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, fmt.Errorf("parsing port %q: %w", field, err)
|
||||||
}
|
}
|
||||||
|
ports[i] = uint16(port)
|
||||||
}
|
}
|
||||||
return ports, nil
|
return ports, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
var errMarkValueMalformed = errors.New("mark value is malformed")
|
|
||||||
|
|
||||||
func parseMark(optionalFields []string) (m mark, consumed int, err error) {
|
|
||||||
switch optionalFields[consumed] {
|
|
||||||
case "match":
|
|
||||||
consumed++
|
|
||||||
if optionalFields[consumed] == "!" {
|
|
||||||
m.invert = true
|
|
||||||
consumed++
|
|
||||||
}
|
|
||||||
|
|
||||||
const base = 0 // auto-detect
|
|
||||||
const bits = 32
|
|
||||||
value, err := strconv.ParseUint(optionalFields[consumed], base, bits)
|
|
||||||
if err != nil {
|
|
||||||
return mark{}, 0, fmt.Errorf("%w: %s", errMarkValueMalformed, optionalFields[consumed])
|
|
||||||
}
|
|
||||||
m.value = uint(value)
|
|
||||||
consumed++
|
|
||||||
default:
|
|
||||||
return mark{}, 0, fmt.Errorf("%w: unexpected mark mode field: %s",
|
|
||||||
ErrChainRuleMalformed, optionalFields[consumed])
|
|
||||||
}
|
|
||||||
return m, consumed, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
var ErrLineNumberIsZero = errors.New("line number is zero")
|
var ErrLineNumberIsZero = errors.New("line number is zero")
|
||||||
|
|
||||||
func parseLineNumber(s string) (n uint16, err error) {
|
func parseLineNumber(s string) (n uint16, err error) {
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
package iptables
|
package firewall
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"net/netip"
|
"net/netip"
|
||||||
+1
-1
@@ -1,3 +1,3 @@
|
|||||||
package iptables
|
package firewall
|
||||||
|
|
||||||
//go:generate mockgen -destination=mocks_test.go -package $GOPACKAGE . CmdRunner,Logger
|
//go:generate mockgen -destination=mocks_test.go -package $GOPACKAGE . CmdRunner,Logger
|
||||||
@@ -1,8 +1,8 @@
|
|||||||
// Code generated by MockGen. DO NOT EDIT.
|
// Code generated by MockGen. DO NOT EDIT.
|
||||||
// Source: github.com/qdm12/gluetun/internal/firewall/iptables (interfaces: CmdRunner,Logger)
|
// Source: github.com/qdm12/gluetun/internal/firewall (interfaces: CmdRunner,Logger)
|
||||||
|
|
||||||
// Package iptables is a generated GoMock package.
|
// Package firewall is a generated GoMock package.
|
||||||
package iptables
|
package firewall
|
||||||
|
|
||||||
import (
|
import (
|
||||||
exec "os/exec"
|
exec "os/exec"
|
||||||
@@ -1,99 +0,0 @@
|
|||||||
package nftables
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"fmt"
|
|
||||||
|
|
||||||
"github.com/google/nftables"
|
|
||||||
)
|
|
||||||
|
|
||||||
// SaveAndRestore saves the current nftables tree and returns a restore function that
|
|
||||||
// can be called to restore the saved tree.
|
|
||||||
func (f *Firewall) SaveAndRestore(_ context.Context) (restore func(context.Context), err error) {
|
|
||||||
f.mutex.Lock()
|
|
||||||
defer f.mutex.Unlock()
|
|
||||||
|
|
||||||
conn, err := nftables.New()
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("creating nftables connection: %w", err)
|
|
||||||
}
|
|
||||||
tables, err := saveTables(conn)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("saving nftables state: %w", err)
|
|
||||||
}
|
|
||||||
return func(_ context.Context) {
|
|
||||||
conn, err := nftables.New()
|
|
||||||
if err != nil {
|
|
||||||
f.logger.Warnf("creating nftables connection for restore: %s", err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
err = restoreTables(conn, tables)
|
|
||||||
if err != nil {
|
|
||||||
f.logger.Warnf("restoring nftables state: %s", err)
|
|
||||||
}
|
|
||||||
}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
type savedTable struct {
|
|
||||||
table *nftables.Table
|
|
||||||
chains []savedChain
|
|
||||||
}
|
|
||||||
|
|
||||||
type savedChain struct {
|
|
||||||
chain *nftables.Chain
|
|
||||||
rules []*nftables.Rule
|
|
||||||
}
|
|
||||||
|
|
||||||
func saveTables(conn *nftables.Conn) ([]savedTable, error) {
|
|
||||||
tables, err := conn.ListTables()
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
savedTables := make([]savedTable, len(tables))
|
|
||||||
for i, table := range tables {
|
|
||||||
savedTables[i].table = table
|
|
||||||
|
|
||||||
chains, err := conn.ListChains()
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, chain := range chains {
|
|
||||||
if chain.Table.Name != table.Name ||
|
|
||||||
chain.Table.Family != table.Family {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
rules, err := conn.GetRules(table, chain)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("getting rules for chain %s in table %s: %w", chain.Name, table.Name, err)
|
|
||||||
}
|
|
||||||
savedChain := savedChain{chain: chain, rules: rules}
|
|
||||||
savedTables[i].chains = append(savedTables[i].chains, savedChain)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return savedTables, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func restoreTables(conn *nftables.Conn, savedTables []savedTable) error {
|
|
||||||
conn.FlushRuleset()
|
|
||||||
|
|
||||||
for _, savedTable := range savedTables {
|
|
||||||
table := conn.AddTable(savedTable.table)
|
|
||||||
for _, savedChain := range savedTable.chains {
|
|
||||||
// Make the [nftables.Chain.Table] points to the new [nftables.Table]
|
|
||||||
// created in this connection.
|
|
||||||
savedChain.chain.Table = table
|
|
||||||
savedChain.chain = conn.AddChain(savedChain.chain)
|
|
||||||
|
|
||||||
for _, rule := range savedChain.rules {
|
|
||||||
rule.Table = table
|
|
||||||
rule.Chain = savedChain.chain
|
|
||||||
conn.AddRule(rule)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return conn.Flush()
|
|
||||||
}
|
|
||||||
@@ -1,50 +0,0 @@
|
|||||||
package nftables
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"errors"
|
|
||||||
"fmt"
|
|
||||||
"strings"
|
|
||||||
|
|
||||||
"github.com/google/nftables"
|
|
||||||
)
|
|
||||||
|
|
||||||
var ErrPolicyUnknown = errors.New("unknown policy")
|
|
||||||
|
|
||||||
// SetBaseChainsPolicy sets the policy of all the base chains (INPUT, FORWARD, or OUTPUT)
|
|
||||||
// for the filter table to the given policy (accept or drop).
|
|
||||||
func (f *Firewall) SetBaseChainsPolicy(_ context.Context, policy string) error {
|
|
||||||
f.mutex.Lock()
|
|
||||||
defer f.mutex.Unlock()
|
|
||||||
|
|
||||||
var chainPolicy nftables.ChainPolicy
|
|
||||||
switch strings.ToLower(policy) {
|
|
||||||
case "accept":
|
|
||||||
chainPolicy = nftables.ChainPolicyAccept
|
|
||||||
case "drop":
|
|
||||||
chainPolicy = nftables.ChainPolicyDrop
|
|
||||||
default:
|
|
||||||
return fmt.Errorf("%w: %s", ErrPolicyUnknown, policy)
|
|
||||||
}
|
|
||||||
|
|
||||||
conn, err := nftables.New()
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("creating nftables connection: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
_, inputChain, forwardChain, outputChain := setupFilterWithBaseChains(conn)
|
|
||||||
inputChain.Policy = &chainPolicy
|
|
||||||
forwardChain.Policy = &chainPolicy
|
|
||||||
outputChain.Policy = &chainPolicy
|
|
||||||
|
|
||||||
conn.AddChain(inputChain)
|
|
||||||
conn.AddChain(forwardChain)
|
|
||||||
conn.AddChain(outputChain)
|
|
||||||
|
|
||||||
err = conn.Flush()
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("flushing nftables changes: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
@@ -1,61 +0,0 @@
|
|||||||
package nftables
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"fmt"
|
|
||||||
|
|
||||||
"github.com/google/nftables"
|
|
||||||
"github.com/google/nftables/expr"
|
|
||||||
)
|
|
||||||
|
|
||||||
func (f *Firewall) AcceptEstablishedRelatedTraffic(_ context.Context) error {
|
|
||||||
f.mutex.Lock()
|
|
||||||
defer f.mutex.Unlock()
|
|
||||||
|
|
||||||
conn, err := nftables.New()
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("creating nftables connection: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
table, inputChain, _, outputChain := setupFilterWithBaseChains(conn)
|
|
||||||
|
|
||||||
ctStateExprs := []expr.Any{
|
|
||||||
&expr.Ct{
|
|
||||||
Key: expr.CtKeySTATE,
|
|
||||||
Register: 1,
|
|
||||||
},
|
|
||||||
&expr.Bitwise{
|
|
||||||
SourceRegister: 1,
|
|
||||||
DestRegister: 1,
|
|
||||||
Len: 4, //nolint:mnd
|
|
||||||
Mask: []byte{byte(expr.CtStateBitESTABLISHED | expr.CtStateBitRELATED), 0x00, 0x00, 0x00},
|
|
||||||
Xor: []byte{0x00, 0x00, 0x00, 0x00},
|
|
||||||
},
|
|
||||||
&expr.Cmp{
|
|
||||||
Op: expr.CmpOpNeq,
|
|
||||||
Register: 1,
|
|
||||||
Data: []byte{0x00, 0x00, 0x00, 0x00},
|
|
||||||
},
|
|
||||||
&expr.Verdict{
|
|
||||||
Kind: expr.VerdictAccept,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
conn.AddRule(&nftables.Rule{
|
|
||||||
Table: table,
|
|
||||||
Chain: inputChain,
|
|
||||||
Exprs: ctStateExprs,
|
|
||||||
})
|
|
||||||
|
|
||||||
conn.AddRule(&nftables.Rule{
|
|
||||||
Table: table,
|
|
||||||
Chain: outputChain,
|
|
||||||
Exprs: ctStateExprs,
|
|
||||||
})
|
|
||||||
|
|
||||||
if err := conn.Flush(); err != nil {
|
|
||||||
return fmt.Errorf("flushing: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
@@ -1,27 +0,0 @@
|
|||||||
package nftables
|
|
||||||
|
|
||||||
import (
|
|
||||||
"errors"
|
|
||||||
"fmt"
|
|
||||||
"reflect"
|
|
||||||
|
|
||||||
"github.com/google/nftables"
|
|
||||||
)
|
|
||||||
|
|
||||||
var errRuleToDeleteNotFound = errors.New("rule not found for removal")
|
|
||||||
|
|
||||||
func (f *Firewall) deleteRule(conn *nftables.Conn, rule *nftables.Rule) error {
|
|
||||||
for i, existing := range f.rules {
|
|
||||||
if !reflect.DeepEqual(existing, rule) {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
err := conn.DelRule(existing)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("deleting rule: %w", err)
|
|
||||||
}
|
|
||||||
f.rules[i], f.rules[len(f.rules)-1] = f.rules[len(f.rules)-1], f.rules[i]
|
|
||||||
f.rules = f.rules[:len(f.rules)-1]
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
return fmt.Errorf("%w: %#v", errRuleToDeleteNotFound, rule)
|
|
||||||
}
|
|
||||||
@@ -1,38 +0,0 @@
|
|||||||
package nftables
|
|
||||||
|
|
||||||
import "github.com/google/nftables"
|
|
||||||
|
|
||||||
func setupFilterWithBaseChains(conn *nftables.Conn) (table *nftables.Table,
|
|
||||||
inputChain, forwardChain, outputChain *nftables.Chain,
|
|
||||||
) {
|
|
||||||
table = conn.AddTable(&nftables.Table{
|
|
||||||
Family: nftables.TableFamilyINet,
|
|
||||||
Name: "filter",
|
|
||||||
})
|
|
||||||
|
|
||||||
inputChain = conn.AddChain(&nftables.Chain{
|
|
||||||
Name: "input",
|
|
||||||
Table: table,
|
|
||||||
Type: nftables.ChainTypeFilter,
|
|
||||||
Hooknum: nftables.ChainHookInput,
|
|
||||||
Priority: nftables.ChainPriorityFilter,
|
|
||||||
})
|
|
||||||
|
|
||||||
forwardChain = conn.AddChain(&nftables.Chain{
|
|
||||||
Name: "forward",
|
|
||||||
Table: table,
|
|
||||||
Type: nftables.ChainTypeFilter,
|
|
||||||
Hooknum: nftables.ChainHookForward,
|
|
||||||
Priority: nftables.ChainPriorityFilter,
|
|
||||||
})
|
|
||||||
|
|
||||||
outputChain = conn.AddChain(&nftables.Chain{
|
|
||||||
Name: "output",
|
|
||||||
Table: table,
|
|
||||||
Type: nftables.ChainTypeFilter,
|
|
||||||
Hooknum: nftables.ChainHookOutput,
|
|
||||||
Priority: nftables.ChainPriorityFilter,
|
|
||||||
})
|
|
||||||
|
|
||||||
return table, inputChain, forwardChain, outputChain
|
|
||||||
}
|
|
||||||
@@ -1,22 +0,0 @@
|
|||||||
package nftables
|
|
||||||
|
|
||||||
import (
|
|
||||||
"sync"
|
|
||||||
|
|
||||||
"github.com/google/nftables"
|
|
||||||
)
|
|
||||||
|
|
||||||
type Firewall struct {
|
|
||||||
logger Logger
|
|
||||||
|
|
||||||
// rules are only rules added and tracked for later removal.
|
|
||||||
// Not all rules added are tracked for removal.
|
|
||||||
rules []*nftables.Rule
|
|
||||||
mutex sync.Mutex
|
|
||||||
}
|
|
||||||
|
|
||||||
func New(logger Logger) *Firewall {
|
|
||||||
return &Firewall{
|
|
||||||
logger: logger,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,170 +0,0 @@
|
|||||||
package nftables
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"fmt"
|
|
||||||
"net/netip"
|
|
||||||
|
|
||||||
"github.com/google/nftables"
|
|
||||||
"github.com/google/nftables/expr"
|
|
||||||
)
|
|
||||||
|
|
||||||
func (f *Firewall) AcceptInputThroughInterface(_ context.Context, intf string) error {
|
|
||||||
f.mutex.Lock()
|
|
||||||
defer f.mutex.Unlock()
|
|
||||||
|
|
||||||
conn, err := nftables.New()
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("creating nftables connection: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
table, inputChain, _, _ := setupFilterWithBaseChains(conn)
|
|
||||||
|
|
||||||
rule := &nftables.Rule{
|
|
||||||
Table: table,
|
|
||||||
Chain: inputChain,
|
|
||||||
Exprs: []expr.Any{
|
|
||||||
&expr.Meta{
|
|
||||||
Key: expr.MetaKeyIIFNAME,
|
|
||||||
Register: 1,
|
|
||||||
},
|
|
||||||
&expr.Cmp{
|
|
||||||
Op: expr.CmpOpEq,
|
|
||||||
Register: 1,
|
|
||||||
Data: []byte(intf + "\x00"),
|
|
||||||
},
|
|
||||||
&expr.Verdict{
|
|
||||||
Kind: expr.VerdictAccept,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
conn.AddRule(rule)
|
|
||||||
|
|
||||||
err = conn.Flush()
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("flushing: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// AcceptInputToPort accepts incoming traffic on the specified port, for both TCP and UDP
|
|
||||||
// protocols, on the interface intf. If intf is empty or "*", the interface is not used as a filter.
|
|
||||||
// If remove is true, the rule is removed instead of added. This is used for port forwarding, with
|
|
||||||
// intf set to the VPN tunnel interface.
|
|
||||||
func (f *Firewall) AcceptInputToPort(_ context.Context, intf string, port uint16, remove bool) error {
|
|
||||||
f.mutex.Lock()
|
|
||||||
defer f.mutex.Unlock()
|
|
||||||
|
|
||||||
conn, err := nftables.New()
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("creating nftables connection: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
table, inputChain, _, _ := setupFilterWithBaseChains(conn)
|
|
||||||
portBytes := []byte{byte(port >> 8), byte(port)} //nolint:mnd
|
|
||||||
const tcp, udp uint8 = 6, 17
|
|
||||||
protocols := []uint8{tcp, udp}
|
|
||||||
|
|
||||||
for _, protocol := range protocols {
|
|
||||||
const maxExprsLen = 7
|
|
||||||
exprs := make([]expr.Any, 0, maxExprsLen)
|
|
||||||
if intf != "" && intf != "*" {
|
|
||||||
exprs = append(exprs,
|
|
||||||
&expr.Meta{Key: expr.MetaKeyIIFNAME, Register: 1},
|
|
||||||
&expr.Cmp{Op: expr.CmpOpEq, Register: 1, Data: []byte(intf + "\x00")},
|
|
||||||
)
|
|
||||||
}
|
|
||||||
exprs = append(exprs,
|
|
||||||
&expr.Payload{DestRegister: 1, Base: expr.PayloadBaseNetworkHeader, Offset: 9, Len: 1}, //nolint:mnd
|
|
||||||
&expr.Cmp{Op: expr.CmpOpEq, Register: 1, Data: []byte{protocol}},
|
|
||||||
&expr.Payload{DestRegister: 1, Base: expr.PayloadBaseTransportHeader, Offset: 2, Len: 2}, //nolint:mnd
|
|
||||||
&expr.Cmp{Op: expr.CmpOpEq, Register: 1, Data: portBytes},
|
|
||||||
&expr.Verdict{Kind: expr.VerdictAccept},
|
|
||||||
)
|
|
||||||
|
|
||||||
rule := &nftables.Rule{
|
|
||||||
Table: table,
|
|
||||||
Chain: inputChain,
|
|
||||||
Exprs: exprs,
|
|
||||||
}
|
|
||||||
|
|
||||||
if !remove {
|
|
||||||
conn.AddRule(rule)
|
|
||||||
f.rules = append(f.rules, rule)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
err = f.deleteRule(conn, rule)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("deleting rule: %w", err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
err = conn.Flush()
|
|
||||||
if err != nil {
|
|
||||||
f.rules = f.rules[:len(f.rules)-len(protocols)]
|
|
||||||
return fmt.Errorf("flushing: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (f *Firewall) AcceptInputToSubnet(_ context.Context, intf string, subnet netip.Prefix) error {
|
|
||||||
f.mutex.Lock()
|
|
||||||
defer f.mutex.Unlock()
|
|
||||||
|
|
||||||
conn, err := nftables.New()
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("creating nftables connection: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
table, inputChain, _, _ := setupFilterWithBaseChains(conn)
|
|
||||||
|
|
||||||
const maxExprsLen = 5
|
|
||||||
exprs := make([]expr.Any, 0, maxExprsLen)
|
|
||||||
|
|
||||||
if intf != "" && intf != "*" {
|
|
||||||
exprs = append(exprs,
|
|
||||||
&expr.Meta{Key: expr.MetaKeyIIFNAME, Register: 1},
|
|
||||||
&expr.Cmp{Op: expr.CmpOpEq, Register: 1, Data: []byte(intf + "\x00")},
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
var payloadOffset uint32
|
|
||||||
if subnet.Addr().Is4() {
|
|
||||||
payloadOffset = 16
|
|
||||||
} else {
|
|
||||||
payloadOffset = 24
|
|
||||||
}
|
|
||||||
|
|
||||||
exprs = append(exprs,
|
|
||||||
&expr.Payload{
|
|
||||||
DestRegister: 1,
|
|
||||||
Base: expr.PayloadBaseNetworkHeader,
|
|
||||||
Offset: payloadOffset,
|
|
||||||
Len: uint32(len(subnet.Addr().AsSlice())), //nolint:gosec
|
|
||||||
},
|
|
||||||
&expr.Cmp{
|
|
||||||
Op: expr.CmpOpEq,
|
|
||||||
Register: 1,
|
|
||||||
Data: subnet.Addr().AsSlice(),
|
|
||||||
},
|
|
||||||
&expr.Verdict{Kind: expr.VerdictAccept},
|
|
||||||
)
|
|
||||||
|
|
||||||
rule := &nftables.Rule{
|
|
||||||
Table: table,
|
|
||||||
Chain: inputChain,
|
|
||||||
Exprs: exprs,
|
|
||||||
}
|
|
||||||
|
|
||||||
conn.AddRule(rule)
|
|
||||||
|
|
||||||
err = conn.Flush()
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("flushing: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
@@ -1,5 +0,0 @@
|
|||||||
package nftables
|
|
||||||
|
|
||||||
type Logger interface {
|
|
||||||
Warnf(format string, args ...any)
|
|
||||||
}
|
|
||||||
@@ -1,78 +0,0 @@
|
|||||||
package nftables
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"fmt"
|
|
||||||
|
|
||||||
"github.com/google/nftables"
|
|
||||||
"github.com/google/nftables/expr"
|
|
||||||
)
|
|
||||||
|
|
||||||
func (f *Firewall) AcceptIpv6MulticastOutput(_ context.Context, intf string) error {
|
|
||||||
f.mutex.Lock()
|
|
||||||
defer f.mutex.Unlock()
|
|
||||||
|
|
||||||
conn, err := nftables.New()
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("creating nftables connection: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
table, _, _, outputChain := setupFilterWithBaseChains(conn)
|
|
||||||
|
|
||||||
const maxExprsLen = 6
|
|
||||||
exprs := make([]expr.Any, 0, maxExprsLen)
|
|
||||||
|
|
||||||
if intf != "" && intf != "*" {
|
|
||||||
exprs = append(exprs,
|
|
||||||
&expr.Meta{Key: expr.MetaKeyOIFNAME, Register: 1},
|
|
||||||
&expr.Cmp{Op: expr.CmpOpEq, Register: 1, Data: []byte(intf + "\x00")},
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
// ff02::1:ff00:0/104 mask is 13 bytes of 0xff
|
|
||||||
mask := []byte{
|
|
||||||
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
|
|
||||||
0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00,
|
|
||||||
} //nolint:mnd
|
|
||||||
addr := []byte{
|
|
||||||
0xff, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
|
||||||
0x00, 0x00, 0x00, 0x01, 0xff, 0x00, 0x00, 0x00,
|
|
||||||
} //nolint:mnd
|
|
||||||
|
|
||||||
exprs = append(exprs,
|
|
||||||
&expr.Payload{
|
|
||||||
DestRegister: 1,
|
|
||||||
Base: expr.PayloadBaseNetworkHeader,
|
|
||||||
Offset: 24, // IPv6 Destination Address offset //nolint:mnd
|
|
||||||
Len: 16, //nolint:mnd
|
|
||||||
},
|
|
||||||
&expr.Bitwise{
|
|
||||||
SourceRegister: 1,
|
|
||||||
DestRegister: 1,
|
|
||||||
Len: 16, //nolint:mnd
|
|
||||||
Mask: mask,
|
|
||||||
Xor: []byte{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, //nolint:mnd
|
|
||||||
},
|
|
||||||
&expr.Cmp{
|
|
||||||
Op: expr.CmpOpEq,
|
|
||||||
Register: 1,
|
|
||||||
Data: addr,
|
|
||||||
},
|
|
||||||
&expr.Verdict{Kind: expr.VerdictAccept},
|
|
||||||
)
|
|
||||||
|
|
||||||
rule := &nftables.Rule{
|
|
||||||
Table: table,
|
|
||||||
Chain: outputChain,
|
|
||||||
Exprs: exprs,
|
|
||||||
}
|
|
||||||
|
|
||||||
conn.AddRule(rule)
|
|
||||||
|
|
||||||
err = conn.Flush()
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("flushing: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
@@ -1,12 +0,0 @@
|
|||||||
package nftables
|
|
||||||
|
|
||||||
import "github.com/google/nftables"
|
|
||||||
|
|
||||||
func IsSupported() bool {
|
|
||||||
conn, err := nftables.New()
|
|
||||||
if err != nil {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
_, err = conn.ListTable("filter")
|
|
||||||
return err == nil
|
|
||||||
}
|
|
||||||
@@ -48,7 +48,7 @@ func (c *Config) removeOutboundSubnets(ctx context.Context, subnets []netip.Pref
|
|||||||
}
|
}
|
||||||
|
|
||||||
firewallUpdated = true
|
firewallUpdated = true
|
||||||
err := c.impl.AcceptOutputFromIPToSubnet(ctx, defaultRoute.NetInterface,
|
err := c.acceptOutputFromIPToSubnet(ctx, defaultRoute.NetInterface,
|
||||||
defaultRoute.AssignedIP, subNet, remove)
|
defaultRoute.AssignedIP, subNet, remove)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
c.logger.Error("cannot remove outdated outbound subnet: " + err.Error())
|
c.logger.Error("cannot remove outdated outbound subnet: " + err.Error())
|
||||||
@@ -77,7 +77,7 @@ func (c *Config) addOutboundSubnets(ctx context.Context, subnets []netip.Prefix)
|
|||||||
}
|
}
|
||||||
|
|
||||||
firewallUpdated = true
|
firewallUpdated = true
|
||||||
err := c.impl.AcceptOutputFromIPToSubnet(ctx, defaultRoute.NetInterface,
|
err := c.acceptOutputFromIPToSubnet(ctx, defaultRoute.NetInterface,
|
||||||
defaultRoute.AssignedIP, subnet, remove)
|
defaultRoute.AssignedIP, subnet, remove)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
package iptables
|
package firewall
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"errors"
|
"errors"
|
||||||
@@ -18,13 +18,10 @@ type iptablesInstruction struct {
|
|||||||
inputInterface string // for example "tun0" or "" for any interface.
|
inputInterface string // for example "tun0" or "" for any interface.
|
||||||
outputInterface string // for example "tun0" or "" for any interface.
|
outputInterface string // for example "tun0" or "" for any interface.
|
||||||
source netip.Prefix // if not valid, then it is unspecified.
|
source netip.Prefix // if not valid, then it is unspecified.
|
||||||
sourcePort uint16 // if zero, there is no source port
|
|
||||||
destination netip.Prefix // if not valid, then it is unspecified.
|
destination netip.Prefix // if not valid, then it is unspecified.
|
||||||
destinationPort uint16 // if zero, there is no destination port
|
destinationPort uint16 // if zero, there is no destination port
|
||||||
toPorts []uint16 // if empty, there is no redirection
|
toPorts []uint16 // if empty, there is no redirection
|
||||||
ctstate []string // if empty, there is no ctstate
|
ctstate []string // if empty, there is no ctstate
|
||||||
tcpFlags tcpFlags
|
|
||||||
mark mark
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (i *iptablesInstruction) setDefaults() {
|
func (i *iptablesInstruction) setDefaults() {
|
||||||
@@ -46,8 +43,6 @@ func (i *iptablesInstruction) equalToRule(table, chain string, rule chainRule) (
|
|||||||
return false
|
return false
|
||||||
case i.destinationPort != rule.destinationPort:
|
case i.destinationPort != rule.destinationPort:
|
||||||
return false
|
return false
|
||||||
case i.sourcePort != rule.sourcePort:
|
|
||||||
return false
|
|
||||||
case !slices.Equal(i.toPorts, rule.redirPorts):
|
case !slices.Equal(i.toPorts, rule.redirPorts):
|
||||||
return false
|
return false
|
||||||
case !slices.Equal(i.ctstate, rule.ctstate):
|
case !slices.Equal(i.ctstate, rule.ctstate):
|
||||||
@@ -60,11 +55,6 @@ func (i *iptablesInstruction) equalToRule(table, chain string, rule chainRule) (
|
|||||||
return false
|
return false
|
||||||
case !ipPrefixesEqual(i.destination, rule.destination):
|
case !ipPrefixesEqual(i.destination, rule.destination):
|
||||||
return false
|
return false
|
||||||
case !slices.Equal(i.tcpFlags.mask, rule.tcpFlags.mask) ||
|
|
||||||
!slices.Equal(i.tcpFlags.comparison, rule.tcpFlags.comparison):
|
|
||||||
return false
|
|
||||||
case i.mark != rule.mark:
|
|
||||||
return false
|
|
||||||
default:
|
default:
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
@@ -87,29 +77,26 @@ func parseIptablesInstruction(s string) (instruction iptablesInstruction, err er
|
|||||||
return iptablesInstruction{}, fmt.Errorf("%w: empty instruction", ErrIptablesCommandMalformed)
|
return iptablesInstruction{}, fmt.Errorf("%w: empty instruction", ErrIptablesCommandMalformed)
|
||||||
}
|
}
|
||||||
fields := strings.Fields(s)
|
fields := strings.Fields(s)
|
||||||
|
if len(fields)%2 != 0 {
|
||||||
|
return iptablesInstruction{}, fmt.Errorf("%w: fields count %d is not even: %q",
|
||||||
|
ErrIptablesCommandMalformed, len(fields), s)
|
||||||
|
}
|
||||||
|
|
||||||
i := 0
|
for i := 0; i < len(fields); i += 2 {
|
||||||
for i < len(fields) {
|
key := fields[i]
|
||||||
consumed, err := parseInstructionFlag(fields[i:], &instruction)
|
value := fields[i+1]
|
||||||
|
err = parseInstructionFlag(key, value, &instruction)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return iptablesInstruction{}, fmt.Errorf("parsing %q: %w", s, err)
|
return iptablesInstruction{}, fmt.Errorf("parsing %q: %w", s, err)
|
||||||
}
|
}
|
||||||
i += consumed
|
|
||||||
}
|
}
|
||||||
|
|
||||||
instruction.setDefaults()
|
instruction.setDefaults()
|
||||||
return instruction, nil
|
return instruction, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func parseInstructionFlag(fields []string, instruction *iptablesInstruction) (consumed int, err error) {
|
func parseInstructionFlag(key, value string, instruction *iptablesInstruction) (err error) {
|
||||||
consumed, err = preCheckInstructionFields(fields)
|
switch key {
|
||||||
if err != nil {
|
|
||||||
return 0, err
|
|
||||||
}
|
|
||||||
flag := fields[0]
|
|
||||||
value := fields[1]
|
|
||||||
|
|
||||||
switch flag {
|
|
||||||
case "-t", "--table":
|
case "-t", "--table":
|
||||||
instruction.table = value
|
instruction.table = value
|
||||||
case "-D", "--delete":
|
case "-D", "--delete":
|
||||||
@@ -122,19 +109,7 @@ func parseInstructionFlag(fields []string, instruction *iptablesInstruction) (co
|
|||||||
instruction.target = value
|
instruction.target = value
|
||||||
case "-p", "--protocol":
|
case "-p", "--protocol":
|
||||||
instruction.protocol = value
|
instruction.protocol = value
|
||||||
case "-m", "--match":
|
case "-m", "--match": // ignore match
|
||||||
consumed, err = parseMatchModule(fields, instruction)
|
|
||||||
if err != nil {
|
|
||||||
return 0, fmt.Errorf("parsing match module: %w", err)
|
|
||||||
}
|
|
||||||
case "--mark":
|
|
||||||
const base = 0 // auto-detect
|
|
||||||
const bits = 32
|
|
||||||
value, err := strconv.ParseUint(value, base, bits)
|
|
||||||
if err != nil {
|
|
||||||
return 0, fmt.Errorf("parsing mark value %q: %w", fields[2], err)
|
|
||||||
}
|
|
||||||
instruction.mark.value = uint(value)
|
|
||||||
case "-i", "--in-interface":
|
case "-i", "--in-interface":
|
||||||
instruction.inputInterface = value
|
instruction.inputInterface = value
|
||||||
case "-o", "--out-interface":
|
case "-o", "--out-interface":
|
||||||
@@ -142,61 +117,37 @@ func parseInstructionFlag(fields []string, instruction *iptablesInstruction) (co
|
|||||||
case "-s", "--source":
|
case "-s", "--source":
|
||||||
instruction.source, err = parseIPPrefix(value)
|
instruction.source, err = parseIPPrefix(value)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return 0, fmt.Errorf("parsing source IP CIDR: %w", err)
|
return fmt.Errorf("parsing source IP CIDR: %w", err)
|
||||||
}
|
|
||||||
case "--sport":
|
|
||||||
instruction.sourcePort, err = parsePort(value)
|
|
||||||
if err != nil {
|
|
||||||
return 0, fmt.Errorf("parsing source port: %w", err)
|
|
||||||
}
|
}
|
||||||
case "-d", "--destination":
|
case "-d", "--destination":
|
||||||
instruction.destination, err = parseIPPrefix(value)
|
instruction.destination, err = parseIPPrefix(value)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return 0, fmt.Errorf("parsing destination IP CIDR: %w", err)
|
return fmt.Errorf("parsing destination IP CIDR: %w", err)
|
||||||
}
|
}
|
||||||
case "--dport":
|
case "--dport":
|
||||||
instruction.destinationPort, err = parsePort(value)
|
const base, bitLength = 10, 16
|
||||||
|
destinationPort, err := strconv.ParseUint(value, base, bitLength)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return 0, fmt.Errorf("parsing destination port: %w", err)
|
return fmt.Errorf("parsing destination port: %w", err)
|
||||||
}
|
}
|
||||||
|
instruction.destinationPort = uint16(destinationPort)
|
||||||
case "--ctstate":
|
case "--ctstate":
|
||||||
instruction.ctstate = strings.Split(value, ",")
|
instruction.ctstate = strings.Split(value, ",")
|
||||||
case "--to-ports":
|
case "--to-ports":
|
||||||
instruction.toPorts, err = parseToPorts(value)
|
portStrings := strings.Split(value, ",")
|
||||||
|
instruction.toPorts = make([]uint16, len(portStrings))
|
||||||
|
for i, portString := range portStrings {
|
||||||
|
const base, bitLength = 10, 16
|
||||||
|
port, err := strconv.ParseUint(portString, base, bitLength)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return 0, fmt.Errorf("parsing port redirection: %w", err)
|
return fmt.Errorf("parsing port redirection: %w", err)
|
||||||
}
|
}
|
||||||
case "--tcp-flags":
|
instruction.toPorts[i] = uint16(port)
|
||||||
mask, comparison := value, fields[2]
|
|
||||||
instruction.tcpFlags, err = parseTCPFlags(mask + "/" + comparison)
|
|
||||||
if err != nil {
|
|
||||||
return 0, fmt.Errorf("parsing TCP flags: %w", err)
|
|
||||||
}
|
}
|
||||||
default:
|
default:
|
||||||
return 0, fmt.Errorf("%w: unknown key %q", ErrIptablesCommandMalformed, flag)
|
return fmt.Errorf("%w: unknown key %q", ErrIptablesCommandMalformed, key)
|
||||||
}
|
|
||||||
return consumed, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func preCheckInstructionFields(fields []string) (consumed int, err error) {
|
|
||||||
flag := fields[0]
|
|
||||||
// All flags use one value after the flag, except the following:
|
|
||||||
switch flag {
|
|
||||||
case "--tcp-flags": // -m can have 1 or 2 values
|
|
||||||
const expected = 3
|
|
||||||
if len(fields) < expected {
|
|
||||||
return 0, fmt.Errorf("%w: flag %q requires at least 2 values, but got %s",
|
|
||||||
ErrIptablesCommandMalformed, flag, strings.Join(fields, " "))
|
|
||||||
}
|
|
||||||
return expected, nil
|
|
||||||
default:
|
|
||||||
const expected = 2
|
|
||||||
if len(fields) < expected {
|
|
||||||
return 0, fmt.Errorf("%w: flag %q requires a value, but got none",
|
|
||||||
ErrIptablesCommandMalformed, flag)
|
|
||||||
}
|
|
||||||
return expected, nil
|
|
||||||
}
|
}
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func parseIPPrefix(value string) (prefix netip.Prefix, err error) {
|
func parseIPPrefix(value string) (prefix netip.Prefix, err error) {
|
||||||
@@ -211,52 +162,3 @@ func parseIPPrefix(value string) (prefix netip.Prefix, err error) {
|
|||||||
}
|
}
|
||||||
return netip.PrefixFrom(ip, ip.BitLen()), nil
|
return netip.PrefixFrom(ip, ip.BitLen()), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func parsePort(value string) (port uint16, err error) {
|
|
||||||
const base, bitLength = 10, 16
|
|
||||||
portValue, err := strconv.ParseUint(value, base, bitLength)
|
|
||||||
if err != nil {
|
|
||||||
return 0, err
|
|
||||||
}
|
|
||||||
return uint16(portValue), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func parseMatchModule(fields []string, instruction *iptablesInstruction) (
|
|
||||||
consumed int, err error,
|
|
||||||
) {
|
|
||||||
_ = fields[consumed] // -m or --match flag already detected
|
|
||||||
consumed++
|
|
||||||
switch fields[consumed] {
|
|
||||||
case "tcp", "udp":
|
|
||||||
consumed++
|
|
||||||
// for now ignore the protocol match since it's auto-loaded
|
|
||||||
// when parsing the -p/--protocol flag, and we don't need to
|
|
||||||
// parse it twice.
|
|
||||||
case "mark":
|
|
||||||
consumed++
|
|
||||||
switch fields[consumed] {
|
|
||||||
case "!":
|
|
||||||
consumed++
|
|
||||||
instruction.mark.invert = true
|
|
||||||
default:
|
|
||||||
return consumed, fmt.Errorf("%w: unsupported match mark with value: %s",
|
|
||||||
ErrIptablesCommandMalformed, fields[2])
|
|
||||||
}
|
|
||||||
default:
|
|
||||||
return 0, fmt.Errorf("%w: unknown match value: %s",
|
|
||||||
ErrIptablesCommandMalformed, fields[consumed])
|
|
||||||
}
|
|
||||||
return consumed, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func parseToPorts(value string) (toPorts []uint16, err error) {
|
|
||||||
portStrings := strings.Split(value, ",")
|
|
||||||
toPorts = make([]uint16, len(portStrings))
|
|
||||||
for i, portString := range portStrings {
|
|
||||||
toPorts[i], err = parsePort(portString)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return toPorts, nil
|
|
||||||
}
|
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
package iptables
|
package firewall
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"net/netip"
|
"net/netip"
|
||||||
@@ -23,7 +23,7 @@ func Test_parseIptablesInstruction(t *testing.T) {
|
|||||||
"uneven_fields": {
|
"uneven_fields": {
|
||||||
s: "-A",
|
s: "-A",
|
||||||
errWrapped: ErrIptablesCommandMalformed,
|
errWrapped: ErrIptablesCommandMalformed,
|
||||||
errMessage: "parsing \"-A\": iptables command is malformed: flag \"-A\" requires a value, but got none",
|
errMessage: "iptables command is malformed: fields count 1 is not even: \"-A\"",
|
||||||
},
|
},
|
||||||
"unknown_key": {
|
"unknown_key": {
|
||||||
s: "-x something",
|
s: "-x something",
|
||||||
@@ -35,7 +35,7 @@ func (c *Config) SetAllowedPort(ctx context.Context, port uint16, intf string) (
|
|||||||
c.logger.Info("setting allowed input port " + fmt.Sprint(port) + " through interface " + intf + "...")
|
c.logger.Info("setting allowed input port " + fmt.Sprint(port) + " through interface " + intf + "...")
|
||||||
|
|
||||||
const remove = false
|
const remove = false
|
||||||
if err := c.impl.AcceptInputToPort(ctx, intf, port, remove); err != nil {
|
if err := c.acceptInputToPort(ctx, intf, port, remove); err != nil {
|
||||||
return fmt.Errorf("allowing input to port %d through interface %s: %w",
|
return fmt.Errorf("allowing input to port %d through interface %s: %w",
|
||||||
port, intf, err)
|
port, intf, err)
|
||||||
}
|
}
|
||||||
@@ -68,7 +68,7 @@ func (c *Config) RemoveAllowedPort(ctx context.Context, port uint16) (err error)
|
|||||||
|
|
||||||
const remove = true
|
const remove = true
|
||||||
for netInterface := range interfacesSet {
|
for netInterface := range interfacesSet {
|
||||||
err := c.impl.AcceptInputToPort(ctx, netInterface, port, remove)
|
err := c.acceptInputToPort(ctx, netInterface, port, remove)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("removing allowed port %d on interface %s: %w",
|
return fmt.Errorf("removing allowed port %d on interface %s: %w",
|
||||||
port, netInterface, err)
|
port, netInterface, err)
|
||||||
|
|||||||
@@ -50,7 +50,7 @@ func (c *Config) RedirectPort(ctx context.Context, intf string, sourcePort,
|
|||||||
return nil
|
return nil
|
||||||
case conflict != nil:
|
case conflict != nil:
|
||||||
const remove = true
|
const remove = true
|
||||||
err = c.impl.RedirectPort(ctx, conflict.interfaceName, conflict.sourcePort,
|
err = c.redirectPort(ctx, conflict.interfaceName, conflict.sourcePort,
|
||||||
conflict.destinationPort, remove)
|
conflict.destinationPort, remove)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("removing conflicting redirection: %w", err)
|
return fmt.Errorf("removing conflicting redirection: %w", err)
|
||||||
@@ -60,7 +60,7 @@ func (c *Config) RedirectPort(ctx context.Context, intf string, sourcePort,
|
|||||||
}
|
}
|
||||||
|
|
||||||
const remove = false
|
const remove = false
|
||||||
err = c.impl.RedirectPort(ctx, intf, sourcePort, destinationPort, remove)
|
err = c.redirectPort(ctx, intf, sourcePort, destinationPort, remove)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("redirecting port: %w", err)
|
return fmt.Errorf("redirecting port: %w", err)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
package iptables
|
package firewall
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
@@ -14,7 +14,7 @@ var (
|
|||||||
ErrNetAdminMissing = errors.New("NET_ADMIN capability is missing")
|
ErrNetAdminMissing = errors.New("NET_ADMIN capability is missing")
|
||||||
ErrTestRuleCleanup = errors.New("failed cleaning up test rule")
|
ErrTestRuleCleanup = errors.New("failed cleaning up test rule")
|
||||||
ErrInputPolicyNotFound = errors.New("input policy not found")
|
ErrInputPolicyNotFound = errors.New("input policy not found")
|
||||||
ErrNotSupported = errors.New("no iptables supported found")
|
ErrIPTablesNotSupported = errors.New("no iptables supported found")
|
||||||
)
|
)
|
||||||
|
|
||||||
func checkIptablesSupport(ctx context.Context, runner CmdRunner,
|
func checkIptablesSupport(ctx context.Context, runner CmdRunner,
|
||||||
@@ -57,7 +57,7 @@ func checkIptablesSupport(ctx context.Context, runner CmdRunner,
|
|||||||
}
|
}
|
||||||
|
|
||||||
return "", fmt.Errorf("%w: errors encountered are: %s",
|
return "", fmt.Errorf("%w: errors encountered are: %s",
|
||||||
ErrNotSupported, strings.Join(allUnsupportedMessages, "; "))
|
ErrIPTablesNotSupported, strings.Join(allUnsupportedMessages, "; "))
|
||||||
}
|
}
|
||||||
|
|
||||||
func testIptablesPath(ctx context.Context, path string,
|
func testIptablesPath(ctx context.Context, path string,
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
package iptables
|
package firewall
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
@@ -101,7 +101,7 @@ func Test_checkIptablesSupport(t *testing.T) {
|
|||||||
return runner
|
return runner
|
||||||
},
|
},
|
||||||
iptablesPathsToTry: []string{"path1", "path2"},
|
iptablesPathsToTry: []string{"path1", "path2"},
|
||||||
errSentinel: ErrNotSupported,
|
errSentinel: ErrIPTablesNotSupported,
|
||||||
errMessage: "no iptables supported found: " +
|
errMessage: "no iptables supported found: " +
|
||||||
"errors encountered are: " +
|
"errors encountered are: " +
|
||||||
"path1: output 1 (exit code 4); " +
|
"path1: output 1 (exit code 4); " +
|
||||||
@@ -28,7 +28,7 @@ func (c *Config) SetVPNConnection(ctx context.Context,
|
|||||||
remove := true
|
remove := true
|
||||||
if c.vpnConnection.IP.IsValid() {
|
if c.vpnConnection.IP.IsValid() {
|
||||||
for _, defaultRoute := range c.defaultRoutes {
|
for _, defaultRoute := range c.defaultRoutes {
|
||||||
if err := c.impl.AcceptOutputTrafficToVPN(ctx, defaultRoute.NetInterface, c.vpnConnection, remove); err != nil {
|
if err := c.acceptOutputTrafficToVPN(ctx, defaultRoute.NetInterface, c.vpnConnection, remove); err != nil {
|
||||||
c.logger.Error("cannot remove outdated VPN connection rule: " + err.Error())
|
c.logger.Error("cannot remove outdated VPN connection rule: " + err.Error())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -36,7 +36,7 @@ func (c *Config) SetVPNConnection(ctx context.Context,
|
|||||||
c.vpnConnection = models.Connection{}
|
c.vpnConnection = models.Connection{}
|
||||||
|
|
||||||
if c.vpnIntf != "" {
|
if c.vpnIntf != "" {
|
||||||
if err = c.impl.AcceptOutputThroughInterface(ctx, c.vpnIntf, remove); err != nil {
|
if err = c.acceptOutputThroughInterface(ctx, c.vpnIntf, remove); err != nil {
|
||||||
c.logger.Error("cannot remove outdated VPN interface rule: " + err.Error())
|
c.logger.Error("cannot remove outdated VPN interface rule: " + err.Error())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -45,13 +45,13 @@ func (c *Config) SetVPNConnection(ctx context.Context,
|
|||||||
remove = false
|
remove = false
|
||||||
|
|
||||||
for _, defaultRoute := range c.defaultRoutes {
|
for _, defaultRoute := range c.defaultRoutes {
|
||||||
if err := c.impl.AcceptOutputTrafficToVPN(ctx, defaultRoute.NetInterface, connection, remove); err != nil {
|
if err := c.acceptOutputTrafficToVPN(ctx, defaultRoute.NetInterface, connection, remove); err != nil {
|
||||||
return fmt.Errorf("allowing output traffic through VPN connection: %w", err)
|
return fmt.Errorf("allowing output traffic through VPN connection: %w", err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
c.vpnConnection = connection
|
c.vpnConnection = connection
|
||||||
|
|
||||||
if err = c.impl.AcceptOutputThroughInterface(ctx, vpnIntf, remove); err != nil {
|
if err = c.acceptOutputThroughInterface(ctx, vpnIntf, remove); err != nil {
|
||||||
return fmt.Errorf("accepting output traffic through interface %s: %w", vpnIntf, err)
|
return fmt.Errorf("accepting output traffic through interface %s: %w", vpnIntf, err)
|
||||||
}
|
}
|
||||||
c.vpnIntf = vpnIntf
|
c.vpnIntf = vpnIntf
|
||||||
|
|||||||
@@ -1,21 +0,0 @@
|
|||||||
package firewall
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"net/netip"
|
|
||||||
)
|
|
||||||
|
|
||||||
func (c *Config) Version(ctx context.Context) (version string, err error) {
|
|
||||||
return c.impl.Version(ctx)
|
|
||||||
}
|
|
||||||
|
|
||||||
// TempDropOutputTCPRST temporarily drops outgoing TCP RST packets to the specified address and port,
|
|
||||||
// for any TCP packets not marked with the excludeMark given.
|
|
||||||
// This is necessary for TCP path MTU discovery to work, as the kernel will try to terminate the connection
|
|
||||||
// by sending a TCP RST packet, although we want to handle the connection manually.
|
|
||||||
func (c *Config) TempDropOutputTCPRST(ctx context.Context,
|
|
||||||
src, dst netip.AddrPort, excludeMark int) (
|
|
||||||
revert func(ctx context.Context) error, err error,
|
|
||||||
) {
|
|
||||||
return c.impl.TempDropOutputTCPRST(ctx, src, dst, excludeMark)
|
|
||||||
}
|
|
||||||
@@ -8,7 +8,6 @@ import (
|
|||||||
"net"
|
"net"
|
||||||
"net/netip"
|
"net/netip"
|
||||||
"strings"
|
"strings"
|
||||||
"sync"
|
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/qdm12/gluetun/internal/healthcheck/dns"
|
"github.com/qdm12/gluetun/internal/healthcheck/dns"
|
||||||
@@ -24,7 +23,6 @@ type Checker struct {
|
|||||||
icmpTargetIPs []netip.Addr
|
icmpTargetIPs []netip.Addr
|
||||||
smallCheckType string
|
smallCheckType string
|
||||||
startupOnFail bool
|
startupOnFail bool
|
||||||
configMutex sync.Mutex
|
|
||||||
|
|
||||||
icmpNotPermitted *bool
|
icmpNotPermitted *bool
|
||||||
|
|
||||||
@@ -55,8 +53,6 @@ func NewChecker(logger Logger) *Checker {
|
|||||||
func (c *Checker) SetConfig(tlsDialAddrs []string, icmpTargets []netip.Addr,
|
func (c *Checker) SetConfig(tlsDialAddrs []string, icmpTargets []netip.Addr,
|
||||||
smallCheckType string, startupOnFail bool,
|
smallCheckType string, startupOnFail bool,
|
||||||
) {
|
) {
|
||||||
c.configMutex.Lock()
|
|
||||||
defer c.configMutex.Unlock()
|
|
||||||
c.tlsDialAddrs = tlsDialAddrs
|
c.tlsDialAddrs = tlsDialAddrs
|
||||||
c.icmpTargetIPs = icmpTargets
|
c.icmpTargetIPs = icmpTargets
|
||||||
c.smallCheckType = smallCheckType
|
c.smallCheckType = smallCheckType
|
||||||
@@ -166,10 +162,8 @@ func (c *Checker) Stop() error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (c *Checker) smallPeriodicCheck(ctx context.Context) error {
|
func (c *Checker) smallPeriodicCheck(ctx context.Context) error {
|
||||||
c.configMutex.Lock()
|
|
||||||
icmpTargetIPs := make([]netip.Addr, len(c.icmpTargetIPs))
|
icmpTargetIPs := make([]netip.Addr, len(c.icmpTargetIPs))
|
||||||
copy(icmpTargetIPs, c.icmpTargetIPs)
|
copy(icmpTargetIPs, c.icmpTargetIPs)
|
||||||
c.configMutex.Unlock()
|
|
||||||
tryTimeouts := []time.Duration{
|
tryTimeouts := []time.Duration{
|
||||||
5 * time.Second,
|
5 * time.Second,
|
||||||
5 * time.Second,
|
5 * time.Second,
|
||||||
|
|||||||
@@ -1,5 +1,3 @@
|
|||||||
//go:build !windows
|
|
||||||
|
|
||||||
package mod
|
package mod
|
||||||
|
|
||||||
import (
|
import (
|
||||||
@@ -1,7 +0,0 @@
|
|||||||
//go:build !linux
|
|
||||||
|
|
||||||
package mod
|
|
||||||
|
|
||||||
func Probe(moduleName string) error {
|
|
||||||
panic("not implemented")
|
|
||||||
}
|
|
||||||
+17
-59
@@ -1,75 +1,33 @@
|
|||||||
|
//go:build linux || darwin
|
||||||
|
|
||||||
package netlink
|
package netlink
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"github.com/vishvananda/netlink"
|
||||||
"net"
|
|
||||||
"net/netip"
|
|
||||||
|
|
||||||
"github.com/jsimonetti/rtnetlink/rtnl"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func (n *NetLink) AddrList(linkIndex uint32, family uint8) (
|
func (n *NetLink) AddrList(link Link, family int) (
|
||||||
ipPrefixes []netip.Prefix, err error,
|
addresses []Addr, err error,
|
||||||
) {
|
) {
|
||||||
conn, err := rtnl.Dial(nil)
|
netlinkLink := linkToNetlinkLink(&link)
|
||||||
|
netlinkAddresses, err := netlink.AddrList(netlinkLink, family)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("dialing netlink: %w", err)
|
return nil, err
|
||||||
}
|
|
||||||
defer conn.Close()
|
|
||||||
|
|
||||||
ifc := &net.Interface{
|
|
||||||
Index: int(linkIndex),
|
|
||||||
}
|
|
||||||
ipNets, err := conn.Addrs(ifc, int(family))
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("failed to list addresses: %w", err)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
ipPrefixes = make([]netip.Prefix, len(ipNets))
|
addresses = make([]Addr, len(netlinkAddresses))
|
||||||
for i := range ipNets {
|
for i := range netlinkAddresses {
|
||||||
ipPrefixes[i] = netIPNetToNetipPrefix(ipNets[i])
|
addresses[i].Network = netIPNetToNetipPrefix(netlinkAddresses[i].IPNet)
|
||||||
}
|
}
|
||||||
|
|
||||||
return ipPrefixes, nil
|
return addresses, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (n *NetLink) AddrReplace(linkIndex uint32, prefix netip.Prefix) error {
|
func (n *NetLink) AddrReplace(link Link, addr Addr) error {
|
||||||
conn, err := rtnl.Dial(nil)
|
netlinkLink := linkToNetlinkLink(&link)
|
||||||
if err != nil {
|
netlinkAddress := netlink.Addr{
|
||||||
return fmt.Errorf("dialing netlink: %w", err)
|
IPNet: netipPrefixToIPNet(addr.Network),
|
||||||
}
|
|
||||||
defer conn.Close()
|
|
||||||
|
|
||||||
ipNet := netipPrefixToIPNet(prefix)
|
|
||||||
|
|
||||||
// Remove any address identical to the one we want to add
|
|
||||||
family := FamilyV4
|
|
||||||
if prefix.Addr().Is6() {
|
|
||||||
family = FamilyV6
|
|
||||||
}
|
|
||||||
ifc := &net.Interface{
|
|
||||||
Index: int(linkIndex),
|
|
||||||
}
|
|
||||||
addresses, err := conn.Addrs(ifc, int(family))
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("listing addresses: %w", err)
|
|
||||||
}
|
|
||||||
for _, address := range addresses {
|
|
||||||
if address.IP.Equal(ipNet.IP) &&
|
|
||||||
net.IP(address.Mask).String() == net.IP(ipNet.Mask).String() {
|
|
||||||
err = conn.AddrDel(ifc, address)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("deleting address from interface: %w", err)
|
|
||||||
}
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Add the new address to the interface
|
return netlink.AddrReplace(netlinkLink, &netlinkAddress)
|
||||||
err = conn.AddrAdd(ifc, ipNet)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("adding address to interface: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,13 @@
|
|||||||
|
//go:build !linux && !darwin
|
||||||
|
|
||||||
|
package netlink
|
||||||
|
|
||||||
|
func (n *NetLink) AddrList(link Link, family int) (
|
||||||
|
addresses []Addr, err error,
|
||||||
|
) {
|
||||||
|
panic("not implemented")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (n *NetLink) AddrReplace(Link, Addr) error {
|
||||||
|
panic("not implemented")
|
||||||
|
}
|
||||||
@@ -1,38 +0,0 @@
|
|||||||
package netlink
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
|
|
||||||
"github.com/mdlayher/netlink"
|
|
||||||
"github.com/ti-mo/netfilter"
|
|
||||||
)
|
|
||||||
|
|
||||||
func (n *NetLink) FlushConntrack() error {
|
|
||||||
conn, err := netfilter.Dial(nil)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("dialing netfilter: %w", err)
|
|
||||||
}
|
|
||||||
defer conn.Close()
|
|
||||||
|
|
||||||
families := [...]netfilter.ProtoFamily{netfilter.ProtoIPv4, netfilter.ProtoIPv6}
|
|
||||||
for _, family := range families {
|
|
||||||
const IPCtnlMsgCtDelete = 2
|
|
||||||
request, err := netfilter.MarshalNetlink(
|
|
||||||
netfilter.Header{
|
|
||||||
SubsystemID: netfilter.NFSubsysCTNetlink,
|
|
||||||
MessageType: netfilter.MessageType(IPCtnlMsgCtDelete),
|
|
||||||
Family: family,
|
|
||||||
Flags: netlink.Request | netlink.Acknowledge,
|
|
||||||
},
|
|
||||||
nil)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("encoding netlink request: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
_, err = conn.Query(request)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("querying netlink request: %w", err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
@@ -36,30 +36,6 @@ func netIPNetToNetipPrefix(ipNet *net.IPNet) (prefix netip.Prefix) {
|
|||||||
return netip.PrefixFrom(ip, bits)
|
return netip.PrefixFrom(ip, bits)
|
||||||
}
|
}
|
||||||
|
|
||||||
func ipAndLengthToPrefix(ip *net.IP, length uint8) netip.Prefix {
|
|
||||||
if ip == nil || len(*ip) == 0 {
|
|
||||||
return netip.Prefix{}
|
|
||||||
}
|
|
||||||
var dstIP netip.Addr
|
|
||||||
if ipv4 := ip.To4(); ipv4 != nil { // IPv6
|
|
||||||
dstIP = netip.AddrFrom4([4]byte(*ip))
|
|
||||||
} else {
|
|
||||||
dstIP = netip.AddrFrom16([16]byte(*ip))
|
|
||||||
}
|
|
||||||
return netip.PrefixFrom(dstIP, int(length))
|
|
||||||
}
|
|
||||||
|
|
||||||
func prefixToIPAndLength(prefix netip.Prefix) (ip *net.IP, length uint8) {
|
|
||||||
if !prefix.IsValid() {
|
|
||||||
return nil, 0
|
|
||||||
}
|
|
||||||
prefixIP := prefix.Addr().Unmap()
|
|
||||||
ip = new(net.IP)
|
|
||||||
*ip = netipAddrToNetIP(prefixIP)
|
|
||||||
length = uint8(prefix.Bits()) //nolint:gosec
|
|
||||||
return ip, length
|
|
||||||
}
|
|
||||||
|
|
||||||
func netipAddrToNetIP(address netip.Addr) (ip net.IP) {
|
func netipAddrToNetIP(address netip.Addr) (ip net.IP) {
|
||||||
switch {
|
switch {
|
||||||
case !address.IsValid():
|
case !address.IsValid():
|
||||||
|
|||||||
@@ -4,7 +4,13 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
)
|
)
|
||||||
|
|
||||||
func FamilyToString(family uint8) string {
|
const (
|
||||||
|
FamilyAll = 0
|
||||||
|
FamilyV4 = 2
|
||||||
|
FamilyV6 = 10
|
||||||
|
)
|
||||||
|
|
||||||
|
func FamilyToString(family int) string {
|
||||||
switch family {
|
switch family {
|
||||||
case FamilyAll:
|
case FamilyAll:
|
||||||
return "all"
|
return "all"
|
||||||
|
|||||||
@@ -1,9 +0,0 @@
|
|||||||
package netlink
|
|
||||||
|
|
||||||
import "golang.org/x/sys/unix"
|
|
||||||
|
|
||||||
const (
|
|
||||||
FamilyAll uint8 = unix.AF_UNSPEC
|
|
||||||
FamilyV4 uint8 = unix.AF_INET
|
|
||||||
FamilyV6 uint8 = unix.AF_INET6
|
|
||||||
)
|
|
||||||
@@ -1,30 +1,16 @@
|
|||||||
package netlink
|
package netlink
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"math/rand/v2"
|
|
||||||
"net/netip"
|
"net/netip"
|
||||||
|
|
||||||
"github.com/qdm12/log"
|
"github.com/qdm12/log"
|
||||||
)
|
)
|
||||||
|
|
||||||
func ptrTo[T any](v T) *T { return &v }
|
|
||||||
|
|
||||||
func makeNetipPrefix(n byte) netip.Prefix {
|
func makeNetipPrefix(n byte) netip.Prefix {
|
||||||
const bits = 24
|
const bits = 24
|
||||||
return netip.PrefixFrom(netip.AddrFrom4([4]byte{n, n, n, 0}), bits)
|
return netip.PrefixFrom(netip.AddrFrom4([4]byte{n, n, n, 0}), bits)
|
||||||
}
|
}
|
||||||
|
|
||||||
var rng = rand.New(rand.NewChaCha8([32]byte{})) //nolint:gosec,gochecknoglobals
|
|
||||||
|
|
||||||
func makeLinkName() string {
|
|
||||||
const alphabet = "abcdefghijklmnopqrstuvwxyz"
|
|
||||||
name := make([]byte, 8)
|
|
||||||
for i := range name {
|
|
||||||
name[i] = alphabet[rng.IntN(len(alphabet))]
|
|
||||||
}
|
|
||||||
return "test" + string(name)
|
|
||||||
}
|
|
||||||
|
|
||||||
type noopLogger struct{}
|
type noopLogger struct{}
|
||||||
|
|
||||||
func (l *noopLogger) Debug(_ string) {}
|
func (l *noopLogger) Debug(_ string) {}
|
||||||
|
|||||||
@@ -19,7 +19,7 @@ func (n *NetLink) IsIPv6Supported() (supported bool, err error) {
|
|||||||
return false, fmt.Errorf("finding link corresponding to route: %w", err)
|
return false, fmt.Errorf("finding link corresponding to route: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
sourceIsIPv6 := route.Src.Addr().IsValid() && route.Src.Addr().Is6()
|
sourceIsIPv6 := route.Src.IsValid() && route.Src.Is6()
|
||||||
destinationIsIPv6 := route.Dst.IsValid() && route.Dst.Addr().Is6()
|
destinationIsIPv6 := route.Dst.IsValid() && route.Dst.Addr().Is6()
|
||||||
switch {
|
switch {
|
||||||
case !sourceIsIPv6 && !destinationIsIPv6,
|
case !sourceIsIPv6 && !destinationIsIPv6,
|
||||||
|
|||||||
+69
-155
@@ -1,191 +1,105 @@
|
|||||||
|
//go:build linux || darwin
|
||||||
|
|
||||||
package netlink
|
package netlink
|
||||||
|
|
||||||
import (
|
import "github.com/vishvananda/netlink"
|
||||||
"errors"
|
|
||||||
"fmt"
|
|
||||||
|
|
||||||
"github.com/jsimonetti/rtnetlink"
|
|
||||||
)
|
|
||||||
|
|
||||||
type DeviceType uint16
|
|
||||||
|
|
||||||
type Link struct {
|
|
||||||
Index uint32
|
|
||||||
Name string
|
|
||||||
DeviceType DeviceType
|
|
||||||
VirtualType string
|
|
||||||
MTU uint32
|
|
||||||
}
|
|
||||||
|
|
||||||
func (n *NetLink) LinkList() (links []Link, err error) {
|
func (n *NetLink) LinkList() (links []Link, err error) {
|
||||||
conn, err := rtnetlink.Dial(nil)
|
netlinkLinks, err := netlink.LinkList()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("dialing netlink: %w", err)
|
return nil, err
|
||||||
}
|
|
||||||
defer conn.Close()
|
|
||||||
|
|
||||||
linkMessages, err := conn.Link.List()
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("listing interfaces: %w", err)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
links = make([]Link, len(linkMessages))
|
links = make([]Link, len(netlinkLinks))
|
||||||
for i, message := range linkMessages {
|
for i := range netlinkLinks {
|
||||||
virtualType := ""
|
links[i] = netlinkLinkToLink(netlinkLinks[i])
|
||||||
if message.Attributes.Info != nil {
|
|
||||||
virtualType = message.Attributes.Info.Kind
|
|
||||||
}
|
|
||||||
links[i] = Link{
|
|
||||||
Index: message.Index,
|
|
||||||
Name: message.Attributes.Name,
|
|
||||||
DeviceType: DeviceType(message.Type),
|
|
||||||
VirtualType: virtualType,
|
|
||||||
MTU: message.Attributes.MTU,
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return links, nil
|
return links, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
var ErrLinkNotFound = errors.New("link not found")
|
|
||||||
|
|
||||||
func (n *NetLink) LinkByName(name string) (link Link, err error) {
|
func (n *NetLink) LinkByName(name string) (link Link, err error) {
|
||||||
links, err := n.LinkList()
|
netlinkLink, err := netlink.LinkByName(name)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return Link{}, fmt.Errorf("listing links: %w", err)
|
return Link{}, err
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, link := range links {
|
return netlinkLinkToLink(netlinkLink), nil
|
||||||
if link.Name == name {
|
|
||||||
return link, nil
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return Link{}, fmt.Errorf("%w: for name %s", ErrLinkNotFound, name)
|
func (n *NetLink) LinkByIndex(index int) (link Link, err error) {
|
||||||
}
|
netlinkLink, err := netlink.LinkByIndex(index)
|
||||||
|
|
||||||
func (n *NetLink) LinkByIndex(index uint32) (link Link, err error) {
|
|
||||||
links, err := n.LinkList()
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return Link{}, fmt.Errorf("listing links: %w", err)
|
return Link{}, err
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, link = range links {
|
return netlinkLinkToLink(netlinkLink), nil
|
||||||
if link.Index == index {
|
|
||||||
return link, nil
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return Link{}, fmt.Errorf("%w: for index %d", ErrLinkNotFound, index)
|
func (n *NetLink) LinkAdd(link Link) (linkIndex int, err error) {
|
||||||
}
|
netlinkLink := linkToNetlinkLink(&link)
|
||||||
|
err = netlink.LinkAdd(netlinkLink)
|
||||||
func (n *NetLink) LinkAdd(link Link) (linkIndex uint32, err error) {
|
|
||||||
conn, err := rtnetlink.Dial(nil)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return 0, fmt.Errorf("dialing netlink: %w", err)
|
return 0, err
|
||||||
}
|
|
||||||
defer conn.Close()
|
|
||||||
|
|
||||||
tx := &rtnetlink.LinkMessage{
|
|
||||||
Type: uint16(link.DeviceType),
|
|
||||||
Attributes: &rtnetlink.LinkAttributes{
|
|
||||||
MTU: link.MTU,
|
|
||||||
Name: link.Name,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
if link.VirtualType != "" {
|
|
||||||
tx.Attributes.Info = &rtnetlink.LinkInfo{
|
|
||||||
Kind: link.VirtualType,
|
|
||||||
}
|
}
|
||||||
|
return netlinkLink.Attrs().Index, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
err = conn.Link.New(tx)
|
func (n *NetLink) LinkDel(link Link) (err error) {
|
||||||
|
return netlink.LinkDel(linkToNetlinkLink(&link))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (n *NetLink) LinkSetUp(link Link) (linkIndex int, err error) {
|
||||||
|
netlinkLink := linkToNetlinkLink(&link)
|
||||||
|
err = netlink.LinkSetUp(netlinkLink)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return 0, fmt.Errorf("creating new link: %w", err)
|
return 0, err
|
||||||
|
}
|
||||||
|
return netlinkLink.Attrs().Index, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
linkMessages, err := conn.Link.List()
|
func (n *NetLink) LinkSetDown(link Link) (err error) {
|
||||||
if err != nil {
|
return netlink.LinkSetDown(linkToNetlinkLink(&link))
|
||||||
return 0, fmt.Errorf("listing links: %w", err)
|
|
||||||
}
|
}
|
||||||
for _, linkMessage := range linkMessages {
|
|
||||||
if linkMessage.Attributes.Name == link.Name {
|
type netlinkLinkImpl struct {
|
||||||
return linkMessage.Index, nil
|
attrs *netlink.LinkAttrs
|
||||||
|
linkType string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (n *netlinkLinkImpl) Attrs() *netlink.LinkAttrs {
|
||||||
|
return n.attrs
|
||||||
|
}
|
||||||
|
|
||||||
|
func (n *netlinkLinkImpl) Type() string {
|
||||||
|
return n.linkType
|
||||||
|
}
|
||||||
|
|
||||||
|
func netlinkLinkToLink(netlinkLink netlink.Link) Link {
|
||||||
|
attributes := netlinkLink.Attrs()
|
||||||
|
return Link{
|
||||||
|
Type: netlinkLink.Type(),
|
||||||
|
Name: attributes.Name,
|
||||||
|
Index: attributes.Index,
|
||||||
|
EncapType: attributes.EncapType,
|
||||||
|
MTU: uint16(attributes.MTU), //nolint:gosec
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return 0, fmt.Errorf("%w: matching name %s", ErrLinkNotFound, link.Name)
|
// Warning: we must return `netlink.Link` and not `netlinkLinkImpl`
|
||||||
}
|
// so that the vishvananda/netlink package can compare the returned
|
||||||
|
// value against an untyped nil.
|
||||||
func (n *NetLink) LinkDel(linkIndex uint32) (err error) {
|
func linkToNetlinkLink(link *Link) netlink.Link {
|
||||||
conn, err := rtnetlink.Dial(nil)
|
if link == nil {
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("dialing netlink: %w", err)
|
|
||||||
}
|
|
||||||
defer conn.Close()
|
|
||||||
|
|
||||||
return conn.Link.Delete(linkIndex)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (n *NetLink) LinkSetUp(linkIndex uint32) (err error) {
|
|
||||||
conn, err := rtnetlink.Dial(nil)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("dialing netlink: %w", err)
|
|
||||||
}
|
|
||||||
defer conn.Close()
|
|
||||||
|
|
||||||
rx, err := conn.Link.Get(linkIndex)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("getting link: %w", err)
|
|
||||||
}
|
|
||||||
tx := &rtnetlink.LinkMessage{
|
|
||||||
Type: rx.Type,
|
|
||||||
Index: linkIndex,
|
|
||||||
Flags: iffUp,
|
|
||||||
Change: iffUp,
|
|
||||||
}
|
|
||||||
return conn.Link.Set(tx)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (n *NetLink) LinkSetDown(linkIndex uint32) (err error) {
|
|
||||||
conn, err := rtnetlink.Dial(nil)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("dialing netlink: %w", err)
|
|
||||||
}
|
|
||||||
defer conn.Close()
|
|
||||||
|
|
||||||
linkInfo, err := conn.Link.Get(linkIndex)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("getting link: %w", err)
|
|
||||||
}
|
|
||||||
message := &rtnetlink.LinkMessage{
|
|
||||||
Type: linkInfo.Type,
|
|
||||||
Index: linkIndex,
|
|
||||||
Flags: 0,
|
|
||||||
Change: iffUp,
|
|
||||||
}
|
|
||||||
return conn.Link.Set(message)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (n *NetLink) LinkSetMTU(linkIndex, mtu uint32) error {
|
|
||||||
conn, err := rtnetlink.Dial(nil)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("dialing netlink: %w", err)
|
|
||||||
}
|
|
||||||
defer conn.Close()
|
|
||||||
|
|
||||||
message := &rtnetlink.LinkMessage{
|
|
||||||
Index: linkIndex,
|
|
||||||
Attributes: &rtnetlink.LinkAttributes{
|
|
||||||
MTU: mtu,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
err = conn.Link.Set(message)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("setting MTU to %d for link at index %d: %w",
|
|
||||||
mtu, linkIndex, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
return &netlinkLinkImpl{
|
||||||
|
linkType: link.Type,
|
||||||
|
attrs: &netlink.LinkAttrs{
|
||||||
|
Name: link.Name,
|
||||||
|
Index: link.Index,
|
||||||
|
EncapType: link.EncapType,
|
||||||
|
MTU: int(link.MTU),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,11 +0,0 @@
|
|||||||
package netlink
|
|
||||||
|
|
||||||
import "golang.org/x/sys/unix"
|
|
||||||
|
|
||||||
const (
|
|
||||||
DeviceTypeEthernet DeviceType = unix.ARPHRD_ETHER
|
|
||||||
DeviceTypeLoopback DeviceType = unix.ARPHRD_LOOPBACK
|
|
||||||
DeviceTypeNone DeviceType = unix.ARPHRD_NONE
|
|
||||||
|
|
||||||
iffUp = unix.IFF_UP
|
|
||||||
)
|
|
||||||
@@ -1,85 +0,0 @@
|
|||||||
//go:build linux
|
|
||||||
|
|
||||||
package netlink
|
|
||||||
|
|
||||||
import (
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
"github.com/stretchr/testify/assert"
|
|
||||||
"github.com/stretchr/testify/require"
|
|
||||||
)
|
|
||||||
|
|
||||||
func Test_NetLink_LinkList(t *testing.T) {
|
|
||||||
t.Parallel()
|
|
||||||
|
|
||||||
netlink := &NetLink{}
|
|
||||||
|
|
||||||
initialLinks, err := netlink.LinkList()
|
|
||||||
require.NoError(t, err)
|
|
||||||
require.NotEmpty(t, initialLinks)
|
|
||||||
|
|
||||||
loopbackFound := false
|
|
||||||
for _, link := range initialLinks {
|
|
||||||
if link.Name != "lo" {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
loopbackFound = true
|
|
||||||
assert.Equal(t, DeviceTypeLoopback, link.DeviceType)
|
|
||||||
break
|
|
||||||
}
|
|
||||||
assert.True(t, loopbackFound, "loopback interface not found")
|
|
||||||
|
|
||||||
testLink := Link{
|
|
||||||
Name: makeLinkName(),
|
|
||||||
// note if [Link.VirtualType] is set, [Link.DeviceType]
|
|
||||||
// is ignored and gets set to [DeviceTypeNone] in LinkAdd.
|
|
||||||
DeviceType: DeviceTypeNone,
|
|
||||||
VirtualType: "wireguard",
|
|
||||||
MTU: 1420,
|
|
||||||
}
|
|
||||||
index, err := netlink.LinkAdd(testLink)
|
|
||||||
require.NoError(t, err)
|
|
||||||
t.Cleanup(func() {
|
|
||||||
_ = netlink.LinkDel(index)
|
|
||||||
})
|
|
||||||
|
|
||||||
links, err := netlink.LinkList()
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
testLink.Index = index
|
|
||||||
for _, link := range links {
|
|
||||||
if link.Name != testLink.Name {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
assert.Equal(t, testLink, link)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
t.Errorf("created link %q not found", testLink.Name)
|
|
||||||
}
|
|
||||||
|
|
||||||
func Test_NetLink_LinkSetMTU(t *testing.T) {
|
|
||||||
t.Parallel()
|
|
||||||
|
|
||||||
netlink := &NetLink{}
|
|
||||||
|
|
||||||
testLink := Link{
|
|
||||||
Name: makeLinkName(),
|
|
||||||
DeviceType: DeviceTypeNone,
|
|
||||||
VirtualType: "wireguard",
|
|
||||||
MTU: 1420,
|
|
||||||
}
|
|
||||||
index, err := netlink.LinkAdd(testLink)
|
|
||||||
require.NoError(t, err)
|
|
||||||
t.Cleanup(func() {
|
|
||||||
_ = netlink.LinkDel(index)
|
|
||||||
})
|
|
||||||
testLink.Index = index
|
|
||||||
|
|
||||||
err = netlink.LinkSetMTU(index, 1500)
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
link, err := netlink.LinkByIndex(index)
|
|
||||||
require.NoError(t, err)
|
|
||||||
testLink.MTU = 1500
|
|
||||||
assert.Equal(t, testLink, link)
|
|
||||||
}
|
|
||||||
@@ -0,0 +1,31 @@
|
|||||||
|
//go:build !linux && !darwin
|
||||||
|
|
||||||
|
package netlink
|
||||||
|
|
||||||
|
func (n *NetLink) LinkList() (links []Link, err error) {
|
||||||
|
panic("not implemented")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (n *NetLink) LinkByName(name string) (link Link, err error) {
|
||||||
|
panic("not implemented")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (n *NetLink) LinkByIndex(index int) (link Link, err error) {
|
||||||
|
panic("not implemented")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (n *NetLink) LinkAdd(link Link) (linkIndex int, err error) {
|
||||||
|
panic("not implemented")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (n *NetLink) LinkDel(link Link) (err error) {
|
||||||
|
panic("not implemented")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (n *NetLink) LinkSetUp(link Link) (linkIndex int, err error) {
|
||||||
|
panic("not implemented")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (n *NetLink) LinkSetDown(link Link) (err error) {
|
||||||
|
panic("not implemented")
|
||||||
|
}
|
||||||
@@ -1,56 +0,0 @@
|
|||||||
//go:build !linux
|
|
||||||
|
|
||||||
package netlink
|
|
||||||
|
|
||||||
const (
|
|
||||||
// FamilyAll is a placeholder only and should not
|
|
||||||
// be used.
|
|
||||||
FamilyAll uint8 = iota
|
|
||||||
// FamilyV4 is a placeholder only and should not
|
|
||||||
// be used.
|
|
||||||
FamilyV4
|
|
||||||
// FamilyV6 is a placeholder only and should not
|
|
||||||
// be used.
|
|
||||||
FamilyV6
|
|
||||||
|
|
||||||
// DeviceTypeEthernet is a placeholder only and should not be used.
|
|
||||||
DeviceTypeEthernet DeviceType = 0
|
|
||||||
// DeviceTypeLoopback is a placeholder only and should not be used.
|
|
||||||
DeviceTypeLoopback DeviceType = 0
|
|
||||||
// DeviceTypeNone is a placeholder only and should not be used.
|
|
||||||
DeviceTypeNone DeviceType = 0
|
|
||||||
|
|
||||||
// iffUp is a placeholder only and should not be used.
|
|
||||||
iffUp = 0
|
|
||||||
|
|
||||||
// RouteTypeUnicast is a placeholder only and should not be used.
|
|
||||||
RouteTypeUnicast = 0
|
|
||||||
// ScopeUniverse is a placeholder only and should not be used.
|
|
||||||
ScopeUniverse = 0
|
|
||||||
// ProtoStatic is a placeholder only and should not be used.
|
|
||||||
ProtoStatic = 0
|
|
||||||
|
|
||||||
// FlagInvert is a placeholder only and should not be used.
|
|
||||||
FlagInvert = 0
|
|
||||||
// ActionToTable is a placeholder only and should not be used.
|
|
||||||
ActionToTable = 0
|
|
||||||
|
|
||||||
// rtTableCompat is a placeholder only and should not be used.
|
|
||||||
rtTableCompat = 0
|
|
||||||
)
|
|
||||||
|
|
||||||
func (n *NetLink) RuleList(family uint8) (rules []Rule, err error) {
|
|
||||||
panic("not implemented")
|
|
||||||
}
|
|
||||||
|
|
||||||
func (n *NetLink) RuleAdd(rule Rule) error {
|
|
||||||
panic("not implemented")
|
|
||||||
}
|
|
||||||
|
|
||||||
func (n *NetLink) RuleDel(rule Rule) error {
|
|
||||||
panic("not implemented")
|
|
||||||
}
|
|
||||||
|
|
||||||
func (n *NetLink) IsWireguardSupported() (bool, error) {
|
|
||||||
panic("not implemented")
|
|
||||||
}
|
|
||||||
+46
-114
@@ -1,137 +1,69 @@
|
|||||||
|
//go:build linux || darwin
|
||||||
|
|
||||||
package netlink
|
package netlink
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"github.com/vishvananda/netlink"
|
||||||
"net/netip"
|
|
||||||
|
|
||||||
"github.com/jsimonetti/rtnetlink"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
type Route struct {
|
func (n *NetLink) RouteList(family int) (routes []Route, err error) {
|
||||||
LinkIndex uint32
|
// We set the filter to netlink.RT_FILTER_TABLE so that
|
||||||
Dst netip.Prefix
|
// routes from all tables are listed, as long as the filter
|
||||||
Src netip.Prefix
|
// table is set to 0.
|
||||||
Gw netip.Addr
|
const filterMask = netlink.RT_FILTER_TABLE
|
||||||
Priority uint32
|
// The filter is not left to `nil` otherwise non-main tables
|
||||||
Family uint8
|
// are ignored.
|
||||||
Table uint32
|
filter := &netlink.Route{}
|
||||||
Type uint8
|
|
||||||
Scope uint8
|
|
||||||
Proto uint8
|
|
||||||
AdvMSS uint32
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *Route) fromMessage(message rtnetlink.RouteMessage) {
|
netlinkRoutes, err := netlink.RouteListFiltered(family, filter, filterMask)
|
||||||
table := uint32(message.Table)
|
|
||||||
if table == 0 || table == rtTableCompat {
|
|
||||||
table = message.Attributes.Table
|
|
||||||
}
|
|
||||||
r.LinkIndex = message.Attributes.OutIface
|
|
||||||
r.Dst = ipAndLengthToPrefix(&message.Attributes.Dst, message.DstLength)
|
|
||||||
r.Src = ipAndLengthToPrefix(&message.Attributes.Src, message.SrcLength)
|
|
||||||
r.Gw = netIPToNetipAddress(message.Attributes.Gateway)
|
|
||||||
r.Priority = message.Attributes.Priority
|
|
||||||
r.Family = message.Family
|
|
||||||
r.Table = table
|
|
||||||
r.Type = message.Type
|
|
||||||
r.Scope = message.Scope
|
|
||||||
r.Proto = message.Protocol
|
|
||||||
if metrics := message.Attributes.Metrics; metrics != nil {
|
|
||||||
r.AdvMSS = metrics.AdvMSS
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r Route) message() *rtnetlink.RouteMessage {
|
|
||||||
dst, dstLength := prefixToIPAndLength(r.Dst)
|
|
||||||
src, srcLength := prefixToIPAndLength(r.Src)
|
|
||||||
var table uint8
|
|
||||||
var extendedTable uint32
|
|
||||||
if r.Table <= uint32(^uint8(0)) {
|
|
||||||
table = uint8(r.Table)
|
|
||||||
} else {
|
|
||||||
table = rtTableCompat
|
|
||||||
extendedTable = r.Table
|
|
||||||
}
|
|
||||||
message := &rtnetlink.RouteMessage{
|
|
||||||
Family: r.Family,
|
|
||||||
DstLength: dstLength,
|
|
||||||
SrcLength: srcLength,
|
|
||||||
Table: table,
|
|
||||||
Type: r.Type,
|
|
||||||
Scope: r.Scope,
|
|
||||||
Protocol: r.Proto,
|
|
||||||
Attributes: rtnetlink.RouteAttributes{
|
|
||||||
OutIface: r.LinkIndex,
|
|
||||||
Gateway: netipAddrToNetIP(r.Gw),
|
|
||||||
Priority: r.Priority,
|
|
||||||
Table: extendedTable,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
if src != nil { // src is optional
|
|
||||||
message.Attributes.Src = *src
|
|
||||||
}
|
|
||||||
if dst != nil {
|
|
||||||
message.Attributes.Dst = *dst
|
|
||||||
}
|
|
||||||
if r.AdvMSS != 0 {
|
|
||||||
if message.Attributes.Metrics == nil {
|
|
||||||
message.Attributes.Metrics = &rtnetlink.RouteMetrics{}
|
|
||||||
}
|
|
||||||
message.Attributes.Metrics.AdvMSS = r.AdvMSS
|
|
||||||
}
|
|
||||||
return message
|
|
||||||
}
|
|
||||||
|
|
||||||
func (n *NetLink) RouteList(family uint8) (routes []Route, err error) {
|
|
||||||
conn, err := rtnetlink.Dial(nil)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("dialing netlink: %w", err)
|
return nil, err
|
||||||
}
|
|
||||||
defer conn.Close()
|
|
||||||
|
|
||||||
routeMessages, err := conn.Route.List()
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("listing interfaces: %w", err)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
routes = make([]Route, 0, len(routeMessages))
|
routes = make([]Route, len(netlinkRoutes))
|
||||||
for _, routeMessage := range routeMessages {
|
for i := range netlinkRoutes {
|
||||||
if family != FamilyAll && routeMessage.Family != family {
|
routes[i] = netlinkRouteToRoute(netlinkRoutes[i])
|
||||||
continue
|
|
||||||
}
|
|
||||||
var route Route
|
|
||||||
route.fromMessage(routeMessage)
|
|
||||||
routes = append(routes, route)
|
|
||||||
}
|
}
|
||||||
return routes, nil
|
return routes, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (n *NetLink) RouteAdd(route Route) error {
|
func (n *NetLink) RouteAdd(route Route) error {
|
||||||
conn, err := rtnetlink.Dial(nil)
|
netlinkRoute := routeToNetlinkRoute(route)
|
||||||
if err != nil {
|
return netlink.RouteAdd(&netlinkRoute)
|
||||||
return fmt.Errorf("dialing netlink: %w", err)
|
|
||||||
}
|
|
||||||
defer conn.Close()
|
|
||||||
|
|
||||||
return conn.Route.Add(route.message())
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (n *NetLink) RouteDel(route Route) error {
|
func (n *NetLink) RouteDel(route Route) error {
|
||||||
conn, err := rtnetlink.Dial(nil)
|
netlinkRoute := routeToNetlinkRoute(route)
|
||||||
if err != nil {
|
return netlink.RouteDel(&netlinkRoute)
|
||||||
return fmt.Errorf("dialing netlink: %w", err)
|
|
||||||
}
|
|
||||||
defer conn.Close()
|
|
||||||
|
|
||||||
return conn.Route.Delete(route.message())
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (n *NetLink) RouteReplace(route Route) error {
|
func (n *NetLink) RouteReplace(route Route) error {
|
||||||
conn, err := rtnetlink.Dial(nil)
|
netlinkRoute := routeToNetlinkRoute(route)
|
||||||
if err != nil {
|
return netlink.RouteReplace(&netlinkRoute)
|
||||||
return fmt.Errorf("dialing netlink: %w", err)
|
|
||||||
}
|
}
|
||||||
defer conn.Close()
|
|
||||||
|
|
||||||
return conn.Route.Replace(route.message())
|
func netlinkRouteToRoute(netlinkRoute netlink.Route) (route Route) {
|
||||||
|
return Route{
|
||||||
|
LinkIndex: netlinkRoute.LinkIndex,
|
||||||
|
Dst: netIPNetToNetipPrefix(netlinkRoute.Dst),
|
||||||
|
Src: netIPToNetipAddress(netlinkRoute.Src),
|
||||||
|
Gw: netIPToNetipAddress(netlinkRoute.Gw),
|
||||||
|
Priority: netlinkRoute.Priority,
|
||||||
|
Family: netlinkRoute.Family,
|
||||||
|
Table: netlinkRoute.Table,
|
||||||
|
Type: netlinkRoute.Type,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func routeToNetlinkRoute(route Route) (netlinkRoute netlink.Route) {
|
||||||
|
return netlink.Route{
|
||||||
|
LinkIndex: route.LinkIndex,
|
||||||
|
Dst: netipPrefixToIPNet(route.Dst),
|
||||||
|
Src: netipAddrToNetIP(route.Src),
|
||||||
|
Gw: netipAddrToNetIP(route.Gw),
|
||||||
|
Priority: route.Priority,
|
||||||
|
Family: route.Family,
|
||||||
|
Table: route.Table,
|
||||||
|
Type: route.Type,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,11 +0,0 @@
|
|||||||
package netlink
|
|
||||||
|
|
||||||
import "golang.org/x/sys/unix"
|
|
||||||
|
|
||||||
const (
|
|
||||||
RouteTypeUnicast = unix.RTN_UNICAST
|
|
||||||
ScopeUniverse = unix.RT_SCOPE_UNIVERSE
|
|
||||||
ProtoStatic = unix.RTPROT_STATIC
|
|
||||||
|
|
||||||
rtTableCompat = unix.RT_TABLE_COMPAT
|
|
||||||
)
|
|
||||||
@@ -0,0 +1,21 @@
|
|||||||
|
//go:build !linux && !darwin
|
||||||
|
|
||||||
|
package netlink
|
||||||
|
|
||||||
|
func (n *NetLink) RouteList(family int) (
|
||||||
|
routes []Route, err error,
|
||||||
|
) {
|
||||||
|
panic("not implemented")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (n *NetLink) RouteAdd(route Route) error {
|
||||||
|
panic("not implemented")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (n *NetLink) RouteDel(route Route) error {
|
||||||
|
panic("not implemented")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (n *NetLink) RouteReplace(route Route) error {
|
||||||
|
panic("not implemented")
|
||||||
|
}
|
||||||
+67
-72
@@ -1,96 +1,91 @@
|
|||||||
|
//go:build linux
|
||||||
|
|
||||||
package netlink
|
package netlink
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"net/netip"
|
|
||||||
|
|
||||||
"github.com/jsimonetti/rtnetlink"
|
"github.com/vishvananda/netlink"
|
||||||
)
|
)
|
||||||
|
|
||||||
type Rule struct {
|
func NewRule() Rule {
|
||||||
Priority *uint32
|
// defaults found from netlink.NewRule() for fields we use,
|
||||||
Family uint8
|
// the rest of the defaults is set when converting from a `Rule`
|
||||||
Table uint32
|
// to a `netlink.Rule`
|
||||||
Mark *uint32
|
return Rule{
|
||||||
Src netip.Prefix
|
Priority: -1,
|
||||||
Dst netip.Prefix
|
Mark: 0,
|
||||||
Flags uint32
|
}
|
||||||
Action uint8
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *Rule) fromMessage(message rtnetlink.RuleMessage) {
|
func (n *NetLink) RuleList(family int) (rules []Rule, err error) {
|
||||||
table := uint32(message.Table)
|
switch family {
|
||||||
if table == 0 || table == rtTableCompat {
|
case FamilyAll:
|
||||||
table = *message.Attributes.Table
|
n.debugLogger.Debug("ip -4 rule list")
|
||||||
|
n.debugLogger.Debug("ip -6 rule list")
|
||||||
|
case FamilyV4:
|
||||||
|
n.debugLogger.Debug("ip -4 rule list")
|
||||||
|
case FamilyV6:
|
||||||
|
n.debugLogger.Debug("ip -6 rule list")
|
||||||
}
|
}
|
||||||
r.Priority = message.Attributes.Priority
|
netlinkRules, err := netlink.RuleList(family)
|
||||||
r.Family = message.Family
|
if err != nil {
|
||||||
r.Table = table
|
return nil, err
|
||||||
r.Mark = message.Attributes.FwMark
|
|
||||||
r.Src = ipAndLengthToPrefix(message.Attributes.Src, message.SrcLength)
|
|
||||||
r.Dst = ipAndLengthToPrefix(message.Attributes.Dst, message.DstLength)
|
|
||||||
r.Flags = message.Flags
|
|
||||||
r.Action = message.Action
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r Rule) message() *rtnetlink.RuleMessage {
|
rules = make([]Rule, len(netlinkRules))
|
||||||
src, srcLength := prefixToIPAndLength(r.Src)
|
for i := range netlinkRules {
|
||||||
dst, dstLength := prefixToIPAndLength(r.Dst)
|
rules[i] = netlinkRuleToRule(netlinkRules[i])
|
||||||
|
}
|
||||||
message := &rtnetlink.RuleMessage{
|
return rules, nil
|
||||||
Family: r.Family,
|
|
||||||
SrcLength: srcLength,
|
|
||||||
DstLength: dstLength,
|
|
||||||
Flags: r.Flags,
|
|
||||||
Action: r.Action,
|
|
||||||
Attributes: &rtnetlink.RuleAttributes{
|
|
||||||
Priority: r.Priority,
|
|
||||||
FwMark: r.Mark,
|
|
||||||
Src: src,
|
|
||||||
Dst: dst,
|
|
||||||
},
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if r.Table <= uint32(^uint8(0)) {
|
func (n *NetLink) RuleAdd(rule Rule) error {
|
||||||
message.Table = uint8(r.Table)
|
n.debugLogger.Debug(ruleDbgMsg(true, rule))
|
||||||
} else {
|
netlinkRule := ruleToNetlinkRule(rule)
|
||||||
message.Table = rtTableCompat
|
return netlink.RuleAdd(&netlinkRule)
|
||||||
message.Attributes.Table = &r.Table
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return message
|
func (n *NetLink) RuleDel(rule Rule) error {
|
||||||
|
n.debugLogger.Debug(ruleDbgMsg(false, rule))
|
||||||
|
netlinkRule := ruleToNetlinkRule(rule)
|
||||||
|
return netlink.RuleDel(&netlinkRule)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r Rule) String() string {
|
func ruleToNetlinkRule(rule Rule) (netlinkRule netlink.Rule) {
|
||||||
from := "all"
|
netlinkRule = *netlink.NewRule()
|
||||||
if r.Src.IsValid() && !r.Src.Addr().IsUnspecified() {
|
netlinkRule.Priority = rule.Priority
|
||||||
from = r.Src.String()
|
netlinkRule.Family = rule.Family
|
||||||
|
netlinkRule.Table = rule.Table
|
||||||
|
netlinkRule.Mark = rule.Mark
|
||||||
|
netlinkRule.Src = netipPrefixToIPNet(rule.Src)
|
||||||
|
netlinkRule.Dst = netipPrefixToIPNet(rule.Dst)
|
||||||
|
netlinkRule.Invert = rule.Invert
|
||||||
|
return netlinkRule
|
||||||
}
|
}
|
||||||
|
|
||||||
to := "all"
|
func netlinkRuleToRule(netlinkRule netlink.Rule) (rule Rule) {
|
||||||
if r.Dst.IsValid() && !r.Dst.Addr().IsUnspecified() {
|
return Rule{
|
||||||
to = r.Dst.String()
|
Priority: netlinkRule.Priority,
|
||||||
|
Family: netlinkRule.Family,
|
||||||
|
Table: netlinkRule.Table,
|
||||||
|
Mark: netlinkRule.Mark,
|
||||||
|
Src: netIPNetToNetipPrefix(netlinkRule.Src),
|
||||||
|
Dst: netIPNetToNetipPrefix(netlinkRule.Dst),
|
||||||
|
Invert: netlinkRule.Invert,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
priority := ""
|
func ruleDbgMsg(add bool, rule Rule) (debugMessage string) {
|
||||||
if r.Priority != nil {
|
|
||||||
priority = fmt.Sprintf(" %d", *r.Priority)
|
|
||||||
}
|
|
||||||
|
|
||||||
return fmt.Sprintf("ip rule%s: from %s to %s table %d",
|
|
||||||
priority, from, to, r.Table)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r Rule) debugMessage(add bool) (debugMessage string) {
|
|
||||||
debugMessage = "ip"
|
debugMessage = "ip"
|
||||||
|
|
||||||
switch r.Family {
|
switch rule.Family {
|
||||||
case FamilyV4:
|
case FamilyV4:
|
||||||
debugMessage += " -f inet"
|
debugMessage += " -f inet"
|
||||||
case FamilyV6:
|
case FamilyV6:
|
||||||
debugMessage += " -f inet6"
|
debugMessage += " -f inet6"
|
||||||
default:
|
default:
|
||||||
debugMessage += " -f " + fmt.Sprint(r.Family)
|
debugMessage += " -f " + fmt.Sprint(rule.Family)
|
||||||
}
|
}
|
||||||
|
|
||||||
debugMessage += " rule"
|
debugMessage += " rule"
|
||||||
@@ -101,20 +96,20 @@ func (r Rule) debugMessage(add bool) (debugMessage string) {
|
|||||||
debugMessage += " del"
|
debugMessage += " del"
|
||||||
}
|
}
|
||||||
|
|
||||||
if r.Src.IsValid() {
|
if rule.Src.IsValid() {
|
||||||
debugMessage += " from " + r.Src.String()
|
debugMessage += " from " + rule.Src.String()
|
||||||
}
|
}
|
||||||
|
|
||||||
if r.Dst.IsValid() {
|
if rule.Dst.IsValid() {
|
||||||
debugMessage += " to " + r.Dst.String()
|
debugMessage += " to " + rule.Dst.String()
|
||||||
}
|
}
|
||||||
|
|
||||||
if r.Table != 0 {
|
if rule.Table != 0 {
|
||||||
debugMessage += " lookup " + fmt.Sprint(r.Table)
|
debugMessage += " lookup " + fmt.Sprint(rule.Table)
|
||||||
}
|
}
|
||||||
|
|
||||||
if r.Priority != nil {
|
if rule.Priority != -1 {
|
||||||
debugMessage += " pref " + fmt.Sprint(*r.Priority)
|
debugMessage += " pref " + fmt.Sprint(rule.Priority)
|
||||||
}
|
}
|
||||||
|
|
||||||
return debugMessage
|
return debugMessage
|
||||||
|
|||||||
@@ -1,69 +0,0 @@
|
|||||||
package netlink
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
|
|
||||||
"github.com/jsimonetti/rtnetlink"
|
|
||||||
"golang.org/x/sys/unix"
|
|
||||||
)
|
|
||||||
|
|
||||||
const (
|
|
||||||
FlagInvert = unix.FIB_RULE_INVERT
|
|
||||||
ActionToTable = unix.FR_ACT_TO_TBL
|
|
||||||
)
|
|
||||||
|
|
||||||
func (n *NetLink) RuleList(family uint8) (rules []Rule, err error) {
|
|
||||||
switch family {
|
|
||||||
case FamilyAll:
|
|
||||||
n.debugLogger.Debug("ip -4 rule list")
|
|
||||||
n.debugLogger.Debug("ip -6 rule list")
|
|
||||||
case FamilyV4:
|
|
||||||
n.debugLogger.Debug("ip -4 rule list")
|
|
||||||
case FamilyV6:
|
|
||||||
n.debugLogger.Debug("ip -6 rule list")
|
|
||||||
}
|
|
||||||
|
|
||||||
conn, err := rtnetlink.Dial(nil)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("dialing netlink: %w", err)
|
|
||||||
}
|
|
||||||
defer conn.Close()
|
|
||||||
|
|
||||||
ruleMessages, err := conn.Rule.List()
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
rules = make([]Rule, 0, len(ruleMessages))
|
|
||||||
for _, message := range ruleMessages {
|
|
||||||
if family != FamilyAll && family != message.Family {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
var rule Rule
|
|
||||||
rule.fromMessage(message)
|
|
||||||
rules = append(rules, rule)
|
|
||||||
}
|
|
||||||
return rules, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (n *NetLink) RuleAdd(rule Rule) error {
|
|
||||||
n.debugLogger.Debug(rule.debugMessage(true))
|
|
||||||
|
|
||||||
conn, err := rtnetlink.Dial(nil)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("dialing netlink: %w", err)
|
|
||||||
}
|
|
||||||
defer conn.Close()
|
|
||||||
return conn.Rule.Add(rule.message())
|
|
||||||
}
|
|
||||||
|
|
||||||
func (n *NetLink) RuleDel(rule Rule) error {
|
|
||||||
n.debugLogger.Debug(rule.debugMessage(false))
|
|
||||||
|
|
||||||
conn, err := rtnetlink.Dial(nil)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("dialing netlink: %w", err)
|
|
||||||
}
|
|
||||||
defer conn.Close()
|
|
||||||
return conn.Rule.Delete(rule.message())
|
|
||||||
}
|
|
||||||
@@ -6,7 +6,7 @@ import (
|
|||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
)
|
)
|
||||||
|
|
||||||
func Test_Rule_debugMessage(t *testing.T) {
|
func Test_ruleDbgMsg(t *testing.T) {
|
||||||
t.Parallel()
|
t.Parallel()
|
||||||
|
|
||||||
testCases := map[string]struct {
|
testCases := map[string]struct {
|
||||||
@@ -15,7 +15,7 @@ func Test_Rule_debugMessage(t *testing.T) {
|
|||||||
dbgMsg string
|
dbgMsg string
|
||||||
}{
|
}{
|
||||||
"default values": {
|
"default values": {
|
||||||
dbgMsg: "ip -f 0 rule del",
|
dbgMsg: "ip -f 0 rule del pref 0",
|
||||||
},
|
},
|
||||||
"add rule": {
|
"add rule": {
|
||||||
add: true,
|
add: true,
|
||||||
@@ -24,7 +24,7 @@ func Test_Rule_debugMessage(t *testing.T) {
|
|||||||
Src: makeNetipPrefix(1),
|
Src: makeNetipPrefix(1),
|
||||||
Dst: makeNetipPrefix(2),
|
Dst: makeNetipPrefix(2),
|
||||||
Table: 100,
|
Table: 100,
|
||||||
Priority: ptrTo(uint32(101)),
|
Priority: 101,
|
||||||
},
|
},
|
||||||
dbgMsg: "ip -f inet rule add from 1.1.1.0/24 to 2.2.2.0/24 lookup 100 pref 101",
|
dbgMsg: "ip -f inet rule add from 1.1.1.0/24 to 2.2.2.0/24 lookup 100 pref 101",
|
||||||
},
|
},
|
||||||
@@ -34,7 +34,7 @@ func Test_Rule_debugMessage(t *testing.T) {
|
|||||||
Src: makeNetipPrefix(1),
|
Src: makeNetipPrefix(1),
|
||||||
Dst: makeNetipPrefix(2),
|
Dst: makeNetipPrefix(2),
|
||||||
Table: 100,
|
Table: 100,
|
||||||
Priority: ptrTo(uint32(101)),
|
Priority: 101,
|
||||||
},
|
},
|
||||||
dbgMsg: "ip -f inet rule del from 1.1.1.0/24 to 2.2.2.0/24 lookup 100 pref 101",
|
dbgMsg: "ip -f inet rule del from 1.1.1.0/24 to 2.2.2.0/24 lookup 100 pref 101",
|
||||||
},
|
},
|
||||||
@@ -44,7 +44,7 @@ func Test_Rule_debugMessage(t *testing.T) {
|
|||||||
t.Run(name, func(t *testing.T) {
|
t.Run(name, func(t *testing.T) {
|
||||||
t.Parallel()
|
t.Parallel()
|
||||||
|
|
||||||
dbgMsg := testCase.rule.debugMessage(testCase.add)
|
dbgMsg := ruleDbgMsg(testCase.add, testCase.rule)
|
||||||
|
|
||||||
assert.Equal(t, testCase.dbgMsg, dbgMsg)
|
assert.Equal(t, testCase.dbgMsg, dbgMsg)
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -0,0 +1,19 @@
|
|||||||
|
//go:build !linux
|
||||||
|
|
||||||
|
package netlink
|
||||||
|
|
||||||
|
func NewRule() Rule {
|
||||||
|
return Rule{}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (n *NetLink) RuleList(family int) (rules []Rule, err error) {
|
||||||
|
panic("not implemented")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (n *NetLink) RuleAdd(rule Rule) error {
|
||||||
|
panic("not implemented")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (n *NetLink) RuleDel(rule Rule) error {
|
||||||
|
panic("not implemented")
|
||||||
|
}
|
||||||
@@ -0,0 +1,58 @@
|
|||||||
|
package netlink
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"net/netip"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Addr struct {
|
||||||
|
Network netip.Prefix
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a Addr) String() string {
|
||||||
|
return a.Network.String()
|
||||||
|
}
|
||||||
|
|
||||||
|
type Link struct {
|
||||||
|
Type string
|
||||||
|
Name string
|
||||||
|
Index int
|
||||||
|
EncapType string
|
||||||
|
MTU uint16
|
||||||
|
}
|
||||||
|
|
||||||
|
type Route struct {
|
||||||
|
LinkIndex int
|
||||||
|
Dst netip.Prefix
|
||||||
|
Src netip.Addr
|
||||||
|
Gw netip.Addr
|
||||||
|
Priority int
|
||||||
|
Family int
|
||||||
|
Table int
|
||||||
|
Type int
|
||||||
|
}
|
||||||
|
|
||||||
|
type Rule struct {
|
||||||
|
Priority int
|
||||||
|
Family int
|
||||||
|
Table int
|
||||||
|
Mark uint32
|
||||||
|
Src netip.Prefix
|
||||||
|
Dst netip.Prefix
|
||||||
|
Invert bool
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r Rule) String() string {
|
||||||
|
from := "all"
|
||||||
|
if r.Src.IsValid() && !r.Src.Addr().IsUnspecified() {
|
||||||
|
from = r.Src.String()
|
||||||
|
}
|
||||||
|
|
||||||
|
to := "all"
|
||||||
|
if r.Dst.IsValid() && !r.Dst.Addr().IsUnspecified() {
|
||||||
|
to = r.Dst.String()
|
||||||
|
}
|
||||||
|
|
||||||
|
return fmt.Sprintf("ip rule %d: from %s to %s table %d",
|
||||||
|
r.Priority, from, to, r.Table)
|
||||||
|
}
|
||||||
@@ -0,0 +1,37 @@
|
|||||||
|
//go:build linux
|
||||||
|
|
||||||
|
package netlink
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/qdm12/gluetun/internal/mod"
|
||||||
|
"github.com/vishvananda/netlink"
|
||||||
|
)
|
||||||
|
|
||||||
|
func (n *NetLink) IsWireguardSupported() bool {
|
||||||
|
// Check for Wireguard family without loading the wireguard module.
|
||||||
|
// Some kernels have the wireguard module built-in, and don't have a
|
||||||
|
// modules directory, such as WSL2 kernels.
|
||||||
|
ok := hasWireguardFamily()
|
||||||
|
if ok {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
// Try loading the wireguard module, since some systems do not load
|
||||||
|
// it after a boot. If this fails, wireguard is assumed to not be supported.
|
||||||
|
n.debugLogger.Debugf("wireguard family not found, trying to load wireguard kernel module")
|
||||||
|
err := mod.Probe("wireguard")
|
||||||
|
if err != nil {
|
||||||
|
n.debugLogger.Debugf("failed loading wireguard kernel module: %s", err)
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
n.debugLogger.Debugf("wireguard kernel module loaded successfully")
|
||||||
|
|
||||||
|
// Re-check if the Wireguard family is now available, after loading
|
||||||
|
// the wireguard kernel module.
|
||||||
|
return hasWireguardFamily()
|
||||||
|
}
|
||||||
|
|
||||||
|
func hasWireguardFamily() bool {
|
||||||
|
_, err := netlink.GenlFamilyGet("wireguard")
|
||||||
|
return err == nil
|
||||||
|
}
|
||||||
@@ -1,58 +0,0 @@
|
|||||||
package netlink
|
|
||||||
|
|
||||||
import (
|
|
||||||
"errors"
|
|
||||||
"fmt"
|
|
||||||
"os"
|
|
||||||
|
|
||||||
"github.com/mdlayher/genetlink"
|
|
||||||
"github.com/qdm12/gluetun/internal/mod"
|
|
||||||
)
|
|
||||||
|
|
||||||
func (n *NetLink) IsWireguardSupported() (ok bool, err error) {
|
|
||||||
// Check for Wireguard family without loading the wireguard module.
|
|
||||||
// Some kernels have the wireguard module built-in, and don't have a
|
|
||||||
// modules directory, such as WSL2 kernels.
|
|
||||||
ok, err = hasWireguardFamily()
|
|
||||||
if err != nil {
|
|
||||||
return false, fmt.Errorf("checking wireguard family: %w", err)
|
|
||||||
} else if ok {
|
|
||||||
return true, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Try loading the wireguard module, since some systems do not load
|
|
||||||
// it after a boot. If this fails, wireguard is assumed to not be supported.
|
|
||||||
n.debugLogger.Debugf("wireguard family not found, trying to load wireguard kernel module")
|
|
||||||
err = mod.Probe("wireguard")
|
|
||||||
if err != nil {
|
|
||||||
n.debugLogger.Debugf("failed loading wireguard kernel module: %s", err)
|
|
||||||
return false, nil
|
|
||||||
}
|
|
||||||
n.debugLogger.Debugf("wireguard kernel module loaded successfully")
|
|
||||||
|
|
||||||
// Re-check if the Wireguard family is now available, after loading
|
|
||||||
// the wireguard kernel module.
|
|
||||||
ok, err = hasWireguardFamily()
|
|
||||||
if err != nil {
|
|
||||||
return false, fmt.Errorf("checking wireguard family: %w", err)
|
|
||||||
}
|
|
||||||
return ok, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func hasWireguardFamily() (ok bool, err error) {
|
|
||||||
conn, err := genetlink.Dial(nil)
|
|
||||||
if err != nil {
|
|
||||||
return false, fmt.Errorf("dialing netlink: %w", err)
|
|
||||||
}
|
|
||||||
defer conn.Close()
|
|
||||||
|
|
||||||
_, err = conn.GetFamily("wireguard")
|
|
||||||
if err != nil {
|
|
||||||
if errors.Is(err, os.ErrNotExist) {
|
|
||||||
return false, nil
|
|
||||||
}
|
|
||||||
return false, fmt.Errorf("getting wireguard family: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
return true, nil
|
|
||||||
}
|
|
||||||
@@ -4,8 +4,6 @@ package netlink
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/stretchr/testify/require"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func Test_NetLink_IsWireguardSupported(t *testing.T) {
|
func Test_NetLink_IsWireguardSupported(t *testing.T) {
|
||||||
@@ -14,8 +12,7 @@ func Test_NetLink_IsWireguardSupported(t *testing.T) {
|
|||||||
netLink := &NetLink{
|
netLink := &NetLink{
|
||||||
debugLogger: &noopLogger{},
|
debugLogger: &noopLogger{},
|
||||||
}
|
}
|
||||||
ok, err := netLink.IsWireguardSupported()
|
ok := netLink.IsWireguardSupported()
|
||||||
require.NoError(t, err)
|
|
||||||
if ok { // cannot assert since this depends on kernel
|
if ok { // cannot assert since this depends on kernel
|
||||||
t.Log("wireguard is supported")
|
t.Log("wireguard is supported")
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
+1
-1
@@ -2,6 +2,6 @@
|
|||||||
|
|
||||||
package netlink
|
package netlink
|
||||||
|
|
||||||
func (n *NetLink) FlushConntrack() error {
|
func (n *NetLink) IsWireguardSupported() (ok bool, err error) {
|
||||||
panic("not implemented")
|
panic("not implemented")
|
||||||
}
|
}
|
||||||
@@ -55,6 +55,8 @@ func extractDataFromLines(lines []string) (
|
|||||||
func extractDataFromLine(line string) (
|
func extractDataFromLine(line string) (
|
||||||
ip netip.Addr, port uint16, protocol string, err error,
|
ip netip.Addr, port uint16, protocol string, err error,
|
||||||
) {
|
) {
|
||||||
|
line = strings.TrimSpace(line)
|
||||||
|
|
||||||
switch {
|
switch {
|
||||||
case strings.HasPrefix(line, "proto "):
|
case strings.HasPrefix(line, "proto "):
|
||||||
protocol, err = extractProto(line)
|
protocol, err = extractProto(line)
|
||||||
@@ -81,10 +83,7 @@ func extractDataFromLine(line string) (
|
|||||||
return ip, 0, "", nil
|
return ip, 0, "", nil
|
||||||
}
|
}
|
||||||
|
|
||||||
var (
|
var errProtoLineFieldsCount = errors.New("proto line has not 2 fields as expected")
|
||||||
errProtoLineFieldsCount = errors.New("proto line has not 2 fields as expected")
|
|
||||||
errProtocolNotSupported = errors.New("network protocol not supported")
|
|
||||||
)
|
|
||||||
|
|
||||||
func extractProto(line string) (protocol string, err error) {
|
func extractProto(line string) (protocol string, err error) {
|
||||||
fields := strings.Fields(line)
|
fields := strings.Fields(line)
|
||||||
@@ -92,13 +91,25 @@ func extractProto(line string) (protocol string, err error) {
|
|||||||
return "", fmt.Errorf("%w: %s", errProtoLineFieldsCount, line)
|
return "", fmt.Errorf("%w: %s", errProtoLineFieldsCount, line)
|
||||||
}
|
}
|
||||||
|
|
||||||
switch fields[1] {
|
return parseProto(fields[1])
|
||||||
case "tcp", "tcp4", "tcp6", "tcp-client", "udp", "udp4", "udp6":
|
|
||||||
default:
|
|
||||||
return "", fmt.Errorf("%w: %s", errProtocolNotSupported, fields[1])
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return fields[1], nil
|
var errProtocolNotSupported = errors.New("network protocol not supported")
|
||||||
|
|
||||||
|
func parseProto(field string) (protocol string, err error) {
|
||||||
|
switch field {
|
||||||
|
case "tcp", "tcp4", "tcp6", "tcp-client":
|
||||||
|
// tcp4, tcp6 can be assimilated as tcp since the IP version is
|
||||||
|
// determined by the remote IP address version.
|
||||||
|
// tcp-client is a synonym of tcp for OpenVPN 2.5+ acting in client mode.
|
||||||
|
return constants.TCP, nil
|
||||||
|
case "udp", "udp4", "udp6":
|
||||||
|
// udp4, udp6 can be assimilated as udp since the IP version is
|
||||||
|
// determined by the remote IP address version.
|
||||||
|
return constants.UDP, nil
|
||||||
|
default:
|
||||||
|
return "", fmt.Errorf("%w: %s", errProtocolNotSupported, field)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
var (
|
var (
|
||||||
@@ -136,11 +147,9 @@ func extractRemote(line string) (ip netip.Addr, port uint16,
|
|||||||
}
|
}
|
||||||
|
|
||||||
if n > 3 { //nolint:mnd
|
if n > 3 { //nolint:mnd
|
||||||
switch fields[3] {
|
protocol, err = parseProto(fields[3])
|
||||||
case "tcp", "udp":
|
if err != nil {
|
||||||
protocol = fields[3]
|
return netip.Addr{}, 0, "", fmt.Errorf("parsing protocol from remote line: %w", err)
|
||||||
default:
|
|
||||||
return netip.Addr{}, 0, "", fmt.Errorf("%w: %s", errProtocolNotSupported, fields[3])
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -62,6 +62,14 @@ func Test_extractDataFromLines(t *testing.T) {
|
|||||||
Protocol: constants.UDP,
|
Protocol: constants.UDP,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
"leading_whitespace": {
|
||||||
|
lines: []string{" proto tcp", "\tremote 1.2.3.4 443 tcp"},
|
||||||
|
connection: models.Connection{
|
||||||
|
IP: netip.AddrFrom4([4]byte{1, 2, 3, 4}),
|
||||||
|
Port: 443,
|
||||||
|
Protocol: constants.TCP,
|
||||||
|
},
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
for name, testCase := range testCases {
|
for name, testCase := range testCases {
|
||||||
@@ -105,7 +113,7 @@ func Test_extractDataFromLine(t *testing.T) {
|
|||||||
},
|
},
|
||||||
"tcp-client": {
|
"tcp-client": {
|
||||||
line: "proto tcp-client",
|
line: "proto tcp-client",
|
||||||
protocol: "tcp-client",
|
protocol: constants.TCP,
|
||||||
},
|
},
|
||||||
"extract remote error": {
|
"extract remote error": {
|
||||||
line: "remote bad",
|
line: "remote bad",
|
||||||
@@ -239,7 +247,7 @@ func Test_extractRemote(t *testing.T) {
|
|||||||
},
|
},
|
||||||
"invalid protocol": {
|
"invalid protocol": {
|
||||||
line: "remote 1.2.3.4 8000 bad",
|
line: "remote 1.2.3.4 8000 bad",
|
||||||
err: errors.New("network protocol not supported: bad"),
|
err: errors.New("parsing protocol from remote line: network protocol not supported: bad"),
|
||||||
},
|
},
|
||||||
"IP host and port and protocol": {
|
"IP host and port and protocol": {
|
||||||
line: "remote 1.2.3.4 8000 udp",
|
line: "remote 1.2.3.4 8000 udp",
|
||||||
|
|||||||
@@ -29,19 +29,16 @@ func (r *Runner) Run(ctx context.Context, errCh chan<- error, ready chan<- struc
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
streamCtx, streamCancel := context.WithCancel(context.Background())
|
|
||||||
streamDone := make(chan struct{})
|
streamDone := make(chan struct{})
|
||||||
go streamLines(streamCtx, streamDone, r.logger,
|
go streamLines(streamDone, r.logger,
|
||||||
stdoutLines, stderrLines, ready)
|
stdoutLines, stderrLines, ready)
|
||||||
|
|
||||||
select {
|
select {
|
||||||
case <-ctx.Done():
|
case <-ctx.Done():
|
||||||
<-waitError
|
<-waitError
|
||||||
streamCancel()
|
|
||||||
<-streamDone
|
<-streamDone
|
||||||
errCh <- ctx.Err()
|
errCh <- ctx.Err()
|
||||||
case err := <-waitError:
|
case err := <-waitError:
|
||||||
streamCancel()
|
|
||||||
<-streamDone
|
<-streamDone
|
||||||
errCh <- err
|
errCh <- err
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import (
|
|||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"os/exec"
|
"os/exec"
|
||||||
|
"syscall"
|
||||||
|
|
||||||
"github.com/qdm12/gluetun/internal/constants/openvpn"
|
"github.com/qdm12/gluetun/internal/constants/openvpn"
|
||||||
)
|
)
|
||||||
@@ -32,7 +33,7 @@ func start(ctx context.Context, starter CmdStarter, version string, flags []stri
|
|||||||
args := []string{"--config", configPath}
|
args := []string{"--config", configPath}
|
||||||
args = append(args, flags...)
|
args = append(args, flags...)
|
||||||
cmd := exec.CommandContext(ctx, bin, args...)
|
cmd := exec.CommandContext(ctx, bin, args...)
|
||||||
setCmdSysProcAttr(cmd)
|
cmd.SysProcAttr = &syscall.SysProcAttr{Setpgid: true}
|
||||||
|
|
||||||
return starter.Start(cmd)
|
return starter.Start(cmd)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,10 +0,0 @@
|
|||||||
package openvpn
|
|
||||||
|
|
||||||
import (
|
|
||||||
"os/exec"
|
|
||||||
"syscall"
|
|
||||||
)
|
|
||||||
|
|
||||||
func setCmdSysProcAttr(cmd *exec.Cmd) {
|
|
||||||
cmd.SysProcAttr = &syscall.SysProcAttr{Setpgid: true}
|
|
||||||
}
|
|
||||||
@@ -1,12 +0,0 @@
|
|||||||
//go:build !linux
|
|
||||||
|
|
||||||
package openvpn
|
|
||||||
|
|
||||||
import (
|
|
||||||
"os/exec"
|
|
||||||
"syscall"
|
|
||||||
)
|
|
||||||
|
|
||||||
func setCmdSysProcAttr(cmd *exec.Cmd) {
|
|
||||||
cmd.SysProcAttr = &syscall.SysProcAttr{}
|
|
||||||
}
|
|
||||||
@@ -1,26 +1,37 @@
|
|||||||
package openvpn
|
package openvpn
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
|
||||||
"strings"
|
"strings"
|
||||||
)
|
)
|
||||||
|
|
||||||
func streamLines(ctx context.Context, done chan<- struct{},
|
func streamLines(done chan<- struct{},
|
||||||
logger Logger, stdout, stderr <-chan string,
|
logger Logger, stdout, stderr <-chan string,
|
||||||
tunnelReady chan<- struct{},
|
tunnelReady chan<- struct{},
|
||||||
) {
|
) {
|
||||||
defer close(done)
|
defer close(done)
|
||||||
|
|
||||||
var line string
|
|
||||||
|
|
||||||
for {
|
for {
|
||||||
|
var line string
|
||||||
|
var ok bool
|
||||||
errLine := false
|
errLine := false
|
||||||
select {
|
select {
|
||||||
case <-ctx.Done():
|
case line, ok = <-stdout:
|
||||||
|
if ok {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
if stderr == nil {
|
||||||
return
|
return
|
||||||
case line = <-stdout:
|
}
|
||||||
case line = <-stderr:
|
stdout = nil
|
||||||
|
case line, ok = <-stderr:
|
||||||
|
if ok {
|
||||||
errLine = true
|
errLine = true
|
||||||
|
break
|
||||||
|
}
|
||||||
|
if stdout == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
stderr = nil
|
||||||
}
|
}
|
||||||
line, level := processLogLine(line)
|
line, level := processLogLine(line)
|
||||||
if line == "" {
|
if line == "" {
|
||||||
|
|||||||
@@ -1,24 +0,0 @@
|
|||||||
package constants
|
|
||||||
|
|
||||||
const (
|
|
||||||
MaxEthernetFrameSize uint32 = 1500
|
|
||||||
// MinIPv4MTU is defined according to
|
|
||||||
// https://en.wikipedia.org/wiki/Maximum_transmission_unit#MTUs_for_common_media
|
|
||||||
MinIPv4MTU uint32 = 68
|
|
||||||
MinIPv6MTU uint32 = 1280
|
|
||||||
|
|
||||||
IPv4HeaderLength uint32 = 20
|
|
||||||
IPv6HeaderLength uint32 = 40
|
|
||||||
UDPHeaderLength uint32 = 8
|
|
||||||
// BaseTCPHeaderLength is the TCP header length without options,
|
|
||||||
// which is the minimum TCP header length.
|
|
||||||
BaseTCPHeaderLength uint32 = 20
|
|
||||||
// MaxTCPHeaderLength is the TCP header length with the maximum options length of 40 bytes.
|
|
||||||
// Note this is a hard maximum because of the 4-bit data offset field in the TCP header (15x4=60).
|
|
||||||
MaxTCPHeaderLength uint32 = 60
|
|
||||||
WireguardHeaderLength uint32 = 32
|
|
||||||
OpenVPNHeaderMaxLength uint32 = 1 + // opcode
|
|
||||||
8 + // session id
|
|
||||||
4 + // packet id
|
|
||||||
28 // max possible auth tag/iv
|
|
||||||
)
|
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user