Compare commits

..

1 Commits

Author SHA1 Message Date
Quentin McGaw
e1c20595b1 fix(health): use TCP dialing instead of ping
- `HEALTH_TARGET_ADDRESS` to replace `HEALTH_ADDRESS_TO_PING`
- Remove `github.com/go-ping/ping` dependency
- Dial TCP the target address, appending `:443` if port is not set
2022-03-21 20:38:55 +00:00
278 changed files with 65542 additions and 84093 deletions

View File

@@ -1,2 +1,2 @@
FROM qmcgaw/godevcontainer FROM qmcgaw/godevcontainer
RUN apk add wireguard-tools htop RUN apk add wireguard-tools

View File

@@ -8,7 +8,7 @@
"vscode" "vscode"
], ],
"shutdownAction": "stopCompose", "shutdownAction": "stopCompose",
"postCreateCommand": "source ~/.windows.sh && go mod download && go mod tidy", "postCreateCommand": "~/.windows.sh && go mod download && go mod tidy",
"workspaceFolder": "/workspace", "workspaceFolder": "/workspace",
"extensions": [ "extensions": [
"golang.go", "golang.go",
@@ -25,7 +25,6 @@
"bajdzis.vscode-database", // Supports connections to mysql or postgres, over SSL, socked "bajdzis.vscode-database", // Supports connections to mysql or postgres, over SSL, socked
"IBM.output-colorizer", // Colorize your output/test logs "IBM.output-colorizer", // Colorize your output/test logs
"mohsen1.prettify-json", // Prettify JSON data "mohsen1.prettify-json", // Prettify JSON data
"github.copilot",
], ],
"settings": { "settings": {
"files.eol": "\n", "files.eol": "\n",

View File

@@ -3,6 +3,7 @@ version: "3.7"
services: services:
vscode: vscode:
build: . build: .
image: godevcontainer
devices: devices:
- /dev/net/tun:/dev/net/tun - /dev/net/tun:/dev/net/tun
volumes: volumes:
@@ -10,16 +11,16 @@ services:
# Docker socket to access Docker server # Docker socket to access Docker server
- /var/run/docker.sock:/var/run/docker.sock - /var/run/docker.sock:/var/run/docker.sock
# Docker configuration # Docker configuration
- ~/.docker:/root/.docker - ~/.docker:/root/.docker:z
# SSH directory for Linux, OSX and WSL # SSH directory for Linux, OSX and WSL
- ~/.ssh:/root/.ssh - ~/.ssh:/root/.ssh:z
# For Windows without WSL, a copy will be made # For Windows without WSL, a copy will be made
# from /tmp/.ssh to ~/.ssh to fix permissions # from /tmp/.ssh to ~/.ssh to fix permissions
#- ~/.ssh:/tmp/.ssh:ro #- ~/.ssh:/tmp/.ssh:ro
# Shell history persistence # Shell history persistence
- ~/.zsh_history:/root/.zsh_history - ~/.zsh_history:/root/.zsh_history:z
# Git config # Git config
- ~/.gitconfig:/root/.gitconfig - ~/.gitconfig:/root/.gitconfig:z
environment: environment:
- TZ= - TZ=
cap_add: cap_add:

View File

@@ -42,7 +42,7 @@ jobs:
env: env:
DOCKER_BUILDKIT: "1" DOCKER_BUILDKIT: "1"
steps: steps:
- uses: actions/checkout@v3 - uses: actions/checkout@v2.4.0
- uses: reviewdog/action-misspell@v1 - uses: reviewdog/action-misspell@v1
with: with:
@@ -89,7 +89,7 @@ jobs:
needs: [verify] needs: [verify]
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- uses: actions/checkout@v3 - uses: actions/checkout@v2.4.0
# extract metadata (tags, labels) for Docker # extract metadata (tags, labels) for Docker
# https://github.com/docker/metadata-action # https://github.com/docker/metadata-action
@@ -123,7 +123,7 @@ jobs:
run: echo "::set-output name=value::$(git rev-parse --short HEAD)" run: echo "::set-output name=value::$(git rev-parse --short HEAD)"
- name: Build and push final image - name: Build and push final image
uses: docker/build-push-action@v2.10.0 uses: docker/build-push-action@v2.8.0
with: with:
platforms: linux/amd64,linux/386,linux/arm64,linux/arm/v6,linux/arm/v7,linux/ppc64le platforms: linux/amd64,linux/386,linux/arm64,linux/arm/v6,linux/arm/v7,linux/ppc64le
labels: ${{ steps.meta.outputs.labels }} labels: ${{ steps.meta.outputs.labels }}

View File

@@ -1,25 +0,0 @@
name: "CodeQL"
on:
push:
branches: [ master ]
pull_request:
# The branches below must be a subset of the branches above
branches: [ master ]
schedule:
- cron: '44 9 * * 0'
jobs:
analyze:
runs-on: ubuntu-latest
permissions:
actions: read
contents: read
security-events: write
steps:
- uses: actions/checkout@v3
- uses: github/codeql-action/init@v2
with:
languages: go
- uses: github/codeql-action/autobuild@v2
- uses: github/codeql-action/analyze@v2

View File

@@ -21,7 +21,7 @@ jobs:
env: env:
DOCKER_BUILDKIT: "1" DOCKER_BUILDKIT: "1"
steps: steps:
- uses: actions/checkout@v3 - uses: actions/checkout@v2.4.0
- name: Build test image - name: Build test image
run: docker build --target test -t test-container . run: docker build --target test -t test-container .

View File

@@ -10,9 +10,9 @@ jobs:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- name: Checkout - name: Checkout
uses: actions/checkout@v3 uses: actions/checkout@v2.4.0
- name: Docker Hub Description - name: Docker Hub Description
uses: peter-evans/dockerhub-description@v3 uses: peter-evans/dockerhub-description@v2
with: with:
username: qmcgaw username: qmcgaw
password: ${{ secrets.DOCKERHUB_PASSWORD }} password: ${{ secrets.DOCKERHUB_PASSWORD }}

View File

@@ -21,7 +21,7 @@ jobs:
env: env:
DOCKER_BUILDKIT: "1" DOCKER_BUILDKIT: "1"
steps: steps:
- uses: actions/checkout@v3 - uses: actions/checkout@v2.4.0
- name: Linting - name: Linting
run: docker build --target lint . run: docker build --target lint .

View File

@@ -9,7 +9,7 @@ jobs:
labeler: labeler:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- uses: actions/checkout@v3 - uses: actions/checkout@v2.4.0
- uses: crazy-max/ghaction-github-labeler@v3 - uses: crazy-max/ghaction-github-labeler@v3
with: with:
yaml-file: .github/labels.yml yaml-file: .github/labels.yml

View File

@@ -124,7 +124,7 @@ ENV VPN_SERVICE_PROVIDER=pia \
LOG_LEVEL=info \ LOG_LEVEL=info \
# Health # Health
HEALTH_SERVER_ADDRESS=127.0.0.1:9999 \ HEALTH_SERVER_ADDRESS=127.0.0.1:9999 \
HEALTH_TARGET_ADDRESS=cloudflare.com:443 \ HEALTH_TARGET_ADDRESS=github.com:443 \
HEALTH_VPN_DURATION_INITIAL=6s \ HEALTH_VPN_DURATION_INITIAL=6s \
HEALTH_VPN_DURATION_ADDITION=5s \ HEALTH_VPN_DURATION_ADDITION=5s \
# DNS over TLS # DNS over TLS
@@ -181,7 +181,7 @@ EXPOSE 8000/tcp 8888/tcp 8388/tcp 8388/udp
HEALTHCHECK --interval=5s --timeout=5s --start-period=10s --retries=1 CMD /gluetun-entrypoint healthcheck HEALTHCHECK --interval=5s --timeout=5s --start-period=10s --retries=1 CMD /gluetun-entrypoint healthcheck
ARG TARGETPLATFORM ARG TARGETPLATFORM
RUN apk add --no-cache --update -l apk-tools && \ RUN apk add --no-cache --update -l apk-tools && \
apk add --no-cache --update -X "https://dl-cdn.alpinelinux.org/alpine/v3.12/main" openvpn==2.4.12-r0 && \ apk add --no-cache --update -X "https://dl-cdn.alpinelinux.org/alpine/v3.12/main" openvpn==2.4.11-r0 && \
mv /usr/sbin/openvpn /usr/sbin/openvpn2.4 && \ mv /usr/sbin/openvpn /usr/sbin/openvpn2.4 && \
apk del openvpn && \ apk del openvpn && \
apk add --no-cache --update openvpn ca-certificates iptables ip6tables unbound tzdata && \ apk add --no-cache --update openvpn ca-certificates iptables ip6tables unbound tzdata && \

View File

@@ -5,6 +5,7 @@ HideMyAss, IPVanish, IVPN, Mullvad, NordVPN, Perfect Privacy, Privado, Private I
ProtonVPN, PureVPN, Surfshark, TorGuard, VPNUnlimited, VyprVPN, WeVPN and Windscribe VPN servers ProtonVPN, PureVPN, Surfshark, TorGuard, VPNUnlimited, VyprVPN, WeVPN and Windscribe VPN servers
using Go, OpenVPN or Wireguard, iptables, DNS over TLS, ShadowSocks and an HTTP proxy* using Go, OpenVPN or Wireguard, iptables, DNS over TLS, ShadowSocks and an HTTP proxy*
**ANNOUNCEMENT**: Large settings refactor merged on 2022-06-01, please file issues if you find any problem!
![Title image](https://raw.githubusercontent.com/qdm12/gluetun/master/title.svg) ![Title image](https://raw.githubusercontent.com/qdm12/gluetun/master/title.svg)
@@ -101,8 +102,6 @@ services:
# line above must be uncommented to allow external containers to connect. See https://github.com/qdm12/gluetun/wiki/Connect-a-container-to-gluetun#external-container-to-gluetun # line above must be uncommented to allow external containers to connect. See https://github.com/qdm12/gluetun/wiki/Connect-a-container-to-gluetun#external-container-to-gluetun
cap_add: cap_add:
- NET_ADMIN - NET_ADMIN
devices:
- /dev/net/tun:/dev/net/tun
ports: ports:
- 8888:8888/tcp # HTTP proxy - 8888:8888/tcp # HTTP proxy
- 8388:8388/tcp # Shadowsocks - 8388:8388/tcp # Shadowsocks

View File

@@ -40,12 +40,12 @@ import (
"github.com/qdm12/gluetun/internal/updater" "github.com/qdm12/gluetun/internal/updater"
"github.com/qdm12/gluetun/internal/vpn" "github.com/qdm12/gluetun/internal/vpn"
"github.com/qdm12/golibs/command" "github.com/qdm12/golibs/command"
"github.com/qdm12/golibs/logging"
"github.com/qdm12/goshutdown" "github.com/qdm12/goshutdown"
"github.com/qdm12/goshutdown/goroutine" "github.com/qdm12/goshutdown/goroutine"
"github.com/qdm12/goshutdown/group" "github.com/qdm12/goshutdown/group"
"github.com/qdm12/goshutdown/order" "github.com/qdm12/goshutdown/order"
"github.com/qdm12/gosplash" "github.com/qdm12/gosplash"
"github.com/qdm12/log"
"github.com/qdm12/updated/pkg/dnscrypto" "github.com/qdm12/updated/pkg/dnscrypto"
) )
@@ -64,11 +64,12 @@ func main() {
} }
background := context.Background() background := context.Background()
signalCh := make(chan os.Signal, 1) signalCtx, stop := signal.NotifyContext(background, syscall.SIGINT, syscall.SIGTERM, os.Interrupt)
signal.Notify(signalCh, os.Interrupt, syscall.SIGTERM)
ctx, cancel := context.WithCancel(background) ctx, cancel := context.WithCancel(background)
logger := log.New(log.SetLevel(log.LevelInfo)) logger := logging.New(logging.Settings{
Level: logging.LevelInfo,
})
args := os.Args args := os.Args
tun := tun.New() tun := tun.New()
@@ -87,11 +88,13 @@ func main() {
}() }()
select { select {
case signal := <-signalCh: case <-signalCtx.Done():
stop()
fmt.Println("") fmt.Println("")
logger.Warn("Caught OS signal " + signal.String() + ", shutting down") logger.Warn("Caught OS signal, shutting down")
cancel() cancel()
case err := <-errorCh: case err := <-errorCh:
stop()
close(errorCh) close(errorCh)
if err == nil { // expected exit such as healthcheck if err == nil { // expected exit such as healthcheck
os.Exit(0) os.Exit(0)
@@ -110,8 +113,6 @@ func main() {
logger.Info("Shutdown successful") logger.Info("Shutdown successful")
case <-timer.C: case <-timer.C:
logger.Warn("Shutdown timed out") logger.Warn("Shutdown timed out")
case signal := <-signalCh:
logger.Warn("Caught OS signal " + signal.String() + ", forcing shut down")
} }
os.Exit(1) os.Exit(1)
@@ -123,7 +124,7 @@ var (
//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, source sources.Source, args []string, logger logging.ParentLogger, source sources.Source,
tun tun.Interface, netLinker netlink.NetLinker, cmder command.RunStarter, tun tun.Interface, netLinker netlink.NetLinker, cmder command.RunStarter,
cli cli.CLIer) error { cli cli.CLIer) error {
if len(args) > 1 { // cli operation if len(args) > 1 { // cli operation
@@ -173,15 +174,17 @@ func _main(ctx context.Context, buildInfo models.BuildInformation,
// - global log level is parsed from source // - global log level is parsed from source
// - firewall Debug and Enabled are booleans parsed from source // - firewall Debug and Enabled are booleans parsed from source
logger.Patch(log.SetLevel(*allSettings.Log.Level)) logger.PatchLevel(*allSettings.Log.Level)
routingLogger := logger.New(log.SetComponent("routing")) routingLogger := logger.NewChild(logging.Settings{
Prefix: "routing: ",
})
if *allSettings.Firewall.Debug { // To remove in v4 if *allSettings.Firewall.Debug { // To remove in v4
routingLogger.Patch(log.SetLevel(log.LevelDebug)) routingLogger.PatchLevel(logging.LevelDebug)
} }
routingConf := routing.New(netLinker, routingLogger) routingConf := routing.New(netLinker, routingLogger)
defaultRoutes, err := routingConf.DefaultRoutes() defaultInterface, defaultGateway, err := routingConf.DefaultRoute()
if err != nil { if err != nil {
return err return err
} }
@@ -191,16 +194,19 @@ func _main(ctx context.Context, buildInfo models.BuildInformation,
return err return err
} }
firewallLogger := logger.New(log.SetComponent("firewall")) defaultIP, err := routingConf.DefaultIP()
if *allSettings.Firewall.Debug { // To remove in v4
firewallLogger.Patch(log.SetLevel(log.LevelDebug))
}
firewallConf, err := firewall.NewConfig(ctx, firewallLogger, cmder,
defaultRoutes, localNetworks)
if err != nil { if err != nil {
return err return err
} }
firewallLogger := logger.NewChild(logging.Settings{
Prefix: "firewall: ",
})
if *allSettings.Firewall.Debug { // To remove in v4
firewallLogger.PatchLevel(logging.LevelDebug)
}
firewallConf := firewall.NewConfig(firewallLogger, cmder,
defaultInterface, defaultGateway, localNetworks, defaultIP)
if *allSettings.Firewall.Enabled { if *allSettings.Firewall.Enabled {
err = firewallConf.SetEnabled(ctx, true) err = firewallConf.SetEnabled(ctx, true)
if err != nil { if err != nil {
@@ -209,7 +215,7 @@ func _main(ctx context.Context, buildInfo models.BuildInformation,
} }
// 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
storageLogger := logger.New(log.SetComponent("storage")) storageLogger := logger.NewChild(logging.Settings{Prefix: "storage: "})
storage, err := storage.New(storageLogger, constants.ServersData) storage, err := storage.New(storageLogger, constants.ServersData)
if err != nil { if err != nil {
return err return err
@@ -222,7 +228,7 @@ func _main(ctx context.Context, buildInfo models.BuildInformation,
return err return err
} }
allSettings.Pprof.HTTPServer.Logger = logger.New(log.SetComponent("pprof")) allSettings.Pprof.HTTPServer.Logger = logger
pprofServer, err := pprof.New(allSettings.Pprof) pprofServer, err := pprof.New(allSettings.Pprof)
if err != nil { if err != nil {
return fmt.Errorf("cannot create Pprof server: %w", err) return fmt.Errorf("cannot create Pprof server: %w", err)
@@ -235,7 +241,7 @@ func _main(ctx context.Context, buildInfo models.BuildInformation,
// Create configurators // Create configurators
alpineConf := alpine.New() alpineConf := alpine.New()
ovpnConf := openvpn.New( ovpnConf := openvpn.New(
logger.New(log.SetComponent("openvpn configurator")), logger.NewChild(logging.Settings{Prefix: "openvpn configurator: "}),
cmder, puid, pgid) cmder, puid, pgid)
dnsCrypto := dnscrypto.New(httpClient, "", "") dnsCrypto := dnscrypto.New(httpClient, "", "")
const cacertsPath = "/etc/ssl/certs/ca-certificates.crt" const cacertsPath = "/etc/ssl/certs/ca-certificates.crt"
@@ -288,9 +294,9 @@ func _main(ctx context.Context, buildInfo models.BuildInformation,
return fmt.Errorf("cannot setup routing: %w", err) return fmt.Errorf("cannot setup routing: %w", err)
} }
defer func() { defer func() {
routingLogger.Info("routing cleanup...") logger.Info("routing cleanup...")
if err := routingConf.TearDown(); err != nil { if err := routingConf.TearDown(); err != nil {
routingLogger.Error("cannot teardown routing: " + err.Error()) logger.Error("cannot teardown routing: " + err.Error())
} }
}() }()
@@ -311,12 +317,10 @@ func _main(ctx context.Context, buildInfo models.BuildInformation,
} }
for _, port := range allSettings.Firewall.InputPorts { for _, port := range allSettings.Firewall.InputPorts {
for _, defaultRoute := range defaultRoutes { err = firewallConf.SetAllowedPort(ctx, port, defaultInterface)
err = firewallConf.SetAllowedPort(ctx, port, defaultRoute.NetInterface)
if err != nil { if err != nil {
return err return err
} }
}
} // TODO move inside firewall? } // TODO move inside firewall?
// Shutdown settings // Shutdown settings
@@ -342,14 +346,14 @@ func _main(ctx context.Context, buildInfo models.BuildInformation,
otherGroupHandler.Add(pprofHandler) otherGroupHandler.Add(pprofHandler)
<-pprofReady <-pprofReady
portForwardLogger := logger.New(log.SetComponent("port forwarding")) portForwardLogger := logger.NewChild(logging.Settings{Prefix: "port forwarding: "})
portForwardLooper := portforward.NewLoop(allSettings.VPN.Provider.PortForwarding, portForwardLooper := portforward.NewLoop(allSettings.VPN.Provider.PortForwarding,
httpClient, firewallConf, portForwardLogger) httpClient, firewallConf, portForwardLogger)
portForwardHandler, portForwardCtx, portForwardDone := goshutdown.NewGoRoutineHandler( portForwardHandler, portForwardCtx, portForwardDone := goshutdown.NewGoRoutineHandler(
"port forwarding", goroutine.OptionTimeout(time.Second)) "port forwarding", goroutine.OptionTimeout(time.Second))
go portForwardLooper.Run(portForwardCtx, portForwardDone) go portForwardLooper.Run(portForwardCtx, portForwardDone)
unboundLogger := logger.New(log.SetComponent("dns over tls")) unboundLogger := logger.NewChild(logging.Settings{Prefix: "dns over tls: "})
unboundLooper := dns.NewLoop(dnsConf, allSettings.DNS, httpClient, unboundLooper := dns.NewLoop(dnsConf, allSettings.DNS, httpClient,
unboundLogger) unboundLogger)
dnsHandler, dnsCtx, dnsDone := goshutdown.NewGoRoutineHandler( dnsHandler, dnsCtx, dnsDone := goshutdown.NewGoRoutineHandler(
@@ -364,7 +368,7 @@ func _main(ctx context.Context, buildInfo models.BuildInformation,
controlGroupHandler.Add(dnsTickerHandler) controlGroupHandler.Add(dnsTickerHandler)
publicIPLooper := publicip.NewLoop(httpClient, publicIPLooper := publicip.NewLoop(httpClient,
logger.New(log.SetComponent("ip getter")), logger.NewChild(logging.Settings{Prefix: "ip getter: "}),
allSettings.PublicIP, puid, pgid) allSettings.PublicIP, puid, pgid)
pubIPHandler, pubIPCtx, pubIPDone := goshutdown.NewGoRoutineHandler( pubIPHandler, pubIPCtx, pubIPDone := goshutdown.NewGoRoutineHandler(
"public IP", goroutine.OptionTimeout(defaultShutdownTimeout)) "public IP", goroutine.OptionTimeout(defaultShutdownTimeout))
@@ -376,7 +380,7 @@ func _main(ctx context.Context, buildInfo models.BuildInformation,
go publicIPLooper.RunRestartTicker(pubIPTickerCtx, pubIPTickerDone) go publicIPLooper.RunRestartTicker(pubIPTickerCtx, pubIPTickerDone)
tickersGroupHandler.Add(pubIPTickerHandler) tickersGroupHandler.Add(pubIPTickerHandler)
vpnLogger := logger.New(log.SetComponent("vpn")) vpnLogger := logger.NewChild(logging.Settings{Prefix: "vpn: "})
vpnLooper := vpn.NewLoop(allSettings.VPN, allSettings.Firewall.VPNInputPorts, vpnLooper := vpn.NewLoop(allSettings.VPN, allSettings.Firewall.VPNInputPorts,
allServers, ovpnConf, netLinker, firewallConf, routingConf, portForwardLooper, allServers, ovpnConf, netLinker, firewallConf, routingConf, portForwardLooper,
cmder, publicIPLooper, unboundLooper, vpnLogger, httpClient, cmder, publicIPLooper, unboundLooper, vpnLogger, httpClient,
@@ -387,7 +391,7 @@ func _main(ctx context.Context, buildInfo models.BuildInformation,
updaterLooper := updater.NewLooper(allSettings.Updater, updaterLooper := updater.NewLooper(allSettings.Updater,
allServers, storage, vpnLooper.SetServers, httpClient, allServers, storage, vpnLooper.SetServers, httpClient,
logger.New(log.SetComponent("updater"))) logger.NewChild(logging.Settings{Prefix: "updater: "}))
updaterHandler, updaterCtx, updaterDone := goshutdown.NewGoRoutineHandler( updaterHandler, updaterCtx, updaterDone := goshutdown.NewGoRoutineHandler(
"updater", goroutine.OptionTimeout(defaultShutdownTimeout)) "updater", goroutine.OptionTimeout(defaultShutdownTimeout))
// wait for updaterLooper.Restart() or its ticket launched with RunRestartTicker // wait for updaterLooper.Restart() or its ticket launched with RunRestartTicker
@@ -400,7 +404,7 @@ func _main(ctx context.Context, buildInfo models.BuildInformation,
controlGroupHandler.Add(updaterTickerHandler) controlGroupHandler.Add(updaterTickerHandler)
httpProxyLooper := httpproxy.NewLoop( httpProxyLooper := httpproxy.NewLoop(
logger.New(log.SetComponent("http proxy")), logger.NewChild(logging.Settings{Prefix: "http proxy: "}),
allSettings.HTTPProxy) allSettings.HTTPProxy)
httpProxyHandler, httpProxyCtx, httpProxyDone := goshutdown.NewGoRoutineHandler( httpProxyHandler, httpProxyCtx, httpProxyDone := goshutdown.NewGoRoutineHandler(
"http proxy", goroutine.OptionTimeout(defaultShutdownTimeout)) "http proxy", goroutine.OptionTimeout(defaultShutdownTimeout))
@@ -408,7 +412,7 @@ func _main(ctx context.Context, buildInfo models.BuildInformation,
otherGroupHandler.Add(httpProxyHandler) otherGroupHandler.Add(httpProxyHandler)
shadowsocksLooper := shadowsocks.NewLooper(allSettings.Shadowsocks, shadowsocksLooper := shadowsocks.NewLooper(allSettings.Shadowsocks,
logger.New(log.SetComponent("shadowsocks"))) logger.NewChild(logging.Settings{Prefix: "shadowsocks: "}))
shadowsocksHandler, shadowsocksCtx, shadowsocksDone := goshutdown.NewGoRoutineHandler( shadowsocksHandler, shadowsocksCtx, shadowsocksDone := goshutdown.NewGoRoutineHandler(
"shadowsocks proxy", goroutine.OptionTimeout(defaultShutdownTimeout)) "shadowsocks proxy", goroutine.OptionTimeout(defaultShutdownTimeout))
go shadowsocksLooper.Run(shadowsocksCtx, shadowsocksDone) go shadowsocksLooper.Run(shadowsocksCtx, shadowsocksDone)
@@ -418,18 +422,13 @@ func _main(ctx context.Context, buildInfo models.BuildInformation,
controlServerLogging := *allSettings.ControlServer.Log controlServerLogging := *allSettings.ControlServer.Log
httpServerHandler, httpServerCtx, httpServerDone := goshutdown.NewGoRoutineHandler( httpServerHandler, httpServerCtx, httpServerDone := goshutdown.NewGoRoutineHandler(
"http server", goroutine.OptionTimeout(defaultShutdownTimeout)) "http server", goroutine.OptionTimeout(defaultShutdownTimeout))
httpServer, err := server.New(httpServerCtx, controlServerAddress, controlServerLogging, httpServer := server.New(httpServerCtx, controlServerAddress, controlServerLogging,
logger.New(log.SetComponent("http server")), logger.NewChild(logging.Settings{Prefix: "http server: "}),
buildInfo, vpnLooper, portForwardLooper, unboundLooper, updaterLooper, publicIPLooper) buildInfo, vpnLooper, portForwardLooper, unboundLooper, updaterLooper, publicIPLooper)
if err != nil { go httpServer.Run(httpServerCtx, httpServerDone)
return fmt.Errorf("cannot setup control server: %w", err)
}
httpServerReady := make(chan struct{})
go httpServer.Run(httpServerCtx, httpServerReady, httpServerDone)
<-httpServerReady
controlGroupHandler.Add(httpServerHandler) controlGroupHandler.Add(httpServerHandler)
healthLogger := logger.New(log.SetComponent("healthcheck")) healthLogger := logger.NewChild(logging.Settings{Prefix: "healthcheck: "})
healthcheckServer := healthcheck.NewServer(allSettings.Health, healthLogger, vpnLooper) healthcheckServer := healthcheck.NewServer(allSettings.Health, healthLogger, vpnLooper)
healthServerHandler, healthServerCtx, healthServerDone := goshutdown.NewGoRoutineHandler( healthServerHandler, healthServerCtx, healthServerDone := goshutdown.NewGoRoutineHandler(
"HTTP health server", goroutine.OptionTimeout(defaultShutdownTimeout)) "HTTP health server", goroutine.OptionTimeout(defaultShutdownTimeout))

5
go.mod
View File

@@ -3,7 +3,7 @@ module github.com/qdm12/gluetun
go 1.17 go 1.17
require ( require (
github.com/breml/rootcerts v0.2.3 github.com/breml/rootcerts v0.2.2
github.com/fatih/color v1.13.0 github.com/fatih/color v1.13.0
github.com/golang/mock v1.6.0 github.com/golang/mock v1.6.0
github.com/qdm12/dns v1.11.0 github.com/qdm12/dns v1.11.0
@@ -12,10 +12,9 @@ require (
github.com/qdm12/gosplash v0.1.0 github.com/qdm12/gosplash v0.1.0
github.com/qdm12/gotree v0.2.0 github.com/qdm12/gotree v0.2.0
github.com/qdm12/govalid v0.1.0 github.com/qdm12/govalid v0.1.0
github.com/qdm12/log v0.1.0
github.com/qdm12/ss-server v0.4.0 github.com/qdm12/ss-server v0.4.0
github.com/qdm12/updated v0.0.0-20210603204757-205acfe6937e github.com/qdm12/updated v0.0.0-20210603204757-205acfe6937e
github.com/stretchr/testify v1.7.1 github.com/stretchr/testify v1.7.0
github.com/vishvananda/netlink v1.1.1-0.20211129163951-9ada19101fc5 github.com/vishvananda/netlink v1.1.1-0.20211129163951-9ada19101fc5
golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c
golang.zx2c4.com/wireguard v0.0.0-20210805125648-3957e9b9dd19 golang.zx2c4.com/wireguard v0.0.0-20210805125648-3957e9b9dd19

9
go.sum
View File

@@ -4,8 +4,8 @@ github.com/alcortesm/tgz v0.0.0-20161220082320-9c5fe88206d7/go.mod h1:6zEj6s6u/g
github.com/anmitsu/go-shlex v0.0.0-20161002113705-648efa622239/go.mod h1:2FmKhYUyUczH0OGQWaF5ceTx0UBShxjsH6f8oGKYe2c= github.com/anmitsu/go-shlex v0.0.0-20161002113705-648efa622239/go.mod h1:2FmKhYUyUczH0OGQWaF5ceTx0UBShxjsH6f8oGKYe2c=
github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5/go.mod h1:wHh0iHkYZB8zMSxRWpUBQtwG5a7fFgvEO+odwuTv2gs= github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5/go.mod h1:wHh0iHkYZB8zMSxRWpUBQtwG5a7fFgvEO+odwuTv2gs=
github.com/asaskevich/govalidator v0.0.0-20180720115003-f9ffefc3facf/go.mod h1:lB+ZfQJz7igIIfQNfa7Ml4HSf2uFQQRzpGGRXenZAgY= github.com/asaskevich/govalidator v0.0.0-20180720115003-f9ffefc3facf/go.mod h1:lB+ZfQJz7igIIfQNfa7Ml4HSf2uFQQRzpGGRXenZAgY=
github.com/breml/rootcerts v0.2.3 h1:1vkYjKOiHVSyuz9Ue4AOrViEvUm8gk8phTg0vbcuU0A= github.com/breml/rootcerts v0.2.2 h1:hkHEpbTdYaNvDoYeq+mwRvCeg/YTTl23DjQ1Tnj71Zs=
github.com/breml/rootcerts v0.2.3/go.mod h1:24FDtzYMpqIeYC7QzaE8VPRQaFZU5TIUDlyk8qwjD88= github.com/breml/rootcerts v0.2.2/go.mod h1:24FDtzYMpqIeYC7QzaE8VPRQaFZU5TIUDlyk8qwjD88=
github.com/creack/pty v1.1.7/go.mod h1:lj5s0c3V2DBrqTV7llrYr5NG6My20zk30Fl46Y7DoTY= github.com/creack/pty v1.1.7/go.mod h1:lj5s0c3V2DBrqTV7llrYr5NG6My20zk30Fl46Y7DoTY=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
@@ -115,8 +115,6 @@ github.com/qdm12/gotree v0.2.0 h1:+58ltxkNLUyHtATFereAcOjBVfY6ETqRex8XK90Fb/c=
github.com/qdm12/gotree v0.2.0/go.mod h1:1SdFaqKZuI46U1apbXIf25pDMNnrPuYLEqMF/qL4lY4= github.com/qdm12/gotree v0.2.0/go.mod h1:1SdFaqKZuI46U1apbXIf25pDMNnrPuYLEqMF/qL4lY4=
github.com/qdm12/govalid v0.1.0 h1:UIFVmuaAg0Q+h0GeyfcFEZ5sQ5KJPvRQwycC1/cqDN8= github.com/qdm12/govalid v0.1.0 h1:UIFVmuaAg0Q+h0GeyfcFEZ5sQ5KJPvRQwycC1/cqDN8=
github.com/qdm12/govalid v0.1.0/go.mod h1:CyS/OEQdOvunBgrtIsW93fjd4jBkwZPBjGSpxq3NwA4= github.com/qdm12/govalid v0.1.0/go.mod h1:CyS/OEQdOvunBgrtIsW93fjd4jBkwZPBjGSpxq3NwA4=
github.com/qdm12/log v0.1.0 h1:jYBd/xscHYpblzZAd2kjZp2YmuYHjAAfbTViJWxoPTw=
github.com/qdm12/log v0.1.0/go.mod h1:Vchi5M8uBvHfPNIblN4mjXn/oSbiWguQIbsgF1zdQPI=
github.com/qdm12/ss-server v0.4.0 h1:lMMYfDGc9P86Lyvd3+p8lK4hhgHUKDzjZC91FqJYkDU= github.com/qdm12/ss-server v0.4.0 h1:lMMYfDGc9P86Lyvd3+p8lK4hhgHUKDzjZC91FqJYkDU=
github.com/qdm12/ss-server v0.4.0/go.mod h1:AY0p4huvPUPW+/CiWsJcDgT6sneDryk26VXSccPNCxY= github.com/qdm12/ss-server v0.4.0/go.mod h1:AY0p4huvPUPW+/CiWsJcDgT6sneDryk26VXSccPNCxY=
github.com/qdm12/updated v0.0.0-20210603204757-205acfe6937e h1:4q+uFLawkaQRq3yARYLsjJPZd2wYwxn4g6G/5v0xW1g= github.com/qdm12/updated v0.0.0-20210603204757-205acfe6937e h1:4q+uFLawkaQRq3yARYLsjJPZd2wYwxn4g6G/5v0xW1g=
@@ -130,9 +128,8 @@ github.com/stretchr/objx v0.2.0/go.mod h1:qt09Ya8vawLte6SNmTgCsAVtYtaKzEcn8ATUoH
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY=
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.7.1 h1:5TQK59W5E3v0r2duFAb7P95B6hEeOyEnHRa8MjYSMTY=
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/vishvananda/netlink v1.1.1-0.20211129163951-9ada19101fc5 h1:b/k/BVWzWRS5v6AB0gf2ckFSbFsHN5jR0HoNso1pN+w= github.com/vishvananda/netlink v1.1.1-0.20211129163951-9ada19101fc5 h1:b/k/BVWzWRS5v6AB0gf2ckFSbFsHN5jR0HoNso1pN+w=
github.com/vishvananda/netlink v1.1.1-0.20211129163951-9ada19101fc5/go.mod h1:twkDnbuQxJYemMlGd4JFIcuhgX83tXhKS2B/PRMpOho= github.com/vishvananda/netlink v1.1.1-0.20211129163951-9ada19101fc5/go.mod h1:twkDnbuQxJYemMlGd4JFIcuhgX83tXhKS2B/PRMpOho=
github.com/vishvananda/netns v0.0.0-20200728191858-db3c7e526aae h1:4hwBBUfQCFe3Cym0ZtKyq7L16eZUtYKs+BaHDN6mAns= github.com/vishvananda/netns v0.0.0-20200728191858-db3c7e526aae h1:4hwBBUfQCFe3Cym0ZtKyq7L16eZUtYKs+BaHDN6mAns=

View File

@@ -8,7 +8,6 @@ import (
"path/filepath" "path/filepath"
"github.com/qdm12/gluetun/internal/constants" "github.com/qdm12/gluetun/internal/constants"
"github.com/qdm12/gluetun/internal/constants/providers"
"github.com/qdm12/gluetun/internal/storage" "github.com/qdm12/gluetun/internal/storage"
) )
@@ -29,26 +28,26 @@ func (c *CLI) FormatServers(args []string) error {
flagSet := flag.NewFlagSet("markdown", flag.ExitOnError) flagSet := flag.NewFlagSet("markdown", flag.ExitOnError)
flagSet.StringVar(&format, "format", "markdown", "Format to use which can be: 'markdown'") flagSet.StringVar(&format, "format", "markdown", "Format to use which can be: 'markdown'")
flagSet.StringVar(&output, "output", "/dev/stdout", "Output file to write the formatted data to") flagSet.StringVar(&output, "output", "/dev/stdout", "Output file to write the formatted data to")
flagSet.BoolVar(&cyberghost, providers.Cyberghost, false, "Format Cyberghost servers") flagSet.BoolVar(&cyberghost, "cyberghost", false, "Format Cyberghost servers")
flagSet.BoolVar(&expressvpn, providers.Expressvpn, false, "Format ExpressVPN servers") flagSet.BoolVar(&expressvpn, "expressvpn", false, "Format ExpressVPN servers")
flagSet.BoolVar(&fastestvpn, providers.Fastestvpn, false, "Format FastestVPN servers") flagSet.BoolVar(&fastestvpn, "fastestvpn", false, "Format FastestVPN servers")
flagSet.BoolVar(&hideMyAss, providers.HideMyAss, false, "Format HideMyAss servers") flagSet.BoolVar(&hideMyAss, "hidemyass", false, "Format HideMyAss servers")
flagSet.BoolVar(&ipvanish, providers.Ipvanish, false, "Format IpVanish servers") flagSet.BoolVar(&ipvanish, "ipvanish", false, "Format IpVanish servers")
flagSet.BoolVar(&ivpn, providers.Ivpn, false, "Format IVPN servers") flagSet.BoolVar(&ivpn, "ivpn", false, "Format IVPN servers")
flagSet.BoolVar(&mullvad, providers.Mullvad, false, "Format Mullvad servers") flagSet.BoolVar(&mullvad, "mullvad", false, "Format Mullvad servers")
flagSet.BoolVar(&nordvpn, providers.Nordvpn, false, "Format Nordvpn servers") flagSet.BoolVar(&nordvpn, "nordvpn", false, "Format Nordvpn servers")
flagSet.BoolVar(&perfectPrivacy, providers.Perfectprivacy, false, "Format Perfect Privacy servers") flagSet.BoolVar(&perfectPrivacy, "perfectprivacy", false, "Format Perfect Privacy servers")
flagSet.BoolVar(&pia, providers.PrivateInternetAccess, false, "Format Private Internet Access servers") flagSet.BoolVar(&pia, "pia", false, "Format Private Internet Access servers")
flagSet.BoolVar(&privado, providers.Privado, false, "Format Privado servers") flagSet.BoolVar(&privado, "privado", false, "Format Privado servers")
flagSet.BoolVar(&privatevpn, providers.Privatevpn, false, "Format Private VPN servers") flagSet.BoolVar(&privatevpn, "privatevpn", false, "Format Private VPN servers")
flagSet.BoolVar(&protonvpn, providers.Protonvpn, false, "Format Protonvpn servers") flagSet.BoolVar(&protonvpn, "protonvpn", false, "Format Protonvpn servers")
flagSet.BoolVar(&purevpn, providers.Purevpn, false, "Format Purevpn servers") flagSet.BoolVar(&purevpn, "purevpn", false, "Format Purevpn servers")
flagSet.BoolVar(&surfshark, providers.Surfshark, false, "Format Surfshark servers") flagSet.BoolVar(&surfshark, "surfshark", false, "Format Surfshark servers")
flagSet.BoolVar(&torguard, providers.Torguard, false, "Format Torguard servers") flagSet.BoolVar(&torguard, "torguard", false, "Format Torguard servers")
flagSet.BoolVar(&vpnUnlimited, providers.VPNUnlimited, false, "Format VPN Unlimited servers") flagSet.BoolVar(&vpnUnlimited, "vpnunlimited", false, "Format VPN Unlimited servers")
flagSet.BoolVar(&vyprvpn, providers.Vyprvpn, false, "Format Vyprvpn servers") flagSet.BoolVar(&vyprvpn, "vyprvpn", false, "Format Vyprvpn servers")
flagSet.BoolVar(&wevpn, providers.Wevpn, false, "Format WeVPN servers") flagSet.BoolVar(&wevpn, "wevpn", false, "Format WeVPN servers")
flagSet.BoolVar(&windscribe, providers.Windscribe, false, "Format Windscribe servers") flagSet.BoolVar(&windscribe, "windscribe", false, "Format Windscribe servers")
if err := flagSet.Parse(args); err != nil { if err := flagSet.Parse(args); err != nil {
return err return err
} }
@@ -67,45 +66,45 @@ func (c *CLI) FormatServers(args []string) error {
var formatted string var formatted string
switch { switch {
case cyberghost: case cyberghost:
formatted = currentServers.Cyberghost.ToMarkdown(providers.Cyberghost) formatted = currentServers.Cyberghost.ToMarkdown()
case expressvpn: case expressvpn:
formatted = currentServers.Expressvpn.ToMarkdown(providers.Expressvpn) formatted = currentServers.Expressvpn.ToMarkdown()
case fastestvpn: case fastestvpn:
formatted = currentServers.Fastestvpn.ToMarkdown(providers.Fastestvpn) formatted = currentServers.Fastestvpn.ToMarkdown()
case hideMyAss: case hideMyAss:
formatted = currentServers.HideMyAss.ToMarkdown(providers.HideMyAss) formatted = currentServers.HideMyAss.ToMarkdown()
case ipvanish: case ipvanish:
formatted = currentServers.Ipvanish.ToMarkdown(providers.Ipvanish) formatted = currentServers.Ipvanish.ToMarkdown()
case ivpn: case ivpn:
formatted = currentServers.Ivpn.ToMarkdown(providers.Ivpn) formatted = currentServers.Ivpn.ToMarkdown()
case mullvad: case mullvad:
formatted = currentServers.Mullvad.ToMarkdown(providers.Mullvad) formatted = currentServers.Mullvad.ToMarkdown()
case nordvpn: case nordvpn:
formatted = currentServers.Nordvpn.ToMarkdown(providers.Nordvpn) formatted = currentServers.Nordvpn.ToMarkdown()
case perfectPrivacy: case perfectPrivacy:
formatted = currentServers.Perfectprivacy.ToMarkdown(providers.Perfectprivacy) formatted = currentServers.Perfectprivacy.ToMarkdown()
case pia: case pia:
formatted = currentServers.Pia.ToMarkdown(providers.PrivateInternetAccess) formatted = currentServers.Pia.ToMarkdown()
case privado: case privado:
formatted = currentServers.Privado.ToMarkdown(providers.Privado) formatted = currentServers.Privado.ToMarkdown()
case privatevpn: case privatevpn:
formatted = currentServers.Privatevpn.ToMarkdown(providers.Privatevpn) formatted = currentServers.Privatevpn.ToMarkdown()
case protonvpn: case protonvpn:
formatted = currentServers.Protonvpn.ToMarkdown(providers.Protonvpn) formatted = currentServers.Protonvpn.ToMarkdown()
case purevpn: case purevpn:
formatted = currentServers.Purevpn.ToMarkdown(providers.Purevpn) formatted = currentServers.Purevpn.ToMarkdown()
case surfshark: case surfshark:
formatted = currentServers.Surfshark.ToMarkdown(providers.Surfshark) formatted = currentServers.Surfshark.ToMarkdown()
case torguard: case torguard:
formatted = currentServers.Torguard.ToMarkdown(providers.Torguard) formatted = currentServers.Torguard.ToMarkdown()
case vpnUnlimited: case vpnUnlimited:
formatted = currentServers.VPNUnlimited.ToMarkdown(providers.VPNUnlimited) formatted = currentServers.VPNUnlimited.ToMarkdown()
case vyprvpn: case vyprvpn:
formatted = currentServers.Vyprvpn.ToMarkdown(providers.Vyprvpn) formatted = currentServers.Vyprvpn.ToMarkdown()
case wevpn: case wevpn:
formatted = currentServers.Wevpn.ToMarkdown(providers.Wevpn) formatted = currentServers.Wevpn.ToMarkdown()
case windscribe: case windscribe:
formatted = currentServers.Windscribe.ToMarkdown(providers.Windscribe) formatted = currentServers.Windscribe.ToMarkdown()
default: default:
return ErrProviderUnspecified return ErrProviderUnspecified
} }

View File

@@ -14,7 +14,6 @@ import (
"github.com/qdm12/gluetun/internal/configuration/settings" "github.com/qdm12/gluetun/internal/configuration/settings"
"github.com/qdm12/gluetun/internal/constants" "github.com/qdm12/gluetun/internal/constants"
"github.com/qdm12/gluetun/internal/constants/providers"
"github.com/qdm12/gluetun/internal/models" "github.com/qdm12/gluetun/internal/models"
"github.com/qdm12/gluetun/internal/storage" "github.com/qdm12/gluetun/internal/storage"
"github.com/qdm12/gluetun/internal/updater" "github.com/qdm12/gluetun/internal/updater"
@@ -63,8 +62,8 @@ func (c *CLI) Update(ctx context.Context, args []string, logger UpdaterLogger) e
} }
if updateAll { if updateAll {
for _, provider := range providers.All() { for _, provider := range constants.AllProviders() {
if provider == providers.Custom { if provider == constants.Custom {
continue continue
} }
options.Providers = append(options.Providers, provider) options.Providers = append(options.Providers, provider)

View File

@@ -65,7 +65,7 @@ func (h *Health) OverrideWith(other Health) {
func (h *Health) SetDefaults() { func (h *Health) SetDefaults() {
h.ServerAddress = helpers.DefaultString(h.ServerAddress, "127.0.0.1:9999") h.ServerAddress = helpers.DefaultString(h.ServerAddress, "127.0.0.1:9999")
h.TargetAddress = helpers.DefaultString(h.TargetAddress, "cloudflare.com:443") h.TargetAddress = helpers.DefaultString(h.TargetAddress, "github.com:443")
h.VPN.setDefaults() h.VPN.setDefaults()
} }

View File

@@ -4,7 +4,7 @@ import (
"net" "net"
"time" "time"
"github.com/qdm12/log" "github.com/qdm12/golibs/logging"
"inet.af/netaddr" "inet.af/netaddr"
) )
@@ -44,15 +44,6 @@ func CopyUint16Ptr(original *uint16) (copied *uint16) {
return copied return copied
} }
func CopyUint32Ptr(original *uint32) (copied *uint32) {
if original == nil {
return nil
}
copied = new(uint32)
*copied = *original
return copied
}
func CopyIntPtr(original *int) (copied *int) { func CopyIntPtr(original *int) (copied *int) {
if original == nil { if original == nil {
return nil return nil
@@ -71,11 +62,11 @@ func CopyDurationPtr(original *time.Duration) (copied *time.Duration) {
return copied return copied
} }
func CopyLogLevelPtr(original *log.Level) (copied *log.Level) { func CopyLogLevelPtr(original *logging.Level) (copied *logging.Level) {
if original == nil { if original == nil {
return nil return nil
} }
copied = new(log.Level) copied = new(logging.Level)
*copied = *original *copied = *original
return copied return copied
} }

View File

@@ -4,7 +4,7 @@ import (
"net" "net"
"time" "time"
"github.com/qdm12/log" "github.com/qdm12/golibs/logging"
) )
func DefaultInt(existing *int, defaultValue int) ( func DefaultInt(existing *int, defaultValue int) (
@@ -36,15 +36,6 @@ func DefaultUint16(existing *uint16, defaultValue uint16) (
*result = defaultValue *result = defaultValue
return result return result
} }
func DefaultUint32(existing *uint32, defaultValue uint32) (
result *uint32) {
if existing != nil {
return existing
}
result = new(uint32)
*result = defaultValue
return result
}
func DefaultBool(existing *bool, defaultValue bool) ( func DefaultBool(existing *bool, defaultValue bool) (
result *bool) { result *bool) {
@@ -83,12 +74,12 @@ func DefaultDuration(existing *time.Duration,
return result return result
} }
func DefaultLogLevel(existing *log.Level, func DefaultLogLevel(existing *logging.Level,
defaultValue log.Level) (result *log.Level) { defaultValue logging.Level) (result *logging.Level) {
if existing != nil { if existing != nil {
return existing return existing
} }
result = new(log.Level) result = new(logging.Level)
*result = defaultValue *result = defaultValue
return result return result
} }

View File

@@ -5,7 +5,7 @@ import (
"net/http" "net/http"
"time" "time"
"github.com/qdm12/log" "github.com/qdm12/golibs/logging"
"inet.af/netaddr" "inet.af/netaddr"
) )
@@ -78,17 +78,6 @@ func MergeWithUint16(existing, other *uint16) (result *uint16) {
return result return result
} }
func MergeWithUint32(existing, other *uint32) (result *uint32) {
if existing != nil {
return existing
} else if other == nil {
return nil
}
result = new(uint32)
*result = *other
return result
}
func MergeWithIP(existing, other net.IP) (result net.IP) { func MergeWithIP(existing, other net.IP) (result net.IP) {
if existing != nil { if existing != nil {
return existing return existing
@@ -107,13 +96,13 @@ func MergeWithDuration(existing, other *time.Duration) (result *time.Duration) {
return other return other
} }
func MergeWithLogLevel(existing, other *log.Level) (result *log.Level) { func MergeWithLogLevel(existing, other *logging.Level) (result *logging.Level) {
if existing != nil { if existing != nil {
return existing return existing
} else if other == nil { } else if other == nil {
return nil return nil
} }
result = new(log.Level) result = new(logging.Level)
*result = *other *result = *other
return result return result
} }

View File

@@ -5,7 +5,7 @@ import (
"net/http" "net/http"
"time" "time"
"github.com/qdm12/log" "github.com/qdm12/golibs/logging"
"inet.af/netaddr" "inet.af/netaddr"
) )
@@ -68,15 +68,6 @@ func OverrideWithUint16(existing, other *uint16) (result *uint16) {
return result return result
} }
func OverrideWithUint32(existing, other *uint32) (result *uint32) {
if other == nil {
return existing
}
result = new(uint32)
*result = *other
return result
}
func OverrideWithIP(existing, other net.IP) (result net.IP) { func OverrideWithIP(existing, other net.IP) (result net.IP) {
if other == nil { if other == nil {
return existing return existing
@@ -95,11 +86,11 @@ func OverrideWithDuration(existing, other *time.Duration) (result *time.Duration
return result return result
} }
func OverrideWithLogLevel(existing, other *log.Level) (result *log.Level) { func OverrideWithLogLevel(existing, other *logging.Level) (result *logging.Level) {
if other == nil { if other == nil {
return existing return existing
} }
result = new(log.Level) result = new(logging.Level)
*result = *other *result = *other
return result return result
} }

View File

@@ -2,15 +2,15 @@ package settings
import ( import (
"github.com/qdm12/gluetun/internal/configuration/settings/helpers" "github.com/qdm12/gluetun/internal/configuration/settings/helpers"
"github.com/qdm12/golibs/logging"
"github.com/qdm12/gotree" "github.com/qdm12/gotree"
"github.com/qdm12/log"
) )
// Log contains settings to configure the logger. // Log contains settings to configure the logger.
type Log struct { type Log struct {
// Level is the log level of the logger. // Level is the log level of the logger.
// It cannot be nil in the internal state. // It cannot be nil in the internal state.
Level *log.Level Level *logging.Level
} }
func (l Log) validate() (err error) { func (l Log) validate() (err error) {
@@ -37,7 +37,7 @@ func (l *Log) overrideWith(other Log) {
} }
func (l *Log) setDefaults() { func (l *Log) setDefaults() {
l.Level = helpers.DefaultLogLevel(l.Level, log.LevelInfo) l.Level = helpers.DefaultLogLevel(l.Level, logging.LevelInfo)
} }
func (l Log) String() string { func (l Log) String() string {

View File

@@ -2,12 +2,10 @@ package settings
import ( import (
"fmt" "fmt"
"regexp"
"strings" "strings"
"github.com/qdm12/gluetun/internal/configuration/settings/helpers" "github.com/qdm12/gluetun/internal/configuration/settings/helpers"
"github.com/qdm12/gluetun/internal/constants" "github.com/qdm12/gluetun/internal/constants"
"github.com/qdm12/gluetun/internal/constants/providers"
"github.com/qdm12/gluetun/internal/openvpn/parse" "github.com/qdm12/gluetun/internal/openvpn/parse"
"github.com/qdm12/gotree" "github.com/qdm12/gotree"
) )
@@ -75,8 +73,6 @@ type OpenVPN struct {
Flags []string Flags []string
} }
var ivpnAccountID = regexp.MustCompile(`^(i|ivpn)\-[a-zA-Z0-9]{4}\-[a-zA-Z0-9]{4}\-[a-zA-Z0-9]{4}$`)
func (o OpenVPN) validate(vpnProvider string) (err error) { func (o OpenVPN) validate(vpnProvider string) (err error) {
// Validate version // Validate version
validVersions := []string{constants.Openvpn24, constants.Openvpn25} validVersions := []string{constants.Openvpn24, constants.Openvpn25}
@@ -85,16 +81,13 @@ func (o OpenVPN) validate(vpnProvider string) (err error) {
ErrOpenVPNVersionIsNotValid, o.Version, strings.Join(validVersions, ", ")) ErrOpenVPNVersionIsNotValid, o.Version, strings.Join(validVersions, ", "))
} }
isCustom := vpnProvider == providers.Custom isCustom := vpnProvider == constants.Custom
if !isCustom && o.User == "" { if !isCustom && o.User == "" {
return ErrOpenVPNUserIsEmpty return ErrOpenVPNUserIsEmpty
} }
passwordRequired := !isCustom && if !isCustom && o.Password == "" {
(vpnProvider != providers.Ivpn || !ivpnAccountID.MatchString(o.User))
if passwordRequired && o.Password == "" {
return ErrOpenVPNPasswordIsEmpty return ErrOpenVPNPasswordIsEmpty
} }
@@ -154,8 +147,8 @@ func validateOpenVPNClientCertificate(vpnProvider,
clientCert string) (err error) { clientCert string) (err error) {
switch vpnProvider { switch vpnProvider {
case case
providers.Cyberghost, constants.Cyberghost,
providers.VPNUnlimited: constants.VPNUnlimited:
if clientCert == "" { if clientCert == "" {
return ErrMissingValue return ErrMissingValue
} }
@@ -175,9 +168,9 @@ func validateOpenVPNClientCertificate(vpnProvider,
func validateOpenVPNClientKey(vpnProvider, clientKey string) (err error) { func validateOpenVPNClientKey(vpnProvider, clientKey string) (err error) {
switch vpnProvider { switch vpnProvider {
case case
providers.Cyberghost, constants.Cyberghost,
providers.VPNUnlimited, constants.VPNUnlimited,
providers.Wevpn: constants.Wevpn:
if clientKey == "" { if clientKey == "" {
return ErrMissingValue return ErrMissingValue
} }
@@ -257,7 +250,7 @@ func (o *OpenVPN) overrideWith(other OpenVPN) {
func (o *OpenVPN) setDefaults(vpnProvider string) { func (o *OpenVPN) setDefaults(vpnProvider string) {
o.Version = helpers.DefaultString(o.Version, constants.Openvpn25) o.Version = helpers.DefaultString(o.Version, constants.Openvpn25)
if vpnProvider == providers.Mullvad { if vpnProvider == constants.Mullvad {
o.Password = "m" o.Password = "m"
} }
@@ -267,7 +260,7 @@ func (o *OpenVPN) setDefaults(vpnProvider string) {
o.ClientKey = helpers.DefaultStringPtr(o.ClientKey, "") o.ClientKey = helpers.DefaultStringPtr(o.ClientKey, "")
var defaultEncPreset string var defaultEncPreset string
if vpnProvider == providers.PrivateInternetAccess { if vpnProvider == constants.PrivateInternetAccess {
defaultEncPreset = constants.PIAEncryptionPresetStrong defaultEncPreset = constants.PIAEncryptionPresetStrong
} }
o.PIAEncPreset = helpers.DefaultStringPtr(o.PIAEncPreset, defaultEncPreset) o.PIAEncPreset = helpers.DefaultStringPtr(o.PIAEncPreset, defaultEncPreset)

View File

@@ -1,44 +0,0 @@
package settings
import (
"testing"
"github.com/stretchr/testify/assert"
)
func Test_ivpnAccountID(t *testing.T) {
t.Parallel()
testCases := []struct {
s string
match bool
}{
{},
{s: "abc"},
{s: "i"},
{s: "ivpn"},
{s: "ivpn-aaaa"},
{s: "ivpn-aaaa-aaaa"},
{s: "ivpn-aaaa-aaaa-aaa"},
{s: "ivpn-aaaa-aaaa-aaaa", match: true},
{s: "ivpn-aaaa-aaaa-aaaaa"},
{s: "ivpn-a6B7-fP91-Zh6Y", match: true},
{s: "i-aaaa"},
{s: "i-aaaa-aaaa"},
{s: "i-aaaa-aaaa-aaa"},
{s: "i-aaaa-aaaa-aaaa", match: true},
{s: "i-aaaa-aaaa-aaaaa"},
{s: "i-a6B7-fP91-Zh6Y", match: true},
}
for _, testCase := range testCases {
testCase := testCase
t.Run(testCase.s, func(t *testing.T) {
t.Parallel()
match := ivpnAccountID.MatchString(testCase.s)
assert.Equal(t, testCase.match, match)
})
}
}

View File

@@ -5,7 +5,6 @@ import (
"github.com/qdm12/gluetun/internal/configuration/settings/helpers" "github.com/qdm12/gluetun/internal/configuration/settings/helpers"
"github.com/qdm12/gluetun/internal/constants" "github.com/qdm12/gluetun/internal/constants"
"github.com/qdm12/gluetun/internal/constants/providers"
"github.com/qdm12/gotree" "github.com/qdm12/gotree"
) )
@@ -40,11 +39,11 @@ func (o OpenVPNSelection) validate(vpnProvider string) (err error) {
// Validate TCP // Validate TCP
if *o.TCP && helpers.IsOneOf(vpnProvider, if *o.TCP && helpers.IsOneOf(vpnProvider,
providers.Ipvanish, constants.Ipvanish,
providers.Perfectprivacy, constants.Perfectprivacy,
providers.Privado, constants.Privado,
providers.VPNUnlimited, constants.VPNUnlimited,
providers.Vyprvpn, constants.Vyprvpn,
) { ) {
return fmt.Errorf("%w: for VPN service provider %s", return fmt.Errorf("%w: for VPN service provider %s",
ErrOpenVPNTCPNotSupported, vpnProvider) ErrOpenVPNTCPNotSupported, vpnProvider)
@@ -54,39 +53,33 @@ func (o OpenVPNSelection) validate(vpnProvider string) (err error) {
if *o.CustomPort != 0 { if *o.CustomPort != 0 {
switch vpnProvider { switch vpnProvider {
// no restriction on port // no restriction on port
case providers.Cyberghost, providers.HideMyAss, case constants.Cyberghost, constants.HideMyAss,
providers.PrivateInternetAccess, providers.Privatevpn, constants.PrivateInternetAccess, constants.Privatevpn,
providers.Protonvpn, providers.Torguard: constants.Protonvpn, constants.Torguard:
// no custom port allowed // no custom port allowed
case providers.Expressvpn, providers.Fastestvpn, case constants.Expressvpn, constants.Fastestvpn,
providers.Ipvanish, providers.Nordvpn, constants.Ipvanish, constants.Nordvpn,
providers.Privado, providers.Purevpn, constants.Privado, constants.Purevpn,
providers.Surfshark, providers.VPNUnlimited, constants.Surfshark, constants.VPNUnlimited,
providers.Vyprvpn: constants.Vyprvpn:
return fmt.Errorf("%w: for VPN service provider %s", return fmt.Errorf("%w: for VPN service provider %s",
ErrOpenVPNCustomPortNotAllowed, vpnProvider) ErrOpenVPNCustomPortNotAllowed, vpnProvider)
default: default:
var allowedTCP, allowedUDP []uint16 var allowedTCP, allowedUDP []uint16
switch vpnProvider { switch vpnProvider {
case providers.Ivpn: case constants.Ivpn:
allowedTCP = []uint16{80, 443, 1143} allowedTCP = []uint16{80, 443, 1143}
allowedUDP = []uint16{53, 1194, 2049, 2050} allowedUDP = []uint16{53, 1194, 2049, 2050}
case providers.Mullvad: case constants.Mullvad:
allowedTCP = []uint16{80, 443, 1401} allowedTCP = []uint16{80, 443, 1401}
allowedUDP = []uint16{53, 1194, 1195, 1196, 1197, 1300, 1301, 1302, 1303, 1400} allowedUDP = []uint16{53, 1194, 1195, 1196, 1197, 1300, 1301, 1302, 1303, 1400}
case providers.Perfectprivacy: case constants.Perfectprivacy:
allowedTCP = []uint16{44, 443, 4433} allowedTCP = []uint16{44, 443, 4433}
allowedUDP = []uint16{44, 443, 4433} allowedUDP = []uint16{44, 443, 4433}
case providers.PrivateInternetAccess: case constants.Wevpn:
allowedTCP = []uint16{80, 110, 443}
allowedUDP = []uint16{53, 1194, 1197, 1198, 8080, 9201}
case providers.Protonvpn:
allowedTCP = []uint16{443, 5995, 8443}
allowedUDP = []uint16{80, 443, 1194, 4569, 5060}
case providers.Wevpn:
allowedTCP = []uint16{53, 1195, 1199, 2018} allowedTCP = []uint16{53, 1195, 1199, 2018}
allowedUDP = []uint16{80, 1194, 1198} allowedUDP = []uint16{80, 1194, 1198}
case providers.Windscribe: case constants.Windscribe:
allowedTCP = []uint16{21, 22, 80, 123, 143, 443, 587, 1194, 3306, 8080, 54783} allowedTCP = []uint16{21, 22, 80, 123, 143, 443, 587, 1194, 3306, 8080, 54783}
allowedUDP = []uint16{53, 80, 123, 443, 1194, 54783} allowedUDP = []uint16{53, 80, 123, 443, 1194, 54783}
} }
@@ -104,7 +97,7 @@ func (o OpenVPNSelection) validate(vpnProvider string) (err error) {
} }
// Validate EncPreset // Validate EncPreset
if vpnProvider == providers.PrivateInternetAccess { if vpnProvider == constants.PrivateInternetAccess {
validEncryptionPresets := []string{ validEncryptionPresets := []string{
constants.PIAEncryptionPresetNone, constants.PIAEncryptionPresetNone,
constants.PIAEncryptionPresetNormal, constants.PIAEncryptionPresetNormal,
@@ -149,7 +142,7 @@ func (o *OpenVPNSelection) setDefaults(vpnProvider string) {
o.CustomPort = helpers.DefaultUint16(o.CustomPort, 0) o.CustomPort = helpers.DefaultUint16(o.CustomPort, 0)
var defaultEncPreset string var defaultEncPreset string
if vpnProvider == providers.PrivateInternetAccess { if vpnProvider == constants.PrivateInternetAccess {
defaultEncPreset = constants.PIAEncryptionPresetStrong defaultEncPreset = constants.PIAEncryptionPresetStrong
} }
o.PIAEncPreset = helpers.DefaultStringPtr(o.PIAEncPreset, defaultEncPreset) o.PIAEncPreset = helpers.DefaultStringPtr(o.PIAEncPreset, defaultEncPreset)

View File

@@ -6,7 +6,7 @@ import (
"strings" "strings"
"github.com/qdm12/gluetun/internal/configuration/settings/helpers" "github.com/qdm12/gluetun/internal/configuration/settings/helpers"
"github.com/qdm12/gluetun/internal/constants/providers" "github.com/qdm12/gluetun/internal/constants"
"github.com/qdm12/gotree" "github.com/qdm12/gotree"
) )
@@ -28,7 +28,7 @@ func (p PortForwarding) validate(vpnProvider string) (err error) {
} }
// Validate Enabled // Validate Enabled
validProviders := []string{providers.PrivateInternetAccess} validProviders := []string{constants.PrivateInternetAccess}
if !helpers.IsOneOf(vpnProvider, validProviders...) { if !helpers.IsOneOf(vpnProvider, validProviders...) {
return fmt.Errorf("%w: for provider %s, it is only available for %s", return fmt.Errorf("%w: for provider %s, it is only available for %s",
ErrPortForwardingEnabled, vpnProvider, strings.Join(validProviders, ", ")) ErrPortForwardingEnabled, vpnProvider, strings.Join(validProviders, ", "))

View File

@@ -4,8 +4,7 @@ import (
"fmt" "fmt"
"github.com/qdm12/gluetun/internal/configuration/settings/helpers" "github.com/qdm12/gluetun/internal/configuration/settings/helpers"
"github.com/qdm12/gluetun/internal/constants/providers" "github.com/qdm12/gluetun/internal/constants"
"github.com/qdm12/gluetun/internal/constants/vpn"
"github.com/qdm12/gluetun/internal/models" "github.com/qdm12/gluetun/internal/models"
"github.com/qdm12/gotree" "github.com/qdm12/gotree"
) )
@@ -26,15 +25,15 @@ type Provider struct {
func (p *Provider) validate(vpnType string, allServers models.AllServers) (err error) { func (p *Provider) validate(vpnType string, allServers models.AllServers) (err error) {
// Validate Name // Validate Name
var validNames []string var validNames []string
if vpnType == vpn.OpenVPN { if vpnType == constants.OpenVPN {
validNames = providers.All() validNames = constants.AllProviders()
validNames = append(validNames, "pia") // Retro-compatibility validNames = append(validNames, "pia") // Retro-compatibility
} else { // Wireguard } else { // Wireguard
validNames = []string{ validNames = []string{
providers.Custom, constants.Custom,
providers.Ivpn, constants.Ivpn,
providers.Mullvad, constants.Mullvad,
providers.Windscribe, constants.Windscribe,
} }
} }
if !helpers.IsOneOf(*p.Name, validNames...) { if !helpers.IsOneOf(*p.Name, validNames...) {
@@ -76,7 +75,7 @@ func (p *Provider) overrideWith(other Provider) {
} }
func (p *Provider) setDefaults() { func (p *Provider) setDefaults() {
p.Name = helpers.DefaultStringPtr(p.Name, providers.PrivateInternetAccess) p.Name = helpers.DefaultStringPtr(p.Name, constants.PrivateInternetAccess)
p.ServerSelection.setDefaults(*p.Name) p.ServerSelection.setDefaults(*p.Name)
p.PortForwarding.setDefaults() p.PortForwarding.setDefaults()
} }

View File

@@ -8,8 +8,7 @@ import (
"github.com/qdm12/gluetun/internal/configuration/settings/helpers" "github.com/qdm12/gluetun/internal/configuration/settings/helpers"
"github.com/qdm12/gluetun/internal/configuration/settings/validation" "github.com/qdm12/gluetun/internal/configuration/settings/validation"
"github.com/qdm12/gluetun/internal/constants/providers" "github.com/qdm12/gluetun/internal/constants"
"github.com/qdm12/gluetun/internal/constants/vpn"
"github.com/qdm12/gluetun/internal/models" "github.com/qdm12/gluetun/internal/models"
"github.com/qdm12/gotree" "github.com/qdm12/gotree"
) )
@@ -70,7 +69,7 @@ var (
func (ss *ServerSelection) validate(vpnServiceProvider string, func (ss *ServerSelection) validate(vpnServiceProvider string,
allServers models.AllServers) (err error) { allServers models.AllServers) (err error) {
switch ss.VPN { switch ss.VPN {
case vpn.OpenVPN, vpn.Wireguard: case constants.OpenVPN, constants.Wireguard:
default: default:
return fmt.Errorf("%w: %s", ErrVPNTypeNotValid, ss.VPN) return fmt.Errorf("%w: %s", ErrVPNTypeNotValid, ss.VPN)
} }
@@ -91,15 +90,15 @@ func (ss *ServerSelection) validate(vpnServiceProvider string,
} }
if *ss.OwnedOnly && if *ss.OwnedOnly &&
vpnServiceProvider != providers.Mullvad { vpnServiceProvider != constants.Mullvad {
return fmt.Errorf("%w: for VPN service provider %s", return fmt.Errorf("%w: for VPN service provider %s",
ErrOwnedOnlyNotSupported, vpnServiceProvider) ErrOwnedOnlyNotSupported, vpnServiceProvider)
} }
if *ss.FreeOnly && if *ss.FreeOnly &&
!helpers.IsOneOf(vpnServiceProvider, !helpers.IsOneOf(vpnServiceProvider,
providers.Protonvpn, constants.Protonvpn,
providers.VPNUnlimited, constants.VPNUnlimited,
) { ) {
return fmt.Errorf("%w: for VPN service provider %s", return fmt.Errorf("%w: for VPN service provider %s",
ErrFreeOnlyNotSupported, vpnServiceProvider) ErrFreeOnlyNotSupported, vpnServiceProvider)
@@ -107,20 +106,20 @@ func (ss *ServerSelection) validate(vpnServiceProvider string,
if *ss.StreamOnly && if *ss.StreamOnly &&
!helpers.IsOneOf(vpnServiceProvider, !helpers.IsOneOf(vpnServiceProvider,
providers.Protonvpn, constants.Protonvpn,
providers.VPNUnlimited, constants.VPNUnlimited,
) { ) {
return fmt.Errorf("%w: for VPN service provider %s", return fmt.Errorf("%w: for VPN service provider %s",
ErrStreamOnlyNotSupported, vpnServiceProvider) ErrStreamOnlyNotSupported, vpnServiceProvider)
} }
if *ss.MultiHopOnly && if *ss.MultiHopOnly &&
vpnServiceProvider != providers.Surfshark { vpnServiceProvider != constants.Surfshark {
return fmt.Errorf("%w: for VPN service provider %s", return fmt.Errorf("%w: for VPN service provider %s",
ErrMultiHopOnlyNotSupported, vpnServiceProvider) ErrMultiHopOnlyNotSupported, vpnServiceProvider)
} }
if ss.VPN == vpn.OpenVPN { if ss.VPN == constants.OpenVPN {
err = ss.OpenVPN.validate(vpnServiceProvider) err = ss.OpenVPN.validate(vpnServiceProvider)
if err != nil { if err != nil {
return fmt.Errorf("OpenVPN server selection settings: %w", err) return fmt.Errorf("OpenVPN server selection settings: %w", err)
@@ -141,85 +140,85 @@ func getLocationFilterChoices(vpnServiceProvider string, ss *ServerSelection,
ispChoices, nameChoices, hostnameChoices []string, ispChoices, nameChoices, hostnameChoices []string,
err error) { err error) {
switch vpnServiceProvider { switch vpnServiceProvider {
case providers.Custom: case constants.Custom:
case providers.Cyberghost: case constants.Cyberghost:
servers := allServers.GetCyberghost() servers := allServers.GetCyberghost()
countryChoices = validation.ExtractCountries(servers) countryChoices = validation.CyberghostCountryChoices(servers)
hostnameChoices = validation.ExtractHostnames(servers) hostnameChoices = validation.CyberghostHostnameChoices(servers)
case providers.Expressvpn: case constants.Expressvpn:
servers := allServers.GetExpressvpn() servers := allServers.GetExpressvpn()
countryChoices = validation.ExtractCountries(servers) countryChoices = validation.ExpressvpnCountriesChoices(servers)
cityChoices = validation.ExtractCities(servers) cityChoices = validation.ExpressvpnCityChoices(servers)
hostnameChoices = validation.ExtractHostnames(servers) hostnameChoices = validation.ExpressvpnHostnameChoices(servers)
case providers.Fastestvpn: case constants.Fastestvpn:
servers := allServers.GetFastestvpn() servers := allServers.GetFastestvpn()
countryChoices = validation.ExtractCountries(servers) countryChoices = validation.FastestvpnCountriesChoices(servers)
hostnameChoices = validation.ExtractHostnames(servers) hostnameChoices = validation.FastestvpnHostnameChoices(servers)
case providers.HideMyAss: case constants.HideMyAss:
servers := allServers.GetHideMyAss() servers := allServers.GetHideMyAss()
countryChoices = validation.ExtractCountries(servers) countryChoices = validation.HideMyAssCountryChoices(servers)
regionChoices = validation.ExtractRegions(servers) regionChoices = validation.HideMyAssRegionChoices(servers)
cityChoices = validation.ExtractCities(servers) cityChoices = validation.HideMyAssCityChoices(servers)
hostnameChoices = validation.ExtractHostnames(servers) hostnameChoices = validation.HideMyAssHostnameChoices(servers)
case providers.Ipvanish: case constants.Ipvanish:
servers := allServers.GetIpvanish() servers := allServers.GetIpvanish()
countryChoices = validation.ExtractCountries(servers) countryChoices = validation.IpvanishCountryChoices(servers)
cityChoices = validation.ExtractCities(servers) cityChoices = validation.IpvanishCityChoices(servers)
hostnameChoices = validation.ExtractHostnames(servers) hostnameChoices = validation.IpvanishHostnameChoices(servers)
case providers.Ivpn: case constants.Ivpn:
servers := allServers.GetIvpn() servers := allServers.GetIvpn()
countryChoices = validation.ExtractCountries(servers) countryChoices = validation.IvpnCountryChoices(servers)
cityChoices = validation.ExtractCities(servers) cityChoices = validation.IvpnCityChoices(servers)
ispChoices = validation.ExtractISPs(servers) ispChoices = validation.IvpnISPChoices(servers)
hostnameChoices = validation.ExtractHostnames(servers) hostnameChoices = validation.IvpnHostnameChoices(servers)
case providers.Mullvad: case constants.Mullvad:
servers := allServers.GetMullvad() servers := allServers.GetMullvad()
countryChoices = validation.ExtractCountries(servers) countryChoices = validation.MullvadCountryChoices(servers)
cityChoices = validation.ExtractCities(servers) cityChoices = validation.MullvadCityChoices(servers)
ispChoices = validation.ExtractISPs(servers) ispChoices = validation.MullvadISPChoices(servers)
hostnameChoices = validation.ExtractHostnames(servers) hostnameChoices = validation.MullvadHostnameChoices(servers)
case providers.Nordvpn: case constants.Nordvpn:
servers := allServers.GetNordvpn() servers := allServers.GetNordvpn()
regionChoices = validation.ExtractRegions(servers) regionChoices = validation.NordvpnRegionChoices(servers)
hostnameChoices = validation.ExtractHostnames(servers) hostnameChoices = validation.NordvpnHostnameChoices(servers)
case providers.Perfectprivacy: case constants.Perfectprivacy:
servers := allServers.GetPerfectprivacy() servers := allServers.GetPerfectprivacy()
cityChoices = validation.ExtractCities(servers) cityChoices = validation.PerfectprivacyCityChoices(servers)
case providers.Privado: case constants.Privado:
servers := allServers.GetPrivado() servers := allServers.GetPrivado()
countryChoices = validation.ExtractCountries(servers) countryChoices = validation.PrivadoCountryChoices(servers)
regionChoices = validation.ExtractRegions(servers) regionChoices = validation.PrivadoRegionChoices(servers)
cityChoices = validation.ExtractCities(servers) cityChoices = validation.PrivadoCityChoices(servers)
hostnameChoices = validation.ExtractHostnames(servers) hostnameChoices = validation.PrivadoHostnameChoices(servers)
case providers.PrivateInternetAccess: case constants.PrivateInternetAccess:
servers := allServers.GetPia() servers := allServers.GetPia()
regionChoices = validation.ExtractRegions(servers) regionChoices = validation.PIAGeoChoices(servers)
hostnameChoices = validation.ExtractHostnames(servers) hostnameChoices = validation.PIAHostnameChoices(servers)
nameChoices = validation.ExtractServerNames(servers) nameChoices = validation.PIANameChoices(servers)
case providers.Privatevpn: case constants.Privatevpn:
servers := allServers.GetPrivatevpn() servers := allServers.GetPrivatevpn()
countryChoices = validation.ExtractCountries(servers) countryChoices = validation.PrivatevpnCountryChoices(servers)
cityChoices = validation.ExtractCities(servers) cityChoices = validation.PrivatevpnCityChoices(servers)
hostnameChoices = validation.ExtractHostnames(servers) hostnameChoices = validation.PrivatevpnHostnameChoices(servers)
case providers.Protonvpn: case constants.Protonvpn:
servers := allServers.GetProtonvpn() servers := allServers.GetProtonvpn()
countryChoices = validation.ExtractCountries(servers) countryChoices = validation.ProtonvpnCountryChoices(servers)
regionChoices = validation.ExtractRegions(servers) regionChoices = validation.ProtonvpnRegionChoices(servers)
cityChoices = validation.ExtractCities(servers) cityChoices = validation.ProtonvpnCityChoices(servers)
nameChoices = validation.ExtractServerNames(servers) nameChoices = validation.ProtonvpnNameChoices(servers)
hostnameChoices = validation.ExtractHostnames(servers) hostnameChoices = validation.ProtonvpnHostnameChoices(servers)
case providers.Purevpn: case constants.Purevpn:
servers := allServers.GetPurevpn() servers := allServers.GetPurevpn()
countryChoices = validation.ExtractCountries(servers) countryChoices = validation.PurevpnCountryChoices(servers)
regionChoices = validation.ExtractRegions(servers) regionChoices = validation.PurevpnRegionChoices(servers)
cityChoices = validation.ExtractCities(servers) cityChoices = validation.PurevpnCityChoices(servers)
hostnameChoices = validation.ExtractHostnames(servers) hostnameChoices = validation.PurevpnHostnameChoices(servers)
case providers.Surfshark: case constants.Surfshark:
servers := allServers.GetSurfshark() servers := allServers.GetSurfshark()
countryChoices = validation.ExtractCountries(servers) countryChoices = validation.SurfsharkCountryChoices(servers)
cityChoices = validation.ExtractCities(servers) cityChoices = validation.SurfsharkCityChoices(servers)
hostnameChoices = validation.ExtractHostnames(servers) hostnameChoices = validation.SurfsharkHostnameChoices(servers)
regionChoices = validation.ExtractRegions(servers) regionChoices = validation.SurfsharkRegionChoices(servers)
// TODO v4 remove // TODO v4 remove
regionChoices = append(regionChoices, validation.SurfsharkRetroLocChoices()...) regionChoices = append(regionChoices, validation.SurfsharkRetroLocChoices()...)
if err := helpers.AreAllOneOf(ss.Regions, regionChoices); err != nil { if err := helpers.AreAllOneOf(ss.Regions, regionChoices); err != nil {
@@ -228,28 +227,28 @@ func getLocationFilterChoices(vpnServiceProvider string, ss *ServerSelection,
// Retro compatibility // Retro compatibility
// TODO remove in v4 // TODO remove in v4
*ss = surfsharkRetroRegion(*ss) *ss = surfsharkRetroRegion(*ss)
case providers.Torguard: case constants.Torguard:
servers := allServers.GetTorguard() servers := allServers.GetTorguard()
countryChoices = validation.ExtractCountries(servers) countryChoices = validation.TorguardCountryChoices(servers)
cityChoices = validation.ExtractCities(servers) cityChoices = validation.TorguardCityChoices(servers)
hostnameChoices = validation.ExtractHostnames(servers) hostnameChoices = validation.TorguardHostnameChoices(servers)
case providers.VPNUnlimited: case constants.VPNUnlimited:
servers := allServers.GetVPNUnlimited() servers := allServers.GetVPNUnlimited()
countryChoices = validation.ExtractCountries(servers) countryChoices = validation.VPNUnlimitedCountryChoices(servers)
cityChoices = validation.ExtractCities(servers) cityChoices = validation.VPNUnlimitedCityChoices(servers)
hostnameChoices = validation.ExtractHostnames(servers) hostnameChoices = validation.VPNUnlimitedHostnameChoices(servers)
case providers.Vyprvpn: case constants.Vyprvpn:
servers := allServers.GetVyprvpn() servers := allServers.GetVyprvpn()
regionChoices = validation.ExtractRegions(servers) regionChoices = validation.VyprvpnRegionChoices(servers)
case providers.Wevpn: case constants.Wevpn:
servers := allServers.GetWevpn() servers := allServers.GetWevpn()
cityChoices = validation.ExtractCities(servers) cityChoices = validation.WevpnCityChoices(servers)
hostnameChoices = validation.ExtractHostnames(servers) hostnameChoices = validation.WevpnHostnameChoices(servers)
case providers.Windscribe: case constants.Windscribe:
servers := allServers.GetWindscribe() servers := allServers.GetWindscribe()
regionChoices = validation.ExtractRegions(servers) regionChoices = validation.WindscribeRegionChoices(servers)
cityChoices = validation.ExtractCities(servers) cityChoices = validation.WindscribeCityChoices(servers)
hostnameChoices = validation.ExtractHostnames(servers) hostnameChoices = validation.WindscribeHostnameChoices(servers)
default: default:
return nil, nil, nil, nil, nil, nil, fmt.Errorf("%w: %s", ErrVPNProviderNameNotValid, vpnServiceProvider) return nil, nil, nil, nil, nil, nil, fmt.Errorf("%w: %s", ErrVPNProviderNameNotValid, vpnServiceProvider)
} }
@@ -348,7 +347,7 @@ func (ss *ServerSelection) overrideWith(other ServerSelection) {
} }
func (ss *ServerSelection) setDefaults(vpnProvider string) { func (ss *ServerSelection) setDefaults(vpnProvider string) {
ss.VPN = helpers.DefaultString(ss.VPN, vpn.OpenVPN) ss.VPN = helpers.DefaultString(ss.VPN, constants.OpenVPN)
ss.TargetIP = helpers.DefaultIP(ss.TargetIP, net.IP{}) ss.TargetIP = helpers.DefaultIP(ss.TargetIP, net.IP{})
ss.OwnedOnly = helpers.DefaultBool(ss.OwnedOnly, false) ss.OwnedOnly = helpers.DefaultBool(ss.OwnedOnly, false)
ss.FreeOnly = helpers.DefaultBool(ss.FreeOnly, false) ss.FreeOnly = helpers.DefaultBool(ss.FreeOnly, false)
@@ -416,7 +415,7 @@ func (ss ServerSelection) toLinesNode() (node *gotree.Node) {
node.Appendf("Multi-hop only servers: yes") node.Appendf("Multi-hop only servers: yes")
} }
if ss.VPN == vpn.OpenVPN { if ss.VPN == constants.OpenVPN {
node.AppendNode(ss.OpenVPN.toLinesNode()) node.AppendNode(ss.OpenVPN.toLinesNode())
} else { } else {
node.AppendNode(ss.Wireguard.toLinesNode()) node.AppendNode(ss.Wireguard.toLinesNode())

View File

@@ -66,7 +66,7 @@ func Test_Settings_String(t *testing.T) {
| └── Log level: INFO | └── Log level: INFO
├── Health settings: ├── Health settings:
| ├── Server listening address: 127.0.0.1:9999 | ├── Server listening address: 127.0.0.1:9999
| ├── Target address: cloudflare.com:443 | ├── Target address: github.com:443
| └── VPN wait durations: | └── VPN wait durations:
| ├── Initial duration: 6s | ├── Initial duration: 6s
| └── Additional duration: 5s | └── Additional duration: 5s

View File

@@ -7,8 +7,8 @@ import (
// System contains settings to configure system related elements. // System contains settings to configure system related elements.
type System struct { type System struct {
PUID *uint32 PUID *uint16
PGID *uint32 PGID *uint16
Timezone string Timezone string
} }
@@ -19,28 +19,28 @@ func (s System) validate() (err error) {
func (s *System) copy() (copied System) { func (s *System) copy() (copied System) {
return System{ return System{
PUID: helpers.CopyUint32Ptr(s.PUID), PUID: helpers.CopyUint16Ptr(s.PUID),
PGID: helpers.CopyUint32Ptr(s.PGID), PGID: helpers.CopyUint16Ptr(s.PGID),
Timezone: s.Timezone, Timezone: s.Timezone,
} }
} }
func (s *System) mergeWith(other System) { func (s *System) mergeWith(other System) {
s.PUID = helpers.MergeWithUint32(s.PUID, other.PUID) s.PUID = helpers.MergeWithUint16(s.PUID, other.PUID)
s.PGID = helpers.MergeWithUint32(s.PGID, other.PGID) s.PGID = helpers.MergeWithUint16(s.PGID, other.PGID)
s.Timezone = helpers.MergeWithString(s.Timezone, other.Timezone) s.Timezone = helpers.MergeWithString(s.Timezone, other.Timezone)
} }
func (s *System) overrideWith(other System) { func (s *System) overrideWith(other System) {
s.PUID = helpers.OverrideWithUint32(s.PUID, other.PUID) s.PUID = helpers.OverrideWithUint16(s.PUID, other.PUID)
s.PGID = helpers.OverrideWithUint32(s.PGID, other.PGID) s.PGID = helpers.OverrideWithUint16(s.PGID, other.PGID)
s.Timezone = helpers.OverrideWithString(s.Timezone, other.Timezone) s.Timezone = helpers.OverrideWithString(s.Timezone, other.Timezone)
} }
func (s *System) setDefaults() { func (s *System) setDefaults() {
const defaultID = 1000 const defaultID = 1000
s.PUID = helpers.DefaultUint32(s.PUID, defaultID) s.PUID = helpers.DefaultUint16(s.PUID, defaultID)
s.PGID = helpers.DefaultUint32(s.PGID, defaultID) s.PGID = helpers.DefaultUint16(s.PGID, defaultID)
} }
func (s System) String() string { func (s System) String() string {

View File

@@ -7,7 +7,7 @@ import (
"time" "time"
"github.com/qdm12/gluetun/internal/configuration/settings/helpers" "github.com/qdm12/gluetun/internal/configuration/settings/helpers"
"github.com/qdm12/gluetun/internal/constants/providers" "github.com/qdm12/gluetun/internal/constants"
"github.com/qdm12/gotree" "github.com/qdm12/gotree"
) )
@@ -42,8 +42,8 @@ func (u Updater) Validate() (err error) {
for i, provider := range u.Providers { for i, provider := range u.Providers {
valid := false valid := false
for _, validProvider := range providers.All() { for _, validProvider := range constants.AllProviders() {
if validProvider == providers.Custom { if validProvider == constants.Custom {
continue continue
} }
@@ -93,7 +93,7 @@ func (u *Updater) SetDefaults(vpnProvider string) {
u.Period = helpers.DefaultDuration(u.Period, 0) u.Period = helpers.DefaultDuration(u.Period, 0)
u.DNSAddress = helpers.DefaultIP(u.DNSAddress, net.IPv4(1, 1, 1, 1)) u.DNSAddress = helpers.DefaultIP(u.DNSAddress, net.IPv4(1, 1, 1, 1))
u.CLI = helpers.DefaultBool(u.CLI, false) u.CLI = helpers.DefaultBool(u.CLI, false)
if len(u.Providers) == 0 && vpnProvider != providers.Custom { if len(u.Providers) == 0 && vpnProvider != constants.Custom {
u.Providers = []string{vpnProvider} u.Providers = []string{vpnProvider}
} }
} }

View File

@@ -0,0 +1,21 @@
package validation
import (
"github.com/qdm12/gluetun/internal/models"
)
func CyberghostCountryChoices(servers []models.CyberghostServer) (choices []string) {
choices = make([]string, len(servers))
for i := range servers {
choices[i] = servers[i].Country
}
return makeUnique(choices)
}
func CyberghostHostnameChoices(servers []models.CyberghostServer) (choices []string) {
choices = make([]string, len(servers))
for i := range servers {
choices[i] = servers[i].Hostname
}
return makeUnique(choices)
}

View File

@@ -0,0 +1,29 @@
package validation
import (
"github.com/qdm12/gluetun/internal/models"
)
func ExpressvpnCountriesChoices(servers []models.ExpressvpnServer) (choices []string) {
choices = make([]string, len(servers))
for i := range servers {
choices[i] = servers[i].Country
}
return makeUnique(choices)
}
func ExpressvpnCityChoices(servers []models.ExpressvpnServer) (choices []string) {
choices = make([]string, len(servers))
for i := range servers {
choices[i] = servers[i].City
}
return makeUnique(choices)
}
func ExpressvpnHostnameChoices(servers []models.ExpressvpnServer) (choices []string) {
choices = make([]string, len(servers))
for i := range servers {
choices[i] = servers[i].Hostname
}
return makeUnique(choices)
}

View File

@@ -0,0 +1,21 @@
package validation
import (
"github.com/qdm12/gluetun/internal/models"
)
func FastestvpnCountriesChoices(servers []models.FastestvpnServer) (choices []string) {
choices = make([]string, len(servers))
for i := range servers {
choices[i] = servers[i].Country
}
return makeUnique(choices)
}
func FastestvpnHostnameChoices(servers []models.FastestvpnServer) (choices []string) {
choices = make([]string, len(servers))
for i := range servers {
choices[i] = servers[i].Hostname
}
return makeUnique(choices)
}

View File

@@ -0,0 +1,23 @@
package validation
import "sort"
func makeUnique(choices []string) (uniqueChoices []string) {
seen := make(map[string]struct{}, len(choices))
uniqueChoices = make([]string, 0, len(uniqueChoices))
for _, choice := range choices {
if _, ok := seen[choice]; ok {
continue
}
seen[choice] = struct{}{}
uniqueChoices = append(uniqueChoices, choice)
}
sort.Slice(uniqueChoices, func(i, j int) bool {
return uniqueChoices[i] < uniqueChoices[j]
})
return uniqueChoices
}

View File

@@ -0,0 +1,37 @@
package validation
import (
"github.com/qdm12/gluetun/internal/models"
)
func HideMyAssCountryChoices(servers []models.HideMyAssServer) (choices []string) {
choices = make([]string, len(servers))
for i := range servers {
choices[i] = servers[i].Country
}
return makeUnique(choices)
}
func HideMyAssRegionChoices(servers []models.HideMyAssServer) (choices []string) {
choices = make([]string, len(servers))
for i := range servers {
choices[i] = servers[i].Region
}
return makeUnique(choices)
}
func HideMyAssCityChoices(servers []models.HideMyAssServer) (choices []string) {
choices = make([]string, len(servers))
for i := range servers {
choices[i] = servers[i].City
}
return makeUnique(choices)
}
func HideMyAssHostnameChoices(servers []models.HideMyAssServer) (choices []string) {
choices = make([]string, len(servers))
for i := range servers {
choices[i] = servers[i].Hostname
}
return makeUnique(choices)
}

View File

@@ -0,0 +1,29 @@
package validation
import (
"github.com/qdm12/gluetun/internal/models"
)
func IpvanishCountryChoices(servers []models.IpvanishServer) (choices []string) {
choices = make([]string, len(servers))
for i := range servers {
choices[i] = servers[i].Country
}
return makeUnique(choices)
}
func IpvanishCityChoices(servers []models.IpvanishServer) (choices []string) {
choices = make([]string, len(servers))
for i := range servers {
choices[i] = servers[i].City
}
return makeUnique(choices)
}
func IpvanishHostnameChoices(servers []models.IpvanishServer) (choices []string) {
choices = make([]string, len(servers))
for i := range servers {
choices[i] = servers[i].Hostname
}
return makeUnique(choices)
}

View File

@@ -0,0 +1,37 @@
package validation
import (
"github.com/qdm12/gluetun/internal/models"
)
func IvpnCountryChoices(servers []models.IvpnServer) (choices []string) {
choices = make([]string, len(servers))
for i := range servers {
choices[i] = servers[i].Country
}
return makeUnique(choices)
}
func IvpnCityChoices(servers []models.IvpnServer) (choices []string) {
choices = make([]string, len(servers))
for i := range servers {
choices[i] = servers[i].City
}
return makeUnique(choices)
}
func IvpnISPChoices(servers []models.IvpnServer) (choices []string) {
choices = make([]string, len(servers))
for i := range servers {
choices[i] = servers[i].ISP
}
return makeUnique(choices)
}
func IvpnHostnameChoices(servers []models.IvpnServer) (choices []string) {
choices = make([]string, len(servers))
for i := range servers {
choices[i] = servers[i].Hostname
}
return makeUnique(choices)
}

View File

@@ -0,0 +1,37 @@
package validation
import (
"github.com/qdm12/gluetun/internal/models"
)
func MullvadCountryChoices(servers []models.MullvadServer) (choices []string) {
choices = make([]string, len(servers))
for i := range servers {
choices[i] = servers[i].Country
}
return makeUnique(choices)
}
func MullvadCityChoices(servers []models.MullvadServer) (choices []string) {
choices = make([]string, len(servers))
for i := range servers {
choices[i] = servers[i].City
}
return makeUnique(choices)
}
func MullvadHostnameChoices(servers []models.MullvadServer) (choices []string) {
choices = make([]string, len(servers))
for i := range servers {
choices[i] = servers[i].Hostname
}
return makeUnique(choices)
}
func MullvadISPChoices(servers []models.MullvadServer) (choices []string) {
choices = make([]string, len(servers))
for i := range servers {
choices[i] = servers[i].ISP
}
return makeUnique(choices)
}

View File

@@ -0,0 +1,21 @@
package validation
import (
"github.com/qdm12/gluetun/internal/models"
)
func NordvpnRegionChoices(servers []models.NordvpnServer) (choices []string) {
choices = make([]string, len(servers))
for i := range servers {
choices[i] = servers[i].Region
}
return makeUnique(choices)
}
func NordvpnHostnameChoices(servers []models.NordvpnServer) (choices []string) {
choices = make([]string, len(servers))
for i := range servers {
choices[i] = servers[i].Hostname
}
return makeUnique(choices)
}

View File

@@ -0,0 +1,13 @@
package validation
import (
"github.com/qdm12/gluetun/internal/models"
)
func PerfectprivacyCityChoices(servers []models.PerfectprivacyServer) (choices []string) {
choices = make([]string, len(servers))
for i := range servers {
choices[i] = servers[i].City
}
return makeUnique(choices)
}

View File

@@ -0,0 +1,29 @@
package validation
import (
"github.com/qdm12/gluetun/internal/models"
)
func PIAGeoChoices(servers []models.PIAServer) (choices []string) {
choices = make([]string, len(servers))
for i := range servers {
choices[i] = servers[i].Region
}
return makeUnique(choices)
}
func PIAHostnameChoices(servers []models.PIAServer) (choices []string) {
choices = make([]string, len(servers))
for i := range servers {
choices[i] = servers[i].Hostname
}
return makeUnique(choices)
}
func PIANameChoices(servers []models.PIAServer) (choices []string) {
choices = make([]string, len(servers))
for i := range servers {
choices[i] = servers[i].ServerName
}
return makeUnique(choices)
}

View File

@@ -0,0 +1,35 @@
package validation
import "github.com/qdm12/gluetun/internal/models"
func PrivadoCountryChoices(servers []models.PrivadoServer) (choices []string) {
choices = make([]string, len(servers))
for i := range servers {
choices[i] = servers[i].Country
}
return makeUnique(choices)
}
func PrivadoRegionChoices(servers []models.PrivadoServer) (choices []string) {
choices = make([]string, len(servers))
for i := range servers {
choices[i] = servers[i].Region
}
return makeUnique(choices)
}
func PrivadoCityChoices(servers []models.PrivadoServer) (choices []string) {
choices = make([]string, len(servers))
for i := range servers {
choices[i] = servers[i].City
}
return makeUnique(choices)
}
func PrivadoHostnameChoices(servers []models.PrivadoServer) (choices []string) {
choices = make([]string, len(servers))
for i := range servers {
choices[i] = servers[i].Hostname
}
return makeUnique(choices)
}

View File

@@ -0,0 +1,27 @@
package validation
import "github.com/qdm12/gluetun/internal/models"
func PrivatevpnCountryChoices(servers []models.PrivatevpnServer) (choices []string) {
choices = make([]string, len(servers))
for i := range servers {
choices[i] = servers[i].Country
}
return makeUnique(choices)
}
func PrivatevpnCityChoices(servers []models.PrivatevpnServer) (choices []string) {
choices = make([]string, len(servers))
for i := range servers {
choices[i] = servers[i].City
}
return makeUnique(choices)
}
func PrivatevpnHostnameChoices(servers []models.PrivatevpnServer) (choices []string) {
choices = make([]string, len(servers))
for i := range servers {
choices[i] = servers[i].Hostname
}
return makeUnique(choices)
}

View File

@@ -0,0 +1,43 @@
package validation
import "github.com/qdm12/gluetun/internal/models"
func ProtonvpnCountryChoices(servers []models.ProtonvpnServer) (choices []string) {
choices = make([]string, len(servers))
for i := range servers {
choices[i] = servers[i].Country
}
return makeUnique(choices)
}
func ProtonvpnRegionChoices(servers []models.ProtonvpnServer) (choices []string) {
choices = make([]string, len(servers))
for i := range servers {
choices[i] = servers[i].Region
}
return makeUnique(choices)
}
func ProtonvpnCityChoices(servers []models.ProtonvpnServer) (choices []string) {
choices = make([]string, len(servers))
for i := range servers {
choices[i] = servers[i].City
}
return makeUnique(choices)
}
func ProtonvpnNameChoices(servers []models.ProtonvpnServer) (choices []string) {
choices = make([]string, len(servers))
for i := range servers {
choices[i] = servers[i].Name
}
return makeUnique(choices)
}
func ProtonvpnHostnameChoices(servers []models.ProtonvpnServer) (choices []string) {
choices = make([]string, len(servers))
for i := range servers {
choices[i] = servers[i].Hostname
}
return makeUnique(choices)
}

View File

@@ -0,0 +1,35 @@
package validation
import "github.com/qdm12/gluetun/internal/models"
func PurevpnRegionChoices(servers []models.PurevpnServer) (choices []string) {
choices = make([]string, len(servers))
for i := range servers {
choices[i] = servers[i].Region
}
return makeUnique(choices)
}
func PurevpnCountryChoices(servers []models.PurevpnServer) (choices []string) {
choices = make([]string, len(servers))
for i := range servers {
choices[i] = servers[i].Country
}
return makeUnique(choices)
}
func PurevpnCityChoices(servers []models.PurevpnServer) (choices []string) {
choices = make([]string, len(servers))
for i := range servers {
choices[i] = servers[i].City
}
return makeUnique(choices)
}
func PurevpnHostnameChoices(servers []models.PurevpnServer) (choices []string) {
choices = make([]string, len(servers))
for i := range servers {
choices[i] = servers[i].Hostname
}
return makeUnique(choices)
}

View File

@@ -1,111 +0,0 @@
package validation
import (
"sort"
"github.com/qdm12/gluetun/internal/models"
)
func sortedInsert(ss []string, s string) []string {
i := sort.SearchStrings(ss, s)
ss = append(ss, "")
copy(ss[i+1:], ss[i:])
ss[i] = s
return ss
}
func ExtractCountries(servers []models.Server) (values []string) {
seen := make(map[string]struct{}, len(servers))
values = make([]string, 0, len(servers))
for _, server := range servers {
value := server.Country
_, alreadySeen := seen[value]
if alreadySeen {
continue
}
seen[value] = struct{}{}
values = sortedInsert(values, value)
}
return values
}
func ExtractRegions(servers []models.Server) (values []string) {
seen := make(map[string]struct{}, len(servers))
values = make([]string, 0, len(servers))
for _, server := range servers {
value := server.Region
_, alreadySeen := seen[value]
if alreadySeen {
continue
}
seen[value] = struct{}{}
values = sortedInsert(values, value)
}
return values
}
func ExtractCities(servers []models.Server) (values []string) {
seen := make(map[string]struct{}, len(servers))
values = make([]string, 0, len(servers))
for _, server := range servers {
value := server.City
_, alreadySeen := seen[value]
if alreadySeen {
continue
}
seen[value] = struct{}{}
values = sortedInsert(values, value)
}
return values
}
func ExtractISPs(servers []models.Server) (values []string) {
seen := make(map[string]struct{}, len(servers))
values = make([]string, 0, len(servers))
for _, server := range servers {
value := server.ISP
_, alreadySeen := seen[value]
if alreadySeen {
continue
}
seen[value] = struct{}{}
values = sortedInsert(values, value)
}
return values
}
func ExtractServerNames(servers []models.Server) (values []string) {
seen := make(map[string]struct{}, len(servers))
values = make([]string, 0, len(servers))
for _, server := range servers {
value := server.ServerName
_, alreadySeen := seen[value]
if alreadySeen {
continue
}
seen[value] = struct{}{}
values = sortedInsert(values, value)
}
return values
}
func ExtractHostnames(servers []models.Server) (values []string) {
seen := make(map[string]struct{}, len(servers))
values = make([]string, 0, len(servers))
for _, server := range servers {
value := server.Hostname
_, alreadySeen := seen[value]
if alreadySeen {
continue
}
seen[value] = struct{}{}
values = sortedInsert(values, value)
}
return values
}

View File

@@ -1,9 +1,44 @@
package validation package validation
import ( import (
"sort"
"github.com/qdm12/gluetun/internal/constants" "github.com/qdm12/gluetun/internal/constants"
"github.com/qdm12/gluetun/internal/models"
) )
func SurfsharkRegionChoices(servers []models.SurfsharkServer) (choices []string) {
choices = make([]string, len(servers))
for i := range servers {
choices[i] = servers[i].Region
}
return makeUnique(choices)
}
func SurfsharkCountryChoices(servers []models.SurfsharkServer) (choices []string) {
choices = make([]string, len(servers))
for i := range servers {
choices[i] = servers[i].Country
}
return makeUnique(choices)
}
func SurfsharkCityChoices(servers []models.SurfsharkServer) (choices []string) {
choices = make([]string, len(servers))
for i := range servers {
choices[i] = servers[i].City
}
return makeUnique(choices)
}
func SurfsharkHostnameChoices(servers []models.SurfsharkServer) (choices []string) {
choices = make([]string, len(servers))
for i := range servers {
choices[i] = servers[i].Hostname
}
return makeUnique(choices)
}
// TODO remove in v4. // TODO remove in v4.
func SurfsharkRetroLocChoices() (choices []string) { func SurfsharkRetroLocChoices() (choices []string) {
locationData := constants.SurfsharkLocationData() locationData := constants.SurfsharkLocationData()
@@ -14,8 +49,12 @@ func SurfsharkRetroLocChoices() (choices []string) {
continue continue
} }
seen[data.RetroLoc] = struct{}{} seen[data.RetroLoc] = struct{}{}
choices = sortedInsert(choices, data.RetroLoc) choices = append(choices, data.RetroLoc)
} }
sort.Slice(choices, func(i, j int) bool {
return choices[i] < choices[j]
})
return choices return choices
} }

View File

@@ -0,0 +1,29 @@
package validation
import (
"github.com/qdm12/gluetun/internal/models"
)
func TorguardCountryChoices(servers []models.TorguardServer) (choices []string) {
choices = make([]string, len(servers))
for i := range servers {
choices[i] = servers[i].Country
}
return makeUnique(choices)
}
func TorguardCityChoices(servers []models.TorguardServer) (choices []string) {
choices = make([]string, len(servers))
for i := range servers {
choices[i] = servers[i].City
}
return makeUnique(choices)
}
func TorguardHostnameChoices(servers []models.TorguardServer) (choices []string) {
choices = make([]string, len(servers))
for i := range servers {
choices[i] = servers[i].Hostname
}
return makeUnique(choices)
}

View File

@@ -0,0 +1,29 @@
package validation
import (
"github.com/qdm12/gluetun/internal/models"
)
func VPNUnlimitedCountryChoices(servers []models.VPNUnlimitedServer) (choices []string) {
choices = make([]string, len(servers))
for i := range servers {
choices[i] = servers[i].Country
}
return makeUnique(choices)
}
func VPNUnlimitedCityChoices(servers []models.VPNUnlimitedServer) (choices []string) {
choices = make([]string, len(servers))
for i := range servers {
choices[i] = servers[i].City
}
return makeUnique(choices)
}
func VPNUnlimitedHostnameChoices(servers []models.VPNUnlimitedServer) (choices []string) {
choices = make([]string, len(servers))
for i := range servers {
choices[i] = servers[i].Hostname
}
return makeUnique(choices)
}

View File

@@ -0,0 +1,13 @@
package validation
import (
"github.com/qdm12/gluetun/internal/models"
)
func VyprvpnRegionChoices(servers []models.VyprvpnServer) (choices []string) {
choices = make([]string, len(servers))
for i := range servers {
choices[i] = servers[i].Region
}
return makeUnique(choices)
}

View File

@@ -0,0 +1,19 @@
package validation
import "github.com/qdm12/gluetun/internal/models"
func WevpnCityChoices(servers []models.WevpnServer) (choices []string) {
choices = make([]string, len(servers))
for i := range servers {
choices[i] = servers[i].City
}
return makeUnique(choices)
}
func WevpnHostnameChoices(servers []models.WevpnServer) (choices []string) {
choices = make([]string, len(servers))
for i := range servers {
choices[i] = servers[i].Hostname
}
return makeUnique(choices)
}

View File

@@ -0,0 +1,27 @@
package validation
import "github.com/qdm12/gluetun/internal/models"
func WindscribeRegionChoices(servers []models.WindscribeServer) (choices []string) {
choices = make([]string, len(servers))
for i := range servers {
choices[i] = servers[i].Region
}
return makeUnique(choices)
}
func WindscribeCityChoices(servers []models.WindscribeServer) (choices []string) {
choices = make([]string, len(servers))
for i := range servers {
choices[i] = servers[i].City
}
return makeUnique(choices)
}
func WindscribeHostnameChoices(servers []models.WindscribeServer) (choices []string) {
choices = make([]string, len(servers))
for i := range servers {
choices[i] = servers[i].Hostname
}
return makeUnique(choices)
}

View File

@@ -5,7 +5,7 @@ import (
"strings" "strings"
"github.com/qdm12/gluetun/internal/configuration/settings/helpers" "github.com/qdm12/gluetun/internal/configuration/settings/helpers"
"github.com/qdm12/gluetun/internal/constants/vpn" "github.com/qdm12/gluetun/internal/constants"
"github.com/qdm12/gluetun/internal/models" "github.com/qdm12/gluetun/internal/models"
"github.com/qdm12/gotree" "github.com/qdm12/gotree"
) )
@@ -23,7 +23,7 @@ type VPN struct {
// TODO v4 remove pointer for receiver (because of Surfshark). // TODO v4 remove pointer for receiver (because of Surfshark).
func (v *VPN) validate(allServers models.AllServers) (err error) { func (v *VPN) validate(allServers models.AllServers) (err error) {
// Validate Type // Validate Type
validVPNTypes := []string{vpn.OpenVPN, vpn.Wireguard} validVPNTypes := []string{constants.OpenVPN, constants.Wireguard}
if !helpers.IsOneOf(v.Type, validVPNTypes...) { if !helpers.IsOneOf(v.Type, validVPNTypes...) {
return fmt.Errorf("%w: %q and can only be one of %s", return fmt.Errorf("%w: %q and can only be one of %s",
ErrVPNTypeNotValid, v.Type, strings.Join(validVPNTypes, ", ")) ErrVPNTypeNotValid, v.Type, strings.Join(validVPNTypes, ", "))
@@ -34,7 +34,7 @@ func (v *VPN) validate(allServers models.AllServers) (err error) {
return fmt.Errorf("provider settings: %w", err) return fmt.Errorf("provider settings: %w", err)
} }
if v.Type == vpn.OpenVPN { if v.Type == constants.OpenVPN {
err := v.OpenVPN.validate(*v.Provider.Name) err := v.OpenVPN.validate(*v.Provider.Name)
if err != nil { if err != nil {
return fmt.Errorf("OpenVPN settings: %w", err) return fmt.Errorf("OpenVPN settings: %w", err)
@@ -73,7 +73,7 @@ func (v *VPN) overrideWith(other VPN) {
} }
func (v *VPN) setDefaults() { func (v *VPN) setDefaults() {
v.Type = helpers.DefaultString(v.Type, vpn.OpenVPN) v.Type = helpers.DefaultString(v.Type, constants.OpenVPN)
v.Provider.setDefaults() v.Provider.setDefaults()
v.OpenVPN.setDefaults(*v.Provider.Name) v.OpenVPN.setDefaults(*v.Provider.Name)
v.Wireguard.setDefaults() v.Wireguard.setDefaults()
@@ -88,7 +88,7 @@ func (v VPN) toLinesNode() (node *gotree.Node) {
node.AppendNode(v.Provider.toLinesNode()) node.AppendNode(v.Provider.toLinesNode())
if v.Type == vpn.OpenVPN { if v.Type == constants.OpenVPN {
node.AppendNode(v.OpenVPN.toLinesNode()) node.AppendNode(v.OpenVPN.toLinesNode())
} else { } else {
node.AppendNode(v.Wireguard.toLinesNode()) node.AppendNode(v.Wireguard.toLinesNode())

View File

@@ -6,7 +6,7 @@ import (
"regexp" "regexp"
"github.com/qdm12/gluetun/internal/configuration/settings/helpers" "github.com/qdm12/gluetun/internal/configuration/settings/helpers"
"github.com/qdm12/gluetun/internal/constants/providers" "github.com/qdm12/gluetun/internal/constants"
"github.com/qdm12/gotree" "github.com/qdm12/gotree"
"golang.zx2c4.com/wireguard/wgctrl/wgtypes" "golang.zx2c4.com/wireguard/wgctrl/wgtypes"
) )
@@ -35,10 +35,10 @@ var regexpInterfaceName = regexp.MustCompile(`^[a-zA-Z0-9_]+$`)
// It should only be ran if the VPN type chosen is Wireguard. // It should only be ran if the VPN type chosen is Wireguard.
func (w Wireguard) validate(vpnProvider string) (err error) { func (w Wireguard) validate(vpnProvider string) (err error) {
if !helpers.IsOneOf(vpnProvider, if !helpers.IsOneOf(vpnProvider,
providers.Custom, constants.Custom,
providers.Ivpn, constants.Ivpn,
providers.Mullvad, constants.Mullvad,
providers.Windscribe, constants.Windscribe,
) { ) {
// do not validate for VPN provider not supporting Wireguard // do not validate for VPN provider not supporting Wireguard
return nil return nil

View File

@@ -5,7 +5,7 @@ import (
"net" "net"
"github.com/qdm12/gluetun/internal/configuration/settings/helpers" "github.com/qdm12/gluetun/internal/configuration/settings/helpers"
"github.com/qdm12/gluetun/internal/constants/providers" "github.com/qdm12/gluetun/internal/constants"
"github.com/qdm12/gotree" "github.com/qdm12/gotree"
"golang.zx2c4.com/wireguard/wgctrl/wgtypes" "golang.zx2c4.com/wireguard/wgctrl/wgtypes"
) )
@@ -36,8 +36,8 @@ type WireguardSelection struct {
func (w WireguardSelection) validate(vpnProvider string) (err error) { func (w WireguardSelection) validate(vpnProvider string) (err error) {
// Validate EndpointIP // Validate EndpointIP
switch vpnProvider { switch vpnProvider {
case providers.Ivpn, providers.Mullvad, providers.Windscribe: // endpoint IP addresses are baked in case constants.Ivpn, constants.Mullvad, constants.Windscribe: // endpoint IP addresses are baked in
case providers.Custom: case constants.Custom:
if len(w.EndpointIP) == 0 { if len(w.EndpointIP) == 0 {
return ErrWireguardEndpointIPNotSet return ErrWireguardEndpointIPNotSet
} }
@@ -47,23 +47,23 @@ func (w WireguardSelection) validate(vpnProvider string) (err error) {
// Validate EndpointPort // Validate EndpointPort
switch vpnProvider { switch vpnProvider {
// EndpointPort is required // EndpointPort is required
case providers.Custom: case constants.Custom:
if *w.EndpointPort == 0 { if *w.EndpointPort == 0 {
return ErrWireguardEndpointPortNotSet return ErrWireguardEndpointPortNotSet
} }
case providers.Ivpn, providers.Mullvad, providers.Windscribe: case constants.Ivpn, constants.Mullvad, constants.Windscribe:
// EndpointPort is optional and can be 0 // EndpointPort is optional and can be 0
if *w.EndpointPort == 0 { if *w.EndpointPort == 0 {
break // no custom endpoint port set break // no custom endpoint port set
} }
if vpnProvider == providers.Mullvad { if vpnProvider == constants.Mullvad {
break // no restriction on custom endpoint port value break // no restriction on custom endpoint port value
} }
var allowed []uint16 var allowed []uint16
switch vpnProvider { switch vpnProvider {
case providers.Ivpn: case constants.Ivpn:
allowed = []uint16{2049, 2050, 53, 30587, 41893, 48574, 58237} allowed = []uint16{2049, 2050, 53, 30587, 41893, 48574, 58237}
case providers.Windscribe: case constants.Windscribe:
allowed = []uint16{53, 80, 123, 443, 1194, 65142} allowed = []uint16{53, 80, 123, 443, 1194, 65142}
} }
@@ -78,8 +78,8 @@ func (w WireguardSelection) validate(vpnProvider string) (err error) {
// Validate PublicKey // Validate PublicKey
switch vpnProvider { switch vpnProvider {
case providers.Ivpn, providers.Mullvad, providers.Windscribe: // public keys are baked in case constants.Ivpn, constants.Mullvad, constants.Windscribe: // public keys are baked in
case providers.Custom: case constants.Custom:
if w.PublicKey == "" { if w.PublicKey == "" {
return ErrWireguardPublicKeyNotSet return ErrWireguardPublicKeyNotSet
} }

View File

@@ -19,7 +19,7 @@ func (r *Reader) ReadHealth() (health settings.Health, err error) {
return health, err return health, err
} }
health.VPN.Addition, err = r.readDurationWithRetro( health.VPN.Initial, err = r.readDurationWithRetro(
"HEALTH_VPN_DURATION_ADDITION", "HEALTH_VPN_DURATION_ADDITION",
"HEALTH_OPENVPN_DURATION_ADDITION") "HEALTH_OPENVPN_DURATION_ADDITION")
if err != nil { if err != nil {

View File

@@ -135,5 +135,5 @@ func unsetEnvKeys(envKeys []string, err error) (newErr error) {
} }
func stringPtr(s string) *string { return &s } func stringPtr(s string) *string { return &s }
func uint32Ptr(n uint32) *uint32 { return &n } func uint16Ptr(n uint16) *uint16 { return &n }
func boolPtr(b bool) *bool { return &b } func boolPtr(b bool) *bool { return &b }

View File

@@ -20,7 +20,3 @@ func setTestEnv(t *testing.T, key, value string) {
}) })
require.NoError(t, err) require.NoError(t, err)
} }
func TestXxx(t *testing.T) {
t.Log(int(^uint32(0)))
}

View File

@@ -7,7 +7,7 @@ import (
"strings" "strings"
"github.com/qdm12/gluetun/internal/configuration/settings" "github.com/qdm12/gluetun/internal/configuration/settings"
"github.com/qdm12/log" "github.com/qdm12/golibs/logging"
) )
func readLog() (log settings.Log, err error) { func readLog() (log settings.Log, err error) {
@@ -19,13 +19,13 @@ func readLog() (log settings.Log, err error) {
return log, nil return log, nil
} }
func readLogLevel() (level *log.Level, err error) { func readLogLevel() (level *logging.Level, err error) {
s := os.Getenv("LOG_LEVEL") s := os.Getenv("LOG_LEVEL")
if s == "" { if s == "" {
return nil, nil //nolint:nilnil return nil, nil //nolint:nilnil
} }
level = new(log.Level) level = new(logging.Level)
*level, err = parseLogLevel(s) *level, err = parseLogLevel(s)
if err != nil { if err != nil {
return nil, fmt.Errorf("environment variable LOG_LEVEL: %w", err) return nil, fmt.Errorf("environment variable LOG_LEVEL: %w", err)
@@ -36,16 +36,16 @@ func readLogLevel() (level *log.Level, err error) {
var ErrLogLevelUnknown = errors.New("log level is unknown") var ErrLogLevelUnknown = errors.New("log level is unknown")
func parseLogLevel(s string) (level log.Level, err error) { func parseLogLevel(s string) (level logging.Level, err error) {
switch strings.ToLower(s) { switch strings.ToLower(s) {
case "debug": case "debug":
return log.LevelDebug, nil return logging.LevelDebug, nil
case "info": case "info":
return log.LevelInfo, nil return logging.LevelInfo, nil
case "warning": case "warning":
return log.LevelWarn, nil return logging.LevelWarn, nil
case "error": case "error":
return log.LevelError, nil return logging.LevelError, nil
default: default:
return level, fmt.Errorf( return level, fmt.Errorf(
"%w: %q is not valid and can be one of debug, info, warning or error", "%w: %q is not valid and can be one of debug, info, warning or error",

View File

@@ -65,11 +65,6 @@ func (r *Reader) readOpenVPN() (
return openVPN, fmt.Errorf("environment variable OPENVPN_VERBOSITY: %w", err) return openVPN, fmt.Errorf("environment variable OPENVPN_VERBOSITY: %w", err)
} }
flagsStr := os.Getenv("OPENVPN_FLAGS")
if flagsStr != "" {
openVPN.Flags = strings.Fields(flagsStr)
}
return openVPN, nil return openVPN, nil
} }

View File

@@ -6,8 +6,7 @@ import (
"strings" "strings"
"github.com/qdm12/gluetun/internal/configuration/settings" "github.com/qdm12/gluetun/internal/configuration/settings"
"github.com/qdm12/gluetun/internal/constants/providers" "github.com/qdm12/gluetun/internal/constants"
"github.com/qdm12/gluetun/internal/constants/vpn"
) )
func (r *Reader) readProvider(vpnType string) (provider settings.Provider, err error) { func (r *Reader) readProvider(vpnType string) (provider settings.Provider, err error) {
@@ -34,13 +33,13 @@ func (r *Reader) readVPNServiceProvider(vpnType string) (vpnProviderPtr *string)
_, s := r.getEnvWithRetro("VPN_SERVICE_PROVIDER", "VPNSP") _, s := r.getEnvWithRetro("VPN_SERVICE_PROVIDER", "VPNSP")
s = strings.ToLower(s) s = strings.ToLower(s)
switch { switch {
case vpnType != vpn.Wireguard && case vpnType != constants.Wireguard &&
os.Getenv("OPENVPN_CUSTOM_CONFIG") != "": // retro compatibility os.Getenv("OPENVPN_CUSTOM_CONFIG") != "": // retro compatibility
return stringPtr(providers.Custom) return stringPtr(constants.Custom)
case s == "": case s == "":
return nil return nil
case s == "pia": // retro compatibility case s == "pia": // retro compatibility
return stringPtr(providers.PrivateInternetAccess) return stringPtr(constants.PrivateInternetAccess)
} }
return stringPtr(s) return stringPtr(s)
} }

View File

@@ -9,7 +9,7 @@ import (
"strings" "strings"
"github.com/qdm12/gluetun/internal/configuration/settings" "github.com/qdm12/gluetun/internal/configuration/settings"
"github.com/qdm12/gluetun/internal/constants/providers" "github.com/qdm12/gluetun/internal/constants"
) )
var ( var (
@@ -27,7 +27,7 @@ func (r *Reader) readServerSelection(vpnProvider, vpnType string) (
countriesKey, _ := r.getEnvWithRetro("SERVER_COUNTRIES", "COUNTRY") countriesKey, _ := r.getEnvWithRetro("SERVER_COUNTRIES", "COUNTRY")
ss.Countries = envToCSV(countriesKey) ss.Countries = envToCSV(countriesKey)
if vpnProvider == providers.Cyberghost && len(ss.Countries) == 0 { if vpnProvider == constants.Cyberghost && len(ss.Countries) == 0 {
// Retro-compatibility for Cyberghost using the REGION variable // Retro-compatibility for Cyberghost using the REGION variable
ss.Countries = envToCSV("REGION") ss.Countries = envToCSV("REGION")
if len(ss.Countries) > 0 { if len(ss.Countries) > 0 {

View File

@@ -34,23 +34,20 @@ func (r *Reader) readSystem() (system settings.System, err error) {
var ErrSystemIDNotValid = errors.New("system ID is not valid") var ErrSystemIDNotValid = errors.New("system ID is not valid")
func (r *Reader) readID(key, retroKey string) ( func (r *Reader) readID(key, retroKey string) (
id *uint32, err error) { id *uint16, err error) {
idEnvKey, idString := r.getEnvWithRetro(key, retroKey) idEnvKey, idString := r.getEnvWithRetro(key, retroKey)
if idString == "" { if idString == "" {
return nil, nil //nolint:nilnil return nil, nil //nolint:nilnil
} }
const base = 10 idInt, err := strconv.Atoi(idString)
const bitSize = 64
const max = uint64(^uint32(0))
idUint64, err := strconv.ParseUint(idString, base, bitSize)
if err != nil { if err != nil {
return nil, fmt.Errorf("environment variable %s: %w: %s", return nil, fmt.Errorf("environment variable %s: %w: %s: %s",
idEnvKey, ErrSystemIDNotValid, err) idEnvKey, ErrSystemIDNotValid, idString, err)
} else if idUint64 > max { } else if idInt < 0 || idInt > 65535 {
return nil, fmt.Errorf("environment variable %s: %w: %d: must be between 0 and %d", return nil, fmt.Errorf("environment variable %s: %w: %d: must be between 0 and 65535",
idEnvKey, ErrSystemIDNotValid, idUint64, max) idEnvKey, ErrSystemIDNotValid, idInt)
} }
return uint32Ptr(uint32(idUint64)), nil return uint16Ptr(uint16(idInt)), nil
} }

View File

@@ -14,51 +14,15 @@ func Test_Reader_readID(t *testing.T) {
keyValue string keyValue string
retroKeyPrefix string retroKeyPrefix string
retroValue string retroValue string
id *uint32 id *uint16
errWrapped error errWrapped error
errMessage string errMessage string
}{ }{
"empty string": {
keyPrefix: "ID",
retroKeyPrefix: "RETRO_ID",
},
"invalid string": {
keyPrefix: "ID",
keyValue: "invalid",
retroKeyPrefix: "RETRO_ID",
errWrapped: ErrSystemIDNotValid,
errMessage: `environment variable IDTest_Reader_readID/invalid_string: ` +
`system ID is not valid: ` +
`strconv.ParseUint: parsing "invalid": invalid syntax`,
},
"negative number": {
keyPrefix: "ID",
keyValue: "-1",
retroKeyPrefix: "RETRO_ID",
errWrapped: ErrSystemIDNotValid,
errMessage: `environment variable IDTest_Reader_readID/negative_number: ` +
`system ID is not valid: ` +
`strconv.ParseUint: parsing "-1": invalid syntax`,
},
"id 1000": { "id 1000": {
keyPrefix: "ID", keyPrefix: "ID",
keyValue: "1000", keyValue: "1000",
retroKeyPrefix: "RETRO_ID", retroKeyPrefix: "RETRO_ID",
id: uint32Ptr(1000), id: uint16Ptr(1000),
},
"max id": {
keyPrefix: "ID",
keyValue: "4294967295",
retroKeyPrefix: "RETRO_ID",
id: uint32Ptr(4294967295),
},
"above max id": {
keyPrefix: "ID",
keyValue: "4294967296",
retroKeyPrefix: "RETRO_ID",
errWrapped: ErrSystemIDNotValid,
errMessage: `environment variable IDTest_Reader_readID/above_max_id: ` +
`system ID is not valid: 4294967296: must be between 0 and 4294967295`,
}, },
} }

View File

@@ -1,8 +0,0 @@
package constants
const (
// TCP is a network protocol (reliable and slower than UDP).
TCP string = "tcp"
// UDP is a network protocol (unreliable and faster than TCP).
UDP string = "udp"
)

View File

@@ -0,0 +1,27 @@
package constants
func AllProviders() []string {
return []string{
Custom,
Cyberghost,
Expressvpn,
Fastestvpn,
HideMyAss,
Ipvanish,
Ivpn,
Mullvad,
Nordvpn,
Perfectprivacy,
Privado,
PrivateInternetAccess,
Privatevpn,
Protonvpn,
Purevpn,
Surfshark,
Torguard,
VPNUnlimited,
Vyprvpn,
Wevpn,
Windscribe,
}
}

View File

@@ -1,53 +0,0 @@
package providers
const (
// Custom is the VPN provider name for custom
// VPN configurations.
Custom = "custom"
Cyberghost = "cyberghost"
Expressvpn = "expressvpn"
Fastestvpn = "fastestvpn"
HideMyAss = "hidemyass"
Ipvanish = "ipvanish"
Ivpn = "ivpn"
Mullvad = "mullvad"
Nordvpn = "nordvpn"
Perfectprivacy = "perfect privacy"
Privado = "privado"
PrivateInternetAccess = "private internet access"
Privatevpn = "privatevpn"
Protonvpn = "protonvpn"
Purevpn = "purevpn"
Surfshark = "surfshark"
Torguard = "torguard"
VPNUnlimited = "vpn unlimited"
Vyprvpn = "vyprvpn"
Wevpn = "wevpn"
Windscribe = "windscribe"
)
func All() []string {
return []string{
Custom,
Cyberghost,
Expressvpn,
Fastestvpn,
HideMyAss,
Ipvanish,
Ivpn,
Mullvad,
Nordvpn,
Perfectprivacy,
Privado,
PrivateInternetAccess,
Privatevpn,
Protonvpn,
Purevpn,
Surfshark,
Torguard,
VPNUnlimited,
Vyprvpn,
Wevpn,
Windscribe,
}
}

59
internal/constants/vpn.go Normal file
View File

@@ -0,0 +1,59 @@
package constants
const (
OpenVPN = "openvpn"
Wireguard = "wireguard"
)
const (
// Custom is the VPN provider name for custom
// VPN configurations.
Custom = "custom"
// Cyberghost is a VPN provider.
Cyberghost = "cyberghost"
// Expressvpn is a VPN provider.
Expressvpn = "expressvpn"
// Fastestvpn is a VPN provider.
Fastestvpn = "fastestvpn"
// HideMyAss is a VPN provider.
HideMyAss = "hidemyass"
// Ipvanish is a VPN provider.
Ipvanish = "ipvanish"
// Ivpn is a VPN provider.
Ivpn = "ivpn"
// Mullvad is a VPN provider.
Mullvad = "mullvad"
// Nordvpn is a VPN provider.
Nordvpn = "nordvpn"
// Perfectprivacy is a VPN provider.
Perfectprivacy = "perfect privacy"
// Privado is a VPN provider.
Privado = "privado"
// PrivateInternetAccess is a VPN provider.
PrivateInternetAccess = "private internet access"
// Privatevpn is a VPN provider.
Privatevpn = "privatevpn"
// Protonvpn is a VPN provider.
Protonvpn = "protonvpn"
// Purevpn is a VPN provider.
Purevpn = "purevpn"
// Surfshark is a VPN provider.
Surfshark = "surfshark"
// Torguard is a VPN provider.
Torguard = "torguard"
// VPNUnlimited is a VPN provider.
VPNUnlimited = "vpn unlimited"
// Vyprvpn is a VPN provider.
Vyprvpn = "vyprvpn"
// WeVPN is a VPN provider.
Wevpn = "wevpn"
// Windscribe is a VPN provider.
Windscribe = "windscribe"
)
const (
// TCP is a network protocol (reliable and slower than UDP).
TCP string = "tcp"
// UDP is a network protocol (unreliable and faster than TCP).
UDP string = "udp"
)

View File

@@ -1,6 +0,0 @@
package vpn
const (
OpenVPN = "openvpn"
Wireguard = "wireguard"
)

View File

@@ -1,61 +0,0 @@
package firewall
import (
"fmt"
"os/exec"
"regexp"
"github.com/golang/mock/gomock"
)
var _ gomock.Matcher = (*cmdMatcher)(nil)
type cmdMatcher struct {
path string
argsRegex []string
argsRegexp []*regexp.Regexp
}
func (cm *cmdMatcher) Matches(x interface{}) bool {
cmd, ok := x.(*exec.Cmd)
if !ok {
return false
}
if cmd.Path != cm.path {
return false
}
if len(cmd.Args) == 0 {
return false
}
arguments := cmd.Args[1:]
if len(arguments) != len(cm.argsRegex) {
return false
}
for i, arg := range arguments {
if !cm.argsRegexp[i].MatchString(arg) {
return false
}
}
return true
}
func (cm *cmdMatcher) String() string {
return fmt.Sprintf("path %s, argument regular expressions %v", cm.path, cm.argsRegex)
}
func newCmdMatcher(path string, argsRegex ...string) *cmdMatcher { //nolint:unparam
argsRegexp := make([]*regexp.Regexp, len(argsRegex))
for i, argRegex := range argsRegex {
argsRegexp[i] = regexp.MustCompile(argRegex)
}
return &cmdMatcher{
path: path,
argsRegex: argsRegex,
argsRegexp: argsRegexp,
}
}

View File

@@ -96,10 +96,14 @@ func (c *Config) enable(ctx context.Context) (err error) {
if err = c.acceptEstablishedRelatedTraffic(ctx, remove); err != nil { if err = c.acceptEstablishedRelatedTraffic(ctx, remove); err != nil {
return err return err
} }
if c.vpnConnection.IP != nil {
if err = c.allowVPNIP(ctx); err != nil { if err = c.acceptOutputTrafficToVPN(ctx, c.defaultInterface, c.vpnConnection, remove); err != nil {
return err return err
} }
if err = c.acceptOutputThroughInterface(ctx, c.vpnIntf, remove); err != nil {
return err
}
}
for _, network := range c.localNetworks { for _, network := range c.localNetworks {
if err := c.acceptOutputFromIPToSubnet(ctx, network.InterfaceName, network.IP, *network.IPNet, remove); err != nil { if err := c.acceptOutputFromIPToSubnet(ctx, network.InterfaceName, network.IP, *network.IPNet, remove); err != nil {
@@ -107,9 +111,11 @@ func (c *Config) enable(ctx context.Context) (err error) {
} }
} }
if err = c.allowOutboundSubnets(ctx); err != nil { for _, subnet := range c.outboundSubnets {
if err := c.acceptOutputFromIPToSubnet(ctx, c.defaultInterface, c.localIP, subnet, remove); err != nil {
return err return err
} }
}
// 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.
@@ -119,9 +125,11 @@ func (c *Config) enable(ctx context.Context) (err error) {
} }
} }
if err = c.allowInputPorts(ctx); err != nil { for port, intf := range c.allowedInputPorts {
if err := c.acceptInputToPort(ctx, intf, port, remove); err != nil {
return err return err
} }
}
if err := c.runUserPostRules(ctx, c.customRulesPath, remove); err != nil { if err := c.runUserPostRules(ctx, c.customRulesPath, remove); err != nil {
return fmt.Errorf("cannot run user defined post firewall rules: %w", err) return fmt.Errorf("cannot run user defined post firewall rules: %w", err)
@@ -129,47 +137,3 @@ func (c *Config) enable(ctx context.Context) (err error) {
return nil return nil
} }
func (c *Config) allowVPNIP(ctx context.Context) (err error) {
if c.vpnConnection.IP == nil {
return nil
}
const remove = false
for _, defaultRoute := range c.defaultRoutes {
err = c.acceptOutputTrafficToVPN(ctx, defaultRoute.NetInterface, c.vpnConnection, remove)
if err != nil {
return fmt.Errorf("cannot accept output traffic through VPN: %w", err)
}
}
return nil
}
func (c *Config) allowOutboundSubnets(ctx context.Context) (err error) {
for _, subnet := range c.outboundSubnets {
for _, defaultRoute := range c.defaultRoutes {
const remove = false
err := c.acceptOutputFromIPToSubnet(ctx, defaultRoute.NetInterface,
defaultRoute.AssignedIP, subnet, remove)
if err != nil {
return err
}
}
}
return nil
}
func (c *Config) allowInputPorts(ctx context.Context) (err error) {
for port, netInterfaces := range c.allowedInputPorts {
for netInterface := range netInterfaces {
const remove = false
err = c.acceptInputToPort(ctx, netInterface, port, remove)
if err != nil {
return fmt.Errorf("cannot accept input port %d on interface %s: %w",
port, netInterface, err)
}
}
}
return nil
}

View File

@@ -27,12 +27,13 @@ type Config struct { //nolint:maligned
logger Logger logger Logger
iptablesMutex sync.Mutex iptablesMutex sync.Mutex
ip6tablesMutex sync.Mutex ip6tablesMutex sync.Mutex
defaultRoutes []routing.DefaultRoute defaultInterface string
defaultGateway net.IP
localNetworks []routing.LocalNetwork localNetworks []routing.LocalNetwork
localIP net.IP
// Fixed state // Fixed state
ipTables string ip6Tables bool
ip6Tables string
customRulesPath string customRulesPath string
// State // State
@@ -40,34 +41,24 @@ type Config struct { //nolint:maligned
vpnConnection models.Connection vpnConnection models.Connection
vpnIntf string vpnIntf string
outboundSubnets []net.IPNet outboundSubnets []net.IPNet
allowedInputPorts map[uint16]map[string]struct{} // port to interfaces set mapping allowedInputPorts map[uint16]string // port to interface mapping
stateMutex sync.Mutex stateMutex sync.Mutex
} }
// NewConfig creates a new Config instance and returns an error // NewConfig creates a new Config instance.
// if no iptables implementation is available. func NewConfig(logger Logger, runner command.Runner,
func NewConfig(ctx context.Context, logger Logger, defaultInterface string, defaultGateway net.IP,
runner command.Runner, defaultRoutes []routing.DefaultRoute, localNetworks []routing.LocalNetwork, localIP net.IP) *Config {
localNetworks []routing.LocalNetwork) (config *Config, err error) {
iptables, err := checkIptablesSupport(ctx, runner, "iptables", "iptables-nft")
if err != nil {
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]string),
ipTables: iptables, ip6Tables: ip6tablesSupported(context.Background(), runner),
ip6Tables: ip6tables,
customRulesPath: "/iptables/post-rules.txt", customRulesPath: "/iptables/post-rules.txt",
// Obtained from routing // Obtained from routing
defaultRoutes: defaultRoutes, defaultInterface: defaultInterface,
defaultGateway: defaultGateway,
localNetworks: localNetworks, localNetworks: localNetworks,
}, nil localIP: localIP,
}
} }

View File

@@ -10,18 +10,16 @@ import (
"github.com/qdm12/golibs/command" "github.com/qdm12/golibs/command"
) )
// findIP6tablesSupported checks for multiple iptables implementations var (
// and returns the iptables path that is supported. If none work, an ErrIP6NotSupported = errors.New("ip6tables not supported")
// empty string path is returned. )
func findIP6tablesSupported(ctx context.Context, runner command.Runner) (
ip6tablesPath string, err error) { func ip6tablesSupported(ctx context.Context, runner command.Runner) (supported bool) {
ip6tablesPath, err = checkIptablesSupport(ctx, runner, "ip6tables", "ip6tables-nft") cmd := exec.CommandContext(ctx, "ip6tables", "-L")
if errors.Is(err, ErrIPTablesNotSupported) { if _, err := runner.Run(cmd); err != nil {
return "", nil return false
} else if err != nil {
return "", err
} }
return ip6tablesPath, nil return true
} }
func (c *Config) runIP6tablesInstructions(ctx context.Context, instructions []string) error { func (c *Config) runIP6tablesInstructions(ctx context.Context, instructions []string) error {
@@ -34,19 +32,18 @@ func (c *Config) runIP6tablesInstructions(ctx context.Context, instructions []st
} }
func (c *Config) runIP6tablesInstruction(ctx context.Context, instruction string) error { func (c *Config) runIP6tablesInstruction(ctx context.Context, instruction string) error {
if c.ip6Tables == "" { if !c.ip6Tables {
return nil return nil
} }
c.ip6tablesMutex.Lock() // only one ip6tables command at once c.ip6tablesMutex.Lock() // only one ip6tables command at once
defer c.ip6tablesMutex.Unlock() defer c.ip6tablesMutex.Unlock()
c.logger.Debug(c.ip6Tables + " " + instruction) c.logger.Debug("ip6tables " + instruction)
flags := strings.Fields(instruction) flags := strings.Fields(instruction)
cmd := exec.CommandContext(ctx, c.ip6Tables, flags...) // #nosec G204 cmd := exec.CommandContext(ctx, "ip6tables", flags...)
if output, err := c.runner.Run(cmd); err != nil { if output, err := c.runner.Run(cmd); err != nil {
return fmt.Errorf("command failed: \"%s %s\": %s: %w", return fmt.Errorf("command failed: \"ip6tables %s\": %s: %w", instruction, output, err)
c.ip6Tables, instruction, output, err)
} }
return nil return nil
} }

View File

@@ -71,13 +71,12 @@ 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()
c.logger.Debug(c.ipTables + " " + instruction) c.logger.Debug("iptables " + instruction)
flags := strings.Fields(instruction) flags := strings.Fields(instruction)
cmd := exec.CommandContext(ctx, c.ipTables, flags...) // #nosec G204 cmd := exec.CommandContext(ctx, "iptables", flags...)
if output, err := c.runner.Run(cmd); err != nil { if output, err := c.runner.Run(cmd); err != nil {
return fmt.Errorf("command failed: \"%s %s\": %s: %w", return fmt.Errorf("command failed: \"iptables %s\": %s: %w", instruction, output, err)
c.ipTables, instruction, output, err)
} }
return nil return nil
} }
@@ -125,7 +124,7 @@ func (c *Config) acceptInputToSubnet(ctx context.Context, intf string, destinati
if isIP4Subnet { if isIP4Subnet {
return c.runIptablesInstruction(ctx, instruction) return c.runIptablesInstruction(ctx, instruction)
} }
if c.ip6Tables == "" { if !c.ip6Tables {
return fmt.Errorf("accept input to subnet %s: %w", destination, ErrNeedIP6Tables) return fmt.Errorf("accept input to subnet %s: %w", destination, ErrNeedIP6Tables)
} }
return c.runIP6tablesInstruction(ctx, instruction) return c.runIP6tablesInstruction(ctx, instruction)
@@ -152,7 +151,7 @@ func (c *Config) acceptOutputTrafficToVPN(ctx context.Context,
isIPv4 := connection.IP.To4() != nil isIPv4 := connection.IP.To4() != nil
if isIPv4 { if isIPv4 {
return c.runIptablesInstruction(ctx, instruction) return c.runIptablesInstruction(ctx, instruction)
} else if c.ip6Tables == "" { } else if !c.ip6Tables {
return fmt.Errorf("accept output to VPN server: %w", ErrNeedIP6Tables) return fmt.Errorf("accept output to VPN server: %w", ErrNeedIP6Tables)
} }
return c.runIP6tablesInstruction(ctx, instruction) return c.runIP6tablesInstruction(ctx, instruction)
@@ -173,7 +172,7 @@ func (c *Config) acceptOutputFromIPToSubnet(ctx context.Context,
if doIPv4 { if doIPv4 {
return c.runIptablesInstruction(ctx, instruction) return c.runIptablesInstruction(ctx, instruction)
} else if c.ip6Tables == "" { } else if !c.ip6Tables {
return fmt.Errorf("accept output from %s to %s: %w", sourceIP, destinationSubnet, ErrNeedIP6Tables) return fmt.Errorf("accept output from %s to %s: %w", sourceIP, destinationSubnet, ErrNeedIP6Tables)
} }
return c.runIP6tablesInstruction(ctx, instruction) return c.runIP6tablesInstruction(ctx, instruction)
@@ -224,15 +223,9 @@ func (c *Config) runUserPostRules(ctx context.Context, filepath string, remove b
case strings.HasPrefix(line, "iptables "): case strings.HasPrefix(line, "iptables "):
ipv4 = true ipv4 = true
rule = strings.TrimPrefix(line, "iptables ") rule = strings.TrimPrefix(line, "iptables ")
case strings.HasPrefix(line, "iptables-nft "):
ipv4 = true
rule = strings.TrimPrefix(line, "iptables-nft ")
case strings.HasPrefix(line, "ip6tables "): case strings.HasPrefix(line, "ip6tables "):
ipv4 = false ipv4 = false
rule = strings.TrimPrefix(line, "ip6tables ") rule = strings.TrimPrefix(line, "ip6tables ")
case strings.HasPrefix(line, "ip6tables-nft "):
ipv4 = false
rule = strings.TrimPrefix(line, "ip6tables-nft ")
default: default:
continue continue
} }
@@ -244,7 +237,7 @@ func (c *Config) runUserPostRules(ctx context.Context, filepath string, remove b
switch { switch {
case ipv4: case ipv4:
err = c.runIptablesInstruction(ctx, rule) err = c.runIptablesInstruction(ctx, rule)
case c.ip6Tables == "": case !c.ip6Tables:
err = fmt.Errorf("cannot run user ip6tables rule: %w", ErrNeedIP6Tables) err = fmt.Errorf("cannot run user ip6tables rule: %w", ErrNeedIP6Tables)
default: // ipv6 default: // ipv6
err = c.runIP6tablesInstruction(ctx, rule) err = c.runIP6tablesInstruction(ctx, rule)

View File

@@ -41,14 +41,10 @@ func (c *Config) SetOutboundSubnets(ctx context.Context, subnets []net.IPNet) (e
func (c *Config) removeOutboundSubnets(ctx context.Context, subnets []net.IPNet) { func (c *Config) removeOutboundSubnets(ctx context.Context, subnets []net.IPNet) {
const remove = true const remove = true
for _, subNet := range subnets { for _, subNet := range subnets {
for _, defaultRoute := range c.defaultRoutes { if err := c.acceptOutputFromIPToSubnet(ctx, c.defaultInterface, c.localIP, subNet, remove); err != nil {
err := c.acceptOutputFromIPToSubnet(ctx, defaultRoute.NetInterface,
defaultRoute.AssignedIP, subNet, remove)
if err != nil {
c.logger.Error("cannot remove outdated outbound subnet: " + err.Error()) c.logger.Error("cannot remove outdated outbound subnet: " + err.Error())
continue continue
} }
}
c.outboundSubnets = subnet.RemoveSubnetFromSubnets(c.outboundSubnets, subNet) c.outboundSubnets = subnet.RemoveSubnetFromSubnets(c.outboundSubnets, subNet)
} }
} }
@@ -56,13 +52,9 @@ func (c *Config) removeOutboundSubnets(ctx context.Context, subnets []net.IPNet)
func (c *Config) addOutboundSubnets(ctx context.Context, subnets []net.IPNet) error { func (c *Config) addOutboundSubnets(ctx context.Context, subnets []net.IPNet) error {
const remove = false const remove = false
for _, subnet := range subnets { for _, subnet := range subnets {
for _, defaultRoute := range c.defaultRoutes { if err := c.acceptOutputFromIPToSubnet(ctx, c.defaultInterface, c.localIP, subnet, remove); err != nil {
err := c.acceptOutputFromIPToSubnet(ctx, defaultRoute.NetInterface,
defaultRoute.AssignedIP, subnet, remove)
if err != nil {
return err return err
} }
}
c.outboundSubnets = append(c.outboundSubnets, subnet) c.outboundSubnets = append(c.outboundSubnets, subnet)
} }
return nil return nil

View File

@@ -21,30 +21,27 @@ func (c *Config) SetAllowedPort(ctx context.Context, port uint16, intf string) (
if !c.enabled { if !c.enabled {
c.logger.Info("firewall disabled, only updating allowed ports internal state") c.logger.Info("firewall disabled, only updating allowed ports internal state")
existingInterfaces, ok := c.allowedInputPorts[port] c.allowedInputPorts[port] = intf
if !ok {
existingInterfaces = make(map[string]struct{})
}
existingInterfaces[intf] = struct{}{}
c.allowedInputPorts[port] = existingInterfaces
return nil
}
netInterfaces, has := c.allowedInputPorts[port]
if !has {
netInterfaces = make(map[string]struct{})
} else if _, exists := netInterfaces[intf]; exists {
return nil return nil
} }
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 + "...")
if existingIntf, ok := c.allowedInputPorts[port]; ok {
if intf == existingIntf {
return nil
}
const remove = true
if err := c.acceptInputToPort(ctx, existingIntf, port, remove); err != nil {
return fmt.Errorf("cannot remove old allowed port %d: %w", port, err)
}
}
const remove = false const remove = false
if err := c.acceptInputToPort(ctx, intf, port, remove); err != nil { if err := c.acceptInputToPort(ctx, intf, port, remove); err != nil {
return fmt.Errorf("cannot allow input to port %d through interface %s: %w", return fmt.Errorf("cannot allow input to port %d: %w", port, err)
port, intf, err)
} }
netInterfaces[intf] = struct{}{} c.allowedInputPorts[port] = intf
return nil return nil
} }
@@ -63,24 +60,17 @@ func (c *Config) RemoveAllowedPort(ctx context.Context, port uint16) (err error)
return nil return nil
} }
c.logger.Info("removing allowed port " + strconv.Itoa(int(port)) + "...") c.logger.Info("removing allowed port " + strconv.Itoa(int(port)) + " ...")
interfacesSet, ok := c.allowedInputPorts[port] intf, ok := c.allowedInputPorts[port]
if !ok { if !ok {
return nil return nil
} }
const remove = true const remove = true
for netInterface := range interfacesSet { if err := c.acceptInputToPort(ctx, intf, port, remove); err != nil {
err := c.acceptInputToPort(ctx, netInterface, port, remove) return fmt.Errorf("cannot remove allowed port %d: %w", port, err)
if err != nil {
return fmt.Errorf("cannot remove allowed port %d on interface %s: %w",
port, netInterface, err)
} }
delete(interfacesSet, netInterface)
}
// All interfaces were removed successfully, so remove the port entry.
delete(c.allowedInputPorts, port) delete(c.allowedInputPorts, port)
return nil return nil

View File

@@ -1,50 +0,0 @@
// Code generated by MockGen. DO NOT EDIT.
// Source: github.com/qdm12/golibs/command (interfaces: Runner)
// Package firewall is a generated GoMock package.
package firewall
import (
reflect "reflect"
gomock "github.com/golang/mock/gomock"
command "github.com/qdm12/golibs/command"
)
// MockRunner is a mock of Runner interface.
type MockRunner struct {
ctrl *gomock.Controller
recorder *MockRunnerMockRecorder
}
// MockRunnerMockRecorder is the mock recorder for MockRunner.
type MockRunnerMockRecorder struct {
mock *MockRunner
}
// NewMockRunner creates a new mock instance.
func NewMockRunner(ctrl *gomock.Controller) *MockRunner {
mock := &MockRunner{ctrl: ctrl}
mock.recorder = &MockRunnerMockRecorder{mock}
return mock
}
// EXPECT returns an object that allows the caller to indicate expected use.
func (m *MockRunner) EXPECT() *MockRunnerMockRecorder {
return m.recorder
}
// Run mocks base method.
func (m *MockRunner) Run(arg0 command.ExecCmd) (string, error) {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "Run", arg0)
ret0, _ := ret[0].(string)
ret1, _ := ret[1].(error)
return ret0, ret1
}
// Run indicates an expected call of Run.
func (mr *MockRunnerMockRecorder) Run(arg0 interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Run", reflect.TypeOf((*MockRunner)(nil).Run), arg0)
}

View File

@@ -1,159 +0,0 @@
package firewall
import (
"context"
"errors"
"fmt"
"math/rand"
"os/exec"
"strings"
"github.com/qdm12/golibs/command"
)
var (
ErrNetAdminMissing = errors.New("NET_ADMIN capability is missing")
ErrTestRuleCleanup = errors.New("failed cleaning up test rule")
ErrInputPolicyNotFound = errors.New("input policy not found")
ErrIPTablesNotSupported = errors.New("no iptables supported found")
)
func checkIptablesSupport(ctx context.Context, runner command.Runner,
iptablesPathsToTry ...string) (iptablesPath string, err error) {
var lastUnsupportedMessage string
for _, pathToTest := range iptablesPathsToTry {
ok, unsupportedMessage, err := testIptablesPath(ctx, pathToTest, runner)
if err != nil {
return "", fmt.Errorf("for %s: %w", pathToTest, err)
} else if ok {
iptablesPath = pathToTest
break
}
lastUnsupportedMessage = unsupportedMessage
}
if iptablesPath == "" { // all iptables to try failed
return "", fmt.Errorf("%w: from %s: last error is: %s",
ErrIPTablesNotSupported, strings.Join(iptablesPathsToTry, ", "),
lastUnsupportedMessage)
}
return iptablesPath, nil
}
func testIptablesPath(ctx context.Context, path string,
runner command.Runner) (ok bool, unsupportedMessage string,
criticalErr error) {
// Just listing iptables rules often work but we need
// to modify them to ensure we can support the iptables
// being tested.
// Append a test rule with a random interface name to the OUTPUT table.
// This should not affect existing rules or the network traffic.
testInterfaceName := randomInterfaceName()
cmd := exec.CommandContext(ctx, path,
"-A", "OUTPUT", "-o", testInterfaceName, "-j", "DROP")
output, err := runner.Run(cmd)
if err != nil {
if isPermissionDenied(output) {
// If the error is related to a denied permission,
// return an error describing what to do from an end-user
// perspective. This is a critical error and likely
// applies to all iptables.
criticalErr = fmt.Errorf("%w: %s", ErrNetAdminMissing, output)
return false, "", criticalErr
}
unsupportedMessage = fmt.Sprintf("%s (%s)", output, err)
return false, unsupportedMessage, nil
}
// Remove the random rule added previously for test.
cmd = exec.CommandContext(ctx, path,
"-D", "OUTPUT", "-o", testInterfaceName, "-j", "DROP")
output, err = runner.Run(cmd)
if err != nil {
// this is a critical error, we want to make sure our test rule gets removed.
criticalErr = fmt.Errorf("%w: %s (%s)", ErrTestRuleCleanup, output, err)
return false, "", criticalErr
}
// Set policy as the existing policy so no mutation is done.
// This is an extra check for some buggy kernels where setting the policy
// does not work.
cmd = exec.CommandContext(ctx, path, "-L", "INPUT")
output, err = runner.Run(cmd)
if err != nil {
if isPermissionDenied(output) {
criticalErr = fmt.Errorf("%w: %s", ErrNetAdminMissing, output)
return false, "", criticalErr
}
unsupportedMessage = fmt.Sprintf("%s (%s)", output, err)
return false, unsupportedMessage, nil
}
var inputPolicy string
for _, line := range strings.Split(output, "\n") {
inputPolicy, ok = extractInputPolicy(line)
if ok {
break
}
}
if inputPolicy == "" {
criticalErr = fmt.Errorf("%w: in INPUT rules: %s", ErrInputPolicyNotFound, output)
return false, "", criticalErr
}
// Set the policy for the INPUT table to the existing policy found.
cmd = exec.CommandContext(ctx, path, "--policy", "INPUT", inputPolicy)
output, err = runner.Run(cmd)
if err != nil {
if isPermissionDenied(output) {
criticalErr = fmt.Errorf("%w: %s", ErrNetAdminMissing, output)
return false, "", criticalErr
}
unsupportedMessage = fmt.Sprintf("%s (%s)", output, err)
return false, unsupportedMessage, nil
}
return true, "", nil // success
}
func isPermissionDenied(errMessage string) (ok bool) {
const permissionDeniedString = "Permission denied (you must be root)"
return strings.Contains(errMessage, permissionDeniedString)
}
func extractInputPolicy(line string) (policy string, ok bool) {
const prefixToFind = "Chain INPUT (policy "
i := strings.Index(line, prefixToFind)
if i == -1 {
return "", false
}
startIndex := i + len(prefixToFind)
endIndex := strings.Index(line, ")")
if endIndex < 0 {
return "", false
}
policy = line[startIndex:endIndex]
policy = strings.TrimSpace(policy)
if policy == "" {
return "", false
}
return policy, true
}
func randomInterfaceName() (interfaceName string) {
const size = 15
letterRunes := []rune("abcdefghijklmnopqrstuvwxyz0123456789")
b := make([]rune, size)
for i := range b {
letterIndex := rand.Intn(len(letterRunes)) //nolint:gosec
b[i] = letterRunes[letterIndex]
}
return string(b)
}

View File

@@ -1,250 +0,0 @@
package firewall
import (
"context"
"errors"
"testing"
"github.com/golang/mock/gomock"
"github.com/qdm12/golibs/command"
"github.com/stretchr/testify/assert"
)
//go:generate mockgen -destination=runner_mock_test.go -package $GOPACKAGE github.com/qdm12/golibs/command Runner
func Test_testIptablesPath(t *testing.T) {
t.Parallel()
ctx := context.Background()
const path = "dummypath"
errDummy := errors.New("exit code 4")
const inputPolicy = "ACCEPT"
appendTestRuleMatcher := newCmdMatcher(path,
"^-A$", "^OUTPUT$", "^-o$", "^[a-z0-9]{15}$",
"^-j$", "^DROP$")
deleteTestRuleMatcher := newCmdMatcher(path,
"^-D$", "^OUTPUT$", "^-o$", "^[a-z0-9]{15}$",
"^-j$", "^DROP$")
listInputRulesMatcher := newCmdMatcher(path,
"^-L$", "^INPUT$")
setPolicyMatcher := newCmdMatcher(path,
"^--policy$", "^INPUT$", "^"+inputPolicy+"$")
testCases := map[string]struct {
buildRunner func(ctrl *gomock.Controller) command.Runner
ok bool
unsupportedMessage string
criticalErrWrapped error
criticalErrMessage string
}{
"append test rule permission denied": {
buildRunner: func(ctrl *gomock.Controller) command.Runner {
runner := NewMockRunner(ctrl)
runner.EXPECT().Run(appendTestRuleMatcher).
Return("Permission denied (you must be root)", errDummy)
return runner
},
criticalErrWrapped: ErrNetAdminMissing,
criticalErrMessage: "NET_ADMIN capability is missing: " +
"Permission denied (you must be root)",
},
"append test rule unsupported": {
buildRunner: func(ctrl *gomock.Controller) command.Runner {
runner := NewMockRunner(ctrl)
runner.EXPECT().Run(appendTestRuleMatcher).
Return("some output", errDummy)
return runner
},
unsupportedMessage: "some output (exit code 4)",
},
"remove test rule error": {
buildRunner: func(ctrl *gomock.Controller) command.Runner {
runner := NewMockRunner(ctrl)
runner.EXPECT().Run(appendTestRuleMatcher).Return("", nil)
runner.EXPECT().Run(deleteTestRuleMatcher).
Return("some output", errDummy)
return runner
},
criticalErrWrapped: ErrTestRuleCleanup,
criticalErrMessage: "failed cleaning up test rule: some output (exit code 4)",
},
"list input rules permission denied": {
buildRunner: func(ctrl *gomock.Controller) command.Runner {
runner := NewMockRunner(ctrl)
runner.EXPECT().Run(appendTestRuleMatcher).Return("", nil)
runner.EXPECT().Run(deleteTestRuleMatcher).Return("", nil)
runner.EXPECT().Run(listInputRulesMatcher).
Return("Permission denied (you must be root)", errDummy)
return runner
},
criticalErrWrapped: ErrNetAdminMissing,
criticalErrMessage: "NET_ADMIN capability is missing: " +
"Permission denied (you must be root)",
},
"list input rules unsupported": {
buildRunner: func(ctrl *gomock.Controller) command.Runner {
runner := NewMockRunner(ctrl)
runner.EXPECT().Run(appendTestRuleMatcher).Return("", nil)
runner.EXPECT().Run(deleteTestRuleMatcher).Return("", nil)
runner.EXPECT().Run(listInputRulesMatcher).
Return("some output", errDummy)
return runner
},
unsupportedMessage: "some output (exit code 4)",
},
"list input rules no policy": {
buildRunner: func(ctrl *gomock.Controller) command.Runner {
runner := NewMockRunner(ctrl)
runner.EXPECT().Run(appendTestRuleMatcher).Return("", nil)
runner.EXPECT().Run(deleteTestRuleMatcher).Return("", nil)
runner.EXPECT().Run(listInputRulesMatcher).
Return("some\noutput", nil)
return runner
},
criticalErrWrapped: ErrInputPolicyNotFound,
criticalErrMessage: "input policy not found: in INPUT rules: some\noutput",
},
"set policy permission denied": {
buildRunner: func(ctrl *gomock.Controller) command.Runner {
runner := NewMockRunner(ctrl)
runner.EXPECT().Run(appendTestRuleMatcher).Return("", nil)
runner.EXPECT().Run(deleteTestRuleMatcher).Return("", nil)
runner.EXPECT().Run(listInputRulesMatcher).
Return("\nChain INPUT (policy "+inputPolicy+")\nxx\n", nil)
runner.EXPECT().Run(setPolicyMatcher).
Return("Permission denied (you must be root)", errDummy)
return runner
},
criticalErrWrapped: ErrNetAdminMissing,
criticalErrMessage: "NET_ADMIN capability is missing: " +
"Permission denied (you must be root)",
},
"set policy unsupported": {
buildRunner: func(ctrl *gomock.Controller) command.Runner {
runner := NewMockRunner(ctrl)
runner.EXPECT().Run(appendTestRuleMatcher).Return("", nil)
runner.EXPECT().Run(deleteTestRuleMatcher).Return("", nil)
runner.EXPECT().Run(listInputRulesMatcher).
Return("\nChain INPUT (policy "+inputPolicy+")\nxx\n", nil)
runner.EXPECT().Run(setPolicyMatcher).
Return("some output", errDummy)
return runner
},
unsupportedMessage: "some output (exit code 4)",
},
"success": {
buildRunner: func(ctrl *gomock.Controller) command.Runner {
runner := NewMockRunner(ctrl)
runner.EXPECT().Run(appendTestRuleMatcher).Return("", nil)
runner.EXPECT().Run(deleteTestRuleMatcher).Return("", nil)
runner.EXPECT().Run(listInputRulesMatcher).
Return("\nChain INPUT (policy "+inputPolicy+")\nxx\n", nil)
runner.EXPECT().Run(setPolicyMatcher).Return("some output", nil)
return runner
},
ok: true,
},
}
for name, testCase := range testCases {
testCase := testCase
t.Run(name, func(t *testing.T) {
t.Parallel()
ctrl := gomock.NewController(t)
runner := testCase.buildRunner(ctrl)
ok, unsupportedMessage, criticalErr :=
testIptablesPath(ctx, path, runner)
assert.Equal(t, testCase.ok, ok)
assert.Equal(t, testCase.unsupportedMessage, unsupportedMessage)
assert.ErrorIs(t, criticalErr, testCase.criticalErrWrapped)
if testCase.criticalErrWrapped != nil {
assert.EqualError(t, criticalErr, testCase.criticalErrMessage)
}
})
}
}
func Test_isPermissionDenied(t *testing.T) {
t.Parallel()
testCases := map[string]struct {
errMessage string
ok bool
}{
"empty error": {},
"other error": {
errMessage: "some error",
},
"permission denied": {
errMessage: "Permission denied (you must be root) have you tried blabla",
ok: true,
},
}
for name, testCase := range testCases {
testCase := testCase
t.Run(name, func(t *testing.T) {
t.Parallel()
ok := isPermissionDenied(testCase.errMessage)
assert.Equal(t, testCase.ok, ok)
})
}
}
func Test_extractInputPolicy(t *testing.T) {
t.Parallel()
testCases := map[string]struct {
line string
policy string
ok bool
}{
"empty line": {},
"random line": {
line: "random line",
},
"only first part": {
line: "Chain INPUT (policy ",
},
"empty policy": {
line: "Chain INPUT (policy )",
},
"ACCEPT policy": {
line: "Chain INPUT (policy ACCEPT)",
policy: "ACCEPT",
ok: true,
},
"ACCEPT policy with surrounding garbage": {
line: "garbage Chain INPUT (policy ACCEPT\t) )g()arbage",
policy: "ACCEPT",
ok: true,
},
}
for name, testCase := range testCases {
testCase := testCase
t.Run(name, func(t *testing.T) {
t.Parallel()
policy, ok := extractInputPolicy(testCase.line)
assert.Equal(t, testCase.policy, policy)
assert.Equal(t, testCase.ok, ok)
})
}
}
func Test_randomInterfaceName(t *testing.T) {
t.Parallel()
const expectedRegex = `^[a-z0-9]{15}$`
interfaceName := randomInterfaceName()
assert.Regexp(t, expectedRegex, interfaceName)
}

View File

@@ -31,12 +31,10 @@ func (c *Config) SetVPNConnection(ctx context.Context,
remove := true remove := true
if c.vpnConnection.IP != nil { if c.vpnConnection.IP != nil {
for _, defaultRoute := range c.defaultRoutes { if err := c.acceptOutputTrafficToVPN(ctx, c.defaultInterface, 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())
} }
} }
}
c.vpnConnection = models.Connection{} c.vpnConnection = models.Connection{}
if c.vpnIntf != "" { if c.vpnIntf != "" {
@@ -48,11 +46,9 @@ func (c *Config) SetVPNConnection(ctx context.Context,
remove = false remove = false
for _, defaultRoute := range c.defaultRoutes { if err := c.acceptOutputTrafficToVPN(ctx, c.defaultInterface, connection, remove); err != nil {
if err := c.acceptOutputTrafficToVPN(ctx, defaultRoute.NetInterface, connection, remove); err != nil {
return fmt.Errorf("cannot allow output traffic through VPN connection: %w", err) return fmt.Errorf("cannot allow output traffic through VPN connection: %w", err)
} }
}
c.vpnConnection = connection c.vpnConnection = connection
if err = c.acceptOutputThroughInterface(ctx, vpnIntf, remove); err != nil { if err = c.acceptOutputThroughInterface(ctx, vpnIntf, remove); err != nil {

View File

@@ -19,7 +19,7 @@ func Test_Server_healthCheck(t *testing.T) {
t.Parallel() t.Parallel()
dialer := &net.Dialer{} dialer := &net.Dialer{}
const address = "cloudflare.com:443" const address = "github.com:443"
server := &Server{ server := &Server{
dialer: dialer, dialer: dialer,

View File

@@ -7,6 +7,7 @@ import (
gomock "github.com/golang/mock/gomock" gomock "github.com/golang/mock/gomock"
) )
func stringPtr(s string) *string { return &s }
func durationPtr(d time.Duration) *time.Duration { return &d } func durationPtr(d time.Duration) *time.Duration { return &d }
var _ Logger = (*testLogger)(nil) var _ Logger = (*testLogger)(nil)

View File

@@ -23,11 +23,12 @@ func (s *Server) Run(ctx context.Context, ready chan<- struct{}, done chan<- str
return return
} }
s.logger.Warn(s.name + " http server shutting down: " + ctx.Err().Error())
shutdownCtx, cancel := context.WithTimeout( shutdownCtx, cancel := context.WithTimeout(
context.Background(), s.shutdownTimeout) context.Background(), s.shutdownTimeout)
defer cancel() defer cancel()
if err := server.Shutdown(shutdownCtx); err != nil { if err := server.Shutdown(shutdownCtx); err != nil {
s.logger.Error("http server failed shutting down within " + s.logger.Error(s.name + " http server failed shutting down within " +
s.shutdownTimeout.String()) s.shutdownTimeout.String())
} }
}() }()
@@ -46,7 +47,7 @@ func (s *Server) Run(ctx context.Context, ready chan<- struct{}, done chan<- str
close(s.addressSet) close(s.addressSet)
// note: no further write so no need to mutex // note: no further write so no need to mutex
s.logger.Info("http server listening on " + s.address) s.logger.Info(s.name + " http server listening on " + s.address)
close(ready) close(ready)
err = server.Serve(listener) err = server.Serve(listener)

View File

@@ -16,10 +16,12 @@ func Test_Server_Run_success(t *testing.T) {
ctrl := gomock.NewController(t) ctrl := gomock.NewController(t)
logger := NewMockLogger(ctrl) logger := NewMockLogger(ctrl)
logger.EXPECT().Info(newRegexMatcher("^http server listening on 127.0.0.1:[1-9][0-9]{0,4}$")) logger.EXPECT().Info(newRegexMatcher("^test http server listening on 127.0.0.1:[1-9][0-9]{0,4}$"))
logger.EXPECT().Warn("test http server shutting down: context canceled")
const shutdownTimeout = 10 * time.Second const shutdownTimeout = 10 * time.Second
server := &Server{ server := &Server{
name: "test",
address: "127.0.0.1:0", address: "127.0.0.1:0",
addressSet: make(chan struct{}), addressSet: make(chan struct{}),
logger: logger, logger: logger,
@@ -53,6 +55,7 @@ func Test_Server_Run_failure(t *testing.T) {
logger.EXPECT().Error("listen tcp: address -1: invalid port") logger.EXPECT().Error("listen tcp: address -1: invalid port")
server := &Server{ server := &Server{
name: "test",
address: "127.0.0.1:-1", address: "127.0.0.1:-1",
addressSet: make(chan struct{}), addressSet: make(chan struct{}),
logger: logger, logger: logger,

View File

@@ -29,6 +29,7 @@ type AddressGetter interface {
// Server is an HTTP server implementation, which uses // Server is an HTTP server implementation, which uses
// the HTTP handler provided. // the HTTP handler provided.
type Server struct { type Server struct {
name string
address string address string
addressSet chan struct{} addressSet chan struct{}
handler http.Handler handler http.Handler
@@ -46,6 +47,7 @@ func New(settings Settings) (s *Server, err error) {
} }
return &Server{ return &Server{
name: *settings.Name,
address: settings.Address, address: settings.Address,
addressSet: make(chan struct{}), addressSet: make(chan struct{}),
handler: settings.Handler, handler: settings.Handler,

View File

@@ -29,12 +29,14 @@ func Test_New(t *testing.T) {
}, },
"filled settings": { "filled settings": {
settings: Settings{ settings: Settings{
Name: stringPtr("name"),
Address: ":8001", Address: ":8001",
Handler: someHandler, Handler: someHandler,
Logger: someLogger, Logger: someLogger,
ShutdownTimeout: durationPtr(time.Second), ShutdownTimeout: durationPtr(time.Second),
}, },
expected: &Server{ expected: &Server{
name: "name",
address: ":8001", address: ":8001",
handler: someHandler, handler: someHandler,
logger: someLogger, logger: someLogger,

View File

@@ -5,6 +5,7 @@ import (
"fmt" "fmt"
"net/http" "net/http"
"os" "os"
"strings"
"time" "time"
"github.com/qdm12/gluetun/internal/configuration/settings/helpers" "github.com/qdm12/gluetun/internal/configuration/settings/helpers"
@@ -13,6 +14,9 @@ import (
) )
type Settings struct { type Settings struct {
// Name is the server name to use in logs.
// It defaults to the empty string.
Name *string
// Address is the server listening address. // Address is the server listening address.
// It defaults to :8000. // It defaults to :8000.
Address string Address string
@@ -28,6 +32,7 @@ type Settings struct {
} }
func (s *Settings) SetDefaults() { func (s *Settings) SetDefaults() {
s.Name = helpers.DefaultStringPtr(s.Name, "")
s.Address = helpers.DefaultString(s.Address, ":8000") s.Address = helpers.DefaultString(s.Address, ":8000")
const defaultShutdownTimeout = 3 * time.Second const defaultShutdownTimeout = 3 * time.Second
s.ShutdownTimeout = helpers.DefaultDuration(s.ShutdownTimeout, defaultShutdownTimeout) s.ShutdownTimeout = helpers.DefaultDuration(s.ShutdownTimeout, defaultShutdownTimeout)
@@ -35,6 +40,7 @@ func (s *Settings) SetDefaults() {
func (s Settings) Copy() Settings { func (s Settings) Copy() Settings {
return Settings{ return Settings{
Name: helpers.CopyStringPtr(s.Name),
Address: s.Address, Address: s.Address,
Handler: s.Handler, Handler: s.Handler,
Logger: s.Logger, Logger: s.Logger,
@@ -43,6 +49,7 @@ func (s Settings) Copy() Settings {
} }
func (s *Settings) MergeWith(other Settings) { func (s *Settings) MergeWith(other Settings) {
s.Name = helpers.MergeWithStringPtr(s.Name, other.Name)
s.Address = helpers.MergeWithString(s.Address, other.Address) s.Address = helpers.MergeWithString(s.Address, other.Address)
s.Handler = helpers.MergeWithHTTPHandler(s.Handler, other.Handler) s.Handler = helpers.MergeWithHTTPHandler(s.Handler, other.Handler)
if s.Logger == nil { if s.Logger == nil {
@@ -52,6 +59,7 @@ func (s *Settings) MergeWith(other Settings) {
} }
func (s *Settings) OverrideWith(other Settings) { func (s *Settings) OverrideWith(other Settings) {
s.Name = helpers.OverrideWithStringPtr(s.Name, other.Name)
s.Address = helpers.OverrideWithString(s.Address, other.Address) s.Address = helpers.OverrideWithString(s.Address, other.Address)
s.Handler = helpers.OverrideWithHTTPHandler(s.Handler, other.Handler) s.Handler = helpers.OverrideWithHTTPHandler(s.Handler, other.Handler)
if other.Logger != nil { if other.Logger != nil {
@@ -92,7 +100,7 @@ func (s Settings) Validate() (err error) {
} }
func (s Settings) ToLinesNode() (node *gotree.Node) { func (s Settings) ToLinesNode() (node *gotree.Node) {
node = gotree.New("HTTP server settings:") node = gotree.New("%s HTTP server settings:", strings.Title(*s.Name))
node.Appendf("Listening address: %s", s.Address) node.Appendf("Listening address: %s", s.Address)
node.Appendf("Shutdown timeout: %s", *s.ShutdownTimeout) node.Appendf("Shutdown timeout: %s", *s.ShutdownTimeout)
return node return node

View File

@@ -21,16 +21,19 @@ func Test_Settings_SetDefaults(t *testing.T) {
"empty settings": { "empty settings": {
settings: Settings{}, settings: Settings{},
expected: Settings{ expected: Settings{
Name: stringPtr(""),
Address: ":8000", Address: ":8000",
ShutdownTimeout: durationPtr(defaultTimeout), ShutdownTimeout: durationPtr(defaultTimeout),
}, },
}, },
"filled settings": { "filled settings": {
settings: Settings{ settings: Settings{
Name: stringPtr("name"),
Address: ":8001", Address: ":8001",
ShutdownTimeout: durationPtr(time.Second), ShutdownTimeout: durationPtr(time.Second),
}, },
expected: Settings{ expected: Settings{
Name: stringPtr("name"),
Address: ":8001", Address: ":8001",
ShutdownTimeout: durationPtr(time.Second), ShutdownTimeout: durationPtr(time.Second),
}, },
@@ -62,12 +65,14 @@ func Test_Settings_Copy(t *testing.T) {
"empty settings": {}, "empty settings": {},
"filled settings": { "filled settings": {
settings: Settings{ settings: Settings{
Name: stringPtr("name"),
Address: ":8001", Address: ":8001",
Handler: someHandler, Handler: someHandler,
Logger: someLogger, Logger: someLogger,
ShutdownTimeout: durationPtr(time.Second), ShutdownTimeout: durationPtr(time.Second),
}, },
expected: Settings{ expected: Settings{
Name: stringPtr("name"),
Address: ":8001", Address: ":8001",
Handler: someHandler, Handler: someHandler,
Logger: someLogger, Logger: someLogger,
@@ -102,12 +107,14 @@ func Test_Settings_MergeWith(t *testing.T) {
"merge empty with empty": {}, "merge empty with empty": {},
"merge empty with filled": { "merge empty with filled": {
other: Settings{ other: Settings{
Name: stringPtr("name"),
Address: ":8001", Address: ":8001",
Handler: someHandler, Handler: someHandler,
Logger: someLogger, Logger: someLogger,
ShutdownTimeout: durationPtr(time.Second), ShutdownTimeout: durationPtr(time.Second),
}, },
expected: Settings{ expected: Settings{
Name: stringPtr("name"),
Address: ":8001", Address: ":8001",
Handler: someHandler, Handler: someHandler,
Logger: someLogger, Logger: someLogger,
@@ -116,12 +123,14 @@ func Test_Settings_MergeWith(t *testing.T) {
}, },
"merge filled with empty": { "merge filled with empty": {
settings: Settings{ settings: Settings{
Name: stringPtr("name"),
Address: ":8001", Address: ":8001",
Handler: someHandler, Handler: someHandler,
Logger: someLogger, Logger: someLogger,
ShutdownTimeout: durationPtr(time.Second), ShutdownTimeout: durationPtr(time.Second),
}, },
expected: Settings{ expected: Settings{
Name: stringPtr("name"),
Address: ":8001", Address: ":8001",
Handler: someHandler, Handler: someHandler,
Logger: someLogger, Logger: someLogger,
@@ -156,12 +165,14 @@ func Test_Settings_OverrideWith(t *testing.T) {
"override empty with empty": {}, "override empty with empty": {},
"override empty with filled": { "override empty with filled": {
other: Settings{ other: Settings{
Name: stringPtr("name"),
Address: ":8001", Address: ":8001",
Handler: someHandler, Handler: someHandler,
Logger: someLogger, Logger: someLogger,
ShutdownTimeout: durationPtr(time.Second), ShutdownTimeout: durationPtr(time.Second),
}, },
expected: Settings{ expected: Settings{
Name: stringPtr("name"),
Address: ":8001", Address: ":8001",
Handler: someHandler, Handler: someHandler,
Logger: someLogger, Logger: someLogger,
@@ -170,12 +181,14 @@ func Test_Settings_OverrideWith(t *testing.T) {
}, },
"override filled with empty": { "override filled with empty": {
settings: Settings{ settings: Settings{
Name: stringPtr("name"),
Address: ":8001", Address: ":8001",
Handler: someHandler, Handler: someHandler,
Logger: someLogger, Logger: someLogger,
ShutdownTimeout: durationPtr(time.Second), ShutdownTimeout: durationPtr(time.Second),
}, },
expected: Settings{ expected: Settings{
Name: stringPtr("name"),
Address: ":8001", Address: ":8001",
Handler: someHandler, Handler: someHandler,
Logger: someLogger, Logger: someLogger,
@@ -184,16 +197,19 @@ func Test_Settings_OverrideWith(t *testing.T) {
}, },
"override filled with filled": { "override filled with filled": {
settings: Settings{ settings: Settings{
Name: stringPtr("name"),
Address: ":8001", Address: ":8001",
Handler: someHandler, Handler: someHandler,
Logger: someLogger, Logger: someLogger,
ShutdownTimeout: durationPtr(time.Second), ShutdownTimeout: durationPtr(time.Second),
}, },
other: Settings{ other: Settings{
Name: stringPtr("name2"),
Address: ":8002", Address: ":8002",
ShutdownTimeout: durationPtr(time.Hour), ShutdownTimeout: durationPtr(time.Hour),
}, },
expected: Settings{ expected: Settings{
Name: stringPtr("name2"),
Address: ":8002", Address: ":8002",
Handler: someHandler, Handler: someHandler,
Logger: someLogger, Logger: someLogger,
@@ -291,10 +307,11 @@ func Test_Settings_String(t *testing.T) {
}{ }{
"all values": { "all values": {
settings: Settings{ settings: Settings{
Name: stringPtr("name"),
Address: ":8000", Address: ":8000",
ShutdownTimeout: durationPtr(time.Second), ShutdownTimeout: durationPtr(time.Second),
}, },
s: `HTTP server settings: s: `Name HTTP server settings:
├── Listening address: :8000 ├── Listening address: :8000
└── Shutdown timeout: 1s`, └── Shutdown timeout: 1s`,
}, },

View File

@@ -28,98 +28,246 @@ func (a AllServers) GetCopy() (servers AllServers) {
return servers return servers
} }
func (a *AllServers) GetCyberghost() (servers []Server) { func (a *AllServers) GetCyberghost() (servers []CyberghostServer) {
return copyServers(a.Cyberghost.Servers) if a.Cyberghost.Servers == nil {
}
func (a *AllServers) GetExpressvpn() (servers []Server) {
return copyServers(a.Expressvpn.Servers)
}
func (a *AllServers) GetFastestvpn() (servers []Server) {
return copyServers(a.Fastestvpn.Servers)
}
func (a *AllServers) GetHideMyAss() (servers []Server) {
return copyServers(a.HideMyAss.Servers)
}
func (a *AllServers) GetIpvanish() (servers []Server) {
return copyServers(a.Ipvanish.Servers)
}
func (a *AllServers) GetIvpn() (servers []Server) {
return copyServers(a.Ivpn.Servers)
}
func (a *AllServers) GetMullvad() (servers []Server) {
return copyServers(a.Mullvad.Servers)
}
func (a *AllServers) GetNordvpn() (servers []Server) {
return copyServers(a.Nordvpn.Servers)
}
func (a *AllServers) GetPerfectprivacy() (servers []Server) {
return copyServers(a.Perfectprivacy.Servers)
}
func (a *AllServers) GetPia() (servers []Server) {
return copyServers(a.Pia.Servers)
}
func (a *AllServers) GetPrivado() (servers []Server) {
return copyServers(a.Privado.Servers)
}
func (a *AllServers) GetPrivatevpn() (servers []Server) {
return copyServers(a.Privatevpn.Servers)
}
func (a *AllServers) GetProtonvpn() (servers []Server) {
return copyServers(a.Protonvpn.Servers)
}
func (a *AllServers) GetPurevpn() (servers []Server) {
return copyServers(a.Purevpn.Servers)
}
func (a *AllServers) GetSurfshark() (servers []Server) {
return copyServers(a.Surfshark.Servers)
}
func (a *AllServers) GetTorguard() (servers []Server) {
return copyServers(a.Torguard.Servers)
}
func (a *AllServers) GetVPNUnlimited() (servers []Server) {
return copyServers(a.VPNUnlimited.Servers)
}
func (a *AllServers) GetVyprvpn() (servers []Server) {
return copyServers(a.Vyprvpn.Servers)
}
func (a *AllServers) GetWevpn() (servers []Server) {
return copyServers(a.Wevpn.Servers)
}
func (a *AllServers) GetWindscribe() (servers []Server) {
return copyServers(a.Windscribe.Servers)
}
func copyServers(servers []Server) (serversCopy []Server) {
if servers == nil {
return nil return nil
} }
servers = make([]CyberghostServer, len(a.Cyberghost.Servers))
serversCopy = make([]Server, len(servers)) for i, serverToCopy := range a.Cyberghost.Servers {
for i, server := range servers { servers[i] = serverToCopy
serversCopy[i] = server servers[i].IPs = copyIPs(serverToCopy.IPs)
serversCopy[i].IPs = copyIPs(server.IPs)
} }
return servers
}
return serversCopy func (a *AllServers) GetExpressvpn() (servers []ExpressvpnServer) {
if a.Expressvpn.Servers == nil {
return nil
}
servers = make([]ExpressvpnServer, len(a.Expressvpn.Servers))
for i, serverToCopy := range a.Expressvpn.Servers {
servers[i] = serverToCopy
servers[i].IPs = copyIPs(serverToCopy.IPs)
}
return servers
}
func (a *AllServers) GetFastestvpn() (servers []FastestvpnServer) {
if a.Fastestvpn.Servers == nil {
return nil
}
servers = make([]FastestvpnServer, len(a.Fastestvpn.Servers))
for i, serverToCopy := range a.Fastestvpn.Servers {
servers[i] = serverToCopy
servers[i].IPs = copyIPs(serverToCopy.IPs)
}
return servers
}
func (a *AllServers) GetHideMyAss() (servers []HideMyAssServer) {
if a.HideMyAss.Servers == nil {
return nil
}
servers = make([]HideMyAssServer, len(a.HideMyAss.Servers))
for i, serverToCopy := range a.HideMyAss.Servers {
servers[i] = serverToCopy
servers[i].IPs = copyIPs(serverToCopy.IPs)
}
return servers
}
func (a *AllServers) GetIpvanish() (servers []IpvanishServer) {
if a.Ipvanish.Servers == nil {
return nil
}
servers = make([]IpvanishServer, len(a.Ipvanish.Servers))
for i, serverToCopy := range a.Ipvanish.Servers {
servers[i] = serverToCopy
servers[i].IPs = copyIPs(serverToCopy.IPs)
}
return servers
}
func (a *AllServers) GetIvpn() (servers []IvpnServer) {
if a.Ivpn.Servers == nil {
return nil
}
servers = make([]IvpnServer, len(a.Ivpn.Servers))
for i, serverToCopy := range a.Ivpn.Servers {
servers[i] = serverToCopy
servers[i].IPs = copyIPs(serverToCopy.IPs)
}
return servers
}
func (a *AllServers) GetMullvad() (servers []MullvadServer) {
if a.Mullvad.Servers == nil {
return nil
}
servers = make([]MullvadServer, len(a.Mullvad.Servers))
for i, serverToCopy := range a.Mullvad.Servers {
servers[i] = serverToCopy
servers[i].IPs = copyIPs(serverToCopy.IPs)
servers[i].IPsV6 = copyIPs(serverToCopy.IPsV6)
}
return servers
}
func (a *AllServers) GetNordvpn() (servers []NordvpnServer) {
if a.Nordvpn.Servers == nil {
return nil
}
servers = make([]NordvpnServer, len(a.Nordvpn.Servers))
for i, serverToCopy := range a.Nordvpn.Servers {
servers[i] = serverToCopy
servers[i].IP = copyIP(serverToCopy.IP)
}
return servers
}
func (a *AllServers) GetPerfectprivacy() (servers []PerfectprivacyServer) {
if a.Perfectprivacy.Servers == nil {
return nil
}
servers = make([]PerfectprivacyServer, len(a.Perfectprivacy.Servers))
for i, serverToCopy := range a.Perfectprivacy.Servers {
servers[i] = serverToCopy
servers[i].IPs = copyIPs(serverToCopy.IPs)
}
return servers
}
func (a *AllServers) GetPia() (servers []PIAServer) {
if a.Pia.Servers == nil {
return nil
}
servers = make([]PIAServer, len(a.Pia.Servers))
for i, serverToCopy := range a.Pia.Servers {
servers[i] = serverToCopy
servers[i].IPs = copyIPs(serverToCopy.IPs)
}
return servers
}
func (a *AllServers) GetPrivado() (servers []PrivadoServer) {
if a.Privado.Servers == nil {
return nil
}
servers = make([]PrivadoServer, len(a.Privado.Servers))
for i, serverToCopy := range a.Privado.Servers {
servers[i] = serverToCopy
servers[i].IP = copyIP(serverToCopy.IP)
}
return servers
}
func (a *AllServers) GetPrivatevpn() (servers []PrivatevpnServer) {
if a.Privatevpn.Servers == nil {
return nil
}
servers = make([]PrivatevpnServer, len(a.Privatevpn.Servers))
for i, serverToCopy := range a.Privatevpn.Servers {
servers[i] = serverToCopy
servers[i].IPs = copyIPs(serverToCopy.IPs)
}
return servers
}
func (a *AllServers) GetProtonvpn() (servers []ProtonvpnServer) {
if a.Protonvpn.Servers == nil {
return nil
}
servers = make([]ProtonvpnServer, len(a.Protonvpn.Servers))
for i, serverToCopy := range a.Protonvpn.Servers {
servers[i] = serverToCopy
servers[i].EntryIP = copyIP(serverToCopy.EntryIP)
servers[i].ExitIPs = copyIPs(serverToCopy.ExitIPs)
}
return servers
}
func (a *AllServers) GetPurevpn() (servers []PurevpnServer) {
if a.Purevpn.Servers == nil {
return nil
}
servers = make([]PurevpnServer, len(a.Purevpn.Servers))
for i, serverToCopy := range a.Purevpn.Servers {
servers[i] = serverToCopy
servers[i].IPs = copyIPs(serverToCopy.IPs)
}
return servers
}
func (a *AllServers) GetSurfshark() (servers []SurfsharkServer) {
if a.Surfshark.Servers == nil {
return nil
}
servers = make([]SurfsharkServer, len(a.Surfshark.Servers))
for i, serverToCopy := range a.Surfshark.Servers {
servers[i] = serverToCopy
servers[i].IPs = copyIPs(serverToCopy.IPs)
}
return servers
}
func (a *AllServers) GetTorguard() (servers []TorguardServer) {
if a.Torguard.Servers == nil {
return nil
}
servers = make([]TorguardServer, len(a.Torguard.Servers))
for i, serverToCopy := range a.Torguard.Servers {
servers[i] = serverToCopy
servers[i].IPs = copyIPs(serverToCopy.IPs)
}
return servers
}
func (a *AllServers) GetVPNUnlimited() (servers []VPNUnlimitedServer) {
if a.VPNUnlimited.Servers == nil {
return nil
}
servers = make([]VPNUnlimitedServer, len(a.VPNUnlimited.Servers))
for i, serverToCopy := range a.VPNUnlimited.Servers {
servers[i] = serverToCopy
servers[i].IPs = copyIPs(serverToCopy.IPs)
}
return servers
}
func (a *AllServers) GetVyprvpn() (servers []VyprvpnServer) {
if a.Vyprvpn.Servers == nil {
return nil
}
servers = make([]VyprvpnServer, len(a.Vyprvpn.Servers))
for i, serverToCopy := range a.Vyprvpn.Servers {
servers[i] = serverToCopy
servers[i].IPs = copyIPs(serverToCopy.IPs)
}
return servers
}
func (a *AllServers) GetWevpn() (servers []WevpnServer) {
if a.Windscribe.Servers == nil {
return nil
}
servers = make([]WevpnServer, len(a.Wevpn.Servers))
for i, serverToCopy := range a.Wevpn.Servers {
servers[i] = serverToCopy
servers[i].IPs = copyIPs(serverToCopy.IPs)
}
return servers
}
func (a *AllServers) GetWindscribe() (servers []WindscribeServer) {
if a.Windscribe.Servers == nil {
return nil
}
servers = make([]WindscribeServer, len(a.Windscribe.Servers))
for i, serverToCopy := range a.Windscribe.Servers {
servers[i] = serverToCopy
servers[i].IPs = copyIPs(serverToCopy.IPs)
}
return servers
} }
func copyIPs(toCopy []net.IP) (copied []net.IP) { func copyIPs(toCopy []net.IP) (copied []net.IP) {

View File

@@ -10,100 +10,101 @@ import (
func Test_AllServers_GetCopy(t *testing.T) { func Test_AllServers_GetCopy(t *testing.T) {
allServers := AllServers{ allServers := AllServers{
Cyberghost: Servers{ Cyberghost: CyberghostServers{
Version: 2, Version: 2,
Servers: []Server{{ Servers: []CyberghostServer{{
IPs: []net.IP{{1, 2, 3, 4}}, IPs: []net.IP{{1, 2, 3, 4}},
}}, }},
}, },
Expressvpn: Servers{ Expressvpn: ExpressvpnServers{
Servers: []Server{{ Servers: []ExpressvpnServer{{
IPs: []net.IP{{1, 2, 3, 4}}, IPs: []net.IP{{1, 2, 3, 4}},
}}, }},
}, },
Fastestvpn: Servers{ Fastestvpn: FastestvpnServers{
Servers: []Server{{ Servers: []FastestvpnServer{{
IPs: []net.IP{{1, 2, 3, 4}}, IPs: []net.IP{{1, 2, 3, 4}},
}}, }},
}, },
HideMyAss: Servers{ HideMyAss: HideMyAssServers{
Servers: []Server{{ Servers: []HideMyAssServer{{
IPs: []net.IP{{1, 2, 3, 4}}, IPs: []net.IP{{1, 2, 3, 4}},
}}, }},
}, },
Ipvanish: Servers{ Ipvanish: IpvanishServers{
Servers: []Server{{ Servers: []IpvanishServer{{
IPs: []net.IP{{1, 2, 3, 4}}, IPs: []net.IP{{1, 2, 3, 4}},
}}, }},
}, },
Ivpn: Servers{ Ivpn: IvpnServers{
Servers: []Server{{ Servers: []IvpnServer{{
IPs: []net.IP{{1, 2, 3, 4}}, IPs: []net.IP{{1, 2, 3, 4}},
}}, }},
}, },
Mullvad: Servers{ Mullvad: MullvadServers{
Servers: []Server{{ Servers: []MullvadServer{{
IPs: []net.IP{{1, 2, 3, 4}}, IPs: []net.IP{{1, 2, 3, 4}},
}}, }},
}, },
Nordvpn: Servers{ Nordvpn: NordvpnServers{
Servers: []Server{{ Servers: []NordvpnServer{{
IP: net.IP{1, 2, 3, 4},
}},
},
Perfectprivacy: PerfectprivacyServers{
Servers: []PerfectprivacyServer{{
IPs: []net.IP{{1, 2, 3, 4}}, IPs: []net.IP{{1, 2, 3, 4}},
}}, }},
}, },
Perfectprivacy: Servers{ Privado: PrivadoServers{
Servers: []Server{{ Servers: []PrivadoServer{{
IP: net.IP{1, 2, 3, 4},
}},
},
Pia: PiaServers{
Servers: []PIAServer{{
IPs: []net.IP{{1, 2, 3, 4}}, IPs: []net.IP{{1, 2, 3, 4}},
}}, }},
}, },
Privado: Servers{ Privatevpn: PrivatevpnServers{
Servers: []Server{{ Servers: []PrivatevpnServer{{
IPs: []net.IP{{1, 2, 3, 4}}, IPs: []net.IP{{1, 2, 3, 4}},
}}, }},
}, },
Pia: Servers{ Protonvpn: ProtonvpnServers{
Servers: []Server{{ Servers: []ProtonvpnServer{{
IPs: []net.IP{{1, 2, 3, 4}}, EntryIP: net.IP{1, 2, 3, 4},
ExitIPs: []net.IP{{1, 2, 3, 4}},
}}, }},
}, },
Privatevpn: Servers{ Purevpn: PurevpnServers{
Servers: []Server{{
IPs: []net.IP{{1, 2, 3, 4}},
}},
},
Protonvpn: Servers{
Servers: []Server{{
IPs: []net.IP{{1, 2, 3, 4}},
}},
},
Purevpn: Servers{
Version: 1, Version: 1,
Servers: []Server{{ Servers: []PurevpnServer{{
IPs: []net.IP{{1, 2, 3, 4}}, IPs: []net.IP{{1, 2, 3, 4}},
}}, }},
}, },
Surfshark: Servers{ Surfshark: SurfsharkServers{
Servers: []Server{{ Servers: []SurfsharkServer{{
IPs: []net.IP{{1, 2, 3, 4}}, IPs: []net.IP{{1, 2, 3, 4}},
}}, }},
}, },
Torguard: Servers{ Torguard: TorguardServers{
Servers: []Server{{ Servers: []TorguardServer{{
IPs: []net.IP{{1, 2, 3, 4}}, IPs: []net.IP{{1, 2, 3, 4}},
}}, }},
}, },
VPNUnlimited: Servers{ VPNUnlimited: VPNUnlimitedServers{
Servers: []Server{{ Servers: []VPNUnlimitedServer{{
IPs: []net.IP{{1, 2, 3, 4}}, IPs: []net.IP{{1, 2, 3, 4}},
}}, }},
}, },
Vyprvpn: Servers{ Vyprvpn: VyprvpnServers{
Servers: []Server{{ Servers: []VyprvpnServer{{
IPs: []net.IP{{1, 2, 3, 4}}, IPs: []net.IP{{1, 2, 3, 4}},
}}, }},
}, },
Windscribe: Servers{ Windscribe: WindscribeServers{
Servers: []Server{{ Servers: []WindscribeServer{{
IPs: []net.IP{{1, 2, 3, 4}}, IPs: []net.IP{{1, 2, 3, 4}},
}}, }},
}, },
@@ -116,8 +117,8 @@ func Test_AllServers_GetCopy(t *testing.T) {
func Test_AllServers_GetVyprvpn(t *testing.T) { func Test_AllServers_GetVyprvpn(t *testing.T) {
allServers := AllServers{ allServers := AllServers{
Vyprvpn: Servers{ Vyprvpn: VyprvpnServers{
Servers: []Server{ Servers: []VyprvpnServer{
{Hostname: "a", IPs: []net.IP{{1, 1, 1, 1}}}, {Hostname: "a", IPs: []net.IP{{1, 1, 1, 1}}},
{Hostname: "b", IPs: []net.IP{{2, 2, 2, 2}}}, {Hostname: "b", IPs: []net.IP{{2, 2, 2, 2}}},
}, },
@@ -126,7 +127,7 @@ func Test_AllServers_GetVyprvpn(t *testing.T) {
servers := allServers.GetVyprvpn() servers := allServers.GetVyprvpn()
expectedServers := []Server{ expectedServers := []VyprvpnServer{
{Hostname: "a", IPs: []net.IP{{1, 1, 1, 1}}}, {Hostname: "a", IPs: []net.IP{{1, 1, 1, 1}}},
{Hostname: "b", IPs: []net.IP{{2, 2, 2, 2}}}, {Hostname: "b", IPs: []net.IP{{2, 2, 2, 2}}},
} }

View File

@@ -3,8 +3,6 @@ package models
import ( import (
"fmt" "fmt"
"strings" "strings"
"github.com/qdm12/gluetun/internal/constants/providers"
) )
func boolToMarkdown(b bool) string { func boolToMarkdown(b bool) string {
@@ -16,126 +14,280 @@ func boolToMarkdown(b bool) string {
func markdownTableHeading(legendFields ...string) (markdown string) { func markdownTableHeading(legendFields ...string) (markdown string) {
return "| " + strings.Join(legendFields, " | ") + " |\n" + return "| " + strings.Join(legendFields, " | ") + " |\n" +
"|" + strings.Repeat(" --- |", len(legendFields)) "|" + strings.Repeat(" --- |", len(legendFields)) + "\n"
} }
const ( func (s *CyberghostServers) ToMarkdown() (markdown string) {
vpnHeader = "VPN" markdown = markdownTableHeading("Country", "Hostname", "TCP", "UDP")
countryHeader = "Country" for _, server := range s.Servers {
regionHeader = "Region" markdown += server.ToMarkdown() + "\n"
cityHeader = "City"
ispHeader = "ISP"
ownedHeader = "Owned"
numberHeader = "Number"
hostnameHeader = "Hostname"
tcpHeader = "TCP"
udpHeader = "UDP"
multiHopHeader = "MultiHop"
freeHeader = "Free"
streamHeader = "Stream"
portForwardHeader = "Port forwarding"
)
func (s *Server) ToMarkdown(headers ...string) (markdown string) {
if len(headers) == 0 {
return ""
} }
fields := make([]string, len(headers))
for i, header := range headers {
switch header {
case vpnHeader:
fields[i] = s.VPN
case countryHeader:
fields[i] = s.Country
case regionHeader:
fields[i] = s.Region
case cityHeader:
fields[i] = s.City
case ispHeader:
fields[i] = s.ISP
case ownedHeader:
fields[i] = boolToMarkdown(s.Owned)
case numberHeader:
fields[i] = fmt.Sprint(s.Number)
case hostnameHeader:
fields[i] = fmt.Sprintf("`%s`", s.Hostname)
case tcpHeader:
fields[i] = boolToMarkdown(s.TCP)
case udpHeader:
fields[i] = boolToMarkdown(s.UDP)
case multiHopHeader:
fields[i] = boolToMarkdown(s.MultiHop)
case freeHeader:
fields[i] = boolToMarkdown(s.Free)
case streamHeader:
fields[i] = boolToMarkdown(s.Stream)
case portForwardHeader:
fields[i] = boolToMarkdown(s.PortForward)
}
}
return "| " + strings.Join(fields, " | ") + " |"
}
func (s *Servers) ToMarkdown(vpnProvider string) (markdown string) {
headers := getMarkdownHeaders(vpnProvider)
legend := markdownTableHeading(headers...)
entries := make([]string, len(s.Servers))
for i, server := range s.Servers {
entries[i] = server.ToMarkdown(headers...)
}
markdown = legend + "\n" +
strings.Join(entries, "\n") + "\n"
return markdown return markdown
} }
func getMarkdownHeaders(vpnProvider string) (headers []string) { func (s CyberghostServer) ToMarkdown() (markdown string) {
switch vpnProvider { return fmt.Sprintf("| %s | `%s` | %s | %s |", s.Country, s.Hostname,
case providers.Cyberghost: boolToMarkdown(s.TCP), boolToMarkdown(s.UDP))
return []string{countryHeader, hostnameHeader, tcpHeader, udpHeader} }
case providers.Expressvpn:
return []string{countryHeader, cityHeader, hostnameHeader, tcpHeader, udpHeader} func (s *ExpressvpnServers) ToMarkdown() (markdown string) {
case providers.Fastestvpn: markdown = markdownTableHeading("Country", "City", "Hostname", "TCP", "UDP")
return []string{countryHeader, hostnameHeader, tcpHeader, udpHeader} for _, server := range s.Servers {
case providers.HideMyAss: markdown += server.ToMarkdown() + "\n"
return []string{countryHeader, regionHeader, cityHeader, hostnameHeader, tcpHeader, udpHeader} }
case providers.Ipvanish: return markdown
return []string{countryHeader, cityHeader, hostnameHeader, tcpHeader, udpHeader} }
case providers.Ivpn:
return []string{countryHeader, cityHeader, ispHeader, hostnameHeader, vpnHeader, tcpHeader, udpHeader} func (s *ExpressvpnServer) ToMarkdown() (markdown string) {
case providers.Mullvad: return fmt.Sprintf("| %s | %s | `%s` | %s | %s |",
return []string{countryHeader, cityHeader, ispHeader, ownedHeader, hostnameHeader, vpnHeader} s.Country, s.City, s.Hostname,
case providers.Nordvpn: boolToMarkdown(s.TCP), boolToMarkdown(s.UDP))
return []string{countryHeader, regionHeader, cityHeader, hostnameHeader} }
case providers.Perfectprivacy:
return []string{cityHeader, tcpHeader, udpHeader} func (s *FastestvpnServers) ToMarkdown() (markdown string) {
case providers.Privado: markdown = markdownTableHeading("Country", "Hostname", "TCP", "UDP")
return []string{countryHeader, regionHeader, cityHeader, hostnameHeader} for _, server := range s.Servers {
case providers.PrivateInternetAccess: markdown += server.ToMarkdown() + "\n"
return []string{regionHeader, hostnameHeader, tcpHeader, udpHeader, portForwardHeader} }
case providers.Privatevpn: return markdown
return []string{countryHeader, cityHeader, hostnameHeader} }
case providers.Protonvpn:
return []string{countryHeader, regionHeader, cityHeader, hostnameHeader, freeHeader} func (s *FastestvpnServer) ToMarkdown() (markdown string) {
case providers.Purevpn: return fmt.Sprintf("| %s | `%s` | %s | %s |",
return []string{countryHeader, regionHeader, cityHeader, hostnameHeader, tcpHeader, udpHeader} s.Country, s.Hostname, boolToMarkdown(s.TCP), boolToMarkdown(s.UDP))
case providers.Surfshark: }
return []string{regionHeader, countryHeader, cityHeader, hostnameHeader, multiHopHeader, tcpHeader, udpHeader}
case providers.Torguard: func (s *HideMyAssServers) ToMarkdown() (markdown string) {
return []string{countryHeader, cityHeader, hostnameHeader, tcpHeader, udpHeader} markdown = markdownTableHeading("Country", "Region", "City", "Hostname", "TCP", "UDP")
case providers.VPNUnlimited: for _, server := range s.Servers {
return []string{countryHeader, cityHeader, hostnameHeader, freeHeader, streamHeader, tcpHeader, udpHeader} markdown += server.ToMarkdown() + "\n"
case providers.Vyprvpn: }
return []string{regionHeader, hostnameHeader, tcpHeader, udpHeader} return markdown
case providers.Wevpn: }
return []string{cityHeader, hostnameHeader, tcpHeader, udpHeader}
case providers.Windscribe: func (s *HideMyAssServer) ToMarkdown() (markdown string) {
return []string{regionHeader, cityHeader, hostnameHeader, vpnHeader} return fmt.Sprintf("| %s | %s | %s | `%s` | %s | %s |",
default: s.Country, s.Region, s.City, s.Hostname,
return nil boolToMarkdown(s.TCP), boolToMarkdown(s.UDP))
} }
func (s *IpvanishServers) ToMarkdown() (markdown string) {
markdown = markdownTableHeading("Country", "City", "Hostname", "TCP", "UDP")
for _, server := range s.Servers {
markdown += server.ToMarkdown() + "\n"
}
return markdown
}
func (s *IpvanishServer) ToMarkdown() (markdown string) {
return fmt.Sprintf("| %s | %s | `%s` | %s | %s |",
s.Country, s.City, s.Hostname,
boolToMarkdown(s.TCP), boolToMarkdown(s.UDP))
}
func (s *IvpnServers) ToMarkdown() (markdown string) {
markdown = markdownTableHeading("Country", "City", "ISP", "Hostname", "VPN", "TCP", "UDP")
for _, server := range s.Servers {
markdown += server.ToMarkdown() + "\n"
}
return markdown
}
func (s *IvpnServer) ToMarkdown() (markdown string) {
return fmt.Sprintf("| %s | %s | %s | `%s` | %s | %s | %s |",
s.Country, s.City, s.ISP, s.Hostname, s.VPN,
boolToMarkdown(s.TCP), boolToMarkdown(s.UDP))
}
func (s *MullvadServers) ToMarkdown() (markdown string) {
markdown = markdownTableHeading("Country", "City", "ISP", "Owned",
"Hostname", "VPN")
for _, server := range s.Servers {
markdown += server.ToMarkdown() + "\n"
}
return markdown
}
func (s *MullvadServer) ToMarkdown() (markdown string) {
return fmt.Sprintf("| %s | %s | %s | %s | `%s` | %s |",
s.Country, s.City, s.ISP, boolToMarkdown(s.Owned),
s.Hostname, s.VPN)
}
func (s *NordvpnServers) ToMarkdown() (markdown string) {
markdown = markdownTableHeading("Region", "Hostname", "TCP", "UDP")
for _, server := range s.Servers {
markdown += server.ToMarkdown() + "\n"
}
return markdown
}
func (s *NordvpnServer) ToMarkdown() (markdown string) {
return fmt.Sprintf("| %s | `%s` | %s | %s |",
s.Region, s.Hostname,
boolToMarkdown(s.TCP), boolToMarkdown(s.UDP))
}
func (s *PrivadoServers) ToMarkdown() (markdown string) {
markdown = markdownTableHeading("Country", "Region", "City", "Hostname")
for _, server := range s.Servers {
markdown += server.ToMarkdown() + "\n"
}
return markdown
}
func (s *PrivadoServer) ToMarkdown() (markdown string) {
return fmt.Sprintf("| %s | %s | %s | `%s` |",
s.Country, s.Region, s.City, s.Hostname)
}
func (s *PerfectprivacyServers) ToMarkdown() (markdown string) {
markdown = markdownTableHeading("City", "TCP", "UDP")
for _, server := range s.Servers {
markdown += server.ToMarkdown() + "\n"
}
return markdown
}
func (s *PerfectprivacyServer) ToMarkdown() (markdown string) {
return fmt.Sprintf("| %s | %s | %s |",
s.City, boolToMarkdown(s.TCP), boolToMarkdown(s.UDP))
}
func (s *PiaServers) ToMarkdown() (markdown string) {
markdown = markdownTableHeading("Region", "Hostname", "TCP", "UDP")
for _, server := range s.Servers {
markdown += server.ToMarkdown() + "\n"
}
return markdown
}
func (s *PIAServer) ToMarkdown() (markdown string) {
return fmt.Sprintf("| %s | `%s` | %s | %s |",
s.Region, s.Hostname,
boolToMarkdown(s.TCP), boolToMarkdown(s.UDP))
}
func (s *PrivatevpnServers) ToMarkdown() (markdown string) {
markdown = markdownTableHeading("Country", "City", "Hostname")
for _, server := range s.Servers {
markdown += server.ToMarkdown() + "\n"
}
return markdown
}
func (s *PrivatevpnServer) ToMarkdown() (markdown string) {
return fmt.Sprintf("| %s | %s | `%s` |",
s.Country, s.City, s.Hostname)
}
func (s *ProtonvpnServers) ToMarkdown() (markdown string) {
markdown = markdownTableHeading("Country", "Region", "City", "Hostname", "Free tier")
for _, server := range s.Servers {
markdown += server.ToMarkdown() + "\n"
}
return markdown
}
func (s *ProtonvpnServer) ToMarkdown() (markdown string) {
isFree := strings.Contains(strings.ToLower(s.Name), "free")
return fmt.Sprintf("| %s | %s | %s | `%s` | %s |",
s.Country, s.Region, s.City, s.Hostname, boolToMarkdown(isFree))
}
func (s *PurevpnServers) ToMarkdown() (markdown string) {
markdown = markdownTableHeading("Country", "Region", "City", "Hostname", "TCP", "UDP")
for _, server := range s.Servers {
markdown += server.ToMarkdown() + "\n"
}
return markdown
}
func (s *PurevpnServer) ToMarkdown() (markdown string) {
return fmt.Sprintf("| %s | %s | %s | `%s` | %s | %s |",
s.Country, s.Region, s.City, s.Hostname,
boolToMarkdown(s.TCP), boolToMarkdown(s.UDP))
}
func (s *SurfsharkServers) ToMarkdown() (markdown string) {
markdown = markdownTableHeading("Region", "Country", "City", "Hostname", "Multi-hop", "TCP", "UDP")
for _, server := range s.Servers {
markdown += server.ToMarkdown() + "\n"
}
return markdown
}
func (s *SurfsharkServer) ToMarkdown() (markdown string) {
return fmt.Sprintf("| %s | %s | %s | `%s` | %s | %s | %s |",
s.Region, s.Country, s.City, s.Hostname, boolToMarkdown(s.MultiHop),
boolToMarkdown(s.TCP), boolToMarkdown(s.UDP))
}
func (s *TorguardServers) ToMarkdown() (markdown string) {
markdown = markdownTableHeading("Country", "City", "Hostname", "TCP", "UDP")
for _, server := range s.Servers {
markdown += server.ToMarkdown() + "\n"
}
return markdown
}
func (s *TorguardServer) ToMarkdown() (markdown string) {
return fmt.Sprintf("| %s | %s | `%s` | %s | %s |",
s.Country, s.City, s.Hostname,
boolToMarkdown(s.TCP), boolToMarkdown(s.UDP))
}
func (s *VPNUnlimitedServers) ToMarkdown() (markdown string) {
markdown = markdownTableHeading("Country", "City", "Hostname", "Free tier", "Streaming", "TCP", "UDP")
for _, server := range s.Servers {
markdown += server.ToMarkdown() + "\n"
}
return markdown
}
func (s *VPNUnlimitedServer) ToMarkdown() (markdown string) {
return fmt.Sprintf("| %s | %s | `%s` | %s | %s | %s | %s |",
s.Country, s.City, s.Hostname,
boolToMarkdown(s.Free), boolToMarkdown(s.Stream),
boolToMarkdown(s.TCP), boolToMarkdown(s.UDP))
}
func (s *VyprvpnServers) ToMarkdown() (markdown string) {
markdown = markdownTableHeading("Region", "Hostname", "TCP", "UDP")
for _, server := range s.Servers {
markdown += server.ToMarkdown() + "\n"
}
return markdown
}
func (s *VyprvpnServer) ToMarkdown() (markdown string) {
return fmt.Sprintf("| %s | `%s` | %s | %s |",
s.Region, s.Hostname,
boolToMarkdown(s.TCP), boolToMarkdown(s.UDP))
}
func (s *WevpnServers) ToMarkdown() (markdown string) {
markdown = markdownTableHeading("City", "Hostname", "TCP", "UDP")
for _, server := range s.Servers {
markdown += server.ToMarkdown() + "\n"
}
return markdown
}
func (s *WevpnServer) ToMarkdown() (markdown string) {
return fmt.Sprintf("| %s | `%s` | %s | %s |",
s.City, s.Hostname, boolToMarkdown(s.TCP), boolToMarkdown(s.UDP))
}
func (s *WindscribeServers) ToMarkdown() (markdown string) {
markdown = markdownTableHeading("Region", "City", "Hostname", "VPN")
for _, server := range s.Servers {
markdown += server.ToMarkdown() + "\n"
}
return markdown
}
func (s *WindscribeServer) ToMarkdown() (markdown string) {
return fmt.Sprintf("| %s | %s | `%s` | %s |",
s.Region, s.City, s.Hostname, s.VPN)
} }

View File

@@ -3,54 +3,43 @@ package models
import ( import (
"testing" "testing"
"github.com/qdm12/gluetun/internal/constants/providers"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
) )
func Test_Servers_ToMarkdown(t *testing.T) { func Test_CyberghostServers_ToMarkdown(t *testing.T) {
t.Parallel() t.Parallel()
testCases := map[string]struct { servers := CyberghostServers{
provider string Servers: []CyberghostServer{
servers Servers
expectedMarkdown string
}{
providers.Cyberghost: {
provider: providers.Cyberghost,
servers: Servers{
Servers: []Server{
{Country: "a", UDP: true, Hostname: "xa"}, {Country: "a", UDP: true, Hostname: "xa"},
{Country: "b", TCP: true, Hostname: "xb"}, {Country: "b", TCP: true, Hostname: "xb"},
}, },
}, }
expectedMarkdown: "| Country | Hostname | TCP | UDP |\n" +
markdown := servers.ToMarkdown()
const expected = "| Country | Hostname | TCP | UDP |\n" +
"| --- | --- | --- | --- |\n" + "| --- | --- | --- | --- |\n" +
"| a | `xa` | ❌ | ✅ |\n" + "| a | `xa` | ❌ | ✅ |\n" +
"| b | `xb` | ✅ | ❌ |\n", "| b | `xb` | ✅ | ❌ |\n"
},
providers.Fastestvpn: { assert.Equal(t, expected, markdown)
provider: providers.Fastestvpn, }
servers: Servers{
Servers: []Server{ func Test_FastestvpnServers_ToMarkdown(t *testing.T) {
t.Parallel()
servers := FastestvpnServers{
Servers: []FastestvpnServer{
{Country: "a", Hostname: "xa", TCP: true}, {Country: "a", Hostname: "xa", TCP: true},
{Country: "b", Hostname: "xb", UDP: true}, {Country: "b", Hostname: "xb", UDP: true},
}, },
}, }
expectedMarkdown: "| Country | Hostname | TCP | UDP |\n" +
markdown := servers.ToMarkdown()
const expected = "| Country | Hostname | TCP | UDP |\n" +
"| --- | --- | --- | --- |\n" + "| --- | --- | --- | --- |\n" +
"| a | `xa` | ✅ | ❌ |\n" + "| a | `xa` | ✅ | ❌ |\n" +
"| b | `xb` | ❌ | ✅ |\n", "| b | `xb` | ❌ | ✅ |\n"
},
}
for name, testCase := range testCases { assert.Equal(t, expected, markdown)
testCase := testCase
t.Run(name, func(t *testing.T) {
t.Parallel()
markdown := testCase.servers.ToMarkdown(testCase.provider)
assert.Equal(t, testCase.expectedMarkdown, markdown)
})
}
} }

View File

@@ -4,25 +4,189 @@ import (
"net" "net"
) )
type Server struct { type CyberghostServer struct {
VPN string `json:"vpn,omitempty"` Country string `json:"country"`
// Surfshark: country is also used for multi-hop Hostname string `json:"hostname"`
Country string `json:"country,omitempty"` TCP bool `json:"tcp"`
Region string `json:"region,omitempty"` UDP bool `json:"udp"`
City string `json:"city,omitempty"` IPs []net.IP `json:"ips"`
ISP string `json:"isp,omitempty"` }
Owned bool `json:"owned,omitempty"`
Number uint16 `json:"number,omitempty"` type ExpressvpnServer struct {
ServerName string `json:"server_name,omitempty"` Country string `json:"country"`
Hostname string `json:"hostname,omitempty"` City string `json:"city,omitempty"`
TCP bool `json:"tcp,omitempty"` Hostname string `json:"hostname"`
UDP bool `json:"udp,omitempty"` TCP bool `json:"tcp"`
OvpnX509 string `json:"x509,omitempty"` UDP bool `json:"udp"`
RetroLoc string `json:"retroloc,omitempty"` // TODO remove in v4 IPs []net.IP `json:"ips"`
MultiHop bool `json:"multihop,omitempty"` }
WgPubKey string `json:"wgpubkey,omitempty"`
Free bool `json:"free,omitempty"` type FastestvpnServer struct {
Stream bool `json:"stream,omitempty"` Hostname string `json:"hostname"`
PortForward bool `json:"port_forward,omitempty"` TCP bool `json:"tcp"`
IPs []net.IP `json:"ips,omitempty"` UDP bool `json:"udp"`
Country string `json:"country"`
IPs []net.IP `json:"ips"`
}
type HideMyAssServer struct {
Country string `json:"country"`
Region string `json:"region"`
City string `json:"city"`
Hostname string `json:"hostname"`
TCP bool `json:"tcp"`
UDP bool `json:"udp"`
IPs []net.IP `json:"ips"`
}
type IpvanishServer struct {
Country string `json:"country"`
City string `json:"city"`
Hostname string `json:"hostname"`
TCP bool `json:"tcp"`
UDP bool `json:"udp"`
IPs []net.IP `json:"ips"`
}
type IvpnServer struct {
VPN string `json:"vpn"`
Country string `json:"country"`
City string `json:"city"`
ISP string `json:"isp"`
Hostname string `json:"hostname"`
WgPubKey string `json:"wgpubkey,omitempty"`
TCP bool `json:"tcp"`
UDP bool `json:"udp"`
IPs []net.IP `json:"ips"`
}
type MullvadServer struct {
VPN string `json:"vpn"`
IPs []net.IP `json:"ips"`
IPsV6 []net.IP `json:"ipsv6"`
Country string `json:"country"`
City string `json:"city"`
Hostname string `json:"hostname"`
ISP string `json:"isp"`
Owned bool `json:"owned"`
WgPubKey string `json:"wgpubkey,omitempty"`
}
type NordvpnServer struct { //nolint:maligned
Region string `json:"region"`
Hostname string `json:"hostname"`
Number uint16 `json:"number"`
IP net.IP `json:"ip"`
TCP bool `json:"tcp"`
UDP bool `json:"udp"`
}
type PerfectprivacyServer struct {
City string `json:"city"` // primary key
IPs []net.IP `json:"ips"`
TCP bool `json:"tcp"`
UDP bool `json:"udp"`
}
type PrivadoServer struct {
Country string `json:"country"`
Region string `json:"region"`
City string `json:"city"`
Hostname string `json:"hostname"`
IP net.IP `json:"ip"`
}
type PIAServer struct {
Region string `json:"region"`
Hostname string `json:"hostname"`
ServerName string `json:"server_name"`
TCP bool `json:"tcp"`
UDP bool `json:"udp"`
PortForward bool `json:"port_forward"`
IPs []net.IP `json:"ips"`
}
type PrivatevpnServer struct {
Country string `json:"country"`
City string `json:"city"`
Hostname string `json:"hostname"`
IPs []net.IP `json:"ip"`
}
type ProtonvpnServer struct {
Country string `json:"country"`
Region string `json:"region"`
City string `json:"city"`
Name string `json:"name"`
Hostname string `json:"hostname"`
EntryIP net.IP `json:"entry_ip"`
ExitIPs []net.IP `json:"exit_ip"` // TODO verify it matches with public IP once connected
}
type PurevpnServer struct {
Country string `json:"country"`
Region string `json:"region"`
City string `json:"city"`
Hostname string `json:"hostname"`
TCP bool `json:"tcp"`
UDP bool `json:"udp"`
IPs []net.IP `json:"ips"`
}
type SurfsharkServer struct {
Region string `json:"region"`
Country string `json:"country"` // Country is also used for multi-hop
City string `json:"city"`
RetroLoc string `json:"retroloc"` // TODO remove in v4
Hostname string `json:"hostname"`
MultiHop bool `json:"multihop"`
TCP bool `json:"tcp"`
UDP bool `json:"udp"`
IPs []net.IP `json:"ips"`
}
type TorguardServer struct {
Country string `json:"country"`
City string `json:"city"`
Hostname string `json:"hostname"`
TCP bool `json:"tcp"`
UDP bool `json:"udp"`
IPs []net.IP `json:"ips"`
}
type VPNUnlimitedServer struct {
Country string `json:"country"`
City string `json:"city"`
Hostname string `json:"hostname"`
Free bool `json:"free"`
Stream bool `json:"stream"`
TCP bool `json:"tcp"`
UDP bool `json:"udp"`
IPs []net.IP `json:"ips"`
}
type VyprvpnServer struct {
Region string `json:"region"`
Hostname string `json:"hostname"`
TCP bool `json:"tcp"`
UDP bool `json:"udp"` // only support for UDP
IPs []net.IP `json:"ips"`
}
type WevpnServer struct {
City string `json:"city"`
Hostname string `json:"hostname"`
TCP bool `json:"tcp"`
UDP bool `json:"udp"`
IPs []net.IP `json:"ips"`
}
type WindscribeServer struct {
VPN string `json:"vpn"`
Region string `json:"region"`
City string `json:"city"`
Hostname string `json:"hostname"`
OvpnX509 string `json:"x509,omitempty"`
WgPubKey string `json:"wgpubkey,omitempty"`
IPs []net.IP `json:"ips"`
} }

View File

@@ -2,26 +2,26 @@ package models
type AllServers struct { type AllServers struct {
Version uint16 `json:"version"` // used for migration of the top level scheme Version uint16 `json:"version"` // used for migration of the top level scheme
Cyberghost Servers `json:"cyberghost"` Cyberghost CyberghostServers `json:"cyberghost"`
Expressvpn Servers `json:"expressvpn"` Expressvpn ExpressvpnServers `json:"expressvpn"`
Fastestvpn Servers `json:"fastestvpn"` Fastestvpn FastestvpnServers `json:"fastestvpn"`
HideMyAss Servers `json:"hidemyass"` HideMyAss HideMyAssServers `json:"hidemyass"`
Ipvanish Servers `json:"ipvanish"` Ipvanish IpvanishServers `json:"ipvanish"`
Ivpn Servers `json:"ivpn"` Ivpn IvpnServers `json:"ivpn"`
Mullvad Servers `json:"mullvad"` Mullvad MullvadServers `json:"mullvad"`
Perfectprivacy Servers `json:"perfectprivacy"` Perfectprivacy PerfectprivacyServers `json:"perfectprivacy"`
Nordvpn Servers `json:"nordvpn"` Nordvpn NordvpnServers `json:"nordvpn"`
Privado Servers `json:"privado"` Privado PrivadoServers `json:"privado"`
Pia Servers `json:"pia"` Pia PiaServers `json:"pia"`
Privatevpn Servers `json:"privatevpn"` Privatevpn PrivatevpnServers `json:"privatevpn"`
Protonvpn Servers `json:"protonvpn"` Protonvpn ProtonvpnServers `json:"protonvpn"`
Purevpn Servers `json:"purevpn"` Purevpn PurevpnServers `json:"purevpn"`
Surfshark Servers `json:"surfshark"` Surfshark SurfsharkServers `json:"surfshark"`
Torguard Servers `json:"torguard"` Torguard TorguardServers `json:"torguard"`
VPNUnlimited Servers `json:"vpnunlimited"` VPNUnlimited VPNUnlimitedServers `json:"vpnunlimited"`
Vyprvpn Servers `json:"vyprvpn"` Vyprvpn VyprvpnServers `json:"vyprvpn"`
Wevpn Servers `json:"wevpn"` Wevpn WevpnServers `json:"wevpn"`
Windscribe Servers `json:"windscribe"` Windscribe WindscribeServers `json:"windscribe"`
} }
func (a *AllServers) Count() int { func (a *AllServers) Count() int {
@@ -47,8 +47,103 @@ func (a *AllServers) Count() int {
len(a.Windscribe.Servers) len(a.Windscribe.Servers)
} }
type Servers struct { type CyberghostServers struct {
Version uint16 `json:"version"` Version uint16 `json:"version"`
Timestamp int64 `json:"timestamp"` Timestamp int64 `json:"timestamp"`
Servers []Server `json:"servers"` Servers []CyberghostServer `json:"servers"`
}
type ExpressvpnServers struct {
Version uint16 `json:"version"`
Timestamp int64 `json:"timestamp"`
Servers []ExpressvpnServer `json:"servers"`
}
type FastestvpnServers struct {
Version uint16 `json:"version"`
Timestamp int64 `json:"timestamp"`
Servers []FastestvpnServer `json:"servers"`
}
type HideMyAssServers struct {
Version uint16 `json:"version"`
Timestamp int64 `json:"timestamp"`
Servers []HideMyAssServer `json:"servers"`
}
type IpvanishServers struct {
Version uint16 `json:"version"`
Timestamp int64 `json:"timestamp"`
Servers []IpvanishServer `json:"servers"`
}
type IvpnServers struct {
Version uint16 `json:"version"`
Timestamp int64 `json:"timestamp"`
Servers []IvpnServer `json:"servers"`
}
type MullvadServers struct {
Version uint16 `json:"version"`
Timestamp int64 `json:"timestamp"`
Servers []MullvadServer `json:"servers"`
}
type NordvpnServers struct {
Version uint16 `json:"version"`
Timestamp int64 `json:"timestamp"`
Servers []NordvpnServer `json:"servers"`
}
type PerfectprivacyServers struct {
Version uint16 `json:"version"`
Timestamp int64 `json:"timestamp"`
Servers []PerfectprivacyServer `json:"servers"`
}
type PrivadoServers struct {
Version uint16 `json:"version"`
Timestamp int64 `json:"timestamp"`
Servers []PrivadoServer `json:"servers"`
}
type PiaServers struct {
Version uint16 `json:"version"`
Timestamp int64 `json:"timestamp"`
Servers []PIAServer `json:"servers"`
}
type PrivatevpnServers struct {
Version uint16 `json:"version"`
Timestamp int64 `json:"timestamp"`
Servers []PrivatevpnServer `json:"servers"`
}
type ProtonvpnServers struct {
Version uint16 `json:"version"`
Timestamp int64 `json:"timestamp"`
Servers []ProtonvpnServer `json:"servers"`
}
type PurevpnServers struct {
Version uint16 `json:"version"`
Timestamp int64 `json:"timestamp"`
Servers []PurevpnServer `json:"servers"`
}
type SurfsharkServers struct {
Version uint16 `json:"version"`
Timestamp int64 `json:"timestamp"`
Servers []SurfsharkServer `json:"servers"`
}
type TorguardServers struct {
Version uint16 `json:"version"`
Timestamp int64 `json:"timestamp"`
Servers []TorguardServer `json:"servers"`
}
type VPNUnlimitedServers struct {
Version uint16 `json:"version"`
Timestamp int64 `json:"timestamp"`
Servers []VPNUnlimitedServer `json:"servers"`
}
type VyprvpnServers struct {
Version uint16 `json:"version"`
Timestamp int64 `json:"timestamp"`
Servers []VyprvpnServer `json:"servers"`
}
type WevpnServers struct {
Version uint16 `json:"version"`
Timestamp int64 `json:"timestamp"`
Servers []WevpnServer `json:"servers"`
}
type WindscribeServers struct {
Version uint16 `json:"version"`
Timestamp int64 `json:"timestamp"`
Servers []WindscribeServer `json:"servers"`
} }

View File

@@ -52,14 +52,10 @@ func (l *Loop) Run(ctx context.Context, done chan<- struct{}) {
} }
stayHere := true stayHere := true
stopped := false
for stayHere { for stayHere {
select { select {
case <-ctx.Done(): case <-ctx.Done():
pfCancel() pfCancel()
if stopped {
return
}
<-errorCh <-errorCh
close(errorCh) close(errorCh)
close(portCh) close(portCh)
@@ -81,7 +77,6 @@ func (l *Loop) Run(ctx context.Context, done chan<- struct{}) {
l.firewallBlockPort(ctx) l.firewallBlockPort(ctx)
l.state.SetPortForwarded(0) l.state.SetPortForwarded(0)
l.stopped <- struct{}{} l.stopped <- struct{}{}
stopped = true
case port := <-portCh: case port := <-portCh:
l.logger.Info("port forwarded is " + strconv.Itoa(int(port))) l.logger.Info("port forwarded is " + strconv.Itoa(int(port)))
l.firewallBlockPort(ctx) l.firewallBlockPort(ctx)

View File

@@ -8,6 +8,7 @@ import (
) )
func boolPtr(b bool) *bool { return &b } func boolPtr(b bool) *bool { return &b }
func stringPtr(s string) *string { return &s }
func durationPtr(d time.Duration) *time.Duration { return &d } func durationPtr(d time.Duration) *time.Duration { return &d }
var _ gomock.Matcher = (*regexMatcher)(nil) var _ gomock.Matcher = (*regexMatcher)(nil)

View File

@@ -26,6 +26,9 @@ func New(settings Settings) (server *httpserver.Server, err error) {
handler.Handle("/debug/pprof/goroutine", pprof.Handler("goroutine")) handler.Handle("/debug/pprof/goroutine", pprof.Handler("goroutine"))
handler.Handle("/debug/pprof/heap", pprof.Handler("heap")) handler.Handle("/debug/pprof/heap", pprof.Handler("heap"))
handler.Handle("/debug/pprof/threadcreate", pprof.Handler("threadcreate")) handler.Handle("/debug/pprof/threadcreate", pprof.Handler("threadcreate"))
httpServerName := "pprof"
settings.HTTPServer.Name = &httpServerName
settings.HTTPServer.Handler = handler settings.HTTPServer.Handler = handler
settings.SetDefaults() settings.SetDefaults()

View File

@@ -22,7 +22,8 @@ func Test_Server(t *testing.T) {
const address = "127.0.0.1:0" const address = "127.0.0.1:0"
logger := NewMockLogger(ctrl) logger := NewMockLogger(ctrl)
logger.EXPECT().Info(newRegexMatcher("^http server listening on 127.0.0.1:[1-9][0-9]{0,4}$")) logger.EXPECT().Info(newRegexMatcher("^pprof http server listening on 127.0.0.1:[1-9][0-9]{0,4}$"))
logger.EXPECT().Warn("pprof http server shutting down: context canceled")
const httpServerShutdownTimeout = 10 * time.Second // 10s in case test worker is slow const httpServerShutdownTimeout = 10 * time.Second // 10s in case test worker is slow
settings := Settings{ settings := Settings{

View File

@@ -26,6 +26,7 @@ type Settings struct {
func (s *Settings) SetDefaults() { func (s *Settings) SetDefaults() {
s.Enabled = helpers.DefaultBool(s.Enabled, false) s.Enabled = helpers.DefaultBool(s.Enabled, false)
s.HTTPServer.Name = helpers.DefaultStringPtr(s.HTTPServer.Name, "pprof")
s.HTTPServer.Address = helpers.DefaultString(s.HTTPServer.Address, "localhost:6060") s.HTTPServer.Address = helpers.DefaultString(s.HTTPServer.Address, "localhost:6060")
s.HTTPServer.SetDefaults() s.HTTPServer.SetDefaults()
} }

Some files were not shown because too many files have changed in this diff Show More