Compare commits

..

3 Commits

Author SHA1 Message Date
Quentin McGaw
c04cd621b0 fix(docker): openvpn 2.4.12-r0 install 2022-03-31 20:53:28 +00:00
Quentin McGaw
69c3f4e872 fix(env): OPENVPN_FLAGS functionality 2022-03-31 20:47:39 +00:00
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
277 changed files with 65541 additions and 84087 deletions

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -124,7 +124,7 @@ ENV VPN_SERVICE_PROVIDER=pia \
LOG_LEVEL=info \
# Health
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_ADDITION=5s \
# DNS over TLS

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
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)
@@ -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
cap_add:
- NET_ADMIN
devices:
- /dev/net/tun:/dev/net/tun
ports:
- 8888:8888/tcp # HTTP proxy
- 8388:8388/tcp # Shadowsocks

View File

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

5
go.mod
View File

@@ -3,7 +3,7 @@ module github.com/qdm12/gluetun
go 1.17
require (
github.com/breml/rootcerts v0.2.3
github.com/breml/rootcerts v0.2.2
github.com/fatih/color v1.13.0
github.com/golang/mock v1.6.0
github.com/qdm12/dns v1.11.0
@@ -12,10 +12,9 @@ require (
github.com/qdm12/gosplash v0.1.0
github.com/qdm12/gotree v0.2.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/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
golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c
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/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/breml/rootcerts v0.2.3 h1:1vkYjKOiHVSyuz9Ue4AOrViEvUm8gk8phTg0vbcuU0A=
github.com/breml/rootcerts v0.2.3/go.mod h1:24FDtzYMpqIeYC7QzaE8VPRQaFZU5TIUDlyk8qwjD88=
github.com/breml/rootcerts v0.2.2 h1:hkHEpbTdYaNvDoYeq+mwRvCeg/YTTl23DjQ1Tnj71Zs=
github.com/breml/rootcerts v0.2.2/go.mod h1:24FDtzYMpqIeYC7QzaE8VPRQaFZU5TIUDlyk8qwjD88=
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.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/govalid v0.1.0 h1:UIFVmuaAg0Q+h0GeyfcFEZ5sQ5KJPvRQwycC1/cqDN8=
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/go.mod h1:AY0p4huvPUPW+/CiWsJcDgT6sneDryk26VXSccPNCxY=
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.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
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.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/go.mod h1:twkDnbuQxJYemMlGd4JFIcuhgX83tXhKS2B/PRMpOho=
github.com/vishvananda/netns v0.0.0-20200728191858-db3c7e526aae h1:4hwBBUfQCFe3Cym0ZtKyq7L16eZUtYKs+BaHDN6mAns=

View File

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

View File

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

View File

@@ -65,7 +65,7 @@ func (h *Health) OverrideWith(other Health) {
func (h *Health) SetDefaults() {
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()
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -2,12 +2,10 @@ package settings
import (
"fmt"
"regexp"
"strings"
"github.com/qdm12/gluetun/internal/configuration/settings/helpers"
"github.com/qdm12/gluetun/internal/constants"
"github.com/qdm12/gluetun/internal/constants/providers"
"github.com/qdm12/gluetun/internal/openvpn/parse"
"github.com/qdm12/gotree"
)
@@ -75,8 +73,6 @@ type OpenVPN struct {
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) {
// Validate version
validVersions := []string{constants.Openvpn24, constants.Openvpn25}
@@ -85,16 +81,13 @@ func (o OpenVPN) validate(vpnProvider string) (err error) {
ErrOpenVPNVersionIsNotValid, o.Version, strings.Join(validVersions, ", "))
}
isCustom := vpnProvider == providers.Custom
isCustom := vpnProvider == constants.Custom
if !isCustom && o.User == "" {
return ErrOpenVPNUserIsEmpty
}
passwordRequired := !isCustom &&
(vpnProvider != providers.Ivpn || !ivpnAccountID.MatchString(o.User))
if passwordRequired && o.Password == "" {
if !isCustom && o.Password == "" {
return ErrOpenVPNPasswordIsEmpty
}
@@ -154,8 +147,8 @@ func validateOpenVPNClientCertificate(vpnProvider,
clientCert string) (err error) {
switch vpnProvider {
case
providers.Cyberghost,
providers.VPNUnlimited:
constants.Cyberghost,
constants.VPNUnlimited:
if clientCert == "" {
return ErrMissingValue
}
@@ -175,9 +168,9 @@ func validateOpenVPNClientCertificate(vpnProvider,
func validateOpenVPNClientKey(vpnProvider, clientKey string) (err error) {
switch vpnProvider {
case
providers.Cyberghost,
providers.VPNUnlimited,
providers.Wevpn:
constants.Cyberghost,
constants.VPNUnlimited,
constants.Wevpn:
if clientKey == "" {
return ErrMissingValue
}
@@ -257,7 +250,7 @@ func (o *OpenVPN) overrideWith(other OpenVPN) {
func (o *OpenVPN) setDefaults(vpnProvider string) {
o.Version = helpers.DefaultString(o.Version, constants.Openvpn25)
if vpnProvider == providers.Mullvad {
if vpnProvider == constants.Mullvad {
o.Password = "m"
}
@@ -267,7 +260,7 @@ func (o *OpenVPN) setDefaults(vpnProvider string) {
o.ClientKey = helpers.DefaultStringPtr(o.ClientKey, "")
var defaultEncPreset string
if vpnProvider == providers.PrivateInternetAccess {
if vpnProvider == constants.PrivateInternetAccess {
defaultEncPreset = constants.PIAEncryptionPresetStrong
}
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/constants"
"github.com/qdm12/gluetun/internal/constants/providers"
"github.com/qdm12/gotree"
)
@@ -40,11 +39,11 @@ func (o OpenVPNSelection) validate(vpnProvider string) (err error) {
// Validate TCP
if *o.TCP && helpers.IsOneOf(vpnProvider,
providers.Ipvanish,
providers.Perfectprivacy,
providers.Privado,
providers.VPNUnlimited,
providers.Vyprvpn,
constants.Ipvanish,
constants.Perfectprivacy,
constants.Privado,
constants.VPNUnlimited,
constants.Vyprvpn,
) {
return fmt.Errorf("%w: for VPN service provider %s",
ErrOpenVPNTCPNotSupported, vpnProvider)
@@ -54,39 +53,33 @@ func (o OpenVPNSelection) validate(vpnProvider string) (err error) {
if *o.CustomPort != 0 {
switch vpnProvider {
// no restriction on port
case providers.Cyberghost, providers.HideMyAss,
providers.PrivateInternetAccess, providers.Privatevpn,
providers.Protonvpn, providers.Torguard:
case constants.Cyberghost, constants.HideMyAss,
constants.PrivateInternetAccess, constants.Privatevpn,
constants.Protonvpn, constants.Torguard:
// no custom port allowed
case providers.Expressvpn, providers.Fastestvpn,
providers.Ipvanish, providers.Nordvpn,
providers.Privado, providers.Purevpn,
providers.Surfshark, providers.VPNUnlimited,
providers.Vyprvpn:
case constants.Expressvpn, constants.Fastestvpn,
constants.Ipvanish, constants.Nordvpn,
constants.Privado, constants.Purevpn,
constants.Surfshark, constants.VPNUnlimited,
constants.Vyprvpn:
return fmt.Errorf("%w: for VPN service provider %s",
ErrOpenVPNCustomPortNotAllowed, vpnProvider)
default:
var allowedTCP, allowedUDP []uint16
switch vpnProvider {
case providers.Ivpn:
case constants.Ivpn:
allowedTCP = []uint16{80, 443, 1143}
allowedUDP = []uint16{53, 1194, 2049, 2050}
case providers.Mullvad:
case constants.Mullvad:
allowedTCP = []uint16{80, 443, 1401}
allowedUDP = []uint16{53, 1194, 1195, 1196, 1197, 1300, 1301, 1302, 1303, 1400}
case providers.Perfectprivacy:
case constants.Perfectprivacy:
allowedTCP = []uint16{44, 443, 4433}
allowedUDP = []uint16{44, 443, 4433}
case providers.PrivateInternetAccess:
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:
case constants.Wevpn:
allowedTCP = []uint16{53, 1195, 1199, 2018}
allowedUDP = []uint16{80, 1194, 1198}
case providers.Windscribe:
case constants.Windscribe:
allowedTCP = []uint16{21, 22, 80, 123, 143, 443, 587, 1194, 3306, 8080, 54783}
allowedUDP = []uint16{53, 80, 123, 443, 1194, 54783}
}
@@ -104,7 +97,7 @@ func (o OpenVPNSelection) validate(vpnProvider string) (err error) {
}
// Validate EncPreset
if vpnProvider == providers.PrivateInternetAccess {
if vpnProvider == constants.PrivateInternetAccess {
validEncryptionPresets := []string{
constants.PIAEncryptionPresetNone,
constants.PIAEncryptionPresetNormal,
@@ -149,7 +142,7 @@ func (o *OpenVPNSelection) setDefaults(vpnProvider string) {
o.CustomPort = helpers.DefaultUint16(o.CustomPort, 0)
var defaultEncPreset string
if vpnProvider == providers.PrivateInternetAccess {
if vpnProvider == constants.PrivateInternetAccess {
defaultEncPreset = constants.PIAEncryptionPresetStrong
}
o.PIAEncPreset = helpers.DefaultStringPtr(o.PIAEncPreset, defaultEncPreset)

View File

@@ -6,7 +6,7 @@ import (
"strings"
"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"
)
@@ -28,7 +28,7 @@ func (p PortForwarding) validate(vpnProvider string) (err error) {
}
// Validate Enabled
validProviders := []string{providers.PrivateInternetAccess}
validProviders := []string{constants.PrivateInternetAccess}
if !helpers.IsOneOf(vpnProvider, validProviders...) {
return fmt.Errorf("%w: for provider %s, it is only available for %s",
ErrPortForwardingEnabled, vpnProvider, strings.Join(validProviders, ", "))

View File

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

View File

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

View File

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

View File

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

View File

@@ -7,7 +7,7 @@ import (
"time"
"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"
)
@@ -42,8 +42,8 @@ func (u Updater) Validate() (err error) {
for i, provider := range u.Providers {
valid := false
for _, validProvider := range providers.All() {
if validProvider == providers.Custom {
for _, validProvider := range constants.AllProviders() {
if validProvider == constants.Custom {
continue
}
@@ -93,7 +93,7 @@ func (u *Updater) SetDefaults(vpnProvider string) {
u.Period = helpers.DefaultDuration(u.Period, 0)
u.DNSAddress = helpers.DefaultIP(u.DNSAddress, net.IPv4(1, 1, 1, 1))
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}
}
}

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
import (
"sort"
"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.
func SurfsharkRetroLocChoices() (choices []string) {
locationData := constants.SurfsharkLocationData()
@@ -14,8 +49,12 @@ func SurfsharkRetroLocChoices() (choices []string) {
continue
}
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
}

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

View File

@@ -6,7 +6,7 @@ import (
"regexp"
"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"
"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.
func (w Wireguard) validate(vpnProvider string) (err error) {
if !helpers.IsOneOf(vpnProvider,
providers.Custom,
providers.Ivpn,
providers.Mullvad,
providers.Windscribe,
constants.Custom,
constants.Ivpn,
constants.Mullvad,
constants.Windscribe,
) {
// do not validate for VPN provider not supporting Wireguard
return nil

View File

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

View File

@@ -19,7 +19,7 @@ func (r *Reader) ReadHealth() (health settings.Health, err error) {
return health, err
}
health.VPN.Addition, err = r.readDurationWithRetro(
health.VPN.Initial, err = r.readDurationWithRetro(
"HEALTH_VPN_DURATION_ADDITION",
"HEALTH_OPENVPN_DURATION_ADDITION")
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 uint32Ptr(n uint32) *uint32 { return &n }
func uint16Ptr(n uint16) *uint16 { return &n }
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)
}
func TestXxx(t *testing.T) {
t.Log(int(^uint32(0)))
}

View File

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

View File

@@ -6,8 +6,7 @@ import (
"strings"
"github.com/qdm12/gluetun/internal/configuration/settings"
"github.com/qdm12/gluetun/internal/constants/providers"
"github.com/qdm12/gluetun/internal/constants/vpn"
"github.com/qdm12/gluetun/internal/constants"
)
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 = strings.ToLower(s)
switch {
case vpnType != vpn.Wireguard &&
case vpnType != constants.Wireguard &&
os.Getenv("OPENVPN_CUSTOM_CONFIG") != "": // retro compatibility
return stringPtr(providers.Custom)
return stringPtr(constants.Custom)
case s == "":
return nil
case s == "pia": // retro compatibility
return stringPtr(providers.PrivateInternetAccess)
return stringPtr(constants.PrivateInternetAccess)
}
return stringPtr(s)
}

View File

@@ -9,7 +9,7 @@ import (
"strings"
"github.com/qdm12/gluetun/internal/configuration/settings"
"github.com/qdm12/gluetun/internal/constants/providers"
"github.com/qdm12/gluetun/internal/constants"
)
var (
@@ -27,7 +27,7 @@ func (r *Reader) readServerSelection(vpnProvider, vpnType string) (
countriesKey, _ := r.getEnvWithRetro("SERVER_COUNTRIES", "COUNTRY")
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
ss.Countries = envToCSV("REGION")
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")
func (r *Reader) readID(key, retroKey string) (
id *uint32, err error) {
id *uint16, err error) {
idEnvKey, idString := r.getEnvWithRetro(key, retroKey)
if idString == "" {
return nil, nil //nolint:nilnil
}
const base = 10
const bitSize = 64
const max = uint64(^uint32(0))
idUint64, err := strconv.ParseUint(idString, base, bitSize)
idInt, err := strconv.Atoi(idString)
if err != nil {
return nil, fmt.Errorf("environment variable %s: %w: %s",
idEnvKey, ErrSystemIDNotValid, err)
} else if idUint64 > max {
return nil, fmt.Errorf("environment variable %s: %w: %d: must be between 0 and %d",
idEnvKey, ErrSystemIDNotValid, idUint64, max)
return nil, fmt.Errorf("environment variable %s: %w: %s: %s",
idEnvKey, ErrSystemIDNotValid, idString, err)
} else if idInt < 0 || idInt > 65535 {
return nil, fmt.Errorf("environment variable %s: %w: %d: must be between 0 and 65535",
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
retroKeyPrefix string
retroValue string
id *uint32
id *uint16
errWrapped error
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": {
keyPrefix: "ID",
keyValue: "1000",
retroKeyPrefix: "RETRO_ID",
id: uint32Ptr(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`,
id: uint16Ptr(1000),
},
}

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,9 +96,13 @@ func (c *Config) enable(ctx context.Context) (err error) {
if err = c.acceptEstablishedRelatedTraffic(ctx, remove); err != nil {
return err
}
if err = c.allowVPNIP(ctx); err != nil {
return err
if c.vpnConnection.IP != nil {
if err = c.acceptOutputTrafficToVPN(ctx, c.defaultInterface, c.vpnConnection, remove); err != nil {
return err
}
if err = c.acceptOutputThroughInterface(ctx, c.vpnIntf, remove); err != nil {
return err
}
}
for _, network := range c.localNetworks {
@@ -107,8 +111,10 @@ func (c *Config) enable(ctx context.Context) (err error) {
}
}
if err = c.allowOutboundSubnets(ctx); err != nil {
return err
for _, subnet := range c.outboundSubnets {
if err := c.acceptOutputFromIPToSubnet(ctx, c.defaultInterface, c.localIP, subnet, remove); err != nil {
return err
}
}
// Allows packets from any IP address to go through eth0 / local network
@@ -119,8 +125,10 @@ func (c *Config) enable(ctx context.Context) (err error) {
}
}
if err = c.allowInputPorts(ctx); err != nil {
return err
for port, intf := range c.allowedInputPorts {
if err := c.acceptInputToPort(ctx, intf, port, remove); err != nil {
return err
}
}
if err := c.runUserPostRules(ctx, c.customRulesPath, remove); err != nil {
@@ -129,47 +137,3 @@ func (c *Config) enable(ctx context.Context) (err error) {
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

@@ -23,16 +23,17 @@ type Configurator interface {
}
type Config struct { //nolint:maligned
runner command.Runner
logger Logger
iptablesMutex sync.Mutex
ip6tablesMutex sync.Mutex
defaultRoutes []routing.DefaultRoute
localNetworks []routing.LocalNetwork
runner command.Runner
logger Logger
iptablesMutex sync.Mutex
ip6tablesMutex sync.Mutex
defaultInterface string
defaultGateway net.IP
localNetworks []routing.LocalNetwork
localIP net.IP
// Fixed state
ipTables string
ip6Tables string
ip6Tables bool
customRulesPath string
// State
@@ -40,34 +41,24 @@ type Config struct { //nolint:maligned
vpnConnection models.Connection
vpnIntf string
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
}
// NewConfig creates a new Config instance and returns an error
// if no iptables implementation is available.
func NewConfig(ctx context.Context, logger Logger,
runner command.Runner, defaultRoutes []routing.DefaultRoute,
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
}
// NewConfig creates a new Config instance.
func NewConfig(logger Logger, runner command.Runner,
defaultInterface string, defaultGateway net.IP,
localNetworks []routing.LocalNetwork, localIP net.IP) *Config {
return &Config{
runner: runner,
logger: logger,
allowedInputPorts: make(map[uint16]map[string]struct{}),
ipTables: iptables,
ip6Tables: ip6tables,
allowedInputPorts: make(map[uint16]string),
ip6Tables: ip6tablesSupported(context.Background(), runner),
customRulesPath: "/iptables/post-rules.txt",
// Obtained from routing
defaultRoutes: defaultRoutes,
localNetworks: localNetworks,
}, nil
defaultInterface: defaultInterface,
defaultGateway: defaultGateway,
localNetworks: localNetworks,
localIP: localIP,
}
}

View File

@@ -10,18 +10,16 @@ import (
"github.com/qdm12/golibs/command"
)
// findIP6tablesSupported checks for multiple iptables implementations
// and returns the iptables path that is supported. If none work, an
// empty string path is returned.
func findIP6tablesSupported(ctx context.Context, runner command.Runner) (
ip6tablesPath string, err error) {
ip6tablesPath, err = checkIptablesSupport(ctx, runner, "ip6tables", "ip6tables-nft")
if errors.Is(err, ErrIPTablesNotSupported) {
return "", nil
} else if err != nil {
return "", err
var (
ErrIP6NotSupported = errors.New("ip6tables not supported")
)
func ip6tablesSupported(ctx context.Context, runner command.Runner) (supported bool) {
cmd := exec.CommandContext(ctx, "ip6tables", "-L")
if _, err := runner.Run(cmd); err != nil {
return false
}
return ip6tablesPath, nil
return true
}
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 {
if c.ip6Tables == "" {
if !c.ip6Tables {
return nil
}
c.ip6tablesMutex.Lock() // only one ip6tables command at once
defer c.ip6tablesMutex.Unlock()
c.logger.Debug(c.ip6Tables + " " + instruction)
c.logger.Debug("ip6tables " + 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 {
return fmt.Errorf("command failed: \"%s %s\": %s: %w",
c.ip6Tables, instruction, output, err)
return fmt.Errorf("command failed: \"ip6tables %s\": %s: %w", instruction, output, err)
}
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
defer c.iptablesMutex.Unlock()
c.logger.Debug(c.ipTables + " " + instruction)
c.logger.Debug("iptables " + 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 {
return fmt.Errorf("command failed: \"%s %s\": %s: %w",
c.ipTables, instruction, output, err)
return fmt.Errorf("command failed: \"iptables %s\": %s: %w", instruction, output, err)
}
return nil
}
@@ -125,7 +124,7 @@ func (c *Config) acceptInputToSubnet(ctx context.Context, intf string, destinati
if isIP4Subnet {
return c.runIptablesInstruction(ctx, instruction)
}
if c.ip6Tables == "" {
if !c.ip6Tables {
return fmt.Errorf("accept input to subnet %s: %w", destination, ErrNeedIP6Tables)
}
return c.runIP6tablesInstruction(ctx, instruction)
@@ -152,7 +151,7 @@ func (c *Config) acceptOutputTrafficToVPN(ctx context.Context,
isIPv4 := connection.IP.To4() != nil
if isIPv4 {
return c.runIptablesInstruction(ctx, instruction)
} else if c.ip6Tables == "" {
} else if !c.ip6Tables {
return fmt.Errorf("accept output to VPN server: %w", ErrNeedIP6Tables)
}
return c.runIP6tablesInstruction(ctx, instruction)
@@ -173,7 +172,7 @@ func (c *Config) acceptOutputFromIPToSubnet(ctx context.Context,
if doIPv4 {
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 c.runIP6tablesInstruction(ctx, instruction)
@@ -224,15 +223,9 @@ func (c *Config) runUserPostRules(ctx context.Context, filepath string, remove b
case strings.HasPrefix(line, "iptables "):
ipv4 = true
rule = strings.TrimPrefix(line, "iptables ")
case strings.HasPrefix(line, "iptables-nft "):
ipv4 = true
rule = strings.TrimPrefix(line, "iptables-nft ")
case strings.HasPrefix(line, "ip6tables "):
ipv4 = false
rule = strings.TrimPrefix(line, "ip6tables ")
case strings.HasPrefix(line, "ip6tables-nft "):
ipv4 = false
rule = strings.TrimPrefix(line, "ip6tables-nft ")
default:
continue
}
@@ -244,7 +237,7 @@ func (c *Config) runUserPostRules(ctx context.Context, filepath string, remove b
switch {
case ipv4:
err = c.runIptablesInstruction(ctx, rule)
case c.ip6Tables == "":
case !c.ip6Tables:
err = fmt.Errorf("cannot run user ip6tables rule: %w", ErrNeedIP6Tables)
default: // ipv6
err = c.runIP6tablesInstruction(ctx, rule)

View File

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

View File

@@ -21,30 +21,27 @@ func (c *Config) SetAllowedPort(ctx context.Context, port uint16, intf string) (
if !c.enabled {
c.logger.Info("firewall disabled, only updating allowed ports internal state")
existingInterfaces, ok := c.allowedInputPorts[port]
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 {
c.allowedInputPorts[port] = intf
return nil
}
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
if err := c.acceptInputToPort(ctx, intf, port, remove); err != nil {
return fmt.Errorf("cannot allow input to port %d through interface %s: %w",
port, intf, err)
return fmt.Errorf("cannot allow input to port %d: %w", port, err)
}
netInterfaces[intf] = struct{}{}
c.allowedInputPorts[port] = intf
return nil
}
@@ -63,24 +60,17 @@ func (c *Config) RemoveAllowedPort(ctx context.Context, port uint16) (err error)
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 {
return nil
}
const remove = true
for netInterface := range interfacesSet {
err := c.acceptInputToPort(ctx, netInterface, port, remove)
if err != nil {
return fmt.Errorf("cannot remove allowed port %d on interface %s: %w",
port, netInterface, err)
}
delete(interfacesSet, netInterface)
if err := c.acceptInputToPort(ctx, intf, port, remove); err != nil {
return fmt.Errorf("cannot remove allowed port %d: %w", port, err)
}
// All interfaces were removed successfully, so remove the port entry.
delete(c.allowedInputPorts, port)
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,10 +31,8 @@ func (c *Config) SetVPNConnection(ctx context.Context,
remove := true
if c.vpnConnection.IP != nil {
for _, defaultRoute := range c.defaultRoutes {
if err := c.acceptOutputTrafficToVPN(ctx, defaultRoute.NetInterface, c.vpnConnection, remove); err != nil {
c.logger.Error("cannot remove outdated VPN connection rule: " + err.Error())
}
if err := c.acceptOutputTrafficToVPN(ctx, c.defaultInterface, c.vpnConnection, remove); err != nil {
c.logger.Error("cannot remove outdated VPN connection rule: " + err.Error())
}
}
c.vpnConnection = models.Connection{}
@@ -48,10 +46,8 @@ func (c *Config) SetVPNConnection(ctx context.Context,
remove = false
for _, defaultRoute := range c.defaultRoutes {
if err := c.acceptOutputTrafficToVPN(ctx, defaultRoute.NetInterface, connection, remove); err != nil {
return fmt.Errorf("cannot allow output traffic through VPN connection: %w", err)
}
if err := c.acceptOutputTrafficToVPN(ctx, c.defaultInterface, connection, remove); err != nil {
return fmt.Errorf("cannot allow output traffic through VPN connection: %w", err)
}
c.vpnConnection = connection

View File

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

View File

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

View File

@@ -23,11 +23,12 @@ func (s *Server) Run(ctx context.Context, ready chan<- struct{}, done chan<- str
return
}
s.logger.Warn(s.name + " http server shutting down: " + ctx.Err().Error())
shutdownCtx, cancel := context.WithTimeout(
context.Background(), s.shutdownTimeout)
defer cancel()
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())
}
}()
@@ -46,7 +47,7 @@ func (s *Server) Run(ctx context.Context, ready chan<- struct{}, done chan<- str
close(s.addressSet)
// 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)
err = server.Serve(listener)

View File

@@ -16,10 +16,12 @@ func Test_Server_Run_success(t *testing.T) {
ctrl := gomock.NewController(t)
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
server := &Server{
name: "test",
address: "127.0.0.1:0",
addressSet: make(chan struct{}),
logger: logger,
@@ -53,6 +55,7 @@ func Test_Server_Run_failure(t *testing.T) {
logger.EXPECT().Error("listen tcp: address -1: invalid port")
server := &Server{
name: "test",
address: "127.0.0.1:-1",
addressSet: make(chan struct{}),
logger: logger,

View File

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

View File

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

View File

@@ -5,6 +5,7 @@ import (
"fmt"
"net/http"
"os"
"strings"
"time"
"github.com/qdm12/gluetun/internal/configuration/settings/helpers"
@@ -13,6 +14,9 @@ import (
)
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.
// It defaults to :8000.
Address string
@@ -28,6 +32,7 @@ type Settings struct {
}
func (s *Settings) SetDefaults() {
s.Name = helpers.DefaultStringPtr(s.Name, "")
s.Address = helpers.DefaultString(s.Address, ":8000")
const defaultShutdownTimeout = 3 * time.Second
s.ShutdownTimeout = helpers.DefaultDuration(s.ShutdownTimeout, defaultShutdownTimeout)
@@ -35,6 +40,7 @@ func (s *Settings) SetDefaults() {
func (s Settings) Copy() Settings {
return Settings{
Name: helpers.CopyStringPtr(s.Name),
Address: s.Address,
Handler: s.Handler,
Logger: s.Logger,
@@ -43,6 +49,7 @@ func (s Settings) Copy() Settings {
}
func (s *Settings) MergeWith(other Settings) {
s.Name = helpers.MergeWithStringPtr(s.Name, other.Name)
s.Address = helpers.MergeWithString(s.Address, other.Address)
s.Handler = helpers.MergeWithHTTPHandler(s.Handler, other.Handler)
if s.Logger == nil {
@@ -52,6 +59,7 @@ func (s *Settings) MergeWith(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.Handler = helpers.OverrideWithHTTPHandler(s.Handler, other.Handler)
if other.Logger != nil {
@@ -92,7 +100,7 @@ func (s Settings) Validate() (err error) {
}
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("Shutdown timeout: %s", *s.ShutdownTimeout)
return node

View File

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

View File

@@ -28,98 +28,246 @@ func (a AllServers) GetCopy() (servers AllServers) {
return servers
}
func (a *AllServers) GetCyberghost() (servers []Server) {
return copyServers(a.Cyberghost.Servers)
}
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 {
func (a *AllServers) GetCyberghost() (servers []CyberghostServer) {
if a.Cyberghost.Servers == nil {
return nil
}
serversCopy = make([]Server, len(servers))
for i, server := range servers {
serversCopy[i] = server
serversCopy[i].IPs = copyIPs(server.IPs)
servers = make([]CyberghostServer, len(a.Cyberghost.Servers))
for i, serverToCopy := range a.Cyberghost.Servers {
servers[i] = serverToCopy
servers[i].IPs = copyIPs(serverToCopy.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) {

View File

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

View File

@@ -3,8 +3,6 @@ package models
import (
"fmt"
"strings"
"github.com/qdm12/gluetun/internal/constants/providers"
)
func boolToMarkdown(b bool) string {
@@ -16,126 +14,280 @@ func boolToMarkdown(b bool) string {
func markdownTableHeading(legendFields ...string) (markdown string) {
return "| " + strings.Join(legendFields, " | ") + " |\n" +
"|" + strings.Repeat(" --- |", len(legendFields))
"|" + strings.Repeat(" --- |", len(legendFields)) + "\n"
}
const (
vpnHeader = "VPN"
countryHeader = "Country"
regionHeader = "Region"
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 ""
func (s *CyberghostServers) ToMarkdown() (markdown string) {
markdown = markdownTableHeading("Country", "Hostname", "TCP", "UDP")
for _, server := range s.Servers {
markdown += server.ToMarkdown() + "\n"
}
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
}
func getMarkdownHeaders(vpnProvider string) (headers []string) {
switch vpnProvider {
case providers.Cyberghost:
return []string{countryHeader, hostnameHeader, tcpHeader, udpHeader}
case providers.Expressvpn:
return []string{countryHeader, cityHeader, hostnameHeader, tcpHeader, udpHeader}
case providers.Fastestvpn:
return []string{countryHeader, hostnameHeader, tcpHeader, udpHeader}
case providers.HideMyAss:
return []string{countryHeader, regionHeader, cityHeader, hostnameHeader, tcpHeader, udpHeader}
case providers.Ipvanish:
return []string{countryHeader, cityHeader, hostnameHeader, tcpHeader, udpHeader}
case providers.Ivpn:
return []string{countryHeader, cityHeader, ispHeader, hostnameHeader, vpnHeader, tcpHeader, udpHeader}
case providers.Mullvad:
return []string{countryHeader, cityHeader, ispHeader, ownedHeader, hostnameHeader, vpnHeader}
case providers.Nordvpn:
return []string{countryHeader, regionHeader, cityHeader, hostnameHeader}
case providers.Perfectprivacy:
return []string{cityHeader, tcpHeader, udpHeader}
case providers.Privado:
return []string{countryHeader, regionHeader, cityHeader, hostnameHeader}
case providers.PrivateInternetAccess:
return []string{regionHeader, hostnameHeader, tcpHeader, udpHeader, portForwardHeader}
case providers.Privatevpn:
return []string{countryHeader, cityHeader, hostnameHeader}
case providers.Protonvpn:
return []string{countryHeader, regionHeader, cityHeader, hostnameHeader, freeHeader}
case providers.Purevpn:
return []string{countryHeader, regionHeader, cityHeader, hostnameHeader, tcpHeader, udpHeader}
case providers.Surfshark:
return []string{regionHeader, countryHeader, cityHeader, hostnameHeader, multiHopHeader, tcpHeader, udpHeader}
case providers.Torguard:
return []string{countryHeader, cityHeader, hostnameHeader, tcpHeader, udpHeader}
case providers.VPNUnlimited:
return []string{countryHeader, cityHeader, hostnameHeader, freeHeader, streamHeader, tcpHeader, udpHeader}
case providers.Vyprvpn:
return []string{regionHeader, hostnameHeader, tcpHeader, udpHeader}
case providers.Wevpn:
return []string{cityHeader, hostnameHeader, tcpHeader, udpHeader}
case providers.Windscribe:
return []string{regionHeader, cityHeader, hostnameHeader, vpnHeader}
default:
return nil
}
func (s CyberghostServer) ToMarkdown() (markdown string) {
return fmt.Sprintf("| %s | `%s` | %s | %s |", s.Country, s.Hostname,
boolToMarkdown(s.TCP), boolToMarkdown(s.UDP))
}
func (s *ExpressvpnServers) ToMarkdown() (markdown string) {
markdown = markdownTableHeading("Country", "City", "Hostname", "TCP", "UDP")
for _, server := range s.Servers {
markdown += server.ToMarkdown() + "\n"
}
return markdown
}
func (s *ExpressvpnServer) 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 *FastestvpnServers) ToMarkdown() (markdown string) {
markdown = markdownTableHeading("Country", "Hostname", "TCP", "UDP")
for _, server := range s.Servers {
markdown += server.ToMarkdown() + "\n"
}
return markdown
}
func (s *FastestvpnServer) ToMarkdown() (markdown string) {
return fmt.Sprintf("| %s | `%s` | %s | %s |",
s.Country, s.Hostname, boolToMarkdown(s.TCP), boolToMarkdown(s.UDP))
}
func (s *HideMyAssServers) ToMarkdown() (markdown string) {
markdown = markdownTableHeading("Country", "Region", "City", "Hostname", "TCP", "UDP")
for _, server := range s.Servers {
markdown += server.ToMarkdown() + "\n"
}
return markdown
}
func (s *HideMyAssServer) 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 *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 (
"testing"
"github.com/qdm12/gluetun/internal/constants/providers"
"github.com/stretchr/testify/assert"
)
func Test_Servers_ToMarkdown(t *testing.T) {
func Test_CyberghostServers_ToMarkdown(t *testing.T) {
t.Parallel()
testCases := map[string]struct {
provider string
servers Servers
expectedMarkdown string
}{
providers.Cyberghost: {
provider: providers.Cyberghost,
servers: Servers{
Servers: []Server{
{Country: "a", UDP: true, Hostname: "xa"},
{Country: "b", TCP: true, Hostname: "xb"},
},
},
expectedMarkdown: "| Country | Hostname | TCP | UDP |\n" +
"| --- | --- | --- | --- |\n" +
"| a | `xa` | ❌ | ✅ |\n" +
"| b | `xb` | ✅ | ❌ |\n",
},
providers.Fastestvpn: {
provider: providers.Fastestvpn,
servers: Servers{
Servers: []Server{
{Country: "a", Hostname: "xa", TCP: true},
{Country: "b", Hostname: "xb", UDP: true},
},
},
expectedMarkdown: "| Country | Hostname | TCP | UDP |\n" +
"| --- | --- | --- | --- |\n" +
"| a | `xa` | ✅ | ❌ |\n" +
"| b | `xb` | ❌ | ✅ |\n",
servers := CyberghostServers{
Servers: []CyberghostServer{
{Country: "a", UDP: true, Hostname: "xa"},
{Country: "b", TCP: true, Hostname: "xb"},
},
}
for name, testCase := range testCases {
testCase := testCase
t.Run(name, func(t *testing.T) {
t.Parallel()
markdown := servers.ToMarkdown()
const expected = "| Country | Hostname | TCP | UDP |\n" +
"| --- | --- | --- | --- |\n" +
"| a | `xa` | ❌ | ✅ |\n" +
"| b | `xb` | ✅ | ❌ |\n"
markdown := testCase.servers.ToMarkdown(testCase.provider)
assert.Equal(t, testCase.expectedMarkdown, markdown)
})
}
assert.Equal(t, expected, markdown)
}
func Test_FastestvpnServers_ToMarkdown(t *testing.T) {
t.Parallel()
servers := FastestvpnServers{
Servers: []FastestvpnServer{
{Country: "a", Hostname: "xa", TCP: true},
{Country: "b", Hostname: "xb", UDP: true},
},
}
markdown := servers.ToMarkdown()
const expected = "| Country | Hostname | TCP | UDP |\n" +
"| --- | --- | --- | --- |\n" +
"| a | `xa` | ✅ | ❌ |\n" +
"| b | `xb` | ❌ | ✅ |\n"
assert.Equal(t, expected, markdown)
}

View File

@@ -4,25 +4,189 @@ import (
"net"
)
type Server struct {
VPN string `json:"vpn,omitempty"`
// Surfshark: country is also used for multi-hop
Country string `json:"country,omitempty"`
Region string `json:"region,omitempty"`
City string `json:"city,omitempty"`
ISP string `json:"isp,omitempty"`
Owned bool `json:"owned,omitempty"`
Number uint16 `json:"number,omitempty"`
ServerName string `json:"server_name,omitempty"`
Hostname string `json:"hostname,omitempty"`
TCP bool `json:"tcp,omitempty"`
UDP bool `json:"udp,omitempty"`
OvpnX509 string `json:"x509,omitempty"`
RetroLoc string `json:"retroloc,omitempty"` // TODO remove in v4
MultiHop bool `json:"multihop,omitempty"`
WgPubKey string `json:"wgpubkey,omitempty"`
Free bool `json:"free,omitempty"`
Stream bool `json:"stream,omitempty"`
PortForward bool `json:"port_forward,omitempty"`
IPs []net.IP `json:"ips,omitempty"`
type CyberghostServer struct {
Country string `json:"country"`
Hostname string `json:"hostname"`
TCP bool `json:"tcp"`
UDP bool `json:"udp"`
IPs []net.IP `json:"ips"`
}
type ExpressvpnServer struct {
Country string `json:"country"`
City string `json:"city,omitempty"`
Hostname string `json:"hostname"`
TCP bool `json:"tcp"`
UDP bool `json:"udp"`
IPs []net.IP `json:"ips"`
}
type FastestvpnServer struct {
Hostname string `json:"hostname"`
TCP bool `json:"tcp"`
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

@@ -1,27 +1,27 @@
package models
type AllServers struct {
Version uint16 `json:"version"` // used for migration of the top level scheme
Cyberghost Servers `json:"cyberghost"`
Expressvpn Servers `json:"expressvpn"`
Fastestvpn Servers `json:"fastestvpn"`
HideMyAss Servers `json:"hidemyass"`
Ipvanish Servers `json:"ipvanish"`
Ivpn Servers `json:"ivpn"`
Mullvad Servers `json:"mullvad"`
Perfectprivacy Servers `json:"perfectprivacy"`
Nordvpn Servers `json:"nordvpn"`
Privado Servers `json:"privado"`
Pia Servers `json:"pia"`
Privatevpn Servers `json:"privatevpn"`
Protonvpn Servers `json:"protonvpn"`
Purevpn Servers `json:"purevpn"`
Surfshark Servers `json:"surfshark"`
Torguard Servers `json:"torguard"`
VPNUnlimited Servers `json:"vpnunlimited"`
Vyprvpn Servers `json:"vyprvpn"`
Wevpn Servers `json:"wevpn"`
Windscribe Servers `json:"windscribe"`
Version uint16 `json:"version"` // used for migration of the top level scheme
Cyberghost CyberghostServers `json:"cyberghost"`
Expressvpn ExpressvpnServers `json:"expressvpn"`
Fastestvpn FastestvpnServers `json:"fastestvpn"`
HideMyAss HideMyAssServers `json:"hidemyass"`
Ipvanish IpvanishServers `json:"ipvanish"`
Ivpn IvpnServers `json:"ivpn"`
Mullvad MullvadServers `json:"mullvad"`
Perfectprivacy PerfectprivacyServers `json:"perfectprivacy"`
Nordvpn NordvpnServers `json:"nordvpn"`
Privado PrivadoServers `json:"privado"`
Pia PiaServers `json:"pia"`
Privatevpn PrivatevpnServers `json:"privatevpn"`
Protonvpn ProtonvpnServers `json:"protonvpn"`
Purevpn PurevpnServers `json:"purevpn"`
Surfshark SurfsharkServers `json:"surfshark"`
Torguard TorguardServers `json:"torguard"`
VPNUnlimited VPNUnlimitedServers `json:"vpnunlimited"`
Vyprvpn VyprvpnServers `json:"vyprvpn"`
Wevpn WevpnServers `json:"wevpn"`
Windscribe WindscribeServers `json:"windscribe"`
}
func (a *AllServers) Count() int {
@@ -47,8 +47,103 @@ func (a *AllServers) Count() int {
len(a.Windscribe.Servers)
}
type Servers struct {
Version uint16 `json:"version"`
Timestamp int64 `json:"timestamp"`
Servers []Server `json:"servers"`
type CyberghostServers struct {
Version uint16 `json:"version"`
Timestamp int64 `json:"timestamp"`
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
stopped := false
for stayHere {
select {
case <-ctx.Done():
pfCancel()
if stopped {
return
}
<-errorCh
close(errorCh)
close(portCh)
@@ -81,7 +77,6 @@ func (l *Loop) Run(ctx context.Context, done chan<- struct{}) {
l.firewallBlockPort(ctx)
l.state.SetPortForwarded(0)
l.stopped <- struct{}{}
stopped = true
case port := <-portCh:
l.logger.Info("port forwarded is " + strconv.Itoa(int(port)))
l.firewallBlockPort(ctx)

View File

@@ -8,6 +8,7 @@ import (
)
func boolPtr(b bool) *bool { return &b }
func stringPtr(s string) *string { return &s }
func durationPtr(d time.Duration) *time.Duration { return &d }
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/heap", pprof.Handler("heap"))
handler.Handle("/debug/pprof/threadcreate", pprof.Handler("threadcreate"))
httpServerName := "pprof"
settings.HTTPServer.Name = &httpServerName
settings.HTTPServer.Handler = handler
settings.SetDefaults()

View File

@@ -22,7 +22,8 @@ func Test_Server(t *testing.T) {
const address = "127.0.0.1:0"
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
settings := Settings{

View File

@@ -26,6 +26,7 @@ type Settings struct {
func (s *Settings) SetDefaults() {
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.SetDefaults()
}

View File

@@ -21,6 +21,7 @@ func Test_Settings_SetDefaults(t *testing.T) {
expected: Settings{
Enabled: boolPtr(false),
HTTPServer: httpserver.Settings{
Name: stringPtr("pprof"),
Address: "localhost:6060",
ShutdownTimeout: durationPtr(3 * time.Second),
},
@@ -32,6 +33,7 @@ func Test_Settings_SetDefaults(t *testing.T) {
BlockProfileRate: 1,
MutexProfileRate: 1,
HTTPServer: httpserver.Settings{
Name: stringPtr("custom"),
Address: ":6061",
ShutdownTimeout: durationPtr(time.Second),
},
@@ -41,6 +43,7 @@ func Test_Settings_SetDefaults(t *testing.T) {
BlockProfileRate: 1,
MutexProfileRate: 1,
HTTPServer: httpserver.Settings{
Name: stringPtr("custom"),
Address: ":6061",
ShutdownTimeout: durationPtr(time.Second),
},
@@ -74,6 +77,7 @@ func Test_Settings_Copy(t *testing.T) {
BlockProfileRate: 1,
MutexProfileRate: 1,
HTTPServer: httpserver.Settings{
Name: stringPtr("custom"),
Address: ":6061",
ShutdownTimeout: durationPtr(time.Second),
},
@@ -83,6 +87,7 @@ func Test_Settings_Copy(t *testing.T) {
BlockProfileRate: 1,
MutexProfileRate: 1,
HTTPServer: httpserver.Settings{
Name: stringPtr("custom"),
Address: ":6061",
ShutdownTimeout: durationPtr(time.Second),
},
@@ -320,6 +325,7 @@ func Test_Settings_String(t *testing.T) {
BlockProfileRate: 2,
MutexProfileRate: 1,
HTTPServer: httpserver.Settings{
Name: stringPtr("name"),
Address: ":8000",
ShutdownTimeout: durationPtr(time.Second),
},
@@ -327,7 +333,7 @@ func Test_Settings_String(t *testing.T) {
s: `Pprof settings:
├── Block profile rate: 2
├── Mutex profile rate: 1
└── HTTP server settings:
└── Name HTTP server settings:
├── Listening address: :8000
└── Shutdown timeout: 1s`,
},

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